| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * EFI Human Interface Infrastructure ... database and packages | |
| * | |
| * Copyright (c) 2017 Leif Lindholm | |
| * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited | |
| */ | |
| #define LOG_CATEGORY LOGC_EFI | |
| #include <efi_loader.h> | |
| #include <malloc.h> | |
| #include <asm/unaligned.h> | |
| const efi_guid_t efi_guid_hii_database_protocol | |
| = EFI_HII_DATABASE_PROTOCOL_GUID; | |
| const efi_guid_t efi_guid_hii_string_protocol = EFI_HII_STRING_PROTOCOL_GUID; | |
| static LIST_HEAD(efi_package_lists); | |
| static LIST_HEAD(efi_keyboard_layout_list); | |
| struct efi_hii_packagelist { | |
| struct list_head link; | |
| // TODO should there be an associated efi_object? | |
| efi_handle_t driver_handle; | |
| u32 max_string_id; | |
| struct list_head string_tables; /* list of efi_string_table */ | |
| struct list_head guid_list; | |
| struct list_head keyboard_packages; | |
| /* we could also track fonts, images, etc */ | |
| }; | |
| static int efi_hii_packagelist_exists(efi_hii_handle_t package_list) | |
| { | |
| struct efi_hii_packagelist *hii; | |
| int found = 0; | |
| list_for_each_entry(hii, &efi_package_lists, link) { | |
| if (hii == package_list) { | |
| found = 1; | |
| break; | |
| } | |
| } | |
| return found; | |
| } | |
| static u32 efi_hii_package_type(struct efi_hii_package_header *header) | |
| { | |
| u32 fields; | |
| fields = get_unaligned_le32(&header->fields); | |
| return (fields >> __EFI_HII_PACKAGE_TYPE_SHIFT) | |
| & __EFI_HII_PACKAGE_TYPE_MASK; | |
| } | |
| static u32 efi_hii_package_len(struct efi_hii_package_header *header) | |
| { | |
| u32 fields; | |
| fields = get_unaligned_le32(&header->fields); | |
| return (fields >> __EFI_HII_PACKAGE_LEN_SHIFT) | |
| & __EFI_HII_PACKAGE_LEN_MASK; | |
| } | |
| struct efi_string_info { | |
| efi_string_t string; | |
| /* we could also track font info, etc */ | |
| }; | |
| struct efi_string_table { | |
| struct list_head link; | |
| efi_string_id_t language_name; | |
| char *language; | |
| u32 nstrings; | |
| /* | |
| * NOTE: | |
| * string id starts at 1 so value is stbl->strings[id-1], | |
| * and strings[] is a array of stbl->nstrings elements | |
| */ | |
| struct efi_string_info *strings; | |
| }; | |
| struct efi_guid_data { | |
| struct list_head link; | |
| struct efi_hii_guid_package package; | |
| }; | |
| struct efi_keyboard_layout_data { | |
| struct list_head link; /* in package */ | |
| struct list_head link_sys; /* in global list */ | |
| struct efi_hii_keyboard_layout keyboard_layout; | |
| }; | |
| struct efi_keyboard_package_data { | |
| struct list_head link; /* in package_list */ | |
| struct list_head keyboard_layout_list; | |
| }; | |
| static void free_strings_table(struct efi_string_table *stbl) | |
| { | |
| int i; | |
| for (i = 0; i < stbl->nstrings; i++) | |
| free(stbl->strings[i].string); | |
| free(stbl->strings); | |
| free(stbl->language); | |
| free(stbl); | |
| } | |
| static void remove_strings_package(struct efi_hii_packagelist *hii) | |
| { | |
| while (!list_empty(&hii->string_tables)) { | |
| struct efi_string_table *stbl; | |
| stbl = list_first_entry(&hii->string_tables, | |
| struct efi_string_table, link); | |
| list_del(&stbl->link); | |
| free_strings_table(stbl); | |
| } | |
| } | |
| static efi_status_t | |
| add_strings_package(struct efi_hii_packagelist *hii, | |
| struct efi_hii_strings_package *strings_package) | |
| { | |
| struct efi_hii_string_block *block; | |
| void *end; | |
| u32 nstrings = 0, idx = 0; | |
| struct efi_string_table *stbl = NULL; | |
| efi_status_t ret; | |
| EFI_PRINT("header_size: %08x\n", | |
| get_unaligned_le32(&strings_package->header_size)); | |
| EFI_PRINT("string_info_offset: %08x\n", | |
| get_unaligned_le32(&strings_package->string_info_offset)); | |
| EFI_PRINT("language_name: %u\n", | |
| get_unaligned_le16(&strings_package->language_name)); | |
| EFI_PRINT("language: %s\n", strings_package->language); | |
| /* count # of string entries: */ | |
| end = ((void *)strings_package) | |
| + efi_hii_package_len(&strings_package->header); | |
| block = ((void *)strings_package) | |
| + get_unaligned_le32(&strings_package->string_info_offset); | |
| while ((void *)block < end) { | |
| switch (block->block_type) { | |
| case EFI_HII_SIBT_STRING_UCS2: { | |
| struct efi_hii_sibt_string_ucs2_block *ucs2; | |
| ucs2 = (void *)block; | |
| nstrings++; | |
| block = efi_hii_sibt_string_ucs2_block_next(ucs2); | |
| break; | |
| } | |
| case EFI_HII_SIBT_END: | |
| block = end; | |
| break; | |
| default: | |
| EFI_PRINT("unknown HII string block type: %02x\n", | |
| block->block_type); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| } | |
| stbl = calloc(sizeof(*stbl), 1); | |
| if (!stbl) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto error; | |
| } | |
| stbl->strings = calloc(sizeof(stbl->strings[0]), nstrings); | |
| if (!stbl->strings) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto error; | |
| } | |
| stbl->language_name = | |
| get_unaligned_le16(&strings_package->language_name); | |
| stbl->language = strdup((char *)strings_package->language); | |
| if (!stbl->language) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto error; | |
| } | |
| stbl->nstrings = nstrings; | |
| /* and now parse string entries and populate efi_string_table */ | |
| block = ((void *)strings_package) | |
| + get_unaligned_le32(&strings_package->string_info_offset); | |
| while ((void *)block < end) { | |
| switch (block->block_type) { | |
| case EFI_HII_SIBT_STRING_UCS2: { | |
| struct efi_hii_sibt_string_ucs2_block *ucs2; | |
| ucs2 = (void *)block; | |
| EFI_PRINT("%4u: \"%ls\"\n", idx + 1, ucs2->string_text); | |
| stbl->strings[idx].string = | |
| u16_strdup(ucs2->string_text); | |
| if (!stbl->strings[idx].string) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto error; | |
| } | |
| idx++; | |
| /* FIXME: accessing u16 * here */ | |
| block = efi_hii_sibt_string_ucs2_block_next(ucs2); | |
| break; | |
| } | |
| case EFI_HII_SIBT_END: | |
| goto out; | |
| default: | |
| EFI_PRINT("unknown HII string block type: %02x\n", | |
| block->block_type); | |
| ret = EFI_INVALID_PARAMETER; | |
| goto error; | |
| } | |
| } | |
| out: | |
| list_add(&stbl->link, &hii->string_tables); | |
| if (hii->max_string_id < nstrings) | |
| hii->max_string_id = nstrings; | |
| return EFI_SUCCESS; | |
| error: | |
| if (stbl) { | |
| free(stbl->language); | |
| while (idx > 0) | |
| free(stbl->strings[--idx].string); | |
| free(stbl->strings); | |
| } | |
| free(stbl); | |
| return ret; | |
| } | |
| static void remove_guid_package(struct efi_hii_packagelist *hii) | |
| { | |
| struct efi_guid_data *data; | |
| while (!list_empty(&hii->guid_list)) { | |
| data = list_first_entry(&hii->guid_list, | |
| struct efi_guid_data, link); | |
| list_del(&data->link); | |
| free(data); | |
| } | |
| } | |
| static efi_status_t | |
| add_guid_package(struct efi_hii_packagelist *hii, | |
| struct efi_hii_guid_package *package) | |
| { | |
| struct efi_guid_data *data; | |
| data = calloc(sizeof(*data), 1); | |
| if (!data) | |
| return EFI_OUT_OF_RESOURCES; | |
| /* TODO: we don't know any about data field */ | |
| memcpy(&data->package, package, sizeof(*package)); | |
| list_add_tail(&data->link, &hii->guid_list); | |
| return EFI_SUCCESS; | |
| } | |
| static void free_keyboard_layouts(struct efi_keyboard_package_data *package) | |
| { | |
| struct efi_keyboard_layout_data *layout_data; | |
| while (!list_empty(&package->keyboard_layout_list)) { | |
| layout_data = list_first_entry(&package->keyboard_layout_list, | |
| struct efi_keyboard_layout_data, | |
| link); | |
| list_del(&layout_data->link); | |
| list_del(&layout_data->link_sys); | |
| free(layout_data); | |
| } | |
| } | |
| static void remove_keyboard_package(struct efi_hii_packagelist *hii) | |
| { | |
| struct efi_keyboard_package_data *package; | |
| while (!list_empty(&hii->keyboard_packages)) { | |
| package = list_first_entry(&hii->keyboard_packages, | |
| struct efi_keyboard_package_data, | |
| link); | |
| free_keyboard_layouts(package); | |
| list_del(&package->link); | |
| free(package); | |
| } | |
| } | |
| static efi_status_t | |
| add_keyboard_package(struct efi_hii_packagelist *hii, | |
| struct efi_hii_keyboard_package *keyboard_package) | |
| { | |
| struct efi_keyboard_package_data *package_data; | |
| struct efi_hii_keyboard_layout *layout; | |
| struct efi_keyboard_layout_data *layout_data; | |
| u16 layout_count, layout_length; | |
| int i; | |
| package_data = malloc(sizeof(*package_data)); | |
| if (!package_data) | |
| return EFI_OUT_OF_RESOURCES; | |
| INIT_LIST_HEAD(&package_data->link); | |
| INIT_LIST_HEAD(&package_data->keyboard_layout_list); | |
| layout = &keyboard_package->layout[0]; | |
| layout_count = get_unaligned_le16(&keyboard_package->layout_count); | |
| for (i = 0; i < layout_count; i++) { | |
| layout_length = get_unaligned_le16(&layout->layout_length); | |
| layout_data = malloc(sizeof(*layout_data) + layout_length); | |
| if (!layout_data) | |
| goto out; | |
| memcpy(&layout_data->keyboard_layout, layout, layout_length); | |
| list_add_tail(&layout_data->link, | |
| &package_data->keyboard_layout_list); | |
| list_add_tail(&layout_data->link_sys, | |
| &efi_keyboard_layout_list); | |
| layout += layout_length; | |
| } | |
| list_add_tail(&package_data->link, &hii->keyboard_packages); | |
| return EFI_SUCCESS; | |
| out: | |
| free_keyboard_layouts(package_data); | |
| free(package_data); | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| static struct efi_hii_packagelist *new_packagelist(void) | |
| { | |
| struct efi_hii_packagelist *hii; | |
| hii = malloc(sizeof(*hii)); | |
| list_add_tail(&hii->link, &efi_package_lists); | |
| hii->max_string_id = 0; | |
| INIT_LIST_HEAD(&hii->string_tables); | |
| INIT_LIST_HEAD(&hii->guid_list); | |
| INIT_LIST_HEAD(&hii->keyboard_packages); | |
| return hii; | |
| } | |
| static void free_packagelist(struct efi_hii_packagelist *hii) | |
| { | |
| remove_strings_package(hii); | |
| remove_guid_package(hii); | |
| remove_keyboard_package(hii); | |
| list_del(&hii->link); | |
| free(hii); | |
| } | |
| static efi_status_t | |
| add_packages(struct efi_hii_packagelist *hii, | |
| const struct efi_hii_package_list_header *package_list) | |
| { | |
| struct efi_hii_package_header *package; | |
| void *end; | |
| efi_status_t ret = EFI_SUCCESS; | |
| end = ((void *)package_list) | |
| + get_unaligned_le32(&package_list->package_length); | |
| EFI_PRINT("package_list: %pUs (%u)\n", &package_list->package_list_guid, | |
| get_unaligned_le32(&package_list->package_length)); | |
| package = ((void *)package_list) + sizeof(*package_list); | |
| while ((void *)package < end) { | |
| EFI_PRINT("package=%p, package type=%x, length=%u\n", package, | |
| efi_hii_package_type(package), | |
| efi_hii_package_len(package)); | |
| switch (efi_hii_package_type(package)) { | |
| case EFI_HII_PACKAGE_TYPE_GUID: | |
| ret = add_guid_package(hii, | |
| (struct efi_hii_guid_package *)package); | |
| break; | |
| case EFI_HII_PACKAGE_FORMS: | |
| EFI_PRINT("Form package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_STRINGS: | |
| ret = add_strings_package(hii, | |
| (struct efi_hii_strings_package *)package); | |
| break; | |
| case EFI_HII_PACKAGE_FONTS: | |
| EFI_PRINT("Font package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_IMAGES: | |
| EFI_PRINT("Image package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_SIMPLE_FONTS: | |
| EFI_PRINT("Simple font package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_DEVICE_PATH: | |
| EFI_PRINT("Device path package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: | |
| ret = add_keyboard_package(hii, | |
| (struct efi_hii_keyboard_package *)package); | |
| break; | |
| case EFI_HII_PACKAGE_ANIMATIONS: | |
| EFI_PRINT("Animation package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_END: | |
| goto out; | |
| case EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN: | |
| case EFI_HII_PACKAGE_TYPE_SYSTEM_END: | |
| default: | |
| break; | |
| } | |
| if (ret != EFI_SUCCESS) | |
| return ret; | |
| package = (void *)package + efi_hii_package_len(package); | |
| } | |
| out: | |
| // TODO in theory there is some notifications that should be sent.. | |
| return EFI_SUCCESS; | |
| } | |
| /* | |
| * EFI_HII_DATABASE_PROTOCOL | |
| */ | |
| static efi_status_t EFIAPI | |
| new_package_list(const struct efi_hii_database_protocol *this, | |
| const struct efi_hii_package_list_header *package_list, | |
| const efi_handle_t driver_handle, | |
| efi_hii_handle_t *handle) | |
| { | |
| struct efi_hii_packagelist *hii; | |
| efi_status_t ret; | |
| EFI_ENTRY("%p, %p, %p, %p", this, package_list, driver_handle, handle); | |
| if (!package_list || !handle) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| hii = new_packagelist(); | |
| if (!hii) | |
| return EFI_EXIT(EFI_OUT_OF_RESOURCES); | |
| ret = add_packages(hii, package_list); | |
| if (ret != EFI_SUCCESS) { | |
| free_packagelist(hii); | |
| return EFI_EXIT(ret); | |
| } | |
| hii->driver_handle = driver_handle; | |
| *handle = hii; | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } | |
| static efi_status_t EFIAPI | |
| remove_package_list(const struct efi_hii_database_protocol *this, | |
| efi_hii_handle_t handle) | |
| { | |
| struct efi_hii_packagelist *hii = handle; | |
| EFI_ENTRY("%p, %p", this, handle); | |
| if (!handle || !efi_hii_packagelist_exists(handle)) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| free_packagelist(hii); | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } | |
| static efi_status_t EFIAPI | |
| update_package_list(const struct efi_hii_database_protocol *this, | |
| efi_hii_handle_t handle, | |
| const struct efi_hii_package_list_header *package_list) | |
| { | |
| struct efi_hii_packagelist *hii = handle; | |
| struct efi_hii_package_header *package; | |
| void *end; | |
| efi_status_t ret = EFI_SUCCESS; | |
| EFI_ENTRY("%p, %p, %p", this, handle, package_list); | |
| if (!handle || !efi_hii_packagelist_exists(handle)) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| if (!package_list) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| EFI_PRINT("package_list: %pUs (%u)\n", &package_list->package_list_guid, | |
| get_unaligned_le32(&package_list->package_length)); | |
| package = ((void *)package_list) + sizeof(*package_list); | |
| end = ((void *)package_list) | |
| + get_unaligned_le32(&package_list->package_length); | |
| while ((void *)package < end) { | |
| EFI_PRINT("package=%p, package type=%x, length=%u\n", package, | |
| efi_hii_package_type(package), | |
| efi_hii_package_len(package)); | |
| switch (efi_hii_package_type(package)) { | |
| case EFI_HII_PACKAGE_TYPE_GUID: | |
| remove_guid_package(hii); | |
| break; | |
| case EFI_HII_PACKAGE_FORMS: | |
| EFI_PRINT("Form package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_STRINGS: | |
| remove_strings_package(hii); | |
| break; | |
| case EFI_HII_PACKAGE_FONTS: | |
| EFI_PRINT("Font package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_IMAGES: | |
| EFI_PRINT("Image package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_SIMPLE_FONTS: | |
| EFI_PRINT("Simple font package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_DEVICE_PATH: | |
| EFI_PRINT("Device path package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: | |
| remove_keyboard_package(hii); | |
| break; | |
| case EFI_HII_PACKAGE_ANIMATIONS: | |
| EFI_PRINT("Animation package not supported\n"); | |
| ret = EFI_INVALID_PARAMETER; | |
| break; | |
| case EFI_HII_PACKAGE_END: | |
| goto out; | |
| case EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN: | |
| case EFI_HII_PACKAGE_TYPE_SYSTEM_END: | |
| default: | |
| break; | |
| } | |
| /* TODO: already removed some packages */ | |
| if (ret != EFI_SUCCESS) | |
| return EFI_EXIT(ret); | |
| package = ((void *)package) | |
| + efi_hii_package_len(package); | |
| } | |
| out: | |
| ret = add_packages(hii, package_list); | |
| return EFI_EXIT(ret); | |
| } | |
| static efi_status_t EFIAPI | |
| list_package_lists(const struct efi_hii_database_protocol *this, | |
| u8 package_type, | |
| const efi_guid_t *package_guid, | |
| efi_uintn_t *handle_buffer_length, | |
| efi_hii_handle_t *handle) | |
| { | |
| struct efi_hii_packagelist *hii = | |
| (struct efi_hii_packagelist *)handle; | |
| int package_cnt, package_max; | |
| efi_status_t ret = EFI_NOT_FOUND; | |
| EFI_ENTRY("%p, %u, %pUs, %p, %p", this, package_type, package_guid, | |
| handle_buffer_length, handle); | |
| if (!handle_buffer_length || | |
| (*handle_buffer_length && !handle)) { | |
| ret = EFI_INVALID_PARAMETER; | |
| goto out; | |
| } | |
| if ((package_type != EFI_HII_PACKAGE_TYPE_GUID && package_guid) || | |
| (package_type == EFI_HII_PACKAGE_TYPE_GUID && !package_guid)) { | |
| ret = EFI_INVALID_PARAMETER; | |
| goto out; | |
| } | |
| EFI_PRINT("package type=%x, guid=%pUs, length=%zu\n", (int)package_type, | |
| package_guid, *handle_buffer_length); | |
| package_cnt = 0; | |
| package_max = *handle_buffer_length / sizeof(*handle); | |
| list_for_each_entry(hii, &efi_package_lists, link) { | |
| switch (package_type) { | |
| case EFI_HII_PACKAGE_TYPE_ALL: | |
| break; | |
| case EFI_HII_PACKAGE_TYPE_GUID: | |
| if (!list_empty(&hii->guid_list)) | |
| break; | |
| continue; | |
| case EFI_HII_PACKAGE_STRINGS: | |
| if (!list_empty(&hii->string_tables)) | |
| break; | |
| continue; | |
| case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: | |
| if (!list_empty(&hii->keyboard_packages)) | |
| break; | |
| continue; | |
| default: | |
| continue; | |
| } | |
| package_cnt++; | |
| if (package_cnt <= package_max) { | |
| *handle++ = hii; | |
| ret = EFI_SUCCESS; | |
| } else { | |
| ret = EFI_BUFFER_TOO_SMALL; | |
| } | |
| } | |
| *handle_buffer_length = package_cnt * sizeof(*handle); | |
| out: | |
| return EFI_EXIT(ret); | |
| } | |
| static efi_status_t EFIAPI | |
| export_package_lists(const struct efi_hii_database_protocol *this, | |
| efi_hii_handle_t handle, | |
| efi_uintn_t *buffer_size, | |
| struct efi_hii_package_list_header *buffer) | |
| { | |
| EFI_ENTRY("%p, %p, %p, %p", this, handle, buffer_size, buffer); | |
| if (!buffer_size || !buffer) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| } | |
| static efi_status_t EFIAPI | |
| register_package_notify(const struct efi_hii_database_protocol *this, | |
| u8 package_type, | |
| const efi_guid_t *package_guid, | |
| const void *package_notify_fn, | |
| efi_uintn_t notify_type, | |
| efi_handle_t *notify_handle) | |
| { | |
| EFI_ENTRY("%p, %u, %pUs, %p, %zu, %p", this, package_type, | |
| package_guid, package_notify_fn, notify_type, | |
| notify_handle); | |
| if (!notify_handle) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| if ((package_type != EFI_HII_PACKAGE_TYPE_GUID && package_guid) || | |
| (package_type == EFI_HII_PACKAGE_TYPE_GUID && !package_guid)) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| return EFI_EXIT(EFI_OUT_OF_RESOURCES); | |
| } | |
| static efi_status_t EFIAPI | |
| unregister_package_notify(const struct efi_hii_database_protocol *this, | |
| efi_handle_t notification_handle) | |
| { | |
| EFI_ENTRY("%p, %p", this, notification_handle); | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| } | |
| static efi_status_t EFIAPI | |
| find_keyboard_layouts(const struct efi_hii_database_protocol *this, | |
| u16 *key_guid_buffer_length, | |
| efi_guid_t *key_guid_buffer) | |
| { | |
| struct efi_keyboard_layout_data *layout_data; | |
| int package_cnt, package_max; | |
| efi_status_t ret = EFI_SUCCESS; | |
| EFI_ENTRY("%p, %p, %p", this, key_guid_buffer_length, key_guid_buffer); | |
| if (!key_guid_buffer_length || | |
| (*key_guid_buffer_length && !key_guid_buffer)) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| package_cnt = 0; | |
| package_max = *key_guid_buffer_length / sizeof(*key_guid_buffer); | |
| list_for_each_entry(layout_data, &efi_keyboard_layout_list, link_sys) { | |
| package_cnt++; | |
| if (package_cnt <= package_max) | |
| memcpy(key_guid_buffer++, | |
| &layout_data->keyboard_layout.guid, | |
| sizeof(*key_guid_buffer)); | |
| else | |
| ret = EFI_BUFFER_TOO_SMALL; | |
| } | |
| *key_guid_buffer_length = package_cnt * sizeof(*key_guid_buffer); | |
| return EFI_EXIT(ret); | |
| } | |
| static efi_status_t EFIAPI | |
| get_keyboard_layout(const struct efi_hii_database_protocol *this, | |
| efi_guid_t *key_guid, | |
| u16 *keyboard_layout_length, | |
| struct efi_hii_keyboard_layout *keyboard_layout) | |
| { | |
| struct efi_keyboard_layout_data *layout_data; | |
| u16 layout_length; | |
| EFI_ENTRY("%p, %pUs, %p, %p", this, key_guid, keyboard_layout_length, | |
| keyboard_layout); | |
| if (!keyboard_layout_length || | |
| (*keyboard_layout_length && !keyboard_layout)) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| /* TODO: no notion of current keyboard layout */ | |
| if (!key_guid) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| list_for_each_entry(layout_data, &efi_keyboard_layout_list, link_sys) { | |
| if (!guidcmp(&layout_data->keyboard_layout.guid, key_guid)) | |
| goto found; | |
| } | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| found: | |
| layout_length = | |
| get_unaligned_le16(&layout_data->keyboard_layout.layout_length); | |
| if (*keyboard_layout_length < layout_length) { | |
| *keyboard_layout_length = layout_length; | |
| return EFI_EXIT(EFI_BUFFER_TOO_SMALL); | |
| } | |
| memcpy(keyboard_layout, &layout_data->keyboard_layout, layout_length); | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } | |
| static efi_status_t EFIAPI | |
| set_keyboard_layout(const struct efi_hii_database_protocol *this, | |
| efi_guid_t *key_guid) | |
| { | |
| EFI_ENTRY("%p, %pUs", this, key_guid); | |
| if (!key_guid) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| } | |
| static efi_status_t EFIAPI | |
| get_package_list_handle(const struct efi_hii_database_protocol *this, | |
| efi_hii_handle_t package_list_handle, | |
| efi_handle_t *driver_handle) | |
| { | |
| struct efi_hii_packagelist *hii; | |
| EFI_ENTRY("%p, %p, %p", this, package_list_handle, driver_handle); | |
| if (!driver_handle) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| list_for_each_entry(hii, &efi_package_lists, link) { | |
| if (hii == package_list_handle) { | |
| *driver_handle = hii->driver_handle; | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } | |
| } | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| } | |
| const struct efi_hii_database_protocol efi_hii_database = { | |
| .new_package_list = new_package_list, | |
| .remove_package_list = remove_package_list, | |
| .update_package_list = update_package_list, | |
| .list_package_lists = list_package_lists, | |
| .export_package_lists = export_package_lists, | |
| .register_package_notify = register_package_notify, | |
| .unregister_package_notify = unregister_package_notify, | |
| .find_keyboard_layouts = find_keyboard_layouts, | |
| .get_keyboard_layout = get_keyboard_layout, | |
| .set_keyboard_layout = set_keyboard_layout, | |
| .get_package_list_handle = get_package_list_handle | |
| }; | |
| /* | |
| * EFI_HII_STRING_PROTOCOL | |
| */ | |
| static bool language_match(char *language, char *languages) | |
| { | |
| size_t n; | |
| n = strlen(language); | |
| /* match primary language? */ | |
| if (!strncasecmp(language, languages, n) && | |
| (languages[n] == ';' || languages[n] == '\0')) | |
| return true; | |
| return false; | |
| } | |
| static efi_status_t EFIAPI | |
| new_string(const struct efi_hii_string_protocol *this, | |
| efi_hii_handle_t package_list, | |
| efi_string_id_t *string_id, | |
| const u8 *language, | |
| const u16 *language_name, | |
| const efi_string_t string, | |
| const struct efi_font_info *string_font_info) | |
| { | |
| struct efi_hii_packagelist *hii = package_list; | |
| struct efi_string_table *stbl; | |
| EFI_ENTRY("%p, %p, %p, \"%s\", %p, \"%ls\", %p", this, package_list, | |
| string_id, language, language_name, string, | |
| string_font_info); | |
| if (!package_list || !efi_hii_packagelist_exists(package_list)) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| if (!string_id || !language || !string) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| list_for_each_entry(stbl, &hii->string_tables, link) { | |
| if (language_match((char *)language, stbl->language)) { | |
| efi_string_id_t new_id; | |
| void *buf; | |
| efi_string_t str; | |
| new_id = ++hii->max_string_id; | |
| if (stbl->nstrings < new_id) { | |
| buf = realloc(stbl->strings, | |
| sizeof(stbl->strings[0]) | |
| * new_id); | |
| if (!buf) | |
| return EFI_EXIT(EFI_OUT_OF_RESOURCES); | |
| memset(&stbl->strings[stbl->nstrings], 0, | |
| (new_id - stbl->nstrings) | |
| * sizeof(stbl->strings[0])); | |
| stbl->strings = buf; | |
| stbl->nstrings = new_id; | |
| } | |
| str = u16_strdup(string); | |
| if (!str) | |
| return EFI_EXIT(EFI_OUT_OF_RESOURCES); | |
| stbl->strings[new_id - 1].string = str; | |
| *string_id = new_id; | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } | |
| } | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| } | |
| static efi_status_t EFIAPI | |
| get_string(const struct efi_hii_string_protocol *this, | |
| const u8 *language, | |
| efi_hii_handle_t package_list, | |
| efi_string_id_t string_id, | |
| efi_string_t string, | |
| efi_uintn_t *string_size, | |
| struct efi_font_info **string_font_info) | |
| { | |
| struct efi_hii_packagelist *hii = package_list; | |
| struct efi_string_table *stbl; | |
| EFI_ENTRY("%p, \"%s\", %p, %u, %p, %p, %p", this, language, | |
| package_list, string_id, string, string_size, | |
| string_font_info); | |
| if (!package_list || !efi_hii_packagelist_exists(package_list)) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| list_for_each_entry(stbl, &hii->string_tables, link) { | |
| if (language_match((char *)language, stbl->language)) { | |
| efi_string_t str; | |
| size_t len; | |
| if (stbl->nstrings < string_id) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| str = stbl->strings[string_id - 1].string; | |
| if (str) { | |
| len = u16_strsize(str); | |
| if (*string_size < len) { | |
| *string_size = len; | |
| return EFI_EXIT(EFI_BUFFER_TOO_SMALL); | |
| } | |
| memcpy(string, str, len); | |
| *string_size = len; | |
| } else { | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| } | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } | |
| } | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| } | |
| static efi_status_t EFIAPI | |
| set_string(const struct efi_hii_string_protocol *this, | |
| efi_hii_handle_t package_list, | |
| efi_string_id_t string_id, | |
| const u8 *language, | |
| const efi_string_t string, | |
| const struct efi_font_info *string_font_info) | |
| { | |
| struct efi_hii_packagelist *hii = package_list; | |
| struct efi_string_table *stbl; | |
| EFI_ENTRY("%p, %p, %u, \"%s\", \"%ls\", %p", this, package_list, | |
| string_id, language, string, string_font_info); | |
| if (!package_list || !efi_hii_packagelist_exists(package_list)) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| if (string_id > hii->max_string_id) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| if (!string || !language) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| list_for_each_entry(stbl, &hii->string_tables, link) { | |
| if (language_match((char *)language, stbl->language)) { | |
| efi_string_t str; | |
| if (hii->max_string_id < string_id) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| if (stbl->nstrings < string_id) { | |
| void *buf; | |
| buf = realloc(stbl->strings, | |
| string_id | |
| * sizeof(stbl->strings[0])); | |
| if (!buf) | |
| return EFI_EXIT(EFI_OUT_OF_RESOURCES); | |
| memset(&stbl->strings[string_id - 1], 0, | |
| (string_id - stbl->nstrings) | |
| * sizeof(stbl->strings[0])); | |
| stbl->strings = buf; | |
| } | |
| str = u16_strdup(string); | |
| if (!str) | |
| return EFI_EXIT(EFI_OUT_OF_RESOURCES); | |
| free(stbl->strings[string_id - 1].string); | |
| stbl->strings[string_id - 1].string = str; | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } | |
| } | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| } | |
| static efi_status_t EFIAPI | |
| get_languages(const struct efi_hii_string_protocol *this, | |
| efi_hii_handle_t package_list, | |
| u8 *languages, | |
| efi_uintn_t *languages_size) | |
| { | |
| struct efi_hii_packagelist *hii = package_list; | |
| struct efi_string_table *stbl; | |
| size_t len = 0; | |
| char *p; | |
| EFI_ENTRY("%p, %p, %p, %p", this, package_list, languages, | |
| languages_size); | |
| if (!package_list || !efi_hii_packagelist_exists(package_list)) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| if (!languages_size || | |
| (*languages_size && !languages)) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| /* figure out required size: */ | |
| list_for_each_entry(stbl, &hii->string_tables, link) { | |
| len += strlen((char *)stbl->language) + 1; | |
| } | |
| if (*languages_size < len) { | |
| *languages_size = len; | |
| return EFI_EXIT(EFI_BUFFER_TOO_SMALL); | |
| } | |
| p = (char *)languages; | |
| list_for_each_entry(stbl, &hii->string_tables, link) { | |
| if (p != (char *)languages) | |
| *p++ = ';'; | |
| strcpy(p, stbl->language); | |
| p += strlen((char *)stbl->language); | |
| } | |
| *p = '\0'; | |
| EFI_PRINT("languages: %s\n", languages); | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } | |
| static efi_status_t EFIAPI | |
| get_secondary_languages(const struct efi_hii_string_protocol *this, | |
| efi_hii_handle_t package_list, | |
| const u8 *primary_language, | |
| u8 *secondary_languages, | |
| efi_uintn_t *secondary_languages_size) | |
| { | |
| struct efi_hii_packagelist *hii = package_list; | |
| struct efi_string_table *stbl; | |
| bool found = false; | |
| EFI_ENTRY("%p, %p, \"%s\", %p, %p", this, package_list, | |
| primary_language, secondary_languages, | |
| secondary_languages_size); | |
| if (!package_list || !efi_hii_packagelist_exists(package_list)) | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| if (!secondary_languages_size || | |
| (*secondary_languages_size && !secondary_languages)) | |
| return EFI_EXIT(EFI_INVALID_PARAMETER); | |
| list_for_each_entry(stbl, &hii->string_tables, link) { | |
| if (language_match((char *)primary_language, stbl->language)) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) | |
| return EFI_EXIT(EFI_INVALID_LANGUAGE); | |
| /* | |
| * TODO: What is secondary language? | |
| * *secondary_languages = '\0'; | |
| * *secondary_languages_size = 0; | |
| */ | |
| return EFI_EXIT(EFI_NOT_FOUND); | |
| } | |
| const struct efi_hii_string_protocol efi_hii_string = { | |
| .new_string = new_string, | |
| .get_string = get_string, | |
| .set_string = set_string, | |
| .get_languages = get_languages, | |
| .get_secondary_languages = get_secondary_languages | |
| }; |