diff options
Diffstat (limited to 'drivers/usb')
34 files changed, 10000 insertions, 0 deletions
diff --git a/drivers/usb/.cvsignore b/drivers/usb/.cvsignore new file mode 100644 index 000000000..b566130de --- /dev/null +++ b/drivers/usb/.cvsignore @@ -0,0 +1,5 @@ +.depend +.*.flags +conmakehash +consolemap_deftbl.c +uni_hash.tbl diff --git a/drivers/usb/CREDITS b/drivers/usb/CREDITS new file mode 100644 index 000000000..6cd211b22 --- /dev/null +++ b/drivers/usb/CREDITS @@ -0,0 +1,154 @@ +Credits for the Simple Linux USB Driver: + +The following people have contributed to this code (in alphabetical +order by last name). I'm sure this list should be longer, its +difficult to maintain, add yourself with a patch if desired. + + Alan Cox <alan@lxorguk.ukuu.org.uk> + Johannes Erdfelt <jerdfelt@sventech.com> + ham <ham@unsuave.com> + Bradley M Keryan <keryan@andrew.cmu.edu> + Vojtech Pavlik <vojtech@twilight.ucw.cz> + Gregory P. Smith <greg@electricrain.com> + Linus Torvalds <torvalds@transmeta.com> + Roman Weissgaerber <weissg@vienna.at> + <Kazuki.Yasumatsu@fujixerox.co.jp> + +Special thanks to: + + Inaky Perez Gonzalez <inaky@peloncho.fis.ucm.es> for starting the + Linux USB driver effort and writing much of the larger uusbd driver. + Much has been learned from that effort. + + The NetBSD & FreeBSD USB developers. For being on the Linux USB list + and offering suggestions and sharing implementation experiences. + +Additional thanks to the following companies and people for donations +of hardware, support, time and development (this is from the original +THANKS file in Inaky's driver): + + The following corporations have helped us in the development +of Linux USB / UUSBD: + + - USAR Systems provided us with one of their excellent USB + Evaluation Kits. It allows us to test the Linux-USB driver + for compilance with the latest USB specification. USAR + Systems recognized the importance of an up-to-date open + Operating System and supports this project with + Hardware. Thanks!. + + - Thanks to Intel Corporation for their precious help. + + - We teamed up with Cherry to make Linux the first OS with + built-in USB support. Cherry is one of the biggest keyboard + makers in the world. + + - CMD Technology, Inc. sponsored us kindly donating a CSA-6700 + PCI-to-USB Controller Board to test the OHCI implementation. + + - Due to their support to us, Keytronic can be sure that they + will sell keyboards to some of the 3 million (at least) + Linux users. + + - Many thanks to ing büro h doran [http://www.ibhdoran.com]! + It was almost imposible to get a PC backplate USB connector + for the motherboard here at Europe (mine, home-made, was + quite lowsy :). Now I know where to adquire nice USB stuff! + + - Genius Germany donated a USB mouse to test the mouse boot + protocol. They've also donated a F-23 digital joystick and a + NetMouse Pro. Thanks! + + - AVM GmbH Berlin is supporting the development of the Linux + USB driver for the AVM ISDN Controller B1 USB. AVM is a + leading manufacturer for active and passive ISDN Controllers + and CAPI 2.0-based software. The active design of the AVM B1 + is open for all OS platforms, including Linux. + + - Thanks to Y-E Data, Inc. for donating their FlashBuster-U + USB Floppy Disk Drive, so we could test the bulk transfer + code. + + - Many thanks to Logitech for contributing a three axis USB + mouse. + + Logitech designs, manufactures and markets + Human Interface Devices, having a long history and + experience in making devices such as keyboards, mice, + trackballs, cameras, loudspeakers and control devices for + gaming and professional use. + + Being a recognized vendor and seller for all these devices, + they have donated USB mice, a joystick and a scanner, as a + way to acknowledge the importance of Linux and to allow + Logitech customers to enjoy support in their favorite + operating systems and all Linux users to use Logitech and + other USB hardware. + + Logitech is official sponsor of the Linux Conference on + Feb. 11th 1999 in Vienna, where we'll will present the + current state of the Linux USB effort. + + - CATC has provided means to uncover dark corners of the UHCI + inner workings with a USB Inspector. + + - Thanks to Entrega for providing PCI to USB cards, hubs and + converter products for development. + + + And thanks go to (hey! in no particular order :) + + - Oren Tirosh <orenti@hishome.net>, for standing so patiently + all my doubts'bout USB and giving lots of cool ideas. + + - Jochen Karrer <karrer@wpfd25.physik.uni-wuerzburg.de>, for + pointing out mortal bugs and giving advice. + + - Edmund Humemberger <ed@atnet.at>, for it's great work on + public relationships and general management stuff for the + Linux-USB effort. + + - Alberto Menegazzi <flash@flash.iol.it> is starting the + documentation for the UUSBD. Go for it! + + - Ric Klaren <ia_ric@cs.utwente.nl> for doing nice + introductory documents (compiting with Alberto's :). + + - Christian Groessler <cpg@aladdin.de>, for it's help on those + itchy bits ... :) + + - Paul MacKerras for polishing OHCI and pushing me harder for + the iMac support, giving improvements and enhancements. + + - Fernando Herrera <fherrera@eurielec.etsit.upm.es> has taken + charge of composing, maintaining and feeding the + long-awaited, unique and marvelous UUSBD FAQ! Tadaaaa!!! + + - Rasca Gmelch <thron@gmx.de> has revived the raw driver and + pointed bugs, as well as started the uusbd-utils package. + + - Peter Dettori <dettori@ozy.dec.com> is unconvering bugs like + crazy, as well as making cool suggestions, great :) + + - All the Free Software and Linux community, the FSF & the GNU + project, the MIT X consortium, the TeX people ... everyone! + You know who you are! + + - Big thanks to Richard Stallman for creating Emacs! + + - The people at the linux-usb mailing list, for reading so + many messages :) Ok, no more kidding; for all your advices! + + - All the people at the USB Implementors Forum for their + help and assistance. + + - Nathan Myers <ncm@cantrip.org>, for his advice! (hope you + liked Cibeles' party). + + - Linus Torvalds, for starting, developing and managing Linux. + + - Mike Smith, Craig Keithley, Thierry Giron and Janet Schank + for convincing me USB Standard hubs are not that standard + and that's good to allow for vendor specific quirks on the + standard hub driver. + diff --git a/drivers/usb/Config.in b/drivers/usb/Config.in new file mode 100644 index 000000000..632e3d08a --- /dev/null +++ b/drivers/usb/Config.in @@ -0,0 +1,30 @@ +# +# USB device configuration +# +# NOTE NOTE NOTE! This is still considered extremely experimental. +# Right now hubs, mice and keyboards work - at least with UHCI. +# But that may be more a lucky coincidence than anything else.. +# +# This was all developed modularly, but I've been lazy in cleaning +# it up, so right now they are all bools. +# +mainmenu_option next_comment +comment 'USB drivers - not for the faint of heart' + +if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + tristate 'Support for USB (EXPERIMENTAL!)' CONFIG_USB + if [ ! "$CONFIG_USB" = "n" ]; then + bool 'UHCI (intel PIIX4 and others) support?' CONFIG_USB_UHCI + bool 'OHCI (compaq and some others) support?' CONFIG_USB_OHCI + bool 'OHCI-HCD (other OHCI opt. Virt. Root Hub) support?' CONFIG_USB_OHCI_HCD + if [ "$CONFIG_USB_OHCI_HCD" = "y" ]; then + bool 'OHCI-HCD Virtual Root Hub' CONFIG_USB_OHCI_VROOTHUB + fi + + bool 'USB mouse support' CONFIG_USB_MOUSE + bool 'USB keyboard support' CONFIG_USB_KBD + bool 'USB audio parsing support' CONFIG_USB_AUDIO + fi +fi + +endmenu diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile new file mode 100644 index 000000000..6c6e7b091 --- /dev/null +++ b/drivers/usb/Makefile @@ -0,0 +1,88 @@ +# +# Makefile for the kernel usb device drivers. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now inherited from the +# parent makes.. +# +# This isn't actually supported yet. Don't try to use it. + +SUB_DIRS := +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + +L_TARGET := usb.a +M_OBJS := +L_OBJS := +LX_OBJS := +USBX_OBJS := usb.o hub.o usb-debug.o + +ifeq ($(CONFIG_USB_MOUSE),y) + USBX_OBJS += mouse.o +endif + +ifeq ($(CONFIG_USB_KBD),y) + USBX_OBJS += keyboard.o keymap.o +endif + +ifeq ($(CONFIG_USB_AUDIO),y) + USBX_OBJS += audio.o +endif + +ifeq ($(CONFIG_USB), y) + L_OBJS += $(USBX_OBJS) +endif + +ifeq ($(CONFIG_USB_UHCI),y) + ifeq ($(CONFIG_USB), y) + L_OBJS += uhci.o uhci-debug.o + else + ifeq ($(CONFIG_USB),m) + M_OBJS += usb-uhci.o + MIX_OBJS += $(USBX_OBJS) + endif + endif +endif + +ifeq ($(CONFIG_USB_OHCI),y) + ifeq ($(CONFIG_USB), y) + L_OBJS += ohci.o ohci-debug.o + else + ifeq ($(CONFIG_USB),m) + USBO_OBJS += ohci.o ohci-debug.o + M_OBJS += usb-ohci.o + MIX_OBJS += $(USBX_OBJS) + endif + endif +endif + +ifeq ($(CONFIG_USB_OHCI_HCD),y) + ifeq ($(CONFIG_USB), y) + L_OBJS += ohci-hcd.o ohci-root-hub.o + else + ifeq ($(CONFIG_USB),m) + USBO_OBJS += ohci-hcd.o ohci-root-hub.o + M_OBJS += usb-ohci-hcd.o + MIX_OBJS += $(USBX_OBJS) + endif + endif +endif +include $(TOPDIR)/Rules.make + +keymap.o: keymap.c + +keymap.c: maps/serial.map maps/usb.map maps/fixup.map + ./mkmap > $@ + +usb-uhci.o: uhci.o uhci-debug.o $(USBX_OBJS) + $(LD) $(LD_RFLAG) -r -o $@ uhci.o uhci-debug.o $(USBX_OBJS) + +usb-ohci.o: ohci.o ohci-debug.o $(USBX_OBJS) + $(LD) $(LD_RFLAG) -r -o $@ ohci.o ohci-debug.o $(USBX_OBJS) + +usb-ohci-hcd.o: ohci-hcd.o ohci-root-hub.o $(USBX_OBJS) + $(LD) $(LD_RFLAG) -r -o $@ ohci-hcd.o ohci-root-hub.o $(USBX_OBJS) + diff --git a/drivers/usb/README.kbd b/drivers/usb/README.kbd new file mode 100644 index 000000000..84a77d90f --- /dev/null +++ b/drivers/usb/README.kbd @@ -0,0 +1,65 @@ +This is a simple USB keyboard driver written from Linus' +USB driver (started with Greg's usb-0.03b.tar.gz source +tree) + +It works fine with my BTC keyboard but I'm still investigating +trouble with my MS keyboard (trouble starts with an inability +to set into boot protocol mode, though, this very well could +be all due to crappy hardware). + +Anyway, I would appreciate you taking a look if you have +any USB keyboards lying around. Oh also, I'm doing this on +UHCI so sorry if it breaks with OHCI. + +-ham + + + +Keyboard patch +-------------- + +Instead of using the multiple keyboard patch and then running into all +of the kernel version problems that the current Linux-USB project has +had, I'm just mapping the USB keycodes to the standard AT-101 keycodes +and sending them directly to "handle_scancode". + +This may or may not be considered a hack. Anyway it is effective, and +I think safe, and allows USB keyboards to coexist with a serial +keyboard (oh yeah, one side effect is that you can for example hold +down the control key on the serial keyboard and press "a" on the USB +keyboard and you get Control-a like with Windows USB) and works +fine for console and X. + +You do need to make a *tiny* patch the kernel source tree so that the +function "handle_scancode" is exported from keyboard.c though. + + $ cd /usr/src/linux + $ patch -p0 < kbd.patch + +And, of course, then, you need to rebuild and install the kernel. + +** [Vojtech]: Alternately, just 'insmod kbd-stub', if you don't want +to use the keyboard and are too lazy to patch the kernel. + +Keyboard map +------------ + +I'm including a stupid utility "mkmap" which generates the USB->serial +keymap. It takes in maps/serial.map (the current serial keymap, +generated by "dumpkeys"), maps/usb.map (the USB keymap), and +maps/fixup.map (fixes for e0 keys and misc.) and spits out keymap.c +Anyway, it is not beautiful but should serve its purpose for the +moment. + +Other changes +------------- +uhci.c: + * added a context value to the irq callback function + (this is exactly like the "dev_id" field to request_irq) + * played with uhci_reset_port to get better hot-plug results + (eg. do a wait_ms(200) before calling uhci_reset_port) +usb.c: + * disconnect all devices after uhci-control thread is killed + * skip over the HID descriptor + * disconnect the high-level driver in usb_disconnect + diff --git a/drivers/usb/README.ohci b/drivers/usb/README.ohci new file mode 100644 index 000000000..7a544eb33 --- /dev/null +++ b/drivers/usb/README.ohci @@ -0,0 +1,26 @@ +May 09, 1999 16:25:58 + +Cool, things are working "well" now. (I'm not getting oops's from the +OHCI code anyways.. ;). I can attach a usb hub and mouse in any +possible arrangement of the two and they get configured properly. + +You can see that the mouse Interrupt transfers are occuring and being +acknowledged because /proc/interrupts usb-ohci goes up accordingly with +mouse movements/events. That means the TD at least returns some data +and requeues itself. + +Device attach/detach from the root hub is not working well. Currently +every interrupt checks for root hub status changes and frame number +overflow interrupts are enabled. This means you shouldn't have to +wait more than 32-33 seconds for the change to occur, less if there is +other activity. (due to checking in the WDH caused interrupts) +My OHCI controller [SiS 5598 motherboard] doesn't seem to play well +with the RHSC interrupt so it has been disabled. The ohci_timer +should be polling but it not currently working, I haven't had time to +look into that problem. + +However, when I tried telling X to use /dev/psaux for the mouse my +machine locked up... + +- greg@electricrain.com + diff --git a/drivers/usb/README.ohci_hcd b/drivers/usb/README.ohci_hcd new file mode 100644 index 000000000..53bd35c95 --- /dev/null +++ b/drivers/usb/README.ohci_hcd @@ -0,0 +1,112 @@ + +The OHCI HCD layer is a simple but nearly complete implementation of what the +USB people would call a HCD for the OHCI. + (ISO comming soon, Bulk disabled, INT u. CTRL transfers enabled) +It is based on Linus Torvalds UHCI code and Gregory Smith OHCI fragments (0.03 source tree). +The layer (functions) on top of it, is for interfacing to the alternate-usb device-drivers. + +- Roman Weissgaerber <weissg@vienna.at> + + + * v2.1 1999/05/09 ep_addr correction, code cleanup + * v0.2.0 1999/05/04 + * everything has been moved into 2 files (ohci-hcd.c, ohci-hub-root.c and headers) + * virtual root hub is now an option, + * memory allocation based on kmalloc and kfree now, simple Bus error handling, + * INT and CTRL transfers enabled, Bulk included but disabled, ISO needs completion + * + * from Linus Torvalds (uhci.c): APM (not tested); hub, usb_device, bus and related stuff + * from Greg Smith (ohci.c): better reset ohci-controller handling, hub + * + * v0.1.0 1999/04/27 initial release + +to remove the module try: +killall root-hub +: +rmmod usb-ohci-hcd + +Features: +- virtual root hub, all basic hub descriptors and commands (state: complete) + this is an option now (v0.2.0) + #define CONFIG_USB_OHCI_VROOTHUB includes the virtual hub code, (VROOTHUB) + default is without. + (at the moment: the Virtual Root Hub option is not recommended) + + files: ohci-root-hub.c, ohci-root-hub.h + + +- Endpoint Descriptor (ED) handling more static approach + (EDs should be allocated in parallel to the SET CONFIGURATION command and they live + as long as the function (device) is alive or another configuration is choosen. + In the HCD layer the EDs has to be allocated manually either by calling a subroutine + or by sending a USB root hub vendor specific command to the virtual root hub. + At the alternate linux usb stack EDs will be added (allocated) at their first use. + + files: ohci-hcd.c ohci-hcd.h + routines: (do not use for drivers, use the top layer alternate usb commands instead) + + int usb_ohci_add_ep(struct ohci * ohci, unsigned int ep_addr1, + int interval, int load, f_handler handler, int ep_size, int speed) + adds an endpoint, (if the endpoint already exists some parameters will be updated) + + int usb_ohci_rm_ep(struct usb_ohci_ed *ed, struct ohci * ohci) + removes an endpoint and all pending TDs of that EP + + usb_ohci_rm_function( struct ohci * ohci, union ep_addr_ ep_addr) + removes all Endpoints of a function (device) + +- Transfer Descriptors (TD): handling and allocation of TDs is transparent to the upper layers + The HCD takes care of TDs and EDs memory allocation whereas the upper layers (UBSD ...) has + to take care of buffer allocation. + files: ohci-hcd.c ohci-hcd.h + + There is one basic command for all types of bus transfers (INT, BULK, ISO, CTRL): + + int ohci_trans_req(struct ohci * ohci, int ep_addr, int ctrl_len, void *ctrl, void * data, int data_len, __OHCI_BAG lw0, __OHCI_BAG lw1) + + CTRL: ctrl, ctrl_len ... cmd buffer + data, data_len ... data buffer (in or out) + INT, BULK: ctrl = NULL, ctrl_len=0, + data, data_len ... data buffer (in or out) + ISO: tbd + + There is no buffer reinsertion done by the internal HCD function. + (The interface layer does this for a INT-pipe on request.) + If you want a transfer then you have to + provide buffers by sending ohci_trans_req requests. As they are queued as TDs on an ED + you can send as many as you like. They should come back by the callback f_handler in + the same order (for each endpoint, not globally) If an error occurs all + queued transfers of an endpoint will return unsent. They will be marked with an error status. + + e.g double-buffering for int transfers: + + ohci_trans_req(ohci, ep_addr, 0, NULL, data0, data0_len, 0,0) + ohci_trans_req(ohci, ep_addr, 0, NULL, data1, data1_len, 0,0) + + and when a data0 packet returns by the callback f_handler requeue it: + ohci_trans_req(ohci, ep_addr, 0, NULL, data0, data0_len, 0,0) + and when a data1 packet returns by the callback f_handler requeue it: + ohci_trans_req(ohci, ep_addr, 0, NULL, data1, data1_len, 0,0) + + lw0, lw1 are private fields for upper layers for ids or fine grained handlers. + The alternate usb uses them for dev_id and usb_device_irq handler. + + +- Done list handling: returns the requests (callback f_handler in ED) and does + some error handling, root-hub request dequeuing + (files: ohci-done-list.c in ohci-hcd.c now(v0.2.0)) + +ep_addr union or int is for addressing devices&endpoints: +__u8 ep_addr.bep.ep ... bit 3..0 endpoint address + bit 4 0 + bit 6,5 type: eg. 10 CTRL, 11 BULK, 01 INT, 00 ISO + bit 7 direction 1 IN, 0 OUT + +__u8 ep_addr.bep.fa ... bit 6..0 function address + bit 7 0 + +(__u8 ep_addr.bep.hc ... host controller nr) not used +(__u8 ep_addr.bep.host ... host nr) not used + + + diff --git a/drivers/usb/audio.c b/drivers/usb/audio.c new file mode 100644 index 000000000..8b0c9d15c --- /dev/null +++ b/drivers/usb/audio.c @@ -0,0 +1,126 @@ +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include "usb.h" + +static int usb_audio_probe(struct usb_device *dev); +static void usb_audio_disconnect(struct usb_device *dev); +static LIST_HEAD(usb_audio_list); + +struct usb_audio +{ + struct usb_device *dev; + struct list_head list; +}; + +static struct usb_driver usb_audio_driver = +{ + "audio", + usb_audio_probe, + usb_audio_disconnect, + {NULL, NULL} +}; + + +static int usb_audio_irq(int state, void *buffer, void *dev_id) +{ + struct usb_audio *aud = (struct usb_audio*) dev_id; + return 1; +} + +static int usb_audio_probe(struct usb_device *dev) +{ + struct usb_interface_descriptor *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_audio *aud; + + int i; + int na=0; + + interface = &dev->config[0].interface[0]; + + for(i=0;i<dev->config[0].bNumInterfaces;i++) + { + int x; + + endpoint = &interface->endpoint[i]; + + if(interface->bInterfaceClass != 1) + continue; + + printk(KERN_INFO "USB audio device detected.\n"); + + switch(interface->bInterfaceSubClass) + { + case 0x01: + printk(KERN_INFO "audio: Control device.\n"); + break; + case 0x02: + printk(KERN_INFO "audio: streaming.\n"); + break; + case 0x03: + printk(KERN_INFO "audio: nonstreaming.\n"); + break; + } + na++; + } + + if(na==0) + return -1; + + aud = kmalloc(sizeof(struct usb_audio), GFP_KERNEL); + if(aud) + { + memset(aud, 0, sizeof(*aud)); + aud->dev = dev; + dev->private = aud; + + endpoint = &interface->endpoint[0]; + +// usb_set_configuration(dev, dev->config[0].bConfigurationValue); +// usb_set_protocol(dev, 0); +// usb_set_idle(dev, 0, 0); + + usb_request_irq(dev, + usb_rcvctrlpipe(dev, endpoint->bEndpointAddress), + usb_audio_irq, + endpoint->bInterval, + aud); + + list_add(&aud->list, &usb_audio_list); + } + return 0; +} + +static void usb_audio_disconnect(struct usb_device *dev) +{ + struct usb_audio *aud = (struct usb_audio*) dev->private; + if(aud) + { + dev->private = NULL; + list_del(&aud->list); + kfree(aud); + } + printk(KERN_INFO "USB audio driver removed.\n"); +} + +int usb_audio_init(void) +{ + usb_register(&usb_audio_driver); + return 0; +} + +/* + * Support functions for parsing + */ + +void usb_audio_interface(struct usb_interface_descriptor *interface, u8 *data) +{ +} + +void usb_audio_endpoint(struct usb_endpoint_descriptor *interface, u8 *data) +{ +} + diff --git a/drivers/usb/hub.c b/drivers/usb/hub.c new file mode 100644 index 000000000..16789b944 --- /dev/null +++ b/drivers/usb/hub.c @@ -0,0 +1,422 @@ +/* + * USB hub driver. + * + * This is horrible, it knows about the UHCI driver + * internals, but it's just meant as a rough example, + * let's do the virtualization later when this works. + * + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 1999 Johannes Erdfelt + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/malloc.h> +#include <linux/smp_lock.h> + +#include <asm/spinlock.h> + +#include "usb.h" +#include "uhci.h" +#include "hub.h" + +extern struct usb_operations uhci_device_operations; + +/* Wakes up khubd */ +static struct wait_queue *usb_hub_wait = NULL; +static spinlock_t hub_event_lock = SPIN_LOCK_UNLOCKED; + +/* List of hubs needing servicing */ +static struct list_head hub_event_list; + +/* PID of khubd */ +static int khubd_pid = 0; + +/* + * A irq handler returns non-zero to indicate to + * the low-level driver that it wants to be re-activated, + * or zero to say "I'm done". + */ +static int hub_irq(int status, void *__buffer, void *dev_id) +{ + struct usb_hub *hub = dev_id; + unsigned long flags; + + if (waitqueue_active(&usb_hub_wait)) { + /* Add the hub to the event queue */ + spin_lock_irqsave(&hub_event_lock, flags); + if (hub->event_list.next == &hub->event_list) { + list_add(&hub->event_list, &hub_event_list); + /* Wake up khubd */ + wake_up(&usb_hub_wait); + } + spin_unlock_irqrestore(&hub_event_lock, flags); + } + + return 1; +} + +static void usb_hub_configure(struct usb_hub *hub) +{ + struct usb_device *dev = hub->dev; + unsigned char hubdescriptor[8], buf[4]; + int charac, i; + + usb_set_configuration(dev, dev->config[0].bConfigurationValue); + + if (usb_get_hub_descriptor(dev, hubdescriptor, 8)) + return; + + hub->nports = dev->maxchild = hubdescriptor[2]; + printk("hub: %d-port%s detected\n", hub->nports, + (hub->nports == 1) ? "" : "s"); + + charac = (hubdescriptor[4] << 8) + hubdescriptor[3]; + switch (charac & HUB_CHAR_LPSM) { + case 0x00: + printk("hub: ganged power switching\n"); + break; + case 0x01: + printk("hub: individual port power switching\n"); + break; + case 0x02: + case 0x03: + printk("hub: unknown reserved power switching mode\n"); + break; + } + + if (charac & HUB_CHAR_COMPOUND) + printk("hub: part of a compound device\n"); + else + printk("hub: standalone hub\n"); + + switch (charac & HUB_CHAR_OCPM) { + case 0x00: + printk("hub: global over current protection\n"); + break; + case 0x08: + printk("hub: individual port over current protection\n"); + break; + case 0x10: + case 0x18: + printk("hub: no over current protection\n"); + break; + } + + printk("hub: power on to power good time: %dms\n", + hubdescriptor[5] * 2); + + printk("hub: hub controller current requirement: %dmA\n", + hubdescriptor[6]); + + for (i = 0; i < dev->maxchild; i++) + printk("hub: port %d is%s removable\n", i + 1, + hubdescriptor[7 + ((i + 1)/8)] & (1 << ((i + 1) % 8)) + ? " not" : ""); + + if (usb_get_hub_status(dev, buf)) + return; + + printk("hub: local power source is %s\n", + (buf[0] & 1) ? "lost (inactive)" : "good"); + + printk("hub: %sover current condition exists\n", + (buf[0] & 2) ? "" : "no "); + +#if 0 + for (i = 0; i < hub->nports; i++) { + int portstat, portchange; + unsigned char portstatus[4]; + + if (usb_get_port_status(dev, i + 1, portstatus)) + return; + portstat = (portstatus[1] << 8) + portstatus[0]; + portchange = (portstatus[3] << 8) + portstatus[2]; + + printk("hub: port %d status\n", i + 1); + printk("hub: %sdevice present\n", (portstat & 1) ? "" : "no "); + printk("hub: %s\n", (portstat & 2) ? "enabled" : "disabled"); + printk("hub: %ssuspended\n", (portstat & 4) ? "" : "not "); + printk("hub: %sover current\n", (portstat & 8) ? "" : "not "); + printk("hub: has %spower\n", (portstat & 0x100) ? "" : "no "); + printk("hub: %s speed\n", (portstat & 0x200) ? "low" : "full"); + } +#endif + + /* Enable power to the ports */ + printk("enabling power on all ports\n"); + for (i = 0; i < hub->nports; i++) + usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); +} + +static int hub_probe(struct usb_device *dev) +{ + struct usb_interface_descriptor *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_hub *hub; + + /* We don't handle multi-config hubs */ + if (dev->descriptor.bNumConfigurations != 1) + return -1; + + /* We don't handle multi-interface hubs */ + if (dev->config[0].bNumInterfaces != 1) + return -1; + + interface = &dev->config[0].interface[0]; + + /* Is it a hub? */ + if (interface->bInterfaceClass != 9) + return -1; + if ((interface->bInterfaceSubClass != 0) && + (interface->bInterfaceSubClass != 1)) + return -1; + + /* Multiple endpoints? What kind of mutant ninja-hub is this? */ + if (interface->bNumEndpoints != 1) + return -1; + + endpoint = &interface->endpoint[0]; + + /* Output endpoint? Curiousier and curiousier.. */ + if (!(endpoint->bEndpointAddress & 0x80)) + return -1; + + /* If it's not an interrupt endpoint, we'd better punt! */ + if ((endpoint->bmAttributes & 3) != 3) + return -1; + + /* We found a hub */ + printk("USB hub found\n"); + + if ((hub = kmalloc(sizeof(*hub), GFP_KERNEL)) == NULL) { + printk("couldn't kmalloc hub struct\n"); + return -1; + } + + memset(hub, 0, sizeof(*hub)); + + dev->private = hub; + + INIT_LIST_HEAD(&hub->event_list); + hub->dev = dev; + + usb_hub_configure(hub); + + usb_request_irq(dev, usb_rcvctrlpipe(dev, endpoint->bEndpointAddress), hub_irq, endpoint->bInterval, hub); + + /* Wake up khubd */ + wake_up(&usb_hub_wait); + + return 0; +} + +static void hub_disconnect(struct usb_device *dev) +{ + struct usb_hub *hub = dev->private; + unsigned long flags; + + spin_lock_irqsave(&hub_event_lock, flags); + + /* Delete it and then reset it */ + list_del(&hub->event_list); + INIT_LIST_HEAD(&hub->event_list); + + spin_unlock_irqrestore(&hub_event_lock, flags); + + /* Free the memory */ + kfree(hub); +} + +static void usb_hub_port_connect_change(struct usb_device *hub, int port) +{ + struct usb_device *usb; + unsigned char buf[4]; + unsigned short portstatus, portchange; + + usb_disconnect(&hub->children[port]); + + usb_set_port_feature(hub, port + 1, USB_PORT_FEAT_RESET); + + wait_ms(50); /* FIXME: This is from the *BSD stack, thanks! :) */ + + if (usb_get_port_status(hub, port + 1, buf)) { + printk("get_port_status failed\n"); + return; + } + + portstatus = *((unsigned short *)buf + 0); + portchange = *((unsigned short *)buf + 1); + + if ((!(portstatus & USB_PORT_STAT_CONNECTION)) && + (!(portstatus & USB_PORT_STAT_ENABLE))) { + /* We're done now, we already disconnected the device */ + /* printk("not connected/enabled\n"); */ + return; + } + + usb = hub->bus->op->allocate(hub); + if (!usb) { + printk("couldn't allocate usb_device\n"); + return; + } + + usb_connect(usb); + + usb->slow = (portstatus & USB_PORT_STAT_LOW_SPEED) ? 1 : 0; + + hub->children[port] = usb; + + usb_new_device(usb); +} + +static void usb_hub_events(void) +{ + unsigned long flags; + unsigned char buf[4]; + unsigned short portstatus, portchange; + int i; + struct list_head *next, *tmp, *head = &hub_event_list; + struct usb_device *dev; + struct usb_hub *hub; + + spin_lock_irqsave(&hub_event_lock, flags); + + tmp = head->next; + while (tmp != head) { + hub = list_entry(tmp, struct usb_hub, event_list); + dev = hub->dev; + + next = tmp->next; + + list_del(tmp); + INIT_LIST_HEAD(tmp); + + for (i = 0; i < hub->nports; i++) { + if (usb_get_port_status(dev, i + 1, buf)) { + printk("get_port_status failed\n"); + continue; + } + + portstatus = *((unsigned short *)buf + 0); + portchange = *((unsigned short *)buf + 1); + + if (portchange & USB_PORT_STAT_C_CONNECTION) { + printk("hub: port %d connection change\n", i + 1); + + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_CONNECTION); + + usb_hub_port_connect_change(dev, i); + } + + if (portchange & USB_PORT_STAT_C_ENABLE) { + printk("hub: port %d enable change\n", i + 1); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_ENABLE); + } + + if (portchange & USB_PORT_STAT_C_SUSPEND) + printk("hub: port %d suspend change\n", i + 1); + + if (portchange & USB_PORT_STAT_C_OVERCURRENT) + printk("hub: port %d over-current change\n", i + 1); + + if (portchange & USB_PORT_STAT_C_RESET) { + printk("hub: port %d reset change\n", i + 1); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_RESET); + } + +#if 0 + if (!portchange) + continue; + + if (usb_get_port_status(dev, i + 1, buf)) + return; + + portstatus = (buf[1] << 8) + buf[0]; + portchange = (buf[3] << 8) + buf[2]; + + printk("hub: port %d status\n", i + 1); + printk("hub: %sdevice present\n", (portstatus & 1) ? "" : "no "); + printk("hub: %s\n", (portstatus & 2) ? "enabled" : "disabled"); + printk("hub: %ssuspended\n", (portstatus & 4) ? "" : "not "); + printk("hub: %sover current\n", (portstatus & 8) ? "" : "not "); + printk("hub: has %spower\n", (portstatus & 0x100) ? "" : "no "); + printk("hub: %s speed\n", (portstatus & 0x200) ? "low" : "full"); +#endif + } + tmp = next; +#if 0 + wait_ms(1000); +#endif + } + + spin_unlock_irqrestore(&hub_event_lock, flags); +} + +static int usb_hub_thread(void *__hub) +{ + lock_kernel(); + + /* + * This thread doesn't need any user-level access, + * so get rid of all our resources + */ + printk("usb_hub_thread at %p\n", &usb_hub_thread); + exit_mm(current); + exit_files(current); + exit_fs(current); + + /* Setup a nice name */ + strcpy(current->comm, "khubd"); + + /* Send me a signal to get me die (for debugging) */ + do { + interruptible_sleep_on(&usb_hub_wait); + usb_hub_events(); + } while (!signal_pending(current)); + + printk("usb_hub_thread exiting\n"); + + return 0; +} + +static struct usb_driver hub_driver = { + "hub", + hub_probe, + hub_disconnect, + { NULL, NULL } +}; + +/* + * This should be a separate module. + */ +int hub_init(void) +{ + int pid; + + INIT_LIST_HEAD(&hub_event_list); + + usb_register(&hub_driver); + pid = kernel_thread(usb_hub_thread, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + if (pid >= 0) { + khubd_pid = pid; + return 0; + } + + /* Fall through if kernel_thread failed */ + usb_deregister(&hub_driver); + + return 0; +} + +void hub_cleanup(void) +{ + if (khubd_pid >= 0) + kill_proc(khubd_pid, SIGINT, 1); + + usb_deregister(&hub_driver); +} diff --git a/drivers/usb/hub.h b/drivers/usb/hub.h new file mode 100644 index 000000000..d015c5a33 --- /dev/null +++ b/drivers/usb/hub.h @@ -0,0 +1,80 @@ +#ifndef __LINUX_HUB_H +#define __LINUX_HUB_H + +#include <linux/list.h> + +/* + * Hub feature numbers + */ +#define C_HUB_LOCAL_POWER 0 +#define C_HUB_OVER_CURRENT 1 + +/* + * Port feature numbers + */ +#define USB_PORT_FEAT_ENABLE 1 +#define USB_PORT_FEAT_SUSPEND 2 +#define USB_PORT_FEAT_OVER_CURRENT 3 +#define USB_PORT_FEAT_RESET 4 +#define USB_PORT_FEAT_POWER 8 +#define USB_PORT_FEAT_LOWSPEED 9 +#define USB_PORT_FEAT_C_CONNECTION 16 +#define USB_PORT_FEAT_C_ENABLE 17 +#define USB_PORT_FEAT_C_SUSPEND 18 +#define USB_PORT_FEAT_C_OVER_CURRENT 19 +#define USB_PORT_FEAT_C_RESET 20 + +/* wPortStatus */ +#define USB_PORT_STAT_CONNECTION 0x0001 +#define USB_PORT_STAT_ENABLE 0x0002 +#define USB_PORT_STAT_SUSPEND 0x0004 +#define USB_PORT_STAT_OVERCURRENT 0x0008 +#define USB_PORT_STAT_RESET 0x0010 +#define USB_PORT_STAT_POWER 0x0100 +#define USB_PORT_STAT_LOW_SPEED 0x0200 + +/* wPortChange */ +#define USB_PORT_STAT_C_CONNECTION 0x0001 +#define USB_PORT_STAT_C_ENABLE 0x0002 +#define USB_PORT_STAT_C_SUSPEND 0x0004 +#define USB_PORT_STAT_C_OVERCURRENT 0x0008 +#define USB_PORT_STAT_C_RESET 0x0010 + +/* Characteristics */ +#define HUB_CHAR_LPSM 0x0003 +#define HUB_CHAR_COMPOUND 0x0004 +#define HUB_CHAR_OCPM 0x0018 + +struct usb_device; + +typedef enum { + USB_PORT_UNPOWERED = 0, /* Default state */ + USB_PORT_POWERED, /* When we've put power to it */ + USB_PORT_ENABLED, /* When it's been enabled */ + USB_PORT_DISABLED, /* If it's been disabled */ + USB_PORT_ADMINDISABLED, /* Forced down */ +} usb_hub_port_state; + +struct usb_hub_port { + usb_hub_port_state cstate; /* Configuration state */ + + struct usb_device *child; /* Device attached to this port */ + + struct usb_hub *parent; /* Parent hub */ +}; + +struct usb_hub { + /* Device structure */ + struct usb_device *dev; + + /* Temporary event list */ + struct list_head event_list; + + /* Number of ports on the hub */ + int nports; + + struct usb_hub_port ports[0]; /* Dynamically allocated */ +}; + +#endif + diff --git a/drivers/usb/inits.h b/drivers/usb/inits.h new file mode 100644 index 000000000..81979ce39 --- /dev/null +++ b/drivers/usb/inits.h @@ -0,0 +1,6 @@ +int bp_mouse_init(void); +int usb_kbd_init(void); +int usb_audio_init(void); +int hub_init(void); +void hub_cleanup(void); +void usb_mouse_cleanup(void); diff --git a/drivers/usb/keyboard.c b/drivers/usb/keyboard.c new file mode 100644 index 000000000..c60c812a5 --- /dev/null +++ b/drivers/usb/keyboard.c @@ -0,0 +1,226 @@ +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/kbd_ll.h> +#include "usb.h" + +#define PCKBD_PRESSED 0x00 +#define PCKBD_RELEASED 0x80 +#define PCKBD_NEEDS_E0 0x80 + +#define USBKBD_MODIFIER_BASE 120 +#define USBKBD_KEYCODE_OFFSET 2 +#define USBKBD_KEYCODE_COUNT 6 + +#define USBKBD_VALID_KEYCODE(key) ((unsigned char)(key) > 3) +#define USBKBD_FIND_KEYCODE(down, key, count) \ + ((unsigned char*) memscan((down), (key), (count)) < ((down) + (count))) + +#define USBKBD_REPEAT_DELAY (HZ / 4) +#define USBKBD_REPEAT_RATE (HZ / 20) + +struct usb_keyboard +{ + struct usb_device *dev; + unsigned long down[2]; + unsigned char repeat_key; + struct timer_list repeat_timer; + struct list_head list; +}; + +extern unsigned char usb_kbd_map[]; + +static int usb_kbd_probe(struct usb_device *dev); +static void usb_kbd_disconnect(struct usb_device *dev); +static void usb_kbd_repeat(unsigned long dummy); + +static LIST_HEAD(usb_kbd_list); + +static struct usb_driver usb_kbd_driver = +{ + "keyboard", + usb_kbd_probe, + usb_kbd_disconnect, + {NULL, NULL} +}; + + +static void +usb_kbd_handle_key(unsigned char key, int down) +{ + int scancode = (int) usb_kbd_map[key]; + if(scancode) + { + if(scancode & PCKBD_NEEDS_E0) + { + handle_scancode(0xe0, 1); + } + handle_scancode((scancode & ~PCKBD_NEEDS_E0), down); + } +} + +static void +usb_kbd_repeat(unsigned long dev_id) +{ + struct usb_keyboard *kbd = (struct usb_keyboard*) dev_id; + + unsigned long flags; + save_flags(flags); + cli(); + + if(kbd->repeat_key) + { + usb_kbd_handle_key(kbd->repeat_key, 1); + + /* reset repeat timer */ + kbd->repeat_timer.function = usb_kbd_repeat; + kbd->repeat_timer.expires = jiffies + USBKBD_REPEAT_RATE; + kbd->repeat_timer.data = (unsigned long) kbd; + kbd->repeat_timer.prev = NULL; + kbd->repeat_timer.next = NULL; + add_timer(&kbd->repeat_timer); + } + + restore_flags(flags); +} + +static int +usb_kbd_irq(int state, void *buffer, void *dev_id) +{ + struct usb_keyboard *kbd = (struct usb_keyboard*) dev_id; + unsigned long *down = (unsigned long*) buffer; + + if(kbd->down[0] != down[0] || kbd->down[1] != down[1]) + { + unsigned char *olddown, *newdown; + unsigned char modsdelta, key; + int i; + + /* handle modifier change */ + modsdelta = (*(unsigned char*) down ^ *(unsigned char*) kbd->down); + if(modsdelta) + { + for(i = 0; i < 8; i++) + { + if(modsdelta & 0x01) + { + int pressed = (*(unsigned char*) down >> i) & 0x01; + usb_kbd_handle_key( + i + USBKBD_MODIFIER_BASE, + pressed); + } + modsdelta >>= 1; + } + } + + olddown = (unsigned char*) kbd->down + USBKBD_KEYCODE_OFFSET; + newdown = (unsigned char*) down + USBKBD_KEYCODE_OFFSET; + + /* handle released keys */ + for(i = 0; i < USBKBD_KEYCODE_COUNT; i++) + { + key = olddown[i]; + if(USBKBD_VALID_KEYCODE(key) + && !USBKBD_FIND_KEYCODE(newdown, key, USBKBD_KEYCODE_COUNT)) + { + usb_kbd_handle_key(key, 0); + } + } + + /* handle pressed keys */ + kbd->repeat_key = 0; + for(i = 0; i < USBKBD_KEYCODE_COUNT; i++) + { + key = newdown[i]; + if(USBKBD_VALID_KEYCODE(key) + && !USBKBD_FIND_KEYCODE(olddown, key, USBKBD_KEYCODE_COUNT)) + { + usb_kbd_handle_key(key, 1); + kbd->repeat_key = key; + } + } + + /* set repeat timer if any keys were pressed */ + if(kbd->repeat_key) + { + del_timer(&kbd->repeat_timer); + kbd->repeat_timer.function = usb_kbd_repeat; + kbd->repeat_timer.expires = jiffies + USBKBD_REPEAT_DELAY; + kbd->repeat_timer.data = (unsigned long) kbd; + kbd->repeat_timer.prev = NULL; + kbd->repeat_timer.next = NULL; + add_timer(&kbd->repeat_timer); + } + + kbd->down[0] = down[0]; + kbd->down[1] = down[1]; + } + + return 1; +} + +static int +usb_kbd_probe(struct usb_device *dev) +{ + struct usb_interface_descriptor *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_keyboard *kbd; + + interface = &dev->config[0].interface[0]; + endpoint = &interface->endpoint[0]; + + if(interface->bInterfaceClass != 3 + || interface->bInterfaceSubClass != 1 + || interface->bInterfaceProtocol != 1) + { + return -1; + } + + printk(KERN_INFO "USB HID boot protocol keyboard detected.\n"); + + kbd = kmalloc(sizeof(struct usb_keyboard), GFP_KERNEL); + if(kbd) + { + memset(kbd, 0, sizeof(*kbd)); + kbd->dev = dev; + dev->private = kbd; + + usb_set_configuration(dev, dev->config[0].bConfigurationValue); + usb_set_protocol(dev, 0); + usb_set_idle(dev, 0, 0); + + usb_request_irq(dev, + usb_rcvctrlpipe(dev, endpoint->bEndpointAddress), + usb_kbd_irq, + endpoint->bInterval, + kbd); + + list_add(&kbd->list, &usb_kbd_list); + } + + return 0; +} + +static void +usb_kbd_disconnect(struct usb_device *dev) +{ + struct usb_keyboard *kbd = (struct usb_keyboard*) dev->private; + if(kbd) + { + dev->private = NULL; + list_del(&kbd->list); + del_timer(&kbd->repeat_timer); + kfree(kbd); + } + + printk(KERN_INFO "USB HID boot protocol keyboard removed.\n"); +} + +int +usb_kbd_init(void) +{ + usb_register(&usb_kbd_driver); + return 0; +} diff --git a/drivers/usb/keymap.c b/drivers/usb/keymap.c new file mode 100644 index 000000000..16c7b28f3 --- /dev/null +++ b/drivers/usb/keymap.c @@ -0,0 +1,50 @@ +unsigned char usb_kbd_map[256] = +{ + 0x00, 0x00, 0x00, 0x00, 0x1e, 0x30, 0x2e, 0x20, + 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, + + 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1f, 0x14, + 0x16, 0x2f, 0x11, 0x2d, 0x15, 0x2c, 0x02, 0x03, + + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x1c, 0x01, 0xd3, 0x0f, 0x39, 0x0c, 0x0d, 0x1a, + + 0x1b, 0x2b, 0x00, 0x27, 0x28, 0x29, 0x33, 0x34, + 0x35, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + + 0x41, 0x42, 0x43, 0x44, 0x57, 0x58, 0xb7, 0x46, + 0x00, 0xd2, 0xc7, 0xc9, 0x63, 0xcf, 0xd1, 0xcd, + + 0xcb, 0xd0, 0xc8, 0x45, 0xb5, 0x37, 0x4a, 0x4e, + 0x9c, 0x4f, 0x50, 0x51, 0x4b, 0x4c, 0x4d, 0x47, + + 0x48, 0x49, 0x52, 0x53, 0x00, 0x6d, 0x00, 0x00, + 0xbd, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1d, 0x2a, 0x38, 0xdb, 0x9d, 0x36, 0xb8, 0xdc, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/drivers/usb/maps/.cvsignore b/drivers/usb/maps/.cvsignore new file mode 100644 index 000000000..b566130de --- /dev/null +++ b/drivers/usb/maps/.cvsignore @@ -0,0 +1,5 @@ +.depend +.*.flags +conmakehash +consolemap_deftbl.c +uni_hash.tbl diff --git a/drivers/usb/maps/fixup.map b/drivers/usb/maps/fixup.map new file mode 100644 index 000000000..fc5d1ed7f --- /dev/null +++ b/drivers/usb/maps/fixup.map @@ -0,0 +1,31 @@ +# misc fixes +keycode 0 = Pause +keycode 29 = Control +keycode 99 = Remove +keycode 42 = Shift +keycode 54 = Shift_R +keycode 109 = Application + +# E0 keys (or'ed with 0x80) +keycode 156 = KP_Enter +keycode 157 = Control_R +keycode 181 = KP_Divide +keycode 183 = Print_Screen +keycode 184 = Alt_R +keycode 189 = F13 +keycode 190 = F14 +keycode 193 = F17 +keycode 198 = Break +keycode 199 = Home +keycode 200 = Up +keycode 201 = Prior +keycode 203 = Left +keycode 205 = Right +keycode 207 = End +keycode 208 = Down +keycode 209 = Next +keycode 210 = Insert +keycode 211 = Delete +keycode 219 = Window +keycode 220 = Window_R +keycode 221 = Menu diff --git a/drivers/usb/maps/serial.map b/drivers/usb/maps/serial.map new file mode 100644 index 000000000..e421a0d23 --- /dev/null +++ b/drivers/usb/maps/serial.map @@ -0,0 +1,370 @@ +keymaps 0-2,4-6,8-9,12 +keycode 1 = Escape + alt keycode 1 = Meta_Escape + shift alt keycode 1 = Meta_Escape + control alt keycode 1 = Meta_Escape +keycode 2 = one exclam + alt keycode 2 = Meta_one + shift alt keycode 2 = Meta_exclam +keycode 3 = two at at nul nul + alt keycode 3 = Meta_two + shift alt keycode 3 = Meta_at + control alt keycode 3 = Meta_nul +keycode 4 = three numbersign + control keycode 4 = Escape + alt keycode 4 = Meta_three + shift alt keycode 4 = Meta_numbersign +keycode 5 = four dollar dollar Control_backslash + alt keycode 5 = Meta_four + shift alt keycode 5 = Meta_dollar + control alt keycode 5 = Meta_Control_backslash +keycode 6 = five percent + control keycode 6 = Control_bracketright + alt keycode 6 = Meta_five + shift alt keycode 6 = Meta_percent +keycode 7 = six asciicircum + control keycode 7 = Control_asciicircum + alt keycode 7 = Meta_six + shift alt keycode 7 = Meta_asciicircum +keycode 8 = seven ampersand braceleft Control_underscore + alt keycode 8 = Meta_seven + shift alt keycode 8 = Meta_ampersand + control alt keycode 8 = Meta_Control_underscore +keycode 9 = eight asterisk bracketleft Delete + alt keycode 9 = Meta_eight + shift alt keycode 9 = Meta_asterisk + control alt keycode 9 = Meta_Delete +keycode 10 = nine parenleft bracketright + alt keycode 10 = Meta_nine + shift alt keycode 10 = Meta_parenleft +keycode 11 = zero parenright braceright + alt keycode 11 = Meta_zero + shift alt keycode 11 = Meta_parenright +keycode 12 = minus underscore backslash Control_underscore Control_underscore + alt keycode 12 = Meta_minus + shift alt keycode 12 = Meta_underscore + control alt keycode 12 = Meta_Control_underscore +keycode 13 = equal plus + alt keycode 13 = Meta_equal + shift alt keycode 13 = Meta_plus +keycode 14 = Delete + alt keycode 14 = Meta_Delete + shift alt keycode 14 = Meta_Delete + control alt keycode 14 = Meta_Delete +keycode 15 = Tab + alt keycode 15 = Meta_Tab + shift alt keycode 15 = Meta_Tab + control alt keycode 15 = Meta_Tab +keycode 16 = q +keycode 17 = w +keycode 18 = e +keycode 19 = r +keycode 20 = t +keycode 21 = y +keycode 22 = u +keycode 23 = i +keycode 24 = o +keycode 25 = p +keycode 26 = bracketleft braceleft + control keycode 26 = Escape + alt keycode 26 = Meta_bracketleft + shift alt keycode 26 = Meta_braceleft +keycode 27 = bracketright braceright asciitilde Control_bracketright + alt keycode 27 = Meta_bracketright + shift alt keycode 27 = Meta_braceright + control alt keycode 27 = Meta_Control_bracketright +keycode 28 = Return + alt keycode 28 = Meta_Control_m +keycode 29 = Control +keycode 30 = a +keycode 31 = s +keycode 32 = d +keycode 33 = f +keycode 34 = g +keycode 35 = h +keycode 36 = j +keycode 37 = k +keycode 38 = l +keycode 39 = semicolon colon + alt keycode 39 = Meta_semicolon + shift alt keycode 39 = Meta_colon +keycode 40 = apostrophe quotedbl + control keycode 40 = Control_g + alt keycode 40 = Meta_apostrophe + shift alt keycode 40 = Meta_quotedbl +keycode 41 = grave asciitilde + control keycode 41 = nul + alt keycode 41 = Meta_grave + shift alt keycode 41 = Meta_asciitilde +keycode 42 = Shift +keycode 43 = backslash bar + control keycode 43 = Control_backslash + alt keycode 43 = Meta_backslash + shift alt keycode 43 = Meta_bar +keycode 44 = z +keycode 45 = x +keycode 46 = c +keycode 47 = v +keycode 48 = b +keycode 49 = n +keycode 50 = m +keycode 51 = comma less + alt keycode 51 = Meta_comma + shift alt keycode 51 = Meta_less +keycode 52 = period greater + alt keycode 52 = Meta_period + shift alt keycode 52 = Meta_greater +keycode 53 = slash question + control keycode 53 = Delete + alt keycode 53 = Meta_slash + shift alt keycode 53 = Meta_question +keycode 54 = Shift +keycode 55 = KP_Multiply + altgr keycode 55 = Hex_C +keycode 56 = Alt +keycode 57 = space + control keycode 57 = nul + alt keycode 57 = Meta_space + shift alt keycode 57 = Meta_space + control alt keycode 57 = Meta_nul +keycode 58 = Caps_Lock +keycode 59 = F1 F13 Console_13 F25 + alt keycode 59 = Console_1 + control alt keycode 59 = Console_1 +keycode 60 = F2 F14 Console_14 F26 + alt keycode 60 = Console_2 + control alt keycode 60 = Console_2 +keycode 61 = F3 F15 Console_15 F27 + alt keycode 61 = Console_3 + control alt keycode 61 = Console_3 +keycode 62 = F4 F16 Console_16 F28 + alt keycode 62 = Console_4 + control alt keycode 62 = Console_4 +keycode 63 = F5 F17 Console_17 F29 + alt keycode 63 = Console_5 + control alt keycode 63 = Console_5 +keycode 64 = F6 F18 Console_18 F30 + alt keycode 64 = Console_6 + control alt keycode 64 = Console_6 +keycode 65 = F7 F19 Console_19 F31 + alt keycode 65 = Console_7 + control alt keycode 65 = Console_7 +keycode 66 = F8 F20 Console_20 F32 + alt keycode 66 = Console_8 + control alt keycode 66 = Console_8 +keycode 67 = F9 F21 Console_21 F33 + alt keycode 67 = Console_9 + control alt keycode 67 = Console_9 +keycode 68 = F10 F22 Console_22 F34 + alt keycode 68 = Console_10 + control alt keycode 68 = Console_10 +keycode 69 = Num_Lock + altgr keycode 69 = Hex_E +keycode 70 = Scroll_Lock Show_Memory Show_Registers Show_State + alt keycode 70 = Scroll_Lock +keycode 71 = KP_7 + altgr keycode 71 = Hex_7 + alt keycode 71 = Ascii_7 +keycode 72 = KP_8 + altgr keycode 72 = Hex_8 + alt keycode 72 = Ascii_8 +keycode 73 = KP_9 + altgr keycode 73 = Hex_9 + alt keycode 73 = Ascii_9 +keycode 74 = KP_Subtract +keycode 75 = KP_4 + altgr keycode 75 = Hex_4 + alt keycode 75 = Ascii_4 +keycode 76 = KP_5 + altgr keycode 76 = Hex_5 + alt keycode 76 = Ascii_5 +keycode 77 = KP_6 + altgr keycode 77 = Hex_6 + alt keycode 77 = Ascii_6 +keycode 78 = KP_Add +keycode 79 = KP_1 + altgr keycode 79 = Hex_1 + alt keycode 79 = Ascii_1 +keycode 80 = KP_2 + altgr keycode 80 = Hex_2 + alt keycode 80 = Ascii_2 +keycode 81 = KP_3 + altgr keycode 81 = Hex_3 + alt keycode 81 = Ascii_3 +keycode 82 = KP_0 + altgr keycode 82 = Hex_0 + alt keycode 82 = Ascii_0 +keycode 83 = KP_Period + altgr control keycode 83 = Boot + control alt keycode 83 = Boot +keycode 84 = Last_Console +keycode 85 = +keycode 86 = less greater bar + alt keycode 86 = Meta_less + shift alt keycode 86 = Meta_greater +keycode 87 = F11 F23 Console_23 F35 + alt keycode 87 = Console_11 + control alt keycode 87 = Console_11 +keycode 88 = F12 F24 Console_24 F36 + alt keycode 88 = Console_12 + control alt keycode 88 = Console_12 +keycode 89 = +keycode 90 = +keycode 91 = +keycode 92 = +keycode 93 = +keycode 94 = +keycode 95 = +keycode 96 = KP_Enter +keycode 97 = Control +keycode 98 = KP_Divide + altgr keycode 98 = Hex_B +keycode 99 = Control_backslash + alt keycode 99 = Meta_Control_backslash + shift alt keycode 99 = Meta_Control_backslash + control alt keycode 99 = Meta_Control_backslash +keycode 100 = AltGr +keycode 101 = Break +keycode 102 = Find +keycode 103 = Up + alt keycode 103 = KeyboardSignal +keycode 104 = Prior + shift keycode 104 = Scroll_Backward +keycode 105 = Left + alt keycode 105 = Decr_Console +keycode 106 = Right + alt keycode 106 = Incr_Console +keycode 107 = Select +keycode 108 = Down +keycode 109 = Next + shift keycode 109 = Scroll_Forward +keycode 110 = Insert +keycode 111 = Remove + altgr control keycode 111 = Boot + control alt keycode 111 = Boot +keycode 112 = Macro + altgr control keycode 112 = VoidSymbol + shift alt keycode 112 = VoidSymbol +keycode 113 = F13 + altgr control keycode 113 = VoidSymbol + shift alt keycode 113 = VoidSymbol +keycode 114 = F14 + altgr control keycode 114 = VoidSymbol + shift alt keycode 114 = VoidSymbol +keycode 115 = Help + altgr control keycode 115 = VoidSymbol + shift alt keycode 115 = VoidSymbol +keycode 116 = Do + altgr control keycode 116 = VoidSymbol + shift alt keycode 116 = VoidSymbol +keycode 117 = F17 + altgr control keycode 117 = VoidSymbol + shift alt keycode 117 = VoidSymbol +keycode 118 = KP_MinPlus + altgr control keycode 118 = VoidSymbol + shift alt keycode 118 = VoidSymbol +keycode 119 = Pause +keycode 120 = +keycode 121 = +keycode 122 = +keycode 123 = +keycode 124 = +keycode 125 = +keycode 126 = +keycode 127 = +string F1 = "\033[[A" +string F2 = "\033[[B" +string F3 = "\033[[C" +string F4 = "\033[[D" +string F5 = "\033[[E" +string F6 = "\033[17~" +string F7 = "\033[18~" +string F8 = "\033[19~" +string F9 = "\033[20~" +string F10 = "\033[21~" +string F11 = "\033[23~" +string F12 = "\033[24~" +string F13 = "\033[25~" +string F14 = "\033[26~" +string F15 = "\033[28~" +string F16 = "\033[29~" +string F17 = "\033[31~" +string F18 = "\033[32~" +string F19 = "\033[33~" +string F20 = "\033[34~" +string Find = "\033[1~" +string Insert = "\033[2~" +string Remove = "\033[3~" +string Select = "\033[4~" +string Prior = "\033[5~" +string Next = "\033[6~" +string Macro = "\033[M" +string Pause = "\033[P" +compose '`' 'A' to 'À' +compose '`' 'a' to 'à' +compose '\'' 'A' to 'Á' +compose '\'' 'a' to 'á' +compose '^' 'A' to 'Â' +compose '^' 'a' to 'â' +compose '~' 'A' to 'Ã' +compose '~' 'a' to 'ã' +compose '"' 'A' to 'Ä' +compose '"' 'a' to 'ä' +compose 'O' 'A' to 'Å' +compose 'o' 'a' to 'å' +compose '0' 'A' to 'Å' +compose '0' 'a' to 'å' +compose 'A' 'A' to 'Å' +compose 'a' 'a' to 'å' +compose 'A' 'E' to 'Æ' +compose 'a' 'e' to 'æ' +compose ',' 'C' to 'Ç' +compose ',' 'c' to 'ç' +compose '`' 'E' to 'È' +compose '`' 'e' to 'è' +compose '\'' 'E' to 'É' +compose '\'' 'e' to 'é' +compose '^' 'E' to 'Ê' +compose '^' 'e' to 'ê' +compose '"' 'E' to 'Ë' +compose '"' 'e' to 'ë' +compose '`' 'I' to 'Ì' +compose '`' 'i' to 'ì' +compose '\'' 'I' to 'Í' +compose '\'' 'i' to 'í' +compose '^' 'I' to 'Î' +compose '^' 'i' to 'î' +compose '"' 'I' to 'Ï' +compose '"' 'i' to 'ï' +compose '-' 'D' to 'Ð' +compose '-' 'd' to 'ð' +compose '~' 'N' to 'Ñ' +compose '~' 'n' to 'ñ' +compose '`' 'O' to 'Ò' +compose '`' 'o' to 'ò' +compose '\'' 'O' to 'Ó' +compose '\'' 'o' to 'ó' +compose '^' 'O' to 'Ô' +compose '^' 'o' to 'ô' +compose '~' 'O' to 'Õ' +compose '~' 'o' to 'õ' +compose '"' 'O' to 'Ö' +compose '"' 'o' to 'ö' +compose '/' 'O' to 'Ø' +compose '/' 'o' to 'ø' +compose '`' 'U' to 'Ù' +compose '`' 'u' to 'ù' +compose '\'' 'U' to 'Ú' +compose '\'' 'u' to 'ú' +compose '^' 'U' to 'Û' +compose '^' 'u' to 'û' +compose '"' 'U' to 'Ü' +compose '"' 'u' to 'ü' +compose '\'' 'Y' to 'Ý' +compose '\'' 'y' to 'ý' +compose 'T' 'H' to 'Þ' +compose 't' 'h' to 'þ' +compose 's' 's' to 'ß' +compose '"' 'y' to 'ÿ' +compose 's' 'z' to 'ß' +compose 'i' 'j' to 'ÿ' diff --git a/drivers/usb/maps/usb.map b/drivers/usb/maps/usb.map new file mode 100644 index 000000000..e05c71cd8 --- /dev/null +++ b/drivers/usb/maps/usb.map @@ -0,0 +1,233 @@ +# USB kernel keymap. +keymaps 0-2,4-5,8,12 + +keycode 4 = a + altgr keycode 30 = Hex_A +keycode 5 = b + altgr keycode 48 = Hex_B +keycode 6 = c + altgr keycode 46 = Hex_C +keycode 7 = d + altgr keycode 32 = Hex_D +keycode 8 = e + altgr keycode 18 = Hex_E +keycode 9 = f + altgr keycode 33 = Hex_F +keycode 10 = g +keycode 11 = h +keycode 12 = i +keycode 13 = j +keycode 14 = k +keycode 15 = l +keycode 16 = m +keycode 17 = n +keycode 18 = o +keycode 19 = p +keycode 20 = q +keycode 21 = r +keycode 22 = s +keycode 23 = t +keycode 24 = u +keycode 25 = v +keycode 26 = w +keycode 27 = x +keycode 28 = y +keycode 29 = z +keycode 30 = one exclam + alt keycode 2 = Meta_one +keycode 31 = two at + control keycode 3 = nul + shift control keycode 3 = nul + alt keycode 3 = Meta_two +keycode 32 = three numbersign + control keycode 4 = Escape + alt keycode 4 = Meta_three +keycode 33 = four dollar + control keycode 5 = Control_backslash + alt keycode 5 = Meta_four +keycode 34 = five percent + control keycode 6 = Control_bracketright + alt keycode 6 = Meta_five +keycode 35 = six asciicircum + control keycode 7 = Control_asciicircum + alt keycode 7 = Meta_six +keycode 36 = seven ampersand + control keycode 8 = Control_underscore + alt keycode 8 = Meta_seven +keycode 37 = eight asterisk + control keycode 9 = Delete + alt keycode 9 = Meta_eight +keycode 38 = nine parenleft + alt keycode 10 = Meta_nine +keycode 39 = zero parenright + alt keycode 11 = Meta_zero +keycode 40 = Return + alt keycode 28 = Meta_Control_m +keycode 41 = Escape Escape + alt keycode 1 = Meta_Escape +keycode 42 = Delete Delete + control keycode 14 = BackSpace + alt keycode 14 = Meta_Delete +keycode 43 = Tab Tab + alt keycode 15 = Meta_Tab +keycode 44 = space space + control keycode 57 = nul + alt keycode 57 = Meta_space +keycode 45 = minus underscore backslash + control keycode 12 = Control_underscore + shift control keycode 12 = Control_underscore + alt keycode 12 = Meta_minus +keycode 46 = equal plus + alt keycode 13 = Meta_equal +keycode 47 = bracketleft braceleft + control keycode 26 = Escape + alt keycode 26 = Meta_bracketleft +keycode 48 = bracketright braceright asciitilde + control keycode 27 = Control_bracketright + alt keycode 27 = Meta_bracketright +keycode 49 = backslash bar + control keycode 43 = Control_backslash + alt keycode 43 = Meta_backslash +keycode 50 = +keycode 51 = semicolon colon + alt keycode 39 = Meta_semicolon +keycode 52 = apostrophe quotedbl + control keycode 40 = Control_g + alt keycode 40 = Meta_apostrophe +keycode 53 = grave asciitilde + control keycode 41 = nul + alt keycode 41 = Meta_grave +keycode 54 = comma less + alt keycode 51 = Meta_comma +keycode 55 = period greater + control keycode 52 = Compose + alt keycode 52 = Meta_period +keycode 56 = slash question + control keycode 53 = Delete + alt keycode 53 = Meta_slash +keycode 57 = Caps_Lock +keycode 58 = F1 F11 Console_13 + control keycode 59 = F1 + alt keycode 59 = Console_1 + control alt keycode 59 = Console_1 +keycode 59 = F2 F12 Console_14 + control keycode 60 = F2 + alt keycode 60 = Console_2 + control alt keycode 60 = Console_2 +keycode 60 = F3 F13 Console_15 + control keycode 61 = F3 + alt keycode 61 = Console_3 + control alt keycode 61 = Console_3 +keycode 61 = F4 F14 Console_16 + control keycode 62 = F4 + alt keycode 62 = Console_4 + control alt keycode 62 = Console_4 +keycode 62 = F5 F15 Console_17 + control keycode 63 = F5 + alt keycode 63 = Console_5 + control alt keycode 63 = Console_5 +keycode 63 = F6 F16 Console_18 + control keycode 64 = F6 + alt keycode 64 = Console_6 + control alt keycode 64 = Console_6 +keycode 64 = F7 F17 Console_19 + control keycode 65 = F7 + alt keycode 65 = Console_7 + control alt keycode 65 = Console_7 +keycode 65 = F8 F18 Console_20 + control keycode 66 = F8 + alt keycode 66 = Console_8 + control alt keycode 66 = Console_8 +keycode 66 = F9 F19 Console_21 + control keycode 67 = F9 + alt keycode 67 = Console_9 + control alt keycode 67 = Console_9 +keycode 67 = F10 F20 Console_22 + control keycode 68 = F10 + alt keycode 68 = Console_10 + control alt keycode 68 = Console_10 +keycode 68 = F11 F11 Console_23 + control keycode 87 = F11 + alt keycode 87 = Console_11 + control alt keycode 87 = Console_11 +keycode 69 = F12 F12 Console_24 + control keycode 88 = F12 + alt keycode 88 = Console_12 + control alt keycode 88 = Console_12 +keycode 70 = Print_Screen +keycode 71 = Scroll_Lock Show_Memory Show_Registers + control keycode 70 = Show_State + alt keycode 70 = Scroll_Lock +keycode 72 = Pause +keycode 73 = Insert +keycode 74 = Home +keycode 75 = Prior + shift keycode 104 = Scroll_Backward +keycode 76 = Remove +# altgr control keycode 111 = Boot + control alt keycode 111 = Boot +keycode 77 = End +keycode 78 = Next + shift keycode 109 = Scroll_Forward +keycode 79 = Right + alt keycode 106 = Incr_Console +keycode 80 = Left + alt keycode 105 = Decr_Console +keycode 81 = Down +keycode 82 = Up +keycode 83 = Num_Lock + shift keycode 69 = Bare_Num_Lock +keycode 84 = KP_Divide +keycode 85 = KP_Multiply +keycode 86 = KP_Subtract +keycode 87 = KP_Add +keycode 88 = KP_Enter +keycode 89 = KP_1 + alt keycode 79 = Ascii_1 + altgr keycode 79 = Hex_1 +keycode 90 = KP_2 + alt keycode 80 = Ascii_2 + altgr keycode 80 = Hex_2 +keycode 91 = KP_3 + alt keycode 81 = Ascii_3 + altgr keycode 81 = Hex_3 +keycode 92 = KP_4 + alt keycode 75 = Ascii_4 + altgr keycode 75 = Hex_4 +keycode 93 = KP_5 + alt keycode 76 = Ascii_5 + altgr keycode 76 = Hex_5 +keycode 94 = KP_6 + alt keycode 77 = Ascii_6 + altgr keycode 77 = Hex_6 +keycode 95 = KP_7 + alt keycode 71 = Ascii_7 + altgr keycode 71 = Hex_7 +keycode 96 = KP_8 + alt keycode 72 = Ascii_8 + altgr keycode 72 = Hex_8 +keycode 97 = KP_9 + alt keycode 73 = Ascii_9 + altgr keycode 73 = Hex_9 +keycode 98 = KP_0 + alt keycode 82 = Ascii_0 + altgr keycode 82 = Hex_0 +keycode 99 = KP_Period +# altgr control keycode 83 = Boot + control alt keycode 83 = Boot +keycode 100 = +keycode 101 = Application +keycode 102 = +keycode 103 = +keycode 104 = F13 +keycode 105 = F14 + +# modifiers +keycode 120 = Control +keycode 121 = Shift +keycode 122 = Alt +keycode 123 = Window +keycode 124 = Control_R +keycode 125 = Shift_R +keycode 126 = Alt_R +keycode 127 = Window_R diff --git a/drivers/usb/mkmap b/drivers/usb/mkmap new file mode 100644 index 000000000..35808f227 --- /dev/null +++ b/drivers/usb/mkmap @@ -0,0 +1,83 @@ +#!/usr/bin/perl + +($ME = $0) =~ s|.*/||; + +$file = "maps/serial.map"; +$line = 1; +open(PC, $file) || die("$!"); +while(<PC>) +{ + if(/^\s*keycode\s+(\d+)\s*=\s*(\S+)/) + { + my($idx) = int($1); + my($sym) = $2; + if(defined($map{uc($sym)})) + { + # print STDERR "$file:$line: warning: `$sym' redefined\n"; + } + $map{uc($sym)} = $idx; + } + $line++; +} +close(PC); + +$file = "maps/fixup.map"; +$line = 1; +open(FIXUP, $file) || die("$!"); +while(<FIXUP>) +{ + if(/^\s*keycode\s+(\d+)\s*=\s*/) + { + my($idx) = int($1); + for $sym (split(/\s+/, $')) + { + $map{uc($sym)} = $idx; + } + } + $line++; +} +close(FIXUP); + +$file = "maps/usb.map"; +$line = 1; +open(USB, $file) || die("$!"); +while(<USB>) +{ + if(/^\s*keycode\s+(\d+)\s*=\s*/) + { + my($idx) = int($1); + for $sym (split(/\s+/, $')) + { + my($val) = $map{uc($sym)}; + $map[$idx] = $val; + if(!defined($val)) + { + print STDERR "$file:$line: warning: `$sym' undefined\n"; + } + else + { + last; + } + } + } + $line++; +} +close(USB); + +print "unsigned char usb_kbd_map[256] = \n{\n"; +for($x = 0; $x < 32; $x++) +{ + if($x && !($x % 2)) + { + print "\n"; + } + print " "; + for($y = 0; $y < 8; $y++) + { + my($idx) = $x * 8 + $y; + print sprintf(" 0x%02x,", + int(defined($map[$idx]) ? $map[$idx]:0)); + } + print "\n"; +} +print "};\n"; diff --git a/drivers/usb/mouse.c b/drivers/usb/mouse.c new file mode 100644 index 000000000..4d346c41a --- /dev/null +++ b/drivers/usb/mouse.c @@ -0,0 +1,293 @@ +/* + * USB HID boot protocol mouse support based on MS BusMouse driver, psaux + * driver, and Linus's skeleton USB mouse driver. Fixed up a lot by Linus. + * + * Brad Keryan 4/3/1999 + * + * version 0.20: Linus rewrote read_mouse() to do PS/2 and do it + * correctly. Events are added together, not queued, to keep the rodent sober. + * + * version 0.02: Hmm, the mouse seems drunk because I'm queueing the events. + * This is wrong: when an application (like X or gpm) reads the mouse device, + * it wants to find out the mouse's current position, not its recent history. + * The button thing turned out to be UHCI not flipping data toggle, so half the + * packets were thrown out. + * + * version 0.01: Switched over to busmouse protocol, and changed the minor + * number to 32 (same as uusbd's hidbp driver). Buttons work more sanely now, + * but it still doesn't generate button events unless you move the mouse. + * + * version 0.0: Driver emulates a PS/2 mouse, stealing /dev/psaux (sorry, I + * know that's not very nice). Moving in the X and Y axes works. Buttons don't + * work right yet: X sees a lot of MotionNotify/ButtonPress/ButtonRelease + * combos when you hold down a button and drag the mouse around. Probably has + * some additional bugs on an SMP machine. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/random.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/malloc.h> + +#include <asm/spinlock.h> + +#include "usb.h" + +#define USB_MOUSE_MINOR 32 + +struct mouse_state { + unsigned char buttons; /* current button state */ + long dx; /* dx, dy, dz are change since last read */ + long dy; + long dz; + int present; /* this mouse is plugged in */ + int active; /* someone is has this mouse's device open */ + int ready; /* the mouse has changed state since the last read */ + struct wait_queue *wait; /* for polling */ + struct fasync_struct *fasync; + /* later, add a list here to support multiple mice */ + /* but we will also need a list of file pointers to identify it */ +}; + +static struct mouse_state static_mouse_state; + +spinlock_t usb_mouse_lock = SPIN_LOCK_UNLOCKED; + +static int mouse_irq(int state, void *__buffer, void *dev_id) +{ + signed char *data = __buffer; + /* finding the mouse is easy when there's only one */ + struct mouse_state *mouse = &static_mouse_state; + + /* if a mouse moves with no one listening, do we care? no */ + if(!mouse->active) + return 1; + + /* if the USB mouse sends an interrupt, then something noteworthy + must have happened */ + mouse->buttons = data[0] & 0x07; + mouse->dx += data[1]; /* data[] is signed, so this works */ + mouse->dy -= data[2]; /* y-axis is reversed */ + mouse->dz += data[3]; + mouse->ready = 1; + + add_mouse_randomness((mouse->buttons << 24) + (mouse->dz << 16 ) + + (mouse->dy << 8) + mouse->dx); + + wake_up_interruptible(&mouse->wait); + if (mouse->fasync) + kill_fasync(mouse->fasync, SIGIO); + + return 1; +} + +static int fasync_mouse(int fd, struct file *filp, int on) +{ + int retval; + struct mouse_state *mouse = &static_mouse_state; + + retval = fasync_helper(fd, filp, on, &mouse->fasync); + if (retval < 0) + return retval; + return 0; +} + +static int release_mouse(struct inode * inode, struct file * file) +{ + struct mouse_state *mouse = &static_mouse_state; + + fasync_mouse(-1, file, 0); + if (--mouse->active) + return 0; + return 0; +} + +static int open_mouse(struct inode * inode, struct file * file) +{ + struct mouse_state *mouse = &static_mouse_state; + + if (!mouse->present) + return -EINVAL; + if (mouse->active++) + return 0; + /* flush state */ + mouse->buttons = mouse->dx = mouse->dy = mouse->dz = 0; + return 0; +} + +static ssize_t write_mouse(struct file * file, + const char * buffer, size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +/* + * Look like a PS/2 mouse, please.. + * + * The PS/2 protocol is fairly strange, but + * oh, well, it's at least common.. + */ +static ssize_t read_mouse(struct file * file, char * buffer, size_t count, loff_t *ppos) +{ + int retval = 0; + static int state = 0; + struct mouse_state *mouse = &static_mouse_state; + + if (count) { + mouse->ready = 0; + switch (state) { + case 0: { /* buttons and sign */ + int buttons = mouse->buttons; + mouse->buttons = 0; + if (mouse->dx < 0) + buttons |= 0x10; + if (mouse->dy < 0) + buttons |= 0x20; + put_user(buttons, buffer); + buffer++; + retval++; + state = 1; + if (!--count) + break; + } + case 1: { /* dx */ + int dx = mouse->dx; + mouse->dx = 0; + put_user(dx, buffer); + buffer++; + retval++; + state = 2; + if (!--count) + break; + } + case 2: { /* dy */ + int dy = mouse->dy; + mouse->dy = 0; + put_user(dy, buffer); + buffer++; + retval++; + state = 0; + } + break; + } + } + return retval; +} + +static unsigned int mouse_poll(struct file *file, poll_table * wait) +{ + struct mouse_state *mouse = &static_mouse_state; + + poll_wait(file, &mouse->wait, wait); + if (mouse->ready) + return POLLIN | POLLRDNORM; + return 0; +} + +struct file_operations usb_mouse_fops = { + NULL, /* mouse_seek */ + read_mouse, + write_mouse, + NULL, /* mouse_readdir */ + mouse_poll, /* mouse_poll */ + NULL, /* mouse_ioctl */ + NULL, /* mouse_mmap */ + open_mouse, + NULL, /* flush */ + release_mouse, + NULL, + fasync_mouse, +}; + +static struct miscdevice usb_mouse = { + USB_MOUSE_MINOR, "USB mouse", &usb_mouse_fops +}; + +static int mouse_probe(struct usb_device *dev) +{ + struct usb_interface_descriptor *interface; + struct usb_endpoint_descriptor *endpoint; + struct mouse_state *mouse = &static_mouse_state; + + /* We don't handle multi-config mice */ + if (dev->descriptor.bNumConfigurations != 1) + return -1; + + /* We don't handle multi-interface mice */ + if (dev->config[0].bNumInterfaces != 1) + return -1; + + /* Is it a mouse interface? */ + interface = &dev->config[0].interface[0]; + if (interface->bInterfaceClass != 3) + return -1; + if (interface->bInterfaceSubClass != 1) + return -1; + if (interface->bInterfaceProtocol != 2) + return -1; + + /* Multiple endpoints? What kind of mutant ninja-mouse is this? */ + if (interface->bNumEndpoints != 1) + return -1; + + endpoint = &interface->endpoint[0]; + + /* Output endpoint? Curiousier and curiousier.. */ + if (!(endpoint->bEndpointAddress & 0x80)) + return -1; + + /* If it's not an interrupt endpoint, we'd better punt! */ + if ((endpoint->bmAttributes & 3) != 3) + return -1; + + printk("USB mouse found\n"); + + usb_set_configuration(dev, dev->config[0].bConfigurationValue); + + usb_request_irq(dev, usb_rcvctrlpipe(dev, endpoint->bEndpointAddress), mouse_irq, endpoint->bInterval, NULL); + + mouse->present = 1; + return 0; +} + +static void mouse_disconnect(struct usb_device *dev) +{ + struct mouse_state *mouse = &static_mouse_state; + + /* this might need work */ + mouse->present = 0; +} + +static struct usb_driver mouse_driver = { + "mouse", + mouse_probe, + mouse_disconnect, + { NULL, NULL } +}; + +int usb_mouse_init(void) +{ + struct mouse_state *mouse = &static_mouse_state; + + misc_register(&usb_mouse); + + mouse->present = mouse->active = 0; + mouse->wait = NULL; + mouse->fasync = NULL; + + usb_register(&mouse_driver); + printk(KERN_INFO "USB HID boot protocol mouse registered.\n"); + return 0; +} + +void usb_mouse_cleanup(void) +{ + /* this, too, probably needs work */ + usb_deregister(&mouse_driver); + misc_deregister(&usb_mouse); +} diff --git a/drivers/usb/ohci-debug.c b/drivers/usb/ohci-debug.c new file mode 100644 index 000000000..1510bb9da --- /dev/null +++ b/drivers/usb/ohci-debug.c @@ -0,0 +1,178 @@ +/* + * OHCI debugging code. It's gross. + * + * (C) Copyright 1999 Gregory P. Smith + */ + +#include <linux/kernel.h> +#include <asm/io.h> + +#include "ohci.h" + +void show_ohci_status(struct ohci *ohci) +{ + struct ohci_regs regs; + int i; + + regs.revision = readl(&ohci->regs->revision); + regs.control = readl(&ohci->regs->control); + regs.cmdstatus = readl(&ohci->regs->cmdstatus); + regs.intrstatus = readl(&ohci->regs->intrstatus); + regs.intrenable = readl(&ohci->regs->intrenable); + regs.hcca = readl(&ohci->regs->hcca); + regs.ed_periodcurrent = readl(&ohci->regs->ed_periodcurrent); + regs.ed_controlhead = readl(&ohci->regs->ed_controlhead); + regs.ed_controlcurrent = readl(&ohci->regs->ed_controlcurrent); + regs.ed_bulkhead = readl(&ohci->regs->ed_bulkhead); + regs.ed_bulkcurrent = readl(&ohci->regs->ed_bulkcurrent); + regs.current_donehead = readl(&ohci->regs->current_donehead); + regs.fminterval = readl(&ohci->regs->fminterval); + regs.fmremaining = readl(&ohci->regs->fmremaining); + regs.fmnumber = readl(&ohci->regs->fmnumber); + regs.periodicstart = readl(&ohci->regs->periodicstart); + regs.lsthresh = readl(&ohci->regs->lsthresh); + regs.roothub.a = readl(&ohci->regs->roothub.a); + regs.roothub.b = readl(&ohci->regs->roothub.b); + regs.roothub.status = readl(&ohci->regs->roothub.status); + for (i=0; i<MAX_ROOT_PORTS; ++i) + regs.roothub.portstatus[i] = readl(&ohci->regs->roothub.portstatus[i]); + + printk(KERN_DEBUG " ohci revision = %x\n", regs.revision); + printk(KERN_DEBUG " ohci control = %x\n", regs.control); + printk(KERN_DEBUG " ohci cmdstatus = %x\n", regs.cmdstatus); + printk(KERN_DEBUG " ohci intrstatus = %x\n", regs.intrstatus); + printk(KERN_DEBUG " ohci intrenable = %x\n", regs.intrenable); + + printk(KERN_DEBUG " ohci hcca = %x\n", regs.hcca); + printk(KERN_DEBUG " ohci ed_pdcur = %x\n", regs.ed_periodcurrent); + printk(KERN_DEBUG " ohci ed_ctrlhead = %x\n", regs.ed_controlhead); + printk(KERN_DEBUG " ohci ed_ctrlcur = %x\n", regs.ed_controlcurrent); + printk(KERN_DEBUG " ohci ed_bulkhead = %x\n", regs.ed_bulkhead); + printk(KERN_DEBUG " ohci ed_bulkcur = %x\n", regs.ed_bulkcurrent); + printk(KERN_DEBUG " ohci curdonehead = %x\n", regs.current_donehead); + + printk(KERN_DEBUG " ohci fminterval = %x\n", regs.fminterval); + printk(KERN_DEBUG " ohci fmremaining = %x\n", regs.fmremaining); + printk(KERN_DEBUG " ohci fmnumber = %x\n", regs.fmnumber); + printk(KERN_DEBUG " ohci pdstart = %x\n", regs.periodicstart); + printk(KERN_DEBUG " ohci lsthresh = %x\n", regs.lsthresh); + + printk(KERN_DEBUG " ohci roothub.a = %x\n", regs.roothub.a); + printk(KERN_DEBUG " ohci roothub.b = %x\n", regs.roothub.b); + printk(KERN_DEBUG " ohci root status = %x\n", regs.roothub.status); + printk(KERN_DEBUG " roothub.port0 = %x\n", regs.roothub.portstatus[0]); + printk(KERN_DEBUG " roothub.port1 = %x\n", regs.roothub.portstatus[1]); +} /* show_ohci_status() */ + + +void show_ohci_ed(struct ohci_ed *ed) +{ + int stat = ed->status; + int skip = (stat & OHCI_ED_SKIP); + int mps = (stat & OHCI_ED_MPS) >> 16; + int isoc = (stat & OHCI_ED_F_ISOC); + int low_speed = (stat & OHCI_ED_S_LOW); + int dir = (stat & OHCI_ED_D); + int endnum = (stat & OHCI_ED_EN) >> 7; + int funcaddr = (stat & OHCI_ED_FA); + int halted = (ed->_head_td & 1); + int toggle = (ed->_head_td & 2) >> 1; + + printk(KERN_DEBUG " ohci ED:\n"); + printk(KERN_DEBUG " status = 0x%x\n", stat); + printk(KERN_DEBUG " %sMPS %d%s%s%s%s tc%d e%d fa%d\n", + skip ? "Skip " : "", + mps, + isoc ? "Isoc. " : "", + low_speed ? " LowSpd" : "", + (dir == OHCI_ED_D_IN) ? " Input" : + (dir == OHCI_ED_D_OUT) ? " Output" : "", + halted ? " Halted" : "", + toggle, + endnum, + funcaddr); + printk(KERN_DEBUG " tail_td = 0x%x\n", ed->tail_td); + printk(KERN_DEBUG " head_td = 0x%x\n", ed_head_td(ed)); + printk(KERN_DEBUG " next_ed = 0x%x\n", ed->next_ed); +} /* show_ohci_ed() */ + + +void show_ohci_td(struct ohci_td *td) +{ + int td_round = td->info & OHCI_TD_ROUND; + int td_dir = td->info & OHCI_TD_D; + int td_int_delay = (td->info & OHCI_TD_IOC_DELAY) >> 21; + int td_toggle = (td->info & OHCI_TD_DT) >> 24; + int td_errcnt = td_errorcount(*td); + int td_cc = OHCI_TD_CC_GET(td->info); + + printk(KERN_DEBUG " ohci TD hardware fields:\n"); + printk(KERN_DEBUG " info = 0x%x\n", td->info); + printk(KERN_DEBUG " %s%s%s%d %s\n", + td_round ? "Rounding " : "", + (td_dir == OHCI_TD_D_IN) ? "Input " : + (td_dir == OHCI_TD_D_OUT) ? "Output " : + (td_dir == OHCI_TD_D_SETUP) ? "Setup " : "", + "IntDelay ", td_int_delay, + (td_toggle < 2) ? " " : + (td_toggle & 1) ? "Data1 " : "Data0 "); + printk(KERN_DEBUG " %s%d %s0x%x, %sAccessed, %sActive\n", + "ErrorCnt ", td_errcnt, + "ComplCode ", td_cc, + td_cc_accessed(*td) ? "" : "Not ", + td_active(*td) ? "" : "Not "); + + printk(KERN_DEBUG " cur_buf = 0x%x\n", td->cur_buf); + printk(KERN_DEBUG " next_td = 0x%x\n", td->next_td); + printk(KERN_DEBUG " buf_end = 0x%x\n", td->buf_end); + printk(KERN_DEBUG " ohci TD driver fields:\n"); + printk(KERN_DEBUG " data = %p\n", td->data); + printk(KERN_DEBUG " dev_id = %p\n", td->dev_id); + printk(KERN_DEBUG " ed = %p\n", td->ed); + if (td->data != NULL) { + unsigned char *d = td->data; + printk(KERN_DEBUG " DATA: %02x %02x %02x %02x %02x %02x %02x %02x\n", + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7] ); + } +} /* show_ohci_td() */ + + +void show_ohci_device(struct ohci_device *dev) +{ + int idx; + printk(KERN_DEBUG " ohci_device usb = %p\n", dev->usb); + printk(KERN_DEBUG " ohci_device ohci = %p\n", dev->ohci); + printk(KERN_DEBUG " ohci_device ohci_hcca = %p\n", dev->hcca); + for (idx=0; idx<3 /*NUM_EDS*/; ++idx) { + printk(KERN_DEBUG " [ed num %d] ", idx); + show_ohci_ed(&dev->ed[idx]); + } + for (idx=0; idx<3 /*NUM_TDS*/; ++idx) { + printk(KERN_DEBUG " [td num %d] ", idx); + show_ohci_td(&dev->td[idx]); + } + printk(KERN_DEBUG " ohci_device data\n "); + for (idx=0; idx<4; ++idx) { + printk(KERN_DEBUG " %08lx", dev->data[idx]); + } + printk(KERN_DEBUG "\n"); +} /* show_ohci_device() */ + + +void show_ohci_hcca(struct ohci_hcca *hcca) +{ + int idx; + + printk(KERN_DEBUG " ohci_hcca\n"); + + for (idx=0; idx<NUM_INTS; idx++) { + printk(KERN_DEBUG " int_table[%2d] == %p\n", idx, hcca->int_table +idx); + } + + printk(KERN_DEBUG " frame_no == %d\n", hcca->frame_no); + printk(KERN_DEBUG " donehead == 0x%08x\n", hcca->donehead); +} /* show_ohci_hcca() */ + + +/* vim:sw=8 + */ diff --git a/drivers/usb/ohci-hcd.c b/drivers/usb/ohci-hcd.c new file mode 100644 index 000000000..da3ef6657 --- /dev/null +++ b/drivers/usb/ohci-hcd.c @@ -0,0 +1,1489 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * + * The OHCI HCD layer is a simple but nearly complete implementation of what the + * USB people would call a HCD for the OHCI. + * (ISO comming soon, Bulk disabled, INT u. CTRL transfers enabled) + * The layer on top of it, is for interfacing to the alternate-usb device-drivers. + * + * [ This is based on Linus' UHCI code and gregs OHCI fragments (0.03c source tree). ] + * [ Open Host Controller Interface driver for USB. ] + * [ (C) Copyright 1999 Linus Torvalds (uhci.c) ] + * [ (C) Copyright 1999 Gregory P. Smith <greg@electricrain.com> ] + * [ $Log: ohci.c,v $ ] + * [ Revision 1.1 1999/04/05 08:32:30 greg ] + * + * + * v2.1 1999/05/09 ep_addr correction, code clean up + * v2.0 1999/05/04 + * virtual root hub is now an option, + * memory allocation based on kmalloc and kfree now, Bus error handling, + * INT and CTRL transfers enabled, Bulk included but disabled, ISO needs completion + * + * from Linus Torvalds (uhci.c) (APM not tested; hub, usb_device, bus and related stuff) + * from Greg Smith (ohci.c) (reset controller handling, hub) + * + * v1.0 1999/04/27 initial release + * ohci-hcd.c + */ + +/* #define OHCI_DBG */ /* printk some debug information */ + + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/timer.h> + +#include <asm/spinlock.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "usb.h" +#include "ohci-hcd.h" +#include "inits.h" + + + +#ifdef CONFIG_APM +#include <linux/apm_bios.h> +static int handle_apm_event(apm_event_t event); +static int apm_resume = 0; +#endif + + + +static struct wait_queue *control_wakeup; +static struct wait_queue *root_hub = NULL; + +static __u8 cc_to_status[16] = { /* mapping of the OHCI CC to the UHCI status codes; first guess */ +/* Activ, Stalled, Data Buffer Err, Babble Detected : NAK recvd, CRC/Timeout, Bitstuff, reservd */ +/* No Error */ 0x00, +/* CRC Error */ 0x04, +/* Bit Stuff */ 0x02, +/* Data Togg */ 0x40, +/* Stall */ 0x40, +/* DevNotResp */ 0x04, +/* PIDCheck */ 0x04, +/* UnExpPID */ 0x40, +/* DataOver */ 0x20, +/* DataUnder */ 0x20, +/* reservd */ 0x40, +/* reservd */ 0x40, +/* BufferOver */ 0x20, +/* BuffUnder */ 0x20, +/* Not Access */ 0x80, +/* Not Access */ 0x80 + }; + + +/******** + **** Interface functions + ***********************************************/ + +static int sohci_int_handler(void * ohci_in, unsigned int ep_addr, int ctrl_len, void * ctrl, void * data, int data_len, int status, __OHCI_BAG lw0, __OHCI_BAG lw1) +{ + + struct ohci * ohci = ohci_in; + usb_device_irq handler=(void *) lw0; + void *dev_id = (void *) lw1; + int ret; + + OHCI_DEBUG({ int i; printk("USB HC IRQ <<<: %x: data(%d):", ep_addr, data_len);) + OHCI_DEBUG( for(i=0; i < data_len; i++ ) printk(" %02x", ((__u8 *) data)[i]);) + OHCI_DEBUG( printk(" ret_status: %x\n", status); }) + + ret = handler(cc_to_status[status & 0xf], data, dev_id); + if(ret == 0) return 0; /* 0 .. do not requeue */ + if(status > 0) return -1; /* error occured do not requeue ? */ + ohci_trans_req(ohci, ep_addr, 0, NULL, data, 8, (__OHCI_BAG) handler, (__OHCI_BAG) dev_id); /* requeue int request */ + return 0; +} + +static int sohci_ctrl_handler(void * ohci_in, unsigned int ep_addr, int ctrl_len, void * ctrl, void * data, int data_len, int status, __OHCI_BAG lw0, __OHCI_BAG lw) +{ + *(int * )lw0 = status; + wake_up(&control_wakeup); + + OHCI_DEBUG( { int i; printk("USB HC CTRL<<<: %x: ctrl(%d):", ep_addr, ctrl_len);) + OHCI_DEBUG( for(i=0; i < 8; i++ ) printk(" %02x", ((__u8 *) ctrl)[i]);) + OHCI_DEBUG( printk(" data(%d):", data_len);) + OHCI_DEBUG( for(i=0; i < data_len; i++ ) printk(" %02x", ((__u8 *) data)[i]);) + OHCI_DEBUG( printk(" ret_status: %x\n", status); }) + return 0; +} + +static int sohci_request_irq(struct usb_device *usb_dev, unsigned int pipe, usb_device_irq handler, int period, void *dev_id) +{ + struct ohci * ohci = usb_dev->bus->hcpriv; + union ep_addr_ ep_addr; + + ep_addr.iep = 0; + ep_addr.bep.ep = ((pipe >> 15) & 0x0f) /* endpoint address */ + | (pipe & 0x80) /* direction */ + | (1 << 5); /* type = int*/ + ep_addr.bep.fa = ((pipe >> 8) & 0x7f); /* device address */ + + OHCI_DEBUG( printk("USB HC IRQ >>>: %x: every %d ms\n", ep_addr.iep, period);) + + usb_ohci_add_ep(ohci, ep_addr.iep, period, 1, sohci_int_handler, 1 << ((pipe & 0x03) + 3) , (pipe >> 26) & 0x01); + + ohci_trans_req(ohci, ep_addr.iep, 0, NULL, ((struct ohci_device *) usb_dev->hcpriv)->data, 8, (__OHCI_BAG) handler, (__OHCI_BAG) dev_id); + return 0; +} + + +static int sohci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void *cmd, void *data, int len) +{ + struct wait_queue wait = { current, NULL }; + struct ohci * ohci = usb_dev->bus->hcpriv; + int status; + union ep_addr_ ep_addr; + + ep_addr.iep = 0; + ep_addr.bep.ep = ((pipe >> 15) & 0x0f) /* endpoint address */ + | (pipe & 0x80) /* direction */ + | (1 << 6); /* type = ctrl*/ + ep_addr.bep.fa = ((pipe >> 8) & 0x7f); /* device address */ + + status = 0xf; /* CC not Accessed */ + OHCI_DEBUG( { int i; printk("USB HC CTRL>>>: %x: ctrl(%d):", ep_addr.iep, 8);) + OHCI_DEBUG( for(i=0; i < 8; i++ ) printk(" %02x", ((__u8 *) cmd)[i]);) + OHCI_DEBUG( printk(" data(%d):", len);) + OHCI_DEBUG( for(i=0; i < len; i++ ) printk(" %02x", ((__u8 *) data)[i]);) + OHCI_DEBUG( printk("\n"); }) + + usb_ohci_add_ep(ohci, ep_addr.iep, 0, 1, sohci_ctrl_handler, 1 << ((pipe & 0x03) + 3) , (pipe >> 26) & 0x01); + + current->state = TASK_UNINTERRUPTIBLE; + add_wait_queue(&control_wakeup, &wait); + + ohci_trans_req(ohci, ep_addr.iep, 8, cmd, data, len, (__OHCI_BAG) &status, 0); + + schedule_timeout(HZ/10); + + remove_wait_queue(&control_wakeup, &wait); + + OHCI_DEBUG(printk("USB HC status::: %x\n", cc_to_status[status & 0x0f]);) + + return cc_to_status[status & 0x0f]; +} + + +static int sohci_usb_deallocate(struct usb_device *usb_dev) { + struct ohci_device *dev = usb_to_ohci(usb_dev); + union ep_addr_ ep_addr; + + ep_addr.iep = 0; + + OHCI_DEBUG(printk("USB HC dealloc %x\n", usb_dev->devnum);) + + /* wait_ms(20); */ + + if(usb_dev->devnum >=0) { + ep_addr.bep.fa = usb_dev->devnum; + usb_ohci_rm_function(((struct ohci_device *)usb_dev->hcpriv)->ohci, ep_addr.iep); + } + + USB_FREE(dev); + USB_FREE(usb_dev); + + return 0; +} + +static struct usb_device *sohci_usb_allocate(struct usb_device *parent) { + + struct usb_device *usb_dev; + struct ohci_device *dev; + + + USB_ALLOC(usb_dev, sizeof(*usb_dev)); + if (!usb_dev) + return NULL; + + memset(usb_dev, 0, sizeof(*usb_dev)); + + USB_ALLOC(dev, sizeof(*dev)); + if (!dev) { + USB_FREE(usb_dev); + return NULL; + } + + /* Initialize "dev" */ + memset(dev, 0, sizeof(*dev)); + + usb_dev->hcpriv = dev; + dev->usb = usb_dev; + + usb_dev->parent = parent; + + if (parent) { + usb_dev->bus = parent->bus; + dev->ohci = usb_to_ohci(parent)->ohci; + } + return usb_dev; +} + +struct usb_operations sohci_device_operations = { + sohci_usb_allocate, + sohci_usb_deallocate, + sohci_control_msg, + sohci_request_irq, +}; + + +/****** + *** ED handling functions + ************************************/ + + + +/* + * search for the right place to insert an interrupt ed into the int tree + * do some load ballancing + * */ + +static int usb_ohci_int_ballance(struct ohci * ohci, int interval, int load) { + + int i,j; + + j = 0; /* search for the least loaded interrupt endpoint branch of all 32 branches */ + for(i=0; i< 32; i++) if(ohci->ohci_int_load[j] > ohci->ohci_int_load[i]) j=i; + + if(interval < 1) interval = 1; + if(interval > 32) interval = 32; + for(i= 0; ((interval >> i) > 1 ); interval &= (0xfffe << i++ )); /* interval = 2^int(ld(interval)) */ + + + for(i=j%interval; i< 32; i+=interval) ohci->ohci_int_load[i] += load; + j = interval + (j % interval); + + OHCI_DEBUG(printk("USB HC new int ed on pos : %x \n",j);) + + return j; +} + +/* get the ed from the endpoint / device adress */ + +struct usb_ohci_ed * ohci_find_ep(struct ohci *ohci, unsigned int ep_addr_in) { + +union ep_addr_ ep_addr; +struct usb_ohci_ed *tmp; +unsigned int mask; + +mask = 0; +ep_addr.iep = ep_addr_in; + +#ifdef VROOTHUB + if(ep_addr.bep.fa == ohci->root_hub_funct_addr) { + if((ep_addr.bep.ep & 0x0f) == 0) + return &ohci->ed_rh_ep0; /* root hub ep0 */ + else + return &ohci->ed_rh_epi; /* root hub int ep */ + } +#endif + + tmp = ohci->ed_func_ep0[ep_addr.bep.fa]; + mask = ((((ep_addr.bep.ep >> 5) & 0x03)==2)?0x7f:0xff); + ep_addr.bep.ep &= mask; /* mask out direction of ctrl ep */ + + while (tmp != NULL) { + if (tmp->ep_addr.iep == ep_addr.iep) + return tmp; + tmp = tmp->ed_list; + } + return NULL; +} + +spinlock_t usb_ed_lock = SPIN_LOCK_UNLOCKED; +/* add a new endpoint ep_addr */ +struct usb_ohci_ed *usb_ohci_add_ep(struct ohci * ohci, unsigned int ep_addr_in, int interval, int load, f_handler handler, int ep_size, int speed) { + + struct usb_ohci_ed * ed; + struct usb_ohci_td * td; + union ep_addr_ ep_addr; + + + int int_junk; + + struct usb_ohci_ed *tmp; + + ep_addr.iep = ep_addr_in ; + ep_addr.bep.ep &= ((((ep_addr.bep.ep >> 5) & 0x03)==2)?0x7f:0xff); /* mask out direction of ctrl ep */ + + spin_lock(&usb_ed_lock); + + tmp = ohci_find_ep(ohci, ep_addr.iep); + if (tmp != NULL) { + +#ifdef VROOTHUB + if(ep_addr.bep.fa == ohci->root_hub_funct_addr) { + if((ep_addr.bep.ep & 0x0f) != 0) { /* root hub int ep */ + ohci->ed_rh_epi.handler = handler; + ohci_init_rh_int_timer(ohci, interval); + } + else { /* root hub ep0 */ + ohci->ed_rh_ep0.handler = handler; + } + } + + else +#endif + + { + tmp->hw.info = ep_addr.bep.fa | ((ep_addr.bep.ep & 0xf) <<7) + + | (((ep_addr.bep.ep & 0x60) == 0)? 0x8000 : 0) + | (speed << 13) + | ep_size <<16; + + tmp->handler = handler; + } + spin_unlock(&usb_ed_lock); + return tmp; /* ed already in use */ + } + + + OHCI_ALLOC(td, sizeof(td)); /* dummy td; end of td list for ed */ + OHCI_ALLOC(ed, sizeof(ed)); + td->prev_td = NULL; + + ed->hw.tail_td = virt_to_bus(&td->hw); + ed->hw.head_td = ed->hw.tail_td; + ed->hw.info = ep_addr.bep.fa | ((ep_addr.bep.ep & 0xf) <<7) + /* | ((ep_addr.bep.port & 0x80)? 0x1000 : 0x0800 ) */ + | (((ep_addr.bep.ep & 0x60) == 0)? 0x8000 : 0) + | (speed << 13) + | ep_size <<16; + + ed->handler = handler; + + switch((ep_addr.bep.ep >> 5) & 0x03) { + case CTRL: + ed->hw.next_ed = 0; + if(ohci->ed_controltail == NULL) { + writel(virt_to_bus(&ed->hw), &ohci->regs->ed_controlhead); + } + else { + ohci->ed_controltail->hw.next_ed = virt_to_bus(&ed->hw); + } + ed->ed_prev = ohci->ed_controltail; + ohci->ed_controltail = ed; + break; + case BULK: + ed->hw.next_ed = 0; + if(ohci->ed_bulktail == NULL) { + writel(virt_to_bus(&ed->hw), &ohci->regs->ed_bulkhead); + } + else { + ohci->ed_bulktail->hw.next_ed = virt_to_bus(&ed->hw); + } + ed->ed_prev = ohci->ed_bulktail; + ohci->ed_bulktail = ed; + break; + case INT: + int_junk = usb_ohci_int_ballance(ohci, interval, load); + ed->hw.next_ed = ohci->hc_area->ed[int_junk].next_ed; + ohci->hc_area->ed[int_junk].next_ed = virt_to_bus(&ed->hw); + ed->ed_prev = (struct usb_ohci_ed *) &ohci->hc_area->ed[int_junk]; + break; + case ISO: + ed->hw.next_ed = 0; + ohci->ed_isotail->hw.next_ed = virt_to_bus(&ed->hw); + ed->ed_prev = ohci->ed_isotail; + ohci->ed_isotail = ed; + break; + } + ed->ep_addr = ep_addr; + + /* Add it to the "hash"-table of known endpoint descriptors */ + + ed->ed_list = ohci->ed_func_ep0[ed->ep_addr.bep.fa]; + ohci->ed_func_ep0[ed->ep_addr.bep.fa] = ed; + + spin_unlock(&usb_ed_lock); + + OHCI_DEBUG(printk("USB HC new ed %x: %x :", ep_addr.iep, (unsigned int ) ed); ) + OHCI_DEBUG({ int i; for( i= 0; i<8 ;i++) printk(" %4x", ((unsigned int *) ed)[i]) ; printk("\n"); }; ) + return 0; +} + +/***** + * Request the removal of an endpoint + * + * put the ep on the rm_list and request a stop of the bulk or ctrl list + * real removal is done at the next start of frame hardware interrupt + */ +int usb_ohci_rm_ep(struct ohci * ohci, struct usb_ohci_ed *ed) +{ + unsigned int flags; + struct usb_ohci_ed *tmp; + + OHCI_DEBUG(printk("USB HC remove ed %x: %x :\n", ed->ep_addr.iep, (unsigned int ) ed); ) + + spin_lock_irqsave(&usb_ed_lock, flags); + + tmp = ohci->ed_func_ep0[ed->ep_addr.bep.fa]; + if (tmp == NULL) { + spin_unlock_irqrestore(&usb_ed_lock, flags); + return 0; + } + + if(tmp == ed) { + ohci->ed_func_ep0[ed->ep_addr.bep.fa] = ed->ed_list; + } + else { + while (tmp->ed_list != ed) { + if (tmp->ed_list == NULL) { + spin_unlock_irqrestore(&usb_ed_lock, flags); + return 0; + } + tmp = tmp->ed_list; + } + tmp->ed_list = ed->ed_list; + } + ed->ed_list = ohci->ed_rm_list; + ohci->ed_rm_list = ed; + ed->hw.info |= OHCI_ED_SKIP; + + switch((ed->ep_addr.bep.ep >> 5) & 0x03) { + case CTRL: + writel_mask(~(0x01<<4), &ohci->regs->control); /* stop CTRL list */ + break; + case BULK: + writel_mask(~(0x01<<5), &ohci->regs->control); /* stop BULK list */ + break; + } + + + writel( OHCI_INTR_SF, &ohci->regs->intrenable); /* enable sof interrupt */ + + spin_unlock_irqrestore(&usb_ed_lock, flags); + + return 1; +} + +/* we have requested to stop the bulk or CTRL list, + * now we can remove the eds on the rm_list */ + +static int ohci_rm_eds(struct ohci * ohci) { + + unsigned int flags; + struct usb_ohci_ed *ed; + struct usb_ohci_ed *ed_tmp; + struct usb_ohci_td *td; + __u32 td_hw_tmp; + __u32 td_hw; + + spin_lock_irqsave(&usb_ed_lock, flags); + + ed = ohci->ed_rm_list; + + while (ed != NULL) { + + switch((ed->ep_addr.bep.ep >> 5) & 0x03) { + case CTRL: + if(ed->ed_prev == NULL) { + writel(ed->hw.next_ed, &ohci->regs->ed_controlhead); + } + else { + ed->ed_prev->hw.next_ed = ed->hw.next_ed; + } + if(ohci->ed_controltail == ed) { + ohci->ed_controltail = ed->ed_prev; + } + break; + case BULK: + if(ed->ed_prev == NULL) { + writel(ed->hw.next_ed, &ohci->regs->ed_bulkhead); + } + else { + ed->ed_prev->hw.next_ed = ed->hw.next_ed; + } + if(ohci->ed_bulktail == ed) { + ohci->ed_bulktail = ed->ed_prev; + } + break; + case INT: + ed->ed_prev->hw.next_ed = ed->hw.next_ed; + break; + case ISO: + ed->ed_prev->hw.next_ed = ed->hw.next_ed; + if(ohci->ed_isotail == ed) { + ohci->ed_isotail = ed->ed_prev; + } + break; + } + + if(ed->hw.next_ed != 0) ((struct usb_ohci_ed *) bus_to_virt(ed->hw.next_ed))->ed_prev = ed->ed_prev; + + +/* tds directly connected to ed */ + + td_hw = ed->hw.head_td & 0xfffffff0; + while(td_hw != 0) { + td = bus_to_virt(td_hw); + td_hw_tmp = td_hw; + td_hw = td->hw.next_td; + OHCI_FREE(td); /* free pending tds */ + if(td_hw_tmp == ed->hw.tail_td) break; + + } + + /* mark TDs on the hc done list (if there are any) */ + td_hw = readl(&ohci->regs->donehead) & 0xfffffff0; + while(td_hw != 0) { + td = bus_to_virt(td_hw); + td_hw = td->hw.next_td; + if(td->ep == ed) td->ep = 0; + } + + /* mark TDs on the hcca done list (if there are any) */ + td_hw = ohci->hc_area->hcca.done_head & 0xfffffff0 ; + + while(td_hw != 0) { + td = bus_to_virt(td_hw); + td_hw = td->hw.next_td; + if(td->ep == ed) td->ep = 0; + } + + ed_tmp = ed; + ed = ed->ed_list; + OHCI_FREE(ed_tmp); /* free ed */ + } + writel(0, &ohci->regs->ed_controlcurrent); /* reset CTRL list */ + writel(0, &ohci->regs->ed_bulkcurrent); /* reset BULK list */ + writel_set((0x01<<4), &ohci->regs->control); /* start CTRL u. (BULK list) */ + + spin_unlock_irqrestore(&usb_ed_lock, flags); + + ohci->ed_rm_list = NULL; + OHCI_DEBUG(printk("USB HC after rm ed control: %4x intrstat: %4x intrenable: %4x\n", readl(&ohci->regs->control),readl(&ohci->regs->intrstatus),readl(&ohci->regs->intrenable));) + + + return 0; +} + +/* remove all endpoints of a function (device) */ +int usb_ohci_rm_function( struct ohci * ohci, unsigned int ep_addr_in) +{ + struct usb_ohci_ed *ed; + struct usb_ohci_ed *tmp; + union ep_addr_ ep_addr; + + + + ep_addr.iep = ep_addr_in; + + for(ed = ohci->ed_func_ep0[ep_addr.bep.fa]; ed != NULL;) { + tmp = ed; + ed = ed->ed_list; + usb_ohci_rm_ep(ohci, tmp); + } + + + + return 1; +} + + + + + +/****** + *** TD handling functions + ************************************/ + + +#define FILL_TD(TD_PT, HANDLER, INFO, DATA, LEN, LW0, LW1) \ + td_pt = (TD_PT); \ + td_pt1 = (struct usb_ohci_td *) bus_to_virt(usb_ep->hw.tail_td); \ + td_pt1->ep = usb_ep; \ + td_pt1->handler = (HANDLER); \ + td_pt1->buffer_start = (DATA); \ + td_pt1->lw0 = (LW0); \ + td_pt1->lw1 = (LW1); \ + td_pt1->hw.info = (INFO); \ + td_pt1->hw.cur_buf = virt_to_bus(DATA); \ + td_pt1->hw.buf_end = td_pt1->hw.cur_buf + (LEN) - 1; \ + td_pt1->hw.next_td = virt_to_bus(td_pt); \ + usb_ep->hw.tail_td = virt_to_bus(td_pt); \ + td_pt->prev_td = td_pt1; \ + td_pt->hw.next_td = 0 + +spinlock_t usb_req_lock = SPIN_LOCK_UNLOCKED; + +int ohci_trans_req(struct ohci * ohci, unsigned int ep_addr, int ctrl_len, void *ctrl, void * data, int data_len, __OHCI_BAG lw0, __OHCI_BAG lw1) { + + int ed_type; + unsigned int flags; + struct usb_ohci_td *td_pt; + struct usb_ohci_td *td_pt1; + struct usb_ohci_td *td_pt_a1, *td_pt_a2, *td_pt_a3; + struct usb_ohci_ed *usb_ep; + f_handler handler; + + + td_pt_a1 =NULL; + td_pt_a2 =NULL; + td_pt_a3 =NULL; + + usb_ep = ohci_find_ep(ohci, ep_addr); + if(usb_ep == NULL ) return -1; /* not known ep */ + + handler = usb_ep->handler; + +#ifdef VROOTHUB + if(usb_ep == &ohci->ed_rh_ep0) { /* root hub ep 0 control endpoint */ + root_hub_control_msg(ohci, 8, ctrl, data, data_len, lw0, lw1, handler); + return 0; + } + + if(usb_ep == &ohci->ed_rh_epi) { /* root hub interrupt endpoint */ + + root_hub_int_req(ohci, 8, ctrl, data, data_len, lw0, lw1, handler); + return 0; + } +#endif + /* struct usb_ohci_ed * usb_ep = usb_ohci_add_ep(pipe, ohci, interval, 1); */ + + ed_type = ((((union ep_addr_)ep_addr).bep.ep >> 5) & 0x07); + + switch(ed_type) { + case BULK_IN: + case BULK_OUT: + case INT_IN: + case INT_OUT: + OHCI_ALLOC(td_pt_a1, sizeof(td_pt_a1)); + break; + + case CTRL_IN: + case CTRL_OUT: + OHCI_ALLOC(td_pt_a1, sizeof(td_pt_a1)); + OHCI_ALLOC(td_pt_a3, sizeof(td_pt_a3)); + if(data_len > 0) { + OHCI_ALLOC(td_pt_a2, sizeof(td_pt_a2)); + } + break; + + case ISO_IN: + case ISO_OUT: + + } + + spin_lock_irqsave(&usb_req_lock, flags); + + switch(ed_type) { + case BULK_IN: + FILL_TD( td_pt_a1, handler, TD_CC | TD_R | TD_DP_IN | TD_T_TOGGLE, data, data_len, lw0, lw1 ); + writel( OHCI_BLF, &ohci->regs->cmdstatus); /* start bulk list */ + break; + + case BULK_OUT: + FILL_TD( td_pt_a1, handler, TD_CC | TD_DP_OUT | TD_T_TOGGLE, data, data_len, lw0, lw1 ); + writel( OHCI_BLF, &ohci->regs->cmdstatus); /* start bulk list */ + break; + + case INT_IN: + FILL_TD( td_pt_a1, handler, TD_CC | TD_R | TD_DP_IN | TD_T_TOGGLE, data, data_len, lw0, lw1 ); + break; + + case INT_OUT: + FILL_TD( td_pt_a1, handler, TD_CC | TD_DP_OUT | TD_T_TOGGLE, data, data_len, lw0, lw1 ); + break; + + case CTRL_IN: + FILL_TD( td_pt_a1, NULL, TD_CC | TD_DP_SETUP | TD_T_DATA0, ctrl, ctrl_len, 0, 0 ); + if(data_len > 0) { + FILL_TD( td_pt_a2, NULL, TD_CC | TD_R | TD_DP_IN | TD_T_DATA1, data, data_len, 0, 0 ); + } + FILL_TD( td_pt_a3, handler, TD_CC | TD_DP_OUT | TD_T_DATA1, NULL, 0, lw0, lw1 ); + writel( OHCI_CLF, &ohci->regs->cmdstatus); /* start Control list */ + break; + + case CTRL_OUT: + FILL_TD( td_pt_a1, NULL, TD_CC | TD_DP_SETUP | TD_T_DATA0, ctrl, ctrl_len, 0, 0 ); + if(data_len > 0) { + FILL_TD( td_pt_a2, NULL, TD_CC | TD_R | TD_DP_OUT | TD_T_DATA1, data, data_len, 0, 0 ); + } + FILL_TD( td_pt_a3, handler, TD_CC | TD_DP_IN | TD_T_DATA1, NULL, 0, lw0, lw1 ); + writel( OHCI_CLF, &ohci->regs->cmdstatus); /* start Control list */ + break; + + case ISO_IN: + case ISO_OUT: + break; + } + + + + + td_pt1 = (struct usb_ohci_td *) bus_to_virt(usb_ep->hw.tail_td); + + + if(td_pt_a3 != NULL) td_pt_a3->prev_td = NULL; + else if (td_pt_a2 != NULL) td_pt_a2->prev_td = NULL; + else if (td_pt_a1 != NULL) td_pt_a1->prev_td = NULL; + + spin_unlock_irqrestore(&usb_req_lock, flags); + return 0; +} + + +/****** + *** Done List handling functions + ************************************/ + +/* replies to the request have to be on a FIFO basis so + * we reverse the reversed done-list */ + +static struct usb_ohci_td * ohci_reverse_done_list(struct ohci * ohci) { + + __u32 td_list_hc; + struct usb_ohci_td * td_list = NULL; + struct usb_ohci_td * td_rev = NULL; + + td_list_hc = ohci->hc_area->hcca.done_head & 0xfffffff0; + ohci->hc_area->hcca.done_head = 0; + + while(td_list_hc) { + + td_list = (struct usb_ohci_td *) bus_to_virt(td_list_hc); + td_list->next_dl_td = td_rev; + + td_rev = td_list; + td_list_hc = td_list->hw.next_td & 0xfffffff0; + } + return td_list; +} + +/* all done requests are replied here */ +static int usb_ohci_done_list(struct ohci * ohci) { + + struct usb_ohci_td * td = NULL; + struct usb_ohci_td * td_list; + struct usb_ohci_td * td_list_next = NULL; + struct usb_ohci_td * td_err = NULL; + __u32 td_hw; + + + td_list = ohci_reverse_done_list(ohci); + + while(td_list) { + td_list_next = td_list->next_dl_td; + td = td_list; + + if(td->ep == NULL) { /* removed ep */ + OHCI_FREE(td_list); + break; + } + + /* the HC halts an ED if an error occurs; put all pendings TDs of an halted ED on the + * done list; they are marked with an 0xf CC_error code + */ + + if(TD_CC_GET(td_list->hw.info) != TD_CC_NOERROR) { /* on error move all pending tds of an ed into the done list */ + printk("******* USB BUS error %x @ep %x\n", TD_CC_GET(td_list->hw.info), td_list->ep->ep_addr.iep); + td_err= td_list; + td_hw = td_list->ep->hw.head_td & 0xfffffff0; + while(td_hw != 0) { + if(td_hw == td_list->ep->hw.tail_td) break; + td = bus_to_virt(td_hw); + td_err->next_dl_td = td; + td_err= td; + td_hw = td->hw.next_td; + } + td_list->ep->hw.head_td = td_list->ep->hw.tail_td; + td->next_dl_td = td_list_next; + td_list_next = td_list->next_dl_td; + + } + /* send the reply */ + if(td_list->handler != NULL) { + if(td_list->prev_td == NULL) { + td_list->handler((void *) ohci, + td_list->ep->ep_addr.iep, + 0, + NULL, + td_list->buffer_start, + td_list->hw.buf_end-virt_to_bus(td_list->buffer_start)+1, + TD_CC_GET(td_list->hw.info), + td_list->lw0, + td_list->lw1); + OHCI_FREE(td_list); + } + else { + if(td_list->prev_td->prev_td == NULL) { /* cntrl 2 Transactions dataless */ + td_list->handler((void *) ohci, + td_list->ep->ep_addr.iep, + td_list->prev_td->hw.buf_end-virt_to_bus(td_list->prev_td->buffer_start)+1, + td_list->prev_td->buffer_start, + NULL, + 0, + (TD_CC_GET(td_list->prev_td->hw.info) > 0) ? TD_CC_GET(td_list->prev_td->hw.info) : TD_CC_GET(td_list->hw.info), + td_list->lw0, + td_list->lw1); + OHCI_FREE(td_list->prev_td); + OHCI_FREE(td_list); + } + else { /* cntrl 3 Transactions */ + td_list->handler((void *) ohci, + td_list->ep->ep_addr.iep, + td_list->prev_td->prev_td->hw.buf_end-virt_to_bus(td_list->prev_td->prev_td->buffer_start)+1, + td_list->prev_td->prev_td->buffer_start, + td_list->prev_td->buffer_start, + td_list->prev_td->hw.buf_end-virt_to_bus(td_list->prev_td->buffer_start)+1, + (TD_CC_GET(td_list->prev_td->prev_td->hw.info) > 0) ? TD_CC_GET(td_list->prev_td->prev_td->hw.info) + : (TD_CC_GET(td_list->prev_td->hw.info) > 0) ? TD_CC_GET(td_list->prev_td->hw.info) : TD_CC_GET(td_list->hw.info), + td_list->lw0, + td_list->lw1); + OHCI_FREE(td_list->prev_td->prev_td); + OHCI_FREE(td_list->prev_td); + OHCI_FREE(td_list); + + } + } + + } + td_list = td_list_next; + } + return 0; +} + + + +/****** + *** HC functions + ************************************/ + + + +void reset_hc(struct ohci *ohci) { + int retries = 5; + int timeout = 30; + int fminterval; + + if(readl(&ohci->regs->control) & 0x100) { /* SMM owns the HC */ + writel(0x08, &ohci->regs->cmdstatus); /* request ownership */ + printk("USB HC TakeOver from SMM\n"); + do { + wait_ms(100); + if(--retries) { + printk("USB HC TakeOver timed out!\n"); + break; + } + } + while(readl(&ohci->regs->control) & 0x100); + } + + writel((1<<31), &ohci->regs->intrdisable); /* Disable HC interrupts */ + OHCI_DEBUG(printk("USB HC reset_hc: %x ; retries: %d\n", readl(&ohci->regs->control), 5-retries);) + fminterval = readl(&ohci->regs->fminterval) & 0x3fff; + writel(1, &ohci->regs->cmdstatus); /* HC Reset */ + while ((readl(&ohci->regs->cmdstatus) & 0x01) != 0) { /* 10us Reset */ + if (--timeout == 0) { + printk("USB HC reset timed out!\n"); + return; + } + udelay(1); + } + /* set the timing */ + fminterval |= (((fminterval -210) * 6)/7)<<16; + writel(fminterval, &ohci->regs->fminterval); + writel(((fminterval&0x3fff)*9)/10, &ohci->regs->periodicstart); +} + + +/* + * Reset and start an OHCI controller + */ +void start_hc(struct ohci *ohci) +{ + /* int fminterval; */ + unsigned int mask; + /* fminterval = readl(&ohci->regs->fminterval) & 0x3fff; + reset_hc(ohci); */ + + + writel(virt_to_bus(&ohci->hc_area->hcca), &ohci->regs->hcca); /* a reset clears this */ + + /* Choose the interrupts we care about now, others later on demand */ + mask = OHCI_INTR_MIE | OHCI_INTR_WDH; + /* | OHCI_INTR_SO | OHCI_INTR_UE |OHCI_INTR_RHSC |OHCI_INTR_SF| + OHCI_INTR_FNO */ + + + + writel((0x00), &ohci->regs->control); /* USB Reset BUS */ + wait_ms(10); + + writel((0x97), &ohci->regs->control); /* USB Operational */ + + writel( 0x10000, &ohci->regs->roothub.status); /* root hub power on */ + wait_ms(50); + + OHCI_DEBUG(printk("USB HC rstart_hc_operational: %x\n", readl(&ohci->regs->control)); ) + OHCI_DEBUG(printk("USB HC roothubstata: %x \n", readl( &(ohci->regs->roothub.a) )); ) + OHCI_DEBUG(printk("USB HC roothubstatb: %x \n", readl( &(ohci->regs->roothub.b) )); ) + OHCI_DEBUG(printk("USB HC roothubstatu: %x \n", readl( &(ohci->regs->roothub.status) )); ) + OHCI_DEBUG(printk("USB HC roothubstat1: %x \n", readl( &(ohci->regs->roothub.portstatus[0]) )); ) + OHCI_DEBUG(printk("USB HC roothubstat2: %x \n", readl( &(ohci->regs->roothub.portstatus[1]) )); ) + + /* control_wakeup = NULL; */ + writel(mask, &ohci->regs->intrenable); + writel(mask, &ohci->regs->intrstatus); + +#ifdef VROOTHUB + { + + struct usb_device * usb_dev; + struct ohci_device *dev; + + usb_dev = sohci_usb_allocate(ohci->root_hub->usb); + dev = usb_dev->hcpriv; + + dev->ohci = ohci; + + usb_connect(usb_dev); + + ohci->root_hub->usb->children[0] = usb_dev; + + usb_new_device(usb_dev); + } +#endif + + + +} + + + + +static void ohci_interrupt(int irq, void *__ohci, struct pt_regs *r) +{ + struct ohci *ohci = __ohci; + struct ohci_regs *regs = ohci->regs; + + int ints; + + + if((ohci->hc_area->hcca.done_head != 0) && !(ohci->hc_area->hcca.done_head & 0x01)) { + ints = OHCI_INTR_WDH; + } + else { + if((ints = (readl(®s->intrstatus) & readl(®s->intrenable))) == 0) + return; + } + + ohci->intrstatus |= ints; + OHCI_DEBUG(printk("USB HC interrupt: %x (%x) \n", ints, readl(&ohci->regs->intrstatus));) + + /* ints &= ~(OHCI_INTR_WDH); WH Bit will be set by done list subroutine */ + /* if(ints & OHCI_INTR_FNO) { + writel(OHCI_INTR_FNO, ®s->intrstatus); + if (waitqueue_active(&ohci_tasks)) wake_up(&ohci_tasks); + } */ + + if(ints & OHCI_INTR_WDH) { + writel(OHCI_INTR_WDH, ®s->intrdisable); + ohci->intrstatus &= (~OHCI_INTR_WDH); + usb_ohci_done_list(ohci); /* prepare out channel list */ + writel(OHCI_INTR_WDH, &ohci->regs->intrstatus); + writel(OHCI_INTR_WDH, &ohci->regs->intrenable); + + } + + if(ints & OHCI_INTR_SF) { + writel(OHCI_INTR_SF, ®s->intrdisable); + writel(OHCI_INTR_SF, &ohci->regs->intrstatus); + ohci->intrstatus &= (~OHCI_INTR_SF); + if(ohci->ed_rm_list != NULL) { + ohci_rm_eds(ohci); + } + } +#ifndef VROOTHUB + if(ints & OHCI_INTR_RHSC) { + writel(OHCI_INTR_RHSC, ®s->intrdisable); + writel(OHCI_INTR_RHSC, &ohci->regs->intrstatus); + wake_up(&root_hub); + + } + #endif + + writel(OHCI_INTR_MIE, ®s->intrenable); + +} + +#ifndef VROOTHUB +/* + * This gets called if the connect status on the root + * hub (and the root hub only) changes. + */ +static void ohci_connect_change(struct ohci *ohci, unsigned int port_nr) +{ + struct usb_device *usb_dev; + struct ohci_device *dev; + OHCI_DEBUG(printk("uhci_connect_change: called for %d stat %x\n", port_nr,readl(&ohci->regs->roothub.portstatus[port_nr]) );) + + /* + * Even if the status says we're connected, + * the fact that the status bits changed may + * that we got disconnected and then reconnected. + * + * So start off by getting rid of any old devices.. + */ + usb_disconnect(&ohci->root_hub->usb->children[port_nr]); + + if(!(readl(&ohci->regs->roothub.portstatus[port_nr]) & RH_PS_CCS)) { + writel(RH_PS_CCS, &ohci->regs->roothub.portstatus[port_nr]); + return; /* nothing connected */ + } + /* + * Ok, we got a new connection. Allocate a device to it, + * and find out what it wants to do.. + */ + usb_dev = sohci_usb_allocate(ohci->root_hub->usb); + dev = usb_dev->hcpriv; + dev->ohci = ohci; + usb_connect(dev->usb); + ohci->root_hub->usb->children[port_nr] = usb_dev; + wait_ms(200); /* wait for powerup */ + /* reset port/device */ + writel(RH_PS_PRS, &ohci->regs->roothub.portstatus[port_nr]); /* reset port */ + while(!(readl( &ohci->regs->roothub.portstatus[port_nr]) & RH_PS_PRSC)) wait_ms(10); /* reset active ? */ + writel(RH_PS_PES, &ohci->regs->roothub.portstatus[port_nr]); /* enable port */ + wait_ms(10); + /* Get speed information */ + usb_dev->slow = (readl( &ohci->regs->roothub.portstatus[port_nr]) & RH_PS_LSDA) ? 1 : 0; + + /* + * Ok, all the stuff specific to the root hub has been done. + * The rest is generic for any new USB attach, regardless of + * hub type. + */ + usb_new_device(usb_dev); +} +#endif + + + +/* + * Allocate the resources required for running an OHCI controller. + * Host controller interrupts must not be running while calling this + * function. + * + * The mem_base parameter must be the usable -virtual- address of the + * host controller's memory mapped I/O registers. + * + * This is where OHCI triumphs over UHCI, because good is dumb. + * Note how much simpler this function is than in uhci.c. + * + * OHCI hardware takes care of most of the scheduling of different + * transfer types with the correct prioritization for us. + */ + + +static struct ohci *alloc_ohci(void* mem_base) +{ + int i,j; + struct ohci *ohci; + struct ohci_hc_area *hc_area; + struct usb_bus *bus; + struct ohci_device *dev; + struct usb_device *usb; + + /* + * Here we allocate some dummy EDs as well as the + * OHCI host controller communications area. The HCCA is just + * a nice pool of memory with pointers to endpoint descriptors + * for the different interrupts. + * + * The first page of memory contains the HCCA and ohci structure + */ + hc_area = (struct ohci_hc_area *) __get_free_pages(GFP_KERNEL, 1); + if (!hc_area) + return NULL; + memset(hc_area, 0, sizeof(*hc_area)); + ohci = &hc_area->ohci; + ohci->irq = -1; + ohci->regs = mem_base; + + ohci->hc_area = hc_area; + /* Tell the controller where the HCCA is */ + writel(virt_to_bus(&hc_area->hcca), &ohci->regs->hcca); + + + /* + * Initialize the ED polling "tree", full tree; + * dummy eds ed[i] (hc should skip them) + * i == 0 is the end of the iso list; + * 1 is the 1ms node, + * 2,3 2ms nodes, + * 4,5,6,7 4ms nodes, + * 8 ... 15 8ms nodes, + * 16 ... 31 16ms nodes, + * 32 ... 63 32ms nodes + * Sequenzes: + * 32-16- 8-4-2-1-0 + * 33-17- 9-5-3-1-0 + * 34-18-10-6-2-1-0 + * 35-19-11-7-3-1-0 + * 36-20-12-4-2-1-0 + * 37-21-13-5-3-1-0 + * 38-22-14-6-2-1-0 + * 39-23-15-7-3-1-0 + * 40-24- 8-4-2-1-0 + * 41-25- 9-5-3-1-0 + * 42-26-10-6-2-1-0 + * : : + * 63-31-15-7-3-1-0 + */ + hc_area->ed[ED_ISO].info |= OHCI_ED_SKIP; /* place holder, so skip it */ + hc_area->ed[ED_ISO].next_ed = 0x0000; /* end of iso list */ + + hc_area->ed[1].next_ed = virt_to_bus(&(hc_area->ed[ED_ISO])); + hc_area->ed[1].info |= OHCI_ED_SKIP; /* place holder, skip it */ + + j=1; + for (i = 2; i < (NUM_INTS * 2); i++) { + if (i >= NUM_INTS) + hc_area->hcca.int_table[i - NUM_INTS] = virt_to_bus(&(hc_area->ed[i])); + + if( i == j*4) j *= 2; + hc_area->ed[i].next_ed = virt_to_bus(&(hc_area->ed[j+ i%j])); + hc_area->ed[i].info |= OHCI_ED_SKIP; /* place holder, skip it */ + } + + + /* + * for load ballancing of the interrupt branches + */ + for (i = 0; i < NUM_INTS; i++) ohci->ohci_int_load[i] = 0; + + /* + * Store the end of control and bulk list eds. So, we know where we can add + * elements to these lists. + */ + ohci->ed_isotail = (struct usb_ohci_ed *) &(hc_area->ed[ED_ISO]); + ohci->ed_controltail = NULL; + ohci->ed_bulktail = NULL; + + /* + * Tell the controller where the control and bulk lists are + * The lists are empty now. + */ + writel(0, &ohci->regs->ed_controlhead); + writel(0, &ohci->regs->ed_bulkhead); + + + USB_ALLOC(bus, sizeof(*bus)); + if (!bus) + return NULL; + + memset(bus, 0, sizeof(*bus)); + + ohci->bus = bus; + bus->hcpriv = (void *) ohci; + bus->op = &sohci_device_operations; + + + usb = sohci_usb_allocate(NULL); + if (!usb) + return NULL; + + dev = ohci->root_hub = usb_to_ohci(usb); + + usb->bus = bus; + /* bus->root_hub = ohci_to_usb(ohci->root_hub); */ + dev->ohci = ohci; + + /* Initialize the root hub */ + + usb->maxchild = readl(&ohci->regs->roothub.a) & 0xff; + usb_init_root_hub(usb); + + return ohci; +} + + +/* + * De-allocate all resources.. + */ + +static void release_ohci(struct ohci *ohci) +{ + int i; + union ep_addr_ ep_addr; + ep_addr.iep = 0; + + OHCI_DEBUG(printk("USB HC release ohci \n");) + + if (ohci->irq >= 0) { + free_irq(ohci->irq, ohci); + ohci->irq = -1; + } + + /* stop hc */ + writel(OHCI_USB_SUSPEND, &ohci->regs->control); + + /* deallocate all EDs and TDs */ + for(i=0; i < 128; i ++) { + ep_addr.bep.fa = i; + usb_ohci_rm_function(ohci, ep_addr.iep); + } + ohci_rm_eds(ohci); /* remove eds */ + + /* disconnect all devices */ + if(ohci->root_hub) + for(i = 0; i < ohci->root_hub->usb->maxchild; i++) + usb_disconnect(ohci->root_hub->usb->children + i); + + USB_FREE(ohci->root_hub->usb); + USB_FREE(ohci->root_hub); + USB_FREE(ohci->bus); + + /* unmap the IO address space */ + iounmap(ohci->regs); + + + free_pages((unsigned int) ohci->hc_area, 1); + +} + + +void cleanup_drivers(void); + +static int ohci_roothub_thread(void * __ohci) +{ + struct ohci *ohci = (struct ohci *)__ohci; + lock_kernel(); + + /* + * This thread doesn't need any user-level access, + * so get rid of all our resources.. + */ + printk("ohci_roothub_thread at %p\n", &ohci_roothub_thread); + exit_mm(current); + exit_files(current); + exit_fs(current); + + + strcpy(current->comm, "root-hub"); + + + start_hc(ohci); + writel( 0x10000, &ohci->regs->roothub.status); + wait_ms(50); /* root hub power on */ + do { +#ifdef CONFIG_APM + if (apm_resume) { + apm_resume = 0; + start_hc(ohci); + continue; + } +#endif + + OHCI_DEBUG(printk("USB RH tasks: int: %x\n", ohci->intrstatus); ) +#ifndef VROOTHUB + /* if (ohci->intrstatus & OHCI_INTR_RHSC) */ + { + int port_nr; + for(port_nr=0; port_nr< ohci->root_hub->usb->maxchild; port_nr++) + if(readl(&ohci->regs->roothub.portstatus[port_nr]) & (RH_PS_CSC | RH_PS_PRSC)) { + ohci_connect_change(ohci, port_nr); + writel(0xffff0000, &ohci->regs->roothub.portstatus[port_nr]); + } + ohci->intrstatus &= ~(OHCI_INTR_RHSC); + writel(OHCI_INTR_RHSC, &ohci->regs->intrenable); + } +#endif + + interruptible_sleep_on(&root_hub); + + } while (!signal_pending(current)); + +#ifdef VROOTHUB + ohci_del_rh_int_timer(ohci); +#endif + + + cleanup_drivers(); + /* reset_hc(ohci); */ + + release_ohci(ohci); + MOD_DEC_USE_COUNT; + + printk("ohci_control_thread exiting\n"); + + return 0; +} + + + + +/* + * Increment the module usage count, start the control thread and + * return success. + */ +static int found_ohci(int irq, void* mem_base) +{ + int retval; + struct ohci *ohci; + OHCI_DEBUG(printk("USB HC found ohci: irq= %d membase= %x \n", irq, (int)mem_base);) + /* Allocate the running OHCI structures */ + ohci = alloc_ohci(mem_base); + if (!ohci) { + return -ENOMEM; + } + + reset_hc(ohci); + + retval = -EBUSY; + if (request_irq(irq, ohci_interrupt, SA_SHIRQ, "ohci-usb", ohci) == 0) { + int pid; + + MOD_INC_USE_COUNT; + ohci->irq = irq; + + pid = kernel_thread(ohci_roothub_thread, ohci, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + if (pid >= 0) + return 0; + + + MOD_DEC_USE_COUNT; + retval = pid; + } + + release_ohci(ohci); + return retval; +} + +static int start_ohci(struct pci_dev *dev) +{ + unsigned int mem_base = dev->base_address[0]; + + /* If its OHCI, its memory */ + if (mem_base & PCI_BASE_ADDRESS_SPACE_IO) + return -ENODEV; + + /* Get the memory address and map it for IO */ + mem_base &= PCI_BASE_ADDRESS_MEM_MASK; + + /* + * FIXME ioremap_nocache isn't implemented on all CPUs (such + * as the Alpha) [?] What should I use instead... + * + * The iounmap() is done on in release_ohci. + */ + mem_base = (unsigned int) ioremap_nocache(mem_base, 4096); + + if (!mem_base) { + printk("Error mapping OHCI memory\n"); + return -EFAULT; + } + + return found_ohci(dev->irq, (void *) mem_base); +} + + + +#ifdef CONFIG_APM +static int handle_apm_event(apm_event_t event) +{ + static int down = 0; + + switch (event) { + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + if (down) { + printk(KERN_DEBUG "ohci: received extra suspend event\n"); + break; + } + down = 1; + break; + case APM_NORMAL_RESUME: + case APM_CRITICAL_RESUME: + if (!down) { + printk(KERN_DEBUG "ohci: received bogus resume event\n"); + break; + } + down = 0; + if (waitqueue_active(&root_hub)) { + apm_resume = 1; + wake_up(&root_hub); + } + break; + } + return 0; +} +#endif + + + int usb_mouse_init(void); +#ifdef MODULE + +void cleanup_module(void) +{ +#ifdef CONFIG_APM + apm_unregister_callback(&handle_apm_event); +#endif +} + +#define ohci_hcd_init init_module + +#endif + +#define PCI_CLASS_SERIAL_USB_OHCI 0x0C0310 +#define PCI_CLASS_SERIAL_USB_OHCI_PG 0x10 + + +int ohci_hcd_init(void) +{ + int retval; + struct pci_dev *dev = NULL; + + retval = -ENODEV; + + dev = NULL; + while((dev = pci_find_class(PCI_CLASS_SERIAL_USB_OHCI, dev))) { /* OHCI */ + retval = start_ohci(dev); + if (retval < 0) break; + + +#ifdef CONFIG_USB_MOUSE + usb_mouse_init(); +#endif +#ifdef CONFIG_USB_KBD + usb_kbd_init(); +#endif + hub_init(); +#ifdef CONFIG_USB_AUDIO + usb_audio_init(); +#endif +#ifdef CONFIG_APM + apm_register_callback(&handle_apm_event); +#endif + + return 0; + } + return retval; +} + +void cleanup_drivers(void) +{ + hub_cleanup(); +#ifdef CONFIG_USB_MOUSE + usb_mouse_cleanup(); +#endif +} + diff --git a/drivers/usb/ohci-hcd.h b/drivers/usb/ohci-hcd.h new file mode 100644 index 000000000..aa782bb1d --- /dev/null +++ b/drivers/usb/ohci-hcd.h @@ -0,0 +1,404 @@ + /* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * + * The OHCI HCD layer is a simple but nearly complete implementation of what the + * USB people would call a HCD for the OHCI. + * (ISO comming soon, Bulk disabled, INT u. CTRL transfers enabled) + * The layer on top of it, is for interfacing to the alternate-usb device-drivers. + * + * [ This is based on Linus' UHCI code and gregs OHCI fragments (0.03c source tree). ] + * [ Open Host Controller Interface driver for USB. ] + * [ (C) Copyright 1999 Linus Torvalds (uhci.c) ] + * [ (C) Copyright 1999 Gregory P. Smith <greg@electricrain.com> ] + * [ $Log: ohci.c,v $ ] + * [ Revision 1.1 1999/04/05 08:32:30 greg ] + * + * + * v2.1 1999/05/09 ep_addr correction, code clean up + * v2.0 1999/05/04 + * v1.0 1999/04/27 + * ohci-hcd.h + */ + +#include <linux/config.h> + +#ifdef CONFIG_USB_OHCI_VROOTHUB +#define VROOTHUB +#endif +/* enables virtual root hub + * (root hub will be managed by the hub controller + * hub.c of the alternate usb driver) + * last time I did more testing without virtual root hub + * -> the virtual root hub could be more unstable now */ + + + +#ifdef OHCI_DBG +#define OHCI_DEBUG(X) X +#else +#define OHCI_DEBUG(X) +#endif + +/* for readl writel functions */ +#include <linux/list.h> +#include <asm/io.h> + +/* for ED and TD structures */ + +typedef void * __OHCI_BAG; +typedef int (*f_handler )(void * ohci, unsigned int ep_addr, int cmd_len, void *cmd, void *data, int data_len, int status, __OHCI_BAG lw0, __OHCI_BAG lw1); + + + +struct ep_address { + __u8 ep; /* bit 7: IN/-OUT, 6,5: type 10..CTRL 00..ISO 11..BULK 10..INT, 3..0: ep nr */ + __u8 fa; /* function address */ + __u8 hc; + __u8 host; +}; + +union ep_addr_ { + unsigned int iep; + struct ep_address bep; +}; + +/* + * ED and TD descriptors has to be 16-byte aligned + */ +struct ohci_hw_ed { + __u32 info; + __u32 tail_td; /* TD Queue tail pointer */ + __u32 head_td; /* TD Queue head pointer */ + __u32 next_ed; /* Next ED */ +} __attribute((aligned(16))); + + +struct usb_ohci_ed { + struct ohci_hw_ed hw; + /* struct ohci * ohci; */ + f_handler handler; + union ep_addr_ ep_addr; + struct usb_ohci_ed *ed_list; + struct usb_ohci_ed *ed_prev; +} __attribute((aligned(32))); + + /* OHCI Hardware fields */ +struct ohci_hw_td { + __u32 info; + __u32 cur_buf; /* Current Buffer Pointer */ + __u32 next_td; /* Next TD Pointer */ + __u32 buf_end; /* Memory Buffer End Pointer */ +} __attribute((aligned(16))); + +/* TD info field */ +#define TD_CC 0xf0000000 +#define TD_CC_GET(td_p) ((td_p >>28) & 0x04) +#define TD_EC 0x0C000000 +#define TD_T 0x03000000 +#define TD_T_DATA0 0x02000000 +#define TD_T_DATA1 0x03000000 +#define TD_T_TOGGLE 0x00000000 +#define TD_R 0x00040000 +#define TD_DI 0x00E00000 +#define TD_DI_SET(X) (((X) & 0x07)<< 21) +#define TD_DP 0x00180000 +#define TD_DP_SETUP 0x00000000 +#define TD_DP_IN 0x00100000 +#define TD_DP_OUT 0x00080000 + +/* CC Codes */ +#define TD_CC_NOERROR 0x00 +#define TD_CC_CRC 0x01 +#define TD_CC_BITSTUFFING 0x02 +#define TD_CC_DATATOGGLEM 0x03 +#define TD_CC_STALL 0x04 +#define TD_DEVNOTRESP 0x05 +#define TD_PIDCHECKFAIL 0x06 +#define TD_UNEXPECTEDPID 0x07 +#define TD_DATAOVERRUN 0x08 +#define TD_DATAUNDERRUN 0x09 +#define TD_BUFFEROVERRUN 0x0C +#define TD_BUFFERUNDERRUN 0x0D +#define TD_NOTACCESSED 0x0F + + + +struct usb_ohci_td { + struct ohci_hw_td hw; + void * buffer_start; + f_handler handler; + struct usb_ohci_td *prev_td; + struct usb_ohci_ed *ep; + struct usb_ohci_td *next_dl_td; + __OHCI_BAG lw0; + __OHCI_BAG lw1; +} __attribute((aligned(32))); + + + +/* TD types */ +#define BULK 0x03 +#define INT 0x01 +#define CTRL 0x02 +#define ISO 0x00 +/* TD types with direction */ +#define BULK_IN 0x07 +#define BULK_OUT 0x03 +#define INT_IN 0x05 +#define INT_OUT 0x01 +#define CTRL_IN 0x06 +#define CTRL_OUT 0x02 +#define ISO_IN 0x04 +#define ISO_OUT 0x00 + +struct ohci_rep_td { + int cmd_len; + void * cmd; + void * data; + int data_len; + f_handler handler; + struct ohci_rep_td *next_td; + int ep_addr; + __OHCI_BAG lw0; + __OHCI_BAG lw1; + __u32 status; +} __attribute((aligned(32))); + +#define OHCI_ED_SKIP (1 << 14) +#define OHCI_ED_MPS (0x7ff << 16) +#define OHCI_ED_F_NORM (0) +#define OHCI_ED_F_ISOC (1 << 15) +#define OHCI_ED_S_LOW (1 << 13) +#define OHCI_ED_S_HIGH (0) +#define OHCI_ED_D (3 << 11) +#define OHCI_ED_D_IN (2 << 11) +#define OHCI_ED_D_OUT (1 << 11) +#define OHCI_ED_EN (0xf << 7) +#define OHCI_ED_FA (0x7f) + + +/* + * The HCCA (Host Controller Communications Area) is a 256 byte + * structure defined in the OHCI spec. that the host controller is + * told the base address of. It must be 256-byte aligned. + */ +#define NUM_INTS 32 /* part of the OHCI standard */ +struct ohci_hcca { + __u32 int_table[NUM_INTS]; /* Interrupt ED table */ + __u16 frame_no; /* current frame number */ + __u16 pad1; /* set to 0 on each frame_no change */ + __u32 done_head; /* info returned for an interrupt */ + u8 reserved_for_hc[116]; +} __attribute((aligned(256))); + + + +#define ED_INT_1 1 +#define ED_INT_2 2 +#define ED_INT_4 4 +#define ED_INT_8 8 +#define ED_INT_16 16 +#define ED_INT_32 32 +#define ED_CONTROL 64 +#define ED_BULK 65 +#define ED_ISO 0 /* same as 1ms interrupt queue */ + + +/* + * This is the maximum number of root hub ports. I don't think we'll + * ever see more than two as that's the space available on an ATX + * motherboard's case, but it could happen. The OHCI spec allows for + * up to 15... (which is insane!) + * + * Although I suppose several "ports" could be connected directly to + * internal laptop devices such as a keyboard, mouse, camera and + * serial/parallel ports. hmm... That'd be neat. + */ +#define MAX_ROOT_PORTS 15 /* maximum OHCI root hub ports */ + +/* + * This is the structure of the OHCI controller's memory mapped I/O + * region. This is Memory Mapped I/O. You must use the readl() and + * writel() macros defined in asm/io.h to access these!! + */ +struct ohci_regs { + /* control and status registers */ + __u32 revision; + __u32 control; + __u32 cmdstatus; + __u32 intrstatus; + __u32 intrenable; + __u32 intrdisable; + /* memory pointers */ + __u32 hcca; + __u32 ed_periodcurrent; + __u32 ed_controlhead; + __u32 ed_controlcurrent; + __u32 ed_bulkhead; + __u32 ed_bulkcurrent; + __u32 donehead; + /* frame counters */ + __u32 fminterval; + __u32 fmremaining; + __u32 fmnumber; + __u32 periodicstart; + __u32 lsthresh; + /* Root hub ports */ + struct ohci_roothub_regs { + __u32 a; + __u32 b; + __u32 status; + __u32 portstatus[MAX_ROOT_PORTS]; + } roothub; +} __attribute((aligned(32))); + + +/* + * Read a MMIO register and re-write it after ANDing with (m) + */ +#define writel_mask(m, a) writel( (readl((__u32)(a))) & (__u32)(m), (__u32)(a) ) + +/* + * Read a MMIO register and re-write it after ORing with (b) + */ +#define writel_set(b, a) writel( (readl((__u32)(a))) | (__u32)(b), (__u32)(a) ) + +/* + * cmdstatus register */ +#define OHCI_CLF 0x02 +#define OHCI_BLF 0x04 + +/* + * Interrupt register masks + */ +#define OHCI_INTR_SO (1) +#define OHCI_INTR_WDH (1 << 1) +#define OHCI_INTR_SF (1 << 2) +#define OHCI_INTR_RD (1 << 3) +#define OHCI_INTR_UE (1 << 4) +#define OHCI_INTR_FNO (1 << 5) +#define OHCI_INTR_RHSC (1 << 6) +#define OHCI_INTR_OC (1 << 30) +#define OHCI_INTR_MIE (1 << 31) + +/* + * Control register masks + */ +#define OHCI_USB_OPER (2 << 6) +#define OHCI_USB_SUSPEND (3 << 6) + +/* + * This is the full ohci controller description + * + * Note how the "proper" USB information is just + * a subset of what the full implementation needs. (Linus) + */ + + +struct ohci { + int irq; + struct ohci_regs *regs; /* OHCI controller's memory */ + struct ohci_hc_area *hc_area; /* hcca, int ed-tree, ohci itself .. */ + int root_hub_funct_addr; /* Address of Root Hub endpoint */ + int ohci_int_load[32]; /* load of the 32 Interrupt Chains (for load ballancing)*/ + struct usb_ohci_ed * ed_rm_list; /* list of all endpoints to be removed */ + struct usb_ohci_ed * ed_bulktail; /* last endpoint of bulk list */ + struct usb_ohci_ed * ed_controltail; /* last endpoint of control list */ + struct usb_ohci_ed * ed_isotail; /* last endpoint of iso list */ + struct ohci_device * root_hub; + struct usb_ohci_ed ed_rh_ep0; + struct usb_ohci_ed ed_rh_epi; + struct ohci_rep_td *td_rh_epi; + int intrstatus; + struct usb_ohci_ed *ed_func_ep0[128]; /* "hash"-table for ep to ed mapping */ + struct ohci_rep_td *repl_queue; /* for internal requests */ + int rh_int_interval; + int rh_int_timer; + struct usb_bus *bus; + + +}; + +/* + * Warning: This constant must not be so large as to cause the + * ohci_device structure to exceed one 4096 byte page. Or "weird + * things will happen" as the alloc_ohci() function assumes that + * its less than one page at the moment. (FIXME) + */ +#define NUM_TDS 4 /* num of preallocated transfer descriptors */ +#define NUM_EDS 80 /* num of preallocated endpoint descriptors */ + +struct ohci_hc_area { + + struct ohci_hcca hcca; /* OHCI mem. mapped IO area 256 Bytes*/ + + struct ohci_hw_ed ed[NUM_EDS]; /* Endpoint Descriptors 80 * 16 : 1280 Bytes */ + struct ohci_hw_td td[NUM_TDS]; /* Transfer Descriptors 2 * 32 : 64 Bytes */ + struct ohci ohci; + +}; +struct ohci_device { + struct usb_device *usb; + struct ohci *ohci; + unsigned long data[16]; +}; + +#define ohci_to_usb(uhci) ((ohci)->usb) +#define usb_to_ohci(usb) ((struct ohci_device *)(usb)->hcpriv) + +/* Debugging code */ +/*void show_ed(struct ohci_ed *ed); +void show_td(struct ohci_td *td); +void show_status(struct ohci *ohci); */ + +/* hcd */ +int ohci_trans_req(struct ohci * ohci, unsigned int ep_addr, int cmd_len, void *cmd, void * data, int data_len, __OHCI_BAG lw0, __OHCI_BAG lw1); +struct usb_ohci_ed *usb_ohci_add_ep(struct ohci * ohci, unsigned int ep_addr, int interval, int load, f_handler handler, int ep_size, int speed); +int usb_ohci_rm_function(struct ohci * ohci, unsigned int ep_addr); +int usb_ohci_rm_ep(struct ohci * ohci, struct usb_ohci_ed *ed); +struct usb_ohci_ed * ohci_find_ep(struct ohci *ohci, unsigned int ep_addr_in); + +/* roothub */ +int ohci_del_rh_int_timer(struct ohci * ohci); +int ohci_init_rh_int_timer(struct ohci * ohci, int interval); +int root_hub_int_req(struct ohci * ohci, int cmd_len, void * ctrl, void * data, int data_len, __OHCI_BAG lw0, __OHCI_BAG lw1, f_handler handler); +int root_hub_send_irq(struct ohci * ohci, void * data, int data_len ); +int root_hub_control_msg(struct ohci *ohci, int cmd_len, void *rh_cmd, void *rh_data, int len, __OHCI_BAG lw0, __OHCI_BAG lw1, f_handler handler); +int queue_reply(struct ohci * ohci, unsigned int ep_addr, int cmd_len,void * cmd, void * data,int len, __OHCI_BAG lw0, __OHCI_BAG lw1, f_handler handler); +int send_replies(struct ohci * ohci); + + + + +/* Root-Hub Register info */ + +#define RH_PS_CCS 0x00000001 +#define RH_PS_PES 0x00000002 +#define RH_PS_PSS 0x00000004 +#define RH_PS_POCI 0x00000008 +#define RH_PS_PRS 0x00000010 +#define RH_PS_PPS 0x00000100 +#define RH_PS_LSDA 0x00000200 +#define RH_PS_CSC 0x00010000 +#define RH_PS_PESC 0x00020000 +#define RH_PS_PSSC 0x00040000 +#define RH_PS_OCIC 0x00080000 +#define RH_PS_PRSC 0x00100000 + + +#ifdef OHCI_DBG +#define OHCI_FREE(x) kfree(x); printk("OHCI FREE: %d\n", -- __ohci_free_cnt) +#define OHCI_ALLOC(x,size) (x) = kmalloc(size, GFP_KERNEL); printk("OHCI ALLO: %d\n", ++ __ohci_free_cnt) +#define USB_FREE(x) kfree(x); printk("USB FREE: %d\n", -- __ohci_free1_cnt) +#define USB_ALLOC(x,size) (x) = kmalloc(size, GFP_KERNEL); printk("USB ALLO: %d\n", ++ __ohci_free1_cnt) +static int __ohci_free_cnt = 0; +static int __ohci_free1_cnt = 0; +#else +#define OHCI_FREE(x) kfree(x) +#define OHCI_ALLOC(x,size) (x) = kmalloc(size, GFP_KERNEL) +#define USB_FREE(x) kfree(x) +#define USB_ALLOC(x,size) (x) = kmalloc(size, GFP_KERNEL) +#endif + diff --git a/drivers/usb/ohci-root-hub.c b/drivers/usb/ohci-root-hub.c new file mode 100644 index 000000000..6d1018eff --- /dev/null +++ b/drivers/usb/ohci-root-hub.c @@ -0,0 +1,604 @@ +/* + * HCD (OHCI) Virtual Root Hub for USB. + * + * (C) Copyright 1999 Roman Weissgaerber (weissg@vienna.at) + * + * The Root Hub is build into the HC (UHCI or OHCI) hardware. + * This piece of code lets it look like it resides on the usb + * like the other hubs. + * (for anyone who wants to do a control operation on the root hub) + * + * v2.1 1999/05/09 + * v2.0 1999/05/04 + * v1.0 1999/04/27 + * ohci-root-hub.c + * + */ + + + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/string.h> + +#include "usb.h" +#include "ohci-hcd.h" + +#ifdef VROOTHUB + +#include "ohci-root-hub.h" + + +static __u8 root_hub_dev_des[] = +{ + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x00, /* __u16 bcdUSB; v1.0 */ + 0x01, + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x00, /* __u8 bDeviceProtocol; */ + 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ + 0x00, /* __u16 idVendor; */ + 0x00, + 0x00, /* __u16 idProduct; */ + 0x00, + 0x00, /* __u16 bcdDevice; */ + 0x00, + 0x00, /* __u8 iManufacturer; */ + 0x00, /* __u8 iProduct; */ + 0x00, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + + +/* Configuration descriptor */ +static __u8 root_hub_config_des[] = +{ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x19, /* __u16 wTotalLength; */ + 0x00, + 0x01, /* __u8 bNumInterfaces; */ + 0x01, /* __u8 bConfigurationValue; */ + 0x00, /* __u8 iConfiguration; */ + 0x40, /* __u8 bmAttributes; + Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, 4..0: resvd */ + 0x00, /* __u8 MaxPower; */ + + /* interface */ + 0x09, /* __u8 if_bLength; */ + 0x04, /* __u8 if_bDescriptorType; Interface */ + 0x00, /* __u8 if_bInterfaceNumber; */ + 0x00, /* __u8 if_bAlternateSetting; */ + 0x01, /* __u8 if_bNumEndpoints; */ + 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* __u8 if_bInterfaceSubClass; */ + 0x00, /* __u8 if_bInterfaceProtocol; */ + 0x00, /* __u8 if_iInterface; */ + + /* endpoint */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + 0x40, /* __u16 ep_wMaxPacketSize; 64 Bytes */ + 0x00, + 0xff /* __u8 ep_bInterval; 255 ms */ +}; + +/* +For OHCI we need just the 2nd Byte, so we +don't need this constant byte-array + +static __u8 root_hub_hub_des[] = +{ + 0x00, * __u8 bLength; * + 0x29, * __u8 bDescriptorType; Hub-descriptor * + 0x02, * __u8 bNbrPorts; * + 0x00, * __u16 wHubCharacteristics; * + 0x00, + 0x01, * __u8 bPwrOn2pwrGood; 2ms * + 0x00, * __u8 bHubContrCurrent; 0 mA * + 0x00, * __u8 DeviceRemovable; *** 8 Ports max *** * + 0xff * __u8 PortPwrCtrlMask; *** 8 ports max *** * +}; +*/ + + +int root_hub_control_msg(struct ohci *ohci, int cmd_len, void *rh_cmd, void *rh_data, int leni, __OHCI_BAG lw0, __OHCI_BAG lw1, f_handler handler) +{ + + __u32 stat; + __u32 rep_handler; + int req_reply=0; + union ep_addr_ ep_addr; + union ep_addr_ ep_addr_ret; + __u8 * cmd = rh_cmd; + __u8 * data = rh_data; + int i; + int len =leni; + + __u8 bmRequestType = cmd[0]; + __u8 bRequest = cmd[1]; + __u16 wValue = cmd[3] << 8 | cmd [2]; + __u16 wIndex = cmd[5] << 8 | cmd [4]; + __u16 wLength = cmd[7] << 8 | cmd [6]; +printk("USB root hub: adr: %8x cmd(%8x): ", ohci->root_hub_funct_addr, 8); +for(i=0;i<8;i++) + printk("%2x", ((char *)rh_cmd)[i]); + +printk(" ; \n"); + + ep_addr_ret.iep = 0; + ep_addr_ret.bep.fa = ohci->root_hub_funct_addr; + ep_addr_ret.bep.ep = (bmRequestType & 0x80) | 0x40; + + switch (bmRequestType | bRequest << 8) { + /* Request Destination: + without flags: Device, + RH_INTERFACE: interface, + RH_ENDPOINT: endpoint, + RH_CLASS means HUB here, + RH_OTHER | RH_CLASS almost ever means HUB_PORT here + */ + + case RH_GET_STATUS: + len = 2; + data[0] = 0x01; + data[1] = 0x00; + req_reply = RH_ACK; + break; + case RH_GET_STATUS | RH_INTERFACE: + len = 2; + data[0] = 0x00; + data[1] = 0x00; + req_reply = RH_ACK; + break; + case RH_GET_STATUS | RH_ENDPOINT: + len = 2; + data[0] = 0x00; + data[1] = 0x00; + req_reply = RH_ACK; + break; + case RH_GET_STATUS | RH_CLASS: /* HUB_STATUS */ + stat = readl(&ohci->regs->roothub.status) & 0x7fff7fff; /* bit 31 u. 15 has other meaning */ + data[0] = stat & 0xff; + data[1] = (stat >> 8) & 0xff; + data[2] = (stat >> 16) & 0xff; + data[3] = (stat >> 24) & 0xff; + len = 4; + req_reply = RH_ACK; + break; + case RH_GET_STATUS | RH_OTHER | RH_CLASS: /* PORT_STATUS */ + stat = readl(&ohci->regs->roothub.portstatus[wIndex-1]); + data[0] = stat & 0xff; + data[1] = (stat >> 8) & 0xff; + data[2] = (stat >> 16) & 0xff; + data[3] = (stat >> 24) & 0xff; + len = 4; + req_reply = RH_ACK; + printk("rh: stat %4x wIndex %4x;\n", stat , wIndex); + break; + + case RH_CLEAR_FEATURE: + switch (wValue) { + case (RH_DEVICE_REMOTE_WAKEUP): + default: + } + break; + + case RH_CLEAR_FEATURE | RH_ENDPOINT: + switch (wValue) { + case (RH_ENDPOINT_STALL): + len=0; + req_reply = RH_ACK; + break; + default: + } + break; + + case RH_CLEAR_FEATURE | RH_CLASS: + switch (wValue) { + /* case (RH_C_HUB_LOCAL_POWER): OHCI says: no switching of this one */ + case (RH_C_HUB_OVER_CURRENT): + writel(RH_PS_OCIC, &ohci->regs->roothub.status); + len=0; + req_reply = RH_ACK; + break; + default: + } + break; + case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS: + switch (wValue) { + case (RH_PORT_ENABLE): + writel(RH_PS_CCS, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_PORT_SUSPEND): + writel(RH_PS_POCI, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_PORT_POWER): + writel(RH_PS_LSDA, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_C_PORT_CONNECTION): + writel(RH_PS_CSC, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_C_PORT_ENABLE): + writel(RH_PS_PESC, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_C_PORT_SUSPEND): + writel(RH_PS_PSSC, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_C_PORT_OVER_CURRENT): + writel(RH_PS_OCIC, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_C_PORT_RESET): + writel(RH_PS_PRSC, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + /* + case (RH_PORT_CONNECTION): + case (RH_PORT_OVER_CURRENT): + case (RH_PORT_RESET): + case (RH_PORT_LOW_SPEED): + */ + default: + } + break; + case RH_SET_FEATURE: + switch (wValue) { + case (RH_DEVICE_REMOTE_WAKEUP): + default: + } + break; + + case RH_SET_FEATURE | RH_ENDPOINT: + switch (wValue) { + case (RH_ENDPOINT_STALL): + default: + } + break; + + case RH_SET_FEATURE | RH_CLASS: + switch (wValue) { + /* case (RH_C_HUB_LOCAL_POWER): Root Hub has no local Power + case (RH_C_HUB_OVER_CURRENT): */ + default: + } + break; + case RH_SET_FEATURE | RH_OTHER | RH_CLASS: + switch (wValue) { + case (RH_PORT_SUSPEND): + writel(RH_PS_PSS, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_PORT_RESET): + if((readl(&ohci->regs->roothub.portstatus[wIndex-1]) &1) != 0) /* BUG IN HUP CODE *********/ + writel(RH_PS_PRS, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_PORT_POWER): + writel(RH_PS_PPS, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + case (RH_PORT_ENABLE): + writel(RH_PS_PES, &ohci->regs->roothub.portstatus[wIndex-1]); + len=0; + req_reply = RH_ACK; + break; + /* + case (RH_PORT_CONNECTION): + case (RH_PORT_OVER_CURRENT): + case (RH_PORT_LOW_SPEED): + case (RH_C_PORT_CONNECTION): + case (RH_C_PORT_ENABLE): + case (RH_C_PORT_SUSPEND): + case (RH_C_PORT_OVER_CURRENT): + case (RH_C_PORT_RESET): + */ + default: + } + break; + + case RH_SET_ADDRESS: + ohci->root_hub_funct_addr = wValue; + /* ohci->ed_func_ep0[wValue] = &ohci->ed_rh_ep0; + ohci->ed_func_ep0[wValue]->ep_addr.bep.fa = wValue; + ohci->ed_func_ep0[wValue]->ed_list = &ohci->ed_rh_epi; */ + ohci->ed_rh_epi.ed_list = NULL; + ohci->ed_rh_epi.ep_addr.bep.fa = wValue; + ohci->ed_rh_epi.ep_addr.bep.ep = 0xa1; /* Int in port 1 */ + ohci->ed_func_ep0[0]= NULL; + len = 0; + req_reply = RH_ACK; + break; + + case RH_GET_DESCRIPTOR: + switch ((wValue & 0xff00) >> 8) { + case (0x01): /* device descriptor */ + len = min(sizeof(root_hub_dev_des), wLength); + memcpy(data, root_hub_dev_des, len); + req_reply = RH_ACK; + break; + case (0x02): /* configuration descriptor */ + len = min(sizeof(root_hub_config_des), wLength); + memcpy(data, root_hub_config_des, len); + req_reply = RH_ACK; + break; + case (0x03): /* string descriptors */ + default: + } + break; + case RH_GET_DESCRIPTOR | RH_CLASS: + data[1] = 0x29; + stat = readl(&ohci->regs->roothub.a); + data[2] = stat & 0xff; /* number of ports */ + data[0] = (data[2] / 8) * 2 + 9; /* length of descriptor */ + if(data[0] > wLength) { + req_reply = RH_REQ_ERR; + break; + } + data[3] = (stat >> 8) & 0xff; + data[4] = (stat >> 16) & 0xff; + data[5] = (stat >> 24) & 0xff; + data[6] = 0; /* Root Hub needs no current from bus */ + stat = readl(&ohci->regs->roothub.b); + if(data[2] <= 8) { /* less than 8 Ports */ + data[7] = stat & 0xff; + data[8] = (stat >> 16) & 0xff; /* 0xff for USB Rev. 1.1 ?, stat >> 16 for USB Rev. 1.0 */ + } + else { + data[7] = stat & 0xff; + data[8] = (stat >> 8) & 0xff; + data[9] = (stat >> 16) & 0xff; /* 0xff for USB Rev. 1.1?, stat >> 16 for USB Rev. 1.0 */ + data[10] = (stat >> 24) & 0xff; /* 0xff for USB Rev. 1.1?, stat >> 24 for USB Rev. 1.0 */ + } + len = data[0]; + req_reply = RH_ACK; + break; + + case RH_SET_DESCRIPTOR: + break; + + case RH_GET_CONFIGURATION: + len = 1; + data[0] = 0x01; + req_reply = RH_ACK; + break; + + case RH_SET_CONFIGURATION: /* start it up */ + writel( 0x10000, &ohci->regs->roothub.status); + /*writel( OHCI_INTR_RHSC, &ohci->regs->intrenable);*/ + len = 0; + req_reply = RH_ACK; + break; + /* Optional or meaningless requests + case RH_GET_STATE | RH_OTHER | RH_CLASS: + case RH_GET_INTERFACE | RH_INTERFACE: + case RH_SET_INTERFACE | RH_INTERFACE: + case RH_SYNC_FRAME | RH_ENDPOINT: + */ + + /* Vendor Requests, we are the vendor! + Will the USB-Consortium give us a Vendor Id + for a virtual hub-device :-) ? + We could use these requests for configuration purposes on the HCD Driver, not used in the altenate usb !*/ + + case RH_SET_FEATURE | RH_VENDOR: /* remove all endpoints of device wIndex = Dev << 8 */ + switch(wValue) { + case RH_REMOVE_EP: + ep_addr.iep = 0; + ep_addr.bep.ep = wIndex & 0xff; + ep_addr.bep.fa = (wIndex << 8) & 0xff00; + usb_ohci_rm_function(ohci, ep_addr.iep); + len=0; + req_reply = RH_ACK; + break; + } + break; + case RH_SET_FEATURE | RH_ENDPOINT | RH_VENDOR: /* remove endpoint wIndex = Dev << 8 | EP */ + switch(wValue) { + case RH_REMOVE_EP: + ep_addr.iep = 0; + ep_addr.bep.ep = wIndex & 0xff; + ep_addr.bep.fa = (wIndex << 8) & 0xff00; + usb_ohci_rm_ep(ohci, ohci_find_ep(ohci, ep_addr.iep)); + len=0; + req_reply = RH_ACK; + break; + } + break; + case RH_SET_EP | RH_ENDPOINT | RH_VENDOR: + ep_addr.bep.ep = data[0]; + ep_addr.bep.fa = data[1]; + ep_addr.bep.hc = data[2]; + ep_addr.bep.host = data[3]; + rep_handler = data[7] << 24 |data[6] << 16 | data[5] << 8 | data[4]; + /* struct usb_ohci_ed *usb_ohci_add_ep(union ep_addr_ ep_addr, + struct ohci * ohci, int interval, int load, int (*handler)(int, void*), int ep_size, int speed) */ + usb_ohci_add_ep(ohci, ep_addr.iep, data[8], data[9], (f_handler) rep_handler, data[11] << 8 | data[10] , data[12]); + len=0; + req_reply = RH_ACK; + break; + + default: + } + printk("USB HC roothubstat1: %x \n", readl( &(ohci->regs->roothub.portstatus[0]) )); + printk("USB HC roothubstat2: %x \n", readl( &(ohci->regs->roothub.portstatus[1]) )); + + /* if (req_reply == RH_ACK) len; */ + queue_reply(ohci, ep_addr_ret.iep, 8, rh_cmd, data, len, lw0, lw1, handler); + return 0; +} + +int root_hub_int_req(struct ohci * ohci, int cmd_len, void * ctrl, void * data, int data_len, __OHCI_BAG lw0, __OHCI_BAG lw1, f_handler handler){ + + struct ohci_rep_td *td; + struct ohci_rep_td *tmp; + union ep_addr_ ep_addr; + + td = kmalloc(sizeof(td),GFP_KERNEL); + tmp = ohci->td_rh_epi; + td->next_td = NULL; + if(tmp == NULL) { /* queue td */ + ohci->td_rh_epi = td; + } + else { + while(tmp->next_td != NULL) tmp = tmp->next_td; + tmp->next_td = td; + } + ep_addr.iep = 0; + ep_addr.bep.fa = ohci->root_hub_funct_addr; + ep_addr.bep.ep = 0xA1; /* INT IN EP endpoint 1 */ + td->cmd_len = 0; + td->cmd = NULL; + td->data = data; + td->data_len = data_len; + td->handler = handler; + td->next_td = NULL; + td->ep_addr = ep_addr.iep; + td->lw0 = lw0; + td->lw1 = lw1; + ohci_init_rh_int_timer(ohci, 255); + return 0; +} + + +/* prepare Interrupt pipe transaction data; HUP INTERRUPT ENDPOINT */ +int root_hub_send_irq(struct ohci * ohci, void * rh_data, int rh_len ) { + + int num_ports; + int i; + int ret; + int len; + + __u8 * data = rh_data; + + num_ports = readl(&ohci->regs->roothub.a) & 0xff; + data[0] = (readl(&ohci->regs->roothub.status) & 0x00030000)>0?1:0; + ret = data[0]; + + for(i=0; i < num_ports; i++) { + data[i/8] |= ((readl(&ohci->regs->roothub.portstatus[i]) & 0x001f0000)>0?1:0) << ((i+1) % 8); + ret += data[i/8]; + } + len = i/8 + 1; + + if (ret > 0) return len; + + return RH_NACK; +} + + + + + +static struct timer_list rh_int_timer; + +/* Virtual Root Hub INTs are polled by this timer every "intervall" ms */ +static void rh_int_timer_do(unsigned long ptr) { + int len; + int interval; + struct ohci * ohci = (struct ohci *) ptr; + struct ohci_rep_td *td = ohci->td_rh_epi; + + if(td != NULL) { /* if ther is a TD handle the INT request */ + + len = root_hub_send_irq(ohci, td->data, td->data_len ); + if(len > 0) { + ohci->td_rh_epi = td->next_td; + td->next_td = ohci->repl_queue; + ohci->repl_queue = td; + send_replies(ohci); + } + } + interval = ohci->rh_int_interval; + init_timer(& rh_int_timer); + rh_int_timer.function = rh_int_timer_do; + rh_int_timer.data = (unsigned long) ohci; + rh_int_timer.expires = jiffies + (HZ * (interval<30?30: interval)) /1000; + add_timer(&rh_int_timer); +} + +/* Root Hub INTs are polled by this timer */ +int ohci_init_rh_int_timer(struct ohci * ohci, int interval) { + + if(!(ohci->rh_int_timer)) { + ohci->rh_int_timer = 1; + ohci->rh_int_interval = interval; + init_timer(& rh_int_timer); + rh_int_timer.function = rh_int_timer_do; + rh_int_timer.data = (unsigned long) ohci; + rh_int_timer.expires = jiffies + (HZ * (interval<30?30: interval)) /1000; + add_timer(&rh_int_timer); + } + return 0; +} + +int ohci_del_rh_int_timer(struct ohci * ohci) { + del_timer(&rh_int_timer); + return 0; +} +/* for root hub replies, queue the reply, (it will be sent immediately now) */ + +int queue_reply(struct ohci * ohci, unsigned int ep_addr, int cmd_len,void * cmd, void * data,int len, __OHCI_BAG lw0, __OHCI_BAG lw1, f_handler handler) { + + struct ohci_rep_td *td; + int status = 0; + +printk("queue_reply ep: %x len: %x\n", ep_addr, len); +td = kmalloc(sizeof(td), GFP_KERNEL); + + if (len < 0) { status = len; len = 0;} + td->cmd_len = cmd_len; + td->cmd = cmd; + td->data = data; + td->data_len = len; + td->handler = handler; + td->next_td = ohci->repl_queue; ohci->repl_queue = td; + td->ep_addr = ep_addr; + td->lw0 = lw0; + td->lw1 = lw1; + td->status = status; + send_replies(ohci); +return 0; +} + +/* for root hub replies; send the reply */ +int send_replies(struct ohci * ohci) { + struct ohci_rep_td *td; + struct ohci_rep_td *tmp; + + td = ohci->repl_queue; ohci->repl_queue = NULL; + while ( td != NULL) { + td->handler((void *) ohci, td->ep_addr,td->cmd_len,td->cmd, td->data, td->data_len, td->status, td->lw0, td->lw1); + tmp = td; + td = td->next_td; + + kfree(tmp); + } + return 0; +} + +#endif diff --git a/drivers/usb/ohci-root-hub.h b/drivers/usb/ohci-root-hub.h new file mode 100644 index 000000000..8f1e80998 --- /dev/null +++ b/drivers/usb/ohci-root-hub.h @@ -0,0 +1,71 @@ +/* + * HCD (OHCI) Virtual Root Hub Protocol for USB. + * + * (C) Copyright 1999 Roman Weissgaerber (weissg@vienna.at) + * + * The Root Hub is build into the HC (UHCI or OHCI) hardware. + * This piece of code lets it look like it resides on the bus + * like the other hubs. + * (for anyone who wants to do a control operation on the root hub) + * + * v1.0 1999/04/27 + * ohci-root-hub.h + * + */ + +/* destination of request */ +#define RH_INTERFACE 0x01 +#define RH_ENDPOINT 0x02 +#define RH_OTHER 0x03 + +#define RH_CLASS 0x20 +#define RH_VENDOR 0x40 + +/* Requests: bRequest << 8 | bmRequestType */ +#define RH_GET_STATUS 0x0080 +#define RH_CLEAR_FEATURE 0x0100 +#define RH_SET_FEATURE 0x0300 +#define RH_SET_ADDRESS 0x0500 +#define RH_GET_DESCRIPTOR 0x0680 +#define RH_SET_DESCRIPTOR 0x0700 +#define RH_GET_CONFIGURATION 0x0880 +#define RH_SET_CONFIGURATION 0x0900 +#define RH_GET_STATE 0x0280 +#define RH_GET_INTERFACE 0x0A80 +#define RH_SET_INTERFACE 0x0B00 +#define RH_SYNC_FRAME 0x0C80 +/* Our Vendor Specific Request */ +#define RH_SET_EP 0x2000 + + +/* Hub port features */ +#define RH_PORT_CONNECTION 0x00 +#define RH_PORT_ENABLE 0x01 +#define RH_PORT_SUSPEND 0x02 +#define RH_PORT_OVER_CURRENT 0x03 +#define RH_PORT_RESET 0x04 +#define RH_PORT_POWER 0x08 +#define RH_PORT_LOW_SPEED 0x09 +#define RH_C_PORT_CONNECTION 0x10 +#define RH_C_PORT_ENABLE 0x11 +#define RH_C_PORT_SUSPEND 0x12 +#define RH_C_PORT_OVER_CURRENT 0x13 +#define RH_C_PORT_RESET 0x14 + +/* Hub features */ +#define RH_C_HUB_LOCAL_POWER 0x00 +#define RH_C_HUB_OVER_CURRENT 0x01 + +#define RH_DEVICE_REMOTE_WAKEUP 0x00 +#define RH_ENDPOINT_STALL 0x01 + +/* Our Vendor Specific feature */ +#define RH_REMOVE_EP 0x00 + + +#define RH_ACK 0x01 +#define RH_REQ_ERR -1 +#define RH_NACK 0x00 + +#define min(a,b) (((a)<(b))?(a):(b)) + diff --git a/drivers/usb/ohci.c b/drivers/usb/ohci.c new file mode 100644 index 000000000..b12d0114c --- /dev/null +++ b/drivers/usb/ohci.c @@ -0,0 +1,1669 @@ +/* + * Open Host Controller Interface driver for USB. + * + * (C) Copyright 1999 Gregory P. Smith <greg@electricrain.com> + * + * This is the "other" host controller interface for USB. You will + * find this on many non-Intel based motherboards, and of course the + * Mac. As Linus hacked his UHCI driver together first, I modeled + * this after his.. (it should be obvious) + * + * From the programming standpoint the OHCI interface seems a little + * prettier and potentially less CPU intensive. This remains to be + * proven. In reality, I don't believe it'll make one darn bit of + * difference. USB v1.1 is a slow bus by today's standards. + * + * OHCI hardware takes care of most of the scheduling of different + * transfer types with the correct prioritization for us. + * + * To get started in USB, I used the "Universal Serial Bus System + * Architecture" book by Mindshare, Inc. It was a reasonable introduction + * and overview of USB and the two dominant host controller interfaces + * however you're better off just reading the real specs available + * from www.usb.org as you'll need them to get enough detailt to + * actually implement a HCD. The book has many typos and omissions + * Beware, the specs are the victim of a committee. + * + * This code was written with Guinness on the brain, xsnow on the desktop + * and Orbital, Orb, Enya & Massive Attack on the CD player. What a life! ;) + * + * No filesystems were harmed in the development of this code. + * + * $Id: ohci.c,v 1.26 1999/05/11 07:34:47 greg Exp $ + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> + +#include <asm/spinlock.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "ohci.h" +#include "inits.h" + +#ifdef CONFIG_APM +#include <linux/apm_bios.h> +static int handle_apm_event(apm_event_t event); +static int apm_resume = 0; +#endif + +static struct wait_queue *ohci_configure = NULL; + +#ifdef OHCI_TIMER +static struct timer_list ohci_timer; /* timer for root hub polling */ +#endif + + +static int ohci_td_result(struct ohci_device *dev, struct ohci_td *td) +{ + unsigned int status; + + status = td->info & OHCI_TD_CC; + + /* TODO Debugging code for TD failures goes here */ + + return status; +} /* ohci_td_result() */ + + +static spinlock_t ohci_edtd_lock = SPIN_LOCK_UNLOCKED; + +/* + * Add a TD to the end of the TD list on a given ED. If td->next_td + * points to any more TDs, they will be added as well (naturally). + * Otherwise td->next_td must be 0. + * + * The SKIP flag will be cleared after this function. + * + * Important! This function needs locking and atomicity as it works + * in parallel with the HC's DMA. Locking ohci_edtd_lock while using + * the function is a must. + * + * This function can be called by the interrupt handler. + */ +static void ohci_add_td_to_ed(struct ohci_td *td, struct ohci_ed *ed) +{ + /* don't let the HC pull anything from underneath us */ + ed->status |= OHCI_ED_SKIP; + + if (ed_head_td(ed) == 0) { /* empty list, put it on the head */ + set_ed_head_td(ed, virt_to_bus(td)); + ed->tail_td = 0; + } else { + struct ohci_td *tail, *head; + head = (ed_head_td(ed) == 0) ? NULL : bus_to_virt(ed_head_td(ed)); + tail = (ed->tail_td == 0) ? NULL : bus_to_virt(ed->tail_td); + if (!tail) { /* no tail, single element list */ + td->next_td = head->next_td; + head->next_td = virt_to_bus(td); + ed->tail_td = virt_to_bus(td); + } else { /* append to the list */ + td->next_td = tail->next_td; + tail->next_td = virt_to_bus(td); + ed->tail_td = virt_to_bus(td); + } + } + + /* save the ED link in each of the TDs added */ + td->ed = ed; + while (td->next_td != 0) { + td = bus_to_virt(td->next_td); + td->ed = ed; + } + + /* turn off the SKIP flag */ + ed->status &= ~OHCI_ED_SKIP; +} /* ohci_add_td_to_ed() */ + + +inline void ohci_start_control(struct ohci *ohci) +{ + /* tell the HC to start processing the control list */ + writel(OHCI_CMDSTAT_CLF, &ohci->regs->cmdstatus); +} + +inline void ohci_start_bulk(struct ohci *ohci) +{ + /* tell the HC to start processing the bulk list */ + writel(OHCI_CMDSTAT_BLF, &ohci->regs->cmdstatus); +} + +inline void ohci_start_periodic(struct ohci *ohci) +{ + /* enable processing periodc transfers starting next frame */ + writel_set(OHCI_USB_PLE, &ohci->regs->control); +} + +inline void ohci_start_isoc(struct ohci *ohci) +{ + /* enable processing isoc. transfers starting next frame */ + writel_set(OHCI_USB_IE, &ohci->regs->control); +} + +/* + * Add an ED to the hardware register ED list pointed to by hw_listhead_p + */ +static void ohci_add_ed_to_hw(struct ohci_ed *ed, void* hw_listhead_p) +{ + __u32 listhead; + unsigned long flags; + + spin_lock_irqsave(&ohci_edtd_lock, flags); + + listhead = readl(hw_listhead_p); + + /* if the list is not empty, insert this ED at the front */ + /* XXX should they go on the end? */ + if (listhead) { + ed->next_ed = listhead; + } + + /* update the hardware listhead pointer */ + writel(virt_to_bus(ed), hw_listhead_p); + + spin_unlock_irqrestore(&ohci_edtd_lock, flags); +} /* ohci_add_ed() */ + + +/* + * Put another control ED on the controller's list + */ +void ohci_add_control_ed(struct ohci *ohci, struct ohci_ed *ed) +{ + ohci_add_ed_to_hw(ed, &ohci->regs->ed_controlhead); + ohci_start_control(ohci); +} /* ohci_add_control_ed() */ + + +#if 0 +/* + * Put another control ED on the controller's list + */ +void ohci_add_periodic_ed(struct ohci *ohci, struct ohci_ed *ed, int period) +{ + ohci_add_ed_to_hw(ed, /* XXX */); + ohci_start_periodic(ohci); +} /* ohci_add_control_ed() */ +#endif + + +/* + * Remove an ED from the HC list whos bus headpointer is pointed to + * by hw_listhead_p + * + * Note that the SKIP bit is left on in the removed ED. + */ +void ohci_remove_ed_from_hw(struct ohci_ed *ed, __u32* hw_listhead_p) +{ + unsigned long flags; + struct ohci_ed *cur; + __u32 bus_ed = virt_to_bus(ed); + __u32 bus_cur; + + if (ed == NULL || !bus_ed) + return; + + /* tell the controller this skip ED */ + ed->status |= OHCI_ED_SKIP; + + bus_cur = readl(hw_listhead_p); + + if (bus_cur == 0) + return; /* the list is already empty */ + + cur = bus_to_virt(bus_cur); + + spin_lock_irqsave(&ohci_edtd_lock, flags); + + /* if its the head ED, move the head */ + if (bus_cur == bus_ed) { + writel(cur->next_ed, hw_listhead_p); + } else if (cur->next_ed != 0) { + struct ohci_ed *prev; + + /* walk the list and unlink the ED if found */ + for (;;) { + prev = cur; + cur = bus_to_virt(cur->next_ed); + + if (virt_to_bus(cur) == bus_ed) { + /* unlink from the list */ + prev->next_ed = cur->next_ed; + break; + } + + if (cur->next_ed == 0) + break; + } + } + + /* clear any links from the ED for safety */ + ed->next_ed = 0; + + spin_unlock_irqrestore(&ohci_edtd_lock, flags); +} /* ohci_remove_ed_from_hw() */ + +/* + * Remove an ED from the controller's control list. Note that the SKIP bit + * is left on in the removed ED. + */ +inline void ohci_remove_control_ed(struct ohci *ohci, struct ohci_ed *ed) +{ + ohci_remove_ed_from_hw(ed, &ohci->regs->ed_controlhead); +} + +/* + * Remove an ED from the controller's bulk list. Note that the SKIP bit + * is left on in the removed ED. + */ +inline void ohci_remove_bulk_ed(struct ohci *ohci, struct ohci_ed *ed) +{ + ohci_remove_ed_from_hw(ed, &ohci->regs->ed_bulkhead); +} + + +/* + * Remove a TD from the given EDs TD list. + */ +static void ohci_remove_td_from_ed(struct ohci_td *td, struct ohci_ed *ed) +{ + unsigned long flags; + struct ohci_td *head_td; + + if ((td == NULL) || (ed == NULL)) + return; + + spin_lock_irqsave(&ohci_edtd_lock, flags); + + if (ed_head_td(ed) == 0) + return; + + /* set the "skip me bit" in this ED */ + ed->status |= OHCI_ED_SKIP; + + /* XXX Assuming this list will never be circular */ + + head_td = bus_to_virt(ed_head_td(ed)); + if (virt_to_bus(td) == ed_head_td(ed)) { + /* It's the first TD, remove it. */ + set_ed_head_td(ed, head_td->next_td); + } else { + struct ohci_td *prev_td, *cur_td; + + /* FIXME: collapse this into a nice simple loop :) */ + if (head_td->next_td != 0) { + prev_td = head_td; + cur_td = bus_to_virt(head_td->next_td); + for (;;) { + if (td == cur_td) { + /* remove it */ + prev_td->next_td = cur_td->next_td; + break; + } + if (cur_td->next_td == 0) + break; + prev_td = cur_td; + cur_td = bus_to_virt(cur_td->next_td); + } + } + } + + td->next_td = 0; /* remove the TDs links */ + td->ed = NULL; + + /* TODO return this TD to the pool of free TDs */ + + /* unset the "skip me bit" in this ED */ + ed->status &= ~OHCI_ED_SKIP; + + spin_unlock_irqrestore(&ohci_edtd_lock, flags); +} /* ohci_remove_td_from_ed() */ + + +/* + * Get a pointer (virtual) to an available TD from the given device's + * pool. + * + * Return NULL if none are left. + */ +static struct ohci_td *ohci_get_free_td(struct ohci_device *dev) +{ + int idx; + + for (idx=0; idx < NUM_TDS; idx++) { + if (!td_allocated(dev->td[idx])) { + struct ohci_td *new_td = &dev->td[idx]; + /* zero out the TD */ + memset(new_td, 0, sizeof(*new_td)); + /* mark the new TDs as unaccessed */ + new_td->info = OHCI_TD_CC_NEW; + /* mark it as allocated */ + allocate_td(new_td); + return new_td; + } + } + + printk("usb-ohci error: unable to allocate a TD\n"); + return NULL; +} /* ohci_get_free_td() */ + + +/* + * Initialize a TD + * + * dir = OHCI_TD_D_IN, OHCI_TD_D_OUT, or OHCI_TD_D_SETUP + * toggle = TOGGLE_AUTO, TOGGLE_DATA0, TOGGLE_DATA1 + */ +inline struct ohci_td *ohci_fill_new_td(struct ohci_td *td, int dir, int toggle, __u32 flags, void *data, __u32 len, void *dev_id, usb_device_irq completed) +{ + /* hardware fields */ + td->info = OHCI_TD_CC_NEW | + (dir & OHCI_TD_D) | + (toggle & OHCI_TD_DT) | + flags; + td->cur_buf = (data == NULL) ? 0 : virt_to_bus(data); + td->buf_end = (len == 0) ? 0 : td->cur_buf + len - 1; + + /* driver fields */ + td->data = data; + td->dev_id = dev_id; + td->completed = completed; + + return td; +} /* ohci_fill_new_td() */ + + +/********************************** + * OHCI interrupt list operations * + **********************************/ + +/* + * Request an interrupt handler for one "pipe" of a USB device. + * (this function is pretty minimal right now) + * + * At the moment this is only good for input interrupts. (ie: for a + * mouse or keyboard) + * + * Period is desired polling interval in ms. The closest, shorter + * match will be used. Powers of two from 1-32 are supported by OHCI. + */ +static int ohci_request_irq(struct usb_device *usb, unsigned int pipe, + usb_device_irq handler, int period, void *dev_id) +{ + struct ohci_device *dev = usb_to_ohci(usb); + struct ohci_td *td; + struct ohci_ed *interrupt_ed; /* endpoint descriptor for this irq */ + + /* + * Pick a good frequency endpoint based on the requested period + */ + interrupt_ed = &dev->ohci->root_hub->ed[ms_to_ed_int(period)]; + + /* + * Set the max packet size, device speed, endpoint number, usb + * device number (function address), and type of TD. + * + * FIXME: Isochronous transfers need a pool of special 32 byte + * TDs (32 byte aligned) in order to be supported. + */ + interrupt_ed->status = \ + ed_set_maxpacket(usb_maxpacket(pipe)) | + ed_set_speed(usb_pipeslow(pipe)) | + usb_pipe_endpdev(pipe) | + OHCI_ED_F_NORM; + + td = ohci_get_free_td(dev); + /* FIXME: check for NULL */ + + /* Fill in the TD */ + ohci_fill_new_td(td, td_set_dir_out(usb_pipeout(pipe)), + TOGGLE_AUTO, + OHCI_TD_ROUND, + dev->data, DATA_BUF_LEN, + dev_id, handler); + /* + * TODO: be aware that OHCI won't advance out of the 4kb + * page cur_buf started in. It'll wrap around to the start + * of the page... annoying or useful? you decide. + * + * We should make sure dev->data doesn't cross a page... + */ + + /* FIXME: this just guarantees that its the end of the list */ + td->next_td = 0; + + /* Linus did this. see asm/system.h; scary concept... I don't + * know if its needed here or not but it won't hurt. */ + wmb(); + + /* + * Put the TD onto our ED + */ + { + unsigned long flags; + spin_lock_irqsave(&ohci_edtd_lock, flags); + ohci_add_td_to_ed(td, interrupt_ed); + spin_unlock_irqrestore(&ohci_edtd_lock, flags); + } + +#if 0 + /* Assimilate the new ED into the collective */ + /* + * When dynamic ED allocation is done, this call will be + * useful. For now, the correct ED already on the + * controller's proper periodic ED lists was chosen above. + */ + ohci_add_periodic_ed(dev->ohci, interrupt_ed, period); +#else + /* enable periodic (interrupt) transfers on the HC */ + ohci_start_periodic(dev->ohci); +#endif + + return 0; +} /* ohci_request_irq() */ + + +/* + * Control thread operations: + */ +static struct wait_queue *control_wakeup; + +/* + * This is the handler that gets called when a control transaction + * completes. + * + * This function is called from the interrupt handler. + */ +static int ohci_control_completed(int stats, void *buffer, void *dev_id) +{ + wake_up(&control_wakeup); + return 0; +} /* ohci_control_completed() */ + + +/* + * Send or receive a control message on a "pipe" + * + * The cmd parameter is a pointer to the 8 byte setup command to be + * sent. FIXME: This is a devrequest in usb.h. The function + * should be updated to accept a devrequest* instead of void*.. + * + * A control message contains: + * - The command itself + * - An optional data phase (if len > 0) + * - Status complete phase + */ +static int ohci_control_msg(struct usb_device *usb, unsigned int pipe, void *cmd, void *data, int len) +{ + struct ohci_device *dev = usb_to_ohci(usb); + /* + * ideally dev->ed should be linked into the root hub's + * control_ed list and used instead of just using it directly. + * This could present a problem as is with more than one + * device. (but who wants to use a keyboard AND a mouse + * anyways? ;) + */ + struct ohci_ed *control_ed = &dev->ohci->root_hub->ed[ED_CONTROL]; + struct ohci_td *setup_td, *data_td, *status_td; + struct wait_queue wait = { current, NULL }; + +#if 0 + printk(KERN_DEBUG "entering ohci_control_msg %p (ohci_dev: %p) pipe 0x%x, cmd %p, data %p, len %d\n", usb, dev, pipe, cmd, data, len); +#endif + + /* + * Set the max packet size, device speed, endpoint number, usb + * device number (function address), and type of TD. + * + */ + control_ed->status = \ + ed_set_maxpacket(usb_maxpacket(pipe)) | + ed_set_speed(usb_pipeslow(pipe)) | + usb_pipe_endpdev(pipe) | + OHCI_ED_F_NORM; + + /* + * Build the control TD + */ + + /* get a TD to send this control message with */ + setup_td = ohci_get_free_td(dev); + /* TODO check for NULL */ + + /* + * Set the not accessed condition code, allow odd sized data, + * and set the data transfer type to SETUP. Setup DATA always + * uses a DATA0 packet. + * + * The setup packet contains a devrequest (usb.h) which + * will always be 8 bytes long. FIXME: the cmd parameter + * should be a pointer to one of these instead of a void* !!! + */ + ohci_fill_new_td(setup_td, OHCI_TD_D_SETUP, TOGGLE_DATA0, + OHCI_TD_IOC_OFF, + cmd, 8, /* cmd is always 8 bytes long */ + NULL, NULL); + + /* allocate the next TD */ + data_td = ohci_get_free_td(dev); /* TODO check for NULL */ + + /* link to the next TD */ + setup_td->next_td = virt_to_bus(data_td); + + if (len > 0) { + + /* build the Control DATA TD, it starts with a DATA1. */ + ohci_fill_new_td(data_td, td_set_dir_out(usb_pipeout(pipe)), + TOGGLE_DATA1, + OHCI_TD_ROUND | OHCI_TD_IOC_OFF, + data, len, + NULL, NULL); + + /* + * XXX we should check that the data buffer doesn't + * cross a 4096 byte boundary. If so, it needs to be + * copied into a single 4096 byte aligned area for the + * OHCI's TD logic to see it all, or multiple TDs need + * to be made for each page. + * + * It's not likely a control transfer will run into + * this problem.. (famous last words) + */ + + status_td = ohci_get_free_td(dev); /* TODO check for NULL */ + data_td->next_td = virt_to_bus(status_td); + } else { + status_td = data_td; /* no data_td, use it for status */ + } + + /* The control status packet always uses a DATA1 */ + ohci_fill_new_td(status_td, + td_set_dir_in(usb_pipeout(pipe) | (len == 0)), + TOGGLE_DATA1, + 0, + NULL, 0, + NULL, ohci_control_completed); + status_td->next_td = 0; /* end of TDs */ + + /* + * Start the control transaction.. + */ + current->state = TASK_UNINTERRUPTIBLE; + add_wait_queue(&control_wakeup, &wait); + + /* + * Add the chain of 2-3 control TDs to the control ED's TD list + */ + { + unsigned long flags; + spin_lock_irqsave(&ohci_edtd_lock, flags); + ohci_add_td_to_ed(setup_td, control_ed); + spin_unlock_irqrestore(&ohci_edtd_lock, flags); + } + +#if 0 + /* complete transaction debugging output (before) */ + printk(KERN_DEBUG " Control ED %lx:\n", virt_to_bus(control_ed)); + show_ohci_ed(control_ed); + printk(KERN_DEBUG " Setup TD %lx:\n", virt_to_bus(setup_td)); + show_ohci_td(setup_td); + if (data_td != status_td) { + printk(KERN_DEBUG " Data TD %lx:\n", virt_to_bus(data_td)); + show_ohci_td(data_td); + } + printk(KERN_DEBUG " Status TD %lx:\n", virt_to_bus(status_td)); + show_ohci_td(status_td); +#endif + + /* Give the ED to the HC */ + ohci_add_control_ed(dev->ohci, control_ed); + + /* FIXME: + * this should really check to see that the transaction completed. + */ + schedule_timeout(HZ/10); + + remove_wait_queue(&control_wakeup, &wait); + +#if 0 + /* complete transaction debugging output (after) */ + printk(KERN_DEBUG " (after) Control ED:\n"); + show_ohci_ed(control_ed); + printk(KERN_DEBUG " (after) Setup TD:\n"); + show_ohci_td(setup_td); + if (data_td != status_td) { + printk(KERN_DEBUG " (after) Data TD:\n"); + show_ohci_td(data_td); + } + printk(KERN_DEBUG " (after) Status TD:\n"); + show_ohci_td(status_td); +#endif + + /* clean up incase it failed */ + /* XXX only do this if their ed pointer still points to control_ed + * incase they've been reclaimed and used by something else + * already. -greg */ + ohci_remove_td_from_ed(setup_td, control_ed); + ohci_remove_td_from_ed(data_td, control_ed); + ohci_remove_td_from_ed(status_td, control_ed); + + /* remove the control ED */ + ohci_remove_control_ed(dev->ohci, control_ed); + +#if 0 + printk(KERN_DEBUG "leaving ohci_control_msg\n"); +#endif + + return ohci_td_result(dev, status_td); +} /* ohci_control_msg() */ + + +/* + * Allocate a new USB device to be attached to an OHCI controller + */ +static struct usb_device *ohci_usb_allocate(struct usb_device *parent) +{ + struct usb_device *usb_dev; + struct ohci_device *dev; + + /* + * Allocate the generic USB device + */ + usb_dev = kmalloc(sizeof(*usb_dev), GFP_KERNEL); + if (!usb_dev) + return NULL; + + memset(usb_dev, 0, sizeof(*usb_dev)); + + /* + * Allocate an OHCI device (EDs and TDs for this device) + */ + dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + kfree(usb_dev); + return NULL; + } + + memset(dev, 0, sizeof(*dev)); + + /* + * Link them together + */ + usb_dev->hcpriv = dev; + dev->usb = usb_dev; + + /* + * Link the device to its parent (hub, etc..) if any. + */ + usb_dev->parent = parent; + + if (parent) { + usb_dev->bus = parent->bus; + dev->ohci = usb_to_ohci(parent)->ohci; + } + + return usb_dev; +} /* ohci_usb_allocate() */ + + +/* + * Free a usb device. + * + * TODO This function needs to take better care of the EDs and TDs, etc. + */ +static int ohci_usb_deallocate(struct usb_device *usb_dev) +{ + kfree(usb_to_ohci(usb_dev)); + kfree(usb_dev); + return 0; +} + + +/* + * functions for the generic USB driver + */ +struct usb_operations ohci_device_operations = { + ohci_usb_allocate, + ohci_usb_deallocate, + ohci_control_msg, + ohci_request_irq, +}; + + +/* + * Reset an OHCI controller. Returns >= 0 on success. + * + * Afterwards the HC will be in the "suspend" state which prevents you + * from writing to some registers. Bring it to the operational state + * ASAP. + */ +static int reset_hc(struct ohci *ohci) +{ + int timeout = 1000; /* prevent an infinite loop */ + +#if 0 + printk(KERN_DEBUG "usb-ohci: resetting HC %p\n", ohci); +#endif + + writel(~0x0, &ohci->regs->intrdisable); /* Disable HC interrupts */ + writel(1, &ohci->regs->cmdstatus); /* HC Reset */ + writel_mask(0x3f, &ohci->regs->control); /* move to UsbReset state */ + + while ((readl(&ohci->regs->cmdstatus) & OHCI_CMDSTAT_HCR) != 0) { + if (!--timeout) { + printk("usb-ohci: USB HC reset timed out!\n"); + return -1; + } + udelay(1); + } + + printk(KERN_DEBUG "usb-ohci: HC %p reset.\n", ohci); + + return 0; +} /* reset_hc() */ + + +/* + * Reset and start an OHCI controller. Returns >= 0 on success. + */ +static int start_hc(struct ohci *ohci) +{ + int ret = 0; + int fminterval; + + fminterval = readl(&ohci->regs->fminterval) & 0x3fff; +#if 0 + printk(KERN_DEBUG "entering start_hc %p\n", ohci); +#endif + + if (reset_hc(ohci) < 0) + return -1; + + /* restore registers cleared by the reset */ + writel(virt_to_bus(ohci->root_hub->hcca), &ohci->regs->hcca); + + /* + * XXX Should fminterval also be set here? + * The spec suggests 0x2edf [11,999]. (FIXME: make this a constant) + */ + fminterval |= (0x2edf << 16); + writel(fminterval, &ohci->regs->fminterval); + /* Start periodic transfers at 90% of fminterval (fmremaining + * counts down; this will put them in the first 10% of the + * frame). */ + writel((0x2edf*9)/10, &ohci->regs->periodicstart); + + /* + * FNO (frame number overflow) could be enabled... they + * occur every 32768 frames (every 32-33 seconds). This is + * useful for debugging and as a bus heartbeat. -greg + */ + /* Choose the interrupts we care about */ + writel( OHCI_INTR_MIE | /* OHCI_INTR_RHSC | */ + OHCI_INTR_WDH | OHCI_INTR_FNO, + &ohci->regs->intrenable); + + /* Enter the USB Operational state & start the frames a flowing.. */ + writel_set(OHCI_USB_OPER, &ohci->regs->control); + + /* Enable control lists */ + writel_set(OHCI_USB_IE | OHCI_USB_CLE | OHCI_USB_BLE, &ohci->regs->control); + + /* Turn on power to the root hub ports (thanks Roman!) */ + writel( OHCI_ROOT_LPSC, &ohci->regs->roothub.status ); + + printk("usb-ohci: host controller operational\n"); + + return ret; +} /* start_hc() */ + + +/* + * Reset a root hub port + */ +static void ohci_reset_port(struct ohci *ohci, unsigned int port) +{ + int status; + + /* Don't allow overflows. */ + if (port >= MAX_ROOT_PORTS) { + printk("usb-ohci: bad port #%d in ohci_reset_port\n", port); + port = MAX_ROOT_PORTS-1; + } + + writel(PORT_PRS, &ohci->regs->roothub.portstatus[port]); /* Reset */ + + /* + * Wait for the reset to complete. + */ + wait_ms(10); + + /* check port status to see that the reset completed */ + status = readl(&ohci->regs->roothub.portstatus[port]); + if (status & PORT_PRS) { + /* reset failed, try harder? */ + printk("usb-ohci: port %d reset failed, retrying\n", port); + writel(PORT_PRS, &ohci->regs->roothub.portstatus[port]); + wait_ms(50); + } + + /* TODO we might need to re-enable the port here or is that + * done elsewhere? */ + +} /* ohci_reset_port */ + + +/* + * This gets called if the connect status on the root hub changes. + */ +static void ohci_connect_change(struct ohci * ohci, int port) +{ + struct usb_device *usb_dev; + struct ohci_device *dev; + /* memory I/O address of the port status register */ + void *portaddr = &ohci->regs->roothub.portstatus[port]; + int portstatus; + + printk(KERN_DEBUG "ohci_connect_change(%p, %d)\n", ohci, port); + + /* + * Because of the status change we have to forget + * everything we think we know about the device + * on this root hub port. It may have changed. + */ + usb_disconnect(ohci->root_hub->usb->children + port); + + portstatus = readl(portaddr); + + /* disable the port if nothing is connected */ + if (!(portstatus & PORT_CCS)) { + writel(PORT_CCS, portaddr); + return; + } + + /* + * Allocate a device for the new thingy that's been attached + */ + usb_dev = ohci_usb_allocate(ohci->root_hub->usb); + dev = usb_dev->hcpriv; + + dev->ohci = ohci; + + usb_connect(dev->usb); + + /* link it into the bus's device tree */ + ohci->root_hub->usb->children[port] = usb_dev; + + wait_ms(200); /* wait for powerup; XXX is this needed? */ + ohci_reset_port(ohci, port); + + /* Get information on speed by using LSD */ + usb_dev->slow = readl(portaddr) & PORT_LSDA ? 1 : 0; + + /* + * Do generic USB device tree processing on the new device. + */ + usb_new_device(usb_dev); + +} /* ohci_connect_change() */ + + +/* + * This gets called when the root hub configuration + * has changed. Just go through each port, seeing if + * there is something interesting happening. + */ +static void ohci_check_configuration(struct ohci *ohci) +{ + struct ohci_regs *regs = ohci->regs; + int num = 0; + int maxport = readl(&ohci->regs->roothub) & 0xff; + +#if 1 + printk(KERN_DEBUG "entering ohci_check_configuration %p\n", ohci); +#endif + + do { + if (readl(®s->roothub.portstatus[num]) & PORT_CSC) { + /* reset the connect status change bit */ + writel(PORT_CSC, ®s->roothub.portstatus[num]); + /* check the port for a nifty device */ + ohci_connect_change(ohci, num); + } + } while (++num < maxport); + +#if 0 + printk(KERN_DEBUG "leaving ohci_check_configuration %p\n", ohci); +#endif +} /* ohci_check_configuration() */ + + + +/* + * Check root hub port status and wake the control thread up if + * anything has changed. + * + * This function is called from the interrupt handler. + */ +static void ohci_root_hub_events(struct ohci *ohci) +{ + if (waitqueue_active(&ohci_configure)) { + int num = 0; + int maxport = ohci->root_hub->usb->maxchild; + + do { + if (readl(&ohci->regs->roothub.portstatus[num]) & + PORT_CSC) { + if (waitqueue_active(&ohci_configure)) + wake_up(&ohci_configure); + return; + } + } while (++num < maxport); + } +} /* ohci_root_hub_events() */ + + +/* + * The done list is in reverse order; we need to process TDs in the + * order they were finished (FIFO). This function builds the FIFO + * list using the next_dl_td pointer. + * + * This function originally by Roman Weissgaerber (weissg@vienna.at) + * + * This function is called from the interrupt handler. + */ +static struct ohci_td * ohci_reverse_donelist(struct ohci * ohci) +{ + __u32 td_list_hc; + struct ohci_hcca *hcca = ohci->root_hub->hcca; + struct ohci_td *td_list = NULL; + struct ohci_td *td_rev = NULL; + + td_list_hc = hcca->donehead & 0xfffffff0; + hcca->donehead = 0; + + while(td_list_hc) { + td_list = (struct ohci_td *) bus_to_virt(td_list_hc); + td_list->next_dl_td = td_rev; + + td_rev = td_list; + td_list_hc = td_list->next_td & 0xfffffff0; + } + + return td_list; +} /* ohci_reverse_donelist() */ + + +/* + * Collect this interrupt's goodies off of the list of finished TDs + * that the OHCI controller is kind enough to setup for us. + * + * This function is called from the interrupt handler. + */ +static void ohci_reap_donelist(struct ohci *ohci) +{ + struct ohci_td *td; /* used for walking the list */ + + spin_lock(&ohci_edtd_lock); + + /* create the FIFO ordered donelist */ + td = ohci_reverse_donelist(ohci); + + while (td != NULL) { + struct ohci_td *next_td = td->next_dl_td; + + /* FIXME: munge td->info into a future standard status format */ + /* Check if TD should be re-queued */ + if ((td->completed != NULL) && + (td->completed(OHCI_TD_CC_GET(td->info), td->data, td->dev_id))) + { + /* Mark the TD as active again: + * Set the not accessed condition code + * FIXME: should this reset OHCI_TD_ERRCNT? + */ + td->info |= OHCI_TD_CC_NEW; + + /* point it back to the start of the data buffer */ + td->cur_buf = virt_to_bus(td->data); + + /* XXX disabled for debugging reasons right now.. */ + /* insert it back on its ED */ + ohci_add_td_to_ed(td, td->ed); + } else { + /* return it to the pool of free TDs */ + ohci_free_td(td); + } + + td = next_td; + } + + spin_unlock(&ohci_edtd_lock); +} /* ohci_reap_donelist() */ + + +#if 0 +static int in_int = 0; +#endif +/* + * Get annoyed at the controller for bothering us. + * This pretty much follows the OHCI v1.0a spec, section 5.3. + */ +static void ohci_interrupt(int irq, void *__ohci, struct pt_regs *r) +{ + struct ohci *ohci = __ohci; + struct ohci_regs *regs = ohci->regs; + struct ohci_hcca *hcca = ohci->root_hub->hcca; + __u32 status, context; + +#if 0 + /* for debugging to keep IRQs from running away. */ + if (in_int >= 2) + return; + ++in_int; + return; +#endif + + /* Save the status of the interrupts that are enabled */ + status = readl(®s->intrstatus); + status &= readl(®s->intrenable); + + + /* make context = the interrupt status bits that we care about */ + if (hcca->donehead != 0) { + context = OHCI_INTR_WDH; /* hcca donehead needs processing */ + if (hcca->donehead & 1) { + context |= status; /* other status change to check */ + } + } else { + context = status; + if (!context) { + /* TODO increment a useless interrupt counter here */ + return; + } + } + + /* Disable HC interrupts */ + writel(OHCI_INTR_MIE, ®s->intrdisable); + + /* Process the done list */ + if (context & OHCI_INTR_WDH) { + /* See which TD's completed.. */ + ohci_reap_donelist(ohci); + + /* reset the done queue and tell the controller */ + hcca->donehead = 0; + writel(OHCI_INTR_WDH, ®s->intrstatus); + + context &= ~OHCI_INTR_WDH; /* mark this as checked */ + } + + /* Process any root hub status changes */ + if (context & OHCI_INTR_RHSC) { + /* Wake the thread to process root hub events */ + if (waitqueue_active(&ohci_configure)) + wake_up(&ohci_configure); + + writel(OHCI_INTR_RHSC, ®s->intrstatus); + /* + * Don't unset RHSC in context; it should be disabled. + * The control thread will re-enable it after it has + * checked the root hub status. + */ + } else { + /* check the root hub status anyways. Some controllers + * might not generate the interrupt properly. (?) */ + ohci_root_hub_events(ohci); + } + + /* Check those "other" pesky bits */ + if (context & (OHCI_INTR_FNO)) { + writel(OHCI_INTR_FNO, ®s->intrstatus); + context &= ~OHCI_INTR_FNO; /* mark this as checked */ + } + if (context & OHCI_INTR_SO) { + writel(OHCI_INTR_SO, ®s->intrstatus); + context &= ~OHCI_INTR_SO; /* mark this as checked */ + } + if (context & OHCI_INTR_RD) { + writel(OHCI_INTR_RD, ®s->intrstatus); + context &= ~OHCI_INTR_RD; /* mark this as checked */ + } + if (context & OHCI_INTR_UE) { + /* FIXME: need to have the control thread reset the + * controller now and keep a count of unrecoverable + * errors. If there are too many, it should just shut + * the broken controller down entirely. */ + writel(OHCI_INTR_UE, ®s->intrstatus); + context &= ~OHCI_INTR_UE; /* mark this as checked */ + } + if (context & OHCI_INTR_OC) { + writel(OHCI_INTR_OC, ®s->intrstatus); + context &= ~OHCI_INTR_OC; /* mark this as checked */ + } + + /* Mask out any remaining unprocessed interrupts so we don't + * get any more of them. */ + if (context & ~OHCI_INTR_MIE) { + writel(context, ®s->intrdisable); + } + + /* Re-enable HC interrupts */ + writel(OHCI_INTR_MIE, ®s->intrenable); + +} /* ohci_interrupt() */ + + +/* + * Allocate the resources required for running an OHCI controller. + * Host controller interrupts must not be running while calling this + * function or the penguins will get angry. + * + * The mem_base parameter must be the usable -virtual- address of the + * host controller's memory mapped I/O registers. + */ +static struct ohci *alloc_ohci(void* mem_base) +{ + int i; + struct ohci *ohci; + struct usb_bus *bus; + struct ohci_device *dev; + struct usb_device *usb; + +#if 0 + printk(KERN_DEBUG "entering alloc_ohci %p\n", mem_base); +#endif + + ohci = kmalloc(sizeof(*ohci), GFP_KERNEL); + if (!ohci) + return NULL; + + memset(ohci, 0, sizeof(*ohci)); + + ohci->irq = -1; + ohci->regs = mem_base; + INIT_LIST_HEAD(&ohci->interrupt_list); + + bus = kmalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) + return NULL; + + memset(bus, 0, sizeof(*bus)); + + ohci->bus = bus; + bus->hcpriv = ohci; + bus->op = &ohci_device_operations; + + /* + * Allocate the USB device structure and root hub. + * + * Here we allocate our own root hub and TDs as well as the + * OHCI host controller communications area. The HCCA is just + * a nice pool of memory with pointers to endpoint descriptors + * for the different interrupts. + */ + usb = ohci_usb_allocate(NULL); + if (!usb) + return NULL; + + dev = ohci->root_hub = usb_to_ohci(usb); + + usb->bus = bus; + + /* Initialize the root hub */ + dev->ohci = ohci; /* link back to the controller */ + + /* + * Allocate the Host Controller Communications Area on a 256 + * byte boundary. XXX take the easy way out and just grab a + * page as that's guaranteed to have a nice boundary. + */ + dev->hcca = (struct ohci_hcca *) __get_free_page(GFP_KERNEL); + + /* Tell the controller where the HCCA is */ + writel(virt_to_bus(dev->hcca), &ohci->regs->hcca); + +#if 0 + printk(KERN_DEBUG "usb-ohci: HCCA allocated at %p (bus %p)\n", dev->hcca, (void*)virt_to_bus(dev->hcca)); +#endif + + /* Get the number of ports on the root hub */ + usb->maxchild = readl(&ohci->regs->roothub.a) & 0xff; + if (usb->maxchild > MAX_ROOT_PORTS) { + printk("usb-ohci: Limited to %d ports\n", MAX_ROOT_PORTS); + usb->maxchild = MAX_ROOT_PORTS; + } + if (usb->maxchild < 1) { + printk("usb-ohci: Less than one root hub port? Impossible!\n"); + usb->maxchild = 1; + } + printk("usb-ohci: %d root hub ports found\n", usb->maxchild); + + /* + * Initialize the ED polling "tree" (for simplicity's sake in + * this driver many nodes in the tree will be identical) + */ + dev->ed[ED_INT_32].next_ed = virt_to_bus(&dev->ed[ED_INT_16]); + dev->ed[ED_INT_16].next_ed = virt_to_bus(&dev->ed[ED_INT_8]); + dev->ed[ED_INT_8].next_ed = virt_to_bus(&dev->ed[ED_INT_4]); + dev->ed[ED_INT_4].next_ed = virt_to_bus(&dev->ed[ED_INT_2]); + dev->ed[ED_INT_2].next_ed = virt_to_bus(&dev->ed[ED_INT_1]); + + /* + * Initialize the polling table to call interrupts at the + * intended intervals. + */ + dev->hcca->int_table[0] = virt_to_bus(&dev->ed[ED_INT_32]); + for (i = 1; i < NUM_INTS; i++) { + if (i & 1) + dev->hcca->int_table[i] = + virt_to_bus(&dev->ed[ED_INT_16]); + else if (i & 2) + dev->hcca->int_table[i] = + virt_to_bus(&dev->ed[ED_INT_8]); + else if (i & 4) + dev->hcca->int_table[i] = + virt_to_bus(&dev->ed[ED_INT_4]); + else if (i & 8) + dev->hcca->int_table[i] = + virt_to_bus(&dev->ed[ED_INT_2]); + else if (i & 16) + dev->hcca->int_table[i] = + virt_to_bus(&dev->ed[ED_INT_1]); + } + + /* + * Tell the controller where the control and bulk lists are + * The lists start out empty. + */ + writel(0, &ohci->regs->ed_controlhead); + writel(0, &ohci->regs->ed_bulkhead); + /* + writel(virt_to_bus(&dev->ed[ED_CONTROL]), &ohci->regs->ed_controlhead); + writel(virt_to_bus(&dev->ed[ED_BULK]), &ohci->regs->ed_bulkhead); + */ + +#if 0 + printk(KERN_DEBUG "alloc_ohci(): controller\n"); + show_ohci_status(ohci); +#endif + +#if 0 + printk(KERN_DEBUG "leaving alloc_ohci %p\n", ohci); +#endif + + return ohci; +} /* alloc_ohci() */ + + +/* + * De-allocate all resoueces.. + */ +static void release_ohci(struct ohci *ohci) +{ + printk(KERN_DEBUG "entering release_ohci %p\n", ohci); + +#ifdef OHCI_TIMER + /* stop our timer */ + del_timer(&ohci_timer); +#endif + if (ohci->irq >= 0) { + free_irq(ohci->irq, ohci); + ohci->irq = -1; + } + + /* stop all OHCI interrupts */ + writel(~0x0, &ohci->regs->intrdisable); + + if (ohci->root_hub) { + /* ensure that HC is stopped before releasing the HCCA */ + writel(OHCI_USB_SUSPEND, &ohci->regs->control); + free_page((unsigned long) ohci->root_hub->hcca); + kfree(ohci->root_hub); + ohci->root_hub->hcca = NULL; + ohci->root_hub = NULL; + } + + /* unmap the IO address space */ + iounmap(ohci->regs); + + kfree(ohci); + + MOD_DEC_USE_COUNT; + + /* If the ohci itself were dynamic we'd free it here */ + + printk(KERN_DEBUG "usb-ohci: HC resources released.\n"); +} /* release_ohci() */ + + +/* + * USB OHCI control thread + */ +static int ohci_control_thread(void * __ohci) +{ + struct ohci *ohci = (struct ohci *)__ohci; + + /* + * I'm unfamiliar with the SMP kernel locking.. where should + * this be released and what does it do? -greg + */ + lock_kernel(); + + /* + * This thread doesn't need any user-level access, + * so get rid of all of our resources.. + */ + printk("ohci_control_thread code at %p\n", &ohci_control_thread); + exit_mm(current); + exit_files(current); + exit_fs(current); + + strcpy(current->comm, "ohci-control"); + + /* + * Damn the torpedoes, full speed ahead + */ + if (start_hc(ohci) < 0) { + printk("usb-ohci: failed to start the controller\n"); + release_ohci(ohci); + printk(KERN_DEBUG "leaving ohci_control_thread %p\n", __ohci); + return 0; + } + + for(;;) { + siginfo_t info; + int unsigned long signr; + + wait_ms(200); + + /* check the root hub configuration for changes. */ + ohci_check_configuration(ohci); + + /* re-enable root hub status change interrupts. */ +#if 0 + writel(OHCI_INTR_RHSC, &ohci->regs->intrenable); +#endif + + printk(KERN_DEBUG "ohci-control thread sleeping\n"); + interruptible_sleep_on(&ohci_configure); +#ifdef CONFIG_APM + if (apm_resume) { + apm_resume = 0; + if (start_hc(ohci) < 0) + break; + continue; + } +#endif + + /* + * If we were woken up by a signal, see if its useful, + * otherwise exit. + */ + if (signal_pending(current)) { + /* sending SIGUSR1 makes us print out some info */ + spin_lock_irq(¤t->sigmask_lock); + signr = dequeue_signal(¤t->blocked, &info); + spin_unlock_irq(¤t->sigmask_lock); + + if(signr == SIGUSR1) { + /* FIXME: have it do a full ed/td queue dump */ + printk(KERN_DEBUG "OHCI status dump:\n"); + show_ohci_status(ohci); + } else { + /* unknown signal, exit the thread */ + break; + } + } + } /* for (;;) */ + + reset_hc(ohci); + release_ohci(ohci); + + printk(KERN_DEBUG "leaving ohci_control_thread %p\n", __ohci); + + return 0; +} /* ohci_control_thread() */ + + +#ifdef CONFIG_APM +static int handle_apm_event(apm_event_t event) +{ + static int down = 0; + + switch (event) { + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + if (down) { + printk(KERN_DEBUG "usb-ohci: received extra suspend event\n"); + break; + } + down = 1; + break; + case APM_NORMAL_RESUME: + case APM_CRITICAL_RESUME: + if (!down) { + printk(KERN_DEBUG "usb-ohci: received bogus resume event\n"); + break; + } + down = 0; + if (waitqueue_active(&ohci_configure)) { + apm_resume = 1; + wake_up(&ohci_configure); + } + break; + } + return 0; +} /* handle_apm_event() */ +#endif + + +#ifdef OHCI_TIMER +/* + * Inspired by Iñaky's driver. This function is a timer routine that + * is called OHCI_TIMER_FREQ times per second. It polls the root hub + * for status changes as on my system things are acting a bit odd at + * the moment.. + */ +static void ohci_timer_func (unsigned long ohci_ptr) +{ + struct ohci *ohci = (struct ohci*)ohci_ptr; + + ohci_root_hub_events(ohci); + + /* press the snooze button... */ + mod_timer(&ohci_timer, jiffies + (OHCI_TIMER_FREQ*HZ)); +} /* ohci_timer_func() */ +#endif + + +/* + * Increment the module usage count, start the control thread and + * return success if the controller is good. + */ +static int found_ohci(int irq, void* mem_base) +{ + int retval; + struct ohci *ohci; + +#if 0 + printk(KERN_DEBUG "entering found_ohci %d %p\n", irq, mem_base); +#endif + + /* Allocate the running OHCI structures */ + ohci = alloc_ohci(mem_base); + if (!ohci) { + return -ENOMEM; + } + +#ifdef OHCI_TIMER + init_timer(&ohci_timer); + ohci_timer.expires = jiffies + (OHCI_TIMER_FREQ*HZ); + ohci_timer.data = (unsigned long)ohci; + ohci_timer.function = ohci_timer_func; +#endif + + retval = -EBUSY; + if (request_irq(irq, ohci_interrupt, SA_SHIRQ, "usb-ohci", ohci) == 0) { + int pid; + + ohci->irq = irq; + +#if 0 + printk(KERN_DEBUG "usb-ohci: starting ohci-control thread\n"); +#endif + + /* fork off the handler */ + pid = kernel_thread(ohci_control_thread, ohci, + CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + if (pid >= 0) { + return 0; + } + + retval = pid; + } else { + printk("usb-ohci: Couldn't allocate interrupt %d\n", irq); + } + release_ohci(ohci); + +#if 0 + printk(KERN_DEBUG "leaving found_ohci %d %p\n", irq, mem_base); +#endif + + return retval; +} /* found_ohci() */ + + +/* + * If this controller is for real, map the IO memory and proceed + */ +static int init_ohci(struct pci_dev *dev) +{ + unsigned long mem_base = dev->base_address[0]; + + /* If its OHCI, its memory */ + if (mem_base & PCI_BASE_ADDRESS_SPACE_IO) + return -ENODEV; + + /* Get the memory address and map it for IO */ + mem_base &= PCI_BASE_ADDRESS_MEM_MASK; + + /* no interrupt won't work... */ + if (dev->irq == 0) { + printk("usb-ohci: no irq assigned? check your BIOS settings.\n"); + return -ENODEV; + } + + /* + * FIXME ioremap_nocache isn't implemented on all CPUs (such + * as the Alpha) [?] What should I use instead... + * + * The iounmap() is done on in release_ohci. + */ + mem_base = (unsigned long) ioremap_nocache(mem_base, 4096); + + if (!mem_base) { + printk("Error mapping OHCI memory\n"); + return -EFAULT; + } + MOD_INC_USE_COUNT; + + if (found_ohci(dev->irq, (void *) mem_base) < 0) { + MOD_DEC_USE_COUNT; + return -1; + } + + return 0; +} /* init_ohci() */ + +#ifdef MODULE +/* + * Clean up when unloading the module + */ +void cleanup_module(void) +{ +#ifdef CONFIG_APM + apm_unregister_callback(&handle_apm_event); +#endif +#ifdef CONFIG_USB_MOUSE + usb_mouse_cleanup(); +#endif + printk("usb-ohci: module unloaded\n"); +} + +#define ohci_init init_module + +#endif + + +/* TODO this should be named following Linux convention and go in pci.h */ +#define PCI_CLASS_SERIAL_USB_OHCI ((PCI_CLASS_SERIAL_USB << 8) | 0x0010) + +/* + * Search the PCI bus for an OHCI USB controller and set it up + * + * If anyone wants multiple controllers this will need to be + * updated.. Right now, it just picks the first one it finds. + */ +int ohci_init(void) +{ + int retval; + struct pci_dev *dev = NULL; + /*u8 type;*/ + + if (sizeof(struct ohci_device) > 4096) { + printk("usb-ohci: struct ohci_device to large\n"); + return -ENODEV; + } + + printk("OHCI USB Driver loading\n"); + + retval = -ENODEV; + for (;;) { + /* Find an OHCI USB controller */ + dev = pci_find_class(PCI_CLASS_SERIAL_USB_OHCI, dev); + if (!dev) + break; + + /* Verify that its OpenHCI by checking for MMIO */ + /* pci_read_config_byte(dev, PCI_CLASS_PROG, &type); + if (!type) + continue; */ + + /* Ok, set it up */ + retval = init_ohci(dev); + if (retval < 0) + continue; + + /* TODO check module params here to determine what to load */ + +#ifdef CONFIG_USB_MOUSE + usb_mouse_init(); +#endif +#ifdef CONFIG_USB_KBD + usb_kbd_init(); +#endif + hub_init(); +#ifdef CONFIG_USB_AUDIO + usb_audio_init(); +#endif +#ifdef CONFIG_APM + apm_register_callback(&handle_apm_event); +#endif + + return 0; /* no error */ + } + return retval; +} /* ohci_init */ + +/* vim:sw=8 + */ diff --git a/drivers/usb/ohci.h b/drivers/usb/ohci.h new file mode 100644 index 000000000..8714cd2e8 --- /dev/null +++ b/drivers/usb/ohci.h @@ -0,0 +1,349 @@ +#ifndef __LINUX_OHCI_H +#define __LINUX_OHCI_H + +/* + * Open Host Controller Interface data structures and defines. + * + * (C) Copyright 1999 Gregory P. Smith <greg@electricrain.com> + * + * $Id: ohci.h,v 1.15 1999/05/09 23:25:49 greg Exp $ + */ + +#include <linux/list.h> +#include <asm/io.h> + +#include "usb.h" + +struct ohci_ed; + +/* + * Each TD must be aligned on a 16-byte boundary. From the OHCI v1.0 spec + * it does not state that TDs must be contiguious in memory (due to the + * use of the next_td field). This gives us extra room at the end of a + * TD for our own driver specific data. + * + * This structure's size must be a multiple of 16 bytes. ?? no way, I + * don't see why. Alignment should be all that matters. + */ +struct ohci_td { + /* OHCI Hardware fields */ + __u32 info; /* TD status & type flags */ + __u32 cur_buf; /* Current Buffer Pointer (bus address) */ + __u32 next_td; /* Next TD Pointer (bus address) */ + __u32 buf_end; /* Memory Buffer End Pointer (bus address) */ + + /* Driver specific fields */ + struct ohci_ed *ed; /* address of the ED this TD is on */ + struct ohci_td *next_dl_td; /* used during donelist processing */ + void *data; /* virt. address of the the buffer */ + usb_device_irq completed; /* Completion handler routine */ + int allocated; /* boolean: is this TD allocated? */ + + /* User or Device class driver specific fields */ + void *dev_id; /* user defined pointer passed to irq handler */ +} __attribute((aligned(16))); + +#define OHCI_TD_ROUND (1 << 18) /* buffer rounding bit */ +#define OHCI_TD_D (3 << 19) /* direction of xfer: */ +#define OHCI_TD_D_IN (2 << 19) +#define OHCI_TD_D_OUT (1 << 19) +#define OHCI_TD_D_SETUP (0 << 19) +#define td_set_dir_in(d) ((d) ? OHCI_TD_D_IN : OHCI_TD_D_OUT ) +#define td_set_dir_out(d) ((d) ? OHCI_TD_D_OUT : OHCI_TD_D_IN ) +#define OHCI_TD_IOC_DELAY (7 << 21) /* frame delay allowed before int. */ +#define OHCI_TD_IOC_OFF (OHCI_TD_IOC_DELAY) /* no interrupt on complete */ +#define OHCI_TD_DT (3 << 24) /* data toggle bits */ +#define TOGGLE_AUTO (0 << 24) /* automatic (from the ED) */ +#define TOGGLE_DATA0 (2 << 24) /* force Data0 */ +#define TOGGLE_DATA1 (3 << 24) /* force Data1 */ +#define td_force_toggle(b) (((b) | 2) << 24) +#define OHCI_TD_ERRCNT (3 << 26) /* error count */ +#define td_errorcount(td) (((td).info >> 26) & 3) +#define OHCI_TD_CC (0xf << 28) /* condition code */ +#define OHCI_TD_CC_GET(td_i) (((td_i) >> 28) & 0xf) +#define OHCI_TD_CC_NEW (OHCI_TD_CC) /* set this on all unaccessed TDs! */ +#define td_cc_notaccessed(td) (((td).info >> 29) == 7) +#define td_cc_accessed(td) (((td).info >> 29) != 7) +#define td_cc_noerror(td) ((((td).info) & OHCI_TD_CC) == 0) +#define td_active(td) (!td_cc_noerror((td)) && (td_errorcount((td)) < 3)) +#define td_done(td) (td_cc_noerror((td)) || (td_errorcount((td)) == 3)) + +#define td_allocated(td) ((td).allocated) +#define allocate_td(td) ((td)->allocated = 1) +#define ohci_free_td(td) ((td)->allocated = 0) + + +/* + * The endpoint descriptors also requires 16-byte alignment + */ +struct ohci_ed { + /* OHCI hardware fields */ + __u32 status; + __u32 tail_td; /* TD Queue tail pointer */ + __u32 _head_td; /* TD Queue head pointer, toggle carry & halted bits */ + __u32 next_ed; /* Next ED */ +} __attribute((aligned(16))); + +/* get the head_td */ +#define ed_head_td(ed) ((ed)->_head_td & 0xfffffff0) + +/* save the carry flag while setting the head_td */ +#define set_ed_head_td(ed, td) ((ed)->_head_td = (td) | ((ed)->_head_td & 3)) + +#define OHCI_ED_SKIP (1 << 14) +#define OHCI_ED_MPS (0x7ff << 16) +/* FIXME: should cap at the USB max packet size [0x4ff] */ +#define ed_set_maxpacket(s) (((s) << 16) & OHCI_ED_MPS) +#define OHCI_ED_F_NORM (0) +#define OHCI_ED_F_ISOC (1 << 15) +#define ed_set_type_isoc(i) ((i) ? OHCI_ED_F_ISOC : OHCI_ED_F_NORM) +#define OHCI_ED_S_LOW (1 << 13) +#define OHCI_ED_S_HIGH (0) +#define ed_set_speed(s) ((s) ? OHCI_ED_S_LOW : OHCI_ED_S_HIGH) +#define OHCI_ED_D (3 << 11) +#define OHCI_ED_D_IN (2 << 11) +#define OHCI_ED_D_OUT (1 << 11) +#define ed_set_dir_in(d) ((d) ? OHCI_ED_D_IN : OHCI_ED_D_OUT) +#define ed_set_dir_out(d) ((d) ? OHCI_ED_D_OUT : OHCI_ED_D_IN) +#define OHCI_ED_EN (0xf << 7) +#define OHCI_ED_FA (0x7f) + + +/* NOTE: bits 27-31 of the status dword are reserved for the driver */ +/* + * We'll use this status flag for to mark if an ED is in use by the + * driver or not. If the bit is set, it is used. + * + * FIXME: implement this! + */ +#define ED_USED (1 << 31) + +/* + * The HCCA (Host Controller Communications Area) is a 256 byte + * structure defined in the OHCI spec. that the host controller is + * told the base address of. It must be 256-byte aligned. + */ +#define NUM_INTS 32 /* part of the OHCI standard */ +struct ohci_hcca { + __u32 int_table[NUM_INTS]; /* Interrupt ED table */ + __u16 frame_no; /* current frame number */ + __u16 pad1; /* set to 0 on each frame_no change */ + __u32 donehead; /* info returned for an interrupt */ + u8 reserved_for_hc[116]; +} __attribute((aligned(256))); + +/* + * The TD entries here are pre-allocated as Linus did with his simple + * UHCI implementation. With the current state of this driver that + * shouldn't be a problem. However if someone ever connects 127 + * supported devices to this driver and tries to use them all at once: + * a) They're insane! + * b) They should code in dynamic allocation + */ +struct ohci; + +/* + * Warning: These constants must not be so large as to cause the + * ohci_device structure to exceed one 4096 byte page. Or "weird + * things will happen" as the alloc_ohci() function assumes that + * its less than one page. (FIXME) + */ +#define NUM_TDS 32 /* num of preallocated transfer descriptors */ +#define DATA_BUF_LEN 16 /* num of unsigned long's for the data buf */ + +/* + * For this "simple" driver we only support a single ED for each + * polling rate. + * + * Later on this driver should be extended to use a full tree of EDs + * so that there can be 32 different 32ms polling frames, etc. + * Individual devices shouldn't need as many as the root hub in that + * case; complicating how things are done at the moment. + * + * Bulk and Control transfers hang off of their own ED lists. + */ +#define NUM_EDS 16 /* num of preallocated endpoint descriptors */ + +#define ohci_to_usb(ohci) ((ohci)->usb) +#define usb_to_ohci(usb) ((struct ohci_device *)(usb)->hcpriv) + +/* The usb_device must be first! */ +struct ohci_device { + struct usb_device *usb; + + struct ohci *ohci; + struct ohci_hcca *hcca; /* OHCI mem. mapped IO area */ + + struct ohci_ed ed[NUM_EDS]; /* Endpoint Descriptors */ + struct ohci_td td[NUM_TDS]; /* Transfer Descriptors */ + + unsigned long data[DATA_BUF_LEN]; +}; + +/* .... */ + +#define ED_INT_1 0 +#define ED_INT_2 1 +#define ED_INT_4 2 +#define ED_INT_8 3 +#define ED_INT_16 4 +#define ED_INT_32 5 +#define ED_CONTROL 6 +#define ED_BULK 7 +#define ED_ISO ED_INT_1 /* same as 1ms interrupt queue */ +#define ED_FIRST_AVAIL 8 /* first non-reserved ED */ + +/* + * Given a period p in ms, convert it to the closest endpoint + * interrupt frequency; rounding down. This is a gross macro. + * Feel free to toss it for actual code. (gasp!) + */ +#define ms_to_ed_int(p) \ + ((p >= 32) ? ED_INT_32 : \ + ((p & 16) ? ED_INT_16 : \ + ((p & 8) ? ED_INT_8 : \ + ((p & 4) ? ED_INT_4 : \ + ((p & 2) ? ED_INT_2 : \ + ED_INT_1))))) /* hmm.. scheme or lisp anyone? */ + +/* + * This is the maximum number of root hub ports. I don't think we'll + * ever see more than two as that's the space available on an ATX + * motherboard's case, but it could happen. The OHCI spec allows for + * up to 15... (which is insane!) + * + * Although I suppose several "ports" could be connected directly to + * internal laptop devices such as a keyboard, mouse, camera and + * serial/parallel ports. hmm... That'd be neat. + */ +#define MAX_ROOT_PORTS 15 /* maximum OHCI root hub ports */ + +/* + * This is the structure of the OHCI controller's memory mapped I/O + * region. This is Memory Mapped I/O. You must use the readl() and + * writel() macros defined in asm/io.h to access these!! + */ +struct ohci_regs { + /* control and status registers */ + __u32 revision; + __u32 control; + __u32 cmdstatus; + __u32 intrstatus; + __u32 intrenable; + __u32 intrdisable; + /* memory pointers */ + __u32 hcca; + __u32 ed_periodcurrent; + __u32 ed_controlhead; + __u32 ed_controlcurrent; + __u32 ed_bulkhead; + __u32 ed_bulkcurrent; + __u32 current_donehead; /* The driver should get this from the HCCA */ + /* frame counters */ + __u32 fminterval; + __u32 fmremaining; + __u32 fmnumber; + __u32 periodicstart; + __u32 lsthresh; + /* Root hub ports */ + struct ohci_roothub_regs { + __u32 a; + __u32 b; + __u32 status; + __u32 portstatus[MAX_ROOT_PORTS]; + } roothub; +} __attribute((aligned(32))); + +/* + * Read a MMIO register and re-write it after ANDing with (m) + */ +#define writel_mask(m, a) writel( (readl((__u32)(a))) & (__u32)(m), (__u32)(a) ) + +/* + * Read a MMIO register and re-write it after ORing with (b) + */ +#define writel_set(b, a) writel( (readl((__u32)(a))) | (__u32)(b), (__u32)(a) ) + + +#define PORT_CCS (1) /* port current connect status */ +#define PORT_PES (1 << 1) /* port enable status */ +#define PORT_PSS (1 << 2) /* port suspend status */ +#define PORT_POCI (1 << 3) /* port overcurrent indicator */ +#define PORT_PRS (1 << 4) /* port reset status */ +#define PORT_PPS (1 << 8) /* port power status */ +#define PORT_LSDA (1 << 9) /* port low speed dev. attached */ +#define PORT_CSC (1 << 16) /* port connect status change */ +#define PORT_PESC (1 << 17) /* port enable status change */ +#define PORT_PSSC (1 << 18) /* port suspend status change */ +#define PORT_OCIC (1 << 19) /* port over current indicator chg */ +#define PORT_PRSC (1 << 20) /* port reset status change */ + +/* + * Root Hub status register masks + */ +#define OHCI_ROOT_LPS (1) /* turn off root hub ports power */ +#define OHCI_ROOT_OCI (1 << 1) /* Overcurrent Indicator */ +#define OHCI_ROOT_DRWE (1 << 15) /* Device remote wakeup enable */ +#define OHCI_ROOT_LPSC (1 << 16) /* turn on root hub ports power */ +#define OHCI_ROOT_OCIC (1 << 17) /* Overcurrent indicator change */ +#define OHCI_ROOT_CRWE (1 << 31) /* Clear RemoteWakeupEnable */ + +/* + * Interrupt register masks + */ +#define OHCI_INTR_SO (1) +#define OHCI_INTR_WDH (1 << 1) +#define OHCI_INTR_SF (1 << 2) +#define OHCI_INTR_RD (1 << 3) +#define OHCI_INTR_UE (1 << 4) +#define OHCI_INTR_FNO (1 << 5) +#define OHCI_INTR_RHSC (1 << 6) +#define OHCI_INTR_OC (1 << 30) +#define OHCI_INTR_MIE (1 << 31) + +/* + * Control register masks + */ +#define OHCI_USB_OPER (2 << 6) +#define OHCI_USB_SUSPEND (3 << 6) +#define OHCI_USB_PLE (1 << 2) /* Periodic (interrupt) list enable */ +#define OHCI_USB_IE (1 << 3) /* Isochronous list enable */ +#define OHCI_USB_CLE (1 << 4) /* Control list enable */ +#define OHCI_USB_BLE (1 << 5) /* Bulk list enable */ + +/* + * Command status register masks + */ +#define OHCI_CMDSTAT_HCR (1) +#define OHCI_CMDSTAT_CLF (1 << 1) +#define OHCI_CMDSTAT_BLF (1 << 2) +#define OHCI_CMDSTAT_OCR (1 << 3) +#define OHCI_CMDSTAT_SOC (3 << 16) + +/* + * This is the full ohci controller description + * + * Note how the "proper" USB information is just + * a subset of what the full implementation needs. (Linus) + */ +struct ohci { + int irq; + struct ohci_regs *regs; /* OHCI controller's memory */ + struct usb_bus *bus; + struct ohci_device *root_hub; /* Root hub & controller */ + struct list_head interrupt_list; /* List of interrupt active TDs for this OHCI */ +}; + +#define OHCI_TIMER +#define OHCI_TIMER_FREQ (1) /* frequency of OHCI status checks */ + +/* Debugging code */ +void show_ohci_ed(struct ohci_ed *ed); +void show_ohci_td(struct ohci_td *td); +void show_ohci_status(struct ohci *ohci); +void show_ohci_device(struct ohci_device *dev); +void show_ohci_hcca(struct ohci_hcca *hcca); + +#endif +/* vim:sw=8 + */ diff --git a/drivers/usb/restart b/drivers/usb/restart new file mode 100644 index 000000000..98ca05367 --- /dev/null +++ b/drivers/usb/restart @@ -0,0 +1,38 @@ +#!/bin/sh + +ME=`basename $0` + +#UMOD=`lsmod | grep '^bp-mouse' | grep -v grep` +#if test "$UMOD"; then +# echo "$ME: removing bp-mouse.o" +# if ! rmmod bp-mouse; then +# echo "$ME: cannot remove bp-mouse.o" +# exit 1 +# fi +#fi + +UPID=`ps aux | grep ohci-control | grep -v grep | awk '{print $2}'` +if test "$UPID"; then + echo "$ME: killing $UPID" + kill $UPID +fi + +UMOD=`lsmod | grep '^usb-ohci' | grep -v grep` +if test "$UMOD"; then + echo "$ME: removing usb-ohci.o" + sleep 1 + if ! rmmod usb-ohci; then + echo "$ME: cannot remove usb-ohci.o" + exit 1 + fi +fi + +dmesg -c > /dev/null + +echo "$ME: starting usb-ohci.o" +insmod -m usb-ohci.o > usb-ohci.map + +sleep 1 +UPID=`ps aux | grep ohci-control | grep -v grep | awk '{print $2}'` +if test "$UPID"; then echo "$ME: ohci-control is pid $UPID" ; fi + diff --git a/drivers/usb/stopusb b/drivers/usb/stopusb new file mode 100644 index 000000000..1dc46980b --- /dev/null +++ b/drivers/usb/stopusb @@ -0,0 +1,7 @@ +#!/bin/sh + +killall ohci-control + +sleep 2 + +rmmod usb-ohci diff --git a/drivers/usb/uhci-debug.c b/drivers/usb/uhci-debug.c new file mode 100644 index 000000000..fd2aba6da --- /dev/null +++ b/drivers/usb/uhci-debug.c @@ -0,0 +1,168 @@ +/* + * UHCI-specific debugging code. Invaluable when something + * goes wrong, but don't get in my face. + * + * (C) Copyright 1999 Linus Torvalds + */ + +#include <linux/kernel.h> +#include <asm/io.h> + +#include "uhci.h" + +void show_td(struct uhci_td * td) +{ + printk("%08x ", td->link); + printk("%se%d %s%s%s%s%s%s%s%s%s%sLength=%x ", + ((td->status >> 29) & 1) ? "SPD " : "", + ((td->status >> 27) & 3), + ((td->status >> 26) & 1) ? "LS " : "", + ((td->status >> 25) & 1) ? "IOS " : "", + ((td->status >> 24) & 1) ? "IOC " : "", + ((td->status >> 23) & 1) ? "Active " : "", + ((td->status >> 22) & 1) ? "Stalled " : "", + ((td->status >> 21) & 1) ? "DataBufErr " : "", + ((td->status >> 20) & 1) ? "Babble " : "", + ((td->status >> 19) & 1) ? "NAK " : "", + ((td->status >> 18) & 1) ? "CRC/Timeo " : "", + ((td->status >> 17) & 1) ? "BitStuff " : "", + td->status & 0x7ff); + printk("MaxLen=%x %sEndPt=%x Dev=%x, PID=%x ", + td->info >> 21, + ((td->info >> 19) & 1) ? "DT " : "", + (td->info >> 15) & 15, + (td->info >> 8) & 127, + td->info & 0xff); + printk("(buf=%08x)\n", td->buffer); +} + +static void show_sc(int port, unsigned short status) +{ + printk(" stat%d = %04x %s%s%s%s%s%s%s%s\n", + port, + status, + (status & (1 << 12)) ? " PortSuspend" : "", + (status & (1 << 9)) ? " PortReset" : "", + (status & (1 << 8)) ? " LowSpeed" : "", + (status & 0x40) ? " ResumeDetect" : "", + (status & 0x08) ? " EnableChange" : "", + (status & 0x04) ? " PortEnabled" : "", + (status & 0x02) ? " ConnectChange" : "", + (status & 0x01) ? " PortConnected" : ""); +} + +void show_status(struct uhci *uhci) +{ + unsigned int io_addr = uhci->io_addr; + unsigned short usbcmd, usbstat, usbint, usbfrnum; + unsigned int flbaseadd; + unsigned char sof; + unsigned short portsc1, portsc2; + + usbcmd = inw(io_addr + 0); + usbstat = inw(io_addr + 2); + usbint = inw(io_addr + 4); + usbfrnum = inw(io_addr + 6); + flbaseadd = inl(io_addr + 8); + sof = inb(io_addr + 12); + portsc1 = inw(io_addr + 16); + portsc2 = inw(io_addr + 18); + + printk(" usbcmd = %04x %s%s%s%s%s%s%s%s\n", + usbcmd, + (usbcmd & 0x80) ? " Maxp64" : " Maxp32", + (usbcmd & 0x40) ? " CF" : "", + (usbcmd & 0x20) ? " SWDBG" : "", + (usbcmd & 0x10) ? " FGR" : "", + (usbcmd & 0x08) ? " EGSM" : "", + (usbcmd & 0x04) ? " GRESET" : "", + (usbcmd & 0x02) ? " HCRESET" : "", + (usbcmd & 0x01) ? " RS" : ""); + + printk(" usbstat = %04x %s%s%s%s%s%s\n", + usbstat, + (usbstat & 0x20) ? " HCHalted" : "", + (usbstat & 0x10) ? " HostControllerProcessError" : "", + (usbstat & 0x08) ? " HostSystemError" : "", + (usbstat & 0x04) ? " ResumeDetect" : "", + (usbstat & 0x02) ? " USBError" : "", + (usbstat & 0x01) ? " USBINT" : ""); + + printk(" usbint = %04x\n", usbint); + printk(" usbfrnum = (%d)%03x\n", (usbfrnum >> 10) & 1, 0xfff & (4*(unsigned int)usbfrnum)); + printk(" flbaseadd = %08x\n", flbaseadd); + printk(" sof = %02x\n", sof); + show_sc(1, portsc1); + show_sc(2, portsc2); +} + +#define uhci_link_to_qh(x) ((struct uhci_qh *) uhci_link_to_td(x)) + +struct uhci_td * uhci_link_to_td(unsigned int link) +{ + if (link & 1) + return NULL; + + return bus_to_virt(link & ~15); +} + +void show_queue(struct uhci_qh *qh) +{ + struct uhci_td *td; + int i = 0; + +#if 0 + printk(" link = %p, element = %p\n", qh->link, qh->element); +#endif + if(!qh->element) { + printk(" td 0 = NULL\n"); + return; + } + + for(td = uhci_link_to_td(qh->element); td; + td = uhci_link_to_td(td->link)) { + printk(" td %d = %p\n", i++, td); + printk(" "); + show_td(td); + } +} + +int is_skeleton_qh(struct uhci *uhci, struct uhci_qh *qh) +{ + int j; + + for (j = 0; j < UHCI_MAXQH; j++) + if (qh == uhci->root_hub->qh + j) + return 1; + + return 0; +} + +static const char *qh_names[] = {"isochronous", "interrupt2", "interrupt4", + "interrupt8", "interrupt16", "interrupt32", + "interrupt64", "interrupt128", "interrupt256", + "control", "bulk0", "bulk1", "bulk2", "bulk3", + "unused", "unused"}; + +void show_queues(struct uhci *uhci) +{ + int i; + struct uhci_qh *qh; + + for (i = 0; i < UHCI_MAXQH; ++i) { + printk(" %s:\n", qh_names[i]); +#if 0 + printk(" qh #%d, %p\n", i, virt_to_bus(uhci->root_hub->qh + i)); + show_queue(uhci->root_hub->qh + i); +#endif + + qh = uhci_link_to_qh(uhci->root_hub->qh[i].link); + for (; qh; qh = uhci_link_to_qh(qh->link)) { + if (is_skeleton_qh(uhci, qh)) + break; + + show_queue(qh); + } + } +} + diff --git a/drivers/usb/uhci.c b/drivers/usb/uhci.c new file mode 100644 index 000000000..3d8ccdb7d --- /dev/null +++ b/drivers/usb/uhci.c @@ -0,0 +1,1213 @@ +/* + * Universal Host Controller Interface driver for USB. + * + * (C) Copyright 1999 Linus Torvalds + * + * Intel documents this fairly well, and as far as I know there + * are no royalties or anything like that, but even so there are + * people who decided that they want to do the same thing in a + * completely different way. + * + * Oh, well. The intel version is the more common by far. As such, + * that's the one I care about right now. + * + * WARNING! The USB documentation is downright evil. Most of it + * is just crap, written by a committee. You're better off ignoring + * most of it, the important stuff is: + * - the low-level protocol (fairly simple but lots of small details) + * - working around the horridness of the rest + */ + +/* 4/4/1999 added data toggle for interrupt pipes -keryan */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> + +#include <asm/spinlock.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "uhci.h" +#include "inits.h" + +#ifdef CONFIG_APM +#include <linux/apm_bios.h> +static int handle_apm_event(apm_event_t event); +static int apm_resume = 0; +#endif + +#define compile_assert(x) do { switch (0) { case 1: case !(x): } } while (0) + +static struct wait_queue *uhci_configure = NULL; + +/* + * Return the result of a TD.. + */ +static int uhci_td_result(struct uhci_device *dev, struct uhci_td *td) +{ + unsigned int status; + + status = (td->status >> 16) & 0xff; + + /* Some debugging code */ + if (status) { + int i = 10; + struct uhci_td *tmp = dev->control_td; + printk("uhci_td_result() failed with status %d\n", status); + show_status(dev->uhci); + do { + show_td(tmp); + tmp++; + if (!--i) + break; + } while (tmp <= td); + } + return status; +} + +/* + * Inserts a td into qh list at the top. + * + * Careful about atomicity: even on UP this + * requires a locked access due to the concurrent + * DMA engine. + * + * NOTE! This assumes that first->last is a valid + * list of TD's with the proper backpointers set + * up and all.. + */ +static void uhci_insert_tds_in_qh(struct uhci_qh *qh, struct uhci_td *first, struct uhci_td *last) +{ + unsigned int link = qh->element; + unsigned int new = 4 | virt_to_bus(first); + + for (;;) { + unsigned char success; + + last->link = link; + first->backptr = &qh->element; + asm volatile("lock ; cmpxchg %4,%2 ; sete %0" + :"=q" (success), "=a" (link) + :"m" (qh->element), "1" (link), "r" (new) + :"memory"); + if (success) { + /* Was there a successor entry? Fix it's backpointer.. */ + if ((link & 1) == 0) { + struct uhci_td *next = bus_to_virt(link & ~15); + next->backptr = &last->link; + } + break; + } + } +} + +static inline void uhci_insert_td_in_qh(struct uhci_qh *qh, struct uhci_td *td) +{ + uhci_insert_tds_in_qh(qh, td, td); +} + +static void uhci_insert_qh(struct uhci_qh *qh, struct uhci_qh *newqh) +{ + newqh->link = qh->link; + qh->link = virt_to_bus(newqh) | 2; +} + +static void uhci_remove_qh(struct uhci_qh *qh, struct uhci_qh *remqh) +{ + unsigned int remphys = virt_to_bus(remqh); + struct uhci_qh *lqh = qh; + + while ((lqh->link & ~0xF) != remphys) { + if (lqh->link & 1) + break; + + lqh = bus_to_virt(lqh->link & ~0xF); + } + + if (lqh->link & 1) { + printk("couldn't find qh in chain!\n"); + return; + } + + lqh->link = remqh->link; +} + +/* + * Removes td from qh if present. + * + * NOTE! We keep track of both forward and back-pointers, + * so this should be trivial, right? + * + * Wrong. While all TD insert/remove operations are synchronous + * on the CPU, the UHCI controller can (and does) play with the + * very first forward pointer. So we need to validate the backptr + * before we change it, so that we don't by mistake reset the QH + * head to something old. + */ +static void uhci_remove_td(struct uhci_td *td) +{ + unsigned int *backptr = td->backptr; + unsigned int link = td->link; + unsigned int me; + + if (!backptr) + return; + + td->backptr = NULL; + + /* + * This is the easy case: the UHCI will never change "td->link", + * so we can always just look at that and fix up the backpointer + * of any next element.. + */ + if (!(link & 1)) { + struct uhci_td *next = bus_to_virt(link & ~15); + next->backptr = backptr; + } + + /* + * The nasty case is "backptr->next", which we need to + * update to "link" _only_ if "backptr" still points + * to us (it may not: maybe backptr is a QH->element + * pointer and the UHCI has changed the value). + */ + me = virt_to_bus(td) | (0xe & *backptr); + asm volatile("lock ; cmpxchg %0,%1" + : + :"r" (link), "m" (*backptr), "a" (me) + :"memory"); +} + +static struct uhci_qh *uhci_qh_allocate(struct uhci_device *dev) +{ + struct uhci_qh *qh; + int inuse; + + qh = dev->qh; + for (; (inuse = test_and_set_bit(0, &qh->inuse)) != 0 && qh < &dev->qh[UHCI_MAXQH]; qh++) + ; + + if (!inuse) + return(qh); + + printk("ran out of qh's for dev %p\n", dev); + return(NULL); +} + +static void uhci_qh_deallocate(struct uhci_qh *qh) +{ + if (qh->element != 1) + printk("qh %p leaving dangling entries? (%X)\n", qh, qh->element); + + qh->element = 1; + qh->link = 1; + + clear_bit(0, &qh->inuse); +} + +static struct uhci_td *uhci_td_allocate(struct uhci_device *dev) +{ + struct uhci_td *td; + int inuse; + + td = dev->td; + for (; (inuse = test_and_set_bit(0, &td->inuse)) != 0 && td < &dev->td[UHCI_MAXTD]; td++) + ; + + if (!inuse) + return(td); + + printk("ran out of td's for dev %p\n", dev); + return(NULL); +} + +/* + * This MUST only be called when it has been removed from a QH already (or + * the QH has been removed from the skeleton + */ +static void uhci_td_deallocate(struct uhci_td *td) +{ + td->link = 1; + + clear_bit(0, &td->inuse); +} + +/* + * UHCI interrupt list operations.. + */ +static spinlock_t irqlist_lock = SPIN_LOCK_UNLOCKED; + +static void uhci_add_irq_list(struct uhci *uhci, struct uhci_td *td, usb_device_irq completed, void *dev_id) +{ + unsigned long flags; + + td->completed = completed; + td->dev_id = dev_id; + + spin_lock_irqsave(&irqlist_lock, flags); + list_add(&td->irq_list, &uhci->interrupt_list); + spin_unlock_irqrestore(&irqlist_lock, flags); +} + +static void uhci_remove_irq_list(struct uhci_td *td) +{ + unsigned long flags; + + spin_lock_irqsave(&irqlist_lock, flags); + list_del(&td->irq_list); + spin_unlock_irqrestore(&irqlist_lock, flags); +} + +/* + * Request a interrupt handler.. + */ +static int uhci_request_irq(struct usb_device *usb_dev, unsigned int pipe, usb_device_irq handler, int period, void *dev_id) +{ + struct uhci_device *dev = usb_to_uhci(usb_dev); + struct uhci_td *td = uhci_td_allocate(dev); + struct uhci_qh *interrupt_qh = uhci_qh_allocate(dev); + + unsigned int destination, status; + + /* Destination: pipe destination with INPUT */ + destination = (pipe & 0x0007ff00) | 0x69; + + /* Status: slow/fast, Interrupt, Active, Short Packet Detect Infinite Errors */ + status = (pipe & (1 << 26)) | (1 << 24) | (1 << 23) | (1 << 29) | (0 << 27); + + if(interrupt_qh->element != 1) + printk("interrupt_qh->element = 0x%x\n", + interrupt_qh->element); + + td->link = 1; + td->status = status; /* In */ + td->info = destination | (7 << 21); /* 8 bytes of data */ + td->buffer = virt_to_bus(dev->data); + td->qh = interrupt_qh; + interrupt_qh->skel = &dev->uhci->root_hub->skel_int8_qh; + + uhci_add_irq_list(dev->uhci, td, handler, dev_id); + + uhci_insert_td_in_qh(interrupt_qh, td); + + /* Add it into the skeleton */ + uhci_insert_qh(&dev->uhci->root_hub->skel_int8_qh, interrupt_qh); + return 0; +} + +/* + * Control thread operations: we just mark the last TD + * in a control thread as an interrupt TD, and wake up + * the front-end on completion. + * + * We need to remove the TD from the lists (both interrupt + * list and TD lists) by hand if something bad happens! + */ +static struct wait_queue *control_wakeup; + +static int uhci_control_completed(int status, void *buffer, void *dev_id) +{ + wake_up(&control_wakeup); + return 0; /* Don't re-instate */ +} + +/* td points to the last td in the list, which interrupts on completion */ +static int uhci_run_control(struct uhci_device *dev, struct uhci_td *first, struct uhci_td *last) +{ + struct wait_queue wait = { current, NULL }; + struct uhci_qh *ctrl_qh = uhci_qh_allocate(dev); + struct uhci_td *curtd; + + current->state = TASK_UNINTERRUPTIBLE; + add_wait_queue(&control_wakeup, &wait); + + uhci_add_irq_list(dev->uhci, last, uhci_control_completed, NULL); + + /* FIXME: This is kinda kludged */ + /* Walk the TD list and update the QH pointer */ + { + int maxcount = 100; + + curtd = first; + do { + curtd->qh = ctrl_qh; + if (curtd->link & 1) + break; + + curtd = bus_to_virt(curtd->link & ~0xF); + if (!--maxcount) { + printk("runaway tds!\n"); + break; + } + } while (1); + } + + uhci_insert_tds_in_qh(ctrl_qh, first, last); + + /* Add it into the skeleton */ + uhci_insert_qh(&dev->uhci->root_hub->skel_control_qh, ctrl_qh); + + schedule_timeout(HZ/10); + + remove_wait_queue(&control_wakeup, &wait); + + /* Clean up in case it failed.. */ + uhci_remove_irq_list(last); + +#if 0 + printk("Looking for tds [%p, %p]\n", dev->control_td, td); +#endif + + /* Remove it from the skeleton */ + uhci_remove_qh(&dev->uhci->root_hub->skel_control_qh, ctrl_qh); + + uhci_qh_deallocate(ctrl_qh); + + return uhci_td_result(dev, last); +} + +/* + * Send or receive a control message on a pipe. + * + * Note that the "pipe" structure is set up to map + * easily to the uhci destination fields. + * + * A control message is built up from three parts: + * - The command itself + * - [ optional ] data phase + * - Status complete phase + * + * The data phase can be an arbitrary number of TD's + * although we currently had better not have more than + * 29 TD's here (we have 31 TD's allocated for control + * operations, and two of them are used for command and + * status). + * + * 29 TD's is a minimum of 232 bytes worth of control + * information, that's just ridiculously high. Most + * control messages have just a few bytes of data. + */ +static int uhci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void *cmd, void *data, int len) +{ + struct uhci_device *dev = usb_to_uhci(usb_dev); + struct uhci_td *first, *td, *prevtd; + unsigned long destination, status; + int ret; + + if (len > usb_maxpacket(usb_dev->maxpacketsize) * 29) + printk("Warning, too much data for a control packet, crashing\n"); + + first = td = uhci_td_allocate(dev); + + /* The "pipe" thing contains the destination in bits 8--18, 0x2D is SETUP */ + destination = (pipe & 0x0007ff00) | 0x2D; + + /* Status: slow/fast, Active, Short Packet Detect Three Errors */ + status = (pipe & (1 << 26)) | (1 << 23) | (1 << 29) | (3 << 27); + + /* + * Build the TD for the control request + */ + td->status = status; /* Try forever */ + td->info = destination | (7 << 21); /* 8 bytes of data */ + td->buffer = virt_to_bus(cmd); + + /* + * If direction is "send", change the frame from SETUP (0x2D) + * to OUT (0xE1). Else change it from SETUP to IN (0x69) + */ + destination ^= (0x2D ^ 0x69); /* SETUP -> IN */ + if (usb_pipeout(pipe)) + destination ^= (0xE1 ^ 0x69); /* IN -> OUT */ + + prevtd = td; + td = uhci_td_allocate(dev); + prevtd->link = 4 | virt_to_bus(td); + + /* + * Build the DATA TD's + */ + while (len > 0) { + /* Build the TD for control status */ + int pktsze = len; + int maxsze = usb_maxpacket(pipe); + + if (pktsze > maxsze) + pktsze = maxsze; + + /* Alternate Data0/1 (start with Data1) */ + destination ^= 1 << 19; + + td->status = status; /* Status */ + td->info = destination | ((pktsze-1) << 21); /* pktsze bytes of data */ + td->buffer = virt_to_bus(data); + td->backptr = &prevtd->link; + + prevtd = td; + td = uhci_td_allocate(dev); + prevtd->link = 4 | virt_to_bus(td); /* Update previous TD */ + + data += maxsze; + len -= maxsze; + } + + /* + * Build the final TD for control status + */ + destination ^= (0xE1 ^ 0x69); /* OUT -> IN */ + destination |= 1 << 19; /* End in Data1 */ + + td->link = 1; /* Terminate */ + td->status = status | (1 << 24); /* IOC */ + td->info = destination | (0x7ff << 21); /* 0 bytes of data */ + td->buffer = 0; + td->backptr = &prevtd->link; + + /* Start it up.. */ + ret = uhci_run_control(dev, first, td); + + { + int maxcount = 100; + struct uhci_td *curtd = first; + unsigned int nextlink; + + do { + nextlink = curtd->link; + uhci_remove_td(curtd); + uhci_td_deallocate(curtd); + if (nextlink & 1) /* Tail? */ + break; + + curtd = bus_to_virt(nextlink & ~0xF); + if (!--maxcount) { + printk("runaway td's!?\n"); + break; + } + } while (1); + } + + return ret; +} + +static struct usb_device *uhci_usb_allocate(struct usb_device *parent) +{ + struct usb_device *usb_dev; + struct uhci_device *dev; + int i; + + usb_dev = kmalloc(sizeof(*usb_dev), GFP_KERNEL); + if (!usb_dev) + return NULL; + + memset(usb_dev, 0, sizeof(*usb_dev)); + + dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + usb_destroy_configuration(usb_dev); + kfree(usb_dev); + return NULL; + } + + /* Initialize "dev" */ + memset(dev, 0, sizeof(*dev)); + + usb_dev->hcpriv = dev; + dev->usb = usb_dev; + + usb_dev->parent = parent; + + if (parent) { + usb_dev->bus = parent->bus; + dev->uhci = usb_to_uhci(parent)->uhci; + } + + /* Reset the QH's and TD's */ + for (i = 0; i < UHCI_MAXQH; i++) { + dev->qh[i].link = 1; + dev->qh[i].element = 1; + dev->qh[i].inuse = 0; + } + + for (i = 0; i < UHCI_MAXTD; i++) { + dev->td[i].link = 1; + dev->td[i].inuse = 0; + } + + return usb_dev; +} + +static int uhci_usb_deallocate(struct usb_device *usb_dev) +{ + struct uhci_device *dev = usb_to_uhci(usb_dev); + int i; + + /* There are UHCI_MAXTD preallocated tds */ + for (i = 0; i < UHCI_MAXTD; ++i) { + struct uhci_td *td = dev->td + i; + + /* And remove it from the irq list, if it's active */ + if (td->status & (1 << 23)) + uhci_remove_irq_list(td); + + if (td->inuse) + uhci_remove_td(td); + } + + /* Remove the td from any queues */ + for (i = 0; i < UHCI_MAXQH; ++i) { + struct uhci_qh *qh = dev->qh + i; + + if (qh->inuse) + uhci_remove_qh(qh->skel, qh); + } + + kfree(dev); + usb_destroy_configuration(usb_dev); + kfree(usb_dev); + + return 0; +} + +struct usb_operations uhci_device_operations = { + uhci_usb_allocate, + uhci_usb_deallocate, + uhci_control_msg, + uhci_request_irq, +}; + +/* + * This is just incredibly fragile. The timings must be just + * right, and they aren't really documented very well. + * + * Note the short delay between disabling reset and enabling + * the port.. + */ +static void uhci_reset_port(unsigned int port) +{ + unsigned short status; + + status = inw(port); + outw(status | USBPORTSC_PR, port); /* reset port */ + wait_ms(10); + outw(status & ~USBPORTSC_PR, port); + udelay(5); + + status = inw(port); + outw(status | USBPORTSC_PE, port); /* enable port */ + wait_ms(10); + + status = inw(port); + if(!(status & USBPORTSC_PE)) { + outw(status | USBPORTSC_PE, port); /* one more try at enabling port */ + wait_ms(50); + } + +} + + +/* + * This gets called if the connect status on the root + * hub (and the root hub only) changes. + */ +static void uhci_connect_change(struct uhci *uhci, unsigned int port, unsigned int nr) +{ + struct usb_device *usb_dev; + struct uhci_device *dev; + unsigned short status; + + printk("uhci_connect_change: called for %d\n", nr); + + /* + * Even if the status says we're connected, + * the fact that the status bits changed may + * that we got disconnected and then reconnected. + * + * So start off by getting rid of any old devices.. + */ + usb_disconnect(&uhci->root_hub->usb->children[nr]); + + status = inw(port); + + /* If we have nothing connected, then clear change status and disable the port */ + status = (status & ~USBPORTSC_PE) | USBPORTSC_PEC; + if (!(status & USBPORTSC_CCS)) { + outw(status, port); + return; + } + + /* + * Ok, we got a new connection. Allocate a device to it, + * and find out what it wants to do.. + */ + usb_dev = uhci_usb_allocate(uhci->root_hub->usb); + dev = usb_dev->hcpriv; + + dev->uhci = uhci; + + usb_connect(usb_dev); + + uhci->root_hub->usb->children[nr] = usb_dev; + + wait_ms(200); /* wait for powerup */ + uhci_reset_port(port); + + /* Get speed information */ + usb_dev->slow = (inw(port) & USBPORTSC_LSDA) ? 1 : 0; + + /* + * Ok, all the stuff specific to the root hub has been done. + * The rest is generic for any new USB attach, regardless of + * hub type. + */ + usb_new_device(usb_dev); +} + +/* + * This gets called when the root hub configuration + * has changed. Just go through each port, seeing if + * there is something interesting happening. + */ +static void uhci_check_configuration(struct uhci *uhci) +{ + unsigned int io_addr = uhci->io_addr + USBPORTSC1; + int maxchild = uhci->root_hub->usb->maxchild; + int nr = 0; + + do { + unsigned short status = inw(io_addr); + + if (status & USBPORTSC_CSC) + uhci_connect_change(uhci, io_addr, nr); + + nr++; io_addr += 2; + } while (nr < maxchild); +} + +static void uhci_interrupt_notify(struct uhci *uhci) +{ + struct list_head *head = &uhci->interrupt_list; + struct list_head *tmp; + + spin_lock(&irqlist_lock); + tmp = head->next; + while (tmp != head) { + struct uhci_td *td = list_entry(tmp, struct uhci_td, irq_list); + struct list_head *next; + + next = tmp->next; + + if (!(td->status & (1 << 23))) { /* No longer active? */ + /* remove from IRQ list */ + __list_del(tmp->prev, next); + INIT_LIST_HEAD(tmp); + if (td->completed(td->status, bus_to_virt(td->buffer), td->dev_id)) { + struct uhci_qh *interrupt_qh = td->qh; + + list_add(&td->irq_list, &uhci->interrupt_list); + td->info ^= 1 << 19; /* toggle between data0 and data1 */ + td->status = (td->status & 0x2f000000) | (1 << 23) | (1 << 24); /* active */ + + /* Remove then readd? Is that necessary */ + uhci_remove_td(td); + uhci_insert_td_in_qh(interrupt_qh, td); + } + /* If completed wants to not reactivate, then it's */ + /* responsible for free'ing the TD's and QH's */ + /* or another function (such as run_control) */ + } + tmp = next; + } + spin_unlock(&irqlist_lock); +} + +/* + * Check port status - Connect Status Change - for + * each of the attached ports (defaults to two ports, + * but at least in theory there can be more of them). + * + * Wake up the configurator if something happened, we + * can't really do much at interrupt time. + */ +static void uhci_root_hub_events(struct uhci *uhci, unsigned int io_addr) +{ + if (waitqueue_active(&uhci_configure)) { + int ports = uhci->root_hub->usb->maxchild; + io_addr += USBPORTSC1; + do { + if (inw(io_addr) & USBPORTSC_CSC) { + wake_up(&uhci_configure); + return; + } + io_addr += 2; + } while (--ports > 0); + } +} + +static void uhci_interrupt(int irq, void *__uhci, struct pt_regs *regs) +{ + struct uhci *uhci = __uhci; + unsigned int io_addr = uhci->io_addr; + unsigned short status; + + /* + * Read the interrupt status, and write it back to clear the interrupt cause + */ + status = inw(io_addr + USBSTS); + outw(status, io_addr + USBSTS); + + /* Walk the list of pending TD's to see which ones completed.. */ + uhci_interrupt_notify(uhci); + + /* Check if there are any events on the root hub.. */ + uhci_root_hub_events(uhci, io_addr); +} + +/* + * We init one packet, and mark it just IOC and _not_ + * active. Which will result in no actual USB traffic, + * but _will_ result in an interrupt every second. + * + * Which is exactly what we want. + */ +static void uhci_init_ticktd(struct uhci *uhci) +{ + struct uhci_device *dev = uhci->root_hub; + struct uhci_td *td = uhci_td_allocate(dev); + + td->link = 1; + td->status = (1 << 24); /* interrupt on completion */ + td->info = (15 << 21) | 0x7f69; /* (ignored) input packet, 16 bytes, device 127 */ + td->buffer = 0; + td->qh = NULL; + + uhci->fl->frame[0] = virt_to_bus(td); +} + +static void reset_hc(struct uhci *uhci) +{ + unsigned int io_addr = uhci->io_addr; + + /* Global reset for 50ms */ + outw(USBCMD_GRESET, io_addr+USBCMD); + wait_ms(50); + outw(0, io_addr+USBCMD); + wait_ms(10); +} + +static void start_hc(struct uhci *uhci) +{ + unsigned int io_addr = uhci->io_addr; + int timeout = 1000; + + uhci_init_ticktd(uhci); + + /* + * Reset the HC - this will force us to get a + * new notification of any already connected + * ports due to the virtual disconnect that it + * implies. + */ + outw(USBCMD_HCRESET, io_addr + USBCMD); + while (inw(io_addr + USBCMD) & USBCMD_HCRESET) { + if (!--timeout) { + printk("USBCMD_HCRESET timed out!\n"); + break; + } + } + + outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, io_addr + USBINTR); + outw(0, io_addr + USBFRNUM); + outl(virt_to_bus(uhci->fl), io_addr + USBFLBASEADD); + + /* Run and mark it configured with a 64-byte max packet */ + outw(USBCMD_RS | USBCMD_CF, io_addr + USBCMD); +} + +/* + * Allocate a frame list, and four regular queues. + * + * The hardware doesn't really know any difference + * in the queues, but the order does matter for the + * protocols higher up. The order is: + * + * - any isochronous events handled before any + * of the queues. We don't do that here, because + * we'll create the actual TD entries on demand. + * - The first queue is the "interrupt queue". + * - The second queue is the "control queue". + * - The third queue is "bulk data". + * + * We could certainly have multiple queues of the same + * type, and maybe we should. We could have per-device + * queues, for example. We begin small. + */ +static struct uhci *alloc_uhci(unsigned int io_addr) +{ + int i; + struct uhci *uhci; + struct usb_bus *bus; + struct uhci_device *dev; + struct usb_device *usb; + + uhci = kmalloc(sizeof(*uhci), GFP_KERNEL); + if (!uhci) + return NULL; + + memset(uhci, 0, sizeof(*uhci)); + + uhci->irq = -1; + uhci->io_addr = io_addr; + INIT_LIST_HEAD(&uhci->interrupt_list); + + /* We need exactly one page (per UHCI specs), how convenient */ + uhci->fl = (void *)__get_free_page(GFP_KERNEL); + + bus = kmalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) + return NULL; + + memset(bus, 0, sizeof(*bus)); + + uhci->bus = bus; + bus->hcpriv = uhci; + bus->op = &uhci_device_operations; + + /* + * We allocate a 8kB area for the UHCI hub. The area + * is described by the uhci_device structure, and basically + * contains everything needed for normal operation. + * + * The first page is the actual device descriptor for the + * hub. + * + * The second page is used for the frame list. + */ + usb = uhci_usb_allocate(NULL); + if (!usb) + return NULL; + + dev = uhci->root_hub = usb_to_uhci(usb); + + usb->bus = bus; + + /* Initialize the root hub */ + /* UHCI specs says devices must have 2 ports, but goes on to say */ + /* they may have more but give no way to determine how many they */ + /* have, so default to 2 */ + usb->maxchild = 2; + usb_init_root_hub(usb); + + /* + * Initialize the queues. They all start out empty, + * linked to each other in the proper order. + */ + for (i = 1 ; i < 9; i++) { + dev->qh[i].link = 2 | virt_to_bus(&dev->skel_control_qh); + dev->qh[i].element = 1; + } + + dev->skel_control_qh.link = 2 | virt_to_bus(&dev->skel_bulk0_qh); + dev->skel_control_qh.element = 1; + + dev->skel_bulk0_qh.link = 2 | virt_to_bus(&dev->skel_bulk1_qh); + dev->skel_bulk0_qh.element = 1; + + dev->skel_bulk1_qh.link = 2 | virt_to_bus(&dev->skel_bulk2_qh); + dev->skel_bulk1_qh.element = 1; + + dev->skel_bulk2_qh.link = 2 | virt_to_bus(&dev->skel_bulk3_qh); + dev->skel_bulk2_qh.element = 1; + + dev->skel_bulk3_qh.link = 1; + dev->skel_bulk3_qh.element = 1; + + /* + * Fill the frame list: make all entries point to + * the proper interrupt queue. + * + * This is probably silly, but it's a simple way to + * scatter the interrupt queues in a way that gives + * us a reasonable dynamic range for irq latencies. + */ + for (i = 0; i < 1024; i++) { + struct uhci_qh * irq = &dev->skel_int2_qh; + if (i & 1) { + irq++; + if (i & 2) { + irq++; + if (i & 4) { + irq++; + if (i & 8) { + irq++; + if (i & 16) { + irq++; + if (i & 32) { + irq++; + if (i & 64) { + irq++; + } + } + } + } + } + } + } + uhci->fl->frame[i] = 2 | virt_to_bus(irq); + } + + return uhci; +} + + +/* + * De-allocate all resources.. + */ +static void release_uhci(struct uhci *uhci) +{ + if (uhci->irq >= 0) { + free_irq(uhci->irq, uhci); + uhci->irq = -1; + } + +#if 0 + if (uhci->root_hub) { + uhci_usb_deallocate(uhci_to_usb(uhci->root_hub)); + uhci->root_hub = NULL; + } +#endif + + if (uhci->fl) { + free_page((unsigned long)uhci->fl); + uhci->fl = NULL; + } + + kfree(uhci->bus); + kfree(uhci); +} + +void cleanup_drivers(void); + +static int uhci_control_thread(void * __uhci) +{ + struct uhci *uhci = (struct uhci *)__uhci; + + lock_kernel(); + request_region(uhci->io_addr, 32, "usb-uhci"); + + /* + * This thread doesn't need any user-level access, + * so get rid of all our resources.. + */ + printk("uhci_control_thread at %p\n", &uhci_control_thread); + exit_mm(current); + exit_files(current); + exit_fs(current); + + strcpy(current->comm, "uhci-control"); + + /* + * Ok, all systems are go.. + */ + start_hc(uhci); + for(;;) { + siginfo_t info; + int unsigned long signr; + + interruptible_sleep_on(&uhci_configure); +#ifdef CONFIG_APM + if (apm_resume) { + apm_resume = 0; + start_hc(uhci); + continue; + } +#endif + uhci_check_configuration(uhci); + + if(signal_pending(current)) { + /* sending SIGUSR1 makes us print out some info */ + spin_lock_irq(¤t->sigmask_lock); + signr = dequeue_signal(¤t->blocked, &info); + spin_unlock_irq(¤t->sigmask_lock); + + if(signr == SIGUSR1) { + printk("UHCI queue dump:\n"); + show_queues(uhci); + } else { + break; + } + } + } + +#if 0 + if(uhci->root_hub) + for(i = 0; i < uhci->root_hub->usb->maxchild; i++) + usb_disconnect(uhci->root_hub->usb->children + i); +#endif + + cleanup_drivers(); + + reset_hc(uhci); + release_region(uhci->io_addr, 32); + + release_uhci(uhci); + MOD_DEC_USE_COUNT; + + printk("uhci_control_thread exiting\n"); + + return 0; +} + +/* + * If we've successfully found a UHCI, now is the time to increment the + * module usage count, start the control thread, and return success.. + */ +static int found_uhci(int irq, unsigned int io_addr) +{ + int retval; + struct uhci *uhci; + + uhci = alloc_uhci(io_addr); + if (!uhci) + return -ENOMEM; + + reset_hc(uhci); + + retval = -EBUSY; + if (request_irq(irq, uhci_interrupt, SA_SHIRQ, "usb", uhci) == 0) { + int pid; + + MOD_INC_USE_COUNT; + uhci->irq = irq; + pid = kernel_thread(uhci_control_thread, uhci, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + if (pid >= 0) + return 0; + + MOD_DEC_USE_COUNT; + retval = pid; + } + release_uhci(uhci); + return retval; +} + +static int start_uhci(struct pci_dev *dev) +{ + int i; + + /* Search for the IO base address.. */ + for (i = 0; i < 6; i++) { + unsigned int io_addr = dev->base_address[i]; + + /* IO address? */ + if (!(io_addr & 1)) + continue; + + io_addr &= PCI_BASE_ADDRESS_IO_MASK; + + /* Is it already in use? */ + if (check_region(io_addr, 32)) + break; + + return found_uhci(dev->irq, io_addr); + } + return -1; +} + +#ifdef CONFIG_APM +static int handle_apm_event(apm_event_t event) +{ + static int down = 0; + + switch (event) { + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + if (down) { + printk(KERN_DEBUG "uhci: received extra suspend event\n"); + break; + } + down = 1; + break; + case APM_NORMAL_RESUME: + case APM_CRITICAL_RESUME: + if (!down) { + printk(KERN_DEBUG "uhci: received bogus resume event\n"); + break; + } + down = 0; + if (waitqueue_active(&uhci_configure)) { + apm_resume = 1; + wake_up(&uhci_configure); + } + break; + } + return 0; +} +#endif + +#ifdef MODULE + +void cleanup_module(void) +{ +#ifdef CONFIG_APM + apm_unregister_callback(&handle_apm_event); +#endif +} + +#define uhci_init init_module + +#endif + +int uhci_init(void) +{ + int retval; + struct pci_dev *dev = NULL; + u8 type; + + retval = -ENODEV; + for (;;) { + dev = pci_find_class(PCI_CLASS_SERIAL_USB<<8, dev); + if (!dev) + break; + /* Is it UHCI */ + pci_read_config_byte(dev, PCI_CLASS_PROG, &type); + if(type != 0) + continue; + /* Ok set it up */ + retval = start_uhci(dev); + if (retval < 0) + continue; + +#ifdef CONFIG_USB_MOUSE + usb_mouse_init(); +#endif +#ifdef CONFIG_USB_KBD + usb_kbd_init(); +#endif + hub_init(); +#ifdef CONFIG_USB_AUDIO + usb_audio_init(); +#endif +#ifdef CONFIG_APM + apm_register_callback(&handle_apm_event); +#endif + + return 0; + } + return retval; +} + +void cleanup_drivers(void) +{ + hub_cleanup(); +#ifdef CONFIG_USB_MOUSE + usb_mouse_cleanup(); +#endif +} diff --git a/drivers/usb/uhci.h b/drivers/usb/uhci.h new file mode 100644 index 000000000..f063356ac --- /dev/null +++ b/drivers/usb/uhci.h @@ -0,0 +1,229 @@ +#ifndef __LINUX_UHCI_H +#define __LINUX_UHCI_H + +#include <linux/list.h> + +#include "usb.h" + +/* + * Universal Host Controller Interface data structures and defines + */ + +/* Command register */ +#define USBCMD 0 +#define USBCMD_RS 0x0001 /* Run/Stop */ +#define USBCMD_HCRESET 0x0002 /* Host reset */ +#define USBCMD_GRESET 0x0004 /* Global reset */ +#define USBCMD_EGSM 0x0008 /* Global Suspend Mode */ +#define USBCMD_FGR 0x0010 /* Force Global Resume */ +#define USBCMD_SWDBG 0x0020 /* SW Debug mode */ +#define USBCMD_CF 0x0040 /* Config Flag (sw only) */ +#define USBCMD_MAXP 0x0080 /* Max Packet (0 = 32, 1 = 64) */ + +/* Status register */ +#define USBSTS 2 +#define USBSTS_USBINT 0x0001 /* Interrupt due to IOC */ +#define USBSTS_ERROR 0x0002 /* Interrupt due to error */ +#define USBSTS_RD 0x0004 /* Resume Detect */ +#define USBSTS_HSE 0x0008 /* Host System Error - basically PCI problems */ +#define USBSTS_HCPE 0x0010 /* Host Controller Process Error - the scripts were buggy */ +#define USBSTS_HCH 0x0020 /* HC Halted */ + +/* Interrupt enable register */ +#define USBINTR 4 +#define USBINTR_TIMEOUT 0x0001 /* Timeout/CRC error enable */ +#define USBINTR_RESUME 0x0002 /* Resume interrupt enable */ +#define USBINTR_IOC 0x0004 /* Interrupt On Complete enable */ +#define USBINTR_SP 0x0008 /* Short packet interrupt enable */ + +#define USBFRNUM 6 +#define USBFLBASEADD 8 +#define USBSOF 12 + +/* USB port status and control registers */ +#define USBPORTSC1 16 +#define USBPORTSC2 18 +#define USBPORTSC_CCS 0x0001 /* Current Connect Status ("device present") */ +#define USBPORTSC_CSC 0x0002 /* Connect Status Change */ +#define USBPORTSC_PE 0x0004 /* Port Enable */ +#define USBPORTSC_PEC 0x0008 /* Port Enable Change */ +#define USBPORTSC_LS 0x0030 /* Line Status */ +#define USBPORTSC_RD 0x0040 /* Resume Detect */ +#define USBPORTSC_LSDA 0x0100 /* Low Speed Device Attached */ +#define USBPORTSC_PR 0x0200 /* Port Reset */ +#define USBPORTSC_SUSP 0x1000 /* Suspend */ + +struct uhci_qh { + unsigned int link; /* Next queue */ + unsigned int element; /* Queue element pointer */ + int inuse; /* Inuse? */ + struct uhci_qh *skel; /* Skeleton head */ +} __attribute__((aligned(16))); + +struct uhci_framelist { + unsigned int frame[1024]; +} __attribute__((aligned(4096))); + +/* + * The documentation says "4 words for hardware, 4 words for software". + * + * That's silly, the hardware doesn't care. The hardware only cares that + * the hardware words are 16-byte aligned, and we can have any amount of + * sw space after the TD entry as far as I can tell. + * + * But let's just go with the documentation, at least for 32-bit machines. + * On 64-bit machines we probably want to take advantage of the fact that + * hw doesn't really care about the size of the sw-only area. + * + * Alas, not anymore, we have more than 4 words of software, woops + */ +struct uhci_td { + /* Hardware fields */ + __u32 link; + __u32 status; + __u32 info; + __u32 buffer; + + /* Software fields */ + struct list_head irq_list; /* Active interrupt list.. */ + usb_device_irq completed; /* Completion handler routine */ + unsigned int *backptr; /* Where to remove this from.. */ + void *dev_id; + int inuse; /* Inuse? */ + struct uhci_qh *qh; +} __attribute__((aligned(32))); + +/* + * Note the alignment requirements of the entries + * + * Each UHCI device has pre-allocated QH and TD entries. + * You can use more than the pre-allocated ones, but I + * don't see you usually needing to. + */ +struct uhci; + +#define UHCI_MAXTD 64 + +#define UHCI_MAXQH 16 + +/* The usb device part must be first! */ +struct uhci_device { + struct usb_device *usb; + + struct uhci *uhci; + struct uhci_qh qh[UHCI_MAXQH]; /* These are the "common" qh's for each device */ + struct uhci_td td[UHCI_MAXTD]; + + unsigned long data[16]; +}; + +#define uhci_to_usb(uhci) ((uhci)->usb) +#define usb_to_uhci(usb) ((struct uhci_device *)(usb)->hcpriv) + +/* + * The root hub pre-allocated QH's and TD's have + * some special global uses.. + */ +#define control_td td /* Td's 0-30 */ +/* This is only for the root hub's TD list */ +#define tick_td td[31] + +/* + * There are various standard queues. We set up several different + * queues for each of the three basic queue types: interrupt, + * control, and bulk. + * + * - There are various different interrupt latencies: ranging from + * every other USB frame (2 ms apart) to every 256 USB frames (ie + * 256 ms apart). Make your choice according to how obnoxious you + * want to be on the wire, vs how critical latency is for you. + * - The control list is done every frame. + * - There are 4 bulk lists, so that up to four devices can have a + * bulk list of their own and when run concurrently all four lists + * will be be serviced. + * + * This is a bit misleading, there are various interrupt latencies, but they + * vary a bit, interrupt2 isn't exactly 2ms, it can vary up to 4ms since the + * other queues can "override" it. interrupt4 can vary up to 8ms, etc. Minor + * problem + * + * In the case of the root hub, these QH's are just head's of qh's. Don't + * be scared, it kinda makes sense. Look at this wonderful picture care of + * Linus: + * + * generic-iso-QH -> dev1-iso-QH -> generic-irq-QH -> dev1-irq-QH -> ... + * | | | | + * End dev1-iso-TD1 End dev1-irq-TD1 + * | + * dev1-iso-TD2 + * | + * .... + * + * This may vary a bit (the UHCI docs don't explicitly say you can put iso + * transfers in QH's and all of their pictures don't have that either) but + * other than that, that is what we're doing now + * + * To keep with Linus' nomenclature, this is called the qh skeleton. These + * labels (below) are only signficant to the root hub's qh's + */ +#define skel_iso_qh qh[0] + +#define skel_int2_qh qh[1] +#define skel_int4_qh qh[2] +#define skel_int8_qh qh[3] +#define skel_int16_qh qh[4] +#define skel_int32_qh qh[5] +#define skel_int64_qh qh[6] +#define skel_int128_qh qh[7] +#define skel_int256_qh qh[8] + +#define skel_control_qh qh[9] + +#define skel_bulk0_qh qh[10] +#define skel_bulk1_qh qh[11] +#define skel_bulk2_qh qh[12] +#define skel_bulk3_qh qh[13] + +/* + * These are significant to the devices allocation of QH's + */ +#if 0 +#define iso_qh qh[0] +#define int_qh qh[1] /* We have 2 "common" interrupt QH's */ +#define control_qh qh[3] +#define bulk_qh qh[4] /* We have 4 "common" bulk QH's */ +#define extra_qh qh[8] /* The rest, anything goes */ +#endif + +/* + * This describes the full uhci information. + * + * Note how the "proper" USB information is just + * a subset of what the full implementation needs. + */ +struct uhci { + int irq; + unsigned int io_addr; + + struct usb_bus *bus; + +#if 0 + /* These are "standard" QH's for the entire bus */ + struct uhci_qh qh[UHCI_MAXQH]; +#endif + struct uhci_device *root_hub; /* Root hub device descriptor.. */ + + struct uhci_framelist *fl; /* Frame list */ + struct list_head interrupt_list; /* List of interrupt-active TD's for this uhci */ +}; + +/* needed for the debugging code */ +struct uhci_td *uhci_link_to_td(unsigned int element); + +/* Debugging code */ +void show_td(struct uhci_td * td); +void show_status(struct uhci *uhci); +void show_queues(struct uhci *uhci); + +#endif + diff --git a/drivers/usb/usb-debug.c b/drivers/usb/usb-debug.c new file mode 100644 index 000000000..86d08cd78 --- /dev/null +++ b/drivers/usb/usb-debug.c @@ -0,0 +1,127 @@ +/* + * debug.c - USB debug helper routines. + * + * I just want these out of the way where they aren't in your + * face, but so that you can still use them.. + */ +#include <linux/kernel.h> + +#include "usb.h" + +static void usb_show_endpoint(struct usb_endpoint_descriptor *endpoint) +{ + usb_show_endpoint_descriptor(endpoint); +} + +static void usb_show_interface(struct usb_interface_descriptor *interface) +{ + int i; + + usb_show_interface_descriptor(interface); + for (i = 0 ; i < interface->bNumEndpoints; i++) + usb_show_endpoint(interface->endpoint + i); +} + +static void usb_show_config(struct usb_config_descriptor *config) +{ + int i; + + usb_show_config_descriptor(config); + for (i = 0 ; i < config->bNumInterfaces; i++) + usb_show_interface(config->interface + i); +} + +void usb_show_device(struct usb_device *dev) +{ + int i; + + usb_show_device_descriptor(&dev->descriptor); + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) + usb_show_config(dev->config + i); +} + + +/* + * Parse and show the different USB descriptors. + */ +void usb_show_device_descriptor(struct usb_device_descriptor *desc) +{ + printk(" USB version %x.%02x\n", desc->bcdUSB >> 8, desc->bcdUSB & 0xff); + printk(" Vendor: %04x\n", desc->idVendor); + printk(" Product: %04x\n", desc->idProduct); + printk(" Configurations: %d\n", desc->bNumConfigurations); + + printk(" Device Class: %d\n", desc->bDeviceClass); + switch (desc->bDeviceClass) { + case 0: + printk(" Per-interface classes\n"); + break; + case 9: + printk(" Hub device class\n"); + break; + case 0xff: + printk(" Vendor class\n"); + break; + default: + printk(" Unknown class\n"); + } +} + +void usb_show_config_descriptor(struct usb_config_descriptor * desc) +{ + printk("Configuration:\n"); + printk(" bLength = %4d%s\n", desc->bLength, + desc->bLength == 9 ? "" : " (!!!)"); + printk(" bDescriptorType = %02x\n", desc->bDescriptorType); + printk(" wTotalLength = %04x\n", desc->wTotalLength); + printk(" bNumInterfaces = %02x\n", desc->bNumInterfaces); + printk(" bConfigurationValue = %02x\n", desc->bConfigurationValue); + printk(" iConfiguration = %02x\n", desc->iConfiguration); + printk(" bmAttributes = %02x\n", desc->bmAttributes); + printk(" MaxPower = %4dmA\n", desc->MaxPower * 2); +} + +void usb_show_interface_descriptor(struct usb_interface_descriptor * desc) +{ + printk(" Interface:\n"); + printk(" bLength = %4d%s\n", desc->bLength, + desc->bLength == 9 ? "" : " (!!!)"); + printk(" bDescriptorType = %02x\n", desc->bDescriptorType); + printk(" bInterfaceNumber = %02x\n", desc->bInterfaceNumber); + printk(" bAlternateSetting = %02x\n", desc->bAlternateSetting); + printk(" bNumEndpoints = %02x\n", desc->bNumEndpoints); + printk(" bInterfaceClass = %02x\n", desc->bInterfaceClass); + printk(" bInterfaceSubClass = %02x\n", desc->bInterfaceSubClass); + printk(" bInterfaceProtocol = %02x\n", desc->bInterfaceProtocol); + printk(" iInterface = %02x\n", desc->iInterface); +} + +void usb_show_endpoint_descriptor(struct usb_endpoint_descriptor * desc) +{ + char *EndpointType[4] = { "Control", "Isochronous", "Bulk", "Interrupt" }; + printk(" Endpoint:\n"); + printk(" bLength = %4d%s\n", desc->bLength, + desc->bLength == 7 ? "" : " (!!!)"); + printk(" bDescriptorType = %02x\n", desc->bDescriptorType); + printk(" bEndpointAddress = %02x (%s)\n", desc->bEndpointAddress, + (desc->bEndpointAddress & 0x80) ? "in" : "out"); + printk(" bmAttributes = %02x (%s)\n", desc->bmAttributes, + EndpointType[3 & desc->bmAttributes]); + printk(" wMaxPacketSize = %04x\n", desc->wMaxPacketSize); + printk(" bInterval = %02x\n", desc->bInterval); +} + +void usb_show_hub_descriptor(struct usb_hub_descriptor * desc) +{ + int len = 7; + unsigned char *ptr = (unsigned char *) desc; + + printk("Interface:"); + while (len) { + printk(" %02x", *ptr); + ptr++; len--; + } + printk("\n"); +} + + 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); +} + diff --git a/drivers/usb/usb.h b/drivers/usb/usb.h new file mode 100644 index 000000000..10379ef88 --- /dev/null +++ b/drivers/usb/usb.h @@ -0,0 +1,376 @@ +#ifndef __LINUX_USB_H +#define __LINUX_USB_H + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/sched.h> + +static __inline__ void wait_ms(unsigned int ms) +{ + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(1 + ms * HZ / 1000); +} + + +typedef struct { + unsigned char requesttype; + unsigned char request; + unsigned short value; + unsigned short index; + unsigned short length; +} devrequest; + +/* + * Class codes + */ +#define USB_CLASS_HUB 9 + +/* + * Descriptor types + */ +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 + +#define USB_DT_HUB 0x29 +#define USB_DT_HID 0x21 + +/* + * Standard requests + */ +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +/* 0x02 is reserved */ +#define USB_REQ_SET_FEATURE 0x03 +/* 0x04 is reserved */ +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +/* + * HIDD requests + */ +#define USB_REQ_GET_REPORT 0x01 +#define USB_REQ_GET_IDLE 0x02 +#define USB_REQ_GET_PROTOCOL 0x03 +#define USB_REQ_SET_REPORT 0x09 +#define USB_REQ_SET_IDLE 0x0A +#define USB_REQ_SET_PROTOCOL 0x0B + +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +/* + * Request target types. + */ +#define USB_RT_DEVICE 0x00 +#define USB_RT_INTERFACE 0x01 +#define USB_RT_ENDPOINT 0x02 + +#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE) +#define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER) + +#define USB_RT_HIDD (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +/* + * USB device number allocation bitmap. There's one bitmap + * per USB tree. + */ +struct usb_devmap { + unsigned long devicemap[128 / (8*sizeof(unsigned long))]; +}; + +/* + * This is a USB device descriptor. + * + * USB device information + * + */ + +#define USB_MAXCONFIG 8 +#define USB_MAXINTERFACES 32 +#define USB_MAXENDPOINTS 32 + +struct usb_device_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u16 bcdUSB; + __u8 bDeviceClass; + __u8 bDeviceSubClass; + __u8 bDeviceProtocol; + __u8 bMaxPacketSize0; + __u16 idVendor; + __u16 idProduct; + __u16 bcdDevice; + __u8 iManufacturer; + __u8 iProduct; + __u8 iSerialNumber; + __u8 bNumConfigurations; +}; + +/* Endpoint descriptor */ +struct usb_endpoint_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bEndpointAddress; + __u8 bmAttributes; + __u16 wMaxPacketSize; + __u8 bInterval; + void *audio; +}; + +/* Interface descriptor */ +struct usb_interface_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bInterfaceNumber; + __u8 bAlternateSetting; + __u8 bNumEndpoints; + __u8 bInterfaceClass; + __u8 bInterfaceSubClass; + __u8 bInterfaceProtocol; + __u8 iInterface; + + struct usb_endpoint_descriptor *endpoint; + void *audio; +}; + +/* Configuration descriptor information.. */ +struct usb_config_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u16 wTotalLength; + __u8 bNumInterfaces; + __u8 bConfigurationValue; + __u8 iConfiguration; + __u8 bmAttributes; + __u8 MaxPower; + + struct usb_interface_descriptor *interface; +}; + +/* String descriptor */ +struct usb_string_descriptor { + __u8 bLength; + __u8 bDescriptorType; +}; + +/* Hub descriptor */ +struct usb_hub_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bNbrPorts; + __u16 wHubCharacteristics; + __u8 bPwrOn2PwrGood; + __u8 bHubContrCurrent; + /* DeviceRemovable and PortPwrCtrlMask want to be variable-length + bitmaps that hold max 256 entries, but for now they're ignored */ + __u8 filler; +}; + +struct usb_device; + +struct usb_driver { + const char * name; + int (*probe)(struct usb_device *); + void (*disconnect)(struct usb_device *); + struct list_head driver_list; +}; + +/* + * Pointer to a device endpoint interrupt function -greg + * Parameters: + * int status - This needs to be defined. Right now each HCD + * passes different transfer status bits back. Don't use it + * until we come up with a common meaning. + * void *buffer - This is a pointer to the data used in this + * USB transfer. + * void *dev_id - This is a user defined pointer set when the IRQ + * is requested that is passed back. + */ +typedef int (*usb_device_irq)(int, void *, void *); + +struct usb_operations { + struct usb_device *(*allocate)(struct usb_device *); + int (*deallocate)(struct usb_device *); + int (*control_msg)(struct usb_device *, unsigned int, void *, void *, int); + int (*request_irq)(struct usb_device *, unsigned int, usb_device_irq, int, void *); +}; + +/* + * Allocated per bus we have + */ +struct usb_bus { + struct usb_devmap devmap; /* Device map */ + struct usb_operations *op; /* Operations (specific to the HC) */ + struct usb_device *root_hub; /* Root hub */ + void *hcpriv; /* Host Controller private data */ +}; + + +#define USB_MAXCHILDREN (8) + +struct usb_device { + int devnum; /* Device number on USB bus */ + int slow; /* Slow device? */ + int maxpacketsize; /* Maximum packet size */ + + struct usb_bus *bus; /* Bus we're apart of */ + struct usb_driver *driver; /* Driver */ + struct usb_device_descriptor descriptor; /* Descriptor */ + struct usb_config_descriptor *config; /* All of the configs */ + struct usb_device *parent; + + /* + * Child devices - these can be either new devices + * (if this is a hub device), or different instances + * of this same device. + * + * Each instance needs its own set of data structuctures. + */ + + int maxchild; /* Number of ports if hub */ + struct usb_device *children[USB_MAXCHILDREN]; + + void *hcpriv; /* Host Controller private data */ + void *private; /* Upper layer private data */ +}; + +extern int usb_register(struct usb_driver *); +extern void usb_deregister(struct usb_driver *); + +extern int usb_request_irq(struct usb_device *, unsigned int, usb_device_irq, int, void *); + +extern void usb_init_root_hub(struct usb_device *dev); +extern void usb_connect(struct usb_device *dev); +extern void usb_disconnect(struct usb_device **); +extern void usb_device_descriptor(struct usb_device *dev); + +extern int usb_parse_configuration(struct usb_device *dev, void *buf, int len); + +/* + * Calling this entity a "pipe" is glorifying it. A USB pipe + * is something embarrassingly simple: it basically consists + * of the following information: + * - device number (7 bits) + * - endpoint number (4 bits) + * - current Data0/1 state (1 bit) + * - direction (1 bit) + * - speed (1 bit) + * - max packet size (2 bits: 8, 16, 32 or 64) + * - pipe type (2 bits: control, interrupt, bulk, isochronous) + * + * That's 18 bits. Really. Nothing more. And the USB people have + * documented these eighteen bits as some kind of glorious + * virtual data structure. + * + * Let's not fall in that trap. We'll just encode it as a simple + * unsigned int. The encoding is: + * + * - device: bits 8-14 + * - endpoint: bits 15-18 + * - Data0/1: bit 19 + * - direction: bit 7 (0 = Host-to-Device, 1 = Device-to-Host) + * - speed: bit 26 (0 = High, 1 = Low Speed) + * - max size: bits 0-1 (00 = 8, 01 = 16, 10 = 32, 11 = 64) + * - pipe type: bits 30-31 (00 = isochronous, 01 = interrupt, 10 = control, 11 = bulk) + * + * Why? Because it's arbitrary, and whatever encoding we select is really + * up to us. This one happens to share a lot of bit positions with the UCHI + * specification, so that much of the uhci driver can just mask the bits + * appropriately. + */ + +#define usb_maxpacket(pipe) (8 << ((pipe) & 3)) +#define usb_packetid(pipe) (((pipe) & 0x80) ? 0x69 : 0xE1) + +#define usb_pipedevice(pipe) (((pipe) >> 8) & 0x7f) +#define usb_pipeendpoint(pipe) (((pipe) >> 15) & 0xf) +#define usb_pipedata(pipe) (((pipe) >> 19) & 1) +#define usb_pipeout(pipe) (((pipe) & 0x80) == 0) +#define usb_pipeslow(pipe) (((pipe) >> 26) & 1) + +#define usb_pipetype(pipe) (((pipe) >> 30) & 3) +#define usb_pipeisoc(pipe) (usb_pipetype((pipe)) == 0) +#define usb_pipeint(pipe) (usb_pipetype((pipe)) == 1) +#define usb_pipecontrol(pipe) (usb_pipetype((pipe)) == 2) +#define usb_pipebulk(pipe) (usb_pipetype((pipe)) == 3) + +#define usb_pipe_endpdev(pipe) (((pipe) >> 8) & 0x7ff) + +static inline unsigned int __create_pipe(struct usb_device *dev, unsigned int endpoint) +{ + return (dev->devnum << 8) | (endpoint << 15) | (dev->slow << 26) | dev->maxpacketsize; +} + +static inline unsigned int __default_pipe(struct usb_device *dev) +{ + return (dev->slow << 26); +} + +/* Create control pipes.. */ +#define usb_sndctrlpipe(dev,endpoint) ((2 << 30) | __create_pipe(dev,endpoint)) +#define usb_rcvctrlpipe(dev,endpoint) ((2 << 30) | __create_pipe(dev,endpoint) | 0x80) +#define usb_snddefctrl(dev) ((2 << 30) | __default_pipe(dev)) +#define usb_rcvdefctrl(dev) ((2 << 30) | __default_pipe(dev) | 0x80) + +/* Create .. */ + +/* + * Send and receive control messages.. + */ +void usb_new_device(struct usb_device *dev); +int usb_set_address(struct usb_device *dev); +int usb_get_descriptor(struct usb_device *dev, unsigned char desctype, unsigned +char descindex, void *buf, int size); +int usb_get_device_descriptor(struct usb_device *dev); +int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size); +int usb_clear_port_feature(struct usb_device *dev, int port, int feature); +int usb_set_port_feature(struct usb_device *dev, int port, int feature); +int usb_get_hub_status(struct usb_device *dev, void *data); +int usb_get_port_status(struct usb_device *dev, int port, void *data); +int usb_get_protocol(struct usb_device *dev); +int usb_set_protocol(struct usb_device *dev, int protocol); +int usb_set_idle(struct usb_device *dev, int duration, int report_id); +int usb_set_configuration(struct usb_device *dev, int configuration); +int usb_get_report(struct usb_device *dev); + +/* + * Debugging helpers.. + */ +void usb_show_device_descriptor(struct usb_device_descriptor *); +void usb_show_config_descriptor(struct usb_config_descriptor *); +void usb_show_interface_descriptor(struct usb_interface_descriptor *); +void usb_show_endpoint_descriptor(struct usb_endpoint_descriptor *); +void usb_show_hub_descriptor(struct usb_hub_descriptor *); +void usb_show_device(struct usb_device *); + +/* + * Audio parsing helpers + */ + +#ifdef CONFIG_USB_AUDIO +void usb_audio_interface(struct usb_interface_descriptor *, u8 *); +void usb_audio_endpoint(struct usb_endpoint_descriptor *, u8 *); +#else +extern inline void usb_audio_interface(struct usb_interface_descriptor *interface, u8 *data) {} +extern inline void usb_audio_endpoint(struct usb_endpoint_descriptor *interface, u8 *data) {} +#endif + +#endif + |