/* * ONU ethernet driver * * Copyright (C) 2010 Ralph Hempel * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * */ #if defined(LINUX) && !defined(ONU_SIMULATION) #include #include #include #include #include #include #include #include #include #include #include #include #include #include "drv_onu_netdev.h" #include "drv_onu_debug.h" #include "drv_onu_types.h" #include "drv_onu_ll_ssb.h" #include "drv_onu_lan_api_intern.h" #include "drv_onu_resource.h" #include "drv_onu_resource_gpe.h" static struct platform_device *onu_netdev_device; static struct net_device *onu_netdev_devs[ONU_NET_MAX_NETDEV_PORT]; static DEFINE_SPINLOCK(tx_lock); STATIC int onu_netdev_if_open(struct net_device *dev); STATIC int onu_netdev_if_stop(struct net_device *dev); STATIC int onu_netdev_if_hard_start_xmit(struct sk_buff *skb, struct net_device *dev); STATIC void onu_netdev_if_set_multicast_list(struct net_device *dev); STATIC int onu_netdev_if_do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); STATIC void onu_netdev_if_tx_timeout(struct net_device *dev); STATIC int onu_netdev_if_set_mac_address(struct net_device *dev, void *p); extern u32 onu_gpon_link_status_get(void); extern u32 onu_mac_link_status_get(const u8 idx); # if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)) static const struct net_device_ops onu_netdev_ops = { .ndo_open = onu_netdev_if_open, .ndo_stop = onu_netdev_if_stop, .ndo_start_xmit = onu_netdev_if_hard_start_xmit, .ndo_set_multicast_list = onu_netdev_if_set_multicast_list, .ndo_do_ioctl = onu_netdev_if_do_ioctl, .ndo_tx_timeout = onu_netdev_if_tx_timeout, .ndo_set_mac_address = onu_netdev_if_set_mac_address }; /** \todo dev->watchdog_timeo = ONU_TX_TIMEOUT;*/ #endif STATIC int onu_netdev_buf_alloc(const uint32_t len, struct net_buf *buf) { struct sk_buff *skb; if (!len || !buf) return -1; skb = dev_alloc_skb(len); if (skb == NULL) return -1; buf->skb = (void*)skb; buf->data = (uint8_t*)skb->data; buf->len = len; return 0; } STATIC int onu_netdev_rx(struct net_device *dev, struct net_buf *buf) { struct sk_buff *skb; if (!dev) { SW_ERR("no dev\n"); return -1; } if (!netif_running(dev)) { SW_ERR("netif not running\n"); return -1; } skb = (struct sk_buff *)buf->skb; if (skb == NULL) { dev->stats.rx_errors++; return -3; } if (buf->len == 0) { dev_kfree_skb(skb); dev->stats.rx_errors++; dev->stats.rx_length_errors++; if (0) /** \todo add crc errors */ dev->stats.rx_crc_errors++; return -5; } if (skb) { skb_put(skb, buf->len); skb->dev = dev; skb->protocol = eth_type_trans(skb, dev); skb->ip_summed = CHECKSUM_NONE; netif_rx(skb); dev->last_rx = jiffies; dev->stats.rx_packets++; dev->stats.rx_bytes += buf->len; } return 0; } STATIC int onu_netdev_irq(void *handle, struct net_buf *buf) { int ret; ret = onu_netdev_rx(handle, buf); return ret; } STATIC int onu_netdev_lan_link_status( void *handle, const uint8_t lan_port, const bool link_up) { struct onu_netdev_if_priv *priv = netdev_priv((struct net_device*)handle); if (link_up) priv->lan_port_status_mask |= (1 << lan_port); else priv->lan_port_status_mask &= ~(1 << lan_port); if (priv->lan_port_status_mask && !netif_carrier_ok((struct net_device*)handle)) { SW_DBG("lan%u up\n", lan_port); net_rx_enable(priv->port_number, true); netif_carrier_on((struct net_device*)handle); } else if (!priv->lan_port_status_mask && netif_carrier_ok((struct net_device*)handle)){ SW_DBG("lan%u down\n", lan_port); net_rx_enable(priv->port_number, false); netif_carrier_off((struct net_device*)handle); } else { return 0; } return 0; } STATIC int onu_netdev_wan_link_status(void *handle, const bool link_up) { struct onu_netdev_if_priv *priv = netdev_priv((struct net_device*)handle); if (link_up && !netif_carrier_ok((struct net_device*)handle)) { SW_DBG("wan up\n"); net_rx_enable(priv->port_number, true); netif_carrier_on((struct net_device*)handle); } else if (!link_up && netif_carrier_ok((struct net_device*)handle)){ SW_DBG("wan down\n"); net_rx_enable(priv->port_number, false); netif_carrier_off((struct net_device*)handle); } else { return 0; } return 0; } STATIC int onu_netdev_exc_link_status(void *handle, const bool link_up) { struct onu_netdev_if_priv *priv = netdev_priv((struct net_device*)handle); if (link_up && !netif_carrier_ok((struct net_device*)handle)) { SW_DBG("exc up\n"); net_rx_enable(priv->port_number, true); netif_carrier_on((struct net_device*)handle); } else if (!link_up && netif_carrier_ok((struct net_device*)handle)){ SW_DBG("exc down\n"); net_rx_enable(priv->port_number, false); netif_carrier_off((struct net_device*)handle); } else { return 0; } return 0; } STATIC int onu_netdev_if_open(struct net_device *dev) { struct onu_netdev_if_priv *priv = netdev_priv(dev); struct net_cb cb_list = { NULL, { onu_netdev_irq, onu_netdev_lan_link_status, onu_netdev_wan_link_status } }; SW_DBG("open %p on %s\n", dev, dev->name); cb_list.net_dev = dev; switch (priv->port_number) { case ONU_NET_NETDEV_WAN_PORT: onu_netdev_wan_link_status(dev, (bool)onu_gpon_link_status_get()); cb_list.cb[NET_CB_LAN_STATUS] = NULL; break; case ONU_NET_NETDEV_LAN0_PORT: case ONU_NET_NETDEV_LAN1_PORT: case ONU_NET_NETDEV_LAN2_PORT: case ONU_NET_NETDEV_LAN3_PORT: onu_netdev_lan_link_status( dev, net_uni_get(priv->port_number), (bool)onu_mac_link_status_get( net_uni_get(priv->port_number))); cb_list.cb[NET_CB_WAN_STATUS] = NULL; break; case ONU_NET_NETDEV_EXC_PORT: onu_netdev_exc_link_status(dev, true); cb_list.cb[NET_CB_LAN_STATUS] = NULL; cb_list.cb[NET_CB_WAN_STATUS] = NULL; break; } if (net_cb_list_register((uint8_t)priv->port_number, &cb_list) < 0) return -1; netif_start_queue(dev); return 0; } STATIC int onu_netdev_if_stop(struct net_device *dev) { struct onu_netdev_if_priv *priv = netdev_priv(dev); struct net_cb cb_list = {0}; int uni; SW_DBG("stop on %s\n", dev->name); netif_stop_queue(dev); uni = net_uni_get(priv->port_number); if (uni < 0) { net_rx_enable((uint8_t)priv->port_number, false); } else { priv->lan_port_status_mask &= ~(1 << uni); if (!priv->lan_port_status_mask) net_rx_enable((uint8_t)priv->port_number, false); } netif_carrier_off(dev); net_cb_list_register((uint8_t)priv->port_number, &cb_list); return 0; } STATIC int onu_netdev_if_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct onu_netdev_if_priv *priv = netdev_priv(dev); int ret; spin_lock_irq(&tx_lock); SW_DBG("xmit on %s\n", dev->name); ret = net_pdu_write((uint8_t)priv->port_number, skb->len, skb->data); if (ret != 0) { SW_DBG("%s unable to transmit, packet dropped\n", dev->name); dev->stats.tx_dropped++; } else { dev->trans_start = jiffies; dev->stats.tx_packets++; dev->stats.tx_bytes += skb->len; } dev_kfree_skb(skb); spin_unlock_irq(&tx_lock); return 0; } STATIC void onu_netdev_if_tx_timeout(struct net_device *dev) { SW_DBG("TX timeout on %s\n", dev->name); } STATIC void onu_netdev_if_set_multicast_list(struct net_device *dev) { struct onu_netdev_if_priv *priv = netdev_priv(dev); (void)priv; SW_DBG("set multicast list on %s\n", dev->name); if (dev->flags & IFF_PROMISC) { /* enable unknown packets */ SW_DBG("enable promisc mode on %s\n", dev->name); } else { /* disable unknown packets */ } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) if (dev->flags & IFF_PROMISC || dev->flags & IFF_ALLMULTI || dev->mc_count) { /* enable multicast packets */ } else { /* disable multicast packets */ } #endif } STATIC int onu_netdev_if_set_mac_address(struct net_device *dev, void *p) { struct onu_netdev_if_priv *priv = netdev_priv(dev); struct sockaddr *addr = p; SW_DBG("set mac address\n"); memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); switch (priv->port_number) { case ONU_NET_NETDEV_LAN0_PORT: case ONU_NET_NETDEV_LAN1_PORT: case ONU_NET_NETDEV_LAN2_PORT: case ONU_NET_NETDEV_LAN3_PORT: if (net_lan_mac_set(addr->sa_data) != 0) return -1; break; default: break; } return 0; } STATIC int onu_netdev_if_do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct onu_netdev_if_priv *priv = netdev_priv(dev); (void)priv; SW_DBG("ioctl on %s\n", dev->name); switch (cmd) { default: return -EOPNOTSUPP; } return 0; } STATIC struct net_device *onu_netdev_if_alloc(void) { struct net_device *dev; struct onu_netdev_if_priv *priv; SW_DBG("alloc\n"); dev = alloc_etherdev(sizeof(*priv)); if (!dev) return NULL; priv = netdev_priv(dev); priv->dev = dev; # if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)) /** \todo crosscheck irq */ /* dev->irq = 42; */ dev->open = onu_netdev_if_open; dev->hard_start_xmit = onu_netdev_if_hard_start_xmit; dev->stop = onu_netdev_if_stop; dev->set_multicast_list = onu_netdev_if_set_multicast_list; dev->do_ioctl = onu_netdev_if_do_ioctl; dev->tx_timeout = onu_netdev_if_tx_timeout; dev->watchdog_timeo = ONU_TX_TIMEOUT; dev->set_mac_address = onu_netdev_if_set_mac_address; # else dev->netdev_ops = &onu_netdev_ops; # endif return dev; } STATIC void onu_netdev_cleanup(void) { int i; SW_DBG("cleanup\n"); for (i = 0; i < ONU_NET_MAX_NETDEV_PORT; i++) { struct net_device *dev = onu_netdev_devs[i]; if (dev) { unregister_netdev(dev); free_netdev(dev); } onu_netdev_devs[i] = NULL; } } STATIC int onu_netdev_probe(struct platform_device *pdev) { int i, err; uint8_t max_lan_port; /* get available LAN ports number */ max_lan_port = net_lan_max_port_get(); for (i = 0; i < ONU_NET_MAX_NETDEV_PORT; i++) { struct net_device *dev; struct onu_netdev_if_priv *priv; /* skip unavailable LAN ports */ if ((i == ONU_NET_NETDEV_LAN0_PORT && max_lan_port < 1) || (i == ONU_NET_NETDEV_LAN1_PORT && max_lan_port < 2) || (i == ONU_NET_NETDEV_LAN2_PORT && max_lan_port < 3) || (i == ONU_NET_NETDEV_LAN3_PORT && max_lan_port < 4)) continue; dev = onu_netdev_if_alloc(); if (!dev) { err = -ENOMEM; goto err; } onu_netdev_devs[i] = dev; priv = netdev_priv(dev); switch (i) { case ONU_NET_NETDEV_WAN_PORT: strcpy(dev->name, ONU_NETDEV_NAME_WAN); break; case ONU_NET_NETDEV_LAN0_PORT: case ONU_NET_NETDEV_LAN1_PORT: case ONU_NET_NETDEV_LAN2_PORT: case ONU_NET_NETDEV_LAN3_PORT: sprintf(dev->name, "%s%u", ONU_NETDEV_NAME_LAN, net_uni_get(i)); break; case ONU_NET_NETDEV_EXC_PORT: strcpy(dev->name, ONU_NETDEV_NAME_EXC); break; default: err = -1; goto err; } priv->port_number = i; priv->lan_port_status_mask = 0; err = register_netdev(dev); if (err) { SW_INFO("%s register failed, error=%d\n", dev->name, err); goto err; } netif_carrier_off(dev); } return 0; err: onu_netdev_cleanup(); SW_ERR("init failed\n"); return err; } STATIC int onu_netdev_remove(struct platform_device *dev) { SW_DBG("remove %s\n", dev->name); onu_netdev_cleanup(); return 0; } static struct platform_driver onu_netdev_driver = { .probe = onu_netdev_probe, .remove = onu_netdev_remove, .driver = { .name = ONU_NETDEV_NAME, }, }; STATIC int __init onu_netdev_mod_init(void) { int ret; struct net_dev dev = { onu_netdev_buf_alloc }; pr_info(ONU_NETDEV_DESC ", Version " ONU_NETDEV_VERSION " (c) Copyright 2011, Lantiq Deutschland GmbH\n"); ret = platform_driver_register(&onu_netdev_driver); if (ret) { printk(KERN_ERR ONU_NETDEV_DESC "Error registering platfom driver\n"); } onu_netdev_device = platform_device_alloc("onu_netdev", -1); if (!onu_netdev_device) { printk(KERN_ERR ONU_NETDEV_DESC "Error allocating platfom driver\n"); ret = -ENOMEM; goto err_unregister_driver; } ret = platform_device_add(onu_netdev_device); if (ret) { printk(KERN_ERR ONU_NETDEV_DESC "Error adding platfom driver\n"); goto err_free_device; } ret = net_dev_register(&dev); if (ret != 0) { printk(KERN_ERR ONU_NETDEV_DESC "Error registering platfom driver\n"); goto err_free_device; } return 0; err_free_device: platform_device_put(onu_netdev_device); err_unregister_driver: platform_driver_unregister(&onu_netdev_driver); return ret; } STATIC void __exit onu_netdev_mod_exit(void) { SW_DBG("exit\n"); platform_device_unregister(onu_netdev_device); platform_driver_unregister(&onu_netdev_driver); } module_init(onu_netdev_mod_init); module_exit(onu_netdev_mod_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Ralph Hempel "); MODULE_DESCRIPTION(ONU_NETDEV_DESC); MODULE_VERSION(ONU_NETDEV_VERSION); #endif /* defined(LINUX) && !defined(ONU_SIMULATION) */