diff --git a/dmi.c b/dmi.c index f18907f..3abefba 100644 --- a/dmi.c +++ b/dmi.c @@ -18,38 +18,304 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "flash.h" #include "programmer.h" int has_dmi_support = 0; #if STANDALONE +static const struct { + const char *keyword; + unsigned char type; + unsigned char offset; +} flashrom_dmi_strings[] = { + { "system-manufacturer", 1, 0x04 }, + { "system-product-name", 1, 0x05 }, + { "system-version", 1, 0x06 }, + { "baseboard-manufacturer", 2, 0x04 }, + { "baseboard-product-name", 2, 0x05 }, + { "baseboard-version", 2, 0x06 }, + { "chassis-type", 3, 0x05 }, +}; -/* Stub to indicate missing DMI functionality. - * has_dmi_support is 0 by default, so nothing to do here. - * Because dmidecode is not available on all systems, the goal is to implement - * the DMI subset we need directly in this file. +/* A full list of chassis types can be found in the System Management BIOS + * (SMBIOS) Reference Specification 2.7.0 section 7.4.1 "Chassis Types" at + * http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.0.pdf + * The types below are the most common ones. */ +static const struct { + unsigned char type; + unsigned char is_laptop; + const char *name; +} dmi_chassis_types[] = { + {0x01, 0, "Other"}, + {0x02, 0, "Unknown"}, + {0x03, 0, "Desktop",}, + {0x08, 1, "Portable"}, + {0x09, 1, "Laptop"}, + {0x0a, 1, "Notebook"}, + {0x0b, 1, "Hand Held"}, + {0x0e, 1, "Sub Notebook"}, +}; + +static char *dmistrings[ARRAY_SIZE(flashrom_dmi_strings)]; + +static int dmi_checksum(const unsigned char *buf, size_t len) +{ + unsigned char sum = 0; + size_t a; + + for (a = 0; a < len; a++) + sum += buf[a]; + return (sum == 0); +} + +static char *dmi_string(char *buffer, unsigned char length, unsigned char string_id) +{ + size_t i, len; + + if (string_id == 0) + return "Not Specified"; + + buffer += length; /* skip to after the handle's data length byte */ + /* Continue till we hit a null which denotes end of string in dmi + or as long as we're not grabing the first string. The string + should be no longer than 64 bytes. We continue looping because + we "jump" to the data string. */ + for (; string_id > 1; string_id--) { + buffer += strlen(buffer); /* skip previous data strings */ + buffer++; /* skip the data string length byte */ + } + + if (!*buffer) /* as long as the current byte we're on isn't null */ + return ""; + + len = strlen(buffer); + if (len > 64) + len = 64; + + for (i = 0; i < len; i++) /* sometimes we need to fix junk bytes in the string */ + if (buffer[i] < 32 || buffer[i] == 127) + buffer[i] = '.'; + + return buffer; +} + +static int dmi_chassis_type(unsigned char code) +{ + int i; + for (i = 0; i < ARRAY_SIZE(dmi_chassis_types); i++) { + if (code == dmi_chassis_types[i].type) { + break; + } + } + msg_pdbg("DMI string chassis-type: \"%s\"\n", dmi_chassis_types[i].name ); + if (dmi_chassis_types[i].is_laptop) { + msg_pdbg("Laptop detected via DMI\n"); + is_laptop = 1; + } + return 0; +} + +static void dmi_table(unsigned int base, unsigned short len, unsigned short num) +{ + unsigned char *data; + unsigned char *dmi_table_mem; + int i = 0, j = 0; + + dmi_table_mem = physmap_try_ro("DMI Tables", base, len); + if (!dmi_table_mem) { + msg_perr("Unable to access DMI Tables\n"); + return; + } + + data = dmi_table_mem; + + /* 4 is the length of an SMBIOS structure header */ + while (i < num && data+4 <= dmi_table_mem + len) { + unsigned char *next; + /* + * If a short entry is found (less than 4 bytes), not only it + * is invalid, but we cannot reliably locate the next entry. + * Better stop at this point, and let the user know his/her + * table is broken. + */ + if (data[1] < 4) { + msg_perr("Invalid entry length (%u). DMI table is " + "broken! Stop.\n\n", (unsigned int)data[1]); + break; + } + + /* Stop decoding after chassis segment */ + if (data[0] == 4) + break; + + /* look for the next handle */ + next = data + data[1]; + while (next - dmi_table_mem + 1 < len && (next[0] != 0 || next[1] != 0)) + next++; + next += 2; + + for (j = 0; j < ARRAY_SIZE(flashrom_dmi_strings); j++) + { + unsigned char offset = flashrom_dmi_strings[j].offset; + unsigned char type = flashrom_dmi_strings[j].type; + + if (offset >= data[1]) + return; + + + switch ((type << 8)|offset) + { + case 0x0305: /* detect if laptop */ + if (type == data[0]) { + dmi_chassis_type(data[offset]); + } + break; + default: + if (type == data[0]) { + dmistrings[j] = dmi_string((char*)data, data[1], data[offset]); + msg_pdbg("DMI string %s: \"%s\"\n", + flashrom_dmi_strings[j].keyword, dmistrings[j]); + } + } + } + data = next; + i++; + } + + physunmap(dmi_table, len); +} + +static int smbios_decode(unsigned char *buf) +{ + if (!dmi_checksum(buf, buf[0x05]) + || (memcmp(buf + 0x10, "_DMI_", 5) != 0) + || !dmi_checksum(buf + 0x10, 0x0F)) + return 0; + + dmi_table(mmio_readl(buf + 0x18), mmio_readw(buf + 0x16), mmio_readw(buf + 0x1C)); + + return 1; +} + +static int legacy_decode(unsigned char *buf) +{ + if (!dmi_checksum(buf, 0x0F)) + return 0; + + dmi_table(mmio_readl(buf + 0x08), mmio_readw(buf + 0x06), mmio_readw(buf + 0x0C)); + + return 1; +} + void dmi_init(void) { + int found = 0; + size_t fp; + unsigned char *dmi_mem = NULL; + has_dmi_support = 1; + + msg_pdbg("Trying Internal DMI decoder.\n"); + dmi_mem = physmap_try_ro("DMI", 0xF0000, 0x10000); + if (!dmi_mem) + goto func_exit; + + for (fp = 0; fp <= 0xFFF0; fp += 16) { + if (memcmp(dmi_mem + fp, "_SM_", 4) == 0 && fp <= 0xFFE0) { + if (smbios_decode(dmi_mem+fp)) { + found++; + fp += 16; + } + } + else if (memcmp(dmi_mem + fp, "_DMI_", 5) == 0) + if (legacy_decode(dmi_mem + fp)) + found++; + } + +func_exit: + if (!found) + { + msg_pinfo("No DMI table found.\n"); + has_dmi_support = 0; + } + + physunmap(dmi_mem, 0x10000); +} + +/** + * Does an substring/prefix/postfix/whole-string match. + * + * The pattern is matched as-is. The only metacharacters supported are '^' + * at the beginning and '$' at the end. So you can look for "^prefix", + * "suffix$", "substring" or "^complete string$". + * + * @param value The string to check. + * @param pattern The pattern. + * @return Nonzero if pattern matches. + */ +static int dmi_compare(const char *value, const char *pattern) +{ + int anchored = 0; + int patternlen; + + msg_pspew("matching %s against %s\n", value, pattern); + /* The empty string is part of all strings! */ + if (pattern[0] == 0) + return 1; + + if (pattern[0] == '^') { + anchored = 1; + pattern++; + } + + patternlen = strlen(pattern); + if (pattern[patternlen - 1] == '$') { + int valuelen = strlen(value); + patternlen--; + if (patternlen > valuelen) + return 0; + + /* full string match: require same length */ + if (anchored && (valuelen != patternlen)) + return 0; + + /* start character to make ends match */ + value += valuelen - patternlen; + anchored = 1; + } + + if (anchored) + return strncmp(value, pattern, patternlen) == 0; + else + return strstr(value, pattern) != NULL; } int dmi_match(const char *pattern) { + int i; + + if (!has_dmi_support) + return 0; + + for (i = 0; i < ARRAY_SIZE(flashrom_dmi_strings); i++) + if (dmi_compare(dmistrings[i], pattern)) + return 1; + return 0; } #else /* STANDALONE */ static const char *dmidecode_names[] = { "system-manufacturer", "system-product-name", "system-version", "baseboard-manufacturer", "baseboard-product-name", "baseboard-version", }; @@ -112,26 +378,27 @@ static char *get_dmi_string(const char *string_name) result = strdup(answerbuf); if (!result) puts("WARNING: Out of memory - DMI support fails"); return result; } void dmi_init(void) { int i; char *chassis_type; + msg_pdbg("Trying External DMI decoder.\n"); has_dmi_support = 1; for (i = 0; i < ARRAY_SIZE(dmidecode_names); i++) { dmistrings[i] = get_dmi_string(dmidecode_names[i]); if (!dmistrings[i]) { has_dmi_support = 0; return; } } chassis_type = get_dmi_string("chassis-type"); if (chassis_type && (!strcmp(chassis_type, "Notebook") || !strcmp(chassis_type, "Portable"))) { msg_pdbg("Laptop detected via DMI\n");