patch-2.2.13 linux/drivers/net/sbni.c

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

diff -u --recursive --new-file v2.2.12/linux/drivers/net/sbni.c linux/drivers/net/sbni.c
@@ -0,0 +1,1533 @@
+/*
+ * Driver for Granch SBNI-12 leased line network adapters.
+ * 
+ * Copyright 1997 - 1999, Granch ltd.
+ * Written 1999 by Yaroslav Polyakov (xenon@granch.ru).
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU Public License, incorporated herein by reference.
+ * 
+ *   // Whole developers team:
+ *   //   Yaroslav Polyakov (xenon@granch.ru)
+ *   //      - main developer of this version
+ *   //   Alexey Zverev (zverev@granch.ru)
+ *   //      - previous SBNI driver for linux
+ *   //   Alexey Chirkov (chirkov@granch.ru)
+ *   //      - all the hardware work and consulting
+ *   //   Max Khon (max@iclub.nsu.ru)
+ *   //      - first SBNI driver for linux
+ *   // --------------------------------------------
+ *   // also I thank: 
+ *   //   Max Krasnyansky (max@uznet.net)
+ *   //      - for bug hunting and many ideas
+ *   //   Alan Cox (Alan.Cox@linux.org)
+ *   //	     - for consulting in some hardcore questions
+ *   //   Donald Becker (becker@cesdis.gsfc.nasa.gov)
+ *   //      - for pretty nice skeleton 
+ * 
+ *   More info and useful utilities to work w/ SBNI you can find at 
+ *   http://www.granch.ru.
+ *
+ *  3.0.0 = Initial Revision, Yaroslav Polyakov (24 Feb 1999)
+ *        - added pre-calculation for CRC, fixed bug with "len-2" frames, 
+ *        - removed outbound fragmentation (MTU=1000), written CRC-calculation 
+ *        - on asm, added work with hard_headers and now we have our own cache 
+ *        - for them, optionally supported word-interchange on some chipsets,
+ *        - something else I cant remember ;) 
+ * 
+ *  3.0.1 = just fixed some bugs (14 apr 1999).
+ * 	  - fixed statistical tx bug 
+ *        - fixed wrong creation dates (1998 -> 1999) in driver source code ;)
+ * 	  - fixed source address bug.
+ *        - fixed permanent nirvana bug 
+ * 
+ *  3.1.0 = (Katyusha) (26 apr 1999)
+ *        - Added balancing feature
+ * 
+ *  3.1.1 = (Medea) (5 aug 1999)
+ *        - Fixed mac.raw bug
+ *	  - Thanks to tolix@olviko.ru and 
+ *        - to Barnaul Brewery, producers of my favorite beer "Medea".
+ *
+ *
+ */
+
+
+#undef GOODBUS16
+#define CRCASM
+#define KATYUSHA
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE >=0x020200
+#define v22
+#endif
+
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <linux/fcntl.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/malloc.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <asm/io.h>
+#include <asm/types.h>
+#include <asm/byteorder.h>
+
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/timer.h>
+#include <linux/config.h>	/* for CONFIG_INET. do we need this?*/
+
+#include <net/arp.h>
+
+
+
+#ifdef v22
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#endif
+
+#include "sbni.h"
+
+
+static const char *version = 
+"sbni.c: ver. 3.1.1 Medea 5 Aug 1999 Yaroslav Polyakov (xenon@granch.ru)\n";
+
+int sbni_probe(struct device *dev);
+static int  sbni_probe1(struct device *dev, int ioaddr);
+static int  sbni_open(struct device *dev);
+static int  sbni_start_xmit(struct sk_buff *skb, struct device *dev);
+static void sbni_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+static int  sbni_close(struct device *dev);
+static void sbni_drop_tx_queue(struct device *dev);
+static struct enet_statistics *sbni_get_stats(struct device *dev);
+void card_start(struct device *dev);
+static inline unsigned short sbni_recv(struct device *dev);
+void change_level(struct device *dev);
+static inline void sbni_xmit(struct device *dev);
+static inline void sbni_get_packet(struct device* dev);
+static void sbni_watchdog(unsigned long arg);
+static void set_multicast_list(struct device *dev);
+static int sbni_ioctl(struct device *dev, struct ifreq *ifr, int cmd);
+static int sbni_set_mac_address(struct device *dev, void *addr);
+unsigned long calc_crc(char *mem, int len, unsigned initial);
+void sbni_nirvana(struct device *dev);
+static int sbni_header(struct sk_buff *skb, struct device *dev, unsigned short type,
+		void *daddr, void *saddr, unsigned len);
+
+static int sbni_rebuild_header(struct sk_buff *skb);
+static int sbni_header_cache(struct neighbour *neigh, struct hh_cache *hh);
+
+static inline void sbni_outs(int port, void *data, int len);
+static inline void sbni_ins(int port, void *data, int len);
+
+
+
+#define SIZE_OF_TIMEOUT_RXL_TAB 4
+static u_char timeout_rxl_tab[] = {
+  0x03, 0x05, 0x08, 0x0b
+};
+
+static u_char rxl_tab[] = {
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, 
+  0x0a, 0x0c, 0x0f, 0x16, 0x18, 0x1a, 0x1c, 0x1f
+};
+
+/* A zero-terminated list of I/O addresses to be probed */
+static unsigned int netcard_portlist[] =  { 
+	0x210, 0x2c0, 0x2d0, 0x2f0, 0x220, 0x230, 0x240, 0x250, 
+	0x260, 0x290, 0x2a0, 0x2b0, 0x224, 0x234, 0x244, 0x254, 
+	0x264, 0x294, 0x2a4, 0x2b4, 0};
+
+static unsigned char magic_reply[] = {
+	0x5a,0x06,0x30,0x00,0x00,0x50,0x65,0x44,0x20
+};
+
+static int def_baud = DEF_RATE;
+static int def_rxl = DEF_RXL_DELTA;
+static long def_mac = 0;
+
+
+/*
+ * CRC-32 stuff
+ */
+
+#define CRC32(c,crc) (crc32tab[((size_t)(crc) ^ (c)) & 0xff] ^ (((crc) >> 8) & 0x00FFFFFF))
+/* CRC generator 0xEDB88320 */
+/* CRC remainder 0x2144DF1C */
+/* CRC initial value 0x00000000 */
+#define CRC32_REMAINDER 0x2144DF1C
+#define CRC32_INITIAL 0x00000000
+
+static unsigned long crc32tab[] = {
+	0xD202EF8D,  0xA505DF1B,  0x3C0C8EA1,  0x4B0BBE37,
+	0xD56F2B94,  0xA2681B02,  0x3B614AB8,  0x4C667A2E,
+	0xDCD967BF,  0xABDE5729,  0x32D70693,  0x45D03605,
+	0xDBB4A3A6,  0xACB39330,  0x35BAC28A,  0x42BDF21C,
+	0xCFB5FFE9,  0xB8B2CF7F,  0x21BB9EC5,  0x56BCAE53,
+	0xC8D83BF0,  0xBFDF0B66,  0x26D65ADC,  0x51D16A4A,
+	0xC16E77DB,  0xB669474D,  0x2F6016F7,  0x58672661,
+	0xC603B3C2,  0xB1048354,  0x280DD2EE,  0x5F0AE278,
+	0xE96CCF45,  0x9E6BFFD3,  0x0762AE69,  0x70659EFF,
+	0xEE010B5C,  0x99063BCA,  0x000F6A70,  0x77085AE6,
+	0xE7B74777,  0x90B077E1,  0x09B9265B,  0x7EBE16CD,
+	0xE0DA836E,  0x97DDB3F8,  0x0ED4E242,  0x79D3D2D4,
+	0xF4DBDF21,  0x83DCEFB7,  0x1AD5BE0D,  0x6DD28E9B,
+	0xF3B61B38,  0x84B12BAE,  0x1DB87A14,  0x6ABF4A82,
+	0xFA005713,  0x8D076785,  0x140E363F,  0x630906A9,
+	0xFD6D930A,  0x8A6AA39C,  0x1363F226,  0x6464C2B0,
+	0xA4DEAE1D,  0xD3D99E8B,  0x4AD0CF31,  0x3DD7FFA7,
+	0xA3B36A04,  0xD4B45A92,  0x4DBD0B28,  0x3ABA3BBE,
+	0xAA05262F,  0xDD0216B9,  0x440B4703,  0x330C7795,
+	0xAD68E236,  0xDA6FD2A0,  0x4366831A,  0x3461B38C,
+	0xB969BE79,  0xCE6E8EEF,  0x5767DF55,  0x2060EFC3,
+	0xBE047A60,  0xC9034AF6,  0x500A1B4C,  0x270D2BDA,
+	0xB7B2364B,  0xC0B506DD,  0x59BC5767,  0x2EBB67F1,
+	0xB0DFF252,  0xC7D8C2C4,  0x5ED1937E,  0x29D6A3E8,
+	0x9FB08ED5,  0xE8B7BE43,  0x71BEEFF9,  0x06B9DF6F,
+	0x98DD4ACC,  0xEFDA7A5A,  0x76D32BE0,  0x01D41B76,
+	0x916B06E7,  0xE66C3671,  0x7F6567CB,  0x0862575D,
+	0x9606C2FE,  0xE101F268,  0x7808A3D2,  0x0F0F9344,
+	0x82079EB1,  0xF500AE27,  0x6C09FF9D,  0x1B0ECF0B,
+	0x856A5AA8,  0xF26D6A3E,  0x6B643B84,  0x1C630B12,	
+	0x8CDC1683,  0xFBDB2615,  0x62D277AF,  0x15D54739,
+	0x8BB1D29A,  0xFCB6E20C,  0x65BFB3B6,  0x12B88320,
+	0x3FBA6CAD,  0x48BD5C3B,  0xD1B40D81,  0xA6B33D17,
+	0x38D7A8B4,  0x4FD09822,  0xD6D9C998,  0xA1DEF90E,
+	0x3161E49F,  0x4666D409,  0xDF6F85B3,  0xA868B525,
+	0x360C2086,  0x410B1010,  0xD80241AA,  0xAF05713C,
+	0x220D7CC9,  0x550A4C5F,  0xCC031DE5,  0xBB042D73,
+	0x2560B8D0,  0x52678846,  0xCB6ED9FC,  0xBC69E96A,
+	0x2CD6F4FB,  0x5BD1C46D,  0xC2D895D7,  0xB5DFA541,
+	0x2BBB30E2,  0x5CBC0074,  0xC5B551CE,  0xB2B26158,
+	0x04D44C65,  0x73D37CF3,  0xEADA2D49,  0x9DDD1DDF,
+	0x03B9887C,  0x74BEB8EA,  0xEDB7E950,  0x9AB0D9C6,
+	0x0A0FC457,  0x7D08F4C1,  0xE401A57B,  0x930695ED,
+	0x0D62004E,  0x7A6530D8,  0xE36C6162,  0x946B51F4,
+	0x19635C01,  0x6E646C97,  0xF76D3D2D,  0x806A0DBB,
+	0x1E0E9818,  0x6909A88E,  0xF000F934,  0x8707C9A2,
+	0x17B8D433,  0x60BFE4A5,  0xF9B6B51F,  0x8EB18589,
+	0x10D5102A,  0x67D220BC,  0xFEDB7106,  0x89DC4190,
+	0x49662D3D,  0x3E611DAB,  0xA7684C11,  0xD06F7C87,
+	0x4E0BE924,  0x390CD9B2,  0xA0058808,  0xD702B89E,
+	0x47BDA50F,  0x30BA9599,  0xA9B3C423,  0xDEB4F4B5,
+	0x40D06116,  0x37D75180,  0xAEDE003A,  0xD9D930AC,
+	0x54D13D59,  0x23D60DCF,  0xBADF5C75,  0xCDD86CE3,
+	0x53BCF940,  0x24BBC9D6,  0xBDB2986C,  0xCAB5A8FA,
+	0x5A0AB56B,  0x2D0D85FD,  0xB404D447,  0xC303E4D1,
+	0x5D677172,  0x2A6041E4,  0xB369105E,  0xC46E20C8,
+	0x72080DF5,  0x050F3D63,  0x9C066CD9,  0xEB015C4F,
+	0x7565C9EC,  0x0262F97A,  0x9B6BA8C0,  0xEC6C9856,
+	0x7CD385C7,  0x0BD4B551,  0x92DDE4EB,  0xE5DAD47D,
+	0x7BBE41DE,  0x0CB97148,  0x95B020F2,  0xE2B71064,
+	0x6FBF1D91,  0x18B82D07,  0x81B17CBD,  0xF6B64C2B,
+	0x68D2D988,  0x1FD5E91E,  0x86DCB8A4,  0xF1DB8832,
+	0x616495A3,  0x1663A535,  0x8F6AF48F,  0xF86DC419,
+	0x660951BA,  0x110E612C,  0x88073096,  0xFF000000
+};
+
+static inline void sbni_outs(int port, void *data, int len)
+{
+#ifdef GOODBUS16
+	outsw(port,data,len/2);
+	if(len & 1)
+		outb(((char*)data)[len - 1],port);
+#else
+	outsb(port,data,len);
+#endif
+}
+
+static inline void sbni_ins(int port, void *data, int len)
+{
+#ifdef GOODBUS16
+	insw(port,data,len/2);
+	if(len & 1)
+		((char*)data)[len - 1] = inb(port);
+#else
+	insb(port,data,len);
+#endif
+}
+
+
+static int sbni_header(struct sk_buff *skb, struct device *dev, unsigned short type,
+	   void *daddr, void *saddr, unsigned len)
+{
+	struct sbni_hard_header *hh = (struct sbni_hard_header *) 
+			skb_push(skb, sizeof(struct sbni_hard_header));
+  
+  
+	if(type!=ETH_P_802_3) 
+		hh->h_proto = htons(type);
+	else
+		hh->h_proto = htons(len);
+  
+	if(saddr)
+		memcpy(hh->h_source,saddr,dev->addr_len);
+	else
+		memcpy(hh->h_source,dev->dev_addr,dev->addr_len);
+
+	if(daddr)
+	{
+		memcpy(hh->h_dest,daddr,dev->addr_len);
+		return dev->hard_header_len;
+	} 
+	return -dev->hard_header_len;
+}
+
+
+int sbni_header_cache(struct neighbour *neigh, struct hh_cache *hh)
+{
+	unsigned short type = hh->hh_type;
+	struct sbni_hard_header *sbni = (struct sbni_hard_header*)
+						(((u8*)hh->hh_data) - 8);
+	struct device *dev = neigh->dev;
+  
+  
+	if (type == __constant_htons(ETH_P_802_3))
+		return -1;
+  
+	sbni->h_proto = type;
+	memcpy(sbni->h_source, dev->dev_addr, dev->addr_len);
+	memcpy(sbni->h_dest, neigh->ha, dev->addr_len);
+	return 0;
+}
+
+static int sbni_rebuild_header(struct sk_buff *skb)
+{
+	struct sbni_hard_header *hh = (struct sbni_hard_header *)skb;
+	/*
+	 *	Only ARP/IP is currently supported
+	 */
+
+	/*
+	 *	Try to get ARP to resolve the header.
+	 */
+  
+#ifdef CONFIG_INET
+  	return arp_find((unsigned char*)hh->h_dest, skb)? 1 : 0;  
+#else
+	return 0;	
+#endif	
+}
+
+static void sbni_header_cache_update(struct hh_cache *hh, struct device *dev, unsigned char * haddr)
+{
+	memcpy(((u8*)hh->hh_data) + 2, haddr, dev->addr_len);
+}
+
+
+
+#ifdef HAVE_DEVLIST
+struct netdev_entry sbni_drv = {
+	"sbni", sbni_probe1, SBNI_IO_EXTENT, netcard_portlist 
+};
+
+#else
+
+int __init sbni_probe(struct device *dev)
+{
+	int i;
+	int base_addr = dev ? dev->base_addr : 0;
+	
+	DP( printk("%s: sbni_probe\n", dev->name); )
+
+	if(base_addr > 0x1ff)	/* Check a single specified location. */
+		return sbni_probe1(dev, base_addr);
+	else if(base_addr != 0)	/* Don't probe at all. */
+		return ENXIO;
+	for(i = 0; (base_addr = netcard_portlist[i]); i++)
+	{ 
+		if(!check_region(base_addr, SBNI_IO_EXTENT) && base_addr != 1)
+		{
+			/* Lock this address, or later we'll try it again */
+			netcard_portlist[i] = 1;
+			if(sbni_probe1(dev, base_addr) == 0)
+				return 0;
+		}
+	}
+	return ENODEV;
+}
+
+#endif /* have devlist*/
+
+/*
+ *	The actual probe. 
+ */
+
+/*
+	Valid combinations in CSR0 (for probing):
+
+	VALID_DECODER	0000,0011,1011,1010
+
+				    	; 0   ; -
+				TR_REQ	; 1   ; +
+			TR_RDY	    	; 2   ; -
+			TR_RDY	TR_REQ	; 3   ; +
+		BU_EMP		    	; 4   ; +
+		BU_EMP	     	TR_REQ	; 5   ; +
+		BU_EMP	TR_RDY	    	; 6   ; -
+		BU_EMP	TR_RDY	TR_REQ	; 7   ; +
+	RC_RDY 		     		; 8   ; +
+	RC_RDY			TR_REQ	; 9   ; +
+	RC_RDY		TR_RDY		; 10  ; -
+	RC_RDY		TR_RDY	TR_REQ	; 11  ; -
+	RC_RDY	BU_EMP			; 12  ; -
+	RC_RDY	BU_EMP		TR_REQ	; 13  ; -
+	RC_RDY	BU_EMP	TR_RDY		; 14  ; -
+	RC_RDY	BU_EMP	TR_RDY	TR_REQ	; 15  ; -
+*/
+#define VALID_DECODER (2 + 8 + 0x10 + 0x20 + 0x80 + 0x100 + 0x200)
+
+static int __init sbni_probe1(struct device *dev, int ioaddr)
+
+{
+	int autoirq = 0;
+	int bad_card = 0;
+	unsigned char csr0;
+	struct net_local* lp;
+	static int version_printed = 0;
+
+	DP( printk("%s: sbni_probe1 ioaddr=%d\n", dev->name, ioaddr); )
+       
+	if(check_region(ioaddr, SBNI_IO_EXTENT) < 0)
+		return -ENODEV;
+	if(version_printed++ == 0)
+		printk(version);
+     
+	/* check for valid combination in CSR0 */
+	csr0 = inb(ioaddr + CSR0);
+	if(csr0 == 0xff || csr0 == 0)
+		bad_card = 1;
+	else 
+	{
+		csr0 &= ~EN_INT;
+		if(csr0 & BU_EMP)
+			csr0 |= EN_INT;
+		if((VALID_DECODER & (1 << (csr0 >> 4))) == 0)
+			bad_card = 1;
+	}
+
+	if(bad_card)
+		return ENODEV;
+	else
+		outb(0, ioaddr + CSR0); 
+	if(dev->irq < 2)
+	{
+		DP( printk("%s: autoprobing\n", dev->name); );
+		autoirq_setup(5);
+		outb(EN_INT | TR_REQ, ioaddr + CSR0);
+		outb(PR_RES, ioaddr + CSR1);
+		autoirq = autoirq_report(5);
+
+		if(autoirq == 0)
+		{
+			printk("sbni probe at %#x failed to detect IRQ line\n", ioaddr);
+			return EAGAIN;
+		}
+	}
+	/* clear FIFO buffer */
+	outb(0, ioaddr + CSR0);
+   
+	if(autoirq)
+		dev->irq = autoirq;
+
+	{
+   		int irqval=request_irq(dev->irq, sbni_interrupt, 0, dev->name, dev);
+		if (irqval) 
+		{
+			printk (" unable to get IRQ %d (irqval=%d).\n", dev->irq, irqval);
+			return EAGAIN;
+		}
+	}
+     
+	/* 
+	 *	Initialize the device structure. 
+	 */
+
+	dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
+	if(dev->priv == NULL)
+	{
+		DP( printk("%s: cannot allocate memory\n", dev->name); )
+		return -ENOMEM;
+	}
+   
+	memset(dev->priv, 0, sizeof(struct net_local));
+	dev->base_addr = ioaddr;
+	request_region(ioaddr, SBNI_IO_EXTENT, "sbni");
+
+	/* 
+	 * generate Ethernet address (0x00ff01xxxxxx)
+	 */
+
+	*(u16*)dev->dev_addr = htons(0x00ff);
+	*(u32*)(dev->dev_addr+2) = htonl(((def_mac ? def_mac : (u32) dev->priv) & 0x00ffffff) | 0x01000000);
+   
+	lp = dev->priv;
+	if(def_rxl < 0)
+	{
+		/* autodetect receive level */
+		lp->rxl_curr = 0xf;
+		lp->rxl_delta = -1;
+	} else {
+		/* fixed receive level */
+		lp->rxl_curr = def_rxl & 0xf;
+		lp->rxl_delta = 0;
+	}
+	lp->csr1.rxl = rxl_tab[lp->rxl_curr];
+	lp->csr1.rate = def_baud & 3;
+	lp->frame_len = DEF_FRAME_LEN;
+	printk("%s: sbni adapter at %#lx, using %sIRQ %d, MAC: 00:ff:01:%x:%x:%x\n", 
+		dev->name, dev->base_addr, autoirq ? "auto":"assigned ", dev->irq,
+	       *(unsigned char*)(dev->dev_addr+3),
+	       *(unsigned char*)(dev->dev_addr+4),
+	       *(unsigned char*)(dev->dev_addr+5)
+	);
+
+	printk("%s: receive level: ", dev->name);
+	if(lp->rxl_delta == 0)
+		printk ("%#1x (fixed)", lp->rxl_curr); 
+	else
+		printk ("autodetect");
+	printk(", baud rate: %u\n", (unsigned)lp->csr1.rate);
+   
+	/*
+	 *	The SBNI-specific entries in the device structure. 
+	 */
+	dev->open = &sbni_open;
+	dev->hard_start_xmit = &sbni_start_xmit;
+	dev->stop = &sbni_close;
+	dev->get_stats = &sbni_get_stats;
+	dev->set_multicast_list = &set_multicast_list;
+	dev->set_mac_address = &sbni_set_mac_address;
+	dev->do_ioctl = &sbni_ioctl;
+   
+	/*
+	 *	Setup the generic properties 
+	 */
+
+	ether_setup(dev);
+   
+	dev->hard_header = sbni_header;
+	dev->hard_header_len = sizeof(struct sbni_hard_header);
+	dev->rebuild_header=sbni_rebuild_header;
+	dev->mtu = DEF_FRAME_LEN;
+
+	dev->hard_header_cache = sbni_header_cache;
+	dev->header_cache_update = sbni_header_cache_update;
+  
+	lp->m=dev;
+	lp->me=dev;
+	lp->next_lp=NULL;
+  
+	return 0;
+}
+
+/*
+ *	Open/initialize the board. 
+ */
+
+static int sbni_open(struct device *dev)
+{
+	struct net_local* lp = (struct net_local*)dev->priv;
+	struct timer_list* watchdog = &lp->watchdog;
+   
+      
+	DP( printk("%s: sbni_open\n", dev->name); )
+     
+	cli();
+	lp->currframe = NULL;
+   
+	card_start(dev);
+	dev->start = 1;
+	/* set timer  watchdog */
+	init_timer(watchdog);
+	watchdog->expires = jiffies + SBNI_TIMEOUT;
+	watchdog->data = (unsigned long)dev;
+	watchdog->function = sbni_watchdog;
+	add_timer(watchdog);
+	DP( printk("%s: sbni timer watchdog initialized\n", dev->name); );
+   
+	sti();
+   
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int sbni_close(struct device *dev)
+{
+	int ioaddr = dev->base_addr;
+	struct net_local* lp = (struct net_local*) dev->priv;
+	struct timer_list* watchdog = &lp->watchdog;
+
+   
+	DP( printk("%s: sbni_close\n", dev->name); )
+
+	cli();
+   
+	sbni_drop_tx_queue(dev);	
+   
+	dev->tbusy = 1;
+	dev->start = 0;
+
+	del_timer(watchdog);
+
+	outb(0, ioaddr + CSR0);
+	sti();
+
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static int sbni_start_xmit(struct sk_buff *skb, struct device *dev)
+{
+	struct net_local *lp = (struct net_local*)dev->priv;
+	struct sbni_hard_header *hh=(struct sbni_hard_header *)skb->data;
+  
+#ifdef KATYUSHA   
+	struct net_local *nl;
+	int stop;
+#endif
+  
+	DP( printk("%s: sbni_start_xmit In \n", dev->name); );
+  
+  
+	if(lp->me != dev)
+		panic("sbni: lp->me != dev !!!\nMail to developer (xenon@granch.ru) if you noticed this error\n");
+  
+	if(dev->interrupt)
+	{
+		DP( printk("sbni_xmit_start: interrupt\n"); )
+		/* May be unloading, don't stamp on */	
+		return 1;	/* the packet buffer this time      */
+	}
+  
+	hh->number = 1;
+	hh->reserv = 0;
+  
+	hh->packetlen =  (skb->len - sizeof (unsigned short) - 
+			(sizeof(struct sbni_hard_header) - SBNI_HH_SZ)) 
+			| PACKET_SEND_OK | PACKET_FIRST_FRAME;
+  
+	/* we should use hairy method to calculate crc because of extra bytes are 
+	  livin between hard header and data*/
+	hh->crc = calc_crc((void*)&hh->packetlen, SBNI_HH_SZ - sizeof(unsigned), CRC32_INITIAL);
+	hh->crc = calc_crc(skb->data + sizeof(struct sbni_hard_header),
+		       skb->len - sizeof(struct sbni_hard_header),
+		       hh->crc);
+  
+#ifdef KATYUSHA
+	/* looking for first idle device */
+	for (stop=0,nl=lp; nl && !stop; nl=nl->next_lp)
+	{
+		if((!nl->currframe) && (nl->carrier)) /* if idle */
+		{
+			skb->dev = lp->me;
+			nl->currframe = skb;
+			/* set request for transmit */
+			outb(inb(nl->me->base_addr + CSR0) | TR_REQ, 
+				nl->me->base_addr + CSR0);
+			stop=1;
+		}
+	}
+  
+	if(!stop) /* we havent found any idle.*/
+	{
+		skb_queue_tail(&lp->queue,skb);
+		outb(inb(dev->base_addr + CSR0) | TR_REQ, dev->base_addr + CSR0);
+      
+	}		
+#else 
+	if (lp->currframe || 1)
+	{
+		skb_queue_tail(&lp->queue,skb);
+		  
+	}
+	else
+	{
+		lp->currframe = skb;
+	}
+	/* set request for transmit */
+	outb(inb(dev->base_addr + CSR0) | TR_REQ, dev->base_addr + CSR0);
+#endif
+	return 0;
+}
+
+void card_start(struct device *dev)
+{
+	struct net_local *lp = (struct net_local*)dev->priv;
+   
+	DP( printk("%s: card_start\n",dev->name); )
+	lp->wait_frame_number = 0;
+	lp->inppos = lp->outpos = 0;
+	lp->eth_trans_buffer_len = 0;
+	lp->tr_err = TR_ERROR_COUNT;
+	lp->last_receive_OK = FALSE;
+	lp->tr_resend = FALSE;
+	lp->timer_ticks = CHANGE_LEVEL_START_TICKS;
+	lp->timeout_rxl = 0;
+
+	lp->waitack=0;
+	skb_queue_head_init(&lp->queue);
+	sbni_drop_tx_queue(dev);
+	dev->tbusy = 0;
+   
+	dev->interrupt = 0;
+	/* Reset the card and set start parameters */
+	outb(PR_RES | *(char*)&lp->csr1, dev->base_addr + CSR1);
+	outb(EN_INT, dev->base_addr + CSR0);
+}
+
+void sbni_nirvana(struct device *dev)
+{
+	sbni_outs(dev->base_addr+DAT,magic_reply,9);
+}
+
+static inline unsigned short sbni_recv(struct device *dev)
+{
+	struct net_local *lp = (struct net_local*)dev->priv;
+	unsigned long crc;
+	unsigned short packetlen = 0;
+	unsigned short packetinf, packetfirst, receiveframeresend;
+	unsigned char current_frame;
+	unsigned int i, j;
+	unsigned char delme,rcv_res=RCV_WR;
+  
+	lp->in_stats.all_rx_number++;
+  
+	if((delme=inb(dev->base_addr + DAT)) == SBNI_SIG)
+	{
+		crc = CRC32_INITIAL;
+		*(((unsigned char *)&packetlen) + 0) = inb(dev->base_addr + DAT);
+		crc = CRC32(*(((unsigned char *)&packetlen) + 0), crc);
+		*(((unsigned char *)&packetlen) + 1) = inb(dev->base_addr + DAT);
+		crc = CRC32(*(((unsigned char *)&packetlen) + 1), crc);
+		packetinf = packetlen & PACKET_INF_MASK;
+		packetfirst = packetlen & PACKET_FIRST_FRAME;
+		receiveframeresend = packetlen & RECEIVE_FRAME_RESEND;
+		packetlen = packetlen & PACKET_LEN_MASK;
+    
+    
+		if((packetlen <= SB_MAX_BUFFER_ARRAY - 3) && (packetlen >= 6))
+		{
+			/* read frame number */
+			current_frame = inb(dev->base_addr + DAT);
+			crc = CRC32(current_frame, crc);
+			/* read HandShake counter */
+			lp->HSCounter = inb(dev->base_addr + DAT);
+			crc = CRC32(lp->HSCounter, crc);
+			packetlen -= 2;
+      
+			sbni_ins(dev->base_addr + DAT, lp->eth_rcv_buffer + lp->inppos, packetlen);
+      
+			for(i = lp->inppos; i < (packetlen + lp->inppos); i++)
+			{
+				crc = CRC32(lp->eth_rcv_buffer[i], crc);
+			}
+      
+			if(crc == CRC32_REMAINDER)
+			{
+				if(packetlen > 4) 
+					rcv_res=RCV_OK;
+				else if(packetlen == 4) 
+					rcv_res=RCV_NO;
+      		
+				if(lp->waitack && packetinf == PACKET_RESEND)
+					lp->in_stats.resend_tx_number++;
+	
+	
+				switch(packetinf)
+				{
+				case PACKET_SEND_OK:
+				{
+					lp->tr_err = TR_ERROR_COUNT;
+					lp->tr_resend = FALSE;
+					/* if(lp->trans_frame_number){ */
+					lp->outpos += lp->realframelen;
+	      
+					/* SendComplete
+					 * not supported
+					 */
+					DP( printk("%s: sbni_recv SendComplete\n",dev->name); );
+					/*
+					 *	We sucessfully sent current packet
+					 */
+	      
+					if(lp->waitack)
+					{
+						dev_kfree_skb(lp->currframe);
+						lp->stats.tx_packets++;
+#ifdef KATYUSHA
+						lp->currframe=skb_dequeue(&(((struct net_local*) (lp->m->priv))->queue));
+#else
+						lp->currframe=skb_dequeue(&lp->queue);
+#endif		       
+						lp->in_stats.all_tx_number++;
+						lp->waitack=0;
+					}
+	      
+					/*
+	      				 * reset output active flags
+					 */
+					dev->tbusy = 0;
+					mark_bh(NET_BH);
+					/*} if */
+				}
+				case PACKET_RESEND:
+				{
+					if(lp->tr_err) /**/
+						lp->tr_err--;
+					if(lp->ok_curr < 0xffffffff)
+						lp->ok_curr++;
+					if(packetlen > 4 && !(lp->last_receive_OK && receiveframeresend))
+					{
+						if(packetfirst)
+						{
+							if(lp->wait_frame_number)
+							{
+								for(i = lp->inppos, j = 0; 
+									i < (lp->inppos + packetlen - 4); 
+									i++, j++)
+								lp->eth_rcv_buffer[j] = lp->eth_rcv_buffer[i];
+							}
+							lp->wait_frame_number = current_frame;
+							lp->inppos = 0;
+						}
+						if(current_frame == lp->wait_frame_number)
+						{
+							lp->inppos += (packetlen - 4);
+							if(lp->wait_frame_number == 1)
+		  					{
+								sbni_get_packet(dev);
+								lp->inppos = 0;
+							}
+							lp->wait_frame_number--;
+						}
+					}
+					lp->last_receive_OK = TRUE;
+					break;
+				}
+				default:
+					break;
+				}
+			}
+			else 
+			{
+				DP(printk("%s: bad CRC32\n",dev->name));
+				change_level(dev);
+			}
+		}
+		else 
+		{
+			DP(printk("%s: bad len\n ",dev->name));
+			change_level(dev);
+			lp->stats.rx_over_errors++;
+		}
+	}
+	else 
+	{
+		DP(printk("%s: bad sig\n",dev->name));
+		change_level(dev);
+	}
+	outb(inb(dev->base_addr + CSR0) ^ CT_ZER, dev->base_addr + CSR0);
+	return (rcv_res);
+}
+
+void change_level(struct device *dev)
+{
+	struct net_local *lp = (struct net_local*)dev->priv;
+
+	lp->in_stats.bad_rx_number++;
+	lp->stats.tx_errors++;
+	if(lp->rxl_delta == 0)
+		return;
+	/* 
+	 * set new rxl_delta value
+	 */
+	if(lp->rxl_curr == 0)
+		lp->rxl_delta = 1;
+	else if(lp->rxl_curr == 0xf)
+		lp->rxl_delta = -1;
+	else if(lp->ok_curr < lp->ok_prev)
+		lp->rxl_delta = -lp->rxl_delta;
+	/*
+	 * set new rxl_curr value
+	 */
+	lp->csr1.rxl = rxl_tab[lp->rxl_curr += lp->rxl_delta];
+	outb(*(char*)&lp->csr1, dev->base_addr + CSR1);
+  
+  
+	/*
+	 * update ok_prev/ok_curr counters
+	 */
+	lp->ok_prev = lp->ok_curr;
+	lp->ok_curr = 0;
+
+	DP( printk("%s: receive error, rxl_curr = %d, rxl_delta = %d\n",\
+		   dev->name,lp->rxl_curr, lp->rxl_delta); )
+     
+}
+
+static inline void sbni_xmit(struct device *dev)
+{
+	struct net_local* lp = (struct net_local *)dev->priv;
+	struct sk_buff *skb;
+ 
+	skb=lp->currframe;
+  
+	DP( printk("%s: sbni_xmit CSR0=%02x\n",dev->name, (unsigned char)inb(dev->base_addr + CSR0)); );
+	  
+	/* push signature*/  
+	outb(SBNI_SIG, dev->base_addr + DAT);
+	
+	/* push frame w/o crc [HAiRY]*/
+	sbni_outs(dev->base_addr + DAT,
+	      &((struct sbni_hard_header *)(skb->data))->packetlen,
+	      SBNI_HH_SZ - sizeof(unsigned)); 
+	
+	sbni_outs(dev->base_addr + DAT,
+	      skb->data + sizeof(struct sbni_hard_header),
+	      skb->len - sizeof(struct sbni_hard_header)); /* успеем еще */
+
+	/* push crc */
+	sbni_outs(dev->base_addr + DAT, skb->data, sizeof(unsigned));
+	
+	lp->waitack=1;
+}
+
+/*
+ *	The typical workload of the driver:
+ *	Handle the ether interface interrupts. 
+ */
+static void sbni_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct device *dev = dev_id;
+	struct net_local* lp;
+	u_char csr0;
+	unsigned short rcv_res = RCV_NO;
+  
+  
+	if(dev == NULL || dev->irq != irq)
+	{
+		printk("sbni: irq %d for unknown device\n", irq);
+		return;
+	}
+   
+	if(dev->interrupt)
+	{
+		printk("%s: Reentering the interrupt driver!\n", dev->name);
+		return;
+	}
+	dev->interrupt = 1;
+   
+	csr0 = inb(dev->base_addr + CSR0);
+	DP( printk("%s: entering interrupt handler, CSR0 = %02x\n", dev->name, csr0); )
+     
+	lp=dev->priv;
+  
+	if(!lp->carrier)
+		lp->carrier=1;
+  
+	/*
+	 * Disable adapter interrupts
+	 */
+	outb((csr0 & ~EN_INT) | TR_REQ, dev->base_addr + CSR0);
+	lp->timer_ticks = CHANGE_LEVEL_START_TICKS;
+	csr0 = inb(dev->base_addr + CSR0);
+   
+	if(csr0 & (TR_RDY | RC_RDY))
+	{
+		if(csr0 & RC_RDY)
+			rcv_res = sbni_recv(dev);
+	   
+		if((lp->currframe) && (rcv_res != RCV_WR))
+			sbni_xmit(dev);
+		else if (rcv_res == RCV_OK)
+			sbni_nirvana(dev);
+       
+		csr0 = inb(dev->base_addr + CSR0);
+		DP( printk("%s: CSR0 = %02x\n",dev->name, (u_int)csr0); );
+	}
+   
+  
+	DP( printk("%s: leaving interrupt handler, CSR0 = %02x\n",dev->name, csr0 | EN_INT); );
+     
+	/* here we should send pong */
+	outb(inb(dev->base_addr+CSR0) & ~TR_REQ, dev->base_addr + CSR0);
+	if(lp->currframe)
+		outb(inb(dev->base_addr+CSR0) | TR_REQ, dev->base_addr + CSR0);
+	else
+		csr0 = inb(dev->base_addr + CSR0);
+  
+	/*
+	 * Enable adapter interrupts
+	 */
+  
+	outb(csr0 | EN_INT, dev->base_addr + CSR0);
+	dev->interrupt = 0;
+}
+
+static struct enet_statistics *sbni_get_stats(struct device *dev)
+{
+	struct net_local *lp = (struct net_local *)dev->priv;
+	return &lp->stats;
+}
+
+static inline void sbni_get_packet(struct device* dev)
+{
+	struct net_local* lp = (struct net_local*)dev->priv;
+	struct sk_buff* skb;
+	unsigned char *rawp;
+    
+   
+     
+	skb = dev_alloc_skb(lp->inppos - ETH_HLEN + sizeof(struct sbni_hard_header));
+   
+	if(skb == NULL)
+	{
+		DP( printk("%s: Memory squeeze, dropping packet.\n", dev->name); )
+		lp->stats.rx_dropped++;
+		return;
+	} else {
+#ifdef KATYUSHA
+		skb->dev = lp->m;
+#else
+		skb->dev = dev;
+#endif      
+		memcpy((unsigned char*)skb_put(skb, lp->inppos + 8)+8,
+			lp->eth_rcv_buffer,
+			lp->inppos);
+      
+      
+		skb->mac.raw = skb->data + 8;
+    
+		if((*(char*)lp->eth_rcv_buffer) & 1)
+		{
+			if(memcmp(lp->eth_rcv_buffer,dev->broadcast, ETH_ALEN)==0)
+				skb->pkt_type=PACKET_BROADCAST;
+			else
+				skb->pkt_type=PACKET_MULTICAST;
+		}
+		else if(dev->flags&(IFF_PROMISC|IFF_ALLMULTI))
+		{
+			if(memcmp(lp->eth_rcv_buffer,dev->dev_addr, ETH_ALEN))
+				skb->pkt_type=PACKET_OTHERHOST;
+		}
+      
+		if( htons(*((unsigned short*)(&lp->eth_rcv_buffer[2*ETH_ALEN]))) >= 1536)
+			skb->protocol =  *((unsigned short*)(&lp->eth_rcv_buffer[2*ETH_ALEN]));
+		else
+		{
+			rawp = (unsigned char*)(&lp->eth_rcv_buffer[2*ETH_ALEN]);
+			if (*(unsigned short *)rawp == 0xFFFF)
+				skb->protocol=htons(ETH_P_802_3);
+			else
+				skb->protocol=htons(ETH_P_802_2);
+		}
+            
+
+		skb_pull(skb,SBNI_HH_SZ);
+   
+		netif_rx(skb);
+		lp->stats.rx_packets++;
+	}
+	return;
+}
+
+static void sbni_watchdog(unsigned long arg)
+{
+	struct device* dev = (struct device*)arg;
+	struct net_local* lp = (struct net_local *)dev->priv;
+	u_char csr0;
+
+
+  
+	DP( printk("%s: watchdog start\n",dev->name); )	
+	/*
+	 * if no pong received and transmission is not in progress
+	 * then assume error
+	 */
+	cli();
+	csr0 = inb(dev->base_addr + CSR0);
+	if(csr0 & (RC_CHK | TR_REQ))
+	{
+		if(lp->timer_ticks)
+		{
+			if(csr0 & (RC_RDY | BU_EMP))
+			{
+				lp->timer_ticks--;
+			}
+		}
+		else 
+		{
+			if(lp->rxl_delta)
+			{
+				lp->ok_prev = lp->ok_curr;
+				lp->ok_curr = 0;
+				lp->rxl_curr = timeout_rxl_tab[lp->timeout_rxl];
+				lp->timeout_rxl++;
+				if(lp->timeout_rxl > SIZE_OF_TIMEOUT_RXL_TAB - 1)
+					lp->timeout_rxl = 0; 
+				lp->csr1.rxl = rxl_tab[lp->rxl_curr];
+				/*
+				 * update ok_prev/ok_curr counters
+				 */
+				lp->ok_prev = lp->ok_curr;
+				lp->ok_curr = 0;
+			}
+			if(lp->tr_err)
+				lp->tr_err--;
+			else 
+			{
+				/* Drop the queue of tx packets */
+				sbni_drop_tx_queue(dev);
+				lp->carrier=0;
+			}
+	     
+			/*
+			 * send pong
+			 */
+
+			csr0 = inb(dev->base_addr + CSR0);
+			outb(csr0 & ~TR_REQ, dev->base_addr + CSR0);
+			outb(*(char*)(&lp->csr1) | PR_RES, dev->base_addr + CSR1);
+			lp->in_stats.timeout_number++;
+		}
+	}
+	sti();
+	outb(csr0 | RC_CHK, dev->base_addr + CSR0);
+	if(dev->start)
+	{
+		struct timer_list* watchdog = &lp->watchdog; 
+		init_timer(watchdog);
+		watchdog->expires = jiffies + SBNI_TIMEOUT;
+		watchdog->data = arg;
+		watchdog->function = sbni_watchdog;
+		add_timer(watchdog);
+	}
+}
+
+static void sbni_drop_tx_queue(struct device *dev)
+{
+	struct net_local* lp = (struct net_local *)dev->priv,*nl;
+	struct sk_buff *tmp;
+   
+	/* first of all, we should try to gift our packets to another interface */
+  
+	nl=(struct net_local *)lp->m->priv;
+	if(nl==lp)
+		nl=lp->next_lp;
+  
+	if(nl)
+	{
+		/* we found device*/
+		if(lp->currframe)
+		{
+			if(!nl->currframe)
+			{
+				nl->currframe=lp->currframe;
+			}
+			else
+			{
+				skb_queue_head(&((struct net_local*)(lp->m->priv))->queue,lp->currframe);
+			}
+		}
+		lp->currframe=NULL;
+
+		if(!nl->currframe)
+			nl->currframe=skb_dequeue(&(((struct net_local*)(lp->m->priv))->queue));
+
+		/* set request for transmit */
+		outb(inb(nl->me->base_addr + CSR0) | TR_REQ,  nl->me->base_addr + CSR0);
+    
+	}
+	else
+	{
+		/* *sigh*, we should forget this packets */
+		nl=lp->m->priv;
+    
+		while((tmp = skb_dequeue(&nl->queue)) != NULL)
+		{
+			dev_kfree_skb(tmp);
+			lp->stats.tx_packets++;
+		}
+    
+		if (lp->currframe)
+		{
+			dev_kfree_skb(lp->currframe);
+			lp->currframe = NULL;
+			lp->stats.tx_packets++;
+		}
+	}
+	lp->waitack=0;
+  	dev->tbusy = 0;
+  
+	mark_bh(NET_BH);
+	DP( printk("%s: queue dropping stoped\n",dev->name); );	
+}
+
+/*
+ * Set or clear the multicast filter for this adaptor.
+ * num_addrs == -1	Promiscuous mode, receive all packets
+ * num_addrs == 0	Normal mode, clear multicast list
+ * num_addrs > 0	Multicast mode, receive normal and MC packets,
+ *			and do best-effort filtering.
+ */
+ 
+static void set_multicast_list(struct device *dev)
+{
+	/*
+	 * always enabled promiscuous mode.
+	*/
+	return;
+}
+
+static int sbni_set_mac_address(struct device *dev, void *addr)
+{	
+	/* struct net_local *lp = (struct net_local *)dev->priv; */
+	struct sockaddr *saddr = addr;
+	
+	if(dev->start)
+	{
+		/* Only possible while card isn't started */
+		return -EBUSY;
+	}
+	memcpy(dev->dev_addr, saddr->sa_data, dev->addr_len);
+	return (0);
+}
+
+static int sbni_ioctl(struct device *dev, struct ifreq *ifr, int cmd)
+{
+	struct net_local* lp = (struct net_local *)dev->priv,*tlp; 
+	struct device *slave;
+	int error = 0;
+	char tmpstr[6];
+  
+  
+	switch(cmd)
+	{
+		case SIOCDEVGETINSTATS:
+		{
+			struct sbni_in_stats *in_stats = (struct sbni_in_stats *)ifr->ifr_data;
+			DP( printk("%s: SIOCDEVGETINSTATS %08x\n",dev->name,(unsigned)in_stats);)
+			if(copy_to_user((void *)in_stats, (void *)(&(lp->in_stats)), sizeof(struct sbni_in_stats)))
+				return -EFAULT;
+			break;
+		}
+		case SIOCDEVRESINSTATS:
+		{
+			DP( printk("%s: SIOCDEVRESINSTATS\n",dev->name); )
+			lp->in_stats.all_rx_number = 0;
+			lp->in_stats.bad_rx_number = 0;
+			lp->in_stats.timeout_number = 0;
+			lp->in_stats.all_tx_number = 0;
+			lp->in_stats.resend_tx_number = 0;
+			break;
+		}
+		case SIOCDEVGHWSTATE:
+		{
+			struct sbni_flags flags;
+			flags.rxl = lp->rxl_curr;
+			flags.rate = lp->csr1.rate;
+			flags.fixed_rxl = (lp->rxl_delta == 0);
+			flags.fixed_rate = 1;
+			ifr->ifr_data = *(caddr_t*)&flags;
+			DP( printk("%s: get flags (0x%02x)\n",dev->name, (unsigned char)ifr->ifr_data); )
+			break;
+		}
+		case SIOCDEVSHWSTATE:
+		{
+			struct sbni_flags flags;
+			DP( printk("%s: SIOCDEVSHWSTATE flags=0x%02x\n",dev->name, (unsigned char)ifr->ifr_data); )
+			/* root only */
+			if(!capable(CAP_NET_ADMIN))
+				return -EPERM;
+			flags = *(struct sbni_flags*)&ifr->ifr_data;
+			if(flags.fixed_rxl)
+			{
+				lp->rxl_delta = 0;
+				lp->rxl_curr = flags.rxl;
+			}
+			else
+			{
+				lp->rxl_delta = DEF_RXL_DELTA;
+				lp->rxl_curr = DEF_RXL;
+			}
+			lp->csr1.rxl = rxl_tab[lp->rxl_curr];
+			if(flags.fixed_rate)
+				lp->csr1.rate = flags.rate;
+			else
+				lp->csr1.rate = DEF_RATE;
+			/*
+			 * Don't be afraid...
+			 */
+			outb(*(char*)(&lp->csr1) | PR_RES, dev->base_addr + CSR1);
+
+			DP( printk("%s: set flags (0x%02x)\n receive level: %u, baud rate: %u\n",\
+				dev->name, (unsigned char)ifr->ifr_data, (unsigned)lp->rxl_curr, (unsigned)lp->csr1.rate); )
+			break;
+		}
+
+		case SIOCDEVENSLAVE:
+			if(!capable(CAP_NET_ADMIN))
+				return -EPERM;
+			if(copy_from_user( tmpstr, ifr->ifr_data, 6))
+				return -EFAULT;
+			slave=dev_get(tmpstr);
+			if(!(slave && slave->flags & IFF_UP && dev->flags & IFF_UP))
+			{
+				printk("%s: Both devices should be UP to enslave!\n",dev->name);
+				return -EINVAL;
+			}
+		
+			if(slave)
+			{
+				if(!((dev->flags & IFF_SLAVE) || (slave->flags & IFF_SLAVE)))
+				{
+					/* drop queue*/
+					sbni_drop_tx_queue(slave);
+					slave->flags |= IFF_SLAVE;
+					((struct net_local *)(slave->priv))->m=dev;
+					while(lp->next_lp)	//tail it after last slave
+						lp=lp->next_lp;
+					lp->next_lp=slave->priv;
+					lp=(struct net_local *)dev->priv;
+					dev->flags |= IFF_MASTER;
+					}
+				else
+				{
+					printk("%s: one of devices is already slave!\n",dev->name);
+					return -EBUSY;
+				}
+			}
+			else
+			{
+				printk("%s: can't find device %s to enslave\n",dev->name,ifr->ifr_data);
+				return -ENOENT;
+			}
+			break;    
+
+		case SIOCDEVEMANSIPATE:
+			if(!capable(CAP_NET_ADMIN))
+				return -EPERM;
+
+			if(dev->flags & IFF_SLAVE)
+			{
+				dev->flags &= ~IFF_SLAVE;
+				/* exclude us from masters slavelist*/
+				for(tlp=lp->m->priv;tlp->next_lp!=lp && tlp->next_lp;tlp=tlp->next_lp);
+				if(tlp->next_lp)
+				{
+					tlp->next_lp = lp->next_lp;
+					if(!((struct net_local *)lp->m->priv)->next_lp)
+					{
+						lp->m->flags &= ~IFF_MASTER;	
+					}
+					lp->next_lp=NULL;
+					lp->m=dev;     	
+				}
+				else
+				{
+					printk("%s: Ooops. drivers structure is mangled!\n",dev->name);
+					return -EIO;
+				}      
+			}
+			else
+			{
+				printk("%s: isn't slave device!\n",dev->name);
+				return -EINVAL;
+			}
+			break;    
+
+		default:
+			DP( printk("%s: invalid ioctl: 0x%x\n",dev->name, cmd); )
+			error = -EINVAL;
+	}
+	return (error);
+}
+
+
+
+#ifdef CRCASM
+
+unsigned long calc_crc(char *mem, int len, unsigned initial)
+{
+   
+	__asm__ (
+		"xorl %%eax,%%eax\n\t"
+		"1:\n\t"
+		"lodsb\n\t"
+		"xorb %%dl,%%al\n\t"
+		"shrl $8,%%edx\n\t"
+		"xorl (%%edi,%%eax,4),%%edx\n\t"
+		"loop 1b\n\t"
+		"movl %%edx,%%eax"
+		: 
+		: "S" (mem), "D" (&crc32tab[0]), "c" (len), "d" (initial)
+		: "eax", "edx", "ecx"
+	);
+	/* return crc; */
+}
+
+#else
+
+unsigned long calc_crc(char *mem, int len, unsigned initial)
+{
+	unsigned crc;
+	crc = initial;
+   
+	for(;len;mem++,len--)
+	{
+		crc = CRC32(*mem, crc);
+	}
+	return(crc);
+}
+#endif /* CRCASM */
+#ifdef MODULE
+
+static int io[SBNI_MAX_NUM_CARDS] = { 0 };
+static int irq[SBNI_MAX_NUM_CARDS] = { 0 };
+static int rxl[SBNI_MAX_NUM_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1 };
+static int baud[SBNI_MAX_NUM_CARDS] = { 0 };
+static long mac[SBNI_MAX_NUM_CARDS] = { 0 };
+
+#ifdef v22
+MODULE_PARM(io, "1-" __MODULE_STRING(SBNI_MAX_NUM_CARDS) "i");
+MODULE_PARM(irq, "1-" __MODULE_STRING(SBNI_MAX_NUM_CARDS) "i");
+MODULE_PARM(rxl, "1-" __MODULE_STRING(SBNI_MAX_NUM_CARDS) "i");
+MODULE_PARM(baud, "1-" __MODULE_STRING(SBNI_MAX_NUM_CARDS) "i");
+MODULE_PARM(mac, "1-" __MODULE_STRING(SBNI_MAX_NUM_CARDS) "i");
+#endif
+
+
+static int sbniautodetect = -1;
+
+static struct device dev_sbni[SBNI_MAX_NUM_CARDS] = {
+	{
+		"sbni0",
+		0, 0, 0, 0,		/* memory */
+		0, 0,			/* base, irq */
+		0, 0, 0, NULL, sbni_probe 
+	},
+	{
+		"sbni1",
+		0, 0, 0, 0,		/* memory */
+		0, 0,			/* base, irq */
+		0, 0, 0, NULL, sbni_probe 
+	},
+	{
+		"sbni2",
+		0, 0, 0, 0,		/* memory */
+		0, 0,			/* base, irq */
+		0, 0, 0, NULL, sbni_probe 
+	},
+	{
+		"sbni3",
+		0, 0, 0, 0,		/* memory */
+		0, 0,			/* base, irq */
+		0, 0, 0, NULL, sbni_probe 
+	},
+	{
+		"sbni4",
+		0, 0, 0, 0,		/* memory */
+		0, 0,			/* base, irq */
+		0, 0, 0, NULL, sbni_probe 
+	},
+	{
+		"sbni5",
+		0, 0, 0, 0,		/* memory */
+		0, 0,			/* base, irq */
+		0, 0, 0, NULL, sbni_probe 
+	},
+	{
+		"sbni6",
+		0, 0, 0, 0,		/* memory */
+		0, 0,			/* base, irq */
+		0, 0, 0, NULL, sbni_probe 
+	},
+	{
+		"sbni7",
+		0, 0, 0, 0,		/* memory */
+		0, 0,			/* base, irq */
+		0, 0, 0, NULL, sbni_probe 
+	}
+};
+
+int init_module(void)
+{
+	int devices = 0;
+	int installed = 0;
+	int i;
+
+	/* My simple plug for this huge init_module. "XenON */
+      
+	if(sbniautodetect != -1)
+	{
+		/* Autodetect mode */
+		printk("sbni: Autodetect mode (not recommended!) ...\n");
+		if(!sbniautodetect)
+			sbniautodetect=SBNI_MAX_NUM_CARDS;
+		printk("Trying to find %d SBNI cards...\n", sbniautodetect);
+		if(sbniautodetect > SBNI_MAX_NUM_CARDS)
+		{
+			sbniautodetect = SBNI_MAX_NUM_CARDS;
+			printk("sbni: You want to detect too many cards. Truncated to %d\n", SBNI_MAX_NUM_CARDS);
+		}
+		for(i = 0; i < sbniautodetect; i++)
+		{
+			if(!register_netdev(&dev_sbni[i]))
+				installed++;
+		}
+		if(installed)
+			return 0;
+		else
+		    return -EIO;
+	}
+	
+	/* Manual mode */
+	for(i = 0; i < SBNI_MAX_NUM_CARDS; i++)
+	{
+		if((io[i] != 0) || (irq[i] != 0))
+			devices++;
+	}
+	for(i = 0; i < devices; i++)
+	{
+		dev_sbni[i].irq = irq[i];
+		dev_sbni[i].base_addr = io[i];
+		def_rxl = rxl[i];
+		def_baud = baud[i];
+		def_mac = mac[i];
+		if(register_netdev(&dev_sbni[i]))
+			printk("sbni: card not found!\n");
+		else
+			installed++;
+	}
+	if(installed)
+		return 0;
+	else
+		return -EIO;
+}
+
+void cleanup_module(void)
+{
+	int i;
+	for(i = 0; i < 4; i++)
+	{
+		if(dev_sbni[i].priv)
+		{
+			free_irq(dev_sbni[i].irq, &dev_sbni[i]);
+			release_region(dev_sbni[i].base_addr, SBNI_IO_EXTENT);
+			unregister_netdev(&dev_sbni[i]);
+			kfree(dev_sbni[i].priv);
+			dev_sbni[i].priv = NULL;
+		}
+	}
+}
+#endif /* MODULE */

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