/*
[_Anarchy_(alan@lightning.swansea.uk.linux.org)] you should do one check
   though - if the board seems to be SMP and the CPU in /proc/cpuinfo is non
   intel dont install an SMP kernel - thats a dual pentium board with a cyrix
   or similar single cpu in it
*/


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

#ifdef __i386__
#define SMP_MAGIC_IDENT	(('_'<<24)|('P'<<16)|('M'<<8)|'_')

struct intel_mp_floating
{
	char mpf_signature[4];		/* "_MP_" 			*/
	unsigned long mpf_physptr;	/* Configuration table address	*/
	unsigned char mpf_length;	/* Our length (paragraphs)	*/
	unsigned char mpf_specification;/* Specification version	*/
	unsigned char mpf_checksum;	/* Checksum (makes sum 0)	*/
	unsigned char mpf_feature1;	/* Standard or configuration ? 	*/
	unsigned char mpf_feature2;	/* Bit7 set for IMCR|PIC	*/
	unsigned char mpf_feature3;	/* Unused (0)			*/
	unsigned char mpf_feature4;	/* Unused (0)			*/
	unsigned char mpf_feature5;	/* Unused (0)			*/
};

struct mp_config_table
{
	char mpc_signature[4];
#define MPC_SIGNATURE "PCMP"
	unsigned short mpc_length;	/* Size of table */
	char  mpc_spec;			/* 0x01 */
	char  mpc_checksum;
	char  mpc_oem[8];
	char  mpc_productid[12];
	unsigned long mpc_oemptr;	/* 0 if not present */
	unsigned short mpc_oemsize;	/* 0 if not present */
	unsigned short mpc_oemcount;
	unsigned long mpc_lapic;	/* APIC address */
	unsigned long reserved;
};

/* Followed by entries */

#define	MP_PROCESSOR	0
#define	MP_BUS		1
#define	MP_IOAPIC	2
#define	MP_INTSRC	3
#define	MP_LINTSRC	4

struct mpc_config_processor
{
	unsigned char mpc_type;
	unsigned char mpc_apicid;	/* Local APIC number */
	unsigned char mpc_apicver;	/* Its versions */
	unsigned char mpc_cpuflag;
#define CPU_ENABLED		1	/* Processor is available */
#define CPU_BOOTPROCESSOR	2	/* Processor is the BP */
	unsigned long mpc_cpufeature;		
#define CPU_STEPPING_MASK 0x0F
#define CPU_MODEL_MASK	0xF0
#define CPU_FAMILY_MASK	0xF00
	unsigned long mpc_featureflag;	/* CPUID feature value */
	unsigned long mpc_reserved[2];
};

struct mpc_config_bus
{
	unsigned char mpc_type;
	unsigned char mpc_busid;
	unsigned char mpc_bustype[6] __attribute((packed));
};

#define BUSTYPE_EISA	"EISA"
#define BUSTYPE_ISA	"ISA"
#define BUSTYPE_INTERN	"INTERN"	/* Internal BUS */
#define BUSTYPE_MCA	"MCA"
#define BUSTYPE_VL	"VL"		/* Local bus */
#define BUSTYPE_PCI	"PCI"
#define BUSTYPE_PCMCIA	"PCMCIA"

/* We don't understand the others */

struct mpc_config_ioapic
{
	unsigned char mpc_type;
	unsigned char mpc_apicid;
	unsigned char mpc_apicver;
	unsigned char mpc_flags;
#define MPC_APIC_USABLE		0x01
	unsigned long mpc_apicaddr;
};

struct mpc_config_intsrc
{
	unsigned char mpc_type;
	unsigned char mpc_irqtype;
	unsigned short mpc_irqflag;
	unsigned char mpc_srcbus;
	unsigned char mpc_srcbusirq;
	unsigned char mpc_dstapic;
	unsigned char mpc_dstirq;
};

#define MP_INT_VECTORED		0
#define MP_INT_NMI		1
#define MP_INT_SMI		2
#define MP_INT_EXTINT		3

#define MP_IRQDIR_DEFAULT	0
#define MP_IRQDIR_HIGH		1
#define MP_IRQDIR_LOW		3


struct mpc_config_intlocal
{
	unsigned char mpc_type;
	unsigned char mpc_irqtype;
	unsigned short mpc_irqflag;
	unsigned char mpc_srcbusid;
	unsigned char mpc_srcbusirq;
	unsigned char mpc_destapic;	
#define MP_APIC_ALL	0xFF
	unsigned char mpc_destapiclint;
};


/*
 *	Default configurations
 *
 *	1	2 CPU ISA 82489DX
 *	2	2 CPU EISA 82489DX no IRQ 8 or timer chaining
 *	3	2 CPU EISA 82489DX
 *	4	2 CPU MCA 82489DX
 *	5	2 CPU ISA+PCI
 *	6	2 CPU EISA+PCI
 *	7	2 CPU MCA+PCI
 */


static int smp_found_config=0;

/*
 *	Checksum an MP configuration block.
 */

static int mpf_checksum(unsigned char *mp, int len)
{
	int sum=0;
	while(len--)
		sum+=*mp++;
	return sum&0xFF;
}

static int do_smp_scan_config(unsigned long *bp, unsigned long length)
{
	struct intel_mp_floating *mpf;

/*
	if (sizeof(*mpf)!=16)
		logMessage("Error: MPF size\n");
*/

	while (length>0)
	{
		if (*bp==SMP_MAGIC_IDENT)
		{
			mpf=(struct intel_mp_floating *)bp;
			if (mpf->mpf_length==1 &&
				!mpf_checksum((unsigned char *)bp,16) &&
				(mpf->mpf_specification == 1
				 || mpf->mpf_specification == 4) )
			{
				/*logMessage("Intel MultiProcessor Specification v1.%d\n", mpf->mpf_specification);
				if (mpf->mpf_feature2&(1<<7))
					logMessage("    IMCR and PIC compatibility mode.\n");
				else
					logMessage("    Virtual Wire compatibility mode.\n");
*/
				smp_found_config=1;
				return 1;
			}
		}
		bp+=4;
		length-=16;
	}

	return 0;
}

static int smp_scan_config(int mem_fd, unsigned long base,
			   unsigned long length)
{
	void *p;
	int o;
	
	o=base&0xFFF;
	base-=o;
	length+=o;
	
	p=mmap(0, (length+4095)&0xFFFFF000,  PROT_READ, MAP_SHARED, 
		mem_fd, (base&0xFFFF0000));
	if(p==MAP_FAILED)
	{
		/*logMessage("SMP Probe error: mmap: %s", strerror(errno));*/
		return 1;
	}	
	do_smp_scan_config(p+o, length-o);
	munmap(p, (length+4095)&0xFFFFF000);
	return 0;
}

static int intelDetectSMP(void)
{
        int mem_fd;
        
	mem_fd=open("/dev/mem", O_RDONLY);
	
	if(mem_fd==-1)
	{
	        /*logMessage("Error detecting SMP: /dev/mem: %s", strerror(errno));*/
	}
	
	/*
	 * FIXME: Linux assumes you have 640K of base ram..
	 * this continues the error...
	 *
	 * 1) Scan the bottom 1K for a signature
	 * 2) Scan the top 1K of base RAM
	 * 3) Scan the 64K of bios
	 */
	if (!smp_scan_config(mem_fd, 0x0, 0x400) &&
	    !smp_scan_config(mem_fd, 639*0x400,0x400) &&
	    !smp_scan_config(mem_fd, 0xF0000,0x10000)) {
#if 0

		/*
		 * If it is an SMP machine we should know now, unless the
		 * configuration is in an EISA/MCA bus machine with an
		 * extended bios data area. 
		 *
		 * there is a real-mode segmented pointer pointing to the
		 * 4K EBDA area at 0x40E, calculate and scan it here.
		 *
		 * NOTE! There are Linux loaders that will corrupt the EBDA
		 * area, and as such this kind of SMP config may be less
		 * trustworthy, simply because the SMP table may have been
		 * stomped on during early boot. These loaders are buggy and
		 * should be fixed.
		 */
		unsigned int address;

		address = *(unsigned short *)phys_to_virt(0x40E);
		address<<=4;
		smp_scan_config(mem_fd, address, 0x1000);
		if (smp_found_config)
			/*logMessage("WARNING: MP table in the EBDA can be UNSAFE, contact linux-smp@vger.rutgers.edu if you experience SMP problems!\n");*/
#endif			
	}
/*
	if(smp_found_config)
		logMessage("Detected SMP capable motherboard\n");
	else
		logMessage("Detected non SMP capable motherboard\n");
*/
	return smp_found_config;
}

int detectSMP(void)
{
    static int isSMP = -1;

    if (isSMP != -1)
	return isSMP;

    return isSMP = intelDetectSMP();
}
	
#endif /* __i386__ */
