patch-2.3.15 linux/drivers/net/sis900.c

Next file: linux/drivers/net/sk_mca.c
Previous file: linux/drivers/net/shaper.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.14/linux/drivers/net/sis900.c linux/drivers/net/sis900.c
@@ -0,0 +1,1909 @@
+/*****************************************************************************/
+/*      sis900.c: A SiS 900 PCI Fast Ethernet driver for Linux.              */
+/*                                                                           */
+/*                Silicon Integrated System Corporation                      */ 
+/*                Revision:	1.05	Aug 7 1999                           */
+/*                                                                           */
+/*****************************************************************************/
+
+/*                                                                            
+      Modified from the driver which is originally written by Donald Becker. 
+
+      This software may be used and distributed according to the terms
+      of the GNU Public License (GPL), incorporated herein by reference.
+      Drivers based on this skeleton fall under the GPL and must retain
+      the authorship (implicit copyright) notice.
+
+      The author may be reached as becker@tidalwave.net, or
+      Donald Becker
+      312 Severn Ave. #W302
+      Annapolis MD 21403
+
+      Support and updates [to the original skeleton] available at
+      http://www.tidalwave.net/~becker/pci-skeleton.html
+*/
+
+static const char *version =
+"sis900.c:v1.05  8/07/99\n";
+
+static int max_interrupt_work = 20;
+#define sis900_debug debug
+static int sis900_debug = 0;
+
+static int multicast_filter_limit = 128;
+
+#define MAX_UNITS 8             /* More are supported, limit only on options */
+static int speeds[MAX_UNITS] = {100, 100, 100, 100, 100, 100, 100, 100};
+static int full_duplex[MAX_UNITS] = {1, 1, 1, 1, 1, 1, 1, 1};
+
+#define TX_BUF_SIZE     1536
+#define RX_BUF_SIZE     1536
+
+#define TX_DMA_BURST    0
+#define RX_DMA_BURST    0
+#define TX_FIFO_THRESH  16
+#define TxDRNT_100      (1536>>5)
+#define TxDRNT_10       16 
+#define RxDRNT_100      8
+#define RxDRNT_10       8 
+#define TRUE            1
+#define FALSE           0
+
+/* Operational parameters that usually are not changed. */
+/* Time in jiffies before concluding the transmitter is hung. */
+#define TX_TIMEOUT  (4*HZ)
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/malloc.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <asm/processor.h>      /* Processor type for cache alignment. */
+#include <asm/bitops.h>
+#include <asm/io.h>
+
+#define RUN_AT(x) (jiffies + (x))
+
+#include <linux/delay.h>
+
+#if LINUX_VERSION_CODE < 0x20123
+#define test_and_set_bit(val, addr) set_bit(val, addr)
+#endif
+#if LINUX_VERSION_CODE <= 0x20139
+#define net_device_stats enet_statistics
+#else
+#define NETSTATS_VER2
+#endif
+#if LINUX_VERSION_CODE < 0x20155  ||  defined(CARDBUS)
+/* Grrrr, the PCI code changed, but did not consider CardBus... */
+#include <linux/bios32.h>
+#define PCI_SUPPORT_VER1
+#else
+#define PCI_SUPPORT_VER2
+#endif
+#if LINUX_VERSION_CODE < 0x20159
+#define dev_free_skb(skb) dev_kfree_skb(skb, FREE_WRITE);
+#else
+#define dev_free_skb(skb) dev_kfree_skb(skb);
+#endif
+
+/* The I/O extent. */
+#define SIS900_TOTAL_SIZE 0x100
+
+/* This table drives the PCI probe routines.  It's mostly boilerplate in all
+   of the drivers, and will likely be provided by some future kernel.
+   Note the matching code -- the first table entry matchs all 56** cards but
+   second only the 1234 card.
+*/
+
+enum pci_flags_bit {
+        PCI_USES_IO=1, PCI_USES_MEM=2, PCI_USES_MASTER=4,
+};
+
+struct pci_id_info {
+        const char *name;
+        u16     vendor_id, device_id, device_id_mask, flags;
+        int io_size;
+        struct net_device *(*probe1)(int pci_bus, int pci_devfn, struct net_device *dev,
+                         long ioaddr, int irq, int chip_idx, int fnd_cnt);
+};
+
+static struct net_device * sis900_probe1(int pci_bus, int pci_devfn,
+                                  struct net_device *dev, long ioaddr,
+                                  int irq, int chp_idx, int fnd_cnt);
+
+static struct pci_id_info pci_tbl[] =
+{{ "SiS 900 PCI Fast Ethernet",
+   0x1039, 0x0900, 0xffff, PCI_USES_IO|PCI_USES_MASTER, 0x100, sis900_probe1},
+  { "SiS 7016 PCI Fast Ethernet",
+   0x1039, 0x7016, 0xffff, PCI_USES_IO|PCI_USES_MASTER, 0x100, sis900_probe1},
+ {0,},                                          /* 0 terminated list. */
+};
+
+/* The capability table matches the chip table above. */
+enum {HAS_MII_XCVR=0x01, HAS_CHIP_XCVR=0x02, HAS_LNK_CHNG=0x04};
+static int sis_cap_tbl[] = {
+        HAS_MII_XCVR|HAS_CHIP_XCVR|HAS_LNK_CHNG,
+        HAS_MII_XCVR|HAS_CHIP_XCVR|HAS_LNK_CHNG,
+};
+
+/* The rest of these values should never change. */
+#define NUM_TX_DESC     16      /* Number of Tx descriptor registers. */
+#define NUM_RX_DESC     8       /* Number of Rx descriptor registers. */
+
+/* Symbolic offsets to registers. */
+enum SIS900_registers {
+        cr=0x0,                 //Command Register
+        cfg=0x4,                //Configuration Register
+        mear=0x8,               //EEPROM Access Register
+        ptscr=0xc,              //PCI Test Control Register
+        isr=0x10,               //Interrupt Status Register
+        imr=0x14,               //Interrupt Mask Register
+        ier=0x18,               //Interrupt Enable Register
+        epar=0x18,              //Enhanced PHY Access Register
+        txdp=0x20,              //Transmit Descriptor Pointer Register
+        txcfg=0x24,             //Transmit Configuration Register
+        rxdp=0x30,              //Receive Descriptor Pointer Register
+        rxcfg=0x34,             //Receive Configuration Register
+        flctrl=0x38,            //Flow Control Register
+        rxlen=0x3c,             //Receive Packet Length Register
+        rfcr=0x48,              //Receive Filter Control Register
+        rfdr=0x4C,              //Receive Filter Data Register
+        pmctrl=0xB0,            //Power Management Control Register
+        pmer=0xB4               //Power Management Wake-up Event Register
+};
+
+#define RESET           0x00000100
+#define SWI             0x00000080
+#define RxRESET         0x00000020
+#define TxRESET         0x00000010
+#define RxDIS           0x00000008
+#define RxENA           0x00000004
+#define TxDIS           0x00000002
+#define TxENA           0x00000001
+
+#define BISE            0x80000000
+#define EUPHCOM         0x00000100
+#define REQALG          0x00000080
+#define SB              0x00000040
+#define POW             0x00000020
+#define EXD             0x00000010
+#define PESEL           0x00000008
+#define LPM             0x00000004
+#define BEM             0x00000001
+
+/* Interrupt register bits, using my own meaningful names. */
+#define WKEVT           0x10000000
+#define TxPAUSEEND      0x08000000
+#define TxPAUSE         0x04000000
+#define TxRCMP          0x02000000
+#define RxRCMP          0x01000000
+#define DPERR           0x00800000
+#define SSERR           0x00400000
+#define RMABT           0x00200000
+#define RTABT           0x00100000
+#define RxSOVR          0x00010000
+#define HIBERR          0x00008000
+#define SWINT           0x00001000
+#define MIBINT          0x00000800
+#define TxURN           0x00000400
+#define TxIDLE          0x00000200
+#define TxERR           0x00000100
+#define TxDESC          0x00000080
+#define TxOK            0x00000040
+#define RxORN           0x00000020
+#define RxIDLE          0x00000010
+#define RxEARLY         0x00000008
+#define RxERR           0x00000004
+#define RxDESC          0x00000002
+#define RxOK            0x00000001
+
+#define IE              0x00000001
+
+#define TxCSI           0x80000000
+#define TxHBI           0x40000000
+#define TxMLB           0x20000000
+#define TxATP           0x10000000
+#define TxIFG           0x0C000000
+#define TxMXF           0x03800000
+#define TxMXF_shift     0x23
+#define TxMXDMA         0x00700000
+#define TxMXDMA_shift   20
+#define TxRTCNT         0x000F0000
+#define TxRTCNT_shift   16
+#define TxFILLT         0x00007F00
+#define TxFILLT_shift   8
+#define TxDRNT          0x0000007F
+
+#define RxAEP           0x80000000
+#define RxARP           0x40000000
+#define RxATP           0x10000000
+#define RxAJAB          0x08000000
+#define RxMXF           0x03800000
+#define RxMXF_shift     23
+#define RxMXDMA         0x00700000
+#define RxMXDMA_shift   20
+#define RxDRNT          0x0000007F
+
+#define RFEN            0x80000000
+#define RFAAB           0x40000000
+#define RFAAM           0x20000000
+#define RFAAP           0x10000000
+#define RFPromiscuous   (RFAAB|RFAAM|RFAAP)
+#define RFAA_shift      28
+#define RFEP            0x00070000
+#define RFEP_shift      16
+
+#define RFDAT           0x0000FFFF
+
+#define OWN             0x80000000
+#define MORE            0x40000000
+#define INTR            0x20000000
+#define OK              0x08000000
+#define DSIZE           0x00000FFF
+
+#define SUPCRC          0x10000000
+#define ABORT           0x04000000
+#define UNDERRUN        0x02000000
+#define NOCARRIER       0x01000000
+#define DEFERD          0x00800000
+#define EXCDEFER        0x00400000
+#define OWCOLL          0x00200000
+#define EXCCOLL         0x00100000
+#define COLCNT          0x000F0000
+
+#define INCCRC          0x10000000
+//      ABORT           0x04000000
+#define OVERRUN         0x02000000
+#define DEST            0x01800000
+#define BCAST           0x01800000
+#define MCAST           0x01000000
+#define UNIMATCH        0x00800000
+#define TOOLONG         0x00400000
+#define RUNT            0x00200000
+#define RXISERR         0x00100000
+#define CRCERR          0x00080000
+#define FAERR           0x00040000
+#define LOOPBK          0x00020000
+#define RXCOL           0x00010000
+
+#define EuphLiteEEMACAddr               0x08
+#define EuphLiteEEVendorID              0x02
+#define EuphLiteEEDeviceID              0x03
+#define EuphLiteEECardTypeRev           0x0b
+#define EuphLiteEEPlexusRev             0x0c
+#define EuphLiteEEChecksum              0x0f
+
+#define RXSTS_shift     18
+#define OWN             0x80000000
+#define MORE            0x40000000
+#define INTR            0x20000000
+#define OK              0x08000000
+#define DSIZE           0x00000FFF
+/* MII register offsets */
+#define MII_CONTROL             0x0000
+#define MII_STATUS              0x0001
+#define MII_PHY_ID0             0x0002
+#define MII_PHY_ID1             0x0003
+#define MII_ANAR                0x0004
+#define MII_ANLPAR              0x0005
+#define MII_ANER                0x0006
+/* MII Control register bit definitions. */
+#define MIICNTL_FDX             0x0100
+#define MIICNTL_RST_AUTO        0x0200
+#define MIICNTL_ISOLATE         0x0400
+#define MIICNTL_PWRDWN          0x0800
+#define MIICNTL_AUTO            0x1000
+#define MIICNTL_SPEED           0x2000
+#define MIICNTL_LPBK            0x4000
+#define MIICNTL_RESET           0x8000
+/* MII Status register bit significance. */
+#define MIISTAT_EXT             0x0001
+#define MIISTAT_JAB             0x0002
+#define MIISTAT_LINK            0x0004
+#define MIISTAT_CAN_AUTO        0x0008
+#define MIISTAT_FAULT           0x0010
+#define MIISTAT_AUTO_DONE       0x0020
+#define MIISTAT_CAN_T           0x0800
+#define MIISTAT_CAN_T_FDX       0x1000
+#define MIISTAT_CAN_TX          0x2000
+#define MIISTAT_CAN_TX_FDX      0x4000
+#define MIISTAT_CAN_T4          0x8000
+/* MII NWAY Register Bits ...
+** valid for the ANAR (Auto-Negotiation Advertisement) and
+** ANLPAR (Auto-Negotiation Link Partner) registers */
+#define MII_NWAY_NODE_SEL       0x001f
+#define MII_NWAY_CSMA_CD        0x0001
+#define MII_NWAY_T              0x0020
+#define MII_NWAY_T_FDX          0x0040
+#define MII_NWAY_TX             0x0080
+#define MII_NWAY_TX_FDX         0x0100
+#define MII_NWAY_T4             0x0200
+#define MII_NWAY_RF             0x2000
+#define MII_NWAY_ACK            0x4000
+#define MII_NWAY_NP             0x8000
+
+/* MII Auto-Negotiation Expansion Register Bits */
+#define MII_ANER_PDF            0x0010
+#define MII_ANER_LP_NP_ABLE     0x0008
+#define MII_ANER_NP_ABLE        0x0004
+#define MII_ANER_RX_PAGE        0x0002
+#define MII_ANER_LP_AN_ABLE     0x0001
+#define HALF_DUPLEX                     1
+#define FDX_CAPABLE_DUPLEX_UNKNOWN      2
+#define FDX_CAPABLE_HALF_SELECTED       3
+#define FDX_CAPABLE_FULL_SELECTED       4
+#define HW_SPEED_UNCONFIG       0
+#define HW_SPEED_10_MBPS        10
+#define HW_SPEED_100_MBPS       100
+#define HW_SPEED_DEFAULT        (HW_SPEED_10_MBPS)
+
+#define ACCEPT_ALL_PHYS         0x01
+#define ACCEPT_ALL_MCASTS       0x02
+#define ACCEPT_ALL_BCASTS       0x04
+#define ACCEPT_ALL_ERRORS       0x08
+#define ACCEPT_CAM_QUALIFIED    0x10
+#define MAC_LOOPBACK            0x20
+//#define FDX_CAPABLE_FULL_SELECTED     4
+#define CRC_SIZE                4
+#define MAC_HEADER_SIZE         14
+
+typedef struct _EuphLiteDesc {
+        u32     llink;
+        unsigned char*  buf;
+        u32     physAddr;
+        /* Hardware sees the physical address of descriptor */
+        u32     plink;
+        u32     cmdsts;
+        u32     bufPhys;
+} EuphLiteDesc;
+
+struct sis900_private {
+        char devname[8];                /* Used only for kernel debugging. */
+        const char *product_name;
+        struct net_device *next_module;
+        int chip_id;
+        int chip_revision;
+        unsigned char pci_bus, pci_devfn;
+#if LINUX_VERSION_CODE > 0x20139
+        struct net_device_stats stats;
+#else
+        struct enet_statistics stats;
+#endif
+        struct timer_list timer;        /* Media selection timer. */
+        unsigned int cur_rx;    /* Index into the Rx buffer of next Rx pkt. */
+        unsigned int cur_tx, dirty_tx, tx_flag;
+
+        /* The saved address of a sent-in-place packet/buffer, for skfree(). */
+        struct sk_buff* tx_skbuff[NUM_TX_DESC];
+        EuphLiteDesc tx_buf[NUM_TX_DESC];       /* Tx bounce buffers */
+        EuphLiteDesc rx_buf[NUM_RX_DESC];
+        unsigned char *rx_bufs;
+        unsigned char *tx_bufs;                 /* Tx bounce buffer region. */
+        char phys[4];                           /* MII device addresses.    */
+        int phy_idx;                            /* Support Max 4 PHY        */
+        u16 pmd_status;
+        unsigned int tx_full;                   /* The Tx queue is full.    */
+	int MediaSpeed;                         /* user force speed         */
+	int MediaDuplex;                        /* user force duplex        */
+        int full_duplex;                        /* Full/Half-duplex.        */
+        int speeds;                             /* 100/10 Mbps.             */
+        u16 LinkOn;
+        u16 LinkChange;
+};
+
+#ifdef MODULE
+#if LINUX_VERSION_CODE > 0x20115
+MODULE_AUTHOR("Jim Huang <cmhuang@sis.com.tw>");
+MODULE_DESCRIPTION("SiS 900 PCI Fast Ethernet driver");
+MODULE_PARM(speeds, "1-" __MODULE_STRING(MAX_UNITS) "i");
+MODULE_PARM(full_duplex, "1-" __MODULE_STRING(MAX_UNITS) "i");
+MODULE_PARM(multicast_filter_limit, "i");
+MODULE_PARM(max_interrupt_work, "i");
+MODULE_PARM(debug, "i");
+#endif
+#endif
+
+static int sis900_open(struct net_device *dev);
+static u16 read_eeprom(long ioaddr, int location);
+static int mdio_read(struct net_device *dev, int phy_id, int location);
+static void mdio_write(struct net_device *dev, int phy_id, int location, int val);
+static void sis900_timer(unsigned long data);
+static void sis900_tx_timeout(struct net_device *dev);
+static void sis900_init_ring(struct net_device *dev);
+static int sis900_start_xmit(struct sk_buff *skb, struct net_device *dev);
+static int sis900_rx(struct net_device *dev);
+static void sis900_interrupt(int irq, void *dev_instance, struct pt_regs *regs);
+static int sis900_close(struct net_device *dev);
+static int mii_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
+static struct enet_statistics *sis900_get_stats(struct net_device *dev);
+static void set_rx_mode(struct net_device *dev);
+static void sis900_reset(struct net_device *dev);
+static u16 elAutoNegotiate(struct net_device *dev, int phy_id, int *duplex, int *speed);
+static void elSetCapability(struct net_device *dev, int phy_id, int duplex, int speed);
+static u16 elPMDreadMode(struct net_device *dev, int phy_id, int *speed, int *duplex);
+static u16 elMIIpollBit(struct net_device *dev, int phy_id, int location, u16 mask, u16 polarity, u16 *value);
+static void elSetMediaType(struct net_device *dev, int speed, int duplex);
+
+/* A list of all installed SiS900 devices, for removing the driver module. */
+static struct net_device *root_sis900_dev = NULL;
+
+/* Ideally we would detect all network cards in slot order.  That would
+   be best done a central PCI probe dispatch, which wouldn't work
+   well when dynamically adding drivers.  So instead we detect just the
+   SiS 900 cards in slot order. */
+
+int sis900_probe(struct net_device *dev)
+{
+        int cards_found = 0;
+        int pci_index = 0;
+        unsigned char pci_bus, pci_device_fn;
+
+        if ( ! pcibios_present())
+                return -ENODEV;
+
+        for (;pci_index < 0xff; pci_index++) {
+                u16 vendor, device, pci_command, new_command;
+                int chip_idx, irq;
+                long ioaddr;
+
+                if (pcibios_find_class (PCI_CLASS_NETWORK_ETHERNET << 8,
+                                                pci_index,
+                                                &pci_bus, &pci_device_fn)
+                                != PCIBIOS_SUCCESSFUL) {
+                        break;
+                }
+                pcibios_read_config_word(pci_bus, pci_device_fn, PCI_VENDOR_ID,
+                                                        &vendor);
+                pcibios_read_config_word(pci_bus, pci_device_fn, PCI_DEVICE_ID,
+                                                        &device);
+
+                for (chip_idx = 0; pci_tbl[chip_idx].vendor_id; chip_idx++)
+                        if (vendor == pci_tbl[chip_idx].vendor_id &&
+                                (device & pci_tbl[chip_idx].device_id_mask) ==
+                                pci_tbl[chip_idx].device_id)
+                                break;
+                if (pci_tbl[chip_idx].vendor_id == 0)   /* Compiled out! */
+                        continue;
+
+	{
+                struct pci_dev *pdev = pci_find_slot(pci_bus, pci_device_fn);
+                ioaddr = pdev->resource[0].start;
+                irq = pdev->irq;
+        }
+
+                if ((pci_tbl[chip_idx].flags & PCI_USES_IO) &&
+                        check_region(ioaddr, pci_tbl[chip_idx].io_size))
+                        continue;
+
+                /* Activate the card: fix for brain-damaged Win98 BIOSes. */
+                pcibios_read_config_word(pci_bus, pci_device_fn,
+                                         PCI_COMMAND, &pci_command);
+                new_command = pci_command | (pci_tbl[chip_idx].flags & 7);
+                if (pci_command != new_command) {
+                        printk(KERN_INFO "  The PCI BIOS has not enabled the"
+                                   " device at %d/%d!"
+                                   "Updating PCI command %4.4x->%4.4x.\n",
+                                        pci_bus, pci_device_fn,
+                                        pci_command, new_command);
+
+                        pcibios_write_config_word(pci_bus, pci_device_fn,
+                                                  PCI_COMMAND, new_command);
+                }
+
+                dev = pci_tbl[chip_idx].probe1(pci_bus,
+                                                pci_device_fn,
+                                                dev,
+                                                ioaddr,
+                                                irq,
+                                                chip_idx,
+                                                cards_found);
+
+                if (dev  && (pci_tbl[chip_idx].flags & PCI_COMMAND_MASTER)) {
+                        u8 pci_latency;
+
+                        pcibios_read_config_byte(pci_bus, pci_device_fn,
+                                        PCI_LATENCY_TIMER, &pci_latency);
+
+                        if (pci_latency < 32) {
+                           printk(KERN_NOTICE "  PCI latency timer (CFLT) is "
+                           "unreasonably low at %d.  Setting to 64 clocks.\n",
+                                           pci_latency);
+                           pcibios_write_config_byte(pci_bus, pci_device_fn,
+                                          PCI_LATENCY_TIMER, 64);
+                        }
+                }
+                dev = 0;
+                cards_found++;
+        }
+        return cards_found ? 0 : -ENODEV;
+}
+
+static struct net_device * sis900_probe1(   int pci_bus,
+                                        int pci_devfn,
+                                        struct net_device *dev,
+                                        long ioaddr,
+                                        int irq,
+                                        int chip_idx,
+                                        int found_cnt)
+{
+        static int did_version = 0;     /* Already printed version info. */
+        struct sis900_private *tp;
+	u16    status;
+        int    duplex = found_cnt < MAX_UNITS ? full_duplex[found_cnt] : 0 ;
+        int    speed  = found_cnt < MAX_UNITS ? speeds[found_cnt] : 0 ;
+        int    phy=0, phy_idx=0, i;
+
+        if (did_version++ == 0)
+                printk(KERN_INFO "%s", version);
+
+        dev = init_etherdev(dev, 0);
+        
+        if(dev==NULL)
+        	return NULL;
+
+        printk(KERN_INFO "%s: %s at %#lx, IRQ %d, ",
+                   dev->name, pci_tbl[chip_idx].name, ioaddr, irq);
+
+        if ((u16)read_eeprom(ioaddr, EuphLiteEEVendorID) != 0xffff) {
+                for (i = 0; i < 3; i++)
+                        ((u16 *)(dev->dev_addr))[i] =
+                                        read_eeprom(ioaddr,i+EuphLiteEEMACAddr);
+                for (i = 0; i < 5; i++)
+                        printk("%2.2x:", (u8)dev->dev_addr[i]);
+                printk("%2.2x.\n", dev->dev_addr[i]);
+        } else
+                printk(KERN_INFO "Error EEPROM read\n");
+
+        /* We do a request_region() to register /proc/ioports info. */
+        request_region(ioaddr, pci_tbl[chip_idx].io_size, dev->name);
+
+        dev->base_addr = ioaddr;
+        dev->irq = irq;
+
+        /* Some data structures must be quadword aligned. */
+        tp = kmalloc(sizeof(*tp), GFP_KERNEL | GFP_DMA);
+        if(tp==NULL)
+        {
+        	release_region(ioaddr, pci_tbl[chip_idx].io_size);
+        	return NULL;
+        }
+        memset(tp, 0, sizeof(*tp));
+        dev->priv = tp;
+
+        tp->next_module = root_sis900_dev;
+        root_sis900_dev = dev;
+
+        tp->chip_id = chip_idx;
+        tp->pci_bus = pci_bus;
+        tp->pci_devfn = pci_devfn;
+
+        /* Find the connected MII xcvrs.
+           Doing this in open() would allow detecting external xcvrs later, but
+           takes too much time. */
+        if (sis_cap_tbl[chip_idx] & HAS_MII_XCVR) {
+                for (phy = 0, phy_idx = 0;
+                        phy < 32 && phy_idx < sizeof(tp->phys); phy++)
+                {
+                        int mii_status ;
+			mii_status = mdio_read(dev, phy, MII_STATUS);
+
+                        if (mii_status != 0xffff && mii_status != 0x0000) {
+                                tp->phy_idx = phy_idx;
+                                tp->phys[phy_idx++] = phy;
+                                tp->pmd_status=mdio_read(dev, phy, MII_STATUS);
+                                printk(KERN_INFO "%s: MII transceiver found "
+                                                 "at address %d.\n",
+                                                 dev->name, phy);
+                                break;
+                        }
+                }
+
+                if (phy_idx == 0) {
+                        printk(KERN_INFO "%s: No MII transceivers found!\n",
+                                        dev->name);
+                        tp->phys[0] = -1;
+			tp->pmd_status = 0;
+                }
+        } else {
+                        tp->phys[0] = -1;
+			tp->pmd_status = 0;
+        }
+
+        if ((tp->pmd_status > 0) && (phy_idx > 0)) {
+		if (sis900_debug > 1) {
+			printk(KERN_INFO "duplex=%d, speed=%d\n",
+						duplex, speed);
+		}
+		if (!duplex && !speed) {  
+			// auto-config media type
+			// Set full capability
+			if (sis900_debug > 1) {
+				printk(KERN_INFO "Auto Config ...\n");
+			}
+			elSetCapability(dev, tp->phys[tp->phy_idx], 1, 100);
+            		tp->pmd_status=elAutoNegotiate(dev,
+               	    				       tp->phys[tp->phy_idx],
+			                  	       &tp->full_duplex,
+						       &tp->speeds);
+		} else {
+			tp->MediaSpeed = speed;
+			tp->MediaDuplex = duplex;
+			elSetCapability(dev, tp->phys[tp->phy_idx],
+					duplex, speed);
+            		elAutoNegotiate(dev, tp->phys[tp->phy_idx],
+			                &tp->full_duplex,
+					&tp->speeds);
+			status = mdio_read(dev, phy, MII_ANLPAR);
+			if ( !(status & (MII_NWAY_T  | MII_NWAY_T_FDX |
+					 MII_NWAY_TX | MII_NWAY_TX_FDX )))
+			{
+				u16 cmd=0;
+				cmd |= ( speed == 100 ?
+					 MIICNTL_SPEED : 0 );
+				cmd |= ( duplex ? MIICNTL_FDX : 0 );
+        			mdio_write(dev, phy, MII_CONTROL, cmd);
+				elSetMediaType(dev, speed==100 ? 
+						    HW_SPEED_100_MBPS :
+						    HW_SPEED_10_MBPS,
+						    duplex ?
+						    FDX_CAPABLE_FULL_SELECTED:
+						    FDX_CAPABLE_HALF_SELECTED);
+        			elMIIpollBit(dev, phy, MII_STATUS,
+						MIISTAT_LINK, TRUE, &status);
+			} else {
+				status = mdio_read(dev, phy, MII_STATUS);
+			}
+		}
+
+                if (tp->pmd_status & MIISTAT_LINK) 
+               	        tp->LinkOn = TRUE;
+	        else
+                        tp->LinkOn = FALSE;
+
+		tp->LinkChange = FALSE;
+	
+        }
+
+	if (sis900_debug > 1) {
+        	if (tp->full_duplex == FDX_CAPABLE_FULL_SELECTED) {
+                	printk(KERN_INFO "%s: Media type is Full Duplex.\n",
+						dev->name);
+        	} else {
+                	printk(KERN_INFO "%s: Media type is Half Duplex.\n",
+						dev->name);
+        	}
+        	if (tp->speeds == HW_SPEED_100_MBPS) {
+                	printk(KERN_INFO "%s: Speed is 100mbps.\n", dev->name);
+        	} else {
+                	printk(KERN_INFO "%s: Speed is 10mbps.\n", dev->name);
+        	}
+	}
+
+        /* The SiS900-specific entries in the device structure. */
+        dev->open = &sis900_open;
+        dev->hard_start_xmit = &sis900_start_xmit;
+        dev->stop = &sis900_close;
+        dev->get_stats = &sis900_get_stats;
+        dev->set_multicast_list = &set_rx_mode;
+        dev->do_ioctl = &mii_ioctl;
+
+        return dev;
+}
+
+/* Serial EEPROM section. */
+
+/*  EEPROM_Ctrl bits. */
+#define EECLK           0x00000004      /* EEPROM shift clock. */
+#define EECS            0x00000008      /* EEPROM chip select. */
+#define EEDO            0x00000002      /* EEPROM chip data out. */
+#define EEDI            0x00000001      /* EEPROM chip data in. */
+
+/* Delay between EEPROM clock transitions.
+   No extra delay is needed with 33Mhz PCI, but 66Mhz may change this.
+ */
+
+#define eeprom_delay()  inl(ee_addr)
+
+/* The EEPROM commands include the alway-set leading bit. */
+#define EEread          0x0180
+#define EEwrite         0x0140
+#define EEerase         0x01C0
+#define EEwriteEnable   0x0130
+#define EEwriteDisable  0x0100
+#define EEeraseAll      0x0120
+#define EEwriteAll      0x0110
+#define EEaddrMask      0x013F
+#define EEcmdShift      16
+
+static u16 read_eeprom(long ioaddr, int location)
+{
+        int i;
+        u16 retval = 0;
+        long ee_addr = ioaddr + mear;
+        u32 read_cmd = location | EEread;
+
+        outl(0, ee_addr);
+        eeprom_delay();
+        outl(EECLK, ee_addr);
+        eeprom_delay();
+
+        /* Shift the read command bits out. */
+        for (i = 8; i >= 0; i--) {
+                u32 dataval = (read_cmd & (1 << i)) ? EEDI | EECS : EECS;
+                outl(dataval, ee_addr);
+                eeprom_delay();
+                outl(dataval | EECLK, ee_addr);
+                eeprom_delay();
+        }
+        outb(EECS, ee_addr);
+        eeprom_delay();
+
+        for (i = 16; i > 0; i--) {
+                outl(EECS, ee_addr);
+                eeprom_delay();
+                outl(EECS | EECLK, ee_addr);
+                eeprom_delay();
+                retval = (retval << 1) | ((inl(ee_addr) & EEDO) ? 1 : 0);
+                eeprom_delay();
+        }
+
+        /* Terminate the EEPROM access. */
+        outl(0, ee_addr);
+        eeprom_delay();
+        outl(EECLK, ee_addr);
+        return (retval);
+}
+
+/* MII serial management: mostly bogus for now. */
+/* Read and write the MII management registers using software-generated
+   serial MDIO protocol.
+   The maximum data clock rate is 2.5 Mhz.  The minimum timing is usually
+   met by back-to-back PCI I/O cycles, but we insert a delay to avoid
+   "overclocking" issues. */
+
+#define mdio_delay()    inl(mdio_addr)
+
+#define MIIread         0x6000
+#define MIIwrite        0x6002
+#define MIIpmdMask      0x0F80
+#define MIIpmdShift     7
+#define MIIregMask      0x007C
+#define MIIregShift     2
+#define MIIturnaroundBits       2
+#define MIIcmdLen       16
+#define MIIcmdShift     16
+#define MIIreset        0xFFFFFFFF
+#define MIIwrLen        32
+
+#define MDC             0x00000040
+#define MDDIR           0x00000020
+#define MDIO            0x00000010
+
+static void mdio_idle(long mdio_addr)
+{
+        outl(MDIO | MDDIR, mdio_addr);
+        mdio_delay();
+        outl(MDIO | MDDIR | MDC, mdio_addr);
+}
+
+/* Syncronize the MII management interface by shifting 32 one bits out. */
+static void mdio_reset(long mdio_addr)
+{
+        int i;
+
+        for (i = 31; i >= 0; i--) {
+                outl(MDDIR | MDIO, mdio_addr);
+                mdio_delay();
+                outl(MDDIR | MDIO | MDC, mdio_addr);
+                mdio_delay();
+        }
+        return;
+}
+
+static int mdio_read(struct net_device *dev, int phy_id, int location)
+{
+        long mdio_addr = dev->base_addr + mear;
+        int mii_cmd = MIIread|(phy_id<<MIIpmdShift)|(location<<MIIregShift);
+        int retval = 0;
+        int i;
+
+        mdio_reset(mdio_addr);
+        mdio_idle(mdio_addr);
+
+        for (i = 15; i >= 0; i--) {
+                int dataval = (mii_cmd & (1 << i)) ? MDDIR | MDIO : MDDIR;
+                outl(dataval, mdio_addr);
+                outl(dataval | MDC, mdio_addr);
+        }
+
+        /* Read the two transition, 16 data, and wire-idle bits. */
+        for (i = 16; i > 0; i--) {
+                outl(0, mdio_addr);
+                //mdio_delay();
+                retval = (retval << 1) | ((inl(mdio_addr) & MDIO) ? 1 : 0);
+                outl(MDC, mdio_addr);
+                mdio_delay();
+        }
+        return retval;
+}
+
+static void mdio_write(struct net_device *dev, int phy_id, int location, int value)
+{
+        long mdio_addr = dev->base_addr + mear;
+        int mii_cmd = MIIwrite|(phy_id<<MIIpmdShift)|(location<<MIIregShift);
+        int i;
+
+        mdio_reset(mdio_addr);
+        mdio_idle(mdio_addr);
+
+        /* Shift the command bits out. */
+        for (i = 31; i >= 0; i--) {
+                int dataval = (mii_cmd & (1 << i)) ? MDDIR | MDIO : MDDIR;
+                outb(dataval, mdio_addr);
+                mdio_delay();
+                outb(dataval | MDC, mdio_addr);
+                mdio_delay();
+        }
+        mdio_delay();
+        /* Clear out extra bits. */
+        for (i = 2; i > 0; i--) {
+                outb(0, mdio_addr);
+                mdio_delay();
+                outb(MDC, mdio_addr);
+                mdio_delay();
+        }
+        return;
+}
+
+static int
+sis900_open(struct net_device *dev)
+{
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        long ioaddr = dev->base_addr;
+
+        if (sis900_debug > 0)
+                printk(KERN_INFO "%s sis900_open, IO Addr=%x, Irq=%x\n",
+                                dev->name, (unsigned int)ioaddr, dev->irq);
+
+        /* Soft reset the chip. */
+        outl(0, ioaddr + imr);
+        outl(0, ioaddr + ier);
+        outl(0, ioaddr + rfcr);
+        outl(RESET | RxRESET | TxRESET, ioaddr + cr);
+
+        if (request_irq(dev->irq, &sis900_interrupt, SA_SHIRQ, dev->name, dev))
+        {
+                return -EAGAIN;
+        }
+
+        MOD_INC_USE_COUNT;      
+
+        tp->tx_bufs = kmalloc(TX_BUF_SIZE * NUM_TX_DESC, GFP_KERNEL);
+        tp->rx_bufs = kmalloc(RX_BUF_SIZE * NUM_RX_DESC, GFP_KERNEL);
+        if (tp->tx_bufs == NULL || tp->rx_bufs == NULL) {
+                if (tp->tx_bufs)
+                        kfree(tp->tx_bufs);
+                if (tp->rx_bufs)
+                        kfree(tp->rx_bufs);
+		if (!tp->tx_bufs) {
+	              printk(KERN_ERR "%s: Can't allocate a %d byte TX Bufs.\n",
+                                   dev->name, TX_BUF_SIZE * NUM_TX_DESC);
+		}
+		if (!tp->rx_bufs) {
+	              printk(KERN_ERR "%s: Can't allocate a %d byte RX Bufs.\n",
+                                   dev->name, RX_BUF_SIZE * NUM_RX_DESC);
+		}
+                return -ENOMEM;
+        }
+
+        {
+                u32 rfcrSave;
+                u32 w;
+                u32 i;
+
+                rfcrSave = inl(rfcr);
+                outl(rfcrSave & ~RFEN, rfcr);
+                for (i=0 ; i<3 ; i++) {
+                        w = (u16)*((u16*)(dev->dev_addr)+i);
+                        outl((((u32) i) << RFEP_shift), ioaddr + rfcr);
+                        outl((u32)w, ioaddr + rfdr);
+                        if (sis900_debug > 4) {
+                                printk(KERN_INFO "Filter Addr[%d]=%x\n",
+                                        i, inl(ioaddr + rfdr));
+                        }
+                }
+                outl(rfcrSave, rfcr);
+        }
+
+        sis900_init_ring(dev);
+        outl((u32)tp->tx_buf[0].physAddr, ioaddr + txdp);
+        outl((u32)tp->rx_buf[0].physAddr, ioaddr + rxdp);
+
+        if (sis900_debug > 4)
+                printk(KERN_INFO "txdp:%8.8x\n", inl(ioaddr + txdp));
+
+        /* Check that the chip has finished the reset. */
+        {
+                u32 status;
+                int j=0;
+                status = TxRCMP | RxRCMP;
+                while (status && (j++ < 30000)) {
+                        status ^= (inl(isr) & status);
+                }
+        }
+
+        outl(PESEL, ioaddr + cfg);
+
+        /* Must enable Tx/Rx before setting transfer thresholds! */
+        /*
+         *      #define TX_DMA_BURST    0
+         *      #define RX_DMA_BURST    0
+         *      #define TX_FIFO_THRESH  16
+         *      #define TxDRNT_100      (1536>>5)
+         *      #define TxDRNT_10       (1536>>5)
+         *      #define RxDRNT_100      (1536>>5)
+         *      #define RxDRNT_10       (1536>>5)
+         */
+        outl((RX_DMA_BURST<<20) | (RxDRNT_10 << 1), ioaddr+rxcfg);
+        outl(TxATP | (TX_DMA_BURST << 20) | (TX_FIFO_THRESH<<8) | TxDRNT_10,
+                                                ioaddr + txcfg);
+        if (sis900_debug > 1)
+        {
+        	if (tp->LinkOn) {
+        		printk(KERN_INFO"%s: Media Type %s%s-duplex.\n",
+                                dev->name,
+                                tp->speeds==HW_SPEED_100_MBPS ?
+					"100mbps " : "10mbps ",
+                                tp->full_duplex== FDX_CAPABLE_FULL_SELECTED ?
+					"full" : "half");
+		}
+		else printk(KERN_INFO"%s: Media Link Off\n", dev->name);
+	}
+        set_rx_mode(dev);
+
+        dev->tbusy = 0;
+        dev->interrupt = 0;
+        dev->start = 1;
+
+        /* Enable all known interrupts by setting the interrupt mask. */
+        outl((RxOK|RxERR|RxORN|RxSOVR|TxOK|TxERR|TxURN), ioaddr + imr);
+        outl(RxENA, ioaddr + cr);
+        outl(IE, ioaddr + ier);
+
+        if (sis900_debug > 3)
+                printk(KERN_INFO "%s: sis900_open() ioaddr %#lx IRQ %d \n",
+                           dev->name, ioaddr, dev->irq);
+
+        /* Set the timer to switch to check for link beat and perhaps switch
+           to an alternate media type. */
+        init_timer(&tp->timer);
+        tp->timer.expires = RUN_AT((24*HZ)/10);         /* 2.4 sec. */
+        tp->timer.data = (unsigned long)dev;
+        tp->timer.function = &sis900_timer;             /* timer handler */
+        add_timer(&tp->timer);
+
+        return 0;
+}
+
+static void sis900_timer(unsigned long data)
+{
+        struct net_device *dev = (struct net_device *)data;
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        int next_tick = 0;
+        u16 status;
+
+        if (!tp->LinkOn) {
+                status = mdio_read(dev, tp->phys[tp->phy_idx], MII_STATUS);
+		if (status & MIISTAT_LINK) {
+                	elPMDreadMode(dev, tp->phys[tp->phy_idx],
+                                        &tp->speeds, &tp->full_duplex);
+			tp->LinkOn = TRUE;
+                        printk(KERN_INFO "%s: Media Link On %s%s-duplex ",
+                                   dev->name,
+                                   tp->speeds == HW_SPEED_100_MBPS ?
+						"100mbps " : "10mbps ",
+                                   tp->full_duplex==FDX_CAPABLE_FULL_SELECTED ?
+						"full" : "half");
+		}
+        } else { // previous link on
+                status = mdio_read(dev, tp->phys[tp->phy_idx], MII_STATUS);
+		if (!(status & MIISTAT_LINK)) {
+			tp->LinkOn = FALSE;
+                        printk(KERN_INFO "%s: Media Link Off\n", dev->name);
+		}
+        }
+        next_tick = 2*HZ;
+
+        if (next_tick) {
+                tp->timer.expires = RUN_AT(next_tick);
+                add_timer(&tp->timer);
+        }
+}
+
+static void sis900_tx_timeout(struct net_device *dev)
+{
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        long ioaddr = dev->base_addr;
+        int i;
+
+        if (sis900_debug > 0)
+                printk(KERN_INFO "%s: Transmit timeout, status %2.2x %4.4x \n",
+                           dev->name, inl(ioaddr + cr), inl(ioaddr + isr));
+
+        /* Disable interrupts by clearing the interrupt mask. */
+        outl(0x0000, ioaddr + imr);
+
+        /* Emit info to figure out what went wrong. */
+	if (sis900_debug > 1) {
+	        printk(KERN_INFO "%s:Tx queue start entry %d dirty entry %d.\n",
+                		   dev->name, tp->cur_tx, tp->dirty_tx);
+        	for (i = 0; i < NUM_TX_DESC; i++) 
+                	printk(KERN_INFO "%s:  Tx descriptor %d is %8.8x.%s\n",
+                        	dev->name, i, (unsigned int)&tp->tx_buf[i],
+                     		i == tp->dirty_tx % NUM_TX_DESC ?
+                      		" (queue head)" : "");
+	}
+
+        /* Soft reset the chip. */
+        //outb(RESET, ioaddr + cr);
+        /* Check that the chip has finished the reset. */
+        /*
+        for (i = 1000; i > 0; i--)
+                if ((inb(ioaddr + cr) & RESET) == 0)
+                        break;
+        */
+
+        tp->cur_rx = 0; 
+        /* Must enable Tx/Rx before setting transfer thresholds! */
+        /*
+        set_rx_mode(dev);
+        */
+        {       /* Save the unsent Tx packets. */
+                struct sk_buff *saved_skb[NUM_TX_DESC], *skb;
+                int j;
+                for (j = 0; tp->cur_tx - tp->dirty_tx > 0 ; j++, tp->dirty_tx++)
+                        saved_skb[j]=tp->tx_skbuff[tp->dirty_tx % NUM_TX_DESC];
+                tp->dirty_tx = tp->cur_tx = 0;
+
+                for (i = 0; i < j; i++) {
+                        skb = tp->tx_skbuff[i] = saved_skb[i];
+                        /* Always alignment */
+                        memcpy((unsigned char*)(tp->tx_buf[i].buf),
+                                                skb->data, skb->len);
+                        tp->tx_buf[i].cmdsts = OWN | skb->len;
+                        /* Note: the chip doesn't have auto-pad! */
+                        /*
+                        outl(tp->tx_flag|(skb->len>=ETH_ZLEN?skb->len:ETH_ZLEN),
+                                 ioaddr + TxStatus0 + i*4);
+                        */
+                }
+                outl(TxENA, ioaddr + cr);
+                tp->cur_tx = i;
+                while (i < NUM_TX_DESC)
+                        tp->tx_skbuff[i++] = 0;
+                if (tp->cur_tx - tp->dirty_tx < NUM_TX_DESC) {/* Typical path */
+                        dev->tbusy = 0;
+                        tp->tx_full = 0;
+                } else {
+                        tp->tx_full = 1;
+                }
+        }
+
+        dev->trans_start = jiffies;
+        tp->stats.tx_errors++;
+        /* Enable all known interrupts by setting the interrupt mask. */
+        outl((RxOK|RxERR|RxORN|RxSOVR|TxOK|TxERR|TxURN), ioaddr + imr);
+        return;
+}
+
+
+/* Initialize the Rx and Tx rings, along with various 'dev' bits. */
+static void
+sis900_init_ring(struct net_device *dev)
+{
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        int i;
+
+        tp->tx_full = 0;
+        tp->cur_rx = 0;
+        tp->dirty_tx = tp->cur_tx = 0;
+
+        /* Tx Buffer */
+        for (i = 0; i < NUM_TX_DESC; i++) {
+                tp->tx_skbuff[i] = 0;
+                tp->tx_buf[i].buf = &tp->tx_bufs[i*TX_BUF_SIZE];
+                tp->tx_buf[i].bufPhys =
+                                virt_to_bus(&tp->tx_bufs[i*TX_BUF_SIZE]);
+        }
+
+        /* Tx Descriptor */
+        for (i = 0; i< NUM_TX_DESC; i++) {
+                tp->tx_buf[i].llink = (u32)
+                        &(tp->tx_buf[((i+1) < NUM_TX_DESC) ? (i+1) : 0]);
+                tp->tx_buf[i].plink = (u32)
+                        virt_to_bus(&(tp->tx_buf[((i+1) < NUM_TX_DESC) ?
+                                (i+1) : 0].plink));
+                tp->tx_buf[i].physAddr=
+                                virt_to_bus(&(tp->tx_buf[i].plink));
+                tp->tx_buf[i].cmdsts=0;
+        }
+
+        /* Rx Buffer */
+        for (i = 0; i < NUM_RX_DESC; i++) {
+                tp->rx_buf[i].buf = &tp->rx_bufs[i*RX_BUF_SIZE];
+                tp->rx_buf[i].bufPhys =
+                                virt_to_bus(&tp->rx_bufs[i*RX_BUF_SIZE]);
+        }
+
+        /* Rx Descriptor */
+        for (i = 0; i< NUM_RX_DESC; i++) {
+                tp->rx_buf[i].llink = (u32)
+                        &(tp->rx_buf[((i+1) < NUM_RX_DESC) ? (i+1) : 0]);
+                tp->rx_buf[i].plink = (u32)
+                        virt_to_bus(&(tp->rx_buf[((i+1) < NUM_RX_DESC) ?
+                                (i+1) : 0].plink));
+                tp->rx_buf[i].physAddr=
+                                virt_to_bus(&(tp->rx_buf[i].plink));
+                tp->rx_buf[i].cmdsts=RX_BUF_SIZE;
+        }
+}
+
+static int
+sis900_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        long ioaddr = dev->base_addr;
+        int entry;
+
+        /* Block a timer-based transmit from overlapping.  This could better be
+           done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */
+        if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) {
+                if (jiffies - dev->trans_start < TX_TIMEOUT)
+                        return 1;
+                sis900_tx_timeout(dev);
+                return 1;
+        }
+
+        /* Calculate the next Tx descriptor entry. ????? */
+        entry = tp->cur_tx % NUM_TX_DESC;
+
+        tp->tx_skbuff[entry] = skb;
+
+        if (sis900_debug > 5) {
+                int i;
+                printk(KERN_INFO "%s: SKB Tx Frame contents:(len=%d)",
+                                                dev->name,skb->len);
+
+                for (i = 0; i < skb->len; i++) {
+                        printk("%2.2x ",
+                        (u8)skb->data[i]);
+                }
+                printk(".\n");
+        }
+
+        memcpy(tp->tx_buf[entry].buf,
+                                skb->data, skb->len);
+
+        tp->tx_buf[entry].cmdsts=(OWN | skb->len);
+
+        //tp->tx_buf[entry].plink = 0;
+        outl(TxENA, ioaddr + cr);
+        if (++tp->cur_tx - tp->dirty_tx < NUM_TX_DESC) {/* Typical path */
+                clear_bit(0, (void*)&dev->tbusy);
+        } else {
+                tp->tx_full = 1;
+        }
+
+        /* Note: the chip doesn't have auto-pad! */
+
+        dev->trans_start = jiffies;
+        if (sis900_debug > 4)
+                printk(KERN_INFO "%s: Queued Tx packet at "
+                                "%p size %d to slot %d.\n",
+                           dev->name, skb->data, (int)skb->len, entry);
+
+        return 0;
+}
+
+/* The interrupt handler does all of the Rx thread work and cleans up
+   after the Tx thread. */
+static void sis900_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
+{
+        struct net_device *dev = (struct net_device *)dev_instance;
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        int boguscnt = max_interrupt_work;
+        int status;
+        long ioaddr = dev->base_addr;
+
+#if defined(__i386__)
+        /* A lock to prevent simultaneous entry bug on Intel SMP machines. */
+        if (test_and_set_bit(0, (void*)&dev->interrupt)) {
+                printk(KERN_INFO "%s: SMP simultaneous entry of "
+                                "an interrupt handler.\n", dev->name);
+                dev->interrupt = 0;     /* Avoid halting machine. */
+                return;
+        }
+#else
+        if (dev->interrupt) {
+                printk(KERN_INFO "%s: Re-entering the "
+                                "interrupt handler.\n", dev->name);
+                return;
+        }
+        dev->interrupt = 1;
+#endif
+
+        do {
+                status = inl(ioaddr + isr);
+                /* Acknowledge all of the current interrupt sources ASAP. */
+                outl(status, ioaddr + isr); // ?????
+
+                if (sis900_debug > 4)
+                        printk(KERN_INFO "%s: interrupt  status=%#4.4x "
+                                "new intstat=%#4.4x.\n",
+                                dev->name, status, inl(ioaddr + isr));
+
+                if ((status & (TxURN|TxERR|TxOK | RxORN|RxERR|RxOK)) == 0) {
+                        break;
+                }
+
+                if (status & (RxOK|RxORN|RxERR)) /* Rx interrupt */
+                        sis900_rx(dev);
+
+                if (status & (TxOK | TxERR)) {
+                        unsigned int dirty_tx;
+
+                        if (sis900_debug > 5) {
+                                printk(KERN_INFO "TxOK:tp->cur_tx:%d,"
+                                                "tp->dirty_tx:%x\n",
+                                        tp->cur_tx, tp->dirty_tx);
+                        }
+                        for (dirty_tx = tp->dirty_tx; dirty_tx < tp->cur_tx;
+                                dirty_tx++)
+                        {
+                                int i;
+                                int entry = dirty_tx % NUM_TX_DESC;
+                                int txstatus = tp->tx_buf[entry].cmdsts;
+
+                                if (sis900_debug > 4) {
+                                        printk(KERN_INFO "%s:     Tx Frame contents:"
+                                                "(len=%d)",
+                                                dev->name, (txstatus & DSIZE));
+
+                                        for (i = 0; i < (txstatus & DSIZE) ;
+                                                                        i++) {
+                                                printk("%2.2x ",
+                                                (u8)(tp->tx_buf[entry].buf[i]));
+                                        }
+                                        printk(".\n");
+                                }
+                                if ( ! (txstatus & (OK | UNDERRUN)))
+                                {
+                                        if (sis900_debug > 1)
+                                                printk(KERN_INFO "Tx NOT (OK,"
+                                                        "UnderRun)\n");
+                                        break;  /* It still hasn't been Txed */
+                                }
+
+                                /* Note: TxCarrierLost is always asserted
+                                                at 100mbps.                 */
+                                if (txstatus & (OWCOLL | ABORT)) {
+                                        /* There was an major error, log it. */
+                                        if (sis900_debug > 1)
+                                                printk(KERN_INFO "Tx Out of "
+                                                        " Window,Abort\n");
+#ifndef final_version
+                                        if (sis900_debug > 1)
+                                                printk(KERN_INFO "%s: Transmit "
+                                                    "error, Tx status %8.8x.\n",
+                                                           dev->name, txstatus);
+#endif
+                                        tp->stats.tx_errors++;
+                                        if (txstatus & ABORT) {
+                                                tp->stats.tx_aborted_errors++;
+                                        }
+                                        if (txstatus & NOCARRIER)
+                                                tp->stats.tx_carrier_errors++;
+                                        if (txstatus & OWCOLL)
+                                                tp->stats.tx_window_errors++;
+#ifdef ETHER_STATS
+                                        if ((txstatus & COLCNT)==COLCNT)
+                                                tp->stats.collisions16++;
+#endif
+                                } else {
+#ifdef ETHER_STATS
+                                        /* No count for tp->stats.tx_deferred */
+#endif
+                                        if (txstatus & UNDERRUN) {
+                                           if (sis900_debug > 2)
+                                             printk(KERN_INFO "Tx UnderRun\n");
+                                        }
+                                        tp->stats.collisions +=
+                                                        (txstatus >> 16) & 0xF;
+#if LINUX_VERSION_CODE > 0x20119
+                                        tp->stats.tx_bytes += txstatus & DSIZE;
+#endif
+                                        if (sis900_debug > 2)
+                                           printk(KERN_INFO "Tx Transmit OK\n");
+                                        tp->stats.tx_packets++;
+                                }
+
+                                /* Free the original skb. */
+                                if (sis900_debug > 2)
+                                        printk(KERN_INFO "Free original skb\n");
+                                dev_free_skb(tp->tx_skbuff[entry]);
+                                tp->tx_skbuff[entry] = 0;
+                        } // for dirty
+
+#ifndef final_version
+                        if (tp->cur_tx - dirty_tx > NUM_TX_DESC) {
+                                printk(KERN_INFO"%s: Out-of-sync dirty pointer,"
+                                                " %d vs. %d, full=%d.\n",
+                                                dev->name, dirty_tx,
+                                                tp->cur_tx, tp->tx_full);
+                                dirty_tx += NUM_TX_DESC;
+                        }
+#endif
+
+                        if (tp->tx_full && dirty_tx > tp->cur_tx-NUM_TX_DESC) {
+                                /* The ring is no longer full, clear tbusy. */
+				if (sis900_debug > 3)
+                                   printk(KERN_INFO "Tx Ring NO LONGER Full\n");
+                                tp->tx_full = 0;
+                                dev->tbusy = 0;
+                                mark_bh(NET_BH);
+                        }
+
+                        tp->dirty_tx = dirty_tx;
+                        if (sis900_debug > 2)
+                           printk(KERN_INFO "TxOK,tp->cur_tx:%d,tp->dirty:%d\n",
+                                                tp->cur_tx, tp->dirty_tx);
+                } // if (TxOK | TxERR)
+
+                /* Check uncommon events with one test. */
+                if (status & (RxORN | TxERR | RxERR)) {
+                        if (sis900_debug > 2)
+                                printk(KERN_INFO "%s: Abnormal interrupt,"
+                                        "status %8.8x.\n", dev->name, status);
+
+                        if (status == 0xffffffff)
+                                break;
+                        if (status & (RxORN | RxERR))
+                                tp->stats.rx_errors++;
+
+
+                        if (status & RxORN) {
+                                tp->stats.rx_over_errors++;
+                        }
+                }
+                if (--boguscnt < 0) {
+                        printk(KERN_INFO "%s: Too much work at interrupt, "
+                                   "IntrStatus=0x%4.4x.\n",
+                                   dev->name, status);
+                        break;
+                }
+        } while (1);
+
+        if (sis900_debug > 3)
+                printk(KERN_INFO "%s: exiting interrupt, intr_status=%#4.4x.\n",
+                           dev->name, inl(ioaddr + isr));
+
+#if defined(__i386__)
+        clear_bit(0, (void*)&dev->interrupt);
+#else
+        dev->interrupt = 0;
+#endif
+        return;
+}
+
+/* The data sheet doesn't describe the Rx ring at all, so I'm guessing at the
+   field alignments and semantics. */
+static int sis900_rx(struct net_device *dev)
+{
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        long ioaddr = dev->base_addr;
+        u16 cur_rx = tp->cur_rx % NUM_RX_DESC;
+        int rx_status=tp->rx_buf[cur_rx].cmdsts;
+
+        if (sis900_debug > 4)
+                printk(KERN_INFO "%s: sis900_rx, current %4.4x,"
+                                " rx status=%8.8x\n",
+                                dev->name, cur_rx,
+                                rx_status);
+
+        while (rx_status & OWN) {
+                int rx_size = rx_status & DSIZE;
+                rx_size -= CRC_SIZE;
+
+                if (sis900_debug > 4) {
+                        int i;
+                        printk(KERN_INFO "%s:  sis900_rx, rx status %8.8x,"
+                                        " size %4.4x, cur %4.4x.\n",
+                                   dev->name, rx_status, rx_size, cur_rx);
+                        printk(KERN_INFO "%s: Rx Frame contents:", dev->name);
+
+                        for (i = 0; i < rx_size; i++) {
+                                printk("%2.2x ",
+                                (u8)(tp->rx_buf[cur_rx].buf[i]));
+                        }
+
+                        printk(".\n");
+                }
+                if (rx_status & TOOLONG) {
+                        if (sis900_debug > 1)
+                                printk(KERN_INFO "%s: Oversized Ethernet frame,"
+                                                " status %4.4x!\n",
+                                           dev->name, rx_status);
+                        tp->stats.rx_length_errors++;
+                } else if (rx_status & (RXISERR | RUNT | CRCERR | FAERR)) {
+                        if (sis900_debug > 1)
+                                printk(KERN_INFO"%s: Ethernet frame had errors,"
+                                        " status %4.4x.\n",
+                                        dev->name, rx_status);
+                        tp->stats.rx_errors++;
+                        if (rx_status & (RXISERR | FAERR))
+                                tp->stats.rx_frame_errors++;
+                        if (rx_status & (RUNT | TOOLONG))
+                                tp->stats.rx_length_errors++;
+                        if (rx_status & CRCERR) tp->stats.rx_crc_errors++;
+                } else {
+                        /* Malloc up new buffer, compatible with net-2e. */
+                        /* Omit the four octet CRC from the length. */
+                        struct sk_buff *skb;
+
+                        skb = dev_alloc_skb(rx_size + 2);
+                        if (skb == NULL) {
+                                printk(KERN_INFO "%s: Memory squeeze,"
+                                                "deferring packet.\n",
+                                                dev->name);
+                                /* We should check that some rx space is free.
+                                   If not,
+                                   free one and mark stats->rx_dropped++. */
+                                tp->stats.rx_dropped++;
+                                tp->rx_buf[cur_rx].cmdsts = RX_BUF_SIZE;
+                                break;
+                        }
+                        skb->dev = dev;
+                        skb_reserve(skb, 2); /* 16 byte align the IP fields. */
+                        if (rx_size+CRC_SIZE > RX_BUF_SIZE) {
+                                /*
+                                int semi_count = RX_BUF_LEN - ring_offset - 4;
+                                memcpy(skb_put(skb, semi_count),
+                                        &rx_bufs[ring_offset + 4], semi_count);
+                                memcpy(skb_put(skb, rx_size-semi_count),
+                                        rx_bufs, rx_size - semi_count);
+                                if (sis900_debug > 4) {
+                                        int i;
+                                        printk(KERN_DEBUG"%s:  Frame wrap @%d",
+                                                   dev->name, semi_count);
+                                        for (i = 0; i < 16; i++)
+                                                printk(" %2.2x", rx_bufs[i]);
+                                        printk(".\n");
+                                        memset(rx_bufs, 0xcc, 16);
+                                }
+                                */
+                        } else {
+#if 0  /* USE_IP_COPYSUM */
+                                eth_copy_and_sum(skb,
+                                   tp->rx_buf[cur_rx].buf, rx_size, 0);
+                                skb_put(skb, rx_size);
+#else
+                                memcpy(skb_put(skb, rx_size),
+                                        tp->rx_buf[cur_rx].buf, rx_size);
+#endif
+                        }
+                        skb->protocol = eth_type_trans(skb, dev);
+                        netif_rx(skb);
+#if LINUX_VERSION_CODE > 0x20119
+                        tp->stats.rx_bytes += rx_size;
+#endif
+                        tp->stats.rx_packets++;
+                }
+                tp->rx_buf[cur_rx].cmdsts = RX_BUF_SIZE;
+
+                cur_rx = ((cur_rx+1) % NUM_RX_DESC);
+                rx_status = tp->rx_buf[cur_rx].cmdsts;
+        } // while
+        if (sis900_debug > 4)
+                printk(KERN_INFO "%s: Done sis900_rx(), current %4.4x "
+                                "Cmd %2.2x.\n",
+                           dev->name, cur_rx,
+                           inb(ioaddr + cr));
+        tp->cur_rx = cur_rx;
+        return 0;
+}
+
+static int
+sis900_close(struct net_device *dev)
+{
+        long ioaddr = dev->base_addr;
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        int i;
+
+        dev->start = 0;
+        dev->tbusy = 1;
+
+        if (sis900_debug > 1)
+                printk(KERN_DEBUG"%s: Shutting down ethercard, status was 0x%4.4x.\n",
+                           dev->name, inl(ioaddr + isr));
+
+        /* Disable interrupts by clearing the interrupt mask. */
+        outl(0x0000, ioaddr + imr);
+
+        /* Stop the chip's Tx and Rx DMA processes. */
+        outl(0x00, ioaddr + cr);
+
+        del_timer(&tp->timer);
+
+        free_irq(dev->irq, dev);
+
+        for (i = 0; i < NUM_TX_DESC; i++) {
+                if (tp->tx_skbuff[i])
+                        dev_free_skb(tp->tx_skbuff[i]);
+                tp->tx_skbuff[i] = 0;
+        }
+        kfree(tp->rx_bufs);
+        kfree(tp->tx_bufs);
+
+        /* Green! Put the chip in low-power mode. */
+
+        MOD_DEC_USE_COUNT;
+
+        return 0;
+}
+
+static int mii_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+        u16 *data = (u16 *)&rq->ifr_data;
+
+        switch(cmd) {
+        case SIOCDEVPRIVATE:            /* Get the address of the PHY in use. */
+                data[0] = tp->phys[tp->phy_idx];
+                /* Fall Through */
+        case SIOCDEVPRIVATE+1:          /* Read the specified MII register. */
+                data[3] = mdio_read(dev, data[0] & 0x1f, data[1] & 0x1f);
+                return 0;
+        case SIOCDEVPRIVATE+2:          /* Write the specified MII register */
+                if (!suser())
+                        return -EPERM;
+                mdio_write(dev, data[0] & 0x1f, data[1] & 0x1f, data[2]);
+                return 0;
+        default:
+                return -EOPNOTSUPP;
+        }
+}
+
+static struct enet_statistics *
+sis900_get_stats(struct net_device *dev)
+{
+        struct sis900_private *tp = (struct sis900_private *)dev->priv;
+
+        return &tp->stats;
+}
+
+/* Set or clear the multicast filter for this adaptor.
+   This routine is not state sensitive and need not be SMP locked. */
+
+static u16 elComputeHashTableIndex(u8 *addr)
+{
+#define POLYNOMIAL 0x04C11DB6L
+    u32      crc = 0xffffffff, msb;
+    int      i, j;
+    u8       byte;
+
+    for( i=0; i<6; i++ ) {
+        byte = *addr++;
+        for( j=0; j<8; j++ ) {
+            msb = crc >> 31;
+            crc <<= 1;
+            if( msb ^ ( byte & 1 )) {
+                crc ^= POLYNOMIAL;
+                crc |= 1;
+            }
+            byte >>= 1;
+        }
+    }
+    // 7 bit crc for 128 bit hash table
+    return( (int)(crc >> 25) );
+}
+
+static u16 elMIIpollBit(struct net_device *dev,
+                         int phy_id,
+                         int location,
+                         u16 mask,
+                         u16 polarity,
+                         u16 *value)
+{
+        u32 i;
+        i=0;
+        while (1) {
+                *value = mdio_read(dev, phy_id, location);
+                if (polarity) {
+                        if (mask & *value) return(TRUE);
+                } else {
+                        if (mask & ~(*value)) return(TRUE);
+                }
+                if (++i == 1200) break;
+        }
+        return(FALSE);
+}
+
+static u16 elPMDreadMode(struct net_device *dev,
+                         int phy_id,
+                         int *speed,
+                         int *duplex)
+{
+        u16 status, OurCap;
+
+        *speed = HW_SPEED_10_MBPS;
+        *duplex = FDX_CAPABLE_HALF_SELECTED;
+
+        status = mdio_read(dev, phy_id, MII_ANLPAR);
+        OurCap = mdio_read(dev, phy_id, MII_ANAR);
+	if (sis900_debug > 1) {
+		printk(KERN_INFO "Link Part Status %4X\n", status);
+		printk(KERN_INFO "Our Status %4X\n", OurCap);
+		printk(KERN_INFO "Status Reg %4X\n",
+					mdio_read(dev, phy_id, MII_STATUS));
+	}
+	status &= OurCap;
+
+        if ( !( status &
+                (MII_NWAY_T|MII_NWAY_T_FDX | MII_NWAY_TX | MII_NWAY_TX_FDX ))) {
+		if (sis900_debug > 1) {
+			printk(KERN_INFO "The other end NOT support NWAY...\n");
+		}
+                while (( status = mdio_read(dev, phy_id, 18)) & 0x4000) ;
+                while (( status = mdio_read(dev, phy_id, 18)) & 0x0020) ;
+                if (status & 0x80)
+                        *speed = HW_SPEED_100_MBPS;
+                if (status & 0x40)
+                        *duplex = FDX_CAPABLE_FULL_SELECTED;
+                if (sis900_debug > 3) {
+                        printk(KERN_INFO"%s: Setting %s%s-duplex.\n",
+                                dev->name,
+                                *speed == HW_SPEED_100_MBPS ?
+                                        "100mbps " : "10mbps ",
+                                *duplex == FDX_CAPABLE_FULL_SELECTED ?
+                                        "full" : "half");
+                }
+        } else {
+		if (sis900_debug > 1) {
+			printk(KERN_INFO "The other end support NWAY...\n");
+		}
+
+                if (status & (MII_NWAY_TX_FDX | MII_NWAY_T_FDX)) {
+                        *duplex = FDX_CAPABLE_FULL_SELECTED;
+                }
+                if (status & (MII_NWAY_TX_FDX | MII_NWAY_TX)) {
+                        *speed = HW_SPEED_100_MBPS;
+                }
+                if (sis900_debug > 3) {
+                        printk(KERN_INFO"%s: Setting %s%s-duplex based on"
+                                " auto-negotiated partner ability.\n",
+                                dev->name,
+                                *speed == HW_SPEED_100_MBPS ?
+                                        "100mbps " : "10mbps ",
+                                *duplex == FDX_CAPABLE_FULL_SELECTED ?
+                                        "full" : "half");
+                }
+        }
+        return (status);
+}
+
+static u16 elAutoNegotiate(struct net_device *dev, int phy_id, int *duplex, int *speed)
+{
+        u16 status, retnVal;
+
+	if (sis900_debug > 1) {
+		printk(KERN_INFO "AutoNegotiate...\n");
+	}
+        mdio_write(dev, phy_id, MII_CONTROL, 0);
+        mdio_write(dev, phy_id, MII_CONTROL, MIICNTL_AUTO | MIICNTL_RST_AUTO);
+        retnVal = elMIIpollBit(dev, phy_id, MII_CONTROL, MIICNTL_RST_AUTO,
+				FALSE,&status);
+	if (!retnVal) {
+		printk(KERN_INFO "Not wait for Reset Complete\n");
+	}
+        retnVal = elMIIpollBit(dev, phy_id, MII_STATUS, MIISTAT_AUTO_DONE,
+				TRUE, &status);
+	if (!retnVal) {
+		printk(KERN_INFO "Not wait for AutoNego Complete\n");
+	}
+        retnVal = elMIIpollBit(dev, phy_id, MII_STATUS, MIISTAT_LINK,
+				TRUE, &status);
+	if (!retnVal) {
+		printk(KERN_INFO "Not wait for Link Complete\n");
+	}
+        if (status & MIISTAT_LINK) {
+                elPMDreadMode(dev, phy_id, speed, duplex);
+                elSetMediaType(dev, *speed, *duplex);
+        }
+        return(status);
+}
+
+static void elSetCapability(struct net_device *dev, int phy_id,
+			    int duplex, int speed)
+{
+        u16 cap = ( MII_NWAY_T  | MII_NWAY_T_FDX  |
+		    MII_NWAY_TX | MII_NWAY_TX_FDX | MII_NWAY_CSMA_CD );
+
+	if (speed != 100) {
+		cap &= ~( MII_NWAY_TX | MII_NWAY_TX_FDX );
+		if (sis900_debug > 1) {
+			printk(KERN_INFO "UNSET 100Mbps\n");
+		}
+	}
+
+	if (!duplex) {
+		cap &= ~( MII_NWAY_T_FDX | MII_NWAY_TX_FDX );
+		if (sis900_debug > 1) {
+			printk(KERN_INFO "UNSET full-duplex\n");
+		}
+	}
+
+        mdio_write(dev, phy_id, MII_ANAR, cap);
+}
+
+static void elSetMediaType(struct net_device *dev, int speed, int duplex)
+{
+        long ioaddr = dev->base_addr;
+        u32     txCfgOn = 0, txCfgOff = TxDRNT;
+        u32     rxCfgOn = 0, rxCfgOff = 0;
+
+        if (speed == HW_SPEED_100_MBPS) {
+                txCfgOn |= (TxDRNT_100 | TxHBI);
+        } else {
+                txCfgOn |= TxDRNT_10;
+        }
+
+        if (duplex == FDX_CAPABLE_FULL_SELECTED) {
+                txCfgOn |= (TxCSI | TxHBI);
+                rxCfgOn |= RxATP;
+        } else {
+                txCfgOff |= (TxCSI | TxHBI);
+                rxCfgOff |= RxATP;
+        }
+        outl( (inl(ioaddr + txcfg) & ~txCfgOff) | txCfgOn, ioaddr + txcfg);
+        outl( (inl(ioaddr + rxcfg) & ~rxCfgOff) | rxCfgOn, ioaddr + rxcfg);
+}
+
+static void set_rx_mode(struct net_device *dev)
+{
+        long ioaddr = dev->base_addr;
+        u16 mc_filter[8];
+        int i;
+        int rx_mode;
+        u32 rxCfgOn = 0, rxCfgOff = 0;
+        u32 txCfgOn = 0, txCfgOff = 0;
+
+        if (sis900_debug > 3)
+                printk(KERN_INFO "%s: set_rx_mode (%4.4x) done--"
+                                "RxCfg %8.8x.\n",
+                                dev->name, dev->flags, inl(ioaddr + rxcfg));
+
+        /* Note: do not reorder, GCC is clever about common statements. */
+        if (dev->flags & IFF_PROMISC) {
+                printk(KERN_NOTICE"%s: Promiscuous mode enabled.\n", dev->name);
+                rx_mode = ACCEPT_ALL_BCASTS | ACCEPT_ALL_MCASTS |
+                                ACCEPT_CAM_QUALIFIED | ACCEPT_ALL_PHYS;
+                for (i=0 ; i<8 ; i++)
+                        mc_filter[i]=0xffff;
+        } else if ((dev->mc_count > multicast_filter_limit)
+                           ||  (dev->flags & IFF_ALLMULTI)) {
+                rx_mode = ACCEPT_ALL_BCASTS | ACCEPT_ALL_MCASTS |
+                                ACCEPT_CAM_QUALIFIED;
+                for (i=0 ; i<8 ; i++)
+                        mc_filter[i]=0xffff;
+        } else {
+                struct dev_mc_list *mclist;
+                rx_mode = ACCEPT_ALL_BCASTS | ACCEPT_ALL_MCASTS |
+                                ACCEPT_CAM_QUALIFIED;
+                for (i=0 ; i<8 ; i++)
+                        mc_filter[i]=0;
+                for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count;
+                         i++, mclist = mclist->next)
+                        set_bit(elComputeHashTableIndex(mclist->dmi_addr),
+                                                mc_filter);
+        }
+
+        for (i=0 ; i<8 ; i++) {
+                outl((u32)(0x00000004+i) << 16, ioaddr + rfcr);
+                outl(mc_filter[i], ioaddr + rfdr);
+        }
+        /* We can safely update without stopping the chip. */
+        //rx_mode = ACCEPT_CAM_QUALIFIED | ACCEPT_ALL_BCASTS | ACCEPT_ALL_PHYS;
+        //rx_mode = ACCEPT_CAM_QUALIFIED | ACCEPT_ALL_BCASTS;
+        outl(RFEN | ((rx_mode & (ACCEPT_ALL_MCASTS | ACCEPT_ALL_BCASTS |
+                          ACCEPT_ALL_PHYS)) << RFAA_shift), ioaddr + rfcr);
+
+        if (rx_mode & ACCEPT_ALL_ERRORS) {
+                rxCfgOn = RxAEP | RxARP | RxAJAB;
+        } else {
+                rxCfgOff = RxAEP | RxARP | RxAJAB;
+        }
+        if (rx_mode & MAC_LOOPBACK) {
+                rxCfgOn |= RxATP;
+                txCfgOn |= TxMLB;
+        } else {
+                if (!(( (struct sis900_private *)(dev->priv) )->full_duplex))
+                        rxCfgOff |= RxATP;
+                txCfgOff |= TxMLB;
+        }
+
+        if (sis900_debug > 2) {
+                printk(KERN_INFO "Before Set TxCfg=%8.8x\n",inl(ioaddr+txcfg));
+                printk(KERN_INFO "Before Set RxCfg=%8.8x\n",inl(ioaddr+rxcfg));
+        }
+
+        outl((inl(ioaddr + rxcfg) | rxCfgOn) & ~rxCfgOff, ioaddr + rxcfg);
+        outl((inl(ioaddr + txcfg) | txCfgOn) & ~txCfgOff, ioaddr + txcfg);
+
+        if (sis900_debug > 2) {
+                printk(KERN_INFO "After Set TxCfg=%8.8x\n",inl(ioaddr+txcfg));
+                printk(KERN_INFO "After Set RxCfg=%8.8x\n",inl(ioaddr+rxcfg));
+                printk(KERN_INFO "Receive Filter Register:%8.8x\n",
+                                                        inl(ioaddr + rfcr));
+        }
+        return;
+}
+
+static void sis900_reset(struct net_device *dev)
+{
+        long ioaddr = dev->base_addr;
+
+        outl(0, ioaddr + ier);
+        outl(0, ioaddr + imr);
+        outl(0, ioaddr + rfcr);
+
+        outl(RxRESET | TxRESET | RESET, ioaddr + cr);
+        outl(PESEL, ioaddr + cfg);
+
+        set_rx_mode(dev);
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+        return sis900_probe(0);
+}
+
+void
+cleanup_module(void)
+{
+        struct net_device *next_dev;
+
+        /* No need to check MOD_IN_USE, as sys_delete_module() checks. */
+        while (root_sis900_dev) {
+                struct sis900_private *tp =
+                        (struct sis900_private *)root_sis900_dev->priv;
+                next_dev = tp->next_module;
+                unregister_netdev(root_sis900_dev);
+                release_region(root_sis900_dev->base_addr,
+                                           pci_tbl[tp->chip_id].io_size);
+                kfree(tp);
+                kfree(root_sis900_dev);
+                root_sis900_dev = next_dev;
+        }
+}
+
+#endif  /* MODULE */
+/*
+ * Local variables:
+ *  compile-command: "gcc -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c sis900.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`"
+ *  SMP-compile-command: "gcc -D__SMP__ -DMODULE -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -c sis900.c `[ -f /usr/include/linux/modversions.h ] && echo -DMODVERSIONS`"
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ *  tab-width: 4
+ * End:
+ */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)