diff options
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 ); + + nl->prev_rxl_rcvd = nl->cur_rxl_rcvd; + nl->cur_rxl_rcvd = 0; +} + +/* -------------------------------------------------------------------------- */ + +/* + * Open/initialize the board. + */ + +static int +sbni_open( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + struct timer_list *w = &nl->watchdog; + + /* + * For double ISA adapters within "common irq" mode, we have to + * determine whether primary or secondary channel is initialized, + * and set the irq handler only in first case. + */ + if( dev->base_addr < 0x400 ) { /* ISA only */ + struct net_device **p = sbni_cards; + for( ; *p && p < sbni_cards + SBNI_MAX_NUM_CARDS; ++p ) + if( (*p)->irq == dev->irq + && ((*p)->base_addr == dev->base_addr + 4 + || (*p)->base_addr == dev->base_addr - 4) + && (*p)->flags & IFF_UP ) { + + ((struct net_local *) ((*p)->priv)) + ->second = dev; + printk( KERN_NOTICE "%s: using shared irq " + "with %s\n", dev->name, (*p)->name ); + nl->state |= FL_SECONDARY; + goto handler_attached; + } + } + + if( request_irq(dev->irq, sbni_interrupt, SA_SHIRQ, dev->name, dev) ) { + printk( KERN_ERR "%s: unable to get IRQ %d.\n", + dev->name, dev->irq ); + return -EAGAIN; + } + +handler_attached: + + spin_lock( &nl->lock ); + memset( &nl->stats, 0, sizeof(struct net_device_stats) ); + memset( &nl->in_stats, 0, sizeof(struct sbni_in_stats) ); + + card_start( dev ); + + netif_start_queue( dev ); + + /* set timer watchdog */ + init_timer( w ); + w->expires = jiffies + SBNI_TIMEOUT; + w->data = (unsigned long) dev; + w->function = sbni_watchdog; + add_timer( w ); + + spin_unlock( &nl->lock ); + return 0; +} + + +static int +sbni_close( struct net_device *dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + + if( nl->second && nl->second->flags & IFF_UP ) { + printk( KERN_NOTICE "Secondary channel (%s) is active!\n", + nl->second->name ); + return -EBUSY; + } + +#ifdef CONFIG_SBNI_MULTILINE + if( nl->state & FL_SLAVE ) + emancipate( dev ); + else + while( nl->link ) /* it's master device! */ + emancipate( nl->link ); +#endif + + spin_lock( &nl->lock ); + + nl->second = NULL; + drop_xmit_queue( dev ); + netif_stop_queue( dev ); + + del_timer( &nl->watchdog ); + + outb( 0, dev->base_addr + CSR0 ); + + if( !(nl->state & FL_SECONDARY) ) + free_irq( dev->irq, dev ); + nl->state &= FL_SECONDARY; + + spin_unlock( &nl->lock ); + return 0; +} + + +/* + 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 +sbni_card_probe( unsigned long ioaddr ) +{ + unsigned char csr0; + + csr0 = inb( ioaddr + CSR0 ); + if( csr0 != 0xff && csr0 != 0x00 ) { + csr0 &= ~EN_INT; + if( csr0 & BU_EMP ) + csr0 |= EN_INT; + + if( VALID_DECODER & (1 << (csr0 >> 4)) ) + return 0; + } + + return -ENODEV; +} + +/* -------------------------------------------------------------------------- */ + +static int +sbni_ioctl( struct net_device *dev, struct ifreq *ifr, int cmd ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + struct sbni_flags flags; + int error = 0; + +#ifdef CONFIG_SBNI_MULTILINE + struct net_device *slave_dev; + char slave_name[ 8 ]; +#endif + + switch( cmd ) { + case SIOCDEVGETINSTATS : + if (copy_to_user( ifr->ifr_data, &nl->in_stats, + sizeof(struct sbni_in_stats) )) + error = -EFAULT; + break; + + case SIOCDEVRESINSTATS : + if( current->euid != 0 ) /* root only */ + return -EPERM; + memset( &nl->in_stats, 0, sizeof(struct sbni_in_stats) ); + break; + + case SIOCDEVGHWSTATE : + flags.mac_addr = *(u32 *)(dev->dev_addr + 3); + flags.rate = nl->csr1.rate; + flags.slow_mode = (nl->state & FL_SLOW_MODE) != 0; + flags.rxl = nl->cur_rxl_index; + flags.fixed_rxl = nl->delta_rxl == 0; + + if (copy_to_user( ifr->ifr_data, &flags, sizeof flags )) + error = -EFAULT; + break; + + case SIOCDEVSHWSTATE : + if( current->euid != 0 ) /* root only */ + return -EPERM; + + spin_lock( &nl->lock ); + flags = *(struct sbni_flags*) &ifr->ifr_ifru; + if( flags.fixed_rxl ) + nl->delta_rxl = 0, + nl->cur_rxl_index = flags.rxl; + else + nl->delta_rxl = DEF_RXL_DELTA, + nl->cur_rxl_index = DEF_RXL; + + nl->csr1.rxl = rxl_tab[ nl->cur_rxl_index ]; + nl->csr1.rate = flags.rate; + outb( *(u8 *)&nl->csr1 | PR_RES, dev->base_addr + CSR1 ); + spin_unlock( &nl->lock ); + break; + +#ifdef CONFIG_SBNI_MULTILINE + + case SIOCDEVENSLAVE : + if( current->euid != 0 ) /* root only */ + return -EPERM; + + if (copy_from_user( slave_name, ifr->ifr_data, sizeof slave_name )) + return -EFAULT; + slave_dev = dev_get_by_name( slave_name ); + if( !slave_dev || !(slave_dev->flags & IFF_UP) ) { + printk( KERN_ERR "%s: trying to enslave non-active " + "device %s\n", dev->name, slave_name ); + return -EPERM; + } + + return enslave( dev, slave_dev ); + + case SIOCDEVEMANSIPATE : + if( current->euid != 0 ) /* root only */ + return -EPERM; + + return emancipate( dev ); + +#endif /* CONFIG_SBNI_MULTILINE */ + + default : + return -EOPNOTSUPP; + } + + return error; +} + + +#ifdef CONFIG_SBNI_MULTILINE + +static int +enslave( struct net_device *dev, struct net_device *slave_dev ) +{ + struct net_local *nl = (struct net_local *) dev->priv; + struct net_local *snl = (struct net_local *) slave_dev->priv; + + if( nl->state & FL_SLAVE ) /* This isn't master or free device */ + return -EBUSY; + + if( snl->state & FL_SLAVE ) /* That was already enslaved */ + return -EBUSY; + + spin_lock( &nl->lock ); + spin_lock( &snl->lock ); + + /* append to list */ + snl->link = nl->link; + nl->link = slave_dev; + snl->master = dev; + snl->state |= FL_SLAVE; + + /* Summary statistics of MultiLine operation will be stored + in master's counters */ + memset( &snl->stats, 0, sizeof(struct net_device_stats) ); + netif_stop_queue( slave_dev ); + netif_wake_queue( dev ); /* Now we are able to transmit */ + + spin_unlock( &snl->lock ); + spin_unlock( &nl->lock ); + printk( KERN_NOTICE "%s: slave device (%s) attached.\n", + dev->name, slave_dev->name ); + return 0; +} + + +static int +emancipate( struct net_device *dev ) +{ + struct net_local *snl = (struct net_local *) dev->priv; + struct net_device *p = snl->master; + struct net_local *nl = (struct net_local *) p->priv; + + if( !(snl->state & FL_SLAVE) ) + return -EINVAL; + + spin_lock( &nl->lock ); + spin_lock( &snl->lock ); + drop_xmit_queue( dev ); + + /* exclude from list */ + for(;;) { /* must be in list */ + struct net_local *t = (struct net_local *) p->priv; + if( t->link == dev ) { + t->link = snl->link; + break; + } + p = t->link; + } + + snl->link = NULL; + snl->master = dev; + snl->state &= ~FL_SLAVE; + + netif_start_queue( dev ); + + spin_unlock( &snl->lock ); + spin_unlock( &nl->lock ); + + dev_put( dev ); + return 0; +} + +#endif + + +static struct net_device_stats * +sbni_get_stats( struct net_device *dev ) +{ + return &((struct net_local *) dev->priv)->stats; +} + + +static void +set_multicast_list( struct net_device *dev ) +{ + return; /* sbni always operate in promiscuos mode */ +} + + +#ifdef MODULE +module_param_array(io, int, NULL, 0); +module_param_array(irq, int, NULL, 0); +module_param_array(baud, int, NULL, 0); +module_param_array(rxl, int, NULL, 0); +module_param_array(mac, int, NULL, 0); +module_param(skip_pci_probe, bool, 0); + +MODULE_LICENSE("GPL"); + + +int +init_module( void ) +{ + struct net_device *dev; + int err; + + while( num < SBNI_MAX_NUM_CARDS ) { + dev = alloc_netdev(sizeof(struct net_local), + "sbni%d", sbni_devsetup); + if( !dev) + break; + + sprintf( dev->name, "sbni%d", num ); + + err = sbni_init(dev); + if (err) { + free_netdev(dev); + break; + } + + if( register_netdev( dev ) ) { + release_region( dev->base_addr, SBNI_IO_EXTENT ); + free_netdev( dev ); + break; + } + } + + return *sbni_cards ? 0 : -ENODEV; +} + +void +cleanup_module( void ) +{ + struct net_device *dev; + int num; + + for( num = 0; num < SBNI_MAX_NUM_CARDS; ++num ) + if( (dev = sbni_cards[ num ]) != NULL ) { + unregister_netdev( dev ); + release_region( dev->base_addr, SBNI_IO_EXTENT ); + free_netdev( dev ); + } +} + +#else /* MODULE */ + +static int __init +sbni_setup( char *p ) +{ + int n, parm; + + if( *p++ != '(' ) + goto bad_param; + + for( n = 0, parm = 0; *p && n < 8; ) { + (*dest[ parm ])[ n ] = simple_strtol( p, &p, 0 ); + if( !*p || *p == ')' ) + return 1; + if( *p == ';' ) + ++p, ++n, parm = 0; + else if( *p++ != ',' ) + break; + else + if( ++parm >= 5 ) + break; + } +bad_param: + printk( KERN_ERR "Error in sbni kernel parameter!\n" ); + return 0; +} + +__setup( "sbni=", sbni_setup ); + +#endif /* MODULE */ + +/* -------------------------------------------------------------------------- */ + +#ifdef ASM_CRC + +static u32 +calc_crc32( u32 crc, u8 *p, u32 len ) +{ + register u32 _crc; + _crc = crc; + + __asm__ __volatile__ ( + "xorl %%ebx, %%ebx\n" + "movl %2, %%esi\n" + "movl %3, %%ecx\n" + "movl $crc32tab, %%edi\n" + "shrl $2, %%ecx\n" + "jz 1f\n" + + ".align 4\n" + "0:\n" + "movb %%al, %%bl\n" + "movl (%%esi), %%edx\n" + "shrl $8, %%eax\n" + "xorb %%dl, %%bl\n" + "shrl $8, %%edx\n" + "xorl (%%edi,%%ebx,4), %%eax\n" + + "movb %%al, %%bl\n" + "shrl $8, %%eax\n" + "xorb %%dl, %%bl\n" + "shrl $8, %%edx\n" + "xorl (%%edi,%%ebx,4), %%eax\n" + + "movb %%al, %%bl\n" + "shrl $8, %%eax\n" + "xorb %%dl, %%bl\n" + "movb %%dh, %%dl\n" + "xorl (%%edi,%%ebx,4), %%eax\n" + + "movb %%al, %%bl\n" + "shrl $8, %%eax\n" + "xorb %%dl, %%bl\n" + "addl $4, %%esi\n" + "xorl (%%edi,%%ebx,4), %%eax\n" + + "decl %%ecx\n" + "jnz 0b\n" + + "1:\n" + "movl %3, %%ecx\n" + "andl $3, %%ecx\n" + "jz 2f\n" + + "movb %%al, %%bl\n" + "shrl $8, %%eax\n" + "xorb (%%esi), %%bl\n" + "xorl (%%edi,%%ebx,4), %%eax\n" + + "decl %%ecx\n" + "jz 2f\n" + + "movb %%al, %%bl\n" + "shrl $8, %%eax\n" + "xorb 1(%%esi), %%bl\n" + "xorl (%%edi,%%ebx,4), %%eax\n" + + "decl %%ecx\n" + "jz 2f\n" + + "movb %%al, %%bl\n" + "shrl $8, %%eax\n" + "xorb 2(%%esi), %%bl\n" + "xorl (%%edi,%%ebx,4), %%eax\n" + "2:\n" + : "=a" (_crc) + : "0" (_crc), "g" (p), "g" (len) + : "bx", "cx", "dx", "si", "di" + ); + + return _crc; +} + +#else /* ASM_CRC */ + +static u32 +calc_crc32( u32 crc, u8 *p, u32 len ) +{ + while( len-- ) + crc = CRC32( *p++, crc ); + + return crc; +} + +#endif /* ASM_CRC */ + + +static u32 crc32tab[] __attribute__ ((aligned(8))) = { + 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 +}; + |