diff options
Diffstat (limited to 'xpp/xpp_usb.c')
-rw-r--r-- | xpp/xpp_usb.c | 882 |
1 files changed, 882 insertions, 0 deletions
diff --git a/xpp/xpp_usb.c b/xpp/xpp_usb.c new file mode 100644 index 0000000..2c8b4a6 --- /dev/null +++ b/xpp/xpp_usb.c @@ -0,0 +1,882 @@ +/* + * Written by Oron Peled <oron@actcom.co.il> + * Copyright (C) 2004-2005, 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> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +# warning "This module is tested only with 2.6 kernels" +#endif + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.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 "xpp_zap.h" + +static const char revision[] = "$Revision$"; + +DEF_PARM(int, print_dbg, 1, "Print DBG statements"); /* must be before zap_debug.h */ + +#include "zap_debug.h" + +/* FIXME: A flag that was deprecated at some point, and rather useless */ +/* anyway. Only used in the code or-ed to other flags */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,14) +# define URB_ASYNC_UNLINK 0 +#endif + +#define USBDEV_MAX 10 +/* Get a minor range for your devices from the usb maintainer */ +#define USB_SKEL_MINOR_BASE 192 + +#ifdef CONFIG_PROC_FS +#define PROC_XBUSES "xpp_usb" +#define PROC_USBXPP_SUMMARY "xpp_usb" +#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 epnum; + int max_size; +}; + +static int xusb_packet_send(xbus_t *xbus, xpacket_t *pack); + +xbus_ops_t xusb_ops = { + .packet_send = xusb_packet_send, + .packet_new = NULL, // Default allocator + .packet_free = NULL, // Default deallocator +}; + +enum { + XUSB_N_RX_PACKETS, + XUSB_N_TX_PACKETS, + XUSB_N_RX_ERRORS, + XUSB_N_TX_ERRORS, + XUSB_N_PCM_READS, + XUSB_N_PCM_WRITES, +}; + +#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_PACKETS), + C_(TX_PACKETS), + C_(RX_ERRORS), + C_(TX_ERRORS), + C_(PCM_READS), + C_(PCM_WRITES), +}; + +#undef C_ + +#define XUSB_COUNTER_MAX ARRAY_SIZE(xusb_counters) + +/* + * USB XPP Bus (a USB Device) + */ +struct xpp_usb_bus { + xbus_t *xbus; + 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 */ + + struct xusb_model_info *model_info; + struct xusb_endpoint ep_in; + struct xusb_endpoint ep_out; + + struct urb *read_urb; + + struct completion write_finished; /* wait for the write to finish */ + + int present; /* if the device is not disconnected */ + int reading; /* is the read_urb reading (listening) */ + struct semaphore sem; /* locks this structure */ + int counters[XUSB_COUNTER_MAX]; +}; + +static spinlock_t xusb_lock = SPIN_LOCK_UNLOCKED; +static struct xpp_usb_bus *xusb_array[USBDEV_MAX] = {}; +static unsigned bus_count = 0; + + +/* prevent races between open() and disconnect() */ +static DECLARE_MUTEX (disconnect_sem); + +/* + * Function Prototypes + */ +#if 0 +static ssize_t xusb_read (struct file *file, char *buffer, size_t count, loff_t *ppos); +static ssize_t xusb_write (struct file *file, const char *buffer, size_t count, loff_t *ppos); +static int xusb_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); +static int xusb_open (struct inode *inode, struct file *file); +static int xusb_release (struct inode *inode, struct file *file); +static void xusb_write_bulk_callback (struct urb *urb, struct pt_regs *regs); +#endif +static void xpp_urb_delete(struct urb *urb); +static struct urb *xpp_urb_new(struct xpp_usb_bus *dev, unsigned int ep_addr, size_t size, usb_complete_t urb_cb); +static void xpp_send_callback(struct urb *urb, struct pt_regs *regs); +static void xpp_receive_callback(struct urb *urb, struct pt_regs *regs); + +static int xusb_probe (struct usb_interface *interface, const struct usb_device_id *id); +static void xusb_disconnect (struct usb_interface *interface); +static int xusb_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data); + +/*------------------------------------------------------------------*/ + +#if 0 +/** + * Allocates a new XPP packet. + * @xbus The XPP bus in which the packet will flow (for counters + * maintenance) + * @flags Flags for kernel memory allocation. + * @returns A pointer to the new packet, or NULL in case of failure. + * + * + * Packet allocation/deallocation: + * Sent packets: + * - Allocated by protocol commands + * - Deallocated by xmus_xmitter + * Receive packets: + * - Allocated/deallocated by xbus_xmiter + */ +xpacket_t *xusb_packet_new(xbus_t *xbus, int flags) +{ + xpacket_t *pack; + + /* To avoid races we increament counter in advance and decrement it later + * in case of failure */ + atomic_inc(&xbus->packet_counter); + //DBG("Incremented packet_counter of bus %s (new packet) to %d\n", + // xbus->busname, atomic_read(&xbus->packet_counter)); + pack = kmem_cache_alloc(packet_cache, flags); + if (pack) { + memset(pack, 0, sizeof(xpacket_t)); + atomic_inc(&xpacket_count); + } else { + atomic_dec(&xbus->packet_counter); + //DBG("Decremented packet_counter of bus %s (failed new packet) to %d\n", + // xbus->busname, atomic_read(&xbus->packet_counter)); + } + return pack; +} + +void xusb_packet_free(xbus_t *xbus, xpacket_t *p) +{ + kmem_cache_free(packet_cache, p); + atomic_dec(&xpacket_count); + atomic_dec(&xbus->packet_counter); + //DBG("Decremented packet_counter of bus %s (freed packet) to %d\n", + // xbus->busname, atomic_read(&xbus->packet_counter)); +} +#endif + +static int xusb_packet_send(xbus_t *xbus, xpacket_t *pack) +{ + struct xpp_usb_bus *xusb = xbus->priv; + struct urb *urb; + int ret = 0; + size_t size; + + BUG_ON(!pack); + if(!xusb->present) { + NOTICE("tried to send packets to non-exitant USB device. Ignored\n"); + goto error; + } +#if SOFT_SIMULATOR + { + int toxpd = XPD_NUM(pack->content.addr); + + if (xbus->sim[toxpd].softloop_xpd) { + // "send" through loopback queue + //DBG("%s: ENQUEUE toxpd=%d, opcode=%X\n", xbus->busname, toxpd, pack->content.opcode); + XBUS_COUNTER(xbus, SOFTSIM_PACKETS)++; + xbus_enqueue_packet(xbus, &xbus->sim_packet_queue, pack); + ret = queue_work(xbus->sim_workqueue, &xbus->sim_work); + if(ret < 0) { + ERR("%s: queue_work failed with %d (ignoring)\n", __FUNCTION__, ret); + goto error; + } + } + return 0; + } +#endif + size = min(PACKET_LEN(pack), (size_t)xusb->ep_out.max_size); + if(pack->content.opcode == XPROTO_NAME(GLOBAL,PCM_WRITE)) { + XUSB_COUNTER(xusb, PCM_WRITES)++; + +#ifdef DEBUG_PCM_TIMING + /* + * DEBUG: high-res timing of PCM_READ to PCM_WRITE + */ + cycles_t diff = get_cycles() - stamp_last_pcm_read; + accumulate_diff += diff; +#endif +#if 0 + static int rate_limit; + if((rate_limit++ % 1009) < 3) { + dump_packet("USB SEND PCM", pack, print_dbg); + } +#endif + } else { + dump_packet("USB_PACKET_SEND", pack, print_dbg); + } + urb = xpp_urb_new(xusb, xusb->ep_out.epnum, size, xpp_send_callback); + if (!urb) { + ERR("No free urbs available\n"); + ret = -ENOMEM; + goto error; + } + + /* FIXME: FIXME: FIXME: we use copy+free until low-level drivers allocate memory themselves */ + memcpy(urb->transfer_buffer, &pack->content, size); + xbus->ops->packet_free(xbus, pack); + + ret = usb_submit_urb(urb, GFP_KERNEL); + if(ret < 0) { + ERR("%s: failed submit_urb\n", __FUNCTION__); + XUSB_COUNTER(xusb, TX_ERRORS)++; + xpp_urb_delete(urb); + return -EBADF; + } + return 0; +error: + xbus->ops->packet_free(xbus, pack); // FIXME: eventually will be done in the urb callback + return ret; +} + +static void xpp_urb_delete(struct urb *urb) +{ + // DBG("%s: (%d) %p %X", __FUNCTION__, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); + usb_buffer_free (urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, + urb->transfer_dma); + usb_free_urb (urb); +} + +static struct urb *xpp_urb_new(struct xpp_usb_bus *dev, unsigned int ep_addr, size_t size, usb_complete_t urb_cb) + +{ + struct usb_device *udev = dev->udev; + struct urb *urb; + unsigned char *buffer; /* the buffer to send data */ + unsigned int epnum = ep_addr & USB_ENDPOINT_NUMBER_MASK; + int pipe = usb_pipein(ep_addr) + ? usb_rcvbulkpipe(udev, epnum) + : usb_sndbulkpipe(udev, epnum); + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + err("No free urbs available"); + return NULL; + } + + /* on some platforms using this kind of buffer alloc + * call eliminates a dma "bounce buffer". + * + * NOTE: you'd normally want i/o buffers that hold + * more than one packet, so that i/o delays between + * packets don't hurt throughput. (Probably applies only to isochronous + * transfers) + */ + urb->transfer_flags = (URB_NO_TRANSFER_DMA_MAP | URB_ASYNC_UNLINK); + buffer = usb_buffer_alloc(udev, size, GFP_ATOMIC, &urb->transfer_dma); + // DBG("(%d) %p / %x", size, buffer, urb->transfer_dma); + if (!buffer) { + err("Couldn't allocate buffer"); + usb_free_urb(urb); + return NULL; + } + usb_fill_bulk_urb(urb, udev, pipe, buffer, size, urb_cb, dev); + return urb; +} + +/*------------------------- XPP USB Bus Handling -------------------*/ + +static struct xusb_model_info { + const char *desc; + struct xusb_endpoint in; + struct xusb_endpoint out; + xbus_type_t bus_type; +} model_table[] = { + { .in = { .epnum = 0x86 }, .out = { .epnum = 0x2 }, .bus_type = FIRMWARE_LOOPBACK, .desc = "bulkloop.hex" }, + { .in = { .epnum = 0x86 }, .out = { .epnum = 0x2 }, .bus_type = FIRMWARE_LOOPBACK, .desc = "FPGA_bulkloop.hex" }, + { .in = { .epnum = 0x86 }, .out = { .epnum = 0x2 }, .bus_type = FIRMWARE_XPP, .desc = "FPGA_XPD.hex" }, +}; + +/* table of devices that work with this driver */ +static struct usb_device_id xusb_table [] = { +// { USB_DEVICE(0x04B4, 0x8613) }, // default of cypress + { USB_DEVICE(0x0547, 0x1002), .driver_info=(int)&model_table[0] }, // bulkloop.hex +// { USB_DEVICE(0x04B4, 0x1004), .driver_info=(int)&model_table[1] }, // FPGA_bulkloop.hex +// { USB_DEVICE(0x04B4, 0x1004), .driver_info=(int)&model_table[2] }, // FIXME: temporary test for Dima + { USB_DEVICE(0xE4E4, 0x2211), .driver_info=(int)&model_table[2] }, // FPGA_XPD.hex + //{ USB_DEVICE(0x0548, 0x1) }, + //{ USB_DEVICE(0x062a, 0x0) }, + /* "Gadget Zero" firmware runs under Linux */ + //{ USB_DEVICE(0x0525, 0xa4a0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, xusb_table); + + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver xusb_driver = { + .owner = THIS_MODULE, + .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 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, +/* FIXME: The sysfs class interfase seems to have chaged around here */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15) + .mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, +#endif + .minor_base = USB_SKEL_MINOR_BASE, +}; + +/* + * 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(struct xpp_usb_bus *xusb, struct usb_interface *interface, struct xusb_model_info *model_info) +{ + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int i; + + iface_desc = &interface->altsetting[0]; + DBG("Found interface. Endpoints: %d\n", iface_desc->desc.bNumEndpoints); + +#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; + int epnum = endpoint->bEndpointAddress; + + if(!BULK_ENDPOINT(endpoint)) { + DBG("endpoint 0x%x is not bulk: mbAttributes=0x%X\n", + epnum, endpoint->bmAttributes); + continue; + } + if(epnum & USB_DIR_IN) { // Input + if(epnum == model_info->in.epnum) { + if(endpoint->wMaxPacketSize < sizeof(xpacket_raw_t)) { + ERR("USB input endpoint 0x%X support only wMaxPacketSize=%d (need USB-2)\n", epnum, endpoint->wMaxPacketSize); + break; + } + xusb->ep_in.epnum = epnum; + xusb->ep_in.max_size = endpoint->wMaxPacketSize; + } + } else { // Output + if(epnum == model_info->out.epnum) { + if(endpoint->wMaxPacketSize < sizeof(xpacket_raw_t)) { + ERR("USB output endpoint 0x%X support only wMaxPacketSize=%d (need USB-2)\n", epnum, endpoint->wMaxPacketSize); + break; + } + xusb->ep_out.epnum = epnum; + xusb->ep_out.max_size = endpoint->wMaxPacketSize; + } + } + } + if (!xusb->ep_in.epnum || !xusb->ep_out.epnum) { + ERR("Couldn't find bulk-in or bulk-out endpoints\n"); + return 0; + } + 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 xpp_usb_bus *xusb = NULL; + struct xusb_model_info *model_info = (struct xusb_model_info*)id->driver_info; + struct proc_dir_entry *procsummary; + xbus_t *xbus; + unsigned long flags; + int retval = -ENOMEM; + int i; + + INFO("New XUSB device MODEL=%s bus_type=%d\n", model_info->desc, model_info->bus_type); + + 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 = kmalloc(sizeof(struct xpp_usb_bus), GFP_KERNEL); + if (xusb == NULL) { + ERR("xpp_usb: Unable to allocate new xpp usb bus\n"); + retval = -ENOMEM; + goto probe_failed; + } + memset(xusb, 0, sizeof(struct xpp_usb_bus)); + + init_MUTEX (&xusb->sem); + xusb->udev = udev; + xusb->interface = interface; + xusb->model_info = model_info; + + if(!set_endpoints(xusb, interface, model_info)) { + retval = -ENODEV; + goto probe_failed; + } + xusb->read_urb = xpp_urb_new(xusb, xusb->ep_in.epnum, xusb->ep_in.max_size, xpp_receive_callback); + if (!xusb->read_urb) { + ERR("No free urbs available\n"); + retval = -ENOMEM; + goto probe_failed; + } + /* 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."); + goto probe_failed; + } + + xusb->minor = interface->minor; + + /* let the user know what node this device is now attached to */ + INFO ("USB XPP device now attached to minor %d\n", xusb->minor); + + /* Allocate high level structures */ + xbus = xbus_new((model_info->bus_type == FIRMWARE_LOOPBACK) ? ~0 : 0); + if(!xbus) { + retval = -ENOMEM; + goto probe_failed; + } + xusb->xbus = xbus; + xbus->priv = xusb; + xbus->bus_type = model_info->bus_type; + + spin_lock_irqsave(&xusb_lock, flags); + for(i = 0; i < USBDEV_MAX; i++) { + if(xusb_array[i] == NULL) + break; + } + if(i >= USBDEV_MAX) { + ERR("xpp_usb: Too many XPP USB buses\n"); + retval = -ENOMEM; + goto probe_failed; + } + spin_unlock_irqrestore(&xusb_lock, flags); + { + char path[XBUS_DESCLEN]; + + usb_make_path(udev, path, XBUS_DESCLEN); // May trunacte... ignore + snprintf(xbus->busdesc, XBUS_DESCLEN, "%s", path); + } + xbus->ops = &xusb_ops; + + DBG("GOT XPP USB BUS #%d: %s (type=%d)\n", i, xbus->busdesc, xbus->bus_type); + + xusb_array[i] = xusb; + + +#ifdef CONFIG_PROC_FS + DBG("Creating proc entry " PROC_USBXPP_SUMMARY " in bus proc dir.\n"); + procsummary = create_proc_read_entry(PROC_USBXPP_SUMMARY, 0444, xbus->proc_xbus_dir, + xusb_read_proc, xusb); + //xbus->procsummary = 1; // temporary: not 0, for the condition below + if (!procsummary) { + ERR("Failed to create proc read entry for xbus %s\n", xbus->busname); + // FIXME: better error handling + retval = -EIO; + goto probe_failed; + } +#endif + retval = usb_submit_urb(xusb->read_urb, GFP_ATOMIC); + if(retval < 0) { + ERR("%s: Failed to submit the receive URB errno=%d\n", __FUNCTION__, retval); + } + bus_count++; + xbus_activate(xusb->xbus); + return retval; +probe_failed: + ERR("Failed to initialize xpp usb bus: %d\n", retval); + usb_set_intfdata (interface, NULL); + if(xusb) { + if(xusb->read_urb) + xpp_urb_delete(xusb->read_urb); + if(xusb->minor) // passed registration phase + usb_deregister_dev(interface, &xusb_class); + kfree(xusb); + } + 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 xpp_usb_bus *xusb; + xbus_t *xbus; + int minor; + int i; + + DBG("CALLED\n"); + /* prevent races with open() */ + down (&disconnect_sem); + + xusb = usb_get_intfdata (interface); + usb_set_intfdata (interface, NULL); + xbus = xusb->xbus; + + /* find our xusb */ + for(i = 0; i < USBDEV_MAX; i++) { + if(xusb_array[i] == xusb) + break; + } + BUG_ON(i >= USBDEV_MAX); + xusb_array[i] = NULL; + +#ifdef CONFIG_PROC_FS + if(xbus->proc_xbus_dir) { + remove_proc_entry(PROC_USBXPP_SUMMARY, xbus->proc_xbus_dir); + } +#endif + xusb->present = 0; + xbus_deactivate(xbus); // Blocking until fully deactivated! + + down (&xusb->sem); + + minor = xusb->minor; + + /* give back our minor */ + usb_deregister_dev (interface, &xusb_class); + + /* terminate an ongoing write */ + // FIXME: Does it really kill pending URB's? + + if(xusb->read_urb) + xpp_urb_delete(xusb->read_urb); + + up (&xusb->sem); + DBG("Semaphore released\n"); + + kfree(xusb); + + up (&disconnect_sem); + INFO("XUSB #%d now disconnected", minor); +} + +static void xpp_send_callback(struct urb *urb, struct pt_regs *regs) +{ + struct xpp_usb_bus *xusb = (struct xpp_usb_bus *)urb->context; + xbus_t *xbus = xusb->xbus; + + BUG_ON(!xbus); + /* 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) + DBG("nonzero read bulk status received: %d", urb->status); + XUSB_COUNTER(xusb, TX_ERRORS)++; + } + if(!xusb->present) { + ERR("A packet from non-connected device?\n"); + return; + } + xpp_urb_delete(urb); + /* allow device read, write and ioctl */ + XUSB_COUNTER(xusb, TX_PACKETS)++; +} + +static void xpp_receive_callback(struct urb *urb, struct pt_regs *regs) +{ + struct xpp_usb_bus *xusb = (struct xpp_usb_bus *)urb->context; + xbus_t *xbus = xusb->xbus; + + xpacket_t *pack; + size_t size; + int retval; + + BUG_ON(!xbus); + if (urb->status) { + /* sync/async unlink faults aren't errors */ + if (!(urb->status == -EOVERFLOW || urb->status == -EMSGSIZE)) { + ERR("Dropped connection due to bad URB status: %d\n", urb->status); + return; + } else { + DBG("nonzero read bulk status received: %d\n", urb->status); + goto end; + } + } + if(!down_read_trylock(&xbus->in_use)) { + ERR("%s: xbus is going down\n", __FUNCTION__); + return; + } + if(!xusb->present) { + ERR("A packet from non-connected device?\n"); + up_read(&xbus->in_use); + return; + } + pack = xbus->ops->packet_new(xbus, GFP_ATOMIC); + if(!pack) { + ERR("%s: Not enough memory for packets. Dropping\n", __FUNCTION__); + goto end; + } + + size = urb->actual_length; + memcpy(&pack->content, urb->transfer_buffer, size); + + pack->datalen = size - sizeof(xpd_addr_t) - 1; // opcode size + // DBG("datalen of new packet: %d\n", pack->datalen); + + // Send UP + if(pack->content.opcode == XPROTO_NAME(GLOBAL,PCM_READ)) { + XUSB_COUNTER(xusb, PCM_READS)++; + +#ifdef DEBUG_PCM_TIMING + /* + * DEBUG: high-res timing of PCM_READ to PCM_WRITE + */ + stamp_last_pcm_read = get_cycles(); +#endif + // fill_beep((u_char *)&PACKET_FIELD(pack, PCM_READS, pcm), 2); // Debugging BEEP +#if 0 + static int rate_limit; + if((rate_limit++ % 1000) == 0) + dump_packet("USB RECEIVE PCM", pack, print_dbg); +#endif + } else if(pack->content.opcode == XPROTO_NAME(GLOBAL,PCM_WRITE)) { // FIRMWARE_LOOPBACK +#if 0 + static int rate_limit; + if((rate_limit++ % 1000) == 0) + dump_packet("USB RECEIVE (LOOPBACK) PCM", pack, print_dbg); +#endif + } else { + char title[XBUS_DESCLEN]; + + snprintf(title, XBUS_DESCLEN, "USB_PACKET_RECEIVE callback (%s)", xbus->busname); + dump_packet(title, pack, print_dbg); + } + packet_receive(xbus, pack); + XUSB_COUNTER(xusb, RX_PACKETS)++; +end: + up_read(&xbus->in_use); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval < 0) { + ERR("failed re-submitting read urb, error %d\n", retval); + return; + } +} + + +/*------------------------- Initialization -------------------------*/ + +int __init xpp_usb_init(void) +{ + int result; + //struct xpp_usb_bus *xusb; + + INFO("%s revision %s\n", THIS_MODULE->name, revision); + + /* register this driver with the USB subsystem */ + result = usb_register(&xusb_driver); + if (result) { + ERR("usb_register failed. Error number %d", result); + return result; + } + return 0; +} + + +void __exit xpp_usb_cleanup(void) +{ + int i, j; + + DBG("\n"); + for(i = 0; i < USBDEV_MAX; i++) { + xbus_t *xbus; + + if(xusb_array[i] == NULL) + continue; + xbus = xusb_array[i]->xbus; + if(!xbus) { + ERR("%s: missing xbus. Skipping\n", __FUNCTION__); + continue; + } + for(j = 0; j < MAX_XPDS; j++) { + xpd_t *xpd = xpd_of(xbus, j); + + if(xpd) { + if(xpd->id != j) { + ERR("%s: BUG: xpd->id=%d != j=%d\n", __FUNCTION__, xpd->id, j); + continue; + } +#if 0 // FIXME: retest after new driver start working + CALL_XMETHOD(CHAN_ENABLE, xbus, xpd, 0xFF, 0); // Disable all hardware channels + CALL_XMETHOD(LED, xbus, xpd, 0xFF, 1, 0); // FIXME: Show activated channels +#endif + } + } + } + /* deregister this driver with the USB subsystem */ + usb_deregister(&xusb_driver); +} + + + +#ifdef CONFIG_PROC_FS + +static int xusb_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + unsigned long flags; + //unsigned long stamp = jiffies; + struct xpp_usb_bus *xusb = data; + + if(!xusb) + goto out; + // TODO: probably needs a per-xusb lock: + spin_lock_irqsave(&xusb_lock, flags); + int i; + + len += sprintf(page + len, "device: %d, #altsettings: %d, minor: %d\n" + "\tBus Type:%d (Model Info: %s)\n" + "\tIn: 0x%02X - Size: %d\n" + "\tOut: 0x%02X - Size: %d\n", + xusb->udev->devnum, + xusb->interface->num_altsetting, + xusb->minor, + xusb->model_info->bus_type, + xusb->model_info->desc, + xusb->ep_in.epnum, + xusb->ep_in.max_size, + xusb->ep_out.epnum, + xusb->ep_out.max_size + ); +#ifdef DEBUG_PCM_TIMING + len += sprintf(page + len, "\nstamp_last_pcm_read=%lld accumulate_diff=%lld\n", stamp_last_pcm_read, accumulate_diff); +#endif + len += sprintf(page + len, "\nCOUNTERS:\n"); + for(i = 0; i < XUSB_COUNTER_MAX; i++) { + len += sprintf(page + len, "\t%-15s = %d\n", xusb_counters[i].name, xusb->counters[i]); + } +#if 0 + len += sprintf(page + len, "<-- len=%d\n", len); +#endif + spin_unlock_irqrestore(&xusb_lock, flags); +out: + if (len <= off+count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; + +} + +#endif + + + +MODULE_DESCRIPTION("XPP USB Driver"); +MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("$Id$"); + +module_init(xpp_usb_init); +module_exit(xpp_usb_cleanup); |