📄 smc91x.c
字号:
SMC_SELECT_BANK(0); old_carrier = netif_carrier_ok(dev) ? 1 : 0; new_carrier = SMC_inw(ioaddr, EPH_STATUS_REG) & ES_LINK_OK ? 1 : 0; if (init || (old_carrier != new_carrier)) { if (!new_carrier) { netif_carrier_off(dev); } else { netif_carrier_on(dev); } if (netif_msg_link(lp)) printk(KERN_INFO "%s: link %s\n", dev->name, new_carrier ? "up" : "down"); } SMC_SELECT_BANK(old_bank);}static void smc_eph_interrupt(struct net_device *dev){ unsigned long ioaddr = dev->base_addr; unsigned int old_bank, ctl; smc_10bt_check_media(dev, 0); old_bank = SMC_CURRENT_BANK(); SMC_SELECT_BANK(1); ctl = SMC_GET_CTL(); SMC_SET_CTL(ctl & ~CTL_LE_ENABLE); SMC_SET_CTL(ctl); SMC_SELECT_BANK(old_bank);}/* * This is the main routine of the driver, to handle the device when * it needs some attention. */static irqreturn_t smc_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct net_device *dev = dev_id; unsigned long ioaddr = dev->base_addr; struct smc_local *lp = netdev_priv(dev); int status, mask, timeout, card_stats; int saved_bank, saved_pointer; DBG(3, "%s: %s\n", dev->name, __FUNCTION__); spin_lock(&lp->lock); saved_bank = SMC_CURRENT_BANK(); SMC_SELECT_BANK(2); saved_pointer = SMC_GET_PTR(); mask = SMC_GET_INT_MASK(); SMC_SET_INT_MASK(0); /* set a timeout value, so I don't stay here forever */ timeout = 8; do { status = SMC_GET_INT(); DBG(2, "%s: IRQ 0x%02x MASK 0x%02x MEM 0x%04x FIFO 0x%04x\n", dev->name, status, mask, ({ int meminfo; SMC_SELECT_BANK(0); meminfo = SMC_GET_MIR(); SMC_SELECT_BANK(2); meminfo; }), SMC_GET_FIFO()); status &= mask; if (!status) break; if (status & IM_RCV_INT) { DBG(3, "%s: RX irq\n", dev->name); smc_rcv(dev); } else if (status & IM_TX_INT) { DBG(3, "%s: TX int\n", dev->name); smc_tx(dev); SMC_ACK_INT(IM_TX_INT);#if THROTTLE_TX_PKTS netif_wake_queue(dev);#endif } else if (status & IM_ALLOC_INT) { DBG(3, "%s: Allocation irq\n", dev->name); smc_hardware_send_packet(dev); mask |= (IM_TX_INT | IM_TX_EMPTY_INT); mask &= ~IM_ALLOC_INT;#if ! THROTTLE_TX_PKTS netif_wake_queue(dev);#endif } else if (status & IM_TX_EMPTY_INT) { DBG(3, "%s: TX empty\n", dev->name); mask &= ~IM_TX_EMPTY_INT; /* update stats */ SMC_SELECT_BANK(0); card_stats = SMC_GET_COUNTER(); SMC_SELECT_BANK(2); /* single collisions */ lp->stats.collisions += card_stats & 0xF; card_stats >>= 4; /* multiple collisions */ lp->stats.collisions += card_stats & 0xF; } else if (status & IM_RX_OVRN_INT) { DBG(1, "%s: RX overrun\n", dev->name); SMC_ACK_INT(IM_RX_OVRN_INT); lp->stats.rx_errors++; lp->stats.rx_fifo_errors++; } else if (status & IM_EPH_INT) { smc_eph_interrupt(dev); } else if (status & IM_MDINT) { SMC_ACK_INT(IM_MDINT); smc_phy_interrupt(dev); } else if (status & IM_ERCV_INT) { SMC_ACK_INT(IM_ERCV_INT); PRINTK("%s: UNSUPPORTED: ERCV INTERRUPT \n", dev->name); } } while (--timeout);#if defined(CONFIG_MACH_OMAP_H4) /* HACKALERT!: This requires investigation * The current issue is that during boot up on 2420 H4 Rev b.4, * if root file system is mounted using nfs, this issue is seen. */ udelay(50);#endif /* restore register states */ SMC_SET_INT_MASK(mask); SMC_SET_PTR(saved_pointer); SMC_SELECT_BANK(saved_bank);#if defined(CONFIG_MACH_OMAP_H4) barrier(); // mem bus is way slow, seems like cpu might be getting ahead this plus read should kill it. mask = SMC_GET_INT_MASK();#endif DBG(3, "%s: Interrupt done (%d loops)\n", dev->name, 8-timeout); spin_unlock(&lp->lock); /* * We return IRQ_HANDLED unconditionally here even if there was * nothing to do. There is a possibility that a packet might * get enqueued into the chip right after TX_EMPTY_INT is raised * but just before the CPU acknowledges the IRQ. * Better take an unneeded IRQ in some occasions than complexifying * the code for all cases. */ return IRQ_HANDLED;}/* Our watchdog timed out. Called by the networking layer */static void smc_timeout(struct net_device *dev){ struct smc_local *lp = netdev_priv(dev); unsigned long flags; spin_lock_irqsave(&lp->lock, flags); DBG(2, "%s: %s\n", dev->name, __FUNCTION__); smc_reset(dev); smc_enable(dev);#if 0 /* * Reconfiguring the PHY doesn't seem like a bad idea here, but * it introduced a problem. Now that this is a timeout routine, * we are getting called from within an interrupt context. * smc_phy_configure() calls msleep() which calls * schedule_timeout() which calls schedule(). When schedule() * is called from an interrupt context, it prints out * "Scheduling in interrupt" and then calls BUG(). This is * obviously not desirable. This was worked around by removing * the call to smc_phy_configure() here because it didn't seem * absolutely necessary. Ultimately, if msleep() is * supposed to be usable from an interrupt context (which it * looks like it thinks it should handle), it should be fixed. */ if (lp->phy_type != 0) smc_phy_configure(dev);#endif /* clear anything saved */ if (lp->saved_skb != NULL) { dev_kfree_skb (lp->saved_skb); lp->saved_skb = NULL; lp->stats.tx_errors++; lp->stats.tx_aborted_errors++; } /* We can accept TX packets again */ dev->trans_start = jiffies; spin_unlock_irqrestore(&lp->lock, flags); netif_wake_queue(dev);}/* * This sets the internal hardware table to filter out unwanted multicast * packets before they take up memory. * * The SMC chip uses a hash table where the high 6 bits of the CRC of * address are the offset into the table. If that bit is 1, then the * multicast packet is accepted. Otherwise, it's dropped silently. * * To use the 6 bits as an offset into the table, the high 3 bits are the * number of the 8 bit register, while the low 3 bits are the bit within * that register. * * This routine is based very heavily on the one provided by Peter Cammaert. */static voidsmc_setmulticast(unsigned long ioaddr, int count, struct dev_mc_list *addrs){ int i; unsigned char multicast_table[8]; struct dev_mc_list *cur_addr; /* table for flipping the order of 3 bits */ static unsigned char invert3[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; /* start with a table of all zeros: reject all */ memset(multicast_table, 0, sizeof(multicast_table)); cur_addr = addrs; for (i = 0; i < count; i++, cur_addr = cur_addr->next) { int position; /* do we have a pointer here? */ if (!cur_addr) break; /* make sure this is a multicast address - shouldn't this be a given if we have it here ? */ if (!(*cur_addr->dmi_addr & 1)) continue; /* only use the low order bits */ position = crc32_le(~0, cur_addr->dmi_addr, 6) & 0x3f; /* do some messy swapping to put the bit in the right spot */ multicast_table[invert3[position&7]] |= (1<<invert3[(position>>3)&7]); } /* now, the table can be loaded into the chipset */ SMC_SELECT_BANK(3); SMC_SET_MCAST(multicast_table);}/* * 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){ struct smc_local *lp = netdev_priv(dev); unsigned long ioaddr = dev->base_addr; DBG(2, "%s: %s\n", dev->name, __FUNCTION__); SMC_SELECT_BANK(0); if (dev->flags & IFF_PROMISC) { DBG(2, "%s: RCR_PRMS\n", dev->name); lp->rcr_cur_mode |= RCR_PRMS; SMC_SET_RCR(lp->rcr_cur_mode); }/* 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 || dev->mc_count > 16) { lp->rcr_cur_mode |= RCR_ALMUL; SMC_SET_RCR(lp->rcr_cur_mode); DBG(2, "%s: RCR_ALMUL\n", dev->name); } /* * 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 */ lp->rcr_cur_mode &= ~(RCR_PRMS | RCR_ALMUL); SMC_SET_RCR(lp->rcr_cur_mode); /* * 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(ioaddr, dev->mc_count, dev->mc_list); } else { DBG(2, "%s: ~(RCR_PRMS|RCR_ALMUL)\n", dev->name); lp->rcr_cur_mode &= ~(RCR_PRMS | RCR_ALMUL); SMC_SET_RCR(lp->rcr_cur_mode); /* * since I'm disabling all multicast entirely, I need to * clear the multicast list */ SMC_SELECT_BANK(3); SMC_CLEAR_MCAST(); }}/* * Open and Initialize the board * * Set up everything, reset the card, etc.. */static intsmc_open(struct net_device *dev){ struct smc_local *lp = netdev_priv(dev); unsigned long ioaddr = dev->base_addr; DBG(2, "%s: %s\n", dev->name, __FUNCTION__); /* * Check that the address is valid. If its not, refuse * to bring the device up. The user must specify an * address using ifconfig eth0 hw ether xx:xx:xx:xx:xx:xx */ if (!is_valid_ether_addr(dev->dev_addr)) { DBG(2, "smc_open: no valid ethernet hw addr\n"); return -EINVAL; } /* clear out all the junk that was put here before... */ lp->saved_skb = NULL; /* Setup the default Register Modes */ lp->tcr_cur_mode = TCR_DEFAULT; lp->rcr_cur_mode = RCR_DEFAULT; lp->rpc_cur_mode = RPC_DEFAULT; /* * If we are not using a MII interface, we need to * monitor our own carrier signal to detect faults. */ if (lp->phy_type == 0) lp->tcr_cur_mode |= TCR_MON_CSN; /* reset the hardware */ smc_reset(dev); smc_enable(dev); SMC_SELECT_BANK(1); SMC_SET_MAC_ADDR(dev->dev_addr); /* Configure the PHY */ if (lp->phy_type != 0) smc_phy_configure(dev); else { spin_lock_irq(&lp->lock); smc_10bt_check_media(dev, 1); spin_unlock_irq(&lp->lock); } /* * make sure to initialize the link state with netif_carrier_off() * somewhere, too --jgarzik * * smc_phy_configure() and smc_10bt_check_media() does that. --rmk */ netif_start_queue(dev); return 0;}/* * 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){ struct smc_local *lp = netdev_priv(dev); DBG(2, "%s: %s\n", dev->name, __FUNCTION__); netif_stop_queue(dev); netif_carrier_off(dev); /* clear everything */ smc_shutdown(dev->base_addr); if (lp->phy_type != 0) smc_phy_powerdown(dev, lp->mii.phy_id); 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 = netdev_priv(dev); DBG(2, "%s: %s\n", dev->name, __FUNCTION__); return &lp->stats;}/* * Ethtool support */static intsmc_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd){ struct smc_local *lp = netdev_priv(dev); int ret; cmd->maxtxpkt = 1; cmd->maxrxpkt = 1; if (lp->phy_type != 0) { spin_lock_irq(&lp->lock); ret = mii_ethtool_gset(&lp->mii, cmd); spin_unlock_irq(&lp->lock); } else { cmd->supported = SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | SUPPORTED_TP | SUPPORTED_AUI; if (lp->ctl_rspeed == 10) cmd->speed = SPEED_10; else if (lp->ctl_rspeed == 100) cmd->speed = SPEED_100; cmd->autoneg = AUTONEG_DISABLE; cmd->transceiver = XCVR_INTERNAL; cmd->port = 0; cmd->duplex = lp->tcr_cur_mode & TCR_SWFDUP ? DUPLEX_FULL : DUPLEX_HALF; ret = 0; } return ret;}static intsmc_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd){ struct smc_local *lp = netdev_priv(dev); int ret; if (lp->phy_type != 0) { spin_lock_irq(&lp->lock); ret = mii_ethtool_sset(&lp->mii, cmd); spin_unlock_irq(&lp->lock); } else { if (cmd->autoneg != AUTONEG_DISABLE || cmd->speed != SPEED_10 || (cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL) || (cmd->port != PORT_TP && cmd->port != PORT_AUI)) return -EINVAL;// lp->port = cmd->port; lp->ctl_rfduplx = cmd->duplex == DUPLEX_FULL;// if (netif_running(dev))// smc_set_port(dev); ret = 0; } return ret;}static voidsmc_ethtool_getdrvinfo(struct net_device *dev, struct ethtool_drvinfo *info){ strncpy(info->driver, CARDNAME, sizeof(info->driver)); strncpy(info->version, version, sizeof(info->version)); strncpy(info->bus_info, dev->class_dev.dev->bus_id, sizeof(info->bus_info));}static int smc_ethtool_nwayreset(struct net_device *dev){ struct smc_local *lp = netdev_priv(dev); int ret = -EINVAL; if (lp->phy_type != 0) { spin_lock_irq(&lp->lock); ret = mii_nway_restart(&lp->mii); spin_unlock_irq(&lp->lock); } return ret;}static u32 smc_ethtool_getmsglevel(struct net_device *dev){ struct smc_local *lp = netdev_priv(dev); return lp->msg_enable;}static void smc_ethtool_setmsglevel(struct net_device *dev, u32 level){ struct smc_local *lp = netdev_priv(dev); lp->msg_enable = level;}static struct ethtool_ops smc_ethtool_ops = { .get_settings = smc_ethtool_getsettings, .set_settings = smc_ethtool_setsettings, .get_drvinfo = smc_ethtool_getdrvinfo, .get_msglevel = smc_ethtool_getmsglevel, .set_msglevel = smc_ethtool_setmsglevel, .nway_reset = smc_ethtool_nwayreset, .get_link = ethtool_op_get_link,// .get_eeprom = smc_ethtool_geteeprom,// .set_eeprom = smc_ethtool_seteeprom,};/* * smc_findirq * * This routine has a simple purpose -- make the SMC chip generate an * interrupt, so an auto-detect routine can detect it, and find the IRQ, *//* * does this still work? * * I just deleted auto_irq.c, since it was never built... * --jgarzik */static int __init smc_findirq(unsigned long ioaddr){ int timeout = 20; unsigned long cookie; DBG(2, "%s: %s\n", CARDNAME, __FUNCTION__); cookie = probe_irq_on(); /* * What I try to do here is trigger an ALLOC_INT. This is done * by allocating a small chunk of memory, which will give an interrupt * when done. */ /* enable ALLOCation interrupts ONLY */ SMC_SELECT_BANK(2); SMC_SET_INT_MASK(IM_ALLOC_INT); /* * Allocate 512 bytes of memory. Note that the chip was just * reset so all the memory is available */ SMC_SET_MMU_CMD(MC_ALLOC | 1); /* * Wait until positive that the interrupt has been generated */ do { int int_status; udelay(10); int_status = SMC_GET_INT(); if (int_status & IM_ALLOC_INT) break; /* got the interrupt */ } while (--timeout); /* * there is really nothing that I can do here if timeout fails, * as autoirq_report will return a 0 anyway, which is what I * want in this case. Plus, the clean up is needed in both * cases.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -