| /* |
| * FarSync X21 driver for Linux (2.4.x kernel version) |
| * |
| * Actually sync driver for X.21, V.35 and V.24 on FarSync T-series cards |
| * |
| * Copyright (C) 2001 FarSite Communications Ltd. |
| * www.farsite.co.uk |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version |
| * 2 of the License, or (at your option) any later version. |
| * |
| * Author: R.J.Dunlop <bob.dunlop@farsite.co.uk> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/pci.h> |
| #include <linux/ioport.h> |
| #include <linux/netdevice.h> |
| #include <linux/init.h> |
| #include <linux/if_arp.h> |
| #include <asm/uaccess.h> |
| #include <net/syncppp.h> |
| |
| #include "farsync.h" |
| |
| |
| /* |
| * Module info |
| */ |
| MODULE_AUTHOR("R.J.Dunlop <bob.dunlop@farsite.co.uk>"); |
| MODULE_DESCRIPTION("FarSync T-Series X21 driver. FarSite Communications Ltd."); |
| |
| EXPORT_NO_SYMBOLS; |
| |
| |
| /* Driver configuration and global parameters |
| * ========================================== |
| */ |
| |
| /* Number of ports (per card) supported |
| */ |
| #define FST_MAX_PORTS 4 |
| |
| |
| /* PCI vendor and device IDs |
| */ |
| #define FSC_PCI_VENDOR_ID 0x1619 /* FarSite Communications Ltd */ |
| #define T2P_PCI_DEVICE_ID 0x0400 /* T2P X21 2 port card */ |
| #define T4P_PCI_DEVICE_ID 0x0440 /* T4P X21 4 port card */ |
| |
| |
| /* Default parameters for the link |
| */ |
| #define FST_TX_QUEUE_LEN 100 /* At 8Mbps a longer queue length is |
| * useful, the syncppp module forces |
| * this down assuming a slower line I |
| * guess. |
| */ |
| #define FST_MAX_MTU 8000 /* Huge but possible */ |
| #define FST_DEF_MTU 1500 /* Common sane value */ |
| |
| #define FST_TX_TIMEOUT (2*HZ) |
| |
| |
| #ifdef ARPHRD_RAWHDLC |
| #define ARPHRD_MYTYPE ARPHRD_RAWHDLC /* Raw frames */ |
| #else |
| #define ARPHRD_MYTYPE ARPHRD_HDLC /* Cisco-HDLC (keepalives etc) */ |
| #endif |
| |
| |
| /* Card shared memory layout |
| * ========================= |
| */ |
| #pragma pack(1) |
| |
| /* This information is derived in part from the FarSite FarSync Smc.h |
| * file. Unfortunately various name clashes and the non-portability of the |
| * bit field declarations in that file have meant that I have chosen to |
| * recreate the information here. |
| * |
| * The SMC (Shared Memory Configuration) has a version number that is |
| * incremented every time there is a significant change. This number can |
| * be used to check that we have not got out of step with the firmware |
| * contained in the .CDE files. |
| */ |
| #define SMC_VERSION 11 |
| |
| #define FST_MEMSIZE 0x100000 /* Size of card memory (1Mb) */ |
| |
| #define SMC_BASE 0x00002000L /* Base offset of the shared memory window main |
| * configuration structure */ |
| #define BFM_BASE 0x00010000L /* Base offset of the shared memory window DMA |
| * buffers */ |
| |
| #define LEN_TX_BUFFER 8192 /* Size of packet buffers */ |
| #define LEN_RX_BUFFER 8192 |
| |
| #define LEN_SMALL_TX_BUFFER 256 /* Size of obsolete buffs used for DOS diags */ |
| #define LEN_SMALL_RX_BUFFER 256 |
| |
| #define NUM_TX_BUFFER 2 /* Must be power of 2. Fixed by firmware */ |
| #define NUM_RX_BUFFER 8 |
| |
| /* Interrupt retry time in milliseconds */ |
| #define INT_RETRY_TIME 2 |
| |
| |
| /* The Am186CH/CC processors support a SmartDMA mode using circular pools |
| * of buffer descriptors. The structure is almost identical to that used |
| * in the LANCE Ethernet controllers. Details available as PDF from the |
| * AMD web site: http://www.amd.com/products/epd/processors/\ |
| * 2.16bitcont/3.am186cxfa/a21914/21914.pdf |
| */ |
| struct txdesc { /* Transmit descriptor */ |
| volatile u16 ladr; /* Low order address of packet. This is a |
| * linear address in the Am186 memory space |
| */ |
| volatile u8 hadr; /* High order address. Low 4 bits only, high 4 |
| * bits must be zero |
| */ |
| volatile u8 bits; /* Status and config */ |
| volatile u16 bcnt; /* 2s complement of packet size in low 15 bits. |
| * Transmit terminal count interrupt enable in |
| * top bit. |
| */ |
| u16 unused; /* Not used in Tx */ |
| }; |
| |
| struct rxdesc { /* Receive descriptor */ |
| volatile u16 ladr; /* Low order address of packet */ |
| volatile u8 hadr; /* High order address */ |
| volatile u8 bits; /* Status and config */ |
| volatile u16 bcnt; /* 2s complement of buffer size in low 15 bits. |
| * Receive terminal count interrupt enable in |
| * top bit. |
| */ |
| volatile u16 mcnt; /* Message byte count (15 bits) */ |
| }; |
| |
| /* Convert a length into the 15 bit 2's complement */ |
| /* #define cnv_bcnt(len) (( ~(len) + 1 ) & 0x7FFF ) */ |
| /* Since we need to set the high bit to enable the completion interrupt this |
| * can be made a lot simpler |
| */ |
| #define cnv_bcnt(len) (-(len)) |
| |
| /* Status and config bits for the above */ |
| #define DMA_OWN 0x80 /* SmartDMA owns the descriptor */ |
| #define TX_STP 0x02 /* Tx: start of packet */ |
| #define TX_ENP 0x01 /* Tx: end of packet */ |
| #define RX_ERR 0x40 /* Rx: error (OR of next 4 bits) */ |
| #define RX_FRAM 0x20 /* Rx: framing error */ |
| #define RX_OFLO 0x10 /* Rx: overflow error */ |
| #define RX_CRC 0x08 /* Rx: CRC error */ |
| #define RX_HBUF 0x04 /* Rx: buffer error */ |
| #define RX_STP 0x02 /* Rx: start of packet */ |
| #define RX_ENP 0x01 /* Rx: end of packet */ |
| |
| |
| /* Interrupts from the card are caused by various events and these are presented |
| * in a circular buffer as several events may be processed on one physical int |
| */ |
| #define MAX_CIRBUFF 32 |
| |
| struct cirbuff { |
| u8 rdindex; /* read, then increment and wrap */ |
| u8 wrindex; /* write, then increment and wrap */ |
| u8 evntbuff[MAX_CIRBUFF]; |
| }; |
| |
| /* Interrupt event codes. |
| * Where appropriate the two low order bits indicate the port number |
| */ |
| #define CTLA_CHG 0x18 /* Control signal changed */ |
| #define CTLB_CHG 0x19 |
| #define CTLC_CHG 0x1A |
| #define CTLD_CHG 0x1B |
| |
| #define INIT_CPLT 0x20 /* Initialisation complete */ |
| #define INIT_FAIL 0x21 /* Initialisation failed */ |
| |
| #define ABTA_SENT 0x24 /* Abort sent */ |
| #define ABTB_SENT 0x25 |
| #define ABTC_SENT 0x26 |
| #define ABTD_SENT 0x27 |
| |
| #define TXA_UNDF 0x28 /* Transmission underflow */ |
| #define TXB_UNDF 0x29 |
| #define TXC_UNDF 0x2A |
| #define TXD_UNDF 0x2B |
| |
| |
| /* Port physical configuration. See farsync.h for field values */ |
| struct port_cfg { |
| u16 lineInterface; /* Physical interface type */ |
| u8 x25op; /* Unused at present */ |
| u8 internalClock; /* 1 => internal clock, 0 => external */ |
| u32 lineSpeed; /* Speed in bps */ |
| }; |
| |
| /* Finally sling all the above together into the shared memory structure. |
| * Sorry it's a hodge podge of arrays, structures and unused bits, it's been |
| * evolving under NT for some time so I guess we're stuck with it. |
| * The structure starts at offset SMC_BASE. |
| * See farsync.h for some field values. |
| */ |
| struct fst_shared { |
| /* DMA descriptor rings */ |
| struct rxdesc rxDescrRing[FST_MAX_PORTS][NUM_RX_BUFFER]; |
| struct txdesc txDescrRing[FST_MAX_PORTS][NUM_TX_BUFFER]; |
| |
| /* Obsolete small buffers */ |
| u8 smallRxBuffer[FST_MAX_PORTS][NUM_RX_BUFFER][LEN_SMALL_RX_BUFFER]; |
| u8 smallTxBuffer[FST_MAX_PORTS][NUM_TX_BUFFER][LEN_SMALL_TX_BUFFER]; |
| |
| u8 taskStatus; /* 0x00 => initialising, 0x01 => running, |
| * 0xFF => halted |
| */ |
| |
| u8 interruptHandshake; /* Set to 0x01 by adapter to signal interrupt, |
| * set to 0xEE by host to acknowledge interrupt |
| */ |
| |
| u16 smcVersion; /* Must match SMC_VERSION */ |
| |
| u32 smcFirmwareVersion; /* 0xIIVVRRBB where II = product ID, VV = major |
| * version, RR = revision and BB = build |
| */ |
| |
| u16 txa_done; /* Obsolete completion flags */ |
| u16 rxa_done; |
| u16 txb_done; |
| u16 rxb_done; |
| u16 txc_done; |
| u16 rxc_done; |
| u16 txd_done; |
| u16 rxd_done; |
| |
| u16 mailbox[4]; /* Diagnostics mailbox. Not used */ |
| |
| struct cirbuff interruptEvent; /* interrupt causes */ |
| |
| u32 v24IpSts[FST_MAX_PORTS]; /* V.24 control input status */ |
| u32 v24OpSts[FST_MAX_PORTS]; /* V.24 control output status */ |
| |
| struct port_cfg portConfig[FST_MAX_PORTS]; |
| |
| u16 clockStatus[FST_MAX_PORTS]; /* lsb: 0=> present, 1=> absent */ |
| |
| u16 cableStatus; /* lsb: 0=> present, 1=> absent */ |
| |
| u16 txDescrIndex[FST_MAX_PORTS]; /* transmit descriptor ring index */ |
| u16 rxDescrIndex[FST_MAX_PORTS]; /* receive descriptor ring index */ |
| |
| u16 portMailbox[FST_MAX_PORTS][2]; /* command, modifier */ |
| u16 cardMailbox[4]; /* Not used */ |
| |
| /* Number of times that card thinks the host has |
| * missed an interrupt by not acknowledging |
| * within 2mS (I guess NT has problems) |
| */ |
| u32 interruptRetryCount; |
| |
| /* Driver private data used as an ID. We'll not |
| * use this on Linux I'd rather keep such things |
| * in main memory rather than on the PCI bus |
| */ |
| u32 portHandle[FST_MAX_PORTS]; |
| |
| /* Count of Tx underflows for stats */ |
| u32 transmitBufferUnderflow[FST_MAX_PORTS]; |
| |
| /* Debounced V.24 control input status */ |
| u32 v24DebouncedSts[FST_MAX_PORTS]; |
| |
| /* Adapter debounce timers. Don't touch */ |
| u32 ctsTimer[FST_MAX_PORTS]; |
| u32 ctsTimerRun[FST_MAX_PORTS]; |
| u32 dcdTimer[FST_MAX_PORTS]; |
| u32 dcdTimerRun[FST_MAX_PORTS]; |
| |
| u32 numberOfPorts; /* Number of ports detected at startup */ |
| |
| u16 _reserved[64]; |
| |
| u16 cardMode; /* Bit-mask to enable features: |
| * Bit 0: 1 enables LED identify mode |
| */ |
| |
| u16 portScheduleOffset; |
| |
| u32 endOfSmcSignature; /* endOfSmcSignature MUST be the last member of |
| * the structure and marks the end of the shared |
| * memory. Adapter code initializes its value as |
| * END_SIG. |
| */ |
| }; |
| |
| /* endOfSmcSignature value */ |
| #define END_SIG 0x12345678 |
| |
| /* Mailbox values. (portMailbox) */ |
| #define NOP 0 /* No operation */ |
| #define ACK 1 /* Positive acknowledgement to PC driver */ |
| #define NAK 2 /* Negative acknowledgement to PC driver */ |
| #define STARTPORT 3 /* Start an HDLC port */ |
| #define STOPPORT 4 /* Stop an HDLC port */ |
| #define ABORTTX 5 /* Abort the transmitter for a port */ |
| #define SETV24O 6 /* Set V24 outputs */ |
| |
| |
| /* Larger buffers are positioned in memory at offset BFM_BASE */ |
| struct buf_window { |
| u8 txBuffer[FST_MAX_PORTS][NUM_TX_BUFFER][LEN_TX_BUFFER]; |
| u8 rxBuffer[FST_MAX_PORTS][NUM_RX_BUFFER][LEN_RX_BUFFER]; |
| }; |
| |
| /* Calculate offset of a buffer object within the shared memory window */ |
| #define BUF_OFFSET(X) ((unsigned int)&(((struct buf_window *)BFM_BASE)->X)) |
| |
| #pragma pack() |
| |
| |
| /* Device driver private information |
| * ================================= |
| */ |
| /* Per port (line or channel) information |
| */ |
| struct fst_port_info { |
| void *if_ptr; /* Some drivers describe this as a |
| * general purpose pointer. However if |
| * using syncPPP it has a very specific |
| * purpose: it must be the first item in |
| * the structure pointed to by dev->priv |
| * and must in turn point to the |
| * associated ppp_device structure. |
| */ |
| struct fst_card_info *card; /* Card we're associated with */ |
| int index; /* Port index on the card */ |
| int proto; /* Protocol we are running */ |
| int hwif; /* Line hardware (lineInterface copy) */ |
| int run; /* Port is running */ |
| int rxpos; /* Next Rx buffer to use */ |
| int txpos; /* Next Tx buffer to use */ |
| int txipos; /* Next Tx buffer to check for free */ |
| int txcnt; /* Count of Tx buffers in use */ |
| struct net_device *dev; /* Kernel network device entry */ |
| struct net_device_stats stats; /* Standard statistics */ |
| struct ppp_device pppdev; /* Link to syncPPP */ |
| }; |
| |
| /* Per card information |
| */ |
| struct fst_card_info { |
| char *mem; /* Card memory mapped to kernel space */ |
| char *ctlmem; /* Control memory for PCI cards */ |
| unsigned int phys_mem; /* Physical memory window address */ |
| unsigned int phys_ctlmem; /* Physical control memory address */ |
| unsigned int irq; /* Interrupt request line number */ |
| unsigned int nports; /* Number of serial ports */ |
| unsigned int type; /* Type index of card */ |
| unsigned int state; /* State of card */ |
| spinlock_t card_lock; /* Lock for SMP access */ |
| unsigned short pci_conf; /* PCI card config in I/O space */ |
| /* Per port info */ |
| struct fst_port_info ports[ FST_MAX_PORTS ]; |
| }; |
| |
| |
| /* |
| * Shared memory window access macros |
| * |
| * We have a nice memory based structure above, which could be directly |
| * mapped on i386 but might not work on other architectures unless we use |
| * the readb,w,l and writeb,w,l macros. Unfortunately these macros take |
| * physical offsets so we have to convert. The only saving grace is that |
| * this should all collapse back to a simple indirection eventually. |
| */ |
| #define WIN_OFFSET(X) ((long)&(((struct fst_shared *)SMC_BASE)->X)) |
| |
| #define FST_RDB(C,E) readb ((C)->mem + WIN_OFFSET(E)) |
| #define FST_RDW(C,E) readw ((C)->mem + WIN_OFFSET(E)) |
| #define FST_RDL(C,E) readl ((C)->mem + WIN_OFFSET(E)) |
| |
| #define FST_WRB(C,E,B) writeb ((B), (C)->mem + WIN_OFFSET(E)) |
| #define FST_WRW(C,E,W) writew ((W), (C)->mem + WIN_OFFSET(E)) |
| #define FST_WRL(C,E,L) writel ((L), (C)->mem + WIN_OFFSET(E)) |
| |
| |
| /* |
| * Debug support |
| */ |
| #if FST_DEBUG |
| |
| static int fst_debug_mask = { FST_DEBUG }; |
| |
| /* Most common debug activity is to print something if the corresponding bit |
| * is set in the debug mask. Note: this uses a non-ANSI extension in GCC to |
| * support variable numbers of macro parameters. The inverted if prevents us |
| * eating someone else's else clause. |
| */ |
| #define dbg(F,fmt,A...) if ( ! ( fst_debug_mask & (F))) \ |
| ; \ |
| else \ |
| printk ( KERN_DEBUG FST_NAME ": " fmt, ## A ) |
| |
| #else |
| # define dbg(X...) /* NOP */ |
| #endif |
| |
| |
| /* Printing short cuts |
| */ |
| #define printk_err(fmt,A...) printk ( KERN_ERR FST_NAME ": " fmt, ## A ) |
| #define printk_warn(fmt,A...) printk ( KERN_WARNING FST_NAME ": " fmt, ## A ) |
| #define printk_info(fmt,A...) printk ( KERN_INFO FST_NAME ": " fmt, ## A ) |
| |
| |
| /* |
| * PCI ID lookup table |
| */ |
| static struct pci_device_id fst_pci_dev_id[] __devinitdata = { |
| { FSC_PCI_VENDOR_ID, T2P_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, |
| FST_TYPE_T2P }, |
| { FSC_PCI_VENDOR_ID, T4P_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, |
| FST_TYPE_T4P }, |
| { 0, } /* End */ |
| }; |
| |
| MODULE_DEVICE_TABLE ( pci, fst_pci_dev_id ); |
| |
| |
| /* Card control functions |
| * ====================== |
| */ |
| /* Place the processor in reset state |
| * |
| * Used to be a simple write to card control space but a glitch in the latest |
| * AMD Am186CH processor means that we now have to do it by asserting and de- |
| * asserting the PLX chip PCI Adapter Software Reset. Bit 30 in CNTRL register |
| * at offset 0x50. |
| */ |
| static inline void |
| fst_cpureset ( struct fst_card_info *card ) |
| { |
| unsigned int regval; |
| |
| regval = inl ( card->pci_conf + 0x50 ); |
| |
| outl ( regval | 0x40000000, card->pci_conf + 0x50 ); |
| outl ( regval & ~0x40000000, card->pci_conf + 0x50 ); |
| } |
| |
| /* Release the processor from reset |
| */ |
| static inline void |
| fst_cpurelease ( struct fst_card_info *card ) |
| { |
| (void) readb ( card->ctlmem ); |
| } |
| |
| /* Clear the cards interrupt flag |
| */ |
| static inline void |
| fst_clear_intr ( struct fst_card_info *card ) |
| { |
| /* Poke the appropriate PLX chip register (same as enabling interrupts) |
| */ |
| outw ( 0x0543, card->pci_conf + 0x4C ); |
| } |
| |
| /* Disable card interrupts |
| */ |
| static inline void |
| fst_disable_intr ( struct fst_card_info *card ) |
| { |
| outw ( 0x0000, card->pci_conf + 0x4C ); |
| } |
| |
| |
| /* Issue a Mailbox command for a port. |
| * Note we issue them on a fire and forget basis, not expecting to see an |
| * error and not waiting for completion. |
| */ |
| static void |
| fst_issue_cmd ( struct fst_port_info *port, unsigned short cmd ) |
| { |
| struct fst_card_info *card; |
| unsigned short mbval; |
| unsigned long flags; |
| int safety; |
| |
| card = port->card; |
| spin_lock_irqsave ( &card->card_lock, flags ); |
| mbval = FST_RDW ( card, portMailbox[port->index][0]); |
| |
| safety = 0; |
| /* Wait for any previous command to complete */ |
| while ( mbval > NAK ) |
| { |
| spin_unlock_irqrestore ( &card->card_lock, flags ); |
| schedule_timeout ( 1 ); |
| spin_lock_irqsave ( &card->card_lock, flags ); |
| |
| if ( ++safety > 1000 ) |
| { |
| printk_err ("Mailbox safety timeout\n"); |
| break; |
| } |
| |
| mbval = FST_RDW ( card, portMailbox[port->index][0]); |
| } |
| if ( safety > 0 ) |
| { |
| dbg ( DBG_CMD,"Mailbox clear after %d jiffies\n", safety ); |
| } |
| if ( mbval == NAK ) |
| { |
| dbg ( DBG_CMD,"issue_cmd: previous command was NAK'd\n"); |
| } |
| |
| FST_WRW ( card, portMailbox[port->index][0], cmd ); |
| |
| if ( cmd == ABORTTX || cmd == STARTPORT ) |
| { |
| port->txpos = 0; |
| port->txipos = 0; |
| port->txcnt = 0; |
| } |
| |
| spin_unlock_irqrestore ( &card->card_lock, flags ); |
| } |
| |
| |
| /* Port output signals control |
| */ |
| static inline void |
| fst_op_raise ( struct fst_port_info *port, unsigned int outputs ) |
| { |
| outputs |= FST_RDL ( port->card, v24OpSts[port->index]); |
| FST_WRL ( port->card, v24OpSts[port->index], outputs ); |
| |
| if ( port->run ) |
| fst_issue_cmd ( port, SETV24O ); |
| } |
| |
| static inline void |
| fst_op_lower ( struct fst_port_info *port, unsigned int outputs ) |
| { |
| outputs = ~outputs & FST_RDL ( port->card, v24OpSts[port->index]); |
| FST_WRL ( port->card, v24OpSts[port->index], outputs ); |
| |
| if ( port->run ) |
| fst_issue_cmd ( port, SETV24O ); |
| } |
| |
| |
| /* |
| * Setup port Rx buffers |
| */ |
| static void |
| fst_rx_config ( struct fst_port_info *port ) |
| { |
| int i; |
| int pi; |
| unsigned int offset; |
| unsigned long flags; |
| struct fst_card_info *card; |
| |
| pi = port->index; |
| card = port->card; |
| spin_lock_irqsave ( &card->card_lock, flags ); |
| for ( i = 0 ; i < NUM_RX_BUFFER ; i++ ) |
| { |
| offset = BUF_OFFSET ( rxBuffer[pi][i][0]); |
| |
| FST_WRW ( card, rxDescrRing[pi][i].ladr, (u16) offset ); |
| FST_WRB ( card, rxDescrRing[pi][i].hadr, (u8)( offset >> 16 )); |
| FST_WRW ( card, rxDescrRing[pi][i].bcnt, |
| cnv_bcnt ( LEN_RX_BUFFER )); |
| FST_WRW ( card, rxDescrRing[pi][i].mcnt, 0 ); |
| FST_WRB ( card, rxDescrRing[pi][i].bits, DMA_OWN ); |
| } |
| port->rxpos = 0; |
| spin_unlock_irqrestore ( &card->card_lock, flags ); |
| } |
| |
| |
| /* |
| * Setup port Tx buffers |
| */ |
| static void |
| fst_tx_config ( struct fst_port_info *port ) |
| { |
| int i; |
| int pi; |
| unsigned int offset; |
| unsigned long flags; |
| struct fst_card_info *card; |
| |
| pi = port->index; |
| card = port->card; |
| spin_lock_irqsave ( &card->card_lock, flags ); |
| for ( i = 0 ; i < NUM_TX_BUFFER ; i++ ) |
| { |
| offset = BUF_OFFSET ( txBuffer[pi][i][0]); |
| |
| FST_WRW ( card, txDescrRing[pi][i].ladr, (u16) offset ); |
| FST_WRB ( card, txDescrRing[pi][i].hadr, (u8)( offset >> 16 )); |
| FST_WRW ( card, txDescrRing[pi][i].bcnt, 0 ); |
| FST_WRB ( card, txDescrRing[pi][i].bits, 0 ); |
| } |
| port->txpos = 0; |
| port->txipos = 0; |
| port->txcnt = 0; |
| spin_unlock_irqrestore ( &card->card_lock, flags ); |
| } |
| |
| |
| /* Control signal change interrupt event |
| */ |
| static void |
| fst_intr_ctlchg ( struct fst_card_info *card, struct fst_port_info *port ) |
| { |
| int signals; |
| |
| signals = FST_RDL ( card, v24DebouncedSts[port->index]); |
| |
| if ( signals & (( port->hwif == X21 ) ? IPSTS_INDICATE : IPSTS_DCD )) |
| { |
| if ( ! netif_carrier_ok ( port->dev )) |
| { |
| dbg ( DBG_INTR,"DCD active\n"); |
| |
| /* Poke sPPP to renegotiate */ |
| if ( port->proto == FST_HDLC || port->proto == FST_PPP ) |
| { |
| sppp_reopen ( port->dev ); |
| } |
| |
| netif_carrier_on ( port->dev ); |
| } |
| } |
| else |
| { |
| if ( netif_carrier_ok ( port->dev )) |
| { |
| dbg ( DBG_INTR,"DCD lost\n"); |
| netif_carrier_off ( port->dev ); |
| } |
| } |
| } |
| |
| |
| /* Rx complete interrupt |
| */ |
| static void |
| fst_intr_rx ( struct fst_card_info *card, struct fst_port_info *port ) |
| { |
| unsigned char dmabits; |
| int pi; |
| int rxp; |
| unsigned short len; |
| struct sk_buff *skb; |
| int i; |
| |
| |
| /* Check we have a buffer to process */ |
| pi = port->index; |
| rxp = port->rxpos; |
| dmabits = FST_RDB ( card, rxDescrRing[pi][rxp].bits ); |
| if ( dmabits & DMA_OWN ) |
| { |
| dbg ( DBG_RX | DBG_INTR,"intr_rx: No buffer port %d pos %d\n", |
| pi, rxp ); |
| return; |
| } |
| |
| /* Get buffer length */ |
| len = FST_RDW ( card, rxDescrRing[pi][rxp].mcnt ); |
| /* Discard the CRC */ |
| len -= 2; |
| |
| /* Check buffer length and for other errors. We insist on one packet |
| * in one buffer. This simplifies things greatly and since we've |
| * allocated 8K it shouldn't be a real world limitation |
| */ |
| dbg ( DBG_RX,"intr_rx: %d,%d: flags %x len %d\n", pi, rxp, dmabits, |
| len ); |
| if ( dmabits != ( RX_STP | RX_ENP ) || len > LEN_RX_BUFFER - 2 ) |
| { |
| port->stats.rx_errors++; |
| |
| /* Update error stats and discard buffer */ |
| if ( dmabits & RX_OFLO ) |
| { |
| port->stats.rx_fifo_errors++; |
| } |
| if ( dmabits & RX_CRC ) |
| { |
| port->stats.rx_crc_errors++; |
| } |
| if ( dmabits & RX_FRAM ) |
| { |
| port->stats.rx_frame_errors++; |
| } |
| if ( dmabits == ( RX_STP | RX_ENP )) |
| { |
| port->stats.rx_length_errors++; |
| } |
| |
| /* Discard buffer descriptors until we see the end of packet |
| * marker |
| */ |
| i = 0; |
| while (( dmabits & ( DMA_OWN | RX_ENP )) == 0 ) |
| { |
| FST_WRB ( card, rxDescrRing[pi][rxp].bits, DMA_OWN ); |
| if ( ++rxp >= NUM_RX_BUFFER ) |
| rxp = 0; |
| if ( ++i > NUM_RX_BUFFER ) |
| { |
| dbg ( DBG_ASS,"intr_rx: Discarding more bufs" |
| " than we have\n"); |
| break; |
| } |
| dmabits = FST_RDB ( card, rxDescrRing[pi][rxp].bits ); |
| } |
| |
| /* Discard the terminal buffer */ |
| if ( ! ( dmabits & DMA_OWN )) |
| { |
| FST_WRB ( card, rxDescrRing[pi][rxp].bits, DMA_OWN ); |
| if ( ++rxp >= NUM_RX_BUFFER ) |
| rxp = 0; |
| } |
| port->rxpos = rxp; |
| return; |
| } |
| |
| /* Allocate SKB */ |
| if (( skb = dev_alloc_skb ( len )) == NULL ) |
| { |
| dbg ( DBG_RX,"intr_rx: can't allocate buffer\n"); |
| |
| port->stats.rx_dropped++; |
| |
| /* Return descriptor to card */ |
| FST_WRB ( card, rxDescrRing[pi][rxp].bits, DMA_OWN ); |
| |
| if ( ++rxp >= NUM_RX_BUFFER ) |
| port->rxpos = 0; |
| else |
| port->rxpos = rxp; |
| return; |
| } |
| |
| memcpy_fromio ( skb_put ( skb, len ), |
| card->mem + BUF_OFFSET ( rxBuffer[pi][rxp][0]), |
| len ); |
| |
| /* Reset buffer descriptor */ |
| FST_WRB ( card, rxDescrRing[pi][rxp].bits, DMA_OWN ); |
| if ( ++rxp >= NUM_RX_BUFFER ) |
| port->rxpos = 0; |
| else |
| port->rxpos = rxp; |
| |
| /* Update stats */ |
| port->stats.rx_packets++; |
| port->stats.rx_bytes += len; |
| |
| /* Push upstream */ |
| if ( port->proto == FST_HDLC || port->proto == FST_PPP ) |
| { |
| /* Mark for further processing by sPPP module */ |
| skb->protocol = htons ( ETH_P_WAN_PPP ); |
| } |
| else |
| { |
| /* DEC customer specific protocol (since nothing defined for |
| * marking raw data), at least one other driver uses this value |
| * for this purpose. |
| */ |
| skb->protocol = htons ( ETH_P_CUST ); |
| skb->pkt_type = PACKET_HOST; |
| } |
| skb->mac.raw = skb->data; |
| skb->dev = port->dev; |
| netif_rx ( skb ); |
| |
| port->dev->last_rx = jiffies; |
| } |
| |
| |
| /* |
| * The interrupt service routine |
| * Dev_id is our fst_card_info pointer |
| */ |
| static void |
| fst_intr ( int irq, void *dev_id, struct pt_regs *regs ) |
| { |
| struct fst_card_info *card; |
| struct fst_port_info *port; |
| int rdidx; /* Event buffer indices */ |
| int wridx; |
| int event; /* Actual event for processing */ |
| int pi; |
| |
| if (( card = dev_id ) == NULL ) |
| { |
| dbg ( DBG_INTR,"intr: spurious %d\n", irq ); |
| return; |
| } |
| |
| dbg ( DBG_INTR,"intr: %d %p\n", irq, card ); |
| |
| spin_lock ( &card->card_lock ); |
| |
| /* Clear and reprime the interrupt source */ |
| fst_clear_intr ( card ); |
| |
| /* Set the software acknowledge */ |
| FST_WRB ( card, interruptHandshake, 0xEE ); |
| |
| /* Drain the event queue */ |
| rdidx = FST_RDB ( card, interruptEvent.rdindex ); |
| wridx = FST_RDB ( card, interruptEvent.wrindex ); |
| while ( rdidx != wridx ) |
| { |
| event = FST_RDB ( card, interruptEvent.evntbuff[rdidx]); |
| |
| port = &card->ports[event & 0x03]; |
| |
| dbg ( DBG_INTR,"intr: %x\n", event ); |
| |
| switch ( event ) |
| { |
| case CTLA_CHG: |
| case CTLB_CHG: |
| case CTLC_CHG: |
| case CTLD_CHG: |
| if ( port->run && port->dev != NULL ) |
| fst_intr_ctlchg ( card, port ); |
| break; |
| |
| case ABTA_SENT: |
| case ABTB_SENT: |
| case ABTC_SENT: |
| case ABTD_SENT: |
| dbg ( DBG_TX,"Abort complete port %d\n", event & 0x03 ); |
| break; |
| |
| case TXA_UNDF: |
| case TXB_UNDF: |
| case TXC_UNDF: |
| case TXD_UNDF: |
| /* Difficult to see how we'd get this given that we |
| * always load up the entire packet for DMA. |
| */ |
| dbg ( DBG_TX,"Tx underflow port %d\n", event & 0x03 ); |
| |
| port->stats.tx_errors++; |
| port->stats.tx_fifo_errors++; |
| break; |
| |
| case INIT_CPLT: |
| dbg ( DBG_INIT,"Card init OK intr\n"); |
| break; |
| |
| case INIT_FAIL: |
| dbg ( DBG_INIT,"Card init FAILED intr\n"); |
| card->state = FST_IFAILED; |
| break; |
| |
| default: |
| printk_err ("intr: unknown card event code. ignored\n"); |
| break; |
| } |
| |
| /* Bump and wrap the index */ |
| if ( ++rdidx >= MAX_CIRBUFF ) |
| rdidx = 0; |
| } |
| FST_WRB ( card, interruptEvent.rdindex, rdidx ); |
| |
| for ( pi = 0, port = card->ports ; pi < card->nports ; pi++, port++ ) |
| { |
| if ( port->dev == NULL || ! port->run ) |
| continue; |
| |
| /* Check for rx completions */ |
| while ( ! ( FST_RDB ( card, rxDescrRing[pi][port->rxpos].bits ) |
| & DMA_OWN )) |
| { |
| fst_intr_rx ( card, port ); |
| } |
| |
| /* Check for Tx completions */ |
| while ( port->txcnt > 0 && ! ( FST_RDB ( card, |
| txDescrRing[pi][port->txipos].bits ) & DMA_OWN )) |
| { |
| --port->txcnt; |
| if ( ++port->txipos >= NUM_TX_BUFFER ) |
| port->txipos = 0; |
| netif_wake_queue ( port->dev ); |
| } |
| } |
| |
| spin_unlock ( &card->card_lock ); |
| } |
| |
| |
| /* Check that the shared memory configuration is one that we can handle |
| * and that some basic parameters are correct |
| */ |
| static void |
| check_started_ok ( struct fst_card_info *card ) |
| { |
| int i; |
| |
| /* Check structure version and end marker */ |
| if ( FST_RDW ( card, smcVersion ) != SMC_VERSION ) |
| { |
| printk_err ("Bad shared memory version %d expected %d\n", |
| FST_RDW ( card, smcVersion ), SMC_VERSION ); |
| card->state = FST_BADVERSION; |
| return; |
| } |
| if ( FST_RDL ( card, endOfSmcSignature ) != END_SIG ) |
| { |
| printk_err ("Missing shared memory signature\n"); |
| card->state = FST_BADVERSION; |
| return; |
| } |
| /* Firmware status flag, 0x00 = initialising, 0x01 = OK, 0xFF = fail */ |
| if (( i = FST_RDB ( card, taskStatus )) == 0x01 ) |
| { |
| card->state = FST_RUNNING; |
| } |
| else if ( i == 0xFF ) |
| { |
| printk_err ("Firmware initialisation failed. Card halted\n"); |
| card->state = FST_HALTED; |
| return; |
| } |
| else if ( i != 0x00 ) |
| { |
| printk_err ("Unknown firmware status 0x%x\n", i ); |
| card->state = FST_HALTED; |
| return; |
| } |
| |
| /* Finally check the number of ports reported by firmware against the |
| * number we assumed at card detection. Should never happen with |
| * existing firmware etc so we just report it for the moment. |
| */ |
| if ( FST_RDL ( card, numberOfPorts ) != card->nports ) |
| { |
| printk_warn ("Port count mismatch." |
| " Firmware thinks %d we say %d\n", |
| FST_RDL ( card, numberOfPorts ), card->nports ); |
| } |
| } |
| |
| |
| static int |
| fst_change_mtu ( struct net_device *dev, int new_mtu ) |
| { |
| if ( new_mtu < 128 || new_mtu > FST_MAX_MTU ) |
| return -EINVAL; |
| dev->mtu = new_mtu; |
| return 0; |
| } |
| |
| |
| /* Sooner or later you can't avoid a forward declaration */ |
| static int fst_ioctl ( struct net_device *dev, struct ifreq *ifr, int cmd ); |
| |
| static int |
| switch_proto ( struct fst_port_info *port, int new_proto ) |
| { |
| int err; |
| int orig_mtu; |
| struct net_device *dev; |
| |
| dev = port->dev; |
| |
| /* Turn off sPPP module ? */ |
| if (( new_proto != FST_HDLC && new_proto != FST_PPP ) |
| && ( port->proto == FST_HDLC || port->proto == FST_PPP )) |
| { |
| sppp_close ( port->pppdev.dev ); |
| sppp_detach ( port->pppdev.dev ); |
| |
| /* Reset some fields overwritten by sPPP */ |
| dev->hard_header = NULL; |
| dev->rebuild_header = NULL; |
| dev->tx_queue_len = FST_TX_QUEUE_LEN; |
| dev->type = ARPHRD_MYTYPE; |
| dev->addr_len = 0; |
| dev->hard_header_len = 0; |
| dev->do_ioctl = fst_ioctl; |
| dev->change_mtu = fst_change_mtu; |
| dev->flags = IFF_POINTOPOINT|IFF_NOARP; |
| |
| return 0; |
| } |
| |
| /* Turn on sPPP ? */ |
| if (( new_proto == FST_HDLC || new_proto == FST_PPP ) |
| && ( port->proto != FST_HDLC && port->proto != FST_PPP )) |
| { |
| orig_mtu = dev->mtu; |
| |
| /* Attach to sync PPP module */ |
| port->pppdev.dev = dev; |
| sppp_attach ( &port->pppdev ); |
| |
| if ( orig_mtu < dev->mtu ) |
| dev->change_mtu ( dev, orig_mtu ); |
| |
| /* Claw back the ioctl routine. We promise to be good and call |
| * the sync PPP routines once we've eliminated our functions. |
| */ |
| dev->do_ioctl = fst_ioctl; |
| |
| /* Set the mode */ |
| if ( new_proto == FST_HDLC ) |
| { |
| err = sppp_do_ioctl ( port->pppdev.dev, NULL, |
| SPPPIOCCISCO ); |
| } |
| else |
| { |
| err = sppp_do_ioctl ( port->pppdev.dev, NULL, |
| SPPPIOCPPP ); |
| } |
| |
| /* Open the device */ |
| if ( err == 0 ) |
| { |
| (void) sppp_open ( port->dev ); |
| } |
| |
| return err; |
| } |
| |
| /* Switch sPPP mode to that desired */ |
| err = 0; |
| if ( new_proto == FST_HDLC && port->pppdev.dev != NULL ) |
| { |
| err = sppp_do_ioctl ( port->pppdev.dev, NULL, SPPPIOCCISCO ); |
| } |
| else if ( new_proto == FST_PPP && port->pppdev.dev != NULL ) |
| { |
| err = sppp_do_ioctl ( port->pppdev.dev, NULL, SPPPIOCPPP ); |
| } |
| |
| /* Anything else is switching from one raw mode to another which is |
| * basically a NOP |
| */ |
| |
| return err; |
| } |
| |
| |
| static int |
| set_conf_from_info ( struct fst_card_info *card, struct fst_port_info *port, |
| struct fstioc_info *info ) |
| { |
| int err; |
| |
| /* Set things according to the user set valid flags */ |
| err = 0; |
| if ( info->valid & FSTVAL_PROTO ) |
| { |
| if ( port->proto != info->proto ) |
| { |
| err = switch_proto ( port, info->proto ); |
| if ( err == 0 ) |
| port->proto = info->proto; |
| } |
| } |
| if ( info->valid & FSTVAL_CABLE ) |
| { |
| FST_WRB ( card, portConfig[port->index].lineInterface, |
| info->lineInterface ); |
| port->hwif = info->lineInterface; |
| } |
| if ( info->valid & FSTVAL_SPEED ) |
| { |
| FST_WRL ( card, portConfig[port->index].lineSpeed, |
| info->lineSpeed ); |
| FST_WRB ( card, portConfig[port->index].internalClock, |
| info->internalClock ); |
| } |
| if ( info->valid & FSTVAL_MODE ) |
| { |
| FST_WRW ( card, cardMode, info->cardMode ); |
| } |
| #if FST_DEBUG |
| if ( info->valid & FSTVAL_DEBUG ) |
| { |
| fst_debug_mask = info->debug; |
| } |
| #endif |
| |
| return err; |
| } |
| |
| static void |
| gather_conf_info ( struct fst_card_info *card, struct fst_port_info *port, |
| struct fstioc_info *info ) |
| { |
| int i; |
| |
| memset ( info, 0, sizeof ( struct fstioc_info )); |
| |
| i = port->index; |
| info->nports = card->nports; |
| info->type = card->type; |
| info->state = card->state; |
| info->proto = port->proto; |
| info->index = i; |
| #if FST_DEBUG |
| info->debug = fst_debug_mask; |
| #endif |
| |
| /* Only mark information as valid if card is running. |
| * Copy the data anyway in case it is useful for diagnostics |
| */ |
| info->valid |
| = (( card->state == FST_RUNNING ) ? FSTVAL_ALL : FSTVAL_CARD ) |
| #if FST_DEBUG |
| | FSTVAL_DEBUG |
| #endif |
| ; |
| |
| info->lineInterface = FST_RDW ( card, portConfig[i].lineInterface ); |
| info->internalClock = FST_RDB ( card, portConfig[i].internalClock ); |
| info->lineSpeed = FST_RDL ( card, portConfig[i].lineSpeed ); |
| info->v24IpSts = FST_RDL ( card, v24IpSts[i] ); |
| info->v24OpSts = FST_RDL ( card, v24OpSts[i] ); |
| info->clockStatus = FST_RDW ( card, clockStatus[i] ); |
| info->cableStatus = FST_RDW ( card, cableStatus ); |
| info->cardMode = FST_RDW ( card, cardMode ); |
| info->smcFirmwareVersion = FST_RDL ( card, smcFirmwareVersion ); |
| } |
| |
| |
| static int |
| fst_ioctl ( struct net_device *dev, struct ifreq *ifr, int cmd ) |
| { |
| struct fst_card_info *card; |
| struct fst_port_info *port; |
| struct fstioc_write wrthdr; |
| struct fstioc_info info; |
| unsigned long flags; |
| |
| dbg ( DBG_IOCTL,"ioctl: %x, %p\n", cmd, ifr->ifr_data ); |
| |
| port = dev->priv; |
| card = port->card; |
| |
| if ( !capable ( CAP_NET_ADMIN )) |
| return -EPERM; |
| |
| switch ( cmd ) |
| { |
| case FSTCPURESET: |
| fst_cpureset ( card ); |
| card->state = FST_RESET; |
| return 0; |
| |
| case FSTCPURELEASE: |
| fst_cpurelease ( card ); |
| card->state = FST_STARTING; |
| return 0; |
| |
| case FSTWRITE: /* Code write (download) */ |
| |
| /* First copy in the header with the length and offset of data |
| * to write |
| */ |
| if ( ifr->ifr_data == NULL ) |
| { |
| return -EINVAL; |
| } |
| if ( copy_from_user ( &wrthdr, ifr->ifr_data, |
| sizeof ( struct fstioc_write ))) |
| { |
| return -EFAULT; |
| } |
| |
| /* Sanity check the parameters. We don't support partial writes |
| * when going over the top |
| */ |
| if ( wrthdr.size > FST_MEMSIZE || wrthdr.offset > FST_MEMSIZE |
| || wrthdr.size + wrthdr.offset > FST_MEMSIZE ) |
| { |
| return -ENXIO; |
| } |
| |
| /* Now copy the data to the card. |
| * This will probably break on some architectures. |
| * I'll fix it when I have something to test on. |
| */ |
| if ( copy_from_user ( card->mem + wrthdr.offset, |
| ifr->ifr_data + sizeof ( struct fstioc_write ), |
| wrthdr.size )) |
| { |
| return -EFAULT; |
| } |
| |
| /* Writes to the memory of a card in the reset state constitute |
| * a download |
| */ |
| if ( card->state == FST_RESET ) |
| { |
| card->state = FST_DOWNLOAD; |
| } |
| return 0; |
| |
| case FSTGETCONF: |
| |
| /* If card has just been started check the shared memory config |
| * version and marker |
| */ |
| if ( card->state == FST_STARTING ) |
| { |
| check_started_ok ( card ); |
| |
| /* If everything checked out enable card interrupts */ |
| if ( card->state == FST_RUNNING ) |
| { |
| spin_lock_irqsave ( &card->card_lock, flags ); |
| fst_clear_intr ( card ); |
| FST_WRB ( card, interruptHandshake, 0xEE ); |
| spin_unlock_irqrestore ( &card->card_lock, |
| flags ); |
| } |
| } |
| |
| if ( ifr->ifr_data == NULL ) |
| { |
| return -EINVAL; |
| } |
| |
| gather_conf_info ( card, port, &info ); |
| |
| if ( copy_to_user ( ifr->ifr_data, &info, sizeof ( info ))) |
| { |
| return -EFAULT; |
| } |
| return 0; |
| |
| case FSTSETCONF: |
| |
| if ( copy_from_user ( &info, ifr->ifr_data, sizeof ( info ))) |
| { |
| return -EFAULT; |
| } |
| |
| return set_conf_from_info ( card, port, &info ); |
| |
| default: |
| /* Not one of ours. Pass it through to sPPP package */ |
| if ( port->proto == FST_HDLC || port->proto == FST_PPP ) |
| return sppp_do_ioctl ( dev, ifr, cmd ); |
| else |
| return -EINVAL; |
| } |
| } |
| |
| |
| static void |
| fst_openport ( struct fst_port_info *port ) |
| { |
| int signals; |
| |
| /* Only init things if card is actually running. This allows open to |
| * succeed for downloads etc. |
| */ |
| if ( port->card->state == FST_RUNNING ) |
| { |
| if ( port->run ) |
| { |
| dbg ( DBG_OPEN,"open: found port already running\n"); |
| |
| fst_issue_cmd ( port, STOPPORT ); |
| port->run = 0; |
| } |
| |
| fst_rx_config ( port ); |
| fst_tx_config ( port ); |
| fst_op_raise ( port, OPSTS_RTS | OPSTS_DTR ); |
| |
| fst_issue_cmd ( port, STARTPORT ); |
| port->run = 1; |
| |
| signals = FST_RDL ( port->card, v24DebouncedSts[port->index]); |
| if ( signals & (( port->hwif == X21 ) ? IPSTS_INDICATE |
| : IPSTS_DCD )) |
| netif_carrier_on ( port->dev ); |
| else |
| netif_carrier_off ( port->dev ); |
| } |
| } |
| |
| static void |
| fst_closeport ( struct fst_port_info *port ) |
| { |
| if ( port->card->state == FST_RUNNING ) |
| { |
| if ( port->run ) |
| { |
| port->run = 0; |
| fst_op_lower ( port, OPSTS_RTS | OPSTS_DTR ); |
| |
| fst_issue_cmd ( port, STOPPORT ); |
| } |
| else |
| { |
| dbg ( DBG_OPEN,"close: port not running\n"); |
| } |
| } |
| } |
| |
| |
| static int |
| fst_open ( struct net_device *dev ) |
| { |
| struct fst_card_info *card; |
| struct fst_port_info *port; |
| int orig_mtu; |
| int err; |
| |
| MOD_INC_USE_COUNT; |
| |
| port = dev->priv; |
| card = port->card; |
| |
| switch ( port->proto ) |
| { |
| case FST_HDLC: |
| case FST_PPP: |
| |
| orig_mtu = dev->mtu; |
| |
| /* Attach to sync PPP module */ |
| port->pppdev.dev = dev; |
| sppp_attach ( &port->pppdev ); |
| |
| if ( orig_mtu < dev->mtu ) |
| dev->change_mtu ( dev, orig_mtu ); |
| |
| /* Claw back the ioctl routine. We promise to be good and call |
| * the sync PPP routines once we've eliminated our functions. |
| */ |
| dev->do_ioctl = fst_ioctl; |
| |
| err = sppp_do_ioctl ( dev, NULL, port->proto == FST_HDLC |
| ? SPPPIOCCISCO : SPPPIOCPPP ); |
| if ( err ) |
| { |
| sppp_detach ( dev ); |
| MOD_DEC_USE_COUNT; |
| return err; |
| } |
| |
| err = sppp_open ( dev ); |
| if ( err ) |
| { |
| sppp_detach ( dev ); |
| MOD_DEC_USE_COUNT; |
| return err; |
| } |
| break; |
| |
| case FST_MONITOR: |
| case FST_RAW: |
| break; |
| |
| default: |
| dbg ( DBG_OPEN,"open: Unknown proto %d\n", port->proto ); |
| break; |
| } |
| |
| fst_openport ( port ); |
| |
| netif_wake_queue ( dev ); |
| return 0; |
| } |
| |
| static int |
| fst_close ( struct net_device *dev ) |
| { |
| struct fst_port_info *port; |
| |
| port = dev->priv; |
| |
| netif_stop_queue ( dev ); |
| switch ( port->proto ) |
| { |
| case FST_HDLC: |
| case FST_PPP: |
| sppp_close ( dev ); |
| sppp_detach ( dev ); |
| break; |
| |
| case FST_MONITOR: |
| case FST_RAW: |
| break; |
| |
| default: |
| dbg ( DBG_OPEN,"close: Unknown proto %d\n", port->proto ); |
| break; |
| } |
| |
| fst_closeport ( port ); |
| |
| MOD_DEC_USE_COUNT; |
| return 0; |
| } |
| |
| |
| static void |
| fst_tx_timeout ( struct net_device *dev ) |
| { |
| struct fst_port_info *port; |
| |
| dbg ( DBG_INTR | DBG_TX,"tx_timeout\n"); |
| |
| port = dev->priv; |
| |
| port->stats.tx_errors++; |
| port->stats.tx_aborted_errors++; |
| |
| if ( port->txcnt > 0 ) |
| fst_issue_cmd ( port, ABORTTX ); |
| |
| dev->trans_start = jiffies; |
| netif_wake_queue ( dev ); |
| } |
| |
| |
| static int |
| fst_start_xmit ( struct sk_buff *skb, struct net_device *dev ) |
| { |
| struct fst_card_info *card; |
| struct fst_port_info *port; |
| unsigned char dmabits; |
| unsigned long flags; |
| int pi; |
| int txp; |
| |
| port = dev->priv; |
| card = port->card; |
| |
| /* Drop packet if in monitor (rx only) mode */ |
| if ( port->proto == FST_MONITOR ) |
| { |
| dev_kfree_skb ( skb ); |
| return 0; |
| } |
| |
| /* Drop packet with error if we don't have carrier */ |
| if ( ! netif_carrier_ok ( dev )) |
| { |
| dev_kfree_skb ( skb ); |
| port->stats.tx_errors++; |
| port->stats.tx_carrier_errors++; |
| return 0; |
| } |
| |
| /* Drop it if it's too big! MTU failure ? */ |
| if ( skb->len > LEN_TX_BUFFER ) |
| { |
| dbg ( DBG_TX,"Packet too large %d vs %d\n", skb->len, |
| LEN_TX_BUFFER ); |
| dev_kfree_skb ( skb ); |
| port->stats.tx_errors++; |
| return 0; |
| } |
| |
| /* Check we have a buffer */ |
| pi = port->index; |
| spin_lock_irqsave ( &card->card_lock, flags ); |
| txp = port->txpos; |
| dmabits = FST_RDB ( card, txDescrRing[pi][txp].bits ); |
| if ( dmabits & DMA_OWN ) |
| { |
| spin_unlock_irqrestore ( &card->card_lock, flags ); |
| dbg ( DBG_TX,"Out of Tx buffers\n"); |
| dev_kfree_skb ( skb ); |
| port->stats.tx_errors++; |
| return 0; |
| } |
| if ( ++port->txpos >= NUM_TX_BUFFER ) |
| port->txpos = 0; |
| |
| if ( ++port->txcnt >= NUM_TX_BUFFER ) |
| netif_stop_queue ( dev ); |
| |
| /* Release the card lock before we copy the data as we now have |
| * exclusive access to the buffer. |
| */ |
| spin_unlock_irqrestore ( &card->card_lock, flags ); |
| |
| /* Enqueue the packet */ |
| memcpy_toio ( card->mem + BUF_OFFSET ( txBuffer[pi][txp][0]), |
| skb->data, skb->len ); |
| FST_WRW ( card, txDescrRing[pi][txp].bcnt, cnv_bcnt ( skb->len )); |
| FST_WRB ( card, txDescrRing[pi][txp].bits, DMA_OWN | TX_STP | TX_ENP ); |
| |
| port->stats.tx_packets++; |
| port->stats.tx_bytes += skb->len; |
| |
| dev_kfree_skb ( skb ); |
| |
| dev->trans_start = jiffies; |
| return 0; |
| } |
| |
| |
| static struct net_device_stats * |
| fst_get_stats ( struct net_device *dev ) |
| { |
| struct fst_port_info *port; |
| |
| if (( port = dev->priv ) != NULL ) |
| return &port->stats; |
| else |
| return NULL; |
| } |
| |
| |
| /* |
| * Card setup having checked hardware resources. |
| * Should be pretty bizarre if we get an error here (kernel memory |
| * exhaustion is one possibility). If we do see a problem we report it |
| * via a printk and leave the corresponding interface and all that follow |
| * disabled. |
| */ |
| static char *type_strings[] __devinitdata = { |
| "no hardware", /* Should never be seen */ |
| "FarSync T2P", |
| "FarSync T4P" |
| }; |
| |
| static void __devinit |
| fst_init_card ( struct fst_card_info *card ) |
| { |
| int i; |
| int err; |
| struct net_device *dev; |
| |
| /* We're working on a number of ports based on the card ID. If the |
| * firmware detects something different later (should never happen) |
| * we'll have to revise it in some way then. |
| */ |
| for ( i = 0 ; i < card->nports ; i++ ) |
| { |
| card->ports[i].if_ptr = &card->ports[i].pppdev; |
| card->ports[i].card = card; |
| card->ports[i].index = i; |
| card->ports[i].proto = FST_HDLC; |
| card->ports[i].run = 0; |
| |
| dev = kmalloc ( sizeof ( struct net_device ), GFP_KERNEL ); |
| if ( dev == NULL ) |
| { |
| printk_err ("Cannot allocate net_device for port %d\n", |
| i ); |
| /* No point going any further */ |
| card->nports = i; |
| break; |
| } |
| memset ( dev, 0, sizeof ( struct net_device )); |
| card->ports[i].dev = dev; |
| |
| if ( dev_alloc_name ( dev, FST_NDEV_NAME "%d") < 0 ) |
| { |
| printk_err ("Cannot allocate i/f name for port %d\n", |
| i ); |
| kfree ( dev ); |
| card->nports = i; |
| break; |
| } |
| |
| /* Fill in remainder of the net device info */ |
| /* Since this is a PCI setup this is purely |
| * informational. Give them the buffer addresses |
| * and basic card I/O. |
| */ |
| dev->mem_start = card->phys_mem |
| + BUF_OFFSET ( txBuffer[i][0][0]); |
| dev->mem_end = card->phys_mem |
| + BUF_OFFSET ( txBuffer[i][NUM_TX_BUFFER][0]); |
| dev->rmem_start = card->phys_mem |
| + BUF_OFFSET ( rxBuffer[i][0][0]); |
| dev->rmem_end = card->phys_mem |
| + BUF_OFFSET ( rxBuffer[i][NUM_RX_BUFFER][0]); |
| dev->base_addr = card->pci_conf; |
| dev->irq = card->irq; |
| |
| dev->get_stats = fst_get_stats; |
| dev->mtu = FST_DEF_MTU; |
| dev->change_mtu = fst_change_mtu; |
| dev->priv = &card->ports[i]; |
| dev->tx_queue_len = FST_TX_QUEUE_LEN; |
| dev->type = ARPHRD_MYTYPE; |
| dev->addr_len = 0; |
| dev->open = fst_open; |
| dev->stop = fst_close; |
| dev->hard_start_xmit = fst_start_xmit; |
| dev->do_ioctl = fst_ioctl; |
| dev->watchdog_timeo = FST_TX_TIMEOUT; |
| dev->tx_timeout = fst_tx_timeout; |
| dev->flags = IFF_POINTOPOINT|IFF_NOARP; |
| |
| if (( err = register_netdev ( dev )) < 0 ) |
| { |
| printk_err ("Cannot register %s (errno %d)\n", |
| dev->name, -err ); |
| kfree ( dev ); |
| card->nports = i; |
| break; |
| } |
| } |
| |
| spin_lock_init ( &card->card_lock ); |
| |
| printk ( KERN_INFO "%s-%s: %s IRQ%d, %d ports\n", |
| card->ports[0].dev->name, |
| card->ports[card->nports-1].dev->name, |
| type_strings[card->type], card->irq, card->nports ); |
| } |
| |
| |
| /* |
| * Initialise card when detected. |
| * Returns 0 to indicate success, or errno otherwise. |
| */ |
| static int __devinit |
| fst_add_one ( struct pci_dev *pdev, const struct pci_device_id *ent ) |
| { |
| static int firsttime_done = 0; |
| struct fst_card_info *card; |
| int err = 0; |
| |
| if ( ! firsttime_done ) |
| { |
| printk ( KERN_INFO "FarSync X21 driver " FST_USER_VERSION |
| " (c) 2001 FarSite Communications Ltd.\n"); |
| firsttime_done = 1; |
| } |
| |
| /* Allocate driver private data */ |
| card = kmalloc ( sizeof ( struct fst_card_info ), GFP_KERNEL); |
| if (card == NULL) |
| { |
| printk_err ("FarSync card found but insufficient memory for" |
| " driver storage\n"); |
| return -ENOMEM; |
| } |
| memset ( card, 0, sizeof ( struct fst_card_info )); |
| |
| /* Record info we need*/ |
| card->irq = pdev->irq; |
| card->pci_conf = pci_resource_start ( pdev, 1 ); |
| card->phys_mem = pci_resource_start ( pdev, 2 ); |
| card->phys_ctlmem = pci_resource_start ( pdev, 3 ); |
| |
| card->type = ent->driver_data; |
| card->nports = ( ent->driver_data == FST_TYPE_T2P ) ? 2 : 4; |
| |
| card->state = FST_UNINIT; |
| |
| dbg ( DBG_PCI,"type %d nports %d irq %d\n", card->type, |
| card->nports, card->irq ); |
| dbg ( DBG_PCI,"conf %04x mem %08x ctlmem %08x\n", |
| card->pci_conf, card->phys_mem, card->phys_ctlmem ); |
| |
| /* Check we can get access to the memory and I/O regions */ |
| if ( ! request_region ( card->pci_conf, 0x80,"PLX config regs")) |
| { |
| printk_err ("Unable to get config I/O @ 0x%04X\n", |
| card->pci_conf ); |
| err = -ENODEV; |
| goto error_free_card; |
| } |
| if ( ! request_mem_region ( card->phys_mem, FST_MEMSIZE,"Shared RAM")) |
| { |
| printk_err ("Unable to get main memory @ 0x%08X\n", |
| card->phys_mem ); |
| err = -ENODEV; |
| goto error_release_io; |
| } |
| if ( ! request_mem_region ( card->phys_ctlmem, 0x10,"Control memory")) |
| { |
| printk_err ("Unable to get control memory @ 0x%08X\n", |
| card->phys_ctlmem ); |
| err = -ENODEV; |
| goto error_release_mem; |
| } |
| |
| /* Try to enable the device */ |
| if (( err = pci_enable_device ( pdev )) != 0 ) |
| { |
| printk_err ("Failed to enable card. Err %d\n", -err ); |
| goto error_release_ctlmem; |
| } |
| |
| /* Get virtual addresses of memory regions */ |
| if (( card->mem = ioremap ( card->phys_mem, FST_MEMSIZE )) == NULL ) |
| { |
| printk_err ("Physical memory remap failed\n"); |
| err = -ENODEV; |
| goto error_release_ctlmem; |
| } |
| if (( card->ctlmem = ioremap ( card->phys_ctlmem, 0x10 )) == NULL ) |
| { |
| printk_err ("Control memory remap failed\n"); |
| err = -ENODEV; |
| goto error_unmap_mem; |
| } |
| dbg ( DBG_PCI,"kernel mem %p, ctlmem %p\n", card->mem, card->ctlmem); |
| |
| /* Reset the card's processor */ |
| fst_cpureset ( card ); |
| card->state = FST_RESET; |
| |
| /* Register the interrupt handler */ |
| if ( request_irq ( card->irq, fst_intr, SA_SHIRQ, FST_DEV_NAME, card )) |
| { |
| |
| printk_err ("Unable to register interrupt %d\n", card->irq ); |
| err = -ENODEV; |
| goto error_unmap_ctlmem; |
| } |
| |
| /* Record driver data for later use */ |
| pci_set_drvdata(pdev, card); |
| |
| /* Remainder of card setup */ |
| fst_init_card ( card ); |
| |
| return 0; /* Success */ |
| |
| |
| /* Failure. Release resources */ |
| error_unmap_ctlmem: |
| iounmap ( card->ctlmem ); |
| |
| error_unmap_mem: |
| iounmap ( card->mem ); |
| |
| error_release_ctlmem: |
| release_mem_region ( card->phys_ctlmem, 0x10 ); |
| |
| error_release_mem: |
| release_mem_region ( card->phys_mem, FST_MEMSIZE ); |
| |
| error_release_io: |
| release_region ( card->pci_conf, 0x80 ); |
| |
| error_free_card: |
| kfree ( card ); |
| return err; |
| } |
| |
| |
| /* |
| * Cleanup and close down a card |
| */ |
| static void __devexit |
| fst_remove_one ( struct pci_dev *pdev ) |
| { |
| struct fst_card_info *card; |
| int i; |
| |
| card = pci_get_drvdata(pdev); |
| |
| for ( i = 0 ; i < card->nports ; i++ ) |
| { |
| unregister_netdev ( card->ports[i].dev ); |
| kfree ( card->ports[i].dev ); |
| } |
| |
| fst_disable_intr ( card ); |
| free_irq ( card->irq, card ); |
| |
| iounmap ( card->ctlmem ); |
| iounmap ( card->mem ); |
| |
| release_mem_region ( card->phys_ctlmem, 0x10 ); |
| release_mem_region ( card->phys_mem, FST_MEMSIZE ); |
| release_region ( card->pci_conf, 0x80 ); |
| |
| kfree ( card ); |
| } |
| |
| static struct pci_driver fst_driver = { |
| name: FST_NAME, |
| id_table: fst_pci_dev_id, |
| probe: fst_add_one, |
| remove: fst_remove_one, |
| suspend: NULL, |
| resume: NULL, |
| }; |
| |
| static int __init |
| fst_init(void) |
| { |
| return pci_module_init ( &fst_driver ); |
| } |
| |
| static void __exit |
| fst_cleanup_module(void) |
| { |
| pci_unregister_driver ( &fst_driver ); |
| } |
| |
| module_init ( fst_init ); |
| module_exit ( fst_cleanup_module ); |
| |
| MODULE_LICENSE("GPL"); |