📄 sbni.c
字号:
/* 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_MULTILINEstatic int enslave( struct net_device *, struct net_device * );static int emancipate( struct net_device * );#endif#ifdef __i386__#define ASM_CRC 1#endifstatic 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 MODULEtypedef 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 __initsbni_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 __initsbni_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 * __initsbni_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_MULTILINEstatic intsbni_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 intsbni_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_tsbni_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 voidhandle_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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -