blob: d93f24c614744e1750af74e70853976daaffec0e [file] [log] [blame]
/*
* FILE NAME cpmodem_wrapper.c
*
* BRIEF MODULE DESCRIPTION
* Custom USB modem wrapper module
*
* Author: CradlePoint Technology, Inc. <source@cradlepoint.com>
* Ben Kendall <benk@cradlepoint.com>
* Cory Atkin <catkin@cradlepoint.com>
*
* Copyright 2012-2023, CradlePoint Technology, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*/
// Necessary includes for this device driver
#include <linux/module.h> // Needed by all modules
#include <linux/kernel.h> // Needed for KERN_xxxx
#include <linux/init.h> // Needed for the macros
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/list.h>
#include <linux/netdevice.h>
#include <cpmodem_wrapper.h>
#define RUNTIME_DEBUG_TRACE (1 << 0)
#define RUNTIME_DEBUG_INFO (1 << 1)
#define RUNTIME_DEBUG_WARN (1 << 2)
#define RUNTIME_DEBUG_ERROR (1 << 3)
//#undef RUNTIME_DEBUG
//#define RUNTIME_DEBUG ( RUNTIME_DEBUG_TRACE | RUNTIME_DEBUG_INFO | RUNTIME_DEBUG_WARN | RUNTIME_DEBUG_ERROR )
static int cp_lkm_wrapper_log_level = 0;
#ifdef RUNTIME_DEBUG
static const char *cp_lkm_wrapper_runtime_debug_level_str[] = {
"ASSERT",
"TRACE",
"INFO",
"WARN",
"ERROR",
};
#else
static const char *cp_lkm_wrapper_debug_log_level_str[] = {
"ASSERT",
"ERROR",
"WARN",
"INFO",
"TRACE",
"PRINTF"
};
#endif
static int cp_out_get_level_index(int level)
{
int level_index = 0;
while (level) {
level = level >> 1;
level_index++;
}
return level_index;
}
static void cp_out(int level, const char * file, int line, const char *fmt, ...)
{
int file_str_len = 0;
char *file_pos = (char *)file;
char *fmt1;
va_list arg;
int level_index = 0;
const char *level_str = NULL;
if (level) { // level of 0 is ASSERT and log - always output
level_index = cp_out_get_level_index(level);
#ifdef RUNTIME_DEBUG
if (!(RUNTIME_DEBUG & level)) {
return;
}
level_str = cp_lkm_wrapper_runtime_debug_level_str[level_index];
#else
if (!(cp_lkm_wrapper_log_level & level)) {
return;
}
level_str = cp_lkm_wrapper_debug_log_level_str[level_index];
#endif
}
va_start(arg, fmt);
if (file) {
char *pos = (char *)file;
while ((pos = strchr(pos, '/'))) {
pos++;
file_pos = pos;
}
file_str_len = strlen(file_pos);
}
fmt1 = kmalloc(strlen(fmt) + file_str_len + 12 + 6, GFP_ATOMIC); // +6 for debug type indication
if (!fmt1) {
return;
}
if (level_str) {
if (file) {
sprintf(fmt1, "%6s %s(%4d):%s\n", level_str, file_pos, line, fmt);
} else {
sprintf(fmt1, "%6s %s\n", level_str, fmt);
}
} else {
if (file) {
sprintf(fmt1, "%s(%4d):%s\n", file_pos, line, fmt);
} else {
sprintf(fmt1, "%s\n", fmt);
}
}
vprintk(fmt1, arg);
kfree(fmt1);
va_end(arg);
}
#ifdef RUNTIME_DEBUG
// assert is always defined if RUNTIME_DEBUG is defined
#define DEBUG_ASSERT(a, args...) \
if (!(a)) { \
cp_out(0, __FILE__, __LINE__, args); \
dump_stack(); \
while(1) { }; \
}
#define DEBUG_TRACE(args...) cp_out(RUNTIME_DEBUG_TRACE, __FILE__, __LINE__, args)
#define DEBUG_INFO(args...) cp_out(RUNTIME_DEBUG_INFO, __FILE__, __LINE__, args)
#define DEBUG_WARN(args...) cp_out(RUNTIME_DEBUG_WARN, __FILE__, __LINE__, args)
#define DEBUG_ERROR(args...) cp_out(RUNTIME_DEBUG_ERROR, __FILE__, __LINE__, args)
#else
#define DEBUG_ASSERT(a, args...)
#define DEBUG_TRACE(args...) cp_out(LOG_DEBUG_LEVEL_TRACE, __FILE__, __LINE__, args)
#define DEBUG_INFO(args...) cp_out(LOG_DEBUG_LEVEL_INFO, __FILE__, __LINE__, args)
#define DEBUG_WARN(args...) cp_out(LOG_DEBUG_LEVEL_WARN, __FILE__, __LINE__, args)
#define DEBUG_ERROR(args...) cp_out(LOG_DEBUG_LEVEL_ERROR, __FILE__, __LINE__, args)
#define DEBUG_PRINTF(args...) cp_out(LOG_DEBUG_LEVEL_PRINTF, __FILE__, __LINE__, args)
#endif
#define LOG(args...) cp_out(0, NULL, 0, args)
void cp_lkm_wrapper_set_log_level(int level)
{
DEBUG_TRACE("%s(%d)", __FUNCTION__, level);
cp_lkm_wrapper_log_level = level;
}
/******************************* usb wrapper module functionality **********************************/
typedef int (*cp_lkm_wrapper_send_op)(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
typedef int (*cp_lkm_wrapper_recv_op)(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_generic_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_generic_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_asix_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_asix_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_asix88179_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_asix88179_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_dip_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_dip_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_ncm_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_ncm_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_msrndis_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_msrndis_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_pegasus_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_pegasus_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_qmap_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static int cp_lkm_qmap_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out);
static void* cp_lkm_msrndis_wrapper_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len);
static void* cp_lkm_ncm_wrapper_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len);
static void* cp_lkm_asix88179_wrapper_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len);
static void* cp_lkm_qmap_wrapper_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len);
#define CP_LKM_WRAPPER_STATE_INIT 0
#define CP_LKM_WRAPPER_STATE_SPLIT 1
#define WRAPPER_WRITE_U8(ptr,val) (*((u8*)(ptr)) = val)
#define WRAPPER_WRITE_U16(ptr,val) (*((u16*)(ptr)) = val)
#define WRAPPER_WRITE_U32(ptr,val) (*((u32*)(ptr)) = val)
#define WRAPPER_READ_U8(ptr) (*((u8*)(ptr)))
#define WRAPPER_READ_U16(ptr) (*((u16*)(ptr)))
#define WRAPPER_READ_U32(ptr) (*((u32*)(ptr)))
struct cp_lkm_wrapper_state_map{
int id;
cp_lkm_wrapper_state_t wrapper_state;
};
#define MAX_STATE_MAPS 16
struct cp_lkm_wrapper_context
{
cp_lkm_wrapper_type_t wrapper;
int send_state; //generic send state that can be used by all wrappers
int recv_state; //generic recv state that can be used by all wrappers
cp_lkm_wrapper_send_op send;
cp_lkm_wrapper_recv_op recv;
int hdr_size;
spinlock_t lock;
struct cp_lkm_wrapper_state_map state_maps[MAX_STATE_MAPS];
int num_state_maps;
struct sk_buff_head skb_ctrl_recv_list;
struct sk_buff_head skb_data_recv_list;
struct sk_buff_head skb_ctrl_send_list;
struct sk_buff_head skb_data_send_list;
};
static void cp_lkm_wrapper_common_init(struct cp_lkm_wrapper_context* cpwc)
{
cpwc->recv_state = CP_LKM_WRAPPER_STATE_INIT;
cpwc->send_state = CP_LKM_WRAPPER_STATE_INIT;
spin_lock_init(&cpwc->lock);
skb_queue_head_init(&cpwc->skb_ctrl_recv_list);
skb_queue_head_init(&cpwc->skb_ctrl_send_list);
skb_queue_head_init(&cpwc->skb_data_recv_list);
skb_queue_head_init(&cpwc->skb_data_send_list);
}
static void cp_lkm_wrapper_clean_list(struct sk_buff_head* list)
{
struct sk_buff *skb;
while((skb = skb_dequeue(list)) != NULL){
DEBUG_INFO("%s() found a straggler", __FUNCTION__);
dev_kfree_skb_any(skb);
}
}
static void cp_lkm_wrapper_common_cleanup(struct cp_lkm_wrapper_context* cpwc)
{
cp_lkm_wrapper_clean_list(&cpwc->skb_ctrl_recv_list);
cp_lkm_wrapper_clean_list(&cpwc->skb_ctrl_send_list);
cp_lkm_wrapper_clean_list(&cpwc->skb_data_recv_list);
cp_lkm_wrapper_clean_list(&cpwc->skb_data_send_list);
}
static struct sk_buff* cp_lkm_wrapper_skb_make_space(struct sk_buff* skb_in, int headspace, int tailspace)
{
int headroom = skb_headroom(skb_in);
int tailroom = skb_tailroom(skb_in);
int space = headspace + tailspace;
if(skb_in == NULL) {
DEBUG_ERROR("%s() NULL skb_in, shouldn't happen", __FUNCTION__);
return NULL;
}
if ((!skb_cloned(skb_in)) && ((headroom + tailroom) >= space)) {
if (headroom < headspace || tailroom < tailspace) {
//printk("%s() move it\n", __FUNCTION__);
skb_in->data = memmove(skb_in->head + headspace, skb_in->data, skb_in->len);
skb_set_tail_pointer(skb_in, skb_in->len);
}
} else {
struct sk_buff *skb2;
//printk("%s() copy it\n", __FUNCTION__);
skb2 = skb_copy_expand(skb_in, headspace, tailspace, GFP_ATOMIC);
dev_kfree_skb_any(skb_in);
skb_in = skb2;
}
return skb_in;
}
// generic helper function for getting the state for id from the ctxt
static cp_lkm_wrapper_state_t cp_lkm_generic_wrapper_get_state(void* ctxt, int id)
{
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
int i;
cp_lkm_wrapper_state_t wrapper_state = CP_LKM_WRAPPER_INVALID;
for (i = 0; i < cpwc->num_state_maps; i++) {
if (cpwc->state_maps[i].id == id) {
wrapper_state = cpwc->state_maps[i].wrapper_state;
break;
}
}
//printk("%s() id: %d, state: %d\n",__FUNCTION__,id,wrapper_state);
return wrapper_state;
}
static int cp_lkm_generic_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
DEBUG_TRACE("%s()", __FUNCTION__);
*skb_out = skb_in;
return CP_LKM_WRAPPER_RES_DONE;
}
static int cp_lkm_generic_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
int result = CP_LKM_WRAPPER_RES_DONE;
cp_lkm_wrapper_state_t wrapper_state = cp_lkm_generic_wrapper_get_state(ctxt, CP_LKM_WRAPPER_DEFAULT_ID);
//printk("%s() state: %d\n", __FUNCTION__, wrapper_state);
*skb_out = skb_in;
if(wrapper_state == CP_LKM_WRAPPER_CTRL) {
//PPP modems will often use the data endpoints for AT while connecting and then PPP data once connected.
//That's why we need to check the state here
DEBUG_TRACE("%s() ctrl pkt", __FUNCTION__);
*dst = CP_LKM_WRAPPER_DST_CTRL;
}
else{
*dst = CP_LKM_WRAPPER_DST_DATA;
}
return result;
}
#define ASIX_ENABLE_PADDING 0xffff0000
#define ASIX_HDR_MASK 0x0000ffff
#define ASIX_16BIT_EVEN_MASK 0xfffe
//============================================== wrapper specific functions
static int cp_lkm_asix_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
int pad_len;
u32 pkt_len;
u32 padding = ASIX_ENABLE_PADDING;
pad_len = ((skb_in->len + sizeof(u32)) % 512) ? 0 : sizeof(u32);
*skb_out = NULL;
if(!skb_in) {
DEBUG_ERROR("%s() NULL skb_in, shouldn't happen", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
//DEBUG_INFO("%s() wrapping", __FUNCTION__);
skb_in = cp_lkm_wrapper_skb_make_space(skb_in, sizeof(u32), pad_len);
if (!skb_in){
DEBUG_INFO("%s() couldn't expand", __FUNCTION__);
*skb_out = NULL;
return CP_LKM_WRAPPER_RES_ERROR;
}
//generate the len for the header
pkt_len = ((skb_in->len ^ ASIX_HDR_MASK) << 16) + skb_in->len;
skb_push(skb_in, sizeof(u32));
cpu_to_le32s(&pkt_len);
memcpy(skb_in->data, &pkt_len, sizeof(u32));
if (pad_len) {
cpu_to_le32s(&padding);
memcpy(skb_tail_pointer(skb_in), &padding, sizeof(u32));
skb_put(skb_in, sizeof(u32));
}
//DEBUG_INFO("%s() wrapped", __FUNCTION__);
*skb_out = skb_in;
return CP_LKM_WRAPPER_RES_DONE;
}
static int cp_lkm_asix_wrapper_recv(void *ctxt, int *dst, int *mux_id, struct sk_buff *skb_in, struct sk_buff **skb_out)
{
u8 *head;
u32 hdr;
char *pkt;
struct sk_buff *pkt_skb;
u16 size;
struct cp_lkm_wrapper_context *cpwc = (struct cp_lkm_wrapper_context *)ctxt;
int result = CP_LKM_WRAPPER_RES_DONE;
cp_lkm_wrapper_state_t wrapper_state = cp_lkm_generic_wrapper_get_state(ctxt, CP_LKM_WRAPPER_DEFAULT_ID);
*skb_out = NULL;
//skb_in is NULL when we returned 'again' previously and so the caller is recalling us. This means there should be
//a queue'd skb for us to process.
if(!skb_in) {
DEBUG_TRACE("%s() had a pending", __FUNCTION__);
skb_in = skb_dequeue(&cpwc->skb_data_recv_list);
}
if(!skb_in) {
//nothing more to do
DEBUG_TRACE("%s() done", __FUNCTION__);
goto asix_recv_done;
}
if(skb_in->len < sizeof(u32)){
DEBUG_ERROR("%s() not enough data", __FUNCTION__);
result = CP_LKM_WRAPPER_RES_ERROR;
goto asix_recv_done;
}
//read the hdr off the front
head = (u8 *) skb_in->data;
memcpy(&hdr, head, sizeof(u32));
le32_to_cpus(&hdr);
pkt = head + sizeof(u32);
skb_pull(skb_in, sizeof(u32));
//the complement sizes don't match, what to do? just keep going
if ((short)(hdr & ASIX_HDR_MASK) !=
~((short)((hdr & ~(ASIX_HDR_MASK)) >> 16))) {
DEBUG_INFO("%s(), bad length", __FUNCTION__);
}
// get the packet length
size = (u16) (hdr & ASIX_HDR_MASK);
//if exact fit, send it
if ((skb_in->len) - ((size + 1) & ASIX_16BIT_EVEN_MASK) == 0){
DEBUG_TRACE("%s(), exact fit", __FUNCTION__);
*skb_out = skb_in;
skb_in = NULL; //so we don't free it below
goto asix_recv_done;
}
if (size > ETH_FRAME_LEN || size > skb_in->len) {
//deverr(dev,"asix_rx_fixup() Bad RX Length %d", size);
DEBUG_ERROR("%s() too big or buff too small", __FUNCTION__);
result = CP_LKM_WRAPPER_RES_ERROR;
goto asix_recv_done;
}
//multiple pkts in this one. Have to copy them
pkt_skb = skb_clone(skb_in, GFP_ATOMIC);
if (!pkt_skb) {
result = CP_LKM_WRAPPER_RES_ERROR;
goto asix_recv_done;
}
pkt_skb->len = size;
pkt_skb->data = pkt;
skb_set_tail_pointer(pkt_skb, size);
*skb_out = pkt_skb;
//This skb has multiple pkts. We just cloned the first pkt into pkt_skb above. Move past that data and if there
//is any more data left, enqueue it and return 'again' so we can process it.
skb_pull(skb_in, (size + 1) & ASIX_16BIT_EVEN_MASK);
//if have more (at least hdr size worth), requeue and tell caller to come again sometime
if (skb_in->len <= sizeof(u32)){
DEBUG_ERROR("%s() overflowed", __FUNCTION__);
result = CP_LKM_WRAPPER_RES_ERROR;
goto asix_recv_done;
}
DEBUG_TRACE("%s() more to do", __FUNCTION__);
skb_queue_tail(&cpwc->skb_data_recv_list, skb_in);
skb_in = NULL;
result = CP_LKM_WRAPPER_RES_AGAIN;
asix_recv_done:
if(skb_in) {
dev_kfree_skb_any(skb_in);
}
//if error, clear the out skb if any
if(result == CP_LKM_WRAPPER_RES_ERROR) {
if(*skb_out) {
dev_kfree_skb_any(*skb_out);
*skb_out = NULL;
}
}
DEBUG_TRACE("%s() done result: 0x%x skb_out:%p", __FUNCTION__, result, *skb_out);
if(wrapper_state == CP_LKM_WRAPPER_CTRL) {
DEBUG_TRACE("%s() ctrl pkt", __FUNCTION__);
*dst = CP_LKM_WRAPPER_DST_CTRL;
}
else{
*dst = CP_LKM_WRAPPER_DST_DATA;
}
return result;
}
// asix88179 defines
#define RX_HDR_CRC_ERR (1 << 31) // should this be 29?
#define RX_HDR_DROP_ERR (1 << 30) // should this be 31?
#define RX_HDR_L3CSUM_ERR 2
#define RX_HDR_L4CSUM_ERR 1
#define RX_HDR_L4_TYPE_UDP 4
#define RX_HDR_L4_TYPE_TCP 16
#define RX_HDR_L4_TYPE_MASK 0x1c
struct cp_lkm_asix88179_wrapper_context {
struct cp_lkm_wrapper_context common;
u32 max_transfer_len;
u32 *pkt_hdr;
int pkt_cnt;
};
#define ASIX_88179_ENABLE_PADDING 0x80008000
#define ASIX_88179_13BIT_MASK 0x1fff
#define ASIX_88179_8BIT_BOUNDARY_MASK 0xFFF8
static int cp_lkm_asix88179_wrapper_send(void *ctxt, int src, int mux_id, struct sk_buff *skb_in, struct sk_buff **skb_out)
{
struct sk_buff *skb2;
struct cp_lkm_asix88179_wrapper_context *asix88179_wc = (struct cp_lkm_asix88179_wrapper_context *)ctxt;
u32 hdr1;
u32 hdr2;
int frame_size = asix88179_wc->max_transfer_len;
u32 mss;
*skb_out = NULL;
mss = skb_shinfo(skb_in)->gso_size;
hdr1 = skb_in->len;
hdr2 = mss;
if (((skb_in->len + 8) % frame_size) == 0) {
hdr2 |= ASIX_88179_ENABLE_PADDING; // enable padding
}
// make space for both headers
skb2 = cp_lkm_wrapper_skb_make_space(skb_in, sizeof(u32) * 2, 0);
if (!skb2) {
// skb_in is already freed in cp_lkm_wrapper_skb_make_space
printk("%s() - could not make space\n", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
skb_in = skb2;
cpu_to_le32s(&hdr2);
skb_push(skb_in, sizeof(u32));
skb_copy_to_linear_data(skb_in, &hdr2, sizeof(u32));
cpu_to_le32s(&hdr1);
skb_push(skb_in, sizeof(u32));
skb_copy_to_linear_data(skb_in, &hdr1, sizeof(u32));
*skb_out = skb_in;
return CP_LKM_WRAPPER_RES_DONE;
}
static void cp_lkm_asix88179_check_csum(struct sk_buff *skb, u32 *pkt_hdr)
{
u32 err_ind = *pkt_hdr;
bool hdr_err = (err_ind & RX_HDR_L3CSUM_ERR) || (err_ind & RX_HDR_L4CSUM_ERR);
bool csum_valid = ((err_ind & RX_HDR_L4_TYPE_MASK) == RX_HDR_L4_TYPE_TCP) || ((err_ind & RX_HDR_L4_TYPE_MASK) == RX_HDR_L4_TYPE_UDP);
skb->ip_summed = CHECKSUM_NONE;
if (!hdr_err && csum_valid) {
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
}
static unsigned long total_pkt_cnt = 0;
static unsigned long total_pkt_processed = 0;
static int cp_lkm_asix88179_wrapper_recv(void *ctxt, int *dst, int *mux_id, struct sk_buff *skb_in, struct sk_buff **skb_out)
{
u32 hdr;
u16 hdr_off;
struct cp_lkm_asix88179_wrapper_context* cp_88179wc = (struct cp_lkm_asix88179_wrapper_context*)ctxt;
struct cp_lkm_wrapper_context *cpwc = (struct cp_lkm_wrapper_context *)ctxt;
int result = CP_LKM_WRAPPER_RES_DONE;
struct sk_buff *pkt_skb;
u16 pkt_len;
bool crc_runt;
unsigned int end_len;
cp_lkm_wrapper_state_t wrapper_state = cp_lkm_generic_wrapper_get_state(ctxt, CP_LKM_WRAPPER_DEFAULT_ID);
*skb_out = NULL;
DEBUG_TRACE("%s()", __FUNCTION__);
//skb_in is NULL when we returned 'again' previously and so the caller is recalling us. This means there should be
//a queue'd skb for us to process.
if(!skb_in) {
DEBUG_TRACE("%s() had a pending", __FUNCTION__);
skb_in = skb_dequeue(&cpwc->skb_data_recv_list);
} else {
DEBUG_TRACE("%s() 1st pkt of queue, skb_in->len=%x", __FUNCTION__, skb_in->len);
skb_trim(skb_in, skb_in->len - 4);
memcpy(&hdr, skb_tail_pointer(skb_in), sizeof(u32));
le32_to_cpus(&hdr);
cp_88179wc->pkt_cnt = (u16)hdr;
total_pkt_cnt += cp_88179wc->pkt_cnt;
hdr_off = (u16)(hdr >> 16);
cp_88179wc->pkt_hdr = (u32 *)(skb_in->data + hdr_off);
le32_to_cpus(cp_88179wc->pkt_hdr);
}
if(!skb_in) {
//nothing more to do
DEBUG_TRACE("%s() done", __FUNCTION__);
goto asix_recv_done;
}
if(skb_in->len < sizeof(u32)){
DEBUG_ERROR("%s() not enough data", __FUNCTION__);
result = CP_LKM_WRAPPER_RES_ERROR;
goto asix_recv_done;
}
while (cp_88179wc->pkt_cnt--) {
pkt_len = (*cp_88179wc->pkt_hdr >> 16) & ASIX_88179_13BIT_MASK;
end_len = (pkt_len + 7) & ASIX_88179_8BIT_BOUNDARY_MASK;
DEBUG_TRACE("%s() rx_hdr = %x, pkt_cnt=%x, pkt_hdr=%x, pkt_len=%x", __FUNCTION__, hdr, cp_88179wc->pkt_cnt, cp_88179wc->pkt_hdr, pkt_len);
// Check CRC or runt packet
crc_runt = (*cp_88179wc->pkt_hdr & RX_HDR_CRC_ERR) || (*cp_88179wc->pkt_hdr & RX_HDR_DROP_ERR);
if (crc_runt) {
skb_pull(skb_in, end_len);
cp_88179wc->pkt_hdr++;
le32_to_cpus(cp_88179wc->pkt_hdr);
DEBUG_TRACE("%s() crc error or runt", __FUNCTION__);
continue;
}
total_pkt_processed++;
//multiple packets in this one. Have to copy them
pkt_skb = skb_clone(skb_in, GFP_ATOMIC);
if (!pkt_skb) {
result = CP_LKM_WRAPPER_RES_ERROR;
goto asix_recv_done;
}
pkt_skb->data = skb_in->data + 2;
pkt_skb->len = pkt_len;
pkt_skb->truesize = pkt_len + sizeof(struct sk_buff);
skb_set_tail_pointer(pkt_skb, pkt_len);
cp_lkm_asix88179_check_csum(pkt_skb, cp_88179wc->pkt_hdr);
*skb_out = pkt_skb;
if (cp_88179wc->pkt_cnt != 0) {
//This skb has multiple pkts. We just cloned the first pkt into pkt_skb above. Move past that data and if there
//is any more data left, enqueue it and return 'again' so we can process it.
skb_pull(skb_in, end_len);
cp_88179wc->pkt_hdr++;
le32_to_cpus(cp_88179wc->pkt_hdr);
DEBUG_TRACE("%s() more to do", __FUNCTION__);
skb_queue_tail(&cpwc->skb_data_recv_list, skb_in);
skb_in = NULL;
result = CP_LKM_WRAPPER_RES_AGAIN;
}
break;
}
asix_recv_done:
if(skb_in) {
dev_kfree_skb_any(skb_in);
}
//if error, clear the out skb if any
if(result == CP_LKM_WRAPPER_RES_ERROR) {
if(*skb_out) {
dev_kfree_skb_any(*skb_out);
*skb_out = NULL;
}
}
DEBUG_TRACE("%s() done result: 0x%x skb_out:%p", __FUNCTION__, result, *skb_out);
if(wrapper_state == CP_LKM_WRAPPER_CTRL) {
DEBUG_TRACE("%s() ctrl pkt", __FUNCTION__);
*dst = CP_LKM_WRAPPER_DST_CTRL;
} else{
*dst = CP_LKM_WRAPPER_DST_DATA;
}
return result;
}
static void* cp_lkm_asix88179_wrapper_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len)
{
struct cp_lkm_asix88179_wrapper_context* asix88179_wc;
struct cp_lkm_wrapper_context* wc;
asix88179_wc = kzalloc(sizeof(struct cp_lkm_asix88179_wrapper_context), GFP_KERNEL);
if(!asix88179_wc) {
return NULL;
}
if(wrapper_info) {
asix88179_wc->max_transfer_len = *((u32*)(wrapper_info));
DEBUG_INFO("%s(), max transfer:%d", __FUNCTION__, asix88179_wc->max_transfer_len);
} else {
DEBUG_ERROR("%s(),no max transfer set", __FUNCTION__);
}
wc = (struct cp_lkm_wrapper_context*)asix88179_wc;
cp_lkm_wrapper_common_init(wc);
wc->wrapper = wrapper;
wc->send = cp_lkm_asix88179_wrapper_send;
wc->recv = cp_lkm_asix88179_wrapper_recv;
return asix88179_wc;
}
// ===== pegasus wrapper
static int cp_lkm_pegasus_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
int padlen = 0;
u32 packet_len;
u32 hdrlen = 2;
*skb_out = NULL;
if(skb_in == NULL) {
DEBUG_ERROR("%s() NULL skb_in, shouldn't happen", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
//DEBUG_INFO("%s() wrapping", __FUNCTION__);
skb_in = cp_lkm_wrapper_skb_make_space(skb_in, hdrlen, padlen);
if (!skb_in){
DEBUG_ERROR("%s() couldn't expand", __FUNCTION__);
*skb_out = NULL;
return CP_LKM_WRAPPER_RES_ERROR;
}
//generate the mirror'd len for the header
packet_len = skb_in->len;
skb_push(skb_in, sizeof(u16));
WRAPPER_WRITE_U16(skb_in->data, cpu_to_le16(packet_len));
//DEBUG_INFO("%s() wrapped", __FUNCTION__);
*skb_out = skb_in;
return CP_LKM_WRAPPER_RES_DONE;
}
static int cp_lkm_pegasus_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
u32 hdr_size;
u32 pkt_size;
//DEBUG_INFO("%s() unwrap it", __FUNCTION__);
*skb_out = NULL;
*dst = CP_LKM_WRAPPER_DST_DATA;
hdr_size = 2;
if(skb_in == NULL) {
//nothing more to do
DEBUG_TRACE("%s() done", __FUNCTION__);
return CP_LKM_WRAPPER_RES_DONE;
}
// If don't have enough for the headers, it is an error
if(skb_in->len < hdr_size) {
dev_kfree_skb_any(skb_in);
return CP_LKM_WRAPPER_RES_ERROR;
}
//read the pkt size and make sure have enough data. the pkt size
//doesn't include the dip header so add it in for comparison
pkt_size = le16_to_cpu(WRAPPER_READ_U16(skb_in->data));
if(pkt_size > skb_in->len){
DEBUG_ERROR("%s() bad data pkt pkt_size:%d, data size: %d", __FUNCTION__, pkt_size, skb_in->len);
dev_kfree_skb_any(skb_in);
return CP_LKM_WRAPPER_RES_ERROR;
}
//remove the dip and ethernet hdrs
skb_pull(skb_in, hdr_size);
*skb_out = skb_in;
DEBUG_TRACE("%s() data pkt", __FUNCTION__);
return CP_LKM_WRAPPER_RES_DONE;
}
//===================direct ip wrapper
#define SIERRA_DIRECTIP_UPLINK_DATA_INDICATION_MSGID 0x3F
#define SIERRA_DIRECTIP_UPLINK_DATA_INDICATION_EXTENDED_MSGID 0x0002
#define SIERRA_DIRECTIP_UPLINK_DATA_INDICATION_MSG_SPECIFIC_ID 0x00
#define SIERRA_DIRECTIP_HDR_SIZE 6
#define SIERRA_DIRECTIP_ETHER_SIZE 14
static int cp_lkm_dip_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
u32 packet_len;
u32 hdr_len;
cp_lkm_wrapper_state_t wrapper_state = cp_lkm_generic_wrapper_get_state(ctxt, CP_LKM_WRAPPER_DEFAULT_ID);
*skb_out = NULL;
//DEBUG_INFO("%s() wrap it", __FUNCTION__);
if(skb_in == NULL) {
DEBUG_ERROR("%s() NULL skb_in, shouldn't happen", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
//in ctrl mode, we don't put a wrapper on (only after data comes up)
if(wrapper_state == CP_LKM_WRAPPER_CTRL) {
*skb_out = skb_in;
DEBUG_TRACE("%s() ctrl pkt", __FUNCTION__);
return CP_LKM_WRAPPER_RES_DONE;
}
//DEBUG_INFO("%s() wrapping", __FUNCTION__);
// Add header:
// HIP header: 6 bytes
// Fake ethernet hdr: 14 bytes
hdr_len = SIERRA_DIRECTIP_HDR_SIZE + SIERRA_DIRECTIP_ETHER_SIZE;
skb_in = cp_lkm_wrapper_skb_make_space(skb_in, hdr_len, 0);
if (!skb_in){
DEBUG_ERROR("%s() couldn't expand", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
packet_len = skb_in->len;
packet_len += SIERRA_DIRECTIP_ETHER_SIZE; //add bytes for the ethernet hdr (the dip hdr isn't counted in the len)
//ethernet protocol
skb_push(skb_in, sizeof(u16));
WRAPPER_WRITE_U16(skb_in->data, cpu_to_be16(0x0800));
//bogus ethernet addrs (modem side doesn't care)
skb_push(skb_in, 12);
memset(skb_in->data, 0, 12);
//extended msg id
skb_push(skb_in, sizeof(u16));
WRAPPER_WRITE_U16(skb_in->data, cpu_to_be16(SIERRA_DIRECTIP_UPLINK_DATA_INDICATION_EXTENDED_MSGID));
//msg specific id
skb_push(skb_in, 1);
WRAPPER_WRITE_U8(skb_in->data, SIERRA_DIRECTIP_UPLINK_DATA_INDICATION_MSG_SPECIFIC_ID);
//msg indication id
skb_push(skb_in, 1);
WRAPPER_WRITE_U8(skb_in->data, SIERRA_DIRECTIP_UPLINK_DATA_INDICATION_MSGID);
//len
skb_push(skb_in, sizeof(u16));
WRAPPER_WRITE_U16(skb_in->data, cpu_to_be16(packet_len));
//DEBUG_INFO("%s() data pkt", __FUNCTION__);
*skb_out = skb_in;
return CP_LKM_WRAPPER_RES_DONE;
}
static int cp_lkm_dip_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
u32 hdr_size;
u32 pkt_size;
cp_lkm_wrapper_state_t wrapper_state = cp_lkm_generic_wrapper_get_state(ctxt, CP_LKM_WRAPPER_DEFAULT_ID);
//DEBUG_INFO("%s() unwrap it", __FUNCTION__);
*skb_out = NULL;
*dst = CP_LKM_WRAPPER_DST_DATA;
hdr_size = SIERRA_DIRECTIP_HDR_SIZE + SIERRA_DIRECTIP_ETHER_SIZE;
if(skb_in == NULL) {
//nothing more to do
DEBUG_TRACE("%s() done", __FUNCTION__);
return CP_LKM_WRAPPER_RES_DONE;
}
//There are no headers on the pkts when in ctrl mode. Only in data mode
if(wrapper_state == CP_LKM_WRAPPER_CTRL) {
DEBUG_TRACE("%s() ctrl pkt", __FUNCTION__);
*skb_out = skb_in;
*dst = CP_LKM_WRAPPER_DST_CTRL;
return CP_LKM_WRAPPER_RES_DONE;
}
//from here down, they are data packets
// If don't have enough for the headers, it is an error
if(skb_in->len < hdr_size) {
dev_kfree_skb_any(skb_in);
return CP_LKM_WRAPPER_RES_ERROR;
}
//read the pkt size and make sure have enough data. the pkt size
//doesn't include the dip header so add it in for comparison
pkt_size = be16_to_cpu(WRAPPER_READ_U16(skb_in->data));
if((pkt_size+SIERRA_DIRECTIP_HDR_SIZE) > skb_in->len){
dev_kfree_skb_any(skb_in);
return CP_LKM_WRAPPER_RES_ERROR;
}
//remove the dip and ethernet hdrs
skb_pull(skb_in, hdr_size);
*skb_out = skb_in;
DEBUG_TRACE("%s() data pkt", __FUNCTION__);
return CP_LKM_WRAPPER_RES_DONE;
}
//===================== msrndis wrapper
#define MSRNDIS_REMOTE_NDIS_PACKET_MSG 0x00000001 // data packet
struct cp_lkm_msrndis_wrapper_context{
struct cp_lkm_wrapper_context common;
u32 max_transfer_len;
};
// data pkt header
struct msrndis_data_hdr { // data packet message header (msrndis_hdr preceeds this header) (payload immediately follows)
u32 data_offset;
u32 data_length;
u32 OOB_data_offset;
u32 OOB_data_length;
u32 num_OOB_data_elements;
u32 per_packet_info_offset;
u32 per_packet_info_length;
u32 reserved[2];
}__attribute__((packed));
struct msrndis_hdr { // general msrndis header at beginning of all messages
u32 message_type;
u32 message_length;
} __attribute__((packed));
static int cp_lkm_msrndis_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
u32 data_hdr_len;
u32 msg_hdr_len;
struct msrndis_data_hdr hdr;
u32 packet_len;
*skb_out = NULL;
DEBUG_TRACE("%s() wrap it", __FUNCTION__);
if(skb_in == NULL) {
DEBUG_ERROR("%s() NULL skb_in, shouldn't happen", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
// This bad boy has pkt data plus a data hdr plus a msg header (it was created by microsoft after all)
packet_len = skb_in->len;
data_hdr_len = sizeof(struct msrndis_data_hdr);
msg_hdr_len = sizeof(struct msrndis_hdr);
//need to add space for both headers
skb_in = cp_lkm_wrapper_skb_make_space(skb_in, data_hdr_len + msg_hdr_len, 0);
if (!skb_in){
DEBUG_ERROR("%s() couldn't expand", __FUNCTION__);
*skb_out = NULL;
return CP_LKM_WRAPPER_RES_ERROR;
}
//create the data hdr
memset(&hdr, 0x00, data_hdr_len);
hdr.data_offset = cpu_to_le32(data_hdr_len); //data starts after the data hdr
hdr.data_length = cpu_to_le32(packet_len); //the data hdr doesn't include the hdr lenght in length, only the data
skb_push(skb_in, data_hdr_len);
memcpy(skb_in->data, &hdr, data_hdr_len);
//Create the msg hdr, the length includes the msg header size as well
packet_len = skb_in->len + msg_hdr_len;
skb_push(skb_in, sizeof(u32));
WRAPPER_WRITE_U32(skb_in->data, cpu_to_le32(packet_len));
skb_push(skb_in, sizeof(u32));
WRAPPER_WRITE_U32(skb_in->data, cpu_to_le32(MSRNDIS_REMOTE_NDIS_PACKET_MSG));
DEBUG_TRACE("%s() data pkt", __FUNCTION__);
*skb_out = skb_in;
return CP_LKM_WRAPPER_RES_DONE;
}
static int cp_lkm_msrndis_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
u32 data_hdr_len = sizeof(struct msrndis_data_hdr);
u32 msg_hdr_len = sizeof(struct msrndis_hdr);
struct msrndis_data_hdr hdr;
u32 adv = 0;
u32 out_len;
u32 pkt_len;
u32 pkt_type;
struct sk_buff *skb_working = NULL;
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
struct cp_lkm_msrndis_wrapper_context* msrndis_wc = (struct cp_lkm_msrndis_wrapper_context*)ctxt;
// DEBUG_INFO("%s() unwrap it", __FUNCTION__);
*skb_out = NULL;
*dst = CP_LKM_WRAPPER_DST_DATA;
if (skb_in) {
cpwc->recv_state = CP_LKM_WRAPPER_STATE_INIT;
DEBUG_TRACE("%s() done", __FUNCTION__);
if (0 == skb_in->len) {
dev_kfree_skb_any(skb_in);
skb_in = NULL;
} else if (msrndis_wc->max_transfer_len == skb_in->len) {
DEBUG_INFO("%s() - max transfer - setting split", __FUNCTION__);
cpwc->recv_state = CP_LKM_WRAPPER_STATE_SPLIT;
}
}
skb_working = skb_dequeue(&cpwc->skb_data_recv_list);
if (!skb_working) {
skb_working = skb_in;
} else if (skb_in) {
// append data to skb_working
skb_working = cp_lkm_wrapper_skb_make_space(skb_working, 0, skb_in->len);
if(!skb_working) {
DEBUG_WARN("%s() failed to make space", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
memcpy(skb_tail_pointer(skb_working), skb_in->data, skb_in->len);
skb_put(skb_working, skb_in->len);
dev_kfree_skb_any(skb_in);
}
if (!skb_working) {
return CP_LKM_WRAPPER_RES_DONE;
}
if(skb_working->len < msg_hdr_len) {
if (CP_LKM_WRAPPER_STATE_SPLIT != cpwc->recv_state) {
DEBUG_INFO("%s() - flushing remaining byte count:%d", __FUNCTION__, skb_working->len);
dev_kfree_skb_any(skb_working);
return CP_LKM_WRAPPER_RES_ERROR;
}
// expecting a split packet
skb_queue_tail(&cpwc->skb_data_recv_list, skb_working);
return CP_LKM_WRAPPER_RES_DONE;
}
pkt_type = le32_to_cpu(WRAPPER_READ_U32(skb_working->data));
skb_pull(skb_working, 4);
pkt_len = le32_to_cpu(WRAPPER_READ_U32(skb_working->data));
skb_pull(skb_working, 4);
// try to determine if this packet len is reasonable
if (pkt_len > (4 * 1024) || pkt_len < 0) {
// probably bad packet length - drop the packets
DEBUG_WARN("%s() - bad packet len:%x", __FUNCTION__, pkt_len);
DEBUG_WARN("%s() - flushing remaining byte count:%d", __FUNCTION__, skb_working->len);
dev_kfree_skb_any(skb_working);
// DEBUG_ASSERT(0, "bad packet len:%d", pkt_len);
return CP_LKM_WRAPPER_RES_ERROR;
}
if (skb_working->len < data_hdr_len) {
if (CP_LKM_WRAPPER_STATE_SPLIT != cpwc->recv_state) {
DEBUG_INFO("%s() - flushing remaining byte count:%d", __FUNCTION__, skb_working->len);
dev_kfree_skb_any(skb_working);
return CP_LKM_WRAPPER_RES_ERROR;
}
// expecting a split packet
skb_push(skb_working, msg_hdr_len);
skb_queue_tail(&cpwc->skb_data_recv_list, skb_working);
return CP_LKM_WRAPPER_RES_DONE;
}
memcpy(&hdr, skb_working->data, data_hdr_len);
hdr.data_offset = le32_to_cpu(hdr.data_offset);
hdr.data_length = le32_to_cpu(hdr.data_length);
skb_pull(skb_working, data_hdr_len);
//account for any gaps between the end of the hdr and the start of data
if(hdr.data_offset > data_hdr_len) {
adv = hdr.data_offset - data_hdr_len;
if(skb_working->len < adv) {
if (CP_LKM_WRAPPER_STATE_SPLIT != cpwc->recv_state) {
DEBUG_INFO("%s() - flushing remaining byte count:%d", __FUNCTION__, skb_working->len);
dev_kfree_skb_any(skb_working);
return CP_LKM_WRAPPER_RES_ERROR;
}
// expecting a split packet
skb_push(skb_working, msg_hdr_len + data_hdr_len);
skb_queue_tail(&cpwc->skb_data_recv_list, skb_working);
return CP_LKM_WRAPPER_RES_DONE;
}
skb_pull(skb_working, adv);
}
if(skb_working->len < hdr.data_length) {
if (CP_LKM_WRAPPER_STATE_SPLIT != cpwc->recv_state) {
DEBUG_INFO("%s() - flushing remaining byte count:%d", __FUNCTION__, skb_working->len);
dev_kfree_skb_any(skb_working);
return CP_LKM_WRAPPER_RES_ERROR;
}
DEBUG_TRACE("%s() data pkt", __FUNCTION__);
// expecting a split packet
skb_push(skb_working, msg_hdr_len + data_hdr_len + adv);
skb_queue_tail(&cpwc->skb_data_recv_list, skb_working);
return CP_LKM_WRAPPER_RES_DONE;
}
out_len = hdr.data_length;
if (MSRNDIS_REMOTE_NDIS_PACKET_MSG != pkt_type) {
out_len = msg_hdr_len + data_hdr_len + adv + hdr.data_length;
skb_push(skb_working, msg_hdr_len + data_hdr_len + adv);
}
*skb_out = skb_clone(skb_working, GFP_ATOMIC);
if (!(*skb_out)) {
DEBUG_WARN("%s() - couldn't clone skb", __FUNCTION__);
dev_kfree_skb_any(skb_working);
return CP_LKM_WRAPPER_RES_ERROR;
}
skb_set_tail_pointer(*skb_out, out_len);
(*skb_out)->len = out_len;
skb_pull(skb_working, out_len);
if (skb_working->len) {
DEBUG_INFO("%s() complete pkt with remaining data: %d", __FUNCTION__, skb_working->len);
skb_queue_tail(&cpwc->skb_data_recv_list, skb_working);
*dst = (MSRNDIS_REMOTE_NDIS_PACKET_MSG == pkt_type) ? CP_LKM_WRAPPER_DST_DATA : CP_LKM_WRAPPER_DST_CTRL;
return CP_LKM_WRAPPER_RES_AGAIN;
}
dev_kfree_skb_any(skb_working);
*dst = (MSRNDIS_REMOTE_NDIS_PACKET_MSG == pkt_type) ? CP_LKM_WRAPPER_DST_DATA : CP_LKM_WRAPPER_DST_CTRL;
return CP_LKM_WRAPPER_RES_DONE;
}
static void* cp_lkm_msrndis_wrapper_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len)
{
struct cp_lkm_msrndis_wrapper_context* msrndis_wc;
struct cp_lkm_wrapper_context* wc;
msrndis_wc = kzalloc(sizeof(struct cp_lkm_msrndis_wrapper_context), GFP_KERNEL);
if(!msrndis_wc) {
return NULL;
}
if(wrapper_info) {
msrndis_wc->max_transfer_len = *((u32*)(wrapper_info));
DEBUG_INFO("%s(), max transfer:%d", __FUNCTION__, msrndis_wc->max_transfer_len);
}
else{
DEBUG_ERROR("%s(),no max transfer set", __FUNCTION__);
}
wc = (struct cp_lkm_wrapper_context*)msrndis_wc;
cp_lkm_wrapper_common_init(wc);
wc->wrapper = wrapper;
wc->send = cp_lkm_msrndis_wrapper_send;
wc->recv = cp_lkm_msrndis_wrapper_recv;
return msrndis_wc;
}
//============== NCM wrapper
//There are 2 modes of operation for an NCM device, 16 bit and 32 bit. 16 bit block allows for transfer bkocks up to 64K in length,
//while 32 allows for 4G length blocks. We will be using the 16 bit, which is set in plug.
#define NTB_HEADER_SIGNATURE 0x484D434E //"NCMH" 16 bit transfer blocks signature.
#define NDP_SIGNATURE_NO_CRC 0x304D434E //"NCM0"
///
/// THIS STRUCTURE MUST BE THE SAME AS THE ONE IN ncm_modem.h
///
struct ncm_ntb_parameters{
u16 wLength;
u16 bmNtbFormatsSupported;
u32 dwNtbInMaxSize;
u16 wNdpInDivisor;
u16 wNdpInPayloadRemainder;
u16 wNdpInAlignment;
u16 reserved;
u32 dwNtbOutMaxSize;
u16 wNdpOutDivisor;
u16 wNdpOutPayloadRemainder;
u16 wNdpOutAlignment;
u16 wNtbOutMaxDatagrams;
};
//NCM Transfer Header (NTH)
struct ncm_transfer_header {
u32 signature;
u16 header_length;
u16 sequence_number;
u16 ntb_length;//length of entire block
u16 ndp_index; //Offset in block in NDP
}__attribute__ ((packed));
struct ncm_datagram_info {
u16 index;
u16 length;
}__attribute__ ((packed));
//NCM Datagram Pointers (NDP)
struct ncm_datagram_pointers {
u32 signature;
u16 length; // Size of this NDP. Must be multiple of 4, and at least 0x10
u16 next_ndp_index; //offset of next ndp in NTB.
struct ncm_datagram_info datagram_info[2]; //Setting to one datagream for now. It's 2 due to the NULL tail item required in list.
}__attribute__ ((packed));
struct cp_lkm_ncm_wrapper_context{
struct cp_lkm_wrapper_context common;
u32 nth_seq_num;
u32 datagram_offset;
struct ncm_ntb_parameters ntb_parms; // usb max transfer size - used to detect runt HIM blocks
};
static int cp_lkm_ncm_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
u32 ndp_padding, datagram_padding, align_factor, total_size, header_size;
u32 payload = skb_in->len;
void *ptr = NULL;
struct cp_lkm_ncm_wrapper_context* ncmwc = (struct cp_lkm_ncm_wrapper_context*)ctxt;
*skb_out = NULL;
//should never see this in here
if(skb_in == NULL) {
return CP_LKM_WRAPPER_RES_DONE;
}
header_size = sizeof(struct ncm_transfer_header);
//Need to align NDP by the align value. offset%align = 0. Add align value -1 and mask off by it's inverse to get aligned offset.
//Then subtract current header size to get the padding.
align_factor = ncmwc->ntb_parms.wNdpInAlignment - 1;
ndp_padding = ((header_size + align_factor) & ~align_factor) - header_size;
header_size += ndp_padding;
header_size += sizeof(struct ncm_datagram_pointers);
//Alignment for
//Need to align NDP by the divisor value + the remainder value. offset%divisor = 0. Add divisor value -1 and mask off by it's inverse to get aligned offset.
//Then subtract current header size to get the alignment padding and add the remainder to get the total padding.
align_factor = ncmwc->ntb_parms.wNdpInDivisor - 1;
datagram_padding = (((header_size + align_factor) & ~align_factor) - header_size) + ncmwc->ntb_parms.wNdpInPayloadRemainder;
header_size += datagram_padding;
total_size = header_size + payload;
//Need to account for our max transfer size allowed by modem. Current modem is 400K, should never hit this.
if (ncmwc->ntb_parms.dwNtbInMaxSize < total_size) {
dev_kfree_skb_any(skb_in);
return CP_LKM_WRAPPER_RES_ERROR;
}
//add space for the header to skb_in
skb_in = cp_lkm_wrapper_skb_make_space(skb_in, header_size, 0);
if(!skb_in) {
DEBUG_WARN("%s() couldn't make space", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
//write NCM Pkt hdr
ptr = (void *)skb_push(skb_in, header_size);
memset(ptr, 0, header_size);
WRAPPER_WRITE_U32(ptr, cpu_to_le32(NTB_HEADER_SIGNATURE));
ptr +=4;
//Write the header size
WRAPPER_WRITE_U16(ptr, cpu_to_le16(sizeof(struct ncm_transfer_header)));
ptr +=4; //Moving 2 to skip using optional sequence number
//Total NTB size
WRAPPER_WRITE_U16(ptr, cpu_to_le16(skb_in->len));
ptr += 2;
//Index of first ndp
WRAPPER_WRITE_U16(ptr, cpu_to_le16(sizeof(struct ncm_transfer_header) + ndp_padding));
ptr += (2 + ndp_padding);
//Write the ndp
WRAPPER_WRITE_U32(ptr, cpu_to_le32(NDP_SIGNATURE_NO_CRC));
ptr +=4;
//Write the ndp size
WRAPPER_WRITE_U16(ptr, cpu_to_le16(sizeof(struct ncm_datagram_pointers)));
ptr +=4; //Moving past 2 reserved as well
//Write the datagram index. It's write after the ntb length
WRAPPER_WRITE_U16(ptr, cpu_to_le16(header_size));
ptr +=2;
//Write the datagram length.
WRAPPER_WRITE_U16(ptr, cpu_to_le16(payload));
//tail entry 0'd in memset.
*skb_out = skb_in;
return CP_LKM_WRAPPER_RES_DONE;
}
/*
* -------------------------------------
* | Signature | NCM Transfer Block
* -------------------------------------
* | Header Length |
* -------------------------------------
* | Sequence Number |
* -------------------------------------
* | Total Packet Length |
* -------------------------------------
* | NDP Index |
* -------------------------------------
*
*
* -------------------------------------
* | Signature | NCM Datagram Pointers
* -------------------------------------
* | Header Length |
* -------------------------------------
* | Index to next NDP |
* -------------------------------------
* | Datagram[0] index |
* -------------------------------------
* | Datagram[0] length |
* -------------------------------------
* | Datagram[1] index |
* -------------------------------------
* | Datagram[1] length |
* -------------------------------------
* .
* .
* .
* -------------------------------------
* | Datagram[n] index |
* -------------------------------------
* | Datagram[n] length |
* -------------------------------------
* | 0 | Termination of header
* -------------------------------------
* | 0 | Termination of header
* ------------------------------------
*
* Ethernet packets....
*
*
* This function processes the NCM Transfer Block. It can consist
* of multiple Ethernet pkts. We specified the max size we could
* handle during plug. Only a single SBK should ever be sent.
*/
static int cp_lkm_ncm_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
u32 tmp_val;
u16 nth_len, ndp_len, datagram_index, datagram_len;
struct sk_buff *ncm_skb_out;
unsigned char *ptr = NULL, *tmp_ptr = NULL;
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
struct cp_lkm_ncm_wrapper_context* ncmwc = (struct cp_lkm_ncm_wrapper_context*)cpwc;
cp_lkm_wrapper_state_t wrapper_state = cp_lkm_generic_wrapper_get_state(ctxt, CP_LKM_WRAPPER_DEFAULT_ID);
*skb_out = NULL;
*dst = CP_LKM_WRAPPER_DST_DATA;
//skb_in is NULL when the caller is recalling us to finish processing the skb.
if(NULL != skb_in) {
//print_hex_dump(KERN_INFO, "SKB_IN:", DUMP_PREFIX_ADDRESS, 16, 1, skb_in->data, 64, false);
ptr = (void *)skb_in->data;
//There are no headers on the pkts when in ctrl mode. Only in data mode. Shouldn't see control on data eps
if(wrapper_state == CP_LKM_WRAPPER_CTRL) {
*skb_out = skb_in;
*dst = CP_LKM_WRAPPER_DST_CTRL;
return CP_LKM_WRAPPER_RES_DONE;
}
// Not enough data for the headers, it is an error.
if(skb_in->len < sizeof(struct ncm_transfer_header) + sizeof(struct ncm_datagram_pointers)) {
//DEBUG_ERROR("%s() NCM ERROR: NCM packet size error, len: %d", __FUNCTION__,skb_in->len);
goto error;
}
//get the signature.
tmp_val = le32_to_cpu(WRAPPER_READ_U32(ptr));
ptr +=4;
if (tmp_val != NTB_HEADER_SIGNATURE) {
DEBUG_ERROR("%s() NCM ERROR: Invalid NCM Signature: 0x%lX", __FUNCTION__, tmp_val);
goto error;
}
//Check the header length
nth_len = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
if (nth_len != sizeof(struct ncm_transfer_header)) {
DEBUG_ERROR("%s() NCM ERROR: Invalid NTH Size: %d", __FUNCTION__, nth_len);
goto error;
}
ncmwc->nth_seq_num = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
//Get the total packet length
tmp_val = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
if (tmp_val != skb_in->len || tmp_val > ncmwc->ntb_parms.dwNtbOutMaxSize) {
DEBUG_ERROR("%s() NCM ERROR: Invalid length: 0x%lX, skb_in->len: 0x%lX, dwNtbOutMaxSize: 0x%lX", __FUNCTION__, tmp_val, skb_in->len, ncmwc->ntb_parms.dwNtbOutMaxSize);
goto error;
}
//Get NDP index
tmp_val = le16_to_cpu(WRAPPER_READ_U16(ptr));
//Validate against spec. Table 3-2
if (((tmp_val % 4) != 0) && (tmp_val < nth_len)) {
DEBUG_ERROR("%s() NCM ERROR: Invalid NDP index: 0x%lX", __FUNCTION__, tmp_val);
goto error;
}
//Move pointer to ndp offset
ptr = ((void *)skb_in->data) + tmp_val;
//get the signature.
tmp_val = le32_to_cpu(WRAPPER_READ_U32(ptr));
ptr +=4;
//We specified no CRC during plug
if (tmp_val != NDP_SIGNATURE_NO_CRC) {
DEBUG_ERROR("%s() NCM ERROR: Invalid NDP Signature: 0x%lX", __FUNCTION__, tmp_val);
goto error;
}
//Check the header length
ndp_len = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
//Need to subtract size of ncm_datagram_info from size of ncm_datagram_pointers to account form empty NTB.
if ((ndp_len < sizeof(struct ncm_datagram_pointers)-sizeof(struct ncm_datagram_info))|| (ndp_len % 4 != 0)) {
DEBUG_ERROR("%s() NCM ERROR: Invalid NDP Size: %ld", __FUNCTION__, ndp_len);
goto error;
}
//Move past 2 bytes reserved.
ptr += 2;
//Validate datagram pointers. There must be a terminator entry or the
//entire packet is to be refused. Section 3.7
tmp_ptr = ptr;
ndp_len -= 8; //Subtrace header to get length of datagram pointers in bytes.
while (0 < ndp_len) {
datagram_index = le16_to_cpu(WRAPPER_READ_U16(tmp_ptr));
tmp_ptr +=2;
datagram_len = le16_to_cpu(WRAPPER_READ_U16(tmp_ptr));
tmp_ptr +=2;
//Need to check for early 0's.
if (0 == datagram_index && 0 == datagram_len) {
break;
}
ndp_len -= sizeof(struct ncm_datagram_info);
}
//We should be at the terminator value.
if (datagram_index != 0 && datagram_len != 0) {
goto error;
}
datagram_index = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
datagram_len = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
} else {
//We'd better have an offset
if (0 == ncmwc->datagram_offset) {
goto error;
}
skb_in = skb_dequeue(&cpwc->skb_data_recv_list);
//We'd better have a queue'd skb for us to process.
if (NULL == skb_in) {
goto error;
}
ptr = skb_in->data + ncmwc->datagram_offset;
//print_hex_dump(KERN_INFO, "Data Gram PTRs:", DUMP_PREFIX_ADDRESS, 16, 1, ptr, 64, false);
//read the next datagram info
datagram_index = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
datagram_len = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
//DEBUG_TRACE("%s() dp_index: 0x%lX", __FUNCTION__, datagram_index);
//DEBUG_TRACE("%s() datagram_len: 0x%lX", __FUNCTION__, datagram_len);
}
//Save offset to next datagram pointer
ncmwc->datagram_offset = ptr - skb_in->data;
//Handle NULL datagram pointer entries. Section 3.7. Terminator would be both having value of 0,
//Spec says ignore anything after either of them is NULL
if (0 == datagram_index || 0 == datagram_len) {
if(skb_in) {
dev_kfree_skb_any(skb_in);
}
ncmwc->datagram_offset = 0;
return CP_LKM_WRAPPER_RES_DONE;
}
//copy out the data packet
ncm_skb_out = skb_clone(skb_in, GFP_ATOMIC);
if (!ncm_skb_out) {
DEBUG_ERROR("%s() Failed to clone skb_in", __FUNCTION__);
goto error;
}
ncm_skb_out->len = datagram_len;
ncm_skb_out->data += datagram_index;
skb_set_tail_pointer(ncm_skb_out, ncm_skb_out->len);
*skb_out = ncm_skb_out;
//print_hex_dump(KERN_INFO, "skb_out:", DUMP_PREFIX_ADDRESS, 0, 1, ncm_skb_out->data, 64, false);
//Check next datagram pointer for terminator
datagram_index = le16_to_cpu(WRAPPER_READ_U16(ptr));
ptr +=2;
datagram_len = le16_to_cpu(WRAPPER_READ_U16(ptr));
if (0 == datagram_index || 0 == datagram_len) {
if(skb_in) {
dev_kfree_skb_any(skb_in);
}
ncmwc->datagram_offset = 0;
return CP_LKM_WRAPPER_RES_DONE;
}
//Not done, so queue up for next call. We need to come back to process the terminator packet.
skb_queue_tail(&cpwc->skb_data_recv_list, skb_in);
return CP_LKM_WRAPPER_RES_AGAIN;
error:
if(skb_in) {
dev_kfree_skb_any(skb_in);
}
ncmwc->datagram_offset = 0;
return CP_LKM_WRAPPER_RES_ERROR;
}
static void* cp_lkm_ncm_wrapper_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len)
{
struct cp_lkm_ncm_wrapper_context* ncmwc;
struct cp_lkm_wrapper_context* wc;
DEBUG_TRACE("%s() ", __FUNCTION__);
ncmwc = kzalloc(sizeof(struct cp_lkm_ncm_wrapper_context), GFP_KERNEL);
if(!ncmwc) {
return NULL;
}
if(wrapper_info) {
memcpy(&ncmwc->ntb_parms,(struct ncm_ntb_parameters*)(wrapper_info), sizeof(struct ncm_ntb_parameters));
}
else{
DEBUG_ERROR("%s(),no ncm ntb parameters", __FUNCTION__);
return NULL;
}
wc = (struct cp_lkm_wrapper_context*)ncmwc;
cp_lkm_wrapper_common_init(wc);
wc->wrapper = wrapper;
wc->send = cp_lkm_ncm_wrapper_send;
wc->recv = cp_lkm_ncm_wrapper_recv;
ncmwc->datagram_offset = 0;
return ncmwc;
}
// ===== QMAP wrapper ================================================================
/*
* qmap mux header:
*
* |-----------------------|-----------------------|-----------------------|-----------------------|
* Octet: | 0 | 1 | 2 | 3 |
* |-----------------------|-----------------------|-----------------------|-----------------------|
* Bit : |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
* |-----------------------|-----------------------|-----------------------|-----------------------|
* Field: |C |R | Pad Bytes | Mux ID | Payload Len With Padding |
* |-----------------------|-----------------------|-----------------------|-----------------------|
*
* C : QMAP control or data packet.
* 1 - QMAP control command
* 0 - Data packet
* R : Reserved
* PAD : Number of bytes padded to achieve 4 byte alignment. Padded bytes can be 0 or not.
* This is only needed if aggregating packets and need next packet to be 4 byte aligned
* Payload Len: Total payload length in bytes including padding (not including header)
*
* Notes: QMAP can aggregate IP packets, each with its own QMAP header in a single USB transfer.
* QMAP adds an empty header at the end, with mux id 0, and len 0.
*
*/
struct qmap_hdr{
u8 pad_bytes;
u8 mux_id;
u16 payload_len;
} __attribute__((packed));
#define CP_LKM_QMAP_DATA 0
#define CP_LKM_QMAP_CTRL 1
static void* cp_lkm_qmap_wrapper_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len)
{
struct cp_lkm_wrapper_context* cpwc;
cpwc = kzalloc(sizeof(struct cp_lkm_wrapper_context), GFP_KERNEL);
if(!cpwc) {
return cpwc;
}
cp_lkm_wrapper_common_init(cpwc);
cpwc->wrapper = wrapper;
cpwc->hdr_size = sizeof(struct qmap_hdr);
cpwc->send = cp_lkm_qmap_wrapper_send;
cpwc->recv = cp_lkm_qmap_wrapper_recv;
return cpwc;
}
/*
We only send one QMAP IP packet at a time.
While the spec is not clear on this (at least to me), it appears we are always supposed to add
a single empty QMAP header at the the end.
For us this will look like this:
QMAP Header
IP pkt
padding
Empty QMAP Header (all values 0)
*/
static int cp_lkm_qmap_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
struct qmap_hdr* qmh;
int in_len;
int hdr_size;
int pad = 0;
int result = CP_LKM_WRAPPER_RES_DONE;
// don't currently care about the wrapper_state, but this is how we would get it if we did
//cp_lkm_wrapper_state_t wrapper_state = cp_lkm_generic_wrapper_get_state(ctxt, mux_id);
hdr_size = sizeof(struct qmap_hdr);
in_len = skb_in->len;
if(in_len & 3) {
pad = 4 - (in_len & 3);
}
//printk("%s() src: %d, len: %d, mux_id: %d, pad: %d\n",__FUNCTION__,src,in_len,mux_id,pad);
//add space for the initial header at the start, plus pad and ending header at the end
skb_in = cp_lkm_wrapper_skb_make_space(skb_in, hdr_size, pad);
if(!skb_in) {
DEBUG_WARN("%s() couldn't make space", __FUNCTION__);
return CP_LKM_WRAPPER_RES_ERROR;
}
skb_push(skb_in, sizeof(struct qmap_hdr));
//add the header at the front
qmh = (struct qmap_hdr*)skb_in->data;
qmh->pad_bytes = CP_LKM_QMAP_DATA + pad;
qmh->mux_id = mux_id;
qmh->payload_len = cpu_to_be16(in_len+pad);
// CA: determined the empty header is not necessary, but not sure about pad so keeping it.
// add pad (if needed) and empty header at the end.
//memset(skb_tail_pointer(skb_in), 0, sizeof(struct qmap_hdr)+pad);
//skb_put(skb_in, sizeof(struct qmap_hdr)+pad);
memset(skb_tail_pointer(skb_in), 0, pad);
skb_put(skb_in, pad);
*skb_out = skb_in;
return result;
}
static int cp_lkm_qmap_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
int c, pad, len;
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
struct qmap_hdr qmh;
int hdr_size;
struct sk_buff* tmp_skb;
int result = CP_LKM_WRAPPER_RES_DONE;
//cp_lkm_wrapper_state_t wrapper_state;
hdr_size = sizeof(struct qmap_hdr);
*skb_out = NULL;
*dst = CP_LKM_WRAPPER_DST_DATA;
//skb_in is NULL when we returned 'again' previously and so the caller is recalling us. This means there should be
//a queue'd skb for us to process.
if(skb_in == NULL) {
//printk("%s() had a pending\n", __FUNCTION__);
skb_in = skb_dequeue(&cpwc->skb_data_recv_list);
}
if(skb_in == NULL) {
//nothing more to do
//printk("%s() done\n", __FUNCTION__);
goto qmap_recv_done;
}
if(skb_in->len < hdr_size){
//printk("%s() not enough data, len: %d, hdr_size: %d\n", __FUNCTION__, skb_in->len, hdr_size);
result = CP_LKM_WRAPPER_RES_ERROR;
goto qmap_recv_done;
}
//read header
memcpy(&qmh, skb_in->data, sizeof(struct qmap_hdr));
qmh.payload_len = be16_to_cpu(qmh.payload_len);
c = qmh.pad_bytes & 0x8;
pad = qmh.pad_bytes & 0x7;
*mux_id = qmh.mux_id;
len = qmh.payload_len; //payload plus pad (doesn't include hdr)
skb_pull(skb_in, hdr_size);
//printk("%s() c: 0x%x, pad: %d, mux_id: 0x%x, pkt len: %d, skb len: %d\n", __FUNCTION__, c,pad,qmh.mux_id,len,skb_in->len);
// don't currently care about the usb state for processing, but if we did this is how we would get it
//wrapper_state = cp_lkm_generic_wrapper_get_state(ctxt, *mux_id);
if(skb_in->len < len){
//printk("%s() not enough data, pkt len: %d, skb len: %d\n", __FUNCTION__, len, skb_in->len);
result = CP_LKM_WRAPPER_RES_ERROR;
goto qmap_recv_done;
}
//printk("%s() pkt len: %d, skb len: %d\n", __FUNCTION__, len, skb_in->len);
if(skb_in->len == (len + sizeof(struct qmap_hdr))){
//this is an exact fit plus an empty hdr at the end.
//Some modems add it, some don't. Dump the empty if present.
skb_set_tail_pointer(skb_in, len);
skb_in->len -= sizeof(struct qmap_hdr);
}
//if exact fit, send it
if (skb_in->len == len){
//printk("%s(), exact fit\n", __FUNCTION__);
skb_set_tail_pointer(skb_in, skb_in->len-pad); //dump the padding if any
*skb_out = skb_in;
skb_in = NULL; //so we don't free it below
if (c == CP_LKM_QMAP_CTRL) {
//TODO: decode ctrl packets to find pauses and resumes if we decide to support that
// when not using flow control, what do I do here?
*dst = CP_LKM_WRAPPER_DST_UNKNOWN;
}
else if (len == 0) {
//this is the 0 len header at the end. Tell the outside world to dump it.
*dst = CP_LKM_WRAPPER_DST_UNKNOWN;
}
goto qmap_recv_done;
}
//multiple packets in this one. Have to copy them
tmp_skb = skb_clone(skb_in, GFP_ATOMIC);
if (!tmp_skb) {
//printk("%s() couldn't clone skb\n", __FUNCTION__);
result = CP_LKM_WRAPPER_RES_ERROR;
goto qmap_recv_done;
}
tmp_skb->len = len-pad;
skb_set_tail_pointer(tmp_skb, len-pad);
*skb_out = tmp_skb;
//This skb has multiple pkts. We just cloned the first pkt into tmp_skb above. Move past that data and if there
//is any more data left, enqueue it and return 'again' so we can process it.
skb_pull(skb_in, len);
//More data after this one, queue and tell caller to come again sometime
//printk("%s() %d more to do\n", __FUNCTION__, skb_in->len);
skb_queue_tail(&cpwc->skb_data_recv_list, skb_in);
skb_in = NULL;
result = CP_LKM_WRAPPER_RES_AGAIN;
if (c == CP_LKM_QMAP_CTRL) {
//TODO: decode ctrl packets to find pauses and resumes if we decide to support that
// when not using flow control, what do I do here?
*dst = CP_LKM_WRAPPER_DST_UNKNOWN;
}
qmap_recv_done:
if(skb_in) {
dev_kfree_skb_any(skb_in);
}
//if error, clear the out skb if any
if(result == CP_LKM_WRAPPER_RES_ERROR) {
if(*skb_out) {
dev_kfree_skb_any(*skb_out);
*skb_out = NULL;
}
}
//printk("%s() done result: %d, dst: %d, mux_id: %d\n", __FUNCTION__, result, *dst, *mux_id);
return result;
}
//================================ API
//If any of the wrappers have wrapper_info passed in they need to save it in their structures since
//it is freed after this function returns
void *cp_lkm_wrapper_instance_alloc(cp_lkm_wrapper_type_t wrapper, void* wrapper_info, int len)
{
struct cp_lkm_wrapper_context* cpwc = NULL;
DEBUG_TRACE("%s() wrapper:%d", __FUNCTION__, wrapper);
switch (wrapper) {
case CP_LKM_WRAPPER_TYPE_ASIX:
cpwc = kzalloc(sizeof(struct cp_lkm_wrapper_context), GFP_KERNEL);
if(!cpwc) {
goto wrap_alloc_done;
}
cp_lkm_wrapper_common_init(cpwc);
cpwc->wrapper = wrapper;
cpwc->hdr_size = 4; //4 byte asix hdr
cpwc->send = cp_lkm_asix_wrapper_send;
cpwc->recv = cp_lkm_asix_wrapper_recv;
break;
case CP_LKM_WRAPPER_TYPE_ASIX_88179:
cpwc = cp_lkm_asix88179_wrapper_alloc(wrapper, wrapper_info, len);
break;
case CP_LKM_WRAPPER_TYPE_LG:
// not supported
break;
case CP_LKM_WRAPPER_TYPE_DIRECT_IP:
cpwc = kzalloc(sizeof(struct cp_lkm_wrapper_context), GFP_KERNEL);
if(!cpwc) {
goto wrap_alloc_done;
}
cp_lkm_wrapper_common_init(cpwc);
cpwc->wrapper = wrapper;
cpwc->send = cp_lkm_dip_wrapper_send;
cpwc->recv = cp_lkm_dip_wrapper_recv;
cpwc->hdr_size = 6; //6 byte dip hdr
break;
case CP_LKM_WRAPPER_TYPE_MSRNDIS:
cpwc = cp_lkm_msrndis_wrapper_alloc(wrapper, wrapper_info, len);
break;
case CP_LKM_WRAPPER_TYPE_PEGASUS:
cpwc = kzalloc(sizeof(struct cp_lkm_wrapper_context), GFP_KERNEL);
if(!cpwc) {
goto wrap_alloc_done;
}
cp_lkm_wrapper_common_init(cpwc);
cpwc->wrapper = wrapper;
cpwc->send = cp_lkm_pegasus_wrapper_send;
cpwc->recv = cp_lkm_pegasus_wrapper_recv;
cpwc->hdr_size = 2; //2 byte pegasus hdr
break;
case CP_LKM_WRAPPER_TYPE_NCM:
cpwc = cp_lkm_ncm_wrapper_alloc(wrapper, wrapper_info, len);
break;
case CP_LKM_WRAPPER_TYPE_QMAP:
cpwc = cp_lkm_qmap_wrapper_alloc(wrapper, wrapper_info, len);
break;
default:
cpwc = kzalloc(sizeof(struct cp_lkm_wrapper_context), GFP_KERNEL);
if(!cpwc) {
goto wrap_alloc_done;
}
cp_lkm_wrapper_common_init(cpwc);
cpwc->wrapper = wrapper;
cpwc->send = cp_lkm_generic_wrapper_send;
cpwc->recv = cp_lkm_generic_wrapper_recv;
break;
}
wrap_alloc_done:
return cpwc;
}
void cp_lkm_wrapper_instance_free(void* ctxt)
{
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
DEBUG_TRACE("%s()", __FUNCTION__);
switch (cpwc->wrapper) {
case CP_LKM_WRAPPER_TYPE_LG:
// not supported
break;
default:
cp_lkm_wrapper_common_cleanup(cpwc);
kfree(ctxt);
break;
}
}
int cp_lkm_wrapper_hdr_size(void* ctxt)
{
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
return cpwc->hdr_size;
}
void cp_lkm_wrapper_set_state(void* ctxt, int id, cp_lkm_wrapper_state_t wrapper_state)
{
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
int i;
for (i = 0; i < cpwc->num_state_maps; i++) {
if (cpwc->state_maps[i].id == id) {
cpwc->state_maps[i].wrapper_state = wrapper_state;
return;
}
}
//if we get here, this is a new id
if (cpwc->num_state_maps < MAX_STATE_MAPS) {
cpwc->state_maps[cpwc->num_state_maps].wrapper_state = wrapper_state;
cpwc->state_maps[cpwc->num_state_maps].id = id;
cpwc->num_state_maps++;
}
else{
//DEBUG_ASSERT(cpwc->num_state_maps < MAX_STATE_MAPS, "Too many wrapper ids");
printk("%s() too many state maps, id: %d, state: %d\n",__FUNCTION__, id, wrapper_state);
}
}
int cp_lkm_wrapper_send(void* ctxt, int src, int mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
int res;
unsigned long flags;
// DEBUG_ERROR("%s() ctxt:%p", __FUNCTION__, ctxt);
spin_lock_irqsave(&cpwc->lock, flags);
res = cpwc->send(ctxt, src, mux_id, skb_in, skb_out);
spin_unlock_irqrestore(&cpwc->lock, flags);
return res;
}
int cp_lkm_wrapper_recv(void* ctxt, int* dst, int* mux_id, struct sk_buff* skb_in, struct sk_buff** skb_out)
{
struct cp_lkm_wrapper_context* cpwc = (struct cp_lkm_wrapper_context*)ctxt;
int res;
unsigned long flags;
//DEBUG_ERROR("%s() ctxt:%p", __FUNCTION__, ctxt);
*mux_id = 0; //default this since a lot of wrappers don't set it
spin_lock_irqsave(&cpwc->lock, flags);
res = cpwc->recv(ctxt, dst, mux_id, skb_in, skb_out);
spin_unlock_irqrestore(&cpwc->lock, flags);
return res;
}