| /* |
| * 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; |
| } |
| |