mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-28 10:41:38 -04:00
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:
commit
9779a8325a
110 changed files with 35819 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
925
drivers/usb/host/hwa-hc.c
Normal file
925
drivers/usb/host/hwa-hc.c
Normal file
|
|
@ -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 <[email protected]>");
|
||||
MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
11
drivers/usb/host/whci/Kbuild
Normal file
11
drivers/usb/host/whci/Kbuild
Normal file
|
|
@ -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
|
||||
367
drivers/usb/host/whci/asl.c
Normal file
367
drivers/usb/host/whci/asl.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
339
drivers/usb/host/whci/hcd.c
Normal file
339
drivers/usb/host/whci/hcd.c
Normal file
|
|
@ -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");
|
||||
87
drivers/usb/host/whci/hw.c
Normal file
87
drivers/usb/host/whci/hw.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
188
drivers/usb/host/whci/init.c
Normal file
188
drivers/usb/host/whci/init.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
95
drivers/usb/host/whci/int.c
Normal file
95
drivers/usb/host/whci/int.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
398
drivers/usb/host/whci/pzl.c
Normal file
398
drivers/usb/host/whci/pzl.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
567
drivers/usb/host/whci/qset.c
Normal file
567
drivers/usb/host/whci/qset.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
197
drivers/usb/host/whci/whcd.h
Normal file
197
drivers/usb/host/whci/whcd.h
Normal file
|
|
@ -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 */
|
||||
416
drivers/usb/host/whci/whci-hc.h
Normal file
416
drivers/usb/host/whci/whci-hc.h
Normal file
|
|
@ -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 */
|
||||
241
drivers/usb/host/whci/wusb.c
Normal file
241
drivers/usb/host/whci/wusb.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
41
drivers/usb/wusbcore/Kconfig
Normal file
41
drivers/usb/wusbcore/Kconfig
Normal file
|
|
@ -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.
|
||||
|
||||
26
drivers/usb/wusbcore/Makefile
Normal file
26
drivers/usb/wusbcore/Makefile
Normal file
|
|
@ -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
|
||||
673
drivers/usb/wusbcore/cbaf.c
Normal file
673
drivers/usb/wusbcore/cbaf.c
Normal file
|
|
@ -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 <[email protected]>");
|
||||
MODULE_DESCRIPTION("Wireless USB Cable Based Association");
|
||||
MODULE_LICENSE("GPL");
|
||||
538
drivers/usb/wusbcore/crypto.c
Normal file
538
drivers/usb/wusbcore/crypto.c
Normal file
|
|
@ -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 */
|
||||
}
|
||||
143
drivers/usb/wusbcore/dev-sysfs.c
Normal file
143
drivers/usb/wusbcore/dev-sysfs.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
1297
drivers/usb/wusbcore/devconnect.c
Normal file
1297
drivers/usb/wusbcore/devconnect.c
Normal file
File diff suppressed because it is too large
Load diff
321
drivers/usb/wusbcore/mmc.c
Normal file
321
drivers/usb/wusbcore/mmc.c
Normal file
|
|
@ -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);
|
||||
42
drivers/usb/wusbcore/pal.c
Normal file
42
drivers/usb/wusbcore/pal.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
115
drivers/usb/wusbcore/reservation.c
Normal file
115
drivers/usb/wusbcore/reservation.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
477
drivers/usb/wusbcore/rh.c
Normal file
477
drivers/usb/wusbcore/rh.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
642
drivers/usb/wusbcore/security.c
Normal file
642
drivers/usb/wusbcore/security.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
95
drivers/usb/wusbcore/wa-hc.c
Normal file
95
drivers/usb/wusbcore/wa-hc.c
Normal file
|
|
@ -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 <[email protected]>");
|
||||
MODULE_DESCRIPTION("Wireless USB Wire Adapter core");
|
||||
MODULE_LICENSE("GPL");
|
||||
417
drivers/usb/wusbcore/wa-hc.h
Normal file
417
drivers/usb/wusbcore/wa-hc.h
Normal file
|
|
@ -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__ */
|
||||
310
drivers/usb/wusbcore/wa-nep.c
Normal file
310
drivers/usb/wusbcore/wa-nep.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
562
drivers/usb/wusbcore/wa-rpipe.c
Normal file
562
drivers/usb/wusbcore/wa-rpipe.c
Normal file
|
|
@ -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);
|
||||
1709
drivers/usb/wusbcore/wa-xfer.c
Normal file
1709
drivers/usb/wusbcore/wa-xfer.c
Normal file
File diff suppressed because it is too large
Load diff
418
drivers/usb/wusbcore/wusbhc.c
Normal file
418
drivers/usb/wusbcore/wusbhc.c
Normal file
|
|
@ -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 <[email protected]>");
|
||||
MODULE_DESCRIPTION("Wireless USB core");
|
||||
MODULE_LICENSE("GPL");
|
||||
495
drivers/usb/wusbcore/wusbhc.h
Normal file
495
drivers/usb/wusbcore/wusbhc.h
Normal file
|
|
@ -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__ */
|
||||
Loading…
Reference in a new issue