#!/usr/bin/perl -w
#
# dumpdmi 0.2
# Chip Salzenberg <chip@valinux.com>
#

use strict;

(my $ME = $0) =~ s#.*/##;

my $DEBUG;
if (@ARGV && $ARGV[0] eq '-d') {
    ++$DEBUG;
    shift;
}

my $BIOS;
open MEM, '/dev/mem'
  or die "$ME: Can't open /dev/mem: $!\n";
sysseek MEM, 0xE0000, 0;
sysread MEM, $BIOS, 0x20000
  or die "$ME: Can't read BIOS from /dev/mem?\n";

while ( $BIOS =~ /_DMI_/g
	&& (pos($BIOS) & 0x0F) != 5 )
    {}
die "$ME: Can't find DMI in BIOS\n"
  unless pos($BIOS);

my $info = substr($BIOS, pos($BIOS) + 1, 10);
my ($DSIZE, $DBASE, $DCOUNT, $DVER) = unpack('v V v C', $info);
$DVER = ($DVER >> 4) . '.' . ($DVER & 0x0F);

print "DMI Version: $DVER\n";

printf "DMI table at 0x%X contains %d structures occupying %d bytes.\n",
	$DBASE, $DCOUNT, $DSIZE
    if $DEBUG;

sysseek MEM, $DBASE, 0;
sysread MEM, $_, $DSIZE
  or die "$ME: Can't read DMI table: $!\n";

my $n = 0;
my $last_type = 0;
while (my $header = substr($_, 0, 4, '')) {
    my ($type, $dlen, $handle) = unpack('CCv', $header);
    last if $type < $last_type || $dlen < 4;

    my $data = substr($_, 0, $dlen - 4, '');
    my @data = unpack 'C*', $data;
    my @str = m{ \G ([^\0]+) \0 }xg;

    if ($DEBUG) {
	printf "DMI type=%02X data=%s\n",
		$type,
		join(':',  map { sprintf '%02X', $_ } @data);
	if (my @s = @str) {
	    for (@s) { s/\\/\\\\/g; s/"/\\"/g; }
	    printf "    strings={ %s }\n",
		    join(', ', map { '"'.$_.'"' } @s);
	}
    }

    my $str = sub {
	my $n = $data[+shift];
	(defined($n) && $n > 0 && $n <= @str)
	    or return '<ERROR>';
	local $_ = $str[$n - 1];
	s/^\s+//;
	s/\s+$//;
	$_;
    };

    if ($type == 0) {
	print "BIOS Vendor: ",		$str->(0), "\n";
	print "BIOS Version: ",		$str->(1), "\n";
	print "BIOS Release: ",		$str->(4), "\n";
    }
    elsif ($type == 1) {
	print "System Vendor: ",	$str->(0), "\n";
	print "System Name: ",		$str->(1), "\n";
	print "System Version: ",	$str->(2), "\n";
	print "System Serial Number: ",	$str->(3), "\n";
    }
    elsif ($type == 2) {
	print "Board Vendor: ",		$str->(0), "\n";
	print "Board Name: ",		$str->(1), "\n";
	print "Board Version: ",	$str->(2), "\n";
	print "Board Serial Number: ",	$str->(3), "\n";
    }
    elsif ($type == 3) {
	print "Asset Tag: ",		$str->(4), "\n";
    }

    last unless s/^.*?\0\0//;
    last if ++$n >= $DCOUNT;

    $last_type = $type;
}
