blob: a7c2125ed98277cdf61181a54e7e862013a317a6 [file] [log] [blame]
/*
* Author: Tzu-Jung Lee <tjlee@ambarella.com>
* Copyright (C) 2012-2013, Ambarella, Inc.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/time.h>
#include <linux/crc16.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/rpmsg.h>
#include <plat/ambcache.h>
#define MIN_MTU 68
#define MAX_MTU (4096 - ETH_HLEN - sizeof(struct rpmsg_hdr))
#define DEF_MTU MAX_MTU
extern int ambveth_do_send(void *data, int len);
struct ambveth
{
spinlock_t lock;
struct net_device_stats stats;
struct net_device *ndev;
};
void ambveth_enqueue(void *priv, void *data, int len)
{
struct ambveth *lp;
struct sk_buff *skb;
lp = ((struct platform_device*)priv)->dev.platform_data;
skb = dev_alloc_skb(len + NET_IP_ALIGN);
if (!skb) {
lp->stats.rx_dropped++;
return;
}
skb_put(skb, len);
memcpy(skb->data, data, len);
lp->stats.rx_packets++;
lp->stats.rx_bytes += len;
skb->dev = lp->ndev;
skb->protocol = eth_type_trans(skb, skb->dev);
netif_rx_ni(skb);
}
static int ambveth_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
struct ambveth *lp;
int ret;
lp = netdev_priv(ndev);
ret = ambveth_do_send(skb->data, skb->len);
if (ret) {
lp->stats.tx_dropped++;
return NETDEV_TX_BUSY;
}
lp->stats.tx_packets++;
lp->stats.tx_bytes += skb->len;
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
static int ambveth_open(struct net_device *ndev)
{
struct ambveth *lp;
lp = netdev_priv(ndev);
netif_start_queue(ndev);
return 0;
}
static int ambveth_stop(struct net_device *ndev)
{
struct ambveth *lp;
lp = netdev_priv(ndev);
netif_stop_queue(ndev);
flush_scheduled_work();
return 0;
}
static void ambveth_timeout(struct net_device *ndev)
{
netif_wake_queue(ndev);
}
static struct net_device_stats *ambveth_get_stats(struct net_device *ndev)
{
struct ambveth *lp;
lp = netdev_priv(ndev);
return &lp->stats;
}
static int ambveth_change_mtu(struct net_device *dev, int mtu)
{
if (mtu < MIN_MTU || mtu + dev->hard_header_len > MAX_MTU)
return -EINVAL;
dev->mtu = mtu;
return 0;
}
static int ambveth_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd)
{
int ret = 0;
if (!netif_running(ndev)) {
ret = -EINVAL;
goto ambveth_get_settings_exit;
}
ambveth_get_settings_exit:
return ret;
}
/* ==========================================================================*/
static const struct net_device_ops ambveth_netdev_ops = {
.ndo_open = ambveth_open,
.ndo_stop = ambveth_stop,
.ndo_start_xmit = ambveth_start_xmit,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_do_ioctl = ambveth_ioctl,
.ndo_tx_timeout = ambveth_timeout,
.ndo_get_stats = ambveth_get_stats,
.ndo_change_mtu = ambveth_change_mtu,
};
struct platform_device ambveth_device = {
.name = "ambveth",
.id = 0,
.resource = NULL,
.num_resources = 0,
.dev = {
.platform_data = NULL,
}
};
static int ambveth_drv_probe(struct platform_device *pdev)
{
int ret = 0;
struct net_device *ndev;
struct ambveth *lp;
char mac_addr[6];
ndev = alloc_etherdev(sizeof(struct ambveth));
if (!ndev)
ret = -ENOMEM;
SET_NETDEV_DEV(ndev, &pdev->dev);
lp = netdev_priv(ndev);
lp->ndev = ndev;
spin_lock_init(&lp->lock);
pdev->dev.platform_data = lp;
ndev->netdev_ops = &ambveth_netdev_ops;
ndev->mtu = DEF_MTU;
sprintf(ndev->name, "veth0");
if (!is_valid_ether_addr(mac_addr))
random_ether_addr(mac_addr);
memcpy(ndev->dev_addr, mac_addr, 6);
ret = register_netdev(ndev);
if (ret)
dev_err(&pdev->dev, " register_netdev fail%d.\n", ret);
return ret;
}
static struct platform_driver ambveth_driver = {
.probe = ambveth_drv_probe,
.driver = {
.name = "ambveth",
.owner = THIS_MODULE,
},
};
static int __init ambveth_init(void)
{
return platform_driver_register(&ambveth_driver);
}
static void __exit ambveth_fini(void)
{
}
module_init(ambveth_init);
module_exit(ambveth_fini);
MODULE_DESCRIPTION("Ambarella veth driver");