Greg Hartman | 76d05dc | 2016-11-23 15:51:27 -0800 | [diff] [blame] | 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 | |
| 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 | |
| 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 | |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 262 | static bool is_ctl_or_whitespace(char c) |
| 263 | { |
| 264 | return (c <= ' ' || c == '\x7f'); |
| 265 | } |
| 266 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | } |