/*
 * dmi.c -- code to get DMI information
 *
 * This code originally came from file arch/i386/kernel/dmi_scan.c from 
 * Linux kernel version 2.4.18
 * 
 * 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, 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.
 */


#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/apm_bios.h>
#include <linux/slab.h>

#include <asm/io.h>
#include <asm/system.h>

#ifdef OMNIBOOK_STANDALONE
#include "omnibook.h"
#else
#include <linux/omnibook.h>
#endif

#include "dmi.h"

struct dmi_header
{
	u8	type;
	u8	length;
	u16	handle;
};

static char * __init dmi_string(struct dmi_header *dm, u8 s)
{
	u8 *bp=(u8 *)dm;
	bp+=dm->length;
	if(!s)
		return "";
	s--;
	while(s>0 && *bp)
	{
		bp+=strlen(bp);
		bp++;
		s--;
	}
	return bp;
}

/*
 *	We have to be cautious here. We have seen BIOSes with DMI pointers
 *	pointing to completely the wrong place for example
 */
 
static int __init dmi_table(u32 base, int len, int num, void (*decode)(struct dmi_header *))
{
	u8 *buf;
	struct dmi_header *dm;
	u8 *data;
	int i=0;
		
	buf = ioremap(base, len);
	if(buf==NULL)
		return -1;

	data = buf;

	/*
 	 *	Stop when we see all the items the table claimed to have
 	 *	OR we run off the end of the table (also happens)
 	 */
 
	while(i<num && data-buf+sizeof(struct dmi_header)<=len)
	{
		dm=(struct dmi_header *)data;
		/*
		 *  We want to know the total length (formated area and strings)
		 *  before decoding to make sure we won't run off the table in
		 *  dmi_decode or dmi_string
		 */
		data+=dm->length;
		while(data-buf<len-1 && (data[0] || data[1]))
			data++;
		if(data-buf<len-1)
			decode(dm);
		data+=2;
		i++;
	}
	iounmap(buf);
	return 0;
}

inline static int __init dmi_checksum(u8 *buf)
{
	u8 sum=0;
	int a;

	for(a=0; a<15; a++)
		sum+=buf[a];
	return (sum==0);
}

static int __init dmi_iterate(void (*decode)(struct dmi_header *))
{
	u8 buf[15];
	u32 fp=0xF0000;

#ifdef CONFIG_SIMNOW
	/*
	 *      Skip on x86/64 with simnow. Will eventually go away
	 *      If you see this ifdef in 2.6pre mail me !
	 */
	return -1;
#endif

	while( fp < 0xFFFFF)
	{
		isa_memcpy_fromio(buf, fp, 15);
		if(memcmp(buf, "_DMI_", 5)==0 && dmi_checksum(buf))
		{
			u16 num=buf[13]<<8|buf[12];
			u16 len=buf[7]<<8|buf[6];
			u32 base=buf[11]<<24|buf[10]<<16|buf[9]<<8|buf[8];

			if(dmi_table(base,len,num,decode)==0)
				return 0;
		}
		fp+=16;
	}
	return -1;
}

static char *omnibook_dmi_ident[OMNIBOOK_STRING_MAX];

/*
 *	Save a DMI string
 */
 
static void __init dmi_save_ident(struct dmi_header *dm, int slot, int string)
{
	char *d = (char*)dm;
	char *p = dmi_string(dm, d[string]);
	if(p==NULL || *p == 0)
		return;
	if (omnibook_dmi_ident[slot])
		return;
	omnibook_dmi_ident[slot] = kmalloc(strlen(p)+1, GFP_KERNEL);
	if(omnibook_dmi_ident[slot])
		strcpy(omnibook_dmi_ident[slot], p);
	else
		printk(KERN_ERR "%s: dmi_save_ident: out of memory.\n", MODULE_NAME);
}

/*
 *	Process a DMI table entry. Right now all we care about are the BIOS
 *	and machine entries. For 2.5 we should pull the smbus controller info
 *	out of here.
 */

static void __init dmi_decode(struct dmi_header *dm)
{
	switch(dm->type)
	{
		case  0:
			dmi_save_ident(dm, OMNIBOOK_BIOS_VENDOR, 4);
			dmi_save_ident(dm, OMNIBOOK_BIOS_VERSION, 5);
			dmi_save_ident(dm, OMNIBOOK_BIOS_DATE, 8);
			break;
			
		case 1:
			dmi_save_ident(dm, OMNIBOOK_SYS_VENDOR, 4);
			dmi_save_ident(dm, OMNIBOOK_PRODUCT_NAME, 5);
			dmi_save_ident(dm, OMNIBOOK_PRODUCT_VERSION, 6);
			dmi_save_ident(dm, OMNIBOOK_SERIAL_NUMBER, 7);
			break;
		case 2:
			dmi_save_ident(dm, OMNIBOOK_BOARD_VENDOR, 4);
			dmi_save_ident(dm, OMNIBOOK_BOARD_NAME, 5);
			dmi_save_ident(dm, OMNIBOOK_BOARD_VERSION, 6);
			break;
	}
}

int __init omnibook_dmi_scan_machine(void)
{
	int err = dmi_iterate(dmi_decode);
	return err;
}

EXPORT_SYMBOL(omnibook_dmi_ident);

/* End of file */
