| /* |
| * bgp_e10000_main.c: net_device source for BlueGene/P 10 GbE driver |
| * |
| * Copyright (c) 2007, 2010 International Business Machines |
| * Author: Andrew Tauferner <ataufer@us.ibm.com> |
| * |
| * 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. |
| * |
| */ |
| |
| |
| #include <linux/module.h> |
| #include <linux/errno.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| #include <linux/init.h> |
| #include <linux/types.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/skbuff.h> |
| #include <linux/if_ether.h> |
| #include <asm/reg_booke.h> |
| #include <linux/proc_fs.h> |
| #include <stdarg.h> |
| #include <asm/bluegene_ras.h> |
| #include <asm/bgp_personality.h> |
| #include <asm/bluegene.h> |
| |
| #include "bgp_e10000.h" |
| #include "bgp_emac.h" |
| #include "bgp_tomal.h" |
| |
| |
| static int e10000_change_mtu(struct net_device*, int); |
| static int e10000_do_ioctl(struct net_device*, struct ifreq*, int); |
| static struct net_device_stats* e10000_get_stats(struct net_device*); |
| static int e10000_hard_start_xmit(struct sk_buff*, struct net_device*); |
| static int e10000_open(struct net_device*); |
| //static void e10000_set_multicast_list(struct net_device*); |
| static int e10000_stop(struct net_device*); |
| static void e10000_tx_timeout(struct net_device*); |
| static int e10000_set_mac_address(struct net_device* netDev, void* macAddr); |
| static void e10000_link_test(unsigned long); |
| |
| static struct net_device* e10000NetDev; |
| static struct timer_list e10000LinkTimer; |
| static const struct net_device_ops e10000NetDevOps = { |
| .ndo_open = e10000_open, |
| .ndo_stop = e10000_stop, |
| .ndo_start_xmit = e10000_hard_start_xmit, |
| .ndo_get_stats = e10000_get_stats, |
| .ndo_set_mac_address = e10000_set_mac_address, |
| .ndo_tx_timeout = e10000_tx_timeout, |
| .ndo_change_mtu = e10000_change_mtu, |
| .ndo_do_ioctl = e10000_do_ioctl, |
| }; |
| |
| static BGP_Personality_t bgpers; |
| static void* e10000DevMapAddr; |
| static unsigned int e10000DevMapLen; |
| |
| static int __init |
| e10000_init(void) |
| { |
| int rc = 0; |
| TOMAL* tomal = NULL; |
| EMAC* emac = NULL; |
| struct proc_dir_entry* e10000Dir; |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry\n"); |
| |
| /* Determine if Ethernet HW is present. */ |
| bluegene_getPersonality((void*) &bgpers, sizeof(bgpers)); |
| if (bgpers.Network_Config.RankInPSet) { /* No HW so exit. */ |
| rc = -ENODEV; |
| goto end; |
| } |
| |
| /* Allocate ethernet device(s). */ |
| e10000NetDev = alloc_etherdev(sizeof(EMAC)); |
| if (!e10000NetDev) { |
| e10000_printr(bg_subcomp_linux, e10000_ras_netdev_alloc_failure, |
| "Failure allocating ethernet device."); |
| rc = -ENOMEM; |
| goto end; |
| } |
| |
| /* Create /proc directory. */ |
| e10000Dir = proc_mkdir("driver/e10000", NULL); |
| |
| /* Create mapping for TOMAL and XEMAC devices. Since they are close in memory one mapping with */ |
| /* a small hole in between will cover both. Tell CNS where XEMAC is mapped. */ |
| e10000DevMapLen = XEMAC_BASE_ADDRESS + sizeof(XEMACRegs) - TOMAL_BASE_ADDRESS; |
| e10000DevMapAddr = ioremap(TOMAL_BASE_ADDRESS, e10000DevMapLen); |
| if (!e10000DevMapAddr) { |
| rc = -ENODEV; |
| goto end; |
| } |
| rc = bluegene_mapXEMAC(e10000DevMapAddr+(XEMAC_BASE_ADDRESS - TOMAL_BASE_ADDRESS)); |
| if (rc) { |
| e10000_printr(bg_subcomp_linux, 0xff, "Failure registering XEMAC mapping with CNS."); |
| rc = -ENODEV; |
| goto unmap_dev; |
| } |
| |
| /* Allocate and intialize TOMAL device. */ |
| tomal = tomal_init(e10000DevMapAddr, e10000NetDev, CONFIG_BGP_E10000_RXB, CONFIG_BGP_E10000_TXB, NULL, |
| 0, 0, TOMAL_IRQ0, TOMAL_IRQ1, e10000Dir); |
| if (IS_ERR(tomal)) { |
| rc = (int) tomal; |
| goto unmap_dev; |
| } |
| |
| /* Initialize XEMAC. */ |
| e10000NetDev->irq = XEMAC_IRQ; |
| emac = (EMAC*) netdev_priv(e10000NetDev); |
| rc = emac_init((char*) e10000DevMapAddr + (XEMAC_BASE_ADDRESS - TOMAL_BASE_ADDRESS), emac, EMAC_TYPE_XEMAC, |
| tomal, 0, e10000NetDev, e10000Dir); |
| if (rc) |
| goto free_tomal; |
| |
| /* Initialize network device operations. */ |
| e10000NetDev->netdev_ops = &e10000NetDevOps; |
| |
| /* Register the net_device. */ |
| rc = register_netdev(e10000NetDev); |
| if (rc) { |
| e10000_printr(bg_subcomp_linux, e10000_ras_netdev_reg_failure, |
| "Failure registering net_device [%p].", e10000NetDev); |
| goto exit_emac; |
| } |
| |
| /* Configure EMAC. */ |
| rc = emac_configure(emac); |
| if (rc) { |
| e10000_printr(bg_subcomp_e10000, e10000_ras_emac_config_error, |
| "EMAC configuration error. rc=%d", rc); |
| goto exit_emac; |
| } |
| |
| /* Initialize the timer. */ |
| e10000LinkTimer.function = e10000_link_test; |
| e10000LinkTimer.data = (unsigned int) e10000NetDev; |
| init_timer(&e10000LinkTimer); |
| |
| goto end; |
| |
| exit_emac: |
| emac_exit(emac); |
| free_tomal: |
| tomal_exit(tomal); |
| unmap_dev: |
| iounmap(e10000DevMapAddr); |
| free_netdev(e10000NetDev); |
| end: |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit rc=0x%x\n", rc); |
| |
| return rc; |
| } |
| |
| |
| |
| static int e10000_set_mac_address(struct net_device* netDev, void* macAddr) |
| { |
| int rc = -EINVAL; |
| struct sockaddr* sockAddr = (struct sockaddr*) macAddr; |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - netDev=%p, macAddr=%p\n", |
| netDev, macAddr); |
| |
| if (is_valid_ether_addr(((struct sockaddr*) macAddr)->sa_data)) { |
| EMAC* emac = (EMAC*) netdev_priv(netDev); |
| unsigned long flags; |
| |
| memcpy(netDev->dev_addr, sockAddr->sa_data, netDev->addr_len); |
| |
| spin_lock_irqsave(&emac->lock, flags); |
| rc = emac_set_mac_address(emac); |
| spin_unlock_irqrestore(&emac->lock, flags); |
| } else |
| rc = -EADDRNOTAVAIL; |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit - rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| |
| |
| static int e10000_change_mtu(struct net_device* netDev, |
| int newMTU) |
| { |
| int rc = 0; |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - netDev=%p, newMTU=%d\n", |
| netDev, newMTU); |
| |
| if (newMTU < BGP_E10000_MIN_MTU || newMTU > BGP_E10000_MAX_MTU) { |
| e10000_printr(bg_subcomp_e10000, e10000_ras_mtu_invalid, |
| "Invalid MTU of [%d] specified. Valid MTU " |
| "values are [%d,%d].\n", newMTU, BGP_E10000_MIN_MTU, |
| BGP_E10000_MAX_MTU); |
| rc = -EINVAL; |
| } else if (netDev->mtu != newMTU && netif_running(netDev)) { |
| /* #ifdef CONFIG_BGP_E10000_NAPI */ |
| /* netDev->weight = tomal->maxRxBuffers[channel]; */ |
| /* #endif */ |
| netDev->mtu = newMTU; |
| } |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit - rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| |
| static int e10000_do_ioctl(struct net_device* netDev, |
| struct ifreq* req, |
| int cmd) |
| { |
| int rc = 0; |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - netDev=%p, req=%p, cmd=0x%x\n", |
| netDev, req, cmd); |
| |
| // printk(KERN_CRIT "IOCTL not supported yet\n"); |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit - rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| |
| static struct net_device_stats* e10000_get_stats(struct net_device* netDev) |
| { |
| struct net_device_stats* stats = &((EMAC*) netdev_priv(netDev))->stats; |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - netDev=%p\nexit - stats=%p\n", |
| netDev, stats); |
| |
| return stats; |
| } |
| #ifdef CONFIG_BGP_E10000_DBG |
| int e10000_diag_count ; |
| /* If the 'skb' has fragments ( is a scatter-gather one), display them all and the base element too */ |
| static void diag_display_sk(struct sk_buff* skb) |
| { |
| int nr_frags = skb_shinfo(skb)->nr_frags; |
| if( skb->data_len >= 4096 || |
| e10000_diag_count > 0) |
| { |
| int f ; |
| if( e10000_diag_count > 0 ) e10000_diag_count -= 1 ; |
| printk(KERN_INFO "diag_display_sk skb=%p nr_frags=%d skb->data=%p skb->len=0x%08x skb->data_len=0x%08x e10000_diag_count=%d\n", |
| skb,nr_frags,skb->data,skb->len,skb->data_len,e10000_diag_count) ; |
| for(f=0;f<nr_frags;f += 1) |
| { |
| struct skb_frag_struct* frag = &skb_shinfo(skb)->frags[f]; |
| printk(KERN_INFO " frags[%d]->(page=%p, page_offset=0x%08x, size=0x%08x)\n", |
| f,frag->page,frag->page_offset,frag->size) ; |
| } |
| } |
| } |
| #endif |
| static int e10000_hard_start_xmit(struct sk_buff* skb, |
| struct net_device* netDev) |
| { |
| int rc; |
| unsigned long flags; |
| EMAC* emac = netdev_priv(netDev); |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - skb=%p, netDev=%p\n", |
| skb, netDev); |
| |
| #ifdef CONFIG_BGP_E10000_DBG |
| if(DBG_SCATTERGATHER & CONFIG_BGP_E10000_DBG_LEVEL ) diag_display_sk(sk) ; |
| #endif |
| |
| spin_lock_irqsave(&emac->tomal->txLock[emac->channel], flags); |
| rc = tomal_xmit_tx_buffer(emac->tomal, emac->channel, skb); |
| if (likely(!rc)) { |
| emac->stats.tx_packets++; |
| emac->stats.tx_bytes += skb->len; |
| rc = NETDEV_TX_OK; |
| netDev->trans_start = jiffies; |
| } else { |
| netif_stop_queue(netDev); |
| rc = NETDEV_TX_BUSY; |
| } |
| spin_unlock_irqrestore(&emac->tomal->txLock[emac->channel], flags); |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit - rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| |
| |
| static int e10000_open(struct net_device* netDev) |
| { |
| int rc = 0; |
| EMAC* emac = (EMAC*) netdev_priv(netDev); |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - netDev=%p\n", netDev); |
| |
| if (!emac->opened) { |
| U32 linkTimer; |
| U8 rxLink, txLink; |
| struct sockaddr sockAddr; |
| |
| /* Set the MAC address for this interface. */ |
| memcpy(sockAddr.sa_data, bgpers.Ethernet_Config.EmacID, netDev->addr_len); |
| e10000_set_mac_address(netDev, &sockAddr); |
| |
| /* Acquire locks for EMAC and TOMAL. */ |
| spin_lock(&emac->tomal->rxLock[emac->channel]); |
| spin_lock(&emac->tomal->txLock[emac->channel]); |
| spin_lock(&emac->lock); |
| |
| emac->opened = 1; |
| |
| #ifndef CONFIG_BGP_E10000_EMAC_LOOPBACK |
| /* Reset TOMAL */ |
| tomal_soft_reset(emac->tomal); |
| |
| /* PHY reset. */ |
| rc = bluegene_macResetPHY(); |
| if (rc) { |
| e10000_printr(bg_subcomp_e10000, e10000_ras_phy_reset_error, |
| "%s: PHY reset error.", netDev->name); |
| spin_unlock(&emac->lock); |
| spin_unlock(&emac->tomal->txLock[emac->channel]); |
| spin_unlock(&emac->tomal->rxLock[emac->channel]); |
| goto exit; |
| } |
| |
| /* Wait for link to be ready. We wait less time for a single ION so that */ |
| /* we timeout before the control system does. */ |
| linkTimer = 240; |
| for (txLink = 0, rxLink = 0; linkTimer && (!txLink || !rxLink); linkTimer--) { |
| txLink = bluegene_macTestTxLink(); |
| rxLink = bluegene_macTestRxLink(); |
| udelay(100000); |
| } |
| printk(KERN_NOTICE "%s: Link status [RX%c,TX%c]\n", netDev->name, |
| rxLink ? '+' : '-', txLink ? '+' : '-'); |
| if (!linkTimer) { |
| e10000_printr(bg_subcomp_e10000, e10000_ras_link_error, |
| "%s: No link detected.", netDev->name); |
| spin_unlock(&emac->lock); |
| spin_unlock(&emac->tomal->txLock[emac->channel]); |
| spin_unlock(&emac->tomal->rxLock[emac->channel]); |
| goto exit; |
| } |
| #endif |
| |
| /* Configure EMAC. */ |
| rc = emac_configure(emac); |
| if (rc) { |
| e10000_printr(bg_subcomp_e10000, e10000_ras_emac_config_error, |
| "EMAC configuration error. rc=%d", rc); |
| spin_unlock(&emac->lock); |
| spin_unlock(&emac->tomal->txLock[emac->channel]); |
| spin_unlock(&emac->tomal->rxLock[emac->channel]); |
| goto exit; |
| } |
| |
| /* Enable TX and RX for TOMAL and EMAC. */ |
| tomal_rx_tx_enable(emac->tomal); |
| emac_rx_enable(emac); |
| emac_tx_enable(emac); |
| |
| /* Enable IRQs. */ |
| tomal_irq_enable(emac->tomal, emac->channel); |
| emac_irq_enable(emac); |
| |
| /* Release the locks. */ |
| spin_unlock(&emac->lock); |
| spin_unlock(&emac->tomal->txLock[emac->channel]); |
| spin_unlock(&emac->tomal->rxLock[emac->channel]); |
| |
| /* Start the queues. */ |
| netif_start_queue(netDev); |
| |
| /* Start link timer. */ |
| mod_timer(&e10000LinkTimer, jiffies + HZ); |
| } |
| exit: |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit - rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| |
| |
| static void e10000_link_test(unsigned long data) |
| { |
| struct net_device* netDev = (struct net_device*) data; |
| static unsigned int linkLossCount = 0; |
| u8 txLink = bluegene_macTestTxLink(); |
| u8 rxLink = bluegene_macTestRxLink(); |
| |
| if (!txLink || !rxLink) { |
| /* Link gone. Have we reached the threshold where we are going to send a fatal event? */ |
| if (linkLossCount == 30) |
| e10000_printr(bg_subcomp_e10000, e10000_ras_link_error, |
| "%s: Link error detected. Link status [RX%c,TX%c]\n", netDev->name, |
| rxLink ? '+' : '-', txLink ? '+' : '-'); |
| else if (linkLossCount == 0) |
| /* Send non-fatal RAS when the link first disappears. */ |
| e10000_printr(bg_subcomp_e10000, e10000_ras_link_loss, |
| "%s: Loss of link detected. Link status [RX%c,TX%c]\n", netDev->name, |
| rxLink ? '+' : '-', txLink ? '+' : '-'); |
| |
| linkLossCount++; |
| } else |
| /* Link present. Reset counter. */ |
| linkLossCount = 0; |
| |
| mod_timer(&e10000LinkTimer, jiffies + HZ); |
| |
| return; |
| } |
| |
| |
| //static void e10000_set_multicast_list(struct net_device* netDev) |
| //{ |
| // PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - netDev=%p\n", netDev); |
| // |
| // emac_set_multicast_list((EMAC*) netdev_priv(netDev)); |
| // |
| // PRINTK(DBG_E10000 | DBG_LEVEL2, "exit\n"); |
| // |
| // return; |
| //} |
| |
| |
| static int e10000_stop(struct net_device* netDev) |
| { |
| int rc = 0; |
| EMAC* emac = (EMAC*) netdev_priv(netDev); |
| unsigned long tomalRxFlags; |
| unsigned long tomalTxFlags; |
| unsigned long emacFlags; |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - netDev=%p\n", netDev); |
| |
| /* Acquire locks for EMAC and TOMAL. */ |
| spin_lock_irqsave(&emac->tomal->rxLock[emac->channel], tomalRxFlags); |
| spin_lock_irqsave(&emac->tomal->txLock[emac->channel], tomalTxFlags); |
| spin_lock_irqsave(&emac->lock, emacFlags); |
| |
| local_bh_disable(); |
| del_timer_sync(&e10000LinkTimer); |
| netif_stop_queue(netDev); |
| |
| emac->opened = 0; |
| emac_rx_disable(emac); |
| emac_tx_disable(emac); |
| emac_irq_disable(emac); |
| tomal_rx_tx_disable(emac->tomal); |
| tomal_irq_disable(emac->tomal, emac->channel); |
| |
| /* Release locks for EMAC and TOMAL. */ |
| spin_unlock_irqrestore(&emac->lock, emacFlags); |
| spin_unlock_irqrestore(&emac->tomal->txLock[emac->channel], tomalTxFlags); |
| spin_unlock_irqrestore(&emac->tomal->rxLock[emac->channel], tomalRxFlags); |
| |
| local_bh_enable(); |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit - rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| |
| static void e10000_tx_timeout(struct net_device* netDev) |
| { |
| EMAC* emac = (EMAC*) netdev_priv(netDev); |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry - netDev=%p\n", netDev); |
| |
| e10000_printr(bg_subcomp_e10000, e10000_ras_tx_timeout, |
| "Transmission timeout at %u, elapsed time %u\n", |
| (U32) jiffies, (U32)(jiffies - netDev->trans_start)); |
| emac->stats.tx_errors++; |
| |
| /* Attempt to reset the interface. */ |
| e10000_stop(netDev); |
| e10000_open(netDev); |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit\n"); |
| |
| return; |
| } |
| |
| |
| static void e10000_exit(void) |
| { |
| EMAC* emac = netdev_priv(e10000NetDev); |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "entry\n"); |
| |
| /* Allow the HW to clean up. */ |
| if (emac) { |
| if (emac->tomal) |
| tomal_exit(emac->tomal); |
| emac_exit(emac); |
| } |
| |
| /* Unmap HW. */ |
| if (e10000DevMapAddr) |
| iounmap(e10000DevMapAddr); |
| |
| /* Unregister and free the net_device. */ |
| if (e10000NetDev) { |
| unregister_netdev(e10000NetDev); |
| free_netdev(e10000NetDev); |
| } |
| |
| PRINTK(DBG_E10000 | DBG_LEVEL2, "exit\n"); |
| |
| return; |
| } |
| |
| |
| extern int bgWriteRasStr(unsigned int component, |
| unsigned int subcomponent, |
| unsigned int errCode, |
| char* str, |
| unsigned int strLen); |
| |
| void e10000_printr(U16 subComponent, |
| U16 id, |
| char* format, |
| ...) |
| { |
| va_list args; |
| int n; |
| char text[BG_RAS_DATA_MAX]; |
| |
| va_start(args, format); |
| n = vsnprintf(text, sizeof(text)-1, format, args); |
| va_end(args); |
| if (n < 0) |
| n = 0; |
| |
| text[n] = '\0'; |
| printk(KERN_WARNING "%s\n", text); |
| bgWriteRasStr(bg_comp_kernel, subComponent, id, text, 0); |
| |
| return; |
| } |
| |
| |
| module_init(e10000_init); |
| module_exit(e10000_exit); |
| |
| |
| |
| MODULE_DESCRIPTION("10Gb Ethernet Driver for BlueGene"); |
| MODULE_VERSION("2.0"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Andrew Tauferner"); |