| // 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 |
| */ |
| |
| #include <common.h> |
| #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; |
| |
| debug("header_size: %08x\n", |
| get_unaligned_le32(&strings_package->header_size)); |
| debug("string_info_offset: %08x\n", |
| get_unaligned_le32(&strings_package->string_info_offset)); |
| debug("language_name: %u\n", |
| get_unaligned_le16(&strings_package->language_name)); |
| debug("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: |
| debug("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; |
| debug("%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: |
| debug("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); |
| if (idx > 0) |
| 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)); |
| 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); |
| |
| debug("package_list: %pUl (%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) { |
| debug("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: |
| printf("\tForm 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: |
| printf("\tFont package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| break; |
| case EFI_HII_PACKAGE_IMAGES: |
| printf("\tImage package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| break; |
| case EFI_HII_PACKAGE_SIMPLE_FONTS: |
| printf("\tSimple font package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| break; |
| case EFI_HII_PACKAGE_DEVICE_PATH: |
| printf("\tDevice 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: |
| printf("\tAnimation 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; |
| list_add_tail(&hii->link, &efi_package_lists); |
| *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); |
| |
| debug("package_list: %pUl (%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) { |
| debug("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: |
| printf("\tForm package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| break; |
| case EFI_HII_PACKAGE_STRINGS: |
| remove_strings_package(hii); |
| break; |
| case EFI_HII_PACKAGE_FONTS: |
| printf("\tFont package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| break; |
| case EFI_HII_PACKAGE_IMAGES: |
| printf("\tImage package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| break; |
| case EFI_HII_PACKAGE_SIMPLE_FONTS: |
| printf("\tSimple font package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| break; |
| case EFI_HII_PACKAGE_DEVICE_PATH: |
| printf("\tDevice 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: |
| printf("\tAnimation 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_SUCCESS; |
| |
| EFI_ENTRY("%p, %u, %pUl, %p, %p", this, package_type, package_guid, |
| handle_buffer_length, handle); |
| |
| if (!handle_buffer_length || |
| (*handle_buffer_length && !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); |
| |
| debug("package type=%x, guid=%pUl, length=%lu\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_FORMS: |
| printf("\tForm package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| continue; |
| case EFI_HII_PACKAGE_STRINGS: |
| if (!list_empty(&hii->string_tables)) |
| break; |
| continue; |
| case EFI_HII_PACKAGE_FONTS: |
| printf("\tFont package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| continue; |
| case EFI_HII_PACKAGE_IMAGES: |
| printf("\tImage package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| continue; |
| case EFI_HII_PACKAGE_SIMPLE_FONTS: |
| printf("\tSimple font package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| continue; |
| case EFI_HII_PACKAGE_DEVICE_PATH: |
| printf("\tDevice path package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| continue; |
| case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: |
| if (!list_empty(&hii->keyboard_packages)) |
| break; |
| continue; |
| case EFI_HII_PACKAGE_ANIMATIONS: |
| printf("\tAnimation package not supported\n"); |
| ret = EFI_INVALID_PARAMETER; |
| continue; |
| case EFI_HII_PACKAGE_END: |
| case EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN: |
| case EFI_HII_PACKAGE_TYPE_SYSTEM_END: |
| default: |
| continue; |
| } |
| |
| package_cnt++; |
| if (package_cnt <= package_max) |
| *handle++ = hii; |
| else |
| ret = EFI_BUFFER_TOO_SMALL; |
| } |
| *handle_buffer_length = package_cnt * sizeof(*handle); |
| |
| 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, %pUl, %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, %pUl, %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, %pUl", this, key_guid); |
| |
| 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_NOT_FOUND); |
| } |
| |
| 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_strlen(str) + 1) * sizeof(u16); |
| 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'; |
| |
| debug("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 |
| }; |