summaryrefslogtreecommitdiffstats
path: root/src/lib/ux_locales.c
blob: 0a285d9b6776ae289317e0e03afacbf0af9aaa59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/* 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_VERSION_BYTE 0x01
#define PRERAM_LOCALES_NAME "preram_locales"

/* We need different delimiters to deal with the case where 'string_name' is the same as
   'localized_string'. */
#define DELIM_STR 0x00
#define DELIM_NAME 0x01

/*
 * 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:
 *
 * [PRERAM_LOCALES_VERSION_BYTE]
 * [string_name_1] [\x00]
 * [language_id_1] [\x00] [localized_string_1] [\x00]
 * [language_id_2] [\x00] [localized_string_2] [\x00] ...
 * [\x01]
 * [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 delim. */
static size_t move_next(const char *data, size_t offset, size_t size, char delim)
{
	while (offset < size && data[offset] != delim)
		offset++;
	/* If we found delim, move to the start of the next string. */
	if (offset < size)
		offset++;
	return offset;
}

/* Find the next occurrence of the specific string. Strings are separated by delim. */
static size_t search_for(const char *data, size_t offset, size_t size,
			 const char *str, char delim)
{
	while (offset < size) {
		if (!strncmp(data + offset, str, size - offset))
			return offset;
		offset = move_next(data, offset, size, delim);
	}
	return size;
}

/* Find the next occurrence of the string_name, which should always follow a DELIM_NAME. */
static inline size_t search_for_name(const char *data, size_t offset, size_t size,
				     const char *name)
{
	return search_for(data, offset, size, name, DELIM_NAME);
}

/* 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, DELIM_STR);
}

const char *ux_locales_get_text(const char *name)
{
	const char *data;
	size_t size, offset, name_offset, next_name_offset, next;
	uint32_t lang_id = 0; /* default language English (0) */
	unsigned char version;

	data = locales_get_map(&size, false);
	if (!data || size == 0) {
		printk(BIOS_ERR, "%s: %s not found.\n", __func__,
		       PRERAM_LOCALES_NAME);
		return NULL;
	}

	if (CONFIG(VBOOT)) {
		/* 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);

	/* Check if the version byte is the expected version. */
	version = (unsigned char)data[0];
	if (version != PRERAM_LOCALES_VERSION_BYTE) {
		printk(BIOS_ERR, "%s: The version %u is not the expected one %u\n",
		       __func__, version, PRERAM_LOCALES_VERSION_BYTE);
		return NULL;
	}

	/* Search for name. Skip the version byte. */
	offset = search_for_name(data, 1, 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. We should not search beyond the range of the current
	   string_name. */
	next_name_offset = move_next(data, offset, size, DELIM_NAME);
	assert(next_name_offset <= size);
	offset = search_for_id(data,  name_offset, next_name_offset, lang_id);
	/* Language ID not supported; fallback to English if the current language is not
	   English (0). */
	if (offset >= next_name_offset) {
		/* 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, next_name_offset, 0);
		if (offset >= next_name_offset) {
			printk(BIOS_ERR, "%s: Neither %d nor 0 found.\n", __func__, lang_id);
			return NULL;
		}
	}

	/* Move to the corresponding localized_string. */
	offset = move_next(data, offset, next_name_offset, DELIM_STR);
	if (offset >= next_name_offset)
		return NULL;

	/* Validity check that the returned string must be NULL terminated. */
	next = move_next(data, offset, next_name_offset, DELIM_STR) - 1;
	if (next >= next_name_offset || data[next] != '\0') {
		printk(BIOS_ERR, "%s: %s is not NULL terminated.\n",
		       __func__, PRERAM_LOCALES_NAME);
		return NULL;
	}

	return data + offset;
}