📄 usbatm.c
字号:
/****************************************************************************** * usbatm.c - Generic USB xDSL driver core * * Copyright (C) 2001, Alcatel * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas * Copyright (C) 2004, David Woodhouse, Roman Kagan * * 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. * * 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 the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ******************************************************************************//* * Written by Johan Verrept, Duncan Sands (duncan.sands@free.fr) and David Woodhouse * * 1.7+: - See the check-in logs * * 1.6: - No longer opens a connection if the firmware is not loaded * - Added support for the speedtouch 330 * - Removed the limit on the number of devices * - Module now autoloads on device plugin * - Merged relevant parts of sarlib * - Replaced the kernel thread with a tasklet * - New packet transmission code * - Changed proc file contents * - Fixed all known SMP races * - Many fixes and cleanups * - Various fixes by Oliver Neukum (oliver@neukum.name) * * 1.5A: - Version for inclusion in 2.5 series kernel * - Modifications by Richard Purdie (rpurdie@rpsys.net) * - made compatible with kernel 2.5.6 onwards by changing * usbatm_usb_send_data_context->urb to a pointer and adding code * to alloc and free it * - remove_wait_queue() added to usbatm_atm_processqueue_thread() * * 1.5: - fixed memory leak when atmsar_decode_aal5 returned NULL. * (reported by stephen.robinson@zen.co.uk) * * 1.4: - changed the spin_lock() under interrupt to spin_lock_irqsave() * - unlink all active send urbs of a vcc that is being closed. * * 1.3.1: - added the version number * * 1.3: - Added multiple send urb support * - fixed memory leak and vcc->tx_inuse starvation bug * when not enough memory left in vcc. * * 1.2: - Fixed race condition in usbatm_usb_send_data() * 1.1: - Turned off packet debugging * */#include "usbatm.h"#include <asm/uaccess.h>#include <linux/crc32.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/proc_fs.h>#include <linux/sched.h>#include <linux/signal.h>#include <linux/slab.h>#include <linux/smp_lock.h>#include <linux/stat.h>#include <linux/timer.h>#include <linux/wait.h>#ifdef VERBOSE_DEBUGstatic int usbatm_print_packet(const unsigned char *data, int len);#define PACKETDEBUG(arg...) usbatm_print_packet (arg)#define vdbg(arg...) dbg (arg)#else#define PACKETDEBUG(arg...)#define vdbg(arg...)#endif#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands <duncan.sands@free.fr>"#define DRIVER_VERSION "1.9"#define DRIVER_DESC "Generic USB ATM/DSL I/O, version " DRIVER_VERSIONstatic const char usbatm_driver_name[] = "usbatm";#define UDSL_MAX_RCV_URBS 16#define UDSL_MAX_SND_URBS 16#define UDSL_MAX_RCV_BUF_SIZE 1024 /* ATM cells */#define UDSL_MAX_SND_BUF_SIZE 1024 /* ATM cells */#define UDSL_DEFAULT_RCV_URBS 4#define UDSL_DEFAULT_SND_URBS 4#define UDSL_DEFAULT_RCV_BUF_SIZE 64 /* ATM cells */#define UDSL_DEFAULT_SND_BUF_SIZE 64 /* ATM cells */#define ATM_CELL_HEADER (ATM_CELL_SIZE - ATM_CELL_PAYLOAD)#define THROTTLE_MSECS 100 /* delay to recover processing after urb submission fails */static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS;static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS;static unsigned int rcv_buf_size = UDSL_DEFAULT_RCV_BUF_SIZE;static unsigned int snd_buf_size = UDSL_DEFAULT_SND_BUF_SIZE;module_param(num_rcv_urbs, uint, S_IRUGO);MODULE_PARM_DESC(num_rcv_urbs, "Number of urbs used for reception (range: 0-" __MODULE_STRING(UDSL_MAX_RCV_URBS) ", default: " __MODULE_STRING(UDSL_DEFAULT_RCV_URBS) ")");module_param(num_snd_urbs, uint, S_IRUGO);MODULE_PARM_DESC(num_snd_urbs, "Number of urbs used for transmission (range: 0-" __MODULE_STRING(UDSL_MAX_SND_URBS) ", default: " __MODULE_STRING(UDSL_DEFAULT_SND_URBS) ")");module_param(rcv_buf_size, uint, S_IRUGO);MODULE_PARM_DESC(rcv_buf_size, "Size of the buffers used for reception in ATM cells (range: 1-" __MODULE_STRING(UDSL_MAX_RCV_BUF_SIZE) ", default: " __MODULE_STRING(UDSL_DEFAULT_RCV_BUF_SIZE) ")");module_param(snd_buf_size, uint, S_IRUGO);MODULE_PARM_DESC(snd_buf_size, "Size of the buffers used for transmission in ATM cells (range: 1-" __MODULE_STRING(UDSL_MAX_SND_BUF_SIZE) ", default: " __MODULE_STRING(UDSL_DEFAULT_SND_BUF_SIZE) ")");/* receive */struct usbatm_vcc_data { /* vpi/vci lookup */ struct list_head list; short vpi; int vci; struct atm_vcc *vcc; /* raw cell reassembly */ struct sk_buff *sarb;};/* send */struct usbatm_control { struct atm_skb_data atm; u32 len; u32 crc;};#define UDSL_SKB(x) ((struct usbatm_control *)(x)->cb)/* ATM */static void usbatm_atm_dev_close(struct atm_dev *dev);static int usbatm_atm_open(struct atm_vcc *vcc);static void usbatm_atm_close(struct atm_vcc *vcc);static int usbatm_atm_ioctl(struct atm_dev *dev, unsigned int cmd, void __user * arg);static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb);static int usbatm_atm_proc_read(struct atm_dev *atm_dev, loff_t * pos, char *page);static struct atmdev_ops usbatm_atm_devops = { .dev_close = usbatm_atm_dev_close, .open = usbatm_atm_open, .close = usbatm_atm_close, .ioctl = usbatm_atm_ioctl, .send = usbatm_atm_send, .proc_read = usbatm_atm_proc_read, .owner = THIS_MODULE,};/************* misc *************/static inline unsigned int usbatm_pdu_length(unsigned int length){ length += ATM_CELL_PAYLOAD - 1 + ATM_AAL5_TRAILER; return length - length % ATM_CELL_PAYLOAD;}static inline void usbatm_pop(struct atm_vcc *vcc, struct sk_buff *skb){ if (vcc->pop) vcc->pop(vcc, skb); else dev_kfree_skb(skb);}/************* urbs **************/static inline struct urb *usbatm_pop_urb(struct usbatm_channel *channel){ struct urb *urb; spin_lock_irq(&channel->lock); if (list_empty(&channel->list)) { spin_unlock_irq(&channel->lock); return NULL; } urb = list_entry(channel->list.next, struct urb, urb_list); list_del(&urb->urb_list); spin_unlock_irq(&channel->lock); return urb;}static inline int usbatm_submit_urb(struct urb *urb){ struct usbatm_channel *channel = urb->context; int ret; vdbg("%s: submitting urb 0x%p, size %u", __func__, urb, urb->transfer_buffer_length); ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret) { atm_dbg(channel->usbatm, "%s: urb 0x%p submission failed (%d)!\n", __func__, urb, ret); /* consider all errors transient and return the buffer back to the queue */ urb->status = -EAGAIN; spin_lock_irq(&channel->lock); /* must add to the front when sending; doesn't matter when receiving */ list_add(&urb->urb_list, &channel->list); spin_unlock_irq(&channel->lock); /* make sure the channel doesn't stall */ mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS)); } return ret;}static void usbatm_complete(struct urb *urb, struct pt_regs *regs){ struct usbatm_channel *channel = urb->context; unsigned long flags; vdbg("%s: urb 0x%p, status %d, actual_length %d", __func__, urb, urb->status, urb->actual_length); /* usually in_interrupt(), but not always */ spin_lock_irqsave(&channel->lock, flags); /* must add to the back when receiving; doesn't matter when sending */ list_add_tail(&urb->urb_list, &channel->list); spin_unlock_irqrestore(&channel->lock, flags); if (unlikely(urb->status)) /* throttle processing in case of an error */ mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS)); else tasklet_schedule(&channel->tasklet);}/*************** decode ***************/static inline struct usbatm_vcc_data *usbatm_find_vcc(struct usbatm_data *instance, short vpi, int vci){ struct usbatm_vcc_data *vcc; list_for_each_entry(vcc, &instance->vcc_list, list) if ((vcc->vci == vci) && (vcc->vpi == vpi)) return vcc; return NULL;}static void usbatm_extract_cells(struct usbatm_data *instance, unsigned char *source, unsigned int avail_data){ struct usbatm_vcc_data *cached_vcc = NULL; struct atm_vcc *vcc; struct sk_buff *sarb; unsigned int stride = instance->rx_channel.stride; int vci, cached_vci = 0; short vpi, cached_vpi = 0; u8 pti; for (; avail_data >= stride; avail_data -= stride, source += stride) { vpi = ((source[0] & 0x0f) << 4) | (source[1] >> 4); vci = ((source[1] & 0x0f) << 12) | (source[2] << 4) | (source[3] >> 4); pti = ((source[3] & 0xe) >> 1); vdbg("%s: vpi %hd, vci %d, pti %d", __func__, vpi, vci, pti); if ((vci != cached_vci) || (vpi != cached_vpi)) { cached_vpi = vpi; cached_vci = vci; cached_vcc = usbatm_find_vcc(instance, vpi, vci); if (!cached_vcc) atm_dbg(instance, "%s: unknown vpi/vci (%hd/%d)!\n", __func__, vpi, vci); } if (!cached_vcc) continue; vcc = cached_vcc->vcc; /* OAM F5 end-to-end */ if (pti == ATM_PTI_E2EF5) { atm_warn(instance, "%s: OAM not supported (vpi %d, vci %d)!\n", __func__, vpi, vci); atomic_inc(&vcc->stats->rx_err); continue; } sarb = cached_vcc->sarb; if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) { atm_dbg(instance, "%s: buffer overrun (sarb->len %u, vcc: 0x%p)!\n", __func__, sarb->len, vcc); /* discard cells already received */ skb_trim(sarb, 0); UDSL_ASSERT(sarb->tail + ATM_CELL_PAYLOAD <= sarb->end); } memcpy(sarb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); __skb_put(sarb, ATM_CELL_PAYLOAD); if (pti & 1) { struct sk_buff *skb; unsigned int length; unsigned int pdu_length; length = (source[ATM_CELL_SIZE - 6] << 8) + source[ATM_CELL_SIZE - 5]; /* guard against overflow */ if (length > ATM_MAX_AAL5_PDU) { atm_dbg(instance, "%s: bogus length %u (vcc: 0x%p)!\n", __func__, length, vcc); atomic_inc(&vcc->stats->rx_err); goto out; } pdu_length = usbatm_pdu_length(length); if (sarb->len < pdu_length) { atm_dbg(instance, "%s: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!\n", __func__, pdu_length, sarb->len, vcc); atomic_inc(&vcc->stats->rx_err); goto out; } if (crc32_be(~0, sarb->tail - pdu_length, pdu_length) != 0xc704dd7b) { atm_dbg(instance, "%s: packet failed crc check (vcc: 0x%p)!\n", __func__, vcc); atomic_inc(&vcc->stats->rx_err); goto out; } vdbg("%s: got packet (length: %u, pdu_length: %u, vcc: 0x%p)", __func__, length, pdu_length, vcc); if (!(skb = dev_alloc_skb(length))) { atm_dbg(instance, "%s: no memory for skb (length: %u)!\n", __func__, length); atomic_inc(&vcc->stats->rx_drop); goto out; } vdbg("%s: allocated new sk_buff (skb: 0x%p, skb->truesize: %u)", __func__, skb, skb->truesize); if (!atm_charge(vcc, skb->truesize)) { atm_dbg(instance, "%s: failed atm_charge (skb->truesize: %u)!\n", __func__, skb->truesize); dev_kfree_skb(skb); goto out; /* atm_charge increments rx_drop */ } memcpy(skb->data, sarb->tail - pdu_length, length); __skb_put(skb, length); vdbg("%s: sending skb 0x%p, skb->len %u, skb->truesize %u", __func__, skb, skb->len, skb->truesize); PACKETDEBUG(skb->data, skb->len); vcc->push(vcc, skb); atomic_inc(&vcc->stats->rx); out: skb_trim(sarb, 0); } }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -