📄 smc9194.c
字号:
}/* * This is our template. Fill the rest in at run-time */static const struct ethtool_cmd ecmd_template = { supported: SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | SUPPORTED_TP | SUPPORTED_AUI, speed: SPEED_10, autoneg: AUTONEG_DISABLE, maxtxpkt: 1, maxrxpkt: 1, transceiver: XCVR_INTERNAL,};static int smc_ioctl(struct net_device *dev, struct ifreq *rq, int cmd){ struct smc_local *smc = dev->priv; u32 etcmd; int ret = -EINVAL; if (cmd != SIOCETHTOOL) return -EOPNOTSUPP; if (get_user(etcmd, (u32 *)rq->ifr_data)) return -EFAULT; switch (etcmd) { case ETHTOOL_GSET: { struct ethtool_cmd ecmd = ecmd_template; ecmd.cmd = etcmd; ecmd.port = smc->port; ecmd.duplex = smc->duplex; ret = copy_to_user(rq->ifr_data, &ecmd, sizeof(ecmd)) ? -EFAULT : 0; break; } case ETHTOOL_SSET: { struct ethtool_cmd ecmd; ret = -EPERM; if (!capable(CAP_NET_ADMIN)) break; ret = -EFAULT; if (copy_from_user(&ecmd, rq->ifr_data, sizeof(ecmd))) break; /* * Sanity-check the arguments. */ ret = -EINVAL; if (ecmd.autoneg != AUTONEG_DISABLE) break; if (ecmd.speed != SPEED_10) break; if (ecmd.duplex != DUPLEX_HALF && ecmd.duplex != DUPLEX_FULL) break; if (ecmd.port != PORT_TP && ecmd.port != PORT_AUI) break; smc->port = ecmd.port; smc->duplex = ecmd.duplex; if (netif_running(dev)) smc_set_port(dev); ret = 0; break; } case ETHTOOL_GDRVINFO: { struct ethtool_drvinfo edrv; memset(&edrv, 0, sizeof(edrv)); edrv.cmd = etcmd; strcpy(edrv.driver, DRV_NAME); strcpy(edrv.version, DRV_VERSION); sprintf(edrv.bus_info, "ISA:%8.8lx:%d", dev->base_addr, dev->irq); ret = copy_to_user(rq->ifr_data, &edrv, sizeof(edrv)) ? -EFAULT : 0; break; } } return ret;}/*-------------------------------------------------------- . Called by the kernel to send a packet out into the void . of the net. This routine is largely based on . skeleton.c, from Becker. .--------------------------------------------------------*/static void smc_timeout(struct net_device *dev){ /* If we get here, some higher level has decided we are broken. There should really be a "kick me" function call instead. */ printk(KERN_WARNING "%s: transmit timed out\n", dev->name); /* "kick" the adaptor */ smc_reset(dev); smc_enable(dev); dev->trans_start = jiffies; /* clear anything saved */ ((struct smc_local *)dev->priv)->saved_skb = NULL; netif_wake_queue(dev);}/*-------------------------------------------------------------------- . . This is the main routine of the driver, to handle the device when . it needs some attention. . . So: . first, save state of the chipset . branch off into routines to handle each case, and acknowledge . each to the interrupt register . and finally restore state. . ---------------------------------------------------------------------*/static void smc_interrupt(int irq, void * dev_id, struct pt_regs * regs){ struct net_device *dev = dev_id; u_int ioaddr = dev->base_addr; struct smc_local *lp = (struct smc_local *)dev->priv; byte status; word card_stats; byte mask; int timeout; /* state registers */ word saved_bank; word saved_pointer; PRINTK3(("%s: SMC interrupt started\n", dev->name)); saved_bank = smc_inw(ioaddr, BANK_SELECT); SMC_SELECT_BANK(2); saved_pointer = smc_inw(ioaddr, POINTER); mask = smc_inb(ioaddr, INT_MASK); /* clear all interrupts */ SMC_SET_INT(0); /* set a timeout value, so I don't stay here forever */ timeout = 4; PRINTK2((KERN_WARNING "%s: MASK IS %x\n", dev->name, mask)); do { /* read the status flag, and mask it */ status = smc_inb(ioaddr, INTERRUPT) & mask; if (!status) break; PRINTK3((KERN_WARNING "%s: handling interrupt status %x\n", dev->name, status)); if (status & IM_RCV_INT) { /* Got a packet(s). */ PRINTK2((KERN_WARNING "%s: receive interrupt\n", dev->name)); smc_rcv(dev); } else if (status & IM_TX_INT) { PRINTK2((KERN_WARNING "%s: TX ERROR handled\n", dev->name)); smc_tx(dev); smc_outb(IM_TX_INT, ioaddr, INTERRUPT); } else if (status & IM_TX_EMPTY_INT) { /* update stats */ SMC_SELECT_BANK(0); card_stats = smc_inw(ioaddr, COUNTER); /* single collisions */ lp->stats.collisions += card_stats & 0xF; card_stats >>= 4; /* multiple collisions */ lp->stats.collisions += card_stats & 0xF; /* these are for when linux supports these statistics */ SMC_SELECT_BANK(2); PRINTK2((KERN_WARNING "%s: TX_BUFFER_EMPTY handled\n", dev->name)); smc_outb(IM_TX_EMPTY_INT, ioaddr, INTERRUPT); mask &= ~IM_TX_EMPTY_INT; lp->stats.tx_packets += lp->packets_waiting; lp->packets_waiting = 0; } else if (status & IM_ALLOC_INT) { PRINTK2((KERN_DEBUG "%s: Allocation interrupt\n", dev->name)); /* clear this interrupt so it doesn't happen again */ mask &= ~IM_ALLOC_INT; smc_hardware_send_packet(dev); /* enable xmit interrupts based on this */ mask |= (IM_TX_EMPTY_INT | IM_TX_INT); /* and let the card send more packets to me */ netif_wake_queue(dev); PRINTK2(("%s: Handoff done successfully.\n", dev->name)); } else if (status & IM_RX_OVRN_INT) { lp->stats.rx_errors++; lp->stats.rx_fifo_errors++; smc_outb(IM_RX_OVRN_INT, ioaddr, INTERRUPT); } else if (status & IM_EPH_INT) { PRINTK(("%s: UNSUPPORTED: EPH INTERRUPT\n", dev->name)); } else if (status & IM_ERCV_INT) { PRINTK(("%s: UNSUPPORTED: ERCV INTERRUPT\n", dev->name)); smc_outb(IM_ERCV_INT, ioaddr, INTERRUPT); } } while (timeout --); /* restore state register */ SMC_SELECT_BANK(2); SMC_SET_INT(mask); PRINTK3((KERN_WARNING "%s: MASK is now %x\n", dev->name, mask)); smc_outw(saved_pointer, ioaddr, POINTER); SMC_SELECT_BANK(saved_bank); PRINTK3(("%s: Interrupt done\n", dev->name)); return;}/*------------------------------------------------------------- . . smc_rcv - receive a packet from the card . . There is (at least) a packet waiting to be read from . chip-memory. . . o Read the status . o If an error, record it . o otherwise, read in the packet --------------------------------------------------------------*/static void smc_rcv(struct net_device *dev){ struct smc_local *lp = (struct smc_local *)dev->priv; u_int ioaddr = dev->base_addr; int packet_number; word status; word packet_length; /* assume bank 2 */ packet_number = smc_inw(ioaddr, FIFO_PORTS); if (packet_number & FP_RXEMPTY) { /* we got called , but nothing was on the FIFO */ PRINTK(("%s: WARNING: smc_rcv with nothing on FIFO.\n", dev->name)); /* don't need to restore anything */ return; } /* start reading from the start of the packet */ smc_outw(PTR_READ | PTR_RCV | PTR_AUTOINC, ioaddr, POINTER); /* First two words are status and packet_length */ status = smc_inw(ioaddr, DATA_1); packet_length = smc_inw(ioaddr, DATA_1); packet_length &= 0x07ff; /* mask off top bits */ PRINTK2(("RCV: STATUS %4x LENGTH %4x\n", status, packet_length)); /* . the packet length contains 3 extra words : . status, length, and an extra word with an odd byte . */ packet_length -= 6; if (!(status & RS_ERRORS)){ /* do stuff to make a new packet */ struct sk_buff * skb; byte * data; /* read one extra byte */ if (status & RS_ODDFRAME) packet_length++; /* set multicast stats */ if (status & RS_MULTICAST) lp->stats.multicast++; skb = dev_alloc_skb(packet_length + 5); if (skb == NULL) { printk(KERN_NOTICE "%s: Low memory, packet dropped.\n", dev->name); lp->stats.rx_dropped++; goto done; } /* ! This should work without alignment, but it could be ! in the worse case */ skb_reserve(skb, 2); /* 16 bit alignment */ skb->dev = dev; data = skb_put(skb, packet_length); smc_ins(ioaddr, DATA_1, data, packet_length); print_packet(data, packet_length); skb->protocol = eth_type_trans(skb, dev); netif_rx(skb); dev->last_rx = jiffies; lp->stats.rx_packets++; lp->stats.rx_bytes += packet_length; } else { /* error ... */ lp->stats.rx_errors++; if (status & RS_ALGNERR) lp->stats.rx_frame_errors++; if (status & (RS_TOOSHORT | RS_TOOLONG)) lp->stats.rx_length_errors++; if (status & RS_BADCRC) lp->stats.rx_crc_errors++; }done: /* error or good, tell the card to get rid of this packet */ smc_outw(MC_RELEASE, ioaddr, MMU_CMD);}/************************************************************************* . smc_tx . . Purpose: Handle a transmit error message. This will only be called . when an error, because of the AUTO_RELEASE mode. . . Algorithm: . Save pointer and packet no . Get the packet no from the top of the queue . check if it's valid (if not, is this an error???) . read the status word . record the error . (resend? Not really, since we don't want old packets around) . Restore saved values ************************************************************************/static void smc_tx(struct net_device * dev){ u_int ioaddr = dev->base_addr; struct smc_local *lp = (struct smc_local *)dev->priv; byte saved_packet; byte packet_no; word tx_status; /* assume bank 2 */ saved_packet = smc_inb(ioaddr, PNR_ARR); packet_no = smc_inw(ioaddr, FIFO_PORTS); packet_no &= 0x7F; /* select this as the packet to read from */ smc_outb(packet_no, ioaddr, PNR_ARR); /* read the first word from this packet */ smc_outw(PTR_AUTOINC | PTR_READ, ioaddr, POINTER); tx_status = smc_inw(ioaddr, DATA_1); PRINTK3(("%s: TX DONE STATUS: %4x\n", dev->name, tx_status)); lp->stats.tx_errors++; if (tx_status & TS_LOSTCAR) lp->stats.tx_carrier_errors++; if (tx_status & TS_LATCOL) { printk(KERN_DEBUG "%s: Late collision occurred on " "last xmit.\n", dev->name); lp->stats.tx_window_errors++; }#if 0 if (tx_status & TS_16COL) { ... }#endif if (tx_status & TS_SUCCESS) { printk("%s: Successful packet caused interrupt\n", dev->name); } /* re-enable transmit */ SMC_SELECT_BANK(0); smc_outw(smc_inw(ioaddr, TCR) | TCR_ENABLE, ioaddr, TCR); /* kill the packet */ SMC_SELECT_BANK(2); smc_outw(MC_FREEPKT, ioaddr, MMU_CMD); /* one less packet waiting for me */ lp->packets_waiting--; smc_outb(saved_packet, ioaddr, PNR_ARR); return;}/*---------------------------------------------------- . smc_close . . this makes the board clean up everything that it can . and not talk to the outside world. Caused by . an 'ifconfig ethX down' . -----------------------------------------------------*/static int smc_close(struct net_device *dev){ netif_stop_queue(dev); /* clear everything */ smc_shutdown(dev); /* Update the statistics here. */ return 0;}/*------------------------------------------------------------ . Get the current statistics. . This may be called with the card open or closed. .-------------------------------------------------------------*/static struct net_device_stats* smc_query_statistics(struct net_device *dev) { struct smc_local *lp = (struct smc_local *)dev->priv; return &lp->stats;}/*----------------------------------------------------------- . smc_set_multicast_list . . This routine will, depending on the values passed to it, . either make it accept multicast packets, go into . promiscuous mode (for TCPDUMP and cousins) or accept . a select set of multicast packets*/static void smc_set_multicast_list(struct net_device *dev){ u_int ioaddr = dev->base_addr; SMC_SELECT_BANK(0); if (dev->flags & IFF_PROMISC) smc_outw(smc_inw(ioaddr, RCR) | RCR_PROMISC, ioaddr, RCR);/* BUG? I never disable promiscuous mode if multicasting was turned on. Now, I turn off promiscuous mode, but I don't do anything to multicasting when promiscuous mode is turned on.*/ /* Here, I am setting this to accept all multicast packets. I don't need to zero the multicast table, because the flag is checked before the table is */ else if (dev->flags & IFF_ALLMULTI) smc_outw(smc_inw(ioaddr, RCR) | RCR_ALMUL, ioaddr, RCR); /* We just get all multicast packets even if we only want them . from one source. This will be changed at some future . point. */ else if (dev->mc_count) { /* support hardware multicasting */ /* be sure I get rid of flags I might have set */ smc_outw(smc_inw(ioaddr, RCR) & ~(RCR_PROMISC | RCR_ALMUL), ioaddr, RCR); /* NOTE: this has to set the bank, so make sure it is the last thing called. The bank is set to zero at the top */ smc_setmulticast(dev, dev->mc_count, dev->mc_list); } else { smc_outw(smc_inw(ioaddr, RCR) & ~(RCR_PROMISC | RCR_ALMUL), ioaddr, RCR); /* since I'm disabling all multicast entirely, I need to clear the multicast list */ SMC_SELECT_BANK(3); smc_outw(0, ioaddr, MULTICAST1); smc_outw(0, ioaddr, MULTICAST2); smc_outw(0, ioaddr, MULTICAST3); smc_outw(0, ioaddr, MULTICAST4); }}#ifdef MODULEstatic struct net_device devSMC9194;static int io;static int irq;static int ifport;MODULE_LICENSE("GPL");MODULE_PARM(io, "i");MODULE_PARM(irq, "i");MODULE_PARM(ifport, "i");MODULE_PARM_DESC(io, "SMC 99194 I/O base address");MODULE_PARM_DESC(irq, "SMC 99194 IRQ number");MODULE_PARM_DESC(ifport, "SMC 99194 interface port (0-default, 1-TP, 2-AUI)");int init_module(void){ if (io == 0) printk(KERN_WARNING CARDNAME ": You shouldn't use auto-probing with insmod!\n"); /* * Note: dev->if_port has changed to be 2.4 compliant. * We keep the ifport insmod parameter the same though. */ switch (ifport) { case 1: devSMC9194.if_port = IF_PORT_10BASET; break; case 2: devSMC9194.if_port = IF_PORT_AUI; break; default: devSMC9194.if_port = 0; break; } /* copy the parameters from insmod into the device structure */ devSMC9194.base_addr = io; devSMC9194.irq = irq; devSMC9194.init = smc_init; return register_netdev(&devSMC9194);}void cleanup_module(void){ /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ unregister_netdev(&devSMC9194); free_irq(devSMC9194.irq, &devSMC9194); release_region(devSMC9194.base_addr, SMC_IO_EXTENT); if (devSMC9194.priv) kfree(devSMC9194.priv);}#endif /* MODULE */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -