ffdfdfb66b
The xpp modules use time differences for various statistics. In one case xpp_usb creates a histogram of the time it took the system to send USB packets (URBs). The time difference is used (after adjustments) as an index to that array. However if the clock happens to go back at that exact point in time, we get to write to an arbitrary negative index in this array. Signed-off-by: Tzafrir Cohen <tzafrir.cohen@xorcom.com>
1151 lines
31 KiB
C
1151 lines
31 KiB
C
/*
|
|
* Written by Oron Peled <oron@actcom.co.il>
|
|
* Copyright (C) 2004-2006, Xorcom
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
#include <linux/version.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h> /* for udelay */
|
|
#include <linux/seq_file.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm/atomic.h>
|
|
#include <asm/timex.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/usb.h>
|
|
#include "xpd.h"
|
|
#include "xproto.h"
|
|
#include "xbus-core.h"
|
|
#include "xframe_queue.h"
|
|
#ifdef DEBUG
|
|
#include "card_fxs.h"
|
|
#include "card_fxo.h"
|
|
#endif
|
|
#include "parport_debug.h"
|
|
|
|
static const char rcsid[] = "$Id$";
|
|
|
|
/* must be before dahdi_debug.h */
|
|
static DEF_PARM(int, debug, 0, 0644, "Print DBG statements");
|
|
static DEF_PARM(int, usb1, 0, 0644, "Allow using USB 1.1 interfaces");
|
|
static DEF_PARM(uint, tx_sluggish, 2000, 0644, "A sluggish transmit (usec)");
|
|
static DEF_PARM(uint, drop_pcm_after, 6, 0644,
|
|
"Number of consecutive tx_sluggish to start dropping PCM");
|
|
static DEF_PARM(uint, sluggish_pcm_keepalive, 50, 0644,
|
|
"During sluggish -- Keep-alive PCM (1 every #)");
|
|
|
|
#include "dahdi_debug.h"
|
|
|
|
#define XUSB_PRINTK(level, xusb, fmt, ...) \
|
|
printk(KERN_ ## level "%s-%s: xusb-%d (%s) [%s]: " fmt, #level, \
|
|
THIS_MODULE->name, (xusb)->index, xusb->path, \
|
|
xusb->serial, ## __VA_ARGS__)
|
|
|
|
#define XUSB_DBG(bits, xusb, fmt, ...) \
|
|
((void)((debug & (DBG_ ## bits)) && XUSB_PRINTK(DEBUG, \
|
|
xusb, "%s: " fmt, __func__, ## __VA_ARGS__)))
|
|
#define XUSB_ERR(xusb, fmt, ...) \
|
|
XUSB_PRINTK(ERR, xusb, fmt, ## __VA_ARGS__)
|
|
#define XUSB_NOTICE(xusb, fmt, ...) \
|
|
XUSB_PRINTK(NOTICE, xusb, fmt, ## __VA_ARGS__)
|
|
#define XUSB_INFO(xusb, fmt, ...) \
|
|
XUSB_PRINTK(INFO, xusb, fmt, ## __VA_ARGS__)
|
|
|
|
/* Get a minor range for your devices from the usb maintainer */
|
|
#define USB_SKEL_MINOR_BASE 192
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
#define PROC_USBXPP_SUMMARY "xpp_usb"
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 34)
|
|
#define usb_alloc_coherent(dev, size, mem_flags, dma) \
|
|
usb_buffer_alloc(dev, size, mem_flags, dma)
|
|
#define usb_free_coherent(dev, size, addr, dma) \
|
|
usb_buffer_free(dev, size, addr, dma)
|
|
#endif
|
|
|
|
#ifdef DEBUG_PCM_TIMING
|
|
static cycles_t stamp_last_pcm_read;
|
|
static cycles_t accumulate_diff;
|
|
#endif
|
|
|
|
struct xusb_model_info;
|
|
|
|
struct xusb_endpoint {
|
|
int ep_addr;
|
|
int max_size;
|
|
usb_complete_t callback;
|
|
};
|
|
|
|
enum xusb_dir {
|
|
XUSB_RECV = 0,
|
|
XUSB_SEND = 1,
|
|
};
|
|
|
|
static __must_check int xframe_send_pcm(xbus_t *xbus, xframe_t *xframe);
|
|
static __must_check int xframe_send_cmd(xbus_t *xbus, xframe_t *xframe);
|
|
static __must_check xframe_t *alloc_xframe(xbus_t *xbus, gfp_t flags);
|
|
static void free_xframe(xbus_t *xbus, xframe_t *frm);
|
|
|
|
static struct xbus_ops xusb_ops = {
|
|
.xframe_send_pcm = xframe_send_pcm,
|
|
.xframe_send_cmd = xframe_send_cmd,
|
|
.alloc_xframe = alloc_xframe,
|
|
.free_xframe = free_xframe,
|
|
};
|
|
|
|
enum {
|
|
XUSB_N_RX_FRAMES,
|
|
XUSB_N_TX_FRAMES,
|
|
XUSB_N_RX_ERRORS,
|
|
XUSB_N_TX_ERRORS,
|
|
XUSB_N_RX_DROPS,
|
|
XUSB_N_TX_DROPS,
|
|
XUSB_N_RCV_ZERO_LEN,
|
|
};
|
|
|
|
#define XUSB_COUNTER(xusb, counter) ((xusb)->counters[XUSB_N_ ## counter])
|
|
|
|
#define C_(x) [ XUSB_N_ ## x ] = { #x }
|
|
|
|
static struct xusb_counters {
|
|
char *name;
|
|
} xusb_counters[] = {
|
|
C_(RX_FRAMES),
|
|
C_(TX_FRAMES),
|
|
C_(RX_ERRORS),
|
|
C_(TX_ERRORS),
|
|
C_(RX_DROPS),
|
|
C_(TX_DROPS),
|
|
C_(RCV_ZERO_LEN),
|
|
};
|
|
|
|
#undef C_
|
|
|
|
#define XUSB_COUNTER_MAX ARRAY_SIZE(xusb_counters)
|
|
|
|
#define MAX_PENDING_WRITES 100
|
|
|
|
static KMEM_CACHE_T *xusb_cache;
|
|
|
|
typedef struct xusb xusb_t;
|
|
|
|
/*
|
|
* A uframe is our low level representation of a frame.
|
|
*
|
|
* It contains the metadata for the usb stack (a urb)
|
|
* and the metadata for the xbus-core (an xframe)
|
|
* as well as pointing to the data (transfer_buffer, transfer_buffer_length)
|
|
* directionality (send/receive) and ownership (xusb).
|
|
*/
|
|
struct uframe {
|
|
unsigned long uframe_magic;
|
|
#define UFRAME_MAGIC 654321L
|
|
struct urb urb;
|
|
xframe_t xframe;
|
|
size_t transfer_buffer_length;
|
|
void *transfer_buffer; /* max XFRAME_DATASIZE */
|
|
xusb_t *xusb;
|
|
};
|
|
|
|
#define urb_to_uframe(urb) \
|
|
container_of(urb, struct uframe, urb)
|
|
#define xframe_to_uframe(xframe) \
|
|
container_of(xframe, struct uframe, xframe)
|
|
#define xusb_of(xbus) \
|
|
((xusb_t *)((xbus)->transport.priv))
|
|
|
|
#define USEC_BUCKET 100 /* usec */
|
|
#define NUM_BUCKETS 15
|
|
#define BUCKET_START (500/USEC_BUCKET) /* skip uninteresting */
|
|
|
|
/*
|
|
* USB XPP Bus (a USB Device)
|
|
*/
|
|
struct xusb {
|
|
uint xbus_num;
|
|
struct usb_device *udev; /* save off the usb device pointer */
|
|
struct usb_interface *interface; /* the interface for this device */
|
|
unsigned char minor; /* the starting minor number for this device */
|
|
uint index;
|
|
char path[XBUS_DESCLEN]; /* a unique path */
|
|
|
|
struct xusb_model_info *model_info;
|
|
struct xusb_endpoint endpoints[2]; /* RECV/SEND endpoints */
|
|
|
|
int present; /* if the device is not disconnected */
|
|
atomic_t pending_writes; /* submited but not out yet */
|
|
atomic_t pending_reads; /* submited but not in yet */
|
|
struct semaphore sem; /* locks this structure */
|
|
int counters[XUSB_COUNTER_MAX];
|
|
|
|
/* metrics */
|
|
struct timeval last_tx;
|
|
unsigned int max_tx_delay;
|
|
uint usb_tx_delay[NUM_BUCKETS];
|
|
uint sluggish_debounce;
|
|
bool drop_pcm; /* due to sluggishness */
|
|
atomic_t usb_sluggish_count;
|
|
|
|
const char *manufacturer;
|
|
const char *product;
|
|
const char *serial;
|
|
const char *interface_name;
|
|
|
|
};
|
|
|
|
static DEFINE_SPINLOCK(xusb_lock);
|
|
static xusb_t *xusb_array[MAX_BUSES] = { };
|
|
|
|
static unsigned bus_count;
|
|
|
|
/* prevent races between open() and disconnect() */
|
|
static DEFINE_MUTEX(protect_xusb_devices);
|
|
|
|
/*
|
|
* AsteriskNow kernel has backported the "lean" callback from 2.6.20
|
|
* to 2.6.19 without any macro to notify of this fact -- how lovely.
|
|
* Debian-Etch and Centos5 are using 2.6.18 for now (lucky for us).
|
|
* Fedora6 jumped from 2.6.18 to 2.6.20. So far luck is on our side ;-)
|
|
*/
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
|
|
#define USB_PASS_CB(u) struct urb *u, struct pt_regs *regs
|
|
#else
|
|
#define USB_PASS_CB(u) struct urb *u
|
|
#endif
|
|
|
|
static void xpp_send_callback(USB_PASS_CB(urb));
|
|
static void xpp_receive_callback(USB_PASS_CB(urb));
|
|
static int xusb_probe(struct usb_interface *interface,
|
|
const struct usb_device_id *id);
|
|
static void xusb_disconnect(struct usb_interface *interface);
|
|
#ifdef CONFIG_PROC_FS
|
|
static const struct file_operations xusb_read_proc_ops;
|
|
#endif
|
|
|
|
/*------------------------------------------------------------------*/
|
|
|
|
/*
|
|
* Updates the urb+xframe metadata from the uframe information.
|
|
*/
|
|
static void uframe_recompute(struct uframe *uframe, enum xusb_dir dir)
|
|
{
|
|
struct urb *urb = &uframe->urb;
|
|
xusb_t *xusb = uframe->xusb;
|
|
struct usb_device *udev = xusb->udev;
|
|
struct xusb_endpoint *xusb_ep = &xusb->endpoints[dir];
|
|
unsigned int ep_addr = xusb_ep->ep_addr;
|
|
usb_complete_t urb_cb = xusb_ep->callback;
|
|
unsigned int epnum = ep_addr & USB_ENDPOINT_NUMBER_MASK;
|
|
int pipe = usb_pipein(ep_addr)
|
|
? usb_rcvbulkpipe(udev, epnum)
|
|
: usb_sndbulkpipe(udev, epnum);
|
|
|
|
BUG_ON(uframe->uframe_magic != UFRAME_MAGIC);
|
|
usb_fill_bulk_urb(urb, udev, pipe, uframe->transfer_buffer,
|
|
uframe->transfer_buffer_length, urb_cb, uframe);
|
|
urb->transfer_flags = (URB_NO_TRANSFER_DMA_MAP);
|
|
}
|
|
|
|
static xframe_t *alloc_xframe(xbus_t *xbus, gfp_t gfp_flags)
|
|
{
|
|
struct uframe *uframe;
|
|
xusb_t *xusb;
|
|
void *p;
|
|
int size;
|
|
static int rate_limit;
|
|
|
|
BUG_ON(!xbus);
|
|
xusb = xusb_of(xbus);
|
|
BUG_ON(!xusb);
|
|
if (!xusb->present) {
|
|
if ((rate_limit++ % 1003) == 0)
|
|
XUSB_ERR(xusb,
|
|
"abort allocations during "
|
|
"device disconnect (%d)\n",
|
|
rate_limit);
|
|
return NULL;
|
|
}
|
|
size =
|
|
min(xusb->endpoints[XUSB_SEND].max_size,
|
|
xusb->endpoints[XUSB_RECV].max_size);
|
|
uframe = kmem_cache_alloc(xusb_cache, gfp_flags);
|
|
if (!uframe) {
|
|
if ((rate_limit++ % 1003) == 0)
|
|
XUSB_ERR(xusb, "frame allocation failed (%d)\n",
|
|
rate_limit);
|
|
return NULL;
|
|
}
|
|
usb_init_urb(&uframe->urb);
|
|
p = usb_alloc_coherent(xusb->udev, size, gfp_flags,
|
|
&uframe->urb.transfer_dma);
|
|
if (!p) {
|
|
if ((rate_limit++ % 1003) == 0)
|
|
XUSB_ERR(xusb, "buffer allocation failed (%d)\n",
|
|
rate_limit);
|
|
kmem_cache_free(xusb_cache, uframe);
|
|
return NULL;
|
|
}
|
|
uframe->uframe_magic = UFRAME_MAGIC;
|
|
uframe->transfer_buffer_length = size;
|
|
uframe->transfer_buffer = p;
|
|
uframe->xusb = xusb;
|
|
xframe_init(xbus, &uframe->xframe, uframe->transfer_buffer,
|
|
uframe->transfer_buffer_length, uframe);
|
|
return &uframe->xframe;
|
|
}
|
|
|
|
static void free_xframe(xbus_t *xbus, xframe_t *xframe)
|
|
{
|
|
struct uframe *uframe = xframe_to_uframe(xframe);
|
|
struct urb *urb = &uframe->urb;
|
|
|
|
BUG_ON(xbus->transport.priv != uframe->xusb);
|
|
//XUSB_INFO(uframe->xusb, "frame_free\n");
|
|
usb_free_coherent(urb->dev, uframe->transfer_buffer_length,
|
|
urb->transfer_buffer, urb->transfer_dma);
|
|
memset(uframe, 0, sizeof(*uframe));
|
|
kmem_cache_free(xusb_cache, uframe);
|
|
}
|
|
|
|
/*------------------------------------------------------------------*/
|
|
|
|
/*
|
|
* Actuall frame sending -- both PCM and commands.
|
|
*/
|
|
static int do_send_xframe(xbus_t *xbus, xframe_t *xframe)
|
|
{
|
|
struct urb *urb;
|
|
xusb_t *xusb;
|
|
int ret = 0;
|
|
struct uframe *uframe;
|
|
|
|
BUG_ON(!xframe);
|
|
BUG_ON(xframe->xframe_magic != XFRAME_MAGIC);
|
|
xusb = xusb_of(xbus);
|
|
BUG_ON(!xusb);
|
|
if (!xusb->present) {
|
|
static int rate_limit;
|
|
|
|
if ((rate_limit++ % 1003) == 0)
|
|
XUSB_ERR(xusb,
|
|
"abort do_send_xframe during "
|
|
"device disconnect (%d)\n",
|
|
rate_limit);
|
|
ret = -ENODEV;
|
|
goto failure;
|
|
}
|
|
/*
|
|
* If something really bad happend, do not overflow the USB stack
|
|
*/
|
|
if (atomic_read(&xusb->pending_writes) > MAX_PENDING_WRITES) {
|
|
static int rate_limit;
|
|
|
|
if ((rate_limit++ % 5000) == 0)
|
|
XUSB_ERR(xusb,
|
|
"USB device is totaly stuck. "
|
|
"Dropping packets (#%d).\n",
|
|
rate_limit);
|
|
ret = -ENODEV;
|
|
goto failure;
|
|
}
|
|
uframe = xframe->priv;
|
|
BUG_ON(!uframe);
|
|
BUG_ON(uframe->uframe_magic != UFRAME_MAGIC);
|
|
uframe_recompute(uframe, XUSB_SEND);
|
|
urb = &uframe->urb;
|
|
BUG_ON(!urb);
|
|
/* update urb length */
|
|
urb->transfer_buffer_length = XFRAME_LEN(xframe);
|
|
do_gettimeofday(&xframe->tv_submitted);
|
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (ret < 0) {
|
|
static int rate_limit;
|
|
|
|
if ((rate_limit++ % 1000) == 0)
|
|
XBUS_ERR(xbus, "%s: usb_submit_urb failed: %d\n",
|
|
__func__, ret);
|
|
ret = -EBADF;
|
|
goto failure;
|
|
}
|
|
// if (debug)
|
|
// dump_xframe("USB_FRAME_SEND", xbus, xframe, debug);
|
|
atomic_inc(&xusb->pending_writes);
|
|
return 0;
|
|
failure:
|
|
XUSB_COUNTER(xusb, TX_ERRORS)++;
|
|
FREE_SEND_XFRAME(xbus, xframe); /* return to pool */
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* PCM wrapper
|
|
*/
|
|
static int xframe_send_pcm(xbus_t *xbus, xframe_t *xframe)
|
|
{
|
|
xusb_t *xusb;
|
|
|
|
BUG_ON(!xbus);
|
|
BUG_ON(!xframe);
|
|
xusb = xusb_of(xbus);
|
|
BUG_ON(!xusb);
|
|
if (xusb->drop_pcm) {
|
|
static int rate_limit;
|
|
|
|
if ((rate_limit++ % 1000) == 0)
|
|
XUSB_ERR(xusb, "Sluggish USB: drop tx-pcm (%d)\n",
|
|
rate_limit);
|
|
/* Let trickle of TX-PCM, so Astribank will not reset */
|
|
if (sluggish_pcm_keepalive &&
|
|
((rate_limit % sluggish_pcm_keepalive) != 0)) {
|
|
XUSB_COUNTER(xusb, TX_DROPS)++;
|
|
goto err;
|
|
}
|
|
}
|
|
return do_send_xframe(xbus, xframe);
|
|
err:
|
|
FREE_SEND_XFRAME(xbus, xframe); /* return to pool */
|
|
return -EIO;
|
|
}
|
|
|
|
/*
|
|
* commands wrapper
|
|
*/
|
|
static int xframe_send_cmd(xbus_t *xbus, xframe_t *xframe)
|
|
{
|
|
BUG_ON(!xbus);
|
|
BUG_ON(!xframe);
|
|
//XBUS_INFO(xbus, "%s:\n", __func__);
|
|
return do_send_xframe(xbus, xframe);
|
|
}
|
|
|
|
/*
|
|
* get a urb from the receive_pool and submit it on the read endpoint.
|
|
*/
|
|
static bool xusb_listen(xusb_t *xusb)
|
|
{
|
|
xbus_t *xbus = xbus_num(xusb->xbus_num);
|
|
xframe_t *xframe;
|
|
struct uframe *uframe;
|
|
int ret = 0;
|
|
|
|
BUG_ON(!xbus);
|
|
xframe = ALLOC_RECV_XFRAME(xbus);
|
|
if (!xframe) {
|
|
XBUS_ERR(xbus, "Empty receive_pool\n");
|
|
goto out;
|
|
}
|
|
uframe = xframe_to_uframe(xframe);
|
|
uframe_recompute(uframe, XUSB_RECV);
|
|
ret = usb_submit_urb(&uframe->urb, GFP_ATOMIC);
|
|
if (ret < 0) {
|
|
static int rate_limit;
|
|
|
|
if ((rate_limit++ % 1000) == 0)
|
|
XBUS_ERR(xbus, "%s: usb_submit_urb failed: %d\n",
|
|
__func__, ret);
|
|
FREE_RECV_XFRAME(xbus, xframe);
|
|
goto out;
|
|
}
|
|
atomic_inc(&xusb->pending_reads);
|
|
ret = 1;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*------------------------- XPP USB Bus Handling -------------------*/
|
|
|
|
enum XUSB_MODELS {
|
|
MODEL_FPGA_XPD
|
|
};
|
|
|
|
static const struct xusb_model_info {
|
|
const char *desc;
|
|
int iface_num;
|
|
struct xusb_endpoint in;
|
|
struct xusb_endpoint out;
|
|
} model_table[] = {
|
|
[MODEL_FPGA_XPD] = {
|
|
.iface_num = 0,
|
|
.in = { .ep_addr = 0x86 },
|
|
.out = { .ep_addr = 0x02 },
|
|
.desc = "FPGA_XPD"
|
|
},
|
|
};
|
|
|
|
/* table of devices that work with this driver */
|
|
static const struct usb_device_id xusb_table[] = {
|
|
/* FPGA_FXS */ {USB_DEVICE(0xE4E4, 0x1132),
|
|
.driver_info = (kernel_ulong_t)&model_table[MODEL_FPGA_XPD]},
|
|
/* FPGA_1141 */ {USB_DEVICE(0xE4E4, 0x1142),
|
|
.driver_info = (kernel_ulong_t)&model_table[MODEL_FPGA_XPD]},
|
|
/* FPGA_1151 */ {USB_DEVICE(0xE4E4, 0x1152),
|
|
.driver_info = (kernel_ulong_t)&model_table[MODEL_FPGA_XPD]},
|
|
/* FPGA_1161 */ {USB_DEVICE(0xE4E4, 0x1162),
|
|
.driver_info = (kernel_ulong_t)&model_table[MODEL_FPGA_XPD]},
|
|
/* Terminate */ {}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(usb, xusb_table);
|
|
|
|
/*
|
|
* USB specific object needed to register this driver
|
|
* with the usb subsystem
|
|
*/
|
|
static struct usb_driver xusb_driver = {
|
|
.name = "xpp_usb",
|
|
.probe = xusb_probe,
|
|
.disconnect = xusb_disconnect,
|
|
.id_table = xusb_table,
|
|
};
|
|
|
|
/*
|
|
* File operations needed when we register this driver.
|
|
* This assumes that this driver NEEDS file operations,
|
|
* of course, which means that the driver is expected
|
|
* to have a node in the /dev directory. If the USB
|
|
* device were for a network interface then the driver
|
|
* would use "struct net_driver" instead, and a serial
|
|
* device would use "struct tty_driver".
|
|
*/
|
|
static const struct file_operations xusb_fops = {
|
|
/*
|
|
* The owner field is part of the module-locking
|
|
* mechanism. The idea is that the kernel knows
|
|
* which module to increment the use-counter of
|
|
* BEFORE it calls the device's open() function.
|
|
* This also means that the kernel can decrement
|
|
* the use-counter again before calling release()
|
|
* or should the open() function fail.
|
|
*/
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
/*
|
|
* usb class driver info in order to get a minor number from the usb core,
|
|
* and to have the device registered with devfs and the driver core
|
|
*/
|
|
static struct usb_class_driver xusb_class = {
|
|
.name = "usb/xpp_usb%d",
|
|
.fops = &xusb_fops,
|
|
.minor_base = USB_SKEL_MINOR_BASE,
|
|
};
|
|
|
|
/*
|
|
* Check that an endpoint's wMaxPacketSize attribute is 512. This
|
|
* indicates that it is a USB2's high speed end point.
|
|
*
|
|
* If it is 64, it means we have a USB1 controller. By default we do not
|
|
* support it and just fail the probe of the device. However if the user
|
|
* has set usb1=1, we continue and just put a notice.
|
|
*
|
|
* Returns true if all OK, false otherwise.
|
|
*/
|
|
static int check_usb1(struct usb_endpoint_descriptor *endpoint)
|
|
{
|
|
const char *msg =
|
|
(usb_pipein(endpoint->bEndpointAddress)) ? "input" : "output";
|
|
|
|
if (endpoint->wMaxPacketSize >= sizeof(xpacket_t))
|
|
return 1;
|
|
|
|
if (usb1) {
|
|
NOTICE("USB1 endpoint detected: "
|
|
"USB %s endpoint 0x%X support only wMaxPacketSize=%d\n",
|
|
msg,
|
|
endpoint->bEndpointAddress,
|
|
endpoint->wMaxPacketSize);
|
|
return 1;
|
|
}
|
|
NOTICE("USB1 endpoint detected: "
|
|
"Device disabled. To enable: usb1=1, and read docs. "
|
|
"(%s, endpoint %d, size %d)\n",
|
|
msg, endpoint->bEndpointAddress, endpoint->wMaxPacketSize);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* set up the endpoint information
|
|
* check out the endpoints
|
|
* FIXME: Should be simplified (above 2.6.10) to use
|
|
* usb_dev->ep_in[0..16] and usb_dev->ep_out[0..16]
|
|
*/
|
|
static int set_endpoints(xusb_t *xusb, struct usb_host_interface *iface_desc,
|
|
struct xusb_model_info *model_info)
|
|
{
|
|
struct usb_endpoint_descriptor *endpoint;
|
|
struct xusb_endpoint *xusb_ep;
|
|
int ep_addr;
|
|
int i;
|
|
|
|
#define BULK_ENDPOINT(ep) \
|
|
(((ep)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \
|
|
USB_ENDPOINT_XFER_BULK)
|
|
|
|
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
|
|
endpoint = &iface_desc->endpoint[i].desc;
|
|
ep_addr = endpoint->bEndpointAddress;
|
|
|
|
if (!BULK_ENDPOINT(endpoint)) {
|
|
DBG(DEVICES,
|
|
"endpoint 0x%x is not bulk: mbAttributes=0x%X\n",
|
|
ep_addr, endpoint->bmAttributes);
|
|
continue;
|
|
}
|
|
if (usb_pipein(ep_addr)) { /* Input */
|
|
if (ep_addr == model_info->in.ep_addr) {
|
|
if (!check_usb1(endpoint))
|
|
return 0;
|
|
xusb_ep = &xusb->endpoints[XUSB_RECV];
|
|
xusb_ep->ep_addr = ep_addr;
|
|
xusb_ep->max_size = endpoint->wMaxPacketSize;
|
|
xusb_ep->callback = xpp_receive_callback;
|
|
}
|
|
} else { /* Output */
|
|
if (ep_addr == model_info->out.ep_addr) {
|
|
if (!check_usb1(endpoint))
|
|
return 0;
|
|
xusb_ep = &xusb->endpoints[XUSB_SEND];
|
|
xusb_ep->ep_addr = ep_addr;
|
|
xusb_ep->max_size = endpoint->wMaxPacketSize;
|
|
xusb_ep->callback = xpp_send_callback;
|
|
}
|
|
}
|
|
}
|
|
if (!xusb->endpoints[XUSB_RECV].ep_addr
|
|
|| !xusb->endpoints[XUSB_SEND].ep_addr) {
|
|
XUSB_ERR(xusb, "Couldn't find bulk-in or bulk-out endpoints\n");
|
|
return 0;
|
|
}
|
|
DBG(DEVICES, "in=0x%02X out=0x%02X\n",
|
|
xusb->endpoints[XUSB_RECV].ep_addr,
|
|
xusb->endpoints[XUSB_SEND].ep_addr);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* xusb_probe
|
|
*
|
|
* Called by the usb core when a new device is connected that it thinks
|
|
* this driver might be interested in.
|
|
*/
|
|
static int xusb_probe(struct usb_interface *interface,
|
|
const struct usb_device_id *id)
|
|
{
|
|
struct usb_device *udev = interface_to_usbdev(interface);
|
|
struct usb_host_interface *iface_desc =
|
|
usb_altnum_to_altsetting(interface, 0);
|
|
xusb_t *xusb = NULL;
|
|
struct xusb_model_info *model_info =
|
|
(struct xusb_model_info *)id->driver_info;
|
|
#ifdef CONFIG_PROC_FS
|
|
struct proc_dir_entry *procsummary = NULL;
|
|
#endif
|
|
xbus_t *xbus = NULL;
|
|
unsigned long flags;
|
|
int retval = -ENOMEM;
|
|
int i;
|
|
|
|
DBG(DEVICES, "New XUSB device MODEL=%s\n", model_info->desc);
|
|
if (iface_desc->desc.bInterfaceNumber != model_info->iface_num) {
|
|
DBG(DEVICES, "Skip interface #%d != #%d\n",
|
|
iface_desc->desc.bInterfaceNumber, model_info->iface_num);
|
|
return -ENODEV;
|
|
}
|
|
mutex_lock(&protect_xusb_devices);
|
|
if ((retval = usb_reset_device(udev)) < 0) {
|
|
ERR("usb_reset_device failed: %d\n", retval);
|
|
goto probe_failed;
|
|
}
|
|
if (!model_info) {
|
|
ERR("Missing endpoint setup for this device %d:%d\n",
|
|
udev->descriptor.idVendor, udev->descriptor.idProduct);
|
|
retval = -ENODEV;
|
|
goto probe_failed;
|
|
}
|
|
|
|
/* allocate memory for our device state and initialize it */
|
|
xusb = KZALLOC(sizeof(xusb_t), GFP_KERNEL);
|
|
if (xusb == NULL) {
|
|
ERR("xpp_usb: Unable to allocate new xpp usb bus\n");
|
|
retval = -ENOMEM;
|
|
goto probe_failed;
|
|
}
|
|
sema_init(&xusb->sem, 1);
|
|
atomic_set(&xusb->pending_writes, 0);
|
|
atomic_set(&xusb->pending_reads, 0);
|
|
atomic_set(&xusb->usb_sluggish_count, 0);
|
|
xusb->udev = udev;
|
|
xusb->interface = interface;
|
|
xusb->model_info = model_info;
|
|
|
|
if (!set_endpoints(xusb, iface_desc, model_info)) {
|
|
retval = -ENODEV;
|
|
goto probe_failed;
|
|
}
|
|
xusb->serial = udev->serial;
|
|
xusb->manufacturer = udev->manufacturer;
|
|
xusb->product = udev->product;
|
|
xusb->interface_name = iface_desc->string;
|
|
INFO("XUSB: %s -- %s -- %s\n", xusb->manufacturer, xusb->product,
|
|
xusb->interface_name);
|
|
|
|
/* allow device read, write and ioctl */
|
|
xusb->present = 1;
|
|
|
|
/* we can register the device now, as it is ready */
|
|
usb_set_intfdata(interface, xusb);
|
|
retval = usb_register_dev(interface, &xusb_class);
|
|
if (retval) {
|
|
/* something prevented us from registering this driver */
|
|
ERR("Not able to get a minor for this device.\n");
|
|
goto probe_failed;
|
|
}
|
|
|
|
xusb->minor = interface->minor;
|
|
|
|
/* let the user know what node this device is now attached to */
|
|
DBG(DEVICES, "USB XPP device now attached to minor %d\n", xusb->minor);
|
|
xbus =
|
|
xbus_new(&xusb_ops,
|
|
min(xusb->endpoints[XUSB_SEND].max_size,
|
|
xusb->endpoints[XUSB_RECV].max_size), &udev->dev,
|
|
xusb);
|
|
if (!xbus) {
|
|
retval = -ENOMEM;
|
|
goto probe_failed;
|
|
}
|
|
snprintf(xbus->transport.model_string,
|
|
ARRAY_SIZE(xbus->transport.model_string), "usb:%04x/%04x/%x",
|
|
udev->descriptor.idVendor, udev->descriptor.idProduct,
|
|
udev->descriptor.bcdDevice);
|
|
spin_lock_irqsave(&xusb_lock, flags);
|
|
for (i = 0; i < MAX_BUSES; i++) {
|
|
if (xusb_array[i] == NULL)
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&xusb_lock, flags);
|
|
if (i >= MAX_BUSES) {
|
|
ERR("xpp_usb: Too many XPP USB buses\n");
|
|
retval = -ENOMEM;
|
|
goto probe_failed;
|
|
}
|
|
/* May trunacte... ignore */
|
|
usb_make_path(udev, xusb->path, XBUS_DESCLEN);
|
|
snprintf(xbus->connector, XBUS_DESCLEN, "%s", xusb->path);
|
|
if (xusb->serial && xusb->serial[0])
|
|
snprintf(xbus->label, LABEL_SIZE, "usb:%s", xusb->serial);
|
|
xusb->index = i;
|
|
xusb_array[i] = xusb;
|
|
XUSB_DBG(DEVICES, xusb, "GOT XPP USB BUS: %s\n", xbus->connector);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
DBG(PROC,
|
|
"Creating proc entry " PROC_USBXPP_SUMMARY " in bus proc dir.\n");
|
|
procsummary = proc_create_data(PROC_USBXPP_SUMMARY, 0444,
|
|
xbus->proc_xbus_dir, &xusb_read_proc_ops,
|
|
xusb);
|
|
if (!procsummary) {
|
|
XBUS_ERR(xbus, "Failed to create proc file '%s'\n",
|
|
PROC_USBXPP_SUMMARY);
|
|
// FIXME: better error handling
|
|
retval = -EIO;
|
|
goto probe_failed;
|
|
}
|
|
#endif
|
|
bus_count++;
|
|
xusb->xbus_num = xbus->num;
|
|
/* prepare several pending frames for receive side */
|
|
for (i = 0; i < 10; i++)
|
|
xusb_listen(xusb);
|
|
xbus_connect(xbus);
|
|
mutex_unlock(&protect_xusb_devices);
|
|
return retval;
|
|
probe_failed:
|
|
ERR("Failed to initialize xpp usb bus: %d\n", retval);
|
|
usb_set_intfdata(interface, NULL);
|
|
if (xusb) {
|
|
if (xusb->minor) { /* passed registration phase */
|
|
ERR("Calling usb_deregister_dev()\n");
|
|
usb_deregister_dev(interface, &xusb_class);
|
|
}
|
|
ERR("Removing failed xusb\n");
|
|
KZFREE(xusb);
|
|
}
|
|
if (xbus) {
|
|
#ifdef CONFIG_PROC_FS
|
|
if (procsummary) {
|
|
XBUS_DBG(PROC, xbus,
|
|
"Remove proc_entry: " PROC_USBXPP_SUMMARY
|
|
"\n");
|
|
remove_proc_entry(PROC_USBXPP_SUMMARY,
|
|
xbus->proc_xbus_dir);
|
|
procsummary = NULL;
|
|
}
|
|
#endif
|
|
ERR("Calling xbus_disconnect()\n");
|
|
xbus_disconnect(xbus); // Blocking until fully deactivated!
|
|
}
|
|
mutex_unlock(&protect_xusb_devices);
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* xusb_disconnect
|
|
*
|
|
* Called by the usb core when the device is removed from the system.
|
|
*
|
|
* This routine guarantees that the driver will not submit any more urbs
|
|
* by clearing dev->udev. It is also supposed to terminate any currently
|
|
* active urbs. Unfortunately, usb_bulk_msg(), used in xusb_read(), does
|
|
* not provide any way to do this. But at least we can cancel an active
|
|
* write.
|
|
*/
|
|
static void xusb_disconnect(struct usb_interface *interface)
|
|
{
|
|
struct usb_host_interface *iface_desc =
|
|
usb_altnum_to_altsetting(interface, 0);
|
|
xusb_t *xusb;
|
|
xbus_t *xbus;
|
|
int i;
|
|
|
|
DBG(DEVICES, "CALLED on interface #%d\n",
|
|
iface_desc->desc.bInterfaceNumber);
|
|
/* prevent races with open() */
|
|
mutex_lock(&protect_xusb_devices);
|
|
|
|
xusb = usb_get_intfdata(interface);
|
|
usb_set_intfdata(interface, NULL);
|
|
xusb->present = 0;
|
|
xbus = xbus_num(xusb->xbus_num);
|
|
|
|
/* find our xusb */
|
|
for (i = 0; i < MAX_BUSES; i++) {
|
|
if (xusb_array[i] == xusb)
|
|
break;
|
|
}
|
|
BUG_ON(i >= MAX_BUSES);
|
|
xusb_array[i] = NULL;
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
if (xbus->proc_xbus_dir) {
|
|
XBUS_DBG(PROC, xbus,
|
|
"Remove proc_entry: " PROC_USBXPP_SUMMARY "\n");
|
|
remove_proc_entry(PROC_USBXPP_SUMMARY, xbus->proc_xbus_dir);
|
|
}
|
|
#endif
|
|
xbus_disconnect(xbus); // Blocking until fully deactivated!
|
|
|
|
down(&xusb->sem);
|
|
|
|
/* give back our minor */
|
|
usb_deregister_dev(interface, &xusb_class);
|
|
|
|
up(&xusb->sem);
|
|
DBG(DEVICES, "Semaphore released\n");
|
|
XUSB_INFO(xusb, "now disconnected\n");
|
|
KZFREE(xusb);
|
|
|
|
mutex_unlock(&protect_xusb_devices);
|
|
}
|
|
|
|
static void xpp_send_callback(USB_PASS_CB(urb))
|
|
{
|
|
struct uframe *uframe = urb_to_uframe(urb);
|
|
xframe_t *xframe = &uframe->xframe;
|
|
xusb_t *xusb = uframe->xusb;
|
|
xbus_t *xbus = xbus_num(xusb->xbus_num);
|
|
struct timeval now;
|
|
long usec;
|
|
int writes = atomic_read(&xusb->pending_writes);
|
|
int i;
|
|
|
|
if (!xbus) {
|
|
XUSB_ERR(xusb,
|
|
"Sent URB does not belong to a valid xbus...\n");
|
|
return;
|
|
}
|
|
//flip_parport_bit(6);
|
|
atomic_dec(&xusb->pending_writes);
|
|
do_gettimeofday(&now);
|
|
xusb->last_tx = xframe->tv_submitted;
|
|
usec = usec_diff(&now, &xframe->tv_submitted);
|
|
if (usec < 0)
|
|
usec = 0; /* System clock jumped */
|
|
if (usec > xusb->max_tx_delay)
|
|
xusb->max_tx_delay = usec;
|
|
i = usec / USEC_BUCKET;
|
|
if (i >= NUM_BUCKETS)
|
|
i = NUM_BUCKETS - 1;
|
|
xusb->usb_tx_delay[i]++;
|
|
if (unlikely(usec > tx_sluggish)) {
|
|
if (xusb->sluggish_debounce++ > drop_pcm_after) {
|
|
static int rate_limit;
|
|
|
|
/* skip first messages */
|
|
if ((rate_limit++ % 1003) == 500)
|
|
XUSB_NOTICE(xusb,
|
|
"Sluggish USB. Dropping next PCM frame "
|
|
"(pending_writes=%d)\n",
|
|
writes);
|
|
atomic_inc(&xusb->usb_sluggish_count);
|
|
xusb->drop_pcm = 1;
|
|
xusb->sluggish_debounce = 0;
|
|
}
|
|
} else {
|
|
xusb->sluggish_debounce = 0;
|
|
xusb->drop_pcm = 0;
|
|
}
|
|
/* sync/async unlink faults aren't errors */
|
|
if (urb->status
|
|
&& !(urb->status == -ENOENT || urb->status == -ECONNRESET)) {
|
|
static int rate_limit;
|
|
if ((rate_limit++ % 1000) < 10) {
|
|
XUSB_ERR(xusb,
|
|
"nonzero write bulk status received: "
|
|
"%d (pending_writes=%d)\n",
|
|
urb->status, writes);
|
|
dump_xframe("usb-write-error", xbus, xframe, DBG_ANY);
|
|
}
|
|
XUSB_COUNTER(xusb, TX_ERRORS)++;
|
|
} else
|
|
XUSB_COUNTER(xusb, TX_FRAMES)++;
|
|
FREE_SEND_XFRAME(xbus, xframe);
|
|
if (!xusb->present)
|
|
XUSB_ERR(xusb, "A urb from non-connected device?\n");
|
|
}
|
|
|
|
static void xpp_receive_callback(USB_PASS_CB(urb))
|
|
{
|
|
struct uframe *uframe = urb_to_uframe(urb);
|
|
xframe_t *xframe = &uframe->xframe;
|
|
xusb_t *xusb = uframe->xusb;
|
|
xbus_t *xbus = xbus_num(xusb->xbus_num);
|
|
size_t size;
|
|
bool do_resubmit = 1;
|
|
struct timeval now;
|
|
|
|
do_gettimeofday(&now);
|
|
atomic_dec(&xusb->pending_reads);
|
|
if (!xbus) {
|
|
XUSB_ERR(xusb,
|
|
"Received URB does not belong to a valid xbus...\n");
|
|
return;
|
|
}
|
|
if (!xusb->present) {
|
|
do_resubmit = 0;
|
|
goto err;
|
|
}
|
|
if (urb->status) {
|
|
DBG(GENERAL, "nonzero read bulk status received: %d\n",
|
|
urb->status);
|
|
XUSB_COUNTER(xusb, RX_ERRORS)++;
|
|
goto err;
|
|
}
|
|
size = urb->actual_length;
|
|
if (size == 0) {
|
|
static int rate_limit;
|
|
|
|
if ((rate_limit++ % 5003) == 0)
|
|
XUSB_NOTICE(xusb, "Received a zero length URBs (%d)\n",
|
|
rate_limit);
|
|
XUSB_COUNTER(xusb, RCV_ZERO_LEN)++;
|
|
goto err;
|
|
}
|
|
atomic_set(&xframe->frame_len, size);
|
|
xframe->tv_received = now;
|
|
|
|
// if (debug)
|
|
// dump_xframe("USB_FRAME_RECEIVE", xbus, xframe, debug);
|
|
XUSB_COUNTER(xusb, RX_FRAMES)++;
|
|
if (xusb->drop_pcm) {
|
|
/* some protocol analysis */
|
|
static int rate_limit;
|
|
xpacket_t *pack = (xpacket_t *)(xframe->packets);
|
|
bool is_pcm = XPACKET_IS_PCM(pack);
|
|
|
|
if (is_pcm) {
|
|
if ((rate_limit++ % 1000) == 0)
|
|
XUSB_ERR(xusb,
|
|
"Sluggish USB: drop rx-pcm (%d)\n",
|
|
rate_limit);
|
|
/* Let trickle of RX-PCM, so Astribank will not reset */
|
|
if (sluggish_pcm_keepalive &&
|
|
((rate_limit % sluggish_pcm_keepalive)
|
|
!= 0)) {
|
|
XUSB_COUNTER(xusb, RX_DROPS)++;
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
/* Send UP */
|
|
xbus_receive_xframe(xbus, xframe);
|
|
end:
|
|
if (do_resubmit)
|
|
xusb_listen(xusb);
|
|
return;
|
|
err:
|
|
FREE_RECV_XFRAME(xbus, xframe);
|
|
goto end;
|
|
}
|
|
|
|
/*------------------------- Initialization -------------------------*/
|
|
|
|
static void xpp_usb_cleanup(void)
|
|
{
|
|
if (xusb_cache) {
|
|
kmem_cache_destroy(xusb_cache);
|
|
xusb_cache = NULL;
|
|
}
|
|
}
|
|
|
|
static int __init xpp_usb_init(void)
|
|
{
|
|
int ret;
|
|
//xusb_t *xusb;
|
|
|
|
INFO("revision %s\n", XPP_VERSION);
|
|
xusb_cache =
|
|
kmem_cache_create("xusb_cache", sizeof(xframe_t) + XFRAME_DATASIZE,
|
|
#if (LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 22)) && defined(CONFIG_SLUB)
|
|
0, SLAB_STORE_USER,
|
|
#else
|
|
0, 0,
|
|
#endif
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 23)
|
|
NULL,
|
|
#endif
|
|
NULL);
|
|
if (!xusb_cache) {
|
|
ret = -ENOMEM;
|
|
goto failure;
|
|
}
|
|
|
|
/* register this driver with the USB subsystem */
|
|
ret = usb_register(&xusb_driver);
|
|
if (ret) {
|
|
ERR("usb_register failed. Error number %d\n", ret);
|
|
goto failure;
|
|
}
|
|
return 0;
|
|
failure:
|
|
xpp_usb_cleanup();
|
|
return ret;
|
|
}
|
|
|
|
static void __exit xpp_usb_shutdown(void)
|
|
{
|
|
DBG(GENERAL, "\n");
|
|
/* deregister this driver with the USB subsystem */
|
|
usb_deregister(&xusb_driver);
|
|
xpp_usb_cleanup();
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
|
|
static int xusb_read_proc_show(struct seq_file *sfile, void *data)
|
|
{
|
|
unsigned long flags;
|
|
int i;
|
|
//unsigned long stamp = jiffies;
|
|
xusb_t *xusb = sfile->private;
|
|
uint usb_tx_delay[NUM_BUCKETS];
|
|
const int mark_limit = tx_sluggish / USEC_BUCKET;
|
|
|
|
if (!xusb)
|
|
return 0;
|
|
|
|
// TODO: probably needs a per-xusb lock:
|
|
spin_lock_irqsave(&xusb_lock, flags);
|
|
seq_printf(sfile, "Device: %03d/%03d\n", xusb->udev->bus->busnum,
|
|
xusb->udev->devnum);
|
|
seq_printf(sfile, "USB: manufacturer=%s\n", xusb->manufacturer);
|
|
seq_printf(sfile, "USB: product=%s\n", xusb->product);
|
|
seq_printf(sfile, "USB: serial=%s\n", xusb->serial);
|
|
seq_printf(sfile, "Minor: %d\nModel Info: %s\n", xusb->minor,
|
|
xusb->model_info->desc);
|
|
seq_printf(sfile,
|
|
"Endpoints:\n" "\tIn: 0x%02X - Size: %d)\n"
|
|
"\tOut: 0x%02X - Size: %d)\n",
|
|
xusb->endpoints[XUSB_RECV].ep_addr,
|
|
xusb->endpoints[XUSB_RECV].max_size,
|
|
xusb->endpoints[XUSB_SEND].ep_addr,
|
|
xusb->endpoints[XUSB_SEND].max_size);
|
|
seq_printf(sfile, "\npending_writes=%d\n",
|
|
atomic_read(&xusb->pending_writes));
|
|
seq_printf(sfile, "pending_reads=%d\n",
|
|
atomic_read(&xusb->pending_reads));
|
|
seq_printf(sfile, "max_tx_delay=%d\n", xusb->max_tx_delay);
|
|
xusb->max_tx_delay = 0;
|
|
#ifdef DEBUG_PCM_TIMING
|
|
seq_printf(sfile,
|
|
"\nstamp_last_pcm_read=%lld accumulate_diff=%lld\n",
|
|
stamp_last_pcm_read, accumulate_diff);
|
|
#endif
|
|
memcpy(usb_tx_delay, xusb->usb_tx_delay, sizeof(usb_tx_delay));
|
|
seq_printf(sfile, "usb_tx_delay[%dus - %dus]: ",
|
|
USEC_BUCKET * BUCKET_START,
|
|
USEC_BUCKET * NUM_BUCKETS);
|
|
for (i = BUCKET_START; i < NUM_BUCKETS; i++) {
|
|
seq_printf(sfile, "%6d ", usb_tx_delay[i]);
|
|
if (i == mark_limit)
|
|
seq_printf(sfile, "| ");
|
|
}
|
|
seq_printf(sfile, "\nSluggish events: %d\n",
|
|
atomic_read(&xusb->usb_sluggish_count));
|
|
seq_printf(sfile, "\nCOUNTERS:\n");
|
|
for (i = 0; i < XUSB_COUNTER_MAX; i++) {
|
|
seq_printf(sfile, "\t%-15s = %d\n", xusb_counters[i].name,
|
|
xusb->counters[i]);
|
|
}
|
|
#if 0
|
|
seq_printf(sfile, "<-- len=%d\n", len);
|
|
#endif
|
|
spin_unlock_irqrestore(&xusb_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int xusb_read_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, xusb_read_proc_show, PDE_DATA(inode));
|
|
}
|
|
|
|
static const struct file_operations xusb_read_proc_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = xusb_read_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
|
|
#endif
|
|
|
|
MODULE_DESCRIPTION("XPP USB Transport Driver");
|
|
MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(XPP_VERSION);
|
|
|
|
module_init(xpp_usb_init);
|
|
module_exit(xpp_usb_shutdown);
|