| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include <cbfs.h> |
| #include <console/console.h> |
| #include <security/vboot/misc.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <ux_locales.h> |
| #include <vb2_api.h> |
| |
| #define LANG_ID_MAX 100 |
| #define LANG_ID_LEN 3 |
| #define PRERAM_LOCALES_NAME "preram_locales" |
| |
| /* |
| * Devices which support early vga have the capability to show localized text in |
| * Code Page 437 encoding. (see src/drivers/pc80/vga/vga_font_8x16.c) |
| * |
| * preram_locales located in CBFS is an uncompressed file located in either RO |
| * or RW CBFS. It contains the localization information in the following format: |
| * |
| * [string_name_1] [\x00] |
| * [language_id_1] [\x00] [localized_string_1] [\x00] |
| * [language_id_2] [\x00] [localized_string_2] [\x00] ... |
| * [string_name_2] [\x00] ... |
| * |
| * This file contains tools to locate the file and search for localized strings |
| * with specific language ID. |
| */ |
| |
| /* Cached state for map (locales_get_map) and unmap (ux_locales_unmap). */ |
| struct preram_locales_state { |
| void *data; |
| size_t size; |
| bool initialized; |
| }; |
| |
| static struct preram_locales_state cached_state; |
| |
| void ux_locales_unmap(void) |
| { |
| if (cached_state.initialized) { |
| if (cached_state.data) |
| cbfs_unmap(cached_state.data); |
| cached_state.initialized = false; |
| cached_state.size = 0; |
| cached_state.data = NULL; |
| } |
| } |
| |
| /* Get the map address of preram_locales. */ |
| static void *locales_get_map(size_t *size_out, bool unmap) |
| { |
| if (cached_state.initialized) { |
| *size_out = cached_state.size; |
| return cached_state.data; |
| } |
| cached_state.initialized = true; |
| cached_state.data = cbfs_ro_map(PRERAM_LOCALES_NAME, |
| &cached_state.size); |
| *size_out = cached_state.size; |
| return cached_state.data; |
| } |
| |
| /* Move to the next string in the data. Strings are separated by 0x00. */ |
| static size_t move_next(const char *data, size_t offset, size_t size) |
| { |
| if (offset >= size) |
| return size; |
| while (offset < size && data[offset] != '\0') |
| offset++; |
| offset++; |
| return offset; |
| } |
| |
| /* Find the next occurrence of the specific string. */ |
| static size_t search_for(const char *data, size_t offset, size_t size, |
| const char *str) |
| { |
| if (offset >= size) |
| return size; |
| while (offset < size) { |
| if (!strncmp(data + offset, str, size - offset)) |
| return offset; |
| offset = move_next(data, offset, size); |
| } |
| return size; |
| } |
| |
| /* Find the next occurrence of the integer ID, where ID is less than 100. */ |
| static size_t search_for_id(const char *data, size_t offset, size_t size, |
| int id) |
| { |
| if (id >= LANG_ID_MAX) |
| return offset; |
| char int_to_str[LANG_ID_LEN] = {}; |
| snprintf(int_to_str, LANG_ID_LEN, "%d", id); |
| return search_for(data, offset, size, int_to_str); |
| } |
| |
| const char *ux_locales_get_text(const char *name) |
| { |
| const char *data; |
| size_t size, offset, name_offset, next; |
| uint32_t lang_id; |
| |
| data = locales_get_map(&size, false); |
| if (!data) { |
| printk(BIOS_ERR, "%s: %s not found.\n", __func__, |
| PRERAM_LOCALES_NAME); |
| return NULL; |
| } |
| |
| /* Get the language ID from vboot API. */ |
| lang_id = vb2api_get_locale_id(vboot_get_context()); |
| /* Validity check: Language ID should smaller than LANG_ID_MAX. */ |
| if (lang_id >= LANG_ID_MAX) { |
| printk(BIOS_WARNING, "%s: ID %d too big; fallback to 0.\n", |
| __func__, lang_id); |
| lang_id = 0; |
| } |
| |
| printk(BIOS_INFO, "%s: Search for %s with language ID: %u\n", |
| __func__, name, lang_id); |
| |
| /* Search for name. */ |
| offset = search_for(data, 0, size, name); |
| if (offset >= size) { |
| printk(BIOS_ERR, "%s: Name %s not found.\n", __func__, name); |
| return NULL; |
| } |
| name_offset = offset; |
| |
| /* Search for language ID. */ |
| offset = search_for_id(data, name_offset, size, lang_id); |
| /* Language ID not supported; fallback to English if the current |
| * language is not English (0). */ |
| if (offset >= size) { |
| /* |
| * Since we only support a limited charset, it is very normal |
| * that a language is not supported and we fallback here |
| * silently. |
| */ |
| if (lang_id != 0) |
| offset = search_for_id(data, name_offset, size, 0); |
| if (offset >= size) { |
| printk(BIOS_ERR, "%s: Neither %d nor 0 found.\n", |
| __func__, lang_id); |
| return NULL; |
| } |
| } |
| |
| offset = move_next(data, offset, size); |
| if (offset >= size) |
| return NULL; |
| |
| /* Validity check that the returned string must be NULL terminated. */ |
| next = move_next(data, offset, size) - 1; |
| if (next >= size || data[next] != '\0') { |
| printk(BIOS_ERR, "%s: %s is not NULL terminated.\n", |
| __func__, PRERAM_LOCALES_NAME); |
| return NULL; |
| } |
| |
| return data + offset; |
| } |