xref: /aosp_15_r20/external/coreboot/src/lib/ux_locales.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <cbfs.h>
4 #include <console/console.h>
5 #include <security/vboot/misc.h>
6 #include <stddef.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <ux_locales.h>
10 #include <vb2_api.h>
11 
12 #define LANG_ID_MAX 100
13 #define LANG_ID_LEN 3
14 
15 #define PRERAM_LOCALES_VERSION_BYTE 0x01
16 #define PRERAM_LOCALES_NAME "preram_locales"
17 
18 /* We need different delimiters to deal with the case where 'string_name' is the same as
19    'localized_string'. */
20 #define DELIM_STR 0x00
21 #define DELIM_NAME 0x01
22 
23 /*
24  * Devices which support early vga have the capability to show localized text in
25  * Code Page 437 encoding. (see src/drivers/pc80/vga/vga_font_8x16.c)
26  *
27  * preram_locales located in CBFS is an uncompressed file located in either RO
28  * or RW CBFS. It contains the localization information in the following format:
29  *
30  * [PRERAM_LOCALES_VERSION_BYTE]
31  * [string_name_1] [\x00]
32  * [language_id_1] [\x00] [localized_string_1] [\x00]
33  * [language_id_2] [\x00] [localized_string_2] [\x00] ...
34  * [\x01]
35  * [string_name_2] [\x00] ...
36  *
37  * This file contains tools to locate the file and search for localized strings
38  * with specific language ID.
39  */
40 
41 /* Cached state for map (locales_get_map) and unmap (ux_locales_unmap). */
42 struct preram_locales_state {
43 	void *data;
44 	size_t size;
45 	bool initialized;
46 };
47 
48 static struct preram_locales_state cached_state;
49 
ux_locales_unmap(void)50 void ux_locales_unmap(void)
51 {
52 	if (cached_state.initialized) {
53 		if (cached_state.data)
54 			cbfs_unmap(cached_state.data);
55 		cached_state.initialized = false;
56 		cached_state.size = 0;
57 		cached_state.data = NULL;
58 	}
59 }
60 
61 /* Get the map address of preram_locales. */
locales_get_map(size_t * size_out,bool unmap)62 static void *locales_get_map(size_t *size_out, bool unmap)
63 {
64 	if (cached_state.initialized) {
65 		*size_out = cached_state.size;
66 		return cached_state.data;
67 	}
68 	cached_state.initialized = true;
69 	cached_state.data = cbfs_ro_map(PRERAM_LOCALES_NAME,
70 					&cached_state.size);
71 	*size_out = cached_state.size;
72 	return cached_state.data;
73 }
74 
75 /* Move to the next string in the data. Strings are separated by delim. */
move_next(const char * data,size_t offset,size_t size,char delim)76 static size_t move_next(const char *data, size_t offset, size_t size, char delim)
77 {
78 	while (offset < size && data[offset] != delim)
79 		offset++;
80 	/* If we found delim, move to the start of the next string. */
81 	if (offset < size)
82 		offset++;
83 	return offset;
84 }
85 
86 /* Find the next occurrence of the specific string. Strings are separated by delim. */
search_for(const char * data,size_t offset,size_t size,const char * str,char delim)87 static size_t search_for(const char *data, size_t offset, size_t size,
88 			 const char *str, char delim)
89 {
90 	while (offset < size) {
91 		if (!strncmp(data + offset, str, size - offset))
92 			return offset;
93 		offset = move_next(data, offset, size, delim);
94 	}
95 	return size;
96 }
97 
98 /* Find the next occurrence of the string_name, which should always follow a DELIM_NAME. */
search_for_name(const char * data,size_t offset,size_t size,const char * name)99 static inline size_t search_for_name(const char *data, size_t offset, size_t size,
100 				     const char *name)
101 {
102 	return search_for(data, offset, size, name, DELIM_NAME);
103 }
104 
105 /* Find the next occurrence of the integer ID, where ID is less than 100. */
search_for_id(const char * data,size_t offset,size_t size,int id)106 static size_t search_for_id(const char *data, size_t offset, size_t size,
107 			    int id)
108 {
109 	if (id >= LANG_ID_MAX)
110 		return offset;
111 	char int_to_str[LANG_ID_LEN] = {};
112 	snprintf(int_to_str, LANG_ID_LEN, "%d", id);
113 	return search_for(data, offset, size, int_to_str, DELIM_STR);
114 }
115 
ux_locales_get_text(const char * name)116 const char *ux_locales_get_text(const char *name)
117 {
118 	const char *data;
119 	size_t size, offset, name_offset, next_name_offset, next;
120 	uint32_t lang_id = 0; /* default language English (0) */
121 	unsigned char version;
122 
123 	data = locales_get_map(&size, false);
124 	if (!data || size == 0) {
125 		printk(BIOS_ERR, "%s: %s not found.\n", __func__,
126 		       PRERAM_LOCALES_NAME);
127 		return NULL;
128 	}
129 
130 	if (CONFIG(VBOOT)) {
131 		/* Get the language ID from vboot API. */
132 		lang_id = vb2api_get_locale_id(vboot_get_context());
133 		/* Validity check: Language ID should smaller than LANG_ID_MAX. */
134 		if (lang_id >= LANG_ID_MAX) {
135 			printk(BIOS_WARNING, "%s: ID %d too big; fallback to 0.\n",
136 			       __func__, lang_id);
137 			lang_id = 0;
138 		}
139 	}
140 
141 	printk(BIOS_INFO, "%s: Search for %s with language ID: %u\n",
142 	       __func__, name, lang_id);
143 
144 	/* Check if the version byte is the expected version. */
145 	version = (unsigned char)data[0];
146 	if (version != PRERAM_LOCALES_VERSION_BYTE) {
147 		printk(BIOS_ERR, "%s: The version %u is not the expected one %u\n",
148 		       __func__, version, PRERAM_LOCALES_VERSION_BYTE);
149 		return NULL;
150 	}
151 
152 	/* Search for name. Skip the version byte. */
153 	offset = search_for_name(data, 1, size, name);
154 	if (offset >= size) {
155 		printk(BIOS_ERR, "%s: Name %s not found.\n", __func__, name);
156 		return NULL;
157 	}
158 	name_offset = offset;
159 
160 	/* Search for language ID. We should not search beyond the range of the current
161 	   string_name. */
162 	next_name_offset = move_next(data, offset, size, DELIM_NAME);
163 	assert(next_name_offset <= size);
164 	offset = search_for_id(data,  name_offset, next_name_offset, lang_id);
165 	/* Language ID not supported; fallback to English if the current language is not
166 	   English (0). */
167 	if (offset >= next_name_offset) {
168 		/* Since we only support a limited charset, it is very normal that a language
169 		   is not supported and we fallback here silently. */
170 		if (lang_id != 0)
171 			offset = search_for_id(data, name_offset, next_name_offset, 0);
172 		if (offset >= next_name_offset) {
173 			printk(BIOS_ERR, "%s: Neither %d nor 0 found.\n", __func__, lang_id);
174 			return NULL;
175 		}
176 	}
177 
178 	/* Move to the corresponding localized_string. */
179 	offset = move_next(data, offset, next_name_offset, DELIM_STR);
180 	if (offset >= next_name_offset)
181 		return NULL;
182 
183 	/* Validity check that the returned string must be NULL terminated. */
184 	next = move_next(data, offset, next_name_offset, DELIM_STR) - 1;
185 	if (next >= next_name_offset || data[next] != '\0') {
186 		printk(BIOS_ERR, "%s: %s is not NULL terminated.\n",
187 		       __func__, PRERAM_LOCALES_NAME);
188 		return NULL;
189 	}
190 
191 	return data + offset;
192 }
193