📄 3c574_cs.c
字号:
kio_addr_t ioaddr = dev->base_addr; struct el3_private *lp = netdev_priv(dev); unsigned long flags; DEBUG(3, "%s: el3_start_xmit(length = %ld) called, " "status %4.4x.\n", dev->name, (long)skb->len, inw(ioaddr + EL3_STATUS)); spin_lock_irqsave(&lp->window_lock, flags); outw(skb->len, ioaddr + TX_FIFO); outw(0, ioaddr + TX_FIFO); outsl(ioaddr + TX_FIFO, skb->data, (skb->len+3)>>2); dev->trans_start = jiffies; /* TxFree appears only in Window 1, not offset 0x1c. */ if (inw(ioaddr + TxFree) <= 1536) { netif_stop_queue(dev); /* Interrupt us when the FIFO has room for max-sized packet. The threshold is in units of dwords. */ outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); } pop_tx_status(dev); spin_unlock_irqrestore(&lp->window_lock, flags); dev_kfree_skb(skb); return 0;}/* The EL3 interrupt handler. */static irqreturn_t el3_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct net_device *dev = (struct net_device *) dev_id; struct el3_private *lp = netdev_priv(dev); kio_addr_t ioaddr; unsigned status; int work_budget = max_interrupt_work; int handled = 0; if (!netif_device_present(dev)) return IRQ_NONE; ioaddr = dev->base_addr; DEBUG(3, "%s: interrupt, status %4.4x.\n", dev->name, inw(ioaddr + EL3_STATUS)); spin_lock(&lp->window_lock); while ((status = inw(ioaddr + EL3_STATUS)) & (IntLatch | RxComplete | RxEarly | StatsFull)) { if (!netif_device_present(dev) || ((status & 0xe000) != 0x2000)) { DEBUG(1, "%s: Interrupt from dead card\n", dev->name); break; } handled = 1; if (status & RxComplete) work_budget = el3_rx(dev, work_budget); if (status & TxAvailable) { DEBUG(3, " TX room bit was handled.\n"); /* There's room in the FIFO for a full-sized packet. */ outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); netif_wake_queue(dev); } if (status & TxComplete) pop_tx_status(dev); if (status & (AdapterFailure | RxEarly | StatsFull)) { /* Handle all uncommon interrupts. */ if (status & StatsFull) update_stats(dev); if (status & RxEarly) { work_budget = el3_rx(dev, work_budget); outw(AckIntr | RxEarly, ioaddr + EL3_CMD); } if (status & AdapterFailure) { u16 fifo_diag; EL3WINDOW(4); fifo_diag = inw(ioaddr + Wn4_FIFODiag); EL3WINDOW(1); printk(KERN_NOTICE "%s: adapter failure, FIFO diagnostic" " register %04x.\n", dev->name, fifo_diag); if (fifo_diag & 0x0400) { /* Tx overrun */ tc574_wait_for_completion(dev, TxReset); outw(TxEnable, ioaddr + EL3_CMD); } if (fifo_diag & 0x2000) { /* Rx underrun */ tc574_wait_for_completion(dev, RxReset); set_rx_mode(dev); outw(RxEnable, ioaddr + EL3_CMD); } outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); } } if (--work_budget < 0) { DEBUG(0, "%s: Too much work in interrupt, " "status %4.4x.\n", dev->name, status); /* Clear all interrupts */ outw(AckIntr | 0xFF, ioaddr + EL3_CMD); break; } /* Acknowledge the IRQ. */ outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); } DEBUG(3, "%s: exiting interrupt, status %4.4x.\n", dev->name, inw(ioaddr + EL3_STATUS)); spin_unlock(&lp->window_lock); return IRQ_RETVAL(handled);}/* This timer serves two purposes: to check for missed interrupts (and as a last resort, poll the NIC for events), and to monitor the MII, reporting changes in cable status.*/static void media_check(unsigned long arg){ struct net_device *dev = (struct net_device *) arg; struct el3_private *lp = netdev_priv(dev); kio_addr_t ioaddr = dev->base_addr; unsigned long flags; unsigned short /* cable, */ media, partner; if (!netif_device_present(dev)) goto reschedule; /* Check for pending interrupt with expired latency timer: with this, we can limp along even if the interrupt is blocked */ if ((inw(ioaddr + EL3_STATUS) & IntLatch) && (inb(ioaddr + Timer) == 0xff)) { if (!lp->fast_poll) printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); el3_interrupt(dev->irq, lp, NULL); lp->fast_poll = HZ; } if (lp->fast_poll) { lp->fast_poll--; lp->media.expires = jiffies + 2*HZ/100; add_timer(&lp->media); return; } spin_lock_irqsave(&lp->window_lock, flags); EL3WINDOW(4); media = mdio_read(ioaddr, lp->phys, 1); partner = mdio_read(ioaddr, lp->phys, 5); EL3WINDOW(1); if (media != lp->media_status) { if ((media ^ lp->media_status) & 0x0004) printk(KERN_INFO "%s: %s link beat\n", dev->name, (lp->media_status & 0x0004) ? "lost" : "found"); if ((media ^ lp->media_status) & 0x0020) { lp->partner = 0; if (lp->media_status & 0x0020) { printk(KERN_INFO "%s: autonegotiation restarted\n", dev->name); } else if (partner) { partner &= lp->advertising; lp->partner = partner; printk(KERN_INFO "%s: autonegotiation complete: " "%sbaseT-%cD selected\n", dev->name, ((partner & 0x0180) ? "100" : "10"), ((partner & 0x0140) ? 'F' : 'H')); } else { printk(KERN_INFO "%s: link partner did not autonegotiate\n", dev->name); } EL3WINDOW(3); outb((partner & 0x0140 ? 0x20 : 0) | (dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl); EL3WINDOW(1); } if (media & 0x0010) printk(KERN_INFO "%s: remote fault detected\n", dev->name); if (media & 0x0002) printk(KERN_INFO "%s: jabber detected\n", dev->name); lp->media_status = media; } spin_unlock_irqrestore(&lp->window_lock, flags);reschedule: lp->media.expires = jiffies + HZ; add_timer(&lp->media);}static struct net_device_stats *el3_get_stats(struct net_device *dev){ struct el3_private *lp = netdev_priv(dev); if (netif_device_present(dev)) { unsigned long flags; spin_lock_irqsave(&lp->window_lock, flags); update_stats(dev); spin_unlock_irqrestore(&lp->window_lock, flags); } return &lp->stats;}/* Update statistics. Suprisingly this need not be run single-threaded, but it effectively is. The counters clear when read, so the adds must merely be atomic. */static void update_stats(struct net_device *dev){ struct el3_private *lp = netdev_priv(dev); kio_addr_t ioaddr = dev->base_addr; u8 rx, tx, up; DEBUG(2, "%s: updating the statistics.\n", dev->name); if (inw(ioaddr+EL3_STATUS) == 0xffff) /* No card. */ return; /* Unlike the 3c509 we need not turn off stats updates while reading. */ /* Switch to the stats window, and read everything. */ EL3WINDOW(6); lp->stats.tx_carrier_errors += inb(ioaddr + 0); lp->stats.tx_heartbeat_errors += inb(ioaddr + 1); /* Multiple collisions. */ inb(ioaddr + 2); lp->stats.collisions += inb(ioaddr + 3); lp->stats.tx_window_errors += inb(ioaddr + 4); lp->stats.rx_fifo_errors += inb(ioaddr + 5); lp->stats.tx_packets += inb(ioaddr + 6); up = inb(ioaddr + 9); lp->stats.tx_packets += (up&0x30) << 4; /* Rx packets */ inb(ioaddr + 7); /* Tx deferrals */ inb(ioaddr + 8); rx = inw(ioaddr + 10); tx = inw(ioaddr + 12); EL3WINDOW(4); /* BadSSD */ inb(ioaddr + 12); up = inb(ioaddr + 13); lp->stats.tx_bytes += tx + ((up & 0xf0) << 12); EL3WINDOW(1);}static int el3_rx(struct net_device *dev, int worklimit){ struct el3_private *lp = netdev_priv(dev); kio_addr_t ioaddr = dev->base_addr; short rx_status; DEBUG(3, "%s: in rx_packet(), status %4.4x, rx_status %4.4x.\n", dev->name, inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus)); while (!((rx_status = inw(ioaddr + RxStatus)) & 0x8000) && (--worklimit >= 0)) { if (rx_status & 0x4000) { /* Error, update stats. */ short error = rx_status & 0x3800; lp->stats.rx_errors++; switch (error) { case 0x0000: lp->stats.rx_over_errors++; break; case 0x0800: lp->stats.rx_length_errors++; break; case 0x1000: lp->stats.rx_frame_errors++; break; case 0x1800: lp->stats.rx_length_errors++; break; case 0x2000: lp->stats.rx_frame_errors++; break; case 0x2800: lp->stats.rx_crc_errors++; break; } } else { short pkt_len = rx_status & 0x7ff; struct sk_buff *skb; skb = dev_alloc_skb(pkt_len+5); DEBUG(3, " Receiving packet size %d status %4.4x.\n", pkt_len, rx_status); if (skb != NULL) { skb->dev = dev; skb_reserve(skb, 2); insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len), ((pkt_len+3)>>2)); skb->protocol = eth_type_trans(skb, dev); netif_rx(skb); dev->last_rx = jiffies; lp->stats.rx_packets++; lp->stats.rx_bytes += pkt_len; } else { DEBUG(1, "%s: couldn't allocate a sk_buff of" " size %d.\n", dev->name, pkt_len); lp->stats.rx_dropped++; } } tc574_wait_for_completion(dev, RxDiscard); } return worklimit;}static void netdev_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info){ strcpy(info->driver, "3c574_cs");}static struct ethtool_ops netdev_ethtool_ops = { .get_drvinfo = netdev_get_drvinfo,};/* Provide ioctl() calls to examine the MII xcvr state. */static int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd){ struct el3_private *lp = netdev_priv(dev); kio_addr_t ioaddr = dev->base_addr; u16 *data = (u16 *)&rq->ifr_ifru; int phy = lp->phys & 0x1f; DEBUG(2, "%s: In ioct(%-.6s, %#4.4x) %4.4x %4.4x %4.4x %4.4x.\n", dev->name, rq->ifr_ifrn.ifrn_name, cmd, data[0], data[1], data[2], data[3]); switch(cmd) { case SIOCGMIIPHY: /* Get the address of the PHY in use. */ data[0] = phy; case SIOCGMIIREG: /* Read the specified MII register. */ { int saved_window; unsigned long flags; spin_lock_irqsave(&lp->window_lock, flags); saved_window = inw(ioaddr + EL3_CMD) >> 13; EL3WINDOW(4); data[3] = mdio_read(ioaddr, data[0] & 0x1f, data[1] & 0x1f); EL3WINDOW(saved_window); spin_unlock_irqrestore(&lp->window_lock, flags); return 0; } case SIOCSMIIREG: /* Write the specified MII register */ { int saved_window; unsigned long flags; if (!capable(CAP_NET_ADMIN)) return -EPERM; spin_lock_irqsave(&lp->window_lock, flags); saved_window = inw(ioaddr + EL3_CMD) >> 13; EL3WINDOW(4); mdio_write(ioaddr, data[0] & 0x1f, data[1] & 0x1f, data[2]); EL3WINDOW(saved_window); spin_unlock_irqrestore(&lp->window_lock, flags); return 0; } default: return -EOPNOTSUPP; }}/* The Odie chip has a 64 bin multicast filter, but the bit layout is not documented. Until it is we revert to receiving all multicast frames when any multicast reception is desired. Note: My other drivers emit a log message whenever promiscuous mode is entered to help detect password sniffers. This is less desirable on typical PC card machines, so we omit the message. */static void set_rx_mode(struct net_device *dev){ kio_addr_t ioaddr = dev->base_addr; if (dev->flags & IFF_PROMISC) outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm, ioaddr + EL3_CMD); else if (dev->mc_count || (dev->flags & IFF_ALLMULTI)) outw(SetRxFilter|RxStation|RxMulticast|RxBroadcast, ioaddr + EL3_CMD); else outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD);}static int el3_close(struct net_device *dev){ kio_addr_t ioaddr = dev->base_addr; struct el3_private *lp = netdev_priv(dev); dev_link_t *link = &lp->link; DEBUG(2, "%s: shutting down ethercard.\n", dev->name); if (DEV_OK(link)) { unsigned long flags; /* Turn off statistics ASAP. We update lp->stats below. */ outw(StatsDisable, ioaddr + EL3_CMD); /* Disable the receiver and transmitter. */ outw(RxDisable, ioaddr + EL3_CMD); outw(TxDisable, ioaddr + EL3_CMD); /* Note: Switching to window 0 may disable the IRQ. */ EL3WINDOW(0); spin_lock_irqsave(&lp->window_lock, flags); update_stats(dev); spin_unlock_irqrestore(&lp->window_lock, flags); } link->open--; netif_stop_queue(dev); del_timer_sync(&lp->media); return 0;}static struct pcmcia_driver tc574_driver = { .owner = THIS_MODULE, .drv = { .name = "3c574_cs", }, .attach = tc574_attach, .detach = tc574_detach,};static int __init init_tc574(void){ return pcmcia_register_driver(&tc574_driver);}static void __exit exit_tc574(void){ pcmcia_unregister_driver(&tc574_driver); BUG_ON(dev_list != NULL);}module_init(init_tc574);module_exit(exit_tc574);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -