patch-2.1.125 linux/drivers/net/rrunner.c

Next file: linux/drivers/net/rrunner.h
Previous file: linux/drivers/net/ppp.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.124/linux/drivers/net/rrunner.c linux/drivers/net/rrunner.c
@@ -0,0 +1,1208 @@
+/*
+ * rrunner.c: Linux driver for the Essential RoadRunner HIPPI board.
+ *
+ * Written 1998 by Jes Sorensen, <Jes.Sorensen@cern.ch>.
+ *
+ * Thanks to Essential Communication for providing us with hardware
+ * and very comprehensive documentation without which I would not have
+ * been able to write this driver. A special thank you to John Gibbon
+ * for sorting out the legal issues, with the NDA, allowing the code to
+ * be released under the GPL.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#define DEBUG 1
+#define RX_DMA_SKBUFF 1
+#define PKT_COPY_THRESHOLD 512
+
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/hippidevice.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/cache.h>
+#include <asm/byteorder.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include "rrunner.h"
+
+/*
+ * Implementation notes:
+ *
+ * The DMA engine only allows for DMA within physical 64KB chunks of
+ * memory. The current approach of the driver (and stack) is to use
+ * linear blocks of memory for the skbuffs. However, as the data block
+ * is always the first part of the skb and skbs are 2^n aligned so we
+ * are guarantted to get the whole block within one 64KB align 64KB
+ * chunk.
+ *
+ * On the long term, relying on being able to allocate 64KB linear
+ * chunks of memory is not feasible and the skb handling code and the
+ * stack will need to know about I/O vectors or something similar.
+ */
+
+static const char *version = "rrunner.c: v0.06 09/02/98  Jes Sorensen (Jes.Sorensen@cern.ch)\n";
+
+static unsigned int read_eeprom(struct rr_private *rrpriv,
+				unsigned long offset,
+				unsigned char *buf,
+				unsigned long length);
+static u32 read_eeprom_word(struct rr_private *rrpriv,
+			    void * offset);
+static int rr_load_firmware(struct device *dev);
+
+
+__initfunc(int rr_hippi_probe (struct device *dev))
+{
+	static int i = 0;
+	int boards_found = 0;
+	int version_disp;	/* was version info already displayed? */
+	u8 pci_bus;		/* PCI bus number (0-255) */
+	u8 pci_dev_fun;		/* PCI device and function numbers (0-255) */
+	u8 pci_latency;
+	u16 command;		/* PCI Configuration space Command register */
+	unsigned int tmp;
+	u8 irq;
+	struct rr_private *rrpriv;
+
+	if (!pci_present())		/* is PCI BIOS even present? */
+		return -ENODEV;
+
+	version_disp = 0;
+
+	for (; i < 255; i++)
+	{
+		if (pcibios_find_device(PCI_VENDOR_ID_ESSENTIAL,
+					PCI_DEVICE_ID_ESSENTIAL_ROADRUNNER,
+					i, &pci_bus, &pci_dev_fun) != 0)
+			break;
+
+		pcibios_read_config_word(pci_bus, pci_dev_fun,
+					 PCI_COMMAND, &command);
+
+		/* Enable mastering */
+
+		command |= PCI_COMMAND_MASTER;
+		pcibios_write_config_word(pci_bus, pci_dev_fun,
+					  PCI_COMMAND, command);
+
+		if (!(command & PCI_COMMAND_MEMORY)){
+			printk("shared mem not enabled - unable to configure RoadRunner\n");
+			break;
+		}
+
+		/*
+		 * So we found our HIPPI ... time to tell the system.
+		 */
+
+		dev = init_hippi_dev(dev, sizeof(struct rr_private));
+
+		if (dev == NULL)
+			break;
+
+		rrpriv = (struct rr_private *)dev->priv;
+
+		/* Read register base address from
+		   PCI Configuration Space */
+
+		pcibios_read_config_dword(pci_bus, pci_dev_fun,
+					  PCI_BASE_ADDRESS_0, &tmp);
+
+		pcibios_read_config_byte(pci_bus, pci_dev_fun,
+					 PCI_INTERRUPT_LINE, &irq);
+
+		dev->irq = irq;
+		rrpriv->pci_bus = pci_bus;
+		rrpriv->pci_dev_fun = pci_dev_fun;
+		sprintf(rrpriv->name, "RoadRunner serial HIPPI");
+#ifdef __SMP__
+		spin_lock_init(&rrpriv->lock);
+#endif
+
+		dev->open = &rr_open;
+		dev->hard_start_xmit = &rr_start_xmit;
+		dev->stop = &rr_close;
+		dev->get_stats = &rr_get_stats;
+		dev->do_ioctl = &rr_ioctl;
+
+		/*
+		 * Dummy value.
+		 */
+		dev->base_addr = 42;
+
+		/* display version info if adapter is found */
+		if (!version_disp)
+		{
+			/* set display flag to TRUE so that */
+			/* we only display this string ONCE */
+			version_disp = 1;
+			printk(version);
+		}
+
+		printk(KERN_INFO "%s: Essential RoadRunner serial HIPPI at 0x%08x, irq %i\n",
+		       dev->name, tmp, dev->irq);
+
+		pcibios_read_config_byte(pci_bus, pci_dev_fun,
+					 PCI_LATENCY_TIMER, &pci_latency);
+#if 0
+		if (pci_latency <= 48){
+			printk("  PCI latency counter too low (%i), setting to 48 clocks\n", pci_latency);
+			pcibios_write_config_byte(pci_bus, pci_dev_fun,
+						  PCI_LATENCY_TIMER, 48);
+		}
+#else
+		if (pci_latency <= 0x58)
+			pcibios_write_config_byte(pci_bus, pci_dev_fun,
+						  PCI_LATENCY_TIMER, 0x58);
+#endif
+		/*
+		 * Remap the regs into kernel space.
+		 */
+
+		rrpriv->regs = (struct rr_regs *)ioremap(tmp, 0x1000);
+		if (!rrpriv->regs){
+			printk(KERN_ERR "%s:  Unable to map I/O register, RoadRunner %i will be disabled.\n", dev->name, i);
+			break;
+		}
+
+		/*
+		 * Don't access any registes before this point!
+		 */
+#ifdef __BIG_ENDIAN
+		regs->HostCtrl |= NO_SWAP;
+#endif
+		/*
+		 * Need to add a case for little-endian 64-bit hosts here.
+		 */
+
+		rr_init(dev);
+
+		boards_found++;
+
+		/*
+		 * This is bollocks, but we need to tell the net-init
+		 * code that it shall go for the next device.
+		 */
+		dev->base_addr = 0;
+	}
+
+	/*
+	 * If we're at this point we're going through rr_hippi_probe()
+	 * for the first time.  Return success (0) if we've initialized
+	 * 1 or more boards. Otherwise, return failure (-ENODEV).
+	 */
+
+	if (boards_found > 0)
+		return 0;
+	else
+		return -ENODEV;
+}
+
+
+/*
+ * Commands are considered to be slow, thus there is no reason to
+ * inline this.
+ */
+static void rr_issue_cmd(struct rr_private *rrpriv, struct cmd *cmd)
+{
+	struct rr_regs *regs;
+	u32 idx;
+
+	regs = rrpriv->regs;
+	/*
+	 * This is temporary - it will go away in the final version.
+	 * We probably also want to make this function inline.
+	 */
+	if (regs->HostCtrl & NIC_HALTED){
+		printk("issuing command for halted NIC, code 0x%x, HostCtrl %08x\n", cmd->code, regs->HostCtrl);
+		if (regs->Mode & FATAL_ERR)
+			printk("error code %02x\n", regs->Fail1);
+	}
+
+	idx = rrpriv->info->cmd_ctrl.pi;
+
+	regs->CmdRing[idx] = *(u32*)(cmd);
+
+	idx = (idx - 1) % CMD_RING_ENTRIES;
+	rrpriv->info->cmd_ctrl.pi = idx;
+
+	if (regs->Mode & FATAL_ERR)
+		printk("error code %02x\n", regs->Fail1);
+}
+
+
+/*
+ * Reset the board in a sensible manner. The NIC is already halted
+ * when we get here and a spin-lock is held.
+ */
+static int rr_reset(struct device *dev)
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+	struct eeprom *hw = NULL;
+	u32 start_pc;
+	int i;
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+
+	rr_load_firmware(dev);
+
+	regs->TX_state = 0x01000000;
+	regs->RX_state = 0xff800000;
+	regs->AssistState = 0;
+	regs->LocalCtrl = CLEAR_INTA;
+	regs->BrkPt = 0x01;
+	regs->Timer = 0;
+	regs->TimerRef = 0;
+	regs->DmaReadState = RESET_DMA;
+	regs->DmaWriteState = RESET_DMA;
+	regs->DmaWriteHostHi = 0;
+	regs->DmaWriteHostLo = 0;
+	regs->DmaReadHostHi = 0;
+	regs->DmaReadHostLo = 0;
+	regs->DmaReadLen = 0;
+	regs->DmaWriteLen = 0;
+	regs->DmaWriteLcl = 0;
+	regs->DmaWriteIPchecksum = 0;
+	regs->DmaReadLcl = 0;
+	regs->DmaReadIPchecksum = 0;
+	regs->PciState = 0; /* 0x90 for GE? */
+	regs->Mode = SWAP_DATA;
+
+#if 0
+	/*
+	 * Don't worry, this is just black magic.
+	 */
+	regs->RxBase = 0xdf000;
+	regs->RxPrd = 0xdf000;
+	regs->RxCon = 0xdf000;
+	regs->TxBase = 0xce000;
+	regs->TxPrd = 0xce000;
+	regs->TxCon = 0xce000;
+	regs->RxIndPro = 0;
+	regs->RxIndCon = 0;
+	regs->RxIndRef = 0;
+	regs->TxIndPro = 0;
+	regs->TxIndCon = 0;
+	regs->TxIndRef = 0;
+	regs->pad10[0] = 0xcc000;
+	regs->DrCmndPro = 0;
+	regs->DrCmndCon = 0;
+	regs->DwCmndPro = 0;
+	regs->DwCmndCon = 0;
+	regs->DwCmndRef = 0;
+	regs->DrDataPro = 0;
+	regs->DrDataCon = 0;
+	regs->DrDataRef = 0;
+	regs->DwDataPro = 0;
+	regs->DwDataCon = 0;
+	regs->DwDataRef = 0;
+#endif
+
+	regs->MbEvent = 0xffffffff;
+	regs->Event = 0;
+
+	regs->TxPi = 0;
+	regs->IpRxPi = 0;
+
+	regs->EvtCon = 0;
+	regs->EvtPrd = 0;
+
+	rrpriv->info->evt_ctrl.pi = 0;
+
+	for (i = 0; i < CMD_RING_ENTRIES; i++)
+		regs->CmdRing[i] = 0;
+
+	regs->PciState = 0;
+
+	start_pc = read_eeprom_word(rrpriv, &hw->rncd_info.FwStart);
+
+#if (DEBUG > 1)
+	printk("%s: Executing firmware at address 0x%06x\n",
+	       dev->name, start_pc);
+#endif
+
+	regs->Pc = start_pc + 0x800;
+	udelay(5);
+
+	regs->Pc = start_pc;
+
+	return 0;
+}
+
+/*
+ * Read a string from the EEPROM.
+ */
+static unsigned int read_eeprom(struct rr_private *rrpriv,
+				unsigned long offset,
+				unsigned char *buf,
+				unsigned long length)
+{
+	struct rr_regs *regs = rrpriv->regs;
+	u32 misc, io, i;
+
+	io = regs->ExtIo;
+	regs->ExtIo = 0;
+	misc = regs->LocalCtrl;
+	regs->LocalCtrl = 0;
+
+	for (i = 0; i < length; i++){
+		regs->WinBase = (EEPROM_BASE + ((offset+i) << 3));
+		buf[i] = (regs->WinData >> 24) & 0xff;
+	}
+
+	regs->LocalCtrl = misc;
+	regs->ExtIo = io;
+
+	return i;
+}
+
+
+/*
+ * Shortcut to read one word (4 bytes) out of the EEPROM and convert
+ * it to our CPU byte-order.
+ */
+static u32 read_eeprom_word(struct rr_private *rrpriv,
+			    void * offset)
+{
+	u32 word;
+
+	if ((read_eeprom(rrpriv, (unsigned long)offset,
+			 (char *)&word, 4) == 4))
+		return be32_to_cpu(word);
+	return 0;
+}
+
+
+__initfunc(static int rr_init(struct device *dev))
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+	u32 sram_size, rev;
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+
+	rev = regs->FwRev;
+	if (rev > 0x00020024)
+		printk("  Firmware revision: %i.%i.%i\n", (rev >> 16),
+		       ((rev >> 8) & 0xff), (rev & 0xff));
+	else{
+		printk("  Firmware revision too old: %i.%i.%i, please upgrade to 2.0.37 or later.\n",
+		       (rev >> 16), ((rev >> 8) & 0xff), (rev & 0xff));
+		return -EFAULT;
+		
+	}
+
+	printk("  Maximum receive rings %i\n", regs->MaxRxRng);
+
+	sram_size = read_eeprom_word(rrpriv, (void *)8);
+	printk("  SRAM size 0x%06x\n", sram_size);
+
+	return 0;
+}
+
+
+static int rr_init1(struct device *dev)
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+	u32 hostctrl;
+	unsigned long myjif, flags, tmp_ptr;
+	struct cmd cmd;
+	short i;
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+
+	spin_lock_irqsave(&rrpriv->lock, flags);
+
+	hostctrl = regs->HostCtrl;
+	regs->HostCtrl |= HALT_NIC;
+
+	if (hostctrl & PARITY_ERR){
+		printk("%s: Parity error halting NIC - this is serious!\n",
+		       dev->name);
+		spin_unlock_irqrestore(&rrpriv->lock, flags);
+		return -EFAULT;
+	}
+
+
+	memset(rrpriv->rx_ctrl, 0, 256 * sizeof(struct ring_ctrl));
+	memset(rrpriv->info, 0, sizeof(struct rr_info));
+
+	tmp_ptr = virt_to_bus((void *)rrpriv->rx_ctrl);
+#if (BITS_PER_LONG == 64)
+	regs->RxRingHi = (tmp_ptr >> 32);
+#else
+	regs->RxRingHi = 0;
+#endif
+	regs->RxRingLo = ((tmp_ptr) & 0xffffffff);
+
+	tmp_ptr = virt_to_bus((void *)rrpriv->info);
+#if (BITS_PER_LONG == 64)
+	regs->InfoPtrHi = (tmp_ptr >> 32);
+#else
+	regs->InfoPtrHi = 0;
+#endif
+	regs->InfoPtrLo = ((tmp_ptr) & 0xffffffff);
+
+	rrpriv->info->evt_ctrl.entry_size = sizeof(struct event);
+	rrpriv->info->evt_ctrl.entries = EVT_RING_ENTRIES;
+	rrpriv->info->evt_ctrl.mode = 0;
+	rrpriv->info->evt_ctrl.pi = 0;
+	rrpriv->info->evt_ctrl.rngptr = virt_to_bus(rrpriv->evt_ring);
+
+	rrpriv->info->cmd_ctrl.entry_size = sizeof(struct cmd);
+	rrpriv->info->cmd_ctrl.entries = CMD_RING_ENTRIES;
+	rrpriv->info->cmd_ctrl.mode = 0;
+	rrpriv->info->cmd_ctrl.pi = 15;
+
+	for (i = 0; i < CMD_RING_ENTRIES; i++) {
+		regs->CmdRing[i] = 0;
+	}
+
+	for (i = 0; i < TX_RING_ENTRIES; i++) {
+		rrpriv->tx_ring[i].size = 0;
+		rrpriv->tx_ring[i].addr = 0;
+		rrpriv->tx_skbuff[i] = 0;
+	}
+
+	rrpriv->info->tx_ctrl.entry_size = sizeof(struct tx_desc);
+	rrpriv->info->tx_ctrl.entries = TX_RING_ENTRIES;
+	rrpriv->info->tx_ctrl.mode = 0;
+	rrpriv->info->tx_ctrl.pi = 0;
+	rrpriv->info->tx_ctrl.rngptr = virt_to_bus(rrpriv->tx_ring);
+
+	/*
+	 * Set dirty_tx before we start receiving interrupts, otherwise
+	 * the interrupt handler might think it is supposed to process
+	 * tx ints before we are up and running, which may cause a null
+	 * pointer access in the int handler.
+	 */
+	rrpriv->tx_full = 0;
+	rrpriv->cur_rx = 0;
+	rrpriv->dirty_rx = rrpriv->dirty_tx = 0;
+
+	rr_reset(dev);
+
+	regs->IntrTmr = 0x60;
+	regs->WriteDmaThresh = 0x80 | 0x1f;
+	regs->ReadDmaThresh = 0x80 | 0x1f;
+
+	rrpriv->fw_running = 0;
+
+	hostctrl &= ~(HALT_NIC | INVALID_INST_B | PARITY_ERR);
+	regs->HostCtrl = hostctrl;
+
+	spin_unlock_irqrestore(&rrpriv->lock, flags);
+
+	udelay(1000);
+
+	/*
+	 * Now start the FirmWare.
+	 */
+	cmd.code = C_START_FW;
+	cmd.ring = 0;
+	cmd.index = 0;
+
+	rr_issue_cmd(rrpriv, &cmd);
+
+	/*
+	 * Give the FirmWare time to chew on the `get running' command.
+	 */
+	myjif = jiffies + 5 * HZ;
+	while ((jiffies < myjif) && !rrpriv->fw_running);
+
+	for (i = 0; i < RX_RING_ENTRIES; i++) {
+		struct sk_buff *skb;
+
+		rrpriv->rx_ring[i].mode = 0;
+		skb = alloc_skb(dev->mtu + HIPPI_HLEN, GFP_ATOMIC);
+		rrpriv->rx_skbuff[i] = skb;
+		/*
+		 * Sanity test to see if we conflict with the DMA
+		 * limitations of the Roadrunner.
+		 */
+		if ((((unsigned long)skb->data) & 0xfff) > ~65320)
+			printk("skb alloc error\n");
+
+#if (BITS_PER_LONG == 32)
+		rrpriv->rx_ring[i].zero = 0;
+#endif
+		rrpriv->rx_ring[i].addr = virt_to_bus(skb->data);
+		rrpriv->rx_ring[i].size = dev->mtu + HIPPI_HLEN;
+	}
+
+	rrpriv->rx_ctrl[4].entry_size = sizeof(struct rx_desc);
+	rrpriv->rx_ctrl[4].entries = RX_RING_ENTRIES;
+	rrpriv->rx_ctrl[4].mode = 8;
+	rrpriv->rx_ctrl[4].pi = 0;
+	rrpriv->rx_ctrl[4].rngptr = virt_to_bus(rrpriv->rx_ring);
+
+	cmd.code = C_NEW_RNG;
+	cmd.ring = 4;
+	cmd.index = 0;
+	rr_issue_cmd(rrpriv, &cmd);
+
+#if 0
+{
+	u32 tmp;
+	tmp = regs->ExtIo;
+	regs->ExtIo = 0x80;
+	
+	i = jiffies + 1 * HZ;
+	while (jiffies < i);
+	regs->ExtIo = tmp;
+}
+#endif
+	dev->tbusy = 0;
+	dev->interrupt = 0;
+	dev->start = 1;
+	return 0;
+}
+
+
+/*
+ * All events are considered to be slow (RX/TX ints do not generate
+ * events) and are handled here, outside the main interrupt handler,
+ * to reduce the size of the handler.
+ */
+static u32 rr_handle_event(struct device *dev, u32 prodidx)
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+	u32 tmp, eidx;
+#if 0
+	short i;
+#endif
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+	eidx = rrpriv->info->evt_ctrl.pi;
+
+	while (prodidx != eidx){
+		switch (rrpriv->evt_ring[eidx].code){
+		case E_NIC_UP:
+			tmp = regs->FwRev;
+			printk("%s: Firmware revision %i.%i.%i up and running\n",
+			       dev->name, (tmp >> 16), ((tmp >> 8) & 0xff),
+			       (tmp & 0xff));
+			rrpriv->fw_running = 1;
+			break;
+		case E_LINK_ON:
+			printk("%s: Optical link ON\n", dev->name);
+			break;
+		case E_LINK_OFF:
+			printk("%s: Optical link OFF\n", dev->name);
+			break;
+		case E_RX_IDLE:
+			printk("%s: RX data not moving\n", dev->name);
+			break;
+		case E_WATCHDOG:
+			printk("%s: The watchdog is here to see us\n",
+			       dev->name);
+			break;
+		/*
+		 * TX events.
+		 */
+		case E_CON_REJ:
+			printk("%s: Connection rejected\n", dev->name);
+			rrpriv->stats.tx_aborted_errors++;
+			break;
+		case E_CON_TMOUT:
+			printk("%s: Connection timeout\n", dev->name);
+			break;
+		case E_DISC_ERR:
+			printk("%s: HIPPI disconnect error\n", dev->name);
+			rrpriv->stats.tx_aborted_errors++;
+			break;
+		case E_TX_IDLE:
+			printk("%s: Transmitter idle\n", dev->name);
+			break;
+		case E_TX_LINK_DROP:
+			printk("%s: Link lost during transmit\n", dev->name);
+			rrpriv->stats.tx_aborted_errors++;
+			break;
+		/*
+		 * RX events.
+		 */
+		case E_VAL_RNG:		/* Should be ignored */
+#if (DEBUG > 2)
+			printk("%s: RX ring valid event\n", dev->name);
+#endif
+			regs->IpRxPi = RX_RING_ENTRIES - 1;
+			break;
+		case E_INV_RNG:
+			printk("%s: RX ring invalid event\n", dev->name);
+			break;
+		case E_RX_RNG_OUT:
+			printk("%s: Receive ring full\n", dev->name);
+			break;
+
+		case E_RX_PAR_ERR:
+			printk("%s: Receive parity error.\n", dev->name);
+			break;
+		case E_RX_LLRC_ERR:
+			printk("%s: Receive LLRC error.\n", dev->name);
+			break;
+		case E_PKT_LN_ERR:
+			printk("%s: Receive packet length error.\n",
+			       dev->name);
+			break;
+		default:
+			printk("%s: Unhandled event 0x%02x\n",
+			       dev->name, rrpriv->evt_ring[eidx].code);
+		}
+		eidx = (eidx + 1) % EVT_RING_ENTRIES;
+	}
+
+	rrpriv->info->evt_ctrl.pi = eidx;
+	return eidx;
+}
+
+
+static int rx_int(struct device *dev, u32 rxlimit)
+{
+	struct rr_private *rrpriv = (struct rr_private *)dev->priv;
+	u32 index, pkt_len;
+	struct rr_regs *regs = rrpriv->regs;
+
+	index = rrpriv->cur_rx;
+
+	while(index != rxlimit){
+		pkt_len = rrpriv->rx_ring[index].size;
+#if (DEBUG > 2)
+		printk("index %i, rxlimit %i\n", index, rxlimit);
+		printk("len %x, mode %x\n", pkt_len,
+		       rrpriv->rx_ring[index].mode);
+#endif
+#if 0
+/*
+ * I have never seen this occur
+ */
+		if(!(rrpriv->rx_skbuff[index])){
+			printk("Trying to receive in empty skbuff\n");
+			goto out;
+		}
+#endif
+
+		if (pkt_len > 0){
+			struct sk_buff *skb;
+
+			if (pkt_len < PKT_COPY_THRESHOLD) {
+				skb = alloc_skb(pkt_len, GFP_ATOMIC);
+				if (skb == NULL){
+					printk("%s: Out of memory deferring packet\n", dev->name);
+					rrpriv->stats.rx_dropped++;
+					goto defer;
+				}else
+					memcpy(skb_put(skb, pkt_len),
+					       rrpriv->rx_skbuff[index]->data,
+					       pkt_len);
+			}else{
+				struct sk_buff *newskb;
+
+				newskb = alloc_skb(dev->mtu + HIPPI_HLEN, GFP_ATOMIC);
+				if (newskb){
+					skb = rrpriv->rx_skbuff[index];
+					skb_put(skb, pkt_len);
+					rrpriv->rx_skbuff[index] = newskb;
+					rrpriv->rx_ring[index].addr = virt_to_bus(newskb->data);
+				}else{
+					printk("%s: Out of memory, deferring packet\n", dev->name);
+					rrpriv->stats.rx_dropped++;
+					goto defer;
+				}
+			}
+			skb->dev = dev;
+			skb->protocol = hippi_type_trans(skb, dev);
+
+			netif_rx(skb);		/* send it up */
+
+			rrpriv->stats.rx_packets++;
+			rrpriv->stats.rx_bytes += skb->len;
+		}
+	defer:
+		rrpriv->rx_ring[index].mode = 0;
+		rrpriv->rx_ring[index].size = dev->mtu + HIPPI_HLEN;
+
+		if ((index & 7) == 7)
+			regs->IpRxPi = index;
+
+		index = (index + 1) % RX_RING_ENTRIES;
+	}
+
+	rrpriv->cur_rx = index;
+	return index;
+}
+
+
+static void rr_interrupt(int irq, void *dev_id, struct pt_regs *ptregs)
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+	struct device *dev = (struct device *)dev_id;
+	u32 prodidx, eidx, txcsmr, rxlimit, txcon;
+	unsigned long flags;
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+
+	if (!(regs->HostCtrl & RR_INT)){
+#if 0
+		/* These are harmless */
+		printk("%s: spurious interrupt detected\n", dev->name);
+#endif
+		return;
+	}
+
+	if (test_and_set_bit(0, (void*)&dev->interrupt) != 0) {
+		printk("%s: Re-entering the interrupt handler.\n", dev->name);
+		return;
+	}
+
+	spin_lock_irqsave(&rrpriv->lock, flags);
+
+	prodidx = regs->EvtPrd;
+	txcsmr = (prodidx >> 8) & 0xff;
+	rxlimit = (prodidx >> 16) & 0xff;
+	prodidx &= 0xff;
+
+#if (DEBUG > 2)
+	printk("%s: interrupt, prodidx = %i, eidx = %i\n", dev->name,
+	       prodidx, rrpriv->info->evt_ctrl.pi);
+#endif
+
+	txcon = rrpriv->dirty_tx;
+	if (txcsmr != txcon) {
+		do {
+			rrpriv->stats.tx_packets++;
+			rrpriv->stats.tx_bytes +=rrpriv->tx_skbuff[txcon]->len;
+			dev_kfree_skb(rrpriv->tx_skbuff[txcon]);
+
+			rrpriv->tx_skbuff[txcon] = NULL;
+			rrpriv->tx_ring[txcon].size = 0;
+			rrpriv->tx_ring[txcon].addr = 0;
+			rrpriv->tx_ring[txcon].mode = 0;
+
+			txcon = (txcon + 1) % TX_RING_ENTRIES;
+		} while (txcsmr != txcon);
+
+		rrpriv->dirty_tx = txcon;
+		if (rrpriv->tx_full && dev->tbusy &&
+		    (((rrpriv->info->tx_ctrl.pi + 1) % TX_RING_ENTRIES)
+		     != rrpriv->dirty_tx)){
+			rrpriv->tx_full = 0;
+			dev->tbusy = 0;
+			mark_bh(NET_BH);
+		}
+	}
+
+	rx_int(dev, rxlimit);
+
+	eidx = rrpriv->info->evt_ctrl.pi;
+
+	if (prodidx != eidx)
+		eidx = rr_handle_event(dev, prodidx);
+
+	eidx |= ((txcsmr << 8) | (rxlimit << 16));
+	regs->EvtCon = eidx;
+
+	spin_unlock_irqrestore(&rrpriv->lock, flags);
+
+	dev->interrupt = 0;
+}
+
+
+static int rr_open(struct device *dev)
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+
+#if 0
+	regs->HostCtrl |= (HALT_NIC | RR_CLEAR_INT);
+#endif
+
+	if (request_irq(dev->irq, rr_interrupt, 0, rrpriv->name, dev))
+	{
+		printk(KERN_WARNING "%s: Requested IRQ %d is busy\n",
+		       dev->name, dev->irq);
+		return -EAGAIN;
+	}
+
+	rrpriv->rx_ctrl = kmalloc(256*sizeof(struct ring_ctrl),
+				  GFP_KERNEL | GFP_DMA);
+	rrpriv->info = kmalloc(sizeof(struct rr_info), GFP_KERNEL | GFP_DMA);
+
+	rr_init1(dev);
+
+	dev->tbusy = 0;
+	dev->interrupt = 0;
+	dev->start = 1;
+
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+
+static void rr_dump(struct device *dev)
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+	u32 index, cons;
+	short i;
+	int len;
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+
+	printk("%s: dumping NIC TX rings\n", dev->name);
+
+	printk("RxPrd %08x, TxPrd %02x, EvtPrd %08x, TxPi %02x, TxCtrlPi %02x\n",
+	       regs->RxPrd, regs->TxPrd, regs->EvtPrd, regs->TxPi,
+	       rrpriv->info->tx_ctrl.pi);
+
+	printk("Error code 0x%x\n", regs->Fail1);
+
+	index = (((regs->EvtPrd >> 8) & 0xff ) - 1) % EVT_RING_ENTRIES;
+	cons = rrpriv->dirty_tx;
+	printk("TX ring index %i, TX consumer %i\n",
+	       index, cons);
+
+	if (rrpriv->tx_skbuff[index]){
+		len = min(0x80, rrpriv->tx_skbuff[index]->len);
+		printk("skbuff for index %i is valid - dumping data (0x%x bytes - DMA len 0x%x)\n", index, len, rrpriv->tx_ring[index].size);
+		for (i = 0; i < len; i++){
+			if (!(i & 7))
+				printk("\n");
+			printk("%02x ", (unsigned char) rrpriv->tx_skbuff[index]->data[i]);
+		}
+		printk("\n");
+	}
+
+	if (rrpriv->tx_skbuff[cons]){
+		len = min(0x80, rrpriv->tx_skbuff[cons]->len);
+		printk("skbuff for cons %i is valid - dumping data (0x%x bytes - skbuff len 0x%x)\n", cons, len, rrpriv->tx_skbuff[cons]->len);
+		printk("mode 0x%x, size 0x%x,\n phys %08x (virt %08x), skbuff-addr %08x, truesize 0x%x\n",
+		       rrpriv->tx_ring[cons].mode,
+		       rrpriv->tx_ring[cons].size,
+		       rrpriv->tx_ring[cons].addr,
+		       (unsigned int)bus_to_virt(rrpriv->tx_ring[cons].addr),
+		       (unsigned int)rrpriv->tx_skbuff[cons]->data,
+		       (unsigned int)rrpriv->tx_skbuff[cons]->truesize);
+		for (i = 0; i < len; i++){
+			if (!(i & 7))
+				printk("\n");
+			printk("%02x ", (unsigned char)rrpriv->tx_ring[cons].size);
+		}
+		printk("\n");
+	}
+
+	printk("dumping TX ring info:\n");
+	for (i = 0; i < TX_RING_ENTRIES; i++)
+		printk("mode 0x%x, size 0x%x, phys-addr %08x\n",
+		       rrpriv->tx_ring[i].mode,
+		       rrpriv->tx_ring[i].size,
+		       rrpriv->tx_ring[i].addr);
+
+}
+
+
+static int rr_close(struct device *dev)
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+	u32 tmp;
+	short i;
+
+	dev->start = 0;
+	set_bit(0, (void*)&dev->tbusy);
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+
+	tmp = regs->HostCtrl;
+	if (tmp & NIC_HALTED){
+		printk("%s: NIC already halted\n", dev->name);
+		rr_dump(dev);
+	}else
+		tmp |= HALT_NIC;
+	regs->HostCtrl = tmp;
+
+	/*
+	 * Lock to make sure we are not cleaning up while another CPU
+	 * handling interrupts.
+	 */
+	spin_lock(&rrpriv->lock);
+
+	regs->TxPi = 0;
+	regs->IpRxPi = 0;
+
+	regs->EvtCon = 0;
+	regs->EvtPrd = 0;
+
+	for (i = 0; i < CMD_RING_ENTRIES; i++)
+		regs->CmdRing[i] = 0;
+
+	rrpriv->info->tx_ctrl.entries = 0;
+	rrpriv->info->cmd_ctrl.pi = 0;
+	rrpriv->info->evt_ctrl.pi = 0;
+	rrpriv->rx_ctrl[4].entries = 0;
+
+	for (i = 0; i < TX_RING_ENTRIES; i++) {
+		if (rrpriv->tx_skbuff[i]) {
+			rrpriv->tx_ring[i].size = 0;
+			rrpriv->tx_ring[i].addr = 0;
+			dev_kfree_skb(rrpriv->tx_skbuff[i]);
+		}
+	}
+
+	for (i = 0; i < RX_RING_ENTRIES; i++) {
+		if (rrpriv->rx_skbuff[i]) {
+			rrpriv->rx_ring[i].size = 0;
+			rrpriv->rx_ring[i].addr = 0;
+			dev_kfree_skb(rrpriv->rx_skbuff[i]);
+		}
+	}
+
+	kfree(rrpriv->rx_ctrl);
+	kfree(rrpriv->info);
+
+	free_irq(dev->irq, dev);
+	spin_unlock(&rrpriv->lock);
+
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+
+static int rr_start_xmit(struct sk_buff *skb, struct device *dev)
+{
+	struct rr_private *rrpriv = (struct rr_private *)dev->priv;
+	struct rr_regs *regs = rrpriv->regs;
+	struct ring_ctrl *txctrl;
+	unsigned long flags;
+	u32 index, len = skb->len;
+	u32 *ifield;
+	struct sk_buff *new_skb;
+
+	/*
+	 * We probably need to deal with tbusy here to prevent overruns.
+	 */
+
+	if (skb_headroom(skb) < 8){
+		printk("incoming skb too small - reallocating\n");
+		if (!(new_skb = dev_alloc_skb(len + 8))) {
+			dev_kfree_skb(skb);
+			dev->tbusy = 0;
+			return -EBUSY;
+		}
+		skb_reserve(new_skb, 8);
+		skb_put(new_skb, len);
+		memcpy(new_skb->data, skb->data, len);
+		dev_kfree_skb(skb);
+		skb = new_skb;
+	}
+
+	ifield = (u32 *)skb_push(skb, 8);
+
+	ifield[0] = 0;
+	ifield[1] = skb->private.ifield;
+
+	/*
+	 * We don't need the lock before we are actually going to start
+	 * fiddling with the control blocks.
+	 */
+	spin_lock_irqsave(&rrpriv->lock, flags);
+
+	txctrl = &rrpriv->info->tx_ctrl;
+
+	index = txctrl->pi;
+
+	rrpriv->tx_skbuff[index] = skb;
+	rrpriv->tx_ring[index].addr = virt_to_bus(skb->data);
+	rrpriv->tx_ring[index].size = len + 8; /* include IFIELD */
+	rrpriv->tx_ring[index].mode = PACKET_START | PACKET_END;
+	txctrl->pi = (index + 1) % TX_RING_ENTRIES;
+	regs->TxPi = txctrl->pi;
+
+	if (txctrl->pi == rrpriv->dirty_tx){
+		rrpriv->tx_full = 1;
+		set_bit(0, (void*)&dev->tbusy);
+	}
+
+	spin_unlock_irqrestore(&rrpriv->lock, flags);
+
+	dev->trans_start = jiffies;
+	return 0;
+}
+
+
+static struct net_device_stats *rr_get_stats(struct device *dev)
+{
+	struct rr_private *rrpriv;
+
+	rrpriv = (struct rr_private *)dev->priv;
+
+	return(&rrpriv->stats);
+}
+
+
+/*
+ * Read the firmware out of the EEPROM and put it into the SRAM
+ * (or from user space - later)
+ *
+ * This operation requires the NIC to be halted and is performed with
+ * interrupts disabled and with the spinlock hold.
+ */
+static int rr_load_firmware(struct device *dev)
+{
+	struct rr_private *rrpriv;
+	struct rr_regs *regs;
+#if 0
+	unsigned long flags;
+#endif
+	int i, j;
+	u32 localctrl, eptr, sptr, segptr, len, tmp;
+	u32 p2len, p2size, nr_seg, revision, io, sram_size;
+	struct eeprom *hw = NULL;
+
+	rrpriv = (struct rr_private *)dev->priv;
+	regs = rrpriv->regs;
+
+	if (dev->flags & IFF_UP)
+		return -EBUSY;
+
+	if (!(regs->HostCtrl & NIC_HALTED)){
+		printk("%s: Trying to load firmware to a running NIC.\n", 
+		       dev->name);
+		return -EBUSY;
+	}
+
+	localctrl = regs->LocalCtrl;
+	regs->LocalCtrl = 0;
+
+	regs->EvtPrd = 0;
+	regs->RxPrd = 0;
+	regs->TxPrd = 0;
+
+	/*
+	 * First wipe the entire SRAM, otherwise we might run into all
+	 * kinds of trouble ... sigh, this took almost all afternoon
+	 * to track down ;-(
+	 */
+	io = regs->ExtIo;
+	regs->ExtIo = 0;
+	sram_size = read_eeprom_word(rrpriv, (void *)8);
+
+	for (i = 200; i < sram_size / 4; i++){
+		regs->WinBase = i * 4;
+		regs->WinData = 0;
+	}
+	regs->ExtIo = io;
+
+	eptr = read_eeprom_word(rrpriv, &hw->rncd_info.AddrRunCodeSegs);
+	eptr = ((eptr & 0x1fffff) >> 3);
+
+	p2len = read_eeprom_word(rrpriv, (void *)(0x83*4));
+	p2len = (p2len << 2);
+	p2size = read_eeprom_word(rrpriv, (void *)(0x84*4));
+	p2size = ((p2size & 0x1fffff) >> 3);
+
+	if ((eptr < p2size) || (eptr > (p2size + p2len))){
+		printk("%s: eptr is invalid\n", dev->name);
+		goto out;
+	}
+
+	revision = read_eeprom_word(rrpriv, &hw->manf.HeaderFmt);
+
+	if (revision != 1){
+		printk("%s: invalid firmware format (%i)\n",
+		       dev->name, revision);
+		goto out;
+	}
+
+	nr_seg = read_eeprom_word(rrpriv, (void *)eptr);
+	eptr +=4;
+#if (DEBUG > 1)
+	printk("%s: nr_seg %i\n", dev->name, nr_seg);
+#endif
+
+	for (i = 0; i < nr_seg; i++){
+		sptr = read_eeprom_word(rrpriv, (void *)eptr);
+		eptr += 4;
+		len = read_eeprom_word(rrpriv, (void *)eptr);
+		eptr += 4;
+		segptr = read_eeprom_word(rrpriv, (void *)eptr);
+		segptr = ((segptr & 0x1fffff) >> 3);
+		eptr += 4;
+#if (DEBUG > 1)
+		printk("%s: segment %i, sram address %06x, length %04x, segptr %06x\n",
+		       dev->name, i, sptr, len, segptr);
+#endif
+		for (j = 0; j < len; j++){
+			tmp = read_eeprom_word(rrpriv, (void *)segptr);
+			regs->WinBase = sptr;
+			regs->WinData = tmp;
+			segptr += 4;
+			sptr += 4;
+		}
+	}
+
+out:
+	regs->LocalCtrl = localctrl;
+	return 0;
+}
+
+
+static int rr_ioctl(struct device *dev, struct ifreq *rq, int cmd)
+{
+	struct rr_private *rrpriv;
+
+	rrpriv = (struct rr_private *)dev->priv;
+
+	switch(cmd){
+	case SIOCRRPFW:
+		if (!suser())
+			return -EPERM;
+
+		if (rrpriv->fw_running){
+			printk("%s: firmware already running\n", dev->name);
+			return -EPERM;
+		}
+		printk("%s: updating firmware", dev->name);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+
+/*
+ * Local variables:
+ * compile-command: "gcc -D__KERNEL__ -I../../include -Wall -Wstrict-prototypes -O2 -pipe -fomit-frame-pointer -fno-strength-reduce -m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2 -DCPU=686 -c rrunner.c"
+ * End:
+ */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov