📄 smc91x.c
字号:
DBG(2, "%s: phy advertised caps=%x\n", dev->name, my_ad_caps); /* Restart auto-negotiation process in order to advertise my caps */ smc_phy_write(dev, phyaddr, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); smc_phy_check_media(dev, 1);smc_phy_configure_exit: SMC_SELECT_BANK(2); spin_unlock_irq(&lp->lock); lp->work_pending = 0;}/* * smc_phy_interrupt * * Purpose: Handle interrupts relating to PHY register 18. This is * called from the "hard" interrupt handler under our private spinlock. */static void smc_phy_interrupt(struct net_device *dev){ struct smc_local *lp = netdev_priv(dev); int phyaddr = lp->mii.phy_id; int phy18; DBG(2, "%s: %s\n", dev->name, __FUNCTION__); if (lp->phy_type == 0) return; for(;;) { smc_phy_check_media(dev, 0); /* Read PHY Register 18, Status Output */ phy18 = smc_phy_read(dev, phyaddr, PHY_INT_REG); if ((phy18 & PHY_INT_INT) == 0) break; }}/*--- END PHY CONTROL AND CONFIGURATION-------------------------------------*/static void smc_10bt_check_media(struct net_device *dev, int init){ struct smc_local *lp = netdev_priv(dev); void __iomem *ioaddr = lp->base; unsigned int old_carrier, new_carrier; old_carrier = netif_carrier_ok(dev) ? 1 : 0; SMC_SELECT_BANK(0); new_carrier = (SMC_GET_EPH_STATUS() & ES_LINK_OK) ? 1 : 0; SMC_SELECT_BANK(2); 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"); }}static void smc_eph_interrupt(struct net_device *dev){ struct smc_local *lp = netdev_priv(dev); void __iomem *ioaddr = lp->base; unsigned int ctl; smc_10bt_check_media(dev, 0); SMC_SELECT_BANK(1); ctl = SMC_GET_CTL(); SMC_SET_CTL(ctl & ~CTL_LE_ENABLE); SMC_SET_CTL(ctl); SMC_SELECT_BANK(2);}/* * 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 net_device *dev = dev_id; struct smc_local *lp = netdev_priv(dev); void __iomem *ioaddr = lp->base; int status, mask, timeout, card_stats; int saved_pointer; DBG(3, "%s: %s\n", dev->name, __FUNCTION__); spin_lock(&lp->lock); /* A preamble may be used when there is a potential race * between the interruptible transmit functions and this * ISR. */ SMC_INTERRUPT_PREAMBLE; 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 = MAX_IRQ_LOOPS; do { status = SMC_GET_INT(); DBG(2, "%s: INT 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_TX_INT) { /* do this before RX as it will free memory quickly */ 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); } else if (status & IM_RCV_INT) { DBG(3, "%s: RX irq\n", dev->name); smc_rcv(dev); } else if (status & IM_ALLOC_INT) { DBG(3, "%s: Allocation irq\n", dev->name); tasklet_hi_schedule(&lp->tx_task); mask &= ~IM_ALLOC_INT; } 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 */ dev->stats.collisions += card_stats & 0xF; card_stats >>= 4; /* multiple collisions */ dev->stats.collisions += card_stats & 0xF; } else if (status & IM_RX_OVRN_INT) { DBG(1, "%s: RX overrun (EPH_ST 0x%04x)\n", dev->name, ({ int eph_st; SMC_SELECT_BANK(0); eph_st = SMC_GET_EPH_STATUS(); SMC_SELECT_BANK(2); eph_st; }) ); SMC_ACK_INT(IM_RX_OVRN_INT); dev->stats.rx_errors++; dev->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); /* restore register states */ SMC_SET_PTR(saved_pointer); SMC_SET_INT_MASK(mask); spin_unlock(&lp->lock); if (timeout == MAX_IRQ_LOOPS) PRINTK("%s: spurious interrupt (mask = 0x%02x)\n", dev->name, mask); DBG(3, "%s: Interrupt done (%d loops)\n", dev->name, MAX_IRQ_LOOPS - timeout); /* * 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;}#ifdef CONFIG_NET_POLL_CONTROLLER/* * Polling receive - used by netconsole and other diagnostic tools * to allow network i/o with interrupts disabled. */static void smc_poll_controller(struct net_device *dev){ disable_irq(dev->irq); smc_interrupt(dev->irq, dev); enable_irq(dev->irq);}#endif/* Our watchdog timed out. Called by the networking layer */static void smc_timeout(struct net_device *dev){ struct smc_local *lp = netdev_priv(dev); void __iomem *ioaddr = lp->base; int status, mask, eph_st, meminfo, fifo; DBG(2, "%s: %s\n", dev->name, __FUNCTION__); spin_lock_irq(&lp->lock); status = SMC_GET_INT(); mask = SMC_GET_INT_MASK(); fifo = SMC_GET_FIFO(); SMC_SELECT_BANK(0); eph_st = SMC_GET_EPH_STATUS(); meminfo = SMC_GET_MIR(); SMC_SELECT_BANK(2); spin_unlock_irq(&lp->lock); PRINTK( "%s: TX timeout (INT 0x%02x INTMASK 0x%02x " "MEM 0x%04x FIFO 0x%04x EPH_ST 0x%04x)\n", dev->name, status, mask, meminfo, fifo, eph_st ); smc_reset(dev); smc_enable(dev); /* * Reconfiguring the PHY doesn't seem like a bad idea here, but * smc_phy_configure() calls msleep() which calls schedule_timeout() * which calls schedule(). Hence we use a work queue. */ if (lp->phy_type != 0) { if (schedule_work(&lp->phy_configure)) { lp->work_pending = 1; } } /* We can accept TX packets again */ dev->trans_start = jiffies; netif_wake_queue(dev);}/* * 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); void __iomem *ioaddr = lp->base; unsigned char multicast_table[8]; int update_multicast = 0; DBG(2, "%s: %s\n", dev->name, __FUNCTION__); if (dev->flags & IFF_PROMISC) { DBG(2, "%s: RCR_PRMS\n", dev->name); lp->rcr_cur_mode |= RCR_PRMS; }/* 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) { DBG(2, "%s: RCR_ALMUL\n", dev->name); lp->rcr_cur_mode |= RCR_ALMUL; } /* * 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. */ else if (dev->mc_count) { int i; struct dev_mc_list *cur_addr; /* table for flipping the order of 3 bits */ static const 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 = dev->mc_list; for (i = 0; i < dev->mc_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]); } /* be sure I get rid of flags I might have set */ lp->rcr_cur_mode &= ~(RCR_PRMS | RCR_ALMUL); /* now, the table can be loaded into the chipset */ update_multicast = 1; } else { DBG(2, "%s: ~(RCR_PRMS|RCR_ALMUL)\n", dev->name); lp->rcr_cur_mode &= ~(RCR_PRMS | RCR_ALMUL); /* * since I'm disabling all multicast entirely, I need to * clear the multicast list */ memset(multicast_table, 0, sizeof(multicast_table)); update_multicast = 1; } spin_lock_irq(&lp->lock); SMC_SELECT_BANK(0); SMC_SET_RCR(lp->rcr_cur_mode); if (update_multicast) { SMC_SELECT_BANK(3); SMC_SET_MCAST(multicast_table); } SMC_SELECT_BANK(2); spin_unlock_irq(&lp->lock);}/* * 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); 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)) { PRINTK("%s: no valid ethernet hw addr\n", __FUNCTION__); return -EINVAL; } /* 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); /* Configure the PHY, initialize the link state */ if (lp->phy_type != 0) smc_phy_configure(&lp->phy_configure); else { spin_lock_irq(&lp->lock); smc_10bt_check_media(dev, 1); spin_unlock_irq(&lp->lock); } 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); tasklet_kill(&lp->tx_task); smc_phy_powerdown(dev); return 0;}/* * 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->dev.parent->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 const 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(void __iomem *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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -