diff -Nru a/Documentation/Configure.help b/Documentation/Configure.help --- a/Documentation/Configure.help Wed Dec 19 10:26:02 2001 +++ b/Documentation/Configure.help Wed Dec 19 10:26:02 2001 @@ -12541,6 +12541,30 @@ If you have an MGE Ellipse UPS, or you see timeouts in HID transactions, say Y; otherwise say N. +EHCI (USB 2.0) support +CONFIG_EHCI_HCD + The Enhanced Host Controller Interface (EHCI) is standard for USB 2.0 + "high speed" (480 Mbit/sec, 60 Mbyte/sec) host controller hardware. + If your USB host controller supports USB 2.0, you will likely want to + configure this Host Controller Driver. At this writing, the primary + implementation of EHCI is a chip from NEC, widely available in add-on + PCI cards, but implementations are in the works from other vendors + including Intel and Philips. Motherboard support is appearing. + + EHCI controllers are packaged with "companion" host controllers (OHCI + or UHCI) to handle USB 1.1 devices connected to root hub ports. Ports + will connect to EHCI if it the device is high speed, otherwise they + connect to a companion controller. If you configure EHCI, you should + probably configure the OHCI (for NEC and some other vendors) USB Host + Controller Driver too. + + You may want to read . + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called ehci-hcd.o. If you want to compile it as a + module, say M here and read . + UHCI (Intel PIIX4, VIA, ...) support CONFIG_USB_UHCI The Universal Host Controller Interface is a standard by Intel for @@ -12841,6 +12865,17 @@ The module will be called visor.o. If you want to compile it as a module, say M here and read . +USB Compaq iPAQ Driver +CONFIG_USB_SERIAL_IPAQ + Say Y here if you want to connect to your Compaq iPAQ running + Windows CE 3.0 using a USB autosync cable. For information on using + the driver, read . + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called ipaq.o. If you want to compile it as a + module, say M here and read . + USB IR Dongle Serial Driver CONFIG_USB_SERIAL_IR Say Y here if you want to enable simple serial support for USB IrDA @@ -13061,6 +13096,22 @@ The module will be called io_edgeport.o. If you want to compile it as a module, say M here and read . +USB PalmConnect (and other KL5KUSB105-based) Single Port Serial Driver +CONFIG_USB_SERIAL_KLSI + Say Y here if you want to use a KL5KUSB105 - based single port + serial adapter. The most widely known -- and currently the only + tested -- device in this category is the PalmConnect USB Serial + adapter sold by Palm Inc. for use with their Palm III and Palm V + series PDAs. + + Please read for more + information. + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called kl5kusb105.o. If you want to compile it as + a module, say M here and read . + USB Serial Converter verbose debug CONFIG_USB_SERIAL_DEBUG Say Y here if you want verbose debug messages from the USB Serial @@ -13167,6 +13218,54 @@ The module will be called pwc.o. If you want to compile it as a module, say M here and read . +USB SE401 Camera support +CONFIG_USB_SE401 + Say Y here if you want to connect this type of camera to your + computer's USB port. See for more + information and for a list of supported cameras. + + This driver uses the Video For Linux API. You must say Y or M to + "Video For Linux" (under Multimedia Devices) to use this driver. + Information on this API and pointers to "v4l" programs may be found + on the WWW at . + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called se401.o. If you want to compile it as a + module, say M here and read . + +USB STV680 (Pencam) Camera support +CONFIG_USB_STV680 + Say Y here if you want to connect this type of camera to your + computer's USB port. This includes the Pencam line of cameras. + See for more information and for + a list of supported cameras. + + This driver uses the Video For Linux API. You must say Y or M to + "Video For Linux" (under Multimedia Devices) to use this driver. + Information on this API and pointers to "v4l" programs may be found + on the WWW at . + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called stv680.o. If you want to compile it as a + module, say M here and read . + +Vicam +CONFIG_USB_VICAM + Say Y here if you have 3com homeconnect camera (vicam). + + This driver uses the Video For Linux API. You must say Y or M to + "Video For Linux" (under Multimedia Devices) to use this driver. + Information on this API and pointers to "v4l" programs may be found + on the WWW at . + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called vicam.o. If you want to compile it as a + module, say M here and read . + + Pegasus/Pegasus II based USB-Ethernet device support CONFIG_USB_PEGASUS Say Y here if you know you have Pegasus or Pegasus II based adapter. @@ -16960,22 +17059,6 @@ Note that, even if you say N here, Linux on the x86 architecture will issue the hlt instruction if nothing is to be done, thereby sending the processor to sleep and saving power. - -USB SE401 Camera support -CONFIG_USB_SE401 - Say Y here if you want to connect this type of camera to your - computer's USB port. See for more - information and for a list of supported cameras. - - This driver uses the Video For Linux API. You must say Y or M to - "Video For Linux" (under Multimedia Devices) to use this driver. - Information on this API and pointers to "v4l" programs may be found - on the WWW at . - - This code is also available as a module ( = code which can be - inserted in and removed from the running kernel whenever you want). - The module will be called se401.o. If you want to compile it as a - module, say M here and read . ACPI support CONFIG_ACPI diff -Nru a/Documentation/usb/ehci.txt b/Documentation/usb/ehci.txt --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/Documentation/usb/ehci.txt Wed Dec 19 10:26:03 2001 @@ -0,0 +1,164 @@ +18-Dec-2001 + +The EHCI driver is used to talk to high speed USB 2.0 devices using +USB 2.0-capable host controller hardware. The USB 2.0 standard is +compatible with the USB 1.1 standard. It defines three transfer speeds: + + - "High Speed" 480 Mbit/sec (60 MByte/sec) + - "Full Speed" 12 Mbit/sec (1.5 MByte/sec) + - "Low Speed" 1.5 Mbit/sec + +USB 1.1 only addressed full speed and low speed. High speed devices +can be used on USB 1.1 systems, but they slow down to USB 1.1 speeds. + +USB 1.1 devices may also be used on USB 2.0 systems. When plugged +into an EHCI controller, they are given to a USB 1.1 "companion" +controller, which is a OHCI or UHCI controller as normally used with +such devices. When USB 1.1 devices plug into USB 2.0 hubs, they +interact with the EHCI controller through a "Transaction Translator" +(TT) in the hub, which turns low or full speed transactions into +high speed "split transactions" that don't waste transfer bandwidth. + +At this writing, high speed devices are finally beginning to appear. +While usb-storage devices have been available for some time (working +quite speedily on the 2.4 version of this driver), hubs have only +very recently become available. + +Note that USB 2.0 support involves more than just EHCI. It requires +other changes to the Linux-USB core APIs, including the hub driver, +but those changes haven't needed to really change the basic "usbcore" +APIs exposed to USB device drivers. + +- David Brownell + + + +FUNCTIONALITY + +This driver is regularly tested on x86 hardware, and has also been +used on PPC hardware so big/little endianneess issues should be gone. +It's believed to do all the right PCI magic so that I/O works even on +systems with interesting DMA mapping issues. + +At this writing the driver should comfortably handle all control and bulk +transfers, including requests to USB 1.1 devices through transaction +translators (TTs) in USB 2.0 hubs. However, there some situations where +the hub driver needs to clear TT error state, which it doesn't yet do. + +Interrupt transfer support is newly functional and not yet as robust as +control and bulk traffic. As yet there is no support for split transaction +scheduling for interrupt transfers, which means among other things that +connecting USB 1.1 hubs, keyboards, and mice to USB 2.0 hubs won't work. +Connect them to USB 1.1 hubs, or to a root hub. + +Isochronous (ISO) transfer support is not yet working. No production +high speed devices are available which would need it (though high quality +webcams are in the works!). Note that split transaction support for ISO +transfers can't share much code with the code for high speed ISO transfers, +since EHCI represents these with a different data structure. + +The EHCI root hub code should hand off USB 1.1 devices to its companion +controller. This driver doesn't need to know anything about those +drivers; a OHCI or UHCI driver that works already doesn't need to change +just because the EHCI driver is also present. + +There are some issues with power management; suspend/resume doesn't +behave quite right at the moment. + + +USE BY + +Assuming you have an EHCI controller (on a PCI card or motherboard) +and have compiled this driver as a module, load this like: + + # modprobe ehci-hcd + +and remove it by: + + # rmmod ehci-hcd + +You should also have a driver for a "companion controller", such as +"ohci-hcd", "usb-ohci", "usb-uhci", or "uhci". In case of any trouble +with the EHCI driver, remove its module and then the driver for that +companion controller will take over (at lower speed) all the devices +that were previously handled by the EHCI driver. + +Module parameters (pass to "modprobe") include: + + log2_irq_thresh (default 0): + Log2 of default interrupt delay, in microframes. The default + value is 0, indicating 1 microframe (125 usec). Maximum value + is 6, indicating 2^6 = 64 microframes. This controls how often + the EHCI controller can issue interrupts. + +The EHCI interrupt handler just acknowledges interrupts and schedules +a tasklet to handle whatever needs handling. That keeps latencies low, +no matter how often interrupts are issued. + +Device drivers shouldn't care whether they're running over EHCI or not, +but they may want to check for "usb_device->speed == USB_SPEED_HIGH". +High speed devices can do things that full speed (or low speed) ones +can't, such as "high bandwidth" periodic (interrupt or ISO) transfers. + + +PERFORMANCE + +USB 2.0 throughput is gated by two main factors: how fast the host +controller can process requests, and how fast devices can respond to +them. The 480 Mbit/sec "raw transfer rate" is obeyed by all devices, +but aggregate throughput is also affected by issues like delays between +individual high speed packets, driver intelligence, and of course the +overall system load. Latency is also a performance concern. + +Bulk transfers are most often used where throughput is an issue. It's +good to keep in mind that bulk transfers are always in 512 byte packets, +and at most 13 of those fit into one USB 2.0 microframe. Eight USB 2.0 +microframes fit in a USB 1.1 frame; a microframe is 1 msec/8 = 125 usec. + +Hardware Performance + +At this writing, individual USB 2.0 devices tend to max out at around +20 MByte/sec transfer rates. This is of course subject to change; +and some devices now go faster, while others go slower. + +The NEC implementation of EHCI seems to have a hardware bottleneck +at around 28 MByte/sec aggregate transfer rate. While this is clearly +enough for a single device at 20 MByte/sec, putting three such devices +onto one bus does not get you 60 MByte/sec. The issue appears to be +that the controller hardware won't do concurrent USB and PCI access, +so that it's only trying six (or maybe seven) USB transactions each +microframe rather than thirteen. (Seems like a reasonable trade off +for a product that beat all the others to market by over a year!) +It's expected that newer implementations will better this, throwing +more silicon real estate at the problem so that new motherboard chip +sets will get closer to that 60 MByte/sec target. + +There's a minimum latency of one microframe (125 usec) for the host +to receive interrupts from the EHCI controller indicating completion +of requests. That latency is tunable; there's a module option. By +default ehci-hcd driver uses the minimum latency, which means that if +you issue a control or bulk request you can often expect to learn that +it completed in less than 250 usec (depending on transfer size). + +Software Performance + +To get even 20 MByte/sec transfer rates, Linux-USB device drivers will +need to keep the EHCI queue full. That means issuing large requests, +or using bulk queuing if a series of small requests needs to be issued. +When drivers don't do that, their performance results will show it. + +In typical situations, a usb_bulk_msg() loop writing out 4 KB chunks is +going to waste more than half the USB 2.0 bandwidth. Delays between the +I/O completion and the driver issuing the next request will take longer +than the I/O. If that same loop used 16 KB chunks, it'd be better; a +sequence of 128 KB chunks would waste a lot less. + +But rather than depending on such large I/O buffers to make synchronous +I/O be efficient, it's better to just queue all several (bulk) requests +to the HC, and wait for them all to complete (or be canceled on error). +Such URB queuing should work with all the USB 1.1 HC drivers too. + +TBD: Interrupt and ISO transfer performance issues. Those periodic +transfers are fully scheduled, so the main issue is likely to be how +to trigger "high bandwidth" modes. + diff -Nru a/Documentation/usb/stv680.txt b/Documentation/usb/stv680.txt --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/Documentation/usb/stv680.txt Wed Dec 19 10:26:03 2001 @@ -0,0 +1,55 @@ +Linux driver for STV0680 based USB cameras + +Copyright, 2001, Kevin Sisson + + +INTRODUCTION: + +STMicroelectronics produces the STV0680B chip, which comes in two +types, -001 and -003. The -003 version allows the recording and downloading +of sound clips from the camera, and allows a flash attachment. Otherwise, +it uses the same commands as the -001 version. Both versions support a +variety of SDRAM sizes and sensors, allowing for a maximum of 26 VGA or 20 +CIF pictures. The STV0680 supports either a serial or a usb interface, and +video is possible through the usb interface. + +The following cameras are known to work with this driver, although any +camera with Vendor/Product codes of 0553/0202 should work: + +Aiptek Pencam (various models) +Nisis QuickPix 2 +Radio Shack 'Kid's digital camera' (#60-1207) +At least one Trust Spycam model +Several other European brand models + +WHAT YOU NEED: + +- USB support +- VIDEO4LINUX support + +More information about USB support for linux can be found at: +http://www.linux-usb.org + + +MODULE OPTIONS: + +When the driver is compiled as a module, you can set a "swapRGB=1" +option, if necessary, for those applications that require it +(such as xawtv). However, the driver should detect and set this +automatically, so this option should not normally be used. + + +KNOWN PROBLEMS: + +The driver seems to work better with the usb-ohci than the usb-uhci host +controller driver. + +HELP: + +The latest info on this driver can be found at: +http://personal.clt.bellsouth.net/~kjsisson or at +http://stv0680-usb.sourceforge.net + +Any questions to me can be send to: kjsisson@bellsouth.net + + diff -Nru a/Documentation/usb/usb-serial.txt b/Documentation/usb/usb-serial.txt --- a/Documentation/usb/usb-serial.txt Wed Dec 19 10:26:02 2001 +++ b/Documentation/usb/usb-serial.txt Wed Dec 19 10:26:02 2001 @@ -95,6 +95,66 @@ Kroah-Hartman at greg@kroah.com +Compaq iPAQ driver + + This driver can be used to connect to Compaq iPAQ PDAs running + Windows CE 3.0 using a USB autosync cable. It has been tested only on + the Compaq H3135. It should work with the H3600 and later models too. + It may work with other CE based handhelds as well. + + The driver presents a serial interface (usually on /dev/ttyUSB0) over + which one may run ppp and establish a TCP/IP link to the iPAQ. Once this + is done, you can transfer files, backup, download email etc. The most + significant advantage of using USB is speed - you can get 73 to 113 + kbytes/sec for download/upload to the iPAQ. + + The driver works intermittently with the usb-uhci driver but quite + reliably with the uhci driver. Make sure you have the right driver + loaded - usb-uhci is often the default. + + You must setup hotplug to invoke pppd as soon as the iPAQ is connected. + A ppp script like the one below may be used: + + #!/bin/bash + + MYIP=linux.box.ip + REMOTEIP=ipaq.ip + MYDNS=my.dns.server + killall -9 pppd + /usr/sbin/pppd /dev/ttyUSB0 \ + connect "/usr/sbin/chat -v TIMEOUT 60 CLIENT 'CLIENTSERVER\c'" \ + nocrtscts local debug passive $MYIP:$REMOTEIP ms-dns $MYDNS noauth \ + proxyarp + + You must also download and install asyncd from http://synce.sourceforge.net + This is required to emulate keep-alive packets which are exchanged by + ActiveSync and the iPAQ. + + On connecting the cable, you should see the usual "Device Connected", + "User Authenticated" messages flash by on your iPAQ. Once connected, + you can use Win CE programs like ftpView, Pocket Outlook from the iPAQ + and other synce utilities from the Linux side. Remember to enable IP + forwarding. + + To use Pocket IE, follow the instructions given at + http://www.tekguru.co.uk/EM500/usbtonet.htm to achieve the same thing + on Win98. Omit the proxy server part; Linux is quite capable of forwarding + packets unlike Win98. Another modification is required at least for the + iPAQ - disable autosync by going to the Start/Settings/Connections menu + and unchecking the "Automatically synchronize ..." box. Go to + Start/Programs/Connections, connect the cable and select "usbdial" (or + whatever you named your new USB connection). You should finally wind + up with a "Connected to usbdial" window with status shown as connected. + Now start up PIE and browse away. + + If it doesn't work for some reason, load both the usbserial and ipaq module + with the module parameter "debug" set to 1 and examine the system log. + You can also try soft-resetting your iPAQ before attempting a connection. + + For any questions or problems with the driver, please contact Ganesh + Varadarajan + + Keyspan PDA Serial Adapter Single port DB-9 serial adapter, pushed as a PDA adapter for iMacs (mostly @@ -301,6 +361,32 @@ For any questions or problems with this driver, please contact Greg Kroah-Hartman at greg@kroah.com + +KL5KUSB105 chipset / PalmConnect USB single-port adapter + +Current status: + The driver was put together by looking at the usb bus transactions + done by Palm's driver under Windows, so a lot of functionality is + still missing. Notably, serial ioctls are sometimes faked or not yet + implemented. Support for finding out about DSR and CTS line status is + however implemented (though not nicely), so your favorite autopilot(1) + and pilot-manager -daemon calls will work. Baud rates up to 115200 + are supported, but handshaking (software or hardware) is not, which is + why it is wise to cut down on the rate used is wise for large + transfers until this is settled. + +Options supported: + If this driver is compiled as a module you can pass the following + options to it: + debug - extra verbose debugging info + (default: 0; nonzero enables) + use_lowlatency - use low_latency flag to speed up tty layer + when reading from from the device. + (default: 0; nonzero enables) + + See http://www.uuhaus.de/linux/palmconnect.html for up-to-date + information on this driver. + Generic Serial driver diff -Nru a/drivers/pci/pci.ids b/drivers/pci/pci.ids --- a/drivers/pci/pci.ids Wed Dec 19 10:26:02 2001 +++ b/drivers/pci/pci.ids Wed Dec 19 10:26:02 2001 @@ -812,6 +812,7 @@ 0074 56k Voice Modem 1033 8014 RCV56ACF 56k Voice Modem 009b Vrc5476 + 00e0 USB 2.0 1034 Framatome Connectors USA Inc. 1035 Comp. & Comm. Research Lab 1036 Future Domain Corp. @@ -5417,6 +5418,7 @@ 03 USB Controller 00 UHCI 10 OHCI + 20 EHCI 80 Unspecified fe USB Device 04 Fibre Channel diff -Nru a/drivers/usb/Config.in b/drivers/usb/Config.in --- a/drivers/usb/Config.in Wed Dec 19 10:26:02 2001 +++ b/drivers/usb/Config.in Wed Dec 19 10:26:02 2001 @@ -18,7 +18,8 @@ bool ' Long timeout for slow-responding devices (some MGE Ellipse UPSes)' CONFIG_USB_LONG_TIMEOUT fi -comment 'USB Controllers' +comment 'USB Host Controller Drivers' +source drivers/usb/hcd/Config.in if [ "$CONFIG_USB_UHCI_ALT" != "y" ]; then dep_tristate ' UHCI (Intel PIIX4, VIA, ...) support' CONFIG_USB_UHCI $CONFIG_USB fi @@ -72,6 +73,8 @@ dep_tristate ' USB OV511 Camera support' CONFIG_USB_OV511 $CONFIG_USB $CONFIG_VIDEO_DEV dep_tristate ' USB Philips Cameras' CONFIG_USB_PWC $CONFIG_USB $CONFIG_VIDEO_DEV dep_tristate ' USB SE401 Camera support' CONFIG_USB_SE401 $CONFIG_USB $CONFIG_VIDEO_DEV + dep_tristate ' USB STV680 (Pencam) Camera support' CONFIG_USB_STV680 $CONFIG_USB $CONFIG_VIDEO_DEV + dep_tristate ' USB 3com HomeConnect (aka vicam) support (EXPERIMENTAL)' CONFIG_USB_VICAM $CONFIG_USB $CONFIG_VIDEO_DEV $CONFIG_EXPERIMENTAL dep_tristate ' D-Link USB FM radio support (EXPERIMENTAL)' CONFIG_USB_DSBR $CONFIG_USB $CONFIG_VIDEO_DEV $CONFIG_EXPERIMENTAL dep_tristate ' DABUSB driver' CONFIG_USB_DABUSB $CONFIG_USB fi diff -Nru a/drivers/usb/Makefile b/drivers/usb/Makefile --- a/drivers/usb/Makefile Wed Dec 19 10:26:02 2001 +++ b/drivers/usb/Makefile Wed Dec 19 10:26:02 2001 @@ -10,12 +10,12 @@ # Objects that export symbols. -export-objs := usb.o +export-objs := usb.o hcd.o # Multipart objects. list-multi := usbcore.o hid.o -usbcore-objs := usb.o usb-debug.o hub.o +usbcore-objs := usb.o usb-debug.o hub.o hcd.o hid-objs := hid-core.o hid-input.o ifneq ($(CONFIG_USB_PWC),n) @@ -65,8 +65,10 @@ obj-$(CONFIG_USB_MDC800) += mdc800.o obj-$(CONFIG_USB_USS720) += uss720.o obj-$(CONFIG_USB_DABUSB) += dabusb.o +obj-$(CONFIG_USB_VICAM) += vicam.o obj-$(CONFIG_USB_OV511) += ov511.o obj-$(CONFIG_USB_SE401) += se401.o +obj-$(CONFIG_USB_STV680) += stv680.o obj-$(CONFIG_USB_PEGASUS) += pegasus.o obj-$(CONFIG_USB_CATC) += catc.o obj-$(CONFIG_USB_KAWETH) += kaweth.o @@ -79,7 +81,9 @@ obj-$(CONFIG_USB_USBNET) += usbnet.o # Object files in subdirectories +mod-subdirs := serial hcd +subdir-$(CONFIG_EHCI_HCD) += hcd subdir-$(CONFIG_USB_SERIAL) += serial subdir-$(CONFIG_USB_STORAGE) += storage diff -Nru a/drivers/usb/catc.c b/drivers/usb/catc.c --- a/drivers/usb/catc.c Wed Dec 19 10:26:03 2001 +++ b/drivers/usb/catc.c Wed Dec 19 10:26:03 2001 @@ -38,7 +38,9 @@ #include #include #include +#include #include +#include #undef DEBUG @@ -48,9 +50,10 @@ * Version information. */ -#define DRIVER_VERSION "v2.7" +#define DRIVER_VERSION "v2.8" #define DRIVER_AUTHOR "Vojtech Pavlik " #define DRIVER_DESC "CATC EL1210A NetMate USB Ethernet driver" +#define SHORT_DRIVER_DESC "EL1210A NetMate USB Ethernet" MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); @@ -259,11 +262,15 @@ } } - if (data[1] & 0x40) + if (data[1] & 0x40) { + netif_carrier_on(catc->netdev); dbg("link ok"); + } - if (data[1] & 0x20) + if (data[1] & 0x20) { + netif_carrier_off(catc->netdev); dbg("link bad"); + } } /* @@ -564,6 +571,54 @@ } /* + * ioctl's + */ +static int netdev_ethtool_ioctl(struct net_device *dev, void *useraddr) +{ + struct catc *catc = dev->priv; + u32 cmd; + char tmp[40]; + + if (get_user(cmd, (u32 *)useraddr)) + return -EFAULT; + + switch (cmd) { + /* get driver info */ + case ETHTOOL_GDRVINFO: { + struct ethtool_drvinfo info = {ETHTOOL_GDRVINFO}; + strncpy(info.driver, SHORT_DRIVER_DESC, ETHTOOL_BUSINFO_LEN); + strncpy(info.version, DRIVER_VERSION, ETHTOOL_BUSINFO_LEN); + sprintf(tmp, "usb%d:%d", catc->usbdev->bus->busnum, catc->usbdev->devnum); + strncpy(info.bus_info, tmp,ETHTOOL_BUSINFO_LEN); + if (copy_to_user(useraddr, &info, sizeof(info))) + return -EFAULT; + return 0; + } + /* get link status */ + case ETHTOOL_GLINK: { + struct ethtool_value edata = {ETHTOOL_GLINK}; + edata.data = netif_carrier_ok(dev); + if (copy_to_user(useraddr, &edata, sizeof(edata))) + return -EFAULT; + return 0; + } + } + + return -EOPNOTSUPP; +} + +static int catc_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + switch(cmd) { + case SIOCETHTOOL: + return netdev_ethtool_ioctl(dev, (void *) rq->ifr_data); + default: + return -EOPNOTSUPP; + } +} + + +/* * Open, close. */ @@ -629,6 +684,7 @@ netdev->tx_timeout = catc_tx_timeout; netdev->watchdog_timeo = TX_TIMEOUT; netdev->set_multicast_list = catc_set_multicast_list; + netdev->do_ioctl = catc_ioctl; netdev->priv = catc; catc->usbdev = usbdev; diff -Nru a/drivers/usb/devio.c b/drivers/usb/devio.c --- a/drivers/usb/devio.c Wed Dec 19 10:26:02 2001 +++ b/drivers/usb/devio.c Wed Dec 19 10:26:02 2001 @@ -471,9 +471,7 @@ */ lock_kernel(); ret = -ENOENT; - if (ITYPE(inode->i_ino) != IDEVICE) - goto out; - dev = inode->u.usbdev_i.p.dev; + dev = inode->u.generic_ip; if (!dev) goto out; ret = -ENOMEM; diff -Nru a/drivers/usb/hcd/Config.in b/drivers/usb/hcd/Config.in --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/Config.in Wed Dec 19 10:26:03 2001 @@ -0,0 +1,7 @@ +# +# USB Host Controller Drivers +# +dep_tristate ' EHCI HCD (USB 2.0) support (EXPERIMENTAL)' CONFIG_EHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL +# dep_tristate ' OHCI HCD support (EXPERIMENTAL)' CONFIG_OHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL +# dep_tristate ' UHCI HCD (most Intel and VIA) support (EXPERIMENTAL)' CONFIG_UHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL + diff -Nru a/drivers/usb/hcd/Makefile b/drivers/usb/hcd/Makefile --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/Makefile Wed Dec 19 10:26:03 2001 @@ -0,0 +1,27 @@ +# +# Makefile for USB Host Controller Driver +# framework and drivers +# + +O_TARGET := + +obj-$(CONFIG_EHCI_HCD) += ehci-hcd.o +# obj-$(CONFIG_OHCI_HCD) += ohci-hcd.o +# obj-$(CONFIG_UHCI_HCD) += uhci-hcd.o + +# Extract lists of the multi-part drivers. +# The 'int-*' lists are the intermediate files used to build the multi's. +multi-y := $(filter $(list-multi), $(obj-y)) +multi-m := $(filter $(list-multi), $(obj-m)) +int-y := $(sort $(foreach m, $(multi-y), $($(basename $(m))-objs))) +int-m := $(sort $(foreach m, $(multi-m), $($(basename $(m))-objs))) + +# Take multi-part drivers out of obj-y and put components in. +obj-y := $(filter-out $(list-multi), $(obj-y)) $(int-y) + +# Translate to Rules.make lists. +OX_OBJS := $(obj-y) +MX_OBJS := $(obj-m) +MIX_OBJS := $(int-m) + +include $(TOPDIR)/Rules.make diff -Nru a/drivers/usb/hcd/ehci-dbg.c b/drivers/usb/hcd/ehci-dbg.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-dbg.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +#ifdef EHCI_VERBOSE_DEBUG +# define vdbg dbg +#else + static inline void vdbg (char *fmt, ...) { } +#endif + +#ifdef DEBUG + +/* check the values in the HCSPARAMS register - host controller structural parameters */ +/* see EHCI 0.95 Spec, Table 2-4 for each value */ +static void dbg_hcs_params (struct ehci_hcd *ehci, char *label) +{ + u32 params = readl (&ehci->caps->hcs_params); + + dbg ("%s hcs_params 0x%x dbg=%d%s cc=%d pcc=%d%s%s ports=%d", + label, params, + HCS_DEBUG_PORT (params), + HCS_INDICATOR (params) ? " ind" : "", + HCS_N_CC (params), + HCS_N_PCC (params), + HCS_PORTROUTED (params) ? "" : " ordered", + HCS_PPC (params) ? "" : " !ppc", + HCS_N_PORTS (params) + ); + /* Port routing, per EHCI 0.95 Spec, Section 2.2.5 */ + if (HCS_PORTROUTED (params)) { + int i; + char buf [46], tmp [7], byte; + + buf[0] = 0; + for (i = 0; i < HCS_N_PORTS (params); i++) { + byte = readb (&ehci->caps->portroute[(i>>1)]); + sprintf(tmp, "%d ", + ((i & 0x1) ? ((byte)&0xf) : ((byte>>4)&0xf))); + strcat(buf, tmp); + } + dbg ("%s: %s portroute %s", + ehci->hcd.bus_name, label, + buf); + } +} +#else + +static inline void dbg_hcs_params (struct ehci_hcd *ehci, char *label) {} + +#endif + +#ifdef DEBUG + +/* check the values in the HCCPARAMS register - host controller capability parameters */ +/* see EHCI 0.95 Spec, Table 2-5 for each value */ +static void dbg_hcc_params (struct ehci_hcd *ehci, char *label) +{ + u32 params = readl (&ehci->caps->hcc_params); + + if (HCC_EXT_CAPS (params)) { + // EHCI 0.96 ... could interpret these (legacy?) + dbg ("%s extended capabilities at pci %d", + label, HCC_EXT_CAPS (params)); + } + if (HCC_ISOC_CACHE (params)) { + dbg ("%s hcc_params 0x%04x caching frame %s%s%s", + label, params, + HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024", + HCC_CANPARK (params) ? " park" : "", + HCC_64BIT_ADDR (params) ? " 64 bit addr" : ""); + } else { + dbg ("%s hcc_params 0x%04x caching %d uframes %s%s%s", + label, + params, + HCC_ISOC_THRES (params), + HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024", + HCC_CANPARK (params) ? " park" : "", + HCC_64BIT_ADDR (params) ? " 64 bit addr" : ""); + } +} +#else + +static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {} + +#endif + +#ifdef DEBUG + +#if 0 +static void dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + dbg ("%s %p info1 %x info2 %x hw_curr %x qtd_next %x", label, + qh, qh->hw_info1, qh->hw_info2, + qh->hw_current, qh->hw_qtd_next); + dbg (" alt+errs= %x, token= %x, page0= %x, page1= %x", + qh->hw_alt_next, qh->hw_token, + qh->hw_buf [0], qh->hw_buf [1]); + if (qh->hw_buf [2]) { + dbg (" page2= %x, page3= %x, page4= %x", + qh->hw_buf [2], qh->hw_buf [3], + qh->hw_buf [4]); + } +} +#endif + +static const char *const fls_strings [] = + { "1024", "512", "256", "??" }; + +#else +#if 0 +static inline void dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) {} +#endif +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ + +#define dbg_status(ehci, label, status) \ + dbg ("%s status 0x%x%s%s%s%s%s%s%s%s%s%s", \ + label, status, \ + (status & STS_ASS) ? " Async" : "", \ + (status & STS_PSS) ? " Periodic" : "", \ + (status & STS_RECL) ? " Recl" : "", \ + (status & STS_HALT) ? " Halt" : "", \ + (status & STS_IAA) ? " IAA" : "", \ + (status & STS_FATAL) ? " FATAL" : "", \ + (status & STS_FLR) ? " FLR" : "", \ + (status & STS_PCD) ? " PCD" : "", \ + (status & STS_ERR) ? " ERR" : "", \ + (status & STS_INT) ? " INT" : "" \ + ) + +#define dbg_cmd(ehci, label, command) \ + dbg ("%s %x cmd %s=%d ithresh=%d%s%s%s%s period=%s%s %s", \ + label, command, \ + (command & CMD_PARK) ? "park" : "(park)", \ + CMD_PARK_CNT (command), \ + (command >> 16) & 0x3f, \ + (command & CMD_LRESET) ? " LReset" : "", \ + (command & CMD_IAAD) ? " IAAD" : "", \ + (command & CMD_ASE) ? " Async" : "", \ + (command & CMD_PSE) ? " Periodic" : "", \ + fls_strings [(command >> 2) & 0x3], \ + (command & CMD_RESET) ? " Reset" : "", \ + (command & CMD_RUN) ? "RUN" : "HALT" \ + ) + +#define dbg_port(hcd, label, port, status) \ + dbg ("%s port %d status 0x%x%s%s speed=%d%s%s%s%s%s%s%s%s%s", \ + label, port, status, \ + (status & PORT_OWNER) ? " OWNER" : "", \ + (status & PORT_POWER) ? " POWER" : "", \ + (status >> 10) & 3, \ + (status & PORT_RESET) ? " RESET" : "", \ + (status & PORT_SUSPEND) ? " SUSPEND" : "", \ + (status & PORT_RESUME) ? " RESUME" : "", \ + (status & PORT_OCC) ? " OCC" : "", \ + (status & PORT_OC) ? " OC" : "", \ + (status & PORT_PEC) ? " PEC" : "", \ + (status & PORT_PE) ? " PE" : "", \ + (status & PORT_CSC) ? " CSC" : "", \ + (status & PORT_CONNECT) ? " CONNECT" : "" \ + ) + diff -Nru a/drivers/usb/hcd/ehci-hcd.c b/drivers/usb/hcd/ehci-hcd.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-hcd.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,758 @@ +/* + * Copyright (c) 2000-2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CONFIG_USB_DEBUG + #define CONFIG_USB_DEBUG /* this is still experimental! */ +#endif + +#ifdef CONFIG_USB_DEBUG + #define DEBUG +#else + #undef DEBUG +#endif + +#include +#include "../hcd.h" + +#include +#include +#include +#include + +//#undef KERN_DEBUG +//#define KERN_DEBUG "" + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI hc_driver implementation ... experimental, incomplete. + * Based on the 0.96 register interface specification. + * + * There are lots of things to help out with here ... notably + * everything "periodic", and of course testing with all sorts + * of usb 2.0 devices and configurations. + * + * USB 2.0 shows up in upcoming www.pcmcia.org technology. + * First was PCMCIA, like ISA; then CardBus, which is PCI. + * Next comes "CardBay", using USB 2.0 signals. + * + * Contains additional contributions by: + * Brad Hards + * Rory Bolt + * ... + */ + +#define DRIVER_VERSION "$Revision: 0.25 $" +#define DRIVER_AUTHOR "David Brownell" +#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver" + + +// #define EHCI_VERBOSE_DEBUG +// #define have_iso + +#ifdef DEBUG +# define EHCI_SLAB_FLAGS (SLAB_POISON) +#else +# define EHCI_SLAB_FLAGS 0 +#endif + +/* magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 0 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 + +/* Initial IRQ latency: lower than default */ +static int log2_irq_thresh = 0; // 0 to 6 +MODULE_PARM (log2_irq_thresh, "i"); +MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +/* Some A steppings of the NEC controller need soft retries */ +//#define EHCI_SOFT_RETRIES 5 /* after CERR-induced fault */ + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_ERR | STS_INT) + +/*-------------------------------------------------------------------------*/ + +#include "ehci.h" +#include "ehci-dbg.c" + +/*-------------------------------------------------------------------------*/ + +/* + * hc states include: unknown, halted, ready, running + * transitional states are messy just now + * trying to avoid "running" unless urbs are active + * a "ready" hc can be finishing prefetched work + */ + +/* halt a non-running controller */ +static void ehci_reset (struct ehci_hcd *ehci) +{ + u32 command = readl (&ehci->regs->command); + + command |= CMD_RESET; + dbg_cmd (ehci, "reset", command); + writel (command, &ehci->regs->command); + while (readl (&ehci->regs->command) & CMD_RESET) + continue; + ehci->hcd.state = USB_STATE_HALT; +} + +/* idle the controller (from running) */ +static void ehci_ready (struct ehci_hcd *ehci) +{ + u32 command; + +#ifdef DEBUG + if (!HCD_IS_RUNNING (ehci->hcd.state)) + BUG (); +#endif + + while (!(readl (&ehci->regs->status) & (STS_ASS | STS_PSS))) + udelay (100); + command = readl (&ehci->regs->command); + command &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); + writel (command, &ehci->regs->command); + + // hardware can take 16 microframes to turn off ... + ehci->hcd.state = USB_STATE_READY; +} + +/*-------------------------------------------------------------------------*/ + +#include "ehci-hub.c" +#include "ehci-mem.c" +#include "ehci-q.c" +#include "ehci-sched.c" + +/*-------------------------------------------------------------------------*/ + +static void ehci_tasklet (unsigned long param); + +/* called by khubd or root hub init threads */ + +static int ehci_start (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 temp; + struct usb_device *udev; + int retval; + u32 hcc_params; + u8 tempbyte; + + // FIXME: given EHCI 0.96 or later, and a controller with + // the USBLEGSUP/USBLEGCTLSTS extended capability, make sure + // the BIOS doesn't still own this controller. + + spin_lock_init (&ehci->lock); + + ehci->caps = (struct ehci_caps *) hcd->regs; + ehci->regs = (struct ehci_regs *) (hcd->regs + ehci->caps->length); + dbg_hcs_params (ehci, "ehci_start"); + dbg_hcc_params (ehci, "ehci_start"); + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + ehci->periodic_size = DEFAULT_I_TDPS; + if ((retval = ehci_mem_init (ehci, EHCI_SLAB_FLAGS | SLAB_KERNEL)) < 0) + return retval; + hcc_params = readl (&ehci->caps->hcc_params); + + /* controllers may cache some of the periodic schedule ... */ + if (HCC_ISOC_CACHE (hcc_params)) // full frame cache + ehci->i_thresh = 8; + else // N microframes cached + ehci->i_thresh = 2 + HCC_ISOC_THRES (hcc_params); + + ehci->async = 0; + ehci->reclaim = 0; + ehci->next_frame = -1; + + /* controller state: unknown --> reset */ + + /* EHCI spec section 4.1 */ + ehci_reset (ehci); + writel (INTR_MASK, &ehci->regs->intr_enable); + writel (ehci->periodic_dma, &ehci->regs->frame_list); + + /* + * hcc_params controls whether ehci->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * By default, pci_alloc_consistent() won't hand out addresses + * above 4GB (via pdev->dma_mask) so we know this value. + * + * NOTE: that pdev->dma_mask setting means that all DMA mappings + * for I/O buffers will have the same restriction, though it's + * neither necessary nor desirable in that case. + */ + if (HCC_64BIT_ADDR (hcc_params)) { + writel (0, &ehci->regs->segment); + info ("using segment 0 for 64bit DMA addresses ..."); + } + + /* clear interrupt enables, set irq latency */ + temp = readl (&ehci->regs->command) & 0xff; + if (log2_irq_thresh < 0 || log2_irq_thresh > 6) + log2_irq_thresh = 0; + temp |= 1 << (16 + log2_irq_thresh); + // keeping default periodic framelist size + temp &= ~(CMD_IAAD | CMD_ASE | CMD_PSE), + writel (temp, &ehci->regs->command); + dbg_cmd (ehci, "init", temp); + + /* set async sleep time = 10 us ... ? */ + + ehci->tasklet.func = ehci_tasklet; + ehci->tasklet.data = (unsigned long) ehci; + + /* wire up the root hub */ + hcd->bus->root_hub = udev = usb_alloc_dev (NULL, hcd->bus); + if (!udev) { +done2: + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* + * Start, enabling full USB 2.0 functionality ... usb 1.1 devices + * are explicitly handed to companion controller(s), so no TT is + * involved with the root hub. + */ + ehci->hcd.state = USB_STATE_READY; + writel (FLAG_CF, &ehci->regs->configured_flag); + readl (&ehci->regs->command); /* unblock posted write */ + + /* PCI Serial Bus Release Number is at 0x60 offset */ + pci_read_config_byte(hcd->pdev, 0x60, &tempbyte); + temp = readw (&ehci->caps->hci_version); + info ("USB %x.%x support enabled, EHCI rev %x.%2x", + ((tempbyte & 0xf0)>>4), + (tempbyte & 0x0f), + temp >> 8, + temp & 0xff); + + /* + * From here on, khubd concurrently accesses the root + * hub; drivers will be talking to enumerated devices. + * + * Before this point the HC was idle/ready. After, khubd + * and device drivers may start it running. + */ + usb_connect (udev); + udev->speed = USB_SPEED_HIGH; + if (usb_new_device (udev) != 0) { + if (hcd->state == USB_STATE_RUNNING) + ehci_ready (ehci); + while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS)) + udelay (100); + ehci_reset (ehci); + // usb_disconnect (udev); + hcd->bus->root_hub = 0; + usb_free_dev (udev); + retval = -ENODEV; + goto done2; + } + + return 0; +} + +/* always called by thread; normally rmmod */ + +static void ehci_stop (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + + dbg ("%s: stop", hcd->bus_name); + + if (hcd->state == USB_STATE_RUNNING) + ehci_ready (ehci); + while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS)) + udelay (100); + ehci_reset (ehci); + + // root hub is shut down separately (first, when possible) + scan_async (ehci); + if (ehci->next_frame != -1) + scan_periodic (ehci); + ehci_mem_cleanup (ehci); + + dbg_status (ehci, "ehci_stop completed", readl (&ehci->regs->status)); +} + +static int ehci_get_frame (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + return (readl (&ehci->regs->frame_index) >> 3) % ehci->periodic_size; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +/* suspend/resume, section 4.3 */ + +static int ehci_suspend (struct usb_hcd *hcd, u32 state) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 params; + int ports; + int i; + + dbg ("%s: suspend to %d", hcd->bus_name, state); + + params = readl (&ehci->caps->hcs_params); + ports = HCS_N_PORTS (params); + + // FIXME: This assumes what's probably a D3 level suspend... + + // FIXME: usb wakeup events on this bus should resume the machine. + // pci config register PORTWAKECAP controls which ports can do it; + // bios may have initted the register... + + /* suspend each port, then stop the hc */ + for (i = 0; i < ports; i++) { + int temp = readl (&ehci->regs->port_status [i]); + + if ((temp & PORT_PE) == 0 + || (temp & PORT_OWNER) != 0) + continue; +dbg ("%s: suspend port %d", hcd->bus_name, i); + temp |= PORT_SUSPEND; + writel (temp, &ehci->regs->port_status [i]); + } + + if (hcd->state == USB_STATE_RUNNING) + ehci_ready (ehci); + while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS)) + udelay (100); + writel (readl (&ehci->regs->command) & ~CMD_RUN, &ehci->regs->command); + +// save pci FLADJ value + + /* who tells PCI to reduce power consumption? */ + + return 0; +} + +static int ehci_resume (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 params; + int ports; + int i; + + dbg ("%s: resume", hcd->bus_name); + + params = readl (&ehci->caps->hcs_params); + ports = HCS_N_PORTS (params); + + // FIXME: if controller didn't retain state, + // return and let generic code clean it up + // test configured_flag ? + + /* resume HC and each port */ +// restore pci FLADJ value + // khubd and drivers will set HC running, if needed; + hcd->state = USB_STATE_READY; + for (i = 0; i < ports; i++) { + int temp = readl (&ehci->regs->port_status [i]); + + if ((temp & PORT_PE) == 0 + || (temp & PORT_SUSPEND) != 0) + continue; +dbg ("%s: resume port %d", hcd->bus_name, i); + temp |= PORT_RESUME; + writel (temp, &ehci->regs->port_status [i]); + readl (&ehci->regs->command); /* unblock posted writes */ + + wait_ms (20); + temp &= ~PORT_RESUME; + writel (temp, &ehci->regs->port_status [i]); + } + readl (&ehci->regs->command); /* unblock posted writes */ + return 0; +} + +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * tasklet scheduled by some interrupts and other events + * calls driver completion functions ... but not in_irq() + */ +static void ehci_tasklet (unsigned long param) +{ + struct ehci_hcd *ehci = (struct ehci_hcd *) param; + + if (ehci->reclaim_ready) + end_unlink_async (ehci); + scan_async (ehci); + if (ehci->next_frame != -1) + scan_periodic (ehci); + + // FIXME: when nothing is connected to the root hub, + // turn off the RUN bit so the host can enter C3 "sleep" power + // saving mode; make root hub code scan memory less often. +} + +/*-------------------------------------------------------------------------*/ + +static void ehci_irq (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 status = readl (&ehci->regs->status); + int bh = 0; + + /* clear (just) interrupts */ + status &= INTR_MASK; + writel (status, &ehci->regs->status); + readl (&ehci->regs->command); /* unblock posted write */ + + if (unlikely (hcd->state == USB_STATE_HALT)) /* irq sharing? */ + return; + +#ifdef EHCI_VERBOSE_DEBUG + /* unrequested/ignored: Port Change Detect, Frame List Rollover */ + if (status & INTR_MASK) + dbg_status (ehci, "irq", status); +#endif + + /* INT, ERR, and IAA interrupt rates can be throttled */ + + /* normal [4.15.1.2] or error [4.15.1.1] completion */ + if (likely ((status & (STS_INT|STS_ERR)) != 0)) + bh = 1; + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + ehci->reclaim_ready = 1; + bh = 1; + } + + /* PCI errors [4.15.2.4] */ + if (unlikely ((status & STS_FATAL) != 0)) { + err ("%s: fatal error, state %x", hcd->bus_name, hcd->state); + ehci_reset (ehci); + // generic layer kills/unlinks all urbs + // then tasklet cleans up the rest + bh = 1; + } + + /* most work doesn't need to be in_irq() */ + if (likely (bh == 1)) + tasklet_schedule (&ehci->tasklet); +} + +/*-------------------------------------------------------------------------*/ + +/* + * non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + * + * urb + dev is in hcd_dev.urb_list + * we're queueing TDs onto software and hardware lists + * + * hcd-specific init for hcpriv hasn't been done yet + * + * NOTE: EHCI queues control and bulk requests transparently, like OHCI. + */ +static int ehci_urb_enqueue ( + struct usb_hcd *hcd, + struct urb *urb, + int mem_flags +) { + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + struct list_head qtd_list; + + urb->transfer_flags &= ~EHCI_STATE_UNLINK; + INIT_LIST_HEAD (&qtd_list); + switch (usb_pipetype (urb->pipe)) { + + case PIPE_CONTROL: + case PIPE_BULK: + if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) + return -ENOMEM; + submit_async (ehci, urb, &qtd_list, mem_flags); + break; + + case PIPE_INTERRUPT: + if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit (ehci, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: +#ifdef have_iso + if (urb->dev->speed == USB_SPEED_HIGH) + return itd_submit (ehci, urb); + else + return sitd_submit (ehci, urb); +#else + // FIXME highspeed iso stuff is written but never run/tested. + // and the split iso support isn't even written yet. + dbg ("no iso support yet"); + return -ENOSYS; +#endif /* have_iso */ + + } + return 0; +} + +/* remove from hardware lists + * completions normally happen asynchronously + */ + +static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; + unsigned long flags; + + dbg ("%s urb_dequeue %p qh state %d", + hcd->bus_name, urb, qh->qh_state); + + switch (usb_pipetype (urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + spin_lock_irqsave (&ehci->lock, flags); + if (ehci->reclaim) { +dbg ("dq: reclaim busy, %s", RUN_CONTEXT); + if (in_interrupt ()) { + spin_unlock_irqrestore (&ehci->lock, flags); + return -EAGAIN; + } + while (qh->qh_state == QH_STATE_LINKED + && ehci->reclaim + && ehci->hcd.state != USB_STATE_HALT + ) { + spin_unlock_irqrestore (&ehci->lock, flags); +// yeech ... this could spin for up to two frames! +dbg ("wait for dequeue: state %d, reclaim %p, hcd state %d", + qh->qh_state, ehci->reclaim, ehci->hcd.state +); + udelay (100); + spin_lock_irqsave (&ehci->lock, flags); + } + } + if (qh->qh_state == QH_STATE_LINKED) + start_unlink_async (ehci, qh); + spin_unlock_irqrestore (&ehci->lock, flags); + return 0; + + case PIPE_INTERRUPT: + intr_deschedule (ehci, urb->start_frame, qh, urb->interval); + if (ehci->hcd.state == USB_STATE_HALT) + urb->status = -ESHUTDOWN; + qh_completions (ehci, &qh->qtd_list, 1); + return 0; + + case PIPE_ISOCHRONOUS: + // itd or sitd ... + + // wait till next completion, do it then. + // completion irqs can wait up to 128 msec, + urb->transfer_flags |= EHCI_STATE_UNLINK; + return 0; + } + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ + +// bulk qh holds the data toggle + +static void ehci_free_config (struct usb_hcd *hcd, struct usb_device *udev) +{ + struct hcd_dev *dev = (struct hcd_dev *)udev->hcpriv; + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + int i; + unsigned long flags; + + /* ASSERT: nobody can be submitting urbs for this any more */ + + dbg ("%s: free_config devnum %d", hcd->bus_name, udev->devnum); + + spin_lock_irqsave (&ehci->lock, flags); + for (i = 0; i < 32; i++) { + if (dev->ep [i]) { + struct ehci_qh *qh; + + // FIXME: this might be an itd/sitd too ... + // or an interrupt urb (not on async list) + // can use "union ehci_shadow" + + qh = (struct ehci_qh *) dev->ep [i]; + vdbg ("free_config, ep 0x%02x qh %p", i, qh); + if (!list_empty (&qh->qtd_list)) { + dbg ("ep 0x%02x qh %p not empty!", i, qh); + BUG (); + } + dev->ep [i] = 0; + + /* wait_ms() won't spin here -- we're a thread */ + while (qh->qh_state == QH_STATE_LINKED + && ehci->reclaim + && ehci->hcd.state != USB_STATE_HALT + ) { + spin_unlock_irqrestore (&ehci->lock, flags); + wait_ms (1); + spin_lock_irqsave (&ehci->lock, flags); + } + if (qh->qh_state == QH_STATE_LINKED) { + start_unlink_async (ehci, qh); + while (qh->qh_state != QH_STATE_IDLE) { + spin_unlock_irqrestore (&ehci->lock, + flags); + wait_ms (1); + spin_lock_irqsave (&ehci->lock, flags); + } + } + qh_unput (ehci, qh); + } + } + + spin_unlock_irqrestore (&ehci->lock, flags); +} + +/*-------------------------------------------------------------------------*/ + +static const char hcd_name [] = "ehci-hcd"; + +static const struct hc_driver ehci_driver = { + description: hcd_name, + + /* + * generic hardware linkage + */ + irq: ehci_irq, + flags: HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + start: ehci_start, +#ifdef CONFIG_PM + suspend: ehci_suspend, + resume: ehci_resume, +#endif + stop: ehci_stop, + + /* + * memory lifecycle (except per-request) + */ + hcd_alloc: ehci_hcd_alloc, + hcd_free: ehci_hcd_free, + + /* + * managing i/o requests and associated device resources + */ + urb_enqueue: ehci_urb_enqueue, + urb_dequeue: ehci_urb_dequeue, + free_config: ehci_free_config, + + /* + * scheduling support + */ + get_frame_number: ehci_get_frame, + + /* + * root hub support + */ + hub_status_data: ehci_hub_status_data, + hub_control: ehci_hub_control, +}; + +/*-------------------------------------------------------------------------*/ + +/* EHCI spec says PCI is required. */ + +/* PCI driver selection metadata; PCI hotplugging uses this */ +static const struct pci_device_id __devinitdata pci_ids [] = { { + + /* handle any USB 2.0 EHCI controller */ + + class: ((PCI_CLASS_SERIAL_USB << 8) | 0x20), + class_mask: ~0, + driver_data: (unsigned long) &ehci_driver, + + /* no matter who makes it */ + vendor: PCI_ANY_ID, + device: PCI_ANY_ID, + subvendor: PCI_ANY_ID, + subdevice: PCI_ANY_ID, + +}, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE (pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver ehci_pci_driver = { + name: (char *) hcd_name, + id_table: pci_ids, + + probe: usb_hcd_pci_probe, + remove: usb_hcd_pci_remove, + +#ifdef CONFIG_PM + suspend: usb_hcd_pci_suspend, + resume: usb_hcd_pci_resume, +#endif +}; + +#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC + +EXPORT_NO_SYMBOLS; +MODULE_DESCRIPTION (DRIVER_INFO); +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_LICENSE ("GPL"); + +static int __init init (void) +{ + dbg (DRIVER_INFO); + dbg ("block sizes: qh %d qtd %d itd %d sitd %d", + sizeof (struct ehci_qh), sizeof (struct ehci_qtd), + sizeof (struct ehci_itd), sizeof (struct ehci_sitd)); + + return pci_module_init (&ehci_pci_driver); +} +module_init (init); + +static void __exit cleanup (void) +{ + pci_unregister_driver (&ehci_pci_driver); +} +module_exit (cleanup); diff -Nru a/drivers/usb/hcd/ehci-hub.c b/drivers/usb/hcd/ehci-hub.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-hub.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Root Hub ... the nonsharable stuff + * + * Registers don't need cpu_to_le32, that happens transparently + */ + +/*-------------------------------------------------------------------------*/ + +static int check_reset_complete ( + struct ehci_hcd *ehci, + int index, + int port_status +) { + if (!(port_status & PORT_CONNECT)) { + ehci->reset_done [index] = 0; + return port_status; + } + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + dbg ("%s port %d full speed, give to companion, 0x%x", + ehci->hcd.bus_name, index + 1, port_status); + + // what happens if HCS_N_CC(params) == 0 ? + port_status |= PORT_OWNER; + writel (port_status, &ehci->regs->port_status [index]); + + } else + dbg ("%s port %d high speed", ehci->hcd.bus_name, index + 1); + + return port_status; +} + +/*-------------------------------------------------------------------------*/ + + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int +ehci_hub_status_data (struct usb_hcd *hcd, char *buf) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 temp, status = 0; + int ports, i, retval = 1; + unsigned long flags; + + /* init status to no-changes */ + buf [0] = 0; + temp = readl (&ehci->caps->hcs_params); + ports = HCS_N_PORTS (temp); + if (ports > 7) { + buf [1] = 0; + retval++; + } + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave (&ehci->lock, flags); + for (i = 0; i < ports; i++) { + temp = readl (&ehci->regs->port_status [i]); + if (temp & PORT_OWNER) { + // get disconnected ports back if no companion driver + if (temp & PORT_CONNECT) + continue; + temp &= ~(PORT_OWNER|PORT_CSC); + writel (temp, &ehci->regs->port_status [i]); + } + if (!(temp & PORT_CONNECT)) + ehci->reset_done [i] = 0; + if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0) { + set_bit (i, buf); + status = STS_PCD; + } + } + spin_unlock_irqrestore (&ehci->lock, flags); + return status ? retval : 0; +} + +/*-------------------------------------------------------------------------*/ + +static void +ehci_hub_descriptor ( + struct ehci_hcd *ehci, + struct usb_hub_descriptor *desc +) { + u32 params = readl (&ehci->caps->hcs_params); + int ports = HCS_N_PORTS (params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 0; /* FIXME: f(system power) */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset (&desc->bitmap [0], 0, temp); + memset (&desc->bitmap [temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + if (HCS_PPC (params)) /* per-port power control */ + temp |= 0x0001; + if (HCS_INDICATOR (params)) /* per-port indicators (LEDs) */ + temp |= 0x0080; + desc->wHubCharacteristics = cpu_to_le16 (temp); +} + +/*-------------------------------------------------------------------------*/ + +static int ehci_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 params = readl (&ehci->caps->hcs_params); + int ports = HCS_N_PORTS (params); + u32 temp; + unsigned long flags; + int retval = 0; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave (&ehci->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl (&ehci->regs->port_status [wIndex]); + if (temp & PORT_OWNER) + break; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + writel (temp & ~PORT_PE, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_C_ENABLE: + writel (temp | PORT_PEC, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_SUSPEND: + case USB_PORT_FEAT_C_SUSPEND: + /* ? */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC (params)) + writel (temp & ~PORT_POWER, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_C_CONNECTION: + writel (temp | PORT_CSC, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + writel (temp | PORT_OCC, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + readl (&ehci->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset (buf, 0, 4); + //cpu_to_le32s ((u32 *) buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + memset (buf, 0, 4); + temp = readl (&ehci->regs->port_status [wIndex]); + + // wPortChange bits + if (temp & PORT_CSC) + set_bit (USB_PORT_FEAT_C_CONNECTION, buf); + if (temp & PORT_PEC) + set_bit (USB_PORT_FEAT_C_ENABLE, buf); + // USB_PORT_FEAT_C_SUSPEND + if (temp & PORT_OCC) + set_bit (USB_PORT_FEAT_C_OVER_CURRENT, buf); + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && jiffies > ehci->reset_done [wIndex]) { + set_bit (USB_PORT_FEAT_C_RESET, buf); + + /* force reset to complete */ + writel (temp & ~PORT_RESET, + &ehci->regs->port_status [wIndex]); + do { + temp = readl ( + &ehci->regs->port_status [wIndex]); + udelay (10); + } while (temp & PORT_RESET); + + /* see what we found out */ + temp = check_reset_complete (ehci, wIndex, temp); + } + + // don't show wPortStatus if it's owned by a companion hc + if (!(temp & PORT_OWNER)) { + if (temp & PORT_CONNECT) { + set_bit (USB_PORT_FEAT_CONNECTION, buf); + set_bit (USB_PORT_FEAT_HIGHSPEED, buf); + } + if (temp & PORT_PE) + set_bit (USB_PORT_FEAT_ENABLE, buf); + if (temp & PORT_SUSPEND) + set_bit (USB_PORT_FEAT_SUSPEND, buf); + if (temp & PORT_OC) + set_bit (USB_PORT_FEAT_OVER_CURRENT, buf); + if (temp & PORT_RESET) + set_bit (USB_PORT_FEAT_RESET, buf); + if (temp & PORT_POWER) + set_bit (USB_PORT_FEAT_POWER, buf); + } + +#ifndef EHCI_VERBOSE_DEBUG + if (*(u16*)(buf+2)) /* only if wPortChange is interesting */ +#endif + dbg_port (hcd, "GetStatus", wIndex + 1, temp); + cpu_to_le32s ((u32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl (&ehci->regs->port_status [wIndex]); + if (temp & PORT_OWNER) + break; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + writel (temp | PORT_SUSPEND, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC (params)) + writel (temp | PORT_POWER, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_RESET: + /* line status bits may report this as low speed */ + if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT + && PORT_USB11 (temp)) { + dbg ("%s port %d low speed, give to companion", + hcd->bus_name, wIndex + 1); + temp |= PORT_OWNER; + } else { + vdbg ("%s port %d reset", + hcd->bus_name, wIndex + 1); + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + ehci->reset_done [wIndex] = jiffies + + ((50 /* msec */ * HZ) / 1000); + } + writel (temp, &ehci->regs->port_status [wIndex]); + break; + default: + goto error; + } + readl (&ehci->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore (&ehci->lock, flags); + return retval; +} diff -Nru a/drivers/usb/hcd/ehci-mem.c b/drivers/usb/hcd/ehci-mem.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-mem.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * There's basically three types of memory: + * - data used only by the HCD ... kmalloc is fine + * - async and periodic schedules, shared by HC and HCD ... these + * need to use pci_pool or pci_alloc_consistent + * - driver buffers, read/written by HC ... single shot DMA mapped + * + * There's also PCI "register" data, which is memory mapped. + * No memory seen by this driver is pagable. + */ + +/*-------------------------------------------------------------------------*/ +/* + * Allocator / cleanup for the per device structure + * Called by hcd init / removal code + */ +static struct usb_hcd *ehci_hcd_alloc (void) +{ + struct ehci_hcd *ehci; + + ehci = (struct ehci_hcd *) + kmalloc (sizeof (struct ehci_hcd), GFP_KERNEL); + if (ehci != 0) { + memset (ehci, 0, sizeof (struct ehci_hcd)); + return &ehci->hcd; + } + return 0; +} + +static void ehci_hcd_free (struct usb_hcd *hcd) +{ + kfree (hcd_to_ehci (hcd)); +} + +/*-------------------------------------------------------------------------*/ + +/* Allocate the key transfer structures from the previously allocated pool */ + +static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, int flags) +{ + struct ehci_qtd *qtd; + dma_addr_t dma; + + qtd = pci_pool_alloc (ehci->qtd_pool, flags, &dma); + if (qtd != 0) { + memset (qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD (&qtd->qtd_list); + } + return qtd; +} + +static inline void ehci_qtd_free (struct ehci_hcd *ehci, struct ehci_qtd *qtd) +{ + pci_pool_free (ehci->qtd_pool, qtd, qtd->qtd_dma); +} + + +static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, int flags) +{ + struct ehci_qh *qh; + dma_addr_t dma; + + qh = (struct ehci_qh *) + pci_pool_alloc (ehci->qh_pool, flags, &dma); + if (qh) { + memset (qh, 0, sizeof *qh); + atomic_set (&qh->refcount, 1); + qh->qh_dma = dma; + // INIT_LIST_HEAD (&qh->qh_list); + INIT_LIST_HEAD (&qh->qtd_list); + } + return qh; +} + +/* to share a qh (cpu threads, or hc) */ +static inline struct ehci_qh *qh_put (/* ehci, */ struct ehci_qh *qh) +{ + // dbg ("put %p (%d++)", qh, qh->refcount.counter); + atomic_inc (&qh->refcount); + return qh; +} + +static void qh_unput (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + // dbg ("unput %p (--%d)", qh, qh->refcount.counter); + if (!atomic_dec_and_test (&qh->refcount)) + return; + /* clean qtds first, and know this is not linked */ + if (!list_empty (&qh->qtd_list) || qh->qh_next.ptr) { + dbg ("unused qh not empty!"); + BUG (); + } + pci_pool_free (ehci->qh_pool, qh, qh->qh_dma); +} + +/*-------------------------------------------------------------------------*/ + +/* The queue heads and transfer descriptors are managed from pools tied + * to each of the "per device" structures. + * This is the initialisation and cleanup code. + */ + +static void ehci_mem_cleanup (struct ehci_hcd *ehci) +{ + /* PCI consistent memory and pools */ + if (ehci->qtd_pool) + pci_pool_destroy (ehci->qtd_pool); + ehci->qtd_pool = 0; + + if (ehci->qh_pool) { + pci_pool_destroy (ehci->qh_pool); + ehci->qh_pool = 0; + } + + if (ehci->itd_pool) + pci_pool_destroy (ehci->itd_pool); + ehci->itd_pool = 0; + + if (ehci->sitd_pool) + pci_pool_destroy (ehci->sitd_pool); + ehci->sitd_pool = 0; + + if (ehci->periodic) + pci_free_consistent (ehci->hcd.pdev, + ehci->periodic_size * sizeof (u32), + ehci->periodic, ehci->periodic_dma); + ehci->periodic = 0; + + /* shadow periodic table */ + if (ehci->pshadow) + kfree (ehci->pshadow); + ehci->pshadow = 0; +} + +/* remember to add cleanup code (above) if you add anything here */ +static int ehci_mem_init (struct ehci_hcd *ehci, int flags) +{ + int i; + + /* QTDs for control/bulk/intr transfers */ + ehci->qtd_pool = pci_pool_create ("ehci_qtd", ehci->hcd.pdev, + sizeof (struct ehci_qtd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */, + flags); + if (!ehci->qtd_pool) { + dbg ("no qtd pool"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* QH for control/bulk/intr transfers */ + ehci->qh_pool = pci_pool_create ("ehci_qh", ehci->hcd.pdev, + sizeof (struct ehci_qh), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */, + flags); + if (!ehci->qh_pool) { + dbg ("no qh pool"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* ITD for high speed ISO transfers */ + ehci->itd_pool = pci_pool_create ("ehci_itd", ehci->hcd.pdev, + sizeof (struct ehci_itd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */, + flags); + if (!ehci->itd_pool) { + dbg ("no itd pool"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* SITD for full/low speed split ISO transfers */ + ehci->sitd_pool = pci_pool_create ("ehci_sitd", ehci->hcd.pdev, + sizeof (struct ehci_sitd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */, + flags); + if (!ehci->sitd_pool) { + dbg ("no sitd pool"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* Hardware periodic table */ + ehci->periodic = (u32 *) + pci_alloc_consistent (ehci->hcd.pdev, + ehci->periodic_size * sizeof (u32), + &ehci->periodic_dma); + if (ehci->periodic == 0) { + dbg ("no hw periodic table"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + for (i = 0; i < ehci->periodic_size; i++) + ehci->periodic [i] = EHCI_LIST_END; + + /* software shadow of hardware table */ + ehci->pshadow = kmalloc (ehci->periodic_size * sizeof (void *), + flags & ~EHCI_SLAB_FLAGS); + if (ehci->pshadow == 0) { + dbg ("no shadow periodic table"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + memset (ehci->pshadow, 0, ehci->periodic_size * sizeof (void *)); + + return 0; +} diff -Nru a/drivers/usb/hcd/ehci-q.c b/drivers/usb/hcd/ehci-q.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-q.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,949 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI hardware queue manipulation + * + * Control, bulk, and interrupt traffic all use "qh" lists. They list "qtd" + * entries describing USB transactions, max 16-20kB/entry (with 4kB-aligned + * buffers needed for the larger number). We use one QH per endpoint, queue + * multiple (bulk or control) urbs per endpoint. URBs may need several qtds. + * A scheduled interrupt qh always has one qtd, one urb. + * + * ISO traffic uses "ISO TD" (itd, and sitd) records, and (along with + * interrupts) needs careful scheduling. Performance improvements can be + * an ongoing challenge. + * + * USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, + * or otherwise through transaction translators (TTs) in USB 2.0 hubs using + * (b) special fields in qh entries or (c) split iso entries. TTs will + * buffer low/full speed data so the host collects it at high speed. + */ + +#ifdef EHCI_SOFT_RETRIES +static int soft_retries = EHCI_SOFT_RETRIES; +MODULE_PARM (soft_retries, "i"); +MODULE_PARM_DESC (soft_retries, "Number of software retries for endpoint i/o"); +#endif + +/*-------------------------------------------------------------------------*/ + +/* fill a qtd, returning how much of the buffer we were able to queue up */ + +static int +qtd_fill (struct ehci_qtd *qtd, dma_addr_t buf, size_t len, int token) +{ + int i, count; + + /* one buffer entry per 4K ... first might be short or unaligned */ + qtd->hw_buf [0] = cpu_to_le32 (buf); + count = 0x1000 - (buf & 0x0fff); /* rest of that page */ + if (likely (len < count)) /* ... iff needed */ + count = len; + else { + buf += 0x1000; + buf &= ~0x0fff; + + /* per-qtd limit: from 16K to 20K (best alignment) */ + for (i = 1; count < len && i < 5; i++) { + u64 addr = buf; + qtd->hw_buf [i] = cpu_to_le32 ((u32)addr); + qtd->hw_buf_hi [i] = cpu_to_le32 ((u32)(addr >> 32)); + buf += 0x1000; + if ((count + 0x1000) < len) + count += 0x1000; + else + count = len; + } + } + qtd->hw_token = cpu_to_le32 ((count << 16) | token); + qtd->length = count; + +#if 0 + vdbg (" qtd_fill %p, token %8x bytes %d dma %x", + qtd, le32_to_cpu (qtd->hw_token), count, qtd->hw_buf [0]); +#endif + + return count; +} + +/*-------------------------------------------------------------------------*/ + +/* update halted (but potentially linked) qh */ + +static inline void qh_update (struct ehci_qh *qh, struct ehci_qtd *qtd) +{ + qh->hw_current = 0; + qh->hw_qtd_next = QTD_NEXT (qtd->qtd_dma); + qh->hw_alt_next = EHCI_LIST_END; + + /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ + qh->hw_token &= __constant_cpu_to_le32 (QTD_TOGGLE | QTD_STS_PING); +} + +/*-------------------------------------------------------------------------*/ + +static inline void qtd_copy_status (struct urb *urb, size_t length, u32 token) +{ + /* count IN/OUT bytes, not SETUP (even short packets) */ + if (likely (QTD_PID (token) != 2)) + urb->actual_length += length - QTD_LENGTH (token); + + /* don't modify error codes */ + if (unlikely (urb->status == -EINPROGRESS && (token & QTD_STS_HALT))) { + if (token & QTD_STS_BABBLE) { + urb->status = -EOVERFLOW; + } else if (!QTD_CERR (token)) { + if (token & QTD_STS_DBE) + urb->status = (QTD_PID (token) == 1) /* IN ? */ + ? -ENOSR /* hc couldn't read data */ + : -ECOMM; /* hc couldn't write data */ + else if (token & QTD_STS_MMF) /* missed tt uframe */ + urb->status = -EPROTO; + else if (token & QTD_STS_XACT) { + if (QTD_LENGTH (token)) + urb->status = -EPIPE; + else { + dbg ("3strikes"); + urb->status = -EPROTO; + } + } else /* presumably a stall */ + urb->status = -EPIPE; + + /* CERR nonzero + data left + halt --> stall */ + } else if (QTD_LENGTH (token)) + urb->status = -EPIPE; + else /* unknown */ + urb->status = -EPROTO; + dbg ("devpath %s ep %d-%s qtd token %x --> status %d", + urb->dev->devpath, usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + token, urb->status); + + /* stall indicates some recovery action is needed */ + if (urb->status == -EPIPE) { + int pipe = urb->pipe; + + if (!usb_pipecontrol (pipe)) + usb_endpoint_halt (urb->dev, + usb_pipeendpoint (pipe), + usb_pipeout (pipe)); + if (urb->dev->tt && !usb_pipeint (pipe)) { +err ("must CLEAR_TT_BUFFER, hub %s port %d%s addr %d ep %d", + urb->dev->tt->hub->devpath, urb->dev->ttport, + urb->dev->tt->multi ? "" : " (all-ports TT)", + urb->dev->devnum, usb_pipeendpoint (urb->pipe)); + // FIXME something (khubd?) should make the hub + // CLEAR_TT_BUFFER ASAP, it's blocking other + // fs/ls requests... hub_tt_clear_buffer() ? + } + } + } +} + +static void ehci_urb_complete ( + struct ehci_hcd *ehci, + dma_addr_t addr, + struct urb *urb +) { + if (urb->transfer_buffer_length && usb_pipein (urb->pipe)) + pci_dma_sync_single (ehci->hcd.pdev, addr, + urb->transfer_buffer_length, + PCI_DMA_FROMDEVICE); + + /* cleanse status if we saw no error */ + if (likely (urb->status == -EINPROGRESS)) { + if (urb->actual_length != urb->transfer_buffer_length + && (urb->transfer_flags & USB_DISABLE_SPD)) + urb->status = -EREMOTEIO; + else + urb->status = 0; + } + + /* only report unlinks once */ + if (likely (urb->status != -ENOENT && urb->status != -ENOTCONN)) + urb->complete (urb); +} + +/* urb->lock ignored from here on (hcd is done with urb) */ + +static void ehci_urb_done ( + struct ehci_hcd *ehci, + dma_addr_t addr, + struct urb *urb +) { + if (urb->transfer_buffer_length) + pci_unmap_single (ehci->hcd.pdev, + addr, + urb->transfer_buffer_length, + usb_pipein (urb->pipe) + ? PCI_DMA_FROMDEVICE + : PCI_DMA_TODEVICE); + if (likely (urb->hcpriv != 0)) { + qh_unput (ehci, (struct ehci_qh *) urb->hcpriv); + urb->hcpriv = 0; + } + + if (likely (urb->status == -EINPROGRESS)) { + if (urb->actual_length != urb->transfer_buffer_length + && (urb->transfer_flags & USB_DISABLE_SPD)) + urb->status = -EREMOTEIO; + else + urb->status = 0; + } + + /* hand off urb ownership */ + usb_hcd_giveback_urb (&ehci->hcd, urb); +} + + +/* + * Process completed qtds for a qh, issuing completions if needed. + * When freeing: frees qtds, unmaps buf, returns URB to driver. + * When not freeing (queued periodic qh): retain qtds, mapping, and urb. + * Races up to qh->hw_current; returns number of urb completions. + */ +static int +qh_completions ( + struct ehci_hcd *ehci, + struct list_head *qtd_list, + int freeing +) { + struct ehci_qtd *qtd = 0; + struct list_head *next = 0; + u32 token; + struct ehci_qh *qh = 0; + struct urb *urb = 0; + int halted = 0; + unsigned long flags; + int retval = 0; + + spin_lock_irqsave (&ehci->lock, flags); + if (unlikely (list_empty (qtd_list))) { + spin_unlock_irqrestore (&ehci->lock, flags); + return retval; + } + + for (qtd = list_entry (qtd_list->next, struct ehci_qtd, qtd_list); + next != qtd_list; + qtd = list_entry (next, struct ehci_qtd, qtd_list)) { + token = le32_to_cpu (qtd->hw_token); + if (!qh) { + urb = qtd->urb; + qh = (struct ehci_qh *) urb->hcpriv; + } + if (likely (qh != 0)) { + halted = halted + || (ehci->hcd.state == USB_STATE_HALT) + || (qh->qh_state == QH_STATE_IDLE); + + if (unlikely ((token & QTD_STS_HALT) != 0)) { +#ifdef EHCI_SOFT_RETRIES + /* extra soft retries for protocol errors */ + if (!halted + && qh->retries < soft_retries + && (QTD_STS_HALT|QTD_STS_XACT) + == (token & 0xff) + && QTD_CERR (token) == 0) { + if (qh->retries == 0) + dbg ("soft retry, qh %p qtd %p", + qh, qtd); + qh->retries++; + token &= ~0x0ff; + token |= QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* qtd update not needed */ + qh->hw_token = cpu_to_le32 (token); + spin_unlock_irqrestore (&ehci->lock, + flags); + return; + + } else if (qh->retries >= soft_retries + && soft_retries) { + dbg ("retried %d times, qh %p qtd %p", + qh->retries, qh, qtd); + } +#endif /* EHCI_SOFT_RETRIES */ + halted = 1; + } + + if (unlikely ((token & QTD_STS_ACTIVE) != 0)) { + /* stop scan if qtd is visible to the HC */ + if (!halted) { + urb = 0; + break; + } + + /* continue cleanup if HC is halted */ + if (ehci->hcd.state == USB_STATE_HALT) { + urb->status = -ESHUTDOWN; + goto scrub; + } + + /* stall? some other urb was unlinked? */ + if (urb->status == -EINPROGRESS) { +dbg ("?why? qh %p, qtd %p halted, urb %p, token %8x, len %d", + qh, qtd, urb, token, urb->actual_length); +spin_unlock_irqrestore (&ehci->lock, flags); +return retval; + /* + * FIXME: write this code. When one queued urb is unlinked, + * unlink every succeeding urb. + */ + continue; + } + + /* else stopped for some other reason */ + } +scrub: + spin_lock (&urb->lock); + qtd_copy_status (urb, qtd->length, token); + spin_unlock (&urb->lock); + } + next = qtd->qtd_list.next; + + /* + * NOTE: this won't work right with interrupt urbs that + * need multiple qtds ... only the first scan of qh->qtd_list + * starts at the right qtd, yet multiple scans could happen + * for transfers that are scheduled across multiple uframes. + */ + if (likely (freeing != 0)) + list_del (&qtd->qtd_list); + else { + /* restore everything the HC could change + * from an interrupt QTD + */ + qtd->hw_token = (qtd->hw_token + & ~__constant_cpu_to_le32 (0x8300)) + | cpu_to_le32 (qtd->length << 16) + | __constant_cpu_to_le32 (QTD_IOC + | (EHCI_TUNE_CERR << 10) + | QTD_STS_ACTIVE); + qtd->hw_buf [0] &= ~__constant_cpu_to_le32 (0x0fff); + + /* this offset, and the length above, + * are likely wrong on QTDs #2..N + */ + qtd->hw_buf [0] |= cpu_to_le32 (0x0fff & qtd->buf_dma); + } + + spin_unlock_irqrestore (&ehci->lock, flags); + +#if 0 + if (urb->status == -EINPROGRESS) + vdbg (" qtd %p ok, urb %p, token %8x, len %d", + qtd, urb, token, urb->actual_length); + else + vdbg ("urb %p status %d, qtd %p, token %8x, len %d", + urb, urb->status, qtd, token, + urb->actual_length); +#endif + + /* SETUP for control urb? */ + if (unlikely (QTD_PID (token) == 2)) + pci_unmap_single (ehci->hcd.pdev, + qtd->buf_dma, sizeof (devrequest), + PCI_DMA_TODEVICE); + + /* another queued urb? */ + if (unlikely (qtd->urb != urb)) { + if (likely (freeing != 0)) + ehci_urb_done (ehci, qtd->buf_dma, urb); + else + ehci_urb_complete (ehci, qtd->buf_dma, urb); + retval++; + urb = qtd->urb; + } + + if (likely (freeing != 0)) + ehci_qtd_free (ehci, qtd); + spin_lock_irqsave (&ehci->lock, flags); + qtd = list_entry (next, struct ehci_qtd, qtd_list); + } + + /* patch up list head? */ + if (unlikely (halted && qh && !list_empty (qtd_list))) { + qh_update (qh, list_entry (qtd_list->next, + struct ehci_qtd, qtd_list)); + } + spin_unlock_irqrestore (&ehci->lock, flags); + + /* last urb's completion might still need calling */ + if (likely (qtd && urb)) { + if (likely (freeing != 0)) + ehci_urb_done (ehci, qtd->buf_dma, urb); + else + ehci_urb_complete (ehci, qtd->buf_dma, urb); + retval++; + } + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* + * create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head * +qh_urb_transaction ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *head, + int flags +) { + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf, map_buf; + int len, maxpacket; + u32 token; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + return 0; + qtd_prev = 0; + list_add_tail (&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + if (usb_pipecontrol (urb->pipe)) { + /* control request data is passed in the "setup" pid */ + + /* NOTE: this isn't smart about 64bit DMA, since it uses the + * default (32bit) mask rather than using the whole address + * space. we could set pdev->dma_mask to all-ones while + * getting this mapping, locking it and restoring before + * allocating qtd/qh/... or maybe only do that for the main + * data phase (below). + */ + qtd->buf_dma = pci_map_single ( + ehci->hcd.pdev, + urb->setup_packet, + sizeof (devrequest), + PCI_DMA_TODEVICE); + if (unlikely (!qtd->buf_dma)) + goto cleanup; + + /* SETUP pid */ + qtd_fill (qtd, qtd->buf_dma, sizeof (devrequest), + token | (2 /* "setup" */ << 8)); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + } + + /* + * data transfer stage: buffer setup + */ + len = urb->transfer_buffer_length; + if (likely (len > 0)) { + /* NOTE: sub-optimal mapping with 64bit DMA (see above) */ + buf = map_buf = pci_map_single (ehci->hcd.pdev, + urb->transfer_buffer, len, + usb_pipein (urb->pipe) + ? PCI_DMA_FROMDEVICE + : PCI_DMA_TODEVICE); + if (unlikely (!buf)) + goto cleanup; + } else + buf = map_buf = 0; + + if (!buf || usb_pipein (urb->pipe)) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = usb_maxpacket (urb->dev, urb->pipe, + usb_pipeout (urb->pipe)); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + qtd->urb = urb; + qtd->buf_dma = map_buf; + this_qtd_len = qtd_fill (qtd, buf, len, token); + len -= this_qtd_len; + buf += this_qtd_len; + + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely (len <= 0)) + break; + + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + } + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (likely (buf != 0)) { + int one_more = 0; + + if (usb_pipecontrol (urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipebulk (urb->pipe) + && (urb->transfer_flags & USB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill (qtd, 0, 0, token); + } + } + + /* by default, enable interrupt on urb completion */ + if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT))) + qtd->hw_token |= __constant_cpu_to_le32 (QTD_IOC); + return head; + +cleanup: + urb->status = -ENOMEM; + qh_completions (ehci, head, 1); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Hardware maintains data toggle (like OHCI) ... here we (re)initialize + * the hardware data toggle in the QH, and set the pseudo-toggle in udev + * so we can see if usb_clear_halt() was called. NOP for control, since + * we set up qh->hw_info1 to always use the QTD toggle bits. + */ +static inline void +clear_toggle (struct usb_device *udev, int ep, int is_out, struct ehci_qh *qh) +{ + vdbg ("clear toggle, dev %d ep 0x%x-%s", + udev->devnum, ep, is_out ? "out" : "in"); + qh->hw_token &= ~__constant_cpu_to_le32 (QTD_TOGGLE); + usb_settoggle (udev, ep, is_out, 1); +} + +// Would be best to create all qh's from config descriptors, +// when each interface/altsetting is established. Unlink +// any previous qh and cancel its urbs first; endpoints are +// implicitly reset then (data toggle too). +// That'd mean updating how usbcore talks to HCDs. (2.5?) + + +/* + * Each QH holds a qtd list; a QH is used for everything except iso. + * + * For interrupt urbs, the scheduler must set the microframe scheduling + * mask(s) each time the QH gets scheduled. For highspeed, that's + * just one microframe in the s-mask. For split interrupt transactions + * there are additional complications: c-mask, maybe FSTNs. + */ +static struct ehci_qh * +ehci_qh_make ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + int flags +) { + struct ehci_qh *qh = ehci_qh_alloc (ehci, flags); + u32 info1 = 0, info2 = 0; + + if (!qh) + return qh; + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint (urb->pipe) << 8; + info1 |= usb_pipedevice (urb->pipe) << 0; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= (1 << 12); /* EPS "low" */ + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + info1 |= (EHCI_TUNE_RL_TT << 28); + if (usb_pipecontrol (urb->pipe)) { + info1 |= (1 << 27); /* for TT */ + info1 |= 1 << 14; /* toggle from qtd */ + } + info1 |= usb_maxpacket (urb->dev, urb->pipe, + usb_pipeout (urb->pipe)) << 16; + + info2 |= (EHCI_TUNE_MULT_TT << 30); + info2 |= urb->dev->ttport << 23; + info2 |= urb->dev->tt->hub->devnum << 16; + + /* NOTE: if (usb_pipeint (urb->pipe)) { scheduler sets c-mask } + * ... and a 0.96 scheduler might use FSTN nodes too + */ + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= (2 << 12); /* EPS "high" */ + info1 |= (EHCI_TUNE_RL_HS << 28); + if (usb_pipecontrol (urb->pipe)) { + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= 1 << 14; /* toggle from qtd */ + } else if (usb_pipebulk (urb->pipe)) { + info1 |= 512 << 16; /* usb2 fixed maxpacket */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else + info1 |= usb_maxpacket (urb->dev, urb->pipe, + usb_pipeout (urb->pipe)) << 16; + break; + default: +#ifdef DEBUG + BUG (); +#endif + } + + /* NOTE: if (usb_pipeint (urb->pipe)) { scheduler sets s-mask } */ + + qh->qh_state = QH_STATE_IDLE; + qh->hw_info1 = cpu_to_le32 (info1); + qh->hw_info2 = cpu_to_le32 (info2); + + /* initialize sw and hw queues with these qtds */ + list_splice (qtd_list, &qh->qtd_list); + qh_update (qh, list_entry (qtd_list->next, struct ehci_qtd, qtd_list)); + + /* initialize data toggle state */ + if (!usb_pipecontrol (urb->pipe)) + clear_toggle (urb->dev, + usb_pipeendpoint (urb->pipe), + usb_pipeout (urb->pipe), + qh); + + return qh; +} + +/*-------------------------------------------------------------------------*/ + +/* move qh (and its qtds) onto async queue; maybe enable queue. */ + +static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + u32 dma = QH_NEXT (qh->qh_dma); + struct ehci_qh *q; + + if (unlikely (!(q = ehci->async))) { + u32 cmd = readl (&ehci->regs->command); + + /* in case a clear of CMD_ASE didn't take yet */ + while (readl (&ehci->regs->status) & STS_ASS) + udelay (100); + + qh->hw_info1 |= __constant_cpu_to_le32 (QH_HEAD); /* [4.8] */ + qh->qh_next.qh = qh; + qh->hw_next = dma; + ehci->async = qh; + writel ((u32)qh->qh_dma, &ehci->regs->async_next); + cmd |= CMD_ASE | CMD_RUN; + writel (cmd, &ehci->regs->command); + ehci->hcd.state = USB_STATE_RUNNING; + /* posted write need not be known to HC yet ... */ + } else { + /* splice right after "start" of ring */ + qh->hw_info1 &= ~__constant_cpu_to_le32 (QH_HEAD); /* [4.8] */ + qh->qh_next = q->qh_next; + qh->hw_next = q->hw_next; + q->qh_next.qh = qh; + q->hw_next = dma; + } + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ +} + +/*-------------------------------------------------------------------------*/ + +static void +submit_async ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + int mem_flags +) { + struct ehci_qtd *qtd; + struct hcd_dev *dev; + int epnum; + unsigned long flags; + struct ehci_qh *qh = 0; + + qtd = list_entry (qtd_list->next, struct ehci_qtd, qtd_list); + dev = (struct hcd_dev *)urb->dev->hcpriv; + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein (urb->pipe)) + epnum |= 0x10; + + vdbg ("%s: submit_async urb %p len %d ep %d-%s qtd %p [qh %p]", + ehci->hcd.bus_name, urb, urb->transfer_buffer_length, + epnum & 0x0f, (epnum & 0x10) ? "in" : "out", + qtd, dev ? dev->ep [epnum] : (void *)~0); + + spin_lock_irqsave (&ehci->lock, flags); + + qh = (struct ehci_qh *) dev->ep [epnum]; + if (likely (qh != 0)) { + u32 hw_next = QTD_NEXT (qtd->qtd_dma); + + /* maybe patch the qh used for set_address */ + if (unlikely (epnum == 0 + && le32_to_cpu (qh->hw_info1 & 0x7f) == 0)) + qh->hw_info1 |= cpu_to_le32 (usb_pipedevice(urb->pipe)); + + /* is an URB is queued to this qh already? */ + if (unlikely (!list_empty (&qh->qtd_list))) { + struct ehci_qtd *last_qtd; + + // dbg_qh ("non-empty qh", ehci, qh); + last_qtd = list_entry (qh->qtd_list.prev, + struct ehci_qtd, qtd_list); + last_qtd->hw_next = hw_next; + + /* previous urb allows short rx? maybe optimize. */ + if (!(last_qtd->urb->transfer_flags & USB_DISABLE_SPD) + && (epnum & 0x10)) { + // only the last QTD for now + last_qtd->hw_alt_next = hw_next; + } + + /* no URB queued */ + } else { + // dbg_qh ("empty qh", ehci, qh); + +// FIXME: how handle usb_clear_halt() for an EP with queued URBs? +// usbcore may not let us handle that cleanly... +// likely must cancel them all first! + + /* usb_clear_halt() means qh data toggle gets reset */ + if (usb_pipebulk (urb->pipe) + && unlikely (!usb_gettoggle (urb->dev, + (epnum & 0x0f), + !(epnum & 0x10)))) { + clear_toggle (urb->dev, + epnum & 0x0f, !(epnum & 0x10), qh); + } + qh_update (qh, qtd); + } + list_splice (qtd_list, qh->qtd_list.prev); + + } else { + /* can't sleep here, we have ehci->lock... */ + qh = ehci_qh_make (ehci, urb, qtd_list, SLAB_ATOMIC); + if (likely (qh != 0)) { + // dbg_qh ("new qh", ehci, qh); + dev->ep [epnum] = qh; + } else + urb->status = -ENOMEM; + } + + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + if (likely (qh != 0)) { + urb->hcpriv = qh_put (qh); + if (likely (qh->qh_state == QH_STATE_IDLE)) + qh_link_async (ehci, qh_put (qh)); + } + spin_unlock_irqrestore (&ehci->lock, flags); + if (unlikely (!qh)) + qh_completions (ehci, qtd_list, 1); +} + +/*-------------------------------------------------------------------------*/ + +/* the async qh for the qtds being reclaimed are now unlinked from the HC */ +/* caller must not own ehci->lock */ + +static void end_unlink_async (struct ehci_hcd *ehci) +{ + struct ehci_qh *qh = ehci->reclaim; + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = 0; + qh_unput (ehci, qh); // refcount from reclaim + ehci->reclaim = 0; + ehci->reclaim_ready = 0; + + qh_completions (ehci, &qh->qtd_list, 1); + + // FIXME unlink any urb should unlink all following urbs, + // so that this will never happen + if (!list_empty (&qh->qtd_list) + && HCD_IS_RUNNING (ehci->hcd.state)) + qh_link_async (ehci, qh); + else + qh_unput (ehci, qh); // refcount from async list +} + + +/* makes sure the async qh will become idle */ +/* caller must own ehci->lock */ + +static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + int cmd = readl (&ehci->regs->command); + struct ehci_qh *prev; + +#ifdef DEBUG + if (ehci->reclaim + || !ehci->async + || qh->qh_state != QH_STATE_LINKED +#ifdef CONFIG_SMP +// this macro lies except on SMP compiles + || !spin_is_locked (&ehci->lock) +#endif + ) + BUG (); +#endif + + qh->qh_state = QH_STATE_UNLINK; + ehci->reclaim = qh = qh_put (qh); + + // dbg_qh ("start unlink", ehci, qh); + + /* Remove the last QH (qhead)? Stop async schedule first. */ + if (unlikely (qh == ehci->async && qh->qh_next.qh == qh)) { + /* can't get here without STS_ASS set */ + if (ehci->hcd.state != USB_STATE_HALT) { + if (cmd & CMD_PSE) + writel (cmd & __constant_cpu_to_le32 (~CMD_ASE), + &ehci->regs->command); + else { + ehci_ready (ehci); + while (!(readl (&ehci->regs->status) & STS_ASS)) + udelay (100); + } + } + qh->qh_next.qh = ehci->async = 0; + + ehci->reclaim_ready = 1; + tasklet_schedule (&ehci->tasklet); + return; + } + + if (unlikely (ehci->hcd.state == USB_STATE_HALT)) { + ehci->reclaim_ready = 1; + tasklet_schedule (&ehci->tasklet); + return; + } + + prev = ehci->async; + while (prev->qh_next.qh != qh && prev->qh_next.qh != ehci->async) + prev = prev->qh_next.qh; +#ifdef DEBUG + if (prev->qh_next.qh != qh) + BUG (); +#endif + + if (qh->hw_info1 & __constant_cpu_to_le32 (QH_HEAD)) { + ehci->async = prev; + prev->hw_info1 |= __constant_cpu_to_le32 (QH_HEAD); + } + prev->hw_next = qh->hw_next; + prev->qh_next = qh->qh_next; + + ehci->reclaim_ready = 0; + cmd |= CMD_IAAD; + writel (cmd, &ehci->regs->command); + /* posted write need not be known to HC yet ... */ +} + +/*-------------------------------------------------------------------------*/ + +static void scan_async (struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + unsigned long flags; + + spin_lock_irqsave (&ehci->lock, flags); +rescan: + qh = ehci->async; + if (likely (qh != 0)) { + do { + /* clean any finished work for this qh */ + if (!list_empty (&qh->qtd_list)) { + // dbg_qh ("scan_async", ehci, qh); + qh = qh_put (qh); + spin_unlock_irqrestore (&ehci->lock, flags); + + /* concurrent unlink could happen here */ + qh_completions (ehci, &qh->qtd_list, 1); + + spin_lock_irqsave (&ehci->lock, flags); + qh_unput (ehci, qh); + } + + /* unlink idle entries (reduces PCI usage) */ + if (list_empty (&qh->qtd_list) && !ehci->reclaim) { + if (qh->qh_next.qh != qh) { + // dbg ("irq/empty"); + start_unlink_async (ehci, qh); + } else { + // FIXME: arrange to stop + // after it's been idle a while. + } + } + qh = qh->qh_next.qh; + if (!qh) /* unlinked? */ + goto rescan; + } while (qh != ehci->async); + } + + spin_unlock_irqrestore (&ehci->lock, flags); +} diff -Nru a/drivers/usb/hcd/ehci-sched.c b/drivers/usb/hcd/ehci-sched.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-sched.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,1053 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI scheduled transaction support: interrupt, iso, split iso + * These are called "periodic" transactions in the EHCI spec. + */ + +/* + * Ceiling microseconds (typical) for that many bytes at high speed + * ISO is a bit less, no ACK ... from USB 2.0 spec, 5.11.3 (and needed + * to preallocate bandwidth) + */ +#define EHCI_HOST_DELAY 5 /* nsec, guess */ +#define HS_USECS(bytes) NS_TO_US ( ((55 * 8 * 2083)/1000) \ + + ((2083UL * (3167 + BitTime (bytes)))/1000) \ + + EHCI_HOST_DELAY) +#define HS_USECS_ISO(bytes) NS_TO_US ( ((long)(38 * 8 * 2.083)) \ + + ((2083UL * (3167 + BitTime (bytes)))/1000) \ + + EHCI_HOST_DELAY) + +static int ehci_get_frame (struct usb_hcd *hcd); + +/*-------------------------------------------------------------------------*/ + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd/sitd + * @tag: hardware tag for type of this record + */ +static union ehci_shadow * +periodic_next_shadow (union ehci_shadow *periodic, int tag) +{ + switch (tag) { + case Q_TYPE_QH: + return &periodic->qh->qh_next; + case Q_TYPE_FSTN: + return &periodic->fstn->fstn_next; +#ifdef have_iso + case Q_TYPE_ITD: + return &periodic->itd->itd_next; + case Q_TYPE_SITD: + return &periodic->sitd->sitd_next; +#endif /* have_iso */ + } + dbg ("BAD shadow %p tag %d", periodic->ptr, tag); + // BUG (); + return 0; +} + +/* returns true after successful unlink */ +/* caller must hold ehci->lock */ +static int periodic_unlink (struct ehci_hcd *ehci, unsigned frame, void *ptr) +{ + union ehci_shadow *prev_p = &ehci->pshadow [frame]; + u32 *hw_p = &ehci->periodic [frame]; + union ehci_shadow here = *prev_p; + union ehci_shadow *next_p; + + /* find predecessor of "ptr"; hw and shadow lists are in sync */ + while (here.ptr && here.ptr != ptr) { + prev_p = periodic_next_shadow (prev_p, Q_NEXT_TYPE (*hw_p)); + hw_p = &here.qh->hw_next; + here = *prev_p; + } + /* an interrupt entry (at list end) could have been shared */ + if (!here.ptr) { + dbg ("entry %p no longer on frame [%d]", ptr, frame); + return 0; + } + // vdbg ("periodic unlink %p from frame %d", ptr, frame); + + /* update hardware list ... HC may still know the old structure, so + * don't change hw_next until it'll have purged its cache + */ + next_p = periodic_next_shadow (&here, Q_NEXT_TYPE (*hw_p)); + *hw_p = here.qh->hw_next; + + /* unlink from shadow list; HCD won't see old structure again */ + *prev_p = *next_p; + next_p->ptr = 0; + + return 1; +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short +periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) +{ + u32 *hw_p = &ehci->periodic [frame]; + union ehci_shadow *q = &ehci->pshadow [frame]; + unsigned usecs = 0; +#ifdef have_iso + u32 temp = 0; +#endif + + while (q->ptr) { + switch (Q_NEXT_TYPE (*hw_p)) { + case Q_TYPE_QH: + /* is it in the S-mask? */ + if (q->qh->hw_info2 & cpu_to_le32 (1 << uframe)) + usecs += q->qh->usecs; + q = &q->qh->qh_next; + break; + case Q_TYPE_FSTN: + /* for "save place" FSTNs, count the relevant INTR + * bandwidth from the previous frame + */ + if (q->fstn->hw_prev != EHCI_LIST_END) { + dbg ("not counting FSTN bandwidth yet ..."); + } + q = &q->fstn->fstn_next; + break; +#ifdef have_iso + case Q_TYPE_ITD: + temp = le32_to_cpu (q->itd->transaction [uframe]); + temp >>= 16; + temp &= 0x0fff; + if (temp) + usecs += HS_USECS_ISO (temp); + q = &q->itd->itd_next; + break; + case Q_TYPE_SITD: + temp = q->sitd->hw_fullspeed_ep & + __constant_cpu_to_le32 (1 << 31); + + // FIXME: this doesn't count data bytes right... + + /* is it in the S-mask? (count SPLIT, DATA) */ + if (q->sitd->hw_uframe & cpu_to_le32 (1 << uframe)) { + if (temp) + usecs += HS_USECS (188); + else + usecs += HS_USECS (1); + } + + /* ... C-mask? (count CSPLIT, DATA) */ + if (q->sitd->hw_uframe & + cpu_to_le32 (1 << (8 + uframe))) { + if (temp) + usecs += HS_USECS (0); + else + usecs += HS_USECS (188); + } + q = &q->sitd->sitd_next; + break; +#endif /* have_iso */ + default: + BUG (); + } + } +#ifdef DEBUG + if (usecs > 100) + err ("overallocated uframe %d, periodic is %d usecs", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +/*-------------------------------------------------------------------------*/ + +static void intr_deschedule ( + struct ehci_hcd *ehci, + unsigned frame, + struct ehci_qh *qh, + unsigned period +) { + unsigned long flags; + + spin_lock_irqsave (&ehci->lock, flags); + + do { + periodic_unlink (ehci, frame, qh); + qh_unput (ehci, qh); + frame += period; + } while (frame < ehci->periodic_size); + + qh->qh_state = QH_STATE_UNLINK; + qh->qh_next.ptr = 0; + ehci->periodic_urbs--; + + /* maybe turn off periodic schedule */ + if (!ehci->periodic_urbs) { + u32 cmd = readl (&ehci->regs->command); + + /* did setting PSE not take effect yet? + * takes effect only at frame boundaries... + */ + while (!(readl (&ehci->regs->status) & STS_PSS)) + udelay (20); + + cmd &= ~CMD_PSE; + writel (cmd, &ehci->regs->command); + /* posted write ... */ + + ehci->next_frame = -1; + } else + vdbg ("periodic schedule still enabled"); + + spin_unlock_irqrestore (&ehci->lock, flags); + + /* + * If the hc may be looking at this qh, then delay a uframe + * (yeech!) to be sure it's done. + * No other threads may be mucking with this qh. + */ + if (((ehci_get_frame (&ehci->hcd) - frame) % period) == 0) + udelay (125); + + qh->qh_state = QH_STATE_IDLE; + qh->hw_next = EHCI_LIST_END; + + vdbg ("descheduled qh %p, per = %d frame = %d count = %d, urbs = %d", + qh, period, frame, + atomic_read (&qh->refcount), ehci->periodic_urbs); +} + +static int intr_submit ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + int mem_flags +) { + unsigned epnum, period; + unsigned temp; + unsigned short mult, usecs; + unsigned long flags; + struct ehci_qh *qh; + struct hcd_dev *dev; + int status = 0; + + /* get endpoint and transfer data */ + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein (urb->pipe)) { + temp = urb->dev->epmaxpacketin [epnum]; + epnum |= 0x10; + } else + temp = urb->dev->epmaxpacketout [epnum]; + mult = 1; + if (urb->dev->speed == USB_SPEED_HIGH) { + /* high speed "high bandwidth" is coded in ep maxpacket */ + mult += (temp >> 11) & 0x03; + temp &= 0x03ff; + } else { + dbg ("no intr/tt scheduling yet"); + status = -ENOSYS; + goto done; + } + + /* + * NOTE: current completion/restart logic doesn't handle more than + * one qtd in a periodic qh ... 16-20 KB/urb is pretty big for this. + * such big requests need many periods to transfer. + */ + if (unlikely (qtd_list->next != qtd_list->prev)) { + dbg ("only one intr qtd per urb allowed"); + status = -EINVAL; + goto done; + } + + usecs = HS_USECS (urb->transfer_buffer_length); + + /* + * force a power-of-two (frames) sized polling interval + * + * NOTE: endpoint->bInterval for highspeed is measured in uframes, + * while for full/low speeds it's in frames. Here we "know" that + * urb->interval doesn't give acccess to high interrupt rates. + */ + period = ehci->periodic_size; + temp = period; + if (unlikely (urb->interval < 1)) + urb->interval = 1; + while (temp > urb->interval) + temp >>= 1; + period = urb->interval = temp; + + spin_lock_irqsave (&ehci->lock, flags); + + /* get the qh (must be empty and idle) */ + dev = (struct hcd_dev *)urb->dev->hcpriv; + qh = (struct ehci_qh *) dev->ep [epnum]; + if (qh) { + /* only allow one queued interrupt urb per EP */ + if (unlikely (qh->qh_state != QH_STATE_IDLE + || !list_empty (&qh->qtd_list))) { + dbg ("interrupt urb already queued"); + status = -EBUSY; + } else { + /* maybe reset hardware's data toggle in the qh */ + if (unlikely (!usb_gettoggle (urb->dev, epnum & 0x0f, + !(epnum & 0x10)))) { + qh->hw_token |= + __constant_cpu_to_le32 (QTD_TOGGLE); + usb_settoggle (urb->dev, epnum & 0x0f, + !(epnum & 0x10), 1); + } + /* trust the QH was set up as interrupt ... */ + list_splice (qtd_list, &qh->qtd_list); + qh_update (qh, list_entry (qtd_list->next, + struct ehci_qtd, qtd_list)); + } + } else { + /* can't sleep here, we have ehci->lock... */ + qh = ehci_qh_make (ehci, urb, qtd_list, SLAB_ATOMIC); + qtd_list = &qh->qtd_list; + if (likely (qh != 0)) { + // dbg ("new INTR qh %p", qh); + dev->ep [epnum] = qh; + } else + status = -ENOMEM; + } + + /* Schedule this periodic QH. */ + if (likely (status == 0)) { + unsigned frame = urb->interval; + + qh->hw_next = EHCI_LIST_END; + qh->hw_info2 |= cpu_to_le32 (mult << 30); + qh->usecs = usecs; + + urb->hcpriv = qh_put (qh); + status = -ENOSPC; + + /* pick a set of schedule slots, link the QH into them */ + do { + int uframe; + + /* Select some frame 0..(urb->interval - 1) with a + * microframe that can hold this transaction. + * + * FIXME for TT splits, need uframes for start and end. + * FSTNs can put end into next frame (uframes 0 or 1). + */ + frame--; + for (uframe = 0; uframe < 8; uframe++) { + int claimed; + claimed = periodic_usecs (ehci, frame, uframe); + /* 80% periodic == 100 usec max committed */ + if ((claimed + usecs) <= 100) { + vdbg ("frame %d.%d: %d usecs, plus %d", + frame, uframe, claimed, usecs); + break; + } + } + if (uframe == 8) + continue; +// FIXME delete when code below handles non-empty queues + if (ehci->pshadow [frame].ptr) + continue; + + /* QH will run once each period, starting there */ + urb->start_frame = frame; + status = 0; + + /* set S-frame mask */ + qh->hw_info2 |= cpu_to_le32 (1 << uframe); + // dbg_qh ("Schedule INTR qh", ehci, qh); + + /* stuff into the periodic schedule */ + qh->qh_state = QH_STATE_LINKED; + vdbg ("qh %p usecs %d period %d starting frame %d.%d", + qh, qh->usecs, period, frame, uframe); + do { + if (unlikely ((int)ehci->pshadow [frame].ptr)) { +// FIXME -- just link to the end, before any qh with a shorter period, +// AND handle it already being (implicitly) linked into this frame + BUG (); + } else { + ehci->pshadow [frame].qh = qh_put (qh); + ehci->periodic [frame] = + QH_NEXT (qh->qh_dma); + } + frame += period; + } while (frame < ehci->periodic_size); + + /* maybe enable periodic schedule processing */ + if (!ehci->periodic_urbs++) { + u32 cmd; + + /* did clearing PSE did take effect yet? + * takes effect only at frame boundaries... + */ + while (readl (&ehci->regs->status) & STS_PSS) + udelay (20); + + cmd = readl (&ehci->regs->command) | CMD_PSE; + writel (cmd, &ehci->regs->command); + /* posted write ... PSS happens later */ + ehci->hcd.state = USB_STATE_RUNNING; + + /* make sure tasklet scans these */ + ehci->next_frame = ehci_get_frame (&ehci->hcd); + } + break; + + } while (frame); + } + spin_unlock_irqrestore (&ehci->lock, flags); +done: + if (status) { + usb_complete_t complete = urb->complete; + + urb->complete = 0; + urb->status = status; + qh_completions (ehci, qtd_list, 1); + urb->complete = complete; + } + return status; +} + +static unsigned long +intr_complete ( + struct ehci_hcd *ehci, + unsigned frame, + struct ehci_qh *qh, + unsigned long flags /* caller owns ehci->lock ... */ +) { + struct ehci_qtd *qtd; + struct urb *urb; + int unlinking; + + /* nothing to report? */ + if (likely ((qh->hw_token & __constant_cpu_to_le32 (QTD_STS_ACTIVE)) + != 0)) + return flags; + + qtd = list_entry (qh->qtd_list.next, struct ehci_qtd, qtd_list); + urb = qtd->urb; + unlinking = (urb->status == -ENOENT) || (urb->status == -ECONNRESET); + + /* call any completions, after patching for reactivation */ + spin_unlock_irqrestore (&ehci->lock, flags); + /* NOTE: currently restricted to one qtd per qh! */ + if (qh_completions (ehci, &qh->qtd_list, 0) == 0) + urb = 0; + spin_lock_irqsave (&ehci->lock, flags); + + /* never reactivate requests that were unlinked ... */ + if (likely (urb != 0)) { + if (unlinking + || urb->status == -ECONNRESET + || urb->status == -ENOENT + // || (urb->dev == null) + || ehci->hcd.state == USB_STATE_HALT) + urb = 0; + // FIXME look at all those unlink cases ... we always + // need exactly one completion that reports unlink. + // the one above might not have been it! + } + + /* normally reactivate */ + if (likely (urb != 0)) { + if (usb_pipeout (urb->pipe)) + pci_dma_sync_single (ehci->hcd.pdev, + qtd->buf_dma, + urb->transfer_buffer_length, + PCI_DMA_TODEVICE); + urb->status = -EINPROGRESS; + urb->actual_length = 0; + + /* patch qh and restart */ + qh_update (qh, qtd); + } + return flags; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef have_iso + +static inline void itd_free (struct ehci_hcd *ehci, struct ehci_itd *itd) +{ + pci_pool_free (ehci->itd_pool, itd, itd->itd_dma); +} + +/* + * Create itd and allocate into uframes within specified frame. + * Caller must update the resulting uframe links. + */ +static struct ehci_itd * +itd_make ( + struct ehci_hcd *ehci, + struct urb *urb, + unsigned index, // urb->iso_frame_desc [index] + unsigned frame, // scheduled start + dma_addr_t dma, // mapped transfer buffer + int mem_flags +) { + struct ehci_itd *itd; + u64 temp; + u32 buf1; + unsigned epnum, maxp, multi, usecs; + unsigned length; + unsigned i, bufnum; + + /* allocate itd, start to fill it */ + itd = pci_pool_alloc (ehci->itd_pool, mem_flags, &dma); + if (!itd) + return itd; + + itd->hw_next = EHCI_LIST_END; + itd->urb = urb; + itd->index = index; + INIT_LIST_HEAD (&itd->itd_list); + itd->uframe = (frame * 8) % ehci->periodic_size; + + /* tell itd about the buffer its transfers will consume */ + length = urb->iso_frame_desc [index].length; + dma += urb->iso_frame_desc [index].offset; + temp = dma & ~0x0fff; + for (i = 0; i < 7; i++) { + itd->hw_bufp [i] = cpu_to_le32 ((u32) temp); + itd->hw_bufp_hi [i] = cpu_to_le32 ((u32)(temp >> 32)); + temp += 0x0fff; + } + + /* + * this might be a "high bandwidth" highspeed endpoint, + * as encoded in the ep descriptor's maxpacket field + */ + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein (urb->pipe)) { + maxp = urb->dev->epmaxpacketin [epnum]; + buf1 = (1 << 11) | maxp; + } else { + maxp = urb->dev->epmaxpacketout [epnum]; + buf1 = maxp; + } + multi = 1; + multi += (temp >> 11) & 0x03; + maxp &= 0x03ff; + + /* "plus" info in low order bits of buffer pointers */ + itd->hw_bufp [0] |= cpu_to_le32 ((epnum << 8) | urb->dev->devnum); + itd->hw_bufp [1] |= cpu_to_le32 (buf1); + itd->hw_bufp [2] |= cpu_to_le32 (multi); + + /* schedule as many uframes as needed */ + maxp *= multi; + usecs = HS_USECS_ISO (maxp); + bufnum = 0; + for (i = 0; i < 8; i++) { + unsigned t, offset, scratch; + + if (length <= 0) { + itd->hw_transaction [i] = 0; + continue; + } + + /* don't commit more than 80% periodic == 100 usec */ + if ((periodic_usecs (ehci, itd->uframe, i) + usecs) > 100) + continue; + + /* we'll use this uframe; figure hw_transaction */ + t = EHCI_ISOC_ACTIVE; + t |= bufnum << 12; // which buffer? + offset = temp & 0x0fff; // offset therein + t |= offset; + if ((offset + maxp) >= 4096) // hc auto-wraps end-of-"page" + bufnum++; + if (length <= maxp) { + // interrupt only needed at end-of-urb + if ((index + 1) == urb->number_of_packets) + t |= EHCI_ITD_IOC; + scratch = length; + } else + scratch = maxp; + t |= scratch << 16; + t = cpu_to_le32 (t); + + itd->hw_transaction [i] = itd->transaction [i] = t; + length -= scratch; + } + if (length > 0) { + dbg ("iso frame too big, urb %p [%d], %d extra (of %d)", + urb, index, length, urb->iso_frame_desc [index].length); + itd_free (ehci, itd); + itd = 0; + } + return itd; +} + +static inline void +itd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_itd *itd) +{ + u32 ptr; + + ptr = cpu_to_le32 (itd->itd_dma); // type 0 == itd + if (ehci->pshadow [frame].ptr) { + if (!itd->itd_next.ptr) { + itd->itd_next = ehci->pshadow [frame]; + itd->hw_next = ehci->periodic [frame]; + } else if (itd->itd_next.ptr != ehci->pshadow [frame].ptr) { + dbg ("frame %d itd link goof", frame); + BUG (); + } + } + ehci->pshadow [frame].itd = itd; + ehci->periodic [frame] = ptr; +} + +#define ISO_ERRS (EHCI_ISOC_BUF_ERR | EHCI_ISOC_BABBLE | EHCI_ISOC_XACTERR) + +static unsigned long +itd_complete (struct ehci_hcd *ehci, struct ehci_itd *itd, unsigned long flags) +{ + struct urb *urb = itd->urb; + + /* if not unlinking: */ + if (!(urb->transfer_flags & EHCI_STATE_UNLINK) + && ehci->hcd.state != USB_STATE_HALT) { + int i; + iso_packet_descriptor_t *desc; + struct ehci_itd *first_itd = urb->hcpriv; + + /* update status for this frame's transfers */ + desc = &urb->iso_frame_desc [itd->index]; + desc->status = 0; + desc->actual_length = 0; + for (i = 0; i < 8; i++) { + u32 t = itd->hw_transaction [i]; + if (t & (ISO_ERRS | EHCI_ISOC_ACTIVE)) { + if (t & EHCI_ISOC_ACTIVE) + desc->status = -EXDEV; + else if (t & EHCI_ISOC_BUF_ERR) + desc->status = usb_pipein (urb->pipe) + ? -ENOSR /* couldn't read */ + : -ECOMM; /* couldn't write */ + else if (t & EHCI_ISOC_BABBLE) + desc->status = -EOVERFLOW; + else /* (t & EHCI_ISOC_XACTERR) */ + desc->status = -EPROTO; + break; + } + desc->actual_length += EHCI_ITD_LENGTH (t); + } + + /* handle completion now? */ + if ((itd->index + 1) != urb->number_of_packets) + return flags; + + i = usb_pipein (urb->pipe); + if (i) + pci_dma_sync_single (ehci->hcd.pdev, + first_itd->buf_dma, + urb->transfer_buffer_length, + PCI_DMA_FROMDEVICE); + + /* call completion with no locks; it can unlink ... */ + spin_unlock_irqrestore (&ehci->lock, flags); + urb->complete (urb); + spin_lock_irqsave (&ehci->lock, flags); + + /* re-activate this URB? or unlink? */ + if (!(urb->transfer_flags & EHCI_STATE_UNLINK) + && ehci->hcd.state != USB_STATE_HALT) { + if (!i) + pci_dma_sync_single (ehci->hcd.pdev, + first_itd->buf_dma, + urb->transfer_buffer_length, + PCI_DMA_TODEVICE); + + itd = urb->hcpriv; + do { + for (i = 0; i < 8; i++) + itd->hw_transaction [i] + = itd->transaction [i]; + itd = list_entry (itd->itd_list.next, + struct ehci_itd, itd_list); + } while (itd != urb->hcpriv); + return flags; + } + + /* unlink done only on the last itd */ + } else if ((itd->index + 1) != urb->number_of_packets) + return flags; + + /* we're unlinking ... */ + + /* decouple urb from the hcd */ + spin_unlock_irqrestore (&ehci->lock, flags); + if (ehci->hcd.state == USB_STATE_HALT) + urb->status = -ESHUTDOWN; + itd = urb->hcpriv; + urb->hcpriv = 0; + ehci_urb_done (ehci, itd->buf_dma, urb); + spin_lock_irqsave (&ehci->lock, flags); + + /* take itds out of the hc's periodic schedule */ + list_entry (itd->itd_list.prev, struct ehci_itd, itd_list) + ->itd_list.next = 0; + do { + struct ehci_itd *next; + + if (itd->itd_list.next) + next = list_entry (itd->itd_list.next, + struct ehci_itd, itd_list); + else + next = 0; + + // FIXME: hc WILL (!) lap us here, if we get behind + // by 128 msec (or less, with smaller periodic_size). + // Reading/caching these itds will cause trouble... + + periodic_unlink (ehci, itd->uframe, itd); + itd_free (ehci, itd); + itd = next; + } while (itd); + return flags; +} + +/*-------------------------------------------------------------------------*/ + +static int itd_submit (struct ehci_hcd *ehci, struct urb *urb) +{ + struct ehci_itd *first_itd = 0, *itd; + unsigned frame_index; + dma_addr_t dma; + unsigned long flags; + + dbg ("itd_submit"); + + /* set up one dma mapping for this urb */ + dma = pci_map_single (ehci->hcd.pdev, + urb->transfer_buffer, urb->transfer_buffer_length, + usb_pipein (urb->pipe) + ? PCI_DMA_FROMDEVICE + : PCI_DMA_TODEVICE); + if (dma == 0) + return -ENOMEM; + + /* + * Schedule as needed. This is VERY optimistic about free + * bandwidth! But the API assumes drivers can pick frames + * intelligently (how?), so there's no other good option. + * + * FIXME this doesn't handle urb->next rings, or try to + * use the iso periodicity. + */ + if (urb->transfer_flags & USB_ISO_ASAP) { + urb->start_frame = ehci_get_frame (&ehci->hcd); + urb->start_frame++; + } + urb->start_frame %= ehci->periodic_size; + + /* create and populate itds (doing uframe scheduling) */ + spin_lock_irqsave (&ehci->lock, flags); + for (frame_index = 0; + frame_index < urb->number_of_packets; + frame_index++) { + itd = itd_make (ehci, urb, frame_index, + urb->start_frame + frame_index, + dma, SLAB_ATOMIC); + if (itd) { + if (first_itd) + list_add_tail (&itd->itd_list, + &first_itd->itd_list); + else + first_itd = itd; + } else { + spin_unlock_irqrestore (&ehci->lock, flags); + if (first_itd) { + while (!list_empty (&first_itd->itd_list)) { + itd = list_entry ( + first_itd->itd_list.next, + struct ehci_itd, itd_list); + list_del (&itd->itd_list); + itd_free (ehci, itd); + } + itd_free (ehci, first_itd); + } + pci_unmap_single (ehci->hcd.pdev, + dma, urb->transfer_buffer_length, + usb_pipein (urb->pipe) + ? PCI_DMA_FROMDEVICE + : PCI_DMA_TODEVICE); + return -ENOMEM; + } + } + + /* stuff into the schedule */ + itd = first_itd; + do { + unsigned i; + + for (i = 0; i < 8; i++) { + if (!itd->hw_transaction [i]) + continue; + itd_link (ehci, itd->uframe + i, itd); + } + itd = list_entry (itd->itd_list.next, + struct ehci_itd, itd_list); + } while (itd != first_itd); + urb->hcpriv = first_itd; + + spin_unlock_irqrestore (&ehci->lock, flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * "Split ISO TDs" ... used for USB 1.1 devices going through + * the TTs in USB 2.0 hubs. + */ + +static inline void +sitd_free (struct ehci_hcd *ehci, struct ehci_sitd *sitd) +{ + pci_pool_free (ehci->sitd_pool, sitd, sitd->sitd_dma); +} + +static struct ehci_sitd * +sitd_make ( + struct ehci_hcd *ehci, + struct urb *urb, + unsigned index, // urb->iso_frame_desc [index] + unsigned uframe, // scheduled start + dma_addr_t dma, // mapped transfer buffer + int mem_flags +) { + struct ehci_sitd *sitd; + unsigned length; + + sitd = pci_pool_alloc (ehci->sitd_pool, mem_flags, &dma); + if (!sitd) + return sitd; + sitd->urb = urb; + length = urb->iso_frame_desc [index].length; + dma += urb->iso_frame_desc [index].offset; + +#if 0 + // FIXME: do the rest! +#else + sitd_free (ehci, sitd); + return 0; +#endif + +} + +static inline void +sitd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_sitd *sitd) +{ + u32 ptr; + + ptr = cpu_to_le32 (sitd->sitd_dma | 2); // type 2 == sitd + if (ehci->pshadow [frame].ptr) { + if (!sitd->sitd_next.ptr) { + sitd->sitd_next = ehci->pshadow [frame]; + sitd->hw_next = ehci->periodic [frame]; + } else if (sitd->sitd_next.ptr != ehci->pshadow [frame].ptr) { + dbg ("frame %d sitd link goof", frame); + BUG (); + } + } + ehci->pshadow [frame].sitd = sitd; + ehci->periodic [frame] = ptr; +} + +static unsigned long +sitd_complete ( + struct ehci_hcd *ehci, + struct ehci_sitd *sitd, + unsigned long flags +) { + // FIXME -- implement! + + dbg ("NYI -- sitd_complete"); + return flags; +} + +/*-------------------------------------------------------------------------*/ + +static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb) +{ + // struct ehci_sitd *first_sitd = 0; + unsigned frame_index; + dma_addr_t dma; + int mem_flags; + + dbg ("NYI -- sitd_submit"); + + // FIXME -- implement! + + // FIXME: setup one big dma mapping + dma = 0; + + mem_flags = SLAB_ATOMIC; + + for (frame_index = 0; + frame_index < urb->number_of_packets; + frame_index++) { + struct ehci_sitd *sitd; + unsigned uframe; + + // FIXME: use real arguments, schedule this! + uframe = -1; + + sitd = sitd_make (ehci, urb, frame_index, + uframe, dma, mem_flags); + + if (sitd) { + /* + if (first_sitd) + list_add_tail (&sitd->sitd_list, + &first_sitd->sitd_list); + else + first_sitd = sitd; + */ + } else { + // FIXME: clean everything up + } + } + + // if we have a first sitd, then + // store them all into the periodic schedule! + // urb->hcpriv = first sitd in sitd_list + + return -ENOSYS; +} + +#endif /* have_iso */ + +/*-------------------------------------------------------------------------*/ + +static void scan_periodic (struct ehci_hcd *ehci) +{ + unsigned frame; + unsigned clock; + unsigned long flags; + + spin_lock_irqsave (&ehci->lock, flags); + + /* + * When running, scan from last scan point up to "now" + * Touches as few pages as possible: cache-friendly. + * It's safe to scan entries more than once, though. + */ + if (HCD_IS_RUNNING (ehci->hcd.state)) { + frame = ehci->next_frame; + clock = ehci_get_frame (&ehci->hcd); + + /* when shutting down, scan everything for thoroughness */ + } else { + frame = 0; + clock = ehci->periodic_size - 1; + } + for (;;) { + union ehci_shadow q; + u32 type; + +restart: + q.ptr = ehci->pshadow [frame].ptr; + type = Q_NEXT_TYPE (ehci->periodic [frame]); + + /* scan each element in frame's queue for completions */ + while (q.ptr != 0) { + int last; + union ehci_shadow temp; + + switch (type) { + case Q_TYPE_QH: + last = (q.qh->hw_next == EHCI_LIST_END); + flags = intr_complete (ehci, frame, + qh_put (q.qh), flags); + type = Q_NEXT_TYPE (q.qh->hw_next); + temp = q.qh->qh_next; + qh_unput (ehci, q.qh); + q = temp; + break; + case Q_TYPE_FSTN: + last = (q.fstn->hw_next == EHCI_LIST_END); + /* for "save place" FSTNs, look at QH entries + * in the previous frame for completions. + */ + if (q.fstn->hw_prev != EHCI_LIST_END) { + dbg ("ignoring completions from FSTNs"); + } + type = Q_NEXT_TYPE (q.fstn->hw_next); + temp = q.fstn->fstn_next; + break; +#ifdef have_iso + case Q_TYPE_ITD: + last = (q.itd->hw_next == EHCI_LIST_END); + flags = itd_complete (ehci, q.itd, flags); + type = Q_NEXT_TYPE (q.itd->hw_next); + q = q.itd->itd_next; + break; + case Q_TYPE_SITD: + last = (q.sitd->hw_next == EHCI_LIST_END); + flags = sitd_complete (ehci, q.sitd, flags); + type = Q_NEXT_TYPE (q.sitd->hw_next); + q = q.sitd->sitd_next; + break; +#endif /* have_iso */ + default: + dbg ("corrupt type %d frame %d shadow %p", + type, frame, q.ptr); + // BUG (); + last = 1; + q.ptr = 0; + } + + /* did completion remove an interior q entry? */ + if (unlikely (q.ptr == 0 && !last)) + goto restart; + } + + /* stop when we catch up to the HC */ + + // FIXME: this assumes we won't get lapped when + // latencies climb; that should be rare, but... + // detect it, and just go all the way around. + // FLR might help detect this case, so long as latencies + // don't exceed periodic_size msec (default 1.024 sec). + + // FIXME: likewise assumes HC doesn't halt mid-scan + + if (frame == clock) { + unsigned now; + + if (!HCD_IS_RUNNING (ehci->hcd.state)) + break; + ehci->next_frame = clock; + now = ehci_get_frame (&ehci->hcd); + if (clock == now) + break; + clock = now; + } else if (++frame >= ehci->periodic_size) + frame = 0; + } + spin_unlock_irqrestore (&ehci->lock, flags); + } diff -Nru a/drivers/usb/hcd/ehci.h b/drivers/usb/hcd/ehci.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci.h Wed Dec 19 10:26:03 2001 @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_EHCI_HCD_H +#define __LINUX_EHCI_HCD_H + +/* definitions used for the EHCI driver */ + +/* ehci_hcd->lock guards shared data against other CPUs: + * ehci_hcd: async, reclaim, periodic (and shadow), ... + * hcd_dev: ep[] + * ehci_qh: qh_next, qtd_list + * ehci_qtd: qtd_list + * + * Also, hold this lock when talking to HC registers or + * when updating hw_* fields in shared qh/qtd/... structures. + */ + +#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */ + +struct ehci_hcd { /* one per controller */ + spinlock_t lock; + + /* async schedule support */ + struct ehci_qh *async; + struct ehci_qh *reclaim; + int reclaim_ready; + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 /* some HCs can do less */ + unsigned periodic_size; + u32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + unsigned i_thresh; /* uframes HC might cache */ + + union ehci_shadow *pshadow; /* mirror hw periodic table */ + int next_frame; /* scan periodic, start here */ + unsigned periodic_urbs; /* how many urbs scheduled? */ + + /* deferred work from IRQ, etc */ + struct tasklet_struct tasklet; + + /* per root hub port */ + unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; + + /* glue to PCI and HCD framework */ + struct usb_hcd hcd; + struct ehci_caps *caps; + struct ehci_regs *regs; + + /* per-HC memory pools (could be per-PCI-bus, but ...) */ + struct pci_pool *qh_pool; /* qh per active urb */ + struct pci_pool *qtd_pool; /* one or more per qh */ + struct pci_pool *itd_pool; /* itd per iso urb */ + struct pci_pool *sitd_pool; /* sitd per split iso urb */ +}; + +/* unwrap an HCD pointer to get an EHCI_HCD pointer */ +#define hcd_to_ehci(hcd_ptr) list_entry(hcd_ptr, struct ehci_hcd, hcd) + +/* NOTE: urb->transfer_flags expected to not use this bit !!! */ +#define EHCI_STATE_UNLINK 0x8000 /* urb being unlinked */ + +/*-------------------------------------------------------------------------*/ + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct ehci_caps { + u8 length; /* CAPLENGTH - size of this struct */ + u8 reserved; /* offset 0x1 */ + u16 hci_version; /* HCIVERSION - offset 0x2 */ + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ +#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ +#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ +#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ +#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ +#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ +#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ +#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ +#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ +#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ + u8 portroute [8]; /* nibbles for routing - offset 0xC */ +} __attribute__ ((packed)); + + +/* Section 2.3 Host Controller Operational Registers */ +struct ehci_regs { + + /* USBCMD: offset 0x00 */ + u32 command; +/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ +#define CMD_PARK (1<<11) /* enable "park" on async qh */ +#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ +#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ +#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ +#define CMD_ASE (1<<5) /* async schedule enable */ +#define CMD_PSE (1<<4) /* periodic schedule enable */ +/* 3:2 is periodic frame list size */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ + + /* USBSTS: offset 0x04 */ + u32 status; +#define STS_ASS (1<<15) /* Async Schedule Status */ +#define STS_PSS (1<<14) /* Periodic Schedule Status */ +#define STS_RECL (1<<13) /* Reclamation */ +#define STS_HALT (1<<12) /* Not running (any reason) */ +/* some bits reserved */ + /* these STS_* flags are also intr_enable bits (USBINTR) */ +#define STS_IAA (1<<5) /* Interrupted on async advance */ +#define STS_FATAL (1<<4) /* such as some PCI access errors */ +#define STS_FLR (1<<3) /* frame list rolled over */ +#define STS_PCD (1<<2) /* port change detect */ +#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ +#define STS_INT (1<<0) /* "normal" completion (short, ...) */ + + /* USBINTR: offset 0x08 */ + u32 intr_enable; + + /* FRINDEX: offset 0x0C */ + u32 frame_index; /* current microframe number */ + /* CTRLDSSEGMENT: offset 0x10 */ + u32 segment; /* address bits 63:32 if needed */ + /* PERIODICLISTBASE: offset 0x14 */ + u32 frame_list; /* points to periodic list */ + /* ASYNCICLISTADDR: offset 0x18 */ + u32 async_next; /* address of next async queue head */ + + u32 reserved [9]; + + /* CONFIGFLAG: offset 0x40 */ + u32 configured_flag; +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + + /* PORTSC: offset 0x44 */ + u32 port_status [0]; /* up to N_PORTS */ +/* 31:23 reserved */ +#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ +#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ +#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ +/* 19:16 for port testing */ +/* 15:14 for using port indicator leds (if HCS_INDICATOR allows) */ +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x)&(3<<10))==(1<<10)) /* USB 1.1 device */ +/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ +/* 9 reserved */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_OCC (1<<5) /* over current change */ +#define PORT_OC (1<<4) /* over current active */ +#define PORT_PEC (1<<3) /* port enable change */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +} __attribute__ ((packed)); + + +/*-------------------------------------------------------------------------*/ + +#define QTD_NEXT(dma) cpu_to_le32((u32)dma) + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct ehci_qtd { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.5.1 */ + u32 hw_alt_next; /* see EHCI 3.5.2 */ + u32 hw_token; /* see EHCI 3.5.3 */ +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + u32 hw_buf [5]; /* see EHCI 3.5.4 */ + u32 hw_buf_hi [5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + + /* dma same in urb's qtds, except 1st control qtd (setup buffer) */ + struct urb *urb; /* qtd's urb */ + dma_addr_t buf_dma; /* buffer address */ + size_t length; /* length of buffer */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* type tag from {qh,itd,sitd,fstn}->hw_next */ +#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1)) + +/* values for that type tag */ +#define Q_TYPE_ITD __constant_cpu_to_le32 (0 << 1) +#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) +#define Q_TYPE_SITD __constant_cpu_to_le32 (2 << 1) +#define Q_TYPE_FSTN __constant_cpu_to_le32 (3 << 1) + +/* next async queue entry, or pointer to interrupt/periodic QH */ +#define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */ + +/* + * Entries in periodic shadow table are pointers to one of four kinds + * of data structure. That's dictated by the hardware; a type tag is + * encoded in the low bits of the hardware's periodic schedule. Use + * Q_NEXT_TYPE to get the tag. + * + * For entries in the async schedule, the type tag always says "qh". + */ +union ehci_shadow { + struct ehci_qh *qh; /* Q_TYPE_QH */ + struct ehci_itd *itd; /* Q_TYPE_ITD */ + struct ehci_sitd *sitd; /* Q_TYPE_SITD */ + struct ehci_fstn *fstn; /* Q_TYPE_FSTN */ + void *ptr; +}; + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + +struct ehci_qh { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.6.1 */ + u32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_HEAD 0x00008000 + u32 hw_info2; /* see EHCI 3.6.2 */ + u32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct ehci_qtd) */ + u32 hw_qtd_next; + u32 hw_alt_next; + u32 hw_token; + u32 hw_buf [5]; + u32 hw_buf_hi [5]; + + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union ehci_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + + atomic_t refcount; + unsigned short usecs; /* intr bandwidth */ + short qh_state; +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ + +#ifdef EHCI_SOFT_RETRIES + int retries; +#endif +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.3 + * Fig 3-4 "Isochronous Transaction Descriptor (iTD)" + * + * Schedule records for high speed iso xfers + */ +struct ehci_itd { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.3.1 */ + u32 hw_transaction [8]; /* see EHCI 3.3.2 */ +#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ +#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ +#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ +#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ +#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ + + u32 hw_bufp [7]; /* see EHCI 3.3.3 */ + u32 hw_bufp_hi [7]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t itd_dma; /* for this itd */ + union ehci_shadow itd_next; /* ptr to periodic q entry */ + + struct urb *urb; + unsigned index; /* in urb->iso_frame_desc */ + struct list_head itd_list; /* list of urb frames' itds */ + dma_addr_t buf_dma; /* frame's buffer address */ + + unsigned uframe; /* in periodic schedule */ + u32 transaction [8]; /* copy of hw_transaction */ + +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.4 + * siTD, aka split-transaction isochronous Transfer Descriptor + * ... describe low/full speed iso xfers through TT in hubs + * see Figure 3-5 "Split-transaction Isochronous Transaction Descriptor (siTD) + */ +struct ehci_sitd { + /* first part defined by EHCI spec */ + u32 hw_next; +/* uses bit field macros above - see EHCI 0.95 Table 3-8 */ + u32 hw_fullspeed_ep; /* see EHCI table 3-9 */ + u32 hw_uframe; /* see EHCI table 3-10 */ + u32 hw_tx_results1; /* see EHCI table 3-11 */ + u32 hw_tx_results2; /* see EHCI table 3-12 */ + u32 hw_tx_results3; /* see EHCI table 3-12 */ + u32 hw_backpointer; /* see EHCI table 3-13 */ + u32 hw_buf_hi [2]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t sitd_dma; + union ehci_shadow sitd_next; /* ptr to periodic q entry */ + struct urb *urb; + dma_addr_t buf_dma; /* buffer address */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.96 Section 3.7 + * Periodic Frame Span Traversal Node (FSTN) + * + * Manages split interrupt transactions (using TT) that span frame boundaries + * into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN + * makes the HC jump (back) to a QH to scan for fs/ls QH completions until + * it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work. + */ +struct ehci_fstn { + u32 hw_next; /* any periodic q entry */ + u32 hw_prev; /* qh or EHCI_LIST_END */ + + /* the rest is HCD-private */ + dma_addr_t fstn_dma; + union ehci_shadow fstn_next; /* ptr to periodic q entry */ +} __attribute__ ((aligned (32))); + +#endif /* __LINUX_EHCI_HCD_H */ diff -Nru a/drivers/usb/hcd.c b/drivers/usb/hcd.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,1305 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for UTS_SYSNAME */ + +#ifndef CONFIG_USB_DEBUG + #define CONFIG_USB_DEBUG /* this is experimental! */ +#endif + +#ifdef CONFIG_USB_DEBUG + #define DEBUG +#else + #undef DEBUG +#endif + +#include +#include "hcd.h" + +#include +#include +#include +#include + + +/*-------------------------------------------------------------------------*/ + +/* + * USB Host Controller Driver framework + * + * Plugs into usbcore (usb_bus) and lets HCDs share code, minimizing + * HCD-specific behaviors/bugs. + * + * This does error checks, tracks devices and urbs, and delegates to a + * "hc_driver" only for code (and data) that really needs to know about + * hardware differences. That includes root hub registers, i/o queues, + * and so on ... but as little else as possible. + * + * Shared code includes most of the "root hub" code (these are emulated, + * though each HC's hardware works differently) and PCI glue, plus request + * tracking overhead. The HCD code should only block on spinlocks or on + * hardware handshaking; blocking on software events (such as other kernel + * threads releasing resources, or completing actions) is all generic. + * + * Happens the USB 2.0 spec says this would be invisible inside the "USBD", + * and includes mostly a "HCDI" (HCD Interface) along with some APIs used + * only by the hub driver ... and that neither should be seen or used by + * usb client device drivers. + * + * Contributors of ideas or unattributed patches include: David Brownell, + * Roman Weissgaerber, Rory Bolt, ... + * + * HISTORY: + * 2001-12-12 Initial patch version for Linux 2.5.1 kernel. + */ + +/*-------------------------------------------------------------------------*/ + +/* host controllers we manage */ +static LIST_HEAD (hcd_list); + +/* used when updating list of hcds */ +static DECLARE_MUTEX (hcd_list_lock); + +/* used when updating hcd data */ +static spinlock_t hcd_data_lock = SPIN_LOCK_UNLOCKED; + +static struct usb_operations hcd_operations; + +/*-------------------------------------------------------------------------*/ + +/* + * Sharable chunks of root hub code. + */ + +/*-------------------------------------------------------------------------*/ + +/* usb 2.0 root hub device descriptor */ +static const u8 usb2_rh_dev_descriptor [18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x00, 0x02, /* __u16 bcdUSB; v2.0 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x01, /* __u8 bDeviceProtocol; [ usb 2.0 single TT ]*/ + 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ + + 0x00, 0x00, /* __u16 idVendor; */ + 0x00, 0x00, /* __u16 idProduct; */ + 0x40, 0x02, /* __u16 bcdDevice; (v2.4) */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + +/* no usb 2.0 root hub "device qualifier" descriptor: one speed only */ + +/* usb 1.1 root hub device descriptor */ +static const u8 usb11_rh_dev_descriptor [18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x10, 0x01, /* __u16 bcdUSB; v1.1 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x00, /* __u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ + + 0x00, 0x00, /* __u16 idVendor; */ + 0x00, 0x00, /* __u16 idProduct; */ + 0x40, 0x02, /* __u16 bcdDevice; (v2.4) */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + + +/*-------------------------------------------------------------------------*/ + +/* Configuration descriptor for all our root hubs */ + +static const u8 rh_config_descriptor [] = { + + /* one configuration */ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x19, 0x00, /* __u16 wTotalLength; */ + 0x01, /* __u8 bNumInterfaces; (1) */ + 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; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one 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; [usb1.1 or single tt] */ + 0x00, /* __u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + 0x02, 0x00, /* __u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ + 0x0c /* __u8 ep_bInterval; (12ms -- usb 2.0 spec) */ +}; + +/*-------------------------------------------------------------------------*/ + +/* + * helper routine for returning string descriptors in UTF-16LE + * input can actually be ISO-8859-1; ASCII is its 7-bit subset + */ +static int ascii2utf (char *ascii, u8 *utf, int utfmax) +{ + int retval; + + for (retval = 0; *ascii && utfmax > 1; utfmax -= 2, retval += 2) { + *utf++ = *ascii++ & 0x7f; + *utf++ = 0; + } + return retval; +} + +/* + * rh_string - provides manufacturer, product and serial strings for root hub + * @id: the string ID number (1: serial number, 2: product, 3: vendor) + * @pci_desc: PCI device descriptor for the relevant HC + * @type: string describing our driver + * @data: return packet in UTF-16 LE + * @len: length of the return packet + * + * Produces either a manufacturer, product or serial number string for the + * virtual root hub device. + */ +static int rh_string ( + int id, + struct pci_dev *pci_desc, + char *type, + u8 *data, + int len +) { + char buf [100]; + + // language ids + if (id == 0) { + *data++ = 4; *data++ = 3; /* 4 bytes string data */ + *data++ = 0; *data++ = 0; /* some language id */ + return 4; + + // serial number + } else if (id == 1) { + strcpy (buf, pci_desc->slot_name); + + // product description + } else if (id == 2) { + strcpy (buf, pci_desc->name); + + // id 3 == vendor description + } else if (id == 3) { + sprintf (buf, "%s %s %s", UTS_SYSNAME, UTS_RELEASE, type); + + // unsupported IDs --> "protocol stall" + } else + return 0; + + data [0] = 2 + ascii2utf (buf, data + 2, len - 2); + data [1] = 3; /* type == string */ + return data [0]; +} + + +/* Root hub control transfers execute synchronously */ +static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) +{ + devrequest *cmd = (devrequest *) urb->setup_packet; + u16 typeReq, wValue, wIndex, wLength; + const u8 *bufp = 0; + u8 *ubuf = urb->transfer_buffer; + int len = 0; + + typeReq = (cmd->requesttype << 8) | cmd->request; + wValue = le16_to_cpu (cmd->value); + wIndex = le16_to_cpu (cmd->index); + wLength = le16_to_cpu (cmd->length); + + if (wLength > urb->transfer_buffer_length) + goto error; + + /* set up for success */ + urb->status = 0; + urb->actual_length = wLength; + switch (typeReq) { + + /* DEVICE REQUESTS */ + + case DeviceRequest | USB_REQ_GET_STATUS: + // DEVICE_REMOTE_WAKEUP + ubuf [0] = 1; // selfpowered + ubuf [1] = 0; + /* FALLTHROUGH */ + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + case DeviceOutRequest | USB_REQ_SET_FEATURE: + dbg ("no device features yet yet"); + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + ubuf [0] = 1; + /* FALLTHROUGH */ + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch (wValue & 0xff00) { + case USB_DT_DEVICE << 8: + if (hcd->driver->flags & HCD_USB2) + bufp = usb2_rh_dev_descriptor; + else if (hcd->driver->flags & HCD_USB11) + bufp = usb11_rh_dev_descriptor; + else + goto error; + len = 18; + break; + case USB_DT_CONFIG << 8: + bufp = rh_config_descriptor; + len = sizeof rh_config_descriptor; + break; + case USB_DT_STRING << 8: + urb->actual_length = rh_string ( + wValue & 0xff, + hcd->pdev, + (char *) hcd->description, + ubuf, wLength); + break; + default: + goto error; + } + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + ubuf [0] = 0; + /* FALLTHROUGH */ + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + // wValue == urb->dev->devaddr + dbg ("%s root hub device address %d", + hcd->bus_name, wValue); + break; + + /* INTERFACE REQUESTS (no defined feature/status flags) */ + + /* ENDPOINT REQUESTS */ + + case EndpointRequest | USB_REQ_GET_STATUS: + // ENDPOINT_HALT flag + ubuf [0] = 0; + ubuf [1] = 0; + /* FALLTHROUGH */ + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + case EndpointOutRequest | USB_REQ_SET_FEATURE: + dbg ("no endpoint features yet"); + break; + + /* CLASS REQUESTS (and errors) */ + + default: + /* non-generic request */ + urb->status = hcd->driver->hub_control (hcd, + typeReq, wValue, wIndex, + ubuf, wLength); + break; +error: + /* "protocol stall" on error */ + urb->status = -EPIPE; + dbg ("unsupported hub control message (maxchild %d)", + urb->dev->maxchild); + } + if (urb->status) { + urb->actual_length = 0; + dbg ("CTRL: TypeReq=0x%x val=0x%x idx=0x%x len=%d ==> %d", + typeReq, wValue, wIndex, wLength, urb->status); + } + if (bufp) { + if (urb->transfer_buffer_length < len) + len = urb->transfer_buffer_length; + urb->actual_length = len; + // always USB_DIR_IN, toward host + memcpy (ubuf, bufp, len); + } + + /* any errors get returned through the urb completion */ + usb_hcd_giveback_urb (hcd, urb); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Root Hub interrupt transfers are synthesized with a timer. + * Completions are called in_interrupt() but not in_irq(). + */ + +static void rh_report_status (unsigned long ptr); + +static int rh_status_urb (struct usb_hcd *hcd, struct urb *urb) +{ + int len = 1 + (urb->dev->maxchild / 8); + + /* rh_timer protected by hcd_data_lock */ + if (timer_pending (&hcd->rh_timer) + || urb->status != -EINPROGRESS + || !HCD_IS_RUNNING (hcd->state) + || urb->transfer_buffer_length < len) { + dbg ("not queuing status urb, stat %d", urb->status); + return -EINVAL; + } + + urb->hcpriv = hcd; /* nonzero to indicate it's queued */ + init_timer (&hcd->rh_timer); + hcd->rh_timer.function = rh_report_status; + hcd->rh_timer.data = (unsigned long) urb; + hcd->rh_timer.expires = jiffies + + (HZ * (urb->interval < 30 + ? 30 + : urb->interval)) / 1000; + add_timer (&hcd->rh_timer); + return 0; +} + +/* timer callback */ + +static void rh_report_status (unsigned long ptr) +{ + struct urb *urb; + struct usb_hcd *hcd; + int length; + unsigned long flags; + + urb = (struct urb *) ptr; + spin_lock_irqsave (&urb->lock, flags); + if (!urb->dev) { + spin_unlock_irqrestore (&urb->lock, flags); + return; + } + + hcd = urb->dev->bus->hcpriv; + if (urb->status == -EINPROGRESS) { + if (HCD_IS_RUNNING (hcd->state)) { + length = hcd->driver->hub_status_data (hcd, + urb->transfer_buffer); + spin_unlock_irqrestore (&urb->lock, flags); + if (length > 0) { + urb->actual_length = length; + urb->status = 0; + urb->complete (urb); + } + spin_lock_irqsave (&hcd_data_lock, flags); + urb->status = -EINPROGRESS; + if (HCD_IS_RUNNING (hcd->state) + && rh_status_urb (hcd, urb) != 0) { + /* another driver snuck in? */ + dbg ("%s, can't resubmit roothub status urb?", + hcd->bus_name); + spin_unlock_irqrestore (&hcd_data_lock, flags); + BUG (); + } + spin_unlock_irqrestore (&hcd_data_lock, flags); + } else + spin_unlock_irqrestore (&urb->lock, flags); + } else { + /* this urb's been unlinked */ + urb->hcpriv = 0; + spin_unlock_irqrestore (&urb->lock, flags); + + usb_hcd_giveback_urb (hcd, urb); + } +} + +/*-------------------------------------------------------------------------*/ + +static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb) +{ + if (usb_pipeint (urb->pipe)) { + int retval; + unsigned long flags; + + spin_lock_irqsave (&hcd_data_lock, flags); + retval = rh_status_urb (hcd, urb); + spin_unlock_irqrestore (&hcd_data_lock, flags); + return retval; + } + if (usb_pipecontrol (urb->pipe)) + return rh_call_control (hcd, urb); + else + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ + +static void rh_status_dequeue (struct usb_hcd *hcd, struct urb *urb) +{ + unsigned long flags; + + spin_lock_irqsave (&hcd_data_lock, flags); + del_timer_sync (&hcd->rh_timer); + hcd->rh_timer.data = 0; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + /* we rely on RH callback code not unlinking its URB! */ + usb_hcd_giveback_urb (hcd, urb); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PCI + +/* PCI-based HCs are normal, but custom bus glue should be ok */ + +static void hcd_irq (int irq, void *__hcd, struct pt_regs *r); +static void hc_died (struct usb_hcd *hcd); + +/*-------------------------------------------------------------------------*/ + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * usb_hcd_pci_probe - initialize PCI-based HCDs + * @dev: USB Host Controller being probed + * @id: pci hotplug id connecting controller to HCD framework + * + * Allocates basic PCI resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + * + * Store this function in the HCD's struct pci_driver as probe(). + */ +int usb_hcd_pci_probe (struct pci_dev *dev, const struct pci_device_id *id) +{ + struct hc_driver *driver; + unsigned long resource, len; + void *base; + u8 latency, limit; + struct usb_bus *bus; + struct usb_hcd *hcd; + int retval, region; + char buf [8], *bufp = buf; + + if (!id || !(driver = (struct hc_driver *) id->driver_data)) + return -EINVAL; + + if (pci_enable_device (dev) < 0) + return -ENODEV; + + if (!dev->irq) { + err ("Found HC with no IRQ. Check BIOS/PCI %s setup!", + dev->slot_name); + return -ENODEV; + } + + if (driver->flags & HCD_MEMORY) { // EHCI, OHCI + region = 0; + resource = pci_resource_start (dev, 0); + len = pci_resource_len (dev, 0); + if (!request_mem_region (resource, len, driver->description)) { + dbg ("controller already in use"); + return -EBUSY; + } + base = ioremap_nocache (resource, len); + if (base == NULL) { + dbg ("error mapping memory"); + retval = -EFAULT; +clean_1: + release_mem_region (resource, len); + err ("init %s fail, %d", dev->slot_name, retval); + return retval; + } + + } else { // UHCI + resource = len = 0; + for (region = 0; region < PCI_ROM_RESOURCE; region++) { + if (!(pci_resource_flags (dev, region) & IORESOURCE_IO)) + continue; + + resource = pci_resource_start (dev, region); + len = pci_resource_len (dev, region); + if (request_region (resource, len, + driver->description)) + break; + } + if (region == PCI_ROM_RESOURCE) { + dbg ("no i/o regions available"); + return -EBUSY; + } + base = (void *) resource; + } + + // driver->start(), later on, will transfer device from + // control by SMM/BIOS to control by Linux (if needed) + + pci_set_master (dev); + hcd = driver->hcd_alloc (); + if (hcd == NULL){ + dbg ("hcd alloc fail"); + retval = -ENOMEM; +clean_2: + if (driver->flags & HCD_MEMORY) { + iounmap (base); + goto clean_1; + } else { + release_region (resource, len); + err ("init %s fail, %d", dev->slot_name, retval); + return retval; + } + } + dev->driver_data = hcd; + hcd->driver = driver; + hcd->description = driver->description; + hcd->pdev = dev; + info ("%s @ %s, %s", hcd->description, dev->slot_name, dev->name); + + pci_read_config_byte (dev, PCI_LATENCY_TIMER, &latency); + if (latency) { + pci_read_config_byte (dev, PCI_MAX_LAT, &limit); + if (limit && limit < latency) { + dbg ("PCI latency reduced to max %d", limit); + pci_write_config_byte (dev, PCI_LATENCY_TIMER, limit); + } + } + +#ifndef __sparc__ + sprintf (buf, "%d", dev->irq); +#else + bufp = __irq_itoa(irq); +#endif + if (request_irq (dev->irq, hcd_irq, SA_SHIRQ, hcd->description, hcd) + != 0) { + err ("request interrupt %s failed", bufp); + retval = -EBUSY; +clean_3: + driver->hcd_free (hcd); + goto clean_2; + } + hcd->irq = dev->irq; + + hcd->regs = base; + hcd->region = region; + info ("irq %s, %s %p", bufp, + (driver->flags & HCD_MEMORY) ? "pci mem" : "io base", + base); + +// FIXME simpler: make "bus" be that data, not pointer to it. + bus = usb_alloc_bus (&hcd_operations); + if (bus == NULL) { + dbg ("usb_alloc_bus fail"); + retval = -ENOMEM; + free_irq (dev->irq, hcd); + goto clean_3; + } + hcd->bus = bus; + hcd->bus_name = dev->slot_name; + bus->hcpriv = (void *) hcd; + + INIT_LIST_HEAD (&hcd->dev_list); + INIT_LIST_HEAD (&hcd->hcd_list); + + down (&hcd_list_lock); + list_add (&hcd->hcd_list, &hcd_list); + up (&hcd_list_lock); + + usb_register_bus (bus); + + if ((retval = driver->start (hcd)) < 0) + usb_hcd_pci_remove (dev); + + return retval; +} +EXPORT_SYMBOL (usb_hcd_pci_probe); + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_pci_remove - shutdown processing for PCI-based HCDs + * @dev: USB Host Controller being removed + * + * Reverses the effect of usb_hcd_pci_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + * Store this function in the HCD's struct pci_driver as remove(). + */ +void usb_hcd_pci_remove (struct pci_dev *dev) +{ + struct usb_hcd *hcd; + struct usb_device *hub; + + hcd = (struct usb_hcd *) dev->driver_data; + if (!hcd) + return; + info ("remove: %s, state %x", hcd->bus_name, hcd->state); + + if (in_interrupt ()) BUG (); + + hub = hcd->bus->root_hub; + hcd->state = USB_STATE_QUIESCING; + + dbg ("%s: roothub graceful disconnect", hcd->bus_name); + usb_disconnect (&hub); + // usb_disconnect (&hcd->bus->root_hub); + + hcd->driver->stop (hcd); + hcd->state = USB_STATE_HALT; + + free_irq (hcd->irq, hcd); + if (hcd->driver->flags & HCD_MEMORY) { + iounmap (hcd->regs); + release_mem_region (pci_resource_start (dev, 0), + pci_resource_len (dev, 0)); + } else { + release_region (pci_resource_start (dev, hcd->region), + pci_resource_len (dev, hcd->region)); + } + + down (&hcd_list_lock); + list_del (&hcd->hcd_list); + up (&hcd_list_lock); + + usb_deregister_bus (hcd->bus); + usb_free_bus (hcd->bus); + hcd->bus = NULL; + + hcd->driver->hcd_free (hcd); +} +EXPORT_SYMBOL (usb_hcd_pci_remove); + + +#ifdef CONFIG_PM + +/* + * Some "sleep" power levels imply updating struct usb_driver + * to include a callback asking hcds to do their bit by checking + * if all the drivers can suspend. Gets involved with remote wakeup. + * + * If there are pending urbs, then HCs will need to access memory, + * causing extra power drain. New sleep()/wakeup() PM calls might + * be needed, beyond PCI suspend()/resume(). The root hub timer + * still be accessing memory though ... + * + * FIXME: USB should have some power budgeting support working with + * all kinds of hubs. + * + * FIXME: This assumes only D0->D3 suspend and D3->D0 resume. + * D1 and D2 states should do something, yes? + * + * FIXME: Should provide generic enable_wake(), calling pci_enable_wake() + * for all supported states, so that USB remote wakeup can work for any + * devices that support it (and are connected via powered hubs). + * + * FIXME: resume doesn't seem to work right any more... + */ + + +// 2.4 kernels have issued concurrent resumes (w/APM) +// we defend against that error; PCI doesn't yet. + +/** + * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD + * @dev: USB Host Controller being suspended + * + * Store this function in the HCD's struct pci_driver as suspend(). + */ +int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state) +{ + struct usb_hcd *hcd; + int retval; + + hcd = (struct usb_hcd *) dev->driver_data; + info ("suspend %s to state %d", hcd->bus_name, state); + + pci_save_state (dev, hcd->pci_state); + + // FIXME for all connected devices, leaf-to-root: + // driver->suspend() + // proposed "new 2.5 driver model" will automate that + + /* driver may want to disable DMA etc */ + retval = hcd->driver->suspend (hcd, state); + hcd->state = USB_STATE_SUSPENDED; + + pci_set_power_state (dev, state); + return retval; +} +EXPORT_SYMBOL (usb_hcd_pci_suspend); + +/** + * usb_hcd_pci_resume - power management resume of a PCI-based HCD + * @dev: USB Host Controller being resumed + * + * Store this function in the HCD's struct pci_driver as resume(). + */ +int usb_hcd_pci_resume (struct pci_dev *dev) +{ + struct usb_hcd *hcd; + int retval; + + hcd = (struct usb_hcd *) dev->driver_data; + info ("resume %s", hcd->bus_name); + + /* guard against multiple resumes (APM bug?) */ + atomic_inc (&hcd->resume_count); + if (atomic_read (&hcd->resume_count) != 1) { + err ("concurrent PCI resumes for %s", hcd->bus_name); + retval = 0; + goto done; + } + + retval = -EBUSY; + if (hcd->state != USB_STATE_SUSPENDED) { + dbg ("can't resume, not suspended!"); + goto done; + } + hcd->state = USB_STATE_RESUMING; + + pci_set_power_state (dev, 0); + pci_restore_state (dev, hcd->pci_state); + + retval = hcd->driver->resume (hcd); + if (!HCD_IS_RUNNING (hcd->state)) { + dbg ("resume %s failure, retval %d", hcd->bus_name, retval); + hc_died (hcd); +// FIXME: recover, reset etc. + } else { + // FIXME for all connected devices, root-to-leaf: + // driver->resume (); + // proposed "new 2.5 driver model" will automate that + } + +done: + atomic_dec (&hcd->resume_count); + return retval; +} +EXPORT_SYMBOL (usb_hcd_pci_resume); + +#endif /* CONFIG_PM */ + +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * Generic HC operations. + */ + +/*-------------------------------------------------------------------------*/ + +/* called from khubd, or root hub init threads for hcd-private init */ +static int hcd_alloc_dev (struct usb_device *udev) +{ + struct hcd_dev *dev; + struct usb_hcd *hcd; + unsigned long flags; + + if (!udev || udev->hcpriv) + return -EINVAL; + if (!udev->bus || !udev->bus->hcpriv) + return -ENODEV; + hcd = udev->bus->hcpriv; + if (hcd->state == USB_STATE_QUIESCING) + return -ENOLINK; + + dev = (struct hcd_dev *) kmalloc (sizeof *dev, GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + memset (dev, 0, sizeof *dev); + + INIT_LIST_HEAD (&dev->dev_list); + INIT_LIST_HEAD (&dev->urb_list); + + spin_lock_irqsave (&hcd_data_lock, flags); + list_add (&dev->dev_list, &hcd->dev_list); + // refcount is implicit + udev->hcpriv = dev; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static void hc_died (struct usb_hcd *hcd) +{ + struct list_head *devlist, *urblist; + struct hcd_dev *dev; + struct urb *urb; + unsigned long flags; + + /* flag every pending urb as done */ + spin_lock_irqsave (&hcd_data_lock, flags); + list_for_each (devlist, &hcd->dev_list) { + dev = list_entry (devlist, struct hcd_dev, dev_list); + list_for_each (urblist, &dev->urb_list) { + urb = list_entry (urblist, struct urb, urb_list); + dbg ("shutdown %s urb %p pipe %x, current status %d", + hcd->bus_name, urb, urb->pipe, urb->status); + if (urb->status == -EINPROGRESS) + urb->status = -ESHUTDOWN; + } + } + urb = (struct urb *) hcd->rh_timer.data; + if (urb) + urb->status = -ESHUTDOWN; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + if (urb) + rh_status_dequeue (hcd, urb); + hcd->driver->stop (hcd); +} + +/*-------------------------------------------------------------------------*/ + +/* may be called in any context with a valid urb->dev usecount */ +/* caller surrenders "ownership" of urb (and chain at urb->next). */ + +static int hcd_submit_urb (struct urb *urb) +{ + int status; + struct usb_hcd *hcd; + struct hcd_dev *dev; + unsigned long flags; + int pipe; + int mem_flags; + + if (!urb || urb->hcpriv || !urb->complete) + return -EINVAL; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + INIT_LIST_HEAD (&urb->urb_list); + + if (!urb->dev || !urb->dev->bus || urb->dev->devnum <= 0) + return -ENODEV; + hcd = urb->dev->bus->hcpriv; + dev = urb->dev->hcpriv; + if (!hcd || !dev) + return -ENODEV; + + /* can't submit new urbs when quiescing, halted, ... */ + if (hcd->state == USB_STATE_QUIESCING || !HCD_IS_RUNNING (hcd->state)) + return -ESHUTDOWN; + pipe = urb->pipe; + if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe), + usb_pipeout (pipe))) + return -EPIPE; + + // FIXME paging/swapping requests over USB should not use GFP_KERNEL + // and might even need to use GFP_NOIO ... that flag actually needs + // to be passed from the higher level. + mem_flags = in_interrupt () ? GFP_ATOMIC : GFP_KERNEL; + +#ifdef DEBUG + { + unsigned int orig_flags = urb->transfer_flags; + unsigned int allowed; + + /* enforce simple/standard policy */ + allowed = USB_ASYNC_UNLINK; // affects later unlinks + allowed |= USB_NO_FSBR; // only affects UHCI + switch (usb_pipetype (pipe)) { + case PIPE_CONTROL: + allowed |= USB_DISABLE_SPD; + break; + case PIPE_BULK: + allowed |= USB_DISABLE_SPD | USB_QUEUE_BULK + | USB_ZERO_PACKET | URB_NO_INTERRUPT; + break; + case PIPE_INTERRUPT: + allowed |= USB_DISABLE_SPD; + break; + case PIPE_ISOCHRONOUS: + allowed |= USB_ISO_ASAP; + break; + } + urb->transfer_flags &= allowed; + + /* warn if submitter gave bogus flags */ + if (urb->transfer_flags != orig_flags) + warn ("BOGUS urb flags, %x --> %x", + orig_flags, urb->transfer_flags); + } +#endif + /* + * FIXME: alloc periodic bandwidth here, for interrupt and iso? + * Need to look at the ring submit mechanism for iso tds ... they + * aren't actually "periodic" in 2.4 kernels. + * + * FIXME: make urb timeouts be generic, keeping the HCD cores + * as simple as possible. + */ + + // NOTE: a generic device/urb monitoring hook would go here. + // hcd_monitor_hook(MONITOR_URB_SUBMIT, urb) + // It would catch submission paths for all urbs. + + /* + * Atomically queue the urb, first to our records, then to the HCD. + * Access to urb->status is controlled by urb->lock ... changes on + * i/o completion (normal or fault) or unlinking. + */ + + // FIXME: verify that quiescing hc works right (RH cleans up) + + spin_lock_irqsave (&hcd_data_lock, flags); + if (HCD_IS_RUNNING (hcd->state) && hcd->state != USB_STATE_QUIESCING) { + usb_inc_dev_use (urb->dev); + list_add (&urb->urb_list, &dev->urb_list); + status = 0; + } else { + INIT_LIST_HEAD (&urb->urb_list); + status = -ESHUTDOWN; + } + spin_unlock_irqrestore (&hcd_data_lock, flags); + + if (!status) { + if (urb->dev == hcd->bus->root_hub) + status = rh_urb_enqueue (hcd, urb); + else + status = hcd->driver->urb_enqueue (hcd, urb, mem_flags); + } + if (status) { + if (urb->dev) { + urb->status = status; + usb_hcd_giveback_urb (hcd, urb); + } + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* called in any context */ +static int hcd_get_frame_number (struct usb_device *udev) +{ + struct usb_hcd *hcd = (struct usb_hcd *)udev->bus->hcpriv; + return hcd->driver->get_frame_number (hcd); +} + +/*-------------------------------------------------------------------------*/ + +struct completion_splice { // modified urb context: + /* did we complete? */ + int done; + + /* original urb data */ + void (*complete)(struct urb *); + void *context; +}; + +static void unlink_complete (struct urb *urb) +{ + struct completion_splice *splice; + + splice = (struct completion_splice *) urb->context; + + /* issue original completion call */ + urb->complete = splice->complete; + urb->context = splice->context; + urb->complete (urb); + + splice->done = 1; +} + +/* + * called in any context; note ASYNC_UNLINK restrictions + * + * caller guarantees urb won't be recycled till both unlink() + * and the urb's completion function return + */ +static int hcd_unlink_urb (struct urb *urb) +{ + struct hcd_dev *dev; + struct usb_hcd *hcd = 0; + unsigned long flags; + struct completion_splice splice; + int retval; + + if (!urb) + return -EINVAL; + + // FIXME: add some explicit records to flag the + // state where the URB is "in periodic completion". + // Workaround is for driver to set the urb status + // to "-EINPROGRESS", so it can get through here + // and unlink from the completion handler. + + /* + * we contend for urb->status with the hcd core, + * which changes it while returning the urb. + */ + spin_lock_irqsave (&urb->lock, flags); + if (!urb->hcpriv + || urb->status != -EINPROGRESS + || urb->transfer_flags & USB_TIMEOUT_KILLED) { + retval = -EINVAL; + goto done; + } + + if (!urb->dev || !urb->dev->bus) { + retval = -ENODEV; + goto done; + } + dev = urb->dev->hcpriv; + hcd = urb->dev->bus->hcpriv; + if (!dev || !hcd) { + retval = -ENODEV; + goto done; + } + + /* maybe set up to block on completion notification */ + if ((urb->transfer_flags & USB_TIMEOUT_KILLED)) + urb->status = -ETIMEDOUT; + else if (!(urb->transfer_flags & USB_ASYNC_UNLINK)) { + if (in_interrupt ()) { + dbg ("non-async unlink in_interrupt"); + retval = -EWOULDBLOCK; + goto done; + } + /* synchronous unlink: block till we see the completion */ + splice.done = 0; + splice.complete = urb->complete; + splice.context = urb->context; + urb->complete = unlink_complete; + urb->context = &splice; + urb->status = -ENOENT; + } else { + /* asynchronous unlink */ + urb->status = -ECONNRESET; + } + spin_unlock_irqrestore (&urb->lock, flags); + + if (urb == (struct urb *) hcd->rh_timer.data) { + rh_status_dequeue (hcd, urb); + retval = 0; + } else { + retval = hcd->driver->urb_dequeue (hcd, urb); +// FIXME: if retval and we tried to splice, whoa!! +if (retval && urb->status == -ENOENT) err ("whoa! retval %d", retval); + } + + /* block till giveback, if needed */ + if (!(urb->transfer_flags & (USB_ASYNC_UNLINK|USB_TIMEOUT_KILLED)) + && HCD_IS_RUNNING (hcd->state) + && !retval) { + while (!splice.done) { + set_current_state (TASK_UNINTERRUPTIBLE); + schedule_timeout ((2/*msec*/ * HZ) / 1000); + dbg ("%s: wait for giveback urb %p", + hcd->bus_name, urb); + } + } + goto bye; +done: + spin_unlock_irqrestore (&urb->lock, flags); +bye: + if (retval) + dbg ("%s: hcd_unlink_urb fail %d", + hcd ? hcd->bus_name : "(no bus?)", + retval); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup */ + +// FIXME: likely best to have explicit per-setting (config+alt) +// setup primitives in the usbcore-to-hcd driver API, so nothing +// is implicit. kernel 2.5 needs a bunch of config cleanup... + +static int hcd_free_dev (struct usb_device *udev) +{ + struct hcd_dev *dev; + struct usb_hcd *hcd; + unsigned long flags; + + if (!udev || !udev->hcpriv) + return -EINVAL; + + if (!udev->bus || !udev->bus->hcpriv) + return -ENODEV; + + // should udev->devnum == -1 ?? + + dev = udev->hcpriv; + hcd = udev->bus->hcpriv; + + /* device driver problem with refcounts? */ + if (!list_empty (&dev->urb_list)) { + dbg ("free busy dev, %s devnum %d (bug!)", + hcd->bus_name, udev->devnum); + return -EINVAL; + } + + hcd->driver->free_config (hcd, udev); + + spin_lock_irqsave (&hcd_data_lock, flags); + list_del (&dev->dev_list); + udev->hcpriv = NULL; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + kfree (dev); + return 0; +} + +static struct usb_operations hcd_operations = { + allocate: hcd_alloc_dev, + get_frame_number: hcd_get_frame_number, + submit_urb: hcd_submit_urb, + unlink_urb: hcd_unlink_urb, + deallocate: hcd_free_dev, +}; + +/*-------------------------------------------------------------------------*/ + +static void hcd_irq (int irq, void *__hcd, struct pt_regs * r) +{ + struct usb_hcd *hcd = __hcd; + int start = hcd->state; + + hcd->driver->irq (hcd); + if (hcd->state != start && hcd->state == USB_STATE_HALT) + hc_died (hcd); +} + +/*-------------------------------------------------------------------------*/ + +/** + * usb_hcd_giveback_urb - return URB from HCD to device driver + * @hcd: host controller returning the URB + * @urb: urb being returned to the USB device driver. + * + * This hands the URB from HCD to its USB device driver, using its + * completion function. The HCD has freed all per-urb resources + * (and is done using urb->hcpriv). It also released all HCD locks; + * the device driver won't cause deadlocks if it resubmits this URB, + * and won't confuse things by modifying and resubmitting this one. + * Bandwidth and other resources will be deallocated. + * + * HCDs must not use this for periodic URBs that are still scheduled + * and will be reissued. They should just call their completion handlers + * until the urb is returned to the device driver by unlinking. + * + * In common cases, urb->next will be submitted before the completion + * function gets called. That's not done if the URB includes error + * status (including unlinking). + */ +void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb) +{ + int flags; + struct usb_device *dev; + + /* Release periodic transfer bandwidth */ + if (urb->bandwidth) { + switch (usb_pipetype (urb->pipe)) { + case PIPE_INTERRUPT: + usb_release_bandwidth (urb->dev, urb, 0); + break; + case PIPE_ISOCHRONOUS: + usb_release_bandwidth (urb->dev, urb, 1); + break; + } + } + + /* clear all state linking urb to this dev (and hcd) */ + + spin_lock_irqsave (&hcd_data_lock, flags); + list_del_init (&urb->urb_list); + dev = urb->dev; + urb->dev = NULL; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + // NOTE: a generic device/urb monitoring hook would go here. + // hcd_monitor_hook(MONITOR_URB_FINISH, urb, dev) + // It would catch exit/unlink paths for all urbs, but non-exit + // completions for periodic urbs need hooks inside the HCD. + // hcd_monitor_hook(MONITOR_URB_UPDATE, urb, dev) + + if (urb->status) + dbg ("giveback urb %p status %d", urb, urb->status); + + /* if no error, make sure urb->next progresses */ + else if (urb->next) { + int status; + + status = usb_submit_urb (urb->next); + if (status) { + dbg ("urb %p chain fail, %d", urb->next, status); + urb->next->status = -ENOTCONN; + } + + /* HCDs never modify the urb->next chain, and only use it here, + * so that if urb->complete sees an URB there with -ENOTCONN, + * it knows the driver chained it but it couldn't be submitted. + */ + } + + /* pass ownership to the completion handler */ + usb_dec_dev_use (dev); + urb->complete (urb); +} +EXPORT_SYMBOL (usb_hcd_giveback_urb); diff -Nru a/drivers/usb/hcd.h b/drivers/usb/hcd.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd.h Wed Dec 19 10:26:03 2001 @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2001 by David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +/*-------------------------------------------------------------------------*/ + +/* + * USB Host Controller Driver (usb_hcd) framework + * + * Since "struct usb_bus" is so thin, you can't share much code in it. + * This framework is a layer over that, and should be more sharable. + */ + +/*-------------------------------------------------------------------------*/ + +struct usb_hcd { /* usb_bus.hcpriv points to this */ + + /* + * housekeeping + */ + struct usb_bus *bus; /* hcd is-a bus */ + struct list_head hcd_list; + + const char *bus_name; + + const char *description; /* "ehci-hcd" etc */ + + struct timer_list rh_timer; /* drives root hub */ + struct list_head dev_list; /* devices on this bus */ + + /* + * hardware info/state + */ + struct hc_driver *driver; /* hw-specific hooks */ + int irq; /* irq allocated */ + void *regs; /* device memory/io */ + +#ifdef CONFIG_PCI + /* a few non-PCI controllers exist, mostly for OHCI */ + struct pci_dev *pdev; /* pci is typical */ + int region; /* pci region for regs */ + u32 pci_state [16]; /* for PM state save */ + atomic_t resume_count; /* multiple resumes issue */ +#endif + + int state; +# define __ACTIVE 0x01 +# define __SLEEPY 0x02 +# define __SUSPEND 0x04 +# define __TRANSIENT 0x80 + +# define USB_STATE_HALT 0 +# define USB_STATE_RUNNING (__ACTIVE) +# define USB_STATE_READY (__ACTIVE|__SLEEPY) +# define USB_STATE_QUIESCING (__SUSPEND|__TRANSIENT|__ACTIVE) +# define USB_STATE_RESUMING (__SUSPEND|__TRANSIENT) +# define USB_STATE_SUSPENDED (__SUSPEND) + +#define HCD_IS_RUNNING(state) ((state) & __ACTIVE) +#define HCD_IS_SUSPENDED(state) ((state) & __SUSPEND) + + /* more shared queuing code would be good; it should support + * smarter scheduling, handle transaction translators, etc; + * input size of periodic table to an interrupt scheduler. + * (ohci 32, uhci 1024, ehci 256/512/1024). + */ +}; + +struct hcd_dev { /* usb_device.hcpriv points to this */ + struct list_head dev_list; /* on this hcd */ + struct list_head urb_list; /* pending on this dev */ + + /* per-configuration HC/HCD state, such as QH or ED */ + void *ep[32]; +}; + +// urb.hcpriv is really hardware-specific + +struct hcd_timeout { /* timeouts we allocate */ + struct list_head timeout_list; + struct timer_list timer; +}; + +/*-------------------------------------------------------------------------*/ + +/* each driver provides one of these, and hardware init support */ + +struct hc_driver { + const char *description; /* "ehci-hcd" etc */ + + /* irq handler */ + void (*irq) (struct usb_hcd *hcd); + + int flags; +#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */ +#define HCD_USB11 0x0010 /* USB 1.1 */ +#define HCD_USB2 0x0020 /* USB 2.0 */ + + /* called to init HCD and root hub */ + int (*start) (struct usb_hcd *hcd); + + /* called after all devices were suspended */ + int (*suspend) (struct usb_hcd *hcd, u32 state); + + /* called before any devices get resumed */ + int (*resume) (struct usb_hcd *hcd); + + /* cleanly make HCD stop writing memory and doing I/O */ + void (*stop) (struct usb_hcd *hcd); + + /* return current frame number */ + int (*get_frame_number) (struct usb_hcd *hcd); + +// FIXME: rework generic-to-specific HCD linkage (specific contains generic) + + /* memory lifecycle */ + struct usb_hcd *(*hcd_alloc) (void); + void (*hcd_free) (struct usb_hcd *hcd); + + /* manage i/o requests, device state */ + int (*urb_enqueue) (struct usb_hcd *hcd, struct urb *urb, + int mem_flags); + int (*urb_dequeue) (struct usb_hcd *hcd, struct urb *urb); + + // frees configuration resources -- allocated as needed during + // urb_enqueue, and not freed by urb_dequeue + void (*free_config) (struct usb_hcd *hcd, + struct usb_device *dev); + + /* root hub support */ + int (*hub_status_data) (struct usb_hcd *hcd, char *buf); + int (*hub_control) (struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); +}; + +extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb); + +#ifdef CONFIG_PCI + +extern int usb_hcd_pci_probe (struct pci_dev *dev, + const struct pci_device_id *id); +extern void usb_hcd_pci_remove (struct pci_dev *dev); + +#ifdef CONFIG_PM +// FIXME: see Documentation/power/pci.txt (2.4.6 and later?) +// extern int usb_hcd_pci_save_state (struct pci_dev *dev, u32 state); +extern int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state); +extern int usb_hcd_pci_resume (struct pci_dev *dev); +// extern int usb_hcd_pci_enable_wake (struct pci_dev *dev, u32 state, int flg); +#endif /* CONFIG_PM */ + +#endif /* CONFIG_PCI */ + +/*-------------------------------------------------------------------------*/ + +/* + * HCD Root Hub support + */ + +#include "hub.h" + +/* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */ +#define DeviceRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) +#define DeviceOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) + +#define InterfaceRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) + +#define EndpointRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) +#define EndpointOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) + +/* table 9.6 standard features */ +#define DEVICE_REMOTE_WAKEUP 1 +#define ENDPOINT_HALT 0 + +/* class requests from the USB 2.0 hub spec, table 11-15 */ +/* GetBusState and SetHubDescriptor are optional, omitted */ +#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE) +#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE) +#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR) +#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS) +#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS) +#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE) +#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE) + + +/*-------------------------------------------------------------------------*/ + +/* hub.h ... DeviceRemovable in 2.4.2-ac11, gone in 2.4.10 */ +// bleech -- resurfaced in 2.4.11 or 2.4.12 +#define bitmap DeviceRemovable + + +/*-------------------------------------------------------------------------*/ + +/* random stuff */ + +#define RUN_CONTEXT (in_irq () ? "in_irq" \ + : (in_interrupt () ? "in_interrupt" : "can sleep")) diff -Nru a/drivers/usb/hub.c b/drivers/usb/hub.c --- a/drivers/usb/hub.c Wed Dec 19 10:26:03 2001 +++ b/drivers/usb/hub.c Wed Dec 19 10:26:03 2001 @@ -35,7 +35,7 @@ static DECLARE_MUTEX(usb_address0_sem); static LIST_HEAD(hub_event_list); /* List of hubs needing servicing */ -static LIST_HEAD(hub_list); /* List containing all of the hubs (for cleanup) */ +static LIST_HEAD(hub_list); /* List of all hubs (for cleanup) */ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait); static int khubd_pid = 0; /* PID of khubd */ @@ -110,22 +110,30 @@ data, sizeof(struct usb_hub_status), HZ); } +/* completion function, fires on port status changes and various faults */ static void hub_irq(struct urb *urb) { struct usb_hub *hub = (struct usb_hub *)urb->context; unsigned long flags; - /* Cause a hub reset after 10 consecutive errors */ - if (urb->status) { - if (urb->status == -ENOENT) - return; - - dbg("nonzero status in irq %d", urb->status); + switch (urb->status) { + case -ENOENT: /* synchronous unlink */ + case -ECONNRESET: /* async unlink */ + case -ESHUTDOWN: /* hardware going away */ + return; + default: /* presumably an error */ + /* Cause a hub reset after 10 consecutive errors */ + dbg("hub '%s' status %d for interrupt transfer", + urb->dev->devpath, urb->status); if ((++hub->nerrors < 10) || hub->error) return; - hub->error = urb->status; + /* FALL THROUGH */ + + /* let khubd handle things */ + case 0: /* we got data: port status changed */ + break; } hub->nerrors = 0; @@ -152,7 +160,8 @@ wait_ms(hub->descriptor->bPwrOn2PwrGood * 2); } -static int usb_hub_configure(struct usb_hub *hub, struct usb_endpoint_descriptor *endpoint) +static int usb_hub_configure(struct usb_hub *hub, + struct usb_endpoint_descriptor *endpoint) { struct usb_device *dev = hub->dev; struct usb_hub_status hubstatus; @@ -162,22 +171,30 @@ hub->descriptor = kmalloc(sizeof(*hub->descriptor), GFP_KERNEL); if (!hub->descriptor) { - err("Unable to kmalloc %Zd bytes for hub descriptor", sizeof(*hub->descriptor)); + err("Unable to kmalloc %Zd bytes for hub descriptor", + sizeof(*hub->descriptor)); return -1; } - /* Request the entire hub descriptor. */ - ret = usb_get_hub_descriptor(dev, hub->descriptor, sizeof(*hub->descriptor)); - /* descriptor> is large enough for a hub with 127 ports; - * the hub can/will return fewer bytes here. */ + /* Request the entire hub descriptor. + * hub->descriptor can handle USB_MAXCHILDREN ports, + * but the hub can/will return fewer bytes here. + */ + ret = usb_get_hub_descriptor(dev, hub->descriptor, + sizeof(*hub->descriptor)); if (ret < 0) { err("Unable to get hub descriptor (err = %d)", ret); kfree(hub->descriptor); return -1; + } else if (hub->descriptor->bNbrPorts > USB_MAXCHILDREN) { + err("Hub is too big! %d children", hub->descriptor->bNbrPorts); + kfree(hub->descriptor); + return -1; } dev->maxchild = hub->descriptor->bNbrPorts; - info("%d port%s detected", hub->descriptor->bNbrPorts, (hub->descriptor->bNbrPorts == 1) ? "" : "s"); + info("%d port%s detected", dev->maxchild, + (dev->maxchild == 1) ? "" : "s"); le16_to_cpus(&hub->descriptor->wHubCharacteristics); @@ -217,9 +234,12 @@ break; case 1: dbg("Single TT"); + hub->tt.hub = dev; break; case 2: - dbg("Multiple TT"); + dbg("TT per port"); + hub->tt.hub = dev; + hub->tt.multi = 1; break; default: dbg("Unrecognized hub protocol %d", @@ -244,13 +264,18 @@ } dbg("Port indicators are %s supported", - (hub->descriptor->wHubCharacteristics & HUB_CHAR_PORTIND) ? "" : "not"); + (hub->descriptor->wHubCharacteristics & HUB_CHAR_PORTIND) + ? "" : "not"); - dbg("power on to power good time: %dms", hub->descriptor->bPwrOn2PwrGood * 2); - dbg("hub controller current requirement: %dmA", hub->descriptor->bHubContrCurrent); + dbg("power on to power good time: %dms", + hub->descriptor->bPwrOn2PwrGood * 2); + dbg("hub controller current requirement: %dmA", + hub->descriptor->bHubContrCurrent); for (i = 0; i < dev->maxchild; i++) - portstr[i] = hub->descriptor->DeviceRemovable[((i + 1) / 8)] & (1 << ((i + 1) % 8)) ? 'F' : 'R'; + portstr[i] = hub->descriptor->DeviceRemovable + [((i + 1) / 8)] & (1 << ((i + 1) % 8)) + ? 'F' : 'R'; portstr[dev->maxchild] = 0; dbg("port removable status: %s", portstr); @@ -265,7 +290,8 @@ le16_to_cpus(&hubstatus.wHubStatus); dbg("local power source is %s", - (hubstatus.wHubStatus & HUB_STATUS_LOCAL_POWER) ? "lost (inactive)" : "good"); + (hubstatus.wHubStatus & HUB_STATUS_LOCAL_POWER) + ? "lost (inactive)" : "good"); dbg("%sover-current condition exists", (hubstatus.wHubStatus & HUB_STATUS_OVERCURRENT) ? "" : "no "); @@ -337,14 +363,15 @@ } /* If it's not an interrupt endpoint, we'd better punt! */ - if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) { - err("Device #%d is hub class, but has endpoint other than interrupt?", + if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + != USB_ENDPOINT_XFER_INT) { + err("Device #%d is hub class, but endpoint is not interrupt?", dev->devnum); return NULL; } /* We found a hub */ - info("USB hub found"); + info("USB hub found at %s", dev->devpath); hub = kmalloc(sizeof(*hub), GFP_KERNEL); if (!hub) { @@ -367,7 +394,7 @@ if (usb_hub_configure(hub, endpoint) >= 0) return hub; - err("hub configuration failed for device #%d", dev->devnum); + err("hub configuration failed for device at %s", dev->devpath); /* free hub, but first clean up its list. */ spin_lock_irqsave(&hub_event_lock, flags); @@ -436,7 +463,8 @@ if (hub->children[i] == NULL) info->port[i] = 0; else - info->port[i] = hub->children[i]->devnum; + info->port[i] = + hub->children[i]->devnum; } } spin_unlock_irqrestore(&hub_event_lock, flags); @@ -493,7 +521,7 @@ } } - err("cannot disconnect hub %d", dev->devnum); + err("cannot disconnect hub %s", dev->devpath); } #define HUB_RESET_TRIES 5 @@ -510,14 +538,17 @@ struct usb_port_status portsts; unsigned short portchange, portstatus; - for (delay_time = 0; delay_time < HUB_RESET_TIMEOUT; delay_time += delay) { + for (delay_time = 0; + delay_time < HUB_RESET_TIMEOUT; + delay_time += delay) { /* wait to give the device a chance to reset */ wait_ms(delay); /* read and decode port status */ ret = usb_get_port_status(hub, port + 1, &portsts); if (ret < 0) { - err("get_port_status(%d) failed (err = %d)", port + 1, ret); + err("get_port_status(%d) failed (err = %d)", + port + 1, ret); return -1; } @@ -546,8 +577,8 @@ if (delay_time >= 2 * HUB_SHORT_RESET_TIME) delay = HUB_LONG_RESET_TIME; - dbg("port %d of hub %d not reset yet, waiting %dms", port + 1, - hub->devnum, delay); + dbg("port %d of hub %s not reset yet, waiting %dms", port + 1, + hub->devpath, delay); } return -1; @@ -566,17 +597,18 @@ /* return on disconnect or reset */ status = usb_hub_port_wait_reset(hub, port, dev, delay); if (status != -1) { - usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_C_RESET); + usb_clear_port_feature(hub, + port + 1, USB_PORT_FEAT_C_RESET); return status; } - dbg("port %d of hub %d not enabled, trying reset again...", - port + 1, hub->devnum); + dbg("port %d of hub %s not enabled, trying reset again...", + port + 1, hub->devpath); delay = HUB_LONG_RESET_TIME; } - err("Cannot enable port %i of hub %d, disabling port.", - port + 1, hub->devnum); + err("Cannot enable port %i of hub %s, disabling port.", + port + 1, hub->devpath); err("Maybe the USB cable is bad?"); return -1; @@ -588,23 +620,24 @@ ret = usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_ENABLE); if (ret) - err("cannot disable port %d of hub %d (err = %d)", - port + 1, hub->devnum, ret); + err("cannot disable port %d of hub %s (err = %d)", + port + 1, hub->devpath, ret); } -static void usb_hub_port_connect_change(struct usb_device *hub, int port, +static void usb_hub_port_connect_change(struct usb_hub *hubstate, int port, struct usb_port_status *portsts) { + struct usb_device *hub = hubstate->dev; struct usb_device *dev; unsigned short portstatus, portchange; unsigned int delay = HUB_SHORT_RESET_TIME; int i; - char *portstr, *tempstr; portstatus = le16_to_cpu(portsts->wPortStatus); portchange = le16_to_cpu(portsts->wPortChange); - dbg("port %d, portstatus %x, change %x, %s", - port + 1, portstatus, portchange, portspeed (portstatus)); + dbg("hub %s port %d, portstatus %x, change %x, %s", + hub->devpath, port + 1, + portstatus, portchange, portspeed (portstatus)); /* Clear the connection change status */ usb_clear_port_feature(hub, port + 1, USB_PORT_FEAT_C_CONNECTION); @@ -630,11 +663,9 @@ down(&usb_address0_sem); - tempstr = kmalloc(1024, GFP_KERNEL); - portstr = kmalloc(1024, GFP_KERNEL); - for (i = 0; i < HUB_PROBE_TRIES; i++) { - struct usb_device *pdev, *cdev; + struct usb_device *pdev; + int len; /* Allocate a new device struct */ dev = usb_alloc_dev(hub, hub->bus); @@ -645,41 +676,46 @@ hub->children[port] = dev; - /* Reset the device */ + /* Reset the device, and detect its speed */ if (usb_hub_port_reset(hub, port, dev, delay)) { usb_free_dev(dev); break; } - /* Find a new device ID for it */ + /* Find a new address for it */ usb_connect(dev); - /* Create a readable topology string */ - cdev = dev; - pdev = dev->parent; - if (portstr && tempstr) { - portstr[0] = 0; - while (pdev) { - int port; - - for (port = 0; port < pdev->maxchild; port++) - if (pdev->children[port] == cdev) - break; - - strcpy(tempstr, portstr); - if (!strlen(tempstr)) - sprintf(portstr, "%d", port + 1); - else - sprintf(portstr, "%d/%s", port + 1, tempstr); + /* Set up TT records, if needed */ + if (hub->tt) { + dev->tt = hub->tt; + dev->ttport = hub->ttport; + } else if (dev->speed != USB_SPEED_HIGH + && hub->speed == USB_SPEED_HIGH) { + dev->tt = &hubstate->tt; + dev->ttport = port + 1; + } - cdev = pdev; - pdev = pdev->parent; - } - info("USB new device connect on bus%d/%s, assigned device number %d", - dev->bus->busnum, portstr, dev->devnum); - } else - info("USB new device connect on bus%d, assigned device number %d", - dev->bus->busnum, dev->devnum); + /* Save readable and stable topology id, distinguishing + * devices by location for diagnostics, tools, etc. The + * string is a path along hub ports, from the root. Each + * device's id will be stable until USB is re-cabled, and + * hubs are often labled with these port numbers. + * + * Initial size: "/NN" times five hubs + NUL = 16 bytes max + * (quite rare, since most hubs have 4-6 ports). + */ + pdev = dev->parent; + if (pdev->devpath [1] != '\0') /* parent not root */ + len = snprintf (dev->devpath, sizeof dev->devpath, + "%s/%d", pdev->devpath, port + 1); + else /* root == "/", root port 2 == "/2" */ + len = snprintf (dev->devpath, sizeof dev->devpath, + "/%d", port + 1); + if (len == sizeof dev->devpath) + warn ("devpath size! usb/%03d/%03d path %s", + dev->bus->busnum, dev->devnum, dev->devpath); + info("new USB device on bus %d path %s, assigned address %d", + dev->bus->busnum, dev->devpath, dev->devnum); /* Run it through the hoops (find a driver, etc) */ if (!usb_new_device(dev)) @@ -696,10 +732,6 @@ usb_hub_port_disable(hub, port); done: up(&usb_address0_sem); - if (portstr) - kfree(portstr); - if (tempstr) - kfree(tempstr); } static void usb_hub_events(void) @@ -737,10 +769,12 @@ spin_unlock_irqrestore(&hub_event_lock, flags); if (hub->error) { - dbg("resetting hub %d for error %d", dev->devnum, hub->error); + dbg("resetting hub %s for error %d", + dev->devpath, hub->error); if (usb_hub_reset(hub)) { - err("error resetting hub %d - disconnecting", dev->devnum); + err("error resetting hub %s - disconnecting", + dev->devpath); up(&hub->khubd_sem); usb_hub_disconnect(dev); continue; @@ -756,7 +790,8 @@ ret = usb_get_port_status(dev, i + 1, &portsts); if (ret < 0) { - err("get_port_status failed (err = %d)", ret); + err("hub %s get_port_status failed (err = %d)", + dev->devpath, ret); continue; } @@ -764,55 +799,68 @@ portchange = le16_to_cpu(portsts.wPortChange); if (portchange & USB_PORT_STAT_C_CONNECTION) { - dbg("port %d connection change", i + 1); - - usb_hub_port_connect_change(dev, i, &portsts); + dbg("hub %s port %d connection change", + dev->devpath, i + 1); + usb_hub_port_connect_change(hub, i, &portsts); } else if (portchange & USB_PORT_STAT_C_ENABLE) { - dbg("port %d enable change, status %x", i + 1, portstatus); - usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_ENABLE); + dbg("hub %s port %d enable change, status %x", + dev->devpath, i + 1, portstatus); + usb_clear_port_feature(dev, + i + 1, USB_PORT_FEAT_C_ENABLE); /* - * EM interference sometimes causes bad shielded USB devices to - * be shutdown by the hub, this hack enables them again. + * EM interference sometimes causes badly + * shielded USB devices to be shutdown by + * the hub, this hack enables them again. * Works at least with mouse driver. */ - if (!(portstatus & USB_PORT_STAT_ENABLE) && - (portstatus & USB_PORT_STAT_CONNECTION) && (dev->children[i])) { - err("already running port %i disabled by hub (EMI?), re-enabling...", - i + 1); - usb_hub_port_connect_change(dev, i, &portsts); + if (!(portstatus & USB_PORT_STAT_ENABLE) + && (portstatus & USB_PORT_STAT_CONNECTION) + && (dev->children[i])) { + err("already running hub %s port %i " + "disabled by hub (EMI?), " + "re-enabling...", + dev->devpath, i + 1); + usb_hub_port_connect_change(hub, + i, &portsts); } } if (portchange & USB_PORT_STAT_C_SUSPEND) { - dbg("port %d suspend change", i + 1); - usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_SUSPEND); + dbg("hub %s port %d suspend change", + dev->devpath, i + 1); + usb_clear_port_feature(dev, + i + 1, USB_PORT_FEAT_C_SUSPEND); } if (portchange & USB_PORT_STAT_C_OVERCURRENT) { - err("port %d over-current change", i + 1); - usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_OVER_CURRENT); + err("hub %s port %d over-current change", + dev->devpath, i + 1); + usb_clear_port_feature(dev, + i + 1, USB_PORT_FEAT_C_OVER_CURRENT); usb_hub_power_on(hub); } if (portchange & USB_PORT_STAT_C_RESET) { - dbg("port %d reset change", i + 1); - usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_RESET); + dbg("hub %s port %d reset change", + dev->devpath, i + 1); + usb_clear_port_feature(dev, + i + 1, USB_PORT_FEAT_C_RESET); } } /* end for i */ /* deal with hub status changes */ if (usb_get_hub_status(dev, &hubsts) < 0) - err("get_hub_status failed"); + err("get_hub_status %s failed", dev->devpath); else { hubstatus = le16_to_cpup(&hubsts.wHubStatus); hubchange = le16_to_cpup(&hubsts.wHubChange); if (hubchange & HUB_CHANGE_LOCAL_POWER) { - dbg("hub power change"); + dbg("hub %s power change", dev->devpath); usb_clear_hub_feature(dev, C_HUB_LOCAL_POWER); } if (hubchange & HUB_CHANGE_OVERCURRENT) { - dbg("hub overcurrent change"); + dbg("hub %s overcurrent change", dev->devpath); wait_ms(500); /* Cool down */ usb_clear_hub_feature(dev, C_HUB_OVER_CURRENT); usb_hub_power_on(hub); @@ -851,6 +899,8 @@ } static struct usb_device_id hub_id_table [] = { + { match_flags: USB_DEVICE_ID_MATCH_DEV_CLASS, + bDeviceClass: USB_CLASS_HUB}, { match_flags: USB_DEVICE_ID_MATCH_INT_CLASS, bInterfaceClass: USB_CLASS_HUB}, { } /* Terminating entry */ @@ -989,9 +1039,13 @@ ret = usb_get_device_descriptor(dev); if (ret < sizeof(dev->descriptor)) { if (ret < 0) - err("unable to get device descriptor (error=%d)", ret); + err("unable to get device %s descriptor " + "(error=%d)", dev->devpath, ret); else - err("USB device descriptor short read (expected %Zi, got %i)", sizeof(dev->descriptor), ret); + err("USB device %s descriptor short read " + "(expected %Zi, got %i)", + dev->devpath, + sizeof(dev->descriptor), ret); clear_bit(dev->devnum, &dev->bus->devmap.devicemap); dev->devnum = -1; @@ -1015,17 +1069,22 @@ ret = usb_set_configuration(dev, dev->actconfig->bConfigurationValue); if (ret < 0) { - err("failed to set active configuration (error=%d)", ret); + err("failed to set dev %s active configuration (error=%d)", + dev->devpath, ret); return ret; } for (i = 0; i < dev->actconfig->bNumInterfaces; i++) { struct usb_interface *intf = &dev->actconfig->interface[i]; - struct usb_interface_descriptor *as = &intf->altsetting[intf->act_altsetting]; + struct usb_interface_descriptor *as; - ret = usb_set_interface(dev, as->bInterfaceNumber, as->bAlternateSetting); + as = &intf->altsetting[intf->act_altsetting]; + ret = usb_set_interface(dev, as->bInterfaceNumber, + as->bAlternateSetting); if (ret < 0) { - err("failed to set active alternate setting for interface %d (error=%d)", i, ret); + err("failed to set active alternate setting " + "for dev %s interface %d (error=%d)", + dev->devpath, i, ret); return ret; } } diff -Nru a/drivers/usb/hub.h b/drivers/usb/hub.h --- a/drivers/usb/hub.h Wed Dec 19 10:26:02 2001 +++ b/drivers/usb/hub.h Wed Dec 19 10:26:02 2001 @@ -1,6 +1,13 @@ #ifndef __LINUX_HUB_H #define __LINUX_HUB_H +/* + * Hub protocol and driver data structures. + * + * Some of these are known to the "virtual root hub" code + * in host controller drivers. + */ + #include /* @@ -11,6 +18,15 @@ #define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER) /* + * Hub class requests + * See USB 2.0 spec Table 11-16 + */ +#define HUB_CLEAR_TT_BUFFER 8 +#define HUB_RESET_TT 9 +#define HUB_GET_TT_STATE 10 +#define HUB_STOP_TT 11 + +/* * Hub Class feature numbers * See USB 2.0 spec Table 11-17 */ @@ -55,7 +71,7 @@ #define USB_PORT_STAT_SUSPEND 0x0004 #define USB_PORT_STAT_OVERCURRENT 0x0008 #define USB_PORT_STAT_RESET 0x0010 -/* bits 5 for 7 are reserved */ +/* bits 5 to 7 are reserved */ #define USB_PORT_STAT_POWER 0x0100 #define USB_PORT_STAT_LOW_SPEED 0x0200 #define USB_PORT_STAT_HIGH_SPEED 0x0400 @@ -120,22 +136,21 @@ struct usb_device; struct usb_hub { - struct usb_device *dev; - - struct urb *urb; /* Interrupt polling pipe */ - - char buffer[(USB_MAXCHILDREN + 1 + 7) / 8]; /* add 1 bit for hub status change */ - /* and add 7 bits to round up to byte boundary */ - int error; - int nerrors; + struct usb_device *dev; /* the "real" device */ + struct urb *urb; /* for interrupt polling pipe */ - struct list_head hub_list; + /* buffer for urb ... 1 bit each for hub and children, rounded up */ + char buffer[(USB_MAXCHILDREN + 1 + 7) / 8]; - struct list_head event_list; + int error; /* last reported error */ + int nerrors; /* track consecutive errors */ - struct usb_hub_descriptor *descriptor; + struct list_head hub_list; /* all hubs */ + struct list_head event_list; /* hubs w/data or errs ready */ - struct semaphore khubd_sem; + struct usb_hub_descriptor *descriptor; /* class descriptor */ + struct semaphore khubd_sem; + struct usb_tt tt; /* Transaction Translator */ }; #endif /* __LINUX_HUB_H */ diff -Nru a/drivers/usb/inode.c b/drivers/usb/inode.c --- a/drivers/usb/inode.c Wed Dec 19 10:26:03 2001 +++ b/drivers/usb/inode.c Wed Dec 19 10:26:03 2001 @@ -3,8 +3,8 @@ /* * inode.c -- Inode/Dentry functions for the USB device file system. * - * Copyright (C) 2000 - * Thomas Sailer (sailer@ife.ee.ethz.ch) + * Copyright (C) 2000 Thomas Sailer (sailer@ife.ee.ethz.ch) + * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,10 +20,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * - * $Id: inode.c,v 1.3 2000/01/11 13:58:25 tom Exp $ - * * History: * 0.1 04.01.2000 Created + * 0.2 10.12.2001 converted to use the vfs layer better */ /*****************************************************************************/ @@ -32,146 +31,40 @@ #include #include #include -#include -#include -#include +#include #include #include #include #include -#include - -/* --------------------------------------------------------------------- */ - -/* - * This list of superblocks is still used, - * but since usbdevfs became FS_SINGLE - * there is only one super_block. - */ -static LIST_HEAD(superlist); - -struct special { - const char *name; - struct file_operations *fops; - struct inode *inode; - struct list_head inodes; -}; - -static struct special special[] = { - { "devices", &usbdevfs_devices_fops, }, - { "drivers", &usbdevfs_drivers_fops, } -}; - -#define NRSPECIAL (sizeof(special)/sizeof(special[0])) - -/* --------------------------------------------------------------------- */ - -static int dnumber(struct dentry *dentry) -{ - const char *name; - unsigned int s; - - if (dentry->d_name.len != 3) - return -1; - name = dentry->d_name.name; - if (name[0] < '0' || name[0] > '9' || - name[1] < '0' || name[1] > '9' || - name[2] < '0' || name[2] > '9') - return -1; - s = name[0] - '0'; - s = s * 10 + name[1] - '0'; - s = s * 10 + name[2] - '0'; - return s; -} - -/* - * utility functions; should be called with the kernel lock held - * to protect against busses/devices appearing/disappearing - */ - -static void new_dev_inode(struct usb_device *dev, struct super_block *sb) -{ - struct inode *inode; - unsigned int devnum = dev->devnum; - unsigned int busnum = dev->bus->busnum; - - if (devnum < 1 || devnum > 127 || busnum > 255) - return; - inode = iget(sb, IDEVICE | (busnum << 8) | devnum); - if (!inode) { - printk(KERN_ERR "usbdevfs: cannot create inode for bus %u device %u\n", busnum, devnum); - return; - } - inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; - inode->i_uid = sb->u.usbdevfs_sb.devuid; - inode->i_gid = sb->u.usbdevfs_sb.devgid; - inode->i_mode = sb->u.usbdevfs_sb.devmode | S_IFREG; - inode->i_fop = &usbdevfs_device_file_operations; - inode->i_size = sizeof(struct usb_device_descriptor); - inode->u.usbdev_i.p.dev = dev; - list_add_tail(&inode->u.usbdev_i.slist, &sb->u.usbdevfs_sb.ilist); - list_add_tail(&inode->u.usbdev_i.dlist, &dev->inodes); -} -static void recurse_new_dev_inode(struct usb_device *dev, struct super_block *sb) -{ - unsigned int i; - if (!dev) - return; - new_dev_inode(dev, sb); - for (i = 0; i < dev->maxchild; i++) { - if (!dev->children[i]) - continue; - recurse_new_dev_inode(dev->children[i], sb); - } -} - -static void new_bus_inode(struct usb_bus *bus, struct super_block *sb) -{ - struct inode *inode; - unsigned int busnum = bus->busnum; - - if (busnum > 255) - return; - inode = iget(sb, IBUS | (busnum << 8)); - if (!inode) { - printk(KERN_ERR "usbdevfs: cannot create inode for bus %u\n", busnum); - return; - } - inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; - inode->i_uid = sb->u.usbdevfs_sb.busuid; - inode->i_gid = sb->u.usbdevfs_sb.busgid; - inode->i_mode = sb->u.usbdevfs_sb.busmode | S_IFDIR; - inode->i_op = &usbdevfs_bus_inode_operations; - inode->i_fop = &usbdevfs_bus_file_operations; - inode->u.usbdev_i.p.bus = bus; - list_add_tail(&inode->u.usbdev_i.slist, &sb->u.usbdevfs_sb.ilist); - list_add_tail(&inode->u.usbdev_i.dlist, &bus->inodes); -} - -static void free_inode(struct inode *inode) -{ - inode->u.usbdev_i.p.bus = NULL; - inode->u.usbdev_i.p.dev = NULL; - inode->i_mode &= ~S_IRWXUGO; - inode->i_uid = inode->i_gid = 0; - inode->i_size = 0; - list_del(&inode->u.usbdev_i.slist); - INIT_LIST_HEAD(&inode->u.usbdev_i.slist); - list_del(&inode->u.usbdev_i.dlist); - INIT_LIST_HEAD(&inode->u.usbdev_i.dlist); - iput(inode); -} +static struct super_operations usbfs_ops; +static struct address_space_operations usbfs_aops; +static struct file_operations usbfs_dir_operations; +static struct file_operations default_file_operations; +static struct inode_operations usbfs_dir_inode_operations; +static struct vfsmount *usbfs_mount; +static spinlock_t mount_lock = SPIN_LOCK_UNLOCKED; +static int mount_count; /* = 0 */ + +static struct dentry *devices_dentry; +static struct dentry *drivers_dentry; +static int num_buses; /* = 0 */ + +static uid_t devuid; /* = 0 */ +static uid_t busuid; /* = 0 */ +static uid_t listuid; /* = 0 */ +static gid_t devgid; /* = 0 */ +static gid_t busgid; /* = 0 */ +static gid_t listgid; /* = 0 */ +static umode_t devmode = S_IWUSR | S_IRUGO; +static umode_t busmode = S_IXUGO | S_IRUGO; +static umode_t listmode = S_IRUGO; static int parse_options(struct super_block *s, char *data) { - uid_t devuid = 0, busuid = 0, listuid = 0; - gid_t devgid = 0, busgid = 0, listgid = 0; - umode_t devmode = S_IWUSR | S_IRUGO, busmode = S_IXUGO | S_IRUGO, listmode = S_IRUGO; char *curopt = NULL, *value; - /* parse options */ if (data) curopt = strtok(data, ","); for (; curopt; curopt = strtok(NULL, ",")) { @@ -242,481 +135,572 @@ } } - s->u.usbdevfs_sb.devuid = devuid; - s->u.usbdevfs_sb.devgid = devgid; - s->u.usbdevfs_sb.devmode = devmode; - s->u.usbdevfs_sb.busuid = busuid; - s->u.usbdevfs_sb.busgid = busgid; - s->u.usbdevfs_sb.busmode = busmode; - s->u.usbdevfs_sb.listuid = listuid; - s->u.usbdevfs_sb.listgid = listgid; - s->u.usbdevfs_sb.listmode = listmode; + return 0; +} + + +/* --------------------------------------------------------------------- */ +static struct dentry *usbfs_lookup (struct inode *dir, struct dentry *dentry) +{ + d_add(dentry, NULL); + return NULL; +} + +static int usbfs_statfs(struct super_block *sb, struct statfs *buf) +{ + buf->f_type = USBDEVICE_SUPER_MAGIC; + buf->f_bsize = PAGE_CACHE_SIZE; + buf->f_namelen = NAME_MAX; return 0; } -static struct usb_bus *usbdevfs_findbus(int busnr) +static struct inode *usbfs_get_inode (struct super_block *sb, int mode, int dev) { - struct list_head *list; - struct usb_bus *bus; + struct inode *inode = new_inode(sb); - down (&usb_bus_list_lock); - for (list = usb_bus_list.next; list != &usb_bus_list; list = list->next) { - bus = list_entry(list, struct usb_bus, bus_list); - if (bus->busnum == busnr) { - up (&usb_bus_list_lock); - return bus; + if (inode) { + inode->i_mode = mode; + inode->i_uid = current->fsuid; + inode->i_gid = current->fsgid; + inode->i_blksize = PAGE_CACHE_SIZE; + inode->i_blocks = 0; + inode->i_rdev = NODEV; + inode->i_mapping->a_ops = &usbfs_aops; + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + switch (mode & S_IFMT) { + default: + init_special_inode(inode, mode, dev); + break; + case S_IFREG: + inode->i_fop = &default_file_operations; + break; + case S_IFDIR: + inode->i_op = &usbfs_dir_inode_operations; + inode->i_fop = &usbfs_dir_operations; + break; } - } - up (&usb_bus_list_lock); - return NULL; + } + return inode; } -#if 0 -static struct usb_device *finddev(struct usb_device *dev, int devnr) +static int usbfs_mknod (struct inode *dir, struct dentry *dentry, int mode, + int dev) { - unsigned int i; - struct usb_device *d2; + struct inode *inode = usbfs_get_inode(dir->i_sb, mode, dev); + int error = -ENOSPC; - if (!dev) - return NULL; - if (dev->devnum == devnr) - return dev; - for (i = 0; i < dev->maxchild; i++) { - if (!dev->children[i]) - continue; - if ((d2 = finddev(dev->children[i], devnr))) - return d2; - } - return NULL; + if (inode) { + d_instantiate(dentry, inode); + dget(dentry); + error = 0; + } + return error; } -static struct usb_device *usbdevfs_finddevice(struct usb_bus *bus, int devnr) +static int usbfs_mkdir (struct inode *dir, struct dentry *dentry, int mode) { - return finddev(bus->root_hub, devnr); + return usbfs_mknod (dir, dentry, mode | S_IFDIR, 0); } -#endif -/* --------------------------------------------------------------------- */ +static int usbfs_create (struct inode *dir, struct dentry *dentry, int mode) +{ + return usbfs_mknod (dir, dentry, mode | S_IFREG, 0); +} -static int usbdevfs_revalidate(struct dentry *dentry, int flags) +static int usbfs_link (struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) { - struct inode *inode = dentry->d_inode; + struct inode *inode = old_dentry->d_inode; - if (!inode) - return 0; - if (ITYPE(inode->i_ino) == IBUS && !inode->u.usbdev_i.p.bus) - return 0; - if (ITYPE(inode->i_ino) == IDEVICE && !inode->u.usbdev_i.p.dev) - return 0; - return 1; + if(S_ISDIR(inode->i_mode)) + return -EPERM; + + inode->i_nlink++; + atomic_inc(&inode->i_count); + dget(dentry); + d_instantiate(dentry, inode); + return 0; } -static struct dentry_operations usbdevfs_dentry_operations = { - d_revalidate: usbdevfs_revalidate, -}; +static inline int usbfs_positive (struct dentry *dentry) +{ + return dentry->d_inode && !d_unhashed(dentry); +} -static struct dentry *usbdevfs_root_lookup(struct inode *dir, struct dentry *dentry) +static int usbfs_empty (struct dentry *dentry) { - int busnr; - unsigned long ino = 0; - unsigned int i; - struct inode *inode; + struct list_head *list; - /* sanity check */ - if (dir->i_ino != IROOT) - return ERR_PTR(-EINVAL); - dentry->d_op = &usbdevfs_dentry_operations; - busnr = dnumber(dentry); - if (busnr >= 0 && busnr <= 255) - ino = IBUS | (busnr << 8); - if (!ino) { - for (i = 0; i < NRSPECIAL; i++) { - if (strlen(special[i].name) == dentry->d_name.len && - !strncmp(special[i].name, dentry->d_name.name, dentry->d_name.len)) { - ino = ISPECIAL | (i + IROOT + 1); - break; - } + spin_lock(&dcache_lock); + + list_for_each(list, &dentry->d_subdirs) { + struct dentry *de = list_entry(list, struct dentry, d_child); + if (usbfs_positive(de)) { + spin_unlock(&dcache_lock); + return 0; } } - if (!ino) - return ERR_PTR(-ENOENT); - inode = iget(dir->i_sb, ino); - if (!inode) - return ERR_PTR(-EINVAL); - if (inode && ITYPE(ino) == IBUS && inode->u.usbdev_i.p.bus == NULL) { - iput(inode); - inode = NULL; - } - d_add(dentry, inode); - return NULL; + + spin_unlock(&dcache_lock); + return 1; } -static struct dentry *usbdevfs_bus_lookup(struct inode *dir, struct dentry *dentry) +static int usbfs_unlink (struct inode *dir, struct dentry *dentry) { - struct inode *inode; - int devnr; + int error = -ENOTEMPTY; - /* sanity check */ - if (ITYPE(dir->i_ino) != IBUS) - return ERR_PTR(-EINVAL); - dentry->d_op = &usbdevfs_dentry_operations; - devnr = dnumber(dentry); - if (devnr < 1 || devnr > 127) - return ERR_PTR(-ENOENT); - inode = iget(dir->i_sb, IDEVICE | (dir->i_ino & (0xff << 8)) | devnr); - if (!inode) - return ERR_PTR(-EINVAL); - if (inode && inode->u.usbdev_i.p.dev == NULL) { - iput(inode); - inode = NULL; + if (usbfs_empty(dentry)) { + struct inode *inode = dentry->d_inode; + + inode->i_nlink--; + dput(dentry); + error = 0; } - d_add(dentry, inode); - return NULL; + return error; } -static int usbdevfs_root_readdir(struct file *filp, void *dirent, filldir_t filldir) +static int usbfs_rename (struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) { - struct inode *inode = filp->f_dentry->d_inode; - unsigned long ino = inode->i_ino; - struct special *spec; - struct list_head *list; - struct usb_bus *bus; - char numbuf[8]; - unsigned int i; - - /* sanity check */ - if (ino != IROOT) - return -EINVAL; - i = filp->f_pos; - switch (i) { - case 0: - if (filldir(dirent, ".", 1, i, IROOT, DT_DIR) < 0) - return 0; - filp->f_pos++; - i++; - /* fall through */ + int error = -ENOTEMPTY; - case 1: - if (filldir(dirent, "..", 2, i, IROOT, DT_DIR) < 0) - return 0; - filp->f_pos++; - i++; - /* fall through */ - - default: - - while (i >= 2 && i < 2+NRSPECIAL) { - spec = &special[filp->f_pos-2]; - if (filldir(dirent, spec->name, strlen(spec->name), i, ISPECIAL | (filp->f_pos-2+IROOT), DT_UNKNOWN) < 0) - return 0; - filp->f_pos++; - i++; - } - if (i < 2+NRSPECIAL) - return 0; - i -= 2+NRSPECIAL; - down (&usb_bus_list_lock); - for (list = usb_bus_list.next; list != &usb_bus_list; list = list->next) { - if (i > 0) { - i--; - continue; - } - bus = list_entry(list, struct usb_bus, bus_list); - sprintf(numbuf, "%03d", bus->busnum); - if (filldir(dirent, numbuf, 3, filp->f_pos, IBUS | ((bus->busnum & 0xff) << 8), DT_UNKNOWN) < 0) - break; - filp->f_pos++; + if (usbfs_empty(new_dentry)) { + struct inode *inode = new_dentry->d_inode; + if (inode) { + inode->i_nlink--; + dput(new_dentry); } - up (&usb_bus_list_lock); - return 0; + error = 0; } + return error; } -static int bus_readdir(struct usb_device *dev, unsigned long ino, int pos, struct file *filp, void *dirent, filldir_t filldir) +#define usbfs_rmdir usbfs_unlink + +/* default file operations */ +static ssize_t default_read_file (struct file *file, char *buf, + size_t count, loff_t *ppos) { - char numbuf[8]; - unsigned int i; + return 0; +} - if (!dev) - return pos; - sprintf(numbuf, "%03d", dev->devnum); - if (pos > 0) - pos--; - else { - if (filldir(dirent, numbuf, 3, filp->f_pos, ino | (dev->devnum & 0xff), DT_UNKNOWN) < 0) - return -1; - filp->f_pos++; - } - for (i = 0; i < dev->maxchild; i++) { - if (!dev->children[i]) - continue; - pos = bus_readdir(dev->children[i], ino, pos, filp, dirent, filldir); - if (pos < 0) - return -1; - } - return pos; +static ssize_t default_write_file (struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + return count; } -static int usbdevfs_bus_readdir(struct file *filp, void *dirent, filldir_t filldir) +static loff_t default_file_lseek (struct file *file, loff_t offset, int orig) { - struct inode *inode = filp->f_dentry->d_inode; - unsigned long ino = inode->i_ino; - struct usb_bus *bus; + loff_t retval = -EINVAL; - /* sanity check */ - if (ITYPE(ino) != IBUS) - return -EINVAL; - switch ((unsigned int)filp->f_pos) { + switch(orig) { case 0: - if (filldir(dirent, ".", 1, filp->f_pos, ino, DT_DIR) < 0) - return 0; - filp->f_pos++; - /* fall through */ - + if (offset > 0) { + file->f_pos = offset; + retval = file->f_pos; + } + break; case 1: - if (filldir(dirent, "..", 2, filp->f_pos, IROOT, DT_DIR) < 0) - return 0; - filp->f_pos++; - /* fall through */ - + if ((offset + file->f_pos) > 0) { + file->f_pos += offset; + retval = file->f_pos; + } + break; default: - lock_kernel(); - bus = usbdevfs_findbus(IBUSNR(ino)); - bus_readdir(bus->root_hub, IDEVICE | ((bus->busnum & 0xff) << 8), filp->f_pos-2, filp, dirent, filldir); - unlock_kernel(); - return 0; + break; } + return retval; +} + +static int default_open (struct inode *inode, struct file *filp) +{ + if (inode->u.generic_ip) + filp->private_data = inode->u.generic_ip; + + return 0; } -static struct file_operations usbdevfs_root_file_operations = { - readdir: usbdevfs_root_readdir, +static int default_sync_file (struct file *file, struct dentry *dentry, + int datasync) +{ + return 0; +} + +static struct address_space_operations usbfs_aops = { }; -static struct inode_operations usbdevfs_root_inode_operations = { - lookup: usbdevfs_root_lookup, +static struct file_operations usbfs_dir_operations = { + read: generic_read_dir, + readdir: dcache_readdir, + fsync: default_sync_file, }; -static struct file_operations usbdevfs_bus_file_operations = { - readdir: usbdevfs_bus_readdir, +static struct file_operations default_file_operations = { + read: default_read_file, + write: default_write_file, + open: default_open, + llseek: default_file_lseek, + fsync: default_sync_file, + mmap: generic_file_mmap, }; -static struct inode_operations usbdevfs_bus_inode_operations = { - lookup: usbdevfs_bus_lookup, +static struct inode_operations usbfs_dir_inode_operations = { + create: usbfs_create, + lookup: usbfs_lookup, + link: usbfs_link, + unlink: usbfs_unlink, + mkdir: usbfs_mkdir, + rmdir: usbfs_rmdir, + mknod: usbfs_mknod, + rename: usbfs_rename, }; -static void usbdevfs_read_inode(struct inode *inode) -{ - struct special *spec; +static struct super_operations usbfs_ops = { + statfs: usbfs_statfs, + put_inode: force_delete, +}; - inode->i_ctime = inode->i_mtime = inode->i_atime = CURRENT_TIME; - inode->i_mode = S_IFREG; - inode->i_gid = inode->i_uid = 0; - INIT_LIST_HEAD(&inode->u.usbdev_i.dlist); - INIT_LIST_HEAD(&inode->u.usbdev_i.slist); - inode->u.usbdev_i.p.dev = NULL; - inode->u.usbdev_i.p.bus = NULL; - switch (ITYPE(inode->i_ino)) { - case ISPECIAL: - if (inode->i_ino == IROOT) { - inode->i_op = &usbdevfs_root_inode_operations; - inode->i_fop = &usbdevfs_root_file_operations; - inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO; - return; - } - if (inode->i_ino <= IROOT || inode->i_ino > IROOT+NRSPECIAL) - return; - spec = &special[inode->i_ino-(IROOT+1)]; - inode->i_fop = spec->fops; - return; +static struct super_block *usbfs_read_super (struct super_block *sb, void *data, + int silent) +{ + struct inode *inode; + struct dentry *root; - case IDEVICE: - return; + if (parse_options(sb, data)) { + warn("usbfs: mount parameter error:"); + return NULL; + } - case IBUS: - return; + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = USBDEVICE_SUPER_MAGIC; + sb->s_op = &usbfs_ops; + inode = usbfs_get_inode(sb, S_IFDIR | 0755, 0); - default: - return; - } -} + if (!inode) { + dbg("%s: could not get inode!\n",__FUNCTION__); + return NULL; + } -static void usbdevfs_put_super(struct super_block *sb) -{ - list_del(&sb->u.usbdevfs_sb.slist); - INIT_LIST_HEAD(&sb->u.usbdevfs_sb.slist); - while (!list_empty(&sb->u.usbdevfs_sb.ilist)) - free_inode(list_entry(sb->u.usbdevfs_sb.ilist.next, struct inode, u.usbdev_i.slist)); + root = d_alloc_root(inode); + if (!root) { + dbg("%s: could not get root dentry!\n",__FUNCTION__); + iput(inode); + return NULL; + } + sb->s_root = root; + return sb; } -static int usbdevfs_statfs(struct super_block *sb, struct statfs *buf) +/** + * fs_create_by_name - create a file, given a name + * @name: name of file + * @mode: type of file + * @parent: dentry of directory to create it in + * @dentry: resulting dentry of file + * + * There is a bit of overhead in creating a file - basically, we + * have to hash the name of the file, then look it up. This will + * prevent files of the same name. + * We then call the proper vfs_ function to take care of all the + * file creation details. + * This function handles both regular files and directories. + */ +static int fs_create_by_name (const char *name, mode_t mode, + struct dentry *parent, struct dentry **dentry) { - buf->f_type = USBDEVICE_SUPER_MAGIC; - buf->f_bsize = PAGE_SIZE/sizeof(long); /* ??? */ - buf->f_bfree = 0; - buf->f_bavail = 0; - buf->f_ffree = 0; - buf->f_namelen = NAME_MAX; - return 0; -} + struct dentry *d = NULL; + struct qstr qstr; + int error; -static int usbdevfs_remount(struct super_block *s, int *flags, char *data) -{ - struct list_head *ilist = s->u.usbdevfs_sb.ilist.next; - struct inode *inode; - int ret; + /* If the parent is not specified, we create it in the root. + * We need the root dentry to do this, which is in the super + * block. A pointer to that is in the struct vfsmount that we + * have around. + */ + if (!parent ) { + if (usbfs_mount && usbfs_mount->mnt_sb) { + parent = usbfs_mount->mnt_sb->s_root; + } + } - if ((ret = parse_options(s, data))) { - printk(KERN_WARNING "usbdevfs: remount parameter error\n"); - return ret; + if (!parent) { + dbg("Ah! can not find a parent!\n"); + return -EFAULT; } - for (; ilist != &s->u.usbdevfs_sb.ilist; ilist = ilist->next) { - inode = list_entry(ilist, struct inode, u.usbdev_i.slist); + *dentry = NULL; + qstr.name = name; + qstr.len = strlen(name); + qstr.hash = full_name_hash(name,qstr.len); - switch (ITYPE(inode->i_ino)) { - case ISPECIAL : - inode->i_uid = s->u.usbdevfs_sb.listuid; - inode->i_gid = s->u.usbdevfs_sb.listgid; - inode->i_mode = s->u.usbdevfs_sb.listmode | S_IFREG; - break; - case IBUS : - inode->i_uid = s->u.usbdevfs_sb.busuid; - inode->i_gid = s->u.usbdevfs_sb.busgid; - inode->i_mode = s->u.usbdevfs_sb.busmode | S_IFDIR; + parent = dget(parent); + + down(&parent->d_inode->i_sem); + + d = lookup_hash(&qstr,parent); + + error = PTR_ERR(d); + if (!IS_ERR(d)) { + switch(mode & S_IFMT) { + case 0: + case S_IFREG: + error = vfs_create(parent->d_inode,d,mode); break; - case IDEVICE : - inode->i_uid = s->u.usbdevfs_sb.devuid; - inode->i_gid = s->u.usbdevfs_sb.devgid; - inode->i_mode = s->u.usbdevfs_sb.devmode | S_IFREG; + case S_IFDIR: + error = vfs_mkdir(parent->d_inode,d,mode); break; + default: + err("cannot create special files\n"); } + *dentry = d; } + up(&parent->d_inode->i_sem); - return 0; + dput(parent); + return error; } -static struct super_operations usbdevfs_sops = { - read_inode: usbdevfs_read_inode, - put_super: usbdevfs_put_super, - statfs: usbdevfs_statfs, - remount_fs: usbdevfs_remount, -}; - -struct super_block *usbdevfs_read_super(struct super_block *s, void *data, int silent) +static struct dentry *fs_create_file (const char *name, mode_t mode, + struct dentry *parent, void *data, + struct file_operations *fops, + uid_t uid, gid_t gid) { - struct inode *root_inode, *inode; - struct list_head *blist; - struct usb_bus *bus; - unsigned int i; + struct dentry *dentry; + int error; - if (parse_options(s, data)) { - printk(KERN_WARNING "usbdevfs: mount parameter error\n"); - return NULL; + dbg("creating file '%s'\n",name); + + error = fs_create_by_name(name,mode,parent,&dentry); + if (error) { + dentry = NULL; + } else { + if (dentry->d_inode) { + if (data) + dentry->d_inode->u.generic_ip = data; + if (fops) + dentry->d_inode->i_fop = fops; + dentry->d_inode->i_uid = uid; + dentry->d_inode->i_gid = gid; + } } - /* fill superblock */ - s->s_blocksize = 1024; - s->s_blocksize_bits = 10; - s->s_magic = USBDEVICE_SUPER_MAGIC; - s->s_op = &usbdevfs_sops; - INIT_LIST_HEAD(&s->u.usbdevfs_sb.slist); - INIT_LIST_HEAD(&s->u.usbdevfs_sb.ilist); - root_inode = iget(s, IROOT); - if (!root_inode) - goto out_no_root; - s->s_root = d_alloc_root(root_inode); - if (!s->s_root) - goto out_no_root; - list_add_tail(&s->u.usbdevfs_sb.slist, &superlist); - for (i = 0; i < NRSPECIAL; i++) { - if (!(inode = iget(s, IROOT+1+i))) - continue; - inode->i_uid = s->u.usbdevfs_sb.listuid; - inode->i_gid = s->u.usbdevfs_sb.listgid; - inode->i_mode = s->u.usbdevfs_sb.listmode | S_IFREG; - special[i].inode = inode; - list_add_tail(&inode->u.usbdev_i.slist, &s->u.usbdevfs_sb.ilist); - list_add_tail(&inode->u.usbdev_i.dlist, &special[i].inodes); - } - down (&usb_bus_list_lock); - for (blist = usb_bus_list.next; blist != &usb_bus_list; blist = blist->next) { - bus = list_entry(blist, struct usb_bus, bus_list); - new_bus_inode(bus, s); - recurse_new_dev_inode(bus->root_hub, s); - } - up (&usb_bus_list_lock); - return s; - - out_no_root: - printk("usbdevfs_read_super: get root inode failed\n"); - iput(root_inode); - return NULL; + return dentry; } +static void fs_remove_file (struct dentry *dentry) +{ + struct dentry *parent = dentry->d_parent; + + if (!parent || !parent->d_inode) + return; + + down(&parent->d_inode->i_sem); + if (usbfs_positive(dentry)) { + if (dentry->d_inode) { + if (S_ISDIR(dentry->d_inode->i_mode)) + vfs_rmdir(parent->d_inode,dentry); + else + vfs_unlink(parent->d_inode,dentry); + } + + dput(dentry); + } + up(&parent->d_inode->i_sem); +} + +/* --------------------------------------------------------------------- */ + + + /* - * The usbdevfs name is now depreciated (as of 2.5.1). + * The usbdevfs name is now deprecated (as of 2.5.1). * It will be removed when the 2.7.x development cycle is started. * You have been warned :) */ -static DECLARE_FSTYPE(usbdevice_fs_type, "usbdevfs", usbdevfs_read_super, FS_SINGLE); -static DECLARE_FSTYPE(usb_fs_type, "usbfs", usbdevfs_read_super, FS_SINGLE); +static DECLARE_FSTYPE(usbdevice_fs_type, "usbdevfs", usbfs_read_super, FS_SINGLE); +static DECLARE_FSTYPE(usb_fs_type, "usbfs", usbfs_read_super, FS_SINGLE); /* --------------------------------------------------------------------- */ +static int get_mount (void) +{ + struct vfsmount *mnt; -static void update_special_inodes (void) + spin_lock (&mount_lock); + if (usbfs_mount) { + mntget(usbfs_mount); + ++mount_count; + spin_unlock (&mount_lock); + goto go_ahead; + } + + spin_unlock (&mount_lock); + mnt = kern_mount (&usbdevice_fs_type); + if (IS_ERR(mnt)) { + err ("could not mount the fs...erroring out!\n"); + return -ENODEV; + } + spin_lock (&mount_lock); + if (!usbfs_mount) { + usbfs_mount = mnt; + ++mount_count; + spin_unlock (&mount_lock); + goto go_ahead; + } + mntget(usbfs_mount); + ++mount_count; + spin_unlock (&mount_lock); + mntput(mnt); + +go_ahead: + dbg("mount_count = %d\n", mount_count); + return 0; +} + +static void remove_mount (void) +{ + struct vfsmount *mnt; + + spin_lock (&mount_lock); + mnt = usbfs_mount; + --mount_count; + if (!mount_count) + usbfs_mount = NULL; + + spin_unlock (&mount_lock); + mntput(mnt); + dbg("mount_count = %d\n", mount_count); +} + +static int create_special_files (void) +{ + int retval; + + /* create the devices and drivers special files */ + retval = get_mount (); + if (retval) + return retval; + devices_dentry = fs_create_file ("devices", + listmode | S_IFREG, + NULL, NULL, + &usbdevfs_devices_fops, + listuid, listgid); + if (devices_dentry == NULL) { + err ("Unable to create devices usbfs file"); + return -ENODEV; + } + + drivers_dentry = fs_create_file ("drivers", + listmode | S_IFREG, + NULL, NULL, + &usbdevfs_drivers_fops, + listuid, listgid); + if (drivers_dentry == NULL) { + err ("Unable to create drivers usbfs file"); + return -ENODEV; + } + + return 0; +} + +static void remove_special_files (void) +{ + if (devices_dentry) + fs_remove_file (devices_dentry); + if (drivers_dentry) + fs_remove_file (drivers_dentry); + devices_dentry = NULL; + drivers_dentry = NULL; + remove_mount(); +} + +void usbfs_update_special (void) { - int i; - for (i = 0; i < NRSPECIAL; i++) { - struct inode *inode = special[i].inode; + struct inode *inode; + + if (devices_dentry) { + inode = devices_dentry->d_inode; + if (inode) + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + } + if (drivers_dentry) { + inode = devices_dentry->d_inode; if (inode) inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; } } - -void usbdevfs_add_bus(struct usb_bus *bus) +void usbfs_add_bus(struct usb_bus *bus) { - struct list_head *slist; + char name[8]; + int retval; + + /* create the special files if this is the first bus added */ + if (num_buses == 0) { + retval = create_special_files(); + if (retval) + return; + } + ++num_buses; + + sprintf (name, "%03d", bus->busnum); + bus->dentry = fs_create_file (name, + busmode | S_IFDIR, + NULL, bus, NULL, + busuid, busgid); + if (bus->dentry == NULL) + return; - lock_kernel(); - for (slist = superlist.next; slist != &superlist; slist = slist->next) - new_bus_inode(bus, list_entry(slist, struct super_block, u.usbdevfs_sb.slist)); - update_special_inodes(); - unlock_kernel(); + usbfs_update_special(); usbdevfs_conn_disc_event(); } -void usbdevfs_remove_bus(struct usb_bus *bus) + +void usbfs_remove_bus(struct usb_bus *bus) { - lock_kernel(); - while (!list_empty(&bus->inodes)) - free_inode(list_entry(bus->inodes.next, struct inode, u.usbdev_i.dlist)); - update_special_inodes(); - unlock_kernel(); + if (bus->dentry) { + fs_remove_file (bus->dentry); + bus->dentry = NULL; + } + + --num_buses; + if (num_buses <= 0) { + remove_special_files(); + num_buses = 0; + } + + usbfs_update_special(); usbdevfs_conn_disc_event(); } -void usbdevfs_add_device(struct usb_device *dev) +void usbfs_add_device(struct usb_device *dev) { - struct list_head *slist; + char name[8]; - lock_kernel(); - for (slist = superlist.next; slist != &superlist; slist = slist->next) - new_dev_inode(dev, list_entry(slist, struct super_block, u.usbdevfs_sb.slist)); - update_special_inodes(); - unlock_kernel(); + sprintf (name, "%03d", dev->devnum); + dev->dentry = fs_create_file (name, + devmode | S_IFREG, + dev->bus->dentry, dev, + &usbdevfs_device_file_operations, + devuid, devgid); + if (dev->dentry == NULL) + return; + + usbfs_update_special(); usbdevfs_conn_disc_event(); } -void usbdevfs_remove_device(struct usb_device *dev) +void usbfs_remove_device(struct usb_device *dev) { struct dev_state *ds; struct siginfo sinfo; - lock_kernel(); - while (!list_empty(&dev->inodes)) - free_inode(list_entry(dev->inodes.next, struct inode, u.usbdev_i.dlist)); + if (dev->dentry) { + fs_remove_file (dev->dentry); + dev->dentry = NULL; + } while (!list_empty(&dev->filelist)) { ds = list_entry(dev->filelist.next, struct dev_state, list); list_del(&ds->list); @@ -732,9 +716,7 @@ send_sig_info(ds->discsignr, &sinfo, ds->disctask); } } - - update_special_inodes(); - unlock_kernel(); + usbfs_update_special(); usbdevfs_conn_disc_event(); } @@ -744,39 +726,42 @@ static struct proc_dir_entry *usbdir = NULL; #endif -int __init usbdevfs_init(void) +int __init usbfs_init(void) { - int ret; + int retval; - for (ret = 0; ret < NRSPECIAL; ret++) { - INIT_LIST_HEAD(&special[ret].inodes); - } - if ((ret = usb_register(&usbdevfs_driver))) - return ret; - if ((ret = register_filesystem(&usb_fs_type))) { + retval = usb_register(&usbdevfs_driver); + if (retval) + return retval; + + retval = register_filesystem(&usb_fs_type); + if (retval) { usb_deregister(&usbdevfs_driver); - return ret; + return retval; } - if ((ret = register_filesystem(&usbdevice_fs_type))) { + retval = register_filesystem(&usbdevice_fs_type); + if (retval) { unregister_filesystem(&usb_fs_type); usb_deregister(&usbdevfs_driver); - return ret; + return retval; } + #ifdef CONFIG_PROC_FS /* create mount point for usbdevfs */ usbdir = proc_mkdir("usb", proc_bus); #endif - return ret; + + return 0; } -void __exit usbdevfs_cleanup(void) +void __exit usbfs_cleanup(void) { usb_deregister(&usbdevfs_driver); unregister_filesystem(&usb_fs_type); unregister_filesystem(&usbdevice_fs_type); #ifdef CONFIG_PROC_FS - if (usbdir) - remove_proc_entry("usb", proc_bus); + if (usbdir) + remove_proc_entry("usb", proc_bus); #endif } diff -Nru a/drivers/usb/scanner.c b/drivers/usb/scanner.c --- a/drivers/usb/scanner.c Wed Dec 19 10:26:03 2001 +++ b/drivers/usb/scanner.c Wed Dec 19 10:26:03 2001 @@ -281,7 +281,7 @@ * 0.4.7 11/28/2001 * - Fixed typo in Documentation/scanner.txt. Thanks to * Karel for pointing it out. - * - Added ID's for a Memorex 6136u. Thanks to =C1lvaro Gaspar de + * - Added ID's for a Memorex 6136u. Thanks to Álvaro Gaspar de * Valenzuela" . * - Added ID's for Agfa e25. Thanks to Heinrich * Rust . Also reported to work with diff -Nru a/drivers/usb/serial/Config.in b/drivers/usb/serial/Config.in --- a/drivers/usb/serial/Config.in Wed Dec 19 10:26:03 2001 +++ b/drivers/usb/serial/Config.in Wed Dec 19 10:26:03 2001 @@ -15,6 +15,7 @@ dep_tristate ' USB Empeg empeg-car Mark I/II Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_EMPEG $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL dep_tristate ' USB FTDI Single Port Serial Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_FTDI_SIO $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL dep_tristate ' USB Handspring Visor / Palm m50x / Sony Clie Driver' CONFIG_USB_SERIAL_VISOR $CONFIG_USB_SERIAL +dep_tristate ' USB Compaq iPAQ Driver' CONFIG_USB_SERIAL_IPAQ $CONFIG_USB_SERIAL dep_tristate ' USB IR Dongle Serial Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_IR $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL dep_tristate ' USB Inside Out Edgeport Serial Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_EDGEPORT $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL dep_tristate ' USB Keyspan PDA Single Port Serial Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_KEYSPAN_PDA $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL @@ -28,6 +29,7 @@ dep_mbool ' USB Keyspan USA-19W Firmware' CONFIG_USB_SERIAL_KEYSPAN_USA19W $CONFIG_USB_SERIAL_KEYSPAN dep_mbool ' USB Keyspan USA-49W Firmware' CONFIG_USB_SERIAL_KEYSPAN_USA49W $CONFIG_USB_SERIAL_KEYSPAN dep_tristate ' USB MCT Single Port Serial Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_MCT_U232 $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL +dep_tristate ' USB KL5KUSB105 (Palmconnect) Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_KLSI $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL dep_tristate ' USB Prolific 2303 Single Port Serial Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_PL2303 $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL dep_tristate ' USB REINER SCT cyberJack pinpad/e-com chipcard reader (EXPERIMENTAL)' CONFIG_USB_SERIAL_CYBERJACK $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL dep_tristate ' USB Xircom / Entregra Single Port Serial Driver (EXPERIMENTAL)' CONFIG_USB_SERIAL_XIRCOM $CONFIG_USB_SERIAL $CONFIG_EXPERIMENTAL diff -Nru a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile --- a/drivers/usb/serial/Makefile Wed Dec 19 10:26:02 2001 +++ b/drivers/usb/serial/Makefile Wed Dec 19 10:26:02 2001 @@ -8,6 +8,7 @@ obj-$(CONFIG_USB_SERIAL) += usbserial.o obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o +obj-$(CONFIG_USB_SERIAL_IPAQ) += ipaq.o obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o obj-$(CONFIG_USB_SERIAL_FTDI_SIO) += ftdi_sio.o obj-$(CONFIG_USB_SERIAL_KEYSPAN_PDA) += keyspan_pda.o @@ -22,7 +23,8 @@ obj-$(CONFIG_USB_SERIAL_PL2303) += pl2303.o obj-$(CONFIG_USB_SERIAL_CYBERJACK) += cyberjack.o obj-$(CONFIG_USB_SERIAL_IR) += ir-usb.o - +obj-$(CONFIG_USB_SERIAL_KLSI) += kl5kusb105.o + # Objects that export symbols. export-objs := usbserial.o diff -Nru a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/serial/ipaq.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,527 @@ +/* + * USB Compaq iPAQ driver + * + * Copyright (C) 2001 + * Ganesh Varadarajan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_USB_SERIAL_DEBUG + static int debug = 1; +#else + static int debug = 0; +#endif + +#include "usb-serial.h" +#include "ipaq.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.1" +#define DRIVER_AUTHOR "Ganesh Varadarajan " +#define DRIVER_DESC "USB Compaq iPAQ driver" + +/* Function prototypes for an ipaq */ +static int ipaq_open (struct usb_serial_port *port, struct file *filp); +static void ipaq_close (struct usb_serial_port *port, struct file *filp); +static int ipaq_startup (struct usb_serial *serial); +static void ipaq_shutdown (struct usb_serial *serial); +static int ipaq_write(struct usb_serial_port *port, int from_user, const unsigned char *buf, + int count); +static int ipaq_write_bulk(struct usb_serial_port *port, int from_user, const unsigned char *buf, + int count); +static int ipaq_write_flush(struct usb_serial_port *port); +static void ipaq_read_bulk_callback (struct urb *urb); +static void ipaq_write_bulk_callback(struct urb *urb); +static int ipaq_write_room(struct usb_serial_port *port); +static int ipaq_chars_in_buffer(struct usb_serial_port *port); +static void ipaq_destroy_lists(struct usb_serial_port *port); + + +static __devinitdata struct usb_device_id ipaq_id_table [] = { + { USB_DEVICE(IPAQ_VENDOR_ID, IPAQ_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, ipaq_id_table); + +/* All of the device info needed for the Compaq iPAQ */ +struct usb_serial_device_type ipaq_device = { + name: "Compaq iPAQ", + id_table: ipaq_id_table, + num_interrupt_in: 0, + num_bulk_in: 1, + num_bulk_out: 1, + num_ports: 1, + open: ipaq_open, + close: ipaq_close, + startup: ipaq_startup, + shutdown: ipaq_shutdown, + write: ipaq_write, + write_room: ipaq_write_room, + chars_in_buffer: ipaq_chars_in_buffer, + read_bulk_callback: ipaq_read_bulk_callback, + write_bulk_callback: ipaq_write_bulk_callback, +}; + +static spinlock_t write_list_lock; +static int bytes_in; +static int bytes_out; + +static int ipaq_open(struct usb_serial_port *port, struct file *filp) +{ + struct usb_serial *serial = port->serial; + struct ipaq_private *priv; + struct ipaq_packet *pkt; + int i, result = 0; + + if (port_paranoia_check(port, __FUNCTION__)) { + return -ENODEV; + } + + dbg(__FUNCTION__ " - port %d", port->number); + + down(&port->sem); + + ++port->open_count; + MOD_INC_USE_COUNT; + + if (port->open_count == 1) { + bytes_in = 0; + bytes_out = 0; + priv = (struct ipaq_private *)kmalloc(sizeof(struct ipaq_private), GFP_KERNEL); + if (priv == NULL) { + err(__FUNCTION__ " - Out of memory"); + return -ENOMEM; + } + port->private = (void *)priv; + priv->active = 0; + priv->queue_len = 0; + INIT_LIST_HEAD(&priv->queue); + INIT_LIST_HEAD(&priv->freelist); + + for (i = 0; i < URBDATA_QUEUE_MAX / PACKET_SIZE; i++) { + pkt = kmalloc(sizeof(struct ipaq_packet), GFP_KERNEL); + if (pkt == NULL) { + goto enomem; + } + pkt->data = kmalloc(PACKET_SIZE, GFP_KERNEL); + if (pkt->data == NULL) { + kfree(pkt); + goto enomem; + } + pkt->len = 0; + pkt->written = 0; + INIT_LIST_HEAD(&pkt->list); + list_add(&pkt->list, &priv->freelist); + priv->free_len += PACKET_SIZE; + } + + /* + * Force low latency on. This will immediately push data to the line + * discipline instead of queueing. + */ + + port->tty->low_latency = 1; + + /* + * Lose the small buffers usbserial provides. Make larger ones. + */ + + kfree(port->bulk_in_buffer); + kfree(port->bulk_out_buffer); + port->bulk_in_buffer = kmalloc(URBDATA_SIZE, GFP_KERNEL); + if (port->bulk_in_buffer == NULL) { + goto enomem; + } + port->bulk_out_buffer = kmalloc(URBDATA_SIZE, GFP_KERNEL); + if (port->bulk_out_buffer == NULL) { + kfree(port->bulk_in_buffer); + goto enomem; + } + port->read_urb->transfer_buffer = port->bulk_in_buffer; + port->write_urb->transfer_buffer = port->bulk_out_buffer; + port->read_urb->transfer_buffer_length = URBDATA_SIZE; + port->bulk_out_size = port->write_urb->transfer_buffer_length = URBDATA_SIZE; + + /* Start reading from the device */ + FILL_BULK_URB(port->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length, + ipaq_read_bulk_callback, port); + result = usb_submit_urb(port->read_urb); + if (result) { + err(__FUNCTION__ " - failed submitting read urb, error %d", result); + } + + /* + * Send out two control messages observed in win98 sniffs. Not sure what + * they do. + */ + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), 0x22, 0x21, + 0x1, 0, NULL, 0, 5 * HZ); + if (result < 0) { + err(__FUNCTION__ " - failed doing control urb, error %d", result); + } + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), 0x22, 0x21, + 0x1, 0, NULL, 0, 5 * HZ); + if (result < 0) { + err(__FUNCTION__ " - failed doing control urb, error %d", result); + } + } + + up(&port->sem); + + return result; + +enomem: + ipaq_destroy_lists(port); + kfree(priv); + err(__FUNCTION__ " - Out of memory"); + return -ENOMEM; +} + + +static void ipaq_close(struct usb_serial_port *port, struct file *filp) +{ + struct usb_serial *serial; + struct ipaq_private *priv = port->private; + + if (port_paranoia_check(port, __FUNCTION__)) { + return; + } + + dbg(__FUNCTION__ " - port %d", port->number); + + serial = get_usb_serial(port, __FUNCTION__); + if (!serial) + return; + + down (&port->sem); + + --port->open_count; + + if (port->open_count <= 0) { + + /* + * shut down bulk read and write + */ + + usb_unlink_urb(port->write_urb); + usb_unlink_urb(port->read_urb); + ipaq_destroy_lists(port); + kfree(priv); + port->private = NULL; + port->open_count = 0; + + } + up (&port->sem); + + /* Uncomment the following line if you want to see some statistics in your syslog */ + /* info ("Bytes In = %d Bytes Out = %d", bytes_in, bytes_out); */ + + MOD_DEC_USE_COUNT; +} + +static void ipaq_read_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = (struct usb_serial_port *)urb->context; + struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); + struct tty_struct *tty; + unsigned char *data = urb->transfer_buffer; + int i, result; + + if (port_paranoia_check(port, __FUNCTION__)) + return; + + dbg(__FUNCTION__ " - port %d", port->number); + + if (!serial) { + dbg(__FUNCTION__ " - bad serial pointer, exiting"); + return; + } + + if (urb->status) { + dbg(__FUNCTION__ " - nonzero read bulk status received: %d", urb->status); + return; + } + + usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data); + + tty = port->tty; + if (urb->actual_length) { + for (i = 0; i < urb->actual_length ; ++i) { + /* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them. */ + if(tty->flip.count >= TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(tty); + } + /* this doesn't actually push the data through unless tty->low_latency is set */ + tty_insert_flip_char(tty, data[i], 0); + } + tty_flip_buffer_push(tty); + bytes_in += urb->actual_length; + } + + /* Continue trying to always read */ + FILL_BULK_URB(port->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length, + ipaq_read_bulk_callback, port); + result = usb_submit_urb(port->read_urb); + if (result) + err(__FUNCTION__ " - failed resubmitting read urb, error %d", result); + return; +} + +static int ipaq_write(struct usb_serial_port *port, int from_user, const unsigned char *buf, + int count) +{ + const unsigned char *current_position = buf; + int bytes_sent = 0; + int transfer_size; + + dbg(__FUNCTION__ " - port %d", port->number); + + usb_serial_debug_data(__FILE__, __FUNCTION__, count, buf); + + while (count > 0) { + transfer_size = min(count, PACKET_SIZE); + if (ipaq_write_bulk(port, from_user, current_position, transfer_size)) { + break; + } + current_position += transfer_size; + bytes_sent += transfer_size; + count -= transfer_size; + bytes_out += transfer_size; + } + + return bytes_sent; +} + +static int ipaq_write_bulk(struct usb_serial_port *port, int from_user, const unsigned char *buf, + int count) +{ + struct ipaq_private *priv = port->private; + struct ipaq_packet *pkt = NULL; + int result = 0; + unsigned long flags; + + if (priv->free_len <= 0) { + dbg(__FUNCTION__ " - we're stuffed"); + return -EAGAIN; + } + + spin_lock_irqsave(&write_list_lock, flags); + if (!list_empty(&priv->freelist)) { + pkt = list_entry(priv->freelist.next, struct ipaq_packet, list); + list_del(&pkt->list); + priv->free_len -= PACKET_SIZE; + } + spin_unlock_irqrestore(&write_list_lock, flags); + if (pkt == NULL) { + dbg(__FUNCTION__ " - we're stuffed"); + return -EAGAIN; + } + + if (from_user) { + copy_from_user(pkt->data, buf, count); + } else { + memcpy(pkt->data, buf, count); + } + usb_serial_debug_data(__FILE__, __FUNCTION__, count, pkt->data); + + pkt->len = count; + pkt->written = 0; + spin_lock_irqsave(&write_list_lock, flags); + list_add_tail(&pkt->list, &priv->queue); + priv->queue_len += count; + if (priv->active == 0) { + priv->active = 1; + result = ipaq_write_flush(port); + } + spin_unlock_irqrestore(&write_list_lock, flags); + return result; +} + +static int ipaq_write_flush(struct usb_serial_port *port) +{ + struct ipaq_private *priv = (struct ipaq_private *)port->private; + struct usb_serial *serial = port->serial; + int count, room, result; + struct ipaq_packet *pkt; + struct urb *urb = port->write_urb; + struct list_head *tmp; + + if (urb->status == -EINPROGRESS) { + /* Should never happen */ + err(__FUNCTION__ " - flushing while urb is active !"); + return -EAGAIN; + } + room = URBDATA_SIZE; + for (tmp = priv->queue.next; tmp != &priv->queue;) { + pkt = list_entry(tmp, struct ipaq_packet, list); + tmp = tmp->next; + count = min(room, (int)(pkt->len - pkt->written)); + memcpy(urb->transfer_buffer + (URBDATA_SIZE - room), + pkt->data + pkt->written, count); + room -= count; + pkt->written += count; + priv->queue_len -= count; + if (pkt->written == pkt->len) { + list_del(&pkt->list); + list_add(&pkt->list, &priv->freelist); + priv->free_len += PACKET_SIZE; + } + if (room == 0) { + break; + } + } + + count = URBDATA_SIZE - room; + FILL_BULK_URB(port->write_urb, serial->dev, + usb_sndbulkpipe(serial->dev, port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, count, ipaq_write_bulk_callback, + port); + result = usb_submit_urb(urb); + if (result) { + err(__FUNCTION__ " - failed submitting write urb, error %d", result); + } + return result; +} + +static void ipaq_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = (struct usb_serial_port *)urb->context; + struct ipaq_private *priv = (struct ipaq_private *)port->private; + unsigned long flags; + + if (port_paranoia_check (port, __FUNCTION__)) { + return; + } + + dbg(__FUNCTION__ " - port %d", port->number); + + if (urb->status) { + dbg(__FUNCTION__ " - nonzero write bulk status received: %d", urb->status); + } + + spin_lock_irqsave(&write_list_lock, flags); + if (!list_empty(&priv->queue)) { + ipaq_write_flush(port); + } else { + priv->active = 0; + } + spin_unlock_irqrestore(&write_list_lock, flags); + queue_task(&port->tqueue, &tq_immediate); + mark_bh(IMMEDIATE_BH); + + return; +} + +static int ipaq_write_room(struct usb_serial_port *port) +{ + struct ipaq_private *priv = (struct ipaq_private *)port->private; + + dbg(__FUNCTION__ " - freelen %d", priv->free_len); + return priv->free_len; +} + +static int ipaq_chars_in_buffer(struct usb_serial_port *port) +{ + struct ipaq_private *priv = (struct ipaq_private *)port->private; + + dbg(__FUNCTION__ " - queuelen %d", priv->queue_len); + return priv->queue_len; +} + +static void ipaq_destroy_lists(struct usb_serial_port *port) +{ + struct ipaq_private *priv = (struct ipaq_private *)port->private; + struct list_head *tmp; + struct ipaq_packet *pkt; + + for (tmp = priv->queue.next; tmp != &priv->queue;) { + pkt = list_entry(tmp, struct ipaq_packet, list); + tmp = tmp->next; + kfree(pkt->data); + kfree(pkt); + } + for (tmp = priv->freelist.next; tmp != &priv->freelist;) { + pkt = list_entry(tmp, struct ipaq_packet, list); + tmp = tmp->next; + kfree(pkt->data); + kfree(pkt); + } + return; +} + + +static int ipaq_startup(struct usb_serial *serial) +{ + dbg(__FUNCTION__); + usb_set_configuration(serial->dev, 1); + return 0; +} + +static void ipaq_shutdown(struct usb_serial *serial) +{ + int i; + + dbg (__FUNCTION__); + + /* stop reads and writes on all ports */ + for (i=0; i < serial->num_ports; ++i) { + while (serial->port[i].open_count > 0) { + ipaq_close(&serial->port[i], NULL); + } + } +} + +static int __init ipaq_init(void) +{ + usb_serial_register(&ipaq_device); + info(DRIVER_DESC " " DRIVER_VERSION); + + return 0; +} + + +static void __exit ipaq_exit(void) +{ + usb_serial_deregister(&ipaq_device); +} + + +module_init(ipaq_init); +module_exit(ipaq_exit); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + +MODULE_PARM(debug, "i"); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + diff -Nru a/drivers/usb/serial/ipaq.h b/drivers/usb/serial/ipaq.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/serial/ipaq.h Wed Dec 19 10:26:03 2001 @@ -0,0 +1,60 @@ +/* + * USB Compaq iPAQ driver + * + * Copyright (C) 2001 + * Ganesh Varadarajan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * + */ + +#ifndef __LINUX_USB_SERIAL_IPAQ_H +#define __LINUX_USB_SERIAL_IPAQ_H + + +#define IPAQ_VENDOR_ID 0x049f +#define IPAQ_PRODUCT_ID 0x0003 + +/* + * Since we can't queue our bulk write urbs (don't know why - it just + * doesn't work), we can send down only one write urb at a time. The simplistic + * approach taken by the generic usbserial driver will work, but it's not good + * for performance. Therefore, we buffer upto URBDATA_QUEUE_MAX bytes of write + * requests coming from the line discipline. This is done by chaining them + * in lists of struct ipaq_packet, each packet holding a maximum of + * PACKET_SIZE bytes. + * + * ipaq_write() can be called from bottom half context; hence we can't + * allocate memory for packets there. So we initialize a pool of packets at + * the first open and maintain a freelist. + * + * The value of PACKET_SIZE was empirically determined by + * checking the maximum write sizes sent down by the ppp ldisc. + * URBDATA_QUEUE_MAX is set to 64K, which is the maximum TCP window size + * supported by the iPAQ. + */ + +struct ipaq_packet { + char *data; + size_t len; + size_t written; + struct list_head list; +}; + +struct ipaq_private { + int active; + int queue_len; + int free_len; + struct list_head queue; + struct list_head freelist; +}; + +#define URBDATA_SIZE 4096 +#define URBDATA_QUEUE_MAX (64 * 1024) +#define PACKET_SIZE 256 + +#endif diff -Nru a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/serial/kl5kusb105.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,1087 @@ +/* + * KLSI KL5KUSB105 chip RS232 converter driver + * + * Copyright (C) 2001 Utz-Uwe Haus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * All information about the device was acquired using SniffUSB ans snoopUSB + * on Windows98. + * It was written out of frustration with the PalmConnect USB Serial adapter + * sold by Palm Inc. + * Neither Palm, nor their contractor (MCCI) or their supplier (KLSI) provided + * information that was not already available. + * + * It seems that KLSI bought some silicon-design information from ScanLogic, + * whose SL11R processor is at the core of the KL5KUSB chipset from KLSI. + * KLSI has firmware available for their devices; it is probable that the + * firmware differs from that used by KLSI in their products. If you have an + * original KLSI device and can provide some information on it, I would be + * most interested in adding support for it here. If you have any information + * on the protocol used (or find errors in my reverse-engineered stuff), please + * let me know. + * + * The code was only tested with a PalmConnect USB adapter; if you + * are adventurous, try it with any KLSI-based device and let me know how it + * breaks so that I can fix it! + */ + +/* TODO: + * check modem line signals + * implement handshaking or decide that we do not support it + */ + +/* History: + * 0.3a - implemented pools of write URBs + * 0.3 - alpha version for public testing + * 0.2 - TIOCMGET works, so autopilot(1) can be used! + * 0.1 - can be used to to pilot-xfer -p /dev/ttyUSB0 -l + * + * The driver skeleton is mainly based on mct_u232.c and various other + * pieces of code shamelessly copied from the drivers/usb/serial/ directory. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_USB_SERIAL_DEBUG + static int debug = 1; +#else + static int debug; +#endif + +#include "usb-serial.h" +#include "kl5kusb105.h" + + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.3a" +#define DRIVER_AUTHOR "Utz-Uwe Haus " +#define DRIVER_DESC "KLSI KL5KUSB105 chipset USB->Serial Converter driver" + + +/* + * Function prototypes + */ +static int klsi_105_startup (struct usb_serial *serial); +static void klsi_105_shutdown (struct usb_serial *serial); +static int klsi_105_open (struct usb_serial_port *port, + struct file *filp); +static void klsi_105_close (struct usb_serial_port *port, + struct file *filp); +static int klsi_105_write (struct usb_serial_port *port, + int from_user, + const unsigned char *buf, + int count); +static void klsi_105_write_bulk_callback (struct urb *urb); +static int klsi_105_chars_in_buffer (struct usb_serial_port *port); +static int klsi_105_write_room (struct usb_serial_port *port); + +static void klsi_105_read_bulk_callback (struct urb *urb); +static void klsi_105_set_termios (struct usb_serial_port *port, + struct termios * old); +static int klsi_105_ioctl (struct usb_serial_port *port, + struct file * file, + unsigned int cmd, + unsigned long arg); +static void klsi_105_throttle (struct usb_serial_port *port); +static void klsi_105_unthrottle (struct usb_serial_port *port); +/* +static void klsi_105_break_ctl (struct usb_serial_port *port, + int break_state ); + */ + +/* + * All of the device info needed for the KLSI converters. + */ +static __devinitdata struct usb_device_id id_table [] = { + { USB_DEVICE(PALMCONNECT_VID, PALMCONNECT_PID) }, + { USB_DEVICE(KLSI_VID, KLSI_KL5KUSB105D_PID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, id_table); + + +static struct usb_serial_device_type kl5kusb105d_device = { + name: "KL5KUSB105D / PalmConnect", + id_table: id_table, + num_interrupt_in: 1, + num_bulk_in: 1, + num_bulk_out: 1, + num_ports: 1, + open: klsi_105_open, + close: klsi_105_close, + write: klsi_105_write, + write_bulk_callback: klsi_105_write_bulk_callback, + chars_in_buffer: klsi_105_chars_in_buffer, + write_room: klsi_105_write_room, + read_bulk_callback: klsi_105_read_bulk_callback, + ioctl: klsi_105_ioctl, + set_termios: klsi_105_set_termios, + /*break_ctl: klsi_105_break_ctl,*/ + startup: klsi_105_startup, + shutdown: klsi_105_shutdown, + throttle: klsi_105_throttle, + unthrottle: klsi_105_unthrottle, +}; + +struct klsi_105_port_settings { + __u8 pktlen; /* always 5, it seems */ + __u8 baudrate; + __u8 databits; + __u8 unknown1; + __u8 unknown2; +} __attribute__ ((packed)); + +/* we implement a pool of NUM_URBS urbs per usb_serial */ +#define NUM_URBS 1 +#define URB_TRANSFER_BUFFER_SIZE 64 +struct klsi_105_private { + struct klsi_105_port_settings cfg; + struct termios termios; + unsigned long line_state; /* modem line settings */ + /* write pool */ + struct urb * write_urb_pool[NUM_URBS]; + spinlock_t write_urb_pool_lock; + unsigned long bytes_in; + unsigned long bytes_out; +}; + + +/* + * Handle vendor specific USB requests + */ + + +#define KLSI_TIMEOUT (HZ * 5 ) /* default urb timeout */ + +static int klsi_105_chg_port_settings(struct usb_serial *serial, + struct klsi_105_port_settings *settings) +{ + int rc; + + rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + KL5KUSB105A_SIO_SET_DATA, + USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_INTERFACE, + 0, /* value */ + 0, /* index */ + settings, + sizeof(struct klsi_105_port_settings), + KLSI_TIMEOUT); + if (rc < 0) + err("Change port settings failed (error = %d)", rc); + info(__FUNCTION__ " - %d byte block, baudrate %x, databits %d, u1 %d, u2 %d", + settings->pktlen, + settings->baudrate, settings->databits, + settings->unknown1, settings->unknown2); + return rc; +} /* klsi_105_chg_port_settings */ + +/* translate a 16-bit status value from the device to linux's TIO bits */ +static unsigned long klsi_105_status2linestate(const __u16 status) +{ + unsigned long res = 0; + + res = ((status & KL5KUSB105A_DSR) ? TIOCM_DSR : 0) + | ((status & KL5KUSB105A_CTS) ? TIOCM_CTS : 0) + ; + + return res; +} +/* + * Read line control via vendor command and return result through + * *line_state_p + */ +/* It seems that the status buffer has always only 2 bytes length */ +#define KLSI_STATUSBUF_LEN 2 +static int klsi_105_get_line_state(struct usb_serial *serial, + unsigned long *line_state_p) +{ + int rc; + __u8 status_buf[KLSI_STATUSBUF_LEN] = { -1,-1}; + __u16 status; + + info(__FUNCTION__ " - sending SIO Poll request"); + rc = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + KL5KUSB105A_SIO_POLL, + USB_TYPE_VENDOR | USB_DIR_IN, + 0, /* value */ + 0, /* index */ + status_buf, KLSI_STATUSBUF_LEN, + 10*HZ + ); + if (rc < 0) + err("Reading line status failed (error = %d)", rc); + else { + status = status_buf[0] + (status_buf[1]<<8); + + info(__FUNCTION__ " - read status %x %x", + status_buf[0], status_buf[1]); + + *line_state_p = klsi_105_status2linestate(status); + } + + return rc; +} + + +/* + * Driver's tty interface functions + */ + +static int klsi_105_startup (struct usb_serial *serial) +{ + struct klsi_105_private *priv; + int i; + + /* check if we support the product id (see keyspan.c) + * FIXME + */ + + /* allocate the private data structure */ + for (i=0; inum_ports; i++) { + serial->port[i].private = kmalloc(sizeof(struct klsi_105_private), + GFP_KERNEL); + if (!serial->port[i].private) { + dbg(__FUNCTION__ "kmalloc for klsi_105_private failed."); + return (-1); /* error */ + } + priv = (struct klsi_105_private *)serial->port[i].private; + /* set initial values for control structures */ + priv->cfg.pktlen = 5; + priv->cfg.baudrate = kl5kusb105a_sio_b9600; + priv->cfg.databits = kl5kusb105a_dtb_8; + priv->cfg.unknown1 = 0; + priv->cfg.unknown2 = 1; + + priv->line_state = 0; + + priv->bytes_in = 0; + priv->bytes_out = 0; + + spin_lock_init (&priv->write_urb_pool_lock); + for (i=0; iwrite_urb_pool[i] = urb; + if (urb == NULL) { + err("No more urbs???"); + continue; + } + + urb->transfer_buffer = NULL; + urb->transfer_buffer = kmalloc (URB_TRANSFER_BUFFER_SIZE, + GFP_KERNEL); + if (!urb->transfer_buffer) { + err (__FUNCTION__ + " - out of memory for urb buffers."); + continue; + } + } + + /* priv->termios is left uninitalized until port opening */ + init_waitqueue_head(&serial->port[i].write_wait); + } + + return (0); +} /* klsi_105_startup */ + + +static void klsi_105_shutdown (struct usb_serial *serial) +{ + int i; + + dbg (__FUNCTION__); + + /* stop reads and writes on all ports */ + for (i=0; i < serial->num_ports; ++i) { + struct klsi_105_private *priv = + (struct klsi_105_private*) serial->port[i].private; + unsigned long flags; + while (serial->port[i].open_count > 0) { + klsi_105_close (&serial->port[i], NULL); + } + + if (priv) { + /* kill our write urb pool */ + int j; + struct urb **write_urbs = priv->write_urb_pool; + spin_lock_irqsave(&priv->write_urb_pool_lock,flags); + + for (j = 0; j < NUM_URBS; j++) { + if (write_urbs[j]) { + /* FIXME - uncomment the following + * usb_unlink_urb call when the host + * controllers get fixed to set + * urb->dev = NULL after the urb is + * finished. Otherwise this call + * oopses. */ + /* usb_unlink_urb(write_urbs[j]); */ + if (write_urbs[j]->transfer_buffer) + kfree(write_urbs[j]->transfer_buffer); + usb_free_urb (write_urbs[j]); + } + } + + spin_unlock_irqrestore (&priv->write_urb_pool_lock, + flags); + + kfree(serial->port[i].private); + } + } +} /* klsi_105_shutdown */ + +static int klsi_105_open (struct usb_serial_port *port, struct file *filp) +{ + struct usb_serial *serial = port->serial; + struct klsi_105_private *priv = (struct klsi_105_private *)port->private; + int retval = 0; + + dbg(__FUNCTION__" port %d", port->number); + + down (&port->sem); + + ++port->open_count; + MOD_INC_USE_COUNT; + + if (port->open_count == 1) { + int rc; + int i; + unsigned long line_state; + + /* force low_latency on so that our tty_push actually forces + * the data through + * port->tty->low_latency = 1; */ + + /* Do a defined restart: + * Set up sane default baud rate and send the 'READ_ON' + * vendor command. + * FIXME: set modem line control (how?) + * Then read the modem line control and store values in + * priv->line_state. + */ + priv->cfg.pktlen = 5; + priv->cfg.baudrate = kl5kusb105a_sio_b9600; + priv->cfg.databits = kl5kusb105a_dtb_8; + priv->cfg.unknown1 = 0; + priv->cfg.unknown2 = 1; + klsi_105_chg_port_settings(serial, &(priv->cfg)); + + /* set up termios structure */ + priv->termios.c_iflag = port->tty->termios->c_iflag; + priv->termios.c_oflag = port->tty->termios->c_oflag; + priv->termios.c_cflag = port->tty->termios->c_cflag; + priv->termios.c_lflag = port->tty->termios->c_lflag; + for (i=0; itermios.c_cc[i] = port->tty->termios->c_cc[i]; + + + /* READ_ON and urb submission */ + FILL_BULK_URB(port->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, + port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, + port->read_urb->transfer_buffer_length, + klsi_105_read_bulk_callback, + port); + port->read_urb->transfer_flags |= USB_QUEUE_BULK; + + rc = usb_submit_urb(port->read_urb); + if (rc) { + err(__FUNCTION__ + " - failed submitting read urb, error %d", rc); + retval = rc; + goto exit; + } + + rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev,0), + KL5KUSB105A_SIO_CONFIGURE, + USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE, + KL5KUSB105A_SIO_CONFIGURE_READ_ON, + 0, /* index */ + NULL, + 0, + KLSI_TIMEOUT); + if (rc < 0) { + err("Enabling read failed (error = %d)", rc); + retval = rc; + } else + dbg(__FUNCTION__ " - enabled reading"); + + rc = klsi_105_get_line_state(serial, &line_state); + if (rc >= 0) { + priv->line_state = line_state; + dbg(__FUNCTION__ + " - read line state 0x%lx", line_state); + retval = 0; + } else + retval = rc; + } + +exit: + up (&port->sem); + + return retval; +} /* klsi_105_open */ + + +static void klsi_105_close (struct usb_serial_port *port, struct file *filp) +{ + struct usb_serial *serial; + struct klsi_105_private *priv + = (struct klsi_105_private *)port->private; + dbg(__FUNCTION__" port %d", port->number); + + serial = get_usb_serial (port, __FUNCTION__); + + if(!serial) + return; + + down (&port->sem); + + --port->open_count; + + if (port->open_count <= 0) { + /* send READ_OFF */ + int rc = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + KL5KUSB105A_SIO_CONFIGURE, + USB_TYPE_VENDOR | USB_DIR_OUT, + KL5KUSB105A_SIO_CONFIGURE_READ_OFF, + 0, /* index */ + NULL, 0, + KLSI_TIMEOUT); + if (rc < 0) + err("Disabling read failed (error = %d)", rc); + + /* shutdown our bulk reads and writes */ + usb_unlink_urb (port->write_urb); + usb_unlink_urb (port->read_urb); + /* unlink our write pool */ + /* FIXME */ + /* wgg - do I need this? I think so. */ + usb_unlink_urb (port->interrupt_in_urb); + port->open_count = 0; + info("kl5kusb105 port stats: %ld bytes in, %ld bytes out", priv->bytes_in, priv->bytes_out); + } + + up (&port->sem); + MOD_DEC_USE_COUNT; +} /* klsi_105_close */ + + +/* We need to write a complete 64-byte data block and encode the + * number actually sent in the first double-byte, LSB-order. That + * leaves at most 62 bytes of payload. + */ +#define KLSI_105_DATA_OFFSET 2 /* in the bulk urb data block */ + + +static int klsi_105_write (struct usb_serial_port *port, int from_user, + const unsigned char *buf, int count) +{ + struct usb_serial *serial = port->serial; + struct klsi_105_private *priv = + (struct klsi_105_private*) port->private; + int result, size; + int bytes_sent=0; + + dbg(__FUNCTION__ " - port %d", port->number); + + down (&port->sem); /* to lock against someone else trying to + take an URB we just selected from the pool */ + + while (count > 0) { + /* try to find a free urb (write 0 bytes if none) */ + struct urb *urb = NULL; + unsigned long flags; + int i; + /* since the pool is per-port we might not need the spin lock !? */ + spin_lock_irqsave (&priv->write_urb_pool_lock, flags); + for (i=0; iwrite_urb_pool[i]->status != -EINPROGRESS) { + urb = priv->write_urb_pool[i]; + dbg(__FUNCTION__ " - using pool URB %d", i); + break; + } + } + spin_unlock_irqrestore (&priv->write_urb_pool_lock, flags); + + if (urb==NULL) { + dbg (__FUNCTION__ " - no more free urbs"); + goto exit; + } + + if (urb->transfer_buffer == NULL) { + urb->transfer_buffer = kmalloc (URB_TRANSFER_BUFFER_SIZE, GFP_KERNEL); + if (urb->transfer_buffer == NULL) { + err(__FUNCTION__ " - no more kernel memory..."); + goto exit; + } + } + + size = min (count, port->bulk_out_size - KLSI_105_DATA_OFFSET); + size = min (size, URB_TRANSFER_BUFFER_SIZE - KLSI_105_DATA_OFFSET); + + if (from_user) { + if (copy_from_user(urb->transfer_buffer + + KLSI_105_DATA_OFFSET, buf, size)) { + up (&port->sem); + return -EFAULT; + } + } else { + memcpy (urb->transfer_buffer + KLSI_105_DATA_OFFSET, + buf, size); + } + + /* write payload size into transfer buffer */ + ((__u8 *)urb->transfer_buffer)[0] = (__u8) (size & 0xFF); + ((__u8 *)urb->transfer_buffer)[1] = (__u8) ((size & 0xFF00)>>8); + + /* set up our urb */ + FILL_BULK_URB(urb, serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), + urb->transfer_buffer, + URB_TRANSFER_BUFFER_SIZE, + klsi_105_write_bulk_callback, + port); + urb->transfer_flags |= USB_QUEUE_BULK; + + + /* send the data out the bulk port */ + result = usb_submit_urb(urb); + if (result) { + err(__FUNCTION__ + " - failed submitting write urb, error %d", result); + goto exit; + } + buf += size; + bytes_sent += size; + count -= size; + } +exit: + up (&port->sem); + priv->bytes_out+=bytes_sent; + + return bytes_sent; /* that's how much we wrote */ +} /* klsi_105_write */ + +static void klsi_105_write_bulk_callback ( struct urb *urb) +{ + struct usb_serial_port *port = (struct usb_serial_port *)urb->context; + struct usb_serial *serial = port->serial; + + dbg(__FUNCTION__ " - port %d", port->number); + + if (!serial) { + dbg(__FUNCTION__ " - bad serial pointer, exiting"); + return; + } + + if (urb->status) { + dbg(__FUNCTION__ " - nonzero write bulk status received: %d", + urb->status); + return; + } + + /* from generic_write_bulk_callback */ + queue_task(&port->tqueue, &tq_immediate); + mark_bh(IMMEDIATE_BH); + + return; +} /* klsi_105_write_bulk_completion_callback */ + + +/* return number of characters currently in the writing process */ +static int klsi_105_chars_in_buffer (struct usb_serial_port *port) +{ + int chars = 0; + int i; + unsigned long flags; + struct klsi_105_private *priv = + (struct klsi_105_private*) port->private; + + spin_lock_irqsave (&priv->write_urb_pool_lock, flags); + + for (i = 0; i < NUM_URBS; ++i) { + if (priv->write_urb_pool[i]->status == -EINPROGRESS) { + chars += URB_TRANSFER_BUFFER_SIZE; + } + } + + spin_unlock_irqrestore (&priv->write_urb_pool_lock, flags); + + dbg (__FUNCTION__ " - returns %d", chars); + return (chars); +} + +static int klsi_105_write_room (struct usb_serial_port *port) +{ + unsigned long flags; + int i; + int room = 0; + struct klsi_105_private *priv = + (struct klsi_105_private*) port->private; + + spin_lock_irqsave (&priv->write_urb_pool_lock, flags); + for (i = 0; i < NUM_URBS; ++i) { + if (priv->write_urb_pool[i]->status != -EINPROGRESS) { + room += URB_TRANSFER_BUFFER_SIZE; + } + } + + spin_unlock_irqrestore (&priv->write_urb_pool_lock, flags); + + dbg(__FUNCTION__ " - returns %d", room); + return (room); +} + + + +static void klsi_105_read_bulk_callback (struct urb *urb) +{ + struct usb_serial_port *port = (struct usb_serial_port *)urb->context; + struct usb_serial *serial = port->serial; + struct klsi_105_private *priv = + (struct klsi_105_private*) port->private; + struct tty_struct *tty; + unsigned char *data = urb->transfer_buffer; + int rc; + + dbg(__FUNCTION__ " - port %d", port->number); + + /* The urb might have been killed. */ + if (urb->status) { + dbg(__FUNCTION__ " - nonzero read bulk status received: %d", + urb->status); + return; + } + if (!serial) { + dbg(__FUNCTION__ " - bad serial pointer, exiting"); + return; + } + + /* The data received is again preceded by a length double-byte in LSB- + * first order (see klsi_105_write() ) + */ + if (urb->actual_length == 0) { + /* empty urbs seem to happen, we ignore them */ + /* dbg(__FUNCTION__ " - emtpy URB"); */ + ; + } else if (urb->actual_length <= 2) { + dbg(__FUNCTION__ " - size %d URB not understood", + urb->actual_length); + usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data); + } else { + int i; + int bytes_sent = ((__u8 *) data)[0] + + ((unsigned int) ((__u8 *) data)[1] << 8); + tty = port->tty; + /* we should immediately resubmit the URB, before attempting + * to pass the data on to the tty layer. But that needs locking + * against re-entry an then mixed-up data because of + * intermixed tty_flip_buffer_push()s + * FIXME + */ + usb_serial_debug_data (__FILE__, __FUNCTION__, + urb->actual_length, data); + + if (bytes_sent + 2 > urb->actual_length) { + dbg(__FUNCTION__ + " - trying to read more data than available" + " (%d vs. %d)", + bytes_sent+2, urb->actual_length); + /* cap at implied limit */ + bytes_sent = urb->actual_length - 2; + } + + for (i = 2; i < 2+bytes_sent; i++) { + /* if we insert more than TTY_FLIPBUF_SIZE characters, + * we drop them. */ + if(tty->flip.count >= TTY_FLIPBUF_SIZE) { + tty_flip_buffer_push(tty); + } + /* this doesn't actually push the data through unless + * tty->low_latency is set */ + tty_insert_flip_char(tty, ((__u8*) data)[i], 0); + } + tty_flip_buffer_push(tty); + priv->bytes_in += bytes_sent; + } + /* Continue trying to always read */ + FILL_BULK_URB(port->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, + port->bulk_in_endpointAddress), + port->read_urb->transfer_buffer, + port->read_urb->transfer_buffer_length, + klsi_105_read_bulk_callback, + port); + rc = usb_submit_urb(port->read_urb); + if (rc) + err(__FUNCTION__ + " - failed resubmitting read urb, error %d", rc); +} /* klsi_105_read_bulk_callback */ + + +static void klsi_105_set_termios (struct usb_serial_port *port, + struct termios *old_termios) +{ + struct usb_serial *serial = port->serial; + struct klsi_105_private *priv = (struct klsi_105_private *)port->private; + unsigned int iflag = port->tty->termios->c_iflag; + unsigned int old_iflag = old_termios->c_iflag; + unsigned int cflag = port->tty->termios->c_cflag; + unsigned int old_cflag = old_termios->c_cflag; + + /* + * Update baud rate + */ + if( (cflag & CBAUD) != (old_cflag & CBAUD) ) { + /* reassert DTR and (maybe) RTS on transition from B0 */ + if( (old_cflag & CBAUD) == B0 ) { + dbg(__FUNCTION__ ": baud was B0"); +#if 0 + priv->control_state |= TIOCM_DTR; + /* don't set RTS if using hardware flow control */ + if (!(old_cflag & CRTSCTS)) { + priv->control_state |= TIOCM_RTS; + } + mct_u232_set_modem_ctrl(serial, priv->control_state); +#endif + } + + switch(cflag & CBAUD) { + case B0: /* handled below */ + break; + case B1200: priv->cfg.baudrate = kl5kusb105a_sio_b1200; + break; + case B2400: priv->cfg.baudrate = kl5kusb105a_sio_b2400; + break; + case B4800: priv->cfg.baudrate = kl5kusb105a_sio_b4800; + break; + case B9600: priv->cfg.baudrate = kl5kusb105a_sio_b9600; + break; + case B19200: priv->cfg.baudrate = kl5kusb105a_sio_b19200; + break; + case B38400: priv->cfg.baudrate = kl5kusb105a_sio_b38400; + break; + case B57600: priv->cfg.baudrate = kl5kusb105a_sio_b57600; + break; + case B115200: priv->cfg.baudrate = kl5kusb105a_sio_b115200; + break; + default: + err("KLSI USB->Serial converter:" + " unsupported baudrate request, using default" + " of 9600"); + priv->cfg.baudrate = kl5kusb105a_sio_b9600; + break; + } + if ((cflag & CBAUD) == B0 ) { + dbg(__FUNCTION__ ": baud is B0"); + /* Drop RTS and DTR */ + /* maybe this should be simulated by sending read + * disable and read enable messages? + */ + ; +#if 0 + priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS); + mct_u232_set_modem_ctrl(serial, priv->control_state); +#endif + } + } + + if ((cflag & CSIZE) != (old_cflag & CSIZE)) { + /* set the number of data bits */ + switch (cflag & CSIZE) { + case CS5: + dbg(__FUNCTION__ " - 5 bits/byte not supported"); + return ; + case CS6: + dbg(__FUNCTION__ " - 6 bits/byte not supported"); + return ; + case CS7: + priv->cfg.databits = kl5kusb105a_dtb_7; + break; + case CS8: + priv->cfg.databits = kl5kusb105a_dtb_8; + break; + default: + err("CSIZE was not CS5-CS8, using default of 8"); + priv->cfg.databits = kl5kusb105a_dtb_8; + break; + } + } + + /* + * Update line control register (LCR) + */ + if ((cflag & (PARENB|PARODD)) != (old_cflag & (PARENB|PARODD)) + || (cflag & CSTOPB) != (old_cflag & CSTOPB) ) { + +#if 0 + priv->last_lcr = 0; + + /* set the parity */ + if (cflag & PARENB) + priv->last_lcr |= (cflag & PARODD) ? + MCT_U232_PARITY_ODD : MCT_U232_PARITY_EVEN; + else + priv->last_lcr |= MCT_U232_PARITY_NONE; + + /* set the number of stop bits */ + priv->last_lcr |= (cflag & CSTOPB) ? + MCT_U232_STOP_BITS_2 : MCT_U232_STOP_BITS_1; + + mct_u232_set_line_ctrl(serial, priv->last_lcr); +#endif + ; + } + + /* + * Set flow control: well, I do not really now how to handle DTR/RTS. + * Just do what we have seen with SniffUSB on Win98. + */ + if( (iflag & IXOFF) != (old_iflag & IXOFF) + || (iflag & IXON) != (old_iflag & IXON) + || (cflag & CRTSCTS) != (old_cflag & CRTSCTS) ) { + + /* Drop DTR/RTS if no flow control otherwise assert */ +#if 0 + if ((iflag & IXOFF) || (iflag & IXON) || (cflag & CRTSCTS) ) + priv->control_state |= TIOCM_DTR | TIOCM_RTS; + else + priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS); + mct_u232_set_modem_ctrl(serial, priv->control_state); +#endif + ; + } + + /* now commit changes to device */ + klsi_105_chg_port_settings(serial, &(priv->cfg)); +} /* klsi_105_set_termios */ + + +#if 0 +static void mct_u232_break_ctl( struct usb_serial_port *port, int break_state ) +{ + struct usb_serial *serial = port->serial; + struct mct_u232_private *priv = (struct mct_u232_private *)port->private; + unsigned char lcr = priv->last_lcr; + + dbg (__FUNCTION__ "state=%d", break_state); + + if (break_state) + lcr |= MCT_U232_SET_BREAK; + + mct_u232_set_line_ctrl(serial, lcr); +} /* mct_u232_break_ctl */ +#endif + +static int klsi_105_ioctl (struct usb_serial_port *port, struct file * file, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial *serial = port->serial; + struct klsi_105_private *priv = (struct klsi_105_private *)port->private; + int mask; + + dbg (__FUNCTION__ "cmd=0x%x", cmd); + + /* Based on code from acm.c and others */ + switch (cmd) { + case TIOCMGET: { + int rc; + unsigned long line_state; + dbg (__FUNCTION__ " - TIOCMGET request, just guessing"); + + rc = klsi_105_get_line_state(serial, &line_state); + if (rc < 0) { + err("Reading line control failed (error = %d)", rc); + /* better return value? EAGAIN? */ + return -ENOIOCTLCMD; + } else { + priv->line_state = line_state; + dbg(__FUNCTION__ " - read line state 0x%lx", line_state); + } + return put_user(priv->line_state, (unsigned long *) arg); + }; + + case TIOCMSET: /* Turns on and off the lines as specified by the mask */ + case TIOCMBIS: /* turns on (Sets) the lines as specified by the mask */ + case TIOCMBIC: /* turns off (Clears) the lines as specified by the mask */ + if (get_user(mask, (unsigned long *) arg)) + return -EFAULT; + + if ((cmd == TIOCMSET) || (mask & TIOCM_RTS)) { + /* RTS needs set */ + if( ((cmd == TIOCMSET) && (mask & TIOCM_RTS)) || + (cmd == TIOCMBIS) ) + dbg (__FUNCTION__ " - set RTS not handled"); + /* priv->control_state |= TIOCM_RTS; */ + else + dbg (__FUNCTION__ " - clear RTS not handled"); + /* priv->control_state &= ~TIOCM_RTS; */ + } + + if ((cmd == TIOCMSET) || (mask & TIOCM_DTR)) { + /* DTR needs set */ + if( ((cmd == TIOCMSET) && (mask & TIOCM_DTR)) || + (cmd == TIOCMBIS) ) + dbg (__FUNCTION__ " - set DTR not handled"); + /* priv->control_state |= TIOCM_DTR; */ + else + dbg (__FUNCTION__ " - clear DTR not handled"); + /* priv->control_state &= ~TIOCM_DTR; */ + } + /* + mct_u232_set_modem_ctrl(serial, priv->control_state); + */ + break; + + case TIOCMIWAIT: + /* wait for any of the 4 modem inputs (DCD,RI,DSR,CTS)*/ + /* TODO */ + dbg (__FUNCTION__ " - TIOCMIWAIT not handled"); + return -ENOIOCTLCMD; + + case TIOCGICOUNT: + /* return count of modemline transitions */ + /* TODO */ + dbg (__FUNCTION__ " - TIOCGICOUNT not handled"); + return -ENOIOCTLCMD; + case TCGETS: { + /* return current info to caller */ + int retval; + + dbg (__FUNCTION__ " - TCGETS data faked/incomplete"); + + retval = verify_area(VERIFY_WRITE, (void *)arg, + sizeof(struct termios)); + + if (retval) + return(retval); + + kernel_termios_to_user_termios((struct termios *)arg, + &priv->termios); + return(0); + } + case TCSETS: { + /* set port termios to the one given by the user */ + int retval; + + dbg (__FUNCTION__ " - TCSETS not handled"); + + retval = verify_area(VERIFY_READ, (void *)arg, + sizeof(struct termios)); + + if (retval) + return(retval); + + user_termios_to_kernel_termios(&priv->termios, + (struct termios *)arg); + klsi_105_set_termios(port, &priv->termios); + return(0); + } + case TCSETSW: { + /* set port termios and try to wait for completion of last + * write operation */ + /* We guess here. If there are not too many write urbs + * outstanding, we lie. */ + /* what is the right way to wait here? schedule() ? */ + /* + while (klsi_105_chars_in_buffer(port) > (NUM_URBS / 4 ) * URB_TRANSFER_BUFFER_SIZE) + schedule(); + */ + return -ENOIOCTLCMD; + } + default: + dbg(__FUNCTION__ ": arg not supported - 0x%04x",cmd); + return(-ENOIOCTLCMD); + break; + } + return 0; +} /* klsi_105_ioctl */ + +static void klsi_105_throttle (struct usb_serial_port *port) +{ + + dbg(__FUNCTION__ " - port %d", port->number); + + down (&port->sem); + + usb_unlink_urb (port->read_urb); + + up (&port->sem); + + return; +} +static void klsi_105_unthrottle (struct usb_serial_port *port) +{ + int result; + + dbg(__FUNCTION__ " - port %d", port->number); + + down (&port->sem); + + port->read_urb->dev = port->serial->dev; + result = usb_submit_urb(port->read_urb); + if (result) + err(__FUNCTION__ " - failed submitting read urb, error %d", + result); + + up (&port->sem); + + return; +} + + + +static int __init klsi_105_init (void) +{ + usb_serial_register (&kl5kusb105d_device); + + info(DRIVER_DESC " " DRIVER_VERSION); + return 0; +} + + +static void __exit klsi_105_exit (void) +{ + usb_serial_deregister (&kl5kusb105d_device); +} + + +module_init (klsi_105_init); +module_exit (klsi_105_exit); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + + +MODULE_PARM(debug, "i"); +MODULE_PARM_DESC(debug, "enable extensive debugging messages"); +/* FIXME: implement +MODULE_PARM(num_urbs, "i"); +MODULE_PARM_DESC(num_urbs, "number of URBs to use in write pool"); +*/ + +/* vim: set sts=8 ts=8 sw=8: */ diff -Nru a/drivers/usb/serial/kl5kusb105.h b/drivers/usb/serial/kl5kusb105.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/serial/kl5kusb105.h Wed Dec 19 10:26:03 2001 @@ -0,0 +1,69 @@ +/* + * Definitions for the KLSI KL5KUSB105 serial port adapter + */ + +/* vendor/product pairs that are known to contain this chipset */ +#define PALMCONNECT_VID 0x0830 +#define PALMCONNECT_PID 0x0080 + +#define KLSI_VID 0x05e9 +#define KLSI_KL5KUSB105D_PID 0x00c0 + +/* Vendor commands: */ + + +/* port table -- the chip supports up to 4 channels */ + +/* baud rates */ + +typedef enum { + kl5kusb105a_sio_b115200 = 0, + kl5kusb105a_sio_b57600 = 1, + kl5kusb105a_sio_b38400 = 2, + kl5kusb105a_sio_b19200 = 4, + kl5kusb105a_sio_b14400 = 5, + kl5kusb105a_sio_b9600 = 6, + kl5kusb105a_sio_b4800 = 8, /* unchecked */ + kl5kusb105a_sio_b2400 = 9, /* unchecked */ + kl5kusb105a_sio_b1200 = 0xa, /* unchecked */ + kl5kusb105a_sio_b600 = 0xb /* unchecked */ +} KL5KUSB105A_SIO_baudrate_t; + +/* data bits */ +#define kl5kusb105a_dtb_7 7 +#define kl5kusb105a_dtb_8 8 + + + +/* requests: */ +#define KL5KUSB105A_SIO_SET_DATA 1 +#define KL5KUSB105A_SIO_POLL 2 +#define KL5KUSB105A_SIO_CONFIGURE 3 +/* values used for request KL5KUSB105A_SIO_CONFIGURE */ +#define KL5KUSB105A_SIO_CONFIGURE_READ_ON 3 +#define KL5KUSB105A_SIO_CONFIGURE_READ_OFF 2 + +/* Interpretation of modem status lines */ +/* These need sorting out by individually connecting pins and checking + * results. FIXME! + * When data is being sent we see 0x30 in the lower byte; this must + * contain DSR and CTS ... + */ +#define KL5KUSB105A_DSR ((1<<4) | (1<<5)) +#define KL5KUSB105A_CTS ((1<<5) | (1<<4)) + +#define KL5KUSB105A_WANTS_TO_SEND 0x30 +//#define KL5KUSB105A_DTR /* Data Terminal Ready */ +//#define KL5KUSB105A_CTS /* Clear To Send */ +//#define KL5KUSB105A_CD /* Carrier Detect */ +//#define KL5KUSB105A_DSR /* Data Set Ready */ +//#define KL5KUSB105A_RxD /* Receive pin */ + +//#define KL5KUSB105A_LE +//#define KL5KUSB105A_RTS +//#define KL5KUSB105A_ST +//#define KL5KUSB105A_SR +//#define KL5KUSB105A_RI /* Ring Indicator */ + +/* vim: set ts=8 sts=8: */ + diff -Nru a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c --- a/drivers/usb/serial/visor.c Wed Dec 19 10:26:02 2001 +++ b/drivers/usb/serial/visor.c Wed Dec 19 10:26:02 2001 @@ -12,6 +12,10 @@ * * See Documentation/usb/usb-serial.txt for more information on using this driver * + * (12/18/2001) gkh + * Added better Clie support for 3.5 devices. Thanks to Geoffrey Levand + * for the patch. + * * (11/11/2001) gkh * Added support for the m125 devices, and added check to prevent oopses * for Clié devices that lie about the number of ports they have. @@ -127,7 +131,7 @@ /* * Version Information */ -#define DRIVER_VERSION "v1.7" +#define DRIVER_VERSION "v1.8" #define DRIVER_AUTHOR "Greg Kroah-Hartman " #define DRIVER_DESC "USB HandSpring Visor, Palm m50x, Sony Clié driver" @@ -145,6 +149,7 @@ static void visor_set_termios (struct usb_serial_port *port, struct termios *old_termios); static void visor_write_bulk_callback (struct urb *urb); static void visor_read_bulk_callback (struct urb *urb); +static int clie_3_5_startup (struct usb_serial *serial); static __devinitdata struct usb_device_id combined_id_table [] = { @@ -210,6 +215,7 @@ close: visor_close, throttle: visor_throttle, unthrottle: visor_unthrottle, + startup: clie_3_5_startup, ioctl: visor_ioctl, set_termios: visor_set_termios, write: visor_write, @@ -644,6 +650,46 @@ return 0; } +static int clie_3_5_startup (struct usb_serial *serial) +{ + int result; + u8 data; + + dbg(__FUNCTION__); + + /* + * Note that PEG-300 series devices expect the following two calls. + */ + + /* get the config number */ + result = usb_control_msg (serial->dev, usb_rcvctrlpipe(serial->dev, 0), + USB_REQ_GET_CONFIGURATION, USB_DIR_IN, + 0, 0, &data, 1, HZ * 3); + if (result < 0) { + err(__FUNCTION__ ": get config number failed: %d", result); + return result; + } + if (result != 1) { + err(__FUNCTION__ ": get config number bad return length: %d", result); + return -EIO; + } + + /* get the interface number */ + result = usb_control_msg (serial->dev, usb_rcvctrlpipe(serial->dev, 0), + USB_REQ_GET_INTERFACE, + USB_DIR_IN | USB_DT_DEVICE, + 0, 0, &data, 1, HZ * 3); + if (result < 0) { + err(__FUNCTION__ ": get interface number failed: %d", result); + return result; + } + if (result != 1) { + err(__FUNCTION__ ": get interface number bad return length: %d", result); + return -EIO; + } + + return 0; +} static void visor_shutdown (struct usb_serial *serial) { diff -Nru a/drivers/usb/stv680.c b/drivers/usb/stv680.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/stv680.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,1633 @@ +/* + * STV0680 USB Camera Driver, by Kevin Sisson (kjsisson@bellsouth.net) + * + * Thanks to STMicroelectronics for information on the usb commands, and + * to Steve Miller at STM for his help and encouragement while I was + * writing this driver. + * + * This driver is based heavily on the + * Endpoints (formerly known as AOX) se401 USB Camera Driver + * Copyright (c) 2000 Jeroen B. Vreeken (pe1rxq@amsat.org) + * + * Still somewhat based on the Linux ov511 driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * History: + * ver 0.1 October, 2001. Initial attempt. + * + * ver 0.2 November, 2001. Fixed asbility to resize, added brightness + * function, made more stable (?) + * + * ver 0.21 Nov, 2001. Added gamma correction and white balance, + * due to Alexander Schwartz. Still trying to + * improve stablility. Moved stuff into stv680.h + * + * ver 0.22 Nov, 2001. Added sharpen function (by Michael Sweet, + * mike@easysw.com) from GIMP, also used in pencam. + * Simple, fast, good integer math routine. + * + * ver 0.23 Dec, 2001 (gkh) + * Took out sharpen function, ran code through + * Lindent, and did other minor tweaks to get + * things to work properly with 2.5.1 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stv680.h" + +static int video_nr = -1; +static int swapRGB = 0; + +static unsigned int debug = 0; + +#define PDEBUG(level, fmt, args...) \ + do { \ + if (debug >= level) \ + info("[" __PRETTY_FUNCTION__ ":%d] " fmt, __LINE__ , ## args); \ + } while (0) + + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.23" +#define DRIVER_AUTHOR "Kevin Sisson " +#define DRIVER_DESC "STV0680 USB Camera Driver" + +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_LICENSE ("GPL"); +MODULE_PARM (debug, "i"); +MODULE_PARM_DESC (debug, "Debug enabled or not"); +MODULE_PARM (swapRGB, "i"); +MODULE_PARM_DESC (swapRGB, "Swap red and blue, e.g., for xawtv"); +MODULE_PARM (video_nr, "i"); +EXPORT_NO_SYMBOLS; + +/******************************************************************** + * + * Memory management + * + * This is a shameless copy from the USB-cpia driver (linux kernel + * version 2.3.29 or so, I have no idea what this code actually does ;). + * Actually it seems to be a copy of a shameless copy of the bttv-driver. + * Or that is a copy of a shameless copy of ... (To the powers: is there + * no generic kernel-function to do this sort of stuff?) + * + * Yes, it was a shameless copy from the bttv-driver. IIRC, Alan says + * there will be one, but apparentely not yet -jerdfelt + * + * So I copied it again for the ov511 driver -claudio + * + * Same for the se401 driver -Jeroen + * + * And the STV0680 driver - Kevin + ********************************************************************/ + +/* Given PGD from the address space's page table, return the kernel + * virtual mapping of the physical memory mapped at ADR. + */ +static inline unsigned long uvirt_to_kva (pgd_t * pgd, unsigned long adr) +{ + unsigned long ret = 0UL; + pmd_t *pmd; + pte_t *ptep, pte; + + if (!pgd_none (*pgd)) { + pmd = pmd_offset (pgd, adr); + if (!pmd_none (*pmd)) { + ptep = pte_offset (pmd, adr); + pte = *ptep; + if (pte_present (pte)) { + ret = (unsigned long) page_address (pte_page (pte)); + ret |= (adr & (PAGE_SIZE - 1)); + } + } + } + return ret; +} + +/* Here we want the physical address of the memory. This is used when + * initializing the contents of the area and marking the pages as reserved. + */ +static inline unsigned long kvirt_to_pa (unsigned long adr) +{ + unsigned long va, kva, ret; + + va = VMALLOC_VMADDR (adr); + kva = uvirt_to_kva (pgd_offset_k (va), va); + ret = __pa (kva); + return ret; +} + +static void *rvmalloc (unsigned long size) +{ + void *mem; + unsigned long adr, page; + + /* Round it off to PAGE_SIZE */ + size += (PAGE_SIZE - 1); + size &= ~(PAGE_SIZE - 1); + + mem = vmalloc_32 (size); + if (!mem) + return NULL; + + memset (mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + page = kvirt_to_pa (adr); + mem_map_reserve (virt_to_page (__va (page))); + adr += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + return mem; +} + +static void rvfree (void *mem, unsigned long size) +{ + unsigned long adr, page; + + if (!mem) + return; + + size += (PAGE_SIZE - 1); + size &= ~(PAGE_SIZE - 1); + + adr = (unsigned long) mem; + while (size > 0) { + page = kvirt_to_pa (adr); + mem_map_unreserve (virt_to_page (__va (page))); + adr += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + vfree (mem); +} + + +/********************************************************************* + * pencam read/write functions + ********************************************************************/ + +static int stv_sndctrl (int set, struct usb_stv *stv680, unsigned short req, unsigned short value, unsigned char *buffer, int size) +{ + int ret = -1; + + switch (set) { + case 0: /* 0xc1 */ + ret = usb_control_msg (stv680->udev, + usb_rcvctrlpipe (stv680->udev, 0), + req, + (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT), + value, 0, buffer, size, PENCAM_TIMEOUT); + break; + + case 1: /* 0x41 */ + ret = usb_control_msg (stv680->udev, + usb_sndctrlpipe (stv680->udev, 0), + req, + (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT), + value, 0, buffer, size, PENCAM_TIMEOUT); + break; + + case 2: /* 0x80 */ + ret = usb_control_msg (stv680->udev, + usb_rcvctrlpipe (stv680->udev, 0), + req, + (USB_DIR_IN | USB_RECIP_DEVICE), + value, 0, buffer, size, PENCAM_TIMEOUT); + break; + + case 3: /* 0x40 */ + ret = usb_control_msg (stv680->udev, + usb_sndctrlpipe (stv680->udev, 0), + req, + (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE), + value, 0, buffer, size, PENCAM_TIMEOUT); + break; + + } + if ((ret < 0) && (req != 0x0a)) { + PDEBUG (1, "STV(e): usb_control_msg error %i, request = 0x%x, error = %i", set, req, ret); + } + return ret; +} + +static int stv_set_config (struct usb_stv *dev, int configuration, int interface, int alternate) +{ + + if (usb_set_configuration (dev->udev, configuration) < 0) { + PDEBUG (1, "STV(e): FAILED to set configuration %i", configuration); + return -1; + } + if (usb_set_interface (dev->udev, interface, alternate) < 0) { + PDEBUG (1, "STV(e): FAILED to set alternate interface %i", alternate); + return -1; + } + return 0; +} + +static int stv_stop_video (struct usb_stv *dev) +{ + int i; + unsigned char *buf; + + buf = kmalloc (40, GFP_KERNEL); + if (buf == NULL) { + PDEBUG (0, "STV(e): Out of (small buf) memory"); + return -1; + } + + /* this is a high priority command; it stops all lower order commands */ + if ((i = stv_sndctrl (1, dev, 0x04, 0x0000, buf, 0x0)) < 0) { + i = stv_sndctrl (0, dev, 0x80, 0, buf, 0x02); /* Get Last Error; 2 = busy */ + PDEBUG (1, "STV(i): last error: %i, command = 0x%x", buf[0], buf[1]); + } else { + PDEBUG (1, "STV(i): Camera reset to idle mode."); + } + + if ((i = stv_set_config (dev, 1, 0, 0)) < 0) + PDEBUG (1, "STV(e): Reset config during exit failed"); + + /* get current mode */ + buf[0] = 0xf0; + if ((i = stv_sndctrl (0, dev, 0x87, 0, buf, 0x08)) != 0x08) /* get mode */ + PDEBUG (0, "STV(e): Stop_video: problem setting original mode"); + if (dev->origMode != buf[0]) { + memset (buf, 0, 8); + buf[0] = (unsigned char) dev->origMode; + if ((i = stv_sndctrl (3, dev, 0x07, 0x0100, buf, 0x08)) != 0x08) { + PDEBUG (0, "STV(e): Stop_video: Set_Camera_Mode failed"); + i = -1; + } + buf[0] = 0xf0; + i = stv_sndctrl (0, dev, 0x87, 0, buf, 0x08); + if ((i != 0x08) || (buf[0] != dev->origMode)) { + PDEBUG (0, "STV(e): camera NOT set to original resolution."); + i = -1; + } else + PDEBUG (0, "STV(i): Camera set to original resolution"); + } + /* origMode */ + kfree (buf); + return i; +} + +static int stv_set_video_mode (struct usb_stv *dev) +{ + int i, stop_video = 1; + unsigned char *buf; + + buf = kmalloc (40, GFP_KERNEL); + if (buf == NULL) { + PDEBUG (0, "STV(e): Out of (small buf) memory"); + return -1; + } + + if ((i = stv_set_config (dev, 1, 0, 0)) < 0) { + kfree (buf); + return i; + } + + i = stv_sndctrl (2, dev, 0x06, 0x0100, buf, 0x12); + if (!(i > 0) && (buf[8] == 0x53) && (buf[9] == 0x05)) { + PDEBUG (1, "STV(e): Could not get descriptor 0100."); + goto error; + } + + /* set alternate interface 1 */ + if ((i = stv_set_config (dev, 1, 0, 1)) < 0) + goto error; + + if ((i = stv_sndctrl (0, dev, 0x85, 0, buf, 0x10)) != 0x10) + goto error; + PDEBUG (1, "STV(i): Setting video mode."); + /* Switch to Video mode: 0x0100 = VGA (640x480), 0x0000 = CIF (352x288) 0x0300 = QVGA (320x240) */ + if ((i = stv_sndctrl (1, dev, 0x09, dev->VideoMode, buf, 0x0)) < 0) { + stop_video = 0; + goto error; + } + goto exit; + +error: + kfree (buf); + if (stop_video == 1) + stv_stop_video (dev); + return -1; + +exit: + kfree (buf); + return 0; +} + +static int stv_init (struct usb_stv *stv680) +{ + int i = 0; + unsigned char *buffer; + unsigned long int bufsize; + + buffer = kmalloc (40, GFP_KERNEL); + if (buffer == NULL) { + PDEBUG (0, "STV(e): Out of (small buf) memory"); + return -1; + } + memset (buffer, 0, 40); + udelay (100); + + /* set config 1, interface 0, alternate 0 */ + if ((i = stv_set_config (stv680, 1, 0, 0)) < 0) { + kfree (buffer); + PDEBUG (0, "STV(e): set config 1,0,0 failed"); + return -1; + } + /* ping camera to be sure STV0680 is present */ + if ((i = stv_sndctrl (0, stv680, 0x88, 0x5678, buffer, 0x02)) != 0x02) + goto error; + if ((buffer[0] != 0x56) || (buffer[1] != 0x78)) { + PDEBUG (1, "STV(e): camera ping failed!!"); + goto error; + } + + /* get camera descriptor */ + if ((i = stv_sndctrl (2, stv680, 0x06, 0x0200, buffer, 0x09)) != 0x09) + goto error; + i = stv_sndctrl (2, stv680, 0x06, 0x0200, buffer, 0x22); + if (!(i >= 0) && (buffer[7] == 0xa0) && (buffer[8] == 0x23)) { + PDEBUG (1, "STV(e): Could not get descriptor 0200."); + goto error; + } + if ((i = stv_sndctrl (0, stv680, 0x8a, 0, buffer, 0x02)) != 0x02) + goto error; + if ((i = stv_sndctrl (0, stv680, 0x8b, 0, buffer, 0x24)) != 0x24) + goto error; + if ((i = stv_sndctrl (0, stv680, 0x85, 0, buffer, 0x10)) != 0x10) + goto error; + + stv680->SupportedModes = buffer[7]; + i = stv680->SupportedModes; + stv680->CIF = 0; + stv680->VGA = 0; + stv680->QVGA = 0; + if (i & 1) + stv680->CIF = 1; + if (i & 2) + stv680->VGA = 1; + if (i & 8) + stv680->QVGA = 1; + if (stv680->SupportedModes == 0) { + PDEBUG (0, "STV(e): There are NO supported STV680 modes!!"); + i = -1; + goto error; + } else { + if (stv680->CIF) + PDEBUG (0, "STV(i): CIF is supported"); + if (stv680->QVGA) + PDEBUG (0, "STV(i): QVGA is supported"); + } + /* FW rev, ASIC rev, sensor ID */ + PDEBUG (1, "STV(i): Firmware rev is %i.%i", buffer[0], buffer[1]); + PDEBUG (1, "STV(i): ASIC rev is %i.%i", buffer[2], buffer[3]); + PDEBUG (1, "STV(i): Sensor ID is %i%i", buffer[4], buffer[5]); + + /* set alternate interface 1 */ + if ((i = stv_set_config (stv680, 1, 0, 1)) < 0) + goto error; + + if ((i = stv_sndctrl (0, stv680, 0x85, 0, buffer, 0x10)) != 0x10) + goto error; + if ((i = stv_sndctrl (0, stv680, 0x8d, 0, buffer, 0x08)) != 0x08) + goto error; + i = buffer[3]; + PDEBUG (0, "STV(i): Camera has %i pictures.", i); + + /* get current mode */ + if ((i = stv_sndctrl (0, stv680, 0x87, 0, buffer, 0x08)) != 0x08) + goto error; + stv680->origMode = buffer[0]; /* 01 = VGA, 03 = QVGA, 00 = CIF */ + + /* This will attemp CIF mode, if supported. If not, set to QVGA */ + memset (buffer, 0, 8); + if (stv680->CIF) + buffer[0] = 0x00; + else if (stv680->QVGA) + buffer[0] = 0x03; + if ((i = stv_sndctrl (3, stv680, 0x07, 0x0100, buffer, 0x08)) != 0x08) { + PDEBUG (0, "STV(i): Set_Camera_Mode failed"); + i = -1; + goto error; + } + buffer[0] = 0xf0; + stv_sndctrl (0, stv680, 0x87, 0, buffer, 0x08); + if (((stv680->CIF == 1) && (buffer[0] != 0x00)) || ((stv680->QVGA == 1) && (buffer[0] != 0x03))) { + PDEBUG (0, "STV(e): Error setting camera video mode!"); + i = -1; + goto error; + } else { + if (buffer[0] == 0) { + stv680->VideoMode = 0x0000; + PDEBUG (0, "STV(i): Video Mode set to CIF"); + } + if (buffer[0] == 0x03) { + stv680->VideoMode = 0x0300; + PDEBUG (0, "STV(i): Video Mode set to QVGA"); + } + } + if ((i = stv_sndctrl (0, stv680, 0x8f, 0, buffer, 0x10)) != 0x10) + goto error; + bufsize = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3]); + stv680->cwidth = (buffer[4] << 8) | (buffer[5]); /* ->camera = 322, 356, 644 */ + stv680->cheight = (buffer[6] << 8) | (buffer[7]); /* ->camera = 242, 292, 484 */ + stv680->origGain = buffer[12]; + + goto exit; + +error: + i = stv_sndctrl (0, stv680, 0x80, 0, buffer, 0x02); /* Get Last Error */ + PDEBUG (1, "STV(i): last error: %i, command = 0x%x", buffer[0], buffer[1]); + kfree (buffer); + return -1; + +exit: + kfree (buffer); + + /* video = 320x240, 352x288 */ + if (stv680->CIF == 1) { + stv680->maxwidth = 352; + stv680->maxheight = 288; + stv680->vwidth = 352; + stv680->vheight = 288; + } + if (stv680->QVGA == 1) { + stv680->maxwidth = 320; + stv680->maxheight = 240; + stv680->vwidth = 320; + stv680->vheight = 240; + } + + stv680->rawbufsize = bufsize; /* must be ./. by 8 */ + stv680->maxframesize = bufsize * 3; /* RGB size */ + PDEBUG (2, "STV(i): cwidth = %i, cheight = %i", stv680->cwidth, stv680->cheight); + PDEBUG (1, "STV(i): width = %i, height = %i, rawbufsize = %li", stv680->vwidth, stv680->vheight, stv680->rawbufsize); + + /* some default values */ + stv680->bulk_in_endpointAddr = 0x82; + stv680->dropped = 0; + stv680->error = 0; + stv680->framecount = 0; + stv680->readcount = 0; + stv680->streaming = 0; + /* bright, white, colour, hue, contrast are set by software, not in stv0680 */ + stv680->brightness = 32767; + stv680->chgbright = 0; + stv680->whiteness = 0; /* only for greyscale */ + stv680->colour = 32767; + stv680->contrast = 32767; + stv680->hue = 32767; + stv680->palette = STV_VIDEO_PALETTE; + stv680->depth = 24; /* rgb24 bits */ + swapRGB = 0; + PDEBUG (1, "STV(i): swapRGB is OFF"); + + if (stv_set_video_mode (stv680) < 0) { + PDEBUG (0, "STV(e): Could not set video mode in stv_init"); + return -1; + } + + return 0; +} + +/***************** last of pencam routines *******************/ + +/******************************************************************** + * /proc interface + *******************************************************************/ + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + +static struct proc_dir_entry *stv680_proc_entry = NULL; +extern struct proc_dir_entry *video_proc_entry; + +#define YES_NO(x) ((x) ? "yes" : "no") + +static int stv680_read_proc (char *page, char **start, off_t off, int count, int *eof, void *data) +{ + char *out = page; + int len; + struct usb_stv *stv680 = data; + + /* Stay under PAGE_SIZE or else bla bla bla.... */ + + out += sprintf (out, "driver_version : %s\n", DRIVER_VERSION); + out += sprintf (out, "model : %s\n", stv680->camera_name); + out += sprintf (out, "in use : %s\n", YES_NO (stv680->user)); + out += sprintf (out, "streaming : %s\n", YES_NO (stv680->streaming)); + out += sprintf (out, "num_frames : %d\n", STV680_NUMFRAMES); + + out += sprintf (out, "Current size : %ix%i\n", stv680->vwidth, stv680->vheight); + out += sprintf (out, "swapRGB : %s\n", YES_NO (swapRGB)); + out += sprintf (out, "Palette : %i", stv680->palette); + + out += sprintf (out, "\n"); + + out += sprintf (out, "Frames total : %d\n", stv680->readcount); + out += sprintf (out, "Frames read : %d\n", stv680->framecount); + out += sprintf (out, "Packets dropped : %d\n", stv680->dropped); + out += sprintf (out, "Decoding Errors : %d\n", stv680->error); + + len = out - page; + len -= off; + if (len < count) { + *eof = 1; + if (len <= 0) + return 0; + } else + len = count; + + *start = page + off; + return len; +} + +static int create_proc_stv680_cam (struct usb_stv *stv680) +{ + char name[9]; + struct proc_dir_entry *ent; + + if (!stv680_proc_entry || !stv680) + return -1; + + sprintf (name, "video%d", stv680->vdev.minor); + + ent = create_proc_entry (name, S_IFREG | S_IRUGO | S_IWUSR, stv680_proc_entry); + if (!ent) + return -1; + + ent->data = stv680; + ent->read_proc = stv680_read_proc; + stv680->proc_entry = ent; + return 0; +} + +static void destroy_proc_stv680_cam (struct usb_stv *stv680) +{ + /* One to much, just to be sure :) */ + char name[9]; + + if (!stv680 || !stv680->proc_entry) + return; + + sprintf (name, "video%d", stv680->vdev.minor); + remove_proc_entry (name, stv680_proc_entry); + stv680->proc_entry = NULL; +} + +static int proc_stv680_create (void) +{ + if (video_proc_entry == NULL) { + PDEBUG (0, "STV(e): /proc/video/ doesn't exist!"); + return -1; + } + stv680_proc_entry = create_proc_entry ("stv680", S_IFDIR, video_proc_entry); + + if (stv680_proc_entry) { + stv680_proc_entry->owner = THIS_MODULE; + } else { + PDEBUG (0, "STV(e): Unable to initialize /proc/video/stv680"); + return -1; + } + return 0; +} + +static void proc_stv680_destroy (void) +{ + if (stv680_proc_entry == NULL) + return; + + remove_proc_entry ("stv", video_proc_entry); +} +#endif /* CONFIG_PROC_FS && CONFIG_VIDEO_PROC_FS */ + +/******************************************************************** + * Camera control + *******************************************************************/ + +static int stv680_get_pict (struct usb_stv *stv680, struct video_picture *p) +{ + /* This sets values for v4l interface. max/min = 65535/0 */ + + p->brightness = stv680->brightness; + p->whiteness = stv680->whiteness; /* greyscale */ + p->colour = stv680->colour; + p->contrast = stv680->contrast; + p->hue = stv680->hue; + p->palette = stv680->palette; + p->depth = stv680->depth; + return 0; +} + +static int stv680_set_pict (struct usb_stv *stv680, struct video_picture *p) +{ + /* See above stv680_get_pict */ + + if (p->palette != STV_VIDEO_PALETTE) { + PDEBUG (2, "STV(e): Palette set error in _set_pic"); + return 1; + } + + if (stv680->brightness != p->brightness) { + stv680->chgbright = 1; + stv680->brightness = p->brightness; + } else { + stv680->chgbright = 0; + } + + stv680->whiteness = p->whiteness; /* greyscale */ + stv680->colour = p->colour; + stv680->contrast = p->contrast; + stv680->hue = p->hue; + stv680->palette = p->palette; + stv680->depth = p->depth; + + return 0; +} + +static void stv680_video_irq (struct urb *urb) +{ + struct usb_stv *stv680 = urb->context; + int length = urb->actual_length; + + if (length < stv680->rawbufsize) + PDEBUG (2, "STV(i): Lost data in transfer: exp %li, got %i", stv680->rawbufsize, length); + + /* ohoh... */ + if (!stv680->streaming) + return; + + if (!stv680->udev) { + PDEBUG (0, "STV(e): device vapourished in video_irq"); + return; + } + + /* 0 sized packets happen if we are to fast, but sometimes the camera + keeps sending them forever... + */ + if (length && !urb->status) { + stv680->nullpackets = 0; + switch (stv680->scratch[stv680->scratch_next].state) { + case BUFFER_READY: + case BUFFER_BUSY: + stv680->dropped++; + break; + + case BUFFER_UNUSED: + memcpy (stv680->scratch[stv680->scratch_next].data, + (unsigned char *) urb->transfer_buffer, length); + stv680->scratch[stv680->scratch_next].state = BUFFER_READY; + stv680->scratch[stv680->scratch_next].length = length; + if (waitqueue_active (&stv680->wq)) { + wake_up_interruptible (&stv680->wq); + } + stv680->scratch_overflow = 0; + stv680->scratch_next++; + if (stv680->scratch_next >= STV680_NUMSCRATCH) + stv680->scratch_next = 0;; + break; + } /* switch */ + } else { + stv680->nullpackets++; + if (stv680->nullpackets > STV680_MAX_NULLPACKETS) { + if (waitqueue_active (&stv680->wq)) { + wake_up_interruptible (&stv680->wq); + } + } + } /* if - else */ + + /* Resubmit urb for new data */ + urb->status = 0; + urb->dev = stv680->udev; + if (usb_submit_urb (urb)) + PDEBUG (0, "STV(e): urb burned down in video irq"); + return; +} /* _video_irq */ + +static int stv680_start_stream (struct usb_stv *stv680) +{ + urb_t *urb; + int err = 0, i; + + stv680->streaming = 1; + + /* Do some memory allocation */ + for (i = 0; i < STV680_NUMFRAMES; i++) { + stv680->frame[i].data = stv680->fbuf + i * stv680->maxframesize; + stv680->frame[i].curpix = 0; + } + /* packet size = 4096 */ + for (i = 0; i < STV680_NUMSBUF; i++) { + stv680->sbuf[i].data = kmalloc (stv680->rawbufsize, GFP_KERNEL); + if (stv680->sbuf[i].data == NULL) { + PDEBUG (0, "STV(e): Could not kmalloc raw data buffer %i", i); + return -1; + } + } + + stv680->scratch_next = 0; + stv680->scratch_use = 0; + stv680->scratch_overflow = 0; + for (i = 0; i < STV680_NUMSCRATCH; i++) { + stv680->scratch[i].data = kmalloc (stv680->rawbufsize, GFP_KERNEL); + if (stv680->scratch[i].data == NULL) { + PDEBUG (0, "STV(e): Could not kmalloc raw scratch buffer %i", i); + return -1; + } + stv680->scratch[i].state = BUFFER_UNUSED; + } + + for (i = 0; i < STV680_NUMSBUF; i++) { + urb = usb_alloc_urb (0); + if (!urb) + return ENOMEM; + + /* sbuf is urb->transfer_buffer, later gets memcpyed to scratch */ + usb_fill_bulk_urb (urb, stv680->udev, + usb_rcvbulkpipe (stv680->udev, stv680->bulk_in_endpointAddr), + stv680->sbuf[i].data, stv680->rawbufsize, + stv680_video_irq, stv680); + urb->timeout = PENCAM_TIMEOUT * 2; + urb->transfer_flags |= USB_QUEUE_BULK; + stv680->urb[i] = urb; + err = usb_submit_urb (stv680->urb[i]); + if (err) + PDEBUG (0, "STV(e): urb burned down in start stream"); + } /* i STV680_NUMSBUF */ + + stv680->framecount = 0; + return 0; +} + +static int stv680_stop_stream (struct usb_stv *stv680) +{ + int i; + + if (!stv680->streaming || !stv680->udev) + return 1; + + stv680->streaming = 0; + + for (i = 0; i < STV680_NUMSBUF; i++) + if (stv680->urb[i]) { + stv680->urb[i]->next = NULL; + usb_unlink_urb (stv680->urb[i]); + usb_free_urb (stv680->urb[i]); + stv680->urb[i] = NULL; + kfree (stv680->sbuf[i].data); + } + for (i = 0; i < STV680_NUMSCRATCH; i++) { + kfree (stv680->scratch[i].data); + stv680->scratch[i].data = NULL; + } + + return 0; +} + +static int stv680_set_size (struct usb_stv *stv680, int width, int height) +{ + int wasstreaming = stv680->streaming; + + /* Check to see if we need to change */ + if ((stv680->vwidth == width) && (stv680->vheight == height)) + return 0; + + PDEBUG (1, "STV(i): size request for %i x %i", width, height); + /* Check for a valid mode */ + if ((!width || !height) || ((width & 1) || (height & 1))) { + PDEBUG (1, "STV(e): set_size error: request: v.width = %i, v.height = %i actual: stv.width = %i, stv.height = %i", width, height, stv680->vwidth, stv680->vheight); + return 1; + } + + if ((width < (stv680->maxwidth / 2)) || (height < (stv680->maxheight / 2))) { + width = stv680->maxwidth / 2; + height = stv680->maxheight / 2; + } else if ((width >= 158) && (width <= 166)) { + width = 160; + height = 120; + } else if ((width >= 172) && (width <= 180)) { + width = 176; + height = 144; + } else if ((width >= 318) && (width <= 350)) { + width = 320; + height = 240; + } else if ((width >= 350) && (width <= 358)) { + width = 352; + height = 288; + } + + /* Stop a current stream and start it again at the new size */ + if (wasstreaming) + stv680_stop_stream (stv680); + stv680->vwidth = width; + stv680->vheight = height; + PDEBUG (1, "STV(i): size set to %i x %i", stv680->vwidth, stv680->vheight); + if (wasstreaming) + stv680_start_stream (stv680); + + return 0; +} + +/********************************************************************** + * Video Decoding + **********************************************************************/ + +/******* routines from the pencam program; hey, they work! ********/ + +/* + * STV0680 Vision Camera Chipset Driver + * Copyright (C) 2000 Adam Harrison +*/ + +#define RED 0 +#define GREEN 1 +#define BLUE 2 +#define AD(x, y, w) (((y)*(w)+(x))*3) + +static void bayer_unshuffle (struct usb_stv *stv680, struct stv680_scratch *buffer) +{ + int x, y, i; + int w = stv680->cwidth; + int vw = stv680->cwidth, vh = stv680->cheight, vstep = 1; + unsigned int p = 0; + int colour = 0, bayer = 0; + unsigned char *raw = buffer->data; + struct stv680_frame *frame = &stv680->frame[stv680->curframe]; + unsigned char *output = frame->data; + unsigned char *temp = frame->data; + int offset = buffer->offset; + + if (frame->curpix == 0) { + if (frame->grabstate == FRAME_READY) { + frame->grabstate = FRAME_GRABBING; + } + } + if (offset != frame->curpix) { /* Regard frame as lost :( */ + frame->curpix = 0; + stv680->error++; + return; + } + + if ((stv680->vwidth == 322) || (stv680->vwidth == 320)) { + vw = 320; + vh = 240; + vstep = 1; + } + if ((stv680->vwidth == 352)) { + vw = 352; + vh = 288; + vstep = 1; + } + if ((stv680->vwidth == 160)) { + vw = 160; + vh = 120; + vstep = 2; + } + if ((stv680->vwidth == 176)) { + vw = 176; + vh = 144; + vstep = 2; + } + memset (output, 0, 3 * vw * vh); /* clear output matrix. Maybe not necessary. */ + + for (y = 0; y < vh; y++) { + for (x = 0; x < vw; x++) { + + switch (vstep) { + case 1: + if (x & 1) + p = *(raw + y * w + (x >> 1)); + else + p = *(raw + y * w + (x >> 1) + (w >> 1)); + break; + + case 2: + if (x & 1) + p = *(raw + ((y * w) << 1) + x); + else + p = *(raw + ((y * w) << 1) + x + (w >> 1)); + break; + } + + if (y & 1) + bayer = 2; + else + bayer = 0; + if (x & 1) + bayer++; + + switch (bayer) { + case 0: + case 3: + colour = 1; + break; + case 1: + colour = 0; + break; + case 2: + colour = 2; + break; + } + i = (y * vw + x) * 3; /* output already zeroed out with memset */ + *(output + i + colour) = (unsigned char) p; + } /* for x */ + } /* for y */ + + /****** gamma correction plus hardcoded white balance */ + /* Thanks to Alexander Schwartx for this code. + Correction values red[], green[], blue[], are generated by + (pow(i/256.0, GAMMA)*255.0)*white balanceRGB where GAMMA=0.55, 1> 1; + *(output + AD (x, y, vw) + RED) = ((int) *(output + AD (x, y - 1, vw) + RED) + (int) *(output + AD (x, y + 1, vw) + RED)) >> 1; + break; + + case 1: /* blue. green lrtb, red diagonals */ + *(output + AD (x, y, vw) + GREEN) = ((int) *(output + AD (x - 1, y, vw) + GREEN) + (int) *(output + AD (x + 1, y, vw) + GREEN) + (int) *(output + AD (x, y - 1, vw) + GREEN) + (int) *(output + AD (x, y + 1, vw) + GREEN)) >> 2; + *(output + AD (x, y, vw) + RED) = ((int) *(output + AD (x - 1, y - 1, vw) + RED) + (int) *(output + AD (x - 1, y + 1, vw) + RED) + (int) *(output + AD (x + 1, y - 1, vw) + RED) + (int) *(output + AD (x + 1, y + 1, vw) + RED)) >> 2; + break; + + case 2: /* red. green lrtb, blue diagonals */ + *(output + AD (x, y, vw) + GREEN) = ((int) *(output + AD (x - 1, y, vw) + GREEN) + (int) *(output + AD (x + 1, y, vw) + GREEN) + (int) *(output + AD (x, y - 1, vw) + GREEN) + (int) *(output + AD (x, y + 1, vw) + GREEN)) >> 2; + *(output + AD (x, y, vw) + BLUE) = ((int) *(output + AD (x - 1, y - 1, vw) + BLUE) + (int) *(output + AD (x + 1, y - 1, vw) + BLUE) + (int) *(output + AD (x - 1, y + 1, vw) + BLUE) + (int) *(output + AD (x + 1, y + 1, vw) + BLUE)) >> 2; + break; + + case 3: /* green. red lr, blue tb */ + *(output + AD (x, y, vw) + RED) = ((int) *(output + AD (x - 1, y, vw) + RED) + (int) *(output + AD (x + 1, y, vw) + RED)) >> 1; + *(output + AD (x, y, vw) + BLUE) = ((int) *(output + AD (x, y - 1, vw) + BLUE) + (int) *(output + AD (x, y + 1, vw) + BLUE)) >> 1; + break; + } /* switch */ + } /* for x */ + } /* for y - end demosaic */ + + /* output is RGB; some programs want BGR */ + if (swapRGB == 1) { + for (y = 0; y < vh; y++) { + for (x = 0; x < vw; x++) { + i = (y * vw + x) * 3; + *(temp) = *(output + i); + *(output + i) = *(output + i + 2); + *(output + i + 2) = *(temp); + } + } + } + /* brightness */ + if (stv680->chgbright == 1) { + if (stv680->brightness >= 32767) { + p = (stv680->brightness - 32767) / 256; + for (x = 0; x < (vw * vh * 3); x++) { + if ((*(output + x) + (unsigned char) p) > 255) + *(output + x) = 255; + else + *(output + x) += (unsigned char) p; + } /* for */ + } else { + p = (32767 - stv680->brightness) / 256; + for (x = 0; x < (vw * vh * 3); x++) { + if ((unsigned char) p > *(output + x)) + *(output + x) = 0; + else + *(output + x) -= (unsigned char) p; + } /* for */ + } /* else */ + } + /* if */ + frame->curpix = 0; + frame->curlinepix = 0; + frame->grabstate = FRAME_DONE; + stv680->framecount++; + stv680->readcount++; + if (stv680->frame[(stv680->curframe + 1) & (STV680_NUMFRAMES - 1)].grabstate == FRAME_READY) { + stv680->curframe = (stv680->curframe + 1) & (STV680_NUMFRAMES - 1); + } + +} /* bayer_unshuffle */ + +/******* end routines from the pencam program *********/ + +static int stv680_newframe (struct usb_stv *stv680, int framenr) +{ + int errors = 0; + + while (stv680->streaming && (stv680->frame[framenr].grabstate == FRAME_READY || stv680->frame[framenr].grabstate == FRAME_GRABBING)) { + if (!stv680->frame[framenr].curpix) { + errors++; + } + wait_event_interruptible (stv680->wq, (stv680->scratch[stv680->scratch_use].state == BUFFER_READY)); + + if (stv680->nullpackets > STV680_MAX_NULLPACKETS) { + stv680->nullpackets = 0; + PDEBUG (2, "STV(i): too many null length packets, restarting capture"); + stv680_stop_stream (stv680); + stv680_start_stream (stv680); + } else { + if (stv680->scratch[stv680->scratch_use].state != BUFFER_READY) { + stv680->frame[framenr].grabstate = FRAME_ERROR; + PDEBUG (2, "STV(e): FRAME_ERROR in _newframe"); + return -EIO; + } + stv680->scratch[stv680->scratch_use].state = BUFFER_BUSY; + + bayer_unshuffle (stv680, &stv680->scratch[stv680->scratch_use]); + + stv680->scratch[stv680->scratch_use].state = BUFFER_UNUSED; + stv680->scratch_use++; + if (stv680->scratch_use >= STV680_NUMSCRATCH) + stv680->scratch_use = 0; + if (errors > STV680_MAX_ERRORS) { + errors = 0; + PDEBUG (2, "STV(i): too many errors, restarting capture"); + stv680_stop_stream (stv680); + stv680_start_stream (stv680); + } + } /* else */ + } /* while */ + return 0; +} + +/********************************************************************* + * Video4Linux + *********************************************************************/ + +static int stv_open (struct video_device *dev, int flags) +{ + struct usb_stv *stv680 = (struct usb_stv *) dev; + int err = 0; + + /* we are called with the BKL held */ + MOD_INC_USE_COUNT; + stv680->user = 1; + err = stv_init (stv680); /* main initialization routine for camera */ + + if (err >= 0) { + stv680->fbuf = rvmalloc (stv680->maxframesize * STV680_NUMFRAMES); + if (!stv680->fbuf) { + PDEBUG (0, "STV(e): Could not rvmalloc frame bufer"); + err = -ENOMEM; + } + } + if (err) { + MOD_DEC_USE_COUNT; + stv680->user = 0; + } + + return err; +} + +static void stv_close (struct video_device *dev) +{ + /* called with BKL held */ + struct usb_stv *stv680 = (struct usb_stv *) dev; + int i; + + for (i = 0; i < STV680_NUMFRAMES; i++) + stv680->frame[i].grabstate = FRAME_UNUSED; + if (stv680->streaming) + stv680_stop_stream (stv680); + + if ((i = stv_stop_video (stv680)) < 0) + PDEBUG (1, "STV(e): stop_video failed in stv_close"); + + rvfree (stv680->fbuf, stv680->maxframesize * STV680_NUMFRAMES); + stv680->user = 0; + + if (stv680->removed) { + video_unregister_device (&stv680->vdev); + kfree (stv680); + stv680 = NULL; + PDEBUG (0, "STV(i): device unregistered"); + } + MOD_DEC_USE_COUNT; +} + +static long stv680_write (struct video_device *dev, const char *buf, unsigned long count, int noblock) +{ + return -EINVAL; +} + +static int stv680_ioctl (struct video_device *vdev, unsigned int cmd, void *arg) +{ + struct usb_stv *stv680 = (struct usb_stv *) vdev; + + if (!stv680->udev) + return -EIO; + + switch (cmd) { + case VIDIOCGCAP:{ + struct video_capability b; + + strcpy (b.name, stv680->camera_name); + b.type = VID_TYPE_CAPTURE; + b.channels = 1; + b.audios = 0; + b.maxwidth = stv680->maxwidth; + b.maxheight = stv680->maxheight; + b.minwidth = stv680->maxwidth / 2; + b.minheight = stv680->maxheight / 2; + + if (copy_to_user (arg, &b, sizeof (b))) { + PDEBUG (2, "STV(e): VIDIOCGGAP failed"); + return -EFAULT; + } + return 0; + } + case VIDIOCGCHAN:{ + struct video_channel v; + + if (copy_from_user (&v, arg, sizeof (v))) + return -EFAULT; + if (v.channel != 0) + return -EINVAL; + + v.flags = 0; + v.tuners = 0; + v.type = VIDEO_TYPE_CAMERA; + strcpy (v.name, "STV Camera"); + + if (copy_to_user (arg, &v, sizeof (v))) { + PDEBUG (2, "STV(e): VIDIOCGCHAN failed"); + return -EFAULT; + } + return 0; + } + case VIDIOCSCHAN:{ + int v; + + if (copy_from_user (&v, arg, sizeof (v))) { + PDEBUG (2, "STV(e): VIDIOCSCHAN failed"); + return -EFAULT; + } + if (v != 0) + return -EINVAL; + + return 0; + } + case VIDIOCGPICT:{ + struct video_picture p; + + stv680_get_pict (stv680, &p); + if (copy_to_user (arg, &p, sizeof (p))) { + PDEBUG (2, "STV(e): VIDIOCGPICT failed"); + return -EFAULT; + } + return 0; + } + case VIDIOCSPICT:{ + struct video_picture p; + + if (copy_from_user (&p, arg, sizeof (p))) { + PDEBUG (2, "STV(e): VIDIOCSPICT failed"); + return -EFAULT; + } + copy_from_user (&p, arg, sizeof (p)); + PDEBUG (2, "STV(i): palette set to RGB in VIDIOSPICT"); + + if (stv680_set_pict (stv680, &p)) + return -EINVAL; + return 0; + } + case VIDIOCSWIN:{ + struct video_window vw; + + if (copy_from_user (&vw, arg, sizeof (vw))) + return -EFAULT; + if (vw.flags) + return -EINVAL; + if (vw.clipcount) + return -EINVAL; + if (vw.width != stv680->vwidth) { + if (stv680_set_size (stv680, vw.width, vw.height)) { + PDEBUG (2, "STV(e): failed (from user) set size in VIDIOCSWIN"); + return -EINVAL; + } + } + return 0; + } + case VIDIOCGWIN:{ + struct video_window vw; + + vw.x = 0; /* FIXME */ + vw.y = 0; + vw.chromakey = 0; + vw.flags = 0; + vw.clipcount = 0; + vw.width = stv680->vwidth; + vw.height = stv680->vheight; + + if (copy_to_user (arg, &vw, sizeof (vw))) { + PDEBUG (2, "STV(e): VIDIOCGWIN failed"); + return -EFAULT; + } + return 0; + } + case VIDIOCGMBUF:{ + struct video_mbuf vm; + int i; + + memset (&vm, 0, sizeof (vm)); + vm.size = STV680_NUMFRAMES * stv680->maxframesize; + vm.frames = STV680_NUMFRAMES; + for (i = 0; i < STV680_NUMFRAMES; i++) + vm.offsets[i] = stv680->maxframesize * i; + + if (copy_to_user ((void *) arg, (void *) &vm, sizeof (vm))) { + PDEBUG (2, "STV(e): VIDIOCGMBUF failed"); + return -EFAULT; + } + + return 0; + } + case VIDIOCMCAPTURE:{ + struct video_mmap vm; + + if (copy_from_user (&vm, arg, sizeof (vm))) { + PDEBUG (2, "STV(e): VIDIOCMCAPTURE failed"); + return -EFAULT; + } + if (vm.format != STV_VIDEO_PALETTE) { + PDEBUG (2, "STV(i): VIDIOCMCAPTURE vm.format (%i) != VIDEO_PALETTE (%i)", + vm.format, STV_VIDEO_PALETTE); + if (vm.format == 3) { + PDEBUG (2, "STV(i): VIDIOCMCAPTURE swapRGB is ON"); + /* this may fix those apps (e.g., xawtv) that want BGR */ + swapRGB = 1; + } + return -EINVAL; + } + if (vm.frame >= STV680_NUMFRAMES) { + PDEBUG (2, "STV(e): VIDIOCMCAPTURE vm.frame > NUMFRAMES"); + return -EINVAL; + } + if (stv680->frame[vm.frame].grabstate != FRAME_UNUSED) { + PDEBUG (2, "STV(e): VIDIOCMCAPTURE grabstate != FRAME_UNUSED"); + return -EBUSY; + } + /* Is this according to the v4l spec??? */ + if (stv680->vwidth != vm.width) { + if (stv680_set_size (stv680, vm.width, vm.height)) { + PDEBUG (2, "STV(e): VIDIOCMCAPTURE set_size failed"); + return -EINVAL; + } + } + stv680->frame[vm.frame].grabstate = FRAME_READY; + + if (!stv680->streaming) + stv680_start_stream (stv680); + + return 0; + } + case VIDIOCSYNC:{ + int frame, ret = 0; + + if (copy_from_user ((void *) &frame, arg, sizeof (int))) { + PDEBUG (2, "STV(e): VIDIOCSYNC failed"); + return -EFAULT; + } + if (frame < 0 || frame >= STV680_NUMFRAMES) { + PDEBUG (2, "STV(e): Bad frame # in VIDIOCSYNC"); + return -EINVAL; + } + ret = stv680_newframe (stv680, frame); + stv680->frame[frame].grabstate = FRAME_UNUSED; + return ret; + } + case VIDIOCGFBUF:{ + struct video_buffer vb; + + memset (&vb, 0, sizeof (vb)); + vb.base = NULL; /* frame buffer not supported, not used */ + + if (copy_to_user ((void *) arg, (void *) &vb, sizeof (vb))) { + PDEBUG (2, "STV(e): VIDIOCSYNC failed"); + return -EFAULT; + } + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + { + PDEBUG (2, "STV(e): VIDIOCCAPTURE failed"); + return -EINVAL; + } + case VIDIOCSFBUF: + return -EINVAL; + case VIDIOCGTUNER: + case VIDIOCSTUNER: + return -EINVAL; + case VIDIOCGFREQ: + case VIDIOCSFREQ: + return -EINVAL; + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } /* end switch */ + + return 0; +} + +static int stv680_mmap (struct video_device *dev, const char *adr, unsigned long size) +{ + struct usb_stv *stv680 = (struct usb_stv *) dev; + unsigned long start = (unsigned long) adr; + unsigned long page, pos; + + down (&stv680->lock); + + if (stv680->udev == NULL) { + up (&stv680->lock); + return -EIO; + } + if (size > (((STV680_NUMFRAMES * stv680->maxframesize) + PAGE_SIZE - 1) + & ~(PAGE_SIZE - 1))) { + up (&stv680->lock); + return -EINVAL; + } + pos = (unsigned long) stv680->fbuf; + while (size > 0) { + page = kvirt_to_pa (pos); + if (remap_page_range (start, page, PAGE_SIZE, PAGE_SHARED)) { + up (&stv680->lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + up (&stv680->lock); + + return 0; +} + +static long stv680_read (struct video_device *dev, char *buf, unsigned long count, int noblock) +{ + unsigned long int realcount = count; + int ret = 0; + struct usb_stv *stv680 = (struct usb_stv *) dev; + unsigned long int i; + + if (STV680_NUMFRAMES != 2) { + PDEBUG (0, "STV(e): STV680_NUMFRAMES needs to be 2!"); + return -1; + } + if (stv680->udev == NULL) + return -EIO; + if (realcount > (stv680->vwidth * stv680->vheight * 3)) + realcount = stv680->vwidth * stv680->vheight * 3; + + /* Shouldn't happen: */ + if (stv680->frame[0].grabstate == FRAME_GRABBING) { + PDEBUG (2, "STV(e): FRAME_GRABBING in stv680_read"); + return -EBUSY; + } + stv680->frame[0].grabstate = FRAME_READY; + stv680->frame[1].grabstate = FRAME_UNUSED; + stv680->curframe = 0; + + if (!stv680->streaming) + stv680_start_stream (stv680); + + if (!stv680->streaming) { + ret = stv680_newframe (stv680, 0); /* ret should = 0 */ + } + + ret = stv680_newframe (stv680, 0); + + if (!ret) { + if ((i = copy_to_user (buf, stv680->frame[0].data, realcount)) != 0) { + PDEBUG (2, "STV(e): copy_to_user frame 0 failed, ret count = %li", i); + return -EFAULT; + } + } else { + realcount = ret; + } + stv680->frame[0].grabstate = FRAME_UNUSED; + return realcount; +} /* stv680_read */ + +static int stv_init_done (struct video_device *dev) +{ + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + if (create_proc_stv680_cam ((struct usb_stv *) dev) < 0) + return -1; +#endif + return 0; +} + +static struct video_device stv680_template = { + owner: THIS_MODULE, + name: "STV0680 USB camera", + type: VID_TYPE_CAPTURE, + hardware: VID_HARDWARE_SE401, + open: stv_open, + close: stv_close, + read: stv680_read, + write: stv680_write, + ioctl: stv680_ioctl, + mmap: stv680_mmap, + initialize: stv_init_done, +}; + +static void *__devinit stv680_probe (struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id) +{ + struct usb_interface_descriptor *interface; + struct usb_stv *stv680; + char *camera_name = NULL; + + /* We don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) { + PDEBUG (0, "STV(e): Number of Configurations != 1"); + return NULL; + } + + interface = &dev->actconfig->interface[ifnum].altsetting[0]; + /* Is it a STV680? */ + if ((dev->descriptor.idVendor == USB_PENCAM_VENDOR_ID) && (dev->descriptor.idProduct == USB_PENCAM_PRODUCT_ID)) { + camera_name = "STV0680"; + PDEBUG (0, "STV(i): STV0680 camera found."); + } else { + PDEBUG (0, "STV(e): Vendor/Product ID do not match STV0680 values."); + PDEBUG (0, "STV(e): Check that the STV0680 camera is connected to the computer."); + return NULL; + } + /* We found one */ + if ((stv680 = kmalloc (sizeof (*stv680), GFP_KERNEL)) == NULL) { + PDEBUG (0, "STV(e): couldn't kmalloc stv680 struct."); + return NULL; + } + + memset (stv680, 0, sizeof (*stv680)); + + stv680->udev = dev; + stv680->camera_name = camera_name; + + memcpy (&stv680->vdev, &stv680_template, sizeof (stv680_template)); + memcpy (stv680->vdev.name, stv680->camera_name, strlen (stv680->camera_name)); + init_waitqueue_head (&stv680->wq); + init_MUTEX (&stv680->lock); + wmb (); + + if (video_register_device (&stv680->vdev, VFL_TYPE_GRABBER, video_nr) == -1) { + kfree (stv680); + PDEBUG (0, "STV(e): video_register_device failed"); + return NULL; + } + PDEBUG (0, "STV(i): registered new video device: video%d", stv680->vdev.minor); + + return stv680; +} + +static inline void usb_stv680_remove_disconnected (struct usb_stv *stv680) +{ + int i; + + stv680->udev = NULL; + stv680->frame[0].grabstate = FRAME_ERROR; + stv680->frame[1].grabstate = FRAME_ERROR; + stv680->streaming = 0; + + wake_up_interruptible (&stv680->wq); + + for (i = 0; i < STV680_NUMSBUF; i++) + if (stv680->urb[i]) { + stv680->urb[i]->next = NULL; + usb_unlink_urb (stv680->urb[i]); + usb_free_urb (stv680->urb[i]); + stv680->urb[i] = NULL; + kfree (stv680->sbuf[i].data); + } + for (i = 0; i < STV680_NUMSCRATCH; i++) + if (stv680->scratch[i].data) { + kfree (stv680->scratch[i].data); + } + PDEBUG (0, "STV(i): %s disconnected", stv680->camera_name); + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + destroy_proc_stv680_cam (stv680); +#endif + /* Free the memory */ + kfree (stv680); +} + +static void stv680_disconnect (struct usb_device *dev, void *ptr) +{ + struct usb_stv *stv680 = (struct usb_stv *) ptr; + + lock_kernel (); + /* We don't want people trying to open up the device */ + if (!stv680->user) { + video_unregister_device (&stv680->vdev); + usb_stv680_remove_disconnected (stv680); + } else { + stv680->removed = 1; + } + unlock_kernel (); +} + +static struct usb_driver stv680_driver = { + name: "stv680", + probe: stv680_probe, + disconnect: stv680_disconnect, + id_table: device_table +}; + +/******************************************************************** + * Module routines + ********************************************************************/ + +static int __init usb_stv680_init (void) +{ +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + if (proc_stv680_create () < 0) + return -1; +#endif + if (usb_register (&stv680_driver) < 0) { + PDEBUG (0, "STV(e): Could not setup STV0680 driver"); + return -1; + } + PDEBUG (0, "STV(i): usb camera driver version %s registering", DRIVER_VERSION); + + info(DRIVER_DESC " " DRIVER_VERSION); + return 0; +} + +static void __exit usb_stv680_exit (void) +{ + usb_deregister (&stv680_driver); + PDEBUG (0, "STV(i): driver deregistered"); + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + proc_stv680_destroy (); +#endif +} + +module_init (usb_stv680_init); +module_exit (usb_stv680_exit); diff -Nru a/drivers/usb/stv680.h b/drivers/usb/stv680.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/stv680.h Wed Dec 19 10:26:03 2001 @@ -0,0 +1,222 @@ +/**************************************************************************** + * + * Filename: stv680.h + * + * Description: + * This is a USB driver for STV0680 based usb video cameras. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ****************************************************************************/ + +/* size of usb transfers */ +#define STV680_PACKETSIZE 4096 + +/* number of queued bulk transfers to use, may have problems if > 1 */ +#define STV680_NUMSBUF 1 + +/* number of frames supported by the v4l part */ +#define STV680_NUMFRAMES 2 + +/* scratch buffers for passing data to the decoders: 2 or 4 are good */ +#define STV680_NUMSCRATCH 2 + +/* number of nul sized packets to receive before kicking the camera */ +#define STV680_MAX_NULLPACKETS 200 + +/* number of decoding errors before kicking the camera */ +#define STV680_MAX_ERRORS 100 + +#define USB_PENCAM_VENDOR_ID 0x0553 +#define USB_PENCAM_PRODUCT_ID 0x0202 +#define PENCAM_TIMEOUT 1000 +/* fmt 4 */ +#define STV_VIDEO_PALETTE VIDEO_PALETTE_RGB24 + +static __devinitdata struct usb_device_id device_table[] = { + {USB_DEVICE (USB_PENCAM_VENDOR_ID, USB_PENCAM_PRODUCT_ID)}, + {} +}; +MODULE_DEVICE_TABLE (usb, device_table); + +struct stv680_sbuf { + unsigned char *data; +}; + +enum { + FRAME_UNUSED, /* Unused (no MCAPTURE) */ + FRAME_READY, /* Ready to start grabbing */ + FRAME_GRABBING, /* In the process of being grabbed into */ + FRAME_DONE, /* Finished grabbing, but not been synced yet */ + FRAME_ERROR, /* Something bad happened while processing */ +}; + +enum { + BUFFER_UNUSED, + BUFFER_READY, + BUFFER_BUSY, + BUFFER_DONE, +}; + +/* raw camera data <- sbuf (urb transfer buf) */ +struct stv680_scratch { + unsigned char *data; + volatile int state; + int offset; + int length; +}; + +/* processed data for display ends up here, after bayer */ +struct stv680_frame { + unsigned char *data; /* Frame buffer */ + volatile int grabstate; /* State of grabbing */ + unsigned char *curline; + int curlinepix; + int curpix; +}; + +/* this is almost the video structure uvd_t, with extra parameters for stv */ +struct usb_stv { + struct video_device vdev; + + struct usb_device *udev; + + unsigned char bulk_in_endpointAddr; /* __u8 the address of the bulk in endpoint */ + char *camera_name; + + unsigned int VideoMode; /* 0x0100 = VGA, 0x0000 = CIF, 0x0300 = QVGA */ + int SupportedModes; + int CIF; + int VGA; + int QVGA; + int cwidth; /* camera width */ + int cheight; /* camera height */ + int maxwidth; /* max video width */ + int maxheight; /* max video height */ + int vwidth; /* current width for video window */ + int vheight; /* current height for video window */ + unsigned long int rawbufsize; + unsigned long int maxframesize; /* rawbufsize * 3 for RGB */ + + int origGain; + int origMode; /* original camera mode */ + + struct semaphore lock; /* to lock the structure */ + int user; /* user count for exclusive use */ + int removed; /* device disconnected */ + int streaming; /* Are we streaming video? */ + char *fbuf; /* Videodev buffer area */ + urb_t *urb[STV680_NUMSBUF]; /* # of queued bulk transfers */ + int curframe; /* Current receiving frame */ + struct stv680_frame frame[STV680_NUMFRAMES]; /* # frames supported by v4l part */ + int readcount; + int framecount; + int error; + int dropped; + int scratch_next; + int scratch_use; + int scratch_overflow; + struct stv680_scratch scratch[STV680_NUMSCRATCH]; /* for decoders */ + struct stv680_sbuf sbuf[STV680_NUMSBUF]; + + unsigned int brightness; + unsigned int chgbright; + unsigned int whiteness; + unsigned int colour; + unsigned int contrast; + unsigned int hue; + unsigned int palette; + unsigned int depth; /* rgb24 in bits */ + + wait_queue_head_t wq; /* Processes waiting */ + + struct proc_dir_entry *proc_entry; /* /proc/stv680/videoX */ + int nullpackets; +}; + +unsigned char red[256] = { + 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 28, 34, 39, 43, 47, + 50, 53, 56, 59, 61, 64, 66, 68, 71, 73, 75, 77, + 79, 80, 82, 84, 86, 87, 89, 91, 92, 94, 95, 97, + 98, 100, 101, 102, 104, 105, 106, 108, 109, 110, 111, 113, + 114, 115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 144, 144, 145, 146, 147, 148, 149, + 150, 151, 151, 152, 153, 154, 155, 156, 156, 157, 158, 159, + 160, 160, 161, 162, 163, 164, 164, 165, 166, 167, 167, 168, + 169, 170, 170, 171, 172, 172, 173, 174, 175, 175, 176, 177, + 177, 178, 179, 179, 180, 181, 182, 182, 183, 184, 184, 185, + 186, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 193, + 193, 194, 194, 195, 196, 196, 197, 198, 198, 199, 199, 200, + 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206, 207, + 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, + 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, + 221, 221, 222, 222, 223, 224, 224, 225, 225, 226, 226, 227, + 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, + 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 239, + 239, 240, 240, 241, 241, 242, 242, 243, 243, 243, 244, 244, + 245, 245, 246, 246 +}; + +unsigned char green[256] = { + 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 32, 39, 45, 50, 54, + 58, 62, 65, 69, 71, 74, 77, 79, 83, 85, 87, 90, + 92, 93, 95, 98, 100, 101, 104, 106, 107, 109, 111, 113, + 114, 116, 118, 119, 121, 122, 124, 126, 127, 128, 129, 132, + 133, 134, 135, 136, 138, 140, 141, 142, 143, 145, 146, 147, + 148, 149, 150, 152, 153, 154, 155, 156, 157, 159, 160, 161, + 162, 163, 164, 166, 167, 168, 168, 169, 170, 171, 173, 174, + 175, 176, 176, 177, 179, 180, 181, 182, 182, 183, 184, 186, + 187, 187, 188, 189, 190, 191, 191, 193, 194, 195, 195, 196, + 197, 198, 198, 200, 201, 201, 202, 203, 204, 204, 205, 207, + 207, 208, 209, 209, 210, 211, 212, 212, 214, 215, 215, 216, + 217, 217, 218, 218, 219, 221, 221, 222, 223, 223, 224, 225, + 225, 226, 226, 228, 229, 229, 230, 231, 231, 232, 232, 233, + 235, 235, 236, 236, 237, 238, 238, 239, 239, 241, 241, 242, + 243, 243, 244, 244, 245, 245, 246, 248, 248, 249, 249, 250, + 250, 251, 251, 252, 253, 253, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255 +}; + +unsigned char blue[256] = { + 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 41, 50, 57, 63, 69, + 74, 78, 82, 87, 90, 94, 97, 100, 105, 108, 111, 113, + 116, 118, 121, 124, 127, 128, 131, 134, 136, 139, 140, 143, + 145, 148, 149, 150, 153, 155, 156, 159, 161, 162, 164, 167, + 168, 170, 171, 173, 174, 177, 179, 180, 182, 183, 185, 186, + 187, 189, 190, 192, 193, 195, 196, 198, 199, 201, 202, 204, + 205, 207, 208, 210, 211, 213, 213, 214, 216, 217, 219, 220, + 222, 223, 223, 224, 226, 227, 229, 230, 230, 232, 233, 235, + 236, 236, 238, 239, 241, 242, 242, 244, 245, 247, 247, 248, + 250, 251, 251, 253, 254, 254, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255 +}; diff -Nru a/drivers/usb/usb.c b/drivers/usb/usb.c --- a/drivers/usb/usb.c Wed Dec 19 10:26:03 2001 +++ b/drivers/usb/usb.c Wed Dec 19 10:26:03 2001 @@ -97,6 +97,8 @@ usb_scan_devices(); + usbfs_update_special(); + return 0; } @@ -192,6 +194,8 @@ usb_drivers_purge(driver, bus->root_hub); } up (&usb_bus_list_lock); + + usbfs_update_special(); } /** @@ -421,7 +425,6 @@ bus->bandwidth_isoc_reqs = 0; INIT_LIST_HEAD(&bus->bus_list); - INIT_LIST_HEAD(&bus->inodes); atomic_set(&bus->refcnt, 1); @@ -468,7 +471,7 @@ list_add(&bus->bus_list, &usb_bus_list); up (&usb_bus_list_lock); - usbdevfs_add_bus(bus); + usbfs_add_bus(bus); info("new USB bus registered, assigned bus number %d", bus->busnum); } @@ -494,7 +497,7 @@ list_del(&bus->bus_list); up (&usb_bus_list_lock); - usbdevfs_remove_bus(bus); + usbfs_remove_bus(bus); clear_bit(bus->busnum, busmap.busmap); @@ -923,7 +926,7 @@ /* a simple/common case: one config, one interface, one driver * with current altsetting being a reasonable setting. - * everything needs a smart agent and usbdevfs; or can rely on + * everything needs a smart agent and usbfs; or can rely on * device-specific binding policies. */ envp [i++] = scratch; @@ -1013,10 +1016,11 @@ usb_bus_get(bus); + if (!parent) + dev->devpath [0] = '/'; dev->bus = bus; dev->parent = parent; atomic_set(&dev->refcnt, 1); - INIT_LIST_HEAD(&dev->inodes); INIT_LIST_HEAD(&dev->filelist); init_MUTEX(&dev->serialize); @@ -1906,7 +1910,7 @@ /* Free the device number and remove the /proc/bus/usb entry */ if (dev->devnum > 0) { clear_bit(dev->devnum, &dev->bus->devmap.devicemap); - usbdevfs_remove_device(dev); + usbfs_remove_device(dev); } /* Free up the device itself */ @@ -2579,7 +2583,7 @@ #endif /* now that the basic setup is over, add a /proc/bus/usb entry */ - usbdevfs_add_device(dev); + usbfs_add_device(dev); /* find drivers willing to handle this device */ usb_find_drivers(dev); @@ -2660,7 +2664,7 @@ { init_MUTEX(&usb_bus_list_lock); usb_major_init(); - usbdevfs_init(); + usbfs_init(); usb_hub_init(); return 0; @@ -2672,7 +2676,7 @@ static void __exit usb_exit(void) { usb_major_cleanup(); - usbdevfs_cleanup(); + usbfs_cleanup(); usb_hub_cleanup(); } diff -Nru a/drivers/usb/usbnet.c b/drivers/usb/usbnet.c --- a/drivers/usb/usbnet.c Wed Dec 19 10:26:02 2001 +++ b/drivers/usb/usbnet.c Wed Dec 19 10:26:02 2001 @@ -1577,7 +1577,7 @@ struct nc_trailer *trailer = 0; #endif /* CONFIG_USB_NET1080 */ - flags = in_interrupt () ? GFP_ATOMIC : GFP_KERNEL; + flags = in_interrupt () ? GFP_ATOMIC : GFP_NOIO; /* might be used for nfs */ // some devices want funky USB-level framing, for // win32 driver (usually) and/or hardware quirks diff -Nru a/drivers/usb/vicam.c b/drivers/usb/vicam.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/vicam.c Wed Dec 19 10:26:03 2001 @@ -0,0 +1,986 @@ +/* -*- linux-c -*- + * USB ViCAM driver + * + * Copyright (c) 2001 Christopher L Cheney (ccheney@cheney.cx) + * Copyright (c) 2001 Pavel Machek (pavel@suse.cz) sponsored by SuSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This driver is for the Vista Imaging ViCAM and 3Com HomeConnect USB + * + * Thanks to Greg Kroah-Hartman for the USB Skeleton driver + * + * TODO: + * - find out the ids for the Vista Imaging ViCAM + * + * History: + * + * 2001_07_07 - 0.1 - christopher: first version + * 2001_08_28 - 0.2 - pavel: messed it up, but for some fun, try + while true; do dd if=/dev/video of=/dev/fb0 bs=$[0x1e480] count=1 2> /dev/null; done + yep, moving pictures. + * 2001_08_29 - 0.3 - pavel: played a little bit more. Experimental mmap support. For some fun, + get gqcam-0.9, compile it and run. Better than dd ;-). + * 2001_08_29 - 0.4 - pavel: added shutter speed control (not much functional) + kill update_params if it does not seem to work for you. + * 2001_08_30 - 0.5 - pavel: fixed stupid bug with update_params & vicam_bulk + + * + * FIXME: It crashes on rmmod with camera plugged. + */ +#define DEBUG 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "vicam.h" +#include "vicamurbs.h" + +/* Version Information */ +#define DRIVER_VERSION "v0" +#define DRIVER_AUTHOR "Christopher L Cheney , Pavel Machek " +#define DRIVER_DESC "USB ViCAM Driver" + +/* Define these values to match your device */ +#define USB_VICAM_VENDOR_ID 0x04C1 +#define USB_VICAM_PRODUCT_ID 0x009D + +/* table of devices that work with this driver */ +static struct usb_device_id vicam_table [] = { + { USB_DEVICE(USB_VICAM_VENDOR_ID, USB_VICAM_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, vicam_table); + +static int video_nr = -1; /* next avail video device */ +static struct usb_driver vicam_driver; + +static char *buf, *buf2; +static int change_pending = 0; + +static int vicam_parameters(struct usb_vicam *vicam); + +/****************************************************************************** + * + * Memory management functions + * + * Taken from bttv-drivers.c 2.4.7-pre3 + * + ******************************************************************************/ + +/* [DaveM] I've recoded most of this so that: + * 1) It's easier to tell what is happening + * 2) It's more portable, especially for translating things + * out of vmalloc mapped areas in the kernel. + * 3) Less unnecessary translations happen. + * + * The code used to assume that the kernel vmalloc mappings + * existed in the page tables of every process, this is simply + * not guarenteed. We now use pgd_offset_k which is the + * defined way to get at the kernel page tables. + */ + +/* Given PGD from the address space's page table, return the kernel + * virtual mapping of the physical memory mapped at ADR. + */ +static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr) +{ + unsigned long ret = 0UL; + pmd_t *pmd; + pte_t *ptep, pte; + + if (!pgd_none(*pgd)) { + pmd = pmd_offset(pgd, adr); + if (!pmd_none(*pmd)) { + ptep = pte_offset(pmd, adr); + pte = *ptep; + if(pte_present(pte)) { + ret = (unsigned long) page_address(pte_page(pte)); + ret |= (adr & (PAGE_SIZE - 1)); + + } + } + } + return ret; +} + +static inline unsigned long uvirt_to_bus(unsigned long adr) +{ + unsigned long kva, ret; + + kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr); + ret = virt_to_bus((void *)kva); + return ret; +} + +static inline unsigned long kvirt_to_bus(unsigned long adr) +{ + unsigned long va, kva, ret; + + va = VMALLOC_VMADDR(adr); + kva = uvirt_to_kva(pgd_offset_k(va), va); + ret = virt_to_bus((void *)kva); + return ret; +} + +/* Here we want the physical address of the memory. + * This is used when initializing the contents of the + * area and marking the pages as reserved. + */ +static inline unsigned long kvirt_to_pa(unsigned long adr) +{ + unsigned long va, kva, ret; + + va = VMALLOC_VMADDR(adr); + kva = uvirt_to_kva(pgd_offset_k(va), va); + ret = __pa(kva); + return ret; +} + +static void * rvmalloc(signed long size) +{ + void * mem; + unsigned long adr, page; + + mem=vmalloc_32(size); + if (mem) + { + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr=(unsigned long) mem; + while (size > 0) + { + page = kvirt_to_pa(adr); + mem_map_reserve(virt_to_page(__va(page))); + adr+=PAGE_SIZE; + size-=PAGE_SIZE; + } + } + return mem; +} + +static void rvfree(void * mem, signed long size) +{ + unsigned long adr, page; + + if (mem) + { + adr=(unsigned long) mem; + while (size > 0) + { + page = kvirt_to_pa(adr); + mem_map_unreserve(virt_to_page(__va(page))); + adr+=PAGE_SIZE; + size-=PAGE_SIZE; + } + vfree(mem); + } +} + +/****************************************************************************** + * + * Foo Bar + * + ******************************************************************************/ + +/** + * usb_vicam_debug_data + */ +static inline void usb_vicam_debug_data (const char *function, int size, const unsigned char *data) +{ + int i; + + if (!debug) + return; + + printk (KERN_DEBUG __FILE__": %s - length = %d, data = ", + function, size); + for (i = 0; i < size; ++i) { + printk ("%.2x ", data[i]); + } + printk ("\n"); +} + +/***************************************************************************** + * + * Send command to vicam + * + *****************************************************************************/ + +static int vicam_sndctrl(int set, struct usb_vicam *vicam, unsigned short req, + unsigned short value, unsigned char *cp, int size) +{ + int ret; + unsigned char *transfer_buffer = kmalloc (size, GFP_KERNEL); + + /* Needs to return data I think, works for sending though */ + memcpy(transfer_buffer, cp, size); + + ret = usb_control_msg ( vicam->udev, set ? usb_sndctrlpipe(vicam->udev, 0) : usb_rcvctrlpipe(vicam->udev, 0), req, (set ? USB_DIR_OUT : USB_DIR_IN) | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value, 0, transfer_buffer, size, HZ); + + kfree(transfer_buffer); + if (ret) + printk("vicam: error: %d\n", ret); + mdelay(100); + return ret; +} + + +/***************************************************************************** + * + * Video4Linux Helpers + * + *****************************************************************************/ + +static int vicam_get_capability(struct usb_vicam *vicam, struct video_capability *b) +{ + dbg("vicam_get_capability"); + + strcpy(b->name, vicam->camera_name); + b->type = VID_TYPE_CAPTURE | VID_TYPE_MONOCHROME; + b->channels = 1; + b->audios = 0; + + b->maxwidth = vicam->width[vicam->sizes-1]; + b->maxheight = vicam->height[vicam->sizes-1]; + b->minwidth = vicam->width[0]; + b->minheight = vicam->height[0]; + + return 0; +} + +static int vicam_get_channel(struct usb_vicam *vicam, struct video_channel *v) +{ + dbg("vicam_get_channel"); + + if (v->channel != 0) + return -EINVAL; + + v->flags = 0; + v->tuners = 0; + v->type = VIDEO_TYPE_CAMERA; + strcpy(v->name, "Camera"); + + return 0; +} + +static int vicam_set_channel(struct usb_vicam *vicam, struct video_channel *v) +{ + dbg("vicam_set_channel"); + + if (v->channel != 0) + return -EINVAL; + + return 0; +} + +static int vicam_get_mmapbuffer(struct usb_vicam *vicam, struct video_mbuf *vm) +{ + int i; + + dbg("vicam_get_mmapbuffer"); + + memset(vm, 0, sizeof(vm)); + vm->size = VICAM_NUMFRAMES * vicam->maxframesize; + vm->frames = VICAM_NUMFRAMES; + + for (i=0; ioffsets[i] = vicam->maxframesize * i; + + return 0; +} + +static int vicam_get_picture(struct usb_vicam *vicam, struct video_picture *p) +{ + dbg("vicam_get_picture"); + + /* This is probably where that weird 0x56 call goes */ + p->brightness = vicam->win.brightness; + p->hue = vicam->win.hue; + p->colour = vicam->win.colour; + p->contrast = vicam->win.contrast; + p->whiteness = vicam->win.whiteness; + p->depth = vicam->win.depth; + p->palette = vicam->win.palette; + + return 0; +} + +static void synchronize(struct usb_vicam *vicam) +{ + change_pending = 1; + interruptible_sleep_on(&vicam->wait); + vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x00, NULL, 0); + mdelay(10); + vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x00, NULL, 0); + mdelay(10); +} + +static void params_changed(struct usb_vicam *vicam) +{ +#if 1 + synchronize(vicam); + mdelay(10); + vicam_parameters(vicam); + printk("Submiting urb: %d\n", usb_submit_urb(&vicam->readurb)); +#endif +} + +static int vicam_set_picture(struct usb_vicam *vicam, struct video_picture *p) +{ + int changed = 0; + info("vicam_set_picture (%d)", p->brightness); + + +#define SET(x) \ + if (vicam->win.x != p->x) \ + vicam->win.x = p->x, changed = 1; + SET(brightness); + SET(hue); + SET(colour); + SET(contrast); + SET(whiteness); + SET(depth); + SET(palette); + if (changed) + params_changed(vicam); + + return 0; + /* Investigate what should be done maybe 0x56 type call */ + if (p->depth != 8) return 1; + if (p->palette != VIDEO_PALETTE_GREY) return 1; + + return 0; +} + +/* FIXME - vicam_sync_frame - important */ +static int vicam_sync_frame(struct usb_vicam *vicam, int frame) +{ + dbg("vicam_sync_frame"); + + if(frame <0 || frame >= VICAM_NUMFRAMES) + return -EINVAL; + + /* Probably need to handle various cases */ +/* ret=vicam_newframe(vicam, frame); + vicam->frame[frame].grabstate=FRAME_UNUSED; +*/ + return 0; +} + +static int vicam_get_window(struct usb_vicam *vicam, struct video_window *vw) +{ + dbg("vicam_get_window"); + + vw->x = 0; + vw->y = 0; + vw->chromakey = 0; + vw->flags = 0; + vw->clipcount = 0; + vw->width = vicam->win.width; + vw->height = vicam->win.height; + + return 0; +} + +static int vicam_set_window(struct usb_vicam *vicam, struct video_window *vw) +{ + info("vicam_set_window"); + + if (vw->flags) + return -EINVAL; + if (vw->clipcount) + return -EINVAL; + + if (vicam->win.width == vw->width && vicam->win.height == vw->height) + return 0; + + /* Pick largest mode that is smaller than specified res */ + /* If specified res is too small reject */ + + /* Add urb send to device... */ + + vicam->win.width = vw->width; + vicam->win.height = vw->height; + params_changed(vicam); + + return 0; +} + +/* FIXME - vicam_mmap_capture - important */ +static int vicam_mmap_capture(struct usb_vicam *vicam, struct video_mmap *vm) +{ + dbg("vicam_mmap_capture"); + + /* usbvideo.c looks good for using here */ + + /* + if (vm->frame >= VICAM_NUMFRAMES) + return -EINVAL; + if (vicam->frame[vm->frame].grabstate != FRAME_UNUSED) + return -EBUSY; + vicam->frame[vm->frame].grabstate=FRAME_READY; + */ + + /* No need to vicam_set_window here according to Alan */ + + /* + if (!vicam->streaming) + vicam_start_stream(vicam); + */ + + /* set frame as ready */ + + return 0; +} + +/***************************************************************************** + * + * Video4Linux + * + *****************************************************************************/ + +static int vicam_v4l_open(struct video_device *vdev, int flags) +{ + struct usb_vicam *vicam = (struct usb_vicam *)vdev; + int err = 0; + + dbg("vicam_v4l_open"); + + MOD_INC_USE_COUNT; + down(&vicam->sem); + + if (vicam->open_count) /* Maybe not needed? */ + err = -EBUSY; + else { + vicam->fbuf = rvmalloc(vicam->maxframesize * VICAM_NUMFRAMES); + if (!vicam->fbuf) + err=-ENOMEM; + else { + vicam->open_count = 1; + } +#ifdef BLINKING + vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x01, NULL, 0); + info ("led on"); + vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x01, NULL, 0); +#endif + } + + up(&vicam->sem); + if (err) + MOD_DEC_USE_COUNT; + return err; +} + +static void vicam_v4l_close(struct video_device *vdev) +{ + struct usb_vicam *vicam = (struct usb_vicam *)vdev; + + dbg("vicam_v4l_close"); + + down(&vicam->sem); + +#ifdef BLINKING + info ("led off"); + vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x00, NULL, 0); +// vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x00, NULL, 0); Leave it on +#endif + + rvfree(vicam->fbuf, vicam->maxframesize * VICAM_NUMFRAMES); + vicam->fbuf = 0; + vicam->open_count=0; + + up(&vicam->sem); + /* Why does se401.c have a usbdevice check here? */ + /* If device is unplugged while open, I guess we only may unregister now */ + MOD_DEC_USE_COUNT; +} + +static long vicam_v4l_read(struct video_device *vdev, char *user_buf, unsigned long buflen, int noblock) +{ + //struct usb_vicam *vicam = (struct usb_vicam *)vdev; + + dbg("vicam_v4l_read(%ld)", buflen); + + if (!vdev || !buf) + return -EFAULT; + + if (copy_to_user(user_buf, buf2, buflen)) + return -EFAULT; + return buflen; +} + +static long vicam_v4l_write(struct video_device *dev, const char *buf, unsigned long count, int noblock) +{ + info("vicam_v4l_write"); + return -EINVAL; +} + +static int vicam_v4l_ioctl(struct video_device *vdev, unsigned int cmd, void *arg) +{ + struct usb_vicam *vicam = (struct usb_vicam *)vdev; + int ret = -EL3RST; + + if (!vicam->udev) + return -EIO; + + down(&vicam->sem); + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability b; + ret = vicam_get_capability(vicam,&b); + dbg("name %s",b.name); + if (copy_to_user(arg, &b, sizeof(b))) + ret = -EFAULT; + } + case VIDIOCGFBUF: + { + struct video_buffer vb; + info("vicam_v4l_ioctl - VIDIOCGBUF - query frame buffer param"); + /* frame buffer not supported, not used */ + memset(&vb, 0, sizeof(vb)); + vb.base = NULL; + + /* FIXME - VIDIOCGFBUF - why the void */ + if (copy_to_user((void *)arg, (void *)&vb, sizeof(vb))) + ret = -EFAULT; + ret = 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + ret = vicam_get_window(vicam, &vw); + if (copy_to_user(arg, &vw, sizeof(vw))) + ret = -EFAULT; + } + case VIDIOCSWIN: + { + struct video_window vw; + if (copy_from_user(&vw, arg, sizeof(vw))) + ret = -EFAULT; + else + ret = vicam_set_window(vicam, &vw); + return ret; + } + case VIDIOCGCHAN: + { + struct video_channel v; + + if (copy_from_user(&v, arg, sizeof(v))) + ret = -EFAULT; + else { + ret = vicam_get_channel(vicam,&v); + if (copy_to_user(arg, &v, sizeof(v))) + ret = -EFAULT; + } + } + case VIDIOCSCHAN: + { + struct video_channel v; + if (copy_from_user(&v, arg, sizeof(v))) + ret = -EFAULT; + else + ret = vicam_set_channel(vicam,&v); + } + case VIDIOCGPICT: + { + struct video_picture p; + ret = vicam_get_picture(vicam, &p); + if (copy_to_user(arg, &p, sizeof(p))) + ret = -EFAULT; + } + case VIDIOCSPICT: + { + struct video_picture p; + if (copy_from_user(&p, arg, sizeof(p))) + ret = -EFAULT; + else + ret = vicam_set_picture(vicam, &p); + } + case VIDIOCGMBUF: + { + struct video_mbuf vm; + ret = vicam_get_mmapbuffer(vicam,&vm); + /* FIXME - VIDIOCGMBUF - why the void */ + if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm))) + ret = -EFAULT; + } + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + ret = vicam_mmap_capture(vicam, &vm); + /* FIXME: This is probably not right */ + } + case VIDIOCSYNC: + { + int frame; + /* FIXME - VIDIOCSYNC - why the void */ + if (copy_from_user((void *)&frame, arg, sizeof(int))) + ret = -EFAULT; + else + ret = vicam_sync_frame(vicam,frame); + } + + case VIDIOCKEY: + ret = 0; + + case VIDIOCCAPTURE: + case VIDIOCSFBUF: + case VIDIOCGTUNER: + case VIDIOCSTUNER: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + case VIDIOCGUNIT: + ret = -EINVAL; + + default: + { + info("vicam_v4l_ioctl - %ui",cmd); + ret = -ENOIOCTLCMD; + } + } /* end switch */ + + up(&vicam->sem); + return ret; +} + +static int vicam_v4l_mmap(struct video_device *dev, const char *adr, unsigned long size) +{ + struct usb_vicam *vicam = (struct usb_vicam *)dev; + unsigned long start = (unsigned long)adr; + unsigned long page, pos; + + down(&vicam->sem); + + if (vicam->udev == NULL) { + up(&vicam->sem); + return -EIO; + } +#if 0 + if (size > (((VICAM_NUMFRAMES * vicam->maxframesize) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))) { + up(&vicam->sem); + return -EINVAL; + } +#endif + pos = (unsigned long)vicam->fbuf; + while (size > 0) { + page = kvirt_to_pa(pos); + if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) { + up(&vicam->sem); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + up(&vicam->sem); + + return 0; +} + +/* FIXME - vicam_v4l_init */ +static int vicam_v4l_init(struct video_device *dev) +{ + /* stick proc fs stuff in here if wanted */ + dbg("vicam_v4l_init"); + return 0; +} + +/* FIXME - vicam_template - important */ +static struct video_device vicam_template = { + name: "vicam USB camera", + type: VID_TYPE_CAPTURE, + hardware: VID_HARDWARE_SE401, /* need to ask for own id */ + open: vicam_v4l_open, + close: vicam_v4l_close, + read: vicam_v4l_read, + write: vicam_v4l_write, + ioctl: vicam_v4l_ioctl, + mmap: vicam_v4l_mmap, + initialize: vicam_v4l_init, +}; + +/****************************************************************************** + * + * Some Routines + * + ******************************************************************************/ + +/* +Flash the led +vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x01, NULL, 0); +info ("led on"); +vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x01, NULL, 0); +info ("led off"); +vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x00, NULL, 0); +vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x00, NULL, 0); +*/ + +static void vicam_bulk(struct urb *urb) +{ + struct usb_vicam *vicam = urb->context; + + /* if (!vicam || !vicam->dev || !vicam->used) + return; + */ + + if (urb->status) + printk("vicam%d: nonzero read/write bulk status received: %d", + 0, urb->status); + + urb->actual_length = 0; + urb->dev = vicam->udev; + + memcpy(buf2, buf+64, 0x1e480); + if (vicam->fbuf) + memcpy(vicam->fbuf, buf+64, 0x1e480); + + if (!change_pending) { + if (usb_submit_urb(urb)) + dbg("failed resubmitting read urb"); + } else { + change_pending = 0; + wake_up_interruptible(&vicam->wait); + } +} + +static int vicam_parameters(struct usb_vicam *vicam) +{ + unsigned char req[0x10]; + unsigned int shutter; + shutter = 10; + + switch (vicam->win.width) { + case 512: + default: + memcpy(req, s512x242bw, 0x10); + break; + case 256: + memcpy(req, s256x242bw, 0x10); + break; + case 128: + memcpy(req, s128x122bw, 0x10); + break; + } + + + mdelay(10); + vicam_sndctrl(1, vicam, VICAM_REQ_CAMERA_POWER, 0x01, NULL, 0); + info ("led on"); + vicam_sndctrl(1, vicam, VICAM_REQ_LED_CONTROL, 0x01, NULL, 0); + + mdelay(10); + + shutter = vicam->win.contrast / 256; + if (shutter == 0) + shutter = 1; + printk("vicam_parameters: brightness %d, shutter %d\n", vicam->win.brightness, shutter ); + req[0] = vicam->win.brightness /256; + shutter = 15600/shutter - 1; + req[6] = shutter & 0xff; + req[7] = (shutter >> 8) & 0xff; + vicam_sndctrl(1, vicam, VICAM_REQ_CAPTURE, 0x80, req, 0x10); + mdelay(10); + vicam_sndctrl(0, vicam, VICAM_REQ_GET_SOMETHIN, 0, buf, 0x10); + mdelay(10); + + return 0; +} + +static int vicam_init(struct usb_vicam *vicam) +{ + int width[] = {128, 256, 512}; + int height[] = {122, 242, 242}; + + dbg("vicam_init"); + buf = kmalloc(0x1e480, GFP_KERNEL); + buf2 = kmalloc(0x1e480, GFP_KERNEL); + if ((!buf) || (!buf2)) { + printk("Not enough memory for vicam!\n"); + goto error; + } + + /* do we do aspect correction in kernel or not? */ + vicam->sizes = 3; + vicam->width = kmalloc(vicam->sizes*sizeof(int), GFP_KERNEL); + vicam->height = kmalloc(vicam->sizes*sizeof(int), GFP_KERNEL); + memcpy(vicam->width, &width, sizeof(width)); + memcpy(vicam->height, &height, sizeof(height)); + vicam->maxframesize = vicam->width[vicam->sizes-1] * vicam->height[vicam->sizes-1]; + + /* Download firmware to camera */ + vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, firmware1, sizeof(firmware1)); + vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, findex1, sizeof(findex1)); + vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, fsetup, sizeof(fsetup)); + vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, firmware2, sizeof(firmware2)); + vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, findex2, sizeof(findex2)); + vicam_sndctrl(1, vicam, VICAM_REQ_VENDOR, 0, fsetup, sizeof(fsetup)); + + vicam_parameters(vicam); + + FILL_BULK_URB(&vicam->readurb, vicam->udev, usb_rcvbulkpipe(vicam->udev, 0x81), + buf, 0x1e480, vicam_bulk, vicam); + printk("Submiting urb: %d\n", usb_submit_urb(&vicam->readurb)); + + return 0; +error: + if (buf) + kfree(buf); + if (buf2) + kfree(buf2); + return 1; +} + +static void * __devinit vicam_probe(struct usb_device *udev, unsigned int ifnum, + const struct usb_device_id *id) +{ + struct usb_vicam *vicam; + char *camera_name=NULL; + + dbg("vicam_probe"); + + /* See if the device offered us matches what we can accept */ + if ((udev->descriptor.idVendor != USB_VICAM_VENDOR_ID) || + (udev->descriptor.idProduct != USB_VICAM_PRODUCT_ID)) { + return NULL; + } + + camera_name="3Com HomeConnect USB"; + info("ViCAM camera found: %s", camera_name); + + vicam = kmalloc (sizeof(struct usb_vicam), GFP_KERNEL); + if (vicam == NULL) { + err ("couldn't kmalloc vicam struct"); + return NULL; + } + memset(vicam, 0, sizeof(*vicam)); + + vicam->udev = udev; + vicam->camera_name = camera_name; + vicam->win.brightness = 128; + vicam->win.contrast = 10; + + /* FIXME */ + if (vicam_init(vicam)) + return NULL; + memcpy(&vicam->vdev, &vicam_template, sizeof(vicam_template)); + memcpy(vicam->vdev.name, vicam->camera_name, strlen(vicam->camera_name)); + + if (video_register_device(&vicam->vdev, VFL_TYPE_GRABBER, video_nr) == -1) { + err("video_register_device"); + return NULL; + } + + info("registered new video device: video%d", vicam->vdev.minor); + + init_MUTEX (&vicam->sem); + init_waitqueue_head(&vicam->wait); + + return vicam; +} + + +/* FIXME - vicam_disconnect - important */ +static void vicam_disconnect(struct usb_device *udev, void *ptr) +{ + struct usb_vicam *vicam; + + vicam = (struct usb_vicam *) ptr; + + if (!vicam->open_count) + video_unregister_device(&vicam->vdev); + vicam->udev = NULL; +/* + vicam->frame[0].grabstate = FRAME_ERROR; + vicam->frame[1].grabstate = FRAME_ERROR; +*/ + + /* Free buffers and shit */ + + info("%s disconnected", vicam->camera_name); + synchronize(vicam); + + if (!vicam->open_count) { + /* Other random junk */ + kfree(vicam); + vicam = NULL; + } +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver vicam_driver = { + name: "vicam", + probe: vicam_probe, + disconnect: vicam_disconnect, + id_table: vicam_table, +}; + +/****************************************************************************** + * + * Module Routines + * + ******************************************************************************/ + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* Module paramaters */ +MODULE_PARM(debug, "i"); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +static int __init usb_vicam_init(void) +{ + int result; + + printk("VICAM: initializing\n"); + /* register this driver with the USB subsystem */ + result = usb_register(&vicam_driver); + if (result < 0) { + err("usb_register failed for the "__FILE__" driver. Error number %d", + result); + return -1; + } + + info(DRIVER_VERSION " " DRIVER_AUTHOR); + info(DRIVER_DESC); + return 0; +} + +static void __exit usb_vicam_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&vicam_driver); +} + +module_init(usb_vicam_init); +module_exit(usb_vicam_exit); diff -Nru a/drivers/usb/vicam.h b/drivers/usb/vicam.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/vicam.h Wed Dec 19 10:26:03 2001 @@ -0,0 +1,81 @@ +/* + * + * Vista Imaging ViCAM / 3Com HomeConnect Usermode Driver + * Christopher L Cheney (C) 2001 + * + */ + +#ifndef __LINUX_VICAM_H +#define __LINUX_VICAM_H + + +#ifdef CONFIG_USB_DEBUG + static int debug = 1; +#else + static int debug; +#endif + +/* Use our own dbg macro */ +#undef dbg +#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg); } while (0) + +#define VICAM_NUMFRAMES 30 +#define VICAM_NUMSBUF 1 + +/* USB REQUEST NUMBERS */ +#define VICAM_REQ_VENDOR 0xff +#define VICAM_REQ_CAMERA_POWER 0x50 +#define VICAM_REQ_CAPTURE 0x51 +#define VICAM_REQ_LED_CONTROL 0x55 +#define VICAM_REQ_GET_SOMETHIN 0x56 + +/* not required but lets you know camera is on */ +/* camera must be on to turn on led */ +/* 0x01 always on 0x03 on when picture taken (flashes) */ + +struct picture_parm +{ + int width; + int height; + int brightness; + int hue; + int colour; + int contrast; + int whiteness; + int depth; + int palette; +}; + +struct vicam_scratch { + unsigned char *data; + volatile int state; + int offset; + int length; +}; + +/* Structure to hold all of our device specific stuff */ +struct usb_vicam +{ + struct video_device vdev; + struct usb_device *udev; + + int open_count; /* number of times this port has been opened */ + struct semaphore sem; /* locks this structure */ + wait_queue_head_t wait; /* Processes waiting */ + + int streaming; + + /* v4l stuff */ + char *camera_name; + char *fbuf; + urb_t *urb[VICAM_NUMSBUF]; + int sizes; + int *width; + int *height; + int maxframesize; + struct picture_parm win; + struct proc_dir_entry *proc_entry; /* /proc/se401/videoX */ + struct urb readurb; +}; + +#endif diff -Nru a/drivers/usb/vicamurbs.h b/drivers/usb/vicamurbs.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/vicamurbs.h Wed Dec 19 10:26:03 2001 @@ -0,0 +1,330 @@ +/* + * + * Vista Imaging ViCAM / 3Com HomeConnect Usermode Driver + * Christopher L Cheney (C) 2001 + * + */ + + +#ifndef __LINUX_VICAMURBS_H +#define __LINUX_VICAMURBS_H + +/* -------------------------------------------------------------------------- */ + +/* FIXME - Figure out transfers so that this doesn't need to be here + * + * Notice: in pieces below, "0" means other code will fill it while "0x00" means this is zero */ + +/* Request 0x51 Image Setup */ + +/* 128x98 ? 0x3180 size */ +static unsigned char s128x98bw[] = { + 0, 0x34, 0xC4, 0x00, 0x00, 0x00, 0, 0, + 0x18, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00 +}; + +/* 128x122 3D80 size */ +static unsigned char s128x122bw[] = { + 0, 0x34, 0xF4, 0x00, 0x00, 0x00, 0, 0, + 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00 +}; + +/* 256x242 ? 0xF280 size */ +static unsigned char s256x242bw[] = { + 0, 0x03, 0xC8, 0x03, 0x00, 0x00, 0, 0, + 0x00, 0x04, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00 +}; + +/* 512x242 0x1E480 size */ +static unsigned char s512x242bw[] = { + 0, 0x05, 0x90, 0x07, 0x00, 0x00, 0, 0, + 0x00, 0x08, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00 +}; + +/* In s512x242: + byte 0: gain -- higher number means brighter image + byte 6, 7: shutter speed, little-endian; set this to 15600 * (shutter speed) - 1. (Where shutter speed is something like 1/1000). +*/ + +/* -------------------------------------------------------------------------- */ + +static unsigned char fsetup[] = { + 0xB6, 0xC3, 0x01, 0x00, 0x06, 0x64, + + 0x00, 0x00 +}; + +static unsigned char firmware1[] = { + 0xB6, 0xC3, 0x1F, 0x00, 0x02, 0x64, + + 0xE7, 0x67, 0xFD, 0xFF, 0x0E, 0xC0, 0xE7, 0x09, + 0xDE, 0x00, 0x8E, 0x00, 0xC0, 0x09, 0x40, 0x03, + 0xC0, 0x17, 0x44, 0x03, 0x4B, 0xAF, 0xC0, 0x07, + 0x00, 0x00, 0x4B, 0xAF, 0x97, 0xCF, 0x00, 0x00 +}; + +static unsigned char findex1[] = { + 0xB6, 0xC3, 0x03, 0x00, 0x03, 0x64, + + 0x18, 0x00, 0x00, 0x00 +}; + +static unsigned char firmware2[] = { + 0xB6, 0xC3, 0x8F, 0x06, 0x02, 0x64, + + 0xE7, 0x07, 0x00, 0x00, 0x08, 0xC0, 0xE7, 0x07, + 0x00, 0x00, 0x3E, 0xC0, 0xE7, 0x07, 0x54, 0x01, + 0xAA, 0x00, 0xE7, 0x07, 0xC8, 0x05, 0xB6, 0x00, + 0xE7, 0x07, 0x42, 0x01, 0xD2, 0x00, 0xE7, 0x07, + 0x7C, 0x00, 0x16, 0x00, 0xE7, 0x07, 0x56, 0x00, + 0x18, 0x00, 0xE7, 0x07, 0x06, 0x00, 0x92, 0xC0, + 0xE7, 0x07, 0x00, 0x00, 0x1E, 0xC0, 0xE7, 0x07, + 0xFF, 0xFF, 0x22, 0xC0, 0xE7, 0x07, 0x04, 0x00, + 0x24, 0xC0, 0xE7, 0x07, 0xEC, 0x27, 0x28, 0xC0, + 0xE7, 0x07, 0x16, 0x01, 0x8E, 0x00, 0xE7, 0x87, + 0x01, 0x00, 0x0E, 0xC0, 0x97, 0xCF, 0xD7, 0x09, + 0x00, 0xC0, 0xE7, 0x77, 0x01, 0x00, 0x92, 0xC0, + 0x09, 0xC1, 0xE7, 0x09, 0xFE, 0x05, 0x24, 0x01, + 0xE7, 0x09, 0x04, 0x06, 0x26, 0x01, 0xE7, 0x07, + 0x07, 0x00, 0x92, 0xC0, 0xE7, 0x05, 0x00, 0xC0, + 0xC0, 0xDF, 0x97, 0xCF, 0x17, 0x00, 0x57, 0x00, + 0x17, 0x02, 0xD7, 0x09, 0x00, 0xC0, 0xE7, 0x77, + 0x01, 0x00, 0x92, 0xC0, 0x0A, 0xC1, 0xE7, 0x57, + 0xFF, 0xFF, 0xFA, 0x05, 0x0D, 0xC0, 0xE7, 0x57, + 0x00, 0x00, 0xFA, 0x05, 0x0F, 0xC0, 0x9F, 0xAF, + 0xC6, 0x00, 0xE7, 0x05, 0x00, 0xC0, 0xC8, 0x05, + 0xC1, 0x05, 0xC0, 0x05, 0xC0, 0xDF, 0x97, 0xCF, + 0x27, 0xDA, 0xFA, 0x05, 0xEF, 0x07, 0x01, 0x00, + 0x0B, 0x06, 0x73, 0xCF, 0x9F, 0xAF, 0x78, 0x01, + 0x9F, 0xAF, 0x1A, 0x03, 0x6E, 0xCF, 0xE7, 0x09, + 0xFC, 0x05, 0x24, 0x01, 0xE7, 0x09, 0x02, 0x06, + 0x26, 0x01, 0xE7, 0x07, 0x07, 0x00, 0x92, 0xC0, + 0xE7, 0x09, 0xFC, 0x05, 0xFE, 0x05, 0xE7, 0x09, + 0x02, 0x06, 0x04, 0x06, 0xE7, 0x09, 0x00, 0x06, + 0xFC, 0x05, 0xE7, 0x09, 0xFE, 0x05, 0x00, 0x06, + 0x27, 0xDA, 0xFA, 0x05, 0xE7, 0x57, 0x01, 0x00, + 0xFA, 0x05, 0x02, 0xCA, 0x04, 0xC0, 0x97, 0xCF, + 0x9F, 0xAF, 0x66, 0x05, 0x97, 0xCF, 0xE7, 0x07, + 0x40, 0x00, 0x02, 0x06, 0xC8, 0x09, 0xFC, 0x05, + 0x9F, 0xAF, 0xDA, 0x02, 0x97, 0xCF, 0xCF, 0x17, + 0x02, 0x00, 0xEF, 0x57, 0x81, 0x00, 0x09, 0x06, + 0x9F, 0xA0, 0xB6, 0x01, 0xEF, 0x57, 0x80, 0x00, + 0x09, 0x06, 0x9F, 0xA0, 0x40, 0x02, 0xEF, 0x57, + 0x01, 0x00, 0x0B, 0x06, 0x9F, 0xA0, 0x46, 0x03, + 0xE7, 0x07, 0x01, 0x00, 0x0A, 0xC0, 0x46, 0xAF, + 0x47, 0xAF, 0x9F, 0xAF, 0x40, 0x02, 0xE7, 0x07, + 0x2E, 0x00, 0x0A, 0xC0, 0xEF, 0x87, 0x80, 0x00, + 0x09, 0x06, 0x97, 0xCF, 0x00, 0x0E, 0x01, 0x00, + 0xC0, 0x57, 0x51, 0x00, 0x9F, 0xC0, 0x9E, 0x02, + 0xC0, 0x57, 0x50, 0x00, 0x20, 0xC0, 0xC0, 0x57, + 0x55, 0x00, 0x12, 0xC0, 0xC0, 0x57, 0x56, 0x00, + 0x9F, 0xC0, 0x72, 0x02, 0x9F, 0xCF, 0xD6, 0x02, + 0xC1, 0x0B, 0x08, 0x06, 0x01, 0xD0, 0x6F, 0x90, + 0x08, 0x06, 0xC0, 0x07, 0x08, 0x00, 0xC1, 0x0B, + 0x08, 0x06, 0x9F, 0xAF, 0x28, 0x05, 0x97, 0xCF, + 0x2F, 0x0E, 0x02, 0x00, 0x08, 0x06, 0xC0, 0x07, + 0x08, 0x00, 0xC1, 0x0B, 0x08, 0x06, 0x9F, 0xAF, + 0x28, 0x05, 0x9F, 0xCF, 0xD6, 0x02, 0x2F, 0x0E, + 0x02, 0x00, 0x09, 0x06, 0xEF, 0x87, 0x80, 0x00, + 0x09, 0x06, 0x9F, 0xCF, 0xD6, 0x02, 0xEF, 0x67, + 0x7F, 0xFF, 0x09, 0x06, 0xE7, 0x67, 0xFF, 0xFD, + 0x22, 0xC0, 0xE7, 0x67, 0xEF, 0xFF, 0x24, 0xC0, + 0xE7, 0x87, 0x10, 0x00, 0x28, 0xC0, 0x9F, 0xAF, + 0xB8, 0x05, 0xE7, 0x87, 0xE0, 0x21, 0x24, 0xC0, + 0x9F, 0xAF, 0xA8, 0x05, 0xE7, 0x87, 0x08, 0x00, + 0x24, 0xC0, 0xE7, 0x67, 0xDF, 0xFF, 0x24, 0xC0, + 0xC8, 0x07, 0x0A, 0x00, 0xC0, 0x07, 0x00, 0x00, + 0xC1, 0x07, 0x01, 0x00, 0x9F, 0xAF, 0x28, 0x05, + 0x9F, 0xAF, 0xB8, 0x05, 0xC0, 0x07, 0x9E, 0x00, + 0x9F, 0xAF, 0x44, 0x05, 0xE7, 0x67, 0xFF, 0xFE, + 0x24, 0xC0, 0xC0, 0x09, 0x20, 0xC0, 0xE7, 0x87, + 0x00, 0x01, 0x24, 0xC0, 0xC0, 0x77, 0x00, 0x02, + 0x0F, 0xC1, 0xE7, 0x67, 0xF7, 0xFF, 0x24, 0xC0, + 0xE7, 0x67, 0xF7, 0xFF, 0x24, 0xC0, 0xE7, 0x87, + 0x08, 0x00, 0x24, 0xC0, 0x08, 0xDA, 0x5E, 0xC1, + 0xEF, 0x07, 0x80, 0x00, 0x09, 0x06, 0x97, 0xCF, + 0xEF, 0x07, 0x01, 0x00, 0x0A, 0x06, 0x97, 0xCF, + 0xEF, 0x07, 0x00, 0x00, 0x0B, 0x06, 0xEF, 0x07, + 0x00, 0x00, 0x0A, 0x06, 0xEF, 0x67, 0x7F, 0xFF, + 0x09, 0x06, 0xEF, 0x07, 0x00, 0x00, 0x0D, 0x06, + 0xE7, 0x67, 0xEF, 0xFF, 0x28, 0xC0, 0xE7, 0x67, + 0x17, 0xD8, 0x24, 0xC0, 0xE7, 0x07, 0x00, 0x00, + 0x1E, 0xC0, 0xE7, 0x07, 0xFF, 0xFF, 0x22, 0xC0, + 0x97, 0xCF, 0xC8, 0x07, 0x0E, 0x06, 0x9F, 0xAF, + 0xDA, 0x02, 0xE7, 0x07, 0x00, 0x00, 0xF2, 0x05, + 0xE7, 0x07, 0x10, 0x00, 0xF6, 0x05, 0xE7, 0x07, + 0x0E, 0x06, 0xF4, 0x05, 0xE7, 0x07, 0xD6, 0x02, + 0xF8, 0x05, 0xC8, 0x07, 0xF2, 0x05, 0xC1, 0x07, + 0x00, 0x80, 0x50, 0xAF, 0x97, 0xCF, 0x2F, 0x0C, + 0x02, 0x00, 0x07, 0x06, 0x2F, 0x0C, 0x04, 0x00, + 0x06, 0x06, 0xE7, 0x07, 0x00, 0x00, 0xF2, 0x05, + 0xE7, 0x07, 0x10, 0x00, 0xF6, 0x05, 0xE7, 0x07, + 0xE2, 0x05, 0xF4, 0x05, 0xE7, 0x07, 0xCE, 0x02, + 0xF8, 0x05, 0xC8, 0x07, 0xF2, 0x05, 0xC1, 0x07, + 0x00, 0x80, 0x51, 0xAF, 0x97, 0xCF, 0x9F, 0xAF, + 0x66, 0x04, 0x9F, 0xAF, 0x1A, 0x03, 0x59, 0xAF, + 0x97, 0xCF, 0xC0, 0x07, 0x0E, 0x00, 0xC1, 0x0B, + 0x0C, 0x06, 0x41, 0xD1, 0x9F, 0xAF, 0x28, 0x05, + 0xC0, 0x07, 0x3C, 0x00, 0x9F, 0xAF, 0x44, 0x05, + 0x68, 0x00, 0xC0, 0x07, 0x3B, 0x00, 0x9F, 0xAF, + 0x44, 0x05, 0x6F, 0x00, 0x0C, 0x06, 0x68, 0x00, + 0xE0, 0x07, 0x04, 0x01, 0xE8, 0x0B, 0x0A, 0x06, + 0xE8, 0x07, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x02, + 0xE0, 0x07, 0xEC, 0x01, 0xE0, 0x07, 0xFC, 0xFF, + 0x97, 0xCF, 0xE7, 0x07, 0xFF, 0xFF, 0xFA, 0x05, + 0xEF, 0x07, 0x00, 0x00, 0x0B, 0x06, 0xE7, 0x07, + 0x0E, 0x06, 0x24, 0x01, 0xE7, 0x07, 0x0E, 0x06, + 0xFE, 0x05, 0xE7, 0x07, 0x40, 0x00, 0x26, 0x01, + 0xE7, 0x07, 0x40, 0x00, 0x04, 0x06, 0xE7, 0x07, + 0x07, 0x00, 0x92, 0xC0, 0x97, 0xCF, 0xEF, 0x07, + 0x02, 0x00, 0x0B, 0x06, 0x9F, 0xAF, 0x78, 0x01, + 0xEF, 0x77, 0x80, 0x00, 0x07, 0x06, 0x9F, 0xC0, + 0x14, 0x04, 0xEF, 0x77, 0x01, 0x00, 0x07, 0x06, + 0x37, 0xC0, 0xEF, 0x77, 0x01, 0x00, 0x0D, 0x06, + 0x0F, 0xC1, 0xEF, 0x07, 0x01, 0x00, 0x0D, 0x06, + 0xC0, 0x07, 0x02, 0x00, 0xC1, 0x07, 0x30, 0x00, + 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, 0x01, 0x00, + 0xC1, 0x07, 0x02, 0x00, 0x9F, 0xAF, 0x28, 0x05, + 0xC8, 0x07, 0xFF, 0x4F, 0x9F, 0xAF, 0xA8, 0x05, + 0xC0, 0x07, 0x38, 0x00, 0x9F, 0xAF, 0x44, 0x05, + 0xC1, 0x77, 0x03, 0x00, 0x02, 0xC1, 0x08, 0xDA, + 0x75, 0xC1, 0xC1, 0x77, 0x01, 0x00, 0x0A, 0xC1, + 0xC0, 0x07, 0x01, 0x00, 0xC1, 0x07, 0x02, 0x00, + 0x9F, 0xAF, 0x28, 0x05, 0xEF, 0x07, 0x01, 0x00, + 0x06, 0x06, 0x2C, 0xCF, 0xC0, 0x07, 0x01, 0x00, + 0xC1, 0x07, 0x04, 0x00, 0x9F, 0xAF, 0x28, 0x05, + 0xEF, 0x07, 0x00, 0x00, 0x06, 0x06, 0x22, 0xCF, + 0xEF, 0x07, 0x00, 0x00, 0x0D, 0x06, 0xEF, 0x57, + 0x01, 0x00, 0x06, 0x06, 0x1B, 0xC0, 0xC0, 0x07, + 0x01, 0x00, 0xC1, 0x07, 0x01, 0x00, 0x9F, 0xAF, + 0x28, 0x05, 0xC0, 0x07, 0x02, 0x00, 0xC1, 0x07, + 0x30, 0x00, 0x9F, 0xAF, 0x28, 0x05, 0xC8, 0x07, + 0xFF, 0x4F, 0x9F, 0xAF, 0xA8, 0x05, 0xC0, 0x07, + 0x38, 0x00, 0x9F, 0xAF, 0x44, 0x05, 0xC1, 0x67, + 0x03, 0x00, 0xC1, 0x57, 0x03, 0x00, 0x02, 0xC0, + 0x08, 0xDA, 0x73, 0xC1, 0xC0, 0x07, 0x02, 0x00, + 0xC1, 0x07, 0x12, 0x00, 0xEF, 0x57, 0x00, 0x00, + 0x06, 0x06, 0x02, 0xC0, 0xC1, 0x07, 0x23, 0x00, + 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, 0x14, 0x00, + 0xC1, 0x0B, 0xEA, 0x05, 0x9F, 0xAF, 0x28, 0x05, + 0xC0, 0x07, 0x3E, 0x00, 0x9F, 0xAF, 0x0A, 0x05, + 0xE7, 0x09, 0xE4, 0x05, 0xFA, 0x05, 0x27, 0xD8, + 0xFA, 0x05, 0xE7, 0x07, 0x0E, 0x06, 0xFC, 0x05, + 0xE7, 0x07, 0x4E, 0x06, 0x00, 0x06, 0xE7, 0x07, + 0x40, 0x00, 0x02, 0x06, 0x9F, 0xAF, 0x66, 0x05, + 0x9F, 0xAF, 0xC6, 0x00, 0x97, 0xCF, 0xC1, 0x0B, + 0xE2, 0x05, 0x41, 0xD0, 0x01, 0xD2, 0xC1, 0x17, + 0x23, 0x00, 0x9F, 0xAF, 0xDC, 0x04, 0xC0, 0x07, + 0x04, 0x00, 0xC1, 0x0B, 0xE3, 0x05, 0x9F, 0xAF, + 0x28, 0x05, 0xC0, 0x07, 0x06, 0x00, 0xC1, 0x09, + 0xE6, 0x05, 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, + 0x07, 0x00, 0xC1, 0x09, 0xE6, 0x05, 0xC1, 0xD1, + 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, 0x0B, 0x00, + 0xC1, 0x09, 0xE8, 0x05, 0x9F, 0xAF, 0x28, 0x05, + 0xC0, 0x07, 0x0C, 0x00, 0xC1, 0x09, 0xE8, 0x05, + 0xC1, 0xD1, 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, + 0x0D, 0x00, 0xC1, 0x07, 0x09, 0x00, 0x9F, 0xAF, + 0x28, 0x05, 0xC0, 0x07, 0x03, 0x00, 0xC1, 0x07, + 0x32, 0x00, 0x9F, 0xAF, 0x28, 0x05, 0xC0, 0x07, + 0x0F, 0x00, 0xC1, 0x07, 0x00, 0x00, 0x9F, 0xAF, + 0x28, 0x05, 0x97, 0xCF, 0xE7, 0x67, 0xFF, 0xD9, + 0x24, 0xC0, 0xC8, 0x07, 0x0A, 0x00, 0x40, 0x00, + 0xC0, 0x67, 0x00, 0x02, 0x27, 0x80, 0x24, 0xC0, + 0xE7, 0x87, 0x00, 0x04, 0x24, 0xC0, 0xE7, 0x67, + 0xFF, 0xF9, 0x24, 0xC0, 0x01, 0xD2, 0x08, 0xDA, + 0x72, 0xC1, 0xE7, 0x87, 0x00, 0x20, 0x24, 0xC0, + 0x97, 0xCF, 0x27, 0x00, 0x1E, 0xC0, 0xE7, 0x87, + 0xFF, 0x00, 0x22, 0xC0, 0xE7, 0x67, 0x7F, 0xFF, + 0x24, 0xC0, 0xE7, 0x87, 0x80, 0x00, 0x24, 0xC0, + 0xE7, 0x87, 0x80, 0x00, 0x24, 0xC0, 0x97, 0xCF, + 0x9F, 0xAF, 0x0A, 0x05, 0x67, 0x00, 0x1E, 0xC0, + 0xE7, 0x67, 0xBF, 0xFF, 0x24, 0xC0, 0xE7, 0x87, + 0x40, 0x00, 0x24, 0xC0, 0xE7, 0x87, 0x40, 0x00, + 0x24, 0xC0, 0x97, 0xCF, 0x9F, 0xAF, 0x0A, 0x05, + 0xE7, 0x67, 0x00, 0xFF, 0x22, 0xC0, 0xE7, 0x67, + 0xFF, 0xFE, 0x24, 0xC0, 0xE7, 0x67, 0xFF, 0xFE, + 0x24, 0xC0, 0xC1, 0x09, 0x20, 0xC0, 0xE7, 0x87, + 0x00, 0x01, 0x24, 0xC0, 0x97, 0xCF, 0xC0, 0x07, + 0x40, 0x00, 0xC8, 0x09, 0xFC, 0x05, 0xE7, 0x67, + 0x00, 0xFF, 0x22, 0xC0, 0xE7, 0x67, 0xFF, 0xFE, + 0x24, 0xC0, 0xE7, 0x67, 0xBF, 0xFF, 0x24, 0xC0, + 0xE7, 0x67, 0xBF, 0xFF, 0x24, 0xC0, 0x00, 0xDA, + 0xE8, 0x09, 0x20, 0xC0, 0xE7, 0x87, 0x40, 0x00, + 0x24, 0xC0, 0xE7, 0x87, 0x40, 0x00, 0x24, 0xC0, + 0x00, 0xDA, 0xE8, 0x09, 0x20, 0xC0, 0x6D, 0xC1, + 0xE7, 0x87, 0x00, 0x01, 0x24, 0xC0, 0x97, 0xCF, + 0xE7, 0x07, 0x32, 0x00, 0x12, 0xC0, 0xE7, 0x77, + 0x00, 0x80, 0x12, 0xC0, 0x7C, 0xC0, 0x97, 0xCF, + 0xE7, 0x07, 0x20, 0x4E, 0x12, 0xC0, 0xE7, 0x77, + 0x00, 0x80, 0x12, 0xC0, 0x7C, 0xC0, 0x97, 0xCF, + 0x09, 0x02, 0x19, 0x00, 0x01, 0x01, 0x00, 0x80, + 0x96, 0x09, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x05, 0x81, 0x02, 0x40, 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, 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 +}; + +static unsigned char findex2[] = { + 0xB6, 0xC3, 0x2F, 0x01, 0x03, 0x64, + + 0x0E, 0x00, 0x14, 0x00, 0x1A, 0x00, 0x20, 0x00, + 0x26, 0x00, 0x4A, 0x00, 0x64, 0x00, 0x6A, 0x00, + 0x92, 0x00, 0x9A, 0x00, 0xA0, 0x00, 0xB2, 0x00, + 0xB8, 0x00, 0xBE, 0x00, 0xC2, 0x00, 0xC8, 0x00, + 0xCE, 0x00, 0xDC, 0x00, 0xDA, 0x00, 0xE2, 0x00, + 0xE0, 0x00, 0xE8, 0x00, 0xE6, 0x00, 0xEE, 0x00, + 0xEC, 0x00, 0xF2, 0x00, 0xF8, 0x00, 0x02, 0x01, + 0x0A, 0x01, 0x0E, 0x01, 0x12, 0x01, 0x1E, 0x01, + 0x22, 0x01, 0x28, 0x01, 0x2C, 0x01, 0x32, 0x01, + 0x36, 0x01, 0x44, 0x01, 0x50, 0x01, 0x5E, 0x01, + 0x72, 0x01, 0x76, 0x01, 0x7A, 0x01, 0x80, 0x01, + 0x88, 0x01, 0x8C, 0x01, 0x94, 0x01, 0x9C, 0x01, + 0xA0, 0x01, 0xA4, 0x01, 0xAA, 0x01, 0xB0, 0x01, + 0xB4, 0x01, 0xBA, 0x01, 0xD0, 0x01, 0xDA, 0x01, + 0xF6, 0x01, 0xFA, 0x01, 0x02, 0x02, 0x34, 0x02, + 0x3C, 0x02, 0x44, 0x02, 0x4A, 0x02, 0x50, 0x02, + 0x56, 0x02, 0x74, 0x02, 0x78, 0x02, 0x7E, 0x02, + 0x84, 0x02, 0x8A, 0x02, 0x88, 0x02, 0x90, 0x02, + 0x8E, 0x02, 0x94, 0x02, 0xA2, 0x02, 0xA8, 0x02, + 0xAE, 0x02, 0xB4, 0x02, 0xBA, 0x02, 0xB8, 0x02, + 0xC0, 0x02, 0xBE, 0x02, 0xC4, 0x02, 0xD0, 0x02, + 0xD4, 0x02, 0xE0, 0x02, 0xE6, 0x02, 0xEE, 0x02, + 0xF8, 0x02, 0xFC, 0x02, 0x06, 0x03, 0x1E, 0x03, + 0x24, 0x03, 0x28, 0x03, 0x30, 0x03, 0x2E, 0x03, + 0x3C, 0x03, 0x4A, 0x03, 0x4E, 0x03, 0x54, 0x03, + 0x58, 0x03, 0x5E, 0x03, 0x66, 0x03, 0x6E, 0x03, + 0x7A, 0x03, 0x86, 0x03, 0x8E, 0x03, 0x96, 0x03, + 0xB2, 0x03, 0xB8, 0x03, 0xC6, 0x03, 0xCC, 0x03, + 0xD4, 0x03, 0xDA, 0x03, 0xE8, 0x03, 0xF4, 0x03, + 0xFC, 0x03, 0x04, 0x04, 0x20, 0x04, 0x2A, 0x04, + 0x32, 0x04, 0x36, 0x04, 0x3E, 0x04, 0x44, 0x04, + 0x42, 0x04, 0x48, 0x04, 0x4E, 0x04, 0x4C, 0x04, + 0x54, 0x04, 0x52, 0x04, 0x5A, 0x04, 0x5E, 0x04, + 0x62, 0x04, 0x68, 0x04, 0x74, 0x04, 0x7C, 0x04, + 0x80, 0x04, 0x88, 0x04, 0x8C, 0x04, 0x94, 0x04, + 0x9A, 0x04, 0xA2, 0x04, 0xA6, 0x04, 0xAE, 0x04, + 0xB4, 0x04, 0xC0, 0x04, 0xCC, 0x04, 0xD8, 0x04, + 0x2A, 0x05, 0x46, 0x05, 0x6C, 0x05, 0x00, 0x00 +}; + +#endif diff -Nru a/include/linux/fs.h b/include/linux/fs.h --- a/include/linux/fs.h Wed Dec 19 10:26:02 2001 +++ b/include/linux/fs.h Wed Dec 19 10:26:02 2001 @@ -311,7 +311,6 @@ #include #include #include -#include #include #include @@ -502,7 +501,6 @@ struct ncp_inode_info ncpfs_i; struct proc_inode_info proc_i; struct socket socket_i; - struct usbdev_inode_info usbdev_i; struct jffs2_inode_info jffs2_i; void *generic_ip; } u; @@ -686,7 +684,6 @@ #include #include #include -#include #include #include @@ -744,7 +741,6 @@ struct bfs_sb_info bfs_sb; struct udf_sb_info udf_sb; struct ncp_sb_info ncpfs_sb; - struct usbdev_sb_info usbdevfs_sb; struct jffs2_sb_info jffs2_sb; struct cramfs_sb_info cramfs_sb; void *generic_sbp; diff -Nru a/include/linux/usb.h b/include/linux/usb.h --- a/include/linux/usb.h Wed Dec 19 10:26:02 2001 +++ b/include/linux/usb.h Wed Dec 19 10:26:02 2001 @@ -521,7 +521,7 @@ struct semaphore serialize; - /* ioctl -- userspace apps can talk to drivers through usbdevfs */ + /* ioctl -- userspace apps can talk to drivers through usbfs */ int (*ioctl)(struct usb_device *dev, unsigned int code, void *buf); /* support for "new-style" USB hotplugging */ @@ -989,8 +989,7 @@ int bandwidth_int_reqs; /* number of Interrupt requesters */ int bandwidth_isoc_reqs; /* number of Isoc. requesters */ - /* usbdevfs inode list */ - struct list_head inodes; + struct dentry *dentry; /* usbfs dentry entry for the bus */ atomic_t refcnt; }; @@ -1028,6 +1027,21 @@ #define NS_TO_US(ns) ((ns + 500L) / 1000L) /* convert & round nanoseconds to microseconds */ +/* + * As of USB 2.0, full/low speed devices are segregated into trees. + * One type grows from USB 1.1 host controllers (OHCI, UHCI etc). + * The other type grows from high speed hubs when they connect to + * full/low speed devices using "Transaction Translators" (TTs). + * + * TTs should only be known to the hub driver, and high speed bus + * drivers (only EHCI for now). They affect periodic scheduling and + * sometimes control/bulk error recovery. + */ +struct usb_tt { + struct usb_device *hub; /* upstream highspeed hub */ + int multi; /* true means one TT per port */ +}; + /* -------------------------------------------------------------------------- */ @@ -1056,7 +1070,8 @@ #define USB_MAXCHILDREN (16) struct usb_device { - int devnum; /* Device number on USB bus */ + int devnum; /* Address on USB bus */ + char devpath [16]; /* Use in messages: /port/port/... */ enum { USB_SPEED_UNKNOWN = 0, /* enumerating */ @@ -1064,8 +1079,8 @@ USB_SPEED_HIGH /* usb 2.0 */ } speed; - struct usb_device *tt; /* usb1.1 device on usb2.0 bus */ - int ttport; /* device/hub port on that tt */ + struct usb_tt *tt; /* low/full speed dev, highspeed hub */ + int ttport; /* device port on that tt hub */ atomic_t refcnt; /* Reference count */ struct semaphore serialize; @@ -1090,9 +1105,8 @@ void *hcpriv; /* Host Controller private data */ - /* usbdevfs inode list */ - struct list_head inodes; struct list_head filelist; + struct dentry *dentry; /* usbfs dentry entry for the device */ /* * Child devices - these can be either new devices @@ -1254,7 +1268,7 @@ /* * bus and driver list - * exported only for usbdevfs (not visible outside usbcore) + * exported only for usbfs (not visible outside usbcore) */ extern struct list_head usb_driver_list; @@ -1271,23 +1285,25 @@ * these are expected to be called from the USB core/hub thread * with the kernel lock held */ -extern void usbdevfs_add_bus(struct usb_bus *bus); -extern void usbdevfs_remove_bus(struct usb_bus *bus); -extern void usbdevfs_add_device(struct usb_device *dev); -extern void usbdevfs_remove_device(struct usb_device *dev); +extern void usbfs_add_bus(struct usb_bus *bus); +extern void usbfs_remove_bus(struct usb_bus *bus); +extern void usbfs_add_device(struct usb_device *dev); +extern void usbfs_remove_device(struct usb_device *dev); +extern void usbfs_update_special (void); -extern int usbdevfs_init(void); -extern void usbdevfs_cleanup(void); +extern int usbfs_init(void); +extern void usbfs_cleanup(void); #else /* CONFIG_USB_DEVICEFS */ -static inline void usbdevfs_add_bus(struct usb_bus *bus) {} -static inline void usbdevfs_remove_bus(struct usb_bus *bus) {} -static inline void usbdevfs_add_device(struct usb_device *dev) {} -static inline void usbdevfs_remove_device(struct usb_device *dev) {} +static inline void usbfs_add_bus(struct usb_bus *bus) {} +static inline void usbfs_remove_bus(struct usb_bus *bus) {} +static inline void usbfs_add_device(struct usb_device *dev) {} +static inline void usbfs_remove_device(struct usb_device *dev) {} +static inline void usbfs_update_special (void) {} -static inline int usbdevfs_init(void) { return 0; } -static inline void usbdevfs_cleanup(void) { } +static inline int usbfs_init(void) { return 0; } +static inline void usbfs_cleanup(void) { } #endif /* CONFIG_USB_DEVICEFS */ diff -Nru a/include/linux/usbdevice_fs.h b/include/linux/usbdevice_fs.h --- a/include/linux/usbdevice_fs.h Wed Dec 19 10:26:02 2001 +++ b/include/linux/usbdevice_fs.h Wed Dec 19 10:26:02 2001 @@ -150,17 +150,6 @@ #include #include -/* - * inode number macros - */ -#define ITYPE(x) ((x)&(0xf<<28)) -#define ISPECIAL (0<<28) -#define IBUS (1<<28) -#define IDEVICE (2<<28) -#define IBUSNR(x) (((x)>>8)&0xff) -#define IDEVNR(x) ((x)&0xff) - -#define IROOT 1 struct dev_state { struct list_head list; /* state list */