Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View file

@ -0,0 +1,39 @@
#
# Wireless USB Core configuration
#
config USB_WUSB
tristate "Enable Wireless USB extensions"
depends on 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.

View file

@ -0,0 +1,25 @@
ccflags-$(CONFIG_USB_WUSB_CBAF_DEBUG) := -DDEBUG
obj-$(CONFIG_USB_WUSB) += wusbcore.o
obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o
obj-$(CONFIG_USB_WUSB_CBAF) += wusb-cbaf.o
wusbcore-y := \
crypto.o \
devconnect.o \
dev-sysfs.o \
mmc.o \
pal.o \
rh.o \
reservation.o \
security.o \
wusbhc.o
wusb-cbaf-y := cbaf.o
wusb-wa-y := \
wa-hc.o \
wa-nep.o \
wa-rpipe.o \
wa-xfer.o

667
drivers/usb/wusbcore/cbaf.c Normal file
View file

@ -0,0 +1,667 @@
/*
* 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/usb.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/random.h>
#include <linux/slab.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, USB_CTRL_GET_TIMEOUT);
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 association "
"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, USB_CTRL_SET_TIMEOUT);
}
/*
* 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, USB_CTRL_GET_TIMEOUT);
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 -ENOENT;
}
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), USB_CTRL_SET_TIMEOUT);
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:
usb_put_intf(iface);
usb_put_dev(cbaf->usb_dev);
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);
usb_put_dev(cbaf->usb_dev);
kfree(cbaf->buffer);
/* paranoia: clean up crypto keys */
kzfree(cbaf);
}
static const 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,
};
module_usb_driver(cbaf_driver);
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
MODULE_DESCRIPTION("Wireless USB Cable Based Association");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,516 @@
/*
* 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/slab.h>
#include <linux/usb/wusb.h>
#include <linux/scatterlist.h>
static int debug_crypto_verify = 0;
module_param(debug_crypto_verify, int, 0);
MODULE_PARM_DESC(debug_crypto_verify, "verify the key generation algorithms");
static void wusb_key_dump(const void *buf, size_t len)
{
print_hex_dump(KERN_ERR, " ", DUMP_PREFIX_OFFSET, 16, 1,
buf, len, 0);
}
/*
* 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 exhausted (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 successive 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 definition 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;
/*
* 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 = 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));
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;
}
/* 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 against 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);
result = 8;
error_cbc_crypt:
kfree(dst_buf);
error_dst_buf:
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;
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:
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);
wusb_key_dump(&stv_hsmic_hs, hs_size);
printk(KERN_ERR "E: CCM Nonce in: (%zu bytes)\n",
sizeof(stv_hsmic_n));
wusb_key_dump(&stv_hsmic_n, sizeof(stv_hsmic_n));
printk(KERN_ERR "E: MIC out:\n");
wusb_key_dump(mic, sizeof(mic));
printk(KERN_ERR "E: MIC out (from WUSB1.0[A.2]):\n");
wusb_key_dump(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\n");
wusb_key_dump(stv_key_a1, sizeof(stv_key_a1));
printk(KERN_ERR "E: keydvt in: nonce\n");
wusb_key_dump( &stv_keydvt_n_a1, sizeof(stv_keydvt_n_a1));
printk(KERN_ERR "E: keydvt in: hnonce & dnonce\n");
wusb_key_dump(&stv_keydvt_in_a1, sizeof(stv_keydvt_in_a1));
printk(KERN_ERR "E: keydvt out: KCK\n");
wusb_key_dump(&keydvt_out.kck, sizeof(keydvt_out.kck));
printk(KERN_ERR "E: keydvt out: PTK\n");
wusb_key_dump(&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;
if (debug_crypto_verify) {
result = wusb_key_derive_verify();
if (result < 0)
return result;
return wusb_oob_mic_verify();
}
return 0;
}
void wusb_crypto_exit(void)
{
/* FIXME: free cached crypto transforms */
}

View file

@ -0,0 +1,139 @@
/*
* 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"
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);
}

File diff suppressed because it is too large Load diff

317
drivers/usb/wusbcore/mmc.c Normal file
View file

@ -0,0 +1,317 @@
/*
* 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 <linux/slab.h>
#include <linux/export.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);
static int wusbhc_mmc_start(struct wusbhc *wusbhc)
{
int ret;
mutex_lock(&wusbhc->mutex);
ret = wusbhc->start(wusbhc);
if (ret >= 0)
wusbhc->active = 1;
mutex_unlock(&wusbhc->mutex);
return ret;
}
static void wusbhc_mmc_stop(struct wusbhc *wusbhc)
{
mutex_lock(&wusbhc->mutex);
wusbhc->active = 0;
wusbhc->stop(wusbhc, WUSB_CHANNEL_STOP_DELAY_MS);
mutex_unlock(&wusbhc->mutex);
}
/*
* wusbhc_start - start transmitting MMCs and accepting connections
* @wusbhc: the HC to start
*
* Establishes a cluster reservation, enables device connections, and
* starts MMCs with appropriate DNTS parameters.
*/
int wusbhc_start(struct wusbhc *wusbhc)
{
int result;
struct device *dev = wusbhc->dev;
WARN_ON(wusbhc->wuie_host_info != NULL);
BUG_ON(wusbhc->uwb_rc == 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);
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;
}
result = wusbhc->set_num_dnts(wusbhc, wusbhc->dnts_interval,
wusbhc->dnts_num_slots);
if (result < 0) {
dev_err(dev, "Cannot set DNTS parameters: %d\n", result);
goto error_set_num_dnts;
}
result = wusbhc_mmc_start(wusbhc);
if (result < 0) {
dev_err(dev, "error starting wusbch: %d\n", result);
goto error_wusbhc_start;
}
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;
}
/*
* wusbhc_stop - stop transmitting MMCs
* @wusbhc: the HC to stop
*
* Stops the WUSB channel and removes the cluster reservation.
*/
void wusbhc_stop(struct wusbhc *wusbhc)
{
wusbhc_mmc_stop(wusbhc);
wusbhc_sec_stop(wusbhc);
wusbhc_devconnect_stop(wusbhc);
wusbhc_rsv_terminate(wusbhc);
}
/*
* 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 (chid) {
if (wusbhc->active) {
mutex_unlock(&wusbhc->mutex);
return -EBUSY;
}
wusbhc->chid = *chid;
}
/* register with UWB if we haven't already since we are about to start
the radio. */
if ((chid) && (wusbhc->uwb_rc == NULL)) {
wusbhc->uwb_rc = uwb_rc_get_by_grandpa(wusbhc->dev->parent);
if (wusbhc->uwb_rc == NULL) {
result = -ENODEV;
dev_err(wusbhc->dev,
"Cannot get associated UWB Host Controller\n");
goto error_rc_get;
}
result = wusbhc_pal_register(wusbhc);
if (result < 0) {
dev_err(wusbhc->dev, "Cannot register as a UWB PAL\n");
goto error_pal_register;
}
}
mutex_unlock(&wusbhc->mutex);
if (chid)
result = uwb_radio_start(&wusbhc->pal);
else if (wusbhc->uwb_rc)
uwb_radio_stop(&wusbhc->pal);
return result;
error_pal_register:
uwb_rc_put(wusbhc->uwb_rc);
wusbhc->uwb_rc = NULL;
error_rc_get:
mutex_unlock(&wusbhc->mutex);
return result;
}
EXPORT_SYMBOL_GPL(wusbhc_chid_set);

View file

@ -0,0 +1,56 @@
/*
* 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"
static void wusbhc_channel_changed(struct uwb_pal *pal, int channel)
{
struct wusbhc *wusbhc = container_of(pal, struct wusbhc, pal);
dev_dbg(wusbhc->dev, "%s: channel = %d\n", __func__, channel);
if (channel < 0)
wusbhc_stop(wusbhc);
else
wusbhc_start(wusbhc);
}
/**
* 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;
wusbhc->pal.rc = wusbhc->uwb_rc;
wusbhc->pal.channel_changed = wusbhc_channel_changed;
return uwb_pal_register(&wusbhc->pal);
}
/**
* wusbhc_pal_unregister - unregister the WUSB HC as a UWB PAL
* @wusbhc: the WUSB HC
*/
void wusbhc_pal_unregister(struct wusbhc *wusbhc)
{
if (wusbhc->uwb_rc)
uwb_pal_unregister(&wusbhc->pal);
}

View file

@ -0,0 +1,122 @@
/*
* 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;
struct uwb_mas_bm mas;
char buf[72];
dev_dbg(dev, "%s: state = %d\n", __func__, rsv->state);
switch (rsv->state) {
case UWB_RSV_STATE_O_ESTABLISHED:
uwb_rsv_get_usable_mas(rsv, &mas);
bitmap_scnprintf(buf, sizeof(buf), mas.bm, UWB_NUM_MAS);
dev_dbg(dev, "established reservation: %s\n", buf);
wusbhc_bwa_set(wusbhc, rsv->stream, &mas);
break;
case UWB_RSV_STATE_NONE:
dev_dbg(dev, "removed reservation\n");
wusbhc_bwa_set(wusbhc, 0, 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 bandwidth 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;
if (rc == NULL)
return -ENODEV;
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->target.type = UWB_RSV_TARGET_DEVADDR;
rsv->target.devaddr = bcid;
rsv->type = UWB_DRP_TYPE_PRIVATE;
rsv->max_mas = 256; /* try to get as much as possible */
rsv->min_mas = 15; /* one MAS per zone */
rsv->max_interval = 1; /* max latency is one 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 the 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);
uwb_rsv_destroy(wusbhc->rsv);
wusbhc->rsv = NULL;
}
}

440
drivers/usb/wusbcore/rh.c Normal file
View file

@ -0,0 +1,440 @@
/*
* 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 <linux/slab.h>
#include <linux/export.h>
#include "wusbhc.h"
/*
* Reset a fake port
*
* Using a Reset Device IE is too heavyweight as it causes the device
* to enter the UnConnected state and leave the cluster, this can mean
* that when the device reconnects it is connected to a different fake
* port.
*
* Instead, reset authenticated devices with a SetAddress(0), followed
* by a SetAddresss(AuthAddr).
*
* For unauthenticated devices just pretend to reset but do nothing.
* If the device initialization continues to fail it will eventually
* time out after TrustTimeout and enter the UnConnected state.
*
* @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);
struct wusb_dev *wusb_dev = port->wusb_dev;
if (wusb_dev == NULL)
return -ENOTCONN;
port->status |= USB_PORT_STAT_RESET;
port->change |= USB_PORT_STAT_C_RESET;
if (wusb_dev->addr & WUSB_DEV_ADDR_UNAUTH)
result = 0;
else
result = wusb_dev_update_address(wusbhc, wusb_dev);
port->status &= ~USB_PORT_STAT_RESET;
port->status |= USB_PORT_STAT_ENABLE;
port->change |= USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_ENABLE;
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 disappear 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, bits_set = 0;
/* WE DON'T LOCK, see comment */
/* round up to bytes. Hub bit is bit 0 so add 1. */
size = DIV_ROUND_UP(wusbhc->ports_max + 1, 8);
/* clear the output buffer. */
memset(_buf, 0, size);
/* set the bit for each changed port. */
for (cnt = 0; cnt < wusbhc->ports_max; cnt++) {
if (wusb_port_by_idx(wusbhc, cnt)->change) {
const int bitpos = cnt+1;
_buf[bitpos/8] |= (1 << (bitpos % 8));
bits_set++;
}
}
return bits_set ? size : 0;
}
EXPORT_SYMBOL_GPL(wusbhc_rh_status_data);
/*
* Return the hub's descriptor
*
* 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->u.hs.DeviceRemovable[0], 0, temp);
memset(&descr->u.hs.DeviceRemovable[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;
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;
}
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)
{
struct device *dev = wusbhc->dev;
if (port_idx > wusbhc->ports_max)
return -EINVAL;
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:
return 0;
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);
return 0;
case USB_PORT_FEAT_RESET:
return wusbhc_rh_port_reset(wusbhc, port_idx);
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);
return -ENOSYS;
default:
dev_err(dev, "(port_idx %d) set feat %d/%d UNKNOWN\n",
port_idx, feature, selector);
return -EPIPE;
}
return 0;
}
/*
* 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 = 0;
struct device *dev = wusbhc->dev;
if (port_idx > wusbhc->ports_max)
return -EINVAL;
mutex_lock(&wusbhc->mutex);
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:
dev_err(dev, "(port_idx %d) Clear feat %d/%d UNIMPLEMENTED\n",
port_idx, feature, selector);
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);
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)
{
__le16 *buf = (__le16 *)_buf;
if (port_idx > wusbhc->ports_max)
return -EINVAL;
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);
mutex_unlock(&wusbhc->mutex);
return 0;
}
/*
* 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_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);
}

View file

@ -0,0 +1,615 @@
/*
* 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/slab.h>
#include <linux/usb/ch9.h>
#include <linux/random.h>
#include <linux/export.h>
#include "wusbhc.h"
static void wusbhc_gtk_rekey_work(struct work_struct *work);
int wusbhc_sec_create(struct wusbhc *wusbhc)
{
/*
* WQ is singlethread because we need to serialize rekey operations.
* Use a separate workqueue for security operations instead of the
* wusbd workqueue because security operations may need to communicate
* directly with downstream wireless devices using synchronous URBs.
* If a device is not responding, this could block other host
* controller operations.
*/
wusbhc->wq_security = create_singlethread_workqueue("wusbd_security");
if (wusbhc->wq_security == NULL) {
pr_err("WUSB-core: Cannot create wusbd_security workqueue\n");
return -ENOMEM;
}
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 = 0;
INIT_WORK(&wusbhc->gtk_rekey_work, wusbhc_gtk_rekey_work);
return 0;
}
/* Called when the HC is destroyed */
void wusbhc_sec_destroy(struct wusbhc *wusbhc)
{
destroy_workqueue(wusbhc->wq_security);
}
/**
* 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 consists of two parts: the device's authenticated
* 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_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, USB_CTRL_SET_TIMEOUT);
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;
u8 key_index = wusb_key_index(wusbhc->gtk_index,
WUSB_KEY_INDEX_TYPE_GTK, WUSB_KEY_INDEX_ORIGINATOR_HOST);
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 | key_index, 0,
&wusbhc->gtk.descr, wusbhc->gtk.descr.bLength,
USB_CTRL_SET_TIMEOUT);
}
/* 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, *new_secd;
const struct usb_encryption_descriptor *etd, *ccm1_etd = NULL;
const void *itr, *top;
char buf[64];
secd = kmalloc(sizeof(*secd), GFP_KERNEL);
if (secd == NULL) {
result = -ENOMEM;
goto out;
}
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 out;
}
secd_size = le16_to_cpu(secd->wTotalLength);
new_secd = krealloc(secd, secd_size, GFP_KERNEL);
if (new_secd == NULL) {
dev_err(dev,
"Can't allocate space for security descriptors\n");
goto out;
}
secd = new_secd;
result = usb_get_descriptor(usb_dev, USB_DT_SECURITY,
0, secd, secd_size);
if (result < secd_size) {
dev_err(dev, "Can't read security descriptor or "
"not enough data: %d\n", result);
goto out;
}
bytes = 0;
itr = &secd[1];
top = (void *)secd + 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 out;
}
wusb_dev->ccm1_etd = *ccm1_etd;
dev_dbg(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;
out:
kfree(secd);
return result;
}
void wusb_dev_sec_rm(struct wusb_dev *wusb_dev)
{
/* Nothing so far */
}
/**
* 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.
*/
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,
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
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_set_device_state(usb_dev, USB_STATE_DEFAULT);
usb_ep0_reinit(usb_dev);
/* Set new (authenticated) address. */
result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
USB_REQ_SET_ADDRESS,
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
new_address, 0, NULL, 0,
USB_CTRL_SET_TIMEOUT);
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_set_device_state(usb_dev, USB_STATE_ADDRESS);
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 = kcalloc(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] */
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]), USB_CTRL_SET_TIMEOUT);
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]), USB_CTRL_GET_TIMEOUT);
if (result < 0) {
dev_err(dev, "Handshake2: request failed: %d\n", result);
goto error_hs2;
}
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;
}
/* 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;
}
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;
}
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]), USB_CTRL_SET_TIMEOUT);
if (result < 0) {
dev_err(dev, "Handshake3: request failed: %d\n", result);
goto error_hs3;
}
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;
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) {
result = wusb_dev_update_address(wusbhc, wusb_dev);
if (result < 0)
goto error_dev_update_address;
}
result = 0;
dev_info(dev, "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)
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_work(struct work_struct *work)
{
struct wusbhc *wusbhc = container_of(work,
struct wusbhc, gtk_rekey_work);
size_t key_size = sizeof(wusbhc->gtk.data);
int port_idx;
struct wusb_dev *wusb_dev, *wusb_dev_next;
LIST_HEAD(rekey_list);
mutex_lock(&wusbhc->mutex);
/* generate the new key */
wusbhc_generate_gtk(wusbhc);
/* roll the gtk index. */
wusbhc->gtk_index = (wusbhc->gtk_index + 1) % (WUSB_KEY_INDEX_MAX + 1);
/*
* Save all connected devices on a list while holding wusbhc->mutex and
* take a reference to each one. Then submit the set key request to
* them after releasing the lock in order to avoid a deadlock.
*/
for (port_idx = 0; port_idx < wusbhc->ports_max; port_idx++) {
wusb_dev = wusbhc->port[port_idx].wusb_dev;
if (!wusb_dev || !wusb_dev->usb_dev
|| !wusb_dev->usb_dev->authenticated)
continue;
wusb_dev_get(wusb_dev);
list_add_tail(&wusb_dev->rekey_node, &rekey_list);
}
mutex_unlock(&wusbhc->mutex);
/* Submit the rekey requests without holding wusbhc->mutex. */
list_for_each_entry_safe(wusb_dev, wusb_dev_next, &rekey_list,
rekey_node) {
list_del_init(&wusb_dev->rekey_node);
dev_dbg(&wusb_dev->usb_dev->dev,
"%s: rekey device at port %d\n",
__func__, wusb_dev->port_idx);
if (wusb_dev_set_gtk(wusbhc, wusb_dev) < 0) {
dev_err(&wusb_dev->usb_dev->dev,
"%s: rekey device at port %d failed\n",
__func__, wusb_dev->port_idx);
}
wusb_dev_put(wusb_dev);
}
/* Switch the host controller to use the new GTK. */
mutex_lock(&wusbhc->mutex);
wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid,
&wusbhc->gtk.descr.bKeyData, key_size);
mutex_unlock(&wusbhc->mutex);
}
/**
* 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)
{
/*
* We need to submit a URB to the downstream WUSB devices in order to
* change the group key. This can't be done while holding the
* wusbhc->mutex since that is also taken in the urb_enqueue routine
* and will cause a deadlock. Instead, queue a work item to do
* it when the lock is not held
*/
queue_work(wusbhc->wq_security, &wusbhc->gtk_rekey_work);
}

View file

@ -0,0 +1,99 @@
/*
* 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 <linux/slab.h>
#include <linux/module.h>
#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,
kernel_ulong_t quirks)
{
int result;
struct device *dev = &iface->dev;
result = wa_rpipes_create(wa);
if (result < 0)
goto error_rpipes_create;
wa->quirks = quirks;
/* 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->dti_buf_size = usb_endpoint_maxp(wa->dti_epd);
wa->dti_buf = kmalloc(wa->dti_buf_size, GFP_KERNEL);
if (wa->dti_buf == NULL) {
result = -ENOMEM;
goto error_dti_buf_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->dti_buf);
error_dti_buf_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);
}
kfree(wa->dti_buf);
wa_nep_destroy(wa);
wa_rpipes_destroy(wa);
}
EXPORT_SYMBOL_GPL(__wa_destroy);
/**
* wa_reset_all - reset the WA device
* @wa: the WA to be reset
*
* For HWAs the radio controller and all other PALs are also reset.
*/
void wa_reset_all(struct wahc *wa)
{
/* FIXME: assuming HWA. */
wusbhc_reset_all(wa->wusb);
}
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
MODULE_DESCRIPTION("Wireless USB Wire Adapter core");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,481 @@
/*
* 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 management: 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 management -- 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 hub_wq 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);
extern void wa_process_errored_transfers_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;
struct list_head list_node;
atomic_t segs_available;
u8 buffer[1]; /* For reads/writes on USB */
};
enum wa_dti_state {
WA_DTI_TRANSFER_RESULT_PENDING,
WA_DTI_ISOC_PACKET_STATUS_PENDING,
WA_DTI_BUF_IN_DATA_PENDING
};
enum wa_quirks {
/*
* The Alereon HWA expects the data frames in isochronous transfer
* requests to be concatenated and not sent as separate packets.
*/
WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC = 0x01,
/*
* The Alereon HWA can be instructed to not send transfer notifications
* as an optimization.
*/
WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS = 0x02,
};
enum wa_vendor_specific_requests {
WA_REQ_ALEREON_DISABLE_XFER_NOTIFICATIONS = 0x4C,
WA_REQ_ALEREON_FEATURE_SET = 0x01,
WA_REQ_ALEREON_FEATURE_CLEAR = 0x00,
};
#define WA_MAX_BUF_IN_URBS 4
/**
* 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 unmodified 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
* commonalities with WHCI), a wa layer (for sharing
* commonalities 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 */
struct list_head rpipe_delayed_list; /* delayed RPIPES. */
spinlock_t rpipe_lock; /* protect rpipe_bm and delayed list */
struct mutex rpipe_mutex; /* assigning resources to endpoints */
/*
* dti_state is used to track the state of the dti_urb. When dti_state
* is WA_DTI_ISOC_PACKET_STATUS_PENDING, dti_isoc_xfer_in_progress and
* dti_isoc_xfer_seg identify which xfer the incoming isoc packet
* status refers to.
*/
enum wa_dti_state dti_state;
u32 dti_isoc_xfer_in_progress;
u8 dti_isoc_xfer_seg;
struct urb *dti_urb; /* URB for reading xfer results */
/* URBs for reading data in */
struct urb buf_in_urbs[WA_MAX_BUF_IN_URBS];
int active_buf_in_urbs; /* number of buf_in_urbs active. */
struct edc dti_edc; /* DTI error density counter */
void *dti_buf;
size_t dti_buf_size;
unsigned long dto_in_use; /* protect dto endoint serialization */
s32 status; /* For reading status */
struct list_head xfer_list;
struct list_head xfer_delayed_list;
struct list_head xfer_errored_list;
/*
* lock for the above xfer lists. Can be taken while a xfer->lock is
* held but not in the reverse order.
*/
spinlock_t xfer_list_lock;
struct work_struct xfer_enqueue_work;
struct work_struct xfer_error_work;
atomic_t xfer_id_count;
kernel_ulong_t quirks;
};
extern int wa_create(struct wahc *wa, struct usb_interface *iface,
kernel_ulong_t);
extern void __wa_destroy(struct wahc *wa);
extern int wa_dti_start(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)
{
INIT_LIST_HEAD(&wa->rpipe_delayed_list);
spin_lock_init(&wa->rpipe_lock);
mutex_init(&wa->rpipe_mutex);
}
static inline void wa_init(struct wahc *wa)
{
int index;
edc_init(&wa->nep_edc);
atomic_set(&wa->notifs_queued, 0);
wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING;
wa_rpipe_init(wa);
edc_init(&wa->dti_edc);
INIT_LIST_HEAD(&wa->xfer_list);
INIT_LIST_HEAD(&wa->xfer_delayed_list);
INIT_LIST_HEAD(&wa->xfer_errored_list);
spin_lock_init(&wa->xfer_list_lock);
INIT_WORK(&wa->xfer_enqueue_work, wa_urb_enqueue_run);
INIT_WORK(&wa->xfer_error_work, wa_process_errored_transfers_run);
wa->dto_in_use = 0;
atomic_set(&wa->xfer_id_count, 1);
/* init the buf in URBs */
for (index = 0; index < WA_MAX_BUF_IN_URBS; ++index)
usb_init_urb(&(wa->buf_in_urbs[index]));
wa->active_buf_in_urbs = 0;
}
/**
* 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 void rpipe_clear_feature_stalled(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 *, int);
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 a 'struct' hcd based solution
*/
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, USB_CTRL_SET_TIMEOUT);
}
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), USB_CTRL_GET_TIMEOUT);
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__ */

View file

@ -0,0 +1,308 @@
/*
* 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/slab.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 HWA_NOTIF_BPST_ADJ:
break; /* no action needed for BPST ADJ. */
case DWA_NOTIF_RWAKE:
case DWA_NOTIF_PORTSTATUS:
/* FIXME: unimplemented WA NOTIFs */
/* fallthru */
default:
dev_err(dev, "HWA: unknown notification 0x%x, "
"%zu bytes; discarding\n",
notif_hdr->bNotifyType,
(size_t)notif_hdr->bLength);
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:
dev_warn(dev, "HWA: device sent short notification, "
"%d bytes missing; discarding %d bytes.\n",
missing, (int)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);
}

View file

@ -0,0 +1,555 @@
/*
* 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
*
* Targeted 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 Address 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/atomic.h>
#include <linux/bitmap.h>
#include <linux/slab.h>
#include <linux/export.h>
#include "wusbhc.h"
#include "wa-hc.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.
*/
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),
USB_CTRL_GET_TIMEOUT);
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.
*/
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),
USB_CTRL_SET_TIMEOUT);
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);
INIT_LIST_HEAD(&rpipe->list_node);
}
static unsigned rpipe_get_idx(struct wahc *wa, unsigned rpipe_idx)
{
unsigned long flags;
spin_lock_irqsave(&wa->rpipe_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_lock, flags);
return rpipe_idx;
}
static void rpipe_put_idx(struct wahc *wa, unsigned rpipe_idx)
{
unsigned long flags;
spin_lock_irqsave(&wa->rpipe_lock, flags);
clear_bit(rpipe_idx, wa->rpipe_bm);
spin_unlock_irqrestore(&wa->rpipe_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);
if (rpipe->ep)
rpipe->ep->hcpriv = NULL;
rpipe_put_idx(rpipe->wa, index);
wa_put(rpipe->wa);
kfree(rpipe);
}
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;
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);
return -ENXIO;
found:
set_bit(rpipe_idx, wa->rpipe_bm);
rpipe->wa = wa_get(wa);
*prpipe = rpipe;
return 0;
}
static int __rpipe_reset(struct wahc *wa, unsigned index)
{
int result;
struct device *dev = &wa->usb_iface->dev;
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, USB_CTRL_SET_TIMEOUT);
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 = 2,
};
/*
* 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;
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->bLength;
}
out:
return epcd;
}
/*
* Aim an rpipe to its device & endpoint destination
*
* Make sure we change the address to unauthenticated 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;
u32 ack_window, epcd_max_sequence;
u8 unauth;
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]) */
if (usb_endpoint_xfer_isoc(&ep->desc))
rpipe->descr.wMaxPacketSize = epcd->wOverTheAirPacketSize;
else
rpipe->descr.wMaxPacketSize = ep->desc.wMaxPacketSize;
rpipe->descr.hwa_bMaxBurst = max(min_t(unsigned int,
epcd->bMaxBurst, 16U), 1U);
rpipe->descr.hwa_bDeviceInfoIndex =
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;
dev_dbg(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);
rpipe->descr.hwa_reserved = 0;
rpipe->descr.bEndpointAddress = ep->desc.bEndpointAddress;
/* FIXME: bDataSequence */
rpipe->descr.bDataSequence = 0;
/* start with base window of hwa_bMaxBurst bits starting at 0. */
ack_window = 0xFFFFFFFF >> (32 - rpipe->descr.hwa_bMaxBurst);
rpipe->descr.dwCurrentWindow = cpu_to_le32(ack_window);
epcd_max_sequence = max(min_t(unsigned int,
epcd->bMaxSequence, 32U), 2U);
rpipe->descr.bMaxDataSequence = epcd_max_sequence - 1;
rpipe->descr.bInterval = ep->desc.bInterval;
if (usb_endpoint_xfer_isoc(&ep->desc))
rpipe->descr.bOverTheAirInterval = epcd->bOverTheAirInterval;
else
rpipe->descr.bOverTheAirInterval = 0; /* 0 if not isoc */
/* FIXME: xmit power & preamble blah blah */
rpipe->descr.bmAttribute = (ep->desc.bmAttributes &
USB_ENDPOINT_XFERTYPE_MASK);
/* rpipe->descr.bmCharacteristics RO */
rpipe->descr.bmRetryOptions = (wa->wusb->retry_count & 0xF);
/* 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:
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;
struct device *dev = &wa->usb_iface->dev;
u8 portnum = wusb_port_no_to_idx(urb->dev->portnum);
#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(hwa_bDeviceInfoIndex, 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(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;
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);
dev_dbg(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 */
dev_dbg(dev, "ep 0x%02x: using rpipe %u\n",
ep->desc.bEndpointAddress,
le16_to_cpu(rpipe->descr.wRPipeIndex));
}
error:
mutex_unlock(&wa->rpipe_mutex);
return result;
}
/*
* Allocate the bitmap for each rpipe.
*/
int wa_rpipes_create(struct wahc *wa)
{
wa->rpipes = le16_to_cpu(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;
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);
}
/*
* 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 wa_rpipe *rpipe;
mutex_lock(&wa->rpipe_mutex);
rpipe = ep->hcpriv;
if (rpipe != NULL) {
u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex);
usb_control_msg(
wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
USB_REQ_RPIPE_ABORT,
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE,
0, index, NULL, 0, USB_CTRL_SET_TIMEOUT);
rpipe_put(rpipe);
}
mutex_unlock(&wa->rpipe_mutex);
}
EXPORT_SYMBOL_GPL(rpipe_ep_disable);
/* Clear the stalled status of an RPIPE. */
void rpipe_clear_feature_stalled(struct wahc *wa, struct usb_host_endpoint *ep)
{
struct wa_rpipe *rpipe;
mutex_lock(&wa->rpipe_mutex);
rpipe = ep->hcpriv;
if (rpipe != NULL) {
u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex);
usb_control_msg(
wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
USB_REQ_CLEAR_FEATURE,
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE,
RPIPE_STALL, index, NULL, 0, USB_CTRL_SET_TIMEOUT);
}
mutex_unlock(&wa->rpipe_mutex);
}
EXPORT_SYMBOL_GPL(rpipe_clear_feature_stalled);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,514 @@
/*
* 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 initialized (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;
}
wusbhc->trust_timeout = min_t(unsigned, trust_timeout, 500);
cancel_delayed_work(&wusbhc->keep_alive_timer);
flush_workqueue(wusbd);
queue_delayed_work(wusbd, &wusbhc->keep_alive_timer,
msecs_to_jiffies(wusbhc->trust_timeout / 2));
out:
return result < 0 ? result : size;
}
static DEVICE_ATTR(wusb_trust_timeout, 0644, wusb_trust_timeout_show,
wusb_trust_timeout_store);
/*
* Show 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);
const struct wusb_ckhdid *chid;
ssize_t result = 0;
if (wusbhc->wuie_host_info != NULL)
chid = &wusbhc->wuie_host_info->CHID;
else
chid = &wusb_ckhdid_zero;
result += ckhdid_printf(buf, PAGE_SIZE, chid);
result += sprintf(buf + result, "\n");
return result;
}
/*
* Store a new CHID.
*
* - Write an all zeros CHID and it will stop the controller
* - Write a non-zero CHID and it will start it.
*
* 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);
static ssize_t wusb_phy_rate_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
return sprintf(buf, "%d\n", wusbhc->phy_rate);
}
static ssize_t wusb_phy_rate_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
uint8_t phy_rate;
ssize_t result;
result = sscanf(buf, "%hhu", &phy_rate);
if (result != 1)
return -EINVAL;
if (phy_rate >= UWB_PHY_RATE_INVALID)
return -EINVAL;
wusbhc->phy_rate = phy_rate;
return size;
}
static DEVICE_ATTR(wusb_phy_rate, 0644, wusb_phy_rate_show,
wusb_phy_rate_store);
static ssize_t wusb_dnts_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
return sprintf(buf, "num slots: %d\ninterval: %dms\n",
wusbhc->dnts_num_slots, wusbhc->dnts_interval);
}
static ssize_t wusb_dnts_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
uint8_t num_slots, interval;
ssize_t result;
result = sscanf(buf, "%hhu %hhu", &num_slots, &interval);
if (result != 2)
return -EINVAL;
wusbhc->dnts_num_slots = num_slots;
wusbhc->dnts_interval = interval;
return size;
}
static DEVICE_ATTR(wusb_dnts, 0644, wusb_dnts_show, wusb_dnts_store);
static ssize_t wusb_retry_count_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
return sprintf(buf, "%d\n", wusbhc->retry_count);
}
static ssize_t wusb_retry_count_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
uint8_t retry_count;
ssize_t result;
result = sscanf(buf, "%hhu", &retry_count);
if (result != 1)
return -EINVAL;
wusbhc->retry_count = max_t(uint8_t, retry_count,
WUSB_RETRY_COUNT_MAX);
return size;
}
static DEVICE_ATTR(wusb_retry_count, 0644, wusb_retry_count_show,
wusb_retry_count_store);
/* Group all the WUSBHC attributes */
static struct attribute *wusbhc_attrs[] = {
&dev_attr_wusb_trust_timeout.attr,
&dev_attr_wusb_chid.attr,
&dev_attr_wusb_phy_rate.attr,
&dev_attr_wusb_dnts.attr,
&dev_attr_wusb_retry_count.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;
/* set defaults. These can be overwritten using sysfs attributes. */
wusbhc->trust_timeout = WUSB_TRUST_TIMEOUT_MS;
wusbhc->phy_rate = UWB_PHY_RATE_INVALID - 1;
wusbhc->dnts_num_slots = 4;
wusbhc->dnts_interval = 2;
wusbhc->retry_count = WUSB_RETRY_COUNT_INFINITE;
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;
}
return 0;
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 specify the stop retransmission
* 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) {
wusb_dev->entry_ts = jiffies;
/* wusbhc_devconnect_acked() can't be called 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);
else
wusb_dev_put(wusb_dev);
}
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)
{
if (wusbhc->uwb_rc)
uwb_rc_reset_all(wusbhc->uwb_rc);
}
EXPORT_SYMBOL_GPL(wusbhc_reset_all);
static struct notifier_block wusb_usb_notifier = {
.notifier_call = wusb_usb_ncb,
.priority = INT_MAX /* Need to be called first of all */
};
static int __init wusbcore_init(void)
{
int result;
result = wusb_crypto_init();
if (result < 0)
goto error_crypto_init;
/* WQ is singlethread because we need to serialize notifications */
wusbd = create_singlethread_workqueue("wusbd");
if (wusbd == NULL) {
result = -ENOMEM;
printk(KERN_ERR "WUSB-core: Cannot create wusbd workqueue\n");
goto error_wusbd_create;
}
usb_register_notify(&wusb_usb_notifier);
bitmap_zero(wusb_cluster_id_table, CLUSTER_IDS);
set_bit(0, wusb_cluster_id_table); /* reserve Cluster ID 0xff */
return 0;
error_wusbd_create:
wusb_crypto_exit();
error_crypto_init:
return result;
}
module_init(wusbcore_init);
static void __exit wusbcore_exit(void)
{
clear_bit(0, wusb_cluster_id_table);
if (!bitmap_empty(wusb_cluster_id_table, CLUSTER_IDS)) {
char buf[256];
bitmap_scnprintf(buf, sizeof(buf), wusb_cluster_id_table,
CLUSTER_IDS);
printk(KERN_ERR "BUG: WUSB Cluster IDs not released "
"on exit: %s\n", buf);
WARN_ON(1);
}
usb_unregister_notify(&wusb_usb_notifier);
destroy_workqueue(wusbd);
wusb_crypto_exit();
}
module_exit(wusbcore_exit);
MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
MODULE_DESCRIPTION("Wireless USB core");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,501 @@
/*
* 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>
#include <linux/usb/hcd.h>
#include <linux/uwb.h>
#include <linux/usb/wusb.h>
/*
* Time from a WUSB channel stop request to the last transmitted MMC.
*
* This needs to be > 4.096 ms in case no MMCs can be transmitted in
* zone 0.
*/
#define WUSB_CHANNEL_STOP_DELAY_MS 8
#define WUSB_RETRY_COUNT_MAX 15
#define WUSB_RETRY_COUNT_INFINITE 0
/**
* 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 */
struct list_head rekey_node; /* GTK rekey 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 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 Controller 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 */
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 length 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.
*
* @start Start the WUSB channel.
*
* @stop Stop the WUSB channel after the specified number of
* milliseconds. Channel Stop IEs should be transmitted
* as required by [WUSB] 4.16.2.1.
*
* @mmcie_add HC specific operation (WHCI or HWA) for adding an
* MMCIE.
*
* @mmcie_rm HC specific operation (WHCI or HWA) for removing an
* MMCIE.
*
* @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
* disappear 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 wusb_ckhdid chid;
uint8_t phy_rate;
uint8_t dnts_num_slots;
uint8_t dnts_interval;
uint8_t retry_count;
struct wuie_host_info *wuie_host_info;
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 delay);
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;
/* workqueue for WUSB security related tasks. */
struct workqueue_struct *wq_security;
struct work_struct gtk_rekey_work;
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);
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);
extern void wusbhc_devconnect_stop(struct wusbhc *wusbhc);
extern void wusbhc_handle_dn(struct wusbhc *, u8 srcaddr,
struct wusb_dn_hdr *dn_hdr, size_t size);
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_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);
int wusb_dev_update_address(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev);
/* 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__ */