569 lines
13 KiB
C
569 lines
13 KiB
C
/*
|
|
* ONU ethernet driver
|
|
*
|
|
* Copyright (C) 2010 Ralph Hempel <ralph.hempel@lantiq.com>
|
|
*
|
|
* 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 <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/io.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/version.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/skbuff.h>
|
|
|
|
#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 <ralph.hempel@lantiq.com>");
|
|
MODULE_DESCRIPTION(ONU_NETDEV_DESC);
|
|
MODULE_VERSION(ONU_NETDEV_VERSION);
|
|
|
|
#endif /* defined(LINUX) && !defined(ONU_SIMULATION) */
|