Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 37d0e34a authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "msm: mhi_dev: Add MHI device network interface driver"

parents f5be2cda f5ab6ca3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -4,3 +4,4 @@ obj-y += mhi.o
obj-y += mhi_ring.o
obj-y += mhi_uci.o
obj-y += mhi_sm.o
obj-y += mhi_dev_net.o
+4 −0
Original line number Diff line number Diff line
@@ -1850,6 +1850,10 @@ static void mhi_dev_enable(struct work_struct *work)
	}

	mhi_uci_init();
	/*Enable MHI dev network stack Interface*/
	rc = mhi_dev_net_interface_init();
	if (rc)
		pr_err("%s Failed to initialize mhi_dev_net iface\n", __func__);

	rc = ep_pcie_get_msi_config(mhi->phandle, &msi_cfg);
	if (rc)
+12 −3
Original line number Diff line number Diff line
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2015-2017, 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
@@ -272,7 +272,7 @@ struct mhi_config {
#define HW_CHANNEL_END			107
#define MHI_ENV_VALUE			2
#define MHI_MASK_ROWS_CH_EV_DB		4
#define TRB_MAX_DATA_SIZE		4096
#define TRB_MAX_DATA_SIZE		8192

/* Possible ring element types */
union mhi_dev_ring_element_type {
@@ -618,7 +618,9 @@ enum mhi_client_channel {
	MHI_CLIENT_CSVT_IN = 43,
	MHI_CLIENT_SMCT_OUT = 44,
	MHI_CLIENT_SMCT_IN = 45,
	MHI_MAX_SOFTWARE_CHANNELS = 46,
	MHI_CLIENT_IP_SW_4_OUT  = 46,
	MHI_CLIENT_IP_SW_4_IN  = 47,
	MHI_MAX_SOFTWARE_CHANNELS = 48,
	MHI_CLIENT_TEST_OUT = 60,
	MHI_CLIENT_TEST_IN = 61,
	MHI_CLIENT_RESERVED_1_LOWER = 62,
@@ -793,6 +795,7 @@ void mhi_dev_read_from_host(struct mhi_dev *mhi,
 * @buf:	Data buffer that needs to be read from the host.
 * @size:	Data buffer size.
 */

void mhi_ring_set_cb(struct mhi_dev_ring *ring,
			void (*ring_cb)(struct mhi_dev *dev,
			union mhi_dev_ring_element_type *el, void *ctx));
@@ -1131,6 +1134,12 @@ int mhi_pcie_config_db_routing(struct mhi_dev *mhi);
 */
int mhi_uci_init(void);

/**
 * mhi_dev_net_interface_init() - Enable Network stack interface for MHI device
 *		which exposes the virtual network interface.
 **/
int mhi_dev_net_interface_init(void);

void mhi_dev_notify_a7_event(struct mhi_dev *mhi);

#endif /* _MHI_H_ */
+559 −0
Original line number Diff line number Diff line
/* Copyright (c) 2017, 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.
 */
/*
 * MHI Device Network interface
 */

#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/ipc_logging.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/ktime.h>

#include "mhi.h"

#define MHI_NET_DRIVER_NAME  "mhi_dev_net_drv"
#define MHI_NET_DEV_NAME     "mhi_dev_net%d"
#define MHI_NET_DEFAULT_MTU   4000
#define MHI_NET_IPC_PAGES     (100)

enum mhi_dev_net_dbg_lvl {
	MHI_VERBOSE = 0x1,
	MHI_INFO = 0x2,
	MHI_DBG = 0x3,
	MHI_WARNING = 0x4,
	MHI_ERROR = 0x5,
	MHI_CRITICAL = 0x6,
	MSG_NET_reserved = 0x80000000
};

static enum mhi_dev_net_dbg_lvl mhi_net_msg_lvl = MHI_CRITICAL;
static enum mhi_dev_net_dbg_lvl mhi_net_ipc_log_lvl = MHI_INFO;
static void *mhi_net_ipc_log;

enum mhi_chan_dir {
	MHI_DIR_INVALID = 0x0,
	MHI_DIR_OUT = 0x1,
	MHI_DIR_IN = 0x2,
	MHI_DIR__reserved = 0x80000000
};

struct mhi_dev_net_chan_attr {
	/* SW maintained channel id */
	enum mhi_client_channel chan_id;
	/* maximum buffer size for this channel */
	size_t max_packet_size;
	/* direction of the channel, see enum mhi_chan_dir */
	enum mhi_chan_dir dir;
};

#define CHAN_TO_CLIENT(_CHAN_NR) (_CHAN_NR / 2)

#define mhi_dev_net_log(_msg_lvl, _msg, ...) do { \
	if (_msg_lvl >= mhi_net_msg_lvl) { \
		pr_err("[%s] "_msg, __func__, ##__VA_ARGS__); \
	} \
	if (mhi_net_ipc_log && (_msg_lvl >= mhi_net_ipc_log_lvl)) { \
		ipc_log_string(mhi_net_ipc_log,                     \
			"[%s] " _msg, __func__, ##__VA_ARGS__);     \
	} \
} while (0)

module_param(mhi_net_msg_lvl , uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(mhi_net_msg_lvl, "mhi dev net dbg lvl");

module_param(mhi_net_ipc_log_lvl, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(mhi_net_ipc_log_lvl, "mhi dev net dbg lvl");

struct mhi_dev_net_client {
	/* write channel - always even*/
	u32 out_chan;
	/* read channel - always odd */
	u32 in_chan;
	struct mhi_dev_client *out_handle;
	struct mhi_dev_client *in_handle;
	/*process pendig packets */
	struct workqueue_struct *pending_pckt_wq;
	struct work_struct       xmit_work;
	/*Read data from host work queue*/
	struct workqueue_struct *read_data_wq;
	struct work_struct       dev_read_wrk;
	atomic_t pckt_queue_count;
	atomic_t  rx_enabled;
	atomic_t  tx_enabled;
	struct net_device *dev;
	struct sk_buff_head tx_buffers;
	struct mhi_dev_net_ctxt *net_ctxt;
	/*To check write channel is empty or not*/
	spinlock_t write_chan_lock;
	struct mutex in_chan_lock;
	struct mutex out_chan_lock;
};

struct mhi_dev_net_ctxt {
	struct mhi_dev_net_chan_attr chan_attr[MHI_MAX_SOFTWARE_CHANNELS];
	struct mhi_dev_net_client *client_handle;
	void (*net_event_notifier)(struct mhi_dev_client_cb_reason *cb);
};

static struct mhi_dev_net_ctxt mhi_net_ctxt;
static ssize_t mhi_dev_net_client_read(struct mhi_dev_net_client *);

static void mhi_dev_net_rx_scheduler(struct work_struct *work)
{
	struct mhi_dev_net_client *mhi_dev_net_client = container_of(work,
			struct mhi_dev_net_client, dev_read_wrk);

	if (mhi_dev_net_client)
		mhi_dev_net_client_read(mhi_dev_net_client);
	else
		mhi_dev_net_log(MHI_CRITICAL, "mhi_dev_net client is NULL\n");
}

static int mhi_dev_net_init_ch_attributes(struct mhi_dev_net_ctxt *mhi_ctxt)
{
	u32 channel = 0;
	struct mhi_dev_net_chan_attr *chan_attrib = NULL;

	channel = MHI_CLIENT_IP_SW_4_OUT;
	chan_attrib = &mhi_ctxt->chan_attr[channel];
	chan_attrib->dir = MHI_DIR_OUT;
	chan_attrib->chan_id = channel;
	chan_attrib->max_packet_size = TRB_MAX_DATA_SIZE;
	mhi_dev_net_log(MHI_INFO, "Write chan attributes dir %d chan_id %d\n",
			chan_attrib->dir, chan_attrib->chan_id);

	channel = MHI_CLIENT_IP_SW_4_IN;
	chan_attrib = &mhi_ctxt->chan_attr[channel];
	chan_attrib->dir = MHI_DIR_IN;
	chan_attrib->chan_id = channel;
	chan_attrib->max_packet_size = TRB_MAX_DATA_SIZE;
	mhi_dev_net_log(MHI_INFO, "Read chan attributes dir %d chan_id %d\n",
			chan_attrib->dir, chan_attrib->chan_id);
	return 0;
}

static void process_queue_packets(struct work_struct *work)
{
	u32 xfer_data = 0;
	ktime_t start_time;

	struct mhi_dev_net_client *mhi_net_client = container_of(work,
			struct mhi_dev_net_client, xmit_work);
	if (mhi_dev_channel_isempty(mhi_net_client->in_handle)) {
		netif_stop_queue(mhi_net_client->dev);
		return;
	}
	while (!skb_queue_empty(&(mhi_net_client->tx_buffers))) {
		struct sk_buff *skb =
			skb_dequeue(&(mhi_net_client->tx_buffers));
		atomic_dec(&mhi_net_client->pckt_queue_count);
		if (!skb) {
			mhi_dev_net_log(MHI_CRITICAL,
					"skb dequeue returned NULL\n");
			return;
		}
		start_time = ktime_get();
		xfer_data =
			mhi_dev_write_channel(mhi_net_client->in_handle,
					skb->data, skb->len);
		if (xfer_data != skb->len) {
			pr_err("Failed to write skb len %d xfered data %d\n",
					skb->len, xfer_data);
			kfree_skb(skb);
			return;
		}
		mhi_net_client->dev->stats.tx_packets++;
		mhi_dev_net_log(MHI_VERBOSE, "write_chan time = %lld\n",
			ktime_to_us(ktime_sub(ktime_get(), start_time)));
		kfree_skb(skb);
		/* Check if free buffers availability */
		if (mhi_dev_channel_isempty(mhi_net_client->in_handle)) {
			netif_stop_queue(mhi_net_client->dev);
			break;
		}
	} /* While TX queue is not empty */
}

static void mhi_dev_net_event_notifier(struct mhi_dev_client_cb_reason *reason)
{
	struct mhi_dev_net_client *mhi_handle = NULL;

	if (reason->reason == MHI_DEV_TRE_AVAILABLE) {
		mhi_handle = mhi_net_ctxt.client_handle;
		mhi_dev_net_log(MHI_VERBOSE,
				"recived TRE available event for chan %d\n",
				mhi_handle->in_chan);
		if (reason->ch_id % 2) {
			spin_lock(&mhi_handle->write_chan_lock);
			if (netif_queue_stopped(mhi_handle->dev)) {
				if (atomic_read(&mhi_handle->pckt_queue_count))
					queue_work(mhi_handle->pending_pckt_wq,
							&mhi_handle->xmit_work);
				else
					netif_wake_queue(mhi_handle->dev);
			}
			spin_unlock(&mhi_handle->write_chan_lock);
		} else
			queue_work(mhi_handle->read_data_wq,
					&mhi_handle->dev_read_wrk);
	}
}

static __be16 mhi_dev_net_eth_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 ssize_t mhi_dev_net_client_read(struct mhi_dev_net_client *mhi_handle)
{
	int bytes_avail = 0;
	int ret_val = 0;
	u32 chan = 0;
	uint32_t buf_size = TRB_MAX_DATA_SIZE;
	uint32_t chained = 0;
	struct mhi_dev_client *client_handle = NULL;
	struct sk_buff *skb_buff;
	ktime_t start_time;

	client_handle = mhi_handle->out_handle;
	chan = mhi_handle->out_chan;
	if (!atomic_read(&mhi_handle->rx_enabled))
		return -EPERM;
	do {
		start_time = ktime_get();
		skb_buff = alloc_skb(MHI_NET_DEFAULT_MTU, GFP_ATOMIC);
		bytes_avail = mhi_dev_read_channel(client_handle,
				skb_buff->data,
				buf_size, &chained);
		mhi_dev_net_log(MHI_VERBOSE,
				"dev_read_channel time = %lld\n",
			ktime_to_us(ktime_sub(ktime_get(), start_time)));
		if (bytes_avail < 0) {
			pr_err("Failed to read chan %d bytes_avail = %d\n",
					chan, bytes_avail);
			ret_val = -EIO;
			break;
		}
		/* no data to send to network stack, break */
		if (!bytes_avail)
			break;

		skb_buff->len = bytes_avail;
		mhi_dev_net_log(MHI_VERBOSE, "reading frm chan %d buff size %d",
				chan, buf_size);
		mhi_dev_net_log(MHI_VERBOSE, "bytes_read %d chained %d",
				bytes_avail, chained);
		skb_buff->protocol =
			mhi_dev_net_eth_type_trans(skb_buff);
		skb_put(skb_buff, bytes_avail);
		mhi_handle->dev->stats.rx_packets++;
		skb_buff->dev = mhi_handle->dev;
		start_time = ktime_get();
		netif_rx(skb_buff);
	} while (1);
	/* coming out while only in case of no data or error */
	kfree_skb(skb_buff);
	return ret_val;
}

static int mhi_dev_net_open(struct net_device *dev)
{
	struct mhi_dev_net_client *mhi_dev_net_ptr =
		*(struct mhi_dev_net_client **)netdev_priv(dev);
	mhi_dev_net_log(MHI_INFO,
			"%s mhi_net_dev interface is up for IN %d OUT %d\n",
			__func__, mhi_dev_net_ptr->out_chan,
			mhi_dev_net_ptr->in_chan);
	netif_start_queue(dev);
	return 0;
}

static int mhi_dev_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct mhi_dev_net_client *mhi_dev_net_ptr =
			*(struct mhi_dev_net_client **)netdev_priv(dev);

	mhi_dev_net_log(MHI_VERBOSE, "SKB received\n");
	if (skb->len <= 0) {
		mhi_dev_net_log(MHI_ERROR,
				"Invalid skb received freeing skb\n");
		kfree_skb(skb);
		return NETDEV_TX_OK;
	}
	skb_queue_tail(&(mhi_dev_net_ptr->tx_buffers), skb);
	atomic_inc(&mhi_dev_net_ptr->pckt_queue_count);
	queue_work(mhi_dev_net_ptr->pending_pckt_wq,
			&mhi_dev_net_ptr->xmit_work);
	mhi_dev_net_log(MHI_VERBOSE, "Exiting from transmit function\n");
	return 0;
}

static int mhi_dev_net_stop(struct net_device *dev)
{
	netif_stop_queue(dev);
	mhi_dev_net_log(MHI_VERBOSE, "mhi_dev_net interface is down\n");
	return 0;
}

static int mhi_dev_net_change_mtu(struct net_device *dev, int new_mtu)
{
	if (0 > new_mtu || MHI_NET_DEFAULT_MTU < new_mtu)
		return -EINVAL;
	dev->mtu = new_mtu;
	return 0;
}

static const struct net_device_ops mhi_dev_net_ops_ip = {
	.ndo_open = mhi_dev_net_open,
	.ndo_stop = mhi_dev_net_stop,
	.ndo_start_xmit = mhi_dev_net_xmit,
	.ndo_change_mtu = mhi_dev_net_change_mtu,
	.ndo_set_mac_address = 0,
	.ndo_validate_addr = 0,
};

static void mhi_dev_net_setup(struct net_device *dev)
{
	dev->netdev_ops = &mhi_dev_net_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->mtu = MHI_NET_DEFAULT_MTU;
	dev->addr_len = 0;
	dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
}

static int mhi_dev_net_enable_iface(struct mhi_dev_net_client *mhi_dev_net_ptr)
{
	int ret = 0;
	struct mhi_dev_net_client **mhi_dev_net_ctxt = NULL;
	struct net_device *netdev;

	if (!mhi_dev_net_ptr)
		return -EINVAL;

	/* Initialize skb list head to queue the packets for mhi dev client */
	skb_queue_head_init(&(mhi_dev_net_ptr->tx_buffers));

	mhi_dev_net_log(MHI_INFO,
			"mhi_dev_net interface registration\n");
	netdev = alloc_netdev(sizeof(struct mhi_dev_net_client),
			MHI_NET_DEV_NAME, NET_NAME_PREDICTABLE,
			mhi_dev_net_setup);
	if (!netdev) {
		pr_err("Failed to allocate netdev for mhi_dev_net\n");
		goto net_dev_alloc_fail;
	}

	mhi_dev_net_ctxt = netdev_priv(netdev);
	mhi_dev_net_ptr->dev = netdev;
	*mhi_dev_net_ctxt = mhi_dev_net_ptr;
	ret = register_netdev(mhi_dev_net_ptr->dev);
	if (ret) {
		pr_err("Failed to register mhi_dev_net device\n");
		goto net_dev_reg_fail;
	}
	mhi_dev_net_log(MHI_INFO, "Successfully registred mhi_dev_net\n");
	return 0;

net_dev_reg_fail:
	free_netdev(mhi_dev_net_ptr->dev);
net_dev_alloc_fail:
	mhi_dev_close_channel(mhi_dev_net_ptr->in_handle);
	mhi_dev_close_channel(mhi_dev_net_ptr->out_handle);
	mhi_dev_net_ptr->dev = NULL;
	return -ENOMEM;
}

static int mhi_dev_net_open_channels(struct mhi_dev_net_client *client)
{
	int rc = 0;
	int ret = 0;

	mhi_dev_net_log(MHI_DBG, "opening OUT %d IN %d channels\n",
			client->out_chan,
			client->in_chan);
	mutex_lock(&client->out_chan_lock);
	mutex_lock(&client->in_chan_lock);
	mhi_dev_net_log(MHI_DBG,
			"Initializing inbound chan %d.\n",
			client->in_chan);

	rc = mhi_dev_open_channel(client->out_chan, &client->out_handle,
			mhi_net_ctxt.net_event_notifier);
	if (rc < 0) {
		mhi_dev_net_log(MHI_ERROR,
				"Failed to open chan %d, ret 0x%x\n",
				client->out_chan, rc);
		goto handle_not_rdy_err;
	} else
		atomic_set(&client->rx_enabled, 1);

	rc = mhi_dev_open_channel(client->in_chan, &client->in_handle,
			mhi_net_ctxt.net_event_notifier);
	if (rc < 0) {
		mhi_dev_net_log(MHI_ERROR,
				"Failed to open chan %d, ret 0x%x\n",
				client->in_chan, rc);
		goto handle_in_err;
	} else
		atomic_set(&client->tx_enabled, 1);

	mutex_unlock(&client->in_chan_lock);
	mutex_unlock(&client->out_chan_lock);
	mhi_dev_net_log(MHI_INFO, "IN %d, OUT %d channels are opened",
			client->in_chan, client->out_chan);
	if (atomic_read(&client->tx_enabled)) {
		ret = mhi_dev_net_enable_iface(client);
		if (ret < 0)
			mhi_dev_net_log(MHI_ERROR,
					"failed to enable mhi_dev_net iface\n");
	}
	return ret;
handle_in_err:
	mhi_dev_close_channel(client->out_handle);
	mutex_unlock(&client->in_chan_lock);
	mutex_unlock(&client->out_chan_lock);
handle_not_rdy_err:
	mutex_unlock(&client->in_chan_lock);
	mutex_unlock(&client->out_chan_lock);
	return rc;
}

static int mhi_dev_net_close(void)
{
	struct mhi_dev_net_client *client;

	mhi_dev_net_log(MHI_INFO,
			"mhi_dev_net module is removed\n");
	client = mhi_net_ctxt.client_handle;
	mhi_dev_close_channel(client->out_handle);
	mhi_dev_close_channel(client->in_handle);
	atomic_set(&client->tx_enabled, 0);
	atomic_set(&client->rx_enabled, 0);
	if (client->dev != 0) {
		netif_stop_queue(client->dev);
		unregister_netdev(client->dev);
		free_netdev(client->dev);
		client->dev = 0;
	}
	/* freeing mhi client and IPC context */
	kfree(client);
	kfree(mhi_net_ipc_log);
	return 0;
}

static int mhi_dev_net_rgstr_client(struct mhi_dev_net_client *client, int idx)
{
	client->out_chan = idx;
	client->in_chan = idx + 1;
	mutex_init(&client->in_chan_lock);
	mutex_init(&client->out_chan_lock);
	spin_lock_init(&client->write_chan_lock);
	mhi_dev_net_log(MHI_INFO, "Registering out %d, In %d channels\n",
			client->out_chan, client->in_chan);

	/* Open IN and OUT channels for Network client*/
	mhi_dev_net_open_channels(client);
	return 0;
}

int mhi_dev_net_interface_init(void)
{
	int ret_val = 0;
	int index = 0;
	struct mhi_dev_net_client *mhi_net_client = NULL;

	mhi_net_client = kzalloc(sizeof(struct mhi_dev_net_client), GFP_KERNEL);
	if (mhi_net_client < 0)
		return -ENOMEM;

	mhi_net_ipc_log = ipc_log_context_create(MHI_NET_IPC_PAGES,
						"mhi-net", 0);
	if (mhi_net_ipc_log == NULL)
		mhi_dev_net_log(MHI_DBG,
				"Failed to create IPC logging for mhi_dev_net\n");
	mhi_net_ctxt.client_handle = mhi_net_client;

	/*Process pending packet work queue*/
	mhi_net_client->pending_pckt_wq =
		create_singlethread_workqueue("pending_xmit_pckt_wq");
	INIT_WORK(&mhi_net_client->xmit_work, process_queue_packets);

	/* read data from host when event trigger */
	mhi_net_client->read_data_wq =
		create_singlethread_workqueue("dev_read_from_host_wq");
	INIT_WORK(&mhi_net_client->dev_read_wrk, mhi_dev_net_rx_scheduler);

	mhi_dev_net_log(MHI_INFO,
			"Registering for MHI transfer events from host\n");
	mhi_net_ctxt.net_event_notifier = mhi_dev_net_event_notifier;

	ret_val = mhi_dev_net_init_ch_attributes(&mhi_net_ctxt);
	if (ret_val < 0) {
		mhi_dev_net_log(MHI_ERROR,
				"Failed to init client attributes\n");
		goto channel_init_fail;
	}
	mhi_dev_net_log(MHI_DBG, "Initializing client\n");
	index = MHI_CLIENT_IP_SW_4_OUT;
	ret_val = mhi_dev_net_rgstr_client(mhi_net_client, index);
	if (ret_val) {
		mhi_dev_net_log(MHI_CRITICAL,
				"Failed to reg client %d ret 0\n", ret_val);
		goto client_register_fail;
	}
	return ret_val;

channel_init_fail:
	kfree(mhi_net_client);
	kfree(mhi_net_ipc_log);
	return ret_val;
client_register_fail:
	kfree(mhi_net_client);
	kfree(mhi_net_ipc_log);
	return ret_val;
}
EXPORT_SYMBOL(mhi_dev_net_interface_init);

void __exit mhi_dev_net_exit(void)
{
	mhi_dev_net_log(MHI_INFO,
			"MHI Network Interface Module exited ");
	mhi_dev_net_close();
}
EXPORT_SYMBOL(mhi_dev_net_exit);