Merge branch 'for-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/dvrabel/uwb
Linus Torvalds [Thu, 23 Oct 2008 15:20:34 +0000 (08:20 -0700)]
* 'for-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/dvrabel/uwb: (47 commits)
  uwb: wrong sizeof argument in mac address compare
  uwb: don't use printk_ratelimit() so often
  uwb: use kcalloc where appropriate
  uwb: use time_after() when purging stale beacons
  uwb: add credits for the original developers of the UWB/WUSB/WLP subsystems
  uwb: add entries in the MAINTAINERS file
  uwb: depend on EXPERIMENTAL
  wusb: wusb-cbaf (CBA driver) sysfs ABI simplification
  uwb: document UWB and WUSB sysfs files
  uwb: add symlinks in sysfs between radio controllers and PALs
  uwb: dont tranmit identification IEs
  uwb: i1480/GUWA100U: fix firmware download issues
  uwb: i1480: remove MAC/PHY information checking function
  uwb: add Intel i1480 HWA to the UWB RC quirk table
  uwb: disable command/event filtering for D-Link DUB-1210
  uwb: initialize the debug sub-system
  uwb: Fix handling IEs with empty IE data in uwb_est_get_size()
  wusb: fix bmRequestType for Abort RPipe request
  wusb: fix error path for wusb_set_dev_addr()
  wusb: add HWA host controller driver
  ...

110 files changed:
CREDITS
Documentation/ABI/testing/sysfs-bus-umc [new file with mode: 0644]
Documentation/ABI/testing/sysfs-bus-usb
Documentation/ABI/testing/sysfs-class-usb_host [new file with mode: 0644]
Documentation/ABI/testing/sysfs-class-uwb_rc [new file with mode: 0644]
Documentation/ABI/testing/sysfs-wusb_cbaf [new file with mode: 0644]
Documentation/usb/WUSB-Design-overview.txt [new file with mode: 0644]
Documentation/usb/wusb-cbaf [new file with mode: 0644]
MAINTAINERS
arch/arm/Kconfig
arch/cris/Kconfig
arch/h8300/Kconfig
drivers/Kconfig
drivers/Makefile
drivers/usb/Kconfig
drivers/usb/Makefile
drivers/usb/host/Kconfig
drivers/usb/host/Makefile
drivers/usb/host/hwa-hc.c [new file with mode: 0644]
drivers/usb/host/whci/Kbuild [new file with mode: 0644]
drivers/usb/host/whci/asl.c [new file with mode: 0644]
drivers/usb/host/whci/hcd.c [new file with mode: 0644]
drivers/usb/host/whci/hw.c [new file with mode: 0644]
drivers/usb/host/whci/init.c [new file with mode: 0644]
drivers/usb/host/whci/int.c [new file with mode: 0644]
drivers/usb/host/whci/pzl.c [new file with mode: 0644]
drivers/usb/host/whci/qset.c [new file with mode: 0644]
drivers/usb/host/whci/whcd.h [new file with mode: 0644]
drivers/usb/host/whci/whci-hc.h [new file with mode: 0644]
drivers/usb/host/whci/wusb.c [new file with mode: 0644]
drivers/usb/wusbcore/Kconfig [new file with mode: 0644]
drivers/usb/wusbcore/Makefile [new file with mode: 0644]
drivers/usb/wusbcore/cbaf.c [new file with mode: 0644]
drivers/usb/wusbcore/crypto.c [new file with mode: 0644]
drivers/usb/wusbcore/dev-sysfs.c [new file with mode: 0644]
drivers/usb/wusbcore/devconnect.c [new file with mode: 0644]
drivers/usb/wusbcore/mmc.c [new file with mode: 0644]
drivers/usb/wusbcore/pal.c [new file with mode: 0644]
drivers/usb/wusbcore/reservation.c [new file with mode: 0644]
drivers/usb/wusbcore/rh.c [new file with mode: 0644]
drivers/usb/wusbcore/security.c [new file with mode: 0644]
drivers/usb/wusbcore/wa-hc.c [new file with mode: 0644]
drivers/usb/wusbcore/wa-hc.h [new file with mode: 0644]
drivers/usb/wusbcore/wa-nep.c [new file with mode: 0644]
drivers/usb/wusbcore/wa-rpipe.c [new file with mode: 0644]
drivers/usb/wusbcore/wa-xfer.c [new file with mode: 0644]
drivers/usb/wusbcore/wusbhc.c [new file with mode: 0644]
drivers/usb/wusbcore/wusbhc.h [new file with mode: 0644]
drivers/uwb/Kconfig [new file with mode: 0644]
drivers/uwb/Makefile [new file with mode: 0644]
drivers/uwb/address.c [new file with mode: 0644]
drivers/uwb/beacon.c [new file with mode: 0644]
drivers/uwb/driver.c [new file with mode: 0644]
drivers/uwb/drp-avail.c [new file with mode: 0644]
drivers/uwb/drp-ie.c [new file with mode: 0644]
drivers/uwb/drp.c [new file with mode: 0644]
drivers/uwb/est.c [new file with mode: 0644]
drivers/uwb/hwa-rc.c [new file with mode: 0644]
drivers/uwb/i1480/Makefile [new file with mode: 0644]
drivers/uwb/i1480/dfu/Makefile [new file with mode: 0644]
drivers/uwb/i1480/dfu/dfu.c [new file with mode: 0644]
drivers/uwb/i1480/dfu/i1480-dfu.h [new file with mode: 0644]
drivers/uwb/i1480/dfu/mac.c [new file with mode: 0644]
drivers/uwb/i1480/dfu/phy.c [new file with mode: 0644]
drivers/uwb/i1480/dfu/usb.c [new file with mode: 0644]
drivers/uwb/i1480/i1480-est.c [new file with mode: 0644]
drivers/uwb/i1480/i1480-wlp.h [new file with mode: 0644]
drivers/uwb/i1480/i1480u-wlp/Makefile [new file with mode: 0644]
drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h [new file with mode: 0644]
drivers/uwb/i1480/i1480u-wlp/lc.c [new file with mode: 0644]
drivers/uwb/i1480/i1480u-wlp/netdev.c [new file with mode: 0644]
drivers/uwb/i1480/i1480u-wlp/rx.c [new file with mode: 0644]
drivers/uwb/i1480/i1480u-wlp/sysfs.c [new file with mode: 0644]
drivers/uwb/i1480/i1480u-wlp/tx.c [new file with mode: 0644]
drivers/uwb/ie.c [new file with mode: 0644]
drivers/uwb/lc-dev.c [new file with mode: 0644]
drivers/uwb/lc-rc.c [new file with mode: 0644]
drivers/uwb/neh.c [new file with mode: 0644]
drivers/uwb/pal.c [new file with mode: 0644]
drivers/uwb/reset.c [new file with mode: 0644]
drivers/uwb/rsv.c [new file with mode: 0644]
drivers/uwb/scan.c [new file with mode: 0644]
drivers/uwb/umc-bus.c [new file with mode: 0644]
drivers/uwb/umc-dev.c [new file with mode: 0644]
drivers/uwb/umc-drv.c [new file with mode: 0644]
drivers/uwb/uwb-debug.c [new file with mode: 0644]
drivers/uwb/uwb-internal.h [new file with mode: 0644]
drivers/uwb/uwbd.c [new file with mode: 0644]
drivers/uwb/whc-rc.c [new file with mode: 0644]
drivers/uwb/whci.c [new file with mode: 0644]
drivers/uwb/wlp/Makefile [new file with mode: 0644]
drivers/uwb/wlp/driver.c [new file with mode: 0644]
drivers/uwb/wlp/eda.c [new file with mode: 0644]
drivers/uwb/wlp/messages.c [new file with mode: 0644]
drivers/uwb/wlp/sysfs.c [new file with mode: 0644]
drivers/uwb/wlp/txrx.c [new file with mode: 0644]
drivers/uwb/wlp/wlp-internal.h [new file with mode: 0644]
drivers/uwb/wlp/wlp-lc.c [new file with mode: 0644]
drivers/uwb/wlp/wss-lc.c [new file with mode: 0644]
include/linux/bitmap.h
include/linux/usb/wusb-wa.h [new file with mode: 0644]
include/linux/usb/wusb.h [new file with mode: 0644]
include/linux/uwb.h [new file with mode: 0644]
include/linux/uwb/debug-cmd.h [new file with mode: 0644]
include/linux/uwb/debug.h [new file with mode: 0644]
include/linux/uwb/spec.h [new file with mode: 0644]
include/linux/uwb/umc.h [new file with mode: 0644]
include/linux/uwb/whci.h [new file with mode: 0644]
include/linux/wlp.h [new file with mode: 0644]
lib/bitmap.c

diff --git a/CREDITS b/CREDITS
index 2358846..b50db17 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -598,6 +598,11 @@ S: Tamsui town, Taipei county,
 S: Taiwan 251
 S: Republic of China
 
+N: Reinette Chatre
+E: reinette.chatre@intel.com
+D: WiMedia Link Protocol implementation
+D: UWB stack bits and pieces
+
 N: Michael Elizabeth Chastain
 E: mec@shout.net
 D: Configure, Menuconfig, xconfig
@@ -2695,6 +2700,12 @@ S: Demonstratsii 8-382
 S: Tula 300000
 S: Russia
 
+N: Inaky Perez-Gonzalez
+E: inaky.perez-gonzalez@intel.com
+D: UWB stack, HWA-RC driver and HWA-HC drivers
+D: Wireless USB additions to the USB stack
+D: WiMedia Link Protocol bits and pieces
+
 N: Gordon Peters
 E: GordPeters@smarttech.com
 D: Isochronous receive for IEEE 1394 driver (OHCI module).
diff --git a/Documentation/ABI/testing/sysfs-bus-umc b/Documentation/ABI/testing/sysfs-bus-umc
new file mode 100644 (file)
index 0000000..948fec4
--- /dev/null
@@ -0,0 +1,28 @@
+What:           /sys/bus/umc/
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                The Wireless Host Controller Interface (WHCI)
+                specification describes a PCI-based device with
+                multiple capabilities; the UWB Multi-interface
+                Controller (UMC).
+
+                The umc bus presents each of the individual
+                capabilties as a device.
+
+What:           /sys/bus/umc/devices/.../capability_id
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                The ID of this capability, with 0 being the radio
+                controller capability.
+
+What:           /sys/bus/umc/devices/.../version
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                The specification version this capability's hardware
+                interface complies with.
index df6c8a0..7772928 100644 (file)
@@ -101,3 +101,46 @@ Description:
 Users:
                USB PM tool
                git://git.moblin.org/users/sarah/usb-pm-tool/
+
+What:          /sys/bus/usb/device/.../authorized
+Date:          July 2008
+KernelVersion: 2.6.26
+Contact:       David Vrabel <david.vrabel@csr.com>
+Description:
+               Authorized devices are available for use by device
+               drivers, non-authorized one are not.  By default, wired
+               USB devices are authorized.
+
+               Certified Wireless USB devices are not authorized
+               initially and should be (by writing 1) after the
+               device has been authenticated.
+
+What:          /sys/bus/usb/device/.../wusb_cdid
+Date:          July 2008
+KernelVersion: 2.6.27
+Contact:       David Vrabel <david.vrabel@csr.com>
+Description:
+               For Certified Wireless USB devices only.
+
+               A devices's CDID, as 16 space-separated hex octets.
+
+What:          /sys/bus/usb/device/.../wusb_ck
+Date:          July 2008
+KernelVersion: 2.6.27
+Contact:       David Vrabel <david.vrabel@csr.com>
+Description:
+               For Certified Wireless USB devices only.
+
+               Write the device's connection key (CK) to start the
+               authentication of the device.  The CK is 16
+               space-separated hex octets.
+
+What:          /sys/bus/usb/device/.../wusb_disconnect
+Date:          July 2008
+KernelVersion: 2.6.27
+Contact:       David Vrabel <david.vrabel@csr.com>
+Description:
+               For Certified Wireless USB devices only.
+
+               Write a 1 to force the device to disconnect
+               (equivalent to unplugging a wired USB device).
diff --git a/Documentation/ABI/testing/sysfs-class-usb_host b/Documentation/ABI/testing/sysfs-class-usb_host
new file mode 100644 (file)
index 0000000..46b66ad
--- /dev/null
@@ -0,0 +1,25 @@
+What:           /sys/class/usb_host/usb_hostN/wusb_chid
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                Write the CHID (16 space-separated hex octets) for this host controller.
+                This starts the host controller, allowing it to accept connection from
+                WUSB devices.
+
+                Set an all zero CHID to stop the host controller.
+
+What:           /sys/class/usb_host/usb_hostN/wusb_trust_timeout
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                Devices that haven't sent a WUSB packet to the host
+                within 'wusb_trust_timeout' ms are considered to have
+                disconnected and are removed.  The default value of
+                4000 ms is the value required by the WUSB
+                specification.
+
+                Since this relates to security (specifically, the
+                lifetime of PTKs and GTKs) it should not be changed
+                from the default.
diff --git a/Documentation/ABI/testing/sysfs-class-uwb_rc b/Documentation/ABI/testing/sysfs-class-uwb_rc
new file mode 100644 (file)
index 0000000..a0d18db
--- /dev/null
@@ -0,0 +1,144 @@
+What:           /sys/class/uwb_rc
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                Interfaces for WiMedia Ultra Wideband Common Radio
+                Platform (UWB) radio controllers.
+
+                Familiarity with the ECMA-368 'High Rate Ultra
+                Wideband MAC and PHY Specification' is assumed.
+
+What:           /sys/class/uwb_rc/beacon_timeout_ms
+Date:           July 2008
+KernelVersion:  2.6.27
+Description:
+                If no beacons are received from a device for at least
+                this time, the device will be considered to have gone
+                and it will be removed.  The default is 3 superframes
+                (~197 ms) as required by the specification.
+
+What:           /sys/class/uwb_rc/uwbN/
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                An individual UWB radio controller.
+
+What:           /sys/class/uwb_rc/uwbN/beacon
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                Write:
+
+                <channel> [<bpst offset>]
+
+                to start beaconing on a specific channel, or stop
+                beaconing if <channel> is -1.  Valid channels depends
+                on the radio controller's supported band groups.
+
+                <bpst offset> may be used to try and join a specific
+                beacon group if more than one was found during a scan.
+
+What:           /sys/class/uwb_rc/uwbN/scan
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                Write:
+
+                <channel> <type> [<bpst offset>]
+
+                to start (or stop) scanning on a channel.  <type> is one of:
+                    0 - scan
+                    1 - scan outside BP
+                    2 - scan while inactive
+                    3 - scanning disabled
+                    4 - scan (with start time of <bpst offset>)
+
+What:           /sys/class/uwb_rc/uwbN/mac_address
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                The EUI-48, in colon-separated hex octets, for this
+                radio controller.  A write will change the radio
+                controller's EUI-48 but only do so while the device is
+                not beaconing or scanning.
+
+What:           /sys/class/uwb_rc/uwbN/wusbhc
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                A symlink to the device (if any) of the WUSB Host
+                Controller PAL using this radio controller.
+
+What:           /sys/class/uwb_rc/uwbN/<EUI-48>/
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                A neighbour UWB device that has either been detected
+                as part of a scan or is a member of the radio
+                controllers beacon group.
+
+What:           /sys/class/uwb_rc/uwbN/<EUI-48>/BPST
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                The time (using the radio controllers internal 1 ms
+                interval superframe timer) of the last beacon from
+                this device was received.
+
+What:           /sys/class/uwb_rc/uwbN/<EUI-48>/DevAddr
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                The current DevAddr of this device in colon separated
+                hex octets.
+
+What:           /sys/class/uwb_rc/uwbN/<EUI-48>/EUI_48
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+
+                The EUI-48 of this device in colon separated hex
+                octets.
+
+What:           /sys/class/uwb_rc/uwbN/<EUI-48>/BPST
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+
+What:           /sys/class/uwb_rc/uwbN/<EUI-48>/IEs
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                The latest IEs included in this device's beacon, in
+                space separated hex octets with one IE per line.
+
+What:           /sys/class/uwb_rc/uwbN/<EUI-48>/LQE
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                Link Quality Estimate - the Signal to Noise Ratio
+                (SNR) of all packets received from this device in dB.
+                This gives an estimate on a suitable PHY rate. Refer
+                to [ECMA-368] section 13.3 for more details.
+
+What:           /sys/class/uwb_rc/uwbN/<EUI-48>/RSSI
+Date:           July 2008
+KernelVersion:  2.6.27
+Contact:        linux-usb@vger.kernel.org
+Description:
+                Received Signal Strength Indication - the strength of
+                the received signal in dB.  LQE is a more useful
+                measure of the radio link quality.
diff --git a/Documentation/ABI/testing/sysfs-wusb_cbaf b/Documentation/ABI/testing/sysfs-wusb_cbaf
new file mode 100644 (file)
index 0000000..a99c5f8
--- /dev/null
@@ -0,0 +1,100 @@
+What:           /sys/bus/usb/drivers/wusb_cbaf/.../wusb_*
+Date:           August 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                Various files for managing Cable Based Association of
+                (wireless) USB devices.
+
+                The sequence of operations should be:
+
+                1. Device is plugged in.
+
+                2. The connection manager (CM) sees a device with CBA capability.
+                   (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE).
+
+                3. The CM writes the host name, supported band groups,
+                   and the CHID (host ID) into the wusb_host_name,
+                   wusb_host_band_groups and wusb_chid files. These
+                   get sent to the device and the CDID (if any) for
+                   this host is requested.
+
+                4. The CM can verify that the device's supported band
+                   groups (wusb_device_band_groups) are compatible
+                   with the host.
+
+                5. The CM reads the wusb_cdid file.
+
+                6. The CM looks it up its database.
+
+                   - If it has a matching CHID,CDID entry, the device
+                     has been authorized before and nothing further
+                     needs to be done.
+
+                   - If the CDID is zero (or the CM doesn't find a
+                     matching CDID in its database), the device is
+                     assumed to be not known.  The CM may associate
+                     the host with device by: writing a randomly
+                     generated CDID to wusb_cdid and then a random CK
+                     to wusb_ck (this uploads the new CC to the
+                     device).
+
+                     CMD may choose to prompt the user before
+                     associating with a new device.
+
+                7. Device is unplugged.
+
+                References:
+                  [WUSB-AM] Association Models Supplement to the
+                            Certified Wireless Universal Serial Bus
+                            Specification, version 1.0.
+
+What:           /sys/bus/usb/drivers/wusb_cbaf/.../wusb_chid
+Date:           August 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                The CHID of the host formatted as 16 space-separated
+                hex octets.
+
+                Writes fetches device's supported band groups and the
+                the CDID for any existing association with this host.
+
+What:           /sys/bus/usb/drivers/wusb_cbaf/.../wusb_host_name
+Date:           August 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                A friendly name for the host as a UTF-8 encoded string.
+
+What:           /sys/bus/usb/drivers/wusb_cbaf/.../wusb_host_band_groups
+Date:           August 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                The band groups supported by the host, in the format
+                defined in [WUSB-AM].
+
+What:           /sys/bus/usb/drivers/wusb_cbaf/.../wusb_device_band_groups
+Date:           August 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                The band groups supported by the device, in the format
+                defined in [WUSB-AM].
+
+What:           /sys/bus/usb/drivers/wusb_cbaf/.../wusb_cdid
+Date:           August 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                The device's CDID formatted as 16 space-separated hex
+                octets.
+
+What:           /sys/bus/usb/drivers/wusb_cbaf/.../wusb_ck
+Date:           August 2008
+KernelVersion:  2.6.27
+Contact:        David Vrabel <david.vrabel@csr.com>
+Description:
+                Write 16 space-separated random, hex octets to
+                associate with the device.
diff --git a/Documentation/usb/WUSB-Design-overview.txt b/Documentation/usb/WUSB-Design-overview.txt
new file mode 100644 (file)
index 0000000..4c3d62c
--- /dev/null
@@ -0,0 +1,448 @@
+
+Linux UWB + Wireless USB + WiNET
+
+   (C) 2005-2006 Intel Corporation
+   Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License version
+   2 as published by the Free Software Foundation.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.
+
+
+Please visit http://bughost.org/thewiki/Design-overview.txt-1.8 for
+updated content.
+
+    * Design-overview.txt-1.8
+
+This code implements a Ultra Wide Band stack for Linux, as well as
+drivers for the the USB based UWB radio controllers defined in the
+Wireless USB 1.0 specification (including Wireless USB host controller
+and an Intel WiNET controller).
+
+   1. Introduction
+         1. HWA: Host Wire adapters, your Wireless USB dongle
+
+         2. DWA: Device Wired Adaptor, a Wireless USB hub for wired
+            devices
+         3. WHCI: Wireless Host Controller Interface, the PCI WUSB host
+            adapter
+   2. The UWB stack
+         1. Devices and hosts: the basic structure
+
+         2. Host Controller life cycle
+
+         3. On the air: beacons and enumerating the radio neighborhood
+
+         4. Device lists
+         5. Bandwidth allocation
+
+   3. Wireless USB Host Controller drivers
+
+   4. Glossary
+
+
+    Introduction
+
+UWB is a wide-band communication protocol that is to serve also as the
+low-level protocol for others (much like TCP sits on IP). Currently
+these others are Wireless USB and TCP/IP, but seems Bluetooth and
+Firewire/1394 are coming along.
+
+UWB uses a band from roughly 3 to 10 GHz, transmitting at a max of
+~-41dB (or 0.074 uW/MHz--geography specific data is still being
+negotiated w/ regulators, so watch for changes). That band is divided in
+a bunch of ~1.5 GHz wide channels (or band groups) composed of three
+subbands/subchannels (528 MHz each). Each channel is independent of each
+other, so you could consider them different "busses". Initially this
+driver considers them all a single one.
+
+Radio time is divided in 65536 us long /superframes/, each one divided
+in 256 256us long /MASs/ (Media Allocation Slots), which are the basic
+time/media allocation units for transferring data. At the beginning of
+each superframe there is a Beacon Period (BP), where every device
+transmit its beacon on a single MAS. The length of the BP depends on how
+many devices are present and the length of their beacons.
+
+Devices have a MAC (fixed, 48 bit address) and a device (changeable, 16
+bit address) and send periodic beacons to advertise themselves and pass
+info on what they are and do. They advertise their capabilities and a
+bunch of other stuff.
+
+The different logical parts of this driver are:
+
+    *
+
+      *UWB*: the Ultra-Wide-Band stack -- manages the radio and
+      associated spectrum to allow for devices sharing it. Allows to
+      control bandwidth assingment, beaconing, scanning, etc
+
+    *
+
+      *WUSB*: the layer that sits on top of UWB to provide Wireless USB.
+      The Wireless USB spec defines means to control a UWB radio and to
+      do the actual WUSB.
+
+
+      HWA: Host Wire adapters, your Wireless USB dongle
+
+WUSB also defines a device called a Host Wire Adaptor (HWA), which in
+mere terms is a USB dongle that enables your PC to have UWB and Wireless
+USB. The Wireless USB Host Controller in a HWA looks to the host like a
+[Wireless] USB controller connected via USB (!)
+
+The HWA itself is broken in two or three main interfaces:
+
+    *
+
+      *RC*: Radio control -- this implements an interface to the
+      Ultra-Wide-Band radio controller. The driver for this implements a
+      USB-based UWB Radio Controller to the UWB stack.
+
+    *
+
+      *HC*: the wireless USB host controller. It looks like a USB host
+      whose root port is the radio and the WUSB devices connect to it.
+      To the system it looks like a separate USB host. The driver (will)
+      implement a USB host controller (similar to UHCI, OHCI or EHCI)
+      for which the root hub is the radio...To reiterate: it is a USB
+      controller that is connected via USB instead of PCI.
+
+    *
+
+      *WINET*: some HW provide a WiNET interface (IP over UWB). This
+      package provides a driver for it (it looks like a network
+      interface, winetX). The driver detects when there is a link up for
+      their type and kick into gear.
+
+
+      DWA: Device Wired Adaptor, a Wireless USB hub for wired devices
+
+These are the complement to HWAs. They are a USB host for connecting
+wired devices, but it is connected to your PC connected via Wireless
+USB. To the system it looks like yet another USB host. To the untrained
+eye, it looks like a hub that connects upstream wirelessly.
+
+We still offer no support for this; however, it should share a lot of
+code with the HWA-RC driver; there is a bunch of factorization work that
+has been done to support that in upcoming releases.
+
+
+      WHCI: Wireless Host Controller Interface, the PCI WUSB host adapter
+
+This is your usual PCI device that implements WHCI. Similar in concept
+to EHCI, it allows your wireless USB devices (including DWAs) to connect
+to your host via a PCI interface. As in the case of the HWA, it has a
+Radio Control interface and the WUSB Host Controller interface per se.
+
+There is still no driver support for this, but will be in upcoming
+releases.
+
+
+    The UWB stack
+
+The main mission of the UWB stack is to keep a tally of which devices
+are in radio proximity to allow drivers to connect to them. As well, it
+provides an API for controlling the local radio controllers (RCs from
+now on), such as to start/stop beaconing, scan, allocate bandwidth, etc.
+
+
+      Devices and hosts: the basic structure
+
+The main building block here is the UWB device (struct uwb_dev). For
+each device that pops up in radio presence (ie: the UWB host receives a
+beacon from it) you get a struct uwb_dev that will show up in
+/sys/class/uwb and in /sys/bus/uwb/devices.
+
+For each RC that is detected, a new struct uwb_rc is created. In turn, a
+RC is also a device, so they also show in /sys/class/uwb and
+/sys/bus/uwb/devices, but at the same time, only radio controllers show
+up in /sys/class/uwb_rc.
+
+    *
+
+      [*] The reason for RCs being also devices is that not only we can
+      see them while enumerating the system device tree, but also on the
+      radio (their beacons and stuff), so the handling has to be
+      likewise to that of a device.
+
+Each RC driver is implemented by a separate driver that plugs into the
+interface that the UWB stack provides through a struct uwb_rc_ops. The
+spec creators have been nice enough to make the message format the same
+for HWA and WHCI RCs, so the driver is really a very thin transport that
+moves the requests from the UWB API to the device [/uwb_rc_ops->cmd()/]
+and sends the replies and notifications back to the API
+[/uwb_rc_neh_grok()/]. Notifications are handled to the UWB daemon, that
+is chartered, among other things, to keep the tab of how the UWB radio
+neighborhood looks, creating and destroying devices as they show up or
+dissapear.
+
+Command execution is very simple: a command block is sent and a event
+block or reply is expected back. For sending/receiving command/events, a
+handle called /neh/ (Notification/Event Handle) is opened with
+/uwb_rc_neh_open()/.
+
+The HWA-RC (USB dongle) driver (drivers/uwb/hwa-rc.c) does this job for
+the USB connected HWA. Eventually, drivers/whci-rc.c will do the same
+for the PCI connected WHCI controller.
+
+
+      Host Controller life cycle
+
+So let's say we connect a dongle to the system: it is detected and
+firmware uploaded if needed [for Intel's i1480
+/drivers/uwb/ptc/usb.c:ptc_usb_probe()/] and then it is reenumerated.
+Now we have a real HWA device connected and
+/drivers/uwb/hwa-rc.c:hwarc_probe()/ picks it up, that will set up the
+Wire-Adaptor environment and then suck it into the UWB stack's vision of
+the world [/drivers/uwb/lc-rc.c:uwb_rc_add()/].
+
+    *
+
+      [*] The stack should put a new RC to scan for devices
+      [/uwb_rc_scan()/] so it finds what's available around and tries to
+      connect to them, but this is policy stuff and should be driven
+      from user space. As of now, the operator is expected to do it
+      manually; see the release notes for documentation on the procedure.
+
+When a dongle is disconnected, /drivers/uwb/hwa-rc.c:hwarc_disconnect()/
+takes time of tearing everything down safely (or not...).
+
+
+      On the air: beacons and enumerating the radio neighborhood
+
+So assuming we have devices and we have agreed for a channel to connect
+on (let's say 9), we put the new RC to beacon:
+
+    *
+
+            $ echo 9 0 > /sys/class/uwb_rc/uwb0/beacon
+
+Now it is visible. If there were other devices in the same radio channel
+and beacon group (that's what the zero is for), the dongle's radio
+control interface will send beacon notifications on its
+notification/event endpoint (NEEP). The beacon notifications are part of
+the event stream that is funneled into the API with
+/drivers/uwb/neh.c:uwb_rc_neh_grok()/ and delivered to the UWBD, the UWB
+daemon through a notification list.
+
+UWBD wakes up and scans the event list; finds a beacon and adds it to
+the BEACON CACHE (/uwb_beca/). If he receives a number of beacons from
+the same device, he considers it to be 'onair' and creates a new device
+[/drivers/uwb/lc-dev.c:uwbd_dev_onair()/]. Similarly, when no beacons
+are received in some time, the device is considered gone and wiped out
+[uwbd calls periodically /uwb/beacon.c:uwb_beca_purge()/ that will purge
+the beacon cache of dead devices].
+
+
+      Device lists
+
+All UWB devices are kept in the list of the struct bus_type uwb_bus.
+
+
+      Bandwidth allocation
+
+The UWB stack maintains a local copy of DRP availability through
+processing of incoming *DRP Availability Change* notifications. This
+local copy is currently used to present the current bandwidth
+availability to the user through the sysfs file
+/sys/class/uwb_rc/uwbx/bw_avail. In the future the bandwidth
+availability information will be used by the bandwidth reservation
+routines.
+
+The bandwidth reservation routines are in progress and are thus not
+present in the current release. When completed they will enable a user
+to initiate DRP reservation requests through interaction with sysfs. DRP
+reservation requests from remote UWB devices will also be handled. The
+bandwidth management done by the UWB stack will include callbacks to the
+higher layers will enable the higher layers to use the reservations upon
+completion. [Note: The bandwidth reservation work is in progress and
+subject to change.]
+
+
+    Wireless USB Host Controller drivers
+
+*WARNING* This section needs a lot of work!
+
+As explained above, there are three different types of HCs in the WUSB
+world: HWA-HC, DWA-HC and WHCI-HC.
+
+HWA-HC and DWA-HC share that they are Wire-Adapters (USB or WUSB
+connected controllers), and their transfer management system is almost
+identical. So is their notification delivery system.
+
+HWA-HC and WHCI-HC share that they are both WUSB host controllers, so
+they have to deal with WUSB device life cycle and maintenance, wireless
+root-hub
+
+HWA exposes a Host Controller interface (HWA-HC 0xe0/02/02). This has
+three endpoints (Notifications, Data Transfer In and Data Transfer
+Out--known as NEP, DTI and DTO in the code).
+
+We reserve UWB bandwidth for our Wireless USB Cluster, create a Cluster
+ID and tell the HC to use all that. Then we start it. This means the HC
+starts sending MMCs.
+
+    *
+
+      The MMCs are blocks of data defined somewhere in the WUSB1.0 spec
+      that define a stream in the UWB channel time allocated for sending
+      WUSB IEs (host to device commands/notifications) and Device
+      Notifications (device initiated to host). Each host defines a
+      unique Wireless USB cluster through MMCs. Devices can connect to a
+      single cluster at the time. The IEs are Information Elements, and
+      among them are the bandwidth allocations that tell each device
+      when can they transmit or receive.
+
+Now it all depends on external stimuli.
+
+*New device connection*
+
+A new device pops up, it scans the radio looking for MMCs that give out
+the existence of Wireless USB channels. Once one (or more) are found,
+selects which one to connect to. Sends a /DN_Connect/ (device
+notification connect) during the DNTS (Device Notification Time
+Slot--announced in the MMCs
+
+HC picks the /DN_Connect/ out (nep module sends to notif.c for delivery
+into /devconnect/). This process starts the authentication process for
+the device. First we allocate a /fake port/ and assign an
+unauthenticated address (128 to 255--what we really do is
+0x80 | fake_port_idx). We fiddle with the fake port status and /khubd/
+sees a new connection, so he moves on to enable the fake port with a reset.
+
+So now we are in the reset path -- we know we have a non-yet enumerated
+device with an unauthorized address; we ask user space to authenticate
+(FIXME: not yet done, similar to bluetooth pairing), then we do the key
+exchange (FIXME: not yet done) and issue a /set address 0/ to bring the
+device to the default state. Device is authenticated.
+
+From here, the USB stack takes control through the usb_hcd ops. khubd
+has seen the port status changes, as we have been toggling them. It will
+start enumerating and doing transfers through usb_hcd->urb_enqueue() to
+read descriptors and move our data.
+
+*Device life cycle and keep alives*
+
+Everytime there is a succesful transfer to/from a device, we update a
+per-device activity timestamp. If not, every now and then we check and
+if the activity timestamp gets old, we ping the device by sending it a
+Keep Alive IE; it responds with a /DN_Alive/ pong during the DNTS (this
+arrives to us as a notification through
+devconnect.c:wusb_handle_dn_alive(). If a device times out, we
+disconnect it from the system (cleaning up internal information and
+toggling the bits in the fake hub port, which kicks khubd into removing
+the rest of the stuff).
+
+This is done through devconnect:__wusb_check_devs(), which will scan the
+device list looking for whom needs refreshing.
+
+If the device wants to disconnect, it will either die (ugly) or send a
+/DN_Disconnect/ that will prompt a disconnection from the system.
+
+*Sending and receiving data*
+
+Data is sent and received through /Remote Pipes/ (rpipes). An rpipe is
+/aimed/ at an endpoint in a WUSB device. This is the same for HWAs and
+DWAs.
+
+Each HC has a number of rpipes and buffers that can be assigned to them;
+when doing a data transfer (xfer), first the rpipe has to be aimed and
+prepared (buffers assigned), then we can start queueing requests for
+data in or out.
+
+Data buffers have to be segmented out before sending--so we send first a
+header (segment request) and then if there is any data, a data buffer
+immediately after to the DTI interface (yep, even the request). If our
+buffer is bigger than the max segment size, then we just do multiple
+requests.
+
+[This sucks, because doing USB scatter gatter in Linux is resource
+intensive, if any...not that the current approach is not. It just has to
+be cleaned up a lot :)].
+
+If reading, we don't send data buffers, just the segment headers saying
+we want to read segments.
+
+When the xfer is executed, we receive a notification that says data is
+ready in the DTI endpoint (handled through
+xfer.c:wa_handle_notif_xfer()). In there we read from the DTI endpoint a
+descriptor that gives us the status of the transfer, its identification
+(given when we issued it) and the segment number. If it was a data read,
+we issue another URB to read into the destination buffer the chunk of
+data coming out of the remote endpoint. Done, wait for the next guy. The
+callbacks for the URBs issued from here are the ones that will declare
+the xfer complete at some point and call it's callback.
+
+Seems simple, but the implementation is not trivial.
+
+    *
+
+      *WARNING* Old!!
+
+The main xfer descriptor, wa_xfer (equivalent to a URB) contains an
+array of segments, tallys on segments and buffers and callback
+information. Buried in there is a lot of URBs for executing the segments
+and buffer transfers.
+
+For OUT xfers, there is an array of segments, one URB for each, another
+one of buffer URB. When submitting, we submit URBs for segment request
+1, buffer 1, segment 2, buffer 2...etc. Then we wait on the DTI for xfer
+result data; when all the segments are complete, we call the callback to
+finalize the transfer.
+
+For IN xfers, we only issue URBs for the segments we want to read and
+then wait for the xfer result data.
+
+*URB mapping into xfers*
+
+This is done by hwahc_op_urb_[en|de]queue(). In enqueue() we aim an
+rpipe to the endpoint where we have to transmit, create a transfer
+context (wa_xfer) and submit it. When the xfer is done, our callback is
+called and we assign the status bits and release the xfer resources.
+
+In dequeue() we are basically cancelling/aborting the transfer. We issue
+a xfer abort request to the HC, cancell all the URBs we had submitted
+and not yet done and when all that is done, the xfer callback will be
+called--this will call the URB callback.
+
+
+    Glossary
+
+*DWA* -- Device Wire Adapter
+
+USB host, wired for downstream devices, upstream connects wirelessly
+with Wireless USB.
+
+*EVENT* -- Response to a command on the NEEP
+
+*HWA* -- Host Wire Adapter / USB dongle for UWB and Wireless USB
+
+*NEH* -- Notification/Event Handle
+
+Handle/file descriptor for receiving notifications or events. The WA
+code requires you to get one of this to listen for notifications or
+events on the NEEP.
+
+*NEEP* -- Notification/Event EndPoint
+
+Stuff related to the management of the first endpoint of a HWA USB
+dongle that is used to deliver an stream of events and notifications to
+the host.
+
+*NOTIFICATION* -- Message coming in the NEEP as response to something.
+
+*RC* -- Radio Control
+
+Design-overview.txt-1.8 (last edited 2006-11-04 12:22:24 by
+InakyPerezGonzalez)
+
diff --git a/Documentation/usb/wusb-cbaf b/Documentation/usb/wusb-cbaf
new file mode 100644 (file)
index 0000000..2e78b70
--- /dev/null
@@ -0,0 +1,139 @@
+#! /bin/bash
+#
+
+set -e
+
+progname=$(basename $0)
+function help
+{
+    cat <<EOF
+Usage: $progname COMMAND DEVICEs [ARGS]
+
+Command for manipulating the pairing/authentication credentials of a
+Wireless USB device that supports wired-mode Cable-Based-Association.
+
+Works in conjunction with the wusb-cba.ko driver from http://linuxuwb.org.
+
+
+DEVICE
+
+ sysfs path to the device to authenticate; for example, both this
+ guys are the same:
+
+ /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-4/1-4.4/1-4.4:1.1
+ /sys/bus/usb/drivers/wusb-cbaf/1-4.4:1.1
+
+COMMAND/ARGS are
+
+ start
+
+   Start a WUSB host controller (by setting up a CHID)
+
+ set-chid DEVICE HOST-CHID HOST-BANDGROUP HOST-NAME
+
+   Sets host information in the device; after this you can call the
+   get-cdid to see how does this device report itself to us.
+
+ get-cdid DEVICE
+
+   Get the device ID associated to the HOST-CHDI we sent with
+   'set-chid'. We might not know about it.
+
+ set-cc DEVICE
+
+   If we allow the device to connect, set a random new CDID and CK
+   (connection key). Device saves them for the next time it wants to
+   connect wireless. We save them for that next time also so we can
+   authenticate the device (when we see the CDID he uses to id
+   itself) and the CK to crypto talk to it.
+
+CHID is always 16 hex bytes in 'XX YY ZZ...' form
+BANDGROUP is almost always 0001
+
+Examples:
+
+  You can default most arguments to '' to get a sane value:
+
+  $ $progname set-chid '' '' '' "My host name"
+
+  A full sequence:
+
+  $ $progname set-chid '' '' '' "My host name"
+  $ $progname get-cdid ''
+  $ $progname set-cc ''
+
+EOF
+}
+
+
+# Defaults
+# FIXME: CHID should come from a database :), band group from the host
+host_CHID="00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"
+host_band_group="0001"
+host_name=$(hostname)
+
+devs="$(echo /sys/bus/usb/drivers/wusb-cbaf/[0-9]*)"
+hdevs="$(for h in /sys/class/uwb_rc/*/wusbhc; do readlink -f $h; done)"
+
+result=0
+case $1 in
+    start)
+        for dev in ${2:-$hdevs}
+          do
+          uwb_rc=$(readlink -f $dev/uwb_rc)
+          if cat $uwb_rc/beacon | grep -q -- "-1"
+              then
+              echo 13 0 > $uwb_rc/beacon
+              echo I: started beaconing on ch 13 on $(basename $uwb_rc) >&2
+          fi
+          echo $host_CHID > $dev/wusb_chid
+          echo I: started host $(basename $dev) >&2
+        done
+        ;;
+    stop)
+        for dev in ${2:-$hdevs}
+          do
+          echo 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 > $dev/wusb_chid
+          echo I: stopped host $(basename $dev) >&2
+          uwb_rc=$(readlink -f $dev/uwb_rc)
+          echo -1 | cat > $uwb_rc/beacon
+          echo I: stopped beaconing on $(basename $uwb_rc) >&2
+        done
+        ;;
+    set-chid)
+        shift
+        for dev in ${2:-$devs}; do
+            echo "${4:-$host_name}" > $dev/wusb_host_name
+            echo "${3:-$host_band_group}" > $dev/wusb_host_band_groups
+            echo ${2:-$host_CHID} > $dev/wusb_chid
+        done
+        ;;
+    get-cdid)
+        for dev in ${2:-$devs}
+          do
+          cat $dev/wusb_cdid
+        done
+        ;;
+    set-cc)
+        for dev in ${2:-$devs}; do
+            shift
+            CDID="$(head --bytes=16 /dev/urandom  | od -tx1 -An)"
+            CK="$(head --bytes=16 /dev/urandom  | od -tx1 -An)"
+            echo "$CDID" > $dev/wusb_cdid
+            echo "$CK" > $dev/wusb_ck
+
+            echo I: CC set >&2
+            echo "CHID: $(cat $dev/wusb_chid)"
+            echo "CDID:$CDID"
+            echo "CK:  $CK"
+        done
+        ;;
+    help|h|--help|-h)
+        help
+        ;;
+    *)
+        echo "E: Unknown usage" 1>&2
+        help 1>&2
+        result=1
+esac
+exit $result
index 1d11b56..a2afc49 100644 (file)
@@ -1053,6 +1053,12 @@ L:       cbe-oss-dev@ozlabs.org
 W:     http://www.ibm.com/developerworks/power/cell/
 S:     Supported
 
+CERTIFIED WIRELESS USB (WUSB) SUBSYSTEM:
+P:     David Vrabel
+M:     david.vrabel@csr.com
+L:     linux-usb@vger.kernel.org
+S:     Supported
+
 CFAG12864B LCD DRIVER
 P:     Miguel Ojeda Sandonis
 M:     miguel.ojeda.sandonis@gmail.com
@@ -4191,6 +4197,12 @@ L:       sparclinux@vger.kernel.org
 T:     git kernel.org:/pub/scm/linux/kernel/git/davem/sparc-2.6.git
 S:     Maintained
 
+ULTRA-WIDEBAND (UWB) SUBSYSTEM:
+P:     David Vrabel
+M:     david.vrabel@csr.com
+L:     linux-usb@vger.kernel.org
+S:     Supported
+
 UNIFORM CDROM DRIVER
 P:     Jens Axboe
 M:     axboe@kernel.dk
@@ -4616,6 +4628,11 @@ M:       zaga@fly.cc.fer.hr
 L:     linux-scsi@vger.kernel.org
 S:     Maintained
 
+WIMEDIA LLC PROTOCOL (WLP) SUBSYSTEM
+P:     David Vrabel
+M:     david.vrabel@csr.com
+S:     Maintained
+
 WISTRON LAPTOP BUTTON DRIVER
 P:     Miloslav Trmac
 M:     mitr@volny.cz
index bc0fcde..f504c80 100644 (file)
@@ -1256,6 +1256,8 @@ source "drivers/hid/Kconfig"
 
 source "drivers/usb/Kconfig"
 
+source "drivers/uwb/Kconfig"
+
 source "drivers/mmc/Kconfig"
 
 source "drivers/memstick/Kconfig"
index 07335e7..b17aeea 100644 (file)
@@ -679,6 +679,8 @@ source "fs/Kconfig"
 
 source "drivers/usb/Kconfig"
 
+source "drivers/uwb/Kconfig"
+
 source "arch/cris/Kconfig.debug"
 
 source "security/Kconfig"
index bd19954..28f06fd 100644 (file)
@@ -216,6 +216,8 @@ source "drivers/hwmon/Kconfig"
 
 source "drivers/usb/Kconfig"
 
+source "drivers/uwb/Kconfig"
+
 endmenu
 
 source "fs/Kconfig"
index d19b6f5..d38f43f 100644 (file)
@@ -78,6 +78,8 @@ source "drivers/hid/Kconfig"
 
 source "drivers/usb/Kconfig"
 
+source "drivers/uwb/Kconfig"
+
 source "drivers/mmc/Kconfig"
 
 source "drivers/memstick/Kconfig"
index 46c8681..cadc64f 100644 (file)
@@ -100,3 +100,4 @@ obj-$(CONFIG_SSB)           += ssb/
 obj-$(CONFIG_VIRTIO)           += virtio/
 obj-$(CONFIG_REGULATOR)                += regulator/
 obj-$(CONFIG_STAGING)          += staging/
+obj-$(CONFIG_UWB)              += uwb/
index bcefbdd..c23a985 100644 (file)
@@ -97,6 +97,8 @@ source "drivers/usb/core/Kconfig"
 
 source "drivers/usb/mon/Kconfig"
 
+source "drivers/usb/wusbcore/Kconfig"
+
 source "drivers/usb/host/Kconfig"
 
 source "drivers/usb/musb/Kconfig"
index a419c42..8b7c419 100644 (file)
@@ -16,9 +16,12 @@ obj-$(CONFIG_USB_UHCI_HCD)   += host/
 obj-$(CONFIG_USB_SL811_HCD)    += host/
 obj-$(CONFIG_USB_U132_HCD)     += host/
 obj-$(CONFIG_USB_R8A66597_HCD) += host/
+obj-$(CONFIG_USB_HWA_HCD)      += host/
 
 obj-$(CONFIG_USB_C67X00_HCD)   += c67x00/
 
+obj-$(CONFIG_USB_WUSB)         += wusbcore/
+
 obj-$(CONFIG_USB_ACM)          += class/
 obj-$(CONFIG_USB_PRINTER)      += class/
 
index 228797e..72fb655 100644 (file)
@@ -305,3 +305,31 @@ config SUPERH_ON_CHIP_R8A66597
        help
           This driver enables support for the on-chip R8A66597 in the
           SH7366 and SH7723 processors.
+
+config USB_WHCI_HCD
+       tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)"
+       depends on EXPERIMENTAL
+       depends on PCI && USB
+       select USB_WUSB
+       select UWB_WHCI
+       help
+         A driver for PCI-based Wireless USB Host Controllers that are
+         compliant with the WHCI specification.
+
+         To compile this driver a module, choose M here: the module
+         will be called "whci-hcd".
+
+config USB_HWA_HCD
+       tristate "Host Wire Adapter (HWA) driver (EXPERIMENTAL)"
+       depends on EXPERIMENTAL
+       depends on USB
+       select USB_WUSB
+       select UWB_HWA
+       help
+         This driver enables you to connect Wireless USB devices to
+         your system using a Host Wire Adaptor USB dongle. This is an
+         UWB Radio Controller and WUSB Host Controller connected to
+         your machine via USB (specified in WUSB1.0).
+
+         To compile this driver a module, choose M here: the module
+         will be called "hwa-hc".
index f1edda2..23be222 100644 (file)
@@ -8,6 +8,8 @@ endif
 
 isp1760-objs := isp1760-hcd.o isp1760-if.o
 
+obj-$(CONFIG_USB_WHCI_HCD)     += whci/
+
 obj-$(CONFIG_PCI)              += pci-quirks.o
 
 obj-$(CONFIG_USB_EHCI_HCD)     += ehci-hcd.o
@@ -19,3 +21,4 @@ obj-$(CONFIG_USB_SL811_CS)    += sl811_cs.o
 obj-$(CONFIG_USB_U132_HCD)     += u132-hcd.o
 obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o
 obj-$(CONFIG_USB_ISP1760_HCD)  += isp1760.o
+obj-$(CONFIG_USB_HWA_HCD)      += hwa-hc.o
diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c
new file mode 100644 (file)
index 0000000..64be4d8
--- /dev/null
@@ -0,0 +1,925 @@
+/*
+ * Host Wire Adapter:
+ * Driver glue, HWA-specific functions, bridges to WAHC and WUSBHC
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * The HWA driver is a simple layer that forwards requests to the WAHC
+ * (Wire Adater Host Controller) or WUSBHC (Wireless USB Host
+ * Controller) layers.
+ *
+ * Host Wire Adapter is the 'WUSB 1.0 standard' name for Wireless-USB
+ * Host Controller that is connected to your system via USB (a USB
+ * dongle that implements a USB host...). There is also a Device Wired
+ * Adaptor, DWA (Wireless USB hub) that uses the same mechanism for
+ * transferring data (it is after all a USB host connected via
+ * Wireless USB), we have a common layer called Wire Adapter Host
+ * Controller that does all the hard work. The WUSBHC (Wireless USB
+ * Host Controller) is the part common to WUSB Host Controllers, the
+ * HWA and the PCI-based one, that is implemented following the WHCI
+ * spec. All these layers are implemented in ../wusbcore.
+ *
+ * The main functions are hwahc_op_urb_{en,de}queue(), that pass the
+ * job of converting a URB to a Wire Adapter
+ *
+ * Entry points:
+ *
+ *   hwahc_driver_*()   Driver initialization, registration and
+ *                      teardown.
+ *
+ *   hwahc_probe()     New device came up, create an instance for
+ *                      it [from device enumeration].
+ *
+ *   hwahc_disconnect()        Remove device instance [from device
+ *                      enumeration].
+ *
+ *   [__]hwahc_op_*()   Host-Wire-Adaptor specific functions for
+ *                      starting/stopping/etc (some might be made also
+ *                      DWA).
+ */
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/completion.h>
+#include "../wusbcore/wa-hc.h"
+#include "../wusbcore/wusbhc.h"
+
+#define D_LOCAL 0
+#include <linux/uwb/debug.h>
+
+struct hwahc {
+       struct wusbhc wusbhc;   /* has to be 1st */
+       struct wahc wa;
+       u8 buffer[16];          /* for misc usb transactions */
+};
+
+/**
+ * FIXME should be wusbhc
+ *
+ * NOTE: we need to cache the Cluster ID because later...there is no
+ *       way to get it :)
+ */
+static int __hwahc_set_cluster_id(struct hwahc *hwahc, u8 cluster_id)
+{
+       int result;
+       struct wusbhc *wusbhc = &hwahc->wusbhc;
+       struct wahc *wa = &hwahc->wa;
+       struct device *dev = &wa->usb_iface->dev;
+
+       result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       WUSB_REQ_SET_CLUSTER_ID,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       cluster_id,
+                       wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+                       NULL, 0, 1000 /* FIXME: arbitrary */);
+       if (result < 0)
+               dev_err(dev, "Cannot set WUSB Cluster ID to 0x%02x: %d\n",
+                       cluster_id, result);
+       else
+               wusbhc->cluster_id = cluster_id;
+       dev_info(dev, "Wireless USB Cluster ID set to 0x%02x\n", cluster_id);
+       return result;
+}
+
+static int __hwahc_op_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
+{
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct wahc *wa = &hwahc->wa;
+
+       return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       WUSB_REQ_SET_NUM_DNTS,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       interval << 8 | slots,
+                       wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+                       NULL, 0, 1000 /* FIXME: arbitrary */);
+}
+
+/*
+ * Reset a WUSB host controller and wait for it to complete doing it.
+ *
+ * @usb_hcd:   Pointer to WUSB Host Controller instance.
+ *
+ */
+static int hwahc_op_reset(struct usb_hcd *usb_hcd)
+{
+       int result;
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct device *dev = &hwahc->wa.usb_iface->dev;
+
+       d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+       mutex_lock(&wusbhc->mutex);
+       wa_nep_disarm(&hwahc->wa);
+       result = __wa_set_feature(&hwahc->wa, WA_RESET);
+       if (result < 0) {
+               dev_err(dev, "error commanding HC to reset: %d\n", result);
+               goto error_unlock;
+       }
+       d_printf(3, dev, "reset: waiting for device to change state\n");
+       result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0);
+       if (result < 0) {
+               dev_err(dev, "error waiting for HC to reset: %d\n", result);
+               goto error_unlock;
+       }
+error_unlock:
+       mutex_unlock(&wusbhc->mutex);
+       d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+       return result;
+}
+
+/*
+ * FIXME: break this function up
+ */
+static int hwahc_op_start(struct usb_hcd *usb_hcd)
+{
+       u8 addr;
+       int result;
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct device *dev = &hwahc->wa.usb_iface->dev;
+
+       /* Set up a Host Info WUSB Information Element */
+       d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+       result = -ENOSPC;
+       mutex_lock(&wusbhc->mutex);
+       /* Start the numbering from the top so that the bottom
+        * range of the unauth addr space is used for devices,
+        * the top for HCs; use 0xfe - RC# */
+       addr = wusb_cluster_id_get();
+       if (addr == 0)
+               goto error_cluster_id_get;
+       result = __hwahc_set_cluster_id(hwahc, addr);
+       if (result < 0)
+               goto error_set_cluster_id;
+
+       result = wa_nep_arm(&hwahc->wa, GFP_KERNEL);
+       if (result < 0) {
+               dev_err(dev, "cannot listen to notifications: %d\n", result);
+               goto error_stop;
+       }
+       usb_hcd->uses_new_polling = 1;
+       usb_hcd->poll_rh = 1;
+       usb_hcd->state = HC_STATE_RUNNING;
+       result = 0;
+out:
+       mutex_unlock(&wusbhc->mutex);
+       d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+       return result;
+
+error_stop:
+       __wa_stop(&hwahc->wa);
+error_set_cluster_id:
+       wusb_cluster_id_put(wusbhc->cluster_id);
+error_cluster_id_get:
+       goto out;
+
+}
+
+/*
+ * FIXME: break this function up
+ */
+static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc)
+{
+       int result;
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct device *dev = &hwahc->wa.usb_iface->dev;
+
+       /* Set up a Host Info WUSB Information Element */
+       d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+       result = -ENOSPC;
+
+       result = __wa_set_feature(&hwahc->wa, WA_ENABLE);
+       if (result < 0) {
+               dev_err(dev, "error commanding HC to start: %d\n", result);
+               goto error_stop;
+       }
+       result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE);
+       if (result < 0) {
+               dev_err(dev, "error waiting for HC to start: %d\n", result);
+               goto error_stop;
+       }
+       result = 0;
+out:
+       d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+       return result;
+
+error_stop:
+       result = __wa_clear_feature(&hwahc->wa, WA_ENABLE);
+       goto out;
+}
+
+static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       dev_err(wusbhc->dev, "%s (%p [%p], 0x%lx) UNIMPLEMENTED\n", __func__,
+               usb_hcd, hwahc, *(unsigned long *) &msg);
+       return -ENOSYS;
+}
+
+static int hwahc_op_resume(struct usb_hcd *usb_hcd)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+       dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__,
+               usb_hcd, hwahc);
+       return -ENOSYS;
+}
+
+static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc)
+{
+       int result;
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct device *dev = &hwahc->wa.usb_iface->dev;
+
+       d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+       /* Nothing for now */
+       d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+       return;
+}
+
+/*
+ * No need to abort pipes, as when this is called, all the children
+ * has been disconnected and that has done it [through
+ * usb_disable_interface() -> usb_disable_endpoint() ->
+ * hwahc_op_ep_disable() - >rpipe_ep_disable()].
+ */
+static void hwahc_op_stop(struct usb_hcd *usb_hcd)
+{
+       int result;
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct wahc *wa = &hwahc->wa;
+       struct device *dev = &wa->usb_iface->dev;
+
+       d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+       mutex_lock(&wusbhc->mutex);
+       wusbhc_stop(wusbhc);
+       wa_nep_disarm(&hwahc->wa);
+       result = __wa_stop(&hwahc->wa);
+       wusb_cluster_id_put(wusbhc->cluster_id);
+       mutex_unlock(&wusbhc->mutex);
+       d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+       return;
+}
+
+static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+       dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__,
+               usb_hcd, hwahc);
+       return -ENOSYS;
+}
+
+static int hwahc_op_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
+                               gfp_t gfp)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+       return wa_urb_enqueue(&hwahc->wa, urb->ep, urb, gfp);
+}
+
+static int hwahc_op_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb,
+                               int status)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+       return wa_urb_dequeue(&hwahc->wa, urb);
+}
+
+/*
+ * Release resources allocated for an endpoint
+ *
+ * If there is an associated rpipe to this endpoint, go ahead and put it.
+ */
+static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd,
+                                     struct usb_host_endpoint *ep)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+       rpipe_ep_disable(&hwahc->wa, ep);
+}
+
+/*
+ * Set the UWB MAS allocation for the WUSB cluster
+ *
+ * @stream_index: stream to use (-1 for cancelling the allocation)
+ * @mas: mas bitmap to use
+ */
+static int __hwahc_op_bwa_set(struct wusbhc *wusbhc, s8 stream_index,
+                             const struct uwb_mas_bm *mas)
+{
+       int result;
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct wahc *wa = &hwahc->wa;
+       struct device *dev = &wa->usb_iface->dev;
+       u8 mas_le[UWB_NUM_MAS/8];
+
+       /* Set the stream index */
+       result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       WUSB_REQ_SET_STREAM_IDX,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       stream_index,
+                       wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+                       NULL, 0, 1000 /* FIXME: arbitrary */);
+       if (result < 0) {
+               dev_err(dev, "Cannot set WUSB stream index: %d\n", result);
+               goto out;
+       }
+       uwb_mas_bm_copy_le(mas_le, mas);
+       /* Set the MAS allocation */
+       result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       WUSB_REQ_SET_WUSB_MAS,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+                       mas_le, 32, 1000 /* FIXME: arbitrary */);
+       if (result < 0)
+               dev_err(dev, "Cannot set WUSB MAS allocation: %d\n", result);
+out:
+       return result;
+}
+
+/*
+ * Add an IE to the host's MMC
+ *
+ * @interval:    See WUSB1.0[8.5.3.1]
+ * @repeat_cnt:  See WUSB1.0[8.5.3.1]
+ * @handle:      See WUSB1.0[8.5.3.1]
+ * @wuie:        Pointer to the header of the WUSB IE data to add.
+ *               MUST BE allocated in a kmalloc buffer (no stack or
+ *               vmalloc).
+ *
+ * NOTE: the format of the WUSB IEs for MMCs are different to the
+ *       normal MBOA MAC IEs (IE Id + Length in MBOA MAC vs. Length +
+ *       Id in WUSB IEs). Standards...you gotta love'em.
+ */
+static int __hwahc_op_mmcie_add(struct wusbhc *wusbhc, u8 interval,
+                               u8 repeat_cnt, u8 handle,
+                               struct wuie_hdr *wuie)
+{
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct wahc *wa = &hwahc->wa;
+       u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+
+       return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       WUSB_REQ_ADD_MMC_IE,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       interval << 8 | repeat_cnt,
+                       handle << 8 | iface_no,
+                       wuie, wuie->bLength, 1000 /* FIXME: arbitrary */);
+}
+
+/*
+ * Remove an IE to the host's MMC
+ *
+ * @handle:      See WUSB1.0[8.5.3.1]
+ */
+static int __hwahc_op_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
+{
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct wahc *wa = &hwahc->wa;
+       u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+       return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       WUSB_REQ_REMOVE_MMC_IE,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       0, handle << 8 | iface_no,
+                       NULL, 0, 1000 /* FIXME: arbitrary */);
+}
+
+/*
+ * Update device information for a given fake port
+ *
+ * @port_idx: Fake port to which device is connected (wusbhc index, not
+ *            USB port number).
+ */
+static int __hwahc_op_dev_info_set(struct wusbhc *wusbhc,
+                                  struct wusb_dev *wusb_dev)
+{
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct wahc *wa = &hwahc->wa;
+       u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+       struct hwa_dev_info *dev_info;
+       int ret;
+
+       /* fill out the Device Info buffer and send it */
+       dev_info = kzalloc(sizeof(struct hwa_dev_info), GFP_KERNEL);
+       if (!dev_info)
+               return -ENOMEM;
+       uwb_mas_bm_copy_le(dev_info->bmDeviceAvailability,
+                          &wusb_dev->availability);
+       dev_info->bDeviceAddress = wusb_dev->addr;
+
+       /*
+        * If the descriptors haven't been read yet, use a default PHY
+        * rate of 53.3 Mbit/s only.  The correct value will be used
+        * when this will be called again as part of the
+        * authentication process (which occurs after the descriptors
+        * have been read).
+        */
+       if (wusb_dev->wusb_cap_descr)
+               dev_info->wPHYRates = wusb_dev->wusb_cap_descr->wPHYRates;
+       else
+               dev_info->wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53);
+
+       ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       WUSB_REQ_SET_DEV_INFO,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       0, wusb_dev->port_idx << 8 | iface_no,
+                       dev_info, sizeof(struct hwa_dev_info),
+                       1000 /* FIXME: arbitrary */);
+       kfree(dev_info);
+       return ret;
+}
+
+/*
+ * Set host's idea of which encryption (and key) method to use when
+ * talking to ad evice on a given port.
+ *
+ * If key is NULL, it means disable encryption for that "virtual port"
+ * (used when we disconnect).
+ */
+static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+                              const void *key, size_t key_size,
+                              u8 key_idx)
+{
+       int result = -ENOMEM;
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct wahc *wa = &hwahc->wa;
+       u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+       struct usb_key_descriptor *keyd;
+       size_t keyd_len;
+
+       keyd_len = sizeof(*keyd) + key_size;
+       keyd = kzalloc(keyd_len, GFP_KERNEL);
+       if (keyd == NULL)
+               return -ENOMEM;
+
+       keyd->bLength = keyd_len;
+       keyd->bDescriptorType = USB_DT_KEY;
+       keyd->tTKID[0] = (tkid >>  0) & 0xff;
+       keyd->tTKID[1] = (tkid >>  8) & 0xff;
+       keyd->tTKID[2] = (tkid >> 16) & 0xff;
+       memcpy(keyd->bKeyData, key, key_size);
+
+       result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       USB_REQ_SET_DESCRIPTOR,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       USB_DT_KEY << 8 | key_idx,
+                       port_idx << 8 | iface_no,
+                       keyd, keyd_len, 1000 /* FIXME: arbitrary */);
+
+       memset(keyd, 0, sizeof(*keyd)); /* clear keys etc. */
+       kfree(keyd);
+       return result;
+}
+
+/*
+ * Set host's idea of which encryption (and key) method to use when
+ * talking to ad evice on a given port.
+ *
+ * If key is NULL, it means disable encryption for that "virtual port"
+ * (used when we disconnect).
+ */
+static int __hwahc_op_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+                             const void *key, size_t key_size)
+{
+       int result = -ENOMEM;
+       struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       struct wahc *wa = &hwahc->wa;
+       u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+       u8 encryption_value;
+
+       /* Tell the host which key to use to talk to the device */
+       if (key) {
+               u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_PTK,
+                                           WUSB_KEY_INDEX_ORIGINATOR_HOST);
+
+               result = __hwahc_dev_set_key(wusbhc, port_idx, tkid,
+                                            key, key_size, key_idx);
+               if (result < 0)
+                       goto error_set_key;
+               encryption_value = wusbhc->ccm1_etd->bEncryptionValue;
+       } else {
+               /* FIXME: this should come from wusbhc->etd[UNSECURE].value */
+               encryption_value = 0;
+       }
+
+       /* Set the encryption type for commmunicating with the device */
+       result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+                       USB_REQ_SET_ENCRYPTION,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       encryption_value, port_idx << 8 | iface_no,
+                       NULL, 0, 1000 /* FIXME: arbitrary */);
+       if (result < 0)
+               dev_err(wusbhc->dev, "Can't set host's WUSB encryption for "
+                       "port index %u to %s (value %d): %d\n", port_idx,
+                       wusb_et_name(wusbhc->ccm1_etd->bEncryptionType),
+                       wusbhc->ccm1_etd->bEncryptionValue, result);
+error_set_key:
+       return result;
+}
+
+/*
+ * Set host's GTK key
+ */
+static int __hwahc_op_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+                             const void *key, size_t key_size)
+{
+       u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK,
+                                   WUSB_KEY_INDEX_ORIGINATOR_HOST);
+
+       return __hwahc_dev_set_key(wusbhc, 0, tkid, key, key_size, key_idx);
+}
+
+/*
+ * Get the Wire Adapter class-specific descriptor
+ *
+ * NOTE: this descriptor comes with the big bundled configuration
+ *       descriptor that includes the interfaces' and endpoints', so
+ *       we just look for it in the cached copy kept by the USB stack.
+ *
+ * NOTE2: We convert LE fields to CPU order.
+ */
+static int wa_fill_descr(struct wahc *wa)
+{
+       int result;
+       struct device *dev = &wa->usb_iface->dev;
+       char *itr;
+       struct usb_device *usb_dev = wa->usb_dev;
+       struct usb_descriptor_header *hdr;
+       struct usb_wa_descriptor *wa_descr;
+       size_t itr_size, actconfig_idx;
+
+       actconfig_idx = (usb_dev->actconfig - usb_dev->config) /
+                       sizeof(usb_dev->config[0]);
+       itr = usb_dev->rawdescriptors[actconfig_idx];
+       itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
+       while (itr_size >= sizeof(*hdr)) {
+               hdr = (struct usb_descriptor_header *) itr;
+               d_printf(3, dev, "Extra device descriptor: "
+                        "type %02x/%u bytes @ %zu (%zu left)\n",
+                        hdr->bDescriptorType, hdr->bLength,
+                        (itr - usb_dev->rawdescriptors[actconfig_idx]),
+                        itr_size);
+               if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER)
+                       goto found;
+               itr += hdr->bLength;
+               itr_size -= hdr->bLength;
+       }
+       dev_err(dev, "cannot find Wire Adapter Class descriptor\n");
+       return -ENODEV;
+
+found:
+       result = -EINVAL;
+       if (hdr->bLength > itr_size) {  /* is it available? */
+               dev_err(dev, "incomplete Wire Adapter Class descriptor "
+                       "(%zu bytes left, %u needed)\n",
+                       itr_size, hdr->bLength);
+               goto error;
+       }
+       if (hdr->bLength < sizeof(*wa->wa_descr)) {
+               dev_err(dev, "short Wire Adapter Class descriptor\n");
+               goto error;
+       }
+       wa->wa_descr = wa_descr = (struct usb_wa_descriptor *) hdr;
+       /* Make LE fields CPU order */
+       wa_descr->bcdWAVersion = le16_to_cpu(wa_descr->bcdWAVersion);
+       wa_descr->wNumRPipes = le16_to_cpu(wa_descr->wNumRPipes);
+       wa_descr->wRPipeMaxBlock = le16_to_cpu(wa_descr->wRPipeMaxBlock);
+       if (wa_descr->bcdWAVersion > 0x0100)
+               dev_warn(dev, "Wire Adapter v%d.%d newer than groked v1.0\n",
+                        wa_descr->bcdWAVersion & 0xff00 >> 8,
+                        wa_descr->bcdWAVersion & 0x00ff);
+       result = 0;
+error:
+       return result;
+}
+
+static struct hc_driver hwahc_hc_driver = {
+       .description = "hwa-hcd",
+       .product_desc = "Wireless USB HWA host controller",
+       .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd),
+       .irq = NULL,                    /* FIXME */
+       .flags = HCD_USB2,              /* FIXME */
+       .reset = hwahc_op_reset,
+       .start = hwahc_op_start,
+       .pci_suspend = hwahc_op_suspend,
+       .pci_resume = hwahc_op_resume,
+       .stop = hwahc_op_stop,
+       .get_frame_number = hwahc_op_get_frame_number,
+       .urb_enqueue = hwahc_op_urb_enqueue,
+       .urb_dequeue = hwahc_op_urb_dequeue,
+       .endpoint_disable = hwahc_op_endpoint_disable,
+
+       .hub_status_data = wusbhc_rh_status_data,
+       .hub_control = wusbhc_rh_control,
+       .bus_suspend = wusbhc_rh_suspend,
+       .bus_resume = wusbhc_rh_resume,
+       .start_port_reset = wusbhc_rh_start_port_reset,
+};
+
+static int hwahc_security_create(struct hwahc *hwahc)
+{
+       int result;
+       struct wusbhc *wusbhc = &hwahc->wusbhc;
+       struct usb_device *usb_dev = hwahc->wa.usb_dev;
+       struct device *dev = &usb_dev->dev;
+       struct usb_security_descriptor *secd;
+       struct usb_encryption_descriptor *etd;
+       void *itr, *top;
+       size_t itr_size, needed, bytes;
+       u8 index;
+       char buf[64];
+
+       /* Find the host's security descriptors in the config descr bundle */
+       index = (usb_dev->actconfig - usb_dev->config) /
+               sizeof(usb_dev->config[0]);
+       itr = usb_dev->rawdescriptors[index];
+       itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
+       top = itr + itr_size;
+       result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index],
+                       le16_to_cpu(usb_dev->actconfig->desc.wTotalLength),
+                       USB_DT_SECURITY, (void **) &secd);
+       if (result == -1) {
+               dev_warn(dev, "BUG? WUSB host has no security descriptors\n");
+               return 0;
+       }
+       needed = sizeof(*secd);
+       if (top - (void *)secd < needed) {
+               dev_err(dev, "BUG? Not enough data to process security "
+                       "descriptor header (%zu bytes left vs %zu needed)\n",
+                       top - (void *) secd, needed);
+               return 0;
+       }
+       needed = le16_to_cpu(secd->wTotalLength);
+       if (top - (void *)secd < needed) {
+               dev_err(dev, "BUG? Not enough data to process security "
+                       "descriptors (%zu bytes left vs %zu needed)\n",
+                       top - (void *) secd, needed);
+               return 0;
+       }
+       /* Walk over the sec descriptors and store CCM1's on wusbhc */
+       itr = (void *) secd + sizeof(*secd);
+       top = (void *) secd + le16_to_cpu(secd->wTotalLength);
+       index = 0;
+       bytes = 0;
+       while (itr < top) {
+               etd = itr;
+               if (top - itr < sizeof(*etd)) {
+                       dev_err(dev, "BUG: bad host security descriptor; "
+                               "not enough data (%zu vs %zu left)\n",
+                               top - itr, sizeof(*etd));
+                       break;
+               }
+               if (etd->bLength < sizeof(*etd)) {
+                       dev_err(dev, "BUG: bad host encryption descriptor; "
+                               "descriptor is too short "
+                               "(%zu vs %zu needed)\n",
+                               (size_t)etd->bLength, sizeof(*etd));
+                       break;
+               }
+               itr += etd->bLength;
+               bytes += snprintf(buf + bytes, sizeof(buf) - bytes,
+                                 "%s (0x%02x) ",
+                                 wusb_et_name(etd->bEncryptionType),
+                                 etd->bEncryptionValue);
+               wusbhc->ccm1_etd = etd;
+       }
+       dev_info(dev, "supported encryption types: %s\n", buf);
+       if (wusbhc->ccm1_etd == NULL) {
+               dev_err(dev, "E: host doesn't support CCM-1 crypto\n");
+               return 0;
+       }
+       /* Pretty print what we support */
+       return 0;
+}
+
+static void hwahc_security_release(struct hwahc *hwahc)
+{
+       /* nothing to do here so far... */
+}
+
+static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface)
+{
+       int result;
+       struct device *dev = &iface->dev;
+       struct wusbhc *wusbhc = &hwahc->wusbhc;
+       struct wahc *wa = &hwahc->wa;
+       struct usb_device *usb_dev = interface_to_usbdev(iface);
+
+       wa->usb_dev = usb_get_dev(usb_dev);     /* bind the USB device */
+       wa->usb_iface = usb_get_intf(iface);
+       wusbhc->dev = dev;
+       wusbhc->uwb_rc = uwb_rc_get_by_grandpa(iface->dev.parent);
+       if (wusbhc->uwb_rc == NULL) {
+               result = -ENODEV;
+               dev_err(dev, "Cannot get associated UWB Host Controller\n");
+               goto error_rc_get;
+       }
+       result = wa_fill_descr(wa);     /* Get the device descriptor */
+       if (result < 0)
+               goto error_fill_descriptor;
+       if (wa->wa_descr->bNumPorts > USB_MAXCHILDREN) {
+               dev_err(dev, "FIXME: USB_MAXCHILDREN too low for WUSB "
+                       "adapter (%u ports)\n", wa->wa_descr->bNumPorts);
+               wusbhc->ports_max = USB_MAXCHILDREN;
+       } else {
+               wusbhc->ports_max = wa->wa_descr->bNumPorts;
+       }
+       wusbhc->mmcies_max = wa->wa_descr->bNumMMCIEs;
+       wusbhc->start = __hwahc_op_wusbhc_start;
+       wusbhc->stop = __hwahc_op_wusbhc_stop;
+       wusbhc->mmcie_add = __hwahc_op_mmcie_add;
+       wusbhc->mmcie_rm = __hwahc_op_mmcie_rm;
+       wusbhc->dev_info_set = __hwahc_op_dev_info_set;
+       wusbhc->bwa_set = __hwahc_op_bwa_set;
+       wusbhc->set_num_dnts = __hwahc_op_set_num_dnts;
+       wusbhc->set_ptk = __hwahc_op_set_ptk;
+       wusbhc->set_gtk = __hwahc_op_set_gtk;
+       result = hwahc_security_create(hwahc);
+       if (result < 0) {
+               dev_err(dev, "Can't initialize security: %d\n", result);
+               goto error_security_create;
+       }
+       wa->wusb = wusbhc;      /* FIXME: ugly, need to fix */
+       result = wusbhc_create(&hwahc->wusbhc);
+       if (result < 0) {
+               dev_err(dev, "Can't create WUSB HC structures: %d\n", result);
+               goto error_wusbhc_create;
+       }
+       result = wa_create(&hwahc->wa, iface);
+       if (result < 0)
+               goto error_wa_create;
+       return 0;
+
+error_wa_create:
+       wusbhc_destroy(&hwahc->wusbhc);
+error_wusbhc_create:
+       /* WA Descr fill allocs no resources */
+error_security_create:
+error_fill_descriptor:
+       uwb_rc_put(wusbhc->uwb_rc);
+error_rc_get:
+       usb_put_intf(iface);
+       usb_put_dev(usb_dev);
+       return result;
+}
+
+static void hwahc_destroy(struct hwahc *hwahc)
+{
+       struct wusbhc *wusbhc = &hwahc->wusbhc;
+
+       d_fnstart(1, NULL, "(hwahc %p)\n", hwahc);
+       mutex_lock(&wusbhc->mutex);
+       __wa_destroy(&hwahc->wa);
+       wusbhc_destroy(&hwahc->wusbhc);
+       hwahc_security_release(hwahc);
+       hwahc->wusbhc.dev = NULL;
+       uwb_rc_put(wusbhc->uwb_rc);
+       usb_put_intf(hwahc->wa.usb_iface);
+       usb_put_dev(hwahc->wa.usb_dev);
+       mutex_unlock(&wusbhc->mutex);
+       d_fnend(1, NULL, "(hwahc %p) = void\n", hwahc);
+}
+
+static void hwahc_init(struct hwahc *hwahc)
+{
+       wa_init(&hwahc->wa);
+}
+
+static int hwahc_probe(struct usb_interface *usb_iface,
+                      const struct usb_device_id *id)
+{
+       int result;
+       struct usb_hcd *usb_hcd;
+       struct wusbhc *wusbhc;
+       struct hwahc *hwahc;
+       struct device *dev = &usb_iface->dev;
+
+       d_fnstart(4, dev, "(%p, %p)\n", usb_iface, id);
+       result = -ENOMEM;
+       usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa");
+       if (usb_hcd == NULL) {
+               dev_err(dev, "unable to allocate instance\n");
+               goto error_alloc;
+       }
+       usb_hcd->wireless = 1;
+       usb_hcd->flags |= HCD_FLAG_SAW_IRQ;
+       wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+       hwahc_init(hwahc);
+       result = hwahc_create(hwahc, usb_iface);
+       if (result < 0) {
+               dev_err(dev, "Cannot initialize internals: %d\n", result);
+               goto error_hwahc_create;
+       }
+       result = usb_add_hcd(usb_hcd, 0, 0);
+       if (result < 0) {
+               dev_err(dev, "Cannot add HCD: %d\n", result);
+               goto error_add_hcd;
+       }
+       result = wusbhc_b_create(&hwahc->wusbhc);
+       if (result < 0) {
+               dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result);
+               goto error_wusbhc_b_create;
+       }
+       d_fnend(4, dev, "(%p, %p) = 0\n", usb_iface, id);
+       return 0;
+
+error_wusbhc_b_create:
+       usb_remove_hcd(usb_hcd);
+error_add_hcd:
+       hwahc_destroy(hwahc);
+error_hwahc_create:
+       usb_put_hcd(usb_hcd);
+error_alloc:
+       d_fnend(4, dev, "(%p, %p) = %d\n", usb_iface, id, result);
+       return result;
+}
+
+static void hwahc_disconnect(struct usb_interface *usb_iface)
+{
+       struct usb_hcd *usb_hcd;
+       struct wusbhc *wusbhc;
+       struct hwahc *hwahc;
+
+       usb_hcd = usb_get_intfdata(usb_iface);
+       wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+       d_fnstart(1, NULL, "(hwahc %p [usb_iface %p])\n", hwahc, usb_iface);
+       wusbhc_b_destroy(&hwahc->wusbhc);
+       usb_remove_hcd(usb_hcd);
+       hwahc_destroy(hwahc);
+       usb_put_hcd(usb_hcd);
+       d_fnend(1, NULL, "(hwahc %p [usb_iface %p]) = void\n", hwahc,
+               usb_iface);
+}
+
+/** USB device ID's that we handle */
+static struct usb_device_id hwahc_id_table[] = {
+       /* FIXME: use class labels for this */
+       { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), },
+       {},
+};
+MODULE_DEVICE_TABLE(usb, hwahc_id_table);
+
+static struct usb_driver hwahc_driver = {
+       .name =         "hwa-hc",
+       .probe =        hwahc_probe,
+       .disconnect =   hwahc_disconnect,
+       .id_table =     hwahc_id_table,
+};
+
+static int __init hwahc_driver_init(void)
+{
+       int result;
+       result = usb_register(&hwahc_driver);
+       if (result < 0) {
+               printk(KERN_ERR "WA-CDS: Cannot register USB driver: %d\n",
+                      result);
+               goto error_usb_register;
+       }
+       return 0;
+
+error_usb_register:
+       return result;
+
+}
+module_init(hwahc_driver_init);
+
+static void __exit hwahc_driver_exit(void)
+{
+       usb_deregister(&hwahc_driver);
+}
+module_exit(hwahc_driver_exit);
+
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild
new file mode 100644 (file)
index 0000000..26a3871
--- /dev/null
@@ -0,0 +1,11 @@
+obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o
+
+whci-hcd-y := \
+       asl.o   \
+       hcd.o   \
+       hw.o    \
+       init.o  \
+       int.o   \
+       pzl.o   \
+       qset.o  \
+       wusb.o
diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c
new file mode 100644 (file)
index 0000000..4d7078e
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+ * Wireless Host Controller (WHC) asynchronous schedule management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+#include <linux/usb.h>
+#define D_LOCAL 0
+#include <linux/uwb/debug.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+#if D_LOCAL >= 4
+static void dump_asl(struct whc *whc, const char *tag)
+{
+       struct device *dev = &whc->umc->dev;
+       struct whc_qset *qset;
+
+       d_printf(4, dev, "ASL %s\n", tag);
+
+       list_for_each_entry(qset, &whc->async_list, list_node) {
+               dump_qset(qset, dev);
+       }
+}
+#else
+static inline void dump_asl(struct whc *whc, const char *tag)
+{
+}
+#endif
+
+
+static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset,
+                              struct whc_qset **next, struct whc_qset **prev)
+{
+       struct list_head *n, *p;
+
+       BUG_ON(list_empty(&whc->async_list));
+
+       n = qset->list_node.next;
+       if (n == &whc->async_list)
+               n = n->next;
+       p = qset->list_node.prev;
+       if (p == &whc->async_list)
+               p = p->prev;
+
+       *next = container_of(n, struct whc_qset, list_node);
+       *prev = container_of(p, struct whc_qset, list_node);
+
+}
+
+static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset)
+{
+       list_move(&qset->list_node, &whc->async_list);
+       qset->in_sw_list = true;
+}
+
+static void asl_qset_insert(struct whc *whc, struct whc_qset *qset)
+{
+       struct whc_qset *next, *prev;
+
+       qset_clear(whc, qset);
+
+       /* Link into ASL. */
+       qset_get_next_prev(whc, qset, &next, &prev);
+       whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma);
+       whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma);
+       qset->in_hw_list = true;
+}
+
+static void asl_qset_remove(struct whc *whc, struct whc_qset *qset)
+{
+       struct whc_qset *prev, *next;
+
+       qset_get_next_prev(whc, qset, &next, &prev);
+
+       list_move(&qset->list_node, &whc->async_removed_list);
+       qset->in_sw_list = false;
+
+       /*
+        * No more qsets in the ASL?  The caller must stop the ASL as
+        * it's no longer valid.
+        */
+       if (list_empty(&whc->async_list))
+               return;
+
+       /* Remove from ASL. */
+       whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma);
+       qset->in_hw_list = false;
+}
+
+/**
+ * process_qset - process any recently inactivated or halted qTDs in a
+ * qset.
+ *
+ * After inactive qTDs are removed, new qTDs can be added if the
+ * urb queue still contains URBs.
+ *
+ * Returns any additional WUSBCMD bits for the ASL sync command (i.e.,
+ * WUSBCMD_ASYNC_QSET_RM if a halted qset was removed).
+ */
+static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
+{
+       enum whc_update update = 0;
+       uint32_t status = 0;
+
+       while (qset->ntds) {
+               struct whc_qtd *td;
+               int t;
+
+               t = qset->td_start;
+               td = &qset->qtd[qset->td_start];
+               status = le32_to_cpu(td->status);
+
+               /*
+                * Nothing to do with a still active qTD.
+                */
+               if (status & QTD_STS_ACTIVE)
+                       break;
+
+               if (status & QTD_STS_HALTED) {
+                       /* Ug, an error. */
+                       process_halted_qtd(whc, qset, td);
+                       goto done;
+               }
+
+               /* Mmm, a completed qTD. */
+               process_inactive_qtd(whc, qset, td);
+       }
+
+       update |= qset_add_qtds(whc, qset);
+
+done:
+       /*
+        * Remove this qset from the ASL if requested, but only if has
+        * no qTDs.
+        */
+       if (qset->remove && qset->ntds == 0) {
+               asl_qset_remove(whc, qset);
+               update |= WHC_UPDATE_REMOVED;
+       }
+       return update;
+}
+
+void asl_start(struct whc *whc)
+{
+       struct whc_qset *qset;
+
+       qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
+
+       le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR);
+
+       whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN);
+       whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+                     WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED,
+                     1000, "start ASL");
+}
+
+void asl_stop(struct whc *whc)
+{
+       whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0);
+       whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+                     WUSBSTS_ASYNC_SCHED, 0,
+                     1000, "stop ASL");
+}
+
+void asl_update(struct whc *whc, uint32_t wusbcmd)
+{
+       whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
+       wait_event(whc->async_list_wq,
+                  (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0);
+}
+
+/**
+ * scan_async_work - scan the ASL for qsets to process.
+ *
+ * Process each qset in the ASL in turn and then signal the WHC that
+ * the ASL has been updated.
+ *
+ * Then start, stop or update the asynchronous schedule as required.
+ */
+void scan_async_work(struct work_struct *work)
+{
+       struct whc *whc = container_of(work, struct whc, async_work);
+       struct whc_qset *qset, *t;
+       enum whc_update update = 0;
+
+       spin_lock_irq(&whc->lock);
+
+       dump_asl(whc, "before processing");
+
+       /*
+        * Transerve the software list backwards so new qsets can be
+        * safely inserted into the ASL without making it non-circular.
+        */
+       list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) {
+               if (!qset->in_hw_list) {
+                       asl_qset_insert(whc, qset);
+                       update |= WHC_UPDATE_ADDED;
+               }
+
+               update |= process_qset(whc, qset);
+       }
+
+       dump_asl(whc, "after processing");
+
+       spin_unlock_irq(&whc->lock);
+
+       if (update) {
+               uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB;
+               if (update & WHC_UPDATE_REMOVED)
+                       wusbcmd |= WUSBCMD_ASYNC_QSET_RM;
+               asl_update(whc, wusbcmd);
+       }
+
+       /*
+        * Now that the ASL is updated, complete the removal of any
+        * removed qsets.
+        */
+       spin_lock(&whc->lock);
+
+       list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) {
+               qset_remove_complete(whc, qset);
+       }
+
+       spin_unlock(&whc->lock);
+}
+
+/**
+ * asl_urb_enqueue - queue an URB onto the asynchronous list (ASL).
+ * @whc: the WHCI host controller
+ * @urb: the URB to enqueue
+ * @mem_flags: flags for any memory allocations
+ *
+ * The qset for the endpoint is obtained and the urb queued on to it.
+ *
+ * Work is scheduled to update the hardware's view of the ASL.
+ */
+int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
+{
+       struct whc_qset *qset;
+       int err;
+       unsigned long flags;
+
+       spin_lock_irqsave(&whc->lock, flags);
+
+       qset = get_qset(whc, urb, GFP_ATOMIC);
+       if (qset == NULL)
+               err = -ENOMEM;
+       else
+               err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
+       if (!err) {
+               usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
+               if (!qset->in_sw_list)
+                       asl_qset_insert_begin(whc, qset);
+       }
+
+       spin_unlock_irqrestore(&whc->lock, flags);
+
+       if (!err)
+               queue_work(whc->workqueue, &whc->async_work);
+
+       return 0;
+}
+
+/**
+ * asl_urb_dequeue - remove an URB (qset) from the async list.
+ * @whc: the WHCI host controller
+ * @urb: the URB to dequeue
+ * @status: the current status of the URB
+ *
+ * URBs that do yet have qTDs can simply be removed from the software
+ * queue, otherwise the qset must be removed from the ASL so the qTDs
+ * can be removed.
+ */
+int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
+{
+       struct whc_urb *wurb = urb->hcpriv;
+       struct whc_qset *qset = wurb->qset;
+       struct whc_std *std, *t;
+       int ret;
+       unsigned long flags;
+
+       spin_lock_irqsave(&whc->lock, flags);
+
+       ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
+       if (ret < 0)
+               goto out;
+
+       list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+               if (std->urb == urb)
+                       qset_free_std(whc, std);
+               else
+                       std->qtd = NULL; /* so this std is re-added when the qset is */
+       }
+
+       asl_qset_remove(whc, qset);
+       wurb->status = status;
+       wurb->is_async = true;
+       queue_work(whc->workqueue, &wurb->dequeue_work);
+
+out:
+       spin_unlock_irqrestore(&whc->lock, flags);
+
+       return ret;
+}
+
+/**
+ * asl_qset_delete - delete a qset from the ASL
+ */
+void asl_qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+       qset->remove = 1;
+       queue_work(whc->workqueue, &whc->async_work);
+       qset_delete(whc, qset);
+}
+
+/**
+ * asl_init - initialize the asynchronous schedule list
+ *
+ * A dummy qset with no qTDs is added to the ASL to simplify removing
+ * qsets (no need to stop the ASL when the last qset is removed).
+ */
+int asl_init(struct whc *whc)
+{
+       struct whc_qset *qset;
+
+       qset = qset_alloc(whc, GFP_KERNEL);
+       if (qset == NULL)
+               return -ENOMEM;
+
+       asl_qset_insert_begin(whc, qset);
+       asl_qset_insert(whc, qset);
+
+       return 0;
+}
+
+/**
+ * asl_clean_up - free ASL resources
+ *
+ * The ASL is stopped and empty except for the dummy qset.
+ */
+void asl_clean_up(struct whc *whc)
+{
+       struct whc_qset *qset;
+
+       if (!list_empty(&whc->async_list)) {
+               qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
+               list_del(&qset->list_node);
+               qset_free(whc, qset);
+       }
+}
diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c
new file mode 100644 (file)
index 0000000..ef3ad4d
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Wireless Host Controller (WHC) driver.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/uwb/umc.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+/*
+ * One time initialization.
+ *
+ * Nothing to do here.
+ */
+static int whc_reset(struct usb_hcd *usb_hcd)
+{
+       return 0;
+}
+
+/*
+ * Start the wireless host controller.
+ *
+ * Start device notification.
+ *
+ * Put hc into run state, set DNTS parameters.
+ */
+static int whc_start(struct usb_hcd *usb_hcd)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       u8 bcid;
+       int ret;
+
+       mutex_lock(&wusbhc->mutex);
+
+       le_writel(WUSBINTR_GEN_CMD_DONE
+                 | WUSBINTR_HOST_ERR
+                 | WUSBINTR_ASYNC_SCHED_SYNCED
+                 | WUSBINTR_DNTS_INT
+                 | WUSBINTR_ERR_INT
+                 | WUSBINTR_INT,
+                 whc->base + WUSBINTR);
+
+       /* set cluster ID */
+       bcid = wusb_cluster_id_get();
+       ret = whc_set_cluster_id(whc, bcid);
+       if (ret < 0)
+               goto out;
+       wusbhc->cluster_id = bcid;
+
+       /* start HC */
+       whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN);
+
+       usb_hcd->uses_new_polling = 1;
+       usb_hcd->poll_rh = 1;
+       usb_hcd->state = HC_STATE_RUNNING;
+
+out:
+       mutex_unlock(&wusbhc->mutex);
+       return ret;
+}
+
+
+/*
+ * Stop the wireless host controller.
+ *
+ * Stop device notification.
+ *
+ * Wait for pending transfer to stop? Put hc into stop state?
+ */
+static void whc_stop(struct usb_hcd *usb_hcd)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+
+       mutex_lock(&wusbhc->mutex);
+
+       wusbhc_stop(wusbhc);
+
+       /* stop HC */
+       le_writel(0, whc->base + WUSBINTR);
+       whc_write_wusbcmd(whc, WUSBCMD_RUN, 0);
+       whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+                     WUSBSTS_HCHALTED, WUSBSTS_HCHALTED,
+                     100, "HC to halt");
+
+       wusb_cluster_id_put(wusbhc->cluster_id);
+
+       mutex_unlock(&wusbhc->mutex);
+}
+
+static int whc_get_frame_number(struct usb_hcd *usb_hcd)
+{
+       /* Frame numbers are not applicable to WUSB. */
+       return -ENOSYS;
+}
+
+
+/*
+ * Queue an URB to the ASL or PZL
+ */
+static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
+                          gfp_t mem_flags)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       int ret;
+
+       switch (usb_pipetype(urb->pipe)) {
+       case PIPE_INTERRUPT:
+               ret = pzl_urb_enqueue(whc, urb, mem_flags);
+               break;
+       case PIPE_ISOCHRONOUS:
+               dev_err(&whc->umc->dev, "isochronous transfers unsupported\n");
+               ret = -ENOTSUPP;
+               break;
+       case PIPE_CONTROL:
+       case PIPE_BULK:
+       default:
+               ret = asl_urb_enqueue(whc, urb, mem_flags);
+               break;
+       };
+
+       return ret;
+}
+
+/*
+ * Remove a queued URB from the ASL or PZL.
+ */
+static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       int ret;
+
+       switch (usb_pipetype(urb->pipe)) {
+       case PIPE_INTERRUPT:
+               ret = pzl_urb_dequeue(whc, urb, status);
+               break;
+       case PIPE_ISOCHRONOUS:
+               ret = -ENOTSUPP;
+               break;
+       case PIPE_CONTROL:
+       case PIPE_BULK:
+       default:
+               ret = asl_urb_dequeue(whc, urb, status);
+               break;
+       };
+
+       return ret;
+}
+
+/*
+ * Wait for all URBs to the endpoint to be completed, then delete the
+ * qset.
+ */
+static void whc_endpoint_disable(struct usb_hcd *usb_hcd,
+                                struct usb_host_endpoint *ep)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       struct whc_qset *qset;
+
+       qset = ep->hcpriv;
+       if (qset) {
+               ep->hcpriv = NULL;
+               if (usb_endpoint_xfer_bulk(&ep->desc)
+                   || usb_endpoint_xfer_control(&ep->desc))
+                       asl_qset_delete(whc, qset);
+               else
+                       pzl_qset_delete(whc, qset);
+       }
+}
+
+static struct hc_driver whc_hc_driver = {
+       .description = "whci-hcd",
+       .product_desc = "Wireless host controller",
+       .hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd),
+       .irq = whc_int_handler,
+       .flags = HCD_USB2,
+
+       .reset = whc_reset,
+       .start = whc_start,
+       .stop = whc_stop,
+       .get_frame_number = whc_get_frame_number,
+       .urb_enqueue = whc_urb_enqueue,
+       .urb_dequeue = whc_urb_dequeue,
+       .endpoint_disable = whc_endpoint_disable,
+
+       .hub_status_data = wusbhc_rh_status_data,
+       .hub_control = wusbhc_rh_control,
+       .bus_suspend = wusbhc_rh_suspend,
+       .bus_resume = wusbhc_rh_resume,
+       .start_port_reset = wusbhc_rh_start_port_reset,
+};
+
+static int whc_probe(struct umc_dev *umc)
+{
+       int ret = -ENOMEM;
+       struct usb_hcd *usb_hcd;
+       struct wusbhc *wusbhc = NULL;
+       struct whc *whc = NULL;
+       struct device *dev = &umc->dev;
+
+       usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci");
+       if (usb_hcd == NULL) {
+               dev_err(dev, "unable to create hcd\n");
+               goto error;
+       }
+
+       usb_hcd->wireless = 1;
+
+       wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       whc = wusbhc_to_whc(wusbhc);
+       whc->umc = umc;
+
+       ret = whc_init(whc);
+       if (ret)
+               goto error;
+
+       wusbhc->dev = dev;
+       wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent);
+       if (!wusbhc->uwb_rc) {
+               ret = -ENODEV;
+               dev_err(dev, "cannot get radio controller\n");
+               goto error;
+       }
+
+       if (whc->n_devices > USB_MAXCHILDREN) {
+               dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n",
+                        whc->n_devices);
+               wusbhc->ports_max = USB_MAXCHILDREN;
+       } else
+               wusbhc->ports_max = whc->n_devices;
+       wusbhc->mmcies_max      = whc->n_mmc_ies;
+       wusbhc->start           = whc_wusbhc_start;
+       wusbhc->stop            = whc_wusbhc_stop;
+       wusbhc->mmcie_add       = whc_mmcie_add;
+       wusbhc->mmcie_rm        = whc_mmcie_rm;
+       wusbhc->dev_info_set    = whc_dev_info_set;
+       wusbhc->bwa_set         = whc_bwa_set;
+       wusbhc->set_num_dnts    = whc_set_num_dnts;
+       wusbhc->set_ptk         = whc_set_ptk;
+       wusbhc->set_gtk         = whc_set_gtk;
+
+       ret = wusbhc_create(wusbhc);
+       if (ret)
+               goto error_wusbhc_create;
+
+       ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED);
+       if (ret) {
+               dev_err(dev, "cannot add HCD: %d\n", ret);
+               goto error_usb_add_hcd;
+       }
+
+       ret = wusbhc_b_create(wusbhc);
+       if (ret) {
+               dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret);
+               goto error_wusbhc_b_create;
+       }
+
+       return 0;
+
+error_wusbhc_b_create:
+       usb_remove_hcd(usb_hcd);
+error_usb_add_hcd:
+       wusbhc_destroy(wusbhc);
+error_wusbhc_create:
+       uwb_rc_put(wusbhc->uwb_rc);
+error:
+       whc_clean_up(whc);
+       if (usb_hcd)
+               usb_put_hcd(usb_hcd);
+       return ret;
+}
+
+
+static void whc_remove(struct umc_dev *umc)
+{
+       struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev);
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+
+       if (usb_hcd) {
+               wusbhc_b_destroy(wusbhc);
+               usb_remove_hcd(usb_hcd);
+               wusbhc_destroy(wusbhc);
+               uwb_rc_put(wusbhc->uwb_rc);
+               whc_clean_up(whc);
+               usb_put_hcd(usb_hcd);
+       }
+}
+
+static struct umc_driver whci_hc_driver = {
+       .name =         "whci-hcd",
+       .cap_id =       UMC_CAP_ID_WHCI_WUSB_HC,
+       .probe =        whc_probe,
+       .remove =       whc_remove,
+};
+
+static int __init whci_hc_driver_init(void)
+{
+       return umc_driver_register(&whci_hc_driver);
+}
+module_init(whci_hc_driver_init);
+
+static void __exit whci_hc_driver_exit(void)
+{
+       umc_driver_unregister(&whci_hc_driver);
+}
+module_exit(whci_hc_driver_exit);
+
+/* PCI device ID's that we handle (so it gets loaded) */
+static struct pci_device_id whci_hcd_id_table[] = {
+       { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
+       { /* empty last entry */ }
+};
+MODULE_DEVICE_TABLE(pci, whci_hcd_id_table);
+
+MODULE_DESCRIPTION("WHCI Wireless USB host controller driver");
+MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c
new file mode 100644 (file)
index 0000000..ac86e59
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Wireless Host Controller (WHC) hardware access helpers.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val)
+{
+       unsigned long flags;
+       u32 cmd;
+
+       spin_lock_irqsave(&whc->lock, flags);
+
+       cmd = le_readl(whc->base + WUSBCMD);
+       cmd = (cmd & ~mask) | val;
+       le_writel(cmd, whc->base + WUSBCMD);
+
+       spin_unlock_irqrestore(&whc->lock, flags);
+}
+
+/**
+ * whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register
+ * @whc:    the WHCI HC
+ * @cmd:    command to start.
+ * @params: parameters for the command (the WUSBGENCMDPARAMS register value).
+ * @addr:   pointer to any data for the command (may be NULL).
+ * @len:    length of the data (if any).
+ */
+int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len)
+{
+       unsigned long flags;
+       dma_addr_t dma_addr;
+       int t;
+
+       mutex_lock(&whc->mutex);
+
+       /* Wait for previous command to complete. */
+       t = wait_event_timeout(whc->cmd_wq,
+                              (le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0,
+                              WHC_GENCMD_TIMEOUT_MS);
+       if (t == 0) {
+               dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n",
+                       le_readl(whc->base + WUSBGENCMDSTS),
+                       le_readl(whc->base + WUSBGENCMDPARAMS));
+               return -ETIMEDOUT;
+       }
+
+       if (addr) {
+               memcpy(whc->gen_cmd_buf, addr, len);
+               dma_addr = whc->gen_cmd_buf_dma;
+       } else
+               dma_addr = 0;
+
+       /* Poke registers to start cmd. */
+       spin_lock_irqsave(&whc->lock, flags);
+
+       le_writel(params, whc->base + WUSBGENCMDPARAMS);
+       le_writeq(dma_addr, whc->base + WUSBGENADDR);
+
+       le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd,
+                 whc->base + WUSBGENCMDSTS);
+
+       spin_unlock_irqrestore(&whc->lock, flags);
+
+       mutex_unlock(&whc->mutex);
+
+       return 0;
+}
diff --git a/drivers/usb/host/whci/init.c b/drivers/usb/host/whci/init.c
new file mode 100644 (file)
index 0000000..34a783c
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Wireless Host Controller (WHC) initialization.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+/*
+ * Reset the host controller.
+ */
+static void whc_hw_reset(struct whc *whc)
+{
+       le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD);
+       whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0,
+                     100, "reset");
+}
+
+static void whc_hw_init_di_buf(struct whc *whc)
+{
+       int d;
+
+       /* Disable all entries in the Device Information buffer. */
+       for (d = 0; d < whc->n_devices; d++)
+               whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE;
+
+       le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR);
+}
+
+static void whc_hw_init_dn_buf(struct whc *whc)
+{
+       /* Clear the Device Notification buffer to ensure the V (valid)
+        * bits are clear.  */
+       memset(whc->dn_buf, 0, 4096);
+
+       le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR);
+}
+
+int whc_init(struct whc *whc)
+{
+       u32 whcsparams;
+       int ret, i;
+       resource_size_t start, len;
+
+       spin_lock_init(&whc->lock);
+       mutex_init(&whc->mutex);
+       init_waitqueue_head(&whc->cmd_wq);
+       init_waitqueue_head(&whc->async_list_wq);
+       init_waitqueue_head(&whc->periodic_list_wq);
+       whc->workqueue = create_singlethread_workqueue(dev_name(&whc->umc->dev));
+       if (whc->workqueue == NULL) {
+               ret = -ENOMEM;
+               goto error;
+       }
+       INIT_WORK(&whc->dn_work, whc_dn_work);
+
+       INIT_WORK(&whc->async_work, scan_async_work);
+       INIT_LIST_HEAD(&whc->async_list);
+       INIT_LIST_HEAD(&whc->async_removed_list);
+
+       INIT_WORK(&whc->periodic_work, scan_periodic_work);
+       for (i = 0; i < 5; i++)
+               INIT_LIST_HEAD(&whc->periodic_list[i]);
+       INIT_LIST_HEAD(&whc->periodic_removed_list);
+
+       /* Map HC registers. */
+       start = whc->umc->resource.start;
+       len   = whc->umc->resource.end - start + 1;
+       if (!request_mem_region(start, len, "whci-hc")) {
+               dev_err(&whc->umc->dev, "can't request HC region\n");
+               ret = -EBUSY;
+               goto error;
+       }
+       whc->base_phys = start;
+       whc->base = ioremap(start, len);
+       if (!whc->base) {
+               dev_err(&whc->umc->dev, "ioremap\n");
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       whc_hw_reset(whc);
+
+       /* Read maximum number of devices, keys and MMC IEs. */
+       whcsparams = le_readl(whc->base + WHCSPARAMS);
+       whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams);
+       whc->n_keys    = WHCSPARAMS_TO_N_KEYS(whcsparams);
+       whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams);
+
+       dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n",
+               whc->n_devices, whc->n_keys, whc->n_mmc_ies);
+
+       whc->qset_pool = dma_pool_create("qset", &whc->umc->dev,
+                                        sizeof(struct whc_qset), 64, 0);
+       if (whc->qset_pool == NULL) {
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       ret = asl_init(whc);
+       if (ret < 0)
+               goto error;
+       ret = pzl_init(whc);
+       if (ret < 0)
+               goto error;
+
+       /* Allocate and initialize a buffer for generic commands, the
+          Device Information buffer, and the Device Notification
+          buffer. */
+
+       whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
+                                             &whc->gen_cmd_buf_dma, GFP_KERNEL);
+       if (whc->gen_cmd_buf == NULL) {
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       whc->dn_buf = dma_alloc_coherent(&whc->umc->dev,
+                                        sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
+                                        &whc->dn_buf_dma, GFP_KERNEL);
+       if (!whc->dn_buf) {
+               ret = -ENOMEM;
+               goto error;
+       }
+       whc_hw_init_dn_buf(whc);
+
+       whc->di_buf = dma_alloc_coherent(&whc->umc->dev,
+                                        sizeof(struct di_buf_entry) * whc->n_devices,
+                                        &whc->di_buf_dma, GFP_KERNEL);
+       if (!whc->di_buf) {
+               ret = -ENOMEM;
+               goto error;
+       }
+       whc_hw_init_di_buf(whc);
+
+       return 0;
+
+error:
+       whc_clean_up(whc);
+       return ret;
+}
+
+void whc_clean_up(struct whc *whc)
+{
+       resource_size_t len;
+
+       if (whc->di_buf)
+               dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices,
+                                 whc->di_buf, whc->di_buf_dma);
+       if (whc->dn_buf)
+               dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
+                                 whc->dn_buf, whc->dn_buf_dma);
+       if (whc->gen_cmd_buf)
+               dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
+                                 whc->gen_cmd_buf, whc->gen_cmd_buf_dma);
+
+       pzl_clean_up(whc);
+       asl_clean_up(whc);
+
+       if (whc->qset_pool)
+               dma_pool_destroy(whc->qset_pool);
+
+       len   = whc->umc->resource.end - whc->umc->resource.start + 1;
+       if (whc->base)
+               iounmap(whc->base);
+       if (whc->base_phys)
+               release_mem_region(whc->base_phys, len);
+
+       if (whc->workqueue)
+               destroy_workqueue(whc->workqueue);
+}
diff --git a/drivers/usb/host/whci/int.c b/drivers/usb/host/whci/int.c
new file mode 100644 (file)
index 0000000..fce0117
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Wireless Host Controller (WHC) interrupt handling.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/uwb/umc.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+static void transfer_done(struct whc *whc)
+{
+       queue_work(whc->workqueue, &whc->async_work);
+       queue_work(whc->workqueue, &whc->periodic_work);
+}
+
+irqreturn_t whc_int_handler(struct usb_hcd *hcd)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd);
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       u32 sts;
+
+       sts = le_readl(whc->base + WUSBSTS);
+       if (!(sts & WUSBSTS_INT_MASK))
+               return IRQ_NONE;
+       le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS);
+
+       if (sts & WUSBSTS_GEN_CMD_DONE)
+               wake_up(&whc->cmd_wq);
+
+       if (sts & WUSBSTS_HOST_ERR)
+               dev_err(&whc->umc->dev, "FIXME: host system error\n");
+
+       if (sts & WUSBSTS_ASYNC_SCHED_SYNCED)
+               wake_up(&whc->async_list_wq);
+
+       if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED)
+               wake_up(&whc->periodic_list_wq);
+
+       if (sts & WUSBSTS_DNTS_INT)
+               queue_work(whc->workqueue, &whc->dn_work);
+
+       /*
+        * A transfer completed (see [WHCI] section 4.7.1.2 for when
+        * this occurs).
+        */
+       if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT))
+               transfer_done(whc);
+
+       return IRQ_HANDLED;
+}
+
+static int process_dn_buf(struct whc *whc)
+{
+       struct wusbhc *wusbhc = &whc->wusbhc;
+       struct dn_buf_entry *dn;
+       int processed = 0;
+
+       for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) {
+               if (dn->status & WHC_DN_STATUS_VALID) {
+                       wusbhc_handle_dn(wusbhc, dn->src_addr,
+                                        (struct wusb_dn_hdr *)dn->dn_data,
+                                        dn->msg_size);
+                       dn->status &= ~WHC_DN_STATUS_VALID;
+                       processed++;
+               }
+       }
+       return processed;
+}
+
+void whc_dn_work(struct work_struct *work)
+{
+       struct whc *whc = container_of(work, struct whc, dn_work);
+       int processed;
+
+       do {
+               processed = process_dn_buf(whc);
+       } while (processed);
+}
diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c
new file mode 100644 (file)
index 0000000..8d62df0
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * Wireless Host Controller (WHC) periodic schedule management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+#include <linux/usb.h>
+#define D_LOCAL 0
+#include <linux/uwb/debug.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+#if D_LOCAL >= 4
+static void dump_pzl(struct whc *whc, const char *tag)
+{
+       struct device *dev = &whc->umc->dev;
+       struct whc_qset *qset;
+       int period = 0;
+
+       d_printf(4, dev, "PZL %s\n", tag);
+
+       for (period = 0; period < 5; period++) {
+               d_printf(4, dev, "Period %d\n", period);
+               list_for_each_entry(qset, &whc->periodic_list[period], list_node) {
+                       dump_qset(qset, dev);
+               }
+       }
+}
+#else
+static inline void dump_pzl(struct whc *whc, const char *tag)
+{
+}
+#endif
+
+static void update_pzl_pointers(struct whc *whc, int period, u64 addr)
+{
+       switch (period) {
+       case 0:
+               whc_qset_set_link_ptr(&whc->pz_list[0], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[2], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[4], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[6], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[8], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[10], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[12], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[14], addr);
+               break;
+       case 1:
+               whc_qset_set_link_ptr(&whc->pz_list[1], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[5], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[9], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[13], addr);
+               break;
+       case 2:
+               whc_qset_set_link_ptr(&whc->pz_list[3], addr);
+               whc_qset_set_link_ptr(&whc->pz_list[11], addr);
+               break;
+       case 3:
+               whc_qset_set_link_ptr(&whc->pz_list[7], addr);
+               break;
+       case 4:
+               whc_qset_set_link_ptr(&whc->pz_list[15], addr);
+               break;
+       }
+}
+
+/*
+ * Return the 'period' to use for this qset.  The minimum interval for
+ * the endpoint is used so whatever urbs are submitted the device is
+ * polled often enough.
+ */
+static int qset_get_period(struct whc *whc, struct whc_qset *qset)
+{
+       uint8_t bInterval = qset->ep->desc.bInterval;
+
+       if (bInterval < 6)
+               bInterval = 6;
+       if (bInterval > 10)
+               bInterval = 10;
+       return bInterval - 6;
+}
+
+static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset)
+{
+       int period;
+
+       period = qset_get_period(whc, qset);
+
+       qset_clear(whc, qset);
+       list_move(&qset->list_node, &whc->periodic_list[period]);
+       qset->in_sw_list = true;
+}
+
+static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset)
+{
+       list_move(&qset->list_node, &whc->periodic_removed_list);
+       qset->in_hw_list = false;
+       qset->in_sw_list = false;
+}
+
+/**
+ * pzl_process_qset - process any recently inactivated or halted qTDs
+ * in a qset.
+ *
+ * After inactive qTDs are removed, new qTDs can be added if the
+ * urb queue still contains URBs.
+ *
+ * Returns the schedule updates required.
+ */
+static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset)
+{
+       enum whc_update update = 0;
+       uint32_t status = 0;
+
+       while (qset->ntds) {
+               struct whc_qtd *td;
+               int t;
+
+               t = qset->td_start;
+               td = &qset->qtd[qset->td_start];
+               status = le32_to_cpu(td->status);
+
+               /*
+                * Nothing to do with a still active qTD.
+                */
+               if (status & QTD_STS_ACTIVE)
+                       break;
+
+               if (status & QTD_STS_HALTED) {
+                       /* Ug, an error. */
+                       process_halted_qtd(whc, qset, td);
+                       goto done;
+               }
+
+               /* Mmm, a completed qTD. */
+               process_inactive_qtd(whc, qset, td);
+       }
+
+       update |= qset_add_qtds(whc, qset);
+
+done:
+       /*
+        * If there are no qTDs in this qset, remove it from the PZL.
+        */
+       if (qset->remove && qset->ntds == 0) {
+               pzl_qset_remove(whc, qset);
+               update |= WHC_UPDATE_REMOVED;
+       }
+
+       return update;
+}
+
+/**
+ * pzl_start - start the periodic schedule
+ * @whc: the WHCI host controller
+ *
+ * The PZL must be valid (e.g., all entries in the list should have
+ * the T bit set).
+ */
+void pzl_start(struct whc *whc)
+{
+       le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
+
+       whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN);
+       whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+                     WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED,
+                     1000, "start PZL");
+}
+
+/**
+ * pzl_stop - stop the periodic schedule
+ * @whc: the WHCI host controller
+ */
+void pzl_stop(struct whc *whc)
+{
+       whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0);
+       whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+                     WUSBSTS_PERIODIC_SCHED, 0,
+                     1000, "stop PZL");
+}
+
+void pzl_update(struct whc *whc, uint32_t wusbcmd)
+{
+       whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
+       wait_event(whc->periodic_list_wq,
+                  (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0);
+}
+
+static void update_pzl_hw_view(struct whc *whc)
+{
+       struct whc_qset *qset, *t;
+       int period;
+       u64 tmp_qh = 0;
+
+       for (period = 0; period < 5; period++) {
+               list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
+                       whc_qset_set_link_ptr(&qset->qh.link, tmp_qh);
+                       tmp_qh = qset->qset_dma;
+                       qset->in_hw_list = true;
+               }
+               update_pzl_pointers(whc, period, tmp_qh);
+       }
+}
+
+/**
+ * scan_periodic_work - scan the PZL for qsets to process.
+ *
+ * Process each qset in the PZL in turn and then signal the WHC that
+ * the PZL has been updated.
+ *
+ * Then start, stop or update the periodic schedule as required.
+ */
+void scan_periodic_work(struct work_struct *work)
+{
+       struct whc *whc = container_of(work, struct whc, periodic_work);
+       struct whc_qset *qset, *t;
+       enum whc_update update = 0;
+       int period;
+
+       spin_lock_irq(&whc->lock);
+
+       dump_pzl(whc, "before processing");
+
+       for (period = 4; period >= 0; period--) {
+               list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
+                       if (!qset->in_hw_list)
+                               update |= WHC_UPDATE_ADDED;
+                       update |= pzl_process_qset(whc, qset);
+               }
+       }
+
+       if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED))
+               update_pzl_hw_view(whc);
+
+       dump_pzl(whc, "after processing");
+
+       spin_unlock_irq(&whc->lock);
+
+       if (update) {
+               uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB;
+               if (update & WHC_UPDATE_REMOVED)
+                       wusbcmd |= WUSBCMD_PERIODIC_QSET_RM;
+               pzl_update(whc, wusbcmd);
+       }
+
+       /*
+        * Now that the PZL is updated, complete the removal of any
+        * removed qsets.
+        */
+       spin_lock(&whc->lock);
+
+       list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) {
+               qset_remove_complete(whc, qset);
+       }
+
+       spin_unlock(&whc->lock);
+}
+
+/**
+ * pzl_urb_enqueue - queue an URB onto the periodic list (PZL)
+ * @whc: the WHCI host controller
+ * @urb: the URB to enqueue
+ * @mem_flags: flags for any memory allocations
+ *
+ * The qset for the endpoint is obtained and the urb queued on to it.
+ *
+ * Work is scheduled to update the hardware's view of the PZL.
+ */
+int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
+{
+       struct whc_qset *qset;
+       int err;
+       unsigned long flags;
+
+       spin_lock_irqsave(&whc->lock, flags);
+
+       qset = get_qset(whc, urb, GFP_ATOMIC);
+       if (qset == NULL)
+               err = -ENOMEM;
+       else
+               err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
+       if (!err) {
+               usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
+               if (!qset->in_sw_list)
+                       qset_insert_in_sw_list(whc, qset);
+       }
+
+       spin_unlock_irqrestore(&whc->lock, flags);
+
+       if (!err)
+               queue_work(whc->workqueue, &whc->periodic_work);
+
+       return 0;
+}
+
+/**
+ * pzl_urb_dequeue - remove an URB (qset) from the periodic list
+ * @whc: the WHCI host controller
+ * @urb: the URB to dequeue
+ * @status: the current status of the URB
+ *
+ * URBs that do yet have qTDs can simply be removed from the software
+ * queue, otherwise the qset must be removed so the qTDs can be safely
+ * removed.
+ */
+int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
+{
+       struct whc_urb *wurb = urb->hcpriv;
+       struct whc_qset *qset = wurb->qset;
+       struct whc_std *std, *t;
+       int ret;
+       unsigned long flags;
+
+       spin_lock_irqsave(&whc->lock, flags);
+
+       ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
+       if (ret < 0)
+               goto out;
+
+       list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+               if (std->urb == urb)
+                       qset_free_std(whc, std);
+               else
+                       std->qtd = NULL; /* so this std is re-added when the qset is */
+       }
+
+       pzl_qset_remove(whc, qset);
+       wurb->status = status;
+       wurb->is_async = false;
+       queue_work(whc->workqueue, &wurb->dequeue_work);
+
+out:
+       spin_unlock_irqrestore(&whc->lock, flags);
+
+       return ret;
+}
+
+/**
+ * pzl_qset_delete - delete a qset from the PZL
+ */
+void pzl_qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+       qset->remove = 1;
+       queue_work(whc->workqueue, &whc->periodic_work);
+       qset_delete(whc, qset);
+}
+
+
+/**
+ * pzl_init - initialize the periodic zone list
+ * @whc: the WHCI host controller
+ */
+int pzl_init(struct whc *whc)
+{
+       int i;
+
+       whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16,
+                                         &whc->pz_list_dma, GFP_KERNEL);
+       if (whc->pz_list == NULL)
+               return -ENOMEM;
+
+       /* Set T bit on all elements in PZL. */
+       for (i = 0; i < 16; i++)
+               whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
+
+       le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
+
+       return 0;
+}
+
+/**
+ * pzl_clean_up - free PZL resources
+ * @whc: the WHCI host controller
+ *
+ * The PZL is stopped and empty.
+ */
+void pzl_clean_up(struct whc *whc)
+{
+       if (whc->pz_list)
+               dma_free_coherent(&whc->umc->dev,  sizeof(u64) * 16, whc->pz_list,
+                                 whc->pz_list_dma);
+}
diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c
new file mode 100644 (file)
index 0000000..0420037
--- /dev/null
@@ -0,0 +1,567 @@
+/*
+ * Wireless Host Controller (WHC) qset management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+#include <linux/usb.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+void dump_qset(struct whc_qset *qset, struct device *dev)
+{
+       struct whc_std *std;
+       struct urb *urb = NULL;
+       int i;
+
+       dev_dbg(dev, "qset %08x\n", (u32)qset->qset_dma);
+       dev_dbg(dev, "  -> %08x\n", (u32)qset->qh.link);
+       dev_dbg(dev, "  info: %08x %08x %08x\n",
+               qset->qh.info1, qset->qh.info2,  qset->qh.info3);
+       dev_dbg(dev, "  sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count);
+       dev_dbg(dev, "  TD: sts: %08x opts: %08x\n",
+               qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options);
+
+       for (i = 0; i < WHCI_QSET_TD_MAX; i++) {
+               dev_dbg(dev, "  %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n",
+                       i == qset->td_start ? 'S' : ' ',
+                       i == qset->td_end ? 'E' : ' ',
+                       i, qset->qtd[i].status, qset->qtd[i].options,
+                       (u32)qset->qtd[i].page_list_ptr);
+       }
+       dev_dbg(dev, "  ntds: %d\n", qset->ntds);
+       list_for_each_entry(std, &qset->stds, list_node) {
+               if (urb != std->urb) {
+                       urb = std->urb;
+                       dev_dbg(dev, "  urb %p transferred: %d bytes\n", urb,
+                               urb->actual_length);
+               }
+               if (std->qtd)
+                       dev_dbg(dev, "    sTD[%td]: %zu bytes @ %08x\n",
+                               std->qtd - &qset->qtd[0],
+                               std->len, std->num_pointers ?
+                               (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
+               else
+                       dev_dbg(dev, "    sTD[-]: %zd bytes @ %08x\n",
+                               std->len, std->num_pointers ?
+                               (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
+       }
+}
+
+struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags)
+{
+       struct whc_qset *qset;
+       dma_addr_t dma;
+
+       qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma);
+       if (qset == NULL)
+               return NULL;
+       memset(qset, 0, sizeof(struct whc_qset));
+
+       qset->qset_dma = dma;
+       qset->whc = whc;
+
+       INIT_LIST_HEAD(&qset->list_node);
+       INIT_LIST_HEAD(&qset->stds);
+
+       return qset;
+}
+
+/**
+ * qset_fill_qh - fill the static endpoint state in a qset's QHead
+ * @qset: the qset whose QH needs initializing with static endpoint
+ *        state
+ * @urb:  an urb for a transfer to this endpoint
+ */
+static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
+{
+       struct usb_device *usb_dev = urb->dev;
+       struct usb_wireless_ep_comp_descriptor *epcd;
+       bool is_out;
+
+       is_out = usb_pipeout(urb->pipe);
+
+       epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
+
+       if (epcd) {
+               qset->max_seq = epcd->bMaxSequence;
+               qset->max_burst = epcd->bMaxBurst;
+       } else {
+               qset->max_seq = 2;
+               qset->max_burst = 1;
+       }
+
+       qset->qh.info1 = cpu_to_le32(
+               QH_INFO1_EP(usb_pipeendpoint(urb->pipe))
+               | (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN)
+               | usb_pipe_to_qh_type(urb->pipe)
+               | QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum))
+               | QH_INFO1_MAX_PKT_LEN(usb_maxpacket(urb->dev, urb->pipe, is_out))
+               );
+       qset->qh.info2 = cpu_to_le32(
+               QH_INFO2_BURST(qset->max_burst)
+               | QH_INFO2_DBP(0)
+               | QH_INFO2_MAX_COUNT(3)
+               | QH_INFO2_MAX_RETRY(3)
+               | QH_INFO2_MAX_SEQ(qset->max_seq - 1)
+               );
+       /* FIXME: where can we obtain these Tx parameters from?  Why
+        * doesn't the chip know what Tx power to use? It knows the Rx
+        * strength and can presumably guess the Tx power required
+        * from that? */
+       qset->qh.info3 = cpu_to_le32(
+               QH_INFO3_TX_RATE_53_3
+               | QH_INFO3_TX_PWR(0) /* 0 == max power */
+               );
+}
+
+/**
+ * qset_clear - clear fields in a qset so it may be reinserted into a
+ * schedule
+ */
+void qset_clear(struct whc *whc, struct whc_qset *qset)
+{
+       qset->td_start = qset->td_end = qset->ntds = 0;
+       qset->remove = 0;
+
+       qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T);
+       qset->qh.status = cpu_to_le16(QH_STATUS_ICUR(qset->td_start));
+       qset->qh.err_count = 0;
+       qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
+       qset->qh.scratch[0] = 0;
+       qset->qh.scratch[1] = 0;
+       qset->qh.scratch[2] = 0;
+
+       memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay));
+
+       init_completion(&qset->remove_complete);
+}
+
+/**
+ * get_qset - get the qset for an async endpoint
+ *
+ * A new qset is created if one does not already exist.
+ */
+struct whc_qset *get_qset(struct whc *whc, struct urb *urb,
+                                gfp_t mem_flags)
+{
+       struct whc_qset *qset;
+
+       qset = urb->ep->hcpriv;
+       if (qset == NULL) {
+               qset = qset_alloc(whc, mem_flags);
+               if (qset == NULL)
+                       return NULL;
+
+               qset->ep = urb->ep;
+               urb->ep->hcpriv = qset;
+               qset_fill_qh(qset, urb);
+       }
+       return qset;
+}
+
+void qset_remove_complete(struct whc *whc, struct whc_qset *qset)
+{
+       list_del_init(&qset->list_node);
+       complete(&qset->remove_complete);
+}
+
+/**
+ * qset_add_qtds - add qTDs for an URB to a qset
+ *
+ * Returns true if the list (ASL/PZL) must be updated because (for a
+ * WHCI 0.95 controller) an activated qTD was pointed to be iCur.
+ */
+enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset)
+{
+       struct whc_std *std;
+       enum whc_update update = 0;
+
+       list_for_each_entry(std, &qset->stds, list_node) {
+               struct whc_qtd *qtd;
+               uint32_t status;
+
+               if (qset->ntds >= WHCI_QSET_TD_MAX
+                   || (qset->pause_after_urb && std->urb != qset->pause_after_urb))
+                       break;
+
+               if (std->qtd)
+                       continue; /* already has a qTD */
+
+               qtd = std->qtd = &qset->qtd[qset->td_end];
+
+               /* Fill in setup bytes for control transfers. */
+               if (usb_pipecontrol(std->urb->pipe))
+                       memcpy(qtd->setup, std->urb->setup_packet, 8);
+
+               status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len);
+
+               if (whc_std_last(std) && usb_pipeout(std->urb->pipe))
+                       status |= QTD_STS_LAST_PKT;
+
+               /*
+                * For an IN transfer the iAlt field should be set so
+                * the h/w will automatically advance to the next
+                * transfer. However, if there are 8 or more TDs
+                * remaining in this transfer then iAlt cannot be set
+                * as it could point to somewhere in this transfer.
+                */
+               if (std->ntds_remaining < WHCI_QSET_TD_MAX) {
+                       int ialt;
+                       ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX;
+                       status |= QTD_STS_IALT(ialt);
+               } else if (usb_pipein(std->urb->pipe))
+                       qset->pause_after_urb = std->urb;
+
+               if (std->num_pointers)
+                       qtd->options = cpu_to_le32(QTD_OPT_IOC);
+               else
+                       qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL);
+               qtd->page_list_ptr = cpu_to_le64(std->dma_addr);
+
+               qtd->status = cpu_to_le32(status);
+
+               if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end)
+                       update = WHC_UPDATE_UPDATED;
+
+               if (++qset->td_end >= WHCI_QSET_TD_MAX)
+                       qset->td_end = 0;
+               qset->ntds++;
+       }
+
+       return update;
+}
+
+/**
+ * qset_remove_qtd - remove the first qTD from a qset.
+ *
+ * The qTD might be still active (if it's part of a IN URB that
+ * resulted in a short read) so ensure it's deactivated.
+ */
+static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
+{
+       qset->qtd[qset->td_start].status = 0;
+
+       if (++qset->td_start >= WHCI_QSET_TD_MAX)
+               qset->td_start = 0;
+       qset->ntds--;
+}
+
+/**
+ * qset_free_std - remove an sTD and free it.
+ * @whc: the WHCI host controller
+ * @std: the sTD to remove and free.
+ */
+void qset_free_std(struct whc *whc, struct whc_std *std)
+{
+       list_del(&std->list_node);
+       if (std->num_pointers) {
+               dma_unmap_single(whc->wusbhc.dev, std->dma_addr,
+                                std->num_pointers * sizeof(struct whc_page_list_entry),
+                                DMA_TO_DEVICE);
+               kfree(std->pl_virt);
+       }
+
+       kfree(std);
+}
+
+/**
+ * qset_remove_qtds - remove an URB's qTDs (and sTDs).
+ */
+static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset,
+                            struct urb *urb)
+{
+       struct whc_std *std, *t;
+
+       list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+               if (std->urb != urb)
+                       break;
+               if (std->qtd != NULL)
+                       qset_remove_qtd(whc, qset);
+               qset_free_std(whc, std);
+       }
+}
+
+/**
+ * qset_free_stds - free any remaining sTDs for an URB.
+ */
+static void qset_free_stds(struct whc_qset *qset, struct urb *urb)
+{
+       struct whc_std *std, *t;
+
+       list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+               if (std->urb == urb)
+                       qset_free_std(qset->whc, std);
+       }
+}
+
+static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags)
+{
+       dma_addr_t dma_addr = std->dma_addr;
+       dma_addr_t sp, ep;
+       size_t std_len = std->len;
+       size_t pl_len;
+       int p;
+
+       sp = ALIGN(dma_addr, WHCI_PAGE_SIZE);
+       ep = dma_addr + std_len;
+       std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
+
+       pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
+       std->pl_virt = kmalloc(pl_len, mem_flags);
+       if (std->pl_virt == NULL)
+               return -ENOMEM;
+       std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE);
+
+       for (p = 0; p < std->num_pointers; p++) {
+               std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
+               dma_addr = ALIGN(dma_addr + WHCI_PAGE_SIZE, WHCI_PAGE_SIZE);
+       }
+
+       return 0;
+}
+
+/**
+ * urb_dequeue_work - executes asl/pzl update and gives back the urb to the system.
+ */
+static void urb_dequeue_work(struct work_struct *work)
+{
+       struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work);
+       struct whc_qset *qset = wurb->qset;
+       struct whc *whc = qset->whc;
+       unsigned long flags;
+
+       if (wurb->is_async == true)
+               asl_update(whc, WUSBCMD_ASYNC_UPDATED
+                          | WUSBCMD_ASYNC_SYNCED_DB
+                          | WUSBCMD_ASYNC_QSET_RM);
+       else
+               pzl_update(whc, WUSBCMD_PERIODIC_UPDATED
+                          | WUSBCMD_PERIODIC_SYNCED_DB
+                          | WUSBCMD_PERIODIC_QSET_RM);
+
+       spin_lock_irqsave(&whc->lock, flags);
+       qset_remove_urb(whc, qset, wurb->urb, wurb->status);
+       spin_unlock_irqrestore(&whc->lock, flags);
+}
+
+/**
+ * qset_add_urb - add an urb to the qset's queue.
+ *
+ * The URB is chopped into sTDs, one for each qTD that will required.
+ * At least one qTD (and sTD) is required even if the transfer has no
+ * data (e.g., for some control transfers).
+ */
+int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
+       gfp_t mem_flags)
+{
+       struct whc_urb *wurb;
+       int remaining = urb->transfer_buffer_length;
+       u64 transfer_dma = urb->transfer_dma;
+       int ntds_remaining;
+
+       ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE);
+       if (ntds_remaining == 0)
+               ntds_remaining = 1;
+
+       wurb = kzalloc(sizeof(struct whc_urb), mem_flags);
+       if (wurb == NULL)
+               goto err_no_mem;
+       urb->hcpriv = wurb;
+       wurb->qset = qset;
+       wurb->urb = urb;
+       INIT_WORK(&wurb->dequeue_work, urb_dequeue_work);
+
+       while (ntds_remaining) {
+               struct whc_std *std;
+               size_t std_len;
+
+               std = kmalloc(sizeof(struct whc_std), mem_flags);
+               if (std == NULL)
+                       goto err_no_mem;
+
+               std_len = remaining;
+               if (std_len > QTD_MAX_XFER_SIZE)
+                       std_len = QTD_MAX_XFER_SIZE;
+
+               std->urb = urb;
+               std->dma_addr = transfer_dma;
+               std->len = std_len;
+               std->ntds_remaining = ntds_remaining;
+               std->qtd = NULL;
+
+               INIT_LIST_HEAD(&std->list_node);
+               list_add_tail(&std->list_node, &qset->stds);
+
+               if (std_len > WHCI_PAGE_SIZE) {
+                       if (qset_fill_page_list(whc, std, mem_flags) < 0)
+                               goto err_no_mem;
+               } else
+                       std->num_pointers = 0;
+
+               ntds_remaining--;
+               remaining -= std_len;
+               transfer_dma += std_len;
+       }
+
+       return 0;
+
+err_no_mem:
+       qset_free_stds(qset, urb);
+       return -ENOMEM;
+}
+
+/**
+ * qset_remove_urb - remove an URB from the urb queue.
+ *
+ * The URB is returned to the USB subsystem.
+ */
+void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
+                           struct urb *urb, int status)
+{
+       struct wusbhc *wusbhc = &whc->wusbhc;
+       struct whc_urb *wurb = urb->hcpriv;
+
+       usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb);
+       /* Drop the lock as urb->complete() may enqueue another urb. */
+       spin_unlock(&whc->lock);
+       wusbhc_giveback_urb(wusbhc, urb, status);
+       spin_lock(&whc->lock);
+
+       kfree(wurb);
+}
+
+/**
+ * get_urb_status_from_qtd - get the completed urb status from qTD status
+ * @urb:    completed urb
+ * @status: qTD status
+ */
+static int get_urb_status_from_qtd(struct urb *urb, u32 status)
+{
+       if (status & QTD_STS_HALTED) {
+               if (status & QTD_STS_DBE)
+                       return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM;
+               else if (status & QTD_STS_BABBLE)
+                       return -EOVERFLOW;
+               else if (status & QTD_STS_RCE)
+                       return -ETIME;
+               return -EPIPE;
+       }
+       if (usb_pipein(urb->pipe)
+           && (urb->transfer_flags & URB_SHORT_NOT_OK)
+           && urb->actual_length < urb->transfer_buffer_length)
+               return -EREMOTEIO;
+       return 0;
+}
+
+/**
+ * process_inactive_qtd - process an inactive (but not halted) qTD.
+ *
+ * Update the urb with the transfer bytes from the qTD, if the urb is
+ * completely transfered or (in the case of an IN only) the LPF is
+ * set, then the transfer is complete and the urb should be returned
+ * to the system.
+ */
+void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
+                                struct whc_qtd *qtd)
+{
+       struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
+       struct urb *urb = std->urb;
+       uint32_t status;
+       bool complete;
+
+       status = le32_to_cpu(qtd->status);
+
+       urb->actual_length += std->len - QTD_STS_TO_LEN(status);
+
+       if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT))
+               complete = true;
+       else
+               complete = whc_std_last(std);
+
+       qset_remove_qtd(whc, qset);
+       qset_free_std(whc, std);
+
+       /*
+        * Transfers for this URB are complete?  Then return it to the
+        * USB subsystem.
+        */
+       if (complete) {
+               qset_remove_qtds(whc, qset, urb);
+               qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status));
+
+               /*
+                * If iAlt isn't valid then the hardware didn't
+                * advance iCur. Adjust the start and end pointers to
+                * match iCur.
+                */
+               if (!(status & QTD_STS_IALT_VALID))
+                       qset->td_start = qset->td_end
+                               = QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status));
+               qset->pause_after_urb = NULL;
+       }
+}
+
+/**
+ * process_halted_qtd - process a qset with a halted qtd
+ *
+ * Remove all the qTDs for the failed URB and return the failed URB to
+ * the USB subsystem.  Then remove all other qTDs so the qset can be
+ * removed.
+ *
+ * FIXME: this is the point where rate adaptation can be done.  If a
+ * transfer failed because it exceeded the maximum number of retries
+ * then it could be reactivated with a slower rate without having to
+ * remove the qset.
+ */
+void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
+                              struct whc_qtd *qtd)
+{
+       struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
+       struct urb *urb = std->urb;
+       int urb_status;
+
+       urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status));
+
+       qset_remove_qtds(whc, qset, urb);
+       qset_remove_urb(whc, qset, urb, urb_status);
+
+       list_for_each_entry(std, &qset->stds, list_node) {
+               if (qset->ntds == 0)
+                       break;
+               qset_remove_qtd(whc, qset);
+               std->qtd = NULL;
+       }
+
+       qset->remove = 1;
+}
+
+void qset_free(struct whc *whc, struct whc_qset *qset)
+{
+       dma_pool_free(whc->qset_pool, qset, qset->qset_dma);
+}
+
+/**
+ * qset_delete - wait for a qset to be unused, then free it.
+ */
+void qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+       wait_for_completion(&qset->remove_complete);
+       qset_free(whc, qset);
+}
diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h
new file mode 100644 (file)
index 0000000..1d2a53b
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Wireless Host Controller (WHC) private header.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#ifndef __WHCD_H
+#define __WHCD_H
+
+#include <linux/uwb/whci.h>
+#include <linux/workqueue.h>
+
+#include "whci-hc.h"
+
+/* Generic command timeout. */
+#define WHC_GENCMD_TIMEOUT_MS 100
+
+
+struct whc {
+       struct wusbhc wusbhc;
+       struct umc_dev *umc;
+
+       resource_size_t base_phys;
+       void __iomem *base;
+       int irq;
+
+       u8 n_devices;
+       u8 n_keys;
+       u8 n_mmc_ies;
+
+       u64 *pz_list;
+       struct dn_buf_entry *dn_buf;
+       struct di_buf_entry *di_buf;
+       dma_addr_t pz_list_dma;
+       dma_addr_t dn_buf_dma;
+       dma_addr_t di_buf_dma;
+
+       spinlock_t   lock;
+       struct mutex mutex;
+
+       void *            gen_cmd_buf;
+       dma_addr_t        gen_cmd_buf_dma;
+       wait_queue_head_t cmd_wq;
+
+       struct workqueue_struct *workqueue;
+       struct work_struct       dn_work;
+
+       struct dma_pool *qset_pool;
+
+       struct list_head async_list;
+       struct list_head async_removed_list;
+       wait_queue_head_t async_list_wq;
+       struct work_struct async_work;
+
+       struct list_head periodic_list[5];
+       struct list_head periodic_removed_list;
+       wait_queue_head_t periodic_list_wq;
+       struct work_struct periodic_work;
+};
+
+#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc))
+
+/**
+ * struct whc_std - a software TD.
+ * @urb: the URB this sTD is for.
+ * @offset: start of the URB's data for this TD.
+ * @len: the length of data in the associated TD.
+ * @ntds_remaining: number of TDs (starting from this one) in this transfer.
+ *
+ * Queued URBs may require more TDs than are available in a qset so we
+ * use a list of these "software TDs" (sTDs) to hold per-TD data.
+ */
+struct whc_std {
+       struct urb *urb;
+       size_t len;
+       int    ntds_remaining;
+       struct whc_qtd *qtd;
+
+       struct list_head list_node;
+       int num_pointers;
+       dma_addr_t dma_addr;
+       struct whc_page_list_entry *pl_virt;
+};
+
+/**
+ * struct whc_urb - per URB host controller structure.
+ * @urb: the URB this struct is for.
+ * @qset: the qset associated to the URB.
+ * @dequeue_work: the work to remove the URB when dequeued.
+ * @is_async: the URB belongs to async sheduler or not.
+ * @status: the status to be returned when calling wusbhc_giveback_urb.
+ */
+struct whc_urb {
+       struct urb *urb;
+       struct whc_qset *qset;
+       struct work_struct dequeue_work;
+       bool is_async;
+       int status;
+};
+
+/**
+ * whc_std_last - is this sTD the URB's last?
+ * @std: the sTD to check.
+ */
+static inline bool whc_std_last(struct whc_std *std)
+{
+       return std->ntds_remaining <= 1;
+}
+
+enum whc_update {
+       WHC_UPDATE_ADDED   = 0x01,
+       WHC_UPDATE_REMOVED = 0x02,
+       WHC_UPDATE_UPDATED = 0x04,
+};
+
+/* init.c */
+int whc_init(struct whc *whc);
+void whc_clean_up(struct whc *whc);
+
+/* hw.c */
+void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val);
+int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len);
+
+/* wusb.c */
+int whc_wusbhc_start(struct wusbhc *wusbhc);
+void whc_wusbhc_stop(struct wusbhc *wusbhc);
+int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+                 u8 handle, struct wuie_hdr *wuie);
+int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle);
+int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm);
+int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev);
+int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots);
+int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+               const void *ptk, size_t key_size);
+int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+               const void *gtk, size_t key_size);
+int whc_set_cluster_id(struct whc *whc, u8 bcid);
+
+/* int.c */
+irqreturn_t whc_int_handler(struct usb_hcd *hcd);
+void whc_dn_work(struct work_struct *work);
+
+/* asl.c */
+void asl_start(struct whc *whc);
+void asl_stop(struct whc *whc);
+int  asl_init(struct whc *whc);
+void asl_clean_up(struct whc *whc);
+int  asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+int  asl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
+void asl_qset_delete(struct whc *whc, struct whc_qset *qset);
+void scan_async_work(struct work_struct *work);
+
+/* pzl.c */
+int  pzl_init(struct whc *whc);
+void pzl_clean_up(struct whc *whc);
+void pzl_start(struct whc *whc);
+void pzl_stop(struct whc *whc);
+int  pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+int  pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
+void pzl_qset_delete(struct whc *whc, struct whc_qset *qset);
+void scan_periodic_work(struct work_struct *work);
+
+/* qset.c */
+struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags);
+void qset_free(struct whc *whc, struct whc_qset *qset);
+struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+void qset_delete(struct whc *whc, struct whc_qset *qset);
+void qset_clear(struct whc *whc, struct whc_qset *qset);
+int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
+                gfp_t mem_flags);
+void qset_free_std(struct whc *whc, struct whc_std *std);
+void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
+                           struct urb *urb, int status);
+void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
+                              struct whc_qtd *qtd);
+void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
+                                struct whc_qtd *qtd);
+enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset);
+void qset_remove_complete(struct whc *whc, struct whc_qset *qset);
+void dump_qset(struct whc_qset *qset, struct device *dev);
+void pzl_update(struct whc *whc, uint32_t wusbcmd);
+void asl_update(struct whc *whc, uint32_t wusbcmd);
+
+#endif /* #ifndef __WHCD_H */
diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h
new file mode 100644 (file)
index 0000000..bff1eb7
--- /dev/null
@@ -0,0 +1,416 @@
+/*
+ * Wireless Host Controller (WHC) data structures.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#ifndef _WHCI_WHCI_HC_H
+#define _WHCI_WHCI_HC_H
+
+#include <linux/list.h>
+
+/**
+ * WHCI_PAGE_SIZE - page size use by WHCI
+ *
+ * WHCI assumes that host system uses pages of 4096 octets.
+ */
+#define WHCI_PAGE_SIZE 4096
+
+
+/**
+ * QTD_MAX_TXFER_SIZE - max number of bytes to transfer with a single
+ * qtd.
+ *
+ * This is 2^20 - 1.
+ */
+#define QTD_MAX_XFER_SIZE 1048575
+
+
+/**
+ * struct whc_qtd - Queue Element Transfer Descriptors (qTD)
+ *
+ * This describes the data for a bulk, control or interrupt transfer.
+ *
+ * [WHCI] section 3.2.4
+ */
+struct whc_qtd {
+       __le32 status; /*< remaining transfer len and transfer status */
+       __le32 options;
+       __le64 page_list_ptr; /*< physical pointer to data buffer page list*/
+       __u8   setup[8];      /*< setup data for control transfers */
+} __attribute__((packed));
+
+#define QTD_STS_ACTIVE     (1 << 31)  /* enable execution of transaction */
+#define QTD_STS_HALTED     (1 << 30)  /* transfer halted */
+#define QTD_STS_DBE        (1 << 29)  /* data buffer error */
+#define QTD_STS_BABBLE     (1 << 28)  /* babble detected */
+#define QTD_STS_RCE        (1 << 27)  /* retry count exceeded */
+#define QTD_STS_LAST_PKT   (1 << 26)  /* set Last Packet Flag in WUSB header */
+#define QTD_STS_INACTIVE   (1 << 25)  /* queue set is marked inactive */
+#define QTD_STS_IALT_VALID (1 << 23)                          /* iAlt field is valid */
+#define QTD_STS_IALT(i)    (QTD_STS_IALT_VALID | ((i) << 20)) /* iAlt field */
+#define QTD_STS_LEN(l)     ((l) << 0) /* transfer length */
+#define QTD_STS_TO_LEN(s)  ((s) & 0x000fffff)
+
+#define QTD_OPT_IOC      (1 << 1) /* page_list_ptr points to buffer directly */
+#define QTD_OPT_SMALL    (1 << 0) /* interrupt on complete */
+
+/**
+ * struct whc_itd - Isochronous Queue Element Transfer Descriptors (iTD)
+ *
+ * This describes the data and other parameters for an isochronous
+ * transfer.
+ *
+ * [WHCI] section 3.2.5
+ */
+struct whc_itd {
+       __le16 presentation_time;    /*< presentation time for OUT transfers */
+       __u8   num_segments;         /*< number of data segments in segment list */
+       __u8   status;               /*< command execution status */
+       __le32 options;              /*< misc transfer options */
+       __le64 page_list_ptr;        /*< physical pointer to data buffer page list */
+       __le64 seg_list_ptr;         /*< physical pointer to segment list */
+} __attribute__((packed));
+
+#define ITD_STS_ACTIVE   (1 << 7) /* enable execution of transaction */
+#define ITD_STS_DBE      (1 << 5) /* data buffer error */
+#define ITD_STS_BABBLE   (1 << 4) /* babble detected */
+#define ITD_STS_INACTIVE (1 << 1) /* queue set is marked inactive */
+
+#define ITD_OPT_IOC      (1 << 1) /* interrupt on complete */
+#define ITD_OPT_SMALL    (1 << 0) /* page_list_ptr points to buffer directly */
+
+/**
+ * Page list entry.
+ *
+ * A TD's page list must contain sufficient page list entries for the
+ * total data length in the TD.
+ *
+ * [WHCI] section 3.2.4.3
+ */
+struct whc_page_list_entry {
+       __le64 buf_ptr; /*< physical pointer to buffer */
+} __attribute__((packed));
+
+/**
+ * struct whc_seg_list_entry - Segment list entry.
+ *
+ * Describes a portion of the data buffer described in the containing
+ * qTD's page list.
+ *
+ * seg_ptr = qtd->page_list_ptr[qtd->seg_list_ptr[seg].idx].buf_ptr
+ *           + qtd->seg_list_ptr[seg].offset;
+ *
+ * Segments can't cross page boundries.
+ *
+ * [WHCI] section 3.2.5.5
+ */
+struct whc_seg_list_entry {
+       __le16 len;    /*< segment length */
+       __u8   idx;    /*< index into page list */
+       __u8   status; /*< segment status */
+       __le16 offset; /*< 12 bit offset into page */
+} __attribute__((packed));
+
+/**
+ * struct whc_qhead - endpoint and status information for a qset.
+ *
+ * [WHCI] section 3.2.6
+ */
+struct whc_qhead {
+       __le64 link; /*< next qset in list */
+       __le32 info1;
+       __le32 info2;
+       __le32 info3;
+       __le16 status;
+       __le16 err_count;  /*< transaction error count */
+       __le32 cur_window;
+       __le32 scratch[3]; /*< h/w scratch area */
+       union {
+               struct whc_qtd qtd;
+               struct whc_itd itd;
+       } overlay;
+} __attribute__((packed));
+
+#define QH_LINK_PTR_MASK (~0x03Full)
+#define QH_LINK_PTR(ptr) ((ptr) & QH_LINK_PTR_MASK)
+#define QH_LINK_IQS      (1 << 4) /* isochronous queue set */
+#define QH_LINK_NTDS(n)  (((n) - 1) << 1) /* number of TDs in queue set */
+#define QH_LINK_T        (1 << 0) /* last queue set in periodic schedule list */
+
+#define QH_INFO1_EP(e)           ((e) << 0)  /* endpoint number */
+#define QH_INFO1_DIR_IN          (1 << 4)    /* IN transfer */
+#define QH_INFO1_DIR_OUT         (0 << 4)    /* OUT transfer */
+#define QH_INFO1_TR_TYPE_CTRL    (0x0 << 5)  /* control transfer */
+#define QH_INFO1_TR_TYPE_ISOC    (0x1 << 5)  /* isochronous transfer */
+#define QH_INFO1_TR_TYPE_BULK    (0x2 << 5)  /* bulk transfer */
+#define QH_INFO1_TR_TYPE_INT     (0x3 << 5)  /* interrupt */
+#define QH_INFO1_TR_TYPE_LP_INT  (0x7 << 5)  /* low power interrupt */
+#define QH_INFO1_DEV_INFO_IDX(i) ((i) << 8)  /* index into device info buffer */
+#define QH_INFO1_SET_INACTIVE    (1 << 15)   /* set inactive after transfer */
+#define QH_INFO1_MAX_PKT_LEN(l)  ((l) << 16) /* maximum packet length */
+
+#define QH_INFO2_BURST(b)        ((b) << 0)  /* maximum burst length */
+#define QH_INFO2_DBP(p)          ((p) << 5)  /* data burst policy (see [WUSB] table 5-7) */
+#define QH_INFO2_MAX_COUNT(c)    ((c) << 8)  /* max isoc/int pkts per zone */
+#define QH_INFO2_RQS             (1 << 15)   /* reactivate queue set */
+#define QH_INFO2_MAX_RETRY(r)    ((r) << 16) /* maximum transaction retries */
+#define QH_INFO2_MAX_SEQ(s)      ((s) << 20) /* maximum sequence number */
+#define QH_INFO3_MAX_DELAY(d)    ((d) << 0)  /* maximum stream delay in 125 us units (isoc only) */
+#define QH_INFO3_INTERVAL(i)     ((i) << 16) /* segment interval in 125 us units (isoc only) */
+
+#define QH_INFO3_TX_RATE_53_3    (0 << 24)
+#define QH_INFO3_TX_RATE_80      (1 << 24)
+#define QH_INFO3_TX_RATE_106_7   (2 << 24)
+#define QH_INFO3_TX_RATE_160     (3 << 24)
+#define QH_INFO3_TX_RATE_200     (4 << 24)
+#define QH_INFO3_TX_RATE_320     (5 << 24)
+#define QH_INFO3_TX_RATE_400     (6 << 24)
+#define QH_INFO3_TX_RATE_480     (7 << 24)
+#define QH_INFO3_TX_PWR(p)       ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */
+
+#define QH_STATUS_FLOW_CTRL      (1 << 15)
+#define QH_STATUS_ICUR(i)        ((i) << 5)
+#define QH_STATUS_TO_ICUR(s)     (((s) >> 5) & 0x7)
+
+/**
+ * usb_pipe_to_qh_type - USB core pipe type to QH transfer type
+ *
+ * Returns the QH type field for a USB core pipe type.
+ */
+static inline unsigned usb_pipe_to_qh_type(unsigned pipe)
+{
+       static const unsigned type[] = {
+               [PIPE_ISOCHRONOUS] = QH_INFO1_TR_TYPE_ISOC,
+               [PIPE_INTERRUPT]   = QH_INFO1_TR_TYPE_INT,
+               [PIPE_CONTROL]     = QH_INFO1_TR_TYPE_CTRL,
+               [PIPE_BULK]        = QH_INFO1_TR_TYPE_BULK,
+       };
+       return type[usb_pipetype(pipe)];
+}
+
+/**
+ * Maxiumum number of TDs in a qset.
+ */
+#define WHCI_QSET_TD_MAX 8
+
+/**
+ * struct whc_qset - WUSB data transfers to a specific endpoint
+ * @qh: the QHead of this qset
+ * @qtd: up to 8 qTDs (for qsets for control, bulk and interrupt
+ * transfers)
+ * @itd: up to 8 iTDs (for qsets for isochronous transfers)
+ * @qset_dma: DMA address for this qset
+ * @whc: WHCI HC this qset is for
+ * @ep: endpoint
+ * @stds: list of sTDs queued to this qset
+ * @ntds: number of qTDs queued (not necessarily the same as nTDs
+ * field in the QH)
+ * @td_start: index of the first qTD in the list
+ * @td_end: index of next free qTD in the list (provided
+ *          ntds < WHCI_QSET_TD_MAX)
+ *
+ * Queue Sets (qsets) are added to the asynchronous schedule list
+ * (ASL) or the periodic zone list (PZL).
+ *
+ * qsets may contain up to 8 TDs (either qTDs or iTDs as appropriate).
+ * Each TD may refer to at most 1 MiB of data. If a single transfer
+ * has > 8MiB of data, TDs can be reused as they are completed since
+ * the TD list is used as a circular buffer.  Similarly, several
+ * (smaller) transfers may be queued in a qset.
+ *
+ * WHCI controllers may cache portions of the qsets in the ASL and
+ * PZL, requiring the WHCD to inform the WHC that the lists have been
+ * updated (fields changed or qsets inserted or removed).  For safe
+ * insertion and removal of qsets from the lists the schedule must be
+ * stopped to avoid races in updating the QH link pointers.
+ *
+ * Since the HC is free to execute qsets in any order, all transfers
+ * to an endpoint should use the same qset to ensure transfers are
+ * executed in the order they're submitted.
+ *
+ * [WHCI] section 3.2.3
+ */
+struct whc_qset {
+       struct whc_qhead qh;
+       union {
+               struct whc_qtd qtd[WHCI_QSET_TD_MAX];
+               struct whc_itd itd[WHCI_QSET_TD_MAX];
+       };
+
+       /* private data for WHCD */
+       dma_addr_t qset_dma;
+       struct whc *whc;
+       struct usb_host_endpoint *ep;
+       struct list_head stds;
+       int ntds;
+       int td_start;
+       int td_end;
+       struct list_head list_node;
+       unsigned in_sw_list:1;
+       unsigned in_hw_list:1;
+       unsigned remove:1;
+       struct urb *pause_after_urb;
+       struct completion remove_complete;
+       int max_burst;
+       int max_seq;
+};
+
+static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target)
+{
+       if (target)
+               *ptr = (*ptr & ~(QH_LINK_PTR_MASK | QH_LINK_T)) | QH_LINK_PTR(target);
+       else
+               *ptr = QH_LINK_T;
+}
+
+/**
+ * struct di_buf_entry - Device Information (DI) buffer entry.
+ *
+ * There's one of these per connected device.
+ */
+struct di_buf_entry {
+       __le32 availability_info[8]; /*< MAS availability information, one MAS per bit */
+       __le32 addr_sec_info;        /*< addressing and security info */
+       __le32 reserved[7];
+} __attribute__((packed));
+
+#define WHC_DI_SECURE           (1 << 31)
+#define WHC_DI_DISABLE          (1 << 30)
+#define WHC_DI_KEY_IDX(k)       ((k) << 8)
+#define WHC_DI_KEY_IDX_MASK     0x0000ff00
+#define WHC_DI_DEV_ADDR(a)      ((a) << 0)
+#define WHC_DI_DEV_ADDR_MASK    0x000000ff
+
+/**
+ * struct dn_buf_entry - Device Notification (DN) buffer entry.
+ *
+ * [WHCI] section 3.2.8
+ */
+struct dn_buf_entry {
+       __u8   msg_size;    /*< number of octets of valid DN data */
+       __u8   reserved1;
+       __u8   src_addr;    /*< source address */
+       __u8   status;      /*< buffer entry status */
+       __le32 tkid;        /*< TKID for source device, valid if secure bit is set */
+       __u8   dn_data[56]; /*< up to 56 octets of DN data */
+} __attribute__((packed));
+
+#define WHC_DN_STATUS_VALID  (1 << 7) /* buffer entry is valid */
+#define WHC_DN_STATUS_SECURE (1 << 6) /* notification received using secure frame */
+
+#define WHC_N_DN_ENTRIES (4096 / sizeof(struct dn_buf_entry))
+
+/* The Add MMC IE WUSB Generic Command may take up to 256 bytes of
+   data. [WHCI] section 2.4.7. */
+#define WHC_GEN_CMD_DATA_LEN 256
+
+/*
+ * HC registers.
+ *
+ * [WHCI] section 2.4
+ */
+
+#define WHCIVERSION          0x00
+
+#define WHCSPARAMS           0x04
+#  define WHCSPARAMS_TO_N_MMC_IES(p) (((p) >> 16) & 0xff)
+#  define WHCSPARAMS_TO_N_KEYS(p)    (((p) >> 8) & 0xff)
+#  define WHCSPARAMS_TO_N_DEVICES(p) (((p) >> 0) & 0x7f)
+
+#define WUSBCMD              0x08
+#  define WUSBCMD_BCID(b)            ((b) << 16)
+#  define WUSBCMD_BCID_MASK          (0xff << 16)
+#  define WUSBCMD_ASYNC_QSET_RM      (1 << 12)
+#  define WUSBCMD_PERIODIC_QSET_RM   (1 << 11)
+#  define WUSBCMD_WUSBSI(s)          ((s) << 8)
+#  define WUSBCMD_WUSBSI_MASK        (0x7 << 8)
+#  define WUSBCMD_ASYNC_SYNCED_DB    (1 << 7)
+#  define WUSBCMD_PERIODIC_SYNCED_DB (1 << 6)
+#  define WUSBCMD_ASYNC_UPDATED      (1 << 5)
+#  define WUSBCMD_PERIODIC_UPDATED   (1 << 4)
+#  define WUSBCMD_ASYNC_EN           (1 << 3)
+#  define WUSBCMD_PERIODIC_EN        (1 << 2)
+#  define WUSBCMD_WHCRESET           (1 << 1)
+#  define WUSBCMD_RUN                (1 << 0)
+
+#define WUSBSTS              0x0c
+#  define WUSBSTS_ASYNC_SCHED             (1 << 15)
+#  define WUSBSTS_PERIODIC_SCHED          (1 << 14)
+#  define WUSBSTS_DNTS_SCHED              (1 << 13)
+#  define WUSBSTS_HCHALTED                (1 << 12)
+#  define WUSBSTS_GEN_CMD_DONE            (1 << 9)
+#  define WUSBSTS_CHAN_TIME_ROLLOVER      (1 << 8)
+#  define WUSBSTS_DNTS_OVERFLOW           (1 << 7)
+#  define WUSBSTS_BPST_ADJUSTMENT_CHANGED (1 << 6)
+#  define WUSBSTS_HOST_ERR                (1 << 5)
+#  define WUSBSTS_ASYNC_SCHED_SYNCED      (1 << 4)
+#  define WUSBSTS_PERIODIC_SCHED_SYNCED   (1 << 3)
+#  define WUSBSTS_DNTS_INT                (1 << 2)
+#  define WUSBSTS_ERR_INT                 (1 << 1)
+#  define WUSBSTS_INT                     (1 << 0)
+#  define WUSBSTS_INT_MASK                0x3ff
+
+#define WUSBINTR             0x10
+#  define WUSBINTR_GEN_CMD_DONE             (1 << 9)
+#  define WUSBINTR_CHAN_TIME_ROLLOVER       (1 << 8)
+#  define WUSBINTR_DNTS_OVERFLOW            (1 << 7)
+#  define WUSBINTR_BPST_ADJUSTMENT_CHANGED  (1 << 6)
+#  define WUSBINTR_HOST_ERR                 (1 << 5)
+#  define WUSBINTR_ASYNC_SCHED_SYNCED       (1 << 4)
+#  define WUSBINTR_PERIODIC_SCHED_SYNCED    (1 << 3)
+#  define WUSBINTR_DNTS_INT                 (1 << 2)
+#  define WUSBINTR_ERR_INT                  (1 << 1)
+#  define WUSBINTR_INT                      (1 << 0)
+#  define WUSBINTR_ALL 0x3ff
+
+#define WUSBGENCMDSTS        0x14
+#  define WUSBGENCMDSTS_ACTIVE (1 << 31)
+#  define WUSBGENCMDSTS_ERROR  (1 << 24)
+#  define WUSBGENCMDSTS_IOC    (1 << 23)
+#  define WUSBGENCMDSTS_MMCIE_ADD 0x01
+#  define WUSBGENCMDSTS_MMCIE_RM  0x02
+#  define WUSBGENCMDSTS_SET_MAS   0x03
+#  define WUSBGENCMDSTS_CHAN_STOP 0x04
+#  define WUSBGENCMDSTS_RWP_EN    0x05
+
+#define WUSBGENCMDPARAMS     0x18
+#define WUSBGENADDR          0x20
+#define WUSBASYNCLISTADDR    0x28
+#define WUSBDNTSBUFADDR      0x30
+#define WUSBDEVICEINFOADDR   0x38
+
+#define WUSBSETSECKEYCMD     0x40
+#  define WUSBSETSECKEYCMD_SET    (1 << 31)
+#  define WUSBSETSECKEYCMD_ERASE  (1 << 30)
+#  define WUSBSETSECKEYCMD_GTK    (1 << 8)
+#  define WUSBSETSECKEYCMD_IDX(i) ((i) << 0)
+
+#define WUSBTKID             0x44
+#define WUSBSECKEY           0x48
+#define WUSBPERIODICLISTBASE 0x58
+#define WUSBMASINDEX         0x60
+
+#define WUSBDNTSCTRL         0x64
+#  define WUSBDNTSCTRL_ACTIVE      (1 << 31)
+#  define WUSBDNTSCTRL_INTERVAL(i) ((i) << 8)
+#  define WUSBDNTSCTRL_SLOTS(s)    ((s) << 0)
+
+#define WUSBTIME             0x68
+#define WUSBBPST             0x6c
+#define WUSBDIBUPDATED       0x70
+
+#endif /* #ifndef _WHCI_WHCI_HC_H */
diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c
new file mode 100644 (file)
index 0000000..66e4ddc
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * Wireless Host Controller (WHC) WUSB operations.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/uwb/umc.h>
+#define D_LOCAL 1
+#include <linux/uwb/debug.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+#if D_LOCAL >= 1
+static void dump_di(struct whc *whc, int idx)
+{
+       struct di_buf_entry *di = &whc->di_buf[idx];
+       struct device *dev = &whc->umc->dev;
+       char buf[128];
+
+       bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS);
+
+       d_printf(1, dev, "DI[%d]\n", idx);
+       d_printf(1, dev, "  availability: %s\n", buf);
+       d_printf(1, dev, "  %c%c key idx: %d dev addr: %d\n",
+                (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ',
+                (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ',
+                (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8,
+                (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK));
+}
+#else
+static inline void dump_di(struct whc *whc, int idx)
+{
+}
+#endif
+
+static int whc_update_di(struct whc *whc, int idx)
+{
+       int offset = idx / 32;
+       u32 bit = 1 << (idx % 32);
+
+       dump_di(whc, idx);
+
+       le_writel(bit, whc->base + WUSBDIBUPDATED + offset);
+
+       return whci_wait_for(&whc->umc->dev,
+                            whc->base + WUSBDIBUPDATED + offset, bit, 0,
+                            100, "DI update");
+}
+
+/*
+ * WHCI starts and stops MMCs based on there being a valid GTK so
+ * these need only start/stop the asynchronous and periodic schedules.
+ */
+
+int whc_wusbhc_start(struct wusbhc *wusbhc)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+
+       asl_start(whc);
+       pzl_start(whc);
+
+       return 0;
+}
+
+void whc_wusbhc_stop(struct wusbhc *wusbhc)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+
+       pzl_stop(whc);
+       asl_stop(whc);
+}
+
+int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+                 u8 handle, struct wuie_hdr *wuie)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       u32 params;
+
+       params = (interval << 24)
+               | (repeat_cnt << 16)
+               | (wuie->bLength << 8)
+               | handle;
+
+       return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength);
+}
+
+int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       u32 params;
+
+       params = handle;
+
+       return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0);
+}
+
+int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+
+       if (stream_index >= 0)
+               whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index));
+
+       return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm));
+}
+
+int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       int idx = wusb_dev->port_idx;
+       struct di_buf_entry *di = &whc->di_buf[idx];
+       int ret;
+
+       mutex_lock(&whc->mutex);
+
+       uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability);
+       di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK);
+       di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr);
+
+       ret = whc_update_di(whc, idx);
+
+       mutex_unlock(&whc->mutex);
+
+       return ret;
+}
+
+/*
+ * Set the number of Device Notification Time Slots (DNTS) and enable
+ * device notifications.
+ */
+int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       u32 dntsctrl;
+
+       dntsctrl = WUSBDNTSCTRL_ACTIVE
+               | WUSBDNTSCTRL_INTERVAL(interval)
+               | WUSBDNTSCTRL_SLOTS(slots);
+
+       le_writel(dntsctrl, whc->base + WUSBDNTSCTRL);
+
+       return 0;
+}
+
+static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid,
+                      const void *key, size_t key_size, bool is_gtk)
+{
+       uint32_t setkeycmd;
+       uint32_t seckey[4];
+       int i;
+       int ret;
+
+       memcpy(seckey, key, key_size);
+       setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index);
+       if (is_gtk)
+               setkeycmd |= WUSBSETSECKEYCMD_GTK;
+
+       le_writel(tkid, whc->base + WUSBTKID);
+       for (i = 0; i < 4; i++)
+               le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i);
+       le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD);
+
+       ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD,
+                           WUSBSETSECKEYCMD_SET, 0, 100, "set key");
+
+       return ret;
+}
+
+/**
+ * whc_set_ptk - set the PTK to use for a device.
+ *
+ * The index into the key table for this PTK is the same as the
+ * device's port index.
+ */
+int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+               const void *ptk, size_t key_size)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       struct di_buf_entry *di = &whc->di_buf[port_idx];
+       int ret;
+
+       mutex_lock(&whc->mutex);
+
+       if (ptk) {
+               ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false);
+               if (ret)
+                       goto out;
+
+               di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK;
+               di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx);
+       } else
+               di->addr_sec_info &= ~WHC_DI_SECURE;
+
+       ret = whc_update_di(whc, port_idx);
+out:
+       mutex_unlock(&whc->mutex);
+       return ret;
+}
+
+/**
+ * whc_set_gtk - set the GTK for subsequent broadcast packets
+ *
+ * The GTK is stored in the last entry in the key table (the previous
+ * N_DEVICES entries are for the per-device PTKs).
+ */
+int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+               const void *gtk, size_t key_size)
+{
+       struct whc *whc = wusbhc_to_whc(wusbhc);
+       int ret;
+
+       mutex_lock(&whc->mutex);
+
+       ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true);
+
+       mutex_unlock(&whc->mutex);
+
+       return ret;
+}
+
+int whc_set_cluster_id(struct whc *whc, u8 bcid)
+{
+       whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid));
+       return 0;
+}
diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig
new file mode 100644 (file)
index 0000000..eb09a0a
--- /dev/null
@@ -0,0 +1,41 @@
+#
+# Wireless USB Core configuration
+#
+config USB_WUSB
+       tristate "Enable Wireless USB extensions (EXPERIMENTAL)"
+       depends on EXPERIMENTAL
+       depends on USB
+        select UWB
+        select CRYPTO
+        select CRYPTO_BLKCIPHER
+        select CRYPTO_CBC
+        select CRYPTO_MANAGER
+        select CRYPTO_AES
+       help
+         Enable the host-side support for Wireless USB.
+
+          To compile this support select Y (built in). It is safe to
+         select even if you don't have the hardware.
+
+config USB_WUSB_CBAF
+       tristate "Support WUSB Cable Based Association (CBA)"
+       depends on USB
+       help
+         Some WUSB devices support Cable Based Association. It's used to
+         enable the secure communication between the host and the
+         device.
+
+         Enable this option if your WUSB device must to be connected
+         via wired USB before establishing a wireless link.
+
+         It is safe to select even if you don't have a compatible
+         hardware.
+
+config USB_WUSB_CBAF_DEBUG
+       bool "Enable CBA debug messages"
+       depends on USB_WUSB_CBAF
+       help
+         Say Y here if you want the CBA to produce a bunch of debug messages
+         to the system log. Select this if you are having a problem with
+         CBA support and want to see more of what is going on.
+
diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile
new file mode 100644 (file)
index 0000000..75f1ade
--- /dev/null
@@ -0,0 +1,26 @@
+obj-$(CONFIG_USB_WUSB)         += wusbcore.o
+obj-$(CONFIG_USB_HWA_HCD)      += wusb-wa.o
+obj-$(CONFIG_USB_WUSB_CBAF)    += wusb-cbaf.o
+
+
+wusbcore-objs :=       \
+       crypto.o        \
+       devconnect.o    \
+       dev-sysfs.o     \
+       mmc.o           \
+       pal.o           \
+       rh.o            \
+       reservation.o   \
+       security.o      \
+       wusbhc.o
+
+wusb-cbaf-objs := cbaf.o
+
+wusb-wa-objs :=        wa-hc.o         \
+               wa-nep.o        \
+               wa-rpipe.o      \
+               wa-xfer.o
+
+ifeq ($(CONFIG_USB_WUSB_CBAF_DEBUG),y)
+EXTRA_CFLAGS += -DDEBUG
+endif
diff --git a/drivers/usb/wusbcore/cbaf.c b/drivers/usb/wusbcore/cbaf.c
new file mode 100644 (file)
index 0000000..ab4788d
--- /dev/null
@@ -0,0 +1,673 @@
+/*
+ * Wireless USB - Cable Based Association
+ *
+ *
+ * Copyright (C) 2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * WUSB devices have to be paired (associated in WUSB lingo) so
+ * that they can connect to the system.
+ *
+ * One way of pairing is using CBA-Cable Based Association. First
+ * time you plug the device with a cable, association is done between
+ * host and device and subsequent times, you can connect wirelessly
+ * without having to associate again. That's the idea.
+ *
+ * This driver does nothing Earth shattering. It just provides an
+ * interface to chat with the wire-connected device so we can get a
+ * CDID (device ID) that might have been previously associated to a
+ * CHID (host ID) and to set up a new <CHID,CDID,CK> triplet
+ * (connection context), with the CK being the secret, or connection
+ * key. This is the pairing data.
+ *
+ * When a device with the CBA capability connects, the probe routine
+ * just creates a bunch of sysfs files that a user space enumeration
+ * manager uses to allow it to connect wirelessly to the system or not.
+ *
+ * The process goes like this:
+ *
+ * 1. Device plugs, cbaf is loaded, notifications happen.
+ *
+ * 2. The connection manager (CM) sees a device with CBAF capability
+ *    (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE).
+ *
+ * 3. The CM writes the host name, supported band groups, and the CHID
+ *    (host ID) into the wusb_host_name, wusb_host_band_groups and
+ *    wusb_chid files. These get sent to the device and the CDID (if
+ *    any) for this host is requested.
+ *
+ * 4. The CM can verify that the device's supported band groups
+ *    (wusb_device_band_groups) are compatible with the host.
+ *
+ * 5. The CM reads the wusb_cdid file.
+ *
+ * 6. The CM looks up its database
+ *
+ * 6.1 If it has a matching CHID,CDID entry, the device has been
+ *     authorized before (paired) and nothing further needs to be
+ *     done.
+ *
+ * 6.2 If the CDID is zero (or the CM doesn't find a matching CDID in
+ *     its database), the device is assumed to be not known.  The CM
+ *     may associate the host with device by: writing a randomly
+ *     generated CDID to wusb_cdid and then a random CK to wusb_ck
+ *     (this uploads the new CC to the device).
+ *
+ *     CMD may choose to prompt the user before associating with a new
+ *     device.
+ *
+ * 7. Device is unplugged.
+ *
+ * When the device tries to connect wirelessly, it will present its
+ * CDID to the WUSB host controller.  The CM will query the
+ * database. If the CHID/CDID pair found, it will (with a 4-way
+ * handshake) challenge the device to demonstrate it has the CK secret
+ * key (from our database) without actually exchanging it. Once
+ * satisfied, crypto keys are derived from the CK, the device is
+ * connected and all communication is encrypted.
+ *
+ * References:
+ *   [WUSB-AM] Association Models Supplement to the Certified Wireless
+ *             Universal Serial Bus Specification, version 1.0.
+ */
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/version.h>
+#include <linux/usb.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/mutex.h>
+#include <linux/uwb.h>
+#include <linux/usb/wusb.h>
+#include <linux/usb/association.h>
+
+#define CBA_NAME_LEN 0x40 /* [WUSB-AM] table 4-7 */
+
+/* An instance of a Cable-Based-Association-Framework device */
+struct cbaf {
+       struct usb_device *usb_dev;
+       struct usb_interface *usb_iface;
+       void *buffer;
+       size_t buffer_size;
+
+       struct wusb_ckhdid chid;
+       char host_name[CBA_NAME_LEN];
+       u16 host_band_groups;
+
+       struct wusb_ckhdid cdid;
+       char device_name[CBA_NAME_LEN];
+       u16 device_band_groups;
+
+       struct wusb_ckhdid ck;
+};
+
+/*
+ * Verify that a CBAF USB-interface has what we need
+ *
+ * According to [WUSB-AM], CBA devices should provide at least two
+ * interfaces:
+ *  - RETRIEVE_HOST_INFO
+ *  - ASSOCIATE
+ *
+ * If the device doesn't provide these interfaces, we do not know how
+ * to deal with it.
+ */
+static int cbaf_check(struct cbaf *cbaf)
+{
+       int result;
+       struct device *dev = &cbaf->usb_iface->dev;
+       struct wusb_cbaf_assoc_info *assoc_info;
+       struct wusb_cbaf_assoc_request *assoc_request;
+       size_t assoc_size;
+       void *itr, *top;
+       int ar_rhi = 0, ar_assoc = 0;
+
+       result = usb_control_msg(
+               cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0),
+               CBAF_REQ_GET_ASSOCIATION_INFORMATION,
+               USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+               0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+               cbaf->buffer, cbaf->buffer_size, 1000 /* FIXME: arbitrary */);
+       if (result < 0) {
+               dev_err(dev, "Cannot get available association types: %d\n",
+                       result);
+               return result;
+       }
+
+       assoc_info = cbaf->buffer;
+       if (result < sizeof(*assoc_info)) {
+               dev_err(dev, "Not enough data to decode association info "
+                       "header (%zu vs %zu bytes required)\n",
+                       (size_t)result, sizeof(*assoc_info));
+               return result;
+       }
+
+       assoc_size = le16_to_cpu(assoc_info->Length);
+       if (result < assoc_size) {
+               dev_err(dev, "Not enough data to decode association info "
+                       "(%zu vs %zu bytes required)\n",
+                       (size_t)assoc_size, sizeof(*assoc_info));
+               return result;
+       }
+       /*
+        * From now on, we just verify, but won't error out unless we
+        * don't find the AR_TYPE_WUSB_{RETRIEVE_HOST_INFO,ASSOCIATE}
+        * types.
+        */
+       itr = cbaf->buffer + sizeof(*assoc_info);
+       top = cbaf->buffer + assoc_size;
+       dev_dbg(dev, "Found %u association requests (%zu bytes)\n",
+                assoc_info->NumAssociationRequests, assoc_size);
+
+       while (itr < top) {
+               u16 ar_type, ar_subtype;
+               u32 ar_size;
+               const char *ar_name;
+
+               assoc_request = itr;
+
+               if (top - itr < sizeof(*assoc_request)) {
+                       dev_err(dev, "Not enough data to decode associaton "
+                               "request (%zu vs %zu bytes needed)\n",
+                               top - itr, sizeof(*assoc_request));
+                       break;
+               }
+
+               ar_type = le16_to_cpu(assoc_request->AssociationTypeId);
+               ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId);
+               ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize);
+               ar_name = "unknown";
+
+               switch (ar_type) {
+               case AR_TYPE_WUSB:
+                       /* Verify we have what is mandated by [WUSB-AM]. */
+                       switch (ar_subtype) {
+                       case AR_TYPE_WUSB_RETRIEVE_HOST_INFO:
+                               ar_name = "RETRIEVE_HOST_INFO";
+                               ar_rhi = 1;
+                               break;
+                       case AR_TYPE_WUSB_ASSOCIATE:
+                               /* send assoc data */
+                               ar_name = "ASSOCIATE";
+                               ar_assoc = 1;
+                               break;
+                       };
+                       break;
+               };
+
+               dev_dbg(dev, "Association request #%02u: 0x%04x/%04x "
+                        "(%zu bytes): %s\n",
+                        assoc_request->AssociationDataIndex, ar_type,
+                        ar_subtype, (size_t)ar_size, ar_name);
+
+               itr += sizeof(*assoc_request);
+       }
+
+       if (!ar_rhi) {
+               dev_err(dev, "Missing RETRIEVE_HOST_INFO association "
+                       "request\n");
+               return -EINVAL;
+       }
+       if (!ar_assoc) {
+               dev_err(dev, "Missing ASSOCIATE association request\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct wusb_cbaf_host_info cbaf_host_info_defaults = {
+       .AssociationTypeId_hdr    = WUSB_AR_AssociationTypeId,
+       .AssociationTypeId        = cpu_to_le16(AR_TYPE_WUSB),
+       .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
+       .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_RETRIEVE_HOST_INFO),
+       .CHID_hdr                 = WUSB_AR_CHID,
+       .LangID_hdr               = WUSB_AR_LangID,
+       .HostFriendlyName_hdr     = WUSB_AR_HostFriendlyName,
+};
+
+/* Send WUSB host information (CHID and name) to a CBAF device */
+static int cbaf_send_host_info(struct cbaf *cbaf)
+{
+       struct wusb_cbaf_host_info *hi;
+       size_t name_len;
+       size_t hi_size;
+
+       hi = cbaf->buffer;
+       memset(hi, 0, sizeof(*hi));
+       *hi = cbaf_host_info_defaults;
+       hi->CHID = cbaf->chid;
+       hi->LangID = 0; /* FIXME: I guess... */
+       strlcpy(hi->HostFriendlyName, cbaf->host_name, CBA_NAME_LEN);
+       name_len = strlen(cbaf->host_name);
+       hi->HostFriendlyName_hdr.len = cpu_to_le16(name_len);
+       hi_size = sizeof(*hi) + name_len;
+
+       return usb_control_msg(cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0),
+                       CBAF_REQ_SET_ASSOCIATION_RESPONSE,
+                       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       0x0101,
+                       cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+                       hi, hi_size, 1000 /* FIXME: arbitrary */);
+}
+
+/*
+ * Get device's information (CDID) associated to CHID
+ *
+ * The device will return it's information (CDID, name, bandgroups)
+ * associated to the CHID we have set before, or 0 CDID and default
+ * name and bandgroup if no CHID set or unknown.
+ */
+static int cbaf_cdid_get(struct cbaf *cbaf)
+{
+       int result;
+       struct device *dev = &cbaf->usb_iface->dev;
+       struct wusb_cbaf_device_info *di;
+       size_t needed;
+
+       di = cbaf->buffer;
+       result = usb_control_msg(
+               cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0),
+               CBAF_REQ_GET_ASSOCIATION_REQUEST,
+               USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+               0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+               di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */);
+       if (result < 0) {
+               dev_err(dev, "Cannot request device information: %d\n", result);
+               return result;
+       }
+
+       needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length);
+       if (result < needed) {
+               dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs "
+                       "%zu bytes needed)\n", (size_t)result, needed);
+               return result;
+       }
+
+       strlcpy(cbaf->device_name, di->DeviceFriendlyName, CBA_NAME_LEN);
+       cbaf->cdid = di->CDID;
+       cbaf->device_band_groups = le16_to_cpu(di->BandGroups);
+
+       return 0;
+}
+
+static ssize_t cbaf_wusb_chid_show(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+       char pr_chid[WUSB_CKHDID_STRSIZE];
+
+       ckhdid_printf(pr_chid, sizeof(pr_chid), &cbaf->chid);
+       return scnprintf(buf, PAGE_SIZE, "%s\n", pr_chid);
+}
+
+static ssize_t cbaf_wusb_chid_store(struct device *dev,
+                                        struct device_attribute *attr,
+                                        const char *buf, size_t size)
+{
+       ssize_t result;
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+
+       result = sscanf(buf,
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx",
+                       &cbaf->chid.data[0] , &cbaf->chid.data[1],
+                       &cbaf->chid.data[2] , &cbaf->chid.data[3],
+                       &cbaf->chid.data[4] , &cbaf->chid.data[5],
+                       &cbaf->chid.data[6] , &cbaf->chid.data[7],
+                       &cbaf->chid.data[8] , &cbaf->chid.data[9],
+                       &cbaf->chid.data[10], &cbaf->chid.data[11],
+                       &cbaf->chid.data[12], &cbaf->chid.data[13],
+                       &cbaf->chid.data[14], &cbaf->chid.data[15]);
+
+       if (result != 16)
+               return -EINVAL;
+
+       result = cbaf_send_host_info(cbaf);
+       if (result < 0)
+               return result;
+       result = cbaf_cdid_get(cbaf);
+       if (result < 0)
+               return -result;
+       return size;
+}
+static DEVICE_ATTR(wusb_chid, 0600, cbaf_wusb_chid_show, cbaf_wusb_chid_store);
+
+static ssize_t cbaf_wusb_host_name_show(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+
+       return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->host_name);
+}
+
+static ssize_t cbaf_wusb_host_name_store(struct device *dev,
+                                        struct device_attribute *attr,
+                                        const char *buf, size_t size)
+{
+       ssize_t result;
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+
+       result = sscanf(buf, "%63s", cbaf->host_name);
+       if (result != 1)
+               return -EINVAL;
+
+       return size;
+}
+static DEVICE_ATTR(wusb_host_name, 0600, cbaf_wusb_host_name_show,
+                                        cbaf_wusb_host_name_store);
+
+static ssize_t cbaf_wusb_host_band_groups_show(struct device *dev,
+                                              struct device_attribute *attr,
+                                              char *buf)
+{
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+
+       return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->host_band_groups);
+}
+
+static ssize_t cbaf_wusb_host_band_groups_store(struct device *dev,
+                                               struct device_attribute *attr,
+                                               const char *buf, size_t size)
+{
+       ssize_t result;
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+       u16 band_groups = 0;
+
+       result = sscanf(buf, "%04hx", &band_groups);
+       if (result != 1)
+               return -EINVAL;
+
+       cbaf->host_band_groups = band_groups;
+
+       return size;
+}
+
+static DEVICE_ATTR(wusb_host_band_groups, 0600,
+                  cbaf_wusb_host_band_groups_show,
+                  cbaf_wusb_host_band_groups_store);
+
+static const struct wusb_cbaf_device_info cbaf_device_info_defaults = {
+       .Length_hdr               = WUSB_AR_Length,
+       .CDID_hdr                 = WUSB_AR_CDID,
+       .BandGroups_hdr           = WUSB_AR_BandGroups,
+       .LangID_hdr               = WUSB_AR_LangID,
+       .DeviceFriendlyName_hdr   = WUSB_AR_DeviceFriendlyName,
+};
+
+static ssize_t cbaf_wusb_cdid_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+       char pr_cdid[WUSB_CKHDID_STRSIZE];
+
+       ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid);
+       return scnprintf(buf, PAGE_SIZE, "%s\n", pr_cdid);
+}
+
+static ssize_t cbaf_wusb_cdid_store(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t size)
+{
+       ssize_t result;
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+       struct wusb_ckhdid cdid;
+
+       result = sscanf(buf,
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx",
+                       &cdid.data[0] , &cdid.data[1],
+                       &cdid.data[2] , &cdid.data[3],
+                       &cdid.data[4] , &cdid.data[5],
+                       &cdid.data[6] , &cdid.data[7],
+                       &cdid.data[8] , &cdid.data[9],
+                       &cdid.data[10], &cdid.data[11],
+                       &cdid.data[12], &cdid.data[13],
+                       &cdid.data[14], &cdid.data[15]);
+       if (result != 16)
+               return -EINVAL;
+
+       cbaf->cdid = cdid;
+
+       return size;
+}
+static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_show, cbaf_wusb_cdid_store);
+
+static ssize_t cbaf_wusb_device_band_groups_show(struct device *dev,
+                                                struct device_attribute *attr,
+                                                char *buf)
+{
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+
+       return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->device_band_groups);
+}
+
+static DEVICE_ATTR(wusb_device_band_groups, 0600,
+                  cbaf_wusb_device_band_groups_show,
+                  NULL);
+
+static ssize_t cbaf_wusb_device_name_show(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+
+       return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->device_name);
+}
+static DEVICE_ATTR(wusb_device_name, 0600, cbaf_wusb_device_name_show, NULL);
+
+static const struct wusb_cbaf_cc_data cbaf_cc_data_defaults = {
+       .AssociationTypeId_hdr    = WUSB_AR_AssociationTypeId,
+       .AssociationTypeId        = cpu_to_le16(AR_TYPE_WUSB),
+       .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
+       .AssociationSubTypeId     = cpu_to_le16(AR_TYPE_WUSB_ASSOCIATE),
+       .Length_hdr               = WUSB_AR_Length,
+       .Length                   = cpu_to_le32(sizeof(struct wusb_cbaf_cc_data)),
+       .ConnectionContext_hdr    = WUSB_AR_ConnectionContext,
+       .BandGroups_hdr           = WUSB_AR_BandGroups,
+};
+
+static const struct wusb_cbaf_cc_data_fail cbaf_cc_data_fail_defaults = {
+       .AssociationTypeId_hdr    = WUSB_AR_AssociationTypeId,
+       .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
+       .Length_hdr               = WUSB_AR_Length,
+       .AssociationStatus_hdr    = WUSB_AR_AssociationStatus,
+};
+
+/*
+ * Send a new CC to the device.
+ */
+static int cbaf_cc_upload(struct cbaf *cbaf)
+{
+       int result;
+       struct device *dev = &cbaf->usb_iface->dev;
+       struct wusb_cbaf_cc_data *ccd;
+       char pr_cdid[WUSB_CKHDID_STRSIZE];
+
+       ccd =  cbaf->buffer;
+       *ccd = cbaf_cc_data_defaults;
+       ccd->CHID = cbaf->chid;
+       ccd->CDID = cbaf->cdid;
+       ccd->CK = cbaf->ck;
+       ccd->BandGroups = cpu_to_le16(cbaf->host_band_groups);
+
+       dev_dbg(dev, "Trying to upload CC:\n");
+       ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID);
+       dev_dbg(dev, "  CHID       %s\n", pr_cdid);
+       ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID);
+       dev_dbg(dev, "  CDID       %s\n", pr_cdid);
+       dev_dbg(dev, "  Bandgroups 0x%04x\n", cbaf->host_band_groups);
+
+       result = usb_control_msg(
+               cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0),
+               CBAF_REQ_SET_ASSOCIATION_RESPONSE,
+               USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+               0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+               ccd, sizeof(*ccd), 1000 /* FIXME: arbitrary */);
+
+       return result;
+}
+
+static ssize_t cbaf_wusb_ck_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t size)
+{
+       ssize_t result;
+       struct usb_interface *iface = to_usb_interface(dev);
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+
+       result = sscanf(buf,
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx",
+                       &cbaf->ck.data[0] , &cbaf->ck.data[1],
+                       &cbaf->ck.data[2] , &cbaf->ck.data[3],
+                       &cbaf->ck.data[4] , &cbaf->ck.data[5],
+                       &cbaf->ck.data[6] , &cbaf->ck.data[7],
+                       &cbaf->ck.data[8] , &cbaf->ck.data[9],
+                       &cbaf->ck.data[10], &cbaf->ck.data[11],
+                       &cbaf->ck.data[12], &cbaf->ck.data[13],
+                       &cbaf->ck.data[14], &cbaf->ck.data[15]);
+       if (result != 16)
+               return -EINVAL;
+
+       result = cbaf_cc_upload(cbaf);
+       if (result < 0)
+               return result;
+
+       return size;
+}
+static DEVICE_ATTR(wusb_ck, 0600, NULL, cbaf_wusb_ck_store);
+
+static struct attribute *cbaf_dev_attrs[] = {
+       &dev_attr_wusb_host_name.attr,
+       &dev_attr_wusb_host_band_groups.attr,
+       &dev_attr_wusb_chid.attr,
+       &dev_attr_wusb_cdid.attr,
+       &dev_attr_wusb_device_name.attr,
+       &dev_attr_wusb_device_band_groups.attr,
+       &dev_attr_wusb_ck.attr,
+       NULL,
+};
+
+static struct attribute_group cbaf_dev_attr_group = {
+       .name = NULL,   /* we want them in the same directory */
+       .attrs = cbaf_dev_attrs,
+};
+
+static int cbaf_probe(struct usb_interface *iface,
+                     const struct usb_device_id *id)
+{
+       struct cbaf *cbaf;
+       struct device *dev = &iface->dev;
+       int result = -ENOMEM;
+
+       cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL);
+       if (cbaf == NULL)
+               goto error_kzalloc;
+       cbaf->buffer = kmalloc(512, GFP_KERNEL);
+       if (cbaf->buffer == NULL)
+               goto error_kmalloc_buffer;
+
+       cbaf->buffer_size = 512;
+       cbaf->usb_dev = usb_get_dev(interface_to_usbdev(iface));
+       cbaf->usb_iface = usb_get_intf(iface);
+       result = cbaf_check(cbaf);
+       if (result < 0) {
+               dev_err(dev, "This device is not WUSB-CBAF compliant"
+                       "and is not supported yet.\n");
+               goto error_check;
+       }
+
+       result = sysfs_create_group(&dev->kobj, &cbaf_dev_attr_group);
+       if (result < 0) {
+               dev_err(dev, "Can't register sysfs attr group: %d\n", result);
+               goto error_create_group;
+       }
+       usb_set_intfdata(iface, cbaf);
+       return 0;
+
+error_create_group:
+error_check:
+       kfree(cbaf->buffer);
+error_kmalloc_buffer:
+       kfree(cbaf);
+error_kzalloc:
+       return result;
+}
+
+static void cbaf_disconnect(struct usb_interface *iface)
+{
+       struct cbaf *cbaf = usb_get_intfdata(iface);
+       struct device *dev = &iface->dev;
+       sysfs_remove_group(&dev->kobj, &cbaf_dev_attr_group);
+       usb_set_intfdata(iface, NULL);
+       usb_put_intf(iface);
+       kfree(cbaf->buffer);
+       /* paranoia: clean up crypto keys */
+       memset(cbaf, 0, sizeof(*cbaf));
+       kfree(cbaf);
+}
+
+static struct usb_device_id cbaf_id_table[] = {
+       { USB_INTERFACE_INFO(0xef, 0x03, 0x01), },
+       { },
+};
+MODULE_DEVICE_TABLE(usb, cbaf_id_table);
+
+static struct usb_driver cbaf_driver = {
+       .name =         "wusb-cbaf",
+       .id_table =     cbaf_id_table,
+       .probe =        cbaf_probe,
+       .disconnect =   cbaf_disconnect,
+};
+
+static int __init cbaf_driver_init(void)
+{
+       return usb_register(&cbaf_driver);
+}
+module_init(cbaf_driver_init);
+
+static void __exit cbaf_driver_exit(void)
+{
+       usb_deregister(&cbaf_driver);
+}
+module_exit(cbaf_driver_exit);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Wireless USB Cable Based Association");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/wusbcore/crypto.c b/drivers/usb/wusbcore/crypto.c
new file mode 100644 (file)
index 0000000..c36c438
--- /dev/null
@@ -0,0 +1,538 @@
+/*
+ * Ultra Wide Band
+ * AES-128 CCM Encryption
+ *
+ * Copyright (C) 2007 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * We don't do any encryption here; we use the Linux Kernel's AES-128
+ * crypto modules to construct keys and payload blocks in a way
+ * defined by WUSB1.0[6]. Check the erratas, as typos are are patched
+ * there.
+ *
+ * Thanks a zillion to John Keys for his help and clarifications over
+ * the designed-by-a-committee text.
+ *
+ * So the idea is that there is this basic Pseudo-Random-Function
+ * defined in WUSB1.0[6.5] which is the core of everything. It works
+ * by tweaking some blocks, AES crypting them and then xoring
+ * something else with them (this seems to be called CBC(AES) -- can
+ * you tell I know jack about crypto?). So we just funnel it into the
+ * Linux Crypto API.
+ *
+ * We leave a crypto test module so we can verify that vectors match,
+ * every now and then.
+ *
+ * Block size: 16 bytes -- AES seems to do things in 'block sizes'. I
+ *             am learning a lot...
+ *
+ *             Conveniently, some data structures that need to be
+ *             funneled through AES are...16 bytes in size!
+ */
+
+#include <linux/crypto.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/uwb.h>
+#include <linux/usb/wusb.h>
+#include <linux/scatterlist.h>
+#define D_LOCAL 0
+#include <linux/uwb/debug.h>
+
+
+/*
+ * Block of data, as understood by AES-CCM
+ *
+ * The code assumes this structure is nothing but a 16 byte array
+ * (packed in a struct to avoid common mess ups that I usually do with
+ * arrays and enforcing type checking).
+ */
+struct aes_ccm_block {
+       u8 data[16];
+} __attribute__((packed));
+
+/*
+ * Counter-mode Blocks (WUSB1.0[6.4])
+ *
+ * According to CCM (or so it seems), for the purpose of calculating
+ * the MIC, the message is broken in N counter-mode blocks, B0, B1,
+ * ... BN.
+ *
+ * B0 contains flags, the CCM nonce and l(m).
+ *
+ * B1 contains l(a), the MAC header, the encryption offset and padding.
+ *
+ * If EO is nonzero, additional blocks are built from payload bytes
+ * until EO is exahusted (FIXME: padding to 16 bytes, I guess). The
+ * padding is not xmitted.
+ */
+
+/* WUSB1.0[T6.4] */
+struct aes_ccm_b0 {
+       u8 flags;       /* 0x59, per CCM spec */
+       struct aes_ccm_nonce ccm_nonce;
+       __be16 lm;
+} __attribute__((packed));
+
+/* WUSB1.0[T6.5] */
+struct aes_ccm_b1 {
+       __be16 la;
+       u8 mac_header[10];
+       __le16 eo;
+       u8 security_reserved;   /* This is always zero */
+       u8 padding;             /* 0 */
+} __attribute__((packed));
+
+/*
+ * Encryption Blocks (WUSB1.0[6.4.4])
+ *
+ * CCM uses Ax blocks to generate a keystream with which the MIC and
+ * the message's payload are encoded. A0 always encrypts/decrypts the
+ * MIC. Ax (x>0) are used for the sucesive payload blocks.
+ *
+ * The x is the counter, and is increased for each block.
+ */
+struct aes_ccm_a {
+       u8 flags;       /* 0x01, per CCM spec */
+       struct aes_ccm_nonce ccm_nonce;
+       __be16 counter; /* Value of x */
+} __attribute__((packed));
+
+static void bytewise_xor(void *_bo, const void *_bi1, const void *_bi2,
+                        size_t size)
+{
+       u8 *bo = _bo;
+       const u8 *bi1 = _bi1, *bi2 = _bi2;
+       size_t itr;
+       for (itr = 0; itr < size; itr++)
+               bo[itr] = bi1[itr] ^ bi2[itr];
+}
+
+/*
+ * CC-MAC function WUSB1.0[6.5]
+ *
+ * Take a data string and produce the encrypted CBC Counter-mode MIC
+ *
+ * Note the names for most function arguments are made to (more or
+ * less) match those used in the pseudo-function definition given in
+ * WUSB1.0[6.5].
+ *
+ * @tfm_cbc: CBC(AES) blkcipher handle (initialized)
+ *
+ * @tfm_aes: AES cipher handle (initialized)
+ *
+ * @mic: buffer for placing the computed MIC (Message Integrity
+ *       Code). This is exactly 8 bytes, and we expect the buffer to
+ *       be at least eight bytes in length.
+ *
+ * @key: 128 bit symmetric key
+ *
+ * @n: CCM nonce
+ *
+ * @a: ASCII string, 14 bytes long (I guess zero padded if needed;
+ *     we use exactly 14 bytes).
+ *
+ * @b: data stream to be processed; cannot be a global or const local
+ *     (will confuse the scatterlists)
+ *
+ * @blen: size of b...
+ *
+ * Still not very clear how this is done, but looks like this: we
+ * create block B0 (as WUSB1.0[6.5] says), then we AES-crypt it with
+ * @key. We bytewise xor B0 with B1 (1) and AES-crypt that. Then we
+ * take the payload and divide it in blocks (16 bytes), xor them with
+ * the previous crypto result (16 bytes) and crypt it, repeat the next
+ * block with the output of the previous one, rinse wash (I guess this
+ * is what AES CBC mode means...but I truly have no idea). So we use
+ * the CBC(AES) blkcipher, that does precisely that. The IV (Initial
+ * Vector) is 16 bytes and is set to zero, so
+ *
+ * See rfc3610. Linux crypto has a CBC implementation, but the
+ * documentation is scarce, to say the least, and the example code is
+ * so intricated that is difficult to understand how things work. Most
+ * of this is guess work -- bite me.
+ *
+ * (1) Created as 6.5 says, again, using as l(a) 'Blen + 14', and
+ *     using the 14 bytes of @a to fill up
+ *     b1.{mac_header,e0,security_reserved,padding}.
+ *
+ * NOTE: The definiton of l(a) in WUSB1.0[6.5] vs the definition of
+ *       l(m) is orthogonal, they bear no relationship, so it is not
+ *       in conflict with the parameter's relation that
+ *       WUSB1.0[6.4.2]) defines.
+ *
+ * NOTE: WUSB1.0[A.1]: Host Nonce is missing a nibble? (1e); fixed in
+ *       first errata released on 2005/07.
+ *
+ * NOTE: we need to clean IV to zero at each invocation to make sure
+ *       we start with a fresh empty Initial Vector, so that the CBC
+ *       works ok.
+ *
+ * NOTE: blen is not aligned to a block size, we'll pad zeros, that's
+ *       what sg[4] is for. Maybe there is a smarter way to do this.
+ */
+static int wusb_ccm_mac(struct crypto_blkcipher *tfm_cbc,
+                       struct crypto_cipher *tfm_aes, void *mic,
+                       const struct aes_ccm_nonce *n,
+                       const struct aes_ccm_label *a, const void *b,
+                       size_t blen)
+{
+       int result = 0;
+       struct blkcipher_desc desc;
+       struct aes_ccm_b0 b0;
+       struct aes_ccm_b1 b1;
+       struct aes_ccm_a ax;
+       struct scatterlist sg[4], sg_dst;
+       void *iv, *dst_buf;
+       size_t ivsize, dst_size;
+       const u8 bzero[16] = { 0 };
+       size_t zero_padding;
+
+       d_fnstart(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, "
+                 "n %p, a %p, b %p, blen %zu)\n",
+                 tfm_cbc, tfm_aes, mic, n, a, b, blen);
+       /*
+        * These checks should be compile time optimized out
+        * ensure @a fills b1's mac_header and following fields
+        */
+       WARN_ON(sizeof(*a) != sizeof(b1) - sizeof(b1.la));
+       WARN_ON(sizeof(b0) != sizeof(struct aes_ccm_block));
+       WARN_ON(sizeof(b1) != sizeof(struct aes_ccm_block));
+       WARN_ON(sizeof(ax) != sizeof(struct aes_ccm_block));
+
+       result = -ENOMEM;
+       zero_padding = sizeof(struct aes_ccm_block)
+               - blen % sizeof(struct aes_ccm_block);
+       zero_padding = blen % sizeof(struct aes_ccm_block);
+       if (zero_padding)
+               zero_padding = sizeof(struct aes_ccm_block) - zero_padding;
+       dst_size = blen + sizeof(b0) + sizeof(b1) + zero_padding;
+       dst_buf = kzalloc(dst_size, GFP_KERNEL);
+       if (dst_buf == NULL) {
+               printk(KERN_ERR "E: can't alloc destination buffer\n");
+               goto error_dst_buf;
+       }
+
+       iv = crypto_blkcipher_crt(tfm_cbc)->iv;
+       ivsize = crypto_blkcipher_ivsize(tfm_cbc);
+       memset(iv, 0, ivsize);
+
+       /* Setup B0 */
+       b0.flags = 0x59;        /* Format B0 */
+       b0.ccm_nonce = *n;
+       b0.lm = cpu_to_be16(0); /* WUSB1.0[6.5] sez l(m) is 0 */
+
+       /* Setup B1
+        *
+        * The WUSB spec is anything but clear! WUSB1.0[6.5]
+        * says that to initialize B1 from A with 'l(a) = blen +
+        * 14'--after clarification, it means to use A's contents
+        * for MAC Header, EO, sec reserved and padding.
+        */
+       b1.la = cpu_to_be16(blen + 14);
+       memcpy(&b1.mac_header, a, sizeof(*a));
+
+       d_printf(4, NULL, "I: B0 (%zu bytes)\n", sizeof(b0));
+       d_dump(4, NULL, &b0, sizeof(b0));
+       d_printf(4, NULL, "I: B1 (%zu bytes)\n", sizeof(b1));
+       d_dump(4, NULL, &b1, sizeof(b1));
+       d_printf(4, NULL, "I: B (%zu bytes)\n", blen);
+       d_dump(4, NULL, b, blen);
+       d_printf(4, NULL, "I: B 0-padding (%zu bytes)\n", zero_padding);
+       d_printf(4, NULL, "D: IV before crypto (%zu)\n", ivsize);
+       d_dump(4, NULL, iv, ivsize);
+
+       sg_init_table(sg, ARRAY_SIZE(sg));
+       sg_set_buf(&sg[0], &b0, sizeof(b0));
+       sg_set_buf(&sg[1], &b1, sizeof(b1));
+       sg_set_buf(&sg[2], b, blen);
+       /* 0 if well behaved :) */
+       sg_set_buf(&sg[3], bzero, zero_padding);
+       sg_init_one(&sg_dst, dst_buf, dst_size);
+
+       desc.tfm = tfm_cbc;
+       desc.flags = 0;
+       result = crypto_blkcipher_encrypt(&desc, &sg_dst, sg, dst_size);
+       if (result < 0) {
+               printk(KERN_ERR "E: can't compute CBC-MAC tag (MIC): %d\n",
+                      result);
+               goto error_cbc_crypt;
+       }
+       d_printf(4, NULL, "D: MIC tag\n");
+       d_dump(4, NULL, iv, ivsize);
+
+       /* Now we crypt the MIC Tag (*iv) with Ax -- values per WUSB1.0[6.5]
+        * The procedure is to AES crypt the A0 block and XOR the MIC
+        * Tag agains it; we only do the first 8 bytes and place it
+        * directly in the destination buffer.
+        *
+        * POS Crypto API: size is assumed to be AES's block size.
+        * Thanks for documenting it -- tip taken from airo.c
+        */
+       ax.flags = 0x01;                /* as per WUSB 1.0 spec */
+       ax.ccm_nonce = *n;
+       ax.counter = 0;
+       crypto_cipher_encrypt_one(tfm_aes, (void *)&ax, (void *)&ax);
+       bytewise_xor(mic, &ax, iv, 8);
+       d_printf(4, NULL, "D: CTR[MIC]\n");
+       d_dump(4, NULL, &ax, 8);
+       d_printf(4, NULL, "D: CCM-MIC tag\n");
+       d_dump(4, NULL, mic, 8);
+       result = 8;
+error_cbc_crypt:
+       kfree(dst_buf);
+error_dst_buf:
+       d_fnend(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, "
+               "n %p, a %p, b %p, blen %zu)\n",
+               tfm_cbc, tfm_aes, mic, n, a, b, blen);
+       return result;
+}
+
+/*
+ * WUSB Pseudo Random Function (WUSB1.0[6.5])
+ *
+ * @b: buffer to the source data; cannot be a global or const local
+ *     (will confuse the scatterlists)
+ */
+ssize_t wusb_prf(void *out, size_t out_size,
+                const u8 key[16], const struct aes_ccm_nonce *_n,
+                const struct aes_ccm_label *a,
+                const void *b, size_t blen, size_t len)
+{
+       ssize_t result, bytes = 0, bitr;
+       struct aes_ccm_nonce n = *_n;
+       struct crypto_blkcipher *tfm_cbc;
+       struct crypto_cipher *tfm_aes;
+       u64 sfn = 0;
+       __le64 sfn_le;
+
+       d_fnstart(3, NULL, "(out %p, out_size %zu, key %p, _n %p, "
+                 "a %p, b %p, blen %zu, len %zu)\n", out, out_size,
+                 key, _n, a, b, blen, len);
+
+       tfm_cbc = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC);
+       if (IS_ERR(tfm_cbc)) {
+               result = PTR_ERR(tfm_cbc);
+               printk(KERN_ERR "E: can't load CBC(AES): %d\n", (int)result);
+               goto error_alloc_cbc;
+       }
+       result = crypto_blkcipher_setkey(tfm_cbc, key, 16);
+       if (result < 0) {
+               printk(KERN_ERR "E: can't set CBC key: %d\n", (int)result);
+               goto error_setkey_cbc;
+       }
+
+       tfm_aes = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC);
+       if (IS_ERR(tfm_aes)) {
+               result = PTR_ERR(tfm_aes);
+               printk(KERN_ERR "E: can't load AES: %d\n", (int)result);
+               goto error_alloc_aes;
+       }
+       result = crypto_cipher_setkey(tfm_aes, key, 16);
+       if (result < 0) {
+               printk(KERN_ERR "E: can't set AES key: %d\n", (int)result);
+               goto error_setkey_aes;
+       }
+
+       for (bitr = 0; bitr < (len + 63) / 64; bitr++) {
+               sfn_le = cpu_to_le64(sfn++);
+               memcpy(&n.sfn, &sfn_le, sizeof(n.sfn)); /* n.sfn++... */
+               result = wusb_ccm_mac(tfm_cbc, tfm_aes, out + bytes,
+                                     &n, a, b, blen);
+               if (result < 0)
+                       goto error_ccm_mac;
+               bytes += result;
+       }
+       result = bytes;
+error_ccm_mac:
+error_setkey_aes:
+       crypto_free_cipher(tfm_aes);
+error_alloc_aes:
+error_setkey_cbc:
+       crypto_free_blkcipher(tfm_cbc);
+error_alloc_cbc:
+       d_fnend(3, NULL, "(out %p, out_size %zu, key %p, _n %p, "
+               "a %p, b %p, blen %zu, len %zu) = %d\n", out, out_size,
+               key, _n, a, b, blen, len, (int)bytes);
+       return result;
+}
+
+/* WUSB1.0[A.2] test vectors */
+static const u8 stv_hsmic_key[16] = {
+       0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d,
+       0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f
+};
+
+static const struct aes_ccm_nonce stv_hsmic_n = {
+       .sfn = { 0 },
+       .tkid = { 0x76, 0x98, 0x01,  },
+       .dest_addr = { .data = { 0xbe, 0x00 } },
+               .src_addr = { .data = { 0x76, 0x98 } },
+};
+
+/*
+ * Out-of-band MIC Generation verification code
+ *
+ */
+static int wusb_oob_mic_verify(void)
+{
+       int result;
+       u8 mic[8];
+       /* WUSB1.0[A.2] test vectors
+        *
+        * Need to keep it in the local stack as GCC 4.1.3something
+        * messes up and generates noise.
+        */
+       struct usb_handshake stv_hsmic_hs = {
+               .bMessageNumber = 2,
+               .bStatus        = 00,
+               .tTKID          = { 0x76, 0x98, 0x01 },
+               .bReserved      = 00,
+               .CDID           = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+                                   0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+                                   0x3c, 0x3d, 0x3e, 0x3f },
+               .nonce          = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
+                                   0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+                                   0x2c, 0x2d, 0x2e, 0x2f },
+               .MIC            = { 0x75, 0x6a, 0x97, 0x51, 0x0c, 0x8c,
+                                   0x14, 0x7b } ,
+       };
+       size_t hs_size;
+
+       result = wusb_oob_mic(mic, stv_hsmic_key, &stv_hsmic_n, &stv_hsmic_hs);
+       if (result < 0)
+               printk(KERN_ERR "E: WUSB OOB MIC test: failed: %d\n", result);
+       else if (memcmp(stv_hsmic_hs.MIC, mic, sizeof(mic))) {
+               printk(KERN_ERR "E: OOB MIC test: "
+                      "mismatch between MIC result and WUSB1.0[A2]\n");
+               hs_size = sizeof(stv_hsmic_hs) - sizeof(stv_hsmic_hs.MIC);
+               printk(KERN_ERR "E: Handshake2 in: (%zu bytes)\n", hs_size);
+               dump_bytes(NULL, &stv_hsmic_hs, hs_size);
+               printk(KERN_ERR "E: CCM Nonce in: (%zu bytes)\n",
+                      sizeof(stv_hsmic_n));
+               dump_bytes(NULL, &stv_hsmic_n, sizeof(stv_hsmic_n));
+               printk(KERN_ERR "E: MIC out:\n");
+               dump_bytes(NULL, mic, sizeof(mic));
+               printk(KERN_ERR "E: MIC out (from WUSB1.0[A.2]):\n");
+               dump_bytes(NULL, stv_hsmic_hs.MIC, sizeof(stv_hsmic_hs.MIC));
+               result = -EINVAL;
+       } else
+               result = 0;
+       return result;
+}
+
+/*
+ * Test vectors for Key derivation
+ *
+ * These come from WUSB1.0[6.5.1], the vectors in WUSB1.0[A.1]
+ * (errata corrected in 2005/07).
+ */
+static const u8 stv_key_a1[16] __attribute__ ((__aligned__(4))) = {
+       0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87,
+       0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f
+};
+
+static const struct aes_ccm_nonce stv_keydvt_n_a1 = {
+       .sfn = { 0 },
+       .tkid = { 0x76, 0x98, 0x01,  },
+       .dest_addr = { .data = { 0xbe, 0x00 } },
+       .src_addr = { .data = { 0x76, 0x98 } },
+};
+
+static const struct wusb_keydvt_out stv_keydvt_out_a1 = {
+       .kck = {
+               0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d,
+               0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f
+       },
+       .ptk = {
+               0xc8, 0x70, 0x62, 0x82, 0xb6, 0x7c, 0xe9, 0x06,
+               0x7b, 0xc5, 0x25, 0x69, 0xf2, 0x36, 0x61, 0x2d
+       }
+};
+
+/*
+ * Performa a test to make sure we match the vectors defined in
+ * WUSB1.0[A.1](Errata2006/12)
+ */
+static int wusb_key_derive_verify(void)
+{
+       int result = 0;
+       struct wusb_keydvt_out keydvt_out;
+       /* These come from WUSB1.0[A.1] + 2006/12 errata
+        * NOTE: can't make this const or global -- somehow it seems
+        *       the scatterlists for crypto get confused and we get
+        *       bad data. There is no doc on this... */
+       struct wusb_keydvt_in stv_keydvt_in_a1 = {
+               .hnonce = {
+                       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                       0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
+               },
+               .dnonce = {
+                       0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+                       0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f
+               }
+       };
+
+       result = wusb_key_derive(&keydvt_out, stv_key_a1, &stv_keydvt_n_a1,
+                                &stv_keydvt_in_a1);
+       if (result < 0)
+               printk(KERN_ERR "E: WUSB key derivation test: "
+                      "derivation failed: %d\n", result);
+       if (memcmp(&stv_keydvt_out_a1, &keydvt_out, sizeof(keydvt_out))) {
+               printk(KERN_ERR "E: WUSB key derivation test: "
+                      "mismatch between key derivation result "
+                      "and WUSB1.0[A1] Errata 2006/12\n");
+               printk(KERN_ERR "E: keydvt in: key (%zu bytes)\n",
+                      sizeof(stv_key_a1));
+               dump_bytes(NULL, stv_key_a1, sizeof(stv_key_a1));
+               printk(KERN_ERR "E: keydvt in: nonce (%zu bytes)\n",
+                      sizeof(stv_keydvt_n_a1));
+               dump_bytes(NULL, &stv_keydvt_n_a1, sizeof(stv_keydvt_n_a1));
+               printk(KERN_ERR "E: keydvt in: hnonce & dnonce (%zu bytes)\n",
+                      sizeof(stv_keydvt_in_a1));
+               dump_bytes(NULL, &stv_keydvt_in_a1, sizeof(stv_keydvt_in_a1));
+               printk(KERN_ERR "E: keydvt out: KCK\n");
+               dump_bytes(NULL, &keydvt_out.kck, sizeof(keydvt_out.kck));
+               printk(KERN_ERR "E: keydvt out: PTK\n");
+               dump_bytes(NULL, &keydvt_out.ptk, sizeof(keydvt_out.ptk));
+               result = -EINVAL;
+       } else
+               result = 0;
+       return result;
+}
+
+/*
+ * Initialize crypto system
+ *
+ * FIXME: we do nothing now, other than verifying. Later on we'll
+ * cache the encryption stuff, so that's why we have a separate init.
+ */
+int wusb_crypto_init(void)
+{
+       int result;
+
+       result = wusb_key_derive_verify();
+       if (result < 0)
+               return result;
+       return wusb_oob_mic_verify();
+}
+
+void wusb_crypto_exit(void)
+{
+       /* FIXME: free cached crypto transforms */
+}
diff --git a/drivers/usb/wusbcore/dev-sysfs.c b/drivers/usb/wusbcore/dev-sysfs.c
new file mode 100644 (file)
index 0000000..7897a19
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * WUSB devices
+ * sysfs bindings
+ *
+ * Copyright (C) 2007 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * Get them out of the way...
+ */
+
+#include <linux/jiffies.h>
+#include <linux/ctype.h>
+#include <linux/workqueue.h>
+#include "wusbhc.h"
+
+#undef D_LOCAL
+#define D_LOCAL 4
+#include <linux/uwb/debug.h>
+
+static ssize_t wusb_disconnect_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t size)
+{
+       struct usb_device *usb_dev;
+       struct wusbhc *wusbhc;
+       unsigned command;
+       u8 port_idx;
+
+       if (sscanf(buf, "%u", &command) != 1)
+               return -EINVAL;
+       if (command == 0)
+               return size;
+       usb_dev = to_usb_device(dev);
+       wusbhc = wusbhc_get_by_usb_dev(usb_dev);
+       if (wusbhc == NULL)
+               return -ENODEV;
+
+       mutex_lock(&wusbhc->mutex);
+       port_idx = wusb_port_no_to_idx(usb_dev->portnum);
+       __wusbhc_dev_disable(wusbhc, port_idx);
+       mutex_unlock(&wusbhc->mutex);
+       wusbhc_put(wusbhc);
+       return size;
+}
+static DEVICE_ATTR(wusb_disconnect, 0200, NULL, wusb_disconnect_store);
+
+static ssize_t wusb_cdid_show(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+       ssize_t result;
+       struct wusb_dev *wusb_dev;
+
+       wusb_dev = wusb_dev_get_by_usb_dev(to_usb_device(dev));
+       if (wusb_dev == NULL)
+               return -ENODEV;
+       result = ckhdid_printf(buf, PAGE_SIZE, &wusb_dev->cdid);
+       strcat(buf, "\n");
+       wusb_dev_put(wusb_dev);
+       return result + 1;
+}
+static DEVICE_ATTR(wusb_cdid, 0444, wusb_cdid_show, NULL);
+
+static ssize_t wusb_ck_store(struct device *dev,
+                            struct device_attribute *attr,
+                            const char *buf, size_t size)
+{
+       int result;
+       struct usb_device *usb_dev;
+       struct wusbhc *wusbhc;
+       struct wusb_ckhdid ck;
+
+       result = sscanf(buf,
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx "
+                       "%02hhx %02hhx %02hhx %02hhx\n",
+                       &ck.data[0] , &ck.data[1],
+                       &ck.data[2] , &ck.data[3],
+                       &ck.data[4] , &ck.data[5],
+                       &ck.data[6] , &ck.data[7],
+                       &ck.data[8] , &ck.data[9],
+                       &ck.data[10], &ck.data[11],
+                       &ck.data[12], &ck.data[13],
+                       &ck.data[14], &ck.data[15]);
+       if (result != 16)
+               return -EINVAL;
+
+       usb_dev = to_usb_device(dev);
+       wusbhc = wusbhc_get_by_usb_dev(usb_dev);
+       if (wusbhc == NULL)
+               return -ENODEV;
+       result = wusb_dev_4way_handshake(wusbhc, usb_dev->wusb_dev, &ck);
+       memset(&ck, 0, sizeof(ck));
+       wusbhc_put(wusbhc);
+       return result < 0 ? result : size;
+}
+static DEVICE_ATTR(wusb_ck, 0200, NULL, wusb_ck_store);
+
+static struct attribute *wusb_dev_attrs[] = {
+               &dev_attr_wusb_disconnect.attr,
+               &dev_attr_wusb_cdid.attr,
+               &dev_attr_wusb_ck.attr,
+               NULL,
+};
+
+static struct attribute_group wusb_dev_attr_group = {
+       .name = NULL,   /* we want them in the same directory */
+       .attrs = wusb_dev_attrs,
+};
+
+int wusb_dev_sysfs_add(struct wusbhc *wusbhc, struct usb_device *usb_dev,
+                      struct wusb_dev *wusb_dev)
+{
+       int result = sysfs_create_group(&usb_dev->dev.kobj,
+                                       &wusb_dev_attr_group);
+       struct device *dev = &usb_dev->dev;
+       if (result < 0)
+               dev_err(dev, "Cannot register WUSB-dev attributes: %d\n",
+                       result);
+       return result;
+}
+
+void wusb_dev_sysfs_rm(struct wusb_dev *wusb_dev)
+{
+       struct usb_device *usb_dev = wusb_dev->usb_dev;
+       if (usb_dev)
+               sysfs_remove_group(&usb_dev->dev.kobj, &wusb_dev_attr_group);
+}
diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c
new file mode 100644 (file)
index 0000000..f45d777
--- /dev/null
@@ -0,0 +1,1297 @@
+/*
+ * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8])
+ * Device Connect handling
+ *
+ * Copyright (C) 2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * FIXME: docs
+ * FIXME: this file needs to be broken up, it's grown too big
+ *
+ *
+ * WUSB1.0[7.1, 7.5.1, ]
+ *
+ * WUSB device connection is kind of messy. Some background:
+ *
+ *     When a device wants to connect it scans the UWB radio channels
+ *     looking for a WUSB Channel; a WUSB channel is defined by MMCs
+ *     (Micro Managed Commands or something like that) [see
+ *     Design-overview for more on this] .
+ *
+ * So, device scans the radio, finds MMCs and thus a host and checks
+ * when the next DNTS is. It sends a Device Notification Connect
+ * (DN_Connect); the host picks it up (through nep.c and notif.c, ends
+ * up in wusb_devconnect_ack(), which creates a wusb_dev structure in
+ * wusbhc->port[port_number].wusb_dev), assigns an unauth address
+ * to the device (this means from 0x80 to 0xfe) and sends, in the MMC
+ * a Connect Ack Information Element (ConnAck IE).
+ *
+ * So now the device now has a WUSB address. From now on, we use
+ * that to talk to it in the RPipes.
+ *
+ * ASSUMPTIONS:
+ *
+ *  - We use the the as device address the port number where it is
+ *    connected (port 0 doesn't exist). For unauth, it is 128 + that.
+ *
+ * ROADMAP:
+ *
+ *   This file contains the logic for doing that--entry points:
+ *
+ *   wusb_devconnect_ack()      Ack a device until _acked() called.
+ *                              Called by notif.c:wusb_handle_dn_connect()
+ *                              when a DN_Connect is received.
+ *
+ *   wusbhc_devconnect_auth()   Called by rh.c:wusbhc_rh_port_reset() when
+ *                              doing the device connect sequence.
+ *
+ *     wusb_devconnect_acked()  Ack done, release resources.
+ *
+ *   wusb_handle_dn_alive()     Called by notif.c:wusb_handle_dn()
+ *                              for processing a DN_Alive pong from a device.
+ *
+ *   wusb_handle_dn_disconnect()Called by notif.c:wusb_handle_dn() to
+ *                              process a disconenct request from a
+ *                              device.
+ *
+ *   wusb_dev_reset()           Called by rh.c:wusbhc_rh_port_reset() when
+ *                              resetting a device.
+ *
+ *   __wusb_dev_disable()       Called by rh.c:wusbhc_rh_clear_port_feat() when
+ *                              disabling a port.
+ *
+ *   wusb_devconnect_create()   Called when creating the host by
+ *                              lc.c:wusbhc_create().
+ *
+ *   wusb_devconnect_destroy()  Cleanup called removing the host. Called
+ *                              by lc.c:wusbhc_destroy().
+ *
+ *   Each Wireless USB host maintains a list of DN_Connect requests
+ *   (actually we maintain a list of pending Connect Acks, the
+ *   wusbhc->ca_list).
+ *
+ * LIFE CYCLE OF port->wusb_dev
+ *
+ *   Before the @wusbhc structure put()s the reference it owns for
+ *   port->wusb_dev [and clean the wusb_dev pointer], it needs to
+ *   lock @wusbhc->mutex.
+ */
+
+#include <linux/jiffies.h>
+#include <linux/ctype.h>
+#include <linux/workqueue.h>
+#include "wusbhc.h"
+
+#undef D_LOCAL
+#define D_LOCAL 1
+#include <linux/uwb/debug.h>
+
+static void wusbhc_devconnect_acked_work(struct work_struct *work);
+
+static void wusb_dev_free(struct wusb_dev *wusb_dev)
+{
+       if (wusb_dev) {
+               kfree(wusb_dev->set_gtk_req);
+               usb_free_urb(wusb_dev->set_gtk_urb);
+               kfree(wusb_dev);
+       }
+}
+
+static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc)
+{
+       struct wusb_dev *wusb_dev;
+       struct urb *urb;
+       struct usb_ctrlrequest *req;
+
+       wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL);
+       if (wusb_dev == NULL)
+               goto err;
+
+       wusb_dev->wusbhc = wusbhc;
+
+       INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work);
+
+       urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (urb == NULL)
+               goto err;
+
+       req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
+       if (req == NULL)
+               goto err;
+
+       req->bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
+       req->bRequest = USB_REQ_SET_DESCRIPTOR;
+       req->wValue = cpu_to_le16(USB_DT_KEY << 8 | wusbhc->gtk_index);
+       req->wIndex = 0;
+       req->wLength = cpu_to_le16(wusbhc->gtk.descr.bLength);
+
+       wusb_dev->set_gtk_urb = urb;
+       wusb_dev->set_gtk_req = req;
+
+       return wusb_dev;
+err:
+       wusb_dev_free(wusb_dev);
+       return NULL;
+}
+
+
+/*
+ * Using the Connect-Ack list, fill out the @wusbhc Connect-Ack WUSB IE
+ * properly so that it can be added to the MMC.
+ *
+ * We just get the @wusbhc->ca_list and fill out the first four ones or
+ * less (per-spec WUSB1.0[7.5, before T7-38). If the ConnectAck WUSB
+ * IE is not allocated, we alloc it.
+ *
+ * @wusbhc->mutex must be taken
+ */
+static void wusbhc_fill_cack_ie(struct wusbhc *wusbhc)
+{
+       unsigned cnt;
+       struct wusb_dev *dev_itr;
+       struct wuie_connect_ack *cack_ie;
+
+       cack_ie = &wusbhc->cack_ie;
+       cnt = 0;
+       list_for_each_entry(dev_itr, &wusbhc->cack_list, cack_node) {
+               cack_ie->blk[cnt].CDID = dev_itr->cdid;
+               cack_ie->blk[cnt].bDeviceAddress = dev_itr->addr;
+               if (++cnt >= WUIE_ELT_MAX)
+                       break;
+       }
+       cack_ie->hdr.bLength = sizeof(cack_ie->hdr)
+               + cnt * sizeof(cack_ie->blk[0]);
+}
+
+/*
+ * Register a new device that wants to connect
+ *
+ * A new device wants to connect, so we add it to the Connect-Ack
+ * list. We give it an address in the unauthorized range (bit 8 set);
+ * user space will have to drive authorization further on.
+ *
+ * @dev_addr: address to use for the device (which is also the port
+ *            number).
+ *
+ * @wusbhc->mutex must be taken
+ */
+static struct wusb_dev *wusbhc_cack_add(struct wusbhc *wusbhc,
+                                       struct wusb_dn_connect *dnc,
+                                       const char *pr_cdid, u8 port_idx)
+{
+       struct device *dev = wusbhc->dev;
+       struct wusb_dev *wusb_dev;
+       int new_connection = wusb_dn_connect_new_connection(dnc);
+       u8 dev_addr;
+       int result;
+
+       /* Is it registered already? */
+       list_for_each_entry(wusb_dev, &wusbhc->cack_list, cack_node)
+               if (!memcmp(&wusb_dev->cdid, &dnc->CDID,
+                           sizeof(wusb_dev->cdid)))
+                       return wusb_dev;
+       /* We don't have it, create an entry, register it */
+       wusb_dev = wusb_dev_alloc(wusbhc);
+       if (wusb_dev == NULL)
+               return NULL;
+       wusb_dev_init(wusb_dev);
+       wusb_dev->cdid = dnc->CDID;
+       wusb_dev->port_idx = port_idx;
+
+       /*
+        * Devices are always available within the cluster reservation
+        * and since the hardware will take the intersection of the
+        * per-device availability and the cluster reservation, the
+        * per-device availability can simply be set to always
+        * available.
+        */
+       bitmap_fill(wusb_dev->availability.bm, UWB_NUM_MAS);
+
+       /* FIXME: handle reconnects instead of assuming connects are
+          always new. */
+       if (1 && new_connection == 0)
+               new_connection = 1;
+       if (new_connection) {
+               dev_addr = (port_idx + 2) | WUSB_DEV_ADDR_UNAUTH;
+
+               dev_info(dev, "Connecting new WUSB device to address %u, "
+                       "port %u\n", dev_addr, port_idx);
+
+               result = wusb_set_dev_addr(wusbhc, wusb_dev, dev_addr);
+               if (result < 0)
+                       return NULL;
+       }
+       wusb_dev->entry_ts = jiffies;
+       list_add_tail(&wusb_dev->cack_node, &wusbhc->cack_list);
+       wusbhc->cack_count++;
+       wusbhc_fill_cack_ie(wusbhc);
+       return wusb_dev;
+}
+
+/*
+ * Remove a Connect-Ack context entry from the HCs view
+ *
+ * @wusbhc->mutex must be taken
+ */
+static void wusbhc_cack_rm(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+       struct device *dev = wusbhc->dev;
+       d_fnstart(3, dev, "(wusbhc %p wusb_dev %p)\n", wusbhc, wusb_dev);
+       list_del_init(&wusb_dev->cack_node);
+       wusbhc->cack_count--;
+       wusbhc_fill_cack_ie(wusbhc);
+       d_fnend(3, dev, "(wusbhc %p wusb_dev %p) = void\n", wusbhc, wusb_dev);
+}
+
+/*
+ * @wusbhc->mutex must be taken */
+static
+void wusbhc_devconnect_acked(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+       struct device *dev = wusbhc->dev;
+       d_fnstart(3, dev, "(wusbhc %p wusb_dev %p)\n", wusbhc, wusb_dev);
+       wusbhc_cack_rm(wusbhc, wusb_dev);
+       if (wusbhc->cack_count)
+               wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr);
+       else
+               wusbhc_mmcie_rm(wusbhc, &wusbhc->cack_ie.hdr);
+       d_fnend(3, dev, "(wusbhc %p wusb_dev %p) = void\n", wusbhc, wusb_dev);
+}
+
+static void wusbhc_devconnect_acked_work(struct work_struct *work)
+{
+       struct wusb_dev *wusb_dev = container_of(work, struct wusb_dev,
+                                                devconnect_acked_work);
+       struct wusbhc *wusbhc = wusb_dev->wusbhc;
+
+       mutex_lock(&wusbhc->mutex);
+       wusbhc_devconnect_acked(wusbhc, wusb_dev);
+       mutex_unlock(&wusbhc->mutex);
+}
+
+/*
+ * Ack a device for connection
+ *
+ * FIXME: docs
+ *
+ * @pr_cdid:   Printable CDID...hex Use @dnc->cdid for the real deal.
+ *
+ * So we get the connect ack IE (may have been allocated already),
+ * find an empty connect block, an empty virtual port, create an
+ * address with it (see below), make it an unauth addr [bit 7 set] and
+ * set the MMC.
+ *
+ * Addresses: because WUSB hosts have no downstream hubs, we can do a
+ *            1:1 mapping between 'port number' and device
+ *            address. This simplifies many things, as during this
+ *            initial connect phase the USB stack has no knoledge of
+ *            the device and hasn't assigned an address yet--we know
+ *            USB's choose_address() will use the same euristics we
+ *            use here, so we can assume which address will be assigned.
+ *
+ *            USB stack always assigns address 1 to the root hub, so
+ *            to the port number we add 2 (thus virtual port #0 is
+ *            addr #2).
+ *
+ * @wusbhc shall be referenced
+ */
+static
+void wusbhc_devconnect_ack(struct wusbhc *wusbhc, struct wusb_dn_connect *dnc,
+                          const char *pr_cdid)
+{
+       int result;
+       struct device *dev = wusbhc->dev;
+       struct wusb_dev *wusb_dev;
+       struct wusb_port *port;
+       unsigned idx, devnum;
+
+       d_fnstart(3, dev, "(%p, %p, %s)\n", wusbhc, dnc, pr_cdid);
+       mutex_lock(&wusbhc->mutex);
+
+       /* Check we are not handling it already */
+       for (idx = 0; idx < wusbhc->ports_max; idx++) {
+               port = wusb_port_by_idx(wusbhc, idx);
+               if (port->wusb_dev
+                   && memcmp(&dnc->CDID, &port->wusb_dev->cdid, sizeof(dnc->CDID)) == 0)
+                       goto error_unlock;
+       }
+       /* Look up those fake ports we have for a free one */
+       for (idx = 0; idx < wusbhc->ports_max; idx++) {
+               port = wusb_port_by_idx(wusbhc, idx);
+               if ((port->status & USB_PORT_STAT_POWER)
+                   && !(port->status & USB_PORT_STAT_CONNECTION))
+                       break;
+       }
+       if (idx >= wusbhc->ports_max) {
+               dev_err(dev, "Host controller can't connect more devices "
+                       "(%u already connected); device %s rejected\n",
+                       wusbhc->ports_max, pr_cdid);
+               /* NOTE: we could send a WUIE_Disconnect here, but we haven't
+                *       event acked, so the device will eventually timeout the
+                *       connection, right? */
+               goto error_unlock;
+       }
+
+       devnum = idx + 2;
+
+       /* Make sure we are using no crypto on that "virtual port" */
+       wusbhc->set_ptk(wusbhc, idx, 0, NULL, 0);
+
+       /* Grab a filled in Connect-Ack context, fill out the
+        * Connect-Ack Wireless USB IE, set the MMC */
+       wusb_dev = wusbhc_cack_add(wusbhc, dnc, pr_cdid, idx);
+       if (wusb_dev == NULL)
+               goto error_unlock;
+       result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr);
+       if (result < 0)
+               goto error_unlock;
+       /* Give the device at least 2ms (WUSB1.0[7.5.1p3]), let's do
+        * three for a good measure */
+       msleep(3);
+       port->wusb_dev = wusb_dev;
+       port->status |= USB_PORT_STAT_CONNECTION;
+       port->change |= USB_PORT_STAT_C_CONNECTION;
+       port->reset_count = 0;
+       /* Now the port status changed to connected; khubd will
+        * pick the change up and try to reset the port to bring it to
+        * the enabled state--so this process returns up to the stack
+        * and it calls back into wusbhc_rh_port_reset() who will call
+        * devconnect_auth().
+        */
+error_unlock:
+       mutex_unlock(&wusbhc->mutex);
+       d_fnend(3, dev, "(%p, %p, %s) = void\n", wusbhc, dnc, pr_cdid);
+       return;
+
+}
+
+/*
+ * Disconnect a Wireless USB device from its fake port
+ *
+ * Marks the port as disconnected so that khubd can pick up the change
+ * and drops our knowledge about the device.
+ *
+ * Assumes there is a device connected
+ *
+ * @port_index: zero based port number
+ *
+ * NOTE: @wusbhc->mutex is locked
+ *
+ * WARNING: From here it is not very safe to access anything hanging off
+ *         wusb_dev
+ */
+static void __wusbhc_dev_disconnect(struct wusbhc *wusbhc,
+                                   struct wusb_port *port)
+{
+       struct device *dev = wusbhc->dev;
+       struct wusb_dev *wusb_dev = port->wusb_dev;
+
+       d_fnstart(3, dev, "(wusbhc %p, port %p)\n", wusbhc, port);
+       port->status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE
+                         | USB_PORT_STAT_SUSPEND | USB_PORT_STAT_RESET
+                         | USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED);
+       port->change |= USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE;
+       if (wusb_dev) {
+               if (!list_empty(&wusb_dev->cack_node))
+                       list_del_init(&wusb_dev->cack_node);
+               /* For the one in cack_add() */
+               wusb_dev_put(wusb_dev);
+       }
+       port->wusb_dev = NULL;
+       /* don't reset the reset_count to zero or wusbhc_rh_port_reset will get
+        * confused! We only reset to zero when we connect a new device.
+        */
+
+       /* After a device disconnects, change the GTK (see [WUSB]
+        * section 6.2.11.2). */
+       wusbhc_gtk_rekey(wusbhc);
+
+       d_fnend(3, dev, "(wusbhc %p, port %p) = void\n", wusbhc, port);
+       /* The Wireless USB part has forgotten about the device already; now
+        * khubd's timer will pick up the disconnection and remove the USB
+        * device from the system
+        */
+}
+
+/*
+ * Authenticate a device into the WUSB Cluster
+ *
+ * Called from the Root Hub code (rh.c:wusbhc_rh_port_reset()) when
+ * asking for a reset on a port that is not enabled (ie: first connect
+ * on the port).
+ *
+ * Performs the 4way handshake to allow the device to comunicate w/ the
+ * WUSB Cluster securely; once done, issue a request to the device for
+ * it to change to address 0.
+ *
+ * This mimics the reset step of Wired USB that once resetting a
+ * device, leaves the port in enabled state and the dev with the
+ * default address (0).
+ *
+ * WUSB1.0[7.1.2]
+ *
+ * @port_idx: port where the change happened--This is the index into
+ *            the wusbhc port array, not the USB port number.
+ */
+int wusbhc_devconnect_auth(struct wusbhc *wusbhc, u8 port_idx)
+{
+       struct device *dev = wusbhc->dev;
+       struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx);
+
+       d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx);
+       port->status &= ~USB_PORT_STAT_RESET;
+       port->status |= USB_PORT_STAT_ENABLE;
+       port->change |= USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_ENABLE;
+       d_fnend(3, dev, "(%p, %u) = 0\n", wusbhc, port_idx);
+       return 0;
+}
+
+/*
+ * Refresh the list of keep alives to emit in the MMC
+ *
+ * Some devices don't respond to keep alives unless they've been
+ * authenticated, so skip unauthenticated devices.
+ *
+ * We only publish the first four devices that have a coming timeout
+ * condition. Then when we are done processing those, we go for the
+ * next ones. We ignore the ones that have timed out already (they'll
+ * be purged).
+ *
+ * This might cause the first devices to timeout the last devices in
+ * the port array...FIXME: come up with a better algorithm?
+ *
+ * Note we can't do much about MMC's ops errors; we hope next refresh
+ * will kind of handle it.
+ *
+ * NOTE: @wusbhc->mutex is locked
+ */
+static void __wusbhc_keep_alive(struct wusbhc *wusbhc)
+{
+       struct device *dev = wusbhc->dev;
+       unsigned cnt;
+       struct wusb_dev *wusb_dev;
+       struct wusb_port *wusb_port;
+       struct wuie_keep_alive *ie = &wusbhc->keep_alive_ie;
+       unsigned keep_alives, old_keep_alives;
+
+       old_keep_alives = ie->hdr.bLength - sizeof(ie->hdr);
+       keep_alives = 0;
+       for (cnt = 0;
+            keep_alives <= WUIE_ELT_MAX && cnt < wusbhc->ports_max;
+            cnt++) {
+               unsigned tt = msecs_to_jiffies(wusbhc->trust_timeout);
+
+               wusb_port = wusb_port_by_idx(wusbhc, cnt);
+               wusb_dev = wusb_port->wusb_dev;
+
+               if (wusb_dev == NULL)
+                       continue;
+               if (wusb_dev->usb_dev == NULL || !wusb_dev->usb_dev->authenticated)
+                       continue;
+
+               if (time_after(jiffies, wusb_dev->entry_ts + tt)) {
+                       dev_err(dev, "KEEPALIVE: device %u timed out\n",
+                               wusb_dev->addr);
+                       __wusbhc_dev_disconnect(wusbhc, wusb_port);
+               } else if (time_after(jiffies, wusb_dev->entry_ts + tt/2)) {
+                       /* Approaching timeout cut out, need to refresh */
+                       ie->bDeviceAddress[keep_alives++] = wusb_dev->addr;
+               }
+       }
+       if (keep_alives & 0x1)  /* pad to even number ([WUSB] section 7.5.9) */
+               ie->bDeviceAddress[keep_alives++] = 0x7f;
+       ie->hdr.bLength = sizeof(ie->hdr) +
+               keep_alives*sizeof(ie->bDeviceAddress[0]);
+       if (keep_alives > 0)
+               wusbhc_mmcie_set(wusbhc, 10, 5, &ie->hdr);
+       else if (old_keep_alives != 0)
+               wusbhc_mmcie_rm(wusbhc, &ie->hdr);
+}
+
+/*
+ * Do a run through all devices checking for timeouts
+ */
+static void wusbhc_keep_alive_run(struct work_struct *ws)
+{
+       struct delayed_work *dw =
+               container_of(ws, struct delayed_work, work);
+       struct wusbhc *wusbhc =
+               container_of(dw, struct wusbhc, keep_alive_timer);
+
+       d_fnstart(5, wusbhc->dev, "(wusbhc %p)\n", wusbhc);
+       if (wusbhc->active) {
+               mutex_lock(&wusbhc->mutex);
+               __wusbhc_keep_alive(wusbhc);
+               mutex_unlock(&wusbhc->mutex);
+               queue_delayed_work(wusbd, &wusbhc->keep_alive_timer,
+                                  (wusbhc->trust_timeout * CONFIG_HZ)/1000/2);
+       }
+       d_fnend(5, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc);
+       return;
+}
+
+/*
+ * Find the wusb_dev from its device address.
+ *
+ * The device can be found directly from the address (see
+ * wusb_cack_add() for where the device address is set to port_idx
+ * +2), except when the address is zero.
+ */
+static struct wusb_dev *wusbhc_find_dev_by_addr(struct wusbhc *wusbhc, u8 addr)
+{
+       int p;
+
+       if (addr == 0xff) /* unconnected */
+               return NULL;
+
+       if (addr > 0) {
+               int port = (addr & ~0x80) - 2;
+               if (port < 0 || port >= wusbhc->ports_max)
+                       return NULL;
+               return wusb_port_by_idx(wusbhc, port)->wusb_dev;
+       }
+
+       /* Look for the device with address 0. */
+       for (p = 0; p < wusbhc->ports_max; p++) {
+               struct wusb_dev *wusb_dev = wusb_port_by_idx(wusbhc, p)->wusb_dev;
+               if (wusb_dev && wusb_dev->addr == addr)
+                       return wusb_dev;
+       }
+       return NULL;
+}
+
+/*
+ * Handle a DN_Alive notification (WUSB1.0[7.6.1])
+ *
+ * This just updates the device activity timestamp and then refreshes
+ * the keep alive IE.
+ *
+ * @wusbhc shall be referenced and unlocked
+ */
+static void wusbhc_handle_dn_alive(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+       struct device *dev = wusbhc->dev;
+
+       d_printf(2, dev, "DN ALIVE: device 0x%02x pong\n", wusb_dev->addr);
+
+       mutex_lock(&wusbhc->mutex);
+       wusb_dev->entry_ts = jiffies;
+       __wusbhc_keep_alive(wusbhc);
+       mutex_unlock(&wusbhc->mutex);
+}
+
+/*
+ * Handle a DN_Connect notification (WUSB1.0[7.6.1])
+ *
+ * @wusbhc
+ * @pkt_hdr
+ * @size:    Size of the buffer where the notification resides; if the
+ *           notification data suggests there should be more data than
+ *           available, an error will be signaled and the whole buffer
+ *           consumed.
+ *
+ * @wusbhc->mutex shall be held
+ */
+static void wusbhc_handle_dn_connect(struct wusbhc *wusbhc,
+                                    struct wusb_dn_hdr *dn_hdr,
+                                    size_t size)
+{
+       struct device *dev = wusbhc->dev;
+       struct wusb_dn_connect *dnc;
+       char pr_cdid[WUSB_CKHDID_STRSIZE];
+       static const char *beacon_behaviour[] = {
+               "reserved",
+               "self-beacon",
+               "directed-beacon",
+               "no-beacon"
+       };
+
+       d_fnstart(3, dev, "(%p, %p, %zu)\n", wusbhc, dn_hdr, size);
+       if (size < sizeof(*dnc)) {
+               dev_err(dev, "DN CONNECT: short notification (%zu < %zu)\n",
+                       size, sizeof(*dnc));
+               goto out;
+       }
+
+       dnc = container_of(dn_hdr, struct wusb_dn_connect, hdr);
+       ckhdid_printf(pr_cdid, sizeof(pr_cdid), &dnc->CDID);
+       dev_info(dev, "DN CONNECT: device %s @ %x (%s) wants to %s\n",
+                pr_cdid,
+                wusb_dn_connect_prev_dev_addr(dnc),
+                beacon_behaviour[wusb_dn_connect_beacon_behavior(dnc)],
+                wusb_dn_connect_new_connection(dnc) ? "connect" : "reconnect");
+       /* ACK the connect */
+       wusbhc_devconnect_ack(wusbhc, dnc, pr_cdid);
+out:
+       d_fnend(3, dev, "(%p, %p, %zu) = void\n",
+               wusbhc, dn_hdr, size);
+       return;
+}
+
+/*
+ * Handle a DN_Disconnect notification (WUSB1.0[7.6.1])
+ *
+ * Device is going down -- do the disconnect.
+ *
+ * @wusbhc shall be referenced and unlocked
+ */
+static void wusbhc_handle_dn_disconnect(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+       struct device *dev = wusbhc->dev;
+
+       dev_info(dev, "DN DISCONNECT: device 0x%02x going down\n", wusb_dev->addr);
+
+       mutex_lock(&wusbhc->mutex);
+       __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, wusb_dev->port_idx));
+       mutex_unlock(&wusbhc->mutex);
+}
+
+/*
+ * Reset a WUSB device on a HWA
+ *
+ * @wusbhc
+ * @port_idx   Index of the port where the device is
+ *
+ * In Wireless USB, a reset is more or less equivalent to a full
+ * disconnect; so we just do a full disconnect and send the device a
+ * Device Reset IE (WUSB1.0[7.5.11]) giving it a few millisecs (6 MMCs).
+ *
+ * @wusbhc should be refcounted and unlocked
+ */
+int wusbhc_dev_reset(struct wusbhc *wusbhc, u8 port_idx)
+{
+       int result;
+       struct device *dev = wusbhc->dev;
+       struct wusb_dev *wusb_dev;
+       struct wuie_reset *ie;
+
+       d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx);
+       mutex_lock(&wusbhc->mutex);
+       result = 0;
+       wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev;
+       if (wusb_dev == NULL) {
+               /* reset no device? ignore */
+               dev_dbg(dev, "RESET: no device at port %u, ignoring\n",
+                       port_idx);
+               goto error_unlock;
+       }
+       result = -ENOMEM;
+       ie = kzalloc(sizeof(*ie), GFP_KERNEL);
+       if (ie == NULL)
+               goto error_unlock;
+       ie->hdr.bLength = sizeof(ie->hdr) + sizeof(ie->CDID);
+       ie->hdr.bIEIdentifier = WUIE_ID_RESET_DEVICE;
+       ie->CDID = wusb_dev->cdid;
+       result = wusbhc_mmcie_set(wusbhc, 0xff, 6, &ie->hdr);
+       if (result < 0) {
+               dev_err(dev, "RESET: cant's set MMC: %d\n", result);
+               goto error_kfree;
+       }
+       __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx));
+
+       /* 120ms, hopefully 6 MMCs (FIXME) */
+       msleep(120);
+       wusbhc_mmcie_rm(wusbhc, &ie->hdr);
+error_kfree:
+       kfree(ie);
+error_unlock:
+       mutex_unlock(&wusbhc->mutex);
+       d_fnend(3, dev, "(%p, %u) = %d\n", wusbhc, port_idx, result);
+       return result;
+}
+
+/*
+ * Handle a Device Notification coming a host
+ *
+ * The Device Notification comes from a host (HWA, DWA or WHCI)
+ * wrapped in a set of headers. Somebody else has peeled off those
+ * headers for us and we just get one Device Notifications.
+ *
+ * Invalid DNs (e.g., too short) are discarded.
+ *
+ * @wusbhc shall be referenced
+ *
+ * FIXMES:
+ *  - implement priorities as in WUSB1.0[Table 7-55]?
+ */
+void wusbhc_handle_dn(struct wusbhc *wusbhc, u8 srcaddr,
+                     struct wusb_dn_hdr *dn_hdr, size_t size)
+{
+       struct device *dev = wusbhc->dev;
+       struct wusb_dev *wusb_dev;
+
+       d_fnstart(3, dev, "(%p, %p)\n", wusbhc, dn_hdr);
+
+       if (size < sizeof(struct wusb_dn_hdr)) {
+               dev_err(dev, "DN data shorter than DN header (%d < %d)\n",
+                       (int)size, (int)sizeof(struct wusb_dn_hdr));
+               goto out;
+       }
+
+       wusb_dev = wusbhc_find_dev_by_addr(wusbhc, srcaddr);
+       if (wusb_dev == NULL && dn_hdr->bType != WUSB_DN_CONNECT) {
+               dev_dbg(dev, "ignoring DN %d from unconnected device %02x\n",
+                       dn_hdr->bType, srcaddr);
+               goto out;
+       }
+
+       switch (dn_hdr->bType) {
+       case WUSB_DN_CONNECT:
+               wusbhc_handle_dn_connect(wusbhc, dn_hdr, size);
+               break;
+       case WUSB_DN_ALIVE:
+               wusbhc_handle_dn_alive(wusbhc, wusb_dev);
+               break;
+       case WUSB_DN_DISCONNECT:
+               wusbhc_handle_dn_disconnect(wusbhc, wusb_dev);
+               break;
+       case WUSB_DN_MASAVAILCHANGED:
+       case WUSB_DN_RWAKE:
+       case WUSB_DN_SLEEP:
+               /* FIXME: handle these DNs. */
+               break;
+       case WUSB_DN_EPRDY:
+               /* The hardware handles these. */
+               break;
+       default:
+               dev_warn(dev, "unknown DN %u (%d octets) from %u\n",
+                        dn_hdr->bType, (int)size, srcaddr);
+       }
+out:
+       d_fnend(3, dev, "(%p, %p) = void\n", wusbhc, dn_hdr);
+       return;
+}
+EXPORT_SYMBOL_GPL(wusbhc_handle_dn);
+
+/*
+ * Disconnect a WUSB device from a the cluster
+ *
+ * @wusbhc
+ * @port     Fake port where the device is (wusbhc index, not USB port number).
+ *
+ * In Wireless USB, a disconnect is basically telling the device he is
+ * being disconnected and forgetting about him.
+ *
+ * We send the device a Device Disconnect IE (WUSB1.0[7.5.11]) for 100
+ * ms and then keep going.
+ *
+ * We don't do much in case of error; we always pretend we disabled
+ * the port and disconnected the device. If physically the request
+ * didn't get there (many things can fail in the way there), the stack
+ * will reject the device's communication attempts.
+ *
+ * @wusbhc should be refcounted and locked
+ */
+void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port_idx)
+{
+       int result;
+       struct device *dev = wusbhc->dev;
+       struct wusb_dev *wusb_dev;
+       struct wuie_disconnect *ie;
+
+       d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx);
+       result = 0;
+       wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev;
+       if (wusb_dev == NULL) {
+               /* reset no device? ignore */
+               dev_dbg(dev, "DISCONNECT: no device at port %u, ignoring\n",
+                       port_idx);
+               goto error;
+       }
+       __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx));
+
+       result = -ENOMEM;
+       ie = kzalloc(sizeof(*ie), GFP_KERNEL);
+       if (ie == NULL)
+               goto error;
+       ie->hdr.bLength = sizeof(*ie);
+       ie->hdr.bIEIdentifier = WUIE_ID_DEVICE_DISCONNECT;
+       ie->bDeviceAddress = wusb_dev->addr;
+       result = wusbhc_mmcie_set(wusbhc, 0, 0, &ie->hdr);
+       if (result < 0) {
+               dev_err(dev, "DISCONNECT: can't set MMC: %d\n", result);
+               goto error_kfree;
+       }
+
+       /* 120ms, hopefully 6 MMCs */
+       msleep(100);
+       wusbhc_mmcie_rm(wusbhc, &ie->hdr);
+error_kfree:
+       kfree(ie);
+error:
+       d_fnend(3, dev, "(%p, %u) = %d\n", wusbhc, port_idx, result);
+       return;
+}
+
+static void wusb_cap_descr_printf(const unsigned level, struct device *dev,
+                                 const struct usb_wireless_cap_descriptor *wcd)
+{
+       d_printf(level, dev,
+                "WUSB Capability Descriptor\n"
+                "  bDevCapabilityType          0x%02x\n"
+                "  bmAttributes                0x%02x\n"
+                "  wPhyRates                   0x%04x\n"
+                "  bmTFITXPowerInfo            0x%02x\n"
+                "  bmFFITXPowerInfo            0x%02x\n"
+                "  bmBandGroup                 0x%04x\n"
+                "  bReserved                   0x%02x\n",
+                wcd->bDevCapabilityType,
+                wcd->bmAttributes,
+                le16_to_cpu(wcd->wPHYRates),
+                wcd->bmTFITXPowerInfo,
+                wcd->bmFFITXPowerInfo,
+                wcd->bmBandGroup,
+                wcd->bReserved);
+}
+
+/*
+ * Walk over the BOS descriptor, verify and grok it
+ *
+ * @usb_dev: referenced
+ * @wusb_dev: referenced and unlocked
+ *
+ * The BOS descriptor is defined at WUSB1.0[7.4.1], and it defines a
+ * "flexible" way to wrap all kinds of descriptors inside an standard
+ * descriptor (wonder why they didn't use normal descriptors,
+ * btw). Not like they lack code.
+ *
+ * At the end we go to look for the WUSB Device Capabilities
+ * (WUSB1.0[7.4.1.1]) that is wrapped in a device capability descriptor
+ * that is part of the BOS descriptor set. That tells us what does the
+ * device support (dual role, beacon type, UWB PHY rates).
+ */
+static int wusb_dev_bos_grok(struct usb_device *usb_dev,
+                            struct wusb_dev *wusb_dev,
+                            struct usb_bos_descriptor *bos, size_t desc_size)
+{
+       ssize_t result;
+       struct device *dev = &usb_dev->dev;
+       void *itr, *top;
+
+       /* Walk over BOS capabilities, verify them */
+       itr = (void *)bos + sizeof(*bos);
+       top = itr + desc_size - sizeof(*bos);
+       while (itr < top) {
+               struct usb_dev_cap_header *cap_hdr = itr;
+               size_t cap_size;
+               u8 cap_type;
+               if (top - itr < sizeof(*cap_hdr)) {
+                       dev_err(dev, "Device BUG? premature end of BOS header "
+                               "data [offset 0x%02x]: only %zu bytes left\n",
+                               (int)(itr - (void *)bos), top - itr);
+                       result = -ENOSPC;
+                       goto error_bad_cap;
+               }
+               cap_size = cap_hdr->bLength;
+               cap_type = cap_hdr->bDevCapabilityType;
+               d_printf(4, dev, "BOS Capability: 0x%02x (%zu bytes)\n",
+                        cap_type, cap_size);
+               if (cap_size == 0)
+                       break;
+               if (cap_size > top - itr) {
+                       dev_err(dev, "Device BUG? premature end of BOS data "
+                               "[offset 0x%02x cap %02x %zu bytes]: "
+                               "only %zu bytes left\n",
+                               (int)(itr - (void *)bos),
+                               cap_type, cap_size, top - itr);
+                       result = -EBADF;
+                       goto error_bad_cap;
+               }
+               d_dump(3, dev, itr, cap_size);
+               switch (cap_type) {
+               case USB_CAP_TYPE_WIRELESS_USB:
+                       if (cap_size != sizeof(*wusb_dev->wusb_cap_descr))
+                               dev_err(dev, "Device BUG? WUSB Capability "
+                                       "descriptor is %zu bytes vs %zu "
+                                       "needed\n", cap_size,
+                                       sizeof(*wusb_dev->wusb_cap_descr));
+                       else {
+                               wusb_dev->wusb_cap_descr = itr;
+                               wusb_cap_descr_printf(3, dev, itr);
+                       }
+                       break;
+               default:
+                       dev_err(dev, "BUG? Unknown BOS capability 0x%02x "
+                               "(%zu bytes) at offset 0x%02x\n", cap_type,
+                               cap_size, (int)(itr - (void *)bos));
+               }
+               itr += cap_size;
+       }
+       result = 0;
+error_bad_cap:
+       return result;
+}
+
+/*
+ * Add information from the BOS descriptors to the device
+ *
+ * @usb_dev: referenced
+ * @wusb_dev: referenced and unlocked
+ *
+ * So what we do is we alloc a space for the BOS descriptor of 64
+ * bytes; read the first four bytes which include the wTotalLength
+ * field (WUSB1.0[T7-26]) and if it fits in those 64 bytes, read the
+ * whole thing. If not we realloc to that size.
+ *
+ * Then we call the groking function, that will fill up
+ * wusb_dev->wusb_cap_descr, which is what we'll need later on.
+ */
+static int wusb_dev_bos_add(struct usb_device *usb_dev,
+                           struct wusb_dev *wusb_dev)
+{
+       ssize_t result;
+       struct device *dev = &usb_dev->dev;
+       struct usb_bos_descriptor *bos;
+       size_t alloc_size = 32, desc_size = 4;
+
+       bos = kmalloc(alloc_size, GFP_KERNEL);
+       if (bos == NULL)
+               return -ENOMEM;
+       result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size);
+       if (result < 4) {
+               dev_err(dev, "Can't get BOS descriptor or too short: %zd\n",
+                       result);
+               goto error_get_descriptor;
+       }
+       desc_size = le16_to_cpu(bos->wTotalLength);
+       if (desc_size >= alloc_size) {
+               kfree(bos);
+               alloc_size = desc_size;
+               bos = kmalloc(alloc_size, GFP_KERNEL);
+               if (bos == NULL)
+                       return -ENOMEM;
+       }
+       result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size);
+       if (result < 0 || result != desc_size) {
+               dev_err(dev, "Can't get  BOS descriptor or too short (need "
+                       "%zu bytes): %zd\n", desc_size, result);
+               goto error_get_descriptor;
+       }
+       if (result < sizeof(*bos)
+           || le16_to_cpu(bos->wTotalLength) != desc_size) {
+               dev_err(dev, "Can't get  BOS descriptor or too short (need "
+                       "%zu bytes): %zd\n", desc_size, result);
+               goto error_get_descriptor;
+       }
+       d_printf(2, dev, "Got BOS descriptor %zd bytes, %u capabilities\n",
+                result, bos->bNumDeviceCaps);
+       d_dump(2, dev, bos, result);
+       result = wusb_dev_bos_grok(usb_dev, wusb_dev, bos, result);
+       if (result < 0)
+               goto error_bad_bos;
+       wusb_dev->bos = bos;
+       return 0;
+
+error_bad_bos:
+error_get_descriptor:
+       kfree(bos);
+       wusb_dev->wusb_cap_descr = NULL;
+       return result;
+}
+
+static void wusb_dev_bos_rm(struct wusb_dev *wusb_dev)
+{
+       kfree(wusb_dev->bos);
+       wusb_dev->wusb_cap_descr = NULL;
+};
+
+static struct usb_wireless_cap_descriptor wusb_cap_descr_default = {
+       .bLength = sizeof(wusb_cap_descr_default),
+       .bDescriptorType = USB_DT_DEVICE_CAPABILITY,
+       .bDevCapabilityType = USB_CAP_TYPE_WIRELESS_USB,
+
+       .bmAttributes = USB_WIRELESS_BEACON_NONE,
+       .wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53),
+       .bmTFITXPowerInfo = 0,
+       .bmFFITXPowerInfo = 0,
+       .bmBandGroup = cpu_to_le16(0x0001),     /* WUSB1.0[7.4.1] bottom */
+       .bReserved = 0
+};
+
+/*
+ * USB stack's device addition Notifier Callback
+ *
+ * Called from drivers/usb/core/hub.c when a new device is added; we
+ * use this hook to perform certain WUSB specific setup work on the
+ * new device. As well, it is the first time we can connect the
+ * wusb_dev and the usb_dev. So we note it down in wusb_dev and take a
+ * reference that we'll drop.
+ *
+ * First we need to determine if the device is a WUSB device (else we
+ * ignore it). For that we use the speed setting (USB_SPEED_VARIABLE)
+ * [FIXME: maybe we'd need something more definitive]. If so, we track
+ * it's usb_busd and from there, the WUSB HC.
+ *
+ * Because all WUSB HCs are contained in a 'struct wusbhc', voila, we
+ * get the wusbhc for the device.
+ *
+ * We have a reference on @usb_dev (as we are called at the end of its
+ * enumeration).
+ *
+ * NOTE: @usb_dev locked
+ */
+static void wusb_dev_add_ncb(struct usb_device *usb_dev)
+{
+       int result = 0;
+       struct wusb_dev *wusb_dev;
+       struct wusbhc *wusbhc;
+       struct device *dev = &usb_dev->dev;
+       u8 port_idx;
+
+       if (usb_dev->wusb == 0 || usb_dev->devnum == 1)
+               return;         /* skip non wusb and wusb RHs */
+
+       d_fnstart(3, dev, "(usb_dev %p)\n", usb_dev);
+
+       wusbhc = wusbhc_get_by_usb_dev(usb_dev);
+       if (wusbhc == NULL)
+               goto error_nodev;
+       mutex_lock(&wusbhc->mutex);
+       wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev);
+       port_idx = wusb_port_no_to_idx(usb_dev->portnum);
+       mutex_unlock(&wusbhc->mutex);
+       if (wusb_dev == NULL)
+               goto error_nodev;
+       wusb_dev->usb_dev = usb_get_dev(usb_dev);
+       usb_dev->wusb_dev = wusb_dev_get(wusb_dev);
+       result = wusb_dev_sec_add(wusbhc, usb_dev, wusb_dev);
+       if (result < 0) {
+               dev_err(dev, "Cannot enable security: %d\n", result);
+               goto error_sec_add;
+       }
+       /* Now query the device for it's BOS and attach it to wusb_dev */
+       result = wusb_dev_bos_add(usb_dev, wusb_dev);
+       if (result < 0) {
+               dev_err(dev, "Cannot get BOS descriptors: %d\n", result);
+               goto error_bos_add;
+       }
+       result = wusb_dev_sysfs_add(wusbhc, usb_dev, wusb_dev);
+       if (result < 0)
+               goto error_add_sysfs;
+out:
+       wusb_dev_put(wusb_dev);
+       wusbhc_put(wusbhc);
+error_nodev:
+       d_fnend(3, dev, "(usb_dev %p) = void\n", usb_dev);
+       return;
+
+       wusb_dev_sysfs_rm(wusb_dev);
+error_add_sysfs:
+       wusb_dev_bos_rm(wusb_dev);
+error_bos_add:
+       wusb_dev_sec_rm(wusb_dev);
+error_sec_add:
+       mutex_lock(&wusbhc->mutex);
+       __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx));
+       mutex_unlock(&wusbhc->mutex);
+       goto out;
+}
+
+/*
+ * Undo all the steps done at connection by the notifier callback
+ *
+ * NOTE: @usb_dev locked
+ */
+static void wusb_dev_rm_ncb(struct usb_device *usb_dev)
+{
+       struct wusb_dev *wusb_dev = usb_dev->wusb_dev;
+
+       if (usb_dev->wusb == 0 || usb_dev->devnum == 1)
+               return;         /* skip non wusb and wusb RHs */
+
+       wusb_dev_sysfs_rm(wusb_dev);
+       wusb_dev_bos_rm(wusb_dev);
+       wusb_dev_sec_rm(wusb_dev);
+       wusb_dev->usb_dev = NULL;
+       usb_dev->wusb_dev = NULL;
+       wusb_dev_put(wusb_dev);
+       usb_put_dev(usb_dev);
+}
+
+/*
+ * Handle notifications from the USB stack (notifier call back)
+ *
+ * This is called when the USB stack does a
+ * usb_{bus,device}_{add,remove}() so we can do WUSB specific
+ * handling. It is called with [for the case of
+ * USB_DEVICE_{ADD,REMOVE} with the usb_dev locked.
+ */
+int wusb_usb_ncb(struct notifier_block *nb, unsigned long val,
+                void *priv)
+{
+       int result = NOTIFY_OK;
+
+       switch (val) {
+       case USB_DEVICE_ADD:
+               wusb_dev_add_ncb(priv);
+               break;
+       case USB_DEVICE_REMOVE:
+               wusb_dev_rm_ncb(priv);
+               break;
+       case USB_BUS_ADD:
+               /* ignore (for now) */
+       case USB_BUS_REMOVE:
+               break;
+       default:
+               WARN_ON(1);
+               result = NOTIFY_BAD;
+       };
+       return result;
+}
+
+/*
+ * Return a referenced wusb_dev given a @wusbhc and @usb_dev
+ */
+struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *wusbhc,
+                                          struct usb_device *usb_dev)
+{
+       struct wusb_dev *wusb_dev;
+       u8 port_idx;
+
+       port_idx = wusb_port_no_to_idx(usb_dev->portnum);
+       BUG_ON(port_idx > wusbhc->ports_max);
+       wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev;
+       if (wusb_dev != NULL)           /* ops, device is gone */
+               wusb_dev_get(wusb_dev);
+       return wusb_dev;
+}
+EXPORT_SYMBOL_GPL(__wusb_dev_get_by_usb_dev);
+
+void wusb_dev_destroy(struct kref *_wusb_dev)
+{
+       struct wusb_dev *wusb_dev
+               = container_of(_wusb_dev, struct wusb_dev, refcnt);
+       list_del_init(&wusb_dev->cack_node);
+       wusb_dev_free(wusb_dev);
+       d_fnend(1, NULL, "%s (wusb_dev %p) = void\n", __func__, wusb_dev);
+}
+EXPORT_SYMBOL_GPL(wusb_dev_destroy);
+
+/*
+ * Create all the device connect handling infrastructure
+ *
+ * This is basically the device info array, Connect Acknowledgement
+ * (cack) lists, keep-alive timers (and delayed work thread).
+ */
+int wusbhc_devconnect_create(struct wusbhc *wusbhc)
+{
+       d_fnstart(3, wusbhc->dev, "(wusbhc %p)\n", wusbhc);
+
+       wusbhc->keep_alive_ie.hdr.bIEIdentifier = WUIE_ID_KEEP_ALIVE;
+       wusbhc->keep_alive_ie.hdr.bLength = sizeof(wusbhc->keep_alive_ie.hdr);
+       INIT_DELAYED_WORK(&wusbhc->keep_alive_timer, wusbhc_keep_alive_run);
+
+       wusbhc->cack_ie.hdr.bIEIdentifier = WUIE_ID_CONNECTACK;
+       wusbhc->cack_ie.hdr.bLength = sizeof(wusbhc->cack_ie.hdr);
+       INIT_LIST_HEAD(&wusbhc->cack_list);
+
+       d_fnend(3, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc);
+       return 0;
+}
+
+/*
+ * Release all resources taken by the devconnect stuff
+ */
+void wusbhc_devconnect_destroy(struct wusbhc *wusbhc)
+{
+       d_fnstart(3, wusbhc->dev, "(wusbhc %p)\n", wusbhc);
+       d_fnend(3, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc);
+}
+
+/*
+ * wusbhc_devconnect_start - start accepting device connections
+ * @wusbhc: the WUSB HC
+ *
+ * Sets the Host Info IE to accept all new connections.
+ *
+ * FIXME: This also enables the keep alives but this is not necessary
+ * until there are connected and authenticated devices.
+ */
+int wusbhc_devconnect_start(struct wusbhc *wusbhc,
+                           const struct wusb_ckhdid *chid)
+{
+       struct device *dev = wusbhc->dev;
+       struct wuie_host_info *hi;
+       int result;
+
+       hi = kzalloc(sizeof(*hi), GFP_KERNEL);
+       if (hi == NULL)
+               return -ENOMEM;
+
+       hi->hdr.bLength       = sizeof(*hi);
+       hi->hdr.bIEIdentifier = WUIE_ID_HOST_INFO;
+       hi->attributes        = cpu_to_le16((wusbhc->rsv->stream << 3) | WUIE_HI_CAP_ALL);
+       hi->CHID              = *chid;
+       result = wusbhc_mmcie_set(wusbhc, 0, 0, &hi->hdr);
+       if (result < 0) {
+               dev_err(dev, "Cannot add Host Info MMCIE: %d\n", result);
+               goto error_mmcie_set;
+       }
+       wusbhc->wuie_host_info = hi;
+
+       queue_delayed_work(wusbd, &wusbhc->keep_alive_timer,
+                          (wusbhc->trust_timeout*CONFIG_HZ)/1000/2);
+
+       return 0;
+
+error_mmcie_set:
+       kfree(hi);
+       return result;
+}
+
+/*
+ * wusbhc_devconnect_stop - stop managing connected devices
+ * @wusbhc: the WUSB HC
+ *
+ * Removes the Host Info IE and stops the keep alives.
+ *
+ * FIXME: should this disconnect all devices?
+ */
+void wusbhc_devconnect_stop(struct wusbhc *wusbhc)
+{
+       cancel_delayed_work_sync(&wusbhc->keep_alive_timer);
+       WARN_ON(!list_empty(&wusbhc->cack_list));
+
+       wusbhc_mmcie_rm(wusbhc, &wusbhc->wuie_host_info->hdr);
+       kfree(wusbhc->wuie_host_info);
+       wusbhc->wuie_host_info = NULL;
+}
+
+/*
+ * wusb_set_dev_addr - set the WUSB device address used by the host
+ * @wusbhc: the WUSB HC the device is connect to
+ * @wusb_dev: the WUSB device
+ * @addr: new device address
+ */
+int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, u8 addr)
+{
+       int result;
+
+       wusb_dev->addr = addr;
+       result = wusbhc->dev_info_set(wusbhc, wusb_dev);
+       if (result < 0)
+               dev_err(wusbhc->dev, "device %d: failed to set device "
+                       "address\n", wusb_dev->port_idx);
+       else
+               dev_info(wusbhc->dev, "device %d: %s addr %u\n",
+                        wusb_dev->port_idx,
+                        (addr & WUSB_DEV_ADDR_UNAUTH) ? "unauth" : "auth",
+                        wusb_dev->addr);
+
+       return result;
+}
diff --git a/drivers/usb/wusbcore/mmc.c b/drivers/usb/wusbcore/mmc.c
new file mode 100644 (file)
index 0000000..cfa77a0
--- /dev/null
@@ -0,0 +1,321 @@
+/*
+ * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8])
+ * MMC (Microscheduled Management Command) handling
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * WUIEs and MMC IEs...well, they are almost the same at the end. MMC
+ * IEs are Wireless USB IEs that go into the MMC period...[what is
+ * that? look in Design-overview.txt].
+ *
+ *
+ * This is a simple subsystem to keep track of which IEs are being
+ * sent by the host in the MMC period.
+ *
+ * For each WUIE we ask to send, we keep it in an array, so we can
+ * request its removal later, or replace the content. They are tracked
+ * by pointer, so be sure to use the same pointer if you want to
+ * remove it or update the contents.
+ *
+ * FIXME:
+ *  - add timers that autoremove intervalled IEs?
+ */
+#include <linux/usb/wusb.h>
+#include "wusbhc.h"
+
+/* Initialize the MMCIEs handling mechanism */
+int wusbhc_mmcie_create(struct wusbhc *wusbhc)
+{
+       u8 mmcies = wusbhc->mmcies_max;
+       wusbhc->mmcie = kcalloc(mmcies, sizeof(wusbhc->mmcie[0]), GFP_KERNEL);
+       if (wusbhc->mmcie == NULL)
+               return -ENOMEM;
+       mutex_init(&wusbhc->mmcie_mutex);
+       return 0;
+}
+
+/* Release resources used by the MMCIEs handling mechanism */
+void wusbhc_mmcie_destroy(struct wusbhc *wusbhc)
+{
+       kfree(wusbhc->mmcie);
+}
+
+/*
+ * Add or replace an MMC Wireless USB IE.
+ *
+ * @interval:    See WUSB1.0[8.5.3.1]
+ * @repeat_cnt:  See WUSB1.0[8.5.3.1]
+ * @handle:      See WUSB1.0[8.5.3.1]
+ * @wuie:        Pointer to the header of the WUSB IE data to add.
+ *               MUST BE allocated in a kmalloc buffer (no stack or
+ *               vmalloc).
+ *               THE CALLER ALWAYS OWNS THE POINTER (we don't free it
+ *               on remove, we just forget about it).
+ * @returns:     0 if ok, < 0 errno code on error.
+ *
+ * Goes over the *whole* @wusbhc->mmcie array looking for (a) the
+ * first free spot and (b) if @wuie is already in the array (aka:
+ * transmitted in the MMCs) the spot were it is.
+ *
+ * If present, we "overwrite it" (update).
+ *
+ *
+ * NOTE: Need special ordering rules -- see below WUSB1.0 Table 7-38.
+ *       The host uses the handle as the 'sort' index. We
+ *       allocate the last one always for the WUIE_ID_HOST_INFO, and
+ *       the rest, first come first serve in inverse order.
+ *
+ *       Host software must make sure that it adds the other IEs in
+ *       the right order... the host hardware is responsible for
+ *       placing the WCTA IEs in the right place with the other IEs
+ *       set by host software.
+ *
+ * NOTE: we can access wusbhc->wa_descr without locking because it is
+ *       read only.
+ */
+int wusbhc_mmcie_set(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+                    struct wuie_hdr *wuie)
+{
+       int result = -ENOBUFS;
+       unsigned handle, itr;
+
+       /* Search a handle, taking into account the ordering */
+       mutex_lock(&wusbhc->mmcie_mutex);
+       switch (wuie->bIEIdentifier) {
+       case WUIE_ID_HOST_INFO:
+               /* Always last */
+               handle = wusbhc->mmcies_max - 1;
+               break;
+       case WUIE_ID_ISOCH_DISCARD:
+               dev_err(wusbhc->dev, "Special ordering case for WUIE ID 0x%x "
+                       "unimplemented\n", wuie->bIEIdentifier);
+               result = -ENOSYS;
+               goto error_unlock;
+       default:
+               /* search for it or find the last empty slot */
+               handle = ~0;
+               for (itr = 0; itr < wusbhc->mmcies_max - 1; itr++) {
+                       if (wusbhc->mmcie[itr] == wuie) {
+                               handle = itr;
+                               break;
+                       }
+                       if (wusbhc->mmcie[itr] == NULL)
+                               handle = itr;
+               }
+               if (handle == ~0)
+                       goto error_unlock;
+       }
+       result = (wusbhc->mmcie_add)(wusbhc, interval, repeat_cnt, handle,
+                                    wuie);
+       if (result >= 0)
+               wusbhc->mmcie[handle] = wuie;
+error_unlock:
+       mutex_unlock(&wusbhc->mmcie_mutex);
+       return result;
+}
+EXPORT_SYMBOL_GPL(wusbhc_mmcie_set);
+
+/*
+ * Remove an MMC IE previously added with wusbhc_mmcie_set()
+ *
+ * @wuie       Pointer used to add the WUIE
+ */
+void wusbhc_mmcie_rm(struct wusbhc *wusbhc, struct wuie_hdr *wuie)
+{
+       int result;
+       unsigned handle, itr;
+
+       mutex_lock(&wusbhc->mmcie_mutex);
+       for (itr = 0; itr < wusbhc->mmcies_max; itr++) {
+               if (wusbhc->mmcie[itr] == wuie) {
+                       handle = itr;
+                       goto found;
+               }
+       }
+       mutex_unlock(&wusbhc->mmcie_mutex);
+       return;
+
+found:
+       result = (wusbhc->mmcie_rm)(wusbhc, handle);
+       if (result == 0)
+               wusbhc->mmcie[itr] = NULL;
+       mutex_unlock(&wusbhc->mmcie_mutex);
+}
+EXPORT_SYMBOL_GPL(wusbhc_mmcie_rm);
+
+/*
+ * wusbhc_start - start transmitting MMCs and accepting connections
+ * @wusbhc: the HC to start
+ * @chid: the CHID to use for this host
+ *
+ * Establishes a cluster reservation, enables device connections, and
+ * starts MMCs with appropriate DNTS parameters.
+ */
+int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid)
+{
+       int result;
+       struct device *dev = wusbhc->dev;
+
+       WARN_ON(wusbhc->wuie_host_info != NULL);
+
+       result = wusbhc_rsv_establish(wusbhc);
+       if (result < 0) {
+               dev_err(dev, "cannot establish cluster reservation: %d\n",
+                       result);
+               goto error_rsv_establish;
+       }
+
+       result = wusbhc_devconnect_start(wusbhc, chid);
+       if (result < 0) {
+               dev_err(dev, "error enabling device connections: %d\n", result);
+               goto error_devconnect_start;
+       }
+
+       result = wusbhc_sec_start(wusbhc);
+       if (result < 0) {
+               dev_err(dev, "error starting security in the HC: %d\n", result);
+               goto error_sec_start;
+       }
+       /* FIXME: the choice of the DNTS parameters is somewhat
+        * arbitrary */
+       result = wusbhc->set_num_dnts(wusbhc, 0, 15);
+       if (result < 0) {
+               dev_err(dev, "Cannot set DNTS parameters: %d\n", result);
+               goto error_set_num_dnts;
+       }
+       result = wusbhc->start(wusbhc);
+       if (result < 0) {
+               dev_err(dev, "error starting wusbch: %d\n", result);
+               goto error_wusbhc_start;
+       }
+       wusbhc->active = 1;
+       return 0;
+
+error_wusbhc_start:
+       wusbhc_sec_stop(wusbhc);
+error_set_num_dnts:
+error_sec_start:
+       wusbhc_devconnect_stop(wusbhc);
+error_devconnect_start:
+       wusbhc_rsv_terminate(wusbhc);
+error_rsv_establish:
+       return result;
+}
+
+/*
+ * Disconnect all from the WUSB Channel
+ *
+ * Send a Host Disconnect IE in the MMC, wait, don't send it any more
+ */
+static int __wusbhc_host_disconnect_ie(struct wusbhc *wusbhc)
+{
+       int result = -ENOMEM;
+       struct wuie_host_disconnect *host_disconnect_ie;
+       might_sleep();
+       host_disconnect_ie = kmalloc(sizeof(*host_disconnect_ie), GFP_KERNEL);
+       if (host_disconnect_ie == NULL)
+               goto error_alloc;
+       host_disconnect_ie->hdr.bLength       = sizeof(*host_disconnect_ie);
+       host_disconnect_ie->hdr.bIEIdentifier = WUIE_ID_HOST_DISCONNECT;
+       result = wusbhc_mmcie_set(wusbhc, 0, 0, &host_disconnect_ie->hdr);
+       if (result < 0)
+               goto error_mmcie_set;
+
+       /* WUSB1.0[8.5.3.1 & 7.5.2] */
+       msleep(100);
+       wusbhc_mmcie_rm(wusbhc, &host_disconnect_ie->hdr);
+error_mmcie_set:
+       kfree(host_disconnect_ie);
+error_alloc:
+       return result;
+}
+
+/*
+ * wusbhc_stop - stop transmitting MMCs
+ * @wusbhc: the HC to stop
+ *
+ * Send a Host Disconnect IE, wait, remove all the MMCs (stop sending MMCs).
+ *
+ * If we can't allocate a Host Stop IE, screw it, we don't notify the
+ * devices we are disconnecting...
+ */
+void wusbhc_stop(struct wusbhc *wusbhc)
+{
+       if (wusbhc->active) {
+               wusbhc->active = 0;
+               wusbhc->stop(wusbhc);
+               wusbhc_sec_stop(wusbhc);
+               __wusbhc_host_disconnect_ie(wusbhc);
+               wusbhc_devconnect_stop(wusbhc);
+               wusbhc_rsv_terminate(wusbhc);
+       }
+}
+EXPORT_SYMBOL_GPL(wusbhc_stop);
+
+/*
+ * Change the CHID in a WUSB Channel
+ *
+ * If it is just a new CHID, send a Host Disconnect IE and then change
+ * the CHID IE.
+ */
+static int __wusbhc_chid_change(struct wusbhc *wusbhc,
+                               const struct wusb_ckhdid *chid)
+{
+       int result = -ENOSYS;
+       struct device *dev = wusbhc->dev;
+       dev_err(dev, "%s() not implemented yet\n", __func__);
+       return result;
+
+       BUG_ON(wusbhc->wuie_host_info == NULL);
+       __wusbhc_host_disconnect_ie(wusbhc);
+       wusbhc->wuie_host_info->CHID = *chid;
+       result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->wuie_host_info->hdr);
+       if (result < 0)
+               dev_err(dev, "Can't update Host Info WUSB IE: %d\n", result);
+       return result;
+}
+
+/*
+ * Set/reset/update a new CHID
+ *
+ * Depending on the previous state of the MMCs, start, stop or change
+ * the sent MMC. This effectively switches the host controller on and
+ * off (radio wise).
+ */
+int wusbhc_chid_set(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid)
+{
+       int result = 0;
+
+       if (memcmp(chid, &wusb_ckhdid_zero, sizeof(chid)) == 0)
+               chid = NULL;
+
+       mutex_lock(&wusbhc->mutex);
+       if (wusbhc->active) {
+               if (chid)
+                       result = __wusbhc_chid_change(wusbhc, chid);
+               else
+                       wusbhc_stop(wusbhc);
+       } else {
+               if (chid)
+                       wusbhc_start(wusbhc, chid);
+       }
+       mutex_unlock(&wusbhc->mutex);
+       return result;
+}
+EXPORT_SYMBOL_GPL(wusbhc_chid_set);
diff --git a/drivers/usb/wusbcore/pal.c b/drivers/usb/wusbcore/pal.c
new file mode 100644 (file)
index 0000000..7cc51e9
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Wireless USB Host Controller
+ * UWB Protocol Adaptation Layer (PAL) glue.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include "wusbhc.h"
+
+/**
+ * wusbhc_pal_register - register the WUSB HC as a UWB PAL
+ * @wusbhc: the WUSB HC
+ */
+int wusbhc_pal_register(struct wusbhc *wusbhc)
+{
+       uwb_pal_init(&wusbhc->pal);
+
+       wusbhc->pal.name   = "wusbhc";
+       wusbhc->pal.device = wusbhc->usb_hcd.self.controller;
+
+       return uwb_pal_register(wusbhc->uwb_rc, &wusbhc->pal);
+}
+
+/**
+ * wusbhc_pal_register - unregister the WUSB HC as a UWB PAL
+ * @wusbhc: the WUSB HC
+ */
+void wusbhc_pal_unregister(struct wusbhc *wusbhc)
+{
+       uwb_pal_unregister(wusbhc->uwb_rc, &wusbhc->pal);
+}
diff --git a/drivers/usb/wusbcore/reservation.c b/drivers/usb/wusbcore/reservation.c
new file mode 100644 (file)
index 0000000..fc63e77
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * WUSB cluster reservation management
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/uwb.h>
+
+#include "wusbhc.h"
+
+/*
+ * WUSB cluster reservations are multicast reservations with the
+ * broadcast cluster ID (BCID) as the target DevAddr.
+ *
+ * FIXME: consider adjusting the reservation depending on what devices
+ * are attached.
+ */
+
+static int wusbhc_bwa_set(struct wusbhc *wusbhc, u8 stream,
+       const struct uwb_mas_bm *mas)
+{
+       if (mas == NULL)
+               mas = &uwb_mas_bm_zero;
+       return wusbhc->bwa_set(wusbhc, stream, mas);
+}
+
+/**
+ * wusbhc_rsv_complete_cb - WUSB HC reservation complete callback
+ * @rsv:    the reservation
+ *
+ * Either set or clear the HC's view of the reservation.
+ *
+ * FIXME: when a reservation is denied the HC should be stopped.
+ */
+static void wusbhc_rsv_complete_cb(struct uwb_rsv *rsv)
+{
+       struct wusbhc *wusbhc = rsv->pal_priv;
+       struct device *dev = wusbhc->dev;
+       char buf[72];
+
+       switch (rsv->state) {
+       case UWB_RSV_STATE_O_ESTABLISHED:
+               bitmap_scnprintf(buf, sizeof(buf), rsv->mas.bm, UWB_NUM_MAS);
+               dev_dbg(dev, "established reservation: %s\n", buf);
+               wusbhc_bwa_set(wusbhc, rsv->stream, &rsv->mas);
+               break;
+       case UWB_RSV_STATE_NONE:
+               dev_dbg(dev, "removed reservation\n");
+               wusbhc_bwa_set(wusbhc, 0, NULL);
+               wusbhc->rsv = NULL;
+               break;
+       default:
+               dev_dbg(dev, "unexpected reservation state: %d\n", rsv->state);
+               break;
+       }
+}
+
+
+/**
+ * wusbhc_rsv_establish - establish a reservation for the cluster
+ * @wusbhc: the WUSB HC requesting a bandwith reservation
+ */
+int wusbhc_rsv_establish(struct wusbhc *wusbhc)
+{
+       struct uwb_rc *rc = wusbhc->uwb_rc;
+       struct uwb_rsv *rsv;
+       struct uwb_dev_addr bcid;
+       int ret;
+
+       rsv = uwb_rsv_create(rc, wusbhc_rsv_complete_cb, wusbhc);
+       if (rsv == NULL)
+               return -ENOMEM;
+
+       bcid.data[0] = wusbhc->cluster_id;
+       bcid.data[1] = 0;
+
+       rsv->owner = &rc->uwb_dev;
+       rsv->target.type = UWB_RSV_TARGET_DEVADDR;
+       rsv->target.devaddr = bcid;
+       rsv->type = UWB_DRP_TYPE_PRIVATE;
+       rsv->max_mas = 256;
+       rsv->min_mas = 16;  /* one MAS per zone? */
+       rsv->sparsity = 16; /* at least one MAS in each zone? */
+       rsv->is_multicast = true;
+
+       ret = uwb_rsv_establish(rsv);
+       if (ret == 0)
+               wusbhc->rsv = rsv;
+       else
+               uwb_rsv_destroy(rsv);
+       return ret;
+}
+
+
+/**
+ * wusbhc_rsv_terminate - terminate any cluster reservation
+ * @wusbhc: the WUSB host whose reservation is to be terminated
+ */
+void wusbhc_rsv_terminate(struct wusbhc *wusbhc)
+{
+       if (wusbhc->rsv)
+               uwb_rsv_terminate(wusbhc->rsv);
+}
diff --git a/drivers/usb/wusbcore/rh.c b/drivers/usb/wusbcore/rh.c
new file mode 100644 (file)
index 0000000..267a643
--- /dev/null
@@ -0,0 +1,477 @@
+/*
+ * Wireless USB Host Controller
+ * Root Hub operations
+ *
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * We fake a root hub that has fake ports (as many as simultaneous
+ * devices the Wireless USB Host Controller can deal with). For each
+ * port we keep an state in @wusbhc->port[index] identical to the one
+ * specified in the USB2.0[ch11] spec and some extra device
+ * information that complements the one in 'struct usb_device' (as
+ * this lacs a hcpriv pointer).
+ *
+ * Note this is common to WHCI and HWA host controllers.
+ *
+ * Through here we enable most of the state changes that the USB stack
+ * will use to connect or disconnect devices. We need to do some
+ * forced adaptation of Wireless USB device states vs. wired:
+ *
+ *        USB:                 WUSB:
+ *
+ * Port   Powered-off          port slot n/a
+ *        Powered-on           port slot available
+ *        Disconnected         port slot available
+ *        Connected            port slot assigned device
+ *                            device sent DN_Connect
+ *                             device was authenticated
+ *        Enabled              device is authenticated, transitioned
+ *                             from unauth -> auth -> default address
+ *                             -> enabled
+ *        Reset                disconnect
+ *        Disable              disconnect
+ *
+ * This maps the standard USB port states with the WUSB device states
+ * so we can fake ports without having to modify the USB stack.
+ *
+ * FIXME: this process will change in the future
+ *
+ *
+ * ENTRY POINTS
+ *
+ * Our entry points into here are, as in hcd.c, the USB stack root hub
+ * ops defined in the usb_hcd struct:
+ *
+ * wusbhc_rh_status_data()     Provide hub and port status data bitmap
+ *
+ * wusbhc_rh_control()          Execution of all the major requests
+ *                              you can do to a hub (Set|Clear
+ *                              features, get descriptors, status, etc).
+ *
+ * wusbhc_rh_[suspend|resume]() That
+ *
+ * wusbhc_rh_start_port_reset() ??? unimplemented
+ */
+#include "wusbhc.h"
+
+#define D_LOCAL 0
+#include <linux/uwb/debug.h>
+
+/*
+ * Reset a fake port
+ *
+ * This can be called to reset a port from any other state or to reset
+ * it when connecting. In Wireless USB they are different; when doing
+ * a new connect that involves going over the authentication. When
+ * just reseting, its a different story.
+ *
+ * The Linux USB stack resets a port twice before it considers it
+ * enabled, so we have to detect and ignore that.
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ *
+ * Supposedly we are the only thread accesing @wusbhc->port; in any
+ * case, maybe we should move the mutex locking from
+ * wusbhc_devconnect_auth() to here.
+ *
+ * @port_idx refers to the wusbhc's port index, not the USB port number
+ */
+static int wusbhc_rh_port_reset(struct wusbhc *wusbhc, u8 port_idx)
+{
+       int result = 0;
+       struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx);
+
+       d_fnstart(3, wusbhc->dev, "(wusbhc %p port_idx %u)\n",
+                 wusbhc, port_idx);
+       if (port->reset_count == 0) {
+               wusbhc_devconnect_auth(wusbhc, port_idx);
+               port->reset_count++;
+       } else if (port->reset_count == 1)
+               /* see header */
+               d_printf(2, wusbhc->dev, "Ignoring second reset on port_idx "
+                       "%u\n", port_idx);
+       else
+               result = wusbhc_dev_reset(wusbhc, port_idx);
+       d_fnend(3, wusbhc->dev, "(wusbhc %p port_idx %u) = %d\n",
+               wusbhc, port_idx, result);
+       return result;
+}
+
+/*
+ * Return the hub change status bitmap
+ *
+ * The bits in the change status bitmap are cleared when a
+ * ClearPortFeature request is issued (USB2.0[11.12.3,11.12.4].
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ *
+ * WARNING!! This gets called from atomic context; we cannot get the
+ *           mutex--the only race condition we can find is some bit
+ *           changing just after we copy it, which shouldn't be too
+ *           big of a problem [and we can't make it an spinlock
+ *           because other parts need to take it and sleep] .
+ *
+ *           @usb_hcd is refcounted, so it won't dissapear under us
+ *           and before killing a host, the polling of the root hub
+ *           would be stopped anyway.
+ */
+int wusbhc_rh_status_data(struct usb_hcd *usb_hcd, char *_buf)
+{
+       struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+       size_t cnt, size;
+       unsigned long *buf = (unsigned long *) _buf;
+
+       d_fnstart(1, wusbhc->dev, "(wusbhc %p)\n", wusbhc);
+       /* WE DON'T LOCK, see comment */
+       size = wusbhc->ports_max + 1 /* hub bit */;
+       size = (size + 8 - 1) / 8;      /* round to bytes */
+       for (cnt = 0; cnt < wusbhc->ports_max; cnt++)
+               if (wusb_port_by_idx(wusbhc, cnt)->change)
+                       set_bit(cnt + 1, buf);
+               else
+                       clear_bit(cnt + 1, buf);
+       d_fnend(1, wusbhc->dev, "(wusbhc %p) %u, buffer:\n", wusbhc, (int)size);
+       d_dump(1, wusbhc->dev, _buf, size);
+       return size;
+}
+EXPORT_SYMBOL_GPL(wusbhc_rh_status_data);
+
+/*
+ * Return the hub's desciptor
+ *
+ * NOTE: almost cut and paste from ehci-hub.c
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked
+ */
+static int wusbhc_rh_get_hub_descr(struct wusbhc *wusbhc, u16 wValue,
+                                  u16 wIndex,
+                                  struct usb_hub_descriptor *descr,
+                                  u16 wLength)
+{
+       u16 temp = 1 + (wusbhc->ports_max / 8);
+       u8 length = 7 + 2 * temp;
+
+       if (wLength < length)
+               return -ENOSPC;
+       descr->bDescLength = 7 + 2 * temp;
+       descr->bDescriptorType = 0x29;  /* HUB type */
+       descr->bNbrPorts = wusbhc->ports_max;
+       descr->wHubCharacteristics = cpu_to_le16(
+               0x00                    /* All ports power at once */
+               | 0x00                  /* not part of compound device */
+               | 0x10                  /* No overcurrent protection */
+               | 0x00                  /* 8 FS think time FIXME ?? */
+               | 0x00);                /* No port indicators */
+       descr->bPwrOn2PwrGood = 0;
+       descr->bHubContrCurrent = 0;
+       /* two bitmaps:  ports removable, and usb 1.0 legacy PortPwrCtrlMask */
+       memset(&descr->bitmap[0], 0, temp);
+       memset(&descr->bitmap[temp], 0xff, temp);
+       return 0;
+}
+
+/*
+ * Clear a hub feature
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ *
+ * Nothing to do, so no locking needed ;)
+ */
+static int wusbhc_rh_clear_hub_feat(struct wusbhc *wusbhc, u16 feature)
+{
+       int result;
+       struct device *dev = wusbhc->dev;
+
+       d_fnstart(4, dev, "(%p, feature 0x%04u)\n", wusbhc, feature);
+       switch (feature) {
+       case C_HUB_LOCAL_POWER:
+               /* FIXME: maybe plug bit 0 to the power input status,
+                * if any?
+                * see wusbhc_rh_get_hub_status() */
+       case C_HUB_OVER_CURRENT:
+               result = 0;
+               break;
+       default:
+               result = -EPIPE;
+       }
+       d_fnend(4, dev, "(%p, feature 0x%04u), %d\n", wusbhc, feature, result);
+       return result;
+}
+
+/*
+ * Return hub status (it is always zero...)
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ *
+ * Nothing to do, so no locking needed ;)
+ */
+static int wusbhc_rh_get_hub_status(struct wusbhc *wusbhc, u32 *buf,
+                                   u16 wLength)
+{
+       /* FIXME: maybe plug bit 0 to the power input status (if any)? */
+       *buf = 0;
+       return 0;
+}
+
+/*
+ * Set a port feature
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ */
+static int wusbhc_rh_set_port_feat(struct wusbhc *wusbhc, u16 feature,
+                                  u8 selector, u8 port_idx)
+{
+       int result = -EINVAL;
+       struct device *dev = wusbhc->dev;
+
+       d_fnstart(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d)\n",
+                 feature, selector, port_idx);
+
+       if (port_idx > wusbhc->ports_max)
+               goto error;
+
+       switch (feature) {
+               /* According to USB2.0[11.24.2.13]p2, these features
+                * are not required to be implemented. */
+       case USB_PORT_FEAT_C_OVER_CURRENT:
+       case USB_PORT_FEAT_C_ENABLE:
+       case USB_PORT_FEAT_C_SUSPEND:
+       case USB_PORT_FEAT_C_CONNECTION:
+       case USB_PORT_FEAT_C_RESET:
+               result = 0;
+               break;
+
+       case USB_PORT_FEAT_POWER:
+               /* No such thing, but we fake it works */
+               mutex_lock(&wusbhc->mutex);
+               wusb_port_by_idx(wusbhc, port_idx)->status |= USB_PORT_STAT_POWER;
+               mutex_unlock(&wusbhc->mutex);
+               result = 0;
+               break;
+       case USB_PORT_FEAT_RESET:
+               result = wusbhc_rh_port_reset(wusbhc, port_idx);
+               break;
+       case USB_PORT_FEAT_ENABLE:
+       case USB_PORT_FEAT_SUSPEND:
+               dev_err(dev, "(port_idx %d) set feat %d/%d UNIMPLEMENTED\n",
+                       port_idx, feature, selector);
+               result = -ENOSYS;
+               break;
+       default:
+               dev_err(dev, "(port_idx %d) set feat %d/%d UNKNOWN\n",
+                       port_idx, feature, selector);
+               result = -EPIPE;
+               break;
+       }
+error:
+       d_fnend(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d) = %d\n",
+               feature, selector, port_idx, result);
+       return result;
+}
+
+/*
+ * Clear a port feature...
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ */
+static int wusbhc_rh_clear_port_feat(struct wusbhc *wusbhc, u16 feature,
+                                    u8 selector, u8 port_idx)
+{
+       int result = -EINVAL;
+       struct device *dev = wusbhc->dev;
+
+       d_fnstart(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d)\n",
+                 wusbhc, feature, selector, port_idx);
+
+       if (port_idx > wusbhc->ports_max)
+               goto error;
+
+       mutex_lock(&wusbhc->mutex);
+       result = 0;
+       switch (feature) {
+       case USB_PORT_FEAT_POWER:       /* fake port always on */
+               /* According to USB2.0[11.24.2.7.1.4], no need to implement? */
+       case USB_PORT_FEAT_C_OVER_CURRENT:
+               break;
+       case USB_PORT_FEAT_C_RESET:
+               wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_RESET;
+               break;
+       case USB_PORT_FEAT_C_CONNECTION:
+               wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_CONNECTION;
+               br