summaryrefslogtreecommitdiff
path: root/xpp/xpp_usb.c
diff options
context:
space:
mode:
authorkpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2006-02-15 02:26:14 +0000
committerkpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2006-02-15 02:26:14 +0000
commita9ababa28c7ccedaf4be1501d009f99d9a7ba580 (patch)
treeb9259934f16b50693c73f9300287a7f92936837c /xpp/xpp_usb.c
parent590e1c07e60d8564388533bb85deee9531df4893 (diff)
Merged revisions 949 via svnmerge from
https://origsvn.digium.com/svn/zaptel/branches/1.2 ........ r949 | kpfleming | 2006-02-14 20:24:18 -0600 (Tue, 14 Feb 2006) | 2 lines initial import of Xorcom Astribank driver (issue #6452, with minor mods) ........ git-svn-id: http://svn.digium.com/svn/zaptel/trunk@950 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'xpp/xpp_usb.c')
-rw-r--r--xpp/xpp_usb.c882
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);