[PATCH v1 3/4] mhi_bus: dev: netdev: add network interface driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



MHI based net device driver is used for transferring IP
traffic between host and modem. Driver allows clients to
transfer data using standard network interface.

Signed-off-by: Sujeev Dias <sdias@xxxxxxxxxxxxxx>
---
 Documentation/devicetree/bindings/bus/mhi.txt |  36 ++
 drivers/bus/Kconfig                           |   1 +
 drivers/bus/mhi/Makefile                      |   2 +-
 drivers/bus/mhi/devices/Kconfig               |  10 +
 drivers/bus/mhi/devices/Makefile              |   1 +
 drivers/bus/mhi/devices/mhi_netdev.c          | 893 ++++++++++++++++++++++++++
 6 files changed, 942 insertions(+), 1 deletion(-)
 create mode 100644 drivers/bus/mhi/devices/Kconfig
 create mode 100644 drivers/bus/mhi/devices/Makefile
 create mode 100644 drivers/bus/mhi/devices/mhi_netdev.c

diff --git a/Documentation/devicetree/bindings/bus/mhi.txt b/Documentation/devicetree/bindings/bus/mhi.txt
index ea1b620..172ae7b 100644
--- a/Documentation/devicetree/bindings/bus/mhi.txt
+++ b/Documentation/devicetree/bindings/bus/mhi.txt
@@ -139,3 +139,39 @@ mhi_controller {
 		<driver specific properties>
 	};
 };
+
+================
+Children Devices
+================
+
+MHI netdev properties
+
+- mhi,chan
+  Usage: required
+  Value type: <string>
+  Definition: Channel name MHI netdev support
+
+- mhi,mru
+  Usage: required
+  Value type: <u32>
+  Definition: Largest packet size interface can receive in bytes.
+
+- mhi,interface-name
+  Usage: optional
+  Value type: <string>
+  Definition: Interface name to be given so clients can identify it
+
+- mhi,recycle-buf
+  Usage: optional
+  Value type: <bool>
+  Definition: Set true if interface support recycling buffers.
+
+========
+Example:
+========
+
+mhi_rmnet@0 {
+	mhi,chan = "IP_HW0";
+	mhi,interface-name = "rmnet_mhi";
+	mhi,mru = <0x4000>;
+};
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
index fb28002..cc03762 100644
--- a/drivers/bus/Kconfig
+++ b/drivers/bus/Kconfig
@@ -190,5 +190,6 @@ config MHI_DEBUG
 
 source "drivers/bus/fsl-mc/Kconfig"
 source drivers/bus/mhi/controllers/Kconfig
+source drivers/bus/mhi/devices/Kconfig
 
 endmenu
diff --git a/drivers/bus/mhi/Makefile b/drivers/bus/mhi/Makefile
index c6a2a91..2382e04 100644
--- a/drivers/bus/mhi/Makefile
+++ b/drivers/bus/mhi/Makefile
@@ -5,4 +5,4 @@
 # core layer
 obj-y += core/
 obj-y += controllers/
-#obj-y += devices/
+obj-y += devices/
diff --git a/drivers/bus/mhi/devices/Kconfig b/drivers/bus/mhi/devices/Kconfig
new file mode 100644
index 0000000..40f964d
--- /dev/null
+++ b/drivers/bus/mhi/devices/Kconfig
@@ -0,0 +1,10 @@
+menu "MHI device support"
+
+config MHI_NETDEV
+       tristate "MHI NETDEV"
+       depends on MHI_BUS
+       help
+	  MHI based net device driver for transferring IP traffic
+	  between host and modem. By enabling this driver, clients
+	  can transfer data using standard network interface.
+endmenu
diff --git a/drivers/bus/mhi/devices/Makefile b/drivers/bus/mhi/devices/Makefile
new file mode 100644
index 0000000..ee12a64
--- /dev/null
+++ b/drivers/bus/mhi/devices/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MHI_NETDEV) +=mhi_netdev.o
diff --git a/drivers/bus/mhi/devices/mhi_netdev.c b/drivers/bus/mhi/devices/mhi_netdev.c
new file mode 100644
index 0000000..23881a9
--- /dev/null
+++ b/drivers/bus/mhi/devices/mhi_netdev.c
@@ -0,0 +1,893 @@
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/dma-mapping.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/rtnetlink.h>
+#include <linux/mhi.h>
+
+#define MHI_NETDEV_DRIVER_NAME "mhi_netdev"
+#define WATCHDOG_TIMEOUT (30 * HZ)
+
+#ifdef CONFIG_MHI_DEBUG
+
+#define MHI_ASSERT(cond, msg) do { \
+	if (cond) \
+		panic(msg); \
+} while (0)
+
+#define MSG_VERB(fmt, ...) do { \
+	if (mhi_netdev->msg_lvl <= MHI_MSG_LVL_VERBOSE) \
+		pr_err("[D][%s] " fmt, __func__, ##__VA_ARGS__);\
+} while (0)
+
+#else
+
+#define MHI_ASSERT(cond, msg) do { \
+	if (cond) { \
+		MSG_ERR(msg); \
+		WARN_ON(cond); \
+	} \
+} while (0)
+
+#define MSG_VERB(fmt, ...)
+
+#endif
+
+#define MSG_LOG(fmt, ...) do { \
+	if (mhi_netdev->msg_lvl <= MHI_MSG_LVL_INFO) \
+		pr_err("[I][%s] " fmt, __func__, ##__VA_ARGS__);\
+} while (0)
+
+#define MSG_ERR(fmt, ...) do { \
+	if (mhi_netdev->msg_lvl <= MHI_MSG_LVL_ERROR) \
+		pr_err("[E][%s] " fmt, __func__, ##__VA_ARGS__); \
+} while (0)
+
+struct mhi_stats {
+	u32 rx_int;
+	u32 tx_full;
+	u32 tx_pkts;
+	u32 rx_budget_overflow;
+	u32 rx_frag;
+	u32 alloc_failed;
+};
+
+/* important: do not exceed sk_buf->cb (48 bytes) */
+struct mhi_skb_priv {
+	void *buf;
+	size_t size;
+	struct mhi_netdev *mhi_netdev;
+};
+
+struct mhi_netdev {
+	int alias;
+	struct mhi_device *mhi_dev;
+	spinlock_t rx_lock;
+	bool enabled;
+	rwlock_t pm_lock; /* state change lock */
+	int (*rx_queue)(struct mhi_netdev *mhi_netdev, gfp_t gfp_t);
+	struct work_struct alloc_work;
+	int wake;
+
+	struct sk_buff_head rx_allocated;
+
+	u32 mru;
+	const char *interface_name;
+	struct napi_struct napi;
+	struct net_device *ndev;
+	struct sk_buff *frag_skb;
+	bool recycle_buf;
+
+	struct mhi_stats stats;
+	struct dentry *dentry;
+	enum MHI_DEBUG_LEVEL msg_lvl;
+};
+
+struct mhi_netdev_priv {
+	struct mhi_netdev *mhi_netdev;
+};
+
+static struct mhi_driver mhi_netdev_driver;
+static void mhi_netdev_create_debugfs(struct mhi_netdev *mhi_netdev);
+
+static __be16 mhi_netdev_ip_type_trans(struct sk_buff *skb)
+{
+	__be16 protocol = 0;
+
+	/* determine L3 protocol */
+	switch (skb->data[0] & 0xf0) {
+	case 0x40:
+		protocol = htons(ETH_P_IP);
+		break;
+	case 0x60:
+		protocol = htons(ETH_P_IPV6);
+		break;
+	default:
+		/* default is QMAP */
+		protocol = htons(ETH_P_MAP);
+		break;
+	}
+	return protocol;
+}
+
+static void mhi_netdev_skb_destructor(struct sk_buff *skb)
+{
+	struct mhi_skb_priv *skb_priv = (struct mhi_skb_priv *)(skb->cb);
+	struct mhi_netdev *mhi_netdev = skb_priv->mhi_netdev;
+
+	skb->data = skb->head;
+	skb_reset_tail_pointer(skb);
+	skb->len = 0;
+	MHI_ASSERT(skb->data != skb_priv->buf, "incorrect buf");
+	skb_queue_tail(&mhi_netdev->rx_allocated, skb);
+}
+
+static int mhi_netdev_alloc_skb(struct mhi_netdev *mhi_netdev, gfp_t gfp_t)
+{
+	u32 cur_mru = mhi_netdev->mru;
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+	struct mhi_skb_priv *skb_priv;
+	int ret;
+	struct sk_buff *skb;
+	int no_tre = mhi_get_no_free_descriptors(mhi_dev, DMA_FROM_DEVICE);
+	int i;
+
+	for (i = 0; i < no_tre; i++) {
+		skb = alloc_skb(cur_mru, gfp_t);
+		if (!skb)
+			return -ENOMEM;
+
+		read_lock_bh(&mhi_netdev->pm_lock);
+		if (unlikely(!mhi_netdev->enabled)) {
+			MSG_ERR("Interface not enabled\n");
+			ret = -EIO;
+			goto error_queue;
+		}
+
+		skb_priv = (struct mhi_skb_priv *)skb->cb;
+		skb_priv->buf = skb->data;
+		skb_priv->size = cur_mru;
+		skb_priv->mhi_netdev = mhi_netdev;
+		skb->dev = mhi_netdev->ndev;
+
+		if (mhi_netdev->recycle_buf)
+			skb->destructor = mhi_netdev_skb_destructor;
+
+		spin_lock_bh(&mhi_netdev->rx_lock);
+		ret = mhi_queue_transfer(mhi_dev, DMA_FROM_DEVICE, skb,
+					 skb_priv->size, MHI_EOT);
+		spin_unlock_bh(&mhi_netdev->rx_lock);
+
+		if (ret) {
+			MSG_ERR("Failed to queue skb, ret:%d\n", ret);
+			ret = -EIO;
+			goto error_queue;
+		}
+
+		read_unlock_bh(&mhi_netdev->pm_lock);
+	}
+
+	return 0;
+
+error_queue:
+	skb->destructor = NULL;
+	read_unlock_bh(&mhi_netdev->pm_lock);
+	dev_kfree_skb_any(skb);
+
+	return ret;
+}
+
+static void mhi_netdev_alloc_work(struct work_struct *work)
+{
+	struct mhi_netdev *mhi_netdev = container_of(work, struct mhi_netdev,
+						   alloc_work);
+	/* sleep about 1 sec and retry, that should be enough time
+	 * for system to reclaim freed memory back.
+	 */
+	const int sleep_ms =  1000;
+	int retry = 60;
+	int ret;
+
+	MSG_LOG("Entered\n");
+	do {
+		ret = mhi_netdev_alloc_skb(mhi_netdev, GFP_KERNEL);
+		/* sleep and try again */
+		if (ret == -ENOMEM) {
+			msleep(sleep_ms);
+			retry--;
+		}
+	} while (ret == -ENOMEM && retry);
+
+	MSG_LOG("Exit with status:%d retry:%d\n", ret, retry);
+}
+
+/* we will recycle buffers */
+static int mhi_netdev_skb_recycle(struct mhi_netdev *mhi_netdev, gfp_t gfp_t)
+{
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+	int no_tre;
+	int ret = 0;
+	struct sk_buff *skb;
+	struct mhi_skb_priv *skb_priv;
+
+	read_lock_bh(&mhi_netdev->pm_lock);
+	if (!mhi_netdev->enabled) {
+		read_unlock_bh(&mhi_netdev->pm_lock);
+		return -EIO;
+	}
+
+	no_tre = mhi_get_no_free_descriptors(mhi_dev, DMA_FROM_DEVICE);
+
+	spin_lock_bh(&mhi_netdev->rx_lock);
+	while (no_tre) {
+		skb = skb_dequeue(&mhi_netdev->rx_allocated);
+
+		/* no free buffers to recycle, reschedule work */
+		if (!skb) {
+			ret = -ENOMEM;
+			goto error_queue;
+		}
+
+		skb_priv = (struct mhi_skb_priv *)(skb->cb);
+		ret = mhi_queue_transfer(mhi_dev, DMA_FROM_DEVICE, skb,
+					 skb_priv->size, MHI_EOT);
+
+		/* failed to queue buffer */
+		if (ret) {
+			MSG_ERR("Failed to queue skb, ret:%d\n", ret);
+			skb_queue_tail(&mhi_netdev->rx_allocated, skb);
+			goto error_queue;
+		}
+
+		no_tre--;
+	}
+
+error_queue:
+	spin_unlock_bh(&mhi_netdev->rx_lock);
+	read_unlock_bh(&mhi_netdev->pm_lock);
+
+	return ret;
+}
+
+static void mhi_netdev_dealloc(struct mhi_netdev *mhi_netdev)
+{
+	struct sk_buff *skb;
+
+	skb = skb_dequeue(&mhi_netdev->rx_allocated);
+	while (skb) {
+		skb->destructor = NULL;
+		kfree_skb(skb);
+		skb = skb_dequeue(&mhi_netdev->rx_allocated);
+	}
+}
+
+static int mhi_netdev_poll(struct napi_struct *napi, int budget)
+{
+	struct net_device *dev = napi->dev;
+	struct mhi_netdev_priv *mhi_netdev_priv = netdev_priv(dev);
+	struct mhi_netdev *mhi_netdev = mhi_netdev_priv->mhi_netdev;
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+	int rx_work = 0;
+	int ret;
+
+	MSG_VERB("Entered\n");
+
+	read_lock_bh(&mhi_netdev->pm_lock);
+
+	if (!mhi_netdev->enabled) {
+		MSG_LOG("interface is disabled!\n");
+		napi_complete(napi);
+		read_unlock_bh(&mhi_netdev->pm_lock);
+		return 0;
+	}
+
+	mhi_device_get(mhi_dev);
+
+	rx_work = mhi_poll(mhi_dev, budget);
+	if (rx_work < 0) {
+		MSG_ERR("Error polling ret:%d\n", rx_work);
+		rx_work = 0;
+		napi_complete(napi);
+		goto exit_poll;
+	}
+
+	/* queue new buffers */
+	ret = mhi_netdev->rx_queue(mhi_netdev, GFP_ATOMIC);
+	if (ret == -ENOMEM) {
+		MSG_LOG("out of tre, queuing bg worker\n");
+		mhi_netdev->stats.alloc_failed++;
+		schedule_work(&mhi_netdev->alloc_work);
+
+	}
+
+	/* complete work if # of packet processed less than allocated budget */
+	if (rx_work < budget)
+		napi_complete(napi);
+	else
+		mhi_netdev->stats.rx_budget_overflow++;
+
+exit_poll:
+	mhi_device_put(mhi_dev);
+	read_unlock_bh(&mhi_netdev->pm_lock);
+
+	MSG_VERB("polled %d pkts\n", rx_work);
+
+	return rx_work;
+}
+
+static int mhi_netdev_open(struct net_device *dev)
+{
+	struct mhi_netdev_priv *mhi_netdev_priv = netdev_priv(dev);
+	struct mhi_netdev *mhi_netdev = mhi_netdev_priv->mhi_netdev;
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+
+	MSG_LOG("Opened net dev interface\n");
+
+	/* tx queue may not necessarily be stopped already
+	 * so stop the queue if tx path is not enabled
+	 */
+	if (!mhi_dev->ul_chan)
+		netif_stop_queue(dev);
+	else
+		netif_start_queue(dev);
+
+	return 0;
+
+}
+
+static int mhi_netdev_change_mtu(struct net_device *dev, int new_mtu)
+{
+	struct mhi_netdev_priv *mhi_netdev_priv = netdev_priv(dev);
+	struct mhi_netdev *mhi_netdev = mhi_netdev_priv->mhi_netdev;
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+
+	if (new_mtu < 0 || mhi_dev->mtu < new_mtu)
+		return -EINVAL;
+
+	dev->mtu = new_mtu;
+	return 0;
+}
+
+static int mhi_netdev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct mhi_netdev_priv *mhi_netdev_priv = netdev_priv(dev);
+	struct mhi_netdev *mhi_netdev = mhi_netdev_priv->mhi_netdev;
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+	int res = 0;
+	struct mhi_skb_priv *tx_priv;
+
+	MSG_VERB("Entered\n");
+
+	tx_priv = (struct mhi_skb_priv *)(skb->cb);
+	tx_priv->mhi_netdev = mhi_netdev;
+	read_lock_bh(&mhi_netdev->pm_lock);
+
+	if (unlikely(!mhi_netdev->enabled)) {
+		/* Only reason interface could be disabled and we get data
+		 * is due to an SSR. We do not want to stop the queue and
+		 * return error. Instead we will flush all the uplink packets
+		 * and return successful
+		 */
+		res = NETDEV_TX_OK;
+		dev_kfree_skb_any(skb);
+		goto mhi_xmit_exit;
+	}
+
+	res = mhi_queue_transfer(mhi_dev, DMA_TO_DEVICE, skb, skb->len,
+				 MHI_EOT);
+	if (res) {
+		MSG_VERB("Failed to queue with reason:%d\n", res);
+		netif_stop_queue(dev);
+		mhi_netdev->stats.tx_full++;
+		res = NETDEV_TX_BUSY;
+		goto mhi_xmit_exit;
+	}
+
+	mhi_netdev->stats.tx_pkts++;
+
+mhi_xmit_exit:
+	read_unlock_bh(&mhi_netdev->pm_lock);
+	MSG_VERB("Exited\n");
+
+	return res;
+}
+
+static int mhi_netdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	int rc = 0;
+
+	switch (cmd) {
+	default:
+		/* don't fail any IOCTL right now */
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+static const struct net_device_ops mhi_netdev_ops_ip = {
+	.ndo_open = mhi_netdev_open,
+	.ndo_start_xmit = mhi_netdev_xmit,
+	.ndo_do_ioctl = mhi_netdev_ioctl,
+	.ndo_change_mtu = mhi_netdev_change_mtu,
+	.ndo_set_mac_address = 0,
+	.ndo_validate_addr = 0,
+};
+
+static void mhi_netdev_setup(struct net_device *dev)
+{
+	dev->netdev_ops = &mhi_netdev_ops_ip;
+	ether_setup(dev);
+
+	/* set this after calling ether_setup */
+	dev->header_ops = 0;  /* No header */
+	dev->type = ARPHRD_RAWIP;
+	dev->hard_header_len = 0;
+	dev->addr_len = 0;
+	dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
+	dev->watchdog_timeo = WATCHDOG_TIMEOUT;
+}
+
+/* enable mhi_netdev netdev, call only after grabbing mhi_netdev.mutex */
+static int mhi_netdev_enable_iface(struct mhi_netdev *mhi_netdev)
+{
+	int ret = 0;
+	char ifalias[IFALIASZ];
+	char ifname[IFNAMSIZ];
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+	int no_tre;
+
+	MSG_LOG("Prepare the channels for transfer\n");
+
+	ret = mhi_prepare_for_transfer(mhi_dev);
+	if (ret) {
+		MSG_ERR("Failed to start TX chan ret %d\n", ret);
+		goto mhi_failed_to_start;
+	}
+
+	/* first time enabling the node */
+	if (!mhi_netdev->ndev) {
+		struct mhi_netdev_priv *mhi_netdev_priv;
+
+		snprintf(ifalias, sizeof(ifalias), "%s_%04x_%02u.%02u.%02u_%u",
+			 mhi_netdev->interface_name, mhi_dev->dev_id,
+			 mhi_dev->domain, mhi_dev->bus, mhi_dev->slot,
+			 mhi_netdev->alias);
+
+		snprintf(ifname, sizeof(ifname), "%s%%d",
+			 mhi_netdev->interface_name);
+
+		rtnl_lock();
+		mhi_netdev->ndev = alloc_netdev(sizeof(*mhi_netdev_priv),
+					ifname, NET_NAME_PREDICTABLE,
+					mhi_netdev_setup);
+
+		if (!mhi_netdev->ndev) {
+			ret = -ENOMEM;
+			rtnl_unlock();
+			goto net_dev_alloc_fail;
+		}
+
+		mhi_netdev->ndev->mtu = mhi_dev->mtu;
+		SET_NETDEV_DEV(mhi_netdev->ndev, &mhi_dev->dev);
+		dev_set_alias(mhi_netdev->ndev, ifalias, strlen(ifalias));
+		mhi_netdev_priv = netdev_priv(mhi_netdev->ndev);
+		mhi_netdev_priv->mhi_netdev = mhi_netdev;
+		rtnl_unlock();
+
+		netif_napi_add(mhi_netdev->ndev, &mhi_netdev->napi,
+			       mhi_netdev_poll, NAPI_POLL_WEIGHT);
+		ret = register_netdev(mhi_netdev->ndev);
+		if (ret) {
+			MSG_ERR("Network device registration failed\n");
+			goto net_dev_reg_fail;
+		}
+
+		skb_queue_head_init(&mhi_netdev->rx_allocated);
+	}
+
+	write_lock_irq(&mhi_netdev->pm_lock);
+	mhi_netdev->enabled =  true;
+	write_unlock_irq(&mhi_netdev->pm_lock);
+
+	/* queue buffer for rx path */
+	no_tre = mhi_get_no_free_descriptors(mhi_dev, DMA_FROM_DEVICE);
+	ret = mhi_netdev_alloc_skb(mhi_netdev, GFP_KERNEL);
+	if (ret)
+		schedule_work(&mhi_netdev->alloc_work);
+
+	/* if we recycle prepare one more set */
+	if (mhi_netdev->recycle_buf)
+		for (; no_tre >= 0; no_tre--) {
+			struct sk_buff *skb = alloc_skb(mhi_netdev->mru,
+							GFP_KERNEL);
+			struct mhi_skb_priv *skb_priv;
+
+			if (!skb)
+				break;
+
+			skb_priv = (struct mhi_skb_priv *)skb->cb;
+			skb_priv->buf = skb->data;
+			skb_priv->size = mhi_netdev->mru;
+			skb_priv->mhi_netdev = mhi_netdev;
+			skb->dev = mhi_netdev->ndev;
+			skb->destructor = mhi_netdev_skb_destructor;
+			skb_queue_tail(&mhi_netdev->rx_allocated, skb);
+		}
+
+	napi_enable(&mhi_netdev->napi);
+
+	MSG_LOG("Exited.\n");
+
+	return 0;
+
+net_dev_reg_fail:
+	netif_napi_del(&mhi_netdev->napi);
+	free_netdev(mhi_netdev->ndev);
+	mhi_netdev->ndev = NULL;
+
+net_dev_alloc_fail:
+	mhi_unprepare_from_transfer(mhi_dev);
+
+mhi_failed_to_start:
+	MSG_ERR("Exited ret %d.\n", ret);
+
+	return ret;
+}
+
+static void mhi_netdev_xfer_ul_cb(struct mhi_device *mhi_dev,
+				  struct mhi_result *mhi_result)
+{
+	struct mhi_netdev *mhi_netdev = mhi_device_get_devdata(mhi_dev);
+	struct sk_buff *skb = mhi_result->buf_addr;
+	struct net_device *ndev = mhi_netdev->ndev;
+
+	ndev->stats.tx_packets++;
+	ndev->stats.tx_bytes += skb->len;
+	dev_kfree_skb(skb);
+
+	if (netif_queue_stopped(ndev))
+		netif_wake_queue(ndev);
+}
+
+static int mhi_netdev_process_fragment(struct mhi_netdev *mhi_netdev,
+				      struct sk_buff *skb)
+{
+	struct sk_buff *temp_skb;
+
+	if (mhi_netdev->frag_skb) {
+		/* merge the new skb into the old fragment */
+		temp_skb = skb_copy_expand(mhi_netdev->frag_skb, 0, skb->len,
+					   GFP_ATOMIC);
+		if (!temp_skb) {
+			dev_kfree_skb(mhi_netdev->frag_skb);
+			mhi_netdev->frag_skb = NULL;
+			return -ENOMEM;
+		}
+
+		dev_kfree_skb_any(mhi_netdev->frag_skb);
+		mhi_netdev->frag_skb = temp_skb;
+		memcpy(skb_put(mhi_netdev->frag_skb, skb->len), skb->data,
+		       skb->len);
+	} else {
+		mhi_netdev->frag_skb = skb_copy(skb, GFP_ATOMIC);
+		if (!mhi_netdev->frag_skb)
+			return -ENOMEM;
+	}
+
+	mhi_netdev->stats.rx_frag++;
+
+	return 0;
+}
+
+static void mhi_netdev_xfer_dl_cb(struct mhi_device *mhi_dev,
+				  struct mhi_result *mhi_result)
+{
+	struct mhi_netdev *mhi_netdev = mhi_device_get_devdata(mhi_dev);
+	struct sk_buff *skb = mhi_result->buf_addr;
+	struct net_device *dev = mhi_netdev->ndev;
+	int ret = 0;
+
+	if (mhi_result->transaction_status == -ENOTCONN) {
+		dev_kfree_skb(skb);
+		return;
+	}
+
+	skb_put(skb, mhi_result->bytes_xferd);
+	dev->stats.rx_packets++;
+	dev->stats.rx_bytes += mhi_result->bytes_xferd;
+
+	/* merge skb's together, it's a chain transfer */
+	if (mhi_result->transaction_status == -EOVERFLOW ||
+	    mhi_netdev->frag_skb) {
+		ret = mhi_netdev_process_fragment(mhi_netdev, skb);
+
+		/* recycle the skb */
+		if (mhi_netdev->recycle_buf)
+			mhi_netdev_skb_destructor(skb);
+		else
+			dev_kfree_skb(skb);
+
+		if (ret)
+			return;
+	}
+
+	/* more data will come, don't submit the buffer */
+	if (mhi_result->transaction_status == -EOVERFLOW)
+		return;
+
+	if (mhi_netdev->frag_skb) {
+		skb = mhi_netdev->frag_skb;
+		skb->dev = dev;
+		mhi_netdev->frag_skb = NULL;
+	}
+
+	skb->protocol = mhi_netdev_ip_type_trans(skb);
+	netif_receive_skb(skb);
+}
+
+static void mhi_netdev_status_cb(struct mhi_device *mhi_dev, enum MHI_CB mhi_cb)
+{
+	struct mhi_netdev *mhi_netdev = mhi_device_get_devdata(mhi_dev);
+
+	if (mhi_cb != MHI_CB_PENDING_DATA)
+		return;
+
+	if (napi_schedule_prep(&mhi_netdev->napi)) {
+		__napi_schedule(&mhi_netdev->napi);
+		mhi_netdev->stats.rx_int++;
+		return;
+	}
+
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+struct dentry *dentry;
+
+static int mhi_netdev_debugfs_trigger_reset(void *data, u64 val)
+{
+	struct mhi_netdev *mhi_netdev = data;
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+	int ret;
+
+	MSG_LOG("Triggering channel reset\n");
+
+	/* disable the interface so no data processing */
+	write_lock_irq(&mhi_netdev->pm_lock);
+	mhi_netdev->enabled = false;
+	write_unlock_irq(&mhi_netdev->pm_lock);
+	napi_disable(&mhi_netdev->napi);
+
+	/* disable all hardware channels */
+	mhi_unprepare_from_transfer(mhi_dev);
+
+	/* clean up all alocated buffers */
+	mhi_netdev_dealloc(mhi_netdev);
+
+	MSG_LOG("Restarting iface\n");
+
+	ret = mhi_netdev_enable_iface(mhi_netdev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(mhi_netdev_debugfs_trigger_reset_fops, NULL,
+			mhi_netdev_debugfs_trigger_reset, "%llu\n");
+
+static void mhi_netdev_create_debugfs(struct mhi_netdev *mhi_netdev)
+{
+	char node_name[32];
+	int i;
+	const umode_t mode = 0600;
+	struct dentry *file;
+	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
+
+	const struct {
+		char *name;
+		u32 *ptr;
+	} debugfs_table[] = {
+		{
+			"rx_int",
+			&mhi_netdev->stats.rx_int
+		},
+		{
+			"tx_full",
+			&mhi_netdev->stats.tx_full
+		},
+		{
+			"tx_pkts",
+			&mhi_netdev->stats.tx_pkts
+		},
+		{
+			"rx_budget_overflow",
+			&mhi_netdev->stats.rx_budget_overflow
+		},
+		{
+			"rx_fragmentation",
+			&mhi_netdev->stats.rx_frag
+		},
+		{
+			"alloc_failed",
+			&mhi_netdev->stats.alloc_failed
+		},
+		{
+			NULL, NULL
+		},
+	};
+
+	/* Both tx & rx client handle contain same device info */
+	snprintf(node_name, sizeof(node_name), "%s_%04x_%02u.%02u.%02u_%u",
+		 mhi_netdev->interface_name, mhi_dev->dev_id, mhi_dev->domain,
+		 mhi_dev->bus, mhi_dev->slot, mhi_netdev->alias);
+
+	if (IS_ERR_OR_NULL(dentry))
+		return;
+
+	mhi_netdev->dentry = debugfs_create_dir(node_name, dentry);
+	if (IS_ERR_OR_NULL(mhi_netdev->dentry))
+		return;
+
+	file = debugfs_create_u32("msg_lvl", mode, mhi_netdev->dentry,
+				  (u32 *)&mhi_netdev->msg_lvl);
+	if (IS_ERR_OR_NULL(file))
+		return;
+
+	/* Add debug stats table */
+	for (i = 0; debugfs_table[i].name; i++) {
+		file = debugfs_create_u32(debugfs_table[i].name, mode,
+					  mhi_netdev->dentry,
+					  debugfs_table[i].ptr);
+		if (IS_ERR_OR_NULL(file))
+			return;
+	}
+
+	debugfs_create_file("reset", mode, mhi_netdev->dentry, mhi_netdev,
+			    &mhi_netdev_debugfs_trigger_reset_fops);
+}
+
+static void mhi_netdev_create_debugfs_dir(void)
+{
+	dentry = debugfs_create_dir(MHI_NETDEV_DRIVER_NAME, 0);
+}
+
+#else
+
+static void mhi_netdev_create_debugfs(struct mhi_netdev_private *mhi_netdev)
+{
+}
+
+static void mhi_netdev_create_debugfs_dir(void)
+{
+}
+
+#endif
+
+static void mhi_netdev_remove(struct mhi_device *mhi_dev)
+{
+	struct mhi_netdev *mhi_netdev = mhi_device_get_devdata(mhi_dev);
+
+	MSG_LOG("Remove notification received\n");
+
+	write_lock_irq(&mhi_netdev->pm_lock);
+	mhi_netdev->enabled = false;
+	write_unlock_irq(&mhi_netdev->pm_lock);
+
+	napi_disable(&mhi_netdev->napi);
+	netif_napi_del(&mhi_netdev->napi);
+	mhi_netdev_dealloc(mhi_netdev);
+	unregister_netdev(mhi_netdev->ndev);
+	free_netdev(mhi_netdev->ndev);
+	flush_work(&mhi_netdev->alloc_work);
+
+	if (!IS_ERR_OR_NULL(mhi_netdev->dentry))
+		debugfs_remove_recursive(mhi_netdev->dentry);
+}
+
+static int mhi_netdev_probe(struct mhi_device *mhi_dev,
+			    const struct mhi_device_id *id)
+{
+	int ret;
+	struct mhi_netdev *mhi_netdev;
+	struct device_node *of_node = mhi_dev->dev.of_node;
+	char node_name[32];
+
+	if (!of_node)
+		return -ENODEV;
+
+	mhi_netdev = devm_kzalloc(&mhi_dev->dev, sizeof(*mhi_netdev),
+				  GFP_KERNEL);
+	if (!mhi_netdev)
+		return -ENOMEM;
+
+	mhi_netdev->alias = of_alias_get_id(of_node, "mhi_netdev");
+	if (mhi_netdev->alias < 0)
+		return -ENODEV;
+
+	mhi_netdev->mhi_dev = mhi_dev;
+	mhi_device_set_devdata(mhi_dev, mhi_netdev);
+
+	ret = of_property_read_u32(of_node, "mhi,mru", &mhi_netdev->mru);
+	if (ret)
+		return -ENODEV;
+
+	ret = of_property_read_string(of_node, "mhi,interface-name",
+				      &mhi_netdev->interface_name);
+	if (ret)
+		mhi_netdev->interface_name = mhi_netdev_driver.driver.name;
+
+	mhi_netdev->recycle_buf = of_property_read_bool(of_node,
+							"mhi,recycle-buf");
+	mhi_netdev->rx_queue = mhi_netdev->recycle_buf ?
+		mhi_netdev_skb_recycle : mhi_netdev_alloc_skb;
+
+	spin_lock_init(&mhi_netdev->rx_lock);
+	rwlock_init(&mhi_netdev->pm_lock);
+	INIT_WORK(&mhi_netdev->alloc_work, mhi_netdev_alloc_work);
+
+	/* create ipc log buffer */
+	snprintf(node_name, sizeof(node_name), "%s_%04x_%02u.%02u.%02u_%u",
+		 mhi_netdev->interface_name, mhi_dev->dev_id, mhi_dev->domain,
+		 mhi_dev->bus, mhi_dev->slot, mhi_netdev->alias);
+
+	mhi_netdev->msg_lvl = MHI_MSG_LVL_ERROR;
+
+	/* setup network interface */
+	ret = mhi_netdev_enable_iface(mhi_netdev);
+	if (ret)
+		return ret;
+
+	mhi_netdev_create_debugfs(mhi_netdev);
+
+	return 0;
+}
+
+static const struct mhi_device_id mhi_netdev_match_table[] = {
+	{ .chan = "IP_HW0" },
+	{ .chan = "IP_HW_ADPL" },
+	{ NULL },
+};
+
+static struct mhi_driver mhi_netdev_driver = {
+	.id_table = mhi_netdev_match_table,
+	.probe = mhi_netdev_probe,
+	.remove = mhi_netdev_remove,
+	.ul_xfer_cb = mhi_netdev_xfer_ul_cb,
+	.dl_xfer_cb = mhi_netdev_xfer_dl_cb,
+	.status_cb = mhi_netdev_status_cb,
+	.driver = {
+		.name = "mhi_netdev",
+		.owner = THIS_MODULE,
+	}
+};
+
+static int __init mhi_netdev_init(void)
+{
+	mhi_netdev_create_debugfs_dir();
+
+	return mhi_driver_register(&mhi_netdev_driver);
+}
+module_init(mhi_netdev_init);
+
+MODULE_DESCRIPTION("MHI NETDEV Network Interface");
+MODULE_LICENSE("GPL v2");
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux