msm: mhi: Add MHI RmNet driver
Add MHI RmNet driver to support exposure of an MHI device as a network interface to the Linux networking stack. The MHI channels are exposed as a standard network interface which supports the NAPI framework. IOCTLs for setting MTU/MRU and LPM are also enabled. CRs-Fixed: 689329 Change-Id: I9540c78acccec35aff4ee4dc36241c6f08cdf04c Signed-off-by: Andrei Danaila <adanaila@codeaurora.org>
This commit is contained in:
parent
c9a0e074ed
commit
fb5e040069
3 changed files with 981 additions and 0 deletions
12
drivers/net/ethernet/msm/Kconfig
Normal file
12
drivers/net/ethernet/msm/Kconfig
Normal file
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# msm network device configuration
|
||||
#
|
||||
|
||||
config MSM_RMNET_MHI
|
||||
bool "RMNET MHI Driver"
|
||||
depends on MSM_MHI
|
||||
help
|
||||
Implements RMNET over MHI interface.
|
||||
RMNET provides a virtual ethernet interface
|
||||
for routing IP packets within the MSM using
|
||||
BAM as a physical transport.
|
5
drivers/net/ethernet/msm/Makefile
Normal file
5
drivers/net/ethernet/msm/Makefile
Normal file
|
@ -0,0 +1,5 @@
|
|||
#
|
||||
# Makefile for the msm networking support.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_MSM_RMNET_MHI) += msm_rmnet_mhi.o
|
964
drivers/net/ethernet/msm/msm_rmnet_mhi.c
Normal file
964
drivers/net/ethernet/msm/msm_rmnet_mhi.c
Normal file
|
@ -0,0 +1,964 @@
|
|||
/* Copyright (c) 2014, 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 RMNET Network interface
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/msm_rmnet.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/msm_mhi.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/ipc_logging.h>
|
||||
|
||||
#define RMNET_MHI_DRIVER_NAME "rmnet_mhi"
|
||||
#define RMNET_MHI_DEV_NAME "rmnet_mhi%d"
|
||||
#define MHI_DEFAULT_MTU 8000
|
||||
#define MHI_DEFAULT_MRU 8000
|
||||
#define MHI_MAX_MRU 0xFFFF
|
||||
#define MHI_NAPI_WEIGHT_VALUE 12
|
||||
#define MHI_RX_HEADROOM 64
|
||||
#define WATCHDOG_TIMEOUT (30 * HZ)
|
||||
#define MHI_RMNET_DEVICE_COUNT 1
|
||||
#define RMNET_IPC_LOG_PAGES (10)
|
||||
#define IS_INBOUND(_chan) (((u32)(_chan)) % 2)
|
||||
|
||||
enum DBG_LVL {
|
||||
MSG_VERBOSE = 0x1,
|
||||
MSG_INFO = 0x2,
|
||||
MSG_DBG = 0x4,
|
||||
MSG_WARNING = 0x8,
|
||||
MSG_ERROR = 0x10,
|
||||
MSG_CRITICAL = 0x20,
|
||||
MSG_reserved = 0x80000000
|
||||
};
|
||||
|
||||
enum DBG_LVL rmnet_ipc_log_lvl = MSG_INFO;
|
||||
enum DBG_LVL rmnet_msg_lvl = MSG_CRITICAL;
|
||||
|
||||
module_param(rmnet_msg_lvl , uint, S_IRUGO | S_IWUSR);
|
||||
MODULE_PARM_DESC(rmnet_msg_lvl, "dbg lvl");
|
||||
module_param(rmnet_ipc_log_lvl, uint, S_IRUGO | S_IWUSR);
|
||||
MODULE_PARM_DESC(rmnet_ipc_log_lvl, "dbg lvl");
|
||||
|
||||
void *rmnet_ipc_log;
|
||||
|
||||
#define rmnet_log(_msg_lvl, _msg, ...) do { \
|
||||
if ((_msg_lvl) >= rmnet_msg_lvl) \
|
||||
pr_alert("[%s] " _msg, __func__, ##__VA_ARGS__);\
|
||||
if (rmnet_ipc_log && ((_msg_lvl) >= rmnet_ipc_log_lvl)) \
|
||||
ipc_log_string(rmnet_ipc_log, \
|
||||
"[%s] " _msg, __func__, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
unsigned long tx_interrupts_count[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(tx_interrupts_count, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(tx_interrupts_count, "Tx interrupts");
|
||||
|
||||
unsigned long rx_interrupts_count[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(rx_interrupts_count, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(rx_interrupts_count, "RX interrupts");
|
||||
|
||||
unsigned long tx_ring_full_count[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(tx_ring_full_count, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(tx_ring_full_count, "RING FULL errors from MHI Core");
|
||||
|
||||
|
||||
unsigned long tx_queued_packets_count[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(tx_queued_packets_count, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(tx_queued_packets_count, "TX packets queued in MHI core");
|
||||
|
||||
unsigned long rx_interrupts_in_masked_irq[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(rx_interrupts_in_masked_irq, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(rx_interrupts_in_masked_irq,
|
||||
"RX interrupts while IRQs are masked");
|
||||
|
||||
unsigned long rx_napi_skb_burst_min[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(rx_napi_skb_burst_min, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(rx_napi_skb_burst_min, "MIN SKBs sent to NS during NAPI");
|
||||
|
||||
unsigned long rx_napi_skb_burst_max[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(rx_napi_skb_burst_max, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(rx_napi_skb_burst_max, "MAX SKBs sent to NS during NAPI");
|
||||
|
||||
unsigned long tx_cb_skb_free_burst_min[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(tx_cb_skb_free_burst_min, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(tx_cb_skb_free_burst_min, "MIN SKBs freed during TX CB");
|
||||
|
||||
unsigned long tx_cb_skb_free_burst_max[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(tx_cb_skb_free_burst_max, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(tx_cb_skb_free_burst_max, "MAX SKBs freed during TX CB");
|
||||
|
||||
unsigned long rx_napi_budget_overflow[MHI_RMNET_DEVICE_COUNT];
|
||||
module_param_array(rx_napi_budget_overflow, ulong, 0, S_IRUGO);
|
||||
MODULE_PARM_DESC(rx_napi_budget_overflow,
|
||||
"Budget hit with more items to read counter");
|
||||
|
||||
struct rmnet_mhi_private {
|
||||
int dev_index;
|
||||
struct mhi_client_handle *tx_client_handle;
|
||||
struct mhi_client_handle *rx_client_handle;
|
||||
enum MHI_CLIENT_CHANNEL tx_channel;
|
||||
enum MHI_CLIENT_CHANNEL rx_channel;
|
||||
struct sk_buff_head tx_buffers;
|
||||
struct sk_buff_head rx_buffers;
|
||||
uint32_t mru;
|
||||
struct napi_struct napi;
|
||||
gfp_t allocation_flags;
|
||||
uint32_t tx_buffers_max;
|
||||
uint32_t rx_buffers_max;
|
||||
u32 tx_enabled;
|
||||
u32 rx_enabled;
|
||||
u32 mhi_enabled;
|
||||
struct net_device *dev;
|
||||
int32_t irq_masked_cntr;
|
||||
};
|
||||
|
||||
struct tx_buffer_priv {
|
||||
dma_addr_t dma_addr;
|
||||
};
|
||||
|
||||
static struct rmnet_mhi_private rmnet_mhi_ctxt_list[MHI_RMNET_DEVICE_COUNT];
|
||||
|
||||
static dma_addr_t rmnet_mhi_internal_get_dma_addr(struct sk_buff *skb,
|
||||
enum dma_data_direction dir)
|
||||
{
|
||||
if (dir == DMA_TO_DEVICE) {
|
||||
struct tx_buffer_priv *tx_priv =
|
||||
(struct tx_buffer_priv *)(skb->cb);
|
||||
return tx_priv->dma_addr;
|
||||
} else /* DMA_FROM_DEVICE */{
|
||||
uintptr_t *cb_ptr = 0;
|
||||
cb_ptr = (uintptr_t *)skb->cb;
|
||||
return (dma_addr_t)(uintptr_t)(*cb_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void rmnet_mhi_internal_clean_unmap_buffers(struct net_device *dev,
|
||||
struct sk_buff_head *queue,
|
||||
enum dma_data_direction dir)
|
||||
{
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr =
|
||||
*(struct rmnet_mhi_private **)netdev_priv(dev);
|
||||
rmnet_log(MSG_INFO, "Entered\n");
|
||||
while (!skb_queue_empty(queue)) {
|
||||
struct sk_buff *skb = skb_dequeue(queue);
|
||||
if (skb != 0) {
|
||||
dma_addr_t dma_addr =
|
||||
rmnet_mhi_internal_get_dma_addr(skb, dir);
|
||||
if (dir == DMA_FROM_DEVICE)
|
||||
dma_unmap_single(&(dev->dev),
|
||||
dma_addr,
|
||||
(rmnet_mhi_ptr->mru - MHI_RX_HEADROOM),
|
||||
dir);
|
||||
else
|
||||
dma_unmap_single(&(dev->dev),
|
||||
dma_addr,
|
||||
skb->len,
|
||||
dir);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
}
|
||||
rmnet_log(MSG_INFO, "Exited\n");
|
||||
}
|
||||
|
||||
static __be16 rmnet_mhi_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 int rmnet_mhi_poll(struct napi_struct *napi, int budget)
|
||||
{
|
||||
int received_packets = 0;
|
||||
struct net_device *dev = napi->dev;
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr =
|
||||
*(struct rmnet_mhi_private **)netdev_priv(dev);
|
||||
enum MHI_STATUS res = MHI_STATUS_reserved;
|
||||
bool should_reschedule = true;
|
||||
struct sk_buff *skb;
|
||||
dma_addr_t dma_addr;
|
||||
uintptr_t *cb_ptr;
|
||||
|
||||
rmnet_log(MSG_VERBOSE, "Entered\n");
|
||||
while (received_packets < budget) {
|
||||
struct mhi_result *result =
|
||||
mhi_poll(rmnet_mhi_ptr->rx_client_handle);
|
||||
if (result->transaction_status == MHI_STATUS_DEVICE_NOT_READY) {
|
||||
continue;
|
||||
} else if (result->transaction_status != MHI_STATUS_SUCCESS) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"mhi_poll failed, error is %d\n",
|
||||
result->transaction_status);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Nothing more to read, or out of buffers in MHI layer */
|
||||
if (unlikely(!result->payload_buf ||
|
||||
!result->bytes_xferd)) {
|
||||
should_reschedule = false;
|
||||
break;
|
||||
}
|
||||
|
||||
skb = skb_dequeue(&(rmnet_mhi_ptr->rx_buffers));
|
||||
if (unlikely(!skb)) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"No RX buffers to match");
|
||||
break;
|
||||
}
|
||||
|
||||
cb_ptr = (uintptr_t *)skb->cb;
|
||||
dma_addr = (dma_addr_t)(uintptr_t)(*cb_ptr);
|
||||
|
||||
/* Sanity check, ensuring that this is actually the buffer */
|
||||
if (unlikely(dma_addr != result->payload_buf)) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Buf mismatch, expected 0x%lx, got 0x%lx",
|
||||
(uintptr_t)dma_addr,
|
||||
(uintptr_t)result->payload_buf);
|
||||
break;
|
||||
}
|
||||
|
||||
dma_unmap_single(&(dev->dev), dma_addr,
|
||||
(rmnet_mhi_ptr->mru - MHI_RX_HEADROOM),
|
||||
DMA_FROM_DEVICE);
|
||||
skb_put(skb, result->bytes_xferd);
|
||||
|
||||
skb->dev = dev;
|
||||
skb->protocol = rmnet_mhi_ip_type_trans(skb);
|
||||
|
||||
netif_receive_skb(skb);
|
||||
|
||||
/* Statistics */
|
||||
received_packets++;
|
||||
dev->stats.rx_packets++;
|
||||
dev->stats.rx_bytes += result->bytes_xferd;
|
||||
|
||||
/* Need to allocate a new buffer instead of this one */
|
||||
skb = alloc_skb(rmnet_mhi_ptr->mru, GFP_ATOMIC);
|
||||
|
||||
if (unlikely(!skb)) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Can't allocate a new RX buffer for MHI");
|
||||
break;
|
||||
}
|
||||
|
||||
skb_reserve(skb, MHI_RX_HEADROOM);
|
||||
|
||||
cb_ptr = (uintptr_t *)skb->cb;
|
||||
dma_addr = dma_map_single(&(dev->dev), skb->data,
|
||||
(rmnet_mhi_ptr->mru - MHI_RX_HEADROOM),
|
||||
DMA_FROM_DEVICE);
|
||||
*cb_ptr = (uintptr_t)dma_addr;
|
||||
|
||||
if (unlikely(dma_mapping_error(&(dev->dev), dma_addr))) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"DMA mapping error in polling function");
|
||||
dev_kfree_skb_irq(skb);
|
||||
break;
|
||||
}
|
||||
|
||||
res = mhi_queue_xfer(
|
||||
rmnet_mhi_ptr->rx_client_handle,
|
||||
(uintptr_t)dma_addr, rmnet_mhi_ptr->mru, MHI_EOT);
|
||||
|
||||
if (unlikely(MHI_STATUS_SUCCESS != res)) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"mhi_queue_xfer failed, error %d", res);
|
||||
dma_unmap_single(&(dev->dev), dma_addr,
|
||||
(rmnet_mhi_ptr->mru - MHI_RX_HEADROOM),
|
||||
DMA_FROM_DEVICE);
|
||||
|
||||
dev_kfree_skb_irq(skb);
|
||||
break;
|
||||
}
|
||||
|
||||
skb_queue_tail(&(rmnet_mhi_ptr->rx_buffers), skb);
|
||||
|
||||
} /* while (received_packets < budget) or any other error */
|
||||
|
||||
napi_complete(napi);
|
||||
|
||||
/* We got a NULL descriptor back */
|
||||
if (should_reschedule == false) {
|
||||
if (rmnet_mhi_ptr->irq_masked_cntr) {
|
||||
mhi_unmask_irq(rmnet_mhi_ptr->rx_client_handle);
|
||||
--rmnet_mhi_ptr->irq_masked_cntr;
|
||||
}
|
||||
} else {
|
||||
if (received_packets == budget)
|
||||
rx_napi_budget_overflow[rmnet_mhi_ptr->dev_index]++;
|
||||
napi_reschedule(napi);
|
||||
}
|
||||
|
||||
rx_napi_skb_burst_min[rmnet_mhi_ptr->dev_index] =
|
||||
min((unsigned long)received_packets,
|
||||
rx_napi_skb_burst_min[rmnet_mhi_ptr->dev_index]);
|
||||
|
||||
rx_napi_skb_burst_max[rmnet_mhi_ptr->dev_index] =
|
||||
max((unsigned long)received_packets,
|
||||
rx_napi_skb_burst_max[rmnet_mhi_ptr->dev_index]);
|
||||
|
||||
rmnet_log(MSG_VERBOSE, "Exited, polled %d pkts\n", received_packets);
|
||||
return received_packets;
|
||||
}
|
||||
|
||||
void rmnet_mhi_clean_buffers(struct net_device *dev)
|
||||
{
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr =
|
||||
*(struct rmnet_mhi_private **)netdev_priv(dev);
|
||||
rmnet_log(MSG_INFO, "Entered\n");
|
||||
/* Clean TX buffers */
|
||||
rmnet_mhi_internal_clean_unmap_buffers(dev,
|
||||
&(rmnet_mhi_ptr->tx_buffers),
|
||||
DMA_TO_DEVICE);
|
||||
|
||||
/* Clean RX buffers */
|
||||
rmnet_mhi_internal_clean_unmap_buffers(dev,
|
||||
&(rmnet_mhi_ptr->rx_buffers),
|
||||
DMA_FROM_DEVICE);
|
||||
rmnet_log(MSG_INFO, "Exited\n");
|
||||
}
|
||||
|
||||
static int rmnet_mhi_disable(struct rmnet_mhi_private *rmnet_mhi_ptr)
|
||||
{
|
||||
rmnet_log(MSG_INFO, "Closing MHI TX channel\n");
|
||||
mhi_close_channel(rmnet_mhi_ptr->tx_client_handle);
|
||||
rmnet_log(MSG_INFO, "Closing MHI RX channel\n");
|
||||
mhi_close_channel(rmnet_mhi_ptr->rx_client_handle);
|
||||
rmnet_log(MSG_INFO, "Clearing Pending TX buffers.\n");
|
||||
rmnet_mhi_clean_buffers(rmnet_mhi_ptr->dev);
|
||||
rmnet_mhi_ptr->tx_client_handle = NULL;
|
||||
rmnet_mhi_ptr->rx_client_handle = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rmnet_mhi_init_inbound(struct rmnet_mhi_private *rmnet_mhi_ptr)
|
||||
{
|
||||
u32 i;
|
||||
enum MHI_STATUS res;
|
||||
rmnet_log(MSG_INFO, "Entered\n");
|
||||
rmnet_mhi_ptr->tx_buffers_max =
|
||||
mhi_get_max_desc(
|
||||
rmnet_mhi_ptr->tx_client_handle);
|
||||
rmnet_mhi_ptr->rx_buffers_max =
|
||||
mhi_get_max_desc(
|
||||
rmnet_mhi_ptr->rx_client_handle);
|
||||
|
||||
for (i = 0; i < rmnet_mhi_ptr->rx_buffers_max; i++) {
|
||||
struct sk_buff *skb = 0;
|
||||
dma_addr_t dma_addr;
|
||||
dma_addr_t *cb_ptr = 0;
|
||||
|
||||
skb = alloc_skb(rmnet_mhi_ptr->mru,
|
||||
rmnet_mhi_ptr->allocation_flags);
|
||||
|
||||
if (!skb) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"SKB allocation failure during open");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
skb_reserve(skb, MHI_RX_HEADROOM);
|
||||
cb_ptr = (dma_addr_t *)skb->cb;
|
||||
dma_addr = dma_map_single(&(rmnet_mhi_ptr->dev->dev), skb->data,
|
||||
(rmnet_mhi_ptr->mru - MHI_RX_HEADROOM),
|
||||
DMA_FROM_DEVICE);
|
||||
*cb_ptr = dma_addr;
|
||||
if (dma_mapping_error(&(rmnet_mhi_ptr->dev->dev), dma_addr)) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"DMA mapping for RX buffers has failed");
|
||||
kfree_skb(skb);
|
||||
return -EIO;
|
||||
}
|
||||
skb_queue_tail(&(rmnet_mhi_ptr->rx_buffers), skb);
|
||||
}
|
||||
|
||||
/* Submit the RX buffers */
|
||||
for (i = 0; i < rmnet_mhi_ptr->rx_buffers_max; i++) {
|
||||
struct sk_buff *skb = skb_dequeue(&(rmnet_mhi_ptr->rx_buffers));
|
||||
res = mhi_queue_xfer(rmnet_mhi_ptr->rx_client_handle,
|
||||
*((dma_addr_t *)(skb->cb)),
|
||||
rmnet_mhi_ptr->mru - MHI_RX_HEADROOM,
|
||||
MHI_EOT);
|
||||
if (MHI_STATUS_SUCCESS != res) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"mhi_queue_xfer failed, error %d", res);
|
||||
return -EIO;
|
||||
}
|
||||
skb_queue_tail(&(rmnet_mhi_ptr->rx_buffers), skb);
|
||||
}
|
||||
rmnet_log(MSG_INFO, "Exited\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rmnet_mhi_tx_cb(struct mhi_result *result)
|
||||
{
|
||||
struct net_device *dev;
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr;
|
||||
unsigned long burst_counter = 0;
|
||||
|
||||
rmnet_mhi_ptr = result->user_data;
|
||||
dev = rmnet_mhi_ptr->dev;
|
||||
tx_interrupts_count[rmnet_mhi_ptr->dev_index]++;
|
||||
|
||||
rmnet_log(MSG_VERBOSE, "Entered\n");
|
||||
if (!result->payload_buf || !result->bytes_xferd)
|
||||
return;
|
||||
/* Free the buffers which are TX'd up to the provided address */
|
||||
while (!skb_queue_empty(&(rmnet_mhi_ptr->tx_buffers))) {
|
||||
struct sk_buff *skb =
|
||||
skb_dequeue(&(rmnet_mhi_ptr->tx_buffers));
|
||||
if (!skb) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"NULL buffer returned, error");
|
||||
break;
|
||||
} else {
|
||||
struct tx_buffer_priv *tx_priv =
|
||||
(struct tx_buffer_priv *)(skb->cb);
|
||||
dma_addr_t dma_addr = tx_priv->dma_addr;
|
||||
int data_len = skb->len;
|
||||
|
||||
dma_unmap_single(&(dev->dev),
|
||||
dma_addr,
|
||||
skb->len,
|
||||
DMA_TO_DEVICE);
|
||||
kfree_skb(skb);
|
||||
burst_counter++;
|
||||
|
||||
/* Update statistics */
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += data_len;
|
||||
|
||||
/* The payload is expected to be the phy addr.
|
||||
Comparing to see if it's the last skb to
|
||||
replenish
|
||||
*/
|
||||
if (dma_addr ==
|
||||
result->payload_buf)
|
||||
break;
|
||||
}
|
||||
} /* While TX queue is not empty */
|
||||
tx_cb_skb_free_burst_min[rmnet_mhi_ptr->dev_index] =
|
||||
min(burst_counter,
|
||||
tx_cb_skb_free_burst_min[rmnet_mhi_ptr->dev_index]);
|
||||
|
||||
tx_cb_skb_free_burst_max[rmnet_mhi_ptr->dev_index] =
|
||||
max(burst_counter,
|
||||
tx_cb_skb_free_burst_max[rmnet_mhi_ptr->dev_index]);
|
||||
|
||||
/* In case we couldn't write again, now we can! */
|
||||
netif_wake_queue(dev);
|
||||
rmnet_log(MSG_VERBOSE, "Exited\n");
|
||||
}
|
||||
|
||||
static void rmnet_mhi_rx_cb(struct mhi_result *result)
|
||||
{
|
||||
struct net_device *dev;
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr;
|
||||
rmnet_mhi_ptr = result->user_data;
|
||||
dev = rmnet_mhi_ptr->dev;
|
||||
|
||||
rmnet_log(MSG_VERBOSE, "Entered\n");
|
||||
rx_interrupts_count[rmnet_mhi_ptr->dev_index]++;
|
||||
|
||||
mhi_mask_irq(rmnet_mhi_ptr->rx_client_handle);
|
||||
rmnet_mhi_ptr->irq_masked_cntr++;
|
||||
|
||||
if (napi_schedule_prep(&(rmnet_mhi_ptr->napi)))
|
||||
__napi_schedule(&(rmnet_mhi_ptr->napi));
|
||||
else
|
||||
rx_interrupts_in_masked_irq[rmnet_mhi_ptr->dev_index]++;
|
||||
rmnet_log(MSG_VERBOSE, "Exited\n");
|
||||
}
|
||||
|
||||
static int rmnet_mhi_open(struct net_device *dev)
|
||||
{
|
||||
struct rmnet_mhi_private **rmnet_mhi_ptr = netdev_priv(dev);
|
||||
|
||||
rmnet_log(MSG_INFO,
|
||||
"Opened net dev interface for MHI chans %d and %d\n",
|
||||
(*rmnet_mhi_ptr)->tx_channel,
|
||||
(*rmnet_mhi_ptr)->rx_channel);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int rmnet_mhi_disable_iface(struct rmnet_mhi_private *rmnet_mhi_ptr)
|
||||
{
|
||||
rmnet_mhi_ptr->rx_enabled = 0;
|
||||
rmnet_mhi_ptr->tx_enabled = 0;
|
||||
rmnet_mhi_ptr->mhi_enabled = 0;
|
||||
if (0 != rmnet_mhi_ptr->dev) {
|
||||
netif_stop_queue(rmnet_mhi_ptr->dev);
|
||||
netif_napi_del(&(rmnet_mhi_ptr->napi));
|
||||
unregister_netdev(rmnet_mhi_ptr->dev);
|
||||
free_netdev(rmnet_mhi_ptr->dev);
|
||||
rmnet_mhi_ptr->dev = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rmnet_mhi_close(struct net_device *dev)
|
||||
{
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr =
|
||||
*(struct rmnet_mhi_private **)netdev_priv(dev);
|
||||
rmnet_mhi_ptr->mhi_enabled = 0;
|
||||
rmnet_mhi_disable_iface(rmnet_mhi_ptr);
|
||||
napi_disable(&(rmnet_mhi_ptr->napi));
|
||||
if (rmnet_mhi_ptr->irq_masked_cntr) {
|
||||
mhi_unmask_irq(rmnet_mhi_ptr->rx_client_handle);
|
||||
--rmnet_mhi_ptr->irq_masked_cntr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rmnet_mhi_stop(struct net_device *dev)
|
||||
{
|
||||
rmnet_mhi_close(dev);
|
||||
netif_stop_queue(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rmnet_mhi_change_mtu(struct net_device *dev, int new_mtu)
|
||||
{
|
||||
if (0 > new_mtu || MHI_MAX_MTU < new_mtu)
|
||||
return -EINVAL;
|
||||
|
||||
dev->mtu = new_mtu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rmnet_mhi_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr =
|
||||
*(struct rmnet_mhi_private **)netdev_priv(dev);
|
||||
enum MHI_STATUS res = MHI_STATUS_reserved;
|
||||
struct tx_buffer_priv *tx_priv;
|
||||
dma_addr_t dma_addr;
|
||||
|
||||
rmnet_log(MSG_VERBOSE, "Entered\n");
|
||||
dma_addr = dma_map_single(&(dev->dev), skb->data, skb->len,
|
||||
DMA_TO_DEVICE);
|
||||
if (dma_mapping_error(&(dev->dev), dma_addr)) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"DMA mapping error in transmit function\n");
|
||||
return NETDEV_TX_BUSY;
|
||||
}
|
||||
|
||||
/* DMA mapping is OK, need to update the cb field properly */
|
||||
tx_priv = (struct tx_buffer_priv *)(skb->cb);
|
||||
tx_priv->dma_addr = dma_addr;
|
||||
|
||||
res = mhi_queue_xfer(rmnet_mhi_ptr->tx_client_handle,
|
||||
(uintptr_t)(dma_addr), skb->len, MHI_EOT);
|
||||
|
||||
if (res == MHI_STATUS_RING_FULL) {
|
||||
/* Need to stop writing until we can write again */
|
||||
tx_ring_full_count[rmnet_mhi_ptr->dev_index]++;
|
||||
netif_stop_queue(dev);
|
||||
goto rmnet_mhi_xmit_error_cleanup;
|
||||
}
|
||||
|
||||
if (MHI_STATUS_SUCCESS != res) {
|
||||
netif_stop_queue(dev);
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"mhi_queue_xfer failed, error %d\n", res);
|
||||
goto rmnet_mhi_xmit_error_cleanup;
|
||||
}
|
||||
|
||||
skb_queue_tail(&(rmnet_mhi_ptr->tx_buffers), skb);
|
||||
|
||||
dev->trans_start = jiffies;
|
||||
|
||||
tx_queued_packets_count[rmnet_mhi_ptr->dev_index]++;
|
||||
rmnet_log(MSG_VERBOSE, "Exited\n");
|
||||
return 0;
|
||||
|
||||
rmnet_mhi_xmit_error_cleanup:
|
||||
dma_unmap_single(&(dev->dev), dma_addr, skb->len,
|
||||
DMA_TO_DEVICE);
|
||||
rmnet_log(MSG_VERBOSE, "Ring full\n");
|
||||
return NETDEV_TX_BUSY;
|
||||
}
|
||||
|
||||
static int rmnet_mhi_ioctl_extended(struct net_device *dev, struct ifreq *ifr)
|
||||
{
|
||||
struct rmnet_ioctl_extended_s ext_cmd;
|
||||
int rc = 0;
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr =
|
||||
*(struct rmnet_mhi_private **)netdev_priv(dev);
|
||||
|
||||
|
||||
rc = copy_from_user(&ext_cmd, ifr->ifr_ifru.ifru_data,
|
||||
sizeof(struct rmnet_ioctl_extended_s));
|
||||
|
||||
if (rc) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"copy_from_user failed ,error %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
switch (ext_cmd.extended_ioctl) {
|
||||
case RMNET_IOCTL_SET_MRU:
|
||||
if ((0 > ext_cmd.u.data) || (ext_cmd.u.data > MHI_MAX_MRU)) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Can't set MRU, value %u is invalid\n",
|
||||
ext_cmd.u.data);
|
||||
return -EINVAL;
|
||||
}
|
||||
rmnet_mhi_ptr->mru = ext_cmd.u.data;
|
||||
break;
|
||||
case RMNET_IOCTL_GET_EPID:
|
||||
ext_cmd.u.data =
|
||||
mhi_get_epid(rmnet_mhi_ptr->tx_client_handle);
|
||||
break;
|
||||
case RMNET_IOCTL_GET_SUPPORTED_FEATURES:
|
||||
ext_cmd.u.data = 0;
|
||||
break;
|
||||
case RMNET_IOCTL_GET_DRIVER_NAME:
|
||||
strlcpy(ext_cmd.u.if_name, RMNET_MHI_DRIVER_NAME,
|
||||
sizeof(ext_cmd.u.if_name));
|
||||
break;
|
||||
case RMNET_IOCTL_SET_SLEEP_STATE:
|
||||
mhi_set_lpm(rmnet_mhi_ptr->tx_client_handle, ext_cmd.u.data);
|
||||
break;
|
||||
default:
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
rc = copy_to_user(ifr->ifr_ifru.ifru_data, &ext_cmd,
|
||||
sizeof(struct rmnet_ioctl_extended_s));
|
||||
|
||||
if (rc)
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"copy_to_user failed, error %d\n",
|
||||
rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int rmnet_mhi_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
|
||||
{
|
||||
int rc = 0;
|
||||
struct rmnet_ioctl_data_s ioctl_data;
|
||||
|
||||
switch (cmd) {
|
||||
case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol */
|
||||
break;
|
||||
case RMNET_IOCTL_GET_LLP: /* Get link protocol state */
|
||||
ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
|
||||
if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data,
|
||||
sizeof(struct rmnet_ioctl_data_s)))
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */
|
||||
ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
|
||||
if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data,
|
||||
sizeof(struct rmnet_ioctl_data_s)))
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
case RMNET_IOCTL_SET_QOS_ENABLE:
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
case RMNET_IOCTL_SET_QOS_DISABLE:
|
||||
rc = 0;
|
||||
break;
|
||||
case RMNET_IOCTL_OPEN:
|
||||
case RMNET_IOCTL_CLOSE:
|
||||
/* We just ignore them and return success */
|
||||
rc = 0;
|
||||
break;
|
||||
case RMNET_IOCTL_EXTENDED:
|
||||
rc = rmnet_mhi_ioctl_extended(dev, ifr);
|
||||
break;
|
||||
default:
|
||||
/* Don't fail any IOCTL right now */
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct net_device_ops rmnet_mhi_ops_ip = {
|
||||
.ndo_open = rmnet_mhi_open,
|
||||
.ndo_stop = rmnet_mhi_stop,
|
||||
.ndo_start_xmit = rmnet_mhi_xmit,
|
||||
.ndo_do_ioctl = rmnet_mhi_ioctl,
|
||||
.ndo_change_mtu = rmnet_mhi_change_mtu,
|
||||
.ndo_set_mac_address = 0,
|
||||
.ndo_validate_addr = 0,
|
||||
};
|
||||
|
||||
static void rmnet_mhi_setup(struct net_device *dev)
|
||||
{
|
||||
dev->netdev_ops = &rmnet_mhi_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_DEFAULT_MTU;
|
||||
dev->addr_len = 0;
|
||||
dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
|
||||
dev->watchdog_timeo = WATCHDOG_TIMEOUT;
|
||||
}
|
||||
|
||||
static int rmnet_mhi_enable_iface(struct rmnet_mhi_private *rmnet_mhi_ptr)
|
||||
{
|
||||
int ret = 0;
|
||||
struct rmnet_mhi_private **rmnet_mhi_ctxt = NULL;
|
||||
enum MHI_STATUS r = MHI_STATUS_SUCCESS;
|
||||
|
||||
memset(tx_interrupts_count, 0, sizeof(tx_interrupts_count));
|
||||
memset(rx_interrupts_count, 0, sizeof(rx_interrupts_count));
|
||||
memset(rx_interrupts_in_masked_irq, 0,
|
||||
sizeof(rx_interrupts_in_masked_irq));
|
||||
memset(rx_napi_skb_burst_min, 0, sizeof(rx_napi_skb_burst_min));
|
||||
memset(rx_napi_skb_burst_max, 0, sizeof(rx_napi_skb_burst_max));
|
||||
memset(tx_cb_skb_free_burst_min, 0, sizeof(tx_cb_skb_free_burst_min));
|
||||
memset(tx_cb_skb_free_burst_max, 0, sizeof(tx_cb_skb_free_burst_max));
|
||||
memset(tx_ring_full_count, 0, sizeof(tx_ring_full_count));
|
||||
memset(tx_queued_packets_count, 0, sizeof(tx_queued_packets_count));
|
||||
memset(rx_napi_budget_overflow, 0, sizeof(rx_napi_budget_overflow));
|
||||
rmnet_log(MSG_INFO, "Entered.\n");
|
||||
if (rmnet_mhi_ptr == NULL) {
|
||||
rmnet_log(MSG_CRITICAL, "Bad input args.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
rmnet_mhi_ptr->dev =
|
||||
alloc_netdev(sizeof(struct rmnet_mhi_private *),
|
||||
RMNET_MHI_DEV_NAME, rmnet_mhi_setup);
|
||||
if (!rmnet_mhi_ptr->dev) {
|
||||
rmnet_log(MSG_CRITICAL, "Network device allocation failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto net_dev_alloc_fail;
|
||||
}
|
||||
|
||||
rmnet_mhi_ctxt = netdev_priv(rmnet_mhi_ptr->dev);
|
||||
*rmnet_mhi_ctxt = rmnet_mhi_ptr;
|
||||
|
||||
ret = dma_set_mask(&(rmnet_mhi_ptr->dev->dev),
|
||||
MHI_DMA_MASK);
|
||||
if (0 != ret)
|
||||
rmnet_mhi_ptr->allocation_flags = GFP_KERNEL;
|
||||
else
|
||||
rmnet_mhi_ptr->allocation_flags = GFP_DMA;
|
||||
|
||||
netif_napi_add(rmnet_mhi_ptr->dev, &(rmnet_mhi_ptr->napi),
|
||||
rmnet_mhi_poll, MHI_NAPI_WEIGHT_VALUE);
|
||||
|
||||
ret = register_netdev(rmnet_mhi_ptr->dev);
|
||||
if (ret) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Network device registration failed\n");
|
||||
goto net_dev_reg_fail;
|
||||
}
|
||||
|
||||
rx_napi_skb_burst_min[rmnet_mhi_ptr->dev_index] = UINT_MAX;
|
||||
tx_cb_skb_free_burst_min[rmnet_mhi_ptr->dev_index] = UINT_MAX;
|
||||
|
||||
skb_queue_head_init(&(rmnet_mhi_ptr->tx_buffers));
|
||||
skb_queue_head_init(&(rmnet_mhi_ptr->rx_buffers));
|
||||
|
||||
if (rmnet_mhi_ptr->tx_client_handle != NULL) {
|
||||
rmnet_log(MSG_INFO,
|
||||
"Opening TX channel\n");
|
||||
r = mhi_open_channel(rmnet_mhi_ptr->tx_client_handle);
|
||||
if (r != MHI_STATUS_SUCCESS) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Failed to start TX chan ret %d\n", r);
|
||||
goto mhi_tx_chan_start_fail;
|
||||
} else {
|
||||
rmnet_mhi_ptr->tx_enabled = 1;
|
||||
}
|
||||
}
|
||||
if (rmnet_mhi_ptr->rx_client_handle != NULL) {
|
||||
rmnet_log(MSG_INFO,
|
||||
"Opening RX channel\n");
|
||||
r = mhi_open_channel(rmnet_mhi_ptr->rx_client_handle);
|
||||
if (r != MHI_STATUS_SUCCESS) {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Failed to start RX chan ret %d\n", r);
|
||||
goto mhi_rx_chan_start_fail;
|
||||
} else {
|
||||
rmnet_mhi_ptr->rx_enabled = 1;
|
||||
rmnet_mhi_init_inbound(rmnet_mhi_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
napi_enable(&(rmnet_mhi_ptr->napi));
|
||||
netif_start_queue(rmnet_mhi_ptr->dev);
|
||||
rmnet_log(MSG_INFO, "Exited.\n");
|
||||
|
||||
return 0;
|
||||
|
||||
mhi_rx_chan_start_fail:
|
||||
mhi_close_channel(rmnet_mhi_ptr->tx_client_handle);
|
||||
mhi_tx_chan_start_fail:
|
||||
unregister_netdev(rmnet_mhi_ptr->dev);
|
||||
net_dev_reg_fail:
|
||||
netif_napi_del(&(rmnet_mhi_ptr->napi));
|
||||
free_netdev(rmnet_mhi_ptr->dev);
|
||||
net_dev_alloc_fail:
|
||||
rmnet_mhi_ptr->dev = NULL;
|
||||
rmnet_log(MSG_INFO, "Exited ret %d.\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rmnet_mhi_cb(struct mhi_cb_info *cb_info)
|
||||
{
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr;
|
||||
struct mhi_result *result;
|
||||
enum MHI_STATUS r = MHI_STATUS_SUCCESS;
|
||||
|
||||
if (NULL != cb_info && NULL != cb_info->result) {
|
||||
result = cb_info->result;
|
||||
rmnet_mhi_ptr = result->user_data;
|
||||
} else {
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Invalid data in MHI callback, quitting\n");
|
||||
}
|
||||
|
||||
switch (cb_info->cb_reason) {
|
||||
case MHI_CB_MHI_DISABLED:
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Got MHI_DISABLED notification. Stopping stack\n");
|
||||
if (rmnet_mhi_ptr->mhi_enabled) {
|
||||
rmnet_mhi_disable(rmnet_mhi_ptr);
|
||||
rmnet_mhi_disable_iface(rmnet_mhi_ptr);
|
||||
}
|
||||
break;
|
||||
case MHI_CB_MHI_ENABLED:
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Got MHI_ENABLED notification. Starting stack\n");
|
||||
if (IS_INBOUND(cb_info->chan))
|
||||
rmnet_mhi_ptr->rx_enabled = 1;
|
||||
else
|
||||
rmnet_mhi_ptr->tx_enabled = 1;
|
||||
|
||||
if (rmnet_mhi_ptr->tx_enabled &&
|
||||
rmnet_mhi_ptr->rx_enabled) {
|
||||
rmnet_log(MSG_INFO,
|
||||
"Both RX/TX are enabled, enabling iface.\n");
|
||||
r = rmnet_mhi_enable_iface(rmnet_mhi_ptr);
|
||||
if (r)
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"Failed to enable iface for chan %d\n",
|
||||
cb_info->chan);
|
||||
else
|
||||
rmnet_log(MSG_INFO,
|
||||
"Enabled iface for chan %d\n",
|
||||
cb_info->chan);
|
||||
}
|
||||
break;
|
||||
case MHI_CB_XFER:
|
||||
if (IS_INBOUND(cb_info->chan))
|
||||
rmnet_mhi_rx_cb(cb_info->result);
|
||||
else
|
||||
rmnet_mhi_tx_cb(cb_info->result);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct mhi_client_info_t rmnet_mhi_info = {rmnet_mhi_cb};
|
||||
|
||||
static int __init rmnet_mhi_init(void)
|
||||
{
|
||||
int i;
|
||||
enum MHI_STATUS res = MHI_STATUS_SUCCESS;
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr = 0;
|
||||
rmnet_ipc_log = ipc_log_context_create(RMNET_IPC_LOG_PAGES,
|
||||
"mhi_rmnet", 0);
|
||||
|
||||
for (i = 0; i < MHI_RMNET_DEVICE_COUNT; i++) {
|
||||
rmnet_mhi_ptr = &rmnet_mhi_ctxt_list[i];
|
||||
|
||||
rmnet_mhi_ptr->tx_channel = MHI_CLIENT_IP_HW_0_OUT +
|
||||
(enum MHI_CLIENT_CHANNEL)(i * 2);
|
||||
rmnet_mhi_ptr->rx_channel = MHI_CLIENT_IP_HW_0_IN +
|
||||
(enum MHI_CLIENT_CHANNEL)((i * 2));
|
||||
|
||||
rmnet_mhi_ptr->tx_client_handle = 0;
|
||||
rmnet_mhi_ptr->rx_client_handle = 0;
|
||||
|
||||
rmnet_mhi_ptr->mru = MHI_DEFAULT_MRU;
|
||||
rmnet_mhi_ptr->dev_index = i;
|
||||
|
||||
res = mhi_register_channel(
|
||||
&(rmnet_mhi_ptr->tx_client_handle),
|
||||
rmnet_mhi_ptr->tx_channel, 0,
|
||||
&rmnet_mhi_info, rmnet_mhi_ptr);
|
||||
|
||||
if (MHI_STATUS_SUCCESS != res) {
|
||||
rmnet_mhi_ptr->tx_client_handle = 0;
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"mhi_register_channel failed chan %d ret %d\n",
|
||||
rmnet_mhi_ptr->tx_channel, res);
|
||||
}
|
||||
res = mhi_register_channel(
|
||||
&(rmnet_mhi_ptr->rx_client_handle),
|
||||
rmnet_mhi_ptr->rx_channel, 0,
|
||||
&rmnet_mhi_info, rmnet_mhi_ptr);
|
||||
|
||||
if (MHI_STATUS_SUCCESS != res) {
|
||||
rmnet_mhi_ptr->rx_client_handle = 0;
|
||||
rmnet_log(MSG_CRITICAL,
|
||||
"mhi_register_channel failed chan %d, ret %d\n",
|
||||
rmnet_mhi_ptr->rx_channel, res);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rmnet_mhi_exit(void)
|
||||
{
|
||||
struct rmnet_mhi_private *rmnet_mhi_ptr = 0;
|
||||
int index = 0;
|
||||
|
||||
for (index = 0; index < MHI_RMNET_DEVICE_COUNT; index++) {
|
||||
rmnet_mhi_ptr = &rmnet_mhi_ctxt_list[index];
|
||||
mhi_deregister_channel(rmnet_mhi_ptr->tx_client_handle);
|
||||
mhi_deregister_channel(rmnet_mhi_ptr->rx_client_handle);
|
||||
}
|
||||
}
|
||||
|
||||
module_exit(rmnet_mhi_exit);
|
||||
module_init(rmnet_mhi_init);
|
||||
|
||||
MODULE_DESCRIPTION("MHI RMNET Network Interface");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Add table
Reference in a new issue