diff -Nru a/CREDITS b/CREDITS --- a/CREDITS Thu Jun 19 17:28:35 2003 +++ b/CREDITS Thu Jun 19 17:28:35 2003 @@ -2687,6 +2687,14 @@ E: gt8134b@prism.gatech.edu D: Dosemu +N: Duncan Sands +E: duncan.sands@wanadoo.fr +W: http://topo.math.u-psud.fr/~sands +D: Alcatel SpeedTouch USB driver +S: 69 rue Dunois +S: 75013 Paris +S: France + N: Hannu Savolainen E: hannu@opensound.com D: Maintainer of the sound drivers until 2.1.x days. diff -Nru a/Documentation/Configure.help b/Documentation/Configure.help --- a/Documentation/Configure.help Thu Jun 19 17:28:36 2003 +++ b/Documentation/Configure.help Thu Jun 19 17:28:36 2003 @@ -13919,11 +13919,12 @@ The module will be called powermate.o. If you want to compile it as a module, say M here and read . -Aiptek 6000U/8000U tablet support +Aiptek HyperPen tablet support CONFIG_USB_AIPTEK - Say Y here if you want to use the USB version of the Aiptek 6000U/8000U - tablet. Make sure to say Y to "Event interface support" - (CONFIG_INPUT_EVDEV) as well. + Say Y here if you want to use the USB version of the Aiptek HyperPen + Digital Tablet (models 4000U, 5000U, 6000U, 8000U, and 12000U.) + Make sure to say Y to "Mouse support" (CONFIG_INPUT_MOUSEDEV) and/or + "Event interface support" (CONFIG_INPUT_EVDEV) as well. This driver is also available as a module ( = code which can be inserted in and removed from the running kernel whenever you want). @@ -14620,6 +14621,24 @@ The module will be called catc.o. If you want to compile it as a module, say M here and read . +USB ASIX AX88172 based ethernet device support +CONFIG_USB_AX8817X + Say Y if you want to use one of the following 10/100 USB2 Ethernet + devices based on the ASIX AX88172 chip. Supported devices are: + Linksys USB200M + Netgear FA120 + D-Link DUB-E100 + Hawking UF200 + + This driver makes the adapter appear as a normal Ethernet interface, + typically on eth0, if it is the only ethernet device, or perhaps on + eth1, if you have a PCI or ISA ethernet card installed. + + 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 ax8817x.o. If you want to compile it as a + module, say M here and read . + USB Kodak DC-2xx Camera support CONFIG_USB_DC2XX Say Y here if you want to connect this type of still camera to your @@ -14902,6 +14921,17 @@ inserted in and removed from the running kernel whenever you want). The module will be called dsbr100.o. If you want to compile it as a module, say M here and read . + +Alcatel Speedtouch USB support +CONFIG_USB_SPEEDTOUCH + Say Y here if you have an Alcatel SpeedTouch USB or SpeedTouch 330 + modem. In order to use your modem you will need to install some user + space tools, see for details. + + 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 speedtch.o. If you want to compile it as + a module, say M here and read . Always do synchronous disk IO for UBD CONFIG_BLK_DEV_UBD_SYNC diff -Nru a/Documentation/devices.txt b/Documentation/devices.txt --- a/Documentation/devices.txt Thu Jun 19 17:28:36 2003 +++ b/Documentation/devices.txt Thu Jun 19 17:28:36 2003 @@ -2170,16 +2170,27 @@ 0 = /dev/usb/lp0 First USB printer ... 15 = /dev/usb/lp15 16th USB printer - 16 = /dev/usb/mouse0 First USB mouse - ... - 31 = /dev/usb/mouse15 16th USB mouse - 32 = /dev/usb/ez0 First USB firmware loader - ... - 47 = /dev/usb/ez15 16th USB firmware loader + 32 = /dev/usb/mdc800 MDC800 USB camera 48 = /dev/usb/scanner0 First USB scanner ... 63 = /dev/usb/scanner15 16th USB scanner 64 = /dev/usb/rio500 Diamond Rio 500 + 80 = /dev/usb/dc2xx0 1st DC2xx camera device + ... + 95 = /dev/usb/dc2xx15 16th DC2xx camera device + 96 = /dev/usb/hiddev0 1st USB HID device + ... + 111 = /dev/usb/hiddev15 16th USB HID device + 112 = /dev/usb/auer0 1st auerswald ISDN device + ... + 127 = /dev/usb/auer15 16th auerswald ISDN device + 128 = /dev/usb/brlvgr0 First Braille Voyager device + ... + 131 = /dev/usb/brlvgr3 Fourth Braille Voyager device + 144 = /dev/usb/lcd USB LCD device + 240 = /dev/usb/dabusb0 First daubusb device + ... + 243 = /dev/usb/dabusb3 Fourth dabusb device 181 char Conrad Electronic parallel port radio clocks 0 = /dev/pcfclock0 First Conrad radio clock diff -Nru a/Documentation/ioctl-number.txt b/Documentation/ioctl-number.txt --- a/Documentation/ioctl-number.txt Thu Jun 19 17:28:36 2003 +++ b/Documentation/ioctl-number.txt Thu Jun 19 17:28:36 2003 @@ -101,7 +101,8 @@ 'S' 82-FF scsi/scsi.h conflict! 'T' all linux/soundcard.h conflict! 'T' all asm-i386/ioctls.h conflict! -'U' all linux/drivers/usb/usb.h +'U' 00-EF linux/drivers/usb/usb.h +'U' F0-FF drivers/usb/auerswald.c 'V' all linux/vt.h 'W' 00-1F linux/watchdog.h conflict! 'W' 00-1F linux/wanrouter.h conflict! diff -Nru a/MAINTAINERS b/MAINTAINERS --- a/MAINTAINERS Thu Jun 19 17:28:36 2003 +++ b/MAINTAINERS Thu Jun 19 17:28:36 2003 @@ -206,6 +206,21 @@ L: linux-scsi@vger.kernel.org S: Maintained +ALCATEL SPEEDTOUCH USB DRIVER +P: Duncan Sands +M: duncan.sands@wanadoo.fr +L: linux-usb-users@lists.sourceforge.net +L: linux-usb-devel@lists.sourceforge.net +W: http://www.linux-usb.org/SpeedTouch/ +S: Maintained + +AIPTEK USB TABLET DRIVER +P: Bryan W. Headley +M: Bryan W. Headley +W: http://aiptektablet.sourceforge.net +L: aiptektablet-users@sourceforge.net +S: Maintained + ALTERA EPXA1/EPXA10 DEVELOPMENT BOARD PORT P: Clive Davies M: cdavies@altera.com diff -Nru a/drivers/usb/CDCEther.c b/drivers/usb/CDCEther.c --- a/drivers/usb/CDCEther.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/CDCEther.c Thu Jun 19 17:28:35 2003 @@ -37,6 +37,7 @@ #define SHORT_DRIVER_DESC "CDC Ethernet Class" #define DRIVER_VERSION "0.98.6" +static const char driver_name[] = "CDCEther"; static const char *version = __FILE__ ": " DRIVER_VERSION " 7 Jan 2002 Brad Hards and another"; // We only try to claim CDC Ethernet model devices */ static struct usb_device_id CDCEther_ids[] = { @@ -460,7 +461,7 @@ /* get driver info */ case ETHTOOL_GDRVINFO: { struct ethtool_drvinfo info = {ETHTOOL_GDRVINFO}; - strncpy(info.driver, SHORT_DRIVER_DESC, ETHTOOL_BUSINFO_LEN); + strncpy(info.driver, driver_name, ETHTOOL_BUSINFO_LEN); strncpy(info.version, DRIVER_VERSION, ETHTOOL_BUSINFO_LEN); sprintf(tmp, "usb%d:%d", ether_dev->usb->bus->busnum, ether_dev->usb->devnum); strncpy(info.bus_info, tmp, ETHTOOL_BUSINFO_LEN); @@ -1421,7 +1422,7 @@ ////////////////////////////////////////////////////////////////////////////// static struct usb_driver CDCEther_driver = { - name: "CDCEther", + name: driver_name, probe: CDCEther_probe, disconnect: CDCEther_disconnect, id_table: CDCEther_ids, diff -Nru a/drivers/usb/Config.in b/drivers/usb/Config.in --- a/drivers/usb/Config.in Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/Config.in Thu Jun 19 17:28:36 2003 @@ -91,6 +91,7 @@ dep_tristate ' USB Realtek RTL8150 based ethernet device support (EXPERIMENTAL)' CONFIG_USB_RTL8150 $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL dep_tristate ' USB KLSI KL5USB101-based ethernet device support (EXPERIMENTAL)' CONFIG_USB_KAWETH $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL dep_tristate ' USB CATC NetMate-based Ethernet device support (EXPERIMENTAL)' CONFIG_USB_CATC $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL + dep_tristate ' USB ASIX AX88172 based ethernet device support (EXPERIMENTAL)' CONFIG_USB_AX8817X $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL dep_tristate ' USB Communication Class Ethernet device support (EXPERIMENTAL)' CONFIG_USB_CDCETHER $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL dep_tristate ' USB-to-USB Networking cables, Linux PDAs, ... (EXPERIMENTAL)' CONFIG_USB_USBNET $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL fi @@ -105,5 +106,6 @@ dep_tristate ' Texas Instruments Graph Link USB (aka SilverLink) cable support' CONFIG_USB_TIGL $CONFIG_USB dep_tristate ' Tieman Voyager USB Braille display support (EXPERIMENTAL)' CONFIG_USB_BRLVGER $CONFIG_USB $CONFIG_EXPERIMENTAL dep_tristate ' USB LCD device support' CONFIG_USB_LCD $CONFIG_USB + dep_tristate ' Alcatel Speedtouch USB support' CONFIG_USB_SPEEDTOUCH $CONFIG_ATM $CONFIG_USB fi endmenu diff -Nru a/drivers/usb/Makefile b/drivers/usb/Makefile --- a/drivers/usb/Makefile Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/Makefile Thu Jun 19 17:28:35 2003 @@ -19,6 +19,7 @@ hid-objs := hid-core.o pwc-objs := pwc-if.o pwc-misc.o pwc-ctrl.o pwc-uncompress.o auerswald-objs := auerbuf.o auerchain.o auerchar.o auermain.o +speedtch-objs := speedcrc.o speedtouch.o # Optional parts of multipart objects. @@ -106,6 +107,7 @@ obj-$(CONFIG_USB_RTL8150) += rtl8150.o obj-$(CONFIG_USB_CATC) += catc.o obj-$(CONFIG_USB_KAWETH) += kaweth.o +obj-$(CONFIG_USB_AX8817X) += ax8817x.o obj-$(CONFIG_USB_CDCETHER) += CDCEther.o obj-$(CONFIG_USB_RIO500) += rio500.o obj-$(CONFIG_USB_TIGL) += tiglusb.o @@ -117,6 +119,7 @@ obj-$(CONFIG_USB_AUERSWALD) += auerswald.o obj-$(CONFIG_USB_BRLVGER) += brlvger.o obj-$(CONFIG_USB_LCD) += usblcd.o +obj-$(CONFIG_USB_SPEEDTOUCH) += speedtch.o # Object files in subdirectories mod-subdirs := serial host @@ -147,3 +150,6 @@ auerswald.o: $(auerswald-objs) $(LD) -r -o $@ $(auerswald-objs) + +speedtch.o: $(speedtch-objs) + $(LD) -r -o $@ $(speedtch-objs) diff -Nru a/drivers/usb/aiptek.c b/drivers/usb/aiptek.c --- a/drivers/usb/aiptek.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/aiptek.c Thu Jun 19 17:28:35 2003 @@ -1,7 +1,9 @@ /* - * Native support for the Aiptek 8000U + * Native support for the Aiptek HyperPen USB Tablets + * (4000U/5000U/6000U/8000U/12000U) * - * Copyright (c) 2001 Chris Atenasio + * Copyright (c) 2001 Chris Atenasio + * Copyright (c) 2002-2003 Bryan W. Headley * * based on wacom.c by * Vojtech Pavlik @@ -11,13 +13,28 @@ * James E. Blair * Daniel Egger * - * * Many thanks to Oliver Kuechemann for his support. * * ChangeLog: * v0.1 - Initial release - * v0.2 - Hack to get around fake event 28's. + * v0.2 - Hack to get around fake event 28's. (Bryan W. Headley) * v0.3 - Make URB dynamic (Bryan W. Headley, Jun-8-2002) + * Released to Linux 2.4.19 and 2.5.x + * v0.4 - Rewrote substantial portions of the code to deal with + * corrected control sequences, timing, dynamic configuration, + * support of 6000U - 12000U, procfs, and macro key support + * (Jan-1-2003 - Feb-5-2003, Bryan W. Headley) + * v1.0 - Added support for diagnostic messages, count of messages + * received from URB - Mar-8-2003, Bryan W. Headley + * + * NOTE: + * This kernel driver is augmented by the "Aiptek" XFree86 input + * driver for your X server, as well as a GUI Front-end "Tablet Manager". + * These three products are highly interactive with one another, + * so therefore it's easier to document them all as one subsystem. + * Please visit the project's "home page", located at, + * http://aiptektablet.sourceforge.net. + * */ /* @@ -42,13 +59,15 @@ #include #include #include +#include +#include /* * Version Information */ -#define DRIVER_VERSION "v0.3" -#define DRIVER_AUTHOR "Chris Atenasio " -#define DRIVER_DESC "USB Aiptek 6000U/8000U tablet driver (Linux 2.4.x)" +#define DRIVER_VERSION "v1.0 Mar-8-2003" +#define DRIVER_AUTHOR "Bryan W. Headley/Chris Atenasio" +#define DRIVER_DESC "Aiptek HyperPen USB Tablet Driver (Linux 2.4.x)" MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); @@ -56,6 +75,7 @@ /* * Aiptek status packet: + * (returned as Report 1) * * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 * byte0 0 0 0 0 0 0 1 0 @@ -69,10 +89,14 @@ * * IR: In Range = Proximity on * DV = Data Valid + * BS = Barrel Switch (as in, macro keys) + * BS2 also referred to as Tablet Pick * - * * Command Summary: * + * Use report_type CONTROL (3) + * Use report_id 2 + * * Command/Data Description Return Bytes Return Value * 0x10/0x00 SwitchToMouse 0 * 0x10/0x01 SwitchToTablet 0 @@ -85,30 +109,278 @@ * 0x03/0x00 GetODMCode 2 ODMCode * 0x08/0x00 GetPressureLevels 2 =512 * 0x04/0x00 GetFirmwareVersion 2 Firmware Version + * 0x11/0x02 EnableMacroKeys 0 * * * To initialize the tablet: * - * (1) Send command Resolution500LPI - * (2) Option Commands (GetXExtension, GetYExtension) - * (3) Send command SwitchToTablet + * (1) Send Resolution500LPI (Command) + * (2) Query for Model code (Option Report) + * (3) Query for ODM code (Option Report) + * (4) Query for firmware (Option Report) + * (5) Query for GetXExtension (Option Report) + * (6) Query for GetYExtension (Option Report) + * (7) Query for GetPressureLevels (Option Report) + * (8) SwitchToTablet for Absolute coordinates, or + * SwitchToMouse for Relative coordinates (Command) + * (9) EnableMacroKeys (Command) + * (10) FilterOn (Command) + * (11) AutoGainOn (Command) + * + * (Step 9 can be omitted, but you'll then have no function keys.) + * + * The procfs interface + * -------------------- + * + * This driver supports delivering configuration/status reports + * through {procfs}/driver/usb/aiptek. ("procfs" is normally mounted + * to /proc.) Said file can be found while the driver is active in + * memory; it will be removed when the driver is removed, either + * through user intervention (rmmod aiptek) or through software + * such as "hotplug". + * + * Reading from the Procfs interface + * --------------------------------- + * + * The user may determine the status of the tablet by reading the + * report in the procfs interface, /proc/driver/usb/aiptek. + * The report as of driver version 1.0, looks like, + * + * Aiptek Tablet (3000x2250, 8.00x6.00", 202x152mm) + * (USB VendorID 0x08ca, ProductID 0x0020, ODMCode 0x0004 + * ModelCode: 0x64, FirmwareCode: 0x0400) + * on /dev/input/event0 + * pointer=either + * coordinate=absolute + * tool=pen + * xtilt=disable + * ytilt=disable + * jitter=50 + * diagnostic=none + * eventsReceived=0 + * + * (spurious ", for the benefit of vim's syntax highlighting.) + * + * This report indicates the tablet recognized. (Because Aiptek reuses + * the USB 'productID' over several tablets, it's pointless for us to + * guess which model you have: we'll instead tell you the size of + * the tablet's drawing area, which we indicate in coordinates, inches, + * and millimeters.) We also indicate datum read from the USB interface, + * such as vendorId, productId, ODMcode, etc. It's there "just in case." + * + * on /dev/input/event0 + * + * Linux supports HID-compliant USB devices (such as this tablet) by + * transposing their reports to the Linux Input Event System format. Which + * means, if you want to data from the tablet, that's where it will be + * made available from. For information on the Input Event System, see + * the docs in ./Documentation/input, in the kernel source tree. + * + * And yes, depending on the order in which other supported Input Event + * devices are recognized and configured, the tablet may be allocated + * to a different device driver name: it's all dynamic. Use of the devfs + * file system is a help. + * + * The keyword=value part of the report mostly shows what the programmable + * parameters have been set to. We describe those below, and how to + * program/reprogram them. Note: tablet parameters are to be programmed + * while the tablet is attached and active. They are not set as arguments + * to the kernel during bootup. + * + * Here are the "read-only" parameters, and what they mean: + * + * diagnostic=stringValue + * eventsReceived=numericValue + * + * diagnostic: The tablet driver attempts to explain why things are not + * working correctly. (To the best of it's insular abilities) + * + * By default, the tablet boots up in Relative Coordinate + * mode. This driver initially attempts to program it in Absolute + * Coordinate mode (and of course, the user can subsequently choose + * which mode they want.) So, therefore, the situation can arise + * where the tablet is in one mode, and the driver believes it + * is in the other mode. The driver, however, cannot divine + * this mismatch until input events are received. + * Two reports indicate such mode-mismatches between the tablet + * and the driver, and are, + * + * "tablet sending relative reports" + * "tablet sending absolute reports" + * + * The next diagnostic operates in conjunction with the "pointer=" + * programmable parameter. With it, you can indicate that you want + * the tablet to only accept reports from the stylus, or only from the + * mouse. (You can also specify to allow reports from either.) What + * happens when you specify that you only want mouse reports, yet + * the tablet keeps receiving reports from the stylus? Well, first, + * it's a "pilot error", but secondly, it tries to diagnose the issue + * with the following reports, + * + * "tablet seeing reports from stylus" + * "tablet seeing reports from mouse" + * + * What if there is nothing to report? The inference in the diagnostic + * reports is that something is happening which shouldn't: when things + * appear to be working right, the report is, + * + * "none" + * + * The error diagnostic report is dynamic: it only reports issues + * that are happening, or have happened as of the last event received. + * It will reset following any attempt to reprogram the tablet's mode. + * + * eventsReceived: Occasionally, your movements on the tablet are not being + * reported. Usually, this indicates that your tablet is out of sync + * with the USB interface driver, or itself is not sending reports + * out. To help diagnose this, we keep an active count of events + * received from the tablet. So, if you move the stylus, and yet + * your client application doesn't notice, make + * note of the eventsReceived, and then move the stylus again. If the + * event counter's number doesn't change, then the tablet indeed has + * "froze". + * + * We have found that sending the tablet a command sequence often + * will clear up "frozen" tablets. Which segues into the section + * about how to program your tablet through the procfs interface, + * + * Writing to the procfs interface + * ------------------------------- + * + * The user may configure the tablet by writing ASCII + * commands to the /proc/driver/usb/aiptek file. Commands which are + * accepted are, + * + * pointer=stringvalue {stylus|mouse|either} + * coordinate=stringvalue {absolute|relative} + * tool=stringvalue {mouse|rubber|pen|pencil|brush|airbrush} + * xtilt=string_or_numeric {disable|[-128..127]} + * ytilt=string_or_numeric {disable|[-128..127]} + * jitter=numericvalue {0..xxx} + * + * pointer: you can specify that reports are to be excepted ONLY from the + * stylus, or ONLY from the mouse. 'either' allows reports from either + * device to be accepted, and is the default. + * coordinate: you can specify that either absolute or relative coordinate + * reports are issued by the tablet. By default, absolute reports are + * sent. + * tool: The stylus by default prepends TOOL_BTN_PEN events with it's + * reports. But you may decide that you want your stylus to behave + * like an eraser (named 'rubber', following tablet conventions,) + * or a pencil, etc. The behavior is dependent upon the client software + * consuming the tablet's events, e.g., the XFree86 tablet driver. + * xtilt: By default this is disabled. However, other tablets have a notion + * of measuring the angle at which the stylus pen is held against the + * drawing surface, along the X axis. Aiptek tablets cannot sense this, + * but if you want to send "held-at-angle" reports, specify the value, + * an integer between -128 and 127 (inclusive) that you want to send. + * This data will be sent along with regular tablet input. Obviously, + * the inference here is that your hand does not change angles + * while drawing (until you go back to this procfs interface, and + * change the value)! + * + * When you consider actual drawing tools (real pens, brushes), + * knowing the tools' tip shape and the angle that you hold the tool + * becomes important, insofar as calculating the surface of the tip + * that actually touches the surface of the paper. Knowledge of what + * to do with xtilt reports is solely in the realm of your client + * software. + * + * Yes, there is a difference between xtilt=0 and xtilt=disable + * settings. The former sends a report that the angle is a 0; + * the other indicates that NO xtilt reports are to be sent at all. + * ytilt: By default this is disabled. This provides similar functionality + * to xtilt, except that we're measuring the angle the stylus pen is + * held against the drawing surface, along the Y axis. Same cavaets + * apply as for xtilt. + * jitter: By default, this is set to 50. When pressing a button on + * either the mouse or the stylus pen, you will probably notice that + * the tool moves slightly from it's original position, until your + * hand steadies it. During that period of time, the pen is "jittering", + * sending spurious movement events that perhaps you'd like it not to + * send. What we do is set a moratorium, measured in milliseconds, + * during which we do not send movement events. So, the default is 50ms; + * you obviously can set it to zero or incredibly unreasonable values + * (no reports for 4 seconds following the pressing of a stylus button!) + * + * Interesting Side-Note + * --------------------- + * + * The tablet has "frozen" and you'd like to send it a command to wake it + * up. But you don't want to change how the driver's currently configured. + * + * 1. Send a command to /proc/driver/usb/aiptek with the same setting + * already reported by the driver. + * 2. Send an illegal string to procfs file ("wakeup=now" is always good) + * 3. Because, the driver always attempts to reprogram the tablet to it's + * current settings following a write to the procfs interface. + * + * Hmm, still does not work. + * ------------------------- + * + * This is slightly harder to diagnose. You may be receiving frame errors + * from the USB interface driver (see /var/log/messages for any diagnostics). + * + * Alternatively, you may be running something like 'hotplug' that attempts + * to match discovered USB devices to it's list of device drivers. + * Unfortunately, because this is a tablet that can send relative X,Y events, + * it "looks like" a mouse! A usb mouse driver may have possession of + * input from the tablet. On the other hand, the tablet also supports + * absolute reports from barrel switches, which sounds a lot like a "joystick", + * and the software again can be fooled into loading the wrong driver for + * the tablet. The distinction is, USB HID devices tell you what they + * are capable of, rather than what they are. + * + * Come visit this driver's home page at http://aiptektablet.sourceforge.net + * for further assistance. */ #define USB_VENDOR_ID_AIPTEK 0x08ca +#define AIPTEK_POINTER_ONLY_MOUSE_MODE 0 +#define AIPTEK_POINTER_ONLY_STYLUS_MODE 1 +#define AIPTEK_POINTER_EITHER_MODE 2 + +#define AIPTEK_POINTER_ALLOW_MOUSE_MODE(a) \ + (a == AIPTEK_POINTER_ONLY_MOUSE_MODE || \ + a == AIPTEK_POINTER_EITHER_MODE) +#define AIPTEK_POINTER_ALLOW_STYLUS_MODE(a) \ + (a == AIPTEK_POINTER_ONLY_STYLUS_MODE || \ + a == AIPTEK_POINTER_EITHER_MODE) + +#define AIPTEK_COORDINATE_RELATIVE_MODE 0 +#define AIPTEK_COORDINATE_ABSOLUTE_MODE 1 + +#define AIPTEK_TILT_MIN (-128) +#define AIPTEK_TILT_MAX 127 +#define AIPTEK_TILT_DISABLE (-10101) + +#define AIPTEK_TOOL_BUTTON_PEN_MODE 0 +#define AIPTEK_TOOL_BUTTON_PENCIL_MODE 1 +#define AIPTEK_TOOL_BUTTON_BRUSH_MODE 2 +#define AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE 3 +#define AIPTEK_TOOL_BUTTON_RUBBER_MODE 4 +#define AIPTEK_TOOL_BUTTON_MOUSE_MODE 5 + +#define AIPTEK_DIAGNOSTIC_NA 0 +#define AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE 1 +#define AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE 2 +#define AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED 3 + + // Time to wait (in ms) to help mask hand jittering + // when pressing the stylus buttons. +#define AIPTEK_JITTER_DELAY_DEFAULT 50 + struct aiptek_features { char *name; int pktlen; int x_max; int y_max; - int pressure_min; int pressure_max; + int odmCode; + int modelCode; + int firmwareCode; void (*irq) (struct urb * urb); - unsigned long evbit; - unsigned long absbit; - unsigned long relbit; - unsigned long btnbit; - unsigned long digibit; }; struct aiptek { @@ -117,59 +389,385 @@ struct usb_device *usbdev; struct urb *irq; struct aiptek_features *features; - int tool; - int open; + unsigned int ifnum; + int open_count; + int pointer_mode; + int coordinate_mode; + int tool_mode; + int xTilt; + int yTilt; + int diagnostic; + unsigned long eventCount; + int jitterDelay; +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *usbProcfsEntry; + struct proc_dir_entry *aiptekProcfsEntry; +#endif }; +/* + * Permit easy lookup of keyboard events to send, versus + * the bitmap which comes from the tablet. This hides the + * issue that the F_keys are not sequentially numbered. + */ +static int macroKeyEvents[] = { KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, + KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, + KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, + KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, KEY_F24, + KEY_STOP, KEY_AGAIN, KEY_PROPS, KEY_UNDO, KEY_FRONT, KEY_COPY, + KEY_OPEN, KEY_PASTE, 0 +}; + +#ifdef CONFIG_PROC_FS +extern struct proc_dir_entry *proc_root_driver; +#endif + +static int +aiptek_convert_from_2s_complement(unsigned char c) +{ + unsigned char b = c; + int negate = 0; + int ret; + + if (b & 0x80) { + b = ~b; + b--; + negate = 1; + } + ret = b; + ret = (negate == 1) ? -ret : ret; + return ret; +} + +/* + * aiptek_irq can receive one of six potential reports. + * The documentation for each is in the body of the function. + * + * The tablet reports on several attributes per invocation of + * aiptek_irq. Because the Linux Input Event system allows the + * transmission of ONE attribute per input_report_xxx() call, + * collation has to be done on the other end to reconstitute + * a complete tablet report. Further, the number of Input Event reports + * submitted varies, depending on what USB report type, and circumstance. + * To deal with this, EV_MSC is used to indicate an 'end-of-report' + * message. This has been an undocumented convention understood by the kernel + * tablet driver and clients such as gpm and XFree86's tablet drivers. + * + * Of the information received from the tablet, the one piece I + * cannot transmit is the proximity bit (without resorting to an EV_MSC + * convention above.) I therefore have taken over REL_MISC and ABS_MISC + * (for relative and absolute reports, respectively) for communicating + * Proximity. Why two events? I thought it interesting to know if the + * Proximity event occured while the tablet was in absolute or relative + * mode. + * + * Other tablets use the notion of a certain minimum stylus pressure + * to infer proximity. While that could have been done, that is yet + * another 'by convention' behavior, the documentation for which + * would be spread between two (or more) pieces of software. + * + * EV_MSC usage is terminated in Linux 2.5.x. + */ + static void aiptek_irq(struct urb *urb) { struct aiptek *aiptek = urb->context; unsigned char *data = aiptek->data; struct input_dev *dev = &aiptek->dev; - int x; - int y; - int pressure; - int proximity; + int jitterable = 0; if (urb->status) return; - if ((data[0] & 2) == 0) { - dbg("received unknown report #%d", data[0]); - } - - proximity = data[5] & 0x01; - input_report_key(dev, BTN_TOOL_PEN, proximity); + aiptek->eventCount++; - x = ((__u32) data[1]) | ((__u32) data[2] << 8); - y = ((__u32) data[3]) | ((__u32) data[4] << 8); - pressure = ((__u32) data[6]) | ((__u32) data[7] << 8); - pressure -= aiptek->features->pressure_min; + // Report 1 delivers relative coordinates with either a stylus + // or the mouse. You do not know which tool generated the event. + if (data[0] == 1) { + if (aiptek->coordinate_mode == AIPTEK_COORDINATE_ABSOLUTE_MODE) { + aiptek->diagnostic = + AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE; + } else { + int x, y, left, right, middle; + + if (aiptek->tool_mode != AIPTEK_TOOL_BUTTON_MOUSE_MODE) { + aiptek->tool_mode = + AIPTEK_TOOL_BUTTON_MOUSE_MODE; + input_report_key(dev, BTN_TOOL_MOUSE, 1); + } + x = aiptek_convert_from_2s_complement(data[2]); + y = aiptek_convert_from_2s_complement(data[3]); + + left = data[5] & 0x01; + right = data[5] & 0x02; + middle = data[5] & 0x04; + + jitterable = left | right | middle; + + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_MIDDLE, middle); + input_report_key(dev, BTN_RIGHT, right); + input_report_rel(dev, REL_X, x); + input_report_rel(dev, REL_Y, y); + input_report_rel(dev, REL_MISC, 1); - if (pressure < 0) { - pressure = 0; + input_event(dev, EV_MSC, MSC_SERIAL, 0); + } } - - if (proximity) { - input_report_abs(dev, ABS_X, x); - input_report_abs(dev, ABS_Y, y); - input_report_abs(dev, ABS_PRESSURE, pressure); - input_report_key(dev, BTN_TOUCH, data[5] & 0x04); - input_report_key(dev, BTN_STYLUS, data[5] & 0x08); - input_report_key(dev, BTN_STYLUS2, data[5] & 0x10); + // Report 2 is delivered only by the stylus, and delivers + // absolute coordinates. + else if (data[0] == 2) { + if (aiptek->coordinate_mode == AIPTEK_COORDINATE_RELATIVE_MODE) { + aiptek->diagnostic = + AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE; + } else + if (!AIPTEK_POINTER_ALLOW_STYLUS_MODE(aiptek->pointer_mode)) + { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED; + } else { + int x = ((__u32) data[1]) | ((__u32) data[2] << 8); + int y = ((__u32) data[3]) | ((__u32) data[4] << 8); + int z = ((__u32) data[6]) | ((__u32) data[7] << 8); + + int p = data[5] & 0x01; + int dv = data[5] & 0x02; + int tip = data[5] & 0x04; + int bs = data[5] & 0x08; + int pck = data[5] & 0x10; + + // dv indicates 'data valid' (e.g., the tablet is in sync + // and has delivered a "correct" report) We will ignore + // all 'bad' reports... + if (dv != 0) { + switch (aiptek->tool_mode) { + case AIPTEK_TOOL_BUTTON_PEN_MODE: + { + input_report_key(dev, + BTN_TOOL_PEN, + 1); + } + break; + + case AIPTEK_TOOL_BUTTON_PENCIL_MODE: + { + input_report_key(dev, + BTN_TOOL_PENCIL, + 1); + } + break; + + case AIPTEK_TOOL_BUTTON_BRUSH_MODE: + { + input_report_key(dev, + BTN_TOOL_BRUSH, + 1); + } + break; + + case AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE: + { + input_report_key(dev, + BTN_TOOL_AIRBRUSH, + 1); + } + break; + + case AIPTEK_TOOL_BUTTON_RUBBER_MODE: + { + input_report_key(dev, + BTN_TOOL_RUBBER, + 1); + } + break; + + case AIPTEK_TOOL_BUTTON_MOUSE_MODE: + { + input_report_key(dev, + BTN_TOOL_MOUSE, + 1); + } + break; + } + + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + + /* + * The user is allowed to switch from one of the + * stylus tools to the Mouse using the front-end GUI. + * An issue that will arise, however, is what happens + * when the user HAS issued a TOOL_BTN_MOUSE, but has not + * yet swapped tools. Well, we can "pretend" to be a mouse + * by sending overriding tip, barrelswitch and pick. + * This stupidity should not be used as an excuse not + * to physically move your Aiptek mouse into the tablet's + * active area -- it merely provides momentary convenience + * during that transition. + */ + if (aiptek->tool_mode == + AIPTEK_TOOL_BUTTON_MOUSE_MODE) { + input_report_key(dev, BTN_LEFT, tip); + input_report_key(dev, BTN_RIGHT, bs); + input_report_key(dev, BTN_MIDDLE, pck); + + jitterable = tip | bs | pck; + } else { + input_report_abs(dev, ABS_PRESSURE, z); + + input_report_key(dev, BTN_TOUCH, tip); + input_report_key(dev, BTN_STYLUS, bs); + input_report_key(dev, BTN_STYLUS2, pck); + + jitterable = tip | bs | pck; + + if (aiptek->xTilt != + AIPTEK_TILT_DISABLE) + input_report_abs(dev, + ABS_TILT_X, + aiptek->xTilt); + if (aiptek->yTilt != + AIPTEK_TILT_DISABLE) + input_report_abs(dev, + ABS_TILT_Y, + aiptek->yTilt); + } + input_report_abs(dev, ABS_MISC, p); + input_event(dev, EV_MSC, MSC_SERIAL, 0); + } + } + } + // Report 3's come from the mouse in absolute mode. + else if (data[0] == 3) { + if (aiptek->coordinate_mode == AIPTEK_COORDINATE_RELATIVE_MODE) { + aiptek->diagnostic = + AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE; + } else + if (!AIPTEK_POINTER_ALLOW_MOUSE_MODE(aiptek->pointer_mode)) + { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED; + } else { + int x = ((__u32) data[1]) | ((__u32) data[2] << 8); + int y = ((__u32) data[3]) | ((__u32) data[4] << 8); + int p = data[5] & 0x01; + int dv = data[5] & 0x02; + int left = data[5] & 0x04; + int right = data[5] & 0x08; + int middle = data[5] & 0x10; + + if (dv != 0) { + input_report_key(dev, BTN_TOOL_MOUSE, 1); + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_MIDDLE, middle); + input_report_key(dev, BTN_RIGHT, right); + + jitterable = left | middle | right; + + input_report_rel(dev, REL_MISC, p); + input_event(dev, EV_MSC, MSC_SERIAL, 0); + } + } + } + // Report 4s come from the macro keys when pressed by stylus + else if (data[0] == 4) { + int p = data[1] & 0x01; + int dv = data[1] & 0x02; + int tip = data[1] & 0x04; + int bs = data[1] & 0x08; + int pck = data[1] & 0x10; + + int m = data[3]; + int z = ((__u32) data[4]) | ((__u32) data[5] << 8); + + if (dv != 0) { + input_report_key(dev, BTN_TOUCH, tip); + input_report_key(dev, BTN_STYLUS, bs); + input_report_key(dev, BTN_STYLUS2, pck); + + jitterable = tip | bs | pck; + + input_report_key(dev, macroKeyEvents[m - 1], 1); + input_report_abs(dev, ABS_PRESSURE, z); + input_report_abs(dev, ABS_MISC, p); + input_event(dev, EV_MSC, MSC_SERIAL, 0); + } + } + // Report 5s come from the macro keys when pressed by mouse + else if (data[0] == 5) { + int p = data[1] & 0x01; + int dv = data[1] & 0x02; + int left = data[1] & 0x04; + int right = data[1] & 0x08; + int middle = data[1] & 0x10; + int macro = data[3]; + + if (dv != 0) { + input_report_key(dev, BTN_LEFT, left); + input_report_key(dev, BTN_MIDDLE, middle); + input_report_key(dev, BTN_RIGHT, right); + + jitterable = left | middle | right; + + input_report_key(dev, macroKeyEvents[macro - 1], 1); + input_report_rel(dev, ABS_MISC, p); + input_event(dev, EV_MSC, MSC_SERIAL, 0); + } + } + // We have no idea which tool can generate a report 6. Theoretically, + // neither need to, having been given reports 4 & 5 for such use. + // However, report 6 is the 'official-looking' report for macroKeys; + // reports 4 & 5 supposively are used to support unnamed, unknown + // hat switches (which just so happen to be the macroKeys.) + else if (data[0] == 6) { + int macro = ((__u32) data[1]) | ((__u32) data[2] << 8); + + input_report_key(dev, macroKeyEvents[macro - 1], 1); + input_report_abs(dev, ABS_MISC, 1); + input_event(dev, EV_MSC, MSC_SERIAL, 0); + } else { + dbg("Unknown report %d", data[0]); } + // Jitter may occur when the user presses a button on the stlyus + // or the mouse. What we do to prevent that is wait 'x' milliseconds + // following a 'jitterable' event, which should give the hand some time + // stabilize itself. + if (jitterable != 0 && aiptek->jitterDelay != 0) { + wait_ms(aiptek->jitterDelay); + } } +/* + * We are not able to reliably determine the tablet featureset by + * asking for the USB productID. Therefore, we will query the + * tablet dynamically and populate the struct in aiptek_probe(). + */ + struct aiptek_features aiptek_features[] = { - {"Aiptek 6000U/8000U", - 8, 3000, 2250, 26, 511, aiptek_irq, 0, 0, 0, 0}, + {"Aiptek", 8, 0, 0, 0, 0, 0, 0, aiptek_irq}, {NULL, 0} }; +/* + * These are the USB id's known so far. We do not identify them to + * specific Aiptek model numbers, because there has been overlaps, + * use, and reuse of id's in existing models. Certain models have + * been known to use more than one ID, indicative perhaps of + * manufacturing revisions. In any event, we consider these + * IDs to not be model-specific nor unique. + */ + struct usb_device_id aiptek_ids[] = { - {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x20), driver_info:0}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x01), driver_info:0}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x10), driver_info:0}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x20), driver_info:0}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x21), driver_info:0}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x22), driver_info:0}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x23), driver_info:0}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x24), driver_info:0}, {} }; @@ -179,8 +777,7 @@ aiptek_open(struct input_dev *dev) { struct aiptek *aiptek = dev->private; - - if (aiptek->open++) + if (aiptek->open_count++) return 0; aiptek->irq->dev = aiptek->usbdev; @@ -195,31 +792,416 @@ { struct aiptek *aiptek = dev->private; - if (!--aiptek->open) + if (!--aiptek->open_count) usb_unlink_urb(aiptek->irq); } +/* + * Send a command to the tablet. No reply is expected. + */ static void aiptek_command(struct usb_device *dev, unsigned int ifnum, unsigned char command, unsigned char data) { __u8 buf[3]; - buf[0] = 4; + buf[0] = 2; + buf[1] = command; + buf[2] = data; + + if (usb_set_report(dev, ifnum, 3, 2, buf, sizeof (buf)) != sizeof (buf)) { + dbg("aiptek_command failed, sending: 0x%02x 0x%02x", command, + data); + } +} + +/* + * Send a query to the tablet. This is done by sending the query stream + * first as a command, waiting a few milliseconds, then submitting the + * same stream as a query. + */ +static unsigned int +aiptek_query(struct usb_device *dev, unsigned int ifnum, + unsigned char command, unsigned char data) +{ + unsigned int ret; + __u8 buf[8]; + buf[0] = 2; buf[1] = command; buf[2] = data; - if (usb_set_report(dev, ifnum, 3, 2, buf, 3) != 3) { - dbg("aiptek_command: 0x%x 0x%x\n", command, data); + aiptek_command(dev, ifnum, command, data); + wait_ms(400); + + if (usb_get_report(dev, ifnum, 3, 2, buf, 3) < 3) { + dbg("aiptek_query failed: returns 0x%02x 0x%02x 0x%02x", + buf[0], buf[1], buf[2]); + return 0; + } + ret = ((__u32) buf[1]) | ((__u32) buf[2] << 8); + return ret; +} + +/* + * Program the tablet into either absolute or relative mode. + * + * We also get information about the tablet's size. + */ +static void +aiptek_program_tablet(struct aiptek *aiptek) +{ + int modelCode, odmCode, firmwareCode; + int xResolution, yResolution, zResolution; + + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_NA; + + // execute Resolution500LPI + aiptek_command(aiptek->usbdev, aiptek->ifnum, 0x18, 0x04); + // query getModelCode + modelCode = aiptek_query(aiptek->usbdev, aiptek->ifnum, 0x02, 0x00); + // query getODMCode + odmCode = aiptek_query(aiptek->usbdev, aiptek->ifnum, 0x03, 0x00); + // query getFirmwareCode + firmwareCode = aiptek_query(aiptek->usbdev, aiptek->ifnum, 0x04, 0x00); + // query getXextension + xResolution = aiptek_query(aiptek->usbdev, aiptek->ifnum, 0x01, 0x00); + // query getYextension + yResolution = aiptek_query(aiptek->usbdev, aiptek->ifnum, 0x01, 0x01); + // query getPressureLevels + zResolution = aiptek_query(aiptek->usbdev, aiptek->ifnum, 0x08, 0x00); + + // Depending on whether we are in absolute or relative mode, we will + // do a switchToTablet(absolute) or switchToMouse(relative) command. + if (aiptek->coordinate_mode == AIPTEK_COORDINATE_ABSOLUTE_MODE) { + // execute switchToTablet + aiptek_command(aiptek->usbdev, aiptek->ifnum, 0x10, 0x01); + } else { + // execute switchToMouse + aiptek_command(aiptek->usbdev, aiptek->ifnum, 0x10, 0x00); + } + // This command enables the macro keys + aiptek_command(aiptek->usbdev, aiptek->ifnum, 0x11, 0x02); + // execute FilterOn + aiptek_command(aiptek->usbdev, aiptek->ifnum, 0x17, 0x00); + // execute AutoGainOn + aiptek_command(aiptek->usbdev, aiptek->ifnum, 0x12, 0xff); + + aiptek->features->odmCode = odmCode; + aiptek->features->modelCode = modelCode & 0xff; + aiptek->features->firmwareCode = firmwareCode; + aiptek->features->pressure_max = zResolution; + aiptek->features->x_max = xResolution; + aiptek->features->y_max = yResolution; + + aiptek->eventCount = 0; +} + +#if defined(CONFIG_PROC_FS) +/* + * This routine determines keywords and their associated values, and + * maps them to supported modes in this driver. It's input comes from + * aiptek_procfs_write(). + */ +static void +aiptek_procfs_parse(struct aiptek *aiptek, char *keyword, char *value) +{ + if (strcmp(keyword, "pointer") == 0) { + if (strcmp(value, "stylus") == 0) { + aiptek->pointer_mode = AIPTEK_POINTER_ONLY_STYLUS_MODE; + } else if (strcmp(value, "mouse") == 0) { + aiptek->pointer_mode = AIPTEK_POINTER_ONLY_MOUSE_MODE; + } else if (strcmp(value, "either") == 0) { + aiptek->pointer_mode = AIPTEK_POINTER_EITHER_MODE; + } + } else if (strcmp(keyword, "coordinate") == 0) { + if (strcmp(value, "relative") == 0) { + aiptek->coordinate_mode = + AIPTEK_COORDINATE_RELATIVE_MODE; + } else if (strcmp(value, "absolute") == 0) { + aiptek->coordinate_mode = + AIPTEK_COORDINATE_ABSOLUTE_MODE; + } + } else if (strcmp(keyword, "xtilt") == 0) { + if (strcmp(value, "disable") == 0) { + aiptek->xTilt = AIPTEK_TILT_DISABLE; + } else { + int x = (int) simple_strtol(value, 0, 10); + if (x >= AIPTEK_TILT_MIN && x <= AIPTEK_TILT_MAX) + aiptek->xTilt = x; + } + } else if (strcmp(keyword, "ytilt") == 0) { + if (strcmp(value, "disable") == 0) { + aiptek->yTilt = AIPTEK_TILT_DISABLE; + } else { + int y = (int) simple_strtol(value, 0, 10); + if (y >= AIPTEK_TILT_MIN && y <= AIPTEK_TILT_MAX) + aiptek->yTilt = y; + } + } else if (strcmp(keyword, "jitter") == 0) { + aiptek->jitterDelay = (int) simple_strtol(value, 0, 10); + } else if (strcmp(keyword, "tool") == 0) { + if (strcmp(value, "mouse") == 0) { + aiptek->tool_mode = AIPTEK_TOOL_BUTTON_MOUSE_MODE; + } else if (strcmp(value, "rubber") == 0) { + aiptek->tool_mode = AIPTEK_TOOL_BUTTON_RUBBER_MODE; + } else if (strcmp(value, "pencil") == 0) { + aiptek->tool_mode = AIPTEK_TOOL_BUTTON_PENCIL_MODE; + } else if (strcmp(value, "pen") == 0) { + aiptek->tool_mode = AIPTEK_TOOL_BUTTON_PEN_MODE; + } else if (strcmp(value, "brush") == 0) { + aiptek->tool_mode = AIPTEK_TOOL_BUTTON_BRUSH_MODE; + } else if (strcmp(value, "airbrush") == 0) { + aiptek->tool_mode = AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE; + } + } +} + +/* + * This routine reads the status of the aiptek driver, and makes it + * available as a procfs file. The description of the procfs file + * is at the top of this driver source code. + */ +static int +aiptek_procfs_read(char *page, char **start, off_t offset, int count, + int *eof, void *data) +{ + int len; + char *out = page; + struct aiptek *aiptek = data; + + int inchX = aiptek->features->x_max / 375 * 100; + int inchY = aiptek->features->y_max / 375 * 100; + int mmX = (int) ((double) aiptek->features->x_max / 14.8); + int mmY = (int) ((double) aiptek->features->y_max / 14.8); + + out += + sprintf(out, "Aiptek Tablet (%dx%d, %d.%02dx%d.%02d\", %dx%dmm)\n", + aiptek->features->x_max, aiptek->features->y_max, + inchX / 100, inchX % 100, inchY / 100, inchY % 100, mmX, + mmY); + + out += + sprintf(out, + "(USB VendorID 0x%04x, ProductID 0x%04x, ODMCode 0x%04x\n", + aiptek->dev.idvendor, aiptek->dev.idproduct, + aiptek->features->odmCode); + out += + sprintf(out, " ModelCode: 0x%02x, FirmwareCode: 0x%04x)\n", + aiptek->features->modelCode, + aiptek->features->firmwareCode); + + out += sprintf(out, "on /dev/input/event%d\n", aiptek->dev.number); + out += sprintf(out, "pointer=%s\n", + (aiptek->pointer_mode == AIPTEK_POINTER_ONLY_MOUSE_MODE + ? "mouse" + : (aiptek->pointer_mode == + AIPTEK_POINTER_ONLY_STYLUS_MODE ? "stylus" : + "either"))); + out += + sprintf(out, "coordinate=%s\n", + (aiptek->coordinate_mode == + AIPTEK_COORDINATE_RELATIVE_MODE ? "relative" : + "absolute")); + + out += sprintf(out, "tool="); + switch (aiptek->tool_mode) { + case AIPTEK_TOOL_BUTTON_MOUSE_MODE: + out += sprintf(out, "mouse\n"); + break; + + case AIPTEK_TOOL_BUTTON_RUBBER_MODE: + out += sprintf(out, "rubber\n"); + break; + + case AIPTEK_TOOL_BUTTON_PEN_MODE: + out += sprintf(out, "pen\n"); + break; + + case AIPTEK_TOOL_BUTTON_PENCIL_MODE: + out += sprintf(out, "pencil\n"); + break; + + case AIPTEK_TOOL_BUTTON_BRUSH_MODE: + out += sprintf(out, "brush\n"); + break; + + case AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE: + out += sprintf(out, "airbrush\n"); + break; } + + out += sprintf(out, "xtilt="); + if (aiptek->xTilt == AIPTEK_TILT_DISABLE) { + out += sprintf(out, "disable\n"); + } else { + out += sprintf(out, "%d\n", aiptek->xTilt); + } + + out += sprintf(out, "ytilt="); + if (aiptek->yTilt == AIPTEK_TILT_DISABLE) { + out += sprintf(out, "disable\n"); + } else { + out += sprintf(out, "%d\n", aiptek->yTilt); + } + + out += sprintf(out, "jitter=%d\n", aiptek->jitterDelay); + + out += sprintf(out, "diagnostic="); + switch (aiptek->diagnostic) { + case AIPTEK_DIAGNOSTIC_NA: + out += sprintf(out, "none\n"); + break; + case AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE: + out += sprintf(out, "tablet sending relative reports\n"); + break; + case AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE: + out += sprintf(out, "tablet sending absolute reports\n"); + break; + case AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED: + out += sprintf(out, "tablet seeing reports from "); + if (aiptek->pointer_mode == AIPTEK_POINTER_ONLY_MOUSE_MODE) + out += sprintf(out, "stylus\n"); + else + out += sprintf(out, "mouse\n"); + break; + } + + out += sprintf(out, "eventsReceived=%lu\n", aiptek->eventCount); + + len = out - page; + len -= offset; + if (len < count) { + *eof = 1; + if (len <= 0) { + return 0; + } + } else { + len = count; + } + + *start = page + offset; + + return len; } -static void* +/* + * This routine permits the setting of driver parameters through a + * procfs file. Writing to the procfs file (/proc/driver/usb/aiptek), + * you can program the tablet's behavior. Parameters that can be programmed + * (and their legal values) are described at the top of this driver. + * + * + * This parser is order-insensitive, and supports one or many parameters + * to be sent in one write request. As many parameters as you may fit + * in 64 bytes; we only require that you separate them with \n's. + * + * Any command that is not understood by the parser is silently ignored. + */ +static int +aiptek_procfs_write(struct file *file, const char *buffer, unsigned long count, + void *data) +{ + char buf[64]; + char *scan; + char *keyword = NULL; + char *value = NULL; + struct aiptek *aiptek = data; + int num; + + num = (count < 64) ? count : 64; + copy_from_user(buf, buffer, num); + buf[num] = '\0'; + + scan = buf; + while (*scan) { + if (*scan == '\n' || *scan == '\0') { + if (*scan == '\n') { + *scan = '\0'; + scan++; + } + if (keyword && value) { + aiptek_procfs_parse(aiptek, keyword, value); + } + keyword = NULL; + value = NULL; + continue; + } + + if (*scan != '=' && keyword == NULL) { + keyword = scan; + } else if (*scan == '=') { + *scan++ = '\0'; + value = scan; + } + scan++; + } + // We're insensitive as to whether the buffer ended in a \n or not. + if (keyword && value) { + aiptek_procfs_parse(aiptek, keyword, value); + } + + aiptek_program_tablet(aiptek); + + return num; +} + +/* + * This routine destroys our procfs device interface. This will occur + * when you remove the driver, either through rmmod or the hotplug system. + */ +static void +destroy_procfs_file(struct aiptek *aiptek) +{ + if (aiptek->aiptekProcfsEntry) + remove_proc_entry("aiptek", aiptek->usbProcfsEntry); + if (aiptek->usbProcfsEntry) + remove_proc_entry("usb", proc_root_driver); + + aiptek->usbProcfsEntry = NULL; + aiptek->aiptekProcfsEntry = NULL; +} + +/* + * This routine builds the procfs file. The file is located at, + * procfs/driver/usb/aiptek. + */ +static void +create_procfs_file(struct aiptek *aiptek) +{ + // Make procfs/driver/usb directory + aiptek->usbProcfsEntry = create_proc_entry("usb", S_IFDIR, + proc_root_driver); + if (!aiptek->usbProcfsEntry) { + dbg("create_procfs_file failed; no procfs/driver/usb control file."); + destroy_procfs_file(aiptek); + return; + } + aiptek->usbProcfsEntry->owner = THIS_MODULE; + + // Make procfs/driver/usb/aiptek file + aiptek->aiptekProcfsEntry = create_proc_entry("aiptek", + S_IFREG | S_IRUGO | + S_IWUGO, + aiptek->usbProcfsEntry); + if (!aiptek->aiptekProcfsEntry) { + dbg("create_procfs_file failed; no procfs/driver/usb control file."); + destroy_procfs_file(aiptek); + return; + } + aiptek->aiptekProcfsEntry->owner = THIS_MODULE; + aiptek->aiptekProcfsEntry->data = aiptek; + aiptek->aiptekProcfsEntry->read_proc = aiptek_procfs_read; + aiptek->aiptekProcfsEntry->write_proc = aiptek_procfs_write; +} +#endif + +static void * aiptek_probe(struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id) { struct usb_endpoint_descriptor *endpoint; struct aiptek *aiptek; + int i; if (!(aiptek = kmalloc(sizeof (struct aiptek), GFP_KERNEL))) return NULL; @@ -231,37 +1213,78 @@ kfree(aiptek); return NULL; } + // This used to be meaningful, when we had a matrix of + // different models with statically-assigned different + // features. Now we ask the tablet about everything. + + aiptek->features = aiptek_features; + + // Reset the tablet. The tablet boots up in 'SwitchtoMouse' + // mode, which indicates relative coordinates. 'SwitchToTablet' + // infers absolute coordinates. (Ergo, mice are inferred to be + // relative-only devices, which is not true. A misnomer.) + // The routine we use, aiptek_program_tablet, has been generalized + // enough such that it's callable through the procfs interface. + // This is why we use struct aiptek throughout. + aiptek->usbdev = dev; + aiptek->ifnum = ifnum; + aiptek->pointer_mode = AIPTEK_POINTER_EITHER_MODE; + aiptek->coordinate_mode = AIPTEK_COORDINATE_ABSOLUTE_MODE; + aiptek->tool_mode = AIPTEK_TOOL_BUTTON_PEN_MODE; + aiptek->xTilt = AIPTEK_TILT_DISABLE; + aiptek->yTilt = AIPTEK_TILT_DISABLE; + aiptek->jitterDelay = AIPTEK_JITTER_DELAY_DEFAULT; + +#ifdef CONFIG_PROC_FS + create_procfs_file(aiptek); +#endif + + aiptek_program_tablet(aiptek); + + aiptek->dev.evbit[0] |= BIT(EV_KEY) + | BIT(EV_ABS) + | BIT(EV_MSC); + + aiptek->dev.absbit[0] |= BIT(ABS_X) + | BIT(ABS_Y) + | BIT(ABS_PRESSURE) + | BIT(ABS_TILT_X) + | BIT(ABS_TILT_Y) + | BIT(ABS_MISC); + + aiptek->dev.relbit[0] |= BIT(REL_X) + | BIT(REL_Y) + | BIT(REL_MISC); + + // Set the macro keys up. They are discontiguous, so it's better + // to set the bitmask this way. + + for (i = 0; i < sizeof (macroKeyEvents) / sizeof (macroKeyEvents[0]); + ++i) { + set_bit(macroKeyEvents[i], aiptek->dev.keybit); + } - // Resolution500LPI - aiptek_command(dev, ifnum, 0x18, 0x04); - - // SwitchToTablet - aiptek_command(dev, ifnum, 0x10, 0x01); - - aiptek->features = aiptek_features + id->driver_info; - - aiptek->dev.evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_MSC) | - aiptek->features->evbit; - - aiptek->dev.absbit[0] |= BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE) | - BIT(ABS_MISC) | aiptek->features->absbit; - - aiptek->dev.relbit[0] |= aiptek->features->relbit; - - aiptek->dev.keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT) | - BIT(BTN_MIDDLE) | aiptek->features->btnbit; - - aiptek->dev.keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_PEN) | - BIT(BTN_TOOL_MOUSE) | BIT(BTN_TOUCH) | - BIT(BTN_STYLUS) | BIT(BTN_STYLUS2) | aiptek->features->digibit; + aiptek->dev.keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) + | BIT(BTN_RIGHT) + | BIT(BTN_MIDDLE); + + aiptek->dev.keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_PEN) + | BIT(BTN_TOOL_RUBBER) + | BIT(BTN_TOOL_PENCIL) + | BIT(BTN_TOOL_AIRBRUSH) + | BIT(BTN_TOOL_BRUSH) + | BIT(BTN_TOOL_MOUSE) + | BIT(BTN_TOUCH) + | BIT(BTN_STYLUS) + | BIT(BTN_STYLUS2); aiptek->dev.mscbit[0] = BIT(MSC_SERIAL); aiptek->dev.absmax[ABS_X] = aiptek->features->x_max; aiptek->dev.absmax[ABS_Y] = aiptek->features->y_max; - aiptek->dev.absmax[ABS_PRESSURE] = aiptek->features->pressure_max - - aiptek->features->pressure_min; - + aiptek->dev.absmax[ABS_PRESSURE] = aiptek->features->pressure_max; + aiptek->dev.absmax[ABS_TILT_X] = AIPTEK_TILT_MAX; + aiptek->dev.absmax[ABS_TILT_Y] = AIPTEK_TILT_MAX; aiptek->dev.absfuzz[ABS_X] = 0; aiptek->dev.absfuzz[ABS_Y] = 0; @@ -278,28 +1301,31 @@ endpoint = dev->config[0].interface[ifnum].altsetting[0].endpoint + 0; - FILL_INT_URB(aiptek->irq, - dev, - usb_rcvintpipe(dev, endpoint->bEndpointAddress), - aiptek->data, - aiptek->features->pktlen, - aiptek->features->irq, - aiptek, - endpoint->bInterval); + usb_fill_int_urb(aiptek->irq, + dev, + usb_rcvintpipe(dev, endpoint->bEndpointAddress), + aiptek->data, + aiptek->features->pktlen, + aiptek->features->irq, aiptek, endpoint->bInterval); input_register_device(&aiptek->dev); printk(KERN_INFO "input%d: %s on usb%d:%d.%d\n", - aiptek->dev.number, aiptek->features->name, dev->bus->busnum, - dev->devnum, ifnum); + aiptek->dev.number, + aiptek->features->name, dev->bus->busnum, dev->devnum, ifnum); return aiptek; } +static struct usb_driver aiptek_driver; + static void aiptek_disconnect(struct usb_device *dev, void *ptr) { struct aiptek *aiptek = ptr; +#ifdef CONFIG_PROC_FS + destroy_procfs_file(aiptek); +#endif usb_unlink_urb(aiptek->irq); input_unregister_device(&aiptek->dev); usb_free_urb(aiptek->irq); @@ -317,7 +1343,7 @@ aiptek_init(void) { usb_register(&aiptek_driver); - info(DRIVER_VERSION " " DRIVER_AUTHOR); + info(DRIVER_VERSION ": " DRIVER_AUTHOR); info(DRIVER_DESC); return 0; } diff -Nru a/drivers/usb/ax8817x.c b/drivers/usb/ax8817x.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/ax8817x.c Thu Jun 19 17:28:36 2003 @@ -0,0 +1,1287 @@ +/* + * ASIX AX8817x USB 2.0 10/100/HomePNA Ethernet controller driver + * + * $Id: ax8817x.c,v 1.15 2003/06/15 18:45:21 dhollis Exp $ + * + * Copyright (c) 2002-2003 TiVo Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * History + * + * 2003-06-12 - Dave Hollis 1.0.1 + * * use usb_make_path for ethtool info + * * Use crc32.h for crc functions + * * Additional callback cases for other host ctrlrs + * * Force minimum default rcv buffers + * + * 2003-06-12 - Dave Hollis 1.0.0 + * * Backport removal of multiple tx_urb / lengthy + * start_xmit routines, etc. + * * ethtool driver name returns driver name, not + * long description + * + * 2003-06-05 - Dave Hollis 0.9.9 + * * Cleanup unnecessary #if 0 + * * Fix coding style to match kernel style + * + * 2003-05-31 - Dave Hollis 0.9.8 + * * Don't stop/start the queue in start_xmit + * * Swallow URB status upon hard removal + * * Cleanup remaining comments (kill // style) + * + * 2003-05-29 - Dave Hollis 0.9.7 + * * Set module owner + * * Follow-up on suggestions from David Brownell & + * Oliver Neukum which should help with robustness + * * Use ether_crc from stock kernel if available + * + * 2003-05-28 - Dave Hollis 0.9.6 + * * Added basic ethtool & mii support + * + * 2003-05-28 - Dave Hollis 0.9.5 + * * Workout devrequest change to usb_ctrlrequest structure + * * Replace FILL_BULK_URB macros to non-deprecated + * usb_fill_bulk_urb macros + * * Replace printks with equivalent macros + * * Use defines for module description, version, author to + * simplify future changes + * + * Known Issues + * usb-uhci.c: process_transfer: fixed toggle message to console + * Possible bug in usb-uhci or bug in this driver that + * makes it spit that out. Doesn't seem to have harmful + * effects. + * + * Todo + * Fix mii/ethtool output +*/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Version Information */ +#define DRIVER_VERSION "v1.0.0" +#define DRIVER_AUTHOR "TiVo, Inc." +#define DRIVER_DESC "ASIX AX8817x USB Ethernet driver" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); + + +#define AX_REQ_READ ( USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE ) +#define AX_REQ_WRITE ( USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE ) + +#define AX_CMD_SET_SW_MII 0x06 +#define AX_CMD_READ_MII_REG 0x07 +#define AX_CMD_WRITE_MII_REG 0x08 +#define AX_CMD_SET_HW_MII 0x0a +#define AX_CMD_WRITE_RX_CTL 0x10 +#define AX_CMD_WRITE_MULTI_FILTER 0x16 +#define AX_CMD_READ_NODE_ID 0x17 +#define AX_CMD_READ_PHY_ID 0x19 +#define AX_CMD_WRITE_MEDIUM_MODE 0x1b +#define AX_CMD_WRITE_GPIOS 0x1f + +#define AX_RX_MAX ETH_FRAME_LEN +#define AX_TIMEOUT_CMD ( HZ / 10 ) +#define AX_TIMEOUT_TX ( HZ * 2 ) +#define AX_MAX_MCAST 64 + +#define AX_DRV_STATE_INITIALIZING 0x00 +#define AX_DRV_STATE_RUNNING 0x01 +#define AX_DRV_STATE_EXITING 0x02 + +#define AX_PHY_STATE_INITIALIZING 0x00 +#define AX_PHY_STATE_NO_LINK 0x01 +#define AX_PHY_STATE_POLLING_1 0x02 +#define AX_PHY_STATE_POLLING_2 0x03 +#define AX_PHY_STATE_POLLING_3 0x04 +#define AX_PHY_STATE_POLLING_4 0x05 +#define AX_PHY_STATE_SETTING_MAC 0x06 +#define AX_PHY_STATE_LINK 0x07 +#define AX_PHY_STATE_ABORT_POLL 0x08 +#define AX_PHY_STATE_ABORTING 0x09 + +#define AX_MAX_PHY_RETRY 50 + +#define AX_RX_URBS_DEFAULT 2 + +static const char driver_name[] = "ax8817x"; +static int n_rx_urbs = AX_RX_URBS_DEFAULT; + +MODULE_PARM(n_rx_urbs, "i"); +MODULE_PARM_DESC(n_rx_urbs, + "Number of rx buffers to queue at once (def 2)"); + +struct ax8817x_info; +struct ax_cmd_req; +typedef int (*ax_cmd_callback_t) (struct ax8817x_info *, + struct ax_cmd_req *); + +struct ax_cmd_req { + struct list_head list; + ax_cmd_callback_t cmd_callback; + void *priv; + int status; + void *data; + int data_size; + int timeout; + struct usb_ctrlrequest devreq; +}; + +struct ax8817x_info { + struct usb_device *usb; + struct net_device *net; + struct urb **rx_urbs; + struct urb *tx_urb; + struct urb *int_urb; + u8 *int_buf; + struct urb *ctl_urb; + struct list_head ctl_queue; + spinlock_t ctl_lock; + atomic_t rx_refill_cnt; + int tx_head; + int tx_tail; + spinlock_t tx_lock; + struct net_device_stats stats; + struct ax_cmd_req phy_req; + u8 phy_id; + u8 phy_state; + u8 drv_state; +}; + + +const struct usb_device_id ax8817x_id_table[] __devinitdata = { + /* Linksys USB200M */ + {USB_DEVICE(0x077b, 0x2226), driver_info:0x00130103}, + /* Hawking UF200, TRENDnet TU2-ET100 */ + {USB_DEVICE(0x07b8, 0x420a), driver_info:0x001f1d1f}, + /* NETGEAR FA120 */ + {USB_DEVICE(0x0846, 0x1040), driver_info:0x00130103}, + /* D-Link DUB-E100 */ + {USB_DEVICE(0x2001, 0x1a00), driver_info:0x009f9d9f}, + + {} +}; + +MODULE_DEVICE_TABLE(usb, ax8817x_id_table); + +static void ax_run_ctl_queue(struct ax8817x_info *, struct ax_cmd_req *, + int); +static void ax_rx_callback(struct urb *urb); + +static void ax_ctl_callback(struct urb *urb) +{ + struct ax8817x_info *ax_info = + (struct ax8817x_info *) urb->context; + + ax_run_ctl_queue(ax_info, NULL, + urb->status ? urb->status : urb->actual_length); +} + +/* + * Queue a new ctl request, or dequeue the first in the list +*/ +static void ax_run_ctl_queue(struct ax8817x_info *ax_info, + struct ax_cmd_req *req, int status) +{ + struct ax_cmd_req *next_req = NULL; + struct ax_cmd_req *last_req = NULL; + unsigned long flags; + + /* Need to lock around queue list manipulation */ + spin_lock_irqsave(&ax_info->ctl_lock, flags); + + if (req == NULL) { + last_req = + list_entry(ax_info->ctl_queue.next, struct ax_cmd_req, + list); + } else { + if (list_empty(&ax_info->ctl_queue)) { + next_req = req; + } + + req->status = -EINPROGRESS; + list_add_tail(&req->list, &ax_info->ctl_queue); + } + + while (1) { + if (last_req != NULL) { + /* dequeue completed entry */ + list_del(&last_req->list); + + last_req->status = status; + if (last_req->cmd_callback(ax_info, last_req)) { + /* requeue if told to do so */ + last_req->status = -EINPROGRESS; + list_add_tail(&last_req->list, + &ax_info->ctl_queue); + } + + if (list_empty(&ax_info->ctl_queue)) { + next_req = NULL; + } else { + next_req = + list_entry(ax_info->ctl_queue.next, + struct ax_cmd_req, list); + } + } + + spin_unlock_irqrestore(&ax_info->ctl_lock, flags); + + if (next_req == NULL) { + break; + } + + /* XXX: do something with timeout */ + usb_fill_control_urb(ax_info->ctl_urb, ax_info->usb, + next_req->devreq. + bRequestType & USB_DIR_IN ? + usb_rcvctrlpipe(ax_info->usb, + 0) : + usb_sndctrlpipe(ax_info->usb, 0), + (void *) &next_req->devreq, + next_req->data, next_req->data_size, + ax_ctl_callback, ax_info); + + status = usb_submit_urb(ax_info->ctl_urb); + if (status >= 0) { + break; + } + + last_req = next_req; + + spin_lock_irqsave(&ax_info->ctl_lock, flags); + } +} + +static int ax_sync_cmd_callback(struct ax8817x_info *unused, + struct ax_cmd_req *req) +{ + wait_queue_head_t *wq = (wait_queue_head_t *) req->priv; + + wake_up(wq); + + return 0; +} + +static int ax_async_cmd_callback(struct ax8817x_info *unused, + struct ax_cmd_req *req) +{ + if (req->status < 0) { + err("%s: Async command %d failed: %d\n", __FUNCTION__, + req->devreq.bRequest, req->status); + } + + /* Nothing else to do here, just need to free the request (and its + allocated data) */ + if (req->data != NULL) { + kfree(req->data); + } + kfree(req); + + return 0; +} + +/* + * This is mostly the same as usb_control_msg(), except that it is able + * to queue control messages +*/ +static int ax_control_msg(struct ax8817x_info *ax_info, u8 requesttype, + u8 request, u16 value, u16 index, void *data, + u16 size, int timeout) +{ + struct ax_cmd_req *req; + DECLARE_WAIT_QUEUE_HEAD(wq); + DECLARE_WAITQUEUE(wait, current); + int ret; + + req = kmalloc(sizeof(struct ax_cmd_req), GFP_KERNEL); + if (req == NULL) { + return -ENOMEM; + } + + req->devreq.bRequestType = requesttype; + req->devreq.bRequest = request; + req->devreq.wValue = cpu_to_le16(value); + req->devreq.wIndex = cpu_to_le16(index); + req->devreq.wLength = cpu_to_le16(size); + req->data = data; + req->data_size = size; + req->timeout = timeout; + + req->priv = &wq; + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&wq, &wait); + + req->cmd_callback = ax_sync_cmd_callback; + + ax_run_ctl_queue(ax_info, req, 0); + schedule(); + + ret = req->status; + + kfree(req); + + return ret; +} + +/* + * Same, but can be used asynchronously, may fail, and returns no exit + * status +*/ +static void ax_control_msg_async(struct ax8817x_info *ax_info, + u8 requesttype, u8 request, u16 value, + u16 index, void *data, u16 size, + int timeout) +{ + struct ax_cmd_req *req; + + req = kmalloc(sizeof(struct ax_cmd_req), GFP_ATOMIC); + if (req == NULL) { + /* There's not much else we can do here... */ + err("%s: Failed alloc\n", __FUNCTION__); + return; + } + + req->devreq.bRequestType = requesttype; + req->devreq.bRequest = request; + req->devreq.wValue = cpu_to_le16(value); + req->devreq.wIndex = cpu_to_le16(index); + req->devreq.wLength = cpu_to_le16(size); + req->data = data; + req->data_size = size; + req->timeout = timeout; + + req->cmd_callback = ax_async_cmd_callback; + + ax_run_ctl_queue(ax_info, req, 0); +} + +static inline int ax_read_cmd(struct ax8817x_info *ax_info, u8 cmd, + u16 value, u16 index, u16 size, void *data) +{ + return ax_control_msg(ax_info, AX_REQ_READ, cmd, value, index, + data, size, AX_TIMEOUT_CMD); +} + +static inline int ax_write_cmd(struct ax8817x_info *ax_info, u8 cmd, + u16 value, u16 index, u16 size, void *data) +{ + return ax_control_msg(ax_info, AX_REQ_WRITE, cmd, value, index, + data, size, AX_TIMEOUT_CMD); +} + +static inline void ax_write_cmd_async(struct ax8817x_info *ax_info, u8 cmd, + u16 value, u16 index, u16 size, + void *data) +{ + ax_control_msg_async(ax_info, AX_REQ_WRITE, cmd, value, index, + data, size, AX_TIMEOUT_CMD); +} + +static int ax_refill_rx_urb(struct ax8817x_info *ax_info, struct urb *urb) +{ + struct sk_buff *skb; + int ret; + + skb = dev_alloc_skb(AX_RX_MAX + 2); + if (skb != NULL) { + skb_reserve(skb, 2); /* for IP header alignment */ + skb->dev = ax_info->net; + + usb_fill_bulk_urb(urb, ax_info->usb, + usb_rcvbulkpipe(ax_info->usb, 3), + skb->data, AX_RX_MAX, ax_rx_callback, + skb); + + ret = usb_submit_urb(urb); + if (ret < 0) { + err("Failed submit rx URB (%d)\n", ret); + dev_kfree_skb_irq(skb); + urb->context = NULL; + } else { + ret = 0; + } + } else { + /* this just means we're low on memory at the moment. Try to + handle it gracefully. */ + urb->context = NULL; + ret = 1; + } + + return ret; +} + +static int ax_phy_cmd_callback(struct ax8817x_info *ax_info, + struct ax_cmd_req *req) +{ + int full_duplex; + int flow_control; + u16 mii_data_le; + + if (req->status < 0) { + err("%s: Failed at state %d: %d\n", __FUNCTION__, + ax_info->phy_state, req->status); + /* Not sure what else we can do, so just bail */ + ax_info->phy_state = AX_PHY_STATE_ABORTING; + } + + switch (ax_info->phy_state) { + /* Now that we're in software MII mode, read the BMSR */ + case AX_PHY_STATE_POLLING_1: + ax_info->phy_state = AX_PHY_STATE_POLLING_2; + req->devreq.bRequestType = AX_REQ_READ; + req->devreq.bRequest = AX_CMD_READ_MII_REG; + req->devreq.wValue = cpu_to_le16(ax_info->phy_id); + req->devreq.wIndex = cpu_to_le16(MII_BMSR); + req->devreq.wLength = cpu_to_le16(2); + req->data_size = 2; + (long) req->priv = 0; /* This is the retry count */ + return 1; + + /* Done reading BMSR */ + case AX_PHY_STATE_POLLING_2: + mii_data_le = *(u16 *) req->data; + if ((mii_data_le & + cpu_to_le16(BMSR_LSTATUS | BMSR_ANEGCAPABLE)) + == cpu_to_le16(BMSR_LSTATUS | BMSR_ANEGCAPABLE)) { + if (mii_data_le & cpu_to_le16(BMSR_ANEGCOMPLETE)) { + /* Autonegotiation done, go on to read LPA */ + ax_info->phy_state = + AX_PHY_STATE_POLLING_3; + req->devreq.wIndex = cpu_to_le16(MII_LPA); + return 1; + } else if ((long) req->priv++ < AX_MAX_PHY_RETRY) { + /* Reread BMSR if it's still autonegotiating. This is + probably unnecessary logic, I've never seen it take + more than 1 try... */ + return 1; + } + /* else fall through to abort */ + } + /* XXX: should probably handle auto-neg failure better, + by reverting to manual setting of something safe. (?) */ + + ax_info->phy_state = AX_PHY_STATE_ABORT_POLL; + /* and then fall through to set hw MII */ + + /* Got what we needed from PHY, set back to hardware MII mode + (Do same for abort in mid-poll) */ + case AX_PHY_STATE_POLLING_3: + case AX_PHY_STATE_ABORT_POLL: + ax_info->phy_state += 1; + req->devreq.bRequestType = AX_REQ_WRITE; + req->devreq.bRequest = AX_CMD_SET_HW_MII; + req->devreq.wValue = cpu_to_le16(0); + req->devreq.wIndex = cpu_to_le16(0); + req->devreq.wLength = cpu_to_le16(0); + req->data_size = 0; + return 1; + + /* The end result, set the right duplex and flow control mode in the + MAC (based on the PHY's LPA reg, which should still be in the data + buffer) */ + case AX_PHY_STATE_POLLING_4: + mii_data_le = *(u16 *) req->data; + ax_info->phy_state = AX_PHY_STATE_SETTING_MAC; + req->devreq.bRequest = AX_CMD_WRITE_MEDIUM_MODE; + full_duplex = mii_data_le & cpu_to_le16(LPA_DUPLEX); + flow_control = full_duplex && + (mii_data_le & cpu_to_le16(0x0400)); + req->devreq.wValue = cpu_to_le16(0x04) | + (full_duplex ? cpu_to_le16(0x02) : 0) | + (flow_control ? cpu_to_le16(0x10) : 0); + info("%s: Link established, %s duplex, flow control %sabled\n", ax_info->net->name, full_duplex ? "full" : "half", flow_control ? "en" : "dis"); + return 1; + + /* All done */ + case AX_PHY_STATE_SETTING_MAC: + ax_info->phy_state = AX_PHY_STATE_LINK; + netif_carrier_on(ax_info->net); + return 0; + + default: + err("%s: Unknown state %d\n", __FUNCTION__, + ax_info->phy_state); + /* fall through */ + case AX_PHY_STATE_ABORTING: + ax_info->phy_state = AX_PHY_STATE_NO_LINK; + return 0; + } +} + +static void ax_int_callback(struct urb *urb) +{ + struct ax8817x_info *ax_info = + (struct ax8817x_info *) urb->context; + u8 phy_link; + + if (ax_info->drv_state == AX_DRV_STATE_EXITING || + urb->actual_length < 3) { + return; + } + + /* Ignore the first PHY link report, it will sometimes be reported as + link active, even though we just told the PHY to reset. If it + really has link, we'll pick it up next int callback. + */ + if (ax_info->phy_state == AX_PHY_STATE_INITIALIZING) { + netif_carrier_off(ax_info->net); + ax_info->phy_state = AX_PHY_STATE_NO_LINK; + return; + } + + /* Assume we're only interested in the primary PHY for now. */ + phy_link = ax_info->int_buf[2] & 1; + + if (phy_link == + (ax_info->phy_state == AX_PHY_STATE_NO_LINK) ? 0 : 1) { + /* Common case, no change */ + return; + } + + if (phy_link == 0) { + netif_carrier_off(ax_info->net); + /* Abort an in-progress poll of the PHY if necessary */ + switch (ax_info->phy_state) { + case AX_PHY_STATE_POLLING_1: + case AX_PHY_STATE_POLLING_2: + case AX_PHY_STATE_POLLING_3: + ax_info->phy_state = AX_PHY_STATE_ABORT_POLL; + break; + + case AX_PHY_STATE_POLLING_4: + case AX_PHY_STATE_SETTING_MAC: + ax_info->phy_state = AX_PHY_STATE_ABORTING; + break; + + case AX_PHY_STATE_LINK: + ax_info->phy_state = AX_PHY_STATE_NO_LINK; + break; + + default: + /* If we're already aborting, continue aborting */ + break; + } + } else { + /* Note that we only fall into this case if previous phy_state was + AX_PHY_STATE_NO_LINK. When the link is reported active while + we're still polling, or when we're aborting, the logic above + will just return, and we'll check again next int callback. */ + + ax_info->phy_state = AX_PHY_STATE_POLLING_1; + ax_info->phy_req.devreq.bRequestType = AX_REQ_WRITE; + ax_info->phy_req.devreq.bRequest = AX_CMD_SET_SW_MII; + ax_info->phy_req.devreq.wValue = cpu_to_le16(0); + ax_info->phy_req.devreq.wIndex = cpu_to_le16(0); + ax_info->phy_req.devreq.wLength = cpu_to_le16(0); + ax_info->phy_req.data_size = 0; + ax_info->phy_req.timeout = AX_TIMEOUT_CMD; + ax_info->phy_req.cmd_callback = ax_phy_cmd_callback; + + ax_run_ctl_queue(ax_info, &ax_info->phy_req, 0); + } +} + +static void ax_rx_callback(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct net_device *net = skb->dev; + struct ax8817x_info *ax_info = (struct ax8817x_info *) net->priv; + int ret, len, refill; + + switch (urb->status) { + case 0: + break; + + default: + err("%s: URB status %d\n", __FUNCTION__, urb->status); + /* It's not clear that we can do much in this case, the rx pipe + doesn't ever seem to stall, so if we got -ETIMEDOUT, that + usually means the device was unplugged, and we just haven't + noticed yet. + Just fall through and free skb without resubmitting urb. */ + case -ENOENT: /* */ + case -ECONNRESET: /* Async unlink */ + case -ESHUTDOWN: /* Hardware gone */ + case -EILSEQ: /* Get this when you yank it out on UHCI */ + case -ETIMEDOUT: /* OHCI */ + case -EPROTO: /* EHCI */ + case -EPIPE: + dev_kfree_skb_any(skb); + urb->context = NULL; + return; + } + + if (ax_info->drv_state == AX_DRV_STATE_INITIALIZING) { + /* Not really expecting this to ever happen, since we haven't yet + enabled receive in the rx_ctl register, but ya never know... */ + goto refill_same; + } else if (ax_info->drv_state == AX_DRV_STATE_EXITING) { + dev_kfree_skb_any(skb); + urb->context = NULL; + return; + } + + len = urb->actual_length; + if (len == 0) { + /* this shouldn't happen... */ + goto refill_same; + } + + refill = ax_refill_rx_urb(ax_info, urb); + + if (refill == 0 + || atomic_read(&ax_info->rx_refill_cnt) < n_rx_urbs) { + /* Send the receive buffer up the network stack */ + skb_put(skb, len); + skb->protocol = eth_type_trans(skb, net); + net->last_rx = jiffies; + ax_info->stats.rx_packets++; + ax_info->stats.rx_bytes += len; + + netif_rx(skb); + + if (refill == 0) { + int i; + + /* This is the common case. This URB got refilled OK, and + no other URBs need to be refilled. */ + if (atomic_read(&ax_info->rx_refill_cnt) == 0) { + return; + } + + for (i = 0; i < n_rx_urbs; i++) { + struct urb *urb = ax_info->rx_urbs[i]; + + if (urb->context == NULL) { + if (ax_refill_rx_urb(ax_info, urb) + == 0) { + atomic_dec(&ax_info-> + rx_refill_cnt); + } else { + break; + } + } + } + } else { + /* remember to refill this one later */ + atomic_inc(&ax_info->rx_refill_cnt); + } + + return; + } else { + ax_info->stats.rx_dropped++; + if (refill < 0) { + /* the error code was already printk'ed in ax_refill_rx_urb() + so just note the consequences here: */ + warn("Halting rx due to error\n"); + return; + } + + /* fall through to resubmit this URB with the existing skb + will try to reallocate skb's on next rx callback */ + } + + refill_same: + usb_fill_bulk_urb(urb, ax_info->usb, + usb_rcvbulkpipe(ax_info->usb, 3), skb->data, + AX_RX_MAX, ax_rx_callback, skb); + + ret = usb_submit_urb(urb); + if (ret < 0) { + err("Failed submit rx URB (%d)\n", ret); + } +} + +static int ax8817x_open(struct net_device *net) +{ + struct ax8817x_info *ax_info = (struct ax8817x_info *) net->priv; + u8 buf[4]; + int i, ret; + + ret = ax_write_cmd(ax_info, AX_CMD_WRITE_RX_CTL, 0x80, 0, 0, buf); + if (ret < 0) { + return ret; + } + + ret = 0; + + ax_info->tx_urb = usb_alloc_urb(0); + if (ax_info->tx_urb == NULL) + ret = -ENOMEM; + + atomic_set(&ax_info->rx_refill_cnt, 0); + + for (i = 0; i < n_rx_urbs && ret == 0; i++) { + struct urb *urb = ax_info->rx_urbs[i]; + + if (urb == NULL) { + urb = ax_info->rx_urbs[i] = usb_alloc_urb(0); + if (urb == NULL) { + ret = -ENOMEM; + break; + } + if (n_rx_urbs > 1) { + urb->transfer_flags |= USB_QUEUE_BULK; + } + } + ret = ax_refill_rx_urb(ax_info, urb); + if (ret == 1) { + atomic_inc(&ax_info->rx_refill_cnt); + ret = 0; + } + } + + /* XXX: should handle the case where we couldn't allocate any skb's + better. They get allocated with GFP_ATOMIC, so they may all fail... */ + if (ret == 0 && atomic_read(&ax_info->rx_refill_cnt) < n_rx_urbs) { + netif_start_queue(net); + } else { + /* Error: clean up anything we allocated and bail. */ + usb_free_urb(ax_info->tx_urb); + + for (i = 0; i < n_rx_urbs; i++) { + struct urb *urb = ax_info->rx_urbs[i]; + + if (urb != NULL) { + /* skb gets freed in the URB callback */ + usb_unlink_urb(urb); + usb_free_urb(urb); + } + } + + err("%s: Failed start rx queue (%d)\n", __FUNCTION__, ret); + } + return ret; +} + +static int ax8817x_stop(struct net_device *net) +{ + struct ax8817x_info *ax_info = (struct ax8817x_info *) net->priv; + u8 buf[4]; + int i, ret; + + netif_stop_queue(net); + + ret = ax_write_cmd(ax_info, AX_CMD_WRITE_RX_CTL, 0x80, 0, 0, buf); + if (ret < 0 && ax_info->drv_state != AX_DRV_STATE_EXITING) { + err("%s: Failed cmd (%d)\n", __FUNCTION__, ret); + } + + if (ax_info->tx_urb != NULL) { + usb_unlink_urb(ax_info->tx_urb); + usb_free_urb(ax_info->tx_urb); + ax_info->tx_urb = NULL; + } + + for (i = 0; i < n_rx_urbs; i++) { + struct urb *urb = ax_info->rx_urbs[i]; + if (urb != NULL) { + /* skb gets freed in the URB callback */ + usb_unlink_urb(urb); + usb_free_urb(urb); + ax_info->rx_urbs[i] = NULL; + } + } + + return 0; +} + +static void write_bulk_callback(struct urb *urb) +{ + struct ax8817x_info *ax_info = urb->context; + + if (!ax_info || (ax_info->drv_state == AX_DRV_STATE_EXITING)) + return; + + if (!netif_device_present(ax_info->net)) + return; + + if (urb->status) + info("%s: TX status %d", ax_info->net->name, urb->status); + + ax_info->net->trans_start = jiffies; + netif_wake_queue(ax_info->net); +} + +static int ax8817x_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct ax8817x_info *ax_info = net->priv; + int res; + + netif_stop_queue(net); + + ax_info->tx_urb->transfer_flags |= USB_ZERO_PACKET; + usb_fill_bulk_urb(ax_info->tx_urb, ax_info->usb, + usb_sndbulkpipe(ax_info->usb, 2), + skb->data, skb->len, write_bulk_callback, + ax_info); + if ((res = usb_submit_urb(ax_info->tx_urb))) { + warn("Failed tx_urb %d", res); + ax_info->stats.tx_errors++; + netif_start_queue(net); + } else { + ax_info->stats.tx_packets++; + ax_info->stats.tx_bytes += skb->len; + net->trans_start = jiffies; + } + dev_kfree_skb(skb); + + return 0; +} + +static void ax8817x_tx_timeout(struct net_device *net) +{ + struct ax8817x_info *ax_info = net->priv; + + if (!ax_info) + return; + + warn("%s: Tx timed out.", net->name); + ax_info->tx_urb->transfer_flags |= USB_ASYNC_UNLINK; + usb_unlink_urb(ax_info->tx_urb); + ax_info->stats.tx_errors++; +} + +static struct net_device_stats *ax8817x_stats(struct net_device *net) +{ + struct ax8817x_info *ax_info = (struct ax8817x_info *) net->priv; + + return &ax_info->stats; +} + +static void ax8817x_set_multicast(struct net_device *net) +{ + struct ax8817x_info *ax_info = (struct ax8817x_info *) net->priv; + u8 rx_ctl = 0x8c; + + if (net->flags & IFF_PROMISC) { + rx_ctl |= 0x01; + } else if (net->flags & IFF_ALLMULTI + || net->mc_count > AX_MAX_MCAST) { + rx_ctl |= 0x02; + } else if (net->mc_count == 0) { + /* just broadcast and directed */ + } else { + struct dev_mc_list *mc_list = net->mc_list; + u8 *multi_filter; + u32 crc_bits; + int i; + + multi_filter = kmalloc(8, GFP_ATOMIC); + if (multi_filter == NULL) { + /* Oops, couldn't allocate a DMA buffer for setting the multicast + filter. Try all multi mode, although the ax_write_cmd_async + will almost certainly fail, too... (but it will printk). */ + rx_ctl |= 0x02; + } else { + memset(multi_filter, 0, 8); + + /* Build the multicast hash filter. */ + for (i = 0; i < net->mc_count; i++) { + crc_bits = + ether_crc(ETH_ALEN, + mc_list->dmi_addr) >> 26; + multi_filter[crc_bits >> 3] |= + 1 << (crc_bits & 7); + mc_list = mc_list->next; + } + + ax_write_cmd_async(ax_info, + AX_CMD_WRITE_MULTI_FILTER, 0, 0, + 8, multi_filter); + + rx_ctl |= 0x10; + } + } + + ax_write_cmd_async(ax_info, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, + NULL); +} + +static int ax8817x_ethtool_ioctl(struct net_device *net, void *uaddr) +{ + struct ax8817x_info *ax_info; + int cmd; + + ax_info = net->priv; + if (get_user(cmd, (int *) uaddr)) + return -EFAULT; + + switch (cmd) { + case ETHTOOL_GDRVINFO:{ + struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO }; + + strncpy(info.driver, driver_name, + ETHTOOL_BUSINFO_LEN); + strncpy(info.version, DRIVER_VERSION, + ETHTOOL_BUSINFO_LEN); + usb_make_path(ax_info->usb, info.bus_info, + sizeof info.bus_info); + if (copy_to_user(uaddr, &info, sizeof(info))) + return -EFAULT; + return 0; + } + case ETHTOOL_GSET:{ + struct ethtool_cmd ecmd; + + if (copy_from_user(&ecmd, uaddr, sizeof(ecmd))) + return -EFAULT; + ecmd.supported = (SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_Autoneg | + SUPPORTED_TP | SUPPORTED_MII); + ecmd.port = PORT_TP; + ecmd.transceiver = XCVR_INTERNAL; + ecmd.phy_address = 0; /* FIXME */ + + if (copy_to_user(uaddr, &ecmd, sizeof(ecmd))) + return -EFAULT; + return 0; + } + case ETHTOOL_SSET: + return -ENOTSUPP; + case ETHTOOL_GLINK:{ + struct ethtool_value edata = { ETHTOOL_GLINK }; + + edata.data = netif_carrier_ok(net); + if (copy_to_user(uaddr, &edata, sizeof(edata))) + return -EFAULT; + return 0; + } + default: + return -EOPNOTSUPP; + } +} + +static int ax8817x_mii_ioctl(struct net_device *net, struct ifreq *ifr, + int cmd) +{ + struct ax8817x_info *ax_info; + struct mii_ioctl_data *data_ptr = + (struct mii_ioctl_data *) &(ifr->ifr_data); + + ax_info = net->priv; + + switch (cmd) { + case SIOCGMIIPHY: + data_ptr->phy_id = ax_info->phy_id; + break; + case SIOCGMIIREG: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + ax_read_cmd(ax_info, AX_CMD_READ_MII_REG, 0, + data_ptr->reg_num & 0x1f, 2, + &(data_ptr->val_out)); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ax8817x_ioctl(struct net_device *net, struct ifreq *ifr, + int cmd) +{ + struct ax8817x_info *ax_info; + int res; + + ax_info = net->priv; + res = 0; + + switch (cmd) { + case SIOCETHTOOL: + res = ax8817x_ethtool_ioctl(net, ifr->ifr_data); + break; + case SIOCGMIIPHY: /* Get address of PHY in use */ + case SIOCGMIIREG: /* Read from MII PHY register */ + case SIOCSMIIREG: /* Write to MII PHY register */ + return ax8817x_mii_ioctl(net, ifr, cmd); + default: + res = -EOPNOTSUPP; + } + + return res; +} + +static int ax8817x_net_init(struct net_device *net) +{ + struct ax8817x_info *ax_info = (struct ax8817x_info *) net->priv; + u8 buf[6]; + u16 *buf16 = (u16 *) buf; + int ret; + + spin_lock_init(&ax_info->tx_lock); + + ret = ax_write_cmd(ax_info, AX_CMD_WRITE_RX_CTL, 0x80, 0, 0, buf); + if (ret < 0) { + return ret; + } + + memset(buf, 0, 6); + + /* Get the MAC address */ + ret = ax_read_cmd(ax_info, AX_CMD_READ_NODE_ID, 0, 0, 6, buf); + if (ret < 0) { + return ret; + } + + memcpy(net->dev_addr, buf, 6); + + /* Get the PHY id */ + ret = ax_read_cmd(ax_info, AX_CMD_READ_PHY_ID, 0, 0, 2, buf); + if (ret < 0) { + return ret; + } else if (ret < 2) { + /* this should always return 2 bytes */ + return -EIO; + } + + /* Reset the PHY, and drop it into auto-negotiation mode */ + ax_info->phy_id = buf[1]; + ax_info->phy_state = AX_PHY_STATE_INITIALIZING; + + ret = ax_write_cmd(ax_info, AX_CMD_SET_SW_MII, 0, 0, 0, &buf); + if (ret < 0) { + return ret; + } + + *buf16 = cpu_to_le16(BMCR_RESET); + ret = ax_write_cmd(ax_info, AX_CMD_WRITE_MII_REG, + ax_info->phy_id, MII_BMCR, 2, buf16); + if (ret < 0) { + return ret; + } + + /* Advertise that we can do full-duplex pause */ + *buf16 = cpu_to_le16(ADVERTISE_ALL | ADVERTISE_CSMA | 0x0400); + ret = ax_write_cmd(ax_info, AX_CMD_WRITE_MII_REG, + ax_info->phy_id, MII_ADVERTISE, 2, buf16); + if (ret < 0) { + return ret; + } + + *buf16 = cpu_to_le16(BMCR_ANENABLE | BMCR_ANRESTART); + ret = ax_write_cmd(ax_info, AX_CMD_WRITE_MII_REG, + ax_info->phy_id, MII_BMCR, 2, buf16); + if (ret < 0) { + return ret; + } + + ret = ax_write_cmd(ax_info, AX_CMD_SET_HW_MII, 0, 0, 0, &buf); + if (ret < 0) { + return ret; + } + + net->open = ax8817x_open; + net->stop = ax8817x_stop; + net->hard_start_xmit = ax8817x_start_xmit; + net->tx_timeout = ax8817x_tx_timeout; + net->watchdog_timeo = AX_TIMEOUT_TX; + net->get_stats = ax8817x_stats; + net->do_ioctl = ax8817x_ioctl; + net->set_multicast_list = ax8817x_set_multicast; + + return 0; +} + +static void *ax8817x_bind(struct usb_device *usb, unsigned intf, + const struct usb_device_id *id) +{ + struct ax8817x_info *ax_info; + struct net_device *net; + int i, ret; + unsigned long gpio_bits = id->driver_info; + u8 buf[2]; + + /* Allocate the URB lists along with the device info struct */ + ax_info = kmalloc(sizeof(struct ax8817x_info) + + n_rx_urbs * sizeof(struct urb *), GFP_KERNEL); + if (ax_info == NULL) { + err("%s: Failed ax alloc\n", __FUNCTION__); + goto exit_err; + } + + memset(ax_info, 0, sizeof(struct ax8817x_info) + + n_rx_urbs * sizeof(struct urb *)); + + ax_info->drv_state = AX_DRV_STATE_INITIALIZING; + ax_info->rx_urbs = (struct urb **) (ax_info + 1); + ax_info->usb = usb; + + /* Set up the control URB queue */ + + INIT_LIST_HEAD(&ax_info->ctl_queue); + spin_lock_init(&ax_info->ctl_lock); + ax_info->ctl_urb = usb_alloc_urb(0); + if (ax_info->ctl_urb == NULL) { + goto exit_err_free_ax; + } + + /* Toggle the GPIOs in a manufacturer/model specific way */ + + for (i = 2; i >= 0; i--) { + ret = ax_write_cmd(ax_info, AX_CMD_WRITE_GPIOS, + (gpio_bits >> (i * 8)) & 0xff, 0, 0, + buf); + if (ret < 0) { + goto exit_err_free_ax; + } + wait_ms(5); + } + + /* Set up the net device */ + + net = alloc_etherdev(0); + if (net == NULL) { + err("%s: Failed net alloc\n", __FUNCTION__); + goto exit_err_free_ax; + } + + ax_info->net = net; + + SET_MODULE_OWNER(net); + net->init = ax8817x_net_init; + net->priv = ax_info; + + ret = register_netdev(net); + if (ret < 0) { + err("%s: Failed net init (%d)\n", __FUNCTION__, ret); + goto exit_err_free_net; + } + + /* Set up the interrupt URB, and start PHY state monitoring */ + + ax_info->int_urb = usb_alloc_urb(0); + if (ax_info->int_urb == NULL) { + goto exit_err_unregister_net; + } + ax_info->int_buf = kmalloc(8, GFP_KERNEL); + if (ax_info->int_buf == NULL) { + goto exit_err_free_int_urb; + } + ax_info->phy_req.data = kmalloc(2, GFP_KERNEL); + if (ax_info->phy_req.data == NULL) { + goto exit_err_free_int_buf; + } + + usb_fill_int_urb(ax_info->int_urb, usb, usb_rcvintpipe(usb, 1), + ax_info->int_buf, 8, ax_int_callback, ax_info, + 100); + + ret = usb_submit_urb(ax_info->int_urb); + if (ret < 0) { + err("%s: Failed int URB submit (%d)\n", __FUNCTION__, ret); + goto exit_err_free_phy_buf; + } + + ax_info->drv_state = AX_DRV_STATE_RUNNING; + return ax_info; + + exit_err_free_phy_buf: + kfree(ax_info->phy_req.data); + + exit_err_free_int_buf: + kfree(ax_info->int_buf); + + exit_err_free_int_urb: + usb_free_urb(ax_info->int_urb); + + exit_err_unregister_net: + ax_info->drv_state = AX_DRV_STATE_EXITING; + unregister_netdev(net); + + exit_err_free_net: + kfree(net); + + exit_err_free_ax: + if (ax_info->ctl_urb != NULL) { + /* no need to unlink, since there should not be any ctl URBs + pending at this point */ + usb_free_urb(ax_info->ctl_urb); + } + + kfree(ax_info); + + exit_err: + err("%s: Failed to initialize\n", __FUNCTION__); + return NULL; +} + +static void ax8817x_disconnect(struct usb_device *usb, void *p) +{ + struct ax8817x_info *ax_info = (struct ax8817x_info *) p; + + ax_info->drv_state = AX_DRV_STATE_EXITING; + + if (ax_info->int_urb != NULL) { + usb_unlink_urb(ax_info->int_urb); + usb_free_urb(ax_info->int_urb); + kfree(ax_info->int_buf); + } + + unregister_netdev(ax_info->net); + + /* XXX: hmmm... need to go through and clear out the ctl queue, too... */ + if (ax_info->ctl_urb != NULL) { + usb_unlink_urb(ax_info->ctl_urb); + usb_free_urb(ax_info->ctl_urb); + } + + kfree(ax_info); +} + + +static struct usb_driver ax8817x_driver = { + .owner = THIS_MODULE, + .name = driver_name, + .probe = ax8817x_bind, + .disconnect = ax8817x_disconnect, + .id_table = ax8817x_id_table, +}; + +static int __init ax8817x_init(void) +{ + int ret; + + if (n_rx_urbs < 1) + n_rx_urbs = AX_RX_URBS_DEFAULT; + + ret = usb_register(&ax8817x_driver); + if (ret < 0) { + err("%s: Failed to register\n", __FUNCTION__); + } else { + info(DRIVER_DESC " " DRIVER_VERSION); + } + + return ret; +} + +static void __exit ax8817x_exit(void) +{ + usb_deregister(&ax8817x_driver); +} + +module_init(ax8817x_init); +module_exit(ax8817x_exit); + +EXPORT_NO_SYMBOLS; diff -Nru a/drivers/usb/catc.c b/drivers/usb/catc.c --- a/drivers/usb/catc.c Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/catc.c Thu Jun 19 17:28:36 2003 @@ -62,6 +62,8 @@ MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); +static const char driver_name[] = "catc"; + /* * Some defines. */ @@ -674,7 +676,7 @@ /* get driver info */ case ETHTOOL_GDRVINFO: { struct ethtool_drvinfo info = {ETHTOOL_GDRVINFO}; - strncpy(info.driver, SHORT_DRIVER_DESC, ETHTOOL_BUSINFO_LEN); + strncpy(info.driver, driver_name, 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); @@ -937,7 +939,7 @@ MODULE_DEVICE_TABLE(usb, catc_id_table); static struct usb_driver catc_driver = { - name: "catc", + name: driver_name, probe: catc_probe, disconnect: catc_disconnect, id_table: catc_id_table, diff -Nru a/drivers/usb/hid-core.c b/drivers/usb/hid-core.c --- a/drivers/usb/hid-core.c Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/hid-core.c Thu Jun 19 17:28:36 2003 @@ -108,10 +108,11 @@ memset(field, 0, sizeof(struct hid_field) + usages * sizeof(struct hid_usage) + values * sizeof(unsigned)); - report->field[report->maxfield++] = field; + report->field[report->maxfield] = field; field->usage = (struct hid_usage *)(field + 1); field->value = (unsigned *)(field->usage + usages); field->report = report; + field->index = report->maxfield++; return field; } @@ -127,18 +128,42 @@ usage = parser->local.usage[0]; - if (type == HID_COLLECTION_APPLICATION - && parser->device->maxapplication < HID_MAX_APPLICATIONS) - parser->device->application[parser->device->maxapplication++] = usage; - if (parser->collection_stack_ptr == HID_COLLECTION_STACK_SIZE) { dbg("collection stack overflow"); return -1; } - collection = parser->collection_stack + parser->collection_stack_ptr++; + if (parser->device->maxcollection == parser->device->collection_size) { + collection = kmalloc(sizeof(struct hid_collection) * + parser->device->collection_size * 2, + GFP_KERNEL); + if (collection == NULL) { + dbg("failed to reallocate collection array"); + return -1; + } + memcpy(collection, parser->device->collection, + sizeof(struct hid_collection) * + parser->device->collection_size); + memset(collection + parser->device->collection_size, 0, + sizeof(struct hid_collection) * + parser->device->collection_size); + kfree(parser->device->collection); + parser->device->collection = collection; + parser->device->collection_size *= 2; + } + + parser->collection_stack[parser->collection_stack_ptr++] = + parser->device->maxcollection; + + collection = parser->device->collection + + parser->device->maxcollection++; + collection->type = type; collection->usage = usage; + collection->level = parser->collection_stack_ptr - 1; + + if (type == HID_COLLECTION_APPLICATION) + parser->device->maxapplication++; return 0; } @@ -166,8 +191,9 @@ { int n; for (n = parser->collection_stack_ptr - 1; n >= 0; n--) - if (parser->collection_stack[n].type == type) - return parser->collection_stack[n].usage; + if (parser->device->collection[parser->collection_stack[n]].type == type) + return parser->device->collection[parser->collection_stack[n]].usage; + return 0; /* we know nothing about this usage type */ } @@ -181,7 +207,12 @@ dbg("usage index exceeded"); return -1; } - parser->local.usage[parser->local.usage_index++] = usage; + parser->local.usage[parser->local.usage_index] = usage; + parser->local.collection_index[parser->local.usage_index] = + parser->collection_stack_ptr ? + parser->collection_stack[parser->collection_stack_ptr - 1] : 0; + parser->local.usage_index++; + return 0; } @@ -202,7 +233,7 @@ return -1; } - if (parser->global.logical_maximum <= parser->global.logical_minimum) { + if (parser->global.logical_maximum < parser->global.logical_minimum) { dbg("logical range invalid %d %d", parser->global.logical_minimum, parser->global.logical_maximum); return -1; } @@ -222,8 +253,11 @@ field->logical = hid_lookup_collection(parser, HID_COLLECTION_LOGICAL); field->application = hid_lookup_collection(parser, HID_COLLECTION_APPLICATION); - for (i = 0; i < usages; i++) + for (i = 0; i < usages; i++) { field->usage[i].hid = parser->local.usage[i]; + field->usage[i].collection_index = + parser->local.collection_index[i]; + } field->maxusage = usages; field->flags = flags; @@ -461,7 +495,7 @@ switch (item->tag) { case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION: - ret = open_collection(parser, data & 3); + ret = open_collection(parser, data & 0xff); break; case HID_MAIN_ITEM_TAG_END_COLLECTION: ret = close_collection(parser); @@ -530,6 +564,7 @@ } if (device->rdesc) kfree(device->rdesc); + if (device->collection) kfree(device->collection); } /* @@ -618,17 +653,30 @@ return NULL; memset(device, 0, sizeof(struct hid_device)); + if (!(device->collection = kmalloc(sizeof(struct hid_collection) * + HID_DEFAULT_NUM_COLLECTIONS, + GFP_KERNEL))) { + kfree(device); + return NULL; + } + memset(device->collection, 0, sizeof(struct hid_collection) * + HID_DEFAULT_NUM_COLLECTIONS); + device->collection_size = HID_DEFAULT_NUM_COLLECTIONS; + for (i = 0; i < HID_REPORT_TYPES; i++) INIT_LIST_HEAD(&device->report_enum[i].report_list); if (!(device->rdesc = (__u8 *)kmalloc(size, GFP_KERNEL))) { + kfree(device->collection); kfree(device); return NULL; } memcpy(device->rdesc, start, size); + device->rsize = size; if (!(parser = kmalloc(sizeof(struct hid_parser), GFP_KERNEL))) { kfree(device->rdesc); + kfree(device->collection); kfree(device); return NULL; } @@ -736,7 +784,7 @@ if (hid->claimed & HID_CLAIMED_INPUT) hidinput_hid_event(hid, field, usage, value); if (hid->claimed & HID_CLAIMED_HIDDEV) - hiddev_hid_event(hid, usage->hid, value); + hiddev_hid_event(hid, field, usage, value); } @@ -827,6 +875,9 @@ return -1; } + if (hid->claimed & HID_CLAIMED_HIDDEV) + hiddev_report_event(hid, report); + size = ((report->size - 1) >> 3) + 1; if (len < size) { @@ -1016,10 +1067,16 @@ void hid_write_report(struct hid_device *hid, struct hid_report *report) { - hid_output_report(report, hid->out[hid->outhead].buffer); + if (hid->report_enum[report->type].numbered) { + hid->out[hid->outhead].buffer[0] = report->id; + hid_output_report(report, hid->out[hid->outhead].buffer + 1); + hid->out[hid->outhead].dr.wLength = cpu_to_le16(((report->size + 7) >> 3) + 1); + } else { + hid_output_report(report, hid->out[hid->outhead].buffer); + hid->out[hid->outhead].dr.wLength = cpu_to_le16((report->size + 7) >> 3); + } - hid->out[hid->outhead].dr.wValue = cpu_to_le16(0x200 | report->id); - hid->out[hid->outhead].dr.wLength = cpu_to_le16((report->size + 7) >> 3); + hid->out[hid->outhead].dr.wValue = cpu_to_le16(((report->type + 1) << 8) | report->id); hid->outhead = (hid->outhead + 1) & (HID_CONTROL_FIFO_SIZE - 1); @@ -1083,12 +1140,29 @@ #define USB_VENDOR_ID_KBGEAR 0x084e #define USB_DEVICE_ID_KBGEAR_JAMSTUDIO 0x1001 +#define USB_VENDOR_ID_AIPTEK 0x08ca +#define USB_DEVICE_ID_AIPTEK_01 0x0001 +#define USB_DEVICE_ID_AIPTEK_10 0x0010 +#define USB_DEVICE_ID_AIPTEK_20 0x0020 +#define USB_DEVICE_ID_AIPTEK_21 0x0021 +#define USB_DEVICE_ID_AIPTEK_22 0x0022 +#define USB_DEVICE_ID_AIPTEK_23 0x0023 +#define USB_DEVICE_ID_AIPTEK_24 0x0024 + #define USB_VENDOR_ID_ATEN 0x0557 #define USB_DEVICE_ID_ATEN_UC100KM 0x2004 #define USB_DEVICE_ID_ATEN_CS124U 0x2202 #define USB_DEVICE_ID_ATEN_2PORTKVM 0x2204 #define USB_DEVICE_ID_ATEN_4PORTKVM 0x2205 +#define USB_VENDOR_ID_TOPMAX 0x0663 +#define USB_DEVICE_ID_TOPMAX_COBRAPAD 0x0103 + +#define USB_VENDOR_ID_HAPP 0x078b +#define USB_DEVICE_ID_UGCI_DRIVING 0x0010 +#define USB_DEVICE_ID_UGCI_FLYING 0x0020 +#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030 + #define USB_VENDOR_ID_GRIFFIN 0x077d #define USB_DEVICE_ID_POWERMATE 0x0410 /* Griffin PowerMate */ #define USB_DEVICE_ID_SOUNDKNOB 0x04AA /* Griffin SoundKnob */ @@ -1105,6 +1179,10 @@ #define USB_VENDOR_ID_ESSENTIAL_REALITY 0x0d7f #define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100 +#define USB_VENDOR_ID_MGE 0x0463 +#define USB_DEVICE_ID_MGE_UPS 0xffff +#define USB_DEVICE_ID_MGE_UPS1 0x0001 + struct hid_blacklist { __u16 idVendor; __u16 idProduct; @@ -1135,8 +1213,19 @@ { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS124U, HID_QUIRK_NOGET }, { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM, HID_QUIRK_NOGET }, { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_01, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_10, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_20, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_21, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_22, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_23, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_24, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_POWERMATE, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_SOUNDKNOB, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 100, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 200, HID_QUIRK_IGNORE }, @@ -1146,6 +1235,8 @@ { USB_VENDOR_ID_TANGTOP, USB_DEVICE_ID_TANGTOP_USBPS2, HID_QUIRK_NOGET }, { USB_VENDOR_ID_OKI, USB_VENDOR_ID_OKI_MULITI, HID_QUIRK_NOGET }, { USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_HIDDEV }, + { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS1, HID_QUIRK_HIDDEV }, { 0, 0 } }; @@ -1292,18 +1383,21 @@ printk(KERN_INFO); if (hid->claimed & HID_CLAIMED_INPUT) - printk("input%d", hid->input.number); + printk("input"); if (hid->claimed == (HID_CLAIMED_INPUT | HID_CLAIMED_HIDDEV)) printk(","); if (hid->claimed & HID_CLAIMED_HIDDEV) printk("hiddev%d", hid->minor); c = "Device"; - for (i = 0; i < hid->maxapplication; i++) - if ((hid->application[i] & 0xffff) < ARRAY_SIZE(hid_types)) { - c = hid_types[hid->application[i] & 0xffff]; + for (i = 0; i < hid->maxcollection; i++) { + if (hid->collection[i].type == HID_COLLECTION_APPLICATION && + (hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK && + (hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) { + c = hid_types[hid->collection[i].usage & 0xffff]; break; } + } printk(": USB HID v%x.%02x %s [%s] on usb%d:%d.%d\n", hid->version >> 8, hid->version & 0xff, c, hid->name, diff -Nru a/drivers/usb/hid-input.c b/drivers/usb/hid-input.c --- a/drivers/usb/hid-input.c Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/hid-input.c Thu Jun 19 17:28:36 2003 @@ -63,9 +63,39 @@ __s32 y; } hid_hat_to_axis[] = {{0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; -static void hidinput_configure_usage(struct hid_device *device, struct hid_field *field, struct hid_usage *usage) +static struct input_dev *find_input(struct hid_device *hid, struct hid_field *field) { - struct input_dev *input = &device->input; + struct list_head *lh; + struct hid_input *hidinput; + + list_for_each (lh, &hid->inputs) { + int i; + + hidinput = list_entry(lh, struct hid_input, list); + + if (! hidinput->report) + continue; + + for (i = 0; i < hidinput->report->maxfield; i++) + if (hidinput->report->field[i] == field) + return &hidinput->input; + } + + /* Assume we only have one input and use it */ + if (!list_empty(&hid->inputs)) { + hidinput = list_entry(hid->inputs.next, struct hid_input, list); + return &hidinput->input; + } + + /* This is really a bug */ + return NULL; +} + +static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field, + struct hid_usage *usage) +{ + struct input_dev *input = &hidinput->input; + struct hid_device *device = hidinput->input.private; int max; unsigned long *bit; @@ -280,10 +310,20 @@ int a = field->logical_minimum; int b = field->logical_maximum; + if ((device->quirks & HID_QUIRK_BADPAD) && (usage->code == ABS_X || usage->code == ABS_Y)) { + a = field->logical_minimum = 0; + b = field->logical_maximum = 255; + } + input->absmin[usage->code] = a; input->absmax[usage->code] = b; - input->absfuzz[usage->code] = (b - a) >> 8; - input->absflat[usage->code] = (b - a) >> 4; + input->absfuzz[usage->code] = 0; + input->absflat[usage->code] = 0; + + if (field->application == HID_GD_GAMEPAD || field->application == HID_GD_JOYSTICK) { + input->absfuzz[usage->code] = (b - a) >> 8; + input->absflat[usage->code] = (b - a) >> 4; + } } if (usage->hat_min != usage->hat_max) { @@ -300,9 +340,12 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value) { - struct input_dev *input = &hid->input; + struct input_dev *input = find_input(hid, field); int *quirks = &hid->quirks; + if (!input) + return; + if (usage->hat_min != usage->hat_max) { value = (value - usage->hat_min) * 8 / (usage->hat_max - usage->hat_min + 1) + 1; if (value < 0 || value > 8) value = 0; @@ -382,44 +425,84 @@ struct hid_report_enum *report_enum; struct hid_report *report; struct list_head *list; + struct hid_input *hidinput = NULL; int i, j, k; - for (i = 0; i < hid->maxapplication; i++) - if (IS_INPUT_APPLICATION(hid->application[i])) + INIT_LIST_HEAD(&hid->inputs); + + for (i = 0; i < hid->maxcollection; i++) + if (hid->collection[i].type == HID_COLLECTION_APPLICATION && + IS_INPUT_APPLICATION(hid->collection[i].usage)) break; - if (i == hid->maxapplication) + if (i == hid->maxcollection) return -1; - hid->input.private = hid; - hid->input.event = hidinput_input_event; - hid->input.open = hidinput_open; - hid->input.close = hidinput_close; - - hid->input.name = hid->name; - hid->input.idbus = BUS_USB; - hid->input.idvendor = dev->descriptor.idVendor; - hid->input.idproduct = dev->descriptor.idProduct; - hid->input.idversion = dev->descriptor.bcdDevice; - for (k = HID_INPUT_REPORT; k <= HID_OUTPUT_REPORT; k++) { report_enum = hid->report_enum + k; list = report_enum->report_list.next; while (list != &report_enum->report_list) { report = (struct hid_report *) list; + + if (!report->maxfield) + continue; + + if (!hidinput) { + hidinput = kmalloc(sizeof(*hidinput), GFP_KERNEL); + if (!hidinput) { + err("Out of memory during hid input probe"); + return -1; + } + memset(hidinput, 0, sizeof(*hidinput)); + list_add_tail(&hidinput->list, &hid->inputs); + + hidinput->input.private = hid; + hidinput->input.event = hidinput_input_event; + hidinput->input.open = hidinput_open; + hidinput->input.close = hidinput_close; + + hidinput->input.name = hid->name; + hidinput->input.idbus = BUS_USB; + hidinput->input.idvendor = dev->descriptor.idVendor; + hidinput->input.idproduct = dev->descriptor.idProduct; + hidinput->input.idversion = dev->descriptor.bcdDevice; + } + for (i = 0; i < report->maxfield; i++) for (j = 0; j < report->field[i]->maxusage; j++) - hidinput_configure_usage(hid, report->field[i], report->field[i]->usage + j); + hidinput_configure_usage(hidinput, report->field[i], + report->field[i]->usage + j); + + if (hid->quirks & HID_QUIRK_MULTI_INPUT) { + /* This will leave hidinput NULL, so that it + * allocates another one if we have more inputs on + * the same interface. Some devices (e.g. Happ's + * UGCI) cram a lot of unrelated inputs into the + * same interface. */ + hidinput->report = report; + input_register_device(&hidinput->input); + hidinput = NULL; + } + list = list->next; } } - input_register_device(&hid->input); + if (hidinput) + input_register_device(&hidinput->input); return 0; } void hidinput_disconnect(struct hid_device *hid) { - input_unregister_device(&hid->input); + struct list_head *lh, *next; + struct hid_input *hidinput; + + list_for_each_safe (lh, next, &hid->inputs) { + hidinput = list_entry(lh, struct hid_input, list); + input_unregister_device(&hidinput->input); + list_del(&hidinput->list); + kfree(hidinput); + } } diff -Nru a/drivers/usb/hid.h b/drivers/usb/hid.h --- a/drivers/usb/hid.h Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/hid.h Thu Jun 19 17:28:36 2003 @@ -186,10 +186,13 @@ #define HID_QUIRK_NOTOUCH 0x02 #define HID_QUIRK_IGNORE 0x04 #define HID_QUIRK_NOGET 0x08 +#define HID_QUIRK_HIDDEV 0x10 +#define HID_QUIRK_BADPAD 0x20 +#define HID_QUIRK_MULTI_INPUT 0x40 /* - * This is the global enviroment of the parser. This information is - * persistent for main-items. The global enviroment can be saved and + * This is the global environment of the parser. This information is + * persistent for main-items. The global environment can be saved and * restored with PUSH/POP statements. */ @@ -207,15 +210,17 @@ }; /* - * This is the local enviroment. It is resistent up the next main-item. + * This is the local environment. It is persistent up the next main-item. */ #define HID_MAX_DESCRIPTOR_SIZE 4096 #define HID_MAX_USAGES 1024 #define HID_MAX_APPLICATIONS 16 +#define HID_DEFAULT_NUM_COLLECTIONS 16 struct hid_local { unsigned usage[HID_MAX_USAGES]; /* usage array */ + unsigned collection_index[HID_MAX_USAGES]; /* collection index array */ unsigned usage_index; unsigned usage_minimum; unsigned delimiter_depth; @@ -230,10 +235,12 @@ struct hid_collection { unsigned type; unsigned usage; + unsigned level; }; struct hid_usage { unsigned hid; /* hid usage code */ + unsigned collection_index; /* index into collection array */ __u16 code; /* input driver code */ __u8 type; /* input driver type */ __s8 hat_min; /* hat switch fun */ @@ -259,6 +266,7 @@ unsigned unit_exponent; unsigned unit; struct hid_report *report; /* associated report */ + unsigned index; /* index into report->field[] */ }; #define HID_MAX_FIELDS 64 @@ -294,10 +302,18 @@ #define HID_CLAIMED_INPUT 1 #define HID_CLAIMED_HIDDEV 2 +struct hid_input { + struct list_head list; + struct hid_report *report; + struct input_dev input; +}; + struct hid_device { /* device report descriptor */ __u8 *rdesc; unsigned rsize; - unsigned application[HID_MAX_APPLICATIONS]; /* List of HID applications */ + struct hid_collection *collection; /* List of HID collections */ + unsigned collection_size; /* Number of allocated hid_collections */ + unsigned maxcollection; /* Number of parsed collections */ unsigned maxapplication; /* Number of applications */ unsigned version; /* HID version */ unsigned country; /* HID country */ @@ -316,7 +332,7 @@ unsigned claimed; /* Claimed by hidinput, hiddev? */ unsigned quirks; /* Various quirks the device can pull on us */ - struct input_dev input; /* The input structure */ + struct list_head inputs; /* The list of inputs */ void *hiddev; /* The hiddev structure */ int minor; /* Hiddev minor number */ @@ -332,7 +348,7 @@ struct hid_global global_stack[HID_GLOBAL_STACK_SIZE]; unsigned global_stack_ptr; struct hid_local local; - struct hid_collection collection_stack[HID_COLLECTION_STACK_SIZE]; + unsigned collection_stack[HID_COLLECTION_STACK_SIZE]; unsigned collection_stack_ptr; struct hid_device *device; }; diff -Nru a/drivers/usb/hiddev.c b/drivers/usb/hiddev.c --- a/drivers/usb/hiddev.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/hiddev.c Thu Jun 19 17:28:35 2003 @@ -50,9 +50,10 @@ }; struct hiddev_list { - struct hiddev_event buffer[HIDDEV_BUFFER_SIZE]; + struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE]; int head; int tail; + unsigned flags; struct fasync_struct *fasync; struct hiddev *hiddev; struct hiddev_list *next; @@ -70,7 +71,8 @@ static struct hid_report * hiddev_lookup_report(struct hid_device *hid, struct hiddev_report_info *rinfo) { - struct hid_report_enum *report_enum; + unsigned flags = rinfo->report_id & ~HID_REPORT_ID_MASK; + struct hid_report_enum *report_enum = NULL; struct list_head *list; if (rinfo->report_type < HID_REPORT_TYPE_MIN || @@ -78,27 +80,29 @@ report_enum = hid->report_enum + (rinfo->report_type - HID_REPORT_TYPE_MIN); - if ((rinfo->report_id & ~HID_REPORT_ID_MASK) != 0) { - switch (rinfo->report_id & ~HID_REPORT_ID_MASK) { - case HID_REPORT_ID_FIRST: - list = report_enum->report_list.next; - if (list == &report_enum->report_list) return NULL; - rinfo->report_id = ((struct hid_report *) list)->id; - break; - - case HID_REPORT_ID_NEXT: - list = (struct list_head *) - report_enum->report_id_hash[rinfo->report_id & - HID_REPORT_ID_MASK]; - if (list == NULL) return NULL; - list = list->next; - if (list == &report_enum->report_list) return NULL; - rinfo->report_id = ((struct hid_report *) list)->id; - break; - - default: - return NULL; - } + + switch (flags) { + case 0: /* Nothing to do -- report_id is already set correctly */ + break; + + case HID_REPORT_ID_FIRST: + list = report_enum->report_list.next; + if (list == &report_enum->report_list) return NULL; + rinfo->report_id = ((struct hid_report *) list)->id; + break; + + case HID_REPORT_ID_NEXT: + list = (struct list_head *) + report_enum->report_id_hash[rinfo->report_id & + HID_REPORT_ID_MASK]; + if (list == NULL) return NULL; + list = list->next; + if (list == &report_enum->report_list) return NULL; + rinfo->report_id = ((struct hid_report *) list)->id; + break; + + default: + return NULL; } return report_enum->report_id_hash[rinfo->report_id]; @@ -142,21 +146,20 @@ return NULL; } -/* - * This is where hid.c calls into hiddev to pass an event that occurred over - * the interrupt pipe - */ -void hiddev_hid_event(struct hid_device *hid, unsigned int usage, int value) +static void hiddev_send_event(struct hid_device *hid, + struct hiddev_usage_ref *uref) { struct hiddev *hiddev = hid->hiddev; struct hiddev_list *list = hiddev->list; while (list) { - list->buffer[list->head].hid = usage; - list->buffer[list->head].value = value; - list->head = (list->head + 1) & (HIDDEV_BUFFER_SIZE - 1); - - kill_fasync(&list->fasync, SIGIO, POLL_IN); + if (uref->field_index != HID_FIELD_INDEX_NONE || + (list->flags & HIDDEV_FLAG_REPORT) != 0) { + list->buffer[list->head] = *uref; + list->head = (list->head + 1) & + (HIDDEV_BUFFER_SIZE - 1); + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } list = list->next; } @@ -165,6 +168,46 @@ } /* + * This is where hid.c calls into hiddev to pass an event that occurred over + * the interrupt pipe + */ +void hiddev_hid_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + unsigned type = field->report_type; + struct hiddev_usage_ref uref; + + uref.report_type = + (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT : + ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : + ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE:0)); + uref.report_id = field->report->id; + uref.field_index = field->index; + uref.usage_index = (usage - field->usage); + uref.usage_code = usage->hid; + uref.value = value; + + hiddev_send_event(hid, &uref); +} + + +void hiddev_report_event(struct hid_device *hid, struct hid_report *report) +{ + unsigned type = report->type; + struct hiddev_usage_ref uref; + + memset(&uref, 0, sizeof(uref)); + uref.report_type = + (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT : + ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : + ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE:0)); + uref.report_id = report->id; + uref.field_index = HID_FIELD_INDEX_NONE; + + hiddev_send_event(hid, &uref); +} + +/* * fasync file op */ static int hiddev_fasync(int fd, struct file *file, int on) @@ -193,7 +236,6 @@ struct hiddev_list *list = file->private_data; struct hiddev_list **listptr; - lock_kernel(); listptr = &list->hiddev->list; hiddev_fasync(-1, file, 0); @@ -209,7 +251,6 @@ } kfree(list); - unlock_kernel(); return 0; } @@ -259,43 +300,66 @@ { DECLARE_WAITQUEUE(wait, current); struct hiddev_list *list = file->private_data; + int event_size; int retval = 0; - if (list->head == list->tail) { + event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ? + sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event); - add_wait_queue(&list->hiddev->wait, &wait); - set_current_state(TASK_INTERRUPTIBLE); + if (count < event_size) return 0; - while (list->head == list->tail) { - - if (file->f_flags & O_NONBLOCK) { - retval = -EAGAIN; - break; - } - if (signal_pending(current)) { - retval = -ERESTARTSYS; - break; - } - if (!list->hiddev->exist) { - retval = -EIO; - break; + while (retval == 0) { + if (list->head == list->tail) { + add_wait_queue(&list->hiddev->wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + while (list->head == list->tail) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (!list->hiddev->exist) { + retval = -EIO; + break; + } + + schedule(); } - schedule(); + set_current_state(TASK_RUNNING); + remove_wait_queue(&list->hiddev->wait, &wait); } - set_current_state(TASK_RUNNING); - remove_wait_queue(&list->hiddev->wait, &wait); - } + if (retval) + return retval; - if (retval) - return retval; + while (list->head != list->tail && + retval + event_size <= count) { + if ((list->flags & HIDDEV_FLAG_UREF) == 0) { + if (list->buffer[list->tail].field_index != + HID_FIELD_INDEX_NONE) { + struct hiddev_event event; + event.hid = list->buffer[list->tail].usage_code; + event.value = list->buffer[list->tail].value; + if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event))) + return -EFAULT; + retval += sizeof(struct hiddev_event); + } + } else { + if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE || + (list->flags & HIDDEV_FLAG_REPORT) != 0) { + if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref))) + return -EFAULT; + retval += sizeof(struct hiddev_usage_ref); + } + } + list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1); + } - while (list->head != list->tail && retval + sizeof(struct hiddev_event) <= count) { - if (copy_to_user(buffer + retval, list->buffer + list->tail, - sizeof(struct hiddev_event))) return -EFAULT; - list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1); - retval += sizeof(struct hiddev_event); } return retval; @@ -329,10 +393,14 @@ struct hiddev *hiddev = list->hiddev; struct hid_device *hid = hiddev->hid; struct usb_device *dev = hid->dev; + struct hiddev_collection_info cinfo; struct hiddev_report_info rinfo; + struct hiddev_field_info finfo; struct hiddev_usage_ref uref; + struct hiddev_devinfo dinfo; struct hid_report *report; struct hid_field *field; + int i; if (!hiddev->exist) return -EIO; @@ -344,11 +412,18 @@ case HIDIOCAPPLICATION: if (arg < 0 || arg >= hid->maxapplication) return -EINVAL; - return hid->application[arg]; + + for (i = 0; i < hid->maxcollection; i++) + if (hid->collection[i].type == + HID_COLLECTION_APPLICATION && arg-- == 0) + break; + + if (i == hid->maxcollection) + return -EINVAL; + + return hid->collection[i].usage; case HIDIOCGDEVINFO: - { - struct hiddev_devinfo dinfo; dinfo.bustype = BUS_USB; dinfo.busnum = dev->bus->busnum; dinfo.devnum = dev->devnum; @@ -358,7 +433,25 @@ dinfo.version = dev->descriptor.bcdDevice; dinfo.num_applications = hid->maxapplication; return copy_to_user((void *) arg, &dinfo, sizeof(dinfo)); - } + + case HIDIOCGFLAG: + return put_user(list->flags, (int *) arg); + + case HIDIOCSFLAG: + { + int newflags; + if (get_user(newflags, (int *) arg)) + return -EFAULT; + + if ((newflags & ~HIDDEV_FLAGS) != 0 || + ((newflags & HIDDEV_FLAG_REPORT) != 0 && + (newflags & HIDDEV_FLAG_UREF) == 0)) + return -EINVAL; + + list->flags = newflags; + + return 0; + } case HIDIOCGSTRING: { @@ -387,7 +480,6 @@ } case HIDIOCINITREPORT: - hid_init_reports(hid); return 0; @@ -432,8 +524,6 @@ return copy_to_user((void *) arg, &rinfo, sizeof(rinfo)); case HIDIOCGFIELDINFO: - { - struct hiddev_field_info finfo; if (copy_from_user(&finfo, (void *) arg, sizeof(finfo))) return -EFAULT; rinfo.report_type = finfo.report_type; @@ -462,7 +552,6 @@ finfo.unit = field->unit; return copy_to_user((void *) arg, &finfo, sizeof(finfo)); - } case HIDIOCGUCODE: if (copy_from_user(&uref, (void *) arg, sizeof(uref))) @@ -485,9 +574,15 @@ return copy_to_user((void *) arg, &uref, sizeof(uref)); case HIDIOCGUSAGE: + case HIDIOCSUSAGE: + case HIDIOCGCOLLECTIONINDEX: if (copy_from_user(&uref, (void *) arg, sizeof(uref))) return -EFAULT; + if (cmd == HIDIOCSUSAGE && + uref.report_type == HID_REPORT_TYPE_INPUT) + return -EINVAL; + if (uref.report_id == HID_REPORT_ID_UNKNOWN) { field = hiddev_lookup_usage(hid, &uref); if (field == NULL) @@ -506,38 +601,32 @@ return -EINVAL; } - uref.value = field->value[uref.usage_index]; + switch (cmd) { + case HIDIOCGUSAGE: + uref.value = field->value[uref.usage_index]; + return copy_to_user((void *) arg, &uref, sizeof(uref)); + + case HIDIOCSUSAGE: + field->value[uref.usage_index] = uref.value; + return 0; - return copy_to_user((void *) arg, &uref, sizeof(uref)); + case HIDIOCGCOLLECTIONINDEX: + return field->usage[uref.usage_index].collection_index; + } + break; - case HIDIOCSUSAGE: - if (copy_from_user(&uref, (void *) arg, sizeof(uref))) + case HIDIOCGCOLLECTIONINFO: + if (copy_from_user(&cinfo, (void *) arg, sizeof(cinfo))) return -EFAULT; - if (uref.report_type == HID_REPORT_TYPE_INPUT) + if (cinfo.index >= hid->maxcollection) return -EINVAL; - if (uref.report_id == HID_REPORT_ID_UNKNOWN) { - field = hiddev_lookup_usage(hid, &uref); - if (field == NULL) - return -EINVAL; - } else { - rinfo.report_type = uref.report_type; - rinfo.report_id = uref.report_id; - if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) - return -EINVAL; - - if (uref.field_index >= report->maxfield) - return -EINVAL; - - field = report->field[uref.field_index]; - if (uref.usage_index >= field->maxusage) - return -EINVAL; - } + cinfo.type = hid->collection[cinfo.index].type; + cinfo.usage = hid->collection[cinfo.index].usage; + cinfo.level = hid->collection[cinfo.index].level; - field->value[uref.usage_index] = uref.value; - - return 0; + return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)); default: @@ -576,12 +665,18 @@ int minor, i; char devfs_name[16]; - for (i = 0; i < hid->maxapplication; i++) - if (!IS_INPUT_APPLICATION(hid->application[i])) - break; - if (i == hid->maxapplication) - return -1; + + if ((hid->quirks & HID_QUIRK_HIDDEV) == 0) { + for (i = 0; i < hid->maxcollection; i++) + if (hid->collection[i].type == + HID_COLLECTION_APPLICATION && + !IS_INPUT_APPLICATION(hid->collection[i].usage)) + break; + + if (i == hid->maxcollection) + return -1; + } for (minor = 0; minor < HIDDEV_MINORS && hiddev_table[minor]; minor++); if (minor == HIDDEV_MINORS) { diff -Nru a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c --- a/drivers/usb/host/ehci-dbg.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/host/ehci-dbg.c Thu Jun 19 17:28:35 2003 @@ -125,19 +125,28 @@ #ifdef DEBUG static void __attribute__((__unused__)) +dbg_qtd (char *label, struct ehci_hcd *ehci, struct ehci_qtd *qtd) +{ + ehci_dbg (ehci, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + cpu_to_le32p (&qtd->hw_next), + cpu_to_le32p (&qtd->hw_alt_next), + cpu_to_le32p (&qtd->hw_token), + cpu_to_le32p (&qtd->hw_buf [0])); + if (qtd->hw_buf [1]) + ehci_dbg (ehci, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + cpu_to_le32p (&qtd->hw_buf [1]), + cpu_to_le32p (&qtd->hw_buf [2]), + cpu_to_le32p (&qtd->hw_buf [3]), + cpu_to_le32p (&qtd->hw_buf [4])); +} + +static void __attribute__((__unused__)) dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) { - dbg ("%s %p n%08x info1 %x info2 %x hw_curr %x qtd_next %x", label, + ehci_dbg (ehci, "%s qh %p n%08x info %x %x qtd %x\n", label, qh, qh->hw_next, qh->hw_info1, qh->hw_info2, - qh->hw_current, qh->hw_qtd_next); - dbg (" alt+nak+t= %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]); - } + qh->hw_current); + dbg_qtd ("overlay", ehci, (struct ehci_qtd *) &qh->hw_qtd_next); } static int __attribute__((__unused__)) @@ -294,8 +303,7 @@ return '*'; if (token & QTD_STS_HALT) return '-'; - if (QTD_PID (token) != 1 /* not IN: OUT or SETUP */ - || QTD_LENGTH (token) == 0) + if (!IS_SHORT_READ (token)) return ' '; /* tries to advance through hw_alt_next */ return '/'; @@ -317,11 +325,14 @@ char *next = *nextp; char mark; - mark = token_mark (qh->hw_token); + if (qh->hw_qtd_next == EHCI_LIST_END) /* NEC does this */ + mark = '@'; + else + mark = token_mark (qh->hw_token); if (mark == '/') { /* qh_alt_next controls qh advance? */ if ((qh->hw_alt_next & QTD_MASK) == ehci->async->hw_alt_next) mark = '#'; /* blocked */ - else if (qh->hw_alt_next & cpu_to_le32 (0x01)) + else if (qh->hw_alt_next == EHCI_LIST_END) mark = '.'; /* use hw_qtd_next */ /* else alt_next points to some other qtd */ } @@ -334,7 +345,7 @@ (scratch >> 8) & 0x000f, scratch, cpu_to_le32p (&qh->hw_info2), cpu_to_le32p (&qh->hw_token), mark, - (cpu_to_le32 (0x8000000) & qh->hw_token) + (__constant_cpu_to_le32 (QTD_TOGGLE) & qh->hw_token) ? "data0" : "data1", (cpu_to_le32p (&qh->hw_alt_next) >> 1) & 0x0f); size -= temp; @@ -400,6 +411,8 @@ char *next; struct ehci_qh *qh; + *buf = 0; + pdev = container_of (dev, struct pci_dev, dev); ehci = container_of (pci_get_drvdata (pdev), struct ehci_hcd, hcd); next = buf; @@ -422,7 +435,7 @@ } spin_unlock_irqrestore (&ehci->lock, flags); - return PAGE_SIZE - size; + return strlen (buf); } static DEVICE_ATTR (async, S_IRUGO, show_async, NULL); @@ -558,7 +571,8 @@ /* Capability Registers */ i = readw (&ehci->caps->hci_version); temp = snprintf (next, size, - "EHCI %x.%02x, hcd state %d (version " DRIVER_VERSION ")\n", + "%s\nEHCI %x.%02x, hcd state %d (driver " DRIVER_VERSION ")\n", + pdev->dev.name, i >> 8, i & 0x0ff, ehci->hcd.state); size -= temp; next += temp; diff -Nru a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c --- a/drivers/usb/host/ehci-hcd.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/host/ehci-hcd.c Thu Jun 19 17:28:35 2003 @@ -31,6 +31,7 @@ #include #include #include +#include /* for generic_ffs */ #ifdef CONFIG_USB_DEBUG #define DEBUG @@ -41,11 +42,7 @@ #include #include -#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,32) #include "../hcd.h" -#else -#include "../core/hcd.h" -#endif #include #include @@ -94,11 +91,11 @@ * 2001-June Works with usb-storage and NEC EHCI on 2.4 */ -#define DRIVER_VERSION "2003-Jan-22" +#define DRIVER_VERSION "2003-Jun-19/2.4" #define DRIVER_AUTHOR "David Brownell" #define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver" -static const char hcd_name [] = "ehci-hcd"; +static const char hcd_name [] = "ehci_hcd"; // #define EHCI_VERBOSE_DEBUG @@ -118,8 +115,10 @@ #define EHCI_TUNE_MULT_TT 1 #define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ -#define EHCI_WATCHDOG_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#define EHCI_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ #define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ +#define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */ /* Initial IRQ latency: lower than default */ static int log2_irq_thresh = 0; // 0 to 6 @@ -268,16 +267,13 @@ } } + /* stop async processing after it's idled a bit */ + if (test_bit (TIMER_ASYNC_OFF, &ehci->actions)) + start_unlink_async (ehci, ehci->async); + + /* ehci could run by timer, without IRQs ... */ ehci_work (ehci, NULL); - if (ehci->reclaim && !timer_pending (&ehci->watchdog)) - mod_timer (&ehci->watchdog, - jiffies + EHCI_WATCHDOG_JIFFIES); - /* stop async processing after it's idled a while */ - else if (ehci->async_idle) { - start_unlink_async (ehci, ehci->async); - ehci->async_idle = 0; - } spin_unlock_irqrestore (&ehci->lock, flags); } @@ -437,7 +433,7 @@ pci_set_mwi (ehci->hcd.pdev); /* clear interrupt enables, set irq latency */ - temp = readl (&ehci->regs->command) & 0xff; + temp = readl (&ehci->regs->command) & 0x0fff; if (log2_irq_thresh < 0 || log2_irq_thresh > 6) log2_irq_thresh = 0; temp |= 1 << (16 + log2_irq_thresh); @@ -660,11 +656,18 @@ */ static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs) { + timer_action_done (ehci, TIMER_IO_WATCHDOG); if (ehci->reclaim_ready) end_unlink_async (ehci, regs); scan_async (ehci, regs); if (ehci->next_uframe != -1) scan_periodic (ehci, regs); + + /* the IO watchdog guards against hardware or driver bugs that + * misplace IRQs, and should let us run completely without IRQs. + */ + if ((ehci->async->qh_next.ptr != 0) || (ehci->periodic_sched != 0)) + timer_action (ehci, TIMER_IO_WATCHDOG); } /*-------------------------------------------------------------------------*/ @@ -1039,8 +1042,8 @@ static int __init init (void) { - dbg (DRIVER_INFO); - dbg ("block sizes: qh %Zd qtd %Zd itd %Zd sitd %Zd", + pr_debug ("%s: block sizes: qh %Zd qtd %Zd itd %Zd sitd %Zd\n", + hcd_name, sizeof (struct ehci_qh), sizeof (struct ehci_qtd), sizeof (struct ehci_itd), sizeof (struct ehci_sitd)); diff -Nru a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c --- a/drivers/usb/host/ehci-q.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/host/ehci-q.c Thu Jun 19 17:28:35 2003 @@ -88,7 +88,6 @@ static inline void qh_update (struct ehci_hcd *ehci, 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; @@ -99,8 +98,6 @@ /*-------------------------------------------------------------------------*/ -#define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1) - static void qtd_copy_status ( struct ehci_hcd *ehci, struct urb *urb, @@ -314,16 +311,15 @@ /* hardware copies qtd out of qh overlay */ rmb (); token = le32_to_cpu (qtd->hw_token); - stopped = stopped - || (HALT_BIT & qh->hw_token) != 0 - || (ehci->hcd.state == USB_STATE_HALT); /* always clean up qtds the hc de-activated */ if ((token & QTD_STS_ACTIVE) == 0) { - /* magic dummy for short reads; won't advance */ - if (IS_SHORT_READ (token) - && !(token & QTD_STS_HALT) + if ((token & QTD_STS_HALT) != 0) { + stopped = 1; + + /* magic dummy for some short reads; qh won't advance */ + } else if (IS_SHORT_READ (token) && (qh->hw_alt_next & QTD_MASK) == ehci->async->hw_alt_next) { stopped = 1; @@ -331,10 +327,13 @@ } /* stop scanning when we reach qtds the hc is using */ - } else if (likely (!stopped)) { + } else if (likely (!stopped + && HCD_IS_RUNNING (ehci->hcd.state))) { break; } else { + stopped = 1; + /* ignore active urbs unless some previous qtd * for the urb faulted (including short read) or * its urb was canceled. we may patch qh or qtds. @@ -393,12 +392,20 @@ qh->qh_state = state; /* update qh after fault cleanup */ - if (unlikely ((HALT_BIT & qh->hw_token) != 0)) { - qh_update (ehci, qh, - list_empty (&qh->qtd_list) - ? qh->dummy - : list_entry (qh->qtd_list.next, - struct ehci_qtd, qtd_list)); + if (unlikely (stopped != 0) + /* some EHCI 0.95 impls will overlay dummy qtds */ + || qh->hw_qtd_next == EHCI_LIST_END) { + if (list_empty (&qh->qtd_list)) + end = qh->dummy; + else { + end = list_entry (qh->qtd_list.next, + struct ehci_qtd, qtd_list); + /* first qtd may already be partially processed */ + if (cpu_to_le32 (end->qtd_dma) == qh->hw_current) + end = 0; + } + if (end) + qh_update (ehci, qh, end); } return count; @@ -718,12 +725,11 @@ /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ - /* init as halted, toggle clear, advance to dummy */ + /* init as live, toggle clear, advance to dummy */ qh->qh_state = QH_STATE_IDLE; qh->hw_info1 = cpu_to_le32 (info1); qh->hw_info2 = cpu_to_le32 (info2); qh_update (ehci, qh, qh->dummy); - qh->hw_token = cpu_to_le32 (QTD_STS_HALT); usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1); return qh; } @@ -741,8 +747,7 @@ /* (re)start the async schedule? */ head = ehci->async; - if (ehci->async_idle) - del_timer (&ehci->watchdog); + timer_action_done (ehci, TIMER_ASYNC_OFF); if (!head->qh_next.qh) { u32 cmd = readl (&ehci->regs->command); @@ -773,8 +778,6 @@ qh->qh_state = QH_STATE_LINKED; /* qtd completions reported later by interrupt */ - - ehci->async_idle = 0; } /*-------------------------------------------------------------------------*/ @@ -831,9 +834,8 @@ } } - /* FIXME: changing config or interface setting is not - * supported yet. preferred fix is for usbcore to tell - * us to clear out each endpoint's state, but... + /* NOTE: changing config or interface setting is not + * supported without the 2.5 endpoint disable logic. */ /* usb_clear_halt() means qh data toggle gets reset */ @@ -955,17 +957,17 @@ struct ehci_qh *qh = ehci->reclaim; struct ehci_qh *next; - del_timer (&ehci->watchdog); + timer_action_done (ehci, TIMER_IAA_WATCHDOG); // qh->hw_next = cpu_to_le32 (qh->qh_dma); qh->qh_state = QH_STATE_IDLE; qh->qh_next.qh = 0; qh_put (ehci, qh); // refcount from reclaim - ehci->reclaim = 0; - ehci->reclaim_ready = 0; /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ next = qh->reclaim; + ehci->reclaim = next; + ehci->reclaim_ready = 0; qh->reclaim = 0; qh_completions (ehci, qh, regs); @@ -980,16 +982,14 @@ * active but idle for a while once it empties. */ if (HCD_IS_RUNNING (ehci->hcd.state) - && ehci->async->qh_next.qh == 0 - && !timer_pending (&ehci->watchdog)) { - ehci->async_idle = 1; - mod_timer (&ehci->watchdog, - jiffies + EHCI_ASYNC_JIFFIES); - } + && ehci->async->qh_next.qh == 0) + timer_action (ehci, TIMER_ASYNC_OFF); } - if (next) + if (next) { + ehci->reclaim = 0; start_unlink_async (ehci, next); + } } /* makes sure the async qh will become idle */ @@ -1020,6 +1020,7 @@ wmb (); // handshake later, if we need to } + timer_action_done (ehci, TIMER_ASYNC_OFF); return; } @@ -1045,9 +1046,8 @@ ehci->reclaim_ready = 0; cmd |= CMD_IAAD; writel (cmd, &ehci->regs->command); - /* posted write need not be known to HC yet ... */ - - mod_timer (&ehci->watchdog, jiffies + EHCI_WATCHDOG_JIFFIES); + (void) readl (&ehci->regs->command); + timer_action (ehci, TIMER_IAA_WATCHDOG); } /*-------------------------------------------------------------------------*/ @@ -1056,10 +1056,11 @@ scan_async (struct ehci_hcd *ehci, struct pt_regs *regs) { struct ehci_qh *qh; - int unlink_delay = 0; + enum ehci_timer_action action = TIMER_IO_WATCHDOG; if (!++(ehci->stamp)) ehci->stamp++; + timer_action_done (ehci, TIMER_ASYNC_SHRINK); rescan: qh = ehci->async->qh_next.qh; if (likely (qh != 0)) { @@ -1091,17 +1092,15 @@ */ if (list_empty (&qh->qtd_list)) { if (qh->stamp == ehci->stamp) - unlink_delay = 1; - else if (!ehci->reclaim) { + action = TIMER_ASYNC_SHRINK; + else if (!ehci->reclaim + && qh->qh_state == QH_STATE_LINKED) start_unlink_async (ehci, qh); - unlink_delay = 0; - } } qh = qh->qh_next.qh; } while (qh); } - - if (unlink_delay && !timer_pending (&ehci->watchdog)) - mod_timer (&ehci->watchdog, jiffies + EHCI_WATCHDOG_JIFFIES/2); + if (action == TIMER_ASYNC_SHRINK) + timer_action (ehci, TIMER_ASYNC_SHRINK); } diff -Nru a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h --- a/drivers/usb/host/ehci.h Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/host/ehci.h Thu Jun 19 17:28:36 2003 @@ -52,8 +52,7 @@ /* async schedule support */ struct ehci_qh *async; struct ehci_qh *reclaim; - int reclaim_ready : 1, - async_idle : 1; + int reclaim_ready : 1; /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ @@ -83,6 +82,7 @@ struct timer_list watchdog; struct notifier_block reboot_notifier; + unsigned long actions; unsigned stamp; /* irq statistics */ @@ -100,6 +100,53 @@ /* NOTE: urb->transfer_flags expected to not use this bit !!! */ #define EHCI_STATE_UNLINK 0x8000 /* urb being unlinked */ +enum ehci_timer_action { + TIMER_IO_WATCHDOG, + TIMER_IAA_WATCHDOG, + TIMER_ASYNC_SHRINK, + TIMER_ASYNC_OFF, +}; + +static inline void +timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action) +{ + clear_bit (action, &ehci->actions); +} + +static inline void +timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) +{ + if (!test_and_set_bit (action, &ehci->actions)) { + unsigned long t; + + switch (action) { + case TIMER_IAA_WATCHDOG: + t = EHCI_IAA_JIFFIES; + break; + case TIMER_IO_WATCHDOG: + t = EHCI_IO_JIFFIES; + break; + case TIMER_ASYNC_OFF: + t = EHCI_ASYNC_JIFFIES; + break; + // case TIMER_ASYNC_SHRINK: + default: + t = EHCI_SHRINK_JIFFIES; + break; + } + t += jiffies; + // all timings except IAA watchdog can be overridden. + // async queue SHRINK often precedes IAA. while it's ready + // to go OFF neither can matter, and afterwards the IO + // watchdog stops unless there's still periodic traffic. + if (action != TIMER_IAA_WATCHDOG + && t > ehci->watchdog.expires + && timer_pending (&ehci->watchdog)) + return; + mod_timer (&ehci->watchdog, t); + } +} + /*-------------------------------------------------------------------------*/ /* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ @@ -243,7 +290,10 @@ size_t length; /* length of buffer */ } __attribute__ ((aligned (32))); -#define QTD_MASK cpu_to_le32 (~0x1f) /* mask NakCnt+T in qh->hw_alt_next */ +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK __constant_cpu_to_le32 (~0x1f) + +#define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1) /*-------------------------------------------------------------------------*/ diff -Nru a/drivers/usb/hub.c b/drivers/usb/hub.c --- a/drivers/usb/hub.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/hub.c Thu Jun 19 17:28:35 2003 @@ -38,7 +38,7 @@ static LIST_HEAD(hub_list); /* List containing all of the hubs (for cleanup) */ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait); -static int khubd_pid = 0; /* PID of khubd */ +static pid_t khubd_pid = 0; /* PID of khubd */ static DECLARE_COMPLETION(khubd_exited); #ifdef DEBUG @@ -716,8 +716,6 @@ break; } - hub->children[port] = dev; - /* Reset the device */ if (usb_hub_port_reset(hub, port, dev, delay)) { usb_free_dev(dev); @@ -761,8 +759,10 @@ dev->bus->bus_name, dev->devpath, dev->devnum); /* Run it through the hoops (find a driver, etc) */ - if (!usb_new_device(dev)) + if (!usb_new_device(dev)) { + hub->children[port] = dev; goto done; + } /* Free the configuration if there was an error */ usb_free_dev(dev); @@ -771,7 +771,6 @@ delay = HUB_LONG_RESET_TIME; } - hub->children[port] = NULL; usb_hub_port_disable(hub, port); done: up(&usb_address0_sem); @@ -949,7 +948,7 @@ */ int usb_hub_init(void) { - int pid; + pid_t pid; if (usb_register(&hub_driver) < 0) { err("Unable to register USB hub driver"); diff -Nru a/drivers/usb/kaweth.c b/drivers/usb/kaweth.c --- a/drivers/usb/kaweth.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/kaweth.c Thu Jun 19 17:28:35 2003 @@ -114,6 +114,8 @@ MODULE_DESCRIPTION("KL5USB101 USB Ethernet driver"); MODULE_LICENSE("GPL"); +static const char driver_name[] = "kaweth"; + static void *kaweth_probe( struct usb_device *dev, /* the device */ unsigned ifnum, /* what interface */ @@ -170,7 +172,7 @@ * kaweth_driver ****************************************************************/ static struct usb_driver kaweth_driver = { - .name = "kaweth", + .name = driver_name, .probe = kaweth_probe, .disconnect = kaweth_disconnect, .id_table = usb_klsi_table, @@ -659,7 +661,7 @@ switch (ethcmd) { case ETHTOOL_GDRVINFO: { struct ethtool_drvinfo info = {ETHTOOL_GDRVINFO}; - strncpy(info.driver, "kaweth", sizeof(info.driver)-1); + strncpy(info.driver, driver_name, sizeof(info.driver)-1); if (copy_to_user(useraddr, &info, sizeof(info))) return -EFAULT; return 0; diff -Nru a/drivers/usb/pegasus.c b/drivers/usb/pegasus.c --- a/drivers/usb/pegasus.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/pegasus.c Thu Jun 19 17:28:35 2003 @@ -42,7 +42,7 @@ /* * Version Information */ -#define DRIVER_VERSION "v0.4.30 (2003/04/01)" +#define DRIVER_VERSION "v0.4.32 (2003/06/06)" #define DRIVER_AUTHOR "Petko Manolov " #define DRIVER_DESC "Pegasus/Pegasus II USB Ethernet driver" @@ -582,18 +582,34 @@ static void write_bulk_callback(struct urb *urb) { pegasus_t *pegasus = urb->context; + struct net_device *net = pegasus->net; if (!pegasus || !(pegasus->flags & PEGASUS_RUNNING)) return; - if (!netif_device_present(pegasus->net)) + if (!netif_device_present(net)) return; - if (urb->status) - info("%s: TX status %d", pegasus->net->name, urb->status); + switch (urb->status) { + case -EPIPE: + /* FIXME schedule_work() to clear the tx halt */ + netif_stop_queue(net); + warn("%s: no tx stall recovery", net->name); + return; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dbg("%s: tx unlink, %d", net->name, urb->status); + return; + default: + info("%s: TX status %d", net->name, urb->status); + /* FALL THROUGH */ + case 0: + break; + } - pegasus->net->trans_start = jiffies; - netif_wake_queue(pegasus->net); + net->trans_start = jiffies; + netif_wake_queue(net); } #ifdef PEGASUS_USE_INTR @@ -665,9 +681,16 @@ write_bulk_callback, pegasus); pegasus->tx_urb->transfer_buffer_length = count; if ((res = usb_submit_urb(pegasus->tx_urb))) { - warn("failed tx_urb %d", res); - pegasus->stats.tx_errors++; - netif_start_queue(net); + switch (res) { + case -EPIPE: /* stall, or disconnect from TT */ + /* cleanup should already have been scheduled */ + break; + case -ENODEV: /* disconnect() upcoming */ + break; + default: + pegasus->stats.tx_errors++; + netif_start_queue(net); + } } else { pegasus->stats.tx_packets++; pegasus->stats.tx_bytes += skb->len; @@ -793,11 +816,16 @@ switch (ethcmd) { /* get driver-specific version/etc. info */ case ETHTOOL_GDRVINFO:{ - struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO }; + struct ethtool_drvinfo info; + + memset (&info, 0, sizeof info); + info.cmd = ETHTOOL_GDRVINFO; strncpy(info.driver, driver_name, sizeof (info.driver) - 1); strncpy(info.version, DRIVER_VERSION, sizeof (info.version) - 1); + usb_make_path(pegasus->usb, info.bus_info, + sizeof info.bus_info); if (copy_to_user(useraddr, &info, sizeof (info))) return -EFAULT; return 0; @@ -860,20 +888,22 @@ { pegasus_t *pegasus; int cmd; - char tmp[128]; pegasus = net->priv; if (get_user(cmd, (int *) uaddr)) return -EFAULT; switch (cmd) { case ETHTOOL_GDRVINFO:{ - struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO }; - strncpy(info.driver, DRIVER_DESC, ETHTOOL_BUSINFO_LEN); + struct ethtool_drvinfo info; + + memset (&info, 0, sizeof info); + info.cmd = ETHTOOL_GDRVINFO; + strncpy(info.driver, driver_name, + sizeof (info.driver) - 1); strncpy(info.version, DRIVER_VERSION, - ETHTOOL_BUSINFO_LEN); - sprintf(tmp, "usb%d:%d", pegasus->usb->bus->busnum, - pegasus->usb->devnum); - strncpy(info.bus_info, tmp, ETHTOOL_BUSINFO_LEN); + sizeof (info.version) - 1); + usb_make_path(pegasus->usb, info.bus_info, + sizeof (info.bus_info) -1); if (copy_to_user(uaddr, &info, sizeof(info))) return -EFAULT; return 0; @@ -881,6 +911,7 @@ case ETHTOOL_GSET:{ struct ethtool_cmd ecmd; short lpa, bmcr; + u8 port; if (copy_from_user(&ecmd, uaddr, sizeof(ecmd))) return -EFAULT; @@ -890,7 +921,11 @@ SUPPORTED_100baseT_Full | SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII); - ecmd.port = PORT_TP; + get_registers(pegasus, Reg7b, 1, &port); + if (port == 0) + ecmd.port = PORT_MII; + else + ecmd.port = PORT_TP; ecmd.transceiver = XCVR_INTERNAL; ecmd.phy_address = pegasus->phy; read_mii_word(pegasus, pegasus->phy, MII_BMCR, &bmcr); @@ -1013,7 +1048,10 @@ set_register(pegasus, Reg1d, 0); set_register(pegasus, Reg7b, 1); mdelay(100); - set_register(pegasus, Reg7b, 2); + if ((pegasus->features & HAS_HOME_PNA) && mii_mode) + set_register(pegasus, Reg7b, 0); + else + set_register(pegasus, Reg7b, 2); set_register(pegasus, 0x83, data); get_registers(pegasus, 0x83, 1, &data); diff -Nru a/drivers/usb/pegasus.h b/drivers/usb/pegasus.h --- a/drivers/usb/pegasus.h Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/pegasus.h Thu Jun 19 17:28:35 2003 @@ -127,6 +127,7 @@ #define VENDOR_ELCON 0x0db7 #define VENDOR_ELSA 0x05cc #define VENDOR_HAWKING 0x0e66 +#define VENDOR_HP 0x03f0 #define VENDOR_IODATA 0x04bb #define VENDOR_KINGSTON 0x0951 #define VENDOR_LANEED 0x056e @@ -219,6 +220,8 @@ PEGASUS_DEV( "Elsa Micolink USB2Ethernet", VENDOR_ELSA, 0x3000, DEFAULT_GPIO_RESET ) PEGASUS_DEV( "Hawking UF100 10/100 Ethernet", VENDOR_HAWKING, 0x400c, + DEFAULT_GPIO_RESET | PEGASUS_II ) +PEGASUS_DEV( "HP hn210c Ethernet USB", VENDOR_HP, 0x811c, DEFAULT_GPIO_RESET | PEGASUS_II ) PEGASUS_DEV( "IO DATA USB ET/TX", VENDOR_IODATA, 0x0904, DEFAULT_GPIO_RESET ) diff -Nru a/drivers/usb/rtl8150.c b/drivers/usb/rtl8150.c --- a/drivers/usb/rtl8150.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/rtl8150.c Thu Jun 19 17:28:35 2003 @@ -111,8 +111,10 @@ static void *rtl8150_probe(struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id); +static const char driver_name[] = "rtl8150"; + static struct usb_driver rtl8150_driver = { - name: "rtl8150", + name: driver_name, probe: rtl8150_probe, disconnect: rtl8150_disconnect, id_table: rtl8150_table, @@ -155,7 +157,7 @@ clear_bit(RX_REG_SET, &dev->flags); } -static int async_set_registers(rtl8150_t * dev, u16 indx, u16 size, void *data) +static int async_set_registers(rtl8150_t * dev, u16 indx, u16 size) { int ret; @@ -426,7 +428,8 @@ if (rtl8150_reset(dev)) { warn("%s - device reset failed", __FUNCTION__); } - dev->rx_creg = rcr = 0x9e; /* bit7=1 attach Rx info at the end */ + rcr = 0x9e; /* bit7=1 attach Rx info at the end */ + dev->rx_creg = cpu_to_le16(rcr); tcr = 0xd8; cr = 0x0c; set_registers(dev, RCR, 1, &rcr); @@ -471,18 +474,18 @@ dev = netdev->priv; netif_stop_queue(netdev); if (netdev->flags & IFF_PROMISC) { - dev->rx_creg |= 0x0001; + dev->rx_creg |= cpu_to_le16(0x0001); info("%s: promiscuous mode", netdev->name); } else if ((netdev->mc_count > multicast_filter_limit) || (netdev->flags & IFF_ALLMULTI)) { - dev->rx_creg &= 0xfffe; - dev->rx_creg |= 0x0002; + dev->rx_creg &= cpu_to_le16(0xfffe); + dev->rx_creg |= cpu_to_le16(0x0002); info("%s: allmulti set", netdev->name); } else { /* ~RX_MULTICAST, ~RX_PROMISCUOUS */ - dev->rx_creg &= 0x00fc; + dev->rx_creg &= cpu_to_le16(0x00fc); } - async_set_registers(dev, RCR, 2, &dev->rx_creg); + async_set_registers(dev, RCR, 2); netif_wake_queue(netdev); } @@ -577,7 +580,7 @@ case ETHTOOL_GDRVINFO:{ struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO }; - strncpy(info.driver, DRIVER_DESC, ETHTOOL_BUSINFO_LEN); + strncpy(info.driver, driver_name, ETHTOOL_BUSINFO_LEN); strncpy(info.version, DRIVER_VERSION, ETHTOOL_BUSINFO_LEN); sprintf(tmp, "usb%d:%d", dev->udev->bus->busnum, diff -Nru a/drivers/usb/scanner.c b/drivers/usb/scanner.c --- a/drivers/usb/scanner.c Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/scanner.c Thu Jun 19 17:28:36 2003 @@ -362,6 +362,11 @@ * that assumption is not correct in all cases. * * + * 0.4.13 2003-06-14 + * - Added vendor/product ids for Genius, Hewlett-Packard, Microtek, + * Mustek, Pacific Image Electronics, Plustek, and Visioneer scanners. + * Fixed names of some other scanners. + * * TODO * - Performance * - Select/poll methods diff -Nru a/drivers/usb/scanner.h b/drivers/usb/scanner.h --- a/drivers/usb/scanner.h Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/scanner.h Thu Jun 19 17:28:35 2003 @@ -44,7 +44,7 @@ // #define DEBUG -#define DRIVER_VERSION "0.4.12" +#define DRIVER_VERSION "0.4.13" #define DRIVER_DESC "USB Scanner Driver" #include @@ -71,7 +71,7 @@ static struct usb_device_id scanner_device_ids [] = { /* Acer (now Benq) */ - { USB_DEVICE(0x04a5, 0x1a20) }, /* Unknown - Oliver Schwartz */ + { USB_DEVICE(0x04a5, 0x1a20) }, /* Prisa 310U */ { USB_DEVICE(0x04a5, 0x1a2a) }, /* Another 620U */ { USB_DEVICE(0x04a5, 0x2022) }, /* 340U */ { USB_DEVICE(0x04a5, 0x2040) }, /* 620U (!) */ @@ -102,6 +102,7 @@ { USB_DEVICE(0x05d8, 0x4003) }, /* E+ 48U */ { USB_DEVICE(0x05d8, 0x4004) }, /* E+ Pro */ /* Avision */ + { USB_DEVICE(0x0638, 0x0268) }, /* iVina 1200U */ { USB_DEVICE(0x0638, 0x0a10) }, /* iVina FB1600 (=Umax Astra 4500) */ /* Benq: see Acer */ /* Brother */ @@ -139,6 +140,7 @@ { USB_DEVICE(0x0458, 0x2013) }, /* ColorPage HR7 */ { USB_DEVICE(0x0458, 0x2015) }, /* ColorPage HR7LE */ { USB_DEVICE(0x0458, 0x2016) }, /* ColorPage HR6X */ + { USB_DEVICE(0x0458, 0x2018) }, /* ColorPage HR7X */ /* Hewlett Packard */ { USB_DEVICE(0x03f0, 0x0101) }, /* ScanJet 4100C */ { USB_DEVICE(0x03f0, 0x0102) }, /* PhotoSmart S20 */ @@ -158,10 +160,10 @@ { USB_DEVICE(0x03F0, 0x1005) }, /* ScanJet 5400C */ { USB_DEVICE(0x03F0, 0x1105) }, /* ScanJet 5470C */ { USB_DEVICE(0x03f0, 0x1305) }, /* Scanjet 4570c */ + { USB_DEVICE(0x03f0, 0x1411) }, /* PSC 750 */ { USB_DEVICE(0x03f0, 0x2005) }, /* ScanJet 3570c */ { USB_DEVICE(0x03f0, 0x2205) }, /* ScanJet 3500c */ - /* iVina */ - { USB_DEVICE(0x0638, 0x0268) }, /* 1200U */ + { USB_DEVICE(0x03f0, 0x2f11) }, /* PSC 1210 */ /* Lexmark */ { USB_DEVICE(0x043d, 0x002d) }, /* X70/X73 */ { USB_DEVICE(0x043d, 0x003d) }, /* X83 */ @@ -174,6 +176,7 @@ /* Microtek */ { USB_DEVICE(0x05da, 0x30ce) }, /* ScanMaker 3800 */ { USB_DEVICE(0x05da, 0x30cf) }, /* ScanMaker 4800 */ + { USB_DEVICE(0x04a7, 0x0224) }, /* Scanport 3000 (actually Visioneer?)*/ /* The following SCSI-over-USB Microtek devices are supported by the microtek driver: Enable SCSI and USB Microtek in kernel config */ // { USB_DEVICE(0x05da, 0x0099) }, /* ScanMaker X6 - X6U */ @@ -206,25 +209,30 @@ { USB_DEVICE(0x055f, 0x021d) }, /* Bearpaw 2400 CU Plus */ { USB_DEVICE(0x055f, 0x021e) }, /* BearPaw 1200 TA/CS */ { USB_DEVICE(0x055f, 0x0400) }, /* BearPaw 2400 TA PRO */ + { USB_DEVICE(0x055f, 0x0401) }, /* P 3600 A3 Pro */ { USB_DEVICE(0x055f, 0x0873) }, /* ScanExpress 600 USB */ { USB_DEVICE(0x055f, 0x1000) }, /* BearPaw 4800 TA PRO */ // { USB_DEVICE(0x05d8, 0x4002) }, /* BearPaw 1200 CU and ScanExpress 1200 UB Plus (see Artec) */ /* Nikon */ { USB_DEVICE(0x04b0, 0x4000) }, /* Coolscan LS 40 ED */ + /* Pacific Image Electronics */ + { USB_DEVICE(0x05e3, 0x0120) }, /* PrimeFilm 1800u */ /* Plustek */ + { USB_DEVICE(0x07b3, 0x0001) }, /* 1212U */ { USB_DEVICE(0x07b3, 0x0005) }, /* Unknown */ { USB_DEVICE(0x07b3, 0x0007) }, /* Unknown */ { USB_DEVICE(0x07b3, 0x000F) }, /* Unknown */ { USB_DEVICE(0x07b3, 0x0010) }, /* OpticPro U12 */ { USB_DEVICE(0x07b3, 0x0011) }, /* OpticPro U24 */ { USB_DEVICE(0x07b3, 0x0012) }, /* Unknown */ - { USB_DEVICE(0x07b3, 0x0013) }, /* Unknown */ + { USB_DEVICE(0x07b3, 0x0013) }, /* UT12 */ { USB_DEVICE(0x07b3, 0x0014) }, /* Unknown */ { USB_DEVICE(0x07b3, 0x0015) }, /* OpticPro U24 */ { USB_DEVICE(0x07b3, 0x0016) }, /* Unknown */ { USB_DEVICE(0x07b3, 0x0017) }, /* OpticPro UT12/UT16/UT24 */ { USB_DEVICE(0x07b3, 0x0400) }, /* OpticPro 1248U */ { USB_DEVICE(0x07b3, 0x0401) }, /* OpticPro 1248U (another one) */ + { USB_DEVICE(0x07b3, 0x0403) }, /* U16B */ /* Primax/Colorado */ { USB_DEVICE(0x0461, 0x0300) }, /* G2-300 #1 */ { USB_DEVICE(0x0461, 0x0301) }, /* G2E-300 #1 */ @@ -282,6 +290,8 @@ /* Visioneer */ { USB_DEVICE(0x04a7, 0x0211) }, /* OneTouch 7600 USB */ { USB_DEVICE(0x04a7, 0x0221) }, /* OneTouch 5300 USB */ + { USB_DEVICE(0x04a7, 0x0224) }, /* OneTouch 4800 USB */ + { USB_DEVICE(0x04a7, 0x0226) }, /* OneTouch 5300 USB */ { USB_DEVICE(0x04a7, 0x0231) }, /* 6100 USB */ { USB_DEVICE(0x04a7, 0x0311) }, /* 6200 EPP/USB */ { USB_DEVICE(0x04a7, 0x0321) }, /* OneTouch 8100 EPP/USB */ diff -Nru a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c --- a/drivers/usb/serial/kobil_sct.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/serial/kobil_sct.c Thu Jun 19 17:28:35 2003 @@ -21,7 +21,8 @@ * Supported readers: USB TWIN, KAAN Standard Plus and SecOVID Reader Plus * (Adapter K), B1 Professional and KAAN Professional (Adapter B) * - * TODO: High baudrates + * (23/05/2003) tw + * Add support for KAAN SIM * * (12/03/2002) tw * Fixed bug with Pro-readers and PNP @@ -66,6 +67,7 @@ #define KOBIL_ADAPTER_B_PRODUCT_ID 0x2011 #define KOBIL_ADAPTER_K_PRODUCT_ID 0x2012 #define KOBIL_USBTWIN_PRODUCT_ID 0x0078 +#define KOBIL_KAAN_SIM_PRODUCT_ID 0x0081 #define KOBIL_TIMEOUT 500 #define KOBIL_BUF_LENGTH 300 @@ -89,6 +91,7 @@ { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_ADAPTER_B_PRODUCT_ID) }, { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_ADAPTER_K_PRODUCT_ID) }, { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_USBTWIN_PRODUCT_ID) }, + { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_KAAN_SIM_PRODUCT_ID) }, { } /* Terminating entry */ }; @@ -157,6 +160,9 @@ case KOBIL_USBTWIN_PRODUCT_ID: printk(KERN_DEBUG "KOBIL USBTWIN detected\n"); break; + case KOBIL_KAAN_SIM_PRODUCT_ID: + printk(KERN_DEBUG "KOBIL KAAN SIM detected\n"); + break; } // search for the neccessary endpoints @@ -322,7 +328,8 @@ dbg("%s - port %d Send reset_all_queues URB returns: %i", __FUNCTION__, port->number, result); } - if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID || priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) { + if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID || priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID || + priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { // start reading (Adapter B 'cause PNP string) result = usb_submit_urb( port->interrupt_in_urb ); dbg("%s - port %d Send read URB returns: %i", __FUNCTION__, port->number, result); @@ -444,12 +451,12 @@ priv->filled = priv->filled + count; - // only send complete block. TWIN and adapter K use the same protocol. + // only send complete block. TWIN, KAAN SIM and adapter K use the same protocol. if ( ((priv->device_type != KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 2) && (priv->filled >= (priv->buf[1] + 3))) || ((priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 3) && (priv->filled >= (priv->buf[2] + 4))) ) { - // stop reading (except TWIN) - if (priv->device_type != KOBIL_USBTWIN_PRODUCT_ID) { + // stop reading (except TWIN and KAAN SIM) + if ( (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) || (priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) ) { usb_unlink_urb( port->interrupt_in_urb ); } @@ -486,8 +493,8 @@ priv->filled = 0; priv->cur_pos = 0; - // start reading (except TWIN) - if (priv->device_type != KOBIL_USBTWIN_PRODUCT_ID) { + // start reading (except TWIN and KAAN SIM) + if ( (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) || (priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) ) { // someone sets the dev to 0 if the close method has been called port->interrupt_in_urb->dev = port->serial->dev; @@ -519,7 +526,7 @@ char *settings; priv = (struct kobil_private *) port->private; - if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID) { + if ((priv->device_type == KOBIL_USBTWIN_PRODUCT_ID) || (priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID)) { // This device doesn't support ioctl calls return 0; } diff -Nru a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c --- a/drivers/usb/serial/pl2303.c Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/serial/pl2303.c Thu Jun 19 17:28:36 2003 @@ -1,7 +1,8 @@ /* * Prolific PL2303 USB to serial adaptor driver * - * Copyright (C) 2001-2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001-2003 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2003 IBM Corp. * * Original driver for 2.2.x by anonymous * @@ -11,6 +12,8 @@ * (at your option) any later version. * * See Documentation/usb/usb-serial.txt for more information on using this driver + * 2003_Apr_24 gkh + * Added line error reporting support. Hopefully it is correct... * * 2001_Oct_06 gkh * Added RTS and DTR line control. Thanks to joe@bndlg.de for parts of it. @@ -101,6 +104,16 @@ #define VENDOR_READ_REQUEST_TYPE 0xc0 #define VENDOR_READ_REQUEST 0x01 +#define UART_STATE 0x08 +#define UART_DCD 0x01 +#define UART_DSR 0x02 +#define UART_BREAK_ERROR 0x04 +#define UART_RING 0x08 +#define UART_FRAME_ERROR 0x10 +#define UART_PARITY_ERROR 0x20 +#define UART_OVERRUN_ERROR 0x40 +#define UART_CTS 0x80 + /* function prototypes for a PL2303 serial converter */ static int pl2303_open (struct usb_serial_port *port, struct file *filp); static void pl2303_close (struct usb_serial_port *port, struct file *filp); @@ -142,6 +155,7 @@ struct pl2303_private { u8 line_control; + u8 line_status; u8 termios_initialized; }; @@ -562,7 +576,7 @@ state = BREAK_ON; dbg("%s - turning break %s", state==BREAK_OFF ? "off" : "on", __FUNCTION__); - result = usb_control_msg (serial->dev, usb_rcvctrlpipe (serial->dev, 0), + result = usb_control_msg (serial->dev, usb_sndctrlpipe (serial->dev, 0), BREAK_REQUEST, BREAK_REQUEST_TYPE, state, 0, NULL, 0, 100); if (result) @@ -585,10 +599,10 @@ { struct usb_serial_port *port = (struct usb_serial_port *) urb->context; struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); - //unsigned char *data = urb->transfer_buffer; - //int i; + struct pl2303_private *priv = port->private; + unsigned char *data = urb->transfer_buffer; -//ints auto restart... + /* ints auto restart... */ if (!serial) { return; @@ -600,9 +614,12 @@ } usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, urb->transfer_buffer); -#if 0 -//FIXME need to update state of terminal lines variable -#endif + + if (urb->actual_length > UART_STATE) + return; + + /* Save off the uart status for others to look at */ + priv->line_status = data[UART_STATE]; return; } @@ -612,10 +629,13 @@ { struct usb_serial_port *port = (struct usb_serial_port *) urb->context; struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); + struct pl2303_private *priv = port->private; struct tty_struct *tty; unsigned char *data = urb->transfer_buffer; int i; int result; + u8 status; + char tty_flag; if (port_paranoia_check (port, __FUNCTION__)) return; @@ -649,13 +669,31 @@ usb_serial_debug_data (__FILE__, __FUNCTION__, urb->actual_length, data); + /* get tty_flag from status */ + tty_flag = TTY_NORMAL; + status = priv->line_status; + + /* break takes precedence over parity, */ + /* which takes precedence over framing errors */ + if (status & UART_BREAK_ERROR ) + tty_flag = TTY_BREAK; + else if (status & UART_PARITY_ERROR) + tty_flag = TTY_PARITY; + else if (status & UART_FRAME_ERROR) + tty_flag = TTY_FRAME; + dbg("%s - tty_flag = %d", __FUNCTION__, tty_flag); + tty = port->tty; if (tty && urb->actual_length) { + /* overrun is special, not associated with a char */ + if (status & UART_OVERRUN_ERROR) + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + for (i = 0; i < urb->actual_length; ++i) { if (tty->flip.count >= TTY_FLIPBUF_SIZE) { tty_flip_buffer_push(tty); } - tty_insert_flip_char (tty, data[i], 0); + tty_insert_flip_char (tty, data[i], tty_flag); } tty_flip_buffer_push (tty); } diff -Nru a/drivers/usb/serial/usbserial.c b/drivers/usb/serial/usbserial.c --- a/drivers/usb/serial/usbserial.c Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/serial/usbserial.c Thu Jun 19 17:28:36 2003 @@ -556,6 +556,7 @@ else generic_close(port, filp); port->open_count = 0; + port->tty = NULL; } if (port->serial->type->owner) diff -Nru a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c --- a/drivers/usb/serial/visor.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/serial/visor.c Thu Jun 19 17:28:35 2003 @@ -987,15 +987,3 @@ MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Debug enabled or not"); - - - - - - - - - - - - diff -Nru a/drivers/usb/speedcrc.c b/drivers/usb/speedcrc.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/speedcrc.c Thu Jun 19 17:28:36 2003 @@ -0,0 +1,124 @@ +/****************************************************************************** + * speedcrc.c -- CRC library for use with speedtouch. + * + * Copyright (C) 2000, Johan Verrept + * + * 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., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#include + +/* + * CRC Routines from net/wan/sbni.c) + * table generated by Rocksoft^tm Model CRC Algorithm Table Generation Program V1.0 + */ +#define CRC32_REMAINDER CBF43926 +#define CRC32_INITIAL 0xffffffff +unsigned long crc32tab[256] = { + 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L, + 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L, + 0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L, + 0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL, + 0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L, + 0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L, + 0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L, + 0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL, + 0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L, + 0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L, + 0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L, + 0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL, + 0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L, + 0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L, + 0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L, + 0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL, + 0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL, + 0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L, + 0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L, + 0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL, + 0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL, + 0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L, + 0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L, + 0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL, + 0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL, + 0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L, + 0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L, + 0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL, + 0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL, + 0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L, + 0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L, + 0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL, + 0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L, + 0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL, + 0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL, + 0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L, + 0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L, + 0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL, + 0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL, + 0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L, + 0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L, + 0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL, + 0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL, + 0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L, + 0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L, + 0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL, + 0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL, + 0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L, + 0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L, + 0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL, + 0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L, + 0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L, + 0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L, + 0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL, + 0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L, + 0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L, + 0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L, + 0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL, + 0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L, + 0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L, + 0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L, + 0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL, + 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L, + 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L +}; + +#ifdef CRCASM + +unsigned long spdcrc32 (char *mem, int len, unsigned initial) +{ + unsigned crc, dummy_len; + __asm__ ("xorl %%eax,%%eax\n\t" "1:\n\t" "movl %%edx,%%eax\n\t" "shrl $16,%%eax\n\t" "lodsb\n\t" "xorb %%ah,%%al\n\t" "andl $255,%%eax\n\t" "shll $8,%%edx\n\t" "xorl (%%edi,%%eax,4),%%edx\n\t" "loop 1b":"=d" (crc), + "=c" + (dummy_len) + : "S" (mem), "D" (&crc32tab[0]), "1" (len), "0" (initial) + : "eax"); + return crc; +} + +#else + +#define CRC32(c,crc) (crc32tab[((size_t)(crc>>24) ^ (c)) & 0xff] ^ (((crc) << 8))) + +unsigned long spdcrc32 (char *mem, int len, unsigned initial) +{ + unsigned crc; + crc = initial; + + for (; len; mem++, len--) { + crc = CRC32 (*mem, crc); + } + return (crc); +} +#endif diff -Nru a/drivers/usb/speedcrc.h b/drivers/usb/speedcrc.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/speedcrc.h Thu Jun 19 17:28:36 2003 @@ -0,0 +1,28 @@ +#ifndef _SPEEDCRC_H_ +#define _SPEEDCRC_H_ + +/****************************************************************************** + * speedcrc.h -- CRC library for use with speedtouch. + * + * Copyright (C) 2000, Johan Verrept + * + * 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., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +unsigned long spdcrc32 (char *mem, int len, unsigned initial); +#define crc32_be(crc, mem, len) spdcrc32(mem, len, crc) + +#endif /* _SPEEDCRC_H_ */ diff -Nru a/drivers/usb/speedtouch.c b/drivers/usb/speedtouch.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/speedtouch.c Thu Jun 19 17:28:36 2003 @@ -0,0 +1,1344 @@ +/****************************************************************************** + * speedtouch.c - Alcatel SpeedTouch USB xDSL modem driver + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands + * + * 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., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +/* + * Written by Johan Verrept, maintained by Duncan Sands (duncan.sands@wanadoo.fr) + * + * 1.6: - No longer opens a connection if the firmware is not loaded + * - Added support for the speedtouch 330 + * - Removed the limit on the number of devices + * - Module now autoloads on device plugin + * - Merged relevant parts of sarlib + * - Replaced the kernel thread with a tasklet + * - New packet transmission code + * - Changed proc file contents + * - Fixed all known SMP races + * - Many fixes and cleanups + * - Various fixes by Oliver Neukum (oliver@neukum.name) + * + * 1.5A: - Backport of version for inclusion in 2.5 series kernel + * - Modifications by Richard Purdie (rpurdie@rpsys.net) + * - made compatible with kernel 2.5.6 onwards by changing + * udsl_usb_send_data_context->urb to a pointer and adding code + * to alloc and free it + * - remove_wait_queue() added to udsl_atm_processqueue_thread() + * + * 1.5: - fixed memory leak when atmsar_decode_aal5 returned NULL. + * (reported by stephen.robinson@zen.co.uk) + * + * 1.4: - changed the spin_lock() under interrupt to spin_lock_irqsave() + * - unlink all active send urbs of a vcc that is being closed. + * + * 1.3.1: - added the version number + * + * 1.3: - Added multiple send urb support + * - fixed memory leak and vcc->tx_inuse starvation bug + * when not enough memory left in vcc. + * + * 1.2: - Fixed race condition in udsl_usb_send_data() + * 1.1: - Turned off packet debugging + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "speedcrc.h" + +/* +#define DEBUG +#define VERBOSE_DEBUG +*/ + +#include + + +#ifdef VERBOSE_DEBUG +static int udsl_print_packet (const unsigned char *data, int len); +#define PACKETDEBUG(arg...) udsl_print_packet (arg) +#define vdbg(arg...) dbg (arg) +#else +#define PACKETDEBUG(arg...) +#define vdbg(arg...) +#endif + +#define DRIVER_AUTHOR "Johan Verrept, Duncan Sands " +#define DRIVER_DESC "Alcatel SpeedTouch USB driver" +#define DRIVER_VERSION "1.6" + +static const char udsl_driver_name [] = "speedtch"; + +#define SPEEDTOUCH_VENDORID 0x06b9 +#define SPEEDTOUCH_PRODUCTID 0x4061 + +#define UDSL_MAX_RCV_URBS 4 +#define UDSL_MAX_SND_URBS 4 +#define UDSL_MAX_RCV_BUFS 8 +#define UDSL_MAX_SND_BUFS 8 +#define UDSL_MAX_RCV_BUF_SIZE 1024 /* ATM cells */ +#define UDSL_MAX_SND_BUF_SIZE 1024 /* ATM cells */ +#define UDSL_DEFAULT_RCV_URBS 1 +#define UDSL_DEFAULT_SND_URBS 1 +#define UDSL_DEFAULT_RCV_BUFS 2 +#define UDSL_DEFAULT_SND_BUFS 2 +#define UDSL_DEFAULT_RCV_BUF_SIZE 64 /* ATM cells */ +#define UDSL_DEFAULT_SND_BUF_SIZE 64 /* ATM cells */ + +static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS; +static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS; +static unsigned int num_rcv_bufs = UDSL_DEFAULT_RCV_BUFS; +static unsigned int num_snd_bufs = UDSL_DEFAULT_SND_BUFS; +static unsigned int rcv_buf_size = UDSL_DEFAULT_RCV_BUF_SIZE; +static unsigned int snd_buf_size = UDSL_DEFAULT_SND_BUF_SIZE; + +MODULE_PARM (num_rcv_urbs, "i"); +MODULE_PARM_DESC (num_rcv_urbs, "Number of urbs used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_URBS) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_URBS) ")"); + +MODULE_PARM (num_snd_urbs, "i"); +MODULE_PARM_DESC (num_snd_urbs, "Number of urbs used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_URBS) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_URBS) ")"); + +MODULE_PARM (num_rcv_bufs, "i"); +MODULE_PARM_DESC (num_rcv_bufs, "Number of buffers used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_BUFS) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_BUFS) ")"); + +MODULE_PARM (num_snd_bufs, "i"); +MODULE_PARM_DESC (num_snd_bufs, "Number of buffers used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_BUFS) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_BUFS) ")"); + +MODULE_PARM (rcv_buf_size, "i"); +MODULE_PARM_DESC (rcv_buf_size, "Size of the buffers used for reception (range: 0-" __MODULE_STRING (UDSL_MAX_RCV_BUF_SIZE) ", default: " __MODULE_STRING (UDSL_DEFAULT_RCV_BUF_SIZE) ")"); + +MODULE_PARM (snd_buf_size, "i"); +MODULE_PARM_DESC (snd_buf_size, "Size of the buffers used for transmission (range: 0-" __MODULE_STRING (UDSL_MAX_SND_BUF_SIZE) ", default: " __MODULE_STRING (UDSL_DEFAULT_SND_BUF_SIZE) ")"); + +#define UDSL_IOCTL_LINE_UP 1 +#define UDSL_IOCTL_LINE_DOWN 2 + +#define UDSL_ENDPOINT_DATA_OUT 0x07 +#define UDSL_ENDPOINT_DATA_IN 0x87 + +#define ATM_CELL_HEADER (ATM_CELL_SIZE - ATM_CELL_PAYLOAD) +#define UDSL_NUM_CELLS(x) (((x) + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 1) / ATM_CELL_PAYLOAD) + +#define hex2int(c) ( (c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9) ) + +static struct usb_device_id udsl_usb_ids [] = { + { USB_DEVICE (SPEEDTOUCH_VENDORID, SPEEDTOUCH_PRODUCTID) }, + { } +}; + +MODULE_DEVICE_TABLE (usb, udsl_usb_ids); + +/* receive */ + +struct udsl_receive_buffer { + struct list_head list; + unsigned char *base; + unsigned int filled_cells; +}; + +struct udsl_receiver { + struct list_head list; + struct udsl_receive_buffer *buffer; + struct urb *urb; + struct udsl_instance_data *instance; +}; + +struct udsl_vcc_data { + /* vpi/vci lookup */ + struct list_head list; + short vpi; + int vci; + struct atm_vcc *vcc; + + /* raw cell reassembly */ + struct sk_buff *skb; + unsigned int max_pdu; +}; + +/* send */ + +struct udsl_send_buffer { + struct list_head list; + unsigned char *base; + unsigned char *free_start; + unsigned int free_cells; +}; + +struct udsl_sender { + struct list_head list; + struct udsl_send_buffer *buffer; + struct urb *urb; + struct udsl_instance_data *instance; +}; + +struct udsl_control { + struct atm_skb_data atm_data; + unsigned int num_cells; + unsigned int num_entire; + unsigned int pdu_padding; + unsigned char cell_header [ATM_CELL_HEADER]; + unsigned char aal5_trailer [ATM_AAL5_TRAILER]; +}; + +#define UDSL_SKB(x) ((struct udsl_control *)(x)->cb) + +/* main driver data */ + +struct udsl_instance_data { + struct semaphore serialize; + + /* USB device part */ + struct usb_device *usb_dev; + char description [64]; + int firmware_loaded; + + /* ATM device part */ + struct atm_dev *atm_dev; + struct list_head vcc_list; + + /* receive */ + struct udsl_receiver receivers [UDSL_MAX_RCV_URBS]; + struct udsl_receive_buffer receive_buffers [UDSL_MAX_RCV_BUFS]; + + spinlock_t receive_lock; + struct list_head spare_receivers; + struct list_head filled_receive_buffers; + + struct tasklet_struct receive_tasklet; + struct list_head spare_receive_buffers; + + /* send */ + struct udsl_sender senders [UDSL_MAX_SND_URBS]; + struct udsl_send_buffer send_buffers [UDSL_MAX_SND_BUFS]; + + struct sk_buff_head sndqueue; + + spinlock_t send_lock; + struct list_head spare_senders; + struct list_head spare_send_buffers; + + struct tasklet_struct send_tasklet; + struct sk_buff *current_skb; /* being emptied */ + struct udsl_send_buffer *current_buffer; /* being filled */ + struct list_head filled_send_buffers; +}; + +/* ATM */ + +static void udsl_atm_dev_close (struct atm_dev *dev); +static int udsl_atm_open (struct atm_vcc *vcc, short vpi, int vci); +static void udsl_atm_close (struct atm_vcc *vcc); +static int udsl_atm_ioctl (struct atm_dev *dev, unsigned int cmd, void *arg); +static int udsl_atm_send (struct atm_vcc *vcc, struct sk_buff *skb); +static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page); + +static struct atmdev_ops udsl_atm_devops = { + dev_close: udsl_atm_dev_close, + open: udsl_atm_open, + close: udsl_atm_close, + ioctl: udsl_atm_ioctl, + send: udsl_atm_send, + proc_read: udsl_atm_proc_read, + owner: THIS_MODULE, +}; + +/* USB */ + +static void *udsl_usb_probe (struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id); +static void udsl_usb_disconnect (struct usb_device *dev, void *ptr); +static int udsl_usb_ioctl (struct usb_device *hub, unsigned int code, void *user_data); + +static struct usb_driver udsl_usb_driver = { + owner: THIS_MODULE, + name: udsl_driver_name, + probe: udsl_usb_probe, + disconnect: udsl_usb_disconnect, + ioctl: udsl_usb_ioctl, + id_table: udsl_usb_ids, +}; + + +/************* +** decode ** +*************/ + +static inline struct udsl_vcc_data *udsl_find_vcc (struct udsl_instance_data *instance, short vpi, int vci) +{ + struct udsl_vcc_data *vcc; + + list_for_each_entry (vcc, &instance->vcc_list, list) + if ((vcc->vci == vci) && (vcc->vpi == vpi)) + return vcc; + return NULL; +} + +static void udsl_extract_cells (struct udsl_instance_data *instance, unsigned char *source, unsigned int howmany) +{ + struct udsl_vcc_data *cached_vcc = NULL; + struct atm_vcc *vcc; + struct sk_buff *skb; + struct udsl_vcc_data *vcc_data; + int cached_vci = 0; + unsigned int i; + unsigned int length; + unsigned int pdu_length; + int pti; + int vci; + short cached_vpi = 0; + short vpi; + + for (i = 0; i < howmany; i++, source += ATM_CELL_SIZE) { + vpi = ((source [0] & 0x0f) << 4) | (source [1] >> 4); + vci = ((source [1] & 0x0f) << 12) | (source [2] << 4) | (source [3] >> 4); + pti = (source [3] & 0x2) != 0; + + vdbg ("udsl_extract_cells: vpi %hd, vci %d, pti %d", vpi, vci, pti); + + if (cached_vcc && (vci == cached_vci) && (vpi == cached_vpi)) + vcc_data = cached_vcc; + else if ((vcc_data = udsl_find_vcc (instance, vpi, vci))) { + cached_vcc = vcc_data; + cached_vpi = vpi; + cached_vci = vci; + } else { + dbg ("udsl_extract_cells: unknown vpi/vci (%hd/%d)!", vpi, vci); + continue; + } + + vcc = vcc_data->vcc; + + if (!vcc_data->skb && !(vcc_data->skb = dev_alloc_skb (vcc_data->max_pdu))) { + dbg ("udsl_extract_cells: no memory for skb (vcc: 0x%p)!", vcc); + if (pti) + atomic_inc (&vcc->stats->rx_err); + continue; + } + + skb = vcc_data->skb; + + if (skb->len + ATM_CELL_PAYLOAD > vcc_data->max_pdu) { + dbg ("udsl_extract_cells: buffer overrun (max_pdu: %u, skb->len %u, vcc: 0x%p)", vcc_data->max_pdu, skb->len, vcc); + /* discard cells already received */ + skb_trim (skb, 0); + BUG_ON (vcc_data->max_pdu < ATM_CELL_PAYLOAD); + } + + memcpy (skb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD); + __skb_put (skb, ATM_CELL_PAYLOAD); + + if (pti) { + length = (source [ATM_CELL_SIZE - 6] << 8) + source [ATM_CELL_SIZE - 5]; + + /* guard against overflow */ + if (length > ATM_MAX_AAL5_PDU) { + dbg ("udsl_extract_cells: bogus length %u (vcc: 0x%p)", length, vcc); + goto drop; + } + + pdu_length = UDSL_NUM_CELLS (length) * ATM_CELL_PAYLOAD; + + if (skb->len < pdu_length) { + dbg ("udsl_extract_cells: bogus pdu_length %u (skb->len: %u, vcc: 0x%p)", pdu_length, skb->len, vcc); + goto drop; + } + + if (crc32_be (~0, skb->tail - pdu_length, pdu_length) != 0xc704dd7b) { + dbg ("udsl_extract_cells: packet failed crc check (vcc: 0x%p)", vcc); + goto drop; + } + + if (!atm_charge (vcc, skb->truesize)) { + dbg ("udsl_extract_cells: failed atm_charge (skb->truesize: %u)", skb->truesize); + goto drop_no_stats; /* atm_charge increments rx_drop */ + } + + /* now that we are sure to send the skb, it is ok to change skb->data */ + if (skb->len > pdu_length) + skb_pull (skb, skb->len - pdu_length); /* discard initial junk */ + + skb_trim (skb, length); /* drop zero padding and trailer */ + + atomic_inc (&vcc->stats->rx); + + PACKETDEBUG (skb->data, skb->len); + + vdbg ("udsl_extract_cells: sending skb 0x%p, skb->len %u, skb->truesize %u", skb, skb->len, skb->truesize); + + vcc->push (vcc, skb); + + vcc_data->skb = NULL; + + continue; + +drop: + atomic_inc (&vcc->stats->rx_err); +drop_no_stats: + skb_trim (skb, 0); + } + } +} + + +/************* +** encode ** +*************/ + +static const unsigned char zeros [ATM_CELL_PAYLOAD]; + +static void udsl_groom_skb (struct atm_vcc *vcc, struct sk_buff *skb) +{ + struct udsl_control *ctrl = UDSL_SKB (skb); + unsigned int zero_padding; + u32 crc; + + ctrl->atm_data.vcc = vcc; + ctrl->cell_header [0] = vcc->vpi >> 4; + ctrl->cell_header [1] = (vcc->vpi << 4) | (vcc->vci >> 12); + ctrl->cell_header [2] = vcc->vci >> 4; + ctrl->cell_header [3] = vcc->vci << 4; + ctrl->cell_header [4] = 0xec; + + ctrl->num_cells = UDSL_NUM_CELLS (skb->len); + ctrl->num_entire = skb->len / ATM_CELL_PAYLOAD; + + zero_padding = ctrl->num_cells * ATM_CELL_PAYLOAD - skb->len - ATM_AAL5_TRAILER; + + if (ctrl->num_entire + 1 < ctrl->num_cells) + ctrl->pdu_padding = zero_padding - (ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); + else + ctrl->pdu_padding = zero_padding; + + ctrl->aal5_trailer [0] = 0; /* UU = 0 */ + ctrl->aal5_trailer [1] = 0; /* CPI = 0 */ + ctrl->aal5_trailer [2] = skb->len >> 8; + ctrl->aal5_trailer [3] = skb->len; + + crc = crc32_be (~0, skb->data, skb->len); + crc = crc32_be (crc, zeros, zero_padding); + crc = crc32_be (crc, ctrl->aal5_trailer, 4); + crc = ~crc; + + ctrl->aal5_trailer [4] = crc >> 24; + ctrl->aal5_trailer [5] = crc >> 16; + ctrl->aal5_trailer [6] = crc >> 8; + ctrl->aal5_trailer [7] = crc; +} + +static unsigned int udsl_write_cells (unsigned int howmany, struct sk_buff *skb, unsigned char **target_p) +{ + struct udsl_control *ctrl = UDSL_SKB (skb); + unsigned char *target = *target_p; + unsigned int nc, ne, i; + + vdbg ("udsl_write_cells: howmany=%u, skb->len=%d, num_cells=%u, num_entire=%u, pdu_padding=%u", howmany, skb->len, ctrl->num_cells, ctrl->num_entire, ctrl->pdu_padding); + + nc = ctrl->num_cells; + ne = min (howmany, ctrl->num_entire); + + for (i = 0; i < ne; i++) { + memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + target += ATM_CELL_HEADER; + memcpy (target, skb->data, ATM_CELL_PAYLOAD); + target += ATM_CELL_PAYLOAD; + __skb_pull (skb, ATM_CELL_PAYLOAD); + } + + ctrl->num_entire -= ne; + + if (!(ctrl->num_cells -= ne) || !(howmany -= ne)) + goto out; + + memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + target += ATM_CELL_HEADER; + memcpy (target, skb->data, skb->len); + target += skb->len; + __skb_pull (skb, skb->len); + memset (target, 0, ctrl->pdu_padding); + target += ctrl->pdu_padding; + + if (--ctrl->num_cells) { + if (!--howmany) { + ctrl->pdu_padding = ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; + goto out; + } + + memcpy (target, ctrl->cell_header, ATM_CELL_HEADER); + target += ATM_CELL_HEADER; + memset (target, 0, ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER); + target += ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER; + + BUG_ON (--ctrl->num_cells); + } + + memcpy (target, ctrl->aal5_trailer, ATM_AAL5_TRAILER); + target += ATM_AAL5_TRAILER; + /* set pti bit in last cell */ + *(target + 3 - ATM_CELL_SIZE) |= 0x2; + +out: + *target_p = target; + return nc - ctrl->num_cells; +} + + +/************** +** receive ** +**************/ + +static void udsl_complete_receive (struct urb *urb) +{ + struct udsl_receive_buffer *buf; + struct udsl_instance_data *instance; + struct udsl_receiver *rcv; + unsigned long flags; + + if (!urb || !(rcv = urb->context)) { + dbg ("udsl_complete_receive: bad urb!"); + return; + } + + instance = rcv->instance; + buf = rcv->buffer; + + buf->filled_cells = urb->actual_length / ATM_CELL_SIZE; + + vdbg ("udsl_complete_receive: urb 0x%p, status %d, actual_length %d, filled_cells %u, rcv 0x%p, buf 0x%p", urb, urb->status, urb->actual_length, buf->filled_cells, rcv, buf); + + BUG_ON (buf->filled_cells > rcv_buf_size); + + /* may not be in_interrupt() */ + spin_lock_irqsave (&instance->receive_lock, flags); + list_add (&rcv->list, &instance->spare_receivers); + list_add_tail (&buf->list, &instance->filled_receive_buffers); + if (likely (!urb->status)) + tasklet_schedule (&instance->receive_tasklet); + spin_unlock_irqrestore (&instance->receive_lock, flags); +} + +static void udsl_process_receive (unsigned long data) +{ + struct udsl_receive_buffer *buf; + struct udsl_instance_data *instance = (struct udsl_instance_data *) data; + struct udsl_receiver *rcv; + int err; + +made_progress: + while (!list_empty (&instance->spare_receive_buffers)) { + spin_lock_irq (&instance->receive_lock); + if (list_empty (&instance->spare_receivers)) { + spin_unlock_irq (&instance->receive_lock); + break; + } + rcv = list_entry (instance->spare_receivers.next, struct udsl_receiver, list); + list_del (&rcv->list); + spin_unlock_irq (&instance->receive_lock); + + buf = list_entry (instance->spare_receive_buffers.next, struct udsl_receive_buffer, list); + list_del (&buf->list); + + rcv->buffer = buf; + + FILL_BULK_URB (rcv->urb, + instance->usb_dev, + usb_rcvbulkpipe (instance->usb_dev, UDSL_ENDPOINT_DATA_IN), + buf->base, + rcv_buf_size * ATM_CELL_SIZE, + udsl_complete_receive, + rcv); + rcv->urb->transfer_flags |= USB_QUEUE_BULK; + + vdbg ("udsl_process_receive: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); + + if ((err = usb_submit_urb(rcv->urb)) < 0) { + dbg ("udsl_process_receive: urb submission failed (%d)!", err); + list_add (&buf->list, &instance->spare_receive_buffers); + spin_lock_irq (&instance->receive_lock); + list_add (&rcv->list, &instance->spare_receivers); + spin_unlock_irq (&instance->receive_lock); + break; + } + } + + spin_lock_irq (&instance->receive_lock); + if (list_empty (&instance->filled_receive_buffers)) { + spin_unlock_irq (&instance->receive_lock); + return; /* done - no more buffers */ + } + buf = list_entry (instance->filled_receive_buffers.next, struct udsl_receive_buffer, list); + list_del (&buf->list); + spin_unlock_irq (&instance->receive_lock); + vdbg ("udsl_process_receive: processing buf 0x%p", buf); + udsl_extract_cells (instance, buf->base, buf->filled_cells); + list_add (&buf->list, &instance->spare_receive_buffers); + goto made_progress; +} + + +/*********** +** send ** +***********/ + +static void udsl_complete_send (struct urb *urb) +{ + struct udsl_instance_data *instance; + struct udsl_sender *snd; + unsigned long flags; + + if (!urb || !(snd = urb->context) || !(instance = snd->instance)) { + dbg ("udsl_complete_send: bad urb!"); + return; + } + + vdbg ("udsl_complete_send: urb 0x%p, status %d, snd 0x%p, buf 0x%p", urb, urb->status, snd, snd->buffer); + + /* may not be in_interrupt() */ + spin_lock_irqsave (&instance->send_lock, flags); + list_add (&snd->list, &instance->spare_senders); + list_add (&snd->buffer->list, &instance->spare_send_buffers); + tasklet_schedule (&instance->send_tasklet); + spin_unlock_irqrestore (&instance->send_lock, flags); +} + +static void udsl_process_send (unsigned long data) +{ + struct udsl_send_buffer *buf; + struct udsl_instance_data *instance = (struct udsl_instance_data *) data; + struct sk_buff *skb; + struct udsl_sender *snd; + int err; + unsigned int num_written; + +made_progress: + spin_lock_irq (&instance->send_lock); + while (!list_empty (&instance->spare_senders)) { + if (!list_empty (&instance->filled_send_buffers)) { + buf = list_entry (instance->filled_send_buffers.next, struct udsl_send_buffer, list); + list_del (&buf->list); + } else if ((buf = instance->current_buffer)) { + instance->current_buffer = NULL; + } else /* all buffers empty */ + break; + + snd = list_entry (instance->spare_senders.next, struct udsl_sender, list); + list_del (&snd->list); + spin_unlock_irq (&instance->send_lock); + + snd->buffer = buf; + FILL_BULK_URB (snd->urb, + instance->usb_dev, + usb_sndbulkpipe (instance->usb_dev, UDSL_ENDPOINT_DATA_OUT), + buf->base, + (snd_buf_size - buf->free_cells) * ATM_CELL_SIZE, + udsl_complete_send, + snd); + snd->urb->transfer_flags |= USB_QUEUE_BULK; + + vdbg ("udsl_process_send: submitting urb 0x%p (%d cells), snd 0x%p, buf 0x%p", snd->urb, snd_buf_size - buf->free_cells, snd, buf); + + if ((err = usb_submit_urb(snd->urb)) < 0) { + dbg ("udsl_process_send: urb submission failed (%d)!", err); + spin_lock_irq (&instance->send_lock); + list_add (&snd->list, &instance->spare_senders); + spin_unlock_irq (&instance->send_lock); + list_add (&buf->list, &instance->filled_send_buffers); + return; /* bail out */ + } + + spin_lock_irq (&instance->send_lock); + } /* while */ + spin_unlock_irq (&instance->send_lock); + + if (!instance->current_skb && !(instance->current_skb = skb_dequeue (&instance->sndqueue))) + return; /* done - no more skbs */ + + skb = instance->current_skb; + + if (!(buf = instance->current_buffer)) { + spin_lock_irq (&instance->send_lock); + if (list_empty (&instance->spare_send_buffers)) { + instance->current_buffer = NULL; + spin_unlock_irq (&instance->send_lock); + return; /* done - no more buffers */ + } + buf = list_entry (instance->spare_send_buffers.next, struct udsl_send_buffer, list); + list_del (&buf->list); + spin_unlock_irq (&instance->send_lock); + + buf->free_start = buf->base; + buf->free_cells = snd_buf_size; + + instance->current_buffer = buf; + } + + num_written = udsl_write_cells (buf->free_cells, skb, &buf->free_start); + + vdbg ("udsl_process_send: wrote %u cells from skb 0x%p to buffer 0x%p", num_written, skb, buf); + + if (!(buf->free_cells -= num_written)) { + list_add_tail (&buf->list, &instance->filled_send_buffers); + instance->current_buffer = NULL; + } + + vdbg ("udsl_process_send: buffer contains %d cells, %d left", snd_buf_size - buf->free_cells, buf->free_cells); + + if (!UDSL_SKB (skb)->num_cells) { + struct atm_vcc *vcc = UDSL_SKB (skb)->atm_data.vcc; + + if (vcc->pop) + vcc->pop (vcc, skb); + else + dev_kfree_skb (skb); + instance->current_skb = NULL; + + atomic_inc (&vcc->stats->tx); + } + + goto made_progress; +} + +static void udsl_cancel_send (struct udsl_instance_data *instance, struct atm_vcc *vcc) +{ + struct sk_buff *skb, *n; + + dbg ("udsl_cancel_send entered"); + spin_lock_irq (&instance->sndqueue.lock); + for (skb = instance->sndqueue.next, n = skb->next; skb != (struct sk_buff *)&instance->sndqueue; skb = n, n = skb->next) + if (UDSL_SKB (skb)->atm_data.vcc == vcc) { + dbg ("udsl_cancel_send: popping skb 0x%p", skb); + __skb_unlink (skb, &instance->sndqueue); + if (vcc->pop) + vcc->pop (vcc, skb); + else + dev_kfree_skb (skb); + } + spin_unlock_irq (&instance->sndqueue.lock); + + tasklet_disable (&instance->send_tasklet); + if ((skb = instance->current_skb) && (UDSL_SKB (skb)->atm_data.vcc == vcc)) { + dbg ("udsl_cancel_send: popping current skb (0x%p)", skb); + instance->current_skb = NULL; + if (vcc->pop) + vcc->pop (vcc, skb); + else + dev_kfree_skb (skb); + } + tasklet_enable (&instance->send_tasklet); + dbg ("udsl_cancel_send done"); +} + +static int udsl_atm_send (struct atm_vcc *vcc, struct sk_buff *skb) +{ + struct udsl_instance_data *instance = vcc->dev->dev_data; + + vdbg ("udsl_atm_send called (skb 0x%p, len %u)", skb, skb->len); + + if (!instance || !instance->usb_dev) { + dbg ("udsl_atm_send: NULL data!"); + return -ENODEV; + } + + if (vcc->qos.aal != ATM_AAL5) { + dbg ("udsl_atm_send: unsupported ATM type %d!", vcc->qos.aal); + return -EINVAL; + } + + if (skb->len > ATM_MAX_AAL5_PDU) { + dbg ("udsl_atm_send: packet too long (%d vs %d)!", skb->len, ATM_MAX_AAL5_PDU); + return -EINVAL; + } + + PACKETDEBUG (skb->data, skb->len); + + udsl_groom_skb (vcc, skb); + skb_queue_tail (&instance->sndqueue, skb); + tasklet_schedule (&instance->send_tasklet); + + return 0; +} + + +/********** +** ATM ** +**********/ + +static void udsl_atm_dev_close (struct atm_dev *dev) +{ + struct udsl_instance_data *instance = dev->dev_data; + + if (!instance) { + dbg ("udsl_atm_dev_close: NULL instance!"); + return; + } + + dbg ("udsl_atm_dev_close: queue has %u elements", instance->sndqueue.qlen); + + tasklet_kill (&instance->receive_tasklet); + tasklet_kill (&instance->send_tasklet); + kfree (instance); + dev->dev_data = NULL; +} + +static int udsl_atm_proc_read (struct atm_dev *atm_dev, loff_t *pos, char *page) +{ + struct udsl_instance_data *instance = atm_dev->dev_data; + int left = *pos; + + if (!instance) { + dbg ("udsl_atm_proc_read: NULL instance!"); + return -ENODEV; + } + + if (!left--) + return sprintf (page, "%s\n", instance->description); + + if (!left--) + return sprintf (page, "MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", + atm_dev->esi [0], atm_dev->esi [1], atm_dev->esi [2], + atm_dev->esi [3], atm_dev->esi [4], atm_dev->esi [5]); + + if (!left--) + return sprintf (page, "AAL5: tx %d ( %d err ), rx %d ( %d err, %d drop )\n", + atomic_read (&atm_dev->stats.aal5.tx), + atomic_read (&atm_dev->stats.aal5.tx_err), + atomic_read (&atm_dev->stats.aal5.rx), + atomic_read (&atm_dev->stats.aal5.rx_err), + atomic_read (&atm_dev->stats.aal5.rx_drop)); + + if (!left--) { + switch (atm_dev->signal) { + case ATM_PHY_SIG_FOUND: + sprintf (page, "Line up"); + break; + case ATM_PHY_SIG_LOST: + sprintf (page, "Line down"); + break; + default: + sprintf (page, "Line state unknown"); + break; + } + + if (instance->usb_dev) { + if (!instance->firmware_loaded) + strcat (page, ", no firmware\n"); + else + strcat (page, ", firmware loaded\n"); + } else + strcat (page, ", disconnected\n"); + + return strlen (page); + } + + return 0; +} + +static int udsl_atm_open (struct atm_vcc *vcc, short vpi, int vci) +{ + struct udsl_instance_data *instance = vcc->dev->dev_data; + struct udsl_vcc_data *new; + + dbg ("udsl_atm_open: vpi %hd, vci %d", vpi, vci); + + if (!instance || !instance->usb_dev) { + dbg ("udsl_atm_open: NULL data!"); + return -ENODEV; + } + + if ((vpi == ATM_VPI_ANY) || (vci == ATM_VCI_ANY)) + return -EINVAL; + + /* only support AAL5 */ + if ((vcc->qos.aal != ATM_AAL5) || (vcc->qos.rxtp.max_sdu < 0) || (vcc->qos.rxtp.max_sdu > ATM_MAX_AAL5_PDU)) + return -EINVAL; + + if (!instance->firmware_loaded) { + dbg ("udsl_atm_open: firmware not loaded!"); + return -EAGAIN; + } + + down (&instance->serialize); /* vs self, udsl_atm_close */ + + if (udsl_find_vcc (instance, vpi, vci)) { + up (&instance->serialize); + return -EADDRINUSE; + } + + if (!(new = kmalloc (sizeof (struct udsl_vcc_data), GFP_KERNEL))) { + up (&instance->serialize); + return -ENOMEM; + } + + memset (new, 0, sizeof (struct udsl_vcc_data)); + new->vcc = vcc; + new->vpi = vpi; + new->vci = vci; + new->max_pdu = max (1, UDSL_NUM_CELLS (vcc->qos.rxtp.max_sdu)) * ATM_CELL_PAYLOAD; + + vcc->dev_data = new; + vcc->vpi = vpi; + vcc->vci = vci; + + tasklet_disable (&instance->receive_tasklet); + list_add (&new->list, &instance->vcc_list); + tasklet_enable (&instance->receive_tasklet); + + set_bit (ATM_VF_ADDR, &vcc->flags); + set_bit (ATM_VF_PARTIAL, &vcc->flags); + set_bit (ATM_VF_READY, &vcc->flags); + + up (&instance->serialize); + + tasklet_schedule (&instance->receive_tasklet); + + dbg ("udsl_atm_open: allocated vcc data 0x%p (max_pdu: %u)", new, new->max_pdu); + + return 0; +} + +static void udsl_atm_close (struct atm_vcc *vcc) +{ + struct udsl_instance_data *instance = vcc->dev->dev_data; + struct udsl_vcc_data *vcc_data = vcc->dev_data; + + dbg ("udsl_atm_close called"); + + if (!instance || !vcc_data) { + dbg ("udsl_atm_close: NULL data!"); + return; + } + + dbg ("udsl_atm_close: deallocating vcc 0x%p with vpi %d vci %d", vcc_data, vcc_data->vpi, vcc_data->vci); + + udsl_cancel_send (instance, vcc); + + down (&instance->serialize); /* vs self, udsl_atm_open */ + + tasklet_disable (&instance->receive_tasklet); + list_del (&vcc_data->list); + tasklet_enable (&instance->receive_tasklet); + + if (vcc_data->skb) + dev_kfree_skb (vcc_data->skb); + vcc_data->skb = NULL; + + kfree (vcc_data); + vcc->dev_data = NULL; + + vcc->vpi = ATM_VPI_UNSPEC; + vcc->vci = ATM_VCI_UNSPEC; + clear_bit (ATM_VF_READY, &vcc->flags); + clear_bit (ATM_VF_PARTIAL, &vcc->flags); + clear_bit (ATM_VF_ADDR, &vcc->flags); + + up (&instance->serialize); + + dbg ("udsl_atm_close successful"); +} + +static int udsl_atm_ioctl (struct atm_dev *dev, unsigned int cmd, void *arg) +{ + switch (cmd) { + case ATM_QUERYLOOP: + return put_user (ATM_LM_NONE, (int *) arg) ? -EFAULT : 0; + default: + return -ENOIOCTLCMD; + } +} + + +/********** +** USB ** +**********/ + +static int udsl_set_alternate (struct udsl_instance_data *instance) +{ + down (&instance->serialize); /* vs self */ + if (!instance->firmware_loaded) { + int ret; + + if ((ret = usb_set_interface (instance->usb_dev, 1, 1)) < 0) { + dbg ("udsl_set_alternate: usb_set_interface returned %d!", ret); + up (&instance->serialize); + return ret; + } + instance->firmware_loaded = 1; + } + up (&instance->serialize); + + tasklet_schedule (&instance->receive_tasklet); + + return 0; +} + +static int udsl_usb_ioctl (struct usb_device *dev, unsigned int code, void *user_data) +{ + struct usb_interface *intf = usb_ifnum_to_if (dev, 1); + struct udsl_instance_data *instance = intf->private_data; + + dbg ("udsl_usb_ioctl entered"); + + if (!instance) { + dbg ("udsl_usb_ioctl: NULL instance!"); + return -ENODEV; + } + + switch (code) { + case UDSL_IOCTL_LINE_UP: + instance->atm_dev->signal = ATM_PHY_SIG_FOUND; + return udsl_set_alternate (instance); + case UDSL_IOCTL_LINE_DOWN: + instance->atm_dev->signal = ATM_PHY_SIG_LOST; + return 0; + default: + return -ENOTTY; + } +} + +static void *udsl_usb_probe (struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id) +{ + struct udsl_instance_data *instance; + unsigned char mac_str [13]; + int i, length; + char *buf; + + dbg ("udsl_usb_probe: trying device with vendor=0x%x, product=0x%x, ifnum %d", + dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum); + + if ((dev->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) || + (dev->descriptor.idVendor != SPEEDTOUCH_VENDORID) || + (dev->descriptor.idProduct != SPEEDTOUCH_PRODUCTID) || (ifnum != 1)) + return NULL; + + dbg ("udsl_usb_probe: device accepted"); + + /* instance init */ + if (!(instance = kmalloc (sizeof (struct udsl_instance_data), GFP_KERNEL))) { + dbg ("udsl_usb_probe: no memory for instance data!"); + return NULL; + } + + memset (instance, 0, sizeof (struct udsl_instance_data)); + + init_MUTEX (&instance->serialize); + + instance->usb_dev = dev; + + INIT_LIST_HEAD (&instance->vcc_list); + + spin_lock_init (&instance->receive_lock); + INIT_LIST_HEAD (&instance->spare_receivers); + INIT_LIST_HEAD (&instance->filled_receive_buffers); + + tasklet_init (&instance->receive_tasklet, udsl_process_receive, (unsigned long) instance); + INIT_LIST_HEAD (&instance->spare_receive_buffers); + + skb_queue_head_init (&instance->sndqueue); + + spin_lock_init (&instance->send_lock); + INIT_LIST_HEAD (&instance->spare_senders); + INIT_LIST_HEAD (&instance->spare_send_buffers); + + tasklet_init (&instance->send_tasklet, udsl_process_send, (unsigned long) instance); + INIT_LIST_HEAD (&instance->filled_send_buffers); + + /* receive init */ + for (i = 0; i < num_rcv_urbs; i++) { + struct udsl_receiver *rcv = &(instance->receivers [i]); + + if (!(rcv->urb = usb_alloc_urb (0))) { + dbg ("udsl_usb_probe: no memory for receive urb %d!", i); + goto fail; + } + + rcv->instance = instance; + + list_add (&rcv->list, &instance->spare_receivers); + } + + for (i = 0; i < num_rcv_bufs; i++) { + struct udsl_receive_buffer *buf = &(instance->receive_buffers [i]); + + if (!(buf->base = kmalloc (rcv_buf_size * ATM_CELL_SIZE, GFP_KERNEL))) { + dbg ("udsl_usb_probe: no memory for receive buffer %d!", i); + goto fail; + } + + list_add (&buf->list, &instance->spare_receive_buffers); + } + + /* send init */ + for (i = 0; i < num_snd_urbs; i++) { + struct udsl_sender *snd = &(instance->senders [i]); + + if (!(snd->urb = usb_alloc_urb (0))) { + dbg ("udsl_usb_probe: no memory for send urb %d!", i); + goto fail; + } + + snd->instance = instance; + + list_add (&snd->list, &instance->spare_senders); + } + + for (i = 0; i < num_snd_bufs; i++) { + struct udsl_send_buffer *buf = &(instance->send_buffers [i]); + + if (!(buf->base = kmalloc (snd_buf_size * ATM_CELL_SIZE, GFP_KERNEL))) { + dbg ("udsl_usb_probe: no memory for send buffer %d!", i); + goto fail; + } + + list_add (&buf->list, &instance->spare_send_buffers); + } + + /* ATM init */ + if (!(instance->atm_dev = atm_dev_register (udsl_driver_name, &udsl_atm_devops, -1, 0))) { + dbg ("udsl_usb_probe: failed to register ATM device!"); + goto fail; + } + + instance->atm_dev->ci_range.vpi_bits = ATM_CI_MAX; + instance->atm_dev->ci_range.vci_bits = ATM_CI_MAX; + instance->atm_dev->signal = ATM_PHY_SIG_UNKNOWN; + + /* temp init ATM device, set to 128kbit */ + instance->atm_dev->link_rate = 128 * 1000 / 424; + + /* set MAC address, it is stored in the serial number */ + memset (instance->atm_dev->esi, 0, sizeof (instance->atm_dev->esi)); + if (usb_string (dev, dev->descriptor.iSerialNumber, mac_str, sizeof (mac_str)) == 12) + for (i = 0; i < 6; i++) + instance->atm_dev->esi [i] = (hex2int (mac_str [i * 2]) * 16) + (hex2int (mac_str [i * 2 + 1])); + + /* device description */ + buf = instance->description; + length = sizeof (instance->description); + + if ((i = usb_string (dev, dev->descriptor.iProduct, buf, length)) < 0) + goto finish; + + buf += i; + length -= i; + + i = snprintf (buf, length, " ("); + buf += i; + length -= i; + + if (length <= 0 || (i = usb_make_path (dev, buf, length)) < 0) + goto finish; + + buf += i; + length -= i; + + snprintf (buf, length, ")"); + +finish: + /* ready for ATM callbacks */ + wmb (); + instance->atm_dev->dev_data = instance; + + return instance; + +fail: + for (i = 0; i < num_snd_bufs; i++) + kfree (instance->send_buffers [i].base); + + for (i = 0; i < num_snd_urbs; i++) + usb_free_urb (instance->senders [i].urb); + + for (i = 0; i < num_rcv_bufs; i++) + kfree (instance->receive_buffers [i].base); + + for (i = 0; i < num_rcv_urbs; i++) + usb_free_urb (instance->receivers [i].urb); + + kfree (instance); + + return NULL; +} + +static void udsl_usb_disconnect (struct usb_device *dev, void *ptr) +{ + struct udsl_instance_data *instance = ptr; + struct list_head *pos; + unsigned int count; + int result, i; + + dbg ("udsl_usb_disconnect entered"); + + if (!instance) { + dbg ("udsl_usb_disconnect: NULL instance!"); + return; + } + + /* receive finalize */ + tasklet_disable (&instance->receive_tasklet); + + for (i = 0; i < num_rcv_urbs; i++) + if ((result = usb_unlink_urb (instance->receivers [i].urb)) < 0) + dbg ("udsl_usb_disconnect: usb_unlink_urb on receive urb %d returned %d", i, result); + + /* wait for completion handlers to finish */ + do { + count = 0; + spin_lock_irq (&instance->receive_lock); + list_for_each (pos, &instance->spare_receivers) + if (++count > num_rcv_urbs) + panic (__FILE__ ": memory corruption detected at line %d!\n", __LINE__); + spin_unlock_irq (&instance->receive_lock); + + dbg ("udsl_usb_disconnect: found %u spare receivers", count); + + if (count == num_rcv_urbs) + break; + + set_current_state (TASK_RUNNING); + schedule (); + } while (1); + + /* no need to take the spinlock */ + INIT_LIST_HEAD (&instance->filled_receive_buffers); + INIT_LIST_HEAD (&instance->spare_receive_buffers); + + tasklet_enable (&instance->receive_tasklet); + + for (i = 0; i < num_rcv_urbs; i++) + usb_free_urb (instance->receivers [i].urb); + + for (i = 0; i < num_rcv_bufs; i++) + kfree (instance->receive_buffers [i].base); + + /* send finalize */ + tasklet_disable (&instance->send_tasklet); + + for (i = 0; i < num_snd_urbs; i++) + if ((result = usb_unlink_urb (instance->senders [i].urb)) < 0) + dbg ("udsl_usb_disconnect: usb_unlink_urb on send urb %d returned %d", i, result); + + /* wait for completion handlers to finish */ + do { + count = 0; + spin_lock_irq (&instance->send_lock); + list_for_each (pos, &instance->spare_senders) + if (++count > num_snd_urbs) + panic (__FILE__ ": memory corruption detected at line %d!\n", __LINE__); + spin_unlock_irq (&instance->send_lock); + + dbg ("udsl_usb_disconnect: found %u spare senders", count); + + if (count == num_snd_urbs) + break; + + set_current_state (TASK_RUNNING); + schedule (); + } while (1); + + /* no need to take the spinlock */ + INIT_LIST_HEAD (&instance->spare_senders); + INIT_LIST_HEAD (&instance->spare_send_buffers); + instance->current_buffer = NULL; + + tasklet_enable (&instance->send_tasklet); + + for (i = 0; i < num_snd_urbs; i++) + usb_free_urb (instance->senders [i].urb); + + for (i = 0; i < num_snd_bufs; i++) + kfree (instance->send_buffers [i].base); + + wmb (); + instance->usb_dev = NULL; + + /* ATM finalize */ + shutdown_atm_dev (instance->atm_dev); /* frees instance, kills tasklets */ +} + + +/*********** +** init ** +***********/ + +static int __init udsl_usb_init (void) +{ + struct sk_buff *skb; /* dummy for sizeof */ + + dbg ("udsl_usb_init: driver version " DRIVER_VERSION); + + if (sizeof (struct udsl_control) > sizeof (skb->cb)) { + printk (KERN_ERR __FILE__ ": unusable with this kernel!\n"); + return -EIO; + } + + if ((num_rcv_urbs > UDSL_MAX_RCV_URBS) || (num_snd_urbs > UDSL_MAX_SND_URBS) || + (num_rcv_bufs > UDSL_MAX_RCV_BUFS) || (num_snd_bufs > UDSL_MAX_SND_BUFS) || + (rcv_buf_size > UDSL_MAX_RCV_BUF_SIZE) || (snd_buf_size > UDSL_MAX_SND_BUF_SIZE)) + return -EINVAL; + + return usb_register (&udsl_usb_driver); +} + +static void __exit udsl_usb_cleanup (void) +{ + dbg ("udsl_usb_cleanup entered"); + + usb_deregister (&udsl_usb_driver); +} + +module_init (udsl_usb_init); +module_exit (udsl_usb_cleanup); + +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_LICENSE ("GPL"); + + +/************ +** debug ** +************/ + +#ifdef VERBOSE_DEBUG +static int udsl_print_packet (const unsigned char *data, int len) +{ + unsigned char buffer [256]; + int i = 0, j = 0; + + for (i = 0; i < len;) { + buffer [0] = '\0'; + sprintf (buffer, "%.3d :", i); + for (j = 0; (j < 16) && (i < len); j++, i++) { + sprintf (buffer, "%s %2.2x", buffer, data [i]); + } + dbg ("%s", buffer); + } + return i; +} +#endif diff -Nru a/drivers/usb/storage/protocol.h b/drivers/usb/storage/protocol.h --- a/drivers/usb/storage/protocol.h Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/storage/protocol.h Thu Jun 19 17:28:35 2003 @@ -57,6 +57,8 @@ #define US_SC_MIN US_SC_RBC #define US_SC_MAX US_SC_ISD200 +#define US_SC_DEVICE 0xff /* Use device's value */ + extern void usb_stor_ATAPI_command(Scsi_Cmnd*, struct us_data*); extern void usb_stor_qic157_command(Scsi_Cmnd*, struct us_data*); extern void usb_stor_ufi_command(Scsi_Cmnd*, struct us_data*); diff -Nru a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c --- a/drivers/usb/storage/transport.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/storage/transport.c Thu Jun 19 17:28:35 2003 @@ -1250,7 +1250,7 @@ US_DEBUGP("Bulk status Sig 0x%x T 0x%x R %d Stat 0x%x\n", le32_to_cpu(bcs->Signature), bcs->Tag, bcs->Residue, bcs->Status); - if (bcs->Signature != cpu_to_le32(US_BULK_CS_SIGN) || + if ((bcs->Signature != cpu_to_le32(US_BULK_CS_SIGN) && bcs->Signature != cpu_to_le32(US_BULK_CS_OLYMPUS_SIGN)) || bcs->Tag != bcb->Tag || bcs->Status > US_BULK_STAT_PHASE || partial != 13) { US_DEBUGP("Bulk logical error\n"); diff -Nru a/drivers/usb/storage/transport.h b/drivers/usb/storage/transport.h --- a/drivers/usb/storage/transport.h Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/storage/transport.h Thu Jun 19 17:28:35 2003 @@ -75,6 +75,8 @@ #define US_PR_JUMPSHOT 0xf3 /* Lexar Jumpshot */ #endif +#define US_PR_DEVICE 0xff /* Use device's value */ + /* * Bulk only data structures */ @@ -106,6 +108,8 @@ #define US_BULK_CS_WRAP_LEN 13 #define US_BULK_CS_SIGN 0x53425355 /* spells out 'USBS' */ +/* This is for Olympus Camedia digital cameras */ +#define US_BULK_CS_OLYMPUS_SIGN 0x55425355 /* spells out 'USBU' */ #define US_BULK_STAT_OK 0 #define US_BULK_STAT_FAIL 1 #define US_BULK_STAT_PHASE 2 diff -Nru a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h --- a/drivers/usb/storage/unusual_devs.h Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/storage/unusual_devs.h Thu Jun 19 17:28:35 2003 @@ -35,6 +35,14 @@ /* If you edit this file, please try to keep it sorted first by VendorID, * then by ProductID. + * + * If you want to add an entry for this file, please send the following + * to greg@kroah.com: + * - patch that adds the entry for your device which includes your + * email address right above the entry. + * - a copy of /proc/bus/usb/devices with your device plugged in + * running with this patch. + * */ UNUSUAL_DEV( 0x03ee, 0x0000, 0x0000, 0x0245, @@ -90,6 +98,18 @@ "Nex II Digital", US_SC_SCSI, US_PR_BULK, NULL, US_FL_START_STOP), +/* Patch submitted by Philipp Friedrich */ +UNUSUAL_DEV( 0x0482, 0x0100, 0x0100, 0x0100, + "Kyocera", + "Finecam S3x", + US_SC_8070, US_PR_CB, NULL, US_FL_FIX_INQUIRY), + +/* Patch submitted by Philipp Friedrich */ +UNUSUAL_DEV( 0x0482, 0x0101, 0x0100, 0x0100, + "Kyocera", + "Finecam S4", + US_SC_8070, US_PR_CB, NULL, US_FL_FIX_INQUIRY), + /* Reported by Paul Stewart * This entry is needed because the device reports Sub=ff */ UNUSUAL_DEV( 0x04a4, 0x0004, 0x0001, 0x0001, @@ -222,10 +242,18 @@ US_SC_8070, US_PR_BULK, NULL, US_FL_FIX_INQUIRY | US_FL_START_STOP ), +/* Submitted by Lars Gemeinhardt + * Needed for START_STOP flag */ +UNUSUAL_DEV( 0x0547, 0x2810, 0x0001, 0x0001, + "Mello", + "MP3 Player", + US_SC_SCSI, US_PR_BULK, NULL, + US_FL_START_STOP), + /* This entry is needed because the device reports Sub=ff */ -UNUSUAL_DEV( 0x054c, 0x0010, 0x0106, 0x0440, +UNUSUAL_DEV( 0x054c, 0x0010, 0x0106, 0x0450, "Sony", - "DSC-S30/S70/S75/505V/F505/F707/F717", + "DSC-S30/S70/S75/505V/F505/F707/F717/P8", US_SC_SCSI, US_PR_CB, NULL, US_FL_SINGLE_LUN | US_FL_START_STOP | US_FL_MODE_XLATE ), @@ -294,6 +322,26 @@ US_SC_SCSI, US_PR_BULK, NULL, US_FL_START_STOP ), +/* This Pentax still camera is not conformant + * to the USB storage specification: - + * - It does not like the INQUIRY command. So we must handle this command + * of the SCSI layer ourselves. + * Tested on Rev. 10.00 (0x1000) + * Submitted by James Courtier-Dutton + */ +UNUSUAL_DEV( 0x0a17, 0x0004, 0x1000, 0x1000, + "Pentax", + "Optio 2/3/400", + US_SC_8070, US_PR_CBI, NULL, + US_FL_FIX_INQUIRY ), + +/* Submitted by Per Winkvist */ +UNUSUAL_DEV( 0x0a17, 0x006, 0x1000, 0x9009, + "Pentax", + "Optio S", + US_SC_8070, US_PR_CBI, NULL, + US_FL_FIX_INQUIRY ), + #ifdef CONFIG_USB_STORAGE_ISD200 UNUSUAL_DEV( 0x05ab, 0x0031, 0x0100, 0x0110, "In-System", @@ -364,6 +412,14 @@ US_SC_SCSI, US_PR_BULK, NULL, US_FL_FIX_INQUIRY ), +/* Reported by Hanno Boeck + * Taken from the Lycoris Kernel */ +UNUSUAL_DEV( 0x0636, 0x0003, 0x0000, 0x9999, + "Vivitar", + "Vivicam 35Xx", + US_SC_SCSI, US_PR_BULK, NULL, + US_FL_FIX_INQUIRY | US_FL_MODE_XLATE), + UNUSUAL_DEV( 0x0644, 0x0000, 0x0100, 0x0100, "TEAC", "Floppy Drive", @@ -543,6 +599,13 @@ US_SC_8070, US_PR_CB, NULL, US_FL_FIX_INQUIRY ), +/* Submitted by Hartmut Wahl */ +UNUSUAL_DEV( 0x0839, 0x000a, 0x0001, 0x0001, + "Samsung", + "Digimax 410", + US_SC_SCSI, US_PR_BULK, NULL, + US_FL_FIX_INQUIRY), + UNUSUAL_DEV( 0x097a, 0x0001, 0x0000, 0x0001, "Minds@Work", "Digital Wallet", @@ -555,6 +618,16 @@ US_SC_SCSI, US_PR_BULK, NULL, US_FL_FIX_INQUIRY ), +/* Pentax Optio S digital camera + * adapted from http://www2.goldfisch.at/knowledge/233 + * (Peter Pilsl ) + * by Christoph Weidemann */ +UNUSUAL_DEV( 0x0a17, 0x0006, 0x0000, 0xffff, + "Pentax", + "Optio S", + US_SC_8070, US_PR_CB, NULL, + US_FL_MODE_XLATE|US_FL_FIX_INQUIRY), + #ifdef CONFIG_USB_STORAGE_ISD200 UNUSUAL_DEV( 0x0bf6, 0xa001, 0x0100, 0x0110, "ATI", @@ -594,3 +667,13 @@ US_SC_SCSI, US_PR_SDDR55, NULL, US_FL_SINGLE_LUN), #endif + +/* Aiptek PocketCAM 3Mega + * Nicolas DUPEUX + */ +UNUSUAL_DEV( 0x08ca, 0x2011, 0x0000, 0x9999, + "AIPTEK", + "PocketCAM 3Mega", + US_SC_SCSI, US_PR_BULK, NULL, + US_FL_MODE_XLATE | US_FL_START_STOP), + diff -Nru a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c --- a/drivers/usb/storage/usb.c Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/storage/usb.c Thu Jun 19 17:28:35 2003 @@ -621,8 +621,12 @@ US_DEBUGP("USB Mass Storage device detected\n"); /* Determine subclass and protocol, or copy from the interface */ - subclass = unusual_dev->useProtocol; - protocol = unusual_dev->useTransport; + subclass = (unusual_dev->useProtocol == US_SC_DEVICE) ? + altsetting->bInterfaceSubClass : + unusual_dev->useProtocol; + protocol = (unusual_dev->useTransport == US_PR_DEVICE) ? + altsetting->bInterfaceProtocol : + unusual_dev->useTransport; flags = unusual_dev->flags; /* diff -Nru a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h --- a/drivers/usb/storage/usb.h Thu Jun 19 17:28:35 2003 +++ b/drivers/usb/storage/usb.h Thu Jun 19 17:28:35 2003 @@ -155,7 +155,7 @@ /* thread information */ Scsi_Cmnd *queue_srb; /* the single queue slot */ int action; /* what to do */ - int pid; /* control thread */ + pid_t pid; /* control thread */ /* interrupt info for CBI devices -- only good if attached */ struct semaphore ip_waitq; /* for CBI interrupts */ diff -Nru a/drivers/usb/vicam.c b/drivers/usb/vicam.c --- a/drivers/usb/vicam.c Thu Jun 19 17:28:36 2003 +++ b/drivers/usb/vicam.c Thu Jun 19 17:28:36 2003 @@ -1,6 +1,10 @@ /* * USB ViCam WebCam driver - * Copyright (c) 2002 Joe Burks (jburks@wavicle.org) + * Copyright (c) 2002 Joe Burks (jburks@wavicle.org), + * Christopher L Cheney (ccheney@cheney.cx), + * Pavel Machek (pavel@suse.cz), + * John Tyner (jtyner@cs.ucr.edu), + * Monroe Williams (monroe@pobox.com) * * Supports 3COM HomeConnect PC Digital WebCam * @@ -18,8 +22,17 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * - * This source code is based heavily on the CPiA webcam driver - * */ + * This source code is based heavily on the CPiA webcam driver which was + * written by Peter Pregler, Scott J. Bertin and Johannes Erdfelt + * + * Portions of this code were also copied from usbvideo.c + * + * Special thanks to the the whole team at Sourceforge for help making + * this driver become a reality. Notably: + * Andy Armstrong who reverse engineered the color encoding and + * Pavel Machek and Chris Cheney who worked on reverse engineering the + * camera controls and wrote the first generation driver. + */ #include #include diff -Nru a/include/linux/hiddev.h b/include/linux/hiddev.h --- a/include/linux/hiddev.h Thu Jun 19 17:28:36 2003 +++ b/include/linux/hiddev.h Thu Jun 19 17:28:36 2003 @@ -49,6 +49,13 @@ unsigned num_applications; }; +struct hiddev_collection_info { + unsigned index; + unsigned type; + unsigned usage; + unsigned level; +}; + #define HID_STRING_SIZE 256 struct hiddev_string_descriptor { int index; @@ -119,12 +126,17 @@ __s32 value; }; +/* FIELD_INDEX_NONE is returned in read() data from the kernel when flags + * is set to (HIDDEV_FLAG_UREF | HIDDEV_FLAG_REPORT) and a new report has + * been sent by the device + */ +#define HID_FIELD_INDEX_NONE 0xffffffff /* * Protocol version. */ -#define HID_VERSION 0x010002 +#define HID_VERSION 0x010004 /* * IOCTLs (0x00 - 0x7f) @@ -138,11 +150,22 @@ #define HIDIOCGNAME(len) _IOC(_IOC_READ, 'H', 0x06, len) #define HIDIOCGREPORT _IOW('H', 0x07, struct hiddev_report_info) #define HIDIOCSREPORT _IOW('H', 0x08, struct hiddev_report_info) -#define HIDIOCGREPORTINFO _IOWR('H', 0x09, struct hiddev_report_info) -#define HIDIOCGFIELDINFO _IOWR('H', 0x0A, struct hiddev_field_info) -#define HIDIOCGUSAGE _IOWR('H', 0x0B, struct hiddev_usage_ref) -#define HIDIOCSUSAGE _IOW('H', 0x0C, struct hiddev_usage_ref) -#define HIDIOCGUCODE _IOWR('H', 0x0D, struct hiddev_usage_ref) +#define HIDIOCGREPORTINFO _IOWR('H', 0x09, struct hiddev_report_info) +#define HIDIOCGFIELDINFO _IOWR('H', 0x0A, struct hiddev_field_info) +#define HIDIOCGUSAGE _IOWR('H', 0x0B, struct hiddev_usage_ref) +#define HIDIOCSUSAGE _IOW('H', 0x0C, struct hiddev_usage_ref) +#define HIDIOCGUCODE _IOWR('H', 0x0D, struct hiddev_usage_ref) +#define HIDIOCGFLAG _IOR('H', 0x0E, int) +#define HIDIOCSFLAG _IOW('H', 0x0F, int) +#define HIDIOCGCOLLECTIONINDEX _IOW('H', 0x10, struct hiddev_usage_ref) +#define HIDIOCGCOLLECTIONINFO _IOWR('H', 0x11, struct hiddev_collection_info) + +/* + * Flags to be used in HIDIOCSFLAG + */ +#define HIDDEV_FLAG_UREF 0x1 +#define HIDDEV_FLAG_REPORT 0x2 +#define HIDDEV_FLAGS 0x3 /* To traverse the input report descriptor info for a HID device, perform the * following: @@ -179,13 +202,19 @@ #ifdef CONFIG_USB_HIDDEV int hiddev_connect(struct hid_device *); void hiddev_disconnect(struct hid_device *); -void hiddev_hid_event(struct hid_device *, unsigned int usage, int value); +void hiddev_hid_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, __s32 value); int __init hiddev_init(void); void __exit hiddev_exit(void); #else static inline int hiddev_connect(struct hid_device *hid) { return -1; } static inline void hiddev_disconnect(struct hid_device *hid) { } -static inline void hiddev_hid_event(struct hid_device *hid, unsigned int usage, int value) { } +static inline void hiddev_hid_event(struct hid_device *hid, + struct hid_field *field, + struct hid_usage *usage, + __s32 value) { } +static inline void hiddev_report_event(struct hid_device *hid, + struct hid_report *report) { } static inline int hiddev_init(void) { return 0; } static inline void hiddev_exit(void) { } #endif