/*****************************************************************************
Detect/identify ISA (16-bit) boards using Plug 'n Play (PnP).
Chris Giese <geezer@execpc.com>	http://www.execpc.com/~geezer
Last revised: Sep 3, 2002

This code is based on Linux isapnptools:

	Copyright
	=========
	These programs are copyright P.J.H.Fox (fox@roestock.demon.co.uk)
	and distributed under the GPL (see COPYING).

Bugs are due to Giese. Or maybe not...
*****************************************************************************/
#include <stdio.h> /* printf(), putchar() */

/* ports */
#define PNP_ADR_PORT		0x279
#define PNP_WRITE_PORT		0xA79

/* MIN and MAX READ_ADDR must have the bottom two bits set */
#define MIN_READ_ADDR		0x203
#define MAX_READ_ADDR		0x3FF
/* READ_ADDR_STEP must be a multiple of 4 */
#define READ_ADDR_STEP		8

/* bits */
#define CONFIG_WAIT_FOR_KEY	0x02
#define CONFIG_RESET_CSN	0x04

#define	IDENT_LEN		9

static int g_port, g_read_port;
static unsigned char g_csum, g_backspaced, g_backspaced_char;

/***************************** TURBO C FOR DOS ******************************/
#if defined(__TURBOC__)
#if !defined(__WIN32__)
#include <dos.h> /* inportb(), outportb(), [_]disable(), [_]enable() */
#else
/************************ TURBO/BORLAND C FOR WIN32 *************************/
/* I doubt this code will run under any flavor of Windows NT.
The fact that it works under Win95 was a great surprise to me... */
unsigned char inportb(unsigned port) {
	_DX = port;
	__emit__(0xEC); /* IN AL,DX */
	return _AL;
}
void outportb(unsigned port, unsigned val) {
	_DX = port;
	_AX = val;
	__emit__(0xEE); /* OUT DX,AL */
}
void enable(void) {
	__emit__(0xFB); /* STI */
}
void disable(void) {
	__emit__(0xFA); /* CLI */
}
#endif
/********************************* MINGW ************************************/
#elif defined(__MINGW32__)
/* Same notes as Borland C for Windows:
code works under Win95 but probably not under Windows NT */
unsigned inportb(unsigned port) {
	unsigned char rv;
	__asm__ __volatile__(
		"inb %w1,%0"
		: "=a"(rv)
		: "d"(port));
	return rv;
}
void outportb(unsigned port, unsigned val) {
	__asm__ __volatile__(
		"outb %b0,%w1"
		:
		: "a"(val), "d"(port));
}
void enable(void) {
	__asm__ __volatile__(
		"sti"
/* 		:
		: */
		);
}
void disable(void) {
	__asm__ __volatile__(
		"cli"
/*		:
		: */
		);
}

/********************************* DJGPP ************************************/
#elif defined(__DJGPP__)
#include <dos.h> /* inportb(), outportb(), [_]disable(), [_]enable() */

/******************************** WATCOM C **********************************/
#elif defined(__WATCOMC__)
#include <conio.h> /* inp(), outp() */
#include <dos.h> /* inportb(), outportb(), [_]disable(), [_]enable() */

#define	inportb(P)	inp(P)
#define	outportb(P,V)	outp(P,V)

#define	disable()	_disable()
#define	enable()	_enable()

#else
#error Not Turbo C, not DJGPP, not Watcom C. Sorry.
#endif
/*****************************************************************************
*****************************************************************************/
static unsigned short ctc(void)
{
	union
	{
		unsigned char byte[2];
		signed short word;
	} ret_val;

	disable();
	outportb(0x43, 0x00);		/* latch channel 0 value */
	ret_val.byte[0] = inportb(0x40); /* read LSB */
	ret_val.byte[1] = inportb(0x40); /* read MSB */
	enable();
	return -ret_val.word;		/* convert down count to up count */
}
/*****************************************************************************
xxx - use Turbo C/DJGPP clock() function?
*****************************************************************************/
static void usleep(unsigned microseconds)
{
	unsigned short curr, prev;
	union
	{
		unsigned short word[2];
		unsigned long dword;
	} end;

	prev = ctc();
	end.dword = prev + ((unsigned long)microseconds * 500) / 419;
	for (;;)
	{
		curr = ctc();
		if (end.word[1] == 0)
		{
			if (curr >= end.word[0])
				break;
		}
		if (curr < prev)		/* timer overflowed */
		{
			if (end.word[1] == 0)
				break;
			(end.word[1])--;
		}
		prev = curr;
	}
}
/*****************************************************************************
*****************************************************************************/
static void pnp_poke(unsigned char x)
{
	outportb(PNP_WRITE_PORT, x);
}
/*****************************************************************************
*****************************************************************************/
static unsigned char pnp_peek(void)
{
	return inportb(g_read_port);
}
/*****************************************************************************
*****************************************************************************/
static void pnp_wake(unsigned char x)
{
	outportb(PNP_ADR_PORT, 3);
	outportb(PNP_WRITE_PORT, x);
}
/*****************************************************************************
*****************************************************************************/
static void pnp_set_read_port(unsigned x)
{
	outportb(PNP_ADR_PORT, 0);
	outportb(PNP_WRITE_PORT, x >> 2);
	g_read_port = x | 3;
}
/*****************************************************************************
*****************************************************************************/
static unsigned char pnp_status(void)
{
	outportb(PNP_ADR_PORT, 5);
	return pnp_peek();
}
/*****************************************************************************
*****************************************************************************/
static unsigned char pnp_resource_data(void)
{
	outportb(PNP_ADR_PORT, 4);
	return pnp_peek();
}
/*****************************************************************************
*****************************************************************************/
static int pnp_await_status(void)
{
	unsigned timeout;

	for (timeout = 5; timeout != 0; timeout--)
	{
		usleep(1000);
		if ((pnp_status() & 1) != 0)
			break;
	}
	if (timeout == 0)
	{
		printf("pnp_await_status: timeout\n");
		return 1;
	}
	return 0;
}
/*****************************************************************************
*****************************************************************************/
static int pnp_read_resource_data(unsigned char *result)
{
	if (pnp_await_status())
		return 1;
	if (g_backspaced)
	{
		*result = g_backspaced_char;
		g_backspaced = 0;
	}
	*result = pnp_resource_data();
	g_csum = (g_csum + *result) & 0xFF;
	return 0;
}
/*****************************************************************************
*****************************************************************************/
static void pnp_unread_resource_data(char prev)
{
	g_csum = (g_csum - prev) & 0xFF;
	g_backspaced = 1;
	g_backspaced_char = prev;
}
/*****************************************************************************
*****************************************************************************/
static int pnp_read_one_resource(void)
{
	unsigned char buffer[256], res_type;
	unsigned short i, res_len;
	unsigned long adr;

	if (pnp_read_resource_data(buffer + 0) != 0)
		return 1;
/* Large item */
	if (buffer[0] & 0x80)
	{
		res_type = buffer[0];
		for (i = 0; i <= 1; i++)
		{
			if (pnp_read_resource_data(buffer + i) != 0)
				return 1;
		}
		res_len = buffer[1];
		res_len <<= 8;
		res_len |= buffer[0];
		printf("large item of size %3u: ", res_len);
	}
/* Small item */
	else
	{
		res_type = (buffer[0] >> 3) & 0x0f;
		res_len = buffer[0] & 7;
		printf("small item of size %3u: ", res_len);
	}
	for (i = 0; i < res_len; i++)
	{
		if (pnp_read_resource_data(buffer + i) != 0)
			return 1;
	}
/* */
	switch (res_type)
	{

#define IRQ_TAG			0x04
#define DMA_TAG			0x05
//#define StartDep_TAG		0x06
//#define EndDep_TAG		0x07
#define IOport_TAG		0x08
#define FixedIO_TAG		0x09
#define End_TAG			0x0F

/* Long  Tags */
#define MemRange_TAG		0x81
#define ANSIstr_TAG		0x82
//efine UNICODEstr_TAG		0x83
//efine VendorLong_TAG		0x84
#define Mem32Range_TAG		0x85
#define FixedMem32Range_TAG	0x86
	case IRQ_TAG:
		i = buffer[1];
		i <<= 8;
		i |= buffer[0];
		printf("IRQ_TAG: 0x%X\n", i);
		break;
	case DMA_TAG:
		printf("DMA_TAG: 0x%X\n", buffer[0]);
		break;
	case IOport_TAG:
		i = buffer[2];
		i <<= 8;
		i |= buffer[1];
		printf("IOport_TAG: start=0x%X, ", i);

		i = buffer[4];
		i <<= 8;
		i |= buffer[3];
		i++;
		printf("end=0x%X, step=%u, size=%u\n",
			i, buffer[5], buffer[6]);
		break;
	case FixedIO_TAG:
		i = buffer[1];
		i <<= 8;
		i |= buffer[0];
		printf("FixedIO_TAG: start=0x%X, size=%u, end=0x%X\n",
			i, buffer[2], i + 1);
		break;
	case MemRange_TAG:
		adr = buffer[2];
		adr <<= 8;
		adr |= buffer[1];
		adr <<= 8;
		printf("MemRange_TAG: start=0x%lX, ", adr);

		adr = buffer[4];
		adr <<= 8;
		adr |= buffer[3];
		adr <<= 8;
		adr++;
		printf("end=0x%lX, ", adr);

		i = buffer[6];
		i <<= 8;
		i |= buffer[5];
		printf("step=0x%X, ", i);

		adr = buffer[8];
		adr <<= 8;
		adr |= buffer[7];
		adr <<= 8;
		adr++;
		printf("size=0x%lX, ", adr);
		break;
	case ANSIstr_TAG:
		printf("ANSIstr_TAG: '");
		for (i = 0; i < res_len; i++)
			putchar(buffer[i]);
		printf("'\n");
		break;
#if 0
	case Mem32Range_TAG:
		res->start = res->end = 0;
		/* STUB */
		break;
	case FixedMem32Range_TAG:
		res->start = res->end = 0;
		/* STUB */
		break;
#endif
	case End_TAG:
		printf("End_TAG\n");
		return 1;
	default:
		printf("unknown tag type %u\n", res_type);
		break;
	}
	return 0;
}
/*****************************************************************************
*****************************************************************************/
static void pnp_read_board(unsigned csn, unsigned char serial_id0)
{
	unsigned char temp, dummy;
	unsigned i;

	pnp_wake(csn);
/* Check for broken cards that don't reset their resource pointer properly.
Get the first byte */
	if (pnp_read_resource_data(&temp) != 0)
		return;
/* Just check the first byte, if these match we assume it's ok */
	if (temp != serial_id0)
	{
/* Assume the card is broken and this is the start of the resource data. */
		pnp_unread_resource_data(temp);
		goto broken;
	}
/* Read resource data past serial identifier */
	for (i = 1; i < IDENT_LEN; i++)
	{
		if (pnp_read_resource_data(&dummy) != 0)
			return;
	}
/* Now for the actual resource data */
	g_csum = 0;
broken:
	do
	{
		i = pnp_read_one_resource();
	} while (!i);
}
/*****************************************************************************
*****************************************************************************/
static int pnp_isolate(void)
{
	static unsigned boards_found;
/**/
	unsigned char checksum = 0, good_adr = 0;
	unsigned char c1, c2, bit, new_bit, serial_id[IDENT_LEN];
	unsigned short byte;

	checksum = 0x6A;
/* Assume we will find one */
	boards_found++;
	pnp_wake(0);
	pnp_set_read_port(g_port);
	usleep(1000);
	outportb(PNP_ADR_PORT, 0x01); /* SERIALISOLATION */
	usleep(1000);
	for (byte = 0; byte < IDENT_LEN - 1; byte++)
	{
/* xxx - tighten this up */
		for (bit = 8; bit != 0; bit--)
		{
			new_bit = 0;
			usleep(250);
			c1 = pnp_peek();
			usleep(250);
			c2 = pnp_peek();
			if (c1 == 0x55)
			{
				if (c2 == 0xAA)
				{
					good_adr = 1;
					new_bit = 0x80;
				}
				else
					good_adr = 0;
			}
			serial_id[byte] >>= 1;
			serial_id[byte] |= new_bit;
/* Update checksum */
			if (((checksum >> 1) ^ checksum) & 1)
				new_bit ^= 0x80;
			checksum >>= 1;
			checksum |= new_bit;
		}
	}
	for (bit = 8; bit != 0; bit--)
	{
		new_bit = 0;
		usleep(250);
		c1 = pnp_peek();
		usleep(250);
		c2 = pnp_peek();
		if (c1 == 0x55)
		{
			if (c2 == 0xAA)
			{
				good_adr = 1;
				new_bit = 0x80;
			}
		}
		serial_id[byte] >>= 1;
		serial_id[byte] |= new_bit;
	}
	if (good_adr && (checksum == serial_id[byte]))
	{
		outportb(PNP_ADR_PORT, 0x06); /* CARDSELECTNUMBER */
		pnp_poke(boards_found);

		printf("found board #%u\n", boards_found);
		pnp_read_board(boards_found, serial_id[0]);
		return 1;
	}
/* We didn't find one */
	boards_found--;
	return 0;
}
/*****************************************************************************
*****************************************************************************/
static void pnp_send_key(void)
{
/* Turbo C++ 1.0 seems to "lose" anything declared 'static const'
	static const char i_data[] = */
	static char i_data[] =
	{
		0x6A, 0xB5, 0xDA, 0xED, 0xF6, 0xFB, 0x7D, 0xBE,
		0xDF, 0x6F, 0x37, 0x1B, 0x0D, 0x86, 0xC3, 0x61,
		0xB0, 0x58, 0x2C, 0x16, 0x8B, 0x45, 0xA2, 0xD1,
		0xE8, 0x74, 0x3A, 0x9D, 0xCE, 0xE7, 0x73, 0x39
	};
/**/
	unsigned short temp;

	outportb(PNP_ADR_PORT, 0);
	outportb(PNP_ADR_PORT, 0);
	for (temp = 0; temp < sizeof(i_data) / sizeof(char); temp++)
		outportb(PNP_ADR_PORT, i_data[temp]);
	usleep(2000);
}
/*****************************************************************************
*****************************************************************************/
void pnp_init(void)
{
	g_read_port=MIN_READ_ADDR;
/* All cards now isolated, read the first one */
	g_port = MIN_READ_ADDR;
	for (; g_port <= MAX_READ_ADDR; g_port += READ_ADDR_STEP)
	{
/* Make sure all cards are in Wait For Key */
		outportb(PNP_ADR_PORT, 0x02); /* CONFIGCONTROL */
		pnp_poke(CONFIG_WAIT_FOR_KEY);
/* Make them listen */
		pnp_send_key();
/* Reset the cards */
		outportb(PNP_ADR_PORT, 0x02); /* CONFIGCONTROL */
		pnp_poke(CONFIG_RESET_CSN | CONFIG_WAIT_FOR_KEY);
		usleep(2000);
/* Send the key again */
		pnp_send_key();
		printf("Trying port address %04x\n", g_port);
/* Look for a PnP board */
		if (pnp_isolate())
			break;
	}
	if (g_port > MAX_READ_ADDR)
	{
		printf("No boards found\n");
		return;
	}
/* found one board: print info then isolate the other boards
and print info for them */
	while (pnp_isolate())
		/* nothing */;
}
/*****************************************************************************
*****************************************************************************/
int main(void)
{
	pnp_init();
	return 0;
}

