| /* |
| * FILE NAME cpmodem_shim.c |
| * |
| * BRIEF MODULE DESCRIPTION |
| * Frankendriver - USB to ethernet, ip or PPP controlled via a block driver. |
| * |
| * Author: CradlePoint Technology, Inc. <source@cradlepoint.com> |
| * Ben Kendall <benk@cradlepoint.com> |
| * Cory Atkin <catkin@cradlepoint.com> |
| * |
| * Copyright 2012, 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 device drivers |
| #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/cdev.h> |
| #include <linux/slab.h> // kmalloc() |
| #include <linux/fs.h> // everything... |
| #include <linux/poll.h> |
| #include <linux/errno.h> // error codes |
| #include <linux/types.h> // size_t |
| #include <linux/proc_fs.h> |
| #include <linux/fcntl.h> |
| #include <linux/skbuff.h> |
| #include <linux/list.h> |
| #include <linux/if_ether.h> |
| #include <linux/if_arp.h> |
| #include <linux/ethtool.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/inetdevice.h> |
| #include <linux/ip.h> |
| #include <net/addrconf.h> |
| #include <linux/tty.h> |
| #include <linux/tty_flip.h> |
| #include <linux/spinlock.h> |
| #include <linux/ktime.h> |
| /* #include <asm/system.h> // cli(), *_flags */ |
| #include <asm/uaccess.h> // copy_from/to_user |
| #include <linux/usb.h> |
| #include <linux/version.h> // LINUX_VERSION_CODE |
| #include <cpmodem_shim.h> |
| #include <cpmodem_wrapper.h> |
| |
| |
| //#define KERNEL_2_6_21 // comment this out for 3.0.29 kernel |
| /*********************************************** logging and debug ************************************************/ |
| |
| #define RUNTIME_DEBUG_TRACE (1 << 0) |
| #define RUNTIME_DEBUG_INFO (1 << 1) |
| #define RUNTIME_DEBUG_WARN (1 << 2) |
| #define RUNTIME_DEBUG_ERROR (1 << 3) |
| #define RUNTIME_LOG 0 |
| #define RUNTIME_ASSERT -1 |
| |
| //#undef RUNTIME_DEBUG |
| //#define RUNTIME_DEBUG ( /*RUNTIME_DEBUG_TRACE |*/ RUNTIME_DEBUG_INFO | RUNTIME_DEBUG_WARN | RUNTIME_DEBUG_ERROR ) |
| |
| |
| static int cp_lkm_log_level = 0; |
| |
| #ifdef RUNTIME_DEBUG |
| static const char *cp_lkm_shim_runtime_debug_level_str[] = { |
| "ASSERT", |
| "TRACE", |
| "INFO", |
| "WARN", |
| "ERROR", |
| }; |
| #else |
| static const char *cp_lkm_shim_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; |
| const char *kernel_lvl_str = NULL; |
| |
| if (level>0) { // level of 0 is LOG and -1 is ASSERT - always output |
| level_index = cp_out_get_level_index(level); |
| |
| #ifdef RUNTIME_DEBUG |
| if (!(RUNTIME_DEBUG & level)) { |
| return; |
| } |
| level_str = cp_lkm_shim_runtime_debug_level_str[level_index]; |
| #else |
| if (!(cp_lkm_log_level & level)) { |
| return; |
| } |
| level_str = cp_lkm_shim_debug_log_level_str[level_index]; |
| #endif |
| } |
| |
| |
| switch(level) { |
| case RUNTIME_DEBUG_TRACE: |
| kernel_lvl_str = KERN_INFO; |
| break; |
| case RUNTIME_DEBUG_INFO: |
| kernel_lvl_str = KERN_INFO; |
| break; |
| case RUNTIME_DEBUG_WARN: |
| kernel_lvl_str = KERN_WARNING; |
| break; |
| case RUNTIME_DEBUG_ERROR: |
| kernel_lvl_str = KERN_ERR; |
| break; |
| case RUNTIME_LOG: |
| kernel_lvl_str = KERN_INFO; |
| break; |
| case RUNTIME_ASSERT: |
| kernel_lvl_str = KERN_ERR; |
| break; |
| default: |
| kernel_lvl_str = KERN_INFO; |
| break; |
| } |
| |
| |
| 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 + 2, GFP_ATOMIC); // +6 for debug type indication, +2 for linux syslog level |
| if (!fmt1) { |
| return; |
| } |
| if (level_str) { |
| if (file) { |
| sprintf(fmt1, "%s%6s %s(%4d):%s\n", kernel_lvl_str, level_str, file_pos, line, fmt); |
| } else { |
| sprintf(fmt1, "%s%6s %s\n", kernel_lvl_str, level_str, fmt); |
| } |
| } else { |
| if (file) { |
| sprintf(fmt1, "%s%s(%4d):%s\n", kernel_lvl_str, file_pos, line, fmt); |
| } else { |
| sprintf(fmt1, "%s%s\n", kernel_lvl_str, fmt); |
| } |
| } |
| vprintk(fmt1, arg); |
| kfree(fmt1); |
| va_end(arg); |
| } |
| |
| #ifdef RUNTIME_DEBUG |
| // assert is always defined if RUNTIME_DEBUG is defined |
| // bad idea to kill things in kernel, so we just print the assert msg and keep going |
| #define DEBUG_ASSERT(a, args...) \ |
| if (!(a)) { \ |
| printk(KERN_ERR "\n!!! CPMODEM_SHIM ASSERT !!!\n"); \ |
| cp_out(RUNTIME_ASSERT, __FILE__, __LINE__, args); \ |
| dump_stack(); \ |
| } |
| #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(RUNTIME_LOG, NULL, 0, args) |
| |
| /*********************************************** general definitions and helper functions *************************/ |
| |
| // Buffer to store data |
| struct cp_lkm_read_msg { |
| struct cp_lkm_msg_hdr hdr; |
| struct sk_buff *skb; |
| struct list_head list; |
| }; |
| |
| struct cp_lkm_common_ctx { |
| u8 open_cnt; |
| |
| // read operation members |
| wait_queue_head_t inq; |
| struct list_head read_list; |
| spinlock_t read_list_lock; |
| bool reading_data; |
| bool q_waiting; |
| // write operation members |
| struct sk_buff *write_skb; |
| |
| int (*open)(struct cp_lkm_common_ctx *ctx); // called at open |
| int (*close)(struct cp_lkm_common_ctx *ctx); // called at close |
| int (*handle_msg)(struct cp_lkm_common_ctx *ctx, struct cp_lkm_msg_hdr *hdr, struct sk_buff *skb); // called at write |
| int (*handle_ioctl)(struct cp_lkm_common_ctx *ctx, int cmd, void *k_argp); // called at ioctl |
| }; |
| |
| |
| int cp_lkm_open(struct inode *inode, struct file *filp); |
| int cp_lkm_release(struct inode *inode, struct file *filp); |
| ssize_t cp_lkm_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); |
| ssize_t cp_lkm_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); |
| #ifdef KERNEL_2_6_21 |
| int cp_lkm_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); |
| #else |
| long cp_lkm_ioctl (struct file *filp, unsigned int cmd, unsigned long arg); |
| #endif |
| unsigned int cp_lkm_poll(struct file *filp, struct poll_table_struct *); |
| |
| static void cp_lkm_common_ctx_init(struct cp_lkm_common_ctx *common); |
| static void cp_lkm_cleanup_msg_list(struct cp_lkm_common_ctx *common); |
| static int cp_lkm_post_message(struct cp_lkm_common_ctx *mgr, struct cp_lkm_msg_hdr* hdr, struct sk_buff *skb); |
| |
| /* Structure that declares the usual file |
| access functions */ |
| struct file_operations cp_lkm_fops = { |
| .owner = THIS_MODULE, |
| .read = cp_lkm_read, |
| .write = cp_lkm_write, |
| #ifdef KERNEL_2_6_21 |
| .ioctl = cp_lkm_ioctl, |
| #else |
| .unlocked_ioctl = cp_lkm_ioctl, |
| #endif |
| .open = cp_lkm_open, |
| .poll = cp_lkm_poll, |
| .release = cp_lkm_release |
| }; |
| |
| static int major; |
| static struct device *cp_lkm_dev[2]; |
| static struct class *cp_lkm_class; |
| |
| #define CP_LKM_USB_MGR_MINOR 0 |
| #define CP_LKM_PM_MGR_MINOR 1 |
| #define CP_LKM_ITER 3000 //CP_LIM_ITER * CP_LKM_TIMEOUT_MS = 30000 or 30 seconds |
| #define CP_LKM_TIMEOUT_MS 10 |
| |
| typedef int (*cp_lkm_data_transfer_t)(void *ctx, struct sk_buff *skb); |
| typedef void (*cp_lkm_data_hdr_size_t)(void *ctx, int wrapper_hdr_size, int *hdr_size, int* hdr_offset); |
| typedef int (*cp_lkm_poll_t)(void *ctx, int budget); |
| typedef void (*cp_lkm_schedule_t)(void *ctx); |
| typedef void (*cp_lkm_complete_t)(void *ctx); |
| typedef int (*cp_lkm_msg_t)(void *ctx); |
| struct cp_lkm_edi { |
| //values provided by usb side, called by pm side |
| cp_lkm_data_transfer_t usb_send; |
| void *usb_send_ctx; |
| |
| //value provided by pm side, called by usb side |
| cp_lkm_msg_t pm_send_pause; //called by usb to pause the network q |
| cp_lkm_msg_t pm_send_resume; //called by usb to resume the network q |
| cp_lkm_data_transfer_t pm_recv; |
| cp_lkm_data_hdr_size_t pm_get_hdr_size; //ask pm how much space it needs for headers |
| void *pm_recv_ctx; |
| |
| void *pm_stats64_ctx; |
| }; |
| |
| static int cp_lkm_pm_usb_link(struct cp_lkm_edi *edi, int pm_unique_id, int link); |
| |
| struct cp_lkm_pm_stats64 { |
| u64 rx_packets; |
| u64 tx_packets; |
| u64 rx_bytes; |
| u64 tx_bytes; |
| u64 rx_errors; |
| u64 tx_errors; |
| u64 rx_dropped; |
| u64 tx_dropped; |
| |
| u64 rx_over_errors; |
| |
| struct u64_stats_sync syncp; |
| }; |
| |
| struct cp_lkm_pm_common { |
| int unique_id; |
| u32 attached; |
| cp_lkm_pm_type_t type; |
| struct net_device *net_dev; |
| struct cp_lkm_edi *edi; |
| struct list_head filter_list; |
| u32 filter_drop_cnt; |
| |
| // keep these in pm context so dual sim hidden unplug/plug do not affect the stats |
| struct cp_lkm_pm_stats64 *pcpu_stats64; |
| |
| int pm_link_count; //token used to prevent xmit and poll from being called if we are linking or unlinking, -1 = unlinking so block xmit and poll, |
| spinlock_t pm_link_lock; //lock to protect getting and releasing the pm_link_count token |
| |
| struct list_head list; |
| }; |
| |
| //static void cp_lkm_pm_update_stats64(struct cp_lkm_pm_stats64 *stats, u64 *field, u64 incr); |
| #define UPDATE_STATS(stats_ctx, field, incr) if (stats_ctx) { \ |
| struct cp_lkm_pm_stats64 *stats = this_cpu_ptr(((struct cp_lkm_pm_common *)stats_ctx)->pcpu_stats64); \ |
| if (stats) { \ |
| u64_stats_update_begin(&stats->syncp); \ |
| stats->field += incr; \ |
| u64_stats_update_end(&stats->syncp); \ |
| } \ |
| } |
| |
| //Keep these commented out for release |
| //static int dbg_memleak_timer_started = 0; |
| //static struct timer_list dbg_memleak_timer; |
| //static spinlock_t dbg_state_lock; |
| //static int dbg_state_init = 0; |
| //static int g_dbg_memalloc_cnt = 0; |
| //static int g_stuck_cnt = 0; |
| //static int g_stuck_chk = 0; |
| //static int g_unlink_cnt = 0; |
| |
| typedef size_t ref_t; |
| typedef void (*memref_final_method_t)(void *buf); |
| struct memref { |
| memref_final_method_t mfree; |
| atomic_t refs; |
| }; |
| |
| |
| void *memref_alloc(size_t size, memref_final_method_t mfree) |
| { |
| struct memref *ptr; |
| |
| ptr = (struct memref *)kmalloc(sizeof(struct memref) + size, GFP_ATOMIC); |
| if (!ptr) { |
| return NULL; |
| } |
| //g_dbg_memalloc_cnt++; |
| ptr->mfree = mfree; |
| atomic_set(&ptr->refs, 1); |
| |
| return (ptr + 1); |
| } |
| |
| void *memref_alloc_and_zero(size_t size, memref_final_method_t mfree) |
| { |
| void *ptr; |
| |
| ptr = memref_alloc(size, mfree); |
| if (!ptr) { |
| return NULL; |
| } |
| |
| memset(ptr, 0x00, size); |
| |
| return ptr; |
| } |
| |
| static void *memref_ref(void *buf) |
| { |
| struct memref *mb; |
| |
| if (!buf) { |
| return NULL; |
| } |
| |
| mb = (struct memref *)(buf) - 1; |
| |
| // if (0 == atomic_read(&mb->refs)) { |
| // DEBUG_INFO("%s() !refs", __FUNCTION__); |
| // return NULL; |
| // } |
| |
| atomic_inc(&mb->refs); |
| |
| return buf; |
| } |
| |
| #if 0 |
| static ref_t memref_cnt(void *buf) |
| { |
| struct memref *mb; |
| |
| if (!buf) { |
| return 0; |
| } |
| |
| mb = (struct memref *)(buf) - 1; |
| return atomic_read(&mb->refs); |
| } |
| #endif |
| |
| static ref_t memref_deref(void *buf) |
| { |
| struct memref *mb; |
| |
| if (!buf) { |
| return 0; |
| } |
| |
| mb = (struct memref *)(buf) - 1; |
| |
| // if (0 == atomic_read(&mb->refs)) { |
| // DEBUG_INFO("%s() !refs", __FUNCTION__); |
| // return NULL; |
| // } |
| |
| if (atomic_dec_and_test(&mb->refs)) { |
| //g_dbg_memalloc_cnt--; |
| if (mb->mfree) { |
| mb->mfree(buf); |
| } |
| kfree(mb); |
| return 0; |
| } |
| |
| return atomic_read(&mb->refs); |
| } |
| |
| /* |
| * Generic function to repeatedly call a function until it either succeeds or the delay and iters |
| * have been exhausted. Optionally it can throw a kernel panic on failure. |
| * |
| * ctxt - the ctxt to pass into do_fun |
| * do_fun - the function to call until it returns success |
| * delay_ms - the amount of time to delay between calls to do_fun on failure |
| * iter - the number of times to call do_fun |
| * die_str - if should panic on failure, then pass in the die_str to display |
| * |
| * if die_str provided, this function will not exit on failure. |
| * else it will exit with the result of the call to do_fun |
| * Note: total wait time is delay_ms * iter |
| */ |
| typedef bool (*do_function_t)(void* ctx1, void* ctx2); |
| bool cp_lkm_do_or_die(void* ctx1, void*ctx2, do_function_t do_fun, u32 delay_ms, u32 iter, const char* die_str) |
| { |
| bool done = false; |
| //set_current_state(TASK_UNINTERRUPTIBLE); |
| while (!done && iter) { |
| iter--; |
| done = do_fun(ctx1,ctx2); |
| if (!done) { |
| msleep(delay_ms); |
| //schedule_timeout(msecs_to_jiffies(delay_ms)); |
| //set_current_state(TASK_UNINTERRUPTIBLE); |
| } |
| } |
| if(!done && die_str) { |
| panic(die_str); |
| //BUG_ON() |
| } |
| //set_current_state(TASK_RUNNING); |
| return done; |
| } |
| |
| /******************************* kernel module USB/Wrapper functionality ********************************* |
| * |
| * The shim has multiple entry points. It can be pumped by hw interrupts, software interrupts, or threads. |
| * The trick to getting the shim to work properly is knowing from which contexts the different functions can be called |
| * and what you can do in that context. |
| * |
| * The biggest concern is to make sure we aren't nulling out a function or instance pointer in one context while another |
| * context is using it. Pointers are changed when linking or unlinking to the protocol manager or when the device unplugs. |
| * For link/unlink or unplug, we need to make sure all other processing has been blocked or stopped. We use a combination of |
| * tokens and spinlocks to achieve this. |
| * |
| * Another complexity is dealing with multi-core processors such as we have in some routers. With multi-core you can have |
| * a hw interrupt, software interrupt or thread running on one core and a hw interrupt, soft interrupt, or thread running on |
| * another at the same time. In addition, the same soft interrupt code can run on both cores at the same time. |
| * With single core, the hw int would block the thread. The shim was orginally designed with a single-core system, so a lot of work |
| * has been put into verifying multi-core works. |
| * |
| * Single core: We can be pumped by: |
| * Hardware interrupt - all interrupts disabled, can't be preempted |
| * Software interrupt - hw interrupts not disabled, can be preempted by hw interrupt |
| * Thread or other process - can be preempted by hw or sw interrupt. |
| * |
| * Multi core: all bets are off. Everything can run at the same time so you have to be very careful with locks and tokens to not corrupt |
| * variables and to not run funtions reentrantly. |
| * |
| * Here are the specific contexts (threads, processes)that pump us: |
| * 1. USB on a hardware interrupt context. This happens on tx and rx done (all interrupts disabled, schedule callbacks and get out fast) |
| * 2. USB on the hub thread. This happens on unplug (can sleep or pause, but be careful because it stops all USB system hub processing) |
| * 3. Kernel workqueue thread (our own callback, can sleep or pause, but be careful, it stops all the kernel workqueue processing) |
| * 4. tasklet or timer soft interrupt context (our own callbacks on sw interrupt, hw interrupts enabled, can't sleep or do pause) |
| * 5. ioctl or device write on a kernel thread (this is cpusb in app space talking to us, runs on a thread, can be prempted in multi-core) |
| * 6. network (send from network side, runs as a software interrupt) |
| * |
| * Which functions are called in which contexts and what they do: |
| * #1 - cp_lkm_usb_xmit_complete - called by usb layer when transmit is done in hw interrupt context |
| * throw transfer in done q, on success, schedule tasklet or NAPI poll (#4) by calling |
| * cp_lkm_usb_done_and_defer_data() for data packets or cp_lkm_usb_done_and_defer_other() for non-data pkts. |
| * On error schedule kevent (#3) by calling cp_lkm_usb_defer_kevent() |
| * cp_lkm_usb_recv_complete - called by usb layer when recv is done in hw interrupt context |
| * throw transfer in done q, schedule tasklet or NAPI poll (#4), on error schedule kevent (#3) |
| * |
| * #2 - cp_lkm_usb_probe - called when the usb hub layer detects a plug, called on hub thread context |
| * cp_lkm_usb_disconnect - called when the usb hub layer detects an unplug, called on hub thread context |
| * schedule mgr_kevent to clean up |
| * |
| * #3 - cp_lkm_usb_kevent - scheduled by tx and rx complete (#1) on USB halt errors or out of memory failure. Is a workqueue thread |
| * clears the halts, sees if memory available. On success, schedules the tasklet or NAPI poll(#4) |
| * |
| * #4 - cp_lkm_usb_process_data_done_tasklet - Scheduled by rx or tx complete (#1). Runs in soft int context. This function is used when we |
| * are using a non-NAPI compliant protocol manager (i.e. PPP). It processes recv'd pkts and sends |
| * them onto the protocol manager, frees all sent skb's and restock more recv urbs to the USB layer. |
| * cp_lkm_usb_process_other_done_tasklet -Same as first one except is it scheduled anytime we recv a pkt that needs to go to the common |
| * modem stack instead of to the network stack (ctrl, status or diagnostics pkt) |
| * |
| * #5 - cp_lkm_usb_handle_ioctl - ioctl mux function called by the kernel when the app ioctl is called |
| * calls the appropriate mux function |
| * cp_lkm_usb_plug_intf - called by ioctl mux to register a device. Register a usb driver to catch |
| * the plug event from the usb stack |
| * cp_lkm_usb_open_intf - called by ioctl mux indicate the data channel is active. This causes us to |
| * mux all data packets to the network stack instead of up to cpusb in app space |
| * cp_lkm_usb_close_intf - called by ioctl mux to indicate the data connection has gone down. |
| * This causes us to mux all packets up to cpusb in app space instead of to network |
| * |
| * cp_lkm_usb_unplug_intf - called by ioctl mux. Releases the interface, deregisters the usb driver, cleans up memory |
| * cp_lkm_usb_handle_msg - called by the device driver write function. This is how cpusb sends us usb packets that |
| * we need to send to usb |
| * #6 - cp_lkm_usb_start_xmit - called by the network interface |
| * sends a transmit to the usb layer |
| */ |
| |
| |
| struct cp_lkm_usb_dev; |
| struct cp_lkm_usb_base_dev; |
| |
| |
| /* we record the state for each of our queued skbs */ |
| enum skb_state { |
| illegal = 0, |
| out_start, // start a data or other transmit |
| out_done, // data or other transmit done |
| in_data_start, // start a recv (either data or other) |
| in_data_done, // recv data done |
| in_data_cleanup, |
| in_other_start, |
| in_other_done, // recv other done |
| in_other_cleanup, |
| ctrl_start, // start a usb ctrl transfer |
| ctrl_done, // usb ctrl transfer finished |
| unlink_start // telling usb to give our urb back |
| }; |
| |
| #define EVENT_TX_HALT 0 |
| #define EVENT_RX_HALT 1 |
| #define EVENT_RX_MEMORY 2 |
| #define EVENT_STS_SPLIT 3 |
| #define EVENT_LINK_RESET 4 |
| |
| //These are standard USB defines |
| #define UE_BULK 0x02 |
| #define UE_INTERRUPT 0x03 |
| |
| #define MAX_INTF_EPS 10 |
| |
| #define CP_LKM_USB_RECV 0x01 |
| #define CP_LKM_USB_LISTEN 0x02 |
| |
| struct cp_lkm_base_ep |
| { |
| struct list_head list; // for inserting in the cpbdev list of base endpoints |
| struct list_head eps; // list of cloned endpoints based off this one |
| struct cp_lkm_usb_base_dev* cpbdev; // pointer back to the cpdev this endpoint belongs to |
| int ep_num; // endpoint number |
| unsigned long err_flags; // errors on the ep (halt, no mem) |
| int con_flags; //connection flags (recv, listen) |
| int q_cnt; //number of urbs down at the lower layer |
| int type; //ep type (interrupt, bulk etc) |
| int max_transfer_size; |
| int pipe; |
| int interval; // interval for interrupt end points |
| }; |
| |
| struct cp_lkm_ep |
| { |
| struct list_head list_bep; // for being inserted into the bep's list of eps |
| struct list_head list_cpdev; // for being inserted into the cpdev's list of eps |
| struct cp_lkm_base_ep* bep; // pointer to this ep's base endpoint |
| struct cp_lkm_usb_dev* cpdev; // pointer back to the cpdev this endpoint belongs to |
| int con_flags; //connection flags (recv, listen) |
| int ep_num; // duplicated from base endpoint for convenience |
| }; |
| |
| /* This struct gets stored in skb->cb which is currently a 48 byte buffer |
| The size of this struct needs to not ever be bigger than 48 |
| */ |
| struct skb_data { |
| //if pointers and ints are 64 bits (8 bytes) then this is 48 bytes currently and |
| //no other variables can be added |
| struct urb *urb; |
| struct cp_lkm_usb_base_dev *cpbdev; |
| struct cp_lkm_base_ep* bep; |
| enum skb_state state; |
| int status; |
| int unique_id; //id of cpdev that sent the tx pkt |
| }; |
| |
| #define MAX_USB_DRVR_NAME 10 |
| #define USB_DRVR_FRMT_STR "cpusb%d" |
| |
| struct cp_lkm_usb_base_dev |
| { |
| struct list_head list; //for inserting in global dev list |
| struct list_head cpdev_list; //list of cpdevs cloned from this base dev |
| struct list_head in_bep_list; // list of base in endpoints |
| struct list_head out_bep_list; // list of base out endpoints |
| int data_in_bep_num; //data in ep number |
| int data_out_bep_num; //data out ep number |
| |
| struct usb_driver* usb_driver; |
| struct usb_device_id* usb_id_table; |
| int vid; |
| int pid; |
| int intf_num; |
| int alt_intf_num; |
| int usb_bus; |
| int usb_addr; |
| int feature_flags; |
| int base_id; //unique id of the first clone to plug |
| cp_lkm_usb_state_t base_state; |
| |
| struct sk_buff_head in_q; //recv skb's are stored here while down at usb waiting to be filled with recv data |
| struct sk_buff_head out_q; //send skb's are stored here while down at usb waiting to be transmitted |
| struct sk_buff_head ctrlq; //ctrl skb's are stored here while down at usb waiting to be filled or transmitted |
| struct sk_buff_head data_tx_done; //tx skb's are stored here while waiting to be freed |
| struct sk_buff_head data_rx_done; //recv and ctrl skb's are stored here while waiting to have recv data processed |
| struct sk_buff_head other_done; //sent skb's are stored here while waiting to be freed |
| |
| u32 data_q_len; // holds count of data pkts (both rx and tx) needing to be processed |
| spinlock_t data_q_lock; // lock to keep data_q_len sync'd |
| spinlock_t processing_state_lock; |
| cp_lkm_usb_process_state_t processing_state; |
| spinlock_t other_state_lock; |
| cp_lkm_usb_process_state_t other_state; |
| bool scheduled; //tasklet scheduled to process the pending |
| |
| struct tasklet_struct other_process_tasklet; |
| struct tasklet_struct data_process_tasklet; |
| |
| int rx_schedule_threshold; |
| int tx_schedule_threshold; |
| int tx_resume_threshold; |
| |
| struct work_struct kevent; |
| char usb_drvr_name[MAX_USB_DRVR_NAME]; |
| void* wrapper_ctxt; |
| int wrapper_hdr_size; |
| int pm_hdr_size; |
| int pm_hdr_offset; |
| |
| struct usb_interface* intf; |
| struct usb_device *udev; |
| |
| int plug_result; |
| bool disconnect_wait; |
| |
| struct timer_list rx_delay; |
| |
| int tx_usb_q_count; |
| bool tx_paused; |
| |
| struct timer_list usb_pause_stuck_timer; |
| int tx_proc_cnt; //how many data tx pkts have we successfully sent |
| int tx_proc_cnt_at_pause; //how many data tx pkts we had sent when we paused |
| |
| #if 0 |
| //debug stuff, comment out |
| //unsigned int dbg_total_stuck_cnt; |
| //unsigned int dbg_total_tx_at_stuck_cnt; |
| //unsigned int dbg_total_tx_proc; |
| #endif |
| }; |
| |
| struct cp_lkm_usb_dev |
| { |
| //init at open |
| struct cp_lkm_usb_base_dev* cpbdev; |
| int unique_id; |
| int pm_id; |
| int clone_num; |
| int mux_id; |
| |
| cp_lkm_usb_state_t state; |
| struct list_head list; //for inserting in base dev list |
| |
| struct cp_lkm_edi* edi; |
| |
| struct list_head in_ep_list; //list of in endpoints on the dev |
| struct list_head out_ep_list; //list of out endpoints on the dev |
| int data_in_ep_num; //data in ep number |
| int data_out_ep_num; //data out ep number |
| |
| //for debug |
| #if 0 |
| struct timer_list dbg_timer; |
| unsigned int dbg_total_rx_irq; |
| unsigned int dbg_total_tx_irq; |
| unsigned int dbg_total_rx_proc; |
| unsigned int dbg_total_d_done; |
| unsigned int dbg_total_o_done; |
| unsigned int dbg_total_pause; |
| unsigned int dbg_total_resume; |
| unsigned int dbg_total_max_work; |
| unsigned int dbg_total_timeout; |
| unsigned int dbg_total_budget; |
| unsigned int dbg_total_o_tasklet; |
| unsigned int dbg_total_d_resched; |
| unsigned int dbg_total_wq_sched; |
| unsigned int dbg_total_napi_sched; |
| unsigned int dbg_total_tasklet_sched; |
| unsigned int dbg_total_d_comp; |
| //unsigned int dbg_total_ic; |
| //unsigned int dbg_total_tc; |
| unsigned int dbg_total_rx_qlen; |
| unsigned int dbg_total_tx_qlen; |
| unsigned int dbg_total_num_hybrid_t; |
| unsigned int dbg_total_num_normal_t; |
| unsigned int dbg_total_num_hybrid; |
| unsigned int dbg_total_num_normal; |
| unsigned int dbg_total_num_d_timers; |
| unsigned int dbg_total_sch_sk; |
| #endif |
| }; |
| |
| struct cp_lkm_usb_ctx |
| { |
| struct cp_lkm_common_ctx common; |
| struct list_head dev_list; |
| spinlock_t lock; //used to protect access to dev_list from different instances. Also used to coordinate thread accesses from usb and cpmodem layers. |
| //when one thread grabs the lock, no other threads can run (soft and hw IRQs can still run). The usb hub unplug handler runs on a thread. |
| //this means if one thread grabs the lock it can be guaranteed the modem can unplug while it is doing its thing. |
| }; |
| |
| //static void cp_lkm_usb_dbg_memleak_timer (unsigned long param); |
| //static void cp_lkm_usb_dbg_timer (unsigned long param); |
| |
| enum { |
| CP_LKM_STUCK_INIT = 0, |
| CP_LKM_STUCK_START, |
| CP_LKM_STUCK_STOP, |
| CP_LKM_STUCK_DEINIT |
| }; |
| static void cp_lkm_usb_stuck_check(struct cp_lkm_usb_base_dev* cpbdev, int action); |
| static void cp_lkm_usb_pause_stuck_timer(struct timer_list *timer); |
| |
| static void cp_lkm_usb_delay_timer (struct timer_list *timer); |
| static void cp_lkm_usb_kevent (struct work_struct *work); |
| static int cp_lkm_usb_open(struct cp_lkm_common_ctx *ctx); |
| static int cp_lkm_usb_close(struct cp_lkm_common_ctx *ctx); |
| static int cp_lkm_usb_handle_ioctl(struct cp_lkm_common_ctx *ctx, int cmd, void *k_argp); |
| static int cp_lkm_usb_handle_msg(struct cp_lkm_common_ctx *ctx, struct cp_lkm_msg_hdr *hdr, struct sk_buff *skb); |
| |
| static int cp_lkm_usb_start_xmit (void *ctx, struct sk_buff *skb); |
| static int cp_lkm_usb_start_xmit_common(void *ctx, struct sk_buff *skb, int src, struct cp_lkm_ep* ep); |
| static void cp_lkm_usb_xmit_complete (struct urb *urb); |
| static int cp_lkm_usb_submit_recv (struct cp_lkm_usb_base_dev* cpbdev, struct urb *urb, gfp_t flags, struct cp_lkm_base_ep* bep, bool data); |
| static void cp_lkm_usb_recv_complete (struct urb *urb); |
| |
| static void cp_lkm_usb_other_recv_process (struct cp_lkm_usb_base_dev* cpbdev, struct sk_buff *skb_in); |
| static void cp_lkm_usb_data_recv_process (struct cp_lkm_usb_base_dev* cpbdev, struct sk_buff *skb); |
| static void cp_lkm_usb_ctrl_process (struct cp_lkm_usb_base_dev* cpbdev, struct sk_buff *skb_in); |
| |
| static int cp_lkm_usb_close_intf(struct cp_lkm_usb_close_intf* ci); |
| static int cp_lkm_usb_unlink_urbs (struct cp_lkm_usb_base_dev *cpbdev, struct sk_buff_head *q, struct cp_lkm_base_ep* bep); |
| |
| static void cp_lkm_usb_process_other_done_tasklet (unsigned long param); |
| static void cp_lkm_usb_process_data_done_tasklet (unsigned long param); |
| static void cp_lkm_usb_rx_data_restock (struct cp_lkm_usb_base_dev* cpdev); |
| static void cp_lkm_usb_rx_other_restock (struct cp_lkm_usb_base_dev* cpbdev); |
| static void cp_lkm_usb_defer_kevent (struct cp_lkm_usb_base_dev* cpbdev, struct cp_lkm_base_ep* bep, int work); |
| static bool cp_lkm_schedule_data_process(struct cp_lkm_usb_base_dev* cpbdev, bool if_data, bool is_resume, bool have_lock); |
| |
| static void cp_lkm_schedule_rx_restock(struct cp_lkm_usb_base_dev* cpbdev, struct cp_lkm_base_ep* bep); |
| static int cp_lkm_usb_start_ctrl_xmit(void *ctx, struct sk_buff *skb_in); |
| static int cp_lkm_usb_have_data(struct cp_lkm_usb_base_dev *cpbdev); |
| |
| static struct cp_lkm_usb_ctx cp_lkm_usb_mgr; |
| |
| // Knobs we can tweak on a processor by processor basis to maximize performance |
| // Dummy values filled in here so we don't get warning on using unitialized variables |
| static int CP_LKM_PM_NAPI_WEIGHT = 0; //budget we register with NAPI (max number of pkts it thinks we will process). |
| static int CP_LKM_USB_NAPI_MAX_WORK = 0; //actual number of pkts we will process (we're not entirely honest with NAPI) |
| static int CP_LKM_USB_MAX_RX_QLEN = 0; //max number of rx data URBs we allow to flow in the shim (we alloc these) |
| static int CP_LKM_USB_MAX_OTHER_QLEN = 0; //max number of rx urbs on non-data endpoints |
| static int CP_LKM_USB_TX_PAUSE_Q_PKTS = 0; //max number of tx data URBs we allow to flow in the shim (alloc'd by network stack, we control this by pausing) |
| static int CP_LKM_USB_TX_RESUME_Q_PKTS = 0; //un-pause network at this number |
| //static int CP_LKM_USB_TX_RESUME_Q_PKTS_HYBRID = 0; //un-pause network at this number when in hybrid mode with pkt counting |
| static int CP_LKM_USB_TX_SCHED_CNT = 0; //How many done tx's we allow to accumulate before scheduling cleanup in normal mode |
| //static int CP_LKM_USB_TX_SCHED_CNT_HYBRID = 0; //How many done tx's we allow to accumulate before scheduling cleanup in hybrid mode with pkt counting |
| static int CP_LKM_USB_RX_SCHED_CNT = 0; //How many done rx's we allow to accumulate before scheduling processing in normal mode |
| //static int CP_LKM_USB_RX_SCHED_CNT_HYBRID = 0; //How many done rx's we allow to accumulate before scheduling processing in hybrid mode with pkt counting |
| static int CP_LKM_USB_RESTOCK_MULTIPLE = 0; //How many rx URBs we should restock as we process them (0 means don't restock as we go, 1 means every one, 2 means 1 out of every 2 etc) |
| //static int CP_LKM_USB_DATA_MAX_PPS = 0; //Packets per second that will cause us to transition from normal to hybrid mode when using pkt counting |
| //static int CP_LKM_USB_DATA_MIN_PPS = 0; //packets per second that will cause us to transition from hybrid back to normal when using pkt counting |
| static int CP_LKM_USB_TASKLET_CNT = 0; //in hybrid mode, schedule tasklet on cnts 0 to this number |
| static int CP_LKM_USB_WORKQUEUE_CNT = 0; //in hybrid mode, schedule workqueue on cnts CP_LKM_USB_TASKLET_CNT to this number, then start cnt over |
| static int CP_LKM_USB_PROCESS_DIVISOR = 0; //times to loop through the process loop, doing pkts/divisor pkts each time. Set to 1 to only process what was there when entering |
| //broadcom EHCI controller has issues we need to work around |
| static int cp_lkm_is_broadcom = 0; |
| |
| #define CP_LKM_USB_PAUSED_CNT 5000 |
| |
| //TODO remove |
| #if 0 |
| static int g_dbg_data_skballoc_cnt = 0; |
| static int g_dbg_other_skballoc_cnt = 0; |
| static int g_dbg_ctrl_skballoc_cnt = 0; |
| static int g_dbg_xmit_skballoc_cnt = 0; |
| static int g_dbg_urballoc_cnt = 0; |
| static int g_dbg_unplug_cnt = 0; |
| static void cp_lkm_usb_urb_cnt(int inc) |
| { |
| unsigned long flags; |
| spin_lock_irqsave(&dbg_state_lock, flags); |
| g_dbg_urballoc_cnt += inc; |
| spin_unlock_irqrestore(&dbg_state_lock, flags); //release lock so interrupts can resume firing |
| } |
| static void cp_lkm_usb_cnts(int state, int inc) |
| { |
| #if 1 |
| unsigned long flags; |
| spin_lock_irqsave(&dbg_state_lock, flags); |
| |
| switch (state) { |
| case in_other_start: |
| case in_other_done: |
| case in_other_cleanup: |
| g_dbg_other_skballoc_cnt+=inc; |
| break; |
| case ctrl_start: |
| case ctrl_done: |
| g_dbg_ctrl_skballoc_cnt+=inc; |
| break; |
| case out_start: |
| case out_done: |
| g_dbg_xmit_skballoc_cnt+=inc; |
| break; |
| case in_data_start: |
| case in_data_done: |
| case in_data_cleanup: |
| g_dbg_data_skballoc_cnt+=inc; |
| break; |
| case unlink_start: |
| g_dbg_unplug_cnt+=inc; |
| break; |
| default: |
| printk("!!clean: unknown skb state: %d\n",state); |
| break; |
| } |
| spin_unlock_irqrestore(&dbg_state_lock, flags); |
| #endif |
| } |
| #endif |
| |
| static struct cp_lkm_usb_dev* cp_lkm_usb_find_muxed_dev(struct cp_lkm_usb_base_dev* cpbdev, int mux_id) |
| { |
| struct list_head *pos; |
| list_for_each(pos, &cpbdev->cpdev_list){ |
| struct cp_lkm_usb_dev* cpdev = list_entry(pos, struct cp_lkm_usb_dev, list); |
| //printk("%s() cpdev: %p, cpdev->mux_id: %d\n", __FUNCTION__, cpdev, cpdev->mux_id); |
| if(cpdev->mux_id == mux_id) { |
| return cpdev; |
| } |
| } |
| return NULL; |
| } |
| |
| static struct cp_lkm_usb_dev* cp_lkm_usb_find_dev(int uniqueid) |
| { |
| struct list_head *bpos; |
| struct list_head *pos; |
| list_for_each(bpos, &cp_lkm_usb_mgr.dev_list){ |
| struct cp_lkm_usb_base_dev* cpbdev = list_entry(bpos, struct cp_lkm_usb_base_dev, list); |
| list_for_each(pos, &cpbdev->cpdev_list){ |
| struct cp_lkm_usb_dev* cpdev = list_entry(pos, struct cp_lkm_usb_dev, list); |
| if(cpdev->unique_id == uniqueid) { |
| return cpdev; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| #define CP_LKM_DEV_MATCH_ALL 1 |
| #define CP_LKM_DEV_MATCH_BUS_ADDR_ONLY 2 |
| |
| // Find base device from its bus, addr and unique id |
| static struct cp_lkm_usb_base_dev* cp_lkm_usb_find_base_dev(int bus, int addr, int unique_id, int match) |
| { |
| struct list_head *pos; |
| struct list_head *bpos; |
| list_for_each(bpos, &cp_lkm_usb_mgr.dev_list){ |
| struct cp_lkm_usb_base_dev* cpbdev = list_entry(bpos, struct cp_lkm_usb_base_dev, list); |
| if(cpbdev->usb_bus == bus && cpbdev->usb_addr == addr) { |
| if (match == CP_LKM_DEV_MATCH_BUS_ADDR_ONLY) { |
| return cpbdev; |
| } |
| if (cpbdev->base_id == unique_id) { |
| //matches the base_id so don't need to look further |
| return cpbdev; |
| } |
| //look to see if matches the unique_id of one of the cpdevs (only hit this case when running clones) |
| list_for_each(pos, &cpbdev->cpdev_list){ |
| struct cp_lkm_usb_dev* cpdev = list_entry(pos, struct cp_lkm_usb_dev, list); |
| if (cpdev->unique_id == unique_id) { |
| return cpbdev; |
| } |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| /* |
| static struct cp_lkm_usb_dev* cp_lkm_usb_get_head_dev(void) |
| { |
| struct list_head *bpos; |
| struct list_head *pos; |
| list_for_each(bpos, &cp_lkm_usb_mgr.dev_list){ |
| struct cp_lkm_usb_base_dev* cpbdev = list_entry(bpos, struct cp_lkm_usb_base_dev, list); |
| list_for_each(pos, &cpbdev->cpdev_list){ |
| struct cp_lkm_usb_dev* cpdev = list_entry(pos, struct cp_lkm_usb_dev, list); |
| return cpdev; |
| } |
| } |
| return NULL; |
| } |
| */ |
| |
| // pause or unpause all cpdevs associated with this cpbdev |
| static void cp_lkm_usb_dev_pause(struct cp_lkm_usb_base_dev* cpbdev, bool pause) |
| { |
| struct list_head *pos; |
| |
| list_for_each(pos, &cpbdev->cpdev_list){ |
| struct cp_lkm_usb_dev* cpdev = list_entry(pos, struct cp_lkm_usb_dev, list); |
| if (pause) { |
| if(cpdev->edi->pm_send_pause) { |
| cpdev->edi->pm_send_pause(cpdev->edi->pm_recv_ctx); |
| //cpdev->dbg_total_pause++; |
| } |
| } |
| else{ |
| if (cpdev->edi->pm_send_resume) { |
| //cpdev->dbg_total_resume++; |
| cpdev->edi->pm_send_resume(cpdev->edi->pm_recv_ctx); |
| } |
| } |
| } |
| cpbdev->tx_paused = pause; |
| } |
| |
| static void cp_lkm_usb_clean_list(struct sk_buff_head* list) |
| { |
| struct sk_buff *skb; |
| struct skb_data *entry; |
| |
| while((skb = skb_dequeue(list)) != NULL){ |
| DEBUG_TRACE("%s() found a straggler", __FUNCTION__); |
| entry = (struct skb_data *) skb->cb; |
| if(entry->urb) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (entry->urb); |
| } |
| //cp_lkm_usb_cnts(entry->state, -1); |
| dev_kfree_skb_any(skb); |
| } |
| } |
| |
| static void cp_lkm_usb_mark_as_dead(struct cp_lkm_usb_dev* cpdev) |
| { |
| cpdev->edi->usb_send_ctx = NULL; |
| if(cpdev->state != CP_LKM_USB_DEAD) { |
| LOG("Device with id:%d unplugged", cpdev->unique_id); |
| } |
| cpdev->state = CP_LKM_USB_DEAD; |
| } |
| |
| static void cp_lkm_usb_mark_base_as_dead(struct cp_lkm_usb_base_dev* cpbdev) |
| { |
| cpbdev->base_state = CP_LKM_USB_DEAD; |
| } |
| |
| static struct cp_lkm_base_ep* cp_lkm_usb_get_bep(struct cp_lkm_usb_base_dev* cpbdev, int ep_num) |
| { |
| struct cp_lkm_base_ep* bep = NULL; |
| struct list_head *entry, *nxt, *head; |
| |
| if(USB_DIR_IN & ep_num) { |
| //printk("%s() search IN list for ep_num: 0x%x\n", __FUNCTION__, ep_num); |
| head = &cpbdev->in_bep_list; |
| } |
| else{ |
| //printk("%s() search OUT list for ep_num: 0x%x\n", __FUNCTION__, ep_num); |
| head = &cpbdev->out_bep_list; |
| } |
| |
| list_for_each_safe(entry, nxt, head) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| if (bep->ep_num == ep_num) { |
| //printk("%s() found ep_num: %d\n", __FUNCTION__, ep_num); |
| return bep; |
| } |
| } |
| //printk("%s() didn't find ep_num: %d\n", __FUNCTION__,ep_num); |
| |
| return NULL; |
| } |
| |
| static struct cp_lkm_ep* cp_lkm_usb_get_ep(struct cp_lkm_usb_dev* cpdev, int ep_num) |
| { |
| struct cp_lkm_ep* ep = NULL; |
| struct list_head *entry, *nxt, *head; |
| |
| if(USB_DIR_IN & ep_num) { |
| //printk("%s() search IN list for ep_num: 0x%x\n", __FUNCTION__, ep_num); |
| head = &cpdev->in_ep_list; |
| } |
| else{ |
| //printk("%s() search OUT list for ep_num: 0x%x\n", __FUNCTION__, ep_num); |
| head = &cpdev->out_ep_list; |
| } |
| |
| list_for_each_safe(entry, nxt, head) { |
| ep = list_entry(entry, struct cp_lkm_ep, list_cpdev); |
| if (ep->ep_num == ep_num) { |
| //printk("%s() found ep_num: %d\n", __FUNCTION__, ep_num); |
| return ep; |
| } |
| } |
| //printk("%s() didn't find ep_num: %d\n", __FUNCTION__,ep_num); |
| |
| return NULL; |
| } |
| |
| static void cp_lkm_usb_bep_finalize(void *arg) |
| { |
| struct cp_lkm_base_ep* bep = (struct cp_lkm_base_ep*)arg; |
| struct list_head *entry, *nxt; |
| struct cp_lkm_ep *ep; |
| |
| //printk("%s() start\n", __FUNCTION__); |
| //todo remove |
| //del_timer_sync(&cpdev->dbg_timer); |
| |
| //printk("%s() - free eps\n",__FUNCTION__); |
| list_for_each_safe(entry, nxt, &bep->eps) { |
| ep = list_entry(entry, struct cp_lkm_ep, list_bep); |
| //printk("%s() - free ep: %p from bep: %p\n",__FUNCTION__,ep,bep); |
| list_del(&ep->list_bep); |
| memref_deref(ep); |
| } |
| |
| } |
| |
| static void cp_lkm_usb_ep_finalize(void *arg) |
| { |
| //struct cp_lkm_ep* ep = (struct cp_lkm_ep*)arg; |
| //printk("%s() - free ep: %p, ep_num: 0x%x\n",__FUNCTION__,arg ,ep->ep_num); |
| } |
| |
| static struct cp_lkm_ep* cp_lkm_usb_create_ep(struct cp_lkm_usb_dev* cpdev, int ep_num) |
| { |
| struct cp_lkm_ep* ep; |
| struct cp_lkm_base_ep* bep; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| |
| DEBUG_ASSERT(cpdev, "cpdev is null"); |
| cpbdev = cpdev->cpbdev; |
| DEBUG_ASSERT(cpbdev, "cpbdev is null"); |
| |
| //see if already exists first |
| ep = cp_lkm_usb_get_ep(cpdev, ep_num); |
| if(ep) { |
| DEBUG_TRACE("%s() ep: %p already exists", __FUNCTION__, ep); |
| //printk("%s() ep: 0x%x already exists\n", __FUNCTION__, ep_num); |
| return ep; |
| } |
| //printk("%s() - create new ep, cpdev: %p, ep_num: 0x%x\n",__FUNCTION__,cpdev, ep_num); |
| |
| //Need to create new ep and possibly a new bep. We will alloc and init everything first and |
| //then if that all works, we will put everything in its proper place (in lists and stuff) |
| ep = memref_alloc_and_zero(sizeof(struct cp_lkm_ep), cp_lkm_usb_ep_finalize); |
| if(!ep) { |
| DEBUG_ERROR("%s() failed to alloc new ep", __FUNCTION__); |
| return NULL; |
| } |
| INIT_LIST_HEAD(&ep->list_bep); |
| INIT_LIST_HEAD(&ep->list_cpdev); |
| ep->ep_num = ep_num; |
| |
| //may need to create a new base ep if this is the first time we've seen this endpoint number and direction |
| //this is always the case for non-cloned interfaces |
| bep = cp_lkm_usb_get_bep(cpbdev, ep_num); |
| if (!bep) { |
| bep = memref_alloc_and_zero(sizeof(struct cp_lkm_base_ep), cp_lkm_usb_bep_finalize); |
| if(!bep) { |
| DEBUG_ERROR("%s() failed to alloc new ep", __FUNCTION__); |
| memref_deref(ep); |
| return NULL; |
| } |
| //printk("%s() - create new bep: %p, cpbdev: %p, ep_num: 0x%x\n",__FUNCTION__,bep, cpbdev, ep_num); |
| bep->ep_num = ep_num; |
| bep->cpbdev = cpbdev; |
| INIT_LIST_HEAD(&bep->list); |
| INIT_LIST_HEAD(&bep->eps); |
| if(USB_DIR_IN & ep_num) { |
| list_add_tail(&bep->list, &cpbdev->in_bep_list); |
| } |
| else{ |
| list_add_tail(&bep->list, &cpbdev->out_bep_list); |
| } |
| } |
| |
| //if we get here, everything alloc'd ok, so can insert in lists and stuf |
| |
| // Each ep will have two memrefs, one from the alloc which is for entry in the cpdev list |
| // and another for entry into the bep list. This way the ep won't be freed until it is removed |
| // from both lists at unplug time |
| ep->cpdev = cpdev; |
| ep->bep = bep; |
| if(USB_DIR_IN & ep_num) { |
| list_add_tail(&ep->list_cpdev, &cpdev->in_ep_list); |
| } |
| else{ |
| list_add_tail(&ep->list_cpdev, &cpdev->out_ep_list); |
| } |
| memref_ref(ep); |
| list_add_tail(&ep->list_bep, &bep->eps); |
| return ep; |
| |
| } |
| |
| // cp_lkm_usb_plug_intf is called by cpusb via the ioctl. It registers a driver for the interface. |
| // This function is then called by the lower usb layer so we can claim that interface. |
| int cp_lkm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev; |
| struct usb_device* udev; |
| struct usb_host_interface* interface; |
| int unique_id; |
| //unsigned long flags; |
| int rc; |
| uintptr_t tmp_uid; |
| |
| usb_get_intf(intf); |
| |
| //printk("%s()\n",__FUNCTION__); |
| |
| udev = interface_to_usbdev (intf); |
| interface = intf->cur_altsetting; |
| |
| unique_id = (int)id->driver_info; |
| tmp_uid = unique_id; |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| |
| // Error scenario to watch for here: |
| // 1. Device unplugs and replugs before the upper app detects the unplug and calls our unplug_intf. In |
| // this case this driver is still registered and will get the new probe (we don't want this, we want the app driver |
| // to get the plug and claim the device orginally). When disconnect happens we set the state to DEAD. If we get |
| // a probe on a dead device, don't take it. |
| cpbdev = cp_lkm_usb_find_base_dev(udev->bus->busnum, udev->devnum, unique_id, CP_LKM_DEV_MATCH_ALL); |
| if(!cpbdev || cpbdev->base_state == CP_LKM_USB_DEAD) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| DEBUG_TRACE("%s() no cpdev or already dead", __FUNCTION__); |
| return -ENXIO; |
| } |
| |
| //make sure it is for our device (match the usb addresses) |
| //printk("%s() id: %d ouraddr:%d, probeaddr:%d, ourintf:%d, probeintf:%d!\n", __FUNCTION__, unique_id, |
| // cpbdev->usb_addr,udev->devnum,cpbdev->intf_num,interface->desc.bInterfaceNumber); |
| if(cpbdev->usb_bus != udev->bus->busnum || cpbdev->usb_addr != udev->devnum || cpbdev->intf_num != interface->desc.bInterfaceNumber) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| DEBUG_TRACE("%s() reject ourbus: %d, probebus: %d, ouraddr:%d, probeaddr:%d, ourintf:%d, probeintf:%d!", __FUNCTION__, |
| cpbdev->usb_bus, udev->bus->busnum, cpbdev->usb_addr,udev->devnum,cpbdev->intf_num,interface->desc.bInterfaceNumber); |
| return -ENXIO; |
| } |
| cpbdev->intf = intf; |
| cpbdev->udev = udev; |
| |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| if(cpbdev->alt_intf_num) { |
| rc = usb_set_interface(udev, cpbdev->intf_num, cpbdev->alt_intf_num); |
| if(rc) { |
| DEBUG_ERROR("%s() set intf failed :%d", __FUNCTION__,rc); |
| cpbdev->plug_result = -1; //only set this on failure, not reject |
| return -1; |
| } |
| } |
| |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| cpbdev->base_state = CP_LKM_USB_CTRL; |
| |
| usb_set_intfdata(intf, (void*)tmp_uid); |
| usb_get_dev (udev); |
| memref_ref(cpbdev); |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| cp_lkm_usb_stuck_check(cpbdev, CP_LKM_STUCK_INIT); |
| |
| //throughput control stuff |
| cpbdev->rx_schedule_threshold = CP_LKM_USB_RX_SCHED_CNT; |
| cpbdev->tx_schedule_threshold = CP_LKM_USB_TX_SCHED_CNT; |
| cpbdev->tx_resume_threshold = CP_LKM_USB_TX_RESUME_Q_PKTS; |
| |
| |
| //todo remove |
| //if (!dbg_memleak_timer_started) { |
| // dbg_memleak_timer_started = 1; |
| // dbg_memleak_timer.function = cp_lkm_usb_dbg_memleak_timer; |
| // dbg_memleak_timer.data = 0; |
| |
| // init_timer(&dbg_memleak_timer); |
| // mod_timer(&dbg_memleak_timer, jiffies + msecs_to_jiffies(20000)); |
| //} |
| //if (dbg_state_init == 0) { |
| // spin_lock_init(&dbg_state_lock); |
| // dbg_state_init = 1; |
| //} |
| |
| |
| |
| DEBUG_TRACE("%s() probe done", __FUNCTION__); |
| return 0; |
| } |
| |
| static bool cp_lkm_usb_shuter_down_do_pm_unlink(void* ctx1, void* ctx2) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev*)ctx1; |
| struct cp_lkm_usb_dev* cpdev; |
| struct list_head *pos; |
| unsigned long flags; |
| //Unlink from the pm and disable the data state machine |
| bool done = false; |
| spin_lock_irqsave(&cpbdev->processing_state_lock, flags); |
| if(cpbdev->processing_state == USB_PROCESS_STATE_IDLE){ |
| cpbdev->processing_state = USB_PROCESS_STATE_PAUSED; //data soft interrupt handlers now won't run |
| |
| spin_lock(&cpbdev->data_q_lock); |
| cpbdev->data_q_len = CP_LKM_USB_PAUSED_CNT; |
| spin_unlock(&cpbdev->data_q_lock); //usb hw interrupts now won't schedule soft interrupt handlers |
| |
| spin_unlock_irqrestore(&cpbdev->processing_state_lock, flags); //release lock so interrupts can resume firing |
| //unlink the pm side for all cpdevs associated with this cpbdev. Once this returns we are guaranteed not to get any new xmit skb's from the pm |
| list_for_each(pos, &cpbdev->cpdev_list){ |
| cpdev = list_entry(pos, struct cp_lkm_usb_dev, list); |
| LOG("Unlink cpdev: %p from pm", cpdev); |
| cp_lkm_pm_usb_link(cpdev->edi, cpdev->pm_id, 0); |
| cpdev->edi->usb_send_ctx = NULL; |
| } |
| |
| spin_lock_irqsave(&cpbdev->processing_state_lock, flags); |
| done = true; |
| } |
| spin_unlock_irqrestore(&cpbdev->processing_state_lock, flags); |
| return done; |
| } |
| |
| static bool cp_lkm_usb_shuter_down_do_other_tasklet(void* ctx1, void* ctx2) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev*)ctx1; |
| unsigned long flags; |
| bool done = false; |
| spin_lock_irqsave(&cpbdev->other_state_lock, flags); |
| if(cpbdev->other_state == USB_PROCESS_STATE_IDLE){ |
| cpbdev->other_state = USB_PROCESS_STATE_PAUSED; |
| done = true; |
| } |
| spin_unlock_irqrestore(&cpbdev->other_state_lock, flags); |
| return done; |
| } |
| |
| static bool cp_lkm_usb_shuter_down_do_empty_queues(void* ctx1, void* ctx2) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev*)ctx1; |
| bool done = false; |
| |
| if (skb_queue_empty(&cpbdev->in_q) && |
| skb_queue_empty(&cpbdev->out_q) && |
| skb_queue_empty(&cpbdev->ctrlq)){ |
| done = true; |
| } |
| return done; |
| } |
| |
| static void cp_lkm_usb_shuter_down(struct cp_lkm_usb_base_dev* cpbdev) |
| { |
| struct list_head *entry, *nxt; |
| struct cp_lkm_base_ep *bep; |
| |
| |
| //printk("%s() done\n", __FUNCTION__); |
| |
| //Unlink from the pm and disable the data state machine |
| LOG("Unlink cpdev from pm"); |
| cp_lkm_do_or_die(cpbdev, NULL, cp_lkm_usb_shuter_down_do_pm_unlink, CP_LKM_TIMEOUT_MS, CP_LKM_ITER, "Failed to unlink pm from cpdev"); |
| |
| //disable the 'other' tasklet |
| LOG("Disable cpdev other tasklet"); |
| cp_lkm_do_or_die(cpbdev, NULL, cp_lkm_usb_shuter_down_do_other_tasklet, CP_LKM_TIMEOUT_MS, CP_LKM_ITER, "Failed to shutdown cpdev other tasklet"); |
| |
| //Once we get here no xmits can happen or any recv or xmit done processing can happen so no new kevents can be scheduled |
| //so we can stop them here |
| //clear all the flags before flushing the kevents so that we won't try to do anything during the kevent callback |
| list_for_each_safe(entry, nxt, &cpbdev->in_bep_list) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| bep->err_flags = 0; |
| bep->con_flags = 0; |
| } |
| list_for_each_safe(entry, nxt, &cpbdev->out_bep_list) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| bep->err_flags = 0; |
| bep->con_flags = 0; |
| } |
| |
| //This forces the kernel to run all scheduled kevents, so any of our pending ones will run. (Note: Make sure |
| //our kevent handlers check to see if we are attached before doing anything so that we don't schedule anything new while |
| //shutting down) |
| LOG("Cancel cpdev kevents"); |
| cancel_work_sync(&cpbdev->kevent); |
| |
| //Make sure all the urbs have been cancelled |
| // ensure there are no more active urbs |
| //set_current_state(TASK_UNINTERRUPTIBLE); |
| //these cause the urbs to be cancelled and the callbacks to be called. The urbs are removed from |
| //the queues in the callbacks. |
| cp_lkm_usb_unlink_urbs (cpbdev, &cpbdev->out_q, NULL); |
| cp_lkm_usb_unlink_urbs (cpbdev, &cpbdev->in_q, NULL); |
| cp_lkm_usb_unlink_urbs (cpbdev, &cpbdev->ctrlq, NULL); |
| |
| LOG("Wait for all cpdev urbs to be returned"); |
| cp_lkm_do_or_die(cpbdev, NULL, cp_lkm_usb_shuter_down_do_empty_queues, CP_LKM_TIMEOUT_MS, CP_LKM_ITER, "Failed to empty cpdev queues"); |
| |
| //shutdown timer and tasklets |
| LOG("Shutdown cpdev timers and tasklets"); |
| del_timer_sync (&cpbdev->rx_delay); |
| cp_lkm_usb_stuck_check(cpbdev, CP_LKM_STUCK_DEINIT); |
| |
| tasklet_kill(&cpbdev->data_process_tasklet); |
| tasklet_kill(&cpbdev->other_process_tasklet); |
| |
| // All outstanding transfers are back, so now we can clean up. |
| cp_lkm_usb_clean_list(&cpbdev->data_tx_done); |
| cp_lkm_usb_clean_list(&cpbdev->data_rx_done); |
| cp_lkm_usb_clean_list(&cpbdev->other_done); |
| |
| //printk("%s() done\n", __FUNCTION__); |
| usb_set_intfdata(cpbdev->intf, NULL); |
| usb_put_intf(cpbdev->intf); |
| cpbdev->intf = NULL; |
| LOG("cpdev unplug done"); |
| |
| return; |
| |
| } |
| |
| // Called when the USB hub detects that our device just unplugged. |
| // Called in a thread context. We do the lower usb cleanup here because there |
| // are some things that have to be done before exiting from disconnect. |
| // We don't clean up the upper layer stuff because the upper layer doesn't yet know |
| // we are unplugged and will continue to send us data. When the upper layer gets the |
| // unplug notify, it will call cp_lkm_usb_unplug_intf. We finish cleaning up in there. |
| void cp_lkm_usb_disconnect(struct usb_interface *intf) |
| { |
| struct cp_lkm_usb_dev* cpdev; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| //unsigned long flags; |
| int unique_id; |
| |
| // We don't want this function to run at the same time as any of the calls from the modem common stack (ioctl and write) |
| // They all grab this lock for the duration of their calls. They also check the state of the device before proceeding. |
| // Once we have the lock, we know none of them are running. Any new calls will block waiting on the lock. |
| // If we then change the state to dead we can release the lock while we do the rest of cleanup. When they get the lock |
| // they will see the state is dead and error out and return immediately. This prevents us from blocking the common modem thread. |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| |
| //If cpdev is not in intf, then this is the close->disconnect path, so do nothing |
| unique_id = (uintptr_t)usb_get_intfdata(intf); |
| |
| //struct usb_device *udev; |
| //printk("%s() start, id: %d\n", __FUNCTION__, unique_id); |
| |
| //see if device already went away, this should be impossible |
| //the unique id is always for the first instance if running clones |
| cpdev = cp_lkm_usb_find_dev(unique_id); |
| if(!cpdev) { |
| //printk("%s() no cpdev, id: %d\n", __FUNCTION__, unique_id); |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| return; |
| } |
| cpbdev = cpdev->cpbdev; |
| cpbdev->disconnect_wait = true; |
| |
| // Mark the device as dead so we won't start anything new. |
| // NOTE: make sure nothing new can be started on the USB side from this point on. |
| // This includes transmits from the network. Transmits from cpusb. |
| // Recv packets, halt clears, ioctls etc |
| cp_lkm_usb_mark_base_as_dead(cpbdev); |
| |
| // Once device is marked dead, we can release the semaphore. This is so write and ioctl from the modem stack |
| // can return quickly with errors instead of blocking while the disconnect completes. |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| cp_lkm_usb_shuter_down(cpbdev); |
| |
| cpbdev->disconnect_wait = false; |
| memref_deref(cpbdev); |
| |
| //printk("%s() done id: %d\n", __FUNCTION__,unique_id); |
| } |
| |
| static void cp_lkm_usb_base_dev_finalize(void *arg) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev*)arg; |
| struct list_head *entry, *nxt; |
| struct cp_lkm_base_ep *bep; |
| //int unique_id = cpbdev->base_id; |
| //printk("%s()\n", __FUNCTION__); |
| |
| //if was added to the list, need to remove it. |
| if(cpbdev->list.next != &cpbdev->list) { |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| list_del(&cpbdev->list); |
| //printk("%s() free cpbdev from global list \n", __FUNCTION__); |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| } |
| |
| //These should already be empty, but just in case |
| //printk("%s() clean lists\n", __FUNCTION__); |
| cp_lkm_usb_clean_list(&cpbdev->in_q); |
| cp_lkm_usb_clean_list(&cpbdev->out_q); |
| cp_lkm_usb_clean_list(&cpbdev->ctrlq); |
| cp_lkm_usb_clean_list(&cpbdev->data_tx_done); |
| cp_lkm_usb_clean_list(&cpbdev->data_rx_done); |
| cp_lkm_usb_clean_list(&cpbdev->other_done); |
| |
| if(cpbdev->wrapper_ctxt) { |
| //printk("%s() free wrapper\n", __FUNCTION__); |
| cp_lkm_wrapper_instance_free(cpbdev->wrapper_ctxt); |
| cpbdev->wrapper_ctxt = NULL; |
| } |
| if(cpbdev->usb_driver) { |
| //printk("%s() free driver\n", __FUNCTION__); |
| kfree(cpbdev->usb_driver); |
| cpbdev->usb_driver = NULL; |
| } |
| if(cpbdev->usb_id_table) { |
| //printk("%s() free id table\n", __FUNCTION__); |
| kfree(cpbdev->usb_id_table); |
| cpbdev->usb_id_table = NULL; |
| } |
| if(cpbdev->udev) { |
| //printk("%s() free udev\n", __FUNCTION__); |
| usb_put_dev (cpbdev->udev); |
| cpbdev->udev = NULL; |
| } |
| |
| //printk("%s() - free eps\n",__FUNCTION__); |
| list_for_each_safe(entry, nxt, &cpbdev->cpdev_list) { |
| struct cp_lkm_usb_dev* cpdev = list_entry(entry, struct cp_lkm_usb_dev, list); |
| //printk("%s() - free cpdev: %p from cpbdev: %p\n",__FUNCTION__, cpdev, cpbdev); |
| list_del(&cpdev->list); |
| memref_deref(cpdev); |
| } |
| list_for_each_safe(entry, nxt, &cpbdev->in_bep_list) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| //printk("%s() - free in bep: %p from cpbdev: %p\n",__FUNCTION__,bep, cpbdev); |
| list_del(&bep->list); |
| memref_deref(bep); |
| } |
| list_for_each_safe(entry, nxt, &cpbdev->out_bep_list) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| //printk("%s() - free out bep: %p from cpbdev: %p\n ",__FUNCTION__,bep, cpbdev); |
| list_del(&bep->list); |
| memref_deref(bep); |
| } |
| //printk("%s() done base_id: %d\n", __FUNCTION__,unique_id); |
| |
| } |
| |
| static void cp_lkm_usb_dev_finalize(void *arg) |
| { |
| struct cp_lkm_usb_dev* cpdev = (struct cp_lkm_usb_dev*)arg; |
| struct list_head *entry, *nxt; |
| struct cp_lkm_ep *ep; |
| |
| //printk("%s() start\n", __FUNCTION__); |
| //todo remove |
| //del_timer_sync(&cpdev->dbg_timer); |
| |
| //printk("%s() - free eps\n",__FUNCTION__); |
| list_for_each_safe(entry, nxt, &cpdev->in_ep_list) { |
| ep = list_entry(entry, struct cp_lkm_ep, list_cpdev); |
| //printk("%s() - free ep: %p, num: %d from cpdev: %p\n",__FUNCTION__,ep, ep->ep_num, cpdev); |
| list_del(&ep->list_cpdev); |
| memref_deref(ep); |
| } |
| list_for_each_safe(entry, nxt, &cpdev->out_ep_list) { |
| ep = list_entry(entry, struct cp_lkm_ep, list_cpdev); |
| //printk("%s() - free ep: %p, num: %d from cpdev: %p\n",__FUNCTION__,ep, ep->ep_num, cpdev); |
| list_del(&ep->list_cpdev); |
| memref_deref(ep); |
| } |
| |
| if(cpdev->edi) { |
| //printk("%s() free edi\n", __FUNCTION__); |
| cpdev->edi->usb_send_ctx = NULL; |
| cpdev->edi->usb_send = NULL; |
| |
| memref_deref(cpdev->edi); |
| cpdev->edi = NULL; |
| } |
| |
| //printk("%s() end \n", __FUNCTION__); |
| } |
| |
| static int cp_lkm_usb_plug_intf(struct cp_lkm_usb_plug_intf* pi) |
| { |
| int retval; |
| struct cp_lkm_usb_dev* cpdev = NULL; |
| struct cp_lkm_usb_base_dev* cpbdev = NULL; |
| bool need_new; |
| bool is_cloneable; |
| |
| //Make sure we aren't going to overflow the skb space reserved for us to use |
| //DEBUG_ASSERT(sizeof(struct skb_data) < sizeof(((struct sk_buff*)0)->cb)); |
| //DEBUG_INFO("%s(), skb_data size: %d, skb_buff cb size: %d",__FUNCTION__,sizeof(struct skb_data),sizeof(((struct sk_buff*)0)->cb)); |
| |
| // We need to alloc a new cpbdev on plug if: |
| // 1. The device is not cloned at this layer (thus each plug has its own cpbdev) |
| // Note: Some devices are cloned at other layers (cpusb_linux.c), so they can be running as clones in the system, but not at this layer. |
| // This is why we can't just look at the clone_num to determine. |
| // 2. It is cloneable and clone_num is 0 (only the first clone gets a new cpbdev, the rest share it) |
| is_cloneable = pi->feature_flags & CP_LKM_FEATURE_CLONE_MUXED_INTF; |
| need_new = !is_cloneable || (is_cloneable && pi->clone_num == 0); |
| |
| //printk("%s() start id:%d vid/pid: 0x%x/0x%x, bus/addr: %d/%d, intf: %d, flags: 0x%x, clone: %d, mux: %d\n", __FUNCTION__, pi->unique_id, pi->vid, pi->pid, pi->bus, pi->addr, pi->intf_num, pi->feature_flags, pi->clone_num, pi->mux_id); |
| |
| if (need_new) { |
| //first instance, so need a new cpbdev |
| cpbdev = memref_alloc_and_zero(sizeof(struct cp_lkm_usb_base_dev), cp_lkm_usb_base_dev_finalize); |
| if(!cpbdev) { |
| //printk("%s() failed to alloc cpbdev\n", __FUNCTION__); |
| goto init_fail; |
| } |
| //printk("%s() id: %d, alloc'd new cpbdev: %p\n", __FUNCTION__, pi->unique_id, cpbdev); |
| cpbdev->base_state = CP_LKM_USB_INIT; |
| cpbdev->vid = pi->vid; |
| cpbdev->pid = pi->pid; |
| cpbdev->intf_num = pi->intf_num; |
| cpbdev->alt_intf_num = pi->alt_intf_num; |
| cpbdev->usb_bus = pi->bus; |
| cpbdev->usb_addr = pi->addr; |
| cpbdev->feature_flags = pi->feature_flags; |
| cpbdev->base_id = pi->unique_id; |
| INIT_LIST_HEAD(&cpbdev->in_bep_list); |
| INIT_LIST_HEAD(&cpbdev->out_bep_list); |
| INIT_LIST_HEAD(&cpbdev->list); |
| INIT_LIST_HEAD(&cpbdev->cpdev_list); |
| cpbdev->data_in_bep_num = pi->ep_in; |
| cpbdev->data_out_bep_num = pi->ep_out; |
| |
| //alloc and register the usb driver |
| cpbdev->usb_driver = kzalloc(sizeof(struct usb_driver), GFP_KERNEL); |
| if(!cpbdev->usb_driver) { |
| //printk("%s() failed to alloc driver\n", __FUNCTION__); |
| goto init_fail; |
| } |
| |
| cpbdev->usb_id_table = kzalloc(sizeof(struct usb_device_id)*2, GFP_KERNEL); |
| if(!cpbdev->usb_id_table) { |
| //printk("%s() failed to alloc table\n", __FUNCTION__); |
| goto init_fail; |
| } |
| |
| cpbdev->usb_id_table[0].idVendor = cpbdev->vid; |
| cpbdev->usb_id_table[0].idProduct = cpbdev->pid; |
| cpbdev->usb_id_table[0].match_flags = USB_DEVICE_ID_MATCH_DEVICE; |
| cpbdev->usb_id_table[0].driver_info = (unsigned long)pi->unique_id; |
| |
| //create unique drvr string |
| sprintf(cpbdev->usb_drvr_name, USB_DRVR_FRMT_STR, pi->unique_id); |
| cpbdev->usb_driver->name = cpbdev->usb_drvr_name; |
| cpbdev->usb_driver->probe = cp_lkm_usb_probe; |
| cpbdev->usb_driver->disconnect = cp_lkm_usb_disconnect; |
| cpbdev->usb_driver->id_table = cpbdev->usb_id_table; |
| |
| |
| skb_queue_head_init (&cpbdev->in_q); |
| skb_queue_head_init (&cpbdev->out_q); |
| skb_queue_head_init (&cpbdev->ctrlq); |
| skb_queue_head_init (&cpbdev->data_tx_done); |
| skb_queue_head_init (&cpbdev->data_rx_done); |
| skb_queue_head_init (&cpbdev->other_done); |
| cpbdev->data_q_len = 0; |
| spin_lock_init(&cpbdev->data_q_lock); |
| spin_lock_init(&cpbdev->processing_state_lock); |
| spin_lock_init(&cpbdev->other_state_lock); |
| cpbdev->processing_state = USB_PROCESS_STATE_IDLE; |
| cpbdev->other_state = USB_PROCESS_STATE_IDLE; |
| INIT_WORK(&cpbdev->kevent, cp_lkm_usb_kevent); |
| timer_setup(&cpbdev->rx_delay, cp_lkm_usb_delay_timer, 0); |
| |
| cpbdev->data_process_tasklet.func = cp_lkm_usb_process_data_done_tasklet; //TODO: modify to take cpbdev |
| cpbdev->data_process_tasklet.data = (unsigned long) cpbdev; |
| |
| cpbdev->other_process_tasklet.func = cp_lkm_usb_process_other_done_tasklet; //TODO: modify to take cpbdev |
| cpbdev->other_process_tasklet.data = (unsigned long) cpbdev; |
| |
| cpbdev->disconnect_wait = false; |
| |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| list_add_tail(&cpbdev->list, &cp_lkm_usb_mgr.dev_list); |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| // When we call register, it calls our probe function with all available matching interfaces. In probe |
| // we save the result of the probe so we can return fail here if it didn't go well |
| //printk("%s() reg drvr for vid:%x, pid:%x, addr:%d, intf:%d\n", __FUNCTION__, pi->vid,pi->pid,pi->addr,pi->intf_num); |
| retval = usb_register(cpbdev->usb_driver); |
| if(retval || cpbdev->plug_result != 0) { |
| //printk("%s() failed to register driver or probe failed retval:%d, plug_result:%d\n", __FUNCTION__, retval, cpbdev->plug_result); |
| goto init_fail; |
| } |
| cpbdev->base_state = CP_LKM_USB_CTRL; |
| DEBUG_TRACE("%s() done", __FUNCTION__); |
| } |
| else{ |
| //clone, should already have a base dev |
| cpbdev = cp_lkm_usb_find_base_dev(pi->bus, pi->addr, pi->unique_id, CP_LKM_DEV_MATCH_BUS_ADDR_ONLY); |
| if(!cpbdev) { |
| //printk("%s() failed to find cpbdev\n", __FUNCTION__); |
| goto init_fail; |
| } |
| //printk("%s() id: %d, already have cpbdev: %p\n", __FUNCTION__, pi->unique_id, cpbdev); |
| } |
| |
| // make sure base dev has all the feature flags of every clone |
| cpbdev->feature_flags |= pi->feature_flags; |
| |
| //printk("%s() id: %d, cpbdev: %p, alloc new cpdev\n", __FUNCTION__, pi->unique_id, cpbdev); |
| cpdev = memref_alloc_and_zero(sizeof(struct cp_lkm_usb_dev), cp_lkm_usb_dev_finalize); |
| if(!cpdev) { |
| //printk("%s() failed to alloc cpdev\n", __FUNCTION__); |
| goto init_fail; |
| } |
| //printk("%s() id: %d, cpdev: %p\n", __FUNCTION__, pi->unique_id, cpdev); |
| |
| INIT_LIST_HEAD(&cpdev->in_ep_list); |
| INIT_LIST_HEAD(&cpdev->out_ep_list); |
| INIT_LIST_HEAD(&cpdev->list); |
| //add to list right away so if anything below fails, it will be cleaned up when cpbdev is cleaned up |
| list_add_tail(&cpdev->list, &cpbdev->cpdev_list); |
| cpdev->cpbdev = cpbdev; |
| cpdev->unique_id = pi->unique_id; |
| //clone and mux are only used with muxed clone interfaces. |
| cpdev->clone_num = (pi->feature_flags & CP_LKM_FEATURE_CLONE_MUXED_INTF) ? pi->clone_num : 0; |
| cpdev->mux_id = (pi->feature_flags & CP_LKM_FEATURE_CLONE_MUXED_INTF) ? pi->mux_id : CP_LKM_WRAPPER_DEFAULT_ID; |
| //printk("%s() unique_id: %d, clone: %d, mux_id: %d\n", __FUNCTION__, pi->unique_id, pi->clone_num, cpdev->mux_id); |
| cpdev->data_in_ep_num = pi->ep_in; |
| cpdev->data_out_ep_num = pi->ep_out; |
| //pre-create the data endpoints so they will be first in the list, since they are most often used |
| cp_lkm_usb_create_ep(cpdev, pi->ep_in); |
| cp_lkm_usb_create_ep(cpdev, pi->ep_out); |
| cpdev->edi = memref_alloc_and_zero(sizeof(struct cp_lkm_edi), NULL); |
| if(!cpdev->edi) { |
| //printk("%s() failed to alloc edi\n", __FUNCTION__); |
| goto init_fail; |
| } |
| cpdev->edi->usb_send = cp_lkm_usb_start_xmit; |
| |
| //for debug, comment out before checkin |
| //cpdev->dbg_timer.function = cp_lkm_usb_dbg_timer; |
| //cpdev->dbg_timer.data = (unsigned long)cpdev; |
| //init_timer(&cpdev->dbg_timer); |
| //mod_timer(&cpdev->dbg_timer, jiffies + msecs_to_jiffies(10000)); |
| |
| //TODO CA: I think this shouldn't be set until open, commenting out for now to see if blows chow in plug fest |
| //cpdev->edi->usb_send_ctx = cpdev; |
| |
| cpdev->state = CP_LKM_USB_CTRL; |
| |
| //printk("%s() done success id: %d\n", __FUNCTION__, pi->unique_id); |
| |
| return 0; |
| |
| init_fail: |
| if(cpbdev) { |
| //the finalizer for cpbdev does the clean up |
| memref_deref(cpbdev); |
| } |
| //returning an error to the modem stack on plug will cause it to hard reset |
| //the modem, thus causing the rest of the driver cleanup to occur |
| //printk("%s() open_intf fail\n", __FUNCTION__); |
| return -1; |
| } |
| |
| static int cp_lkm_usb_set_wrapper(struct cp_lkm_usb_set_wrapper* sw) |
| { //unsigned long flags; |
| struct cp_lkm_usb_dev* cpdev; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| void* wrapper_info = NULL; |
| unsigned long not_copied; |
| int res = 0; |
| //printk("%s() unique_id: %d, clone: %d, mux_id: %d\n", __FUNCTION__, sw->unique_id, sw->clone_num, sw->mux_id); |
| |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| cpdev = cp_lkm_usb_find_dev(sw->unique_id); |
| |
| if(!cpdev) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() no cpdev found for id: %d\n", __FUNCTION__, sw->unique_id); |
| return -1; |
| } |
| cpbdev = cpdev->cpbdev; |
| if(cpbdev->base_state == CP_LKM_USB_DEAD){ |
| //modem is unplugging, upper layer just doesn't know it yet, so act like ok until it finds out |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() set_wrapper fail cpdev:%p, state:%d\n", __FUNCTION__, cpdev, cpdev->state); |
| return 0; |
| } |
| |
| // benk - what if wrapper_info_len is 0??? |
| if(cpbdev->wrapper_ctxt){ |
| //already have a wrapper so free it |
| cp_lkm_wrapper_instance_free(cpbdev->wrapper_ctxt); |
| } |
| |
| if(sw->wrapper_info_len) { |
| wrapper_info = kzalloc(sw->wrapper_info_len, GFP_KERNEL); |
| if(!wrapper_info) { |
| DEBUG_ERROR("%s() couldn't alloc wrapper info", __FUNCTION__); |
| res = -1; |
| goto set_wrapper_done; |
| } |
| } |
| |
| |
| //copy the wrapper info from user to kernel space |
| not_copied = copy_from_user(wrapper_info, sw->wrapper_info, sw->wrapper_info_len); |
| if (not_copied) { |
| DEBUG_ERROR("%s() couldn't copy wrapper info", __FUNCTION__); |
| res = -1; |
| goto set_wrapper_done; |
| } |
| //alloc the wrapper instance. On success it takes ownership of the wrapper_info and is responsible for freeing it |
| DEBUG_INFO("%s() wrapper: %d", __FUNCTION__, sw->wrapper); |
| cpbdev->wrapper_ctxt = cp_lkm_wrapper_instance_alloc(sw->wrapper, wrapper_info, sw->wrapper_info_len); |
| if(!cpbdev->wrapper_ctxt){ |
| DEBUG_ERROR("%s() couldn't alloc wrapper", __FUNCTION__); |
| res = -1; |
| goto set_wrapper_done; |
| } |
| cpbdev->wrapper_hdr_size = cp_lkm_wrapper_hdr_size(cpbdev->wrapper_ctxt); |
| cp_lkm_wrapper_set_state(cpbdev->wrapper_ctxt, cpdev->mux_id, CP_LKM_WRAPPER_CTRL); |
| |
| cpdev->clone_num = sw->clone_num; |
| cpdev->mux_id = sw->mux_id; |
| |
| |
| set_wrapper_done: |
| if(wrapper_info) { |
| kfree(wrapper_info); |
| } |
| |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| return res; |
| |
| } |
| |
| static int cp_lkm_usb_set_mux_id(struct cp_lkm_usb_set_mux_id* smi) |
| { //unsigned long flags; |
| struct cp_lkm_usb_dev* cpdev; |
| //struct cp_lkm_usb_base_dev* cpbdev; |
| int res = 0; |
| |
| //printk("%s()\n", __FUNCTION__); |
| |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| cpdev = cp_lkm_usb_find_dev(smi->unique_id); |
| if(!cpdev) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() failed to find cpdev for id: %d\n", __FUNCTION__, smi->unique_id); |
| return -1; |
| } |
| if(cpdev->cpbdev->base_state == CP_LKM_USB_DEAD){ |
| //modem is unplugging, upper layer just doesn't know it yet, so act like ok until it finds out |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| return 0; |
| } |
| cpdev->mux_id = smi->mux_id; |
| //printk("%s() unique_id: %d, mux_id: %d\n", __FUNCTION__, smi->unique_id, smi->mux_id); |
| |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| return res; |
| |
| } |
| |
| static int cp_lkm_usb_open_intf(struct cp_lkm_usb_open_intf* oi) |
| { |
| //unsigned long flags; |
| struct cp_lkm_usb_dev* cpdev; |
| |
| //printk("%s() u-uid: %d\n", __FUNCTION__,oi->unique_id); |
| |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| cpdev = cp_lkm_usb_find_dev(oi->unique_id); |
| |
| //if state isn't CP_LKM_USB_CTRL, then the interface either did not plug for some reason (i.e. didn't get probe from usb), |
| //or it plugged, but then unplugged before open was called. |
| if(!cpdev || cpdev->cpbdev->base_state != CP_LKM_USB_CTRL) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() open_intf fail cpdev:%p, state:%d\n", __FUNCTION__, cpdev, cpdev?cpdev->state:0xff); |
| return -1; |
| } |
| cpdev->state = CP_LKM_USB_ACTIVE; |
| cpdev->edi->usb_send_ctx = cpdev; //this allows the network side to call me |
| cp_lkm_wrapper_set_state(cpdev->cpbdev->wrapper_ctxt, cpdev->mux_id, CP_LKM_WRAPPER_ACTIVE); |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() done\n", __FUNCTION__); |
| return 0; |
| |
| } |
| |
| static int cp_lkm_usb_close_intf(struct cp_lkm_usb_close_intf* ci) |
| { |
| //unsigned long flags; |
| struct cp_lkm_usb_dev* cpdev; |
| |
| //printk("%s() u-uid: %d\n", __FUNCTION__, ci->unique_id); |
| |
| //down(&cp_lkm_usb_mgr.thread_sem); |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| cpdev = cp_lkm_usb_find_dev(ci->unique_id); |
| |
| if(!cpdev || cpdev->cpbdev->base_state == CP_LKM_USB_DEAD) { |
| //device has already unplugged, or is half-unplugged, so don't allow this action to complete |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //up(&cp_lkm_usb_mgr.thread_sem); |
| return 0; |
| } |
| cpdev->edi->usb_send_ctx = NULL; //disconnect from network side so he won't send me any more data |
| cpdev->state = CP_LKM_USB_CTRL; |
| cp_lkm_wrapper_set_state(cpdev->cpbdev->wrapper_ctxt, cpdev->mux_id, CP_LKM_WRAPPER_CTRL); |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //up(&cp_lkm_usb_mgr.thread_sem); |
| //printk("%s() done\n", __FUNCTION__); |
| |
| return 0; |
| } |
| |
| static bool cp_lkm_usb_unplug_do_disconnect_wait(void* ctx1, void* ctx2) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev*)ctx1; |
| bool done = false; |
| if (cpbdev->disconnect_wait == false){ |
| done = true; |
| } |
| return done; |
| } |
| |
| /* |
| * This function is called when the common modem stack wants to give up the interface. |
| * There are two scenarios: |
| * 1. Modem unplugs which leads to the following flow: |
| * -> cp_lkm_usb_disconnect is called by USB sublayer, it cleans up bottom half of cpdev and waits for common modem stack unplug |
| * -> common modem stack sees unplug event |
| * -> it calls this function to finish the cleanup and deregister the driver |
| * -> we are done |
| * |
| * 2. Common modem stack decides to give up the interface due to one common |
| * modem driver relinquishing the modem and another common modem driver grabbing it. |
| * This leads to the following flow: |
| * -> Common modem stack calls this function. |
| * -> it calls usb_deregister() which will call cp_lkm_usb_disconnect in context |
| * -> cp_lkm_usb_disconnect shuts down and frees the usb interface |
| * -> After usb_deregister() exits we finish and exit. |
| * |
| * Notes: This means the two shutdown functions, this one and cp_lkm_usb_disconnect can be |
| * run in any order, so they must not stomp on each other. For example since |
| * cp_lkm_usb_disconnect frees the interface with the kernel, this function better |
| * not do anything that requires the interface after calling usb_deregister() |
| * |
| * The modem stack is single threaded so this function can never be reentrant |
| */ |
| static int cp_lkm_usb_unplug_intf(struct cp_lkm_usb_unplug_intf* ui) |
| { |
| //find dev in list by unique id |
| struct cp_lkm_usb_dev* cpdev; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| bool shuter_down = true; |
| struct list_head *pos; |
| |
| //printk("%s() start id: %d\n", __FUNCTION__, ui->unique_id); |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| //The device should always exist, but if it doesn't, there is no need to blow up, so exit peacefully |
| cpdev = cp_lkm_usb_find_dev(ui->unique_id); |
| if(!cpdev) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| return -1; |
| } |
| cpbdev = cpdev->cpbdev; |
| |
| cp_lkm_usb_mark_as_dead(cpdev); |
| |
| list_for_each(pos, &cpbdev->cpdev_list){ |
| struct cp_lkm_usb_dev* tmp_cpdev = list_entry(pos, struct cp_lkm_usb_dev, list); |
| if(tmp_cpdev->state != CP_LKM_USB_DEAD) { |
| //don't shut down until all clone devices have unplugged |
| shuter_down = false; |
| break; |
| } |
| } |
| |
| //free semaphore before calling usb_deregister because it causes disconnect to be called for case 2 in the header comments |
| //which will try and grab the semaphore, so we would be deadlocked |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| if (shuter_down) { |
| LOG("Wait for cpdev to finish unplugging"); |
| cp_lkm_do_or_die(cpbdev, NULL, cp_lkm_usb_unplug_do_disconnect_wait,CP_LKM_TIMEOUT_MS,CP_LKM_ITER,"cpdev failed to finish disconnecting"); |
| |
| //printk("%s() usb_deregister\n",__FUNCTION__); |
| usb_deregister(cpbdev->usb_driver); |
| |
| /* clean up */ |
| memref_deref(cpbdev); |
| |
| } |
| /* IMPORTANT: don't do anything other than deref after call to deregister*/ |
| |
| LOG("cpdev done unplugging"); |
| |
| return 0; |
| } |
| |
| /* |
| * Handle endpoint action requests from modem stack. |
| * |
| * Important things to know: |
| * In normal mode: |
| * 1. There will be 1 cpdev per cpbdev, and 1 ep per bep. |
| * 2. Every different ep can either be listened on or recv'd on, but never both at the same time |
| * |
| * In clone mode: |
| * 1. There will be n cpdevs per cpbdev, and n eps ber bep (depending on number of clones). |
| * 2. Every different ep can either be listened on or recv'd on, but never both at the same time. |
| * 3. All cloned data eps can be listened on at the same time (data header allows us to mux data between all the data eps, data endpoints don't use recv). |
| * 4. With all other cloned eps of the same type (AT, CNS, QMI), only one clone can be listened on or recv'd on at a time. |
| * This is because there are not headers on these channels to let us know where to mux the data to. Fortunately, the |
| * modem stack enforces this, so we don't have to enforce it here, but we can use it to know how to route cloned packets |
| * coming in on non-data channel endpoints |
| */ |
| static int cp_lkm_usb_ep_action(struct cp_lkm_usb_ep_action* ea) |
| { |
| struct cp_lkm_ep* ep; |
| struct cp_lkm_base_ep* bep = NULL; |
| struct cp_lkm_usb_dev* cpdev; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| //unsigned long flags; |
| int pump_recv = 0; |
| |
| //printk("%s() - action: %d, ep_num: 0x%x, id: %d\n",__FUNCTION__,ea->action, ea->ep_num, ea->unique_id); |
| |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| //There should always be a device, and it should always be plugged |
| cpdev = cp_lkm_usb_find_dev(ea->unique_id); |
| if(!cpdev) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() no device found for unique id: %d\n", __FUNCTION__, ea->unique_id); |
| return -1; |
| } |
| |
| cpbdev = cpdev->cpbdev; |
| if(cpbdev->base_state == CP_LKM_USB_INIT) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() no probe yet, unique_id: %d, action: %d\n", __FUNCTION__,ea->unique_id,ea->action); |
| return -1; |
| } |
| if(cpbdev->base_state == CP_LKM_USB_DEAD) { |
| // The device can unplug down here before cpusb knows about it so it can continue to send us stuff. |
| // The modem will unplug soon so just act like we did it and return ok. I didn't want to |
| // return an error because that might cause cpusb unnecessary heartburn. |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() cpdev already dead, shouldn't be doing this: id: %d, action: %d cpbdev: %p, cpdev: %p\n", __FUNCTION__,ea->unique_id,ea->action,cpbdev,cpdev); |
| return 0; |
| } |
| DEBUG_ASSERT(cpbdev, "cpbdev is null"); |
| //create the ep if it doesn't already exist |
| if(ea->action == EP_ACTION_CREATE) { |
| cp_lkm_usb_create_ep(cpdev, ea->ep_num); |
| } |
| |
| if (ea->action == EP_ACTION_FLUSH_CONTROL) { |
| ep = NULL; |
| } else { |
| ep = cp_lkm_usb_get_ep(cpdev, ea->ep_num); |
| if(!ep) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() failed to find ep: 0x%x for action: %d\n", __FUNCTION__, ea->ep_num, ea->action); |
| return -1; |
| } |
| bep = ep->bep; |
| DEBUG_ASSERT(bep,"base ep is null"); |
| } |
| |
| |
| //if (ep && ea->action != EP_ACTION_RECV) { |
| // printk("%s() - action: %d, ep_num: 0x%x, bep: %p, ep: %p, cpbdev: %p, cpdev: %p, id: %d\n",__FUNCTION__,ea->action, ea->ep_num, bep, ep, bep->cpbdev, ep->cpdev,ea->unique_id); |
| //} |
| |
| //printk("ea->action: %d, ep_num: %d\n", ea->action, ea->ep_num); |
| switch(ea->action) { |
| case EP_ACTION_CREATE: |
| //printk("%s() - action: %d, ep_num: 0x%x, bep: %p, ep: %p, cpbdev: %p, cpdev: %p, id: %d\n",__FUNCTION__,ea->action, ea->ep_num, bep, ep, bep->cpbdev, ep->cpdev,ea->unique_id); |
| //initialize endpoint fields |
| bep->type = ea->ep_type; |
| bep->max_transfer_size = ea->max_transfer_size; |
| bep->interval = ea->interval; |
| |
| DEBUG_ASSERT(cpbdev->udev,"udev is null"); |
| if(bep->ep_num & USB_DIR_IN) { //in |
| if(bep->type == UE_BULK) { |
| bep->pipe = usb_rcvbulkpipe(cpbdev->udev,bep->ep_num); |
| } |
| else{ //interrupt |
| bep->pipe = usb_rcvintpipe(cpbdev->udev, bep->ep_num); |
| } |
| } |
| else{ //out |
| if(bep->type == UE_BULK) { |
| bep->pipe = usb_sndbulkpipe(cpbdev->udev,bep->ep_num); |
| } |
| else{ //interrupt |
| bep->pipe = usb_sndintpipe(cpbdev->udev, bep->ep_num); |
| } |
| } |
| DEBUG_TRACE("%s() create action:%d, ep:0x%x, type:%d, pipe:0x%x", __FUNCTION__, ea->action, ea->ep_num, ea->ep_type, bep->pipe); |
| break; |
| |
| case EP_ACTION_LISTEN: |
| DEBUG_TRACE("%s() listen action:%d, ep:0x%x, type:%d, pipe:0x%x", __FUNCTION__, ea->action, ea->ep_num, ea->ep_type, bep->pipe); |
| ep->con_flags |= CP_LKM_USB_LISTEN; |
| //listen on any endpoint starts listen on base |
| bep->con_flags |= CP_LKM_USB_LISTEN; |
| pump_recv = 1; |
| break; |
| |
| case EP_ACTION_LISTEN_STOP: |
| { |
| bool listen_done = true; |
| struct list_head *entry, *nxt; |
| struct cp_lkm_ep *tmp_ep; |
| |
| DEBUG_TRACE("%s() listen stop action:%d, ep:0x%x, type:%d, pipe:0x%x", __FUNCTION__, ea->action, ea->ep_num, ea->ep_type, bep->pipe); |
| |
| // the ep is done listening |
| ep->con_flags &= ~CP_LKM_USB_LISTEN; |
| |
| //now see if all eps on this bep are done listening |
| list_for_each_safe(entry, nxt, &bep->eps) { |
| tmp_ep = list_entry(entry, struct cp_lkm_ep, list_bep); |
| if(tmp_ep->con_flags & CP_LKM_USB_LISTEN) { |
| //if any of the eps on the bep still listening, then still listen on the bep |
| listen_done = false; |
| break; |
| } |
| } |
| if(listen_done) { |
| bep->con_flags &= ~CP_LKM_USB_LISTEN; |
| //If RX_HALT bit set then there is an error on this endpoint and the kevent will be scheduled to fix the error. As part of the fix |
| //he will unlink the urbs. Bad things can happen if we call cp_lkm_usb_unlink_urbs here at same time the kevent handler is calling it |
| if(!test_bit (EVENT_RX_HALT, &bep->err_flags)){ |
| //TODO CORY: is it ok to call unlink while holding the global lock?? Can I set a flag and run the tasklet to do the work instead?? |
| cp_lkm_usb_unlink_urbs(cpbdev, &cpbdev->in_q, bep); |
| } |
| } |
| } |
| break; |
| |
| case EP_ACTION_RECV: |
| DEBUG_TRACE("%s() recv action:%d, ep:0x%x, type:%d, pipe:0x%x", __FUNCTION__, ea->action, ea->ep_num, ea->ep_type, bep->pipe); |
| // can only have one pending recv on a given ep |
| ep->con_flags |= CP_LKM_USB_RECV; |
| bep->con_flags |= CP_LKM_USB_RECV; |
| pump_recv = 1; |
| break; |
| |
| case EP_ACTION_FLUSH_CONTROL: |
| //printk("%s() flush control action:%d\n", __FUNCTION__, ea->action); |
| //TODO CORY: is it ok to call unlink while holding the global lock?? Can I set a flag and run the tasklet to do the work instead?? |
| //We don't schedule kevents to clear endpoint halts since they are self recovering so we don't need to test the halt bits on the ctrl channel |
| cp_lkm_usb_unlink_urbs(cpbdev, &cpbdev->ctrlq, NULL); |
| break; |
| |
| case EP_ACTION_SET_MAX_TX_SIZE: |
| //printk("%s() set max tx size to %d on ep: 0x%x\n",__FUNCTION__,ea->max_transfer_size, ea->ep_num); |
| bep->max_transfer_size = ea->max_transfer_size; |
| break; |
| |
| default: |
| break; |
| } |
| |
| |
| if(pump_recv) { |
| cp_lkm_schedule_rx_restock(cpbdev, bep); |
| } |
| |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| return 0; |
| } |
| |
| static bool cp_lkm_usb_do_pm_link(void* ctx1, void* ctx2) |
| { |
| struct cp_lkm_usb_dev* cpdev = (struct cp_lkm_usb_dev*)ctx1; |
| struct cp_lkm_usb_base_dev* cpbdev = cpdev->cpbdev; |
| struct cp_lkm_usb_pm_link* upl = (struct cp_lkm_usb_pm_link*)ctx2; |
| unsigned long flags; |
| bool done = false; |
| int rc; |
| |
| //printk("%s() usb id: %d, pm id: %d, link: %d\n", __FUNCTION__, upl->usb_unique_id, upl->pm_unique_id ,upl->link); |
| |
| // We are getting ready to either link or unlink the usb to the protocol manager. This means we will be changing |
| // function pointers that are used by the data processing state machine and by the code that schedules the data |
| // processing machine. |
| // |
| // We need to shut both of those down before doing the linking. |
| // 1: We shut the machine down by setting the state to USB_PROCESS_STATE_PAUSED. |
| // 2: We shut down the scheduling by putting the data_q_len to CP_LKM_USB_PAUSED_CNT so the hw interrupts won't schedule a process |
| spin_lock_irqsave(&cpbdev->processing_state_lock, flags); |
| if(cpbdev->processing_state == USB_PROCESS_STATE_IDLE){ |
| cpbdev->processing_state = USB_PROCESS_STATE_PAUSED; //pauses the data processing soft irq handler |
| |
| spin_lock(&cpbdev->data_q_lock); |
| cpbdev->data_q_len = CP_LKM_USB_PAUSED_CNT; //stops the hw irq handlers from trying to schedule the soft irq handler |
| spin_unlock(&cpbdev->data_q_lock); |
| |
| if(upl->link) { |
| cpdev->edi->usb_send_ctx = cpdev; |
| } |
| |
| //release lock while calling pm since we don't know how long they may take. We have already set the processing_state to |
| //paused so the soft interrupt routines won't try to do anything so we are safe. |
| spin_unlock_irqrestore(&cpbdev->processing_state_lock, flags); |
| |
| rc = cp_lkm_pm_usb_link(cpdev->edi, upl->pm_unique_id, upl->link); |
| DEBUG_ASSERT(rc == 0, "Failed to link usb and pm"); |
| |
| spin_lock_irqsave(&cpbdev->processing_state_lock, flags); |
| if(upl->link) { |
| if (cpdev->edi->pm_get_hdr_size && cpdev->edi->pm_recv_ctx) { |
| cpdev->edi->pm_get_hdr_size(cpdev->edi->pm_recv_ctx, cpbdev->wrapper_hdr_size, &cpbdev->pm_hdr_size, &cpbdev->pm_hdr_offset); |
| } |
| } |
| else{ |
| cpdev->edi->usb_send_ctx = NULL; |
| } |
| |
| cpdev->pm_id = upl->pm_unique_id; |
| |
| spin_lock(&cpbdev->data_q_lock); |
| //set things back up properly before re-enabling the soft irq and hardware handlers |
| cpbdev->data_q_len = cpbdev->data_rx_done.qlen + cpbdev->data_tx_done.qlen; //this must be set before calling schedule_data_process |
| spin_unlock(&cpbdev->data_q_lock); |
| |
| cpbdev->processing_state = USB_PROCESS_STATE_IDLE; |
| done = true; |
| } |
| spin_unlock_irqrestore(&cpbdev->processing_state_lock, flags); |
| |
| return done; |
| } |
| |
| static int cp_lkm_usb_pm_link(struct cp_lkm_usb_pm_link* upl) |
| { |
| struct cp_lkm_usb_dev* cpdev; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| //There should always be a device, and it should always be plugged |
| cpdev = cp_lkm_usb_find_dev(upl->usb_unique_id); |
| |
| //printk("%s() cpdev: %p, u-uid: %d, pm-uid: %d, up: %d\n", __FUNCTION__, cpdev, upl->usb_unique_id, upl->pm_unique_id, upl->link); |
| |
| if(!cpdev || cpdev->cpbdev->base_state == CP_LKM_USB_INIT) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() no device or no probe yet\n", __FUNCTION__); |
| return -1; |
| } |
| cpbdev = cpdev->cpbdev; |
| // The device can unplug down here before cpusb knows about it so it can continue to send us stuff. |
| // The modem will unplug soon so just act like we did it and return ok. I didn't want to |
| // return an error because that might cause cpusb unnecessary heartburn. |
| if(cpbdev->base_state == CP_LKM_USB_DEAD) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //printk("%s() device already unplugged\n", __FUNCTION__); |
| return 0; |
| } |
| |
| //printk("%s() usb id: %d, pm id: %d, link: %d\n", __FUNCTION__, upl->usb_unique_id, upl->pm_unique_id ,upl->link); |
| // We are getting ready to either link or unlink the usb to the protocol manager. This means we will be changing |
| // function pointers that are used by the data processing state machine and by the code that schedules the data |
| // processing machine. |
| // |
| // We need to shut both of those down before doing the linking. |
| // 1: We shut the machine down by setting the state to USB_processing_state_PAUSED. |
| // 2: We shut down the scheduling by putting the data_q_len to CP_LKM_USB_PAUSED_CNT so the hw interrupts won't schedule a process |
| cp_lkm_do_or_die(cpdev, upl, cp_lkm_usb_do_pm_link, CP_LKM_TIMEOUT_MS, CP_LKM_ITER, "cpdev failed to link with pm"); |
| |
| //printk("%s() done\n", __FUNCTION__); |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| //force a resume |
| cp_lkm_schedule_data_process(cpbdev, false, true, false); |
| return 0; |
| } |
| |
| static int cp_lkm_usb_is_alive_intf(struct cp_lkm_usb_is_alive_intf *alivei) |
| { |
| //find dev in list by unique id |
| struct cp_lkm_usb_dev *cpdev; |
| int alive; |
| |
| //printk("%s() start\n", __FUNCTION__); |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| //The device should always exist, but if it doesn't, there is no need to blow up, so exit peacefully |
| cpdev = cp_lkm_usb_find_dev(alivei->unique_id); |
| |
| if(!cpdev) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| return -1; |
| } |
| |
| alive = (cpdev->state == CP_LKM_USB_DEAD) ? -1 : 0; |
| //free semaphore before calling usb_deregister because it causes disconnect to be called for case 2 in the header comments |
| //which will try and grab the semaphore, so we would be deadlocked |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| return alive; |
| } |
| static bool cp_lkm_usb_is_attached(struct cp_lkm_usb_dev* cpdev) |
| { |
| return (cpdev->state == CP_LKM_USB_ACTIVE || cpdev->state == CP_LKM_USB_CTRL); |
| } |
| |
| static bool cp_lkm_usb_is_base_attached(struct cp_lkm_usb_base_dev* cpbdev) |
| { |
| //base has three possible states: INIT, CTRL, DEAD (it never goes to ACTIVE, only the cpdev's do that) |
| return cpbdev->base_state == CP_LKM_USB_CTRL; |
| } |
| |
| |
| // |
| // Input: |
| // if_data: set to true if caller only wants to schedule if there is data pending |
| // is_reschedule: set to true if the caller is the scheduled handler to see if it should be rescheduled |
| // have_lock: true if the caller already has the lock |
| // |
| // returns: |
| // true if scheduled new processing |
| // false if didn't schedule. |
| // |
| // Note: returns false if it was currently scheduled |
| static bool cp_lkm_schedule_data_process(struct cp_lkm_usb_base_dev* cpbdev, bool if_data, bool is_reschedule, bool have_lock) |
| { |
| unsigned long flags; |
| bool res = false; |
| |
| if (!have_lock) { |
| spin_lock_irqsave(&cpbdev->data_q_lock, flags); |
| } |
| |
| //never schedule processing when we are paused |
| if (cpbdev->data_q_len == CP_LKM_USB_PAUSED_CNT) { |
| goto schedule_done; |
| } |
| |
| if (is_reschedule) { |
| cpbdev->scheduled = false; |
| } |
| |
| if (cpbdev->scheduled == true) { |
| goto schedule_done; |
| } |
| |
| if (if_data) { |
| if(!cp_lkm_usb_have_data(cpbdev)){ |
| goto schedule_done; |
| } |
| } |
| |
| cpbdev->scheduled = true; |
| res = true; |
| |
| //cpdev->dbg_total_tasklet_sched++; |
| tasklet_schedule(&cpbdev->data_process_tasklet); |
| |
| schedule_done: |
| if (!have_lock) { |
| spin_unlock_irqrestore(&cpbdev->data_q_lock, flags); |
| } |
| return res; |
| } |
| |
| static void cp_lkm_schedule_rx_restock(struct cp_lkm_usb_base_dev* cpbdev, struct cp_lkm_base_ep* bep) |
| { |
| if(bep == NULL) { |
| cp_lkm_schedule_data_process(cpbdev,false,false,false); |
| tasklet_schedule(&cpbdev->other_process_tasklet); |
| } |
| else if(bep->ep_num == cpbdev->data_in_bep_num) { |
| //printk("start data ep listen\n"); |
| cp_lkm_schedule_data_process(cpbdev,false,false,false); |
| } |
| else{ |
| tasklet_schedule(&cpbdev->other_process_tasklet); |
| } |
| } |
| |
| #define DATA_SRC_TX 0 |
| #define DATA_SRC_RX 1 |
| #define DATA_SRC_OTHER 2 |
| static void cp_lkm_usb_done_and_defer_data(struct cp_lkm_usb_base_dev* cpbdev, struct sk_buff *skb, int src) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cpbdev->data_q_lock, flags); |
| if(src == DATA_SRC_TX) { |
| __skb_queue_tail(&cpbdev->data_tx_done, skb); |
| } |
| else{ |
| __skb_queue_tail(&cpbdev->data_rx_done, skb); |
| } |
| if(cpbdev->data_q_len != CP_LKM_USB_PAUSED_CNT) { |
| cpbdev->data_q_len++; |
| cp_lkm_schedule_data_process(cpbdev,true,false,true); |
| } |
| spin_unlock_irqrestore(&cpbdev->data_q_lock, flags); |
| |
| } |
| |
| //for non data endpoint pkts |
| static void cp_lkm_usb_done_and_defer_other(struct cp_lkm_usb_base_dev* cpbdev, struct sk_buff *skb) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cpbdev->other_done.lock, flags); |
| __skb_queue_tail(&cpbdev->other_done, skb); |
| //only rearm the softirq if the list was empty |
| if(cpbdev->other_done.qlen == 1) { |
| tasklet_schedule(&cpbdev->other_process_tasklet); |
| } |
| spin_unlock_irqrestore(&cpbdev->other_done.lock, flags); |
| } |
| |
| static void cp_lkm_usb_process_other_done_tasklet (unsigned long param) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev *)param; |
| struct sk_buff *skb; |
| struct skb_data *entry; |
| bool timed_out = false; |
| unsigned long time_limit = jiffies + 2; |
| bool can_restock = true; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cpbdev->other_state_lock, flags); |
| if(cpbdev->other_state != USB_PROCESS_STATE_IDLE){ |
| spin_unlock_irqrestore(&cpbdev->other_state_lock, flags); |
| return; |
| } |
| cpbdev->other_state = USB_PROCESS_STATE_ACTIVE; |
| spin_unlock_irqrestore(&cpbdev->other_state_lock, flags); |
| |
| if (timer_pending(&cpbdev->rx_delay) || !cp_lkm_usb_is_base_attached(cpbdev)) { |
| //printk("%s(), cpbdev %p delaying or no longer attached, base_state: %d\n", __FUNCTION__,cpbdev,cpbdev->base_state); |
| can_restock = false; |
| } |
| //cpdev->dbg_total_o_done++; |
| |
| while(!timed_out) { |
| skb = skb_dequeue(&cpbdev->other_done); |
| if(skb == NULL) { |
| break; |
| } |
| entry = (struct skb_data *) skb->cb; |
| |
| //printk("%s(), other data cpbdev: %p, bep: %p, num: 0x%x\n",__FUNCTION__,cpbdev,entry->bep,(entry->bep?entry->bep->ep_num:0)); |
| |
| //cp_lkm_usb_cnts(entry->state,-1); |
| switch (entry->state) { |
| case in_other_done: |
| if(entry->urb) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (entry->urb); |
| } |
| cp_lkm_usb_other_recv_process(cpbdev, skb); |
| break; |
| case ctrl_done: |
| if(entry->urb) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (entry->urb); |
| } |
| cp_lkm_usb_ctrl_process(cpbdev, skb); |
| break; |
| case out_done: |
| case in_other_cleanup: |
| if(entry->urb) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (entry->urb); |
| } |
| dev_kfree_skb_any(skb); |
| break; |
| case unlink_start: |
| default: |
| //printk("!!other: unknown skb state: %d\n",entry->state); |
| break; |
| } |
| |
| if(time_after_eq(jiffies, time_limit)) { |
| //ran out of time, process this one and then bail |
| timed_out = true; |
| } |
| } |
| |
| if(can_restock) { |
| cp_lkm_usb_rx_other_restock(cpbdev); |
| } |
| |
| if(timed_out) { |
| tasklet_schedule(&cpbdev->other_process_tasklet); |
| } |
| |
| spin_lock_irqsave(&cpbdev->other_state_lock, flags); |
| cpbdev->other_state = USB_PROCESS_STATE_IDLE; |
| spin_unlock_irqrestore(&cpbdev->other_state_lock, flags); |
| |
| return ; |
| } |
| |
| // Timer callback. This runs in soft interrupt context. |
| // |
| // The call to restock can blow chow (actually when it calls cp_lkm_schedule_data_process) |
| // if an unlink or unplug happens while we are still in the call. |
| // |
| // Unlink or plug can happen during this call on multi core platforms with kernel preemption enabled. |
| // This timer is scheduled if we ran into some unexpected USB error and want |
| // to give the USB endpoint some time before trying to reschedule recv urbs on it. |
| // |
| // The whole purpose of this function is to pump the system if it is otherwise idle. If |
| // it isn't idle, we can count on those processes to call cp_lkm_schedule_rx_restock when done. |
| static void cp_lkm_usb_delay_timer (struct timer_list *timer) |
| { |
| unsigned long flags; |
| |
| struct cp_lkm_usb_base_dev* cpbdev = from_timer(cpbdev,timer,rx_delay); |
| |
| spin_lock_irqsave(&cpbdev->processing_state_lock, flags); |
| if(cpbdev->processing_state == USB_PROCESS_STATE_IDLE){ |
| cp_lkm_schedule_rx_restock(cpbdev,NULL); |
| } |
| spin_unlock_irqrestore(&cpbdev->processing_state_lock, flags); |
| } |
| |
| #if 0 |
| static void cp_lkm_usb_dbg_memleak_timer (unsigned long param) |
| { |
| printk("+=+=+=+=+=!!!!mem: %d, urb: %d, skb: data: %d, other: %d, xmit: %d, ctrl: %d, unplug:%d, stck_cnt: %d, stck_chk: %d, unlink: %d\n",g_dbg_memalloc_cnt,g_dbg_urballoc_cnt,g_dbg_data_skballoc_cnt,g_dbg_other_skballoc_cnt,g_dbg_xmit_skballoc_cnt,g_dbg_ctrl_skballoc_cnt,g_dbg_unplug_cnt,g_stuck_cnt,g_stuck_chk,g_unlink_cnt); |
| mod_timer(&dbg_memleak_timer, jiffies + msecs_to_jiffies(5000)); |
| } |
| #endif |
| |
| |
| /* |
| * We pause the transmit if there are too many urbs down at the usb layer. |
| * The Broadcom processor's USB block sometimes gets stuck meaning we will never |
| * unpause. This function is used to detect if we are paused because of a stuck and |
| * try to recover it. |
| */ |
| static void cp_lkm_usb_stuck_check(struct cp_lkm_usb_base_dev* cpbdev, int action) |
| { |
| //only broadcom has the stuck problem |
| if (cp_lkm_is_broadcom == 0) { |
| //printk("Not BRCM!!!!\n"); |
| return; |
| } |
| |
| //TODO: it seems like this might work fine with clones. I don't think it hurts to be inited, |
| // started or stopped multiple times?? |
| //g_stuck_chk++; |
| switch(action) { |
| case CP_LKM_STUCK_INIT: |
| timer_setup(&cpbdev->usb_pause_stuck_timer, cp_lkm_usb_pause_stuck_timer, 0); |
| break; |
| case CP_LKM_STUCK_START: |
| mod_timer(&cpbdev->usb_pause_stuck_timer, jiffies + msecs_to_jiffies(3000)); |
| cpbdev->tx_proc_cnt_at_pause = cpbdev->tx_proc_cnt; |
| break; |
| case CP_LKM_STUCK_STOP: |
| case CP_LKM_STUCK_DEINIT: |
| del_timer_sync(&cpbdev->usb_pause_stuck_timer); |
| break; |
| } |
| } |
| |
| // Broadcom has a problem in the EHCI controller where if it gets a NAK on an out packet |
| // it occassionally doesn't update the status of the URB and retry it. This results in the endpoint getting stuck. |
| // If we detect that it is stuck (if the tx has been paused for more than 3 seconds) then we cancel the |
| // struck urb and this gets things going again. The cancelled urb results in a dropped packet which is undesirable, |
| // but preferrable to being stuck. |
| static void cp_lkm_usb_pause_stuck_timer (struct timer_list *timer) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = from_timer(cpbdev,timer,usb_pause_stuck_timer); |
| struct skb_data *entry; |
| struct sk_buff *skb; |
| struct urb *urb = NULL; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cpbdev->out_q.lock, flags); |
| if (cpbdev->tx_paused) { |
| // cancel stuck urb? |
| skb = skb_peek(&cpbdev->out_q); |
| if (skb) { |
| entry = (struct skb_data *) skb->cb; |
| if (entry) { |
| if(cpbdev->tx_proc_cnt_at_pause == cpbdev->tx_proc_cnt){ |
| //printk("\n!!!!!!Canceling stuck URB, cnt at stuck: %d, cnt at unstick: %d!!!!!!!!!!!!!!!!!!!!!!!!!\n", cpbdev->tx_proc_cnt_at_pause, cpbdev->tx_proc_cnt); |
| urb = entry->urb; |
| usb_get_urb(urb); |
| } |
| //else{ |
| //some pkts were transmitted successfully while waiting, though not enough to unpause us. |
| //this means the tx is not stuck, so don't need to cancel anything |
| //printk("\n!!!!!!Restarting stuck URB timer, cnt at stuck: %d, cnt at unstick: %d!!!!!!!!!!!!!!!!!!!!!!!!!\n",cpbdev->tx_proc_cnt_at_pause, cpbdev->tx_proc_cnt); |
| //} |
| // restart just in case this doesn't unpause tx |
| cp_lkm_usb_stuck_check(cpbdev, CP_LKM_STUCK_START); |
| //g_stuck_cnt++; |
| } |
| } |
| } |
| spin_unlock_irqrestore(&cpbdev->out_q.lock, flags); |
| if (urb) { |
| //printk("\n!!!!!!Canceling stuck URB!!!!!!!!!!\n"); |
| //cpbdev->dbg_total_stuck_cnt++; |
| usb_unlink_urb (urb); |
| usb_put_urb(urb); |
| } |
| } |
| |
| #if 0 |
| static void cp_lkm_usb_dbg_timer (unsigned long param) |
| { |
| struct cp_lkm_usb_dev* cpdev = (struct cp_lkm_usb_dev *)param; |
| struct cp_lkm_usb_base_dev* cpbdev = cpdev->cpbdev; |
| printk("!!!!cpdev: %p, clone: %d, id: 0x%x, q_cnt: %d, p: %d, stuck_cnt: %d, tx done: %d, ip_copies: %d!!!!!!!\n",cpdev, cpdev->clone_num,cpdev->mux_id,cpbdev->tx_usb_q_count,cpbdev->tx_paused, cpbdev->dbg_total_stuck_cnt, cpbdev->tx_proc_cnt,num_ip_copies); |
| |
| //printk("!!!!Stuck urb count: %d, total_pause: %d, cpdev: %p, is_brcm: %d!!!!!!!\n",cpdev->dbg_total_stuck_cnt,cpdev->dbg_total_pause,cpdev,cp_lkm_is_broadcom); |
| //printk("!!!!!!!!!!!\n"); |
| #if 0 |
| int txa; |
| int rxa; |
| int drql; |
| int dtql; |
| //int ab; |
| int tx,rx; |
| int pkt_avg; |
| //int epqc, in_q; |
| |
| cpdev->dbg_total_rx_qlen += cpdev->data_rx_done.qlen; |
| cpdev->dbg_total_tx_qlen += cpdev->data_tx_done.qlen; |
| |
| //ab = cpdev->dbg_total_budget/(cpdev->dbg_total_d_done+1); |
| txa = cpdev->dbg_total_tx_proc/(cpdev->dbg_total_d_done+1); |
| rxa = cpdev->dbg_total_rx_proc/(cpdev->dbg_total_d_done+1); |
| drql = cpdev->dbg_total_rx_qlen/(cpdev->dbg_total_d_done+1); |
| dtql = cpdev->dbg_total_tx_qlen/(cpdev->dbg_total_d_done+1); |
| //epqc = cpdev->in_eps[CP_LKM_DATA_INDEX].q_cnt; |
| //in_q = cpdev->in_q.qlen; |
| tx = cpdev->dbg_total_tx_irq; |
| rx = cpdev->dbg_total_rx_irq; |
| pkt_avg = (tx+rx)/5; |
| printk("tot: %d, tx: %d, rx: %d, pa: %d, dones: %d, p: %d\n", tx+rx, tx, rx, pkt_avg, cpdev->dbg_total_d_done, cpdev->dbg_total_pause); |
| printk("resch: %d, d_c: %d, sch_n: %d, sch_t: %d, sch_wq: %d, sch_sk: %d, ds: %d\n", cpdev->dbg_total_d_resched, cpdev->dbg_total_d_comp, cpdev->dbg_total_napi_sched,cpdev->dbg_total_tasklet_sched, cpdev->dbg_total_wq_sched,cpdev->dbg_total_sch_sk, cpdev->data_state); |
| printk("txa: %d, rxa: %d, to: %d, HZ:%d \n", txa , rxa, cpdev->dbg_total_timeout, HZ); |
| printk("nrm_t: %d, blk_t: %d, nrm: %d, blk: %d, ntmrs: %d \n", cpdev->dbg_total_num_normal_t,cpdev->dbg_total_num_hybrid_t,cpdev->dbg_total_num_normal,cpdev->dbg_total_num_hybrid, cpdev->dbg_total_num_d_timers); |
| printk("psd: %d, tuqc: %d, schd: %d, dql: %d, rql: %d, tql: %d, toq: %d\n",cpdev->tx_paused,cpdev->tx_usb_q_count,cpdev->scheduled,cpdev->data_q_len,cpdev->data_rx_done.qlen,cpdev->data_tx_done.qlen,cpdev->out_q.qlen); |
| printk("txirq: %d, txprc: %d\n",cpdev->dbg_total_tx_irq, cpdev->dbg_total_tx_proc); |
| |
| //printk("ipqc: %d, in_q: %d\n", epqc, in_q); |
| //printk("d0: %p,d1: %p,d2: %p,d3: %p,d4: %p\n", devs[0],devs[1],devs[2],devs[3],devs[4]); |
| cpdev->dbg_total_d_done = cpdev->dbg_total_d_resched = cpdev->dbg_total_d_comp = 0; |
| cpdev->dbg_total_pause = cpdev->dbg_total_max_work = cpdev->dbg_total_budget = 0; |
| cpdev->dbg_total_tx_irq = cpdev->dbg_total_rx_irq = 0; |
| cpdev->dbg_total_tx_proc = cpdev->dbg_total_rx_proc = 0; |
| cpdev->dbg_total_rx_qlen = cpdev->dbg_total_tx_qlen = 0; |
| cpdev->dbg_total_napi_sched=cpdev->dbg_total_tasklet_sched=cpdev->dbg_total_wq_sched=0; |
| cpdev->dbg_total_num_normal_t=cpdev->dbg_total_num_hybrid_t=cpdev->dbg_total_num_normal=cpdev->dbg_total_num_hybrid=cpdev->dbg_total_num_d_timers = 0; |
| #endif |
| |
| mod_timer(&cpdev->dbg_timer, jiffies + msecs_to_jiffies(5000)); |
| |
| } |
| #endif |
| |
| |
| //Caller must have the data_q_lock before calling |
| static int cp_lkm_usb_have_data(struct cp_lkm_usb_base_dev *cpbdev) |
| { |
| //return the amount of work to be done if it exceeds the threshold, else return 0 |
| if(cpbdev->data_rx_done.qlen >= cpbdev->rx_schedule_threshold || cpbdev->data_tx_done.qlen >= cpbdev->tx_schedule_threshold){ |
| return cpbdev->data_rx_done.qlen + cpbdev->data_tx_done.qlen; |
| } |
| return 0; |
| } |
| |
| |
| #if 1 |
| static int cp_lkm_usb_process_data_done(struct cp_lkm_usb_base_dev *cpbdev, int budget) |
| { |
| struct sk_buff *skb; |
| struct skb_data *entry; |
| struct cp_lkm_usb_dev* cpdev __attribute__((unused)); |
| unsigned long time_limit = jiffies + 3; |
| int retval; |
| int restock = 0; |
| unsigned long flags; |
| int rx_work_done = 0; |
| int tx_work_done = 0; |
| int work_done = 0; |
| int can_restock = 1; |
| int i; |
| int loop; |
| int num_proc; |
| int actual_budget; |
| int num_rx; |
| int num_tx; |
| struct sk_buff_head done_q; |
| bool paused; |
| |
| skb_queue_head_init (&done_q); |
| |
| //cpdev->dbg_total_d_done++; |
| //cpdev->dbg_total_budget += budget; |
| //cpdev->dbg_total_rx_qlen += cpdev->data_rx_done.qlen; |
| //cpdev->dbg_total_tx_qlen += cpdev->data_tx_done.qlen; |
| |
| // if the delay timer is running, we aren't supposed to send any more recv urbs to the usb layer. |
| // if the device has detached, we need to finish processing done pkts, but don't resubmit any new urbs |
| if (timer_pending(&cpbdev->rx_delay) || !cp_lkm_usb_is_base_attached(cpbdev)) { |
| //printk("%s(), cpdev delaying or no longer attached\n", __FUNCTION__); |
| can_restock = 0; |
| } |
| |
| paused = cpbdev->tx_paused; |
| |
| actual_budget = CP_LKM_USB_NAPI_MAX_WORK; |
| for(loop=0;loop<CP_LKM_USB_PROCESS_DIVISOR;loop++) { |
| if(time_after_eq(jiffies, time_limit)) { |
| //ran out of time, process this one and then bail |
| work_done = budget; |
| //cpdev->dbg_total_timeout++; |
| break; |
| } |
| //keep restocking the q until we max out the budget or timeout or runout |
| if(rx_work_done >= actual_budget || (paused && tx_work_done >= actual_budget)) { |
| work_done = budget; |
| break; |
| } |
| spin_lock_irqsave(&cpbdev->data_q_lock, flags); |
| num_rx = cpbdev->data_rx_done.qlen; |
| num_tx = cpbdev->data_tx_done.qlen; |
| num_proc = max(num_rx,num_tx); |
| num_proc = min(num_proc,actual_budget/CP_LKM_USB_PROCESS_DIVISOR); //grab 1/divisor of remaining budget each time |
| // Note: A unit of work for the shim is either a lone tx, a lone rx or a combo of a rx and a tx. |
| // Here we calculate how much work to do on this poll. If there was work left over from last time |
| // finish processing it. |
| for(i = 0; i < num_proc; i++) { |
| skb = __skb_dequeue (&cpbdev->data_rx_done); |
| if(skb){ |
| cpbdev->data_q_len--; |
| __skb_queue_tail(&done_q, skb); |
| } |
| skb = __skb_dequeue (&cpbdev->data_tx_done); |
| if(skb){ |
| cpbdev->data_q_len--; |
| __skb_queue_tail(&done_q, skb); |
| } |
| } |
| spin_unlock_irqrestore(&cpbdev->data_q_lock, flags); |
| |
| //nothing in the q, we are done |
| if(done_q.qlen == 0) { |
| break; |
| } |
| |
| while((skb = __skb_dequeue(&done_q))){ |
| entry = (struct skb_data *) skb->cb; |
| //cp_lkm_usb_cnts(entry->state,-1); |
| switch (entry->state) { |
| case in_data_done: |
| //cpdev->dbg_total_rx_proc++; |
| entry->bep->q_cnt--; |
| restock++; |
| rx_work_done++; |
| work_done++; |
| if(can_restock && restock == CP_LKM_USB_RESTOCK_MULTIPLE) { |
| restock = 0; |
| |
| retval = cp_lkm_usb_submit_recv (cpbdev, entry->urb, GFP_ATOMIC, entry->bep, true); |
| if (retval < 0) { |
| //printk("%s(), can't resubmit\n", __FUNCTION__); |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (entry->urb); |
| can_restock = 0; |
| } |
| } |
| else{ |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (entry->urb); |
| } |
| cp_lkm_usb_data_recv_process(cpbdev, skb); |
| break; |
| case out_done: |
| work_done++; |
| tx_work_done++; |
| //fall through on purpose |
| fallthrough; |
| case in_data_cleanup: |
| if(entry->urb) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (entry->urb); |
| } |
| dev_kfree_skb_any(skb); |
| break; |
| |
| case unlink_start: |
| default: |
| //printk("!!data: unknown skb state: %d\n",entry->state); |
| break; |
| } |
| } |
| } |
| |
| //restock recv urbs to usb layer if we processed any |
| if(can_restock) { |
| cp_lkm_usb_rx_data_restock(cpbdev); |
| } |
| |
| //see if we need to resume the tx side |
| if(tx_work_done) { |
| spin_lock_irqsave (&cpbdev->out_q.lock, flags); |
| cpbdev->tx_proc_cnt += tx_work_done; |
| |
| if(tx_work_done > cpbdev->tx_usb_q_count) { |
| cpbdev->tx_usb_q_count = 0; |
| } |
| else{ |
| cpbdev->tx_usb_q_count -= tx_work_done; |
| } |
| if(cpbdev->tx_usb_q_count <= cpbdev->tx_resume_threshold) { |
| if(cpbdev->tx_paused){ |
| //unpause all cpdevs |
| cp_lkm_usb_dev_pause(cpbdev, false); |
| // cancel usb_pause_stuck_timer |
| cp_lkm_usb_stuck_check(cpbdev, CP_LKM_STUCK_STOP); |
| } |
| |
| } |
| spin_unlock_irqrestore (&cpbdev->out_q.lock, flags); |
| } |
| |
| //if(work_done > cpdev->dbg_total_max_work){ |
| // cpdev->dbg_total_max_work = work_done; |
| //} |
| |
| //can't return greater than the passed in budget |
| if(work_done > budget) { |
| work_done = budget; |
| } |
| |
| return work_done; |
| //return 1; |
| } |
| #endif |
| |
| static int cp_lkm_usb_common_process_data_done(struct cp_lkm_usb_base_dev* cpbdev, int budget) |
| { |
| unsigned long flags; |
| int work_done = -1; |
| bool rescheduled; |
| bool ran_data_done = false; |
| if(NULL == cpbdev) { |
| //printk("%s() !!!!!!!!!!!!!!!!no ctxt\n", __FUNCTION__); |
| return work_done; |
| } |
| |
| spin_lock_irqsave(&cpbdev->processing_state_lock, flags); |
| if(cpbdev->processing_state == USB_PROCESS_STATE_IDLE){ |
| cpbdev->processing_state = USB_PROCESS_STATE_ACTIVE; |
| spin_unlock_irqrestore(&cpbdev->processing_state_lock, flags); |
| work_done = cp_lkm_usb_process_data_done(cpbdev, budget); |
| spin_lock_irqsave(&cpbdev->processing_state_lock, flags); |
| ran_data_done = true; |
| cpbdev->processing_state = USB_PROCESS_STATE_IDLE; |
| } |
| spin_unlock_irqrestore(&cpbdev->processing_state_lock, flags); |
| if (ran_data_done) { |
| rescheduled = cp_lkm_schedule_data_process(cpbdev,true,true,false); |
| if (rescheduled) { |
| work_done = budget; |
| //cpdev->dbg_total_d_resched++; |
| } |
| else if(work_done){ |
| work_done--; |
| //cpdev->dbg_total_d_comp++; |
| } |
| } |
| else{ |
| //cpdev->dbg_total_sch_sk++; |
| } |
| return work_done; |
| } |
| |
| |
| static void cp_lkm_usb_process_data_done_tasklet (unsigned long param) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev *)param; |
| |
| cp_lkm_usb_common_process_data_done(cpbdev, CP_LKM_PM_NAPI_WEIGHT); |
| } |
| |
| |
| static void cp_lkm_usb_rx_data_restock (struct cp_lkm_usb_base_dev* cpbdev) |
| { |
| //struct cp_lkm_usb_dev* cpdev = (struct cp_lkm_usb_dev *)param; |
| //int cur_token; |
| struct urb *urb; |
| //int ep_index; |
| int q_len; |
| struct cp_lkm_base_ep* bep; |
| int retval; |
| int q_cnt; |
| |
| // timer_pending means we had an error and are waiting for a recovery period before submitting any more rx urbs |
| if (timer_pending(&cpbdev->rx_delay)) { |
| return; |
| } |
| |
| // restock the recv queues on any ep's that are listening |
| bep = cp_lkm_usb_get_bep(cpbdev, cpbdev->data_in_bep_num); |
| if(!(bep->con_flags & CP_LKM_USB_LISTEN) && !(bep->con_flags & CP_LKM_USB_RECV)) { |
| return; |
| } |
| if(test_bit (EVENT_RX_HALT, &bep->err_flags)){ |
| return; |
| } |
| |
| if(bep->con_flags & CP_LKM_USB_RECV) { |
| //only post 1 for recv's |
| q_len = 1; |
| } |
| else{ |
| //its a listen |
| q_len = CP_LKM_USB_MAX_RX_QLEN; |
| } |
| |
| // Try to q up to q_len recv buffs with usb. We may not be able to get to that amount if |
| // there is a problem with usb, so only try up to q_len times to insert them. |
| retval = 0; |
| q_cnt = bep->q_cnt; |
| |
| while(q_cnt < q_len) { |
| urb = usb_alloc_urb (0, GFP_ATOMIC); |
| if (!urb) { |
| if (q_cnt == 0) { |
| cp_lkm_usb_defer_kevent (cpbdev, bep, EVENT_RX_MEMORY); |
| } |
| break; |
| } |
| //cp_lkm_usb_urb_cnt(1); |
| retval = cp_lkm_usb_submit_recv (cpbdev, urb, GFP_ATOMIC, bep, true); |
| if (retval < 0) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (urb); |
| break; |
| } |
| q_cnt++; |
| } |
| } |
| |
| static void cp_lkm_usb_rx_other_restock (struct cp_lkm_usb_base_dev* cpbdev) |
| { |
| struct urb *urb; |
| int q_len; |
| struct cp_lkm_base_ep* bep; |
| int retval; |
| int q_cnt; |
| struct list_head *entry, *nxt; |
| |
| // timer_pending means we had an error and are waiting for a recovery period before submitting any more rx urbs |
| if (timer_pending(&cpbdev->rx_delay)) { |
| return; |
| } |
| |
| // restock the recv queues on any ep's that are listening |
| list_for_each_safe(entry, nxt, &cpbdev->in_bep_list) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| if(!(bep->con_flags & CP_LKM_USB_LISTEN) && !(bep->con_flags & CP_LKM_USB_RECV)) { |
| continue; |
| } |
| if(test_bit (EVENT_RX_HALT, &bep->err_flags)){ |
| continue; |
| } |
| if(bep->ep_num == cpbdev->data_in_bep_num) { |
| continue; |
| } |
| |
| if(bep->con_flags & CP_LKM_USB_RECV) { |
| //only post 1 for recv's |
| q_len = 1; |
| } |
| else{ |
| //its a listen |
| q_len = CP_LKM_USB_MAX_OTHER_QLEN; |
| } |
| |
| // Try to q up to q_len recv buffs with usb. We may not be able to get to that amount if |
| // there is a problem with usb, so only try up to q_len times to insert them. |
| retval = 0; |
| q_cnt = bep->q_cnt; |
| |
| while(q_cnt < q_len) { |
| urb = usb_alloc_urb (0, GFP_ATOMIC); |
| if (!urb) { |
| if (q_cnt == 0) { |
| cp_lkm_usb_defer_kevent (cpbdev, bep, EVENT_RX_MEMORY); |
| } |
| break; |
| } |
| //cp_lkm_usb_urb_cnt(1); |
| retval = cp_lkm_usb_submit_recv (cpbdev, urb, GFP_ATOMIC, bep, false); |
| if (retval < 0) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (urb); |
| break; |
| } |
| q_cnt++; |
| } |
| } |
| } |
| |
| //unlink all urbs with the given ep, or all if ep is NULL |
| static int cp_lkm_usb_unlink_urbs (struct cp_lkm_usb_base_dev *cpbdev, struct sk_buff_head *q, struct cp_lkm_base_ep* bep) |
| { |
| unsigned long flags; |
| struct sk_buff *skb; |
| int count = 0; |
| |
| spin_lock_irqsave (&q->lock, flags); |
| while (!skb_queue_empty(q)) { |
| struct skb_data *entry; |
| struct urb *urb; |
| int retval; |
| |
| skb_queue_walk(q, skb) { |
| entry = (struct skb_data *) skb->cb; |
| urb = entry->urb; |
| if(urb && (entry->state != unlink_start) && (entry->bep == bep || bep == NULL)) { |
| goto found; |
| } |
| } |
| break; |
| found: |
| entry->state = unlink_start; |
| |
| /* |
| * Get reference count of the URB to avoid it to be |
| * freed during usb_unlink_urb, which may trigger |
| * use-after-free problem inside usb_unlink_urb since |
| * usb_unlink_urb is always racing with .complete |
| * handler(include defer_bh). |
| */ |
| usb_get_urb(urb); |
| spin_unlock_irqrestore(&q->lock, flags); |
| // during some PM-driven resume scenarios, |
| // these (async) unlinks complete immediately |
| //usb_kill_urb(urb); |
| retval = usb_unlink_urb (urb); |
| //g_unlink_cnt++; |
| if (retval != -EINPROGRESS && retval != 0){ |
| //netdev_dbg(dev->net, "unlink urb err, %d\n", retval); |
| } else{ |
| count++; |
| } |
| usb_put_urb(urb); |
| spin_lock_irqsave(&q->lock, flags); |
| } |
| spin_unlock_irqrestore (&q->lock, flags); |
| return count; |
| } |
| |
| |
| static void cp_lkm_usb_defer_kevent (struct cp_lkm_usb_base_dev* cpbdev, struct cp_lkm_base_ep* bep, int work) |
| { |
| set_bit (work, &bep->err_flags); |
| if (!schedule_work (&cpbdev->kevent)) { |
| //deverr (dev, "kevent %d may have been dropped", work); |
| } else { |
| //devdbg (dev, "kevent %d scheduled", work); |
| } |
| } |
| |
| // Workqueue callback function. This runs in thread context |
| static void cp_lkm_usb_kevent (struct work_struct *work) |
| { |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev *)container_of(work, struct cp_lkm_usb_base_dev, kevent); |
| int status; |
| struct cp_lkm_base_ep* bep; |
| struct list_head *entry, *nxt; |
| |
| |
| //grab global lock while testing dev state so it can't change on us. |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| if(!cp_lkm_usb_is_base_attached(cpbdev)){ |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| return; |
| } |
| |
| //don't want to hold global lock while doing this since don't know how long this will take, see next note |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| |
| //NOTE: if kernel preemption is enabled and the disconnect gets called right here, bad things could happen if the cpdev->udev |
| // is released. Fortunately, cp_lkm_usb_disconnect() calls cancel_work_sync() before releasing it. This will either cancel this |
| // function if it isn't currently running, or will wait until it exits before returning if it is running. This protects us. |
| |
| list_for_each_safe(entry, nxt, &cpbdev->out_bep_list) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| /* usb_clear_halt() needs a thread context */ |
| if (test_bit (EVENT_TX_HALT, &bep->err_flags)) { |
| cp_lkm_usb_unlink_urbs (cpbdev, &cpbdev->out_q, bep); |
| status = usb_clear_halt (cpbdev->udev, bep->pipe); |
| DEBUG_TRACE("%s() EVENT_TX_HALT status:%d", __FUNCTION__, status); |
| if (status < 0 && status != -EPIPE && status != -ESHUTDOWN) { |
| //if (netif_msg_tx_err (dev)) |
| // deverr (dev, "can't clear tx halt, status %d", |
| DEBUG_TRACE("%s() failed EVENT_TX_HALT status:%d", __FUNCTION__, status); |
| // status); |
| } else { |
| clear_bit (EVENT_TX_HALT, &bep->err_flags); |
| //if (status != -ESHUTDOWN) |
| // netif_wake_queue (dev->net); |
| } |
| } |
| } |
| |
| list_for_each_safe(entry, nxt, &cpbdev->in_bep_list) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| if (test_bit (EVENT_RX_HALT, &bep->err_flags)) { |
| cp_lkm_usb_unlink_urbs (cpbdev, &cpbdev->in_q, bep); |
| status = usb_clear_halt (cpbdev->udev, bep->pipe); |
| DEBUG_TRACE("%s() EVENT_RX_HALT status:%d", __FUNCTION__, status); |
| if (status < 0 && status != -EPIPE && status != -ESHUTDOWN) { |
| DEBUG_TRACE("%s() failed EVENT_RX_HALT status:%d", __FUNCTION__, status); |
| //if (netif_msg_rx_err (dev)) |
| // deverr (dev, "can't clear rx halt, status %d", |
| // status); |
| } else { |
| clear_bit (EVENT_RX_HALT, &bep->err_flags); |
| //grab global lock so link/unlink or unplug can't mess up the restock shedule pointers mid scheduling |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| if (cp_lkm_usb_is_base_attached(cpbdev)){ |
| cp_lkm_schedule_rx_restock(cpbdev,bep); |
| } |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| } |
| } |
| } |
| /* tasklet could resubmit itself forever if memory is tight */ |
| list_for_each_safe(entry, nxt, &cpbdev->in_bep_list) { |
| bep = list_entry(entry, struct cp_lkm_base_ep, list); |
| if (test_bit (EVENT_RX_MEMORY, &bep->err_flags)) { |
| DEBUG_TRACE("%s() EVENT_RX_MEMORY", __FUNCTION__); |
| |
| clear_bit (EVENT_RX_MEMORY, &bep->err_flags); |
| |
| //grab global lock so link/unlink or unplug can't mess up the restock shedule pointers mid scheduling |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| if (cp_lkm_usb_is_base_attached(cpbdev) && bep->q_cnt == 0){ |
| cp_lkm_schedule_rx_restock(cpbdev,bep); |
| |
| } |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| } |
| } |
| //if (test_bit (EVENT_LINK_RESET, &cpdev->flags)) { |
| // struct driver_info *info = dev->driver_info; |
| // int retval = 0; |
| // |
| // clear_bit (EVENT_LINK_RESET, &dev->flags); |
| // if(info->link_reset && (retval = info->link_reset(dev)) < 0) { |
| // devinfo(dev, "link reset failed (%d) usbnet usb-%s-%s, %s", |
| // retval, |
| // dev->udev->bus->bus_name, dev->udev->devpath, |
| // info->description); |
| // } |
| //} |
| |
| //if (dev->flags) |
| // devdbg (dev, "kevent done, flags = 0x%lx", |
| // dev->flags); |
| } |
| |
| static void cp_lkm_usb_ctrl_complete(struct urb *urb) |
| { |
| unsigned long flags; |
| struct sk_buff *skb = (struct sk_buff *) urb->context; |
| struct skb_data *entry = (struct skb_data *) skb->cb; |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev *)entry->cpbdev; |
| |
| //remove skb from the list first thing so no other code conext looking at the |
| //list (such as unlink_urbs) can mess with it. |
| spin_lock_irqsave(&cpbdev->ctrlq.lock, flags); |
| __skb_unlink(skb, &cpbdev->ctrlq); |
| spin_unlock_irqrestore(&cpbdev->ctrlq.lock,flags); |
| |
| skb->len = urb->actual_length; |
| |
| //skip status and error checking if the device has unplugged |
| if(!cp_lkm_usb_is_base_attached(cpbdev)) { |
| urb->status = -ENODEV; |
| goto ctrl_done; |
| } |
| |
| if (urb->status != 0) { |
| switch (urb->status) { |
| case -EPIPE: |
| break; |
| |
| /* software-driven interface shutdown */ |
| case -ECONNRESET: // async unlink |
| case -ESHUTDOWN: // hardware gone |
| break; |
| |
| case -ENODEV: |
| //printk("ctrl fail, no dev\n"); |
| break; |
| |
| case -EPROTO: |
| case -ETIME: |
| case -EILSEQ: |
| //CA: decided not to throttle on ctrl channel transfers since they are a different beast |
| //if (!timer_pending (&cpdev->rx_delay)) { |
| // mod_timer (&cpdev->rx_delay, jiffies + THROTTLE_JIFFIES); |
| //if (netif_msg_link (dev)) |
| // devdbg (dev, "tx throttle %d", |
| // urb->status); |
| //} |
| //netif_stop_queue (dev->net); |
| break; |
| default: |
| //if (netif_msg_tx_err (dev)) |
| // devdbg (dev, "tx err %d", entry->urb->status); |
| break; |
| } |
| } |
| |
| ctrl_done: |
| urb->dev = NULL; |
| entry->state = ctrl_done; |
| entry->status = urb->status; |
| entry->urb = NULL; |
| if(urb->setup_packet) { |
| kfree(urb->setup_packet); |
| } |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (urb); |
| cp_lkm_usb_done_and_defer_other(cpbdev, skb); |
| } |
| |
| |
| static int cp_lkm_usb_start_ctrl_xmit(void *ctx, struct sk_buff *skb_in) |
| { |
| struct cp_lkm_usb_dev* cpdev = (struct cp_lkm_usb_dev *)ctx; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| int retval = NET_XMIT_SUCCESS; |
| struct urb *urb = NULL; |
| struct skb_data *entry; |
| unsigned long flags; |
| int pipe; |
| u8* tmp8; |
| u16* tmp16; |
| struct usb_ctrlrequest *req = NULL; |
| |
| if(NULL == cpdev || !cp_lkm_usb_is_attached(cpdev) || !cp_lkm_usb_is_base_attached(cpdev->cpbdev)) { |
| //printk("%s() no ctxt\n", __FUNCTION__); |
| goto ctrl_done; |
| } |
| |
| cpbdev = cpdev->cpbdev; |
| |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| |
| if ((urb = usb_alloc_urb(0, GFP_ATOMIC)) == NULL) { |
| retval = -ENOMEM; |
| goto ctrl_done; |
| } |
| //cp_lkm_usb_urb_cnt(1); |
| |
| if ((req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC)) == NULL) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb(urb); |
| retval = -ENOMEM; |
| goto ctrl_done; |
| } |
| |
| //The upper layer driver put all the ctrl stuff at the end of the buffer (in correct le order) |
| //This layer puts it in a separate buffer |
| tmp8 = (u8*)skb_in->data; |
| req->bRequestType = *tmp8; |
| skb_pull(skb_in, 1); |
| |
| tmp8 = (u8*)skb_in->data; |
| req->bRequest = *tmp8; |
| skb_pull(skb_in, 1); |
| |
| tmp16 = (u16*)skb_in->data; |
| req->wValue = *tmp16; |
| skb_pull(skb_in, 2); |
| |
| tmp16 = (u16*)skb_in->data; |
| req->wIndex = *tmp16; |
| skb_pull(skb_in, 2); |
| |
| tmp16 = (u16*)skb_in->data; |
| req->wLength = *tmp16; |
| skb_pull(skb_in, 2); |
| //printk("%s() RT:%x, R:%x, V:%x, I:%x, L:%x\n", __FUNCTION__, req->bRequestType, req->bRequest, req->wValue, req->wIndex, req->wLength); |
| |
| entry = (struct skb_data *) skb_in->cb; |
| entry->urb = urb; |
| entry->cpbdev = cpbdev; |
| entry->state = ctrl_start; |
| entry->status = 0; |
| entry->bep = NULL; |
| entry->unique_id = cpdev->unique_id; |
| |
| if(req->bRequestType & USB_DIR_IN) { |
| DEBUG_TRACE("%s() ctrl in len: %d", __FUNCTION__,skb_in->len); |
| pipe = usb_rcvctrlpipe(cpbdev->udev, 0); |
| } |
| else{ |
| DEBUG_TRACE("%s() ctrl out len: %d", __FUNCTION__,skb_in->len); |
| pipe = usb_sndctrlpipe(cpbdev->udev, 0); |
| } |
| |
| usb_fill_control_urb(urb, cpbdev->udev, pipe, |
| (void *)req, skb_in->data, skb_in->len, |
| cp_lkm_usb_ctrl_complete, skb_in); |
| |
| //cp_lkm_usb_cnts(ctrl_start,1); |
| spin_lock_irqsave (&cpbdev->ctrlq.lock, flags); |
| retval = usb_submit_urb (urb, GFP_ATOMIC); |
| switch (retval) { |
| case 0: |
| //net->trans_start = jiffies; |
| //success: queue it |
| __skb_queue_tail (&cpbdev->ctrlq, skb_in); |
| skb_in = NULL; |
| urb = NULL; |
| req = NULL; |
| break; |
| case -ENODEV: |
| break; |
| case -EPROTO: |
| case -EPIPE: |
| break; |
| default: |
| break; |
| } |
| spin_unlock_irqrestore (&cpbdev->ctrlq.lock, flags); |
| |
| ctrl_done: |
| if(req) { |
| kfree(req); |
| } |
| if(urb) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb(urb); |
| } |
| if(skb_in) { |
| //cp_lkm_usb_cnts(ctrl_start,-1); |
| dev_kfree_skb_any (skb_in); |
| } |
| |
| DEBUG_TRACE("%s() retval %d", __FUNCTION__, retval); |
| |
| return retval; |
| } |
| |
| |
| #define THROTTLE_JIFFIES (HZ/8) |
| /* |
| * This function runs in a hw interrupt context. Do not put any DEBUG_XX print messages in here. |
| */ |
| static void cp_lkm_usb_xmit_complete (struct urb *urb) |
| { |
| unsigned long flags; |
| struct sk_buff *skb = (struct sk_buff *) urb->context; |
| struct skb_data *entry = (struct skb_data *) skb->cb; |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev *)entry->cpbdev; |
| struct cp_lkm_base_ep* bep = (struct cp_lkm_base_ep*)entry->bep; |
| bool is_data = false; |
| struct cp_lkm_usb_dev* cpdev; |
| |
| //remove skb from the list first thing so no other code context looking at the |
| //list (such as unlink_urbs) can mess with it. |
| spin_lock_irqsave(&cpbdev->out_q.lock,flags); |
| __skb_unlink(skb, &cpbdev->out_q); |
| spin_unlock_irqrestore(&cpbdev->out_q.lock,flags); |
| |
| bep->q_cnt--; |
| |
| if(bep->ep_num == cpbdev->data_out_bep_num) { |
| is_data = true; |
| } |
| |
| // we save mux id of the cpdev that sent each tx pckt. |
| cpdev = cp_lkm_usb_find_dev(entry->unique_id); |
| |
| //skip status and error checking if the device has unplugged |
| if(!cp_lkm_usb_is_base_attached(cpbdev)) { |
| goto xmit_done; |
| } |
| |
| if (urb->status != 0) { |
| UPDATE_STATS(cpdev->edi->pm_stats64_ctx, tx_errors, 1); |
| switch (urb->status) { |
| case -EPIPE: |
| //don't have to clear halts on ctrl ep |
| if (bep->ep_num != 0) { |
| cp_lkm_usb_defer_kevent (cpbdev, bep, EVENT_TX_HALT); |
| } |
| break; |
| |
| /* software-driven interface shutdown */ |
| case -ECONNRESET: // async unlink |
| case -ESHUTDOWN: // hardware gone |
| break; |
| |
| case -ENODEV: |
| break; |
| |
| // like rx, tx gets controller i/o faults during khubd delays |
| // and so it uses the same throttling mechanism. |
| case -EPROTO: |
| case -ETIME: |
| case -EILSEQ: |
| if (!timer_pending (&cpbdev->rx_delay)) { |
| mod_timer (&cpbdev->rx_delay, jiffies + THROTTLE_JIFFIES); |
| //if (netif_msg_link (dev)) |
| // devdbg (dev, "tx throttle %d", |
| // urb->status); |
| } |
| //netif_stop_queue (dev->net); |
| break; |
| default: |
| //if (netif_msg_tx_err (dev)) |
| // devdbg (dev, "tx err %d", entry->urb->status); |
| break; |
| } |
| } |
| |
| xmit_done: |
| entry->state = out_done; |
| |
| if(is_data) { |
| //cpdev->dbg_total_tx_irq++; |
| cp_lkm_usb_done_and_defer_data(cpbdev, skb, DATA_SRC_TX); |
| } |
| else{ |
| cp_lkm_usb_done_and_defer_other(cpbdev, skb); |
| } |
| } |
| |
| static int cp_lkm_usb_start_xmit_common(void *ctx, struct sk_buff *skb_in, int src, struct cp_lkm_ep* ep) |
| { |
| struct cp_lkm_usb_dev* cpdev = (struct cp_lkm_usb_dev *)ctx; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| struct cp_lkm_base_ep* bep; |
| int length; |
| int retval = NET_XMIT_SUCCESS; |
| struct urb *urb = NULL; |
| struct skb_data *entry; |
| unsigned long flags; |
| struct sk_buff* skb_out = NULL; |
| int wres; |
| |
| if(NULL == cpdev || !cp_lkm_usb_is_attached(cpdev) || !cp_lkm_usb_is_base_attached(cpdev->cpbdev)) { |
| //printk("%s() no ctxt\n", __FUNCTION__); |
| dev_kfree_skb_any(skb_in); |
| return -1; |
| } |
| |
| cpbdev = cpdev->cpbdev; |
| |
| //the network doesn't have a pointer to the ep readily available so he passes in NULL for ep so we can |
| //fetch the well known ep for the data out ep |
| length = 0; |
| if(src == CP_LKM_WRAPPER_SRC_DATA && ep == NULL){ |
| ep = cp_lkm_usb_get_ep(cpdev,cpdev->data_out_ep_num); |
| length = skb_in->len; |
| } |
| bep = ep->bep; |
| |
| while(1) { |
| skb_out = NULL; |
| urb = NULL; |
| retval = NET_XMIT_SUCCESS; |
| |
| //DEBUG_ERROR("%s() wrap it skb_in:%p", __FUNCTION__, skb_in); |
| |
| //only use wrappers on the data endpoint |
| if(ep->ep_num == cpdev->data_out_ep_num) { |
| //DEBUG_ERROR("%s() wrap it", __FUNCTION__); |
| //spin_lock_irqsave (&cp_lkm_usb_mgr.lock, flags); |
| wres = cp_lkm_wrapper_send(cpbdev->wrapper_ctxt, src, cpdev->mux_id, skb_in, &skb_out); |
| skb_in = NULL; //we no longer own skb so null its pointer for future call if we loop |
| //spin_unlock_irqrestore (&cp_lkm_usb_mgr.lock, flags); |
| if (wres == CP_LKM_WRAPPER_RES_ERROR) { |
| DEBUG_ERROR("%s() wrapper error wres:0x%x, skb_out:%p", __FUNCTION__, wres, skb_out); |
| UPDATE_STATS(cpdev->edi->pm_stats64_ctx, tx_dropped, 1); |
| retval = -ENOMEM; |
| goto xmit_done; |
| } |
| } |
| else{ |
| //Not a data ep, send the skb and then we are done |
| skb_out = skb_in; |
| skb_in = NULL; |
| wres = CP_LKM_WRAPPER_RES_DONE; |
| } |
| |
| //If we get here, send returned either done or again. skb_out can be NULL if there is nothing to |
| //send, so check that first |
| if(NULL == skb_out) { |
| // DEBUG_INFO("%s() no wrapped data", __FUNCTION__); |
| goto xmit_done; |
| } |
| |
| if(cp_lkm_is_broadcom && ((uintptr_t)(skb_out->data) & 0x3)) { |
| //broadcom unaligned packets that are multiples of 512 plus 3,4 or 5 bytes (515,516,517,1027,1028,1029,etc) |
| //are corrupted for some reason, so need to copy into an aligned buffer |
| int r = skb_out->len & 0x000001FF; //poor man's mod |
| if (r >= 3 && r <= 5) { |
| struct sk_buff* skb_new = skb_copy_expand(skb_out, 0, 0, GFP_ATOMIC); |
| if(!skb_new) { |
| retval = -ENOMEM; |
| goto xmit_done; |
| } |
| //printk("%s() unaligned: %p, aligned: %p, len: %d, r: %d\n",__FUNCTION__,skb_out->data, skb_new->data, skb_out->len, r); |
| dev_kfree_skb_any(skb_out); |
| skb_out=skb_new; |
| } |
| } |
| |
| if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) { |
| //if (netif_msg_tx_err (dev)) |
| // devdbg (dev, "no urb"); |
| DEBUG_ERROR("%s() urb alloc failed", __FUNCTION__); |
| UPDATE_STATS(cpdev->edi->pm_stats64_ctx, tx_dropped, 1); |
| retval = -ENOMEM; |
| goto xmit_done; |
| } |
| //cp_lkm_usb_urb_cnt(1); |
| entry = (struct skb_data *) skb_out->cb; |
| entry->urb = urb; |
| entry->cpbdev = cpbdev; |
| entry->bep = bep; |
| entry->state = out_start; |
| entry->unique_id = cpdev->unique_id; |
| //cp_lkm_usb_cnts(out_start,1); |
| |
| if(bep->type == UE_BULK) { |
| usb_fill_bulk_urb (urb, cpbdev->udev, bep->pipe, skb_out->data, |
| skb_out->len, cp_lkm_usb_xmit_complete, skb_out); |
| } |
| else{ |
| usb_fill_int_urb (urb, cpbdev->udev, bep->pipe, skb_out->data, skb_out->len, |
| cp_lkm_usb_xmit_complete, skb_out, bep->interval); |
| } |
| |
| if (!(cpbdev->feature_flags & CP_LKM_FEATURE_NO_ZERO_PACKETS)) { |
| urb->transfer_flags |= URB_ZERO_PACKET; |
| } |
| |
| // DEBUG_INFO("%s()", __FUNCTION__); |
| // DEBUG_INFO("%s() send to ep: 0x%x type:%d, pipe:0x%x", __FUNCTION__, ep->ep_num, ep->type, ep->pipe); |
| |
| spin_lock_irqsave (&cpbdev->out_q.lock, flags); |
| retval = usb_submit_urb (urb, GFP_ATOMIC); |
| switch (retval) { |
| case 0: |
| //net->trans_start = jiffies; |
| //success: queue it |
| __skb_queue_tail (&cpbdev->out_q, skb_out); |
| bep->q_cnt++; |
| skb_out = NULL; |
| urb = NULL; |
| if(ep->ep_num == cpdev->data_out_ep_num) { |
| cpbdev->tx_usb_q_count++; |
| if(cpbdev->tx_usb_q_count >= CP_LKM_USB_TX_PAUSE_Q_PKTS){ |
| if(!cpbdev->tx_paused) { |
| //pause all cpdevs |
| cp_lkm_usb_dev_pause(cpbdev, true); |
| cp_lkm_usb_stuck_check(cpbdev, CP_LKM_STUCK_START); |
| } |
| } |
| } |
| break; |
| case -EPIPE: |
| UPDATE_STATS(cpdev->edi->pm_stats64_ctx, tx_errors, 1); |
| //don't clear halts on ctrl ep |
| if(ep->ep_num != 0) { |
| cp_lkm_usb_defer_kevent(cpbdev, bep, EVENT_TX_HALT); |
| } |
| break; |
| case -ENODEV: |
| break; |
| case -EPROTO: |
| default: |
| //if (netif_msg_tx_err (dev)) |
| UPDATE_STATS(cpdev->edi->pm_stats64_ctx, tx_errors, 1); |
| // devdbg (dev, "tx: submit urb err %d", retval); |
| break; |
| } |
| spin_unlock_irqrestore (&cpbdev->out_q.lock, flags); |
| |
| xmit_done: |
| if (retval) { |
| DEBUG_TRACE("%s() failed to send: %d", __FUNCTION__, retval); |
| //cp_lkm_usb_cnts(out_start,-1); |
| } |
| |
| //if these are non null then they weren't sent so free them |
| if (skb_out){ |
| dev_kfree_skb_any (skb_out); |
| } |
| if(urb) { |
| //cp_lkm_usb_urb_cnt(-1); |
| usb_free_urb (urb); |
| } |
| |
| //Bail out of while loop unless the wrapper asked to be called again |
| if(wres != CP_LKM_WRAPPER_RES_AGAIN) { |
| break; |
| } |
| |
| length = 0; |
| |
| } |
| return retval; |
| } |
| |
| static int cp_lkm_usb_start_xmit (void *ctx, struct sk_buff *skb) |
| { |
| struct cp_lkm_usb_dev* cpdev = (struct cp_lkm_usb_dev *)ctx; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| int res; |
| |
| if(NULL == cpdev){ |
| DEBUG_TRACE("%s() no ctxt", __FUNCTION__); |
| dev_kfree_skb_any(skb); |
| return -1; |
| } |
| cpbdev = cpdev->cpbdev; |
| if(cpbdev->tx_paused || CP_LKM_USB_ACTIVE != cpdev->state) { |
| DEBUG_TRACE("%s() no ctxt", __FUNCTION__); |
| dev_kfree_skb_any(skb); |
| return -1; |
| } |
| res = cp_lkm_usb_start_xmit_common(ctx, skb, CP_LKM_WRAPPER_SRC_DATA, NULL); |
| return res; |
| } |
| |
| static int cp_lkm_usb_to_cplkm_status(int usb_status) |
| { |
| int cplkm_status; |
| switch(usb_status) { |
| case 0: |
| cplkm_status = CP_LKM_STATUS_OK; |
| break; |
| default: |
| //printk("usb err: %d\n", usb_status); |
| cplkm_status = CP_LKM_STATUS_ERROR; |
| break; |
| } |
| return cplkm_status; |
| } |
| |
| static void cp_lkm_usb_other_recv_process (struct cp_lkm_usb_base_dev* cpbdev, struct sk_buff *skb_in) |
| { |
| struct skb_data *entry; |
| struct cp_lkm_msg_hdr hdr; |
| int status; |
| struct cp_lkm_base_ep* bep; |
| struct cp_lkm_usb_dev* cpdev = NULL; |
| struct list_head *tmp, *nxt; |
| struct cp_lkm_ep *ep; |
| |
| if(!cp_lkm_usb_is_base_attached(cpbdev)){ |
| //printk("%s(), cpbdev: %p not attached. state: %d\n",__FUNCTION__,cpbdev,cpbdev->base_state); |
| dev_kfree_skb_any (skb_in); |
| return; |
| } |
| entry = (struct skb_data *)skb_in->cb; |
| bep = entry->bep; |
| |
| //Note: pkts on non-data endpoints when running with clones present a problem because there are no headers on these |
| // pkts to tell us which clone ep to send this to. Fortunately, the modem stack serializes clone instances so |
| // only one can be accessing the non-data endpoints at a time. In order to get any responses from the module |
| // over their endpoint, they must be either listening or have posted a recv. We use this fact to find the |
| // ep we need to send the recv back on. |
| list_for_each_safe(tmp, nxt, &bep->eps) { |
| ep = list_entry(tmp, struct cp_lkm_ep, list_bep); |
| if (ep->con_flags & (CP_LKM_USB_LISTEN | CP_LKM_USB_RECV)) { |
| cpdev = ep->cpdev; |
| if (ep->con_flags & CP_LKM_USB_RECV) { |
| //can only have one recv pending on non-data endpoints for a given ep number. |
| //therefor when the clone is done, the base is done |
| ep->con_flags &= ~CP_LKM_USB_RECV; |
| bep->con_flags &= ~CP_LKM_USB_RECV; |
| } |
| //printk("%s(), other data cpdev: %p, ep: %p, num: 0x%x, flags: 0x%x\n",__FUNCTION__,cpdev,ep, ep->ep_num,ep->con_flags); |
| break; |
| } |
| } |
| |
| if (!cpdev) { |
| //printk("%s() no cpdev unexpectedly for unique_id: %d",__FUNCTION__, entry->unique_id); |
| dev_kfree_skb_any (skb_in); |
| return; |
| } |
| |
| status = cp_lkm_usb_to_cplkm_status(entry->status); |
| //printk("%s() other data uid: %d, ep_num:0x%x, status:%d, len: %d\n", __FUNCTION__, cpdev->unique_id,bep->ep_num, entry->status, skb_in->len); |
| |
| memset(&hdr,0,sizeof(hdr)); |
| hdr.instance_id = cpdev->unique_id; |
| hdr.cmd = CP_LKM_USB_CMD_DATA_RECV; |
| hdr.status = status; |
| hdr.len = skb_in?skb_in->len:0; |
| hdr.arg1 = bep->ep_num; |
| cp_lkm_post_message(&cp_lkm_usb_mgr.common, &hdr, skb_in); |
| |
| return; |
| } |
| |
| |
| static void cp_lkm_usb_ctrl_process (struct cp_lkm_usb_base_dev* cpbdev, struct sk_buff *skb_in) |
| { |
| struct skb_data *entry; |
| struct cp_lkm_msg_hdr hdr; |
| int status; |
| static struct cp_lkm_usb_dev* cpdev = NULL; |
| |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| if(!cp_lkm_usb_is_base_attached(cpbdev)){ |
| dev_kfree_skb_any (skb_in); |
| return; |
| } |
| |
| entry = (struct skb_data *)skb_in->cb; |
| cpdev = cp_lkm_usb_find_dev(entry->unique_id); |
| if (!cpdev) { |
| //printk("%s() no cpdev unexpectedly for unique_id: %d",__FUNCTION__, entry->unique_id); |
| dev_kfree_skb_any (skb_in); |
| return; |
| } |
| |
| status = cp_lkm_usb_to_cplkm_status(entry->status); |
| memset(&hdr,0,sizeof(hdr)); |
| hdr.instance_id = cpdev->unique_id; |
| hdr.cmd = CP_LKM_USB_CMD_CTRL_RECV; |
| hdr.status = status; |
| hdr.len = skb_in?skb_in->len:0; |
| hdr.arg1 = 0; //ctrl channel ep is always 0 |
| |
| cp_lkm_post_message(&cp_lkm_usb_mgr.common, &hdr, skb_in); |
| DEBUG_TRACE("%s() ctrl response status:%d", __FUNCTION__, entry->status); |
| |
| return; |
| } |
| |
| |
| //This function runs in an interrupt context so it can't be preempted. This means cpdev can't |
| //be deleted out from under |
| static void cp_lkm_usb_data_recv_process (struct cp_lkm_usb_base_dev* cpbdev, struct sk_buff *skb_in) |
| { |
| struct sk_buff *skb_out; |
| int res; |
| int dst; |
| struct skb_data *entry; |
| struct cp_lkm_usb_dev* cpdev; |
| struct cp_lkm_base_ep* bep; |
| int ep_num; |
| int mux_id; |
| |
| // WARNING: The memory this pointer points to will be freed by the wrapper, so copy everything you need |
| // out of it here before going into the while loop |
| entry = (struct skb_data *)skb_in->cb; |
| bep = entry->bep; |
| ep_num = bep->ep_num; |
| |
| //printk("%s() cpbdev: %p, bep: %p base_state: %d\n", __FUNCTION__, cpbdev, bep, cpbdev->base_state); |
| |
| if(!cp_lkm_usb_is_base_attached(cpbdev)){ |
| dev_kfree_skb_any (skb_in); |
| return; |
| } |
| |
| while(1) { |
| skb_out = NULL; |
| |
| mux_id = 0; |
| |
| res = cp_lkm_wrapper_recv(cpbdev->wrapper_ctxt, &dst, &mux_id, skb_in, &skb_out); |
| |
| if (dst != CP_LKM_WRAPPER_DST_CTRL && dst != CP_LKM_WRAPPER_DST_DATA) { |
| // this is something other than data that we don't know what to do with, so drop it. |
| goto recv_done; |
| } |
| |
| cpdev = cp_lkm_usb_find_muxed_dev(cpbdev, mux_id); |
| |
| skb_in = NULL; |
| |
| if (NULL == cpdev) { |
| //LOG("%s(), no cpdev found for mux_id: 0x%x, or base_id: %d", __FUNCTION__,mux_id,cpbdev->base_id); |
| DEBUG_WARN("%s(), no cpdev found for mux_id: 0x%x, or base_id: %d", __FUNCTION__,mux_id,cpbdev->base_id); |
| goto recv_done; |
| } |
| |
| if(res == CP_LKM_WRAPPER_RES_ERROR) { |
| UPDATE_STATS(cpdev->edi->pm_stats64_ctx, rx_dropped, 1); |
| goto recv_done; |
| } |
| |
| //printk("%s() cpdev: %p, ep_num: 0x%x, dst: %d, mux_id: %d, state: %d, res: %d\n", __FUNCTION__, cpdev, ep_num, dst, mux_id, cpdev->state, res); |
| |
| //DEBUG_INFO("%s() while() - skb_out:%p, dst:%d, res:%d", __FUNCTION__, skb_out, dst, res); |
| |
| //if nothing to send, see if we can bail or if need to call again |
| if(NULL == skb_out){ |
| goto recv_done; |
| } |
| |
| if(dst == CP_LKM_WRAPPER_DST_CTRL) { |
| //printk("%s() ctrl pkt cpdev: %p\n", __FUNCTION__, cpdev); |
| if (skb_out->len) { // watch for 0 length short packets |
| struct cp_lkm_msg_hdr hdr; |
| |
| DEBUG_TRACE("%s() recv app pkt", __FUNCTION__); |
| memset(&hdr,0,sizeof(hdr)); |
| hdr.instance_id = cpdev->unique_id; |
| hdr.cmd = CP_LKM_USB_CMD_DATA_RECV; |
| hdr.status = CP_LKM_STATUS_OK; |
| hdr.len = skb_out->len; |
| hdr.arg1 = ep_num; |
| |
| cp_lkm_post_message(&cp_lkm_usb_mgr.common, &hdr, skb_out); |
| skb_out = NULL; |
| } |
| } |
| //dst == CP_LKM_WRAPPER_DST_DATA |
| else{ |
| //printk("%s() data pkt cpdev: %p\n", __FUNCTION__, cpdev); |
| if (skb_out->len && cpdev->edi->pm_recv){ |
| //printk("%s() data pkt send to pm cpdev: %p, first byte: 0x%x\n", __FUNCTION__, cpdev, skb_out->data[0]); |
| cpdev->edi->pm_recv(cpdev->edi->pm_recv_ctx, skb_out); |
| skb_out = NULL; |
| } |
| } |
| |
| recv_done: |
| if(skb_out) { |
| dev_kfree_skb_any(skb_out); |
| } |
| |
| //if wrapper didn't ask to be called back, then done |
| if(res != CP_LKM_WRAPPER_RES_AGAIN) { |
| break; |
| } |
| |
| } |
| |
| return; |
| } |
| |
| /* |
| * This function runs in a hw interrupt context. Do not put any DEBUG_XX print messages in here. |
| */ |
| static void cp_lkm_usb_recv_complete (struct urb *urb) |
| { |
| unsigned long flags; |
| struct sk_buff *skb = (struct sk_buff *) urb->context; |
| struct skb_data *entry = (struct skb_data *) skb->cb; |
| struct cp_lkm_usb_base_dev* cpbdev = (struct cp_lkm_usb_base_dev *)entry->cpbdev; |
| struct cp_lkm_usb_dev* cpdev_stats_only; |
| int urb_status = urb->status; |
| struct cp_lkm_base_ep* bep = entry->bep; |
| bool is_data = false; |
| //if(urb->status) { |
| // printk("recv_done: status: %d, len:%d\n", urb->status, urb->actual_length); |
| //} |
| |
| // we don't know what cpdev recv packets are destined for when running muxed clones, so report all errors |
| // to the base device (for non cloned cases, this will always be the correct cpdev) |
| cpdev_stats_only = cp_lkm_usb_find_dev(cpbdev->base_id); |
| |
| //remove skb from the list first thing so no other code conext looking at the |
| //list (such as unlink_urbs) can mess with it. |
| spin_lock_irqsave(&cpbdev->in_q.lock,flags); |
| __skb_unlink(skb, &cpbdev->in_q); |
| spin_unlock_irqrestore(&cpbdev->in_q.lock,flags); |
| |
| skb_put (skb, urb->actual_length); |
| if(bep->ep_num == cpbdev->data_in_bep_num) { |
| is_data = true; |
| entry->state = in_data_done; |
| //note we don't decrement the data ep cnt until we process the pkt |
| } else{ |
| bep->q_cnt--; |
| entry->state = in_other_done; |
| } |
| entry->status = urb->status; |
| |
| //skip status and error checking if the device has unplugged |
| if(!cp_lkm_usb_is_base_attached(cpbdev)) { |
| entry->status = -ENODEV; |
| goto recv_done; |
| } |
| |
| switch (urb_status) { |
| // success |
| case 0: |
| break; |
| |
| // stalls need manual reset. this is rare ... except that |
| // when going through USB 2.0 TTs, unplug appears this way. |
| // we avoid the highspeed version of the ETIMEOUT/EILSEQ |
| // storm, recovering as needed. |
| case -EPIPE: |
| UPDATE_STATS(cpdev_stats_only->edi->pm_stats64_ctx, rx_errors, 1); |
| //don't clear halts on ctrl ep |
| if(bep->ep_num != 0) { |
| cp_lkm_usb_defer_kevent (cpbdev, bep, EVENT_RX_HALT); |
| } |
| goto block; |
| |
| // software-driven interface shutdown |
| case -ECONNRESET: // async unlink |
| case -ESHUTDOWN: // hardware gone |
| goto block; |
| |
| case -ENODEV: |
| //printk("recv_done nodev:%d\n", ENODEV); |
| goto block; |
| |
| // we get controller i/o faults during khubd disconnect() delays. |
| // throttle down resubmits, to avoid log floods; just temporarily, |
| // so we still recover when the fault isn't a khubd delay. |
| case -EPROTO: |
| case -ETIME: |
| case -EILSEQ: |
| UPDATE_STATS(cpdev_stats_only->edi->pm_stats64_ctx, rx_errors, 1); |
| if (!timer_pending (&cpbdev->rx_delay)) { |
| mod_timer (&cpbdev->rx_delay, jiffies + THROTTLE_JIFFIES); |
| } |
| block: |
| if(bep->ep_num == cpbdev->data_in_bep_num) { |
| bep->q_cnt--; |
| entry->state = in_data_cleanup; |
| } |
| else{ |
| entry->state = in_other_cleanup; |
| } |
| |
| break; |
| |
| // data overrun ... flush fifo? |
| case -EOVERFLOW: |
| UPDATE_STATS(cpdev_stats_only->edi->pm_stats64_ctx, rx_over_errors, 1); |
| |
| // FALLTHROUGH |
| |
| default: |
| if(bep->ep_num == cpbdev->data_in_bep_num) { |
| bep->q_cnt--; |
| entry->state = in_data_cleanup; |
| } |
| else{ |
| entry->state = in_other_cleanup; |
| } |
| UPDATE_STATS(cpdev_stats_only->edi->pm_stats64_ctx, rx_errors, 1); |
| break; |
| } |
| |
| // on responses to a requested recv from the app driver, we need to always return something even on error so force it here |
| if(bep->con_flags & CP_LKM_USB_RECV) { |
| if(is_data){ |
| entry->state = in_data_done; //this should never happen, data endpoints always listen, they don't post recv's |
| } |
| else{ |
| entry->state = in_other_done; |
| } |
| } |
| |
| recv_done: |
| //do not use the 'entry' struct after this call. It is part of the skb and the skb will be freed when the _bh function runs. |
| //if you need something from it save it off before calling this |
| if(is_data) { |
| //cpdev->dbg_total_rx_irq++; |
| //printk("%s(), got data on cpbdev: %p, bep: %p, id: %d\n",__FUNCTION__, cpbdev, entry->bep, cpbdev->base_id); |
| cp_lkm_usb_done_and_defer_data(cpbdev, skb, DATA_SRC_RX); |
| } |
| else{ |
| //printk("%s(), got other data on cpbdev: %p, bep: %p, id: %d\n",__FUNCTION__, cpbdev, entry->bep, cpbdev->base_id); |
| cp_lkm_usb_done_and_defer_other(cpbdev, skb); |
| } |
| } |
| |
| //static int g_num_adjusts = 0; |
| //static int g_num_recv_pkts = 0; |
| //static int g_num_iters = 0; |
| static int cp_lkm_usb_submit_recv(struct cp_lkm_usb_base_dev* cpbdev , struct urb *urb, gfp_t flags, struct cp_lkm_base_ep* bep, bool data) |
| { |
| struct sk_buff *skb; |
| struct skb_data *entry; |
| int retval = 0; |
| unsigned long lockflags; |
| size_t size; |
| int hdr_size = 0; |
| int hdr_offset = 0; |
| int pad = 0; //some platforms require alignment override. pad takes care of that. |
| |
| //g_num_recv_pkts++; |
| //g_num_iters++; |
| //if(g_num_iters > 10000){ |
| // printk("%s() num pkts: %d, num adjusts: %d\n",__FUNCTION__,g_num_recv_pkts,g_num_adjusts); |
| // g_num_iters = 0; |
| //} |
| size = bep->max_transfer_size; |
| if (data) { |
| hdr_size = cpbdev->pm_hdr_size; |
| hdr_offset = cpbdev->pm_hdr_offset; |
| } |
| |
| if(cp_lkm_is_broadcom && (hdr_offset & 0x3)) { |
| //Jira issue FW-14929: On broadcom, we have to keep the buffers four byte aligned else the USB block |
| //corrupts the data (no idea why). |
| //Round up the hdr_offset to nearest 4 byte boundary. This means pkts may not be aligned as expected, |
| //so recieve function will need to either realign with a copy, or send up to the stack unaligned |
| // See cp_lkm_pm_net_recv() to see how we decided to deal with it (subject to change). |
| pad = 4 - (hdr_offset&0x3); |
| //g_num_adjusts++; |
| } |
| |
| if ((skb = alloc_skb (size+hdr_size+pad, flags)) == NULL) { |
| //if (netif_msg_rx_err (dev)) |
| // devdbg (dev, "no rx skb"); |
| cp_lkm_usb_defer_kevent (cpbdev, bep, EVENT_RX_MEMORY); |
| return -ENOMEM; |
| } |
| if (data) { |
| skb_reserve(skb, hdr_offset+pad); |
| //printk("%s(), data: %p, len: %d, whs:%d, hs:%d, ho:%d\n",__FUNCTION__,skb->data,skb->len,wrapper_hdr_size,hdr_size,hdr_offset); |
| } |
| entry = (struct skb_data *) skb->cb; |
| entry->urb = urb; |
| entry->cpbdev = cpbdev; |
| if(data) { |
| entry->state = in_data_start; |
| } |
| else{ |
| entry->state = in_other_start; |
| } |
| |
| entry->status = 0; |
| entry->bep = bep; |
| |
| if(bep->type == UE_BULK) { |
| usb_fill_bulk_urb (urb, cpbdev->udev, bep->pipe, skb->data, size, |
| cp_lkm_usb_recv_complete, skb); |
| } |
| else{ |
| usb_fill_int_urb (urb, cpbdev->udev, bep->pipe, skb->data, size, |
| cp_lkm_usb_recv_complete, skb, bep->interval); |
| } |
| //cp_lkm_usb_cnts(entry->state,1); |
| spin_lock_irqsave (&cpbdev->in_q.lock, lockflags); |
| if (cp_lkm_usb_is_base_attached(cpbdev) && !test_bit (EVENT_RX_HALT, &bep->err_flags)) { |
| DEBUG_TRACE("%s() ep:0x%x, size:%d, type:%d, pipe:0x%x",__FUNCTION__, bep->ep_num, size, bep->type, bep->pipe); |
| retval = usb_submit_urb (urb, GFP_ATOMIC); |
| switch (retval) { |
| case -EPIPE: |
| //don't clear halts on ctrl ep |
| if(bep->ep_num != 0) { |
| cp_lkm_usb_defer_kevent (cpbdev, bep, EVENT_RX_HALT); |
| } |
| break; |
| case -ENOMEM: |
| cp_lkm_usb_defer_kevent (cpbdev, bep, EVENT_RX_MEMORY); |
| break; |
| case -ENODEV: |
| //if (netif_msg_ifdown (dev)) |
| // devdbg (dev, "device gone"); |
| //netif_device_detach (dev->net); |
| break; |
| case -EPROTO: |
| default: |
| //if (netif_msg_rx_err (dev)) |
| // devdbg (dev, "rx submit, %d", retval); |
| cp_lkm_schedule_rx_restock(cpbdev,bep); |
| break; |
| case 0: |
| __skb_queue_tail (&cpbdev->in_q, skb); |
| bep->q_cnt++; |
| //if(cpdev->in_q.qlen == 1 && ep->index == CP_LKM_DATA_INDEX){ |
| // printk("rx q empty\n"); |
| //} |
| |
| } |
| } else { |
| //if (netif_msg_ifdown (dev)) |
| // devdbg (dev, "rx: stopped"); |
| retval = -ENOLINK; |
| } |
| spin_unlock_irqrestore (&cpbdev->in_q.lock, lockflags); |
| if (retval) { |
| DEBUG_TRACE("%s() FAILED ep_num:0x%x ep_type:%d, retval: %d",__FUNCTION__, bep->ep_num, bep->type, retval); |
| //cp_lkm_usb_cnts(entry->state,-1); |
| dev_kfree_skb_any (skb); |
| } |
| |
| return retval; |
| } |
| |
| |
| static int cp_lkm_usb_init(void) |
| { |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| memset(&cp_lkm_usb_mgr, 0x00, sizeof(struct cp_lkm_usb_ctx)); |
| cp_lkm_usb_mgr.common.open = cp_lkm_usb_open; |
| cp_lkm_usb_mgr.common.close = cp_lkm_usb_close; |
| cp_lkm_usb_mgr.common.handle_msg = cp_lkm_usb_handle_msg; |
| cp_lkm_usb_mgr.common.handle_ioctl = cp_lkm_usb_handle_ioctl; |
| INIT_LIST_HEAD(&cp_lkm_usb_mgr.dev_list); |
| |
| cp_lkm_common_ctx_init(&cp_lkm_usb_mgr.common); |
| |
| spin_lock_init(&cp_lkm_usb_mgr.lock); |
| //sema_init(&cp_lkm_usb_mgr.thread_sem, 1); |
| |
| if(!strcmp(PRODUCT_PLATFORM, "brcm_arm")) { |
| LOG("cp_lkm: Broadcom platform"); |
| cp_lkm_is_broadcom = 1; |
| } |
| |
| LOG("cp_lkm: Product chipset %s",PRODUCT_INFO_CHIPSET); |
| LOG("cp_lkm: Product platform %s",PRODUCT_PLATFORM); |
| |
| //Things work better if the napi weight here matchs the global weight set in service_manager/services/firewall.py |
| //This is even true if we don't use napi here since ethernet on some platforms use it |
| if ((strcmp(PRODUCT_PLATFORM,"ramips")==0) && (strcmp(PRODUCT_INFO_CHIPSET, "3883")!=0)){ |
| //all ralink (mediatek) platforms except for 3883 use the low settings |
| //use_high = false; |
| CP_LKM_PM_NAPI_WEIGHT = 32; |
| } |
| else{ |
| //use_high = true; |
| CP_LKM_PM_NAPI_WEIGHT = 64; |
| } |
| |
| //set up default settings for all platforms |
| CP_LKM_USB_NAPI_MAX_WORK = CP_LKM_PM_NAPI_WEIGHT; |
| CP_LKM_USB_MAX_RX_QLEN = CP_LKM_USB_NAPI_MAX_WORK; |
| CP_LKM_USB_MAX_OTHER_QLEN = 2; |
| CP_LKM_USB_TX_PAUSE_Q_PKTS = CP_LKM_USB_NAPI_MAX_WORK; |
| CP_LKM_USB_TX_RESUME_Q_PKTS = CP_LKM_USB_TX_PAUSE_Q_PKTS/4; |
| CP_LKM_USB_TX_SCHED_CNT = 1; |
| CP_LKM_USB_RX_SCHED_CNT = 1; |
| CP_LKM_USB_RESTOCK_MULTIPLE = 1; //restock rx as we process them |
| CP_LKM_USB_TASKLET_CNT = 10; |
| CP_LKM_USB_WORKQUEUE_CNT = 5; |
| CP_LKM_USB_PROCESS_DIVISOR = 4; |
| |
| LOG("cp_lkm: Processor: %s, Max work: %d, NAPI budget: %d, QLEN: %d.",PRODUCT_INFO_CHIPSET, CP_LKM_USB_NAPI_MAX_WORK, CP_LKM_PM_NAPI_WEIGHT, CP_LKM_USB_MAX_RX_QLEN); |
| |
| return 0; |
| |
| } |
| |
| static int cp_lkm_usb_cleanup(void) |
| { |
| //module is unloading, clean up everything |
| // empty pending posted messages |
| cp_lkm_cleanup_msg_list(&cp_lkm_usb_mgr.common); |
| |
| cp_lkm_usb_close(&cp_lkm_usb_mgr.common); |
| return 0; |
| } |
| |
| static int cp_lkm_usb_open(struct cp_lkm_common_ctx *ctx) |
| { |
| //struct cp_lkm_usb_ctx* mgr; |
| |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| //mgr = (struct cp_lkm_usb_ctx*)ctx; |
| |
| return 0; |
| } |
| |
| static int cp_lkm_usb_close(struct cp_lkm_common_ctx *ctx) |
| { |
| //unsigned long flags; |
| //struct cp_lkm_usb_dev* cpdev; |
| //struct cp_lkm_usb_close_intf ci; |
| //struct cp_lkm_usb_unplug_intf ui; |
| LOG("%s() called unexpectedly.", __FUNCTION__); |
| |
| //NOTE: catkin 10/11/2019 - Close is only called in our system if the modem stack crashes. This means |
| // things are in a bad state and the router will be rebooting. We decided not |
| // to clean things up here because this code got into an infinite loop in |
| // certain fail situations, which prevented the router from rebooting. |
| // Revisit if close ever becomes a normal event. |
| |
| /* |
| while(1) { |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| |
| cpdev = cp_lkm_usb_get_head_dev(); |
| |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| if(!cpdev) { |
| return 0; |
| } |
| |
| //TODO - when this closed we have a modem plugged, we will be deleting the top half of the driver while the bottom half is |
| // still plugged. Figure out how to force the driver to disconnect the modem |
| ci.unique_id = cpdev->unique_id; |
| cp_lkm_usb_close_intf(&ci); |
| |
| //the unplug removes the device from the list which prevents us from infinite looping here |
| ui.unique_id = cpdev->unique_id; |
| cp_lkm_usb_unplug_intf(&ui); |
| } |
| |
| cp_lkm_cleanup_msg_list(ctx); |
| */ |
| return 0; |
| } |
| |
| static int cp_lkm_usb_handle_msg(struct cp_lkm_common_ctx *ctx, struct cp_lkm_msg_hdr *hdr, struct sk_buff *skb) |
| { |
| int retval = -1; |
| struct cp_lkm_ep* ep; |
| struct cp_lkm_usb_dev* cpdev; |
| struct cp_lkm_usb_base_dev* cpbdev; |
| |
| //grab lock to protect global device list before searching (don't want to search it if another thread is adding or removing a cpdev) |
| spin_lock(&cp_lkm_usb_mgr.lock); |
| cpdev = cp_lkm_usb_find_dev(hdr->instance_id); |
| |
| //grab thread semaphore so disconnect can't run and delete the cpdev while we are running here |
| if(!cpdev || !cp_lkm_usb_is_attached(cpdev) || !cp_lkm_usb_is_base_attached(cpdev->cpbdev)) { |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| dev_kfree_skb_any (skb); |
| //printk("%s() no device or no probe yet\n", __FUNCTION__); |
| return 0; |
| } |
| cpbdev = cpdev->cpbdev; |
| switch(hdr->cmd) { |
| case CP_LKM_USB_CMD_DATA_SEND: |
| { |
| ep = cp_lkm_usb_get_ep(cpdev, hdr->arg1); |
| if(ep) { |
| //printk("%s(), send other data cpbdev: %p, cpdev: %p, bep: %p, ep: %p, num: 0x%x\n",__FUNCTION__,cpdev->cpbdev,cpdev,ep->bep,ep,ep->ep_num); |
| retval = cp_lkm_usb_start_xmit_common(cpdev, skb, CP_LKM_WRAPPER_SRC_CTRL, ep); |
| skb = NULL; |
| } |
| else{ |
| DEBUG_TRACE("%s() Invalid EP number 0x%x", __FUNCTION__, hdr->arg1); |
| retval = -1; |
| } |
| } |
| break; |
| case CP_LKM_USB_CMD_CTRL_SEND: |
| { |
| retval = cp_lkm_usb_start_ctrl_xmit(cpdev, skb); |
| skb = NULL; |
| } |
| break; |
| } |
| |
| spin_unlock(&cp_lkm_usb_mgr.lock); |
| |
| if(skb) { |
| dev_kfree_skb_any (skb); |
| } |
| return retval; |
| } |
| |
| static int cp_lkm_usb_handle_ioctl(struct cp_lkm_common_ctx *ctx, int cmd, void *k_argp) |
| { |
| int retval = -1; |
| //printk("%s(), cmd:0x%x\n", __FUNCTION__, _IOC_NR(cmd)); |
| |
| switch(cmd) { |
| case CP_LKM_IOCTL_USB_PLUG_INTF: |
| { |
| struct cp_lkm_usb_plug_intf* pi = (struct cp_lkm_usb_plug_intf*)k_argp; |
| retval = cp_lkm_usb_plug_intf(pi); |
| } |
| break; |
| case CP_LKM_IOCTL_USB_SET_WRAPPER: |
| { |
| struct cp_lkm_usb_set_wrapper* sw = (struct cp_lkm_usb_set_wrapper*)k_argp; |
| retval = cp_lkm_usb_set_wrapper(sw); |
| } |
| break; |
| case CP_LKM_IOCTL_USB_SET_MUX_ID: |
| { |
| struct cp_lkm_usb_set_mux_id* smi = (struct cp_lkm_usb_set_mux_id*)k_argp; |
| retval = cp_lkm_usb_set_mux_id(smi); |
| } |
| break; |
| case CP_LKM_IOCTL_USB_OPEN_INTF: |
| { |
| struct cp_lkm_usb_open_intf* oi = (struct cp_lkm_usb_open_intf*)k_argp; |
| retval = cp_lkm_usb_open_intf(oi); |
| } |
| break; |
| case CP_LKM_IOCTL_USB_CLOSE_INTF: |
| { |
| struct cp_lkm_usb_close_intf* ci = (struct cp_lkm_usb_close_intf*)k_argp; |
| retval = cp_lkm_usb_close_intf(ci); |
| } |
| break; |
| case CP_LKM_IOCTL_USB_UNPLUG_INTF: |
| { |
| struct cp_lkm_usb_unplug_intf* ui = (struct cp_lkm_usb_unplug_intf*)k_argp; |
| retval = cp_lkm_usb_unplug_intf(ui); |
| } |
| break; |
| case CP_LKM_IOCTL_USB_EP_ACTION: |
| { |
| struct cp_lkm_usb_ep_action* ea = (struct cp_lkm_usb_ep_action*)k_argp; |
| retval = cp_lkm_usb_ep_action(ea); |
| } |
| break; |
| case CP_LKM_IOCTL_USB_PM_LINK: |
| { |
| struct cp_lkm_usb_pm_link *upl = (struct cp_lkm_usb_pm_link *)k_argp; |
| retval = cp_lkm_usb_pm_link(upl); |
| } |
| break; |
| case CP_LKM_IOCTL_USB_IS_ALIVE_INTF: |
| { |
| struct cp_lkm_usb_is_alive_intf* alivei = (struct cp_lkm_usb_is_alive_intf*)k_argp; |
| retval = cp_lkm_usb_is_alive_intf(alivei); |
| } |
| } |
| |
| return retval; |
| } |
| |
| |
| /******************************* kernel module PM instance functionality **********************************/ |
| struct cp_lkm_pm_ctx { |
| struct cp_lkm_common_ctx common; |
| struct list_head pm_list; |
| spinlock_t pm_list_lock; |
| }; |
| |
| struct cp_lkm_pm_ctx cp_lkm_pm_mgr; |
| |
| |
| static void cp_lkm_pm_filter_empty_list(struct cp_lkm_pm_common *pm) |
| { |
| |
| struct cp_lkm_pm_filter *filter; |
| struct list_head *entry, *tmp; |
| |
| list_for_each_safe(entry, tmp, &pm->filter_list) { |
| filter = list_entry(entry, struct cp_lkm_pm_filter, list); |
| list_del(&filter->list); |
| kfree(filter); |
| } |
| } |
| |
| static bool cp_lkm_pm_filter_ok(struct cp_lkm_pm_common *pm, unsigned char *buf, unsigned int buf_len) |
| { |
| bool allow = true; // default allow the egress packet |
| |
| struct list_head *pos; |
| |
| struct in_device *in_dev; |
| struct in_ifaddr *ifa; |
| struct iphdr *ipv4_hdr; |
| u32 ipv4_src_addr = 0; |
| u32 ipv4_net_addr = 0; |
| u32 ipv4_net_mask = 0; |
| |
| ipv4_hdr = (struct iphdr *)buf; |
| |
| // these are the include filters (white list) - exclude filters (black list) are not currently supported |
| // exclude filters may need to be processed in another loop through the filters |
| list_for_each(pos, &pm->filter_list) { |
| struct cp_lkm_pm_filter *filter = list_entry(pos, struct cp_lkm_pm_filter, list); |
| switch(filter->type) { |
| case CP_LKM_PM_FILTER_TYPE_IP_SRC_WAN_SUBNET_INCLUDE: |
| if (4 == ipv4_hdr->version) { |
| // ipv4 |
| allow = false; |
| ipv4_src_addr = __be32_to_cpu(ipv4_hdr->saddr); |
| if(ipv4_src_addr == 0){ |
| //DHCP rebind packets may have a src addr of 0.0.0.0 and we want to let those through. |
| allow = true; |
| } |
| else{ |
| // get network device IP address and check against src packet ip address |
| rcu_read_lock(); |
| in_dev = rcu_dereference(pm->net_dev->ip_ptr); |
| // in_dev has a list of IP addresses (because an interface can have multiple - check them all) |
| for (ifa = in_dev->ifa_list; ifa != NULL; ifa = ifa->ifa_next) { |
| ipv4_net_addr = __be32_to_cpu(ifa->ifa_local); |
| ipv4_net_mask = __be32_to_cpu(ifa->ifa_mask); |
| if ((ipv4_net_addr & ipv4_net_mask) == (ipv4_src_addr & ipv4_net_mask)) { |
| // allow the packet |
| allow = true; |
| break; |
| } |
| } |
| rcu_read_unlock(); |
| } |
| }/* benk needs to be tested before ok to execute |
| else if (6 == ipv4_hdr->version) { |
| struct in6_addr *addr = (struct in6_addr *)&buf[2 * sizeof(u32)]; |
| if (ipv6_chk_prefix(addr, pm->net_dev)) { |
| allow = true; |
| } |
| } */ |
| break; |
| case CP_LKM_PM_FILTER_TYPE_IP_SRC_SUBNET_INCLUDE: |
| if (4 == ipv4_hdr->version) { |
| // ipv4 |
| allow = false; |
| ipv4_src_addr = __be32_to_cpu(ipv4_hdr->saddr); |
| if(ipv4_src_addr == 0){ |
| //DHCP rebind packets may have a src addr of 0.0.0.0 and we want to let those through. |
| allow = true; |
| } |
| else if ((filter->subnet.ipv4_addr & filter->subnet.ipv4_mask) == (ipv4_src_addr & filter->subnet.ipv4_mask)) { |
| allow = true; |
| } |
| } |
| |
| default: |
| break; |
| } |
| |
| if (allow) { |
| break; |
| } |
| } |
| |
| if (!allow) { |
| DEBUG_WARN("%s() dropping packet - src:0x%x\n", __FUNCTION__, ipv4_src_addr); |
| } |
| |
| return allow; |
| } |
| /******************************* kernel module pm common functionality **********************************/ |
| int cp_lkm_common_init(struct cp_lkm_pm_common *pmc) |
| { |
| // allocate stats struct |
| pmc->pcpu_stats64 = netdev_alloc_pcpu_stats(struct cp_lkm_pm_stats64); |
| if (!pmc->pcpu_stats64) { |
| return -ENOMEM; |
| } |
| |
| |
| pmc->pm_link_count = 0; |
| spin_lock_init(&pmc->pm_link_lock); |
| INIT_LIST_HEAD(&pmc->filter_list); |
| |
| return 0; |
| } |
| |
| void cp_lkm_common_deinit(struct cp_lkm_pm_common *pmc) |
| { |
| if (!pmc->pcpu_stats64) { |
| return; |
| } |
| free_percpu(pmc->pcpu_stats64); |
| pmc->pcpu_stats64 = NULL; |
| } |
| // The pm_link_lock is used to coordinate activity between xmit, poll, and link/unlink |
| // It is okay to poll and xmit at the same time, but we don't want to do either if we are linking or unlinking. |
| // link/unlink sets the pm_link_count negative to block both poll and xmit. If pm_link_count is not negative then |
| // both poll and xmit are free to grab the link at any time and at the same time. |
| //retval: |
| // 0 = you have the token, proceed |
| // -1 = you don't have the token, do not pass go |
| int cp_lkm_common_inc_link_lock(struct cp_lkm_pm_common* pmc) |
| { |
| unsigned long flags; |
| int retval = 0; |
| spin_lock_irqsave(&pmc->pm_link_lock, flags); |
| if(pmc->pm_link_count < 0) { |
| retval = -1; |
| } |
| else{ |
| pmc->pm_link_count++; |
| } |
| spin_unlock_irqrestore(&pmc->pm_link_lock, flags); |
| return retval; |
| } |
| |
| int cp_lkm_common_dec_link_lock(struct cp_lkm_pm_common* pmc) |
| { |
| unsigned long flags; |
| int retval = 0; |
| spin_lock_irqsave(&pmc->pm_link_lock, flags); |
| if(pmc->pm_link_count > 0) { |
| pmc->pm_link_count--; |
| } |
| else{ |
| //should never hit this |
| retval = -1; |
| } |
| spin_unlock_irqrestore(&pmc->pm_link_lock, flags); |
| return retval; |
| } |
| |
| /******************************* kernel module net PM functionality **********************************/ |
| |
| // common structure for ethernet and IP protocol managers |
| struct cp_lkm_pm_net { |
| struct cp_lkm_pm_common common; |
| struct ethhdr eth_hdr; |
| |
| }; |
| |
| static void cp_lkm_pm_get_stats64(struct net_device *netdev, struct rtnl_link_stats64 *stats) |
| { |
| struct cp_lkm_pm_net *pm_net; |
| int i; |
| struct cp_lkm_pm_stats64 *pstats; |
| |
| pm_net = netdev_priv(netdev); |
| |
| for_each_possible_cpu(i) { |
| u64 rx_packets, rx_bytes, rx_errors, rx_dropped, rx_over_errors; |
| u64 tx_packets, tx_bytes, tx_errors, tx_dropped; |
| unsigned int start; |
| pstats = per_cpu_ptr(pm_net->common.pcpu_stats64, i); |
| do { |
| start = u64_stats_fetch_begin_irq(&pstats->syncp); |
| rx_packets = pstats->rx_packets; |
| tx_packets = pstats->tx_packets; |
| rx_bytes = pstats->rx_bytes; |
| tx_bytes = pstats->tx_bytes; |
| rx_errors = pstats->rx_errors; |
| tx_errors = pstats->tx_errors; |
| rx_dropped = pstats->rx_dropped; |
| tx_dropped = pstats->tx_dropped; |
| rx_over_errors = pstats->rx_over_errors; |
| } while (u64_stats_fetch_retry_irq(&pstats->syncp, start)); |
| |
| stats->rx_packets += rx_packets; |
| stats->tx_packets += tx_packets; |
| stats->rx_bytes += rx_bytes; |
| stats->tx_bytes += tx_bytes; |
| stats->rx_errors += rx_errors; |
| stats->tx_errors += tx_errors; |
| stats->rx_dropped += rx_dropped; |
| stats->tx_dropped += tx_dropped; |
| stats->rx_over_errors += rx_over_errors; |
| } |
| |
| } |
| |
| static int cp_lkm_pm_net_open(struct net_device *dev) |
| { |
| struct cp_lkm_pm_net *pm_net; |
| |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| |
| pm_net = netdev_priv(dev); |
| netif_start_queue(dev); |
| |
| // is this link up? |
| return 0; |
| } |
| |
| static int cp_lkm_pm_net_close(struct net_device *dev) |
| { |
| struct cp_lkm_pm_net *pm_net = netdev_priv(dev); |
| struct cp_lkm_msg_hdr hdr; |
| |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| |
| // link change |
| netif_stop_queue(dev); |
| |
| // post message to indicate link down |
| memset(&hdr,0,sizeof(hdr)); |
| hdr.instance_id = pm_net->common.unique_id; |
| hdr.cmd = CP_LKM_PM_LINK_DOWN; |
| hdr.status = CP_LKM_STATUS_OK; |
| cp_lkm_post_message(&cp_lkm_pm_mgr.common, &hdr, NULL); |
| LOG("Link Down indicated - id:%d\n", hdr.instance_id); |
| |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_net_xmit(struct sk_buff *skb, struct net_device *dev) |
| { |
| struct cp_lkm_pm_net *pm_net = netdev_priv(dev); |
| bool filter_ok = true; |
| int link_res; |
| |
| //see if we can grab the link lock, if not, we are either bringing up or taking down the link between USB and PM, so not safe to proceed |
| link_res = cp_lkm_common_inc_link_lock(&pm_net->common); |
| if(link_res < 0) { |
| dev_kfree_skb_any(skb); |
| return NETDEV_TX_OK; |
| } |
| |
| if (!pm_net->common.edi) { |
| // cannot do anything without edi |
| dev_kfree_skb_any(skb); |
| goto net_xmit_done; |
| } |
| |
| //DEBUG_INFO("%s() - %s len:%d", __FUNCTION__, pm_net->common.net_dev->name, skb->len); |
| UPDATE_STATS(pm_net->common.edi->pm_stats64_ctx, tx_bytes, (skb->len - sizeof(struct ethhdr))); |
| UPDATE_STATS(pm_net->common.edi->pm_stats64_ctx, tx_packets, 1); |
| /* Drop packet if interface is not attached */ |
| if (0 == pm_net->common.attached) |
| goto drop; |
| |
| if (!pm_net->common.edi->usb_send) { |
| goto drop; |
| } |
| |
| filter_ok = cp_lkm_pm_filter_ok(&pm_net->common, skb->data + sizeof(struct ethhdr), skb->len - sizeof(struct ethhdr)); |
| if (!filter_ok) { |
| pm_net->common.filter_drop_cnt++; |
| DEBUG_WARN("%s() filter dropped packet cnt:%u", __FUNCTION__, pm_net->common.filter_drop_cnt); |
| goto drop; |
| } |
| |
| switch(pm_net->common.type) { |
| case CP_LKM_PM_TYPE_IP_DHCP: |
| case CP_LKM_PM_TYPE_IP_STATIC: |
| skb_pull(skb, sizeof(struct ethhdr)); // strip off the ethernet header |
| break; |
| default: |
| break; |
| } |
| |
| // send data to USB module |
| pm_net->common.edi->usb_send(pm_net->common.edi->usb_send_ctx, skb); |
| goto net_xmit_done; |
| |
| drop: |
| DEBUG_INFO("%s() - dropped", __FUNCTION__); |
| UPDATE_STATS(pm_net->common.edi->pm_stats64_ctx, tx_dropped, 1); |
| dev_kfree_skb_any(skb); |
| |
| net_xmit_done: |
| cp_lkm_common_dec_link_lock(&pm_net->common); |
| return NETDEV_TX_OK; |
| } |
| |
| |
| #if 0 |
| static u8 cp_lkm_pm_test_find(u8* pkt, u32 pkt_len, u8* pattern, u32 pattern_len) |
| { |
| s32 i; |
| for(i = 0; i < (pkt_len - pattern_len); i++) { |
| if (memcmp(&pkt[i],pattern,pattern_len) == 0) { |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| static int cp_lkm_pm_test(struct sk_buff *skb) |
| { |
| static u8 first_pkt = 1; |
| static u8 started = 0; |
| static unsigned long total_data = 0; |
| static unsigned long start_time = 0; |
| static unsigned long stop_time = 0; |
| |
| static unsigned long invalid_pkts = 0; |
| static unsigned long total_pkts = 0; |
| |
| int drop = 0; |
| unsigned char *ptr = skb->data; |
| u32 pkt_len = skb->len; |
| u8 prot; |
| //u8 type; |
| u16 udp_len; |
| u16 dst_port; |
| |
| if (pkt_len < 20) { |
| return 0; |
| } |
| //function is set up to parse IP pkts, may be called with ether framed pkts as well. |
| //auto detect ether hdr and remove it |
| if (ptr[0] != 0x45) { |
| //ether header |
| if(ptr[14] == 0x45){ |
| ptr+=14; |
| pkt_len -= 14; |
| } |
| //vlan hdr |
| else if (ptr[12] == 0x81 && ptr[18] == 0x45) { |
| ptr+=18; |
| pkt_len -=18; |
| } |
| } |
| |
| if (ptr[0] != 0x45) { |
| invalid_pkts++; |
| } |
| |
| //printk("0x%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x len: %d \n",ptr[0],ptr[1],ptr[2],ptr[3],ptr[4],ptr[5],ptr[6],ptr[7],ptr[8],ptr[9],ptr[10],ptr[11],ptr[12],ptr[13],ptr[14],ptr[15],pkt_len); |
| //printk("0x%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x len: %d \n",ptr[0],ptr[1],ptr[2],ptr[3],ptr[4],ptr[5],ptr[6],ptr[7],ptr[8],ptr[9],ptr[10],ptr[11],ptr[12],ptr[13],ptr[14],ptr[15],pkt_len); |
| if (pkt_len >= 28) { |
| prot = ptr[9]; |
| if (prot == 0x11) { |
| ptr += 20; //skip ip header |
| pkt_len -= 20; |
| dst_port = ntohs(*((u16*)(&ptr[2]))); |
| udp_len = ntohs(*((u16*)(&ptr[4]))); |
| //printk("Got UDP pkt\n"); |
| if (started && dst_port == 5001) { |
| drop = 1; |
| if (first_pkt == 1) { |
| first_pkt = 0; |
| total_data = 0; |
| start_time = jiffies; |
| invalid_pkts = 0; |
| total_pkts = 0; |
| } |
| total_data += (udp_len+34); //add ip and ether hdrs |
| stop_time = jiffies; |
| total_pkts++; |
| } |
| else if(dst_port == 5002) { |
| drop = 1; |
| ptr += 8; //skip udp header |
| printk("SHIM START PORT len: %d data: 0x%x, start=%x, stop=%x\n",udp_len, ptr[0], start_time, stop_time); |
| if(cp_lkm_pm_test_find(ptr, udp_len, "START", 5)){ |
| printk("Got IPERF START\n"); |
| first_pkt = 1; |
| started = 1; |
| cp_lkm_wrapper_start_debug(); |
| } |
| else if (cp_lkm_pm_test_find(ptr, udp_len, "STOP", 4)) { |
| u32 delta_time = (stop_time - start_time)*1000/HZ; |
| u32 bits_per_sec = (total_data/delta_time)*8000; //in bytes per milisecond, need bits per second |
| delta_time -= 2; //iperf has 2 second delay waiting for an ack we won't send |
| started = 0; |
| printk("Got IPERF STOP: Total data: %u, Total pkts: %u, Total invalid: %u, Total time: %u msec, BitsPerSec: %u\n",total_data, total_pkts, invalid_pkts, delta_time,bits_per_sec); |
| cp_lkm_wrapper_stop_debug(); |
| } |
| } |
| } |
| } |
| return drop; |
| } |
| #endif |
| |
| // called in soft interrupt context - otherwise some protection around pm_net is required |
| //int num_ip_copies = 0; |
| //int num_eth_copies = 0; |
| //int num_pkts = 0; |
| //int num_iters = 0; |
| //int num_unaligned = 0; |
| static int cp_lkm_pm_net_recv(void *ctx, struct sk_buff *skb) |
| { |
| struct cp_lkm_pm_net *pm_net; |
| int err; |
| int recv_bytes; |
| struct sk_buff *skb_new; |
| int align = 0; //set to 1 to always send 4 byte aligned IP pkts to network stack |
| int pad = 20; //number of bytes to put on front of new skbs |
| |
| //DEBUG_INFO("%s()", __FUNCTION__); |
| if(NULL == ctx) { |
| dev_kfree_skb_any(skb); |
| return 0; |
| } |
| |
| //num_pkts++; |
| //num_iters++; |
| pm_net = (struct cp_lkm_pm_net *)ctx; |
| |
| //printk("%s() pm_net: %p\n", __FUNCTION__, pm_net); |
| |
| |
| skb->dev = pm_net->common.net_dev; |
| |
| switch(pm_net->common.type) { |
| case CP_LKM_PM_TYPE_ETHERNET_DHCP: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC_NOARP: |
| //this strips the ether header off the packet |
| skb->protocol = eth_type_trans(skb, pm_net->common.net_dev); |
| //Need IP hdr aligned for IP stack to avoid unaligned access interrupts |
| if(align && ((uintptr_t)(skb->data) & 0x3)) { |
| //num_eth_copies++; |
| skb_new = skb_copy_expand(skb, pad, 0, GFP_ATOMIC); |
| dev_kfree_skb_any(skb); |
| skb=skb_new; |
| } |
| if (!skb) { |
| // packet dropped |
| UPDATE_STATS(pm_net->common.edi->pm_stats64_ctx, rx_dropped, 1); |
| return -ENOMEM; |
| } |
| break; |
| |
| case CP_LKM_PM_TYPE_IP_DHCP: |
| case CP_LKM_PM_TYPE_IP_STATIC: |
| // Need to add ether header first for processing, then remove it. Need IP hdr aligned when done. |
| // |
| // Note: avoid the temptation to skip adding the ether header and doing manually what the call |
| // to eth_type_trans() does. We did that and it bit us (see Jira issue FW-16149) |
| // The kernel expects the ether header to be present in the skb buff even though the data ptr |
| // has been moved past it. Also, if the skb has been cloned, then we are dealing with an |
| // aggregated modem protocol (multiple pkts per skb), so we have to make a copy to guarantee |
| // our tmp ether header isn't written into the data space of the previous pkt from the set. |
| // |
| if((align && ((uintptr_t)(skb->data) & 0x3)) || (skb_headroom(skb) < ETH_HLEN) || skb_cloned(skb)){ |
| //printk("copy: align: %d, head: %d, cloned: %d, len: %d\n", ((uintptr_t)(skb->data) & 0x3), skb_headroom(skb), skb_cloned(skb), skb->len); |
| //num_ip_copies++; |
| skb_new = skb_copy_expand(skb, 16+pad, 0, GFP_ATOMIC); |
| dev_kfree_skb_any(skb); |
| skb=skb_new; |
| } |
| |
| if (!skb) { |
| // packet dropped |
| UPDATE_STATS(pm_net->common.edi->pm_stats64_ctx, rx_dropped, 1); |
| return -ENOMEM; |
| } |
| |
| if (0x60 == (skb->data[0] & 0xF0)) { //mask off version bits of first byte of IP packet to check for ip version |
| // set the hdr protocol type to IPV6 |
| pm_net->eth_hdr.h_proto = __constant_htons(ETH_P_IPV6); |
| } else { |
| // probably ipv4, but not explicitly checking |
| // set the hdr protocol type to IPV4 |
| pm_net->eth_hdr.h_proto = __constant_htons(ETH_P_IP); |
| } |
| memcpy(skb_push(skb, sizeof(struct ethhdr)), (unsigned char *)&pm_net->eth_hdr, sizeof(struct ethhdr)); |
| //this strips the ether hdr off the packet |
| skb->protocol = eth_type_trans(skb, pm_net->common.net_dev); |
| break; |
| |
| default: |
| DEBUG_INFO("%s() invalid protocol type: %d", __FUNCTION__, pm_net->common.type); |
| // packet dropped |
| UPDATE_STATS(pm_net->common.edi->pm_stats64_ctx, rx_errors, 1); |
| kfree(skb); |
| return NET_RX_DROP; |
| } |
| |
| recv_bytes = skb->len; |
| |
| //if (cp_lkm_pm_test(skb) == 1) { |
| // dev_kfree_skb_any(skb); |
| // return NET_RX_SUCCESS; |
| //} |
| |
| //if((int)(skb->data) & 0x3){ |
| //printk("Unaligned IP pkt!!!!!!!!!!!!\n"); |
| //num_unaligned++; |
| //} |
| |
| |
| //if(num_iters >= 10000) { |
| // num_iters = 0; |
| // printk("num_ip_copies: %d, num_eth_copies: %d, num_unaligned: %d, num_pkts: %d\n",num_ip_copies,num_eth_copies,num_unaligned,num_pkts); |
| //} |
| |
| netif_rx(skb); |
| err = NET_RX_SUCCESS; |
| |
| UPDATE_STATS(pm_net->common.edi->pm_stats64_ctx, rx_packets, 1); |
| UPDATE_STATS(pm_net->common.edi->pm_stats64_ctx, rx_bytes, recv_bytes); |
| |
| return 0; |
| } |
| |
| |
| static void cp_lkm_pm_net_get_hdr_size(void *ctx, int wrapper_hdr_size, int* hdr_size, int* hdr_offset) |
| { |
| struct cp_lkm_pm_net *pm_net; |
| int pad; |
| int tmp_size; |
| int pm_hdr = ETH_HLEN; |
| int pm_extra = 6; |
| |
| *hdr_size = 0; |
| *hdr_offset = 0; |
| |
| pm_net = (struct cp_lkm_pm_net *)ctx; |
| if(!pm_net) { |
| return; |
| } |
| //temp return here |
| //return; |
| |
| //calculate how much header space there is before the IP hdr. |
| //this is needed to align the IP hdr properly for optimal performance |
| switch(pm_net->common.type) { |
| case CP_LKM_PM_TYPE_ETHERNET_DHCP: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC_NOARP: |
| //pkts will need room for the wrapper header and the ether hdr. |
| //both headers will be present at the same time. |
| tmp_size = wrapper_hdr_size + pm_hdr + pm_extra; |
| pad = ((~tmp_size)+1)&0x3; //calculate padding needed for 4 byte boundary on alloc |
| *hdr_size = tmp_size + pad; |
| *hdr_offset = pad+pm_extra; |
| break; |
| |
| case CP_LKM_PM_TYPE_IP_DHCP: |
| case CP_LKM_PM_TYPE_IP_STATIC: |
| //pkts will need room for the wrapper header or the ether hdr |
| //both headers won't be present at the same time. The wrapper is present |
| //up through the USB side of the shim. We (the pm) add a temp ether header |
| //for processing after the wrapper header is removed |
| tmp_size = max(wrapper_hdr_size, pm_hdr+pm_extra); |
| pad = ((~tmp_size)+1)&0x3; //calculate padding needed for 4 byte boundary on alloc |
| *hdr_size = tmp_size + pad; |
| *hdr_offset = *hdr_size - wrapper_hdr_size; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| |
| static u32 cp_lkm_pm_net_get_link(struct net_device *dev) |
| { |
| struct cp_lkm_pm_net *pm_net; |
| |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| pm_net = netdev_priv(dev); |
| if(!pm_net) { |
| return 0; |
| } |
| return pm_net->common.attached; |
| } |
| |
| |
| #ifndef KERNEL_2_6_21 |
| static const struct net_device_ops cp_lkm_pm_net_device_ops = { |
| .ndo_open = cp_lkm_pm_net_open, |
| .ndo_start_xmit = cp_lkm_pm_net_xmit, |
| .ndo_stop = cp_lkm_pm_net_close, |
| .ndo_get_stats64 = cp_lkm_pm_get_stats64 |
| }; |
| #endif |
| |
| static const struct ethtool_ops cp_lkm_pm_net_ethtool_ops = { |
| .get_link = cp_lkm_pm_net_get_link, |
| }; |
| |
| static void cp_lkm_pm_net_setup(struct net_device *net_dev) |
| { |
| struct cp_lkm_pm_net *pm_net; |
| |
| DEBUG_INFO("%s()", __FUNCTION__); |
| pm_net = netdev_priv(net_dev); |
| ether_setup(net_dev); |
| |
| #ifdef KERNEL_2_6_21 |
| net_dev->open = cp_lkm_pm_net_open; |
| net_dev->hard_start_xmit = cp_lkm_pm_net_xmit; |
| net_dev->stop = cp_lkm_pm_net_close; |
| #else |
| net_dev->netdev_ops = &cp_lkm_pm_net_device_ops; |
| net_dev->needed_headroom = 48; |
| net_dev->needed_tailroom = 8; |
| #endif |
| |
| net_dev->ethtool_ops = &cp_lkm_pm_net_ethtool_ops; |
| |
| } |
| |
| static int cp_lkm_pm_net_attach(struct cp_lkm_pm_ctx *mgr, cp_lkm_pm_type_t type, int uid, char *name, unsigned char *mac) |
| { |
| int err; |
| struct cp_lkm_pm_net *pm_net; |
| struct net_device *net_dev; |
| unsigned long flags; |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17,0) |
| net_dev = alloc_netdev(sizeof(struct cp_lkm_pm_net), name, NET_NAME_UNKNOWN, cp_lkm_pm_net_setup); |
| #else |
| net_dev = alloc_netdev(sizeof(struct cp_lkm_pm_net), name, cp_lkm_pm_net_setup); |
| #endif |
| if (!net_dev) { |
| DEBUG_INFO("%s() alloc failed: %s", __FUNCTION__, name); |
| return -ENOMEM; |
| } |
| |
| pm_net= netdev_priv(net_dev); |
| |
| err = cp_lkm_common_init(&pm_net->common); |
| if (err) { |
| free_netdev(net_dev); |
| return err; |
| } |
| |
| pm_net->common.net_dev = net_dev; |
| pm_net->common.unique_id = uid; |
| pm_net->common.type = type; |
| pm_net->common.edi = NULL; |
| |
| //printk("%s(%p) pm-uid: %d, pm_net: %p\n", __FUNCTION__, mgr, uid, pm_net); |
| |
| switch (type) { |
| case CP_LKM_PM_TYPE_ETHERNET_DHCP: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC_NOARP: |
| if(!memcmp(mac, "\x00\x00\x00\x00\x00\x00", ETH_ALEN)) { |
| random_ether_addr(net_dev->dev_addr); |
| } else { |
| memcpy (net_dev->dev_addr, mac, ETH_ALEN); |
| } |
| |
| /////////////////////////Need to only do if driver says so. |
| if (type == CP_LKM_PM_TYPE_ETHERNET_STATIC_NOARP) { |
| net_dev->flags |= IFF_NOARP; |
| } |
| break; |
| case CP_LKM_PM_TYPE_IP_DHCP: |
| case CP_LKM_PM_TYPE_IP_STATIC: |
| // random addr for DHCP functionality |
| if(!memcmp(mac, "\x00\x00\x00\x00\x00\x00", ETH_ALEN) || !memcmp(mac, "\x00\x30\x44\x00\x00\x00", ETH_ALEN)) { |
| random_ether_addr(net_dev->dev_addr); |
| } else { |
| memcpy (net_dev->dev_addr, mac, ETH_ALEN); |
| } |
| |
| net_dev->flags |= IFF_NOARP; |
| memcpy(pm_net->eth_hdr.h_dest, net_dev->dev_addr, ETH_ALEN); |
| random_ether_addr(pm_net->eth_hdr.h_source); |
| break; |
| default: |
| DEBUG_INFO("%s() invalid protocol type: %d", __FUNCTION__, type); |
| cp_lkm_common_deinit(&pm_net->common); |
| free_netdev(net_dev); |
| return -EINVAL; |
| } |
| |
| DEBUG_INFO("%s register netdev", __FUNCTION__); |
| err = register_netdev(net_dev); |
| if (err < 0) { |
| DEBUG_INFO("%s netdev registration error", __FUNCTION__); |
| cp_lkm_common_deinit(&pm_net->common); |
| free_netdev(net_dev); |
| return err; |
| } |
| |
| netif_device_attach(pm_net->common.net_dev); |
| |
| netif_stop_queue(pm_net->common.net_dev); |
| |
| pm_net->common.attached = 1; |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| list_add(&pm_net->common.list, &mgr->pm_list); |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_net_detach(struct cp_lkm_pm_ctx *mgr, int uid) |
| { |
| |
| // find the object in the list |
| struct list_head *pos; |
| struct cp_lkm_pm_common *pm = NULL; |
| unsigned long flags; |
| |
| DEBUG_TRACE("%s(%p)", __FUNCTION__, mgr); |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| list_for_each(pos, &mgr->pm_list){ |
| struct cp_lkm_pm_common *pm_tmp = list_entry(pos, struct cp_lkm_pm_common, list); |
| if(pm_tmp->unique_id == uid) { |
| pm = pm_tmp; |
| break; |
| } |
| } |
| |
| if (!pm) { |
| // already detached |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| DEBUG_INFO("%s() already detached", __FUNCTION__); |
| return 0; |
| } |
| |
| // remove the object |
| list_del(&pm->list); |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| if (pm->attached) { |
| DEBUG_INFO("%s() detaching", __FUNCTION__); |
| netif_device_detach(pm->net_dev); |
| pm->attached = 0; |
| } |
| |
| unregister_netdev(pm->net_dev); |
| |
| // clean the filter list |
| cp_lkm_pm_filter_empty_list(pm); |
| |
| cp_lkm_common_deinit(pm); |
| free_netdev(pm->net_dev); // this also frees the pm since it was allocated as part of the net_dev |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_net_activate(struct cp_lkm_pm_ctx *mgr, int uid, bool activate) |
| { |
| // find the object in the list |
| struct list_head *pos; |
| struct cp_lkm_pm_common *pm = NULL; |
| unsigned long flags; |
| //printk("%s(%p) activate: %d\n", __FUNCTION__, mgr, activate); |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| list_for_each(pos, &mgr->pm_list){ |
| struct cp_lkm_pm_common *pm_tmp = list_entry(pos, struct cp_lkm_pm_common, list); |
| if(pm_tmp->unique_id == uid) { |
| pm = pm_tmp; |
| break; |
| } |
| } |
| |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| if (!pm) { |
| // couldn't find object - already unplugged |
| DEBUG_INFO("%s() already unplugged", __FUNCTION__); |
| return 0; |
| } |
| |
| if (activate) { |
| //netif_start_queue(pm->net_dev); |
| if (pm->edi) { |
| pm->edi->pm_recv_ctx = pm; |
| } |
| netif_wake_queue(pm->net_dev); |
| } else { |
| netif_stop_queue(pm->net_dev); |
| if (pm->edi) { |
| pm->edi->pm_recv_ctx = NULL; |
| //printk("pm_recv_ctx null\n"); |
| } |
| |
| // remove the filters - will be added back in before activate |
| cp_lkm_pm_filter_empty_list(pm); |
| } |
| |
| return 0; |
| } |
| |
| int cp_lkm_pm_net_pause(void *ctx) |
| { |
| struct cp_lkm_pm_common* pm = (struct cp_lkm_pm_common *)ctx; |
| if(!ctx) { |
| return 0; |
| } |
| netif_stop_queue(pm->net_dev); |
| return 0; |
| |
| } |
| int cp_lkm_pm_net_resume(void *ctx) |
| { |
| struct cp_lkm_pm_common* pm = (struct cp_lkm_pm_common *)ctx; |
| if(!ctx) { |
| return 0; |
| } |
| //netif_start_queue(pm->net_dev); |
| netif_wake_queue(pm->net_dev); |
| return 0; |
| } |
| |
| |
| /******************************* kernel module PPP/tty PM functionality **********************************/ |
| struct cp_lkm_pm_ppp { |
| struct cp_lkm_pm_common common; |
| u8 *no_carrier_ptr; |
| bool in_frame; |
| |
| struct tty_struct *tty; // pointer to the tty for this device |
| int minor; |
| int open_count; |
| }; |
| |
| #define CP_TTY_MINORS 10 |
| #define CP_TTY_DEVICE_NAME "ttyCP" |
| |
| #define PPP_MGR_NO_CARRIER "NO CARRIER" |
| #define PPP_FLAG 0x7E |
| |
| static struct cp_lkm_pm_ppp *cp_lkm_pm_ppp_table[CP_TTY_MINORS]; |
| static struct tty_driver *cp_lkm_pm_tty_driver = NULL; |
| static struct tty_port cp_lkm_pm_tty_port[CP_TTY_MINORS]; |
| |
| static void cp_lkm_pm_ppp_finalize(void *arg) |
| { |
| struct cp_lkm_pm_ppp *pm_ppp = (struct cp_lkm_pm_ppp *)arg; |
| tty_unregister_device(cp_lkm_pm_tty_driver, pm_ppp->minor); |
| cp_lkm_pm_ppp_table[pm_ppp->minor] = NULL; |
| if (pm_ppp->common.edi) { |
| pm_ppp->common.edi = NULL; |
| } |
| // clean the filter list |
| cp_lkm_pm_filter_empty_list(&pm_ppp->common); |
| } |
| |
| static int cp_lkm_pm_ppp_attach(struct cp_lkm_pm_ctx *mgr, cp_lkm_pm_type_t type, int uid, char *name) |
| { |
| int minor; |
| int err; |
| unsigned long flags; |
| struct cp_lkm_pm_ppp *pm_ppp; |
| |
| DEBUG_INFO("%s(%p)", __FUNCTION__, mgr); |
| |
| //printk("%s() uid: %d, type: %d\n", __FUNCTION__, uid, type); |
| |
| // find an empty minor device slot and register |
| for (minor = 0; minor < CP_TTY_MINORS && cp_lkm_pm_ppp_table[minor]; minor++); |
| |
| if (minor == CP_TTY_MINORS) { |
| DEBUG_WARN("%s(%p) - out of devices", __FUNCTION__, mgr); |
| return -ENODEV; |
| } |
| |
| if (!(pm_ppp = memref_alloc_and_zero(sizeof(struct cp_lkm_pm_ppp), cp_lkm_pm_ppp_finalize))) { |
| DEBUG_WARN("%s(%p) - no memory", __FUNCTION__, mgr); |
| return -ENOMEM; |
| } |
| |
| err = cp_lkm_common_init(&pm_ppp->common); |
| if (err) { |
| return -ENOMEM; |
| } |
| pm_ppp->common.type = type; |
| pm_ppp->common.unique_id = uid; |
| |
| pm_ppp->no_carrier_ptr = PPP_MGR_NO_CARRIER; |
| |
| pm_ppp->minor = minor; |
| |
| cp_lkm_pm_ppp_table[minor] = pm_ppp; |
| sprintf(name, "%s%d", CP_TTY_DEVICE_NAME, minor); |
| |
| //printk("%s(%p) attached\n", __FUNCTION__, &pm_ppp->common); |
| pm_ppp->common.attached = 1; |
| pm_ppp->open_count = 0; |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| list_add(&pm_ppp->common.list, &mgr->pm_list); |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| tty_port_register_device(&cp_lkm_pm_tty_port[minor], cp_lkm_pm_tty_driver, minor, NULL); |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_ppp_detach(struct cp_lkm_pm_ctx *mgr, int uid) |
| { |
| |
| // find the object in the list |
| struct list_head *pos; |
| struct cp_lkm_pm_common *pm = NULL; |
| struct cp_lkm_pm_ppp *pm_ppp; |
| unsigned long flags; |
| |
| DEBUG_INFO("%s(%p)", __FUNCTION__, mgr); |
| //printk("%s() uid: %d\n", __FUNCTION__, uid); |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| list_for_each(pos, &mgr->pm_list){ |
| struct cp_lkm_pm_common *pm_tmp = list_entry(pos, struct cp_lkm_pm_common, list); |
| if(pm_tmp->unique_id == uid) { |
| pm = pm_tmp; |
| break; |
| } |
| } |
| |
| if (!pm) { |
| // already detached |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| DEBUG_INFO("%s() already detached", __FUNCTION__); |
| return 0; |
| } |
| |
| // remove the object |
| list_del(&pm->list); |
| |
| pm_ppp = (struct cp_lkm_pm_ppp *)pm; |
| |
| //printk("%s() !attached\n", __FUNCTION__); |
| pm->attached = 0; |
| |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| // clean the filter list |
| cp_lkm_pm_filter_empty_list(pm); |
| |
| cp_lkm_common_deinit(pm); |
| |
| memref_deref(pm_ppp); |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_ppp_activate(struct cp_lkm_pm_ctx *mgr, int uid, bool activate) |
| { |
| // find the object in the list |
| struct list_head *pos; |
| struct cp_lkm_pm_common *pm = NULL; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| list_for_each(pos, &mgr->pm_list){ |
| struct cp_lkm_pm_common *pm_tmp = list_entry(pos, struct cp_lkm_pm_common, list); |
| if(pm_tmp->unique_id == uid) { |
| pm = pm_tmp; |
| break; |
| } |
| } |
| |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| if (!pm) { |
| // already detached |
| DEBUG_INFO("%s() already detached", __FUNCTION__); |
| return 0; |
| } |
| //printk("%s(%p) activate: %d, attached: %d\n", __FUNCTION__, pm, activate, pm->attached); |
| |
| if (activate) { |
| if (pm->edi) { |
| pm->edi->pm_recv_ctx = pm; |
| } |
| } else { |
| if (pm->edi) { |
| pm->edi->pm_recv_ctx = NULL; |
| //printk("pm_recv_ctx null\n"); |
| } |
| // clean the filter list |
| cp_lkm_pm_filter_empty_list(pm); |
| } |
| |
| return 0; |
| } |
| |
| |
| static int cp_lkm_pm_tty_open(struct tty_struct * tty, struct file * filp) |
| { |
| struct cp_lkm_pm_ppp *pm_ppp; |
| int index; |
| unsigned long flags; |
| |
| DEBUG_INFO("%s()", __FUNCTION__); |
| |
| index = tty->index; |
| |
| // get the pm_ppp associated with this tty pointer |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| pm_ppp = cp_lkm_pm_ppp_table[index]; |
| if (!pm_ppp /*|| tty->driver_data */|| !pm_ppp->common.attached) { |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| return -EINVAL; |
| } |
| |
| if (pm_ppp->open_count++) { |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| return 0; |
| } |
| |
| memref_ref(pm_ppp); |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| // save our structure within the tty structure |
| tty->driver_data = pm_ppp; |
| pm_ppp->tty = tty; |
| |
| // XXX 3.10 hack |
| //tty->low_latency = 0; |
| |
| return 0; |
| } |
| |
| static void cp_lkm_pm_tty_close(struct tty_struct * tty, struct file * filp) |
| { |
| struct cp_lkm_pm_ppp *pm_ppp; |
| unsigned long flags; |
| |
| DEBUG_INFO("%s()", __FUNCTION__); |
| |
| pm_ppp = tty->driver_data; |
| if(!pm_ppp) { |
| return; |
| } |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| if (--pm_ppp->open_count) { |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| return; |
| } |
| tty->driver_data = NULL; |
| pm_ppp->tty = NULL; |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| memref_deref(pm_ppp); |
| } |
| static bool cp_lkm_pm_ppp_check_match(struct cp_lkm_pm_ppp *pm_ppp, u8 ch) |
| { |
| if (*(pm_ppp->no_carrier_ptr) == ch) { |
| // character match - advance to next character |
| pm_ppp->no_carrier_ptr++; |
| if (! *(pm_ppp->no_carrier_ptr)) { |
| // end of no carrier string - found oob no carrier |
| return true; |
| } |
| return false; |
| } |
| // characters don't match |
| if (pm_ppp->no_carrier_ptr != (u8 *)PPP_MGR_NO_CARRIER) { |
| // characters don't match - start over |
| pm_ppp->no_carrier_ptr = (u8 *)PPP_MGR_NO_CARRIER; |
| // check not matching character against first character of no carrier - 1 level of recursion |
| return cp_lkm_pm_ppp_check_match(pm_ppp, ch); |
| } |
| |
| return false; |
| } |
| |
| static bool cp_lkm_pm_ppp_is_no_carrier(struct cp_lkm_pm_ppp *pm_ppp, struct sk_buff *skb) |
| { |
| // search thru skb for data between frame markers for NO CARRIER |
| bool no_carrier = false; |
| unsigned int len = skb->len; |
| u8 *pos = skb->data; |
| |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| |
| while (len--) { |
| if (PPP_FLAG == (*pos)) { |
| pm_ppp->in_frame = !pm_ppp->in_frame; |
| } else if (!pm_ppp->in_frame) { |
| // look for match |
| no_carrier = cp_lkm_pm_ppp_check_match(pm_ppp, *pos); |
| if (no_carrier) { |
| DEBUG_INFO("%s() found no carrier", __FUNCTION__); |
| return true; |
| } |
| } else { |
| pm_ppp->no_carrier_ptr = PPP_MGR_NO_CARRIER; |
| } |
| |
| pos++; |
| } |
| |
| return false; |
| } |
| |
| static void cp_lkm_pm_ppp_get_hdr_size(void *ctx, int wrapper_hdr_size, int* hdr_size, int* hdr_offset) |
| { |
| *hdr_size = 0; |
| *hdr_offset = 0; |
| } |
| |
| // called in soft interrupt context |
| static int cp_lkm_pm_ppp_recv(void *ctx, struct sk_buff *skb) |
| { |
| #ifdef KERNEL_2_6_21 |
| int size; |
| #endif |
| struct cp_lkm_pm_ppp *pm_ppp; |
| bool oob_no_carrier; |
| |
| if(NULL == ctx || !skb->len) { |
| DEBUG_INFO("%s() - null ctx - dropped", __FUNCTION__); |
| goto done; |
| } |
| |
| pm_ppp = (struct cp_lkm_pm_ppp *)ctx; |
| |
| if (!pm_ppp) { |
| DEBUG_INFO("%s() - NULL pm_ppp - dropped", __FUNCTION__); |
| goto done; |
| } |
| |
| // check for OOB NO CARRIER - signal up through file descriptor |
| oob_no_carrier = cp_lkm_pm_ppp_is_no_carrier(pm_ppp, skb); |
| if (oob_no_carrier) { |
| struct cp_lkm_msg_hdr hdr; |
| |
| DEBUG_INFO("%s() - posting no carrier", __FUNCTION__); |
| memset(&hdr,0,sizeof(hdr)); |
| hdr.instance_id = pm_ppp->common.unique_id; |
| hdr.cmd = CP_LKM_PM_LINK_DOWN; |
| hdr.status = CP_LKM_STATUS_OK; |
| hdr.len = 0; |
| |
| LOG("Received NO CARRIER\n"); |
| DEBUG_INFO("%s() - posting link down", __FUNCTION__); |
| cp_lkm_post_message(&cp_lkm_pm_mgr.common, &hdr, NULL); |
| |
| goto done; |
| } |
| |
| if (!pm_ppp->tty || !pm_ppp->tty->driver_data) { |
| DEBUG_INFO("%s() - not setup - dropped", __FUNCTION__); |
| goto done; |
| } |
| |
| #ifdef KERNEL_2_6_21 |
| size = tty_buffer_request_room(pm_ppp->tty, skb->len); |
| if(size < skb->len) { |
| // dropped data - or we need to queue for later |
| DEBUG_WARN("%s() - dropping network data", __FUNCTION__); |
| goto done; |
| } |
| #endif |
| |
| tty_insert_flip_string(pm_ppp->tty->port, skb->data, skb->len); |
| tty_flip_buffer_push(pm_ppp->tty->port); |
| |
| done: |
| dev_kfree_skb_any(skb); |
| return 0; |
| } |
| |
| // this can be called from interrupt thread or normal kernel thread |
| static int cp_lkm_pm_tty_write(struct tty_struct * tty, const unsigned char *buf, int count) |
| { |
| struct cp_lkm_pm_ppp *pm_ppp; |
| struct sk_buff *skb; |
| int link_res; |
| int retval = count; |
| |
| if (!count) { |
| //printk("%s() !count \n", __FUNCTION__); |
| return 0; |
| } |
| |
| pm_ppp = (struct cp_lkm_pm_ppp *)tty->driver_data; |
| |
| if (!pm_ppp) { |
| //printk("%s() !pm_ppp \n", __FUNCTION__); |
| return -EINVAL; |
| } |
| |
| //printk("%s(%p) id:%d, attached: %d\n", __FUNCTION__, &pm_ppp->common, pm_ppp->common.unique_id, pm_ppp->common.attached); |
| |
| //see if we can grab the link lock, if not, we are either bringing up or taking down the link between USB and PM, so not safe to proceed |
| link_res = cp_lkm_common_inc_link_lock(&pm_ppp->common); |
| if(link_res < 0) { |
| //printk("%s() !link \n", __FUNCTION__); |
| return 0; |
| } |
| |
| /* Drop packet if interface is not attached */ |
| if (!pm_ppp->common.attached){ |
| retval = 0; |
| //printk("%s() !attached: %d \n", __FUNCTION__, pm_ppp->common.attached); |
| goto drop; |
| } |
| |
| if (!(pm_ppp->common.edi) || !(pm_ppp->common.edi->usb_send) || !(pm_ppp->common.edi->usb_send_ctx)) { |
| retval = 0; |
| //printk("%s() !edi \n", __FUNCTION__); |
| goto drop; |
| } |
| |
| //benk check for enabled filter - send in buffer pointer to ip header |
| |
| // alloc skb to send |
| if ((skb = alloc_skb (count, GFP_ATOMIC)) == NULL) { |
| retval = -ENOMEM; |
| goto pm_tty_write_done; |
| } |
| |
| memcpy(skb->data, buf, count); |
| skb->len = count; |
| skb_set_tail_pointer(skb, skb->len); |
| |
| // send data to USB module |
| pm_ppp->common.edi->usb_send(pm_ppp->common.edi->usb_send_ctx, skb); |
| retval = count; |
| goto pm_tty_write_done; |
| |
| drop: |
| pm_tty_write_done: |
| cp_lkm_common_dec_link_lock(&pm_ppp->common); |
| //printk("%s() done\n", __FUNCTION__); |
| |
| return retval; |
| } |
| |
| static int cp_lkm_pm_tty_write_room(struct tty_struct *tty) |
| { |
| struct cp_lkm_pm_ppp *pm_ppp; |
| |
| DEBUG_INFO("%s()", __FUNCTION__); |
| |
| pm_ppp = (struct cp_lkm_pm_ppp *)tty->driver_data; |
| |
| if (!pm_ppp) { |
| return -EINVAL; |
| } |
| |
| return 2048; |
| } |
| |
| static int cp_lkm_pm_tty_chars_in_buffer(struct tty_struct *tty) |
| { |
| struct cp_lkm_pm_ppp *pm_ppp; |
| |
| DEBUG_INFO("%s()", __FUNCTION__); |
| |
| pm_ppp = (struct cp_lkm_pm_ppp *)tty->driver_data; |
| |
| if (!pm_ppp) { |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void cp_lkm_pm_tty_set_termios(struct tty_struct *tty, struct ktermios * old) |
| { |
| DEBUG_INFO("%s()", __FUNCTION__); |
| |
| } |
| |
| #ifdef KERNEL_2_6_21 |
| static int cp_lkm_pm_tty_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) |
| #else |
| static int cp_lkm_pm_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) |
| #endif |
| { |
| struct cp_lkm_pm_ppp *pm_ppp; |
| |
| DEBUG_TRACE("%s(%x)", __FUNCTION__, cmd); |
| |
| pm_ppp = (struct cp_lkm_pm_ppp *)tty->driver_data; |
| |
| if (!pm_ppp) { |
| return -EINVAL; |
| } |
| |
| return -ENOIOCTLCMD; |
| } |
| |
| static struct tty_operations cp_lkm_pm_tty_ops = { |
| .open = cp_lkm_pm_tty_open, |
| .close = cp_lkm_pm_tty_close, |
| .write = cp_lkm_pm_tty_write, |
| .write_room = cp_lkm_pm_tty_write_room, |
| .chars_in_buffer = cp_lkm_pm_tty_chars_in_buffer, |
| .set_termios = cp_lkm_pm_tty_set_termios, |
| .ioctl = cp_lkm_pm_tty_ioctl |
| |
| /* |
| .throttle = acm_tty_throttle, |
| .unthrottle = acm_tty_unthrottle, |
| */ |
| }; |
| |
| static int cp_lkm_pm_tty_init(void) |
| { |
| int retval; |
| int i; |
| |
| for(i = 0; i < CP_TTY_MINORS; i++) { |
| tty_port_init(&cp_lkm_pm_tty_port[i]); |
| } |
| |
| cp_lkm_pm_tty_driver = alloc_tty_driver(CP_TTY_MINORS); |
| if (!cp_lkm_pm_tty_driver) { |
| return -ENOMEM; |
| } |
| |
| // initialize the tty driver |
| cp_lkm_pm_tty_driver->owner = THIS_MODULE; |
| cp_lkm_pm_tty_driver->driver_name = "cptty"; |
| cp_lkm_pm_tty_driver->name = CP_TTY_DEVICE_NAME; |
| cp_lkm_pm_tty_driver->major = 0; // dynamically assign major number |
| cp_lkm_pm_tty_driver->minor_start = 0, |
| cp_lkm_pm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; |
| cp_lkm_pm_tty_driver->subtype = SERIAL_TYPE_NORMAL; |
| cp_lkm_pm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; |
| cp_lkm_pm_tty_driver->init_termios = tty_std_termios; |
| tty_set_operations(cp_lkm_pm_tty_driver, &cp_lkm_pm_tty_ops); |
| |
| retval = tty_register_driver(cp_lkm_pm_tty_driver); |
| if (retval) { |
| DEBUG_ERROR("%s() failed to register cp tty driver", __FUNCTION__); |
| put_tty_driver(cp_lkm_pm_tty_driver); |
| for(i = 0; i < CP_TTY_MINORS; i++) { |
| tty_port_destroy(&cp_lkm_pm_tty_port[i]); |
| } |
| } |
| return retval; |
| |
| } |
| |
| static void cp_lkm_pm_tty_cleanup(void) |
| { |
| int i; |
| if (cp_lkm_pm_tty_driver) { |
| tty_unregister_driver(cp_lkm_pm_tty_driver); |
| put_tty_driver(cp_lkm_pm_tty_driver); |
| for(i = 0; i < CP_TTY_MINORS; i++) { |
| tty_port_destroy(&cp_lkm_pm_tty_port[i]); |
| } |
| cp_lkm_pm_tty_driver = NULL; |
| } |
| } |
| |
| /******************************* kernel module PM mgr functionality **********************************/ |
| |
| |
| static int cp_lkm_pm_open(struct cp_lkm_common_ctx *ctx); |
| static int cp_lkm_pm_close(struct cp_lkm_common_ctx *ctx); |
| static int cp_lkm_pm_handle_msg(struct cp_lkm_common_ctx *ctx, struct cp_lkm_msg_hdr *hdr, struct sk_buff *skb); |
| static int cp_lkm_pm_handle_ioctl(struct cp_lkm_common_ctx *ctx, int cmd, void *k_argp); |
| |
| |
| static int cp_lkm_pm_init(void) |
| { |
| DEBUG_INFO("%s()", __FUNCTION__); |
| |
| memset(&cp_lkm_pm_mgr, 0x00, sizeof(struct cp_lkm_pm_ctx)); |
| cp_lkm_pm_mgr.common.open = cp_lkm_pm_open; |
| cp_lkm_pm_mgr.common.close = cp_lkm_pm_close; |
| cp_lkm_pm_mgr.common.handle_msg = cp_lkm_pm_handle_msg; |
| cp_lkm_pm_mgr.common.handle_ioctl = cp_lkm_pm_handle_ioctl; |
| INIT_LIST_HEAD(&cp_lkm_pm_mgr.pm_list); |
| spin_lock_init(&cp_lkm_pm_mgr.pm_list_lock); |
| |
| cp_lkm_common_ctx_init(&cp_lkm_pm_mgr.common); |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_cleanup(void) |
| { |
| struct cp_lkm_pm_common *pmi; |
| struct list_head *entry, *tmp; |
| unsigned long flags; |
| |
| DEBUG_INFO("%s()", __FUNCTION__); |
| |
| // clean up msg list |
| cp_lkm_cleanup_msg_list(&cp_lkm_pm_mgr.common); |
| |
| // cleanup any PM in list |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| list_for_each_safe(entry, tmp, &cp_lkm_pm_mgr.pm_list) { |
| pmi = list_entry(entry, struct cp_lkm_pm_common, list); |
| if (pmi->edi) { |
| pmi->edi->pm_recv_ctx = NULL; |
| //printk("pm_recv_ctx null\n"); |
| pmi->edi->pm_stats64_ctx = NULL; |
| pmi->edi = NULL; |
| } |
| list_del(&pmi->list); |
| // clean the filter list |
| cp_lkm_pm_filter_empty_list(pmi); |
| |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| if (pmi->net_dev) { |
| // network device |
| cp_lkm_common_deinit(pmi); |
| unregister_netdev(pmi->net_dev); |
| free_netdev(pmi->net_dev); // this also frees the pmi since it was allocated as part of the net_dev |
| } else { |
| // tty device |
| memref_deref(pmi); |
| } |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| } |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_open(struct cp_lkm_common_ctx *ctx) |
| { |
| // struct cp_lkm_pm_ctx *pm_mgr; |
| |
| DEBUG_INFO("%s(%p)", __FUNCTION__, ctx); |
| |
| // pm_mgr = (struct cp_lkm_pm_ctx *)ctx; |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_close(struct cp_lkm_common_ctx *ctx) |
| { |
| //struct cp_lkm_pm_ctx *pm_mgr = (struct cp_lkm_pm_ctx *)ctx; |
| //struct cp_lkm_pm_common *pm_tmp = NULL; |
| //struct list_head *entry, *tmp; |
| //unsigned long flags; |
| |
| LOG("%s() called unexpectedly.", __FUNCTION__); |
| |
| //NOTE: catkin 10/11/2019 - Close is only called in our system if the modem stack crashes. This means |
| // things are in a bad state and the router will be rebooting. We decided not |
| // to clean things up here because close code on usb side got into an infinite loop |
| // and prevented the router from rebooting. Revisit if close ever becomes a normal event. |
| |
| /* |
| spin_lock_irqsave(&pm_mgr->pm_list_lock, flags); |
| |
| list_for_each_safe(entry, tmp, &pm_mgr->pm_list) { |
| pm_tmp = list_entry(entry, struct cp_lkm_pm_common, list); |
| spin_unlock_irqrestore(&pm_mgr->pm_list_lock, flags); |
| |
| // call detach to clean up network interface |
| if (CP_LKM_PM_TYPE_PPP_CLIENT == pm_tmp->type || CP_LKM_PM_TYPE_PPP_SERVER == pm_tmp->type) { |
| cp_lkm_pm_ppp_detach(pm_mgr, pm_tmp->unique_id); |
| } else { |
| cp_lkm_pm_net_detach(pm_mgr, pm_tmp->unique_id); |
| } |
| } |
| |
| spin_unlock_irqrestore(&pm_mgr->pm_list_lock, flags); |
| |
| cp_lkm_cleanup_msg_list(ctx); |
| */ |
| return 0; |
| } |
| |
| static int cp_lkm_pm_handle_msg(struct cp_lkm_common_ctx *ctx, struct cp_lkm_msg_hdr *hdr, struct sk_buff *skb) |
| { |
| struct cp_lkm_pm_ctx *pm_mgr; |
| |
| //printk("%s(%p)\n", __FUNCTION__, ctx); |
| |
| pm_mgr = (struct cp_lkm_pm_ctx *)ctx; |
| |
| |
| // how to write back response with common function? |
| if (skb) { |
| kfree(skb); |
| } |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_add_filter(struct cp_lkm_pm_ctx *mgr, int uid, struct cp_lkm_pm_filter *filter) |
| { |
| // find the object in the list |
| struct list_head *pos; |
| struct cp_lkm_pm_common *pm = NULL; |
| unsigned long flags; |
| struct cp_lkm_pm_filter *new_filter; |
| |
| DEBUG_TRACE("%s(%p)", __FUNCTION__, mgr); |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| list_for_each(pos, &mgr->pm_list){ |
| struct cp_lkm_pm_common *pm_tmp = list_entry(pos, struct cp_lkm_pm_common, list); |
| if(pm_tmp->unique_id == uid) { |
| pm = pm_tmp; |
| break; |
| } |
| } |
| |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| if (!pm) { |
| DEBUG_WARN("%s() pm not attached", __FUNCTION__); |
| return -ENODEV; |
| } |
| |
| new_filter = kmalloc(sizeof(struct cp_lkm_pm_filter), GFP_ATOMIC); |
| if (!new_filter) { |
| DEBUG_WARN("%s() - failed to alloc filter\n", __FUNCTION__); |
| return -1; |
| } |
| |
| memcpy(new_filter, filter, sizeof(struct cp_lkm_pm_filter)); |
| INIT_LIST_HEAD(&new_filter->list); |
| |
| list_add_tail(&new_filter->list, &pm->filter_list); |
| |
| return 0; |
| } |
| |
| static int cp_lkm_pm_handle_ioctl(struct cp_lkm_common_ctx *ctx, int cmd, void *k_argp) |
| { |
| struct cp_lkm_pm_ctx *pm_mgr; |
| int result = 0; |
| struct cp_lkm_pm_attach_ioctl *attach_params; |
| struct cp_lkm_pm_detach_ioctl *detach_params; |
| struct cp_lkm_pm_activate_deactivate_ioctl *activate_params; |
| struct cp_lkm_pm_add_filter_ioctl *filter_params; |
| |
| char name[CP_LKM_MAX_IF_NAME]; |
| unsigned long not_copied; |
| |
| //printk("%s(%p) cmd:%d\n", __FUNCTION__, ctx, _IOC_NR(cmd)); |
| |
| pm_mgr = (struct cp_lkm_pm_ctx *)ctx; |
| |
| switch (cmd) { |
| case CP_LKM_IOCTL_PM_ATTACH: |
| attach_params = (struct cp_lkm_pm_attach_ioctl *)k_argp; |
| not_copied = copy_from_user(name, attach_params->name, CP_LKM_MAX_IF_NAME); |
| if (not_copied) { |
| return -ENOMEM; |
| } |
| DEBUG_INFO("%s(%s) attach", __FUNCTION__, name); |
| switch(attach_params->type) { |
| case CP_LKM_PM_TYPE_PPP_CLIENT: |
| case CP_LKM_PM_TYPE_PPP_SERVER: |
| result = cp_lkm_pm_ppp_attach(pm_mgr, attach_params->type, attach_params->uid, name); |
| if (!result) { |
| not_copied = copy_to_user(attach_params->name, name, CP_LKM_MAX_IF_NAME); |
| if (not_copied) { |
| return -ENOMEM; |
| } |
| } |
| break; |
| case CP_LKM_PM_TYPE_ETHERNET_DHCP: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC_NOARP: |
| case CP_LKM_PM_TYPE_IP_STATIC: |
| case CP_LKM_PM_TYPE_IP_DHCP: |
| result = cp_lkm_pm_net_attach(pm_mgr, attach_params->type, attach_params->uid, name, attach_params->mac); |
| break; |
| default: |
| result = -ENOTSUPP; |
| break; |
| } |
| break; |
| case CP_LKM_IOCTL_PM_DETACH: |
| detach_params = (struct cp_lkm_pm_detach_ioctl *)k_argp; |
| DEBUG_INFO("%s() detach uid:%d", __FUNCTION__, detach_params->uid); |
| switch(detach_params->type) { |
| case CP_LKM_PM_TYPE_PPP_CLIENT: |
| case CP_LKM_PM_TYPE_PPP_SERVER: |
| result = cp_lkm_pm_ppp_detach(pm_mgr, detach_params->uid); |
| break; |
| case CP_LKM_PM_TYPE_ETHERNET_DHCP: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC_NOARP: |
| case CP_LKM_PM_TYPE_IP_STATIC: |
| case CP_LKM_PM_TYPE_IP_DHCP: |
| result = cp_lkm_pm_net_detach(pm_mgr, detach_params->uid); |
| break; |
| default: |
| result = -ENOTSUPP; |
| break; |
| } |
| break; |
| case CP_LKM_IOCTL_PM_ACTIVATE: |
| activate_params = (struct cp_lkm_pm_activate_deactivate_ioctl *)k_argp; |
| switch(activate_params->type) { |
| case CP_LKM_PM_TYPE_PPP_CLIENT: |
| case CP_LKM_PM_TYPE_PPP_SERVER: |
| result = cp_lkm_pm_ppp_activate(pm_mgr, activate_params->uid, true); |
| break; |
| case CP_LKM_PM_TYPE_ETHERNET_DHCP: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC_NOARP: |
| case CP_LKM_PM_TYPE_IP_STATIC: |
| case CP_LKM_PM_TYPE_IP_DHCP: |
| result = cp_lkm_pm_net_activate(pm_mgr, activate_params->uid, true); |
| break; |
| default: |
| result = -ENOTSUPP; |
| break; |
| } |
| break; |
| case CP_LKM_IOCTL_PM_DEACTIVATE: |
| activate_params = (struct cp_lkm_pm_activate_deactivate_ioctl *)k_argp; |
| switch(activate_params->type) { |
| case CP_LKM_PM_TYPE_PPP_CLIENT: |
| case CP_LKM_PM_TYPE_PPP_SERVER: |
| result = cp_lkm_pm_ppp_activate(pm_mgr, activate_params->uid, false); |
| break; |
| case CP_LKM_PM_TYPE_ETHERNET_DHCP: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC: |
| case CP_LKM_PM_TYPE_ETHERNET_STATIC_NOARP: |
| case CP_LKM_PM_TYPE_IP_STATIC: |
| case CP_LKM_PM_TYPE_IP_DHCP: |
| result = cp_lkm_pm_net_activate(pm_mgr, activate_params->uid, false); |
| break; |
| default: |
| result = -ENOTSUPP; |
| break; |
| } |
| break; |
| case CP_LKM_IOCTL_PM_ADD_FILTER: |
| filter_params = (struct cp_lkm_pm_add_filter_ioctl *)k_argp; |
| result = cp_lkm_pm_add_filter(pm_mgr, filter_params->uid, &filter_params->filter); |
| break; |
| default: |
| break; |
| } |
| |
| return result; |
| } |
| |
| static bool cp_lkm_pm_usb_do_link_lock(void* ctx1, void* ctx2) |
| { |
| struct cp_lkm_pm_common *pm = (struct cp_lkm_pm_common*)ctx1; |
| bool done = false; |
| unsigned long flags; |
| // grab the lock and set the link_count. The link_count is used to keep send and poll from |
| // being called over to the USB layer while we are mucking with the send and poll pointers |
| spin_lock_irqsave(&pm->pm_link_lock, flags); |
| if(pm->pm_link_count <= 0) { |
| pm->pm_link_count = -1; |
| done = true; |
| } |
| spin_unlock_irqrestore(&pm->pm_link_lock, flags); |
| |
| return done; |
| } |
| |
| // This function changes the shared edi pointers. |
| // !!!It is the only function in the pm that is permitted to change edi function pointers!!! |
| // Other functions can change the ctxt pointers |
| static int cp_lkm_pm_usb_link(struct cp_lkm_edi *edi, int pm_unique_id, int link) |
| { |
| struct list_head *pos; |
| struct cp_lkm_pm_common *pm = NULL; |
| unsigned long flags; |
| struct cp_lkm_edi *tmp_edi; |
| |
| spin_lock_irqsave(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| list_for_each(pos, &cp_lkm_pm_mgr.pm_list){ |
| struct cp_lkm_pm_common *pm_tmp = list_entry(pos, struct cp_lkm_pm_common, list); |
| if(pm_tmp->unique_id == pm_unique_id) { |
| pm = pm_tmp; |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&cp_lkm_pm_mgr.pm_list_lock, flags); |
| |
| if (!pm) { |
| // couldn't find object |
| //printk("%s() unable to find protocol manager with id:%d\n", __FUNCTION__, pm_unique_id); |
| return -EINVAL; |
| } |
| |
| //printk("%s() pm_net: %p\n", __FUNCTION__, pm); |
| |
| // grab the lock and set the link_count. The link_count is used to keep send and poll from |
| // being called over to the USB layer while we are mucking with the send and poll pointers |
| cp_lkm_do_or_die(pm, NULL, cp_lkm_pm_usb_do_link_lock, CP_LKM_TIMEOUT_MS, CP_LKM_ITER, "Failed to grab cp pm lock"); |
| |
| //printk("%s() pm: %p, attached: %d, pm_type: %d\n", __FUNCTION__, pm, pm->attached,pm->type); |
| tmp_edi = pm->edi; |
| pm->edi = NULL; |
| if (link) { |
| if (tmp_edi) { |
| // already linked - unlink from previous edi |
| // just a precaution, should never happen |
| tmp_edi->pm_recv = NULL; |
| tmp_edi->pm_recv_ctx = NULL; |
| tmp_edi->pm_get_hdr_size = NULL; |
| |
| //printk("pm_recv_ctx null\n"); |
| tmp_edi->pm_send_pause = NULL; |
| tmp_edi->pm_send_resume = NULL; |
| |
| tmp_edi->pm_stats64_ctx = NULL; |
| |
| //pm->edi = NULL; |
| } |
| |
| tmp_edi = edi; |
| tmp_edi->pm_recv_ctx = pm; |
| |
| switch(pm->type) { |
| case CP_LKM_PM_TYPE_PPP_CLIENT: |
| case CP_LKM_PM_TYPE_PPP_SERVER: |
| tmp_edi->pm_recv = cp_lkm_pm_ppp_recv; |
| tmp_edi->pm_get_hdr_size = cp_lkm_pm_ppp_get_hdr_size; |
| tmp_edi->pm_stats64_ctx = NULL; |
| break; |
| default: |
| tmp_edi->pm_recv = cp_lkm_pm_net_recv; |
| tmp_edi->pm_get_hdr_size = cp_lkm_pm_net_get_hdr_size; |
| tmp_edi->pm_send_pause = cp_lkm_pm_net_pause; |
| tmp_edi->pm_send_resume = cp_lkm_pm_net_resume; |
| tmp_edi->pm_stats64_ctx = pm; |
| break; |
| } |
| |
| pm->edi = tmp_edi; |
| |
| // release the link_count on link so things can start flowing. |
| // don't release it on unlink since we don't want things to flow when unlinked |
| spin_lock_irqsave(&pm->pm_link_lock, flags); |
| pm->pm_link_count = 0; |
| spin_unlock_irqrestore(&pm->pm_link_lock, flags); |
| |
| } else { |
| if (tmp_edi) { |
| tmp_edi->pm_recv = NULL; |
| tmp_edi->pm_recv_ctx = NULL; |
| tmp_edi->pm_get_hdr_size = NULL; |
| |
| //printk("pm_recv_ctx null\n"); |
| tmp_edi->pm_send_pause = NULL; |
| tmp_edi->pm_send_resume = NULL; |
| tmp_edi->pm_stats64_ctx = NULL; |
| |
| //pm->edi = NULL; |
| } |
| } |
| |
| return 0; |
| |
| } |
| |
| /******************** common user/kernel communication functions **************/ |
| |
| static void cp_lkm_common_ctx_init(struct cp_lkm_common_ctx *common) |
| { |
| DEBUG_WARN("%s()", __FUNCTION__); |
| |
| INIT_LIST_HEAD(&common->read_list); |
| spin_lock_init(&common->read_list_lock); |
| |
| init_waitqueue_head(&common->inq); |
| common->open_cnt = 0; |
| common->reading_data = false; |
| common->write_skb = NULL; |
| } |
| |
| static void cp_lkm_cleanup_msg_list(struct cp_lkm_common_ctx *common) |
| { |
| struct cp_lkm_read_msg *msg; |
| unsigned long flags; |
| struct list_head *entry, *tmp; |
| |
| spin_lock_irqsave(&common->read_list_lock, flags); |
| |
| list_for_each_safe(entry, tmp, &common->read_list) { |
| msg = list_entry(entry, struct cp_lkm_read_msg, list); |
| list_del(&msg->list); |
| dev_kfree_skb_any(msg->skb); |
| kfree(msg); |
| } |
| spin_unlock_irqrestore(&common->read_list_lock, flags); |
| } |
| |
| // this may be called from soft interrupt context or normal kernel thread context |
| static int cp_lkm_post_message(struct cp_lkm_common_ctx *mgr, struct cp_lkm_msg_hdr* hdr, struct sk_buff *skb) |
| { |
| |
| struct cp_lkm_read_msg *msg; |
| unsigned long flags; |
| |
| msg = kmalloc(sizeof(struct cp_lkm_read_msg), GFP_ATOMIC); |
| if (!msg) { |
| if (skb) { |
| dev_kfree_skb_any(skb); |
| } |
| return -ENOMEM; |
| } |
| |
| msg->skb = skb; |
| memcpy(&msg->hdr, hdr, sizeof(struct cp_lkm_msg_hdr)); |
| |
| spin_lock_irqsave(&mgr->read_list_lock, flags); |
| list_add_tail(&msg->list, &mgr->read_list); |
| spin_unlock_irqrestore(&mgr->read_list_lock, flags); |
| |
| mgr->q_waiting = false; |
| |
| // signal poll |
| wake_up_interruptible(&mgr->inq); |
| |
| return 0; |
| } |
| |
| int cp_lkm_open(struct inode *inode, struct file *filp) |
| { |
| |
| int result = 0; |
| struct cp_lkm_common_ctx *common; |
| |
| DEBUG_TRACE("%s()", __FUNCTION__); |
| |
| try_module_get(THIS_MODULE); |
| |
| // set private data |
| if (iminor(inode) == CP_LKM_USB_MGR_MINOR) { |
| filp->private_data = &cp_lkm_usb_mgr; |
| common = &cp_lkm_usb_mgr.common; |
| DEBUG_INFO("%s() open usb manager", __FUNCTION__); |
| } else if (iminor(inode) == CP_LKM_PM_MGR_MINOR) { |
| filp->private_data = &cp_lkm_pm_mgr; |
| common = &cp_lkm_pm_mgr.common; |
| DEBUG_INFO("%s() open pm manager", __FUNCTION__); |
| } else { |
| return -ENOENT; |
| } |
| |
| if (common->open_cnt) { |
| return -EBUSY; |
| } |
| |
| common->open_cnt++; |
| |
| if (common->open) { |
| result = common->open(common); |
| } |
| |
| return result; |
| } |
| |
| int cp_lkm_release(struct inode *inode, struct file *filp) |
| { |
| |
| int result = 0; |
| struct cp_lkm_common_ctx *common; |
| common = (struct cp_lkm_common_ctx *)filp->private_data; |
| |
| DEBUG_TRACE("%s() release", __FUNCTION__); |
| |
| if (0 == common->open_cnt) { |
| return 0; |
| } |
| |
| if (common->close) { |
| result = common->close(common); |
| } |
| |
| module_put(THIS_MODULE); |
| |
| common->open_cnt--; |
| |
| return result; |
| } |
| |
| // first read is the header |
| // second read is the data. If no data, then no second read |
| // if error in either stage, negative value is returned and next read will be for header |
| // messages are not removed until successfully read header and data (if any) |
| ssize_t cp_lkm_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) |
| { |
| |
| struct cp_lkm_common_ctx *common; |
| ssize_t result; |
| struct cp_lkm_read_msg *msg; |
| unsigned long flags; |
| unsigned long not_copied; |
| |
| // DEBUG_INFO("%s() reading %d bytes", __FUNCTION__, count); |
| common = (struct cp_lkm_common_ctx *)filp->private_data; |
| |
| spin_lock_irqsave(&common->read_list_lock, flags); |
| if (list_empty(&common->read_list)) { |
| spin_unlock_irqrestore(&common->read_list_lock, flags); |
| return -EAGAIN; |
| } |
| msg = list_first_entry(&common->read_list, struct cp_lkm_read_msg, list); |
| spin_unlock_irqrestore(&common->read_list_lock, flags); |
| |
| if (!common->reading_data) { // header mode |
| // read header |
| if (sizeof(struct cp_lkm_msg_hdr) != count) { |
| return -EINVAL; |
| } |
| |
| not_copied = copy_to_user(buf, &msg->hdr, sizeof(struct cp_lkm_msg_hdr)); |
| if (not_copied) { |
| return -ENOMEM; |
| } |
| |
| if (!msg->hdr.len) { |
| result = count; |
| goto read_free; |
| } |
| |
| // switch to data mode |
| common->reading_data = !common->reading_data; |
| return count; |
| } |
| |
| // switch to header mode |
| common->reading_data = !common->reading_data; |
| |
| // data mode - handle the data transfer |
| if (msg->hdr.len != count) { |
| return -EINVAL; |
| } |
| |
| not_copied = copy_to_user(buf, msg->skb->data, msg->hdr.len); |
| |
| if (not_copied) { |
| return -ENOMEM; |
| } |
| |
| result = count; |
| |
| read_free: |
| spin_lock_irqsave(&common->read_list_lock, flags); |
| list_del(&msg->list); |
| spin_unlock_irqrestore(&common->read_list_lock, flags); |
| |
| if (msg->skb) { |
| dev_kfree_skb_any(msg->skb); |
| } |
| kfree(msg); |
| |
| return result; |
| } |
| // the user must write the header first |
| // then the user must write the data equivalent to the hdr.len |
| // on error, a negative value is returned and the entire message is lost |
| // on error, the next write must be header |
| ssize_t cp_lkm_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) |
| { |
| struct cp_lkm_common_ctx *common; |
| unsigned long not_copied; |
| int result; |
| struct sk_buff *skb = NULL; |
| struct cp_lkm_msg_hdr hdr; |
| struct cp_lkm_msg_hdr *hdrp; |
| |
| // DEBUG_INFO("%s() writing %d bytes", __FUNCTION__, count); |
| |
| common = (struct cp_lkm_common_ctx *)filp->private_data; |
| |
| if (!common->write_skb) { |
| // handle the header |
| if (count != sizeof(struct cp_lkm_msg_hdr)) { |
| return -EINVAL; |
| } |
| not_copied = copy_from_user(&hdr, buf, count); |
| if (not_copied) { |
| return -ENOMEM; |
| } |
| |
| if ((skb = alloc_skb (count + hdr.len, GFP_KERNEL)) == NULL) { |
| return -ENOMEM; |
| } |
| |
| memcpy(skb->data, &hdr, count); |
| |
| // setup skb pointers - skb->data points to message data with header immediately before skb->data |
| skb->len = hdr.len; |
| skb->data += sizeof(struct cp_lkm_msg_hdr); |
| skb_set_tail_pointer(skb, hdr.len); |
| |
| if (!hdr.len) { |
| goto send_msg; |
| } |
| |
| // save until we get the data |
| common->write_skb = skb; |
| |
| return count; |
| } |
| |
| // handle the data |
| skb = common->write_skb; |
| common->write_skb = NULL; |
| |
| hdrp = (struct cp_lkm_msg_hdr *)(skb->data) - 1; |
| if (count != hdrp->len) { |
| dev_kfree_skb_any(skb); |
| return -EINVAL; |
| } |
| |
| not_copied = copy_from_user(skb->data, buf, count); |
| if (not_copied) { |
| dev_kfree_skb_any(skb); |
| return -ENOMEM; |
| } |
| |
| |
| send_msg: |
| if (common->handle_msg) { |
| result = common->handle_msg(common, (struct cp_lkm_msg_hdr *)(skb->data) - 1, skb); |
| if (result) { |
| return result; |
| } |
| } |
| |
| return count; |
| } |
| |
| unsigned int cp_lkm_poll(struct file *filp, struct poll_table_struct *wait) |
| { |
| unsigned long flags; |
| unsigned int mask = 0; |
| struct cp_lkm_common_ctx *common; |
| |
| common = (struct cp_lkm_common_ctx *)filp->private_data; |
| |
| poll_wait(filp, &common->inq, wait); |
| |
| spin_lock_irqsave(&common->read_list_lock, flags); |
| |
| if (!list_empty(&common->read_list)) { |
| mask = POLLIN | POLLRDNORM; // readable |
| } |
| |
| spin_unlock_irqrestore(&common->read_list_lock, flags); |
| |
| return mask; |
| } |
| |
| #ifdef KERNEL_2_6_21 |
| int cp_lkm_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) |
| #else |
| long cp_lkm_ioctl (struct file *filp, unsigned int cmd, unsigned long arg) |
| #endif |
| { |
| int result = -EINVAL; |
| |
| void __user *uargp = (void __user *)arg; |
| void *kargp = NULL; |
| struct cp_lkm_common_ctx *common = (struct cp_lkm_common_ctx *)filp->private_data; |
| |
| DEBUG_TRACE("%s(%p) - cmd:%d", __FUNCTION__, filp, _IOC_NR(cmd)); |
| |
| switch(cmd) { |
| case CP_LKM_IOCTL_SET_LOG_LEVEL: |
| cp_lkm_log_level = (uintptr_t)uargp; |
| LOG("Setting debug log level:%d", cp_lkm_log_level); |
| cp_lkm_wrapper_set_log_level(cp_lkm_log_level); |
| return 0; |
| default: |
| if (_IOC_SIZE(cmd)) { |
| kargp = kmalloc(_IOC_SIZE(cmd), GFP_ATOMIC); |
| if (!kargp) { |
| result = -ENOMEM; |
| goto done; |
| } |
| if (copy_from_user(kargp, uargp, _IOC_SIZE(cmd))) { |
| result = -EFAULT; |
| goto done; |
| } |
| } |
| } |
| |
| if (common->handle_ioctl) { |
| result = common->handle_ioctl(common, cmd, kargp); |
| } |
| |
| |
| if (_IOC_DIR(cmd) & _IOC_READ) { |
| if (copy_to_user(uargp, kargp, _IOC_SIZE(cmd))) { |
| result = -EFAULT; |
| goto done; |
| } |
| } |
| |
| done: |
| if (kargp) { |
| kfree(kargp); |
| } |
| |
| return result; |
| } |
| |
| |
| static int __init cp_lkm_start(void) |
| { |
| int err; |
| |
| //printk("%s() Initializing module...\n", __FUNCTION__); |
| |
| // initialize global structures |
| |
| err = cp_lkm_pm_tty_init(); |
| if (err) { |
| return err; |
| } |
| |
| cp_lkm_usb_init(); |
| |
| cp_lkm_pm_init(); |
| |
| // Allocating memory for the buffer |
| if ((major = register_chrdev(0, "cp_lkm", &cp_lkm_fops)) < 0) { |
| DEBUG_INFO("%s() failed dynamic registration", __FUNCTION__); |
| cp_lkm_pm_tty_cleanup(); |
| return major; |
| } |
| |
| cp_lkm_class = class_create(THIS_MODULE, "cp_lkm"); |
| if (IS_ERR(cp_lkm_class)) { |
| DEBUG_INFO("%s() failed class create", __FUNCTION__); |
| unregister_chrdev(major, "cp_lkm"); |
| cp_lkm_pm_tty_cleanup(); |
| return -ENODEV; |
| } |
| #ifdef KERNEL_2_6_21 |
| cp_lkm_dev[0] = device_create(cp_lkm_class, NULL, MKDEV(major, CP_LKM_USB_MGR_MINOR), "cp_lkm_usb"); |
| #else |
| cp_lkm_dev[0] = device_create(cp_lkm_class, NULL, MKDEV(major, CP_LKM_USB_MGR_MINOR), NULL, "cp_lkm_usb"); |
| #endif |
| if (IS_ERR(cp_lkm_dev[0])){ |
| DEBUG_INFO("%s() failed device create: i", __FUNCTION__); |
| // clean up previous devices |
| class_destroy(cp_lkm_class); |
| unregister_chrdev(major, "cp_lkm"); |
| cp_lkm_pm_tty_cleanup(); |
| return -ENODEV; |
| } |
| |
| #ifdef KERNEL_2_6_21 |
| cp_lkm_dev[1] = device_create(cp_lkm_class, NULL, MKDEV(major, CP_LKM_PM_MGR_MINOR), "cp_lkm_pm"); |
| #else |
| cp_lkm_dev[1] = device_create(cp_lkm_class, NULL, MKDEV(major, CP_LKM_PM_MGR_MINOR), NULL, "cp_lkm_pm"); |
| #endif |
| if (IS_ERR(cp_lkm_dev[1])){ |
| DEBUG_INFO("%s() failed device create: i", __FUNCTION__); |
| // clean up previous devices |
| device_destroy(cp_lkm_class, MKDEV(major, 0)); |
| class_destroy(cp_lkm_class); |
| unregister_chrdev(major, "cp_lkm"); |
| cp_lkm_pm_tty_cleanup(); |
| return -ENODEV; |
| } |
| |
| LOG("cp_lkm: Inserting kernel module"); |
| |
| return 0; |
| } |
| |
| static void __exit cp_lkm_end(void) |
| { |
| int i; |
| |
| //TODO remove |
| //del_timer_sync (&dbg_memleak_timer); |
| |
| |
| cp_lkm_pm_cleanup(); |
| cp_lkm_usb_cleanup(); |
| |
| for (i = 0; i < 2; i++) { |
| device_destroy(cp_lkm_class, MKDEV(major, i)); |
| } |
| class_destroy(cp_lkm_class); |
| unregister_chrdev(major, "cp_lkm"); |
| |
| cp_lkm_pm_tty_cleanup(); |
| |
| LOG("cp_lkm: Removing kernel module"); |
| } |
| |
| module_init(cp_lkm_start); |
| module_exit(cp_lkm_end); |
| MODULE_LICENSE("GPL"); |
| |
| |