/*
	Copyright 1999, Be Incorporated.   All Rights Reserved.
	This file may be used under the terms of the Be Sample Code License.
*/
/*
 * etherpci.c
 * Copyright (c) 1998 Be, Inc.	All Rights Reserved 
 *
 * Ethernet driver: handles PCI NE2000 cards
 *
 * Modification History (most recent first):
 *
 * 18 May 98	malyn	new today
 */
#include <OS.h>
#include <KernelExport.h>
#include <Drivers.h>
#include "etherpci_private.h"
#include <ether_driver.h>
#include <stdarg.h>
#include <PCI.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <SupportDefs.h>

#define kDevName "etherpci"
#define kDevDir "net/" kDevName "/"
#define DEVNAME_LENGTH		64			
#define MAX_CARDS 			 4			/* maximum number of driver instances */


/* debug flags */
#define ERR       0x0001
#define INFO      0x0002
#define RX        0x0004		/* dump received frames */
#define TX        0x0008		/* dump transmitted frames */
#define INTERRUPT 0x0010		/* interrupt calls */
#define FUNCTION  0x0020		/* function calls */
#define PCI_IO    0x0040		/* pci reads and writes */
#define SEQ		  0x0080		/* trasnmit & receive TCP/IP sequence sequence numbers */
#define WARN	  0x0100		/* Warnings - off on final release */

/* diagnostic debug flags - compile in here or set while running with debugger "AcmeRoadRunner" command */
#define DEFAULT_DEBUG_FLAGS ( ERR | INFO | WARN )

void ETHER_DEBUG(int32 debug_mask, int32 enabled, char * format, ...) {
	if (debug_mask & enabled) {
		va_list		args;
		char		s[4096];
		va_start(args, format);
		vsprintf( s, format, args );
		va_end(args);
		dprintf("%s",s);
	}
}


static pci_module_info		*gPCIModInfo;
static char 				*gDevNameList[MAX_CARDS+1];
static pci_info 			*gDevList[MAX_CARDS+1];
static int32 				gOpenMask = 0;


const char** publish_devices(void ) {
	dprintf(kDevName ": publish_devices()\n" );
	return (const char **)gDevNameList;
}


/* Driver Entry Points */
__declspec(dllexport) status_t init_hardware(void );
__declspec(dllexport) status_t init_driver(void );
__declspec(dllexport) void uninit_driver(void );
__declspec(dllexport) const char** publish_devices(void );
__declspec(dllexport) device_hooks *find_device(const char *name );



/*
 * Define STAY_ON_PAGE_0 if you want the driver to stay on page 0 all the
 * time.  This is faster than if it has to switch to page 1 to get the
 * current register to see if there are any more packets.  Reason: the
 * interrupt handler does not have to do any locking if it knows it is
 * always on page 0.  I'm not sure why the speed difference is so dramatic,
 * so perhaps there is actually a bug in page-changing version.
 *
 * STAY_ON_PAGE_0 uses a rather dubious technique to avoid changing the
 * register page and it may not be 100% reliable.  The technique used is to
 * make sure all ring headers are zeroed out before packets are received
 * into them.  Then, if you detect a non-zero ring header, you can be pretty
 * sure that it is another packet. 
 * 
 * We never read the "might-be-a-packet" immediately.  Instead, we just
 * release a semaphore so that the next read will occur later on enough
 * so that the ring header information should be completely filled in.
 * 
 */
#define STAY_ON_PAGE_0	0


/*
 * We only care about these interrupts in our driver
 */
#define INTS_WE_CARE_ABOUT (ISR_RECEIVE | ISR_RECEIVE_ERROR |\
							ISR_TRANSMIT | ISR_TRANSMIT_ERROR | ISR_COUNTER)


typedef struct etherpci_private {
	int32			devID; 		/* device identifier: 0-n */
	pci_info		*pciInfo;
	uint16			irq;		/* IRQ line */
	uint32			reg_base;	/* hardware register base address */
	area_id			ioarea; 	/* Area used for MMIO of hostRegs */
	int boundary;			/* boundary register value (mirrored) */
	ether_address_t myaddr;	/* my ethernet address */
	unsigned nmulti;		/* number of multicast addresses */
	ether_address_t multi[MAX_MULTI];	/* multicast addresses */
	sem_id iolock;			/* ethercard io, except interrupt handler */
	int nonblocking;		/* non-blocking mode */
#if !STAY_ON_PAGE_0
	spinlock intrlock;		/* ethercard io, including interrupt handler */
#endif /* !STAY_ON_PAGE_0 */
	volatile int interrupted;   /* interrupted system call */
	sem_id inrw;			/* in read or write function */
	sem_id ilock;			/* waiting for input */
	sem_id olock;	 		/* waiting to output */

	/*
	 * Various statistics
	 */
	volatile ints;	/* total number of interrupts */
	volatile rints;	/* read interrupts */
	volatile wints;	/* write interrupts */
	volatile reads;	/* reads */
	volatile writes;	/* writes */
	volatile resets;	/* resets */
	volatile rerrs;	/* read errors */
	volatile werrs;	/* write errors */
	volatile interrs;/* unknown interrupts */
	volatile frame_errs;	/* frame alignment errors */
	volatile crc_errs;	/* crc errors */
	volatile frames_lost;/* frames lost due to buffer problems */

	/*
	 * Most recent values of the error statistics to detect any changes in them
	 */
	int rerrs_last;	
	int werrs_last;	
	int interrs_last;
	int frame_errs_last;
	int crc_errs_last;
	int frames_lost_last;


	/* stats from the hardware */
	int chip_rx_frame_errors;
	int chip_rx_crc_errors;
	int chip_rx_missed_errors;
	/*
	 * These values are set once and never looked at again.  They are
	 * almost as good as constants, but they differ for the various
	 * cards, so we can't set them now.
	 */
	int ETHER_BUF_START;
	int ETHER_BUF_SIZE;
	int EC_VMEM_PAGE;
	int EC_VMEM_NPAGES;
	int EC_RXBUF_START_PAGE;
	int EC_RXBUF_END_PAGE;
	int EC_RINGSTART;
	int EC_RINGSIZE;

	uint32 debug;
	
} etherpci_private_t;



#if __INTEL__
#define ether_outb(device, offset, value)	(*gPCIModInfo->write_io_8)((device->reg_base + (offset)), (value))
#define ether_outw(device, offset, value)	(*gPCIModInfo->write_io_16)((device->reg_base + (offset)), (value))
#define ether_inb(device, offset)			((*gPCIModInfo->read_io_8)(device->reg_base + (offset)))
#define ether_inw(device, offset)			((*gPCIModInfo->read_io_16)(device->reg_base + (offset)))

#else /* PPC */

#define ether_inb(device, offset)  			(*((volatile uint8*)(device->reg_base + (offset)))); __eieio()
#define ether_inw(device, offset)  			(*((volatile uint16*)(device->reg_base + (offset)))); __eieio()
#define ether_outb(device, offset, value)  	(*((volatile uint8 *)(device->reg_base + (offset))) = (value)); __eieio()
#define ether_outw(device, offset, value) 	(*((volatile uint16*)(device->reg_base + (offset))) = (value)); __eieio()

#endif

#if 0
#if __INTEL__

uint8  ether_inb(etherpci_private_t *device, uint32 offset) {
  	uint8 result;	
	result = ((*gPCIModInfo->read_io_8)(device->reg_base + (offset)));
	ETHER_DEBUG(PCI_IO, device->debug, " inb(%x) %x \n", offset, result);
	return result;
};
uint16  ether_inw(etherpci_private_t *device, uint32 offset) {
  	uint16 result;	
	result = ((*gPCIModInfo->read_io_16)(device->reg_base + (offset)));
	ETHER_DEBUG(PCI_IO, device->debug, " inw(%x) %x \n", offset, result);
	return result;
};
void  ether_outb(etherpci_private_t *device, uint32 offset, uint8 value) {
	(*gPCIModInfo->write_io_8)((device->reg_base + (offset)), (value));
	ETHER_DEBUG(PCI_IO, device->debug, " outb(%x) %x \n", offset, value);
};
void  ether_outw(etherpci_private_t *device, uint32 offset, uint16 value) {
	(*gPCIModInfo->write_io_16)((device->reg_base + (offset)), (value));
	ETHER_DEBUG(PCI_IO, device->debug, " outb(%x) %x \n", offset, value);
};


#else /* PPC */

uint8  ether_inb(etherpci_private_t *device, uint32 offset) {
  	uint8 result;	
	result = (*((volatile uint8*) (device->reg_base + (offset)))); __eieio();
	ETHER_DEBUG(PCI_IO, device->debug, " inb(%x) %x \n", offset, result);
	return result;
};

uint16  ether_inw(etherpci_private_t *device, uint32 offset) {
  	uint16 result;	
	result = (*((volatile uint16*) (device->reg_base + (offset)))); __eieio();
	ETHER_DEBUG(PCI_IO, device->debug, " inw(%x) %x \n", offset, result);
	return result;
};

void  ether_outb(etherpci_private_t *device, uint32 offset, uint8 value) {
	(*((volatile uint8 *)(device->reg_base + (offset))) = (value)); __eieio();
	ETHER_DEBUG(PCI_IO, device->debug, " outb(%x) %x \n", offset, value);
};
void  ether_outw(etherpci_private_t *device, uint32 offset, uint16 value) {
	(*((volatile uint16 *)(device->reg_base + (offset))) = (value)); __eieio();
	ETHER_DEBUG(PCI_IO, device->debug, " outb(%x) %x \n", offset, value);
};

#endif
#endif





/* for serial debug command*/
#define DEBUGGER_COMMAND true
#if DEBUGGER_COMMAND
etherpci_private_t * gdev;
static int 		etherpci(int argc, char **argv);					   /* serial debug command */
#endif

/*
 * io_lock gets you exclusive access to the card, except that 
 * the interrupt handler can still run.
 * There is probably no need to io_lock() a 3com card, so look into
 * removing it for that case.
 */
#define io_lock(data)			acquire_sem(data->iolock)
#define io_unlock(data)			release_sem_etc(data->iolock, 1, B_DO_NOT_RESCHEDULE)

/*
 * output_wait wakes up when the card is ready to transmit another packet
 */
#define output_wait(data, t) acquire_sem_etc(data->olock, 1, B_TIMEOUT, t)
#define output_unwait(data, c)	release_sem_etc(data->olock, c, B_DO_NOT_RESCHEDULE)

/*
 * input_wait wakes up when the card has at least one packet on it
 */
#define input_wait(data)		acquire_sem_etc(data->ilock ,1, B_CAN_INTERRUPT, 0)
#define input_unwait(data, c)	release_sem_etc(data->ilock, c, B_DO_NOT_RESCHEDULE)


/* prototypes */
static status_t open_hook(const char *name, uint32 flags, void **cookie);
static status_t close_hook(void *);
static status_t free_hook(void *);
static status_t control_hook(void * cookie,uint32 msg,void *buf,size_t len);
static status_t read_hook(void *data, off_t pos, void *buf, size_t *len);
static status_t write_hook(void *data, off_t pos, const void *buf, size_t *len);
static int32    etherpci_interrupt(void *data);                	   /* interrupt handler */
static int32    get_pci_list(pci_info *info[], int32 maxEntries);  /* Get pci_info for each device */
static status_t free_pci_list(pci_info *info[]);                   /* Free storage used by pci_info list */
static void 	dump_packet(const char * msg, unsigned char * buf, uint16 size); /* diagnostic packet trace */
static status_t enable_addressing(etherpci_private_t  *data);             /* enable pci io address space for device */
static int domulti(etherpci_private_t *data,char *addr);

static device_hooks gDeviceHooks =  {
	open_hook, 			/* -> open entry point */
	close_hook,         /* -> close entry point */
	free_hook,          /* -> free entry point */
	control_hook,       /* -> control entry point */
	read_hook,          /* -> read entry point */
	write_hook,         /* -> write entry point */
	NULL,               /* -> select entry point */
	NULL,                /* -> deselect entry point */
	NULL,               /* -> readv */
	NULL                /* -> writev */
};

/* Driver Entry Points */
status_t init_hardware(void )
{
	return B_NO_ERROR;
}


status_t init_driver()
{
	status_t 		status;
	int32			entries;
	char			devName[64];
	int32			i;
	
	dprintf(kDevName ": init_driver ");	

	if((status = get_module( B_PCI_MODULE_NAME, (module_info **)&gPCIModInfo )) != B_OK) {
		dprintf(kDevName " Get module failed! %s\n", strerror(status ));
		return status;
	}
		
	/* Find Lan cards*/
	if ((entries = get_pci_list(gDevList, MAX_CARDS )) == 0) {
		dprintf("init_driver: " kDevName " not found\n");
		free_pci_list(gDevList);
		put_module(B_PCI_MODULE_NAME );
		return B_ERROR;
	}
	
	/* Create device name list*/
	for (i=0; i<entries; i++ )
	{
		sprintf(devName, "%s%ld", kDevDir, i );
		gDevNameList[i] = (char *)malloc(strlen(devName)+1);
		strcpy(gDevNameList[i], devName);
	}
	gDevNameList[i] = NULL;
	
	return B_OK;
}

void uninit_driver(void)
{
	int32 	i;
	void 	*item;

	/*Free device name list*/
	for (i=0; (item=gDevNameList[i]); i++)
		free(item);
	
	/*Free device list*/
	free_pci_list(gDevList);
	put_module(B_PCI_MODULE_NAME);
}

static int32 get_pci_list(pci_info *info[], int32 maxEntries)
{
	status_t status;
	int32 i, entries;
	pci_info	*item;
	
	item = (pci_info *)malloc(sizeof(pci_info));
	
	for (i=0, entries=0; entries<maxEntries; i++) {
		if ((status = gPCIModInfo->get_nth_pci_info(i, item)) != B_OK)
			break;
		if (   ((item->vendor_id == 0x10ec) && (item->device_id == 0x8029))		// RealTek 8029
			|| ((item->vendor_id == 0x1106) && (item->device_id == 0x0926))		// VIA
			|| ((item->vendor_id == 0x4a14) && (item->device_id == 0x5000))		// NetVin 5000
			|| ((item->vendor_id == 0x1050) && (item->device_id == 0x0940))		// ProLAN
			|| ((item->vendor_id == 0x11f6) && (item->device_id == 0x1401))		// Compex
			|| ((item->vendor_id == 0x8e2e) && (item->device_id == 0x3000))		// KTI
		) {			/* check if the device really has an IRQ */
			if ((item->u.h0.interrupt_line == 0) || (item->u.h0.interrupt_line == 0xFF)) {
				dprintf(kDevName " found with invalid IRQ - check IRQ assignement");
				continue;
			}
			dprintf(kDevName " found at IRQ %x ", item->u.h0.interrupt_line);
			info[entries++] = item;
			item = (pci_info *)malloc(sizeof(pci_info));
		}
	}
	info[entries] = NULL;
	free(item);
	return entries;
}

static status_t free_pci_list(pci_info *info[])
{
	int32 		i;
	pci_info	*item;
	
	for (i=0; (item=info[i]); i++)
		free(item);
	return B_OK;
}

/*
 * How many waiting for io?
 */
static long
io_count(etherpci_private_t *data)
{
	long count;

	get_sem_count(data->iolock, &count);
	return (count);
}

/*
 * How many waiting for output?
 */
static long
output_count(etherpci_private_t *data)
{
	long count;

	get_sem_count(data->olock, &count);
	return (count);
}

/*
 * How many waiting for input?
 */
static long
input_count(etherpci_private_t *data)
{
	long count;

	get_sem_count(data->ilock, &count);
	return (count);
}

#if STAY_ON_PAGE_0

#define INTR_LOCK(data, expression) (expression)

#else /* STAY_ON_PAGE_0 */

/*
 * Spinlock for negotiating access to card with interrupt handler
 */
#define intr_lock(data)			acquire_spinlock(&data->intrlock)
#define intr_unlock(data)		release_spinlock(&data->intrlock)

/*
 * The interrupt handler must lock all calls to the card
 * This macro is useful for that purpose.
 */
#define INTR_LOCK(data, expression) (intr_lock(data), (expression), intr_unlock(data))

#endif /* STAY_ON_PAGE_0 */




/*
 * Calculate various constants
 * These must be done at runtime, since 3com and ne2000 cards have different
 * values.
 */
static void
calc_constants(etherpci_private_t *data)
{
	data->EC_VMEM_PAGE = (data->ETHER_BUF_START >> EC_PAGE_SHIFT);
	data->EC_VMEM_NPAGES = (data->ETHER_BUF_SIZE >> EC_PAGE_SHIFT);

	data->EC_RXBUF_START_PAGE = (data->EC_VMEM_PAGE + 6);
	data->EC_RXBUF_END_PAGE = (data->EC_VMEM_PAGE + data->EC_VMEM_NPAGES);

	data->EC_RINGSTART = (data->EC_RXBUF_START_PAGE << EC_PAGE_SHIFT);
	data->EC_RINGSIZE = ((data->EC_VMEM_NPAGES - 6) << EC_PAGE_SHIFT);
}

/*
 * Print an ethernet address
 */
static void
print_address(
			  ether_address_t *addr
			  )
{
	int i;
	char buf[3 * 6 + 1];

	for (i = 0; i < 5; i++) {
		sprintf(&buf[3*i], "%02x:", addr->ebyte[i]);
	}
	sprintf(&buf[3*5], "%02x", addr->ebyte[5]);
	dprintf("%s\n", buf);
}



/*
 * Get the isr register
 */
static unsigned char
getisr(etherpci_private_t *data)
{
	unsigned char isr;

	isr = ether_inb(data, EN0_ISR);
	return (isr);
}

/*
 * Set the isr register
 */
static void
setisr(etherpci_private_t *data, unsigned char isr)
{
	ether_outb(data, EN0_ISR, isr);
}

/*
 * Wait for the DMA to complete
 */
static int
wait_for_dma_complete(
					  etherpci_private_t *data,
					  unsigned short addr,
					  unsigned short size
					  )
{
	unsigned short hi, low;
	unsigned short where;
	int bogus;

#define MAXBOGUS 20

	/*
	 * This is the expected way to wait for DMA completion, which
	 * is in fact incorrect. I think ISR_DMADONE gets set too early.
	 */
	bogus = 0;
	while (!(getisr(data) & ISR_DMADONE) && ++bogus < MAXBOGUS) {
		/* keep waiting */
	}
	if (bogus >= MAXBOGUS) {
		dprintf("Bogus alert: waiting for ISR\n");
	}

	/*
	 * This is the workaround
	 */
	bogus = 0;
	do {
		hi = ether_inb(data, EN0_RADDRHI);
		low = ether_inb(data, EN0_RADDRLO);
		where = (hi << 8) | low;
	} while (where < addr + size && ++bogus < MAXBOGUS);
	if (bogus >= MAXBOGUS * 2) {
		/*
		 * On some cards, the counters will never clear. 
		 * So only print this message when debugging.
		 */
		dprintf("Bogus alert: waiting for counters to zero\n");
		return -1;
	}
	
	setisr(data, ISR_DMADONE);
	ether_outb(data, EN_CCMD, ENC_NODMA);
	return 0;
}

/*
 * Get data from the ne2000 card
 */
static void
etherpci_min(
				 etherpci_private_t *data, 
				 unsigned char *dst, 
				 unsigned src, 
				 unsigned len
				 )
{
	int i;


	if (len & 1) {
		len++;
	}
	ether_outb(data, EN0_RCNTLO, len & 0xff);
	ether_outb(data, EN0_RCNTHI, len >> 8); 
	ether_outb(data, EN0_RADDRLO, src & 0xff); 
	ether_outb(data, EN0_RADDRHI, src >> 8); 
	ether_outb(data, EN_CCMD, ENC_DMAREAD);
	for (i = 0; i < len; i += 2) {
		unsigned short word;

		word = ether_inw(data, NE_DATA);
#if __INTEL__
		dst[i + 1] = word >> 8;
		dst[i + 0] = word & 0xff;
#else
		dst[i] = word >> 8;
		dst[i + 1] = word & 0xff;
#endif
	}
	wait_for_dma_complete(data, src, len);
}

/*
 * Put data on the ne2000 card
 */
static void
etherpci_mout(
				  etherpci_private_t *data, 
				  unsigned dst, 
				  const unsigned char *src, 
				  unsigned len
				  )
{
	int i;
	int tries = 1;

	
	// This loop is for a bug that showed up with the old ISA 3com cards
	// that were in the original BeBox.  Sometimes the dma status would just
	// stop on some part of the buffer, never finishing.
	// If we notice this error, we redo the dma transfer.
again:
#define MAXTRIES 2
	if (tries > MAXTRIES) {
		dprintf("ether_mout : tried %d times, stopping\n");
		return;
	}

	if (len & 1) {
		len++;
	}
	ether_outb(data, EN0_RCNTHI, len >> 8);
	ether_outb(data, EN0_RADDRLO, dst & 0xff);
	ether_outb(data, EN0_RADDRHI, dst >> 8);

	/*
	 * The 8390 hardware has documented bugs in it related to DMA write.
	 * So, we follow the documentation on how to work around them.
	 */

	/*
	 * Step 1: You must put a non-zero value in this register. We use 2.
	 */
	if ((len & 0xff) == 0) {
		ether_outb(data, EN0_RCNTLO, 2);
	}  else {
		ether_outb(data, EN0_RCNTLO, len & 0xff);
	}

#if you_want_to_follow_step2_even_though_it_hangs
	ether_outb(data, EN_CCMD, ENC_DMAREAD);				/* Step 2 */
#endif

	ether_outb(data, EN_CCMD, ENC_DMAWRITE);

	for (i = 0; i < len; i += 2) {
		unsigned short word;

#if __INTEL__
		word = (src[i + 1] << 8) | src[i + 0];
#else
		word = (src[i] << 8) | src[i + 1];
#endif
		ether_outw(data, NE_DATA, word);
	}
	if ((len & 0xff) == 0) {
		/*
		 * Write out the two extra bytes
		 */
		ether_outw(data, NE_DATA, 0);
		len += 2;
	}
	if (wait_for_dma_complete(data, dst, len) != 0) {
		tries++;
		goto again;
	}
	//if (tries != 1) { dprintf("wait_for_dma worked after %d tries\n", tries); }
}


#if STAY_ON_PAGE_0
/* 
 * Zero out the headers in the ring buffer
 */
static void
ringzero(
		 etherpci_private_t *data,
		 unsigned boundary,
		 unsigned next_boundary
		 )
{
	ring_header ring;
	int i;
	int pages;
	unsigned offset;

	ring.count = 0;
	ring.next_packet = 0;
	ring.status = 0;

	if (data->boundary < next_boundary) {
		pages = next_boundary - data->boundary;
	} else {
		pages = (data->EC_RINGSIZE >> EC_PAGE_SHIFT) - (data->boundary - next_boundary);
	}

	for (i = 0; i < pages; i++) {
		offset = data->boundary << EC_PAGE_SHIFT;
		etherpci_mout(data, offset, (unsigned char *)&ring, sizeof(ring));
		data->boundary++;
		if (data->boundary >= data->EC_RXBUF_END_PAGE) {
			data->boundary = data->EC_RXBUF_START_PAGE;
		}
	}
}
#endif /* STAY_ON_PAGE_0 */


/*
 * Determine if we have an ne2000 PCI card
 */
static int probe(etherpci_private_t *data)
{
	int i;
	int reg;
	unsigned char test[EC_PAGE_SIZE];
	short waddr[ETHER_ADDR_LEN];
	long port = data->reg_base;
	uint8 reg_val;

	data->ETHER_BUF_START = ETHER_BUF_START_NE2000;
	data->ETHER_BUF_SIZE = ETHER_BUF_SIZE_NE2000;
	calc_constants(data);

	reg = ether_inb(data, NE_RESET);
	snooze(2000);
	ether_outb(data, NE_RESET, reg);
	snooze(2000);
	ether_outb(data, EN_CCMD, ENC_NODMA | ENC_STOP | ENC_PAGE0);
	i = 10000;
	while ( i-- > 0) {
		reg_val = ether_inb(data, EN0_ISR);
		if (reg_val & ISR_RESET) break;
	};
	if (i < 0) {
		dprintf("reset failed -- ignoring\n");
	}
	ether_outb(data, EN0_ISR, 0xff);
	ether_outb(data, EN0_DCFG, DCFG_BM16);
	ether_outb(data, EN_CCMD, ENC_NODMA | ENC_STOP | ENC_PAGE0);
	snooze(2000);
	reg = ether_inb(data, EN_CCMD);
	if (reg != (ENC_NODMA|ENC_STOP|ENC_PAGE0)) {
		dprintf("command register failed: %02x != %02x\n", reg, ENC_NODMA|ENC_STOP); 
		return (0);
	}
	ether_outb(data, EN0_TXCR, 0);
	ether_outb(data, EN0_RXCR, ENRXCR_MON);
	ether_outb(data, EN0_STARTPG, data->EC_RXBUF_START_PAGE);
	ether_outb(data, EN0_STOPPG, data->EC_RXBUF_END_PAGE);
	ether_outb(data, EN0_BOUNDARY, data->EC_RXBUF_END_PAGE);
	ether_outb(data, EN0_IMR, 0);
	ether_outb(data, EN0_ISR, 0);
	ether_outb(data, EN_CCMD, ENC_NODMA | ENC_PAGE1 | ENC_STOP);
	ether_outb(data, EN1_CURPAG, data->EC_RXBUF_START_PAGE);
	ether_outb(data, EN_CCMD, ENC_NODMA | ENC_PAGE0 | ENC_STOP);

	/* stop chip */
	ether_outb(data, EN_CCMD, ENC_NODMA | ENC_PAGE0);
	ether_outb(data, EN_CCMD, ENC_NODMA | ENC_STOP);

	memset(&waddr[0], 0, sizeof(waddr));
	etherpci_min(data, (unsigned char *)&waddr[0], 0, sizeof(waddr));

	for (i = 0; i < ETHER_ADDR_LEN; i++) {
		data->myaddr.ebyte[i] = ((unsigned char *)&waddr[0])[2*i];
	}

	/* test memory */
	for (i = 0; i < sizeof(test); i++) {
		test[i] = i;
	}
	etherpci_mout(data, data->ETHER_BUF_START, (unsigned char *)&test[0], sizeof(test));
	memset(&test, 0, sizeof(test));
	etherpci_min(data, (unsigned char *)&test[0], data->ETHER_BUF_START, sizeof(test));
	for (i = 0; i < sizeof(test); i++) {
		if (test[i] != i) {
			dprintf("memory test failed: %02x %02x\n", i, test[i]);
			return (0);
		}
	}

	dprintf("ne2000 pci ethernet card found - ");
	print_address(&data->myaddr);
	return (1);
}


/*
 * Initialize the ethernet card
 */
static void
init(
	 etherpci_private_t *data
	 )
{
	int i;
	

#if STAY_ON_PAGE_0
	/*
	 * Set all the ring headers to zero
	 */
	ringzero(data, data->EC_RXBUF_START_PAGE, data->EC_RXBUF_END_PAGE);
#endif /* STAY_ON_PAGE_0 */
	

	/* initialize data configuration register */
	ether_outb(data, EN0_DCFG, DCFG_BM16);
	
	/* clear remote byte count registers */
	ether_outb(data, EN0_RCNTLO, 0x0);
	ether_outb(data, EN0_RCNTHI, 0x0);
	
	/* initialize receive configuration register */
	ether_outb(data, EN0_RXCR, ENRXCR_BCST);
	
	/* get into loopback mode */
	ether_outb(data, EN0_TXCR, TXCR_LOOPBACK);
	
	/* set boundary, page start and page stop */
	ether_outb(data, EN0_BOUNDARY, data->EC_RXBUF_END_PAGE);
	data->boundary = data->EC_RXBUF_START_PAGE;
	ether_outb(data, EN0_STARTPG, data->EC_RXBUF_START_PAGE);
	ether_outb(data, EN0_STOPPG, data->EC_RXBUF_END_PAGE);

	/* set transmit page start register */
	ether_outb(data, EN0_TPSR, data->EC_VMEM_PAGE);
	
	/* clear interrupt status register */
	ether_outb(data, EN0_ISR, 0xff);
	
	/* initialize interrupt mask register */
	ether_outb(data, EN0_IMR, INTS_WE_CARE_ABOUT);
	
	/* set page 1 */
	ether_outb(data, EN_CCMD, ENC_NODMA | ENC_PAGE1);
	
	/* set physical address */
	for (i = 0; i < 6; i++) {
		ether_outb(data, EN1_PHYS + i, data->myaddr.ebyte[i]);
	}
	
	/* set multicast address */
	for (i = 0; i < 8; i++) {
		ether_outb(data, EN1_MULT+i, 0xff); 
	}
	data->nmulti = 0;
	
	/* set current pointer */
	ether_outb(data, EN1_CURPAG, data->EC_RXBUF_START_PAGE);


	/* start chip */
	ether_outb(data, EN_CCMD, ENC_START | ENC_PAGE0 | ENC_NODMA);
	
	/* initialize transmit configuration register */
	ether_outb(data, EN0_TXCR, 0x00);
}



/* 
 * Copy data from the card's ring buffer
 */
static void
ringcopy(
		 etherpci_private_t *data,
		 unsigned char *ether_buf,
		 int offset,
		 int len
		 )
{
	int roffset;
	int rem;

	roffset = offset - data->EC_RINGSTART;
	rem = data->EC_RINGSIZE - roffset;
	if (len > rem) {
		etherpci_min(data, &ether_buf[0], offset, rem);
		etherpci_min(data, &ether_buf[rem], data->EC_RINGSTART, len - rem);
	} else {
		etherpci_min(data, &ether_buf[0], offset, len);
	}
}


/*
 * Set the boundary register, both on the card and internally
 * NOTE: you cannot make the boundary = current register on the card,
 * so we actually set it one behind.
 */
static void
setboundary(etherpci_private_t *data, unsigned char nextboundary)
{
	if (nextboundary != data->EC_RXBUF_START_PAGE) {
		ether_outb(data, EN0_BOUNDARY, nextboundary - 1);
	} else {
		/* since it's a ring buffer */
		ether_outb(data, EN0_BOUNDARY, data->EC_RXBUF_END_PAGE - 1);
	}
	data->boundary = nextboundary;
}

/*
 * Start resetting the chip, because of ring overflow
 */
static int 
reset(etherpci_private_t *data)
{
	unsigned char cmd;
	unsigned char isr;
	int resend = false;

	cmd = ether_inb(data, EN_CCMD);
	ether_outb(data, EN_CCMD, ENC_STOP | ENC_NODMA);
	snooze(10 * 1000);
	ether_outb(data, EN0_RCNTLO, 0x0);
	ether_outb(data, EN0_RCNTHI, 0x0);
	if (cmd & ENC_TRANS) {
		if(!(getisr(data) & (ISR_TRANSMIT | ISR_TRANSMIT_ERROR)))
			resend = true;  // xmit command issued but ISR shows its not completed
	}
	/* get into loopback mode */
	ether_outb(data, EN0_TXCR, TXCR_LOOPBACK);
	ether_outb(data, EN_CCMD, ENC_START | ENC_PAGE0 | ENC_NODMA);
	return (resend);
}

/*
 * finish the reset
 */
static void
finish_reset(
			 etherpci_private_t *data,
			 int resend
			 )
{
	setisr(data, ISR_OVERWRITE);
	ether_outb(data, EN0_TXCR, 0x00);

	if (resend) {
//		dprintf("Xmit CMD resent\n");
		ether_outb(data, EN_CCMD, ENC_START | ENC_PAGE0 | ENC_NODMA | ENC_TRANS);
	}
}


/*
 * Handle ethernet interrupts
 */
static int32
etherpci_interrupt(void *_data)
{
	etherpci_private_t *data = (etherpci_private_t *) _data;
	unsigned char isr;
	int wakeup_reader = 0;
	int wakeup_writer = 0;
	int32 handled = B_UNHANDLED_INTERRUPT;
	data->ints++;
		
	ETHER_DEBUG(INTERRUPT, data->debug, "ENTR isr=%x & %x?\n",getisr(data), INTS_WE_CARE_ABOUT);
	for (INTR_LOCK(data, isr = getisr(data)); 
		 isr & INTS_WE_CARE_ABOUT; 
		 INTR_LOCK(data, isr = getisr(data))) {
		
		
		if (isr & ISR_RECEIVE) {
			data->rints++;
			wakeup_reader++;
			INTR_LOCK(data, setisr(data, ISR_RECEIVE));
			handled = B_HANDLED_INTERRUPT;
			continue;
		}
		if (isr & ISR_TRANSMIT_ERROR) {
			data->werrs++;
			INTR_LOCK(data, setisr(data, ISR_TRANSMIT_ERROR));
			wakeup_writer++;
			handled = B_HANDLED_INTERRUPT;
			continue;
		}
		if (isr & ISR_TRANSMIT) {
			data->wints++;
			INTR_LOCK(data, setisr(data, ISR_TRANSMIT));
			wakeup_writer++;
			handled = B_HANDLED_INTERRUPT;
			continue;
		}
		if (isr & ISR_RECEIVE_ERROR) {
			uint32 err_count;
			err_count = ether_inb(data, EN0_CNTR0);  data->frame_errs += err_count;
			err_count = ether_inb(data, EN0_CNTR1);  data->crc_errs += err_count;
			err_count = ether_inb(data, EN0_CNTR2);  data->frames_lost += err_count;

			isr &= ~ISR_RECEIVE_ERROR;
			INTR_LOCK(data, setisr(data, ISR_RECEIVE_ERROR));
			handled = B_HANDLED_INTERRUPT;
		}
		if (isr & ISR_DMADONE) {
			isr &= ~ISR_DMADONE;	/* handled elsewhere */
			handled = B_HANDLED_INTERRUPT;
		}
		if (isr & ISR_OVERWRITE) {
			isr &= ~ISR_OVERWRITE; /* handled elsewhere */
			handled = B_HANDLED_INTERRUPT;
		}
		
		if (isr & ISR_COUNTER) {
			isr &= ~ISR_COUNTER; /* handled here */
//			dprintf("Clearing Stats Cntr\n");
			INTR_LOCK(data, setisr(data, ISR_COUNTER));
			handled = B_HANDLED_INTERRUPT;
		}
		
		if (isr) {
			/*
			 * If any other interrupts, just clear them (hmmm....)
			 * ??? This doesn't seem right - HB
			 */
//			ddprintf("ISR=%x rdr=%x wtr=%x io=%x inrw=%x nonblk=%x\n",
//				isr,input_count(data), output_count(data),io_count(data),
//				data->inrw,data->nonblocking);
			INTR_LOCK(data, setisr(data, isr));
			data->interrs++;
		}
	}
	if (wakeup_reader) {
		input_unwait(data, 1);
	}
	if (wakeup_writer) {
		output_unwait(data, 1);
	}

	return handled;
}

/*
 * Check to see if there are any new errors
 */
static void
check_errors(etherpci_private_t *data)
{
#define DOIT(stat, message) \
	if (stat > stat##_last) { \
		stat##_last = stat; \
		dprintf(message, stat##_last); \
	}

	DOIT(data->rerrs, "Receive errors now %d\n");
	DOIT(data->werrs, "Transmit errors now %d\n");
	DOIT(data->interrs, "Interrupt errors now %d\n");
	DOIT(data->frames_lost, "Frames lost now %d\n");
#undef DOIT
#if 0
	/* 
	 * these are normal errors because collisions are normal
	 * so don't make a big deal about them.
	 */
	DOIT(data->frame_errs, "Frame alignment errors now %d\n");
	DOIT(data->crc_errs, "CRC errors now %d\n");
#endif
}

/*
 * Find out if there are any more packets on the card
 */
static int
more_packets(
			 etherpci_private_t *data,
			 int didreset
			 )
{
#if STAY_ON_PAGE_0

	unsigned offset;
	ring_header ring;
	
	offset = data->boundary << EC_PAGE_SHIFT;
	etherpci_min(data, (unsigned char *)&ring, offset, sizeof(ring));
	return (ring.status && ring.next_packet && ring.count);

#else /* STAY_ON_PAGE_0 */

	cpu_status ps;
	unsigned char cur;

	/*
	 * First, get the current registe
	 */
	ps = disable_interrupts();
	intr_lock(data);
	ether_outb(data, EN_CCMD, ENC_PAGE1);
	cur = ether_inb(data, EN1_CURPAG);
	ether_outb(data, EN_CCMD, ENC_PAGE0);
	intr_unlock(data);
	restore_interrupts(ps);

	/*
	 * Then return the result
	 * Must use didreset since cur == boundary in
	 * an overflow situation.
	 */
	return (didreset || cur != data->boundary);

#endif	/* STAY_ON_PAGE_0 */
}

/*
 * Copy a packet from the ethernet card
 */
static int
copy_packet(
			etherpci_private_t *data,
			unsigned char *ether_buf,
			int buflen
			)
{
	ring_header ring;
	unsigned offset;
	int len;
	int rlen;
	int ether_len = 0;
	int didreset = 0;
	int resend = 0;

	io_lock(data);
	check_errors(data);


	/*
	 * Check for overwrite error first
	 */
	if (getisr(data) & ISR_OVERWRITE) {
//		dprintf("starting ether reset!\n");
		data->resets++;
		resend = reset(data);
		didreset++;
	}
	
	if (more_packets(data, didreset)) do {
		/*
		 * Read packet ring header
		 */
		offset = data->boundary << EC_PAGE_SHIFT;
		etherpci_min(data, (unsigned char *)&ring, offset, sizeof(ring));

		len = swapshort(ring.count);

		if (!(ring.status & RSR_INTACT)) {
			dprintf("packet not intact! (%02x,%u,%02x) (%d)\n",
					ring.status,
					ring.next_packet,
					ring.count,
					data->boundary);
			/* discard bad packet */
			ether_len = 0; setboundary(data, ring.next_packet);
			break;
		}
		if (ring.next_packet < data->EC_RXBUF_START_PAGE || 
			ring.next_packet >= data->EC_RXBUF_END_PAGE) {
			dprintf("etherpci_read: bad next packet! (%02x,%u,%02x) (%d)\n", 
					ring.status,
					ring.next_packet,
					ring.count,
					data->boundary);
			data->rerrs++;
			/* discard bad packet */
			ether_len = 0; setboundary(data, ring.next_packet);
			break;
		}

		len = swapshort(ring.count);
		rlen = len - 4;
		if (rlen < ETHER_MIN_SIZE || rlen > ETHER_MAX_SIZE) {
			dprintf("etherpci_read: bad length! (%02x,%u,%02x) (%d)\n", 
					ring.status,
					ring.next_packet,
					ring.count,
					data->boundary);
			data->rerrs++;
			/* discard bad packet */
			ether_len = 0; setboundary(data, ring.next_packet);
			break;
		}
			
	
		if (rlen > buflen) {
			rlen = buflen;
		}
		ringcopy(data, ether_buf, offset + 4, rlen);

#if STAY_ON_PAGE_0
		ringzero(data, data->boundary, ring.next_packet);
#endif /* STAY_ON_PAGE_0 */

		ether_len = rlen;
		setboundary(data, ring.next_packet);
		data->reads++;

		
	} while (0);

 done:
	if (didreset) {
		dprintf("finishing reset!\n");
		finish_reset(data, resend);
	}

	if (input_count(data) <= 0 && more_packets(data, didreset)) {
		/*
		 * Looks like there is another packet
		 * So, make sure they get woken up
		 */
		input_unwait(data, 1);
	}

	io_unlock(data);
	return (ether_len);
}

static int
my_packet(
		  etherpci_private_t *data,
		  char *addr
		  )
{
	int i;
	const char broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

	if (memcmp(addr, &data->myaddr, sizeof(data->myaddr)) == 0) {
		return (1);
	}
	if (memcmp(addr, broadcast, sizeof(broadcast)) == 0) {
		return (1);
	}
	for (i = 0; i < data->nmulti; i++) {
		if (memcmp(addr, &data->multi[i], sizeof(data->multi[i])) == 0) {
			return (1);
		}
	}
	return (0);
}

/*
 * Implements the read() system call to the ethernet driver
 */
static status_t 
read_hook( void *_data,off_t pos,void *buf, size_t *len)
{
	etherpci_private_t *data = (etherpci_private_t *) _data;
	ulong buflen;
	int packet_len;

	buflen = *len;
	atomic_add(&data->inrw, 1);
	if (data->interrupted) {
		atomic_add(&data->inrw, -1);
		return (B_INTERRUPTED);
	}
	do {
		if (!data->nonblocking) {
			input_wait(data);
		}
		if (data->interrupted) {
			atomic_add(&data->inrw, -1);
			return (B_INTERRUPTED);
		}
		packet_len = copy_packet(data, (unsigned char *)buf, buflen);
		if ((packet_len) && (data->debug & RX)) {
			dump_packet("RX:" ,buf, packet_len);
		}
	} while (!data->nonblocking && packet_len == 0 && !my_packet(data, buf));
	atomic_add(&data->inrw, -1);
	*len = packet_len;
	return 0;
}


/*
 * Check the status of the last packet transmitted
 */
static void
check_transmit_status(etherpci_private_t *data)
{
	unsigned char status;

	status = ether_inb(data, EN0_TPSR);
	if (status & (TSR_ABORTED | TSR_UNDERRUN)) {
		dprintf("transmit error: %02x\n", status);
	}
#if 0
	if (data->wints + data->werrs != data->writes) {
		dprintf("Write interrupts %d, errors %d, transmits %d\n",
				data->wints, data->werrs, data->writes);
	}
#endif
}


/*
 * implements the write() system call to the ethernet
 */
static status_t
write_hook(void *_data,off_t pos,const void *buf,size_t *len)
{
	etherpci_private_t *data = (etherpci_private_t *) _data;
	ulong buflen;
	int status;

	
	buflen = *len;
	atomic_add(&data->inrw, 1);
	if (data->interrupted) {
		atomic_add(&data->inrw, -1);
		return (B_INTERRUPTED);
	}
	/*
	 * Wait for somebody else (if any) to finish transmitting 
	 */
	status = output_wait(data, ETHER_TRANSMIT_TIMEOUT);
	if (status < B_NO_ERROR || data->interrupted) {
		atomic_add(&data->inrw, -1);
		return (status);
	}

	io_lock(data);
	check_errors(data);

	if (data->writes > 0) {
		check_transmit_status(data);
	}
	etherpci_mout(data, data->ETHER_BUF_START, (const unsigned char *)buf, buflen);
	if (buflen < ETHER_MIN_SIZE) {
		/*
		 * Round up to ETHER_MIN_SIZE
		 */
		buflen = ETHER_MIN_SIZE;
	}
	ether_outb(data, EN0_TCNTLO, (char)(buflen & 0xff));
	ether_outb(data, EN0_TCNTHI, (char)(buflen >> 8));
	ether_outb(data, EN_CCMD, ENC_NODMA | ENC_TRANS);
	data->writes++;
	io_unlock(data);
	atomic_add(&data->inrw, -1);
	*len = buflen;	
	
	if (data->debug & TX) {
		dump_packet("TX:",(unsigned char *) buf, buflen);
	}
	return 0;
}

long
inrw(etherpci_private_t *data)
{
	return (data->inrw);
}


/*
 * Allocate and initialize semaphores and spinlocks.
 */
static status_t allocate_resources(etherpci_private_t *data) {
		
	// create locks
	data->iolock = create_sem(1, "ethercard io");
	set_sem_owner(data->iolock, B_SYSTEM_TEAM);
	data->olock = create_sem(1, "ethercard output");
	set_sem_owner(data->olock, B_SYSTEM_TEAM);
	data->ilock = create_sem(0, "ethercard input");
	set_sem_owner(data->ilock, B_SYSTEM_TEAM);
	data->interrupted = 0;
	data->inrw = 0;
	data->nonblocking = 0;
		
	return (B_OK);
}

static void free_resources(etherpci_private_t *data) {
		delete_sem(data->iolock);
		delete_sem(data->olock);
		delete_sem(data->ilock);
}

device_hooks *find_device(const char *name)
{
	int32 	i;
	char 	*item;

	/* Find device name */
	for (i=0; (item=gDevNameList[i]); i++)
	{
		if (strcmp(name, item) == 0)
		{
			return &gDeviceHooks;
		}
	}
	return NULL; /*Device not found */
}


static status_t
open_hook(const char *name, uint32 flags,  void **cookie)
{
	int32				devID;
	int32				mask;
	status_t			status;
	char 				*devName;
	etherpci_private_t 		*data;

	/*	Find device name*/
	for (devID=0; (devName=gDevNameList[devID]); devID++) {
		if (strcmp(name, devName) == 0)
			break;
	}
	if (!devName)
		return EINVAL;
	
	/* Check if the device is busy and set in-use flag if not */
	mask = 1 << devID;
	if (atomic_or(&gOpenMask, mask) &mask) {
		return B_BUSY;
	}

	/*	Allocate storage for the cookie*/
	if (!(*cookie = data = (etherpci_private_t *)malloc(sizeof(etherpci_private_t)))) {
		status = B_NO_MEMORY;
		goto err0;
	}
	memset(data, 0, sizeof(etherpci_private_t));
	
	/* Setup the cookie */
	data->pciInfo = gDevList[devID];
	data->devID = devID;

	data->debug = DEFAULT_DEBUG_FLAGS;
	ETHER_DEBUG(FUNCTION, data->debug, kDevName ": open %s dev=%x\n", name, data);
	
#if DEBUGGER_COMMAND
	gdev = data;
	add_debugger_command (kDevName, etherpci, "Ethernet driver Info");
#endif
	
	/* enable access to the cards address space */
	if ((status = enable_addressing(data)) != B_OK)
		goto err1;

	if (!probe(data)) {
		dprintf(kDevName ": probe failed\n");
		goto err1;
	}

	if (allocate_resources(data) != B_OK) {
		goto err2;
	}	
	
	/* Setup interrupts */
	install_io_interrupt_handler( data->pciInfo->u.h0.interrupt_line, etherpci_interrupt, *cookie, 0 );
	
	dprintf("Interrupts installed at %x\n", data->pciInfo->u.h0.interrupt_line);
	/* Init Device */
	init(data);

	return B_NO_ERROR;


	err2:
#if !__INTEL__
	delete_area(data->ioarea);
#endif
	err1:
#if DEBUGGER_COMMAND
		remove_debugger_command (kDevName, etherpci);
#endif
		free(data);	
	
	err0:
		atomic_and(&gOpenMask, ~mask);
		dprintf(kDevName ": open failed!\n");
		return B_ERROR;
}


static status_t
close_hook(void *_data)
{
	etherpci_private_t *data = (etherpci_private_t *) _data;

	ETHER_DEBUG(FUNCTION, data->debug, kDevName ": close dev=%x\n", data);


	/* 
	 * Force pending reads and writes to terminate
	 */
	io_lock(data);
	data->interrupted = 1;
	input_unwait(data, 1);
	output_unwait(data, 1);
	io_unlock(data);
	while (inrw(data)) {
		snooze(1000000);
		dprintf("ether: still waiting for read/write to finish\n");
	}

	/*
	 * Stop the chip
	 */
	ether_outb(data, EN_CCMD, ENC_STOP);
	snooze(2000);
	
	/*
	 * And clean up
	 */
	remove_io_interrupt_handler(data->pciInfo->u.h0.interrupt_line, etherpci_interrupt, data);
	delete_sem(data->iolock);
	delete_sem(data->ilock);
	delete_sem(data->olock);
	
	
	/*
	 * Reset all the statistics
	 */
	data->ints = 0;	
	data->rints = 0;	
	data->rerrs = 0;	
	data->wints = 0;	
	data->werrs = 0;	
	data->reads = 0;	
	data->writes = 0;	
	data->interrs = 0;
	data->resets = 0;
	data->frame_errs = 0;
	data->crc_errs = 0;
	data->frames_lost = 0;
	
	data->rerrs_last = 0;	
	data->werrs_last = 0;	
	data->interrs_last = 0;
	data->frame_errs_last = 0;
	data->crc_errs_last = 0;
	data->frames_lost_last = 0;
	
	data->chip_rx_frame_errors = 0;
	data->chip_rx_crc_errors = 0;
	data->chip_rx_missed_errors = 0;

#if DEBUGGER_COMMAND
	remove_debugger_command (kDevName, etherpci);
#endif

	
	return (B_NO_ERROR);
}

static status_t
free_hook(void *_data)
{
	etherpci_private_t *data = (etherpci_private_t *) _data;

	ETHER_DEBUG(FUNCTION, data->debug, kDevName ": free %s dev=%x\n", data);

#if !__INTEL__
	delete_area(data->ioarea);
#endif
	free(data);
	return 0;
}

static status_t enable_addressing(etherpci_private_t  *data)
{

	int32			base, size, offset, i;
	int32			*ptr;
	unsigned char 	cmd;

#if __INTEL__
	data->reg_base = data->pciInfo->u.h0.base_registers[0];
#else

	
	base = data->pciInfo->u.h0.base_registers[0];
	size = data->pciInfo->u.h0.base_register_sizes[0];
	
	/* Round down to nearest page boundary */
	base = base & ~(B_PAGE_SIZE-1);
	
	/* Adjust the size */
	offset = data->pciInfo->u.h0.base_registers[0] - base;
	size += offset;
	size = (size +(B_PAGE_SIZE-1)) & ~(B_PAGE_SIZE-1);

	dprintf(kDevName ": PCI base=%x size=%x offset=%x\n", base, size, offset);
		
	if ((data->ioarea = map_physical_memory(kDevName "_regs", (void *)base, size, B_ANY_KERNEL_ADDRESS, B_READ_AREA | B_WRITE_AREA, (void **)&data->reg_base)) < 0)
		return B_ERROR;

	data->reg_base = data->reg_base + offset;

#endif	
	dprintf(kDevName ": reg_base=%x\n", data->reg_base);
	
	/* enable pci address access */	
	cmd = (gPCIModInfo->read_pci_config)(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_command, 2);
	(gPCIModInfo->write_pci_config)(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_command, 2, cmd | PCI_command_io);

	return B_OK;
}




/*
 * Standard driver control function
 */
static status_t
control_hook(void *_data,uint32 msg,void *buf,size_t len)
{
	etherpci_private_t *data = (etherpci_private_t *) _data;

	switch (msg) {
	case ETHER_INIT:
		ETHER_DEBUG(FUNCTION, data->debug, kDevName ": control: ETHER_INIT \n");
		return B_NO_ERROR;

	case ETHER_GETADDR:
		if (data == NULL) {
			return (B_ERROR);
		}
		ETHER_DEBUG(FUNCTION, data->debug, kDevName ": control: GET_ADDR \n");
		memcpy(buf, &data->myaddr, sizeof(data->myaddr));
		return (B_NO_ERROR);

	case ETHER_NONBLOCK:
		if (data == NULL) {
			return (B_ERROR);
		}
		memcpy(&data->nonblocking, buf, sizeof(data->nonblocking));
		ETHER_DEBUG(FUNCTION, data->debug, kDevName ": control: NON_BLOCK %x\n", data->nonblocking);
		return (B_NO_ERROR);

	case ETHER_ADDMULTI:
		ETHER_DEBUG(FUNCTION, data->debug, kDevName ": control: DO_MULTI %x\n");
		return (domulti(data, (char *)buf));
	}
	return B_ERROR;
}



static int
domulti(etherpci_private_t *data,char *addr)
{
	int i;
	int nmulti = data->nmulti;

	if (nmulti == MAX_MULTI) {
		return (B_ERROR);
	}
	for (i = 0; i < nmulti; i++) {
		if (memcmp(&data->multi[i], addr, sizeof(data->multi[i])) == 0) {
			break;
		}
	}
	if (i == nmulti) {
		/*
		 * Only copy if it isn't there already
		 */
		memcpy(&data->multi[i], addr, sizeof(data->multi[i]));
		data->nmulti++;
	}
	if (data->nmulti == 1) {
		dprintf("Enabling multicast\n");
		ether_outb(data, EN0_RXCR, ENRXCR_BCST | ENRXCR_MCST);
	}
	return (B_NO_ERROR);
}

/* Serial Debugger command
   Connect a terminal emulator to the serial port at 19.2 8-1-None
   Press the keys ( alt-sysreq on Intel) or (Clover-leaf Power on Mac ) to enter the debugger
   At the kdebug> prompt enter "etherpci arg...",
   for example "etherpci R" to enable a received packet trace.
*/
#if DEBUGGER_COMMAND
static int
etherpci(int argc, char **argv) {
	uint16 i,j;
	const char * usage = "usage: etherpci { Function_calls | PCI_IO | Stats | Rx_trace | Tx_trace }\n";	
	

	if (argc < 2) {
		kprintf("%s",usage);	return 0;
	}

	for (i= argc, j= 1; i > 1; i--, j++) {
		switch (*argv[j]) {
		case 'F':
		case 'f':
			gdev->debug ^= FUNCTION;
			if (gdev->debug & FUNCTION) 
				kprintf("Function() call trace Enabled\n");
			else 			
				kprintf("Function() call trace Disabled\n");
			break; 
		case 'N':
		case 'n':
			gdev->debug ^= SEQ;
			if (gdev->debug & SEQ) 
				kprintf("Sequence numbers packet trace Enabled\n");
			else 			
				kprintf("Sequence numbers packet trace Disabled\n");
			break; 
		case 'R':
		case 'r':
			gdev->debug ^= RX;
			if (gdev->debug & RX) 
				kprintf("Receive packet trace Enabled\n");
			else 			
				kprintf("Receive packet trace Disabled\n");
			break;
		case 'T':
		case 't':
			gdev->debug ^= TX;
			if (gdev->debug & TX) 
				kprintf("Transmit packet trace Enabled\n");
			else 			
				kprintf("Transmit packet trace Disabled\n");
			break; 
		case 'S':
		case 's':
			kprintf(kDevName " statistics\n");			
			kprintf("rx_ints %d,  tx_ints %d\n", gdev->rints, gdev->wints);
			kprintf("resets %d \n", gdev->resets);
			kprintf("crc_errs %d, frame_errs %d, frames_lost %d\n", gdev->crc_errs, gdev->frame_errs, gdev->frames_lost);
			break; 
		case 'P':
		case 'p':
			gdev->debug ^= PCI_IO;
			if (gdev->debug & PCI_IO) 
				kprintf("PCI IO trace Enabled\n");
			else 			
				kprintf("PCI IO trace Disabled\n");
			break; 
		default:
			kprintf("%s",usage);
			return 0;
		}
	}
	
	return 0;
}
#endif /* DEBUGGER_COMMAND */

void dump_packet( const char * msg, unsigned char * buf, uint16 size) {

	uint16 j;
	
	dprintf("%s dumping %x size %d \n", msg, buf, size);
	for (j=0; j<size; j++) {
		if ((j & 0xF) == 0) dprintf("\n");
		dprintf("%2.2x ", buf[j]);
	}
}



int32	api_version = B_CUR_DRIVER_API_VERSION;

