/*
 * Copyright (c) 1996, by Steve Passe
 * All rights reserved.
 *
 * hacked to make it work in userspace Linux by Ingo Molnar, same copyright
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	notice, this list of conditions and the following disclaimer.
 * 2. The name of the developer may NOT be used to endorse or promote products
 *	derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	  $Id: mptable.c,v 1.8 2005/04/25 20:54:12 davej Exp $
 */


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

#include "mptable.h"
#include "x86info.h"

/* EBDA is @ 40:0e in real-mode terms */
#define EBDA_POINTER			0x040e		  /* location of EBDA pointer */

/* CMOS 'top of mem' is @ 40:13 in real-mode terms */
#define TOPOFMEM_POINTER		0x0413		  /* BIOS: base memory size */

#define DEFAULT_TOPOFMEM		0xa0000

#define BIOS_BASE			   0xf0000
#define BIOS_BASE2			  0xe0000
#define BIOS_SIZE			   0x10000
#define ONE_KBYTE			   1024

#define GROPE_AREA1			 0x80000
#define GROPE_AREA2			 0x90000
#define GROPE_SIZE			  0x10000

#define PROCENTRY_FLAG_EN	   0x01
#define PROCENTRY_FLAG_BP	   0x02
#define IOAPICENTRY_FLAG_EN	 0x01

#define MAXPNSTR				132

/* global data */
static int pfd;			/* physical /dev/mem fd */

static int	busses[16];
static int	apics[16];

static int	ncpu;
static int	nbus;
static int	napic;
static int	nintr;
static int verbose_mp;

typedef struct TABLE_ENTRY {
	u8	type;
	u8	length;
	char	name[32];
} tableEntry;

static tableEntry basetableEntryTypes[] =
{
	{ 0, 20, "Processor" },
	{ 1,  8, "Bus" },
	{ 2,  8, "I/O APIC" },
	{ 3,  8, "I/O INT" },
	{ 4,  8, "Local INT" }
};

/* MP Floating Pointer Structure */
typedef struct MPFPS {
	char	signature[4];
	u32		pap;
	u8	length;
	u8	spec_rev;
	u8	checksum;
	u8	mpfb1;
	u8	mpfb2;
	u8	mpfb3;
	u8	mpfb4;
	u8	mpfb5;
} mpfps_t;

/* MP Configuration Table Header */
typedef struct MPCTH {
	char	signature[4];
	u16	base_table_length;
	u8	spec_rev;
	u8	checksum;
	u8	oem_id[8];
	u8	product_id[12];
	u32 oem_table_pointer;
	u16	oem_table_size;
	u16	entry_count;
	u32	apic_address;
	u16	extended_table_length;
	u8	extended_table_checksum;
	u8	reserved;
} mpcth_t;

typedef struct PROCENTRY {
	u8	type;
	u8	apicID;
	u8	apicVersion;
	u8	cpuFlags;
	u32	cpuSignature;
	u32	featureFlags;
	u32	reserved1;
	u32	reserved2;
} ProcEntry;


static void seekEntry(vm_offset_t addr)
{
	if (lseek(pfd, (off_t)addr, SEEK_SET) < 0) {
		perror("/dev/mem seek");
		exit(EXIT_FAILURE);
	}
}

static void readEntry(void* entry, int size)
{
	if (read(pfd, entry, size) != size) {
		perror("readEntry");
		exit(EXIT_FAILURE);
	}
}

static int readType(void)
{
	unsigned char type;

	if (read(pfd, &type, sizeof(unsigned char)) != sizeof(unsigned char)) {
		perror("type read");
		exit(EXIT_FAILURE);
	}

	if (lseek(pfd, -1, SEEK_CUR) < 0) {
		perror("type seek");
		exit(EXIT_FAILURE);
	}

	return (int)type;
}

static void processorEntry(void)
{
	ProcEntry entry;

	/* read it into local memory */
	readEntry(&entry, sizeof(entry));

	/* count it */
	++ncpu;

	if (verbose_mp) {
		printf("#\t%2d", (int) entry.apicID);
		printf("\t 0x%2x", (unsigned int) entry.apicVersion);

		printf("\t %s, %s",
				(entry.cpuFlags & PROCENTRY_FLAG_BP) ? "BSP" : "AP",
				(entry.cpuFlags & PROCENTRY_FLAG_EN) ? "usable" : "unusable");

		printf("\t %d\t %d\t %d",
				(int) (entry.cpuSignature >> 8) & 0x0f,
				(int) (entry.cpuSignature >> 4) & 0x0f,
				(int)  entry.cpuSignature & 0x0f);

		printf("\t 0x%04x\n", entry.featureFlags);
	}
}


static int MPConfigTableHeader(u32 pap)
{
	vm_offset_t paddr;
	mpcth_t	 cth;
	int x;
	int totalSize, t;
	int count, c;

	if (pap == 0) {
		printf("MP Configuration Table Header MISSING!\n");
		return SMP_NO;
	}

	/* convert physical address to virtual address */
	paddr = (vm_offset_t)pap;

	/* read in cth structure */
	seekEntry(paddr);
	readEntry(&cth, sizeof(cth));

	totalSize = cth.base_table_length - sizeof(struct MPCTH);
	count = cth.entry_count;

	/* initialize tables */
	for (x = 0; x < 16; ++x)
		busses[ x ] = apics[ x ] = 0xff;

	ncpu = 0;
	nbus = 0;
	napic = 0;
	nintr = 0;

	/* process all the CPUs */
	if (verbose_mp)
		printf("MP Table:\n#\tAPIC ID\tVersion\tState\t\tFamily\tModel\tStep\tFlags\n");
	for (t = totalSize, c = count; c; c--) {
		if (readType() == 0)
			processorEntry();
		totalSize -= basetableEntryTypes[ 0 ].length;
	}
	if (verbose_mp)
		printf ("\n");

	return SMP_YES;
}



/*
 * set PHYSICAL address of MP floating pointer structure
 */
#define NEXT(X)		((X) += 4)
static int apic_probe(vm_offset_t* paddr)
{
	unsigned int x;
	u16 segment;
	vm_offset_t target;
	unsigned int buffer[BIOS_SIZE];
	const char MP_SIG[]="_MP_";

	/* search Extended Bios Data Area, if present */
	seekEntry((vm_offset_t)EBDA_POINTER);
	readEntry(&segment, 2);
	if (segment) {				/* search EBDA */
		target = (vm_offset_t)segment << 4;
		seekEntry(target);
		readEntry(buffer, ONE_KBYTE);

		for (x = 0; x < ONE_KBYTE / 4; NEXT(x)) {
			if (!strncmp((char *)&buffer[x], MP_SIG, 4)) {
				*paddr = (x*4) + target;
				return 1;
			}
		}
	}

	/* read CMOS for real top of mem */
	seekEntry((vm_offset_t)TOPOFMEM_POINTER);
	readEntry(&segment, 2);
	--segment;						  /* less ONE_KBYTE */
	target = segment * 1024;
	seekEntry(target);
	readEntry(buffer, ONE_KBYTE);

	for (x = 0; x < ONE_KBYTE/4; NEXT(x)) {
		if (!strncmp((char *)&buffer[x], MP_SIG, 4)) {
			*paddr = (x*4) + target;
			return 2;
		}
	}

	/* we don't necessarily believe CMOS, check base of the last 1K of 640K */
	if (target != (DEFAULT_TOPOFMEM - 1024)) {
		target = (DEFAULT_TOPOFMEM - 1024);
		seekEntry(target);
		readEntry(buffer, ONE_KBYTE);

		for (x = 0; x < ONE_KBYTE/4; NEXT(x)) {
			if (!strncmp((char *)&buffer[x], MP_SIG, 4)) {
				*paddr = (x*4) + target;
				return 3;
			}
		}
	}

	/* search the BIOS */
	seekEntry(BIOS_BASE);
	readEntry(buffer, BIOS_SIZE);

	for (x = 0; x < BIOS_SIZE/4; NEXT(x)) {
		if (!strncmp((char *)&buffer[x], MP_SIG, 4)) {
			*paddr = (x*4) + BIOS_BASE;
			return 4;
		}
	}

	/* search the extended BIOS */
	seekEntry(BIOS_BASE2);
	readEntry(buffer, BIOS_SIZE);

	for (x = 0; x < BIOS_SIZE/4; NEXT(x)) {
		if (!strncmp((char *)&buffer[x], MP_SIG, 4)) {
			*paddr = (x*4) + BIOS_BASE2;
			return 4;
		}
	}

	/* search additional memory */
	target = GROPE_AREA1;
	seekEntry(target);
	readEntry(buffer, GROPE_SIZE);

	for (x = 0; x < GROPE_SIZE/4; NEXT(x)) {
		if (!strncmp((char *)&buffer[x], MP_SIG, 4)) {
			*paddr = (x*4) + GROPE_AREA1;
			return 5;
		}
	}

	target = GROPE_AREA2;
	seekEntry(target);
	readEntry(buffer, GROPE_SIZE);

	for (x = 0; x < GROPE_SIZE/4; NEXT(x)) {
		if (!strncmp((char *)&buffer[x], MP_SIG, 4)) {
			*paddr = (x*4) + GROPE_AREA2;
			return 6;
		}
	}

	*paddr = (vm_offset_t)0;
	return 0;
}


int enumerate_cpus(void)
{
	vm_offset_t paddr;
	mpfps_t mpfps;

	/* open physical memory for access to MP structures */
	if ((pfd = open("/dev/mem", O_RDONLY)) < 0) {
		fprintf(stderr, "enumerate_cpus(): /dev/mem: %s\n", strerror(errno));
		return -1;
	}

	/* probe for MP structures */
	if (apic_probe(&paddr) <= 0)
		return 1;

	/* read in mpfps structure*/
	seekEntry(paddr);
	readEntry(&mpfps, sizeof(mpfps_t));

	/* check whether an MP config table exists */
	if (!mpfps.mpfb1)
		if (MPConfigTableHeader(mpfps.pap) == SMP_YES)
			return ncpu;

	return 1;
}

int issmp(int verb)
{
	vm_offset_t paddr;
	mpfps_t mpfps;

	verbose_mp=verb;
	/* open physical memory for access to MP structures */
	if ((pfd = open("/dev/mem", O_RDONLY)) < 0) {
		fprintf(stderr, "issmp(): /dev/mem: %s\n", strerror(errno));
		return -1;
	}

	/* probe for MP structures */
	if (apic_probe(&paddr) <= 0)
		return SMP_NO;

	/* read in mpfps structure*/
	seekEntry(paddr);
	readEntry(&mpfps, sizeof(mpfps_t));

	/* check whether an MP config table exists */
	if (!mpfps.mpfb1)
		return MPConfigTableHeader(mpfps.pap);

	return SMP_NO;
}

#ifdef STANDALONE
int main()
{
	int	numcpu, smp;
	numcpu = enumerate_cpus();
	smp=issmp(1);
	printf("SMP: %d\nCPU: %d\n", smp, numcpu);
	return 0;
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1