/* eexpress.c: Diagnostic program for Intel EtherExpress

   Written 1993 by Donald Becker.

   The Author may be reached as becker@super.org or
   C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715

   This is a diagnostic program for the Intel EtherExpress.  Don't expect it
   to test every possible hardware fault, but it does verify the basic
   functionality of the board.  I wrote this primarily to get hands-on
   experience to understand how the hardware works.
*/

static char *version =
    "eexpress.c:v0.01 10/9/93 Donald Becker (becker@super.org)\n";

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <asm/io.h>
#include <sys/time.h>
#include <linux/if_ether.h>

#include </usr/src/linux/drivers/net/iow.h>

/* Offsets from the base address. */
#define DATAPORT	0	/* Data Transfer Register. */
#define WRITE_PTR	2	/* Write Address Pointer. */
#define READ_PTR	4	/* Read Address Pointer. */
#define CA_CTRL		6	/* Channel Attention Control. */
#define SET_IRQ		7	/* IRQ Select. */
#define SHADOW_PTR	8	/* Shadow Memory Bank Pointer. */
#define MEM_Ctrl	11
#define MEM_Page_Ctrl	12
#define Config		13
#define EEPROM_Ctrl 	14
#define ID_PORT		15

/*  EEPROM_Ctrl bits. */

#define EE_SHIFT_CLK	0x01	/* EEPROM shift clock. */
#define EE_CS		0x02	/* EEPROM chip select. */
#define EE_DATA_WRITE	0x04	/* EEPROM chip data in. */
#define EE_DATA_READ	0x08	/* EEPROM chip data out. */
#define EE_CTRL_BITS	(EE_SHIFT_CLK | EE_CS | EE_DATA_WRITE | EE_DATA_READ)
#define ASIC_RESET	0x40
#define _586_RESET	0x80

/* These need to be verified. */
#define RX_BUF_SIZE	(1518+14+18)	/* packet+header+RBD */
#define NUM_TX_BUFS	4
#define TX_BUF_SIZE	(1518+14+20+16)	/* packet+header+TBD */
#define TxCB_STATUS	0x4000
#define TxCB_CMD	0x4002
#define TxCB_LINK	0x4004
#define	TxCB_TBD_Offset	0x4006
#define TBD_BYTECNT	0x4008
#define TxBlk_Data_Offset	16

#define FD_STATUS	0
#define FD_CMD		2
#define FD_LINK		4
#define	RBD_PKT_SIZE	8

#define PKT_OFFSET	18    /* Offset of Rx packet data from Rx packet frame */

/* Offsets into the System Control Block structure. */
#define SCB_STATUS	0xc008
#define SCB_CMD		0xc00A
#define  CUC_START	 0x0100
#define  RX_START	 0x0010
#define SCB_CBL		0xc00C	/* Command BLock offset. */
#define SCB_RFA		0xc00E	/* Rx Frame Area offset. */

#define CB_STATUS	0x4000
#define CB_CMD		0x4002
#define CB_LINK		0x4004	/* LINK to next Command Bock (offset). */
#define CB_PARAMS	0x4006

enum commands {CmdNOp = 0, CmdSASetup = 1, CmdConfigure = 2, CmdMultcastList = 3,
		   CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7};

/* Delay between EEPROM clock transitions. */
#define eeprom_delay()	do { int _i = 40; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0)

#define EE_WRITE_CMD	(5 << 6)
#define EE_READ_CMD	(6 << 6)
#define EE_ERASE_CMD	(7 << 6)

struct option longopts[] = {
 /* { name  has_arg  *flag  val } */
    {"base-address", 1, 0, 'p'},
    {"all",	   0, 0, 'a'},	/* Print all registers. */
    {"help",       0, 0, 'h'},	/* Give help */
    {"interface",  0, 0, 'f'},	/* Interface number (built-in, AUI) */
    {"irq",	   1, 0, 'i'},	/* Interrupt number */
    {"verbose",    0, 0, 'v'},	/* Verbose mode */
    {"version",    0, 0, 'V'},	/* Display version number */
    {"write-EEPROM", 1, 0, 'w'},/* Write th EEPROMS with the specified vals */
    { 0, 0, 0, 0 }
};

#define printk printf

int verbose = 0;
struct device {
    char *name;
    short base_addr;
    unsigned char irq;
    unsigned char if_port;
    int tbusy;
    int trans_start;
    unsigned char dev_addr[6];	/* Hardware station address. */
    int rx_head;
    int tx_head;
} devs, *dev = &devs;

struct local_stuff {
    struct enet_statistics stats;
} lps, *lp = &lps;

unsigned char fake_packet[100];

void print_mem(short ioaddr, short start);
int expprobe(short ioaddr);
void show_eeprom(short ioaddr);
int read_eeprom(int ioaddr, int location);
int check_eeprom(short ioaddr);
int reset_board(short ioaddr);
int test_buffer_memory(short ioaddr);
void hardware_send_packet(short ioaddr, void *buf, short length);
void diagnose_cmd(short ioaddr);
void cmd_clear(short ioaddr);
void setup_command_block(short ioaddr, enum commands cmd);
void issue_command(short ioaddr, short scb_cmd, short status_bit);
void init_82586_mem(short ioaddr);

int
main(int argc, char **argv)
{
    int port_base = 0x300, irq = -1;
    int errflag = 0, shared_mode = 0;
    int write_eeprom = 0, interface = -1, all_regs = 0;
    int c, longind;
    extern char *optarg;

    while ((c = getopt_long(argc, argv, "af:i:p:svw", longopts, &longind))
	   != -1)
	switch (c) {
	case 'f':
	    interface = atoi(optarg);
	    break;
	case 'i':
	    irq = atoi(optarg);
	    break;
	case 'p':
	    port_base = strtol(optarg, NULL, 16);
	    break;
	case 's': shared_mode++; break;
	case 'v': verbose++;		 break;
	case 'w': write_eeprom++;	 break;
	case 'a': all_regs++;		 break;
	case '?':
	    errflag++;
	}
    if (errflag) {
	fprintf(stderr, "usage:");
	return 2;
    }

    if (verbose)
	printf(version);

    dev->name = "eexpress";
    
    if (iopl(3) < 0) {
	perror("ethertest: ioperm()");
	return 1;
    }

    reset_board(port_base);

    expprobe(port_base);

    if (verbose > 1) {
	show_eeprom(port_base);
    }
    check_eeprom(port_base);
    test_buffer_memory(port_base);

    print_mem(port_base, 0);
    init_82586_mem(port_base);
    print_mem(port_base, 0);
    reset_board(port_base);
    print_mem(port_base, dev->tx_head);

    diagnose_cmd(port_base);

    printf("Sending packet.\n");
    {
	char pkt[64];
	hardware_send_packet(port_base,  pkt, 64);
    }
    print_mem(port_base, 0);
    print_mem(port_base, dev->tx_head);

    return 0;
}

void
print_mem(short ioaddr, short start)
{
    int i;
    printf("Memory at %04x: ", start);
    outw(start, ioaddr + READ_PTR);
    for (i = 0; i < 32; i += 2)
	printf(" %04x", inw(ioaddr + DATAPORT));
    printf("\n");
}

int expmagic(short ioaddr)
{
    short id_addr = ioaddr + ID_PORT;
    unsigned short sum = 0;
    int i;
    for (i = 4; i > 0; i--) {
	short id_val = inb(id_addr);
	sum |= (id_val >> 4) << ((id_val & 3) << 2);
    }
    return sum;
}

int expprobe(short ioaddr)
{
    unsigned short station_addr[3];
    unsigned short sum = expmagic(ioaddr);
    short i;

    if (sum != 0xbaba) {
	printf("Probe failed ID checksum, expected 0xbaba, got %#04x.\n", sum);
	return 1;
    }

    printf("EtherExpress found at %#x, station address", ioaddr);

    /* The station address is stored backwards in the EEPROM, reverse after reading. */
    station_addr[0] = read_eeprom(ioaddr, 2);
    station_addr[1] = read_eeprom(ioaddr, 3);
    station_addr[2] = read_eeprom(ioaddr, 4);
    for (i = 0; i < 6; i++) {
	dev->dev_addr[i] = ((unsigned char*)station_addr)[5-i];
	printf(" %02x", dev->dev_addr[i]);
    }

    {
	char irqmap[] = {0, 9, 3, 4, 5, 10, 11, 0};
	char *ifmap[] = {"AUI", "BNC", "10baseT"};
#define AUI	0
#define BNC	1
#define TP	2
	unsigned short setupval = read_eeprom(ioaddr, 0);
	dev->irq = irqmap[setupval >> 13];
	dev->if_port = (setupval & 0x1000) == 0 ? AUI : read_eeprom(ioaddr, 5) & 0x1 ? TP : BNC;
	printf(", IRQ %d, Interface %s.\n", dev->irq, ifmap[dev->if_port]);
    }
    return 0;
}

int
set_irq(short ioaddr)
{
    char irqrmap[] = {0, 0, 1, 2, 3, 4, 0, 0, 0, 1, 5, 6, 0, 0, 0, 0};	/* Reverse IRQ map. */
    if (dev->irq == 0  ||  irqrmap[dev->irq] == 0)
	return 1;		/* Can't use the specified IRQ. */
    return 0;
}

void
show_eeprom(short ioaddr)
{
    int j;

    for (j = 0; j < 16; j++) {
	printk(" EEPROM location %2x is %4.4x\n", j, read_eeprom(ioaddr, j));
    }
}

int
read_eeprom(int ioaddr, int location)
{
    int i;
    unsigned short retval = 0;
    short ee_addr = ioaddr + EEPROM_Ctrl;
    int read_cmd = location | EE_READ_CMD;
    short ctrl_val = inb(ee_addr) & ~ASIC_RESET & ~EE_CTRL_BITS;

    if (verbose > 2) printk(" EEPROM reg @ %x (%2x) ", ee_addr, ctrl_val);

    ctrl_val |= EE_CS;
    outb(ctrl_val, ee_addr);

    /* Shift the read command bits out. */
    for (i = 8; i >= 0; i--) {
	short outval = (read_cmd & (1 << i)) ? ctrl_val | EE_DATA_WRITE : ctrl_val;
	if (verbose > 3) printf("%x ", outval);
	outb(outval, ee_addr);
	outb(outval | EE_SHIFT_CLK, ee_addr);	/* Give the EEPROM a clock tick. */
	eeprom_delay();
	outb(outval, ee_addr);	/* Give the EEPROM a clock tick. */
	eeprom_delay();
    }
    if (verbose > 3) printf(" ");
    outb(ctrl_val, ee_addr);

    for (i = 16; i > 0; i--) {
	outb(ctrl_val | EE_SHIFT_CLK, ee_addr);  eeprom_delay();
	retval = (retval << 1) | ((inb(ee_addr) & EE_DATA_READ) ? 1 : 0);
	outb(ctrl_val, ee_addr);  eeprom_delay();
    }

    /* Terminate the EEPROM access. */
    ctrl_val &= ~EE_CS;
    outb(ctrl_val | EE_SHIFT_CLK, ee_addr);
    eeprom_delay();
    outb(ctrl_val, ee_addr);
    eeprom_delay();

    return retval;
}

int
check_eeprom(short ioaddr)
{
    int i;
    unsigned short sum = 0;

    for (i = 0; i < 64; i++)
	sum += read_eeprom(ioaddr, i);
    printk("EEPROM checksum is %#04x, which is %s.\n", sum,
	   sum == 0xbaba ? "correct" : "bad (correct value is 0xbaba)" );
    return sum != 0xbaba;
}

int
reset_board(short ioaddr)
{
    int boguscnt = 1000;

    outb(ASIC_RESET, ioaddr + EEPROM_Ctrl);
    outb(0x00, ioaddr + EEPROM_Ctrl);
    while (--boguscnt > 0)
	if (expmagic(ioaddr) == 0xbaba) {
	    if (verbose > 1)
		printk("Exited reset after %d checks.\n", 1000 - boguscnt);
	    return 0;
	}

    printk("Failed to find the board after a reset.\n");
    return 1;
}

/* Test the internal buffer memory by writing then read a linear recursive seqeunce.
   This isn't _quite_ the "right thing" -- we should first probe for the memory size instead
   of only checking the minimum 32K, but the Linux driver only uses 32K. */

int
test_buffer_memory(short ioaddr)
{
    int mem_size = 32 * 1024;		/* Only check 32K memory for now. */
    int i;
    unsigned short lrs_state;

    /* I don't know why, but the Crynwr driver "warms up" the transfer.  Magic?
       Superstitiously, we do too. */
    outw(0x0000, ioaddr + WRITE_PTR);
    port_write(ioaddr + DATAPORT, dev, 16);

    printf("The current write pointer is %4x.\n", inw(ioaddr + WRITE_PTR));

    outw(0x0000, ioaddr + WRITE_PTR);
    for (i = 0, lrs_state = 0xff; i < mem_size; i+=2) {
	outw(lrs_state, ioaddr + DATAPORT);
	lrs_state <<= 1;
	lrs_state = lrs_state & 0x100 ? lrs_state ^ 0xcf : lrs_state;
    }

    outw(0x0000, ioaddr + READ_PTR);
    for (i = 0, lrs_state = 0xff; i < mem_size; i+=2) {
	unsigned short inval = inw(ioaddr + DATAPORT);
	if (inval != lrs_state) {
	    printf("***The buffer memory test failed at %#04x with value  %#04x.\n", i, inval);
	    return 1;
	}
	lrs_state <<= 1;
	lrs_state = lrs_state & 0x100 ? lrs_state ^ 0xcf : lrs_state;
    }
    printf("The buffer memory test of the first %#04x bytes passed.\n", mem_size);
    return 0;
}

void
hardware_send_packet(short ioaddr, void *buf, short length)
{
    short txstatus;
    short tx_block = dev->tx_head;

    cmd_clear(ioaddr);
    /* Switch into "shadow memory" mode. */
    outw(tx_block, ioaddr + SHADOW_PTR);
    outw(length | 0x8000, ioaddr + TBD_BYTECNT);
    outw(0x8004, ioaddr + TxCB_CMD);
#ifdef continuous_tx
    outw(tx_block, ioaddr + TxCB_LINK);
#endif

    /* Output the packet using the write pointer.
       Hmmm, it feels a little like a 3c501! */
    outw(tx_block + TxBlk_Data_Offset, ioaddr + WRITE_PTR);
    port_write(ioaddr + DATAPORT, buf, (length + 1) >> 1);

    /*; Set the SCBs command block pointer to this send packet. */
    outw(tx_block, ioaddr + SCB_CBL);
    /*; Set the SCB command to start the command unit. */
    outw(CUC_START, ioaddr + SCB_CMD);
    outb(0, ioaddr + CA_CTRL);			/* Issue channel-attn. */

    print_mem(ioaddr, dev->tx_head);

    cmd_clear(ioaddr);
    outw(tx_block, ioaddr + SHADOW_PTR);

    do
	txstatus = inw(ioaddr + TxCB_STATUS);
    while (txstatus >= 0);

    if (! txstatus & 0x2000)
	printk("tx_error, status %04x.\n", txstatus);
}

/* Wait for the command unit to become idle. */
void
cmd_clear(short ioaddr)
{
    int boguscnt = 1000;
    while (inw(ioaddr + SCB_CMD))
	if (--boguscnt < 0)
	    break;
}

unsigned char rx_pkt_buf[1518];

int
rx_packet(short ioaddr)
{
    short rx_head = dev->rx_head;
    short rxstatus;
    short rx_tail;

    cmd_clear(ioaddr);
    rxstatus = inw(ioaddr + SCB_STATUS);

    /* Acknowledge the interrupt sources. */
    outw(rxstatus & 0xf000, ioaddr + SCB_CMD);
    outb(0, ioaddr + CA_CTRL);			/* Issue channel-attn. */
    
    while (rxstatus & 0x5000) {			/* Frame Rx, or Rx unit busy. */
	short frame_status;
	short pkt_len;

	/* Set the shadow pointer to the Rx frame. */
	outw(rx_head, ioaddr + SHADOW_PTR);
	frame_status = inw(ioaddr + FD_STATUS);

	if (frame_status >= 0)		/* Command incomplete (buf empty). */
	    break;
	if ((frame_status & 0x2000) == 0) {	/* Frame Rxed, but with error. */
	    lp->stats.rx_errors++;
	    if (frame_status & 0x0800) lp->stats.rx_crc_errors++;
	    if (frame_status & 0x0400) lp->stats.rx_frame_errors++;
	    if (frame_status & 0x0200) lp->stats.rx_fifo_errors++;
	    if (frame_status & 0x0100) lp->stats.rx_over_errors++;
	    if (frame_status & 0x0080) lp->stats.rx_length_errors++;
	}

	pkt_len = inw(ioaddr + RBD_PKT_SIZE) & 0x3fff;

	outw(rx_head + PKT_OFFSET, ioaddr + READ_PTR);
	port_read(ioaddr + DATAPORT, rx_pkt_buf, pkt_len);

	/* Set the shadow pointer to the Rx frame. */
	outw(rx_head, ioaddr + SHADOW_PTR);

	/* Clear the status word and set End-of-List on the current rx frame. */
	outw(0x0000, ioaddr + FD_STATUS);
	outw(0x8000, ioaddr + FD_CMD);

	rx_tail = rx_head;
	rx_head = inw(ioaddr + FD_LINK);

	outw(rx_tail + FD_CMD, ioaddr + WRITE_PTR);
	outw(0x0000, ioaddr + DATAPORT);
    }
    if ((rxstatus & 0x0040) == 0) {
	/* The Rx unit is not ready, it must be hung.  Restart the receiver by
	   initializing the rx buffers, and issueing an Rx start command. */
	outw(rx_head, ioaddr + SCB_RFA);
	cmd_clear(ioaddr);
	outw(RX_START, ioaddr + SCB_CMD);
	outb(0, ioaddr + CA_CTRL);			/* Issue channel-attn. */
    }

    dev->rx_head = rx_head;
    return 0;
}

const int command_blk = 0x20;

void
set_address(short ioaddr)
{
    int boguscnt = 100000;
    short cmd_status;

    printf("Setting up Set Address command, status ...");
    setup_command_block(ioaddr, CmdSASetup);

    port_write(ioaddr + DATAPORT, dev->dev_addr, 3);

    printf("issue...");
    issue_command(ioaddr, CUC_START, 0x2000);

    outw(command_blk, ioaddr + SHADOW_PTR);
    while ((cmd_status = inw(ioaddr + CB_STATUS)) >= 0)
	if (--boguscnt < 0) {
	    printk(" i82586 not responding to SetAddr, status %04x.\n",
		   inw(ioaddr + CB_STATUS));
	    return;
	}
    printf("done.\n");

    return;
}

void
setup_command_block(short ioaddr, enum commands cmd)
{

    cmd_clear(ioaddr);
    /* Set the master command block pointing to the new command. */
    outw(command_blk, ioaddr + SCB_CBL);

    /* Clear the status, set the command word, and make link loop-back. */
    outw(command_blk, ioaddr + WRITE_PTR);
    outw(0x0000, 	ioaddr + DATAPORT);
    outw(0x8000+cmd, 	ioaddr + DATAPORT);
    outw(command_blk,	ioaddr + DATAPORT);
}

void
issue_command(short ioaddr, short scb_cmd, short status_bit)
{
    int boguscnt = 100000;

    cmd_clear(ioaddr);
    outw(scb_cmd, ioaddr + SCB_CMD);
    outb(0, ioaddr + CA_CTRL);			/* Issue channel-attn. */
    while (inw(ioaddr + SCB_STATUS) != status_bit)
	if (--boguscnt < 0) {
	    printk("%s: i82586 not responding, status %04x vs. %04x.\n",
		   dev->name, inw(ioaddr + SCB_STATUS), status_bit);
	    return;
	}

    /* Acknowledge done. */
    outw(status_bit, ioaddr + SCB_CMD);
    outb(0, ioaddr + CA_CTRL);			/* Issue channel-attn. */
}

void
init_cmd(short ioaddr)
{
    issue_command(ioaddr, 0x0000, 0xa000);
}

void
diagnose_cmd(short ioaddr)
{
    int cmd_status;

    printf("Setting up diagnose command...");
    setup_command_block(ioaddr, CmdDiagnose);
    printf("issue...");
    issue_command(ioaddr, CUC_START, 0x2000);
    outw(command_blk, ioaddr + SHADOW_PTR);
    cmd_status = inw(ioaddr|0x4000);
    printk("Diagnose command %s with status %04x.\n",
	   cmd_status & 0x0800 ?"failed" : "passed", cmd_status);

    printf("Setting up a internal state dump...");
    setup_command_block(ioaddr, CmdDump);
    outw(0x7000, ioaddr+DATAPORT); 	/* Dump all data to offset 0x7000. */
    printf("issue...");
    issue_command(ioaddr, CUC_START, 0x2000);
    print_mem(ioaddr, 0x7000);
    print_mem(ioaddr, 0x7020);

    return;
}     

void
init_82586_mem(short ioaddr)
{
    /* Initialize the SCP, which is loaded from address 0xfffff6 at reset.  The
       databook is particularly useless here. */
    /* Put our "shadow" frame at 0xffe0. */
    outw(0xffe0, ioaddr + SHADOW_PTR);
    outw(0x0000, ioaddr + 0x8006);	/* Set bus size to 16 bits. */
    outw(0x0000, ioaddr + 0x800C);	/* Set control mailbox (SCB) addr. */
    outw(0x0000, ioaddr + 0x800E);	/* to 0x000000. */
    
    /* Move the shadow pointer to the beginning of memory, the ISCP & SCB. */
    outw(0x0000, ioaddr + SHADOW_PTR);
#ifdef notdef
    outw(0x0565, ioaddr + 0x4000);	/* Cleared when init. is done. */
#endif
    outw(0x0001, ioaddr + 0x4000);	/* Cleared when init. is done. */
    outw(0x0008, ioaddr + 0x4002);	/* SCB offset */
    outw(0x0000, ioaddr + 0x4004);
    outw(0x0000, ioaddr + 0x4006);
    outw(0x0000, ioaddr + 0x4008);	/* SCB Status. */
    outw(0x0000, ioaddr + 0x400A);	/* SCB Command. */
    outw(0x0100, ioaddr + 0x400C);	/* CBL offset (command list pointer). */
    outw(0x2000, ioaddr + 0x400E);	/* Rx block list. */
    outw(0x0000, ioaddr + 0x8000);	/* Stats. */
    outw(0x0000, ioaddr + 0x8002);
    outw(0x0000, ioaddr + 0x8004);
    outw(0x0000, ioaddr + 0x8006);

    /* ToDo: Initialize the send-block list. */
    {
	int cur_txbuf = dev->tx_head = 0x100;
	int i;

	for (i = 0; i < NUM_TX_BUFS; i++) {
	    outw(cur_txbuf, ioaddr + SHADOW_PTR);
	    outw(0x0000, ioaddr + 0x4000); 	/* TCB status. */
	    outw(0x0000, ioaddr + 0x4002); 	/* TCB command. */
	    outw(cur_txbuf + TX_BUF_SIZE, ioaddr + 0x4004); /* Link to next. */
	    outw(cur_txbuf + 8, ioaddr + 0x4006); /* Pointer Tx data buf. */
	    outw(0x0000, ioaddr + 0x4008);	/* Data buf bytecount. */
	    outw(-1, ioaddr + 0x400A);		/* Link == end-of-list. */
	    outw(cur_txbuf + 16, ioaddr + 0x400C); /* Packet data pointer. */
	    outw(0x0000, ioaddr + 0x400E);	/* Packet data pointer high. */

	    cur_txbuf += TX_BUF_SIZE;
	}
	/* Wrap the last pointer around to make the list a ring. */
	outw(dev->tx_head, ioaddr + 0x4004);
    }

    {
	int cur_rxbuf;
	cur_rxbuf = dev->rx_head = 0x2000;

	/* Initialize each Rx frame. */
	while (cur_rxbuf <= 0x8000 - RX_BUF_SIZE) {
	    outw(cur_rxbuf, ioaddr + SHADOW_PTR);
	    outw(0x0000, ioaddr + 0x4000);
	    outw(0x0000, ioaddr + 0x4002);
	    outw(cur_rxbuf + RX_BUF_SIZE, ioaddr + 0x4004);
	    outw(cur_rxbuf + 8, ioaddr + 0x4006);
	    outw(0x0000, ioaddr + 0x4008);		 /* Actual count */
	    outw(-1, ioaddr + 0x400A);
	    outw(cur_rxbuf + 0x12, ioaddr + 0x400C);
	    outw(0x0000, ioaddr + 0x400E);
	    outw(0x8000 + RX_BUF_SIZE-0x12 /*header len*/, ioaddr + 0x8000);

	    cur_rxbuf += RX_BUF_SIZE;
	}
	/* Make list a ring. */
	outw(0x2000, ioaddr + 0x4004);
    }
    /* Enable loopback to protect the wire while starting up.
       This is Superstition From Crynwr. */
    outb(inb(ioaddr + Config) | 0x02, ioaddr + Config);

    /* Start the 586 by releasing the reset line. */
    outb(_586_RESET, ioaddr + EEPROM_Ctrl);
    outb(0x00, ioaddr + EEPROM_Ctrl);
    /* Do Init, configure, diagnose, set_addr command, then turn off loopback. */
    init_cmd(ioaddr);

    setup_command_block(ioaddr, CmdConfigure);
    {
	/* Magic config commands.  These are the same as the default values,
	   good for ethernet, except that we store the ethernet address and
	   protocol/length with the packet instead of in the header. */
	char config_params[] = {4, 8, 0x40, 0x2e, };
	port_write(ioaddr + DATAPORT, config_params,
		   (sizeof(config_params) + 1) >> 1);
    }
    issue_command(ioaddr, CUC_START, 0x2000);

    set_address(ioaddr);
    /* Disable loopback. */
    outb(inb(ioaddr + Config) & ~0x02, ioaddr + Config);
}

/*
 * Local variables:
 *  compile-command: "cc -N -O -Wall -o eexpress eexpress.c"
 * End:
 */
