libpayload: Add iterators for CMOS variables
[coreboot.git] / payloads / libpayload / drivers / options.c
1 /*
2  * This file is part of the libpayload project.
3  *
4  * Copyright (C) 2008 coresystems GmbH
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <libpayload.h>
31 #include <coreboot_tables.h>
32
33 u8 *mem_accessor_base;
34
35 static u8 mem_read(u8 reg)
36 {
37         return mem_accessor_base[reg];
38 }
39
40 static void mem_write(u8 val, u8 reg)
41 {
42         mem_accessor_base[reg] = val;
43 }
44
45 struct nvram_accessor *use_nvram = &(struct nvram_accessor) {
46         nvram_read,
47         nvram_write
48 };
49
50 struct nvram_accessor *use_mem = &(struct nvram_accessor) {
51         mem_read,
52         mem_write
53 };
54
55 struct cb_cmos_option_table *get_system_option_table(void)
56 {
57         return phys_to_virt(lib_sysinfo.option_table);
58 }
59
60 int options_checksum_valid(const struct nvram_accessor *nvram)
61 {
62         int i;
63         int range_start = lib_sysinfo.cmos_range_start / 8;
64         int range_end = lib_sysinfo.cmos_range_end / 8;
65         int checksum_location = lib_sysinfo.cmos_checksum_location / 8;
66         u16 checksum = 0, checksum_old;
67
68         for(i = range_start; i <= range_end; i++) {
69                 checksum += nvram->read(i);
70         }
71
72         checksum_old = ((nvram->read(checksum_location)<<8) | nvram->read(checksum_location+1));
73
74         return (checksum_old == checksum);
75 }
76
77 void fix_options_checksum_with(const struct nvram_accessor *nvram)
78 {
79         int i;
80         int range_start = lib_sysinfo.cmos_range_start / 8;
81         int range_end = lib_sysinfo.cmos_range_end / 8;
82         int checksum_location = lib_sysinfo.cmos_checksum_location / 8;
83         u16 checksum = 0;
84
85         for(i = range_start; i <= range_end; i++) {
86                 checksum += nvram->read(i);
87         }
88
89         nvram->write((checksum >> 8), checksum_location);
90         nvram->write((checksum & 0xff), checksum_location + 1);
91 }
92
93 void fix_options_checksum(void)
94 {
95         fix_options_checksum_with(use_nvram);
96 }
97
98 static int get_cmos_value(const struct nvram_accessor *nvram, u32 bitnum, u32 len, void *valptr)
99 {
100         u8 *value = (u8 *)valptr;
101         int offs = 0;
102         u32 addr, bit;
103         u8 reg8;
104
105         /* Convert to byte borders */
106         addr=(bitnum / 8);
107         bit=(bitnum % 8);
108
109         /* Handle single byte or less */
110         if(len <= 8) {
111                 reg8 = nvram->read(addr);
112                 reg8 >>= bit;
113                 value[0] = reg8 & ((1 << len) -1);
114                 return 0;
115         }
116
117         /* When handling more than a byte, copy whole bytes */
118         while (len > 0) {
119                 len -= 8;
120                 value[offs++]=nvram->read(addr++);
121         }
122
123         return 0;
124 }
125
126 static int set_cmos_value(const struct nvram_accessor *nvram, u32 bitnum, u32 len, void *valptr)
127 {
128         u8 *value = (u8 *)valptr;
129         int offs = 0;
130         u32 addr, bit;
131         u8 reg8;
132
133         /* Convert to byte borders */
134         addr=(bitnum / 8);
135         bit=(bitnum % 8);
136
137         /* Handle single byte or less */
138         if (len <= 8) {
139                 reg8 = nvram->read(addr);
140                 reg8 &= ~(((1 << len) - 1) << bit);
141                 reg8 |= (value[0] & ((1 << len) - 1)) << bit;
142                 nvram->write(reg8, addr);
143                 return 0;
144         }
145
146         /* When handling more than a byte, copy whole bytes */
147         while (len > 0) {
148                 len -= 8;
149                 nvram->write(value[offs++], addr++);
150         }
151
152         return 0;
153 }
154
155 static struct cb_cmos_entries *lookup_cmos_entry(struct cb_cmos_option_table *option_table, char *name)
156 {
157         struct cb_cmos_entries *cmos_entry;
158         int len = strnlen(name, CMOS_MAX_NAME_LENGTH);
159
160         /* cmos entries are located right after the option table */
161
162         for (   cmos_entry = (struct cb_cmos_entries*)((unsigned char *)option_table + option_table->header_length);
163                 cmos_entry->tag == CB_TAG_OPTION;
164                 cmos_entry = (struct cb_cmos_entries*)((unsigned char *)cmos_entry + cmos_entry->size)) {
165                 if (memcmp((const char*)cmos_entry->name, name, len))
166                         continue;
167                 return cmos_entry;
168         }
169
170         printf("ERROR: No such CMOS option (%s)\n", name);
171         return NULL;
172 }
173
174 struct cb_cmos_entries *first_cmos_entry(struct cb_cmos_option_table *option_table)
175 {
176         return lookup_cmos_entry(option_table, "");
177 }
178
179 struct cb_cmos_entries *next_cmos_entry(struct cb_cmos_entries *cmos_entry)
180 {
181         struct cb_cmos_entries *next = (struct cb_cmos_entries*)((unsigned char *)cmos_entry + cmos_entry->size);
182         if (next->tag == CB_TAG_OPTION)
183                 return next;
184         else
185                 return NULL;
186 }
187
188 /* Either value or text must be NULL. Returns the field that matches "the other" for a given config_id */
189 static struct cb_cmos_enums *lookup_cmos_enum_core(struct cb_cmos_option_table *option_table, int config_id, u8 *value, char *text)
190 {
191         struct cb_cmos_entries *cmos_entry;
192         int len = strnlen(text, CMOS_MAX_TEXT_LENGTH);
193
194         /* cmos entries are located right after the option table. Skip them */
195         cmos_entry = (struct cb_cmos_entries*)((unsigned char *)option_table + option_table->header_length);
196         while (cmos_entry->tag == CB_TAG_OPTION)
197                 cmos_entry = (struct cb_cmos_entries*)((unsigned char *)cmos_entry + cmos_entry->size);
198
199         /* cmos enums are located after cmos entries. */
200         struct cb_cmos_enums *cmos_enum;
201         for (   cmos_enum = (struct cb_cmos_enums*)cmos_entry;
202                 cmos_enum->tag == CB_TAG_OPTION_ENUM;
203                 cmos_enum = (struct cb_cmos_enums*)((unsigned char *)cmos_enum + cmos_enum->size)) {
204                 if ((cmos_enum->config_id == config_id)
205                    && ((value == NULL) || (cmos_enum->value == *value))
206                    && ((text == NULL) || (memcmp((const char*)cmos_enum->text, text, len)))) {
207                         return cmos_enum;
208                 }
209         }
210
211         return NULL;
212 }
213
214 static struct cb_cmos_enums *lookup_cmos_enum_by_value(struct cb_cmos_option_table *option_table, int config_id, u8 *value)
215 {
216         return lookup_cmos_enum_core(option_table, config_id, value, NULL);
217 }
218
219 static struct cb_cmos_enums *lookup_cmos_enum_by_label(struct cb_cmos_option_table *option_table, int config_id, char *label)
220 {
221         return lookup_cmos_enum_core(option_table, config_id, NULL, label);
222 }
223
224 int get_option_with(const struct nvram_accessor *nvram, struct cb_cmos_option_table *option_table, void *dest, char *name)
225 {
226         struct cb_cmos_entries *cmos_entry = lookup_cmos_entry(option_table, name);
227         if (cmos_entry) {
228                 if(get_cmos_value(nvram, cmos_entry->bit, cmos_entry->length, dest))
229                         return 1;
230
231                 if(!options_checksum_valid(nvram))
232                         return 1;
233
234                 return 0;
235         }
236         return 1;
237 }
238
239 int get_option_from(struct cb_cmos_option_table *option_table, void *dest, char *name)
240 {
241         return get_option_with(use_nvram, option_table, dest, name);
242 }
243
244 int get_option(void *dest, char *name)
245 {
246         return get_option_from(get_system_option_table(), dest, name);
247 }
248
249 int set_option_with(const struct nvram_accessor *nvram, struct cb_cmos_option_table *option_table, void *value, char *name)
250 {
251         struct cb_cmos_entries *cmos_entry = lookup_cmos_entry(option_table, name);
252         if (cmos_entry) {
253                 set_cmos_value(nvram, cmos_entry->bit, cmos_entry->length, value);
254                 fix_options_checksum_with(nvram);
255                 return 0;
256         }
257         return 1;
258 }
259
260 int set_option(void *value, char *name)
261 {
262         return set_option_with(use_nvram, get_system_option_table(), value, name);
263 }
264
265 int get_option_as_string(const struct nvram_accessor *nvram, struct cb_cmos_option_table *option_table, char **dest, char *name)
266 {
267         void *raw;
268         struct cb_cmos_entries *cmos_entry = lookup_cmos_entry(option_table, name);
269         if (!cmos_entry)
270                 return 1;
271         int cmos_length = (cmos_entry->length+7)/8;
272
273         /* extra byte to ensure 0-terminated strings */
274         raw = malloc(cmos_length+1);
275         memset(raw, 0, cmos_length+1);
276
277         int ret = get_option_with(nvram, option_table, raw, name);
278
279         struct cb_cmos_enums *cmos_enum;
280         switch (cmos_entry->config) {
281                 case 'h':
282                         /* only works on little endian.
283                            26 bytes is enough for a 64bit value in decimal */
284                         *dest = malloc(26);
285                         sprintf(*dest, "%ull", *(u64*)raw);
286                         break;
287                 case 's':
288                         *dest = strdup(raw);
289                         break;
290                 case 'e':
291                         cmos_enum = lookup_cmos_enum_by_value(option_table, cmos_entry->config_id, (u8*)raw);
292                         *dest = strdup((const char*)cmos_enum->text);
293                         break;
294                 default: /* fail */
295                         return 1;
296         }
297         free(raw);
298         return ret;
299 }
300
301 int set_option_from_string(const struct nvram_accessor *nvram, struct cb_cmos_option_table *option_table, char *value, char *name)
302 {
303         void *raw;
304         struct cb_cmos_entries *cmos_entry = lookup_cmos_entry(option_table, name);
305         if (!cmos_entry)
306                 return 1;
307
308         struct cb_cmos_enums *cmos_enum;
309         switch (cmos_entry->config) {
310                 case 'h':
311                         /* only works on little endian */
312                         raw = malloc(8);
313                         *(u64*)raw = strtoull(value, NULL, 0);
314                         break;
315                 case 's':
316                         raw = strdup(value);
317                         break;
318                 case 'e':
319                         cmos_enum = lookup_cmos_enum_by_label(option_table, cmos_entry->config_id, value);
320                         raw = malloc(sizeof(u32));
321                         *(u32*)raw = cmos_enum->value;
322                         break;
323                 default: /* fail */
324                         return 1;
325         }
326
327         int ret = set_option_with(nvram, option_table, raw, name);
328         free(raw);
329         return ret;
330 }