summaryrefslogtreecommitdiffstats
path: root/drivers/usb/usb.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1999-06-13 16:29:25 +0000
committerRalf Baechle <ralf@linux-mips.org>1999-06-13 16:29:25 +0000
commitdb7d4daea91e105e3859cf461d7e53b9b77454b2 (patch)
tree9bb65b95440af09e8aca63abe56970dd3360cc57 /drivers/usb/usb.c
parent9c1c01ead627bdda9211c9abd5b758d6c687d8ac (diff)
Merge with Linux 2.2.8.
Diffstat (limited to 'drivers/usb/usb.c')
-rw-r--r--drivers/usb/usb.c673
1 files changed, 673 insertions, 0 deletions
diff --git a/drivers/usb/usb.c b/drivers/usb/usb.c
new file mode 100644
index 000000000..f5e89ea89
--- /dev/null
+++ b/drivers/usb/usb.c
@@ -0,0 +1,673 @@
+/*
+ * driver/usb/usb.c
+ *
+ * (C) Copyright Linus Torvalds 1999
+ *
+ * NOTE! This is not actually a driver at all, rather this is
+ * just a collection of helper routines that implement the
+ * generic USB things that the real drivers can use..
+ *
+ * Think of this as a "USB library" rather than anything else.
+ * It should be considered a slave, with no callbacks. Callbacks
+ * are evil.
+ */
+
+/*
+ * Table 9-2
+ *
+ * Offset Field Size Value Desc
+ * 0 bmRequestType 1 Bitmap D7: Direction
+ * 0 = Host-to-device
+ * 1 = Device-to-host
+ * D6..5: Type
+ * 0 = Standard
+ * 1 = Class
+ * 2 = Vendor
+ * 3 = Reserved
+ * D4..0: Recipient
+ * 0 = Device
+ * 1 = Interface
+ * 2 = Endpoint
+ * 3 = Other
+ * 4..31 = Reserved
+ * 1 bRequest 1 Value Specific request (9-3)
+ * 2 wValue 2 Value Varies
+ * 4 wIndex 2 Index/Offset Varies
+ * 6 wLength 2 Count Bytes for data
+ */
+
+#include <linux/string.h>
+#include <linux/bitops.h>
+#include <linux/malloc.h>
+
+#include "usb.h"
+
+/*
+ * We have a per-interface "registered driver" list.
+ */
+static LIST_HEAD(usb_driver_list);
+
+int usb_register(struct usb_driver *new_driver)
+{
+ /* Add it to the list of known drivers */
+ list_add(&new_driver->driver_list, &usb_driver_list);
+
+ /*
+ * We should go through all existing devices, and see if any of
+ * them would be acceptable to the new driver.. Let's do that
+ * in version 2.0.
+ */
+ return 0;
+}
+
+void usb_deregister(struct usb_driver *driver)
+{
+ list_del(&driver->driver_list);
+}
+
+/*
+ * This entrypoint gets called for each new device.
+ *
+ * We now walk the list of registered USB drivers,
+ * looking for one that will accept this device as
+ * his..
+ */
+void usb_device_descriptor(struct usb_device *dev)
+{
+ struct list_head *tmp = usb_driver_list.next;
+
+ while (tmp != &usb_driver_list) {
+ struct usb_driver *driver = list_entry(tmp, struct usb_driver, driver_list);
+ tmp = tmp->next;
+ if (driver->probe(dev))
+ continue;
+ dev->driver = driver;
+ return;
+ }
+
+ /*
+ * Ok, no driver accepted the device, so show the info
+ * for debugging..
+ */
+ printk("Unknown new USB device:\n");
+ usb_show_device(dev);
+}
+
+/*
+ * Parse the fairly incomprehensible output of
+ * the USB configuration data, and build up the
+ * USB device database.
+ */
+static int usb_expect_descriptor(unsigned char *ptr, int len, unsigned char desctype, unsigned char descindex)
+{
+ int parsed = 0;
+ int n_len;
+ unsigned short n_desc;
+
+ for (;;) {
+ int i;
+
+ if (len < descindex)
+ return -1;
+ n_desc = *(unsigned short *)ptr;
+ n_len = n_desc & 0xff;
+
+ if (n_desc == ((desctype << 8) + descindex))
+ break;
+
+ if (((n_desc >> 8)&0xFF) == desctype &&
+ n_len > descindex)
+ {
+ printk("bug: oversized descriptor.\n");
+ break;
+ }
+
+ if (n_len < 2 || n_len > len)
+ {
+ printk("Short descriptor.\n");
+ return -1;
+ }
+ printk(
+ "Expected descriptor %02X/%02X, got %02X/%02X - skipping\n",
+ desctype, descindex,
+ (n_desc >> 8) & 0xFF, n_desc & 0xFF);
+ for (i = 0 ; i < n_len; i++)
+ printk(" %d %02x\n", i, ptr[i]);
+ len -= n_len;
+ ptr += n_len;
+ parsed += n_len;
+ }
+
+ printk("Found %02X:%02X\n",
+ desctype, descindex);
+ return parsed;
+}
+
+/*
+ * Parse the even more incomprehensible mess made of the USB spec
+ * by USB audio having private magic to go with it.
+ */
+
+static int usb_check_descriptor(unsigned char *ptr, int len, unsigned char desctype)
+{
+ int n_len = ptr[0];
+
+ if (n_len < 2 || n_len > len)
+ {
+ printk("Short descriptor.\n");
+ return -1;
+ }
+
+ if (ptr[1] == desctype)
+ return 0;
+
+ return -1;
+}
+
+
+static int usb_parse_endpoint(struct usb_device *dev, struct usb_endpoint_descriptor *endpoint, unsigned char *ptr, int len)
+{
+ int parsed = usb_expect_descriptor(ptr, len, USB_DT_ENDPOINT, 7);
+ int i;
+
+ if (parsed < 0)
+ return parsed;
+ memcpy(endpoint, ptr + parsed, ptr[parsed]);
+
+ parsed += ptr[parsed];
+ len -= ptr[parsed];
+
+ while((i = usb_check_descriptor(ptr+parsed, len, 0x25))>=0)
+ {
+ usb_audio_endpoint(endpoint, ptr+parsed+i);
+ len -= ptr[parsed+i];
+ parsed += ptr[parsed+i];
+ }
+
+ return parsed;// + ptr[parsed];
+}
+
+static int usb_parse_interface(struct usb_device *dev, struct usb_interface_descriptor *interface, unsigned char *ptr, int len)
+{
+ int i;
+ int parsed = usb_expect_descriptor(ptr, len, USB_DT_INTERFACE, 9);
+ int retval;
+
+ if (parsed < 0)
+ return parsed;
+
+ memcpy(interface, ptr + parsed, *ptr);
+ len -= ptr[parsed];
+ parsed += ptr[parsed];
+
+ while((i=usb_check_descriptor(ptr+parsed, len, 0x24))>=0)
+ {
+ usb_audio_interface(interface, ptr+parsed+i);
+ len -= ptr[parsed+i];
+ parsed += ptr[parsed+i];
+ }
+
+ if (interface->bNumEndpoints > USB_MAXENDPOINTS)
+ {
+ printk(KERN_WARNING "usb: too many endpoints.\n");
+ return -1;
+ }
+
+ interface->endpoint = (struct usb_endpoint_descriptor *)
+ kmalloc(interface->bNumEndpoints * sizeof(struct usb_endpoint_descriptor), GFP_KERNEL);
+ if(interface->endpoint==NULL)
+ {
+ printk(KERN_WARNING "usb: out of memory.\n");
+ return -1;
+ }
+ memset(interface->endpoint, 0, interface->bNumEndpoints*sizeof(struct usb_endpoint_descriptor));
+
+ for (i = 0; i < interface->bNumEndpoints; i++) {
+// if(((USB_DT_HID << 8) | 9) == *(unsigned short*)(ptr + parsed)) {
+// parsed += 9; /* skip over the HID descriptor for now */
+// len -= 9;
+// }
+ retval = usb_parse_endpoint(dev, interface->endpoint + i, ptr + parsed, len);
+ if (retval < 0) return retval;
+ parsed += retval;
+ len -= retval;
+ }
+ return parsed;
+}
+
+static int usb_parse_config(struct usb_device *dev, struct usb_config_descriptor *config, unsigned char *ptr, int len)
+{
+ int i;
+ int parsed = usb_expect_descriptor(ptr, len, USB_DT_CONFIG, 9);
+
+ if (parsed < 0)
+ return parsed;
+
+ memcpy(config, ptr + parsed, *ptr);
+ len -= *ptr;
+ parsed += *ptr;
+
+ if (config->bNumInterfaces > USB_MAXINTERFACES)
+ {
+ printk(KERN_WARNING "usb: too many interfaces.\n");
+ return -1;
+
+ }
+
+ config->interface = (struct usb_interface_descriptor *)
+ kmalloc(config->bNumInterfaces * sizeof(struct usb_interface_descriptor), GFP_KERNEL);
+ if(config->interface==NULL)
+ {
+ printk(KERN_WARNING "usb: out of memory.\n");
+ return -1;
+ }
+ memset(config->interface, 0, config->bNumInterfaces*sizeof(struct usb_interface_descriptor));
+
+ for (i = 0; i < config->bNumInterfaces; i++) {
+ int retval = usb_parse_interface(dev, config->interface + i, ptr + parsed, len);
+ if (retval < 0)
+ return parsed; // HACK
+// return retval;
+ parsed += retval;
+ len -= retval;
+ }
+ return parsed;
+}
+
+int usb_parse_configuration(struct usb_device *dev, void *__buf, int bytes)
+{
+ int i;
+ unsigned char *ptr = __buf;
+
+ if (dev->descriptor.bNumConfigurations > USB_MAXCONFIG)
+ {
+ printk(KERN_WARNING "usb: too many configurations.\n");
+ return -1;
+ }
+
+ dev->config = (struct usb_config_descriptor *)
+ kmalloc(dev->descriptor.bNumConfigurations * sizeof(struct usb_config_descriptor), GFP_KERNEL);
+ if(dev->config==NULL)
+ {
+ printk(KERN_WARNING "usb: out of memory.\n");
+ return -1;
+ }
+ memset(dev->config, 0, dev->descriptor.bNumConfigurations*sizeof(struct usb_config_descriptor));
+ for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
+ int retval = usb_parse_config(dev, dev->config + i, ptr, bytes);
+ if (retval < 0)
+ return retval;
+ ptr += retval;
+ bytes += retval;
+ }
+ return 0;
+}
+
+void usb_destroy_configuration(struct usb_device *dev)
+{
+ int c, i;
+ struct usb_config_descriptor *cf;
+ struct usb_interface_descriptor *ifp;
+
+ if(dev->config==NULL)
+ return;
+ for(c=0;c<dev->descriptor.bNumConfigurations;c++)
+ {
+ cf=&dev->config[c];
+ if(cf->interface==NULL)
+ break;
+ for(i=0;i<cf->bNumInterfaces;i++)
+ {
+ ifp=&cf->interface[i];
+ if(ifp->endpoint==NULL)
+ break;
+ kfree(ifp->endpoint);
+ }
+ kfree(cf->interface);
+ }
+ kfree(dev->config);
+}
+
+void usb_init_root_hub(struct usb_device *dev)
+{
+ dev->devnum = -1;
+ dev->slow = 0;
+}
+
+/*
+ * Something got disconnected. Get rid of it, and all of its children.
+ */
+void usb_disconnect(struct usb_device **pdev)
+{
+ struct usb_device * dev = *pdev;
+
+ if (dev) {
+ int i;
+
+ *pdev = NULL;
+
+ printk("USB disconnect on device %d\n", dev->devnum);
+
+ if(dev->driver) dev->driver->disconnect(dev);
+
+ /* Free up all the children.. */
+ for (i = 0; i < USB_MAXCHILDREN; i++) {
+ struct usb_device **child = dev->children + i;
+ usb_disconnect(child);
+ }
+
+ /* Free up the device itself, including its device number */
+ if (dev->devnum > 0)
+ clear_bit(dev->devnum, &dev->bus->devmap.devicemap);
+ dev->bus->op->deallocate(dev);
+ }
+}
+
+
+/*
+ * Connect a new USB device. This basically just initializes
+ * the USB device information and sets up the topology - it's
+ * up to the low-level driver to reset the port and actually
+ * do the setup (the upper levels don't know how to do that).
+ */
+void usb_connect(struct usb_device *dev)
+{
+ int devnum;
+
+ dev->descriptor.bMaxPacketSize0 = 8; /* XXX fixed 8 bytes for now */
+
+ devnum = find_next_zero_bit(dev->bus->devmap.devicemap, 128, 1);
+ if (devnum < 128) {
+ set_bit(devnum, dev->bus->devmap.devicemap);
+ dev->devnum = devnum;
+ }
+}
+
+/*
+ * These are the actual routines to send
+ * and receive control messages.
+ */
+int usb_set_address(struct usb_device *dev)
+{
+ devrequest dr;
+
+ dr.requesttype = 0;
+ dr.request = USB_REQ_SET_ADDRESS;
+ dr.value = dev->devnum;
+ dr.index = 0;
+ dr.length = 0;
+
+ return dev->bus->op->control_msg(dev, usb_snddefctrl(dev), &dr, NULL, 0);
+}
+
+int usb_get_descriptor(struct usb_device *dev, unsigned char type, unsigned char index, void *buf, int size)
+{
+ devrequest dr;
+
+ dr.requesttype = 0x80;
+ dr.request = USB_REQ_GET_DESCRIPTOR;
+ dr.value = (type << 8) + index;
+ dr.index = 0;
+ dr.length = size;
+
+ return dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, buf, size);
+}
+
+int usb_get_device_descriptor(struct usb_device *dev)
+{
+ return usb_get_descriptor(dev, USB_DT_DEVICE, 0, &dev->descriptor, sizeof(dev->descriptor));
+}
+
+int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size)
+{
+ devrequest dr;
+
+ dr.requesttype = USB_RT_HUB | 0x80;
+ dr.request = USB_REQ_GET_DESCRIPTOR;
+ dr.value = (USB_DT_HUB << 8);
+ dr.index = 0;
+ dr.length = size;
+
+ return dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, data, size);
+}
+
+int usb_clear_port_feature(struct usb_device *dev, int port, int feature)
+{
+ devrequest dr;
+
+ dr.requesttype = USB_RT_PORT;
+ dr.request = USB_REQ_CLEAR_FEATURE;
+ dr.value = feature;
+ dr.index = port;
+ dr.length = 0;
+
+ return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0);
+}
+
+int usb_set_port_feature(struct usb_device *dev, int port, int feature)
+{
+ devrequest dr;
+
+ dr.requesttype = USB_RT_PORT;
+ dr.request = USB_REQ_SET_FEATURE;
+ dr.value = feature;
+ dr.index = port;
+ dr.length = 0;
+
+ return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0);
+}
+
+int usb_get_hub_status(struct usb_device *dev, void *data)
+{
+ devrequest dr;
+
+ dr.requesttype = USB_RT_HUB | 0x80;
+ dr.request = USB_REQ_GET_STATUS;
+ dr.value = 0;
+ dr.index = 0;
+ dr.length = 4;
+
+ return dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, data, 4);
+}
+
+int usb_get_port_status(struct usb_device *dev, int port, void *data)
+{
+ devrequest dr;
+
+ dr.requesttype = USB_RT_PORT | 0x80;
+ dr.request = USB_REQ_GET_STATUS;
+ dr.value = 0;
+ dr.index = port;
+ dr.length = 4;
+
+ return dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, data, 4);
+}
+
+int usb_get_protocol(struct usb_device *dev)
+{
+ unsigned char buf[8];
+ devrequest dr;
+
+ dr.requesttype = USB_RT_HIDD | 0x80;
+ dr.request = USB_REQ_GET_PROTOCOL;
+ dr.value = 0;
+ dr.index = 1;
+ dr.length = 1;
+
+ if (dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev, 0), &dr, buf, 1))
+ return -1;
+
+ return buf[0];
+}
+
+int usb_set_protocol(struct usb_device *dev, int protocol)
+{
+ devrequest dr;
+
+ dr.requesttype = USB_RT_HIDD;
+ dr.request = USB_REQ_SET_PROTOCOL;
+ dr.value = protocol;
+ dr.index = 1;
+ dr.length = 0;
+
+ if (dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev, 0), &dr, NULL, 0))
+ return -1;
+
+ return 0;
+}
+
+/* keyboards want a nonzero duration according to HID spec, but
+ mice should use infinity (0) -keryan */
+int usb_set_idle(struct usb_device *dev, int duration, int report_id)
+{
+ devrequest dr;
+
+ dr.requesttype = USB_RT_HIDD;
+ dr.request = USB_REQ_SET_IDLE;
+ dr.value = (duration << 8) | report_id;
+ dr.index = 1;
+ dr.length = 0;
+
+ if (dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev, 0), &dr, NULL, 0))
+ return -1;
+
+ return 0;
+}
+
+int usb_set_configuration(struct usb_device *dev, int configuration)
+{
+ devrequest dr;
+
+ dr.requesttype = 0;
+ dr.request = USB_REQ_SET_CONFIGURATION;
+ dr.value = configuration;
+ dr.index = 0;
+ dr.length = 0;
+
+ if (dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev, 0), &dr, NULL, 0))
+ return -1;
+
+ return 0;
+}
+
+int usb_get_report(struct usb_device *dev)
+{
+ unsigned char buf[8];
+ devrequest dr;
+
+ dr.requesttype = USB_RT_HIDD | 0x80;
+ dr.request = USB_REQ_GET_REPORT;
+ dr.value = 0x100;
+ dr.index = 1;
+ dr.length = 3;
+
+ if (dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev, 0), &dr, buf, 3))
+ return -1;
+
+ return buf[0];
+}
+
+int usb_get_configuration(struct usb_device *dev)
+{
+ unsigned int cfgno,size;
+ unsigned char buffer[400];
+ unsigned char * bufptr;
+
+ bufptr=buffer;
+ for (cfgno=0;cfgno<dev->descriptor.bNumConfigurations;cfgno++) {
+ /* Get the first 8 bytes - guaranteed */
+ if (usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, bufptr, 8))
+ return -1;
+
+ /* Get the full buffer */
+ size = *(unsigned short *)(bufptr+2);
+ if (bufptr+size > buffer+sizeof(buffer))
+ {
+ printk(KERN_INFO "usb: truncated DT_CONFIG (want %d).\n", size);
+ size = buffer+sizeof(buffer)-bufptr;
+ }
+ if (usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, bufptr, size))
+ return -1;
+
+ /* Prepare for next configuration */
+ bufptr+=size;
+ }
+ return usb_parse_configuration(dev, buffer, size);
+}
+
+/*
+ * By the time we get here, the device has gotten a new device ID
+ * and is in the default state. We need to identify the thing and
+ * get the ball rolling..
+ */
+void usb_new_device(struct usb_device *dev)
+{
+ int addr, i;
+
+ printk("USB new device connect, assigned device number %d\n",
+ dev->devnum);
+
+ dev->maxpacketsize = 0; /* Default to 8 byte max packet size */
+
+ addr = dev->devnum;
+ dev->devnum = 0;
+
+ /* Slow devices */
+ for (i = 0; i < 5; i++) {
+ if (!usb_get_descriptor(dev, USB_DT_DEVICE, 0, &dev->descriptor, 8))
+ break;
+
+ printk("get_descriptor failed, waiting\n");
+ wait_ms(200);
+ }
+ if (i == 5) {
+ printk("giving up\n");
+ return;
+ }
+
+#if 0
+ printk("maxpacketsize: %d\n", dev->descriptor.bMaxPacketSize0);
+#endif
+ switch (dev->descriptor.bMaxPacketSize0) {
+ case 8: dev->maxpacketsize = 0; break;
+ case 16: dev->maxpacketsize = 1; break;
+ case 32: dev->maxpacketsize = 2; break;
+ case 64: dev->maxpacketsize = 3; break;
+ }
+#if 0
+ printk("dev->mps: %d\n", dev->maxpacketsize);
+#endif
+
+ dev->devnum = addr;
+
+ if (usb_set_address(dev)) {
+ printk("Unable to set address\n");
+ /* FIXME: We should disable the port */
+ return;
+ }
+
+ wait_ms(10); /* Let the SET_ADDRESS settle */
+
+ if (usb_get_device_descriptor(dev)) {
+ printk("Unable to get device descriptor\n");
+ return;
+ }
+
+ if (usb_get_configuration(dev)) {
+ printk("Unable to get configuration\n");
+ return;
+ }
+
+#if 0
+ printk("Vendor: %X\n", dev->descriptor.idVendor);
+ printk("Product: %X\n", dev->descriptor.idProduct);
+#endif
+
+ usb_device_descriptor(dev);
+}
+
+int usb_request_irq(struct usb_device *dev, unsigned int pipe, usb_device_irq handler, int period, void *dev_id)
+{
+ return dev->bus->op->request_irq(dev, pipe, handler, period, dev_id);
+}
+