/*
 * Mainboard and BIOS identification
 *
 *   (c) 2002 Bernhard "Bero" Rosenkraenzer <bero@arklinux.org>
 *   (c) 2003 Sergei Haller.
 *
 *   $Id: bios.c 858 2006-07-09 06:59:27Z bistr-o-math $
 *
 * Released under the terms of the GPL version 2 or if, and only if,
 * the GPL version 2 is ruled invalid in a court of law, any later
 * version of the GPL.
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <syslog.h>

#include "nvram-wakeup.h"

struct header
{
     __uint8_t      type;
     __uint8_t      size;
     __uint16_t     handle;
};

static char const *__bios_vendor   = NULL;
static char const *__bios_version  = NULL;
static char const *__bios_release  = NULL;

static char const *__board_vendor  = NULL;
static char const *__board_type    = NULL;
static char const *__board_version = NULL;

static int __dmi_probed = 0;
static int __have_bios  = 0;
static int __have_board = 0;

static __uint8_t *data;

char const * __dmi_string(struct header *dmi, int i);
int __dmi_probe(void);

#define EXPORT(x)   char const * x(void) { \
                    if(!__dmi_probed) \
                         __dmi_probe(); \
                    return __##x; \
               }

EXPORT(bios_vendor)
EXPORT(bios_version)
EXPORT(bios_release)

EXPORT(board_vendor)
EXPORT(board_type)
EXPORT(board_version)

#define __PRINTDEBUG fprintf(stderr, " *** file: %s, line: %d\n", __FILE__, __LINE__);

char const * __dmi_string(struct header *dmi, int i)
{
     __uint8_t *bp = (__uint8_t *)dmi;
     __uint8_t   s = bp[i];
     
     if (!s) return NULL;
     
     bp += dmi->size;

     while(--s) {
          if ( (int)bp >= (int)data ) return NULL; /* broken DMI entry */
          bp += strlen((char *)bp) + 1;
     }
     
     nvprintf(LOG_DEBUG, "found string \"%s\"\n", (char const * const)strip((char *)bp));
     return (char const * const)strip((char *)bp);
}

int __dmi_probe(void)
{
     __uint8_t buf[16];
     off_t mem_offset=0; /* offset as number of 16 byte blocks */
     int fd_mem;
     
     /* ... at least we tried ... */
     __dmi_probed = 1;

     /* open the rtc device */
     nvprintf(LOG_DEBUG, "Opening %s in O_RDONLY mode...\n", MEM_DEV);
     fd_mem = open(MEM_DEV, O_RDONLY);

     if (fd_mem < 0) {
          nvprintf(LOG_ERR, "%s: %m\n", MEM_DEV);
          return 0;
     }
     
     if (lseek(fd_mem, 0xE0000L, 0) < 0) {
          nvprintf(LOG_ERR, "%s: %m\n", MEM_DEV);
          return 0;
     }
     
     while (mem_offset < 0x1FFF) {
          mem_offset ++;

          if ( read(fd_mem, buf, 16) != 16) {
               nvprintf(LOG_ERR, "%s: %m\n", MEM_DEV);
               close(fd_mem);
               return 0;
          }

          if (memcmp(buf, "_DMI_", 5)==0) {
               __uint16_t      count = buf[13]<<8  | buf[12];
               __uint16_t      size  = buf[7] <<8  | buf[6];
               __uint32_t      base  = buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8];
               char           *entry = alloca(size);
               struct header  *dm;
               int             i     = 0;
               
               nvprintf(LOG_DEBUG, "_DMI_ table found: base: 0x%X, size: 0x%X, count: %d\n", base, size, count);
              
               if (lseek(fd_mem, (long)base, SEEK_SET) < 0) {
                    nvprintf(LOG_ERR, "%s: %m\n", MEM_DEV);
                    close(fd_mem);
                    return 0;
               }
               if (read(fd_mem, entry, size) != size) {
                    nvprintf(LOG_ERR, "%s: %m\n", MEM_DEV);
                    close(fd_mem);
                    return 0;
               }
               data = (__uint8_t *)entry;
               
               while(i++<count) {

                    if ( (int)data - (int)entry >= size-4 ) break; /* broken DMI euntry */
                    dm = (struct header *)data;

                    nvprintf(LOG_DEBUG, "data block %3d at offset 0x%03X: type %3d, size 0x%03X (%3d)\n", 
                                                  i, (int)data - (int)entry, dm->type, dm->size, dm->size );
               
                    
                    /* compute the address of the next data block */
                    data += dm->size;
                    while(data[0] || data[1]) data++;
                    data+=2;
                    
                    if((!__have_bios)  && dm->type == 0 && dm->size >= 8) { /* BIOS info */
                         __bios_vendor   =__dmi_string(dm, 4 );
                         __bios_version  =__dmi_string(dm, 5 );
                         __bios_release  =__dmi_string(dm, 8 );
                         __have_bios = 1;
                    } else 
                    if((!__have_board) && dm->type == 2 && dm->size >= 6) { /* Mainboard info */
                         __board_vendor  =__dmi_string(dm, 4 );
                         __board_type    =__dmi_string(dm, 5 );
                         __board_version =__dmi_string(dm, 6 );
                         __have_board = 1;
                    } 
               }
          }
     }
     nvprintf(LOG_DEBUG, "Following DMI entries found:\n");
     nvprintf(LOG_DEBUG, " - Mainboard vendor:   %s%s%s\n", (board_vendor() == NULL) ? "" : "\"", (board_vendor() == NULL) ? "NULL" : board_vendor() , (board_vendor() == NULL) ? "" : "\"" );
     nvprintf(LOG_DEBUG, " - Mainboard type:     %s%s%s\n", (board_type()   == NULL) ? "" : "\"", (board_type()   == NULL) ? "NULL" : board_type()   , (board_type()   == NULL) ? "" : "\"" );
     nvprintf(LOG_DEBUG, " - Mainboard revision: %s%s%s\n", (board_version()== NULL) ? "" : "\"", (board_version()== NULL) ? "NULL" : board_version(), (board_version()== NULL) ? "" : "\"" );
     nvprintf(LOG_DEBUG, " - BIOS vendor:        %s%s%s\n", (bios_vendor()  == NULL) ? "" : "\"", (bios_vendor()  == NULL) ? "NULL" : bios_vendor()  , (bios_vendor()  == NULL) ? "" : "\"" );
     nvprintf(LOG_DEBUG, " - BIOS version:       %s%s%s\n", (bios_version() == NULL) ? "" : "\"", (bios_version() == NULL) ? "NULL" : bios_version() , (bios_version() == NULL) ? "" : "\"" );
     nvprintf(LOG_DEBUG, " - BIOS release:       %s%s%s\n", (bios_release() == NULL) ? "" : "\"", (bios_release() == NULL) ? "NULL" : bios_release() , (bios_release() == NULL) ? "" : "\"" );
     close(fd_mem);
     return 1;
}
