/* * This is part of the Sequans SQN1130 driver. * Copyright 2009 SEQUANS Communications * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. */ #include #include #include #include #include #include #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) #include #endif #include #include #include #include #include #include #include #include "version.h" #include "msg.h" #include "thp.h" #include "thp_ioctl.h" #define THP_TRACE 0 /* print info messages from THP read/write handlers */ #define THP_HEADER_DUMP 0 /* verbosely dump header of THP TX/RX packets */ #define THP_DEBUG 0 #define SKB_DEBUG 0 #define DRVREV SQN_MODULE_VERSION extern bool drop_packet; const uint8_t host_macaddr[ETH_ALEN] = {0x00, 0x16, 0x08, 0xff, 0x00, 0x01}; const uint8_t ss_macaddr[ETH_ALEN] = {0x00, 0x16, 0x08, 0xff, 0x00, 0x00}; // Queue of packets destined to the Connection Manager // TODO: check size of the queue, it's should always be one. struct sk_buff_head to_sqntool_queue; DECLARE_WAIT_QUEUE_HEAD(to_sqntool_wait); struct net_device *this_device = NULL; struct packet_type rx_packet_type = { 0 }; extern int mmc_wimax_uart_switch(int uart); uint8_t is_thp_packet(uint8_t *dest_addr) { return (memcmp(dest_addr, host_macaddr, ETH_ALEN)==0); } inline struct ethhdr *skb2ethhdr(const struct sk_buff *skb) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) return (struct ethhdr *)skb->mac.raw; #else return (struct ethhdr *)skb_mac_header(skb); #endif } /* TODO: Fix kernel version */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) int thp_handler(struct sk_buff *skb, struct net_device *pDev, struct packet_type *pPt, struct net_device *pOrigDev) #else int thp_handler(struct sk_buff *skb, struct net_device *pDev, struct packet_type *pPt) #endif { struct sk_buff *skb_thp = 0; struct ethhdr *eth = 0; sqn_pr_enter(); /* We need only ETH_P_802_2 protocol packets with THP mac address */ eth = skb2ethhdr(skb); if(ntohs(skb->protocol) != ETH_P_802_2 || !is_thp_packet(eth->h_dest)) { //for DDTM, drop all NOT THP packets if(drop_packet) { sqn_pr_dbg("HTC CODE: drop packet for DDTM\n"); skb->pkt_type = PACKET_OTHERHOST; } goto not_thp_out; } skb_thp = skb_clone(skb, GFP_ATOMIC); /* Bugz 22554: strip CRC at the end of packet */ skb_trim(skb_thp, skb_thp->len - 4); #if THP_TRACE sqn_pr_info("%s: RX packet, len = %d\n", __func__, skb_thp->len); #endif sqn_pr_dbg("RX THP packet, length %d\n", skb_thp->len); skb_queue_tail(&to_sqntool_queue, skb_thp); if(skb_queue_len(&to_sqntool_queue) == 256){ skb_thp = skb_dequeue(&to_sqntool_queue); kfree_skb(skb_thp); } wake_up_interruptible(&to_sqntool_wait); //Wake up wait queue thp_out: dev_kfree_skb_any(skb); sqn_pr_leave(); return NET_RX_DROP; not_thp_out: dev_kfree_skb_any(skb); sqn_pr_leave(); return NET_RX_SUCCESS; } // Initialization function for THP handler int init_thp_handler(struct net_device *dev) { sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "init_thp_handler +\n"); #endif skb_queue_head_init(&to_sqntool_queue); /* Define type of intercepted packets */ rx_packet_type.type = htons(ETH_P_ALL); /* Intercept all packets */ rx_packet_type.dev = dev; rx_packet_type.func = thp_handler; /* Network packet handler function */ /* Register packet handler */ dev_add_pack(&rx_packet_type); #if THP_DEBUG printk(KERN_WARNING "init_thp_handler -\n"); #endif sqn_pr_leave(); return 0; } // Clean up function for THP handler void cleanup_thp_handler(void) { sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "cleanup_thp_handler +\n"); #endif /* unregister packet handler */ dev_remove_pack(&rx_packet_type); if(!skb_queue_empty(&to_sqntool_queue)) skb_queue_purge(&to_sqntool_queue) ; #if THP_DEBUG printk(KERN_WARNING "cleanup_thp_handler -\n"); #endif sqn_pr_leave(); } #define PROC_DIR_NAME "kthp" #define DRV_REVISION "kthp/drvrev" #define IFACE_FILENAME "kthp/iface_name" char procfs_dir[64] = PROC_DIR_NAME; static struct proc_dir_entry* kthp_proc_dir; extern struct net_device *this_device; /** PROC_FS Read Functions */ static int ifacename_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "ifacename_read +\n"); #endif if(this_device) len += sprintf(page, "%s\n", this_device->name); *eof = 1; #if THP_DEBUG printk(KERN_WARNING "ifacename_read -\n"); #endif sqn_pr_leave(); return len; } static int drvrev_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "drvrev_read +\n"); #endif len += sprintf(page, "%s\n", SQN_MODULE_VERSION); *eof = 1; sqn_pr_leave(); return len; } static int install_entry(char *entry_name, read_proc_t* read_func) { struct proc_dir_entry* proc; sqn_pr_enter(); proc = create_proc_read_entry(entry_name, S_IFREG | S_IRUGO | S_IWUSR , NULL, (read_proc_t*)read_func, NULL); if (proc) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) proc->owner = THIS_MODULE; #endif } else { printk(KERN_ALERT"/proc/ %s failed", entry_name); return 1; } sqn_pr_leave(); return 0; } int init_procfs_handler(void) { sqn_pr_enter(); kthp_proc_dir = proc_mkdir(procfs_dir, NULL); if (kthp_proc_dir) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) kthp_proc_dir->owner = THIS_MODULE; #endif } else { remove_proc_entry(PROC_DIR_NAME, NULL); return 1; } if(install_entry(IFACE_FILENAME, ifacename_read) || install_entry(DRV_REVISION, drvrev_read)) { return 1; } #if THP_DEBUG printk(KERN_WARNING "drvrev_read -\n"); #endif sqn_pr_leave(); return 0; } void cleanup_procfs_handler(void) { sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "cleanup_procfs_handler +\n"); #endif remove_proc_entry(IFACE_FILENAME, NULL); remove_proc_entry(DRV_REVISION, NULL); if (kthp_proc_dir) remove_proc_entry(procfs_dir, NULL); #if THP_DEBUG printk(KERN_WARNING "cleanup_procfs_handler -\n"); #endif sqn_pr_leave(); } #define THP_FILENAME "thp" static dev_t dev_num; //static int dev_index; static struct cdev *thp_dev; static struct class *thp_class; static struct device *thp_device; static uint8_t once_open_flag = 0; const char thp_filename[64] = THP_FILENAME; /********************File operations*****************************/ static int thp_open(struct inode*, struct file*); static ssize_t thp_release(struct inode*, struct file*); static ssize_t thp_read(struct file*, char*, size_t, loff_t*); static ssize_t thp_write(struct file *file, const char *buf, size_t count, loff_t *ppos); static unsigned int thp_poll(struct file *filp, poll_table *wait); static int thp_ioctl(struct inode*, struct file*, unsigned int, unsigned long); struct file_operations thp_fops = { .owner = THIS_MODULE , .open = thp_open , .release= thp_release , .read = thp_read , .write= thp_write , .poll = thp_poll , .ioctl = thp_ioctl }; /********************** File Operations BEGIN *****************************/ static int thp_open(struct inode * inode, struct file * filp) { sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "thp_open +\n"); #endif // allow multiple open() call for supporting ioctl on HTC Supersonic /* if(once_open_flag) return -EBUSY; */ once_open_flag = 1; #if THP_DEBUG printk(KERN_WARNING "thp_open -\n"); #endif sqn_pr_leave(); return 0; } static ssize_t thp_release(struct inode *inode, struct file *filp) { sqn_pr_enter(); once_open_flag = 0; if(!skb_queue_empty(&to_sqntool_queue)) skb_queue_purge(&to_sqntool_queue); sqn_pr_leave(); return 0; } static ssize_t thp_read(struct file *filp, char *buf, size_t count, loff_t*ppos) { DECLARE_WAITQUEUE(wait, current); struct sk_buff_head *head = &to_sqntool_queue; struct sk_buff *curr = NULL; ssize_t retval; #if THP_HEADER_DUMP const struct sqn_thp_header *th = 0; #endif sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "thp_read +\n"); #endif add_wait_queue(&to_sqntool_wait, &wait); retval = -ERESTARTSYS; if(0 == this_device) { printk(KERN_WARNING "thp_read() device removed\n"); retval = -EINVAL; goto out; } while(1) { if(!skb_queue_empty(head)) break; if(signal_pending(current) || 0 == this_device) { printk(KERN_WARNING "thp_read() interrupted by signal\n"); retval = -EINTR; goto out; } set_current_state(TASK_INTERRUPTIBLE); schedule(); } curr = skb_dequeue(head); if (count < curr->len) { printk(KERN_WARNING "%s: userspace buffer is too small (%u bytes)" " to hold THP packet (%u bytes)\n" , __func__, count, curr->len); retval = -EINVAL; goto free_skb; } else { count = curr->len; } if(copy_to_user(buf, curr->data, count)) { printk(KERN_ERR "error copying data to user space\n"); retval = -EFAULT; goto free_skb; } #if THP_TRACE sqn_pr_info("%s: [to_user]: len = %d\n", __func__, count); #endif #if THP_HEADER_DUMP th = (struct sqn_thp_header *) curr->data; sqn_pr_info("%s: PKTLen: %4u | TVer: 0x0%x | Flags: 0x0%x | Len: %4u" " | SeqNum: %5u | AckNum: %5u | TLen: %5u\n", __func__ , count , th->transport_version , th->flags , be16_to_cpu(th->length) , be16_to_cpu(th->seq_number) , be16_to_cpu(th->ack_number) , be32_to_cpu(th->total_length)); #endif sqn_pr_dbg("[to_user]: len = %d\n", count); #ifdef SQN_DEBUG_DUMP sqn_pr_dbg_dump("RX:", curr->data, count); #endif #if SKB_DEBUG sqn_pr_info("%s: free skb [0x%p], users %d\n", __func__, curr, atomic_read(&curr->users)); #endif retval = (ssize_t)count; free_skb: dev_kfree_skb_any(curr); out: set_current_state(TASK_RUNNING); remove_wait_queue(&to_sqntool_wait, &wait); #if THP_DEBUG printk(KERN_WARNING "thp_read -\n"); #endif sqn_pr_leave(); return retval; } static ssize_t thp_write(struct file *file, const char *buf, size_t count, loff_t *ppos) { ssize_t retval = -ENOMEM; struct sk_buff *skb; struct ethhdr ethh; int size = count + ETH_HLEN; #if THP_HEADER_DUMP const struct sqn_thp_header *th = 0; #endif sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "thp_write +\n"); #endif if(0 == this_device) return -ENODEV; skb = __dev_alloc_skb(size, GFP_ATOMIC | GFP_DMA); if(skb == NULL) return retval; #if SKB_DEBUG sqn_pr_info("%s: [0x%p] alloc skb, users %d\n", __func__, skb, atomic_read(&skb->users)); #endif memcpy(ethh.h_dest, ss_macaddr, ETH_ALEN); memcpy(ethh.h_source, host_macaddr, ETH_ALEN); ethh.h_proto = htons(count); memcpy(skb->data, ðh, sizeof(struct ethhdr)); skb_put(skb, sizeof(struct ethhdr)); if(copy_from_user(skb->tail, buf, count)) { dev_kfree_skb_any(skb); return -EFAULT; } skb_put(skb, count); #if THP_TRACE sqn_pr_info("%s: [from_user]: len = %d\n", __func__, count); #endif #if THP_HEADER_DUMP th = (struct sqn_thp_header *) buf; sqn_pr_info("%s: PKTLen: %4u | TVer: 0x0%x | Flags: 0x0%x | Len: %4u" " | SeqNum: %5u | AckNum: %5u | TLen: %5u\n", __func__ , count , th->transport_version , th->flags , be16_to_cpu(th->length) , be16_to_cpu(th->seq_number) , be16_to_cpu(th->ack_number) , be32_to_cpu(th->total_length)); #endif sqn_pr_dbg("[from_user]: len = %d\n", count); #ifdef SQN_DEBUG_DUMP sqn_pr_dbg_dump("TX:", skb->data, count); #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29) this_device->hard_start_xmit(skb, this_device); #else this_device->netdev_ops->ndo_start_xmit(skb, this_device); #endif retval = count; #if THP_DEBUG printk(KERN_WARNING "thp_write -\n"); #endif sqn_pr_leave(); return retval; } /* */ static unsigned int thp_poll(struct file *filp, poll_table *wait) { unsigned int mask = 0; sqn_pr_enter(); #if THP_DEBUG //printk(KERN_WARNING "thp_poll +\n"); #endif poll_wait(filp, &to_sqntool_wait, wait); if (0 == this_device) { printk(KERN_WARNING "thp_poll() device removed\n"); mask = POLLERR; } else if(skb_queue_empty(&to_sqntool_queue)) { mask = 0; } else { mask = (POLLIN | POLLRDNORM); } #if THP_DEBUG //printk(KERN_WARNING "thp_poll -\n"); #endif sqn_pr_leave(); return mask; } static int thp_ioctl(struct inode* dev, struct file* handle, unsigned int cmd, unsigned long arg) { #if THP_DEBUG printk(KERN_WARNING "thp_ioctl +\n"); #endif sqn_pr_enter(); switch (cmd) { case IOCTL_DROP_PACKETS: printk(KERN_WARNING "IOCTL_DROP_PACKETS arg=%d\n",(int)arg); if(arg == 1) drop_packet = true; else drop_packet = false; break; case IOCTL_SWITCH_UART: printk(KERN_WARNING "IOCTL_SWITCH_UART arg=%d\n",(int)arg); if(arg == 1) mmc_wimax_uart_switch(2); // Wimax else mmc_wimax_uart_switch(0); // USB break; default: printk(KERN_WARNING "UNKNOWN OPERATION in thp_ioctl\n"); return -1; } sqn_pr_leave(); #if THP_DEBUG printk(KERN_WARNING "thp_ioctl -\n"); #endif return 0; } int init_thp_devfile(void) { sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "init_thp_devfile +\n"); #endif //Dynamic allocation of device number if(alloc_chrdev_region(&dev_num, 0, 1, thp_filename)) return -ENOMEM; thp_dev = cdev_alloc(); if(thp_dev == NULL) return -ENOMEM; thp_dev->ops = &thp_fops; thp_dev->owner = THIS_MODULE; if(cdev_add(thp_dev, dev_num, 1)) return -ENOMEM; thp_class = class_create(THIS_MODULE, thp_filename); if (IS_ERR(thp_class)) { printk("class_create error(0x%x)\n",(unsigned int)(thp_class)); return PTR_ERR(thp_class); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) thp_device = device_create(thp_class, NULL, dev_num, NULL, thp_filename); #else thp_device = device_create(thp_class, NULL, dev_num, thp_filename); #endif if (IS_ERR(thp_device)) { printk("device_create error(0x%x)\n", (unsigned int)(thp_device)); return PTR_ERR(thp_device); } #if THP_DEBUG printk(KERN_WARNING "init_thp_devfile -\n"); #endif sqn_pr_leave(); return 0; } /** \brief dev-fs cleanup function * This function is called in module cleanup function */ void cleanup_thp_devfile(void) { sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "cleanup_thp_devfile +\n"); #endif /* Unregister entry from /dev */ device_destroy(thp_class, dev_num); class_destroy(thp_class); unregister_chrdev_region(dev_num, 1); cdev_del(thp_dev); #if THP_DEBUG printk(KERN_WARNING "cleanup_thp_devfile -\n"); #endif sqn_pr_leave(); } /********************** File Operations END *****************************/ int thp_wimax_uart_switch(int on) { printk("%s on%d\n", __func__, on); if (on) { mmc_wimax_uart_switch(2); // Wimax } else { mmc_wimax_uart_switch(0); // USB } return 0; } int init_thp(struct net_device* dev) { sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "init_thp +\n"); #endif if (0 == this_device) { if(init_procfs_handler()) { return -1; } if(init_thp_devfile()) return -1; /* Don't call init_thp_handler() here, it will be called from * probe() before interrupts are enabled, to ensure that we will * catch all THP packets as soon as they appear */ /* if (init_thp_handler(dev)) */ /* return -1; */ this_device = dev; sqn_pr_info("KTHP initialized\n"); } #if THP_DEBUG printk(KERN_WARNING "init_thp -\n"); #endif sqn_pr_leave(); return 0; } void cleanup_thp(void) { sqn_pr_enter(); #if THP_DEBUG printk(KERN_WARNING "cleanup_thp +\n"); #endif if (this_device) { cleanup_procfs_handler(); cleanup_thp_handler(); cleanup_thp_devfile(); this_device = 0; sqn_pr_info("KTHP cleaned up\n"); } #if THP_DEBUG printk(KERN_WARNING "cleanup_thp -\n"); #endif sqn_pr_leave(); }