Merge branch 'for-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/dvrabel/uwb
* 'for-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/dvrabel/uwb: (47 commits) uwb: wrong sizeof argument in mac address compare uwb: don't use printk_ratelimit() so often uwb: use kcalloc where appropriate uwb: use time_after() when purging stale beacons uwb: add credits for the original developers of the UWB/WUSB/WLP subsystems uwb: add entries in the MAINTAINERS file uwb: depend on EXPERIMENTAL wusb: wusb-cbaf (CBA driver) sysfs ABI simplification uwb: document UWB and WUSB sysfs files uwb: add symlinks in sysfs between radio controllers and PALs uwb: dont tranmit identification IEs uwb: i1480/GUWA100U: fix firmware download issues uwb: i1480: remove MAC/PHY information checking function uwb: add Intel i1480 HWA to the UWB RC quirk table uwb: disable command/event filtering for D-Link DUB-1210 uwb: initialize the debug sub-system uwb: Fix handling IEs with empty IE data in uwb_est_get_size() wusb: fix bmRequestType for Abort RPipe request wusb: fix error path for wusb_set_dev_addr() wusb: add HWA host controller driver ...
This commit is contained in:
Коммит
9779a8325a
11
CREDITS
11
CREDITS
|
@ -598,6 +598,11 @@ S: Tamsui town, Taipei county,
|
|||
S: Taiwan 251
|
||||
S: Republic of China
|
||||
|
||||
N: Reinette Chatre
|
||||
E: reinette.chatre@intel.com
|
||||
D: WiMedia Link Protocol implementation
|
||||
D: UWB stack bits and pieces
|
||||
|
||||
N: Michael Elizabeth Chastain
|
||||
E: mec@shout.net
|
||||
D: Configure, Menuconfig, xconfig
|
||||
|
@ -2695,6 +2700,12 @@ S: Demonstratsii 8-382
|
|||
S: Tula 300000
|
||||
S: Russia
|
||||
|
||||
N: Inaky Perez-Gonzalez
|
||||
E: inaky.perez-gonzalez@intel.com
|
||||
D: UWB stack, HWA-RC driver and HWA-HC drivers
|
||||
D: Wireless USB additions to the USB stack
|
||||
D: WiMedia Link Protocol bits and pieces
|
||||
|
||||
N: Gordon Peters
|
||||
E: GordPeters@smarttech.com
|
||||
D: Isochronous receive for IEEE 1394 driver (OHCI module).
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
What: /sys/bus/umc/
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
The Wireless Host Controller Interface (WHCI)
|
||||
specification describes a PCI-based device with
|
||||
multiple capabilities; the UWB Multi-interface
|
||||
Controller (UMC).
|
||||
|
||||
The umc bus presents each of the individual
|
||||
capabilties as a device.
|
||||
|
||||
What: /sys/bus/umc/devices/.../capability_id
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
The ID of this capability, with 0 being the radio
|
||||
controller capability.
|
||||
|
||||
What: /sys/bus/umc/devices/.../version
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
The specification version this capability's hardware
|
||||
interface complies with.
|
|
@ -101,3 +101,46 @@ Description:
|
|||
Users:
|
||||
USB PM tool
|
||||
git://git.moblin.org/users/sarah/usb-pm-tool/
|
||||
|
||||
What: /sys/bus/usb/device/.../authorized
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.26
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
Authorized devices are available for use by device
|
||||
drivers, non-authorized one are not. By default, wired
|
||||
USB devices are authorized.
|
||||
|
||||
Certified Wireless USB devices are not authorized
|
||||
initially and should be (by writing 1) after the
|
||||
device has been authenticated.
|
||||
|
||||
What: /sys/bus/usb/device/.../wusb_cdid
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
For Certified Wireless USB devices only.
|
||||
|
||||
A devices's CDID, as 16 space-separated hex octets.
|
||||
|
||||
What: /sys/bus/usb/device/.../wusb_ck
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
For Certified Wireless USB devices only.
|
||||
|
||||
Write the device's connection key (CK) to start the
|
||||
authentication of the device. The CK is 16
|
||||
space-separated hex octets.
|
||||
|
||||
What: /sys/bus/usb/device/.../wusb_disconnect
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
For Certified Wireless USB devices only.
|
||||
|
||||
Write a 1 to force the device to disconnect
|
||||
(equivalent to unplugging a wired USB device).
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
What: /sys/class/usb_host/usb_hostN/wusb_chid
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
Write the CHID (16 space-separated hex octets) for this host controller.
|
||||
This starts the host controller, allowing it to accept connection from
|
||||
WUSB devices.
|
||||
|
||||
Set an all zero CHID to stop the host controller.
|
||||
|
||||
What: /sys/class/usb_host/usb_hostN/wusb_trust_timeout
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
Devices that haven't sent a WUSB packet to the host
|
||||
within 'wusb_trust_timeout' ms are considered to have
|
||||
disconnected and are removed. The default value of
|
||||
4000 ms is the value required by the WUSB
|
||||
specification.
|
||||
|
||||
Since this relates to security (specifically, the
|
||||
lifetime of PTKs and GTKs) it should not be changed
|
||||
from the default.
|
|
@ -0,0 +1,144 @@
|
|||
What: /sys/class/uwb_rc
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
Interfaces for WiMedia Ultra Wideband Common Radio
|
||||
Platform (UWB) radio controllers.
|
||||
|
||||
Familiarity with the ECMA-368 'High Rate Ultra
|
||||
Wideband MAC and PHY Specification' is assumed.
|
||||
|
||||
What: /sys/class/uwb_rc/beacon_timeout_ms
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Description:
|
||||
If no beacons are received from a device for at least
|
||||
this time, the device will be considered to have gone
|
||||
and it will be removed. The default is 3 superframes
|
||||
(~197 ms) as required by the specification.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
An individual UWB radio controller.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/beacon
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
Write:
|
||||
|
||||
<channel> [<bpst offset>]
|
||||
|
||||
to start beaconing on a specific channel, or stop
|
||||
beaconing if <channel> is -1. Valid channels depends
|
||||
on the radio controller's supported band groups.
|
||||
|
||||
<bpst offset> may be used to try and join a specific
|
||||
beacon group if more than one was found during a scan.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/scan
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
Write:
|
||||
|
||||
<channel> <type> [<bpst offset>]
|
||||
|
||||
to start (or stop) scanning on a channel. <type> is one of:
|
||||
0 - scan
|
||||
1 - scan outside BP
|
||||
2 - scan while inactive
|
||||
3 - scanning disabled
|
||||
4 - scan (with start time of <bpst offset>)
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/mac_address
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
The EUI-48, in colon-separated hex octets, for this
|
||||
radio controller. A write will change the radio
|
||||
controller's EUI-48 but only do so while the device is
|
||||
not beaconing or scanning.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/wusbhc
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
A symlink to the device (if any) of the WUSB Host
|
||||
Controller PAL using this radio controller.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/<EUI-48>/
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
A neighbour UWB device that has either been detected
|
||||
as part of a scan or is a member of the radio
|
||||
controllers beacon group.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/<EUI-48>/BPST
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
The time (using the radio controllers internal 1 ms
|
||||
interval superframe timer) of the last beacon from
|
||||
this device was received.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/<EUI-48>/DevAddr
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
The current DevAddr of this device in colon separated
|
||||
hex octets.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/<EUI-48>/EUI_48
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
|
||||
The EUI-48 of this device in colon separated hex
|
||||
octets.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/<EUI-48>/BPST
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/<EUI-48>/IEs
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
The latest IEs included in this device's beacon, in
|
||||
space separated hex octets with one IE per line.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/<EUI-48>/LQE
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
Link Quality Estimate - the Signal to Noise Ratio
|
||||
(SNR) of all packets received from this device in dB.
|
||||
This gives an estimate on a suitable PHY rate. Refer
|
||||
to [ECMA-368] section 13.3 for more details.
|
||||
|
||||
What: /sys/class/uwb_rc/uwbN/<EUI-48>/RSSI
|
||||
Date: July 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: linux-usb@vger.kernel.org
|
||||
Description:
|
||||
Received Signal Strength Indication - the strength of
|
||||
the received signal in dB. LQE is a more useful
|
||||
measure of the radio link quality.
|
|
@ -0,0 +1,100 @@
|
|||
What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_*
|
||||
Date: August 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
Various files for managing Cable Based Association of
|
||||
(wireless) USB devices.
|
||||
|
||||
The sequence of operations should be:
|
||||
|
||||
1. Device is plugged in.
|
||||
|
||||
2. The connection manager (CM) sees a device with CBA capability.
|
||||
(the wusb_chid etc. files in /sys/devices/blah/OURDEVICE).
|
||||
|
||||
3. The CM writes the host name, supported band groups,
|
||||
and the CHID (host ID) into the wusb_host_name,
|
||||
wusb_host_band_groups and wusb_chid files. These
|
||||
get sent to the device and the CDID (if any) for
|
||||
this host is requested.
|
||||
|
||||
4. The CM can verify that the device's supported band
|
||||
groups (wusb_device_band_groups) are compatible
|
||||
with the host.
|
||||
|
||||
5. The CM reads the wusb_cdid file.
|
||||
|
||||
6. The CM looks it up its database.
|
||||
|
||||
- If it has a matching CHID,CDID entry, the device
|
||||
has been authorized before and nothing further
|
||||
needs to be done.
|
||||
|
||||
- If the CDID is zero (or the CM doesn't find a
|
||||
matching CDID in its database), the device is
|
||||
assumed to be not known. The CM may associate
|
||||
the host with device by: writing a randomly
|
||||
generated CDID to wusb_cdid and then a random CK
|
||||
to wusb_ck (this uploads the new CC to the
|
||||
device).
|
||||
|
||||
CMD may choose to prompt the user before
|
||||
associating with a new device.
|
||||
|
||||
7. Device is unplugged.
|
||||
|
||||
References:
|
||||
[WUSB-AM] Association Models Supplement to the
|
||||
Certified Wireless Universal Serial Bus
|
||||
Specification, version 1.0.
|
||||
|
||||
What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_chid
|
||||
Date: August 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
The CHID of the host formatted as 16 space-separated
|
||||
hex octets.
|
||||
|
||||
Writes fetches device's supported band groups and the
|
||||
the CDID for any existing association with this host.
|
||||
|
||||
What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_host_name
|
||||
Date: August 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
A friendly name for the host as a UTF-8 encoded string.
|
||||
|
||||
What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_host_band_groups
|
||||
Date: August 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
The band groups supported by the host, in the format
|
||||
defined in [WUSB-AM].
|
||||
|
||||
What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_device_band_groups
|
||||
Date: August 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
The band groups supported by the device, in the format
|
||||
defined in [WUSB-AM].
|
||||
|
||||
What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_cdid
|
||||
Date: August 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
The device's CDID formatted as 16 space-separated hex
|
||||
octets.
|
||||
|
||||
What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_ck
|
||||
Date: August 2008
|
||||
KernelVersion: 2.6.27
|
||||
Contact: David Vrabel <david.vrabel@csr.com>
|
||||
Description:
|
||||
Write 16 space-separated random, hex octets to
|
||||
associate with the device.
|
|
@ -0,0 +1,448 @@
|
|||
|
||||
Linux UWB + Wireless USB + WiNET
|
||||
|
||||
(C) 2005-2006 Intel Corporation
|
||||
Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version
|
||||
2 as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
02110-1301, USA.
|
||||
|
||||
|
||||
Please visit http://bughost.org/thewiki/Design-overview.txt-1.8 for
|
||||
updated content.
|
||||
|
||||
* Design-overview.txt-1.8
|
||||
|
||||
This code implements a Ultra Wide Band stack for Linux, as well as
|
||||
drivers for the the USB based UWB radio controllers defined in the
|
||||
Wireless USB 1.0 specification (including Wireless USB host controller
|
||||
and an Intel WiNET controller).
|
||||
|
||||
1. Introduction
|
||||
1. HWA: Host Wire adapters, your Wireless USB dongle
|
||||
|
||||
2. DWA: Device Wired Adaptor, a Wireless USB hub for wired
|
||||
devices
|
||||
3. WHCI: Wireless Host Controller Interface, the PCI WUSB host
|
||||
adapter
|
||||
2. The UWB stack
|
||||
1. Devices and hosts: the basic structure
|
||||
|
||||
2. Host Controller life cycle
|
||||
|
||||
3. On the air: beacons and enumerating the radio neighborhood
|
||||
|
||||
4. Device lists
|
||||
5. Bandwidth allocation
|
||||
|
||||
3. Wireless USB Host Controller drivers
|
||||
|
||||
4. Glossary
|
||||
|
||||
|
||||
Introduction
|
||||
|
||||
UWB is a wide-band communication protocol that is to serve also as the
|
||||
low-level protocol for others (much like TCP sits on IP). Currently
|
||||
these others are Wireless USB and TCP/IP, but seems Bluetooth and
|
||||
Firewire/1394 are coming along.
|
||||
|
||||
UWB uses a band from roughly 3 to 10 GHz, transmitting at a max of
|
||||
~-41dB (or 0.074 uW/MHz--geography specific data is still being
|
||||
negotiated w/ regulators, so watch for changes). That band is divided in
|
||||
a bunch of ~1.5 GHz wide channels (or band groups) composed of three
|
||||
subbands/subchannels (528 MHz each). Each channel is independent of each
|
||||
other, so you could consider them different "busses". Initially this
|
||||
driver considers them all a single one.
|
||||
|
||||
Radio time is divided in 65536 us long /superframes/, each one divided
|
||||
in 256 256us long /MASs/ (Media Allocation Slots), which are the basic
|
||||
time/media allocation units for transferring data. At the beginning of
|
||||
each superframe there is a Beacon Period (BP), where every device
|
||||
transmit its beacon on a single MAS. The length of the BP depends on how
|
||||
many devices are present and the length of their beacons.
|
||||
|
||||
Devices have a MAC (fixed, 48 bit address) and a device (changeable, 16
|
||||
bit address) and send periodic beacons to advertise themselves and pass
|
||||
info on what they are and do. They advertise their capabilities and a
|
||||
bunch of other stuff.
|
||||
|
||||
The different logical parts of this driver are:
|
||||
|
||||
*
|
||||
|
||||
*UWB*: the Ultra-Wide-Band stack -- manages the radio and
|
||||
associated spectrum to allow for devices sharing it. Allows to
|
||||
control bandwidth assingment, beaconing, scanning, etc
|
||||
|
||||
*
|
||||
|
||||
*WUSB*: the layer that sits on top of UWB to provide Wireless USB.
|
||||
The Wireless USB spec defines means to control a UWB radio and to
|
||||
do the actual WUSB.
|
||||
|
||||
|
||||
HWA: Host Wire adapters, your Wireless USB dongle
|
||||
|
||||
WUSB also defines a device called a Host Wire Adaptor (HWA), which in
|
||||
mere terms is a USB dongle that enables your PC to have UWB and Wireless
|
||||
USB. The Wireless USB Host Controller in a HWA looks to the host like a
|
||||
[Wireless] USB controller connected via USB (!)
|
||||
|
||||
The HWA itself is broken in two or three main interfaces:
|
||||
|
||||
*
|
||||
|
||||
*RC*: Radio control -- this implements an interface to the
|
||||
Ultra-Wide-Band radio controller. The driver for this implements a
|
||||
USB-based UWB Radio Controller to the UWB stack.
|
||||
|
||||
*
|
||||
|
||||
*HC*: the wireless USB host controller. It looks like a USB host
|
||||
whose root port is the radio and the WUSB devices connect to it.
|
||||
To the system it looks like a separate USB host. The driver (will)
|
||||
implement a USB host controller (similar to UHCI, OHCI or EHCI)
|
||||
for which the root hub is the radio...To reiterate: it is a USB
|
||||
controller that is connected via USB instead of PCI.
|
||||
|
||||
*
|
||||
|
||||
*WINET*: some HW provide a WiNET interface (IP over UWB). This
|
||||
package provides a driver for it (it looks like a network
|
||||
interface, winetX). The driver detects when there is a link up for
|
||||
their type and kick into gear.
|
||||
|
||||
|
||||
DWA: Device Wired Adaptor, a Wireless USB hub for wired devices
|
||||
|
||||
These are the complement to HWAs. They are a USB host for connecting
|
||||
wired devices, but it is connected to your PC connected via Wireless
|
||||
USB. To the system it looks like yet another USB host. To the untrained
|
||||
eye, it looks like a hub that connects upstream wirelessly.
|
||||
|
||||
We still offer no support for this; however, it should share a lot of
|
||||
code with the HWA-RC driver; there is a bunch of factorization work that
|
||||
has been done to support that in upcoming releases.
|
||||
|
||||
|
||||
WHCI: Wireless Host Controller Interface, the PCI WUSB host adapter
|
||||
|
||||
This is your usual PCI device that implements WHCI. Similar in concept
|
||||
to EHCI, it allows your wireless USB devices (including DWAs) to connect
|
||||
to your host via a PCI interface. As in the case of the HWA, it has a
|
||||
Radio Control interface and the WUSB Host Controller interface per se.
|
||||
|
||||
There is still no driver support for this, but will be in upcoming
|
||||
releases.
|
||||
|
||||
|
||||
The UWB stack
|
||||
|
||||
The main mission of the UWB stack is to keep a tally of which devices
|
||||
are in radio proximity to allow drivers to connect to them. As well, it
|
||||
provides an API for controlling the local radio controllers (RCs from
|
||||
now on), such as to start/stop beaconing, scan, allocate bandwidth, etc.
|
||||
|
||||
|
||||
Devices and hosts: the basic structure
|
||||
|
||||
The main building block here is the UWB device (struct uwb_dev). For
|
||||
each device that pops up in radio presence (ie: the UWB host receives a
|
||||
beacon from it) you get a struct uwb_dev that will show up in
|
||||
/sys/class/uwb and in /sys/bus/uwb/devices.
|
||||
|
||||
For each RC that is detected, a new struct uwb_rc is created. In turn, a
|
||||
RC is also a device, so they also show in /sys/class/uwb and
|
||||
/sys/bus/uwb/devices, but at the same time, only radio controllers show
|
||||
up in /sys/class/uwb_rc.
|
||||
|
||||
*
|
||||
|
||||
[*] The reason for RCs being also devices is that not only we can
|
||||
see them while enumerating the system device tree, but also on the
|
||||
radio (their beacons and stuff), so the handling has to be
|
||||
likewise to that of a device.
|
||||
|
||||
Each RC driver is implemented by a separate driver that plugs into the
|
||||
interface that the UWB stack provides through a struct uwb_rc_ops. The
|
||||
spec creators have been nice enough to make the message format the same
|
||||
for HWA and WHCI RCs, so the driver is really a very thin transport that
|
||||
moves the requests from the UWB API to the device [/uwb_rc_ops->cmd()/]
|
||||
and sends the replies and notifications back to the API
|
||||
[/uwb_rc_neh_grok()/]. Notifications are handled to the UWB daemon, that
|
||||
is chartered, among other things, to keep the tab of how the UWB radio
|
||||
neighborhood looks, creating and destroying devices as they show up or
|
||||
dissapear.
|
||||
|
||||
Command execution is very simple: a command block is sent and a event
|
||||
block or reply is expected back. For sending/receiving command/events, a
|
||||
handle called /neh/ (Notification/Event Handle) is opened with
|
||||
/uwb_rc_neh_open()/.
|
||||
|
||||
The HWA-RC (USB dongle) driver (drivers/uwb/hwa-rc.c) does this job for
|
||||
the USB connected HWA. Eventually, drivers/whci-rc.c will do the same
|
||||
for the PCI connected WHCI controller.
|
||||
|
||||
|
||||
Host Controller life cycle
|
||||
|
||||
So let's say we connect a dongle to the system: it is detected and
|
||||
firmware uploaded if needed [for Intel's i1480
|
||||
/drivers/uwb/ptc/usb.c:ptc_usb_probe()/] and then it is reenumerated.
|
||||
Now we have a real HWA device connected and
|
||||
/drivers/uwb/hwa-rc.c:hwarc_probe()/ picks it up, that will set up the
|
||||
Wire-Adaptor environment and then suck it into the UWB stack's vision of
|
||||
the world [/drivers/uwb/lc-rc.c:uwb_rc_add()/].
|
||||
|
||||
*
|
||||
|
||||
[*] The stack should put a new RC to scan for devices
|
||||
[/uwb_rc_scan()/] so it finds what's available around and tries to
|
||||
connect to them, but this is policy stuff and should be driven
|
||||
from user space. As of now, the operator is expected to do it
|
||||
manually; see the release notes for documentation on the procedure.
|
||||
|
||||
When a dongle is disconnected, /drivers/uwb/hwa-rc.c:hwarc_disconnect()/
|
||||
takes time of tearing everything down safely (or not...).
|
||||
|
||||
|
||||
On the air: beacons and enumerating the radio neighborhood
|
||||
|
||||
So assuming we have devices and we have agreed for a channel to connect
|
||||
on (let's say 9), we put the new RC to beacon:
|
||||
|
||||
*
|
||||
|
||||
$ echo 9 0 > /sys/class/uwb_rc/uwb0/beacon
|
||||
|
||||
Now it is visible. If there were other devices in the same radio channel
|
||||
and beacon group (that's what the zero is for), the dongle's radio
|
||||
control interface will send beacon notifications on its
|
||||
notification/event endpoint (NEEP). The beacon notifications are part of
|
||||
the event stream that is funneled into the API with
|
||||
/drivers/uwb/neh.c:uwb_rc_neh_grok()/ and delivered to the UWBD, the UWB
|
||||
daemon through a notification list.
|
||||
|
||||
UWBD wakes up and scans the event list; finds a beacon and adds it to
|
||||
the BEACON CACHE (/uwb_beca/). If he receives a number of beacons from
|
||||
the same device, he considers it to be 'onair' and creates a new device
|
||||
[/drivers/uwb/lc-dev.c:uwbd_dev_onair()/]. Similarly, when no beacons
|
||||
are received in some time, the device is considered gone and wiped out
|
||||
[uwbd calls periodically /uwb/beacon.c:uwb_beca_purge()/ that will purge
|
||||
the beacon cache of dead devices].
|
||||
|
||||
|
||||
Device lists
|
||||
|
||||
All UWB devices are kept in the list of the struct bus_type uwb_bus.
|
||||
|
||||
|
||||
Bandwidth allocation
|
||||
|
||||
The UWB stack maintains a local copy of DRP availability through
|
||||
processing of incoming *DRP Availability Change* notifications. This
|
||||
local copy is currently used to present the current bandwidth
|
||||
availability to the user through the sysfs file
|
||||
/sys/class/uwb_rc/uwbx/bw_avail. In the future the bandwidth
|
||||
availability information will be used by the bandwidth reservation
|
||||
routines.
|
||||
|
||||
The bandwidth reservation routines are in progress and are thus not
|
||||
present in the current release. When completed they will enable a user
|
||||
to initiate DRP reservation requests through interaction with sysfs. DRP
|
||||
reservation requests from remote UWB devices will also be handled. The
|
||||
bandwidth management done by the UWB stack will include callbacks to the
|
||||
higher layers will enable the higher layers to use the reservations upon
|
||||
completion. [Note: The bandwidth reservation work is in progress and
|
||||
subject to change.]
|
||||
|
||||
|
||||
Wireless USB Host Controller drivers
|
||||
|
||||
*WARNING* This section needs a lot of work!
|
||||
|
||||
As explained above, there are three different types of HCs in the WUSB
|
||||
world: HWA-HC, DWA-HC and WHCI-HC.
|
||||
|
||||
HWA-HC and DWA-HC share that they are Wire-Adapters (USB or WUSB
|
||||
connected controllers), and their transfer management system is almost
|
||||
identical. So is their notification delivery system.
|
||||
|
||||
HWA-HC and WHCI-HC share that they are both WUSB host controllers, so
|
||||
they have to deal with WUSB device life cycle and maintenance, wireless
|
||||
root-hub
|
||||
|
||||
HWA exposes a Host Controller interface (HWA-HC 0xe0/02/02). This has
|
||||
three endpoints (Notifications, Data Transfer In and Data Transfer
|
||||
Out--known as NEP, DTI and DTO in the code).
|
||||
|
||||
We reserve UWB bandwidth for our Wireless USB Cluster, create a Cluster
|
||||
ID and tell the HC to use all that. Then we start it. This means the HC
|
||||
starts sending MMCs.
|
||||
|
||||
*
|
||||
|
||||
The MMCs are blocks of data defined somewhere in the WUSB1.0 spec
|
||||
that define a stream in the UWB channel time allocated for sending
|
||||
WUSB IEs (host to device commands/notifications) and Device
|
||||
Notifications (device initiated to host). Each host defines a
|
||||
unique Wireless USB cluster through MMCs. Devices can connect to a
|
||||
single cluster at the time. The IEs are Information Elements, and
|
||||
among them are the bandwidth allocations that tell each device
|
||||
when can they transmit or receive.
|
||||
|
||||
Now it all depends on external stimuli.
|
||||
|
||||
*New device connection*
|
||||
|
||||
A new device pops up, it scans the radio looking for MMCs that give out
|
||||
the existence of Wireless USB channels. Once one (or more) are found,
|
||||
selects which one to connect to. Sends a /DN_Connect/ (device
|
||||
notification connect) during the DNTS (Device Notification Time
|
||||
Slot--announced in the MMCs
|
||||
|
||||
HC picks the /DN_Connect/ out (nep module sends to notif.c for delivery
|
||||
into /devconnect/). This process starts the authentication process for
|
||||
the device. First we allocate a /fake port/ and assign an
|
||||
unauthenticated address (128 to 255--what we really do is
|
||||
0x80 | fake_port_idx). We fiddle with the fake port status and /khubd/
|
||||
sees a new connection, so he moves on to enable the fake port with a reset.
|
||||
|
||||
So now we are in the reset path -- we know we have a non-yet enumerated
|
||||
device with an unauthorized address; we ask user space to authenticate
|
||||
(FIXME: not yet done, similar to bluetooth pairing), then we do the key
|
||||
exchange (FIXME: not yet done) and issue a /set address 0/ to bring the
|
||||
device to the default state. Device is authenticated.
|
||||
|
||||
From here, the USB stack takes control through the usb_hcd ops. khubd
|
||||
has seen the port status changes, as we have been toggling them. It will
|
||||
start enumerating and doing transfers through usb_hcd->urb_enqueue() to
|
||||
read descriptors and move our data.
|
||||
|
||||
*Device life cycle and keep alives*
|
||||
|
||||
Everytime there is a succesful transfer to/from a device, we update a
|
||||
per-device activity timestamp. If not, every now and then we check and
|
||||
if the activity timestamp gets old, we ping the device by sending it a
|
||||
Keep Alive IE; it responds with a /DN_Alive/ pong during the DNTS (this
|
||||
arrives to us as a notification through
|
||||
devconnect.c:wusb_handle_dn_alive(). If a device times out, we
|
||||
disconnect it from the system (cleaning up internal information and
|
||||
toggling the bits in the fake hub port, which kicks khubd into removing
|
||||
the rest of the stuff).
|
||||
|
||||
This is done through devconnect:__wusb_check_devs(), which will scan the
|
||||
device list looking for whom needs refreshing.
|
||||
|
||||
If the device wants to disconnect, it will either die (ugly) or send a
|
||||
/DN_Disconnect/ that will prompt a disconnection from the system.
|
||||
|
||||
*Sending and receiving data*
|
||||
|
||||
Data is sent and received through /Remote Pipes/ (rpipes). An rpipe is
|
||||
/aimed/ at an endpoint in a WUSB device. This is the same for HWAs and
|
||||
DWAs.
|
||||
|
||||
Each HC has a number of rpipes and buffers that can be assigned to them;
|
||||
when doing a data transfer (xfer), first the rpipe has to be aimed and
|
||||
prepared (buffers assigned), then we can start queueing requests for
|
||||
data in or out.
|
||||
|
||||
Data buffers have to be segmented out before sending--so we send first a
|
||||
header (segment request) and then if there is any data, a data buffer
|
||||
immediately after to the DTI interface (yep, even the request). If our
|
||||
buffer is bigger than the max segment size, then we just do multiple
|
||||
requests.
|
||||
|
||||
[This sucks, because doing USB scatter gatter in Linux is resource
|
||||
intensive, if any...not that the current approach is not. It just has to
|
||||
be cleaned up a lot :)].
|
||||
|
||||
If reading, we don't send data buffers, just the segment headers saying
|
||||
we want to read segments.
|
||||
|
||||
When the xfer is executed, we receive a notification that says data is
|
||||
ready in the DTI endpoint (handled through
|
||||
xfer.c:wa_handle_notif_xfer()). In there we read from the DTI endpoint a
|
||||
descriptor that gives us the status of the transfer, its identification
|
||||
(given when we issued it) and the segment number. If it was a data read,
|
||||
we issue another URB to read into the destination buffer the chunk of
|
||||
data coming out of the remote endpoint. Done, wait for the next guy. The
|
||||
callbacks for the URBs issued from here are the ones that will declare
|
||||
the xfer complete at some point and call it's callback.
|
||||
|
||||
Seems simple, but the implementation is not trivial.
|
||||
|
||||
*
|
||||
|
||||
*WARNING* Old!!
|
||||
|
||||
The main xfer descriptor, wa_xfer (equivalent to a URB) contains an
|
||||
array of segments, tallys on segments and buffers and callback
|
||||
information. Buried in there is a lot of URBs for executing the segments
|
||||
and buffer transfers.
|
||||
|
||||
For OUT xfers, there is an array of segments, one URB for each, another
|
||||
one of buffer URB. When submitting, we submit URBs for segment request
|
||||
1, buffer 1, segment 2, buffer 2...etc. Then we wait on the DTI for xfer
|
||||
result data; when all the segments are complete, we call the callback to
|
||||
finalize the transfer.
|
||||
|
||||
For IN xfers, we only issue URBs for the segments we want to read and
|
||||
then wait for the xfer result data.
|
||||
|
||||
*URB mapping into xfers*
|
||||
|
||||
This is done by hwahc_op_urb_[en|de]queue(). In enqueue() we aim an
|
||||
rpipe to the endpoint where we have to transmit, create a transfer
|
||||
context (wa_xfer) and submit it. When the xfer is done, our callback is
|
||||
called and we assign the status bits and release the xfer resources.
|
||||
|
||||
In dequeue() we are basically cancelling/aborting the transfer. We issue
|
||||
a xfer abort request to the HC, cancell all the URBs we had submitted
|
||||
and not yet done and when all that is done, the xfer callback will be
|
||||
called--this will call the URB callback.
|
||||
|
||||
|
||||
Glossary
|
||||
|
||||
*DWA* -- Device Wire Adapter
|
||||
|
||||
USB host, wired for downstream devices, upstream connects wirelessly
|
||||
with Wireless USB.
|
||||
|
||||
*EVENT* -- Response to a command on the NEEP
|
||||
|
||||
*HWA* -- Host Wire Adapter / USB dongle for UWB and Wireless USB
|
||||
|
||||
*NEH* -- Notification/Event Handle
|
||||
|
||||
Handle/file descriptor for receiving notifications or events. The WA
|
||||
code requires you to get one of this to listen for notifications or
|
||||
events on the NEEP.
|
||||
|
||||
*NEEP* -- Notification/Event EndPoint
|
||||
|
||||
Stuff related to the management of the first endpoint of a HWA USB
|
||||
dongle that is used to deliver an stream of events and notifications to
|
||||
the host.
|
||||
|
||||
*NOTIFICATION* -- Message coming in the NEEP as response to something.
|
||||
|
||||
*RC* -- Radio Control
|
||||
|
||||
Design-overview.txt-1.8 (last edited 2006-11-04 12:22:24 by
|
||||
InakyPerezGonzalez)
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
#! /bin/bash
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
progname=$(basename $0)
|
||||
function help
|
||||
{
|
||||
cat <<EOF
|
||||
Usage: $progname COMMAND DEVICEs [ARGS]
|
||||
|
||||
Command for manipulating the pairing/authentication credentials of a
|
||||
Wireless USB device that supports wired-mode Cable-Based-Association.
|
||||
|
||||
Works in conjunction with the wusb-cba.ko driver from http://linuxuwb.org.
|
||||
|
||||
|
||||
DEVICE
|
||||
|
||||
sysfs path to the device to authenticate; for example, both this
|
||||
guys are the same:
|
||||
|
||||
/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-4/1-4.4/1-4.4:1.1
|
||||
/sys/bus/usb/drivers/wusb-cbaf/1-4.4:1.1
|
||||
|
||||
COMMAND/ARGS are
|
||||
|
||||
start
|
||||
|
||||
Start a WUSB host controller (by setting up a CHID)
|
||||
|
||||
set-chid DEVICE HOST-CHID HOST-BANDGROUP HOST-NAME
|
||||
|
||||
Sets host information in the device; after this you can call the
|
||||
get-cdid to see how does this device report itself to us.
|
||||
|
||||
get-cdid DEVICE
|
||||
|
||||
Get the device ID associated to the HOST-CHDI we sent with
|
||||
'set-chid'. We might not know about it.
|
||||
|
||||
set-cc DEVICE
|
||||
|
||||
If we allow the device to connect, set a random new CDID and CK
|
||||
(connection key). Device saves them for the next time it wants to
|
||||
connect wireless. We save them for that next time also so we can
|
||||
authenticate the device (when we see the CDID he uses to id
|
||||
itself) and the CK to crypto talk to it.
|
||||
|
||||
CHID is always 16 hex bytes in 'XX YY ZZ...' form
|
||||
BANDGROUP is almost always 0001
|
||||
|
||||
Examples:
|
||||
|
||||
You can default most arguments to '' to get a sane value:
|
||||
|
||||
$ $progname set-chid '' '' '' "My host name"
|
||||
|
||||
A full sequence:
|
||||
|
||||
$ $progname set-chid '' '' '' "My host name"
|
||||
$ $progname get-cdid ''
|
||||
$ $progname set-cc ''
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
# Defaults
|
||||
# FIXME: CHID should come from a database :), band group from the host
|
||||
host_CHID="00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"
|
||||
host_band_group="0001"
|
||||
host_name=$(hostname)
|
||||
|
||||
devs="$(echo /sys/bus/usb/drivers/wusb-cbaf/[0-9]*)"
|
||||
hdevs="$(for h in /sys/class/uwb_rc/*/wusbhc; do readlink -f $h; done)"
|
||||
|
||||
result=0
|
||||
case $1 in
|
||||
start)
|
||||
for dev in ${2:-$hdevs}
|
||||
do
|
||||
uwb_rc=$(readlink -f $dev/uwb_rc)
|
||||
if cat $uwb_rc/beacon | grep -q -- "-1"
|
||||
then
|
||||
echo 13 0 > $uwb_rc/beacon
|
||||
echo I: started beaconing on ch 13 on $(basename $uwb_rc) >&2
|
||||
fi
|
||||
echo $host_CHID > $dev/wusb_chid
|
||||
echo I: started host $(basename $dev) >&2
|
||||
done
|
||||
;;
|
||||
stop)
|
||||
for dev in ${2:-$hdevs}
|
||||
do
|
||||
echo 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 > $dev/wusb_chid
|
||||
echo I: stopped host $(basename $dev) >&2
|
||||
uwb_rc=$(readlink -f $dev/uwb_rc)
|
||||
echo -1 | cat > $uwb_rc/beacon
|
||||
echo I: stopped beaconing on $(basename $uwb_rc) >&2
|
||||
done
|
||||
;;
|
||||
set-chid)
|
||||
shift
|
||||
for dev in ${2:-$devs}; do
|
||||
echo "${4:-$host_name}" > $dev/wusb_host_name
|
||||
echo "${3:-$host_band_group}" > $dev/wusb_host_band_groups
|
||||
echo ${2:-$host_CHID} > $dev/wusb_chid
|
||||
done
|
||||
;;
|
||||
get-cdid)
|
||||
for dev in ${2:-$devs}
|
||||
do
|
||||
cat $dev/wusb_cdid
|
||||
done
|
||||
;;
|
||||
set-cc)
|
||||
for dev in ${2:-$devs}; do
|
||||
shift
|
||||
CDID="$(head --bytes=16 /dev/urandom | od -tx1 -An)"
|
||||
CK="$(head --bytes=16 /dev/urandom | od -tx1 -An)"
|
||||
echo "$CDID" > $dev/wusb_cdid
|
||||
echo "$CK" > $dev/wusb_ck
|
||||
|
||||
echo I: CC set >&2
|
||||
echo "CHID: $(cat $dev/wusb_chid)"
|
||||
echo "CDID:$CDID"
|
||||
echo "CK: $CK"
|
||||
done
|
||||
;;
|
||||
help|h|--help|-h)
|
||||
help
|
||||
;;
|
||||
*)
|
||||
echo "E: Unknown usage" 1>&2
|
||||
help 1>&2
|
||||
result=1
|
||||
esac
|
||||
exit $result
|
17
MAINTAINERS
17
MAINTAINERS
|
@ -1053,6 +1053,12 @@ L: cbe-oss-dev@ozlabs.org
|
|||
W: http://www.ibm.com/developerworks/power/cell/
|
||||
S: Supported
|
||||
|
||||
CERTIFIED WIRELESS USB (WUSB) SUBSYSTEM:
|
||||
P: David Vrabel
|
||||
M: david.vrabel@csr.com
|
||||
L: linux-usb@vger.kernel.org
|
||||
S: Supported
|
||||
|
||||
CFAG12864B LCD DRIVER
|
||||
P: Miguel Ojeda Sandonis
|
||||
M: miguel.ojeda.sandonis@gmail.com
|
||||
|
@ -4191,6 +4197,12 @@ L: sparclinux@vger.kernel.org
|
|||
T: git kernel.org:/pub/scm/linux/kernel/git/davem/sparc-2.6.git
|
||||
S: Maintained
|
||||
|
||||
ULTRA-WIDEBAND (UWB) SUBSYSTEM:
|
||||
P: David Vrabel
|
||||
M: david.vrabel@csr.com
|
||||
L: linux-usb@vger.kernel.org
|
||||
S: Supported
|
||||
|
||||
UNIFORM CDROM DRIVER
|
||||
P: Jens Axboe
|
||||
M: axboe@kernel.dk
|
||||
|
@ -4616,6 +4628,11 @@ M: zaga@fly.cc.fer.hr
|
|||
L: linux-scsi@vger.kernel.org
|
||||
S: Maintained
|
||||
|
||||
WIMEDIA LLC PROTOCOL (WLP) SUBSYSTEM
|
||||
P: David Vrabel
|
||||
M: david.vrabel@csr.com
|
||||
S: Maintained
|
||||
|
||||
WISTRON LAPTOP BUTTON DRIVER
|
||||
P: Miloslav Trmac
|
||||
M: mitr@volny.cz
|
||||
|
|
|
@ -1256,6 +1256,8 @@ source "drivers/hid/Kconfig"
|
|||
|
||||
source "drivers/usb/Kconfig"
|
||||
|
||||
source "drivers/uwb/Kconfig"
|
||||
|
||||
source "drivers/mmc/Kconfig"
|
||||
|
||||
source "drivers/memstick/Kconfig"
|
||||
|
|
|
@ -679,6 +679,8 @@ source "fs/Kconfig"
|
|||
|
||||
source "drivers/usb/Kconfig"
|
||||
|
||||
source "drivers/uwb/Kconfig"
|
||||
|
||||
source "arch/cris/Kconfig.debug"
|
||||
|
||||
source "security/Kconfig"
|
||||
|
|
|
@ -216,6 +216,8 @@ source "drivers/hwmon/Kconfig"
|
|||
|
||||
source "drivers/usb/Kconfig"
|
||||
|
||||
source "drivers/uwb/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
||||
source "fs/Kconfig"
|
||||
|
|
|
@ -78,6 +78,8 @@ source "drivers/hid/Kconfig"
|
|||
|
||||
source "drivers/usb/Kconfig"
|
||||
|
||||
source "drivers/uwb/Kconfig"
|
||||
|
||||
source "drivers/mmc/Kconfig"
|
||||
|
||||
source "drivers/memstick/Kconfig"
|
||||
|
|
|
@ -100,3 +100,4 @@ obj-$(CONFIG_SSB) += ssb/
|
|||
obj-$(CONFIG_VIRTIO) += virtio/
|
||||
obj-$(CONFIG_REGULATOR) += regulator/
|
||||
obj-$(CONFIG_STAGING) += staging/
|
||||
obj-$(CONFIG_UWB) += uwb/
|
||||
|
|
|
@ -97,6 +97,8 @@ source "drivers/usb/core/Kconfig"
|
|||
|
||||
source "drivers/usb/mon/Kconfig"
|
||||
|
||||
source "drivers/usb/wusbcore/Kconfig"
|
||||
|
||||
source "drivers/usb/host/Kconfig"
|
||||
|
||||
source "drivers/usb/musb/Kconfig"
|
||||
|
|
|
@ -16,9 +16,12 @@ obj-$(CONFIG_USB_UHCI_HCD) += host/
|
|||
obj-$(CONFIG_USB_SL811_HCD) += host/
|
||||
obj-$(CONFIG_USB_U132_HCD) += host/
|
||||
obj-$(CONFIG_USB_R8A66597_HCD) += host/
|
||||
obj-$(CONFIG_USB_HWA_HCD) += host/
|
||||
|
||||
obj-$(CONFIG_USB_C67X00_HCD) += c67x00/
|
||||
|
||||
obj-$(CONFIG_USB_WUSB) += wusbcore/
|
||||
|
||||
obj-$(CONFIG_USB_ACM) += class/
|
||||
obj-$(CONFIG_USB_PRINTER) += class/
|
||||
|
||||
|
|
|
@ -305,3 +305,31 @@ config SUPERH_ON_CHIP_R8A66597
|
|||
help
|
||||
This driver enables support for the on-chip R8A66597 in the
|
||||
SH7366 and SH7723 processors.
|
||||
|
||||
config USB_WHCI_HCD
|
||||
tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL
|
||||
depends on PCI && USB
|
||||
select USB_WUSB
|
||||
select UWB_WHCI
|
||||
help
|
||||
A driver for PCI-based Wireless USB Host Controllers that are
|
||||
compliant with the WHCI specification.
|
||||
|
||||
To compile this driver a module, choose M here: the module
|
||||
will be called "whci-hcd".
|
||||
|
||||
config USB_HWA_HCD
|
||||
tristate "Host Wire Adapter (HWA) driver (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL
|
||||
depends on USB
|
||||
select USB_WUSB
|
||||
select UWB_HWA
|
||||
help
|
||||
This driver enables you to connect Wireless USB devices to
|
||||
your system using a Host Wire Adaptor USB dongle. This is an
|
||||
UWB Radio Controller and WUSB Host Controller connected to
|
||||
your machine via USB (specified in WUSB1.0).
|
||||
|
||||
To compile this driver a module, choose M here: the module
|
||||
will be called "hwa-hc".
|
||||
|
|
|
@ -8,6 +8,8 @@ endif
|
|||
|
||||
isp1760-objs := isp1760-hcd.o isp1760-if.o
|
||||
|
||||
obj-$(CONFIG_USB_WHCI_HCD) += whci/
|
||||
|
||||
obj-$(CONFIG_PCI) += pci-quirks.o
|
||||
|
||||
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
|
||||
|
@ -19,3 +21,4 @@ obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o
|
|||
obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o
|
||||
obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o
|
||||
obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o
|
||||
obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o
|
||||
|
|
|
@ -0,0 +1,925 @@
|
|||
/*
|
||||
* Host Wire Adapter:
|
||||
* Driver glue, HWA-specific functions, bridges to WAHC and WUSBHC
|
||||
*
|
||||
* Copyright (C) 2005-2006 Intel Corporation
|
||||
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*
|
||||
* The HWA driver is a simple layer that forwards requests to the WAHC
|
||||
* (Wire Adater Host Controller) or WUSBHC (Wireless USB Host
|
||||
* Controller) layers.
|
||||
*
|
||||
* Host Wire Adapter is the 'WUSB 1.0 standard' name for Wireless-USB
|
||||
* Host Controller that is connected to your system via USB (a USB
|
||||
* dongle that implements a USB host...). There is also a Device Wired
|
||||
* Adaptor, DWA (Wireless USB hub) that uses the same mechanism for
|
||||
* transferring data (it is after all a USB host connected via
|
||||
* Wireless USB), we have a common layer called Wire Adapter Host
|
||||
* Controller that does all the hard work. The WUSBHC (Wireless USB
|
||||
* Host Controller) is the part common to WUSB Host Controllers, the
|
||||
* HWA and the PCI-based one, that is implemented following the WHCI
|
||||
* spec. All these layers are implemented in ../wusbcore.
|
||||
*
|
||||
* The main functions are hwahc_op_urb_{en,de}queue(), that pass the
|
||||
* job of converting a URB to a Wire Adapter
|
||||
*
|
||||
* Entry points:
|
||||
*
|
||||
* hwahc_driver_*() Driver initialization, registration and
|
||||
* teardown.
|
||||
*
|
||||
* hwahc_probe() New device came up, create an instance for
|
||||
* it [from device enumeration].
|
||||
*
|
||||
* hwahc_disconnect() Remove device instance [from device
|
||||
* enumeration].
|
||||
*
|
||||
* [__]hwahc_op_*() Host-Wire-Adaptor specific functions for
|
||||
* starting/stopping/etc (some might be made also
|
||||
* DWA).
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/completion.h>
|
||||
#include "../wusbcore/wa-hc.h"
|
||||
#include "../wusbcore/wusbhc.h"
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
struct hwahc {
|
||||
struct wusbhc wusbhc; /* has to be 1st */
|
||||
struct wahc wa;
|
||||
u8 buffer[16]; /* for misc usb transactions */
|
||||
};
|
||||
|
||||
/**
|
||||
* FIXME should be wusbhc
|
||||
*
|
||||
* NOTE: we need to cache the Cluster ID because later...there is no
|
||||
* way to get it :)
|
||||
*/
|
||||
static int __hwahc_set_cluster_id(struct hwahc *hwahc, u8 cluster_id)
|
||||
{
|
||||
int result;
|
||||
struct wusbhc *wusbhc = &hwahc->wusbhc;
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
WUSB_REQ_SET_CLUSTER_ID,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
cluster_id,
|
||||
wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0)
|
||||
dev_err(dev, "Cannot set WUSB Cluster ID to 0x%02x: %d\n",
|
||||
cluster_id, result);
|
||||
else
|
||||
wusbhc->cluster_id = cluster_id;
|
||||
dev_info(dev, "Wireless USB Cluster ID set to 0x%02x\n", cluster_id);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int __hwahc_op_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
|
||||
{
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
|
||||
return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
WUSB_REQ_SET_NUM_DNTS,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
interval << 8 | slots,
|
||||
wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset a WUSB host controller and wait for it to complete doing it.
|
||||
*
|
||||
* @usb_hcd: Pointer to WUSB Host Controller instance.
|
||||
*
|
||||
*/
|
||||
static int hwahc_op_reset(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
int result;
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct device *dev = &hwahc->wa.usb_iface->dev;
|
||||
|
||||
d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
wa_nep_disarm(&hwahc->wa);
|
||||
result = __wa_set_feature(&hwahc->wa, WA_RESET);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "error commanding HC to reset: %d\n", result);
|
||||
goto error_unlock;
|
||||
}
|
||||
d_printf(3, dev, "reset: waiting for device to change state\n");
|
||||
result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "error waiting for HC to reset: %d\n", result);
|
||||
goto error_unlock;
|
||||
}
|
||||
error_unlock:
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: break this function up
|
||||
*/
|
||||
static int hwahc_op_start(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
u8 addr;
|
||||
int result;
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct device *dev = &hwahc->wa.usb_iface->dev;
|
||||
|
||||
/* Set up a Host Info WUSB Information Element */
|
||||
d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
|
||||
result = -ENOSPC;
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
/* Start the numbering from the top so that the bottom
|
||||
* range of the unauth addr space is used for devices,
|
||||
* the top for HCs; use 0xfe - RC# */
|
||||
addr = wusb_cluster_id_get();
|
||||
if (addr == 0)
|
||||
goto error_cluster_id_get;
|
||||
result = __hwahc_set_cluster_id(hwahc, addr);
|
||||
if (result < 0)
|
||||
goto error_set_cluster_id;
|
||||
|
||||
result = wa_nep_arm(&hwahc->wa, GFP_KERNEL);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot listen to notifications: %d\n", result);
|
||||
goto error_stop;
|
||||
}
|
||||
usb_hcd->uses_new_polling = 1;
|
||||
usb_hcd->poll_rh = 1;
|
||||
usb_hcd->state = HC_STATE_RUNNING;
|
||||
result = 0;
|
||||
out:
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
|
||||
return result;
|
||||
|
||||
error_stop:
|
||||
__wa_stop(&hwahc->wa);
|
||||
error_set_cluster_id:
|
||||
wusb_cluster_id_put(wusbhc->cluster_id);
|
||||
error_cluster_id_get:
|
||||
goto out;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: break this function up
|
||||
*/
|
||||
static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc)
|
||||
{
|
||||
int result;
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct device *dev = &hwahc->wa.usb_iface->dev;
|
||||
|
||||
/* Set up a Host Info WUSB Information Element */
|
||||
d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
|
||||
result = -ENOSPC;
|
||||
|
||||
result = __wa_set_feature(&hwahc->wa, WA_ENABLE);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "error commanding HC to start: %d\n", result);
|
||||
goto error_stop;
|
||||
}
|
||||
result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "error waiting for HC to start: %d\n", result);
|
||||
goto error_stop;
|
||||
}
|
||||
result = 0;
|
||||
out:
|
||||
d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
|
||||
return result;
|
||||
|
||||
error_stop:
|
||||
result = __wa_clear_feature(&hwahc->wa, WA_ENABLE);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
dev_err(wusbhc->dev, "%s (%p [%p], 0x%lx) UNIMPLEMENTED\n", __func__,
|
||||
usb_hcd, hwahc, *(unsigned long *) &msg);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int hwahc_op_resume(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
|
||||
dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__,
|
||||
usb_hcd, hwahc);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc)
|
||||
{
|
||||
int result;
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct device *dev = &hwahc->wa.usb_iface->dev;
|
||||
|
||||
d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
|
||||
/* Nothing for now */
|
||||
d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* No need to abort pipes, as when this is called, all the children
|
||||
* has been disconnected and that has done it [through
|
||||
* usb_disable_interface() -> usb_disable_endpoint() ->
|
||||
* hwahc_op_ep_disable() - >rpipe_ep_disable()].
|
||||
*/
|
||||
static void hwahc_op_stop(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
int result;
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
wusbhc_stop(wusbhc);
|
||||
wa_nep_disarm(&hwahc->wa);
|
||||
result = __wa_stop(&hwahc->wa);
|
||||
wusb_cluster_id_put(wusbhc->cluster_id);
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
|
||||
return;
|
||||
}
|
||||
|
||||
static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
|
||||
dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__,
|
||||
usb_hcd, hwahc);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int hwahc_op_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
|
||||
gfp_t gfp)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
|
||||
return wa_urb_enqueue(&hwahc->wa, urb->ep, urb, gfp);
|
||||
}
|
||||
|
||||
static int hwahc_op_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb,
|
||||
int status)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
|
||||
return wa_urb_dequeue(&hwahc->wa, urb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Release resources allocated for an endpoint
|
||||
*
|
||||
* If there is an associated rpipe to this endpoint, go ahead and put it.
|
||||
*/
|
||||
static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd,
|
||||
struct usb_host_endpoint *ep)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
|
||||
rpipe_ep_disable(&hwahc->wa, ep);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the UWB MAS allocation for the WUSB cluster
|
||||
*
|
||||
* @stream_index: stream to use (-1 for cancelling the allocation)
|
||||
* @mas: mas bitmap to use
|
||||
*/
|
||||
static int __hwahc_op_bwa_set(struct wusbhc *wusbhc, s8 stream_index,
|
||||
const struct uwb_mas_bm *mas)
|
||||
{
|
||||
int result;
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
u8 mas_le[UWB_NUM_MAS/8];
|
||||
|
||||
/* Set the stream index */
|
||||
result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
WUSB_REQ_SET_STREAM_IDX,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
stream_index,
|
||||
wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot set WUSB stream index: %d\n", result);
|
||||
goto out;
|
||||
}
|
||||
uwb_mas_bm_copy_le(mas_le, mas);
|
||||
/* Set the MAS allocation */
|
||||
result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
WUSB_REQ_SET_WUSB_MAS,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
mas_le, 32, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0)
|
||||
dev_err(dev, "Cannot set WUSB MAS allocation: %d\n", result);
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an IE to the host's MMC
|
||||
*
|
||||
* @interval: See WUSB1.0[8.5.3.1]
|
||||
* @repeat_cnt: See WUSB1.0[8.5.3.1]
|
||||
* @handle: See WUSB1.0[8.5.3.1]
|
||||
* @wuie: Pointer to the header of the WUSB IE data to add.
|
||||
* MUST BE allocated in a kmalloc buffer (no stack or
|
||||
* vmalloc).
|
||||
*
|
||||
* NOTE: the format of the WUSB IEs for MMCs are different to the
|
||||
* normal MBOA MAC IEs (IE Id + Length in MBOA MAC vs. Length +
|
||||
* Id in WUSB IEs). Standards...you gotta love'em.
|
||||
*/
|
||||
static int __hwahc_op_mmcie_add(struct wusbhc *wusbhc, u8 interval,
|
||||
u8 repeat_cnt, u8 handle,
|
||||
struct wuie_hdr *wuie)
|
||||
{
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
|
||||
|
||||
return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
WUSB_REQ_ADD_MMC_IE,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
interval << 8 | repeat_cnt,
|
||||
handle << 8 | iface_no,
|
||||
wuie, wuie->bLength, 1000 /* FIXME: arbitrary */);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove an IE to the host's MMC
|
||||
*
|
||||
* @handle: See WUSB1.0[8.5.3.1]
|
||||
*/
|
||||
static int __hwahc_op_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
|
||||
{
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
|
||||
return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
WUSB_REQ_REMOVE_MMC_IE,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0, handle << 8 | iface_no,
|
||||
NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update device information for a given fake port
|
||||
*
|
||||
* @port_idx: Fake port to which device is connected (wusbhc index, not
|
||||
* USB port number).
|
||||
*/
|
||||
static int __hwahc_op_dev_info_set(struct wusbhc *wusbhc,
|
||||
struct wusb_dev *wusb_dev)
|
||||
{
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
|
||||
struct hwa_dev_info *dev_info;
|
||||
int ret;
|
||||
|
||||
/* fill out the Device Info buffer and send it */
|
||||
dev_info = kzalloc(sizeof(struct hwa_dev_info), GFP_KERNEL);
|
||||
if (!dev_info)
|
||||
return -ENOMEM;
|
||||
uwb_mas_bm_copy_le(dev_info->bmDeviceAvailability,
|
||||
&wusb_dev->availability);
|
||||
dev_info->bDeviceAddress = wusb_dev->addr;
|
||||
|
||||
/*
|
||||
* If the descriptors haven't been read yet, use a default PHY
|
||||
* rate of 53.3 Mbit/s only. The correct value will be used
|
||||
* when this will be called again as part of the
|
||||
* authentication process (which occurs after the descriptors
|
||||
* have been read).
|
||||
*/
|
||||
if (wusb_dev->wusb_cap_descr)
|
||||
dev_info->wPHYRates = wusb_dev->wusb_cap_descr->wPHYRates;
|
||||
else
|
||||
dev_info->wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53);
|
||||
|
||||
ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
WUSB_REQ_SET_DEV_INFO,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0, wusb_dev->port_idx << 8 | iface_no,
|
||||
dev_info, sizeof(struct hwa_dev_info),
|
||||
1000 /* FIXME: arbitrary */);
|
||||
kfree(dev_info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set host's idea of which encryption (and key) method to use when
|
||||
* talking to ad evice on a given port.
|
||||
*
|
||||
* If key is NULL, it means disable encryption for that "virtual port"
|
||||
* (used when we disconnect).
|
||||
*/
|
||||
static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
|
||||
const void *key, size_t key_size,
|
||||
u8 key_idx)
|
||||
{
|
||||
int result = -ENOMEM;
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
|
||||
struct usb_key_descriptor *keyd;
|
||||
size_t keyd_len;
|
||||
|
||||
keyd_len = sizeof(*keyd) + key_size;
|
||||
keyd = kzalloc(keyd_len, GFP_KERNEL);
|
||||
if (keyd == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
keyd->bLength = keyd_len;
|
||||
keyd->bDescriptorType = USB_DT_KEY;
|
||||
keyd->tTKID[0] = (tkid >> 0) & 0xff;
|
||||
keyd->tTKID[1] = (tkid >> 8) & 0xff;
|
||||
keyd->tTKID[2] = (tkid >> 16) & 0xff;
|
||||
memcpy(keyd->bKeyData, key, key_size);
|
||||
|
||||
result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
USB_REQ_SET_DESCRIPTOR,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
USB_DT_KEY << 8 | key_idx,
|
||||
port_idx << 8 | iface_no,
|
||||
keyd, keyd_len, 1000 /* FIXME: arbitrary */);
|
||||
|
||||
memset(keyd, 0, sizeof(*keyd)); /* clear keys etc. */
|
||||
kfree(keyd);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set host's idea of which encryption (and key) method to use when
|
||||
* talking to ad evice on a given port.
|
||||
*
|
||||
* If key is NULL, it means disable encryption for that "virtual port"
|
||||
* (used when we disconnect).
|
||||
*/
|
||||
static int __hwahc_op_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
|
||||
const void *key, size_t key_size)
|
||||
{
|
||||
int result = -ENOMEM;
|
||||
struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
|
||||
u8 encryption_value;
|
||||
|
||||
/* Tell the host which key to use to talk to the device */
|
||||
if (key) {
|
||||
u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_PTK,
|
||||
WUSB_KEY_INDEX_ORIGINATOR_HOST);
|
||||
|
||||
result = __hwahc_dev_set_key(wusbhc, port_idx, tkid,
|
||||
key, key_size, key_idx);
|
||||
if (result < 0)
|
||||
goto error_set_key;
|
||||
encryption_value = wusbhc->ccm1_etd->bEncryptionValue;
|
||||
} else {
|
||||
/* FIXME: this should come from wusbhc->etd[UNSECURE].value */
|
||||
encryption_value = 0;
|
||||
}
|
||||
|
||||
/* Set the encryption type for commmunicating with the device */
|
||||
result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
USB_REQ_SET_ENCRYPTION,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
encryption_value, port_idx << 8 | iface_no,
|
||||
NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0)
|
||||
dev_err(wusbhc->dev, "Can't set host's WUSB encryption for "
|
||||
"port index %u to %s (value %d): %d\n", port_idx,
|
||||
wusb_et_name(wusbhc->ccm1_etd->bEncryptionType),
|
||||
wusbhc->ccm1_etd->bEncryptionValue, result);
|
||||
error_set_key:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set host's GTK key
|
||||
*/
|
||||
static int __hwahc_op_set_gtk(struct wusbhc *wusbhc, u32 tkid,
|
||||
const void *key, size_t key_size)
|
||||
{
|
||||
u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK,
|
||||
WUSB_KEY_INDEX_ORIGINATOR_HOST);
|
||||
|
||||
return __hwahc_dev_set_key(wusbhc, 0, tkid, key, key_size, key_idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the Wire Adapter class-specific descriptor
|
||||
*
|
||||
* NOTE: this descriptor comes with the big bundled configuration
|
||||
* descriptor that includes the interfaces' and endpoints', so
|
||||
* we just look for it in the cached copy kept by the USB stack.
|
||||
*
|
||||
* NOTE2: We convert LE fields to CPU order.
|
||||
*/
|
||||
static int wa_fill_descr(struct wahc *wa)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
char *itr;
|
||||
struct usb_device *usb_dev = wa->usb_dev;
|
||||
struct usb_descriptor_header *hdr;
|
||||
struct usb_wa_descriptor *wa_descr;
|
||||
size_t itr_size, actconfig_idx;
|
||||
|
||||
actconfig_idx = (usb_dev->actconfig - usb_dev->config) /
|
||||
sizeof(usb_dev->config[0]);
|
||||
itr = usb_dev->rawdescriptors[actconfig_idx];
|
||||
itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
|
||||
while (itr_size >= sizeof(*hdr)) {
|
||||
hdr = (struct usb_descriptor_header *) itr;
|
||||
d_printf(3, dev, "Extra device descriptor: "
|
||||
"type %02x/%u bytes @ %zu (%zu left)\n",
|
||||
hdr->bDescriptorType, hdr->bLength,
|
||||
(itr - usb_dev->rawdescriptors[actconfig_idx]),
|
||||
itr_size);
|
||||
if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER)
|
||||
goto found;
|
||||
itr += hdr->bLength;
|
||||
itr_size -= hdr->bLength;
|
||||
}
|
||||
dev_err(dev, "cannot find Wire Adapter Class descriptor\n");
|
||||
return -ENODEV;
|
||||
|
||||
found:
|
||||
result = -EINVAL;
|
||||
if (hdr->bLength > itr_size) { /* is it available? */
|
||||
dev_err(dev, "incomplete Wire Adapter Class descriptor "
|
||||
"(%zu bytes left, %u needed)\n",
|
||||
itr_size, hdr->bLength);
|
||||
goto error;
|
||||
}
|
||||
if (hdr->bLength < sizeof(*wa->wa_descr)) {
|
||||
dev_err(dev, "short Wire Adapter Class descriptor\n");
|
||||
goto error;
|
||||
}
|
||||
wa->wa_descr = wa_descr = (struct usb_wa_descriptor *) hdr;
|
||||
/* Make LE fields CPU order */
|
||||
wa_descr->bcdWAVersion = le16_to_cpu(wa_descr->bcdWAVersion);
|
||||
wa_descr->wNumRPipes = le16_to_cpu(wa_descr->wNumRPipes);
|
||||
wa_descr->wRPipeMaxBlock = le16_to_cpu(wa_descr->wRPipeMaxBlock);
|
||||
if (wa_descr->bcdWAVersion > 0x0100)
|
||||
dev_warn(dev, "Wire Adapter v%d.%d newer than groked v1.0\n",
|
||||
wa_descr->bcdWAVersion & 0xff00 >> 8,
|
||||
wa_descr->bcdWAVersion & 0x00ff);
|
||||
result = 0;
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct hc_driver hwahc_hc_driver = {
|
||||
.description = "hwa-hcd",
|
||||
.product_desc = "Wireless USB HWA host controller",
|
||||
.hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd),
|
||||
.irq = NULL, /* FIXME */
|
||||
.flags = HCD_USB2, /* FIXME */
|
||||
.reset = hwahc_op_reset,
|
||||
.start = hwahc_op_start,
|
||||
.pci_suspend = hwahc_op_suspend,
|
||||
.pci_resume = hwahc_op_resume,
|
||||
.stop = hwahc_op_stop,
|
||||
.get_frame_number = hwahc_op_get_frame_number,
|
||||
.urb_enqueue = hwahc_op_urb_enqueue,
|
||||
.urb_dequeue = hwahc_op_urb_dequeue,
|
||||
.endpoint_disable = hwahc_op_endpoint_disable,
|
||||
|
||||
.hub_status_data = wusbhc_rh_status_data,
|
||||
.hub_control = wusbhc_rh_control,
|
||||
.bus_suspend = wusbhc_rh_suspend,
|
||||
.bus_resume = wusbhc_rh_resume,
|
||||
.start_port_reset = wusbhc_rh_start_port_reset,
|
||||
};
|
||||
|
||||
static int hwahc_security_create(struct hwahc *hwahc)
|
||||
{
|
||||
int result;
|
||||
struct wusbhc *wusbhc = &hwahc->wusbhc;
|
||||
struct usb_device *usb_dev = hwahc->wa.usb_dev;
|
||||
struct device *dev = &usb_dev->dev;
|
||||
struct usb_security_descriptor *secd;
|
||||
struct usb_encryption_descriptor *etd;
|
||||
void *itr, *top;
|
||||
size_t itr_size, needed, bytes;
|
||||
u8 index;
|
||||
char buf[64];
|
||||
|
||||
/* Find the host's security descriptors in the config descr bundle */
|
||||
index = (usb_dev->actconfig - usb_dev->config) /
|
||||
sizeof(usb_dev->config[0]);
|
||||
itr = usb_dev->rawdescriptors[index];
|
||||
itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
|
||||
top = itr + itr_size;
|
||||
result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index],
|
||||
le16_to_cpu(usb_dev->actconfig->desc.wTotalLength),
|
||||
USB_DT_SECURITY, (void **) &secd);
|
||||
if (result == -1) {
|
||||
dev_warn(dev, "BUG? WUSB host has no security descriptors\n");
|
||||
return 0;
|
||||
}
|
||||
needed = sizeof(*secd);
|
||||
if (top - (void *)secd < needed) {
|
||||
dev_err(dev, "BUG? Not enough data to process security "
|
||||
"descriptor header (%zu bytes left vs %zu needed)\n",
|
||||
top - (void *) secd, needed);
|
||||
return 0;
|
||||
}
|
||||
needed = le16_to_cpu(secd->wTotalLength);
|
||||
if (top - (void *)secd < needed) {
|
||||
dev_err(dev, "BUG? Not enough data to process security "
|
||||
"descriptors (%zu bytes left vs %zu needed)\n",
|
||||
top - (void *) secd, needed);
|
||||
return 0;
|
||||
}
|
||||
/* Walk over the sec descriptors and store CCM1's on wusbhc */
|
||||
itr = (void *) secd + sizeof(*secd);
|
||||
top = (void *) secd + le16_to_cpu(secd->wTotalLength);
|
||||
index = 0;
|
||||
bytes = 0;
|
||||
while (itr < top) {
|
||||
etd = itr;
|
||||
if (top - itr < sizeof(*etd)) {
|
||||
dev_err(dev, "BUG: bad host security descriptor; "
|
||||
"not enough data (%zu vs %zu left)\n",
|
||||
top - itr, sizeof(*etd));
|
||||
break;
|
||||
}
|
||||
if (etd->bLength < sizeof(*etd)) {
|
||||
dev_err(dev, "BUG: bad host encryption descriptor; "
|
||||
"descriptor is too short "
|
||||
"(%zu vs %zu needed)\n",
|
||||
(size_t)etd->bLength, sizeof(*etd));
|
||||
break;
|
||||
}
|
||||
itr += etd->bLength;
|
||||
bytes += snprintf(buf + bytes, sizeof(buf) - bytes,
|
||||
"%s (0x%02x) ",
|
||||
wusb_et_name(etd->bEncryptionType),
|
||||
etd->bEncryptionValue);
|
||||
wusbhc->ccm1_etd = etd;
|
||||
}
|
||||
dev_info(dev, "supported encryption types: %s\n", buf);
|
||||
if (wusbhc->ccm1_etd == NULL) {
|
||||
dev_err(dev, "E: host doesn't support CCM-1 crypto\n");
|
||||
return 0;
|
||||
}
|
||||
/* Pretty print what we support */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hwahc_security_release(struct hwahc *hwahc)
|
||||
{
|
||||
/* nothing to do here so far... */
|
||||
}
|
||||
|
||||
static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &iface->dev;
|
||||
struct wusbhc *wusbhc = &hwahc->wusbhc;
|
||||
struct wahc *wa = &hwahc->wa;
|
||||
struct usb_device *usb_dev = interface_to_usbdev(iface);
|
||||
|
||||
wa->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */
|
||||
wa->usb_iface = usb_get_intf(iface);
|
||||
wusbhc->dev = dev;
|
||||
wusbhc->uwb_rc = uwb_rc_get_by_grandpa(iface->dev.parent);
|
||||
if (wusbhc->uwb_rc == NULL) {
|
||||
result = -ENODEV;
|
||||
dev_err(dev, "Cannot get associated UWB Host Controller\n");
|
||||
goto error_rc_get;
|
||||
}
|
||||
result = wa_fill_descr(wa); /* Get the device descriptor */
|
||||
if (result < 0)
|
||||
goto error_fill_descriptor;
|
||||
if (wa->wa_descr->bNumPorts > USB_MAXCHILDREN) {
|
||||
dev_err(dev, "FIXME: USB_MAXCHILDREN too low for WUSB "
|
||||
"adapter (%u ports)\n", wa->wa_descr->bNumPorts);
|
||||
wusbhc->ports_max = USB_MAXCHILDREN;
|
||||
} else {
|
||||
wusbhc->ports_max = wa->wa_descr->bNumPorts;
|
||||
}
|
||||
wusbhc->mmcies_max = wa->wa_descr->bNumMMCIEs;
|
||||
wusbhc->start = __hwahc_op_wusbhc_start;
|
||||
wusbhc->stop = __hwahc_op_wusbhc_stop;
|
||||
wusbhc->mmcie_add = __hwahc_op_mmcie_add;
|
||||
wusbhc->mmcie_rm = __hwahc_op_mmcie_rm;
|
||||
wusbhc->dev_info_set = __hwahc_op_dev_info_set;
|
||||
wusbhc->bwa_set = __hwahc_op_bwa_set;
|
||||
wusbhc->set_num_dnts = __hwahc_op_set_num_dnts;
|
||||
wusbhc->set_ptk = __hwahc_op_set_ptk;
|
||||
wusbhc->set_gtk = __hwahc_op_set_gtk;
|
||||
result = hwahc_security_create(hwahc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Can't initialize security: %d\n", result);
|
||||
goto error_security_create;
|
||||
}
|
||||
wa->wusb = wusbhc; /* FIXME: ugly, need to fix */
|
||||
result = wusbhc_create(&hwahc->wusbhc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Can't create WUSB HC structures: %d\n", result);
|
||||
goto error_wusbhc_create;
|
||||
}
|
||||
result = wa_create(&hwahc->wa, iface);
|
||||
if (result < 0)
|
||||
goto error_wa_create;
|
||||
return 0;
|
||||
|
||||
error_wa_create:
|
||||
wusbhc_destroy(&hwahc->wusbhc);
|
||||
error_wusbhc_create:
|
||||
/* WA Descr fill allocs no resources */
|
||||
error_security_create:
|
||||
error_fill_descriptor:
|
||||
uwb_rc_put(wusbhc->uwb_rc);
|
||||
error_rc_get:
|
||||
usb_put_intf(iface);
|
||||
usb_put_dev(usb_dev);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hwahc_destroy(struct hwahc *hwahc)
|
||||
{
|
||||
struct wusbhc *wusbhc = &hwahc->wusbhc;
|
||||
|
||||
d_fnstart(1, NULL, "(hwahc %p)\n", hwahc);
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
__wa_destroy(&hwahc->wa);
|
||||
wusbhc_destroy(&hwahc->wusbhc);
|
||||
hwahc_security_release(hwahc);
|
||||
hwahc->wusbhc.dev = NULL;
|
||||
uwb_rc_put(wusbhc->uwb_rc);
|
||||
usb_put_intf(hwahc->wa.usb_iface);
|
||||
usb_put_dev(hwahc->wa.usb_dev);
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
d_fnend(1, NULL, "(hwahc %p) = void\n", hwahc);
|
||||
}
|
||||
|
||||
static void hwahc_init(struct hwahc *hwahc)
|
||||
{
|
||||
wa_init(&hwahc->wa);
|
||||
}
|
||||
|
||||
static int hwahc_probe(struct usb_interface *usb_iface,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
int result;
|
||||
struct usb_hcd *usb_hcd;
|
||||
struct wusbhc *wusbhc;
|
||||
struct hwahc *hwahc;
|
||||
struct device *dev = &usb_iface->dev;
|
||||
|
||||
d_fnstart(4, dev, "(%p, %p)\n", usb_iface, id);
|
||||
result = -ENOMEM;
|
||||
usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa");
|
||||
if (usb_hcd == NULL) {
|
||||
dev_err(dev, "unable to allocate instance\n");
|
||||
goto error_alloc;
|
||||
}
|
||||
usb_hcd->wireless = 1;
|
||||
usb_hcd->flags |= HCD_FLAG_SAW_IRQ;
|
||||
wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
hwahc_init(hwahc);
|
||||
result = hwahc_create(hwahc, usb_iface);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot initialize internals: %d\n", result);
|
||||
goto error_hwahc_create;
|
||||
}
|
||||
result = usb_add_hcd(usb_hcd, 0, 0);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot add HCD: %d\n", result);
|
||||
goto error_add_hcd;
|
||||
}
|
||||
result = wusbhc_b_create(&hwahc->wusbhc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result);
|
||||
goto error_wusbhc_b_create;
|
||||
}
|
||||
d_fnend(4, dev, "(%p, %p) = 0\n", usb_iface, id);
|
||||
return 0;
|
||||
|
||||
error_wusbhc_b_create:
|
||||
usb_remove_hcd(usb_hcd);
|
||||
error_add_hcd:
|
||||
hwahc_destroy(hwahc);
|
||||
error_hwahc_create:
|
||||
usb_put_hcd(usb_hcd);
|
||||
error_alloc:
|
||||
d_fnend(4, dev, "(%p, %p) = %d\n", usb_iface, id, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hwahc_disconnect(struct usb_interface *usb_iface)
|
||||
{
|
||||
struct usb_hcd *usb_hcd;
|
||||
struct wusbhc *wusbhc;
|
||||
struct hwahc *hwahc;
|
||||
|
||||
usb_hcd = usb_get_intfdata(usb_iface);
|
||||
wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
hwahc = container_of(wusbhc, struct hwahc, wusbhc);
|
||||
|
||||
d_fnstart(1, NULL, "(hwahc %p [usb_iface %p])\n", hwahc, usb_iface);
|
||||
wusbhc_b_destroy(&hwahc->wusbhc);
|
||||
usb_remove_hcd(usb_hcd);
|
||||
hwahc_destroy(hwahc);
|
||||
usb_put_hcd(usb_hcd);
|
||||
d_fnend(1, NULL, "(hwahc %p [usb_iface %p]) = void\n", hwahc,
|
||||
usb_iface);
|
||||
}
|
||||
|
||||
/** USB device ID's that we handle */
|
||||
static struct usb_device_id hwahc_id_table[] = {
|
||||
/* FIXME: use class labels for this */
|
||||
{ USB_INTERFACE_INFO(0xe0, 0x02, 0x01), },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, hwahc_id_table);
|
||||
|
||||
static struct usb_driver hwahc_driver = {
|
||||
.name = "hwa-hc",
|
||||
.probe = hwahc_probe,
|
||||
.disconnect = hwahc_disconnect,
|
||||
.id_table = hwahc_id_table,
|
||||
};
|
||||
|
||||
static int __init hwahc_driver_init(void)
|
||||
{
|
||||
int result;
|
||||
result = usb_register(&hwahc_driver);
|
||||
if (result < 0) {
|
||||
printk(KERN_ERR "WA-CDS: Cannot register USB driver: %d\n",
|
||||
result);
|
||||
goto error_usb_register;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error_usb_register:
|
||||
return result;
|
||||
|
||||
}
|
||||
module_init(hwahc_driver_init);
|
||||
|
||||
static void __exit hwahc_driver_exit(void)
|
||||
{
|
||||
usb_deregister(&hwahc_driver);
|
||||
}
|
||||
module_exit(hwahc_driver_exit);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,11 @@
|
|||
obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o
|
||||
|
||||
whci-hcd-y := \
|
||||
asl.o \
|
||||
hcd.o \
|
||||
hw.o \
|
||||
init.o \
|
||||
int.o \
|
||||
pzl.o \
|
||||
qset.o \
|
||||
wusb.o
|
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) asynchronous schedule management.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
#include <linux/usb.h>
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
#include "../../wusbcore/wusbhc.h"
|
||||
|
||||
#include "whcd.h"
|
||||
|
||||
#if D_LOCAL >= 4
|
||||
static void dump_asl(struct whc *whc, const char *tag)
|
||||
{
|
||||
struct device *dev = &whc->umc->dev;
|
||||
struct whc_qset *qset;
|
||||
|
||||
d_printf(4, dev, "ASL %s\n", tag);
|
||||
|
||||
list_for_each_entry(qset, &whc->async_list, list_node) {
|
||||
dump_qset(qset, dev);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void dump_asl(struct whc *whc, const char *tag)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset,
|
||||
struct whc_qset **next, struct whc_qset **prev)
|
||||
{
|
||||
struct list_head *n, *p;
|
||||
|
||||
BUG_ON(list_empty(&whc->async_list));
|
||||
|
||||
n = qset->list_node.next;
|
||||
if (n == &whc->async_list)
|
||||
n = n->next;
|
||||
p = qset->list_node.prev;
|
||||
if (p == &whc->async_list)
|
||||
p = p->prev;
|
||||
|
||||
*next = container_of(n, struct whc_qset, list_node);
|
||||
*prev = container_of(p, struct whc_qset, list_node);
|
||||
|
||||
}
|
||||
|
||||
static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
list_move(&qset->list_node, &whc->async_list);
|
||||
qset->in_sw_list = true;
|
||||
}
|
||||
|
||||
static void asl_qset_insert(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
struct whc_qset *next, *prev;
|
||||
|
||||
qset_clear(whc, qset);
|
||||
|
||||
/* Link into ASL. */
|
||||
qset_get_next_prev(whc, qset, &next, &prev);
|
||||
whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma);
|
||||
whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma);
|
||||
qset->in_hw_list = true;
|
||||
}
|
||||
|
||||
static void asl_qset_remove(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
struct whc_qset *prev, *next;
|
||||
|
||||
qset_get_next_prev(whc, qset, &next, &prev);
|
||||
|
||||
list_move(&qset->list_node, &whc->async_removed_list);
|
||||
qset->in_sw_list = false;
|
||||
|
||||
/*
|
||||
* No more qsets in the ASL? The caller must stop the ASL as
|
||||
* it's no longer valid.
|
||||
*/
|
||||
if (list_empty(&whc->async_list))
|
||||
return;
|
||||
|
||||
/* Remove from ASL. */
|
||||
whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma);
|
||||
qset->in_hw_list = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* process_qset - process any recently inactivated or halted qTDs in a
|
||||
* qset.
|
||||
*
|
||||
* After inactive qTDs are removed, new qTDs can be added if the
|
||||
* urb queue still contains URBs.
|
||||
*
|
||||
* Returns any additional WUSBCMD bits for the ASL sync command (i.e.,
|
||||
* WUSBCMD_ASYNC_QSET_RM if a halted qset was removed).
|
||||
*/
|
||||
static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
enum whc_update update = 0;
|
||||
uint32_t status = 0;
|
||||
|
||||
while (qset->ntds) {
|
||||
struct whc_qtd *td;
|
||||
int t;
|
||||
|
||||
t = qset->td_start;
|
||||
td = &qset->qtd[qset->td_start];
|
||||
status = le32_to_cpu(td->status);
|
||||
|
||||
/*
|
||||
* Nothing to do with a still active qTD.
|
||||
*/
|
||||
if (status & QTD_STS_ACTIVE)
|
||||
break;
|
||||
|
||||
if (status & QTD_STS_HALTED) {
|
||||
/* Ug, an error. */
|
||||
process_halted_qtd(whc, qset, td);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Mmm, a completed qTD. */
|
||||
process_inactive_qtd(whc, qset, td);
|
||||
}
|
||||
|
||||
update |= qset_add_qtds(whc, qset);
|
||||
|
||||
done:
|
||||
/*
|
||||
* Remove this qset from the ASL if requested, but only if has
|
||||
* no qTDs.
|
||||
*/
|
||||
if (qset->remove && qset->ntds == 0) {
|
||||
asl_qset_remove(whc, qset);
|
||||
update |= WHC_UPDATE_REMOVED;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
void asl_start(struct whc *whc)
|
||||
{
|
||||
struct whc_qset *qset;
|
||||
|
||||
qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
|
||||
|
||||
le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR);
|
||||
|
||||
whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN);
|
||||
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
|
||||
WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED,
|
||||
1000, "start ASL");
|
||||
}
|
||||
|
||||
void asl_stop(struct whc *whc)
|
||||
{
|
||||
whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0);
|
||||
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
|
||||
WUSBSTS_ASYNC_SCHED, 0,
|
||||
1000, "stop ASL");
|
||||
}
|
||||
|
||||
void asl_update(struct whc *whc, uint32_t wusbcmd)
|
||||
{
|
||||
whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
|
||||
wait_event(whc->async_list_wq,
|
||||
(le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* scan_async_work - scan the ASL for qsets to process.
|
||||
*
|
||||
* Process each qset in the ASL in turn and then signal the WHC that
|
||||
* the ASL has been updated.
|
||||
*
|
||||
* Then start, stop or update the asynchronous schedule as required.
|
||||
*/
|
||||
void scan_async_work(struct work_struct *work)
|
||||
{
|
||||
struct whc *whc = container_of(work, struct whc, async_work);
|
||||
struct whc_qset *qset, *t;
|
||||
enum whc_update update = 0;
|
||||
|
||||
spin_lock_irq(&whc->lock);
|
||||
|
||||
dump_asl(whc, "before processing");
|
||||
|
||||
/*
|
||||
* Transerve the software list backwards so new qsets can be
|
||||
* safely inserted into the ASL without making it non-circular.
|
||||
*/
|
||||
list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) {
|
||||
if (!qset->in_hw_list) {
|
||||
asl_qset_insert(whc, qset);
|
||||
update |= WHC_UPDATE_ADDED;
|
||||
}
|
||||
|
||||
update |= process_qset(whc, qset);
|
||||
}
|
||||
|
||||
dump_asl(whc, "after processing");
|
||||
|
||||
spin_unlock_irq(&whc->lock);
|
||||
|
||||
if (update) {
|
||||
uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB;
|
||||
if (update & WHC_UPDATE_REMOVED)
|
||||
wusbcmd |= WUSBCMD_ASYNC_QSET_RM;
|
||||
asl_update(whc, wusbcmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that the ASL is updated, complete the removal of any
|
||||
* removed qsets.
|
||||
*/
|
||||
spin_lock(&whc->lock);
|
||||
|
||||
list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) {
|
||||
qset_remove_complete(whc, qset);
|
||||
}
|
||||
|
||||
spin_unlock(&whc->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* asl_urb_enqueue - queue an URB onto the asynchronous list (ASL).
|
||||
* @whc: the WHCI host controller
|
||||
* @urb: the URB to enqueue
|
||||
* @mem_flags: flags for any memory allocations
|
||||
*
|
||||
* The qset for the endpoint is obtained and the urb queued on to it.
|
||||
*
|
||||
* Work is scheduled to update the hardware's view of the ASL.
|
||||
*/
|
||||
int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
|
||||
{
|
||||
struct whc_qset *qset;
|
||||
int err;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&whc->lock, flags);
|
||||
|
||||
qset = get_qset(whc, urb, GFP_ATOMIC);
|
||||
if (qset == NULL)
|
||||
err = -ENOMEM;
|
||||
else
|
||||
err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
|
||||
if (!err) {
|
||||
usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
|
||||
if (!qset->in_sw_list)
|
||||
asl_qset_insert_begin(whc, qset);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&whc->lock, flags);
|
||||
|
||||
if (!err)
|
||||
queue_work(whc->workqueue, &whc->async_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* asl_urb_dequeue - remove an URB (qset) from the async list.
|
||||
* @whc: the WHCI host controller
|
||||
* @urb: the URB to dequeue
|
||||
* @status: the current status of the URB
|
||||
*
|
||||
* URBs that do yet have qTDs can simply be removed from the software
|
||||
* queue, otherwise the qset must be removed from the ASL so the qTDs
|
||||
* can be removed.
|
||||
*/
|
||||
int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
|
||||
{
|
||||
struct whc_urb *wurb = urb->hcpriv;
|
||||
struct whc_qset *qset = wurb->qset;
|
||||
struct whc_std *std, *t;
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&whc->lock, flags);
|
||||
|
||||
ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
|
||||
if (std->urb == urb)
|
||||
qset_free_std(whc, std);
|
||||
else
|
||||
std->qtd = NULL; /* so this std is re-added when the qset is */
|
||||
}
|
||||
|
||||
asl_qset_remove(whc, qset);
|
||||
wurb->status = status;
|
||||
wurb->is_async = true;
|
||||
queue_work(whc->workqueue, &wurb->dequeue_work);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&whc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* asl_qset_delete - delete a qset from the ASL
|
||||
*/
|
||||
void asl_qset_delete(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
qset->remove = 1;
|
||||
queue_work(whc->workqueue, &whc->async_work);
|
||||
qset_delete(whc, qset);
|
||||
}
|
||||
|
||||
/**
|
||||
* asl_init - initialize the asynchronous schedule list
|
||||
*
|
||||
* A dummy qset with no qTDs is added to the ASL to simplify removing
|
||||
* qsets (no need to stop the ASL when the last qset is removed).
|
||||
*/
|
||||
int asl_init(struct whc *whc)
|
||||
{
|
||||
struct whc_qset *qset;
|
||||
|
||||
qset = qset_alloc(whc, GFP_KERNEL);
|
||||
if (qset == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
asl_qset_insert_begin(whc, qset);
|
||||
asl_qset_insert(whc, qset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* asl_clean_up - free ASL resources
|
||||
*
|
||||
* The ASL is stopped and empty except for the dummy qset.
|
||||
*/
|
||||
void asl_clean_up(struct whc *whc)
|
||||
{
|
||||
struct whc_qset *qset;
|
||||
|
||||
if (!list_empty(&whc->async_list)) {
|
||||
qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
|
||||
list_del(&qset->list_node);
|
||||
qset_free(whc, qset);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) driver.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
|
||||
#include "../../wusbcore/wusbhc.h"
|
||||
|
||||
#include "whcd.h"
|
||||
|
||||
/*
|
||||
* One time initialization.
|
||||
*
|
||||
* Nothing to do here.
|
||||
*/
|
||||
static int whc_reset(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start the wireless host controller.
|
||||
*
|
||||
* Start device notification.
|
||||
*
|
||||
* Put hc into run state, set DNTS parameters.
|
||||
*/
|
||||
static int whc_start(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
u8 bcid;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
|
||||
le_writel(WUSBINTR_GEN_CMD_DONE
|
||||
| WUSBINTR_HOST_ERR
|
||||
| WUSBINTR_ASYNC_SCHED_SYNCED
|
||||
| WUSBINTR_DNTS_INT
|
||||
| WUSBINTR_ERR_INT
|
||||
| WUSBINTR_INT,
|
||||
whc->base + WUSBINTR);
|
||||
|
||||
/* set cluster ID */
|
||||
bcid = wusb_cluster_id_get();
|
||||
ret = whc_set_cluster_id(whc, bcid);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
wusbhc->cluster_id = bcid;
|
||||
|
||||
/* start HC */
|
||||
whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN);
|
||||
|
||||
usb_hcd->uses_new_polling = 1;
|
||||
usb_hcd->poll_rh = 1;
|
||||
usb_hcd->state = HC_STATE_RUNNING;
|
||||
|
||||
out:
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Stop the wireless host controller.
|
||||
*
|
||||
* Stop device notification.
|
||||
*
|
||||
* Wait for pending transfer to stop? Put hc into stop state?
|
||||
*/
|
||||
static void whc_stop(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
|
||||
wusbhc_stop(wusbhc);
|
||||
|
||||
/* stop HC */
|
||||
le_writel(0, whc->base + WUSBINTR);
|
||||
whc_write_wusbcmd(whc, WUSBCMD_RUN, 0);
|
||||
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
|
||||
WUSBSTS_HCHALTED, WUSBSTS_HCHALTED,
|
||||
100, "HC to halt");
|
||||
|
||||
wusb_cluster_id_put(wusbhc->cluster_id);
|
||||
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
}
|
||||
|
||||
static int whc_get_frame_number(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
/* Frame numbers are not applicable to WUSB. */
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Queue an URB to the ASL or PZL
|
||||
*/
|
||||
static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
|
||||
gfp_t mem_flags)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
int ret;
|
||||
|
||||
switch (usb_pipetype(urb->pipe)) {
|
||||
case PIPE_INTERRUPT:
|
||||
ret = pzl_urb_enqueue(whc, urb, mem_flags);
|
||||
break;
|
||||
case PIPE_ISOCHRONOUS:
|
||||
dev_err(&whc->umc->dev, "isochronous transfers unsupported\n");
|
||||
ret = -ENOTSUPP;
|
||||
break;
|
||||
case PIPE_CONTROL:
|
||||
case PIPE_BULK:
|
||||
default:
|
||||
ret = asl_urb_enqueue(whc, urb, mem_flags);
|
||||
break;
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a queued URB from the ASL or PZL.
|
||||
*/
|
||||
static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
int ret;
|
||||
|
||||
switch (usb_pipetype(urb->pipe)) {
|
||||
case PIPE_INTERRUPT:
|
||||
ret = pzl_urb_dequeue(whc, urb, status);
|
||||
break;
|
||||
case PIPE_ISOCHRONOUS:
|
||||
ret = -ENOTSUPP;
|
||||
break;
|
||||
case PIPE_CONTROL:
|
||||
case PIPE_BULK:
|
||||
default:
|
||||
ret = asl_urb_dequeue(whc, urb, status);
|
||||
break;
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for all URBs to the endpoint to be completed, then delete the
|
||||
* qset.
|
||||
*/
|
||||
static void whc_endpoint_disable(struct usb_hcd *usb_hcd,
|
||||
struct usb_host_endpoint *ep)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
struct whc_qset *qset;
|
||||
|
||||
qset = ep->hcpriv;
|
||||
if (qset) {
|
||||
ep->hcpriv = NULL;
|
||||
if (usb_endpoint_xfer_bulk(&ep->desc)
|
||||
|| usb_endpoint_xfer_control(&ep->desc))
|
||||
asl_qset_delete(whc, qset);
|
||||
else
|
||||
pzl_qset_delete(whc, qset);
|
||||
}
|
||||
}
|
||||
|
||||
static struct hc_driver whc_hc_driver = {
|
||||
.description = "whci-hcd",
|
||||
.product_desc = "Wireless host controller",
|
||||
.hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd),
|
||||
.irq = whc_int_handler,
|
||||
.flags = HCD_USB2,
|
||||
|
||||
.reset = whc_reset,
|
||||
.start = whc_start,
|
||||
.stop = whc_stop,
|
||||
.get_frame_number = whc_get_frame_number,
|
||||
.urb_enqueue = whc_urb_enqueue,
|
||||
.urb_dequeue = whc_urb_dequeue,
|
||||
.endpoint_disable = whc_endpoint_disable,
|
||||
|
||||
.hub_status_data = wusbhc_rh_status_data,
|
||||
.hub_control = wusbhc_rh_control,
|
||||
.bus_suspend = wusbhc_rh_suspend,
|
||||
.bus_resume = wusbhc_rh_resume,
|
||||
.start_port_reset = wusbhc_rh_start_port_reset,
|
||||
};
|
||||
|
||||
static int whc_probe(struct umc_dev *umc)
|
||||
{
|
||||
int ret = -ENOMEM;
|
||||
struct usb_hcd *usb_hcd;
|
||||
struct wusbhc *wusbhc = NULL;
|
||||
struct whc *whc = NULL;
|
||||
struct device *dev = &umc->dev;
|
||||
|
||||
usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci");
|
||||
if (usb_hcd == NULL) {
|
||||
dev_err(dev, "unable to create hcd\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
usb_hcd->wireless = 1;
|
||||
|
||||
wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
whc = wusbhc_to_whc(wusbhc);
|
||||
whc->umc = umc;
|
||||
|
||||
ret = whc_init(whc);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
wusbhc->dev = dev;
|
||||
wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent);
|
||||
if (!wusbhc->uwb_rc) {
|
||||
ret = -ENODEV;
|
||||
dev_err(dev, "cannot get radio controller\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (whc->n_devices > USB_MAXCHILDREN) {
|
||||
dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n",
|
||||
whc->n_devices);
|
||||
wusbhc->ports_max = USB_MAXCHILDREN;
|
||||
} else
|
||||
wusbhc->ports_max = whc->n_devices;
|
||||
wusbhc->mmcies_max = whc->n_mmc_ies;
|
||||
wusbhc->start = whc_wusbhc_start;
|
||||
wusbhc->stop = whc_wusbhc_stop;
|
||||
wusbhc->mmcie_add = whc_mmcie_add;
|
||||
wusbhc->mmcie_rm = whc_mmcie_rm;
|
||||
wusbhc->dev_info_set = whc_dev_info_set;
|
||||
wusbhc->bwa_set = whc_bwa_set;
|
||||
wusbhc->set_num_dnts = whc_set_num_dnts;
|
||||
wusbhc->set_ptk = whc_set_ptk;
|
||||
wusbhc->set_gtk = whc_set_gtk;
|
||||
|
||||
ret = wusbhc_create(wusbhc);
|
||||
if (ret)
|
||||
goto error_wusbhc_create;
|
||||
|
||||
ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot add HCD: %d\n", ret);
|
||||
goto error_usb_add_hcd;
|
||||
}
|
||||
|
||||
ret = wusbhc_b_create(wusbhc);
|
||||
if (ret) {
|
||||
dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret);
|
||||
goto error_wusbhc_b_create;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_wusbhc_b_create:
|
||||
usb_remove_hcd(usb_hcd);
|
||||
error_usb_add_hcd:
|
||||
wusbhc_destroy(wusbhc);
|
||||
error_wusbhc_create:
|
||||
uwb_rc_put(wusbhc->uwb_rc);
|
||||
error:
|
||||
whc_clean_up(whc);
|
||||
if (usb_hcd)
|
||||
usb_put_hcd(usb_hcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void whc_remove(struct umc_dev *umc)
|
||||
{
|
||||
struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev);
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
|
||||
if (usb_hcd) {
|
||||
wusbhc_b_destroy(wusbhc);
|
||||
usb_remove_hcd(usb_hcd);
|
||||
wusbhc_destroy(wusbhc);
|
||||
uwb_rc_put(wusbhc->uwb_rc);
|
||||
whc_clean_up(whc);
|
||||
usb_put_hcd(usb_hcd);
|
||||
}
|
||||
}
|
||||
|
||||
static struct umc_driver whci_hc_driver = {
|
||||
.name = "whci-hcd",
|
||||
.cap_id = UMC_CAP_ID_WHCI_WUSB_HC,
|
||||
.probe = whc_probe,
|
||||
.remove = whc_remove,
|
||||
};
|
||||
|
||||
static int __init whci_hc_driver_init(void)
|
||||
{
|
||||
return umc_driver_register(&whci_hc_driver);
|
||||
}
|
||||
module_init(whci_hc_driver_init);
|
||||
|
||||
static void __exit whci_hc_driver_exit(void)
|
||||
{
|
||||
umc_driver_unregister(&whci_hc_driver);
|
||||
}
|
||||
module_exit(whci_hc_driver_exit);
|
||||
|
||||
/* PCI device ID's that we handle (so it gets loaded) */
|
||||
static struct pci_device_id whci_hcd_id_table[] = {
|
||||
{ PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
|
||||
{ /* empty last entry */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, whci_hcd_id_table);
|
||||
|
||||
MODULE_DESCRIPTION("WHCI Wireless USB host controller driver");
|
||||
MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) hardware access helpers.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
|
||||
#include "../../wusbcore/wusbhc.h"
|
||||
|
||||
#include "whcd.h"
|
||||
|
||||
void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 cmd;
|
||||
|
||||
spin_lock_irqsave(&whc->lock, flags);
|
||||
|
||||
cmd = le_readl(whc->base + WUSBCMD);
|
||||
cmd = (cmd & ~mask) | val;
|
||||
le_writel(cmd, whc->base + WUSBCMD);
|
||||
|
||||
spin_unlock_irqrestore(&whc->lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register
|
||||
* @whc: the WHCI HC
|
||||
* @cmd: command to start.
|
||||
* @params: parameters for the command (the WUSBGENCMDPARAMS register value).
|
||||
* @addr: pointer to any data for the command (may be NULL).
|
||||
* @len: length of the data (if any).
|
||||
*/
|
||||
int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len)
|
||||
{
|
||||
unsigned long flags;
|
||||
dma_addr_t dma_addr;
|
||||
int t;
|
||||
|
||||
mutex_lock(&whc->mutex);
|
||||
|
||||
/* Wait for previous command to complete. */
|
||||
t = wait_event_timeout(whc->cmd_wq,
|
||||
(le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0,
|
||||
WHC_GENCMD_TIMEOUT_MS);
|
||||
if (t == 0) {
|
||||
dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n",
|
||||
le_readl(whc->base + WUSBGENCMDSTS),
|
||||
le_readl(whc->base + WUSBGENCMDPARAMS));
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (addr) {
|
||||
memcpy(whc->gen_cmd_buf, addr, len);
|
||||
dma_addr = whc->gen_cmd_buf_dma;
|
||||
} else
|
||||
dma_addr = 0;
|
||||
|
||||
/* Poke registers to start cmd. */
|
||||
spin_lock_irqsave(&whc->lock, flags);
|
||||
|
||||
le_writel(params, whc->base + WUSBGENCMDPARAMS);
|
||||
le_writeq(dma_addr, whc->base + WUSBGENADDR);
|
||||
|
||||
le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd,
|
||||
whc->base + WUSBGENCMDSTS);
|
||||
|
||||
spin_unlock_irqrestore(&whc->lock, flags);
|
||||
|
||||
mutex_unlock(&whc->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) initialization.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
|
||||
#include "../../wusbcore/wusbhc.h"
|
||||
|
||||
#include "whcd.h"
|
||||
|
||||
/*
|
||||
* Reset the host controller.
|
||||
*/
|
||||
static void whc_hw_reset(struct whc *whc)
|
||||
{
|
||||
le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD);
|
||||
whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0,
|
||||
100, "reset");
|
||||
}
|
||||
|
||||
static void whc_hw_init_di_buf(struct whc *whc)
|
||||
{
|
||||
int d;
|
||||
|
||||
/* Disable all entries in the Device Information buffer. */
|
||||
for (d = 0; d < whc->n_devices; d++)
|
||||
whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE;
|
||||
|
||||
le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR);
|
||||
}
|
||||
|
||||
static void whc_hw_init_dn_buf(struct whc *whc)
|
||||
{
|
||||
/* Clear the Device Notification buffer to ensure the V (valid)
|
||||
* bits are clear. */
|
||||
memset(whc->dn_buf, 0, 4096);
|
||||
|
||||
le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR);
|
||||
}
|
||||
|
||||
int whc_init(struct whc *whc)
|
||||
{
|
||||
u32 whcsparams;
|
||||
int ret, i;
|
||||
resource_size_t start, len;
|
||||
|
||||
spin_lock_init(&whc->lock);
|
||||
mutex_init(&whc->mutex);
|
||||
init_waitqueue_head(&whc->cmd_wq);
|
||||
init_waitqueue_head(&whc->async_list_wq);
|
||||
init_waitqueue_head(&whc->periodic_list_wq);
|
||||
whc->workqueue = create_singlethread_workqueue(dev_name(&whc->umc->dev));
|
||||
if (whc->workqueue == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
INIT_WORK(&whc->dn_work, whc_dn_work);
|
||||
|
||||
INIT_WORK(&whc->async_work, scan_async_work);
|
||||
INIT_LIST_HEAD(&whc->async_list);
|
||||
INIT_LIST_HEAD(&whc->async_removed_list);
|
||||
|
||||
INIT_WORK(&whc->periodic_work, scan_periodic_work);
|
||||
for (i = 0; i < 5; i++)
|
||||
INIT_LIST_HEAD(&whc->periodic_list[i]);
|
||||
INIT_LIST_HEAD(&whc->periodic_removed_list);
|
||||
|
||||
/* Map HC registers. */
|
||||
start = whc->umc->resource.start;
|
||||
len = whc->umc->resource.end - start + 1;
|
||||
if (!request_mem_region(start, len, "whci-hc")) {
|
||||
dev_err(&whc->umc->dev, "can't request HC region\n");
|
||||
ret = -EBUSY;
|
||||
goto error;
|
||||
}
|
||||
whc->base_phys = start;
|
||||
whc->base = ioremap(start, len);
|
||||
if (!whc->base) {
|
||||
dev_err(&whc->umc->dev, "ioremap\n");
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
whc_hw_reset(whc);
|
||||
|
||||
/* Read maximum number of devices, keys and MMC IEs. */
|
||||
whcsparams = le_readl(whc->base + WHCSPARAMS);
|
||||
whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams);
|
||||
whc->n_keys = WHCSPARAMS_TO_N_KEYS(whcsparams);
|
||||
whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams);
|
||||
|
||||
dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n",
|
||||
whc->n_devices, whc->n_keys, whc->n_mmc_ies);
|
||||
|
||||
whc->qset_pool = dma_pool_create("qset", &whc->umc->dev,
|
||||
sizeof(struct whc_qset), 64, 0);
|
||||
if (whc->qset_pool == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = asl_init(whc);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
ret = pzl_init(whc);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
/* Allocate and initialize a buffer for generic commands, the
|
||||
Device Information buffer, and the Device Notification
|
||||
buffer. */
|
||||
|
||||
whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
|
||||
&whc->gen_cmd_buf_dma, GFP_KERNEL);
|
||||
if (whc->gen_cmd_buf == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
whc->dn_buf = dma_alloc_coherent(&whc->umc->dev,
|
||||
sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
|
||||
&whc->dn_buf_dma, GFP_KERNEL);
|
||||
if (!whc->dn_buf) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
whc_hw_init_dn_buf(whc);
|
||||
|
||||
whc->di_buf = dma_alloc_coherent(&whc->umc->dev,
|
||||
sizeof(struct di_buf_entry) * whc->n_devices,
|
||||
&whc->di_buf_dma, GFP_KERNEL);
|
||||
if (!whc->di_buf) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
whc_hw_init_di_buf(whc);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
whc_clean_up(whc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void whc_clean_up(struct whc *whc)
|
||||
{
|
||||
resource_size_t len;
|
||||
|
||||
if (whc->di_buf)
|
||||
dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices,
|
||||
whc->di_buf, whc->di_buf_dma);
|
||||
if (whc->dn_buf)
|
||||
dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
|
||||
whc->dn_buf, whc->dn_buf_dma);
|
||||
if (whc->gen_cmd_buf)
|
||||
dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
|
||||
whc->gen_cmd_buf, whc->gen_cmd_buf_dma);
|
||||
|
||||
pzl_clean_up(whc);
|
||||
asl_clean_up(whc);
|
||||
|
||||
if (whc->qset_pool)
|
||||
dma_pool_destroy(whc->qset_pool);
|
||||
|
||||
len = whc->umc->resource.end - whc->umc->resource.start + 1;
|
||||
if (whc->base)
|
||||
iounmap(whc->base);
|
||||
if (whc->base_phys)
|
||||
release_mem_region(whc->base_phys, len);
|
||||
|
||||
if (whc->workqueue)
|
||||
destroy_workqueue(whc->workqueue);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) interrupt handling.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
|
||||
#include "../../wusbcore/wusbhc.h"
|
||||
|
||||
#include "whcd.h"
|
||||
|
||||
static void transfer_done(struct whc *whc)
|
||||
{
|
||||
queue_work(whc->workqueue, &whc->async_work);
|
||||
queue_work(whc->workqueue, &whc->periodic_work);
|
||||
}
|
||||
|
||||
irqreturn_t whc_int_handler(struct usb_hcd *hcd)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd);
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
u32 sts;
|
||||
|
||||
sts = le_readl(whc->base + WUSBSTS);
|
||||
if (!(sts & WUSBSTS_INT_MASK))
|
||||
return IRQ_NONE;
|
||||
le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS);
|
||||
|
||||
if (sts & WUSBSTS_GEN_CMD_DONE)
|
||||
wake_up(&whc->cmd_wq);
|
||||
|
||||
if (sts & WUSBSTS_HOST_ERR)
|
||||
dev_err(&whc->umc->dev, "FIXME: host system error\n");
|
||||
|
||||
if (sts & WUSBSTS_ASYNC_SCHED_SYNCED)
|
||||
wake_up(&whc->async_list_wq);
|
||||
|
||||
if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED)
|
||||
wake_up(&whc->periodic_list_wq);
|
||||
|
||||
if (sts & WUSBSTS_DNTS_INT)
|
||||
queue_work(whc->workqueue, &whc->dn_work);
|
||||
|
||||
/*
|
||||
* A transfer completed (see [WHCI] section 4.7.1.2 for when
|
||||
* this occurs).
|
||||
*/
|
||||
if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT))
|
||||
transfer_done(whc);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int process_dn_buf(struct whc *whc)
|
||||
{
|
||||
struct wusbhc *wusbhc = &whc->wusbhc;
|
||||
struct dn_buf_entry *dn;
|
||||
int processed = 0;
|
||||
|
||||
for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) {
|
||||
if (dn->status & WHC_DN_STATUS_VALID) {
|
||||
wusbhc_handle_dn(wusbhc, dn->src_addr,
|
||||
(struct wusb_dn_hdr *)dn->dn_data,
|
||||
dn->msg_size);
|
||||
dn->status &= ~WHC_DN_STATUS_VALID;
|
||||
processed++;
|
||||
}
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
void whc_dn_work(struct work_struct *work)
|
||||
{
|
||||
struct whc *whc = container_of(work, struct whc, dn_work);
|
||||
int processed;
|
||||
|
||||
do {
|
||||
processed = process_dn_buf(whc);
|
||||
} while (processed);
|
||||
}
|
|
@ -0,0 +1,398 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) periodic schedule management.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
#include <linux/usb.h>
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
#include "../../wusbcore/wusbhc.h"
|
||||
|
||||
#include "whcd.h"
|
||||
|
||||
#if D_LOCAL >= 4
|
||||
static void dump_pzl(struct whc *whc, const char *tag)
|
||||
{
|
||||
struct device *dev = &whc->umc->dev;
|
||||
struct whc_qset *qset;
|
||||
int period = 0;
|
||||
|
||||
d_printf(4, dev, "PZL %s\n", tag);
|
||||
|
||||
for (period = 0; period < 5; period++) {
|
||||
d_printf(4, dev, "Period %d\n", period);
|
||||
list_for_each_entry(qset, &whc->periodic_list[period], list_node) {
|
||||
dump_qset(qset, dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void dump_pzl(struct whc *whc, const char *tag)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static void update_pzl_pointers(struct whc *whc, int period, u64 addr)
|
||||
{
|
||||
switch (period) {
|
||||
case 0:
|
||||
whc_qset_set_link_ptr(&whc->pz_list[0], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[2], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[4], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[6], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[8], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[10], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[12], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[14], addr);
|
||||
break;
|
||||
case 1:
|
||||
whc_qset_set_link_ptr(&whc->pz_list[1], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[5], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[9], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[13], addr);
|
||||
break;
|
||||
case 2:
|
||||
whc_qset_set_link_ptr(&whc->pz_list[3], addr);
|
||||
whc_qset_set_link_ptr(&whc->pz_list[11], addr);
|
||||
break;
|
||||
case 3:
|
||||
whc_qset_set_link_ptr(&whc->pz_list[7], addr);
|
||||
break;
|
||||
case 4:
|
||||
whc_qset_set_link_ptr(&whc->pz_list[15], addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the 'period' to use for this qset. The minimum interval for
|
||||
* the endpoint is used so whatever urbs are submitted the device is
|
||||
* polled often enough.
|
||||
*/
|
||||
static int qset_get_period(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
uint8_t bInterval = qset->ep->desc.bInterval;
|
||||
|
||||
if (bInterval < 6)
|
||||
bInterval = 6;
|
||||
if (bInterval > 10)
|
||||
bInterval = 10;
|
||||
return bInterval - 6;
|
||||
}
|
||||
|
||||
static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
int period;
|
||||
|
||||
period = qset_get_period(whc, qset);
|
||||
|
||||
qset_clear(whc, qset);
|
||||
list_move(&qset->list_node, &whc->periodic_list[period]);
|
||||
qset->in_sw_list = true;
|
||||
}
|
||||
|
||||
static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
list_move(&qset->list_node, &whc->periodic_removed_list);
|
||||
qset->in_hw_list = false;
|
||||
qset->in_sw_list = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* pzl_process_qset - process any recently inactivated or halted qTDs
|
||||
* in a qset.
|
||||
*
|
||||
* After inactive qTDs are removed, new qTDs can be added if the
|
||||
* urb queue still contains URBs.
|
||||
*
|
||||
* Returns the schedule updates required.
|
||||
*/
|
||||
static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
enum whc_update update = 0;
|
||||
uint32_t status = 0;
|
||||
|
||||
while (qset->ntds) {
|
||||
struct whc_qtd *td;
|
||||
int t;
|
||||
|
||||
t = qset->td_start;
|
||||
td = &qset->qtd[qset->td_start];
|
||||
status = le32_to_cpu(td->status);
|
||||
|
||||
/*
|
||||
* Nothing to do with a still active qTD.
|
||||
*/
|
||||
if (status & QTD_STS_ACTIVE)
|
||||
break;
|
||||
|
||||
if (status & QTD_STS_HALTED) {
|
||||
/* Ug, an error. */
|
||||
process_halted_qtd(whc, qset, td);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Mmm, a completed qTD. */
|
||||
process_inactive_qtd(whc, qset, td);
|
||||
}
|
||||
|
||||
update |= qset_add_qtds(whc, qset);
|
||||
|
||||
done:
|
||||
/*
|
||||
* If there are no qTDs in this qset, remove it from the PZL.
|
||||
*/
|
||||
if (qset->remove && qset->ntds == 0) {
|
||||
pzl_qset_remove(whc, qset);
|
||||
update |= WHC_UPDATE_REMOVED;
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
/**
|
||||
* pzl_start - start the periodic schedule
|
||||
* @whc: the WHCI host controller
|
||||
*
|
||||
* The PZL must be valid (e.g., all entries in the list should have
|
||||
* the T bit set).
|
||||
*/
|
||||
void pzl_start(struct whc *whc)
|
||||
{
|
||||
le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
|
||||
|
||||
whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN);
|
||||
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
|
||||
WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED,
|
||||
1000, "start PZL");
|
||||
}
|
||||
|
||||
/**
|
||||
* pzl_stop - stop the periodic schedule
|
||||
* @whc: the WHCI host controller
|
||||
*/
|
||||
void pzl_stop(struct whc *whc)
|
||||
{
|
||||
whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0);
|
||||
whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
|
||||
WUSBSTS_PERIODIC_SCHED, 0,
|
||||
1000, "stop PZL");
|
||||
}
|
||||
|
||||
void pzl_update(struct whc *whc, uint32_t wusbcmd)
|
||||
{
|
||||
whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
|
||||
wait_event(whc->periodic_list_wq,
|
||||
(le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0);
|
||||
}
|
||||
|
||||
static void update_pzl_hw_view(struct whc *whc)
|
||||
{
|
||||
struct whc_qset *qset, *t;
|
||||
int period;
|
||||
u64 tmp_qh = 0;
|
||||
|
||||
for (period = 0; period < 5; period++) {
|
||||
list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
|
||||
whc_qset_set_link_ptr(&qset->qh.link, tmp_qh);
|
||||
tmp_qh = qset->qset_dma;
|
||||
qset->in_hw_list = true;
|
||||
}
|
||||
update_pzl_pointers(whc, period, tmp_qh);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* scan_periodic_work - scan the PZL for qsets to process.
|
||||
*
|
||||
* Process each qset in the PZL in turn and then signal the WHC that
|
||||
* the PZL has been updated.
|
||||
*
|
||||
* Then start, stop or update the periodic schedule as required.
|
||||
*/
|
||||
void scan_periodic_work(struct work_struct *work)
|
||||
{
|
||||
struct whc *whc = container_of(work, struct whc, periodic_work);
|
||||
struct whc_qset *qset, *t;
|
||||
enum whc_update update = 0;
|
||||
int period;
|
||||
|
||||
spin_lock_irq(&whc->lock);
|
||||
|
||||
dump_pzl(whc, "before processing");
|
||||
|
||||
for (period = 4; period >= 0; period--) {
|
||||
list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
|
||||
if (!qset->in_hw_list)
|
||||
update |= WHC_UPDATE_ADDED;
|
||||
update |= pzl_process_qset(whc, qset);
|
||||
}
|
||||
}
|
||||
|
||||
if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED))
|
||||
update_pzl_hw_view(whc);
|
||||
|
||||
dump_pzl(whc, "after processing");
|
||||
|
||||
spin_unlock_irq(&whc->lock);
|
||||
|
||||
if (update) {
|
||||
uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB;
|
||||
if (update & WHC_UPDATE_REMOVED)
|
||||
wusbcmd |= WUSBCMD_PERIODIC_QSET_RM;
|
||||
pzl_update(whc, wusbcmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that the PZL is updated, complete the removal of any
|
||||
* removed qsets.
|
||||
*/
|
||||
spin_lock(&whc->lock);
|
||||
|
||||
list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) {
|
||||
qset_remove_complete(whc, qset);
|
||||
}
|
||||
|
||||
spin_unlock(&whc->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* pzl_urb_enqueue - queue an URB onto the periodic list (PZL)
|
||||
* @whc: the WHCI host controller
|
||||
* @urb: the URB to enqueue
|
||||
* @mem_flags: flags for any memory allocations
|
||||
*
|
||||
* The qset for the endpoint is obtained and the urb queued on to it.
|
||||
*
|
||||
* Work is scheduled to update the hardware's view of the PZL.
|
||||
*/
|
||||
int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
|
||||
{
|
||||
struct whc_qset *qset;
|
||||
int err;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&whc->lock, flags);
|
||||
|
||||
qset = get_qset(whc, urb, GFP_ATOMIC);
|
||||
if (qset == NULL)
|
||||
err = -ENOMEM;
|
||||
else
|
||||
err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
|
||||
if (!err) {
|
||||
usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
|
||||
if (!qset->in_sw_list)
|
||||
qset_insert_in_sw_list(whc, qset);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&whc->lock, flags);
|
||||
|
||||
if (!err)
|
||||
queue_work(whc->workqueue, &whc->periodic_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pzl_urb_dequeue - remove an URB (qset) from the periodic list
|
||||
* @whc: the WHCI host controller
|
||||
* @urb: the URB to dequeue
|
||||
* @status: the current status of the URB
|
||||
*
|
||||
* URBs that do yet have qTDs can simply be removed from the software
|
||||
* queue, otherwise the qset must be removed so the qTDs can be safely
|
||||
* removed.
|
||||
*/
|
||||
int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
|
||||
{
|
||||
struct whc_urb *wurb = urb->hcpriv;
|
||||
struct whc_qset *qset = wurb->qset;
|
||||
struct whc_std *std, *t;
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&whc->lock, flags);
|
||||
|
||||
ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
|
||||
if (std->urb == urb)
|
||||
qset_free_std(whc, std);
|
||||
else
|
||||
std->qtd = NULL; /* so this std is re-added when the qset is */
|
||||
}
|
||||
|
||||
pzl_qset_remove(whc, qset);
|
||||
wurb->status = status;
|
||||
wurb->is_async = false;
|
||||
queue_work(whc->workqueue, &wurb->dequeue_work);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&whc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* pzl_qset_delete - delete a qset from the PZL
|
||||
*/
|
||||
void pzl_qset_delete(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
qset->remove = 1;
|
||||
queue_work(whc->workqueue, &whc->periodic_work);
|
||||
qset_delete(whc, qset);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* pzl_init - initialize the periodic zone list
|
||||
* @whc: the WHCI host controller
|
||||
*/
|
||||
int pzl_init(struct whc *whc)
|
||||
{
|
||||
int i;
|
||||
|
||||
whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16,
|
||||
&whc->pz_list_dma, GFP_KERNEL);
|
||||
if (whc->pz_list == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Set T bit on all elements in PZL. */
|
||||
for (i = 0; i < 16; i++)
|
||||
whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
|
||||
|
||||
le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pzl_clean_up - free PZL resources
|
||||
* @whc: the WHCI host controller
|
||||
*
|
||||
* The PZL is stopped and empty.
|
||||
*/
|
||||
void pzl_clean_up(struct whc *whc)
|
||||
{
|
||||
if (whc->pz_list)
|
||||
dma_free_coherent(&whc->umc->dev, sizeof(u64) * 16, whc->pz_list,
|
||||
whc->pz_list_dma);
|
||||
}
|
|
@ -0,0 +1,567 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) qset management.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
#include "../../wusbcore/wusbhc.h"
|
||||
|
||||
#include "whcd.h"
|
||||
|
||||
void dump_qset(struct whc_qset *qset, struct device *dev)
|
||||
{
|
||||
struct whc_std *std;
|
||||
struct urb *urb = NULL;
|
||||
int i;
|
||||
|
||||
dev_dbg(dev, "qset %08x\n", (u32)qset->qset_dma);
|
||||
dev_dbg(dev, " -> %08x\n", (u32)qset->qh.link);
|
||||
dev_dbg(dev, " info: %08x %08x %08x\n",
|
||||
qset->qh.info1, qset->qh.info2, qset->qh.info3);
|
||||
dev_dbg(dev, " sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count);
|
||||
dev_dbg(dev, " TD: sts: %08x opts: %08x\n",
|
||||
qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options);
|
||||
|
||||
for (i = 0; i < WHCI_QSET_TD_MAX; i++) {
|
||||
dev_dbg(dev, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n",
|
||||
i == qset->td_start ? 'S' : ' ',
|
||||
i == qset->td_end ? 'E' : ' ',
|
||||
i, qset->qtd[i].status, qset->qtd[i].options,
|
||||
(u32)qset->qtd[i].page_list_ptr);
|
||||
}
|
||||
dev_dbg(dev, " ntds: %d\n", qset->ntds);
|
||||
list_for_each_entry(std, &qset->stds, list_node) {
|
||||
if (urb != std->urb) {
|
||||
urb = std->urb;
|
||||
dev_dbg(dev, " urb %p transferred: %d bytes\n", urb,
|
||||
urb->actual_length);
|
||||
}
|
||||
if (std->qtd)
|
||||
dev_dbg(dev, " sTD[%td]: %zu bytes @ %08x\n",
|
||||
std->qtd - &qset->qtd[0],
|
||||
std->len, std->num_pointers ?
|
||||
(u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
|
||||
else
|
||||
dev_dbg(dev, " sTD[-]: %zd bytes @ %08x\n",
|
||||
std->len, std->num_pointers ?
|
||||
(u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
|
||||
}
|
||||
}
|
||||
|
||||
struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags)
|
||||
{
|
||||
struct whc_qset *qset;
|
||||
dma_addr_t dma;
|
||||
|
||||
qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma);
|
||||
if (qset == NULL)
|
||||
return NULL;
|
||||
memset(qset, 0, sizeof(struct whc_qset));
|
||||
|
||||
qset->qset_dma = dma;
|
||||
qset->whc = whc;
|
||||
|
||||
INIT_LIST_HEAD(&qset->list_node);
|
||||
INIT_LIST_HEAD(&qset->stds);
|
||||
|
||||
return qset;
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_fill_qh - fill the static endpoint state in a qset's QHead
|
||||
* @qset: the qset whose QH needs initializing with static endpoint
|
||||
* state
|
||||
* @urb: an urb for a transfer to this endpoint
|
||||
*/
|
||||
static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
|
||||
{
|
||||
struct usb_device *usb_dev = urb->dev;
|
||||
struct usb_wireless_ep_comp_descriptor *epcd;
|
||||
bool is_out;
|
||||
|
||||
is_out = usb_pipeout(urb->pipe);
|
||||
|
||||
epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
|
||||
|
||||
if (epcd) {
|
||||
qset->max_seq = epcd->bMaxSequence;
|
||||
qset->max_burst = epcd->bMaxBurst;
|
||||
} else {
|
||||
qset->max_seq = 2;
|
||||
qset->max_burst = 1;
|
||||
}
|
||||
|
||||
qset->qh.info1 = cpu_to_le32(
|
||||
QH_INFO1_EP(usb_pipeendpoint(urb->pipe))
|
||||
| (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN)
|
||||
| usb_pipe_to_qh_type(urb->pipe)
|
||||
| QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum))
|
||||
| QH_INFO1_MAX_PKT_LEN(usb_maxpacket(urb->dev, urb->pipe, is_out))
|
||||
);
|
||||
qset->qh.info2 = cpu_to_le32(
|
||||
QH_INFO2_BURST(qset->max_burst)
|
||||
| QH_INFO2_DBP(0)
|
||||
| QH_INFO2_MAX_COUNT(3)
|
||||
| QH_INFO2_MAX_RETRY(3)
|
||||
| QH_INFO2_MAX_SEQ(qset->max_seq - 1)
|
||||
);
|
||||
/* FIXME: where can we obtain these Tx parameters from? Why
|
||||
* doesn't the chip know what Tx power to use? It knows the Rx
|
||||
* strength and can presumably guess the Tx power required
|
||||
* from that? */
|
||||
qset->qh.info3 = cpu_to_le32(
|
||||
QH_INFO3_TX_RATE_53_3
|
||||
| QH_INFO3_TX_PWR(0) /* 0 == max power */
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_clear - clear fields in a qset so it may be reinserted into a
|
||||
* schedule
|
||||
*/
|
||||
void qset_clear(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
qset->td_start = qset->td_end = qset->ntds = 0;
|
||||
qset->remove = 0;
|
||||
|
||||
qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T);
|
||||
qset->qh.status = cpu_to_le16(QH_STATUS_ICUR(qset->td_start));
|
||||
qset->qh.err_count = 0;
|
||||
qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
|
||||
qset->qh.scratch[0] = 0;
|
||||
qset->qh.scratch[1] = 0;
|
||||
qset->qh.scratch[2] = 0;
|
||||
|
||||
memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay));
|
||||
|
||||
init_completion(&qset->remove_complete);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_qset - get the qset for an async endpoint
|
||||
*
|
||||
* A new qset is created if one does not already exist.
|
||||
*/
|
||||
struct whc_qset *get_qset(struct whc *whc, struct urb *urb,
|
||||
gfp_t mem_flags)
|
||||
{
|
||||
struct whc_qset *qset;
|
||||
|
||||
qset = urb->ep->hcpriv;
|
||||
if (qset == NULL) {
|
||||
qset = qset_alloc(whc, mem_flags);
|
||||
if (qset == NULL)
|
||||
return NULL;
|
||||
|
||||
qset->ep = urb->ep;
|
||||
urb->ep->hcpriv = qset;
|
||||
qset_fill_qh(qset, urb);
|
||||
}
|
||||
return qset;
|
||||
}
|
||||
|
||||
void qset_remove_complete(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
list_del_init(&qset->list_node);
|
||||
complete(&qset->remove_complete);
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_add_qtds - add qTDs for an URB to a qset
|
||||
*
|
||||
* Returns true if the list (ASL/PZL) must be updated because (for a
|
||||
* WHCI 0.95 controller) an activated qTD was pointed to be iCur.
|
||||
*/
|
||||
enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
struct whc_std *std;
|
||||
enum whc_update update = 0;
|
||||
|
||||
list_for_each_entry(std, &qset->stds, list_node) {
|
||||
struct whc_qtd *qtd;
|
||||
uint32_t status;
|
||||
|
||||
if (qset->ntds >= WHCI_QSET_TD_MAX
|
||||
|| (qset->pause_after_urb && std->urb != qset->pause_after_urb))
|
||||
break;
|
||||
|
||||
if (std->qtd)
|
||||
continue; /* already has a qTD */
|
||||
|
||||
qtd = std->qtd = &qset->qtd[qset->td_end];
|
||||
|
||||
/* Fill in setup bytes for control transfers. */
|
||||
if (usb_pipecontrol(std->urb->pipe))
|
||||
memcpy(qtd->setup, std->urb->setup_packet, 8);
|
||||
|
||||
status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len);
|
||||
|
||||
if (whc_std_last(std) && usb_pipeout(std->urb->pipe))
|
||||
status |= QTD_STS_LAST_PKT;
|
||||
|
||||
/*
|
||||
* For an IN transfer the iAlt field should be set so
|
||||
* the h/w will automatically advance to the next
|
||||
* transfer. However, if there are 8 or more TDs
|
||||
* remaining in this transfer then iAlt cannot be set
|
||||
* as it could point to somewhere in this transfer.
|
||||
*/
|
||||
if (std->ntds_remaining < WHCI_QSET_TD_MAX) {
|
||||
int ialt;
|
||||
ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX;
|
||||
status |= QTD_STS_IALT(ialt);
|
||||
} else if (usb_pipein(std->urb->pipe))
|
||||
qset->pause_after_urb = std->urb;
|
||||
|
||||
if (std->num_pointers)
|
||||
qtd->options = cpu_to_le32(QTD_OPT_IOC);
|
||||
else
|
||||
qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL);
|
||||
qtd->page_list_ptr = cpu_to_le64(std->dma_addr);
|
||||
|
||||
qtd->status = cpu_to_le32(status);
|
||||
|
||||
if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end)
|
||||
update = WHC_UPDATE_UPDATED;
|
||||
|
||||
if (++qset->td_end >= WHCI_QSET_TD_MAX)
|
||||
qset->td_end = 0;
|
||||
qset->ntds++;
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_remove_qtd - remove the first qTD from a qset.
|
||||
*
|
||||
* The qTD might be still active (if it's part of a IN URB that
|
||||
* resulted in a short read) so ensure it's deactivated.
|
||||
*/
|
||||
static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
qset->qtd[qset->td_start].status = 0;
|
||||
|
||||
if (++qset->td_start >= WHCI_QSET_TD_MAX)
|
||||
qset->td_start = 0;
|
||||
qset->ntds--;
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_free_std - remove an sTD and free it.
|
||||
* @whc: the WHCI host controller
|
||||
* @std: the sTD to remove and free.
|
||||
*/
|
||||
void qset_free_std(struct whc *whc, struct whc_std *std)
|
||||
{
|
||||
list_del(&std->list_node);
|
||||
if (std->num_pointers) {
|
||||
dma_unmap_single(whc->wusbhc.dev, std->dma_addr,
|
||||
std->num_pointers * sizeof(struct whc_page_list_entry),
|
||||
DMA_TO_DEVICE);
|
||||
kfree(std->pl_virt);
|
||||
}
|
||||
|
||||
kfree(std);
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_remove_qtds - remove an URB's qTDs (and sTDs).
|
||||
*/
|
||||
static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset,
|
||||
struct urb *urb)
|
||||
{
|
||||
struct whc_std *std, *t;
|
||||
|
||||
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
|
||||
if (std->urb != urb)
|
||||
break;
|
||||
if (std->qtd != NULL)
|
||||
qset_remove_qtd(whc, qset);
|
||||
qset_free_std(whc, std);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_free_stds - free any remaining sTDs for an URB.
|
||||
*/
|
||||
static void qset_free_stds(struct whc_qset *qset, struct urb *urb)
|
||||
{
|
||||
struct whc_std *std, *t;
|
||||
|
||||
list_for_each_entry_safe(std, t, &qset->stds, list_node) {
|
||||
if (std->urb == urb)
|
||||
qset_free_std(qset->whc, std);
|
||||
}
|
||||
}
|
||||
|
||||
static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags)
|
||||
{
|
||||
dma_addr_t dma_addr = std->dma_addr;
|
||||
dma_addr_t sp, ep;
|
||||
size_t std_len = std->len;
|
||||
size_t pl_len;
|
||||
int p;
|
||||
|
||||
sp = ALIGN(dma_addr, WHCI_PAGE_SIZE);
|
||||
ep = dma_addr + std_len;
|
||||
std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
|
||||
|
||||
pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
|
||||
std->pl_virt = kmalloc(pl_len, mem_flags);
|
||||
if (std->pl_virt == NULL)
|
||||
return -ENOMEM;
|
||||
std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE);
|
||||
|
||||
for (p = 0; p < std->num_pointers; p++) {
|
||||
std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
|
||||
dma_addr = ALIGN(dma_addr + WHCI_PAGE_SIZE, WHCI_PAGE_SIZE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* urb_dequeue_work - executes asl/pzl update and gives back the urb to the system.
|
||||
*/
|
||||
static void urb_dequeue_work(struct work_struct *work)
|
||||
{
|
||||
struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work);
|
||||
struct whc_qset *qset = wurb->qset;
|
||||
struct whc *whc = qset->whc;
|
||||
unsigned long flags;
|
||||
|
||||
if (wurb->is_async == true)
|
||||
asl_update(whc, WUSBCMD_ASYNC_UPDATED
|
||||
| WUSBCMD_ASYNC_SYNCED_DB
|
||||
| WUSBCMD_ASYNC_QSET_RM);
|
||||
else
|
||||
pzl_update(whc, WUSBCMD_PERIODIC_UPDATED
|
||||
| WUSBCMD_PERIODIC_SYNCED_DB
|
||||
| WUSBCMD_PERIODIC_QSET_RM);
|
||||
|
||||
spin_lock_irqsave(&whc->lock, flags);
|
||||
qset_remove_urb(whc, qset, wurb->urb, wurb->status);
|
||||
spin_unlock_irqrestore(&whc->lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_add_urb - add an urb to the qset's queue.
|
||||
*
|
||||
* The URB is chopped into sTDs, one for each qTD that will required.
|
||||
* At least one qTD (and sTD) is required even if the transfer has no
|
||||
* data (e.g., for some control transfers).
|
||||
*/
|
||||
int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
|
||||
gfp_t mem_flags)
|
||||
{
|
||||
struct whc_urb *wurb;
|
||||
int remaining = urb->transfer_buffer_length;
|
||||
u64 transfer_dma = urb->transfer_dma;
|
||||
int ntds_remaining;
|
||||
|
||||
ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE);
|
||||
if (ntds_remaining == 0)
|
||||
ntds_remaining = 1;
|
||||
|
||||
wurb = kzalloc(sizeof(struct whc_urb), mem_flags);
|
||||
if (wurb == NULL)
|
||||
goto err_no_mem;
|
||||
urb->hcpriv = wurb;
|
||||
wurb->qset = qset;
|
||||
wurb->urb = urb;
|
||||
INIT_WORK(&wurb->dequeue_work, urb_dequeue_work);
|
||||
|
||||
while (ntds_remaining) {
|
||||
struct whc_std *std;
|
||||
size_t std_len;
|
||||
|
||||
std = kmalloc(sizeof(struct whc_std), mem_flags);
|
||||
if (std == NULL)
|
||||
goto err_no_mem;
|
||||
|
||||
std_len = remaining;
|
||||
if (std_len > QTD_MAX_XFER_SIZE)
|
||||
std_len = QTD_MAX_XFER_SIZE;
|
||||
|
||||
std->urb = urb;
|
||||
std->dma_addr = transfer_dma;
|
||||
std->len = std_len;
|
||||
std->ntds_remaining = ntds_remaining;
|
||||
std->qtd = NULL;
|
||||
|
||||
INIT_LIST_HEAD(&std->list_node);
|
||||
list_add_tail(&std->list_node, &qset->stds);
|
||||
|
||||
if (std_len > WHCI_PAGE_SIZE) {
|
||||
if (qset_fill_page_list(whc, std, mem_flags) < 0)
|
||||
goto err_no_mem;
|
||||
} else
|
||||
std->num_pointers = 0;
|
||||
|
||||
ntds_remaining--;
|
||||
remaining -= std_len;
|
||||
transfer_dma += std_len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_no_mem:
|
||||
qset_free_stds(qset, urb);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_remove_urb - remove an URB from the urb queue.
|
||||
*
|
||||
* The URB is returned to the USB subsystem.
|
||||
*/
|
||||
void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
|
||||
struct urb *urb, int status)
|
||||
{
|
||||
struct wusbhc *wusbhc = &whc->wusbhc;
|
||||
struct whc_urb *wurb = urb->hcpriv;
|
||||
|
||||
usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb);
|
||||
/* Drop the lock as urb->complete() may enqueue another urb. */
|
||||
spin_unlock(&whc->lock);
|
||||
wusbhc_giveback_urb(wusbhc, urb, status);
|
||||
spin_lock(&whc->lock);
|
||||
|
||||
kfree(wurb);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_urb_status_from_qtd - get the completed urb status from qTD status
|
||||
* @urb: completed urb
|
||||
* @status: qTD status
|
||||
*/
|
||||
static int get_urb_status_from_qtd(struct urb *urb, u32 status)
|
||||
{
|
||||
if (status & QTD_STS_HALTED) {
|
||||
if (status & QTD_STS_DBE)
|
||||
return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM;
|
||||
else if (status & QTD_STS_BABBLE)
|
||||
return -EOVERFLOW;
|
||||
else if (status & QTD_STS_RCE)
|
||||
return -ETIME;
|
||||
return -EPIPE;
|
||||
}
|
||||
if (usb_pipein(urb->pipe)
|
||||
&& (urb->transfer_flags & URB_SHORT_NOT_OK)
|
||||
&& urb->actual_length < urb->transfer_buffer_length)
|
||||
return -EREMOTEIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* process_inactive_qtd - process an inactive (but not halted) qTD.
|
||||
*
|
||||
* Update the urb with the transfer bytes from the qTD, if the urb is
|
||||
* completely transfered or (in the case of an IN only) the LPF is
|
||||
* set, then the transfer is complete and the urb should be returned
|
||||
* to the system.
|
||||
*/
|
||||
void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
|
||||
struct whc_qtd *qtd)
|
||||
{
|
||||
struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
|
||||
struct urb *urb = std->urb;
|
||||
uint32_t status;
|
||||
bool complete;
|
||||
|
||||
status = le32_to_cpu(qtd->status);
|
||||
|
||||
urb->actual_length += std->len - QTD_STS_TO_LEN(status);
|
||||
|
||||
if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT))
|
||||
complete = true;
|
||||
else
|
||||
complete = whc_std_last(std);
|
||||
|
||||
qset_remove_qtd(whc, qset);
|
||||
qset_free_std(whc, std);
|
||||
|
||||
/*
|
||||
* Transfers for this URB are complete? Then return it to the
|
||||
* USB subsystem.
|
||||
*/
|
||||
if (complete) {
|
||||
qset_remove_qtds(whc, qset, urb);
|
||||
qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status));
|
||||
|
||||
/*
|
||||
* If iAlt isn't valid then the hardware didn't
|
||||
* advance iCur. Adjust the start and end pointers to
|
||||
* match iCur.
|
||||
*/
|
||||
if (!(status & QTD_STS_IALT_VALID))
|
||||
qset->td_start = qset->td_end
|
||||
= QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status));
|
||||
qset->pause_after_urb = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* process_halted_qtd - process a qset with a halted qtd
|
||||
*
|
||||
* Remove all the qTDs for the failed URB and return the failed URB to
|
||||
* the USB subsystem. Then remove all other qTDs so the qset can be
|
||||
* removed.
|
||||
*
|
||||
* FIXME: this is the point where rate adaptation can be done. If a
|
||||
* transfer failed because it exceeded the maximum number of retries
|
||||
* then it could be reactivated with a slower rate without having to
|
||||
* remove the qset.
|
||||
*/
|
||||
void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
|
||||
struct whc_qtd *qtd)
|
||||
{
|
||||
struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
|
||||
struct urb *urb = std->urb;
|
||||
int urb_status;
|
||||
|
||||
urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status));
|
||||
|
||||
qset_remove_qtds(whc, qset, urb);
|
||||
qset_remove_urb(whc, qset, urb, urb_status);
|
||||
|
||||
list_for_each_entry(std, &qset->stds, list_node) {
|
||||
if (qset->ntds == 0)
|
||||
break;
|
||||
qset_remove_qtd(whc, qset);
|
||||
std->qtd = NULL;
|
||||
}
|
||||
|
||||
qset->remove = 1;
|
||||
}
|
||||
|
||||
void qset_free(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
dma_pool_free(whc->qset_pool, qset, qset->qset_dma);
|
||||
}
|
||||
|
||||
/**
|
||||
* qset_delete - wait for a qset to be unused, then free it.
|
||||
*/
|
||||
void qset_delete(struct whc *whc, struct whc_qset *qset)
|
||||
{
|
||||
wait_for_completion(&qset->remove_complete);
|
||||
qset_free(whc, qset);
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) private header.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
#ifndef __WHCD_H
|
||||
#define __WHCD_H
|
||||
|
||||
#include <linux/uwb/whci.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "whci-hc.h"
|
||||
|
||||
/* Generic command timeout. */
|
||||
#define WHC_GENCMD_TIMEOUT_MS 100
|
||||
|
||||
|
||||
struct whc {
|
||||
struct wusbhc wusbhc;
|
||||
struct umc_dev *umc;
|
||||
|
||||
resource_size_t base_phys;
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
|
||||
u8 n_devices;
|
||||
u8 n_keys;
|
||||
u8 n_mmc_ies;
|
||||
|
||||
u64 *pz_list;
|
||||
struct dn_buf_entry *dn_buf;
|
||||
struct di_buf_entry *di_buf;
|
||||
dma_addr_t pz_list_dma;
|
||||
dma_addr_t dn_buf_dma;
|
||||
dma_addr_t di_buf_dma;
|
||||
|
||||
spinlock_t lock;
|
||||
struct mutex mutex;
|
||||
|
||||
void * gen_cmd_buf;
|
||||
dma_addr_t gen_cmd_buf_dma;
|
||||
wait_queue_head_t cmd_wq;
|
||||
|
||||
struct workqueue_struct *workqueue;
|
||||
struct work_struct dn_work;
|
||||
|
||||
struct dma_pool *qset_pool;
|
||||
|
||||
struct list_head async_list;
|
||||
struct list_head async_removed_list;
|
||||
wait_queue_head_t async_list_wq;
|
||||
struct work_struct async_work;
|
||||
|
||||
struct list_head periodic_list[5];
|
||||
struct list_head periodic_removed_list;
|
||||
wait_queue_head_t periodic_list_wq;
|
||||
struct work_struct periodic_work;
|
||||
};
|
||||
|
||||
#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc))
|
||||
|
||||
/**
|
||||
* struct whc_std - a software TD.
|
||||
* @urb: the URB this sTD is for.
|
||||
* @offset: start of the URB's data for this TD.
|
||||
* @len: the length of data in the associated TD.
|
||||
* @ntds_remaining: number of TDs (starting from this one) in this transfer.
|
||||
*
|
||||
* Queued URBs may require more TDs than are available in a qset so we
|
||||
* use a list of these "software TDs" (sTDs) to hold per-TD data.
|
||||
*/
|
||||
struct whc_std {
|
||||
struct urb *urb;
|
||||
size_t len;
|
||||
int ntds_remaining;
|
||||
struct whc_qtd *qtd;
|
||||
|
||||
struct list_head list_node;
|
||||
int num_pointers;
|
||||
dma_addr_t dma_addr;
|
||||
struct whc_page_list_entry *pl_virt;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct whc_urb - per URB host controller structure.
|
||||
* @urb: the URB this struct is for.
|
||||
* @qset: the qset associated to the URB.
|
||||
* @dequeue_work: the work to remove the URB when dequeued.
|
||||
* @is_async: the URB belongs to async sheduler or not.
|
||||
* @status: the status to be returned when calling wusbhc_giveback_urb.
|
||||
*/
|
||||
struct whc_urb {
|
||||
struct urb *urb;
|
||||
struct whc_qset *qset;
|
||||
struct work_struct dequeue_work;
|
||||
bool is_async;
|
||||
int status;
|
||||
};
|
||||
|
||||
/**
|
||||
* whc_std_last - is this sTD the URB's last?
|
||||
* @std: the sTD to check.
|
||||
*/
|
||||
static inline bool whc_std_last(struct whc_std *std)
|
||||
{
|
||||
return std->ntds_remaining <= 1;
|
||||
}
|
||||
|
||||
enum whc_update {
|
||||
WHC_UPDATE_ADDED = 0x01,
|
||||
WHC_UPDATE_REMOVED = 0x02,
|
||||
WHC_UPDATE_UPDATED = 0x04,
|
||||
};
|
||||
|
||||
/* init.c */
|
||||
int whc_init(struct whc *whc);
|
||||
void whc_clean_up(struct whc *whc);
|
||||
|
||||
/* hw.c */
|
||||
void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val);
|
||||
int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len);
|
||||
|
||||
/* wusb.c */
|
||||
int whc_wusbhc_start(struct wusbhc *wusbhc);
|
||||
void whc_wusbhc_stop(struct wusbhc *wusbhc);
|
||||
int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
|
||||
u8 handle, struct wuie_hdr *wuie);
|
||||
int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle);
|
||||
int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm);
|
||||
int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev);
|
||||
int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots);
|
||||
int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
|
||||
const void *ptk, size_t key_size);
|
||||
int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
|
||||
const void *gtk, size_t key_size);
|
||||
int whc_set_cluster_id(struct whc *whc, u8 bcid);
|
||||
|
||||
/* int.c */
|
||||
irqreturn_t whc_int_handler(struct usb_hcd *hcd);
|
||||
void whc_dn_work(struct work_struct *work);
|
||||
|
||||
/* asl.c */
|
||||
void asl_start(struct whc *whc);
|
||||
void asl_stop(struct whc *whc);
|
||||
int asl_init(struct whc *whc);
|
||||
void asl_clean_up(struct whc *whc);
|
||||
int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
|
||||
int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
|
||||
void asl_qset_delete(struct whc *whc, struct whc_qset *qset);
|
||||
void scan_async_work(struct work_struct *work);
|
||||
|
||||
/* pzl.c */
|
||||
int pzl_init(struct whc *whc);
|
||||
void pzl_clean_up(struct whc *whc);
|
||||
void pzl_start(struct whc *whc);
|
||||
void pzl_stop(struct whc *whc);
|
||||
int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
|
||||
int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
|
||||
void pzl_qset_delete(struct whc *whc, struct whc_qset *qset);
|
||||
void scan_periodic_work(struct work_struct *work);
|
||||
|
||||
/* qset.c */
|
||||
struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags);
|
||||
void qset_free(struct whc *whc, struct whc_qset *qset);
|
||||
struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags);
|
||||
void qset_delete(struct whc *whc, struct whc_qset *qset);
|
||||
void qset_clear(struct whc *whc, struct whc_qset *qset);
|
||||
int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
|
||||
gfp_t mem_flags);
|
||||
void qset_free_std(struct whc *whc, struct whc_std *std);
|
||||
void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
|
||||
struct urb *urb, int status);
|
||||
void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
|
||||
struct whc_qtd *qtd);
|
||||
void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
|
||||
struct whc_qtd *qtd);
|
||||
enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset);
|
||||
void qset_remove_complete(struct whc *whc, struct whc_qset *qset);
|
||||
void dump_qset(struct whc_qset *qset, struct device *dev);
|
||||
void pzl_update(struct whc *whc, uint32_t wusbcmd);
|
||||
void asl_update(struct whc *whc, uint32_t wusbcmd);
|
||||
|
||||
#endif /* #ifndef __WHCD_H */
|
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) data structures.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
#ifndef _WHCI_WHCI_HC_H
|
||||
#define _WHCI_WHCI_HC_H
|
||||
|
||||
#include <linux/list.h>
|
||||
|
||||
/**
|
||||
* WHCI_PAGE_SIZE - page size use by WHCI
|
||||
*
|
||||
* WHCI assumes that host system uses pages of 4096 octets.
|
||||
*/
|
||||
#define WHCI_PAGE_SIZE 4096
|
||||
|
||||
|
||||
/**
|
||||
* QTD_MAX_TXFER_SIZE - max number of bytes to transfer with a single
|
||||
* qtd.
|
||||
*
|
||||
* This is 2^20 - 1.
|
||||
*/
|
||||
#define QTD_MAX_XFER_SIZE 1048575
|
||||
|
||||
|
||||
/**
|
||||
* struct whc_qtd - Queue Element Transfer Descriptors (qTD)
|
||||
*
|
||||
* This describes the data for a bulk, control or interrupt transfer.
|
||||
*
|
||||
* [WHCI] section 3.2.4
|
||||
*/
|
||||
struct whc_qtd {
|
||||
__le32 status; /*< remaining transfer len and transfer status */
|
||||
__le32 options;
|
||||
__le64 page_list_ptr; /*< physical pointer to data buffer page list*/
|
||||
__u8 setup[8]; /*< setup data for control transfers */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define QTD_STS_ACTIVE (1 << 31) /* enable execution of transaction */
|
||||
#define QTD_STS_HALTED (1 << 30) /* transfer halted */
|
||||
#define QTD_STS_DBE (1 << 29) /* data buffer error */
|
||||
#define QTD_STS_BABBLE (1 << 28) /* babble detected */
|
||||
#define QTD_STS_RCE (1 << 27) /* retry count exceeded */
|
||||
#define QTD_STS_LAST_PKT (1 << 26) /* set Last Packet Flag in WUSB header */
|
||||
#define QTD_STS_INACTIVE (1 << 25) /* queue set is marked inactive */
|
||||
#define QTD_STS_IALT_VALID (1 << 23) /* iAlt field is valid */
|
||||
#define QTD_STS_IALT(i) (QTD_STS_IALT_VALID | ((i) << 20)) /* iAlt field */
|
||||
#define QTD_STS_LEN(l) ((l) << 0) /* transfer length */
|
||||
#define QTD_STS_TO_LEN(s) ((s) & 0x000fffff)
|
||||
|
||||
#define QTD_OPT_IOC (1 << 1) /* page_list_ptr points to buffer directly */
|
||||
#define QTD_OPT_SMALL (1 << 0) /* interrupt on complete */
|
||||
|
||||
/**
|
||||
* struct whc_itd - Isochronous Queue Element Transfer Descriptors (iTD)
|
||||
*
|
||||
* This describes the data and other parameters for an isochronous
|
||||
* transfer.
|
||||
*
|
||||
* [WHCI] section 3.2.5
|
||||
*/
|
||||
struct whc_itd {
|
||||
__le16 presentation_time; /*< presentation time for OUT transfers */
|
||||
__u8 num_segments; /*< number of data segments in segment list */
|
||||
__u8 status; /*< command execution status */
|
||||
__le32 options; /*< misc transfer options */
|
||||
__le64 page_list_ptr; /*< physical pointer to data buffer page list */
|
||||
__le64 seg_list_ptr; /*< physical pointer to segment list */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define ITD_STS_ACTIVE (1 << 7) /* enable execution of transaction */
|
||||
#define ITD_STS_DBE (1 << 5) /* data buffer error */
|
||||
#define ITD_STS_BABBLE (1 << 4) /* babble detected */
|
||||
#define ITD_STS_INACTIVE (1 << 1) /* queue set is marked inactive */
|
||||
|
||||
#define ITD_OPT_IOC (1 << 1) /* interrupt on complete */
|
||||
#define ITD_OPT_SMALL (1 << 0) /* page_list_ptr points to buffer directly */
|
||||
|
||||
/**
|
||||
* Page list entry.
|
||||
*
|
||||
* A TD's page list must contain sufficient page list entries for the
|
||||
* total data length in the TD.
|
||||
*
|
||||
* [WHCI] section 3.2.4.3
|
||||
*/
|
||||
struct whc_page_list_entry {
|
||||
__le64 buf_ptr; /*< physical pointer to buffer */
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* struct whc_seg_list_entry - Segment list entry.
|
||||
*
|
||||
* Describes a portion of the data buffer described in the containing
|
||||
* qTD's page list.
|
||||
*
|
||||
* seg_ptr = qtd->page_list_ptr[qtd->seg_list_ptr[seg].idx].buf_ptr
|
||||
* + qtd->seg_list_ptr[seg].offset;
|
||||
*
|
||||
* Segments can't cross page boundries.
|
||||
*
|
||||
* [WHCI] section 3.2.5.5
|
||||
*/
|
||||
struct whc_seg_list_entry {
|
||||
__le16 len; /*< segment length */
|
||||
__u8 idx; /*< index into page list */
|
||||
__u8 status; /*< segment status */
|
||||
__le16 offset; /*< 12 bit offset into page */
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* struct whc_qhead - endpoint and status information for a qset.
|
||||
*
|
||||
* [WHCI] section 3.2.6
|
||||
*/
|
||||
struct whc_qhead {
|
||||
__le64 link; /*< next qset in list */
|
||||
__le32 info1;
|
||||
__le32 info2;
|
||||
__le32 info3;
|
||||
__le16 status;
|
||||
__le16 err_count; /*< transaction error count */
|
||||
__le32 cur_window;
|
||||
__le32 scratch[3]; /*< h/w scratch area */
|
||||
union {
|
||||
struct whc_qtd qtd;
|
||||
struct whc_itd itd;
|
||||
} overlay;
|
||||
} __attribute__((packed));
|
||||
|
||||
#define QH_LINK_PTR_MASK (~0x03Full)
|
||||
#define QH_LINK_PTR(ptr) ((ptr) & QH_LINK_PTR_MASK)
|
||||
#define QH_LINK_IQS (1 << 4) /* isochronous queue set */
|
||||
#define QH_LINK_NTDS(n) (((n) - 1) << 1) /* number of TDs in queue set */
|
||||
#define QH_LINK_T (1 << 0) /* last queue set in periodic schedule list */
|
||||
|
||||
#define QH_INFO1_EP(e) ((e) << 0) /* endpoint number */
|
||||
#define QH_INFO1_DIR_IN (1 << 4) /* IN transfer */
|
||||
#define QH_INFO1_DIR_OUT (0 << 4) /* OUT transfer */
|
||||
#define QH_INFO1_TR_TYPE_CTRL (0x0 << 5) /* control transfer */
|
||||
#define QH_INFO1_TR_TYPE_ISOC (0x1 << 5) /* isochronous transfer */
|
||||
#define QH_INFO1_TR_TYPE_BULK (0x2 << 5) /* bulk transfer */
|
||||
#define QH_INFO1_TR_TYPE_INT (0x3 << 5) /* interrupt */
|
||||
#define QH_INFO1_TR_TYPE_LP_INT (0x7 << 5) /* low power interrupt */
|
||||
#define QH_INFO1_DEV_INFO_IDX(i) ((i) << 8) /* index into device info buffer */
|
||||
#define QH_INFO1_SET_INACTIVE (1 << 15) /* set inactive after transfer */
|
||||
#define QH_INFO1_MAX_PKT_LEN(l) ((l) << 16) /* maximum packet length */
|
||||
|
||||
#define QH_INFO2_BURST(b) ((b) << 0) /* maximum burst length */
|
||||
#define QH_INFO2_DBP(p) ((p) << 5) /* data burst policy (see [WUSB] table 5-7) */
|
||||
#define QH_INFO2_MAX_COUNT(c) ((c) << 8) /* max isoc/int pkts per zone */
|
||||
#define QH_INFO2_RQS (1 << 15) /* reactivate queue set */
|
||||
#define QH_INFO2_MAX_RETRY(r) ((r) << 16) /* maximum transaction retries */
|
||||
#define QH_INFO2_MAX_SEQ(s) ((s) << 20) /* maximum sequence number */
|
||||
#define QH_INFO3_MAX_DELAY(d) ((d) << 0) /* maximum stream delay in 125 us units (isoc only) */
|
||||
#define QH_INFO3_INTERVAL(i) ((i) << 16) /* segment interval in 125 us units (isoc only) */
|
||||
|
||||
#define QH_INFO3_TX_RATE_53_3 (0 << 24)
|
||||
#define QH_INFO3_TX_RATE_80 (1 << 24)
|
||||
#define QH_INFO3_TX_RATE_106_7 (2 << 24)
|
||||
#define QH_INFO3_TX_RATE_160 (3 << 24)
|
||||
#define QH_INFO3_TX_RATE_200 (4 << 24)
|
||||
#define QH_INFO3_TX_RATE_320 (5 << 24)
|
||||
#define QH_INFO3_TX_RATE_400 (6 << 24)
|
||||
#define QH_INFO3_TX_RATE_480 (7 << 24)
|
||||
#define QH_INFO3_TX_PWR(p) ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */
|
||||
|
||||
#define QH_STATUS_FLOW_CTRL (1 << 15)
|
||||
#define QH_STATUS_ICUR(i) ((i) << 5)
|
||||
#define QH_STATUS_TO_ICUR(s) (((s) >> 5) & 0x7)
|
||||
|
||||
/**
|
||||
* usb_pipe_to_qh_type - USB core pipe type to QH transfer type
|
||||
*
|
||||
* Returns the QH type field for a USB core pipe type.
|
||||
*/
|
||||
static inline unsigned usb_pipe_to_qh_type(unsigned pipe)
|
||||
{
|
||||
static const unsigned type[] = {
|
||||
[PIPE_ISOCHRONOUS] = QH_INFO1_TR_TYPE_ISOC,
|
||||
[PIPE_INTERRUPT] = QH_INFO1_TR_TYPE_INT,
|
||||
[PIPE_CONTROL] = QH_INFO1_TR_TYPE_CTRL,
|
||||
[PIPE_BULK] = QH_INFO1_TR_TYPE_BULK,
|
||||
};
|
||||
return type[usb_pipetype(pipe)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maxiumum number of TDs in a qset.
|
||||
*/
|
||||
#define WHCI_QSET_TD_MAX 8
|
||||
|
||||
/**
|
||||
* struct whc_qset - WUSB data transfers to a specific endpoint
|
||||
* @qh: the QHead of this qset
|
||||
* @qtd: up to 8 qTDs (for qsets for control, bulk and interrupt
|
||||
* transfers)
|
||||
* @itd: up to 8 iTDs (for qsets for isochronous transfers)
|
||||
* @qset_dma: DMA address for this qset
|
||||
* @whc: WHCI HC this qset is for
|
||||
* @ep: endpoint
|
||||
* @stds: list of sTDs queued to this qset
|
||||
* @ntds: number of qTDs queued (not necessarily the same as nTDs
|
||||
* field in the QH)
|
||||
* @td_start: index of the first qTD in the list
|
||||
* @td_end: index of next free qTD in the list (provided
|
||||
* ntds < WHCI_QSET_TD_MAX)
|
||||
*
|
||||
* Queue Sets (qsets) are added to the asynchronous schedule list
|
||||
* (ASL) or the periodic zone list (PZL).
|
||||
*
|
||||
* qsets may contain up to 8 TDs (either qTDs or iTDs as appropriate).
|
||||
* Each TD may refer to at most 1 MiB of data. If a single transfer
|
||||
* has > 8MiB of data, TDs can be reused as they are completed since
|
||||
* the TD list is used as a circular buffer. Similarly, several
|
||||
* (smaller) transfers may be queued in a qset.
|
||||
*
|
||||
* WHCI controllers may cache portions of the qsets in the ASL and
|
||||
* PZL, requiring the WHCD to inform the WHC that the lists have been
|
||||
* updated (fields changed or qsets inserted or removed). For safe
|
||||
* insertion and removal of qsets from the lists the schedule must be
|
||||
* stopped to avoid races in updating the QH link pointers.
|
||||
*
|
||||
* Since the HC is free to execute qsets in any order, all transfers
|
||||
* to an endpoint should use the same qset to ensure transfers are
|
||||
* executed in the order they're submitted.
|
||||
*
|
||||
* [WHCI] section 3.2.3
|
||||
*/
|
||||
struct whc_qset {
|
||||
struct whc_qhead qh;
|
||||
union {
|
||||
struct whc_qtd qtd[WHCI_QSET_TD_MAX];
|
||||
struct whc_itd itd[WHCI_QSET_TD_MAX];
|
||||
};
|
||||
|
||||
/* private data for WHCD */
|
||||
dma_addr_t qset_dma;
|
||||
struct whc *whc;
|
||||
struct usb_host_endpoint *ep;
|
||||
struct list_head stds;
|
||||
int ntds;
|
||||
int td_start;
|
||||
int td_end;
|
||||
struct list_head list_node;
|
||||
unsigned in_sw_list:1;
|
||||
unsigned in_hw_list:1;
|
||||
unsigned remove:1;
|
||||
struct urb *pause_after_urb;
|
||||
struct completion remove_complete;
|
||||
int max_burst;
|
||||
int max_seq;
|
||||
};
|
||||
|
||||
static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target)
|
||||
{
|
||||
if (target)
|
||||
*ptr = (*ptr & ~(QH_LINK_PTR_MASK | QH_LINK_T)) | QH_LINK_PTR(target);
|
||||
else
|
||||
*ptr = QH_LINK_T;
|
||||
}
|
||||
|
||||
/**
|
||||
* struct di_buf_entry - Device Information (DI) buffer entry.
|
||||
*
|
||||
* There's one of these per connected device.
|
||||
*/
|
||||
struct di_buf_entry {
|
||||
__le32 availability_info[8]; /*< MAS availability information, one MAS per bit */
|
||||
__le32 addr_sec_info; /*< addressing and security info */
|
||||
__le32 reserved[7];
|
||||
} __attribute__((packed));
|
||||
|
||||
#define WHC_DI_SECURE (1 << 31)
|
||||
#define WHC_DI_DISABLE (1 << 30)
|
||||
#define WHC_DI_KEY_IDX(k) ((k) << 8)
|
||||
#define WHC_DI_KEY_IDX_MASK 0x0000ff00
|
||||
#define WHC_DI_DEV_ADDR(a) ((a) << 0)
|
||||
#define WHC_DI_DEV_ADDR_MASK 0x000000ff
|
||||
|
||||
/**
|
||||
* struct dn_buf_entry - Device Notification (DN) buffer entry.
|
||||
*
|
||||
* [WHCI] section 3.2.8
|
||||
*/
|
||||
struct dn_buf_entry {
|
||||
__u8 msg_size; /*< number of octets of valid DN data */
|
||||
__u8 reserved1;
|
||||
__u8 src_addr; /*< source address */
|
||||
__u8 status; /*< buffer entry status */
|
||||
__le32 tkid; /*< TKID for source device, valid if secure bit is set */
|
||||
__u8 dn_data[56]; /*< up to 56 octets of DN data */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define WHC_DN_STATUS_VALID (1 << 7) /* buffer entry is valid */
|
||||
#define WHC_DN_STATUS_SECURE (1 << 6) /* notification received using secure frame */
|
||||
|
||||
#define WHC_N_DN_ENTRIES (4096 / sizeof(struct dn_buf_entry))
|
||||
|
||||
/* The Add MMC IE WUSB Generic Command may take up to 256 bytes of
|
||||
data. [WHCI] section 2.4.7. */
|
||||
#define WHC_GEN_CMD_DATA_LEN 256
|
||||
|
||||
/*
|
||||
* HC registers.
|
||||
*
|
||||
* [WHCI] section 2.4
|
||||
*/
|
||||
|
||||
#define WHCIVERSION 0x00
|
||||
|
||||
#define WHCSPARAMS 0x04
|
||||
# define WHCSPARAMS_TO_N_MMC_IES(p) (((p) >> 16) & 0xff)
|
||||
# define WHCSPARAMS_TO_N_KEYS(p) (((p) >> 8) & 0xff)
|
||||
# define WHCSPARAMS_TO_N_DEVICES(p) (((p) >> 0) & 0x7f)
|
||||
|
||||
#define WUSBCMD 0x08
|
||||
# define WUSBCMD_BCID(b) ((b) << 16)
|
||||
# define WUSBCMD_BCID_MASK (0xff << 16)
|
||||
# define WUSBCMD_ASYNC_QSET_RM (1 << 12)
|
||||
# define WUSBCMD_PERIODIC_QSET_RM (1 << 11)
|
||||
# define WUSBCMD_WUSBSI(s) ((s) << 8)
|
||||
# define WUSBCMD_WUSBSI_MASK (0x7 << 8)
|
||||
# define WUSBCMD_ASYNC_SYNCED_DB (1 << 7)
|
||||
# define WUSBCMD_PERIODIC_SYNCED_DB (1 << 6)
|
||||
# define WUSBCMD_ASYNC_UPDATED (1 << 5)
|
||||
# define WUSBCMD_PERIODIC_UPDATED (1 << 4)
|
||||
# define WUSBCMD_ASYNC_EN (1 << 3)
|
||||
# define WUSBCMD_PERIODIC_EN (1 << 2)
|
||||
# define WUSBCMD_WHCRESET (1 << 1)
|
||||
# define WUSBCMD_RUN (1 << 0)
|
||||
|
||||
#define WUSBSTS 0x0c
|
||||
# define WUSBSTS_ASYNC_SCHED (1 << 15)
|
||||
# define WUSBSTS_PERIODIC_SCHED (1 << 14)
|
||||
# define WUSBSTS_DNTS_SCHED (1 << 13)
|
||||
# define WUSBSTS_HCHALTED (1 << 12)
|
||||
# define WUSBSTS_GEN_CMD_DONE (1 << 9)
|
||||
# define WUSBSTS_CHAN_TIME_ROLLOVER (1 << 8)
|
||||
# define WUSBSTS_DNTS_OVERFLOW (1 << 7)
|
||||
# define WUSBSTS_BPST_ADJUSTMENT_CHANGED (1 << 6)
|
||||
# define WUSBSTS_HOST_ERR (1 << 5)
|
||||
# define WUSBSTS_ASYNC_SCHED_SYNCED (1 << 4)
|
||||
# define WUSBSTS_PERIODIC_SCHED_SYNCED (1 << 3)
|
||||
# define WUSBSTS_DNTS_INT (1 << 2)
|
||||
# define WUSBSTS_ERR_INT (1 << 1)
|
||||
# define WUSBSTS_INT (1 << 0)
|
||||
# define WUSBSTS_INT_MASK 0x3ff
|
||||
|
||||
#define WUSBINTR 0x10
|
||||
# define WUSBINTR_GEN_CMD_DONE (1 << 9)
|
||||
# define WUSBINTR_CHAN_TIME_ROLLOVER (1 << 8)
|
||||
# define WUSBINTR_DNTS_OVERFLOW (1 << 7)
|
||||
# define WUSBINTR_BPST_ADJUSTMENT_CHANGED (1 << 6)
|
||||
# define WUSBINTR_HOST_ERR (1 << 5)
|
||||
# define WUSBINTR_ASYNC_SCHED_SYNCED (1 << 4)
|
||||
# define WUSBINTR_PERIODIC_SCHED_SYNCED (1 << 3)
|
||||
# define WUSBINTR_DNTS_INT (1 << 2)
|
||||
# define WUSBINTR_ERR_INT (1 << 1)
|
||||
# define WUSBINTR_INT (1 << 0)
|
||||
# define WUSBINTR_ALL 0x3ff
|
||||
|
||||
#define WUSBGENCMDSTS 0x14
|
||||
# define WUSBGENCMDSTS_ACTIVE (1 << 31)
|
||||
# define WUSBGENCMDSTS_ERROR (1 << 24)
|
||||
# define WUSBGENCMDSTS_IOC (1 << 23)
|
||||
# define WUSBGENCMDSTS_MMCIE_ADD 0x01
|
||||
# define WUSBGENCMDSTS_MMCIE_RM 0x02
|
||||
# define WUSBGENCMDSTS_SET_MAS 0x03
|
||||
# define WUSBGENCMDSTS_CHAN_STOP 0x04
|
||||
# define WUSBGENCMDSTS_RWP_EN 0x05
|
||||
|
||||
#define WUSBGENCMDPARAMS 0x18
|
||||
#define WUSBGENADDR 0x20
|
||||
#define WUSBASYNCLISTADDR 0x28
|
||||
#define WUSBDNTSBUFADDR 0x30
|
||||
#define WUSBDEVICEINFOADDR 0x38
|
||||
|
||||
#define WUSBSETSECKEYCMD 0x40
|
||||
# define WUSBSETSECKEYCMD_SET (1 << 31)
|
||||
# define WUSBSETSECKEYCMD_ERASE (1 << 30)
|
||||
# define WUSBSETSECKEYCMD_GTK (1 << 8)
|
||||
# define WUSBSETSECKEYCMD_IDX(i) ((i) << 0)
|
||||
|
||||
#define WUSBTKID 0x44
|
||||
#define WUSBSECKEY 0x48
|
||||
#define WUSBPERIODICLISTBASE 0x58
|
||||
#define WUSBMASINDEX 0x60
|
||||
|
||||
#define WUSBDNTSCTRL 0x64
|
||||
# define WUSBDNTSCTRL_ACTIVE (1 << 31)
|
||||
# define WUSBDNTSCTRL_INTERVAL(i) ((i) << 8)
|
||||
# define WUSBDNTSCTRL_SLOTS(s) ((s) << 0)
|
||||
|
||||
#define WUSBTIME 0x68
|
||||
#define WUSBBPST 0x6c
|
||||
#define WUSBDIBUPDATED 0x70
|
||||
|
||||
#endif /* #ifndef _WHCI_WHCI_HC_H */
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Wireless Host Controller (WHC) WUSB operations.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
#define D_LOCAL 1
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
#include "../../wusbcore/wusbhc.h"
|
||||
|
||||
#include "whcd.h"
|
||||
|
||||
#if D_LOCAL >= 1
|
||||
static void dump_di(struct whc *whc, int idx)
|
||||
{
|
||||
struct di_buf_entry *di = &whc->di_buf[idx];
|
||||
struct device *dev = &whc->umc->dev;
|
||||
char buf[128];
|
||||
|
||||
bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS);
|
||||
|
||||
d_printf(1, dev, "DI[%d]\n", idx);
|
||||
d_printf(1, dev, " availability: %s\n", buf);
|
||||
d_printf(1, dev, " %c%c key idx: %d dev addr: %d\n",
|
||||
(di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ',
|
||||
(di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ',
|
||||
(di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8,
|
||||
(di->addr_sec_info & WHC_DI_DEV_ADDR_MASK));
|
||||
}
|
||||
#else
|
||||
static inline void dump_di(struct whc *whc, int idx)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static int whc_update_di(struct whc *whc, int idx)
|
||||
{
|
||||
int offset = idx / 32;
|
||||
u32 bit = 1 << (idx % 32);
|
||||
|
||||
dump_di(whc, idx);
|
||||
|
||||
le_writel(bit, whc->base + WUSBDIBUPDATED + offset);
|
||||
|
||||
return whci_wait_for(&whc->umc->dev,
|
||||
whc->base + WUSBDIBUPDATED + offset, bit, 0,
|
||||
100, "DI update");
|
||||
}
|
||||
|
||||
/*
|
||||
* WHCI starts and stops MMCs based on there being a valid GTK so
|
||||
* these need only start/stop the asynchronous and periodic schedules.
|
||||
*/
|
||||
|
||||
int whc_wusbhc_start(struct wusbhc *wusbhc)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
|
||||
asl_start(whc);
|
||||
pzl_start(whc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void whc_wusbhc_stop(struct wusbhc *wusbhc)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
|
||||
pzl_stop(whc);
|
||||
asl_stop(whc);
|
||||
}
|
||||
|
||||
int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
|
||||
u8 handle, struct wuie_hdr *wuie)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
u32 params;
|
||||
|
||||
params = (interval << 24)
|
||||
| (repeat_cnt << 16)
|
||||
| (wuie->bLength << 8)
|
||||
| handle;
|
||||
|
||||
return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength);
|
||||
}
|
||||
|
||||
int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
u32 params;
|
||||
|
||||
params = handle;
|
||||
|
||||
return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0);
|
||||
}
|
||||
|
||||
int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
|
||||
if (stream_index >= 0)
|
||||
whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index));
|
||||
|
||||
return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm));
|
||||
}
|
||||
|
||||
int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
int idx = wusb_dev->port_idx;
|
||||
struct di_buf_entry *di = &whc->di_buf[idx];
|
||||
int ret;
|
||||
|
||||
mutex_lock(&whc->mutex);
|
||||
|
||||
uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability);
|
||||
di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK);
|
||||
di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr);
|
||||
|
||||
ret = whc_update_di(whc, idx);
|
||||
|
||||
mutex_unlock(&whc->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the number of Device Notification Time Slots (DNTS) and enable
|
||||
* device notifications.
|
||||
*/
|
||||
int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
u32 dntsctrl;
|
||||
|
||||
dntsctrl = WUSBDNTSCTRL_ACTIVE
|
||||
| WUSBDNTSCTRL_INTERVAL(interval)
|
||||
| WUSBDNTSCTRL_SLOTS(slots);
|
||||
|
||||
le_writel(dntsctrl, whc->base + WUSBDNTSCTRL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid,
|
||||
const void *key, size_t key_size, bool is_gtk)
|
||||
{
|
||||
uint32_t setkeycmd;
|
||||
uint32_t seckey[4];
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
memcpy(seckey, key, key_size);
|
||||
setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index);
|
||||
if (is_gtk)
|
||||
setkeycmd |= WUSBSETSECKEYCMD_GTK;
|
||||
|
||||
le_writel(tkid, whc->base + WUSBTKID);
|
||||
for (i = 0; i < 4; i++)
|
||||
le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i);
|
||||
le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD);
|
||||
|
||||
ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD,
|
||||
WUSBSETSECKEYCMD_SET, 0, 100, "set key");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* whc_set_ptk - set the PTK to use for a device.
|
||||
*
|
||||
* The index into the key table for this PTK is the same as the
|
||||
* device's port index.
|
||||
*/
|
||||
int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
|
||||
const void *ptk, size_t key_size)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
struct di_buf_entry *di = &whc->di_buf[port_idx];
|
||||
int ret;
|
||||
|
||||
mutex_lock(&whc->mutex);
|
||||
|
||||
if (ptk) {
|
||||
ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK;
|
||||
di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx);
|
||||
} else
|
||||
di->addr_sec_info &= ~WHC_DI_SECURE;
|
||||
|
||||
ret = whc_update_di(whc, port_idx);
|
||||
out:
|
||||
mutex_unlock(&whc->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* whc_set_gtk - set the GTK for subsequent broadcast packets
|
||||
*
|
||||
* The GTK is stored in the last entry in the key table (the previous
|
||||
* N_DEVICES entries are for the per-device PTKs).
|
||||
*/
|
||||
int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
|
||||
const void *gtk, size_t key_size)
|
||||
{
|
||||
struct whc *whc = wusbhc_to_whc(wusbhc);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&whc->mutex);
|
||||
|
||||
ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true);
|
||||
|
||||
mutex_unlock(&whc->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int whc_set_cluster_id(struct whc *whc, u8 bcid)
|
||||
{
|
||||
whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid));
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# Wireless USB Core configuration
|
||||
#
|
||||
config USB_WUSB
|
||||
tristate "Enable Wireless USB extensions (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL
|
||||
depends on USB
|
||||
select UWB
|
||||
select CRYPTO
|
||||
select CRYPTO_BLKCIPHER
|
||||
select CRYPTO_CBC
|
||||
select CRYPTO_MANAGER
|
||||
select CRYPTO_AES
|
||||
help
|
||||
Enable the host-side support for Wireless USB.
|
||||
|
||||
To compile this support select Y (built in). It is safe to
|
||||
select even if you don't have the hardware.
|
||||
|
||||
config USB_WUSB_CBAF
|
||||
tristate "Support WUSB Cable Based Association (CBA)"
|
||||
depends on USB
|
||||
help
|
||||
Some WUSB devices support Cable Based Association. It's used to
|
||||
enable the secure communication between the host and the
|
||||
device.
|
||||
|
||||
Enable this option if your WUSB device must to be connected
|
||||
via wired USB before establishing a wireless link.
|
||||
|
||||
It is safe to select even if you don't have a compatible
|
||||
hardware.
|
||||
|
||||
config USB_WUSB_CBAF_DEBUG
|
||||
bool "Enable CBA debug messages"
|
||||
depends on USB_WUSB_CBAF
|
||||
help
|
||||
Say Y here if you want the CBA to produce a bunch of debug messages
|
||||
to the system log. Select this if you are having a problem with
|
||||
CBA support and want to see more of what is going on.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
obj-$(CONFIG_USB_WUSB) += wusbcore.o
|
||||
obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o
|
||||
obj-$(CONFIG_USB_WUSB_CBAF) += wusb-cbaf.o
|
||||
|
||||
|
||||
wusbcore-objs := \
|
||||
crypto.o \
|
||||
devconnect.o \
|
||||
dev-sysfs.o \
|
||||
mmc.o \
|
||||
pal.o \
|
||||
rh.o \
|
||||
reservation.o \
|
||||
security.o \
|
||||
wusbhc.o
|
||||
|
||||
wusb-cbaf-objs := cbaf.o
|
||||
|
||||
wusb-wa-objs := wa-hc.o \
|
||||
wa-nep.o \
|
||||
wa-rpipe.o \
|
||||
wa-xfer.o
|
||||
|
||||
ifeq ($(CONFIG_USB_WUSB_CBAF_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
|
@ -0,0 +1,673 @@
|
|||
/*
|
||||
* Wireless USB - Cable Based Association
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2006 Intel Corporation
|
||||
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
|
||||
* Copyright (C) 2008 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*
|
||||
* WUSB devices have to be paired (associated in WUSB lingo) so
|
||||
* that they can connect to the system.
|
||||
*
|
||||
* One way of pairing is using CBA-Cable Based Association. First
|
||||
* time you plug the device with a cable, association is done between
|
||||
* host and device and subsequent times, you can connect wirelessly
|
||||
* without having to associate again. That's the idea.
|
||||
*
|
||||
* This driver does nothing Earth shattering. It just provides an
|
||||
* interface to chat with the wire-connected device so we can get a
|
||||
* CDID (device ID) that might have been previously associated to a
|
||||
* CHID (host ID) and to set up a new <CHID,CDID,CK> triplet
|
||||
* (connection context), with the CK being the secret, or connection
|
||||
* key. This is the pairing data.
|
||||
*
|
||||
* When a device with the CBA capability connects, the probe routine
|
||||
* just creates a bunch of sysfs files that a user space enumeration
|
||||
* manager uses to allow it to connect wirelessly to the system or not.
|
||||
*
|
||||
* The process goes like this:
|
||||
*
|
||||
* 1. Device plugs, cbaf is loaded, notifications happen.
|
||||
*
|
||||
* 2. The connection manager (CM) sees a device with CBAF capability
|
||||
* (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE).
|
||||
*
|
||||
* 3. The CM writes the host name, supported band groups, and the CHID
|
||||
* (host ID) into the wusb_host_name, wusb_host_band_groups and
|
||||
* wusb_chid files. These get sent to the device and the CDID (if
|
||||
* any) for this host is requested.
|
||||
*
|
||||
* 4. The CM can verify that the device's supported band groups
|
||||
* (wusb_device_band_groups) are compatible with the host.
|
||||
*
|
||||
* 5. The CM reads the wusb_cdid file.
|
||||
*
|
||||
* 6. The CM looks up its database
|
||||
*
|
||||
* 6.1 If it has a matching CHID,CDID entry, the device has been
|
||||
* authorized before (paired) and nothing further needs to be
|
||||
* done.
|
||||
*
|
||||
* 6.2 If the CDID is zero (or the CM doesn't find a matching CDID in
|
||||
* its database), the device is assumed to be not known. The CM
|
||||
* may associate the host with device by: writing a randomly
|
||||
* generated CDID to wusb_cdid and then a random CK to wusb_ck
|
||||
* (this uploads the new CC to the device).
|
||||
*
|
||||
* CMD may choose to prompt the user before associating with a new
|
||||
* device.
|
||||
*
|
||||
* 7. Device is unplugged.
|
||||
*
|
||||
* When the device tries to connect wirelessly, it will present its
|
||||
* CDID to the WUSB host controller. The CM will query the
|
||||
* database. If the CHID/CDID pair found, it will (with a 4-way
|
||||
* handshake) challenge the device to demonstrate it has the CK secret
|
||||
* key (from our database) without actually exchanging it. Once
|
||||
* satisfied, crypto keys are derived from the CK, the device is
|
||||
* connected and all communication is encrypted.
|
||||
*
|
||||
* References:
|
||||
* [WUSB-AM] Association Models Supplement to the Certified Wireless
|
||||
* Universal Serial Bus Specification, version 1.0.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/usb/wusb.h>
|
||||
#include <linux/usb/association.h>
|
||||
|
||||
#define CBA_NAME_LEN 0x40 /* [WUSB-AM] table 4-7 */
|
||||
|
||||
/* An instance of a Cable-Based-Association-Framework device */
|
||||
struct cbaf {
|
||||
struct usb_device *usb_dev;
|
||||
struct usb_interface *usb_iface;
|
||||
void *buffer;
|
||||
size_t buffer_size;
|
||||
|
||||
struct wusb_ckhdid chid;
|
||||
char host_name[CBA_NAME_LEN];
|
||||
u16 host_band_groups;
|
||||
|
||||
struct wusb_ckhdid cdid;
|
||||
char device_name[CBA_NAME_LEN];
|
||||
u16 device_band_groups;
|
||||
|
||||
struct wusb_ckhdid ck;
|
||||
};
|
||||
|
||||
/*
|
||||
* Verify that a CBAF USB-interface has what we need
|
||||
*
|
||||
* According to [WUSB-AM], CBA devices should provide at least two
|
||||
* interfaces:
|
||||
* - RETRIEVE_HOST_INFO
|
||||
* - ASSOCIATE
|
||||
*
|
||||
* If the device doesn't provide these interfaces, we do not know how
|
||||
* to deal with it.
|
||||
*/
|
||||
static int cbaf_check(struct cbaf *cbaf)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &cbaf->usb_iface->dev;
|
||||
struct wusb_cbaf_assoc_info *assoc_info;
|
||||
struct wusb_cbaf_assoc_request *assoc_request;
|
||||
size_t assoc_size;
|
||||
void *itr, *top;
|
||||
int ar_rhi = 0, ar_assoc = 0;
|
||||
|
||||
result = usb_control_msg(
|
||||
cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0),
|
||||
CBAF_REQ_GET_ASSOCIATION_INFORMATION,
|
||||
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
cbaf->buffer, cbaf->buffer_size, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot get available association types: %d\n",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
assoc_info = cbaf->buffer;
|
||||
if (result < sizeof(*assoc_info)) {
|
||||
dev_err(dev, "Not enough data to decode association info "
|
||||
"header (%zu vs %zu bytes required)\n",
|
||||
(size_t)result, sizeof(*assoc_info));
|
||||
return result;
|
||||
}
|
||||
|
||||
assoc_size = le16_to_cpu(assoc_info->Length);
|
||||
if (result < assoc_size) {
|
||||
dev_err(dev, "Not enough data to decode association info "
|
||||
"(%zu vs %zu bytes required)\n",
|
||||
(size_t)assoc_size, sizeof(*assoc_info));
|
||||
return result;
|
||||
}
|
||||
/*
|
||||
* From now on, we just verify, but won't error out unless we
|
||||
* don't find the AR_TYPE_WUSB_{RETRIEVE_HOST_INFO,ASSOCIATE}
|
||||
* types.
|
||||
*/
|
||||
itr = cbaf->buffer + sizeof(*assoc_info);
|
||||
top = cbaf->buffer + assoc_size;
|
||||
dev_dbg(dev, "Found %u association requests (%zu bytes)\n",
|
||||
assoc_info->NumAssociationRequests, assoc_size);
|
||||
|
||||
while (itr < top) {
|
||||
u16 ar_type, ar_subtype;
|
||||
u32 ar_size;
|
||||
const char *ar_name;
|
||||
|
||||
assoc_request = itr;
|
||||
|
||||
if (top - itr < sizeof(*assoc_request)) {
|
||||
dev_err(dev, "Not enough data to decode associaton "
|
||||
"request (%zu vs %zu bytes needed)\n",
|
||||
top - itr, sizeof(*assoc_request));
|
||||
break;
|
||||
}
|
||||
|
||||
ar_type = le16_to_cpu(assoc_request->AssociationTypeId);
|
||||
ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId);
|
||||
ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize);
|
||||
ar_name = "unknown";
|
||||
|
||||
switch (ar_type) {
|
||||
case AR_TYPE_WUSB:
|
||||
/* Verify we have what is mandated by [WUSB-AM]. */
|
||||
switch (ar_subtype) {
|
||||
case AR_TYPE_WUSB_RETRIEVE_HOST_INFO:
|
||||
ar_name = "RETRIEVE_HOST_INFO";
|
||||
ar_rhi = 1;
|
||||
break;
|
||||
case AR_TYPE_WUSB_ASSOCIATE:
|
||||
/* send assoc data */
|
||||
ar_name = "ASSOCIATE";
|
||||
ar_assoc = 1;
|
||||
break;
|
||||
};
|
||||
break;
|
||||
};
|
||||
|
||||
dev_dbg(dev, "Association request #%02u: 0x%04x/%04x "
|
||||
"(%zu bytes): %s\n",
|
||||
assoc_request->AssociationDataIndex, ar_type,
|
||||
ar_subtype, (size_t)ar_size, ar_name);
|
||||
|
||||
itr += sizeof(*assoc_request);
|
||||
}
|
||||
|
||||
if (!ar_rhi) {
|
||||
dev_err(dev, "Missing RETRIEVE_HOST_INFO association "
|
||||
"request\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!ar_assoc) {
|
||||
dev_err(dev, "Missing ASSOCIATE association request\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct wusb_cbaf_host_info cbaf_host_info_defaults = {
|
||||
.AssociationTypeId_hdr = WUSB_AR_AssociationTypeId,
|
||||
.AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB),
|
||||
.AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
|
||||
.AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_RETRIEVE_HOST_INFO),
|
||||
.CHID_hdr = WUSB_AR_CHID,
|
||||
.LangID_hdr = WUSB_AR_LangID,
|
||||
.HostFriendlyName_hdr = WUSB_AR_HostFriendlyName,
|
||||
};
|
||||
|
||||
/* Send WUSB host information (CHID and name) to a CBAF device */
|
||||
static int cbaf_send_host_info(struct cbaf *cbaf)
|
||||
{
|
||||
struct wusb_cbaf_host_info *hi;
|
||||
size_t name_len;
|
||||
size_t hi_size;
|
||||
|
||||
hi = cbaf->buffer;
|
||||
memset(hi, 0, sizeof(*hi));
|
||||
*hi = cbaf_host_info_defaults;
|
||||
hi->CHID = cbaf->chid;
|
||||
hi->LangID = 0; /* FIXME: I guess... */
|
||||
strlcpy(hi->HostFriendlyName, cbaf->host_name, CBA_NAME_LEN);
|
||||
name_len = strlen(cbaf->host_name);
|
||||
hi->HostFriendlyName_hdr.len = cpu_to_le16(name_len);
|
||||
hi_size = sizeof(*hi) + name_len;
|
||||
|
||||
return usb_control_msg(cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0),
|
||||
CBAF_REQ_SET_ASSOCIATION_RESPONSE,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0x0101,
|
||||
cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
hi, hi_size, 1000 /* FIXME: arbitrary */);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get device's information (CDID) associated to CHID
|
||||
*
|
||||
* The device will return it's information (CDID, name, bandgroups)
|
||||
* associated to the CHID we have set before, or 0 CDID and default
|
||||
* name and bandgroup if no CHID set or unknown.
|
||||
*/
|
||||
static int cbaf_cdid_get(struct cbaf *cbaf)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &cbaf->usb_iface->dev;
|
||||
struct wusb_cbaf_device_info *di;
|
||||
size_t needed;
|
||||
|
||||
di = cbaf->buffer;
|
||||
result = usb_control_msg(
|
||||
cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0),
|
||||
CBAF_REQ_GET_ASSOCIATION_REQUEST,
|
||||
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot request device information: %d\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length);
|
||||
if (result < needed) {
|
||||
dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs "
|
||||
"%zu bytes needed)\n", (size_t)result, needed);
|
||||
return result;
|
||||
}
|
||||
|
||||
strlcpy(cbaf->device_name, di->DeviceFriendlyName, CBA_NAME_LEN);
|
||||
cbaf->cdid = di->CDID;
|
||||
cbaf->device_band_groups = le16_to_cpu(di->BandGroups);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t cbaf_wusb_chid_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
char pr_chid[WUSB_CKHDID_STRSIZE];
|
||||
|
||||
ckhdid_printf(pr_chid, sizeof(pr_chid), &cbaf->chid);
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", pr_chid);
|
||||
}
|
||||
|
||||
static ssize_t cbaf_wusb_chid_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
|
||||
result = sscanf(buf,
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx",
|
||||
&cbaf->chid.data[0] , &cbaf->chid.data[1],
|
||||
&cbaf->chid.data[2] , &cbaf->chid.data[3],
|
||||
&cbaf->chid.data[4] , &cbaf->chid.data[5],
|
||||
&cbaf->chid.data[6] , &cbaf->chid.data[7],
|
||||
&cbaf->chid.data[8] , &cbaf->chid.data[9],
|
||||
&cbaf->chid.data[10], &cbaf->chid.data[11],
|
||||
&cbaf->chid.data[12], &cbaf->chid.data[13],
|
||||
&cbaf->chid.data[14], &cbaf->chid.data[15]);
|
||||
|
||||
if (result != 16)
|
||||
return -EINVAL;
|
||||
|
||||
result = cbaf_send_host_info(cbaf);
|
||||
if (result < 0)
|
||||
return result;
|
||||
result = cbaf_cdid_get(cbaf);
|
||||
if (result < 0)
|
||||
return -result;
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_chid, 0600, cbaf_wusb_chid_show, cbaf_wusb_chid_store);
|
||||
|
||||
static ssize_t cbaf_wusb_host_name_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->host_name);
|
||||
}
|
||||
|
||||
static ssize_t cbaf_wusb_host_name_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
|
||||
result = sscanf(buf, "%63s", cbaf->host_name);
|
||||
if (result != 1)
|
||||
return -EINVAL;
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_host_name, 0600, cbaf_wusb_host_name_show,
|
||||
cbaf_wusb_host_name_store);
|
||||
|
||||
static ssize_t cbaf_wusb_host_band_groups_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->host_band_groups);
|
||||
}
|
||||
|
||||
static ssize_t cbaf_wusb_host_band_groups_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
u16 band_groups = 0;
|
||||
|
||||
result = sscanf(buf, "%04hx", &band_groups);
|
||||
if (result != 1)
|
||||
return -EINVAL;
|
||||
|
||||
cbaf->host_band_groups = band_groups;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(wusb_host_band_groups, 0600,
|
||||
cbaf_wusb_host_band_groups_show,
|
||||
cbaf_wusb_host_band_groups_store);
|
||||
|
||||
static const struct wusb_cbaf_device_info cbaf_device_info_defaults = {
|
||||
.Length_hdr = WUSB_AR_Length,
|
||||
.CDID_hdr = WUSB_AR_CDID,
|
||||
.BandGroups_hdr = WUSB_AR_BandGroups,
|
||||
.LangID_hdr = WUSB_AR_LangID,
|
||||
.DeviceFriendlyName_hdr = WUSB_AR_DeviceFriendlyName,
|
||||
};
|
||||
|
||||
static ssize_t cbaf_wusb_cdid_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
char pr_cdid[WUSB_CKHDID_STRSIZE];
|
||||
|
||||
ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid);
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", pr_cdid);
|
||||
}
|
||||
|
||||
static ssize_t cbaf_wusb_cdid_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
struct wusb_ckhdid cdid;
|
||||
|
||||
result = sscanf(buf,
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx",
|
||||
&cdid.data[0] , &cdid.data[1],
|
||||
&cdid.data[2] , &cdid.data[3],
|
||||
&cdid.data[4] , &cdid.data[5],
|
||||
&cdid.data[6] , &cdid.data[7],
|
||||
&cdid.data[8] , &cdid.data[9],
|
||||
&cdid.data[10], &cdid.data[11],
|
||||
&cdid.data[12], &cdid.data[13],
|
||||
&cdid.data[14], &cdid.data[15]);
|
||||
if (result != 16)
|
||||
return -EINVAL;
|
||||
|
||||
cbaf->cdid = cdid;
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_show, cbaf_wusb_cdid_store);
|
||||
|
||||
static ssize_t cbaf_wusb_device_band_groups_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->device_band_groups);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(wusb_device_band_groups, 0600,
|
||||
cbaf_wusb_device_band_groups_show,
|
||||
NULL);
|
||||
|
||||
static ssize_t cbaf_wusb_device_name_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->device_name);
|
||||
}
|
||||
static DEVICE_ATTR(wusb_device_name, 0600, cbaf_wusb_device_name_show, NULL);
|
||||
|
||||
static const struct wusb_cbaf_cc_data cbaf_cc_data_defaults = {
|
||||
.AssociationTypeId_hdr = WUSB_AR_AssociationTypeId,
|
||||
.AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB),
|
||||
.AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
|
||||
.AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_ASSOCIATE),
|
||||
.Length_hdr = WUSB_AR_Length,
|
||||
.Length = cpu_to_le32(sizeof(struct wusb_cbaf_cc_data)),
|
||||
.ConnectionContext_hdr = WUSB_AR_ConnectionContext,
|
||||
.BandGroups_hdr = WUSB_AR_BandGroups,
|
||||
};
|
||||
|
||||
static const struct wusb_cbaf_cc_data_fail cbaf_cc_data_fail_defaults = {
|
||||
.AssociationTypeId_hdr = WUSB_AR_AssociationTypeId,
|
||||
.AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
|
||||
.Length_hdr = WUSB_AR_Length,
|
||||
.AssociationStatus_hdr = WUSB_AR_AssociationStatus,
|
||||
};
|
||||
|
||||
/*
|
||||
* Send a new CC to the device.
|
||||
*/
|
||||
static int cbaf_cc_upload(struct cbaf *cbaf)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &cbaf->usb_iface->dev;
|
||||
struct wusb_cbaf_cc_data *ccd;
|
||||
char pr_cdid[WUSB_CKHDID_STRSIZE];
|
||||
|
||||
ccd = cbaf->buffer;
|
||||
*ccd = cbaf_cc_data_defaults;
|
||||
ccd->CHID = cbaf->chid;
|
||||
ccd->CDID = cbaf->cdid;
|
||||
ccd->CK = cbaf->ck;
|
||||
ccd->BandGroups = cpu_to_le16(cbaf->host_band_groups);
|
||||
|
||||
dev_dbg(dev, "Trying to upload CC:\n");
|
||||
ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID);
|
||||
dev_dbg(dev, " CHID %s\n", pr_cdid);
|
||||
ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID);
|
||||
dev_dbg(dev, " CDID %s\n", pr_cdid);
|
||||
dev_dbg(dev, " Bandgroups 0x%04x\n", cbaf->host_band_groups);
|
||||
|
||||
result = usb_control_msg(
|
||||
cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0),
|
||||
CBAF_REQ_SET_ASSOCIATION_RESPONSE,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
ccd, sizeof(*ccd), 1000 /* FIXME: arbitrary */);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ssize_t cbaf_wusb_ck_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct usb_interface *iface = to_usb_interface(dev);
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
|
||||
result = sscanf(buf,
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx",
|
||||
&cbaf->ck.data[0] , &cbaf->ck.data[1],
|
||||
&cbaf->ck.data[2] , &cbaf->ck.data[3],
|
||||
&cbaf->ck.data[4] , &cbaf->ck.data[5],
|
||||
&cbaf->ck.data[6] , &cbaf->ck.data[7],
|
||||
&cbaf->ck.data[8] , &cbaf->ck.data[9],
|
||||
&cbaf->ck.data[10], &cbaf->ck.data[11],
|
||||
&cbaf->ck.data[12], &cbaf->ck.data[13],
|
||||
&cbaf->ck.data[14], &cbaf->ck.data[15]);
|
||||
if (result != 16)
|
||||
return -EINVAL;
|
||||
|
||||
result = cbaf_cc_upload(cbaf);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_ck, 0600, NULL, cbaf_wusb_ck_store);
|
||||
|
||||
static struct attribute *cbaf_dev_attrs[] = {
|
||||
&dev_attr_wusb_host_name.attr,
|
||||
&dev_attr_wusb_host_band_groups.attr,
|
||||
&dev_attr_wusb_chid.attr,
|
||||
&dev_attr_wusb_cdid.attr,
|
||||
&dev_attr_wusb_device_name.attr,
|
||||
&dev_attr_wusb_device_band_groups.attr,
|
||||
&dev_attr_wusb_ck.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group cbaf_dev_attr_group = {
|
||||
.name = NULL, /* we want them in the same directory */
|
||||
.attrs = cbaf_dev_attrs,
|
||||
};
|
||||
|
||||
static int cbaf_probe(struct usb_interface *iface,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
struct cbaf *cbaf;
|
||||
struct device *dev = &iface->dev;
|
||||
int result = -ENOMEM;
|
||||
|
||||
cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL);
|
||||
if (cbaf == NULL)
|
||||
goto error_kzalloc;
|
||||
cbaf->buffer = kmalloc(512, GFP_KERNEL);
|
||||
if (cbaf->buffer == NULL)
|
||||
goto error_kmalloc_buffer;
|
||||
|
||||
cbaf->buffer_size = 512;
|
||||
cbaf->usb_dev = usb_get_dev(interface_to_usbdev(iface));
|
||||
cbaf->usb_iface = usb_get_intf(iface);
|
||||
result = cbaf_check(cbaf);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "This device is not WUSB-CBAF compliant"
|
||||
"and is not supported yet.\n");
|
||||
goto error_check;
|
||||
}
|
||||
|
||||
result = sysfs_create_group(&dev->kobj, &cbaf_dev_attr_group);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Can't register sysfs attr group: %d\n", result);
|
||||
goto error_create_group;
|
||||
}
|
||||
usb_set_intfdata(iface, cbaf);
|
||||
return 0;
|
||||
|
||||
error_create_group:
|
||||
error_check:
|
||||
kfree(cbaf->buffer);
|
||||
error_kmalloc_buffer:
|
||||
kfree(cbaf);
|
||||
error_kzalloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
static void cbaf_disconnect(struct usb_interface *iface)
|
||||
{
|
||||
struct cbaf *cbaf = usb_get_intfdata(iface);
|
||||
struct device *dev = &iface->dev;
|
||||
sysfs_remove_group(&dev->kobj, &cbaf_dev_attr_group);
|
||||
usb_set_intfdata(iface, NULL);
|
||||
usb_put_intf(iface);
|
||||
kfree(cbaf->buffer);
|
||||
/* paranoia: clean up crypto keys */
|
||||
memset(cbaf, 0, sizeof(*cbaf));
|
||||
kfree(cbaf);
|
||||
}
|
||||
|
||||
static struct usb_device_id cbaf_id_table[] = {
|
||||
{ USB_INTERFACE_INFO(0xef, 0x03, 0x01), },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, cbaf_id_table);
|
||||
|
||||
static struct usb_driver cbaf_driver = {
|
||||
.name = "wusb-cbaf",
|
||||
.id_table = cbaf_id_table,
|
||||
.probe = cbaf_probe,
|
||||
.disconnect = cbaf_disconnect,
|
||||
};
|
||||
|
||||
static int __init cbaf_driver_init(void)
|
||||
{
|
||||
return usb_register(&cbaf_driver);
|
||||
}
|
||||
module_init(cbaf_driver_init);
|
||||
|
||||
static void __exit cbaf_driver_exit(void)
|
||||
{
|
||||
usb_deregister(&cbaf_driver);
|
||||
}
|
||||
module_exit(cbaf_driver_exit);
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("Wireless USB Cable Based Association");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,538 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* AES-128 CCM Encryption
|
||||
*
|
||||
* Copyright (C) 2007 Intel Corporation
|
||||
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*
|
||||
* We don't do any encryption here; we use the Linux Kernel's AES-128
|
||||
* crypto modules to construct keys and payload blocks in a way
|
||||
* defined by WUSB1.0[6]. Check the erratas, as typos are are patched
|
||||
* there.
|
||||
*
|
||||
* Thanks a zillion to John Keys for his help and clarifications over
|
||||
* the designed-by-a-committee text.
|
||||
*
|
||||
* So the idea is that there is this basic Pseudo-Random-Function
|
||||
* defined in WUSB1.0[6.5] which is the core of everything. It works
|
||||
* by tweaking some blocks, AES crypting them and then xoring
|
||||
* something else with them (this seems to be called CBC(AES) -- can
|
||||
* you tell I know jack about crypto?). So we just funnel it into the
|
||||
* Linux Crypto API.
|
||||
*
|
||||
* We leave a crypto test module so we can verify that vectors match,
|
||||
* every now and then.
|
||||
*
|
||||
* Block size: 16 bytes -- AES seems to do things in 'block sizes'. I
|
||||
* am learning a lot...
|
||||
*
|
||||
* Conveniently, some data structures that need to be
|
||||
* funneled through AES are...16 bytes in size!
|
||||
*/
|
||||
|
||||
#include <linux/crypto.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/usb/wusb.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
|
||||
/*
|
||||
* Block of data, as understood by AES-CCM
|
||||
*
|
||||
* The code assumes this structure is nothing but a 16 byte array
|
||||
* (packed in a struct to avoid common mess ups that I usually do with
|
||||
* arrays and enforcing type checking).
|
||||
*/
|
||||
struct aes_ccm_block {
|
||||
u8 data[16];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* Counter-mode Blocks (WUSB1.0[6.4])
|
||||
*
|
||||
* According to CCM (or so it seems), for the purpose of calculating
|
||||
* the MIC, the message is broken in N counter-mode blocks, B0, B1,
|
||||
* ... BN.
|
||||
*
|
||||
* B0 contains flags, the CCM nonce and l(m).
|
||||
*
|
||||
* B1 contains l(a), the MAC header, the encryption offset and padding.
|
||||
*
|
||||
* If EO is nonzero, additional blocks are built from payload bytes
|
||||
* until EO is exahusted (FIXME: padding to 16 bytes, I guess). The
|
||||
* padding is not xmitted.
|
||||
*/
|
||||
|
||||
/* WUSB1.0[T6.4] */
|
||||
struct aes_ccm_b0 {
|
||||
u8 flags; /* 0x59, per CCM spec */
|
||||
struct aes_ccm_nonce ccm_nonce;
|
||||
__be16 lm;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* WUSB1.0[T6.5] */
|
||||
struct aes_ccm_b1 {
|
||||
__be16 la;
|
||||
u8 mac_header[10];
|
||||
__le16 eo;
|
||||
u8 security_reserved; /* This is always zero */
|
||||
u8 padding; /* 0 */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* Encryption Blocks (WUSB1.0[6.4.4])
|
||||
*
|
||||
* CCM uses Ax blocks to generate a keystream with which the MIC and
|
||||
* the message's payload are encoded. A0 always encrypts/decrypts the
|
||||
* MIC. Ax (x>0) are used for the sucesive payload blocks.
|
||||
*
|
||||
* The x is the counter, and is increased for each block.
|
||||
*/
|
||||
struct aes_ccm_a {
|
||||
u8 flags; /* 0x01, per CCM spec */
|
||||
struct aes_ccm_nonce ccm_nonce;
|
||||
__be16 counter; /* Value of x */
|
||||
} __attribute__((packed));
|
||||
|
||||
static void bytewise_xor(void *_bo, const void *_bi1, const void *_bi2,
|
||||
size_t size)
|
||||
{
|
||||
u8 *bo = _bo;
|
||||
const u8 *bi1 = _bi1, *bi2 = _bi2;
|
||||
size_t itr;
|
||||
for (itr = 0; itr < size; itr++)
|
||||
bo[itr] = bi1[itr] ^ bi2[itr];
|
||||
}
|
||||
|
||||
/*
|
||||
* CC-MAC function WUSB1.0[6.5]
|
||||
*
|
||||
* Take a data string and produce the encrypted CBC Counter-mode MIC
|
||||
*
|
||||
* Note the names for most function arguments are made to (more or
|
||||
* less) match those used in the pseudo-function definition given in
|
||||
* WUSB1.0[6.5].
|
||||
*
|
||||
* @tfm_cbc: CBC(AES) blkcipher handle (initialized)
|
||||
*
|
||||
* @tfm_aes: AES cipher handle (initialized)
|
||||
*
|
||||
* @mic: buffer for placing the computed MIC (Message Integrity
|
||||
* Code). This is exactly 8 bytes, and we expect the buffer to
|
||||
* be at least eight bytes in length.
|
||||
*
|
||||
* @key: 128 bit symmetric key
|
||||
*
|
||||
* @n: CCM nonce
|
||||
*
|
||||
* @a: ASCII string, 14 bytes long (I guess zero padded if needed;
|
||||
* we use exactly 14 bytes).
|
||||
*
|
||||
* @b: data stream to be processed; cannot be a global or const local
|
||||
* (will confuse the scatterlists)
|
||||
*
|
||||
* @blen: size of b...
|
||||
*
|
||||
* Still not very clear how this is done, but looks like this: we
|
||||
* create block B0 (as WUSB1.0[6.5] says), then we AES-crypt it with
|
||||
* @key. We bytewise xor B0 with B1 (1) and AES-crypt that. Then we
|
||||
* take the payload and divide it in blocks (16 bytes), xor them with
|
||||
* the previous crypto result (16 bytes) and crypt it, repeat the next
|
||||
* block with the output of the previous one, rinse wash (I guess this
|
||||
* is what AES CBC mode means...but I truly have no idea). So we use
|
||||
* the CBC(AES) blkcipher, that does precisely that. The IV (Initial
|
||||
* Vector) is 16 bytes and is set to zero, so
|
||||
*
|
||||
* See rfc3610. Linux crypto has a CBC implementation, but the
|
||||
* documentation is scarce, to say the least, and the example code is
|
||||
* so intricated that is difficult to understand how things work. Most
|
||||
* of this is guess work -- bite me.
|
||||
*
|
||||
* (1) Created as 6.5 says, again, using as l(a) 'Blen + 14', and
|
||||
* using the 14 bytes of @a to fill up
|
||||
* b1.{mac_header,e0,security_reserved,padding}.
|
||||
*
|
||||
* NOTE: The definiton of l(a) in WUSB1.0[6.5] vs the definition of
|
||||
* l(m) is orthogonal, they bear no relationship, so it is not
|
||||
* in conflict with the parameter's relation that
|
||||
* WUSB1.0[6.4.2]) defines.
|
||||
*
|
||||
* NOTE: WUSB1.0[A.1]: Host Nonce is missing a nibble? (1e); fixed in
|
||||
* first errata released on 2005/07.
|
||||
*
|
||||
* NOTE: we need to clean IV to zero at each invocation to make sure
|
||||
* we start with a fresh empty Initial Vector, so that the CBC
|
||||
* works ok.
|
||||
*
|
||||
* NOTE: blen is not aligned to a block size, we'll pad zeros, that's
|
||||
* what sg[4] is for. Maybe there is a smarter way to do this.
|
||||
*/
|
||||
static int wusb_ccm_mac(struct crypto_blkcipher *tfm_cbc,
|
||||
struct crypto_cipher *tfm_aes, void *mic,
|
||||
const struct aes_ccm_nonce *n,
|
||||
const struct aes_ccm_label *a, const void *b,
|
||||
size_t blen)
|
||||
{
|
||||
int result = 0;
|
||||
struct blkcipher_desc desc;
|
||||
struct aes_ccm_b0 b0;
|
||||
struct aes_ccm_b1 b1;
|
||||
struct aes_ccm_a ax;
|
||||
struct scatterlist sg[4], sg_dst;
|
||||
void *iv, *dst_buf;
|
||||
size_t ivsize, dst_size;
|
||||
const u8 bzero[16] = { 0 };
|
||||
size_t zero_padding;
|
||||
|
||||
d_fnstart(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, "
|
||||
"n %p, a %p, b %p, blen %zu)\n",
|
||||
tfm_cbc, tfm_aes, mic, n, a, b, blen);
|
||||
/*
|
||||
* These checks should be compile time optimized out
|
||||
* ensure @a fills b1's mac_header and following fields
|
||||
*/
|
||||
WARN_ON(sizeof(*a) != sizeof(b1) - sizeof(b1.la));
|
||||
WARN_ON(sizeof(b0) != sizeof(struct aes_ccm_block));
|
||||
WARN_ON(sizeof(b1) != sizeof(struct aes_ccm_block));
|
||||
WARN_ON(sizeof(ax) != sizeof(struct aes_ccm_block));
|
||||
|
||||
result = -ENOMEM;
|
||||
zero_padding = sizeof(struct aes_ccm_block)
|
||||
- blen % sizeof(struct aes_ccm_block);
|
||||
zero_padding = blen % sizeof(struct aes_ccm_block);
|
||||
if (zero_padding)
|
||||
zero_padding = sizeof(struct aes_ccm_block) - zero_padding;
|
||||
dst_size = blen + sizeof(b0) + sizeof(b1) + zero_padding;
|
||||
dst_buf = kzalloc(dst_size, GFP_KERNEL);
|
||||
if (dst_buf == NULL) {
|
||||
printk(KERN_ERR "E: can't alloc destination buffer\n");
|
||||
goto error_dst_buf;
|
||||
}
|
||||
|
||||
iv = crypto_blkcipher_crt(tfm_cbc)->iv;
|
||||
ivsize = crypto_blkcipher_ivsize(tfm_cbc);
|
||||
memset(iv, 0, ivsize);
|
||||
|
||||
/* Setup B0 */
|
||||
b0.flags = 0x59; /* Format B0 */
|
||||
b0.ccm_nonce = *n;
|
||||
b0.lm = cpu_to_be16(0); /* WUSB1.0[6.5] sez l(m) is 0 */
|
||||
|
||||
/* Setup B1
|
||||
*
|
||||
* The WUSB spec is anything but clear! WUSB1.0[6.5]
|
||||
* says that to initialize B1 from A with 'l(a) = blen +
|
||||
* 14'--after clarification, it means to use A's contents
|
||||
* for MAC Header, EO, sec reserved and padding.
|
||||
*/
|
||||
b1.la = cpu_to_be16(blen + 14);
|
||||
memcpy(&b1.mac_header, a, sizeof(*a));
|
||||
|
||||
d_printf(4, NULL, "I: B0 (%zu bytes)\n", sizeof(b0));
|
||||
d_dump(4, NULL, &b0, sizeof(b0));
|
||||
d_printf(4, NULL, "I: B1 (%zu bytes)\n", sizeof(b1));
|
||||
d_dump(4, NULL, &b1, sizeof(b1));
|
||||
d_printf(4, NULL, "I: B (%zu bytes)\n", blen);
|
||||
d_dump(4, NULL, b, blen);
|
||||
d_printf(4, NULL, "I: B 0-padding (%zu bytes)\n", zero_padding);
|
||||
d_printf(4, NULL, "D: IV before crypto (%zu)\n", ivsize);
|
||||
d_dump(4, NULL, iv, ivsize);
|
||||
|
||||
sg_init_table(sg, ARRAY_SIZE(sg));
|
||||
sg_set_buf(&sg[0], &b0, sizeof(b0));
|
||||
sg_set_buf(&sg[1], &b1, sizeof(b1));
|
||||
sg_set_buf(&sg[2], b, blen);
|
||||
/* 0 if well behaved :) */
|
||||
sg_set_buf(&sg[3], bzero, zero_padding);
|
||||
sg_init_one(&sg_dst, dst_buf, dst_size);
|
||||
|
||||
desc.tfm = tfm_cbc;
|
||||
desc.flags = 0;
|
||||
result = crypto_blkcipher_encrypt(&desc, &sg_dst, sg, dst_size);
|
||||
if (result < 0) {
|
||||
printk(KERN_ERR "E: can't compute CBC-MAC tag (MIC): %d\n",
|
||||
result);
|
||||
goto error_cbc_crypt;
|
||||
}
|
||||
d_printf(4, NULL, "D: MIC tag\n");
|
||||
d_dump(4, NULL, iv, ivsize);
|
||||
|
||||
/* Now we crypt the MIC Tag (*iv) with Ax -- values per WUSB1.0[6.5]
|
||||
* The procedure is to AES crypt the A0 block and XOR the MIC
|
||||
* Tag agains it; we only do the first 8 bytes and place it
|
||||
* directly in the destination buffer.
|
||||
*
|
||||
* POS Crypto API: size is assumed to be AES's block size.
|
||||
* Thanks for documenting it -- tip taken from airo.c
|
||||
*/
|
||||
ax.flags = 0x01; /* as per WUSB 1.0 spec */
|
||||
ax.ccm_nonce = *n;
|
||||
ax.counter = 0;
|
||||
crypto_cipher_encrypt_one(tfm_aes, (void *)&ax, (void *)&ax);
|
||||
bytewise_xor(mic, &ax, iv, 8);
|
||||
d_printf(4, NULL, "D: CTR[MIC]\n");
|
||||
d_dump(4, NULL, &ax, 8);
|
||||
d_printf(4, NULL, "D: CCM-MIC tag\n");
|
||||
d_dump(4, NULL, mic, 8);
|
||||
result = 8;
|
||||
error_cbc_crypt:
|
||||
kfree(dst_buf);
|
||||
error_dst_buf:
|
||||
d_fnend(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, "
|
||||
"n %p, a %p, b %p, blen %zu)\n",
|
||||
tfm_cbc, tfm_aes, mic, n, a, b, blen);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* WUSB Pseudo Random Function (WUSB1.0[6.5])
|
||||
*
|
||||
* @b: buffer to the source data; cannot be a global or const local
|
||||
* (will confuse the scatterlists)
|
||||
*/
|
||||
ssize_t wusb_prf(void *out, size_t out_size,
|
||||
const u8 key[16], const struct aes_ccm_nonce *_n,
|
||||
const struct aes_ccm_label *a,
|
||||
const void *b, size_t blen, size_t len)
|
||||
{
|
||||
ssize_t result, bytes = 0, bitr;
|
||||
struct aes_ccm_nonce n = *_n;
|
||||
struct crypto_blkcipher *tfm_cbc;
|
||||
struct crypto_cipher *tfm_aes;
|
||||
u64 sfn = 0;
|
||||
__le64 sfn_le;
|
||||
|
||||
d_fnstart(3, NULL, "(out %p, out_size %zu, key %p, _n %p, "
|
||||
"a %p, b %p, blen %zu, len %zu)\n", out, out_size,
|
||||
key, _n, a, b, blen, len);
|
||||
|
||||
tfm_cbc = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC);
|
||||
if (IS_ERR(tfm_cbc)) {
|
||||
result = PTR_ERR(tfm_cbc);
|
||||
printk(KERN_ERR "E: can't load CBC(AES): %d\n", (int)result);
|
||||
goto error_alloc_cbc;
|
||||
}
|
||||
result = crypto_blkcipher_setkey(tfm_cbc, key, 16);
|
||||
if (result < 0) {
|
||||
printk(KERN_ERR "E: can't set CBC key: %d\n", (int)result);
|
||||
goto error_setkey_cbc;
|
||||
}
|
||||
|
||||
tfm_aes = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC);
|
||||
if (IS_ERR(tfm_aes)) {
|
||||
result = PTR_ERR(tfm_aes);
|
||||
printk(KERN_ERR "E: can't load AES: %d\n", (int)result);
|
||||
goto error_alloc_aes;
|
||||
}
|
||||
result = crypto_cipher_setkey(tfm_aes, key, 16);
|
||||
if (result < 0) {
|
||||
printk(KERN_ERR "E: can't set AES key: %d\n", (int)result);
|
||||
goto error_setkey_aes;
|
||||
}
|
||||
|
||||
for (bitr = 0; bitr < (len + 63) / 64; bitr++) {
|
||||
sfn_le = cpu_to_le64(sfn++);
|
||||
memcpy(&n.sfn, &sfn_le, sizeof(n.sfn)); /* n.sfn++... */
|
||||
result = wusb_ccm_mac(tfm_cbc, tfm_aes, out + bytes,
|
||||
&n, a, b, blen);
|
||||
if (result < 0)
|
||||
goto error_ccm_mac;
|
||||
bytes += result;
|
||||
}
|
||||
result = bytes;
|
||||
error_ccm_mac:
|
||||
error_setkey_aes:
|
||||
crypto_free_cipher(tfm_aes);
|
||||
error_alloc_aes:
|
||||
error_setkey_cbc:
|
||||
crypto_free_blkcipher(tfm_cbc);
|
||||
error_alloc_cbc:
|
||||
d_fnend(3, NULL, "(out %p, out_size %zu, key %p, _n %p, "
|
||||
"a %p, b %p, blen %zu, len %zu) = %d\n", out, out_size,
|
||||
key, _n, a, b, blen, len, (int)bytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* WUSB1.0[A.2] test vectors */
|
||||
static const u8 stv_hsmic_key[16] = {
|
||||
0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d,
|
||||
0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f
|
||||
};
|
||||
|
||||
static const struct aes_ccm_nonce stv_hsmic_n = {
|
||||
.sfn = { 0 },
|
||||
.tkid = { 0x76, 0x98, 0x01, },
|
||||
.dest_addr = { .data = { 0xbe, 0x00 } },
|
||||
.src_addr = { .data = { 0x76, 0x98 } },
|
||||
};
|
||||
|
||||
/*
|
||||
* Out-of-band MIC Generation verification code
|
||||
*
|
||||
*/
|
||||
static int wusb_oob_mic_verify(void)
|
||||
{
|
||||
int result;
|
||||
u8 mic[8];
|
||||
/* WUSB1.0[A.2] test vectors
|
||||
*
|
||||
* Need to keep it in the local stack as GCC 4.1.3something
|
||||
* messes up and generates noise.
|
||||
*/
|
||||
struct usb_handshake stv_hsmic_hs = {
|
||||
.bMessageNumber = 2,
|
||||
.bStatus = 00,
|
||||
.tTKID = { 0x76, 0x98, 0x01 },
|
||||
.bReserved = 00,
|
||||
.CDID = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
|
||||
0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
|
||||
0x3c, 0x3d, 0x3e, 0x3f },
|
||||
.nonce = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
|
||||
0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f },
|
||||
.MIC = { 0x75, 0x6a, 0x97, 0x51, 0x0c, 0x8c,
|
||||
0x14, 0x7b } ,
|
||||
};
|
||||
size_t hs_size;
|
||||
|
||||
result = wusb_oob_mic(mic, stv_hsmic_key, &stv_hsmic_n, &stv_hsmic_hs);
|
||||
if (result < 0)
|
||||
printk(KERN_ERR "E: WUSB OOB MIC test: failed: %d\n", result);
|
||||
else if (memcmp(stv_hsmic_hs.MIC, mic, sizeof(mic))) {
|
||||
printk(KERN_ERR "E: OOB MIC test: "
|
||||
"mismatch between MIC result and WUSB1.0[A2]\n");
|
||||
hs_size = sizeof(stv_hsmic_hs) - sizeof(stv_hsmic_hs.MIC);
|
||||
printk(KERN_ERR "E: Handshake2 in: (%zu bytes)\n", hs_size);
|
||||
dump_bytes(NULL, &stv_hsmic_hs, hs_size);
|
||||
printk(KERN_ERR "E: CCM Nonce in: (%zu bytes)\n",
|
||||
sizeof(stv_hsmic_n));
|
||||
dump_bytes(NULL, &stv_hsmic_n, sizeof(stv_hsmic_n));
|
||||
printk(KERN_ERR "E: MIC out:\n");
|
||||
dump_bytes(NULL, mic, sizeof(mic));
|
||||
printk(KERN_ERR "E: MIC out (from WUSB1.0[A.2]):\n");
|
||||
dump_bytes(NULL, stv_hsmic_hs.MIC, sizeof(stv_hsmic_hs.MIC));
|
||||
result = -EINVAL;
|
||||
} else
|
||||
result = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test vectors for Key derivation
|
||||
*
|
||||
* These come from WUSB1.0[6.5.1], the vectors in WUSB1.0[A.1]
|
||||
* (errata corrected in 2005/07).
|
||||
*/
|
||||
static const u8 stv_key_a1[16] __attribute__ ((__aligned__(4))) = {
|
||||
0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87,
|
||||
0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f
|
||||
};
|
||||
|
||||
static const struct aes_ccm_nonce stv_keydvt_n_a1 = {
|
||||
.sfn = { 0 },
|
||||
.tkid = { 0x76, 0x98, 0x01, },
|
||||
.dest_addr = { .data = { 0xbe, 0x00 } },
|
||||
.src_addr = { .data = { 0x76, 0x98 } },
|
||||
};
|
||||
|
||||
static const struct wusb_keydvt_out stv_keydvt_out_a1 = {
|
||||
.kck = {
|
||||
0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d,
|
||||
0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f
|
||||
},
|
||||
.ptk = {
|
||||
0xc8, 0x70, 0x62, 0x82, 0xb6, 0x7c, 0xe9, 0x06,
|
||||
0x7b, 0xc5, 0x25, 0x69, 0xf2, 0x36, 0x61, 0x2d
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Performa a test to make sure we match the vectors defined in
|
||||
* WUSB1.0[A.1](Errata2006/12)
|
||||
*/
|
||||
static int wusb_key_derive_verify(void)
|
||||
{
|
||||
int result = 0;
|
||||
struct wusb_keydvt_out keydvt_out;
|
||||
/* These come from WUSB1.0[A.1] + 2006/12 errata
|
||||
* NOTE: can't make this const or global -- somehow it seems
|
||||
* the scatterlists for crypto get confused and we get
|
||||
* bad data. There is no doc on this... */
|
||||
struct wusb_keydvt_in stv_keydvt_in_a1 = {
|
||||
.hnonce = {
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
|
||||
},
|
||||
.dnonce = {
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
||||
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f
|
||||
}
|
||||
};
|
||||
|
||||
result = wusb_key_derive(&keydvt_out, stv_key_a1, &stv_keydvt_n_a1,
|
||||
&stv_keydvt_in_a1);
|
||||
if (result < 0)
|
||||
printk(KERN_ERR "E: WUSB key derivation test: "
|
||||
"derivation failed: %d\n", result);
|
||||
if (memcmp(&stv_keydvt_out_a1, &keydvt_out, sizeof(keydvt_out))) {
|
||||
printk(KERN_ERR "E: WUSB key derivation test: "
|
||||
"mismatch between key derivation result "
|
||||
"and WUSB1.0[A1] Errata 2006/12\n");
|
||||
printk(KERN_ERR "E: keydvt in: key (%zu bytes)\n",
|
||||
sizeof(stv_key_a1));
|
||||
dump_bytes(NULL, stv_key_a1, sizeof(stv_key_a1));
|
||||
printk(KERN_ERR "E: keydvt in: nonce (%zu bytes)\n",
|
||||
sizeof(stv_keydvt_n_a1));
|
||||
dump_bytes(NULL, &stv_keydvt_n_a1, sizeof(stv_keydvt_n_a1));
|
||||
printk(KERN_ERR "E: keydvt in: hnonce & dnonce (%zu bytes)\n",
|
||||
sizeof(stv_keydvt_in_a1));
|
||||
dump_bytes(NULL, &stv_keydvt_in_a1, sizeof(stv_keydvt_in_a1));
|
||||
printk(KERN_ERR "E: keydvt out: KCK\n");
|
||||
dump_bytes(NULL, &keydvt_out.kck, sizeof(keydvt_out.kck));
|
||||
printk(KERN_ERR "E: keydvt out: PTK\n");
|
||||
dump_bytes(NULL, &keydvt_out.ptk, sizeof(keydvt_out.ptk));
|
||||
result = -EINVAL;
|
||||
} else
|
||||
result = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize crypto system
|
||||
*
|
||||
* FIXME: we do nothing now, other than verifying. Later on we'll
|
||||
* cache the encryption stuff, so that's why we have a separate init.
|
||||
*/
|
||||
int wusb_crypto_init(void)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = wusb_key_derive_verify();
|
||||
if (result < 0)
|
||||
return result;
|
||||
return wusb_oob_mic_verify();
|
||||
}
|
||||
|
||||
void wusb_crypto_exit(void)
|
||||
{
|
||||
/* FIXME: free cached crypto transforms */
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* WUSB devices
|
||||
* sysfs bindings
|
||||
*
|
||||
* Copyright (C) 2007 Intel Corporation
|
||||
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*
|
||||
* Get them out of the way...
|
||||
*/
|
||||
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "wusbhc.h"
|
||||
|
||||
#undef D_LOCAL
|
||||
#define D_LOCAL 4
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
static ssize_t wusb_disconnect_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct usb_device *usb_dev;
|
||||
struct wusbhc *wusbhc;
|
||||
unsigned command;
|
||||
u8 port_idx;
|
||||
|
||||
if (sscanf(buf, "%u", &command) != 1)
|
||||
return -EINVAL;
|
||||
if (command == 0)
|
||||
return size;
|
||||
usb_dev = to_usb_device(dev);
|
||||
wusbhc = wusbhc_get_by_usb_dev(usb_dev);
|
||||
if (wusbhc == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
port_idx = wusb_port_no_to_idx(usb_dev->portnum);
|
||||
__wusbhc_dev_disable(wusbhc, port_idx);
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
wusbhc_put(wusbhc);
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_disconnect, 0200, NULL, wusb_disconnect_store);
|
||||
|
||||
static ssize_t wusb_cdid_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
ssize_t result;
|
||||
struct wusb_dev *wusb_dev;
|
||||
|
||||
wusb_dev = wusb_dev_get_by_usb_dev(to_usb_device(dev));
|
||||
if (wusb_dev == NULL)
|
||||
return -ENODEV;
|
||||
result = ckhdid_printf(buf, PAGE_SIZE, &wusb_dev->cdid);
|
||||
strcat(buf, "\n");
|
||||
wusb_dev_put(wusb_dev);
|
||||
return result + 1;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_cdid, 0444, wusb_cdid_show, NULL);
|
||||
|
||||
static ssize_t wusb_ck_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
int result;
|
||||
struct usb_device *usb_dev;
|
||||
struct wusbhc *wusbhc;
|
||||
struct wusb_ckhdid ck;
|
||||
|
||||
result = sscanf(buf,
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx\n",
|
||||
&ck.data[0] , &ck.data[1],
|
||||
&ck.data[2] , &ck.data[3],
|
||||
&ck.data[4] , &ck.data[5],
|
||||
&ck.data[6] , &ck.data[7],
|
||||
&ck.data[8] , &ck.data[9],
|
||||
&ck.data[10], &ck.data[11],
|
||||
&ck.data[12], &ck.data[13],
|
||||
&ck.data[14], &ck.data[15]);
|
||||
if (result != 16)
|
||||
return -EINVAL;
|
||||
|
||||
usb_dev = to_usb_device(dev);
|
||||
wusbhc = wusbhc_get_by_usb_dev(usb_dev);
|
||||
if (wusbhc == NULL)
|
||||
return -ENODEV;
|
||||
result = wusb_dev_4way_handshake(wusbhc, usb_dev->wusb_dev, &ck);
|
||||
memset(&ck, 0, sizeof(ck));
|
||||
wusbhc_put(wusbhc);
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_ck, 0200, NULL, wusb_ck_store);
|
||||
|
||||
static struct attribute *wusb_dev_attrs[] = {
|
||||
&dev_attr_wusb_disconnect.attr,
|
||||
&dev_attr_wusb_cdid.attr,
|
||||
&dev_attr_wusb_ck.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group wusb_dev_attr_group = {
|
||||
.name = NULL, /* we want them in the same directory */
|
||||
.attrs = wusb_dev_attrs,
|
||||
};
|
||||
|
||||
int wusb_dev_sysfs_add(struct wusbhc *wusbhc, struct usb_device *usb_dev,
|
||||
struct wusb_dev *wusb_dev)
|
||||
{
|
||||
int result = sysfs_create_group(&usb_dev->dev.kobj,
|
||||
&wusb_dev_attr_group);
|
||||
struct device *dev = &usb_dev->dev;
|
||||
if (result < 0)
|
||||
dev_err(dev, "Cannot register WUSB-dev attributes: %d\n",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void wusb_dev_sysfs_rm(struct wusb_dev *wusb_dev)
|
||||
{
|
||||
struct usb_device *usb_dev = wusb_dev->usb_dev;
|
||||
if (usb_dev)
|
||||
sysfs_remove_group(&usb_dev->dev.kobj, &wusb_dev_attr_group);
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8])
|
||||
* MMC (Microscheduled Management Command) handling
|
||||
*
|
||||
* Copyright (C) 2005-2006 Intel Corporation
|
||||
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*
|
||||
* WUIEs and MMC IEs...well, they are almost the same at the end. MMC
|
||||
* IEs are Wireless USB IEs that go into the MMC period...[what is
|
||||
* that? look in Design-overview.txt].
|
||||
*
|
||||
*
|
||||
* This is a simple subsystem to keep track of which IEs are being
|
||||
* sent by the host in the MMC period.
|
||||
*
|
||||
* For each WUIE we ask to send, we keep it in an array, so we can
|
||||
* request its removal later, or replace the content. They are tracked
|
||||
* by pointer, so be sure to use the same pointer if you want to
|
||||
* remove it or update the contents.
|
||||
*
|
||||
* FIXME:
|
||||
* - add timers that autoremove intervalled IEs?
|
||||
*/
|
||||
#include <linux/usb/wusb.h>
|
||||
#include "wusbhc.h"
|
||||
|
||||
/* Initialize the MMCIEs handling mechanism */
|
||||
int wusbhc_mmcie_create(struct wusbhc *wusbhc)
|
||||
{
|
||||
u8 mmcies = wusbhc->mmcies_max;
|
||||
wusbhc->mmcie = kcalloc(mmcies, sizeof(wusbhc->mmcie[0]), GFP_KERNEL);
|
||||
if (wusbhc->mmcie == NULL)
|
||||
return -ENOMEM;
|
||||
mutex_init(&wusbhc->mmcie_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Release resources used by the MMCIEs handling mechanism */
|
||||
void wusbhc_mmcie_destroy(struct wusbhc *wusbhc)
|
||||
{
|
||||
kfree(wusbhc->mmcie);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add or replace an MMC Wireless USB IE.
|
||||
*
|
||||
* @interval: See WUSB1.0[8.5.3.1]
|
||||
* @repeat_cnt: See WUSB1.0[8.5.3.1]
|
||||
* @handle: See WUSB1.0[8.5.3.1]
|
||||
* @wuie: Pointer to the header of the WUSB IE data to add.
|
||||
* MUST BE allocated in a kmalloc buffer (no stack or
|
||||
* vmalloc).
|
||||
* THE CALLER ALWAYS OWNS THE POINTER (we don't free it
|
||||
* on remove, we just forget about it).
|
||||
* @returns: 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* Goes over the *whole* @wusbhc->mmcie array looking for (a) the
|
||||
* first free spot and (b) if @wuie is already in the array (aka:
|
||||
* transmitted in the MMCs) the spot were it is.
|
||||
*
|
||||
* If present, we "overwrite it" (update).
|
||||
*
|
||||
*
|
||||
* NOTE: Need special ordering rules -- see below WUSB1.0 Table 7-38.
|
||||
* The host uses the handle as the 'sort' index. We
|
||||
* allocate the last one always for the WUIE_ID_HOST_INFO, and
|
||||
* the rest, first come first serve in inverse order.
|
||||
*
|
||||
* Host software must make sure that it adds the other IEs in
|
||||
* the right order... the host hardware is responsible for
|
||||
* placing the WCTA IEs in the right place with the other IEs
|
||||
* set by host software.
|
||||
*
|
||||
* NOTE: we can access wusbhc->wa_descr without locking because it is
|
||||
* read only.
|
||||
*/
|
||||
int wusbhc_mmcie_set(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
|
||||
struct wuie_hdr *wuie)
|
||||
{
|
||||
int result = -ENOBUFS;
|
||||
unsigned handle, itr;
|
||||
|
||||
/* Search a handle, taking into account the ordering */
|
||||
mutex_lock(&wusbhc->mmcie_mutex);
|
||||
switch (wuie->bIEIdentifier) {
|
||||
case WUIE_ID_HOST_INFO:
|
||||
/* Always last */
|
||||
handle = wusbhc->mmcies_max - 1;
|
||||
break;
|
||||
case WUIE_ID_ISOCH_DISCARD:
|
||||
dev_err(wusbhc->dev, "Special ordering case for WUIE ID 0x%x "
|
||||
"unimplemented\n", wuie->bIEIdentifier);
|
||||
result = -ENOSYS;
|
||||
goto error_unlock;
|
||||
default:
|
||||
/* search for it or find the last empty slot */
|
||||
handle = ~0;
|
||||
for (itr = 0; itr < wusbhc->mmcies_max - 1; itr++) {
|
||||
if (wusbhc->mmcie[itr] == wuie) {
|
||||
handle = itr;
|
||||
break;
|
||||
}
|
||||
if (wusbhc->mmcie[itr] == NULL)
|
||||
handle = itr;
|
||||
}
|
||||
if (handle == ~0)
|
||||
goto error_unlock;
|
||||
}
|
||||
result = (wusbhc->mmcie_add)(wusbhc, interval, repeat_cnt, handle,
|
||||
wuie);
|
||||
if (result >= 0)
|
||||
wusbhc->mmcie[handle] = wuie;
|
||||
error_unlock:
|
||||
mutex_unlock(&wusbhc->mmcie_mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_mmcie_set);
|
||||
|
||||
/*
|
||||
* Remove an MMC IE previously added with wusbhc_mmcie_set()
|
||||
*
|
||||
* @wuie Pointer used to add the WUIE
|
||||
*/
|
||||
void wusbhc_mmcie_rm(struct wusbhc *wusbhc, struct wuie_hdr *wuie)
|
||||
{
|
||||
int result;
|
||||
unsigned handle, itr;
|
||||
|
||||
mutex_lock(&wusbhc->mmcie_mutex);
|
||||
for (itr = 0; itr < wusbhc->mmcies_max; itr++) {
|
||||
if (wusbhc->mmcie[itr] == wuie) {
|
||||
handle = itr;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&wusbhc->mmcie_mutex);
|
||||
return;
|
||||
|
||||
found:
|
||||
result = (wusbhc->mmcie_rm)(wusbhc, handle);
|
||||
if (result == 0)
|
||||
wusbhc->mmcie[itr] = NULL;
|
||||
mutex_unlock(&wusbhc->mmcie_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_mmcie_rm);
|
||||
|
||||
/*
|
||||
* wusbhc_start - start transmitting MMCs and accepting connections
|
||||
* @wusbhc: the HC to start
|
||||
* @chid: the CHID to use for this host
|
||||
*
|
||||
* Establishes a cluster reservation, enables device connections, and
|
||||
* starts MMCs with appropriate DNTS parameters.
|
||||
*/
|
||||
int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = wusbhc->dev;
|
||||
|
||||
WARN_ON(wusbhc->wuie_host_info != NULL);
|
||||
|
||||
result = wusbhc_rsv_establish(wusbhc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot establish cluster reservation: %d\n",
|
||||
result);
|
||||
goto error_rsv_establish;
|
||||
}
|
||||
|
||||
result = wusbhc_devconnect_start(wusbhc, chid);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "error enabling device connections: %d\n", result);
|
||||
goto error_devconnect_start;
|
||||
}
|
||||
|
||||
result = wusbhc_sec_start(wusbhc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "error starting security in the HC: %d\n", result);
|
||||
goto error_sec_start;
|
||||
}
|
||||
/* FIXME: the choice of the DNTS parameters is somewhat
|
||||
* arbitrary */
|
||||
result = wusbhc->set_num_dnts(wusbhc, 0, 15);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot set DNTS parameters: %d\n", result);
|
||||
goto error_set_num_dnts;
|
||||
}
|
||||
result = wusbhc->start(wusbhc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "error starting wusbch: %d\n", result);
|
||||
goto error_wusbhc_start;
|
||||
}
|
||||
wusbhc->active = 1;
|
||||
return 0;
|
||||
|
||||
error_wusbhc_start:
|
||||
wusbhc_sec_stop(wusbhc);
|
||||
error_set_num_dnts:
|
||||
error_sec_start:
|
||||
wusbhc_devconnect_stop(wusbhc);
|
||||
error_devconnect_start:
|
||||
wusbhc_rsv_terminate(wusbhc);
|
||||
error_rsv_establish:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disconnect all from the WUSB Channel
|
||||
*
|
||||
* Send a Host Disconnect IE in the MMC, wait, don't send it any more
|
||||
*/
|
||||
static int __wusbhc_host_disconnect_ie(struct wusbhc *wusbhc)
|
||||
{
|
||||
int result = -ENOMEM;
|
||||
struct wuie_host_disconnect *host_disconnect_ie;
|
||||
might_sleep();
|
||||
host_disconnect_ie = kmalloc(sizeof(*host_disconnect_ie), GFP_KERNEL);
|
||||
if (host_disconnect_ie == NULL)
|
||||
goto error_alloc;
|
||||
host_disconnect_ie->hdr.bLength = sizeof(*host_disconnect_ie);
|
||||
host_disconnect_ie->hdr.bIEIdentifier = WUIE_ID_HOST_DISCONNECT;
|
||||
result = wusbhc_mmcie_set(wusbhc, 0, 0, &host_disconnect_ie->hdr);
|
||||
if (result < 0)
|
||||
goto error_mmcie_set;
|
||||
|
||||
/* WUSB1.0[8.5.3.1 & 7.5.2] */
|
||||
msleep(100);
|
||||
wusbhc_mmcie_rm(wusbhc, &host_disconnect_ie->hdr);
|
||||
error_mmcie_set:
|
||||
kfree(host_disconnect_ie);
|
||||
error_alloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* wusbhc_stop - stop transmitting MMCs
|
||||
* @wusbhc: the HC to stop
|
||||
*
|
||||
* Send a Host Disconnect IE, wait, remove all the MMCs (stop sending MMCs).
|
||||
*
|
||||
* If we can't allocate a Host Stop IE, screw it, we don't notify the
|
||||
* devices we are disconnecting...
|
||||
*/
|
||||
void wusbhc_stop(struct wusbhc *wusbhc)
|
||||
{
|
||||
if (wusbhc->active) {
|
||||
wusbhc->active = 0;
|
||||
wusbhc->stop(wusbhc);
|
||||
wusbhc_sec_stop(wusbhc);
|
||||
__wusbhc_host_disconnect_ie(wusbhc);
|
||||
wusbhc_devconnect_stop(wusbhc);
|
||||
wusbhc_rsv_terminate(wusbhc);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_stop);
|
||||
|
||||
/*
|
||||
* Change the CHID in a WUSB Channel
|
||||
*
|
||||
* If it is just a new CHID, send a Host Disconnect IE and then change
|
||||
* the CHID IE.
|
||||
*/
|
||||
static int __wusbhc_chid_change(struct wusbhc *wusbhc,
|
||||
const struct wusb_ckhdid *chid)
|
||||
{
|
||||
int result = -ENOSYS;
|
||||
struct device *dev = wusbhc->dev;
|
||||
dev_err(dev, "%s() not implemented yet\n", __func__);
|
||||
return result;
|
||||
|
||||
BUG_ON(wusbhc->wuie_host_info == NULL);
|
||||
__wusbhc_host_disconnect_ie(wusbhc);
|
||||
wusbhc->wuie_host_info->CHID = *chid;
|
||||
result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->wuie_host_info->hdr);
|
||||
if (result < 0)
|
||||
dev_err(dev, "Can't update Host Info WUSB IE: %d\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set/reset/update a new CHID
|
||||
*
|
||||
* Depending on the previous state of the MMCs, start, stop or change
|
||||
* the sent MMC. This effectively switches the host controller on and
|
||||
* off (radio wise).
|
||||
*/
|
||||
int wusbhc_chid_set(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
if (memcmp(chid, &wusb_ckhdid_zero, sizeof(chid)) == 0)
|
||||
chid = NULL;
|
||||
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
if (wusbhc->active) {
|
||||
if (chid)
|
||||
result = __wusbhc_chid_change(wusbhc, chid);
|
||||
else
|
||||
wusbhc_stop(wusbhc);
|
||||
} else {
|
||||
if (chid)
|
||||
wusbhc_start(wusbhc, chid);
|
||||
}
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_chid_set);
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Wireless USB Host Controller
|
||||
* UWB Protocol Adaptation Layer (PAL) glue.
|
||||
*
|
||||
* Copyright (C) 2008 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "wusbhc.h"
|
||||
|
||||
/**
|
||||
* wusbhc_pal_register - register the WUSB HC as a UWB PAL
|
||||
* @wusbhc: the WUSB HC
|
||||
*/
|
||||
int wusbhc_pal_register(struct wusbhc *wusbhc)
|
||||
{
|
||||
uwb_pal_init(&wusbhc->pal);
|
||||
|
||||
wusbhc->pal.name = "wusbhc";
|
||||
wusbhc->pal.device = wusbhc->usb_hcd.self.controller;
|
||||
|
||||
return uwb_pal_register(wusbhc->uwb_rc, &wusbhc->pal);
|
||||
}
|
||||
|
||||
/**
|
||||
* wusbhc_pal_register - unregister the WUSB HC as a UWB PAL
|
||||
* @wusbhc: the WUSB HC
|
||||
*/
|
||||
void wusbhc_pal_unregister(struct wusbhc *wusbhc)
|
||||
{
|
||||
uwb_pal_unregister(wusbhc->uwb_rc, &wusbhc->pal);
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* WUSB cluster reservation management
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/uwb.h>
|
||||
|
||||
#include "wusbhc.h"
|
||||
|
||||
/*
|
||||
* WUSB cluster reservations are multicast reservations with the
|
||||
* broadcast cluster ID (BCID) as the target DevAddr.
|
||||
*
|
||||
* FIXME: consider adjusting the reservation depending on what devices
|
||||
* are attached.
|
||||
*/
|
||||
|
||||
static int wusbhc_bwa_set(struct wusbhc *wusbhc, u8 stream,
|
||||
const struct uwb_mas_bm *mas)
|
||||
{
|
||||
if (mas == NULL)
|
||||
mas = &uwb_mas_bm_zero;
|
||||
return wusbhc->bwa_set(wusbhc, stream, mas);
|
||||
}
|
||||
|
||||
/**
|
||||
* wusbhc_rsv_complete_cb - WUSB HC reservation complete callback
|
||||
* @rsv: the reservation
|
||||
*
|
||||
* Either set or clear the HC's view of the reservation.
|
||||
*
|
||||
* FIXME: when a reservation is denied the HC should be stopped.
|
||||
*/
|
||||
static void wusbhc_rsv_complete_cb(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct wusbhc *wusbhc = rsv->pal_priv;
|
||||
struct device *dev = wusbhc->dev;
|
||||
char buf[72];
|
||||
|
||||
switch (rsv->state) {
|
||||
case UWB_RSV_STATE_O_ESTABLISHED:
|
||||
bitmap_scnprintf(buf, sizeof(buf), rsv->mas.bm, UWB_NUM_MAS);
|
||||
dev_dbg(dev, "established reservation: %s\n", buf);
|
||||
wusbhc_bwa_set(wusbhc, rsv->stream, &rsv->mas);
|
||||
break;
|
||||
case UWB_RSV_STATE_NONE:
|
||||
dev_dbg(dev, "removed reservation\n");
|
||||
wusbhc_bwa_set(wusbhc, 0, NULL);
|
||||
wusbhc->rsv = NULL;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(dev, "unexpected reservation state: %d\n", rsv->state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* wusbhc_rsv_establish - establish a reservation for the cluster
|
||||
* @wusbhc: the WUSB HC requesting a bandwith reservation
|
||||
*/
|
||||
int wusbhc_rsv_establish(struct wusbhc *wusbhc)
|
||||
{
|
||||
struct uwb_rc *rc = wusbhc->uwb_rc;
|
||||
struct uwb_rsv *rsv;
|
||||
struct uwb_dev_addr bcid;
|
||||
int ret;
|
||||
|
||||
rsv = uwb_rsv_create(rc, wusbhc_rsv_complete_cb, wusbhc);
|
||||
if (rsv == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
bcid.data[0] = wusbhc->cluster_id;
|
||||
bcid.data[1] = 0;
|
||||
|
||||
rsv->owner = &rc->uwb_dev;
|
||||
rsv->target.type = UWB_RSV_TARGET_DEVADDR;
|
||||
rsv->target.devaddr = bcid;
|
||||
rsv->type = UWB_DRP_TYPE_PRIVATE;
|
||||
rsv->max_mas = 256;
|
||||
rsv->min_mas = 16; /* one MAS per zone? */
|
||||
rsv->sparsity = 16; /* at least one MAS in each zone? */
|
||||
rsv->is_multicast = true;
|
||||
|
||||
ret = uwb_rsv_establish(rsv);
|
||||
if (ret == 0)
|
||||
wusbhc->rsv = rsv;
|
||||
else
|
||||
uwb_rsv_destroy(rsv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* wusbhc_rsv_terminate - terminate any cluster reservation
|
||||
* @wusbhc: the WUSB host whose reservation is to be terminated
|
||||
*/
|
||||
void wusbhc_rsv_terminate(struct wusbhc *wusbhc)
|
||||
{
|
||||
if (wusbhc->rsv)
|
||||
uwb_rsv_terminate(wusbhc->rsv);
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
/*
|
||||
* Wireless USB Host Controller
|
||||
* Root Hub operations
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2005-2006 Intel Corporation
|
||||
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version
|
||||
* 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*
|
||||
* We fake a root hub that has fake ports (as many as simultaneous
|
||||
* devices the Wireless USB Host Controller can deal with). For each
|
||||
* port we keep an state in @wusbhc->port[index] identical to the one
|
||||
* specified in the USB2.0[ch11] spec and some extra device
|
||||
* information that complements the one in 'struct usb_device' (as
|
||||
* this lacs a hcpriv pointer).
|
||||
*
|
||||
* Note this is common to WHCI and HWA host controllers.
|
||||
*
|
||||
* Through here we enable most of the state changes that the USB stack
|
||||
* will use to connect or disconnect devices. We need to do some
|
||||
* forced adaptation of Wireless USB device states vs. wired:
|
||||
*
|
||||
* USB: WUSB:
|
||||
*
|
||||
* Port Powered-off port slot n/a
|
||||
* Powered-on port slot available
|
||||
* Disconnected port slot available
|
||||
* Connected port slot assigned device
|
||||
* device sent DN_Connect
|
||||
* device was authenticated
|
||||
* Enabled device is authenticated, transitioned
|
||||
* from unauth -> auth -> default address
|
||||
* -> enabled
|
||||
* Reset disconnect
|
||||
* Disable disconnect
|
||||
*
|
||||
* This maps the standard USB port states with the WUSB device states
|
||||
* so we can fake ports without having to modify the USB stack.
|
||||
*
|
||||
* FIXME: this process will change in the future
|
||||
*
|
||||
*
|
||||
* ENTRY POINTS
|
||||
*
|
||||
* Our entry points into here are, as in hcd.c, the USB stack root hub
|
||||
* ops defined in the usb_hcd struct:
|
||||
*
|
||||
* wusbhc_rh_status_data() Provide hub and port status data bitmap
|
||||
*
|
||||
* wusbhc_rh_control() Execution of all the major requests
|
||||
* you can do to a hub (Set|Clear
|
||||
* features, get descriptors, status, etc).
|
||||
*
|
||||
* wusbhc_rh_[suspend|resume]() That
|
||||
*
|
||||
* wusbhc_rh_start_port_reset() ??? unimplemented
|
||||
*/
|
||||
#include "wusbhc.h"
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/*
|
||||
* Reset a fake port
|
||||
*
|
||||
* This can be called to reset a port from any other state or to reset
|
||||
* it when connecting. In Wireless USB they are different; when doing
|
||||
* a new connect that involves going over the authentication. When
|
||||
* just reseting, its a different story.
|
||||
*
|
||||
* The Linux USB stack resets a port twice before it considers it
|
||||
* enabled, so we have to detect and ignore that.
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
|
||||
*
|
||||
* Supposedly we are the only thread accesing @wusbhc->port; in any
|
||||
* case, maybe we should move the mutex locking from
|
||||
* wusbhc_devconnect_auth() to here.
|
||||
*
|
||||
* @port_idx refers to the wusbhc's port index, not the USB port number
|
||||
*/
|
||||
static int wusbhc_rh_port_reset(struct wusbhc *wusbhc, u8 port_idx)
|
||||
{
|
||||
int result = 0;
|
||||
struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx);
|
||||
|
||||
d_fnstart(3, wusbhc->dev, "(wusbhc %p port_idx %u)\n",
|
||||
wusbhc, port_idx);
|
||||
if (port->reset_count == 0) {
|
||||
wusbhc_devconnect_auth(wusbhc, port_idx);
|
||||
port->reset_count++;
|
||||
} else if (port->reset_count == 1)
|
||||
/* see header */
|
||||
d_printf(2, wusbhc->dev, "Ignoring second reset on port_idx "
|
||||
"%u\n", port_idx);
|
||||
else
|
||||
result = wusbhc_dev_reset(wusbhc, port_idx);
|
||||
d_fnend(3, wusbhc->dev, "(wusbhc %p port_idx %u) = %d\n",
|
||||
wusbhc, port_idx, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the hub change status bitmap
|
||||
*
|
||||
* The bits in the change status bitmap are cleared when a
|
||||
* ClearPortFeature request is issued (USB2.0[11.12.3,11.12.4].
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
|
||||
*
|
||||
* WARNING!! This gets called from atomic context; we cannot get the
|
||||
* mutex--the only race condition we can find is some bit
|
||||
* changing just after we copy it, which shouldn't be too
|
||||
* big of a problem [and we can't make it an spinlock
|
||||
* because other parts need to take it and sleep] .
|
||||
*
|
||||
* @usb_hcd is refcounted, so it won't dissapear under us
|
||||
* and before killing a host, the polling of the root hub
|
||||
* would be stopped anyway.
|
||||
*/
|
||||
int wusbhc_rh_status_data(struct usb_hcd *usb_hcd, char *_buf)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
size_t cnt, size;
|
||||
unsigned long *buf = (unsigned long *) _buf;
|
||||
|
||||
d_fnstart(1, wusbhc->dev, "(wusbhc %p)\n", wusbhc);
|
||||
/* WE DON'T LOCK, see comment */
|
||||
size = wusbhc->ports_max + 1 /* hub bit */;
|
||||
size = (size + 8 - 1) / 8; /* round to bytes */
|
||||
for (cnt = 0; cnt < wusbhc->ports_max; cnt++)
|
||||
if (wusb_port_by_idx(wusbhc, cnt)->change)
|
||||
set_bit(cnt + 1, buf);
|
||||
else
|
||||
clear_bit(cnt + 1, buf);
|
||||
d_fnend(1, wusbhc->dev, "(wusbhc %p) %u, buffer:\n", wusbhc, (int)size);
|
||||
d_dump(1, wusbhc->dev, _buf, size);
|
||||
return size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_rh_status_data);
|
||||
|
||||
/*
|
||||
* Return the hub's desciptor
|
||||
*
|
||||
* NOTE: almost cut and paste from ehci-hub.c
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked
|
||||
*/
|
||||
static int wusbhc_rh_get_hub_descr(struct wusbhc *wusbhc, u16 wValue,
|
||||
u16 wIndex,
|
||||
struct usb_hub_descriptor *descr,
|
||||
u16 wLength)
|
||||
{
|
||||
u16 temp = 1 + (wusbhc->ports_max / 8);
|
||||
u8 length = 7 + 2 * temp;
|
||||
|
||||
if (wLength < length)
|
||||
return -ENOSPC;
|
||||
descr->bDescLength = 7 + 2 * temp;
|
||||
descr->bDescriptorType = 0x29; /* HUB type */
|
||||
descr->bNbrPorts = wusbhc->ports_max;
|
||||
descr->wHubCharacteristics = cpu_to_le16(
|
||||
0x00 /* All ports power at once */
|
||||
| 0x00 /* not part of compound device */
|
||||
| 0x10 /* No overcurrent protection */
|
||||
| 0x00 /* 8 FS think time FIXME ?? */
|
||||
| 0x00); /* No port indicators */
|
||||
descr->bPwrOn2PwrGood = 0;
|
||||
descr->bHubContrCurrent = 0;
|
||||
/* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
|
||||
memset(&descr->bitmap[0], 0, temp);
|
||||
memset(&descr->bitmap[temp], 0xff, temp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear a hub feature
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
|
||||
*
|
||||
* Nothing to do, so no locking needed ;)
|
||||
*/
|
||||
static int wusbhc_rh_clear_hub_feat(struct wusbhc *wusbhc, u16 feature)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = wusbhc->dev;
|
||||
|
||||
d_fnstart(4, dev, "(%p, feature 0x%04u)\n", wusbhc, feature);
|
||||
switch (feature) {
|
||||
case C_HUB_LOCAL_POWER:
|
||||
/* FIXME: maybe plug bit 0 to the power input status,
|
||||
* if any?
|
||||
* see wusbhc_rh_get_hub_status() */
|
||||
case C_HUB_OVER_CURRENT:
|
||||
result = 0;
|
||||
break;
|
||||
default:
|
||||
result = -EPIPE;
|
||||
}
|
||||
d_fnend(4, dev, "(%p, feature 0x%04u), %d\n", wusbhc, feature, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return hub status (it is always zero...)
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
|
||||
*
|
||||
* Nothing to do, so no locking needed ;)
|
||||
*/
|
||||
static int wusbhc_rh_get_hub_status(struct wusbhc *wusbhc, u32 *buf,
|
||||
u16 wLength)
|
||||
{
|
||||
/* FIXME: maybe plug bit 0 to the power input status (if any)? */
|
||||
*buf = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set a port feature
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
|
||||
*/
|
||||
static int wusbhc_rh_set_port_feat(struct wusbhc *wusbhc, u16 feature,
|
||||
u8 selector, u8 port_idx)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
struct device *dev = wusbhc->dev;
|
||||
|
||||
d_fnstart(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d)\n",
|
||||
feature, selector, port_idx);
|
||||
|
||||
if (port_idx > wusbhc->ports_max)
|
||||
goto error;
|
||||
|
||||
switch (feature) {
|
||||
/* According to USB2.0[11.24.2.13]p2, these features
|
||||
* are not required to be implemented. */
|
||||
case USB_PORT_FEAT_C_OVER_CURRENT:
|
||||
case USB_PORT_FEAT_C_ENABLE:
|
||||
case USB_PORT_FEAT_C_SUSPEND:
|
||||
case USB_PORT_FEAT_C_CONNECTION:
|
||||
case USB_PORT_FEAT_C_RESET:
|
||||
result = 0;
|
||||
break;
|
||||
|
||||
case USB_PORT_FEAT_POWER:
|
||||
/* No such thing, but we fake it works */
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
wusb_port_by_idx(wusbhc, port_idx)->status |= USB_PORT_STAT_POWER;
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
result = 0;
|
||||
break;
|
||||
case USB_PORT_FEAT_RESET:
|
||||
result = wusbhc_rh_port_reset(wusbhc, port_idx);
|
||||
break;
|
||||
case USB_PORT_FEAT_ENABLE:
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
dev_err(dev, "(port_idx %d) set feat %d/%d UNIMPLEMENTED\n",
|
||||
port_idx, feature, selector);
|
||||
result = -ENOSYS;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "(port_idx %d) set feat %d/%d UNKNOWN\n",
|
||||
port_idx, feature, selector);
|
||||
result = -EPIPE;
|
||||
break;
|
||||
}
|
||||
error:
|
||||
d_fnend(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d) = %d\n",
|
||||
feature, selector, port_idx, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear a port feature...
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
|
||||
*/
|
||||
static int wusbhc_rh_clear_port_feat(struct wusbhc *wusbhc, u16 feature,
|
||||
u8 selector, u8 port_idx)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
struct device *dev = wusbhc->dev;
|
||||
|
||||
d_fnstart(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d)\n",
|
||||
wusbhc, feature, selector, port_idx);
|
||||
|
||||
if (port_idx > wusbhc->ports_max)
|
||||
goto error;
|
||||
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
result = 0;
|
||||
switch (feature) {
|
||||
case USB_PORT_FEAT_POWER: /* fake port always on */
|
||||
/* According to USB2.0[11.24.2.7.1.4], no need to implement? */
|
||||
case USB_PORT_FEAT_C_OVER_CURRENT:
|
||||
break;
|
||||
case USB_PORT_FEAT_C_RESET:
|
||||
wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_RESET;
|
||||
break;
|
||||
case USB_PORT_FEAT_C_CONNECTION:
|
||||
wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_CONNECTION;
|
||||
break;
|
||||
case USB_PORT_FEAT_ENABLE:
|
||||
__wusbhc_dev_disable(wusbhc, port_idx);
|
||||
break;
|
||||
case USB_PORT_FEAT_C_ENABLE:
|
||||
wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_ENABLE;
|
||||
break;
|
||||
case USB_PORT_FEAT_SUSPEND:
|
||||
case USB_PORT_FEAT_C_SUSPEND:
|
||||
case 0xffff: /* ??? FIXME */
|
||||
dev_err(dev, "(port_idx %d) Clear feat %d/%d UNIMPLEMENTED\n",
|
||||
port_idx, feature, selector);
|
||||
/* dump_stack(); */
|
||||
result = -ENOSYS;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "(port_idx %d) Clear feat %d/%d UNKNOWN\n",
|
||||
port_idx, feature, selector);
|
||||
result = -EPIPE;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
error:
|
||||
d_fnend(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d) = "
|
||||
"%d\n", wusbhc, feature, selector, port_idx, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the port's status
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
|
||||
*/
|
||||
static int wusbhc_rh_get_port_status(struct wusbhc *wusbhc, u16 port_idx,
|
||||
u32 *_buf, u16 wLength)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
u16 *buf = (u16 *) _buf;
|
||||
|
||||
d_fnstart(1, wusbhc->dev, "(wusbhc %p port_idx %u wLength %u)\n",
|
||||
wusbhc, port_idx, wLength);
|
||||
if (port_idx > wusbhc->ports_max)
|
||||
goto error;
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
buf[0] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->status);
|
||||
buf[1] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->change);
|
||||
result = 0;
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
error:
|
||||
d_fnend(1, wusbhc->dev, "(wusbhc %p) = %d, buffer:\n", wusbhc, result);
|
||||
d_dump(1, wusbhc->dev, _buf, wLength);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Entry point for Root Hub operations
|
||||
*
|
||||
* @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
|
||||
*/
|
||||
int wusbhc_rh_control(struct usb_hcd *usb_hcd, u16 reqntype, u16 wValue,
|
||||
u16 wIndex, char *buf, u16 wLength)
|
||||
{
|
||||
int result = -ENOSYS;
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
|
||||
switch (reqntype) {
|
||||
case GetHubDescriptor:
|
||||
result = wusbhc_rh_get_hub_descr(
|
||||
wusbhc, wValue, wIndex,
|
||||
(struct usb_hub_descriptor *) buf, wLength);
|
||||
break;
|
||||
case ClearHubFeature:
|
||||
result = wusbhc_rh_clear_hub_feat(wusbhc, wValue);
|
||||
break;
|
||||
case GetHubStatus:
|
||||
result = wusbhc_rh_get_hub_status(wusbhc, (u32 *)buf, wLength);
|
||||
break;
|
||||
|
||||
case SetPortFeature:
|
||||
result = wusbhc_rh_set_port_feat(wusbhc, wValue, wIndex >> 8,
|
||||
(wIndex & 0xff) - 1);
|
||||
break;
|
||||
case ClearPortFeature:
|
||||
result = wusbhc_rh_clear_port_feat(wusbhc, wValue, wIndex >> 8,
|
||||
(wIndex & 0xff) - 1);
|
||||
break;
|
||||
case GetPortStatus:
|
||||
result = wusbhc_rh_get_port_status(wusbhc, wIndex - 1,
|
||||
(u32 *)buf, wLength);
|
||||
break;
|
||||
|
||||
case SetHubFeature:
|
||||
default:
|
||||
dev_err(wusbhc->dev, "%s (%p [%p], %x, %x, %x, %p, %x) "
|
||||
"UNIMPLEMENTED\n", __func__, usb_hcd, wusbhc, reqntype,
|
||||
wValue, wIndex, buf, wLength);
|
||||
/* dump_stack(); */
|
||||
result = -ENOSYS;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_rh_control);
|
||||
|
||||
int wusbhc_rh_suspend(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__,
|
||||
usb_hcd, wusbhc);
|
||||
/* dump_stack(); */
|
||||
return -ENOSYS;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_rh_suspend);
|
||||
|
||||
int wusbhc_rh_resume(struct usb_hcd *usb_hcd)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__,
|
||||
usb_hcd, wusbhc);
|
||||
/* dump_stack(); */
|
||||
return -ENOSYS;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_rh_resume);
|
||||
|
||||
int wusbhc_rh_start_port_reset(struct usb_hcd *usb_hcd, unsigned port_idx)
|
||||
{
|
||||
struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
dev_err(wusbhc->dev, "%s (%p [%p], port_idx %u) UNIMPLEMENTED\n",
|
||||
__func__, usb_hcd, wusbhc, port_idx);
|
||||
WARN_ON(1);
|
||||
return -ENOSYS;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_rh_start_port_reset);
|
||||
|
||||
static void wusb_port_init(struct wusb_port *port)
|
||||
{
|
||||
port->status |= USB_PORT_STAT_HIGH_SPEED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Alloc fake port specific fields and status.
|
||||
*/
|
||||
int wusbhc_rh_create(struct wusbhc *wusbhc)
|
||||
{
|
||||
int result = -ENOMEM;
|
||||
size_t port_size, itr;
|
||||
port_size = wusbhc->ports_max * sizeof(wusbhc->port[0]);
|
||||
wusbhc->port = kzalloc(port_size, GFP_KERNEL);
|
||||
if (wusbhc->port == NULL)
|
||||
goto error_port_alloc;
|
||||
for (itr = 0; itr < wusbhc->ports_max; itr++)
|
||||
wusb_port_init(&wusbhc->port[itr]);
|
||||
result = 0;
|
||||
error_port_alloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
void wusbhc_rh_destroy(struct wusbhc *wusbhc)
|
||||
{
|
||||
kfree(wusbhc->port);
|
||||
}
|
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
* Wireless USB Host Controller
|
||||
* Security support: encryption enablement, etc
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
#include <linux/types.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/random.h>
|
||||
#include "wusbhc.h"
|
||||
|
||||
/*
|
||||
* DEBUG & SECURITY WARNING!!!!
|
||||
*
|
||||
* If you enable this past 1, the debug code will weaken the
|
||||
* cryptographic safety of the system (on purpose, for debugging).
|
||||
*
|
||||
* Weaken means:
|
||||
* we print secret keys and intermediate values all the way,
|
||||
*/
|
||||
#undef D_LOCAL
|
||||
#define D_LOCAL 2
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
static void wusbhc_set_gtk_callback(struct urb *urb);
|
||||
static void wusbhc_gtk_rekey_done_work(struct work_struct *work);
|
||||
|
||||
int wusbhc_sec_create(struct wusbhc *wusbhc)
|
||||
{
|
||||
wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data);
|
||||
wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY;
|
||||
wusbhc->gtk.descr.bReserved = 0;
|
||||
|
||||
wusbhc->gtk_index = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK,
|
||||
WUSB_KEY_INDEX_ORIGINATOR_HOST);
|
||||
|
||||
INIT_WORK(&wusbhc->gtk_rekey_done_work, wusbhc_gtk_rekey_done_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Called when the HC is destroyed */
|
||||
void wusbhc_sec_destroy(struct wusbhc *wusbhc)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* wusbhc_next_tkid - generate a new, currently unused, TKID
|
||||
* @wusbhc: the WUSB host controller
|
||||
* @wusb_dev: the device whose PTK the TKID is for
|
||||
* (or NULL for a TKID for a GTK)
|
||||
*
|
||||
* The generated TKID consist of two parts: the device's authenicated
|
||||
* address (or 0 or a GTK); and an incrementing number. This ensures
|
||||
* that TKIDs cannot be shared between devices and by the time the
|
||||
* incrementing number wraps around the older TKIDs will no longer be
|
||||
* in use (a maximum of two keys may be active at any one time).
|
||||
*/
|
||||
static u32 wusbhc_next_tkid(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
|
||||
{
|
||||
u32 *tkid;
|
||||
u32 addr;
|
||||
|
||||
if (wusb_dev == NULL) {
|
||||
tkid = &wusbhc->gtk_tkid;
|
||||
addr = 0;
|
||||
} else {
|
||||
tkid = &wusb_port_by_idx(wusbhc, wusb_dev->port_idx)->ptk_tkid;
|
||||
addr = wusb_dev->addr & 0x7f;
|
||||
}
|
||||
|
||||
*tkid = (addr << 8) | ((*tkid + 1) & 0xff);
|
||||
|
||||
return *tkid;
|
||||
}
|
||||
|
||||
static void wusbhc_generate_gtk(struct wusbhc *wusbhc)
|
||||
{
|
||||
const size_t key_size = sizeof(wusbhc->gtk.data);
|
||||
u32 tkid;
|
||||
|
||||
tkid = wusbhc_next_tkid(wusbhc, NULL);
|
||||
|
||||
wusbhc->gtk.descr.tTKID[0] = (tkid >> 0) & 0xff;
|
||||
wusbhc->gtk.descr.tTKID[1] = (tkid >> 8) & 0xff;
|
||||
wusbhc->gtk.descr.tTKID[2] = (tkid >> 16) & 0xff;
|
||||
|
||||
get_random_bytes(wusbhc->gtk.descr.bKeyData, key_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* wusbhc_sec_start - start the security management process
|
||||
* @wusbhc: the WUSB host controller
|
||||
*
|
||||
* Generate and set an initial GTK on the host controller.
|
||||
*
|
||||
* Called when the HC is started.
|
||||
*/
|
||||
int wusbhc_sec_start(struct wusbhc *wusbhc)
|
||||
{
|
||||
const size_t key_size = sizeof(wusbhc->gtk.data);
|
||||
int result;
|
||||
|
||||
wusbhc_generate_gtk(wusbhc);
|
||||
|
||||
result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid,
|
||||
&wusbhc->gtk.descr.bKeyData, key_size);
|
||||
if (result < 0)
|
||||
dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n",
|
||||
result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* wusbhc_sec_stop - stop the security management process
|
||||
* @wusbhc: the WUSB host controller
|
||||
*
|
||||
* Wait for any pending GTK rekeys to stop.
|
||||
*/
|
||||
void wusbhc_sec_stop(struct wusbhc *wusbhc)
|
||||
{
|
||||
cancel_work_sync(&wusbhc->gtk_rekey_done_work);
|
||||
}
|
||||
|
||||
|
||||
/** @returns encryption type name */
|
||||
const char *wusb_et_name(u8 x)
|
||||
{
|
||||
switch (x) {
|
||||
case USB_ENC_TYPE_UNSECURE: return "unsecure";
|
||||
case USB_ENC_TYPE_WIRED: return "wired";
|
||||
case USB_ENC_TYPE_CCM_1: return "CCM-1";
|
||||
case USB_ENC_TYPE_RSA_1: return "RSA-1";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusb_et_name);
|
||||
|
||||
/*
|
||||
* Set the device encryption method
|
||||
*
|
||||
* We tell the device which encryption method to use; we do this when
|
||||
* setting up the device's security.
|
||||
*/
|
||||
static int wusb_dev_set_encryption(struct usb_device *usb_dev, int value)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &usb_dev->dev;
|
||||
struct wusb_dev *wusb_dev = usb_dev->wusb_dev;
|
||||
|
||||
if (value) {
|
||||
value = wusb_dev->ccm1_etd.bEncryptionValue;
|
||||
} else {
|
||||
/* FIXME: should be wusb_dev->etd[UNSECURE].bEncryptionValue */
|
||||
value = 0;
|
||||
}
|
||||
/* Set device's */
|
||||
result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
|
||||
USB_REQ_SET_ENCRYPTION,
|
||||
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
|
||||
value, 0, NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0)
|
||||
dev_err(dev, "Can't set device's WUSB encryption to "
|
||||
"%s (value %d): %d\n",
|
||||
wusb_et_name(wusb_dev->ccm1_etd.bEncryptionType),
|
||||
wusb_dev->ccm1_etd.bEncryptionValue, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the GTK to be used by a device.
|
||||
*
|
||||
* The device must be authenticated.
|
||||
*/
|
||||
static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
|
||||
{
|
||||
struct usb_device *usb_dev = wusb_dev->usb_dev;
|
||||
|
||||
return usb_control_msg(
|
||||
usb_dev, usb_sndctrlpipe(usb_dev, 0),
|
||||
USB_REQ_SET_DESCRIPTOR,
|
||||
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
|
||||
USB_DT_KEY << 8 | wusbhc->gtk_index, 0,
|
||||
&wusbhc->gtk.descr, wusbhc->gtk.descr.bLength,
|
||||
1000);
|
||||
}
|
||||
|
||||
|
||||
/* FIXME: prototype for adding security */
|
||||
int wusb_dev_sec_add(struct wusbhc *wusbhc,
|
||||
struct usb_device *usb_dev, struct wusb_dev *wusb_dev)
|
||||
{
|
||||
int result, bytes, secd_size;
|
||||
struct device *dev = &usb_dev->dev;
|
||||
struct usb_security_descriptor secd;
|
||||
const struct usb_encryption_descriptor *etd, *ccm1_etd = NULL;
|
||||
void *secd_buf;
|
||||
const void *itr, *top;
|
||||
char buf[64];
|
||||
|
||||
d_fnstart(3, dev, "(usb_dev %p, wusb_dev %p)\n", usb_dev, wusb_dev);
|
||||
result = usb_get_descriptor(usb_dev, USB_DT_SECURITY,
|
||||
0, &secd, sizeof(secd));
|
||||
if (result < sizeof(secd)) {
|
||||
dev_err(dev, "Can't read security descriptor or "
|
||||
"not enough data: %d\n", result);
|
||||
goto error_secd;
|
||||
}
|
||||
secd_size = le16_to_cpu(secd.wTotalLength);
|
||||
d_printf(5, dev, "got %d bytes of sec descriptor, total is %d\n",
|
||||
result, secd_size);
|
||||
secd_buf = kmalloc(secd_size, GFP_KERNEL);
|
||||
if (secd_buf == NULL) {
|
||||
dev_err(dev, "Can't allocate space for security descriptors\n");
|
||||
goto error_secd_alloc;
|
||||
}
|
||||
result = usb_get_descriptor(usb_dev, USB_DT_SECURITY,
|
||||
0, secd_buf, secd_size);
|
||||
if (result < secd_size) {
|
||||
dev_err(dev, "Can't read security descriptor or "
|
||||
"not enough data: %d\n", result);
|
||||
goto error_secd_all;
|
||||
}
|
||||
d_printf(5, dev, "got %d bytes of sec descriptors\n", result);
|
||||
bytes = 0;
|
||||
itr = secd_buf + sizeof(secd);
|
||||
top = secd_buf + result;
|
||||
while (itr < top) {
|
||||
etd = itr;
|
||||
if (top - itr < sizeof(*etd)) {
|
||||
dev_err(dev, "BUG: bad device security descriptor; "
|
||||
"not enough data (%zu vs %zu bytes left)\n",
|
||||
top - itr, sizeof(*etd));
|
||||
break;
|
||||
}
|
||||
if (etd->bLength < sizeof(*etd)) {
|
||||
dev_err(dev, "BUG: bad device encryption descriptor; "
|
||||
"descriptor is too short "
|
||||
"(%u vs %zu needed)\n",
|
||||
etd->bLength, sizeof(*etd));
|
||||
break;
|
||||
}
|
||||
itr += etd->bLength;
|
||||
bytes += snprintf(buf + bytes, sizeof(buf) - bytes,
|
||||
"%s (0x%02x/%02x) ",
|
||||
wusb_et_name(etd->bEncryptionType),
|
||||
etd->bEncryptionValue, etd->bAuthKeyIndex);
|
||||
if (etd->bEncryptionType == USB_ENC_TYPE_CCM_1)
|
||||
ccm1_etd = etd;
|
||||
}
|
||||
/* This code only supports CCM1 as of now. */
|
||||
/* FIXME: user has to choose which sec mode to use?
|
||||
* In theory we want CCM */
|
||||
if (ccm1_etd == NULL) {
|
||||
dev_err(dev, "WUSB device doesn't support CCM1 encryption, "
|
||||
"can't use!\n");
|
||||
result = -EINVAL;
|
||||
goto error_no_ccm1;
|
||||
}
|
||||
wusb_dev->ccm1_etd = *ccm1_etd;
|
||||
dev_info(dev, "supported encryption: %s; using %s (0x%02x/%02x)\n",
|
||||
buf, wusb_et_name(ccm1_etd->bEncryptionType),
|
||||
ccm1_etd->bEncryptionValue, ccm1_etd->bAuthKeyIndex);
|
||||
result = 0;
|
||||
kfree(secd_buf);
|
||||
out:
|
||||
d_fnend(3, dev, "(usb_dev %p, wusb_dev %p) = %d\n",
|
||||
usb_dev, wusb_dev, result);
|
||||
return result;
|
||||
|
||||
|
||||
error_no_ccm1:
|
||||
error_secd_all:
|
||||
kfree(secd_buf);
|
||||
error_secd_alloc:
|
||||
error_secd:
|
||||
goto out;
|
||||
}
|
||||
|
||||
void wusb_dev_sec_rm(struct wusb_dev *wusb_dev)
|
||||
{
|
||||
/* Nothing so far */
|
||||
}
|
||||
|
||||
static void hs_printk(unsigned level, struct device *dev,
|
||||
struct usb_handshake *hs)
|
||||
{
|
||||
d_printf(level, dev,
|
||||
" bMessageNumber: %u\n"
|
||||
" bStatus: %u\n"
|
||||
" tTKID: %02x %02x %02x\n"
|
||||
" CDID: %02x %02x %02x %02x %02x %02x %02x %02x\n"
|
||||
" %02x %02x %02x %02x %02x %02x %02x %02x\n"
|
||||
" nonce: %02x %02x %02x %02x %02x %02x %02x %02x\n"
|
||||
" %02x %02x %02x %02x %02x %02x %02x %02x\n"
|
||||
" MIC: %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
hs->bMessageNumber, hs->bStatus,
|
||||
hs->tTKID[2], hs->tTKID[1], hs->tTKID[0],
|
||||
hs->CDID[0], hs->CDID[1], hs->CDID[2], hs->CDID[3],
|
||||
hs->CDID[4], hs->CDID[5], hs->CDID[6], hs->CDID[7],
|
||||
hs->CDID[8], hs->CDID[9], hs->CDID[10], hs->CDID[11],
|
||||
hs->CDID[12], hs->CDID[13], hs->CDID[14], hs->CDID[15],
|
||||
hs->nonce[0], hs->nonce[1], hs->nonce[2], hs->nonce[3],
|
||||
hs->nonce[4], hs->nonce[5], hs->nonce[6], hs->nonce[7],
|
||||
hs->nonce[8], hs->nonce[9], hs->nonce[10], hs->nonce[11],
|
||||
hs->nonce[12], hs->nonce[13], hs->nonce[14], hs->nonce[15],
|
||||
hs->MIC[0], hs->MIC[1], hs->MIC[2], hs->MIC[3],
|
||||
hs->MIC[4], hs->MIC[5], hs->MIC[6], hs->MIC[7]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the address of an unauthenticated WUSB device
|
||||
*
|
||||
* Once we have successfully authenticated, we take it to addr0 state
|
||||
* and then to a normal address.
|
||||
*
|
||||
* Before the device's address (as known by it) was usb_dev->devnum |
|
||||
* 0x80 (unauthenticated address). With this we update it to usb_dev->devnum.
|
||||
*/
|
||||
static int wusb_dev_update_address(struct wusbhc *wusbhc,
|
||||
struct wusb_dev *wusb_dev)
|
||||
{
|
||||
int result = -ENOMEM;
|
||||
struct usb_device *usb_dev = wusb_dev->usb_dev;
|
||||
struct device *dev = &usb_dev->dev;
|
||||
u8 new_address = wusb_dev->addr & 0x7F;
|
||||
|
||||
/* Set address 0 */
|
||||
result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
|
||||
USB_REQ_SET_ADDRESS, 0,
|
||||
0, 0, NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "auth failed: can't set address 0: %d\n",
|
||||
result);
|
||||
goto error_addr0;
|
||||
}
|
||||
result = wusb_set_dev_addr(wusbhc, wusb_dev, 0);
|
||||
if (result < 0)
|
||||
goto error_addr0;
|
||||
usb_ep0_reinit(usb_dev);
|
||||
|
||||
/* Set new (authenticated) address. */
|
||||
result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
|
||||
USB_REQ_SET_ADDRESS, 0,
|
||||
new_address, 0, NULL, 0,
|
||||
1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "auth failed: can't set address %u: %d\n",
|
||||
new_address, result);
|
||||
goto error_addr;
|
||||
}
|
||||
result = wusb_set_dev_addr(wusbhc, wusb_dev, new_address);
|
||||
if (result < 0)
|
||||
goto error_addr;
|
||||
usb_ep0_reinit(usb_dev);
|
||||
usb_dev->authenticated = 1;
|
||||
error_addr:
|
||||
error_addr0:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
*/
|
||||
/* FIXME: split and cleanup */
|
||||
int wusb_dev_4way_handshake(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev,
|
||||
struct wusb_ckhdid *ck)
|
||||
{
|
||||
int result = -ENOMEM;
|
||||
struct usb_device *usb_dev = wusb_dev->usb_dev;
|
||||
struct device *dev = &usb_dev->dev;
|
||||
u32 tkid;
|
||||
__le32 tkid_le;
|
||||
struct usb_handshake *hs;
|
||||
struct aes_ccm_nonce ccm_n;
|
||||
u8 mic[8];
|
||||
struct wusb_keydvt_in keydvt_in;
|
||||
struct wusb_keydvt_out keydvt_out;
|
||||
|
||||
hs = kzalloc(3*sizeof(hs[0]), GFP_KERNEL);
|
||||
if (hs == NULL) {
|
||||
dev_err(dev, "can't allocate handshake data\n");
|
||||
goto error_kzalloc;
|
||||
}
|
||||
|
||||
/* We need to turn encryption before beginning the 4way
|
||||
* hshake (WUSB1.0[.3.2.2]) */
|
||||
result = wusb_dev_set_encryption(usb_dev, 1);
|
||||
if (result < 0)
|
||||
goto error_dev_set_encryption;
|
||||
|
||||
tkid = wusbhc_next_tkid(wusbhc, wusb_dev);
|
||||
tkid_le = cpu_to_le32(tkid);
|
||||
|
||||
hs[0].bMessageNumber = 1;
|
||||
hs[0].bStatus = 0;
|
||||
memcpy(hs[0].tTKID, &tkid_le, sizeof(hs[0].tTKID));
|
||||
hs[0].bReserved = 0;
|
||||
memcpy(hs[0].CDID, &wusb_dev->cdid, sizeof(hs[0].CDID));
|
||||
get_random_bytes(&hs[0].nonce, sizeof(hs[0].nonce));
|
||||
memset(hs[0].MIC, 0, sizeof(hs[0].MIC)); /* Per WUSB1.0[T7-22] */
|
||||
|
||||
d_printf(1, dev, "I: sending hs1:\n");
|
||||
hs_printk(2, dev, &hs[0]);
|
||||
|
||||
result = usb_control_msg(
|
||||
usb_dev, usb_sndctrlpipe(usb_dev, 0),
|
||||
USB_REQ_SET_HANDSHAKE,
|
||||
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
|
||||
1, 0, &hs[0], sizeof(hs[0]), 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Handshake1: request failed: %d\n", result);
|
||||
goto error_hs1;
|
||||
}
|
||||
|
||||
/* Handshake 2, from the device -- need to verify fields */
|
||||
result = usb_control_msg(
|
||||
usb_dev, usb_rcvctrlpipe(usb_dev, 0),
|
||||
USB_REQ_GET_HANDSHAKE,
|
||||
USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
|
||||
2, 0, &hs[1], sizeof(hs[1]), 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Handshake2: request failed: %d\n", result);
|
||||
goto error_hs2;
|
||||
}
|
||||
d_printf(1, dev, "got HS2:\n");
|
||||
hs_printk(2, dev, &hs[1]);
|
||||
|
||||
result = -EINVAL;
|
||||
if (hs[1].bMessageNumber != 2) {
|
||||
dev_err(dev, "Handshake2 failed: bad message number %u\n",
|
||||
hs[1].bMessageNumber);
|
||||
goto error_hs2;
|
||||
}
|
||||
if (hs[1].bStatus != 0) {
|
||||
dev_err(dev, "Handshake2 failed: bad status %u\n",
|
||||
hs[1].bStatus);
|
||||
goto error_hs2;
|
||||
}
|
||||
if (memcmp(hs[0].tTKID, hs[1].tTKID, sizeof(hs[0].tTKID))) {
|
||||
dev_err(dev, "Handshake2 failed: TKID mismatch "
|
||||
"(#1 0x%02x%02x%02x vs #2 0x%02x%02x%02x)\n",
|
||||
hs[0].tTKID[0], hs[0].tTKID[1], hs[0].tTKID[2],
|
||||
hs[1].tTKID[0], hs[1].tTKID[1], hs[1].tTKID[2]);
|
||||
goto error_hs2;
|
||||
}
|
||||
if (memcmp(hs[0].CDID, hs[1].CDID, sizeof(hs[0].CDID))) {
|
||||
dev_err(dev, "Handshake2 failed: CDID mismatch\n");
|
||||
goto error_hs2;
|
||||
}
|
||||
|
||||
/* Setup the CCM nonce */
|
||||
memset(&ccm_n.sfn, 0, sizeof(ccm_n.sfn)); /* Per WUSB1.0[6.5.2] */
|
||||
memcpy(ccm_n.tkid, &tkid_le, sizeof(ccm_n.tkid));
|
||||
ccm_n.src_addr = wusbhc->uwb_rc->uwb_dev.dev_addr;
|
||||
ccm_n.dest_addr.data[0] = wusb_dev->addr;
|
||||
ccm_n.dest_addr.data[1] = 0;
|
||||
|
||||
/* Derive the KCK and PTK from CK, the CCM, H and D nonces */
|
||||
memcpy(keydvt_in.hnonce, hs[0].nonce, sizeof(keydvt_in.hnonce));
|
||||
memcpy(keydvt_in.dnonce, hs[1].nonce, sizeof(keydvt_in.dnonce));
|
||||
result = wusb_key_derive(&keydvt_out, ck->data, &ccm_n, &keydvt_in);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Handshake2 failed: cannot derive keys: %d\n",
|
||||
result);
|
||||
goto error_hs2;
|
||||
}
|
||||
d_printf(2, dev, "KCK:\n");
|
||||
d_dump(2, dev, keydvt_out.kck, sizeof(keydvt_out.kck));
|
||||
d_printf(2, dev, "PTK:\n");
|
||||
d_dump(2, dev, keydvt_out.ptk, sizeof(keydvt_out.ptk));
|
||||
|
||||
/* Compute MIC and verify it */
|
||||
result = wusb_oob_mic(mic, keydvt_out.kck, &ccm_n, &hs[1]);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Handshake2 failed: cannot compute MIC: %d\n",
|
||||
result);
|
||||
goto error_hs2;
|
||||
}
|
||||
|
||||
d_printf(2, dev, "MIC:\n");
|
||||
d_dump(2, dev, mic, sizeof(mic));
|
||||
if (memcmp(hs[1].MIC, mic, sizeof(hs[1].MIC))) {
|
||||
dev_err(dev, "Handshake2 failed: MIC mismatch\n");
|
||||
goto error_hs2;
|
||||
}
|
||||
|
||||
/* Send Handshake3 */
|
||||
hs[2].bMessageNumber = 3;
|
||||
hs[2].bStatus = 0;
|
||||
memcpy(hs[2].tTKID, &tkid_le, sizeof(hs[2].tTKID));
|
||||
hs[2].bReserved = 0;
|
||||
memcpy(hs[2].CDID, &wusb_dev->cdid, sizeof(hs[2].CDID));
|
||||
memcpy(hs[2].nonce, hs[0].nonce, sizeof(hs[2].nonce));
|
||||
result = wusb_oob_mic(hs[2].MIC, keydvt_out.kck, &ccm_n, &hs[2]);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Handshake3 failed: cannot compute MIC: %d\n",
|
||||
result);
|
||||
goto error_hs2;
|
||||
}
|
||||
|
||||
d_printf(1, dev, "I: sending hs3:\n");
|
||||
hs_printk(2, dev, &hs[2]);
|
||||
|
||||
result = usb_control_msg(
|
||||
usb_dev, usb_sndctrlpipe(usb_dev, 0),
|
||||
USB_REQ_SET_HANDSHAKE,
|
||||
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
|
||||
3, 0, &hs[2], sizeof(hs[2]), 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Handshake3: request failed: %d\n", result);
|
||||
goto error_hs3;
|
||||
}
|
||||
|
||||
d_printf(1, dev, "I: turning on encryption on host for device\n");
|
||||
d_dump(2, dev, keydvt_out.ptk, sizeof(keydvt_out.ptk));
|
||||
result = wusbhc->set_ptk(wusbhc, wusb_dev->port_idx, tkid,
|
||||
keydvt_out.ptk, sizeof(keydvt_out.ptk));
|
||||
if (result < 0)
|
||||
goto error_wusbhc_set_ptk;
|
||||
|
||||
d_printf(1, dev, "I: setting a GTK\n");
|
||||
result = wusb_dev_set_gtk(wusbhc, wusb_dev);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Set GTK for device: request failed: %d\n",
|
||||
result);
|
||||
goto error_wusbhc_set_gtk;
|
||||
}
|
||||
|
||||
/* Update the device's address from unauth to auth */
|
||||
if (usb_dev->authenticated == 0) {
|
||||
d_printf(1, dev, "I: updating addres to auth from non-auth\n");
|
||||
result = wusb_dev_update_address(wusbhc, wusb_dev);
|
||||
if (result < 0)
|
||||
goto error_dev_update_address;
|
||||
}
|
||||
result = 0;
|
||||
d_printf(1, dev, "I: 4way handshke done, device authenticated\n");
|
||||
|
||||
error_dev_update_address:
|
||||
error_wusbhc_set_gtk:
|
||||
error_wusbhc_set_ptk:
|
||||
error_hs3:
|
||||
error_hs2:
|
||||
error_hs1:
|
||||
memset(hs, 0, 3*sizeof(hs[0]));
|
||||
memset(&keydvt_out, 0, sizeof(keydvt_out));
|
||||
memset(&keydvt_in, 0, sizeof(keydvt_in));
|
||||
memset(&ccm_n, 0, sizeof(ccm_n));
|
||||
memset(mic, 0, sizeof(mic));
|
||||
if (result < 0) {
|
||||
/* error path */
|
||||
wusb_dev_set_encryption(usb_dev, 0);
|
||||
}
|
||||
error_dev_set_encryption:
|
||||
kfree(hs);
|
||||
error_kzalloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Once all connected and authenticated devices have received the new
|
||||
* GTK, switch the host to using it.
|
||||
*/
|
||||
static void wusbhc_gtk_rekey_done_work(struct work_struct *work)
|
||||
{
|
||||
struct wusbhc *wusbhc = container_of(work, struct wusbhc, gtk_rekey_done_work);
|
||||
size_t key_size = sizeof(wusbhc->gtk.data);
|
||||
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
|
||||
if (--wusbhc->pending_set_gtks == 0)
|
||||
wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size);
|
||||
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
}
|
||||
|
||||
static void wusbhc_set_gtk_callback(struct urb *urb)
|
||||
{
|
||||
struct wusbhc *wusbhc = urb->context;
|
||||
|
||||
queue_work(wusbd, &wusbhc->gtk_rekey_done_work);
|
||||
}
|
||||
|
||||
/**
|
||||
* wusbhc_gtk_rekey - generate and distribute a new GTK
|
||||
* @wusbhc: the WUSB host controller
|
||||
*
|
||||
* Generate a new GTK and distribute it to all connected and
|
||||
* authenticated devices. When all devices have the new GTK, the host
|
||||
* starts using it.
|
||||
*
|
||||
* This must be called after every device disconnect (see [WUSB]
|
||||
* section 6.2.11.2).
|
||||
*/
|
||||
void wusbhc_gtk_rekey(struct wusbhc *wusbhc)
|
||||
{
|
||||
static const size_t key_size = sizeof(wusbhc->gtk.data);
|
||||
int p;
|
||||
|
||||
wusbhc_generate_gtk(wusbhc);
|
||||
|
||||
for (p = 0; p < wusbhc->ports_max; p++) {
|
||||
struct wusb_dev *wusb_dev;
|
||||
|
||||
wusb_dev = wusbhc->port[p].wusb_dev;
|
||||
if (!wusb_dev || !wusb_dev->usb_dev | !wusb_dev->usb_dev->authenticated)
|
||||
continue;
|
||||
|
||||
usb_fill_control_urb(wusb_dev->set_gtk_urb, wusb_dev->usb_dev,
|
||||
usb_sndctrlpipe(wusb_dev->usb_dev, 0),
|
||||
(void *)wusb_dev->set_gtk_req,
|
||||
&wusbhc->gtk.descr, wusbhc->gtk.descr.bLength,
|
||||
wusbhc_set_gtk_callback, wusbhc);
|
||||
if (usb_submit_urb(wusb_dev->set_gtk_urb, GFP_KERNEL) == 0)
|
||||
wusbhc->pending_set_gtks++;
|
||||
}
|
||||
if (wusbhc->pending_set_gtks == 0)
|
||||
wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Wire Adapter Host Controller Driver
|
||||
* Common items to HWA and DWA based HCDs
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*/
|
||||
#include "wusbhc.h"
|
||||
#include "wa-hc.h"
|
||||
|
||||
/**
|
||||
* Assumes
|
||||
*
|
||||
* wa->usb_dev and wa->usb_iface initialized and refcounted,
|
||||
* wa->wa_descr initialized.
|
||||
*/
|
||||
int wa_create(struct wahc *wa, struct usb_interface *iface)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &iface->dev;
|
||||
|
||||
result = wa_rpipes_create(wa);
|
||||
if (result < 0)
|
||||
goto error_rpipes_create;
|
||||
/* Fill up Data Transfer EP pointers */
|
||||
wa->dti_epd = &iface->cur_altsetting->endpoint[1].desc;
|
||||
wa->dto_epd = &iface->cur_altsetting->endpoint[2].desc;
|
||||
wa->xfer_result_size = le16_to_cpu(wa->dti_epd->wMaxPacketSize);
|
||||
wa->xfer_result = kmalloc(wa->xfer_result_size, GFP_KERNEL);
|
||||
if (wa->xfer_result == NULL)
|
||||
goto error_xfer_result_alloc;
|
||||
result = wa_nep_create(wa, iface);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "WA-CDS: can't initialize notif endpoint: %d\n",
|
||||
result);
|
||||
goto error_nep_create;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error_nep_create:
|
||||
kfree(wa->xfer_result);
|
||||
error_xfer_result_alloc:
|
||||
wa_rpipes_destroy(wa);
|
||||
error_rpipes_create:
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wa_create);
|
||||
|
||||
|
||||
void __wa_destroy(struct wahc *wa)
|
||||
{
|
||||
if (wa->dti_urb) {
|
||||
usb_kill_urb(wa->dti_urb);
|
||||
usb_put_urb(wa->dti_urb);
|
||||
usb_kill_urb(wa->buf_in_urb);
|
||||
usb_put_urb(wa->buf_in_urb);
|
||||
}
|
||||
kfree(wa->xfer_result);
|
||||
wa_nep_destroy(wa);
|
||||
wa_rpipes_destroy(wa);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__wa_destroy);
|
||||
|
||||
/**
|
||||
* wa_reset_all - reset the WA device
|
||||
* @wa: the WA to be reset
|
||||
*
|
||||
* For HWAs the radio controller and all other PALs are also reset.
|
||||
*/
|
||||
void wa_reset_all(struct wahc *wa)
|
||||
{
|
||||
/* FIXME: assuming HWA. */
|
||||
wusbhc_reset_all(wa->wusb);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("Wireless USB Wire Adapter core");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
* HWA Host Controller Driver
|
||||
* Wire Adapter Control/Data Streaming Iface (WUSB1.0[8])
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This driver implements a USB Host Controller (struct usb_hcd) for a
|
||||
* Wireless USB Host Controller based on the Wireless USB 1.0
|
||||
* Host-Wire-Adapter specification (in layman terms, a USB-dongle that
|
||||
* implements a Wireless USB host).
|
||||
*
|
||||
* Check out the Design-overview.txt file in the source documentation
|
||||
* for other details on the implementation.
|
||||
*
|
||||
* Main blocks:
|
||||
*
|
||||
* driver glue with the driver API, workqueue daemon
|
||||
*
|
||||
* lc RC instance life cycle management (create, destroy...)
|
||||
*
|
||||
* hcd glue with the USB API Host Controller Interface API.
|
||||
*
|
||||
* nep Notification EndPoint managent: collect notifications
|
||||
* and queue them with the workqueue daemon.
|
||||
*
|
||||
* Handle notifications as coming from the NEP. Sends them
|
||||
* off others to their respective modules (eg: connect,
|
||||
* disconnect and reset go to devconnect).
|
||||
*
|
||||
* rpipe Remote Pipe management; rpipe is what we use to write
|
||||
* to an endpoint on a WUSB device that is connected to a
|
||||
* HWA RC.
|
||||
*
|
||||
* xfer Transfer managment -- this is all the code that gets a
|
||||
* buffer and pushes it to a device (or viceversa). *
|
||||
*
|
||||
* Some day a lot of this code will be shared between this driver and
|
||||
* the drivers for DWA (xfer, rpipe).
|
||||
*
|
||||
* All starts at driver.c:hwahc_probe(), when one of this guys is
|
||||
* connected. hwahc_disconnect() stops it.
|
||||
*
|
||||
* During operation, the main driver is devices connecting or
|
||||
* disconnecting. They cause the HWA RC to send notifications into
|
||||
* nep.c:hwahc_nep_cb() that will dispatch them to
|
||||
* notif.c:wa_notif_dispatch(). From there they will fan to cause
|
||||
* device connects, disconnects, etc.
|
||||
*
|
||||
* Note much of the activity is difficult to follow. For example a
|
||||
* device connect goes to devconnect, which will cause the "fake" root
|
||||
* hub port to show a connect and stop there. Then khubd will notice
|
||||
* and call into the rh.c:hwahc_rc_port_reset() code to authenticate
|
||||
* the device (and this might require user intervention) and enable
|
||||
* the port.
|
||||
*
|
||||
* We also have a timer workqueue going from devconnect.c that
|
||||
* schedules in hwahc_devconnect_create().
|
||||
*
|
||||
* The rest of the traffic is in the usual entry points of a USB HCD,
|
||||
* which are hooked up in driver.c:hwahc_rc_driver, and defined in
|
||||
* hcd.c.
|
||||
*/
|
||||
|
||||
#ifndef __HWAHC_INTERNAL_H__
|
||||
#define __HWAHC_INTERNAL_H__
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/usb/wusb.h>
|
||||
#include <linux/usb/wusb-wa.h>
|
||||
|
||||
struct wusbhc;
|
||||
struct wahc;
|
||||
extern void wa_urb_enqueue_run(struct work_struct *ws);
|
||||
|
||||
/**
|
||||
* RPipe instance
|
||||
*
|
||||
* @descr's fields are kept in LE, as we need to send it back and
|
||||
* forth.
|
||||
*
|
||||
* @wa is referenced when set
|
||||
*
|
||||
* @segs_available is the number of requests segments that still can
|
||||
* be submitted to the controller without overloading
|
||||
* it. It is initialized to descr->wRequests when
|
||||
* aiming.
|
||||
*
|
||||
* A rpipe supports a max of descr->wRequests at the same time; before
|
||||
* submitting seg_lock has to be taken. If segs_avail > 0, then we can
|
||||
* submit; if not, we have to queue them.
|
||||
*/
|
||||
struct wa_rpipe {
|
||||
struct kref refcnt;
|
||||
struct usb_rpipe_descriptor descr;
|
||||
struct usb_host_endpoint *ep;
|
||||
struct wahc *wa;
|
||||
spinlock_t seg_lock;
|
||||
struct list_head seg_list;
|
||||
atomic_t segs_available;
|
||||
u8 buffer[1]; /* For reads/writes on USB */
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Instance of a HWA Host Controller
|
||||
*
|
||||
* Except where a more specific lock/mutex applies or atomic, all
|
||||
* fields protected by @mutex.
|
||||
*
|
||||
* @wa_descr Can be accessed without locking because it is in
|
||||
* the same area where the device descriptors were
|
||||
* read, so it is guaranteed to exist umodified while
|
||||
* the device exists.
|
||||
*
|
||||
* Endianess has been converted to CPU's.
|
||||
*
|
||||
* @nep_* can be accessed without locking as its processing is
|
||||
* serialized; we submit a NEP URB and it comes to
|
||||
* hwahc_nep_cb(), which won't issue another URB until it is
|
||||
* done processing it.
|
||||
*
|
||||
* @xfer_list:
|
||||
*
|
||||
* List of active transfers to verify existence from a xfer id
|
||||
* gotten from the xfer result message. Can't use urb->list because
|
||||
* it goes by endpoint, and we don't know the endpoint at the time
|
||||
* when we get the xfer result message. We can't really rely on the
|
||||
* pointer (will have to change for 64 bits) as the xfer id is 32 bits.
|
||||
*
|
||||
* @xfer_delayed_list: List of transfers that need to be started
|
||||
* (with a workqueue, because they were
|
||||
* submitted from an atomic context).
|
||||
*
|
||||
* FIXME: this needs to be layered up: a wusbhc layer (for sharing
|
||||
* comonalities with WHCI), a wa layer (for sharing
|
||||
* comonalities with DWA-RC).
|
||||
*/
|
||||
struct wahc {
|
||||
struct usb_device *usb_dev;
|
||||
struct usb_interface *usb_iface;
|
||||
|
||||
/* HC to deliver notifications */
|
||||
union {
|
||||
struct wusbhc *wusb;
|
||||
struct dwahc *dwa;
|
||||
};
|
||||
|
||||
const struct usb_endpoint_descriptor *dto_epd, *dti_epd;
|
||||
const struct usb_wa_descriptor *wa_descr;
|
||||
|
||||
struct urb *nep_urb; /* Notification EndPoint [lockless] */
|
||||
struct edc nep_edc;
|
||||
void *nep_buffer;
|
||||
size_t nep_buffer_size;
|
||||
|
||||
atomic_t notifs_queued;
|
||||
|
||||
u16 rpipes;
|
||||
unsigned long *rpipe_bm; /* rpipe usage bitmap */
|
||||
spinlock_t rpipe_bm_lock; /* protect rpipe_bm */
|
||||
struct mutex rpipe_mutex; /* assigning resources to endpoints */
|
||||
|
||||
struct urb *dti_urb; /* URB for reading xfer results */
|
||||
struct urb *buf_in_urb; /* URB for reading data in */
|
||||
struct edc dti_edc; /* DTI error density counter */
|
||||
struct wa_xfer_result *xfer_result; /* real size = dti_ep maxpktsize */
|
||||
size_t xfer_result_size;
|
||||
|
||||
s32 status; /* For reading status */
|
||||
|
||||
struct list_head xfer_list;
|
||||
struct list_head xfer_delayed_list;
|
||||
spinlock_t xfer_list_lock;
|
||||
struct work_struct xfer_work;
|
||||
atomic_t xfer_id_count;
|
||||
};
|
||||
|
||||
|
||||
extern int wa_create(struct wahc *wa, struct usb_interface *iface);
|
||||
extern void __wa_destroy(struct wahc *wa);
|
||||
void wa_reset_all(struct wahc *wa);
|
||||
|
||||
|
||||
/* Miscellaneous constants */
|
||||
enum {
|
||||
/** Max number of EPROTO errors we tolerate on the NEP in a
|
||||
* period of time */
|
||||
HWAHC_EPROTO_MAX = 16,
|
||||
/** Period of time for EPROTO errors (in jiffies) */
|
||||
HWAHC_EPROTO_PERIOD = 4 * HZ,
|
||||
};
|
||||
|
||||
|
||||
/* Notification endpoint handling */
|
||||
extern int wa_nep_create(struct wahc *, struct usb_interface *);
|
||||
extern void wa_nep_destroy(struct wahc *);
|
||||
|
||||
static inline int wa_nep_arm(struct wahc *wa, gfp_t gfp_mask)
|
||||
{
|
||||
struct urb *urb = wa->nep_urb;
|
||||
urb->transfer_buffer = wa->nep_buffer;
|
||||
urb->transfer_buffer_length = wa->nep_buffer_size;
|
||||
return usb_submit_urb(urb, gfp_mask);
|
||||
}
|
||||
|
||||
static inline void wa_nep_disarm(struct wahc *wa)
|
||||
{
|
||||
usb_kill_urb(wa->nep_urb);
|
||||
}
|
||||
|
||||
|
||||
/* RPipes */
|
||||
static inline void wa_rpipe_init(struct wahc *wa)
|
||||
{
|
||||
spin_lock_init(&wa->rpipe_bm_lock);
|
||||
mutex_init(&wa->rpipe_mutex);
|
||||
}
|
||||
|
||||
static inline void wa_init(struct wahc *wa)
|
||||
{
|
||||
edc_init(&wa->nep_edc);
|
||||
atomic_set(&wa->notifs_queued, 0);
|
||||
wa_rpipe_init(wa);
|
||||
edc_init(&wa->dti_edc);
|
||||
INIT_LIST_HEAD(&wa->xfer_list);
|
||||
INIT_LIST_HEAD(&wa->xfer_delayed_list);
|
||||
spin_lock_init(&wa->xfer_list_lock);
|
||||
INIT_WORK(&wa->xfer_work, wa_urb_enqueue_run);
|
||||
atomic_set(&wa->xfer_id_count, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a pipe (when refcount drops to zero)
|
||||
*
|
||||
* Assumes it has been moved to the "QUIESCING" state.
|
||||
*/
|
||||
struct wa_xfer;
|
||||
extern void rpipe_destroy(struct kref *_rpipe);
|
||||
static inline
|
||||
void __rpipe_get(struct wa_rpipe *rpipe)
|
||||
{
|
||||
kref_get(&rpipe->refcnt);
|
||||
}
|
||||
extern int rpipe_get_by_ep(struct wahc *, struct usb_host_endpoint *,
|
||||
struct urb *, gfp_t);
|
||||
static inline void rpipe_put(struct wa_rpipe *rpipe)
|
||||
{
|
||||
kref_put(&rpipe->refcnt, rpipe_destroy);
|
||||
|
||||
}
|
||||
extern void rpipe_ep_disable(struct wahc *, struct usb_host_endpoint *);
|
||||
extern int wa_rpipes_create(struct wahc *);
|
||||
extern void wa_rpipes_destroy(struct wahc *);
|
||||
static inline void rpipe_avail_dec(struct wa_rpipe *rpipe)
|
||||
{
|
||||
atomic_dec(&rpipe->segs_available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the rpipe is ready to submit more segments.
|
||||
*/
|
||||
static inline int rpipe_avail_inc(struct wa_rpipe *rpipe)
|
||||
{
|
||||
return atomic_inc_return(&rpipe->segs_available) > 0
|
||||
&& !list_empty(&rpipe->seg_list);
|
||||
}
|
||||
|
||||
|
||||
/* Transferring data */
|
||||
extern int wa_urb_enqueue(struct wahc *, struct usb_host_endpoint *,
|
||||
struct urb *, gfp_t);
|
||||
extern int wa_urb_dequeue(struct wahc *, struct urb *);
|
||||
extern void wa_handle_notif_xfer(struct wahc *, struct wa_notif_hdr *);
|
||||
|
||||
|
||||
/* Misc
|
||||
*
|
||||
* FIXME: Refcounting for the actual @hwahc object is not correct; I
|
||||
* mean, this should be refcounting on the HCD underneath, but
|
||||
* it is not. In any case, the semantics for HCD refcounting
|
||||
* are *weird*...on refcount reaching zero it just frees
|
||||
* it...no RC specific function is called...unless I miss
|
||||
* something.
|
||||
*
|
||||
* FIXME: has to go away in favour of an 'struct' hcd based sollution
|
||||
*/
|
||||
static inline struct wahc *wa_get(struct wahc *wa)
|
||||
{
|
||||
usb_get_intf(wa->usb_iface);
|
||||
return wa;
|
||||
}
|
||||
|
||||
static inline void wa_put(struct wahc *wa)
|
||||
{
|
||||
usb_put_intf(wa->usb_iface);
|
||||
}
|
||||
|
||||
|
||||
static inline int __wa_feature(struct wahc *wa, unsigned op, u16 feature)
|
||||
{
|
||||
return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
op ? USB_REQ_SET_FEATURE : USB_REQ_CLEAR_FEATURE,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
feature,
|
||||
wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
}
|
||||
|
||||
|
||||
static inline int __wa_set_feature(struct wahc *wa, u16 feature)
|
||||
{
|
||||
return __wa_feature(wa, 1, feature);
|
||||
}
|
||||
|
||||
|
||||
static inline int __wa_clear_feature(struct wahc *wa, u16 feature)
|
||||
{
|
||||
return __wa_feature(wa, 0, feature);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the status of a Wire Adapter
|
||||
*
|
||||
* @wa: Wire Adapter instance
|
||||
* @returns < 0 errno code on error, or status bitmap as described
|
||||
* in WUSB1.0[8.3.1.6].
|
||||
*
|
||||
* NOTE: need malloc, some arches don't take USB from the stack
|
||||
*/
|
||||
static inline
|
||||
s32 __wa_get_status(struct wahc *wa)
|
||||
{
|
||||
s32 result;
|
||||
result = usb_control_msg(
|
||||
wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0),
|
||||
USB_REQ_GET_STATUS,
|
||||
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
&wa->status, sizeof(wa->status),
|
||||
1000 /* FIXME: arbitrary */);
|
||||
if (result >= 0)
|
||||
result = wa->status;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits until the Wire Adapter's status matches @mask/@value
|
||||
*
|
||||
* @wa: Wire Adapter instance.
|
||||
* @returns < 0 errno code on error, otherwise status.
|
||||
*
|
||||
* Loop until the WAs status matches the mask and value (status & mask
|
||||
* == value). Timeout if it doesn't happen.
|
||||
*
|
||||
* FIXME: is there an official specification on how long status
|
||||
* changes can take?
|
||||
*/
|
||||
static inline s32 __wa_wait_status(struct wahc *wa, u32 mask, u32 value)
|
||||
{
|
||||
s32 result;
|
||||
unsigned loops = 10;
|
||||
do {
|
||||
msleep(50);
|
||||
result = __wa_get_status(wa);
|
||||
if ((result & mask) == value)
|
||||
break;
|
||||
if (loops-- == 0) {
|
||||
result = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
} while (result >= 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/** Command @hwahc to stop, @returns 0 if ok, < 0 errno code on error */
|
||||
static inline int __wa_stop(struct wahc *wa)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
result = __wa_clear_feature(wa, WA_ENABLE);
|
||||
if (result < 0 && result != -ENODEV) {
|
||||
dev_err(dev, "error commanding HC to stop: %d\n", result);
|
||||
goto out;
|
||||
}
|
||||
result = __wa_wait_status(wa, WA_ENABLE, 0);
|
||||
if (result < 0 && result != -ENODEV)
|
||||
dev_err(dev, "error waiting for HC to stop: %d\n", result);
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#endif /* #ifndef __HWAHC_INTERNAL_H__ */
|
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8])
|
||||
* Notification EndPoint support
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This part takes care of getting the notification from the hw
|
||||
* only and dispatching through wusbwad into
|
||||
* wa_notif_dispatch. Handling is done there.
|
||||
*
|
||||
* WA notifications are limited in size; most of them are three or
|
||||
* four bytes long, and the longest is the HWA Device Notification,
|
||||
* which would not exceed 38 bytes (DNs are limited in payload to 32
|
||||
* bytes plus 3 bytes header (WUSB1.0[7.6p2]), plus 3 bytes HWA
|
||||
* header (WUSB1.0[8.5.4.2]).
|
||||
*
|
||||
* It is not clear if more than one Device Notification can be packed
|
||||
* in a HWA Notification, I assume no because of the wording in
|
||||
* WUSB1.0[8.5.4.2]. In any case, the bigger any notification could
|
||||
* get is 256 bytes (as the bLength field is a byte).
|
||||
*
|
||||
* So what we do is we have this buffer and read into it; when a
|
||||
* notification arrives we schedule work to a specific, single thread
|
||||
* workqueue (so notifications are serialized) and copy the
|
||||
* notification data. After scheduling the work, we rearm the read from
|
||||
* the notification endpoint.
|
||||
*
|
||||
* Entry points here are:
|
||||
*
|
||||
* wa_nep_[create|destroy]() To initialize/release this subsystem
|
||||
*
|
||||
* wa_nep_cb() Callback for the notification
|
||||
* endpoint; when data is ready, this
|
||||
* does the dispatching.
|
||||
*/
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "wa-hc.h"
|
||||
#include "wusbhc.h"
|
||||
|
||||
/* Structure for queueing notifications to the workqueue */
|
||||
struct wa_notif_work {
|
||||
struct work_struct work;
|
||||
struct wahc *wa;
|
||||
size_t size;
|
||||
u8 data[];
|
||||
};
|
||||
|
||||
/*
|
||||
* Process incoming notifications from the WA's Notification EndPoint
|
||||
* [the wuswad daemon, basically]
|
||||
*
|
||||
* @_nw: Pointer to a descriptor which has the pointer to the
|
||||
* @wa, the size of the buffer and the work queue
|
||||
* structure (so we can free all when done).
|
||||
* @returns 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* All notifications follow the same format; they need to start with a
|
||||
* 'struct wa_notif_hdr' header, so it is easy to parse through
|
||||
* them. We just break the buffer in individual notifications (the
|
||||
* standard doesn't say if it can be done or is forbidden, so we are
|
||||
* cautious) and dispatch each.
|
||||
*
|
||||
* So the handling layers are is:
|
||||
*
|
||||
* WA specific notification (from NEP)
|
||||
* Device Notification Received -> wa_handle_notif_dn()
|
||||
* WUSB Device notification generic handling
|
||||
* BPST Adjustment -> wa_handle_notif_bpst_adj()
|
||||
* ... -> ...
|
||||
*
|
||||
* @wa has to be referenced
|
||||
*/
|
||||
static void wa_notif_dispatch(struct work_struct *ws)
|
||||
{
|
||||
void *itr;
|
||||
u8 missing = 0;
|
||||
struct wa_notif_work *nw = container_of(ws, struct wa_notif_work, work);
|
||||
struct wahc *wa = nw->wa;
|
||||
struct wa_notif_hdr *notif_hdr;
|
||||
size_t size;
|
||||
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
#if 0
|
||||
/* FIXME: need to check for this??? */
|
||||
if (usb_hcd->state == HC_STATE_QUIESCING) /* Going down? */
|
||||
goto out; /* screw it */
|
||||
#endif
|
||||
atomic_dec(&wa->notifs_queued); /* Throttling ctl */
|
||||
dev = &wa->usb_iface->dev;
|
||||
size = nw->size;
|
||||
itr = nw->data;
|
||||
|
||||
while (size) {
|
||||
if (size < sizeof(*notif_hdr)) {
|
||||
missing = sizeof(*notif_hdr) - size;
|
||||
goto exhausted_buffer;
|
||||
}
|
||||
notif_hdr = itr;
|
||||
if (size < notif_hdr->bLength)
|
||||
goto exhausted_buffer;
|
||||
itr += notif_hdr->bLength;
|
||||
size -= notif_hdr->bLength;
|
||||
/* Dispatch the notification [don't use itr or size!] */
|
||||
switch (notif_hdr->bNotifyType) {
|
||||
case HWA_NOTIF_DN: {
|
||||
struct hwa_notif_dn *hwa_dn;
|
||||
hwa_dn = container_of(notif_hdr, struct hwa_notif_dn,
|
||||
hdr);
|
||||
wusbhc_handle_dn(wa->wusb, hwa_dn->bSourceDeviceAddr,
|
||||
hwa_dn->dndata,
|
||||
notif_hdr->bLength - sizeof(*hwa_dn));
|
||||
break;
|
||||
}
|
||||
case WA_NOTIF_TRANSFER:
|
||||
wa_handle_notif_xfer(wa, notif_hdr);
|
||||
break;
|
||||
case DWA_NOTIF_RWAKE:
|
||||
case DWA_NOTIF_PORTSTATUS:
|
||||
case HWA_NOTIF_BPST_ADJ:
|
||||
/* FIXME: unimplemented WA NOTIFs */
|
||||
/* fallthru */
|
||||
default:
|
||||
if (printk_ratelimit()) {
|
||||
dev_err(dev, "HWA: unknown notification 0x%x, "
|
||||
"%zu bytes; discarding\n",
|
||||
notif_hdr->bNotifyType,
|
||||
(size_t)notif_hdr->bLength);
|
||||
dump_bytes(dev, notif_hdr, 16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
out:
|
||||
wa_put(wa);
|
||||
kfree(nw);
|
||||
return;
|
||||
|
||||
/* THIS SHOULD NOT HAPPEN
|
||||
*
|
||||
* Buffer exahusted with partial data remaining; just warn and
|
||||
* discard the data, as this should not happen.
|
||||
*/
|
||||
exhausted_buffer:
|
||||
if (!printk_ratelimit())
|
||||
goto out;
|
||||
dev_warn(dev, "HWA: device sent short notification, "
|
||||
"%d bytes missing; discarding %d bytes.\n",
|
||||
missing, (int)size);
|
||||
dump_bytes(dev, itr, size);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Deliver incoming WA notifications to the wusbwa workqueue
|
||||
*
|
||||
* @wa: Pointer the Wire Adapter Controller Data Streaming
|
||||
* instance (part of an 'struct usb_hcd').
|
||||
* @size: Size of the received buffer
|
||||
* @returns 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* The input buffer is @wa->nep_buffer, with @size bytes
|
||||
* (guaranteed to fit in the allocated space,
|
||||
* @wa->nep_buffer_size).
|
||||
*/
|
||||
static int wa_nep_queue(struct wahc *wa, size_t size)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
struct wa_notif_work *nw;
|
||||
|
||||
/* dev_fnstart(dev, "(wa %p, size %zu)\n", wa, size); */
|
||||
BUG_ON(size > wa->nep_buffer_size);
|
||||
if (size == 0)
|
||||
goto out;
|
||||
if (atomic_read(&wa->notifs_queued) > 200) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "Too many notifications queued, "
|
||||
"throttling back\n");
|
||||
goto out;
|
||||
}
|
||||
nw = kzalloc(sizeof(*nw) + size, GFP_ATOMIC);
|
||||
if (nw == NULL) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "No memory to queue notification\n");
|
||||
goto out;
|
||||
}
|
||||
INIT_WORK(&nw->work, wa_notif_dispatch);
|
||||
nw->wa = wa_get(wa);
|
||||
nw->size = size;
|
||||
memcpy(nw->data, wa->nep_buffer, size);
|
||||
atomic_inc(&wa->notifs_queued); /* Throttling ctl */
|
||||
queue_work(wusbd, &nw->work);
|
||||
out:
|
||||
/* dev_fnend(dev, "(wa %p, size %zu) = result\n", wa, size, result); */
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback for the notification event endpoint
|
||||
*
|
||||
* Check's that everything is fine and then passes the data to be
|
||||
* queued to the workqueue.
|
||||
*/
|
||||
static void wa_nep_cb(struct urb *urb)
|
||||
{
|
||||
int result;
|
||||
struct wahc *wa = urb->context;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
switch (result = urb->status) {
|
||||
case 0:
|
||||
result = wa_nep_queue(wa, urb->actual_length);
|
||||
if (result < 0)
|
||||
dev_err(dev, "NEP: unable to process notification(s): "
|
||||
"%d\n", result);
|
||||
break;
|
||||
case -ECONNRESET: /* Not an error, but a controlled situation; */
|
||||
case -ENOENT: /* (we killed the URB)...so, no broadcast */
|
||||
case -ESHUTDOWN:
|
||||
dev_dbg(dev, "NEP: going down %d\n", urb->status);
|
||||
goto out;
|
||||
default: /* On general errors, we retry unless it gets ugly */
|
||||
if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS,
|
||||
EDC_ERROR_TIMEFRAME)) {
|
||||
dev_err(dev, "NEP: URB max acceptable errors "
|
||||
"exceeded, resetting device\n");
|
||||
wa_reset_all(wa);
|
||||
goto out;
|
||||
}
|
||||
dev_err(dev, "NEP: URB error %d\n", urb->status);
|
||||
}
|
||||
result = wa_nep_arm(wa, GFP_ATOMIC);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "NEP: cannot submit URB: %d\n", result);
|
||||
wa_reset_all(wa);
|
||||
}
|
||||
out:
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize @wa's notification and event's endpoint stuff
|
||||
*
|
||||
* This includes the allocating the read buffer, the context ID
|
||||
* allocation bitmap, the URB and submitting the URB.
|
||||
*/
|
||||
int wa_nep_create(struct wahc *wa, struct usb_interface *iface)
|
||||
{
|
||||
int result;
|
||||
struct usb_endpoint_descriptor *epd;
|
||||
struct usb_device *usb_dev = interface_to_usbdev(iface);
|
||||
struct device *dev = &iface->dev;
|
||||
|
||||
edc_init(&wa->nep_edc);
|
||||
epd = &iface->cur_altsetting->endpoint[0].desc;
|
||||
wa->nep_buffer_size = 1024;
|
||||
wa->nep_buffer = kmalloc(wa->nep_buffer_size, GFP_KERNEL);
|
||||
if (wa->nep_buffer == NULL) {
|
||||
dev_err(dev, "Unable to allocate notification's read buffer\n");
|
||||
goto error_nep_buffer;
|
||||
}
|
||||
wa->nep_urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (wa->nep_urb == NULL) {
|
||||
dev_err(dev, "Unable to allocate notification URB\n");
|
||||
goto error_urb_alloc;
|
||||
}
|
||||
usb_fill_int_urb(wa->nep_urb, usb_dev,
|
||||
usb_rcvintpipe(usb_dev, epd->bEndpointAddress),
|
||||
wa->nep_buffer, wa->nep_buffer_size,
|
||||
wa_nep_cb, wa, epd->bInterval);
|
||||
result = wa_nep_arm(wa, GFP_KERNEL);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot submit notification URB: %d\n", result);
|
||||
goto error_nep_arm;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error_nep_arm:
|
||||
usb_free_urb(wa->nep_urb);
|
||||
error_urb_alloc:
|
||||
kfree(wa->nep_buffer);
|
||||
error_nep_buffer:
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
void wa_nep_destroy(struct wahc *wa)
|
||||
{
|
||||
wa_nep_disarm(wa);
|
||||
usb_free_urb(wa->nep_urb);
|
||||
kfree(wa->nep_buffer);
|
||||
}
|
|
@ -0,0 +1,562 @@
|
|||
/*
|
||||
* WUSB Wire Adapter
|
||||
* rpipe management
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*
|
||||
* RPIPE
|
||||
*
|
||||
* Targetted at different downstream endpoints
|
||||
*
|
||||
* Descriptor: use to config the remote pipe.
|
||||
*
|
||||
* The number of blocks could be dynamic (wBlocks in descriptor is
|
||||
* 0)--need to schedule them then.
|
||||
*
|
||||
* Each bit in wa->rpipe_bm represents if an rpipe is being used or
|
||||
* not. Rpipes are represented with a 'struct wa_rpipe' that is
|
||||
* attached to the hcpriv member of a 'struct usb_host_endpoint'.
|
||||
*
|
||||
* When you need to xfer data to an endpoint, you get an rpipe for it
|
||||
* with wa_ep_rpipe_get(), which gives you a reference to the rpipe
|
||||
* and keeps a single one (the first one) with the endpoint. When you
|
||||
* are done transferring, you drop that reference. At the end the
|
||||
* rpipe is always allocated and bound to the endpoint. There it might
|
||||
* be recycled when not used.
|
||||
*
|
||||
* Addresses:
|
||||
*
|
||||
* We use a 1:1 mapping mechanism between port address (0 based
|
||||
* index, actually) and the address. The USB stack knows about this.
|
||||
*
|
||||
* USB Stack port number 4 (1 based)
|
||||
* WUSB code port index 3 (0 based)
|
||||
* USB Addresss 5 (2 based -- 0 is for default, 1 for root hub)
|
||||
*
|
||||
* Now, because we don't use the concept as default address exactly
|
||||
* like the (wired) USB code does, we need to kind of skip it. So we
|
||||
* never take addresses from the urb->pipe, but from the
|
||||
* urb->dev->devnum, to make sure that we always have the right
|
||||
* destination address.
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <asm/atomic.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include "wusbhc.h"
|
||||
#include "wa-hc.h"
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
|
||||
static int __rpipe_get_descr(struct wahc *wa,
|
||||
struct usb_rpipe_descriptor *descr, u16 index)
|
||||
{
|
||||
ssize_t result;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
/* Get the RPIPE descriptor -- we cannot use the usb_get_descriptor()
|
||||
* function because the arguments are different.
|
||||
*/
|
||||
d_printf(1, dev, "rpipe %u: get descr\n", index);
|
||||
result = usb_control_msg(
|
||||
wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0),
|
||||
USB_REQ_GET_DESCRIPTOR,
|
||||
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE,
|
||||
USB_DT_RPIPE<<8, index, descr, sizeof(*descr),
|
||||
1000 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "rpipe %u: get descriptor failed: %d\n",
|
||||
index, (int)result);
|
||||
goto error;
|
||||
}
|
||||
if (result < sizeof(*descr)) {
|
||||
dev_err(dev, "rpipe %u: got short descriptor "
|
||||
"(%zd vs %zd bytes needed)\n",
|
||||
index, result, sizeof(*descr));
|
||||
result = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
result = 0;
|
||||
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* The descriptor is assumed to be properly initialized (ie: you got
|
||||
* it through __rpipe_get_descr()).
|
||||
*/
|
||||
static int __rpipe_set_descr(struct wahc *wa,
|
||||
struct usb_rpipe_descriptor *descr, u16 index)
|
||||
{
|
||||
ssize_t result;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
/* we cannot use the usb_get_descriptor() function because the
|
||||
* arguments are different.
|
||||
*/
|
||||
d_printf(1, dev, "rpipe %u: set descr\n", index);
|
||||
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_RPIPE,
|
||||
USB_DT_RPIPE<<8, index, descr, sizeof(*descr),
|
||||
HZ / 10);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "rpipe %u: set descriptor failed: %d\n",
|
||||
index, (int)result);
|
||||
goto error;
|
||||
}
|
||||
if (result < sizeof(*descr)) {
|
||||
dev_err(dev, "rpipe %u: sent short descriptor "
|
||||
"(%zd vs %zd bytes required)\n",
|
||||
index, result, sizeof(*descr));
|
||||
result = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
result = 0;
|
||||
|
||||
error:
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
static void rpipe_init(struct wa_rpipe *rpipe)
|
||||
{
|
||||
kref_init(&rpipe->refcnt);
|
||||
spin_lock_init(&rpipe->seg_lock);
|
||||
INIT_LIST_HEAD(&rpipe->seg_list);
|
||||
}
|
||||
|
||||
static unsigned rpipe_get_idx(struct wahc *wa, unsigned rpipe_idx)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&wa->rpipe_bm_lock, flags);
|
||||
rpipe_idx = find_next_zero_bit(wa->rpipe_bm, wa->rpipes, rpipe_idx);
|
||||
if (rpipe_idx < wa->rpipes)
|
||||
set_bit(rpipe_idx, wa->rpipe_bm);
|
||||
spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags);
|
||||
|
||||
return rpipe_idx;
|
||||
}
|
||||
|
||||
static void rpipe_put_idx(struct wahc *wa, unsigned rpipe_idx)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&wa->rpipe_bm_lock, flags);
|
||||
clear_bit(rpipe_idx, wa->rpipe_bm);
|
||||
spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags);
|
||||
}
|
||||
|
||||
void rpipe_destroy(struct kref *_rpipe)
|
||||
{
|
||||
struct wa_rpipe *rpipe = container_of(_rpipe, struct wa_rpipe, refcnt);
|
||||
u8 index = le16_to_cpu(rpipe->descr.wRPipeIndex);
|
||||
d_fnstart(1, NULL, "(rpipe %p %u)\n", rpipe, index);
|
||||
if (rpipe->ep)
|
||||
rpipe->ep->hcpriv = NULL;
|
||||
rpipe_put_idx(rpipe->wa, index);
|
||||
wa_put(rpipe->wa);
|
||||
kfree(rpipe);
|
||||
d_fnend(1, NULL, "(rpipe %p %u)\n", rpipe, index);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpipe_destroy);
|
||||
|
||||
/*
|
||||
* Locate an idle rpipe, create an structure for it and return it
|
||||
*
|
||||
* @wa is referenced and unlocked
|
||||
* @crs enum rpipe_attr, required endpoint characteristics
|
||||
*
|
||||
* The rpipe can be used only sequentially (not in parallel).
|
||||
*
|
||||
* The rpipe is moved into the "ready" state.
|
||||
*/
|
||||
static int rpipe_get_idle(struct wa_rpipe **prpipe, struct wahc *wa, u8 crs,
|
||||
gfp_t gfp)
|
||||
{
|
||||
int result;
|
||||
unsigned rpipe_idx;
|
||||
struct wa_rpipe *rpipe;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
d_fnstart(3, dev, "(wa %p crs 0x%02x)\n", wa, crs);
|
||||
rpipe = kzalloc(sizeof(*rpipe), gfp);
|
||||
if (rpipe == NULL)
|
||||
return -ENOMEM;
|
||||
rpipe_init(rpipe);
|
||||
|
||||
/* Look for an idle pipe */
|
||||
for (rpipe_idx = 0; rpipe_idx < wa->rpipes; rpipe_idx++) {
|
||||
rpipe_idx = rpipe_get_idx(wa, rpipe_idx);
|
||||
if (rpipe_idx >= wa->rpipes) /* no more pipes :( */
|
||||
break;
|
||||
result = __rpipe_get_descr(wa, &rpipe->descr, rpipe_idx);
|
||||
if (result < 0)
|
||||
dev_err(dev, "Can't get descriptor for rpipe %u: %d\n",
|
||||
rpipe_idx, result);
|
||||
else if ((rpipe->descr.bmCharacteristics & crs) != 0)
|
||||
goto found;
|
||||
rpipe_put_idx(wa, rpipe_idx);
|
||||
}
|
||||
*prpipe = NULL;
|
||||
kfree(rpipe);
|
||||
d_fnend(3, dev, "(wa %p crs 0x%02x) = -ENXIO\n", wa, crs);
|
||||
return -ENXIO;
|
||||
|
||||
found:
|
||||
set_bit(rpipe_idx, wa->rpipe_bm);
|
||||
rpipe->wa = wa_get(wa);
|
||||
*prpipe = rpipe;
|
||||
d_fnstart(3, dev, "(wa %p crs 0x%02x) = 0\n", wa, crs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __rpipe_reset(struct wahc *wa, unsigned index)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
|
||||
d_printf(1, dev, "rpipe %u: reset\n", index);
|
||||
result = usb_control_msg(
|
||||
wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
|
||||
USB_REQ_RPIPE_RESET,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE,
|
||||
0, index, NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0)
|
||||
dev_err(dev, "rpipe %u: reset failed: %d\n",
|
||||
index, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fake companion descriptor for ep0
|
||||
*
|
||||
* See WUSB1.0[7.4.4], most of this is zero for bulk/int/ctl
|
||||
*/
|
||||
static struct usb_wireless_ep_comp_descriptor epc0 = {
|
||||
.bLength = sizeof(epc0),
|
||||
.bDescriptorType = USB_DT_WIRELESS_ENDPOINT_COMP,
|
||||
/* .bMaxBurst = 1, */
|
||||
.bMaxSequence = 31,
|
||||
};
|
||||
|
||||
/*
|
||||
* Look for EP companion descriptor
|
||||
*
|
||||
* Get there, look for Inara in the endpoint's extra descriptors
|
||||
*/
|
||||
static struct usb_wireless_ep_comp_descriptor *rpipe_epc_find(
|
||||
struct device *dev, struct usb_host_endpoint *ep)
|
||||
{
|
||||
void *itr;
|
||||
size_t itr_size;
|
||||
struct usb_descriptor_header *hdr;
|
||||
struct usb_wireless_ep_comp_descriptor *epcd;
|
||||
|
||||
d_fnstart(3, dev, "(ep %p)\n", ep);
|
||||
if (ep->desc.bEndpointAddress == 0) {
|
||||
epcd = &epc0;
|
||||
goto out;
|
||||
}
|
||||
itr = ep->extra;
|
||||
itr_size = ep->extralen;
|
||||
epcd = NULL;
|
||||
while (itr_size > 0) {
|
||||
if (itr_size < sizeof(*hdr)) {
|
||||
dev_err(dev, "HW Bug? ep 0x%02x: extra descriptors "
|
||||
"at offset %zu: only %zu bytes left\n",
|
||||
ep->desc.bEndpointAddress,
|
||||
itr - (void *) ep->extra, itr_size);
|
||||
break;
|
||||
}
|
||||
hdr = itr;
|
||||
if (hdr->bDescriptorType == USB_DT_WIRELESS_ENDPOINT_COMP) {
|
||||
epcd = itr;
|
||||
break;
|
||||
}
|
||||
if (hdr->bLength > itr_size) {
|
||||
dev_err(dev, "HW Bug? ep 0x%02x: extra descriptor "
|
||||
"at offset %zu (type 0x%02x) "
|
||||
"length %d but only %zu bytes left\n",
|
||||
ep->desc.bEndpointAddress,
|
||||
itr - (void *) ep->extra, hdr->bDescriptorType,
|
||||
hdr->bLength, itr_size);
|
||||
break;
|
||||
}
|
||||
itr += hdr->bLength;
|
||||
itr_size -= hdr->bDescriptorType;
|
||||
}
|
||||
out:
|
||||
d_fnend(3, dev, "(ep %p) = %p\n", ep, epcd);
|
||||
return epcd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Aim an rpipe to its device & endpoint destination
|
||||
*
|
||||
* Make sure we change the address to unauthenticathed if the device
|
||||
* is WUSB and it is not authenticated.
|
||||
*/
|
||||
static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa,
|
||||
struct usb_host_endpoint *ep, struct urb *urb, gfp_t gfp)
|
||||
{
|
||||
int result = -ENOMSG; /* better code for lack of companion? */
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
struct usb_device *usb_dev = urb->dev;
|
||||
struct usb_wireless_ep_comp_descriptor *epcd;
|
||||
u8 unauth;
|
||||
|
||||
d_fnstart(3, dev, "(rpipe %p wa %p ep %p, urb %p)\n",
|
||||
rpipe, wa, ep, urb);
|
||||
epcd = rpipe_epc_find(dev, ep);
|
||||
if (epcd == NULL) {
|
||||
dev_err(dev, "ep 0x%02x: can't find companion descriptor\n",
|
||||
ep->desc.bEndpointAddress);
|
||||
goto error;
|
||||
}
|
||||
unauth = usb_dev->wusb && !usb_dev->authenticated ? 0x80 : 0;
|
||||
__rpipe_reset(wa, le16_to_cpu(rpipe->descr.wRPipeIndex));
|
||||
atomic_set(&rpipe->segs_available, le16_to_cpu(rpipe->descr.wRequests));
|
||||
/* FIXME: block allocation system; request with queuing and timeout */
|
||||
/* FIXME: compute so seg_size > ep->maxpktsize */
|
||||
rpipe->descr.wBlocks = cpu_to_le16(16); /* given */
|
||||
/* ep0 maxpktsize is 0x200 (WUSB1.0[4.8.1]) */
|
||||
rpipe->descr.wMaxPacketSize = cpu_to_le16(ep->desc.wMaxPacketSize);
|
||||
rpipe->descr.bHSHubAddress = 0; /* reserved: zero */
|
||||
rpipe->descr.bHSHubPort = wusb_port_no_to_idx(urb->dev->portnum);
|
||||
/* FIXME: use maximum speed as supported or recommended by device */
|
||||
rpipe->descr.bSpeed = usb_pipeendpoint(urb->pipe) == 0 ?
|
||||
UWB_PHY_RATE_53 : UWB_PHY_RATE_200;
|
||||
d_printf(2, dev, "addr %u (0x%02x) rpipe #%u ep# %u speed %d\n",
|
||||
urb->dev->devnum, urb->dev->devnum | unauth,
|
||||
le16_to_cpu(rpipe->descr.wRPipeIndex),
|
||||
usb_pipeendpoint(urb->pipe), rpipe->descr.bSpeed);
|
||||
/* see security.c:wusb_update_address() */
|
||||
if (unlikely(urb->dev->devnum == 0x80))
|
||||
rpipe->descr.bDeviceAddress = 0;
|
||||
else
|
||||
rpipe->descr.bDeviceAddress = urb->dev->devnum | unauth;
|
||||
rpipe->descr.bEndpointAddress = ep->desc.bEndpointAddress;
|
||||
/* FIXME: bDataSequence */
|
||||
rpipe->descr.bDataSequence = 0;
|
||||
/* FIXME: dwCurrentWindow */
|
||||
rpipe->descr.dwCurrentWindow = cpu_to_le32(1);
|
||||
/* FIXME: bMaxDataSequence */
|
||||
rpipe->descr.bMaxDataSequence = epcd->bMaxSequence - 1;
|
||||
rpipe->descr.bInterval = ep->desc.bInterval;
|
||||
/* FIXME: bOverTheAirInterval */
|
||||
rpipe->descr.bOverTheAirInterval = 0; /* 0 if not isoc */
|
||||
/* FIXME: xmit power & preamble blah blah */
|
||||
rpipe->descr.bmAttribute = ep->desc.bmAttributes & 0x03;
|
||||
/* rpipe->descr.bmCharacteristics RO */
|
||||
/* FIXME: bmRetryOptions */
|
||||
rpipe->descr.bmRetryOptions = 15;
|
||||
/* FIXME: use for assessing link quality? */
|
||||
rpipe->descr.wNumTransactionErrors = 0;
|
||||
result = __rpipe_set_descr(wa, &rpipe->descr,
|
||||
le16_to_cpu(rpipe->descr.wRPipeIndex));
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot aim rpipe: %d\n", result);
|
||||
goto error;
|
||||
}
|
||||
result = 0;
|
||||
error:
|
||||
d_fnend(3, dev, "(rpipe %p wa %p ep %p urb %p) = %d\n",
|
||||
rpipe, wa, ep, urb, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check an aimed rpipe to make sure it points to where we want
|
||||
*
|
||||
* We use bit 19 of the Linux USB pipe bitmap for unauth vs auth
|
||||
* space; when it is like that, we or 0x80 to make an unauth address.
|
||||
*/
|
||||
static int rpipe_check_aim(const struct wa_rpipe *rpipe, const struct wahc *wa,
|
||||
const struct usb_host_endpoint *ep,
|
||||
const struct urb *urb, gfp_t gfp)
|
||||
{
|
||||
int result = 0; /* better code for lack of companion? */
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
struct usb_device *usb_dev = urb->dev;
|
||||
u8 unauth = (usb_dev->wusb && !usb_dev->authenticated) ? 0x80 : 0;
|
||||
u8 portnum = wusb_port_no_to_idx(urb->dev->portnum);
|
||||
|
||||
d_fnstart(3, dev, "(rpipe %p wa %p ep %p, urb %p)\n",
|
||||
rpipe, wa, ep, urb);
|
||||
#define AIM_CHECK(rdf, val, text) \
|
||||
do { \
|
||||
if (rpipe->descr.rdf != (val)) { \
|
||||
dev_err(dev, \
|
||||
"rpipe aim discrepancy: " #rdf " " text "\n", \
|
||||
rpipe->descr.rdf, (val)); \
|
||||
result = -EINVAL; \
|
||||
WARN_ON(1); \
|
||||
} \
|
||||
} while (0)
|
||||
AIM_CHECK(wMaxPacketSize, cpu_to_le16(ep->desc.wMaxPacketSize),
|
||||
"(%u vs %u)");
|
||||
AIM_CHECK(bHSHubPort, portnum, "(%u vs %u)");
|
||||
AIM_CHECK(bSpeed, usb_pipeendpoint(urb->pipe) == 0 ?
|
||||
UWB_PHY_RATE_53 : UWB_PHY_RATE_200,
|
||||
"(%u vs %u)");
|
||||
AIM_CHECK(bDeviceAddress, urb->dev->devnum | unauth, "(%u vs %u)");
|
||||
AIM_CHECK(bEndpointAddress, ep->desc.bEndpointAddress, "(%u vs %u)");
|
||||
AIM_CHECK(bInterval, ep->desc.bInterval, "(%u vs %u)");
|
||||
AIM_CHECK(bmAttribute, ep->desc.bmAttributes & 0x03, "(%u vs %u)");
|
||||
#undef AIM_CHECK
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_BUG
|
||||
#define CONFIG_BUG 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Make sure there is an rpipe allocated for an endpoint
|
||||
*
|
||||
* If already allocated, we just refcount it; if not, we get an
|
||||
* idle one, aim it to the right location and take it.
|
||||
*
|
||||
* Attaches to ep->hcpriv and rpipe->ep to ep.
|
||||
*/
|
||||
int rpipe_get_by_ep(struct wahc *wa, struct usb_host_endpoint *ep,
|
||||
struct urb *urb, gfp_t gfp)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
struct wa_rpipe *rpipe;
|
||||
u8 eptype;
|
||||
|
||||
d_fnstart(3, dev, "(wa %p ep %p urb %p gfp 0x%08x)\n", wa, ep, urb,
|
||||
gfp);
|
||||
mutex_lock(&wa->rpipe_mutex);
|
||||
rpipe = ep->hcpriv;
|
||||
if (rpipe != NULL) {
|
||||
if (CONFIG_BUG == 1) {
|
||||
result = rpipe_check_aim(rpipe, wa, ep, urb, gfp);
|
||||
if (result < 0)
|
||||
goto error;
|
||||
}
|
||||
__rpipe_get(rpipe);
|
||||
d_printf(2, dev, "ep 0x%02x: reusing rpipe %u\n",
|
||||
ep->desc.bEndpointAddress,
|
||||
le16_to_cpu(rpipe->descr.wRPipeIndex));
|
||||
} else {
|
||||
/* hmm, assign idle rpipe, aim it */
|
||||
result = -ENOBUFS;
|
||||
eptype = ep->desc.bmAttributes & 0x03;
|
||||
result = rpipe_get_idle(&rpipe, wa, 1 << eptype, gfp);
|
||||
if (result < 0)
|
||||
goto error;
|
||||
result = rpipe_aim(rpipe, wa, ep, urb, gfp);
|
||||
if (result < 0) {
|
||||
rpipe_put(rpipe);
|
||||
goto error;
|
||||
}
|
||||
ep->hcpriv = rpipe;
|
||||
rpipe->ep = ep;
|
||||
__rpipe_get(rpipe); /* for caching into ep->hcpriv */
|
||||
d_printf(2, dev, "ep 0x%02x: using rpipe %u\n",
|
||||
ep->desc.bEndpointAddress,
|
||||
le16_to_cpu(rpipe->descr.wRPipeIndex));
|
||||
}
|
||||
d_dump(4, dev, &rpipe->descr, sizeof(rpipe->descr));
|
||||
error:
|
||||
mutex_unlock(&wa->rpipe_mutex);
|
||||
d_fnend(3, dev, "(wa %p ep %p urb %p gfp 0x%08x)\n", wa, ep, urb, gfp);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the bitmap for each rpipe.
|
||||
*/
|
||||
int wa_rpipes_create(struct wahc *wa)
|
||||
{
|
||||
wa->rpipes = wa->wa_descr->wNumRPipes;
|
||||
wa->rpipe_bm = kzalloc(BITS_TO_LONGS(wa->rpipes)*sizeof(unsigned long),
|
||||
GFP_KERNEL);
|
||||
if (wa->rpipe_bm == NULL)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wa_rpipes_destroy(struct wahc *wa)
|
||||
{
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
d_fnstart(3, dev, "(wa %p)\n", wa);
|
||||
if (!bitmap_empty(wa->rpipe_bm, wa->rpipes)) {
|
||||
char buf[256];
|
||||
WARN_ON(1);
|
||||
bitmap_scnprintf(buf, sizeof(buf), wa->rpipe_bm, wa->rpipes);
|
||||
dev_err(dev, "BUG: pipes not released on exit: %s\n", buf);
|
||||
}
|
||||
kfree(wa->rpipe_bm);
|
||||
d_fnend(3, dev, "(wa %p)\n", wa);
|
||||
}
|
||||
|
||||
/*
|
||||
* Release resources allocated for an endpoint
|
||||
*
|
||||
* If there is an associated rpipe to this endpoint, Abort any pending
|
||||
* transfers and put it. If the rpipe ends up being destroyed,
|
||||
* __rpipe_destroy() will cleanup ep->hcpriv.
|
||||
*
|
||||
* This is called before calling hcd->stop(), so you don't need to do
|
||||
* anything else in there.
|
||||
*/
|
||||
void rpipe_ep_disable(struct wahc *wa, struct usb_host_endpoint *ep)
|
||||
{
|
||||
struct device *dev = &wa->usb_iface->dev;
|
||||
struct wa_rpipe *rpipe;
|
||||
d_fnstart(2, dev, "(wa %p ep %p)\n", wa, ep);
|
||||
mutex_lock(&wa->rpipe_mutex);
|
||||
rpipe = ep->hcpriv;
|
||||
if (rpipe != NULL) {
|
||||
unsigned rc = atomic_read(&rpipe->refcnt.refcount);
|
||||
int result;
|
||||
u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex);
|
||||
|
||||
if (rc != 1)
|
||||
d_printf(1, dev, "(wa %p ep %p) rpipe %p refcnt %u\n",
|
||||
wa, ep, rpipe, rc);
|
||||
|
||||
d_printf(1, dev, "rpipe %u: abort\n", index);
|
||||
result = usb_control_msg(
|
||||
wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0),
|
||||
USB_REQ_RPIPE_ABORT,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE,
|
||||
0, index, NULL, 0, 1000 /* FIXME: arbitrary */);
|
||||
if (result < 0 && result != -ENODEV /* dev is gone */)
|
||||
d_printf(1, dev, "(wa %p rpipe %u): abort failed: %d\n",
|
||||
wa, index, result);
|
||||
rpipe_put(rpipe);
|
||||
}
|
||||
mutex_unlock(&wa->rpipe_mutex);
|
||||
d_fnend(2, dev, "(wa %p ep %p)\n", wa, ep);
|
||||
return;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpipe_ep_disable);
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* Wireless USB Host Controller
|
||||
* sysfs glue, wusbcore module support and life cycle management
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Creation/destruction of wusbhc is split in two parts; that that
|
||||
* doesn't require the HCD to be added (wusbhc_{create,destroy}) and
|
||||
* the one that requires (phase B, wusbhc_b_{create,destroy}).
|
||||
*
|
||||
* This is so because usb_add_hcd() will start the HC, and thus, all
|
||||
* the HC specific stuff has to be already initialiazed (like sysfs
|
||||
* thingies).
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include "wusbhc.h"
|
||||
|
||||
/**
|
||||
* Extract the wusbhc that corresponds to a USB Host Controller class device
|
||||
*
|
||||
* WARNING! Apply only if @dev is that of a
|
||||
* wusbhc.usb_hcd.self->class_dev; otherwise, you loose.
|
||||
*/
|
||||
static struct wusbhc *usbhc_dev_to_wusbhc(struct device *dev)
|
||||
{
|
||||
struct usb_bus *usb_bus = dev_get_drvdata(dev);
|
||||
struct usb_hcd *usb_hcd = bus_to_hcd(usb_bus);
|
||||
return usb_hcd_to_wusbhc(usb_hcd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Show & store the current WUSB trust timeout
|
||||
*
|
||||
* We don't do locking--it is an 'atomic' value.
|
||||
*
|
||||
* The units that we store/show are always MILLISECONDS. However, the
|
||||
* value of trust_timeout is jiffies.
|
||||
*/
|
||||
static ssize_t wusb_trust_timeout_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", wusbhc->trust_timeout);
|
||||
}
|
||||
|
||||
static ssize_t wusb_trust_timeout_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
|
||||
ssize_t result = -ENOSYS;
|
||||
unsigned trust_timeout;
|
||||
|
||||
result = sscanf(buf, "%u", &trust_timeout);
|
||||
if (result != 1) {
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
/* FIXME: maybe we should check for range validity? */
|
||||
wusbhc->trust_timeout = trust_timeout;
|
||||
cancel_delayed_work(&wusbhc->keep_alive_timer);
|
||||
flush_workqueue(wusbd);
|
||||
queue_delayed_work(wusbd, &wusbhc->keep_alive_timer,
|
||||
(trust_timeout * CONFIG_HZ)/1000/2);
|
||||
out:
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_trust_timeout, 0644, wusb_trust_timeout_show,
|
||||
wusb_trust_timeout_store);
|
||||
|
||||
/*
|
||||
* Show & store the current WUSB CHID
|
||||
*/
|
||||
static ssize_t wusb_chid_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
|
||||
ssize_t result = 0;
|
||||
|
||||
if (wusbhc->wuie_host_info != NULL)
|
||||
result += ckhdid_printf(buf, PAGE_SIZE,
|
||||
&wusbhc->wuie_host_info->CHID);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Store a new CHID
|
||||
*
|
||||
* This will (FIXME) trigger many changes.
|
||||
*
|
||||
* - Send an all zeros CHID and it will stop the controller
|
||||
* - Send a non-zero CHID and it will start it
|
||||
* (unless it was started, it will just change the CHID,
|
||||
* diconnecting all devices first).
|
||||
*
|
||||
* So first we scan the MMC we are sent and then we act on it. We
|
||||
* read it in the same format as we print it, an ASCII string of 16
|
||||
* hex bytes.
|
||||
*
|
||||
* See wusbhc_chid_set() for more info.
|
||||
*/
|
||||
static ssize_t wusb_chid_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
|
||||
struct wusb_ckhdid chid;
|
||||
ssize_t result;
|
||||
|
||||
result = sscanf(buf,
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx\n",
|
||||
&chid.data[0] , &chid.data[1] ,
|
||||
&chid.data[2] , &chid.data[3] ,
|
||||
&chid.data[4] , &chid.data[5] ,
|
||||
&chid.data[6] , &chid.data[7] ,
|
||||
&chid.data[8] , &chid.data[9] ,
|
||||
&chid.data[10], &chid.data[11],
|
||||
&chid.data[12], &chid.data[13],
|
||||
&chid.data[14], &chid.data[15]);
|
||||
if (result != 16) {
|
||||
dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits): "
|
||||
"%d\n", (int)result);
|
||||
return -EINVAL;
|
||||
}
|
||||
result = wusbhc_chid_set(wusbhc, &chid);
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
static DEVICE_ATTR(wusb_chid, 0644, wusb_chid_show, wusb_chid_store);
|
||||
|
||||
/* Group all the WUSBHC attributes */
|
||||
static struct attribute *wusbhc_attrs[] = {
|
||||
&dev_attr_wusb_trust_timeout.attr,
|
||||
&dev_attr_wusb_chid.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group wusbhc_attr_group = {
|
||||
.name = NULL, /* we want them in the same directory */
|
||||
.attrs = wusbhc_attrs,
|
||||
};
|
||||
|
||||
/*
|
||||
* Create a wusbhc instance
|
||||
*
|
||||
* NOTEs:
|
||||
*
|
||||
* - assumes *wusbhc has been zeroed and wusbhc->usb_hcd has been
|
||||
* initialized but not added.
|
||||
*
|
||||
* - fill out ports_max, mmcies_max and mmcie_{add,rm} before calling.
|
||||
*
|
||||
* - fill out wusbhc->uwb_rc and refcount it before calling
|
||||
* - fill out the wusbhc->sec_modes array
|
||||
*/
|
||||
int wusbhc_create(struct wusbhc *wusbhc)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
wusbhc->trust_timeout = WUSB_TRUST_TIMEOUT_MS;
|
||||
mutex_init(&wusbhc->mutex);
|
||||
result = wusbhc_mmcie_create(wusbhc);
|
||||
if (result < 0)
|
||||
goto error_mmcie_create;
|
||||
result = wusbhc_devconnect_create(wusbhc);
|
||||
if (result < 0)
|
||||
goto error_devconnect_create;
|
||||
result = wusbhc_rh_create(wusbhc);
|
||||
if (result < 0)
|
||||
goto error_rh_create;
|
||||
result = wusbhc_sec_create(wusbhc);
|
||||
if (result < 0)
|
||||
goto error_sec_create;
|
||||
return 0;
|
||||
|
||||
error_sec_create:
|
||||
wusbhc_rh_destroy(wusbhc);
|
||||
error_rh_create:
|
||||
wusbhc_devconnect_destroy(wusbhc);
|
||||
error_devconnect_create:
|
||||
wusbhc_mmcie_destroy(wusbhc);
|
||||
error_mmcie_create:
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_create);
|
||||
|
||||
static inline struct kobject *wusbhc_kobj(struct wusbhc *wusbhc)
|
||||
{
|
||||
return &wusbhc->usb_hcd.self.controller->kobj;
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase B of a wusbhc instance creation
|
||||
*
|
||||
* Creates fields that depend on wusbhc->usb_hcd having been
|
||||
* added. This is where we create the sysfs files in
|
||||
* /sys/class/usb_host/usb_hostX/.
|
||||
*
|
||||
* NOTE: Assumes wusbhc->usb_hcd has been already added by the upper
|
||||
* layer (hwahc or whci)
|
||||
*/
|
||||
int wusbhc_b_create(struct wusbhc *wusbhc)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = wusbhc->usb_hcd.self.controller;
|
||||
|
||||
result = sysfs_create_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot register WUSBHC attributes: %d\n", result);
|
||||
goto error_create_attr_group;
|
||||
}
|
||||
|
||||
result = wusbhc_pal_register(wusbhc);
|
||||
if (result < 0)
|
||||
goto error_pal_register;
|
||||
return 0;
|
||||
|
||||
error_pal_register:
|
||||
sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group);
|
||||
error_create_attr_group:
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_b_create);
|
||||
|
||||
void wusbhc_b_destroy(struct wusbhc *wusbhc)
|
||||
{
|
||||
wusbhc_pal_unregister(wusbhc);
|
||||
sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_b_destroy);
|
||||
|
||||
void wusbhc_destroy(struct wusbhc *wusbhc)
|
||||
{
|
||||
wusbhc_sec_destroy(wusbhc);
|
||||
wusbhc_rh_destroy(wusbhc);
|
||||
wusbhc_devconnect_destroy(wusbhc);
|
||||
wusbhc_mmcie_destroy(wusbhc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_destroy);
|
||||
|
||||
struct workqueue_struct *wusbd;
|
||||
EXPORT_SYMBOL_GPL(wusbd);
|
||||
|
||||
/*
|
||||
* WUSB Cluster ID allocation map
|
||||
*
|
||||
* Each WUSB bus in a channel is identified with a Cluster Id in the
|
||||
* unauth address pace (WUSB1.0[4.3]). We take the range 0xe0 to 0xff
|
||||
* (that's space for 31 WUSB controllers, as 0xff can't be taken). We
|
||||
* start taking from 0xff, 0xfe, 0xfd... (hence the += or -= 0xff).
|
||||
*
|
||||
* For each one we taken, we pin it in the bitap
|
||||
*/
|
||||
#define CLUSTER_IDS 32
|
||||
static DECLARE_BITMAP(wusb_cluster_id_table, CLUSTER_IDS);
|
||||
static DEFINE_SPINLOCK(wusb_cluster_ids_lock);
|
||||
|
||||
/*
|
||||
* Get a WUSB Cluster ID
|
||||
*
|
||||
* Need to release with wusb_cluster_id_put() when done w/ it.
|
||||
*/
|
||||
/* FIXME: coordinate with the choose_addres() from the USB stack */
|
||||
/* we want to leave the top of the 128 range for cluster addresses and
|
||||
* the bottom for device addresses (as we map them one on one with
|
||||
* ports). */
|
||||
u8 wusb_cluster_id_get(void)
|
||||
{
|
||||
u8 id;
|
||||
spin_lock(&wusb_cluster_ids_lock);
|
||||
id = find_first_zero_bit(wusb_cluster_id_table, CLUSTER_IDS);
|
||||
if (id > CLUSTER_IDS) {
|
||||
id = 0;
|
||||
goto out;
|
||||
}
|
||||
set_bit(id, wusb_cluster_id_table);
|
||||
id = (u8) 0xff - id;
|
||||
out:
|
||||
spin_unlock(&wusb_cluster_ids_lock);
|
||||
return id;
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusb_cluster_id_get);
|
||||
|
||||
/*
|
||||
* Release a WUSB Cluster ID
|
||||
*
|
||||
* Obtained it with wusb_cluster_id_get()
|
||||
*/
|
||||
void wusb_cluster_id_put(u8 id)
|
||||
{
|
||||
id = 0xff - id;
|
||||
BUG_ON(id >= CLUSTER_IDS);
|
||||
spin_lock(&wusb_cluster_ids_lock);
|
||||
WARN_ON(!test_bit(id, wusb_cluster_id_table));
|
||||
clear_bit(id, wusb_cluster_id_table);
|
||||
spin_unlock(&wusb_cluster_ids_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusb_cluster_id_put);
|
||||
|
||||
/**
|
||||
* wusbhc_giveback_urb - return an URB to the USB core
|
||||
* @wusbhc: the host controller the URB is from.
|
||||
* @urb: the URB.
|
||||
* @status: the URB's status.
|
||||
*
|
||||
* Return an URB to the USB core doing some additional WUSB specific
|
||||
* processing.
|
||||
*
|
||||
* - After a successful transfer, update the trust timeout timestamp
|
||||
* for the WUSB device.
|
||||
*
|
||||
* - [WUSB] sections 4.13 and 7.5.1 specifies the stop retrasmittion
|
||||
* condition for the WCONNECTACK_IE is that the host has observed
|
||||
* the associated device responding to a control transfer.
|
||||
*/
|
||||
void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, int status)
|
||||
{
|
||||
struct wusb_dev *wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev);
|
||||
|
||||
if (status == 0) {
|
||||
wusb_dev->entry_ts = jiffies;
|
||||
|
||||
/* wusbhc_devconnect_acked() can't be called from from
|
||||
atomic context so defer it to a work queue. */
|
||||
if (!list_empty(&wusb_dev->cack_node))
|
||||
queue_work(wusbd, &wusb_dev->devconnect_acked_work);
|
||||
}
|
||||
|
||||
usb_hcd_giveback_urb(&wusbhc->usb_hcd, urb, status);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_giveback_urb);
|
||||
|
||||
/**
|
||||
* wusbhc_reset_all - reset the HC hardware
|
||||
* @wusbhc: the host controller to reset.
|
||||
*
|
||||
* Request a full hardware reset of the chip. This will also reset
|
||||
* the radio controller and any other PALs.
|
||||
*/
|
||||
void wusbhc_reset_all(struct wusbhc *wusbhc)
|
||||
{
|
||||
uwb_rc_reset_all(wusbhc->uwb_rc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wusbhc_reset_all);
|
||||
|
||||
static struct notifier_block wusb_usb_notifier = {
|
||||
.notifier_call = wusb_usb_ncb,
|
||||
.priority = INT_MAX /* Need to be called first of all */
|
||||
};
|
||||
|
||||
static int __init wusbcore_init(void)
|
||||
{
|
||||
int result;
|
||||
result = wusb_crypto_init();
|
||||
if (result < 0)
|
||||
goto error_crypto_init;
|
||||
/* WQ is singlethread because we need to serialize notifications */
|
||||
wusbd = create_singlethread_workqueue("wusbd");
|
||||
if (wusbd == NULL) {
|
||||
result = -ENOMEM;
|
||||
printk(KERN_ERR "WUSB-core: Cannot create wusbd workqueue\n");
|
||||
goto error_wusbd_create;
|
||||
}
|
||||
usb_register_notify(&wusb_usb_notifier);
|
||||
bitmap_zero(wusb_cluster_id_table, CLUSTER_IDS);
|
||||
set_bit(0, wusb_cluster_id_table); /* reserve Cluster ID 0xff */
|
||||
return 0;
|
||||
|
||||
error_wusbd_create:
|
||||
wusb_crypto_exit();
|
||||
error_crypto_init:
|
||||
return result;
|
||||
|
||||
}
|
||||
module_init(wusbcore_init);
|
||||
|
||||
static void __exit wusbcore_exit(void)
|
||||
{
|
||||
clear_bit(0, wusb_cluster_id_table);
|
||||
if (!bitmap_empty(wusb_cluster_id_table, CLUSTER_IDS)) {
|
||||
char buf[256];
|
||||
bitmap_scnprintf(buf, sizeof(buf), wusb_cluster_id_table,
|
||||
CLUSTER_IDS);
|
||||
printk(KERN_ERR "BUG: WUSB Cluster IDs not released "
|
||||
"on exit: %s\n", buf);
|
||||
WARN_ON(1);
|
||||
}
|
||||
usb_unregister_notify(&wusb_usb_notifier);
|
||||
destroy_workqueue(wusbd);
|
||||
wusb_crypto_exit();
|
||||
}
|
||||
module_exit(wusbcore_exit);
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("Wireless USB core");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,495 @@
|
|||
/*
|
||||
* Wireless USB Host Controller
|
||||
* Common infrastructure for WHCI and HWA WUSB-HC drivers
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This driver implements parts common to all Wireless USB Host
|
||||
* Controllers (struct wusbhc, embedding a struct usb_hcd) and is used
|
||||
* by:
|
||||
*
|
||||
* - hwahc: HWA, USB-dongle that implements a Wireless USB host
|
||||
* controller, (Wireless USB 1.0 Host-Wire-Adapter specification).
|
||||
*
|
||||
* - whci: WHCI, a PCI card with a wireless host controller
|
||||
* (Wireless Host Controller Interface 1.0 specification).
|
||||
*
|
||||
* Check out the Design-overview.txt file in the source documentation
|
||||
* for other details on the implementation.
|
||||
*
|
||||
* Main blocks:
|
||||
*
|
||||
* rh Root Hub emulation (part of the HCD glue)
|
||||
*
|
||||
* devconnect Handle all the issues related to device connection,
|
||||
* authentication, disconnection, timeout, reseting,
|
||||
* keepalives, etc.
|
||||
*
|
||||
* mmc MMC IE broadcasting handling
|
||||
*
|
||||
* A host controller driver just initializes its stuff and as part of
|
||||
* that, creates a 'struct wusbhc' instance that handles all the
|
||||
* common WUSB mechanisms. Links in the function ops that are specific
|
||||
* to it and then registers the host controller. Ready to run.
|
||||
*/
|
||||
|
||||
#ifndef __WUSBHC_H__
|
||||
#define __WUSBHC_H__
|
||||
|
||||
#include <linux/usb.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/workqueue.h>
|
||||
/* FIXME: Yes, I know: BAD--it's not my fault the USB HC iface is not
|
||||
* public */
|
||||
#include <linux/../../drivers/usb/core/hcd.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/usb/wusb.h>
|
||||
|
||||
|
||||
/**
|
||||
* Wireless USB device
|
||||
*
|
||||
* Describe a WUSB device connected to the cluster. This struct
|
||||
* belongs to the 'struct wusb_port' it is attached to and it is
|
||||
* responsible for putting and clearing the pointer to it.
|
||||
*
|
||||
* Note this "complements" the 'struct usb_device' that the usb_hcd
|
||||
* keeps for each connected USB device. However, it extends some
|
||||
* information that is not available (there is no hcpriv ptr in it!)
|
||||
* *and* most importantly, it's life cycle is different. It is created
|
||||
* as soon as we get a DN_Connect (connect request notification) from
|
||||
* the device through the WUSB host controller; the USB stack doesn't
|
||||
* create the device until we authenticate it. FIXME: this will
|
||||
* change.
|
||||
*
|
||||
* @bos: This is allocated when the BOS descriptors are read from
|
||||
* the device and freed upon the wusb_dev struct dying.
|
||||
* @wusb_cap_descr: points into @bos, and has been verified to be size
|
||||
* safe.
|
||||
*/
|
||||
struct wusb_dev {
|
||||
struct kref refcnt;
|
||||
struct wusbhc *wusbhc;
|
||||
struct list_head cack_node; /* Connect-Ack list */
|
||||
u8 port_idx;
|
||||
u8 addr;
|
||||
u8 beacon_type:4;
|
||||
struct usb_encryption_descriptor ccm1_etd;
|
||||
struct wusb_ckhdid cdid;
|
||||
unsigned long entry_ts;
|
||||
struct usb_bos_descriptor *bos;
|
||||
struct usb_wireless_cap_descriptor *wusb_cap_descr;
|
||||
struct uwb_mas_bm availability;
|
||||
struct work_struct devconnect_acked_work;
|
||||
struct urb *set_gtk_urb;
|
||||
struct usb_ctrlrequest *set_gtk_req;
|
||||
struct usb_device *usb_dev;
|
||||
};
|
||||
|
||||
#define WUSB_DEV_ADDR_UNAUTH 0x80
|
||||
|
||||
static inline void wusb_dev_init(struct wusb_dev *wusb_dev)
|
||||
{
|
||||
kref_init(&wusb_dev->refcnt);
|
||||
/* no need to init the cack_node */
|
||||
}
|
||||
|
||||
extern void wusb_dev_destroy(struct kref *_wusb_dev);
|
||||
|
||||
static inline struct wusb_dev *wusb_dev_get(struct wusb_dev *wusb_dev)
|
||||
{
|
||||
kref_get(&wusb_dev->refcnt);
|
||||
return wusb_dev;
|
||||
}
|
||||
|
||||
static inline void wusb_dev_put(struct wusb_dev *wusb_dev)
|
||||
{
|
||||
kref_put(&wusb_dev->refcnt, wusb_dev_destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wireless USB Host Controlller root hub "fake" ports
|
||||
* (state and device information)
|
||||
*
|
||||
* Wireless USB is wireless, so there are no ports; but we
|
||||
* fake'em. Each RC can connect a max of devices at the same time
|
||||
* (given in the Wireless Adapter descriptor, bNumPorts or WHCI's
|
||||
* caps), referred to in wusbhc->ports_max.
|
||||
*
|
||||
* See rh.c for more information.
|
||||
*
|
||||
* The @status and @change use the same bits as in USB2.0[11.24.2.7],
|
||||
* so we don't have to do much when getting the port's status.
|
||||
*
|
||||
* WUSB1.0[7.1], USB2.0[11.24.2.7.1,fig 11-10],
|
||||
* include/linux/usb_ch9.h (#define USB_PORT_STAT_*)
|
||||
*/
|
||||
struct wusb_port {
|
||||
u16 status;
|
||||
u16 change;
|
||||
struct wusb_dev *wusb_dev; /* connected device's info */
|
||||
unsigned reset_count;
|
||||
u32 ptk_tkid;
|
||||
};
|
||||
|
||||
/**
|
||||
* WUSB Host Controller specifics
|
||||
*
|
||||
* All fields that are common to all Wireless USB controller types
|
||||
* (HWA and WHCI) are grouped here. Host Controller
|
||||
* functions/operations that only deal with general Wireless USB HC
|
||||
* issues use this data type to refer to the host.
|
||||
*
|
||||
* @usb_hcd Instantiation of a USB host controller
|
||||
* (initialized by upper layer [HWA=HC or WHCI].
|
||||
*
|
||||
* @dev Device that implements this; initialized by the
|
||||
* upper layer (HWA-HC, WHCI...); this device should
|
||||
* have a refcount.
|
||||
*
|
||||
* @trust_timeout After this time without hearing for device
|
||||
* activity, we consider the device gone and we have to
|
||||
* re-authenticate.
|
||||
*
|
||||
* Can be accessed w/o locking--however, read to a
|
||||
* local variable then use.
|
||||
*
|
||||
* @chid WUSB Cluster Host ID: this is supposed to be a
|
||||
* unique value that doesn't change across reboots (so
|
||||
* that your devices do not require re-association).
|
||||
*
|
||||
* Read/Write protected by @mutex
|
||||
*
|
||||
* @dev_info This array has ports_max elements. It is used to
|
||||
* give the HC information about the WUSB devices (see
|
||||
* 'struct wusb_dev_info').
|
||||
*
|
||||
* For HWA we need to allocate it in heap; for WHCI it
|
||||
* needs to be permanently mapped, so we keep it for
|
||||
* both and make it easy. Call wusbhc->dev_info_set()
|
||||
* to update an entry.
|
||||
*
|
||||
* @ports_max Number of simultaneous device connections (fake
|
||||
* ports) this HC will take. Read-only.
|
||||
*
|
||||
* @port Array of port status for each fake root port. Guaranteed to
|
||||
* always be the same lenght during device existence
|
||||
* [this allows for some unlocked but referenced reading].
|
||||
*
|
||||
* @mmcies_max Max number of Information Elements this HC can send
|
||||
* in its MMC. Read-only.
|
||||
*
|
||||
* @mmcie_add HC specific operation (WHCI or HWA) for adding an
|
||||
* MMCIE.
|
||||
*
|
||||
* @mmcie_rm HC specific operation (WHCI or HWA) for removing an
|
||||
* MMCIE.
|
||||
*
|
||||
* @enc_types Array which describes the encryptions methods
|
||||
* supported by the host as described in WUSB1.0 --
|
||||
* one entry per supported method. As of WUSB1.0 there
|
||||
* is only four methods, we make space for eight just in
|
||||
* case they decide to add some more (and pray they do
|
||||
* it in sequential order). if 'enc_types[enc_method]
|
||||
* != 0', then it is supported by the host. enc_method
|
||||
* is USB_ENC_TYPE*.
|
||||
*
|
||||
* @set_ptk: Set the PTK and enable encryption for a device. Or, if
|
||||
* the supplied key is NULL, disable encryption for that
|
||||
* device.
|
||||
*
|
||||
* @set_gtk: Set the GTK to be used for all future broadcast packets
|
||||
* (i.e., MMCs). With some hardware, setting the GTK may start
|
||||
* MMC transmission.
|
||||
*
|
||||
* NOTE:
|
||||
*
|
||||
* - If wusb_dev->usb_dev is not NULL, then usb_dev is valid
|
||||
* (wusb_dev has a refcount on it). Likewise, if usb_dev->wusb_dev
|
||||
* is not NULL, usb_dev->wusb_dev is valid (usb_dev keeps a
|
||||
* refcount on it).
|
||||
*
|
||||
* Most of the times when you need to use it, it will be non-NULL,
|
||||
* so there is no real need to check for it (wusb_dev will
|
||||
* dissapear before usb_dev).
|
||||
*
|
||||
* - The following fields need to be filled out before calling
|
||||
* wusbhc_create(): ports_max, mmcies_max, mmcie_{add,rm}.
|
||||
*
|
||||
* - there is no wusbhc_init() method, we do everything in
|
||||
* wusbhc_create().
|
||||
*
|
||||
* - Creation is done in two phases, wusbhc_create() and
|
||||
* wusbhc_create_b(); b are the parts that need to be called after
|
||||
* calling usb_hcd_add(&wusbhc->usb_hcd).
|
||||
*/
|
||||
struct wusbhc {
|
||||
struct usb_hcd usb_hcd; /* HAS TO BE 1st */
|
||||
struct device *dev;
|
||||
struct uwb_rc *uwb_rc;
|
||||
struct uwb_pal pal;
|
||||
|
||||
unsigned trust_timeout; /* in jiffies */
|
||||
struct wuie_host_info *wuie_host_info; /* Includes CHID */
|
||||
|
||||
struct mutex mutex; /* locks everything else */
|
||||
u16 cluster_id; /* Wireless USB Cluster ID */
|
||||
struct wusb_port *port; /* Fake port status handling */
|
||||
struct wusb_dev_info *dev_info; /* for Set Device Info mgmt */
|
||||
u8 ports_max;
|
||||
unsigned active:1; /* currently xmit'ing MMCs */
|
||||
struct wuie_keep_alive keep_alive_ie; /* protected by mutex */
|
||||
struct delayed_work keep_alive_timer;
|
||||
struct list_head cack_list; /* Connect acknowledging */
|
||||
size_t cack_count; /* protected by 'mutex' */
|
||||
struct wuie_connect_ack cack_ie;
|
||||
struct uwb_rsv *rsv; /* cluster bandwidth reservation */
|
||||
|
||||
struct mutex mmcie_mutex; /* MMC WUIE handling */
|
||||
struct wuie_hdr **mmcie; /* WUIE array */
|
||||
u8 mmcies_max;
|
||||
/* FIXME: make wusbhc_ops? */
|
||||
int (*start)(struct wusbhc *wusbhc);
|
||||
void (*stop)(struct wusbhc *wusbhc);
|
||||
int (*mmcie_add)(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
|
||||
u8 handle, struct wuie_hdr *wuie);
|
||||
int (*mmcie_rm)(struct wusbhc *wusbhc, u8 handle);
|
||||
int (*dev_info_set)(struct wusbhc *, struct wusb_dev *wusb_dev);
|
||||
int (*bwa_set)(struct wusbhc *wusbhc, s8 stream_index,
|
||||
const struct uwb_mas_bm *);
|
||||
int (*set_ptk)(struct wusbhc *wusbhc, u8 port_idx,
|
||||
u32 tkid, const void *key, size_t key_size);
|
||||
int (*set_gtk)(struct wusbhc *wusbhc,
|
||||
u32 tkid, const void *key, size_t key_size);
|
||||
int (*set_num_dnts)(struct wusbhc *wusbhc, u8 interval, u8 slots);
|
||||
|
||||
struct {
|
||||
struct usb_key_descriptor descr;
|
||||
u8 data[16]; /* GTK key data */
|
||||
} __attribute__((packed)) gtk;
|
||||
u8 gtk_index;
|
||||
u32 gtk_tkid;
|
||||
struct work_struct gtk_rekey_done_work;
|
||||
int pending_set_gtks;
|
||||
|
||||
struct usb_encryption_descriptor *ccm1_etd;
|
||||
};
|
||||
|
||||
#define usb_hcd_to_wusbhc(u) container_of((u), struct wusbhc, usb_hcd)
|
||||
|
||||
|
||||
extern int wusbhc_create(struct wusbhc *);
|
||||
extern int wusbhc_b_create(struct wusbhc *);
|
||||
extern void wusbhc_b_destroy(struct wusbhc *);
|
||||
extern void wusbhc_destroy(struct wusbhc *);
|
||||
extern int wusb_dev_sysfs_add(struct wusbhc *, struct usb_device *,
|
||||
struct wusb_dev *);
|
||||
extern void wusb_dev_sysfs_rm(struct wusb_dev *);
|
||||
extern int wusbhc_sec_create(struct wusbhc *);
|
||||
extern int wusbhc_sec_start(struct wusbhc *);
|
||||
extern void wusbhc_sec_stop(struct wusbhc *);
|
||||
extern void wusbhc_sec_destroy(struct wusbhc *);
|
||||
extern void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb,
|
||||
int status);
|
||||
void wusbhc_reset_all(struct wusbhc *wusbhc);
|
||||
|
||||
int wusbhc_pal_register(struct wusbhc *wusbhc);
|
||||
void wusbhc_pal_unregister(struct wusbhc *wusbhc);
|
||||
|
||||
/*
|
||||
* Return @usb_dev's @usb_hcd (properly referenced) or NULL if gone
|
||||
*
|
||||
* @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr)
|
||||
*
|
||||
* This is a safe assumption as @usb_dev->bus is referenced all the
|
||||
* time during the @usb_dev life cycle.
|
||||
*/
|
||||
static inline struct usb_hcd *usb_hcd_get_by_usb_dev(struct usb_device *usb_dev)
|
||||
{
|
||||
struct usb_hcd *usb_hcd;
|
||||
usb_hcd = container_of(usb_dev->bus, struct usb_hcd, self);
|
||||
return usb_get_hcd(usb_hcd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Increment the reference count on a wusbhc.
|
||||
*
|
||||
* @wusbhc's life cycle is identical to that of the underlying usb_hcd.
|
||||
*/
|
||||
static inline struct wusbhc *wusbhc_get(struct wusbhc *wusbhc)
|
||||
{
|
||||
return usb_get_hcd(&wusbhc->usb_hcd) ? wusbhc : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the wusbhc associated to a @usb_dev
|
||||
*
|
||||
* @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr)
|
||||
*
|
||||
* @returns: wusbhc for @usb_dev; NULL if the @usb_dev is being torn down.
|
||||
* WARNING: referenced at the usb_hcd level, unlocked
|
||||
*
|
||||
* FIXME: move offline
|
||||
*/
|
||||
static inline struct wusbhc *wusbhc_get_by_usb_dev(struct usb_device *usb_dev)
|
||||
{
|
||||
struct wusbhc *wusbhc = NULL;
|
||||
struct usb_hcd *usb_hcd;
|
||||
if (usb_dev->devnum > 1 && !usb_dev->wusb) {
|
||||
/* but root hubs */
|
||||
dev_err(&usb_dev->dev, "devnum %d wusb %d\n", usb_dev->devnum,
|
||||
usb_dev->wusb);
|
||||
BUG_ON(usb_dev->devnum > 1 && !usb_dev->wusb);
|
||||
}
|
||||
usb_hcd = usb_hcd_get_by_usb_dev(usb_dev);
|
||||
if (usb_hcd == NULL)
|
||||
return NULL;
|
||||
BUG_ON(usb_hcd->wireless == 0);
|
||||
return wusbhc = usb_hcd_to_wusbhc(usb_hcd);
|
||||
}
|
||||
|
||||
|
||||
static inline void wusbhc_put(struct wusbhc *wusbhc)
|
||||
{
|
||||
usb_put_hcd(&wusbhc->usb_hcd);
|
||||
}
|
||||
|
||||
int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid);
|
||||
void wusbhc_stop(struct wusbhc *wusbhc);
|
||||
extern int wusbhc_chid_set(struct wusbhc *, const struct wusb_ckhdid *);
|
||||
|
||||
/* Device connect handling */
|
||||
extern int wusbhc_devconnect_create(struct wusbhc *);
|
||||
extern void wusbhc_devconnect_destroy(struct wusbhc *);
|
||||
extern int wusbhc_devconnect_start(struct wusbhc *wusbhc,
|
||||
const struct wusb_ckhdid *chid);
|
||||
extern void wusbhc_devconnect_stop(struct wusbhc *wusbhc);
|
||||
extern int wusbhc_devconnect_auth(struct wusbhc *, u8);
|
||||
extern void wusbhc_handle_dn(struct wusbhc *, u8 srcaddr,
|
||||
struct wusb_dn_hdr *dn_hdr, size_t size);
|
||||
extern int wusbhc_dev_reset(struct wusbhc *wusbhc, u8 port);
|
||||
extern void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port);
|
||||
extern int wusb_usb_ncb(struct notifier_block *nb, unsigned long val,
|
||||
void *priv);
|
||||
extern int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev,
|
||||
u8 addr);
|
||||
|
||||
/* Wireless USB fake Root Hub methods */
|
||||
extern int wusbhc_rh_create(struct wusbhc *);
|
||||
extern void wusbhc_rh_destroy(struct wusbhc *);
|
||||
|
||||
extern int wusbhc_rh_status_data(struct usb_hcd *, char *);
|
||||
extern int wusbhc_rh_control(struct usb_hcd *, u16, u16, u16, char *, u16);
|
||||
extern int wusbhc_rh_suspend(struct usb_hcd *);
|
||||
extern int wusbhc_rh_resume(struct usb_hcd *);
|
||||
extern int wusbhc_rh_start_port_reset(struct usb_hcd *, unsigned);
|
||||
|
||||
/* MMC handling */
|
||||
extern int wusbhc_mmcie_create(struct wusbhc *);
|
||||
extern void wusbhc_mmcie_destroy(struct wusbhc *);
|
||||
extern int wusbhc_mmcie_set(struct wusbhc *, u8 interval, u8 repeat_cnt,
|
||||
struct wuie_hdr *);
|
||||
extern void wusbhc_mmcie_rm(struct wusbhc *, struct wuie_hdr *);
|
||||
|
||||
/* Bandwidth reservation */
|
||||
int wusbhc_rsv_establish(struct wusbhc *wusbhc);
|
||||
void wusbhc_rsv_terminate(struct wusbhc *wusbhc);
|
||||
|
||||
/*
|
||||
* I've always said
|
||||
* I wanted a wedding in a church...
|
||||
*
|
||||
* but lately I've been thinking about
|
||||
* the Botanical Gardens.
|
||||
*
|
||||
* We could do it by the tulips.
|
||||
* It'll be beautiful
|
||||
*
|
||||
* --Security!
|
||||
*/
|
||||
extern int wusb_dev_sec_add(struct wusbhc *, struct usb_device *,
|
||||
struct wusb_dev *);
|
||||
extern void wusb_dev_sec_rm(struct wusb_dev *) ;
|
||||
extern int wusb_dev_4way_handshake(struct wusbhc *, struct wusb_dev *,
|
||||
struct wusb_ckhdid *ck);
|
||||
void wusbhc_gtk_rekey(struct wusbhc *wusbhc);
|
||||
|
||||
|
||||
/* WUSB Cluster ID handling */
|
||||
extern u8 wusb_cluster_id_get(void);
|
||||
extern void wusb_cluster_id_put(u8);
|
||||
|
||||
/*
|
||||
* wusb_port_by_idx - return the port associated to a zero-based port index
|
||||
*
|
||||
* NOTE: valid without locking as long as wusbhc is referenced (as the
|
||||
* number of ports doesn't change). The data pointed to has to
|
||||
* be verified though :)
|
||||
*/
|
||||
static inline struct wusb_port *wusb_port_by_idx(struct wusbhc *wusbhc,
|
||||
u8 port_idx)
|
||||
{
|
||||
return &wusbhc->port[port_idx];
|
||||
}
|
||||
|
||||
/*
|
||||
* wusb_port_no_to_idx - Convert port number (per usb_dev->portnum) to
|
||||
* a port_idx.
|
||||
*
|
||||
* USB stack USB ports are 1 based!!
|
||||
*
|
||||
* NOTE: only valid for WUSB devices!!!
|
||||
*/
|
||||
static inline u8 wusb_port_no_to_idx(u8 port_no)
|
||||
{
|
||||
return port_no - 1;
|
||||
}
|
||||
|
||||
extern struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *,
|
||||
struct usb_device *);
|
||||
|
||||
/*
|
||||
* Return a referenced wusb_dev given a @usb_dev
|
||||
*
|
||||
* Returns NULL if the usb_dev is being torn down.
|
||||
*
|
||||
* FIXME: move offline
|
||||
*/
|
||||
static inline
|
||||
struct wusb_dev *wusb_dev_get_by_usb_dev(struct usb_device *usb_dev)
|
||||
{
|
||||
struct wusbhc *wusbhc;
|
||||
struct wusb_dev *wusb_dev;
|
||||
wusbhc = wusbhc_get_by_usb_dev(usb_dev);
|
||||
if (wusbhc == NULL)
|
||||
return NULL;
|
||||
mutex_lock(&wusbhc->mutex);
|
||||
wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev);
|
||||
mutex_unlock(&wusbhc->mutex);
|
||||
wusbhc_put(wusbhc);
|
||||
return wusb_dev;
|
||||
}
|
||||
|
||||
/* Misc */
|
||||
|
||||
extern struct workqueue_struct *wusbd;
|
||||
#endif /* #ifndef __WUSBHC_H__ */
|
|
@ -0,0 +1,90 @@
|
|||
#
|
||||
# UWB device configuration
|
||||
#
|
||||
|
||||
menuconfig UWB
|
||||
tristate "Ultra Wideband devices (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL
|
||||
depends on PCI
|
||||
default n
|
||||
help
|
||||
UWB is a high-bandwidth, low-power, point-to-point radio
|
||||
technology using a wide spectrum (3.1-10.6GHz). It is
|
||||
optimized for in-room use (480Mbps at 2 meters, 110Mbps at
|
||||
10m). It serves as the transport layer for other protocols,
|
||||
such as Wireless USB (WUSB), IP (WLP) and upcoming
|
||||
Bluetooth and 1394
|
||||
|
||||
The topology is peer to peer; however, higher level
|
||||
protocols (such as WUSB) might impose a master/slave
|
||||
relationship.
|
||||
|
||||
Say Y here if your computer has UWB radio controllers (USB or PCI)
|
||||
based. You will need to enable the radio controllers
|
||||
below. It is ok to select all of them, no harm done.
|
||||
|
||||
For more help check the UWB and WUSB related files in
|
||||
<file:Documentation/usb/>.
|
||||
|
||||
To compile the UWB stack as a module, choose M here.
|
||||
|
||||
if UWB
|
||||
|
||||
config UWB_HWA
|
||||
tristate "UWB Radio Control driver for WUSB-compliant USB dongles (HWA)"
|
||||
depends on USB
|
||||
help
|
||||
This driver enables the radio controller for HWA USB
|
||||
devices. HWA stands for Host Wire Adapter, and it is a UWB
|
||||
Radio Controller connected to your system via USB. Most of
|
||||
them come with a Wireless USB host controller also.
|
||||
|
||||
To compile this driver select Y (built in) or M (module). It
|
||||
is safe to select any even if you do not have the hardware.
|
||||
|
||||
config UWB_WHCI
|
||||
tristate "UWB Radio Control driver for WHCI-compliant cards"
|
||||
depends on PCI
|
||||
help
|
||||
This driver enables the radio controller for WHCI cards.
|
||||
|
||||
WHCI is an specification developed by Intel
|
||||
(http://www.intel.com/technology/comms/wusb/whci.htm) much
|
||||
in the spirit of USB's EHCI, but for UWB and Wireless USB
|
||||
radio/host controllers connected via memmory mapping (eg:
|
||||
PCI). Most of these cards come also with a Wireless USB host
|
||||
controller.
|
||||
|
||||
To compile this driver select Y (built in) or M (module). It
|
||||
is safe to select any even if you do not have the hardware.
|
||||
|
||||
config UWB_WLP
|
||||
tristate "Support WiMedia Link Protocol (Ethernet/IP over UWB)"
|
||||
depends on UWB && NET
|
||||
help
|
||||
This is a common library for drivers that implement
|
||||
networking over UWB.
|
||||
|
||||
config UWB_I1480U
|
||||
tristate "Support for Intel Wireless UWB Link 1480 HWA"
|
||||
depends on UWB_HWA
|
||||
select FW_LOADER
|
||||
help
|
||||
This driver enables support for the i1480 when connected via
|
||||
USB. It consists of a firmware uploader that will enable it
|
||||
to behave as an HWA device.
|
||||
|
||||
To compile this driver select Y (built in) or M (module). It
|
||||
is safe to select any even if you do not have the hardware.
|
||||
|
||||
config UWB_I1480U_WLP
|
||||
tristate "Support for Intel Wireless UWB Link 1480 HWA's WLP interface"
|
||||
depends on UWB_I1480U && UWB_WLP && NET
|
||||
help
|
||||
This driver enables WLP support for the i1480 when connected via
|
||||
USB. WLP is the WiMedia Link Protocol, or IP over UWB.
|
||||
|
||||
To compile this driver select Y (built in) or M (module). It
|
||||
is safe to select any even if you don't have the hardware.
|
||||
|
||||
endif # UWB
|
|
@ -0,0 +1,29 @@
|
|||
obj-$(CONFIG_UWB) += uwb.o
|
||||
obj-$(CONFIG_UWB_WLP) += wlp/
|
||||
obj-$(CONFIG_UWB_WHCI) += umc.o whci.o whc-rc.o
|
||||
obj-$(CONFIG_UWB_HWA) += hwa-rc.o
|
||||
obj-$(CONFIG_UWB_I1480U) += i1480/
|
||||
|
||||
uwb-objs := \
|
||||
address.o \
|
||||
beacon.o \
|
||||
driver.o \
|
||||
drp.o \
|
||||
drp-avail.o \
|
||||
drp-ie.o \
|
||||
est.o \
|
||||
ie.o \
|
||||
lc-dev.o \
|
||||
lc-rc.o \
|
||||
neh.o \
|
||||
pal.o \
|
||||
reset.o \
|
||||
rsv.o \
|
||||
scan.o \
|
||||
uwb-debug.o \
|
||||
uwbd.o
|
||||
|
||||
umc-objs := \
|
||||
umc-bus.o \
|
||||
umc-dev.o \
|
||||
umc-drv.o
|
|
@ -0,0 +1,374 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Address management
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
|
||||
/** Device Address Management command */
|
||||
struct uwb_rc_cmd_dev_addr_mgmt {
|
||||
struct uwb_rccb rccb;
|
||||
u8 bmOperationType;
|
||||
u8 baAddr[6];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/**
|
||||
* Low level command for setting/getting UWB radio's addresses
|
||||
*
|
||||
* @hwarc: HWA Radio Control interface instance
|
||||
* @bmOperationType:
|
||||
* Set/get, MAC/DEV (see WUSB1.0[8.6.2.2])
|
||||
* @baAddr: address buffer--assumed to have enough data to hold
|
||||
* the address type requested.
|
||||
* @reply: Pointer to reply buffer (can be stack allocated)
|
||||
* @returns: 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* @cmd has to be allocated because USB cannot grok USB or vmalloc
|
||||
* buffers depending on your combination of host architecture.
|
||||
*/
|
||||
static
|
||||
int uwb_rc_dev_addr_mgmt(struct uwb_rc *rc,
|
||||
u8 bmOperationType, const u8 *baAddr,
|
||||
struct uwb_rc_evt_dev_addr_mgmt *reply)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rc_cmd_dev_addr_mgmt *cmd;
|
||||
|
||||
result = -ENOMEM;
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
goto error_kzalloc;
|
||||
cmd->rccb.bCommandType = UWB_RC_CET_GENERAL;
|
||||
cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_DEV_ADDR_MGMT);
|
||||
cmd->bmOperationType = bmOperationType;
|
||||
if (baAddr) {
|
||||
size_t size = 0;
|
||||
switch (bmOperationType >> 1) {
|
||||
case 0: size = 2; break;
|
||||
case 1: size = 6; break;
|
||||
default: BUG();
|
||||
}
|
||||
memcpy(cmd->baAddr, baAddr, size);
|
||||
}
|
||||
reply->rceb.bEventType = UWB_RC_CET_GENERAL;
|
||||
reply->rceb.wEvent = UWB_RC_CMD_DEV_ADDR_MGMT;
|
||||
result = uwb_rc_cmd(rc, "DEV-ADDR-MGMT",
|
||||
&cmd->rccb, sizeof(*cmd),
|
||||
&reply->rceb, sizeof(*reply));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
if (result < sizeof(*reply)) {
|
||||
dev_err(&rc->uwb_dev.dev,
|
||||
"DEV-ADDR-MGMT: not enough data replied: "
|
||||
"%d vs %zu bytes needed\n", result, sizeof(*reply));
|
||||
result = -ENOMSG;
|
||||
} else if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(&rc->uwb_dev.dev,
|
||||
"DEV-ADDR-MGMT: command execution failed: %s (%d)\n",
|
||||
uwb_rc_strerror(reply->bResultCode),
|
||||
reply->bResultCode);
|
||||
result = -EIO;
|
||||
} else
|
||||
result = 0;
|
||||
error_cmd:
|
||||
kfree(cmd);
|
||||
error_kzalloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the UWB RC MAC or device address.
|
||||
*
|
||||
* @rc: UWB Radio Controller
|
||||
* @_addr: Pointer to address to write [assumed to be either a
|
||||
* 'struct uwb_mac_addr *' or a 'struct uwb_dev_addr *'].
|
||||
* @type: Type of address to set (UWB_ADDR_DEV or UWB_ADDR_MAC).
|
||||
* @returns: 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* Some anal retentivity here: even if both 'struct
|
||||
* uwb_{dev,mac}_addr' have the actual byte array in the same offset
|
||||
* and I could just pass _addr to hwarc_cmd_dev_addr_mgmt(), I prefer
|
||||
* to use some syntatic sugar in case someday we decide to change the
|
||||
* format of the structs. The compiler will optimize it out anyway.
|
||||
*/
|
||||
static int uwb_rc_addr_set(struct uwb_rc *rc,
|
||||
const void *_addr, enum uwb_addr_type type)
|
||||
{
|
||||
int result;
|
||||
u8 bmOperationType = 0x1; /* Set address */
|
||||
const struct uwb_dev_addr *dev_addr = _addr;
|
||||
const struct uwb_mac_addr *mac_addr = _addr;
|
||||
struct uwb_rc_evt_dev_addr_mgmt reply;
|
||||
const u8 *baAddr;
|
||||
|
||||
result = -EINVAL;
|
||||
switch (type) {
|
||||
case UWB_ADDR_DEV:
|
||||
baAddr = dev_addr->data;
|
||||
break;
|
||||
case UWB_ADDR_MAC:
|
||||
baAddr = mac_addr->data;
|
||||
bmOperationType |= 0x2;
|
||||
break;
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
return uwb_rc_dev_addr_mgmt(rc, bmOperationType, baAddr, &reply);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the UWB radio's MAC or device address.
|
||||
*
|
||||
* @rc: UWB Radio Controller
|
||||
* @_addr: Where to write the address data [assumed to be either a
|
||||
* 'struct uwb_mac_addr *' or a 'struct uwb_dev_addr *'].
|
||||
* @type: Type of address to get (UWB_ADDR_DEV or UWB_ADDR_MAC).
|
||||
* @returns: 0 if ok (and *_addr set), < 0 errno code on error.
|
||||
*
|
||||
* See comment in uwb_rc_addr_set() about anal retentivity in the
|
||||
* type handling of the address variables.
|
||||
*/
|
||||
static int uwb_rc_addr_get(struct uwb_rc *rc,
|
||||
void *_addr, enum uwb_addr_type type)
|
||||
{
|
||||
int result;
|
||||
u8 bmOperationType = 0x0; /* Get address */
|
||||
struct uwb_rc_evt_dev_addr_mgmt evt;
|
||||
struct uwb_dev_addr *dev_addr = _addr;
|
||||
struct uwb_mac_addr *mac_addr = _addr;
|
||||
u8 *baAddr;
|
||||
|
||||
result = -EINVAL;
|
||||
switch (type) {
|
||||
case UWB_ADDR_DEV:
|
||||
baAddr = dev_addr->data;
|
||||
break;
|
||||
case UWB_ADDR_MAC:
|
||||
bmOperationType |= 0x2;
|
||||
baAddr = mac_addr->data;
|
||||
break;
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
result = uwb_rc_dev_addr_mgmt(rc, bmOperationType, baAddr, &evt);
|
||||
if (result == 0)
|
||||
switch (type) {
|
||||
case UWB_ADDR_DEV:
|
||||
memcpy(&dev_addr->data, evt.baAddr,
|
||||
sizeof(dev_addr->data));
|
||||
break;
|
||||
case UWB_ADDR_MAC:
|
||||
memcpy(&mac_addr->data, evt.baAddr,
|
||||
sizeof(mac_addr->data));
|
||||
break;
|
||||
default: /* shut gcc up */
|
||||
BUG();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/** Get @rc's MAC address to @addr */
|
||||
int uwb_rc_mac_addr_get(struct uwb_rc *rc,
|
||||
struct uwb_mac_addr *addr) {
|
||||
return uwb_rc_addr_get(rc, addr, UWB_ADDR_MAC);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_mac_addr_get);
|
||||
|
||||
|
||||
/** Get @rc's device address to @addr */
|
||||
int uwb_rc_dev_addr_get(struct uwb_rc *rc,
|
||||
struct uwb_dev_addr *addr) {
|
||||
return uwb_rc_addr_get(rc, addr, UWB_ADDR_DEV);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_dev_addr_get);
|
||||
|
||||
|
||||
/** Set @rc's address to @addr */
|
||||
int uwb_rc_mac_addr_set(struct uwb_rc *rc,
|
||||
const struct uwb_mac_addr *addr)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
result = uwb_rc_addr_set(rc, addr, UWB_ADDR_MAC);
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/** Set @rc's address to @addr */
|
||||
int uwb_rc_dev_addr_set(struct uwb_rc *rc,
|
||||
const struct uwb_dev_addr *addr)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
result = uwb_rc_addr_set(rc, addr, UWB_ADDR_DEV);
|
||||
rc->uwb_dev.dev_addr = *addr;
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Returns !0 if given address is already assigned to device. */
|
||||
int __uwb_mac_addr_assigned_check(struct device *dev, void *_addr)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_mac_addr *addr = _addr;
|
||||
|
||||
if (!uwb_mac_addr_cmp(addr, &uwb_dev->mac_addr))
|
||||
return !0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns !0 if given address is already assigned to device. */
|
||||
int __uwb_dev_addr_assigned_check(struct device *dev, void *_addr)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_dev_addr *addr = _addr;
|
||||
if (!uwb_dev_addr_cmp(addr, &uwb_dev->dev_addr))
|
||||
return !0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_dev_addr_assign - assigned a generated DevAddr to a radio controller
|
||||
* @rc: the (local) radio controller device requiring a new DevAddr
|
||||
*
|
||||
* A new DevAddr is required when:
|
||||
* - first setting up a radio controller
|
||||
* - if the hardware reports a DevAddr conflict
|
||||
*
|
||||
* The DevAddr is randomly generated in the generated DevAddr range
|
||||
* [0x100, 0xfeff]. The number of devices in a beacon group is limited
|
||||
* by mMaxBPLength (96) so this address space will never be exhausted.
|
||||
*
|
||||
* [ECMA-368] 17.1.1, 17.16.
|
||||
*/
|
||||
int uwb_rc_dev_addr_assign(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_dev_addr new_addr;
|
||||
|
||||
do {
|
||||
get_random_bytes(new_addr.data, sizeof(new_addr.data));
|
||||
} while (new_addr.data[0] == 0x00 || new_addr.data[0] == 0xff
|
||||
|| __uwb_dev_addr_assigned(rc, &new_addr));
|
||||
|
||||
return uwb_rc_dev_addr_set(rc, &new_addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwbd_evt_handle_rc_dev_addr_conflict - handle a DEV_ADDR_CONFLICT event
|
||||
* @evt: the DEV_ADDR_CONFLICT notification from the radio controller
|
||||
*
|
||||
* A new (non-conflicting) DevAddr is assigned to the radio controller.
|
||||
*
|
||||
* [ECMA-368] 17.1.1.1.
|
||||
*/
|
||||
int uwbd_evt_handle_rc_dev_addr_conflict(struct uwb_event *evt)
|
||||
{
|
||||
struct uwb_rc *rc = evt->rc;
|
||||
|
||||
return uwb_rc_dev_addr_assign(rc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print the 48-bit EUI MAC address of the radio controller when
|
||||
* reading /sys/class/uwb_rc/XX/mac_address
|
||||
*/
|
||||
static ssize_t uwb_rc_mac_addr_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_rc *rc = uwb_dev->rc;
|
||||
struct uwb_mac_addr addr;
|
||||
ssize_t result;
|
||||
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
result = uwb_rc_addr_get(rc, &addr, UWB_ADDR_MAC);
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
if (result >= 0) {
|
||||
result = uwb_mac_addr_print(buf, UWB_ADDR_STRSIZE, &addr);
|
||||
buf[result++] = '\n';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a 48 bit address written to /sys/class/uwb_rc/XX/mac_address
|
||||
* and if correct, set it.
|
||||
*/
|
||||
static ssize_t uwb_rc_mac_addr_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_rc *rc = uwb_dev->rc;
|
||||
struct uwb_mac_addr addr;
|
||||
ssize_t result;
|
||||
|
||||
result = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\n",
|
||||
&addr.data[0], &addr.data[1], &addr.data[2],
|
||||
&addr.data[3], &addr.data[4], &addr.data[5]);
|
||||
if (result != 6) {
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (is_multicast_ether_addr(addr.data)) {
|
||||
dev_err(&rc->uwb_dev.dev, "refusing to set multicast "
|
||||
"MAC address %s\n", buf);
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
result = uwb_rc_mac_addr_set(rc, &addr);
|
||||
if (result == 0)
|
||||
rc->uwb_dev.mac_addr = addr;
|
||||
out:
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
DEVICE_ATTR(mac_address, S_IRUGO | S_IWUSR, uwb_rc_mac_addr_show, uwb_rc_mac_addr_store);
|
||||
|
||||
/** Print @addr to @buf, @return bytes written */
|
||||
size_t __uwb_addr_print(char *buf, size_t buf_size, const unsigned char *addr,
|
||||
int type)
|
||||
{
|
||||
size_t result;
|
||||
if (type)
|
||||
result = scnprintf(buf, buf_size,
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
addr[0], addr[1], addr[2],
|
||||
addr[3], addr[4], addr[5]);
|
||||
else
|
||||
result = scnprintf(buf, buf_size, "%02x:%02x",
|
||||
addr[1], addr[0]);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__uwb_addr_print);
|
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Beacon management
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/** Start Beaconing command structure */
|
||||
struct uwb_rc_cmd_start_beacon {
|
||||
struct uwb_rccb rccb;
|
||||
__le16 wBPSTOffset;
|
||||
u8 bChannelNumber;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
static int uwb_rc_start_beacon(struct uwb_rc *rc, u16 bpst_offset, u8 channel)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rc_cmd_start_beacon *cmd;
|
||||
struct uwb_rc_evt_confirm reply;
|
||||
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
return -ENOMEM;
|
||||
cmd->rccb.bCommandType = UWB_RC_CET_GENERAL;
|
||||
cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_START_BEACON);
|
||||
cmd->wBPSTOffset = cpu_to_le16(bpst_offset);
|
||||
cmd->bChannelNumber = channel;
|
||||
reply.rceb.bEventType = UWB_RC_CET_GENERAL;
|
||||
reply.rceb.wEvent = UWB_RC_CMD_START_BEACON;
|
||||
result = uwb_rc_cmd(rc, "START-BEACON", &cmd->rccb, sizeof(*cmd),
|
||||
&reply.rceb, sizeof(reply));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(&rc->uwb_dev.dev,
|
||||
"START-BEACON: command execution failed: %s (%d)\n",
|
||||
uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
|
||||
result = -EIO;
|
||||
}
|
||||
error_cmd:
|
||||
kfree(cmd);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int uwb_rc_stop_beacon(struct uwb_rc *rc)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rccb *cmd;
|
||||
struct uwb_rc_evt_confirm reply;
|
||||
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
return -ENOMEM;
|
||||
cmd->bCommandType = UWB_RC_CET_GENERAL;
|
||||
cmd->wCommand = cpu_to_le16(UWB_RC_CMD_STOP_BEACON);
|
||||
reply.rceb.bEventType = UWB_RC_CET_GENERAL;
|
||||
reply.rceb.wEvent = UWB_RC_CMD_STOP_BEACON;
|
||||
result = uwb_rc_cmd(rc, "STOP-BEACON", cmd, sizeof(*cmd),
|
||||
&reply.rceb, sizeof(reply));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(&rc->uwb_dev.dev,
|
||||
"STOP-BEACON: command execution failed: %s (%d)\n",
|
||||
uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
|
||||
result = -EIO;
|
||||
}
|
||||
error_cmd:
|
||||
kfree(cmd);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start/stop beacons
|
||||
*
|
||||
* @rc: UWB Radio Controller to operate on
|
||||
* @channel: UWB channel on which to beacon (WUSB[table
|
||||
* 5-12]). If -1, stop beaconing.
|
||||
* @bpst_offset: Beacon Period Start Time offset; FIXME-do zero
|
||||
*
|
||||
* According to WHCI 0.95 [4.13.6] the driver will only receive the RCEB
|
||||
* of a SET IE command after the device sent the first beacon that includes
|
||||
* the IEs specified in the SET IE command. So, after we start beaconing we
|
||||
* check if there is anything in the IE cache and call the SET IE command
|
||||
* if needed.
|
||||
*/
|
||||
int uwb_rc_beacon(struct uwb_rc *rc, int channel, unsigned bpst_offset)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
if (channel < 0)
|
||||
channel = -1;
|
||||
if (channel == -1)
|
||||
result = uwb_rc_stop_beacon(rc);
|
||||
else {
|
||||
/* channel >= 0...dah */
|
||||
result = uwb_rc_start_beacon(rc, bpst_offset, channel);
|
||||
if (result < 0)
|
||||
goto out_up;
|
||||
if (le16_to_cpu(rc->ies->wIELength) > 0) {
|
||||
result = uwb_rc_set_ie(rc, rc->ies);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot set new IE on device: "
|
||||
"%d\n", result);
|
||||
result = uwb_rc_stop_beacon(rc);
|
||||
channel = -1;
|
||||
bpst_offset = 0;
|
||||
} else
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (result < 0)
|
||||
goto out_up;
|
||||
rc->beaconing = channel;
|
||||
|
||||
uwb_notify(rc, NULL, uwb_bg_joined(rc) ? UWB_NOTIF_BG_JOIN : UWB_NOTIF_BG_LEAVE);
|
||||
|
||||
out_up:
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Beacon cache
|
||||
*
|
||||
* The purpose of this is to speed up the lookup of becon information
|
||||
* when a new beacon arrives. The UWB Daemon uses it also to keep a
|
||||
* tab of which devices are in radio distance and which not. When a
|
||||
* device's beacon stays present for more than a certain amount of
|
||||
* time, it is considered a new, usable device. When a beacon ceases
|
||||
* to be received for a certain amount of time, it is considered that
|
||||
* the device is gone.
|
||||
*
|
||||
* FIXME: use an allocator for the entries
|
||||
* FIXME: use something faster for search than a list
|
||||
*/
|
||||
|
||||
struct uwb_beca uwb_beca = {
|
||||
.list = LIST_HEAD_INIT(uwb_beca.list),
|
||||
.mutex = __MUTEX_INITIALIZER(uwb_beca.mutex)
|
||||
};
|
||||
|
||||
|
||||
void uwb_bce_kfree(struct kref *_bce)
|
||||
{
|
||||
struct uwb_beca_e *bce = container_of(_bce, struct uwb_beca_e, refcnt);
|
||||
|
||||
kfree(bce->be);
|
||||
kfree(bce);
|
||||
}
|
||||
|
||||
|
||||
/* Find a beacon by dev addr in the cache */
|
||||
static
|
||||
struct uwb_beca_e *__uwb_beca_find_bydev(const struct uwb_dev_addr *dev_addr)
|
||||
{
|
||||
struct uwb_beca_e *bce, *next;
|
||||
list_for_each_entry_safe(bce, next, &uwb_beca.list, node) {
|
||||
d_printf(6, NULL, "looking for addr %02x:%02x in %02x:%02x\n",
|
||||
dev_addr->data[0], dev_addr->data[1],
|
||||
bce->dev_addr.data[0], bce->dev_addr.data[1]);
|
||||
if (!memcmp(&bce->dev_addr, dev_addr, sizeof(bce->dev_addr)))
|
||||
goto out;
|
||||
}
|
||||
bce = NULL;
|
||||
out:
|
||||
return bce;
|
||||
}
|
||||
|
||||
/* Find a beacon by dev addr in the cache */
|
||||
static
|
||||
struct uwb_beca_e *__uwb_beca_find_bymac(const struct uwb_mac_addr *mac_addr)
|
||||
{
|
||||
struct uwb_beca_e *bce, *next;
|
||||
list_for_each_entry_safe(bce, next, &uwb_beca.list, node) {
|
||||
if (!memcmp(bce->mac_addr, mac_addr->data,
|
||||
sizeof(struct uwb_mac_addr)))
|
||||
goto out;
|
||||
}
|
||||
bce = NULL;
|
||||
out:
|
||||
return bce;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_dev_get_by_devaddr - get a UWB device with a specific DevAddr
|
||||
* @rc: the radio controller that saw the device
|
||||
* @devaddr: DevAddr of the UWB device to find
|
||||
*
|
||||
* There may be more than one matching device (in the case of a
|
||||
* DevAddr conflict), but only the first one is returned.
|
||||
*/
|
||||
struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc,
|
||||
const struct uwb_dev_addr *devaddr)
|
||||
{
|
||||
struct uwb_dev *found = NULL;
|
||||
struct uwb_beca_e *bce;
|
||||
|
||||
mutex_lock(&uwb_beca.mutex);
|
||||
bce = __uwb_beca_find_bydev(devaddr);
|
||||
if (bce)
|
||||
found = uwb_dev_try_get(rc, bce->uwb_dev);
|
||||
mutex_unlock(&uwb_beca.mutex);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_dev_get_by_macaddr - get a UWB device with a specific EUI-48
|
||||
* @rc: the radio controller that saw the device
|
||||
* @devaddr: EUI-48 of the UWB device to find
|
||||
*/
|
||||
struct uwb_dev *uwb_dev_get_by_macaddr(struct uwb_rc *rc,
|
||||
const struct uwb_mac_addr *macaddr)
|
||||
{
|
||||
struct uwb_dev *found = NULL;
|
||||
struct uwb_beca_e *bce;
|
||||
|
||||
mutex_lock(&uwb_beca.mutex);
|
||||
bce = __uwb_beca_find_bymac(macaddr);
|
||||
if (bce)
|
||||
found = uwb_dev_try_get(rc, bce->uwb_dev);
|
||||
mutex_unlock(&uwb_beca.mutex);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/* Initialize a beacon cache entry */
|
||||
static void uwb_beca_e_init(struct uwb_beca_e *bce)
|
||||
{
|
||||
mutex_init(&bce->mutex);
|
||||
kref_init(&bce->refcnt);
|
||||
stats_init(&bce->lqe_stats);
|
||||
stats_init(&bce->rssi_stats);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a beacon to the cache
|
||||
*
|
||||
* @be: Beacon event information
|
||||
* @bf: Beacon frame (part of b, really)
|
||||
* @ts_jiffies: Timestamp (in jiffies) when the beacon was received
|
||||
*/
|
||||
struct uwb_beca_e *__uwb_beca_add(struct uwb_rc_evt_beacon *be,
|
||||
struct uwb_beacon_frame *bf,
|
||||
unsigned long ts_jiffies)
|
||||
{
|
||||
struct uwb_beca_e *bce;
|
||||
|
||||
bce = kzalloc(sizeof(*bce), GFP_KERNEL);
|
||||
if (bce == NULL)
|
||||
return NULL;
|
||||
uwb_beca_e_init(bce);
|
||||
bce->ts_jiffies = ts_jiffies;
|
||||
bce->uwb_dev = NULL;
|
||||
list_add(&bce->node, &uwb_beca.list);
|
||||
return bce;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wipe out beacon entries that became stale
|
||||
*
|
||||
* Remove associated devicest too.
|
||||
*/
|
||||
void uwb_beca_purge(void)
|
||||
{
|
||||
struct uwb_beca_e *bce, *next;
|
||||
unsigned long expires;
|
||||
|
||||
mutex_lock(&uwb_beca.mutex);
|
||||
list_for_each_entry_safe(bce, next, &uwb_beca.list, node) {
|
||||
expires = bce->ts_jiffies + msecs_to_jiffies(beacon_timeout_ms);
|
||||
if (time_after(jiffies, expires)) {
|
||||
uwbd_dev_offair(bce);
|
||||
list_del(&bce->node);
|
||||
uwb_bce_put(bce);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&uwb_beca.mutex);
|
||||
}
|
||||
|
||||
/* Clean up the whole beacon cache. Called on shutdown */
|
||||
void uwb_beca_release(void)
|
||||
{
|
||||
struct uwb_beca_e *bce, *next;
|
||||
mutex_lock(&uwb_beca.mutex);
|
||||
list_for_each_entry_safe(bce, next, &uwb_beca.list, node) {
|
||||
list_del(&bce->node);
|
||||
uwb_bce_put(bce);
|
||||
}
|
||||
mutex_unlock(&uwb_beca.mutex);
|
||||
}
|
||||
|
||||
static void uwb_beacon_print(struct uwb_rc *rc, struct uwb_rc_evt_beacon *be,
|
||||
struct uwb_beacon_frame *bf)
|
||||
{
|
||||
char macbuf[UWB_ADDR_STRSIZE];
|
||||
char devbuf[UWB_ADDR_STRSIZE];
|
||||
char dstbuf[UWB_ADDR_STRSIZE];
|
||||
|
||||
uwb_mac_addr_print(macbuf, sizeof(macbuf), &bf->Device_Identifier);
|
||||
uwb_dev_addr_print(devbuf, sizeof(devbuf), &bf->hdr.SrcAddr);
|
||||
uwb_dev_addr_print(dstbuf, sizeof(dstbuf), &bf->hdr.DestAddr);
|
||||
dev_info(&rc->uwb_dev.dev,
|
||||
"BEACON from %s to %s (ch%u offset %u slot %u MAC %s)\n",
|
||||
devbuf, dstbuf, be->bChannelNumber, be->wBPSTOffset,
|
||||
bf->Beacon_Slot_Number, macbuf);
|
||||
}
|
||||
|
||||
/*
|
||||
* @bce: beacon cache entry, referenced
|
||||
*/
|
||||
ssize_t uwb_bce_print_IEs(struct uwb_dev *uwb_dev, struct uwb_beca_e *bce,
|
||||
char *buf, size_t size)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
struct uwb_rc_evt_beacon *be;
|
||||
struct uwb_beacon_frame *bf;
|
||||
struct uwb_buf_ctx ctx = {
|
||||
.buf = buf,
|
||||
.bytes = 0,
|
||||
.size = size
|
||||
};
|
||||
|
||||
mutex_lock(&bce->mutex);
|
||||
be = bce->be;
|
||||
if (be == NULL)
|
||||
goto out;
|
||||
bf = (void *) be->BeaconInfo;
|
||||
uwb_ie_for_each(uwb_dev, uwb_ie_dump_hex, &ctx,
|
||||
bf->IEData, be->wBeaconInfoLength - sizeof(*bf));
|
||||
result = ctx.bytes;
|
||||
out:
|
||||
mutex_unlock(&bce->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that the beacon event, frame and IEs are ok
|
||||
*/
|
||||
static int uwb_verify_beacon(struct uwb_rc *rc, struct uwb_event *evt,
|
||||
struct uwb_rc_evt_beacon *be)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
struct uwb_beacon_frame *bf;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
|
||||
/* Is there enough data to decode a beacon frame? */
|
||||
if (evt->notif.size < sizeof(*be) + sizeof(*bf)) {
|
||||
dev_err(dev, "BEACON event: Not enough data to decode "
|
||||
"(%zu vs %zu bytes needed)\n", evt->notif.size,
|
||||
sizeof(*be) + sizeof(*bf));
|
||||
goto error;
|
||||
}
|
||||
/* FIXME: make sure beacon frame IEs are fine and that the whole thing
|
||||
* is consistent */
|
||||
result = 0;
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle UWB_RC_EVT_BEACON events
|
||||
*
|
||||
* We check the beacon cache to see how the received beacon fares. If
|
||||
* is there already we refresh the timestamp. If not we create a new
|
||||
* entry.
|
||||
*
|
||||
* According to the WHCI and WUSB specs, only one beacon frame is
|
||||
* allowed per notification block, so we don't bother about scanning
|
||||
* for more.
|
||||
*/
|
||||
int uwbd_evt_handle_rc_beacon(struct uwb_event *evt)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
struct uwb_rc *rc;
|
||||
struct uwb_rc_evt_beacon *be;
|
||||
struct uwb_beacon_frame *bf;
|
||||
struct uwb_beca_e *bce;
|
||||
unsigned long last_ts;
|
||||
|
||||
rc = evt->rc;
|
||||
be = container_of(evt->notif.rceb, struct uwb_rc_evt_beacon, rceb);
|
||||
result = uwb_verify_beacon(rc, evt, be);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
/* FIXME: handle alien beacons. */
|
||||
if (be->bBeaconType == UWB_RC_BEACON_TYPE_OL_ALIEN ||
|
||||
be->bBeaconType == UWB_RC_BEACON_TYPE_NOL_ALIEN) {
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
bf = (struct uwb_beacon_frame *) be->BeaconInfo;
|
||||
|
||||
/*
|
||||
* Drop beacons from devices with a NULL EUI-48 -- they cannot
|
||||
* be uniquely identified.
|
||||
*
|
||||
* It's expected that these will all be WUSB devices and they
|
||||
* have a WUSB specific connection method so ignoring them
|
||||
* here shouldn't be a problem.
|
||||
*/
|
||||
if (uwb_mac_addr_bcast(&bf->Device_Identifier))
|
||||
return 0;
|
||||
|
||||
mutex_lock(&uwb_beca.mutex);
|
||||
bce = __uwb_beca_find_bymac(&bf->Device_Identifier);
|
||||
if (bce == NULL) {
|
||||
/* Not in there, a new device is pinging */
|
||||
uwb_beacon_print(evt->rc, be, bf);
|
||||
bce = __uwb_beca_add(be, bf, evt->ts_jiffies);
|
||||
if (bce == NULL) {
|
||||
mutex_unlock(&uwb_beca.mutex);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&uwb_beca.mutex);
|
||||
|
||||
mutex_lock(&bce->mutex);
|
||||
/* purge old beacon data */
|
||||
kfree(bce->be);
|
||||
|
||||
last_ts = bce->ts_jiffies;
|
||||
|
||||
/* Update commonly used fields */
|
||||
bce->ts_jiffies = evt->ts_jiffies;
|
||||
bce->be = be;
|
||||
bce->dev_addr = bf->hdr.SrcAddr;
|
||||
bce->mac_addr = &bf->Device_Identifier;
|
||||
be->wBPSTOffset = le16_to_cpu(be->wBPSTOffset);
|
||||
be->wBeaconInfoLength = le16_to_cpu(be->wBeaconInfoLength);
|
||||
stats_add_sample(&bce->lqe_stats, be->bLQI - 7);
|
||||
stats_add_sample(&bce->rssi_stats, be->bRSSI + 18);
|
||||
|
||||
/*
|
||||
* This might be a beacon from a new device.
|
||||
*/
|
||||
if (bce->uwb_dev == NULL)
|
||||
uwbd_dev_onair(evt->rc, bce);
|
||||
|
||||
mutex_unlock(&bce->mutex);
|
||||
|
||||
return 1; /* we keep the event data */
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle UWB_RC_EVT_BEACON_SIZE events
|
||||
*
|
||||
* XXXXX
|
||||
*/
|
||||
int uwbd_evt_handle_rc_beacon_size(struct uwb_event *evt)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
struct device *dev = &evt->rc->uwb_dev.dev;
|
||||
struct uwb_rc_evt_beacon_size *bs;
|
||||
|
||||
/* Is there enough data to decode the event? */
|
||||
if (evt->notif.size < sizeof(*bs)) {
|
||||
dev_err(dev, "BEACON SIZE notification: Not enough data to "
|
||||
"decode (%zu vs %zu bytes needed)\n",
|
||||
evt->notif.size, sizeof(*bs));
|
||||
goto error;
|
||||
}
|
||||
bs = container_of(evt->notif.rceb, struct uwb_rc_evt_beacon_size, rceb);
|
||||
if (0)
|
||||
dev_info(dev, "Beacon size changed to %u bytes "
|
||||
"(FIXME: action?)\n", le16_to_cpu(bs->wNewBeaconSize));
|
||||
else {
|
||||
/* temporary hack until we do something with this message... */
|
||||
static unsigned count;
|
||||
if (++count % 1000 == 0)
|
||||
dev_info(dev, "Beacon size changed %u times "
|
||||
"(FIXME: action?)\n", count);
|
||||
}
|
||||
result = 0;
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwbd_evt_handle_rc_bp_slot_change - handle a BP_SLOT_CHANGE event
|
||||
* @evt: the BP_SLOT_CHANGE notification from the radio controller
|
||||
*
|
||||
* If the event indicates that no beacon period slots were available
|
||||
* then radio controller has transitioned to a non-beaconing state.
|
||||
* Otherwise, simply save the current beacon slot.
|
||||
*/
|
||||
int uwbd_evt_handle_rc_bp_slot_change(struct uwb_event *evt)
|
||||
{
|
||||
struct uwb_rc *rc = evt->rc;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_rc_evt_bp_slot_change *bpsc;
|
||||
|
||||
if (evt->notif.size < sizeof(*bpsc)) {
|
||||
dev_err(dev, "BP SLOT CHANGE event: Not enough data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
bpsc = container_of(evt->notif.rceb, struct uwb_rc_evt_bp_slot_change, rceb);
|
||||
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
if (uwb_rc_evt_bp_slot_change_no_slot(bpsc)) {
|
||||
dev_info(dev, "stopped beaconing: No free slots in BP\n");
|
||||
rc->beaconing = -1;
|
||||
} else
|
||||
rc->uwb_dev.beacon_slot = uwb_rc_evt_bp_slot_change_slot_num(bpsc);
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle UWB_RC_EVT_BPOIE_CHANGE events
|
||||
*
|
||||
* XXXXX
|
||||
*/
|
||||
struct uwb_ie_bpo {
|
||||
struct uwb_ie_hdr hdr;
|
||||
u8 bp_length;
|
||||
u8 data[];
|
||||
} __attribute__((packed));
|
||||
|
||||
int uwbd_evt_handle_rc_bpoie_change(struct uwb_event *evt)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
struct device *dev = &evt->rc->uwb_dev.dev;
|
||||
struct uwb_rc_evt_bpoie_change *bpoiec;
|
||||
struct uwb_ie_bpo *bpoie;
|
||||
static unsigned count; /* FIXME: this is a temp hack */
|
||||
size_t iesize;
|
||||
|
||||
/* Is there enough data to decode it? */
|
||||
if (evt->notif.size < sizeof(*bpoiec)) {
|
||||
dev_err(dev, "BPOIEC notification: Not enough data to "
|
||||
"decode (%zu vs %zu bytes needed)\n",
|
||||
evt->notif.size, sizeof(*bpoiec));
|
||||
goto error;
|
||||
}
|
||||
bpoiec = container_of(evt->notif.rceb, struct uwb_rc_evt_bpoie_change, rceb);
|
||||
iesize = le16_to_cpu(bpoiec->wBPOIELength);
|
||||
if (iesize < sizeof(*bpoie)) {
|
||||
dev_err(dev, "BPOIEC notification: Not enough IE data to "
|
||||
"decode (%zu vs %zu bytes needed)\n",
|
||||
iesize, sizeof(*bpoie));
|
||||
goto error;
|
||||
}
|
||||
if (++count % 1000 == 0) /* Lame placeholder */
|
||||
dev_info(dev, "BPOIE: %u changes received\n", count);
|
||||
/*
|
||||
* FIXME: At this point we should go over all the IEs in the
|
||||
* bpoiec->BPOIE array and act on each.
|
||||
*/
|
||||
result = 0;
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_bg_joined - is the RC in a beacon group?
|
||||
* @rc: the radio controller
|
||||
*
|
||||
* Returns true if the radio controller is in a beacon group (even if
|
||||
* it's the sole member).
|
||||
*/
|
||||
int uwb_bg_joined(struct uwb_rc *rc)
|
||||
{
|
||||
return rc->beaconing != -1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_bg_joined);
|
||||
|
||||
/*
|
||||
* Print beaconing state.
|
||||
*/
|
||||
static ssize_t uwb_rc_beacon_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_rc *rc = uwb_dev->rc;
|
||||
ssize_t result;
|
||||
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
result = sprintf(buf, "%d\n", rc->beaconing);
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start beaconing on the specified channel, or stop beaconing.
|
||||
*
|
||||
* The BPST offset of when to start searching for a beacon group to
|
||||
* join may be specified.
|
||||
*/
|
||||
static ssize_t uwb_rc_beacon_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_rc *rc = uwb_dev->rc;
|
||||
int channel;
|
||||
unsigned bpst_offset = 0;
|
||||
ssize_t result = -EINVAL;
|
||||
|
||||
result = sscanf(buf, "%d %u\n", &channel, &bpst_offset);
|
||||
if (result >= 1)
|
||||
result = uwb_rc_beacon(rc, channel, bpst_offset);
|
||||
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
DEVICE_ATTR(beacon, S_IRUGO | S_IWUSR, uwb_rc_beacon_show, uwb_rc_beacon_store);
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Driver initialization, etc
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*
|
||||
* Life cycle: FIXME: explain
|
||||
*
|
||||
* UWB radio controller:
|
||||
*
|
||||
* 1. alloc a uwb_rc, zero it
|
||||
* 2. call uwb_rc_init() on it to set it up + ops (won't do any
|
||||
* kind of allocation)
|
||||
* 3. register (now it is owned by the UWB stack--deregister before
|
||||
* freeing/destroying).
|
||||
* 4. It lives on it's own now (UWB stack handles)--when it
|
||||
* disconnects, call unregister()
|
||||
* 5. free it.
|
||||
*
|
||||
* Make sure you have a reference to the uwb_rc before calling
|
||||
* any of the UWB API functions.
|
||||
*
|
||||
* TODO:
|
||||
*
|
||||
* 1. Locking and life cycle management is crappy still. All entry
|
||||
* points to the UWB HCD API assume you have a reference on the
|
||||
* uwb_rc structure and that it won't go away. They mutex lock it
|
||||
* before doing anything.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
|
||||
/* UWB stack attributes (or 'global' constants) */
|
||||
|
||||
|
||||
/**
|
||||
* If a beacon dissapears for longer than this, then we consider the
|
||||
* device who was represented by that beacon to be gone.
|
||||
*
|
||||
* ECMA-368[17.2.3, last para] establishes that a device must not
|
||||
* consider a device to be its neighbour if he doesn't receive a beacon
|
||||
* for more than mMaxLostBeacons. mMaxLostBeacons is defined in
|
||||
* ECMA-368[17.16] as 3; because we can get only one beacon per
|
||||
* superframe, that'd be 3 * 65ms = 195 ~ 200 ms. Let's give it time
|
||||
* for jitter and stuff and make it 500 ms.
|
||||
*/
|
||||
unsigned long beacon_timeout_ms = 500;
|
||||
|
||||
static
|
||||
ssize_t beacon_timeout_ms_show(struct class *class, char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "%lu\n", beacon_timeout_ms);
|
||||
}
|
||||
|
||||
static
|
||||
ssize_t beacon_timeout_ms_store(struct class *class,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
unsigned long bt;
|
||||
ssize_t result;
|
||||
result = sscanf(buf, "%lu", &bt);
|
||||
if (result != 1)
|
||||
return -EINVAL;
|
||||
beacon_timeout_ms = bt;
|
||||
return size;
|
||||
}
|
||||
|
||||
static struct class_attribute uwb_class_attrs[] = {
|
||||
__ATTR(beacon_timeout_ms, S_IWUSR | S_IRUGO,
|
||||
beacon_timeout_ms_show, beacon_timeout_ms_store),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
/** Device model classes */
|
||||
struct class uwb_rc_class = {
|
||||
.name = "uwb_rc",
|
||||
.class_attrs = uwb_class_attrs,
|
||||
};
|
||||
|
||||
|
||||
static int __init uwb_subsys_init(void)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
result = uwb_est_create();
|
||||
if (result < 0) {
|
||||
printk(KERN_ERR "uwb: Can't initialize EST subsystem\n");
|
||||
goto error_est_init;
|
||||
}
|
||||
|
||||
result = class_register(&uwb_rc_class);
|
||||
if (result < 0)
|
||||
goto error_uwb_rc_class_register;
|
||||
uwbd_start();
|
||||
uwb_dbg_init();
|
||||
return 0;
|
||||
|
||||
error_uwb_rc_class_register:
|
||||
uwb_est_destroy();
|
||||
error_est_init:
|
||||
return result;
|
||||
}
|
||||
module_init(uwb_subsys_init);
|
||||
|
||||
static void __exit uwb_subsys_exit(void)
|
||||
{
|
||||
uwb_dbg_exit();
|
||||
uwbd_stop();
|
||||
class_unregister(&uwb_rc_class);
|
||||
uwb_est_destroy();
|
||||
return;
|
||||
}
|
||||
module_exit(uwb_subsys_exit);
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("Ultra Wide Band core");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* DRP availability management
|
||||
*
|
||||
* Copyright (C) 2005-2006 Intel Corporation
|
||||
* Reinette Chatre <reinette.chatre@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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* Manage DRP Availability (the MAS available for DRP
|
||||
* reservations). Thus:
|
||||
*
|
||||
* - Handle DRP Availability Change notifications
|
||||
*
|
||||
* - Allow the reservation manager to indicate MAS reserved/released
|
||||
* by local (owned by/targeted at the radio controller)
|
||||
* reservations.
|
||||
*
|
||||
* - Based on the two sources above, generate a DRP Availability IE to
|
||||
* be included in the beacon.
|
||||
*
|
||||
* See also the documentation for struct uwb_drp_avail.
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
/**
|
||||
* uwb_drp_avail_init - initialize an RC's MAS availability
|
||||
*
|
||||
* All MAS are available initially. The RC will inform use which
|
||||
* slots are used for the BP (it may change in size).
|
||||
*/
|
||||
void uwb_drp_avail_init(struct uwb_rc *rc)
|
||||
{
|
||||
bitmap_fill(rc->drp_avail.global, UWB_NUM_MAS);
|
||||
bitmap_fill(rc->drp_avail.local, UWB_NUM_MAS);
|
||||
bitmap_fill(rc->drp_avail.pending, UWB_NUM_MAS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine MAS available for new local reservations.
|
||||
*
|
||||
* avail = global & local & pending
|
||||
*/
|
||||
static void uwb_drp_available(struct uwb_rc *rc, struct uwb_mas_bm *avail)
|
||||
{
|
||||
bitmap_and(avail->bm, rc->drp_avail.global, rc->drp_avail.local, UWB_NUM_MAS);
|
||||
bitmap_and(avail->bm, avail->bm, rc->drp_avail.pending, UWB_NUM_MAS);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_drp_avail_reserve_pending - reserve MAS for a new reservation
|
||||
* @rc: the radio controller
|
||||
* @mas: the MAS to reserve
|
||||
*
|
||||
* Returns 0 on success, or -EBUSY if the MAS requested aren't available.
|
||||
*/
|
||||
int uwb_drp_avail_reserve_pending(struct uwb_rc *rc, struct uwb_mas_bm *mas)
|
||||
{
|
||||
struct uwb_mas_bm avail;
|
||||
|
||||
uwb_drp_available(rc, &avail);
|
||||
if (!bitmap_subset(mas->bm, avail.bm, UWB_NUM_MAS))
|
||||
return -EBUSY;
|
||||
|
||||
bitmap_andnot(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_drp_avail_reserve - reserve MAS for an established reservation
|
||||
* @rc: the radio controller
|
||||
* @mas: the MAS to reserve
|
||||
*/
|
||||
void uwb_drp_avail_reserve(struct uwb_rc *rc, struct uwb_mas_bm *mas)
|
||||
{
|
||||
bitmap_or(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS);
|
||||
bitmap_andnot(rc->drp_avail.local, rc->drp_avail.local, mas->bm, UWB_NUM_MAS);
|
||||
rc->drp_avail.ie_valid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_drp_avail_release - release MAS from a pending or established reservation
|
||||
* @rc: the radio controller
|
||||
* @mas: the MAS to release
|
||||
*/
|
||||
void uwb_drp_avail_release(struct uwb_rc *rc, struct uwb_mas_bm *mas)
|
||||
{
|
||||
bitmap_or(rc->drp_avail.local, rc->drp_avail.local, mas->bm, UWB_NUM_MAS);
|
||||
bitmap_or(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS);
|
||||
rc->drp_avail.ie_valid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_drp_avail_ie_update - update the DRP Availability IE
|
||||
* @rc: the radio controller
|
||||
*
|
||||
* avail = global & local
|
||||
*/
|
||||
void uwb_drp_avail_ie_update(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_mas_bm avail;
|
||||
|
||||
bitmap_and(avail.bm, rc->drp_avail.global, rc->drp_avail.local, UWB_NUM_MAS);
|
||||
|
||||
rc->drp_avail.ie.hdr.element_id = UWB_IE_DRP_AVAILABILITY;
|
||||
rc->drp_avail.ie.hdr.length = UWB_NUM_MAS / 8;
|
||||
uwb_mas_bm_copy_le(rc->drp_avail.ie.bmp, &avail);
|
||||
rc->drp_avail.ie_valid = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an unsigned long from a buffer containing a byte stream.
|
||||
*
|
||||
* @array: pointer to buffer
|
||||
* @itr: index of buffer from where we start
|
||||
* @len: the buffer's remaining size may not be exact multiple of
|
||||
* sizeof(unsigned long), @len is the length of buffer that needs
|
||||
* to be converted. This will be sizeof(unsigned long) or smaller
|
||||
* (BUG if not). If it is smaller then we will pad the remaining
|
||||
* space of the result with zeroes.
|
||||
*/
|
||||
static
|
||||
unsigned long get_val(u8 *array, size_t itr, size_t len)
|
||||
{
|
||||
unsigned long val = 0;
|
||||
size_t top = itr + len;
|
||||
|
||||
BUG_ON(len > sizeof(val));
|
||||
|
||||
while (itr < top) {
|
||||
val <<= 8;
|
||||
val |= array[top - 1];
|
||||
top--;
|
||||
}
|
||||
val <<= 8 * (sizeof(val) - len); /* padding */
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize bitmap from data buffer.
|
||||
*
|
||||
* The bitmap to be converted could come from a IE, for example a
|
||||
* DRP Availability IE.
|
||||
* From ECMA-368 1.0 [16.8.7]: "
|
||||
* octets: 1 1 N * (0 to 32)
|
||||
* Element ID Length (=N) DRP Availability Bitmap
|
||||
*
|
||||
* The DRP Availability Bitmap field is up to 256 bits long, one
|
||||
* bit for each MAS in the superframe, where the least-significant
|
||||
* bit of the field corresponds to the first MAS in the superframe
|
||||
* and successive bits correspond to successive MASs."
|
||||
*
|
||||
* The DRP Availability bitmap is in octets from 0 to 32, so octet
|
||||
* 32 contains bits for MAS 1-8, etc. If the bitmap is smaller than 32
|
||||
* octets, the bits in octets not included at the end of the bitmap are
|
||||
* treated as zero. In this case (when the bitmap is smaller than 32
|
||||
* octets) the MAS represented range from MAS 1 to MAS (size of bitmap)
|
||||
* with the last octet still containing bits for MAS 1-8, etc.
|
||||
*
|
||||
* For example:
|
||||
* F00F0102 03040506 0708090A 0B0C0D0E 0F010203
|
||||
* ^^^^
|
||||
* ||||
|
||||
* ||||
|
||||
* |||\LSB of byte is MAS 9
|
||||
* ||\MSB of byte is MAS 16
|
||||
* |\LSB of first byte is MAS 1
|
||||
* \ MSB of byte is MAS 8
|
||||
*
|
||||
* An example of this encoding can be found in ECMA-368 Annex-D [Table D.11]
|
||||
*
|
||||
* The resulting bitmap will have the following mapping:
|
||||
* bit position 0 == MAS 1
|
||||
* bit position 1 == MAS 2
|
||||
* ...
|
||||
* bit position (UWB_NUM_MAS - 1) == MAS UWB_NUM_MAS
|
||||
*
|
||||
* @bmp_itr: pointer to bitmap (can be declared with DECLARE_BITMAP)
|
||||
* @buffer: pointer to buffer containing bitmap data in big endian
|
||||
* format (MSB first)
|
||||
* @buffer_size:number of bytes with which bitmap should be initialized
|
||||
*/
|
||||
static
|
||||
void buffer_to_bmp(unsigned long *bmp_itr, void *_buffer,
|
||||
size_t buffer_size)
|
||||
{
|
||||
u8 *buffer = _buffer;
|
||||
size_t itr, len;
|
||||
unsigned long val;
|
||||
|
||||
itr = 0;
|
||||
while (itr < buffer_size) {
|
||||
len = buffer_size - itr >= sizeof(val) ?
|
||||
sizeof(val) : buffer_size - itr;
|
||||
val = get_val(buffer, itr, len);
|
||||
bmp_itr[itr / sizeof(val)] = val;
|
||||
itr += sizeof(val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract DRP Availability bitmap from the notification.
|
||||
*
|
||||
* The notification that comes in contains a bitmap of (UWB_NUM_MAS / 8) bytes
|
||||
* We convert that to our internal representation.
|
||||
*/
|
||||
static
|
||||
int uwbd_evt_get_drp_avail(struct uwb_event *evt, unsigned long *bmp)
|
||||
{
|
||||
struct device *dev = &evt->rc->uwb_dev.dev;
|
||||
struct uwb_rc_evt_drp_avail *drp_evt;
|
||||
int result = -EINVAL;
|
||||
|
||||
/* Is there enough data to decode the event? */
|
||||
if (evt->notif.size < sizeof(*drp_evt)) {
|
||||
dev_err(dev, "DRP Availability Change: Not enough "
|
||||
"data to decode event [%zu bytes, %zu "
|
||||
"needed]\n", evt->notif.size, sizeof(*drp_evt));
|
||||
goto error;
|
||||
}
|
||||
drp_evt = container_of(evt->notif.rceb, struct uwb_rc_evt_drp_avail, rceb);
|
||||
buffer_to_bmp(bmp, drp_evt->bmp, UWB_NUM_MAS/8);
|
||||
result = 0;
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming DRP Availability notification.
|
||||
*
|
||||
* @evt: Event information (packs the actual event data, which
|
||||
* radio controller it came to, etc).
|
||||
*
|
||||
* @returns: 0 on success (so uwbd() frees the event buffer), < 0
|
||||
* on error.
|
||||
*
|
||||
* According to ECMA-368 1.0 [16.8.7], bits set to ONE indicate that
|
||||
* the MAS slot is available, bits set to ZERO indicate that the slot
|
||||
* is busy.
|
||||
*
|
||||
* So we clear available slots, we set used slots :)
|
||||
*
|
||||
* The notification only marks non-availability based on the BP and
|
||||
* received DRP IEs that are not for this radio controller. A copy of
|
||||
* this bitmap is needed to generate the real availability (which
|
||||
* includes local and pending reservations).
|
||||
*
|
||||
* The DRP Availability IE that this radio controller emits will need
|
||||
* to be updated.
|
||||
*/
|
||||
int uwbd_evt_handle_rc_drp_avail(struct uwb_event *evt)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rc *rc = evt->rc;
|
||||
DECLARE_BITMAP(bmp, UWB_NUM_MAS);
|
||||
|
||||
result = uwbd_evt_get_drp_avail(evt, bmp);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
mutex_lock(&rc->rsvs_mutex);
|
||||
bitmap_copy(rc->drp_avail.global, bmp, UWB_NUM_MAS);
|
||||
rc->drp_avail.ie_valid = false;
|
||||
mutex_unlock(&rc->rsvs_mutex);
|
||||
|
||||
uwb_rsv_sched_update(rc);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* UWB DRP IE management.
|
||||
*
|
||||
* Copyright (C) 2005-2006 Intel Corporation
|
||||
* 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 <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/uwb.h>
|
||||
|
||||
#include "uwb-internal.h"
|
||||
|
||||
/*
|
||||
* Allocate a DRP IE.
|
||||
*
|
||||
* To save having to free/allocate a DRP IE when its MAS changes,
|
||||
* enough memory is allocated for the maxiumum number of DRP
|
||||
* allocation fields. This gives an overhead per reservation of up to
|
||||
* (UWB_NUM_ZONES - 1) * 4 = 60 octets.
|
||||
*/
|
||||
static struct uwb_ie_drp *uwb_drp_ie_alloc(void)
|
||||
{
|
||||
struct uwb_ie_drp *drp_ie;
|
||||
unsigned tiebreaker;
|
||||
|
||||
drp_ie = kzalloc(sizeof(struct uwb_ie_drp) +
|
||||
UWB_NUM_ZONES * sizeof(struct uwb_drp_alloc),
|
||||
GFP_KERNEL);
|
||||
if (drp_ie) {
|
||||
drp_ie->hdr.element_id = UWB_IE_DRP;
|
||||
|
||||
get_random_bytes(&tiebreaker, sizeof(unsigned));
|
||||
uwb_ie_drp_set_tiebreaker(drp_ie, tiebreaker & 1);
|
||||
}
|
||||
return drp_ie;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Fill a DRP IE's allocation fields from a MAS bitmap.
|
||||
*/
|
||||
static void uwb_drp_ie_from_bm(struct uwb_ie_drp *drp_ie,
|
||||
struct uwb_mas_bm *mas)
|
||||
{
|
||||
int z, i, num_fields = 0, next = 0;
|
||||
struct uwb_drp_alloc *zones;
|
||||
__le16 current_bmp;
|
||||
DECLARE_BITMAP(tmp_bmp, UWB_NUM_MAS);
|
||||
DECLARE_BITMAP(tmp_mas_bm, UWB_MAS_PER_ZONE);
|
||||
|
||||
zones = drp_ie->allocs;
|
||||
|
||||
bitmap_copy(tmp_bmp, mas->bm, UWB_NUM_MAS);
|
||||
|
||||
/* Determine unique MAS bitmaps in zones from bitmap. */
|
||||
for (z = 0; z < UWB_NUM_ZONES; z++) {
|
||||
bitmap_copy(tmp_mas_bm, tmp_bmp, UWB_MAS_PER_ZONE);
|
||||
if (bitmap_weight(tmp_mas_bm, UWB_MAS_PER_ZONE) > 0) {
|
||||
bool found = false;
|
||||
current_bmp = (__le16) *tmp_mas_bm;
|
||||
for (i = 0; i < next; i++) {
|
||||
if (current_bmp == zones[i].mas_bm) {
|
||||
zones[i].zone_bm |= 1 << z;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
num_fields++;
|
||||
zones[next].zone_bm = 1 << z;
|
||||
zones[next].mas_bm = current_bmp;
|
||||
next++;
|
||||
}
|
||||
}
|
||||
bitmap_shift_right(tmp_bmp, tmp_bmp, UWB_MAS_PER_ZONE, UWB_NUM_MAS);
|
||||
}
|
||||
|
||||
/* Store in format ready for transmission (le16). */
|
||||
for (i = 0; i < num_fields; i++) {
|
||||
drp_ie->allocs[i].zone_bm = cpu_to_le16(zones[i].zone_bm);
|
||||
drp_ie->allocs[i].mas_bm = cpu_to_le16(zones[i].mas_bm);
|
||||
}
|
||||
|
||||
drp_ie->hdr.length = sizeof(struct uwb_ie_drp) - sizeof(struct uwb_ie_hdr)
|
||||
+ num_fields * sizeof(struct uwb_drp_alloc);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_drp_ie_update - update a reservation's DRP IE
|
||||
* @rsv: the reservation
|
||||
*/
|
||||
int uwb_drp_ie_update(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct device *dev = &rsv->rc->uwb_dev.dev;
|
||||
struct uwb_ie_drp *drp_ie;
|
||||
int reason_code, status;
|
||||
|
||||
switch (rsv->state) {
|
||||
case UWB_RSV_STATE_NONE:
|
||||
kfree(rsv->drp_ie);
|
||||
rsv->drp_ie = NULL;
|
||||
return 0;
|
||||
case UWB_RSV_STATE_O_INITIATED:
|
||||
reason_code = UWB_DRP_REASON_ACCEPTED;
|
||||
status = 0;
|
||||
break;
|
||||
case UWB_RSV_STATE_O_PENDING:
|
||||
reason_code = UWB_DRP_REASON_ACCEPTED;
|
||||
status = 0;
|
||||
break;
|
||||
case UWB_RSV_STATE_O_MODIFIED:
|
||||
reason_code = UWB_DRP_REASON_MODIFIED;
|
||||
status = 1;
|
||||
break;
|
||||
case UWB_RSV_STATE_O_ESTABLISHED:
|
||||
reason_code = UWB_DRP_REASON_ACCEPTED;
|
||||
status = 1;
|
||||
break;
|
||||
case UWB_RSV_STATE_T_ACCEPTED:
|
||||
reason_code = UWB_DRP_REASON_ACCEPTED;
|
||||
status = 1;
|
||||
break;
|
||||
case UWB_RSV_STATE_T_DENIED:
|
||||
reason_code = UWB_DRP_REASON_DENIED;
|
||||
status = 0;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(dev, "rsv with unhandled state (%d)\n", rsv->state);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rsv->drp_ie == NULL) {
|
||||
rsv->drp_ie = uwb_drp_ie_alloc();
|
||||
if (rsv->drp_ie == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
drp_ie = rsv->drp_ie;
|
||||
|
||||
uwb_ie_drp_set_owner(drp_ie, uwb_rsv_is_owner(rsv));
|
||||
uwb_ie_drp_set_status(drp_ie, status);
|
||||
uwb_ie_drp_set_reason_code(drp_ie, reason_code);
|
||||
uwb_ie_drp_set_stream_index(drp_ie, rsv->stream);
|
||||
uwb_ie_drp_set_type(drp_ie, rsv->type);
|
||||
|
||||
if (uwb_rsv_is_owner(rsv)) {
|
||||
switch (rsv->target.type) {
|
||||
case UWB_RSV_TARGET_DEV:
|
||||
drp_ie->dev_addr = rsv->target.dev->dev_addr;
|
||||
break;
|
||||
case UWB_RSV_TARGET_DEVADDR:
|
||||
drp_ie->dev_addr = rsv->target.devaddr;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
drp_ie->dev_addr = rsv->owner->dev_addr;
|
||||
|
||||
uwb_drp_ie_from_bm(drp_ie, &rsv->mas);
|
||||
|
||||
rsv->ie_valid = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set MAS bits from given MAS bitmap in a single zone of large bitmap.
|
||||
*
|
||||
* We are given a zone id and the MAS bitmap of bits that need to be set in
|
||||
* this zone. Note that this zone may already have bits set and this only
|
||||
* adds settings - we cannot simply assign the MAS bitmap contents to the
|
||||
* zone contents. We iterate over the the bits (MAS) in the zone and set the
|
||||
* bits that are set in the given MAS bitmap.
|
||||
*/
|
||||
static
|
||||
void uwb_drp_ie_single_zone_to_bm(struct uwb_mas_bm *bm, u8 zone, u16 mas_bm)
|
||||
{
|
||||
int mas;
|
||||
u16 mas_mask;
|
||||
|
||||
for (mas = 0; mas < UWB_MAS_PER_ZONE; mas++) {
|
||||
mas_mask = 1 << mas;
|
||||
if (mas_bm & mas_mask)
|
||||
set_bit(zone * UWB_NUM_ZONES + mas, bm->bm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_drp_ie_zones_to_bm - convert DRP allocation fields to a bitmap
|
||||
* @mas: MAS bitmap that will be populated to correspond to the
|
||||
* allocation fields in the DRP IE
|
||||
* @drp_ie: the DRP IE that contains the allocation fields.
|
||||
*
|
||||
* The input format is an array of MAS allocation fields (16 bit Zone
|
||||
* bitmap, 16 bit MAS bitmap) as described in [ECMA-368] section
|
||||
* 16.8.6. The output is a full 256 bit MAS bitmap.
|
||||
*
|
||||
* We go over all the allocation fields, for each allocation field we
|
||||
* know which zones are impacted. We iterate over all the zones
|
||||
* impacted and call a function that will set the correct MAS bits in
|
||||
* each zone.
|
||||
*/
|
||||
void uwb_drp_ie_to_bm(struct uwb_mas_bm *bm, const struct uwb_ie_drp *drp_ie)
|
||||
{
|
||||
int numallocs = (drp_ie->hdr.length - 4) / 4;
|
||||
const struct uwb_drp_alloc *alloc;
|
||||
int cnt;
|
||||
u16 zone_bm, mas_bm;
|
||||
u8 zone;
|
||||
u16 zone_mask;
|
||||
|
||||
for (cnt = 0; cnt < numallocs; cnt++) {
|
||||
alloc = &drp_ie->allocs[cnt];
|
||||
zone_bm = le16_to_cpu(alloc->zone_bm);
|
||||
mas_bm = le16_to_cpu(alloc->mas_bm);
|
||||
for (zone = 0; zone < UWB_NUM_ZONES; zone++) {
|
||||
zone_mask = 1 << zone;
|
||||
if (zone_bm & zone_mask)
|
||||
uwb_drp_ie_single_zone_to_bm(bm, zone, mas_bm);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,461 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Dynamic Reservation Protocol handling
|
||||
*
|
||||
* Copyright (C) 2005-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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/delay.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
/**
|
||||
* Construct and send the SET DRP IE
|
||||
*
|
||||
* @rc: UWB Host controller
|
||||
* @returns: >= 0 number of bytes still available in the beacon
|
||||
* < 0 errno code on error.
|
||||
*
|
||||
* See WUSB[8.6.2.7]: The host must set all the DRP IEs that it wants the
|
||||
* device to include in its beacon at the same time. We thus have to
|
||||
* traverse all reservations and include the DRP IEs of all PENDING
|
||||
* and NEGOTIATED reservations in a SET DRP command for transmission.
|
||||
*
|
||||
* A DRP Availability IE is appended.
|
||||
*
|
||||
* rc->uwb_dev.mutex is held
|
||||
*
|
||||
* FIXME We currently ignore the returned value indicating the remaining space
|
||||
* in beacon. This could be used to deny reservation requests earlier if
|
||||
* determined that they would cause the beacon space to be exceeded.
|
||||
*/
|
||||
static
|
||||
int uwb_rc_gen_send_drp_ie(struct uwb_rc *rc)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_rc_cmd_set_drp_ie *cmd;
|
||||
struct uwb_rc_evt_set_drp_ie reply;
|
||||
struct uwb_rsv *rsv;
|
||||
int num_bytes = 0;
|
||||
u8 *IEDataptr;
|
||||
|
||||
result = -ENOMEM;
|
||||
/* First traverse all reservations to determine memory needed. */
|
||||
list_for_each_entry(rsv, &rc->reservations, rc_node) {
|
||||
if (rsv->drp_ie != NULL)
|
||||
num_bytes += rsv->drp_ie->hdr.length + 2;
|
||||
}
|
||||
num_bytes += sizeof(rc->drp_avail.ie);
|
||||
cmd = kzalloc(sizeof(*cmd) + num_bytes, GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
goto error;
|
||||
cmd->rccb.bCommandType = UWB_RC_CET_GENERAL;
|
||||
cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SET_DRP_IE);
|
||||
cmd->wIELength = num_bytes;
|
||||
IEDataptr = (u8 *)&cmd->IEData[0];
|
||||
|
||||
/* Next traverse all reservations to place IEs in allocated memory. */
|
||||
list_for_each_entry(rsv, &rc->reservations, rc_node) {
|
||||
if (rsv->drp_ie != NULL) {
|
||||
memcpy(IEDataptr, rsv->drp_ie,
|
||||
rsv->drp_ie->hdr.length + 2);
|
||||
IEDataptr += rsv->drp_ie->hdr.length + 2;
|
||||
}
|
||||
}
|
||||
memcpy(IEDataptr, &rc->drp_avail.ie, sizeof(rc->drp_avail.ie));
|
||||
|
||||
reply.rceb.bEventType = UWB_RC_CET_GENERAL;
|
||||
reply.rceb.wEvent = UWB_RC_CMD_SET_DRP_IE;
|
||||
result = uwb_rc_cmd(rc, "SET-DRP-IE", &cmd->rccb,
|
||||
sizeof(*cmd) + num_bytes, &reply.rceb,
|
||||
sizeof(reply));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
result = le16_to_cpu(reply.wRemainingSpace);
|
||||
if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(&rc->uwb_dev.dev, "SET-DRP-IE: command execution "
|
||||
"failed: %s (%d). RemainingSpace in beacon "
|
||||
"= %d\n", uwb_rc_strerror(reply.bResultCode),
|
||||
reply.bResultCode, result);
|
||||
result = -EIO;
|
||||
} else {
|
||||
dev_dbg(dev, "SET-DRP-IE sent. RemainingSpace in beacon "
|
||||
"= %d.\n", result);
|
||||
result = 0;
|
||||
}
|
||||
error_cmd:
|
||||
kfree(cmd);
|
||||
error:
|
||||
return result;
|
||||
|
||||
}
|
||||
/**
|
||||
* Send all DRP IEs associated with this host
|
||||
*
|
||||
* @returns: >= 0 number of bytes still available in the beacon
|
||||
* < 0 errno code on error.
|
||||
*
|
||||
* As per the protocol we obtain the host controller device lock to access
|
||||
* bandwidth structures.
|
||||
*/
|
||||
int uwb_rc_send_all_drp_ie(struct uwb_rc *rc)
|
||||
{
|
||||
int result;
|
||||
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
result = uwb_rc_gen_send_drp_ie(rc);
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
void uwb_drp_handle_timeout(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct device *dev = &rsv->rc->uwb_dev.dev;
|
||||
|
||||
dev_dbg(dev, "reservation timeout in state %s (%d)\n",
|
||||
uwb_rsv_state_str(rsv->state), rsv->state);
|
||||
|
||||
switch (rsv->state) {
|
||||
case UWB_RSV_STATE_O_INITIATED:
|
||||
if (rsv->is_multicast) {
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case UWB_RSV_STATE_O_ESTABLISHED:
|
||||
if (rsv->is_multicast)
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uwb_rsv_remove(rsv);
|
||||
}
|
||||
|
||||
/*
|
||||
* Based on the DRP IE, transition a target reservation to a new
|
||||
* state.
|
||||
*/
|
||||
static void uwb_drp_process_target(struct uwb_rc *rc, struct uwb_rsv *rsv,
|
||||
struct uwb_ie_drp *drp_ie)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
int status;
|
||||
enum uwb_drp_reason reason_code;
|
||||
|
||||
status = uwb_ie_drp_status(drp_ie);
|
||||
reason_code = uwb_ie_drp_reason_code(drp_ie);
|
||||
|
||||
if (status) {
|
||||
switch (reason_code) {
|
||||
case UWB_DRP_REASON_ACCEPTED:
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_ACCEPTED);
|
||||
break;
|
||||
case UWB_DRP_REASON_MODIFIED:
|
||||
dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n",
|
||||
reason_code, status);
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n",
|
||||
reason_code, status);
|
||||
}
|
||||
} else {
|
||||
switch (reason_code) {
|
||||
case UWB_DRP_REASON_ACCEPTED:
|
||||
/* New reservations are handled in uwb_rsv_find(). */
|
||||
break;
|
||||
case UWB_DRP_REASON_DENIED:
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
|
||||
break;
|
||||
case UWB_DRP_REASON_CONFLICT:
|
||||
case UWB_DRP_REASON_MODIFIED:
|
||||
dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n",
|
||||
reason_code, status);
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n",
|
||||
reason_code, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Based on the DRP IE, transition an owner reservation to a new
|
||||
* state.
|
||||
*/
|
||||
static void uwb_drp_process_owner(struct uwb_rc *rc, struct uwb_rsv *rsv,
|
||||
struct uwb_ie_drp *drp_ie)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
int status;
|
||||
enum uwb_drp_reason reason_code;
|
||||
|
||||
status = uwb_ie_drp_status(drp_ie);
|
||||
reason_code = uwb_ie_drp_reason_code(drp_ie);
|
||||
|
||||
if (status) {
|
||||
switch (reason_code) {
|
||||
case UWB_DRP_REASON_ACCEPTED:
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED);
|
||||
break;
|
||||
case UWB_DRP_REASON_MODIFIED:
|
||||
dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n",
|
||||
reason_code, status);
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n",
|
||||
reason_code, status);
|
||||
}
|
||||
} else {
|
||||
switch (reason_code) {
|
||||
case UWB_DRP_REASON_PENDING:
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_PENDING);
|
||||
break;
|
||||
case UWB_DRP_REASON_DENIED:
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
|
||||
break;
|
||||
case UWB_DRP_REASON_CONFLICT:
|
||||
case UWB_DRP_REASON_MODIFIED:
|
||||
dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n",
|
||||
reason_code, status);
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n",
|
||||
reason_code, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a received DRP IE, it's either for a reservation owned by
|
||||
* the RC or targeted at it (or it's for a WUSB cluster reservation).
|
||||
*/
|
||||
static void uwb_drp_process(struct uwb_rc *rc, struct uwb_dev *src,
|
||||
struct uwb_ie_drp *drp_ie)
|
||||
{
|
||||
struct uwb_rsv *rsv;
|
||||
|
||||
rsv = uwb_rsv_find(rc, src, drp_ie);
|
||||
if (!rsv) {
|
||||
/*
|
||||
* No reservation? It's either for a recently
|
||||
* terminated reservation; or the DRP IE couldn't be
|
||||
* processed (e.g., an invalid IE or out of memory).
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do nothing with DRP IEs for reservations that have been
|
||||
* terminated.
|
||||
*/
|
||||
if (rsv->state == UWB_RSV_STATE_NONE) {
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uwb_ie_drp_owner(drp_ie))
|
||||
uwb_drp_process_target(rc, rsv, drp_ie);
|
||||
else
|
||||
uwb_drp_process_owner(rc, rsv, drp_ie);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Process all the DRP IEs (both DRP IEs and the DRP Availability IE)
|
||||
* from a device.
|
||||
*/
|
||||
static
|
||||
void uwb_drp_process_all(struct uwb_rc *rc, struct uwb_rc_evt_drp *drp_evt,
|
||||
size_t ielen, struct uwb_dev *src_dev)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_ie_hdr *ie_hdr;
|
||||
void *ptr;
|
||||
|
||||
ptr = drp_evt->ie_data;
|
||||
for (;;) {
|
||||
ie_hdr = uwb_ie_next(&ptr, &ielen);
|
||||
if (!ie_hdr)
|
||||
break;
|
||||
|
||||
switch (ie_hdr->element_id) {
|
||||
case UWB_IE_DRP_AVAILABILITY:
|
||||
/* FIXME: does something need to be done with this? */
|
||||
break;
|
||||
case UWB_IE_DRP:
|
||||
uwb_drp_process(rc, src_dev, (struct uwb_ie_drp *)ie_hdr);
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev, "unexpected IE in DRP notification\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ielen > 0)
|
||||
dev_warn(dev, "%d octets remaining in DRP notification\n",
|
||||
(int)ielen);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Go through all the DRP IEs and find the ones that conflict with our
|
||||
* reservations.
|
||||
*
|
||||
* FIXME: must resolve the conflict according the the rules in
|
||||
* [ECMA-368].
|
||||
*/
|
||||
static
|
||||
void uwb_drp_process_conflict_all(struct uwb_rc *rc, struct uwb_rc_evt_drp *drp_evt,
|
||||
size_t ielen, struct uwb_dev *src_dev)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_ie_hdr *ie_hdr;
|
||||
struct uwb_ie_drp *drp_ie;
|
||||
void *ptr;
|
||||
|
||||
ptr = drp_evt->ie_data;
|
||||
for (;;) {
|
||||
ie_hdr = uwb_ie_next(&ptr, &ielen);
|
||||
if (!ie_hdr)
|
||||
break;
|
||||
|
||||
drp_ie = container_of(ie_hdr, struct uwb_ie_drp, hdr);
|
||||
|
||||
/* FIXME: check if this DRP IE conflicts. */
|
||||
}
|
||||
|
||||
if (ielen > 0)
|
||||
dev_warn(dev, "%d octets remaining in DRP notification\n",
|
||||
(int)ielen);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Terminate all reservations owned by, or targeted at, 'uwb_dev'.
|
||||
*/
|
||||
static void uwb_drp_terminate_all(struct uwb_rc *rc, struct uwb_dev *uwb_dev)
|
||||
{
|
||||
struct uwb_rsv *rsv;
|
||||
|
||||
list_for_each_entry(rsv, &rc->reservations, rc_node) {
|
||||
if (rsv->owner == uwb_dev
|
||||
|| (rsv->target.type == UWB_RSV_TARGET_DEV && rsv->target.dev == uwb_dev))
|
||||
uwb_rsv_remove(rsv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* uwbd_evt_handle_rc_drp - handle a DRP_IE event
|
||||
* @evt: the DRP_IE event from the radio controller
|
||||
*
|
||||
* This processes DRP notifications from the radio controller, either
|
||||
* initiating a new reservation or transitioning an existing
|
||||
* reservation into a different state.
|
||||
*
|
||||
* DRP notifications can occur for three different reasons:
|
||||
*
|
||||
* - UWB_DRP_NOTIF_DRP_IE_RECVD: one or more DRP IEs with the RC as
|
||||
* the target or source have been recieved.
|
||||
*
|
||||
* These DRP IEs could be new or for an existing reservation.
|
||||
*
|
||||
* If the DRP IE for an existing reservation ceases to be to
|
||||
* recieved for at least mMaxLostBeacons, the reservation should be
|
||||
* considered to be terminated. Note that the TERMINATE reason (see
|
||||
* below) may not always be signalled (e.g., the remote device has
|
||||
* two or more reservations established with the RC).
|
||||
*
|
||||
* - UWB_DRP_NOTIF_CONFLICT: DRP IEs from any device in the beacon
|
||||
* group conflict with the RC's reservations.
|
||||
*
|
||||
* - UWB_DRP_NOTIF_TERMINATE: DRP IEs are no longer being received
|
||||
* from a device (i.e., it's terminated all reservations).
|
||||
*
|
||||
* Only the software state of the reservations is changed; the setting
|
||||
* of the radio controller's DRP IEs is done after all the events in
|
||||
* an event buffer are processed. This saves waiting multiple times
|
||||
* for the SET_DRP_IE command to complete.
|
||||
*/
|
||||
int uwbd_evt_handle_rc_drp(struct uwb_event *evt)
|
||||
{
|
||||
struct device *dev = &evt->rc->uwb_dev.dev;
|
||||
struct uwb_rc *rc = evt->rc;
|
||||
struct uwb_rc_evt_drp *drp_evt;
|
||||
size_t ielength, bytes_left;
|
||||
struct uwb_dev_addr src_addr;
|
||||
struct uwb_dev *src_dev;
|
||||
int reason;
|
||||
|
||||
/* Is there enough data to decode the event (and any IEs in
|
||||
its payload)? */
|
||||
if (evt->notif.size < sizeof(*drp_evt)) {
|
||||
dev_err(dev, "DRP event: Not enough data to decode event "
|
||||
"[%zu bytes left, %zu needed]\n",
|
||||
evt->notif.size, sizeof(*drp_evt));
|
||||
return 0;
|
||||
}
|
||||
bytes_left = evt->notif.size - sizeof(*drp_evt);
|
||||
drp_evt = container_of(evt->notif.rceb, struct uwb_rc_evt_drp, rceb);
|
||||
ielength = le16_to_cpu(drp_evt->ie_length);
|
||||
if (bytes_left != ielength) {
|
||||
dev_err(dev, "DRP event: Not enough data in payload [%zu"
|
||||
"bytes left, %zu declared in the event]\n",
|
||||
bytes_left, ielength);
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(src_addr.data, &drp_evt->src_addr, sizeof(src_addr));
|
||||
src_dev = uwb_dev_get_by_devaddr(rc, &src_addr);
|
||||
if (!src_dev) {
|
||||
/*
|
||||
* A DRP notification from an unrecognized device.
|
||||
*
|
||||
* This is probably from a WUSB device that doesn't
|
||||
* have an EUI-48 and therefore doesn't show up in the
|
||||
* UWB device database. It's safe to simply ignore
|
||||
* these.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
mutex_lock(&rc->rsvs_mutex);
|
||||
|
||||
reason = uwb_rc_evt_drp_reason(drp_evt);
|
||||
|
||||
switch (reason) {
|
||||
case UWB_DRP_NOTIF_DRP_IE_RCVD:
|
||||
uwb_drp_process_all(rc, drp_evt, ielength, src_dev);
|
||||
break;
|
||||
case UWB_DRP_NOTIF_CONFLICT:
|
||||
uwb_drp_process_conflict_all(rc, drp_evt, ielength, src_dev);
|
||||
break;
|
||||
case UWB_DRP_NOTIF_TERMINATE:
|
||||
uwb_drp_terminate_all(rc, src_dev);
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev, "ignored DRP event with reason code: %d\n", reason);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&rc->rsvs_mutex);
|
||||
|
||||
uwb_dev_put(src_dev);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
/*
|
||||
* Ultra Wide Band Radio Control
|
||||
* Event Size Tables management
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*
|
||||
* Infrastructure, code and data tables for guessing the size of
|
||||
* events received on the notification endpoints of UWB radio
|
||||
* controllers.
|
||||
*
|
||||
* You define a table of events and for each, its size and how to get
|
||||
* the extra size.
|
||||
*
|
||||
* ENTRY POINTS:
|
||||
*
|
||||
* uwb_est_{init/destroy}(): To initialize/release the EST subsystem.
|
||||
*
|
||||
* uwb_est_[u]register(): To un/register event size tables
|
||||
* uwb_est_grow()
|
||||
*
|
||||
* uwb_est_find_size(): Get the size of an event
|
||||
* uwb_est_get_size()
|
||||
*/
|
||||
#include <linux/spinlock.h>
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
|
||||
struct uwb_est {
|
||||
u16 type_event_high;
|
||||
u16 vendor, product;
|
||||
u8 entries;
|
||||
const struct uwb_est_entry *entry;
|
||||
};
|
||||
|
||||
|
||||
static struct uwb_est *uwb_est;
|
||||
static u8 uwb_est_size;
|
||||
static u8 uwb_est_used;
|
||||
static DEFINE_RWLOCK(uwb_est_lock);
|
||||
|
||||
/**
|
||||
* WUSB Standard Event Size Table, HWA-RC interface
|
||||
*
|
||||
* Sizes for events and notifications type 0 (general), high nibble 0.
|
||||
*/
|
||||
static
|
||||
struct uwb_est_entry uwb_est_00_00xx[] = {
|
||||
[UWB_RC_EVT_IE_RCV] = {
|
||||
.size = sizeof(struct uwb_rc_evt_ie_rcv),
|
||||
.offset = 1 + offsetof(struct uwb_rc_evt_ie_rcv, wIELength),
|
||||
},
|
||||
[UWB_RC_EVT_BEACON] = {
|
||||
.size = sizeof(struct uwb_rc_evt_beacon),
|
||||
.offset = 1 + offsetof(struct uwb_rc_evt_beacon, wBeaconInfoLength),
|
||||
},
|
||||
[UWB_RC_EVT_BEACON_SIZE] = {
|
||||
.size = sizeof(struct uwb_rc_evt_beacon_size),
|
||||
},
|
||||
[UWB_RC_EVT_BPOIE_CHANGE] = {
|
||||
.size = sizeof(struct uwb_rc_evt_bpoie_change),
|
||||
.offset = 1 + offsetof(struct uwb_rc_evt_bpoie_change,
|
||||
wBPOIELength),
|
||||
},
|
||||
[UWB_RC_EVT_BP_SLOT_CHANGE] = {
|
||||
.size = sizeof(struct uwb_rc_evt_bp_slot_change),
|
||||
},
|
||||
[UWB_RC_EVT_BP_SWITCH_IE_RCV] = {
|
||||
.size = sizeof(struct uwb_rc_evt_bp_switch_ie_rcv),
|
||||
.offset = 1 + offsetof(struct uwb_rc_evt_bp_switch_ie_rcv, wIELength),
|
||||
},
|
||||
[UWB_RC_EVT_DEV_ADDR_CONFLICT] = {
|
||||
.size = sizeof(struct uwb_rc_evt_dev_addr_conflict),
|
||||
},
|
||||
[UWB_RC_EVT_DRP_AVAIL] = {
|
||||
.size = sizeof(struct uwb_rc_evt_drp_avail)
|
||||
},
|
||||
[UWB_RC_EVT_DRP] = {
|
||||
.size = sizeof(struct uwb_rc_evt_drp),
|
||||
.offset = 1 + offsetof(struct uwb_rc_evt_drp, ie_length),
|
||||
},
|
||||
[UWB_RC_EVT_BP_SWITCH_STATUS] = {
|
||||
.size = sizeof(struct uwb_rc_evt_bp_switch_status),
|
||||
},
|
||||
[UWB_RC_EVT_CMD_FRAME_RCV] = {
|
||||
.size = sizeof(struct uwb_rc_evt_cmd_frame_rcv),
|
||||
.offset = 1 + offsetof(struct uwb_rc_evt_cmd_frame_rcv, dataLength),
|
||||
},
|
||||
[UWB_RC_EVT_CHANNEL_CHANGE_IE_RCV] = {
|
||||
.size = sizeof(struct uwb_rc_evt_channel_change_ie_rcv),
|
||||
.offset = 1 + offsetof(struct uwb_rc_evt_channel_change_ie_rcv, wIELength),
|
||||
},
|
||||
[UWB_RC_CMD_CHANNEL_CHANGE] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_DEV_ADDR_MGMT] = {
|
||||
.size = sizeof(struct uwb_rc_evt_dev_addr_mgmt) },
|
||||
[UWB_RC_CMD_GET_IE] = {
|
||||
.size = sizeof(struct uwb_rc_evt_get_ie),
|
||||
.offset = 1 + offsetof(struct uwb_rc_evt_get_ie, wIELength),
|
||||
},
|
||||
[UWB_RC_CMD_RESET] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_SCAN] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_SET_BEACON_FILTER] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_SET_DRP_IE] = {
|
||||
.size = sizeof(struct uwb_rc_evt_set_drp_ie),
|
||||
},
|
||||
[UWB_RC_CMD_SET_IE] = {
|
||||
.size = sizeof(struct uwb_rc_evt_set_ie),
|
||||
},
|
||||
[UWB_RC_CMD_SET_NOTIFICATION_FILTER] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_SET_TX_POWER] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_SLEEP] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_START_BEACON] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_STOP_BEACON] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_BP_MERGE] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_SEND_COMMAND_FRAME] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
[UWB_RC_CMD_SET_ASIE_NOTIF] = {
|
||||
.size = sizeof(struct uwb_rc_evt_confirm),
|
||||
},
|
||||
};
|
||||
|
||||
static
|
||||
struct uwb_est_entry uwb_est_01_00xx[] = {
|
||||
[UWB_RC_DAA_ENERGY_DETECTED] = {
|
||||
.size = sizeof(struct uwb_rc_evt_daa_energy_detected),
|
||||
},
|
||||
[UWB_RC_SET_DAA_ENERGY_MASK] = {
|
||||
.size = sizeof(struct uwb_rc_evt_set_daa_energy_mask),
|
||||
},
|
||||
[UWB_RC_SET_NOTIFICATION_FILTER_EX] = {
|
||||
.size = sizeof(struct uwb_rc_evt_set_notification_filter_ex),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the EST subsystem
|
||||
*
|
||||
* Register the standard tables also.
|
||||
*
|
||||
* FIXME: tag init
|
||||
*/
|
||||
int uwb_est_create(void)
|
||||
{
|
||||
int result;
|
||||
|
||||
uwb_est_size = 2;
|
||||
uwb_est_used = 0;
|
||||
uwb_est = kzalloc(uwb_est_size * sizeof(uwb_est[0]), GFP_KERNEL);
|
||||
if (uwb_est == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
result = uwb_est_register(UWB_RC_CET_GENERAL, 0, 0xffff, 0xffff,
|
||||
uwb_est_00_00xx, ARRAY_SIZE(uwb_est_00_00xx));
|
||||
if (result < 0)
|
||||
goto out;
|
||||
result = uwb_est_register(UWB_RC_CET_EX_TYPE_1, 0, 0xffff, 0xffff,
|
||||
uwb_est_01_00xx, ARRAY_SIZE(uwb_est_01_00xx));
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/** Clean it up */
|
||||
void uwb_est_destroy(void)
|
||||
{
|
||||
kfree(uwb_est);
|
||||
uwb_est = NULL;
|
||||
uwb_est_size = uwb_est_used = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Double the capacity of the EST table
|
||||
*
|
||||
* @returns 0 if ok, < 0 errno no error.
|
||||
*/
|
||||
static
|
||||
int uwb_est_grow(void)
|
||||
{
|
||||
size_t actual_size = uwb_est_size * sizeof(uwb_est[0]);
|
||||
void *new = kmalloc(2 * actual_size, GFP_ATOMIC);
|
||||
if (new == NULL)
|
||||
return -ENOMEM;
|
||||
memcpy(new, uwb_est, actual_size);
|
||||
memset(new + actual_size, 0, actual_size);
|
||||
kfree(uwb_est);
|
||||
uwb_est = new;
|
||||
uwb_est_size *= 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register an event size table
|
||||
*
|
||||
* Makes room for it if the table is full, and then inserts it in the
|
||||
* right position (entries are sorted by type, event_high, vendor and
|
||||
* then product).
|
||||
*
|
||||
* @vendor: vendor code for matching against the device (0x0000 and
|
||||
* 0xffff mean any); use 0x0000 to force all to match without
|
||||
* checking possible vendor specific ones, 0xfffff to match
|
||||
* after checking vendor specific ones.
|
||||
*
|
||||
* @product: product code from that vendor; same matching rules, use
|
||||
* 0x0000 for not allowing vendor specific matches, 0xffff
|
||||
* for allowing.
|
||||
*
|
||||
* This arragement just makes the tables sort differenty. Because the
|
||||
* table is sorted by growing type-event_high-vendor-product, a zero
|
||||
* vendor will match before than a 0x456a vendor, that will match
|
||||
* before a 0xfffff vendor.
|
||||
*
|
||||
* @returns 0 if ok, < 0 errno on error (-ENOENT if not found).
|
||||
*/
|
||||
/* FIXME: add bus type to vendor/product code */
|
||||
int uwb_est_register(u8 type, u8 event_high, u16 vendor, u16 product,
|
||||
const struct uwb_est_entry *entry, size_t entries)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned itr;
|
||||
u16 type_event_high;
|
||||
int result = 0;
|
||||
|
||||
write_lock_irqsave(&uwb_est_lock, flags);
|
||||
if (uwb_est_used == uwb_est_size) {
|
||||
result = uwb_est_grow();
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
/* Find the right spot to insert it in */
|
||||
type_event_high = type << 8 | event_high;
|
||||
for (itr = 0; itr < uwb_est_used; itr++)
|
||||
if (uwb_est[itr].type_event_high < type
|
||||
&& uwb_est[itr].vendor < vendor
|
||||
&& uwb_est[itr].product < product)
|
||||
break;
|
||||
|
||||
/* Shift others to make room for the new one? */
|
||||
if (itr < uwb_est_used)
|
||||
memmove(&uwb_est[itr+1], &uwb_est[itr], uwb_est_used - itr);
|
||||
uwb_est[itr].type_event_high = type << 8 | event_high;
|
||||
uwb_est[itr].vendor = vendor;
|
||||
uwb_est[itr].product = product;
|
||||
uwb_est[itr].entry = entry;
|
||||
uwb_est[itr].entries = entries;
|
||||
uwb_est_used++;
|
||||
out:
|
||||
write_unlock_irqrestore(&uwb_est_lock, flags);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_est_register);
|
||||
|
||||
|
||||
/**
|
||||
* Unregister an event size table
|
||||
*
|
||||
* This just removes the specified entry and moves the ones after it
|
||||
* to fill in the gap. This is needed to keep the list sorted; no
|
||||
* reallocation is done to reduce the size of the table.
|
||||
*
|
||||
* We unregister by all the data we used to register instead of by
|
||||
* pointer to the @entry array because we might have used the same
|
||||
* table for a bunch of IDs (for example).
|
||||
*
|
||||
* @returns 0 if ok, < 0 errno on error (-ENOENT if not found).
|
||||
*/
|
||||
int uwb_est_unregister(u8 type, u8 event_high, u16 vendor, u16 product,
|
||||
const struct uwb_est_entry *entry, size_t entries)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned itr;
|
||||
struct uwb_est est_cmp = {
|
||||
.type_event_high = type << 8 | event_high,
|
||||
.vendor = vendor,
|
||||
.product = product,
|
||||
.entry = entry,
|
||||
.entries = entries
|
||||
};
|
||||
write_lock_irqsave(&uwb_est_lock, flags);
|
||||
for (itr = 0; itr < uwb_est_used; itr++)
|
||||
if (!memcmp(&uwb_est[itr], &est_cmp, sizeof(est_cmp)))
|
||||
goto found;
|
||||
write_unlock_irqrestore(&uwb_est_lock, flags);
|
||||
return -ENOENT;
|
||||
|
||||
found:
|
||||
if (itr < uwb_est_used - 1) /* Not last one? move ones above */
|
||||
memmove(&uwb_est[itr], &uwb_est[itr+1], uwb_est_used - itr - 1);
|
||||
uwb_est_used--;
|
||||
write_unlock_irqrestore(&uwb_est_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_est_unregister);
|
||||
|
||||
|
||||
/**
|
||||
* Get the size of an event from a table
|
||||
*
|
||||
* @rceb: pointer to the buffer with the event
|
||||
* @rceb_size: size of the area pointed to by @rceb in bytes.
|
||||
* @returns: > 0 Size of the event
|
||||
* -ENOSPC An area big enough was not provided to look
|
||||
* ahead into the event's guts and guess the size.
|
||||
* -EINVAL Unknown event code (wEvent).
|
||||
*
|
||||
* This will look at the received RCEB and guess what is the total
|
||||
* size. For variable sized events, it will look further ahead into
|
||||
* their length field to see how much data should be read.
|
||||
*
|
||||
* Note this size is *not* final--the neh (Notification/Event Handle)
|
||||
* might specificy an extra size to add.
|
||||
*/
|
||||
static
|
||||
ssize_t uwb_est_get_size(struct uwb_rc *uwb_rc, struct uwb_est *est,
|
||||
u8 event_low, const struct uwb_rceb *rceb,
|
||||
size_t rceb_size)
|
||||
{
|
||||
unsigned offset;
|
||||
ssize_t size;
|
||||
struct device *dev = &uwb_rc->uwb_dev.dev;
|
||||
const struct uwb_est_entry *entry;
|
||||
|
||||
size = -ENOENT;
|
||||
if (event_low >= est->entries) { /* in range? */
|
||||
dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: event %u out of range\n",
|
||||
est, est->type_event_high, est->vendor, est->product,
|
||||
est->entries, event_low);
|
||||
goto out;
|
||||
}
|
||||
size = -ENOENT;
|
||||
entry = &est->entry[event_low];
|
||||
if (entry->size == 0 && entry->offset == 0) { /* unknown? */
|
||||
dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: event %u unknown\n",
|
||||
est, est->type_event_high, est->vendor, est->product,
|
||||
est->entries, event_low);
|
||||
goto out;
|
||||
}
|
||||
offset = entry->offset; /* extra fries with that? */
|
||||
if (offset == 0)
|
||||
size = entry->size;
|
||||
else {
|
||||
/* Ops, got an extra size field at 'offset'--read it */
|
||||
const void *ptr = rceb;
|
||||
size_t type_size = 0;
|
||||
offset--;
|
||||
size = -ENOSPC; /* enough data for more? */
|
||||
switch (entry->type) {
|
||||
case UWB_EST_16: type_size = sizeof(__le16); break;
|
||||
case UWB_EST_8: type_size = sizeof(u8); break;
|
||||
default: BUG();
|
||||
}
|
||||
if (offset + type_size > rceb_size) {
|
||||
dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: "
|
||||
"not enough data to read extra size\n",
|
||||
est, est->type_event_high, est->vendor,
|
||||
est->product, est->entries);
|
||||
goto out;
|
||||
}
|
||||
size = entry->size;
|
||||
ptr += offset;
|
||||
switch (entry->type) {
|
||||
case UWB_EST_16: size += le16_to_cpu(*(__le16 *)ptr); break;
|
||||
case UWB_EST_8: size += *(u8 *)ptr; break;
|
||||
default: BUG();
|
||||
}
|
||||
}
|
||||
out:
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Guesses the size of a WA event
|
||||
*
|
||||
* @rceb: pointer to the buffer with the event
|
||||
* @rceb_size: size of the area pointed to by @rceb in bytes.
|
||||
* @returns: > 0 Size of the event
|
||||
* -ENOSPC An area big enough was not provided to look
|
||||
* ahead into the event's guts and guess the size.
|
||||
* -EINVAL Unknown event code (wEvent).
|
||||
*
|
||||
* This will look at the received RCEB and guess what is the total
|
||||
* size by checking all the tables registered with
|
||||
* uwb_est_register(). For variable sized events, it will look further
|
||||
* ahead into their length field to see how much data should be read.
|
||||
*
|
||||
* Note this size is *not* final--the neh (Notification/Event Handle)
|
||||
* might specificy an extra size to add or replace.
|
||||
*/
|
||||
ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb,
|
||||
size_t rceb_size)
|
||||
{
|
||||
/* FIXME: add vendor/product data */
|
||||
ssize_t size;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
unsigned long flags;
|
||||
unsigned itr;
|
||||
u16 type_event_high, event;
|
||||
u8 *ptr = (u8 *) rceb;
|
||||
|
||||
read_lock_irqsave(&uwb_est_lock, flags);
|
||||
d_printf(2, dev, "Size query for event 0x%02x/%04x/%02x,"
|
||||
" buffer size %ld\n",
|
||||
(unsigned) rceb->bEventType,
|
||||
(unsigned) le16_to_cpu(rceb->wEvent),
|
||||
(unsigned) rceb->bEventContext,
|
||||
(long) rceb_size);
|
||||
size = -ENOSPC;
|
||||
if (rceb_size < sizeof(*rceb))
|
||||
goto out;
|
||||
event = le16_to_cpu(rceb->wEvent);
|
||||
type_event_high = rceb->bEventType << 8 | (event & 0xff00) >> 8;
|
||||
for (itr = 0; itr < uwb_est_used; itr++) {
|
||||
d_printf(3, dev, "Checking EST 0x%04x/%04x/%04x\n",
|
||||
uwb_est[itr].type_event_high, uwb_est[itr].vendor,
|
||||
uwb_est[itr].product);
|
||||
if (uwb_est[itr].type_event_high != type_event_high)
|
||||
continue;
|
||||
size = uwb_est_get_size(rc, &uwb_est[itr],
|
||||
event & 0x00ff, rceb, rceb_size);
|
||||
/* try more tables that might handle the same type */
|
||||
if (size != -ENOENT)
|
||||
goto out;
|
||||
}
|
||||
dev_dbg(dev, "event 0x%02x/%04x/%02x: no handlers available; "
|
||||
"RCEB %02x %02x %02x %02x\n",
|
||||
(unsigned) rceb->bEventType,
|
||||
(unsigned) le16_to_cpu(rceb->wEvent),
|
||||
(unsigned) rceb->bEventContext,
|
||||
ptr[0], ptr[1], ptr[2], ptr[3]);
|
||||
size = -ENOENT;
|
||||
out:
|
||||
read_unlock_irqrestore(&uwb_est_lock, flags);
|
||||
return size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_est_find_size);
|
|
@ -0,0 +1,926 @@
|
|||
/*
|
||||
* WUSB Host Wire Adapter: Radio Control Interface (WUSB[8.6])
|
||||
* Radio Control command/event transport
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Initialize the Radio Control interface Driver.
|
||||
*
|
||||
* For each device probed, creates an 'struct hwarc' which contains
|
||||
* just the representation of the UWB Radio Controller, and the logic
|
||||
* for reading notifications and passing them to the UWB Core.
|
||||
*
|
||||
* So we initialize all of those, register the UWB Radio Controller
|
||||
* and setup the notification/event handle to pipe the notifications
|
||||
* to the UWB management Daemon.
|
||||
*
|
||||
* Command and event filtering.
|
||||
*
|
||||
* This is the driver for the Radio Control Interface described in WUSB
|
||||
* 1.0. The core UWB module assumes that all drivers are compliant to the
|
||||
* WHCI 0.95 specification. We thus create a filter that parses all
|
||||
* incoming messages from the (WUSB 1.0) device and manipulate them to
|
||||
* conform to the WHCI 0.95 specification. Similarly, outgoing messages
|
||||
* are parsed and manipulated to conform to the WUSB 1.0 compliant messages
|
||||
* that the device expects. Only a few messages are affected:
|
||||
* Affected events:
|
||||
* UWB_RC_EVT_BEACON
|
||||
* UWB_RC_EVT_BP_SLOT_CHANGE
|
||||
* UWB_RC_EVT_DRP_AVAIL
|
||||
* UWB_RC_EVT_DRP
|
||||
* Affected commands:
|
||||
* UWB_RC_CMD_SCAN
|
||||
* UWB_RC_CMD_SET_DRP_IE
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
#include <linux/version.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/wusb.h>
|
||||
#include <linux/usb/wusb-wa.h>
|
||||
#include <linux/uwb.h>
|
||||
#include "uwb-internal.h"
|
||||
#define D_LOCAL 1
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/* The device uses commands and events from the WHCI specification, although
|
||||
* reporting itself as WUSB compliant. */
|
||||
#define WUSB_QUIRK_WHCI_CMD_EVT 0x01
|
||||
|
||||
/**
|
||||
* Descriptor for an instance of the UWB Radio Control Driver that
|
||||
* attaches to the RCI interface of the Host Wired Adapter.
|
||||
*
|
||||
* Unless there is a lock specific to the 'data members', all access
|
||||
* is protected by uwb_rc->mutex.
|
||||
*
|
||||
* The NEEP (Notification/Event EndPoint) URB (@neep_urb) writes to
|
||||
* @rd_buffer. Note there is no locking because it is perfectly (heh!)
|
||||
* serialized--probe() submits an URB, callback is called, processes
|
||||
* the data (synchronously), submits another URB, and so on. There is
|
||||
* no concurrent access to the buffer.
|
||||
*/
|
||||
struct hwarc {
|
||||
struct usb_device *usb_dev;
|
||||
struct usb_interface *usb_iface;
|
||||
struct uwb_rc *uwb_rc; /* UWB host controller */
|
||||
struct urb *neep_urb; /* Notification endpoint handling */
|
||||
struct edc neep_edc;
|
||||
void *rd_buffer; /* NEEP read buffer */
|
||||
};
|
||||
|
||||
|
||||
/* Beacon received notification (WUSB 1.0 [8.6.3.2]) */
|
||||
struct uwb_rc_evt_beacon_WUSB_0100 {
|
||||
struct uwb_rceb rceb;
|
||||
u8 bChannelNumber;
|
||||
__le16 wBPSTOffset;
|
||||
u8 bLQI;
|
||||
u8 bRSSI;
|
||||
__le16 wBeaconInfoLength;
|
||||
u8 BeaconInfo[];
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* Filter WUSB 1.0 BEACON RCV notification to be WHCI 0.95
|
||||
*
|
||||
* @header: the incoming event
|
||||
* @buf_size: size of buffer containing incoming event
|
||||
* @new_size: size of event after filtering completed
|
||||
*
|
||||
* The WHCI 0.95 spec has a "Beacon Type" field. This value is unknown at
|
||||
* the time we receive the beacon from WUSB so we just set it to
|
||||
* UWB_RC_BEACON_TYPE_NEIGHBOR as a default.
|
||||
* The solution below allocates memory upon receipt of every beacon from a
|
||||
* WUSB device. This will deteriorate performance. What is the right way to
|
||||
* do this?
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_evt_beacon_WUSB_0100(struct uwb_rc *rc,
|
||||
struct uwb_rceb **header,
|
||||
const size_t buf_size,
|
||||
size_t *new_size)
|
||||
{
|
||||
struct uwb_rc_evt_beacon_WUSB_0100 *be;
|
||||
struct uwb_rc_evt_beacon *newbe;
|
||||
size_t bytes_left, ielength;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
|
||||
be = container_of(*header, struct uwb_rc_evt_beacon_WUSB_0100, rceb);
|
||||
bytes_left = buf_size;
|
||||
if (bytes_left < sizeof(*be)) {
|
||||
dev_err(dev, "Beacon Received Notification: Not enough data "
|
||||
"to decode for filtering (%zu vs %zu bytes needed)\n",
|
||||
bytes_left, sizeof(*be));
|
||||
return -EINVAL;
|
||||
}
|
||||
bytes_left -= sizeof(*be);
|
||||
ielength = le16_to_cpu(be->wBeaconInfoLength);
|
||||
if (bytes_left < ielength) {
|
||||
dev_err(dev, "Beacon Received Notification: Not enough data "
|
||||
"to decode IEs (%zu vs %zu bytes needed)\n",
|
||||
bytes_left, ielength);
|
||||
return -EINVAL;
|
||||
}
|
||||
newbe = kzalloc(sizeof(*newbe) + ielength, GFP_ATOMIC);
|
||||
if (newbe == NULL)
|
||||
return -ENOMEM;
|
||||
newbe->rceb = be->rceb;
|
||||
newbe->bChannelNumber = be->bChannelNumber;
|
||||
newbe->bBeaconType = UWB_RC_BEACON_TYPE_NEIGHBOR;
|
||||
newbe->wBPSTOffset = be->wBPSTOffset;
|
||||
newbe->bLQI = be->bLQI;
|
||||
newbe->bRSSI = be->bRSSI;
|
||||
newbe->wBeaconInfoLength = be->wBeaconInfoLength;
|
||||
memcpy(newbe->BeaconInfo, be->BeaconInfo, ielength);
|
||||
*header = &newbe->rceb;
|
||||
*new_size = sizeof(*newbe) + ielength;
|
||||
return 1; /* calling function will free memory */
|
||||
}
|
||||
|
||||
|
||||
/* DRP Availability change notification (WUSB 1.0 [8.6.3.8]) */
|
||||
struct uwb_rc_evt_drp_avail_WUSB_0100 {
|
||||
struct uwb_rceb rceb;
|
||||
__le16 wIELength;
|
||||
u8 IEData[];
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* Filter WUSB 1.0 DRP AVAILABILITY CHANGE notification to be WHCI 0.95
|
||||
*
|
||||
* @header: the incoming event
|
||||
* @buf_size: size of buffer containing incoming event
|
||||
* @new_size: size of event after filtering completed
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_evt_drp_avail_WUSB_0100(struct uwb_rc *rc,
|
||||
struct uwb_rceb **header,
|
||||
const size_t buf_size,
|
||||
size_t *new_size)
|
||||
{
|
||||
struct uwb_rc_evt_drp_avail_WUSB_0100 *da;
|
||||
struct uwb_rc_evt_drp_avail *newda;
|
||||
struct uwb_ie_hdr *ie_hdr;
|
||||
size_t bytes_left, ielength;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
|
||||
|
||||
da = container_of(*header, struct uwb_rc_evt_drp_avail_WUSB_0100, rceb);
|
||||
bytes_left = buf_size;
|
||||
if (bytes_left < sizeof(*da)) {
|
||||
dev_err(dev, "Not enough data to decode DRP Avail "
|
||||
"Notification for filtering. Expected %zu, "
|
||||
"received %zu.\n", (size_t)sizeof(*da), bytes_left);
|
||||
return -EINVAL;
|
||||
}
|
||||
bytes_left -= sizeof(*da);
|
||||
ielength = le16_to_cpu(da->wIELength);
|
||||
if (bytes_left < ielength) {
|
||||
dev_err(dev, "DRP Avail Notification filter: IE length "
|
||||
"[%zu bytes] does not match actual length "
|
||||
"[%zu bytes].\n", ielength, bytes_left);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (ielength < sizeof(*ie_hdr)) {
|
||||
dev_err(dev, "DRP Avail Notification filter: Not enough "
|
||||
"data to decode IE [%zu bytes, %zu needed]\n",
|
||||
ielength, sizeof(*ie_hdr));
|
||||
return -EINVAL;
|
||||
}
|
||||
ie_hdr = (void *) da->IEData;
|
||||
if (ie_hdr->length > 32) {
|
||||
dev_err(dev, "DRP Availability Change event has unexpected "
|
||||
"length for filtering. Expected < 32 bytes, "
|
||||
"got %zu bytes.\n", (size_t)ie_hdr->length);
|
||||
return -EINVAL;
|
||||
}
|
||||
newda = kzalloc(sizeof(*newda), GFP_ATOMIC);
|
||||
if (newda == NULL)
|
||||
return -ENOMEM;
|
||||
newda->rceb = da->rceb;
|
||||
memcpy(newda->bmp, (u8 *) ie_hdr + sizeof(*ie_hdr), ie_hdr->length);
|
||||
*header = &newda->rceb;
|
||||
*new_size = sizeof(*newda);
|
||||
return 1; /* calling function will free memory */
|
||||
}
|
||||
|
||||
|
||||
/* DRP notification (WUSB 1.0 [8.6.3.9]) */
|
||||
struct uwb_rc_evt_drp_WUSB_0100 {
|
||||
struct uwb_rceb rceb;
|
||||
struct uwb_dev_addr wSrcAddr;
|
||||
u8 bExplicit;
|
||||
__le16 wIELength;
|
||||
u8 IEData[];
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* Filter WUSB 1.0 DRP Notification to be WHCI 0.95
|
||||
*
|
||||
* @header: the incoming event
|
||||
* @buf_size: size of buffer containing incoming event
|
||||
* @new_size: size of event after filtering completed
|
||||
*
|
||||
* It is hard to manage DRP reservations without having a Reason code.
|
||||
* Unfortunately there is none in the WUSB spec. We just set the default to
|
||||
* DRP IE RECEIVED.
|
||||
* We do not currently use the bBeaconSlotNumber value, so we set this to
|
||||
* zero for now.
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_evt_drp_WUSB_0100(struct uwb_rc *rc,
|
||||
struct uwb_rceb **header,
|
||||
const size_t buf_size,
|
||||
size_t *new_size)
|
||||
{
|
||||
struct uwb_rc_evt_drp_WUSB_0100 *drpev;
|
||||
struct uwb_rc_evt_drp *newdrpev;
|
||||
size_t bytes_left, ielength;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
|
||||
drpev = container_of(*header, struct uwb_rc_evt_drp_WUSB_0100, rceb);
|
||||
bytes_left = buf_size;
|
||||
if (bytes_left < sizeof(*drpev)) {
|
||||
dev_err(dev, "Not enough data to decode DRP Notification "
|
||||
"for filtering. Expected %zu, received %zu.\n",
|
||||
(size_t)sizeof(*drpev), bytes_left);
|
||||
return -EINVAL;
|
||||
}
|
||||
ielength = le16_to_cpu(drpev->wIELength);
|
||||
bytes_left -= sizeof(*drpev);
|
||||
if (bytes_left < ielength) {
|
||||
dev_err(dev, "DRP Notification filter: header length [%zu "
|
||||
"bytes] does not match actual length [%zu "
|
||||
"bytes].\n", ielength, bytes_left);
|
||||
return -EINVAL;
|
||||
}
|
||||
newdrpev = kzalloc(sizeof(*newdrpev) + ielength, GFP_ATOMIC);
|
||||
if (newdrpev == NULL)
|
||||
return -ENOMEM;
|
||||
newdrpev->rceb = drpev->rceb;
|
||||
newdrpev->src_addr = drpev->wSrcAddr;
|
||||
newdrpev->reason = UWB_DRP_NOTIF_DRP_IE_RCVD;
|
||||
newdrpev->beacon_slot_number = 0;
|
||||
newdrpev->ie_length = drpev->wIELength;
|
||||
memcpy(newdrpev->ie_data, drpev->IEData, ielength);
|
||||
*header = &newdrpev->rceb;
|
||||
*new_size = sizeof(*newdrpev) + ielength;
|
||||
return 1; /* calling function will free memory */
|
||||
}
|
||||
|
||||
|
||||
/* Scan Command (WUSB 1.0 [8.6.2.5]) */
|
||||
struct uwb_rc_cmd_scan_WUSB_0100 {
|
||||
struct uwb_rccb rccb;
|
||||
u8 bChannelNumber;
|
||||
u8 bScanState;
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* Filter WHCI 0.95 SCAN command to be WUSB 1.0 SCAN command
|
||||
*
|
||||
* @header: command sent to device (compliant to WHCI 0.95)
|
||||
* @size: size of command sent to device
|
||||
*
|
||||
* We only reduce the size by two bytes because the WUSB 1.0 scan command
|
||||
* does not have the last field (wStarttime). Also, make sure we don't send
|
||||
* the device an unexpected scan type.
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_cmd_scan_WUSB_0100(struct uwb_rc *rc,
|
||||
struct uwb_rccb **header,
|
||||
size_t *size)
|
||||
{
|
||||
struct uwb_rc_cmd_scan *sc;
|
||||
|
||||
sc = container_of(*header, struct uwb_rc_cmd_scan, rccb);
|
||||
|
||||
if (sc->bScanState == UWB_SCAN_ONLY_STARTTIME)
|
||||
sc->bScanState = UWB_SCAN_ONLY;
|
||||
/* Don't send the last two bytes. */
|
||||
*size -= 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* SET DRP IE command (WUSB 1.0 [8.6.2.7]) */
|
||||
struct uwb_rc_cmd_set_drp_ie_WUSB_0100 {
|
||||
struct uwb_rccb rccb;
|
||||
u8 bExplicit;
|
||||
__le16 wIELength;
|
||||
struct uwb_ie_drp IEData[];
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* Filter WHCI 0.95 SET DRP IE command to be WUSB 1.0 SET DRP IE command
|
||||
*
|
||||
* @header: command sent to device (compliant to WHCI 0.95)
|
||||
* @size: size of command sent to device
|
||||
*
|
||||
* WUSB has an extra bExplicit field - we assume always explicit
|
||||
* negotiation so this field is set. The command expected by the device is
|
||||
* thus larger than the one prepared by the driver so we need to
|
||||
* reallocate memory to accommodate this.
|
||||
* We trust the driver to send us the correct data so no checking is done
|
||||
* on incoming data - evn though it is variable length.
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_cmd_set_drp_ie_WUSB_0100(struct uwb_rc *rc,
|
||||
struct uwb_rccb **header,
|
||||
size_t *size)
|
||||
{
|
||||
struct uwb_rc_cmd_set_drp_ie *orgcmd;
|
||||
struct uwb_rc_cmd_set_drp_ie_WUSB_0100 *cmd;
|
||||
size_t ielength;
|
||||
|
||||
orgcmd = container_of(*header, struct uwb_rc_cmd_set_drp_ie, rccb);
|
||||
ielength = le16_to_cpu(orgcmd->wIELength);
|
||||
cmd = kzalloc(sizeof(*cmd) + ielength, GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
return -ENOMEM;
|
||||
cmd->rccb = orgcmd->rccb;
|
||||
cmd->bExplicit = 0;
|
||||
cmd->wIELength = orgcmd->wIELength;
|
||||
memcpy(cmd->IEData, orgcmd->IEData, ielength);
|
||||
*header = &cmd->rccb;
|
||||
*size = sizeof(*cmd) + ielength;
|
||||
return 1; /* calling function will free memory */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter data from WHCI driver to WUSB device
|
||||
*
|
||||
* @header: WHCI 0.95 compliant command from driver
|
||||
* @size: length of command
|
||||
*
|
||||
* The routine managing commands to the device (uwb_rc_cmd()) will call the
|
||||
* filtering function pointer (if it exists) before it passes any data to
|
||||
* the device. At this time the command has been formatted according to
|
||||
* WHCI 0.95 and is ready to be sent to the device.
|
||||
*
|
||||
* The filter function will be provided with the current command and its
|
||||
* length. The function will manipulate the command if necessary and
|
||||
* potentially reallocate memory for a command that needed more memory that
|
||||
* the given command. If new memory was created the function will return 1
|
||||
* to indicate to the calling function that the memory need to be freed
|
||||
* when not needed any more. The size will contain the new length of the
|
||||
* command.
|
||||
* If memory has not been allocated we rely on the original mechanisms to
|
||||
* free the memory of the command - even when we reduce the value of size.
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_cmd_WUSB_0100(struct uwb_rc *rc, struct uwb_rccb **header,
|
||||
size_t *size)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rccb *rccb = *header;
|
||||
int cmd = le16_to_cpu(rccb->wCommand);
|
||||
switch (cmd) {
|
||||
case UWB_RC_CMD_SCAN:
|
||||
result = hwarc_filter_cmd_scan_WUSB_0100(rc, header, size);
|
||||
break;
|
||||
case UWB_RC_CMD_SET_DRP_IE:
|
||||
result = hwarc_filter_cmd_set_drp_ie_WUSB_0100(rc, header, size);
|
||||
break;
|
||||
default:
|
||||
result = -ENOANO;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter data from WHCI driver to WUSB device
|
||||
*
|
||||
* @header: WHCI 0.95 compliant command from driver
|
||||
* @size: length of command
|
||||
*
|
||||
* Filter commands based on which protocol the device supports. The WUSB
|
||||
* errata should be the same as WHCI 0.95 so we do not filter that here -
|
||||
* only WUSB 1.0.
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_cmd(struct uwb_rc *rc, struct uwb_rccb **header,
|
||||
size_t *size)
|
||||
{
|
||||
int result = -ENOANO;
|
||||
if (rc->version == 0x0100)
|
||||
result = hwarc_filter_cmd_WUSB_0100(rc, header, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute return value as sum of incoming value and value at given offset
|
||||
*
|
||||
* @rceb: event for which we compute the size, it contains a variable
|
||||
* length field.
|
||||
* @core_size: size of the "non variable" part of the event
|
||||
* @offset: place in event where the length of the variable part is stored
|
||||
* @buf_size: total length of buffer in which event arrived - we need to make
|
||||
* sure we read the offset in memory that is still part of the event
|
||||
*/
|
||||
static
|
||||
ssize_t hwarc_get_event_size(struct uwb_rc *rc, const struct uwb_rceb *rceb,
|
||||
size_t core_size, size_t offset,
|
||||
const size_t buf_size)
|
||||
{
|
||||
ssize_t size = -ENOSPC;
|
||||
const void *ptr = rceb;
|
||||
size_t type_size = sizeof(__le16);
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
|
||||
if (offset + type_size >= buf_size) {
|
||||
dev_err(dev, "Not enough data to read extra size of event "
|
||||
"0x%02x/%04x/%02x, only got %zu bytes.\n",
|
||||
rceb->bEventType, le16_to_cpu(rceb->wEvent),
|
||||
rceb->bEventContext, buf_size);
|
||||
goto out;
|
||||
}
|
||||
ptr += offset;
|
||||
size = core_size + le16_to_cpu(*(__le16 *)ptr);
|
||||
out:
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
/* Beacon slot change notification (WUSB 1.0 [8.6.3.5]) */
|
||||
struct uwb_rc_evt_bp_slot_change_WUSB_0100 {
|
||||
struct uwb_rceb rceb;
|
||||
u8 bSlotNumber;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/**
|
||||
* Filter data from WUSB device to WHCI driver
|
||||
*
|
||||
* @header: incoming event
|
||||
* @buf_size: size of buffer in which event arrived
|
||||
* @_event_size: actual size of event in the buffer
|
||||
* @new_size: size of event after filtered
|
||||
*
|
||||
* We don't know how the buffer is constructed - there may be more than one
|
||||
* event in it so buffer length does not determine event length. We first
|
||||
* determine the expected size of the incoming event. This value is passed
|
||||
* back only if the actual filtering succeeded (so we know the computed
|
||||
* expected size is correct). This value will be zero if
|
||||
* the event did not need any filtering.
|
||||
*
|
||||
* WHCI interprets the BP Slot Change event's data differently than
|
||||
* WUSB. The event sizes are exactly the same. The data field
|
||||
* indicates the new beacon slot in which a RC is transmitting its
|
||||
* beacon. The maximum value of this is 96 (wMacBPLength ECMA-368
|
||||
* 17.16 (Table 117)). We thus know that the WUSB value will not set
|
||||
* the bit bNoSlot, so we don't really do anything (placeholder).
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_event_WUSB_0100(struct uwb_rc *rc, struct uwb_rceb **header,
|
||||
const size_t buf_size, size_t *_real_size,
|
||||
size_t *_new_size)
|
||||
{
|
||||
int result = -ENOANO;
|
||||
struct uwb_rceb *rceb = *header;
|
||||
int event = le16_to_cpu(rceb->wEvent);
|
||||
size_t event_size;
|
||||
size_t core_size, offset;
|
||||
|
||||
if (rceb->bEventType != UWB_RC_CET_GENERAL)
|
||||
goto out;
|
||||
switch (event) {
|
||||
case UWB_RC_EVT_BEACON:
|
||||
core_size = sizeof(struct uwb_rc_evt_beacon_WUSB_0100);
|
||||
offset = offsetof(struct uwb_rc_evt_beacon_WUSB_0100,
|
||||
wBeaconInfoLength);
|
||||
event_size = hwarc_get_event_size(rc, rceb, core_size,
|
||||
offset, buf_size);
|
||||
if (event_size < 0)
|
||||
goto out;
|
||||
*_real_size = event_size;
|
||||
result = hwarc_filter_evt_beacon_WUSB_0100(rc, header,
|
||||
buf_size, _new_size);
|
||||
break;
|
||||
case UWB_RC_EVT_BP_SLOT_CHANGE:
|
||||
*_new_size = *_real_size =
|
||||
sizeof(struct uwb_rc_evt_bp_slot_change_WUSB_0100);
|
||||
result = 0;
|
||||
break;
|
||||
|
||||
case UWB_RC_EVT_DRP_AVAIL:
|
||||
core_size = sizeof(struct uwb_rc_evt_drp_avail_WUSB_0100);
|
||||
offset = offsetof(struct uwb_rc_evt_drp_avail_WUSB_0100,
|
||||
wIELength);
|
||||
event_size = hwarc_get_event_size(rc, rceb, core_size,
|
||||
offset, buf_size);
|
||||
if (event_size < 0)
|
||||
goto out;
|
||||
*_real_size = event_size;
|
||||
result = hwarc_filter_evt_drp_avail_WUSB_0100(
|
||||
rc, header, buf_size, _new_size);
|
||||
break;
|
||||
|
||||
case UWB_RC_EVT_DRP:
|
||||
core_size = sizeof(struct uwb_rc_evt_drp_WUSB_0100);
|
||||
offset = offsetof(struct uwb_rc_evt_drp_WUSB_0100, wIELength);
|
||||
event_size = hwarc_get_event_size(rc, rceb, core_size,
|
||||
offset, buf_size);
|
||||
if (event_size < 0)
|
||||
goto out;
|
||||
*_real_size = event_size;
|
||||
result = hwarc_filter_evt_drp_WUSB_0100(rc, header,
|
||||
buf_size, _new_size);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter data from WUSB device to WHCI driver
|
||||
*
|
||||
* @header: incoming event
|
||||
* @buf_size: size of buffer in which event arrived
|
||||
* @_event_size: actual size of event in the buffer
|
||||
* @_new_size: size of event after filtered
|
||||
*
|
||||
* Filter events based on which protocol the device supports. The WUSB
|
||||
* errata should be the same as WHCI 0.95 so we do not filter that here -
|
||||
* only WUSB 1.0.
|
||||
*
|
||||
* If we don't handle it, we return -ENOANO (why the weird error code?
|
||||
* well, so if I get it, I can pinpoint in the code that raised
|
||||
* it...after all, not too many places use the higher error codes).
|
||||
*/
|
||||
static
|
||||
int hwarc_filter_event(struct uwb_rc *rc, struct uwb_rceb **header,
|
||||
const size_t buf_size, size_t *_real_size,
|
||||
size_t *_new_size)
|
||||
{
|
||||
int result = -ENOANO;
|
||||
if (rc->version == 0x0100)
|
||||
result = hwarc_filter_event_WUSB_0100(
|
||||
rc, header, buf_size, _real_size, _new_size);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute an UWB RC command on HWA
|
||||
*
|
||||
* @rc: Instance of a Radio Controller that is a HWA
|
||||
* @cmd: Buffer containing the RCCB and payload to execute
|
||||
* @cmd_size: Size of the command buffer.
|
||||
*
|
||||
* NOTE: rc's mutex has to be locked
|
||||
*/
|
||||
static
|
||||
int hwarc_cmd(struct uwb_rc *uwb_rc, const struct uwb_rccb *cmd, size_t cmd_size)
|
||||
{
|
||||
struct hwarc *hwarc = uwb_rc->priv;
|
||||
return usb_control_msg(
|
||||
hwarc->usb_dev, usb_sndctrlpipe(hwarc->usb_dev, 0),
|
||||
WA_EXEC_RC_CMD, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
0, hwarc->usb_iface->cur_altsetting->desc.bInterfaceNumber,
|
||||
(void *) cmd, cmd_size, 100 /* FIXME: this is totally arbitrary */);
|
||||
}
|
||||
|
||||
static
|
||||
int hwarc_reset(struct uwb_rc *uwb_rc)
|
||||
{
|
||||
struct hwarc *hwarc = uwb_rc->priv;
|
||||
return usb_reset_device(hwarc->usb_dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the notification and event endpoint
|
||||
*
|
||||
* Check's that everything is fine and then passes the read data to
|
||||
* the notification/event handling mechanism (neh).
|
||||
*/
|
||||
static
|
||||
void hwarc_neep_cb(struct urb *urb)
|
||||
{
|
||||
struct hwarc *hwarc = urb->context;
|
||||
struct usb_interface *usb_iface = hwarc->usb_iface;
|
||||
struct device *dev = &usb_iface->dev;
|
||||
int result;
|
||||
|
||||
switch (result = urb->status) {
|
||||
case 0:
|
||||
d_printf(3, dev, "NEEP: receive stat %d, %zu bytes\n",
|
||||
urb->status, (size_t)urb->actual_length);
|
||||
uwb_rc_neh_grok(hwarc->uwb_rc, urb->transfer_buffer,
|
||||
urb->actual_length);
|
||||
break;
|
||||
case -ECONNRESET: /* Not an error, but a controlled situation; */
|
||||
case -ENOENT: /* (we killed the URB)...so, no broadcast */
|
||||
d_printf(2, dev, "NEEP: URB reset/noent %d\n", urb->status);
|
||||
goto out;
|
||||
case -ESHUTDOWN: /* going away! */
|
||||
d_printf(2, dev, "NEEP: URB down %d\n", urb->status);
|
||||
goto out;
|
||||
default: /* On general errors, retry unless it gets ugly */
|
||||
if (edc_inc(&hwarc->neep_edc, EDC_MAX_ERRORS,
|
||||
EDC_ERROR_TIMEFRAME))
|
||||
goto error_exceeded;
|
||||
dev_err(dev, "NEEP: URB error %d\n", urb->status);
|
||||
}
|
||||
result = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
d_printf(3, dev, "NEEP: submit %d\n", result);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "NEEP: Can't resubmit URB (%d) resetting device\n",
|
||||
result);
|
||||
goto error;
|
||||
}
|
||||
out:
|
||||
return;
|
||||
|
||||
error_exceeded:
|
||||
dev_err(dev, "NEEP: URB max acceptable errors "
|
||||
"exceeded, resetting device\n");
|
||||
error:
|
||||
uwb_rc_neh_error(hwarc->uwb_rc, result);
|
||||
uwb_rc_reset_all(hwarc->uwb_rc);
|
||||
return;
|
||||
}
|
||||
|
||||
static void hwarc_init(struct hwarc *hwarc)
|
||||
{
|
||||
edc_init(&hwarc->neep_edc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the notification/event endpoint stuff
|
||||
*
|
||||
* Note this is effectively a parallel thread; it knows that
|
||||
* hwarc->uwb_rc always exists because the existence of a 'hwarc'
|
||||
* means that there is a reverence on the hwarc->uwb_rc (see
|
||||
* _probe()), and thus _neep_cb() can execute safely.
|
||||
*/
|
||||
static int hwarc_neep_init(struct uwb_rc *rc)
|
||||
{
|
||||
struct hwarc *hwarc = rc->priv;
|
||||
struct usb_interface *iface = hwarc->usb_iface;
|
||||
struct usb_device *usb_dev = interface_to_usbdev(iface);
|
||||
struct device *dev = &iface->dev;
|
||||
int result;
|
||||
struct usb_endpoint_descriptor *epd;
|
||||
|
||||
epd = &iface->cur_altsetting->endpoint[0].desc;
|
||||
hwarc->rd_buffer = (void *) __get_free_page(GFP_KERNEL);
|
||||
if (hwarc->rd_buffer == NULL) {
|
||||
dev_err(dev, "Unable to allocate notification's read buffer\n");
|
||||
goto error_rd_buffer;
|
||||
}
|
||||
hwarc->neep_urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (hwarc->neep_urb == NULL) {
|
||||
dev_err(dev, "Unable to allocate notification URB\n");
|
||||
goto error_urb_alloc;
|
||||
}
|
||||
usb_fill_int_urb(hwarc->neep_urb, usb_dev,
|
||||
usb_rcvintpipe(usb_dev, epd->bEndpointAddress),
|
||||
hwarc->rd_buffer, PAGE_SIZE,
|
||||
hwarc_neep_cb, hwarc, epd->bInterval);
|
||||
result = usb_submit_urb(hwarc->neep_urb, GFP_ATOMIC);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot submit notification URB: %d\n", result);
|
||||
goto error_neep_submit;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error_neep_submit:
|
||||
usb_free_urb(hwarc->neep_urb);
|
||||
error_urb_alloc:
|
||||
free_page((unsigned long)hwarc->rd_buffer);
|
||||
error_rd_buffer:
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
|
||||
/** Clean up all the notification endpoint resources */
|
||||
static void hwarc_neep_release(struct uwb_rc *rc)
|
||||
{
|
||||
struct hwarc *hwarc = rc->priv;
|
||||
|
||||
usb_kill_urb(hwarc->neep_urb);
|
||||
usb_free_urb(hwarc->neep_urb);
|
||||
free_page((unsigned long)hwarc->rd_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version from 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 hwarc_get_version(struct uwb_rc *rc)
|
||||
{
|
||||
int result;
|
||||
|
||||
struct hwarc *hwarc = rc->priv;
|
||||
struct uwb_rc_control_intf_class_desc *descr;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct usb_device *usb_dev = hwarc->usb_dev;
|
||||
char *itr;
|
||||
struct usb_descriptor_header *hdr;
|
||||
size_t itr_size, actconfig_idx;
|
||||
u16 version;
|
||||
|
||||
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_CS_RADIO_CONTROL)
|
||||
goto found;
|
||||
itr += hdr->bLength;
|
||||
itr_size -= hdr->bLength;
|
||||
}
|
||||
dev_err(dev, "cannot find Radio Control Interface Class descriptor\n");
|
||||
return -ENODEV;
|
||||
|
||||
found:
|
||||
result = -EINVAL;
|
||||
if (hdr->bLength > itr_size) { /* is it available? */
|
||||
dev_err(dev, "incomplete Radio Control Interface Class "
|
||||
"descriptor (%zu bytes left, %u needed)\n",
|
||||
itr_size, hdr->bLength);
|
||||
goto error;
|
||||
}
|
||||
if (hdr->bLength < sizeof(*descr)) {
|
||||
dev_err(dev, "short Radio Control Interface Class "
|
||||
"descriptor\n");
|
||||
goto error;
|
||||
}
|
||||
descr = (struct uwb_rc_control_intf_class_desc *) hdr;
|
||||
/* Make LE fields CPU order */
|
||||
version = __le16_to_cpu(descr->bcdRCIVersion);
|
||||
if (version != 0x0100) {
|
||||
dev_err(dev, "Device reports protocol version 0x%04x. We "
|
||||
"do not support that. \n", version);
|
||||
result = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
rc->version = version;
|
||||
d_printf(3, dev, "Device supports WUSB protocol version 0x%04x \n",
|
||||
rc->version);
|
||||
result = 0;
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* By creating a 'uwb_rc', we have a reference on it -- that reference
|
||||
* is the one we drop when we disconnect.
|
||||
*
|
||||
* No need to switch altsettings; according to WUSB1.0[8.6.1.1], there
|
||||
* is only one altsetting allowed.
|
||||
*/
|
||||
static int hwarc_probe(struct usb_interface *iface,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rc *uwb_rc;
|
||||
struct hwarc *hwarc;
|
||||
struct device *dev = &iface->dev;
|
||||
|
||||
result = -ENOMEM;
|
||||
uwb_rc = uwb_rc_alloc();
|
||||
if (uwb_rc == NULL) {
|
||||
dev_err(dev, "unable to allocate RC instance\n");
|
||||
goto error_rc_alloc;
|
||||
}
|
||||
hwarc = kzalloc(sizeof(*hwarc), GFP_KERNEL);
|
||||
if (hwarc == NULL) {
|
||||
dev_err(dev, "unable to allocate HWA RC instance\n");
|
||||
goto error_alloc;
|
||||
}
|
||||
hwarc_init(hwarc);
|
||||
hwarc->usb_dev = usb_get_dev(interface_to_usbdev(iface));
|
||||
hwarc->usb_iface = usb_get_intf(iface);
|
||||
hwarc->uwb_rc = uwb_rc;
|
||||
|
||||
uwb_rc->owner = THIS_MODULE;
|
||||
uwb_rc->start = hwarc_neep_init;
|
||||
uwb_rc->stop = hwarc_neep_release;
|
||||
uwb_rc->cmd = hwarc_cmd;
|
||||
uwb_rc->reset = hwarc_reset;
|
||||
if (id->driver_info & WUSB_QUIRK_WHCI_CMD_EVT) {
|
||||
uwb_rc->filter_cmd = NULL;
|
||||
uwb_rc->filter_event = NULL;
|
||||
} else {
|
||||
uwb_rc->filter_cmd = hwarc_filter_cmd;
|
||||
uwb_rc->filter_event = hwarc_filter_event;
|
||||
}
|
||||
|
||||
result = uwb_rc_add(uwb_rc, dev, hwarc);
|
||||
if (result < 0)
|
||||
goto error_rc_add;
|
||||
result = hwarc_get_version(uwb_rc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot retrieve version of RC \n");
|
||||
goto error_get_version;
|
||||
}
|
||||
usb_set_intfdata(iface, hwarc);
|
||||
return 0;
|
||||
|
||||
error_get_version:
|
||||
uwb_rc_rm(uwb_rc);
|
||||
error_rc_add:
|
||||
usb_put_intf(iface);
|
||||
usb_put_dev(hwarc->usb_dev);
|
||||
error_alloc:
|
||||
uwb_rc_put(uwb_rc);
|
||||
error_rc_alloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hwarc_disconnect(struct usb_interface *iface)
|
||||
{
|
||||
struct hwarc *hwarc = usb_get_intfdata(iface);
|
||||
struct uwb_rc *uwb_rc = hwarc->uwb_rc;
|
||||
|
||||
usb_set_intfdata(hwarc->usb_iface, NULL);
|
||||
uwb_rc_rm(uwb_rc);
|
||||
usb_put_intf(hwarc->usb_iface);
|
||||
usb_put_dev(hwarc->usb_dev);
|
||||
d_printf(1, &hwarc->usb_iface->dev, "freed hwarc %p\n", hwarc);
|
||||
kfree(hwarc);
|
||||
uwb_rc_put(uwb_rc); /* when creating the device, refcount = 1 */
|
||||
}
|
||||
|
||||
/** USB device ID's that we handle */
|
||||
static struct usb_device_id hwarc_id_table[] = {
|
||||
/* D-Link DUB-1210 */
|
||||
{ USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3d02, 0xe0, 0x01, 0x02),
|
||||
.driver_info = WUSB_QUIRK_WHCI_CMD_EVT },
|
||||
/* Intel i1480 (using firmware 1.3PA2-20070828) */
|
||||
{ USB_DEVICE_AND_INTERFACE_INFO(0x8086, 0x0c3b, 0xe0, 0x01, 0x02),
|
||||
.driver_info = WUSB_QUIRK_WHCI_CMD_EVT },
|
||||
/* Generic match for the Radio Control interface */
|
||||
{ USB_INTERFACE_INFO(0xe0, 0x01, 0x02), },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, hwarc_id_table);
|
||||
|
||||
static struct usb_driver hwarc_driver = {
|
||||
.name = "hwa-rc",
|
||||
.probe = hwarc_probe,
|
||||
.disconnect = hwarc_disconnect,
|
||||
.id_table = hwarc_id_table,
|
||||
};
|
||||
|
||||
static int __init hwarc_driver_init(void)
|
||||
{
|
||||
int result;
|
||||
result = usb_register(&hwarc_driver);
|
||||
if (result < 0)
|
||||
printk(KERN_ERR "HWA-RC: Cannot register USB driver: %d\n",
|
||||
result);
|
||||
return result;
|
||||
|
||||
}
|
||||
module_init(hwarc_driver_init);
|
||||
|
||||
static void __exit hwarc_driver_exit(void)
|
||||
{
|
||||
usb_deregister(&hwarc_driver);
|
||||
}
|
||||
module_exit(hwarc_driver_exit);
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("Host Wireless Adapter Radio Control Driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,2 @@
|
|||
obj-$(CONFIG_UWB_I1480U) += dfu/ i1480-est.o
|
||||
obj-$(CONFIG_UWB_I1480U_WLP) += i1480u-wlp/
|
|
@ -0,0 +1,9 @@
|
|||
obj-$(CONFIG_UWB_I1480U) += i1480-dfu-usb.o
|
||||
|
||||
i1480-dfu-usb-objs := \
|
||||
dfu.o \
|
||||
mac.o \
|
||||
phy.o \
|
||||
usb.o
|
||||
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Intel Wireless UWB Link 1480
|
||||
* Main driver
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Common code for firmware upload used by the USB and PCI version;
|
||||
* i1480_fw_upload() takes a device descriptor and uses the function
|
||||
* pointers it provides to upload firmware and prepare the PHY.
|
||||
*
|
||||
* As well, provides common functions used by the rest of the code.
|
||||
*/
|
||||
#include "i1480-dfu.h"
|
||||
#include <linux/errno.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/random.h>
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/**
|
||||
* i1480_rceb_check - Check RCEB for expected field values
|
||||
* @i1480: pointer to device for which RCEB is being checked
|
||||
* @rceb: RCEB being checked
|
||||
* @cmd: which command the RCEB is related to
|
||||
* @context: expected context
|
||||
* @expected_type: expected event type
|
||||
* @expected_event: expected event
|
||||
*
|
||||
* If @cmd is NULL, do not print error messages, but still return an error
|
||||
* code.
|
||||
*
|
||||
* Return 0 if @rceb matches the expected values, -EINVAL otherwise.
|
||||
*/
|
||||
int i1480_rceb_check(const struct i1480 *i1480, const struct uwb_rceb *rceb,
|
||||
const char *cmd, u8 context, u8 expected_type,
|
||||
unsigned expected_event)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = i1480->dev;
|
||||
if (rceb->bEventContext != context) {
|
||||
if (cmd)
|
||||
dev_err(dev, "%s: unexpected context id 0x%02x "
|
||||
"(expected 0x%02x)\n", cmd,
|
||||
rceb->bEventContext, context);
|
||||
result = -EINVAL;
|
||||
}
|
||||
if (rceb->bEventType != expected_type) {
|
||||
if (cmd)
|
||||
dev_err(dev, "%s: unexpected event type 0x%02x "
|
||||
"(expected 0x%02x)\n", cmd,
|
||||
rceb->bEventType, expected_type);
|
||||
result = -EINVAL;
|
||||
}
|
||||
if (le16_to_cpu(rceb->wEvent) != expected_event) {
|
||||
if (cmd)
|
||||
dev_err(dev, "%s: unexpected event 0x%04x "
|
||||
"(expected 0x%04x)\n", cmd,
|
||||
le16_to_cpu(rceb->wEvent), expected_event);
|
||||
result = -EINVAL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i1480_rceb_check);
|
||||
|
||||
|
||||
/**
|
||||
* Execute a Radio Control Command
|
||||
*
|
||||
* Command data has to be in i1480->cmd_buf.
|
||||
*
|
||||
* @returns size of the reply data filled in i1480->evt_buf or < 0 errno
|
||||
* code on error.
|
||||
*/
|
||||
ssize_t i1480_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size,
|
||||
size_t reply_size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct uwb_rceb *reply = i1480->evt_buf;
|
||||
struct uwb_rccb *cmd = i1480->cmd_buf;
|
||||
u16 expected_event = reply->wEvent;
|
||||
u8 expected_type = reply->bEventType;
|
||||
u8 context;
|
||||
|
||||
d_fnstart(3, i1480->dev, "(%p, %s, %zu)\n", i1480, cmd_name, cmd_size);
|
||||
init_completion(&i1480->evt_complete);
|
||||
i1480->evt_result = -EINPROGRESS;
|
||||
do {
|
||||
get_random_bytes(&context, 1);
|
||||
} while (context == 0x00 || context == 0xff);
|
||||
cmd->bCommandContext = context;
|
||||
result = i1480->cmd(i1480, cmd_name, cmd_size);
|
||||
if (result < 0)
|
||||
goto error;
|
||||
/* wait for the callback to report a event was received */
|
||||
result = wait_for_completion_interruptible_timeout(
|
||||
&i1480->evt_complete, HZ);
|
||||
if (result == 0) {
|
||||
result = -ETIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
if (result < 0)
|
||||
goto error;
|
||||
result = i1480->evt_result;
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "%s: command reply reception failed: %zd\n",
|
||||
cmd_name, result);
|
||||
goto error;
|
||||
}
|
||||
/*
|
||||
* Firmware versions >= 1.4.12224 for IOGear GUWA100U generate a
|
||||
* spurious notification after firmware is downloaded. So check whether
|
||||
* the receibed RCEB is such notification before assuming that the
|
||||
* command has failed.
|
||||
*/
|
||||
if (i1480_rceb_check(i1480, i1480->evt_buf, NULL,
|
||||
0, 0xfd, 0x0022) == 0) {
|
||||
/* Now wait for the actual RCEB for this command. */
|
||||
result = i1480->wait_init_done(i1480);
|
||||
if (result < 0)
|
||||
goto error;
|
||||
result = i1480->evt_result;
|
||||
}
|
||||
if (result != reply_size) {
|
||||
dev_err(i1480->dev, "%s returned only %zu bytes, %zu expected\n",
|
||||
cmd_name, result, reply_size);
|
||||
result = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
/* Verify we got the right event in response */
|
||||
result = i1480_rceb_check(i1480, i1480->evt_buf, cmd_name, context,
|
||||
expected_type, expected_event);
|
||||
error:
|
||||
d_fnend(3, i1480->dev, "(%p, %s, %zu) = %zd\n",
|
||||
i1480, cmd_name, cmd_size, result);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i1480_cmd);
|
||||
|
||||
|
||||
static
|
||||
int i1480_print_state(struct i1480 *i1480)
|
||||
{
|
||||
int result;
|
||||
u32 *buf = (u32 *) i1480->cmd_buf;
|
||||
|
||||
result = i1480->read(i1480, 0x80080000, 2 * sizeof(*buf));
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "cannot read U & L states: %d\n", result);
|
||||
goto error;
|
||||
}
|
||||
dev_info(i1480->dev, "state U 0x%08x, L 0x%08x\n", buf[0], buf[1]);
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PCI probe, firmware uploader
|
||||
*
|
||||
* _mac_fw_upload() will call rc_setup(), which needs an rc_release().
|
||||
*/
|
||||
int i1480_fw_upload(struct i1480 *i1480)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = i1480_pre_fw_upload(i1480); /* PHY pre fw */
|
||||
if (result < 0 && result != -ENOENT) {
|
||||
i1480_print_state(i1480);
|
||||
goto error;
|
||||
}
|
||||
result = i1480_mac_fw_upload(i1480); /* MAC fw */
|
||||
if (result < 0) {
|
||||
if (result == -ENOENT)
|
||||
dev_err(i1480->dev, "Cannot locate MAC FW file '%s'\n",
|
||||
i1480->mac_fw_name);
|
||||
else
|
||||
i1480_print_state(i1480);
|
||||
goto error;
|
||||
}
|
||||
result = i1480_phy_fw_upload(i1480); /* PHY fw */
|
||||
if (result < 0 && result != -ENOENT) {
|
||||
i1480_print_state(i1480);
|
||||
goto error_rc_release;
|
||||
}
|
||||
/*
|
||||
* FIXME: find some reliable way to check whether firmware is running
|
||||
* properly. Maybe use some standard request that has no side effects?
|
||||
*/
|
||||
dev_info(i1480->dev, "firmware uploaded successfully\n");
|
||||
error_rc_release:
|
||||
if (i1480->rc_release)
|
||||
i1480->rc_release(i1480);
|
||||
result = 0;
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i1480_fw_upload);
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* i1480 Device Firmware Upload
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This driver is the firmware uploader for the Intel Wireless UWB
|
||||
* Link 1480 device (both in the USB and PCI incarnations).
|
||||
*
|
||||
* The process is quite simple: we stop the device, write the firmware
|
||||
* to its memory and then restart it. Wait for the device to let us
|
||||
* know it is done booting firmware. Ready.
|
||||
*
|
||||
* We might have to upload before or after a phy firmware (which might
|
||||
* be done in two methods, using a normal firmware image or through
|
||||
* the MPI port).
|
||||
*
|
||||
* Because USB and PCI use common methods, we just make ops out of the
|
||||
* common operations (read, write, wait_init_done and cmd) and
|
||||
* implement them in usb.c and pci.c.
|
||||
*
|
||||
* The flow is (some parts omitted):
|
||||
*
|
||||
* i1480_{usb,pci}_probe() On enumerate/discovery
|
||||
* i1480_fw_upload()
|
||||
* i1480_pre_fw_upload()
|
||||
* __mac_fw_upload()
|
||||
* fw_hdrs_load()
|
||||
* mac_fw_hdrs_push()
|
||||
* i1480->write() [i1480_{usb,pci}_write()]
|
||||
* i1480_fw_cmp()
|
||||
* i1480->read() [i1480_{usb,pci}_read()]
|
||||
* i1480_mac_fw_upload()
|
||||
* __mac_fw_upload()
|
||||
* i1480->setup(()
|
||||
* i1480->wait_init_done()
|
||||
* i1480_cmd_reset()
|
||||
* i1480->cmd() [i1480_{usb,pci}_cmd()]
|
||||
* ...
|
||||
* i1480_phy_fw_upload()
|
||||
* request_firmware()
|
||||
* i1480_mpi_write()
|
||||
* i1480->cmd() [i1480_{usb,pci}_cmd()]
|
||||
*
|
||||
* Once the probe function enumerates the device and uploads the
|
||||
* firmware, we just exit with -ENODEV, as we don't really want to
|
||||
* attach to the device.
|
||||
*/
|
||||
#ifndef __i1480_DFU_H__
|
||||
#define __i1480_DFU_H__
|
||||
|
||||
#include <linux/uwb/spec.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
#define i1480_FW_UPLOAD_MODE_MASK (cpu_to_le32(0x00000018))
|
||||
|
||||
#if i1480_FW > 0x00000302
|
||||
#define i1480_RCEB_EXTENDED
|
||||
#endif
|
||||
|
||||
struct uwb_rccb;
|
||||
struct uwb_rceb;
|
||||
|
||||
/*
|
||||
* Common firmware upload handlers
|
||||
*
|
||||
* Normally you embed this struct in another one specific to your hw.
|
||||
*
|
||||
* @write Write to device's memory from buffer.
|
||||
* @read Read from device's memory to i1480->evt_buf.
|
||||
* @setup Setup device after basic firmware is uploaded
|
||||
* @wait_init_done
|
||||
* Wait for the device to send a notification saying init
|
||||
* is done.
|
||||
* @cmd FOP for issuing the command to the hardware. The
|
||||
* command data is contained in i1480->cmd_buf and the size
|
||||
* is supplied as an argument. The command replied is put
|
||||
* in i1480->evt_buf and the size in i1480->evt_result (or if
|
||||
* an error, a < 0 errno code).
|
||||
*
|
||||
* @cmd_buf Memory buffer used to send commands to the device.
|
||||
* Allocated by the upper layers i1480_fw_upload().
|
||||
* Size has to be @buf_size.
|
||||
* @evt_buf Memory buffer used to place the async notifications
|
||||
* received by the hw. Allocated by the upper layers
|
||||
* i1480_fw_upload().
|
||||
* Size has to be @buf_size.
|
||||
* @cmd_complete
|
||||
* Low level driver uses this to notify code waiting afor
|
||||
* an event that the event has arrived and data is in
|
||||
* i1480->evt_buf (and size/result in i1480->evt_result).
|
||||
* @hw_rev
|
||||
* Use this value to activate dfu code to support new revisions
|
||||
* of hardware. i1480_init() sets this to a default value.
|
||||
* It should be updated by the USB and PCI code.
|
||||
*/
|
||||
struct i1480 {
|
||||
struct device *dev;
|
||||
|
||||
int (*write)(struct i1480 *, u32 addr, const void *, size_t);
|
||||
int (*read)(struct i1480 *, u32 addr, size_t);
|
||||
int (*rc_setup)(struct i1480 *);
|
||||
void (*rc_release)(struct i1480 *);
|
||||
int (*wait_init_done)(struct i1480 *);
|
||||
int (*cmd)(struct i1480 *, const char *cmd_name, size_t cmd_size);
|
||||
const char *pre_fw_name;
|
||||
const char *mac_fw_name;
|
||||
const char *mac_fw_name_deprecate; /* FIXME: Will go away */
|
||||
const char *phy_fw_name;
|
||||
u8 hw_rev;
|
||||
|
||||
size_t buf_size; /* size of both evt_buf and cmd_buf */
|
||||
void *evt_buf, *cmd_buf;
|
||||
ssize_t evt_result;
|
||||
struct completion evt_complete;
|
||||
};
|
||||
|
||||
static inline
|
||||
void i1480_init(struct i1480 *i1480)
|
||||
{
|
||||
i1480->hw_rev = 1;
|
||||
init_completion(&i1480->evt_complete);
|
||||
}
|
||||
|
||||
extern int i1480_fw_upload(struct i1480 *);
|
||||
extern int i1480_pre_fw_upload(struct i1480 *);
|
||||
extern int i1480_mac_fw_upload(struct i1480 *);
|
||||
extern int i1480_phy_fw_upload(struct i1480 *);
|
||||
extern ssize_t i1480_cmd(struct i1480 *, const char *, size_t, size_t);
|
||||
extern int i1480_rceb_check(const struct i1480 *,
|
||||
const struct uwb_rceb *, const char *, u8,
|
||||
u8, unsigned);
|
||||
|
||||
enum {
|
||||
/* Vendor specific command type */
|
||||
i1480_CET_VS1 = 0xfd,
|
||||
/* i1480 commands */
|
||||
i1480_CMD_SET_IP_MAS = 0x000e,
|
||||
i1480_CMD_GET_MAC_PHY_INFO = 0x0003,
|
||||
i1480_CMD_MPI_WRITE = 0x000f,
|
||||
i1480_CMD_MPI_READ = 0x0010,
|
||||
/* i1480 events */
|
||||
#if i1480_FW > 0x00000302
|
||||
i1480_EVT_CONFIRM = 0x0002,
|
||||
i1480_EVT_RM_INIT_DONE = 0x0101,
|
||||
i1480_EVT_DEV_ADD = 0x0103,
|
||||
i1480_EVT_DEV_RM = 0x0104,
|
||||
i1480_EVT_DEV_ID_CHANGE = 0x0105,
|
||||
i1480_EVT_GET_MAC_PHY_INFO = i1480_CMD_GET_MAC_PHY_INFO,
|
||||
#else
|
||||
i1480_EVT_CONFIRM = 0x0002,
|
||||
i1480_EVT_RM_INIT_DONE = 0x0101,
|
||||
i1480_EVT_DEV_ADD = 0x0103,
|
||||
i1480_EVT_DEV_RM = 0x0104,
|
||||
i1480_EVT_DEV_ID_CHANGE = 0x0105,
|
||||
i1480_EVT_GET_MAC_PHY_INFO = i1480_EVT_CONFIRM,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
struct i1480_evt_confirm {
|
||||
struct uwb_rceb rceb;
|
||||
#ifdef i1480_RCEB_EXTENDED
|
||||
__le16 wParamLength;
|
||||
#endif
|
||||
u8 bResultCode;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
struct i1480_rceb {
|
||||
struct uwb_rceb rceb;
|
||||
#ifdef i1480_RCEB_EXTENDED
|
||||
__le16 wParamLength;
|
||||
#endif
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/**
|
||||
* Get MAC & PHY Information confirm event structure
|
||||
*
|
||||
* Confirm event returned by the command.
|
||||
*/
|
||||
struct i1480_evt_confirm_GMPI {
|
||||
#if i1480_FW > 0x00000302
|
||||
struct uwb_rceb rceb;
|
||||
__le16 wParamLength;
|
||||
__le16 status;
|
||||
u8 mac_addr[6]; /* EUI-64 bit IEEE address [still 8 bytes?] */
|
||||
u8 dev_addr[2];
|
||||
__le16 mac_fw_rev; /* major = v >> 8; minor = v & 0xff */
|
||||
u8 hw_rev;
|
||||
u8 phy_vendor;
|
||||
u8 phy_rev; /* major v = >> 8; minor = v & 0xff */
|
||||
__le16 mac_caps;
|
||||
u8 phy_caps[3];
|
||||
u8 key_stores;
|
||||
__le16 mcast_addr_stores;
|
||||
u8 sec_mode_supported;
|
||||
#else
|
||||
struct uwb_rceb rceb;
|
||||
u8 status;
|
||||
u8 mac_addr[8]; /* EUI-64 bit IEEE address [still 8 bytes?] */
|
||||
u8 dev_addr[2];
|
||||
__le16 mac_fw_rev; /* major = v >> 8; minor = v & 0xff */
|
||||
__le16 phy_fw_rev; /* major v = >> 8; minor = v & 0xff */
|
||||
__le16 mac_caps;
|
||||
u8 phy_caps;
|
||||
u8 key_stores;
|
||||
__le16 mcast_addr_stores;
|
||||
u8 sec_mode_supported;
|
||||
#endif
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
struct i1480_cmd_mpi_write {
|
||||
struct uwb_rccb rccb;
|
||||
__le16 size;
|
||||
u8 data[];
|
||||
};
|
||||
|
||||
|
||||
struct i1480_cmd_mpi_read {
|
||||
struct uwb_rccb rccb;
|
||||
__le16 size;
|
||||
struct {
|
||||
u8 page, offset;
|
||||
} __attribute__((packed)) data[];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
struct i1480_evt_mpi_read {
|
||||
struct uwb_rceb rceb;
|
||||
#ifdef i1480_RCEB_EXTENDED
|
||||
__le16 wParamLength;
|
||||
#endif
|
||||
u8 bResultCode;
|
||||
__le16 size;
|
||||
struct {
|
||||
u8 page, offset, value;
|
||||
} __attribute__((packed)) data[];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
#endif /* #ifndef __i1480_DFU_H__ */
|
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
* Intel Wireless UWB Link 1480
|
||||
* MAC Firmware upload implementation
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Implementation of the code for parsing the firmware file (extract
|
||||
* the headers and binary code chunks) in the fw_*() functions. The
|
||||
* code to upload pre and mac firmwares is the same, so it uses a
|
||||
* common entry point in __mac_fw_upload(), which uses the i1480
|
||||
* function pointers to push the firmware to the device.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/uwb.h>
|
||||
#include "i1480-dfu.h"
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/*
|
||||
* Descriptor for a continuous segment of MAC fw data
|
||||
*/
|
||||
struct fw_hdr {
|
||||
unsigned long address;
|
||||
size_t length;
|
||||
const u32 *bin;
|
||||
struct fw_hdr *next;
|
||||
};
|
||||
|
||||
|
||||
/* Free a chain of firmware headers */
|
||||
static
|
||||
void fw_hdrs_free(struct fw_hdr *hdr)
|
||||
{
|
||||
struct fw_hdr *next;
|
||||
|
||||
while (hdr) {
|
||||
next = hdr->next;
|
||||
kfree(hdr);
|
||||
hdr = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Fill a firmware header descriptor from a memory buffer */
|
||||
static
|
||||
int fw_hdr_load(struct i1480 *i1480, struct fw_hdr *hdr, unsigned hdr_cnt,
|
||||
const char *_data, const u32 *data_itr, const u32 *data_top)
|
||||
{
|
||||
size_t hdr_offset = (const char *) data_itr - _data;
|
||||
size_t remaining_size = (void *) data_top - (void *) data_itr;
|
||||
if (data_itr + 2 > data_top) {
|
||||
dev_err(i1480->dev, "fw hdr #%u/%zu: EOF reached in header at "
|
||||
"offset %zu, limit %zu\n",
|
||||
hdr_cnt, hdr_offset,
|
||||
(const char *) data_itr + 2 - _data,
|
||||
(const char *) data_top - _data);
|
||||
return -EINVAL;
|
||||
}
|
||||
hdr->next = NULL;
|
||||
hdr->address = le32_to_cpu(*data_itr++);
|
||||
hdr->length = le32_to_cpu(*data_itr++);
|
||||
hdr->bin = data_itr;
|
||||
if (hdr->length > remaining_size) {
|
||||
dev_err(i1480->dev, "fw hdr #%u/%zu: EOF reached in data; "
|
||||
"chunk too long (%zu bytes), only %zu left\n",
|
||||
hdr_cnt, hdr_offset, hdr->length, remaining_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a buffer where the firmware is supposed to be and create a
|
||||
* chain of headers linking them together.
|
||||
*
|
||||
* @phdr: where to place the pointer to the first header (headers link
|
||||
* to the next via the @hdr->next ptr); need to free the whole
|
||||
* chain when done.
|
||||
*
|
||||
* @_data: Pointer to the data buffer.
|
||||
*
|
||||
* @_data_size: Size of the data buffer (bytes); data size has to be a
|
||||
* multiple of 4. Function will fail if not.
|
||||
*
|
||||
* Goes over the whole binary blob; reads the first chunk and creates
|
||||
* a fw hdr from it (which points to where the data is in @_data and
|
||||
* the length of the chunk); then goes on to the next chunk until
|
||||
* done. Each header is linked to the next.
|
||||
*/
|
||||
static
|
||||
int fw_hdrs_load(struct i1480 *i1480, struct fw_hdr **phdr,
|
||||
const char *_data, size_t data_size)
|
||||
{
|
||||
int result;
|
||||
unsigned hdr_cnt = 0;
|
||||
u32 *data = (u32 *) _data, *data_itr, *data_top;
|
||||
struct fw_hdr *hdr, **prev_hdr = phdr;
|
||||
|
||||
result = -EINVAL;
|
||||
/* Check size is ok and pointer is aligned */
|
||||
if (data_size % sizeof(u32) != 0)
|
||||
goto error;
|
||||
if ((unsigned long) _data % sizeof(u16) != 0)
|
||||
goto error;
|
||||
*phdr = NULL;
|
||||
data_itr = data;
|
||||
data_top = (u32 *) (_data + data_size);
|
||||
while (data_itr < data_top) {
|
||||
result = -ENOMEM;
|
||||
hdr = kmalloc(sizeof(*hdr), GFP_KERNEL);
|
||||
if (hdr == NULL) {
|
||||
dev_err(i1480->dev, "Cannot allocate fw header "
|
||||
"for chunk #%u\n", hdr_cnt);
|
||||
goto error_alloc;
|
||||
}
|
||||
result = fw_hdr_load(i1480, hdr, hdr_cnt,
|
||||
_data, data_itr, data_top);
|
||||
if (result < 0)
|
||||
goto error_load;
|
||||
data_itr += 2 + hdr->length;
|
||||
*prev_hdr = hdr;
|
||||
prev_hdr = &hdr->next;
|
||||
hdr_cnt++;
|
||||
};
|
||||
*prev_hdr = NULL;
|
||||
return 0;
|
||||
|
||||
error_load:
|
||||
kfree(hdr);
|
||||
error_alloc:
|
||||
fw_hdrs_free(*phdr);
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares a chunk of fw with one in the devices's memory
|
||||
*
|
||||
* @i1480: Device instance
|
||||
* @hdr: Pointer to the firmware chunk
|
||||
* @returns: 0 if equal, < 0 errno on error. If > 0, it is the offset
|
||||
* where the difference was found (plus one).
|
||||
*
|
||||
* Kind of dirty and simplistic, but does the trick in both the PCI
|
||||
* and USB version. We do a quick[er] memcmp(), and if it fails, we do
|
||||
* a byte-by-byte to find the offset.
|
||||
*/
|
||||
static
|
||||
ssize_t i1480_fw_cmp(struct i1480 *i1480, struct fw_hdr *hdr)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
u32 src_itr = 0, cnt;
|
||||
size_t size = hdr->length*sizeof(hdr->bin[0]);
|
||||
size_t chunk_size;
|
||||
u8 *bin = (u8 *) hdr->bin;
|
||||
|
||||
while (size > 0) {
|
||||
chunk_size = size < i1480->buf_size ? size : i1480->buf_size;
|
||||
result = i1480->read(i1480, hdr->address + src_itr, chunk_size);
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "error reading for verification: "
|
||||
"%zd\n", result);
|
||||
goto error;
|
||||
}
|
||||
if (memcmp(i1480->cmd_buf, bin + src_itr, result)) {
|
||||
u8 *buf = i1480->cmd_buf;
|
||||
d_printf(2, i1480->dev,
|
||||
"original data @ %p + %u, %zu bytes\n",
|
||||
bin, src_itr, result);
|
||||
d_dump(4, i1480->dev, bin + src_itr, result);
|
||||
for (cnt = 0; cnt < result; cnt++)
|
||||
if (bin[src_itr + cnt] != buf[cnt]) {
|
||||
dev_err(i1480->dev, "byte failed at "
|
||||
"src_itr %u cnt %u [0x%02x "
|
||||
"vs 0x%02x]\n", src_itr, cnt,
|
||||
bin[src_itr + cnt], buf[cnt]);
|
||||
result = src_itr + cnt + 1;
|
||||
goto cmp_failed;
|
||||
}
|
||||
}
|
||||
src_itr += result;
|
||||
size -= result;
|
||||
}
|
||||
result = 0;
|
||||
error:
|
||||
cmp_failed:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes firmware headers to the device.
|
||||
*
|
||||
* @prd: PRD instance
|
||||
* @hdr: Processed firmware
|
||||
* @returns: 0 if ok, < 0 errno on error.
|
||||
*/
|
||||
static
|
||||
int mac_fw_hdrs_push(struct i1480 *i1480, struct fw_hdr *hdr,
|
||||
const char *fw_name, const char *fw_tag)
|
||||
{
|
||||
struct device *dev = i1480->dev;
|
||||
ssize_t result = 0;
|
||||
struct fw_hdr *hdr_itr;
|
||||
int verif_retry_count;
|
||||
|
||||
d_fnstart(3, dev, "(%p, %p)\n", i1480, hdr);
|
||||
/* Now, header by header, push them to the hw */
|
||||
for (hdr_itr = hdr; hdr_itr != NULL; hdr_itr = hdr_itr->next) {
|
||||
verif_retry_count = 0;
|
||||
retry:
|
||||
dev_dbg(dev, "fw chunk (%zu @ 0x%08lx)\n",
|
||||
hdr_itr->length * sizeof(hdr_itr->bin[0]),
|
||||
hdr_itr->address);
|
||||
result = i1480->write(i1480, hdr_itr->address, hdr_itr->bin,
|
||||
hdr_itr->length*sizeof(hdr_itr->bin[0]));
|
||||
if (result < 0) {
|
||||
dev_err(dev, "%s fw '%s': write failed (%zuB @ 0x%lx):"
|
||||
" %zd\n", fw_tag, fw_name,
|
||||
hdr_itr->length * sizeof(hdr_itr->bin[0]),
|
||||
hdr_itr->address, result);
|
||||
break;
|
||||
}
|
||||
result = i1480_fw_cmp(i1480, hdr_itr);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "%s fw '%s': verification read "
|
||||
"failed (%zuB @ 0x%lx): %zd\n",
|
||||
fw_tag, fw_name,
|
||||
hdr_itr->length * sizeof(hdr_itr->bin[0]),
|
||||
hdr_itr->address, result);
|
||||
break;
|
||||
}
|
||||
if (result > 0) { /* Offset where it failed + 1 */
|
||||
result--;
|
||||
dev_err(dev, "%s fw '%s': WARNING: verification "
|
||||
"failed at 0x%lx: retrying\n",
|
||||
fw_tag, fw_name, hdr_itr->address + result);
|
||||
if (++verif_retry_count < 3)
|
||||
goto retry; /* write this block again! */
|
||||
dev_err(dev, "%s fw '%s': verification failed at 0x%lx: "
|
||||
"tried %d times\n", fw_tag, fw_name,
|
||||
hdr_itr->address + result, verif_retry_count);
|
||||
result = -EINVAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
d_fnend(3, dev, "(%zd)\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/** Puts the device in firmware upload mode.*/
|
||||
static
|
||||
int mac_fw_upload_enable(struct i1480 *i1480)
|
||||
{
|
||||
int result;
|
||||
u32 reg = 0x800000c0;
|
||||
u32 *buffer = (u32 *)i1480->cmd_buf;
|
||||
|
||||
if (i1480->hw_rev > 1)
|
||||
reg = 0x8000d0d4;
|
||||
result = i1480->read(i1480, reg, sizeof(u32));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
*buffer &= ~i1480_FW_UPLOAD_MODE_MASK;
|
||||
result = i1480->write(i1480, reg, buffer, sizeof(u32));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
return 0;
|
||||
error_cmd:
|
||||
dev_err(i1480->dev, "can't enable fw upload mode: %d\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/** Gets the device out of firmware upload mode. */
|
||||
static
|
||||
int mac_fw_upload_disable(struct i1480 *i1480)
|
||||
{
|
||||
int result;
|
||||
u32 reg = 0x800000c0;
|
||||
u32 *buffer = (u32 *)i1480->cmd_buf;
|
||||
|
||||
if (i1480->hw_rev > 1)
|
||||
reg = 0x8000d0d4;
|
||||
result = i1480->read(i1480, reg, sizeof(u32));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
*buffer |= i1480_FW_UPLOAD_MODE_MASK;
|
||||
result = i1480->write(i1480, reg, buffer, sizeof(u32));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
return 0;
|
||||
error_cmd:
|
||||
dev_err(i1480->dev, "can't disable fw upload mode: %d\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generic function for uploading a MAC firmware.
|
||||
*
|
||||
* @i1480: Device instance
|
||||
* @fw_name: Name of firmware file to upload.
|
||||
* @fw_tag: Name of the firmware type (for messages)
|
||||
* [eg: MAC, PRE]
|
||||
* @do_wait: Wait for device to emit initialization done message (0
|
||||
* for PRE fws, 1 for MAC fws).
|
||||
* @returns: 0 if ok, < 0 errno on error.
|
||||
*/
|
||||
static
|
||||
int __mac_fw_upload(struct i1480 *i1480, const char *fw_name,
|
||||
const char *fw_tag)
|
||||
{
|
||||
int result;
|
||||
const struct firmware *fw;
|
||||
struct fw_hdr *fw_hdrs;
|
||||
|
||||
d_fnstart(3, i1480->dev, "(%p, %s, %s)\n", i1480, fw_name, fw_tag);
|
||||
result = request_firmware(&fw, fw_name, i1480->dev);
|
||||
if (result < 0) /* Up to caller to complain on -ENOENT */
|
||||
goto out;
|
||||
d_printf(3, i1480->dev, "%s fw '%s': uploading\n", fw_tag, fw_name);
|
||||
result = fw_hdrs_load(i1480, &fw_hdrs, fw->data, fw->size);
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "%s fw '%s': failed to parse firmware "
|
||||
"file: %d\n", fw_tag, fw_name, result);
|
||||
goto out_release;
|
||||
}
|
||||
result = mac_fw_upload_enable(i1480);
|
||||
if (result < 0)
|
||||
goto out_hdrs_release;
|
||||
result = mac_fw_hdrs_push(i1480, fw_hdrs, fw_name, fw_tag);
|
||||
mac_fw_upload_disable(i1480);
|
||||
out_hdrs_release:
|
||||
if (result >= 0)
|
||||
dev_info(i1480->dev, "%s fw '%s': uploaded\n", fw_tag, fw_name);
|
||||
else
|
||||
dev_err(i1480->dev, "%s fw '%s': failed to upload (%d), "
|
||||
"power cycle device\n", fw_tag, fw_name, result);
|
||||
fw_hdrs_free(fw_hdrs);
|
||||
out_release:
|
||||
release_firmware(fw);
|
||||
out:
|
||||
d_fnend(3, i1480->dev, "(%p, %s, %s) = %d\n", i1480, fw_name, fw_tag,
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upload a pre-PHY firmware
|
||||
*
|
||||
*/
|
||||
int i1480_pre_fw_upload(struct i1480 *i1480)
|
||||
{
|
||||
int result;
|
||||
result = __mac_fw_upload(i1480, i1480->pre_fw_name, "PRE");
|
||||
if (result == 0)
|
||||
msleep(400);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset a the MAC and PHY
|
||||
*
|
||||
* @i1480: Device's instance
|
||||
* @returns: 0 if ok, < 0 errno code on error
|
||||
*
|
||||
* We put the command on kmalloc'ed memory as some arches cannot do
|
||||
* USB from the stack. The reply event is copied from an stage buffer,
|
||||
* so it can be in the stack. See WUSB1.0[8.6.2.4] for more details.
|
||||
*
|
||||
* We issue the reset to make sure the UWB controller reinits the PHY;
|
||||
* this way we can now if the PHY init went ok.
|
||||
*/
|
||||
static
|
||||
int i1480_cmd_reset(struct i1480 *i1480)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rccb *cmd = (void *) i1480->cmd_buf;
|
||||
struct i1480_evt_reset {
|
||||
struct uwb_rceb rceb;
|
||||
u8 bResultCode;
|
||||
} __attribute__((packed)) *reply = (void *) i1480->evt_buf;
|
||||
|
||||
result = -ENOMEM;
|
||||
cmd->bCommandType = UWB_RC_CET_GENERAL;
|
||||
cmd->wCommand = cpu_to_le16(UWB_RC_CMD_RESET);
|
||||
reply->rceb.bEventType = UWB_RC_CET_GENERAL;
|
||||
reply->rceb.wEvent = UWB_RC_CMD_RESET;
|
||||
result = i1480_cmd(i1480, "RESET", sizeof(*cmd), sizeof(*reply));
|
||||
if (result < 0)
|
||||
goto out;
|
||||
if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(i1480->dev, "RESET: command execution failed: %u\n",
|
||||
reply->bResultCode);
|
||||
result = -EIO;
|
||||
}
|
||||
out:
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Wait for the MAC FW to start running */
|
||||
static
|
||||
int i1480_fw_is_running_q(struct i1480 *i1480)
|
||||
{
|
||||
int cnt = 0;
|
||||
int result;
|
||||
u32 *val = (u32 *) i1480->cmd_buf;
|
||||
|
||||
d_fnstart(3, i1480->dev, "(i1480 %p)\n", i1480);
|
||||
for (cnt = 0; cnt < 10; cnt++) {
|
||||
msleep(100);
|
||||
result = i1480->read(i1480, 0x80080000, 4);
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "Can't read 0x8008000: %d\n", result);
|
||||
goto out;
|
||||
}
|
||||
if (*val == 0x55555555UL) /* fw running? cool */
|
||||
goto out;
|
||||
}
|
||||
dev_err(i1480->dev, "Timed out waiting for fw to start\n");
|
||||
result = -ETIMEDOUT;
|
||||
out:
|
||||
d_fnend(3, i1480->dev, "(i1480 %p) = %d\n", i1480, result);
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upload MAC firmware, wait for it to start
|
||||
*
|
||||
* @i1480: Device instance
|
||||
* @fw_name: Name of the file that contains the firmware
|
||||
*
|
||||
* This has to be called after the pre fw has been uploaded (if
|
||||
* there is any).
|
||||
*/
|
||||
int i1480_mac_fw_upload(struct i1480 *i1480)
|
||||
{
|
||||
int result = 0, deprecated_name = 0;
|
||||
struct i1480_rceb *rcebe = (void *) i1480->evt_buf;
|
||||
|
||||
d_fnstart(3, i1480->dev, "(%p)\n", i1480);
|
||||
result = __mac_fw_upload(i1480, i1480->mac_fw_name, "MAC");
|
||||
if (result == -ENOENT) {
|
||||
result = __mac_fw_upload(i1480, i1480->mac_fw_name_deprecate,
|
||||
"MAC");
|
||||
deprecated_name = 1;
|
||||
}
|
||||
if (result < 0)
|
||||
return result;
|
||||
if (deprecated_name == 1)
|
||||
dev_warn(i1480->dev,
|
||||
"WARNING: firmware file name %s is deprecated, "
|
||||
"please rename to %s\n",
|
||||
i1480->mac_fw_name_deprecate, i1480->mac_fw_name);
|
||||
result = i1480_fw_is_running_q(i1480);
|
||||
if (result < 0)
|
||||
goto error_fw_not_running;
|
||||
result = i1480->rc_setup ? i1480->rc_setup(i1480) : 0;
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "Cannot setup after MAC fw upload: %d\n",
|
||||
result);
|
||||
goto error_setup;
|
||||
}
|
||||
result = i1480->wait_init_done(i1480); /* wait init'on */
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "MAC fw '%s': Initialization timed out "
|
||||
"(%d)\n", i1480->mac_fw_name, result);
|
||||
goto error_init_timeout;
|
||||
}
|
||||
/* verify we got the right initialization done event */
|
||||
if (i1480->evt_result != sizeof(*rcebe)) {
|
||||
dev_err(i1480->dev, "MAC fw '%s': initialization event returns "
|
||||
"wrong size (%zu bytes vs %zu needed)\n",
|
||||
i1480->mac_fw_name, i1480->evt_result, sizeof(*rcebe));
|
||||
dump_bytes(i1480->dev, rcebe, min(i1480->evt_result, (ssize_t)32));
|
||||
goto error_size;
|
||||
}
|
||||
result = -EIO;
|
||||
if (i1480_rceb_check(i1480, &rcebe->rceb, NULL, 0, i1480_CET_VS1,
|
||||
i1480_EVT_RM_INIT_DONE) < 0) {
|
||||
dev_err(i1480->dev, "wrong initialization event 0x%02x/%04x/%02x "
|
||||
"received; expected 0x%02x/%04x/00\n",
|
||||
rcebe->rceb.bEventType, le16_to_cpu(rcebe->rceb.wEvent),
|
||||
rcebe->rceb.bEventContext, i1480_CET_VS1,
|
||||
i1480_EVT_RM_INIT_DONE);
|
||||
goto error_init_timeout;
|
||||
}
|
||||
result = i1480_cmd_reset(i1480);
|
||||
if (result < 0)
|
||||
dev_err(i1480->dev, "MAC fw '%s': MBOA reset failed (%d)\n",
|
||||
i1480->mac_fw_name, result);
|
||||
error_fw_not_running:
|
||||
error_init_timeout:
|
||||
error_size:
|
||||
error_setup:
|
||||
d_fnend(3, i1480->dev, "(i1480 %p) = %d\n", i1480, result);
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Intel Wireless UWB Link 1480
|
||||
* PHY parameters upload
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Code for uploading the PHY parameters to the PHY through the UWB
|
||||
* Radio Control interface.
|
||||
*
|
||||
* We just send the data through the MPI interface using HWA-like
|
||||
* commands and then reset the PHY to make sure it is ok.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/usb/wusb.h>
|
||||
#include "i1480-dfu.h"
|
||||
|
||||
|
||||
/**
|
||||
* Write a value array to an address of the MPI interface
|
||||
*
|
||||
* @i1480: Device descriptor
|
||||
* @data: Data array to write
|
||||
* @size: Size of the data array
|
||||
* @returns: 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* The data array is organized into pairs:
|
||||
*
|
||||
* ADDRESS VALUE
|
||||
*
|
||||
* ADDRESS is BE 16 bit unsigned, VALUE 8 bit unsigned. Size thus has
|
||||
* to be a multiple of three.
|
||||
*/
|
||||
static
|
||||
int i1480_mpi_write(struct i1480 *i1480, const void *data, size_t size)
|
||||
{
|
||||
int result;
|
||||
struct i1480_cmd_mpi_write *cmd = i1480->cmd_buf;
|
||||
struct i1480_evt_confirm *reply = i1480->evt_buf;
|
||||
|
||||
BUG_ON(size > 480);
|
||||
result = -ENOMEM;
|
||||
cmd->rccb.bCommandType = i1480_CET_VS1;
|
||||
cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_WRITE);
|
||||
cmd->size = cpu_to_le16(size);
|
||||
memcpy(cmd->data, data, size);
|
||||
reply->rceb.bEventType = i1480_CET_VS1;
|
||||
reply->rceb.wEvent = i1480_CMD_MPI_WRITE;
|
||||
result = i1480_cmd(i1480, "MPI-WRITE", sizeof(*cmd) + size, sizeof(*reply));
|
||||
if (result < 0)
|
||||
goto out;
|
||||
if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(i1480->dev, "MPI-WRITE: command execution failed: %d\n",
|
||||
reply->bResultCode);
|
||||
result = -EIO;
|
||||
}
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a value array to from an address of the MPI interface
|
||||
*
|
||||
* @i1480: Device descriptor
|
||||
* @data: where to place the read array
|
||||
* @srcaddr: Where to read from
|
||||
* @size: Size of the data read array
|
||||
* @returns: 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* The command data array is organized into pairs ADDR0 ADDR1..., and
|
||||
* the returned data in ADDR0 VALUE0 ADDR1 VALUE1...
|
||||
*
|
||||
* We generate the command array to be a sequential read and then
|
||||
* rearrange the result.
|
||||
*
|
||||
* We use the i1480->cmd_buf for the command, i1480->evt_buf for the reply.
|
||||
*
|
||||
* As the reply has to fit in 512 bytes (i1480->evt_buffer), the max amount
|
||||
* of values we can read is (512 - sizeof(*reply)) / 3
|
||||
*/
|
||||
static
|
||||
int i1480_mpi_read(struct i1480 *i1480, u8 *data, u16 srcaddr, size_t size)
|
||||
{
|
||||
int result;
|
||||
struct i1480_cmd_mpi_read *cmd = i1480->cmd_buf;
|
||||
struct i1480_evt_mpi_read *reply = i1480->evt_buf;
|
||||
unsigned cnt;
|
||||
|
||||
memset(i1480->cmd_buf, 0x69, 512);
|
||||
memset(i1480->evt_buf, 0x69, 512);
|
||||
|
||||
BUG_ON(size > (i1480->buf_size - sizeof(*reply)) / 3);
|
||||
result = -ENOMEM;
|
||||
cmd->rccb.bCommandType = i1480_CET_VS1;
|
||||
cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_READ);
|
||||
cmd->size = cpu_to_le16(3*size);
|
||||
for (cnt = 0; cnt < size; cnt++) {
|
||||
cmd->data[cnt].page = (srcaddr + cnt) >> 8;
|
||||
cmd->data[cnt].offset = (srcaddr + cnt) & 0xff;
|
||||
}
|
||||
reply->rceb.bEventType = i1480_CET_VS1;
|
||||
reply->rceb.wEvent = i1480_CMD_MPI_READ;
|
||||
result = i1480_cmd(i1480, "MPI-READ", sizeof(*cmd) + 2*size,
|
||||
sizeof(*reply) + 3*size);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(i1480->dev, "MPI-READ: command execution failed: %d\n",
|
||||
reply->bResultCode);
|
||||
result = -EIO;
|
||||
}
|
||||
for (cnt = 0; cnt < size; cnt++) {
|
||||
if (reply->data[cnt].page != (srcaddr + cnt) >> 8)
|
||||
dev_err(i1480->dev, "MPI-READ: page inconsistency at "
|
||||
"index %u: expected 0x%02x, got 0x%02x\n", cnt,
|
||||
(srcaddr + cnt) >> 8, reply->data[cnt].page);
|
||||
if (reply->data[cnt].offset != ((srcaddr + cnt) & 0x00ff))
|
||||
dev_err(i1480->dev, "MPI-READ: offset inconsistency at "
|
||||
"index %u: expected 0x%02x, got 0x%02x\n", cnt,
|
||||
(srcaddr + cnt) & 0x00ff,
|
||||
reply->data[cnt].offset);
|
||||
data[cnt] = reply->data[cnt].value;
|
||||
}
|
||||
result = 0;
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upload a PHY firmware, wait for it to start
|
||||
*
|
||||
* @i1480: Device instance
|
||||
* @fw_name: Name of the file that contains the firmware
|
||||
*
|
||||
* We assume the MAC fw is up and running. This means we can use the
|
||||
* MPI interface to write the PHY firmware. Once done, we issue an
|
||||
* MBOA Reset, which will force the MAC to reset and reinitialize the
|
||||
* PHY. If that works, we are ready to go.
|
||||
*
|
||||
* Max packet size for the MPI write is 512, so the max buffer is 480
|
||||
* (which gives us 160 byte triads of MSB, LSB and VAL for the data).
|
||||
*/
|
||||
int i1480_phy_fw_upload(struct i1480 *i1480)
|
||||
{
|
||||
int result;
|
||||
const struct firmware *fw;
|
||||
const char *data_itr, *data_top;
|
||||
const size_t MAX_BLK_SIZE = 480; /* 160 triads */
|
||||
size_t data_size;
|
||||
u8 phy_stat;
|
||||
|
||||
result = request_firmware(&fw, i1480->phy_fw_name, i1480->dev);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
/* Loop writing data in chunks as big as possible until done. */
|
||||
for (data_itr = fw->data, data_top = data_itr + fw->size;
|
||||
data_itr < data_top; data_itr += MAX_BLK_SIZE) {
|
||||
data_size = min(MAX_BLK_SIZE, (size_t) (data_top - data_itr));
|
||||
result = i1480_mpi_write(i1480, data_itr, data_size);
|
||||
if (result < 0)
|
||||
goto error_mpi_write;
|
||||
}
|
||||
/* Read MPI page 0, offset 6; if 0, PHY was initialized correctly. */
|
||||
result = i1480_mpi_read(i1480, &phy_stat, 0x0006, 1);
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "PHY: can't get status: %d\n", result);
|
||||
goto error_mpi_status;
|
||||
}
|
||||
if (phy_stat != 0) {
|
||||
result = -ENODEV;
|
||||
dev_info(i1480->dev, "error, PHY not ready: %u\n", phy_stat);
|
||||
goto error_phy_status;
|
||||
}
|
||||
dev_info(i1480->dev, "PHY fw '%s': uploaded\n", i1480->phy_fw_name);
|
||||
error_phy_status:
|
||||
error_mpi_status:
|
||||
error_mpi_write:
|
||||
release_firmware(fw);
|
||||
if (result < 0)
|
||||
dev_err(i1480->dev, "PHY fw '%s': failed to upload (%d), "
|
||||
"power cycle device\n", i1480->phy_fw_name, result);
|
||||
out:
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,500 @@
|
|||
/*
|
||||
* Intel Wireless UWB Link 1480
|
||||
* USB SKU firmware upload implementation
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This driver will prepare the i1480 device to behave as a real
|
||||
* Wireless USB HWA adaptor by uploading the firmware.
|
||||
*
|
||||
* When the device is connected or driver is loaded, i1480_usb_probe()
|
||||
* is called--this will allocate and initialize the device structure,
|
||||
* fill in the pointers to the common functions (read, write,
|
||||
* wait_init_done and cmd for HWA command execution) and once that is
|
||||
* done, call the common firmware uploading routine. Then clean up and
|
||||
* return -ENODEV, as we don't attach to the device.
|
||||
*
|
||||
* The rest are the basic ops we implement that the fw upload code
|
||||
* uses to do its job. All the ops in the common code are i1480->NAME,
|
||||
* the functions are i1480_usb_NAME().
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/usb/wusb.h>
|
||||
#include <linux/usb/wusb-wa.h>
|
||||
#include "i1480-dfu.h"
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
|
||||
struct i1480_usb {
|
||||
struct i1480 i1480;
|
||||
struct usb_device *usb_dev;
|
||||
struct usb_interface *usb_iface;
|
||||
struct urb *neep_urb; /* URB for reading from EP1 */
|
||||
};
|
||||
|
||||
|
||||
static
|
||||
void i1480_usb_init(struct i1480_usb *i1480_usb)
|
||||
{
|
||||
i1480_init(&i1480_usb->i1480);
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
int i1480_usb_create(struct i1480_usb *i1480_usb, struct usb_interface *iface)
|
||||
{
|
||||
struct usb_device *usb_dev = interface_to_usbdev(iface);
|
||||
int result = -ENOMEM;
|
||||
|
||||
i1480_usb->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */
|
||||
i1480_usb->usb_iface = usb_get_intf(iface);
|
||||
usb_set_intfdata(iface, i1480_usb); /* Bind the driver to iface0 */
|
||||
i1480_usb->neep_urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (i1480_usb->neep_urb == NULL)
|
||||
goto error;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
usb_set_intfdata(iface, NULL);
|
||||
usb_put_intf(iface);
|
||||
usb_put_dev(usb_dev);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void i1480_usb_destroy(struct i1480_usb *i1480_usb)
|
||||
{
|
||||
usb_kill_urb(i1480_usb->neep_urb);
|
||||
usb_free_urb(i1480_usb->neep_urb);
|
||||
usb_set_intfdata(i1480_usb->usb_iface, NULL);
|
||||
usb_put_intf(i1480_usb->usb_iface);
|
||||
usb_put_dev(i1480_usb->usb_dev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write a buffer to a memory address in the i1480 device
|
||||
*
|
||||
* @i1480: i1480 instance
|
||||
* @memory_address:
|
||||
* Address where to write the data buffer to.
|
||||
* @buffer: Buffer to the data
|
||||
* @size: Size of the buffer [has to be < 512].
|
||||
* @returns: 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* Data buffers to USB cannot be on the stack or in vmalloc'ed areas,
|
||||
* so we copy it to the local i1480 buffer before proceeding. In any
|
||||
* case, we have a max size we can send, soooo.
|
||||
*/
|
||||
static
|
||||
int i1480_usb_write(struct i1480 *i1480, u32 memory_address,
|
||||
const void *buffer, size_t size)
|
||||
{
|
||||
int result = 0;
|
||||
struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480);
|
||||
size_t buffer_size, itr = 0;
|
||||
|
||||
d_fnstart(3, i1480->dev, "(%p, 0x%08x, %p, %zu)\n",
|
||||
i1480, memory_address, buffer, size);
|
||||
BUG_ON(size & 0x3); /* Needs to be a multiple of 4 */
|
||||
while (size > 0) {
|
||||
buffer_size = size < i1480->buf_size ? size : i1480->buf_size;
|
||||
memcpy(i1480->cmd_buf, buffer + itr, buffer_size);
|
||||
result = usb_control_msg(
|
||||
i1480_usb->usb_dev, usb_sndctrlpipe(i1480_usb->usb_dev, 0),
|
||||
0xf0, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||||
cpu_to_le16(memory_address & 0xffff),
|
||||
cpu_to_le16((memory_address >> 16) & 0xffff),
|
||||
i1480->cmd_buf, buffer_size, 100 /* FIXME: arbitrary */);
|
||||
if (result < 0)
|
||||
break;
|
||||
d_printf(3, i1480->dev,
|
||||
"wrote @ 0x%08x %u bytes (of %zu bytes requested)\n",
|
||||
memory_address, result, buffer_size);
|
||||
d_dump(4, i1480->dev, i1480->cmd_buf, result);
|
||||
itr += result;
|
||||
memory_address += result;
|
||||
size -= result;
|
||||
}
|
||||
d_fnend(3, i1480->dev, "(%p, 0x%08x, %p, %zu) = %d\n",
|
||||
i1480, memory_address, buffer, size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a block [max size 512] of the device's memory to @i1480's buffer.
|
||||
*
|
||||
* @i1480: i1480 instance
|
||||
* @memory_address:
|
||||
* Address where to read from.
|
||||
* @size: Size to read. Smaller than or equal to 512.
|
||||
* @returns: >= 0 number of bytes written if ok, < 0 errno code on error.
|
||||
*
|
||||
* NOTE: if the memory address or block is incorrect, you might get a
|
||||
* stall or a different memory read. Caller has to verify the
|
||||
* memory address and size passed back in the @neh structure.
|
||||
*/
|
||||
static
|
||||
int i1480_usb_read(struct i1480 *i1480, u32 addr, size_t size)
|
||||
{
|
||||
ssize_t result = 0, bytes = 0;
|
||||
size_t itr, read_size = i1480->buf_size;
|
||||
struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480);
|
||||
|
||||
d_fnstart(3, i1480->dev, "(%p, 0x%08x, %zu)\n",
|
||||
i1480, addr, size);
|
||||
BUG_ON(size > i1480->buf_size);
|
||||
BUG_ON(size & 0x3); /* Needs to be a multiple of 4 */
|
||||
BUG_ON(read_size > 512);
|
||||
|
||||
if (addr >= 0x8000d200 && addr < 0x8000d400) /* Yeah, HW quirk */
|
||||
read_size = 4;
|
||||
|
||||
for (itr = 0; itr < size; itr += read_size) {
|
||||
size_t itr_addr = addr + itr;
|
||||
size_t itr_size = min(read_size, size - itr);
|
||||
result = usb_control_msg(
|
||||
i1480_usb->usb_dev, usb_rcvctrlpipe(i1480_usb->usb_dev, 0),
|
||||
0xf0, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||||
cpu_to_le16(itr_addr & 0xffff),
|
||||
cpu_to_le16((itr_addr >> 16) & 0xffff),
|
||||
i1480->cmd_buf + itr, itr_size,
|
||||
100 /* FIXME: arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(i1480->dev, "%s: USB read error: %zd\n",
|
||||
__func__, result);
|
||||
goto out;
|
||||
}
|
||||
if (result != itr_size) {
|
||||
result = -EIO;
|
||||
dev_err(i1480->dev,
|
||||
"%s: partial read got only %zu bytes vs %zu expected\n",
|
||||
__func__, result, itr_size);
|
||||
goto out;
|
||||
}
|
||||
bytes += result;
|
||||
}
|
||||
result = bytes;
|
||||
out:
|
||||
d_fnend(3, i1480->dev, "(%p, 0x%08x, %zu) = %zd\n",
|
||||
i1480, addr, size, result);
|
||||
if (result > 0)
|
||||
d_dump(4, i1480->dev, i1480->cmd_buf, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for reads on the notification/event endpoint
|
||||
*
|
||||
* Just enables the completion read handler.
|
||||
*/
|
||||
static
|
||||
void i1480_usb_neep_cb(struct urb *urb)
|
||||
{
|
||||
struct i1480 *i1480 = urb->context;
|
||||
struct device *dev = i1480->dev;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
break;
|
||||
case -ECONNRESET: /* Not an error, but a controlled situation; */
|
||||
case -ENOENT: /* (we killed the URB)...so, no broadcast */
|
||||
dev_dbg(dev, "NEEP: reset/noent %d\n", urb->status);
|
||||
break;
|
||||
case -ESHUTDOWN: /* going away! */
|
||||
dev_dbg(dev, "NEEP: down %d\n", urb->status);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "NEEP: unknown status %d\n", urb->status);
|
||||
break;
|
||||
}
|
||||
i1480->evt_result = urb->actual_length;
|
||||
complete(&i1480->evt_complete);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wait for the MAC FW to initialize
|
||||
*
|
||||
* MAC FW sends a 0xfd/0101/00 notification to EP1 when done
|
||||
* initializing. Get that notification into i1480->evt_buf; upper layer
|
||||
* will verify it.
|
||||
*
|
||||
* Set i1480->evt_result with the result of getting the event or its
|
||||
* size (if succesful).
|
||||
*
|
||||
* Delivers the data directly to i1480->evt_buf
|
||||
*/
|
||||
static
|
||||
int i1480_usb_wait_init_done(struct i1480 *i1480)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = i1480->dev;
|
||||
struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480);
|
||||
struct usb_endpoint_descriptor *epd;
|
||||
|
||||
d_fnstart(3, dev, "(%p)\n", i1480);
|
||||
init_completion(&i1480->evt_complete);
|
||||
i1480->evt_result = -EINPROGRESS;
|
||||
epd = &i1480_usb->usb_iface->cur_altsetting->endpoint[0].desc;
|
||||
usb_fill_int_urb(i1480_usb->neep_urb, i1480_usb->usb_dev,
|
||||
usb_rcvintpipe(i1480_usb->usb_dev, epd->bEndpointAddress),
|
||||
i1480->evt_buf, i1480->buf_size,
|
||||
i1480_usb_neep_cb, i1480, epd->bInterval);
|
||||
result = usb_submit_urb(i1480_usb->neep_urb, GFP_KERNEL);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "init done: cannot submit NEEP read: %d\n",
|
||||
result);
|
||||
goto error_submit;
|
||||
}
|
||||
/* Wait for the USB callback to get the data */
|
||||
result = wait_for_completion_interruptible_timeout(
|
||||
&i1480->evt_complete, HZ);
|
||||
if (result <= 0) {
|
||||
result = result == 0 ? -ETIMEDOUT : result;
|
||||
goto error_wait;
|
||||
}
|
||||
usb_kill_urb(i1480_usb->neep_urb);
|
||||
d_fnend(3, dev, "(%p) = 0\n", i1480);
|
||||
return 0;
|
||||
|
||||
error_wait:
|
||||
usb_kill_urb(i1480_usb->neep_urb);
|
||||
error_submit:
|
||||
i1480->evt_result = result;
|
||||
d_fnend(3, dev, "(%p) = %d\n", i1480, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic function for issuing commands to the i1480
|
||||
*
|
||||
* @i1480: i1480 instance
|
||||
* @cmd_name: Name of the command (for error messages)
|
||||
* @cmd: Pointer to command buffer
|
||||
* @cmd_size: Size of the command buffer
|
||||
* @reply: Buffer for the reply event
|
||||
* @reply_size: Expected size back (including RCEB); the reply buffer
|
||||
* is assumed to be as big as this.
|
||||
* @returns: >= 0 size of the returned event data if ok,
|
||||
* < 0 errno code on error.
|
||||
*
|
||||
* Arms the NE handle, issues the command to the device and checks the
|
||||
* basics of the reply event.
|
||||
*/
|
||||
static
|
||||
int i1480_usb_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = i1480->dev;
|
||||
struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480);
|
||||
struct usb_endpoint_descriptor *epd;
|
||||
struct uwb_rccb *cmd = i1480->cmd_buf;
|
||||
u8 iface_no;
|
||||
|
||||
d_fnstart(3, dev, "(%p, %s, %zu)\n", i1480, cmd_name, cmd_size);
|
||||
/* Post a read on the notification & event endpoint */
|
||||
iface_no = i1480_usb->usb_iface->cur_altsetting->desc.bInterfaceNumber;
|
||||
epd = &i1480_usb->usb_iface->cur_altsetting->endpoint[0].desc;
|
||||
usb_fill_int_urb(
|
||||
i1480_usb->neep_urb, i1480_usb->usb_dev,
|
||||
usb_rcvintpipe(i1480_usb->usb_dev, epd->bEndpointAddress),
|
||||
i1480->evt_buf, i1480->buf_size,
|
||||
i1480_usb_neep_cb, i1480, epd->bInterval);
|
||||
result = usb_submit_urb(i1480_usb->neep_urb, GFP_KERNEL);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "%s: cannot submit NEEP read: %d\n",
|
||||
cmd_name, result);
|
||||
goto error_submit_ep1;
|
||||
}
|
||||
/* Now post the command on EP0 */
|
||||
result = usb_control_msg(
|
||||
i1480_usb->usb_dev, usb_sndctrlpipe(i1480_usb->usb_dev, 0),
|
||||
WA_EXEC_RC_CMD,
|
||||
USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS,
|
||||
0, iface_no,
|
||||
cmd, cmd_size,
|
||||
100 /* FIXME: this is totally arbitrary */);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "%s: control request failed: %d\n",
|
||||
cmd_name, result);
|
||||
goto error_submit_ep0;
|
||||
}
|
||||
d_fnend(3, dev, "(%p, %s, %zu) = %d\n",
|
||||
i1480, cmd_name, cmd_size, result);
|
||||
return result;
|
||||
|
||||
error_submit_ep0:
|
||||
usb_kill_urb(i1480_usb->neep_urb);
|
||||
error_submit_ep1:
|
||||
d_fnend(3, dev, "(%p, %s, %zu) = %d\n",
|
||||
i1480, cmd_name, cmd_size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Probe a i1480 device for uploading firmware.
|
||||
*
|
||||
* We attach only to interface #0, which is the radio control interface.
|
||||
*/
|
||||
static
|
||||
int i1480_usb_probe(struct usb_interface *iface, const struct usb_device_id *id)
|
||||
{
|
||||
struct i1480_usb *i1480_usb;
|
||||
struct i1480 *i1480;
|
||||
struct device *dev = &iface->dev;
|
||||
int result;
|
||||
|
||||
result = -ENODEV;
|
||||
if (iface->cur_altsetting->desc.bInterfaceNumber != 0) {
|
||||
dev_dbg(dev, "not attaching to iface %d\n",
|
||||
iface->cur_altsetting->desc.bInterfaceNumber);
|
||||
goto error;
|
||||
}
|
||||
if (iface->num_altsetting > 1
|
||||
&& interface_to_usbdev(iface)->descriptor.idProduct == 0xbabe) {
|
||||
/* Need altsetting #1 [HW QUIRK] or EP1 won't work */
|
||||
result = usb_set_interface(interface_to_usbdev(iface), 0, 1);
|
||||
if (result < 0)
|
||||
dev_warn(dev,
|
||||
"can't set altsetting 1 on iface 0: %d\n",
|
||||
result);
|
||||
}
|
||||
|
||||
result = -ENOMEM;
|
||||
i1480_usb = kzalloc(sizeof(*i1480_usb), GFP_KERNEL);
|
||||
if (i1480_usb == NULL) {
|
||||
dev_err(dev, "Unable to allocate instance\n");
|
||||
goto error;
|
||||
}
|
||||
i1480_usb_init(i1480_usb);
|
||||
|
||||
i1480 = &i1480_usb->i1480;
|
||||
i1480->buf_size = 512;
|
||||
i1480->cmd_buf = kmalloc(2 * i1480->buf_size, GFP_KERNEL);
|
||||
if (i1480->cmd_buf == NULL) {
|
||||
dev_err(dev, "Cannot allocate transfer buffers\n");
|
||||
result = -ENOMEM;
|
||||
goto error_buf_alloc;
|
||||
}
|
||||
i1480->evt_buf = i1480->cmd_buf + i1480->buf_size;
|
||||
|
||||
result = i1480_usb_create(i1480_usb, iface);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot create instance: %d\n", result);
|
||||
goto error_create;
|
||||
}
|
||||
|
||||
/* setup the fops and upload the firmare */
|
||||
i1480->pre_fw_name = "i1480-pre-phy-0.0.bin";
|
||||
i1480->mac_fw_name = "i1480-usb-0.0.bin";
|
||||
i1480->mac_fw_name_deprecate = "ptc-0.0.bin";
|
||||
i1480->phy_fw_name = "i1480-phy-0.0.bin";
|
||||
i1480->dev = &iface->dev;
|
||||
i1480->write = i1480_usb_write;
|
||||
i1480->read = i1480_usb_read;
|
||||
i1480->rc_setup = NULL;
|
||||
i1480->wait_init_done = i1480_usb_wait_init_done;
|
||||
i1480->cmd = i1480_usb_cmd;
|
||||
|
||||
result = i1480_fw_upload(&i1480_usb->i1480); /* the real thing */
|
||||
if (result >= 0) {
|
||||
usb_reset_device(i1480_usb->usb_dev);
|
||||
result = -ENODEV; /* we don't want to bind to the iface */
|
||||
}
|
||||
i1480_usb_destroy(i1480_usb);
|
||||
error_create:
|
||||
kfree(i1480->cmd_buf);
|
||||
error_buf_alloc:
|
||||
kfree(i1480_usb);
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
#define i1480_USB_DEV(v, p) \
|
||||
{ \
|
||||
.match_flags = USB_DEVICE_ID_MATCH_DEVICE \
|
||||
| USB_DEVICE_ID_MATCH_DEV_INFO \
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO, \
|
||||
.idVendor = (v), \
|
||||
.idProduct = (p), \
|
||||
.bDeviceClass = 0xff, \
|
||||
.bDeviceSubClass = 0xff, \
|
||||
.bDeviceProtocol = 0xff, \
|
||||
.bInterfaceClass = 0xff, \
|
||||
.bInterfaceSubClass = 0xff, \
|
||||
.bInterfaceProtocol = 0xff, \
|
||||
}
|
||||
|
||||
|
||||
/** USB device ID's that we handle */
|
||||
static struct usb_device_id i1480_usb_id_table[] = {
|
||||
i1480_USB_DEV(0x8086, 0xdf3b),
|
||||
i1480_USB_DEV(0x15a9, 0x0005),
|
||||
i1480_USB_DEV(0x07d1, 0x3802),
|
||||
i1480_USB_DEV(0x050d, 0x305a),
|
||||
i1480_USB_DEV(0x3495, 0x3007),
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, i1480_usb_id_table);
|
||||
|
||||
|
||||
static struct usb_driver i1480_dfu_driver = {
|
||||
.name = "i1480-dfu-usb",
|
||||
.id_table = i1480_usb_id_table,
|
||||
.probe = i1480_usb_probe,
|
||||
.disconnect = NULL,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the i1480 DFU driver.
|
||||
*
|
||||
* We also need to register our function for guessing event sizes.
|
||||
*/
|
||||
static int __init i1480_dfu_driver_init(void)
|
||||
{
|
||||
return usb_register(&i1480_dfu_driver);
|
||||
}
|
||||
module_init(i1480_dfu_driver_init);
|
||||
|
||||
|
||||
static void __exit i1480_dfu_driver_exit(void)
|
||||
{
|
||||
usb_deregister(&i1480_dfu_driver);
|
||||
}
|
||||
module_exit(i1480_dfu_driver_exit);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("Intel Wireless UWB Link 1480 firmware uploader for USB");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Intel Wireless UWB Link 1480
|
||||
* Event Size tables for Wired Adaptors
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/uwb.h>
|
||||
#include "dfu/i1480-dfu.h"
|
||||
|
||||
|
||||
/** Event size table for wEvents 0x00XX */
|
||||
static struct uwb_est_entry i1480_est_fd00[] = {
|
||||
/* Anybody expecting this response has to use
|
||||
* neh->extra_size to specify the real size that will
|
||||
* come back. */
|
||||
[i1480_EVT_CONFIRM] = { .size = sizeof(struct i1480_evt_confirm) },
|
||||
[i1480_CMD_SET_IP_MAS] = { .size = sizeof(struct i1480_evt_confirm) },
|
||||
#ifdef i1480_RCEB_EXTENDED
|
||||
[0x09] = {
|
||||
.size = sizeof(struct i1480_rceb),
|
||||
.offset = 1 + offsetof(struct i1480_rceb, wParamLength),
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
/** Event size table for wEvents 0x01XX */
|
||||
static struct uwb_est_entry i1480_est_fd01[] = {
|
||||
[0xff & i1480_EVT_RM_INIT_DONE] = { .size = sizeof(struct i1480_rceb) },
|
||||
[0xff & i1480_EVT_DEV_ADD] = { .size = sizeof(struct i1480_rceb) + 9 },
|
||||
[0xff & i1480_EVT_DEV_RM] = { .size = sizeof(struct i1480_rceb) + 9 },
|
||||
[0xff & i1480_EVT_DEV_ID_CHANGE] = {
|
||||
.size = sizeof(struct i1480_rceb) + 2 },
|
||||
};
|
||||
|
||||
static int i1480_est_init(void)
|
||||
{
|
||||
int result = uwb_est_register(i1480_CET_VS1, 0x00, 0x8086, 0x0c3b,
|
||||
i1480_est_fd00,
|
||||
ARRAY_SIZE(i1480_est_fd00));
|
||||
if (result < 0) {
|
||||
printk(KERN_ERR "Can't register EST table fd00: %d\n", result);
|
||||
return result;
|
||||
}
|
||||
result = uwb_est_register(i1480_CET_VS1, 0x01, 0x8086, 0x0c3b,
|
||||
i1480_est_fd01, ARRAY_SIZE(i1480_est_fd01));
|
||||
if (result < 0) {
|
||||
printk(KERN_ERR "Can't register EST table fd01: %d\n", result);
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
module_init(i1480_est_init);
|
||||
|
||||
static void i1480_est_exit(void)
|
||||
{
|
||||
uwb_est_unregister(i1480_CET_VS1, 0x00, 0x8086, 0x0c3b,
|
||||
i1480_est_fd00, ARRAY_SIZE(i1480_est_fd00));
|
||||
uwb_est_unregister(i1480_CET_VS1, 0x01, 0x8086, 0x0c3b,
|
||||
i1480_est_fd01, ARRAY_SIZE(i1480_est_fd01));
|
||||
}
|
||||
module_exit(i1480_est_exit);
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("i1480's Vendor Specific Event Size Tables");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/**
|
||||
* USB device ID's that we handle
|
||||
*
|
||||
* [so we are loaded when this kind device is connected]
|
||||
*/
|
||||
static struct usb_device_id i1480_est_id_table[] = {
|
||||
{ USB_DEVICE(0x8086, 0xdf3b), },
|
||||
{ USB_DEVICE(0x8086, 0x0c3b), },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, i1480_est_id_table);
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Intel 1480 Wireless UWB Link
|
||||
* WLP specific definitions
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*/
|
||||
|
||||
#ifndef __i1480_wlp_h__
|
||||
#define __i1480_wlp_h__
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
/* New simplified header format? */
|
||||
#undef WLP_HDR_FMT_2 /* FIXME: rename */
|
||||
|
||||
/**
|
||||
* Values of the Delivery ID & Type field when PCA or DRP
|
||||
*
|
||||
* The Delivery ID & Type field in the WLP TX header indicates whether
|
||||
* the frame is PCA or DRP. This is done based on the high level bit of
|
||||
* this field.
|
||||
* We use this constant to test if the traffic is PCA or DRP as follows:
|
||||
* if (wlp_tx_hdr_delivery_id_type(wlp_tx_hdr) & WLP_DRP)
|
||||
* this is DRP traffic
|
||||
* else
|
||||
* this is PCA traffic
|
||||
*/
|
||||
enum deliver_id_type_bit {
|
||||
WLP_DRP = 8,
|
||||
};
|
||||
|
||||
/**
|
||||
* WLP TX header
|
||||
*
|
||||
* Indicates UWB/WLP-specific transmission parameters for a network
|
||||
* packet.
|
||||
*/
|
||||
struct wlp_tx_hdr {
|
||||
/* dword 0 */
|
||||
struct uwb_dev_addr dstaddr;
|
||||
u8 key_index;
|
||||
u8 mac_params;
|
||||
/* dword 1 */
|
||||
u8 phy_params;
|
||||
#ifndef WLP_HDR_FMT_2
|
||||
u8 reserved;
|
||||
__le16 oui01; /* FIXME: not so sure if __le16 or u8[2] */
|
||||
/* dword 2 */
|
||||
u8 oui2; /* if all LE, it could be merged */
|
||||
__le16 prid;
|
||||
#endif
|
||||
} __attribute__((packed));
|
||||
|
||||
static inline int wlp_tx_hdr_delivery_id_type(const struct wlp_tx_hdr *hdr)
|
||||
{
|
||||
return hdr->mac_params & 0x0f;
|
||||
}
|
||||
|
||||
static inline int wlp_tx_hdr_ack_policy(const struct wlp_tx_hdr *hdr)
|
||||
{
|
||||
return (hdr->mac_params >> 4) & 0x07;
|
||||
}
|
||||
|
||||
static inline int wlp_tx_hdr_rts_cts(const struct wlp_tx_hdr *hdr)
|
||||
{
|
||||
return (hdr->mac_params >> 7) & 0x01;
|
||||
}
|
||||
|
||||
static inline void wlp_tx_hdr_set_delivery_id_type(struct wlp_tx_hdr *hdr, int id)
|
||||
{
|
||||
hdr->mac_params = (hdr->mac_params & ~0x0f) | id;
|
||||
}
|
||||
|
||||
static inline void wlp_tx_hdr_set_ack_policy(struct wlp_tx_hdr *hdr,
|
||||
enum uwb_ack_pol policy)
|
||||
{
|
||||
hdr->mac_params = (hdr->mac_params & ~0x70) | (policy << 4);
|
||||
}
|
||||
|
||||
static inline void wlp_tx_hdr_set_rts_cts(struct wlp_tx_hdr *hdr, int rts_cts)
|
||||
{
|
||||
hdr->mac_params = (hdr->mac_params & ~0x80) | (rts_cts << 7);
|
||||
}
|
||||
|
||||
static inline enum uwb_phy_rate wlp_tx_hdr_phy_rate(const struct wlp_tx_hdr *hdr)
|
||||
{
|
||||
return hdr->phy_params & 0x0f;
|
||||
}
|
||||
|
||||
static inline int wlp_tx_hdr_tx_power(const struct wlp_tx_hdr *hdr)
|
||||
{
|
||||
return (hdr->phy_params >> 4) & 0x0f;
|
||||
}
|
||||
|
||||
static inline void wlp_tx_hdr_set_phy_rate(struct wlp_tx_hdr *hdr, enum uwb_phy_rate rate)
|
||||
{
|
||||
hdr->phy_params = (hdr->phy_params & ~0x0f) | rate;
|
||||
}
|
||||
|
||||
static inline void wlp_tx_hdr_set_tx_power(struct wlp_tx_hdr *hdr, int pwr)
|
||||
{
|
||||
hdr->phy_params = (hdr->phy_params & ~0xf0) | (pwr << 4);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WLP RX header
|
||||
*
|
||||
* Provides UWB/WLP-specific transmission data for a received
|
||||
* network packet.
|
||||
*/
|
||||
struct wlp_rx_hdr {
|
||||
/* dword 0 */
|
||||
struct uwb_dev_addr dstaddr;
|
||||
struct uwb_dev_addr srcaddr;
|
||||
/* dword 1 */
|
||||
u8 LQI;
|
||||
s8 RSSI;
|
||||
u8 reserved3;
|
||||
#ifndef WLP_HDR_FMT_2
|
||||
u8 oui0;
|
||||
/* dword 2 */
|
||||
__le16 oui12;
|
||||
__le16 prid;
|
||||
#endif
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/** User configurable options for WLP */
|
||||
struct wlp_options {
|
||||
struct mutex mutex; /* access to user configurable options*/
|
||||
struct wlp_tx_hdr def_tx_hdr; /* default tx hdr */
|
||||
u8 pca_base_priority;
|
||||
u8 bw_alloc; /*index into bw_allocs[] for PCA/DRP reservations*/
|
||||
};
|
||||
|
||||
|
||||
static inline
|
||||
void wlp_options_init(struct wlp_options *options)
|
||||
{
|
||||
mutex_init(&options->mutex);
|
||||
wlp_tx_hdr_set_ack_policy(&options->def_tx_hdr, UWB_ACK_INM);
|
||||
wlp_tx_hdr_set_rts_cts(&options->def_tx_hdr, 1);
|
||||
/* FIXME: default to phy caps */
|
||||
wlp_tx_hdr_set_phy_rate(&options->def_tx_hdr, UWB_PHY_RATE_480);
|
||||
#ifndef WLP_HDR_FMT_2
|
||||
options->def_tx_hdr.prid = cpu_to_le16(0x0000);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* sysfs helpers */
|
||||
|
||||
extern ssize_t uwb_pca_base_priority_store(struct wlp_options *,
|
||||
const char *, size_t);
|
||||
extern ssize_t uwb_pca_base_priority_show(const struct wlp_options *, char *);
|
||||
extern ssize_t uwb_bw_alloc_store(struct wlp_options *, const char *, size_t);
|
||||
extern ssize_t uwb_bw_alloc_show(const struct wlp_options *, char *);
|
||||
extern ssize_t uwb_ack_policy_store(struct wlp_options *,
|
||||
const char *, size_t);
|
||||
extern ssize_t uwb_ack_policy_show(const struct wlp_options *, char *);
|
||||
extern ssize_t uwb_rts_cts_store(struct wlp_options *, const char *, size_t);
|
||||
extern ssize_t uwb_rts_cts_show(const struct wlp_options *, char *);
|
||||
extern ssize_t uwb_phy_rate_store(struct wlp_options *, const char *, size_t);
|
||||
extern ssize_t uwb_phy_rate_show(const struct wlp_options *, char *);
|
||||
|
||||
|
||||
/** Simple bandwidth allocation (temporary and too simple) */
|
||||
struct wlp_bw_allocs {
|
||||
const char *name;
|
||||
struct {
|
||||
u8 mask, stream;
|
||||
} tx, rx;
|
||||
};
|
||||
|
||||
|
||||
#endif /* #ifndef __i1480_wlp_h__ */
|
|
@ -0,0 +1,8 @@
|
|||
obj-$(CONFIG_UWB_I1480U_WLP) += i1480u-wlp.o
|
||||
|
||||
i1480u-wlp-objs := \
|
||||
lc.o \
|
||||
netdev.o \
|
||||
rx.o \
|
||||
sysfs.o \
|
||||
tx.o
|
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* Intel 1480 Wireless UWB Link USB
|
||||
* Header formats, constants, general internal interfaces
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This is not an standard interface.
|
||||
*
|
||||
* FIXME: docs
|
||||
*
|
||||
* i1480u-wlp is pretty simple: two endpoints, one for tx, one for
|
||||
* rx. rx is polled. Network packets (ethernet, whatever) are wrapped
|
||||
* in i1480 TX or RX headers (for sending over the air), and these
|
||||
* packets are wrapped in UNTD headers (for sending to the WLP UWB
|
||||
* controller).
|
||||
*
|
||||
* UNTD packets (UNTD hdr + i1480 hdr + network packet) packets
|
||||
* cannot be bigger than i1480u_MAX_FRG_SIZE. When this happens, the
|
||||
* i1480 packet is broken in chunks/packets:
|
||||
*
|
||||
* UNTD-1st.hdr + i1480.hdr + payload
|
||||
* UNTD-next.hdr + payload
|
||||
* ...
|
||||
* UNTD-last.hdr + payload
|
||||
*
|
||||
* so that each packet is smaller or equal than i1480u_MAX_FRG_SIZE.
|
||||
*
|
||||
* All HW structures and bitmaps are little endian, so we need to play
|
||||
* ugly tricks when defining bitfields. Hoping for the day GCC
|
||||
* implements __attribute__((endian(1234))).
|
||||
*
|
||||
* FIXME: ROADMAP to the whole implementation
|
||||
*/
|
||||
|
||||
#ifndef __i1480u_wlp_h__
|
||||
#define __i1480u_wlp_h__
|
||||
|
||||
#include <linux/usb.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/uwb.h> /* struct uwb_rc, struct uwb_notifs_handler */
|
||||
#include <linux/wlp.h>
|
||||
#include "../i1480-wlp.h"
|
||||
|
||||
#undef i1480u_FLOW_CONTROL /* Enable flow control code */
|
||||
|
||||
/**
|
||||
* Basic flow control
|
||||
*/
|
||||
enum {
|
||||
i1480u_TX_INFLIGHT_MAX = 1000,
|
||||
i1480u_TX_INFLIGHT_THRESHOLD = 100,
|
||||
};
|
||||
|
||||
/** Maximum size of a transaction that we can tx/rx */
|
||||
enum {
|
||||
/* Maximum packet size computed as follows: max UNTD header (8) +
|
||||
* i1480 RX header (8) + max Ethernet header and payload (4096) +
|
||||
* Padding added by skb_reserve (2) to make post Ethernet payload
|
||||
* start on 16 byte boundary*/
|
||||
i1480u_MAX_RX_PKT_SIZE = 4114,
|
||||
i1480u_MAX_FRG_SIZE = 512,
|
||||
i1480u_RX_BUFS = 9,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* UNTD packet type
|
||||
*
|
||||
* We need to fragment any payload whose UNTD packet is going to be
|
||||
* bigger than i1480u_MAX_FRG_SIZE.
|
||||
*/
|
||||
enum i1480u_pkt_type {
|
||||
i1480u_PKT_FRAG_1ST = 0x1,
|
||||
i1480u_PKT_FRAG_NXT = 0x0,
|
||||
i1480u_PKT_FRAG_LST = 0x2,
|
||||
i1480u_PKT_FRAG_CMP = 0x3
|
||||
};
|
||||
enum {
|
||||
i1480u_PKT_NONE = 0x4,
|
||||
};
|
||||
|
||||
/** USB Network Transfer Descriptor - common */
|
||||
struct untd_hdr {
|
||||
u8 type;
|
||||
__le16 len;
|
||||
} __attribute__((packed));
|
||||
|
||||
static inline enum i1480u_pkt_type untd_hdr_type(const struct untd_hdr *hdr)
|
||||
{
|
||||
return hdr->type & 0x03;
|
||||
}
|
||||
|
||||
static inline int untd_hdr_rx_tx(const struct untd_hdr *hdr)
|
||||
{
|
||||
return (hdr->type >> 2) & 0x01;
|
||||
}
|
||||
|
||||
static inline void untd_hdr_set_type(struct untd_hdr *hdr, enum i1480u_pkt_type type)
|
||||
{
|
||||
hdr->type = (hdr->type & ~0x03) | type;
|
||||
}
|
||||
|
||||
static inline void untd_hdr_set_rx_tx(struct untd_hdr *hdr, int rx_tx)
|
||||
{
|
||||
hdr->type = (hdr->type & ~0x04) | (rx_tx << 2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* USB Network Transfer Descriptor - Complete Packet
|
||||
*
|
||||
* This is for a packet that is smaller (header + payload) than
|
||||
* i1480u_MAX_FRG_SIZE.
|
||||
*
|
||||
* @hdr.total_len is the size of the payload; the payload doesn't
|
||||
* count this header nor the padding, but includes the size of i1480
|
||||
* header.
|
||||
*/
|
||||
struct untd_hdr_cmp {
|
||||
struct untd_hdr hdr;
|
||||
u8 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/**
|
||||
* USB Network Transfer Descriptor - First fragment
|
||||
*
|
||||
* @hdr.len is the size of the *whole packet* (excluding UNTD
|
||||
* headers); @fragment_len is the size of the payload (excluding UNTD
|
||||
* headers, but including i1480 headers).
|
||||
*/
|
||||
struct untd_hdr_1st {
|
||||
struct untd_hdr hdr;
|
||||
__le16 fragment_len;
|
||||
u8 padding[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/**
|
||||
* USB Network Transfer Descriptor - Next / Last [Rest]
|
||||
*
|
||||
* @hdr.len is the size of the payload, not including headrs.
|
||||
*/
|
||||
struct untd_hdr_rst {
|
||||
struct untd_hdr hdr;
|
||||
u8 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/**
|
||||
* Transmission context
|
||||
*
|
||||
* Wraps all the stuff needed to track a pending/active tx
|
||||
* operation.
|
||||
*/
|
||||
struct i1480u_tx {
|
||||
struct list_head list_node;
|
||||
struct i1480u *i1480u;
|
||||
struct urb *urb;
|
||||
|
||||
struct sk_buff *skb;
|
||||
struct wlp_tx_hdr *wlp_tx_hdr;
|
||||
|
||||
void *buf; /* if NULL, no new buf was used */
|
||||
size_t buf_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* Basic flow control
|
||||
*
|
||||
* We maintain a basic flow control counter. "count" how many TX URBs are
|
||||
* outstanding. Only allow "max"
|
||||
* TX URBs to be outstanding. If this value is reached the queue will be
|
||||
* stopped. The queue will be restarted when there are
|
||||
* "threshold" URBs outstanding.
|
||||
* Maintain a counter of how many time the TX queue needed to be restarted
|
||||
* due to the "max" being exceeded and the "threshold" reached again. The
|
||||
* timestamp "restart_ts" is to keep track from when the counter was last
|
||||
* queried (see sysfs handling of file wlp_tx_inflight).
|
||||
*/
|
||||
struct i1480u_tx_inflight {
|
||||
atomic_t count;
|
||||
unsigned long max;
|
||||
unsigned long threshold;
|
||||
unsigned long restart_ts;
|
||||
atomic_t restart_count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instance of a i1480u WLP interface
|
||||
*
|
||||
* Keeps references to the USB device that wraps it, as well as it's
|
||||
* interface and associated UWB host controller. As well, it also
|
||||
* keeps a link to the netdevice for integration into the networking
|
||||
* stack.
|
||||
* We maintian separate error history for the tx and rx endpoints because
|
||||
* the implementation does not rely on locking - having one shared
|
||||
* structure between endpoints may cause problems. Adding locking to the
|
||||
* implementation will have higher cost than adding a separate structure.
|
||||
*/
|
||||
struct i1480u {
|
||||
struct usb_device *usb_dev;
|
||||
struct usb_interface *usb_iface;
|
||||
struct net_device *net_dev;
|
||||
|
||||
spinlock_t lock;
|
||||
struct net_device_stats stats;
|
||||
|
||||
/* RX context handling */
|
||||
struct sk_buff *rx_skb;
|
||||
struct uwb_dev_addr rx_srcaddr;
|
||||
size_t rx_untd_pkt_size;
|
||||
struct i1480u_rx_buf {
|
||||
struct i1480u *i1480u; /* back pointer */
|
||||
struct urb *urb;
|
||||
struct sk_buff *data; /* i1480u_MAX_RX_PKT_SIZE each */
|
||||
} rx_buf[i1480u_RX_BUFS]; /* N bufs */
|
||||
|
||||
spinlock_t tx_list_lock; /* TX context */
|
||||
struct list_head tx_list;
|
||||
u8 tx_stream;
|
||||
|
||||
struct stats lqe_stats, rssi_stats; /* radio statistics */
|
||||
|
||||
/* Options we can set from sysfs */
|
||||
struct wlp_options options;
|
||||
struct uwb_notifs_handler uwb_notifs_handler;
|
||||
struct edc tx_errors;
|
||||
struct edc rx_errors;
|
||||
struct wlp wlp;
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
struct urb *notif_urb;
|
||||
struct edc notif_edc; /* error density counter */
|
||||
u8 notif_buffer[1];
|
||||
#endif
|
||||
struct i1480u_tx_inflight tx_inflight;
|
||||
};
|
||||
|
||||
/* Internal interfaces */
|
||||
extern void i1480u_rx_cb(struct urb *urb);
|
||||
extern int i1480u_rx_setup(struct i1480u *);
|
||||
extern void i1480u_rx_release(struct i1480u *);
|
||||
extern void i1480u_tx_release(struct i1480u *);
|
||||
extern int i1480u_xmit_frame(struct wlp *, struct sk_buff *,
|
||||
struct uwb_dev_addr *);
|
||||
extern void i1480u_stop_queue(struct wlp *);
|
||||
extern void i1480u_start_queue(struct wlp *);
|
||||
extern int i1480u_sysfs_setup(struct i1480u *);
|
||||
extern void i1480u_sysfs_release(struct i1480u *);
|
||||
|
||||
/* netdev interface */
|
||||
extern int i1480u_open(struct net_device *);
|
||||
extern int i1480u_stop(struct net_device *);
|
||||
extern int i1480u_hard_start_xmit(struct sk_buff *, struct net_device *);
|
||||
extern void i1480u_tx_timeout(struct net_device *);
|
||||
extern int i1480u_set_config(struct net_device *, struct ifmap *);
|
||||
extern struct net_device_stats *i1480u_get_stats(struct net_device *);
|
||||
extern int i1480u_change_mtu(struct net_device *, int);
|
||||
extern void i1480u_uwb_notifs_cb(void *, struct uwb_dev *, enum uwb_notifs);
|
||||
|
||||
/* bandwidth allocation callback */
|
||||
extern void i1480u_bw_alloc_cb(struct uwb_rsv *);
|
||||
|
||||
/* Sys FS */
|
||||
extern struct attribute_group i1480u_wlp_attr_group;
|
||||
|
||||
#endif /* #ifndef __i1480u_wlp_h__ */
|
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: WLP interface
|
||||
* Driver for the Linux Network stack.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*
|
||||
* This implements a very simple network driver for the WLP USB
|
||||
* device that is associated to a UWB (Ultra Wide Band) host.
|
||||
*
|
||||
* This is seen as an interface of a composite device. Once the UWB
|
||||
* host has an association to another WLP capable device, the
|
||||
* networking interface (aka WLP) can start to send packets back and
|
||||
* forth.
|
||||
*
|
||||
* Limitations:
|
||||
*
|
||||
* - Hand cranked; can't ifup the interface until there is an association
|
||||
*
|
||||
* - BW allocation very simplistic [see i1480u_mas_set() and callees].
|
||||
*
|
||||
*
|
||||
* ROADMAP:
|
||||
*
|
||||
* ENTRY POINTS (driver model):
|
||||
*
|
||||
* i1480u_driver_{exit,init}(): initialization of the driver.
|
||||
*
|
||||
* i1480u_probe(): called by the driver code when a device
|
||||
* matching 'i1480u_id_table' is connected.
|
||||
*
|
||||
* This allocs a netdev instance, inits with
|
||||
* i1480u_add(), then registers_netdev().
|
||||
* i1480u_init()
|
||||
* i1480u_add()
|
||||
*
|
||||
* i1480u_disconnect(): device has been disconnected/module
|
||||
* is being removed.
|
||||
* i1480u_rm()
|
||||
*/
|
||||
#include <linux/version.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "i1480u-wlp.h"
|
||||
|
||||
|
||||
|
||||
static inline
|
||||
void i1480u_init(struct i1480u *i1480u)
|
||||
{
|
||||
/* nothing so far... doesn't it suck? */
|
||||
spin_lock_init(&i1480u->lock);
|
||||
INIT_LIST_HEAD(&i1480u->tx_list);
|
||||
spin_lock_init(&i1480u->tx_list_lock);
|
||||
wlp_options_init(&i1480u->options);
|
||||
edc_init(&i1480u->tx_errors);
|
||||
edc_init(&i1480u->rx_errors);
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
edc_init(&i1480u->notif_edc);
|
||||
#endif
|
||||
stats_init(&i1480u->lqe_stats);
|
||||
stats_init(&i1480u->rssi_stats);
|
||||
wlp_init(&i1480u->wlp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill WLP device information structure
|
||||
*
|
||||
* The structure will contain a few character arrays, each ending with a
|
||||
* null terminated string. Each string has to fit (excluding terminating
|
||||
* character) into a specified range obtained from the WLP substack.
|
||||
*
|
||||
* It is still not clear exactly how this device information should be
|
||||
* obtained. Until we find out we use the USB device descriptor as backup, some
|
||||
* information elements have intuitive mappings, other not.
|
||||
*/
|
||||
static
|
||||
void i1480u_fill_device_info(struct wlp *wlp, struct wlp_device_info *dev_info)
|
||||
{
|
||||
struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp);
|
||||
struct usb_device *usb_dev = i1480u->usb_dev;
|
||||
/* Treat device name and model name the same */
|
||||
if (usb_dev->descriptor.iProduct) {
|
||||
usb_string(usb_dev, usb_dev->descriptor.iProduct,
|
||||
dev_info->name, sizeof(dev_info->name));
|
||||
usb_string(usb_dev, usb_dev->descriptor.iProduct,
|
||||
dev_info->model_name, sizeof(dev_info->model_name));
|
||||
}
|
||||
if (usb_dev->descriptor.iManufacturer)
|
||||
usb_string(usb_dev, usb_dev->descriptor.iManufacturer,
|
||||
dev_info->manufacturer,
|
||||
sizeof(dev_info->manufacturer));
|
||||
scnprintf(dev_info->model_nr, sizeof(dev_info->model_nr), "%04x",
|
||||
__le16_to_cpu(usb_dev->descriptor.bcdDevice));
|
||||
if (usb_dev->descriptor.iSerialNumber)
|
||||
usb_string(usb_dev, usb_dev->descriptor.iSerialNumber,
|
||||
dev_info->serial, sizeof(dev_info->serial));
|
||||
/* FIXME: where should we obtain category? */
|
||||
dev_info->prim_dev_type.category = cpu_to_le16(WLP_DEV_CAT_OTHER);
|
||||
/* FIXME: Complete OUI and OUIsubdiv attributes */
|
||||
}
|
||||
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
/**
|
||||
* Callback for the notification endpoint
|
||||
*
|
||||
* This mostly controls the xon/xoff protocol. In case of hard error,
|
||||
* we stop the queue. If not, we always retry.
|
||||
*/
|
||||
static
|
||||
void i1480u_notif_cb(struct urb *urb, struct pt_regs *regs)
|
||||
{
|
||||
struct i1480u *i1480u = urb->context;
|
||||
struct usb_interface *usb_iface = i1480u->usb_iface;
|
||||
struct device *dev = &usb_iface->dev;
|
||||
int result;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0: /* Got valid data, do xon/xoff */
|
||||
switch (i1480u->notif_buffer[0]) {
|
||||
case 'N':
|
||||
dev_err(dev, "XOFF STOPPING queue at %lu\n", jiffies);
|
||||
netif_stop_queue(i1480u->net_dev);
|
||||
break;
|
||||
case 'A':
|
||||
dev_err(dev, "XON STARTING queue at %lu\n", jiffies);
|
||||
netif_start_queue(i1480u->net_dev);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "NEP: unknown data 0x%02hhx\n",
|
||||
i1480u->notif_buffer[0]);
|
||||
}
|
||||
break;
|
||||
case -ECONNRESET: /* Controlled situation ... */
|
||||
case -ENOENT: /* we killed the URB... */
|
||||
dev_err(dev, "NEP: URB reset/noent %d\n", urb->status);
|
||||
goto error;
|
||||
case -ESHUTDOWN: /* going away! */
|
||||
dev_err(dev, "NEP: URB down %d\n", urb->status);
|
||||
goto error;
|
||||
default: /* Retry unless it gets ugly */
|
||||
if (edc_inc(&i1480u->notif_edc, EDC_MAX_ERRORS,
|
||||
EDC_ERROR_TIMEFRAME)) {
|
||||
dev_err(dev, "NEP: URB max acceptable errors "
|
||||
"exceeded; resetting device\n");
|
||||
goto error_reset;
|
||||
}
|
||||
dev_err(dev, "NEP: URB error %d\n", urb->status);
|
||||
break;
|
||||
}
|
||||
result = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "NEP: Can't resubmit URB: %d; resetting device\n",
|
||||
result);
|
||||
goto error_reset;
|
||||
}
|
||||
return;
|
||||
|
||||
error_reset:
|
||||
wlp_reset_all(&i1480-wlp);
|
||||
error:
|
||||
netif_stop_queue(i1480u->net_dev);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
static
|
||||
int i1480u_add(struct i1480u *i1480u, struct usb_interface *iface)
|
||||
{
|
||||
int result = -ENODEV;
|
||||
struct wlp *wlp = &i1480u->wlp;
|
||||
struct usb_device *usb_dev = interface_to_usbdev(iface);
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
struct uwb_rc *rc;
|
||||
struct uwb_dev *uwb_dev;
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
struct usb_endpoint_descriptor *epd;
|
||||
#endif
|
||||
|
||||
i1480u->usb_dev = usb_get_dev(usb_dev);
|
||||
i1480u->usb_iface = iface;
|
||||
rc = uwb_rc_get_by_grandpa(&i1480u->usb_dev->dev);
|
||||
if (rc == NULL) {
|
||||
dev_err(&iface->dev, "Cannot get associated UWB Radio "
|
||||
"Controller\n");
|
||||
goto out;
|
||||
}
|
||||
wlp->xmit_frame = i1480u_xmit_frame;
|
||||
wlp->fill_device_info = i1480u_fill_device_info;
|
||||
wlp->stop_queue = i1480u_stop_queue;
|
||||
wlp->start_queue = i1480u_start_queue;
|
||||
result = wlp_setup(wlp, rc);
|
||||
if (result < 0) {
|
||||
dev_err(&iface->dev, "Cannot setup WLP\n");
|
||||
goto error_wlp_setup;
|
||||
}
|
||||
result = 0;
|
||||
ether_setup(net_dev); /* make it an etherdevice */
|
||||
uwb_dev = &rc->uwb_dev;
|
||||
/* FIXME: hookup address change notifications? */
|
||||
|
||||
memcpy(net_dev->dev_addr, uwb_dev->mac_addr.data,
|
||||
sizeof(net_dev->dev_addr));
|
||||
|
||||
net_dev->hard_header_len = sizeof(struct untd_hdr_cmp)
|
||||
+ sizeof(struct wlp_tx_hdr)
|
||||
+ WLP_DATA_HLEN
|
||||
+ ETH_HLEN;
|
||||
net_dev->mtu = 3500;
|
||||
net_dev->tx_queue_len = 20; /* FIXME: maybe use 1000? */
|
||||
|
||||
/* net_dev->flags &= ~IFF_BROADCAST; FIXME: BUG in firmware */
|
||||
/* FIXME: multicast disabled */
|
||||
net_dev->flags &= ~IFF_MULTICAST;
|
||||
net_dev->features &= ~NETIF_F_SG;
|
||||
net_dev->features &= ~NETIF_F_FRAGLIST;
|
||||
/* All NETIF_F_*_CSUM disabled */
|
||||
net_dev->features |= NETIF_F_HIGHDMA;
|
||||
net_dev->watchdog_timeo = 5*HZ; /* FIXME: a better default? */
|
||||
|
||||
net_dev->open = i1480u_open;
|
||||
net_dev->stop = i1480u_stop;
|
||||
net_dev->hard_start_xmit = i1480u_hard_start_xmit;
|
||||
net_dev->tx_timeout = i1480u_tx_timeout;
|
||||
net_dev->get_stats = i1480u_get_stats;
|
||||
net_dev->set_config = i1480u_set_config;
|
||||
net_dev->change_mtu = i1480u_change_mtu;
|
||||
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
/* Notification endpoint setup (submitted when we open the device) */
|
||||
i1480u->notif_urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (i1480u->notif_urb == NULL) {
|
||||
dev_err(&iface->dev, "Unable to allocate notification URB\n");
|
||||
result = -ENOMEM;
|
||||
goto error_urb_alloc;
|
||||
}
|
||||
epd = &iface->cur_altsetting->endpoint[0].desc;
|
||||
usb_fill_int_urb(i1480u->notif_urb, usb_dev,
|
||||
usb_rcvintpipe(usb_dev, epd->bEndpointAddress),
|
||||
i1480u->notif_buffer, sizeof(i1480u->notif_buffer),
|
||||
i1480u_notif_cb, i1480u, epd->bInterval);
|
||||
|
||||
#endif
|
||||
|
||||
i1480u->tx_inflight.max = i1480u_TX_INFLIGHT_MAX;
|
||||
i1480u->tx_inflight.threshold = i1480u_TX_INFLIGHT_THRESHOLD;
|
||||
i1480u->tx_inflight.restart_ts = jiffies;
|
||||
usb_set_intfdata(iface, i1480u);
|
||||
return result;
|
||||
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
error_urb_alloc:
|
||||
#endif
|
||||
wlp_remove(wlp);
|
||||
error_wlp_setup:
|
||||
uwb_rc_put(rc);
|
||||
out:
|
||||
usb_put_dev(i1480u->usb_dev);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void i1480u_rm(struct i1480u *i1480u)
|
||||
{
|
||||
struct uwb_rc *rc = i1480u->wlp.rc;
|
||||
usb_set_intfdata(i1480u->usb_iface, NULL);
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
usb_kill_urb(i1480u->notif_urb);
|
||||
usb_free_urb(i1480u->notif_urb);
|
||||
#endif
|
||||
wlp_remove(&i1480u->wlp);
|
||||
uwb_rc_put(rc);
|
||||
usb_put_dev(i1480u->usb_dev);
|
||||
}
|
||||
|
||||
/** Just setup @net_dev's i1480u private data */
|
||||
static void i1480u_netdev_setup(struct net_device *net_dev)
|
||||
{
|
||||
struct i1480u *i1480u = netdev_priv(net_dev);
|
||||
/* Initialize @i1480u */
|
||||
memset(i1480u, 0, sizeof(*i1480u));
|
||||
i1480u_init(i1480u);
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe a i1480u interface and register it
|
||||
*
|
||||
* @iface: USB interface to link to
|
||||
* @id: USB class/subclass/protocol id
|
||||
* @returns: 0 if ok, < 0 errno code on error.
|
||||
*
|
||||
* Does basic housekeeping stuff and then allocs a netdev with space
|
||||
* for the i1480u data. Initializes, registers in i1480u, registers in
|
||||
* netdev, ready to go.
|
||||
*/
|
||||
static int i1480u_probe(struct usb_interface *iface,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
int result;
|
||||
struct net_device *net_dev;
|
||||
struct device *dev = &iface->dev;
|
||||
struct i1480u *i1480u;
|
||||
|
||||
/* Allocate instance [calls i1480u_netdev_setup() on it] */
|
||||
result = -ENOMEM;
|
||||
net_dev = alloc_netdev(sizeof(*i1480u), "wlp%d", i1480u_netdev_setup);
|
||||
if (net_dev == NULL) {
|
||||
dev_err(dev, "no memory for network device instance\n");
|
||||
goto error_alloc_netdev;
|
||||
}
|
||||
SET_NETDEV_DEV(net_dev, dev);
|
||||
i1480u = netdev_priv(net_dev);
|
||||
i1480u->net_dev = net_dev;
|
||||
result = i1480u_add(i1480u, iface); /* Now setup all the wlp stuff */
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot add i1480u device: %d\n", result);
|
||||
goto error_i1480u_add;
|
||||
}
|
||||
result = register_netdev(net_dev); /* Okey dokey, bring it up */
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot register network device: %d\n", result);
|
||||
goto error_register_netdev;
|
||||
}
|
||||
i1480u_sysfs_setup(i1480u);
|
||||
if (result < 0)
|
||||
goto error_sysfs_init;
|
||||
return 0;
|
||||
|
||||
error_sysfs_init:
|
||||
unregister_netdev(net_dev);
|
||||
error_register_netdev:
|
||||
i1480u_rm(i1480u);
|
||||
error_i1480u_add:
|
||||
free_netdev(net_dev);
|
||||
error_alloc_netdev:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconect a i1480u from the system.
|
||||
*
|
||||
* i1480u_stop() has been called before, so al the rx and tx contexts
|
||||
* have been taken down already. Make sure the queue is stopped,
|
||||
* unregister netdev and i1480u, free and kill.
|
||||
*/
|
||||
static void i1480u_disconnect(struct usb_interface *iface)
|
||||
{
|
||||
struct i1480u *i1480u;
|
||||
struct net_device *net_dev;
|
||||
|
||||
i1480u = usb_get_intfdata(iface);
|
||||
net_dev = i1480u->net_dev;
|
||||
netif_stop_queue(net_dev);
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
usb_kill_urb(i1480u->notif_urb);
|
||||
#endif
|
||||
i1480u_sysfs_release(i1480u);
|
||||
unregister_netdev(net_dev);
|
||||
i1480u_rm(i1480u);
|
||||
free_netdev(net_dev);
|
||||
}
|
||||
|
||||
static struct usb_device_id i1480u_id_table[] = {
|
||||
{
|
||||
.match_flags = USB_DEVICE_ID_MATCH_DEVICE \
|
||||
| USB_DEVICE_ID_MATCH_DEV_INFO \
|
||||
| USB_DEVICE_ID_MATCH_INT_INFO,
|
||||
.idVendor = 0x8086,
|
||||
.idProduct = 0x0c3b,
|
||||
.bDeviceClass = 0xef,
|
||||
.bDeviceSubClass = 0x02,
|
||||
.bDeviceProtocol = 0x02,
|
||||
.bInterfaceClass = 0xff,
|
||||
.bInterfaceSubClass = 0xff,
|
||||
.bInterfaceProtocol = 0xff,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, i1480u_id_table);
|
||||
|
||||
static struct usb_driver i1480u_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.probe = i1480u_probe,
|
||||
.disconnect = i1480u_disconnect,
|
||||
.id_table = i1480u_id_table,
|
||||
};
|
||||
|
||||
static int __init i1480u_driver_init(void)
|
||||
{
|
||||
return usb_register(&i1480u_driver);
|
||||
}
|
||||
module_init(i1480u_driver_init);
|
||||
|
||||
|
||||
static void __exit i1480u_driver_exit(void)
|
||||
{
|
||||
usb_deregister(&i1480u_driver);
|
||||
}
|
||||
module_exit(i1480u_driver_exit);
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("i1480 Wireless UWB Link WLP networking for USB");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,368 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: WLP interface
|
||||
* Driver for the Linux Network stack.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*
|
||||
* Implementation of the netdevice linkage (except tx and rx related stuff).
|
||||
*
|
||||
* ROADMAP:
|
||||
*
|
||||
* ENTRY POINTS (Net device):
|
||||
*
|
||||
* i1480u_open(): Called when we ifconfig up the interface;
|
||||
* associates to a UWB host controller, reserves
|
||||
* bandwidth (MAS), sets up RX USB URB and starts
|
||||
* the queue.
|
||||
*
|
||||
* i1480u_stop(): Called when we ifconfig down a interface;
|
||||
* reverses _open().
|
||||
*
|
||||
* i1480u_set_config():
|
||||
*/
|
||||
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "i1480u-wlp.h"
|
||||
|
||||
struct i1480u_cmd_set_ip_mas {
|
||||
struct uwb_rccb rccb;
|
||||
struct uwb_dev_addr addr;
|
||||
u8 stream;
|
||||
u8 owner;
|
||||
u8 type; /* enum uwb_drp_type */
|
||||
u8 baMAS[32];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
static
|
||||
int i1480u_set_ip_mas(
|
||||
struct uwb_rc *rc,
|
||||
const struct uwb_dev_addr *dstaddr,
|
||||
u8 stream, u8 owner, u8 type, unsigned long *mas)
|
||||
{
|
||||
|
||||
int result;
|
||||
struct i1480u_cmd_set_ip_mas *cmd;
|
||||
struct uwb_rc_evt_confirm reply;
|
||||
|
||||
result = -ENOMEM;
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
goto error_kzalloc;
|
||||
cmd->rccb.bCommandType = 0xfd;
|
||||
cmd->rccb.wCommand = cpu_to_le16(0x000e);
|
||||
cmd->addr = *dstaddr;
|
||||
cmd->stream = stream;
|
||||
cmd->owner = owner;
|
||||
cmd->type = type;
|
||||
if (mas == NULL)
|
||||
memset(cmd->baMAS, 0x00, sizeof(cmd->baMAS));
|
||||
else
|
||||
memcpy(cmd->baMAS, mas, sizeof(cmd->baMAS));
|
||||
reply.rceb.bEventType = 0xfd;
|
||||
reply.rceb.wEvent = cpu_to_le16(0x000e);
|
||||
result = uwb_rc_cmd(rc, "SET-IP-MAS", &cmd->rccb, sizeof(*cmd),
|
||||
&reply.rceb, sizeof(reply));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
if (reply.bResultCode != UWB_RC_RES_FAIL) {
|
||||
dev_err(&rc->uwb_dev.dev,
|
||||
"SET-IP-MAS: command execution failed: %d\n",
|
||||
reply.bResultCode);
|
||||
result = -EIO;
|
||||
}
|
||||
error_cmd:
|
||||
kfree(cmd);
|
||||
error_kzalloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inform a WLP interface of a MAS reservation
|
||||
*
|
||||
* @rc is assumed refcnted.
|
||||
*/
|
||||
/* FIXME: detect if remote device is WLP capable? */
|
||||
static int i1480u_mas_set_dev(struct uwb_dev *uwb_dev, struct uwb_rc *rc,
|
||||
u8 stream, u8 owner, u8 type, unsigned long *mas)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
|
||||
result = i1480u_set_ip_mas(rc, &uwb_dev->dev_addr, stream, owner,
|
||||
type, mas);
|
||||
if (result < 0) {
|
||||
char rcaddrbuf[UWB_ADDR_STRSIZE], devaddrbuf[UWB_ADDR_STRSIZE];
|
||||
uwb_dev_addr_print(rcaddrbuf, sizeof(rcaddrbuf),
|
||||
&rc->uwb_dev.dev_addr);
|
||||
uwb_dev_addr_print(devaddrbuf, sizeof(devaddrbuf),
|
||||
&uwb_dev->dev_addr);
|
||||
dev_err(dev, "Set IP MAS (%s to %s) failed: %d\n",
|
||||
rcaddrbuf, devaddrbuf, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by bandwidth allocator when change occurs in reservation.
|
||||
*
|
||||
* @rsv: The reservation that is being established, modified, or
|
||||
* terminated.
|
||||
*
|
||||
* When a reservation is established, modified, or terminated the upper layer
|
||||
* (WLP here) needs set/update the currently available Media Access Slots
|
||||
* that can be use for IP traffic.
|
||||
*
|
||||
* Our action taken during failure depends on how the reservation is being
|
||||
* changed:
|
||||
* - if reservation is being established we do nothing if we cannot set the
|
||||
* new MAS to be used
|
||||
* - if reservation is being terminated we revert back to PCA whether the
|
||||
* SET IP MAS command succeeds or not.
|
||||
*/
|
||||
void i1480u_bw_alloc_cb(struct uwb_rsv *rsv)
|
||||
{
|
||||
int result = 0;
|
||||
struct i1480u *i1480u = rsv->pal_priv;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
struct uwb_dev *target_dev = rsv->target.dev;
|
||||
struct uwb_rc *rc = i1480u->wlp.rc;
|
||||
u8 stream = rsv->stream;
|
||||
int type = rsv->type;
|
||||
int is_owner = rsv->owner == &rc->uwb_dev;
|
||||
unsigned long *bmp = rsv->mas.bm;
|
||||
|
||||
dev_err(dev, "WLP callback called - sending set ip mas\n");
|
||||
/*user cannot change options while setting configuration*/
|
||||
mutex_lock(&i1480u->options.mutex);
|
||||
switch (rsv->state) {
|
||||
case UWB_RSV_STATE_T_ACCEPTED:
|
||||
case UWB_RSV_STATE_O_ESTABLISHED:
|
||||
result = i1480u_mas_set_dev(target_dev, rc, stream, is_owner,
|
||||
type, bmp);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "MAS reservation failed: %d\n", result);
|
||||
goto out;
|
||||
}
|
||||
if (is_owner) {
|
||||
wlp_tx_hdr_set_delivery_id_type(&i1480u->options.def_tx_hdr,
|
||||
WLP_DRP | stream);
|
||||
wlp_tx_hdr_set_rts_cts(&i1480u->options.def_tx_hdr, 0);
|
||||
}
|
||||
break;
|
||||
case UWB_RSV_STATE_NONE:
|
||||
/* revert back to PCA */
|
||||
result = i1480u_mas_set_dev(target_dev, rc, stream, is_owner,
|
||||
type, bmp);
|
||||
if (result < 0)
|
||||
dev_err(dev, "MAS reservation failed: %d\n", result);
|
||||
/* Revert to PCA even though SET IP MAS failed. */
|
||||
wlp_tx_hdr_set_delivery_id_type(&i1480u->options.def_tx_hdr,
|
||||
i1480u->options.pca_base_priority);
|
||||
wlp_tx_hdr_set_rts_cts(&i1480u->options.def_tx_hdr, 1);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "unexpected WLP reservation state: %s (%d).\n",
|
||||
uwb_rsv_state_str(rsv->state), rsv->state);
|
||||
break;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&i1480u->options.mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Called on 'ifconfig up'
|
||||
*/
|
||||
int i1480u_open(struct net_device *net_dev)
|
||||
{
|
||||
int result;
|
||||
struct i1480u *i1480u = netdev_priv(net_dev);
|
||||
struct wlp *wlp = &i1480u->wlp;
|
||||
struct uwb_rc *rc;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
|
||||
rc = wlp->rc;
|
||||
result = i1480u_rx_setup(i1480u); /* Alloc RX stuff */
|
||||
if (result < 0)
|
||||
goto error_rx_setup;
|
||||
netif_wake_queue(net_dev);
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
result = usb_submit_urb(i1480u->notif_urb, GFP_KERNEL);;
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Can't submit notification URB: %d\n", result);
|
||||
goto error_notif_urb_submit;
|
||||
}
|
||||
#endif
|
||||
i1480u->uwb_notifs_handler.cb = i1480u_uwb_notifs_cb;
|
||||
i1480u->uwb_notifs_handler.data = i1480u;
|
||||
if (uwb_bg_joined(rc))
|
||||
netif_carrier_on(net_dev);
|
||||
else
|
||||
netif_carrier_off(net_dev);
|
||||
uwb_notifs_register(rc, &i1480u->uwb_notifs_handler);
|
||||
/* Interface is up with an address, now we can create WSS */
|
||||
result = wlp_wss_setup(net_dev, &wlp->wss);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Can't create WSS: %d. \n", result);
|
||||
goto error_notif_deregister;
|
||||
}
|
||||
return 0;
|
||||
error_notif_deregister:
|
||||
uwb_notifs_deregister(rc, &i1480u->uwb_notifs_handler);
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
error_notif_urb_submit:
|
||||
#endif
|
||||
netif_stop_queue(net_dev);
|
||||
i1480u_rx_release(i1480u);
|
||||
error_rx_setup:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called on 'ifconfig down'
|
||||
*/
|
||||
int i1480u_stop(struct net_device *net_dev)
|
||||
{
|
||||
struct i1480u *i1480u = netdev_priv(net_dev);
|
||||
struct wlp *wlp = &i1480u->wlp;
|
||||
struct uwb_rc *rc = wlp->rc;
|
||||
|
||||
BUG_ON(wlp->rc == NULL);
|
||||
wlp_wss_remove(&wlp->wss);
|
||||
uwb_notifs_deregister(rc, &i1480u->uwb_notifs_handler);
|
||||
netif_carrier_off(net_dev);
|
||||
#ifdef i1480u_FLOW_CONTROL
|
||||
usb_kill_urb(i1480u->notif_urb);
|
||||
#endif
|
||||
netif_stop_queue(net_dev);
|
||||
i1480u_rx_release(i1480u);
|
||||
i1480u_tx_release(i1480u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/** Report statistics */
|
||||
struct net_device_stats *i1480u_get_stats(struct net_device *net_dev)
|
||||
{
|
||||
struct i1480u *i1480u = netdev_priv(net_dev);
|
||||
return &i1480u->stats;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Change the interface config--we probably don't have to do anything.
|
||||
*/
|
||||
int i1480u_set_config(struct net_device *net_dev, struct ifmap *map)
|
||||
{
|
||||
int result;
|
||||
struct i1480u *i1480u = netdev_priv(net_dev);
|
||||
BUG_ON(i1480u->wlp.rc == NULL);
|
||||
result = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the MTU of the interface
|
||||
*/
|
||||
int i1480u_change_mtu(struct net_device *net_dev, int mtu)
|
||||
{
|
||||
static union {
|
||||
struct wlp_tx_hdr tx;
|
||||
struct wlp_rx_hdr rx;
|
||||
} i1480u_all_hdrs;
|
||||
|
||||
if (mtu < ETH_HLEN) /* We encap eth frames */
|
||||
return -ERANGE;
|
||||
if (mtu > 4000 - sizeof(i1480u_all_hdrs))
|
||||
return -ERANGE;
|
||||
net_dev->mtu = mtu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback function to handle events from UWB
|
||||
* When we see other devices we know the carrier is ok,
|
||||
* if we are the only device in the beacon group we set the carrier
|
||||
* state to off.
|
||||
* */
|
||||
void i1480u_uwb_notifs_cb(void *data, struct uwb_dev *uwb_dev,
|
||||
enum uwb_notifs event)
|
||||
{
|
||||
struct i1480u *i1480u = data;
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
switch (event) {
|
||||
case UWB_NOTIF_BG_JOIN:
|
||||
netif_carrier_on(net_dev);
|
||||
dev_info(dev, "Link is up\n");
|
||||
break;
|
||||
case UWB_NOTIF_BG_LEAVE:
|
||||
netif_carrier_off(net_dev);
|
||||
dev_info(dev, "Link is down\n");
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "don't know how to handle event %d from uwb\n",
|
||||
event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the network queue
|
||||
*
|
||||
* Enable WLP substack to stop network queue. We also set the flow control
|
||||
* threshold at this time to prevent the flow control from restarting the
|
||||
* queue.
|
||||
*
|
||||
* we are loosing the current threshold value here ... FIXME?
|
||||
*/
|
||||
void i1480u_stop_queue(struct wlp *wlp)
|
||||
{
|
||||
struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp);
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
i1480u->tx_inflight.threshold = 0;
|
||||
netif_stop_queue(net_dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the network queue
|
||||
*
|
||||
* Enable WLP substack to start network queue. Also re-enable the flow
|
||||
* control to manage the queue again.
|
||||
*
|
||||
* We re-enable the flow control by storing the default threshold in the
|
||||
* flow control threshold. This means that if the user modified the
|
||||
* threshold before the queue was stopped and restarted that information
|
||||
* will be lost. FIXME?
|
||||
*/
|
||||
void i1480u_start_queue(struct wlp *wlp)
|
||||
{
|
||||
struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp);
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
i1480u->tx_inflight.threshold = i1480u_TX_INFLIGHT_THRESHOLD;
|
||||
netif_start_queue(net_dev);
|
||||
}
|
|
@ -0,0 +1,486 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: WLP interface
|
||||
* Driver for the Linux Network stack.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* i1480u's RX handling is simple. i1480u will send the received
|
||||
* network packets broken up in fragments; 1 to N fragments make a
|
||||
* packet, we assemble them together and deliver the packet with netif_rx().
|
||||
*
|
||||
* Beacuse each USB transfer is a *single* fragment (except when the
|
||||
* transfer contains a first fragment), each URB called thus
|
||||
* back contains one or two fragments. So we queue N URBs, each with its own
|
||||
* fragment buffer. When a URB is done, we process it (adding to the
|
||||
* current skb from the fragment buffer until complete). Once
|
||||
* processed, we requeue the URB. There is always a bunch of URBs
|
||||
* ready to take data, so the intergap should be minimal.
|
||||
*
|
||||
* An URB's transfer buffer is the data field of a socket buffer. This
|
||||
* reduces copying as data can be passed directly to network layer. If a
|
||||
* complete packet or 1st fragment is received the URB's transfer buffer is
|
||||
* taken away from it and used to send data to the network layer. In this
|
||||
* case a new transfer buffer is allocated to the URB before being requeued.
|
||||
* If a "NEXT" or "LAST" fragment is received, the fragment contents is
|
||||
* appended to the RX packet under construction and the transfer buffer
|
||||
* is reused. To be able to use this buffer to assemble complete packets
|
||||
* we set each buffer's size to that of the MAX ethernet packet that can
|
||||
* be received. There is thus room for improvement in memory usage.
|
||||
*
|
||||
* When the max tx fragment size increases, we should be able to read
|
||||
* data into the skbs directly with very simple code.
|
||||
*
|
||||
* ROADMAP:
|
||||
*
|
||||
* ENTRY POINTS:
|
||||
*
|
||||
* i1480u_rx_setup(): setup RX context [from i1480u_open()]
|
||||
*
|
||||
* i1480u_rx_release(): release RX context [from i1480u_stop()]
|
||||
*
|
||||
* i1480u_rx_cb(): called when the RX USB URB receives a
|
||||
* packet. It removes the header and pushes it up
|
||||
* the Linux netdev stack with netif_rx().
|
||||
*
|
||||
* i1480u_rx_buffer()
|
||||
* i1480u_drop() and i1480u_fix()
|
||||
* i1480u_skb_deliver
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include "i1480u-wlp.h"
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
|
||||
/**
|
||||
* Setup the RX context
|
||||
*
|
||||
* Each URB is provided with a transfer_buffer that is the data field
|
||||
* of a new socket buffer.
|
||||
*/
|
||||
int i1480u_rx_setup(struct i1480u *i1480u)
|
||||
{
|
||||
int result, cnt;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
struct usb_endpoint_descriptor *epd;
|
||||
struct sk_buff *skb;
|
||||
|
||||
/* Alloc RX stuff */
|
||||
i1480u->rx_skb = NULL; /* not in process of receiving packet */
|
||||
result = -ENOMEM;
|
||||
epd = &i1480u->usb_iface->cur_altsetting->endpoint[1].desc;
|
||||
for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) {
|
||||
struct i1480u_rx_buf *rx_buf = &i1480u->rx_buf[cnt];
|
||||
rx_buf->i1480u = i1480u;
|
||||
skb = dev_alloc_skb(i1480u_MAX_RX_PKT_SIZE);
|
||||
if (!skb) {
|
||||
dev_err(dev,
|
||||
"RX: cannot allocate RX buffer %d\n", cnt);
|
||||
result = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
skb->dev = net_dev;
|
||||
skb->ip_summed = CHECKSUM_NONE;
|
||||
skb_reserve(skb, 2);
|
||||
rx_buf->data = skb;
|
||||
rx_buf->urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (unlikely(rx_buf->urb == NULL)) {
|
||||
dev_err(dev, "RX: cannot allocate URB %d\n", cnt);
|
||||
result = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
usb_fill_bulk_urb(rx_buf->urb, i1480u->usb_dev,
|
||||
usb_rcvbulkpipe(i1480u->usb_dev, epd->bEndpointAddress),
|
||||
rx_buf->data->data, i1480u_MAX_RX_PKT_SIZE - 2,
|
||||
i1480u_rx_cb, rx_buf);
|
||||
result = usb_submit_urb(rx_buf->urb, GFP_NOIO);
|
||||
if (unlikely(result < 0)) {
|
||||
dev_err(dev, "RX: cannot submit URB %d: %d\n",
|
||||
cnt, result);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
error:
|
||||
i1480u_rx_release(i1480u);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/** Release resources associated to the rx context */
|
||||
void i1480u_rx_release(struct i1480u *i1480u)
|
||||
{
|
||||
int cnt;
|
||||
for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) {
|
||||
if (i1480u->rx_buf[cnt].data)
|
||||
dev_kfree_skb(i1480u->rx_buf[cnt].data);
|
||||
if (i1480u->rx_buf[cnt].urb) {
|
||||
usb_kill_urb(i1480u->rx_buf[cnt].urb);
|
||||
usb_free_urb(i1480u->rx_buf[cnt].urb);
|
||||
}
|
||||
}
|
||||
if (i1480u->rx_skb != NULL)
|
||||
dev_kfree_skb(i1480u->rx_skb);
|
||||
}
|
||||
|
||||
static
|
||||
void i1480u_rx_unlink_urbs(struct i1480u *i1480u)
|
||||
{
|
||||
int cnt;
|
||||
for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) {
|
||||
if (i1480u->rx_buf[cnt].urb)
|
||||
usb_unlink_urb(i1480u->rx_buf[cnt].urb);
|
||||
}
|
||||
}
|
||||
|
||||
/** Fix an out-of-sequence packet */
|
||||
#define i1480u_fix(i1480u, msg...) \
|
||||
do { \
|
||||
if (printk_ratelimit()) \
|
||||
dev_err(&i1480u->usb_iface->dev, msg); \
|
||||
dev_kfree_skb_irq(i1480u->rx_skb); \
|
||||
i1480u->rx_skb = NULL; \
|
||||
i1480u->rx_untd_pkt_size = 0; \
|
||||
} while (0)
|
||||
|
||||
|
||||
/** Drop an out-of-sequence packet */
|
||||
#define i1480u_drop(i1480u, msg...) \
|
||||
do { \
|
||||
if (printk_ratelimit()) \
|
||||
dev_err(&i1480u->usb_iface->dev, msg); \
|
||||
i1480u->stats.rx_dropped++; \
|
||||
} while (0)
|
||||
|
||||
|
||||
|
||||
|
||||
/** Finalizes setting up the SKB and delivers it
|
||||
*
|
||||
* We first pass the incoming frame to WLP substack for verification. It
|
||||
* may also be a WLP association frame in which case WLP will take over the
|
||||
* processing. If WLP does not take it over it will still verify it, if the
|
||||
* frame is invalid the skb will be freed by WLP and we will not continue
|
||||
* parsing.
|
||||
* */
|
||||
static
|
||||
void i1480u_skb_deliver(struct i1480u *i1480u)
|
||||
{
|
||||
int should_parse;
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
|
||||
d_printf(6, dev, "RX delivered pre skb(%p), %u bytes\n",
|
||||
i1480u->rx_skb, i1480u->rx_skb->len);
|
||||
d_dump(7, dev, i1480u->rx_skb->data, i1480u->rx_skb->len);
|
||||
should_parse = wlp_receive_frame(dev, &i1480u->wlp, i1480u->rx_skb,
|
||||
&i1480u->rx_srcaddr);
|
||||
if (!should_parse)
|
||||
goto out;
|
||||
i1480u->rx_skb->protocol = eth_type_trans(i1480u->rx_skb, net_dev);
|
||||
d_printf(5, dev, "RX delivered skb(%p), %u bytes\n",
|
||||
i1480u->rx_skb, i1480u->rx_skb->len);
|
||||
d_dump(7, dev, i1480u->rx_skb->data,
|
||||
i1480u->rx_skb->len > 72 ? 72 : i1480u->rx_skb->len);
|
||||
i1480u->stats.rx_packets++;
|
||||
i1480u->stats.rx_bytes += i1480u->rx_untd_pkt_size;
|
||||
net_dev->last_rx = jiffies;
|
||||
/* FIXME: flow control: check netif_rx() retval */
|
||||
|
||||
netif_rx(i1480u->rx_skb); /* deliver */
|
||||
out:
|
||||
i1480u->rx_skb = NULL;
|
||||
i1480u->rx_untd_pkt_size = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process a buffer of data received from the USB RX endpoint
|
||||
*
|
||||
* First fragment arrives with next or last fragment. All other fragments
|
||||
* arrive alone.
|
||||
*
|
||||
* /me hates long functions.
|
||||
*/
|
||||
static
|
||||
void i1480u_rx_buffer(struct i1480u_rx_buf *rx_buf)
|
||||
{
|
||||
unsigned pkt_completed = 0; /* !0 when we got all pkt fragments */
|
||||
size_t untd_hdr_size, untd_frg_size;
|
||||
size_t i1480u_hdr_size;
|
||||
struct wlp_rx_hdr *i1480u_hdr = NULL;
|
||||
|
||||
struct i1480u *i1480u = rx_buf->i1480u;
|
||||
struct sk_buff *skb = rx_buf->data;
|
||||
int size_left = rx_buf->urb->actual_length;
|
||||
void *ptr = rx_buf->urb->transfer_buffer; /* also rx_buf->data->data */
|
||||
struct untd_hdr *untd_hdr;
|
||||
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
struct sk_buff *new_skb;
|
||||
|
||||
#if 0
|
||||
dev_fnstart(dev,
|
||||
"(i1480u %p ptr %p size_left %zu)\n", i1480u, ptr, size_left);
|
||||
dev_err(dev, "RX packet, %zu bytes\n", size_left);
|
||||
dump_bytes(dev, ptr, size_left);
|
||||
#endif
|
||||
i1480u_hdr_size = sizeof(struct wlp_rx_hdr);
|
||||
|
||||
while (size_left > 0) {
|
||||
if (pkt_completed) {
|
||||
i1480u_drop(i1480u, "RX: fragment follows completed"
|
||||
"packet in same buffer. Dropping\n");
|
||||
break;
|
||||
}
|
||||
untd_hdr = ptr;
|
||||
if (size_left < sizeof(*untd_hdr)) { /* Check the UNTD header */
|
||||
i1480u_drop(i1480u, "RX: short UNTD header! Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
if (unlikely(untd_hdr_rx_tx(untd_hdr) == 0)) { /* Paranoia: TX set? */
|
||||
i1480u_drop(i1480u, "RX: TX bit set! Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
switch (untd_hdr_type(untd_hdr)) { /* Check the UNTD header type */
|
||||
case i1480u_PKT_FRAG_1ST: {
|
||||
struct untd_hdr_1st *untd_hdr_1st = (void *) untd_hdr;
|
||||
dev_dbg(dev, "1st fragment\n");
|
||||
untd_hdr_size = sizeof(struct untd_hdr_1st);
|
||||
if (i1480u->rx_skb != NULL)
|
||||
i1480u_fix(i1480u, "RX: 1st fragment out of "
|
||||
"sequence! Fixing\n");
|
||||
if (size_left < untd_hdr_size + i1480u_hdr_size) {
|
||||
i1480u_drop(i1480u, "RX: short 1st fragment! "
|
||||
"Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
i1480u->rx_untd_pkt_size = le16_to_cpu(untd_hdr->len)
|
||||
- i1480u_hdr_size;
|
||||
untd_frg_size = le16_to_cpu(untd_hdr_1st->fragment_len);
|
||||
if (size_left < untd_hdr_size + untd_frg_size) {
|
||||
i1480u_drop(i1480u,
|
||||
"RX: short payload! Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
i1480u->rx_skb = skb;
|
||||
i1480u_hdr = (void *) untd_hdr_1st + untd_hdr_size;
|
||||
i1480u->rx_srcaddr = i1480u_hdr->srcaddr;
|
||||
skb_put(i1480u->rx_skb, untd_hdr_size + untd_frg_size);
|
||||
skb_pull(i1480u->rx_skb, untd_hdr_size + i1480u_hdr_size);
|
||||
stats_add_sample(&i1480u->lqe_stats, (s8) i1480u_hdr->LQI - 7);
|
||||
stats_add_sample(&i1480u->rssi_stats, i1480u_hdr->RSSI + 18);
|
||||
rx_buf->data = NULL; /* need to create new buffer */
|
||||
break;
|
||||
}
|
||||
case i1480u_PKT_FRAG_NXT: {
|
||||
dev_dbg(dev, "nxt fragment\n");
|
||||
untd_hdr_size = sizeof(struct untd_hdr_rst);
|
||||
if (i1480u->rx_skb == NULL) {
|
||||
i1480u_drop(i1480u, "RX: next fragment out of "
|
||||
"sequence! Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
if (size_left < untd_hdr_size) {
|
||||
i1480u_drop(i1480u, "RX: short NXT fragment! "
|
||||
"Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
untd_frg_size = le16_to_cpu(untd_hdr->len);
|
||||
if (size_left < untd_hdr_size + untd_frg_size) {
|
||||
i1480u_drop(i1480u,
|
||||
"RX: short payload! Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
memmove(skb_put(i1480u->rx_skb, untd_frg_size),
|
||||
ptr + untd_hdr_size, untd_frg_size);
|
||||
break;
|
||||
}
|
||||
case i1480u_PKT_FRAG_LST: {
|
||||
dev_dbg(dev, "Lst fragment\n");
|
||||
untd_hdr_size = sizeof(struct untd_hdr_rst);
|
||||
if (i1480u->rx_skb == NULL) {
|
||||
i1480u_drop(i1480u, "RX: last fragment out of "
|
||||
"sequence! Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
if (size_left < untd_hdr_size) {
|
||||
i1480u_drop(i1480u, "RX: short LST fragment! "
|
||||
"Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
untd_frg_size = le16_to_cpu(untd_hdr->len);
|
||||
if (size_left < untd_frg_size + untd_hdr_size) {
|
||||
i1480u_drop(i1480u,
|
||||
"RX: short payload! Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
memmove(skb_put(i1480u->rx_skb, untd_frg_size),
|
||||
ptr + untd_hdr_size, untd_frg_size);
|
||||
pkt_completed = 1;
|
||||
break;
|
||||
}
|
||||
case i1480u_PKT_FRAG_CMP: {
|
||||
dev_dbg(dev, "cmp fragment\n");
|
||||
untd_hdr_size = sizeof(struct untd_hdr_cmp);
|
||||
if (i1480u->rx_skb != NULL)
|
||||
i1480u_fix(i1480u, "RX: fix out-of-sequence CMP"
|
||||
" fragment!\n");
|
||||
if (size_left < untd_hdr_size + i1480u_hdr_size) {
|
||||
i1480u_drop(i1480u, "RX: short CMP fragment! "
|
||||
"Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
i1480u->rx_untd_pkt_size = le16_to_cpu(untd_hdr->len);
|
||||
untd_frg_size = i1480u->rx_untd_pkt_size;
|
||||
if (size_left < i1480u->rx_untd_pkt_size + untd_hdr_size) {
|
||||
i1480u_drop(i1480u,
|
||||
"RX: short payload! Dropping\n");
|
||||
goto out;
|
||||
}
|
||||
i1480u->rx_skb = skb;
|
||||
i1480u_hdr = (void *) untd_hdr + untd_hdr_size;
|
||||
i1480u->rx_srcaddr = i1480u_hdr->srcaddr;
|
||||
stats_add_sample(&i1480u->lqe_stats, (s8) i1480u_hdr->LQI - 7);
|
||||
stats_add_sample(&i1480u->rssi_stats, i1480u_hdr->RSSI + 18);
|
||||
skb_put(i1480u->rx_skb, untd_hdr_size + i1480u->rx_untd_pkt_size);
|
||||
skb_pull(i1480u->rx_skb, untd_hdr_size + i1480u_hdr_size);
|
||||
rx_buf->data = NULL; /* for hand off skb to network stack */
|
||||
pkt_completed = 1;
|
||||
i1480u->rx_untd_pkt_size -= i1480u_hdr_size; /* accurate stat */
|
||||
break;
|
||||
}
|
||||
default:
|
||||
i1480u_drop(i1480u, "RX: unknown packet type %u! "
|
||||
"Dropping\n", untd_hdr_type(untd_hdr));
|
||||
goto out;
|
||||
}
|
||||
size_left -= untd_hdr_size + untd_frg_size;
|
||||
if (size_left > 0)
|
||||
ptr += untd_hdr_size + untd_frg_size;
|
||||
}
|
||||
if (pkt_completed)
|
||||
i1480u_skb_deliver(i1480u);
|
||||
out:
|
||||
/* recreate needed RX buffers*/
|
||||
if (rx_buf->data == NULL) {
|
||||
/* buffer is being used to receive packet, create new */
|
||||
new_skb = dev_alloc_skb(i1480u_MAX_RX_PKT_SIZE);
|
||||
if (!new_skb) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev,
|
||||
"RX: cannot allocate RX buffer\n");
|
||||
} else {
|
||||
new_skb->dev = net_dev;
|
||||
new_skb->ip_summed = CHECKSUM_NONE;
|
||||
skb_reserve(new_skb, 2);
|
||||
rx_buf->data = new_skb;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when an RX URB has finished receiving or has found some kind
|
||||
* of error condition.
|
||||
*
|
||||
* LIMITATIONS:
|
||||
*
|
||||
* - We read USB-transfers, each transfer contains a SINGLE fragment
|
||||
* (can contain a complete packet, or a 1st, next, or last fragment
|
||||
* of a packet).
|
||||
* Looks like a transfer can contain more than one fragment (07/18/06)
|
||||
*
|
||||
* - Each transfer buffer is the size of the maximum packet size (minus
|
||||
* headroom), i1480u_MAX_PKT_SIZE - 2
|
||||
*
|
||||
* - We always read the full USB-transfer, no partials.
|
||||
*
|
||||
* - Each transfer is read directly into a skb. This skb will be used to
|
||||
* send data to the upper layers if it is the first fragment or a complete
|
||||
* packet. In the other cases the data will be copied from the skb to
|
||||
* another skb that is being prepared for the upper layers from a prev
|
||||
* first fragment.
|
||||
*
|
||||
* It is simply too much of a pain. Gosh, there should be a unified
|
||||
* SG infrastructure for *everything* [so that I could declare a SG
|
||||
* buffer, pass it to USB for receiving, append some space to it if
|
||||
* I wish, receive more until I have the whole chunk, adapt
|
||||
* pointers on each fragment to remove hardware headers and then
|
||||
* attach that to an skbuff and netif_rx()].
|
||||
*/
|
||||
void i1480u_rx_cb(struct urb *urb)
|
||||
{
|
||||
int result;
|
||||
int do_parse_buffer = 1;
|
||||
struct i1480u_rx_buf *rx_buf = urb->context;
|
||||
struct i1480u *i1480u = rx_buf->i1480u;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
unsigned long flags;
|
||||
u8 rx_buf_idx = rx_buf - i1480u->rx_buf;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
break;
|
||||
case -ECONNRESET: /* Not an error, but a controlled situation; */
|
||||
case -ENOENT: /* (we killed the URB)...so, no broadcast */
|
||||
case -ESHUTDOWN: /* going away! */
|
||||
dev_err(dev, "RX URB[%u]: goind down %d\n",
|
||||
rx_buf_idx, urb->status);
|
||||
goto error;
|
||||
default:
|
||||
dev_err(dev, "RX URB[%u]: unknown status %d\n",
|
||||
rx_buf_idx, urb->status);
|
||||
if (edc_inc(&i1480u->rx_errors, EDC_MAX_ERRORS,
|
||||
EDC_ERROR_TIMEFRAME)) {
|
||||
dev_err(dev, "RX: max acceptable errors exceeded,"
|
||||
" resetting device.\n");
|
||||
i1480u_rx_unlink_urbs(i1480u);
|
||||
wlp_reset_all(&i1480u->wlp);
|
||||
goto error;
|
||||
}
|
||||
do_parse_buffer = 0;
|
||||
break;
|
||||
}
|
||||
spin_lock_irqsave(&i1480u->lock, flags);
|
||||
/* chew the data fragments, extract network packets */
|
||||
if (do_parse_buffer) {
|
||||
i1480u_rx_buffer(rx_buf);
|
||||
if (rx_buf->data) {
|
||||
rx_buf->urb->transfer_buffer = rx_buf->data->data;
|
||||
result = usb_submit_urb(rx_buf->urb, GFP_ATOMIC);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "RX URB[%u]: cannot submit %d\n",
|
||||
rx_buf_idx, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&i1480u->lock, flags);
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: WLP interface
|
||||
* Sysfs interfaces
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*/
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/uwb/debug.h>
|
||||
#include <linux/device.h>
|
||||
#include "i1480u-wlp.h"
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @dev: Class device from the net_device; assumed refcnted.
|
||||
*
|
||||
* Yes, I don't lock--we assume it is refcounted and I am getting a
|
||||
* single byte value that is kind of atomic to read.
|
||||
*/
|
||||
ssize_t uwb_phy_rate_show(const struct wlp_options *options, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n",
|
||||
wlp_tx_hdr_phy_rate(&options->def_tx_hdr));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_phy_rate_show);
|
||||
|
||||
|
||||
ssize_t uwb_phy_rate_store(struct wlp_options *options,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
unsigned rate;
|
||||
|
||||
result = sscanf(buf, "%u\n", &rate);
|
||||
if (result != 1) {
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
result = -EINVAL;
|
||||
if (rate >= UWB_PHY_RATE_INVALID)
|
||||
goto out;
|
||||
wlp_tx_hdr_set_phy_rate(&options->def_tx_hdr, rate);
|
||||
result = 0;
|
||||
out:
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_phy_rate_store);
|
||||
|
||||
|
||||
ssize_t uwb_rts_cts_show(const struct wlp_options *options, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n",
|
||||
wlp_tx_hdr_rts_cts(&options->def_tx_hdr));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rts_cts_show);
|
||||
|
||||
|
||||
ssize_t uwb_rts_cts_store(struct wlp_options *options,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
unsigned value;
|
||||
|
||||
result = sscanf(buf, "%u\n", &value);
|
||||
if (result != 1) {
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
result = -EINVAL;
|
||||
wlp_tx_hdr_set_rts_cts(&options->def_tx_hdr, !!value);
|
||||
result = 0;
|
||||
out:
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rts_cts_store);
|
||||
|
||||
|
||||
ssize_t uwb_ack_policy_show(const struct wlp_options *options, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n",
|
||||
wlp_tx_hdr_ack_policy(&options->def_tx_hdr));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_ack_policy_show);
|
||||
|
||||
|
||||
ssize_t uwb_ack_policy_store(struct wlp_options *options,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
unsigned value;
|
||||
|
||||
result = sscanf(buf, "%u\n", &value);
|
||||
if (result != 1 || value > UWB_ACK_B_REQ) {
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
wlp_tx_hdr_set_ack_policy(&options->def_tx_hdr, value);
|
||||
result = 0;
|
||||
out:
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_ack_policy_store);
|
||||
|
||||
|
||||
/**
|
||||
* Show the PCA base priority.
|
||||
*
|
||||
* We can access without locking, as the value is (for now) orthogonal
|
||||
* to other values.
|
||||
*/
|
||||
ssize_t uwb_pca_base_priority_show(const struct wlp_options *options,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n",
|
||||
options->pca_base_priority);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_pca_base_priority_show);
|
||||
|
||||
|
||||
/**
|
||||
* Set the PCA base priority.
|
||||
*
|
||||
* We can access without locking, as the value is (for now) orthogonal
|
||||
* to other values.
|
||||
*/
|
||||
ssize_t uwb_pca_base_priority_store(struct wlp_options *options,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result = -EINVAL;
|
||||
u8 pca_base_priority;
|
||||
|
||||
result = sscanf(buf, "%hhu\n", &pca_base_priority);
|
||||
if (result != 1) {
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
result = -EINVAL;
|
||||
if (pca_base_priority >= 8)
|
||||
goto out;
|
||||
options->pca_base_priority = pca_base_priority;
|
||||
/* Update TX header if we are currently using PCA. */
|
||||
if (result >= 0 && (wlp_tx_hdr_delivery_id_type(&options->def_tx_hdr) & WLP_DRP) == 0)
|
||||
wlp_tx_hdr_set_delivery_id_type(&options->def_tx_hdr, options->pca_base_priority);
|
||||
result = 0;
|
||||
out:
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_pca_base_priority_store);
|
||||
|
||||
/**
|
||||
* Show current inflight values
|
||||
*
|
||||
* Will print the current MAX and THRESHOLD values for the basic flow
|
||||
* control. In addition it will report how many times the TX queue needed
|
||||
* to be restarted since the last time this query was made.
|
||||
*/
|
||||
static ssize_t wlp_tx_inflight_show(struct i1480u_tx_inflight *inflight,
|
||||
char *buf)
|
||||
{
|
||||
ssize_t result;
|
||||
unsigned long sec_elapsed = (jiffies - inflight->restart_ts)/HZ;
|
||||
unsigned long restart_count = atomic_read(&inflight->restart_count);
|
||||
|
||||
result = scnprintf(buf, PAGE_SIZE, "%lu %lu %d %lu %lu %lu\n"
|
||||
"#read: threshold max inflight_count restarts "
|
||||
"seconds restarts/sec\n"
|
||||
"#write: threshold max\n",
|
||||
inflight->threshold, inflight->max,
|
||||
atomic_read(&inflight->count),
|
||||
restart_count, sec_elapsed,
|
||||
sec_elapsed == 0 ? 0 : restart_count/sec_elapsed);
|
||||
inflight->restart_ts = jiffies;
|
||||
atomic_set(&inflight->restart_count, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
static
|
||||
ssize_t wlp_tx_inflight_store(struct i1480u_tx_inflight *inflight,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
unsigned long in_threshold, in_max;
|
||||
ssize_t result;
|
||||
result = sscanf(buf, "%lu %lu", &in_threshold, &in_max);
|
||||
if (result != 2)
|
||||
return -EINVAL;
|
||||
if (in_max <= in_threshold)
|
||||
return -EINVAL;
|
||||
inflight->max = in_max;
|
||||
inflight->threshold = in_threshold;
|
||||
return size;
|
||||
}
|
||||
/*
|
||||
* Glue (or function adaptors) for accesing info on sysfs
|
||||
*
|
||||
* [we need this indirection because the PCI driver does almost the
|
||||
* same]
|
||||
*
|
||||
* Linux 2.6.21 changed how 'struct netdevice' does attributes (from
|
||||
* having a 'struct class_dev' to having a 'struct device'). That is
|
||||
* quite of a pain.
|
||||
*
|
||||
* So we try to abstract that here. i1480u_SHOW() and i1480u_STORE()
|
||||
* create adaptors for extracting the 'struct i1480u' from a 'struct
|
||||
* dev' and calling a function for doing a sysfs operation (as we have
|
||||
* them factorized already). i1480u_ATTR creates the attribute file
|
||||
* (CLASS_DEVICE_ATTR or DEVICE_ATTR) and i1480u_ATTR_NAME produces a
|
||||
* class_device_attr_NAME or device_attr_NAME (for group registration).
|
||||
*/
|
||||
#include <linux/version.h>
|
||||
|
||||
#define i1480u_SHOW(name, fn, param) \
|
||||
static ssize_t i1480u_show_##name(struct device *dev, \
|
||||
struct device_attribute *attr,\
|
||||
char *buf) \
|
||||
{ \
|
||||
struct i1480u *i1480u = netdev_priv(to_net_dev(dev)); \
|
||||
return fn(&i1480u->param, buf); \
|
||||
}
|
||||
|
||||
#define i1480u_STORE(name, fn, param) \
|
||||
static ssize_t i1480u_store_##name(struct device *dev, \
|
||||
struct device_attribute *attr,\
|
||||
const char *buf, size_t size)\
|
||||
{ \
|
||||
struct i1480u *i1480u = netdev_priv(to_net_dev(dev)); \
|
||||
return fn(&i1480u->param, buf, size); \
|
||||
}
|
||||
|
||||
#define i1480u_ATTR(name, perm) static DEVICE_ATTR(name, perm, \
|
||||
i1480u_show_##name,\
|
||||
i1480u_store_##name)
|
||||
|
||||
#define i1480u_ATTR_SHOW(name) static DEVICE_ATTR(name, \
|
||||
S_IRUGO, \
|
||||
i1480u_show_##name, NULL)
|
||||
|
||||
#define i1480u_ATTR_NAME(a) (dev_attr_##a)
|
||||
|
||||
|
||||
/*
|
||||
* Sysfs adaptors
|
||||
*/
|
||||
i1480u_SHOW(uwb_phy_rate, uwb_phy_rate_show, options);
|
||||
i1480u_STORE(uwb_phy_rate, uwb_phy_rate_store, options);
|
||||
i1480u_ATTR(uwb_phy_rate, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(uwb_rts_cts, uwb_rts_cts_show, options);
|
||||
i1480u_STORE(uwb_rts_cts, uwb_rts_cts_store, options);
|
||||
i1480u_ATTR(uwb_rts_cts, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(uwb_ack_policy, uwb_ack_policy_show, options);
|
||||
i1480u_STORE(uwb_ack_policy, uwb_ack_policy_store, options);
|
||||
i1480u_ATTR(uwb_ack_policy, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(uwb_pca_base_priority, uwb_pca_base_priority_show, options);
|
||||
i1480u_STORE(uwb_pca_base_priority, uwb_pca_base_priority_store, options);
|
||||
i1480u_ATTR(uwb_pca_base_priority, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_eda, wlp_eda_show, wlp);
|
||||
i1480u_STORE(wlp_eda, wlp_eda_store, wlp);
|
||||
i1480u_ATTR(wlp_eda, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_uuid, wlp_uuid_show, wlp);
|
||||
i1480u_STORE(wlp_uuid, wlp_uuid_store, wlp);
|
||||
i1480u_ATTR(wlp_uuid, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_name, wlp_dev_name_show, wlp);
|
||||
i1480u_STORE(wlp_dev_name, wlp_dev_name_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_name, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_manufacturer, wlp_dev_manufacturer_show, wlp);
|
||||
i1480u_STORE(wlp_dev_manufacturer, wlp_dev_manufacturer_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_manufacturer, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_model_name, wlp_dev_model_name_show, wlp);
|
||||
i1480u_STORE(wlp_dev_model_name, wlp_dev_model_name_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_model_name, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_model_nr, wlp_dev_model_nr_show, wlp);
|
||||
i1480u_STORE(wlp_dev_model_nr, wlp_dev_model_nr_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_model_nr, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_serial, wlp_dev_serial_show, wlp);
|
||||
i1480u_STORE(wlp_dev_serial, wlp_dev_serial_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_serial, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_prim_category, wlp_dev_prim_category_show, wlp);
|
||||
i1480u_STORE(wlp_dev_prim_category, wlp_dev_prim_category_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_prim_category, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_prim_OUI, wlp_dev_prim_OUI_show, wlp);
|
||||
i1480u_STORE(wlp_dev_prim_OUI, wlp_dev_prim_OUI_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_prim_OUI, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_prim_OUI_sub, wlp_dev_prim_OUI_sub_show, wlp);
|
||||
i1480u_STORE(wlp_dev_prim_OUI_sub, wlp_dev_prim_OUI_sub_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_prim_OUI_sub, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_dev_prim_subcat, wlp_dev_prim_subcat_show, wlp);
|
||||
i1480u_STORE(wlp_dev_prim_subcat, wlp_dev_prim_subcat_store, wlp);
|
||||
i1480u_ATTR(wlp_dev_prim_subcat, S_IRUGO | S_IWUSR);
|
||||
|
||||
i1480u_SHOW(wlp_neighborhood, wlp_neighborhood_show, wlp);
|
||||
i1480u_ATTR_SHOW(wlp_neighborhood);
|
||||
|
||||
i1480u_SHOW(wss_activate, wlp_wss_activate_show, wlp.wss);
|
||||
i1480u_STORE(wss_activate, wlp_wss_activate_store, wlp.wss);
|
||||
i1480u_ATTR(wss_activate, S_IRUGO | S_IWUSR);
|
||||
|
||||
/*
|
||||
* Show the (min, max, avg) Line Quality Estimate (LQE, in dB) as over
|
||||
* the last 256 received WLP frames (ECMA-368 13.3).
|
||||
*
|
||||
* [the -7dB that have to be substracted from the LQI to make the LQE
|
||||
* are already taken into account].
|
||||
*/
|
||||
i1480u_SHOW(wlp_lqe, stats_show, lqe_stats);
|
||||
i1480u_STORE(wlp_lqe, stats_store, lqe_stats);
|
||||
i1480u_ATTR(wlp_lqe, S_IRUGO | S_IWUSR);
|
||||
|
||||
/*
|
||||
* Show the Receive Signal Strength Indicator averaged over all the
|
||||
* received WLP frames (ECMA-368 13.3). Still is not clear what
|
||||
* this value is, but is kind of a percentage of the signal strength
|
||||
* at the antenna.
|
||||
*/
|
||||
i1480u_SHOW(wlp_rssi, stats_show, rssi_stats);
|
||||
i1480u_STORE(wlp_rssi, stats_store, rssi_stats);
|
||||
i1480u_ATTR(wlp_rssi, S_IRUGO | S_IWUSR);
|
||||
|
||||
/**
|
||||
* We maintain a basic flow control counter. "count" how many TX URBs are
|
||||
* outstanding. Only allow "max"
|
||||
* TX URBs to be outstanding. If this value is reached the queue will be
|
||||
* stopped. The queue will be restarted when there are
|
||||
* "threshold" URBs outstanding.
|
||||
*/
|
||||
i1480u_SHOW(wlp_tx_inflight, wlp_tx_inflight_show, tx_inflight);
|
||||
i1480u_STORE(wlp_tx_inflight, wlp_tx_inflight_store, tx_inflight);
|
||||
i1480u_ATTR(wlp_tx_inflight, S_IRUGO | S_IWUSR);
|
||||
|
||||
static struct attribute *i1480u_attrs[] = {
|
||||
&i1480u_ATTR_NAME(uwb_phy_rate).attr,
|
||||
&i1480u_ATTR_NAME(uwb_rts_cts).attr,
|
||||
&i1480u_ATTR_NAME(uwb_ack_policy).attr,
|
||||
&i1480u_ATTR_NAME(uwb_pca_base_priority).attr,
|
||||
&i1480u_ATTR_NAME(wlp_lqe).attr,
|
||||
&i1480u_ATTR_NAME(wlp_rssi).attr,
|
||||
&i1480u_ATTR_NAME(wlp_eda).attr,
|
||||
&i1480u_ATTR_NAME(wlp_uuid).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_name).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_manufacturer).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_model_name).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_model_nr).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_serial).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_prim_category).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_prim_OUI).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_prim_OUI_sub).attr,
|
||||
&i1480u_ATTR_NAME(wlp_dev_prim_subcat).attr,
|
||||
&i1480u_ATTR_NAME(wlp_neighborhood).attr,
|
||||
&i1480u_ATTR_NAME(wss_activate).attr,
|
||||
&i1480u_ATTR_NAME(wlp_tx_inflight).attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group i1480u_attr_group = {
|
||||
.name = NULL, /* we want them in the same directory */
|
||||
.attrs = i1480u_attrs,
|
||||
};
|
||||
|
||||
int i1480u_sysfs_setup(struct i1480u *i1480u)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
result = sysfs_create_group(&i1480u->net_dev->dev.kobj,
|
||||
&i1480u_attr_group);
|
||||
if (result < 0)
|
||||
dev_err(dev, "cannot initialize sysfs attributes: %d\n",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void i1480u_sysfs_release(struct i1480u *i1480u)
|
||||
{
|
||||
sysfs_remove_group(&i1480u->net_dev->dev.kobj,
|
||||
&i1480u_attr_group);
|
||||
}
|
|
@ -0,0 +1,632 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: WLP interface
|
||||
* Deal with TX (massaging data to transmit, handling it)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Transmission engine. Get an skb, create from that a WLP transmit
|
||||
* context, add a WLP TX header (which we keep prefilled in the
|
||||
* device's instance), fill out the target-specific fields and
|
||||
* fire it.
|
||||
*
|
||||
* ROADMAP:
|
||||
*
|
||||
* Entry points:
|
||||
*
|
||||
* i1480u_tx_release(): called by i1480u_disconnect() to release
|
||||
* pending tx contexts.
|
||||
*
|
||||
* i1480u_tx_cb(): callback for TX contexts (USB URBs)
|
||||
* i1480u_tx_destroy():
|
||||
*
|
||||
* i1480u_tx_timeout(): called for timeout handling from the
|
||||
* network stack.
|
||||
*
|
||||
* i1480u_hard_start_xmit(): called for transmitting an skb from
|
||||
* the network stack. Will interact with WLP
|
||||
* substack to verify and prepare frame.
|
||||
* i1480u_xmit_frame(): actual transmission on hardware
|
||||
*
|
||||
* i1480u_tx_create() Creates TX context
|
||||
* i1480u_tx_create_1() For packets in 1 fragment
|
||||
* i1480u_tx_create_n() For packets in >1 fragments
|
||||
*
|
||||
* TODO:
|
||||
*
|
||||
* - FIXME: rewrite using usb_sg_*(), add asynch support to
|
||||
* usb_sg_*(). It might not make too much sense as most of
|
||||
* the times the MTU will be smaller than one page...
|
||||
*/
|
||||
|
||||
#include "i1480u-wlp.h"
|
||||
#define D_LOCAL 5
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
enum {
|
||||
/* This is only for Next and Last TX packets */
|
||||
i1480u_MAX_PL_SIZE = i1480u_MAX_FRG_SIZE
|
||||
- sizeof(struct untd_hdr_rst),
|
||||
};
|
||||
|
||||
/** Free resources allocated to a i1480u tx context. */
|
||||
static
|
||||
void i1480u_tx_free(struct i1480u_tx *wtx)
|
||||
{
|
||||
kfree(wtx->buf);
|
||||
if (wtx->skb)
|
||||
dev_kfree_skb_irq(wtx->skb);
|
||||
usb_free_urb(wtx->urb);
|
||||
kfree(wtx);
|
||||
}
|
||||
|
||||
static
|
||||
void i1480u_tx_destroy(struct i1480u *i1480u, struct i1480u_tx *wtx)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&i1480u->tx_list_lock, flags); /* not active any more */
|
||||
list_del(&wtx->list_node);
|
||||
i1480u_tx_free(wtx);
|
||||
spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
|
||||
}
|
||||
|
||||
static
|
||||
void i1480u_tx_unlink_urbs(struct i1480u *i1480u)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct i1480u_tx *wtx, *next;
|
||||
|
||||
spin_lock_irqsave(&i1480u->tx_list_lock, flags);
|
||||
list_for_each_entry_safe(wtx, next, &i1480u->tx_list, list_node) {
|
||||
usb_unlink_urb(wtx->urb);
|
||||
}
|
||||
spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for a completed tx USB URB.
|
||||
*
|
||||
* TODO:
|
||||
*
|
||||
* - FIXME: recover errors more gracefully
|
||||
* - FIXME: handle NAKs (I dont think they come here) for flow ctl
|
||||
*/
|
||||
static
|
||||
void i1480u_tx_cb(struct urb *urb)
|
||||
{
|
||||
struct i1480u_tx *wtx = urb->context;
|
||||
struct i1480u *i1480u = wtx->i1480u;
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
unsigned long flags;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
spin_lock_irqsave(&i1480u->lock, flags);
|
||||
i1480u->stats.tx_packets++;
|
||||
i1480u->stats.tx_bytes += urb->actual_length;
|
||||
spin_unlock_irqrestore(&i1480u->lock, flags);
|
||||
break;
|
||||
case -ECONNRESET: /* Not an error, but a controlled situation; */
|
||||
case -ENOENT: /* (we killed the URB)...so, no broadcast */
|
||||
dev_dbg(dev, "notif endp: reset/noent %d\n", urb->status);
|
||||
netif_stop_queue(net_dev);
|
||||
break;
|
||||
case -ESHUTDOWN: /* going away! */
|
||||
dev_dbg(dev, "notif endp: down %d\n", urb->status);
|
||||
netif_stop_queue(net_dev);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "TX: unknown URB status %d\n", urb->status);
|
||||
if (edc_inc(&i1480u->tx_errors, EDC_MAX_ERRORS,
|
||||
EDC_ERROR_TIMEFRAME)) {
|
||||
dev_err(dev, "TX: max acceptable errors exceeded."
|
||||
"Reset device.\n");
|
||||
netif_stop_queue(net_dev);
|
||||
i1480u_tx_unlink_urbs(i1480u);
|
||||
wlp_reset_all(&i1480u->wlp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
i1480u_tx_destroy(i1480u, wtx);
|
||||
if (atomic_dec_return(&i1480u->tx_inflight.count)
|
||||
<= i1480u->tx_inflight.threshold
|
||||
&& netif_queue_stopped(net_dev)
|
||||
&& i1480u->tx_inflight.threshold != 0) {
|
||||
if (d_test(2) && printk_ratelimit())
|
||||
d_printf(2, dev, "Restart queue. \n");
|
||||
netif_start_queue(net_dev);
|
||||
atomic_inc(&i1480u->tx_inflight.restart_count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a buffer that doesn't fit in a single fragment, create an
|
||||
* scatter/gather structure for delivery to the USB pipe.
|
||||
*
|
||||
* Implements functionality of i1480u_tx_create().
|
||||
*
|
||||
* @wtx: tx descriptor
|
||||
* @skb: skb to send
|
||||
* @gfp_mask: gfp allocation mask
|
||||
* @returns: Pointer to @wtx if ok, NULL on error.
|
||||
*
|
||||
* Sorry, TOO LONG a function, but breaking it up is kind of hard
|
||||
*
|
||||
* This will break the buffer in chunks smaller than
|
||||
* i1480u_MAX_FRG_SIZE (including the header) and add proper headers
|
||||
* to each:
|
||||
*
|
||||
* 1st header \
|
||||
* i1480 tx header | fragment 1
|
||||
* fragment data /
|
||||
* nxt header \ fragment 2
|
||||
* fragment data /
|
||||
* ..
|
||||
* ..
|
||||
* last header \ fragment 3
|
||||
* last fragment data /
|
||||
*
|
||||
* This does not fill the i1480 TX header, it is left up to the
|
||||
* caller to do that; you can get it from @wtx->wlp_tx_hdr.
|
||||
*
|
||||
* This function consumes the skb unless there is an error.
|
||||
*/
|
||||
static
|
||||
int i1480u_tx_create_n(struct i1480u_tx *wtx, struct sk_buff *skb,
|
||||
gfp_t gfp_mask)
|
||||
{
|
||||
int result;
|
||||
void *pl;
|
||||
size_t pl_size;
|
||||
|
||||
void *pl_itr, *buf_itr;
|
||||
size_t pl_size_left, frgs, pl_size_1st, frg_pl_size = 0;
|
||||
struct untd_hdr_1st *untd_hdr_1st;
|
||||
struct wlp_tx_hdr *wlp_tx_hdr;
|
||||
struct untd_hdr_rst *untd_hdr_rst;
|
||||
|
||||
wtx->skb = NULL;
|
||||
pl = skb->data;
|
||||
pl_itr = pl;
|
||||
pl_size = skb->len;
|
||||
pl_size_left = pl_size; /* payload size */
|
||||
/* First fragment; fits as much as i1480u_MAX_FRG_SIZE minus
|
||||
* the headers */
|
||||
pl_size_1st = i1480u_MAX_FRG_SIZE
|
||||
- sizeof(struct untd_hdr_1st) - sizeof(struct wlp_tx_hdr);
|
||||
BUG_ON(pl_size_1st > pl_size);
|
||||
pl_size_left -= pl_size_1st;
|
||||
/* The rest have an smaller header (no i1480 TX header). We
|
||||
* need to break up the payload in blocks smaller than
|
||||
* i1480u_MAX_PL_SIZE (payload excluding header). */
|
||||
frgs = (pl_size_left + i1480u_MAX_PL_SIZE - 1) / i1480u_MAX_PL_SIZE;
|
||||
/* Allocate space for the new buffer. In this new buffer we'll
|
||||
* place the headers followed by the data fragment, headers,
|
||||
* data fragments, etc..
|
||||
*/
|
||||
result = -ENOMEM;
|
||||
wtx->buf_size = sizeof(*untd_hdr_1st)
|
||||
+ sizeof(*wlp_tx_hdr)
|
||||
+ frgs * sizeof(*untd_hdr_rst)
|
||||
+ pl_size;
|
||||
wtx->buf = kmalloc(wtx->buf_size, gfp_mask);
|
||||
if (wtx->buf == NULL)
|
||||
goto error_buf_alloc;
|
||||
|
||||
buf_itr = wtx->buf; /* We got the space, let's fill it up */
|
||||
/* Fill 1st fragment */
|
||||
untd_hdr_1st = buf_itr;
|
||||
buf_itr += sizeof(*untd_hdr_1st);
|
||||
untd_hdr_set_type(&untd_hdr_1st->hdr, i1480u_PKT_FRAG_1ST);
|
||||
untd_hdr_set_rx_tx(&untd_hdr_1st->hdr, 0);
|
||||
untd_hdr_1st->hdr.len = cpu_to_le16(pl_size + sizeof(*wlp_tx_hdr));
|
||||
untd_hdr_1st->fragment_len =
|
||||
cpu_to_le16(pl_size_1st + sizeof(*wlp_tx_hdr));
|
||||
memset(untd_hdr_1st->padding, 0, sizeof(untd_hdr_1st->padding));
|
||||
/* Set up i1480 header info */
|
||||
wlp_tx_hdr = wtx->wlp_tx_hdr = buf_itr;
|
||||
buf_itr += sizeof(*wlp_tx_hdr);
|
||||
/* Copy the first fragment */
|
||||
memcpy(buf_itr, pl_itr, pl_size_1st);
|
||||
pl_itr += pl_size_1st;
|
||||
buf_itr += pl_size_1st;
|
||||
|
||||
/* Now do each remaining fragment */
|
||||
result = -EINVAL;
|
||||
while (pl_size_left > 0) {
|
||||
d_printf(5, NULL, "ITR HDR: pl_size_left %zu buf_itr %zu\n",
|
||||
pl_size_left, buf_itr - wtx->buf);
|
||||
if (buf_itr + sizeof(*untd_hdr_rst) - wtx->buf
|
||||
> wtx->buf_size) {
|
||||
printk(KERN_ERR "BUG: no space for header\n");
|
||||
goto error_bug;
|
||||
}
|
||||
d_printf(5, NULL, "ITR HDR 2: pl_size_left %zu buf_itr %zu\n",
|
||||
pl_size_left, buf_itr - wtx->buf);
|
||||
untd_hdr_rst = buf_itr;
|
||||
buf_itr += sizeof(*untd_hdr_rst);
|
||||
if (pl_size_left > i1480u_MAX_PL_SIZE) {
|
||||
frg_pl_size = i1480u_MAX_PL_SIZE;
|
||||
untd_hdr_set_type(&untd_hdr_rst->hdr, i1480u_PKT_FRAG_NXT);
|
||||
} else {
|
||||
frg_pl_size = pl_size_left;
|
||||
untd_hdr_set_type(&untd_hdr_rst->hdr, i1480u_PKT_FRAG_LST);
|
||||
}
|
||||
d_printf(5, NULL,
|
||||
"ITR PL: pl_size_left %zu buf_itr %zu frg_pl_size %zu\n",
|
||||
pl_size_left, buf_itr - wtx->buf, frg_pl_size);
|
||||
untd_hdr_set_rx_tx(&untd_hdr_rst->hdr, 0);
|
||||
untd_hdr_rst->hdr.len = cpu_to_le16(frg_pl_size);
|
||||
untd_hdr_rst->padding = 0;
|
||||
if (buf_itr + frg_pl_size - wtx->buf
|
||||
> wtx->buf_size) {
|
||||
printk(KERN_ERR "BUG: no space for payload\n");
|
||||
goto error_bug;
|
||||
}
|
||||
memcpy(buf_itr, pl_itr, frg_pl_size);
|
||||
buf_itr += frg_pl_size;
|
||||
pl_itr += frg_pl_size;
|
||||
pl_size_left -= frg_pl_size;
|
||||
d_printf(5, NULL,
|
||||
"ITR PL 2: pl_size_left %zu buf_itr %zu frg_pl_size %zu\n",
|
||||
pl_size_left, buf_itr - wtx->buf, frg_pl_size);
|
||||
}
|
||||
dev_kfree_skb_irq(skb);
|
||||
return 0;
|
||||
|
||||
error_bug:
|
||||
printk(KERN_ERR
|
||||
"BUG: skb %u bytes\n"
|
||||
"BUG: frg_pl_size %zd i1480u_MAX_FRG_SIZE %u\n"
|
||||
"BUG: buf_itr %zu buf_size %zu pl_size_left %zu\n",
|
||||
skb->len,
|
||||
frg_pl_size, i1480u_MAX_FRG_SIZE,
|
||||
buf_itr - wtx->buf, wtx->buf_size, pl_size_left);
|
||||
|
||||
kfree(wtx->buf);
|
||||
error_buf_alloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a buffer that fits in a single fragment, fill out a @wtx
|
||||
* struct for transmitting it down the USB pipe.
|
||||
*
|
||||
* Uses the fact that we have space reserved in front of the skbuff
|
||||
* for hardware headers :]
|
||||
*
|
||||
* This does not fill the i1480 TX header, it is left up to the
|
||||
* caller to do that; you can get it from @wtx->wlp_tx_hdr.
|
||||
*
|
||||
* @pl: pointer to payload data
|
||||
* @pl_size: size of the payuload
|
||||
*
|
||||
* This function does not consume the @skb.
|
||||
*/
|
||||
static
|
||||
int i1480u_tx_create_1(struct i1480u_tx *wtx, struct sk_buff *skb,
|
||||
gfp_t gfp_mask)
|
||||
{
|
||||
struct untd_hdr_cmp *untd_hdr_cmp;
|
||||
struct wlp_tx_hdr *wlp_tx_hdr;
|
||||
|
||||
wtx->buf = NULL;
|
||||
wtx->skb = skb;
|
||||
BUG_ON(skb_headroom(skb) < sizeof(*wlp_tx_hdr));
|
||||
wlp_tx_hdr = (void *) __skb_push(skb, sizeof(*wlp_tx_hdr));
|
||||
wtx->wlp_tx_hdr = wlp_tx_hdr;
|
||||
BUG_ON(skb_headroom(skb) < sizeof(*untd_hdr_cmp));
|
||||
untd_hdr_cmp = (void *) __skb_push(skb, sizeof(*untd_hdr_cmp));
|
||||
|
||||
untd_hdr_set_type(&untd_hdr_cmp->hdr, i1480u_PKT_FRAG_CMP);
|
||||
untd_hdr_set_rx_tx(&untd_hdr_cmp->hdr, 0);
|
||||
untd_hdr_cmp->hdr.len = cpu_to_le16(skb->len - sizeof(*untd_hdr_cmp));
|
||||
untd_hdr_cmp->padding = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a skb to transmit, massage it to become palatable for the TX pipe
|
||||
*
|
||||
* This will break the buffer in chunks smaller than
|
||||
* i1480u_MAX_FRG_SIZE and add proper headers to each.
|
||||
*
|
||||
* 1st header \
|
||||
* i1480 tx header | fragment 1
|
||||
* fragment data /
|
||||
* nxt header \ fragment 2
|
||||
* fragment data /
|
||||
* ..
|
||||
* ..
|
||||
* last header \ fragment 3
|
||||
* last fragment data /
|
||||
*
|
||||
* Each fragment will be always smaller or equal to i1480u_MAX_FRG_SIZE.
|
||||
*
|
||||
* If the first fragment is smaller than i1480u_MAX_FRG_SIZE, then the
|
||||
* following is composed:
|
||||
*
|
||||
* complete header \
|
||||
* i1480 tx header | single fragment
|
||||
* packet data /
|
||||
*
|
||||
* We were going to use s/g support, but because the interface is
|
||||
* synch and at the end there is plenty of overhead to do it, it
|
||||
* didn't seem that worth for data that is going to be smaller than
|
||||
* one page.
|
||||
*/
|
||||
static
|
||||
struct i1480u_tx *i1480u_tx_create(struct i1480u *i1480u,
|
||||
struct sk_buff *skb, gfp_t gfp_mask)
|
||||
{
|
||||
int result;
|
||||
struct usb_endpoint_descriptor *epd;
|
||||
int usb_pipe;
|
||||
unsigned long flags;
|
||||
|
||||
struct i1480u_tx *wtx;
|
||||
const size_t pl_max_size =
|
||||
i1480u_MAX_FRG_SIZE - sizeof(struct untd_hdr_cmp)
|
||||
- sizeof(struct wlp_tx_hdr);
|
||||
|
||||
wtx = kmalloc(sizeof(*wtx), gfp_mask);
|
||||
if (wtx == NULL)
|
||||
goto error_wtx_alloc;
|
||||
wtx->urb = usb_alloc_urb(0, gfp_mask);
|
||||
if (wtx->urb == NULL)
|
||||
goto error_urb_alloc;
|
||||
epd = &i1480u->usb_iface->cur_altsetting->endpoint[2].desc;
|
||||
usb_pipe = usb_sndbulkpipe(i1480u->usb_dev, epd->bEndpointAddress);
|
||||
/* Fits in a single complete packet or need to split? */
|
||||
if (skb->len > pl_max_size) {
|
||||
result = i1480u_tx_create_n(wtx, skb, gfp_mask);
|
||||
if (result < 0)
|
||||
goto error_create;
|
||||
usb_fill_bulk_urb(wtx->urb, i1480u->usb_dev, usb_pipe,
|
||||
wtx->buf, wtx->buf_size, i1480u_tx_cb, wtx);
|
||||
} else {
|
||||
result = i1480u_tx_create_1(wtx, skb, gfp_mask);
|
||||
if (result < 0)
|
||||
goto error_create;
|
||||
usb_fill_bulk_urb(wtx->urb, i1480u->usb_dev, usb_pipe,
|
||||
skb->data, skb->len, i1480u_tx_cb, wtx);
|
||||
}
|
||||
spin_lock_irqsave(&i1480u->tx_list_lock, flags);
|
||||
list_add(&wtx->list_node, &i1480u->tx_list);
|
||||
spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
|
||||
return wtx;
|
||||
|
||||
error_create:
|
||||
kfree(wtx->urb);
|
||||
error_urb_alloc:
|
||||
kfree(wtx);
|
||||
error_wtx_alloc:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual fragmentation and transmission of frame
|
||||
*
|
||||
* @wlp: WLP substack data structure
|
||||
* @skb: To be transmitted
|
||||
* @dst: Device address of destination
|
||||
* @returns: 0 on success, <0 on failure
|
||||
*
|
||||
* This function can also be called directly (not just from
|
||||
* hard_start_xmit), so we also check here if the interface is up before
|
||||
* taking sending anything.
|
||||
*/
|
||||
int i1480u_xmit_frame(struct wlp *wlp, struct sk_buff *skb,
|
||||
struct uwb_dev_addr *dst)
|
||||
{
|
||||
int result = -ENXIO;
|
||||
struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp);
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
struct net_device *net_dev = i1480u->net_dev;
|
||||
struct i1480u_tx *wtx;
|
||||
struct wlp_tx_hdr *wlp_tx_hdr;
|
||||
static unsigned char dev_bcast[2] = { 0xff, 0xff };
|
||||
#if 0
|
||||
int lockup = 50;
|
||||
#endif
|
||||
|
||||
d_fnstart(6, dev, "(skb %p (%u), net_dev %p)\n", skb, skb->len,
|
||||
net_dev);
|
||||
BUG_ON(i1480u->wlp.rc == NULL);
|
||||
if ((net_dev->flags & IFF_UP) == 0)
|
||||
goto out;
|
||||
result = -EBUSY;
|
||||
if (atomic_read(&i1480u->tx_inflight.count) >= i1480u->tx_inflight.max) {
|
||||
if (d_test(2) && printk_ratelimit())
|
||||
d_printf(2, dev, "Max frames in flight "
|
||||
"stopping queue.\n");
|
||||
netif_stop_queue(net_dev);
|
||||
goto error_max_inflight;
|
||||
}
|
||||
result = -ENOMEM;
|
||||
wtx = i1480u_tx_create(i1480u, skb, GFP_ATOMIC);
|
||||
if (unlikely(wtx == NULL)) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "TX: no memory for WLP TX URB,"
|
||||
"dropping packet (in flight %d)\n",
|
||||
atomic_read(&i1480u->tx_inflight.count));
|
||||
netif_stop_queue(net_dev);
|
||||
goto error_wtx_alloc;
|
||||
}
|
||||
wtx->i1480u = i1480u;
|
||||
/* Fill out the i1480 header; @i1480u->def_tx_hdr read without
|
||||
* locking. We do so because they are kind of orthogonal to
|
||||
* each other (and thus not changed in an atomic batch).
|
||||
* The ETH header is right after the WLP TX header. */
|
||||
wlp_tx_hdr = wtx->wlp_tx_hdr;
|
||||
*wlp_tx_hdr = i1480u->options.def_tx_hdr;
|
||||
wlp_tx_hdr->dstaddr = *dst;
|
||||
if (!memcmp(&wlp_tx_hdr->dstaddr, dev_bcast, sizeof(dev_bcast))
|
||||
&& (wlp_tx_hdr_delivery_id_type(wlp_tx_hdr) & WLP_DRP)) {
|
||||
/*Broadcast message directed to DRP host. Send as best effort
|
||||
* on PCA. */
|
||||
wlp_tx_hdr_set_delivery_id_type(wlp_tx_hdr, i1480u->options.pca_base_priority);
|
||||
}
|
||||
|
||||
#if 0
|
||||
dev_info(dev, "TX delivering skb -> USB, %zu bytes\n", skb->len);
|
||||
dump_bytes(dev, skb->data, skb->len > 72 ? 72 : skb->len);
|
||||
#endif
|
||||
#if 0
|
||||
/* simulates a device lockup after every lockup# packets */
|
||||
if (lockup && ((i1480u->stats.tx_packets + 1) % lockup) == 0) {
|
||||
/* Simulate a dropped transmit interrupt */
|
||||
net_dev->trans_start = jiffies;
|
||||
netif_stop_queue(net_dev);
|
||||
dev_err(dev, "Simulate lockup at %ld\n", jiffies);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
result = usb_submit_urb(wtx->urb, GFP_ATOMIC); /* Go baby */
|
||||
if (result < 0) {
|
||||
dev_err(dev, "TX: cannot submit URB: %d\n", result);
|
||||
/* We leave the freeing of skb to calling function */
|
||||
wtx->skb = NULL;
|
||||
goto error_tx_urb_submit;
|
||||
}
|
||||
atomic_inc(&i1480u->tx_inflight.count);
|
||||
net_dev->trans_start = jiffies;
|
||||
d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len,
|
||||
net_dev, result);
|
||||
return result;
|
||||
|
||||
error_tx_urb_submit:
|
||||
i1480u_tx_destroy(i1480u, wtx);
|
||||
error_wtx_alloc:
|
||||
error_max_inflight:
|
||||
out:
|
||||
d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len,
|
||||
net_dev, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transmit an skb Called when an skbuf has to be transmitted
|
||||
*
|
||||
* The skb is first passed to WLP substack to ensure this is a valid
|
||||
* frame. If valid the device address of destination will be filled and
|
||||
* the WLP header prepended to the skb. If this step fails we fake sending
|
||||
* the frame, if we return an error the network stack will just keep trying.
|
||||
*
|
||||
* Broadcast frames inside a WSS needs to be treated special as multicast is
|
||||
* not supported. A broadcast frame is sent as unicast to each member of the
|
||||
* WSS - this is done by the WLP substack when it finds a broadcast frame.
|
||||
* So, we test if the WLP substack took over the skb and only transmit it
|
||||
* if it has not (been taken over).
|
||||
*
|
||||
* @net_dev->xmit_lock is held
|
||||
*/
|
||||
int i1480u_hard_start_xmit(struct sk_buff *skb, struct net_device *net_dev)
|
||||
{
|
||||
int result;
|
||||
struct i1480u *i1480u = netdev_priv(net_dev);
|
||||
struct device *dev = &i1480u->usb_iface->dev;
|
||||
struct uwb_dev_addr dst;
|
||||
|
||||
d_fnstart(6, dev, "(skb %p (%u), net_dev %p)\n", skb, skb->len,
|
||||
net_dev);
|
||||
BUG_ON(i1480u->wlp.rc == NULL);
|
||||
if ((net_dev->flags & IFF_UP) == 0)
|
||||
goto error;
|
||||
result = wlp_prepare_tx_frame(dev, &i1480u->wlp, skb, &dst);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "WLP verification of TX frame failed (%d). "
|
||||
"Dropping packet.\n", result);
|
||||
goto error;
|
||||
} else if (result == 1) {
|
||||
d_printf(6, dev, "WLP will transmit frame. \n");
|
||||
/* trans_start time will be set when WLP actually transmits
|
||||
* the frame */
|
||||
goto out;
|
||||
}
|
||||
d_printf(6, dev, "Transmitting frame. \n");
|
||||
result = i1480u_xmit_frame(&i1480u->wlp, skb, &dst);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Frame TX failed (%d).\n", result);
|
||||
goto error;
|
||||
}
|
||||
d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len,
|
||||
net_dev, result);
|
||||
return NETDEV_TX_OK;
|
||||
error:
|
||||
dev_kfree_skb_any(skb);
|
||||
i1480u->stats.tx_dropped++;
|
||||
out:
|
||||
d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len,
|
||||
net_dev, result);
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a pkt transmission doesn't complete in a reasonable period
|
||||
* Device reset may sleep - do it outside of interrupt context (delayed)
|
||||
*/
|
||||
void i1480u_tx_timeout(struct net_device *net_dev)
|
||||
{
|
||||
struct i1480u *i1480u = netdev_priv(net_dev);
|
||||
|
||||
wlp_reset_all(&i1480u->wlp);
|
||||
}
|
||||
|
||||
|
||||
void i1480u_tx_release(struct i1480u *i1480u)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct i1480u_tx *wtx, *next;
|
||||
int count = 0, empty;
|
||||
|
||||
spin_lock_irqsave(&i1480u->tx_list_lock, flags);
|
||||
list_for_each_entry_safe(wtx, next, &i1480u->tx_list, list_node) {
|
||||
count++;
|
||||
usb_unlink_urb(wtx->urb);
|
||||
}
|
||||
spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
|
||||
count = count*10; /* i1480ut 200ms per unlinked urb (intervals of 20ms) */
|
||||
/*
|
||||
* We don't like this sollution too much (dirty as it is), but
|
||||
* it is cheaper than putting a refcount on each i1480u_tx and
|
||||
* i1480uting for all of them to go away...
|
||||
*
|
||||
* Called when no more packets can be added to tx_list
|
||||
* so can i1480ut for it to be empty.
|
||||
*/
|
||||
while (1) {
|
||||
spin_lock_irqsave(&i1480u->tx_list_lock, flags);
|
||||
empty = list_empty(&i1480u->tx_list);
|
||||
spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
|
||||
if (empty)
|
||||
break;
|
||||
count--;
|
||||
BUG_ON(count == 0);
|
||||
msleep(20);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,541 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Information Element Handling
|
||||
*
|
||||
* Copyright (C) 2005-2006 Intel Corporation
|
||||
* Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
|
||||
* Reinette Chatre <reinette.chatre@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
|
||||
*/
|
||||
|
||||
#include "uwb-internal.h"
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/**
|
||||
* uwb_ie_next - get the next IE in a buffer
|
||||
* @ptr: start of the buffer containing the IE data
|
||||
* @len: length of the buffer
|
||||
*
|
||||
* Both @ptr and @len are updated so subsequent calls to uwb_ie_next()
|
||||
* will get the next IE.
|
||||
*
|
||||
* NULL is returned (and @ptr and @len will not be updated) if there
|
||||
* are no more IEs in the buffer or the buffer is too short.
|
||||
*/
|
||||
struct uwb_ie_hdr *uwb_ie_next(void **ptr, size_t *len)
|
||||
{
|
||||
struct uwb_ie_hdr *hdr;
|
||||
size_t ie_len;
|
||||
|
||||
if (*len < sizeof(struct uwb_ie_hdr))
|
||||
return NULL;
|
||||
|
||||
hdr = *ptr;
|
||||
ie_len = sizeof(struct uwb_ie_hdr) + hdr->length;
|
||||
|
||||
if (*len < ie_len)
|
||||
return NULL;
|
||||
|
||||
*ptr += ie_len;
|
||||
*len -= ie_len;
|
||||
|
||||
return hdr;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_ie_next);
|
||||
|
||||
/**
|
||||
* Get the IEs that a radio controller is sending in its beacon
|
||||
*
|
||||
* @uwb_rc: UWB Radio Controller
|
||||
* @returns: Size read from the system
|
||||
*
|
||||
* We don't need to lock the uwb_rc's mutex because we don't modify
|
||||
* anything. Once done with the iedata buffer, call
|
||||
* uwb_rc_ie_release(iedata). Don't call kfree on it.
|
||||
*/
|
||||
ssize_t uwb_rc_get_ie(struct uwb_rc *uwb_rc, struct uwb_rc_evt_get_ie **pget_ie)
|
||||
{
|
||||
ssize_t result;
|
||||
struct device *dev = &uwb_rc->uwb_dev.dev;
|
||||
struct uwb_rccb *cmd = NULL;
|
||||
struct uwb_rceb *reply = NULL;
|
||||
struct uwb_rc_evt_get_ie *get_ie;
|
||||
|
||||
d_fnstart(3, dev, "(%p, %p)\n", uwb_rc, pget_ie);
|
||||
result = -ENOMEM;
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
goto error_kzalloc;
|
||||
cmd->bCommandType = UWB_RC_CET_GENERAL;
|
||||
cmd->wCommand = cpu_to_le16(UWB_RC_CMD_GET_IE);
|
||||
result = uwb_rc_vcmd(uwb_rc, "GET_IE", cmd, sizeof(*cmd),
|
||||
UWB_RC_CET_GENERAL, UWB_RC_CMD_GET_IE,
|
||||
&reply);
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
get_ie = container_of(reply, struct uwb_rc_evt_get_ie, rceb);
|
||||
if (result < sizeof(*get_ie)) {
|
||||
dev_err(dev, "not enough data returned for decoding GET IE "
|
||||
"(%zu bytes received vs %zu needed)\n",
|
||||
result, sizeof(*get_ie));
|
||||
result = -EINVAL;
|
||||
} else if (result < sizeof(*get_ie) + le16_to_cpu(get_ie->wIELength)) {
|
||||
dev_err(dev, "not enough data returned for decoding GET IE "
|
||||
"payload (%zu bytes received vs %zu needed)\n", result,
|
||||
sizeof(*get_ie) + le16_to_cpu(get_ie->wIELength));
|
||||
result = -EINVAL;
|
||||
} else
|
||||
*pget_ie = get_ie;
|
||||
error_cmd:
|
||||
kfree(cmd);
|
||||
error_kzalloc:
|
||||
d_fnend(3, dev, "(%p, %p) = %d\n", uwb_rc, pget_ie, (int)result);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_get_ie);
|
||||
|
||||
|
||||
/*
|
||||
* Given a pointer to an IE, print it in ASCII/hex followed by a new line
|
||||
*
|
||||
* @ie_hdr: pointer to the IE header. Length is in there, and it is
|
||||
* guaranteed that the ie_hdr->length bytes following it are
|
||||
* safely accesible.
|
||||
*
|
||||
* @_data: context data passed from uwb_ie_for_each(), an struct output_ctx
|
||||
*/
|
||||
int uwb_ie_dump_hex(struct uwb_dev *uwb_dev, const struct uwb_ie_hdr *ie_hdr,
|
||||
size_t offset, void *_ctx)
|
||||
{
|
||||
struct uwb_buf_ctx *ctx = _ctx;
|
||||
const u8 *pl = (void *)(ie_hdr + 1);
|
||||
u8 pl_itr;
|
||||
|
||||
ctx->bytes += scnprintf(ctx->buf + ctx->bytes, ctx->size - ctx->bytes,
|
||||
"%02x %02x ", (unsigned) ie_hdr->element_id,
|
||||
(unsigned) ie_hdr->length);
|
||||
pl_itr = 0;
|
||||
while (pl_itr < ie_hdr->length && ctx->bytes < ctx->size)
|
||||
ctx->bytes += scnprintf(ctx->buf + ctx->bytes,
|
||||
ctx->size - ctx->bytes,
|
||||
"%02x ", (unsigned) pl[pl_itr++]);
|
||||
if (ctx->bytes < ctx->size)
|
||||
ctx->buf[ctx->bytes++] = '\n';
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_ie_dump_hex);
|
||||
|
||||
|
||||
/**
|
||||
* Verify that a pointer in a buffer points to valid IE
|
||||
*
|
||||
* @start: pointer to start of buffer in which IE appears
|
||||
* @itr: pointer to IE inside buffer that will be verified
|
||||
* @top: pointer to end of buffer
|
||||
*
|
||||
* @returns: 0 if IE is valid, <0 otherwise
|
||||
*
|
||||
* Verification involves checking that the buffer can contain a
|
||||
* header and the amount of data reported in the IE header can be found in
|
||||
* the buffer.
|
||||
*/
|
||||
static
|
||||
int uwb_rc_ie_verify(struct uwb_dev *uwb_dev, const void *start,
|
||||
const void *itr, const void *top)
|
||||
{
|
||||
struct device *dev = &uwb_dev->dev;
|
||||
const struct uwb_ie_hdr *ie_hdr;
|
||||
|
||||
if (top - itr < sizeof(*ie_hdr)) {
|
||||
dev_err(dev, "Bad IE: no data to decode header "
|
||||
"(%zu bytes left vs %zu needed) at offset %zu\n",
|
||||
top - itr, sizeof(*ie_hdr), itr - start);
|
||||
return -EINVAL;
|
||||
}
|
||||
ie_hdr = itr;
|
||||
itr += sizeof(*ie_hdr);
|
||||
if (top - itr < ie_hdr->length) {
|
||||
dev_err(dev, "Bad IE: not enough data for payload "
|
||||
"(%zu bytes left vs %zu needed) at offset %zu\n",
|
||||
top - itr, (size_t)ie_hdr->length,
|
||||
(void *)ie_hdr - start);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Walk a buffer filled with consecutive IE's a buffer
|
||||
*
|
||||
* @uwb_dev: UWB device this IEs belong to (for err messages mainly)
|
||||
*
|
||||
* @fn: function to call with each IE; if it returns 0, we keep
|
||||
* traversing the buffer. If it returns !0, we'll stop and return
|
||||
* that value.
|
||||
*
|
||||
* @data: pointer passed to @fn
|
||||
*
|
||||
* @buf: buffer where the consecutive IEs are located
|
||||
*
|
||||
* @size: size of @buf
|
||||
*
|
||||
* Each IE is checked for basic correctness (there is space left for
|
||||
* the header and the payload). If that test is failed, we stop
|
||||
* processing. For every good IE, @fn is called.
|
||||
*/
|
||||
ssize_t uwb_ie_for_each(struct uwb_dev *uwb_dev, uwb_ie_f fn, void *data,
|
||||
const void *buf, size_t size)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
const struct uwb_ie_hdr *ie_hdr;
|
||||
const void *itr = buf, *top = itr + size;
|
||||
|
||||
while (itr < top) {
|
||||
if (uwb_rc_ie_verify(uwb_dev, buf, itr, top) != 0)
|
||||
break;
|
||||
ie_hdr = itr;
|
||||
itr += sizeof(*ie_hdr) + ie_hdr->length;
|
||||
result = fn(uwb_dev, ie_hdr, itr - buf, data);
|
||||
if (result != 0)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_ie_for_each);
|
||||
|
||||
|
||||
/**
|
||||
* Replace all IEs currently being transmitted by a device
|
||||
*
|
||||
* @cmd: pointer to the SET-IE command with the IEs to set
|
||||
* @size: size of @buf
|
||||
*/
|
||||
int uwb_rc_set_ie(struct uwb_rc *rc, struct uwb_rc_cmd_set_ie *cmd)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_rc_evt_set_ie reply;
|
||||
|
||||
reply.rceb.bEventType = UWB_RC_CET_GENERAL;
|
||||
reply.rceb.wEvent = UWB_RC_CMD_SET_IE;
|
||||
result = uwb_rc_cmd(rc, "SET-IE", &cmd->rccb,
|
||||
sizeof(*cmd) + le16_to_cpu(cmd->wIELength),
|
||||
&reply.rceb, sizeof(reply));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
else if (result != sizeof(reply)) {
|
||||
dev_err(dev, "SET-IE: not enough data to decode reply "
|
||||
"(%d bytes received vs %zu needed)\n",
|
||||
result, sizeof(reply));
|
||||
result = -EIO;
|
||||
} else if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(dev, "SET-IE: command execution failed: %s (%d)\n",
|
||||
uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
|
||||
result = -EIO;
|
||||
} else
|
||||
result = 0;
|
||||
error_cmd:
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine by IE id if IE is host settable
|
||||
* WUSB 1.0 [8.6.2.8 Table 8.85]
|
||||
*
|
||||
* EXCEPTION:
|
||||
* All but UWB_IE_WLP appears in Table 8.85 from WUSB 1.0. Setting this IE
|
||||
* is required for the WLP substack to perform association with its WSS so
|
||||
* we hope that the WUSB spec will be changed to reflect this.
|
||||
*/
|
||||
static
|
||||
int uwb_rc_ie_is_host_settable(enum uwb_ie element_id)
|
||||
{
|
||||
if (element_id == UWB_PCA_AVAILABILITY ||
|
||||
element_id == UWB_BP_SWITCH_IE ||
|
||||
element_id == UWB_MAC_CAPABILITIES_IE ||
|
||||
element_id == UWB_PHY_CAPABILITIES_IE ||
|
||||
element_id == UWB_APP_SPEC_PROBE_IE ||
|
||||
element_id == UWB_IDENTIFICATION_IE ||
|
||||
element_id == UWB_MASTER_KEY_ID_IE ||
|
||||
element_id == UWB_IE_WLP ||
|
||||
element_id == UWB_APP_SPEC_IE)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract Host Settable IEs from IE
|
||||
*
|
||||
* @ie_data: pointer to buffer containing all IEs
|
||||
* @size: size of buffer
|
||||
*
|
||||
* @returns: length of buffer that only includes host settable IEs
|
||||
*
|
||||
* Given a buffer of IEs we move all Host Settable IEs to front of buffer
|
||||
* by overwriting the IEs that are not Host Settable.
|
||||
* Buffer length is adjusted accordingly.
|
||||
*/
|
||||
static
|
||||
ssize_t uwb_rc_parse_host_settable_ie(struct uwb_dev *uwb_dev,
|
||||
void *ie_data, size_t size)
|
||||
{
|
||||
size_t new_len = size;
|
||||
struct uwb_ie_hdr *ie_hdr;
|
||||
size_t ie_length;
|
||||
void *itr = ie_data, *top = itr + size;
|
||||
|
||||
while (itr < top) {
|
||||
if (uwb_rc_ie_verify(uwb_dev, ie_data, itr, top) != 0)
|
||||
break;
|
||||
ie_hdr = itr;
|
||||
ie_length = sizeof(*ie_hdr) + ie_hdr->length;
|
||||
if (uwb_rc_ie_is_host_settable(ie_hdr->element_id)) {
|
||||
itr += ie_length;
|
||||
} else {
|
||||
memmove(itr, itr + ie_length, top - (itr + ie_length));
|
||||
new_len -= ie_length;
|
||||
top -= ie_length;
|
||||
}
|
||||
}
|
||||
return new_len;
|
||||
}
|
||||
|
||||
|
||||
/* Cleanup the whole IE management subsystem */
|
||||
void uwb_rc_ie_init(struct uwb_rc *uwb_rc)
|
||||
{
|
||||
mutex_init(&uwb_rc->ies_mutex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set up cache for host settable IEs currently being transmitted
|
||||
*
|
||||
* First we just call GET-IE to get the current IEs being transmitted
|
||||
* (or we workaround and pretend we did) and (because the format is
|
||||
* the same) reuse that as the IE cache (with the command prefix, as
|
||||
* explained in 'struct uwb_rc').
|
||||
*
|
||||
* @returns: size of cache created
|
||||
*/
|
||||
ssize_t uwb_rc_ie_setup(struct uwb_rc *uwb_rc)
|
||||
{
|
||||
struct device *dev = &uwb_rc->uwb_dev.dev;
|
||||
ssize_t result;
|
||||
size_t capacity;
|
||||
struct uwb_rc_evt_get_ie *ie_info;
|
||||
|
||||
d_fnstart(3, dev, "(%p)\n", uwb_rc);
|
||||
mutex_lock(&uwb_rc->ies_mutex);
|
||||
result = uwb_rc_get_ie(uwb_rc, &ie_info);
|
||||
if (result < 0)
|
||||
goto error_get_ie;
|
||||
capacity = result;
|
||||
d_printf(5, dev, "Got IEs %zu bytes (%zu long at %p)\n", result,
|
||||
(size_t)le16_to_cpu(ie_info->wIELength), ie_info);
|
||||
|
||||
/* Remove IEs that host should not set. */
|
||||
result = uwb_rc_parse_host_settable_ie(&uwb_rc->uwb_dev,
|
||||
ie_info->IEData, le16_to_cpu(ie_info->wIELength));
|
||||
if (result < 0)
|
||||
goto error_parse;
|
||||
d_printf(5, dev, "purged non-settable IEs to %zu bytes\n", result);
|
||||
uwb_rc->ies = (void *) ie_info;
|
||||
uwb_rc->ies->rccb.bCommandType = UWB_RC_CET_GENERAL;
|
||||
uwb_rc->ies->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SET_IE);
|
||||
uwb_rc->ies_capacity = capacity;
|
||||
d_printf(5, dev, "IE cache at %p %zu bytes, %zu capacity\n",
|
||||
ie_info, result, capacity);
|
||||
result = 0;
|
||||
error_parse:
|
||||
error_get_ie:
|
||||
mutex_unlock(&uwb_rc->ies_mutex);
|
||||
d_fnend(3, dev, "(%p) = %zu\n", uwb_rc, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* Cleanup the whole IE management subsystem */
|
||||
void uwb_rc_ie_release(struct uwb_rc *uwb_rc)
|
||||
{
|
||||
kfree(uwb_rc->ies);
|
||||
uwb_rc->ies = NULL;
|
||||
uwb_rc->ies_capacity = 0;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
int __acc_size(struct uwb_dev *uwb_dev, const struct uwb_ie_hdr *ie_hdr,
|
||||
size_t offset, void *_ctx)
|
||||
{
|
||||
size_t *acc_size = _ctx;
|
||||
*acc_size += sizeof(*ie_hdr) + ie_hdr->length;
|
||||
d_printf(6, &uwb_dev->dev, "new acc size %zu\n", *acc_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new IE to IEs currently being transmitted by device
|
||||
*
|
||||
* @ies: the buffer containing the new IE or IEs to be added to
|
||||
* the device's beacon. The buffer will be verified for
|
||||
* consistence (meaning the headers should be right) and
|
||||
* consistent with the buffer size.
|
||||
* @size: size of @ies (in bytes, total buffer size)
|
||||
* @returns: 0 if ok, <0 errno code on error
|
||||
*
|
||||
* According to WHCI 0.95 [4.13.6] the driver will only receive the RCEB
|
||||
* after the device sent the first beacon that includes the IEs specified
|
||||
* in the SET IE command. We thus cannot send this command if the device is
|
||||
* not beaconing. Instead, a SET IE command will be sent later right after
|
||||
* we start beaconing.
|
||||
*
|
||||
* Setting an IE on the device will overwrite all current IEs in device. So
|
||||
* we take the current IEs being transmitted by the device, append the
|
||||
* new one, and call SET IE with all the IEs needed.
|
||||
*
|
||||
* The local IE cache will only be updated with the new IE if SET IE
|
||||
* completed successfully.
|
||||
*/
|
||||
int uwb_rc_ie_add(struct uwb_rc *uwb_rc,
|
||||
const struct uwb_ie_hdr *ies, size_t size)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = &uwb_rc->uwb_dev.dev;
|
||||
struct uwb_rc_cmd_set_ie *new_ies;
|
||||
size_t ies_size, total_size, acc_size = 0;
|
||||
|
||||
if (uwb_rc->ies == NULL)
|
||||
return -ESHUTDOWN;
|
||||
uwb_ie_for_each(&uwb_rc->uwb_dev, __acc_size, &acc_size, ies, size);
|
||||
if (acc_size != size) {
|
||||
dev_err(dev, "BUG: bad IEs, misconstructed headers "
|
||||
"[%zu bytes reported vs %zu calculated]\n",
|
||||
size, acc_size);
|
||||
WARN_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
mutex_lock(&uwb_rc->ies_mutex);
|
||||
ies_size = le16_to_cpu(uwb_rc->ies->wIELength);
|
||||
total_size = sizeof(*uwb_rc->ies) + ies_size;
|
||||
if (total_size + size > uwb_rc->ies_capacity) {
|
||||
d_printf(4, dev, "Reallocating IE cache from %p capacity %zu "
|
||||
"to capacity %zu\n", uwb_rc->ies, uwb_rc->ies_capacity,
|
||||
total_size + size);
|
||||
new_ies = kzalloc(total_size + size, GFP_KERNEL);
|
||||
if (new_ies == NULL) {
|
||||
dev_err(dev, "No memory for adding new IE\n");
|
||||
result = -ENOMEM;
|
||||
goto error_alloc;
|
||||
}
|
||||
memcpy(new_ies, uwb_rc->ies, total_size);
|
||||
uwb_rc->ies_capacity = total_size + size;
|
||||
kfree(uwb_rc->ies);
|
||||
uwb_rc->ies = new_ies;
|
||||
d_printf(4, dev, "New IE cache at %p capacity %zu\n",
|
||||
uwb_rc->ies, uwb_rc->ies_capacity);
|
||||
}
|
||||
memcpy((void *)uwb_rc->ies + total_size, ies, size);
|
||||
uwb_rc->ies->wIELength = cpu_to_le16(ies_size + size);
|
||||
if (uwb_rc->beaconing != -1) {
|
||||
result = uwb_rc_set_ie(uwb_rc, uwb_rc->ies);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Cannot set new IE on device: %d\n",
|
||||
result);
|
||||
uwb_rc->ies->wIELength = cpu_to_le16(ies_size);
|
||||
} else
|
||||
result = 0;
|
||||
}
|
||||
d_printf(4, dev, "IEs now occupy %hu bytes of %zu capacity at %p\n",
|
||||
le16_to_cpu(uwb_rc->ies->wIELength), uwb_rc->ies_capacity,
|
||||
uwb_rc->ies);
|
||||
error_alloc:
|
||||
mutex_unlock(&uwb_rc->ies_mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_ie_add);
|
||||
|
||||
|
||||
/*
|
||||
* Remove an IE from internal cache
|
||||
*
|
||||
* We are dealing with our internal IE cache so no need to verify that the
|
||||
* IEs are valid (it has been done already).
|
||||
*
|
||||
* Should be called with ies_mutex held
|
||||
*
|
||||
* We do not break out once an IE is found in the cache. It is currently
|
||||
* possible to have more than one IE with the same ID included in the
|
||||
* beacon. We don't reallocate, we just mark the size smaller.
|
||||
*/
|
||||
static
|
||||
int uwb_rc_ie_cache_rm(struct uwb_rc *uwb_rc, enum uwb_ie to_remove)
|
||||
{
|
||||
struct uwb_ie_hdr *ie_hdr;
|
||||
size_t new_len = le16_to_cpu(uwb_rc->ies->wIELength);
|
||||
void *itr = uwb_rc->ies->IEData;
|
||||
void *top = itr + new_len;
|
||||
|
||||
while (itr < top) {
|
||||
ie_hdr = itr;
|
||||
if (ie_hdr->element_id != to_remove) {
|
||||
itr += sizeof(*ie_hdr) + ie_hdr->length;
|
||||
} else {
|
||||
int ie_length;
|
||||
ie_length = sizeof(*ie_hdr) + ie_hdr->length;
|
||||
if (top - itr != ie_length)
|
||||
memmove(itr, itr + ie_length, top - itr + ie_length);
|
||||
top -= ie_length;
|
||||
new_len -= ie_length;
|
||||
}
|
||||
}
|
||||
uwb_rc->ies->wIELength = cpu_to_le16(new_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove an IE currently being transmitted by device
|
||||
*
|
||||
* @element_id: id of IE to be removed from device's beacon
|
||||
*/
|
||||
int uwb_rc_ie_rm(struct uwb_rc *uwb_rc, enum uwb_ie element_id)
|
||||
{
|
||||
struct device *dev = &uwb_rc->uwb_dev.dev;
|
||||
int result;
|
||||
|
||||
if (uwb_rc->ies == NULL)
|
||||
return -ESHUTDOWN;
|
||||
mutex_lock(&uwb_rc->ies_mutex);
|
||||
result = uwb_rc_ie_cache_rm(uwb_rc, element_id);
|
||||
if (result < 0)
|
||||
dev_err(dev, "Cannot remove IE from cache.\n");
|
||||
if (uwb_rc->beaconing != -1) {
|
||||
result = uwb_rc_set_ie(uwb_rc, uwb_rc->ies);
|
||||
if (result < 0)
|
||||
dev_err(dev, "Cannot set new IE on device.\n");
|
||||
}
|
||||
mutex_unlock(&uwb_rc->ies_mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_ie_rm);
|
|
@ -0,0 +1,492 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Life cycle of devices
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/random.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
#define D_LOCAL 1
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
|
||||
/* We initialize addresses to 0xff (invalid, as it is bcast) */
|
||||
static inline void uwb_dev_addr_init(struct uwb_dev_addr *addr)
|
||||
{
|
||||
memset(&addr->data, 0xff, sizeof(addr->data));
|
||||
}
|
||||
|
||||
static inline void uwb_mac_addr_init(struct uwb_mac_addr *addr)
|
||||
{
|
||||
memset(&addr->data, 0xff, sizeof(addr->data));
|
||||
}
|
||||
|
||||
/* @returns !0 if a device @addr is a broadcast address */
|
||||
static inline int uwb_dev_addr_bcast(const struct uwb_dev_addr *addr)
|
||||
{
|
||||
static const struct uwb_dev_addr bcast = { .data = { 0xff, 0xff } };
|
||||
return !uwb_dev_addr_cmp(addr, &bcast);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add callback @new to be called when an event occurs in @rc.
|
||||
*/
|
||||
int uwb_notifs_register(struct uwb_rc *rc, struct uwb_notifs_handler *new)
|
||||
{
|
||||
if (mutex_lock_interruptible(&rc->notifs_chain.mutex))
|
||||
return -ERESTARTSYS;
|
||||
list_add(&new->list_node, &rc->notifs_chain.list);
|
||||
mutex_unlock(&rc->notifs_chain.mutex);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_notifs_register);
|
||||
|
||||
/*
|
||||
* Remove event handler (callback)
|
||||
*/
|
||||
int uwb_notifs_deregister(struct uwb_rc *rc, struct uwb_notifs_handler *entry)
|
||||
{
|
||||
if (mutex_lock_interruptible(&rc->notifs_chain.mutex))
|
||||
return -ERESTARTSYS;
|
||||
list_del(&entry->list_node);
|
||||
mutex_unlock(&rc->notifs_chain.mutex);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_notifs_deregister);
|
||||
|
||||
/*
|
||||
* Notify all event handlers of a given event on @rc
|
||||
*
|
||||
* We are called with a valid reference to the device, or NULL if the
|
||||
* event is not for a particular event (e.g., a BG join event).
|
||||
*/
|
||||
void uwb_notify(struct uwb_rc *rc, struct uwb_dev *uwb_dev, enum uwb_notifs event)
|
||||
{
|
||||
struct uwb_notifs_handler *handler;
|
||||
if (mutex_lock_interruptible(&rc->notifs_chain.mutex))
|
||||
return;
|
||||
if (!list_empty(&rc->notifs_chain.list)) {
|
||||
list_for_each_entry(handler, &rc->notifs_chain.list, list_node) {
|
||||
handler->cb(handler->data, uwb_dev, event);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&rc->notifs_chain.mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Release the backing device of a uwb_dev that has been dynamically allocated.
|
||||
*/
|
||||
static void uwb_dev_sys_release(struct device *dev)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
|
||||
d_fnstart(4, NULL, "(dev %p uwb_dev %p)\n", dev, uwb_dev);
|
||||
uwb_bce_put(uwb_dev->bce);
|
||||
d_printf(0, &uwb_dev->dev, "uwb_dev %p freed\n", uwb_dev);
|
||||
memset(uwb_dev, 0x69, sizeof(*uwb_dev));
|
||||
kfree(uwb_dev);
|
||||
d_fnend(4, NULL, "(dev %p uwb_dev %p) = void\n", dev, uwb_dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a UWB device instance
|
||||
*
|
||||
* Alloc, zero and call this function.
|
||||
*/
|
||||
void uwb_dev_init(struct uwb_dev *uwb_dev)
|
||||
{
|
||||
mutex_init(&uwb_dev->mutex);
|
||||
device_initialize(&uwb_dev->dev);
|
||||
uwb_dev->dev.release = uwb_dev_sys_release;
|
||||
uwb_dev_addr_init(&uwb_dev->dev_addr);
|
||||
uwb_mac_addr_init(&uwb_dev->mac_addr);
|
||||
bitmap_fill(uwb_dev->streams, UWB_NUM_GLOBAL_STREAMS);
|
||||
}
|
||||
|
||||
static ssize_t uwb_dev_EUI_48_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
char addr[UWB_ADDR_STRSIZE];
|
||||
|
||||
uwb_mac_addr_print(addr, sizeof(addr), &uwb_dev->mac_addr);
|
||||
return sprintf(buf, "%s\n", addr);
|
||||
}
|
||||
static DEVICE_ATTR(EUI_48, S_IRUGO, uwb_dev_EUI_48_show, NULL);
|
||||
|
||||
static ssize_t uwb_dev_DevAddr_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
char addr[UWB_ADDR_STRSIZE];
|
||||
|
||||
uwb_dev_addr_print(addr, sizeof(addr), &uwb_dev->dev_addr);
|
||||
return sprintf(buf, "%s\n", addr);
|
||||
}
|
||||
static DEVICE_ATTR(DevAddr, S_IRUGO, uwb_dev_DevAddr_show, NULL);
|
||||
|
||||
/*
|
||||
* Show the BPST of this device.
|
||||
*
|
||||
* Calculated from the receive time of the device's beacon and it's
|
||||
* slot number.
|
||||
*/
|
||||
static ssize_t uwb_dev_BPST_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_beca_e *bce;
|
||||
struct uwb_beacon_frame *bf;
|
||||
u16 bpst;
|
||||
|
||||
bce = uwb_dev->bce;
|
||||
mutex_lock(&bce->mutex);
|
||||
bf = (struct uwb_beacon_frame *)bce->be->BeaconInfo;
|
||||
bpst = bce->be->wBPSTOffset
|
||||
- (u16)(bf->Beacon_Slot_Number * UWB_BEACON_SLOT_LENGTH_US);
|
||||
mutex_unlock(&bce->mutex);
|
||||
|
||||
return sprintf(buf, "%d\n", bpst);
|
||||
}
|
||||
static DEVICE_ATTR(BPST, S_IRUGO, uwb_dev_BPST_show, NULL);
|
||||
|
||||
/*
|
||||
* Show the IEs a device is beaconing
|
||||
*
|
||||
* We need to access the beacon cache, so we just lock it really
|
||||
* quick, print the IEs and unlock.
|
||||
*
|
||||
* We have a reference on the cache entry, so that should be
|
||||
* quite safe.
|
||||
*/
|
||||
static ssize_t uwb_dev_IEs_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
|
||||
return uwb_bce_print_IEs(uwb_dev, uwb_dev->bce, buf, PAGE_SIZE);
|
||||
}
|
||||
static DEVICE_ATTR(IEs, S_IRUGO | S_IWUSR, uwb_dev_IEs_show, NULL);
|
||||
|
||||
static ssize_t uwb_dev_LQE_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_beca_e *bce = uwb_dev->bce;
|
||||
size_t result;
|
||||
|
||||
mutex_lock(&bce->mutex);
|
||||
result = stats_show(&uwb_dev->bce->lqe_stats, buf);
|
||||
mutex_unlock(&bce->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ssize_t uwb_dev_LQE_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_beca_e *bce = uwb_dev->bce;
|
||||
ssize_t result;
|
||||
|
||||
mutex_lock(&bce->mutex);
|
||||
result = stats_store(&uwb_dev->bce->lqe_stats, buf, size);
|
||||
mutex_unlock(&bce->mutex);
|
||||
return result;
|
||||
}
|
||||
static DEVICE_ATTR(LQE, S_IRUGO | S_IWUSR, uwb_dev_LQE_show, uwb_dev_LQE_store);
|
||||
|
||||
static ssize_t uwb_dev_RSSI_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_beca_e *bce = uwb_dev->bce;
|
||||
size_t result;
|
||||
|
||||
mutex_lock(&bce->mutex);
|
||||
result = stats_show(&uwb_dev->bce->rssi_stats, buf);
|
||||
mutex_unlock(&bce->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ssize_t uwb_dev_RSSI_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_beca_e *bce = uwb_dev->bce;
|
||||
ssize_t result;
|
||||
|
||||
mutex_lock(&bce->mutex);
|
||||
result = stats_store(&uwb_dev->bce->rssi_stats, buf, size);
|
||||
mutex_unlock(&bce->mutex);
|
||||
return result;
|
||||
}
|
||||
static DEVICE_ATTR(RSSI, S_IRUGO | S_IWUSR, uwb_dev_RSSI_show, uwb_dev_RSSI_store);
|
||||
|
||||
|
||||
static struct attribute *dev_attrs[] = {
|
||||
&dev_attr_EUI_48.attr,
|
||||
&dev_attr_DevAddr.attr,
|
||||
&dev_attr_BPST.attr,
|
||||
&dev_attr_IEs.attr,
|
||||
&dev_attr_LQE.attr,
|
||||
&dev_attr_RSSI.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group dev_attr_group = {
|
||||
.attrs = dev_attrs,
|
||||
};
|
||||
|
||||
static struct attribute_group *groups[] = {
|
||||
&dev_attr_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/**
|
||||
* Device SYSFS registration
|
||||
*
|
||||
*
|
||||
*/
|
||||
static int __uwb_dev_sys_add(struct uwb_dev *uwb_dev, struct device *parent_dev)
|
||||
{
|
||||
int result;
|
||||
struct device *dev;
|
||||
|
||||
d_fnstart(4, NULL, "(uwb_dev %p parent_dev %p)\n", uwb_dev, parent_dev);
|
||||
BUG_ON(parent_dev == NULL);
|
||||
|
||||
dev = &uwb_dev->dev;
|
||||
/* Device sysfs files are only useful for neighbor devices not
|
||||
local radio controllers. */
|
||||
if (&uwb_dev->rc->uwb_dev != uwb_dev)
|
||||
dev->groups = groups;
|
||||
dev->parent = parent_dev;
|
||||
dev_set_drvdata(dev, uwb_dev);
|
||||
|
||||
result = device_add(dev);
|
||||
d_fnend(4, NULL, "(uwb_dev %p parent_dev %p) = %d\n", uwb_dev, parent_dev, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void __uwb_dev_sys_rm(struct uwb_dev *uwb_dev)
|
||||
{
|
||||
d_fnstart(4, NULL, "(uwb_dev %p)\n", uwb_dev);
|
||||
dev_set_drvdata(&uwb_dev->dev, NULL);
|
||||
device_del(&uwb_dev->dev);
|
||||
d_fnend(4, NULL, "(uwb_dev %p) = void\n", uwb_dev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register and initialize a new UWB device
|
||||
*
|
||||
* Did you call uwb_dev_init() on it?
|
||||
*
|
||||
* @parent_rc: is the parent radio controller who has the link to the
|
||||
* device. When registering the UWB device that is a UWB
|
||||
* Radio Controller, we point back to it.
|
||||
*
|
||||
* If registering the device that is part of a radio, caller has set
|
||||
* rc->uwb_dev->dev. Otherwise it is to be left NULL--a new one will
|
||||
* be allocated.
|
||||
*/
|
||||
int uwb_dev_add(struct uwb_dev *uwb_dev, struct device *parent_dev,
|
||||
struct uwb_rc *parent_rc)
|
||||
{
|
||||
int result;
|
||||
struct device *dev;
|
||||
|
||||
BUG_ON(uwb_dev == NULL);
|
||||
BUG_ON(parent_dev == NULL);
|
||||
BUG_ON(parent_rc == NULL);
|
||||
|
||||
mutex_lock(&uwb_dev->mutex);
|
||||
dev = &uwb_dev->dev;
|
||||
uwb_dev->rc = parent_rc;
|
||||
result = __uwb_dev_sys_add(uwb_dev, parent_dev);
|
||||
if (result < 0)
|
||||
printk(KERN_ERR "UWB: unable to register dev %s with sysfs: %d\n",
|
||||
dev_name(dev), result);
|
||||
mutex_unlock(&uwb_dev->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void uwb_dev_rm(struct uwb_dev *uwb_dev)
|
||||
{
|
||||
mutex_lock(&uwb_dev->mutex);
|
||||
__uwb_dev_sys_rm(uwb_dev);
|
||||
mutex_unlock(&uwb_dev->mutex);
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
int __uwb_dev_try_get(struct device *dev, void *__target_uwb_dev)
|
||||
{
|
||||
struct uwb_dev *target_uwb_dev = __target_uwb_dev;
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
if (uwb_dev == target_uwb_dev) {
|
||||
uwb_dev_get(uwb_dev);
|
||||
return 1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a UWB device descriptor, validate and refcount it
|
||||
*
|
||||
* @returns NULL if the device does not exist or is quiescing; the ptr to
|
||||
* it otherwise.
|
||||
*/
|
||||
struct uwb_dev *uwb_dev_try_get(struct uwb_rc *rc, struct uwb_dev *uwb_dev)
|
||||
{
|
||||
if (uwb_dev_for_each(rc, __uwb_dev_try_get, uwb_dev))
|
||||
return uwb_dev;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_dev_try_get);
|
||||
|
||||
|
||||
/**
|
||||
* Remove a device from the system [grunt for other functions]
|
||||
*/
|
||||
int __uwb_dev_offair(struct uwb_dev *uwb_dev, struct uwb_rc *rc)
|
||||
{
|
||||
struct device *dev = &uwb_dev->dev;
|
||||
char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE];
|
||||
|
||||
d_fnstart(3, NULL, "(dev %p [uwb_dev %p], uwb_rc %p)\n", dev, uwb_dev, rc);
|
||||
uwb_mac_addr_print(macbuf, sizeof(macbuf), &uwb_dev->mac_addr);
|
||||
uwb_dev_addr_print(devbuf, sizeof(devbuf), &uwb_dev->dev_addr);
|
||||
dev_info(dev, "uwb device (mac %s dev %s) disconnected from %s %s\n",
|
||||
macbuf, devbuf,
|
||||
rc ? rc->uwb_dev.dev.parent->bus->name : "n/a",
|
||||
rc ? dev_name(rc->uwb_dev.dev.parent) : "");
|
||||
uwb_dev_rm(uwb_dev);
|
||||
uwb_dev_put(uwb_dev); /* for the creation in _onair() */
|
||||
d_fnend(3, NULL, "(dev %p [uwb_dev %p], uwb_rc %p) = 0\n", dev, uwb_dev, rc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A device went off the air, clean up after it!
|
||||
*
|
||||
* This is called by the UWB Daemon (through the beacon purge function
|
||||
* uwb_bcn_cache_purge) when it is detected that a device has been in
|
||||
* radio silence for a while.
|
||||
*
|
||||
* If this device is actually a local radio controller we don't need
|
||||
* to go through the offair process, as it is not registered as that.
|
||||
*
|
||||
* NOTE: uwb_bcn_cache.mutex is held!
|
||||
*/
|
||||
void uwbd_dev_offair(struct uwb_beca_e *bce)
|
||||
{
|
||||
struct uwb_dev *uwb_dev;
|
||||
|
||||
uwb_dev = bce->uwb_dev;
|
||||
if (uwb_dev) {
|
||||
uwb_notify(uwb_dev->rc, uwb_dev, UWB_NOTIF_OFFAIR);
|
||||
__uwb_dev_offair(uwb_dev, uwb_dev->rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A device went on the air, start it up!
|
||||
*
|
||||
* This is called by the UWB Daemon when it is detected that a device
|
||||
* has popped up in the radio range of the radio controller.
|
||||
*
|
||||
* It will just create the freaking device, register the beacon and
|
||||
* stuff and yatla, done.
|
||||
*
|
||||
*
|
||||
* NOTE: uwb_beca.mutex is held, bce->mutex is held
|
||||
*/
|
||||
void uwbd_dev_onair(struct uwb_rc *rc, struct uwb_beca_e *bce)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_dev *uwb_dev;
|
||||
char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE];
|
||||
|
||||
uwb_mac_addr_print(macbuf, sizeof(macbuf), bce->mac_addr);
|
||||
uwb_dev_addr_print(devbuf, sizeof(devbuf), &bce->dev_addr);
|
||||
uwb_dev = kzalloc(sizeof(struct uwb_dev), GFP_KERNEL);
|
||||
if (uwb_dev == NULL) {
|
||||
dev_err(dev, "new device %s: Cannot allocate memory\n",
|
||||
macbuf);
|
||||
return;
|
||||
}
|
||||
uwb_dev_init(uwb_dev); /* This sets refcnt to one, we own it */
|
||||
uwb_dev->mac_addr = *bce->mac_addr;
|
||||
uwb_dev->dev_addr = bce->dev_addr;
|
||||
dev_set_name(&uwb_dev->dev, macbuf);
|
||||
result = uwb_dev_add(uwb_dev, &rc->uwb_dev.dev, rc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "new device %s: cannot instantiate device\n",
|
||||
macbuf);
|
||||
goto error_dev_add;
|
||||
}
|
||||
/* plug the beacon cache */
|
||||
bce->uwb_dev = uwb_dev;
|
||||
uwb_dev->bce = bce;
|
||||
uwb_bce_get(bce); /* released in uwb_dev_sys_release() */
|
||||
dev_info(dev, "uwb device (mac %s dev %s) connected to %s %s\n",
|
||||
macbuf, devbuf, rc->uwb_dev.dev.parent->bus->name,
|
||||
dev_name(rc->uwb_dev.dev.parent));
|
||||
uwb_notify(rc, uwb_dev, UWB_NOTIF_ONAIR);
|
||||
return;
|
||||
|
||||
error_dev_add:
|
||||
kfree(uwb_dev);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over the list of UWB devices, calling a @function on each
|
||||
*
|
||||
* See docs for bus_for_each()....
|
||||
*
|
||||
* @rc: radio controller for the devices.
|
||||
* @function: function to call.
|
||||
* @priv: data to pass to @function.
|
||||
* @returns: 0 if no invocation of function() returned a value
|
||||
* different to zero. That value otherwise.
|
||||
*/
|
||||
int uwb_dev_for_each(struct uwb_rc *rc, uwb_dev_for_each_f function, void *priv)
|
||||
{
|
||||
return device_for_each_child(&rc->uwb_dev.dev, priv, function);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_dev_for_each);
|
|
@ -0,0 +1,495 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Life cycle of radio controllers
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
*
|
||||
* A UWB radio controller is also a UWB device, so it embeds one...
|
||||
*
|
||||
* List of RCs comes from the 'struct class uwb_rc_class'.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
#define D_LOCAL 1
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
static int uwb_rc_index_match(struct device *dev, void *data)
|
||||
{
|
||||
int *index = data;
|
||||
struct uwb_rc *rc = dev_get_drvdata(dev);
|
||||
|
||||
if (rc->index == *index)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct uwb_rc *uwb_rc_find_by_index(int index)
|
||||
{
|
||||
struct device *dev;
|
||||
struct uwb_rc *rc = NULL;
|
||||
|
||||
dev = class_find_device(&uwb_rc_class, NULL, &index, uwb_rc_index_match);
|
||||
if (dev)
|
||||
rc = dev_get_drvdata(dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int uwb_rc_new_index(void)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
for (;;) {
|
||||
if (!uwb_rc_find_by_index(index))
|
||||
return index;
|
||||
if (++index < 0)
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the backing device of a uwb_rc that has been dynamically allocated.
|
||||
*/
|
||||
static void uwb_rc_sys_release(struct device *dev)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = container_of(dev, struct uwb_dev, dev);
|
||||
struct uwb_rc *rc = container_of(uwb_dev, struct uwb_rc, uwb_dev);
|
||||
|
||||
uwb_rc_neh_destroy(rc);
|
||||
uwb_rc_ie_release(rc);
|
||||
d_printf(1, dev, "freed uwb_rc %p\n", rc);
|
||||
kfree(rc);
|
||||
}
|
||||
|
||||
|
||||
void uwb_rc_init(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = &rc->uwb_dev;
|
||||
|
||||
uwb_dev_init(uwb_dev);
|
||||
rc->uwb_dev.dev.class = &uwb_rc_class;
|
||||
rc->uwb_dev.dev.release = uwb_rc_sys_release;
|
||||
uwb_rc_neh_create(rc);
|
||||
rc->beaconing = -1;
|
||||
rc->scan_type = UWB_SCAN_DISABLED;
|
||||
INIT_LIST_HEAD(&rc->notifs_chain.list);
|
||||
mutex_init(&rc->notifs_chain.mutex);
|
||||
uwb_drp_avail_init(rc);
|
||||
uwb_rc_ie_init(rc);
|
||||
uwb_rsv_init(rc);
|
||||
uwb_rc_pal_init(rc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_init);
|
||||
|
||||
|
||||
struct uwb_rc *uwb_rc_alloc(void)
|
||||
{
|
||||
struct uwb_rc *rc;
|
||||
rc = kzalloc(sizeof(*rc), GFP_KERNEL);
|
||||
if (rc == NULL)
|
||||
return NULL;
|
||||
uwb_rc_init(rc);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_alloc);
|
||||
|
||||
static struct attribute *rc_attrs[] = {
|
||||
&dev_attr_mac_address.attr,
|
||||
&dev_attr_scan.attr,
|
||||
&dev_attr_beacon.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group rc_attr_group = {
|
||||
.attrs = rc_attrs,
|
||||
};
|
||||
|
||||
/*
|
||||
* Registration of sysfs specific stuff
|
||||
*/
|
||||
static int uwb_rc_sys_add(struct uwb_rc *rc)
|
||||
{
|
||||
return sysfs_create_group(&rc->uwb_dev.dev.kobj, &rc_attr_group);
|
||||
}
|
||||
|
||||
|
||||
static void __uwb_rc_sys_rm(struct uwb_rc *rc)
|
||||
{
|
||||
sysfs_remove_group(&rc->uwb_dev.dev.kobj, &rc_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rc_mac_addr_setup - get an RC's EUI-48 address or set it
|
||||
* @rc: the radio controller.
|
||||
*
|
||||
* If the EUI-48 address is 00:00:00:00:00:00 or FF:FF:FF:FF:FF:FF
|
||||
* then a random locally administered EUI-48 is generated and set on
|
||||
* the device. The probability of address collisions is sufficiently
|
||||
* unlikely (1/2^40 = 9.1e-13) that they're not checked for.
|
||||
*/
|
||||
static
|
||||
int uwb_rc_mac_addr_setup(struct uwb_rc *rc)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_dev *uwb_dev = &rc->uwb_dev;
|
||||
char devname[UWB_ADDR_STRSIZE];
|
||||
struct uwb_mac_addr addr;
|
||||
|
||||
result = uwb_rc_mac_addr_get(rc, &addr);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot retrieve UWB EUI-48 address: %d\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (uwb_mac_addr_unset(&addr) || uwb_mac_addr_bcast(&addr)) {
|
||||
addr.data[0] = 0x02; /* locally adminstered and unicast */
|
||||
get_random_bytes(&addr.data[1], sizeof(addr.data)-1);
|
||||
|
||||
result = uwb_rc_mac_addr_set(rc, &addr);
|
||||
if (result < 0) {
|
||||
uwb_mac_addr_print(devname, sizeof(devname), &addr);
|
||||
dev_err(dev, "cannot set EUI-48 address %s: %d\n",
|
||||
devname, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
uwb_dev->mac_addr = addr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int uwb_rc_setup(struct uwb_rc *rc)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
|
||||
result = uwb_rc_reset(rc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot reset UWB radio: %d\n", result);
|
||||
goto error;
|
||||
}
|
||||
result = uwb_rc_mac_addr_setup(rc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot setup UWB MAC address: %d\n", result);
|
||||
goto error;
|
||||
}
|
||||
result = uwb_rc_dev_addr_assign(rc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot assign UWB DevAddr: %d\n", result);
|
||||
goto error;
|
||||
}
|
||||
result = uwb_rc_ie_setup(rc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot setup IE subsystem: %d\n", result);
|
||||
goto error_ie_setup;
|
||||
}
|
||||
result = uwb_rsv_setup(rc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot setup reservation subsystem: %d\n", result);
|
||||
goto error_rsv_setup;
|
||||
}
|
||||
uwb_dbg_add_rc(rc);
|
||||
return 0;
|
||||
|
||||
error_rsv_setup:
|
||||
uwb_rc_ie_release(rc);
|
||||
error_ie_setup:
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a new UWB radio controller
|
||||
*
|
||||
* Did you call uwb_rc_init() on your rc?
|
||||
*
|
||||
* We assume that this is being called with a > 0 refcount on
|
||||
* it [through ops->{get|put}_device(). We'll take our own, though.
|
||||
*
|
||||
* @parent_dev is our real device, the one that provides the actual UWB device
|
||||
*/
|
||||
int uwb_rc_add(struct uwb_rc *rc, struct device *parent_dev, void *priv)
|
||||
{
|
||||
int result;
|
||||
struct device *dev;
|
||||
char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE];
|
||||
|
||||
rc->index = uwb_rc_new_index();
|
||||
|
||||
dev = &rc->uwb_dev.dev;
|
||||
dev_set_name(dev, "uwb%d", rc->index);
|
||||
|
||||
rc->priv = priv;
|
||||
|
||||
result = rc->start(rc);
|
||||
if (result < 0)
|
||||
goto error_rc_start;
|
||||
|
||||
result = uwb_rc_setup(rc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "cannot setup UWB radio controller: %d\n", result);
|
||||
goto error_rc_setup;
|
||||
}
|
||||
|
||||
result = uwb_dev_add(&rc->uwb_dev, parent_dev, rc);
|
||||
if (result < 0 && result != -EADDRNOTAVAIL)
|
||||
goto error_dev_add;
|
||||
|
||||
result = uwb_rc_sys_add(rc);
|
||||
if (result < 0) {
|
||||
dev_err(parent_dev, "cannot register UWB radio controller "
|
||||
"dev attributes: %d\n", result);
|
||||
goto error_sys_add;
|
||||
}
|
||||
|
||||
uwb_mac_addr_print(macbuf, sizeof(macbuf), &rc->uwb_dev.mac_addr);
|
||||
uwb_dev_addr_print(devbuf, sizeof(devbuf), &rc->uwb_dev.dev_addr);
|
||||
dev_info(dev,
|
||||
"new uwb radio controller (mac %s dev %s) on %s %s\n",
|
||||
macbuf, devbuf, parent_dev->bus->name, dev_name(parent_dev));
|
||||
rc->ready = 1;
|
||||
return 0;
|
||||
|
||||
error_sys_add:
|
||||
uwb_dev_rm(&rc->uwb_dev);
|
||||
error_dev_add:
|
||||
error_rc_setup:
|
||||
rc->stop(rc);
|
||||
uwbd_flush(rc);
|
||||
error_rc_start:
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_add);
|
||||
|
||||
|
||||
static int uwb_dev_offair_helper(struct device *dev, void *priv)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
|
||||
return __uwb_dev_offair(uwb_dev, uwb_dev->rc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a Radio Controller; stop beaconing/scanning, disconnect all children
|
||||
*/
|
||||
void uwb_rc_rm(struct uwb_rc *rc)
|
||||
{
|
||||
rc->ready = 0;
|
||||
|
||||
uwb_dbg_del_rc(rc);
|
||||
uwb_rsv_cleanup(rc);
|
||||
uwb_rc_ie_rm(rc, UWB_IDENTIFICATION_IE);
|
||||
if (rc->beaconing >= 0)
|
||||
uwb_rc_beacon(rc, -1, 0);
|
||||
if (rc->scan_type != UWB_SCAN_DISABLED)
|
||||
uwb_rc_scan(rc, rc->scanning, UWB_SCAN_DISABLED, 0);
|
||||
uwb_rc_reset(rc);
|
||||
|
||||
rc->stop(rc);
|
||||
uwbd_flush(rc);
|
||||
|
||||
uwb_dev_lock(&rc->uwb_dev);
|
||||
rc->priv = NULL;
|
||||
rc->cmd = NULL;
|
||||
uwb_dev_unlock(&rc->uwb_dev);
|
||||
mutex_lock(&uwb_beca.mutex);
|
||||
uwb_dev_for_each(rc, uwb_dev_offair_helper, NULL);
|
||||
__uwb_rc_sys_rm(rc);
|
||||
mutex_unlock(&uwb_beca.mutex);
|
||||
uwb_dev_rm(&rc->uwb_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_rm);
|
||||
|
||||
static int find_rc_try_get(struct device *dev, void *data)
|
||||
{
|
||||
struct uwb_rc *target_rc = data;
|
||||
struct uwb_rc *rc = dev_get_drvdata(dev);
|
||||
|
||||
if (rc == NULL) {
|
||||
WARN_ON(1);
|
||||
return 0;
|
||||
}
|
||||
if (rc == target_rc) {
|
||||
if (rc->ready == 0)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a radio controller descriptor, validate and refcount it
|
||||
*
|
||||
* @returns NULL if the rc does not exist or is quiescing; the ptr to
|
||||
* it otherwise.
|
||||
*/
|
||||
struct uwb_rc *__uwb_rc_try_get(struct uwb_rc *target_rc)
|
||||
{
|
||||
struct device *dev;
|
||||
struct uwb_rc *rc = NULL;
|
||||
|
||||
dev = class_find_device(&uwb_rc_class, NULL, target_rc,
|
||||
find_rc_try_get);
|
||||
if (dev) {
|
||||
rc = dev_get_drvdata(dev);
|
||||
__uwb_rc_get(rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__uwb_rc_try_get);
|
||||
|
||||
/*
|
||||
* RC get for external refcount acquirers...
|
||||
*
|
||||
* Increments the refcount of the device and it's backend modules
|
||||
*/
|
||||
static inline struct uwb_rc *uwb_rc_get(struct uwb_rc *rc)
|
||||
{
|
||||
if (rc->ready == 0)
|
||||
return NULL;
|
||||
uwb_dev_get(&rc->uwb_dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int find_rc_grandpa(struct device *dev, void *data)
|
||||
{
|
||||
struct device *grandpa_dev = data;
|
||||
struct uwb_rc *rc = dev_get_drvdata(dev);
|
||||
|
||||
if (rc->uwb_dev.dev.parent->parent == grandpa_dev) {
|
||||
rc = uwb_rc_get(rc);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate and refcount a radio controller given a common grand-parent
|
||||
*
|
||||
* @grandpa_dev Pointer to the 'grandparent' device structure.
|
||||
* @returns NULL If the rc does not exist or is quiescing; the ptr to
|
||||
* it otherwise, properly referenced.
|
||||
*
|
||||
* The Radio Control interface (or the UWB Radio Controller) is always
|
||||
* an interface of a device. The parent is the interface, the
|
||||
* grandparent is the device that encapsulates the interface.
|
||||
*
|
||||
* There is no need to lock around as the "grandpa" would be
|
||||
* refcounted by the target, and to remove the referemes, the
|
||||
* uwb_rc_class->sem would have to be taken--we hold it, ergo we
|
||||
* should be safe.
|
||||
*/
|
||||
struct uwb_rc *uwb_rc_get_by_grandpa(const struct device *grandpa_dev)
|
||||
{
|
||||
struct device *dev;
|
||||
struct uwb_rc *rc = NULL;
|
||||
|
||||
dev = class_find_device(&uwb_rc_class, NULL, (void *)grandpa_dev,
|
||||
find_rc_grandpa);
|
||||
if (dev)
|
||||
rc = dev_get_drvdata(dev);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_get_by_grandpa);
|
||||
|
||||
/**
|
||||
* Find a radio controller by device address
|
||||
*
|
||||
* @returns the pointer to the radio controller, properly referenced
|
||||
*/
|
||||
static int find_rc_dev(struct device *dev, void *data)
|
||||
{
|
||||
struct uwb_dev_addr *addr = data;
|
||||
struct uwb_rc *rc = dev_get_drvdata(dev);
|
||||
|
||||
if (rc == NULL) {
|
||||
WARN_ON(1);
|
||||
return 0;
|
||||
}
|
||||
if (!uwb_dev_addr_cmp(&rc->uwb_dev.dev_addr, addr)) {
|
||||
rc = uwb_rc_get(rc);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct uwb_rc *uwb_rc_get_by_dev(const struct uwb_dev_addr *addr)
|
||||
{
|
||||
struct device *dev;
|
||||
struct uwb_rc *rc = NULL;
|
||||
|
||||
dev = class_find_device(&uwb_rc_class, NULL, (void *)addr,
|
||||
find_rc_dev);
|
||||
if (dev)
|
||||
rc = dev_get_drvdata(dev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_get_by_dev);
|
||||
|
||||
/**
|
||||
* Drop a reference on a radio controller
|
||||
*
|
||||
* This is the version that should be done by entities external to the
|
||||
* UWB Radio Control stack (ie: clients of the API).
|
||||
*/
|
||||
void uwb_rc_put(struct uwb_rc *rc)
|
||||
{
|
||||
__uwb_rc_put(rc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_put);
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
*/
|
||||
ssize_t uwb_rc_print_IEs(struct uwb_rc *uwb_rc, char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct uwb_rc_evt_get_ie *ie_info;
|
||||
struct uwb_buf_ctx ctx;
|
||||
|
||||
result = uwb_rc_get_ie(uwb_rc, &ie_info);
|
||||
if (result < 0)
|
||||
goto error_get_ie;
|
||||
ctx.buf = buf;
|
||||
ctx.size = size;
|
||||
ctx.bytes = 0;
|
||||
uwb_ie_for_each(&uwb_rc->uwb_dev, uwb_ie_dump_hex, &ctx,
|
||||
ie_info->IEData, result - sizeof(*ie_info));
|
||||
result = ctx.bytes;
|
||||
kfree(ie_info);
|
||||
error_get_ie:
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,616 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: Radio Control Interface (WUSB[8])
|
||||
* Notification and Event 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.
|
||||
*
|
||||
*
|
||||
* The RC interface of the Host Wire Adapter (USB dongle) or WHCI PCI
|
||||
* card delivers a stream of notifications and events to the
|
||||
* notification end event endpoint or area. This code takes care of
|
||||
* getting a buffer with that data, breaking it up in separate
|
||||
* notifications and events and then deliver those.
|
||||
*
|
||||
* Events are answers to commands and they carry a context ID that
|
||||
* associates them to the command. Notifications are that,
|
||||
* notifications, they come out of the blue and have a context ID of
|
||||
* zero. Think of the context ID kind of like a handler. The
|
||||
* uwb_rc_neh_* code deals with managing context IDs.
|
||||
*
|
||||
* This is why you require a handle to operate on a UWB host. When you
|
||||
* open a handle a context ID is assigned to you.
|
||||
*
|
||||
* So, as it is done is:
|
||||
*
|
||||
* 1. Add an event handler [uwb_rc_neh_add()] (assigns a ctx id)
|
||||
* 2. Issue command [rc->cmd(rc, ...)]
|
||||
* 3. Arm the timeout timer [uwb_rc_neh_arm()]
|
||||
* 4, Release the reference to the neh [uwb_rc_neh_put()]
|
||||
* 5. Wait for the callback
|
||||
* 6. Command result (RCEB) is passed to the callback
|
||||
*
|
||||
* If (2) fails, you should remove the handle [uwb_rc_neh_rm()]
|
||||
* instead of arming the timer.
|
||||
*
|
||||
* Handles are for using in *serialized* code, single thread.
|
||||
*
|
||||
* When the notification/event comes, the IRQ handler/endpoint
|
||||
* callback passes the data read to uwb_rc_neh_grok() which will break
|
||||
* it up in a discrete series of events, look up who is listening for
|
||||
* them and execute the pertinent callbacks.
|
||||
*
|
||||
* If the reader detects an error while reading the data stream, call
|
||||
* uwb_rc_neh_error().
|
||||
*
|
||||
* CONSTRAINTS/ASSUMPTIONS:
|
||||
*
|
||||
* - Most notifications/events are small (less thank .5k), copying
|
||||
* around is ok.
|
||||
*
|
||||
* - Notifications/events are ALWAYS smaller than PAGE_SIZE
|
||||
*
|
||||
* - Notifications/events always come in a single piece (ie: a buffer
|
||||
* will always contain entire notifications/events).
|
||||
*
|
||||
* - we cannot know in advance how long each event is (because they
|
||||
* lack a length field in their header--smart move by the standards
|
||||
* body, btw). So we need a facility to get the event size given the
|
||||
* header. This is what the EST code does (notif/Event Size
|
||||
* Tables), check nest.c--as well, you can associate the size to
|
||||
* the handle [w/ neh->extra_size()].
|
||||
*
|
||||
* - Most notifications/events are fixed size; only a few are variable
|
||||
* size (NEST takes care of that).
|
||||
*
|
||||
* - Listeners of events expect them, so they usually provide a
|
||||
* buffer, as they know the size. Listeners to notifications don't,
|
||||
* so we allocate their buffers dynamically.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include "uwb-internal.h"
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/*
|
||||
* UWB Radio Controller Notification/Event Handle
|
||||
*
|
||||
* Represents an entity waiting for an event coming from the UWB Radio
|
||||
* Controller with a given context id (context) and type (evt_type and
|
||||
* evt). On reception of the notification/event, the callback (cb) is
|
||||
* called with the event.
|
||||
*
|
||||
* If the timer expires before the event is received, the callback is
|
||||
* called with -ETIMEDOUT as the event size.
|
||||
*/
|
||||
struct uwb_rc_neh {
|
||||
struct kref kref;
|
||||
|
||||
struct uwb_rc *rc;
|
||||
u8 evt_type;
|
||||
__le16 evt;
|
||||
u8 context;
|
||||
uwb_rc_cmd_cb_f cb;
|
||||
void *arg;
|
||||
|
||||
struct timer_list timer;
|
||||
struct list_head list_node;
|
||||
};
|
||||
|
||||
static void uwb_rc_neh_timer(unsigned long arg);
|
||||
|
||||
static void uwb_rc_neh_release(struct kref *kref)
|
||||
{
|
||||
struct uwb_rc_neh *neh = container_of(kref, struct uwb_rc_neh, kref);
|
||||
|
||||
kfree(neh);
|
||||
}
|
||||
|
||||
static void uwb_rc_neh_get(struct uwb_rc_neh *neh)
|
||||
{
|
||||
kref_get(&neh->kref);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rc_neh_put - release reference to a neh
|
||||
* @neh: the neh
|
||||
*/
|
||||
void uwb_rc_neh_put(struct uwb_rc_neh *neh)
|
||||
{
|
||||
kref_put(&neh->kref, uwb_rc_neh_release);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assigns @neh a context id from @rc's pool
|
||||
*
|
||||
* @rc: UWB Radio Controller descriptor; @rc->neh_lock taken
|
||||
* @neh: Notification/Event Handle
|
||||
* @returns 0 if context id was assigned ok; < 0 errno on error (if
|
||||
* all the context IDs are taken).
|
||||
*
|
||||
* (assumes @wa is locked).
|
||||
*
|
||||
* NOTE: WUSB spec reserves context ids 0x00 for notifications and
|
||||
* 0xff is invalid, so they must not be used. Initialization
|
||||
* fills up those two in the bitmap so they are not allocated.
|
||||
*
|
||||
* We spread the allocation around to reduce the posiblity of two
|
||||
* consecutive opened @neh's getting the same context ID assigned (to
|
||||
* avoid surprises with late events that timed out long time ago). So
|
||||
* first we search from where @rc->ctx_roll is, if not found, we
|
||||
* search from zero.
|
||||
*/
|
||||
static
|
||||
int __uwb_rc_ctx_get(struct uwb_rc *rc, struct uwb_rc_neh *neh)
|
||||
{
|
||||
int result;
|
||||
result = find_next_zero_bit(rc->ctx_bm, UWB_RC_CTX_MAX,
|
||||
rc->ctx_roll++);
|
||||
if (result < UWB_RC_CTX_MAX)
|
||||
goto found;
|
||||
result = find_first_zero_bit(rc->ctx_bm, UWB_RC_CTX_MAX);
|
||||
if (result < UWB_RC_CTX_MAX)
|
||||
goto found;
|
||||
return -ENFILE;
|
||||
found:
|
||||
set_bit(result, rc->ctx_bm);
|
||||
neh->context = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/** Releases @neh's context ID back to @rc (@rc->neh_lock is locked). */
|
||||
static
|
||||
void __uwb_rc_ctx_put(struct uwb_rc *rc, struct uwb_rc_neh *neh)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
if (neh->context == 0)
|
||||
return;
|
||||
if (test_bit(neh->context, rc->ctx_bm) == 0) {
|
||||
dev_err(dev, "context %u not set in bitmap\n",
|
||||
neh->context);
|
||||
WARN_ON(1);
|
||||
}
|
||||
clear_bit(neh->context, rc->ctx_bm);
|
||||
neh->context = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rc_neh_add - add a neh for a radio controller command
|
||||
* @rc: the radio controller
|
||||
* @cmd: the radio controller command
|
||||
* @expected_type: the type of the expected response event
|
||||
* @expected_event: the expected event ID
|
||||
* @cb: callback for when the event is received
|
||||
* @arg: argument for the callback
|
||||
*
|
||||
* Creates a neh and adds it to the list of those waiting for an
|
||||
* event. A context ID will be assigned to the command.
|
||||
*/
|
||||
struct uwb_rc_neh *uwb_rc_neh_add(struct uwb_rc *rc, struct uwb_rccb *cmd,
|
||||
u8 expected_type, u16 expected_event,
|
||||
uwb_rc_cmd_cb_f cb, void *arg)
|
||||
{
|
||||
int result;
|
||||
unsigned long flags;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_rc_neh *neh;
|
||||
|
||||
neh = kzalloc(sizeof(*neh), GFP_KERNEL);
|
||||
if (neh == NULL) {
|
||||
result = -ENOMEM;
|
||||
goto error_kzalloc;
|
||||
}
|
||||
|
||||
kref_init(&neh->kref);
|
||||
INIT_LIST_HEAD(&neh->list_node);
|
||||
init_timer(&neh->timer);
|
||||
neh->timer.function = uwb_rc_neh_timer;
|
||||
neh->timer.data = (unsigned long)neh;
|
||||
|
||||
neh->rc = rc;
|
||||
neh->evt_type = expected_type;
|
||||
neh->evt = cpu_to_le16(expected_event);
|
||||
neh->cb = cb;
|
||||
neh->arg = arg;
|
||||
|
||||
spin_lock_irqsave(&rc->neh_lock, flags);
|
||||
result = __uwb_rc_ctx_get(rc, neh);
|
||||
if (result >= 0) {
|
||||
cmd->bCommandContext = neh->context;
|
||||
list_add_tail(&neh->list_node, &rc->neh_list);
|
||||
uwb_rc_neh_get(neh);
|
||||
}
|
||||
spin_unlock_irqrestore(&rc->neh_lock, flags);
|
||||
if (result < 0)
|
||||
goto error_ctx_get;
|
||||
|
||||
return neh;
|
||||
|
||||
error_ctx_get:
|
||||
kfree(neh);
|
||||
error_kzalloc:
|
||||
dev_err(dev, "cannot open handle to radio controller: %d\n", result);
|
||||
return ERR_PTR(result);
|
||||
}
|
||||
|
||||
static void __uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh)
|
||||
{
|
||||
del_timer(&neh->timer);
|
||||
__uwb_rc_ctx_put(rc, neh);
|
||||
list_del(&neh->list_node);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rc_neh_rm - remove a neh.
|
||||
* @rc: the radio controller
|
||||
* @neh: the neh to remove
|
||||
*
|
||||
* Remove an active neh immediately instead of waiting for the event
|
||||
* (or a time out).
|
||||
*/
|
||||
void uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&rc->neh_lock, flags);
|
||||
__uwb_rc_neh_rm(rc, neh);
|
||||
spin_unlock_irqrestore(&rc->neh_lock, flags);
|
||||
|
||||
uwb_rc_neh_put(neh);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rc_neh_arm - arm an event handler timeout timer
|
||||
*
|
||||
* @rc: UWB Radio Controller
|
||||
* @neh: Notification/event handler for @rc
|
||||
*
|
||||
* The timer is only armed if the neh is active.
|
||||
*/
|
||||
void uwb_rc_neh_arm(struct uwb_rc *rc, struct uwb_rc_neh *neh)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&rc->neh_lock, flags);
|
||||
if (neh->context)
|
||||
mod_timer(&neh->timer,
|
||||
jiffies + msecs_to_jiffies(UWB_RC_CMD_TIMEOUT_MS));
|
||||
spin_unlock_irqrestore(&rc->neh_lock, flags);
|
||||
}
|
||||
|
||||
static void uwb_rc_neh_cb(struct uwb_rc_neh *neh, struct uwb_rceb *rceb, size_t size)
|
||||
{
|
||||
(*neh->cb)(neh->rc, neh->arg, rceb, size);
|
||||
uwb_rc_neh_put(neh);
|
||||
}
|
||||
|
||||
static bool uwb_rc_neh_match(struct uwb_rc_neh *neh, const struct uwb_rceb *rceb)
|
||||
{
|
||||
return neh->evt_type == rceb->bEventType
|
||||
&& neh->evt == rceb->wEvent
|
||||
&& neh->context == rceb->bEventContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the handle waiting for a RC Radio Control Event
|
||||
*
|
||||
* @rc: UWB Radio Controller
|
||||
* @rceb: Pointer to the RCEB buffer
|
||||
* @event_size: Pointer to the size of the RCEB buffer. Might be
|
||||
* adjusted to take into account the @neh->extra_size
|
||||
* settings.
|
||||
*
|
||||
* If the listener has no buffer (NULL buffer), one is allocated for
|
||||
* the right size (the amount of data received). @neh->ptr will point
|
||||
* to the event payload, which always starts with a 'struct
|
||||
* uwb_rceb'. kfree() it when done.
|
||||
*/
|
||||
static
|
||||
struct uwb_rc_neh *uwb_rc_neh_lookup(struct uwb_rc *rc,
|
||||
const struct uwb_rceb *rceb)
|
||||
{
|
||||
struct uwb_rc_neh *neh = NULL, *h;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&rc->neh_lock, flags);
|
||||
|
||||
list_for_each_entry(h, &rc->neh_list, list_node) {
|
||||
if (uwb_rc_neh_match(h, rceb)) {
|
||||
neh = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (neh)
|
||||
__uwb_rc_neh_rm(rc, neh);
|
||||
|
||||
spin_unlock_irqrestore(&rc->neh_lock, flags);
|
||||
|
||||
return neh;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process notifications coming from the radio control interface
|
||||
*
|
||||
* @rc: UWB Radio Control Interface descriptor
|
||||
* @neh: Notification/Event Handler @neh->ptr points to
|
||||
* @uwb_evt->buffer.
|
||||
*
|
||||
* This function is called by the event/notif handling subsystem when
|
||||
* notifications arrive (hwarc_probe() arms a notification/event handle
|
||||
* that calls back this function for every received notification; this
|
||||
* function then will rearm itself).
|
||||
*
|
||||
* Notification data buffers are dynamically allocated by the NEH
|
||||
* handling code in neh.c [uwb_rc_neh_lookup()]. What is actually
|
||||
* allocated is space to contain the notification data.
|
||||
*
|
||||
* Buffers are prefixed with a Radio Control Event Block (RCEB) as
|
||||
* defined by the WUSB Wired-Adapter Radio Control interface. We
|
||||
* just use it for the notification code.
|
||||
*
|
||||
* On each case statement we just transcode endianess of the different
|
||||
* fields. We declare a pointer to a RCI definition of an event, and
|
||||
* then to a UWB definition of the same event (which are the same,
|
||||
* remember). Event if we use different pointers
|
||||
*/
|
||||
static
|
||||
void uwb_rc_notif(struct uwb_rc *rc, struct uwb_rceb *rceb, ssize_t size)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_event *uwb_evt;
|
||||
|
||||
if (size == -ESHUTDOWN)
|
||||
return;
|
||||
if (size < 0) {
|
||||
dev_err(dev, "ignoring event with error code %zu\n",
|
||||
size);
|
||||
return;
|
||||
}
|
||||
|
||||
uwb_evt = kzalloc(sizeof(*uwb_evt), GFP_ATOMIC);
|
||||
if (unlikely(uwb_evt == NULL)) {
|
||||
dev_err(dev, "no memory to queue event 0x%02x/%04x/%02x\n",
|
||||
rceb->bEventType, le16_to_cpu(rceb->wEvent),
|
||||
rceb->bEventContext);
|
||||
return;
|
||||
}
|
||||
uwb_evt->rc = __uwb_rc_get(rc); /* will be put by uwbd's uwbd_event_handle() */
|
||||
uwb_evt->ts_jiffies = jiffies;
|
||||
uwb_evt->type = UWB_EVT_TYPE_NOTIF;
|
||||
uwb_evt->notif.size = size;
|
||||
uwb_evt->notif.rceb = rceb;
|
||||
|
||||
switch (le16_to_cpu(rceb->wEvent)) {
|
||||
/* Trap some vendor specific events
|
||||
*
|
||||
* FIXME: move this to handling in ptc-est, where we
|
||||
* register a NULL event handler for these two guys
|
||||
* using the Intel IDs.
|
||||
*/
|
||||
case 0x0103:
|
||||
dev_info(dev, "FIXME: DEVICE ADD\n");
|
||||
return;
|
||||
case 0x0104:
|
||||
dev_info(dev, "FIXME: DEVICE RM\n");
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
uwbd_event_queue(uwb_evt);
|
||||
}
|
||||
|
||||
static void uwb_rc_neh_grok_event(struct uwb_rc *rc, struct uwb_rceb *rceb, size_t size)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_rc_neh *neh;
|
||||
struct uwb_rceb *notif;
|
||||
|
||||
if (rceb->bEventContext == 0) {
|
||||
notif = kmalloc(size, GFP_ATOMIC);
|
||||
if (notif) {
|
||||
memcpy(notif, rceb, size);
|
||||
uwb_rc_notif(rc, notif, size);
|
||||
} else
|
||||
dev_err(dev, "event 0x%02x/%04x/%02x (%zu bytes): no memory\n",
|
||||
rceb->bEventType, le16_to_cpu(rceb->wEvent),
|
||||
rceb->bEventContext, size);
|
||||
} else {
|
||||
neh = uwb_rc_neh_lookup(rc, rceb);
|
||||
if (neh)
|
||||
uwb_rc_neh_cb(neh, rceb, size);
|
||||
else
|
||||
dev_warn(dev, "event 0x%02x/%04x/%02x (%zu bytes): nobody cared\n",
|
||||
rceb->bEventType, le16_to_cpu(rceb->wEvent),
|
||||
rceb->bEventContext, size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a buffer with one or more UWB RC events/notifications, break
|
||||
* them up and dispatch them.
|
||||
*
|
||||
* @rc: UWB Radio Controller
|
||||
* @buf: Buffer with the stream of notifications/events
|
||||
* @buf_size: Amount of data in the buffer
|
||||
*
|
||||
* Note each notification/event starts always with a 'struct
|
||||
* uwb_rceb', so the minimum size if 4 bytes.
|
||||
*
|
||||
* The device may pass us events formatted differently than expected.
|
||||
* These are first filtered, potentially creating a new event in a new
|
||||
* memory location. If a new event is created by the filter it is also
|
||||
* freed here.
|
||||
*
|
||||
* For each notif/event, tries to guess the size looking at the EST
|
||||
* tables, then looks for a neh that is waiting for that event and if
|
||||
* found, copies the payload to the neh's buffer and calls it back. If
|
||||
* not, the data is ignored.
|
||||
*
|
||||
* Note that if we can't find a size description in the EST tables, we
|
||||
* still might find a size in the 'neh' handle in uwb_rc_neh_lookup().
|
||||
*
|
||||
* Assumptions:
|
||||
*
|
||||
* @rc->neh_lock is NOT taken
|
||||
*
|
||||
* We keep track of various sizes here:
|
||||
* size: contains the size of the buffer that is processed for the
|
||||
* incoming event. this buffer may contain events that are not
|
||||
* formatted as WHCI.
|
||||
* real_size: the actual space taken by this event in the buffer.
|
||||
* We need to keep track of the real size of an event to be able to
|
||||
* advance the buffer correctly.
|
||||
* event_size: the size of the event as expected by the core layer
|
||||
* [OR] the size of the event after filtering. if the filtering
|
||||
* created a new event in a new memory location then this is
|
||||
* effectively the size of a new event buffer
|
||||
*/
|
||||
void uwb_rc_neh_grok(struct uwb_rc *rc, void *buf, size_t buf_size)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
void *itr;
|
||||
struct uwb_rceb *rceb;
|
||||
size_t size, real_size, event_size;
|
||||
int needtofree;
|
||||
|
||||
d_fnstart(3, dev, "(rc %p buf %p %zu buf_size)\n", rc, buf, buf_size);
|
||||
d_printf(2, dev, "groking event block: %zu bytes\n", buf_size);
|
||||
itr = buf;
|
||||
size = buf_size;
|
||||
while (size > 0) {
|
||||
if (size < sizeof(*rceb)) {
|
||||
dev_err(dev, "not enough data in event buffer to "
|
||||
"process incoming events (%zu left, minimum is "
|
||||
"%zu)\n", size, sizeof(*rceb));
|
||||
break;
|
||||
}
|
||||
|
||||
rceb = itr;
|
||||
if (rc->filter_event) {
|
||||
needtofree = rc->filter_event(rc, &rceb, size,
|
||||
&real_size, &event_size);
|
||||
if (needtofree < 0 && needtofree != -ENOANO) {
|
||||
dev_err(dev, "BUG: Unable to filter event "
|
||||
"(0x%02x/%04x/%02x) from "
|
||||
"device. \n", rceb->bEventType,
|
||||
le16_to_cpu(rceb->wEvent),
|
||||
rceb->bEventContext);
|
||||
break;
|
||||
}
|
||||
} else
|
||||
needtofree = -ENOANO;
|
||||
/* do real processing if there was no filtering or the
|
||||
* filtering didn't act */
|
||||
if (needtofree == -ENOANO) {
|
||||
ssize_t ret = uwb_est_find_size(rc, rceb, size);
|
||||
if (ret < 0)
|
||||
break;
|
||||
if (ret > size) {
|
||||
dev_err(dev, "BUG: hw sent incomplete event "
|
||||
"0x%02x/%04x/%02x (%zd bytes), only got "
|
||||
"%zu bytes. We don't handle that.\n",
|
||||
rceb->bEventType, le16_to_cpu(rceb->wEvent),
|
||||
rceb->bEventContext, ret, size);
|
||||
break;
|
||||
}
|
||||
real_size = event_size = ret;
|
||||
}
|
||||
uwb_rc_neh_grok_event(rc, rceb, event_size);
|
||||
|
||||
if (needtofree == 1)
|
||||
kfree(rceb);
|
||||
|
||||
itr += real_size;
|
||||
size -= real_size;
|
||||
d_printf(2, dev, "consumed %zd bytes, %zu left\n",
|
||||
event_size, size);
|
||||
}
|
||||
d_fnend(3, dev, "(rc %p buf %p %zu buf_size) = void\n", rc, buf, buf_size);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_neh_grok);
|
||||
|
||||
|
||||
/**
|
||||
* The entity that reads from the device notification/event channel has
|
||||
* detected an error.
|
||||
*
|
||||
* @rc: UWB Radio Controller
|
||||
* @error: Errno error code
|
||||
*
|
||||
*/
|
||||
void uwb_rc_neh_error(struct uwb_rc *rc, int error)
|
||||
{
|
||||
struct uwb_rc_neh *neh, *next;
|
||||
unsigned long flags;
|
||||
|
||||
BUG_ON(error >= 0);
|
||||
spin_lock_irqsave(&rc->neh_lock, flags);
|
||||
list_for_each_entry_safe(neh, next, &rc->neh_list, list_node) {
|
||||
__uwb_rc_neh_rm(rc, neh);
|
||||
uwb_rc_neh_cb(neh, NULL, error);
|
||||
}
|
||||
spin_unlock_irqrestore(&rc->neh_lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_neh_error);
|
||||
|
||||
|
||||
static void uwb_rc_neh_timer(unsigned long arg)
|
||||
{
|
||||
struct uwb_rc_neh *neh = (struct uwb_rc_neh *)arg;
|
||||
struct uwb_rc *rc = neh->rc;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&rc->neh_lock, flags);
|
||||
__uwb_rc_neh_rm(rc, neh);
|
||||
spin_unlock_irqrestore(&rc->neh_lock, flags);
|
||||
|
||||
uwb_rc_neh_cb(neh, NULL, -ETIMEDOUT);
|
||||
}
|
||||
|
||||
/** Initializes the @rc's neh subsystem
|
||||
*/
|
||||
void uwb_rc_neh_create(struct uwb_rc *rc)
|
||||
{
|
||||
spin_lock_init(&rc->neh_lock);
|
||||
INIT_LIST_HEAD(&rc->neh_list);
|
||||
set_bit(0, rc->ctx_bm); /* 0 is reserved (see [WUSB] table 8-65) */
|
||||
set_bit(0xff, rc->ctx_bm); /* and 0xff is invalid */
|
||||
rc->ctx_roll = 1;
|
||||
}
|
||||
|
||||
|
||||
/** Release's the @rc's neh subsystem */
|
||||
void uwb_rc_neh_destroy(struct uwb_rc *rc)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct uwb_rc_neh *neh, *next;
|
||||
|
||||
spin_lock_irqsave(&rc->neh_lock, flags);
|
||||
list_for_each_entry_safe(neh, next, &rc->neh_list, list_node) {
|
||||
__uwb_rc_neh_rm(rc, neh);
|
||||
uwb_rc_neh_put(neh);
|
||||
}
|
||||
spin_unlock_irqrestore(&rc->neh_lock, flags);
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* UWB PAL support.
|
||||
*
|
||||
* 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 <linux/kernel.h>
|
||||
#include <linux/uwb.h>
|
||||
|
||||
#include "uwb-internal.h"
|
||||
|
||||
/**
|
||||
* uwb_pal_init - initialize a UWB PAL
|
||||
* @pal: the PAL to initialize
|
||||
*/
|
||||
void uwb_pal_init(struct uwb_pal *pal)
|
||||
{
|
||||
INIT_LIST_HEAD(&pal->node);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_pal_init);
|
||||
|
||||
/**
|
||||
* uwb_pal_register - register a UWB PAL
|
||||
* @rc: the radio controller the PAL will be using
|
||||
* @pal: the PAL
|
||||
*
|
||||
* The PAL must be initialized with uwb_pal_init().
|
||||
*/
|
||||
int uwb_pal_register(struct uwb_rc *rc, struct uwb_pal *pal)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (pal->device) {
|
||||
ret = sysfs_create_link(&pal->device->kobj,
|
||||
&rc->uwb_dev.dev.kobj, "uwb_rc");
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = sysfs_create_link(&rc->uwb_dev.dev.kobj,
|
||||
&pal->device->kobj, pal->name);
|
||||
if (ret < 0) {
|
||||
sysfs_remove_link(&pal->device->kobj, "uwb_rc");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock(&rc->pal_lock);
|
||||
list_add(&pal->node, &rc->pals);
|
||||
spin_unlock(&rc->pal_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_pal_register);
|
||||
|
||||
/**
|
||||
* uwb_pal_register - unregister a UWB PAL
|
||||
* @rc: the radio controller the PAL was using
|
||||
* @pal: the PAL
|
||||
*/
|
||||
void uwb_pal_unregister(struct uwb_rc *rc, struct uwb_pal *pal)
|
||||
{
|
||||
spin_lock(&rc->pal_lock);
|
||||
list_del(&pal->node);
|
||||
spin_unlock(&rc->pal_lock);
|
||||
|
||||
if (pal->device) {
|
||||
sysfs_remove_link(&rc->uwb_dev.dev.kobj, pal->name);
|
||||
sysfs_remove_link(&pal->device->kobj, "uwb_rc");
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_pal_unregister);
|
||||
|
||||
/**
|
||||
* uwb_rc_pal_init - initialize the PAL related parts of a radio controller
|
||||
* @rc: the radio controller
|
||||
*/
|
||||
void uwb_rc_pal_init(struct uwb_rc *rc)
|
||||
{
|
||||
spin_lock_init(&rc->pal_lock);
|
||||
INIT_LIST_HEAD(&rc->pals);
|
||||
}
|
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* UWB basic command support and radio reset
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME:
|
||||
*
|
||||
* - docs
|
||||
*
|
||||
* - Now we are serializing (using the uwb_dev->mutex) the command
|
||||
* execution; it should be parallelized as much as possible some
|
||||
* day.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include "uwb-internal.h"
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/**
|
||||
* Command result codes (WUSB1.0[T8-69])
|
||||
*/
|
||||
static
|
||||
const char *__strerror[] = {
|
||||
"success",
|
||||
"failure",
|
||||
"hardware failure",
|
||||
"no more slots",
|
||||
"beacon is too large",
|
||||
"invalid parameter",
|
||||
"unsupported power level",
|
||||
"time out (wa) or invalid ie data (whci)",
|
||||
"beacon size exceeded",
|
||||
"cancelled",
|
||||
"invalid state",
|
||||
"invalid size",
|
||||
"ack not recieved",
|
||||
"no more asie notification",
|
||||
};
|
||||
|
||||
|
||||
/** Return a string matching the given error code */
|
||||
const char *uwb_rc_strerror(unsigned code)
|
||||
{
|
||||
if (code == 255)
|
||||
return "time out";
|
||||
if (code >= ARRAY_SIZE(__strerror))
|
||||
return "unknown error";
|
||||
return __strerror[code];
|
||||
}
|
||||
|
||||
int uwb_rc_cmd_async(struct uwb_rc *rc, const char *cmd_name,
|
||||
struct uwb_rccb *cmd, size_t cmd_size,
|
||||
u8 expected_type, u16 expected_event,
|
||||
uwb_rc_cmd_cb_f cb, void *arg)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_rc_neh *neh;
|
||||
int needtofree = 0;
|
||||
int result;
|
||||
|
||||
uwb_dev_lock(&rc->uwb_dev); /* Protect against rc->priv being removed */
|
||||
if (rc->priv == NULL) {
|
||||
uwb_dev_unlock(&rc->uwb_dev);
|
||||
return -ESHUTDOWN;
|
||||
}
|
||||
|
||||
if (rc->filter_cmd) {
|
||||
needtofree = rc->filter_cmd(rc, &cmd, &cmd_size);
|
||||
if (needtofree < 0 && needtofree != -ENOANO) {
|
||||
dev_err(dev, "%s: filter error: %d\n",
|
||||
cmd_name, needtofree);
|
||||
uwb_dev_unlock(&rc->uwb_dev);
|
||||
return needtofree;
|
||||
}
|
||||
}
|
||||
|
||||
neh = uwb_rc_neh_add(rc, cmd, expected_type, expected_event, cb, arg);
|
||||
if (IS_ERR(neh)) {
|
||||
result = PTR_ERR(neh);
|
||||
goto out;
|
||||
}
|
||||
|
||||
result = rc->cmd(rc, cmd, cmd_size);
|
||||
uwb_dev_unlock(&rc->uwb_dev);
|
||||
if (result < 0)
|
||||
uwb_rc_neh_rm(rc, neh);
|
||||
else
|
||||
uwb_rc_neh_arm(rc, neh);
|
||||
uwb_rc_neh_put(neh);
|
||||
out:
|
||||
if (needtofree == 1)
|
||||
kfree(cmd);
|
||||
return result < 0 ? result : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_cmd_async);
|
||||
|
||||
struct uwb_rc_cmd_done_params {
|
||||
struct completion completion;
|
||||
struct uwb_rceb *reply;
|
||||
ssize_t reply_size;
|
||||
};
|
||||
|
||||
static void uwb_rc_cmd_done(struct uwb_rc *rc, void *arg,
|
||||
struct uwb_rceb *reply, ssize_t reply_size)
|
||||
{
|
||||
struct uwb_rc_cmd_done_params *p = (struct uwb_rc_cmd_done_params *)arg;
|
||||
|
||||
if (reply_size > 0) {
|
||||
if (p->reply)
|
||||
reply_size = min(p->reply_size, reply_size);
|
||||
else
|
||||
p->reply = kmalloc(reply_size, GFP_ATOMIC);
|
||||
|
||||
if (p->reply)
|
||||
memcpy(p->reply, reply, reply_size);
|
||||
else
|
||||
reply_size = -ENOMEM;
|
||||
}
|
||||
p->reply_size = reply_size;
|
||||
complete(&p->completion);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic function for issuing commands to the Radio Control Interface
|
||||
*
|
||||
* @rc: UWB Radio Control descriptor
|
||||
* @cmd_name: Name of the command being issued (for error messages)
|
||||
* @cmd: Pointer to rccb structure containing the command;
|
||||
* normally you embed this structure as the first member of
|
||||
* the full command structure.
|
||||
* @cmd_size: Size of the whole command buffer pointed to by @cmd.
|
||||
* @reply: Pointer to where to store the reply
|
||||
* @reply_size: @reply's size
|
||||
* @expected_type: Expected type in the return event
|
||||
* @expected_event: Expected event code in the return event
|
||||
* @preply: Here a pointer to where the event data is received will
|
||||
* be stored. Once done with the data, free with kfree().
|
||||
*
|
||||
* This function is generic; it works for commands that return a fixed
|
||||
* and known size or for commands that return a variable amount of data.
|
||||
*
|
||||
* If a buffer is provided, that is used, although it could be chopped
|
||||
* to the maximum size of the buffer. If the buffer is NULL, then one
|
||||
* be allocated in *preply with the whole contents of the reply.
|
||||
*
|
||||
* @rc needs to be referenced
|
||||
*/
|
||||
static
|
||||
ssize_t __uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name,
|
||||
struct uwb_rccb *cmd, size_t cmd_size,
|
||||
struct uwb_rceb *reply, size_t reply_size,
|
||||
u8 expected_type, u16 expected_event,
|
||||
struct uwb_rceb **preply)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_rc_cmd_done_params params;
|
||||
|
||||
init_completion(¶ms.completion);
|
||||
params.reply = reply;
|
||||
params.reply_size = reply_size;
|
||||
|
||||
result = uwb_rc_cmd_async(rc, cmd_name, cmd, cmd_size,
|
||||
expected_type, expected_event,
|
||||
uwb_rc_cmd_done, ¶ms);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
wait_for_completion(¶ms.completion);
|
||||
|
||||
if (preply)
|
||||
*preply = params.reply;
|
||||
|
||||
if (params.reply_size < 0)
|
||||
dev_err(dev, "%s: confirmation event 0x%02x/%04x/%02x "
|
||||
"reception failed: %d\n", cmd_name,
|
||||
expected_type, expected_event, cmd->bCommandContext,
|
||||
(int)params.reply_size);
|
||||
return params.reply_size;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic function for issuing commands to the Radio Control Interface
|
||||
*
|
||||
* @rc: UWB Radio Control descriptor
|
||||
* @cmd_name: Name of the command being issued (for error messages)
|
||||
* @cmd: Pointer to rccb structure containing the command;
|
||||
* normally you embed this structure as the first member of
|
||||
* the full command structure.
|
||||
* @cmd_size: Size of the whole command buffer pointed to by @cmd.
|
||||
* @reply: Pointer to the beginning of the confirmation event
|
||||
* buffer. Normally bigger than an 'struct hwarc_rceb'.
|
||||
* You need to fill out reply->bEventType and reply->wEvent (in
|
||||
* cpu order) as the function will use them to verify the
|
||||
* confirmation event.
|
||||
* @reply_size: Size of the reply buffer
|
||||
*
|
||||
* The function checks that the length returned in the reply is at
|
||||
* least as big as @reply_size; if not, it will be deemed an error and
|
||||
* -EIO returned.
|
||||
*
|
||||
* @rc needs to be referenced
|
||||
*/
|
||||
ssize_t uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name,
|
||||
struct uwb_rccb *cmd, size_t cmd_size,
|
||||
struct uwb_rceb *reply, size_t reply_size)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
ssize_t result;
|
||||
|
||||
result = __uwb_rc_cmd(rc, cmd_name,
|
||||
cmd, cmd_size, reply, reply_size,
|
||||
reply->bEventType, reply->wEvent, NULL);
|
||||
|
||||
if (result > 0 && result < reply_size) {
|
||||
dev_err(dev, "%s: not enough data returned for decoding reply "
|
||||
"(%zu bytes received vs at least %zu needed)\n",
|
||||
cmd_name, result, reply_size);
|
||||
result = -EIO;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_cmd);
|
||||
|
||||
|
||||
/**
|
||||
* Generic function for issuing commands to the Radio Control
|
||||
* Interface that return an unknown amount of data
|
||||
*
|
||||
* @rc: UWB Radio Control descriptor
|
||||
* @cmd_name: Name of the command being issued (for error messages)
|
||||
* @cmd: Pointer to rccb structure containing the command;
|
||||
* normally you embed this structure as the first member of
|
||||
* the full command structure.
|
||||
* @cmd_size: Size of the whole command buffer pointed to by @cmd.
|
||||
* @expected_type: Expected type in the return event
|
||||
* @expected_event: Expected event code in the return event
|
||||
* @preply: Here a pointer to where the event data is received will
|
||||
* be stored. Once done with the data, free with kfree().
|
||||
*
|
||||
* The function checks that the length returned in the reply is at
|
||||
* least as big as a 'struct uwb_rceb *'; if not, it will be deemed an
|
||||
* error and -EIO returned.
|
||||
*
|
||||
* @rc needs to be referenced
|
||||
*/
|
||||
ssize_t uwb_rc_vcmd(struct uwb_rc *rc, const char *cmd_name,
|
||||
struct uwb_rccb *cmd, size_t cmd_size,
|
||||
u8 expected_type, u16 expected_event,
|
||||
struct uwb_rceb **preply)
|
||||
{
|
||||
return __uwb_rc_cmd(rc, cmd_name, cmd, cmd_size, NULL, 0,
|
||||
expected_type, expected_event, preply);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_vcmd);
|
||||
|
||||
|
||||
/**
|
||||
* Reset a UWB Host Controller (and all radio settings)
|
||||
*
|
||||
* @rc: Host Controller descriptor
|
||||
* @returns: 0 if ok, < 0 errno code on error
|
||||
*
|
||||
* We put the command on kmalloc'ed memory as some arches cannot do
|
||||
* USB from the stack. The reply event is copied from an stage buffer,
|
||||
* so it can be in the stack. See WUSB1.0[8.6.2.4] for more details.
|
||||
*/
|
||||
int uwb_rc_reset(struct uwb_rc *rc)
|
||||
{
|
||||
int result = -ENOMEM;
|
||||
struct uwb_rc_evt_confirm reply;
|
||||
struct uwb_rccb *cmd;
|
||||
size_t cmd_size = sizeof(*cmd);
|
||||
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
goto error_kzalloc;
|
||||
cmd->bCommandType = UWB_RC_CET_GENERAL;
|
||||
cmd->wCommand = cpu_to_le16(UWB_RC_CMD_RESET);
|
||||
reply.rceb.bEventType = UWB_RC_CET_GENERAL;
|
||||
reply.rceb.wEvent = UWB_RC_CMD_RESET;
|
||||
result = uwb_rc_cmd(rc, "RESET", cmd, cmd_size,
|
||||
&reply.rceb, sizeof(reply));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(&rc->uwb_dev.dev,
|
||||
"RESET: command execution failed: %s (%d)\n",
|
||||
uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
|
||||
result = -EIO;
|
||||
}
|
||||
error_cmd:
|
||||
kfree(cmd);
|
||||
error_kzalloc:
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
int uwbd_msg_handle_reset(struct uwb_event *evt)
|
||||
{
|
||||
struct uwb_rc *rc = evt->rc;
|
||||
int ret;
|
||||
|
||||
/* Need to prevent the RC hardware module going away while in
|
||||
the rc->reset() call. */
|
||||
if (!try_module_get(rc->owner))
|
||||
return 0;
|
||||
|
||||
dev_info(&rc->uwb_dev.dev, "resetting radio controller\n");
|
||||
ret = rc->reset(rc);
|
||||
if (ret)
|
||||
dev_err(&rc->uwb_dev.dev, "failed to reset hardware: %d\n", ret);
|
||||
|
||||
module_put(rc->owner);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rc_reset_all - request a reset of the radio controller and PALs
|
||||
* @rc: the radio controller of the hardware device to be reset.
|
||||
*
|
||||
* The full hardware reset of the radio controller and all the PALs
|
||||
* will be scheduled.
|
||||
*/
|
||||
void uwb_rc_reset_all(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_event *evt;
|
||||
|
||||
evt = kzalloc(sizeof(struct uwb_event), GFP_ATOMIC);
|
||||
if (unlikely(evt == NULL))
|
||||
return;
|
||||
|
||||
evt->rc = __uwb_rc_get(rc); /* will be put by uwbd's uwbd_event_handle() */
|
||||
evt->ts_jiffies = jiffies;
|
||||
evt->type = UWB_EVT_TYPE_MSG;
|
||||
evt->message = UWB_EVT_MSG_RESET;
|
||||
|
||||
uwbd_event_queue(evt);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rc_reset_all);
|
|
@ -0,0 +1,680 @@
|
|||
/*
|
||||
* UWB reservation management.
|
||||
*
|
||||
* 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 <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/uwb.h>
|
||||
|
||||
#include "uwb-internal.h"
|
||||
|
||||
static void uwb_rsv_timer(unsigned long arg);
|
||||
|
||||
static const char *rsv_states[] = {
|
||||
[UWB_RSV_STATE_NONE] = "none",
|
||||
[UWB_RSV_STATE_O_INITIATED] = "initiated",
|
||||
[UWB_RSV_STATE_O_PENDING] = "pending",
|
||||
[UWB_RSV_STATE_O_MODIFIED] = "modified",
|
||||
[UWB_RSV_STATE_O_ESTABLISHED] = "established",
|
||||
[UWB_RSV_STATE_T_ACCEPTED] = "accepted",
|
||||
[UWB_RSV_STATE_T_DENIED] = "denied",
|
||||
[UWB_RSV_STATE_T_PENDING] = "pending",
|
||||
};
|
||||
|
||||
static const char *rsv_types[] = {
|
||||
[UWB_DRP_TYPE_ALIEN_BP] = "alien-bp",
|
||||
[UWB_DRP_TYPE_HARD] = "hard",
|
||||
[UWB_DRP_TYPE_SOFT] = "soft",
|
||||
[UWB_DRP_TYPE_PRIVATE] = "private",
|
||||
[UWB_DRP_TYPE_PCA] = "pca",
|
||||
};
|
||||
|
||||
/**
|
||||
* uwb_rsv_state_str - return a string for a reservation state
|
||||
* @state: the reservation state.
|
||||
*/
|
||||
const char *uwb_rsv_state_str(enum uwb_rsv_state state)
|
||||
{
|
||||
if (state < UWB_RSV_STATE_NONE || state >= UWB_RSV_STATE_LAST)
|
||||
return "unknown";
|
||||
return rsv_states[state];
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rsv_state_str);
|
||||
|
||||
/**
|
||||
* uwb_rsv_type_str - return a string for a reservation type
|
||||
* @type: the reservation type
|
||||
*/
|
||||
const char *uwb_rsv_type_str(enum uwb_drp_type type)
|
||||
{
|
||||
if (type < UWB_DRP_TYPE_ALIEN_BP || type > UWB_DRP_TYPE_PCA)
|
||||
return "invalid";
|
||||
return rsv_types[type];
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rsv_type_str);
|
||||
|
||||
static void uwb_rsv_dump(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct device *dev = &rsv->rc->uwb_dev.dev;
|
||||
struct uwb_dev_addr devaddr;
|
||||
char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE];
|
||||
|
||||
uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr);
|
||||
if (rsv->target.type == UWB_RSV_TARGET_DEV)
|
||||
devaddr = rsv->target.dev->dev_addr;
|
||||
else
|
||||
devaddr = rsv->target.devaddr;
|
||||
uwb_dev_addr_print(target, sizeof(target), &devaddr);
|
||||
|
||||
dev_dbg(dev, "rsv %s -> %s: %s\n", owner, target, uwb_rsv_state_str(rsv->state));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a free stream index for a reservation.
|
||||
*
|
||||
* If the target is a DevAddr (e.g., a WUSB cluster reservation) then
|
||||
* the stream is allocated from a pool of per-RC stream indexes,
|
||||
* otherwise a unique stream index for the target is selected.
|
||||
*/
|
||||
static int uwb_rsv_get_stream(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct uwb_rc *rc = rsv->rc;
|
||||
unsigned long *streams_bm;
|
||||
int stream;
|
||||
|
||||
switch (rsv->target.type) {
|
||||
case UWB_RSV_TARGET_DEV:
|
||||
streams_bm = rsv->target.dev->streams;
|
||||
break;
|
||||
case UWB_RSV_TARGET_DEVADDR:
|
||||
streams_bm = rc->uwb_dev.streams;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
stream = find_first_zero_bit(streams_bm, UWB_NUM_STREAMS);
|
||||
if (stream >= UWB_NUM_STREAMS)
|
||||
return -EBUSY;
|
||||
|
||||
rsv->stream = stream;
|
||||
set_bit(stream, streams_bm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uwb_rsv_put_stream(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct uwb_rc *rc = rsv->rc;
|
||||
unsigned long *streams_bm;
|
||||
|
||||
switch (rsv->target.type) {
|
||||
case UWB_RSV_TARGET_DEV:
|
||||
streams_bm = rsv->target.dev->streams;
|
||||
break;
|
||||
case UWB_RSV_TARGET_DEVADDR:
|
||||
streams_bm = rc->uwb_dev.streams;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
clear_bit(rsv->stream, streams_bm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate a MAS allocation with a single row component.
|
||||
*/
|
||||
static void uwb_rsv_gen_alloc_row(struct uwb_mas_bm *mas,
|
||||
int first_mas, int mas_per_zone,
|
||||
int zs, int ze)
|
||||
{
|
||||
struct uwb_mas_bm col;
|
||||
int z;
|
||||
|
||||
bitmap_zero(mas->bm, UWB_NUM_MAS);
|
||||
bitmap_zero(col.bm, UWB_NUM_MAS);
|
||||
bitmap_fill(col.bm, mas_per_zone);
|
||||
bitmap_shift_left(col.bm, col.bm, first_mas + zs * UWB_MAS_PER_ZONE, UWB_NUM_MAS);
|
||||
|
||||
for (z = zs; z <= ze; z++) {
|
||||
bitmap_or(mas->bm, mas->bm, col.bm, UWB_NUM_MAS);
|
||||
bitmap_shift_left(col.bm, col.bm, UWB_MAS_PER_ZONE, UWB_NUM_MAS);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate some MAS for this reservation based on current local
|
||||
* availability, the reservation parameters (max_mas, min_mas,
|
||||
* sparsity), and the WiMedia rules for MAS allocations.
|
||||
*
|
||||
* Returns -EBUSY is insufficient free MAS are available.
|
||||
*
|
||||
* FIXME: to simplify this, only safe reservations with a single row
|
||||
* component in zones 1 to 15 are tried (zone 0 is skipped to avoid
|
||||
* problems with the MAS reserved for the BP).
|
||||
*
|
||||
* [ECMA-368] section B.2.
|
||||
*/
|
||||
static int uwb_rsv_alloc_mas(struct uwb_rsv *rsv)
|
||||
{
|
||||
static const int safe_mas_in_row[UWB_NUM_ZONES] = {
|
||||
8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 2, 1,
|
||||
};
|
||||
int n, r;
|
||||
struct uwb_mas_bm mas;
|
||||
bool found = false;
|
||||
|
||||
/*
|
||||
* Search all valid safe allocations until either: too few MAS
|
||||
* are available; or the smallest allocation with sufficient
|
||||
* MAS is found.
|
||||
*
|
||||
* The top of the zones are preferred, so space for larger
|
||||
* allocations is available in the bottom of the zone (e.g., a
|
||||
* 15 MAS allocation should start in row 14 leaving space for
|
||||
* a 120 MAS allocation at row 0).
|
||||
*/
|
||||
for (n = safe_mas_in_row[0]; n >= 1; n--) {
|
||||
int num_mas;
|
||||
|
||||
num_mas = n * (UWB_NUM_ZONES - 1);
|
||||
if (num_mas < rsv->min_mas)
|
||||
break;
|
||||
if (found && num_mas < rsv->max_mas)
|
||||
break;
|
||||
|
||||
for (r = UWB_MAS_PER_ZONE-1; r >= 0; r--) {
|
||||
if (safe_mas_in_row[r] < n)
|
||||
continue;
|
||||
uwb_rsv_gen_alloc_row(&mas, r, n, 1, UWB_NUM_ZONES);
|
||||
if (uwb_drp_avail_reserve_pending(rsv->rc, &mas) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return -EBUSY;
|
||||
|
||||
bitmap_copy(rsv->mas.bm, mas.bm, UWB_NUM_MAS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uwb_rsv_stroke_timer(struct uwb_rsv *rsv)
|
||||
{
|
||||
int sframes = UWB_MAX_LOST_BEACONS;
|
||||
|
||||
/*
|
||||
* Multicast reservations can become established within 1
|
||||
* super frame and should not be terminated if no response is
|
||||
* received.
|
||||
*/
|
||||
if (rsv->is_multicast) {
|
||||
if (rsv->state == UWB_RSV_STATE_O_INITIATED)
|
||||
sframes = 1;
|
||||
if (rsv->state == UWB_RSV_STATE_O_ESTABLISHED)
|
||||
sframes = 0;
|
||||
}
|
||||
|
||||
rsv->expired = false;
|
||||
if (sframes > 0) {
|
||||
/*
|
||||
* Add an additional 2 superframes to account for the
|
||||
* time to send the SET DRP IE command.
|
||||
*/
|
||||
unsigned timeout_us = (sframes + 2) * UWB_SUPERFRAME_LENGTH_US;
|
||||
mod_timer(&rsv->timer, jiffies + usecs_to_jiffies(timeout_us));
|
||||
} else
|
||||
del_timer(&rsv->timer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update a reservations state, and schedule an update of the
|
||||
* transmitted DRP IEs.
|
||||
*/
|
||||
static void uwb_rsv_state_update(struct uwb_rsv *rsv,
|
||||
enum uwb_rsv_state new_state)
|
||||
{
|
||||
rsv->state = new_state;
|
||||
rsv->ie_valid = false;
|
||||
|
||||
uwb_rsv_dump(rsv);
|
||||
|
||||
uwb_rsv_stroke_timer(rsv);
|
||||
uwb_rsv_sched_update(rsv->rc);
|
||||
}
|
||||
|
||||
static void uwb_rsv_callback(struct uwb_rsv *rsv)
|
||||
{
|
||||
if (rsv->callback)
|
||||
rsv->callback(rsv);
|
||||
}
|
||||
|
||||
void uwb_rsv_set_state(struct uwb_rsv *rsv, enum uwb_rsv_state new_state)
|
||||
{
|
||||
if (rsv->state == new_state) {
|
||||
switch (rsv->state) {
|
||||
case UWB_RSV_STATE_O_ESTABLISHED:
|
||||
case UWB_RSV_STATE_T_ACCEPTED:
|
||||
case UWB_RSV_STATE_NONE:
|
||||
uwb_rsv_stroke_timer(rsv);
|
||||
break;
|
||||
default:
|
||||
/* Expecting a state transition so leave timer
|
||||
as-is. */
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (new_state) {
|
||||
case UWB_RSV_STATE_NONE:
|
||||
uwb_drp_avail_release(rsv->rc, &rsv->mas);
|
||||
uwb_rsv_put_stream(rsv);
|
||||
uwb_rsv_state_update(rsv, UWB_RSV_STATE_NONE);
|
||||
uwb_rsv_callback(rsv);
|
||||
break;
|
||||
case UWB_RSV_STATE_O_INITIATED:
|
||||
uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_INITIATED);
|
||||
break;
|
||||
case UWB_RSV_STATE_O_PENDING:
|
||||
uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_PENDING);
|
||||
break;
|
||||
case UWB_RSV_STATE_O_ESTABLISHED:
|
||||
uwb_drp_avail_reserve(rsv->rc, &rsv->mas);
|
||||
uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_ESTABLISHED);
|
||||
uwb_rsv_callback(rsv);
|
||||
break;
|
||||
case UWB_RSV_STATE_T_ACCEPTED:
|
||||
uwb_drp_avail_reserve(rsv->rc, &rsv->mas);
|
||||
uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_ACCEPTED);
|
||||
uwb_rsv_callback(rsv);
|
||||
break;
|
||||
case UWB_RSV_STATE_T_DENIED:
|
||||
uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_DENIED);
|
||||
break;
|
||||
default:
|
||||
dev_err(&rsv->rc->uwb_dev.dev, "unhandled state: %s (%d)\n",
|
||||
uwb_rsv_state_str(new_state), new_state);
|
||||
}
|
||||
}
|
||||
|
||||
static struct uwb_rsv *uwb_rsv_alloc(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_rsv *rsv;
|
||||
|
||||
rsv = kzalloc(sizeof(struct uwb_rsv), GFP_KERNEL);
|
||||
if (!rsv)
|
||||
return NULL;
|
||||
|
||||
INIT_LIST_HEAD(&rsv->rc_node);
|
||||
INIT_LIST_HEAD(&rsv->pal_node);
|
||||
init_timer(&rsv->timer);
|
||||
rsv->timer.function = uwb_rsv_timer;
|
||||
rsv->timer.data = (unsigned long)rsv;
|
||||
|
||||
rsv->rc = rc;
|
||||
|
||||
return rsv;
|
||||
}
|
||||
|
||||
static void uwb_rsv_free(struct uwb_rsv *rsv)
|
||||
{
|
||||
uwb_dev_put(rsv->owner);
|
||||
if (rsv->target.type == UWB_RSV_TARGET_DEV)
|
||||
uwb_dev_put(rsv->target.dev);
|
||||
kfree(rsv);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rsv_create - allocate and initialize a UWB reservation structure
|
||||
* @rc: the radio controller
|
||||
* @cb: callback to use when the reservation completes or terminates
|
||||
* @pal_priv: data private to the PAL to be passed in the callback
|
||||
*
|
||||
* The callback is called when the state of the reservation changes from:
|
||||
*
|
||||
* - pending to accepted
|
||||
* - pending to denined
|
||||
* - accepted to terminated
|
||||
* - pending to terminated
|
||||
*/
|
||||
struct uwb_rsv *uwb_rsv_create(struct uwb_rc *rc, uwb_rsv_cb_f cb, void *pal_priv)
|
||||
{
|
||||
struct uwb_rsv *rsv;
|
||||
|
||||
rsv = uwb_rsv_alloc(rc);
|
||||
if (!rsv)
|
||||
return NULL;
|
||||
|
||||
rsv->callback = cb;
|
||||
rsv->pal_priv = pal_priv;
|
||||
|
||||
return rsv;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rsv_create);
|
||||
|
||||
void uwb_rsv_remove(struct uwb_rsv *rsv)
|
||||
{
|
||||
if (rsv->state != UWB_RSV_STATE_NONE)
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
|
||||
del_timer_sync(&rsv->timer);
|
||||
list_del(&rsv->rc_node);
|
||||
uwb_rsv_free(rsv);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rsv_destroy - free a UWB reservation structure
|
||||
* @rsv: the reservation to free
|
||||
*
|
||||
* The reservation will be terminated if it is pending or established.
|
||||
*/
|
||||
void uwb_rsv_destroy(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct uwb_rc *rc = rsv->rc;
|
||||
|
||||
mutex_lock(&rc->rsvs_mutex);
|
||||
uwb_rsv_remove(rsv);
|
||||
mutex_unlock(&rc->rsvs_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rsv_destroy);
|
||||
|
||||
/**
|
||||
* usb_rsv_establish - start a reservation establishment
|
||||
* @rsv: the reservation
|
||||
*
|
||||
* The PAL should fill in @rsv's owner, target, type, max_mas,
|
||||
* min_mas, sparsity and is_multicast fields. If the target is a
|
||||
* uwb_dev it must be referenced.
|
||||
*
|
||||
* The reservation's callback will be called when the reservation is
|
||||
* accepted, denied or times out.
|
||||
*/
|
||||
int uwb_rsv_establish(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct uwb_rc *rc = rsv->rc;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&rc->rsvs_mutex);
|
||||
|
||||
ret = uwb_rsv_get_stream(rsv);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = uwb_rsv_alloc_mas(rsv);
|
||||
if (ret) {
|
||||
uwb_rsv_put_stream(rsv);
|
||||
goto out;
|
||||
}
|
||||
|
||||
list_add_tail(&rsv->rc_node, &rc->reservations);
|
||||
rsv->owner = &rc->uwb_dev;
|
||||
uwb_dev_get(rsv->owner);
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_INITIATED);
|
||||
out:
|
||||
mutex_unlock(&rc->rsvs_mutex);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rsv_establish);
|
||||
|
||||
/**
|
||||
* uwb_rsv_modify - modify an already established reservation
|
||||
* @rsv: the reservation to modify
|
||||
* @max_mas: new maximum MAS to reserve
|
||||
* @min_mas: new minimum MAS to reserve
|
||||
* @sparsity: new sparsity to use
|
||||
*
|
||||
* FIXME: implement this once there are PALs that use it.
|
||||
*/
|
||||
int uwb_rsv_modify(struct uwb_rsv *rsv, int max_mas, int min_mas, int sparsity)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rsv_modify);
|
||||
|
||||
/**
|
||||
* uwb_rsv_terminate - terminate an established reservation
|
||||
* @rsv: the reservation to terminate
|
||||
*
|
||||
* A reservation is terminated by removing the DRP IE from the beacon,
|
||||
* the other end will consider the reservation to be terminated when
|
||||
* it does not see the DRP IE for at least mMaxLostBeacons.
|
||||
*
|
||||
* If applicable, the reference to the target uwb_dev will be released.
|
||||
*/
|
||||
void uwb_rsv_terminate(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct uwb_rc *rc = rsv->rc;
|
||||
|
||||
mutex_lock(&rc->rsvs_mutex);
|
||||
|
||||
uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
|
||||
|
||||
mutex_unlock(&rc->rsvs_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rsv_terminate);
|
||||
|
||||
/**
|
||||
* uwb_rsv_accept - accept a new reservation from a peer
|
||||
* @rsv: the reservation
|
||||
* @cb: call back for reservation changes
|
||||
* @pal_priv: data to be passed in the above call back
|
||||
*
|
||||
* Reservation requests from peers are denied unless a PAL accepts it
|
||||
* by calling this function.
|
||||
*/
|
||||
void uwb_rsv_accept(struct uwb_rsv *rsv, uwb_rsv_cb_f cb, void *pal_priv)
|
||||
{
|
||||
rsv->callback = cb;
|
||||
rsv->pal_priv = pal_priv;
|
||||
rsv->state = UWB_RSV_STATE_T_ACCEPTED;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uwb_rsv_accept);
|
||||
|
||||
/*
|
||||
* Is a received DRP IE for this reservation?
|
||||
*/
|
||||
static bool uwb_rsv_match(struct uwb_rsv *rsv, struct uwb_dev *src,
|
||||
struct uwb_ie_drp *drp_ie)
|
||||
{
|
||||
struct uwb_dev_addr *rsv_src;
|
||||
int stream;
|
||||
|
||||
stream = uwb_ie_drp_stream_index(drp_ie);
|
||||
|
||||
if (rsv->stream != stream)
|
||||
return false;
|
||||
|
||||
switch (rsv->target.type) {
|
||||
case UWB_RSV_TARGET_DEVADDR:
|
||||
return rsv->stream == stream;
|
||||
case UWB_RSV_TARGET_DEV:
|
||||
if (uwb_ie_drp_owner(drp_ie))
|
||||
rsv_src = &rsv->owner->dev_addr;
|
||||
else
|
||||
rsv_src = &rsv->target.dev->dev_addr;
|
||||
return uwb_dev_addr_cmp(&src->dev_addr, rsv_src) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct uwb_rsv *uwb_rsv_new_target(struct uwb_rc *rc,
|
||||
struct uwb_dev *src,
|
||||
struct uwb_ie_drp *drp_ie)
|
||||
{
|
||||
struct uwb_rsv *rsv;
|
||||
struct uwb_pal *pal;
|
||||
enum uwb_rsv_state state;
|
||||
|
||||
rsv = uwb_rsv_alloc(rc);
|
||||
if (!rsv)
|
||||
return NULL;
|
||||
|
||||
rsv->rc = rc;
|
||||
rsv->owner = src;
|
||||
uwb_dev_get(rsv->owner);
|
||||
rsv->target.type = UWB_RSV_TARGET_DEV;
|
||||
rsv->target.dev = &rc->uwb_dev;
|
||||
rsv->type = uwb_ie_drp_type(drp_ie);
|
||||
rsv->stream = uwb_ie_drp_stream_index(drp_ie);
|
||||
set_bit(rsv->stream, rsv->owner->streams);
|
||||
uwb_drp_ie_to_bm(&rsv->mas, drp_ie);
|
||||
|
||||
/*
|
||||
* See if any PALs are interested in this reservation. If not,
|
||||
* deny the request.
|
||||
*/
|
||||
rsv->state = UWB_RSV_STATE_T_DENIED;
|
||||
spin_lock(&rc->pal_lock);
|
||||
list_for_each_entry(pal, &rc->pals, node) {
|
||||
if (pal->new_rsv)
|
||||
pal->new_rsv(rsv);
|
||||
if (rsv->state == UWB_RSV_STATE_T_ACCEPTED)
|
||||
break;
|
||||
}
|
||||
spin_unlock(&rc->pal_lock);
|
||||
|
||||
list_add_tail(&rsv->rc_node, &rc->reservations);
|
||||
state = rsv->state;
|
||||
rsv->state = UWB_RSV_STATE_NONE;
|
||||
uwb_rsv_set_state(rsv, state);
|
||||
|
||||
return rsv;
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_rsv_find - find a reservation for a received DRP IE.
|
||||
* @rc: the radio controller
|
||||
* @src: source of the DRP IE
|
||||
* @drp_ie: the DRP IE
|
||||
*
|
||||
* If the reservation cannot be found and the DRP IE is from a peer
|
||||
* attempting to establish a new reservation, create a new reservation
|
||||
* and add it to the list.
|
||||
*/
|
||||
struct uwb_rsv *uwb_rsv_find(struct uwb_rc *rc, struct uwb_dev *src,
|
||||
struct uwb_ie_drp *drp_ie)
|
||||
{
|
||||
struct uwb_rsv *rsv;
|
||||
|
||||
list_for_each_entry(rsv, &rc->reservations, rc_node) {
|
||||
if (uwb_rsv_match(rsv, src, drp_ie))
|
||||
return rsv;
|
||||
}
|
||||
|
||||
if (uwb_ie_drp_owner(drp_ie))
|
||||
return uwb_rsv_new_target(rc, src, drp_ie);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Go through all the reservations and check for timeouts and (if
|
||||
* necessary) update their DRP IEs.
|
||||
*
|
||||
* FIXME: look at building the SET_DRP_IE command here rather than
|
||||
* having to rescan the list in uwb_rc_send_all_drp_ie().
|
||||
*/
|
||||
static bool uwb_rsv_update_all(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_rsv *rsv, *t;
|
||||
bool ie_updated = false;
|
||||
|
||||
list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) {
|
||||
if (rsv->expired)
|
||||
uwb_drp_handle_timeout(rsv);
|
||||
if (!rsv->ie_valid) {
|
||||
uwb_drp_ie_update(rsv);
|
||||
ie_updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ie_updated;
|
||||
}
|
||||
|
||||
void uwb_rsv_sched_update(struct uwb_rc *rc)
|
||||
{
|
||||
queue_work(rc->rsv_workq, &rc->rsv_update_work);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update DRP IEs and, if necessary, the DRP Availability IE and send
|
||||
* the updated IEs to the radio controller.
|
||||
*/
|
||||
static void uwb_rsv_update_work(struct work_struct *work)
|
||||
{
|
||||
struct uwb_rc *rc = container_of(work, struct uwb_rc, rsv_update_work);
|
||||
bool ie_updated;
|
||||
|
||||
mutex_lock(&rc->rsvs_mutex);
|
||||
|
||||
ie_updated = uwb_rsv_update_all(rc);
|
||||
|
||||
if (!rc->drp_avail.ie_valid) {
|
||||
uwb_drp_avail_ie_update(rc);
|
||||
ie_updated = true;
|
||||
}
|
||||
|
||||
if (ie_updated)
|
||||
uwb_rc_send_all_drp_ie(rc);
|
||||
|
||||
mutex_unlock(&rc->rsvs_mutex);
|
||||
}
|
||||
|
||||
static void uwb_rsv_timer(unsigned long arg)
|
||||
{
|
||||
struct uwb_rsv *rsv = (struct uwb_rsv *)arg;
|
||||
|
||||
rsv->expired = true;
|
||||
uwb_rsv_sched_update(rsv->rc);
|
||||
}
|
||||
|
||||
void uwb_rsv_init(struct uwb_rc *rc)
|
||||
{
|
||||
INIT_LIST_HEAD(&rc->reservations);
|
||||
mutex_init(&rc->rsvs_mutex);
|
||||
INIT_WORK(&rc->rsv_update_work, uwb_rsv_update_work);
|
||||
|
||||
bitmap_complement(rc->uwb_dev.streams, rc->uwb_dev.streams, UWB_NUM_STREAMS);
|
||||
}
|
||||
|
||||
int uwb_rsv_setup(struct uwb_rc *rc)
|
||||
{
|
||||
char name[16];
|
||||
|
||||
snprintf(name, sizeof(name), "%s_rsvd", dev_name(&rc->uwb_dev.dev));
|
||||
rc->rsv_workq = create_singlethread_workqueue(name);
|
||||
if (rc->rsv_workq == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uwb_rsv_cleanup(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_rsv *rsv, *t;
|
||||
|
||||
mutex_lock(&rc->rsvs_mutex);
|
||||
list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) {
|
||||
uwb_rsv_remove(rsv);
|
||||
}
|
||||
mutex_unlock(&rc->rsvs_mutex);
|
||||
|
||||
cancel_work_sync(&rc->rsv_update_work);
|
||||
destroy_workqueue(rc->rsv_workq);
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Scanning management
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
*
|
||||
* FIXME: docs
|
||||
* FIXME: there are issues here on how BEACON and SCAN on USB RCI deal
|
||||
* with each other. Currently seems that START_BEACON while
|
||||
* SCAN_ONLY will cancel the scan, so we need to update the
|
||||
* state here. Clarification request sent by email on
|
||||
* 10/05/2005.
|
||||
* 10/28/2005 No clear answer heard--maybe we'll hack the API
|
||||
* so that when we start beaconing, if the HC is
|
||||
* scanning in a mode not compatible with beaconing
|
||||
* we just fail.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
|
||||
/**
|
||||
* Start/stop scanning in a radio controller
|
||||
*
|
||||
* @rc: UWB Radio Controlller
|
||||
* @channel: Channel to scan; encodings in WUSB1.0[Table 5.12]
|
||||
* @type: Type of scanning to do.
|
||||
* @bpst_offset: value at which to start scanning (if type ==
|
||||
* UWB_SCAN_ONLY_STARTTIME)
|
||||
* @returns: 0 if ok, < 0 errno code on error
|
||||
*
|
||||
* We put the command on kmalloc'ed memory as some arches cannot do
|
||||
* USB from the stack. The reply event is copied from an stage buffer,
|
||||
* so it can be in the stack. See WUSB1.0[8.6.2.4] for more details.
|
||||
*/
|
||||
int uwb_rc_scan(struct uwb_rc *rc,
|
||||
unsigned channel, enum uwb_scan_type type,
|
||||
unsigned bpst_offset)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rc_cmd_scan *cmd;
|
||||
struct uwb_rc_evt_confirm reply;
|
||||
|
||||
result = -ENOMEM;
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (cmd == NULL)
|
||||
goto error_kzalloc;
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
cmd->rccb.bCommandType = UWB_RC_CET_GENERAL;
|
||||
cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SCAN);
|
||||
cmd->bChannelNumber = channel;
|
||||
cmd->bScanState = type;
|
||||
cmd->wStartTime = cpu_to_le16(bpst_offset);
|
||||
reply.rceb.bEventType = UWB_RC_CET_GENERAL;
|
||||
reply.rceb.wEvent = UWB_RC_CMD_SCAN;
|
||||
result = uwb_rc_cmd(rc, "SCAN", &cmd->rccb, sizeof(*cmd),
|
||||
&reply.rceb, sizeof(reply));
|
||||
if (result < 0)
|
||||
goto error_cmd;
|
||||
if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
|
||||
dev_err(&rc->uwb_dev.dev,
|
||||
"SCAN: command execution failed: %s (%d)\n",
|
||||
uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
|
||||
result = -EIO;
|
||||
goto error_cmd;
|
||||
}
|
||||
rc->scanning = channel;
|
||||
rc->scan_type = type;
|
||||
error_cmd:
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
kfree(cmd);
|
||||
error_kzalloc:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print scanning state
|
||||
*/
|
||||
static ssize_t uwb_rc_scan_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_rc *rc = uwb_dev->rc;
|
||||
ssize_t result;
|
||||
|
||||
mutex_lock(&rc->uwb_dev.mutex);
|
||||
result = sprintf(buf, "%d %d\n", rc->scanning, rc->scan_type);
|
||||
mutex_unlock(&rc->uwb_dev.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static ssize_t uwb_rc_scan_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
struct uwb_rc *rc = uwb_dev->rc;
|
||||
unsigned channel;
|
||||
unsigned type;
|
||||
unsigned bpst_offset = 0;
|
||||
ssize_t result = -EINVAL;
|
||||
|
||||
result = sscanf(buf, "%u %u %u\n", &channel, &type, &bpst_offset);
|
||||
if (result >= 2 && type < UWB_SCAN_TOP)
|
||||
result = uwb_rc_scan(rc, channel, type, bpst_offset);
|
||||
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
|
||||
/** Radio Control sysfs interface (declaration) */
|
||||
DEVICE_ATTR(scan, S_IRUGO | S_IWUSR, uwb_rc_scan_show, uwb_rc_scan_store);
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Bus for UWB Multi-interface Controller capabilities.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This file is released under the GNU GPL v2.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
static int umc_bus_unbind_helper(struct device *dev, void *data)
|
||||
{
|
||||
struct device *parent = data;
|
||||
|
||||
if (dev->parent == parent && dev->driver)
|
||||
device_release_driver(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* umc_controller_reset - reset the whole UMC controller
|
||||
* @umc: the UMC device for the radio controller.
|
||||
*
|
||||
* Drivers will be unbound from all UMC devices belonging to the
|
||||
* controller and then the radio controller will be rebound. The
|
||||
* radio controller is expected to do a full hardware reset when it is
|
||||
* probed.
|
||||
*
|
||||
* If this is called while a probe() or remove() is in progress it
|
||||
* will return -EAGAIN and not perform the reset.
|
||||
*/
|
||||
int umc_controller_reset(struct umc_dev *umc)
|
||||
{
|
||||
struct device *parent = umc->dev.parent;
|
||||
int ret;
|
||||
|
||||
if (down_trylock(&parent->sem))
|
||||
return -EAGAIN;
|
||||
bus_for_each_dev(&umc_bus_type, NULL, parent, umc_bus_unbind_helper);
|
||||
ret = device_attach(&umc->dev);
|
||||
if (ret == 1)
|
||||
ret = 0;
|
||||
up(&parent->sem);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(umc_controller_reset);
|
||||
|
||||
/**
|
||||
* umc_match_pci_id - match a UMC driver to a UMC device's parent PCI device.
|
||||
* @umc_drv: umc driver with match_data pointing to a zero-terminated
|
||||
* table of pci_device_id's.
|
||||
* @umc: umc device whose parent is to be matched.
|
||||
*/
|
||||
int umc_match_pci_id(struct umc_driver *umc_drv, struct umc_dev *umc)
|
||||
{
|
||||
const struct pci_device_id *id_table = umc_drv->match_data;
|
||||
struct pci_dev *pci;
|
||||
|
||||
if (umc->dev.parent->bus != &pci_bus_type)
|
||||
return 0;
|
||||
|
||||
pci = to_pci_dev(umc->dev.parent);
|
||||
return pci_match_id(id_table, pci) != NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(umc_match_pci_id);
|
||||
|
||||
static int umc_bus_rescan_helper(struct device *dev, void *data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!dev->driver)
|
||||
ret = device_attach(dev);
|
||||
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
static void umc_bus_rescan(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
/*
|
||||
* We can't use bus_rescan_devices() here as it deadlocks when
|
||||
* it tries to retake the dev->parent semaphore.
|
||||
*/
|
||||
err = bus_for_each_dev(&umc_bus_type, NULL, NULL, umc_bus_rescan_helper);
|
||||
if (err < 0)
|
||||
printk(KERN_WARNING "%s: rescan of bus failed: %d\n",
|
||||
KBUILD_MODNAME, err);
|
||||
}
|
||||
|
||||
static int umc_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct umc_dev *umc = to_umc_dev(dev);
|
||||
struct umc_driver *umc_driver = to_umc_driver(drv);
|
||||
|
||||
if (umc->cap_id == umc_driver->cap_id) {
|
||||
if (umc_driver->match)
|
||||
return umc_driver->match(umc_driver, umc);
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int umc_device_probe(struct device *dev)
|
||||
{
|
||||
struct umc_dev *umc;
|
||||
struct umc_driver *umc_driver;
|
||||
int err;
|
||||
|
||||
umc_driver = to_umc_driver(dev->driver);
|
||||
umc = to_umc_dev(dev);
|
||||
|
||||
get_device(dev);
|
||||
err = umc_driver->probe(umc);
|
||||
if (err)
|
||||
put_device(dev);
|
||||
else
|
||||
umc_bus_rescan();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int umc_device_remove(struct device *dev)
|
||||
{
|
||||
struct umc_dev *umc;
|
||||
struct umc_driver *umc_driver;
|
||||
|
||||
umc_driver = to_umc_driver(dev->driver);
|
||||
umc = to_umc_dev(dev);
|
||||
|
||||
umc_driver->remove(umc);
|
||||
put_device(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int umc_device_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct umc_dev *umc;
|
||||
struct umc_driver *umc_driver;
|
||||
int err = 0;
|
||||
|
||||
umc = to_umc_dev(dev);
|
||||
|
||||
if (dev->driver) {
|
||||
umc_driver = to_umc_driver(dev->driver);
|
||||
if (umc_driver->suspend)
|
||||
err = umc_driver->suspend(umc, state);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int umc_device_resume(struct device *dev)
|
||||
{
|
||||
struct umc_dev *umc;
|
||||
struct umc_driver *umc_driver;
|
||||
int err = 0;
|
||||
|
||||
umc = to_umc_dev(dev);
|
||||
|
||||
if (dev->driver) {
|
||||
umc_driver = to_umc_driver(dev->driver);
|
||||
if (umc_driver->resume)
|
||||
err = umc_driver->resume(umc);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t capability_id_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct umc_dev *umc = to_umc_dev(dev);
|
||||
|
||||
return sprintf(buf, "0x%02x\n", umc->cap_id);
|
||||
}
|
||||
|
||||
static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct umc_dev *umc = to_umc_dev(dev);
|
||||
|
||||
return sprintf(buf, "0x%04x\n", umc->version);
|
||||
}
|
||||
|
||||
static struct device_attribute umc_dev_attrs[] = {
|
||||
__ATTR_RO(capability_id),
|
||||
__ATTR_RO(version),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
struct bus_type umc_bus_type = {
|
||||
.name = "umc",
|
||||
.match = umc_bus_match,
|
||||
.probe = umc_device_probe,
|
||||
.remove = umc_device_remove,
|
||||
.suspend = umc_device_suspend,
|
||||
.resume = umc_device_resume,
|
||||
.dev_attrs = umc_dev_attrs,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(umc_bus_type);
|
||||
|
||||
static int __init umc_bus_init(void)
|
||||
{
|
||||
return bus_register(&umc_bus_type);
|
||||
}
|
||||
module_init(umc_bus_init);
|
||||
|
||||
static void __exit umc_bus_exit(void)
|
||||
{
|
||||
bus_unregister(&umc_bus_type);
|
||||
}
|
||||
module_exit(umc_bus_exit);
|
||||
|
||||
MODULE_DESCRIPTION("UWB Multi-interface Controller capability bus");
|
||||
MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* UWB Multi-interface Controller device management.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This file is released under the GNU GPL v2.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
static void umc_device_release(struct device *dev)
|
||||
{
|
||||
struct umc_dev *umc = to_umc_dev(dev);
|
||||
|
||||
kfree(umc);
|
||||
}
|
||||
|
||||
/**
|
||||
* umc_device_create - allocate a child UMC device
|
||||
* @parent: parent of the new UMC device.
|
||||
* @n: index of the new device.
|
||||
*
|
||||
* The new UMC device will have a bus ID of the parent with '-n'
|
||||
* appended.
|
||||
*/
|
||||
struct umc_dev *umc_device_create(struct device *parent, int n)
|
||||
{
|
||||
struct umc_dev *umc;
|
||||
|
||||
umc = kzalloc(sizeof(struct umc_dev), GFP_KERNEL);
|
||||
if (umc) {
|
||||
snprintf(umc->dev.bus_id, sizeof(umc->dev.bus_id), "%s-%d",
|
||||
parent->bus_id, n);
|
||||
umc->dev.parent = parent;
|
||||
umc->dev.bus = &umc_bus_type;
|
||||
umc->dev.release = umc_device_release;
|
||||
|
||||
umc->dev.dma_mask = parent->dma_mask;
|
||||
}
|
||||
return umc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(umc_device_create);
|
||||
|
||||
/**
|
||||
* umc_device_register - register a UMC device
|
||||
* @umc: pointer to the UMC device
|
||||
*
|
||||
* The memory resource for the UMC device is acquired and the device
|
||||
* registered with the system.
|
||||
*/
|
||||
int umc_device_register(struct umc_dev *umc)
|
||||
{
|
||||
int err;
|
||||
|
||||
d_fnstart(3, &umc->dev, "(umc_dev %p)\n", umc);
|
||||
|
||||
err = request_resource(umc->resource.parent, &umc->resource);
|
||||
if (err < 0) {
|
||||
dev_err(&umc->dev, "can't allocate resource range "
|
||||
"%016Lx to %016Lx: %d\n",
|
||||
(unsigned long long)umc->resource.start,
|
||||
(unsigned long long)umc->resource.end,
|
||||
err);
|
||||
goto error_request_resource;
|
||||
}
|
||||
|
||||
err = device_register(&umc->dev);
|
||||
if (err < 0)
|
||||
goto error_device_register;
|
||||
d_fnend(3, &umc->dev, "(umc_dev %p) = 0\n", umc);
|
||||
return 0;
|
||||
|
||||
error_device_register:
|
||||
release_resource(&umc->resource);
|
||||
error_request_resource:
|
||||
d_fnend(3, &umc->dev, "(umc_dev %p) = %d\n", umc, err);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(umc_device_register);
|
||||
|
||||
/**
|
||||
* umc_device_unregister - unregister a UMC device
|
||||
* @umc: pointer to the UMC device
|
||||
*
|
||||
* First we unregister the device, make sure the driver can do it's
|
||||
* resource release thing and then we try to release any left over
|
||||
* resources. We take a ref to the device, to make sure it doesn't
|
||||
* dissapear under our feet.
|
||||
*/
|
||||
void umc_device_unregister(struct umc_dev *umc)
|
||||
{
|
||||
struct device *dev;
|
||||
if (!umc)
|
||||
return;
|
||||
dev = get_device(&umc->dev);
|
||||
d_fnstart(3, dev, "(umc_dev %p)\n", umc);
|
||||
device_unregister(&umc->dev);
|
||||
release_resource(&umc->resource);
|
||||
d_fnend(3, dev, "(umc_dev %p) = void\n", umc);
|
||||
put_device(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(umc_device_unregister);
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* UWB Multi-interface Controller driver management.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This file is released under the GNU GPL v2.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
|
||||
int __umc_driver_register(struct umc_driver *umc_drv, struct module *module,
|
||||
const char *mod_name)
|
||||
{
|
||||
umc_drv->driver.name = umc_drv->name;
|
||||
umc_drv->driver.owner = module;
|
||||
umc_drv->driver.mod_name = mod_name;
|
||||
umc_drv->driver.bus = &umc_bus_type;
|
||||
|
||||
return driver_register(&umc_drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__umc_driver_register);
|
||||
|
||||
/**
|
||||
* umc_driver_register - unregister a UMC capabiltity driver.
|
||||
* @umc_drv: pointer to the driver.
|
||||
*/
|
||||
void umc_driver_unregister(struct umc_driver *umc_drv)
|
||||
{
|
||||
driver_unregister(&umc_drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(umc_driver_unregister);
|
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Debug support
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* FIXME: doc
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include <linux/uwb/debug-cmd.h>
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
#include "uwb-internal.h"
|
||||
|
||||
void dump_bytes(struct device *dev, const void *_buf, size_t rsize)
|
||||
{
|
||||
const char *buf = _buf;
|
||||
char line[32];
|
||||
size_t offset = 0;
|
||||
int cnt, cnt2;
|
||||
for (cnt = 0; cnt < rsize; cnt += 8) {
|
||||
size_t rtop = rsize - cnt < 8 ? rsize - cnt : 8;
|
||||
for (offset = cnt2 = 0; cnt2 < rtop; cnt2++) {
|
||||
offset += scnprintf(line + offset, sizeof(line) - offset,
|
||||
"%02x ", buf[cnt + cnt2] & 0xff);
|
||||
}
|
||||
if (dev)
|
||||
dev_info(dev, "%s\n", line);
|
||||
else
|
||||
printk(KERN_INFO "%s\n", line);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dump_bytes);
|
||||
|
||||
/*
|
||||
* Debug interface
|
||||
*
|
||||
* Per radio controller debugfs files (in uwb/uwbN/):
|
||||
*
|
||||
* command: Flexible command interface (see <linux/uwb/debug-cmd.h>).
|
||||
*
|
||||
* reservations: information on reservations.
|
||||
*
|
||||
* accept: Set to true (Y or 1) to accept reservation requests from
|
||||
* peers.
|
||||
*
|
||||
* drp_avail: DRP availability information.
|
||||
*/
|
||||
|
||||
struct uwb_dbg {
|
||||
struct uwb_pal pal;
|
||||
|
||||
u32 accept;
|
||||
struct list_head rsvs;
|
||||
|
||||
struct dentry *root_d;
|
||||
struct dentry *command_f;
|
||||
struct dentry *reservations_f;
|
||||
struct dentry *accept_f;
|
||||
struct dentry *drp_avail_f;
|
||||
};
|
||||
|
||||
static struct dentry *root_dir;
|
||||
|
||||
static void uwb_dbg_rsv_cb(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct uwb_rc *rc = rsv->rc;
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
struct uwb_dev_addr devaddr;
|
||||
char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE];
|
||||
|
||||
uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr);
|
||||
if (rsv->target.type == UWB_RSV_TARGET_DEV)
|
||||
devaddr = rsv->target.dev->dev_addr;
|
||||
else
|
||||
devaddr = rsv->target.devaddr;
|
||||
uwb_dev_addr_print(target, sizeof(target), &devaddr);
|
||||
|
||||
dev_dbg(dev, "debug: rsv %s -> %s: %s\n",
|
||||
owner, target, uwb_rsv_state_str(rsv->state));
|
||||
}
|
||||
|
||||
static int cmd_rsv_establish(struct uwb_rc *rc,
|
||||
struct uwb_dbg_cmd_rsv_establish *cmd)
|
||||
{
|
||||
struct uwb_mac_addr macaddr;
|
||||
struct uwb_rsv *rsv;
|
||||
struct uwb_dev *target;
|
||||
int ret;
|
||||
|
||||
memcpy(&macaddr, cmd->target, sizeof(macaddr));
|
||||
target = uwb_dev_get_by_macaddr(rc, &macaddr);
|
||||
if (target == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
rsv = uwb_rsv_create(rc, uwb_dbg_rsv_cb, NULL);
|
||||
if (rsv == NULL) {
|
||||
uwb_dev_put(target);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rsv->owner = &rc->uwb_dev;
|
||||
rsv->target.type = UWB_RSV_TARGET_DEV;
|
||||
rsv->target.dev = target;
|
||||
rsv->type = cmd->type;
|
||||
rsv->max_mas = cmd->max_mas;
|
||||
rsv->min_mas = cmd->min_mas;
|
||||
rsv->sparsity = cmd->sparsity;
|
||||
|
||||
ret = uwb_rsv_establish(rsv);
|
||||
if (ret)
|
||||
uwb_rsv_destroy(rsv);
|
||||
else
|
||||
list_add_tail(&rsv->pal_node, &rc->dbg->rsvs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cmd_rsv_terminate(struct uwb_rc *rc,
|
||||
struct uwb_dbg_cmd_rsv_terminate *cmd)
|
||||
{
|
||||
struct uwb_rsv *rsv, *found = NULL;
|
||||
int i = 0;
|
||||
|
||||
list_for_each_entry(rsv, &rc->dbg->rsvs, pal_node) {
|
||||
if (i == cmd->index) {
|
||||
found = rsv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return -EINVAL;
|
||||
|
||||
list_del(&found->pal_node);
|
||||
uwb_rsv_terminate(found);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int command_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = inode->i_private;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t command_write(struct file *file, const char __user *buf,
|
||||
size_t len, loff_t *off)
|
||||
{
|
||||
struct uwb_rc *rc = file->private_data;
|
||||
struct uwb_dbg_cmd cmd;
|
||||
int ret;
|
||||
|
||||
if (len != sizeof(struct uwb_dbg_cmd))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(&cmd, buf, len) != 0)
|
||||
return -EFAULT;
|
||||
|
||||
switch (cmd.type) {
|
||||
case UWB_DBG_CMD_RSV_ESTABLISH:
|
||||
ret = cmd_rsv_establish(rc, &cmd.rsv_establish);
|
||||
break;
|
||||
case UWB_DBG_CMD_RSV_TERMINATE:
|
||||
ret = cmd_rsv_terminate(rc, &cmd.rsv_terminate);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret < 0 ? ret : len;
|
||||
}
|
||||
|
||||
static struct file_operations command_fops = {
|
||||
.open = command_open,
|
||||
.write = command_write,
|
||||
.read = NULL,
|
||||
.llseek = no_llseek,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int reservations_print(struct seq_file *s, void *p)
|
||||
{
|
||||
struct uwb_rc *rc = s->private;
|
||||
struct uwb_rsv *rsv;
|
||||
|
||||
mutex_lock(&rc->rsvs_mutex);
|
||||
|
||||
list_for_each_entry(rsv, &rc->reservations, rc_node) {
|
||||
struct uwb_dev_addr devaddr;
|
||||
char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE];
|
||||
bool is_owner;
|
||||
char buf[72];
|
||||
|
||||
uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr);
|
||||
if (rsv->target.type == UWB_RSV_TARGET_DEV) {
|
||||
devaddr = rsv->target.dev->dev_addr;
|
||||
is_owner = &rc->uwb_dev == rsv->owner;
|
||||
} else {
|
||||
devaddr = rsv->target.devaddr;
|
||||
is_owner = true;
|
||||
}
|
||||
uwb_dev_addr_print(target, sizeof(target), &devaddr);
|
||||
|
||||
seq_printf(s, "%c %s -> %s: %s\n",
|
||||
is_owner ? 'O' : 'T',
|
||||
owner, target, uwb_rsv_state_str(rsv->state));
|
||||
seq_printf(s, " stream: %d type: %s\n",
|
||||
rsv->stream, uwb_rsv_type_str(rsv->type));
|
||||
bitmap_scnprintf(buf, sizeof(buf), rsv->mas.bm, UWB_NUM_MAS);
|
||||
seq_printf(s, " %s\n", buf);
|
||||
}
|
||||
|
||||
mutex_unlock(&rc->rsvs_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reservations_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, reservations_print, inode->i_private);
|
||||
}
|
||||
|
||||
static struct file_operations reservations_fops = {
|
||||
.open = reservations_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int drp_avail_print(struct seq_file *s, void *p)
|
||||
{
|
||||
struct uwb_rc *rc = s->private;
|
||||
char buf[72];
|
||||
|
||||
bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.global, UWB_NUM_MAS);
|
||||
seq_printf(s, "global: %s\n", buf);
|
||||
bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.local, UWB_NUM_MAS);
|
||||
seq_printf(s, "local: %s\n", buf);
|
||||
bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.pending, UWB_NUM_MAS);
|
||||
seq_printf(s, "pending: %s\n", buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int drp_avail_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, drp_avail_print, inode->i_private);
|
||||
}
|
||||
|
||||
static struct file_operations drp_avail_fops = {
|
||||
.open = drp_avail_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void uwb_dbg_new_rsv(struct uwb_rsv *rsv)
|
||||
{
|
||||
struct uwb_rc *rc = rsv->rc;
|
||||
|
||||
if (rc->dbg->accept)
|
||||
uwb_rsv_accept(rsv, uwb_dbg_rsv_cb, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_dbg_add_rc - add a debug interface for a radio controller
|
||||
* @rc: the radio controller
|
||||
*/
|
||||
void uwb_dbg_add_rc(struct uwb_rc *rc)
|
||||
{
|
||||
rc->dbg = kzalloc(sizeof(struct uwb_dbg), GFP_KERNEL);
|
||||
if (rc->dbg == NULL)
|
||||
return;
|
||||
|
||||
INIT_LIST_HEAD(&rc->dbg->rsvs);
|
||||
|
||||
uwb_pal_init(&rc->dbg->pal);
|
||||
rc->dbg->pal.new_rsv = uwb_dbg_new_rsv;
|
||||
uwb_pal_register(rc, &rc->dbg->pal);
|
||||
if (root_dir) {
|
||||
rc->dbg->root_d = debugfs_create_dir(dev_name(&rc->uwb_dev.dev),
|
||||
root_dir);
|
||||
rc->dbg->command_f = debugfs_create_file("command", 0200,
|
||||
rc->dbg->root_d, rc,
|
||||
&command_fops);
|
||||
rc->dbg->reservations_f = debugfs_create_file("reservations", 0444,
|
||||
rc->dbg->root_d, rc,
|
||||
&reservations_fops);
|
||||
rc->dbg->accept_f = debugfs_create_bool("accept", 0644,
|
||||
rc->dbg->root_d,
|
||||
&rc->dbg->accept);
|
||||
rc->dbg->drp_avail_f = debugfs_create_file("drp_avail", 0444,
|
||||
rc->dbg->root_d, rc,
|
||||
&drp_avail_fops);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_dbg_add_rc - remove a radio controller's debug interface
|
||||
* @rc: the radio controller
|
||||
*/
|
||||
void uwb_dbg_del_rc(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_rsv *rsv, *t;
|
||||
|
||||
if (rc->dbg == NULL)
|
||||
return;
|
||||
|
||||
list_for_each_entry_safe(rsv, t, &rc->dbg->rsvs, pal_node) {
|
||||
uwb_rsv_destroy(rsv);
|
||||
}
|
||||
|
||||
uwb_pal_unregister(rc, &rc->dbg->pal);
|
||||
|
||||
if (root_dir) {
|
||||
debugfs_remove(rc->dbg->drp_avail_f);
|
||||
debugfs_remove(rc->dbg->accept_f);
|
||||
debugfs_remove(rc->dbg->reservations_f);
|
||||
debugfs_remove(rc->dbg->command_f);
|
||||
debugfs_remove(rc->dbg->root_d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_dbg_exit - initialize the debug interface sub-module
|
||||
*/
|
||||
void uwb_dbg_init(void)
|
||||
{
|
||||
root_dir = debugfs_create_dir("uwb", NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* uwb_dbg_exit - clean-up the debug interface sub-module
|
||||
*/
|
||||
void uwb_dbg_exit(void)
|
||||
{
|
||||
debugfs_remove(root_dir);
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* UWB internal API
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This contains most of the internal API for UWB. This is stuff used
|
||||
* across the stack that of course, is of no interest to the rest.
|
||||
*
|
||||
* Some parts might end up going public (like uwb_rc_*())...
|
||||
*/
|
||||
|
||||
#ifndef __UWB_INTERNAL_H__
|
||||
#define __UWB_INTERNAL_H__
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
struct uwb_beca_e;
|
||||
|
||||
/* General device API */
|
||||
extern void uwb_dev_init(struct uwb_dev *uwb_dev);
|
||||
extern int __uwb_dev_offair(struct uwb_dev *, struct uwb_rc *);
|
||||
extern int uwb_dev_add(struct uwb_dev *uwb_dev, struct device *parent_dev,
|
||||
struct uwb_rc *parent_rc);
|
||||
extern void uwb_dev_rm(struct uwb_dev *uwb_dev);
|
||||
extern void uwbd_dev_onair(struct uwb_rc *, struct uwb_beca_e *);
|
||||
extern void uwbd_dev_offair(struct uwb_beca_e *);
|
||||
void uwb_notify(struct uwb_rc *rc, struct uwb_dev *uwb_dev, enum uwb_notifs event);
|
||||
|
||||
/* General UWB Radio Controller Internal API */
|
||||
extern struct uwb_rc *__uwb_rc_try_get(struct uwb_rc *);
|
||||
static inline struct uwb_rc *__uwb_rc_get(struct uwb_rc *rc)
|
||||
{
|
||||
uwb_dev_get(&rc->uwb_dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static inline void __uwb_rc_put(struct uwb_rc *rc)
|
||||
{
|
||||
uwb_dev_put(&rc->uwb_dev);
|
||||
}
|
||||
|
||||
extern int uwb_rc_reset(struct uwb_rc *rc);
|
||||
extern int uwb_rc_beacon(struct uwb_rc *rc,
|
||||
int channel, unsigned bpst_offset);
|
||||
extern int uwb_rc_scan(struct uwb_rc *rc,
|
||||
unsigned channel, enum uwb_scan_type type,
|
||||
unsigned bpst_offset);
|
||||
extern int uwb_rc_send_all_drp_ie(struct uwb_rc *rc);
|
||||
extern ssize_t uwb_rc_print_IEs(struct uwb_rc *rc, char *, size_t);
|
||||
extern void uwb_rc_ie_init(struct uwb_rc *);
|
||||
extern void uwb_rc_ie_init(struct uwb_rc *);
|
||||
extern ssize_t uwb_rc_ie_setup(struct uwb_rc *);
|
||||
extern void uwb_rc_ie_release(struct uwb_rc *);
|
||||
extern int uwb_rc_ie_add(struct uwb_rc *,
|
||||
const struct uwb_ie_hdr *, size_t);
|
||||
extern int uwb_rc_ie_rm(struct uwb_rc *, enum uwb_ie);
|
||||
|
||||
extern const char *uwb_rc_strerror(unsigned code);
|
||||
|
||||
/*
|
||||
* Time to wait for a response to an RC command.
|
||||
*
|
||||
* Some commands can take a long time to response. e.g., START_BEACON
|
||||
* may scan for several superframes before joining an existing beacon
|
||||
* group and this can take around 600 ms.
|
||||
*/
|
||||
#define UWB_RC_CMD_TIMEOUT_MS 1000 /* ms */
|
||||
|
||||
/*
|
||||
* Notification/Event Handlers
|
||||
*/
|
||||
|
||||
struct uwb_rc_neh;
|
||||
|
||||
void uwb_rc_neh_create(struct uwb_rc *rc);
|
||||
void uwb_rc_neh_destroy(struct uwb_rc *rc);
|
||||
|
||||
struct uwb_rc_neh *uwb_rc_neh_add(struct uwb_rc *rc, struct uwb_rccb *cmd,
|
||||
u8 expected_type, u16 expected_event,
|
||||
uwb_rc_cmd_cb_f cb, void *arg);
|
||||
void uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh);
|
||||
void uwb_rc_neh_arm(struct uwb_rc *rc, struct uwb_rc_neh *neh);
|
||||
void uwb_rc_neh_put(struct uwb_rc_neh *neh);
|
||||
|
||||
/* Event size tables */
|
||||
extern int uwb_est_create(void);
|
||||
extern void uwb_est_destroy(void);
|
||||
|
||||
|
||||
/*
|
||||
* UWB Events & management daemon
|
||||
*/
|
||||
|
||||
/**
|
||||
* enum uwb_event_type - types of UWB management daemon events
|
||||
*
|
||||
* The UWB management daemon (uwbd) can receive two types of events:
|
||||
* UWB_EVT_TYPE_NOTIF - notification from the radio controller.
|
||||
* UWB_EVT_TYPE_MSG - a simple message.
|
||||
*/
|
||||
enum uwb_event_type {
|
||||
UWB_EVT_TYPE_NOTIF,
|
||||
UWB_EVT_TYPE_MSG,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct uwb_event_notif - an event for a radio controller notification
|
||||
* @size: Size of the buffer (ie: Guaranteed to contain at least
|
||||
* a full 'struct uwb_rceb')
|
||||
* @rceb: Pointer to a kmalloced() event payload
|
||||
*/
|
||||
struct uwb_event_notif {
|
||||
size_t size;
|
||||
struct uwb_rceb *rceb;
|
||||
};
|
||||
|
||||
/**
|
||||
* enum uwb_event_message - an event for a message for asynchronous processing
|
||||
*
|
||||
* UWB_EVT_MSG_RESET - reset the radio controller and all PAL hardware.
|
||||
*/
|
||||
enum uwb_event_message {
|
||||
UWB_EVT_MSG_RESET,
|
||||
};
|
||||
|
||||
/**
|
||||
* UWB Event
|
||||
* @rc: Radio controller that emitted the event (referenced)
|
||||
* @ts_jiffies: Timestamp, when was it received
|
||||
* @type: This event's type.
|
||||
*/
|
||||
struct uwb_event {
|
||||
struct list_head list_node;
|
||||
struct uwb_rc *rc;
|
||||
unsigned long ts_jiffies;
|
||||
enum uwb_event_type type;
|
||||
union {
|
||||
struct uwb_event_notif notif;
|
||||
enum uwb_event_message message;
|
||||
};
|
||||
};
|
||||
|
||||
extern void uwbd_start(void);
|
||||
extern void uwbd_stop(void);
|
||||
extern struct uwb_event *uwb_event_alloc(size_t, gfp_t gfp_mask);
|
||||
extern void uwbd_event_queue(struct uwb_event *);
|
||||
void uwbd_flush(struct uwb_rc *rc);
|
||||
|
||||
/* UWB event handlers */
|
||||
extern int uwbd_evt_handle_rc_beacon(struct uwb_event *);
|
||||
extern int uwbd_evt_handle_rc_beacon_size(struct uwb_event *);
|
||||
extern int uwbd_evt_handle_rc_bpoie_change(struct uwb_event *);
|
||||
extern int uwbd_evt_handle_rc_bp_slot_change(struct uwb_event *);
|
||||
extern int uwbd_evt_handle_rc_drp(struct uwb_event *);
|
||||
extern int uwbd_evt_handle_rc_drp_avail(struct uwb_event *);
|
||||
|
||||
int uwbd_msg_handle_reset(struct uwb_event *evt);
|
||||
|
||||
|
||||
/*
|
||||
* Address management
|
||||
*/
|
||||
int uwb_rc_dev_addr_assign(struct uwb_rc *rc);
|
||||
int uwbd_evt_handle_rc_dev_addr_conflict(struct uwb_event *evt);
|
||||
|
||||
/*
|
||||
* UWB Beacon Cache
|
||||
*
|
||||
* Each beacon we received is kept in a cache--when we receive that
|
||||
* beacon consistently, that means there is a new device that we have
|
||||
* to add to the system.
|
||||
*/
|
||||
|
||||
extern unsigned long beacon_timeout_ms;
|
||||
|
||||
/** Beacon cache list */
|
||||
struct uwb_beca {
|
||||
struct list_head list;
|
||||
size_t entries;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
extern struct uwb_beca uwb_beca;
|
||||
|
||||
/**
|
||||
* Beacon cache entry
|
||||
*
|
||||
* @jiffies_refresh: last time a beacon was received that refreshed
|
||||
* this cache entry.
|
||||
* @uwb_dev: device connected to this beacon. This pointer is not
|
||||
* safe, you need to get it with uwb_dev_try_get()
|
||||
*
|
||||
* @hits: how many time we have seen this beacon since last time we
|
||||
* cleared it
|
||||
*/
|
||||
struct uwb_beca_e {
|
||||
struct mutex mutex;
|
||||
struct kref refcnt;
|
||||
struct list_head node;
|
||||
struct uwb_mac_addr *mac_addr;
|
||||
struct uwb_dev_addr dev_addr;
|
||||
u8 hits;
|
||||
unsigned long ts_jiffies;
|
||||
struct uwb_dev *uwb_dev;
|
||||
struct uwb_rc_evt_beacon *be;
|
||||
struct stats lqe_stats, rssi_stats; /* radio statistics */
|
||||
};
|
||||
struct uwb_beacon_frame;
|
||||
extern ssize_t uwb_bce_print_IEs(struct uwb_dev *, struct uwb_beca_e *,
|
||||
char *, size_t);
|
||||
extern struct uwb_beca_e *__uwb_beca_add(struct uwb_rc_evt_beacon *,
|
||||
struct uwb_beacon_frame *,
|
||||
unsigned long);
|
||||
|
||||
extern void uwb_bce_kfree(struct kref *_bce);
|
||||
static inline void uwb_bce_get(struct uwb_beca_e *bce)
|
||||
{
|
||||
kref_get(&bce->refcnt);
|
||||
}
|
||||
static inline void uwb_bce_put(struct uwb_beca_e *bce)
|
||||
{
|
||||
kref_put(&bce->refcnt, uwb_bce_kfree);
|
||||
}
|
||||
extern void uwb_beca_purge(void);
|
||||
extern void uwb_beca_release(void);
|
||||
|
||||
struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc,
|
||||
const struct uwb_dev_addr *devaddr);
|
||||
struct uwb_dev *uwb_dev_get_by_macaddr(struct uwb_rc *rc,
|
||||
const struct uwb_mac_addr *macaddr);
|
||||
|
||||
/* -- UWB Sysfs representation */
|
||||
extern struct class uwb_rc_class;
|
||||
extern struct device_attribute dev_attr_mac_address;
|
||||
extern struct device_attribute dev_attr_beacon;
|
||||
extern struct device_attribute dev_attr_scan;
|
||||
|
||||
/* -- DRP Bandwidth allocator: bandwidth allocations, reservations, DRP */
|
||||
void uwb_rsv_init(struct uwb_rc *rc);
|
||||
int uwb_rsv_setup(struct uwb_rc *rc);
|
||||
void uwb_rsv_cleanup(struct uwb_rc *rc);
|
||||
|
||||
void uwb_rsv_set_state(struct uwb_rsv *rsv, enum uwb_rsv_state new_state);
|
||||
void uwb_rsv_remove(struct uwb_rsv *rsv);
|
||||
struct uwb_rsv *uwb_rsv_find(struct uwb_rc *rc, struct uwb_dev *src,
|
||||
struct uwb_ie_drp *drp_ie);
|
||||
void uwb_rsv_sched_update(struct uwb_rc *rc);
|
||||
|
||||
void uwb_drp_handle_timeout(struct uwb_rsv *rsv);
|
||||
int uwb_drp_ie_update(struct uwb_rsv *rsv);
|
||||
void uwb_drp_ie_to_bm(struct uwb_mas_bm *bm, const struct uwb_ie_drp *drp_ie);
|
||||
|
||||
void uwb_drp_avail_init(struct uwb_rc *rc);
|
||||
int uwb_drp_avail_reserve_pending(struct uwb_rc *rc, struct uwb_mas_bm *mas);
|
||||
void uwb_drp_avail_reserve(struct uwb_rc *rc, struct uwb_mas_bm *mas);
|
||||
void uwb_drp_avail_release(struct uwb_rc *rc, struct uwb_mas_bm *mas);
|
||||
void uwb_drp_avail_ie_update(struct uwb_rc *rc);
|
||||
|
||||
/* -- PAL support */
|
||||
void uwb_rc_pal_init(struct uwb_rc *rc);
|
||||
|
||||
/* -- Misc */
|
||||
|
||||
extern ssize_t uwb_mac_frame_hdr_print(char *, size_t,
|
||||
const struct uwb_mac_frame_hdr *);
|
||||
|
||||
/* -- Debug interface */
|
||||
void uwb_dbg_init(void);
|
||||
void uwb_dbg_exit(void);
|
||||
void uwb_dbg_add_rc(struct uwb_rc *rc);
|
||||
void uwb_dbg_del_rc(struct uwb_rc *rc);
|
||||
|
||||
/* Workarounds for version specific stuff */
|
||||
|
||||
static inline void uwb_dev_lock(struct uwb_dev *uwb_dev)
|
||||
{
|
||||
down(&uwb_dev->dev.sem);
|
||||
}
|
||||
|
||||
static inline void uwb_dev_unlock(struct uwb_dev *uwb_dev)
|
||||
{
|
||||
up(&uwb_dev->dev.sem);
|
||||
}
|
||||
|
||||
#endif /* #ifndef __UWB_INTERNAL_H__ */
|
|
@ -0,0 +1,410 @@
|
|||
/*
|
||||
* Ultra Wide Band
|
||||
* Neighborhood Management Daemon
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This daemon takes care of maintaing information that describes the
|
||||
* UWB neighborhood that the radios in this machine can see. It also
|
||||
* keeps a tab of which devices are visible, makes sure each HC sits
|
||||
* on a different channel to avoid interfering, etc.
|
||||
*
|
||||
* Different drivers (radio controller, device, any API in general)
|
||||
* communicate with this daemon through an event queue. Daemon wakes
|
||||
* up, takes a list of events and handles them one by one; handling
|
||||
* function is extracted from a table based on the event's type and
|
||||
* subtype. Events are freed only if the handling function says so.
|
||||
*
|
||||
* . Lock protecting the event list has to be an spinlock and locked
|
||||
* with IRQSAVE because it might be called from an interrupt
|
||||
* context (ie: when events arrive and the notification drops
|
||||
* down from the ISR).
|
||||
*
|
||||
* . UWB radio controller drivers queue events to the daemon using
|
||||
* uwbd_event_queue(). They just get the event, chew it to make it
|
||||
* look like UWBD likes it and pass it in a buffer allocated with
|
||||
* uwb_event_alloc().
|
||||
*
|
||||
* EVENTS
|
||||
*
|
||||
* Events have a type, a subtype, a lenght, some other stuff and the
|
||||
* data blob, which depends on the event. The header is 'struct
|
||||
* uwb_event'; for payloads, see 'struct uwbd_evt_*'.
|
||||
*
|
||||
* EVENT HANDLER TABLES
|
||||
*
|
||||
* To find a handling function for an event, the type is used to index
|
||||
* a subtype-table in the type-table. The subtype-table is indexed
|
||||
* with the subtype to get the function that handles the event. Start
|
||||
* with the main type-table 'uwbd_evt_type_handler'.
|
||||
*
|
||||
* DEVICES
|
||||
*
|
||||
* Devices are created when a bunch of beacons have been received and
|
||||
* it is stablished that the device has stable radio presence. CREATED
|
||||
* only, not configured. Devices are ONLY configured when an
|
||||
* Application-Specific IE Probe is receieved, in which the device
|
||||
* declares which Protocol ID it groks. Then the device is CONFIGURED
|
||||
* (and the driver->probe() stuff of the device model is invoked).
|
||||
*
|
||||
* Devices are considered disconnected when a certain number of
|
||||
* beacons are not received in an amount of time.
|
||||
*
|
||||
* Handler functions are called normally uwbd_evt_handle_*().
|
||||
*/
|
||||
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/freezer.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
#define D_LOCAL 1
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
|
||||
/**
|
||||
* UWBD Event handler function signature
|
||||
*
|
||||
* Return !0 if the event needs not to be freed (ie the handler
|
||||
* takes/took care of it). 0 means the daemon code will free the
|
||||
* event.
|
||||
*
|
||||
* @evt->rc is already referenced and guaranteed to exist. See
|
||||
* uwb_evt_handle().
|
||||
*/
|
||||
typedef int (*uwbd_evt_handler_f)(struct uwb_event *);
|
||||
|
||||
/**
|
||||
* Properties of a UWBD event
|
||||
*
|
||||
* @handler: the function that will handle this event
|
||||
* @name: text name of event
|
||||
*/
|
||||
struct uwbd_event {
|
||||
uwbd_evt_handler_f handler;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
/** Table of handlers for and properties of the UWBD Radio Control Events */
|
||||
static
|
||||
struct uwbd_event uwbd_events[] = {
|
||||
[UWB_RC_EVT_BEACON] = {
|
||||
.handler = uwbd_evt_handle_rc_beacon,
|
||||
.name = "BEACON_RECEIVED"
|
||||
},
|
||||
[UWB_RC_EVT_BEACON_SIZE] = {
|
||||
.handler = uwbd_evt_handle_rc_beacon_size,
|
||||
.name = "BEACON_SIZE_CHANGE"
|
||||
},
|
||||
[UWB_RC_EVT_BPOIE_CHANGE] = {
|
||||
.handler = uwbd_evt_handle_rc_bpoie_change,
|
||||
.name = "BPOIE_CHANGE"
|
||||
},
|
||||
[UWB_RC_EVT_BP_SLOT_CHANGE] = {
|
||||
.handler = uwbd_evt_handle_rc_bp_slot_change,
|
||||
.name = "BP_SLOT_CHANGE"
|
||||
},
|
||||
[UWB_RC_EVT_DRP_AVAIL] = {
|
||||
.handler = uwbd_evt_handle_rc_drp_avail,
|
||||
.name = "DRP_AVAILABILITY_CHANGE"
|
||||
},
|
||||
[UWB_RC_EVT_DRP] = {
|
||||
.handler = uwbd_evt_handle_rc_drp,
|
||||
.name = "DRP"
|
||||
},
|
||||
[UWB_RC_EVT_DEV_ADDR_CONFLICT] = {
|
||||
.handler = uwbd_evt_handle_rc_dev_addr_conflict,
|
||||
.name = "DEV_ADDR_CONFLICT",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct uwbd_evt_type_handler {
|
||||
const char *name;
|
||||
struct uwbd_event *uwbd_events;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
#define UWBD_EVT_TYPE_HANDLER(n,a) { \
|
||||
.name = (n), \
|
||||
.uwbd_events = (a), \
|
||||
.size = sizeof(a)/sizeof((a)[0]) \
|
||||
}
|
||||
|
||||
|
||||
/** Table of handlers for each UWBD Event type. */
|
||||
static
|
||||
struct uwbd_evt_type_handler uwbd_evt_type_handlers[] = {
|
||||
[UWB_RC_CET_GENERAL] = UWBD_EVT_TYPE_HANDLER("RC", uwbd_events)
|
||||
};
|
||||
|
||||
static const
|
||||
size_t uwbd_evt_type_handlers_len =
|
||||
sizeof(uwbd_evt_type_handlers) / sizeof(uwbd_evt_type_handlers[0]);
|
||||
|
||||
static const struct uwbd_event uwbd_message_handlers[] = {
|
||||
[UWB_EVT_MSG_RESET] = {
|
||||
.handler = uwbd_msg_handle_reset,
|
||||
.name = "reset",
|
||||
},
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(uwbd_event_mutex);
|
||||
|
||||
/**
|
||||
* Handle an URC event passed to the UWB Daemon
|
||||
*
|
||||
* @evt: the event to handle
|
||||
* @returns: 0 if the event can be kfreed, !0 on the contrary
|
||||
* (somebody else took ownership) [coincidentally, returning
|
||||
* a <0 errno code will free it :)].
|
||||
*
|
||||
* Looks up the two indirection tables (one for the type, one for the
|
||||
* subtype) to decide which function handles it and then calls the
|
||||
* handler.
|
||||
*
|
||||
* The event structure passed to the event handler has the radio
|
||||
* controller in @evt->rc referenced. The reference will be dropped
|
||||
* once the handler returns, so if it needs it for longer (async),
|
||||
* it'll need to take another one.
|
||||
*/
|
||||
static
|
||||
int uwbd_event_handle_urc(struct uwb_event *evt)
|
||||
{
|
||||
struct uwbd_evt_type_handler *type_table;
|
||||
uwbd_evt_handler_f handler;
|
||||
u8 type, context;
|
||||
u16 event;
|
||||
|
||||
type = evt->notif.rceb->bEventType;
|
||||
event = le16_to_cpu(evt->notif.rceb->wEvent);
|
||||
context = evt->notif.rceb->bEventContext;
|
||||
|
||||
if (type > uwbd_evt_type_handlers_len) {
|
||||
printk(KERN_ERR "UWBD: event type %u: unknown (too high)\n", type);
|
||||
return -EINVAL;
|
||||
}
|
||||
type_table = &uwbd_evt_type_handlers[type];
|
||||
if (type_table->uwbd_events == NULL) {
|
||||
printk(KERN_ERR "UWBD: event type %u: unknown\n", type);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (event > type_table->size) {
|
||||
printk(KERN_ERR "UWBD: event %s[%u]: unknown (too high)\n",
|
||||
type_table->name, event);
|
||||
return -EINVAL;
|
||||
}
|
||||
handler = type_table->uwbd_events[event].handler;
|
||||
if (handler == NULL) {
|
||||
printk(KERN_ERR "UWBD: event %s[%u]: unknown\n", type_table->name, event);
|
||||
return -EINVAL;
|
||||
}
|
||||
return (*handler)(evt);
|
||||
}
|
||||
|
||||
static void uwbd_event_handle_message(struct uwb_event *evt)
|
||||
{
|
||||
struct uwb_rc *rc;
|
||||
int result;
|
||||
|
||||
rc = evt->rc;
|
||||
|
||||
if (evt->message < 0 || evt->message >= ARRAY_SIZE(uwbd_message_handlers)) {
|
||||
dev_err(&rc->uwb_dev.dev, "UWBD: invalid message type %d\n", evt->message);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If this is a reset event we need to drop the
|
||||
* uwbd_event_mutex or it deadlocks when the reset handler
|
||||
* attempts to flush the uwbd events. */
|
||||
if (evt->message == UWB_EVT_MSG_RESET)
|
||||
mutex_unlock(&uwbd_event_mutex);
|
||||
|
||||
result = uwbd_message_handlers[evt->message].handler(evt);
|
||||
if (result < 0)
|
||||
dev_err(&rc->uwb_dev.dev, "UWBD: '%s' message failed: %d\n",
|
||||
uwbd_message_handlers[evt->message].name, result);
|
||||
|
||||
if (evt->message == UWB_EVT_MSG_RESET)
|
||||
mutex_lock(&uwbd_event_mutex);
|
||||
}
|
||||
|
||||
static void uwbd_event_handle(struct uwb_event *evt)
|
||||
{
|
||||
struct uwb_rc *rc;
|
||||
int should_keep;
|
||||
|
||||
rc = evt->rc;
|
||||
|
||||
if (rc->ready) {
|
||||
switch (evt->type) {
|
||||
case UWB_EVT_TYPE_NOTIF:
|
||||
should_keep = uwbd_event_handle_urc(evt);
|
||||
if (should_keep <= 0)
|
||||
kfree(evt->notif.rceb);
|
||||
break;
|
||||
case UWB_EVT_TYPE_MSG:
|
||||
uwbd_event_handle_message(evt);
|
||||
break;
|
||||
default:
|
||||
dev_err(&rc->uwb_dev.dev, "UWBD: invalid event type %d\n", evt->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
__uwb_rc_put(rc); /* for the __uwb_rc_get() in uwb_rc_notif_cb() */
|
||||
}
|
||||
/* The UWB Daemon */
|
||||
|
||||
|
||||
/** Daemon's PID: used to decide if we can queue or not */
|
||||
static int uwbd_pid;
|
||||
/** Daemon's task struct for managing the kthread */
|
||||
static struct task_struct *uwbd_task;
|
||||
/** Daemon's waitqueue for waiting for new events */
|
||||
static DECLARE_WAIT_QUEUE_HEAD(uwbd_wq);
|
||||
/** Daemon's list of events; we queue/dequeue here */
|
||||
static struct list_head uwbd_event_list = LIST_HEAD_INIT(uwbd_event_list);
|
||||
/** Daemon's list lock to protect concurent access */
|
||||
static DEFINE_SPINLOCK(uwbd_event_list_lock);
|
||||
|
||||
|
||||
/**
|
||||
* UWB Daemon
|
||||
*
|
||||
* Listens to all UWB notifications and takes care to track the state
|
||||
* of the UWB neighboorhood for the kernel. When we do a run, we
|
||||
* spinlock, move the list to a private copy and release the
|
||||
* lock. Hold it as little as possible. Not a conflict: it is
|
||||
* guaranteed we own the events in the private list.
|
||||
*
|
||||
* FIXME: should change so we don't have a 1HZ timer all the time, but
|
||||
* only if there are devices.
|
||||
*/
|
||||
static int uwbd(void *unused)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct list_head list = LIST_HEAD_INIT(list);
|
||||
struct uwb_event *evt, *nxt;
|
||||
int should_stop = 0;
|
||||
while (1) {
|
||||
wait_event_interruptible_timeout(
|
||||
uwbd_wq,
|
||||
!list_empty(&uwbd_event_list)
|
||||
|| (should_stop = kthread_should_stop()),
|
||||
HZ);
|
||||
if (should_stop)
|
||||
break;
|
||||
try_to_freeze();
|
||||
|
||||
mutex_lock(&uwbd_event_mutex);
|
||||
spin_lock_irqsave(&uwbd_event_list_lock, flags);
|
||||
list_splice_init(&uwbd_event_list, &list);
|
||||
spin_unlock_irqrestore(&uwbd_event_list_lock, flags);
|
||||
list_for_each_entry_safe(evt, nxt, &list, list_node) {
|
||||
list_del(&evt->list_node);
|
||||
uwbd_event_handle(evt);
|
||||
kfree(evt);
|
||||
}
|
||||
mutex_unlock(&uwbd_event_mutex);
|
||||
|
||||
uwb_beca_purge(); /* Purge devices that left */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/** Start the UWB daemon */
|
||||
void uwbd_start(void)
|
||||
{
|
||||
uwbd_task = kthread_run(uwbd, NULL, "uwbd");
|
||||
if (uwbd_task == NULL)
|
||||
printk(KERN_ERR "UWB: Cannot start management daemon; "
|
||||
"UWB won't work\n");
|
||||
else
|
||||
uwbd_pid = uwbd_task->pid;
|
||||
}
|
||||
|
||||
/* Stop the UWB daemon and free any unprocessed events */
|
||||
void uwbd_stop(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct uwb_event *evt, *nxt;
|
||||
kthread_stop(uwbd_task);
|
||||
spin_lock_irqsave(&uwbd_event_list_lock, flags);
|
||||
uwbd_pid = 0;
|
||||
list_for_each_entry_safe(evt, nxt, &uwbd_event_list, list_node) {
|
||||
if (evt->type == UWB_EVT_TYPE_NOTIF)
|
||||
kfree(evt->notif.rceb);
|
||||
kfree(evt);
|
||||
}
|
||||
spin_unlock_irqrestore(&uwbd_event_list_lock, flags);
|
||||
uwb_beca_release();
|
||||
}
|
||||
|
||||
/*
|
||||
* Queue an event for the management daemon
|
||||
*
|
||||
* When some lower layer receives an event, it uses this function to
|
||||
* push it forward to the UWB daemon.
|
||||
*
|
||||
* Once you pass the event, you don't own it any more, but the daemon
|
||||
* does. It will uwb_event_free() it when done, so make sure you
|
||||
* uwb_event_alloc()ed it or bad things will happen.
|
||||
*
|
||||
* If the daemon is not running, we just free the event.
|
||||
*/
|
||||
void uwbd_event_queue(struct uwb_event *evt)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&uwbd_event_list_lock, flags);
|
||||
if (uwbd_pid != 0) {
|
||||
list_add(&evt->list_node, &uwbd_event_list);
|
||||
wake_up_all(&uwbd_wq);
|
||||
} else {
|
||||
__uwb_rc_put(evt->rc);
|
||||
if (evt->type == UWB_EVT_TYPE_NOTIF)
|
||||
kfree(evt->notif.rceb);
|
||||
kfree(evt);
|
||||
}
|
||||
spin_unlock_irqrestore(&uwbd_event_list_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
void uwbd_flush(struct uwb_rc *rc)
|
||||
{
|
||||
struct uwb_event *evt, *nxt;
|
||||
|
||||
mutex_lock(&uwbd_event_mutex);
|
||||
|
||||
spin_lock_irq(&uwbd_event_list_lock);
|
||||
list_for_each_entry_safe(evt, nxt, &uwbd_event_list, list_node) {
|
||||
if (evt->rc == rc) {
|
||||
__uwb_rc_put(rc);
|
||||
list_del(&evt->list_node);
|
||||
if (evt->type == UWB_EVT_TYPE_NOTIF)
|
||||
kfree(evt->notif.rceb);
|
||||
kfree(evt);
|
||||
}
|
||||
}
|
||||
spin_unlock_irq(&uwbd_event_list_lock);
|
||||
|
||||
mutex_unlock(&uwbd_event_mutex);
|
||||
}
|
|
@ -0,0 +1,520 @@
|
|||
/*
|
||||
* Wireless Host Controller: Radio Control Interface (WHCI v0.95[2.3])
|
||||
* Radio Control command/event transport to the UWB stack
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Initialize and hook up the Radio Control interface.
|
||||
*
|
||||
* For each device probed, creates an 'struct whcrc' which contains
|
||||
* just the representation of the UWB Radio Controller, and the logic
|
||||
* for reading notifications and passing them to the UWB Core.
|
||||
*
|
||||
* So we initialize all of those, register the UWB Radio Controller
|
||||
* and setup the notification/event handle to pipe the notifications
|
||||
* to the UWB management Daemon.
|
||||
*
|
||||
* Once uwb_rc_add() is called, the UWB stack takes control, resets
|
||||
* the radio and readies the device to take commands the UWB
|
||||
* API/user-space.
|
||||
*
|
||||
* Note this driver is just a transport driver; the commands are
|
||||
* formed at the UWB stack and given to this driver who will deliver
|
||||
* them to the hw and transfer the replies/notifications back to the
|
||||
* UWB stack through the UWB daemon (UWBD).
|
||||
*/
|
||||
#include <linux/version.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/uwb.h>
|
||||
#include <linux/uwb/whci.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
#include "uwb-internal.h"
|
||||
|
||||
#define D_LOCAL 0
|
||||
#include <linux/uwb/debug.h>
|
||||
|
||||
/**
|
||||
* Descriptor for an instance of the UWB Radio Control Driver that
|
||||
* attaches to the URC interface of the WHCI PCI card.
|
||||
*
|
||||
* Unless there is a lock specific to the 'data members', all access
|
||||
* is protected by uwb_rc->mutex.
|
||||
*/
|
||||
struct whcrc {
|
||||
struct umc_dev *umc_dev;
|
||||
struct uwb_rc *uwb_rc; /* UWB host controller */
|
||||
|
||||
unsigned long area;
|
||||
void __iomem *rc_base;
|
||||
size_t rc_len;
|
||||
spinlock_t irq_lock;
|
||||
|
||||
void *evt_buf, *cmd_buf;
|
||||
dma_addr_t evt_dma_buf, cmd_dma_buf;
|
||||
wait_queue_head_t cmd_wq;
|
||||
struct work_struct event_work;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute an UWB RC command on WHCI/RC
|
||||
*
|
||||
* @rc: Instance of a Radio Controller that is a whcrc
|
||||
* @cmd: Buffer containing the RCCB and payload to execute
|
||||
* @cmd_size: Size of the command buffer.
|
||||
*
|
||||
* We copy the command into whcrc->cmd_buf (as it is pretty and
|
||||
* aligned`and physically contiguous) and then press the right keys in
|
||||
* the controller's URCCMD register to get it to read it. We might
|
||||
* have to wait for the cmd_sem to be open to us.
|
||||
*
|
||||
* NOTE: rc's mutex has to be locked
|
||||
*/
|
||||
static int whcrc_cmd(struct uwb_rc *uwb_rc,
|
||||
const struct uwb_rccb *cmd, size_t cmd_size)
|
||||
{
|
||||
int result = 0;
|
||||
struct whcrc *whcrc = uwb_rc->priv;
|
||||
struct device *dev = &whcrc->umc_dev->dev;
|
||||
u32 urccmd;
|
||||
|
||||
d_fnstart(3, dev, "(%p, %p, %zu)\n", uwb_rc, cmd, cmd_size);
|
||||
might_sleep();
|
||||
|
||||
if (cmd_size >= 4096) {
|
||||
result = -E2BIG;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the URC is halted, then the hardware has reset itself.
|
||||
* Attempt to recover by restarting the device and then return
|
||||
* an error as it's likely that the current command isn't
|
||||
* valid for a newly started RC.
|
||||
*/
|
||||
if (le_readl(whcrc->rc_base + URCSTS) & URCSTS_HALTED) {
|
||||
dev_err(dev, "requesting reset of halted radio controller\n");
|
||||
uwb_rc_reset_all(uwb_rc);
|
||||
result = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
result = wait_event_timeout(whcrc->cmd_wq,
|
||||
!(le_readl(whcrc->rc_base + URCCMD) & URCCMD_ACTIVE), HZ/2);
|
||||
if (result == 0) {
|
||||
dev_err(dev, "device is not ready to execute commands\n");
|
||||
result = -ETIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
memmove(whcrc->cmd_buf, cmd, cmd_size);
|
||||
le_writeq(whcrc->cmd_dma_buf, whcrc->rc_base + URCCMDADDR);
|
||||
|
||||
spin_lock(&whcrc->irq_lock);
|
||||
urccmd = le_readl(whcrc->rc_base + URCCMD);
|
||||
urccmd &= ~(URCCMD_EARV | URCCMD_SIZE_MASK);
|
||||
le_writel(urccmd | URCCMD_ACTIVE | URCCMD_IWR | cmd_size,
|
||||
whcrc->rc_base + URCCMD);
|
||||
spin_unlock(&whcrc->irq_lock);
|
||||
|
||||
error:
|
||||
d_fnend(3, dev, "(%p, %p, %zu) = %d\n",
|
||||
uwb_rc, cmd, cmd_size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int whcrc_reset(struct uwb_rc *rc)
|
||||
{
|
||||
struct whcrc *whcrc = rc->priv;
|
||||
|
||||
return umc_controller_reset(whcrc->umc_dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset event reception mechanism and tell hw we are ready to get more
|
||||
*
|
||||
* We have read all the events in the event buffer, so we are ready to
|
||||
* reset it to the beginning.
|
||||
*
|
||||
* This is only called during initialization or after an event buffer
|
||||
* has been retired. This means we can be sure that event processing
|
||||
* is disabled and it's safe to update the URCEVTADDR register.
|
||||
*
|
||||
* There's no need to wait for the event processing to start as the
|
||||
* URC will not clear URCCMD_ACTIVE until (internal) event buffer
|
||||
* space is available.
|
||||
*/
|
||||
static
|
||||
void whcrc_enable_events(struct whcrc *whcrc)
|
||||
{
|
||||
struct device *dev = &whcrc->umc_dev->dev;
|
||||
u32 urccmd;
|
||||
|
||||
d_fnstart(4, dev, "(whcrc %p)\n", whcrc);
|
||||
|
||||
le_writeq(whcrc->evt_dma_buf, whcrc->rc_base + URCEVTADDR);
|
||||
|
||||
spin_lock(&whcrc->irq_lock);
|
||||
urccmd = le_readl(whcrc->rc_base + URCCMD) & ~URCCMD_ACTIVE;
|
||||
le_writel(urccmd | URCCMD_EARV, whcrc->rc_base + URCCMD);
|
||||
spin_unlock(&whcrc->irq_lock);
|
||||
|
||||
d_fnend(4, dev, "(whcrc %p) = void\n", whcrc);
|
||||
}
|
||||
|
||||
static void whcrc_event_work(struct work_struct *work)
|
||||
{
|
||||
struct whcrc *whcrc = container_of(work, struct whcrc, event_work);
|
||||
struct device *dev = &whcrc->umc_dev->dev;
|
||||
size_t size;
|
||||
u64 urcevtaddr;
|
||||
|
||||
urcevtaddr = le_readq(whcrc->rc_base + URCEVTADDR);
|
||||
size = urcevtaddr & URCEVTADDR_OFFSET_MASK;
|
||||
|
||||
d_printf(3, dev, "received %zu octet event\n", size);
|
||||
d_dump(4, dev, whcrc->evt_buf, size > 32 ? 32 : size);
|
||||
|
||||
uwb_rc_neh_grok(whcrc->uwb_rc, whcrc->evt_buf, size);
|
||||
whcrc_enable_events(whcrc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch interrupts?
|
||||
*
|
||||
* We ack inmediately (and expect the hw to do the right thing and
|
||||
* raise another IRQ if things have changed :)
|
||||
*/
|
||||
static
|
||||
irqreturn_t whcrc_irq_cb(int irq, void *_whcrc)
|
||||
{
|
||||
struct whcrc *whcrc = _whcrc;
|
||||
struct device *dev = &whcrc->umc_dev->dev;
|
||||
u32 urcsts;
|
||||
|
||||
urcsts = le_readl(whcrc->rc_base + URCSTS);
|
||||
if (!(urcsts & URCSTS_INT_MASK))
|
||||
return IRQ_NONE;
|
||||
le_writel(urcsts & URCSTS_INT_MASK, whcrc->rc_base + URCSTS);
|
||||
|
||||
d_printf(4, dev, "acked 0x%08x, urcsts 0x%08x\n",
|
||||
le_readl(whcrc->rc_base + URCSTS), urcsts);
|
||||
|
||||
if (urcsts & URCSTS_HSE) {
|
||||
dev_err(dev, "host system error -- hardware halted\n");
|
||||
/* FIXME: do something sensible here */
|
||||
goto out;
|
||||
}
|
||||
if (urcsts & URCSTS_ER) {
|
||||
d_printf(3, dev, "ER: event ready\n");
|
||||
schedule_work(&whcrc->event_work);
|
||||
}
|
||||
if (urcsts & URCSTS_RCI) {
|
||||
d_printf(3, dev, "RCI: ready to execute another command\n");
|
||||
wake_up_all(&whcrc->cmd_wq);
|
||||
}
|
||||
out:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize a UMC RC interface: map regions, get (shared) IRQ
|
||||
*/
|
||||
static
|
||||
int whcrc_setup_rc_umc(struct whcrc *whcrc)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = &whcrc->umc_dev->dev;
|
||||
struct umc_dev *umc_dev = whcrc->umc_dev;
|
||||
|
||||
whcrc->area = umc_dev->resource.start;
|
||||
whcrc->rc_len = umc_dev->resource.end - umc_dev->resource.start + 1;
|
||||
result = -EBUSY;
|
||||
if (request_mem_region(whcrc->area, whcrc->rc_len, KBUILD_MODNAME)
|
||||
== NULL) {
|
||||
dev_err(dev, "can't request URC region (%zu bytes @ 0x%lx): %d\n",
|
||||
whcrc->rc_len, whcrc->area, result);
|
||||
goto error_request_region;
|
||||
}
|
||||
|
||||
whcrc->rc_base = ioremap_nocache(whcrc->area, whcrc->rc_len);
|
||||
if (whcrc->rc_base == NULL) {
|
||||
dev_err(dev, "can't ioremap registers (%zu bytes @ 0x%lx): %d\n",
|
||||
whcrc->rc_len, whcrc->area, result);
|
||||
goto error_ioremap_nocache;
|
||||
}
|
||||
|
||||
result = request_irq(umc_dev->irq, whcrc_irq_cb, IRQF_SHARED,
|
||||
KBUILD_MODNAME, whcrc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "can't allocate IRQ %d: %d\n",
|
||||
umc_dev->irq, result);
|
||||
goto error_request_irq;
|
||||
}
|
||||
|
||||
result = -ENOMEM;
|
||||
whcrc->cmd_buf = dma_alloc_coherent(&umc_dev->dev, PAGE_SIZE,
|
||||
&whcrc->cmd_dma_buf, GFP_KERNEL);
|
||||
if (whcrc->cmd_buf == NULL) {
|
||||
dev_err(dev, "Can't allocate cmd transfer buffer\n");
|
||||
goto error_cmd_buffer;
|
||||
}
|
||||
|
||||
whcrc->evt_buf = dma_alloc_coherent(&umc_dev->dev, PAGE_SIZE,
|
||||
&whcrc->evt_dma_buf, GFP_KERNEL);
|
||||
if (whcrc->evt_buf == NULL) {
|
||||
dev_err(dev, "Can't allocate evt transfer buffer\n");
|
||||
goto error_evt_buffer;
|
||||
}
|
||||
d_printf(3, dev, "UWB RC Interface: %zu bytes at 0x%p, irq %u\n",
|
||||
whcrc->rc_len, whcrc->rc_base, umc_dev->irq);
|
||||
return 0;
|
||||
|
||||
error_evt_buffer:
|
||||
dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->cmd_buf,
|
||||
whcrc->cmd_dma_buf);
|
||||
error_cmd_buffer:
|
||||
free_irq(umc_dev->irq, whcrc);
|
||||
error_request_irq:
|
||||
iounmap(whcrc->rc_base);
|
||||
error_ioremap_nocache:
|
||||
release_mem_region(whcrc->area, whcrc->rc_len);
|
||||
error_request_region:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Release RC's UMC resources
|
||||
*/
|
||||
static
|
||||
void whcrc_release_rc_umc(struct whcrc *whcrc)
|
||||
{
|
||||
struct umc_dev *umc_dev = whcrc->umc_dev;
|
||||
|
||||
dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->evt_buf,
|
||||
whcrc->evt_dma_buf);
|
||||
dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->cmd_buf,
|
||||
whcrc->cmd_dma_buf);
|
||||
free_irq(umc_dev->irq, whcrc);
|
||||
iounmap(whcrc->rc_base);
|
||||
release_mem_region(whcrc->area, whcrc->rc_len);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* whcrc_start_rc - start a WHCI radio controller
|
||||
* @whcrc: the radio controller to start
|
||||
*
|
||||
* Reset the UMC device, start the radio controller, enable events and
|
||||
* finally enable interrupts.
|
||||
*/
|
||||
static int whcrc_start_rc(struct uwb_rc *rc)
|
||||
{
|
||||
struct whcrc *whcrc = rc->priv;
|
||||
int result = 0;
|
||||
struct device *dev = &whcrc->umc_dev->dev;
|
||||
unsigned long start, duration;
|
||||
|
||||
/* Reset the thing */
|
||||
le_writel(URCCMD_RESET, whcrc->rc_base + URCCMD);
|
||||
if (d_test(3))
|
||||
start = jiffies;
|
||||
if (whci_wait_for(dev, whcrc->rc_base + URCCMD, URCCMD_RESET, 0,
|
||||
5000, "device to reset at init") < 0) {
|
||||
result = -EBUSY;
|
||||
goto error;
|
||||
} else if (d_test(3)) {
|
||||
duration = jiffies - start;
|
||||
if (duration > msecs_to_jiffies(40))
|
||||
dev_err(dev, "Device took %ums to "
|
||||
"reset. MAX expected: 40ms\n",
|
||||
jiffies_to_msecs(duration));
|
||||
}
|
||||
|
||||
/* Set the event buffer, start the controller (enable IRQs later) */
|
||||
le_writel(0, whcrc->rc_base + URCINTR);
|
||||
le_writel(URCCMD_RS, whcrc->rc_base + URCCMD);
|
||||
result = -ETIMEDOUT;
|
||||
if (d_test(3))
|
||||
start = jiffies;
|
||||
if (whci_wait_for(dev, whcrc->rc_base + URCSTS, URCSTS_HALTED, 0,
|
||||
5000, "device to start") < 0)
|
||||
goto error;
|
||||
if (d_test(3)) {
|
||||
duration = jiffies - start;
|
||||
if (duration > msecs_to_jiffies(40))
|
||||
dev_err(dev, "Device took %ums to start. "
|
||||
"MAX expected: 40ms\n",
|
||||
jiffies_to_msecs(duration));
|
||||
}
|
||||
whcrc_enable_events(whcrc);
|
||||
result = 0;
|
||||
le_writel(URCINTR_EN_ALL, whcrc->rc_base + URCINTR);
|
||||
error:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* whcrc_stop_rc - stop a WHCI radio controller
|
||||
* @whcrc: the radio controller to stop
|
||||
*
|
||||
* Disable interrupts and cancel any pending event processing work
|
||||
* before clearing the Run/Stop bit.
|
||||
*/
|
||||
static
|
||||
void whcrc_stop_rc(struct uwb_rc *rc)
|
||||
{
|
||||
struct whcrc *whcrc = rc->priv;
|
||||
struct umc_dev *umc_dev = whcrc->umc_dev;
|
||||
|
||||
le_writel(0, whcrc->rc_base + URCINTR);
|
||||
cancel_work_sync(&whcrc->event_work);
|
||||
|
||||
le_writel(0, whcrc->rc_base + URCCMD);
|
||||
whci_wait_for(&umc_dev->dev, whcrc->rc_base + URCSTS,
|
||||
URCSTS_HALTED, 0, 40, "URCSTS.HALTED");
|
||||
}
|
||||
|
||||
static void whcrc_init(struct whcrc *whcrc)
|
||||
{
|
||||
spin_lock_init(&whcrc->irq_lock);
|
||||
init_waitqueue_head(&whcrc->cmd_wq);
|
||||
INIT_WORK(&whcrc->event_work, whcrc_event_work);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the radio controller.
|
||||
*
|
||||
* NOTE: we setup whcrc->uwb_rc before calling uwb_rc_add(); in the
|
||||
* IRQ handler we use that to determine if the hw is ready to
|
||||
* handle events. Looks like a race condition, but it really is
|
||||
* not.
|
||||
*/
|
||||
static
|
||||
int whcrc_probe(struct umc_dev *umc_dev)
|
||||
{
|
||||
int result;
|
||||
struct uwb_rc *uwb_rc;
|
||||
struct whcrc *whcrc;
|
||||
struct device *dev = &umc_dev->dev;
|
||||
|
||||
d_fnstart(3, dev, "(umc_dev %p)\n", umc_dev);
|
||||
result = -ENOMEM;
|
||||
uwb_rc = uwb_rc_alloc();
|
||||
if (uwb_rc == NULL) {
|
||||
dev_err(dev, "unable to allocate RC instance\n");
|
||||
goto error_rc_alloc;
|
||||
}
|
||||
whcrc = kzalloc(sizeof(*whcrc), GFP_KERNEL);
|
||||
if (whcrc == NULL) {
|
||||
dev_err(dev, "unable to allocate WHC-RC instance\n");
|
||||
goto error_alloc;
|
||||
}
|
||||
whcrc_init(whcrc);
|
||||
whcrc->umc_dev = umc_dev;
|
||||
|
||||
result = whcrc_setup_rc_umc(whcrc);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Can't setup RC UMC interface: %d\n", result);
|
||||
goto error_setup_rc_umc;
|
||||
}
|
||||
whcrc->uwb_rc = uwb_rc;
|
||||
|
||||
uwb_rc->owner = THIS_MODULE;
|
||||
uwb_rc->cmd = whcrc_cmd;
|
||||
uwb_rc->reset = whcrc_reset;
|
||||
uwb_rc->start = whcrc_start_rc;
|
||||
uwb_rc->stop = whcrc_stop_rc;
|
||||
|
||||
result = uwb_rc_add(uwb_rc, dev, whcrc);
|
||||
if (result < 0)
|
||||
goto error_rc_add;
|
||||
umc_set_drvdata(umc_dev, whcrc);
|
||||
d_fnend(3, dev, "(umc_dev %p) = 0\n", umc_dev);
|
||||
return 0;
|
||||
|
||||
error_rc_add:
|
||||
whcrc_release_rc_umc(whcrc);
|
||||
error_setup_rc_umc:
|
||||
kfree(whcrc);
|
||||
error_alloc:
|
||||
uwb_rc_put(uwb_rc);
|
||||
error_rc_alloc:
|
||||
d_fnend(3, dev, "(umc_dev %p) = %d\n", umc_dev, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the radio control resources
|
||||
*
|
||||
* When we up the command semaphore, everybody possibly held trying to
|
||||
* execute a command should be granted entry and then they'll see the
|
||||
* host is quiescing and up it (so it will chain to the next waiter).
|
||||
* This should not happen (in any case), as we can only remove when
|
||||
* there are no handles open...
|
||||
*/
|
||||
static void whcrc_remove(struct umc_dev *umc_dev)
|
||||
{
|
||||
struct whcrc *whcrc = umc_get_drvdata(umc_dev);
|
||||
struct uwb_rc *uwb_rc = whcrc->uwb_rc;
|
||||
|
||||
umc_set_drvdata(umc_dev, NULL);
|
||||
uwb_rc_rm(uwb_rc);
|
||||
whcrc_release_rc_umc(whcrc);
|
||||
kfree(whcrc);
|
||||
uwb_rc_put(uwb_rc);
|
||||
d_printf(1, &umc_dev->dev, "freed whcrc %p\n", whcrc);
|
||||
}
|
||||
|
||||
/* PCI device ID's that we handle [so it gets loaded] */
|
||||
static struct pci_device_id whcrc_id_table[] = {
|
||||
{ PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
|
||||
{ /* empty last entry */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, whcrc_id_table);
|
||||
|
||||
static struct umc_driver whcrc_driver = {
|
||||
.name = "whc-rc",
|
||||
.cap_id = UMC_CAP_ID_WHCI_RC,
|
||||
.probe = whcrc_probe,
|
||||
.remove = whcrc_remove,
|
||||
};
|
||||
|
||||
static int __init whcrc_driver_init(void)
|
||||
{
|
||||
return umc_driver_register(&whcrc_driver);
|
||||
}
|
||||
module_init(whcrc_driver_init);
|
||||
|
||||
static void __exit whcrc_driver_exit(void)
|
||||
{
|
||||
umc_driver_unregister(&whcrc_driver);
|
||||
}
|
||||
module_exit(whcrc_driver_exit);
|
||||
|
||||
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
|
||||
MODULE_DESCRIPTION("Wireless Host Controller Radio Control Driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* WHCI UWB Multi-interface Controller enumerator.
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
*
|
||||
* This file is released under the GNU GPL v2.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/uwb/whci.h>
|
||||
#include <linux/uwb/umc.h>
|
||||
|
||||
struct whci_card {
|
||||
struct pci_dev *pci;
|
||||
void __iomem *uwbbase;
|
||||
u8 n_caps;
|
||||
struct umc_dev *devs[0];
|
||||
};
|
||||
|
||||
|
||||
/* Fix faulty HW :( */
|
||||
static
|
||||
u64 whci_capdata_quirks(struct whci_card *card, u64 capdata)
|
||||
{
|
||||
u64 capdata_orig = capdata;
|
||||
struct pci_dev *pci_dev = card->pci;
|
||||
if (pci_dev->vendor == PCI_VENDOR_ID_INTEL
|
||||
&& (pci_dev->device == 0x0c3b || pci_dev->device == 0004)
|
||||
&& pci_dev->class == 0x0d1010) {
|
||||
switch (UWBCAPDATA_TO_CAP_ID(capdata)) {
|
||||
/* WLP capability has 0x100 bytes of aperture */
|
||||
case 0x80:
|
||||
capdata |= 0x40 << 8; break;
|
||||
/* WUSB capability has 0x80 bytes of aperture
|
||||
* and ID is 1 */
|
||||
case 0x02:
|
||||
capdata &= ~0xffff;
|
||||
capdata |= 0x2001;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (capdata_orig != capdata)
|
||||
dev_warn(&pci_dev->dev,
|
||||
"PCI v%04x d%04x c%06x#%02x: "
|
||||
"corrected capdata from %016Lx to %016Lx\n",
|
||||
pci_dev->vendor, pci_dev->device, pci_dev->class,
|
||||
(unsigned)UWBCAPDATA_TO_CAP_ID(capdata),
|
||||
(unsigned long long)capdata_orig,
|
||||
(unsigned long long)capdata);
|
||||
return capdata;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* whci_wait_for - wait for a WHCI register to be set
|
||||
*
|
||||
* Polls (for at most @max_ms ms) until '*@reg & @mask == @result'.
|
||||
*/
|
||||
int whci_wait_for(struct device *dev, u32 __iomem *reg, u32 mask, u32 result,
|
||||
unsigned long max_ms, const char *tag)
|
||||
{
|
||||
unsigned t = 0;
|
||||
u32 val;
|
||||
for (;;) {
|
||||
val = le_readl(reg);
|
||||
if ((val & mask) == result)
|
||||
break;
|
||||
msleep(10);
|
||||
if (t >= max_ms) {
|
||||
dev_err(dev, "timed out waiting for %s ", tag);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
t += 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(whci_wait_for);
|
||||
|
||||
|
||||
/*
|
||||
* NOTE: the capinfo and capdata registers are slightly different
|
||||
* (size and cap-id fields). So for cap #0, we need to fill
|
||||
* in. Size comes from the size of the register block
|
||||
* (statically calculated); cap_id comes from nowhere, we use
|
||||
* zero, that is reserved, for the radio controller, because
|
||||
* none was defined at the spec level.
|
||||
*/
|
||||
static int whci_add_cap(struct whci_card *card, int n)
|
||||
{
|
||||
struct umc_dev *umc;
|
||||
u64 capdata;
|
||||
int bar, err;
|
||||
|
||||
umc = umc_device_create(&card->pci->dev, n);
|
||||
if (umc == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
capdata = le_readq(card->uwbbase + UWBCAPDATA(n));
|
||||
|
||||
bar = UWBCAPDATA_TO_BAR(capdata) << 1;
|
||||
|
||||
capdata = whci_capdata_quirks(card, capdata);
|
||||
/* Capability 0 is the radio controller. It's size is 32
|
||||
* bytes (WHCI0.95[2.3, T2-9]). */
|
||||
umc->version = UWBCAPDATA_TO_VERSION(capdata);
|
||||
umc->cap_id = n == 0 ? 0 : UWBCAPDATA_TO_CAP_ID(capdata);
|
||||
umc->bar = bar;
|
||||
umc->resource.start = pci_resource_start(card->pci, bar)
|
||||
+ UWBCAPDATA_TO_OFFSET(capdata);
|
||||
umc->resource.end = umc->resource.start
|
||||
+ (n == 0 ? 0x20 : UWBCAPDATA_TO_SIZE(capdata)) - 1;
|
||||
umc->resource.name = umc->dev.bus_id;
|
||||
umc->resource.flags = card->pci->resource[bar].flags;
|
||||
umc->resource.parent = &card->pci->resource[bar];
|
||||
umc->irq = card->pci->irq;
|
||||
|
||||
err = umc_device_register(umc);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
card->devs[n] = umc;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
kfree(umc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void whci_del_cap(struct whci_card *card, int n)
|
||||
{
|
||||
struct umc_dev *umc = card->devs[n];
|
||||
|
||||
if (umc != NULL)
|
||||
umc_device_unregister(umc);
|
||||
}
|
||||
|
||||
static int whci_n_caps(struct pci_dev *pci)
|
||||
{
|
||||
void __iomem *uwbbase;
|
||||
u64 capinfo;
|
||||
|
||||
uwbbase = pci_iomap(pci, 0, 8);
|
||||
if (!uwbbase)
|
||||
return -ENOMEM;
|
||||
capinfo = le_readq(uwbbase + UWBCAPINFO);
|
||||
pci_iounmap(pci, uwbbase);
|
||||
|
||||
return UWBCAPINFO_TO_N_CAPS(capinfo);
|
||||
}
|
||||
|
||||
static int whci_probe(struct pci_dev *pci, const struct pci_device_id *id)
|
||||
{
|
||||
struct whci_card *card;
|
||||
int err, n_caps, n;
|
||||
|
||||
err = pci_enable_device(pci);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
pci_enable_msi(pci);
|
||||
pci_set_master(pci);
|
||||
err = -ENXIO;
|
||||
if (!pci_set_dma_mask(pci, DMA_64BIT_MASK))
|
||||
pci_set_consistent_dma_mask(pci, DMA_64BIT_MASK);
|
||||
else if (!pci_set_dma_mask(pci, DMA_32BIT_MASK))
|
||||
pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK);
|
||||
else
|
||||
goto error_dma;
|
||||
|
||||
err = n_caps = whci_n_caps(pci);
|
||||
if (n_caps < 0)
|
||||
goto error_ncaps;
|
||||
|
||||
err = -ENOMEM;
|
||||
card = kzalloc(sizeof(struct whci_card)
|
||||
+ sizeof(struct whci_dev *) * (n_caps + 1),
|
||||
GFP_KERNEL);
|
||||
if (card == NULL)
|
||||
goto error_kzalloc;
|
||||
card->pci = pci;
|
||||
card->n_caps = n_caps;
|
||||
|
||||
err = -EBUSY;
|
||||
if (!request_mem_region(pci_resource_start(pci, 0),
|
||||
UWBCAPDATA_SIZE(card->n_caps),
|
||||
"whci (capability data)"))
|
||||
goto error_request_memregion;
|
||||
err = -ENOMEM;
|
||||
card->uwbbase = pci_iomap(pci, 0, UWBCAPDATA_SIZE(card->n_caps));
|
||||
if (!card->uwbbase)
|
||||
goto error_iomap;
|
||||
|
||||
/* Add each capability. */
|
||||
for (n = 0; n <= card->n_caps; n++) {
|
||||
err = whci_add_cap(card, n);
|
||||
if (err < 0 && n == 0) {
|
||||
dev_err(&pci->dev, "cannot bind UWB radio controller:"
|
||||
" %d\n", err);
|
||||
goto error_bind;
|
||||
}
|
||||
if (err < 0)
|
||||
dev_warn(&pci->dev, "warning: cannot bind capability "
|
||||
"#%u: %d\n", n, err);
|
||||
}
|
||||
pci_set_drvdata(pci, card);
|
||||
return 0;
|
||||
|
||||
error_bind:
|
||||
pci_iounmap(pci, card->uwbbase);
|
||||
error_iomap:
|
||||
release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps));
|
||||
error_request_memregion:
|
||||
kfree(card);
|
||||
error_kzalloc:
|
||||
error_ncaps:
|
||||
error_dma:
|
||||
pci_disable_msi(pci);
|
||||
pci_disable_device(pci);
|
||||
error:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void whci_remove(struct pci_dev *pci)
|
||||
{
|
||||
struct whci_card *card = pci_get_drvdata(pci);
|
||||
int n;
|
||||
|
||||
pci_set_drvdata(pci, NULL);
|
||||
/* Unregister each capability in reverse (so the master device
|
||||
* is unregistered last). */
|
||||
for (n = card->n_caps; n >= 0 ; n--)
|
||||
whci_del_cap(card, n);
|
||||
pci_iounmap(pci, card->uwbbase);
|
||||
release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps));
|
||||
kfree(card);
|
||||
pci_disable_msi(pci);
|
||||
pci_disable_device(pci);
|
||||
}
|
||||
|
||||
static struct pci_device_id whci_id_table[] = {
|
||||
{ PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
|
||||
{ 0 },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, whci_id_table);
|
||||
|
||||
|
||||
static struct pci_driver whci_driver = {
|
||||
.name = "whci",
|
||||
.id_table = whci_id_table,
|
||||
.probe = whci_probe,
|
||||
.remove = whci_remove,
|
||||
};
|
||||
|
||||
static int __init whci_init(void)
|
||||
{
|
||||
return pci_register_driver(&whci_driver);
|
||||
}
|
||||
|
||||
static void __exit whci_exit(void)
|
||||
{
|
||||
pci_unregister_driver(&whci_driver);
|
||||
}
|
||||
|
||||
module_init(whci_init);
|
||||
module_exit(whci_exit);
|
||||
|
||||
MODULE_DESCRIPTION("WHCI UWB Multi-interface Controller enumerator");
|
||||
MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,10 @@
|
|||
obj-$(CONFIG_UWB_WLP) := wlp.o
|
||||
|
||||
wlp-objs := \
|
||||
driver.o \
|
||||
eda.o \
|
||||
messages.o \
|
||||
sysfs.o \
|
||||
txrx.o \
|
||||
wlp-lc.o \
|
||||
wss-lc.o
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* WiMedia Logical Link Control Protocol (WLP)
|
||||
*
|
||||
* Copyright (C) 2007 Intel Corporation
|
||||
* Reinette Chatre <reinette.chatre@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.
|
||||
*
|
||||
*
|
||||
* Life cycle of WLP substack
|
||||
*
|
||||
* FIXME: Docs
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
|
||||
static int __init wlp_subsys_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
module_init(wlp_subsys_init);
|
||||
|
||||
static void __exit wlp_subsys_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
module_exit(wlp_subsys_exit);
|
||||
|
||||
MODULE_AUTHOR("Reinette Chatre <reinette.chatre@intel.com>");
|
||||
MODULE_DESCRIPTION("WiMedia Logical Link Control Protocol (WLP)");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,449 @@
|
|||
/*
|
||||
* WUSB Wire Adapter: WLP interface
|
||||
* Ethernet to device address cache
|
||||
*
|
||||
* 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 need to be able to map ethernet addresses to device addresses
|
||||
* and back because there is not explicit relationship between the eth
|
||||
* addresses used in the ETH frames and the device addresses (no, it
|
||||
* would not have been simpler to force as ETH address the MBOA MAC
|
||||
* address...no, not at all :).
|
||||
*
|
||||
* A device has one MBOA MAC address and one device address. It is possible
|
||||
* for a device to have more than one virtual MAC address (although a
|
||||
* virtual address can be the same as the MBOA MAC address). The device
|
||||
* address is guaranteed to be unique among the devices in the extended
|
||||
* beacon group (see ECMA 17.1.1). We thus use the device address as index
|
||||
* to this cache. We do allow searching based on virtual address as this
|
||||
* is how Ethernet frames will be addressed.
|
||||
*
|
||||
* We need to support virtual EUI-48. Although, right now the virtual
|
||||
* EUI-48 will always be the same as the MAC SAP address. The EDA cache
|
||||
* entry thus contains a MAC SAP address as well as the virtual address
|
||||
* (used to map the network stack address to a neighbor). When we move
|
||||
* to support more than one virtual MAC on a host then this organization
|
||||
* will have to change. Perhaps a neighbor has a list of WSSs, each with a
|
||||
* tag and virtual EUI-48.
|
||||
*
|
||||
* On data transmission
|
||||
* it is used to determine if the neighbor is connected and what WSS it
|
||||
* belongs to. With this we know what tag to add to the WLP frame. Storing
|
||||
* the WSS in the EDA cache may be overkill because we only support one
|
||||
* WSS. Hopefully we will support more than one WSS at some point.
|
||||
* On data reception it is used to determine the WSS based on
|
||||
* the tag and address of the transmitting neighbor.
|
||||
*/
|
||||
|
||||
#define D_LOCAL 5
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/uwb/debug.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/wlp.h>
|
||||
#include "wlp-internal.h"
|
||||
|
||||
|
||||
/* FIXME: cache is not purged, only on device close */
|
||||
|
||||
/* FIXME: does not scale, change to dynamic array */
|
||||
|
||||
/*
|
||||
* Initialize the EDA cache
|
||||
*
|
||||
* @returns 0 if ok, < 0 errno code on error
|
||||
*
|
||||
* Call when the interface is being brought up
|
||||
*
|
||||
* NOTE: Keep it as a separate function as the implementation will
|
||||
* change and be more complex.
|
||||
*/
|
||||
void wlp_eda_init(struct wlp_eda *eda)
|
||||
{
|
||||
INIT_LIST_HEAD(&eda->cache);
|
||||
spin_lock_init(&eda->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Release the EDA cache
|
||||
*
|
||||
* @returns 0 if ok, < 0 errno code on error
|
||||
*
|
||||
* Called when the interface is brought down
|
||||
*/
|
||||
void wlp_eda_release(struct wlp_eda *eda)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct wlp_eda_node *itr, *next;
|
||||
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
list_for_each_entry_safe(itr, next, &eda->cache, list_node) {
|
||||
list_del(&itr->list_node);
|
||||
kfree(itr);
|
||||
}
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an address mapping
|
||||
*
|
||||
* @returns 0 if ok, < 0 errno code on error
|
||||
*
|
||||
* An address mapping is initially created when the neighbor device is seen
|
||||
* for the first time (it is "onair"). At this time the neighbor is not
|
||||
* connected or associated with a WSS so we only populate the Ethernet and
|
||||
* Device address fields.
|
||||
*
|
||||
*/
|
||||
int wlp_eda_create_node(struct wlp_eda *eda,
|
||||
const unsigned char eth_addr[ETH_ALEN],
|
||||
const struct uwb_dev_addr *dev_addr)
|
||||
{
|
||||
int result = 0;
|
||||
struct wlp_eda_node *itr;
|
||||
unsigned long flags;
|
||||
|
||||
BUG_ON(dev_addr == NULL || eth_addr == NULL);
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
list_for_each_entry(itr, &eda->cache, list_node) {
|
||||
if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) {
|
||||
printk(KERN_ERR "EDA cache already contains entry "
|
||||
"for neighbor %02x:%02x\n",
|
||||
dev_addr->data[1], dev_addr->data[0]);
|
||||
result = -EEXIST;
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
itr = kzalloc(sizeof(*itr), GFP_ATOMIC);
|
||||
if (itr != NULL) {
|
||||
memcpy(itr->eth_addr, eth_addr, sizeof(itr->eth_addr));
|
||||
itr->dev_addr = *dev_addr;
|
||||
list_add(&itr->list_node, &eda->cache);
|
||||
} else
|
||||
result = -ENOMEM;
|
||||
out_unlock:
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove entry from EDA cache
|
||||
*
|
||||
* This is done when the device goes off air.
|
||||
*/
|
||||
void wlp_eda_rm_node(struct wlp_eda *eda, const struct uwb_dev_addr *dev_addr)
|
||||
{
|
||||
struct wlp_eda_node *itr, *next;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
list_for_each_entry_safe(itr, next, &eda->cache, list_node) {
|
||||
if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) {
|
||||
list_del(&itr->list_node);
|
||||
kfree(itr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update an address mapping
|
||||
*
|
||||
* @returns 0 if ok, < 0 errno code on error
|
||||
*/
|
||||
int wlp_eda_update_node(struct wlp_eda *eda,
|
||||
const struct uwb_dev_addr *dev_addr,
|
||||
struct wlp_wss *wss,
|
||||
const unsigned char virt_addr[ETH_ALEN],
|
||||
const u8 tag, const enum wlp_wss_connect state)
|
||||
{
|
||||
int result = -ENOENT;
|
||||
struct wlp_eda_node *itr;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
list_for_each_entry(itr, &eda->cache, list_node) {
|
||||
if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) {
|
||||
/* Found it, update it */
|
||||
itr->wss = wss;
|
||||
memcpy(itr->virt_addr, virt_addr,
|
||||
sizeof(itr->virt_addr));
|
||||
itr->tag = tag;
|
||||
itr->state = state;
|
||||
result = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
/* Not found */
|
||||
out_unlock:
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update only state field of an address mapping
|
||||
*
|
||||
* @returns 0 if ok, < 0 errno code on error
|
||||
*/
|
||||
int wlp_eda_update_node_state(struct wlp_eda *eda,
|
||||
const struct uwb_dev_addr *dev_addr,
|
||||
const enum wlp_wss_connect state)
|
||||
{
|
||||
int result = -ENOENT;
|
||||
struct wlp_eda_node *itr;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
list_for_each_entry(itr, &eda->cache, list_node) {
|
||||
if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) {
|
||||
/* Found it, update it */
|
||||
itr->state = state;
|
||||
result = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
/* Not found */
|
||||
out_unlock:
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return contents of EDA cache entry
|
||||
*
|
||||
* @dev_addr: index to EDA cache
|
||||
* @eda_entry: pointer to where contents of EDA cache will be copied
|
||||
*/
|
||||
int wlp_copy_eda_node(struct wlp_eda *eda, struct uwb_dev_addr *dev_addr,
|
||||
struct wlp_eda_node *eda_entry)
|
||||
{
|
||||
int result = -ENOENT;
|
||||
struct wlp_eda_node *itr;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
list_for_each_entry(itr, &eda->cache, list_node) {
|
||||
if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) {
|
||||
*eda_entry = *itr;
|
||||
result = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
/* Not found */
|
||||
out_unlock:
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute function for every element in the cache
|
||||
*
|
||||
* @function: function to execute on element of cache (must be atomic)
|
||||
* @priv: private data of function
|
||||
* @returns: result of first function that failed, or last function
|
||||
* executed if no function failed.
|
||||
*
|
||||
* Stop executing when function returns error for any element in cache.
|
||||
*
|
||||
* IMPORTANT: We are using a spinlock here: the function executed on each
|
||||
* element has to be atomic.
|
||||
*/
|
||||
int wlp_eda_for_each(struct wlp_eda *eda, wlp_eda_for_each_f function,
|
||||
void *priv)
|
||||
{
|
||||
int result = 0;
|
||||
struct wlp *wlp = container_of(eda, struct wlp, eda);
|
||||
struct wlp_eda_node *entry;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
list_for_each_entry(entry, &eda->cache, list_node) {
|
||||
result = (*function)(wlp, entry, priv);
|
||||
if (result < 0)
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute function for single element in the cache (return dev addr)
|
||||
*
|
||||
* @virt_addr: index into EDA cache used to determine which element to
|
||||
* execute the function on
|
||||
* @dev_addr: device address of element in cache will be returned using
|
||||
* @dev_addr
|
||||
* @function: function to execute on element of cache (must be atomic)
|
||||
* @priv: private data of function
|
||||
* @returns: result of function
|
||||
*
|
||||
* IMPORTANT: We are using a spinlock here: the function executed on the
|
||||
* element has to be atomic.
|
||||
*/
|
||||
int wlp_eda_for_virtual(struct wlp_eda *eda,
|
||||
const unsigned char virt_addr[ETH_ALEN],
|
||||
struct uwb_dev_addr *dev_addr,
|
||||
wlp_eda_for_each_f function,
|
||||
void *priv)
|
||||
{
|
||||
int result = 0;
|
||||
struct wlp *wlp = container_of(eda, struct wlp, eda);
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
struct wlp_eda_node *itr;
|
||||
unsigned long flags;
|
||||
int found = 0;
|
||||
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
list_for_each_entry(itr, &eda->cache, list_node) {
|
||||
if (!memcmp(itr->virt_addr, virt_addr,
|
||||
sizeof(itr->virt_addr))) {
|
||||
d_printf(6, dev, "EDA: looking for "
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x hit %02x:%02x "
|
||||
"wss %p tag 0x%02x state %u\n",
|
||||
virt_addr[0], virt_addr[1],
|
||||
virt_addr[2], virt_addr[3],
|
||||
virt_addr[4], virt_addr[5],
|
||||
itr->dev_addr.data[1],
|
||||
itr->dev_addr.data[0], itr->wss,
|
||||
itr->tag, itr->state);
|
||||
result = (*function)(wlp, itr, priv);
|
||||
*dev_addr = itr->dev_addr;
|
||||
found = 1;
|
||||
break;
|
||||
} else
|
||||
d_printf(6, dev, "EDA: looking for "
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x "
|
||||
"against "
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x miss\n",
|
||||
virt_addr[0], virt_addr[1],
|
||||
virt_addr[2], virt_addr[3],
|
||||
virt_addr[4], virt_addr[5],
|
||||
itr->virt_addr[0], itr->virt_addr[1],
|
||||
itr->virt_addr[2], itr->virt_addr[3],
|
||||
itr->virt_addr[4], itr->virt_addr[5]);
|
||||
}
|
||||
if (!found) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "EDA: Eth addr %02x:%02x:%02x"
|
||||
":%02x:%02x:%02x not found.\n",
|
||||
virt_addr[0], virt_addr[1],
|
||||
virt_addr[2], virt_addr[3],
|
||||
virt_addr[4], virt_addr[5]);
|
||||
result = -ENODEV;
|
||||
}
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *__wlp_wss_connect_state[] = { "WLP_WSS_UNCONNECTED",
|
||||
"WLP_WSS_CONNECTED",
|
||||
"WLP_WSS_CONNECT_FAILED",
|
||||
};
|
||||
|
||||
static const char *wlp_wss_connect_state_str(unsigned id)
|
||||
{
|
||||
if (id >= ARRAY_SIZE(__wlp_wss_connect_state))
|
||||
return "unknown WSS connection state";
|
||||
return __wlp_wss_connect_state[id];
|
||||
}
|
||||
|
||||
/*
|
||||
* View EDA cache from user space
|
||||
*
|
||||
* A debugging feature to give user visibility into the EDA cache. Also
|
||||
* used to display members of WSS to user (called from wlp_wss_members_show())
|
||||
*/
|
||||
ssize_t wlp_eda_show(struct wlp *wlp, char *buf)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
struct wlp_eda_node *entry;
|
||||
unsigned long flags;
|
||||
struct wlp_eda *eda = &wlp->eda;
|
||||
spin_lock_irqsave(&eda->lock, flags);
|
||||
result = scnprintf(buf, PAGE_SIZE, "#eth_addr dev_addr wss_ptr "
|
||||
"tag state virt_addr\n");
|
||||
list_for_each_entry(entry, &eda->cache, list_node) {
|
||||
result += scnprintf(buf + result, PAGE_SIZE - result,
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x %02x:%02x "
|
||||
"%p 0x%02x %s "
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
entry->eth_addr[0], entry->eth_addr[1],
|
||||
entry->eth_addr[2], entry->eth_addr[3],
|
||||
entry->eth_addr[4], entry->eth_addr[5],
|
||||
entry->dev_addr.data[1],
|
||||
entry->dev_addr.data[0], entry->wss,
|
||||
entry->tag,
|
||||
wlp_wss_connect_state_str(entry->state),
|
||||
entry->virt_addr[0], entry->virt_addr[1],
|
||||
entry->virt_addr[2], entry->virt_addr[3],
|
||||
entry->virt_addr[4], entry->virt_addr[5]);
|
||||
if (result >= PAGE_SIZE)
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&eda->lock, flags);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_eda_show);
|
||||
|
||||
/*
|
||||
* Add new EDA cache entry based on user input in sysfs
|
||||
*
|
||||
* Should only be used for debugging.
|
||||
*
|
||||
* The WSS is assumed to be the only WSS supported. This needs to be
|
||||
* redesigned when we support more than one WSS.
|
||||
*/
|
||||
ssize_t wlp_eda_store(struct wlp *wlp, const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct wlp_eda *eda = &wlp->eda;
|
||||
u8 eth_addr[6];
|
||||
struct uwb_dev_addr dev_addr;
|
||||
u8 tag;
|
||||
unsigned state;
|
||||
|
||||
result = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx "
|
||||
"%02hhx:%02hhx %02hhx %u\n",
|
||||
ð_addr[0], ð_addr[1],
|
||||
ð_addr[2], ð_addr[3],
|
||||
ð_addr[4], ð_addr[5],
|
||||
&dev_addr.data[1], &dev_addr.data[0], &tag, &state);
|
||||
switch (result) {
|
||||
case 6: /* no dev addr specified -- remove entry NOT IMPLEMENTED */
|
||||
/*result = wlp_eda_rm(eda, eth_addr, &dev_addr);*/
|
||||
result = -ENOSYS;
|
||||
break;
|
||||
case 10:
|
||||
state = state >= 1 ? 1 : 0;
|
||||
result = wlp_eda_create_node(eda, eth_addr, &dev_addr);
|
||||
if (result < 0 && result != -EEXIST)
|
||||
goto error;
|
||||
/* Set virtual addr to be same as MAC */
|
||||
result = wlp_eda_update_node(eda, &dev_addr, &wlp->wss,
|
||||
eth_addr, tag, state);
|
||||
if (result < 0)
|
||||
goto error;
|
||||
break;
|
||||
default: /* bad format */
|
||||
result = -EINVAL;
|
||||
}
|
||||
error:
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_eda_store);
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,709 @@
|
|||
/*
|
||||
* WiMedia Logical Link Control Protocol (WLP)
|
||||
* sysfs functions
|
||||
*
|
||||
* Copyright (C) 2007 Intel Corporation
|
||||
* Reinette Chatre <reinette.chatre@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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/wlp.h>
|
||||
#include "wlp-internal.h"
|
||||
|
||||
static
|
||||
size_t wlp_wss_wssid_e_print(char *buf, size_t bufsize,
|
||||
struct wlp_wssid_e *wssid_e)
|
||||
{
|
||||
size_t used = 0;
|
||||
used += scnprintf(buf, bufsize, " WSS: ");
|
||||
used += wlp_wss_uuid_print(buf + used, bufsize - used,
|
||||
&wssid_e->wssid);
|
||||
|
||||
if (wssid_e->info != NULL) {
|
||||
used += scnprintf(buf + used, bufsize - used, " ");
|
||||
used += uwb_mac_addr_print(buf + used, bufsize - used,
|
||||
&wssid_e->info->bcast);
|
||||
used += scnprintf(buf + used, bufsize - used, " %u %u %s\n",
|
||||
wssid_e->info->accept_enroll,
|
||||
wssid_e->info->sec_status,
|
||||
wssid_e->info->name);
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print out information learned from neighbor discovery
|
||||
*
|
||||
* Some fields being printed may not be included in the device discovery
|
||||
* information (it is not mandatory). We are thus careful how the
|
||||
* information is printed to ensure it is clear to the user what field is
|
||||
* being referenced.
|
||||
* The information being printed is for one time use - temporary storage is
|
||||
* cleaned after it is printed.
|
||||
*
|
||||
* Ideally sysfs output should be on one line. The information printed here
|
||||
* contain a few strings so it will be hard to parse if they are all
|
||||
* printed on the same line - without agreeing on a standard field
|
||||
* separator.
|
||||
*/
|
||||
static
|
||||
ssize_t wlp_wss_neighborhood_print_remove(struct wlp *wlp, char *buf,
|
||||
size_t bufsize)
|
||||
{
|
||||
size_t used = 0;
|
||||
struct wlp_neighbor_e *neighb;
|
||||
struct wlp_wssid_e *wssid_e;
|
||||
|
||||
mutex_lock(&wlp->nbmutex);
|
||||
used = scnprintf(buf, bufsize, "#Neighbor information\n"
|
||||
"#uuid dev_addr\n"
|
||||
"# Device Name:\n# Model Name:\n# Manufacturer:\n"
|
||||
"# Model Nr:\n# Serial:\n"
|
||||
"# Pri Dev type: CategoryID OUI OUISubdiv "
|
||||
"SubcategoryID\n"
|
||||
"# WSS: WSSID WSS_name accept_enroll sec_status "
|
||||
"bcast\n"
|
||||
"# WSS: WSSID WSS_name accept_enroll sec_status "
|
||||
"bcast\n\n");
|
||||
list_for_each_entry(neighb, &wlp->neighbors, node) {
|
||||
if (bufsize - used <= 0)
|
||||
goto out;
|
||||
used += wlp_wss_uuid_print(buf + used, bufsize - used,
|
||||
&neighb->uuid);
|
||||
buf[used++] = ' ';
|
||||
used += uwb_dev_addr_print(buf + used, bufsize - used,
|
||||
&neighb->uwb_dev->dev_addr);
|
||||
if (neighb->info != NULL)
|
||||
used += scnprintf(buf + used, bufsize - used,
|
||||
"\n Device Name: %s\n"
|
||||
" Model Name: %s\n"
|
||||
" Manufacturer:%s \n"
|
||||
" Model Nr: %s\n"
|
||||
" Serial: %s\n"
|
||||
" Pri Dev type: "
|
||||
"%u %02x:%02x:%02x %u %u\n",
|
||||
neighb->info->name,
|
||||
neighb->info->model_name,
|
||||
neighb->info->manufacturer,
|
||||
neighb->info->model_nr,
|
||||
neighb->info->serial,
|
||||
neighb->info->prim_dev_type.category,
|
||||
neighb->info->prim_dev_type.OUI[0],
|
||||
neighb->info->prim_dev_type.OUI[1],
|
||||
neighb->info->prim_dev_type.OUI[2],
|
||||
neighb->info->prim_dev_type.OUIsubdiv,
|
||||
neighb->info->prim_dev_type.subID);
|
||||
list_for_each_entry(wssid_e, &neighb->wssid, node) {
|
||||
used += wlp_wss_wssid_e_print(buf + used,
|
||||
bufsize - used,
|
||||
wssid_e);
|
||||
}
|
||||
buf[used++] = '\n';
|
||||
wlp_remove_neighbor_tmp_info(neighb);
|
||||
}
|
||||
|
||||
|
||||
out:
|
||||
mutex_unlock(&wlp->nbmutex);
|
||||
return used;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show properties of all WSS in neighborhood.
|
||||
*
|
||||
* Will trigger a complete discovery of WSS activated by this device and
|
||||
* its neighbors.
|
||||
*/
|
||||
ssize_t wlp_neighborhood_show(struct wlp *wlp, char *buf)
|
||||
{
|
||||
wlp_discover(wlp);
|
||||
return wlp_wss_neighborhood_print_remove(wlp, buf, PAGE_SIZE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_neighborhood_show);
|
||||
|
||||
static
|
||||
ssize_t __wlp_wss_properties_show(struct wlp_wss *wss, char *buf,
|
||||
size_t bufsize)
|
||||
{
|
||||
ssize_t result;
|
||||
|
||||
result = wlp_wss_uuid_print(buf, bufsize, &wss->wssid);
|
||||
result += scnprintf(buf + result, bufsize - result, " ");
|
||||
result += uwb_mac_addr_print(buf + result, bufsize - result,
|
||||
&wss->bcast);
|
||||
result += scnprintf(buf + result, bufsize - result,
|
||||
" 0x%02x %u ", wss->hash, wss->secure_status);
|
||||
result += wlp_wss_key_print(buf + result, bufsize - result,
|
||||
wss->master_key);
|
||||
result += scnprintf(buf + result, bufsize - result, " 0x%02x ",
|
||||
wss->tag);
|
||||
result += uwb_mac_addr_print(buf + result, bufsize - result,
|
||||
&wss->virtual_addr);
|
||||
result += scnprintf(buf + result, bufsize - result, " %s", wss->name);
|
||||
result += scnprintf(buf + result, bufsize - result,
|
||||
"\n\n#WSSID\n#WSS broadcast address\n"
|
||||
"#WSS hash\n#WSS secure status\n"
|
||||
"#WSS master key\n#WSS local tag\n"
|
||||
"#WSS local virtual EUI-48\n#WSS name\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show which WSS is activated.
|
||||
*/
|
||||
ssize_t wlp_wss_activate_show(struct wlp_wss *wss, char *buf)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
if (mutex_lock_interruptible(&wss->mutex))
|
||||
goto out;
|
||||
if (wss->state >= WLP_WSS_STATE_ACTIVE)
|
||||
result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE);
|
||||
else
|
||||
result = scnprintf(buf, PAGE_SIZE, "No local WSS active.\n");
|
||||
result += scnprintf(buf + result, PAGE_SIZE - result,
|
||||
"\n\n"
|
||||
"# echo WSSID SECURE_STATUS ACCEPT_ENROLLMENT "
|
||||
"NAME #create new WSS\n"
|
||||
"# echo WSSID [DEV ADDR] #enroll in and activate "
|
||||
"existing WSS, can request registrar\n"
|
||||
"#\n"
|
||||
"# WSSID is a 16 byte hex array. Eg. 12 A3 3B ... \n"
|
||||
"# SECURE_STATUS 0 - unsecure, 1 - secure (default)\n"
|
||||
"# ACCEPT_ENROLLMENT 0 - no, 1 - yes (default)\n"
|
||||
"# NAME is the text string identifying the WSS\n"
|
||||
"# DEV ADDR is the device address of neighbor "
|
||||
"that should be registrar. Eg. 32:AB\n");
|
||||
|
||||
mutex_unlock(&wss->mutex);
|
||||
out:
|
||||
return result;
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_wss_activate_show);
|
||||
|
||||
/**
|
||||
* Create/activate a new WSS or enroll/activate in neighboring WSS
|
||||
*
|
||||
* The user can provide the WSSID of a WSS in which it wants to enroll.
|
||||
* Only the WSSID is necessary if the WSS have been discovered before. If
|
||||
* the WSS has not been discovered before, or the user wants to use a
|
||||
* particular neighbor as its registrar, then the user can also provide a
|
||||
* device address or the neighbor that will be used as registrar.
|
||||
*
|
||||
* A new WSS is created when the user provides a WSSID, secure status, and
|
||||
* WSS name.
|
||||
*/
|
||||
ssize_t wlp_wss_activate_store(struct wlp_wss *wss,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result = -EINVAL;
|
||||
struct wlp_uuid wssid;
|
||||
struct uwb_dev_addr dev;
|
||||
struct uwb_dev_addr bcast = {.data = {0xff, 0xff} };
|
||||
char name[65];
|
||||
unsigned sec_status, accept;
|
||||
memset(name, 0, sizeof(name));
|
||||
result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx:%02hhx",
|
||||
&wssid.data[0] , &wssid.data[1],
|
||||
&wssid.data[2] , &wssid.data[3],
|
||||
&wssid.data[4] , &wssid.data[5],
|
||||
&wssid.data[6] , &wssid.data[7],
|
||||
&wssid.data[8] , &wssid.data[9],
|
||||
&wssid.data[10], &wssid.data[11],
|
||||
&wssid.data[12], &wssid.data[13],
|
||||
&wssid.data[14], &wssid.data[15],
|
||||
&dev.data[1], &dev.data[0]);
|
||||
if (result == 16 || result == 17) {
|
||||
result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%u %u %64c",
|
||||
&wssid.data[0] , &wssid.data[1],
|
||||
&wssid.data[2] , &wssid.data[3],
|
||||
&wssid.data[4] , &wssid.data[5],
|
||||
&wssid.data[6] , &wssid.data[7],
|
||||
&wssid.data[8] , &wssid.data[9],
|
||||
&wssid.data[10], &wssid.data[11],
|
||||
&wssid.data[12], &wssid.data[13],
|
||||
&wssid.data[14], &wssid.data[15],
|
||||
&sec_status, &accept, name);
|
||||
if (result == 16)
|
||||
result = wlp_wss_enroll_activate(wss, &wssid, &bcast);
|
||||
else if (result == 19) {
|
||||
sec_status = sec_status == 0 ? 0 : 1;
|
||||
accept = accept == 0 ? 0 : 1;
|
||||
/* We read name using %c, so the newline needs to be
|
||||
* removed */
|
||||
if (strlen(name) != sizeof(name) - 1)
|
||||
name[strlen(name) - 1] = '\0';
|
||||
result = wlp_wss_create_activate(wss, &wssid, name,
|
||||
sec_status, accept);
|
||||
} else
|
||||
result = -EINVAL;
|
||||
} else if (result == 18)
|
||||
result = wlp_wss_enroll_activate(wss, &wssid, &dev);
|
||||
else
|
||||
result = -EINVAL;
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_wss_activate_store);
|
||||
|
||||
/**
|
||||
* Show the UUID of this host
|
||||
*/
|
||||
ssize_t wlp_uuid_show(struct wlp *wlp, char *buf)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
|
||||
mutex_lock(&wlp->mutex);
|
||||
result = wlp_wss_uuid_print(buf, PAGE_SIZE, &wlp->uuid);
|
||||
buf[result++] = '\n';
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_uuid_show);
|
||||
|
||||
/**
|
||||
* Store a new UUID for this host
|
||||
*
|
||||
* According to the spec this should be encoded as an octet string in the
|
||||
* order the octets are shown in string representation in RFC 4122 (WLP
|
||||
* 0.99 [Table 6])
|
||||
*
|
||||
* We do not check value provided by user.
|
||||
*/
|
||||
ssize_t wlp_uuid_store(struct wlp *wlp, const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
struct wlp_uuid uuid;
|
||||
|
||||
mutex_lock(&wlp->mutex);
|
||||
result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx "
|
||||
"%02hhx %02hhx %02hhx %02hhx ",
|
||||
&uuid.data[0] , &uuid.data[1],
|
||||
&uuid.data[2] , &uuid.data[3],
|
||||
&uuid.data[4] , &uuid.data[5],
|
||||
&uuid.data[6] , &uuid.data[7],
|
||||
&uuid.data[8] , &uuid.data[9],
|
||||
&uuid.data[10], &uuid.data[11],
|
||||
&uuid.data[12], &uuid.data[13],
|
||||
&uuid.data[14], &uuid.data[15]);
|
||||
if (result != 16) {
|
||||
result = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
wlp->uuid = uuid;
|
||||
error:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_uuid_store);
|
||||
|
||||
/**
|
||||
* Show contents of members of device information structure
|
||||
*/
|
||||
#define wlp_dev_info_show(type) \
|
||||
ssize_t wlp_dev_##type##_show(struct wlp *wlp, char *buf) \
|
||||
{ \
|
||||
ssize_t result = 0; \
|
||||
mutex_lock(&wlp->mutex); \
|
||||
if (wlp->dev_info == NULL) { \
|
||||
result = __wlp_setup_device_info(wlp); \
|
||||
if (result < 0) \
|
||||
goto out; \
|
||||
} \
|
||||
result = scnprintf(buf, PAGE_SIZE, "%s\n", wlp->dev_info->type);\
|
||||
out: \
|
||||
mutex_unlock(&wlp->mutex); \
|
||||
return result; \
|
||||
} \
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_##type##_show);
|
||||
|
||||
wlp_dev_info_show(name)
|
||||
wlp_dev_info_show(model_name)
|
||||
wlp_dev_info_show(model_nr)
|
||||
wlp_dev_info_show(manufacturer)
|
||||
wlp_dev_info_show(serial)
|
||||
|
||||
/**
|
||||
* Store contents of members of device information structure
|
||||
*/
|
||||
#define wlp_dev_info_store(type, len) \
|
||||
ssize_t wlp_dev_##type##_store(struct wlp *wlp, const char *buf, size_t size)\
|
||||
{ \
|
||||
ssize_t result; \
|
||||
char format[10]; \
|
||||
mutex_lock(&wlp->mutex); \
|
||||
if (wlp->dev_info == NULL) { \
|
||||
result = __wlp_alloc_device_info(wlp); \
|
||||
if (result < 0) \
|
||||
goto out; \
|
||||
} \
|
||||
memset(wlp->dev_info->type, 0, sizeof(wlp->dev_info->type)); \
|
||||
sprintf(format, "%%%uc", len); \
|
||||
result = sscanf(buf, format, wlp->dev_info->type); \
|
||||
out: \
|
||||
mutex_unlock(&wlp->mutex); \
|
||||
return result < 0 ? result : size; \
|
||||
} \
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_##type##_store);
|
||||
|
||||
wlp_dev_info_store(name, 32)
|
||||
wlp_dev_info_store(manufacturer, 64)
|
||||
wlp_dev_info_store(model_name, 32)
|
||||
wlp_dev_info_store(model_nr, 32)
|
||||
wlp_dev_info_store(serial, 32)
|
||||
|
||||
static
|
||||
const char *__wlp_dev_category[] = {
|
||||
[WLP_DEV_CAT_COMPUTER] = "Computer",
|
||||
[WLP_DEV_CAT_INPUT] = "Input device",
|
||||
[WLP_DEV_CAT_PRINT_SCAN_FAX_COPIER] = "Printer, scanner, FAX, or "
|
||||
"Copier",
|
||||
[WLP_DEV_CAT_CAMERA] = "Camera",
|
||||
[WLP_DEV_CAT_STORAGE] = "Storage Network",
|
||||
[WLP_DEV_CAT_INFRASTRUCTURE] = "Infrastructure",
|
||||
[WLP_DEV_CAT_DISPLAY] = "Display",
|
||||
[WLP_DEV_CAT_MULTIM] = "Multimedia device",
|
||||
[WLP_DEV_CAT_GAMING] = "Gaming device",
|
||||
[WLP_DEV_CAT_TELEPHONE] = "Telephone",
|
||||
[WLP_DEV_CAT_OTHER] = "Other",
|
||||
};
|
||||
|
||||
static
|
||||
const char *wlp_dev_category_str(unsigned cat)
|
||||
{
|
||||
if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE)
|
||||
|| cat == WLP_DEV_CAT_OTHER)
|
||||
return __wlp_dev_category[cat];
|
||||
return "unknown category";
|
||||
}
|
||||
|
||||
ssize_t wlp_dev_prim_category_show(struct wlp *wlp, char *buf)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info == NULL) {
|
||||
result = __wlp_setup_device_info(wlp);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
result = scnprintf(buf, PAGE_SIZE, "%s\n",
|
||||
wlp_dev_category_str(wlp->dev_info->prim_dev_type.category));
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_prim_category_show);
|
||||
|
||||
ssize_t wlp_dev_prim_category_store(struct wlp *wlp, const char *buf,
|
||||
size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
u16 cat;
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info == NULL) {
|
||||
result = __wlp_alloc_device_info(wlp);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
result = sscanf(buf, "%hu", &cat);
|
||||
if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE)
|
||||
|| cat == WLP_DEV_CAT_OTHER)
|
||||
wlp->dev_info->prim_dev_type.category = cat;
|
||||
else
|
||||
result = -EINVAL;
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_prim_category_store);
|
||||
|
||||
ssize_t wlp_dev_prim_OUI_show(struct wlp *wlp, char *buf)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info == NULL) {
|
||||
result = __wlp_setup_device_info(wlp);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
result = scnprintf(buf, PAGE_SIZE, "%02x:%02x:%02x\n",
|
||||
wlp->dev_info->prim_dev_type.OUI[0],
|
||||
wlp->dev_info->prim_dev_type.OUI[1],
|
||||
wlp->dev_info->prim_dev_type.OUI[2]);
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_show);
|
||||
|
||||
ssize_t wlp_dev_prim_OUI_store(struct wlp *wlp, const char *buf, size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
u8 OUI[3];
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info == NULL) {
|
||||
result = __wlp_alloc_device_info(wlp);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
result = sscanf(buf, "%hhx:%hhx:%hhx",
|
||||
&OUI[0], &OUI[1], &OUI[2]);
|
||||
if (result != 3) {
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
} else
|
||||
memcpy(wlp->dev_info->prim_dev_type.OUI, OUI, sizeof(OUI));
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_store);
|
||||
|
||||
|
||||
ssize_t wlp_dev_prim_OUI_sub_show(struct wlp *wlp, char *buf)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info == NULL) {
|
||||
result = __wlp_setup_device_info(wlp);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
result = scnprintf(buf, PAGE_SIZE, "%u\n",
|
||||
wlp->dev_info->prim_dev_type.OUIsubdiv);
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_show);
|
||||
|
||||
ssize_t wlp_dev_prim_OUI_sub_store(struct wlp *wlp, const char *buf,
|
||||
size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
unsigned sub;
|
||||
u8 max_sub = ~0;
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info == NULL) {
|
||||
result = __wlp_alloc_device_info(wlp);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
result = sscanf(buf, "%u", &sub);
|
||||
if (sub <= max_sub)
|
||||
wlp->dev_info->prim_dev_type.OUIsubdiv = sub;
|
||||
else
|
||||
result = -EINVAL;
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_store);
|
||||
|
||||
ssize_t wlp_dev_prim_subcat_show(struct wlp *wlp, char *buf)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info == NULL) {
|
||||
result = __wlp_setup_device_info(wlp);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
result = scnprintf(buf, PAGE_SIZE, "%u\n",
|
||||
wlp->dev_info->prim_dev_type.subID);
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_show);
|
||||
|
||||
ssize_t wlp_dev_prim_subcat_store(struct wlp *wlp, const char *buf,
|
||||
size_t size)
|
||||
{
|
||||
ssize_t result;
|
||||
unsigned sub;
|
||||
__le16 max_sub = ~0;
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info == NULL) {
|
||||
result = __wlp_alloc_device_info(wlp);
|
||||
if (result < 0)
|
||||
goto out;
|
||||
}
|
||||
result = sscanf(buf, "%u", &sub);
|
||||
if (sub <= max_sub)
|
||||
wlp->dev_info->prim_dev_type.subID = sub;
|
||||
else
|
||||
result = -EINVAL;
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result < 0 ? result : size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_store);
|
||||
|
||||
/**
|
||||
* Subsystem implementation for interaction with individual WSS via sysfs
|
||||
*
|
||||
* Followed instructions for subsystem in Documentation/filesystems/sysfs.txt
|
||||
*/
|
||||
|
||||
#define kobj_to_wlp_wss(obj) container_of(obj, struct wlp_wss, kobj)
|
||||
#define attr_to_wlp_wss_attr(_attr) \
|
||||
container_of(_attr, struct wlp_wss_attribute, attr)
|
||||
|
||||
/**
|
||||
* Sysfs subsystem: forward read calls
|
||||
*
|
||||
* Sysfs operation for forwarding read call to the show method of the
|
||||
* attribute owner
|
||||
*/
|
||||
static
|
||||
ssize_t wlp_wss_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct wlp_wss_attribute *wss_attr = attr_to_wlp_wss_attr(attr);
|
||||
struct wlp_wss *wss = kobj_to_wlp_wss(kobj);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (wss_attr->show)
|
||||
ret = wss_attr->show(wss, buf);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* Sysfs subsystem: forward write calls
|
||||
*
|
||||
* Sysfs operation for forwarding write call to the store method of the
|
||||
* attribute owner
|
||||
*/
|
||||
static
|
||||
ssize_t wlp_wss_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct wlp_wss_attribute *wss_attr = attr_to_wlp_wss_attr(attr);
|
||||
struct wlp_wss *wss = kobj_to_wlp_wss(kobj);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (wss_attr->store)
|
||||
ret = wss_attr->store(wss, buf, count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static
|
||||
struct sysfs_ops wss_sysfs_ops = {
|
||||
.show = wlp_wss_attr_show,
|
||||
.store = wlp_wss_attr_store,
|
||||
};
|
||||
|
||||
struct kobj_type wss_ktype = {
|
||||
.release = wlp_wss_release,
|
||||
.sysfs_ops = &wss_sysfs_ops,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sysfs files for individual WSS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Print static properties of this WSS
|
||||
*
|
||||
* The name of a WSS may not be null teminated. It's max size is 64 bytes
|
||||
* so we copy it to a larger array just to make sure we print sane data.
|
||||
*/
|
||||
static ssize_t wlp_wss_properties_show(struct wlp_wss *wss, char *buf)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
if (mutex_lock_interruptible(&wss->mutex))
|
||||
goto out;
|
||||
result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE);
|
||||
mutex_unlock(&wss->mutex);
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
WSS_ATTR(properties, S_IRUGO, wlp_wss_properties_show, NULL);
|
||||
|
||||
/**
|
||||
* Print all connected members of this WSS
|
||||
* The EDA cache contains all members of WSS neighborhood.
|
||||
*/
|
||||
static ssize_t wlp_wss_members_show(struct wlp_wss *wss, char *buf)
|
||||
{
|
||||
struct wlp *wlp = container_of(wss, struct wlp, wss);
|
||||
return wlp_eda_show(wlp, buf);
|
||||
}
|
||||
WSS_ATTR(members, S_IRUGO, wlp_wss_members_show, NULL);
|
||||
|
||||
static
|
||||
const char *__wlp_strstate[] = {
|
||||
"none",
|
||||
"partially enrolled",
|
||||
"enrolled",
|
||||
"active",
|
||||
"connected",
|
||||
};
|
||||
|
||||
static const char *wlp_wss_strstate(unsigned state)
|
||||
{
|
||||
if (state >= ARRAY_SIZE(__wlp_strstate))
|
||||
return "unknown state";
|
||||
return __wlp_strstate[state];
|
||||
}
|
||||
|
||||
/*
|
||||
* Print current state of this WSS
|
||||
*/
|
||||
static ssize_t wlp_wss_state_show(struct wlp_wss *wss, char *buf)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
if (mutex_lock_interruptible(&wss->mutex))
|
||||
goto out;
|
||||
result = scnprintf(buf, PAGE_SIZE, "%s\n",
|
||||
wlp_wss_strstate(wss->state));
|
||||
mutex_unlock(&wss->mutex);
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
WSS_ATTR(state, S_IRUGO, wlp_wss_state_show, NULL);
|
||||
|
||||
|
||||
static
|
||||
struct attribute *wss_attrs[] = {
|
||||
&wss_attr_properties.attr,
|
||||
&wss_attr_members.attr,
|
||||
&wss_attr_state.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct attribute_group wss_attr_group = {
|
||||
.name = NULL, /* we want them in the same directory */
|
||||
.attrs = wss_attrs,
|
||||
};
|
|
@ -0,0 +1,374 @@
|
|||
/*
|
||||
* WiMedia Logical Link Control Protocol (WLP)
|
||||
* Message exchange infrastructure
|
||||
*
|
||||
* Copyright (C) 2007 Intel Corporation
|
||||
* Reinette Chatre <reinette.chatre@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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/wlp.h>
|
||||
#define D_LOCAL 5
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "wlp-internal.h"
|
||||
|
||||
|
||||
/**
|
||||
* Direct incoming association msg to correct parsing routine
|
||||
*
|
||||
* We only expect D1, E1, C1, C3 messages as new. All other incoming
|
||||
* association messages should form part of an established session that is
|
||||
* handled elsewhere.
|
||||
* The handling of these messages often require calling sleeping functions
|
||||
* - this cannot be done in interrupt context. We use the kernel's
|
||||
* workqueue to handle these messages.
|
||||
*/
|
||||
static
|
||||
void wlp_direct_assoc_frame(struct wlp *wlp, struct sk_buff *skb,
|
||||
struct uwb_dev_addr *src)
|
||||
{
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
struct wlp_frame_assoc *assoc = (void *) skb->data;
|
||||
struct wlp_assoc_frame_ctx *frame_ctx;
|
||||
d_fnstart(5, dev, "wlp %p, skb %p\n", wlp, skb);
|
||||
frame_ctx = kmalloc(sizeof(*frame_ctx), GFP_ATOMIC);
|
||||
if (frame_ctx == NULL) {
|
||||
dev_err(dev, "WLP: Unable to allocate memory for association "
|
||||
"frame handling.\n");
|
||||
kfree_skb(skb);
|
||||
goto out;
|
||||
}
|
||||
frame_ctx->wlp = wlp;
|
||||
frame_ctx->skb = skb;
|
||||
frame_ctx->src = *src;
|
||||
switch (assoc->type) {
|
||||
case WLP_ASSOC_D1:
|
||||
d_printf(5, dev, "Received a D1 frame.\n");
|
||||
INIT_WORK(&frame_ctx->ws, wlp_handle_d1_frame);
|
||||
schedule_work(&frame_ctx->ws);
|
||||
break;
|
||||
case WLP_ASSOC_E1:
|
||||
d_printf(5, dev, "Received a E1 frame. FIXME?\n");
|
||||
kfree_skb(skb); /* Temporary until we handle it */
|
||||
kfree(frame_ctx); /* Temporary until we handle it */
|
||||
break;
|
||||
case WLP_ASSOC_C1:
|
||||
d_printf(5, dev, "Received a C1 frame.\n");
|
||||
INIT_WORK(&frame_ctx->ws, wlp_handle_c1_frame);
|
||||
schedule_work(&frame_ctx->ws);
|
||||
break;
|
||||
case WLP_ASSOC_C3:
|
||||
d_printf(5, dev, "Received a C3 frame.\n");
|
||||
INIT_WORK(&frame_ctx->ws, wlp_handle_c3_frame);
|
||||
schedule_work(&frame_ctx->ws);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "Received unexpected association frame. "
|
||||
"Type = %d \n", assoc->type);
|
||||
kfree_skb(skb);
|
||||
kfree(frame_ctx);
|
||||
break;
|
||||
}
|
||||
out:
|
||||
d_fnend(5, dev, "wlp %p\n", wlp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process incoming association frame
|
||||
*
|
||||
* Although it could be possible to deal with some incoming association
|
||||
* messages without creating a new session we are keeping things simple. We
|
||||
* do not accept new association messages if there is a session in progress
|
||||
* and the messages do not belong to that session.
|
||||
*
|
||||
* If an association message arrives that causes the creation of a session
|
||||
* (WLP_ASSOC_E1) while we are in the process of creating a session then we
|
||||
* rely on the neighbor mutex to protect the data. That is, the new session
|
||||
* will not be started until the previous is completed.
|
||||
*/
|
||||
static
|
||||
void wlp_receive_assoc_frame(struct wlp *wlp, struct sk_buff *skb,
|
||||
struct uwb_dev_addr *src)
|
||||
{
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
struct wlp_frame_assoc *assoc = (void *) skb->data;
|
||||
struct wlp_session *session = wlp->session;
|
||||
u8 version;
|
||||
d_fnstart(5, dev, "wlp %p, skb %p\n", wlp, skb);
|
||||
|
||||
if (wlp_get_version(wlp, &assoc->version, &version,
|
||||
sizeof(assoc->version)) < 0)
|
||||
goto error;
|
||||
if (version != WLP_VERSION) {
|
||||
dev_err(dev, "Unsupported WLP version in association "
|
||||
"message.\n");
|
||||
goto error;
|
||||
}
|
||||
if (session != NULL) {
|
||||
/* Function that created this session is still holding the
|
||||
* &wlp->mutex to protect this session. */
|
||||
if (assoc->type == session->exp_message ||
|
||||
assoc->type == WLP_ASSOC_F0) {
|
||||
if (!memcmp(&session->neighbor_addr, src,
|
||||
sizeof(*src))) {
|
||||
session->data = skb;
|
||||
(session->cb)(wlp);
|
||||
} else {
|
||||
dev_err(dev, "Received expected message from "
|
||||
"unexpected source. Expected message "
|
||||
"%d or F0 from %02x:%02x, but received "
|
||||
"it from %02x:%02x. Dropping.\n",
|
||||
session->exp_message,
|
||||
session->neighbor_addr.data[1],
|
||||
session->neighbor_addr.data[0],
|
||||
src->data[1], src->data[0]);
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
dev_err(dev, "Association already in progress. "
|
||||
"Dropping.\n");
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
wlp_direct_assoc_frame(wlp, skb, src);
|
||||
}
|
||||
d_fnend(5, dev, "wlp %p\n", wlp);
|
||||
return;
|
||||
error:
|
||||
kfree_skb(skb);
|
||||
d_fnend(5, dev, "wlp %p\n", wlp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify incoming frame is from connected neighbor, prep to pass to WLP client
|
||||
*
|
||||
* Verification proceeds according to WLP 0.99 [7.3.1]. The source address
|
||||
* is used to determine which neighbor is sending the frame and the WSS tag
|
||||
* is used to know to which WSS the frame belongs (we only support one WSS
|
||||
* so this test is straight forward).
|
||||
* With the WSS found we need to ensure that we are connected before
|
||||
* allowing the exchange of data frames.
|
||||
*/
|
||||
static
|
||||
int wlp_verify_prep_rx_frame(struct wlp *wlp, struct sk_buff *skb,
|
||||
struct uwb_dev_addr *src)
|
||||
{
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
int result = -EINVAL;
|
||||
struct wlp_eda_node eda_entry;
|
||||
struct wlp_frame_std_abbrv_hdr *hdr = (void *) skb->data;
|
||||
|
||||
d_fnstart(6, dev, "wlp %p, skb %p \n", wlp, skb);
|
||||
/*verify*/
|
||||
result = wlp_copy_eda_node(&wlp->eda, src, &eda_entry);
|
||||
if (result < 0) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "WLP: Incoming frame is from unknown "
|
||||
"neighbor %02x:%02x.\n", src->data[1],
|
||||
src->data[0]);
|
||||
goto out;
|
||||
}
|
||||
if (hdr->tag != eda_entry.tag) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "WLP: Tag of incoming frame from "
|
||||
"%02x:%02x does not match expected tag. "
|
||||
"Received 0x%02x, expected 0x%02x. \n",
|
||||
src->data[1], src->data[0], hdr->tag,
|
||||
eda_entry.tag);
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (eda_entry.state != WLP_WSS_CONNECTED) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "WLP: Incoming frame from "
|
||||
"%02x:%02x does is not from connected WSS.\n",
|
||||
src->data[1], src->data[0]);
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
/*prep*/
|
||||
skb_pull(skb, sizeof(*hdr));
|
||||
out:
|
||||
d_fnend(6, dev, "wlp %p, skb %p, result = %d \n", wlp, skb, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a WLP frame from device
|
||||
*
|
||||
* @returns: 1 if calling function should free the skb
|
||||
* 0 if it successfully handled skb and freed it
|
||||
* 0 if error occured, will free skb in this case
|
||||
*/
|
||||
int wlp_receive_frame(struct device *dev, struct wlp *wlp, struct sk_buff *skb,
|
||||
struct uwb_dev_addr *src)
|
||||
{
|
||||
unsigned len = skb->len;
|
||||
void *ptr = skb->data;
|
||||
struct wlp_frame_hdr *hdr;
|
||||
int result = 0;
|
||||
|
||||
d_fnstart(6, dev, "skb (%p), len (%u)\n", skb, len);
|
||||
if (len < sizeof(*hdr)) {
|
||||
dev_err(dev, "Not enough data to parse WLP header.\n");
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
hdr = ptr;
|
||||
d_dump(6, dev, hdr, sizeof(*hdr));
|
||||
if (le16_to_cpu(hdr->mux_hdr) != WLP_PROTOCOL_ID) {
|
||||
dev_err(dev, "Not a WLP frame type.\n");
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
switch (hdr->type) {
|
||||
case WLP_FRAME_STANDARD:
|
||||
if (len < sizeof(struct wlp_frame_std_abbrv_hdr)) {
|
||||
dev_err(dev, "Not enough data to parse Standard "
|
||||
"WLP header.\n");
|
||||
goto out;
|
||||
}
|
||||
result = wlp_verify_prep_rx_frame(wlp, skb, src);
|
||||
if (result < 0) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "WLP: Verification of frame "
|
||||
"from neighbor %02x:%02x failed.\n",
|
||||
src->data[1], src->data[0]);
|
||||
goto out;
|
||||
}
|
||||
result = 1;
|
||||
break;
|
||||
case WLP_FRAME_ABBREVIATED:
|
||||
dev_err(dev, "Abbreviated frame received. FIXME?\n");
|
||||
kfree_skb(skb);
|
||||
break;
|
||||
case WLP_FRAME_CONTROL:
|
||||
dev_err(dev, "Control frame received. FIXME?\n");
|
||||
kfree_skb(skb);
|
||||
break;
|
||||
case WLP_FRAME_ASSOCIATION:
|
||||
if (len < sizeof(struct wlp_frame_assoc)) {
|
||||
dev_err(dev, "Not enough data to parse Association "
|
||||
"WLP header.\n");
|
||||
goto out;
|
||||
}
|
||||
d_printf(5, dev, "Association frame received.\n");
|
||||
wlp_receive_assoc_frame(wlp, skb, src);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "Invalid frame received.\n");
|
||||
result = -EINVAL;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
if (result < 0) {
|
||||
kfree_skb(skb);
|
||||
result = 0;
|
||||
}
|
||||
d_fnend(6, dev, "skb (%p)\n", skb);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_receive_frame);
|
||||
|
||||
|
||||
/**
|
||||
* Verify frame from network stack, prepare for further transmission
|
||||
*
|
||||
* @skb: the socket buffer that needs to be prepared for transmission (it
|
||||
* is in need of a WLP header). If this is a broadcast frame we take
|
||||
* over the entire transmission.
|
||||
* If it is a unicast the WSS connection should already be established
|
||||
* and transmission will be done by the calling function.
|
||||
* @dst: On return this will contain the device address to which the
|
||||
* frame is destined.
|
||||
* @returns: 0 on success no tx : WLP header sucessfully applied to skb buffer,
|
||||
* calling function can proceed with tx
|
||||
* 1 on success with tx : WLP will take over transmission of this
|
||||
* frame
|
||||
* <0 on error
|
||||
*
|
||||
* The network stack (WLP client) is attempting to transmit a frame. We can
|
||||
* only transmit data if a local WSS is at least active (connection will be
|
||||
* done here if this is a broadcast frame and neighbor also has the WSS
|
||||
* active).
|
||||
*
|
||||
* The frame can be either broadcast or unicast. Broadcast in a WSS is
|
||||
* supported via multicast, but we don't support multicast yet (until
|
||||
* devices start to support MAB IEs). If a broadcast frame needs to be
|
||||
* transmitted it is treated as a unicast frame to each neighbor. In this
|
||||
* case the WLP takes over transmission of the skb and returns 1
|
||||
* to the caller to indicate so. Also, in this case, if a neighbor has the
|
||||
* same WSS activated but is not connected then the WSS connection will be
|
||||
* done at this time. The neighbor's virtual address will be learned at
|
||||
* this time.
|
||||
*
|
||||
* The destination address in a unicast frame is the virtual address of the
|
||||
* neighbor. This address only becomes known when a WSS connection is
|
||||
* established. We thus rely on a broadcast frame to trigger the setup of
|
||||
* WSS connections to all neighbors before we are able to send unicast
|
||||
* frames to them. This seems reasonable as IP would usually use ARP first
|
||||
* before any unicast frames are sent.
|
||||
*
|
||||
* If we are already connected to the neighbor (neighbor's virtual address
|
||||
* is known) we just prepare the WLP header and the caller will continue to
|
||||
* send the frame.
|
||||
*
|
||||
* A failure in this function usually indicates something that cannot be
|
||||
* fixed automatically. So, if this function fails (@return < 0) the calling
|
||||
* function should not retry to send the frame as it will very likely keep
|
||||
* failing.
|
||||
*
|
||||
*/
|
||||
int wlp_prepare_tx_frame(struct device *dev, struct wlp *wlp,
|
||||
struct sk_buff *skb, struct uwb_dev_addr *dst)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
struct ethhdr *eth_hdr = (void *) skb->data;
|
||||
|
||||
d_fnstart(6, dev, "wlp (%p), skb (%p) \n", wlp, skb);
|
||||
if (is_broadcast_ether_addr(eth_hdr->h_dest)) {
|
||||
d_printf(6, dev, "WLP: handling broadcast frame. \n");
|
||||
result = wlp_eda_for_each(&wlp->eda, wlp_wss_send_copy, skb);
|
||||
if (result < 0) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "Unable to handle broadcast "
|
||||
"frame from WLP client.\n");
|
||||
goto out;
|
||||
}
|
||||
dev_kfree_skb_irq(skb);
|
||||
result = 1;
|
||||
/* Frame will be transmitted by WLP. */
|
||||
} else {
|
||||
d_printf(6, dev, "WLP: handling unicast frame. \n");
|
||||
result = wlp_eda_for_virtual(&wlp->eda, eth_hdr->h_dest, dst,
|
||||
wlp_wss_prep_hdr, skb);
|
||||
if (unlikely(result < 0)) {
|
||||
if (printk_ratelimit())
|
||||
dev_err(dev, "Unable to prepare "
|
||||
"skb for transmission. \n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
out:
|
||||
d_fnend(6, dev, "wlp (%p), skb (%p). result = %d \n", wlp, skb, result);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_prepare_tx_frame);
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* WiMedia Logical Link Control Protocol (WLP)
|
||||
* Internal API
|
||||
*
|
||||
* Copyright (C) 2007 Intel Corporation
|
||||
* Reinette Chatre <reinette.chatre@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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __WLP_INTERNAL_H__
|
||||
#define __WLP_INTERNAL_H__
|
||||
|
||||
/**
|
||||
* State of WSS connection
|
||||
*
|
||||
* A device needs to connect to a neighbor in an activated WSS before data
|
||||
* can be transmitted. The spec also distinguishes between a new connection
|
||||
* attempt and a connection attempt after previous connection attempts. The
|
||||
* state WLP_WSS_CONNECT_FAILED is used for this scenario. See WLP 0.99
|
||||
* [7.2.6]
|
||||
*/
|
||||
enum wlp_wss_connect {
|
||||
WLP_WSS_UNCONNECTED = 0,
|
||||
WLP_WSS_CONNECTED,
|
||||
WLP_WSS_CONNECT_FAILED,
|
||||
};
|
||||
|
||||
extern struct kobj_type wss_ktype;
|
||||
extern struct attribute_group wss_attr_group;
|
||||
|
||||
extern int uwb_rc_ie_add(struct uwb_rc *, const struct uwb_ie_hdr *, size_t);
|
||||
extern int uwb_rc_ie_rm(struct uwb_rc *, enum uwb_ie);
|
||||
|
||||
|
||||
/* This should be changed to a dynamic array where entries are sorted
|
||||
* by eth_addr and search is done in a binary form
|
||||
*
|
||||
* Although thinking twice about it: this technologie's maximum reach
|
||||
* is 10 meters...unless you want to pack too much stuff in around
|
||||
* your radio controller/WLP device, the list will probably not be
|
||||
* too big.
|
||||
*
|
||||
* In any case, there is probably some data structure in the kernel
|
||||
* than we could reused for that already.
|
||||
*
|
||||
* The below structure is really just good while we support one WSS per
|
||||
* host.
|
||||
*/
|
||||
struct wlp_eda_node {
|
||||
struct list_head list_node;
|
||||
unsigned char eth_addr[ETH_ALEN];
|
||||
struct uwb_dev_addr dev_addr;
|
||||
struct wlp_wss *wss;
|
||||
unsigned char virt_addr[ETH_ALEN];
|
||||
u8 tag;
|
||||
enum wlp_wss_connect state;
|
||||
};
|
||||
|
||||
typedef int (*wlp_eda_for_each_f)(struct wlp *, struct wlp_eda_node *, void *);
|
||||
|
||||
extern void wlp_eda_init(struct wlp_eda *);
|
||||
extern void wlp_eda_release(struct wlp_eda *);
|
||||
extern int wlp_eda_create_node(struct wlp_eda *,
|
||||
const unsigned char eth_addr[ETH_ALEN],
|
||||
const struct uwb_dev_addr *);
|
||||
extern void wlp_eda_rm_node(struct wlp_eda *, const struct uwb_dev_addr *);
|
||||
extern int wlp_eda_update_node(struct wlp_eda *,
|
||||
const struct uwb_dev_addr *,
|
||||
struct wlp_wss *,
|
||||
const unsigned char virt_addr[ETH_ALEN],
|
||||
const u8, const enum wlp_wss_connect);
|
||||
extern int wlp_eda_update_node_state(struct wlp_eda *,
|
||||
const struct uwb_dev_addr *,
|
||||
const enum wlp_wss_connect);
|
||||
|
||||
extern int wlp_copy_eda_node(struct wlp_eda *, struct uwb_dev_addr *,
|
||||
struct wlp_eda_node *);
|
||||
extern int wlp_eda_for_each(struct wlp_eda *, wlp_eda_for_each_f , void *);
|
||||
extern int wlp_eda_for_virtual(struct wlp_eda *,
|
||||
const unsigned char eth_addr[ETH_ALEN],
|
||||
struct uwb_dev_addr *,
|
||||
wlp_eda_for_each_f , void *);
|
||||
|
||||
|
||||
extern void wlp_remove_neighbor_tmp_info(struct wlp_neighbor_e *);
|
||||
|
||||
extern size_t wlp_wss_key_print(char *, size_t, u8 *);
|
||||
|
||||
/* Function called when no more references to WSS exists */
|
||||
extern void wlp_wss_release(struct kobject *);
|
||||
|
||||
extern void wlp_wss_reset(struct wlp_wss *);
|
||||
extern int wlp_wss_create_activate(struct wlp_wss *, struct wlp_uuid *,
|
||||
char *, unsigned, unsigned);
|
||||
extern int wlp_wss_enroll_activate(struct wlp_wss *, struct wlp_uuid *,
|
||||
struct uwb_dev_addr *);
|
||||
extern ssize_t wlp_discover(struct wlp *);
|
||||
|
||||
extern int wlp_enroll_neighbor(struct wlp *, struct wlp_neighbor_e *,
|
||||
struct wlp_wss *, struct wlp_uuid *);
|
||||
extern int wlp_wss_is_active(struct wlp *, struct wlp_wss *,
|
||||
struct uwb_dev_addr *);
|
||||
|
||||
struct wlp_assoc_conn_ctx {
|
||||
struct work_struct ws;
|
||||
struct wlp *wlp;
|
||||
struct sk_buff *skb;
|
||||
struct wlp_eda_node eda_entry;
|
||||
};
|
||||
|
||||
|
||||
extern int wlp_wss_connect_prep(struct wlp *, struct wlp_eda_node *, void *);
|
||||
extern int wlp_wss_send_copy(struct wlp *, struct wlp_eda_node *, void *);
|
||||
|
||||
|
||||
/* Message handling */
|
||||
struct wlp_assoc_frame_ctx {
|
||||
struct work_struct ws;
|
||||
struct wlp *wlp;
|
||||
struct sk_buff *skb;
|
||||
struct uwb_dev_addr src;
|
||||
};
|
||||
|
||||
extern int wlp_wss_prep_hdr(struct wlp *, struct wlp_eda_node *, void *);
|
||||
extern void wlp_handle_d1_frame(struct work_struct *);
|
||||
extern int wlp_parse_d2_frame_to_cache(struct wlp *, struct sk_buff *,
|
||||
struct wlp_neighbor_e *);
|
||||
extern int wlp_parse_d2_frame_to_enroll(struct wlp_wss *, struct sk_buff *,
|
||||
struct wlp_neighbor_e *,
|
||||
struct wlp_uuid *);
|
||||
extern void wlp_handle_c1_frame(struct work_struct *);
|
||||
extern void wlp_handle_c3_frame(struct work_struct *);
|
||||
extern int wlp_parse_c3c4_frame(struct wlp *, struct sk_buff *,
|
||||
struct wlp_uuid *, u8 *,
|
||||
struct uwb_mac_addr *);
|
||||
extern int wlp_parse_f0(struct wlp *, struct sk_buff *);
|
||||
extern int wlp_send_assoc_frame(struct wlp *, struct wlp_wss *,
|
||||
struct uwb_dev_addr *, enum wlp_assoc_type);
|
||||
extern ssize_t wlp_get_version(struct wlp *, struct wlp_attr_version *,
|
||||
u8 *, ssize_t);
|
||||
extern ssize_t wlp_get_wssid(struct wlp *, struct wlp_attr_wssid *,
|
||||
struct wlp_uuid *, ssize_t);
|
||||
extern int __wlp_alloc_device_info(struct wlp *);
|
||||
extern int __wlp_setup_device_info(struct wlp *);
|
||||
|
||||
extern struct wlp_wss_attribute wss_attribute_properties;
|
||||
extern struct wlp_wss_attribute wss_attribute_members;
|
||||
extern struct wlp_wss_attribute wss_attribute_state;
|
||||
|
||||
static inline
|
||||
size_t wlp_wss_uuid_print(char *buf, size_t bufsize, struct wlp_uuid *uuid)
|
||||
{
|
||||
size_t result;
|
||||
|
||||
result = scnprintf(buf, bufsize,
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x:"
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x:"
|
||||
"%02x:%02x:%02x:%02x",
|
||||
uuid->data[0], uuid->data[1],
|
||||
uuid->data[2], uuid->data[3],
|
||||
uuid->data[4], uuid->data[5],
|
||||
uuid->data[6], uuid->data[7],
|
||||
uuid->data[8], uuid->data[9],
|
||||
uuid->data[10], uuid->data[11],
|
||||
uuid->data[12], uuid->data[13],
|
||||
uuid->data[14], uuid->data[15]);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME: How should a nonce be displayed?
|
||||
*/
|
||||
static inline
|
||||
size_t wlp_wss_nonce_print(char *buf, size_t bufsize, struct wlp_nonce *nonce)
|
||||
{
|
||||
size_t result;
|
||||
|
||||
result = scnprintf(buf, bufsize,
|
||||
"%02x %02x %02x %02x %02x %02x "
|
||||
"%02x %02x %02x %02x %02x %02x "
|
||||
"%02x %02x %02x %02x",
|
||||
nonce->data[0], nonce->data[1],
|
||||
nonce->data[2], nonce->data[3],
|
||||
nonce->data[4], nonce->data[5],
|
||||
nonce->data[6], nonce->data[7],
|
||||
nonce->data[8], nonce->data[9],
|
||||
nonce->data[10], nonce->data[11],
|
||||
nonce->data[12], nonce->data[13],
|
||||
nonce->data[14], nonce->data[15]);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static inline
|
||||
void wlp_session_cb(struct wlp *wlp)
|
||||
{
|
||||
struct completion *completion = wlp->session->cb_priv;
|
||||
complete(completion);
|
||||
}
|
||||
|
||||
static inline
|
||||
int wlp_uuid_is_set(struct wlp_uuid *uuid)
|
||||
{
|
||||
struct wlp_uuid zero_uuid = { .data = { 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00} };
|
||||
|
||||
if (!memcmp(uuid, &zero_uuid, sizeof(*uuid)))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* __WLP_INTERNAL_H__ */
|
|
@ -0,0 +1,585 @@
|
|||
/*
|
||||
* WiMedia Logical Link Control Protocol (WLP)
|
||||
*
|
||||
* Copyright (C) 2005-2006 Intel Corporation
|
||||
* Reinette Chatre <reinette.chatre@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
|
||||
*/
|
||||
|
||||
#include <linux/wlp.h>
|
||||
#define D_LOCAL 6
|
||||
#include <linux/uwb/debug.h>
|
||||
#include "wlp-internal.h"
|
||||
|
||||
|
||||
static
|
||||
void wlp_neighbor_init(struct wlp_neighbor_e *neighbor)
|
||||
{
|
||||
INIT_LIST_HEAD(&neighbor->wssid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create area for device information storage
|
||||
*
|
||||
* wlp->mutex must be held
|
||||
*/
|
||||
int __wlp_alloc_device_info(struct wlp *wlp)
|
||||
{
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
BUG_ON(wlp->dev_info != NULL);
|
||||
wlp->dev_info = kzalloc(sizeof(struct wlp_device_info), GFP_KERNEL);
|
||||
if (wlp->dev_info == NULL) {
|
||||
dev_err(dev, "WLP: Unable to allocate memory for "
|
||||
"device information.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fill in device information using function provided by driver
|
||||
*
|
||||
* wlp->mutex must be held
|
||||
*/
|
||||
static
|
||||
void __wlp_fill_device_info(struct wlp *wlp)
|
||||
{
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
|
||||
BUG_ON(wlp->fill_device_info == NULL);
|
||||
d_printf(6, dev, "Retrieving device information "
|
||||
"from device driver.\n");
|
||||
wlp->fill_device_info(wlp, wlp->dev_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup device information
|
||||
*
|
||||
* Allocate area for device information and populate it.
|
||||
*
|
||||
* wlp->mutex must be held
|
||||
*/
|
||||
int __wlp_setup_device_info(struct wlp *wlp)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
|
||||
result = __wlp_alloc_device_info(wlp);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "WLP: Unable to allocate area for "
|
||||
"device information.\n");
|
||||
return result;
|
||||
}
|
||||
__wlp_fill_device_info(wlp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove information about neighbor stored temporarily
|
||||
*
|
||||
* Information learned during discovey should only be stored when the
|
||||
* device enrolls in the neighbor's WSS. We do need to store this
|
||||
* information temporarily in order to present it to the user.
|
||||
*
|
||||
* We are only interested in keeping neighbor WSS information if that
|
||||
* neighbor is accepting enrollment.
|
||||
*
|
||||
* should be called with wlp->nbmutex held
|
||||
*/
|
||||
void wlp_remove_neighbor_tmp_info(struct wlp_neighbor_e *neighbor)
|
||||
{
|
||||
struct wlp_wssid_e *wssid_e, *next;
|
||||
u8 keep;
|
||||
if (!list_empty(&neighbor->wssid)) {
|
||||
list_for_each_entry_safe(wssid_e, next, &neighbor->wssid,
|
||||
node) {
|
||||
if (wssid_e->info != NULL) {
|
||||
keep = wssid_e->info->accept_enroll;
|
||||
kfree(wssid_e->info);
|
||||
wssid_e->info = NULL;
|
||||
if (!keep) {
|
||||
list_del(&wssid_e->node);
|
||||
kfree(wssid_e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (neighbor->info != NULL) {
|
||||
kfree(neighbor->info);
|
||||
neighbor->info = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate WLP neighborhood cache with neighbor information
|
||||
*
|
||||
* A new neighbor is found. If it is discoverable then we add it to the
|
||||
* neighborhood cache.
|
||||
*
|
||||
*/
|
||||
static
|
||||
int wlp_add_neighbor(struct wlp *wlp, struct uwb_dev *dev)
|
||||
{
|
||||
int result = 0;
|
||||
int discoverable;
|
||||
struct wlp_neighbor_e *neighbor;
|
||||
|
||||
d_fnstart(6, &dev->dev, "uwb %p \n", dev);
|
||||
d_printf(6, &dev->dev, "Found neighbor device %02x:%02x \n",
|
||||
dev->dev_addr.data[1], dev->dev_addr.data[0]);
|
||||
/**
|
||||
* FIXME:
|
||||
* Use contents of WLP IE found in beacon cache to determine if
|
||||
* neighbor is discoverable.
|
||||
* The device does not support WLP IE yet so this still needs to be
|
||||
* done. Until then we assume all devices are discoverable.
|
||||
*/
|
||||
discoverable = 1; /* will be changed when FIXME disappears */
|
||||
if (discoverable) {
|
||||
/* Add neighbor to cache for discovery */
|
||||
neighbor = kzalloc(sizeof(*neighbor), GFP_KERNEL);
|
||||
if (neighbor == NULL) {
|
||||
dev_err(&dev->dev, "Unable to create memory for "
|
||||
"new neighbor. \n");
|
||||
result = -ENOMEM;
|
||||
goto error_no_mem;
|
||||
}
|
||||
wlp_neighbor_init(neighbor);
|
||||
uwb_dev_get(dev);
|
||||
neighbor->uwb_dev = dev;
|
||||
list_add(&neighbor->node, &wlp->neighbors);
|
||||
}
|
||||
error_no_mem:
|
||||
d_fnend(6, &dev->dev, "uwb %p, result = %d \n", dev, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove one neighbor from cache
|
||||
*/
|
||||
static
|
||||
void __wlp_neighbor_release(struct wlp_neighbor_e *neighbor)
|
||||
{
|
||||
struct wlp_wssid_e *wssid_e, *next_wssid_e;
|
||||
|
||||
list_for_each_entry_safe(wssid_e, next_wssid_e,
|
||||
&neighbor->wssid, node) {
|
||||
list_del(&wssid_e->node);
|
||||
kfree(wssid_e);
|
||||
}
|
||||
uwb_dev_put(neighbor->uwb_dev);
|
||||
list_del(&neighbor->node);
|
||||
kfree(neighbor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear entire neighborhood cache.
|
||||
*/
|
||||
static
|
||||
void __wlp_neighbors_release(struct wlp *wlp)
|
||||
{
|
||||
struct wlp_neighbor_e *neighbor, *next;
|
||||
if (list_empty(&wlp->neighbors))
|
||||
return;
|
||||
list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) {
|
||||
__wlp_neighbor_release(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void wlp_neighbors_release(struct wlp *wlp)
|
||||
{
|
||||
mutex_lock(&wlp->nbmutex);
|
||||
__wlp_neighbors_release(wlp);
|
||||
mutex_unlock(&wlp->nbmutex);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send D1 message to neighbor, receive D2 message
|
||||
*
|
||||
* @neighbor: neighbor to which D1 message will be sent
|
||||
* @wss: if not NULL, it is an enrollment request for this WSS
|
||||
* @wssid: if wss not NULL, this is the wssid of the WSS in which we
|
||||
* want to enroll
|
||||
*
|
||||
* A D1/D2 exchange is done for one of two reasons: discovery or
|
||||
* enrollment. If done for discovery the D1 message is sent to the neighbor
|
||||
* and the contents of the D2 response is stored in a temporary cache.
|
||||
* If done for enrollment the @wss and @wssid are provided also. In this
|
||||
* case the D1 message is sent to the neighbor, the D2 response is parsed
|
||||
* for enrollment of the WSS with wssid.
|
||||
*
|
||||
* &wss->mutex is held
|
||||
*/
|
||||
static
|
||||
int wlp_d1d2_exchange(struct wlp *wlp, struct wlp_neighbor_e *neighbor,
|
||||
struct wlp_wss *wss, struct wlp_uuid *wssid)
|
||||
{
|
||||
int result;
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
DECLARE_COMPLETION_ONSTACK(completion);
|
||||
struct wlp_session session;
|
||||
struct sk_buff *skb;
|
||||
struct wlp_frame_assoc *resp;
|
||||
struct uwb_dev_addr *dev_addr = &neighbor->uwb_dev->dev_addr;
|
||||
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (!wlp_uuid_is_set(&wlp->uuid)) {
|
||||
dev_err(dev, "WLP: UUID is not set. Set via sysfs to "
|
||||
"proceed.\n");
|
||||
result = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
/* Send D1 association frame */
|
||||
result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_D1);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Unable to send D1 frame to neighbor "
|
||||
"%02x:%02x (%d)\n", dev_addr->data[1],
|
||||
dev_addr->data[0], result);
|
||||
d_printf(6, dev, "Add placeholders into buffer next to "
|
||||
"neighbor information we have (dev address).\n");
|
||||
goto out;
|
||||
}
|
||||
/* Create session, wait for response */
|
||||
session.exp_message = WLP_ASSOC_D2;
|
||||
session.cb = wlp_session_cb;
|
||||
session.cb_priv = &completion;
|
||||
session.neighbor_addr = *dev_addr;
|
||||
BUG_ON(wlp->session != NULL);
|
||||
wlp->session = &session;
|
||||
/* Wait for D2/F0 frame */
|
||||
result = wait_for_completion_interruptible_timeout(&completion,
|
||||
WLP_PER_MSG_TIMEOUT * HZ);
|
||||
if (result == 0) {
|
||||
result = -ETIMEDOUT;
|
||||
dev_err(dev, "Timeout while sending D1 to neighbor "
|
||||
"%02x:%02x.\n", dev_addr->data[1],
|
||||
dev_addr->data[0]);
|
||||
goto error_session;
|
||||
}
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Unable to discover/enroll neighbor %02x:%02x.\n",
|
||||
dev_addr->data[1], dev_addr->data[0]);
|
||||
goto error_session;
|
||||
}
|
||||
/* Parse message in session->data: it will be either D2 or F0 */
|
||||
skb = session.data;
|
||||
resp = (void *) skb->data;
|
||||
d_printf(6, dev, "Received response to D1 frame. \n");
|
||||
d_dump(6, dev, skb->data, skb->len > 72 ? 72 : skb->len);
|
||||
|
||||
if (resp->type == WLP_ASSOC_F0) {
|
||||
result = wlp_parse_f0(wlp, skb);
|
||||
if (result < 0)
|
||||
dev_err(dev, "WLP: Unable to parse F0 from neighbor "
|
||||
"%02x:%02x.\n", dev_addr->data[1],
|
||||
dev_addr->data[0]);
|
||||
result = -EINVAL;
|
||||
goto error_resp_parse;
|
||||
}
|
||||
if (wss == NULL) {
|
||||
/* Discovery */
|
||||
result = wlp_parse_d2_frame_to_cache(wlp, skb, neighbor);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "WLP: Unable to parse D2 message from "
|
||||
"neighbor %02x:%02x for discovery.\n",
|
||||
dev_addr->data[1], dev_addr->data[0]);
|
||||
goto error_resp_parse;
|
||||
}
|
||||
} else {
|
||||
/* Enrollment */
|
||||
result = wlp_parse_d2_frame_to_enroll(wss, skb, neighbor,
|
||||
wssid);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "WLP: Unable to parse D2 message from "
|
||||
"neighbor %02x:%02x for enrollment.\n",
|
||||
dev_addr->data[1], dev_addr->data[0]);
|
||||
goto error_resp_parse;
|
||||
}
|
||||
}
|
||||
error_resp_parse:
|
||||
kfree_skb(skb);
|
||||
error_session:
|
||||
wlp->session = NULL;
|
||||
out:
|
||||
mutex_unlock(&wlp->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enroll into WSS of provided WSSID by using neighbor as registrar
|
||||
*
|
||||
* &wss->mutex is held
|
||||
*/
|
||||
int wlp_enroll_neighbor(struct wlp *wlp, struct wlp_neighbor_e *neighbor,
|
||||
struct wlp_wss *wss, struct wlp_uuid *wssid)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
char buf[WLP_WSS_UUID_STRSIZE];
|
||||
struct uwb_dev_addr *dev_addr = &neighbor->uwb_dev->dev_addr;
|
||||
wlp_wss_uuid_print(buf, sizeof(buf), wssid);
|
||||
d_fnstart(6, dev, "wlp %p, neighbor %p, wss %p, wssid %p (%s)\n",
|
||||
wlp, neighbor, wss, wssid, buf);
|
||||
d_printf(6, dev, "Complete me.\n");
|
||||
result = wlp_d1d2_exchange(wlp, neighbor, wss, wssid);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "WLP: D1/D2 message exchange for enrollment "
|
||||
"failed. result = %d \n", result);
|
||||
goto out;
|
||||
}
|
||||
if (wss->state != WLP_WSS_STATE_PART_ENROLLED) {
|
||||
dev_err(dev, "WLP: Unable to enroll into WSS %s using "
|
||||
"neighbor %02x:%02x. \n", buf,
|
||||
dev_addr->data[1], dev_addr->data[0]);
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (wss->secure_status == WLP_WSS_SECURE) {
|
||||
dev_err(dev, "FIXME: need to complete secure enrollment.\n");
|
||||
result = -EINVAL;
|
||||
goto error;
|
||||
} else {
|
||||
wss->state = WLP_WSS_STATE_ENROLLED;
|
||||
d_printf(2, dev, "WLP: Success Enrollment into unsecure WSS "
|
||||
"%s using neighbor %02x:%02x. \n", buf,
|
||||
dev_addr->data[1], dev_addr->data[0]);
|
||||
}
|
||||
|
||||
d_fnend(6, dev, "wlp %p, neighbor %p, wss %p, wssid %p (%s)\n",
|
||||
wlp, neighbor, wss, wssid, buf);
|
||||
out:
|
||||
return result;
|
||||
error:
|
||||
wlp_wss_reset(wss);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover WSS information of neighbor's active WSS
|
||||
*/
|
||||
static
|
||||
int wlp_discover_neighbor(struct wlp *wlp,
|
||||
struct wlp_neighbor_e *neighbor)
|
||||
{
|
||||
return wlp_d1d2_exchange(wlp, neighbor, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Each neighbor in the neighborhood cache is discoverable. Discover it.
|
||||
*
|
||||
* Discovery is done through sending of D1 association frame and parsing
|
||||
* the D2 association frame response. Only wssid from D2 will be included
|
||||
* in neighbor cache, rest is just displayed to user and forgotten.
|
||||
*
|
||||
* The discovery is not done in parallel. This is simple and enables us to
|
||||
* maintain only one association context.
|
||||
*
|
||||
* The discovery of one neighbor does not affect the other, but if the
|
||||
* discovery of a neighbor fails it is removed from the neighborhood cache.
|
||||
*/
|
||||
static
|
||||
int wlp_discover_all_neighbors(struct wlp *wlp)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
struct wlp_neighbor_e *neighbor, *next;
|
||||
|
||||
list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) {
|
||||
result = wlp_discover_neighbor(wlp, neighbor);
|
||||
if (result < 0) {
|
||||
dev_err(dev, "WLP: Unable to discover neighbor "
|
||||
"%02x:%02x, removing from neighborhood. \n",
|
||||
neighbor->uwb_dev->dev_addr.data[1],
|
||||
neighbor->uwb_dev->dev_addr.data[0]);
|
||||
__wlp_neighbor_release(neighbor);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int wlp_add_neighbor_helper(struct device *dev, void *priv)
|
||||
{
|
||||
struct wlp *wlp = priv;
|
||||
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
|
||||
|
||||
return wlp_add_neighbor(wlp, uwb_dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover WLP neighborhood
|
||||
*
|
||||
* Will send D1 association frame to all devices in beacon group that have
|
||||
* discoverable bit set in WLP IE. D2 frames will be received, information
|
||||
* displayed to user in @buf. Partial information (from D2 association
|
||||
* frame) will be cached to assist with future association
|
||||
* requests.
|
||||
*
|
||||
* The discovery of the WLP neighborhood is triggered by the user. This
|
||||
* should occur infrequently and we thus free current cache and re-allocate
|
||||
* memory if needed.
|
||||
*
|
||||
* If one neighbor fails during initial discovery (determining if it is a
|
||||
* neighbor or not), we fail all - note that interaction with neighbor has
|
||||
* not occured at this point so if a failure occurs we know something went wrong
|
||||
* locally. We thus undo everything.
|
||||
*/
|
||||
ssize_t wlp_discover(struct wlp *wlp)
|
||||
{
|
||||
int result = 0;
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
|
||||
d_fnstart(6, dev, "wlp %p \n", wlp);
|
||||
mutex_lock(&wlp->nbmutex);
|
||||
/* Clear current neighborhood cache. */
|
||||
__wlp_neighbors_release(wlp);
|
||||
/* Determine which devices in neighborhood. Repopulate cache. */
|
||||
result = uwb_dev_for_each(wlp->rc, wlp_add_neighbor_helper, wlp);
|
||||
if (result < 0) {
|
||||
/* May have partial neighbor information, release all. */
|
||||
__wlp_neighbors_release(wlp);
|
||||
goto error_dev_for_each;
|
||||
}
|
||||
/* Discover the properties of devices in neighborhood. */
|
||||
result = wlp_discover_all_neighbors(wlp);
|
||||
/* In case of failure we still print our partial results. */
|
||||
if (result < 0) {
|
||||
dev_err(dev, "Unable to fully discover neighborhood. \n");
|
||||
result = 0;
|
||||
}
|
||||
error_dev_for_each:
|
||||
mutex_unlock(&wlp->nbmutex);
|
||||
d_fnend(6, dev, "wlp %p \n", wlp);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle events from UWB stack
|
||||
*
|
||||
* We handle events conservatively. If a neighbor goes off the air we
|
||||
* remove it from the neighborhood. If an association process is in
|
||||
* progress this function will block waiting for the nbmutex to become
|
||||
* free. The association process will thus be allowed to complete before it
|
||||
* is removed.
|
||||
*/
|
||||
static
|
||||
void wlp_uwb_notifs_cb(void *_wlp, struct uwb_dev *uwb_dev,
|
||||
enum uwb_notifs event)
|
||||
{
|
||||
struct wlp *wlp = _wlp;
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
struct wlp_neighbor_e *neighbor, *next;
|
||||
int result;
|
||||
switch (event) {
|
||||
case UWB_NOTIF_ONAIR:
|
||||
d_printf(6, dev, "UWB device %02x:%02x is onair\n",
|
||||
uwb_dev->dev_addr.data[1],
|
||||
uwb_dev->dev_addr.data[0]);
|
||||
result = wlp_eda_create_node(&wlp->eda,
|
||||
uwb_dev->mac_addr.data,
|
||||
&uwb_dev->dev_addr);
|
||||
if (result < 0)
|
||||
dev_err(dev, "WLP: Unable to add new neighbor "
|
||||
"%02x:%02x to EDA cache.\n",
|
||||
uwb_dev->dev_addr.data[1],
|
||||
uwb_dev->dev_addr.data[0]);
|
||||
break;
|
||||
case UWB_NOTIF_OFFAIR:
|
||||
d_printf(6, dev, "UWB device %02x:%02x is offair\n",
|
||||
uwb_dev->dev_addr.data[1],
|
||||
uwb_dev->dev_addr.data[0]);
|
||||
wlp_eda_rm_node(&wlp->eda, &uwb_dev->dev_addr);
|
||||
mutex_lock(&wlp->nbmutex);
|
||||
list_for_each_entry_safe(neighbor, next, &wlp->neighbors,
|
||||
node) {
|
||||
if (neighbor->uwb_dev == uwb_dev) {
|
||||
d_printf(6, dev, "Removing device from "
|
||||
"neighborhood.\n");
|
||||
__wlp_neighbor_release(neighbor);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&wlp->nbmutex);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "don't know how to handle event %d from uwb\n",
|
||||
event);
|
||||
}
|
||||
}
|
||||
|
||||
int wlp_setup(struct wlp *wlp, struct uwb_rc *rc)
|
||||
{
|
||||
struct device *dev = &rc->uwb_dev.dev;
|
||||
int result;
|
||||
|
||||
d_fnstart(6, dev, "wlp %p\n", wlp);
|
||||
BUG_ON(wlp->fill_device_info == NULL);
|
||||
BUG_ON(wlp->xmit_frame == NULL);
|
||||
BUG_ON(wlp->stop_queue == NULL);
|
||||
BUG_ON(wlp->start_queue == NULL);
|
||||
wlp->rc = rc;
|
||||
wlp_eda_init(&wlp->eda);/* Set up address cache */
|
||||
wlp->uwb_notifs_handler.cb = wlp_uwb_notifs_cb;
|
||||
wlp->uwb_notifs_handler.data = wlp;
|
||||
uwb_notifs_register(rc, &wlp->uwb_notifs_handler);
|
||||
|
||||
uwb_pal_init(&wlp->pal);
|
||||
result = uwb_pal_register(rc, &wlp->pal);
|
||||
if (result < 0)
|
||||
uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler);
|
||||
|
||||
d_fnend(6, dev, "wlp %p, result = %d\n", wlp, result);
|
||||
return result;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_setup);
|
||||
|
||||
void wlp_remove(struct wlp *wlp)
|
||||
{
|
||||
struct device *dev = &wlp->rc->uwb_dev.dev;
|
||||
d_fnstart(6, dev, "wlp %p\n", wlp);
|
||||
wlp_neighbors_release(wlp);
|
||||
uwb_pal_unregister(wlp->rc, &wlp->pal);
|
||||
uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler);
|
||||
wlp_eda_release(&wlp->eda);
|
||||
mutex_lock(&wlp->mutex);
|
||||
if (wlp->dev_info != NULL)
|
||||
kfree(wlp->dev_info);
|
||||
mutex_unlock(&wlp->mutex);
|
||||
wlp->rc = NULL;
|
||||
/* We have to use NULL here because this function can be called
|
||||
* when the device disappeared. */
|
||||
d_fnend(6, NULL, "wlp %p\n", wlp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_remove);
|
||||
|
||||
/**
|
||||
* wlp_reset_all - reset the WLP hardware
|
||||
* @wlp: the WLP device to reset.
|
||||
*
|
||||
* This schedules a full hardware reset of the WLP device. The radio
|
||||
* controller and any other PALs will also be reset.
|
||||
*/
|
||||
void wlp_reset_all(struct wlp *wlp)
|
||||
{
|
||||
uwb_rc_reset_all(wlp->rc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wlp_reset_all);
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -129,6 +129,7 @@ extern void bitmap_fold(unsigned long *dst, const unsigned long *orig,
|
|||
extern int bitmap_find_free_region(unsigned long *bitmap, int bits, int order);
|
||||
extern void bitmap_release_region(unsigned long *bitmap, int pos, int order);
|
||||
extern int bitmap_allocate_region(unsigned long *bitmap, int pos, int order);
|
||||
extern void bitmap_copy_le(void *dst, const unsigned long *src, int nbits);
|
||||
|
||||
#define BITMAP_LAST_WORD_MASK(nbits) \
|
||||
( \
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче