/*      dmifreq.cpp
//      
//      Copyright 2005-2011 Lucas Tsatiris <systester.project@gmail.com>
//      
//      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 Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.
//      
*/      

/*
 *  This code comes from http://www.nongnu.org/dmidecode/
 *  Ported to Visual C++ by Lucas Tsatiris
 */

/*
 * DMI Decode
 *
 *   (C) 2000-2002 Alan Cox <alan@redhat.com>
 *   (C) 2002-2005 Jean Delvare <khali@linux-fr.org>
 *
 *   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *   For the avoidance of doubt the "preferred form" of this code is one which
 *   is in an open unpatent encumbered format. Where cryptographic key signing
 *   forms part of the process of creating an executable the information
 *   including keys needed to generate an equivalently functional executable
 *   are deemed to be part of the source code.
 *
 * Unless specified otherwise, all references are aimed at the "System
 * Management BIOS Reference Specification, Version 2.4" document,
 * available from http://www.dmtf.org/standards/smbios/.
 *
 * Note to contributors:
 * Please reference every value you add or modify, especially if the
 * information does not come from the above mentioned specification.
 *
 * Additional references:
 *  - Intel AP-485 revision 28
 *    "Intel Processor Identification and the CPUID Instruction"
 *    http://developer.intel.com/design/xeon/applnots/241618.htm
 *  - DMTF Master MIF version 040707
 *    "DMTF approved standard groups"
 *    http://www.dmtf.org/standards/dmi
 *  - IPMI 2.0 revision 1.0
 *    "Intelligent Platform Management Interface Specification"
 *    http://developer.intel.com/design/servers/ipmi/spec.htm
 *  - AMD publication #20734 revision 3.04
 *    "AMD Processor Recognition Application Note"
 *    http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/20734.pdf
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "config.h"
#include "types.h"
#include "util.h"

struct opt
{
  const char *devmem;
  unsigned int flags;
  u8 *type;
  const struct string_keyword *string;
};
struct opt opt;

#if defined (_MSC_VER) || defined (__MINGW32__) /* Windows */
#include "winsmbios.h"
#endif /* _MSC_VER */

#ifdef ALIGNMENT_WORKAROUND
#define HANDLE(x) WORD((u8 *)&(x->handle))
#else /*  */
#define HANDLE(x) x->handle
#endif /*  */
static u16
dmi_table (u32 base, u16 len, u16 num, u16 ver, const char *devmem)
{
  u8 *buf;
  u8 *data;
  int i = 0;
  u16 freq = 1;

  /*
   * if devmem is NULL then base has the SMBIOS Table address
   * already allocated and not the physical memory address that
   * needs to be mapped.
   * This change was made to support Windows 2003 that blocks
   * access to physical memory but returns the SMBIOS Table
   * througth GetSystemFirmwareTable API.
   *
   * see more on winsmbios.h and winsmbios.c
   */
  if (devmem == NULL)
    {
      buf = (u8 *) base;
    }
  else
    {
      if ((buf = (u8 *) mem_chunk (base, len, devmem)) == NULL)
	return 1;
    }
  data = buf;
  while (i < num && data + sizeof (struct dmi_header) <= buf + len)

    {
      u8 *next;
      struct dmi_header *h = (struct dmi_header *) data;
      next = data + h->length;
      while (next - buf + 1 < len && (next[0] != 0 || next[1] != 0))
	next++;
      next += 2;
      if (h->type == 4)

	{
	  if (next - buf <= len)
	    freq = WORD (data + 0x16);
	}
      data = next;
      i++;
    }
  if (devmem != NULL)
    free (buf);
  if (i != num)
    return 1;
  if (data - buf != len)
    return 1;
  return freq;
}

static u16
smbios_decode (u8 * buf, const char *devmem)
{
  u16 freq;
  if (checksum (buf, buf[0x05])
      && memcmp (buf + 0x10, "_DMI_", 5) == 0 && checksum (buf + 0x10, 0x0F))

    {
      freq =
	dmi_table (DWORD (buf + 0x18), WORD (buf + 0x16), WORD (buf + 0x1C),
		   (buf[0x06] << 8) + buf[0x07], devmem);
      return freq;
    }
  return 0;
}

static u16
legacy_decode (u8 * buf, const char *devmem)
{
  u16 freq;
  if (checksum (buf, 0x0F))
    {
      freq =
	dmi_table (DWORD (buf + 0x08), WORD (buf + 0x06), WORD (buf + 0x0C),
		   ((buf[0x0E] & 0xF0) << 4) + (buf[0x0E] & 0x0F), devmem);
      return freq;
    }
  return 0;
}

unsigned short
dmifreq (void)
{
  int found = 0;
  size_t fp;
  u8 *buf;
  u16 ret = 0;

#if defined (_MSC_VER) || defined (__MINGW32__)
  /*
   * these varibles are used only when run on windows 2003 or above.
   * Since these versions block access to physical memory.
   * Windows NT, 2000 and XP still accessing physical memory througth
   * mem_chunck
   */
  int num_structures = 0;
  PRawSMBIOSData smb = NULL;

#endif /* _MSC_VER */
  if (sizeof (u8) != 1 || sizeof (u16) != 2 || sizeof (u32) != 4 || '\0' != 0)
    return 0;

  /* Set default option values */
  opt.devmem = DEFAULT_MEM_DEV;
  opt.flags = 0;

  /*
   * If running on windows, checks if its Windows 2003 or vista and
   * get the SMBIOS data without access to physical memory.
   * If its Windows NT, 2000 or XP, access the physical memory and
   * scans for SMBIOS table entry point, just like all other OS.
   * If its Windows 9x or Me, print error and exits.
   */
#if defined (_MSC_VER) || defined (__MINGW32__)
  switch (get_windows_platform ())
    {
    case WIN_2003_VISTA:	//gets the SMBIOS table, prints values and exits

      //loads the GetSystemFirmwareTable function
      if (!LocateNtdllEntryPoints ())
	{
	  ret = 0;
	  goto exit_free;
	}

      //gets the raw smbios table
      smb = get_raw_smbios_table ();
      num_structures =
	count_smbios_structures ((const char *) &smb->SMBIOSTableData[0],
				 smb->Length);

      //shows the smbios information
      ret =
	dmi_table ((u32) & smb->SMBIOSTableData[0], smb->Length,
		   num_structures,
		   (smb->SMBIOSMajorVersion << 8) + smb->SMBIOSMinorVersion,
		   NULL);
      free (smb);
      goto exit_free;
      break;
    case WIN_UNSUPORTED:	/* prints error and exits */

      ret = 0;
      goto exit_free;
      break;
    default:

      /*
       * do nothing. Follow the code below, scans for the
       * SMBIOS table entry point, etc...
       */
      break;
    }

#endif /* _MSC_VER */
  if ((buf = (u8 *) mem_chunk (0xF0000, 0x10000, opt.devmem)) == NULL)

    {
      ret = 0;
      goto exit_free;
    }
  for (fp = 0; fp <= 0xFFF0; fp += 16)

    {
      if (memcmp (buf + fp, "_SM_", 4) == 0 && fp <= 0xFFE0)

	{
	  ret = smbios_decode (buf + fp, opt.devmem);
	  if (ret > 0)
	    found++;
	  if (ret > 1)
	    goto found_free;
	  fp += 16;
	}

      else if (memcmp (buf + fp, "_DMI_", 5) == 0)

	{
	  ret = legacy_decode (buf + fp, opt.devmem);
	  if (ret > 0)
	    found++;
	  if (ret > 1)
	    goto found_free;
	}
    }
found_free:
  free (buf);
  if (!found)
    ret = 0;
exit_free:
  return ret;
}
