1 /* ----------------------------------------------------------------------- *
2 *
3 * Copyright 2011 Intel Corporation; author: H. Peter Anvin
4 *
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation
7 * files (the "Software"), to deal in the Software without
8 * restriction, including without limitation the rights to use,
9 * copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom
11 * the Software is furnished to do so, subject to the following
12 * conditions:
13 *
14 * The above copyright notice and this permission notice shall
15 * be included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 * OTHER DEALINGS IN THE SOFTWARE.
25 *
26 * ----------------------------------------------------------------------- */
27
28 /*
29 * Search DMI information for specific data or strings
30 */
31
32 #include <string.h>
33 #include <stdio.h>
34 #include <sys/bitops.h>
35 #include <sys/cpu.h>
36 #include <syslinux/sysappend.h>
37 #include "core.h"
38
39 struct dmi_table {
40 uint8_t type;
41 uint8_t length;
42 uint16_t handle;
43 };
44
45 struct dmi_header {
46 char signature[5];
47 uint8_t csum;
48 uint16_t tbllen;
49 uint32_t tbladdr;
50 uint16_t nstruc;
51 uint8_t revision;
52 uint8_t reserved;
53 };
54
55 struct smbios_header {
56 char signature[4];
57 uint8_t csum;
58 uint8_t len;
59 uint8_t major;
60 uint8_t minor;
61 uint16_t maxsize;
62 uint8_t revision;
63 uint8_t fmt[5];
64
65 struct dmi_header dmi;
66 };
67
68 static const struct dmi_header *dmi;
69
checksum(const void * buf,size_t len)70 static uint8_t checksum(const void *buf, size_t len)
71 {
72 const uint8_t *p = buf;
73 uint8_t csum = 0;
74
75 while (len--)
76 csum += *p++;
77
78 return csum;
79 }
80
is_old_dmi(size_t dptr)81 static bool is_old_dmi(size_t dptr)
82 {
83 const struct dmi_header *dmi = (void *)dptr;
84
85 return !memcmp(dmi->signature, "_DMI_", 5) &&
86 !checksum(dmi, 0x0f);
87 return false;
88 }
89
is_smbios(size_t dptr)90 static bool is_smbios(size_t dptr)
91 {
92 const struct smbios_header *smb = (void *)dptr;
93
94 return !memcmp(smb->signature, "_SM_", 4) &&
95 !checksum(smb, smb->len) &&
96 is_old_dmi(dptr+16);
97 }
98
99 /*
100 * Find the root structure
101 */
dmi_find_header(void)102 static void dmi_find_header(void)
103 {
104 size_t dptr;
105
106 /* Search for _SM_ or _DMI_ structure */
107 for (dptr = 0xf0000 ; dptr < 0x100000 ; dptr += 16) {
108 if (is_smbios(dptr)) {
109 dmi = (const struct dmi_header *)(dptr + 16);
110 break;
111 } else if (is_old_dmi(dptr)) {
112 dmi = (const struct dmi_header *)dptr;
113 break;
114 }
115 }
116 }
117
118 /*
119 * Return a specific data element in a specific table, and verify
120 * that it is within the bounds of the table.
121 */
dmi_find_data(uint8_t type,uint8_t base,uint8_t length)122 static const void *dmi_find_data(uint8_t type, uint8_t base, uint8_t length)
123 {
124 const struct dmi_table *table;
125 size_t offset, end;
126 unsigned int tblcount;
127
128 if (!dmi)
129 return NULL;
130
131 if (base < 2)
132 return NULL;
133
134 end = base+length;
135
136 offset = 0;
137 tblcount = dmi->nstruc;
138
139 while (offset+6 <= dmi->tbllen && tblcount--) {
140 table = (const struct dmi_table *)(dmi->tbladdr + offset);
141
142 if (table->type == 127) /* End of table */
143 break;
144
145 if (table->length < sizeof *table)
146 break; /* Invalid length */
147
148 offset += table->length;
149
150 if (table->type == type && end <= table->length)
151 return (const char *)table + base;
152
153 /* Search for a double NUL terminating the string table */
154 while (offset+2 <= dmi->tbllen &&
155 *(const uint16_t *)(dmi->tbladdr + offset) != 0)
156 offset++;
157
158 offset += 2;
159 }
160
161 return NULL;
162 }
163
164 /*
165 * Return a specific string in a specific table.
166 */
dmi_find_string(uint8_t type,uint8_t base)167 static const char *dmi_find_string(uint8_t type, uint8_t base)
168 {
169 const struct dmi_table *table;
170 size_t offset;
171 unsigned int tblcount;
172
173 if (!dmi)
174 return NULL;
175
176 if (base < 2)
177 return NULL;
178
179 offset = 0;
180 tblcount = dmi->nstruc;
181
182 while (offset+6 <= dmi->tbllen && tblcount--) {
183 table = (const struct dmi_table *)(dmi->tbladdr + offset);
184
185 if (table->type == 127) /* End of table */
186 break;
187
188 if (table->length < sizeof *table)
189 break; /* Invalid length */
190
191 offset += table->length;
192
193 if (table->type == type && base < table->length) {
194 uint8_t index = ((const uint8_t *)table)[base];
195 const char *p = (const char *)table + table->length;
196 const char *str;
197 char c;
198
199 if (!index)
200 return NULL; /* String not present */
201
202 while (--index) {
203 if (!*p)
204 return NULL;
205
206 do {
207 if (offset++ >= dmi->tbllen)
208 return NULL;
209 c = *p++;
210 } while (c);
211 }
212
213 /* Make sure the string is null-terminated */
214 str = p;
215 do {
216 if (offset++ >= dmi->tbllen)
217 return NULL;
218 c = *p++;
219 } while (c);
220 return str;
221 }
222
223 /* Search for a double NUL terminating the string table */
224 while (offset+2 <= dmi->tbllen &&
225 *(const uint16_t *)(dmi->tbladdr + offset) != 0)
226 offset++;
227
228 offset += 2;
229 }
230
231 return NULL;
232 }
233
234 struct sysappend_dmi_strings {
235 const char *prefix;
236 enum syslinux_sysappend sa;
237 uint8_t index;
238 uint8_t offset;
239 };
240
241 static const struct sysappend_dmi_strings dmi_strings[] = {
242 { "SYSVENDOR=", SYSAPPEND_SYSVENDOR, 1, 0x04 },
243 { "SYSPRODUCT=", SYSAPPEND_SYSPRODUCT, 1, 0x05 },
244 { "SYSVERSION=", SYSAPPEND_SYSVERSION, 1, 0x06 },
245 { "SYSSERIAL=", SYSAPPEND_SYSSERIAL, 1, 0x07 },
246 { "SYSSKU=", SYSAPPEND_SYSSKU, 1, 0x19 },
247 { "SYSFAMILY=", SYSAPPEND_SYSFAMILY, 1, 0x1a },
248 { "MBVENDOR=", SYSAPPEND_MBVENDOR, 2, 0x04 },
249 { "MBPRODUCT=", SYSAPPEND_MBPRODUCT, 2, 0x05 },
250 { "MBVERSION=", SYSAPPEND_MBVERSION, 2, 0x06 },
251 { "MBSERIAL=", SYSAPPEND_MBSERIAL, 2, 0x07 },
252 { "MBASSET=", SYSAPPEND_MBASSET, 2, 0x08 },
253 { "BIOSVENDOR=", SYSAPPEND_BIOSVENDOR, 0, 0x04 },
254 { "BIOSVERSION=", SYSAPPEND_BIOSVERSION, 0, 0x05 },
255 { NULL, 0, 0, 0 }
256 };
257
258 /*
259 * Install the string in the string table, if nonempty, after
260 * removing leading and trailing whitespace.
261 */
is_ctl_or_whitespace(char c)262 static bool is_ctl_or_whitespace(char c)
263 {
264 return (c <= ' ' || c == '\x7f');
265 }
266
dmi_install_string(const char * pfx,const char * str)267 static const char *dmi_install_string(const char *pfx, const char *str)
268 {
269 const char *p, *ep;
270 size_t pfxlen;
271 char *nstr, *q;
272
273 if (!str)
274 return NULL;
275
276 while (*str && is_ctl_or_whitespace(*str))
277 str++;
278
279 if (!*str)
280 return NULL;
281
282 ep = p = str;
283 while (*p) {
284 if (!is_ctl_or_whitespace(*p))
285 ep = p+1;
286 p++;
287 }
288
289 pfxlen = strlen(pfx);
290 q = nstr = malloc(pfxlen + (ep-str) + 1);
291 if (!nstr)
292 return NULL;
293 memcpy(q, pfx, pfxlen);
294 q += pfxlen;
295 memcpy(q, str, ep-str);
296 q += (ep-str);
297 *q = '\0';
298
299 return nstr;
300 }
301
sysappend_set_sysff(const uint8_t * type)302 static void sysappend_set_sysff(const uint8_t *type)
303 {
304 static char sysff_str[] = "SYSFF=000";
305
306 if (!type || !*type)
307 return;
308
309 sprintf(sysff_str+6, "%u", *type & 0x7f);
310 sysappend_strings[SYSAPPEND_SYSFF] = sysff_str;
311 }
312
313 struct cpuflag {
314 uint8_t bit;
315 char flag;
316 };
317
sysappend_set_cpu(void)318 static void sysappend_set_cpu(void)
319 {
320 static char cpu_str[6+6] = "CPU=";
321 char *p = cpu_str + 4;
322 static const struct cpuflag cpuflags[] = {
323 { 0*32+ 6, 'P' }, /* PAE */
324 { 1*32+ 5, 'V' }, /* VMX */
325 { 1*32+ 6, 'T' }, /* SMX (TXT) */
326 { 2*32+20, 'X' }, /* XD/NX */
327 { 2*32+29, 'L' }, /* Long mode (x86-64) */
328 { 3*32+ 2, 'S' }, /* SVM */
329 { 0, 0 }
330 };
331 const struct cpuflag *cf;
332
333 /* Not technically from DMI, but it fit here... */
334
335 if (!cpu_has_eflag(EFLAGS_ID)) {
336 /* No CPUID */
337 *p++ = cpu_has_eflag(EFLAGS_AC) ? '4' : '3';
338 } else {
339 uint32_t flags[4], eax, ebx, family;
340 uint32_t ext_level;
341
342 cpuid(1, &eax, &ebx, &flags[1], &flags[0]);
343 family = (eax & 0x0ff00f00) >> 8;
344 *p++ = family >= 6 ? '6' : family + '0';
345
346 ext_level = cpuid_eax(0x80000000);
347 if (ext_level >= 0x80000001 && ext_level <= 0x8000ffff) {
348 cpuid(0x80000001, &eax, &ebx, &flags[3], &flags[2]);
349 } else {
350 flags[2] = flags[3] = 0;
351 }
352
353 for (cf = cpuflags; cf->flag; cf++) {
354 if (test_bit(cf->bit, flags))
355 *p++ = cf->flag;
356 }
357 }
358
359 *p = '\0';
360
361 sysappend_strings[SYSAPPEND_CPU] = cpu_str;
362 }
363
dmi_init(void)364 void dmi_init(void)
365 {
366 const struct sysappend_dmi_strings *ds;
367
368 sysappend_set_cpu();
369
370 dmi_find_header();
371 if (!dmi)
372 return;
373
374 sysappend_set_uuid(dmi_find_data(1, 0x08, 16));
375 sysappend_set_sysff(dmi_find_data(3, 0x05, 1));
376
377 for (ds = dmi_strings; ds->prefix; ds++) {
378 if (!sysappend_strings[ds->sa]) {
379 const char *str = dmi_find_string(ds->index, ds->offset);
380 sysappend_strings[ds->sa] = dmi_install_string(ds->prefix, str);
381 }
382 }
383 }
384