blob: a754519bd39ae19a33880fa0cab23ec2ad43537f [file] [log] [blame]
/*
* Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/wait.h>
#include <linux/wakelock.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/semaphore.h>
#include "nvshm_types.h"
#include "nvshm_if.h"
#include "nvshm_priv.h"
#include "nvshm_iobuf.h"
#define MAX_XMIT_SIZE 1500
#define NVSHM_NETIF_PREFIX "wwan"
/* This structure holds the per network port information like
* nvshm_iobuf queues, and the back reference to nvshm_channel */
struct nvshm_net_line {
int use;
int nvshm_chan;
struct net_device_stats stats;
/* iobuf queues for nvshm flow control support */
struct nvshm_iobuf *q_head;
struct nvshm_iobuf *q_tail;
struct net_device *net;
struct nvshm_channel *pchan; /* contains (struct net_device *)data */
int errno;
spinlock_t lock;
};
struct nvshm_net_device {
struct nvshm_handle *handle;
struct nvshm_net_line *line[NVSHM_MAX_CHANNELS];
int nlines;
};
static struct nvshm_net_device netdev;
/* rx_event() is called when a packet of data is received.
* The receiver should consume all iobuf in the given list.
*/
void nvshm_netif_rx_event(struct nvshm_channel *chan,
struct nvshm_iobuf *iobuf)
{
struct net_device *dev = (struct net_device *)chan->data;
struct nvshm_net_line *priv = netdev_priv(dev);
struct nvshm_iobuf *bb_iob, *bb_next, *ap_iob, *ap_next;
unsigned char *src; /* AP address for BB source buffer */
unsigned char *dst; /* AP address for skb */
unsigned int datagram_len; /* datagram total data length */
struct sk_buff *skb;
pr_debug("%s()\n", __func__);
if (!priv) {
pr_err("%s() no private info on iface!\n", __func__);
return;
}
if (!iobuf) {
pr_err("%s() null input buffer address\n", __func__);
return;
}
ap_next = iobuf;
bb_next = NVSHM_A2B(netdev.handle, iobuf);
while (bb_next) {
datagram_len = 0;
ap_iob = ap_next;
bb_iob = bb_next;
while (bb_iob) {
datagram_len += ap_iob->length;
bb_iob = ap_iob->sg_next;
ap_iob = NVSHM_B2A(netdev.handle, bb_iob);
}
if (datagram_len > dev->mtu) {
pr_err("%s: MTU %d>%d\n", __func__,
dev->mtu, datagram_len);
priv->stats.rx_errors++;
/* move to next datagram - drop current one */
ap_iob = ap_next;
bb_next = ap_next->next;
ap_next = NVSHM_B2A(netdev.handle, bb_next);
/* Break ->next chain before free */
ap_iob->next = NULL;
nvshm_iobuf_free_cluster(ap_iob);
continue;
}
/* construct the skb */
skb = (struct sk_buff *) __netdev_alloc_skb(dev,
datagram_len,
GFP_KERNEL);
if (!skb) {
/* Out of memory - nothing to do except */
/* free current iobufs and return */
pr_err("%s: skb alloc failed!\n", __func__);
priv->stats.rx_errors++;
nvshm_iobuf_free_cluster(ap_next);
return;
}
dst = skb_put(skb, datagram_len);
ap_iob = ap_next;
bb_iob = bb_next;
bb_next = ap_next->next;
ap_next = NVSHM_B2A(netdev.handle, bb_next);
while (bb_iob) {
src = NVSHM_B2A(netdev.handle, ap_iob->npdu_data)
+ ap_iob->data_offset;
memcpy(dst, src, ap_iob->length);
dst += ap_iob->length;
bb_iob = ap_iob->sg_next;
nvshm_iobuf_free(ap_iob);
ap_iob = NVSHM_B2A(netdev.handle, bb_iob);
}
/* deliver skb to netif */
skb->dev = dev;
skb_reset_mac_header(skb);
skb_reset_transport_header(skb);
switch (skb->data[0] & 0xf0) {
case 0x40:
skb->protocol = htons(ETH_P_IP);
break;
case 0x60:
skb->protocol = htons(ETH_P_IPV6);
break;
default:
pr_err("%s() Non IP packet received!\n", __func__);
priv->stats.rx_errors++;
/* Drop packet */
kfree_skb(skb);
/* move to next datagram */
continue;
}
skb->pkt_type = PACKET_HOST;
skb->ip_summed = CHECKSUM_UNNECESSARY;
priv->stats.rx_packets++;
priv->stats.rx_bytes += datagram_len;
if (netif_rx(skb) == NET_RX_DROP)
pr_debug("%s() : dropped packet\n", __func__);
}
}
/* error_event() is called when an error event is received */
void nvshm_netif_error_event(struct nvshm_channel *chan,
enum nvshm_error_id error)
{
struct net_device *dev = (struct net_device *)chan->data;
struct nvshm_net_line *priv = netdev_priv(dev);
pr_debug("%s()\n", __func__);
if (chan->data == NULL) {
pr_err("%s() chan->data is null pointer\n", __func__);
return;
}
priv->errno = error;
spin_lock(&priv->lock);
priv->stats.tx_errors++;
spin_unlock(&priv->lock);
pr_err("%s() : error on nvshm net interface!\n", __func__);
}
/* start_tx() is called to restart the transmit */
void nvshm_netif_start_tx(struct nvshm_channel *chan)
{
struct net_device *dev = (struct net_device *)chan->data;
struct nvshm_net_line *priv = netdev_priv(dev);
pr_debug("%s()\n", __func__);
if (!priv)
return;
/* Wake up queue */
netif_wake_queue(dev);
}
static struct nvshm_if_operations nvshm_netif_ops = {
.rx_event = nvshm_netif_rx_event, /* nvshm_queue.c */
.error_event = nvshm_netif_error_event, /* nvshm_iobuf.c */
.start_tx = nvshm_netif_start_tx,
};
/* called when ifconfig <if> up */
static int nvshm_netops_open(struct net_device *dev)
{
struct nvshm_net_line *priv = netdev_priv(dev);
int ret = 0;
pr_debug("%s()\n", __func__);
if (!priv)
return -EINVAL;
spin_lock(&priv->lock);
if (!priv->use) {
priv->pchan = nvshm_open_channel(priv->nvshm_chan,
&nvshm_netif_ops,
dev);
if (priv->pchan == NULL)
ret = -EINVAL;
}
if (!ret)
priv->use++;
spin_unlock(&priv->lock);
/* Start if queue */
netif_start_queue(dev);
return ret;
}
/* called when ifconfig <if> down */
static int nvshm_netops_close(struct net_device *dev)
{
struct nvshm_net_line *priv = netdev_priv(dev);
pr_debug("%s()\n", __func__);
if (!priv)
return -EINVAL;
spin_lock(&priv->lock);
if (priv->use > 0)
priv->use--;
spin_unlock(&priv->lock);
if (!priv->use) {
/* Cleanup if data are still present in io queue */
if (priv->q_head) {
pr_debug("%s: still some data in queue!\n", __func__);
nvshm_iobuf_free_cluster(
(struct nvshm_iobuf *)priv->q_head);
priv->q_head = priv->q_tail = NULL;
}
nvshm_close_channel(priv->pchan);
}
netif_stop_queue(dev);
return 0;
}
static int nvshm_netops_xmit_frame(struct sk_buff *skb, struct net_device *dev)
{
struct nvshm_net_line *priv = netdev_priv(dev);
struct nvshm_iobuf *iob, *leaf = NULL, *list = NULL;
int to_send = 0, remain;
int len;
char *data;
pr_debug("Transmit frame\n");
pr_debug("%s()\n", __func__);
if (!priv)
return -EINVAL;
len = skb->len;
data = skb->data;
/* write a frame to an nvshm channel */
pr_debug("len=%d\n", len);
/* write data from skb (data,len) to net_device dev */
remain = len;
while (remain) {
pr_debug("remain=%d\n", remain);
to_send = remain < MAX_XMIT_SIZE ? remain : MAX_XMIT_SIZE;
iob = nvshm_iobuf_alloc(priv->pchan, to_send);
if (!iob) {
pr_warn("%s iobuf alloc failed\n", __func__);
netif_stop_queue(dev);
if (list)
nvshm_iobuf_free_cluster(list);
return NETDEV_TX_BUSY; /* was return -ENOMEM; */
}
iob->length = to_send;
remain -= to_send;
memcpy(NVSHM_B2A(netdev.handle,
iob->npdu_data + iob->data_offset),
data,
to_send);
data += to_send;
if (!list) {
leaf = list = iob;
} else {
leaf->sg_next = NVSHM_A2B(netdev.handle, iob);
leaf = iob;
}
}
if (nvshm_write(priv->pchan, list)) {
/* no more transmit possible - stop queue */
pr_warning("%s rate limit hit on channel %d\n",
__func__, priv->nvshm_chan);
netif_stop_queue(dev);
return NETDEV_TX_BUSY;
}
/* successfully written len data bytes */
priv->stats.tx_packets++;
priv->stats.tx_bytes += len;
pr_debug("packets=%ld, tx_bytes=%ld\n", priv->stats.tx_packets,
priv->stats.tx_bytes);
/* free skb now as nvshm is a ref on it now */
kfree_skb(skb);
return NETDEV_TX_OK;
}
static int nvshm_netops_change_mtu(struct net_device *dev, int new_mtu)
{
struct nvshm_net_line *priv = netdev_priv(dev);
pr_debug("%s()\n", __func__);
if (!priv)
return -EINVAL;
return 0;
}
struct net_device_stats *nvshm_netops_get_stats(struct net_device *dev)
{
struct nvshm_net_line *priv = netdev_priv(dev);
pr_debug("%s()\n", __func__);
if (priv)
return &priv->stats;
else
return NULL;
}
static void nvshm_netops_tx_timeout(struct net_device *dev)
{
struct nvshm_net_line *priv = netdev_priv(dev);
pr_debug("%s()\n", __func__);
if (!priv)
return;
spin_lock(&priv->lock);
priv->stats.tx_errors++;
spin_unlock(&priv->lock);
netif_wake_queue(dev);
}
static const struct net_device_ops nvshm_netdev_ops = {
.ndo_open = nvshm_netops_open,
.ndo_stop = nvshm_netops_close,
.ndo_start_xmit = nvshm_netops_xmit_frame,
.ndo_get_stats = nvshm_netops_get_stats,
.ndo_change_mtu = nvshm_netops_change_mtu,
.ndo_tx_timeout = nvshm_netops_tx_timeout,
};
static void nvshm_nwif_init_dev(struct net_device *dev)
{
dev->netdev_ops = &nvshm_netdev_ops;
dev->mtu = 1500;
dev->type = ARPHRD_NONE;
/* No hardware address */
dev->hard_header_len = 0;
/* Should be tuned as soon as framer allow multiple frames */
dev->tx_queue_len = 10;
/* No hardware address */
dev->addr_len = 0;
dev->watchdog_timeo = HZ;
dev->flags |= IFF_POINTOPOINT | IFF_NOARP;
}
int nvshm_net_init(struct nvshm_handle *handle)
{
struct net_device *dev;
int chan;
int ret = 0;
struct nvshm_net_line *priv;
pr_debug("%s()\n", __func__);
memset(&netdev, 0, sizeof(netdev));
netdev.handle = handle;
/* check nvshm_channels[] for net devices */
for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
if (handle->chan[chan].map.type == NVSHM_CHAN_NET) {
pr_debug("Registering %s%d\n",
NVSHM_NETIF_PREFIX,
netdev.nlines);
dev = alloc_netdev(sizeof(struct nvshm_net_line),
NVSHM_NETIF_PREFIX"%d",
nvshm_nwif_init_dev);
if (!dev)
goto err_exit;
dev->base_addr = netdev.nlines;
priv = netdev_priv(dev);
netdev.line[netdev.nlines] = priv;
netdev.line[netdev.nlines]->net = dev;
priv->nvshm_chan = chan;
spin_lock_init(&priv->lock);
ret = register_netdev(dev);
if (ret) {
pr_err("Error %i registering %s%d device\n",
ret,
NVSHM_NETIF_PREFIX,
netdev.nlines);
goto err_exit;
}
netdev.nlines++;
}
}
return ret;
err_exit:
nvshm_net_cleanup();
return ret;
}
void nvshm_net_cleanup(void)
{
int chan;
pr_debug("%s()\n", __func__);
for (chan = 0; chan < netdev.nlines; chan++) {
if (netdev.line[chan]->net) {
pr_debug("%s free %s%d\n",
__func__,
NVSHM_NETIF_PREFIX,
chan);
unregister_netdev(netdev.line[chan]->net);
free_netdev(netdev.line[chan]->net);
}
}
}