/* el21.c: Diagnostic program for Cabletron E2100 ethercards. */
/*
	Written 1993,1994,2000 by Donald Becker.
	Copyright 1994,2000 Donald Becker
	Copyright 1993 United States Government as represented by the
	Director, National Security Agency.

	This software may be used and distributed according to the terms of
	the GNU General Public License (GPL), incorporated herein by reference.

	This program must be compiled with "-O"!
	See the bottom of this file for the suggested compile-command.

	The author may be reached as becker@scyld.com, or C/O
	 Scyld Computing Corporation
	 914 Bay Ridge Road, Suite 220
	 Annapolis MD 21403

	Information and updates available at
	http://www.scyld.com/diag/index.html

	Common-sense licensing statement: Using any portion of this program in
	your own program means that you must give credit to the original author
	and release the resulting code under the GPL.
*/

static const char cvs_id[] =
"$Id: e21.c,v 1.5 2005/03/22 04:41:05 becker Exp $";

static const char version_msg[] =
"e21.c:v1.05 3/21/2005 Donald Becker  http://www.scyld.com/diag/index.html\n";

static const char usage_msg[] =
"Usage: e21 [-fhmvV] [-p <port>]\n\
   -p <I/O base address>  Use the card at this I/O address (default 0x300).\n\
              Common addresses are 0x300, 0x280, 0x380, and 0x220.\n\
";

#if ! defined(__OPTIMIZE__)
#warning  You must compile this program with the correct options!
#warning  See the last lines of the source file.
#error You must compile this driver with "-O".
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#if defined(__linux__)  &&  __GNU_LIBRARY__ == 1
#include <asm/io.h>			/* Newer libraries use <sys/io.h> instead. */
#else
#include <sys/io.h>
#endif

/* #include "8390.h" */


#define printk printf

/* Offsets from the base_addr. */
#define E21_NIC_OFFSET	0		/* Offset to the 8390 NIC. */
#define E21_ASIC		0x10	/* The E21** series ASIC, know as PAXI. */
/* The following registers are heavy-duty magic.  Their obvious function is
   to provide the hardware station address.  But after you read from them the
   three low-order address bits of the next outb() sets a write-only internal
   register! */
#define E21_MEM_ENABLE	0x10
#define E21_MEM_BASE	0x11
#define	 E21_MEM_ON		0x05	/* Enable memory in 16 bit mode. */
#define	 E21_MEM_ON_8	0x07	/* Enable memory in	 8 bit mode. */
#define E21_IRQ_LOW		0x12	/* Low three bits of the IRQ selection. */
#define E21_IRQ_HIGH	0x14	/* High bit of the IRQ, and media select. */
#define E21_SAPROM		0x18	/* Offset to station address data. */
#define ETHERCARD_TOTAL_SIZE	0x20


extern inline void mem_on(short port, volatile char *mem_base,
												  unsigned char start_page )
{
		/* This is a little weird: set the shared memory window by doing a
		   read.  The low address bits specify the starting page. */
		mem_base[start_page];
		inb(port + E21_MEM_ENABLE);
		outb(E21_MEM_ON, port + E21_MEM_ENABLE + E21_MEM_ON);
}

extern inline void mem_off(short port)
{
		inb(port + E21_MEM_ENABLE);
		outb(0x00, port + E21_MEM_ENABLE);
}

struct option longopts[] = {
 /* { name	has_arg	 *flag	val } */
	{"base-address", 1, 0, 'p'}, /* Give help */
	{"help",		0, 0, 'h'},	/* Give help */
	{"force", 		0, 0, 'f'},	/* Force an operation, even with bad status. */
	{"irq",			1, 0, 'i'},	/* Interrupt number */
	{"verbose",		0, 0, 'v'},	/* Verbose mode */
	{"version",		0, 0, 'V'},	/* Display version number */
	{ 0, 0, 0, 0 }
};


/*	Probe for E2100 series ethercards.
   
   E21xx boards have a "PAXI" located in the 16 bytes above the 8390.
   The "PAXI" reports the station address when read, and has an wierd
   address-as-data scheme to set registers when written.
*/

int
main(int argc, char *argv[])
{
	int port_base = 0x300, irq = -1;
	int errflag = 0, verbose = 0, force = 0, show_version = 0;
	int option, memfd, ioaddr, i, longind;
	extern char *optarg;
	caddr_t shared_mem;
	unsigned char station_addr[6];
	int if_port = 0, dump_shared_memory = 0;

	while ((option = getopt_long(argc, argv, "afhi:mp:svw",
								 longopts, &longind)) != -1)
		switch (option) {
		case 'f':  force++; break;
		case 'h':  printf(usage_msg); return 0;
		case 'i':
			irq = atoi(optarg);
			break;
		case 'm':
		  dump_shared_memory++;
		  break;
		case 'p':
			port_base = strtol(optarg, NULL, 16);
			break;
		case 'q': verbose--;			break;
		case 'v': verbose++;			break;
		case '?':
			errflag++;
		}
	if (errflag) {
		fprintf(stderr, usage_msg);
		return 2;
	}

	if (verbose || show_version)
		printf(version_msg);

	/* Turn on I/O permissions before accessing the board registers. */
	if (ioperm(port_base, 32, 1)
		|| ioperm(0x80, 1, 1)) {				/* Needed for SLOW_DOWN_IO. */
		fprintf(stderr, "e21-diag: ioperm() failure: %s\n",
				strerror(errno));
		fprintf(stderr,
				" This program directly accesses hardware device registers \n\
 and thus must be run as 'root'.  This program may interfere with current\n\
 network traffic when run, and may change the hardware configuration.\n");
		return 2;
	}

	ioaddr = port_base;
	printf("Checking for an Cabletron E2100 series ethercard at %#3x.\n",
		   port_base);
	/* Check for 8390-like registers. */
	{
		int cmdreg;
		int oldcmdreg = inb(ioaddr);
		outb_p(0x21, ioaddr);
		cmdreg = inb(ioaddr);
		if (cmdreg != 0x21	&&	cmdreg != 0x23) {
			outb(oldcmdreg, ioaddr);			/* Restore the old values. */
			printk(" Failed initial E2100 probe.\n"
				   " The 8390 command register value was %#2.2x instead"
				   " of 0x21 of 0x23.\n"
				   "Use the '-f' flag to ignore this failure and proceed, or\n"
				   "use '-p <port>' to specify the correct base I/O address.\n",
				   cmdreg);
			if (! force) return 0;
		} else
			printk("  Passed initial E2100 probe, value %2.2x.\n", cmdreg);
	}

	printk("8390 registers:");
	for (i = 0; i < 16; i++)
		printk(" %02x", inb(ioaddr + i));
	printk("\nPAXI registers:");
	for (i = 0; i < 16; i++)
		printk(" %02x", inb(ioaddr + 16 + i));
	printk("\n");

	printf("The ethernet station address is ");
	/* Read the station address PROM.  */
	for (i = 0; i < 6; i++) {
		station_addr[i] = inb(ioaddr + E21_SAPROM + i);
		printk("%2.2x%c", station_addr[i], i < 5 ? ':' : '\n');
	}

	/* Do a media probe.  This is magic.
	   First we set the media register to the primary (TP) port. */
	inb_p(ioaddr + E21_IRQ_HIGH); 	/* Select if_port detect. */
	for(i = 0; i < 6; i++)
		if (station_addr[i] != inb(ioaddr + E21_SAPROM + i))
			if_port = 1;

	if (if_port)
	  printk("Link beat not detected on the primary transceiver.\n"
			 "  Using the secondany transceiver instead.\n");
	else
	  printk("Link beat detected on the primary transceiver.\n");
	
	/* Map the board shared memory into our address space -- this code is
	   a good example of non-kernel access to ISA shared memory space. */
	memfd = open("/dev/kmem", O_RDWR);
	if (memfd < 0) {
		perror("/dev/kmem (shared memory)");
		return 2;
	} else
	  printf("Mapped in ISA shared memory.\n");
	shared_mem = mmap((caddr_t)0xd0000, 0x8000, PROT_READ|PROT_WRITE,
					  MAP_SHARED, memfd, 0xd0000);
	printf("Shared memory at %#x.\n", (int)shared_mem);
	
	inb_p(ioaddr + E21_MEM_BASE);
	inb_p(ioaddr + E21_MEM_BASE);
	outb_p(0, ioaddr + E21_ASIC + ((0xd000 >> 13) & 7));

	{
		volatile ushort *mem = (ushort*)shared_mem;
		int page;
		for (page = 0; page < 0x41; page++) {
			mem_on(ioaddr, shared_mem, page);
			mem[0] = page << 8;
			mem_off(ioaddr);
		}

		printk("Read %04x %04x.\n", mem[0], mem[1]);
		if (dump_shared_memory) {
		  for (page = 0; page < 256; page++) {
			int i;
			mem_on(ioaddr, shared_mem, page);
			printk("Page %#x:", page);
			for (i = 0; i < 16; i++)
				printk(" %04x", mem[i*128]);
			printk("\n");
			/*printk(" %04x%s", mem[0], (page & 7) == 7 ? "\n":"");*/
			mem_off(ioaddr);
		  }
		  printk("\n");
		}
		mem[0] = 0x5742;
		printk("Read %04x %04x.\n", mem[0], mem[1]);
		printk("Read %04x %04x.\n", mem[2], mem[3]);
	}
	/* do_probe(port_base);*/
	return 0;
}


/*
 * Local variables:
 *  compile-command: "gcc -Wall -Wstrict-prototypes -O6 -o e21 e21.c"
 *  tab-width: 4
 *  c-indent-level: 4
 * End:
 */
