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
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).
--- /dev/null
+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.
Users:
PowerTOP <power@bughost.org>
http://www.lesswatts.org/projects/powertop/
+
+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).
--- /dev/null
+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.
--- /dev/null
+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.
--- /dev/null
+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.
--- /dev/null
+
+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)
+
--- /dev/null
+#! /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
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
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
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
source "drivers/usb/Kconfig"
+source "drivers/uwb/Kconfig"
+
source "drivers/mmc/Kconfig"
source "drivers/leds/Kconfig"
source "drivers/usb/Kconfig"
+source "drivers/uwb/Kconfig"
+
source "arch/cris/Kconfig.debug"
source "security/Kconfig"
source "drivers/usb/Kconfig"
+source "drivers/uwb/Kconfig"
+
endmenu
source "fs/Kconfig"
source "drivers/usb/Kconfig"
+source "drivers/uwb/Kconfig"
+
source "drivers/mmc/Kconfig"
source "drivers/memstick/Kconfig"
obj-$(CONFIG_SSB) += ssb/
obj-$(CONFIG_VIRTIO) += virtio/
obj-$(CONFIG_REGULATOR) += regulator/
+obj-$(CONFIG_UWB) += uwb/
source "drivers/usb/mon/Kconfig"
+source "drivers/usb/wusbcore/Kconfig"
+
source "drivers/usb/host/Kconfig"
source "drivers/usb/musb/Kconfig"
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/
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".
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
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
--- /dev/null
+/*
+ * 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");
--- /dev/null
+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
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#
+# 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.
+
--- /dev/null
+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
--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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 */
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+
+ d_fnstart(3, dev, "(wusbhc %p port_idx %d)\n", wusbhc, port_idx);
+ /* 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) {
+ if (printk_ratelimit())
+ dev_err(dev, "DN CONNECT: no memory to process %s's %s "
+ "request\n", pr_cdid,
+ new_connection ? "connect" : "reconnect");
+ 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);
+ d_fnend(3, dev, "(wusbhc %p port_idx %d)\n", wusbhc, port_idx);
+ 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))) {
+ if (printk_ratelimit())
+ dev_err(dev, "Already handling dev %s "
+ " (it might be slow)\n", pr_cdid);
+ 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)
+{
+ int result;
+ 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;
+
+ d_fnstart(5, dev, "(wusbhc %p)\n", wusbhc);
+ 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) {
+ result = wusbhc_mmcie_set(wusbhc, 10, 5, &ie->hdr);
+ if (result < 0 && printk_ratelimit())
+ dev_err(dev, "KEEPALIVE: can't set MMC: %d\n", result);
+ } else if (old_keep_alives != 0)
+ wusbhc_mmcie_rm(wusbhc, &ie->hdr);
+ d_fnend(5, dev, "(wusbhc %p) = void\n", wusbhc);
+}
+
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 = kzalloc(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;
+ struct device *dev = wusbhc->dev;
+ 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) {
+ if (printk_ratelimit())
+ dev_err(dev, "MMC handle space exhausted\n");
+ 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;
+ struct device *dev = wusbhc->dev;
+ 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;
+ else if (printk_ratelimit())
+ dev_err(dev, "MMC: Failed to remove IE %p (0x%02x)\n",
+ wuie, wuie->bIEIdentifier);
+ mutex_unlock(&wusbhc->mmcie_mutex);
+ return;
+}
+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);
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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