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

Commit f5ab6ca3 authored by Prasad Malisetty's avatar Prasad Malisetty
Browse files

msm: mhi_dev: Add MHI device network interface driver



The mhi_dev_net interface driver exposes virtual interface
to communicate IP network data packets between MHI host rmnet
interface and device over PCIe link.

The driver registers with mhi(client) driver when mhi
device is enabled by host. It registers read, write channels
along with notifier callback function to receive notifications
from mhi host on data transactions.

The mhi host rmnet driver uses read, write channels
to receive/send data packets to mhi device. when data
packet is received from the host, channel event notifier
gets triggered, mhi_dev_net client driver reads data
from host memory and send that packet to network stack.

Change-Id: I94894c83b6dd2a8ac850b275fd2a006569957b89
Signed-off-by: default avatarPrasad Malisetty <prasadm@codeaurora.org>
parent b198c183
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);