summaryrefslogtreecommitdiffstats
path: root/src/lib/ux_locales.c
blob: 86126195b4a4dc8606a8f65a05cb880597bc4f9f (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
/* 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;
}