Patchwork Native DMI parser and cross checking with dmidecode

login
register
about
Submitter Sean Nelson
Date 2010-06-24 01:13:02
Message ID <4C22B11E.4050502@gmail.com>
Download mbox | patch
Permalink /patch/1550/
State Superseded
Headers show

Comments

Sean Nelson - 2010-06-24 01:13:02
The DMI Parser is based on dmidecode 2.10's dmidecode.c
Stripped to bare minimums and only parses the System, Baseboard, and 
Chassis tables.
Uses our physmap calls for hardware access.
All of the cross-check code is contained in '#if 1' to easily find and 
shut off usage.
Added my copyright line.

Signed-off-by: Sean Nelson <audiohacked@gmail.com>
From f354e65589533f1313d9d323eda940f09fb9d5d3 Mon Sep 17 00:00:00 2001
From: Sean Nelson <audiohacked@gmail.com>
Date: Wed, 23 Jun 2010 17:52:14 -0700
Subject: [PATCH] our own dmi parser with cross-check


Signed-off-by: Sean Nelson <audiohacked@gmail.com>
---
 dmi.c |  233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 209 insertions(+), 24 deletions(-)

Patch

diff --git a/dmi.c b/dmi.c
index 40ca85c..09b2fba 100644
--- a/dmi.c
+++ b/dmi.c
@@ -1,76 +1,230 @@ 
 /*
  * This file is part of the flashrom project.
  *
  * Copyright (C) 2009,2010 Michael Karcher
+ * Copyright (C) 2010 Sean Nelson
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  */
 
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
 
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
 #include "flash.h"
 
 int has_dmi_support = 0;
 
 #if STANDALONE
 
 /* 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.
  */
 void dmi_init(void)
 {
 }
 
 int dmi_match(const char *pattern)
 {
 	return 0;
 }
 
 #else /* STANDALONE */
 
-const char *dmidecode_names[] = {
-	"system-manufacturer",
-	"system-product-name",
-	"system-version",
-	"baseboard-manufacturer",
-	"baseboard-product-name",
-	"baseboard-version",
+/* the actual code, based on the dmidecode parsing logic */
+static const struct string_keyword {
+	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 },
 };
 
+#if 1
 #define DMI_COMMAND_LEN_MAX 260
 const char *dmidecode_command = "dmidecode";
+char *external_dmi[ARRAY_SIZE(flashrom_dmi_strings)];
+#define DMI_MAX_ANSWER_LEN 4096
+#endif
 
-char *dmistrings[ARRAY_SIZE(dmidecode_names)];
+char *dmistrings[ARRAY_SIZE(flashrom_dmi_strings)];
 
-/* Strings longer than 4096 in DMI are just insane. */
-#define DMI_MAX_ANSWER_LEN 4096
+#define WORD(x) (unsigned short)(*(const unsigned short *)(x))
+#define DWORD(x) (unsigned int)(*(const unsigned int *)(x))
+
+static int 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 *bp, unsigned char length, unsigned char s)
+{
+	size_t i, len;
+
+	if (s == 0)
+		return "Not Specified";
+
+	bp += length;
+	while (s > 1 && *bp) {
+		bp += strlen(bp);
+		bp++;
+		s--;
+	}
+
+	if (!*bp)
+		return "<BAD INDEX>";
+
+	len = strlen(bp);
+	for (i = 0; i < len; i++)
+		if (bp[i] < 32 || bp[i] == 127)
+			bp[i] = '.';
+
+	return bp;
+}
+
+static int dmi_chassis_type(unsigned char code)
+{
+	switch(code) {
+		case 0x08: /* Portable */
+		case 0x09: /* Laptop */
+		case 0x0A: /* Notebook */
+		case 0x0E: /* Sub Notebook */
+			return 1;
+		default:
+			return 0;
+	}
+}
+
+static void dmi_table(unsigned int base, unsigned short len, unsigned short num)
+{
+	unsigned char *data;
+	unsigned char *dmi_table_mem;
+	int key;
+	int i=0, j=0;
+
+	dmi_table_mem = physmap_try_ro("DMI Tables", base, len);
+	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;
+
+			key = (type << 8) | offset;
+
+			switch (key)
+			{
+			case 0x305: /* detect if laptop */
+				if (dmi_chassis_type(data[offset])) {
+					msg_pdbg("Laptop detected via DMI\n");
+					is_laptop = 1;
+				}
+				break;
+			default:
+				if (type == data[0]) {
+					dmistrings[j] = dmi_string((char*)data,	data[1], data[offset]);
+					msg_pinfo("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 (!checksum(buf, buf[0x05]) 
+		|| (memcmp(buf + 0x10, "_DMI_", 5) != 0) 
+		|| !checksum(buf + 0x10, 0x0F))
+			return 0;
+
+	dmi_table(DWORD(buf + 0x18), WORD(buf + 0x16), WORD(buf + 0x1C));
+
+	return 1;
+}
+
+static int legacy_decode(unsigned char *buf)
+{
+	if (!checksum(buf, 0x0F))
+		return 0;
+
+	dmi_table(DWORD(buf + 0x08), WORD(buf + 0x06), WORD(buf + 0x0C));
+
+	return 1;
+}
+
+#if 1
 static char *get_dmi_string(const char *string_name)
 {
 	FILE *dmidecode_pipe;
 	char *result;
 	char answerbuf[DMI_MAX_ANSWER_LEN];
 	char commandline[DMI_COMMAND_LEN_MAX + 40];
 
 	snprintf(commandline, sizeof(commandline),
 		 "%s -s %s", dmidecode_command, string_name);
 	dmidecode_pipe = popen(commandline, "r");
 	if (!dmidecode_pipe) {
 		msg_perr("DMI pipe open error\n");
 		return NULL;
@@ -96,48 +250,79 @@  static char *get_dmi_string(const char *string_name)
 
 	/* Chomp trailing newline. */
 	if (answerbuf[0] != 0 &&
 	    answerbuf[strlen(answerbuf) - 1] == '\n')
 		answerbuf[strlen(answerbuf) - 1] = 0;
 	msg_pdbg("DMI string %s: \"%s\"\n", string_name, answerbuf);
 
 	result = strdup(answerbuf);
 	if (!result)
 		puts("WARNING: Out of memory - DMI support fails");
 
 	return result;
 }
+#endif
 
 void dmi_init(void)
 {
-	int i;
-	char *chassis_type;
+	int i = 0;
+	int found = 0;
+	size_t fp;
+	unsigned char *dmi_mem;
 
 	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;
+
+	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++;
+	}
+
+#if 1
+	for (i = 0;  i < ARRAY_SIZE(flashrom_dmi_strings); i++)
+	{
+		external_dmi[i] = get_dmi_string(flashrom_dmi_strings[i].keyword);
+		if (!external_dmi[i])
+			goto func_exit;
 	}
 
-	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");
-		is_laptop = 1;
+	for (i = 0;  i < ARRAY_SIZE(flashrom_dmi_strings)-1; i++)
+	{
+		if (strcmp(dmistrings[i], external_dmi[i])) 
+			msg_perr("dmidecode vs internal-dmi differs: %s\n", flashrom_dmi_strings[i].keyword);
+		else
+			msg_pspew("Matching of dmidecode and internal-dmi succeeded!\n");
 	}
-	free(chassis_type);
+#endif
+
+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.
  */
@@ -175,21 +360,21 @@  static int dmi_compare(const char *value, const char *pattern)
 	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(dmidecode_names); i++)
+	for (i = 0; i < ARRAY_SIZE(flashrom_dmi_strings); i++)
 		if (dmi_compare(dmistrings[i], pattern))
 			return 1;
 
 	return 0;
 }
 
 #endif /* STANDALONE */