diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/net/wan/sbni.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/net/wan/sbni.c')
-rw-r--r-- | drivers/net/wan/sbni.c | 1735 |
1 files changed, 1735 insertions, 0 deletions
diff --git a/drivers/net/wan/sbni.c b/drivers/net/wan/sbni.c new file mode 100644 index 00000000000..db2c798ba89 --- /dev/null +++ b/drivers/net/wan/sbni.c @@ -0,0 +1,1735 @@ +/* sbni.c: Granch SBNI12 leased line adapters driver for linux + * + * Written 2001 by Denis I.Timofeev (timofeev@granch.ru) + * + * Previous versions were written by Yaroslav Polyakov, + * Alexey Zverev and Max Khon. + * + * Driver supports SBNI12-02,-04,-05,-10,-11 cards, single and + * double-channel, PCI and ISA modifications. + * More info and useful utilities to work with SBNI12 cards you can find + * at http://www.granch.com (English) or http://www.granch.ru (Russian) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License. + * + * + * 5.0.1 Jun 22 2001 + * - Fixed bug in probe + * 5.0.0 Jun 06 2001 + * - Driver was completely redesigned by Denis I.Timofeev, + * - now PCI/Dual, ISA/Dual (with single interrupt line) models are + * - supported + * 3.3.0 Thu Feb 24 21:30:28 NOVT 2000 + * - PCI cards support + * 3.2.0 Mon Dec 13 22:26:53 NOVT 1999 + * - Completely rebuilt all the packet storage system + * - to work in Ethernet-like style. + * 3.1.1 just fixed some bugs (5 aug 1999) + * 3.1.0 added balancing feature (26 apr 1999) + * 3.0.1 just fixed some bugs (14 apr 1999). + * 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, + * + * Known problem: this driver wasn't tested on multiprocessor machine. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/fcntl.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/pci.h> +#include <linux/skbuff.h> +#include <linux/timer.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include <net/arp.h> + +#include <asm/io.h> +#include <asm/types.h> +#include <asm/byteorder.h> +#include <asm/irq.h> +#include <asm/uaccess.h> + +#include "sbni.h" + +/* device private data */ + +struct net_local { + struct net_device_stats stats; + struct timer_list watchdog; + + spinlock_t lock; + struct sk_buff *rx_buf_p; /* receive buffer ptr */ + struct sk_buff *tx_buf_p; /* transmit buffer ptr */ + + unsigned int framelen; /* current frame length */ + unsigned int maxframe; /* maximum valid frame length */ + unsigned int state; + unsigned int inppos, outpos; /* positions in rx/tx buffers */ + + /* transmitting frame number - from frames qty to 1 */ + unsigned int tx_frameno; + + /* expected number of next receiving frame */ + unsigned int wait_frameno; + + /* count of failed attempts to frame send - 32 attempts do before + error - while receiver tunes on opposite side of wire */ + unsigned int trans_errors; + + /* idle time; send pong when limit exceeded */ + unsigned int timer_ticks; + + /* fields used for receive level autoselection */ + int delta_rxl; + unsigned int cur_rxl_index, timeout_rxl; + unsigned long cur_rxl_rcvd, prev_rxl_rcvd; + + struct sbni_csr1 csr1; /* current value of CSR1 */ + struct sbni_in_stats in_stats; /* internal statistics */ + + struct net_device *second; /* for ISA/dual cards */ + +#ifdef CONFIG_SBNI_MULTILINE + struct net_device *master; + struct net_device *link; +#endif +}; + + +static int sbni_card_probe( unsigned long ); +static int sbni_pci_probe( struct net_device * ); +static struct net_device *sbni_probe1(struct net_device *, unsigned long, int); +static int sbni_open( struct net_device * ); +static int sbni_close( struct net_device * ); +static int sbni_start_xmit( struct sk_buff *, struct net_device * ); +static int sbni_ioctl( struct net_device *, struct ifreq *, int ); +static struct net_device_stats *sbni_get_stats( struct net_device * ); +static void set_multicast_list( struct net_device * ); + +static irqreturn_t sbni_interrupt( int, void *, struct pt_regs * ); +static void handle_channel( struct net_device * ); +static int recv_frame( struct net_device * ); +static void send_frame( struct net_device * ); +static int upload_data( struct net_device *, + unsigned, unsigned, unsigned, u32 ); +static void download_data( struct net_device *, u32 * ); +static void sbni_watchdog( unsigned long ); +static void interpret_ack( struct net_device *, unsigned ); +static int append_frame_to_pkt( struct net_device *, unsigned, u32 ); +static void indicate_pkt( struct net_device * ); +static void card_start( struct net_device * ); +static void prepare_to_send( struct sk_buff *, struct net_device * ); +static void drop_xmit_queue( struct net_device * ); +static void send_frame_header( struct net_device *, u32 * ); +static int skip_tail( unsigned int, unsigned int, u32 ); +static int check_fhdr( u32, u32 *, u32 *, u32 *, u32 *, u32 * ); +static void change_level( struct net_device * ); +static void timeout_change_level( struct net_device * ); +static u32 calc_crc32( u32, u8 *, u32 ); +static struct sk_buff * get_rx_buf( struct net_device * ); +static int sbni_init( struct net_device * ); + +#ifdef CONFIG_SBNI_MULTILINE +static int enslave( struct net_device *, struct net_device * ); +static int emancipate( struct net_device * ); +#endif + +#ifdef __i386__ +#define ASM_CRC 1 +#endif + +static const char version[] = + "Granch SBNI12 driver ver 5.0.1 Jun 22 2001 Denis I.Timofeev.\n"; + +static int skip_pci_probe __initdata = 0; +static int scandone __initdata = 0; +static int num __initdata = 0; + +static unsigned char rxl_tab[]; +static u32 crc32tab[]; + +/* A list of all installed devices, for removing the driver module. */ +static struct net_device *sbni_cards[ SBNI_MAX_NUM_CARDS ]; + +/* Lists of device's parameters */ +static u32 io[ SBNI_MAX_NUM_CARDS ] __initdata = + { [0 ... SBNI_MAX_NUM_CARDS-1] = -1 }; +static u32 irq[ SBNI_MAX_NUM_CARDS ] __initdata; +static u32 baud[ SBNI_MAX_NUM_CARDS ] __initdata; +static u32 rxl[ SBNI_MAX_NUM_CARDS ] __initdata = + { [0 ... SBNI_MAX_NUM_CARDS-1] = -1 }; +static u32 mac[ SBNI_MAX_NUM_CARDS ] __initdata; + +#ifndef MODULE +typedef u32 iarr[]; +static iarr __initdata *dest[5] = { &io, &irq, &baud, &rxl, &mac }; +#endif + +/* A zero-terminated list of I/O addresses to be probed on ISA bus */ +static unsigned int netcard_portlist[ ] __initdata = { + 0x210, 0x214, 0x220, 0x224, 0x230, 0x234, 0x240, 0x244, 0x250, 0x254, + 0x260, 0x264, 0x270, 0x274, 0x280, 0x284, 0x290, 0x294, 0x2a0, 0x2a4, + 0x2b0, 0x2b4, 0x2c0, 0x2c4, 0x2d0, 0x2d4, 0x2e0, 0x2e4, 0x2f0, 0x2f4, + 0 }; + + +/* + * Look for SBNI card which addr stored in dev->base_addr, if nonzero. + * Otherwise, look through PCI bus. If none PCI-card was found, scan ISA. + */ + +static inline int __init +sbni_isa_probe( struct net_device *dev ) +{ + if( dev->base_addr > 0x1ff + && request_region( dev->base_addr, SBNI_IO_EXTENT, dev->name ) + && sbni_probe1( dev, dev->base_addr, dev->irq ) ) + + return 0; + else { + printk( KERN_ERR "sbni: base address 0x%lx is busy, or adapter " + "is malfunctional!\n", dev->base_addr ); + return -ENODEV; + } +} + +static void __init sbni_devsetup(struct net_device *dev) +{ + ether_setup( dev ); + dev->open = &sbni_open; + dev->stop = &sbni_close; + dev->hard_start_xmit = &sbni_start_xmit; + dev->get_stats = &sbni_get_stats; + dev->set_multicast_list = &set_multicast_list; + dev->do_ioctl = &sbni_ioctl; + + SET_MODULE_OWNER( dev ); +} + +int __init sbni_probe(int unit) +{ + struct net_device *dev; + static unsigned version_printed __initdata = 0; + int err; + + dev = alloc_netdev(sizeof(struct net_local), "sbni", sbni_devsetup); + if (!dev) + return -ENOMEM; + + sprintf(dev->name, "sbni%d", unit); + netdev_boot_setup_check(dev); + + err = sbni_init(dev); + if (err) { + free_netdev(dev); + return err; + } + + err = register_netdev(dev); + if (err) { + release_region( dev->base_addr, SBNI_IO_EXTENT ); + free_netdev(dev); + return err; + } + if( version_printed++ == 0 ) + printk( KERN_INFO "%s", version ); + return 0; +} + +static int __init sbni_init(struct net_device *dev) +{ + int i; + if( dev->base_addr ) + return sbni_isa_probe( dev ); + /* otherwise we have to perform search our adapter */ + + if( io[ num ] != -1 ) + dev->base_addr = io[ num ], + dev->irq = irq[ num ]; + else if( scandone || io[ 0 ] != -1 ) + return -ENODEV; + + /* if io[ num ] contains non-zero address, then that is on ISA bus */ + if( dev->base_addr ) + return sbni_isa_probe( dev ); + + /* ...otherwise - scan PCI first */ + if( !skip_pci_probe && !sbni_pci_probe( dev ) ) + return 0; + + if( io[ num ] == -1 ) { + /* Auto-scan will be stopped when first ISA card were found */ + scandone = 1; + if( num > 0 ) + return -ENODEV; + } + + for( i = 0; netcard_portlist[ i ]; ++i ) { + int ioaddr = netcard_portlist[ i ]; + if( request_region( ioaddr, SBNI_IO_EXTENT, dev->name ) + && sbni_probe1( dev, ioaddr, 0 )) + return 0; + } + + return -ENODEV; +} + + +int __init +sbni_pci_probe( struct net_device *dev ) +{ + struct pci_dev *pdev = NULL; + + while( (pdev = pci_get_class( PCI_CLASS_NETWORK_OTHER << 8, pdev )) + != NULL ) { + int pci_irq_line; + unsigned long pci_ioaddr; + u16 subsys; + + if( pdev->vendor != SBNI_PCI_VENDOR + && pdev->device != SBNI_PCI_DEVICE ) + continue; + + pci_ioaddr = pci_resource_start( pdev, 0 ); + pci_irq_line = pdev->irq; + + /* Avoid already found cards from previous calls */ + if( !request_region( pci_ioaddr, SBNI_IO_EXTENT, dev->name ) ) { + pci_read_config_word( pdev, PCI_SUBSYSTEM_ID, &subsys ); + + if (subsys != 2) + continue; + + /* Dual adapter is present */ + if (!request_region(pci_ioaddr += 4, SBNI_IO_EXTENT, + dev->name ) ) + continue; + } + + if( pci_irq_line <= 0 || pci_irq_line >= NR_IRQS ) + printk( KERN_WARNING " WARNING: The PCI BIOS assigned " + "this PCI card to IRQ %d, which is unlikely " + "to work!.\n" + KERN_WARNING " You should use the PCI BIOS " + "setup to assign a valid IRQ line.\n", + pci_irq_line ); + + /* avoiding re-enable dual adapters */ + if( (pci_ioaddr & 7) == 0 && pci_enable_device( pdev ) ) { + release_region( pci_ioaddr, SBNI_IO_EXTENT ); + pci_dev_put( pdev ); + return -EIO; + } + if( sbni_probe1( dev, pci_ioaddr, pci_irq_line ) ) { + SET_NETDEV_DEV(dev, &pdev->dev); + /* not the best thing to do, but this is all messed up + for hotplug systems anyway... */ + pci_dev_put( pdev ); + return 0; + } + } + return -ENODEV; +} + + +static struct net_device * __init +sbni_probe1( struct net_device *dev, unsigned long ioaddr, int irq ) +{ + struct net_local *nl; + + if( sbni_card_probe( ioaddr ) ) { + release_region( ioaddr, SBNI_IO_EXTENT ); + return NULL; + } + + outb( 0, ioaddr + CSR0 ); + + if( irq < 2 ) { + unsigned long irq_mask; + + irq_mask = probe_irq_on(); + outb( EN_INT | TR_REQ, ioaddr + CSR0 ); + outb( PR_RES, ioaddr + CSR1 ); + mdelay(50); + irq = probe_irq_off(irq_mask); + outb( 0, ioaddr + CSR0 ); + + if( !irq ) { + printk( KERN_ERR "%s: can't detect device irq!\n", + dev->name ); + release_region( ioaddr, SBNI_IO_EXTENT ); + return NULL; + } + } else if( irq == 2 ) + irq = 9; + + dev->irq = irq; + dev->base_addr = ioaddr; + + /* Allocate dev->priv and fill in sbni-specific dev fields. */ + nl = dev->priv; + if( !nl ) { + printk( KERN_ERR "%s: unable to get memory!\n", dev->name ); + release_region( ioaddr, SBNI_IO_EXTENT ); + return NULL; + } + + dev->priv = nl; + memset( nl, 0, sizeof(struct net_local) ); + spin_lock_init( &nl->lock ); + + /* store MAC address (generate if that isn't known) */ + *(u16 *)dev->dev_addr = htons( 0x00ff ); + *(u32 *)(dev->dev_addr + 2) = htonl( 0x01000000 | + ( (mac[num] ? mac[num] : (u32)((long)dev->priv)) & 0x00ffffff) ); + + /* store link settings (speed, receive level ) */ + nl->maxframe = DEFAULT_FRAME_LEN; + nl->csr1.rate = baud[ num ]; + + if( (nl->cur_rxl_index = rxl[ num ]) == -1 ) + /* autotune rxl */ + nl->cur_rxl_index = DEF_RXL, + nl->delta_rxl = DEF_RXL_DELTA; + else + nl->delta_rxl = 0; + nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index ]; + if( inb( ioaddr + CSR0 ) & 0x01 ) + nl->state |= FL_SLOW_MODE; + + printk( KERN_NOTICE "%s: ioaddr %#lx, irq %d, " + "MAC: 00:ff:01:%02x:%02x:%02x\n", + dev->name, dev->base_addr, dev->irq, + ((u8 *) dev->dev_addr) [3], + ((u8 *) dev->dev_addr) [4], + ((u8 *) dev->dev_addr) [5] ); + + printk( KERN_NOTICE "%s: speed %d, receive level ", dev->name, + ( (nl->state & FL_SLOW_MODE) ? 500000 : 2000000) + / (1 << nl->csr1.rate) ); + + if( nl->delta_rxl == 0 ) + printk( "0x%x (fixed)\n", nl->cur_rxl_index ); + else + printk( "(auto)\n"); + +#ifdef CONFIG_SBNI_MULTILINE + nl->master = dev; + nl->link = NULL; +#endif + + sbni_cards[ num++ ] = dev; + return dev; +} + +/* -------------------------------------------------------------------------- */ + +#ifdef CONFIG_SBNI_MULTILINE + +static int +sbni_start_xmit( struct sk_buff *skb, struct net_device *dev ) +{ + struct net_device *p; + + netif_stop_queue( dev ); + + /* Looking for idle device in the list */ + for( p = dev; p; ) { + struct net_local *nl = (struct net_local *) p->priv; + spin_lock( &nl->lock ); + if( nl->tx_buf_p || (nl->state & FL_LINE_DOWN) ) { + p = nl->link; + spin_unlock( &nl->lock ); + } else { + /* Idle dev is found */ + prepare_to_send( skb, p ); + spin_unlock( &nl->lock ); + netif_start_queue( dev ); + return 0; + } + } + + return 1; +} + +#else /* CONFIG_SBNI_MULTILINE */ + +static int +sbni_start_xmit( struct sk_buff *skb, struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + netif_stop_queue( dev ); + spin_lock( &nl->lock ); + + prepare_to_send( skb, dev ); + + spin_unlock( &nl->lock ); + return 0; +} + +#endif /* CONFIG_SBNI_MULTILINE */ + +/* -------------------------------------------------------------------------- */ + +/* interrupt handler */ + +/* + * SBNI12D-10, -11/ISA boards within "common interrupt" mode could not + * be looked as two independent single-channel devices. Every channel seems + * as Ethernet interface but interrupt handler must be common. Really, first + * channel ("master") driver only registers the handler. In its struct net_local + * it has got pointer to "slave" channel's struct net_local and handles that's + * interrupts too. + * dev of successfully attached ISA SBNI boards is linked to list. + * While next board driver is initialized, it scans this list. If one + * has found dev with same irq and ioaddr different by 4 then it assumes + * this board to be "master". + */ + +static irqreturn_t +sbni_interrupt( int irq, void *dev_id, struct pt_regs *regs ) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct net_local *nl = (struct net_local *) dev->priv; + int repeat; + + spin_lock( &nl->lock ); + if( nl->second ) + spin_lock( &((struct net_local *) nl->second->priv)->lock ); + + do { + repeat = 0; + if( inb( dev->base_addr + CSR0 ) & (RC_RDY | TR_RDY) ) + handle_channel( dev ), + repeat = 1; + if( nl->second && /* second channel present */ + (inb( nl->second->base_addr+CSR0 ) & (RC_RDY | TR_RDY)) ) + handle_channel( nl->second ), + repeat = 1; + } while( repeat ); + + if( nl->second ) + spin_unlock( &((struct net_local *)nl->second->priv)->lock ); + spin_unlock( &nl->lock ); + return IRQ_HANDLED; +} + + +static void +handle_channel( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + unsigned long ioaddr = dev->base_addr; + + int req_ans; + unsigned char csr0; + +#ifdef CONFIG_SBNI_MULTILINE + /* Lock the master device because we going to change its local data */ + if( nl->state & FL_SLAVE ) + spin_lock( &((struct net_local *) nl->master->priv)->lock ); +#endif + + outb( (inb( ioaddr + CSR0 ) & ~EN_INT) | TR_REQ, ioaddr + CSR0 ); + + nl->timer_ticks = CHANGE_LEVEL_START_TICKS; + for(;;) { + csr0 = inb( ioaddr + CSR0 ); + if( ( csr0 & (RC_RDY | TR_RDY) ) == 0 ) + break; + + req_ans = !(nl->state & FL_PREV_OK); + + if( csr0 & RC_RDY ) + req_ans = recv_frame( dev ); + + /* + * TR_RDY always equals 1 here because we have owned the marker, + * and we set TR_REQ when disabled interrupts + */ + csr0 = inb( ioaddr + CSR0 ); + if( !(csr0 & TR_RDY) || (csr0 & RC_RDY) ) + printk( KERN_ERR "%s: internal error!\n", dev->name ); + + /* if state & FL_NEED_RESEND != 0 then tx_frameno != 0 */ + if( req_ans || nl->tx_frameno != 0 ) + send_frame( dev ); + else + /* send marker without any data */ + outb( inb( ioaddr + CSR0 ) & ~TR_REQ, ioaddr + CSR0 ); + } + + outb( inb( ioaddr + CSR0 ) | EN_INT, ioaddr + CSR0 ); + +#ifdef CONFIG_SBNI_MULTILINE + if( nl->state & FL_SLAVE ) + spin_unlock( &((struct net_local *) nl->master->priv)->lock ); +#endif +} + + +/* + * Routine returns 1 if it need to acknoweledge received frame. + * Empty frame received without errors won't be acknoweledged. + */ + +static int +recv_frame( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + unsigned long ioaddr = dev->base_addr; + + u32 crc = CRC32_INITIAL; + + unsigned framelen, frameno, ack; + unsigned is_first, frame_ok; + + if( check_fhdr( ioaddr, &framelen, &frameno, &ack, &is_first, &crc ) ) { + frame_ok = framelen > 4 + ? upload_data( dev, framelen, frameno, is_first, crc ) + : skip_tail( ioaddr, framelen, crc ); + if( frame_ok ) + interpret_ack( dev, ack ); + } else + frame_ok = 0; + + outb( inb( ioaddr + CSR0 ) ^ CT_ZER, ioaddr + CSR0 ); + if( frame_ok ) { + nl->state |= FL_PREV_OK; + if( framelen > 4 ) + nl->in_stats.all_rx_number++; + } else + nl->state &= ~FL_PREV_OK, + change_level( dev ), + nl->in_stats.all_rx_number++, + nl->in_stats.bad_rx_number++; + + return !frame_ok || framelen > 4; +} + + +static void +send_frame( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + u32 crc = CRC32_INITIAL; + + if( nl->state & FL_NEED_RESEND ) { + + /* if frame was sended but not ACK'ed - resend it */ + if( nl->trans_errors ) { + --nl->trans_errors; + if( nl->framelen != 0 ) + nl->in_stats.resend_tx_number++; + } else { + /* cannot xmit with many attempts */ +#ifdef CONFIG_SBNI_MULTILINE + if( (nl->state & FL_SLAVE) || nl->link ) +#endif + nl->state |= FL_LINE_DOWN; + drop_xmit_queue( dev ); + goto do_send; + } + } else + nl->trans_errors = TR_ERROR_COUNT; + + send_frame_header( dev, &crc ); + nl->state |= FL_NEED_RESEND; + /* + * FL_NEED_RESEND will be cleared after ACK, but if empty + * frame sended then in prepare_to_send next frame + */ + + + if( nl->framelen ) { + download_data( dev, &crc ); + nl->in_stats.all_tx_number++; + nl->state |= FL_WAIT_ACK; + } + + outsb( dev->base_addr + DAT, (u8 *)&crc, sizeof crc ); + +do_send: + outb( inb( dev->base_addr + CSR0 ) & ~TR_REQ, dev->base_addr + CSR0 ); + + if( nl->tx_frameno ) + /* next frame exists - we request card to send it */ + outb( inb( dev->base_addr + CSR0 ) | TR_REQ, + dev->base_addr + CSR0 ); +} + + +/* + * Write the frame data into adapter's buffer memory, and calculate CRC. + * Do padding if necessary. + */ + +static void +download_data( struct net_device *dev, u32 *crc_p ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + struct sk_buff *skb = nl->tx_buf_p; + + unsigned len = min_t(unsigned int, skb->len - nl->outpos, nl->framelen); + + outsb( dev->base_addr + DAT, skb->data + nl->outpos, len ); + *crc_p = calc_crc32( *crc_p, skb->data + nl->outpos, len ); + + /* if packet too short we should write some more bytes to pad */ + for( len = nl->framelen - len; len--; ) + outb( 0, dev->base_addr + DAT ), + *crc_p = CRC32( 0, *crc_p ); +} + + +static int +upload_data( struct net_device *dev, unsigned framelen, unsigned frameno, + unsigned is_first, u32 crc ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + int frame_ok; + + if( is_first ) + nl->wait_frameno = frameno, + nl->inppos = 0; + + if( nl->wait_frameno == frameno ) { + + if( nl->inppos + framelen <= ETHER_MAX_LEN ) + frame_ok = append_frame_to_pkt( dev, framelen, crc ); + + /* + * if CRC is right but framelen incorrect then transmitter + * error was occurred... drop entire packet + */ + else if( (frame_ok = skip_tail( dev->base_addr, framelen, crc )) + != 0 ) + nl->wait_frameno = 0, + nl->inppos = 0, +#ifdef CONFIG_SBNI_MULTILINE + ((struct net_local *) nl->master->priv) + ->stats.rx_errors++, + ((struct net_local *) nl->master->priv) + ->stats.rx_missed_errors++; +#else + nl->stats.rx_errors++, + nl->stats.rx_missed_errors++; +#endif + /* now skip all frames until is_first != 0 */ + } else + frame_ok = skip_tail( dev->base_addr, framelen, crc ); + + if( is_first && !frame_ok ) + /* + * Frame has been broken, but we had already stored + * is_first... Drop entire packet. + */ + nl->wait_frameno = 0, +#ifdef CONFIG_SBNI_MULTILINE + ((struct net_local *) nl->master->priv)->stats.rx_errors++, + ((struct net_local *) nl->master->priv)->stats.rx_crc_errors++; +#else + nl->stats.rx_errors++, + nl->stats.rx_crc_errors++; +#endif + + return frame_ok; +} + + +static __inline void +send_complete( struct net_local *nl ) +{ +#ifdef CONFIG_SBNI_MULTILINE + ((struct net_local *) nl->master->priv)->stats.tx_packets++; + ((struct net_local *) nl->master->priv)->stats.tx_bytes + += nl->tx_buf_p->len; +#else + nl->stats.tx_packets++; + nl->stats.tx_bytes += nl->tx_buf_p->len; +#endif + dev_kfree_skb_irq( nl->tx_buf_p ); + + nl->tx_buf_p = NULL; + + nl->outpos = 0; + nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND); + nl->framelen = 0; +} + + +static void +interpret_ack( struct net_device *dev, unsigned ack ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + if( ack == FRAME_SENT_OK ) { + nl->state &= ~FL_NEED_RESEND; + + if( nl->state & FL_WAIT_ACK ) { + nl->outpos += nl->framelen; + + if( --nl->tx_frameno ) + nl->framelen = min_t(unsigned int, + nl->maxframe, + nl->tx_buf_p->len - nl->outpos); + else + send_complete( nl ), +#ifdef CONFIG_SBNI_MULTILINE + netif_wake_queue( nl->master ); +#else + netif_wake_queue( dev ); +#endif + } + } + + nl->state &= ~FL_WAIT_ACK; +} + + +/* + * Glue received frame with previous fragments of packet. + * Indicate packet when last frame would be accepted. + */ + +static int +append_frame_to_pkt( struct net_device *dev, unsigned framelen, u32 crc ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + u8 *p; + + if( nl->inppos + framelen > ETHER_MAX_LEN ) + return 0; + + if( !nl->rx_buf_p && !(nl->rx_buf_p = get_rx_buf( dev )) ) + return 0; + + p = nl->rx_buf_p->data + nl->inppos; + insb( dev->base_addr + DAT, p, framelen ); + if( calc_crc32( crc, p, framelen ) != CRC32_REMAINDER ) + return 0; + + nl->inppos += framelen - 4; + if( --nl->wait_frameno == 0 ) /* last frame received */ + indicate_pkt( dev ); + + return 1; +} + + +/* + * Prepare to start output on adapter. + * Transmitter will be actually activated when marker is accepted. + */ + +static void +prepare_to_send( struct sk_buff *skb, struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + unsigned int len; + + /* nl->tx_buf_p == NULL here! */ + if( nl->tx_buf_p ) + printk( KERN_ERR "%s: memory leak!\n", dev->name ); + + nl->outpos = 0; + nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND); + + len = skb->len; + if( len < SBNI_MIN_LEN ) + len = SBNI_MIN_LEN; + + nl->tx_buf_p = skb; + nl->tx_frameno = (len + nl->maxframe - 1) / nl->maxframe; + nl->framelen = len < nl->maxframe ? len : nl->maxframe; + + outb( inb( dev->base_addr + CSR0 ) | TR_REQ, dev->base_addr + CSR0 ); +#ifdef CONFIG_SBNI_MULTILINE + nl->master->trans_start = jiffies; +#else + dev->trans_start = jiffies; +#endif +} + + +static void +drop_xmit_queue( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + if( nl->tx_buf_p ) + dev_kfree_skb_any( nl->tx_buf_p ), + nl->tx_buf_p = NULL, +#ifdef CONFIG_SBNI_MULTILINE + ((struct net_local *) nl->master->priv) + ->stats.tx_errors++, + ((struct net_local *) nl->master->priv) + ->stats.tx_carrier_errors++; +#else + nl->stats.tx_errors++, + nl->stats.tx_carrier_errors++; +#endif + + nl->tx_frameno = 0; + nl->framelen = 0; + nl->outpos = 0; + nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND); +#ifdef CONFIG_SBNI_MULTILINE + netif_start_queue( nl->master ); + nl->master->trans_start = jiffies; +#else + netif_start_queue( dev ); + dev->trans_start = jiffies; +#endif +} + + +static void +send_frame_header( struct net_device *dev, u32 *crc_p ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + u32 crc = *crc_p; + u32 len_field = nl->framelen + 6; /* CRC + frameno + reserved */ + u8 value; + + if( nl->state & FL_NEED_RESEND ) + len_field |= FRAME_RETRY; /* non-first attempt... */ + + if( nl->outpos == 0 ) + len_field |= FRAME_FIRST; + + len_field |= (nl->state & FL_PREV_OK) ? FRAME_SENT_OK : FRAME_SENT_BAD; + outb( SBNI_SIG, dev->base_addr + DAT ); + + value = (u8) len_field; + outb( value, dev->base_addr + DAT ); + crc = CRC32( value, crc ); + value = (u8) (len_field >> 8); + outb( value, dev->base_addr + DAT ); + crc = CRC32( value, crc ); + + outb( nl->tx_frameno, dev->base_addr + DAT ); + crc = CRC32( nl->tx_frameno, crc ); + outb( 0, dev->base_addr + DAT ); + crc = CRC32( 0, crc ); + *crc_p = crc; +} + + +/* + * if frame tail not needed (incorrect number or received twice), + * it won't store, but CRC will be calculated + */ + +static int +skip_tail( unsigned int ioaddr, unsigned int tail_len, u32 crc ) +{ + while( tail_len-- ) + crc = CRC32( inb( ioaddr + DAT ), crc ); + + return crc == CRC32_REMAINDER; +} + + +/* + * Preliminary checks if frame header is correct, calculates its CRC + * and split it to simple fields + */ + +static int +check_fhdr( u32 ioaddr, u32 *framelen, u32 *frameno, u32 *ack, + u32 *is_first, u32 *crc_p ) +{ + u32 crc = *crc_p; + u8 value; + + if( inb( ioaddr + DAT ) != SBNI_SIG ) + return 0; + + value = inb( ioaddr + DAT ); + *framelen = (u32)value; + crc = CRC32( value, crc ); + value = inb( ioaddr + DAT ); + *framelen |= ((u32)value) << 8; + crc = CRC32( value, crc ); + + *ack = *framelen & FRAME_ACK_MASK; + *is_first = (*framelen & FRAME_FIRST) != 0; + + if( (*framelen &= FRAME_LEN_MASK) < 6 + || *framelen > SBNI_MAX_FRAME - 3 ) + return 0; + + value = inb( ioaddr + DAT ); + *frameno = (u32)value; + crc = CRC32( value, crc ); + + crc = CRC32( inb( ioaddr + DAT ), crc ); /* reserved byte */ + *framelen -= 2; + + *crc_p = crc; + return 1; +} + + +static struct sk_buff * +get_rx_buf( struct net_device *dev ) +{ + /* +2 is to compensate for the alignment fixup below */ + struct sk_buff *skb = dev_alloc_skb( ETHER_MAX_LEN + 2 ); + if( !skb ) + return NULL; + +#ifdef CONFIG_SBNI_MULTILINE + skb->dev = ((struct net_local *) dev->priv)->master; +#else + skb->dev = dev; +#endif + skb_reserve( skb, 2 ); /* Align IP on longword boundaries */ + return skb; +} + + +static void +indicate_pkt( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + struct sk_buff *skb = nl->rx_buf_p; + + skb_put( skb, nl->inppos ); + +#ifdef CONFIG_SBNI_MULTILINE + skb->protocol = eth_type_trans( skb, nl->master ); + netif_rx( skb ); + dev->last_rx = jiffies; + ++((struct net_local *) nl->master->priv)->stats.rx_packets; + ((struct net_local *) nl->master->priv)->stats.rx_bytes += nl->inppos; +#else + skb->protocol = eth_type_trans( skb, dev ); + netif_rx( skb ); + dev->last_rx = jiffies; + ++nl->stats.rx_packets; + nl->stats.rx_bytes += nl->inppos; +#endif + nl->rx_buf_p = NULL; /* protocol driver will clear this sk_buff */ +} + + +/* -------------------------------------------------------------------------- */ + +/* + * Routine checks periodically wire activity and regenerates marker if + * connect was inactive for a long time. + */ + +static void +sbni_watchdog( unsigned long arg ) +{ + struct net_device *dev = (struct net_device *) arg; + struct net_local *nl = (struct net_local *) dev->priv; + struct timer_list *w = &nl->watchdog; + unsigned long flags; + unsigned char csr0; + + spin_lock_irqsave( &nl->lock, flags ); + + csr0 = inb( dev->base_addr + CSR0 ); + if( csr0 & RC_CHK ) { + + if( nl->timer_ticks ) { + if( csr0 & (RC_RDY | BU_EMP) ) + /* receiving not active */ + nl->timer_ticks--; + } else { + nl->in_stats.timeout_number++; + if( nl->delta_rxl ) + timeout_change_level( dev ); + + outb( *(u_char *)&nl->csr1 | PR_RES, + dev->base_addr + CSR1 ); + csr0 = inb( dev->base_addr + CSR0 ); + } + } else + nl->state &= ~FL_LINE_DOWN; + + outb( csr0 | RC_CHK, dev->base_addr + CSR0 ); + + init_timer( w ); + w->expires = jiffies + SBNI_TIMEOUT; + w->data = arg; + w->function = sbni_watchdog; + add_timer( w ); + + spin_unlock_irqrestore( &nl->lock, flags ); +} + + +static unsigned char rxl_tab[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, + 0x0a, 0x0c, 0x0f, 0x16, 0x18, 0x1a, 0x1c, 0x1f +}; + +#define SIZE_OF_TIMEOUT_RXL_TAB 4 +static unsigned char timeout_rxl_tab[] = { + 0x03, 0x05, 0x08, 0x0b +}; + +/* -------------------------------------------------------------------------- */ + +static void +card_start( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + nl->timer_ticks = CHANGE_LEVEL_START_TICKS; + nl->state &= ~(FL_WAIT_ACK | FL_NEED_RESEND); + nl->state |= FL_PREV_OK; + + nl->inppos = nl->outpos = 0; + nl->wait_frameno = 0; + nl->tx_frameno = 0; + nl->framelen = 0; + + outb( *(u_char *)&nl->csr1 | PR_RES, dev->base_addr + CSR1 ); + outb( EN_INT, dev->base_addr + CSR0 ); +} + +/* -------------------------------------------------------------------------- */ + +/* Receive level auto-selection */ + +static void +change_level( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + if( nl->delta_rxl == 0 ) /* do not auto-negotiate RxL */ + return; + + if( nl->cur_rxl_index == 0 ) + nl->delta_rxl = 1; + else if( nl->cur_rxl_index == 15 ) + nl->delta_rxl = -1; + else if( nl->cur_rxl_rcvd < nl->prev_rxl_rcvd ) + nl->delta_rxl = -nl->delta_rxl; + + nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index += nl->delta_rxl ]; + inb( dev->base_addr + CSR0 ); /* needs for PCI cards */ + outb( *(u8 *)&nl->csr1, dev->base_addr + CSR1 ); + + nl->prev_rxl_rcvd = nl->cur_rxl_rcvd; + nl->cur_rxl_rcvd = 0; +} + + +static void +timeout_change_level( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + nl->cur_rxl_index = timeout_rxl_tab[ nl->timeout_rxl ]; + if( ++nl->timeout_rxl >= 4 ) + nl->timeout_rxl = 0; + + nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index ]; + inb( dev->base_addr + CSR0 ); + outb( *(unsigned char *)&nl->csr1, dev->base_addr + CSR1 ); + + n |