From 90ff96f22426a9d1a06df97dead0a9098facb567 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:23 +0100 Subject: wusb: add the Wireless USB core Add support for Ceritified Wireless USB 1.0 to the USB stack. This has been split into several patches for easier review. core (this patch): - host controller infrastructure - cluster reservation - UWB PAL registration - fake root hub protocol: - MMC management (start/stop, managing IEs) - device connection security: - device authentication and authorization build-system: - Kconfig and Kbuild files Signed-off-by: David Vrabel --- drivers/usb/wusbcore/dev-sysfs.c | 143 +++++++++++ drivers/usb/wusbcore/pal.c | 39 +++ drivers/usb/wusbcore/reservation.c | 115 +++++++++ drivers/usb/wusbcore/rh.c | 477 +++++++++++++++++++++++++++++++++++ drivers/usb/wusbcore/wusbhc.c | 416 +++++++++++++++++++++++++++++++ drivers/usb/wusbcore/wusbhc.h | 495 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1685 insertions(+) create mode 100644 drivers/usb/wusbcore/dev-sysfs.c create mode 100644 drivers/usb/wusbcore/pal.c create mode 100644 drivers/usb/wusbcore/reservation.c create mode 100644 drivers/usb/wusbcore/rh.c create mode 100644 drivers/usb/wusbcore/wusbhc.c create mode 100644 drivers/usb/wusbcore/wusbhc.h (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/dev-sysfs.c b/drivers/usb/wusbcore/dev-sysfs.c new file mode 100644 index 000000000000..7897a19652e5 --- /dev/null +++ b/drivers/usb/wusbcore/dev-sysfs.c @@ -0,0 +1,143 @@ +/* + * WUSB devices + * sysfs bindings + * + * Copyright (C) 2007 Intel Corporation + * Inaky Perez-Gonzalez + * + * 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 +#include +#include +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 4 +#include + +static ssize_t wusb_disconnect_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_device *usb_dev; + struct wusbhc *wusbhc; + unsigned command; + u8 port_idx; + + if (sscanf(buf, "%u", &command) != 1) + return -EINVAL; + if (command == 0) + return size; + usb_dev = to_usb_device(dev); + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return -ENODEV; + + mutex_lock(&wusbhc->mutex); + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + __wusbhc_dev_disable(wusbhc, port_idx); + mutex_unlock(&wusbhc->mutex); + wusbhc_put(wusbhc); + return size; +} +static DEVICE_ATTR(wusb_disconnect, 0200, NULL, wusb_disconnect_store); + +static ssize_t wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t result; + struct wusb_dev *wusb_dev; + + wusb_dev = wusb_dev_get_by_usb_dev(to_usb_device(dev)); + if (wusb_dev == NULL) + return -ENODEV; + result = ckhdid_printf(buf, PAGE_SIZE, &wusb_dev->cdid); + strcat(buf, "\n"); + wusb_dev_put(wusb_dev); + return result + 1; +} +static DEVICE_ATTR(wusb_cdid, 0444, wusb_cdid_show, NULL); + +static ssize_t wusb_ck_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int result; + struct usb_device *usb_dev; + struct wusbhc *wusbhc; + struct wusb_ckhdid ck; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &ck.data[0] , &ck.data[1], + &ck.data[2] , &ck.data[3], + &ck.data[4] , &ck.data[5], + &ck.data[6] , &ck.data[7], + &ck.data[8] , &ck.data[9], + &ck.data[10], &ck.data[11], + &ck.data[12], &ck.data[13], + &ck.data[14], &ck.data[15]); + if (result != 16) + return -EINVAL; + + usb_dev = to_usb_device(dev); + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return -ENODEV; + result = wusb_dev_4way_handshake(wusbhc, usb_dev->wusb_dev, &ck); + memset(&ck, 0, sizeof(ck)); + wusbhc_put(wusbhc); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_ck, 0200, NULL, wusb_ck_store); + +static struct attribute *wusb_dev_attrs[] = { + &dev_attr_wusb_disconnect.attr, + &dev_attr_wusb_cdid.attr, + &dev_attr_wusb_ck.attr, + NULL, +}; + +static struct attribute_group wusb_dev_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wusb_dev_attrs, +}; + +int wusb_dev_sysfs_add(struct wusbhc *wusbhc, struct usb_device *usb_dev, + struct wusb_dev *wusb_dev) +{ + int result = sysfs_create_group(&usb_dev->dev.kobj, + &wusb_dev_attr_group); + struct device *dev = &usb_dev->dev; + if (result < 0) + dev_err(dev, "Cannot register WUSB-dev attributes: %d\n", + result); + return result; +} + +void wusb_dev_sysfs_rm(struct wusb_dev *wusb_dev) +{ + struct usb_device *usb_dev = wusb_dev->usb_dev; + if (usb_dev) + sysfs_remove_group(&usb_dev->dev.kobj, &wusb_dev_attr_group); +} diff --git a/drivers/usb/wusbcore/pal.c b/drivers/usb/wusbcore/pal.c new file mode 100644 index 000000000000..cc126b444734 --- /dev/null +++ b/drivers/usb/wusbcore/pal.c @@ -0,0 +1,39 @@ +/* + * 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 . + */ +#include "wusbhc.h" + +/** + * wusbhc_pal_register - register the WUSB HC as a UWB PAL + * @wusbhc: the WUSB HC + */ +int wusbhc_pal_register(struct wusbhc *wusbhc) +{ + uwb_pal_init(&wusbhc->pal); + + return uwb_pal_register(wusbhc->uwb_rc, &wusbhc->pal); +} + +/** + * wusbhc_pal_register - unregister the WUSB HC as a UWB PAL + * @wusbhc: the WUSB HC + */ +void wusbhc_pal_unregister(struct wusbhc *wusbhc) +{ + uwb_pal_unregister(wusbhc->uwb_rc, &wusbhc->pal); +} diff --git a/drivers/usb/wusbcore/reservation.c b/drivers/usb/wusbcore/reservation.c new file mode 100644 index 000000000000..fc63e77ded2d --- /dev/null +++ b/drivers/usb/wusbcore/reservation.c @@ -0,0 +1,115 @@ +/* + * WUSB cluster reservation management + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include + +#include "wusbhc.h" + +/* + * WUSB cluster reservations are multicast reservations with the + * broadcast cluster ID (BCID) as the target DevAddr. + * + * FIXME: consider adjusting the reservation depending on what devices + * are attached. + */ + +static int wusbhc_bwa_set(struct wusbhc *wusbhc, u8 stream, + const struct uwb_mas_bm *mas) +{ + if (mas == NULL) + mas = &uwb_mas_bm_zero; + return wusbhc->bwa_set(wusbhc, stream, mas); +} + +/** + * wusbhc_rsv_complete_cb - WUSB HC reservation complete callback + * @rsv: the reservation + * + * Either set or clear the HC's view of the reservation. + * + * FIXME: when a reservation is denied the HC should be stopped. + */ +static void wusbhc_rsv_complete_cb(struct uwb_rsv *rsv) +{ + struct wusbhc *wusbhc = rsv->pal_priv; + struct device *dev = wusbhc->dev; + char buf[72]; + + switch (rsv->state) { + case UWB_RSV_STATE_O_ESTABLISHED: + bitmap_scnprintf(buf, sizeof(buf), rsv->mas.bm, UWB_NUM_MAS); + dev_dbg(dev, "established reservation: %s\n", buf); + wusbhc_bwa_set(wusbhc, rsv->stream, &rsv->mas); + break; + case UWB_RSV_STATE_NONE: + dev_dbg(dev, "removed reservation\n"); + wusbhc_bwa_set(wusbhc, 0, NULL); + wusbhc->rsv = NULL; + break; + default: + dev_dbg(dev, "unexpected reservation state: %d\n", rsv->state); + break; + } +} + + +/** + * wusbhc_rsv_establish - establish a reservation for the cluster + * @wusbhc: the WUSB HC requesting a bandwith reservation + */ +int wusbhc_rsv_establish(struct wusbhc *wusbhc) +{ + struct uwb_rc *rc = wusbhc->uwb_rc; + struct uwb_rsv *rsv; + struct uwb_dev_addr bcid; + int ret; + + rsv = uwb_rsv_create(rc, wusbhc_rsv_complete_cb, wusbhc); + if (rsv == NULL) + return -ENOMEM; + + bcid.data[0] = wusbhc->cluster_id; + bcid.data[1] = 0; + + rsv->owner = &rc->uwb_dev; + rsv->target.type = UWB_RSV_TARGET_DEVADDR; + rsv->target.devaddr = bcid; + rsv->type = UWB_DRP_TYPE_PRIVATE; + rsv->max_mas = 256; + rsv->min_mas = 16; /* one MAS per zone? */ + rsv->sparsity = 16; /* at least one MAS in each zone? */ + rsv->is_multicast = true; + + ret = uwb_rsv_establish(rsv); + if (ret == 0) + wusbhc->rsv = rsv; + else + uwb_rsv_destroy(rsv); + return ret; +} + + +/** + * wusbhc_rsv_terminate - terminate any cluster reservation + * @wusbhc: the WUSB host whose reservation is to be terminated + */ +void wusbhc_rsv_terminate(struct wusbhc *wusbhc) +{ + if (wusbhc->rsv) + uwb_rsv_terminate(wusbhc->rsv); +} diff --git a/drivers/usb/wusbcore/rh.c b/drivers/usb/wusbcore/rh.c new file mode 100644 index 000000000000..267a64325106 --- /dev/null +++ b/drivers/usb/wusbcore/rh.c @@ -0,0 +1,477 @@ +/* + * Wireless USB Host Controller + * Root Hub operations + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We fake a root hub that has fake ports (as many as simultaneous + * devices the Wireless USB Host Controller can deal with). For each + * port we keep an state in @wusbhc->port[index] identical to the one + * specified in the USB2.0[ch11] spec and some extra device + * information that complements the one in 'struct usb_device' (as + * this lacs a hcpriv pointer). + * + * Note this is common to WHCI and HWA host controllers. + * + * Through here we enable most of the state changes that the USB stack + * will use to connect or disconnect devices. We need to do some + * forced adaptation of Wireless USB device states vs. wired: + * + * USB: WUSB: + * + * Port Powered-off port slot n/a + * Powered-on port slot available + * Disconnected port slot available + * Connected port slot assigned device + * device sent DN_Connect + * device was authenticated + * Enabled device is authenticated, transitioned + * from unauth -> auth -> default address + * -> enabled + * Reset disconnect + * Disable disconnect + * + * This maps the standard USB port states with the WUSB device states + * so we can fake ports without having to modify the USB stack. + * + * FIXME: this process will change in the future + * + * + * ENTRY POINTS + * + * Our entry points into here are, as in hcd.c, the USB stack root hub + * ops defined in the usb_hcd struct: + * + * wusbhc_rh_status_data() Provide hub and port status data bitmap + * + * wusbhc_rh_control() Execution of all the major requests + * you can do to a hub (Set|Clear + * features, get descriptors, status, etc). + * + * wusbhc_rh_[suspend|resume]() That + * + * wusbhc_rh_start_port_reset() ??? unimplemented + */ +#include "wusbhc.h" + +#define D_LOCAL 0 +#include + +/* + * Reset a fake port + * + * This can be called to reset a port from any other state or to reset + * it when connecting. In Wireless USB they are different; when doing + * a new connect that involves going over the authentication. When + * just reseting, its a different story. + * + * The Linux USB stack resets a port twice before it considers it + * enabled, so we have to detect and ignore that. + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Supposedly we are the only thread accesing @wusbhc->port; in any + * case, maybe we should move the mutex locking from + * wusbhc_devconnect_auth() to here. + * + * @port_idx refers to the wusbhc's port index, not the USB port number + */ +static int wusbhc_rh_port_reset(struct wusbhc *wusbhc, u8 port_idx) +{ + int result = 0; + struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx); + + d_fnstart(3, wusbhc->dev, "(wusbhc %p port_idx %u)\n", + wusbhc, port_idx); + if (port->reset_count == 0) { + wusbhc_devconnect_auth(wusbhc, port_idx); + port->reset_count++; + } else if (port->reset_count == 1) + /* see header */ + d_printf(2, wusbhc->dev, "Ignoring second reset on port_idx " + "%u\n", port_idx); + else + result = wusbhc_dev_reset(wusbhc, port_idx); + d_fnend(3, wusbhc->dev, "(wusbhc %p port_idx %u) = %d\n", + wusbhc, port_idx, result); + return result; +} + +/* + * Return the hub change status bitmap + * + * The bits in the change status bitmap are cleared when a + * ClearPortFeature request is issued (USB2.0[11.12.3,11.12.4]. + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * WARNING!! This gets called from atomic context; we cannot get the + * mutex--the only race condition we can find is some bit + * changing just after we copy it, which shouldn't be too + * big of a problem [and we can't make it an spinlock + * because other parts need to take it and sleep] . + * + * @usb_hcd is refcounted, so it won't dissapear under us + * and before killing a host, the polling of the root hub + * would be stopped anyway. + */ +int wusbhc_rh_status_data(struct usb_hcd *usb_hcd, char *_buf) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + size_t cnt, size; + unsigned long *buf = (unsigned long *) _buf; + + d_fnstart(1, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + /* WE DON'T LOCK, see comment */ + size = wusbhc->ports_max + 1 /* hub bit */; + size = (size + 8 - 1) / 8; /* round to bytes */ + for (cnt = 0; cnt < wusbhc->ports_max; cnt++) + if (wusb_port_by_idx(wusbhc, cnt)->change) + set_bit(cnt + 1, buf); + else + clear_bit(cnt + 1, buf); + d_fnend(1, wusbhc->dev, "(wusbhc %p) %u, buffer:\n", wusbhc, (int)size); + d_dump(1, wusbhc->dev, _buf, size); + return size; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_status_data); + +/* + * Return the hub's desciptor + * + * NOTE: almost cut and paste from ehci-hub.c + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked + */ +static int wusbhc_rh_get_hub_descr(struct wusbhc *wusbhc, u16 wValue, + u16 wIndex, + struct usb_hub_descriptor *descr, + u16 wLength) +{ + u16 temp = 1 + (wusbhc->ports_max / 8); + u8 length = 7 + 2 * temp; + + if (wLength < length) + return -ENOSPC; + descr->bDescLength = 7 + 2 * temp; + descr->bDescriptorType = 0x29; /* HUB type */ + descr->bNbrPorts = wusbhc->ports_max; + descr->wHubCharacteristics = cpu_to_le16( + 0x00 /* All ports power at once */ + | 0x00 /* not part of compound device */ + | 0x10 /* No overcurrent protection */ + | 0x00 /* 8 FS think time FIXME ?? */ + | 0x00); /* No port indicators */ + descr->bPwrOn2PwrGood = 0; + descr->bHubContrCurrent = 0; + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&descr->bitmap[0], 0, temp); + memset(&descr->bitmap[temp], 0xff, temp); + return 0; +} + +/* + * Clear a hub feature + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Nothing to do, so no locking needed ;) + */ +static int wusbhc_rh_clear_hub_feat(struct wusbhc *wusbhc, u16 feature) +{ + int result; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(%p, feature 0x%04u)\n", wusbhc, feature); + switch (feature) { + case C_HUB_LOCAL_POWER: + /* FIXME: maybe plug bit 0 to the power input status, + * if any? + * see wusbhc_rh_get_hub_status() */ + case C_HUB_OVER_CURRENT: + result = 0; + break; + default: + result = -EPIPE; + } + d_fnend(4, dev, "(%p, feature 0x%04u), %d\n", wusbhc, feature, result); + return result; +} + +/* + * Return hub status (it is always zero...) + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Nothing to do, so no locking needed ;) + */ +static int wusbhc_rh_get_hub_status(struct wusbhc *wusbhc, u32 *buf, + u16 wLength) +{ + /* FIXME: maybe plug bit 0 to the power input status (if any)? */ + *buf = 0; + return 0; +} + +/* + * Set a port feature + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_set_port_feat(struct wusbhc *wusbhc, u16 feature, + u8 selector, u8 port_idx) +{ + int result = -EINVAL; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d)\n", + feature, selector, port_idx); + + if (port_idx > wusbhc->ports_max) + goto error; + + switch (feature) { + /* According to USB2.0[11.24.2.13]p2, these features + * are not required to be implemented. */ + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_RESET: + result = 0; + break; + + case USB_PORT_FEAT_POWER: + /* No such thing, but we fake it works */ + mutex_lock(&wusbhc->mutex); + wusb_port_by_idx(wusbhc, port_idx)->status |= USB_PORT_STAT_POWER; + mutex_unlock(&wusbhc->mutex); + result = 0; + break; + case USB_PORT_FEAT_RESET: + result = wusbhc_rh_port_reset(wusbhc, port_idx); + break; + case USB_PORT_FEAT_ENABLE: + case USB_PORT_FEAT_SUSPEND: + dev_err(dev, "(port_idx %d) set feat %d/%d UNIMPLEMENTED\n", + port_idx, feature, selector); + result = -ENOSYS; + break; + default: + dev_err(dev, "(port_idx %d) set feat %d/%d UNKNOWN\n", + port_idx, feature, selector); + result = -EPIPE; + break; + } +error: + d_fnend(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d) = %d\n", + feature, selector, port_idx, result); + return result; +} + +/* + * Clear a port feature... + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_clear_port_feat(struct wusbhc *wusbhc, u16 feature, + u8 selector, u8 port_idx) +{ + int result = -EINVAL; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d)\n", + wusbhc, feature, selector, port_idx); + + if (port_idx > wusbhc->ports_max) + goto error; + + mutex_lock(&wusbhc->mutex); + result = 0; + switch (feature) { + case USB_PORT_FEAT_POWER: /* fake port always on */ + /* According to USB2.0[11.24.2.7.1.4], no need to implement? */ + case USB_PORT_FEAT_C_OVER_CURRENT: + break; + case USB_PORT_FEAT_C_RESET: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_RESET; + break; + case USB_PORT_FEAT_C_CONNECTION: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_CONNECTION; + break; + case USB_PORT_FEAT_ENABLE: + __wusbhc_dev_disable(wusbhc, port_idx); + break; + case USB_PORT_FEAT_C_ENABLE: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_ENABLE; + break; + case USB_PORT_FEAT_SUSPEND: + case USB_PORT_FEAT_C_SUSPEND: + case 0xffff: /* ??? FIXME */ + dev_err(dev, "(port_idx %d) Clear feat %d/%d UNIMPLEMENTED\n", + port_idx, feature, selector); + /* dump_stack(); */ + result = -ENOSYS; + break; + default: + dev_err(dev, "(port_idx %d) Clear feat %d/%d UNKNOWN\n", + port_idx, feature, selector); + result = -EPIPE; + break; + } + mutex_unlock(&wusbhc->mutex); +error: + d_fnend(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d) = " + "%d\n", wusbhc, feature, selector, port_idx, result); + return result; +} + +/* + * Return the port's status + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_get_port_status(struct wusbhc *wusbhc, u16 port_idx, + u32 *_buf, u16 wLength) +{ + int result = -EINVAL; + u16 *buf = (u16 *) _buf; + + d_fnstart(1, wusbhc->dev, "(wusbhc %p port_idx %u wLength %u)\n", + wusbhc, port_idx, wLength); + if (port_idx > wusbhc->ports_max) + goto error; + mutex_lock(&wusbhc->mutex); + buf[0] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->status); + buf[1] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->change); + result = 0; + mutex_unlock(&wusbhc->mutex); +error: + d_fnend(1, wusbhc->dev, "(wusbhc %p) = %d, buffer:\n", wusbhc, result); + d_dump(1, wusbhc->dev, _buf, wLength); + return result; +} + +/* + * Entry point for Root Hub operations + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +int wusbhc_rh_control(struct usb_hcd *usb_hcd, u16 reqntype, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + int result = -ENOSYS; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + + switch (reqntype) { + case GetHubDescriptor: + result = wusbhc_rh_get_hub_descr( + wusbhc, wValue, wIndex, + (struct usb_hub_descriptor *) buf, wLength); + break; + case ClearHubFeature: + result = wusbhc_rh_clear_hub_feat(wusbhc, wValue); + break; + case GetHubStatus: + result = wusbhc_rh_get_hub_status(wusbhc, (u32 *)buf, wLength); + break; + + case SetPortFeature: + result = wusbhc_rh_set_port_feat(wusbhc, wValue, wIndex >> 8, + (wIndex & 0xff) - 1); + break; + case ClearPortFeature: + result = wusbhc_rh_clear_port_feat(wusbhc, wValue, wIndex >> 8, + (wIndex & 0xff) - 1); + break; + case GetPortStatus: + result = wusbhc_rh_get_port_status(wusbhc, wIndex - 1, + (u32 *)buf, wLength); + break; + + case SetHubFeature: + default: + dev_err(wusbhc->dev, "%s (%p [%p], %x, %x, %x, %p, %x) " + "UNIMPLEMENTED\n", __func__, usb_hcd, wusbhc, reqntype, + wValue, wIndex, buf, wLength); + /* dump_stack(); */ + result = -ENOSYS; + } + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_control); + +int wusbhc_rh_suspend(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, wusbhc); + /* dump_stack(); */ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_suspend); + +int wusbhc_rh_resume(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, wusbhc); + /* dump_stack(); */ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_resume); + +int wusbhc_rh_start_port_reset(struct usb_hcd *usb_hcd, unsigned port_idx) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p], port_idx %u) UNIMPLEMENTED\n", + __func__, usb_hcd, wusbhc, port_idx); + WARN_ON(1); + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_start_port_reset); + +static void wusb_port_init(struct wusb_port *port) +{ + port->status |= USB_PORT_STAT_HIGH_SPEED; +} + +/* + * Alloc fake port specific fields and status. + */ +int wusbhc_rh_create(struct wusbhc *wusbhc) +{ + int result = -ENOMEM; + size_t port_size, itr; + port_size = wusbhc->ports_max * sizeof(wusbhc->port[0]); + wusbhc->port = kzalloc(port_size, GFP_KERNEL); + if (wusbhc->port == NULL) + goto error_port_alloc; + for (itr = 0; itr < wusbhc->ports_max; itr++) + wusb_port_init(&wusbhc->port[itr]); + result = 0; +error_port_alloc: + return result; +} + +void wusbhc_rh_destroy(struct wusbhc *wusbhc) +{ + kfree(wusbhc->port); +} diff --git a/drivers/usb/wusbcore/wusbhc.c b/drivers/usb/wusbcore/wusbhc.c new file mode 100644 index 000000000000..1149b1e59c86 --- /dev/null +++ b/drivers/usb/wusbcore/wusbhc.c @@ -0,0 +1,416 @@ +/* + * Wireless USB Host Controller + * sysfs glue, wusbcore module support and life cycle management + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Creation/destruction of wusbhc is split in two parts; that that + * doesn't require the HCD to be added (wusbhc_{create,destroy}) and + * the one that requires (phase B, wusbhc_b_{create,destroy}). + * + * This is so because usb_add_hcd() will start the HC, and thus, all + * the HC specific stuff has to be already initialiazed (like sysfs + * thingies). + */ +#include +#include +#include "wusbhc.h" + +/** + * Extract the wusbhc that corresponds to a USB Host Controller class device + * + * WARNING! Apply only if @dev is that of a + * wusbhc.usb_hcd.self->class_dev; otherwise, you loose. + */ +static struct wusbhc *usbhc_dev_to_wusbhc(struct device *dev) +{ + struct usb_bus *usb_bus = dev_get_drvdata(dev); + struct usb_hcd *usb_hcd = bus_to_hcd(usb_bus); + return usb_hcd_to_wusbhc(usb_hcd); +} + +/* + * Show & store the current WUSB trust timeout + * + * We don't do locking--it is an 'atomic' value. + * + * The units that we store/show are always MILLISECONDS. However, the + * value of trust_timeout is jiffies. + */ +static ssize_t wusb_trust_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", wusbhc->trust_timeout); +} + +static ssize_t wusb_trust_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + ssize_t result = -ENOSYS; + unsigned trust_timeout; + + result = sscanf(buf, "%u", &trust_timeout); + if (result != 1) { + result = -EINVAL; + goto out; + } + /* FIXME: maybe we should check for range validity? */ + wusbhc->trust_timeout = trust_timeout; + cancel_delayed_work(&wusbhc->keep_alive_timer); + flush_workqueue(wusbd); + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (trust_timeout * CONFIG_HZ)/1000/2); +out: + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_trust_timeout, 0644, wusb_trust_timeout_show, + wusb_trust_timeout_store); + +/* + * Show & store the current WUSB CHID + */ +static ssize_t wusb_chid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + ssize_t result = 0; + + if (wusbhc->wuie_host_info != NULL) + result += ckhdid_printf(buf, PAGE_SIZE, + &wusbhc->wuie_host_info->CHID); + return result; +} + +/* + * Store a new CHID + * + * This will (FIXME) trigger many changes. + * + * - Send an all zeros CHID and it will stop the controller + * - Send a non-zero CHID and it will start it + * (unless it was started, it will just change the CHID, + * diconnecting all devices first). + * + * So first we scan the MMC we are sent and then we act on it. We + * read it in the same format as we print it, an ASCII string of 16 + * hex bytes. + * + * See wusbhc_chid_set() for more info. + */ +static ssize_t wusb_chid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + struct wusb_ckhdid chid; + ssize_t result; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &chid.data[0] , &chid.data[1] , + &chid.data[2] , &chid.data[3] , + &chid.data[4] , &chid.data[5] , + &chid.data[6] , &chid.data[7] , + &chid.data[8] , &chid.data[9] , + &chid.data[10], &chid.data[11], + &chid.data[12], &chid.data[13], + &chid.data[14], &chid.data[15]); + if (result != 16) { + dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits): " + "%d\n", (int)result); + return -EINVAL; + } + result = wusbhc_chid_set(wusbhc, &chid); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_chid, 0644, wusb_chid_show, wusb_chid_store); + +/* Group all the WUSBHC attributes */ +static struct attribute *wusbhc_attrs[] = { + &dev_attr_wusb_trust_timeout.attr, + &dev_attr_wusb_chid.attr, + NULL, +}; + +static struct attribute_group wusbhc_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wusbhc_attrs, +}; + +/* + * Create a wusbhc instance + * + * NOTEs: + * + * - assumes *wusbhc has been zeroed and wusbhc->usb_hcd has been + * initialized but not added. + * + * - fill out ports_max, mmcies_max and mmcie_{add,rm} before calling. + * + * - fill out wusbhc->uwb_rc and refcount it before calling + * - fill out the wusbhc->sec_modes array + */ +int wusbhc_create(struct wusbhc *wusbhc) +{ + int result = 0; + + wusbhc->trust_timeout = WUSB_TRUST_TIMEOUT_MS; + mutex_init(&wusbhc->mutex); + result = wusbhc_mmcie_create(wusbhc); + if (result < 0) + goto error_mmcie_create; + result = wusbhc_devconnect_create(wusbhc); + if (result < 0) + goto error_devconnect_create; + result = wusbhc_rh_create(wusbhc); + if (result < 0) + goto error_rh_create; + result = wusbhc_sec_create(wusbhc); + if (result < 0) + goto error_sec_create; + result = wusbhc_pal_register(wusbhc); + if (result < 0) + goto error_pal_register; + return 0; + +error_pal_register: + wusbhc_sec_destroy(wusbhc); +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; + } + /* Yep, I plan to add stuff here... */ +error_create_attr_group: + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_b_create); + +void wusbhc_b_destroy(struct wusbhc *wusbhc) +{ + sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); +} +EXPORT_SYMBOL_GPL(wusbhc_b_destroy); + +void wusbhc_destroy(struct wusbhc *wusbhc) +{ + wusbhc_pal_unregister(wusbhc); + wusbhc_sec_destroy(wusbhc); + wusbhc_rh_destroy(wusbhc); + wusbhc_devconnect_destroy(wusbhc); + wusbhc_mmcie_destroy(wusbhc); +} +EXPORT_SYMBOL_GPL(wusbhc_destroy); + +struct workqueue_struct *wusbd; +EXPORT_SYMBOL_GPL(wusbd); + +/* + * WUSB Cluster ID allocation map + * + * Each WUSB bus in a channel is identified with a Cluster Id in the + * unauth address pace (WUSB1.0[4.3]). We take the range 0xe0 to 0xff + * (that's space for 31 WUSB controllers, as 0xff can't be taken). We + * start taking from 0xff, 0xfe, 0xfd... (hence the += or -= 0xff). + * + * For each one we taken, we pin it in the bitap + */ +#define CLUSTER_IDS 32 +static DECLARE_BITMAP(wusb_cluster_id_table, CLUSTER_IDS); +static DEFINE_SPINLOCK(wusb_cluster_ids_lock); + +/* + * Get a WUSB Cluster ID + * + * Need to release with wusb_cluster_id_put() when done w/ it. + */ +/* FIXME: coordinate with the choose_addres() from the USB stack */ +/* we want to leave the top of the 128 range for cluster addresses and + * the bottom for device addresses (as we map them one on one with + * ports). */ +u8 wusb_cluster_id_get(void) +{ + u8 id; + spin_lock(&wusb_cluster_ids_lock); + id = find_first_zero_bit(wusb_cluster_id_table, CLUSTER_IDS); + if (id > CLUSTER_IDS) { + id = 0; + goto out; + } + set_bit(id, wusb_cluster_id_table); + id = (u8) 0xff - id; +out: + spin_unlock(&wusb_cluster_ids_lock); + return id; + +} +EXPORT_SYMBOL_GPL(wusb_cluster_id_get); + +/* + * Release a WUSB Cluster ID + * + * Obtained it with wusb_cluster_id_get() + */ +void wusb_cluster_id_put(u8 id) +{ + id = 0xff - id; + BUG_ON(id >= CLUSTER_IDS); + spin_lock(&wusb_cluster_ids_lock); + WARN_ON(!test_bit(id, wusb_cluster_id_table)); + clear_bit(id, wusb_cluster_id_table); + spin_unlock(&wusb_cluster_ids_lock); +} +EXPORT_SYMBOL_GPL(wusb_cluster_id_put); + +/** + * wusbhc_giveback_urb - return an URB to the USB core + * @wusbhc: the host controller the URB is from. + * @urb: the URB. + * @status: the URB's status. + * + * Return an URB to the USB core doing some additional WUSB specific + * processing. + * + * - After a successful transfer, update the trust timeout timestamp + * for the WUSB device. + * + * - [WUSB] sections 4.13 and 7.5.1 specifies the stop retrasmittion + * condition for the WCONNECTACK_IE is that the host has observed + * the associated device responding to a control transfer. + */ +void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, int status) +{ + struct wusb_dev *wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); + + if (status == 0) { + wusb_dev->entry_ts = jiffies; + + /* wusbhc_devconnect_acked() can't be called from from + atomic context so defer it to a work queue. */ + if (!list_empty(&wusb_dev->cack_node)) + queue_work(wusbd, &wusb_dev->devconnect_acked_work); + } + + usb_hcd_giveback_urb(&wusbhc->usb_hcd, urb, status); +} +EXPORT_SYMBOL_GPL(wusbhc_giveback_urb); + +/** + * wusbhc_reset_all - reset the HC hardware + * @wusbhc: the host controller to reset. + * + * Request a full hardware reset of the chip. This will also reset + * the radio controller and any other PALs. + */ +void wusbhc_reset_all(struct wusbhc *wusbhc) +{ + uwb_rc_reset_all(wusbhc->uwb_rc); +} +EXPORT_SYMBOL_GPL(wusbhc_reset_all); + +static struct notifier_block wusb_usb_notifier = { + .notifier_call = wusb_usb_ncb, + .priority = INT_MAX /* Need to be called first of all */ +}; + +static int __init wusbcore_init(void) +{ + int result; + result = wusb_crypto_init(); + if (result < 0) + goto error_crypto_init; + /* WQ is singlethread because we need to serialize notifications */ + wusbd = create_singlethread_workqueue("wusbd"); + if (wusbd == NULL) { + result = -ENOMEM; + printk(KERN_ERR "WUSB-core: Cannot create wusbd workqueue\n"); + goto error_wusbd_create; + } + usb_register_notify(&wusb_usb_notifier); + bitmap_zero(wusb_cluster_id_table, CLUSTER_IDS); + set_bit(0, wusb_cluster_id_table); /* reserve Cluster ID 0xff */ + return 0; + +error_wusbd_create: + wusb_crypto_exit(); +error_crypto_init: + return result; + +} +module_init(wusbcore_init); + +static void __exit wusbcore_exit(void) +{ + clear_bit(0, wusb_cluster_id_table); + if (!bitmap_empty(wusb_cluster_id_table, CLUSTER_IDS)) { + char buf[256]; + bitmap_scnprintf(buf, sizeof(buf), wusb_cluster_id_table, + CLUSTER_IDS); + printk(KERN_ERR "BUG: WUSB Cluster IDs not released " + "on exit: %s\n", buf); + WARN_ON(1); + } + usb_unregister_notify(&wusb_usb_notifier); + destroy_workqueue(wusbd); + wusb_crypto_exit(); +} +module_exit(wusbcore_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/wusbhc.h b/drivers/usb/wusbcore/wusbhc.h new file mode 100644 index 000000000000..d0c132434f1b --- /dev/null +++ b/drivers/usb/wusbcore/wusbhc.h @@ -0,0 +1,495 @@ +/* + * Wireless USB Host Controller + * Common infrastructure for WHCI and HWA WUSB-HC drivers + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * 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 +#include +#include +#include +#include +/* FIXME: Yes, I know: BAD--it's not my fault the USB HC iface is not + * public */ +#include +#include +#include + + +/** + * Wireless USB device + * + * Describe a WUSB device connected to the cluster. This struct + * belongs to the 'struct wusb_port' it is attached to and it is + * responsible for putting and clearing the pointer to it. + * + * Note this "complements" the 'struct usb_device' that the usb_hcd + * keeps for each connected USB device. However, it extends some + * information that is not available (there is no hcpriv ptr in it!) + * *and* most importantly, it's life cycle is different. It is created + * as soon as we get a DN_Connect (connect request notification) from + * the device through the WUSB host controller; the USB stack doesn't + * create the device until we authenticate it. FIXME: this will + * change. + * + * @bos: This is allocated when the BOS descriptors are read from + * the device and freed upon the wusb_dev struct dying. + * @wusb_cap_descr: points into @bos, and has been verified to be size + * safe. + */ +struct wusb_dev { + struct kref refcnt; + struct wusbhc *wusbhc; + struct list_head cack_node; /* Connect-Ack list */ + u8 port_idx; + u8 addr; + u8 beacon_type:4; + struct usb_encryption_descriptor ccm1_etd; + struct wusb_ckhdid cdid; + unsigned long entry_ts; + struct usb_bos_descriptor *bos; + struct usb_wireless_cap_descriptor *wusb_cap_descr; + struct uwb_mas_bm availability; + struct work_struct devconnect_acked_work; + struct urb *set_gtk_urb; + struct usb_ctrlrequest *set_gtk_req; + struct usb_device *usb_dev; +}; + +#define WUSB_DEV_ADDR_UNAUTH 0x80 + +static inline void wusb_dev_init(struct wusb_dev *wusb_dev) +{ + kref_init(&wusb_dev->refcnt); + /* no need to init the cack_node */ +} + +extern void wusb_dev_destroy(struct kref *_wusb_dev); + +static inline struct wusb_dev *wusb_dev_get(struct wusb_dev *wusb_dev) +{ + kref_get(&wusb_dev->refcnt); + return wusb_dev; +} + +static inline void wusb_dev_put(struct wusb_dev *wusb_dev) +{ + kref_put(&wusb_dev->refcnt, wusb_dev_destroy); +} + +/** + * Wireless USB Host Controlller root hub "fake" ports + * (state and device information) + * + * Wireless USB is wireless, so there are no ports; but we + * fake'em. Each RC can connect a max of devices at the same time + * (given in the Wireless Adapter descriptor, bNumPorts or WHCI's + * caps), referred to in wusbhc->ports_max. + * + * See rh.c for more information. + * + * The @status and @change use the same bits as in USB2.0[11.24.2.7], + * so we don't have to do much when getting the port's status. + * + * WUSB1.0[7.1], USB2.0[11.24.2.7.1,fig 11-10], + * include/linux/usb_ch9.h (#define USB_PORT_STAT_*) + */ +struct wusb_port { + u16 status; + u16 change; + struct wusb_dev *wusb_dev; /* connected device's info */ + unsigned reset_count; + u32 ptk_tkid; +}; + +/** + * WUSB Host Controller specifics + * + * All fields that are common to all Wireless USB controller types + * (HWA and WHCI) are grouped here. Host Controller + * functions/operations that only deal with general Wireless USB HC + * issues use this data type to refer to the host. + * + * @usb_hcd Instantiation of a USB host controller + * (initialized by upper layer [HWA=HC or WHCI]. + * + * @dev Device that implements this; initialized by the + * upper layer (HWA-HC, WHCI...); this device should + * have a refcount. + * + * @trust_timeout After this time without hearing for device + * activity, we consider the device gone and we have to + * re-authenticate. + * + * Can be accessed w/o locking--however, read to a + * local variable then use. + * + * @chid WUSB Cluster Host ID: this is supposed to be a + * unique value that doesn't change across reboots (so + * that your devices do not require re-association). + * + * Read/Write protected by @mutex + * + * @dev_info This array has ports_max elements. It is used to + * give the HC information about the WUSB devices (see + * 'struct wusb_dev_info'). + * + * For HWA we need to allocate it in heap; for WHCI it + * needs to be permanently mapped, so we keep it for + * both and make it easy. Call wusbhc->dev_info_set() + * to update an entry. + * + * @ports_max Number of simultaneous device connections (fake + * ports) this HC will take. Read-only. + * + * @port Array of port status for each fake root port. Guaranteed to + * always be the same lenght during device existence + * [this allows for some unlocked but referenced reading]. + * + * @mmcies_max Max number of Information Elements this HC can send + * in its MMC. Read-only. + * + * @mmcie_add HC specific operation (WHCI or HWA) for adding an + * MMCIE. + * + * @mmcie_rm HC specific operation (WHCI or HWA) for removing an + * MMCIE. + * + * @enc_types Array which describes the encryptions methods + * supported by the host as described in WUSB1.0 -- + * one entry per supported method. As of WUSB1.0 there + * is only four methods, we make space for eight just in + * case they decide to add some more (and pray they do + * it in sequential order). if 'enc_types[enc_method] + * != 0', then it is supported by the host. enc_method + * is USB_ENC_TYPE*. + * + * @set_ptk: Set the PTK and enable encryption for a device. Or, if + * the supplied key is NULL, disable encryption for that + * device. + * + * @set_gtk: Set the GTK to be used for all future broadcast packets + * (i.e., MMCs). With some hardware, setting the GTK may start + * MMC transmission. + * + * NOTE: + * + * - If wusb_dev->usb_dev is not NULL, then usb_dev is valid + * (wusb_dev has a refcount on it). Likewise, if usb_dev->wusb_dev + * is not NULL, usb_dev->wusb_dev is valid (usb_dev keeps a + * refcount on it). + * + * Most of the times when you need to use it, it will be non-NULL, + * so there is no real need to check for it (wusb_dev will + * dissapear before usb_dev). + * + * - The following fields need to be filled out before calling + * wusbhc_create(): ports_max, mmcies_max, mmcie_{add,rm}. + * + * - there is no wusbhc_init() method, we do everything in + * wusbhc_create(). + * + * - Creation is done in two phases, wusbhc_create() and + * wusbhc_create_b(); b are the parts that need to be called after + * calling usb_hcd_add(&wusbhc->usb_hcd). + */ +struct wusbhc { + struct usb_hcd usb_hcd; /* HAS TO BE 1st */ + struct device *dev; + struct uwb_rc *uwb_rc; + struct uwb_pal pal; + + unsigned trust_timeout; /* in jiffies */ + struct wuie_host_info *wuie_host_info; /* Includes CHID */ + + struct mutex mutex; /* locks everything else */ + u16 cluster_id; /* Wireless USB Cluster ID */ + struct wusb_port *port; /* Fake port status handling */ + struct wusb_dev_info *dev_info; /* for Set Device Info mgmt */ + u8 ports_max; + unsigned active:1; /* currently xmit'ing MMCs */ + struct wuie_keep_alive keep_alive_ie; /* protected by mutex */ + struct delayed_work keep_alive_timer; + struct list_head cack_list; /* Connect acknowledging */ + size_t cack_count; /* protected by 'mutex' */ + struct wuie_connect_ack cack_ie; + struct uwb_rsv *rsv; /* cluster bandwidth reservation */ + + struct mutex mmcie_mutex; /* MMC WUIE handling */ + struct wuie_hdr **mmcie; /* WUIE array */ + u8 mmcies_max; + /* FIXME: make wusbhc_ops? */ + int (*start)(struct wusbhc *wusbhc); + void (*stop)(struct wusbhc *wusbhc); + int (*mmcie_add)(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie); + int (*mmcie_rm)(struct wusbhc *wusbhc, u8 handle); + int (*dev_info_set)(struct wusbhc *, struct wusb_dev *wusb_dev); + int (*bwa_set)(struct wusbhc *wusbhc, s8 stream_index, + const struct uwb_mas_bm *); + int (*set_ptk)(struct wusbhc *wusbhc, u8 port_idx, + u32 tkid, const void *key, size_t key_size); + int (*set_gtk)(struct wusbhc *wusbhc, + u32 tkid, const void *key, size_t key_size); + int (*set_num_dnts)(struct wusbhc *wusbhc, u8 interval, u8 slots); + + struct { + struct usb_key_descriptor descr; + u8 data[16]; /* GTK key data */ + } __attribute__((packed)) gtk; + u8 gtk_index; + u32 gtk_tkid; + struct work_struct gtk_rekey_done_work; + int pending_set_gtks; + + struct usb_encryption_descriptor *ccm1_etd; +}; + +#define usb_hcd_to_wusbhc(u) container_of((u), struct wusbhc, usb_hcd) + + +extern int wusbhc_create(struct wusbhc *); +extern int wusbhc_b_create(struct wusbhc *); +extern void wusbhc_b_destroy(struct wusbhc *); +extern void wusbhc_destroy(struct wusbhc *); +extern int wusb_dev_sysfs_add(struct wusbhc *, struct usb_device *, + struct wusb_dev *); +extern void wusb_dev_sysfs_rm(struct wusb_dev *); +extern int wusbhc_sec_create(struct wusbhc *); +extern int wusbhc_sec_start(struct wusbhc *); +extern void wusbhc_sec_stop(struct wusbhc *); +extern void wusbhc_sec_destroy(struct wusbhc *); +extern void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, + int status); +void wusbhc_reset_all(struct wusbhc *wusbhc); + +int wusbhc_pal_register(struct wusbhc *wusbhc); +void wusbhc_pal_unregister(struct wusbhc *wusbhc); + +/* + * Return @usb_dev's @usb_hcd (properly referenced) or NULL if gone + * + * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr) + * + * This is a safe assumption as @usb_dev->bus is referenced all the + * time during the @usb_dev life cycle. + */ +static inline struct usb_hcd *usb_hcd_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct usb_hcd *usb_hcd; + usb_hcd = container_of(usb_dev->bus, struct usb_hcd, self); + return usb_get_hcd(usb_hcd); +} + +/* + * Increment the reference count on a wusbhc. + * + * @wusbhc's life cycle is identical to that of the underlying usb_hcd. + */ +static inline struct wusbhc *wusbhc_get(struct wusbhc *wusbhc) +{ + return usb_get_hcd(&wusbhc->usb_hcd) ? wusbhc : NULL; +} + +/* + * Return the wusbhc associated to a @usb_dev + * + * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr) + * + * @returns: wusbhc for @usb_dev; NULL if the @usb_dev is being torn down. + * WARNING: referenced at the usb_hcd level, unlocked + * + * FIXME: move offline + */ +static inline struct wusbhc *wusbhc_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct wusbhc *wusbhc = NULL; + struct usb_hcd *usb_hcd; + if (usb_dev->devnum > 1 && !usb_dev->wusb) { + /* but root hubs */ + dev_err(&usb_dev->dev, "devnum %d wusb %d\n", usb_dev->devnum, + usb_dev->wusb); + BUG_ON(usb_dev->devnum > 1 && !usb_dev->wusb); + } + usb_hcd = usb_hcd_get_by_usb_dev(usb_dev); + if (usb_hcd == NULL) + return NULL; + BUG_ON(usb_hcd->wireless == 0); + return wusbhc = usb_hcd_to_wusbhc(usb_hcd); +} + + +static inline void wusbhc_put(struct wusbhc *wusbhc) +{ + usb_put_hcd(&wusbhc->usb_hcd); +} + +int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid); +void wusbhc_stop(struct wusbhc *wusbhc); +extern int wusbhc_chid_set(struct wusbhc *, const struct wusb_ckhdid *); + +/* Device connect handling */ +extern int wusbhc_devconnect_create(struct wusbhc *); +extern void wusbhc_devconnect_destroy(struct wusbhc *); +extern int wusbhc_devconnect_start(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid); +extern void wusbhc_devconnect_stop(struct wusbhc *wusbhc); +extern int wusbhc_devconnect_auth(struct wusbhc *, u8); +extern void wusbhc_handle_dn(struct wusbhc *, u8 srcaddr, + struct wusb_dn_hdr *dn_hdr, size_t size); +extern int wusbhc_dev_reset(struct wusbhc *wusbhc, u8 port); +extern void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port); +extern int wusb_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv); +extern int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, + u8 addr); + +/* Wireless USB fake Root Hub methods */ +extern int wusbhc_rh_create(struct wusbhc *); +extern void wusbhc_rh_destroy(struct wusbhc *); + +extern int wusbhc_rh_status_data(struct usb_hcd *, char *); +extern int wusbhc_rh_control(struct usb_hcd *, u16, u16, u16, char *, u16); +extern int wusbhc_rh_suspend(struct usb_hcd *); +extern int wusbhc_rh_resume(struct usb_hcd *); +extern int wusbhc_rh_start_port_reset(struct usb_hcd *, unsigned); + +/* MMC handling */ +extern int wusbhc_mmcie_create(struct wusbhc *); +extern void wusbhc_mmcie_destroy(struct wusbhc *); +extern int wusbhc_mmcie_set(struct wusbhc *, u8 interval, u8 repeat_cnt, + struct wuie_hdr *); +extern void wusbhc_mmcie_rm(struct wusbhc *, struct wuie_hdr *); + +/* Bandwidth reservation */ +int wusbhc_rsv_establish(struct wusbhc *wusbhc); +void wusbhc_rsv_terminate(struct wusbhc *wusbhc); + +/* + * I've always said + * I wanted a wedding in a church... + * + * but lately I've been thinking about + * the Botanical Gardens. + * + * We could do it by the tulips. + * It'll be beautiful + * + * --Security! + */ +extern int wusb_dev_sec_add(struct wusbhc *, struct usb_device *, + struct wusb_dev *); +extern void wusb_dev_sec_rm(struct wusb_dev *) ; +extern int wusb_dev_4way_handshake(struct wusbhc *, struct wusb_dev *, + struct wusb_ckhdid *ck); +void wusbhc_gtk_rekey(struct wusbhc *wusbhc); + + +/* WUSB Cluster ID handling */ +extern u8 wusb_cluster_id_get(void); +extern void wusb_cluster_id_put(u8); + +/* + * wusb_port_by_idx - return the port associated to a zero-based port index + * + * NOTE: valid without locking as long as wusbhc is referenced (as the + * number of ports doesn't change). The data pointed to has to + * be verified though :) + */ +static inline struct wusb_port *wusb_port_by_idx(struct wusbhc *wusbhc, + u8 port_idx) +{ + return &wusbhc->port[port_idx]; +} + +/* + * wusb_port_no_to_idx - Convert port number (per usb_dev->portnum) to + * a port_idx. + * + * USB stack USB ports are 1 based!! + * + * NOTE: only valid for WUSB devices!!! + */ +static inline u8 wusb_port_no_to_idx(u8 port_no) +{ + return port_no - 1; +} + +extern struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *, + struct usb_device *); + +/* + * Return a referenced wusb_dev given a @usb_dev + * + * Returns NULL if the usb_dev is being torn down. + * + * FIXME: move offline + */ +static inline +struct wusb_dev *wusb_dev_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct wusbhc *wusbhc; + struct wusb_dev *wusb_dev; + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return NULL; + mutex_lock(&wusbhc->mutex); + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev); + mutex_unlock(&wusbhc->mutex); + wusbhc_put(wusbhc); + return wusb_dev; +} + +/* Misc */ + +extern struct workqueue_struct *wusbd; +#endif /* #ifndef __WUSBHC_H__ */ -- cgit v1.2.2 From b69fada68b92fa7061d59a3e54b428759a5e5717 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:24 +0100 Subject: wusb: add the Wireless USB core (protocol) Add the WUSB protocol (MMC management and device connection) code. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/devconnect.c | 1314 +++++++++++++++++++++++++++++++++++++ drivers/usb/wusbcore/mmc.c | 329 ++++++++++ 2 files changed, 1643 insertions(+) create mode 100644 drivers/usb/wusbcore/devconnect.c create mode 100644 drivers/usb/wusbcore/mmc.c (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c new file mode 100644 index 000000000000..f05f9b4d7751 --- /dev/null +++ b/drivers/usb/wusbcore/devconnect.c @@ -0,0 +1,1314 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * Device Connect handling + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * FIXME: this file needs to be broken up, it's grown too big + * + * + * WUSB1.0[7.1, 7.5.1, ] + * + * WUSB device connection is kind of messy. Some background: + * + * When a device wants to connect it scans the UWB radio channels + * looking for a WUSB Channel; a WUSB channel is defined by MMCs + * (Micro Managed Commands or something like that) [see + * Design-overview for more on this] . + * + * So, device scans the radio, finds MMCs and thus a host and checks + * when the next DNTS is. It sends a Device Notification Connect + * (DN_Connect); the host picks it up (through nep.c and notif.c, ends + * up in wusb_devconnect_ack(), which creates a wusb_dev structure in + * wusbhc->port[port_number].wusb_dev), assigns an unauth address + * to the device (this means from 0x80 to 0xfe) and sends, in the MMC + * a Connect Ack Information Element (ConnAck IE). + * + * So now the device now has a WUSB address. From now on, we use + * that to talk to it in the RPipes. + * + * ASSUMPTIONS: + * + * - We use the the as device address the port number where it is + * connected (port 0 doesn't exist). For unauth, it is 128 + that. + * + * ROADMAP: + * + * This file contains the logic for doing that--entry points: + * + * wusb_devconnect_ack() Ack a device until _acked() called. + * Called by notif.c:wusb_handle_dn_connect() + * when a DN_Connect is received. + * + * wusbhc_devconnect_auth() Called by rh.c:wusbhc_rh_port_reset() when + * doing the device connect sequence. + * + * wusb_devconnect_acked() Ack done, release resources. + * + * wusb_handle_dn_alive() Called by notif.c:wusb_handle_dn() + * for processing a DN_Alive pong from a device. + * + * wusb_handle_dn_disconnect()Called by notif.c:wusb_handle_dn() to + * process a disconenct request from a + * device. + * + * wusb_dev_reset() Called by rh.c:wusbhc_rh_port_reset() when + * resetting a device. + * + * __wusb_dev_disable() Called by rh.c:wusbhc_rh_clear_port_feat() when + * disabling a port. + * + * wusb_devconnect_create() Called when creating the host by + * lc.c:wusbhc_create(). + * + * wusb_devconnect_destroy() Cleanup called removing the host. Called + * by lc.c:wusbhc_destroy(). + * + * Each Wireless USB host maintains a list of DN_Connect requests + * (actually we maintain a list of pending Connect Acks, the + * wusbhc->ca_list). + * + * LIFE CYCLE OF port->wusb_dev + * + * Before the @wusbhc structure put()s the reference it owns for + * port->wusb_dev [and clean the wusb_dev pointer], it needs to + * lock @wusbhc->mutex. + */ + +#include +#include +#include +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 1 +#include + +static void wusbhc_devconnect_acked_work(struct work_struct *work); + +static void wusb_dev_free(struct wusb_dev *wusb_dev) +{ + if (wusb_dev) { + kfree(wusb_dev->set_gtk_req); + usb_free_urb(wusb_dev->set_gtk_urb); + kfree(wusb_dev); + } +} + +static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc) +{ + struct wusb_dev *wusb_dev; + struct urb *urb; + struct usb_ctrlrequest *req; + + wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL); + if (wusb_dev == NULL) + goto err; + + wusb_dev->wusbhc = wusbhc; + + INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) + goto err; + + req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (req == NULL) + goto err; + + req->bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req->bRequest = USB_REQ_SET_DESCRIPTOR; + req->wValue = cpu_to_le16(USB_DT_KEY << 8 | wusbhc->gtk_index); + req->wIndex = 0; + req->wLength = cpu_to_le16(wusbhc->gtk.descr.bLength); + + wusb_dev->set_gtk_urb = urb; + wusb_dev->set_gtk_req = req; + + return wusb_dev; +err: + wusb_dev_free(wusb_dev); + return NULL; +} + + +/* + * Using the Connect-Ack list, fill out the @wusbhc Connect-Ack WUSB IE + * properly so that it can be added to the MMC. + * + * We just get the @wusbhc->ca_list and fill out the first four ones or + * less (per-spec WUSB1.0[7.5, before T7-38). If the ConnectAck WUSB + * IE is not allocated, we alloc it. + * + * @wusbhc->mutex must be taken + */ +static void wusbhc_fill_cack_ie(struct wusbhc *wusbhc) +{ + unsigned cnt; + struct wusb_dev *dev_itr; + struct wuie_connect_ack *cack_ie; + + cack_ie = &wusbhc->cack_ie; + cnt = 0; + list_for_each_entry(dev_itr, &wusbhc->cack_list, cack_node) { + cack_ie->blk[cnt].CDID = dev_itr->cdid; + cack_ie->blk[cnt].bDeviceAddress = dev_itr->addr; + if (++cnt >= WUIE_ELT_MAX) + break; + } + cack_ie->hdr.bLength = sizeof(cack_ie->hdr) + + cnt * sizeof(cack_ie->blk[0]); +} + +/* + * Register a new device that wants to connect + * + * A new device wants to connect, so we add it to the Connect-Ack + * list. We give it an address in the unauthorized range (bit 8 set); + * user space will have to drive authorization further on. + * + * @dev_addr: address to use for the device (which is also the port + * number). + * + * @wusbhc->mutex must be taken + */ +static struct wusb_dev *wusbhc_cack_add(struct wusbhc *wusbhc, + struct wusb_dn_connect *dnc, + const char *pr_cdid, u8 port_idx) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + int new_connection = wusb_dn_connect_new_connection(dnc); + u8 dev_addr; + int result; + + d_fnstart(3, dev, "(wusbhc %p port_idx %d)\n", wusbhc, port_idx); + /* Is it registered already? */ + list_for_each_entry(wusb_dev, &wusbhc->cack_list, cack_node) + if (!memcmp(&wusb_dev->cdid, &dnc->CDID, + sizeof(wusb_dev->cdid))) + return wusb_dev; + /* We don't have it, create an entry, register it */ + wusb_dev = wusb_dev_alloc(wusbhc); + if (wusb_dev == NULL) { + if (printk_ratelimit()) + dev_err(dev, "DN CONNECT: no memory to process %s's %s " + "request\n", pr_cdid, + new_connection ? "connect" : "reconnect"); + return NULL; + } + wusb_dev_init(wusb_dev); + wusb_dev->cdid = dnc->CDID; + wusb_dev->port_idx = port_idx; + + /* + * Devices are always available within the cluster reservation + * and since the hardware will take the intersection of the + * per-device availability and the cluster reservation, the + * per-device availability can simply be set to always + * available. + */ + bitmap_fill(wusb_dev->availability.bm, UWB_NUM_MAS); + + /* FIXME: handle reconnects instead of assuming connects are + always new. */ + if (1 && new_connection == 0) + new_connection = 1; + if (new_connection) { + dev_addr = (port_idx + 2) | WUSB_DEV_ADDR_UNAUTH; + + dev_info(dev, "Connecting new WUSB device to address %u, " + "port %u\n", dev_addr, port_idx); + + result = wusb_set_dev_addr(wusbhc, wusb_dev, dev_addr); + if (result) + return NULL; + } + wusb_dev->entry_ts = jiffies; + list_add_tail(&wusb_dev->cack_node, &wusbhc->cack_list); + wusbhc->cack_count++; + wusbhc_fill_cack_ie(wusbhc); + d_fnend(3, dev, "(wusbhc %p port_idx %d)\n", wusbhc, port_idx); + return wusb_dev; +} + +/* + * Remove a Connect-Ack context entry from the HCs view + * + * @wusbhc->mutex must be taken + */ +static void wusbhc_cack_rm(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + d_fnstart(3, dev, "(wusbhc %p wusb_dev %p)\n", wusbhc, wusb_dev); + list_del_init(&wusb_dev->cack_node); + wusbhc->cack_count--; + wusbhc_fill_cack_ie(wusbhc); + d_fnend(3, dev, "(wusbhc %p wusb_dev %p) = void\n", wusbhc, wusb_dev); +} + +/* + * @wusbhc->mutex must be taken */ +static +void wusbhc_devconnect_acked(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + d_fnstart(3, dev, "(wusbhc %p wusb_dev %p)\n", wusbhc, wusb_dev); + wusbhc_cack_rm(wusbhc, wusb_dev); + if (wusbhc->cack_count) + wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr); + else + wusbhc_mmcie_rm(wusbhc, &wusbhc->cack_ie.hdr); + d_fnend(3, dev, "(wusbhc %p wusb_dev %p) = void\n", wusbhc, wusb_dev); +} + +static void wusbhc_devconnect_acked_work(struct work_struct *work) +{ + struct wusb_dev *wusb_dev = container_of(work, struct wusb_dev, + devconnect_acked_work); + struct wusbhc *wusbhc = wusb_dev->wusbhc; + + mutex_lock(&wusbhc->mutex); + wusbhc_devconnect_acked(wusbhc, wusb_dev); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Ack a device for connection + * + * FIXME: docs + * + * @pr_cdid: Printable CDID...hex Use @dnc->cdid for the real deal. + * + * So we get the connect ack IE (may have been allocated already), + * find an empty connect block, an empty virtual port, create an + * address with it (see below), make it an unauth addr [bit 7 set] and + * set the MMC. + * + * Addresses: because WUSB hosts have no downstream hubs, we can do a + * 1:1 mapping between 'port number' and device + * address. This simplifies many things, as during this + * initial connect phase the USB stack has no knoledge of + * the device and hasn't assigned an address yet--we know + * USB's choose_address() will use the same euristics we + * use here, so we can assume which address will be assigned. + * + * USB stack always assigns address 1 to the root hub, so + * to the port number we add 2 (thus virtual port #0 is + * addr #2). + * + * @wusbhc shall be referenced + */ +static +void wusbhc_devconnect_ack(struct wusbhc *wusbhc, struct wusb_dn_connect *dnc, + const char *pr_cdid) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wusb_port *port; + unsigned idx, devnum; + + d_fnstart(3, dev, "(%p, %p, %s)\n", wusbhc, dnc, pr_cdid); + mutex_lock(&wusbhc->mutex); + + /* Check we are not handling it already */ + for (idx = 0; idx < wusbhc->ports_max; idx++) { + port = wusb_port_by_idx(wusbhc, idx); + if (port->wusb_dev + && !memcmp(&dnc->CDID, &port->wusb_dev->cdid, + sizeof(dnc->CDID))) { + if (printk_ratelimit()) + dev_err(dev, "Already handling dev %s " + " (it might be slow)\n", pr_cdid); + goto error_unlock; + } + } + /* Look up those fake ports we have for a free one */ + for (idx = 0; idx < wusbhc->ports_max; idx++) { + port = wusb_port_by_idx(wusbhc, idx); + if ((port->status & USB_PORT_STAT_POWER) + && !(port->status & USB_PORT_STAT_CONNECTION)) + break; + } + if (idx >= wusbhc->ports_max) { + dev_err(dev, "Host controller can't connect more devices " + "(%u already connected); device %s rejected\n", + wusbhc->ports_max, pr_cdid); + /* NOTE: we could send a WUIE_Disconnect here, but we haven't + * event acked, so the device will eventually timeout the + * connection, right? */ + goto error_unlock; + } + + devnum = idx + 2; + + /* Make sure we are using no crypto on that "virtual port" */ + wusbhc->set_ptk(wusbhc, idx, 0, NULL, 0); + + /* Grab a filled in Connect-Ack context, fill out the + * Connect-Ack Wireless USB IE, set the MMC */ + wusb_dev = wusbhc_cack_add(wusbhc, dnc, pr_cdid, idx); + if (wusb_dev == NULL) + goto error_unlock; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr); + if (result < 0) + goto error_unlock; + /* Give the device at least 2ms (WUSB1.0[7.5.1p3]), let's do + * three for a good measure */ + msleep(3); + port->wusb_dev = wusb_dev; + port->status |= USB_PORT_STAT_CONNECTION; + port->change |= USB_PORT_STAT_C_CONNECTION; + port->reset_count = 0; + /* Now the port status changed to connected; khubd will + * pick the change up and try to reset the port to bring it to + * the enabled state--so this process returns up to the stack + * and it calls back into wusbhc_rh_port_reset() who will call + * devconnect_auth(). + */ +error_unlock: + mutex_unlock(&wusbhc->mutex); + d_fnend(3, dev, "(%p, %p, %s) = void\n", wusbhc, dnc, pr_cdid); + return; + +} + +/* + * Disconnect a Wireless USB device from its fake port + * + * Marks the port as disconnected so that khubd can pick up the change + * and drops our knowledge about the device. + * + * Assumes there is a device connected + * + * @port_index: zero based port number + * + * NOTE: @wusbhc->mutex is locked + * + * WARNING: From here it is not very safe to access anything hanging off + * wusb_dev + */ +static void __wusbhc_dev_disconnect(struct wusbhc *wusbhc, + struct wusb_port *port) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev = port->wusb_dev; + + d_fnstart(3, dev, "(wusbhc %p, port %p)\n", wusbhc, port); + port->status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE + | USB_PORT_STAT_SUSPEND | USB_PORT_STAT_RESET + | USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED); + port->change |= USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE; + if (wusb_dev) { + if (!list_empty(&wusb_dev->cack_node)) + list_del_init(&wusb_dev->cack_node); + /* For the one in cack_add() */ + wusb_dev_put(wusb_dev); + } + port->wusb_dev = NULL; + /* don't reset the reset_count to zero or wusbhc_rh_port_reset will get + * confused! We only reset to zero when we connect a new device. + */ + + /* After a device disconnects, change the GTK (see [WUSB] + * section 6.2.11.2). */ + wusbhc_gtk_rekey(wusbhc); + + d_fnend(3, dev, "(wusbhc %p, port %p) = void\n", wusbhc, port); + /* The Wireless USB part has forgotten about the device already; now + * khubd's timer will pick up the disconnection and remove the USB + * device from the system + */ +} + +/* + * Authenticate a device into the WUSB Cluster + * + * Called from the Root Hub code (rh.c:wusbhc_rh_port_reset()) when + * asking for a reset on a port that is not enabled (ie: first connect + * on the port). + * + * Performs the 4way handshake to allow the device to comunicate w/ the + * WUSB Cluster securely; once done, issue a request to the device for + * it to change to address 0. + * + * This mimics the reset step of Wired USB that once resetting a + * device, leaves the port in enabled state and the dev with the + * default address (0). + * + * WUSB1.0[7.1.2] + * + * @port_idx: port where the change happened--This is the index into + * the wusbhc port array, not the USB port number. + */ +int wusbhc_devconnect_auth(struct wusbhc *wusbhc, u8 port_idx) +{ + struct device *dev = wusbhc->dev; + struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx); + + d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx); + port->status &= ~USB_PORT_STAT_RESET; + port->status |= USB_PORT_STAT_ENABLE; + port->change |= USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_ENABLE; + d_fnend(3, dev, "(%p, %u) = 0\n", wusbhc, port_idx); + return 0; +} + +/* + * Refresh the list of keep alives to emit in the MMC + * + * Some devices don't respond to keep alives unless they've been + * authenticated, so skip unauthenticated devices. + * + * We only publish the first four devices that have a coming timeout + * condition. Then when we are done processing those, we go for the + * next ones. We ignore the ones that have timed out already (they'll + * be purged). + * + * This might cause the first devices to timeout the last devices in + * the port array...FIXME: come up with a better algorithm? + * + * Note we can't do much about MMC's ops errors; we hope next refresh + * will kind of handle it. + * + * NOTE: @wusbhc->mutex is locked + */ +static void __wusbhc_keep_alive(struct wusbhc *wusbhc) +{ + int result; + struct device *dev = wusbhc->dev; + unsigned cnt; + struct wusb_dev *wusb_dev; + struct wusb_port *wusb_port; + struct wuie_keep_alive *ie = &wusbhc->keep_alive_ie; + unsigned keep_alives, old_keep_alives; + + d_fnstart(5, dev, "(wusbhc %p)\n", wusbhc); + old_keep_alives = ie->hdr.bLength - sizeof(ie->hdr); + keep_alives = 0; + for (cnt = 0; + keep_alives <= WUIE_ELT_MAX && cnt < wusbhc->ports_max; + cnt++) { + unsigned tt = msecs_to_jiffies(wusbhc->trust_timeout); + + wusb_port = wusb_port_by_idx(wusbhc, cnt); + wusb_dev = wusb_port->wusb_dev; + + if (wusb_dev == NULL) + continue; + if (wusb_dev->usb_dev == NULL || !wusb_dev->usb_dev->authenticated) + continue; + + if (time_after(jiffies, wusb_dev->entry_ts + tt)) { + dev_err(dev, "KEEPALIVE: device %u timed out\n", + wusb_dev->addr); + __wusbhc_dev_disconnect(wusbhc, wusb_port); + } else if (time_after(jiffies, wusb_dev->entry_ts + tt/2)) { + /* Approaching timeout cut out, need to refresh */ + ie->bDeviceAddress[keep_alives++] = wusb_dev->addr; + } + } + if (keep_alives & 0x1) /* pad to even number ([WUSB] section 7.5.9) */ + ie->bDeviceAddress[keep_alives++] = 0x7f; + ie->hdr.bLength = sizeof(ie->hdr) + + keep_alives*sizeof(ie->bDeviceAddress[0]); + if (keep_alives > 0) { + result = wusbhc_mmcie_set(wusbhc, 10, 5, &ie->hdr); + if (result < 0 && printk_ratelimit()) + dev_err(dev, "KEEPALIVE: can't set MMC: %d\n", result); + } else if (old_keep_alives != 0) + wusbhc_mmcie_rm(wusbhc, &ie->hdr); + d_fnend(5, dev, "(wusbhc %p) = void\n", wusbhc); +} + +/* + * Do a run through all devices checking for timeouts + */ +static void wusbhc_keep_alive_run(struct work_struct *ws) +{ + struct delayed_work *dw = + container_of(ws, struct delayed_work, work); + struct wusbhc *wusbhc = + container_of(dw, struct wusbhc, keep_alive_timer); + + d_fnstart(5, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + if (wusbhc->active) { + mutex_lock(&wusbhc->mutex); + __wusbhc_keep_alive(wusbhc); + mutex_unlock(&wusbhc->mutex); + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (wusbhc->trust_timeout * CONFIG_HZ)/1000/2); + } + d_fnend(5, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); + return; +} + +/* + * Find the wusb_dev from its device address. + * + * The device can be found directly from the address (see + * wusb_cack_add() for where the device address is set to port_idx + * +2), except when the address is zero. + */ +static struct wusb_dev *wusbhc_find_dev_by_addr(struct wusbhc *wusbhc, u8 addr) +{ + int p; + + if (addr == 0xff) /* unconnected */ + return NULL; + + if (addr > 0) { + int port = (addr & ~0x80) - 2; + if (port < 0 || port >= wusbhc->ports_max) + return NULL; + return wusb_port_by_idx(wusbhc, port)->wusb_dev; + } + + /* Look for the device with address 0. */ + for (p = 0; p < wusbhc->ports_max; p++) { + struct wusb_dev *wusb_dev = wusb_port_by_idx(wusbhc, p)->wusb_dev; + if (wusb_dev && wusb_dev->addr == addr) + return wusb_dev; + } + return NULL; +} + +/* + * Handle a DN_Alive notification (WUSB1.0[7.6.1]) + * + * This just updates the device activity timestamp and then refreshes + * the keep alive IE. + * + * @wusbhc shall be referenced and unlocked + */ +static void wusbhc_handle_dn_alive(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + + d_printf(2, dev, "DN ALIVE: device 0x%02x pong\n", wusb_dev->addr); + + mutex_lock(&wusbhc->mutex); + wusb_dev->entry_ts = jiffies; + __wusbhc_keep_alive(wusbhc); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Handle a DN_Connect notification (WUSB1.0[7.6.1]) + * + * @wusbhc + * @pkt_hdr + * @size: Size of the buffer where the notification resides; if the + * notification data suggests there should be more data than + * available, an error will be signaled and the whole buffer + * consumed. + * + * @wusbhc->mutex shall be held + */ +static void wusbhc_handle_dn_connect(struct wusbhc *wusbhc, + struct wusb_dn_hdr *dn_hdr, + size_t size) +{ + struct device *dev = wusbhc->dev; + struct wusb_dn_connect *dnc; + char pr_cdid[WUSB_CKHDID_STRSIZE]; + static const char *beacon_behaviour[] = { + "reserved", + "self-beacon", + "directed-beacon", + "no-beacon" + }; + + d_fnstart(3, dev, "(%p, %p, %zu)\n", wusbhc, dn_hdr, size); + if (size < sizeof(*dnc)) { + dev_err(dev, "DN CONNECT: short notification (%zu < %zu)\n", + size, sizeof(*dnc)); + goto out; + } + + dnc = container_of(dn_hdr, struct wusb_dn_connect, hdr); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &dnc->CDID); + dev_info(dev, "DN CONNECT: device %s @ %x (%s) wants to %s\n", + pr_cdid, + wusb_dn_connect_prev_dev_addr(dnc), + beacon_behaviour[wusb_dn_connect_beacon_behavior(dnc)], + wusb_dn_connect_new_connection(dnc) ? "connect" : "reconnect"); + /* ACK the connect */ + wusbhc_devconnect_ack(wusbhc, dnc, pr_cdid); +out: + d_fnend(3, dev, "(%p, %p, %zu) = void\n", + wusbhc, dn_hdr, size); + return; +} + +/* + * Handle a DN_Disconnect notification (WUSB1.0[7.6.1]) + * + * Device is going down -- do the disconnect. + * + * @wusbhc shall be referenced and unlocked + */ +static void wusbhc_handle_dn_disconnect(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + + dev_info(dev, "DN DISCONNECT: device 0x%02x going down\n", wusb_dev->addr); + + mutex_lock(&wusbhc->mutex); + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, wusb_dev->port_idx)); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Reset a WUSB device on a HWA + * + * @wusbhc + * @port_idx Index of the port where the device is + * + * In Wireless USB, a reset is more or less equivalent to a full + * disconnect; so we just do a full disconnect and send the device a + * Device Reset IE (WUSB1.0[7.5.11]) giving it a few millisecs (6 MMCs). + * + * @wusbhc should be refcounted and unlocked + */ +int wusbhc_dev_reset(struct wusbhc *wusbhc, u8 port_idx) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wuie_reset *ie; + + d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx); + mutex_lock(&wusbhc->mutex); + result = 0; + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev == NULL) { + /* reset no device? ignore */ + dev_dbg(dev, "RESET: no device at port %u, ignoring\n", + port_idx); + goto error_unlock; + } + result = -ENOMEM; + ie = kzalloc(sizeof(*ie), GFP_KERNEL); + if (ie == NULL) + goto error_unlock; + ie->hdr.bLength = sizeof(ie->hdr) + sizeof(ie->CDID); + ie->hdr.bIEIdentifier = WUIE_ID_RESET_DEVICE; + ie->CDID = wusb_dev->cdid; + result = wusbhc_mmcie_set(wusbhc, 0xff, 6, &ie->hdr); + if (result < 0) { + dev_err(dev, "RESET: cant's set MMC: %d\n", result); + goto error_kfree; + } + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + + /* 120ms, hopefully 6 MMCs (FIXME) */ + msleep(120); + wusbhc_mmcie_rm(wusbhc, &ie->hdr); +error_kfree: + kfree(ie); +error_unlock: + mutex_unlock(&wusbhc->mutex); + d_fnend(3, dev, "(%p, %u) = %d\n", wusbhc, port_idx, result); + return result; +} + +/* + * Handle a Device Notification coming a host + * + * The Device Notification comes from a host (HWA, DWA or WHCI) + * wrapped in a set of headers. Somebody else has peeled off those + * headers for us and we just get one Device Notifications. + * + * Invalid DNs (e.g., too short) are discarded. + * + * @wusbhc shall be referenced + * + * FIXMES: + * - implement priorities as in WUSB1.0[Table 7-55]? + */ +void wusbhc_handle_dn(struct wusbhc *wusbhc, u8 srcaddr, + struct wusb_dn_hdr *dn_hdr, size_t size) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + + d_fnstart(3, dev, "(%p, %p)\n", wusbhc, dn_hdr); + + if (size < sizeof(struct wusb_dn_hdr)) { + dev_err(dev, "DN data shorter than DN header (%d < %d)\n", + (int)size, (int)sizeof(struct wusb_dn_hdr)); + goto out; + } + + wusb_dev = wusbhc_find_dev_by_addr(wusbhc, srcaddr); + if (wusb_dev == NULL && dn_hdr->bType != WUSB_DN_CONNECT) { + dev_dbg(dev, "ignoring DN %d from unconnected device %02x\n", + dn_hdr->bType, srcaddr); + goto out; + } + + switch (dn_hdr->bType) { + case WUSB_DN_CONNECT: + wusbhc_handle_dn_connect(wusbhc, dn_hdr, size); + break; + case WUSB_DN_ALIVE: + wusbhc_handle_dn_alive(wusbhc, wusb_dev); + break; + case WUSB_DN_DISCONNECT: + wusbhc_handle_dn_disconnect(wusbhc, wusb_dev); + break; + case WUSB_DN_MASAVAILCHANGED: + case WUSB_DN_RWAKE: + case WUSB_DN_SLEEP: + /* FIXME: handle these DNs. */ + break; + case WUSB_DN_EPRDY: + /* The hardware handles these. */ + break; + default: + dev_warn(dev, "unknown DN %u (%d octets) from %u\n", + dn_hdr->bType, (int)size, srcaddr); + } +out: + d_fnend(3, dev, "(%p, %p) = void\n", wusbhc, dn_hdr); + return; +} +EXPORT_SYMBOL_GPL(wusbhc_handle_dn); + +/* + * Disconnect a WUSB device from a the cluster + * + * @wusbhc + * @port Fake port where the device is (wusbhc index, not USB port number). + * + * In Wireless USB, a disconnect is basically telling the device he is + * being disconnected and forgetting about him. + * + * We send the device a Device Disconnect IE (WUSB1.0[7.5.11]) for 100 + * ms and then keep going. + * + * We don't do much in case of error; we always pretend we disabled + * the port and disconnected the device. If physically the request + * didn't get there (many things can fail in the way there), the stack + * will reject the device's communication attempts. + * + * @wusbhc should be refcounted and locked + */ +void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port_idx) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wuie_disconnect *ie; + + d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx); + result = 0; + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev == NULL) { + /* reset no device? ignore */ + dev_dbg(dev, "DISCONNECT: no device at port %u, ignoring\n", + port_idx); + goto error; + } + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + + result = -ENOMEM; + ie = kzalloc(sizeof(*ie), GFP_KERNEL); + if (ie == NULL) + goto error; + ie->hdr.bLength = sizeof(*ie); + ie->hdr.bIEIdentifier = WUIE_ID_DEVICE_DISCONNECT; + ie->bDeviceAddress = wusb_dev->addr; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &ie->hdr); + if (result < 0) { + dev_err(dev, "DISCONNECT: can't set MMC: %d\n", result); + goto error_kfree; + } + + /* 120ms, hopefully 6 MMCs */ + msleep(100); + wusbhc_mmcie_rm(wusbhc, &ie->hdr); +error_kfree: + kfree(ie); +error: + d_fnend(3, dev, "(%p, %u) = %d\n", wusbhc, port_idx, result); + return; +} + +static void wusb_cap_descr_printf(const unsigned level, struct device *dev, + const struct usb_wireless_cap_descriptor *wcd) +{ + d_printf(level, dev, + "WUSB Capability Descriptor\n" + " bDevCapabilityType 0x%02x\n" + " bmAttributes 0x%02x\n" + " wPhyRates 0x%04x\n" + " bmTFITXPowerInfo 0x%02x\n" + " bmFFITXPowerInfo 0x%02x\n" + " bmBandGroup 0x%04x\n" + " bReserved 0x%02x\n", + wcd->bDevCapabilityType, + wcd->bmAttributes, + le16_to_cpu(wcd->wPHYRates), + wcd->bmTFITXPowerInfo, + wcd->bmFFITXPowerInfo, + wcd->bmBandGroup, + wcd->bReserved); +} + +/* + * Walk over the BOS descriptor, verify and grok it + * + * @usb_dev: referenced + * @wusb_dev: referenced and unlocked + * + * The BOS descriptor is defined at WUSB1.0[7.4.1], and it defines a + * "flexible" way to wrap all kinds of descriptors inside an standard + * descriptor (wonder why they didn't use normal descriptors, + * btw). Not like they lack code. + * + * At the end we go to look for the WUSB Device Capabilities + * (WUSB1.0[7.4.1.1]) that is wrapped in a device capability descriptor + * that is part of the BOS descriptor set. That tells us what does the + * device support (dual role, beacon type, UWB PHY rates). + */ +static int wusb_dev_bos_grok(struct usb_device *usb_dev, + struct wusb_dev *wusb_dev, + struct usb_bos_descriptor *bos, size_t desc_size) +{ + ssize_t result; + struct device *dev = &usb_dev->dev; + void *itr, *top; + + /* Walk over BOS capabilities, verify them */ + itr = (void *)bos + sizeof(*bos); + top = itr + desc_size - sizeof(*bos); + while (itr < top) { + struct usb_dev_cap_header *cap_hdr = itr; + size_t cap_size; + u8 cap_type; + if (top - itr < sizeof(*cap_hdr)) { + dev_err(dev, "Device BUG? premature end of BOS header " + "data [offset 0x%02x]: only %zu bytes left\n", + (int)(itr - (void *)bos), top - itr); + result = -ENOSPC; + goto error_bad_cap; + } + cap_size = cap_hdr->bLength; + cap_type = cap_hdr->bDevCapabilityType; + d_printf(4, dev, "BOS Capability: 0x%02x (%zu bytes)\n", + cap_type, cap_size); + if (cap_size == 0) + break; + if (cap_size > top - itr) { + dev_err(dev, "Device BUG? premature end of BOS data " + "[offset 0x%02x cap %02x %zu bytes]: " + "only %zu bytes left\n", + (int)(itr - (void *)bos), + cap_type, cap_size, top - itr); + result = -EBADF; + goto error_bad_cap; + } + d_dump(3, dev, itr, cap_size); + switch (cap_type) { + case USB_CAP_TYPE_WIRELESS_USB: + if (cap_size != sizeof(*wusb_dev->wusb_cap_descr)) + dev_err(dev, "Device BUG? WUSB Capability " + "descriptor is %zu bytes vs %zu " + "needed\n", cap_size, + sizeof(*wusb_dev->wusb_cap_descr)); + else { + wusb_dev->wusb_cap_descr = itr; + wusb_cap_descr_printf(3, dev, itr); + } + break; + default: + dev_err(dev, "BUG? Unknown BOS capability 0x%02x " + "(%zu bytes) at offset 0x%02x\n", cap_type, + cap_size, (int)(itr - (void *)bos)); + } + itr += cap_size; + } + result = 0; +error_bad_cap: + return result; +} + +/* + * Add information from the BOS descriptors to the device + * + * @usb_dev: referenced + * @wusb_dev: referenced and unlocked + * + * So what we do is we alloc a space for the BOS descriptor of 64 + * bytes; read the first four bytes which include the wTotalLength + * field (WUSB1.0[T7-26]) and if it fits in those 64 bytes, read the + * whole thing. If not we realloc to that size. + * + * Then we call the groking function, that will fill up + * wusb_dev->wusb_cap_descr, which is what we'll need later on. + */ +static int wusb_dev_bos_add(struct usb_device *usb_dev, + struct wusb_dev *wusb_dev) +{ + ssize_t result; + struct device *dev = &usb_dev->dev; + struct usb_bos_descriptor *bos; + size_t alloc_size = 32, desc_size = 4; + + bos = kmalloc(alloc_size, GFP_KERNEL); + if (bos == NULL) + return -ENOMEM; + result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size); + if (result < 4) { + dev_err(dev, "Can't get BOS descriptor or too short: %zd\n", + result); + goto error_get_descriptor; + } + desc_size = le16_to_cpu(bos->wTotalLength); + if (desc_size >= alloc_size) { + kfree(bos); + alloc_size = desc_size; + bos = kmalloc(alloc_size, GFP_KERNEL); + if (bos == NULL) + return -ENOMEM; + } + result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size); + if (result < 0 || result != desc_size) { + dev_err(dev, "Can't get BOS descriptor or too short (need " + "%zu bytes): %zd\n", desc_size, result); + goto error_get_descriptor; + } + if (result < sizeof(*bos) + || le16_to_cpu(bos->wTotalLength) != desc_size) { + dev_err(dev, "Can't get BOS descriptor or too short (need " + "%zu bytes): %zd\n", desc_size, result); + goto error_get_descriptor; + } + d_printf(2, dev, "Got BOS descriptor %zd bytes, %u capabilities\n", + result, bos->bNumDeviceCaps); + d_dump(2, dev, bos, result); + result = wusb_dev_bos_grok(usb_dev, wusb_dev, bos, result); + if (result < 0) + goto error_bad_bos; + wusb_dev->bos = bos; + return 0; + +error_bad_bos: +error_get_descriptor: + kfree(bos); + wusb_dev->wusb_cap_descr = NULL; + return result; +} + +static void wusb_dev_bos_rm(struct wusb_dev *wusb_dev) +{ + kfree(wusb_dev->bos); + wusb_dev->wusb_cap_descr = NULL; +}; + +static struct usb_wireless_cap_descriptor wusb_cap_descr_default = { + .bLength = sizeof(wusb_cap_descr_default), + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_CAP_TYPE_WIRELESS_USB, + + .bmAttributes = USB_WIRELESS_BEACON_NONE, + .wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53), + .bmTFITXPowerInfo = 0, + .bmFFITXPowerInfo = 0, + .bmBandGroup = cpu_to_le16(0x0001), /* WUSB1.0[7.4.1] bottom */ + .bReserved = 0 +}; + +/* + * USB stack's device addition Notifier Callback + * + * Called from drivers/usb/core/hub.c when a new device is added; we + * use this hook to perform certain WUSB specific setup work on the + * new device. As well, it is the first time we can connect the + * wusb_dev and the usb_dev. So we note it down in wusb_dev and take a + * reference that we'll drop. + * + * First we need to determine if the device is a WUSB device (else we + * ignore it). For that we use the speed setting (USB_SPEED_VARIABLE) + * [FIXME: maybe we'd need something more definitive]. If so, we track + * it's usb_busd and from there, the WUSB HC. + * + * Because all WUSB HCs are contained in a 'struct wusbhc', voila, we + * get the wusbhc for the device. + * + * We have a reference on @usb_dev (as we are called at the end of its + * enumeration). + * + * NOTE: @usb_dev locked + */ +static void wusb_dev_add_ncb(struct usb_device *usb_dev) +{ + int result = 0; + struct wusb_dev *wusb_dev; + struct wusbhc *wusbhc; + struct device *dev = &usb_dev->dev; + u8 port_idx; + + if (usb_dev->wusb == 0 || usb_dev->devnum == 1) + return; /* skip non wusb and wusb RHs */ + + d_fnstart(3, dev, "(usb_dev %p)\n", usb_dev); + + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + goto error_nodev; + mutex_lock(&wusbhc->mutex); + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev); + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + mutex_unlock(&wusbhc->mutex); + if (wusb_dev == NULL) + goto error_nodev; + wusb_dev->usb_dev = usb_get_dev(usb_dev); + usb_dev->wusb_dev = wusb_dev_get(wusb_dev); + result = wusb_dev_sec_add(wusbhc, usb_dev, wusb_dev); + if (result < 0) { + dev_err(dev, "Cannot enable security: %d\n", result); + goto error_sec_add; + } + /* Now query the device for it's BOS and attach it to wusb_dev */ + result = wusb_dev_bos_add(usb_dev, wusb_dev); + if (result < 0) { + dev_err(dev, "Cannot get BOS descriptors: %d\n", result); + goto error_bos_add; + } + result = wusb_dev_sysfs_add(wusbhc, usb_dev, wusb_dev); + if (result < 0) + goto error_add_sysfs; +out: + wusb_dev_put(wusb_dev); + wusbhc_put(wusbhc); +error_nodev: + d_fnend(3, dev, "(usb_dev %p) = void\n", usb_dev); + return; + + wusb_dev_sysfs_rm(wusb_dev); +error_add_sysfs: + wusb_dev_bos_rm(wusb_dev); +error_bos_add: + wusb_dev_sec_rm(wusb_dev); +error_sec_add: + mutex_lock(&wusbhc->mutex); + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + mutex_unlock(&wusbhc->mutex); + goto out; +} + +/* + * Undo all the steps done at connection by the notifier callback + * + * NOTE: @usb_dev locked + */ +static void wusb_dev_rm_ncb(struct usb_device *usb_dev) +{ + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + + if (usb_dev->wusb == 0 || usb_dev->devnum == 1) + return; /* skip non wusb and wusb RHs */ + + wusb_dev_sysfs_rm(wusb_dev); + wusb_dev_bos_rm(wusb_dev); + wusb_dev_sec_rm(wusb_dev); + wusb_dev->usb_dev = NULL; + usb_dev->wusb_dev = NULL; + wusb_dev_put(wusb_dev); + usb_put_dev(usb_dev); +} + +/* + * Handle notifications from the USB stack (notifier call back) + * + * This is called when the USB stack does a + * usb_{bus,device}_{add,remove}() so we can do WUSB specific + * handling. It is called with [for the case of + * USB_DEVICE_{ADD,REMOVE} with the usb_dev locked. + */ +int wusb_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv) +{ + int result = NOTIFY_OK; + + switch (val) { + case USB_DEVICE_ADD: + wusb_dev_add_ncb(priv); + break; + case USB_DEVICE_REMOVE: + wusb_dev_rm_ncb(priv); + break; + case USB_BUS_ADD: + /* ignore (for now) */ + case USB_BUS_REMOVE: + break; + default: + WARN_ON(1); + result = NOTIFY_BAD; + }; + return result; +} + +/* + * Return a referenced wusb_dev given a @wusbhc and @usb_dev + */ +struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *wusbhc, + struct usb_device *usb_dev) +{ + struct wusb_dev *wusb_dev; + u8 port_idx; + + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + BUG_ON(port_idx > wusbhc->ports_max); + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev != NULL) /* ops, device is gone */ + wusb_dev_get(wusb_dev); + return wusb_dev; +} +EXPORT_SYMBOL_GPL(__wusb_dev_get_by_usb_dev); + +void wusb_dev_destroy(struct kref *_wusb_dev) +{ + struct wusb_dev *wusb_dev + = container_of(_wusb_dev, struct wusb_dev, refcnt); + list_del_init(&wusb_dev->cack_node); + wusb_dev_free(wusb_dev); + d_fnend(1, NULL, "%s (wusb_dev %p) = void\n", __func__, wusb_dev); +} +EXPORT_SYMBOL_GPL(wusb_dev_destroy); + +/* + * Create all the device connect handling infrastructure + * + * This is basically the device info array, Connect Acknowledgement + * (cack) lists, keep-alive timers (and delayed work thread). + */ +int wusbhc_devconnect_create(struct wusbhc *wusbhc) +{ + d_fnstart(3, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + + wusbhc->keep_alive_ie.hdr.bIEIdentifier = WUIE_ID_KEEP_ALIVE; + wusbhc->keep_alive_ie.hdr.bLength = sizeof(wusbhc->keep_alive_ie.hdr); + INIT_DELAYED_WORK(&wusbhc->keep_alive_timer, wusbhc_keep_alive_run); + + wusbhc->cack_ie.hdr.bIEIdentifier = WUIE_ID_CONNECTACK; + wusbhc->cack_ie.hdr.bLength = sizeof(wusbhc->cack_ie.hdr); + INIT_LIST_HEAD(&wusbhc->cack_list); + + d_fnend(3, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); + return 0; +} + +/* + * Release all resources taken by the devconnect stuff + */ +void wusbhc_devconnect_destroy(struct wusbhc *wusbhc) +{ + d_fnstart(3, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + d_fnend(3, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); +} + +/* + * wusbhc_devconnect_start - start accepting device connections + * @wusbhc: the WUSB HC + * + * Sets the Host Info IE to accept all new connections. + * + * FIXME: This also enables the keep alives but this is not necessary + * until there are connected and authenticated devices. + */ +int wusbhc_devconnect_start(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid) +{ + struct device *dev = wusbhc->dev; + struct wuie_host_info *hi; + int result; + + hi = kzalloc(sizeof(*hi), GFP_KERNEL); + if (hi == NULL) + return -ENOMEM; + + hi->hdr.bLength = sizeof(*hi); + hi->hdr.bIEIdentifier = WUIE_ID_HOST_INFO; + hi->attributes = cpu_to_le16((wusbhc->rsv->stream << 3) | WUIE_HI_CAP_ALL); + hi->CHID = *chid; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &hi->hdr); + if (result < 0) { + dev_err(dev, "Cannot add Host Info MMCIE: %d\n", result); + goto error_mmcie_set; + } + wusbhc->wuie_host_info = hi; + + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (wusbhc->trust_timeout*CONFIG_HZ)/1000/2); + + return 0; + +error_mmcie_set: + kfree(hi); + return result; +} + +/* + * wusbhc_devconnect_stop - stop managing connected devices + * @wusbhc: the WUSB HC + * + * Removes the Host Info IE and stops the keep alives. + * + * FIXME: should this disconnect all devices? + */ +void wusbhc_devconnect_stop(struct wusbhc *wusbhc) +{ + cancel_delayed_work_sync(&wusbhc->keep_alive_timer); + WARN_ON(!list_empty(&wusbhc->cack_list)); + + wusbhc_mmcie_rm(wusbhc, &wusbhc->wuie_host_info->hdr); + kfree(wusbhc->wuie_host_info); + wusbhc->wuie_host_info = NULL; +} + +/* + * wusb_set_dev_addr - set the WUSB device address used by the host + * @wusbhc: the WUSB HC the device is connect to + * @wusb_dev: the WUSB device + * @addr: new device address + */ +int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, u8 addr) +{ + int result; + + wusb_dev->addr = addr; + result = wusbhc->dev_info_set(wusbhc, wusb_dev); + if (result) + dev_err(wusbhc->dev, "device %d: failed to set device " + "address\n", wusb_dev->port_idx); + else + dev_info(wusbhc->dev, "device %d: %s addr %u\n", + wusb_dev->port_idx, + (addr & WUSB_DEV_ADDR_UNAUTH) ? "unauth" : "auth", + wusb_dev->addr); + + return result; +} diff --git a/drivers/usb/wusbcore/mmc.c b/drivers/usb/wusbcore/mmc.c new file mode 100644 index 000000000000..e5390b77aaaa --- /dev/null +++ b/drivers/usb/wusbcore/mmc.c @@ -0,0 +1,329 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * MMC (Microscheduled Management Command) handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * 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 +#include "wusbhc.h" + +/* Initialize the MMCIEs handling mechanism */ +int wusbhc_mmcie_create(struct wusbhc *wusbhc) +{ + u8 mmcies = wusbhc->mmcies_max; + wusbhc->mmcie = kzalloc(mmcies * sizeof(wusbhc->mmcie[0]), GFP_KERNEL); + if (wusbhc->mmcie == NULL) + return -ENOMEM; + mutex_init(&wusbhc->mmcie_mutex); + return 0; +} + +/* Release resources used by the MMCIEs handling mechanism */ +void wusbhc_mmcie_destroy(struct wusbhc *wusbhc) +{ + kfree(wusbhc->mmcie); +} + +/* + * Add or replace an MMC Wireless USB IE. + * + * @interval: See WUSB1.0[8.5.3.1] + * @repeat_cnt: See WUSB1.0[8.5.3.1] + * @handle: See WUSB1.0[8.5.3.1] + * @wuie: Pointer to the header of the WUSB IE data to add. + * MUST BE allocated in a kmalloc buffer (no stack or + * vmalloc). + * THE CALLER ALWAYS OWNS THE POINTER (we don't free it + * on remove, we just forget about it). + * @returns: 0 if ok, < 0 errno code on error. + * + * Goes over the *whole* @wusbhc->mmcie array looking for (a) the + * first free spot and (b) if @wuie is already in the array (aka: + * transmitted in the MMCs) the spot were it is. + * + * If present, we "overwrite it" (update). + * + * + * NOTE: Need special ordering rules -- see below WUSB1.0 Table 7-38. + * The host uses the handle as the 'sort' index. We + * allocate the last one always for the WUIE_ID_HOST_INFO, and + * the rest, first come first serve in inverse order. + * + * Host software must make sure that it adds the other IEs in + * the right order... the host hardware is responsible for + * placing the WCTA IEs in the right place with the other IEs + * set by host software. + * + * NOTE: we can access wusbhc->wa_descr without locking because it is + * read only. + */ +int wusbhc_mmcie_set(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + struct wuie_hdr *wuie) +{ + int result = -ENOBUFS; + struct device *dev = wusbhc->dev; + unsigned handle, itr; + + /* Search a handle, taking into account the ordering */ + mutex_lock(&wusbhc->mmcie_mutex); + switch (wuie->bIEIdentifier) { + case WUIE_ID_HOST_INFO: + /* Always last */ + handle = wusbhc->mmcies_max - 1; + break; + case WUIE_ID_ISOCH_DISCARD: + dev_err(wusbhc->dev, "Special ordering case for WUIE ID 0x%x " + "unimplemented\n", wuie->bIEIdentifier); + result = -ENOSYS; + goto error_unlock; + default: + /* search for it or find the last empty slot */ + handle = ~0; + for (itr = 0; itr < wusbhc->mmcies_max - 1; itr++) { + if (wusbhc->mmcie[itr] == wuie) { + handle = itr; + break; + } + if (wusbhc->mmcie[itr] == NULL) + handle = itr; + } + if (handle == ~0) { + if (printk_ratelimit()) + dev_err(dev, "MMC handle space exhausted\n"); + goto error_unlock; + } + } + result = (wusbhc->mmcie_add)(wusbhc, interval, repeat_cnt, handle, + wuie); + if (result >= 0) + wusbhc->mmcie[handle] = wuie; +error_unlock: + mutex_unlock(&wusbhc->mmcie_mutex); + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_mmcie_set); + +/* + * Remove an MMC IE previously added with wusbhc_mmcie_set() + * + * @wuie Pointer used to add the WUIE + */ +void wusbhc_mmcie_rm(struct wusbhc *wusbhc, struct wuie_hdr *wuie) +{ + int result; + struct device *dev = wusbhc->dev; + unsigned handle, itr; + + mutex_lock(&wusbhc->mmcie_mutex); + for (itr = 0; itr < wusbhc->mmcies_max; itr++) + if (wusbhc->mmcie[itr] == wuie) { + handle = itr; + goto found; + } + mutex_unlock(&wusbhc->mmcie_mutex); + return; + +found: + result = (wusbhc->mmcie_rm)(wusbhc, handle); + if (result == 0) + wusbhc->mmcie[itr] = NULL; + else if (printk_ratelimit()) + dev_err(dev, "MMC: Failed to remove IE %p (0x%02x)\n", + wuie, wuie->bIEIdentifier); + mutex_unlock(&wusbhc->mmcie_mutex); + return; +} +EXPORT_SYMBOL_GPL(wusbhc_mmcie_rm); + +/* + * wusbhc_start - start transmitting MMCs and accepting connections + * @wusbhc: the HC to start + * @chid: the CHID to use for this host + * + * Establishes a cluster reservation, enables device connections, and + * starts MMCs with appropriate DNTS parameters. + */ +int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid) +{ + int result; + struct device *dev = wusbhc->dev; + + WARN_ON(wusbhc->wuie_host_info != NULL); + + result = wusbhc_rsv_establish(wusbhc); + if (result < 0) { + dev_err(dev, "cannot establish cluster reservation: %d\n", + result); + goto error_rsv_establish; + } + + result = wusbhc_devconnect_start(wusbhc, chid); + if (result < 0) { + dev_err(dev, "error enabling device connections: %d\n", result); + goto error_devconnect_start; + } + + result = wusbhc_sec_start(wusbhc); + if (result < 0) { + dev_err(dev, "error starting security in the HC: %d\n", result); + goto error_sec_start; + } + /* FIXME: the choice of the DNTS parameters is somewhat + * arbitrary */ + result = wusbhc->set_num_dnts(wusbhc, 0, 15); + if (result < 0) { + dev_err(dev, "Cannot set DNTS parameters: %d\n", result); + goto error_set_num_dnts; + } + result = wusbhc->start(wusbhc); + if (result < 0) { + dev_err(dev, "error starting wusbch: %d\n", result); + goto error_wusbhc_start; + } + wusbhc->active = 1; + return 0; + +error_wusbhc_start: + wusbhc_sec_stop(wusbhc); +error_set_num_dnts: +error_sec_start: + wusbhc_devconnect_stop(wusbhc); +error_devconnect_start: + wusbhc_rsv_terminate(wusbhc); +error_rsv_establish: + return result; +} + +/* + * Disconnect all from the WUSB Channel + * + * Send a Host Disconnect IE in the MMC, wait, don't send it any more + */ +static int __wusbhc_host_disconnect_ie(struct wusbhc *wusbhc) +{ + int result = -ENOMEM; + struct wuie_host_disconnect *host_disconnect_ie; + might_sleep(); + host_disconnect_ie = kmalloc(sizeof(*host_disconnect_ie), GFP_KERNEL); + if (host_disconnect_ie == NULL) + goto error_alloc; + host_disconnect_ie->hdr.bLength = sizeof(*host_disconnect_ie); + host_disconnect_ie->hdr.bIEIdentifier = WUIE_ID_HOST_DISCONNECT; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &host_disconnect_ie->hdr); + if (result < 0) + goto error_mmcie_set; + + /* WUSB1.0[8.5.3.1 & 7.5.2] */ + msleep(100); + wusbhc_mmcie_rm(wusbhc, &host_disconnect_ie->hdr); +error_mmcie_set: + kfree(host_disconnect_ie); +error_alloc: + return result; +} + +/* + * wusbhc_stop - stop transmitting MMCs + * @wusbhc: the HC to stop + * + * Send a Host Disconnect IE, wait, remove all the MMCs (stop sending MMCs). + * + * If we can't allocate a Host Stop IE, screw it, we don't notify the + * devices we are disconnecting... + */ +void wusbhc_stop(struct wusbhc *wusbhc) +{ + if (wusbhc->active) { + wusbhc->active = 0; + wusbhc->stop(wusbhc); + wusbhc_sec_stop(wusbhc); + __wusbhc_host_disconnect_ie(wusbhc); + wusbhc_devconnect_stop(wusbhc); + wusbhc_rsv_terminate(wusbhc); + } +} +EXPORT_SYMBOL_GPL(wusbhc_stop); + +/* + * Change the CHID in a WUSB Channel + * + * If it is just a new CHID, send a Host Disconnect IE and then change + * the CHID IE. + */ +static int __wusbhc_chid_change(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid) +{ + int result = -ENOSYS; + struct device *dev = wusbhc->dev; + dev_err(dev, "%s() not implemented yet\n", __func__); + return result; + + BUG_ON(wusbhc->wuie_host_info == NULL); + __wusbhc_host_disconnect_ie(wusbhc); + wusbhc->wuie_host_info->CHID = *chid; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->wuie_host_info->hdr); + if (result < 0) + dev_err(dev, "Can't update Host Info WUSB IE: %d\n", result); + return result; +} + +/* + * Set/reset/update a new CHID + * + * Depending on the previous state of the MMCs, start, stop or change + * the sent MMC. This effectively switches the host controller on and + * off (radio wise). + */ +int wusbhc_chid_set(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid) +{ + int result = 0; + + if (memcmp(chid, &wusb_ckhdid_zero, sizeof(chid)) == 0) + chid = NULL; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + if (chid) + result = __wusbhc_chid_change(wusbhc, chid); + else + wusbhc_stop(wusbhc); + } else { + if (chid) + wusbhc_start(wusbhc, chid); + } + mutex_unlock(&wusbhc->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_chid_set); -- cgit v1.2.2 From d59db761b8559f07a7161ca3387d6c6949667ede Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:25 +0100 Subject: wusb: add the Wireless USB core (security) Add the WUSB security (authentication) code. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/crypto.c | 538 +++++++++++++++++++++++++++++++++ drivers/usb/wusbcore/security.c | 642 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1180 insertions(+) create mode 100644 drivers/usb/wusbcore/crypto.c create mode 100644 drivers/usb/wusbcore/security.c (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/crypto.c b/drivers/usb/wusbcore/crypto.c new file mode 100644 index 000000000000..c36c4389baae --- /dev/null +++ b/drivers/usb/wusbcore/crypto.c @@ -0,0 +1,538 @@ +/* + * Ultra Wide Band + * AES-128 CCM Encryption + * + * Copyright (C) 2007 Intel Corporation + * Inaky Perez-Gonzalez + * + * 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 +#include +#include +#include +#include +#include +#define D_LOCAL 0 +#include + + +/* + * Block of data, as understood by AES-CCM + * + * The code assumes this structure is nothing but a 16 byte array + * (packed in a struct to avoid common mess ups that I usually do with + * arrays and enforcing type checking). + */ +struct aes_ccm_block { + u8 data[16]; +} __attribute__((packed)); + +/* + * Counter-mode Blocks (WUSB1.0[6.4]) + * + * According to CCM (or so it seems), for the purpose of calculating + * the MIC, the message is broken in N counter-mode blocks, B0, B1, + * ... BN. + * + * B0 contains flags, the CCM nonce and l(m). + * + * B1 contains l(a), the MAC header, the encryption offset and padding. + * + * If EO is nonzero, additional blocks are built from payload bytes + * until EO is exahusted (FIXME: padding to 16 bytes, I guess). The + * padding is not xmitted. + */ + +/* WUSB1.0[T6.4] */ +struct aes_ccm_b0 { + u8 flags; /* 0x59, per CCM spec */ + struct aes_ccm_nonce ccm_nonce; + __be16 lm; +} __attribute__((packed)); + +/* WUSB1.0[T6.5] */ +struct aes_ccm_b1 { + __be16 la; + u8 mac_header[10]; + __le16 eo; + u8 security_reserved; /* This is always zero */ + u8 padding; /* 0 */ +} __attribute__((packed)); + +/* + * Encryption Blocks (WUSB1.0[6.4.4]) + * + * CCM uses Ax blocks to generate a keystream with which the MIC and + * the message's payload are encoded. A0 always encrypts/decrypts the + * MIC. Ax (x>0) are used for the sucesive payload blocks. + * + * The x is the counter, and is increased for each block. + */ +struct aes_ccm_a { + u8 flags; /* 0x01, per CCM spec */ + struct aes_ccm_nonce ccm_nonce; + __be16 counter; /* Value of x */ +} __attribute__((packed)); + +static void bytewise_xor(void *_bo, const void *_bi1, const void *_bi2, + size_t size) +{ + u8 *bo = _bo; + const u8 *bi1 = _bi1, *bi2 = _bi2; + size_t itr; + for (itr = 0; itr < size; itr++) + bo[itr] = bi1[itr] ^ bi2[itr]; +} + +/* + * CC-MAC function WUSB1.0[6.5] + * + * Take a data string and produce the encrypted CBC Counter-mode MIC + * + * Note the names for most function arguments are made to (more or + * less) match those used in the pseudo-function definition given in + * WUSB1.0[6.5]. + * + * @tfm_cbc: CBC(AES) blkcipher handle (initialized) + * + * @tfm_aes: AES cipher handle (initialized) + * + * @mic: buffer for placing the computed MIC (Message Integrity + * Code). This is exactly 8 bytes, and we expect the buffer to + * be at least eight bytes in length. + * + * @key: 128 bit symmetric key + * + * @n: CCM nonce + * + * @a: ASCII string, 14 bytes long (I guess zero padded if needed; + * we use exactly 14 bytes). + * + * @b: data stream to be processed; cannot be a global or const local + * (will confuse the scatterlists) + * + * @blen: size of b... + * + * Still not very clear how this is done, but looks like this: we + * create block B0 (as WUSB1.0[6.5] says), then we AES-crypt it with + * @key. We bytewise xor B0 with B1 (1) and AES-crypt that. Then we + * take the payload and divide it in blocks (16 bytes), xor them with + * the previous crypto result (16 bytes) and crypt it, repeat the next + * block with the output of the previous one, rinse wash (I guess this + * is what AES CBC mode means...but I truly have no idea). So we use + * the CBC(AES) blkcipher, that does precisely that. The IV (Initial + * Vector) is 16 bytes and is set to zero, so + * + * See rfc3610. Linux crypto has a CBC implementation, but the + * documentation is scarce, to say the least, and the example code is + * so intricated that is difficult to understand how things work. Most + * of this is guess work -- bite me. + * + * (1) Created as 6.5 says, again, using as l(a) 'Blen + 14', and + * using the 14 bytes of @a to fill up + * b1.{mac_header,e0,security_reserved,padding}. + * + * NOTE: The definiton of l(a) in WUSB1.0[6.5] vs the definition of + * l(m) is orthogonal, they bear no relationship, so it is not + * in conflict with the parameter's relation that + * WUSB1.0[6.4.2]) defines. + * + * NOTE: WUSB1.0[A.1]: Host Nonce is missing a nibble? (1e); fixed in + * first errata released on 2005/07. + * + * NOTE: we need to clean IV to zero at each invocation to make sure + * we start with a fresh empty Initial Vector, so that the CBC + * works ok. + * + * NOTE: blen is not aligned to a block size, we'll pad zeros, that's + * what sg[4] is for. Maybe there is a smarter way to do this. + */ +static int wusb_ccm_mac(struct crypto_blkcipher *tfm_cbc, + struct crypto_cipher *tfm_aes, void *mic, + const struct aes_ccm_nonce *n, + const struct aes_ccm_label *a, const void *b, + size_t blen) +{ + int result = 0; + struct blkcipher_desc desc; + struct aes_ccm_b0 b0; + struct aes_ccm_b1 b1; + struct aes_ccm_a ax; + struct scatterlist sg[4], sg_dst; + void *iv, *dst_buf; + size_t ivsize, dst_size; + const u8 bzero[16] = { 0 }; + size_t zero_padding; + + d_fnstart(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, " + "n %p, a %p, b %p, blen %zu)\n", + tfm_cbc, tfm_aes, mic, n, a, b, blen); + /* + * These checks should be compile time optimized out + * ensure @a fills b1's mac_header and following fields + */ + WARN_ON(sizeof(*a) != sizeof(b1) - sizeof(b1.la)); + WARN_ON(sizeof(b0) != sizeof(struct aes_ccm_block)); + WARN_ON(sizeof(b1) != sizeof(struct aes_ccm_block)); + WARN_ON(sizeof(ax) != sizeof(struct aes_ccm_block)); + + result = -ENOMEM; + zero_padding = sizeof(struct aes_ccm_block) + - blen % sizeof(struct aes_ccm_block); + zero_padding = blen % sizeof(struct aes_ccm_block); + if (zero_padding) + zero_padding = sizeof(struct aes_ccm_block) - zero_padding; + dst_size = blen + sizeof(b0) + sizeof(b1) + zero_padding; + dst_buf = kzalloc(dst_size, GFP_KERNEL); + if (dst_buf == NULL) { + printk(KERN_ERR "E: can't alloc destination buffer\n"); + goto error_dst_buf; + } + + iv = crypto_blkcipher_crt(tfm_cbc)->iv; + ivsize = crypto_blkcipher_ivsize(tfm_cbc); + memset(iv, 0, ivsize); + + /* Setup B0 */ + b0.flags = 0x59; /* Format B0 */ + b0.ccm_nonce = *n; + b0.lm = cpu_to_be16(0); /* WUSB1.0[6.5] sez l(m) is 0 */ + + /* Setup B1 + * + * The WUSB spec is anything but clear! WUSB1.0[6.5] + * says that to initialize B1 from A with 'l(a) = blen + + * 14'--after clarification, it means to use A's contents + * for MAC Header, EO, sec reserved and padding. + */ + b1.la = cpu_to_be16(blen + 14); + memcpy(&b1.mac_header, a, sizeof(*a)); + + d_printf(4, NULL, "I: B0 (%zu bytes)\n", sizeof(b0)); + d_dump(4, NULL, &b0, sizeof(b0)); + d_printf(4, NULL, "I: B1 (%zu bytes)\n", sizeof(b1)); + d_dump(4, NULL, &b1, sizeof(b1)); + d_printf(4, NULL, "I: B (%zu bytes)\n", blen); + d_dump(4, NULL, b, blen); + d_printf(4, NULL, "I: B 0-padding (%zu bytes)\n", zero_padding); + d_printf(4, NULL, "D: IV before crypto (%zu)\n", ivsize); + d_dump(4, NULL, iv, ivsize); + + sg_init_table(sg, ARRAY_SIZE(sg)); + sg_set_buf(&sg[0], &b0, sizeof(b0)); + sg_set_buf(&sg[1], &b1, sizeof(b1)); + sg_set_buf(&sg[2], b, blen); + /* 0 if well behaved :) */ + sg_set_buf(&sg[3], bzero, zero_padding); + sg_init_one(&sg_dst, dst_buf, dst_size); + + desc.tfm = tfm_cbc; + desc.flags = 0; + result = crypto_blkcipher_encrypt(&desc, &sg_dst, sg, dst_size); + if (result < 0) { + printk(KERN_ERR "E: can't compute CBC-MAC tag (MIC): %d\n", + result); + goto error_cbc_crypt; + } + d_printf(4, NULL, "D: MIC tag\n"); + d_dump(4, NULL, iv, ivsize); + + /* Now we crypt the MIC Tag (*iv) with Ax -- values per WUSB1.0[6.5] + * The procedure is to AES crypt the A0 block and XOR the MIC + * Tag agains it; we only do the first 8 bytes and place it + * directly in the destination buffer. + * + * POS Crypto API: size is assumed to be AES's block size. + * Thanks for documenting it -- tip taken from airo.c + */ + ax.flags = 0x01; /* as per WUSB 1.0 spec */ + ax.ccm_nonce = *n; + ax.counter = 0; + crypto_cipher_encrypt_one(tfm_aes, (void *)&ax, (void *)&ax); + bytewise_xor(mic, &ax, iv, 8); + d_printf(4, NULL, "D: CTR[MIC]\n"); + d_dump(4, NULL, &ax, 8); + d_printf(4, NULL, "D: CCM-MIC tag\n"); + d_dump(4, NULL, mic, 8); + result = 8; +error_cbc_crypt: + kfree(dst_buf); +error_dst_buf: + d_fnend(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, " + "n %p, a %p, b %p, blen %zu)\n", + tfm_cbc, tfm_aes, mic, n, a, b, blen); + return result; +} + +/* + * WUSB Pseudo Random Function (WUSB1.0[6.5]) + * + * @b: buffer to the source data; cannot be a global or const local + * (will confuse the scatterlists) + */ +ssize_t wusb_prf(void *out, size_t out_size, + const u8 key[16], const struct aes_ccm_nonce *_n, + const struct aes_ccm_label *a, + const void *b, size_t blen, size_t len) +{ + ssize_t result, bytes = 0, bitr; + struct aes_ccm_nonce n = *_n; + struct crypto_blkcipher *tfm_cbc; + struct crypto_cipher *tfm_aes; + u64 sfn = 0; + __le64 sfn_le; + + d_fnstart(3, NULL, "(out %p, out_size %zu, key %p, _n %p, " + "a %p, b %p, blen %zu, len %zu)\n", out, out_size, + key, _n, a, b, blen, len); + + tfm_cbc = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm_cbc)) { + result = PTR_ERR(tfm_cbc); + printk(KERN_ERR "E: can't load CBC(AES): %d\n", (int)result); + goto error_alloc_cbc; + } + result = crypto_blkcipher_setkey(tfm_cbc, key, 16); + if (result < 0) { + printk(KERN_ERR "E: can't set CBC key: %d\n", (int)result); + goto error_setkey_cbc; + } + + tfm_aes = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm_aes)) { + result = PTR_ERR(tfm_aes); + printk(KERN_ERR "E: can't load AES: %d\n", (int)result); + goto error_alloc_aes; + } + result = crypto_cipher_setkey(tfm_aes, key, 16); + if (result < 0) { + printk(KERN_ERR "E: can't set AES key: %d\n", (int)result); + goto error_setkey_aes; + } + + for (bitr = 0; bitr < (len + 63) / 64; bitr++) { + sfn_le = cpu_to_le64(sfn++); + memcpy(&n.sfn, &sfn_le, sizeof(n.sfn)); /* n.sfn++... */ + result = wusb_ccm_mac(tfm_cbc, tfm_aes, out + bytes, + &n, a, b, blen); + if (result < 0) + goto error_ccm_mac; + bytes += result; + } + result = bytes; +error_ccm_mac: +error_setkey_aes: + crypto_free_cipher(tfm_aes); +error_alloc_aes: +error_setkey_cbc: + crypto_free_blkcipher(tfm_cbc); +error_alloc_cbc: + d_fnend(3, NULL, "(out %p, out_size %zu, key %p, _n %p, " + "a %p, b %p, blen %zu, len %zu) = %d\n", out, out_size, + key, _n, a, b, blen, len, (int)bytes); + return result; +} + +/* WUSB1.0[A.2] test vectors */ +static const u8 stv_hsmic_key[16] = { + 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d, + 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f +}; + +static const struct aes_ccm_nonce stv_hsmic_n = { + .sfn = { 0 }, + .tkid = { 0x76, 0x98, 0x01, }, + .dest_addr = { .data = { 0xbe, 0x00 } }, + .src_addr = { .data = { 0x76, 0x98 } }, +}; + +/* + * Out-of-band MIC Generation verification code + * + */ +static int wusb_oob_mic_verify(void) +{ + int result; + u8 mic[8]; + /* WUSB1.0[A.2] test vectors + * + * Need to keep it in the local stack as GCC 4.1.3something + * messes up and generates noise. + */ + struct usb_handshake stv_hsmic_hs = { + .bMessageNumber = 2, + .bStatus = 00, + .tTKID = { 0x76, 0x98, 0x01 }, + .bReserved = 00, + .CDID = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f }, + .nonce = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f }, + .MIC = { 0x75, 0x6a, 0x97, 0x51, 0x0c, 0x8c, + 0x14, 0x7b } , + }; + size_t hs_size; + + result = wusb_oob_mic(mic, stv_hsmic_key, &stv_hsmic_n, &stv_hsmic_hs); + if (result < 0) + printk(KERN_ERR "E: WUSB OOB MIC test: failed: %d\n", result); + else if (memcmp(stv_hsmic_hs.MIC, mic, sizeof(mic))) { + printk(KERN_ERR "E: OOB MIC test: " + "mismatch between MIC result and WUSB1.0[A2]\n"); + hs_size = sizeof(stv_hsmic_hs) - sizeof(stv_hsmic_hs.MIC); + printk(KERN_ERR "E: Handshake2 in: (%zu bytes)\n", hs_size); + dump_bytes(NULL, &stv_hsmic_hs, hs_size); + printk(KERN_ERR "E: CCM Nonce in: (%zu bytes)\n", + sizeof(stv_hsmic_n)); + dump_bytes(NULL, &stv_hsmic_n, sizeof(stv_hsmic_n)); + printk(KERN_ERR "E: MIC out:\n"); + dump_bytes(NULL, mic, sizeof(mic)); + printk(KERN_ERR "E: MIC out (from WUSB1.0[A.2]):\n"); + dump_bytes(NULL, stv_hsmic_hs.MIC, sizeof(stv_hsmic_hs.MIC)); + result = -EINVAL; + } else + result = 0; + return result; +} + +/* + * Test vectors for Key derivation + * + * These come from WUSB1.0[6.5.1], the vectors in WUSB1.0[A.1] + * (errata corrected in 2005/07). + */ +static const u8 stv_key_a1[16] __attribute__ ((__aligned__(4))) = { + 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, + 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f +}; + +static const struct aes_ccm_nonce stv_keydvt_n_a1 = { + .sfn = { 0 }, + .tkid = { 0x76, 0x98, 0x01, }, + .dest_addr = { .data = { 0xbe, 0x00 } }, + .src_addr = { .data = { 0x76, 0x98 } }, +}; + +static const struct wusb_keydvt_out stv_keydvt_out_a1 = { + .kck = { + 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d, + 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f + }, + .ptk = { + 0xc8, 0x70, 0x62, 0x82, 0xb6, 0x7c, 0xe9, 0x06, + 0x7b, 0xc5, 0x25, 0x69, 0xf2, 0x36, 0x61, 0x2d + } +}; + +/* + * Performa a test to make sure we match the vectors defined in + * WUSB1.0[A.1](Errata2006/12) + */ +static int wusb_key_derive_verify(void) +{ + int result = 0; + struct wusb_keydvt_out keydvt_out; + /* These come from WUSB1.0[A.1] + 2006/12 errata + * NOTE: can't make this const or global -- somehow it seems + * the scatterlists for crypto get confused and we get + * bad data. There is no doc on this... */ + struct wusb_keydvt_in stv_keydvt_in_a1 = { + .hnonce = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }, + .dnonce = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f + } + }; + + result = wusb_key_derive(&keydvt_out, stv_key_a1, &stv_keydvt_n_a1, + &stv_keydvt_in_a1); + if (result < 0) + printk(KERN_ERR "E: WUSB key derivation test: " + "derivation failed: %d\n", result); + if (memcmp(&stv_keydvt_out_a1, &keydvt_out, sizeof(keydvt_out))) { + printk(KERN_ERR "E: WUSB key derivation test: " + "mismatch between key derivation result " + "and WUSB1.0[A1] Errata 2006/12\n"); + printk(KERN_ERR "E: keydvt in: key (%zu bytes)\n", + sizeof(stv_key_a1)); + dump_bytes(NULL, stv_key_a1, sizeof(stv_key_a1)); + printk(KERN_ERR "E: keydvt in: nonce (%zu bytes)\n", + sizeof(stv_keydvt_n_a1)); + dump_bytes(NULL, &stv_keydvt_n_a1, sizeof(stv_keydvt_n_a1)); + printk(KERN_ERR "E: keydvt in: hnonce & dnonce (%zu bytes)\n", + sizeof(stv_keydvt_in_a1)); + dump_bytes(NULL, &stv_keydvt_in_a1, sizeof(stv_keydvt_in_a1)); + printk(KERN_ERR "E: keydvt out: KCK\n"); + dump_bytes(NULL, &keydvt_out.kck, sizeof(keydvt_out.kck)); + printk(KERN_ERR "E: keydvt out: PTK\n"); + dump_bytes(NULL, &keydvt_out.ptk, sizeof(keydvt_out.ptk)); + result = -EINVAL; + } else + result = 0; + return result; +} + +/* + * Initialize crypto system + * + * FIXME: we do nothing now, other than verifying. Later on we'll + * cache the encryption stuff, so that's why we have a separate init. + */ +int wusb_crypto_init(void) +{ + int result; + + result = wusb_key_derive_verify(); + if (result < 0) + return result; + return wusb_oob_mic_verify(); +} + +void wusb_crypto_exit(void) +{ + /* FIXME: free cached crypto transforms */ +} diff --git a/drivers/usb/wusbcore/security.c b/drivers/usb/wusbcore/security.c new file mode 100644 index 000000000000..a101cad6a8d4 --- /dev/null +++ b/drivers/usb/wusbcore/security.c @@ -0,0 +1,642 @@ +/* + * Wireless USB Host Controller + * Security support: encryption enablement, etc + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * 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 +#include +#include +#include "wusbhc.h" + +/* + * DEBUG & SECURITY WARNING!!!! + * + * If you enable this past 1, the debug code will weaken the + * cryptographic safety of the system (on purpose, for debugging). + * + * Weaken means: + * we print secret keys and intermediate values all the way, + */ +#undef D_LOCAL +#define D_LOCAL 2 +#include + +static void wusbhc_set_gtk_callback(struct urb *urb); +static void wusbhc_gtk_rekey_done_work(struct work_struct *work); + +int wusbhc_sec_create(struct wusbhc *wusbhc) +{ + wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data); + wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY; + wusbhc->gtk.descr.bReserved = 0; + + wusbhc->gtk_index = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + INIT_WORK(&wusbhc->gtk_rekey_done_work, wusbhc_gtk_rekey_done_work); + + return 0; +} + + +/* Called when the HC is destroyed */ +void wusbhc_sec_destroy(struct wusbhc *wusbhc) +{ +} + + +/** + * wusbhc_next_tkid - generate a new, currently unused, TKID + * @wusbhc: the WUSB host controller + * @wusb_dev: the device whose PTK the TKID is for + * (or NULL for a TKID for a GTK) + * + * The generated TKID consist of two parts: the device's authenicated + * address (or 0 or a GTK); and an incrementing number. This ensures + * that TKIDs cannot be shared between devices and by the time the + * incrementing number wraps around the older TKIDs will no longer be + * in use (a maximum of two keys may be active at any one time). + */ +static u32 wusbhc_next_tkid(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + u32 *tkid; + u32 addr; + + if (wusb_dev == NULL) { + tkid = &wusbhc->gtk_tkid; + addr = 0; + } else { + tkid = &wusb_port_by_idx(wusbhc, wusb_dev->port_idx)->ptk_tkid; + addr = wusb_dev->addr & 0x7f; + } + + *tkid = (addr << 8) | ((*tkid + 1) & 0xff); + + return *tkid; +} + +static void wusbhc_generate_gtk(struct wusbhc *wusbhc) +{ + const size_t key_size = sizeof(wusbhc->gtk.data); + u32 tkid; + + tkid = wusbhc_next_tkid(wusbhc, NULL); + + wusbhc->gtk.descr.tTKID[0] = (tkid >> 0) & 0xff; + wusbhc->gtk.descr.tTKID[1] = (tkid >> 8) & 0xff; + wusbhc->gtk.descr.tTKID[2] = (tkid >> 16) & 0xff; + + get_random_bytes(wusbhc->gtk.descr.bKeyData, key_size); +} + +/** + * wusbhc_sec_start - start the security management process + * @wusbhc: the WUSB host controller + * + * Generate and set an initial GTK on the host controller. + * + * Called when the HC is started. + */ +int wusbhc_sec_start(struct wusbhc *wusbhc) +{ + const size_t key_size = sizeof(wusbhc->gtk.data); + int result; + + wusbhc_generate_gtk(wusbhc); + + result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, + &wusbhc->gtk.descr.bKeyData, key_size); + if (result < 0) + dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n", + result); + + return result; +} + +/** + * wusbhc_sec_stop - stop the security management process + * @wusbhc: the WUSB host controller + * + * Wait for any pending GTK rekeys to stop. + */ +void wusbhc_sec_stop(struct wusbhc *wusbhc) +{ + cancel_work_sync(&wusbhc->gtk_rekey_done_work); +} + + +/** @returns encryption type name */ +const char *wusb_et_name(u8 x) +{ + switch (x) { + case USB_ENC_TYPE_UNSECURE: return "unsecure"; + case USB_ENC_TYPE_WIRED: return "wired"; + case USB_ENC_TYPE_CCM_1: return "CCM-1"; + case USB_ENC_TYPE_RSA_1: return "RSA-1"; + default: return "unknown"; + } +} +EXPORT_SYMBOL_GPL(wusb_et_name); + +/* + * Set the device encryption method + * + * We tell the device which encryption method to use; we do this when + * setting up the device's security. + */ +static int wusb_dev_set_encryption(struct usb_device *usb_dev, int value) +{ + int result; + struct device *dev = &usb_dev->dev; + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + + if (value) { + value = wusb_dev->ccm1_etd.bEncryptionValue; + } else { + /* FIXME: should be wusb_dev->etd[UNSECURE].bEncryptionValue */ + value = 0; + } + /* Set device's */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ENCRYPTION, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + value, 0, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Can't set device's WUSB encryption to " + "%s (value %d): %d\n", + wusb_et_name(wusb_dev->ccm1_etd.bEncryptionType), + wusb_dev->ccm1_etd.bEncryptionValue, result); + return result; +} + +/* + * Set the GTK to be used by a device. + * + * The device must be authenticated. + */ +static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct usb_device *usb_dev = wusb_dev->usb_dev; + + return usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_DT_KEY << 8 | wusbhc->gtk_index, 0, + &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, + 1000); +} + + +/* FIXME: prototype for adding security */ +int wusb_dev_sec_add(struct wusbhc *wusbhc, + struct usb_device *usb_dev, struct wusb_dev *wusb_dev) +{ + int result, bytes, secd_size; + struct device *dev = &usb_dev->dev; + struct usb_security_descriptor secd; + const struct usb_encryption_descriptor *etd, *ccm1_etd = NULL; + void *secd_buf; + const void *itr, *top; + char buf[64]; + + d_fnstart(3, dev, "(usb_dev %p, wusb_dev %p)\n", usb_dev, wusb_dev); + result = usb_get_descriptor(usb_dev, USB_DT_SECURITY, + 0, &secd, sizeof(secd)); + if (result < sizeof(secd)) { + dev_err(dev, "Can't read security descriptor or " + "not enough data: %d\n", result); + goto error_secd; + } + secd_size = le16_to_cpu(secd.wTotalLength); + d_printf(5, dev, "got %d bytes of sec descriptor, total is %d\n", + result, secd_size); + secd_buf = kmalloc(secd_size, GFP_KERNEL); + if (secd_buf == NULL) { + dev_err(dev, "Can't allocate space for security descriptors\n"); + goto error_secd_alloc; + } + result = usb_get_descriptor(usb_dev, USB_DT_SECURITY, + 0, secd_buf, secd_size); + if (result < secd_size) { + dev_err(dev, "Can't read security descriptor or " + "not enough data: %d\n", result); + goto error_secd_all; + } + d_printf(5, dev, "got %d bytes of sec descriptors\n", result); + bytes = 0; + itr = secd_buf + sizeof(secd); + top = secd_buf + result; + while (itr < top) { + etd = itr; + if (top - itr < sizeof(*etd)) { + dev_err(dev, "BUG: bad device security descriptor; " + "not enough data (%zu vs %zu bytes left)\n", + top - itr, sizeof(*etd)); + break; + } + if (etd->bLength < sizeof(*etd)) { + dev_err(dev, "BUG: bad device encryption descriptor; " + "descriptor is too short " + "(%u vs %zu needed)\n", + etd->bLength, sizeof(*etd)); + break; + } + itr += etd->bLength; + bytes += snprintf(buf + bytes, sizeof(buf) - bytes, + "%s (0x%02x/%02x) ", + wusb_et_name(etd->bEncryptionType), + etd->bEncryptionValue, etd->bAuthKeyIndex); + if (etd->bEncryptionType == USB_ENC_TYPE_CCM_1) + ccm1_etd = etd; + } + /* This code only supports CCM1 as of now. */ + /* FIXME: user has to choose which sec mode to use? + * In theory we want CCM */ + if (ccm1_etd == NULL) { + dev_err(dev, "WUSB device doesn't support CCM1 encryption, " + "can't use!\n"); + result = -EINVAL; + goto error_no_ccm1; + } + wusb_dev->ccm1_etd = *ccm1_etd; + dev_info(dev, "supported encryption: %s; using %s (0x%02x/%02x)\n", + buf, wusb_et_name(ccm1_etd->bEncryptionType), + ccm1_etd->bEncryptionValue, ccm1_etd->bAuthKeyIndex); + result = 0; + kfree(secd_buf); +out: + d_fnend(3, dev, "(usb_dev %p, wusb_dev %p) = %d\n", + usb_dev, wusb_dev, result); + return result; + + +error_no_ccm1: +error_secd_all: + kfree(secd_buf); +error_secd_alloc: +error_secd: + goto out; +} + +void wusb_dev_sec_rm(struct wusb_dev *wusb_dev) +{ + /* Nothing so far */ +} + +static void hs_printk(unsigned level, struct device *dev, + struct usb_handshake *hs) +{ + d_printf(level, dev, + " bMessageNumber: %u\n" + " bStatus: %u\n" + " tTKID: %02x %02x %02x\n" + " CDID: %02x %02x %02x %02x %02x %02x %02x %02x\n" + " %02x %02x %02x %02x %02x %02x %02x %02x\n" + " nonce: %02x %02x %02x %02x %02x %02x %02x %02x\n" + " %02x %02x %02x %02x %02x %02x %02x %02x\n" + " MIC: %02x %02x %02x %02x %02x %02x %02x %02x\n", + hs->bMessageNumber, hs->bStatus, + hs->tTKID[2], hs->tTKID[1], hs->tTKID[0], + hs->CDID[0], hs->CDID[1], hs->CDID[2], hs->CDID[3], + hs->CDID[4], hs->CDID[5], hs->CDID[6], hs->CDID[7], + hs->CDID[8], hs->CDID[9], hs->CDID[10], hs->CDID[11], + hs->CDID[12], hs->CDID[13], hs->CDID[14], hs->CDID[15], + hs->nonce[0], hs->nonce[1], hs->nonce[2], hs->nonce[3], + hs->nonce[4], hs->nonce[5], hs->nonce[6], hs->nonce[7], + hs->nonce[8], hs->nonce[9], hs->nonce[10], hs->nonce[11], + hs->nonce[12], hs->nonce[13], hs->nonce[14], hs->nonce[15], + hs->MIC[0], hs->MIC[1], hs->MIC[2], hs->MIC[3], + hs->MIC[4], hs->MIC[5], hs->MIC[6], hs->MIC[7]); +} + +/** + * Update the address of an unauthenticated WUSB device + * + * Once we have successfully authenticated, we take it to addr0 state + * and then to a normal address. + * + * Before the device's address (as known by it) was usb_dev->devnum | + * 0x80 (unauthenticated address). With this we update it to usb_dev->devnum. + */ +static int wusb_dev_update_address(struct wusbhc *wusbhc, + struct wusb_dev *wusb_dev) +{ + int result = -ENOMEM; + struct usb_device *usb_dev = wusb_dev->usb_dev; + struct device *dev = &usb_dev->dev; + u8 new_address = wusb_dev->addr & 0x7F; + + /* Set address 0 */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ADDRESS, 0, + 0, 0, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "auth failed: can't set address 0: %d\n", + result); + goto error_addr0; + } + result = wusb_set_dev_addr(wusbhc, wusb_dev, 0); + if (result < 0) + goto error_addr0; + usb_ep0_reinit(usb_dev); + + /* Set new (authenticated) address. */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ADDRESS, 0, + new_address, 0, NULL, 0, + 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "auth failed: can't set address %u: %d\n", + new_address, result); + goto error_addr; + } + result = wusb_set_dev_addr(wusbhc, wusb_dev, new_address); + if (result < 0) + goto error_addr; + usb_ep0_reinit(usb_dev); + usb_dev->authenticated = 1; +error_addr: +error_addr0: + return result; +} + +/* + * + * + */ +/* FIXME: split and cleanup */ +int wusb_dev_4way_handshake(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, + struct wusb_ckhdid *ck) +{ + int result = -ENOMEM; + struct usb_device *usb_dev = wusb_dev->usb_dev; + struct device *dev = &usb_dev->dev; + u32 tkid; + __le32 tkid_le; + struct usb_handshake *hs; + struct aes_ccm_nonce ccm_n; + u8 mic[8]; + struct wusb_keydvt_in keydvt_in; + struct wusb_keydvt_out keydvt_out; + + hs = kzalloc(3*sizeof(hs[0]), GFP_KERNEL); + if (hs == NULL) { + dev_err(dev, "can't allocate handshake data\n"); + goto error_kzalloc; + } + + /* We need to turn encryption before beginning the 4way + * hshake (WUSB1.0[.3.2.2]) */ + result = wusb_dev_set_encryption(usb_dev, 1); + if (result < 0) + goto error_dev_set_encryption; + + tkid = wusbhc_next_tkid(wusbhc, wusb_dev); + tkid_le = cpu_to_le32(tkid); + + hs[0].bMessageNumber = 1; + hs[0].bStatus = 0; + memcpy(hs[0].tTKID, &tkid_le, sizeof(hs[0].tTKID)); + hs[0].bReserved = 0; + memcpy(hs[0].CDID, &wusb_dev->cdid, sizeof(hs[0].CDID)); + get_random_bytes(&hs[0].nonce, sizeof(hs[0].nonce)); + memset(hs[0].MIC, 0, sizeof(hs[0].MIC)); /* Per WUSB1.0[T7-22] */ + + d_printf(1, dev, "I: sending hs1:\n"); + hs_printk(2, dev, &hs[0]); + + result = usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_HANDSHAKE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 1, 0, &hs[0], sizeof(hs[0]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake1: request failed: %d\n", result); + goto error_hs1; + } + + /* Handshake 2, from the device -- need to verify fields */ + result = usb_control_msg( + usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_GET_HANDSHAKE, + USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 2, 0, &hs[1], sizeof(hs[1]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake2: request failed: %d\n", result); + goto error_hs2; + } + d_printf(1, dev, "got HS2:\n"); + hs_printk(2, dev, &hs[1]); + + result = -EINVAL; + if (hs[1].bMessageNumber != 2) { + dev_err(dev, "Handshake2 failed: bad message number %u\n", + hs[1].bMessageNumber); + goto error_hs2; + } + if (hs[1].bStatus != 0) { + dev_err(dev, "Handshake2 failed: bad status %u\n", + hs[1].bStatus); + goto error_hs2; + } + if (memcmp(hs[0].tTKID, hs[1].tTKID, sizeof(hs[0].tTKID))) { + dev_err(dev, "Handshake2 failed: TKID mismatch " + "(#1 0x%02x%02x%02x vs #2 0x%02x%02x%02x)\n", + hs[0].tTKID[0], hs[0].tTKID[1], hs[0].tTKID[2], + hs[1].tTKID[0], hs[1].tTKID[1], hs[1].tTKID[2]); + goto error_hs2; + } + if (memcmp(hs[0].CDID, hs[1].CDID, sizeof(hs[0].CDID))) { + dev_err(dev, "Handshake2 failed: CDID mismatch\n"); + goto error_hs2; + } + + /* Setup the CCM nonce */ + memset(&ccm_n.sfn, 0, sizeof(ccm_n.sfn)); /* Per WUSB1.0[6.5.2] */ + memcpy(ccm_n.tkid, &tkid_le, sizeof(ccm_n.tkid)); + ccm_n.src_addr = wusbhc->uwb_rc->uwb_dev.dev_addr; + ccm_n.dest_addr.data[0] = wusb_dev->addr; + ccm_n.dest_addr.data[1] = 0; + + /* Derive the KCK and PTK from CK, the CCM, H and D nonces */ + memcpy(keydvt_in.hnonce, hs[0].nonce, sizeof(keydvt_in.hnonce)); + memcpy(keydvt_in.dnonce, hs[1].nonce, sizeof(keydvt_in.dnonce)); + result = wusb_key_derive(&keydvt_out, ck->data, &ccm_n, &keydvt_in); + if (result < 0) { + dev_err(dev, "Handshake2 failed: cannot derive keys: %d\n", + result); + goto error_hs2; + } + d_printf(2, dev, "KCK:\n"); + d_dump(2, dev, keydvt_out.kck, sizeof(keydvt_out.kck)); + d_printf(2, dev, "PTK:\n"); + d_dump(2, dev, keydvt_out.ptk, sizeof(keydvt_out.ptk)); + + /* Compute MIC and verify it */ + result = wusb_oob_mic(mic, keydvt_out.kck, &ccm_n, &hs[1]); + if (result < 0) { + dev_err(dev, "Handshake2 failed: cannot compute MIC: %d\n", + result); + goto error_hs2; + } + + d_printf(2, dev, "MIC:\n"); + d_dump(2, dev, mic, sizeof(mic)); + if (memcmp(hs[1].MIC, mic, sizeof(hs[1].MIC))) { + dev_err(dev, "Handshake2 failed: MIC mismatch\n"); + goto error_hs2; + } + + /* Send Handshake3 */ + hs[2].bMessageNumber = 3; + hs[2].bStatus = 0; + memcpy(hs[2].tTKID, &tkid_le, sizeof(hs[2].tTKID)); + hs[2].bReserved = 0; + memcpy(hs[2].CDID, &wusb_dev->cdid, sizeof(hs[2].CDID)); + memcpy(hs[2].nonce, hs[0].nonce, sizeof(hs[2].nonce)); + result = wusb_oob_mic(hs[2].MIC, keydvt_out.kck, &ccm_n, &hs[2]); + if (result < 0) { + dev_err(dev, "Handshake3 failed: cannot compute MIC: %d\n", + result); + goto error_hs2; + } + + d_printf(1, dev, "I: sending hs3:\n"); + hs_printk(2, dev, &hs[2]); + + result = usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_HANDSHAKE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 3, 0, &hs[2], sizeof(hs[2]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake3: request failed: %d\n", result); + goto error_hs3; + } + + d_printf(1, dev, "I: turning on encryption on host for device\n"); + d_dump(2, dev, keydvt_out.ptk, sizeof(keydvt_out.ptk)); + result = wusbhc->set_ptk(wusbhc, wusb_dev->port_idx, tkid, + keydvt_out.ptk, sizeof(keydvt_out.ptk)); + if (result < 0) + goto error_wusbhc_set_ptk; + + d_printf(1, dev, "I: setting a GTK\n"); + result = wusb_dev_set_gtk(wusbhc, wusb_dev); + if (result < 0) { + dev_err(dev, "Set GTK for device: request failed: %d\n", + result); + goto error_wusbhc_set_gtk; + } + + /* Update the device's address from unauth to auth */ + if (usb_dev->authenticated == 0) { + d_printf(1, dev, "I: updating addres to auth from non-auth\n"); + result = wusb_dev_update_address(wusbhc, wusb_dev); + if (result < 0) + goto error_dev_update_address; + } + result = 0; + d_printf(1, dev, "I: 4way handshke done, device authenticated\n"); + +error_dev_update_address: +error_wusbhc_set_gtk: +error_wusbhc_set_ptk: +error_hs3: +error_hs2: +error_hs1: + memset(hs, 0, 3*sizeof(hs[0])); + memset(&keydvt_out, 0, sizeof(keydvt_out)); + memset(&keydvt_in, 0, sizeof(keydvt_in)); + memset(&ccm_n, 0, sizeof(ccm_n)); + memset(mic, 0, sizeof(mic)); + if (result < 0) { + /* error path */ + wusb_dev_set_encryption(usb_dev, 0); + } +error_dev_set_encryption: + kfree(hs); +error_kzalloc: + return result; +} + +/* + * Once all connected and authenticated devices have received the new + * GTK, switch the host to using it. + */ +static void wusbhc_gtk_rekey_done_work(struct work_struct *work) +{ + struct wusbhc *wusbhc = container_of(work, struct wusbhc, gtk_rekey_done_work); + size_t key_size = sizeof(wusbhc->gtk.data); + + mutex_lock(&wusbhc->mutex); + + if (--wusbhc->pending_set_gtks == 0) + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); + + mutex_unlock(&wusbhc->mutex); +} + +static void wusbhc_set_gtk_callback(struct urb *urb) +{ + struct wusbhc *wusbhc = urb->context; + + queue_work(wusbd, &wusbhc->gtk_rekey_done_work); +} + +/** + * wusbhc_gtk_rekey - generate and distribute a new GTK + * @wusbhc: the WUSB host controller + * + * Generate a new GTK and distribute it to all connected and + * authenticated devices. When all devices have the new GTK, the host + * starts using it. + * + * This must be called after every device disconnect (see [WUSB] + * section 6.2.11.2). + */ +void wusbhc_gtk_rekey(struct wusbhc *wusbhc) +{ + static const size_t key_size = sizeof(wusbhc->gtk.data); + int p; + + wusbhc_generate_gtk(wusbhc); + + for (p = 0; p < wusbhc->ports_max; p++) { + struct wusb_dev *wusb_dev; + + wusb_dev = wusbhc->port[p].wusb_dev; + if (!wusb_dev || !wusb_dev->usb_dev | !wusb_dev->usb_dev->authenticated) + continue; + + usb_fill_control_urb(wusb_dev->set_gtk_urb, wusb_dev->usb_dev, + usb_sndctrlpipe(wusb_dev->usb_dev, 0), + (void *)wusb_dev->set_gtk_req, + &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, + wusbhc_set_gtk_callback, wusbhc); + if (usb_submit_urb(wusb_dev->set_gtk_urb, GFP_KERNEL) == 0) + wusbhc->pending_set_gtks++; + } + if (wusbhc->pending_set_gtks == 0) + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); +} -- cgit v1.2.2 From 470cc4150367d369bdc98ee04902b04baa2b2464 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 17 Sep 2008 16:34:26 +0100 Subject: wusb: add the Wireless USB core (build-system) Add the WUSB build system (Kconfig and Kbuild) files. Signed-off-by: Greg Kroah-Hartman Signed-off-by: David Vrabel --- drivers/usb/Kconfig | 2 ++ drivers/usb/Makefile | 2 ++ drivers/usb/wusbcore/Kconfig | 17 +++++++++++++++++ drivers/usb/wusbcore/Makefile | 12 ++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 drivers/usb/wusbcore/Kconfig create mode 100644 drivers/usb/wusbcore/Makefile (limited to 'drivers/usb') diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index bcefbddeba50..c23a9857ee67 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -97,6 +97,8 @@ source "drivers/usb/core/Kconfig" source "drivers/usb/mon/Kconfig" +source "drivers/usb/wusbcore/Kconfig" + source "drivers/usb/host/Kconfig" source "drivers/usb/musb/Kconfig" diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index a419c42e880e..19ede32f0df1 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -19,6 +19,8 @@ obj-$(CONFIG_USB_R8A66597_HCD) += host/ obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ +obj-$(CONFIG_USB_WUSB) += wusbcore/ + obj-$(CONFIG_USB_ACM) += class/ obj-$(CONFIG_USB_PRINTER) += class/ diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig new file mode 100644 index 000000000000..add077e5c5d0 --- /dev/null +++ b/drivers/usb/wusbcore/Kconfig @@ -0,0 +1,17 @@ +# +# Wireless USB Core configuration +# +config USB_WUSB + tristate "Enable Wireless USB extensions" + depends on USB + select UWB + select CRYPTO + select CRYPTO_BLKCIPHER + select CRYPTO_CBC + select CRYPTO_MANAGER + select CRYPTO_AES + help + Enable the host-side support for Wireless USB. + + To compile this support select Y (built in). It is safe to + select even if you don't have the hardware. diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile new file mode 100644 index 000000000000..ea1dbfca6135 --- /dev/null +++ b/drivers/usb/wusbcore/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_USB_WUSB) += wusbcore.o + +wusbcore-objs := \ + crypto.o \ + devconnect.o \ + dev-sysfs.o \ + mmc.o \ + pal.o \ + rh.o \ + reservation.o \ + security.o \ + wusbhc.o -- cgit v1.2.2 From 870d5395045bfe8e5213525152682c863a10f8d2 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:27 +0100 Subject: wusb: add the USB wusb-cbaf driver Add a driver for cable based associated of (Wireless) USB devices. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/Makefile | 4 +- drivers/usb/wusbcore/cbaf.c | 620 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/wusbcore/cbaf.c (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile index ea1dbfca6135..6504f42ca367 100644 --- a/drivers/usb/wusbcore/Makefile +++ b/drivers/usb/wusbcore/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_USB_WUSB) += wusbcore.o +obj-$(CONFIG_USB_WUSB) += wusbcore.o wusb-cbaf.o wusbcore-objs := \ crypto.o \ @@ -10,3 +10,5 @@ wusbcore-objs := \ reservation.o \ security.o \ wusbhc.o + +wusb-cbaf-objs := cbaf.o diff --git a/drivers/usb/wusbcore/cbaf.c b/drivers/usb/wusbcore/cbaf.c new file mode 100644 index 000000000000..584eabe274b3 --- /dev/null +++ b/drivers/usb/wusbcore/cbaf.c @@ -0,0 +1,620 @@ +/* + * Wireless USB - Cable Based Association + * + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * 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 (authenticated in WUSB lingo) so + * that they can connect to the system. + * + * One way of pairing is using CBA-Cable Based Authentication, devices + * that can connect via wired or wireless USB. First time you plug + * them with a cable, pairing is done between host and device and + * subsequent times, you can connect wirelessly without having to + * pair. 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 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 sees a device with CBAF capability (the + * wusb_{host_info,cdid,cc} files are in /sys/device/blah/OURDEVICE). + * + * 3. CM (connection manager) writes the CHID (host ID) and a host + * name into the wusb_host_info file. This gets sent to the device. + * + * 4. CM cats the wusb_cdid file; this asks the device if it has any + * CDID associated to the CHDI we just wrote before. If it does, it + * is printed, along with the device 'friendly name' and the band + * groups the device supports. + * + * 5. CM looks up its database + * + * 5.1 If it has a matching CHID,CDID entry, the device has been + * authorized before (paired). Now we can optionally ask the user + * if he wants to allow the device to connect. Then we generate a + * new CDID and CK, send it to the device and update the database + * (writing to the wusb_cc file so they are uploaded to the device). + * + * 5.2 If the CDID is zero (or we didn't find a matching CDID in our + * database), we assume the device is not known. We ask the user + * if s/he wants to allow the device to be connected wirelessly + * to the system. If nope, nothing else is done (FIXME: maybe + * send a zero CDID to clean up our CHID?). If yes, we generate + * random CDID and CKs (and write them to the wusb_cc file so + * they are uploaded to the device). + * + * 6. device is unplugged + * + * When the device tries to connect wirelessly, it will present it's + * CDID to the WUSB host controller with ID CHID, which will query the + * database. If found, the host will (with a 4way 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 crypted. + * + * + * NOTES ABOUT THE IMPLEMENTATION + * + * The descriptors sent back and forth use this horrible format from + * hell on which each field is actually a field ID, field length and + * then the field itself. How stupid can that get, taking into account + * the structures are defined by the spec?? oh well. + * + * + * FIXME: we don't provide a way to tell the device the pairing failed + * (ie: send a CC_DATA_FAIL). Should add some day. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef D_LOCAL +#define D_LOCAL 6 +#include + +/* 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;/* Host Information */ + char host_name[65]; /* max length: + Assoc Models Suplement 1.0[T4-7] */ + u16 host_band_groups; + + struct wusb_ckhdid cdid;/* Device Information */ + char device_name[65]; /* max length: + Assoc Models Suplement 1.0[T4-7] */ + u16 device_band_groups; + struct wusb_ckhdid ck; /* Connection Key */ +}; + +/* + * Verify that a CBAF USB-interface has what we need + * + * (like we care, we are going to fail the enumeration if not :) + * + * FIXME: ugly function, need to split + */ +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; + unsigned ar_index; + int ar_rhi_idx = -1, ar_assoc_idx = -1; + + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_INFORMATION, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + cbaf->buffer, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "cannot get available association types: %d\n", + result); + goto error_get_assoc_types; + } + 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)); + goto error_bad_header; + } + 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)); + goto error_bad_data; + } + /* + * 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. + */ + ar_index = 0; + itr = cbaf->buffer + sizeof(*assoc_info); + top = cbaf->buffer + assoc_size; + d_printf(1, dev, "Found %u association requests (%zu bytes)\n", + assoc_info->NumAssociationRequests, assoc_size); + while (itr < top) { + u16 ar_type, ar_subtype; + u32 ar_size; + const char *ar_name; + + assoc_request = itr; + if (top - itr < sizeof(*assoc_request)) { + dev_err(dev, "not enough data to decode associaton " + "request (%zu vs %zu bytes needed)\n", + top - itr, sizeof(*assoc_request)); + break; + } + ar_type = le16_to_cpu(assoc_request->AssociationTypeId); + ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId); + ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize); + switch (ar_type) { + case AR_TYPE_WUSB: + /* Verify we have what is mandated by AMS1.0 */ + switch (ar_subtype) { + case AR_TYPE_WUSB_RETRIEVE_HOST_INFO: + ar_name = "retrieve_host_info"; + ar_rhi_idx = ar_index; + break; + case AR_TYPE_WUSB_ASSOCIATE: + /* send assoc data */ + ar_name = "associate"; + ar_assoc_idx = ar_index; + break; + default: + ar_name = "unknown"; + }; + break; + default: + ar_name = "unknown"; + }; + d_printf(1, 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); + ar_index++; + } + if (ar_rhi_idx == -1) { + dev_err(dev, "Missing RETRIEVE_HOST_INFO association " + "request\n"); + goto error_bad_reqs; + } + if (ar_assoc_idx == -1) { + dev_err(dev, "Missing ASSOCIATE association request\n"); + goto error_bad_reqs; + } + return 0; + +error_bad_header: +error_bad_data: +error_bad_reqs: +error_get_assoc_types: + return -EINVAL; +} + +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 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... */ + strncpy(hi->HostFriendlyName, cbaf->host_name, + hi->HostFriendlyName_hdr.len); + hi->HostFriendlyName_hdr.len = + cpu_to_le16(strlen(hi->HostFriendlyName)); + hi_size = sizeof(*hi) + strlen(hi->HostFriendlyName); + return usb_control_msg(cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_SET_ASSOCIATION_RESPONSE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0101, + cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + hi, hi_size, 1000 /* FIXME: arbitrary */); +} + +/* Show current CHID info we have set from user space */ +static ssize_t cbaf_wusb_host_info_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, "CHID: %s\nName: %s\n", + pr_chid, cbaf->host_name); +} + +/* + * Get a host info CHID from user space and send it to the device. + * + * The user can recover a CC from the device associated to that CHID + * by cat'ing wusb_connection_context. + */ +static ssize_t cbaf_wusb_host_info_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 " + "%04hx %64s\n", + &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], + &cbaf->host_band_groups, cbaf->host_name); + if (result != 18) { + dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits, " + "a 16 bit hex band group mask " + "and a host name, got only %d)\n", (int)result); + return -EINVAL; + } + result = cbaf_send_host_info(cbaf); + if (result < 0) + dev_err(dev, "Couldn't send host information to device: %d\n", + (int)result); + else + d_printf(1, dev, "HI sent, wusb_cc can be read now\n"); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_host_info, 0600, cbaf_wusb_host_info_show, + cbaf_wusb_host_info_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, +}; + +/* + * 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, dev_name_size; + + di = cbaf->buffer; + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_REQUEST, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot request device information: %d\n", result); + goto error_req_di; + } + 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); + goto error_bad_di; + } + cbaf->cdid = di->CDID; + dev_name_size = le16_to_cpu(di->DeviceFriendlyName_hdr.len); + dev_name_size = dev_name_size > 65 - 1 ? 65 - 1 : dev_name_size; + memcpy(cbaf->device_name, di->DeviceFriendlyName, dev_name_size); + cbaf->device_name[dev_name_size] = 0; + cbaf->device_band_groups = le16_to_cpu(di->BandGroups); + result = 0; +error_req_di: +error_bad_di: + return result; +} + +/* + * Get device information and print it to sysfs + * + * See cbaf_cdid_get() + */ +static ssize_t cbaf_wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_cdid[WUSB_CKHDID_STRSIZE]; + + result = cbaf_cdid_get(cbaf); + if (result < 0) { + dev_err(dev, "Cannot read device information: %d\n", + (int)result); + goto error_get_di; + } + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid); + result = scnprintf(buf, PAGE_SIZE, + "CDID: %s\nName: %s\nBand_groups: 0x%04x\n", + pr_cdid, cbaf->device_name, + cbaf->device_band_groups); +error_get_di: + return result; +} +static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_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 + * + * So we update the CK and send the whole thing 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); + result = usb_control_msg( + cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_SET_ASSOCIATION_RESPONSE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + ccd, sizeof(*ccd), 1000 /* FIXME: arbitrary */); + d_printf(1, dev, "Uploaded CC:\n"); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID); + d_printf(1, dev, " CHID %s\n", pr_cdid); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID); + d_printf(1, dev, " CDID %s\n", pr_cdid); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CK); + d_printf(1, dev, " CK %s\n", pr_cdid); + d_printf(1, dev, " bandgroups 0x%04x\n", cbaf->host_band_groups); + return result; +} + +/* + * Send a new CC to the device + * + * We take the CDID and CK from user space, the rest from the info we + * set with host_info. + */ +static ssize_t cbaf_wusb_cc_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, + "CDID: %02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n" + "CK: %02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &cbaf->cdid.data[0] , &cbaf->cdid.data[1], + &cbaf->cdid.data[2] , &cbaf->cdid.data[3], + &cbaf->cdid.data[4] , &cbaf->cdid.data[5], + &cbaf->cdid.data[6] , &cbaf->cdid.data[7], + &cbaf->cdid.data[8] , &cbaf->cdid.data[9], + &cbaf->cdid.data[10], &cbaf->cdid.data[11], + &cbaf->cdid.data[12], &cbaf->cdid.data[13], + &cbaf->cdid.data[14], &cbaf->cdid.data[15], + + &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 != 32) { + dev_err(dev, "Unrecognized CHID/CK (need 32 8-bit " + "hex digits, got only %d)\n", (int)result); + return -EINVAL; + } + result = cbaf_cc_upload(cbaf); + if (result < 0) + dev_err(dev, "Couldn't upload connection context: %d\n", + (int)result); + else + d_printf(1, dev, "Connection context uploaded\n"); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_cc, 0600, NULL, cbaf_wusb_cc_store); + +static struct attribute *cbaf_dev_attrs[] = { + &dev_attr_wusb_host_info.attr, + &dev_attr_wusb_cdid.attr, + &dev_attr_wusb_cc.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) +{ + int result; + struct cbaf *cbaf; + struct device *dev = &iface->dev; + + result = -ENOMEM; + cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL); + if (cbaf == NULL) { + dev_err(dev, "Unable to allocate instance\n"); + 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) + 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); + d_printf(2, dev, "CBA attached\n"); + return 0; + +error_create_group: +error_check: + kfree(cbaf->buffer); +error_kmalloc_buffer: + kfree(cbaf); +error_kzalloc: + return result; +} + +static void cbaf_disconnect(struct usb_interface *iface) +{ + struct cbaf *cbaf = usb_get_intfdata(iface); + struct device *dev = &iface->dev; + sysfs_remove_group(&dev->kobj, &cbaf_dev_attr_group); + usb_set_intfdata(iface, NULL); + usb_put_intf(iface); + kfree(cbaf->buffer); + /* paranoia: clean up crypto keys */ + memset(cbaf, 0, sizeof(*cbaf)); + kfree(cbaf); + d_printf(1, dev, "CBA detached\n"); +} + +static struct usb_device_id cbaf_id_table[] = { + { USB_INTERFACE_INFO(0xef, 0x03, 0x01), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, cbaf_id_table); + +static struct usb_driver cbaf_driver = { + .name = "wusb-cbaf", + .id_table = cbaf_id_table, + .probe = cbaf_probe, + .disconnect = cbaf_disconnect, +}; + +static int __init cbaf_driver_init(void) +{ + return usb_register(&cbaf_driver); +} +module_init(cbaf_driver_init); + +static void __exit cbaf_driver_exit(void) +{ + usb_deregister(&cbaf_driver); +} +module_exit(cbaf_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB Cable Based Association"); +MODULE_LICENSE("GPL"); -- cgit v1.2.2 From 7e6133aa42920ea87ad9791a0fb2b95d1a23b8f9 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:28 +0100 Subject: wusb: WHCI host controller driver A driver for Wireless USB host controllers that comply with the Wireless Host Controller Interface (HCI) specification as published by Intel. The latest publically available version of the specification (0.95) is supported (except for isochronous transfers). Build fixes by Randy Dunlap Signed-off-by: David Vrabel --- drivers/usb/host/Kconfig | 12 + drivers/usb/host/Makefile | 2 + drivers/usb/host/whci/Kbuild | 11 + drivers/usb/host/whci/asl.c | 367 ++++++++++++++++++++++++++ drivers/usb/host/whci/hcd.c | 339 ++++++++++++++++++++++++ drivers/usb/host/whci/hw.c | 87 ++++++ drivers/usb/host/whci/init.c | 188 +++++++++++++ drivers/usb/host/whci/int.c | 95 +++++++ drivers/usb/host/whci/pzl.c | 398 ++++++++++++++++++++++++++++ drivers/usb/host/whci/qset.c | 567 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/whci/whcd.h | 197 ++++++++++++++ drivers/usb/host/whci/whci-hc.h | 416 +++++++++++++++++++++++++++++ drivers/usb/host/whci/wusb.c | 241 +++++++++++++++++ 13 files changed, 2920 insertions(+) create mode 100644 drivers/usb/host/whci/Kbuild create mode 100644 drivers/usb/host/whci/asl.c create mode 100644 drivers/usb/host/whci/hcd.c create mode 100644 drivers/usb/host/whci/hw.c create mode 100644 drivers/usb/host/whci/init.c create mode 100644 drivers/usb/host/whci/int.c create mode 100644 drivers/usb/host/whci/pzl.c create mode 100644 drivers/usb/host/whci/qset.c create mode 100644 drivers/usb/host/whci/whcd.h create mode 100644 drivers/usb/host/whci/whci-hc.h create mode 100644 drivers/usb/host/whci/wusb.c (limited to 'drivers/usb') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 228797e54f9c..d3ba351398e2 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -305,3 +305,15 @@ config SUPERH_ON_CHIP_R8A66597 help This driver enables support for the on-chip R8A66597 in the SH7366 and SH7723 processors. + +config USB_WHCI_HCD + tristate "Wireless USB Host Controller Interface (WHCI) driver" + depends on PCI && USB + select USB_WUSB + select UWB_WHCI + help + A driver for PCI-based Wireless USB Host Controllers that are + compliant with the WHCI specification. + + To compile this driver a module, choose M here: the module + will be called "whci-hcd". diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index f1edda2dcfde..c90cf865ccf3 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -8,6 +8,8 @@ endif isp1760-objs := isp1760-hcd.o isp1760-if.o +obj-$(CONFIG_USB_WHCI_HCD) += whci/ + obj-$(CONFIG_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild new file mode 100644 index 000000000000..26a3871ea0f9 --- /dev/null +++ b/drivers/usb/host/whci/Kbuild @@ -0,0 +1,11 @@ +obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o + +whci-hcd-y := \ + asl.o \ + hcd.o \ + hw.o \ + init.o \ + int.o \ + pzl.o \ + qset.o \ + wusb.o diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c new file mode 100644 index 000000000000..4d7078e50572 --- /dev/null +++ b/drivers/usb/host/whci/asl.c @@ -0,0 +1,367 @@ +/* + * Wireless Host Controller (WHC) asynchronous schedule management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#define D_LOCAL 0 +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 4 +static void dump_asl(struct whc *whc, const char *tag) +{ + struct device *dev = &whc->umc->dev; + struct whc_qset *qset; + + d_printf(4, dev, "ASL %s\n", tag); + + list_for_each_entry(qset, &whc->async_list, list_node) { + dump_qset(qset, dev); + } +} +#else +static inline void dump_asl(struct whc *whc, const char *tag) +{ +} +#endif + + +static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset, + struct whc_qset **next, struct whc_qset **prev) +{ + struct list_head *n, *p; + + BUG_ON(list_empty(&whc->async_list)); + + n = qset->list_node.next; + if (n == &whc->async_list) + n = n->next; + p = qset->list_node.prev; + if (p == &whc->async_list) + p = p->prev; + + *next = container_of(n, struct whc_qset, list_node); + *prev = container_of(p, struct whc_qset, list_node); + +} + +static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset) +{ + list_move(&qset->list_node, &whc->async_list); + qset->in_sw_list = true; +} + +static void asl_qset_insert(struct whc *whc, struct whc_qset *qset) +{ + struct whc_qset *next, *prev; + + qset_clear(whc, qset); + + /* Link into ASL. */ + qset_get_next_prev(whc, qset, &next, &prev); + whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma); + whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma); + qset->in_hw_list = true; +} + +static void asl_qset_remove(struct whc *whc, struct whc_qset *qset) +{ + struct whc_qset *prev, *next; + + qset_get_next_prev(whc, qset, &next, &prev); + + list_move(&qset->list_node, &whc->async_removed_list); + qset->in_sw_list = false; + + /* + * No more qsets in the ASL? The caller must stop the ASL as + * it's no longer valid. + */ + if (list_empty(&whc->async_list)) + return; + + /* Remove from ASL. */ + whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma); + qset->in_hw_list = false; +} + +/** + * process_qset - process any recently inactivated or halted qTDs in a + * qset. + * + * After inactive qTDs are removed, new qTDs can be added if the + * urb queue still contains URBs. + * + * Returns any additional WUSBCMD bits for the ASL sync command (i.e., + * WUSBCMD_ASYNC_QSET_RM if a halted qset was removed). + */ +static uint32_t process_qset(struct whc *whc, struct whc_qset *qset) +{ + enum whc_update update = 0; + uint32_t status = 0; + + while (qset->ntds) { + struct whc_qtd *td; + int t; + + t = qset->td_start; + td = &qset->qtd[qset->td_start]; + status = le32_to_cpu(td->status); + + /* + * Nothing to do with a still active qTD. + */ + if (status & QTD_STS_ACTIVE) + break; + + if (status & QTD_STS_HALTED) { + /* Ug, an error. */ + process_halted_qtd(whc, qset, td); + goto done; + } + + /* Mmm, a completed qTD. */ + process_inactive_qtd(whc, qset, td); + } + + update |= qset_add_qtds(whc, qset); + +done: + /* + * Remove this qset from the ASL if requested, but only if has + * no qTDs. + */ + if (qset->remove && qset->ntds == 0) { + asl_qset_remove(whc, qset); + update |= WHC_UPDATE_REMOVED; + } + return update; +} + +void asl_start(struct whc *whc) +{ + struct whc_qset *qset; + + qset = list_first_entry(&whc->async_list, struct whc_qset, list_node); + + le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR); + + whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED, + 1000, "start ASL"); +} + +void asl_stop(struct whc *whc) +{ + whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_ASYNC_SCHED, 0, + 1000, "stop ASL"); +} + +void asl_update(struct whc *whc, uint32_t wusbcmd) +{ + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + wait_event(whc->async_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0); +} + +/** + * scan_async_work - scan the ASL for qsets to process. + * + * Process each qset in the ASL in turn and then signal the WHC that + * the ASL has been updated. + * + * Then start, stop or update the asynchronous schedule as required. + */ +void scan_async_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, async_work); + struct whc_qset *qset, *t; + enum whc_update update = 0; + + spin_lock_irq(&whc->lock); + + dump_asl(whc, "before processing"); + + /* + * Transerve the software list backwards so new qsets can be + * safely inserted into the ASL without making it non-circular. + */ + list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) { + if (!qset->in_hw_list) { + asl_qset_insert(whc, qset); + update |= WHC_UPDATE_ADDED; + } + + update |= process_qset(whc, qset); + } + + dump_asl(whc, "after processing"); + + spin_unlock_irq(&whc->lock); + + if (update) { + uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB; + if (update & WHC_UPDATE_REMOVED) + wusbcmd |= WUSBCMD_ASYNC_QSET_RM; + asl_update(whc, wusbcmd); + } + + /* + * Now that the ASL is updated, complete the removal of any + * removed qsets. + */ + spin_lock(&whc->lock); + + list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) { + qset_remove_complete(whc, qset); + } + + spin_unlock(&whc->lock); +} + +/** + * asl_urb_enqueue - queue an URB onto the asynchronous list (ASL). + * @whc: the WHCI host controller + * @urb: the URB to enqueue + * @mem_flags: flags for any memory allocations + * + * The qset for the endpoint is obtained and the urb queued on to it. + * + * Work is scheduled to update the hardware's view of the ASL. + */ +int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags) +{ + struct whc_qset *qset; + int err; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + qset = get_qset(whc, urb, GFP_ATOMIC); + if (qset == NULL) + err = -ENOMEM; + else + err = qset_add_urb(whc, qset, urb, GFP_ATOMIC); + if (!err) { + usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb); + if (!qset->in_sw_list) + asl_qset_insert_begin(whc, qset); + } + + spin_unlock_irqrestore(&whc->lock, flags); + + if (!err) + queue_work(whc->workqueue, &whc->async_work); + + return 0; +} + +/** + * asl_urb_dequeue - remove an URB (qset) from the async list. + * @whc: the WHCI host controller + * @urb: the URB to dequeue + * @status: the current status of the URB + * + * URBs that do yet have qTDs can simply be removed from the software + * queue, otherwise the qset must be removed from the ASL so the qTDs + * can be removed. + */ +int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status) +{ + struct whc_urb *wurb = urb->hcpriv; + struct whc_qset *qset = wurb->qset; + struct whc_std *std, *t; + int ret; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status); + if (ret < 0) + goto out; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(whc, std); + else + std->qtd = NULL; /* so this std is re-added when the qset is */ + } + + asl_qset_remove(whc, qset); + wurb->status = status; + wurb->is_async = true; + queue_work(whc->workqueue, &wurb->dequeue_work); + +out: + spin_unlock_irqrestore(&whc->lock, flags); + + return ret; +} + +/** + * asl_qset_delete - delete a qset from the ASL + */ +void asl_qset_delete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 1; + queue_work(whc->workqueue, &whc->async_work); + qset_delete(whc, qset); +} + +/** + * asl_init - initialize the asynchronous schedule list + * + * A dummy qset with no qTDs is added to the ASL to simplify removing + * qsets (no need to stop the ASL when the last qset is removed). + */ +int asl_init(struct whc *whc) +{ + struct whc_qset *qset; + + qset = qset_alloc(whc, GFP_KERNEL); + if (qset == NULL) + return -ENOMEM; + + asl_qset_insert_begin(whc, qset); + asl_qset_insert(whc, qset); + + return 0; +} + +/** + * asl_clean_up - free ASL resources + * + * The ASL is stopped and empty except for the dummy qset. + */ +void asl_clean_up(struct whc *whc) +{ + struct whc_qset *qset; + + if (!list_empty(&whc->async_list)) { + qset = list_first_entry(&whc->async_list, struct whc_qset, list_node); + list_del(&qset->list_node); + qset_free(whc, qset); + } +} diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c new file mode 100644 index 000000000000..ef3ad4dca945 --- /dev/null +++ b/drivers/usb/host/whci/hcd.c @@ -0,0 +1,339 @@ +/* + * Wireless Host Controller (WHC) driver. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +/* + * One time initialization. + * + * Nothing to do here. + */ +static int whc_reset(struct usb_hcd *usb_hcd) +{ + return 0; +} + +/* + * Start the wireless host controller. + * + * Start device notification. + * + * Put hc into run state, set DNTS parameters. + */ +static int whc_start(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + u8 bcid; + int ret; + + mutex_lock(&wusbhc->mutex); + + le_writel(WUSBINTR_GEN_CMD_DONE + | WUSBINTR_HOST_ERR + | WUSBINTR_ASYNC_SCHED_SYNCED + | WUSBINTR_DNTS_INT + | WUSBINTR_ERR_INT + | WUSBINTR_INT, + whc->base + WUSBINTR); + + /* set cluster ID */ + bcid = wusb_cluster_id_get(); + ret = whc_set_cluster_id(whc, bcid); + if (ret < 0) + goto out; + wusbhc->cluster_id = bcid; + + /* start HC */ + whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN); + + usb_hcd->uses_new_polling = 1; + usb_hcd->poll_rh = 1; + usb_hcd->state = HC_STATE_RUNNING; + +out: + mutex_unlock(&wusbhc->mutex); + return ret; +} + + +/* + * Stop the wireless host controller. + * + * Stop device notification. + * + * Wait for pending transfer to stop? Put hc into stop state? + */ +static void whc_stop(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + + mutex_lock(&wusbhc->mutex); + + wusbhc_stop(wusbhc); + + /* stop HC */ + le_writel(0, whc->base + WUSBINTR); + whc_write_wusbcmd(whc, WUSBCMD_RUN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_HCHALTED, WUSBSTS_HCHALTED, + 100, "HC to halt"); + + wusb_cluster_id_put(wusbhc->cluster_id); + + mutex_unlock(&wusbhc->mutex); +} + +static int whc_get_frame_number(struct usb_hcd *usb_hcd) +{ + /* Frame numbers are not applicable to WUSB. */ + return -ENOSYS; +} + + +/* + * Queue an URB to the ASL or PZL + */ +static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + ret = pzl_urb_enqueue(whc, urb, mem_flags); + break; + case PIPE_ISOCHRONOUS: + dev_err(&whc->umc->dev, "isochronous transfers unsupported\n"); + ret = -ENOTSUPP; + break; + case PIPE_CONTROL: + case PIPE_BULK: + default: + ret = asl_urb_enqueue(whc, urb, mem_flags); + break; + }; + + return ret; +} + +/* + * Remove a queued URB from the ASL or PZL. + */ +static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + ret = pzl_urb_dequeue(whc, urb, status); + break; + case PIPE_ISOCHRONOUS: + ret = -ENOTSUPP; + break; + case PIPE_CONTROL: + case PIPE_BULK: + default: + ret = asl_urb_dequeue(whc, urb, status); + break; + }; + + return ret; +} + +/* + * Wait for all URBs to the endpoint to be completed, then delete the + * qset. + */ +static void whc_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + struct whc_qset *qset; + + qset = ep->hcpriv; + if (qset) { + ep->hcpriv = NULL; + if (usb_endpoint_xfer_bulk(&ep->desc) + || usb_endpoint_xfer_control(&ep->desc)) + asl_qset_delete(whc, qset); + else + pzl_qset_delete(whc, qset); + } +} + +static struct hc_driver whc_hc_driver = { + .description = "whci-hcd", + .product_desc = "Wireless host controller", + .hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd), + .irq = whc_int_handler, + .flags = HCD_USB2, + + .reset = whc_reset, + .start = whc_start, + .stop = whc_stop, + .get_frame_number = whc_get_frame_number, + .urb_enqueue = whc_urb_enqueue, + .urb_dequeue = whc_urb_dequeue, + .endpoint_disable = whc_endpoint_disable, + + .hub_status_data = wusbhc_rh_status_data, + .hub_control = wusbhc_rh_control, + .bus_suspend = wusbhc_rh_suspend, + .bus_resume = wusbhc_rh_resume, + .start_port_reset = wusbhc_rh_start_port_reset, +}; + +static int whc_probe(struct umc_dev *umc) +{ + int ret = -ENOMEM; + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc = NULL; + struct whc *whc = NULL; + struct device *dev = &umc->dev; + + usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci"); + if (usb_hcd == NULL) { + dev_err(dev, "unable to create hcd\n"); + goto error; + } + + usb_hcd->wireless = 1; + + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + whc = wusbhc_to_whc(wusbhc); + whc->umc = umc; + + ret = whc_init(whc); + if (ret) + goto error; + + wusbhc->dev = dev; + wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent); + if (!wusbhc->uwb_rc) { + ret = -ENODEV; + dev_err(dev, "cannot get radio controller\n"); + goto error; + } + + if (whc->n_devices > USB_MAXCHILDREN) { + dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n", + whc->n_devices); + wusbhc->ports_max = USB_MAXCHILDREN; + } else + wusbhc->ports_max = whc->n_devices; + wusbhc->mmcies_max = whc->n_mmc_ies; + wusbhc->start = whc_wusbhc_start; + wusbhc->stop = whc_wusbhc_stop; + wusbhc->mmcie_add = whc_mmcie_add; + wusbhc->mmcie_rm = whc_mmcie_rm; + wusbhc->dev_info_set = whc_dev_info_set; + wusbhc->bwa_set = whc_bwa_set; + wusbhc->set_num_dnts = whc_set_num_dnts; + wusbhc->set_ptk = whc_set_ptk; + wusbhc->set_gtk = whc_set_gtk; + + ret = wusbhc_create(wusbhc); + if (ret) + goto error_wusbhc_create; + + ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED); + if (ret) { + dev_err(dev, "cannot add HCD: %d\n", ret); + goto error_usb_add_hcd; + } + + ret = wusbhc_b_create(wusbhc); + if (ret) { + dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret); + goto error_wusbhc_b_create; + } + + return 0; + +error_wusbhc_b_create: + usb_remove_hcd(usb_hcd); +error_usb_add_hcd: + wusbhc_destroy(wusbhc); +error_wusbhc_create: + uwb_rc_put(wusbhc->uwb_rc); +error: + whc_clean_up(whc); + if (usb_hcd) + usb_put_hcd(usb_hcd); + return ret; +} + + +static void whc_remove(struct umc_dev *umc) +{ + struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev); + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + + if (usb_hcd) { + wusbhc_b_destroy(wusbhc); + usb_remove_hcd(usb_hcd); + wusbhc_destroy(wusbhc); + uwb_rc_put(wusbhc->uwb_rc); + whc_clean_up(whc); + usb_put_hcd(usb_hcd); + } +} + +static struct umc_driver whci_hc_driver = { + .name = "whci-hcd", + .cap_id = UMC_CAP_ID_WHCI_WUSB_HC, + .probe = whc_probe, + .remove = whc_remove, +}; + +static int __init whci_hc_driver_init(void) +{ + return umc_driver_register(&whci_hc_driver); +} +module_init(whci_hc_driver_init); + +static void __exit whci_hc_driver_exit(void) +{ + umc_driver_unregister(&whci_hc_driver); +} +module_exit(whci_hc_driver_exit); + +/* PCI device ID's that we handle (so it gets loaded) */ +static struct pci_device_id whci_hcd_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, + { /* empty last entry */ } +}; +MODULE_DEVICE_TABLE(pci, whci_hcd_id_table); + +MODULE_DESCRIPTION("WHCI Wireless USB host controller driver"); +MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c new file mode 100644 index 000000000000..ac86e59c1225 --- /dev/null +++ b/drivers/usb/host/whci/hw.c @@ -0,0 +1,87 @@ +/* + * Wireless Host Controller (WHC) hardware access helpers. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val) +{ + unsigned long flags; + u32 cmd; + + spin_lock_irqsave(&whc->lock, flags); + + cmd = le_readl(whc->base + WUSBCMD); + cmd = (cmd & ~mask) | val; + le_writel(cmd, whc->base + WUSBCMD); + + spin_unlock_irqrestore(&whc->lock, flags); +} + +/** + * whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register + * @whc: the WHCI HC + * @cmd: command to start. + * @params: parameters for the command (the WUSBGENCMDPARAMS register value). + * @addr: pointer to any data for the command (may be NULL). + * @len: length of the data (if any). + */ +int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len) +{ + unsigned long flags; + dma_addr_t dma_addr; + int t; + + mutex_lock(&whc->mutex); + + /* Wait for previous command to complete. */ + t = wait_event_timeout(whc->cmd_wq, + (le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0, + WHC_GENCMD_TIMEOUT_MS); + if (t == 0) { + dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n", + le_readl(whc->base + WUSBGENCMDSTS), + le_readl(whc->base + WUSBGENCMDPARAMS)); + return -ETIMEDOUT; + } + + if (addr) { + memcpy(whc->gen_cmd_buf, addr, len); + dma_addr = whc->gen_cmd_buf_dma; + } else + dma_addr = 0; + + /* Poke registers to start cmd. */ + spin_lock_irqsave(&whc->lock, flags); + + le_writel(params, whc->base + WUSBGENCMDPARAMS); + le_writeq(dma_addr, whc->base + WUSBGENADDR); + + le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd, + whc->base + WUSBGENCMDSTS); + + spin_unlock_irqrestore(&whc->lock, flags); + + mutex_unlock(&whc->mutex); + + return 0; +} diff --git a/drivers/usb/host/whci/init.c b/drivers/usb/host/whci/init.c new file mode 100644 index 000000000000..34a783cb0133 --- /dev/null +++ b/drivers/usb/host/whci/init.c @@ -0,0 +1,188 @@ +/* + * Wireless Host Controller (WHC) initialization. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +/* + * Reset the host controller. + */ +static void whc_hw_reset(struct whc *whc) +{ + le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD); + whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0, + 100, "reset"); +} + +static void whc_hw_init_di_buf(struct whc *whc) +{ + int d; + + /* Disable all entries in the Device Information buffer. */ + for (d = 0; d < whc->n_devices; d++) + whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE; + + le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR); +} + +static void whc_hw_init_dn_buf(struct whc *whc) +{ + /* Clear the Device Notification buffer to ensure the V (valid) + * bits are clear. */ + memset(whc->dn_buf, 0, 4096); + + le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR); +} + +int whc_init(struct whc *whc) +{ + u32 whcsparams; + int ret, i; + resource_size_t start, len; + + spin_lock_init(&whc->lock); + mutex_init(&whc->mutex); + init_waitqueue_head(&whc->cmd_wq); + init_waitqueue_head(&whc->async_list_wq); + init_waitqueue_head(&whc->periodic_list_wq); + whc->workqueue = create_singlethread_workqueue(dev_name(&whc->umc->dev)); + if (whc->workqueue == NULL) { + ret = -ENOMEM; + goto error; + } + INIT_WORK(&whc->dn_work, whc_dn_work); + + INIT_WORK(&whc->async_work, scan_async_work); + INIT_LIST_HEAD(&whc->async_list); + INIT_LIST_HEAD(&whc->async_removed_list); + + INIT_WORK(&whc->periodic_work, scan_periodic_work); + for (i = 0; i < 5; i++) + INIT_LIST_HEAD(&whc->periodic_list[i]); + INIT_LIST_HEAD(&whc->periodic_removed_list); + + /* Map HC registers. */ + start = whc->umc->resource.start; + len = whc->umc->resource.end - start + 1; + if (!request_mem_region(start, len, "whci-hc")) { + dev_err(&whc->umc->dev, "can't request HC region\n"); + ret = -EBUSY; + goto error; + } + whc->base_phys = start; + whc->base = ioremap(start, len); + if (!whc->base) { + dev_err(&whc->umc->dev, "ioremap\n"); + ret = -ENOMEM; + goto error; + } + + whc_hw_reset(whc); + + /* Read maximum number of devices, keys and MMC IEs. */ + whcsparams = le_readl(whc->base + WHCSPARAMS); + whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams); + whc->n_keys = WHCSPARAMS_TO_N_KEYS(whcsparams); + whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams); + + dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n", + whc->n_devices, whc->n_keys, whc->n_mmc_ies); + + whc->qset_pool = dma_pool_create("qset", &whc->umc->dev, + sizeof(struct whc_qset), 64, 0); + if (whc->qset_pool == NULL) { + ret = -ENOMEM; + goto error; + } + + ret = asl_init(whc); + if (ret < 0) + goto error; + ret = pzl_init(whc); + if (ret < 0) + goto error; + + /* Allocate and initialize a buffer for generic commands, the + Device Information buffer, and the Device Notification + buffer. */ + + whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN, + &whc->gen_cmd_buf_dma, GFP_KERNEL); + if (whc->gen_cmd_buf == NULL) { + ret = -ENOMEM; + goto error; + } + + whc->dn_buf = dma_alloc_coherent(&whc->umc->dev, + sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES, + &whc->dn_buf_dma, GFP_KERNEL); + if (!whc->dn_buf) { + ret = -ENOMEM; + goto error; + } + whc_hw_init_dn_buf(whc); + + whc->di_buf = dma_alloc_coherent(&whc->umc->dev, + sizeof(struct di_buf_entry) * whc->n_devices, + &whc->di_buf_dma, GFP_KERNEL); + if (!whc->di_buf) { + ret = -ENOMEM; + goto error; + } + whc_hw_init_di_buf(whc); + + return 0; + +error: + whc_clean_up(whc); + return ret; +} + +void whc_clean_up(struct whc *whc) +{ + resource_size_t len; + + if (whc->di_buf) + dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices, + whc->di_buf, whc->di_buf_dma); + if (whc->dn_buf) + dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES, + whc->dn_buf, whc->dn_buf_dma); + if (whc->gen_cmd_buf) + dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN, + whc->gen_cmd_buf, whc->gen_cmd_buf_dma); + + pzl_clean_up(whc); + asl_clean_up(whc); + + if (whc->qset_pool) + dma_pool_destroy(whc->qset_pool); + + len = whc->umc->resource.end - whc->umc->resource.start + 1; + if (whc->base) + iounmap(whc->base); + if (whc->base_phys) + release_mem_region(whc->base_phys, len); + + if (whc->workqueue) + destroy_workqueue(whc->workqueue); +} diff --git a/drivers/usb/host/whci/int.c b/drivers/usb/host/whci/int.c new file mode 100644 index 000000000000..fce01174aa9b --- /dev/null +++ b/drivers/usb/host/whci/int.c @@ -0,0 +1,95 @@ +/* + * Wireless Host Controller (WHC) interrupt handling. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +static void transfer_done(struct whc *whc) +{ + queue_work(whc->workqueue, &whc->async_work); + queue_work(whc->workqueue, &whc->periodic_work); +} + +irqreturn_t whc_int_handler(struct usb_hcd *hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 sts; + + sts = le_readl(whc->base + WUSBSTS); + if (!(sts & WUSBSTS_INT_MASK)) + return IRQ_NONE; + le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS); + + if (sts & WUSBSTS_GEN_CMD_DONE) + wake_up(&whc->cmd_wq); + + if (sts & WUSBSTS_HOST_ERR) + dev_err(&whc->umc->dev, "FIXME: host system error\n"); + + if (sts & WUSBSTS_ASYNC_SCHED_SYNCED) + wake_up(&whc->async_list_wq); + + if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED) + wake_up(&whc->periodic_list_wq); + + if (sts & WUSBSTS_DNTS_INT) + queue_work(whc->workqueue, &whc->dn_work); + + /* + * A transfer completed (see [WHCI] section 4.7.1.2 for when + * this occurs). + */ + if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT)) + transfer_done(whc); + + return IRQ_HANDLED; +} + +static int process_dn_buf(struct whc *whc) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + struct dn_buf_entry *dn; + int processed = 0; + + for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) { + if (dn->status & WHC_DN_STATUS_VALID) { + wusbhc_handle_dn(wusbhc, dn->src_addr, + (struct wusb_dn_hdr *)dn->dn_data, + dn->msg_size); + dn->status &= ~WHC_DN_STATUS_VALID; + processed++; + } + } + return processed; +} + +void whc_dn_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, dn_work); + int processed; + + do { + processed = process_dn_buf(whc); + } while (processed); +} diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c new file mode 100644 index 000000000000..8d62df0c330b --- /dev/null +++ b/drivers/usb/host/whci/pzl.c @@ -0,0 +1,398 @@ +/* + * Wireless Host Controller (WHC) periodic schedule management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#define D_LOCAL 0 +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 4 +static void dump_pzl(struct whc *whc, const char *tag) +{ + struct device *dev = &whc->umc->dev; + struct whc_qset *qset; + int period = 0; + + d_printf(4, dev, "PZL %s\n", tag); + + for (period = 0; period < 5; period++) { + d_printf(4, dev, "Period %d\n", period); + list_for_each_entry(qset, &whc->periodic_list[period], list_node) { + dump_qset(qset, dev); + } + } +} +#else +static inline void dump_pzl(struct whc *whc, const char *tag) +{ +} +#endif + +static void update_pzl_pointers(struct whc *whc, int period, u64 addr) +{ + switch (period) { + case 0: + whc_qset_set_link_ptr(&whc->pz_list[0], addr); + whc_qset_set_link_ptr(&whc->pz_list[2], addr); + whc_qset_set_link_ptr(&whc->pz_list[4], addr); + whc_qset_set_link_ptr(&whc->pz_list[6], addr); + whc_qset_set_link_ptr(&whc->pz_list[8], addr); + whc_qset_set_link_ptr(&whc->pz_list[10], addr); + whc_qset_set_link_ptr(&whc->pz_list[12], addr); + whc_qset_set_link_ptr(&whc->pz_list[14], addr); + break; + case 1: + whc_qset_set_link_ptr(&whc->pz_list[1], addr); + whc_qset_set_link_ptr(&whc->pz_list[5], addr); + whc_qset_set_link_ptr(&whc->pz_list[9], addr); + whc_qset_set_link_ptr(&whc->pz_list[13], addr); + break; + case 2: + whc_qset_set_link_ptr(&whc->pz_list[3], addr); + whc_qset_set_link_ptr(&whc->pz_list[11], addr); + break; + case 3: + whc_qset_set_link_ptr(&whc->pz_list[7], addr); + break; + case 4: + whc_qset_set_link_ptr(&whc->pz_list[15], addr); + break; + } +} + +/* + * Return the 'period' to use for this qset. The minimum interval for + * the endpoint is used so whatever urbs are submitted the device is + * polled often enough. + */ +static int qset_get_period(struct whc *whc, struct whc_qset *qset) +{ + uint8_t bInterval = qset->ep->desc.bInterval; + + if (bInterval < 6) + bInterval = 6; + if (bInterval > 10) + bInterval = 10; + return bInterval - 6; +} + +static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset) +{ + int period; + + period = qset_get_period(whc, qset); + + qset_clear(whc, qset); + list_move(&qset->list_node, &whc->periodic_list[period]); + qset->in_sw_list = true; +} + +static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset) +{ + list_move(&qset->list_node, &whc->periodic_removed_list); + qset->in_hw_list = false; + qset->in_sw_list = false; +} + +/** + * pzl_process_qset - process any recently inactivated or halted qTDs + * in a qset. + * + * After inactive qTDs are removed, new qTDs can be added if the + * urb queue still contains URBs. + * + * Returns the schedule updates required. + */ +static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset) +{ + enum whc_update update = 0; + uint32_t status = 0; + + while (qset->ntds) { + struct whc_qtd *td; + int t; + + t = qset->td_start; + td = &qset->qtd[qset->td_start]; + status = le32_to_cpu(td->status); + + /* + * Nothing to do with a still active qTD. + */ + if (status & QTD_STS_ACTIVE) + break; + + if (status & QTD_STS_HALTED) { + /* Ug, an error. */ + process_halted_qtd(whc, qset, td); + goto done; + } + + /* Mmm, a completed qTD. */ + process_inactive_qtd(whc, qset, td); + } + + update |= qset_add_qtds(whc, qset); + +done: + /* + * If there are no qTDs in this qset, remove it from the PZL. + */ + if (qset->remove && qset->ntds == 0) { + pzl_qset_remove(whc, qset); + update |= WHC_UPDATE_REMOVED; + } + + return update; +} + +/** + * pzl_start - start the periodic schedule + * @whc: the WHCI host controller + * + * The PZL must be valid (e.g., all entries in the list should have + * the T bit set). + */ +void pzl_start(struct whc *whc) +{ + le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE); + + whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED, + 1000, "start PZL"); +} + +/** + * pzl_stop - stop the periodic schedule + * @whc: the WHCI host controller + */ +void pzl_stop(struct whc *whc) +{ + whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_PERIODIC_SCHED, 0, + 1000, "stop PZL"); +} + +void pzl_update(struct whc *whc, uint32_t wusbcmd) +{ + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + wait_event(whc->periodic_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0); +} + +static void update_pzl_hw_view(struct whc *whc) +{ + struct whc_qset *qset, *t; + int period; + u64 tmp_qh = 0; + + for (period = 0; period < 5; period++) { + list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { + whc_qset_set_link_ptr(&qset->qh.link, tmp_qh); + tmp_qh = qset->qset_dma; + qset->in_hw_list = true; + } + update_pzl_pointers(whc, period, tmp_qh); + } +} + +/** + * scan_periodic_work - scan the PZL for qsets to process. + * + * Process each qset in the PZL in turn and then signal the WHC that + * the PZL has been updated. + * + * Then start, stop or update the periodic schedule as required. + */ +void scan_periodic_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, periodic_work); + struct whc_qset *qset, *t; + enum whc_update update = 0; + int period; + + spin_lock_irq(&whc->lock); + + dump_pzl(whc, "before processing"); + + for (period = 4; period >= 0; period--) { + list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { + if (!qset->in_hw_list) + update |= WHC_UPDATE_ADDED; + update |= pzl_process_qset(whc, qset); + } + } + + if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED)) + update_pzl_hw_view(whc); + + dump_pzl(whc, "after processing"); + + spin_unlock_irq(&whc->lock); + + if (update) { + uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB; + if (update & WHC_UPDATE_REMOVED) + wusbcmd |= WUSBCMD_PERIODIC_QSET_RM; + pzl_update(whc, wusbcmd); + } + + /* + * Now that the PZL is updated, complete the removal of any + * removed qsets. + */ + spin_lock(&whc->lock); + + list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) { + qset_remove_complete(whc, qset); + } + + spin_unlock(&whc->lock); +} + +/** + * pzl_urb_enqueue - queue an URB onto the periodic list (PZL) + * @whc: the WHCI host controller + * @urb: the URB to enqueue + * @mem_flags: flags for any memory allocations + * + * The qset for the endpoint is obtained and the urb queued on to it. + * + * Work is scheduled to update the hardware's view of the PZL. + */ +int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags) +{ + struct whc_qset *qset; + int err; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + qset = get_qset(whc, urb, GFP_ATOMIC); + if (qset == NULL) + err = -ENOMEM; + else + err = qset_add_urb(whc, qset, urb, GFP_ATOMIC); + if (!err) { + usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb); + if (!qset->in_sw_list) + qset_insert_in_sw_list(whc, qset); + } + + spin_unlock_irqrestore(&whc->lock, flags); + + if (!err) + queue_work(whc->workqueue, &whc->periodic_work); + + return 0; +} + +/** + * pzl_urb_dequeue - remove an URB (qset) from the periodic list + * @whc: the WHCI host controller + * @urb: the URB to dequeue + * @status: the current status of the URB + * + * URBs that do yet have qTDs can simply be removed from the software + * queue, otherwise the qset must be removed so the qTDs can be safely + * removed. + */ +int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status) +{ + struct whc_urb *wurb = urb->hcpriv; + struct whc_qset *qset = wurb->qset; + struct whc_std *std, *t; + int ret; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status); + if (ret < 0) + goto out; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(whc, std); + else + std->qtd = NULL; /* so this std is re-added when the qset is */ + } + + pzl_qset_remove(whc, qset); + wurb->status = status; + wurb->is_async = false; + queue_work(whc->workqueue, &wurb->dequeue_work); + +out: + spin_unlock_irqrestore(&whc->lock, flags); + + return ret; +} + +/** + * pzl_qset_delete - delete a qset from the PZL + */ +void pzl_qset_delete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 1; + queue_work(whc->workqueue, &whc->periodic_work); + qset_delete(whc, qset); +} + + +/** + * pzl_init - initialize the periodic zone list + * @whc: the WHCI host controller + */ +int pzl_init(struct whc *whc) +{ + int i; + + whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16, + &whc->pz_list_dma, GFP_KERNEL); + if (whc->pz_list == NULL) + return -ENOMEM; + + /* Set T bit on all elements in PZL. */ + for (i = 0; i < 16; i++) + whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T); + + le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE); + + return 0; +} + +/** + * pzl_clean_up - free PZL resources + * @whc: the WHCI host controller + * + * The PZL is stopped and empty. + */ +void pzl_clean_up(struct whc *whc) +{ + if (whc->pz_list) + dma_free_coherent(&whc->umc->dev, sizeof(u64) * 16, whc->pz_list, + whc->pz_list_dma); +} diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c new file mode 100644 index 000000000000..0420037d2e18 --- /dev/null +++ b/drivers/usb/host/whci/qset.c @@ -0,0 +1,567 @@ +/* + * Wireless Host Controller (WHC) qset management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +void dump_qset(struct whc_qset *qset, struct device *dev) +{ + struct whc_std *std; + struct urb *urb = NULL; + int i; + + dev_dbg(dev, "qset %08x\n", (u32)qset->qset_dma); + dev_dbg(dev, " -> %08x\n", (u32)qset->qh.link); + dev_dbg(dev, " info: %08x %08x %08x\n", + qset->qh.info1, qset->qh.info2, qset->qh.info3); + dev_dbg(dev, " sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count); + dev_dbg(dev, " TD: sts: %08x opts: %08x\n", + qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options); + + for (i = 0; i < WHCI_QSET_TD_MAX; i++) { + dev_dbg(dev, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n", + i == qset->td_start ? 'S' : ' ', + i == qset->td_end ? 'E' : ' ', + i, qset->qtd[i].status, qset->qtd[i].options, + (u32)qset->qtd[i].page_list_ptr); + } + dev_dbg(dev, " ntds: %d\n", qset->ntds); + list_for_each_entry(std, &qset->stds, list_node) { + if (urb != std->urb) { + urb = std->urb; + dev_dbg(dev, " urb %p transferred: %d bytes\n", urb, + urb->actual_length); + } + if (std->qtd) + dev_dbg(dev, " sTD[%td]: %zu bytes @ %08x\n", + std->qtd - &qset->qtd[0], + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + else + dev_dbg(dev, " sTD[-]: %zd bytes @ %08x\n", + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + } +} + +struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags) +{ + struct whc_qset *qset; + dma_addr_t dma; + + qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma); + if (qset == NULL) + return NULL; + memset(qset, 0, sizeof(struct whc_qset)); + + qset->qset_dma = dma; + qset->whc = whc; + + INIT_LIST_HEAD(&qset->list_node); + INIT_LIST_HEAD(&qset->stds); + + return qset; +} + +/** + * qset_fill_qh - fill the static endpoint state in a qset's QHead + * @qset: the qset whose QH needs initializing with static endpoint + * state + * @urb: an urb for a transfer to this endpoint + */ +static void qset_fill_qh(struct whc_qset *qset, struct urb *urb) +{ + struct usb_device *usb_dev = urb->dev; + struct usb_wireless_ep_comp_descriptor *epcd; + bool is_out; + + is_out = usb_pipeout(urb->pipe); + + epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra; + + if (epcd) { + qset->max_seq = epcd->bMaxSequence; + qset->max_burst = epcd->bMaxBurst; + } else { + qset->max_seq = 2; + qset->max_burst = 1; + } + + qset->qh.info1 = cpu_to_le32( + QH_INFO1_EP(usb_pipeendpoint(urb->pipe)) + | (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN) + | usb_pipe_to_qh_type(urb->pipe) + | QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum)) + | QH_INFO1_MAX_PKT_LEN(usb_maxpacket(urb->dev, urb->pipe, is_out)) + ); + qset->qh.info2 = cpu_to_le32( + QH_INFO2_BURST(qset->max_burst) + | QH_INFO2_DBP(0) + | QH_INFO2_MAX_COUNT(3) + | QH_INFO2_MAX_RETRY(3) + | QH_INFO2_MAX_SEQ(qset->max_seq - 1) + ); + /* FIXME: where can we obtain these Tx parameters from? Why + * doesn't the chip know what Tx power to use? It knows the Rx + * strength and can presumably guess the Tx power required + * from that? */ + qset->qh.info3 = cpu_to_le32( + QH_INFO3_TX_RATE_53_3 + | QH_INFO3_TX_PWR(0) /* 0 == max power */ + ); +} + +/** + * qset_clear - clear fields in a qset so it may be reinserted into a + * schedule + */ +void qset_clear(struct whc *whc, struct whc_qset *qset) +{ + qset->td_start = qset->td_end = qset->ntds = 0; + qset->remove = 0; + + qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T); + qset->qh.status = cpu_to_le16(QH_STATUS_ICUR(qset->td_start)); + qset->qh.err_count = 0; + qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1); + qset->qh.scratch[0] = 0; + qset->qh.scratch[1] = 0; + qset->qh.scratch[2] = 0; + + memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay)); + + init_completion(&qset->remove_complete); +} + +/** + * get_qset - get the qset for an async endpoint + * + * A new qset is created if one does not already exist. + */ +struct whc_qset *get_qset(struct whc *whc, struct urb *urb, + gfp_t mem_flags) +{ + struct whc_qset *qset; + + qset = urb->ep->hcpriv; + if (qset == NULL) { + qset = qset_alloc(whc, mem_flags); + if (qset == NULL) + return NULL; + + qset->ep = urb->ep; + urb->ep->hcpriv = qset; + qset_fill_qh(qset, urb); + } + return qset; +} + +void qset_remove_complete(struct whc *whc, struct whc_qset *qset) +{ + list_del_init(&qset->list_node); + complete(&qset->remove_complete); +} + +/** + * qset_add_qtds - add qTDs for an URB to a qset + * + * Returns true if the list (ASL/PZL) must be updated because (for a + * WHCI 0.95 controller) an activated qTD was pointed to be iCur. + */ +enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset) +{ + struct whc_std *std; + enum whc_update update = 0; + + list_for_each_entry(std, &qset->stds, list_node) { + struct whc_qtd *qtd; + uint32_t status; + + if (qset->ntds >= WHCI_QSET_TD_MAX + || (qset->pause_after_urb && std->urb != qset->pause_after_urb)) + break; + + if (std->qtd) + continue; /* already has a qTD */ + + qtd = std->qtd = &qset->qtd[qset->td_end]; + + /* Fill in setup bytes for control transfers. */ + if (usb_pipecontrol(std->urb->pipe)) + memcpy(qtd->setup, std->urb->setup_packet, 8); + + status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len); + + if (whc_std_last(std) && usb_pipeout(std->urb->pipe)) + status |= QTD_STS_LAST_PKT; + + /* + * For an IN transfer the iAlt field should be set so + * the h/w will automatically advance to the next + * transfer. However, if there are 8 or more TDs + * remaining in this transfer then iAlt cannot be set + * as it could point to somewhere in this transfer. + */ + if (std->ntds_remaining < WHCI_QSET_TD_MAX) { + int ialt; + ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX; + status |= QTD_STS_IALT(ialt); + } else if (usb_pipein(std->urb->pipe)) + qset->pause_after_urb = std->urb; + + if (std->num_pointers) + qtd->options = cpu_to_le32(QTD_OPT_IOC); + else + qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL); + qtd->page_list_ptr = cpu_to_le64(std->dma_addr); + + qtd->status = cpu_to_le32(status); + + if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end) + update = WHC_UPDATE_UPDATED; + + if (++qset->td_end >= WHCI_QSET_TD_MAX) + qset->td_end = 0; + qset->ntds++; + } + + return update; +} + +/** + * qset_remove_qtd - remove the first qTD from a qset. + * + * The qTD might be still active (if it's part of a IN URB that + * resulted in a short read) so ensure it's deactivated. + */ +static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset) +{ + qset->qtd[qset->td_start].status = 0; + + if (++qset->td_start >= WHCI_QSET_TD_MAX) + qset->td_start = 0; + qset->ntds--; +} + +/** + * qset_free_std - remove an sTD and free it. + * @whc: the WHCI host controller + * @std: the sTD to remove and free. + */ +void qset_free_std(struct whc *whc, struct whc_std *std) +{ + list_del(&std->list_node); + if (std->num_pointers) { + dma_unmap_single(whc->wusbhc.dev, std->dma_addr, + std->num_pointers * sizeof(struct whc_page_list_entry), + DMA_TO_DEVICE); + kfree(std->pl_virt); + } + + kfree(std); +} + +/** + * qset_remove_qtds - remove an URB's qTDs (and sTDs). + */ +static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset, + struct urb *urb) +{ + struct whc_std *std, *t; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb != urb) + break; + if (std->qtd != NULL) + qset_remove_qtd(whc, qset); + qset_free_std(whc, std); + } +} + +/** + * qset_free_stds - free any remaining sTDs for an URB. + */ +static void qset_free_stds(struct whc_qset *qset, struct urb *urb) +{ + struct whc_std *std, *t; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(qset->whc, std); + } +} + +static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags) +{ + dma_addr_t dma_addr = std->dma_addr; + dma_addr_t sp, ep; + size_t std_len = std->len; + size_t pl_len; + int p; + + sp = ALIGN(dma_addr, WHCI_PAGE_SIZE); + ep = dma_addr + std_len; + std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE); + + pl_len = std->num_pointers * sizeof(struct whc_page_list_entry); + std->pl_virt = kmalloc(pl_len, mem_flags); + if (std->pl_virt == NULL) + return -ENOMEM; + std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE); + + for (p = 0; p < std->num_pointers; p++) { + std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr); + dma_addr = ALIGN(dma_addr + WHCI_PAGE_SIZE, WHCI_PAGE_SIZE); + } + + return 0; +} + +/** + * urb_dequeue_work - executes asl/pzl update and gives back the urb to the system. + */ +static void urb_dequeue_work(struct work_struct *work) +{ + struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work); + struct whc_qset *qset = wurb->qset; + struct whc *whc = qset->whc; + unsigned long flags; + + if (wurb->is_async == true) + asl_update(whc, WUSBCMD_ASYNC_UPDATED + | WUSBCMD_ASYNC_SYNCED_DB + | WUSBCMD_ASYNC_QSET_RM); + else + pzl_update(whc, WUSBCMD_PERIODIC_UPDATED + | WUSBCMD_PERIODIC_SYNCED_DB + | WUSBCMD_PERIODIC_QSET_RM); + + spin_lock_irqsave(&whc->lock, flags); + qset_remove_urb(whc, qset, wurb->urb, wurb->status); + spin_unlock_irqrestore(&whc->lock, flags); +} + +/** + * qset_add_urb - add an urb to the qset's queue. + * + * The URB is chopped into sTDs, one for each qTD that will required. + * At least one qTD (and sTD) is required even if the transfer has no + * data (e.g., for some control transfers). + */ +int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags) +{ + struct whc_urb *wurb; + int remaining = urb->transfer_buffer_length; + u64 transfer_dma = urb->transfer_dma; + int ntds_remaining; + + ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE); + if (ntds_remaining == 0) + ntds_remaining = 1; + + wurb = kzalloc(sizeof(struct whc_urb), mem_flags); + if (wurb == NULL) + goto err_no_mem; + urb->hcpriv = wurb; + wurb->qset = qset; + wurb->urb = urb; + INIT_WORK(&wurb->dequeue_work, urb_dequeue_work); + + while (ntds_remaining) { + struct whc_std *std; + size_t std_len; + + std = kmalloc(sizeof(struct whc_std), mem_flags); + if (std == NULL) + goto err_no_mem; + + std_len = remaining; + if (std_len > QTD_MAX_XFER_SIZE) + std_len = QTD_MAX_XFER_SIZE; + + std->urb = urb; + std->dma_addr = transfer_dma; + std->len = std_len; + std->ntds_remaining = ntds_remaining; + std->qtd = NULL; + + INIT_LIST_HEAD(&std->list_node); + list_add_tail(&std->list_node, &qset->stds); + + if (std_len > WHCI_PAGE_SIZE) { + if (qset_fill_page_list(whc, std, mem_flags) < 0) + goto err_no_mem; + } else + std->num_pointers = 0; + + ntds_remaining--; + remaining -= std_len; + transfer_dma += std_len; + } + + return 0; + +err_no_mem: + qset_free_stds(qset, urb); + return -ENOMEM; +} + +/** + * qset_remove_urb - remove an URB from the urb queue. + * + * The URB is returned to the USB subsystem. + */ +void qset_remove_urb(struct whc *whc, struct whc_qset *qset, + struct urb *urb, int status) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + struct whc_urb *wurb = urb->hcpriv; + + usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb); + /* Drop the lock as urb->complete() may enqueue another urb. */ + spin_unlock(&whc->lock); + wusbhc_giveback_urb(wusbhc, urb, status); + spin_lock(&whc->lock); + + kfree(wurb); +} + +/** + * get_urb_status_from_qtd - get the completed urb status from qTD status + * @urb: completed urb + * @status: qTD status + */ +static int get_urb_status_from_qtd(struct urb *urb, u32 status) +{ + if (status & QTD_STS_HALTED) { + if (status & QTD_STS_DBE) + return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM; + else if (status & QTD_STS_BABBLE) + return -EOVERFLOW; + else if (status & QTD_STS_RCE) + return -ETIME; + return -EPIPE; + } + if (usb_pipein(urb->pipe) + && (urb->transfer_flags & URB_SHORT_NOT_OK) + && urb->actual_length < urb->transfer_buffer_length) + return -EREMOTEIO; + return 0; +} + +/** + * process_inactive_qtd - process an inactive (but not halted) qTD. + * + * Update the urb with the transfer bytes from the qTD, if the urb is + * completely transfered or (in the case of an IN only) the LPF is + * set, then the transfer is complete and the urb should be returned + * to the system. + */ +void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd) +{ + struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node); + struct urb *urb = std->urb; + uint32_t status; + bool complete; + + status = le32_to_cpu(qtd->status); + + urb->actual_length += std->len - QTD_STS_TO_LEN(status); + + if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT)) + complete = true; + else + complete = whc_std_last(std); + + qset_remove_qtd(whc, qset); + qset_free_std(whc, std); + + /* + * Transfers for this URB are complete? Then return it to the + * USB subsystem. + */ + if (complete) { + qset_remove_qtds(whc, qset, urb); + qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status)); + + /* + * If iAlt isn't valid then the hardware didn't + * advance iCur. Adjust the start and end pointers to + * match iCur. + */ + if (!(status & QTD_STS_IALT_VALID)) + qset->td_start = qset->td_end + = QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status)); + qset->pause_after_urb = NULL; + } +} + +/** + * process_halted_qtd - process a qset with a halted qtd + * + * Remove all the qTDs for the failed URB and return the failed URB to + * the USB subsystem. Then remove all other qTDs so the qset can be + * removed. + * + * FIXME: this is the point where rate adaptation can be done. If a + * transfer failed because it exceeded the maximum number of retries + * then it could be reactivated with a slower rate without having to + * remove the qset. + */ +void process_halted_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd) +{ + struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node); + struct urb *urb = std->urb; + int urb_status; + + urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status)); + + qset_remove_qtds(whc, qset, urb); + qset_remove_urb(whc, qset, urb, urb_status); + + list_for_each_entry(std, &qset->stds, list_node) { + if (qset->ntds == 0) + break; + qset_remove_qtd(whc, qset); + std->qtd = NULL; + } + + qset->remove = 1; +} + +void qset_free(struct whc *whc, struct whc_qset *qset) +{ + dma_pool_free(whc->qset_pool, qset, qset->qset_dma); +} + +/** + * qset_delete - wait for a qset to be unused, then free it. + */ +void qset_delete(struct whc *whc, struct whc_qset *qset) +{ + wait_for_completion(&qset->remove_complete); + qset_free(whc, qset); +} diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h new file mode 100644 index 000000000000..1d2a53bd39fd --- /dev/null +++ b/drivers/usb/host/whci/whcd.h @@ -0,0 +1,197 @@ +/* + * Wireless Host Controller (WHC) private header. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#ifndef __WHCD_H +#define __WHCD_H + +#include +#include + +#include "whci-hc.h" + +/* Generic command timeout. */ +#define WHC_GENCMD_TIMEOUT_MS 100 + + +struct whc { + struct wusbhc wusbhc; + struct umc_dev *umc; + + resource_size_t base_phys; + void __iomem *base; + int irq; + + u8 n_devices; + u8 n_keys; + u8 n_mmc_ies; + + u64 *pz_list; + struct dn_buf_entry *dn_buf; + struct di_buf_entry *di_buf; + dma_addr_t pz_list_dma; + dma_addr_t dn_buf_dma; + dma_addr_t di_buf_dma; + + spinlock_t lock; + struct mutex mutex; + + void * gen_cmd_buf; + dma_addr_t gen_cmd_buf_dma; + wait_queue_head_t cmd_wq; + + struct workqueue_struct *workqueue; + struct work_struct dn_work; + + struct dma_pool *qset_pool; + + struct list_head async_list; + struct list_head async_removed_list; + wait_queue_head_t async_list_wq; + struct work_struct async_work; + + struct list_head periodic_list[5]; + struct list_head periodic_removed_list; + wait_queue_head_t periodic_list_wq; + struct work_struct periodic_work; +}; + +#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc)) + +/** + * struct whc_std - a software TD. + * @urb: the URB this sTD is for. + * @offset: start of the URB's data for this TD. + * @len: the length of data in the associated TD. + * @ntds_remaining: number of TDs (starting from this one) in this transfer. + * + * Queued URBs may require more TDs than are available in a qset so we + * use a list of these "software TDs" (sTDs) to hold per-TD data. + */ +struct whc_std { + struct urb *urb; + size_t len; + int ntds_remaining; + struct whc_qtd *qtd; + + struct list_head list_node; + int num_pointers; + dma_addr_t dma_addr; + struct whc_page_list_entry *pl_virt; +}; + +/** + * struct whc_urb - per URB host controller structure. + * @urb: the URB this struct is for. + * @qset: the qset associated to the URB. + * @dequeue_work: the work to remove the URB when dequeued. + * @is_async: the URB belongs to async sheduler or not. + * @status: the status to be returned when calling wusbhc_giveback_urb. + */ +struct whc_urb { + struct urb *urb; + struct whc_qset *qset; + struct work_struct dequeue_work; + bool is_async; + int status; +}; + +/** + * whc_std_last - is this sTD the URB's last? + * @std: the sTD to check. + */ +static inline bool whc_std_last(struct whc_std *std) +{ + return std->ntds_remaining <= 1; +} + +enum whc_update { + WHC_UPDATE_ADDED = 0x01, + WHC_UPDATE_REMOVED = 0x02, + WHC_UPDATE_UPDATED = 0x04, +}; + +/* init.c */ +int whc_init(struct whc *whc); +void whc_clean_up(struct whc *whc); + +/* hw.c */ +void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val); +int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len); + +/* wusb.c */ +int whc_wusbhc_start(struct wusbhc *wusbhc); +void whc_wusbhc_stop(struct wusbhc *wusbhc); +int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie); +int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle); +int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm); +int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev); +int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots); +int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *ptk, size_t key_size); +int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *gtk, size_t key_size); +int whc_set_cluster_id(struct whc *whc, u8 bcid); + +/* int.c */ +irqreturn_t whc_int_handler(struct usb_hcd *hcd); +void whc_dn_work(struct work_struct *work); + +/* asl.c */ +void asl_start(struct whc *whc); +void asl_stop(struct whc *whc); +int asl_init(struct whc *whc); +void asl_clean_up(struct whc *whc); +int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags); +int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status); +void asl_qset_delete(struct whc *whc, struct whc_qset *qset); +void scan_async_work(struct work_struct *work); + +/* pzl.c */ +int pzl_init(struct whc *whc); +void pzl_clean_up(struct whc *whc); +void pzl_start(struct whc *whc); +void pzl_stop(struct whc *whc); +int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags); +int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status); +void pzl_qset_delete(struct whc *whc, struct whc_qset *qset); +void scan_periodic_work(struct work_struct *work); + +/* qset.c */ +struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags); +void qset_free(struct whc *whc, struct whc_qset *qset); +struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags); +void qset_delete(struct whc *whc, struct whc_qset *qset); +void qset_clear(struct whc *whc, struct whc_qset *qset); +int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags); +void qset_free_std(struct whc *whc, struct whc_std *std); +void qset_remove_urb(struct whc *whc, struct whc_qset *qset, + struct urb *urb, int status); +void process_halted_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd); +void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd); +enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset); +void qset_remove_complete(struct whc *whc, struct whc_qset *qset); +void dump_qset(struct whc_qset *qset, struct device *dev); +void pzl_update(struct whc *whc, uint32_t wusbcmd); +void asl_update(struct whc *whc, uint32_t wusbcmd); + +#endif /* #ifndef __WHCD_H */ diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h new file mode 100644 index 000000000000..bff1eb7a35cf --- /dev/null +++ b/drivers/usb/host/whci/whci-hc.h @@ -0,0 +1,416 @@ +/* + * Wireless Host Controller (WHC) data structures. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#ifndef _WHCI_WHCI_HC_H +#define _WHCI_WHCI_HC_H + +#include + +/** + * WHCI_PAGE_SIZE - page size use by WHCI + * + * WHCI assumes that host system uses pages of 4096 octets. + */ +#define WHCI_PAGE_SIZE 4096 + + +/** + * QTD_MAX_TXFER_SIZE - max number of bytes to transfer with a single + * qtd. + * + * This is 2^20 - 1. + */ +#define QTD_MAX_XFER_SIZE 1048575 + + +/** + * struct whc_qtd - Queue Element Transfer Descriptors (qTD) + * + * This describes the data for a bulk, control or interrupt transfer. + * + * [WHCI] section 3.2.4 + */ +struct whc_qtd { + __le32 status; /*< remaining transfer len and transfer status */ + __le32 options; + __le64 page_list_ptr; /*< physical pointer to data buffer page list*/ + __u8 setup[8]; /*< setup data for control transfers */ +} __attribute__((packed)); + +#define QTD_STS_ACTIVE (1 << 31) /* enable execution of transaction */ +#define QTD_STS_HALTED (1 << 30) /* transfer halted */ +#define QTD_STS_DBE (1 << 29) /* data buffer error */ +#define QTD_STS_BABBLE (1 << 28) /* babble detected */ +#define QTD_STS_RCE (1 << 27) /* retry count exceeded */ +#define QTD_STS_LAST_PKT (1 << 26) /* set Last Packet Flag in WUSB header */ +#define QTD_STS_INACTIVE (1 << 25) /* queue set is marked inactive */ +#define QTD_STS_IALT_VALID (1 << 23) /* iAlt field is valid */ +#define QTD_STS_IALT(i) (QTD_STS_IALT_VALID | ((i) << 20)) /* iAlt field */ +#define QTD_STS_LEN(l) ((l) << 0) /* transfer length */ +#define QTD_STS_TO_LEN(s) ((s) & 0x000fffff) + +#define QTD_OPT_IOC (1 << 1) /* page_list_ptr points to buffer directly */ +#define QTD_OPT_SMALL (1 << 0) /* interrupt on complete */ + +/** + * struct whc_itd - Isochronous Queue Element Transfer Descriptors (iTD) + * + * This describes the data and other parameters for an isochronous + * transfer. + * + * [WHCI] section 3.2.5 + */ +struct whc_itd { + __le16 presentation_time; /*< presentation time for OUT transfers */ + __u8 num_segments; /*< number of data segments in segment list */ + __u8 status; /*< command execution status */ + __le32 options; /*< misc transfer options */ + __le64 page_list_ptr; /*< physical pointer to data buffer page list */ + __le64 seg_list_ptr; /*< physical pointer to segment list */ +} __attribute__((packed)); + +#define ITD_STS_ACTIVE (1 << 7) /* enable execution of transaction */ +#define ITD_STS_DBE (1 << 5) /* data buffer error */ +#define ITD_STS_BABBLE (1 << 4) /* babble detected */ +#define ITD_STS_INACTIVE (1 << 1) /* queue set is marked inactive */ + +#define ITD_OPT_IOC (1 << 1) /* interrupt on complete */ +#define ITD_OPT_SMALL (1 << 0) /* page_list_ptr points to buffer directly */ + +/** + * Page list entry. + * + * A TD's page list must contain sufficient page list entries for the + * total data length in the TD. + * + * [WHCI] section 3.2.4.3 + */ +struct whc_page_list_entry { + __le64 buf_ptr; /*< physical pointer to buffer */ +} __attribute__((packed)); + +/** + * struct whc_seg_list_entry - Segment list entry. + * + * Describes a portion of the data buffer described in the containing + * qTD's page list. + * + * seg_ptr = qtd->page_list_ptr[qtd->seg_list_ptr[seg].idx].buf_ptr + * + qtd->seg_list_ptr[seg].offset; + * + * Segments can't cross page boundries. + * + * [WHCI] section 3.2.5.5 + */ +struct whc_seg_list_entry { + __le16 len; /*< segment length */ + __u8 idx; /*< index into page list */ + __u8 status; /*< segment status */ + __le16 offset; /*< 12 bit offset into page */ +} __attribute__((packed)); + +/** + * struct whc_qhead - endpoint and status information for a qset. + * + * [WHCI] section 3.2.6 + */ +struct whc_qhead { + __le64 link; /*< next qset in list */ + __le32 info1; + __le32 info2; + __le32 info3; + __le16 status; + __le16 err_count; /*< transaction error count */ + __le32 cur_window; + __le32 scratch[3]; /*< h/w scratch area */ + union { + struct whc_qtd qtd; + struct whc_itd itd; + } overlay; +} __attribute__((packed)); + +#define QH_LINK_PTR_MASK (~0x03Full) +#define QH_LINK_PTR(ptr) ((ptr) & QH_LINK_PTR_MASK) +#define QH_LINK_IQS (1 << 4) /* isochronous queue set */ +#define QH_LINK_NTDS(n) (((n) - 1) << 1) /* number of TDs in queue set */ +#define QH_LINK_T (1 << 0) /* last queue set in periodic schedule list */ + +#define QH_INFO1_EP(e) ((e) << 0) /* endpoint number */ +#define QH_INFO1_DIR_IN (1 << 4) /* IN transfer */ +#define QH_INFO1_DIR_OUT (0 << 4) /* OUT transfer */ +#define QH_INFO1_TR_TYPE_CTRL (0x0 << 5) /* control transfer */ +#define QH_INFO1_TR_TYPE_ISOC (0x1 << 5) /* isochronous transfer */ +#define QH_INFO1_TR_TYPE_BULK (0x2 << 5) /* bulk transfer */ +#define QH_INFO1_TR_TYPE_INT (0x3 << 5) /* interrupt */ +#define QH_INFO1_TR_TYPE_LP_INT (0x7 << 5) /* low power interrupt */ +#define QH_INFO1_DEV_INFO_IDX(i) ((i) << 8) /* index into device info buffer */ +#define QH_INFO1_SET_INACTIVE (1 << 15) /* set inactive after transfer */ +#define QH_INFO1_MAX_PKT_LEN(l) ((l) << 16) /* maximum packet length */ + +#define QH_INFO2_BURST(b) ((b) << 0) /* maximum burst length */ +#define QH_INFO2_DBP(p) ((p) << 5) /* data burst policy (see [WUSB] table 5-7) */ +#define QH_INFO2_MAX_COUNT(c) ((c) << 8) /* max isoc/int pkts per zone */ +#define QH_INFO2_RQS (1 << 15) /* reactivate queue set */ +#define QH_INFO2_MAX_RETRY(r) ((r) << 16) /* maximum transaction retries */ +#define QH_INFO2_MAX_SEQ(s) ((s) << 20) /* maximum sequence number */ +#define QH_INFO3_MAX_DELAY(d) ((d) << 0) /* maximum stream delay in 125 us units (isoc only) */ +#define QH_INFO3_INTERVAL(i) ((i) << 16) /* segment interval in 125 us units (isoc only) */ + +#define QH_INFO3_TX_RATE_53_3 (0 << 24) +#define QH_INFO3_TX_RATE_80 (1 << 24) +#define QH_INFO3_TX_RATE_106_7 (2 << 24) +#define QH_INFO3_TX_RATE_160 (3 << 24) +#define QH_INFO3_TX_RATE_200 (4 << 24) +#define QH_INFO3_TX_RATE_320 (5 << 24) +#define QH_INFO3_TX_RATE_400 (6 << 24) +#define QH_INFO3_TX_RATE_480 (7 << 24) +#define QH_INFO3_TX_PWR(p) ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */ + +#define QH_STATUS_FLOW_CTRL (1 << 15) +#define QH_STATUS_ICUR(i) ((i) << 5) +#define QH_STATUS_TO_ICUR(s) (((s) >> 5) & 0x7) + +/** + * usb_pipe_to_qh_type - USB core pipe type to QH transfer type + * + * Returns the QH type field for a USB core pipe type. + */ +static inline unsigned usb_pipe_to_qh_type(unsigned pipe) +{ + static const unsigned type[] = { + [PIPE_ISOCHRONOUS] = QH_INFO1_TR_TYPE_ISOC, + [PIPE_INTERRUPT] = QH_INFO1_TR_TYPE_INT, + [PIPE_CONTROL] = QH_INFO1_TR_TYPE_CTRL, + [PIPE_BULK] = QH_INFO1_TR_TYPE_BULK, + }; + return type[usb_pipetype(pipe)]; +} + +/** + * Maxiumum number of TDs in a qset. + */ +#define WHCI_QSET_TD_MAX 8 + +/** + * struct whc_qset - WUSB data transfers to a specific endpoint + * @qh: the QHead of this qset + * @qtd: up to 8 qTDs (for qsets for control, bulk and interrupt + * transfers) + * @itd: up to 8 iTDs (for qsets for isochronous transfers) + * @qset_dma: DMA address for this qset + * @whc: WHCI HC this qset is for + * @ep: endpoint + * @stds: list of sTDs queued to this qset + * @ntds: number of qTDs queued (not necessarily the same as nTDs + * field in the QH) + * @td_start: index of the first qTD in the list + * @td_end: index of next free qTD in the list (provided + * ntds < WHCI_QSET_TD_MAX) + * + * Queue Sets (qsets) are added to the asynchronous schedule list + * (ASL) or the periodic zone list (PZL). + * + * qsets may contain up to 8 TDs (either qTDs or iTDs as appropriate). + * Each TD may refer to at most 1 MiB of data. If a single transfer + * has > 8MiB of data, TDs can be reused as they are completed since + * the TD list is used as a circular buffer. Similarly, several + * (smaller) transfers may be queued in a qset. + * + * WHCI controllers may cache portions of the qsets in the ASL and + * PZL, requiring the WHCD to inform the WHC that the lists have been + * updated (fields changed or qsets inserted or removed). For safe + * insertion and removal of qsets from the lists the schedule must be + * stopped to avoid races in updating the QH link pointers. + * + * Since the HC is free to execute qsets in any order, all transfers + * to an endpoint should use the same qset to ensure transfers are + * executed in the order they're submitted. + * + * [WHCI] section 3.2.3 + */ +struct whc_qset { + struct whc_qhead qh; + union { + struct whc_qtd qtd[WHCI_QSET_TD_MAX]; + struct whc_itd itd[WHCI_QSET_TD_MAX]; + }; + + /* private data for WHCD */ + dma_addr_t qset_dma; + struct whc *whc; + struct usb_host_endpoint *ep; + struct list_head stds; + int ntds; + int td_start; + int td_end; + struct list_head list_node; + unsigned in_sw_list:1; + unsigned in_hw_list:1; + unsigned remove:1; + struct urb *pause_after_urb; + struct completion remove_complete; + int max_burst; + int max_seq; +}; + +static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target) +{ + if (target) + *ptr = (*ptr & ~(QH_LINK_PTR_MASK | QH_LINK_T)) | QH_LINK_PTR(target); + else + *ptr = QH_LINK_T; +} + +/** + * struct di_buf_entry - Device Information (DI) buffer entry. + * + * There's one of these per connected device. + */ +struct di_buf_entry { + __le32 availability_info[8]; /*< MAS availability information, one MAS per bit */ + __le32 addr_sec_info; /*< addressing and security info */ + __le32 reserved[7]; +} __attribute__((packed)); + +#define WHC_DI_SECURE (1 << 31) +#define WHC_DI_DISABLE (1 << 30) +#define WHC_DI_KEY_IDX(k) ((k) << 8) +#define WHC_DI_KEY_IDX_MASK 0x0000ff00 +#define WHC_DI_DEV_ADDR(a) ((a) << 0) +#define WHC_DI_DEV_ADDR_MASK 0x000000ff + +/** + * struct dn_buf_entry - Device Notification (DN) buffer entry. + * + * [WHCI] section 3.2.8 + */ +struct dn_buf_entry { + __u8 msg_size; /*< number of octets of valid DN data */ + __u8 reserved1; + __u8 src_addr; /*< source address */ + __u8 status; /*< buffer entry status */ + __le32 tkid; /*< TKID for source device, valid if secure bit is set */ + __u8 dn_data[56]; /*< up to 56 octets of DN data */ +} __attribute__((packed)); + +#define WHC_DN_STATUS_VALID (1 << 7) /* buffer entry is valid */ +#define WHC_DN_STATUS_SECURE (1 << 6) /* notification received using secure frame */ + +#define WHC_N_DN_ENTRIES (4096 / sizeof(struct dn_buf_entry)) + +/* The Add MMC IE WUSB Generic Command may take up to 256 bytes of + data. [WHCI] section 2.4.7. */ +#define WHC_GEN_CMD_DATA_LEN 256 + +/* + * HC registers. + * + * [WHCI] section 2.4 + */ + +#define WHCIVERSION 0x00 + +#define WHCSPARAMS 0x04 +# define WHCSPARAMS_TO_N_MMC_IES(p) (((p) >> 16) & 0xff) +# define WHCSPARAMS_TO_N_KEYS(p) (((p) >> 8) & 0xff) +# define WHCSPARAMS_TO_N_DEVICES(p) (((p) >> 0) & 0x7f) + +#define WUSBCMD 0x08 +# define WUSBCMD_BCID(b) ((b) << 16) +# define WUSBCMD_BCID_MASK (0xff << 16) +# define WUSBCMD_ASYNC_QSET_RM (1 << 12) +# define WUSBCMD_PERIODIC_QSET_RM (1 << 11) +# define WUSBCMD_WUSBSI(s) ((s) << 8) +# define WUSBCMD_WUSBSI_MASK (0x7 << 8) +# define WUSBCMD_ASYNC_SYNCED_DB (1 << 7) +# define WUSBCMD_PERIODIC_SYNCED_DB (1 << 6) +# define WUSBCMD_ASYNC_UPDATED (1 << 5) +# define WUSBCMD_PERIODIC_UPDATED (1 << 4) +# define WUSBCMD_ASYNC_EN (1 << 3) +# define WUSBCMD_PERIODIC_EN (1 << 2) +# define WUSBCMD_WHCRESET (1 << 1) +# define WUSBCMD_RUN (1 << 0) + +#define WUSBSTS 0x0c +# define WUSBSTS_ASYNC_SCHED (1 << 15) +# define WUSBSTS_PERIODIC_SCHED (1 << 14) +# define WUSBSTS_DNTS_SCHED (1 << 13) +# define WUSBSTS_HCHALTED (1 << 12) +# define WUSBSTS_GEN_CMD_DONE (1 << 9) +# define WUSBSTS_CHAN_TIME_ROLLOVER (1 << 8) +# define WUSBSTS_DNTS_OVERFLOW (1 << 7) +# define WUSBSTS_BPST_ADJUSTMENT_CHANGED (1 << 6) +# define WUSBSTS_HOST_ERR (1 << 5) +# define WUSBSTS_ASYNC_SCHED_SYNCED (1 << 4) +# define WUSBSTS_PERIODIC_SCHED_SYNCED (1 << 3) +# define WUSBSTS_DNTS_INT (1 << 2) +# define WUSBSTS_ERR_INT (1 << 1) +# define WUSBSTS_INT (1 << 0) +# define WUSBSTS_INT_MASK 0x3ff + +#define WUSBINTR 0x10 +# define WUSBINTR_GEN_CMD_DONE (1 << 9) +# define WUSBINTR_CHAN_TIME_ROLLOVER (1 << 8) +# define WUSBINTR_DNTS_OVERFLOW (1 << 7) +# define WUSBINTR_BPST_ADJUSTMENT_CHANGED (1 << 6) +# define WUSBINTR_HOST_ERR (1 << 5) +# define WUSBINTR_ASYNC_SCHED_SYNCED (1 << 4) +# define WUSBINTR_PERIODIC_SCHED_SYNCED (1 << 3) +# define WUSBINTR_DNTS_INT (1 << 2) +# define WUSBINTR_ERR_INT (1 << 1) +# define WUSBINTR_INT (1 << 0) +# define WUSBINTR_ALL 0x3ff + +#define WUSBGENCMDSTS 0x14 +# define WUSBGENCMDSTS_ACTIVE (1 << 31) +# define WUSBGENCMDSTS_ERROR (1 << 24) +# define WUSBGENCMDSTS_IOC (1 << 23) +# define WUSBGENCMDSTS_MMCIE_ADD 0x01 +# define WUSBGENCMDSTS_MMCIE_RM 0x02 +# define WUSBGENCMDSTS_SET_MAS 0x03 +# define WUSBGENCMDSTS_CHAN_STOP 0x04 +# define WUSBGENCMDSTS_RWP_EN 0x05 + +#define WUSBGENCMDPARAMS 0x18 +#define WUSBGENADDR 0x20 +#define WUSBASYNCLISTADDR 0x28 +#define WUSBDNTSBUFADDR 0x30 +#define WUSBDEVICEINFOADDR 0x38 + +#define WUSBSETSECKEYCMD 0x40 +# define WUSBSETSECKEYCMD_SET (1 << 31) +# define WUSBSETSECKEYCMD_ERASE (1 << 30) +# define WUSBSETSECKEYCMD_GTK (1 << 8) +# define WUSBSETSECKEYCMD_IDX(i) ((i) << 0) + +#define WUSBTKID 0x44 +#define WUSBSECKEY 0x48 +#define WUSBPERIODICLISTBASE 0x58 +#define WUSBMASINDEX 0x60 + +#define WUSBDNTSCTRL 0x64 +# define WUSBDNTSCTRL_ACTIVE (1 << 31) +# define WUSBDNTSCTRL_INTERVAL(i) ((i) << 8) +# define WUSBDNTSCTRL_SLOTS(s) ((s) << 0) + +#define WUSBTIME 0x68 +#define WUSBBPST 0x6c +#define WUSBDIBUPDATED 0x70 + +#endif /* #ifndef _WHCI_WHCI_HC_H */ diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c new file mode 100644 index 000000000000..66e4ddcd961d --- /dev/null +++ b/drivers/usb/host/whci/wusb.c @@ -0,0 +1,241 @@ +/* + * Wireless Host Controller (WHC) WUSB operations. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#define D_LOCAL 1 +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 1 +static void dump_di(struct whc *whc, int idx) +{ + struct di_buf_entry *di = &whc->di_buf[idx]; + struct device *dev = &whc->umc->dev; + char buf[128]; + + bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS); + + d_printf(1, dev, "DI[%d]\n", idx); + d_printf(1, dev, " availability: %s\n", buf); + d_printf(1, dev, " %c%c key idx: %d dev addr: %d\n", + (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ', + (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ', + (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8, + (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK)); +} +#else +static inline void dump_di(struct whc *whc, int idx) +{ +} +#endif + +static int whc_update_di(struct whc *whc, int idx) +{ + int offset = idx / 32; + u32 bit = 1 << (idx % 32); + + dump_di(whc, idx); + + le_writel(bit, whc->base + WUSBDIBUPDATED + offset); + + return whci_wait_for(&whc->umc->dev, + whc->base + WUSBDIBUPDATED + offset, bit, 0, + 100, "DI update"); +} + +/* + * WHCI starts and stops MMCs based on there being a valid GTK so + * these need only start/stop the asynchronous and periodic schedules. + */ + +int whc_wusbhc_start(struct wusbhc *wusbhc) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + asl_start(whc); + pzl_start(whc); + + return 0; +} + +void whc_wusbhc_stop(struct wusbhc *wusbhc) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + pzl_stop(whc); + asl_stop(whc); +} + +int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 params; + + params = (interval << 24) + | (repeat_cnt << 16) + | (wuie->bLength << 8) + | handle; + + return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength); +} + +int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 params; + + params = handle; + + return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0); +} + +int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + if (stream_index >= 0) + whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index)); + + return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm)); +} + +int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + int idx = wusb_dev->port_idx; + struct di_buf_entry *di = &whc->di_buf[idx]; + int ret; + + mutex_lock(&whc->mutex); + + uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability); + di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK); + di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr); + + ret = whc_update_di(whc, idx); + + mutex_unlock(&whc->mutex); + + return ret; +} + +/* + * Set the number of Device Notification Time Slots (DNTS) and enable + * device notifications. + */ +int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 dntsctrl; + + dntsctrl = WUSBDNTSCTRL_ACTIVE + | WUSBDNTSCTRL_INTERVAL(interval) + | WUSBDNTSCTRL_SLOTS(slots); + + le_writel(dntsctrl, whc->base + WUSBDNTSCTRL); + + return 0; +} + +static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid, + const void *key, size_t key_size, bool is_gtk) +{ + uint32_t setkeycmd; + uint32_t seckey[4]; + int i; + int ret; + + memcpy(seckey, key, key_size); + setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index); + if (is_gtk) + setkeycmd |= WUSBSETSECKEYCMD_GTK; + + le_writel(tkid, whc->base + WUSBTKID); + for (i = 0; i < 4; i++) + le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i); + le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD); + + ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD, + WUSBSETSECKEYCMD_SET, 0, 100, "set key"); + + return ret; +} + +/** + * whc_set_ptk - set the PTK to use for a device. + * + * The index into the key table for this PTK is the same as the + * device's port index. + */ +int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *ptk, size_t key_size) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + struct di_buf_entry *di = &whc->di_buf[port_idx]; + int ret; + + mutex_lock(&whc->mutex); + + if (ptk) { + ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false); + if (ret) + goto out; + + di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK; + di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx); + } else + di->addr_sec_info &= ~WHC_DI_SECURE; + + ret = whc_update_di(whc, port_idx); +out: + mutex_unlock(&whc->mutex); + return ret; +} + +/** + * whc_set_gtk - set the GTK for subsequent broadcast packets + * + * The GTK is stored in the last entry in the key table (the previous + * N_DEVICES entries are for the per-device PTKs). + */ +int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *gtk, size_t key_size) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + mutex_lock(&whc->mutex); + + ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true); + + mutex_unlock(&whc->mutex); + + return ret; +} + +int whc_set_cluster_id(struct whc *whc, u8 bcid) +{ + whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid)); + return 0; +} -- cgit v1.2.2 From df3654236e31f6cf425ed2ee5a74ceac366a7a9e Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:29 +0100 Subject: wusb: add the Wire Adapter (WA) core Common code for supporting Host Wire Adapters and Device Wire Adapters. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/Makefile | 6 + drivers/usb/wusbcore/wa-hc.c | 95 +++ drivers/usb/wusbcore/wa-hc.h | 417 ++++++++++ drivers/usb/wusbcore/wa-nep.c | 310 +++++++ drivers/usb/wusbcore/wa-rpipe.c | 562 +++++++++++++ drivers/usb/wusbcore/wa-xfer.c | 1709 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 3099 insertions(+) create mode 100644 drivers/usb/wusbcore/wa-hc.c create mode 100644 drivers/usb/wusbcore/wa-hc.h create mode 100644 drivers/usb/wusbcore/wa-nep.c create mode 100644 drivers/usb/wusbcore/wa-rpipe.c create mode 100644 drivers/usb/wusbcore/wa-xfer.c (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile index 6504f42ca367..7a4d00724039 100644 --- a/drivers/usb/wusbcore/Makefile +++ b/drivers/usb/wusbcore/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_USB_WUSB) += wusbcore.o wusb-cbaf.o +obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o wusbcore-objs := \ crypto.o \ @@ -12,3 +13,8 @@ wusbcore-objs := \ wusbhc.o wusb-cbaf-objs := cbaf.o + +wusb-wa-objs := wa-hc.o \ + wa-nep.o \ + wa-rpipe.o \ + wa-xfer.o diff --git a/drivers/usb/wusbcore/wa-hc.c b/drivers/usb/wusbcore/wa-hc.c new file mode 100644 index 000000000000..9d04722415bb --- /dev/null +++ b/drivers/usb/wusbcore/wa-hc.c @@ -0,0 +1,95 @@ +/* + * Wire Adapter Host Controller Driver + * Common items to HWA and DWA based HCDs + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ +#include "wusbhc.h" +#include "wa-hc.h" + +/** + * Assumes + * + * wa->usb_dev and wa->usb_iface initialized and refcounted, + * wa->wa_descr initialized. + */ +int wa_create(struct wahc *wa, struct usb_interface *iface) +{ + int result; + struct device *dev = &iface->dev; + + result = wa_rpipes_create(wa); + if (result < 0) + goto error_rpipes_create; + /* Fill up Data Transfer EP pointers */ + wa->dti_epd = &iface->cur_altsetting->endpoint[1].desc; + wa->dto_epd = &iface->cur_altsetting->endpoint[2].desc; + wa->xfer_result_size = le16_to_cpu(wa->dti_epd->wMaxPacketSize); + wa->xfer_result = kmalloc(wa->xfer_result_size, GFP_KERNEL); + if (wa->xfer_result == NULL) + goto error_xfer_result_alloc; + result = wa_nep_create(wa, iface); + if (result < 0) { + dev_err(dev, "WA-CDS: can't initialize notif endpoint: %d\n", + result); + goto error_nep_create; + } + return 0; + +error_nep_create: + kfree(wa->xfer_result); +error_xfer_result_alloc: + wa_rpipes_destroy(wa); +error_rpipes_create: + return result; +} +EXPORT_SYMBOL_GPL(wa_create); + + +void __wa_destroy(struct wahc *wa) +{ + if (wa->dti_urb) { + usb_kill_urb(wa->dti_urb); + usb_put_urb(wa->dti_urb); + usb_kill_urb(wa->buf_in_urb); + usb_put_urb(wa->buf_in_urb); + } + kfree(wa->xfer_result); + wa_nep_destroy(wa); + wa_rpipes_destroy(wa); +} +EXPORT_SYMBOL_GPL(__wa_destroy); + +/** + * wa_reset_all - reset the WA device + * @wa: the WA to be reset + * + * For HWAs the radio controller and all other PALs are also reset. + */ +void wa_reset_all(struct wahc *wa) +{ + /* FIXME: assuming HWA. */ + wusbhc_reset_all(wa->wusb); +} + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB Wire Adapter core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/wa-hc.h b/drivers/usb/wusbcore/wa-hc.h new file mode 100644 index 000000000000..586d350cdb4d --- /dev/null +++ b/drivers/usb/wusbcore/wa-hc.h @@ -0,0 +1,417 @@ +/* + * HWA Host Controller Driver + * Wire Adapter Control/Data Streaming Iface (WUSB1.0[8]) + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver implements a USB Host Controller (struct usb_hcd) for a + * Wireless USB Host Controller based on the Wireless USB 1.0 + * Host-Wire-Adapter specification (in layman terms, a USB-dongle that + * implements a Wireless USB host). + * + * Check out the Design-overview.txt file in the source documentation + * for other details on the implementation. + * + * Main blocks: + * + * driver glue with the driver API, workqueue daemon + * + * lc RC instance life cycle management (create, destroy...) + * + * hcd glue with the USB API Host Controller Interface API. + * + * nep Notification EndPoint managent: collect notifications + * and queue them with the workqueue daemon. + * + * Handle notifications as coming from the NEP. Sends them + * off others to their respective modules (eg: connect, + * disconnect and reset go to devconnect). + * + * rpipe Remote Pipe management; rpipe is what we use to write + * to an endpoint on a WUSB device that is connected to a + * HWA RC. + * + * xfer Transfer managment -- this is all the code that gets a + * buffer and pushes it to a device (or viceversa). * + * + * Some day a lot of this code will be shared between this driver and + * the drivers for DWA (xfer, rpipe). + * + * All starts at driver.c:hwahc_probe(), when one of this guys is + * connected. hwahc_disconnect() stops it. + * + * During operation, the main driver is devices connecting or + * disconnecting. They cause the HWA RC to send notifications into + * nep.c:hwahc_nep_cb() that will dispatch them to + * notif.c:wa_notif_dispatch(). From there they will fan to cause + * device connects, disconnects, etc. + * + * Note much of the activity is difficult to follow. For example a + * device connect goes to devconnect, which will cause the "fake" root + * hub port to show a connect and stop there. Then khubd will notice + * and call into the rh.c:hwahc_rc_port_reset() code to authenticate + * the device (and this might require user intervention) and enable + * the port. + * + * We also have a timer workqueue going from devconnect.c that + * schedules in hwahc_devconnect_create(). + * + * The rest of the traffic is in the usual entry points of a USB HCD, + * which are hooked up in driver.c:hwahc_rc_driver, and defined in + * hcd.c. + */ + +#ifndef __HWAHC_INTERNAL_H__ +#define __HWAHC_INTERNAL_H__ + +#include +#include +#include +#include +#include +#include +#include + +struct wusbhc; +struct wahc; +extern void wa_urb_enqueue_run(struct work_struct *ws); + +/** + * RPipe instance + * + * @descr's fields are kept in LE, as we need to send it back and + * forth. + * + * @wa is referenced when set + * + * @segs_available is the number of requests segments that still can + * be submitted to the controller without overloading + * it. It is initialized to descr->wRequests when + * aiming. + * + * A rpipe supports a max of descr->wRequests at the same time; before + * submitting seg_lock has to be taken. If segs_avail > 0, then we can + * submit; if not, we have to queue them. + */ +struct wa_rpipe { + struct kref refcnt; + struct usb_rpipe_descriptor descr; + struct usb_host_endpoint *ep; + struct wahc *wa; + spinlock_t seg_lock; + struct list_head seg_list; + atomic_t segs_available; + u8 buffer[1]; /* For reads/writes on USB */ +}; + + +/** + * Instance of a HWA Host Controller + * + * Except where a more specific lock/mutex applies or atomic, all + * fields protected by @mutex. + * + * @wa_descr Can be accessed without locking because it is in + * the same area where the device descriptors were + * read, so it is guaranteed to exist umodified while + * the device exists. + * + * Endianess has been converted to CPU's. + * + * @nep_* can be accessed without locking as its processing is + * serialized; we submit a NEP URB and it comes to + * hwahc_nep_cb(), which won't issue another URB until it is + * done processing it. + * + * @xfer_list: + * + * List of active transfers to verify existence from a xfer id + * gotten from the xfer result message. Can't use urb->list because + * it goes by endpoint, and we don't know the endpoint at the time + * when we get the xfer result message. We can't really rely on the + * pointer (will have to change for 64 bits) as the xfer id is 32 bits. + * + * @xfer_delayed_list: List of transfers that need to be started + * (with a workqueue, because they were + * submitted from an atomic context). + * + * FIXME: this needs to be layered up: a wusbhc layer (for sharing + * comonalities with WHCI), a wa layer (for sharing + * comonalities with DWA-RC). + */ +struct wahc { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + + /* HC to deliver notifications */ + union { + struct wusbhc *wusb; + struct dwahc *dwa; + }; + + const struct usb_endpoint_descriptor *dto_epd, *dti_epd; + const struct usb_wa_descriptor *wa_descr; + + struct urb *nep_urb; /* Notification EndPoint [lockless] */ + struct edc nep_edc; + void *nep_buffer; + size_t nep_buffer_size; + + atomic_t notifs_queued; + + u16 rpipes; + unsigned long *rpipe_bm; /* rpipe usage bitmap */ + spinlock_t rpipe_bm_lock; /* protect rpipe_bm */ + struct mutex rpipe_mutex; /* assigning resources to endpoints */ + + struct urb *dti_urb; /* URB for reading xfer results */ + struct urb *buf_in_urb; /* URB for reading data in */ + struct edc dti_edc; /* DTI error density counter */ + struct wa_xfer_result *xfer_result; /* real size = dti_ep maxpktsize */ + size_t xfer_result_size; + + s32 status; /* For reading status */ + + struct list_head xfer_list; + struct list_head xfer_delayed_list; + spinlock_t xfer_list_lock; + struct work_struct xfer_work; + atomic_t xfer_id_count; +}; + + +extern int wa_create(struct wahc *wa, struct usb_interface *iface); +extern void __wa_destroy(struct wahc *wa); +void wa_reset_all(struct wahc *wa); + + +/* Miscellaneous constants */ +enum { + /** Max number of EPROTO errors we tolerate on the NEP in a + * period of time */ + HWAHC_EPROTO_MAX = 16, + /** Period of time for EPROTO errors (in jiffies) */ + HWAHC_EPROTO_PERIOD = 4 * HZ, +}; + + +/* Notification endpoint handling */ +extern int wa_nep_create(struct wahc *, struct usb_interface *); +extern void wa_nep_destroy(struct wahc *); + +static inline int wa_nep_arm(struct wahc *wa, gfp_t gfp_mask) +{ + struct urb *urb = wa->nep_urb; + urb->transfer_buffer = wa->nep_buffer; + urb->transfer_buffer_length = wa->nep_buffer_size; + return usb_submit_urb(urb, gfp_mask); +} + +static inline void wa_nep_disarm(struct wahc *wa) +{ + usb_kill_urb(wa->nep_urb); +} + + +/* RPipes */ +static inline void wa_rpipe_init(struct wahc *wa) +{ + spin_lock_init(&wa->rpipe_bm_lock); + mutex_init(&wa->rpipe_mutex); +} + +static inline void wa_init(struct wahc *wa) +{ + edc_init(&wa->nep_edc); + atomic_set(&wa->notifs_queued, 0); + wa_rpipe_init(wa); + edc_init(&wa->dti_edc); + INIT_LIST_HEAD(&wa->xfer_list); + INIT_LIST_HEAD(&wa->xfer_delayed_list); + spin_lock_init(&wa->xfer_list_lock); + INIT_WORK(&wa->xfer_work, wa_urb_enqueue_run); + atomic_set(&wa->xfer_id_count, 1); +} + +/** + * Destroy a pipe (when refcount drops to zero) + * + * Assumes it has been moved to the "QUIESCING" state. + */ +struct wa_xfer; +extern void rpipe_destroy(struct kref *_rpipe); +static inline +void __rpipe_get(struct wa_rpipe *rpipe) +{ + kref_get(&rpipe->refcnt); +} +extern int rpipe_get_by_ep(struct wahc *, struct usb_host_endpoint *, + struct urb *, gfp_t); +static inline void rpipe_put(struct wa_rpipe *rpipe) +{ + kref_put(&rpipe->refcnt, rpipe_destroy); + +} +extern void rpipe_ep_disable(struct wahc *, struct usb_host_endpoint *); +extern int wa_rpipes_create(struct wahc *); +extern void wa_rpipes_destroy(struct wahc *); +static inline void rpipe_avail_dec(struct wa_rpipe *rpipe) +{ + atomic_dec(&rpipe->segs_available); +} + +/** + * Returns true if the rpipe is ready to submit more segments. + */ +static inline int rpipe_avail_inc(struct wa_rpipe *rpipe) +{ + return atomic_inc_return(&rpipe->segs_available) > 0 + && !list_empty(&rpipe->seg_list); +} + + +/* Transferring data */ +extern int wa_urb_enqueue(struct wahc *, struct usb_host_endpoint *, + struct urb *, gfp_t); +extern int wa_urb_dequeue(struct wahc *, struct urb *); +extern void wa_handle_notif_xfer(struct wahc *, struct wa_notif_hdr *); + + +/* Misc + * + * FIXME: Refcounting for the actual @hwahc object is not correct; I + * mean, this should be refcounting on the HCD underneath, but + * it is not. In any case, the semantics for HCD refcounting + * are *weird*...on refcount reaching zero it just frees + * it...no RC specific function is called...unless I miss + * something. + * + * FIXME: has to go away in favour of an 'struct' hcd based sollution + */ +static inline struct wahc *wa_get(struct wahc *wa) +{ + usb_get_intf(wa->usb_iface); + return wa; +} + +static inline void wa_put(struct wahc *wa) +{ + usb_put_intf(wa->usb_iface); +} + + +static inline int __wa_feature(struct wahc *wa, unsigned op, u16 feature) +{ + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + op ? USB_REQ_SET_FEATURE : USB_REQ_CLEAR_FEATURE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + feature, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + + +static inline int __wa_set_feature(struct wahc *wa, u16 feature) +{ + return __wa_feature(wa, 1, feature); +} + + +static inline int __wa_clear_feature(struct wahc *wa, u16 feature) +{ + return __wa_feature(wa, 0, feature); +} + + +/** + * Return the status of a Wire Adapter + * + * @wa: Wire Adapter instance + * @returns < 0 errno code on error, or status bitmap as described + * in WUSB1.0[8.3.1.6]. + * + * NOTE: need malloc, some arches don't take USB from the stack + */ +static inline +s32 __wa_get_status(struct wahc *wa) +{ + s32 result; + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_GET_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + &wa->status, sizeof(wa->status), + 1000 /* FIXME: arbitrary */); + if (result >= 0) + result = wa->status; + return result; +} + + +/** + * Waits until the Wire Adapter's status matches @mask/@value + * + * @wa: Wire Adapter instance. + * @returns < 0 errno code on error, otherwise status. + * + * Loop until the WAs status matches the mask and value (status & mask + * == value). Timeout if it doesn't happen. + * + * FIXME: is there an official specification on how long status + * changes can take? + */ +static inline s32 __wa_wait_status(struct wahc *wa, u32 mask, u32 value) +{ + s32 result; + unsigned loops = 10; + do { + msleep(50); + result = __wa_get_status(wa); + if ((result & mask) == value) + break; + if (loops-- == 0) { + result = -ETIMEDOUT; + break; + } + } while (result >= 0); + return result; +} + + +/** Command @hwahc to stop, @returns 0 if ok, < 0 errno code on error */ +static inline int __wa_stop(struct wahc *wa) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + + result = __wa_clear_feature(wa, WA_ENABLE); + if (result < 0 && result != -ENODEV) { + dev_err(dev, "error commanding HC to stop: %d\n", result); + goto out; + } + result = __wa_wait_status(wa, WA_ENABLE, 0); + if (result < 0 && result != -ENODEV) + dev_err(dev, "error waiting for HC to stop: %d\n", result); +out: + return 0; +} + + +#endif /* #ifndef __HWAHC_INTERNAL_H__ */ diff --git a/drivers/usb/wusbcore/wa-nep.c b/drivers/usb/wusbcore/wa-nep.c new file mode 100644 index 000000000000..3f542990c73f --- /dev/null +++ b/drivers/usb/wusbcore/wa-nep.c @@ -0,0 +1,310 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * Notification EndPoint support + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * 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 +#include +#include +#include "wa-hc.h" +#include "wusbhc.h" + +/* Structure for queueing notifications to the workqueue */ +struct wa_notif_work { + struct work_struct work; + struct wahc *wa; + size_t size; + u8 data[]; +}; + +/* + * Process incoming notifications from the WA's Notification EndPoint + * [the wuswad daemon, basically] + * + * @_nw: Pointer to a descriptor which has the pointer to the + * @wa, the size of the buffer and the work queue + * structure (so we can free all when done). + * @returns 0 if ok, < 0 errno code on error. + * + * All notifications follow the same format; they need to start with a + * 'struct wa_notif_hdr' header, so it is easy to parse through + * them. We just break the buffer in individual notifications (the + * standard doesn't say if it can be done or is forbidden, so we are + * cautious) and dispatch each. + * + * So the handling layers are is: + * + * WA specific notification (from NEP) + * Device Notification Received -> wa_handle_notif_dn() + * WUSB Device notification generic handling + * BPST Adjustment -> wa_handle_notif_bpst_adj() + * ... -> ... + * + * @wa has to be referenced + */ +static void wa_notif_dispatch(struct work_struct *ws) +{ + void *itr; + u8 missing = 0; + struct wa_notif_work *nw = container_of(ws, struct wa_notif_work, work); + struct wahc *wa = nw->wa; + struct wa_notif_hdr *notif_hdr; + size_t size; + + struct device *dev = &wa->usb_iface->dev; + +#if 0 + /* FIXME: need to check for this??? */ + if (usb_hcd->state == HC_STATE_QUIESCING) /* Going down? */ + goto out; /* screw it */ +#endif + atomic_dec(&wa->notifs_queued); /* Throttling ctl */ + dev = &wa->usb_iface->dev; + size = nw->size; + itr = nw->data; + + while (size) { + if (size < sizeof(*notif_hdr)) { + missing = sizeof(*notif_hdr) - size; + goto exhausted_buffer; + } + notif_hdr = itr; + if (size < notif_hdr->bLength) + goto exhausted_buffer; + itr += notif_hdr->bLength; + size -= notif_hdr->bLength; + /* Dispatch the notification [don't use itr or size!] */ + switch (notif_hdr->bNotifyType) { + case HWA_NOTIF_DN: { + struct hwa_notif_dn *hwa_dn; + hwa_dn = container_of(notif_hdr, struct hwa_notif_dn, + hdr); + wusbhc_handle_dn(wa->wusb, hwa_dn->bSourceDeviceAddr, + hwa_dn->dndata, + notif_hdr->bLength - sizeof(*hwa_dn)); + break; + } + case WA_NOTIF_TRANSFER: + wa_handle_notif_xfer(wa, notif_hdr); + break; + case DWA_NOTIF_RWAKE: + case DWA_NOTIF_PORTSTATUS: + case HWA_NOTIF_BPST_ADJ: + /* FIXME: unimplemented WA NOTIFs */ + /* fallthru */ + default: + if (printk_ratelimit()) { + dev_err(dev, "HWA: unknown notification 0x%x, " + "%zu bytes; discarding\n", + notif_hdr->bNotifyType, + (size_t)notif_hdr->bLength); + dump_bytes(dev, notif_hdr, 16); + } + break; + } + } +out: + wa_put(wa); + kfree(nw); + return; + + /* THIS SHOULD NOT HAPPEN + * + * Buffer exahusted with partial data remaining; just warn and + * discard the data, as this should not happen. + */ +exhausted_buffer: + if (!printk_ratelimit()) + goto out; + dev_warn(dev, "HWA: device sent short notification, " + "%d bytes missing; discarding %d bytes.\n", + missing, (int)size); + dump_bytes(dev, itr, size); + goto out; +} + +/* + * Deliver incoming WA notifications to the wusbwa workqueue + * + * @wa: Pointer the Wire Adapter Controller Data Streaming + * instance (part of an 'struct usb_hcd'). + * @size: Size of the received buffer + * @returns 0 if ok, < 0 errno code on error. + * + * The input buffer is @wa->nep_buffer, with @size bytes + * (guaranteed to fit in the allocated space, + * @wa->nep_buffer_size). + */ +static int wa_nep_queue(struct wahc *wa, size_t size) +{ + int result = 0; + struct device *dev = &wa->usb_iface->dev; + struct wa_notif_work *nw; + + /* dev_fnstart(dev, "(wa %p, size %zu)\n", wa, size); */ + BUG_ON(size > wa->nep_buffer_size); + if (size == 0) + goto out; + if (atomic_read(&wa->notifs_queued) > 200) { + if (printk_ratelimit()) + dev_err(dev, "Too many notifications queued, " + "throttling back\n"); + goto out; + } + nw = kzalloc(sizeof(*nw) + size, GFP_ATOMIC); + if (nw == NULL) { + if (printk_ratelimit()) + dev_err(dev, "No memory to queue notification\n"); + goto out; + } + INIT_WORK(&nw->work, wa_notif_dispatch); + nw->wa = wa_get(wa); + nw->size = size; + memcpy(nw->data, wa->nep_buffer, size); + atomic_inc(&wa->notifs_queued); /* Throttling ctl */ + queue_work(wusbd, &nw->work); +out: + /* dev_fnend(dev, "(wa %p, size %zu) = result\n", wa, size, result); */ + return result; +} + +/* + * Callback for the notification event endpoint + * + * Check's that everything is fine and then passes the data to be + * queued to the workqueue. + */ +static void wa_nep_cb(struct urb *urb) +{ + int result; + struct wahc *wa = urb->context; + struct device *dev = &wa->usb_iface->dev; + + switch (result = urb->status) { + case 0: + result = wa_nep_queue(wa, urb->actual_length); + if (result < 0) + dev_err(dev, "NEP: unable to process notification(s): " + "%d\n", result); + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: + dev_dbg(dev, "NEP: going down %d\n", urb->status); + goto out; + default: /* On general errors, we retry unless it gets ugly */ + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "NEP: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + goto out; + } + dev_err(dev, "NEP: URB error %d\n", urb->status); + } + result = wa_nep_arm(wa, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "NEP: cannot submit URB: %d\n", result); + wa_reset_all(wa); + } +out: + return; +} + +/* + * Initialize @wa's notification and event's endpoint stuff + * + * This includes the allocating the read buffer, the context ID + * allocation bitmap, the URB and submitting the URB. + */ +int wa_nep_create(struct wahc *wa, struct usb_interface *iface) +{ + int result; + struct usb_endpoint_descriptor *epd; + struct usb_device *usb_dev = interface_to_usbdev(iface); + struct device *dev = &iface->dev; + + edc_init(&wa->nep_edc); + epd = &iface->cur_altsetting->endpoint[0].desc; + wa->nep_buffer_size = 1024; + wa->nep_buffer = kmalloc(wa->nep_buffer_size, GFP_KERNEL); + if (wa->nep_buffer == NULL) { + dev_err(dev, "Unable to allocate notification's read buffer\n"); + goto error_nep_buffer; + } + wa->nep_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->nep_urb == NULL) { + dev_err(dev, "Unable to allocate notification URB\n"); + goto error_urb_alloc; + } + usb_fill_int_urb(wa->nep_urb, usb_dev, + usb_rcvintpipe(usb_dev, epd->bEndpointAddress), + wa->nep_buffer, wa->nep_buffer_size, + wa_nep_cb, wa, epd->bInterval); + result = wa_nep_arm(wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "Cannot submit notification URB: %d\n", result); + goto error_nep_arm; + } + return 0; + +error_nep_arm: + usb_free_urb(wa->nep_urb); +error_urb_alloc: + kfree(wa->nep_buffer); +error_nep_buffer: + return -ENOMEM; +} + +void wa_nep_destroy(struct wahc *wa) +{ + wa_nep_disarm(wa); + usb_free_urb(wa->nep_urb); + kfree(wa->nep_buffer); +} diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c new file mode 100644 index 000000000000..bfe3752e7c9a --- /dev/null +++ b/drivers/usb/wusbcore/wa-rpipe.c @@ -0,0 +1,562 @@ +/* + * WUSB Wire Adapter + * rpipe management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * RPIPE + * + * Targetted at different downstream endpoints + * + * Descriptor: use to config the remote pipe. + * + * The number of blocks could be dynamic (wBlocks in descriptor is + * 0)--need to schedule them then. + * + * Each bit in wa->rpipe_bm represents if an rpipe is being used or + * not. Rpipes are represented with a 'struct wa_rpipe' that is + * attached to the hcpriv member of a 'struct usb_host_endpoint'. + * + * When you need to xfer data to an endpoint, you get an rpipe for it + * with wa_ep_rpipe_get(), which gives you a reference to the rpipe + * and keeps a single one (the first one) with the endpoint. When you + * are done transferring, you drop that reference. At the end the + * rpipe is always allocated and bound to the endpoint. There it might + * be recycled when not used. + * + * Addresses: + * + * We use a 1:1 mapping mechanism between port address (0 based + * index, actually) and the address. The USB stack knows about this. + * + * USB Stack port number 4 (1 based) + * WUSB code port index 3 (0 based) + * USB Addresss 5 (2 based -- 0 is for default, 1 for root hub) + * + * Now, because we don't use the concept as default address exactly + * like the (wired) USB code does, we need to kind of skip it. So we + * never take addresses from the urb->pipe, but from the + * urb->dev->devnum, to make sure that we always have the right + * destination address. + */ +#include +#include +#include +#include "wusbhc.h" +#include "wa-hc.h" + +#define D_LOCAL 0 +#include + + +static int __rpipe_get_descr(struct wahc *wa, + struct usb_rpipe_descriptor *descr, u16 index) +{ + ssize_t result; + struct device *dev = &wa->usb_iface->dev; + + /* Get the RPIPE descriptor -- we cannot use the usb_get_descriptor() + * function because the arguments are different. + */ + d_printf(1, dev, "rpipe %u: get descr\n", index); + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DT_RPIPE<<8, index, descr, sizeof(*descr), + 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "rpipe %u: get descriptor failed: %d\n", + index, (int)result); + goto error; + } + if (result < sizeof(*descr)) { + dev_err(dev, "rpipe %u: got short descriptor " + "(%zd vs %zd bytes needed)\n", + index, result, sizeof(*descr)); + result = -EINVAL; + goto error; + } + result = 0; + +error: + return result; +} + +/* + * + * The descriptor is assumed to be properly initialized (ie: you got + * it through __rpipe_get_descr()). + */ +static int __rpipe_set_descr(struct wahc *wa, + struct usb_rpipe_descriptor *descr, u16 index) +{ + ssize_t result; + struct device *dev = &wa->usb_iface->dev; + + /* we cannot use the usb_get_descriptor() function because the + * arguments are different. + */ + d_printf(1, dev, "rpipe %u: set descr\n", index); + result = usb_control_msg( + wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DT_RPIPE<<8, index, descr, sizeof(*descr), + HZ / 10); + if (result < 0) { + dev_err(dev, "rpipe %u: set descriptor failed: %d\n", + index, (int)result); + goto error; + } + if (result < sizeof(*descr)) { + dev_err(dev, "rpipe %u: sent short descriptor " + "(%zd vs %zd bytes required)\n", + index, result, sizeof(*descr)); + result = -EINVAL; + goto error; + } + result = 0; + +error: + return result; + +} + +static void rpipe_init(struct wa_rpipe *rpipe) +{ + kref_init(&rpipe->refcnt); + spin_lock_init(&rpipe->seg_lock); + INIT_LIST_HEAD(&rpipe->seg_list); +} + +static unsigned rpipe_get_idx(struct wahc *wa, unsigned rpipe_idx) +{ + unsigned long flags; + + spin_lock_irqsave(&wa->rpipe_bm_lock, flags); + rpipe_idx = find_next_zero_bit(wa->rpipe_bm, wa->rpipes, rpipe_idx); + if (rpipe_idx < wa->rpipes) + set_bit(rpipe_idx, wa->rpipe_bm); + spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags); + + return rpipe_idx; +} + +static void rpipe_put_idx(struct wahc *wa, unsigned rpipe_idx) +{ + unsigned long flags; + + spin_lock_irqsave(&wa->rpipe_bm_lock, flags); + clear_bit(rpipe_idx, wa->rpipe_bm); + spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags); +} + +void rpipe_destroy(struct kref *_rpipe) +{ + struct wa_rpipe *rpipe = container_of(_rpipe, struct wa_rpipe, refcnt); + u8 index = le16_to_cpu(rpipe->descr.wRPipeIndex); + d_fnstart(1, NULL, "(rpipe %p %u)\n", rpipe, index); + if (rpipe->ep) + rpipe->ep->hcpriv = NULL; + rpipe_put_idx(rpipe->wa, index); + wa_put(rpipe->wa); + kfree(rpipe); + d_fnend(1, NULL, "(rpipe %p %u)\n", rpipe, index); +} +EXPORT_SYMBOL_GPL(rpipe_destroy); + +/* + * Locate an idle rpipe, create an structure for it and return it + * + * @wa is referenced and unlocked + * @crs enum rpipe_attr, required endpoint characteristics + * + * The rpipe can be used only sequentially (not in parallel). + * + * The rpipe is moved into the "ready" state. + */ +static int rpipe_get_idle(struct wa_rpipe **prpipe, struct wahc *wa, u8 crs, + gfp_t gfp) +{ + int result; + unsigned rpipe_idx; + struct wa_rpipe *rpipe; + struct device *dev = &wa->usb_iface->dev; + + d_fnstart(3, dev, "(wa %p crs 0x%02x)\n", wa, crs); + rpipe = kzalloc(sizeof(*rpipe), gfp); + if (rpipe == NULL) + return -ENOMEM; + rpipe_init(rpipe); + + /* Look for an idle pipe */ + for (rpipe_idx = 0; rpipe_idx < wa->rpipes; rpipe_idx++) { + rpipe_idx = rpipe_get_idx(wa, rpipe_idx); + if (rpipe_idx >= wa->rpipes) /* no more pipes :( */ + break; + result = __rpipe_get_descr(wa, &rpipe->descr, rpipe_idx); + if (result < 0) + dev_err(dev, "Can't get descriptor for rpipe %u: %d\n", + rpipe_idx, result); + else if ((rpipe->descr.bmCharacteristics & crs) != 0) + goto found; + rpipe_put_idx(wa, rpipe_idx); + } + *prpipe = NULL; + kfree(rpipe); + d_fnend(3, dev, "(wa %p crs 0x%02x) = -ENXIO\n", wa, crs); + return -ENXIO; + +found: + set_bit(rpipe_idx, wa->rpipe_bm); + rpipe->wa = wa_get(wa); + *prpipe = rpipe; + d_fnstart(3, dev, "(wa %p crs 0x%02x) = 0\n", wa, crs); + return 0; +} + +static int __rpipe_reset(struct wahc *wa, unsigned index) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + + d_printf(1, dev, "rpipe %u: reset\n", index); + result = usb_control_msg( + wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_RPIPE_RESET, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "rpipe %u: reset failed: %d\n", + index, result); + return result; +} + +/* + * Fake companion descriptor for ep0 + * + * See WUSB1.0[7.4.4], most of this is zero for bulk/int/ctl + */ +static struct usb_wireless_ep_comp_descriptor epc0 = { + .bLength = sizeof(epc0), + .bDescriptorType = USB_DT_WIRELESS_ENDPOINT_COMP, +/* .bMaxBurst = 1, */ + .bMaxSequence = 31, +}; + +/* + * Look for EP companion descriptor + * + * Get there, look for Inara in the endpoint's extra descriptors + */ +static struct usb_wireless_ep_comp_descriptor *rpipe_epc_find( + struct device *dev, struct usb_host_endpoint *ep) +{ + void *itr; + size_t itr_size; + struct usb_descriptor_header *hdr; + struct usb_wireless_ep_comp_descriptor *epcd; + + d_fnstart(3, dev, "(ep %p)\n", ep); + if (ep->desc.bEndpointAddress == 0) { + epcd = &epc0; + goto out; + } + itr = ep->extra; + itr_size = ep->extralen; + epcd = NULL; + while (itr_size > 0) { + if (itr_size < sizeof(*hdr)) { + dev_err(dev, "HW Bug? ep 0x%02x: extra descriptors " + "at offset %zu: only %zu bytes left\n", + ep->desc.bEndpointAddress, + itr - (void *) ep->extra, itr_size); + break; + } + hdr = itr; + if (hdr->bDescriptorType == USB_DT_WIRELESS_ENDPOINT_COMP) { + epcd = itr; + break; + } + if (hdr->bLength > itr_size) { + dev_err(dev, "HW Bug? ep 0x%02x: extra descriptor " + "at offset %zu (type 0x%02x) " + "length %d but only %zu bytes left\n", + ep->desc.bEndpointAddress, + itr - (void *) ep->extra, hdr->bDescriptorType, + hdr->bLength, itr_size); + break; + } + itr += hdr->bLength; + itr_size -= hdr->bDescriptorType; + } +out: + d_fnend(3, dev, "(ep %p) = %p\n", ep, epcd); + return epcd; +} + +/* + * Aim an rpipe to its device & endpoint destination + * + * Make sure we change the address to unauthenticathed if the device + * is WUSB and it is not authenticated. + */ +static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa, + struct usb_host_endpoint *ep, struct urb *urb, gfp_t gfp) +{ + int result = -ENOMSG; /* better code for lack of companion? */ + struct device *dev = &wa->usb_iface->dev; + struct usb_device *usb_dev = urb->dev; + struct usb_wireless_ep_comp_descriptor *epcd; + u8 unauth; + + d_fnstart(3, dev, "(rpipe %p wa %p ep %p, urb %p)\n", + rpipe, wa, ep, urb); + epcd = rpipe_epc_find(dev, ep); + if (epcd == NULL) { + dev_err(dev, "ep 0x%02x: can't find companion descriptor\n", + ep->desc.bEndpointAddress); + goto error; + } + unauth = usb_dev->wusb && !usb_dev->authenticated ? 0x80 : 0; + __rpipe_reset(wa, le16_to_cpu(rpipe->descr.wRPipeIndex)); + atomic_set(&rpipe->segs_available, le16_to_cpu(rpipe->descr.wRequests)); + /* FIXME: block allocation system; request with queuing and timeout */ + /* FIXME: compute so seg_size > ep->maxpktsize */ + rpipe->descr.wBlocks = cpu_to_le16(16); /* given */ + /* ep0 maxpktsize is 0x200 (WUSB1.0[4.8.1]) */ + rpipe->descr.wMaxPacketSize = cpu_to_le16(ep->desc.wMaxPacketSize); + rpipe->descr.bHSHubAddress = 0; /* reserved: zero */ + rpipe->descr.bHSHubPort = wusb_port_no_to_idx(urb->dev->portnum); + /* FIXME: use maximum speed as supported or recommended by device */ + rpipe->descr.bSpeed = usb_pipeendpoint(urb->pipe) == 0 ? + UWB_PHY_RATE_53 : UWB_PHY_RATE_200; + d_printf(2, dev, "addr %u (0x%02x) rpipe #%u ep# %u speed %d\n", + urb->dev->devnum, urb->dev->devnum | unauth, + le16_to_cpu(rpipe->descr.wRPipeIndex), + usb_pipeendpoint(urb->pipe), rpipe->descr.bSpeed); + /* see security.c:wusb_update_address() */ + if (unlikely(urb->dev->devnum == 0x80)) + rpipe->descr.bDeviceAddress = 0; + else + rpipe->descr.bDeviceAddress = urb->dev->devnum | unauth; + rpipe->descr.bEndpointAddress = ep->desc.bEndpointAddress; + /* FIXME: bDataSequence */ + rpipe->descr.bDataSequence = 0; + /* FIXME: dwCurrentWindow */ + rpipe->descr.dwCurrentWindow = cpu_to_le32(1); + /* FIXME: bMaxDataSequence */ + rpipe->descr.bMaxDataSequence = epcd->bMaxSequence - 1; + rpipe->descr.bInterval = ep->desc.bInterval; + /* FIXME: bOverTheAirInterval */ + rpipe->descr.bOverTheAirInterval = 0; /* 0 if not isoc */ + /* FIXME: xmit power & preamble blah blah */ + rpipe->descr.bmAttribute = ep->desc.bmAttributes & 0x03; + /* rpipe->descr.bmCharacteristics RO */ + /* FIXME: bmRetryOptions */ + rpipe->descr.bmRetryOptions = 15; + /* FIXME: use for assessing link quality? */ + rpipe->descr.wNumTransactionErrors = 0; + result = __rpipe_set_descr(wa, &rpipe->descr, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + if (result < 0) { + dev_err(dev, "Cannot aim rpipe: %d\n", result); + goto error; + } + result = 0; +error: + d_fnend(3, dev, "(rpipe %p wa %p ep %p urb %p) = %d\n", + rpipe, wa, ep, urb, result); + return result; +} + +/* + * Check an aimed rpipe to make sure it points to where we want + * + * We use bit 19 of the Linux USB pipe bitmap for unauth vs auth + * space; when it is like that, we or 0x80 to make an unauth address. + */ +static int rpipe_check_aim(const struct wa_rpipe *rpipe, const struct wahc *wa, + const struct usb_host_endpoint *ep, + const struct urb *urb, gfp_t gfp) +{ + int result = 0; /* better code for lack of companion? */ + struct device *dev = &wa->usb_iface->dev; + struct usb_device *usb_dev = urb->dev; + u8 unauth = (usb_dev->wusb && !usb_dev->authenticated) ? 0x80 : 0; + u8 portnum = wusb_port_no_to_idx(urb->dev->portnum); + + d_fnstart(3, dev, "(rpipe %p wa %p ep %p, urb %p)\n", + rpipe, wa, ep, urb); +#define AIM_CHECK(rdf, val, text) \ + do { \ + if (rpipe->descr.rdf != (val)) { \ + dev_err(dev, \ + "rpipe aim discrepancy: " #rdf " " text "\n", \ + rpipe->descr.rdf, (val)); \ + result = -EINVAL; \ + WARN_ON(1); \ + } \ + } while (0) + AIM_CHECK(wMaxPacketSize, cpu_to_le16(ep->desc.wMaxPacketSize), + "(%u vs %u)"); + AIM_CHECK(bHSHubPort, portnum, "(%u vs %u)"); + AIM_CHECK(bSpeed, usb_pipeendpoint(urb->pipe) == 0 ? + UWB_PHY_RATE_53 : UWB_PHY_RATE_200, + "(%u vs %u)"); + AIM_CHECK(bDeviceAddress, urb->dev->devnum | unauth, "(%u vs %u)"); + AIM_CHECK(bEndpointAddress, ep->desc.bEndpointAddress, "(%u vs %u)"); + AIM_CHECK(bInterval, ep->desc.bInterval, "(%u vs %u)"); + AIM_CHECK(bmAttribute, ep->desc.bmAttributes & 0x03, "(%u vs %u)"); +#undef AIM_CHECK + return result; +} + +#ifndef CONFIG_BUG +#define CONFIG_BUG 0 +#endif + +/* + * Make sure there is an rpipe allocated for an endpoint + * + * If already allocated, we just refcount it; if not, we get an + * idle one, aim it to the right location and take it. + * + * Attaches to ep->hcpriv and rpipe->ep to ep. + */ +int rpipe_get_by_ep(struct wahc *wa, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t gfp) +{ + int result = 0; + struct device *dev = &wa->usb_iface->dev; + struct wa_rpipe *rpipe; + u8 eptype; + + d_fnstart(3, dev, "(wa %p ep %p urb %p gfp 0x%08x)\n", wa, ep, urb, + gfp); + mutex_lock(&wa->rpipe_mutex); + rpipe = ep->hcpriv; + if (rpipe != NULL) { + if (CONFIG_BUG == 1) { + result = rpipe_check_aim(rpipe, wa, ep, urb, gfp); + if (result < 0) + goto error; + } + __rpipe_get(rpipe); + d_printf(2, dev, "ep 0x%02x: reusing rpipe %u\n", + ep->desc.bEndpointAddress, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + } else { + /* hmm, assign idle rpipe, aim it */ + result = -ENOBUFS; + eptype = ep->desc.bmAttributes & 0x03; + result = rpipe_get_idle(&rpipe, wa, 1 << eptype, gfp); + if (result < 0) + goto error; + result = rpipe_aim(rpipe, wa, ep, urb, gfp); + if (result < 0) { + rpipe_put(rpipe); + goto error; + } + ep->hcpriv = rpipe; + rpipe->ep = ep; + __rpipe_get(rpipe); /* for caching into ep->hcpriv */ + d_printf(2, dev, "ep 0x%02x: using rpipe %u\n", + ep->desc.bEndpointAddress, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + } + d_dump(4, dev, &rpipe->descr, sizeof(rpipe->descr)); +error: + mutex_unlock(&wa->rpipe_mutex); + d_fnend(3, dev, "(wa %p ep %p urb %p gfp 0x%08x)\n", wa, ep, urb, gfp); + return result; +} + +/* + * Allocate the bitmap for each rpipe. + */ +int wa_rpipes_create(struct wahc *wa) +{ + wa->rpipes = wa->wa_descr->wNumRPipes; + wa->rpipe_bm = kzalloc(BITS_TO_LONGS(wa->rpipes)*sizeof(unsigned long), + GFP_KERNEL); + if (wa->rpipe_bm == NULL) + return -ENOMEM; + return 0; +} + +void wa_rpipes_destroy(struct wahc *wa) +{ + struct device *dev = &wa->usb_iface->dev; + d_fnstart(3, dev, "(wa %p)\n", wa); + if (!bitmap_empty(wa->rpipe_bm, wa->rpipes)) { + char buf[256]; + WARN_ON(1); + bitmap_scnprintf(buf, sizeof(buf), wa->rpipe_bm, wa->rpipes); + dev_err(dev, "BUG: pipes not released on exit: %s\n", buf); + } + kfree(wa->rpipe_bm); + d_fnend(3, dev, "(wa %p)\n", wa); +} + +/* + * Release resources allocated for an endpoint + * + * If there is an associated rpipe to this endpoint, Abort any pending + * transfers and put it. If the rpipe ends up being destroyed, + * __rpipe_destroy() will cleanup ep->hcpriv. + * + * This is called before calling hcd->stop(), so you don't need to do + * anything else in there. + */ +void rpipe_ep_disable(struct wahc *wa, struct usb_host_endpoint *ep) +{ + struct device *dev = &wa->usb_iface->dev; + struct wa_rpipe *rpipe; + d_fnstart(2, dev, "(wa %p ep %p)\n", wa, ep); + mutex_lock(&wa->rpipe_mutex); + rpipe = ep->hcpriv; + if (rpipe != NULL) { + unsigned rc = atomic_read(&rpipe->refcnt.refcount); + int result; + u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex); + + if (rc != 1) + d_printf(1, dev, "(wa %p ep %p) rpipe %p refcnt %u\n", + wa, ep, rpipe, rc); + + d_printf(1, dev, "rpipe %u: abort\n", index); + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_RPIPE_ABORT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE, + 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0 && result != -ENODEV /* dev is gone */) + d_printf(1, dev, "(wa %p rpipe %u): abort failed: %d\n", + wa, index, result); + rpipe_put(rpipe); + } + mutex_unlock(&wa->rpipe_mutex); + d_fnend(2, dev, "(wa %p ep %p)\n", wa, ep); + return; +} +EXPORT_SYMBOL_GPL(rpipe_ep_disable); diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c new file mode 100644 index 000000000000..7d192f3e6742 --- /dev/null +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -0,0 +1,1709 @@ +/* + * WUSB Wire Adapter + * Data transfer and URB enqueing + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * 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. + * + * + * How transfers work: get a buffer, break it up in segments (segment + * size is a multiple of the maxpacket size). For each segment issue a + * segment request (struct wa_xfer_*), then send the data buffer if + * out or nothing if in (all over the DTO endpoint). + * + * For each submitted segment request, a notification will come over + * the NEP endpoint and a transfer result (struct xfer_result) will + * arrive in the DTI URB. Read it, get the xfer ID, see if there is + * data coming (inbound transfer), schedule a read and handle it. + * + * Sounds simple, it is a pain to implement. + * + * + * ENTRY POINTS + * + * FIXME + * + * LIFE CYCLE / STATE DIAGRAM + * + * FIXME + * + * THIS CODE IS DISGUSTING + * + * Warned you are; it's my second try and still not happy with it. + * + * NOTES: + * + * - No iso + * + * - Supports DMA xfers, control, bulk and maybe interrupt + * + * - Does not recycle unused rpipes + * + * An rpipe is assigned to an endpoint the first time it is used, + * and then it's there, assigned, until the endpoint is disabled + * (destroyed [{h,d}wahc_op_ep_disable()]. The assignment of the + * rpipe to the endpoint is done under the wa->rpipe_sem semaphore + * (should be a mutex). + * + * Two methods it could be done: + * + * (a) set up a timer everytime an rpipe's use count drops to 1 + * (which means unused) or when a transfer ends. Reset the + * timer when a xfer is queued. If the timer expires, release + * the rpipe [see rpipe_ep_disable()]. + * + * (b) when looking for free rpipes to attach [rpipe_get_by_ep()], + * when none are found go over the list, check their endpoint + * and their activity record (if no last-xfer-done-ts in the + * last x seconds) take it + * + * However, due to the fact that we have a set of limited + * resources (max-segments-at-the-same-time per xfer, + * xfers-per-ripe, blocks-per-rpipe, rpipes-per-host), at the end + * we are going to have to rebuild all this based on an scheduler, + * to where we have a list of transactions to do and based on the + * availability of the different requried components (blocks, + * rpipes, segment slots, etc), we go scheduling them. Painful. + */ +#include +#include +#include +#include "wa-hc.h" +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 0 /* 0 disabled, > 0 different levels... */ +#include + +enum { + WA_SEGS_MAX = 255, +}; + +enum wa_seg_status { + WA_SEG_NOTREADY, + WA_SEG_READY, + WA_SEG_DELAYED, + WA_SEG_SUBMITTED, + WA_SEG_PENDING, + WA_SEG_DTI_PENDING, + WA_SEG_DONE, + WA_SEG_ERROR, + WA_SEG_ABORTED, +}; + +static void wa_xfer_delayed_run(struct wa_rpipe *); + +/* + * Life cycle governed by 'struct urb' (the refcount of the struct is + * that of the 'struct urb' and usb_free_urb() would free the whole + * struct). + */ +struct wa_seg { + struct urb urb; + struct urb *dto_urb; /* for data output? */ + struct list_head list_node; /* for rpipe->req_list */ + struct wa_xfer *xfer; /* out xfer */ + u8 index; /* which segment we are */ + enum wa_seg_status status; + ssize_t result; /* bytes xfered or error */ + struct wa_xfer_hdr xfer_hdr; + u8 xfer_extra[]; /* xtra space for xfer_hdr_ctl */ +}; + +static void wa_seg_init(struct wa_seg *seg) +{ + /* usb_init_urb() repeats a lot of work, so we do it here */ + kref_init(&seg->urb.kref); +} + +/* + * Protected by xfer->lock + * + */ +struct wa_xfer { + struct kref refcnt; + struct list_head list_node; + spinlock_t lock; + u32 id; + + struct wahc *wa; /* Wire adapter we are plugged to */ + struct usb_host_endpoint *ep; + struct urb *urb; /* URB we are transfering for */ + struct wa_seg **seg; /* transfer segments */ + u8 segs, segs_submitted, segs_done; + unsigned is_inbound:1; + unsigned is_dma:1; + size_t seg_size; + int result; + + gfp_t gfp; /* allocation mask */ + + struct wusb_dev *wusb_dev; /* for activity timestamps */ +}; + +static inline void wa_xfer_init(struct wa_xfer *xfer) +{ + kref_init(&xfer->refcnt); + INIT_LIST_HEAD(&xfer->list_node); + spin_lock_init(&xfer->lock); +} + +/* + * Destory a transfer structure + * + * Note that the xfer->seg[index] thingies follow the URB life cycle, + * so we need to put them, not free them. + */ +static void wa_xfer_destroy(struct kref *_xfer) +{ + struct wa_xfer *xfer = container_of(_xfer, struct wa_xfer, refcnt); + if (xfer->seg) { + unsigned cnt; + for (cnt = 0; cnt < xfer->segs; cnt++) { + if (xfer->is_inbound) + usb_put_urb(xfer->seg[cnt]->dto_urb); + usb_put_urb(&xfer->seg[cnt]->urb); + } + } + kfree(xfer); + d_printf(2, NULL, "xfer %p destroyed\n", xfer); +} + +static void wa_xfer_get(struct wa_xfer *xfer) +{ + kref_get(&xfer->refcnt); +} + +static void wa_xfer_put(struct wa_xfer *xfer) +{ + d_fnstart(3, NULL, "(xfer %p) -- ref count bef put %d\n", + xfer, atomic_read(&xfer->refcnt.refcount)); + kref_put(&xfer->refcnt, wa_xfer_destroy); + d_fnend(3, NULL, "(xfer %p) = void\n", xfer); +} + +/* + * xfer is referenced + * + * xfer->lock has to be unlocked + * + * We take xfer->lock for setting the result; this is a barrier + * against drivers/usb/core/hcd.c:unlink1() being called after we call + * usb_hcd_giveback_urb() and wa_urb_dequeue() trying to get a + * reference to the transfer. + */ +static void wa_xfer_giveback(struct wa_xfer *xfer) +{ + unsigned long flags; + d_fnstart(3, NULL, "(xfer %p)\n", xfer); + spin_lock_irqsave(&xfer->wa->xfer_list_lock, flags); + list_del_init(&xfer->list_node); + spin_unlock_irqrestore(&xfer->wa->xfer_list_lock, flags); + /* FIXME: segmentation broken -- kills DWA */ + wusbhc_giveback_urb(xfer->wa->wusb, xfer->urb, xfer->result); + wa_put(xfer->wa); + wa_xfer_put(xfer); + d_fnend(3, NULL, "(xfer %p) = void\n", xfer); +} + +/* + * xfer is referenced + * + * xfer->lock has to be unlocked + */ +static void wa_xfer_completion(struct wa_xfer *xfer) +{ + d_fnstart(3, NULL, "(xfer %p)\n", xfer); + if (xfer->wusb_dev) + wusb_dev_put(xfer->wusb_dev); + rpipe_put(xfer->ep->hcpriv); + wa_xfer_giveback(xfer); + d_fnend(3, NULL, "(xfer %p) = void\n", xfer); + return; +} + +/* + * If transfer is done, wrap it up and return true + * + * xfer->lock has to be locked + */ +static unsigned __wa_xfer_is_done(struct wa_xfer *xfer) +{ + unsigned result, cnt; + struct wa_seg *seg; + struct urb *urb = xfer->urb; + unsigned found_short = 0; + + d_fnstart(3, NULL, "(xfer %p)\n", xfer); + result = xfer->segs_done == xfer->segs_submitted; + if (result == 0) + goto out; + urb->actual_length = 0; + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt]; + switch (seg->status) { + case WA_SEG_DONE: + if (found_short && seg->result > 0) { + if (printk_ratelimit()) + printk(KERN_ERR "xfer %p#%u: bad short " + "segments (%zu)\n", xfer, cnt, + seg->result); + urb->status = -EINVAL; + goto out; + } + urb->actual_length += seg->result; + if (seg->result < xfer->seg_size + && cnt != xfer->segs-1) + found_short = 1; + d_printf(2, NULL, "xfer %p#%u: DONE short %d " + "result %zu urb->actual_length %d\n", + xfer, seg->index, found_short, seg->result, + urb->actual_length); + break; + case WA_SEG_ERROR: + xfer->result = seg->result; + d_printf(2, NULL, "xfer %p#%u: ERROR result %zu\n", + xfer, seg->index, seg->result); + goto out; + case WA_SEG_ABORTED: + WARN_ON(urb->status != -ECONNRESET + && urb->status != -ENOENT); + d_printf(2, NULL, "xfer %p#%u ABORTED: result %d\n", + xfer, seg->index, urb->status); + xfer->result = urb->status; + goto out; + default: + /* if (printk_ratelimit()) */ + printk(KERN_ERR "xfer %p#%u: " + "is_done bad state %d\n", + xfer, cnt, seg->status); + xfer->result = -EINVAL; + WARN_ON(1); + goto out; + } + } + xfer->result = 0; +out: + d_fnend(3, NULL, "(xfer %p) = void\n", xfer); + return result; +} + +/* + * Initialize a transfer's ID + * + * We need to use a sequential number; if we use the pointer or the + * hash of the pointer, it can repeat over sequential transfers and + * then it will confuse the HWA....wonder why in hell they put a 32 + * bit handle in there then. + */ +static void wa_xfer_id_init(struct wa_xfer *xfer) +{ + xfer->id = atomic_add_return(1, &xfer->wa->xfer_id_count); +} + +/* + * Return the xfer's ID associated with xfer + * + * Need to generate a + */ +static u32 wa_xfer_id(struct wa_xfer *xfer) +{ + return xfer->id; +} + +/* + * Search for a transfer list ID on the HCD's URB list + * + * For 32 bit architectures, we use the pointer itself; for 64 bits, a + * 32-bit hash of the pointer. + * + * @returns NULL if not found. + */ +static struct wa_xfer *wa_xfer_get_by_id(struct wahc *wa, u32 id) +{ + unsigned long flags; + struct wa_xfer *xfer_itr; + spin_lock_irqsave(&wa->xfer_list_lock, flags); + list_for_each_entry(xfer_itr, &wa->xfer_list, list_node) { + if (id == xfer_itr->id) { + wa_xfer_get(xfer_itr); + goto out; + } + } + xfer_itr = NULL; +out: + spin_unlock_irqrestore(&wa->xfer_list_lock, flags); + return xfer_itr; +} + +struct wa_xfer_abort_buffer { + struct urb urb; + struct wa_xfer_abort cmd; +}; + +static void __wa_xfer_abort_cb(struct urb *urb) +{ + struct wa_xfer_abort_buffer *b = urb->context; + usb_put_urb(&b->urb); +} + +/* + * Aborts an ongoing transaction + * + * Assumes the transfer is referenced and locked and in a submitted + * state (mainly that there is an endpoint/rpipe assigned). + * + * The callback (see above) does nothing but freeing up the data by + * putting the URB. Because the URB is allocated at the head of the + * struct, the whole space we allocated is kfreed. + * + * We'll get an 'aborted transaction' xfer result on DTI, that'll + * politely ignore because at this point the transaction has been + * marked as aborted already. + */ +static void __wa_xfer_abort(struct wa_xfer *xfer) +{ + int result; + struct device *dev = &xfer->wa->usb_iface->dev; + struct wa_xfer_abort_buffer *b; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + b = kmalloc(sizeof(*b), GFP_ATOMIC); + if (b == NULL) + goto error_kmalloc; + b->cmd.bLength = sizeof(b->cmd); + b->cmd.bRequestType = WA_XFER_ABORT; + b->cmd.wRPipe = rpipe->descr.wRPipeIndex; + b->cmd.dwTransferID = wa_xfer_id(xfer); + + usb_init_urb(&b->urb); + usb_fill_bulk_urb(&b->urb, xfer->wa->usb_dev, + usb_sndbulkpipe(xfer->wa->usb_dev, + xfer->wa->dto_epd->bEndpointAddress), + &b->cmd, sizeof(b->cmd), __wa_xfer_abort_cb, b); + result = usb_submit_urb(&b->urb, GFP_ATOMIC); + if (result < 0) + goto error_submit; + return; /* callback frees! */ + + +error_submit: + if (printk_ratelimit()) + dev_err(dev, "xfer %p: Can't submit abort request: %d\n", + xfer, result); + kfree(b); +error_kmalloc: + return; + +} + +/* + * + * @returns < 0 on error, transfer segment request size if ok + */ +static ssize_t __wa_xfer_setup_sizes(struct wa_xfer *xfer, + enum wa_xfer_type *pxfer_type) +{ + ssize_t result; + struct device *dev = &xfer->wa->usb_iface->dev; + size_t maxpktsize; + struct urb *urb = xfer->urb; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + d_fnstart(3, dev, "(xfer %p [rpipe %p] urb %p)\n", + xfer, rpipe, urb); + switch (rpipe->descr.bmAttribute & 0x3) { + case USB_ENDPOINT_XFER_CONTROL: + *pxfer_type = WA_XFER_TYPE_CTL; + result = sizeof(struct wa_xfer_ctl); + break; + case USB_ENDPOINT_XFER_INT: + case USB_ENDPOINT_XFER_BULK: + *pxfer_type = WA_XFER_TYPE_BI; + result = sizeof(struct wa_xfer_bi); + break; + case USB_ENDPOINT_XFER_ISOC: + dev_err(dev, "FIXME: ISOC not implemented\n"); + result = -ENOSYS; + goto error; + default: + /* never happens */ + BUG(); + result = -EINVAL; /* shut gcc up */ + }; + xfer->is_inbound = urb->pipe & USB_DIR_IN ? 1 : 0; + xfer->is_dma = urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP ? 1 : 0; + xfer->seg_size = le16_to_cpu(rpipe->descr.wBlocks) + * 1 << (xfer->wa->wa_descr->bRPipeBlockSize - 1); + /* Compute the segment size and make sure it is a multiple of + * the maxpktsize (WUSB1.0[8.3.3.1])...not really too much of + * a check (FIXME) */ + maxpktsize = le16_to_cpu(rpipe->descr.wMaxPacketSize); + if (xfer->seg_size < maxpktsize) { + dev_err(dev, "HW BUG? seg_size %zu smaller than maxpktsize " + "%zu\n", xfer->seg_size, maxpktsize); + result = -EINVAL; + goto error; + } + xfer->seg_size = (xfer->seg_size / maxpktsize) * maxpktsize; + xfer->segs = (urb->transfer_buffer_length + xfer->seg_size - 1) + / xfer->seg_size; + if (xfer->segs >= WA_SEGS_MAX) { + dev_err(dev, "BUG? ops, number of segments %d bigger than %d\n", + (int)(urb->transfer_buffer_length / xfer->seg_size), + WA_SEGS_MAX); + result = -EINVAL; + goto error; + } + if (xfer->segs == 0 && *pxfer_type == WA_XFER_TYPE_CTL) + xfer->segs = 1; +error: + d_fnend(3, dev, "(xfer %p [rpipe %p] urb %p) = %d\n", + xfer, rpipe, urb, (int)result); + return result; +} + +/** Fill in the common request header and xfer-type specific data. */ +static void __wa_xfer_setup_hdr0(struct wa_xfer *xfer, + struct wa_xfer_hdr *xfer_hdr0, + enum wa_xfer_type xfer_type, + size_t xfer_hdr_size) +{ + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + xfer_hdr0 = &xfer->seg[0]->xfer_hdr; + xfer_hdr0->bLength = xfer_hdr_size; + xfer_hdr0->bRequestType = xfer_type; + xfer_hdr0->wRPipe = rpipe->descr.wRPipeIndex; + xfer_hdr0->dwTransferID = wa_xfer_id(xfer); + xfer_hdr0->bTransferSegment = 0; + switch (xfer_type) { + case WA_XFER_TYPE_CTL: { + struct wa_xfer_ctl *xfer_ctl = + container_of(xfer_hdr0, struct wa_xfer_ctl, hdr); + xfer_ctl->bmAttribute = xfer->is_inbound ? 1 : 0; + BUG_ON(xfer->urb->transfer_flags & URB_NO_SETUP_DMA_MAP + && xfer->urb->setup_packet == NULL); + memcpy(&xfer_ctl->baSetupData, xfer->urb->setup_packet, + sizeof(xfer_ctl->baSetupData)); + break; + } + case WA_XFER_TYPE_BI: + break; + case WA_XFER_TYPE_ISO: + printk(KERN_ERR "FIXME: ISOC not implemented\n"); + default: + BUG(); + }; +} + +/* + * Callback for the OUT data phase of the segment request + * + * Check wa_seg_cb(); most comments also apply here because this + * function does almost the same thing and they work closely + * together. + * + * If the seg request has failed but this DTO phase has suceeded, + * wa_seg_cb() has already failed the segment and moved the + * status to WA_SEG_ERROR, so this will go through 'case 0' and + * effectively do nothing. + */ +static void wa_seg_dto_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned long flags; + unsigned rpipe_ready = 0; + u8 done = 0; + + d_fnstart(3, NULL, "(urb %p [%d])\n", urb, urb->status); + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + d_printf(2, dev, "xfer %p#%u: data out done (%d bytes)\n", + xfer, seg->index, urb->actual_length); + if (seg->status < WA_SEG_PENDING) + seg->status = WA_SEG_PENDING; + seg->result = urb->actual_length; + spin_unlock_irqrestore(&xfer->lock, flags); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: data out error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + if (seg->status != WA_SEG_ERROR) { + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + __wa_xfer_abort(xfer); + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + } + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } + d_fnend(3, NULL, "(urb %p [%d]) = void\n", urb, urb->status); +} + +/* + * Callback for the segment request + * + * If succesful transition state (unless already transitioned or + * outbound transfer); otherwise, take a note of the error, mark this + * segment done and try completion. + * + * Note we don't access until we are sure that the transfer hasn't + * been cancelled (ECONNRESET, ENOENT), which could mean that + * seg->xfer could be already gone. + * + * We have to check before setting the status to WA_SEG_PENDING + * because sometimes the xfer result callback arrives before this + * callback (geeeeeeze), so it might happen that we are already in + * another state. As well, we don't set it if the transfer is inbound, + * as in that case, wa_seg_dto_cb will do it when the OUT data phase + * finishes. + */ +static void wa_seg_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned long flags; + unsigned rpipe_ready; + u8 done = 0; + + d_fnstart(3, NULL, "(urb %p [%d])\n", urb, urb->status); + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + d_printf(2, dev, "xfer %p#%u: request done\n", + xfer, seg->index); + if (xfer->is_inbound && seg->status < WA_SEG_PENDING) + seg->status = WA_SEG_PENDING; + spin_unlock_irqrestore(&xfer->lock, flags); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: request error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + usb_unlink_urb(seg->dto_urb); + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + __wa_xfer_abort(xfer); + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } + d_fnend(3, NULL, "(urb %p [%d]) = void\n", urb, urb->status); +} + +/* + * Allocate the segs array and initialize each of them + * + * The segments are freed by wa_xfer_destroy() when the xfer use count + * drops to zero; however, because each segment is given the same life + * cycle as the USB URB it contains, it is actually freed by + * usb_put_urb() on the contained USB URB (twisted, eh?). + */ +static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size) +{ + int result, cnt; + size_t alloc_size = sizeof(*xfer->seg[0]) + - sizeof(xfer->seg[0]->xfer_hdr) + xfer_hdr_size; + struct usb_device *usb_dev = xfer->wa->usb_dev; + const struct usb_endpoint_descriptor *dto_epd = xfer->wa->dto_epd; + struct wa_seg *seg; + size_t buf_itr, buf_size, buf_itr_size; + + result = -ENOMEM; + xfer->seg = kzalloc(xfer->segs * sizeof(xfer->seg[0]), GFP_ATOMIC); + if (xfer->seg == NULL) + goto error_segs_kzalloc; + buf_itr = 0; + buf_size = xfer->urb->transfer_buffer_length; + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt] = kzalloc(alloc_size, GFP_ATOMIC); + if (seg == NULL) + goto error_seg_kzalloc; + wa_seg_init(seg); + seg->xfer = xfer; + seg->index = cnt; + usb_fill_bulk_urb(&seg->urb, usb_dev, + usb_sndbulkpipe(usb_dev, + dto_epd->bEndpointAddress), + &seg->xfer_hdr, xfer_hdr_size, + wa_seg_cb, seg); + buf_itr_size = buf_size > xfer->seg_size ? + xfer->seg_size : buf_size; + if (xfer->is_inbound == 0 && buf_size > 0) { + seg->dto_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (seg->dto_urb == NULL) + goto error_dto_alloc; + usb_fill_bulk_urb( + seg->dto_urb, usb_dev, + usb_sndbulkpipe(usb_dev, + dto_epd->bEndpointAddress), + NULL, 0, wa_seg_dto_cb, seg); + if (xfer->is_dma) { + seg->dto_urb->transfer_dma = + xfer->urb->transfer_dma + buf_itr; + seg->dto_urb->transfer_flags |= + URB_NO_TRANSFER_DMA_MAP; + } else + seg->dto_urb->transfer_buffer = + xfer->urb->transfer_buffer + buf_itr; + seg->dto_urb->transfer_buffer_length = buf_itr_size; + } + seg->status = WA_SEG_READY; + buf_itr += buf_itr_size; + buf_size -= buf_itr_size; + } + return 0; + +error_dto_alloc: + kfree(xfer->seg[cnt]); + cnt--; +error_seg_kzalloc: + /* use the fact that cnt is left at were it failed */ + for (; cnt > 0; cnt--) { + if (xfer->is_inbound == 0) + kfree(xfer->seg[cnt]->dto_urb); + kfree(xfer->seg[cnt]); + } +error_segs_kzalloc: + return result; +} + +/* + * Allocates all the stuff needed to submit a transfer + * + * Breaks the whole data buffer in a list of segments, each one has a + * structure allocated to it and linked in xfer->seg[index] + * + * FIXME: merge setup_segs() and the last part of this function, no + * need to do two for loops when we could run everything in a + * single one + */ +static int __wa_xfer_setup(struct wa_xfer *xfer, struct urb *urb) +{ + int result; + struct device *dev = &xfer->wa->usb_iface->dev; + enum wa_xfer_type xfer_type = 0; /* shut up GCC */ + size_t xfer_hdr_size, cnt, transfer_size; + struct wa_xfer_hdr *xfer_hdr0, *xfer_hdr; + + d_fnstart(3, dev, "(xfer %p [rpipe %p] urb %p)\n", + xfer, xfer->ep->hcpriv, urb); + + result = __wa_xfer_setup_sizes(xfer, &xfer_type); + if (result < 0) + goto error_setup_sizes; + xfer_hdr_size = result; + result = __wa_xfer_setup_segs(xfer, xfer_hdr_size); + if (result < 0) { + dev_err(dev, "xfer %p: Failed to allocate %d segments: %d\n", + xfer, xfer->segs, result); + goto error_setup_segs; + } + /* Fill the first header */ + xfer_hdr0 = &xfer->seg[0]->xfer_hdr; + wa_xfer_id_init(xfer); + __wa_xfer_setup_hdr0(xfer, xfer_hdr0, xfer_type, xfer_hdr_size); + + /* Fill remainig headers */ + xfer_hdr = xfer_hdr0; + transfer_size = urb->transfer_buffer_length; + xfer_hdr0->dwTransferLength = transfer_size > xfer->seg_size ? + xfer->seg_size : transfer_size; + transfer_size -= xfer->seg_size; + for (cnt = 1; cnt < xfer->segs; cnt++) { + xfer_hdr = &xfer->seg[cnt]->xfer_hdr; + memcpy(xfer_hdr, xfer_hdr0, xfer_hdr_size); + xfer_hdr->bTransferSegment = cnt; + xfer_hdr->dwTransferLength = transfer_size > xfer->seg_size ? + cpu_to_le32(xfer->seg_size) + : cpu_to_le32(transfer_size); + xfer->seg[cnt]->status = WA_SEG_READY; + transfer_size -= xfer->seg_size; + } + xfer_hdr->bTransferSegment |= 0x80; /* this is the last segment */ + result = 0; +error_setup_segs: +error_setup_sizes: + d_fnend(3, dev, "(xfer %p [rpipe %p] urb %p) = %d\n", + xfer, xfer->ep->hcpriv, urb, result); + return result; +} + +/* + * + * + * rpipe->seg_lock is held! + */ +static int __wa_seg_submit(struct wa_rpipe *rpipe, struct wa_xfer *xfer, + struct wa_seg *seg) +{ + int result; + result = usb_submit_urb(&seg->urb, GFP_ATOMIC); + if (result < 0) { + printk(KERN_ERR "xfer %p#%u: REQ submit failed: %d\n", + xfer, seg->index, result); + goto error_seg_submit; + } + if (seg->dto_urb) { + result = usb_submit_urb(seg->dto_urb, GFP_ATOMIC); + if (result < 0) { + printk(KERN_ERR "xfer %p#%u: DTO submit failed: %d\n", + xfer, seg->index, result); + goto error_dto_submit; + } + } + seg->status = WA_SEG_SUBMITTED; + rpipe_avail_dec(rpipe); + return 0; + +error_dto_submit: + usb_unlink_urb(&seg->urb); +error_seg_submit: + seg->status = WA_SEG_ERROR; + seg->result = result; + return result; +} + +/* + * Execute more queued request segments until the maximum concurrent allowed + * + * The ugly unlock/lock sequence on the error path is needed as the + * xfer->lock normally nests the seg_lock and not viceversa. + * + */ +static void wa_xfer_delayed_run(struct wa_rpipe *rpipe) +{ + int result; + struct device *dev = &rpipe->wa->usb_iface->dev; + struct wa_seg *seg; + struct wa_xfer *xfer; + unsigned long flags; + + d_fnstart(1, dev, "(rpipe #%d) %d segments available\n", + le16_to_cpu(rpipe->descr.wRPipeIndex), + atomic_read(&rpipe->segs_available)); + spin_lock_irqsave(&rpipe->seg_lock, flags); + while (atomic_read(&rpipe->segs_available) > 0 + && !list_empty(&rpipe->seg_list)) { + seg = list_entry(rpipe->seg_list.next, struct wa_seg, + list_node); + list_del(&seg->list_node); + xfer = seg->xfer; + result = __wa_seg_submit(rpipe, xfer, seg); + d_printf(1, dev, "xfer %p#%u submitted from delayed " + "[%d segments available] %d\n", + xfer, seg->index, + atomic_read(&rpipe->segs_available), result); + if (unlikely(result < 0)) { + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + spin_lock_irqsave(&xfer->lock, flags); + __wa_xfer_abort(xfer); + xfer->segs_done++; + spin_unlock_irqrestore(&xfer->lock, flags); + spin_lock_irqsave(&rpipe->seg_lock, flags); + } + } + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + d_fnend(1, dev, "(rpipe #%d) = void, %d segments available\n", + le16_to_cpu(rpipe->descr.wRPipeIndex), + atomic_read(&rpipe->segs_available)); + +} + +/* + * + * xfer->lock is taken + * + * On failure submitting we just stop submitting and return error; + * wa_urb_enqueue_b() will execute the completion path + */ +static int __wa_xfer_submit(struct wa_xfer *xfer) +{ + int result; + struct wahc *wa = xfer->wa; + struct device *dev = &wa->usb_iface->dev; + unsigned cnt; + struct wa_seg *seg; + unsigned long flags; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + size_t maxrequests = le16_to_cpu(rpipe->descr.wRequests); + u8 available; + u8 empty; + + d_fnstart(3, dev, "(xfer %p [rpipe %p])\n", + xfer, xfer->ep->hcpriv); + + spin_lock_irqsave(&wa->xfer_list_lock, flags); + list_add_tail(&xfer->list_node, &wa->xfer_list); + spin_unlock_irqrestore(&wa->xfer_list_lock, flags); + + BUG_ON(atomic_read(&rpipe->segs_available) > maxrequests); + result = 0; + spin_lock_irqsave(&rpipe->seg_lock, flags); + for (cnt = 0; cnt < xfer->segs; cnt++) { + available = atomic_read(&rpipe->segs_available); + empty = list_empty(&rpipe->seg_list); + seg = xfer->seg[cnt]; + d_printf(2, dev, "xfer %p#%u: available %u empty %u (%s)\n", + xfer, cnt, available, empty, + available == 0 || !empty ? "delayed" : "submitted"); + if (available == 0 || !empty) { + d_printf(1, dev, "xfer %p#%u: delayed\n", xfer, cnt); + seg->status = WA_SEG_DELAYED; + list_add_tail(&seg->list_node, &rpipe->seg_list); + } else { + result = __wa_seg_submit(rpipe, xfer, seg); + if (result < 0) + goto error_seg_submit; + } + xfer->segs_submitted++; + } + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + d_fnend(3, dev, "(xfer %p [rpipe %p]) = void\n", xfer, + xfer->ep->hcpriv); + return result; + +error_seg_submit: + __wa_xfer_abort(xfer); + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + d_fnend(3, dev, "(xfer %p [rpipe %p]) = void\n", xfer, + xfer->ep->hcpriv); + return result; +} + +/* + * Second part of a URB/transfer enqueuement + * + * Assumes this comes from wa_urb_enqueue() [maybe through + * wa_urb_enqueue_run()]. At this point: + * + * xfer->wa filled and refcounted + * xfer->ep filled with rpipe refcounted if + * delayed == 0 + * xfer->urb filled and refcounted (this is the case when called + * from wa_urb_enqueue() as we come from usb_submit_urb() + * and when called by wa_urb_enqueue_run(), as we took an + * extra ref dropped by _run() after we return). + * xfer->gfp filled + * + * If we fail at __wa_xfer_submit(), then we just check if we are done + * and if so, we run the completion procedure. However, if we are not + * yet done, we do nothing and wait for the completion handlers from + * the submitted URBs or from the xfer-result path to kick in. If xfer + * result never kicks in, the xfer will timeout from the USB code and + * dequeue() will be called. + */ +static void wa_urb_enqueue_b(struct wa_xfer *xfer) +{ + int result; + unsigned long flags; + struct urb *urb = xfer->urb; + struct wahc *wa = xfer->wa; + struct wusbhc *wusbhc = wa->wusb; + struct device *dev = &wa->usb_iface->dev; + struct wusb_dev *wusb_dev; + unsigned done; + + d_fnstart(3, dev, "(wa %p urb %p)\n", wa, urb); + result = rpipe_get_by_ep(wa, xfer->ep, urb, xfer->gfp); + if (result < 0) + goto error_rpipe_get; + result = -ENODEV; + /* FIXME: segmentation broken -- kills DWA */ + mutex_lock(&wusbhc->mutex); /* get a WUSB dev */ + if (urb->dev == NULL) + goto error_dev_gone; + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); + if (wusb_dev == NULL) { + mutex_unlock(&wusbhc->mutex); + goto error_dev_gone; + } + mutex_unlock(&wusbhc->mutex); + + spin_lock_irqsave(&xfer->lock, flags); + xfer->wusb_dev = wusb_dev; + result = urb->status; + if (urb->status != -EINPROGRESS) + goto error_dequeued; + + result = __wa_xfer_setup(xfer, urb); + if (result < 0) + goto error_xfer_setup; + result = __wa_xfer_submit(xfer); + if (result < 0) + goto error_xfer_submit; + spin_unlock_irqrestore(&xfer->lock, flags); + d_fnend(3, dev, "(wa %p urb %p) = void\n", wa, urb); + return; + + /* this is basically wa_xfer_completion() broken up wa_xfer_giveback() + * does a wa_xfer_put() that will call wa_xfer_destroy() and clean + * upundo setup(). + */ +error_xfer_setup: +error_dequeued: + spin_unlock_irqrestore(&xfer->lock, flags); + /* FIXME: segmentation broken, kills DWA */ + if (wusb_dev) + wusb_dev_put(wusb_dev); +error_dev_gone: + rpipe_put(xfer->ep->hcpriv); +error_rpipe_get: + xfer->result = result; + wa_xfer_giveback(xfer); + d_fnend(3, dev, "(wa %p urb %p) = (void) %d\n", wa, urb, result); + return; + +error_xfer_submit: + done = __wa_xfer_is_done(xfer); + xfer->result = result; + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + d_fnend(3, dev, "(wa %p urb %p) = (void) %d\n", wa, urb, result); + return; +} + +/* + * Execute the delayed transfers in the Wire Adapter @wa + * + * We need to be careful here, as dequeue() could be called in the + * middle. That's why we do the whole thing under the + * wa->xfer_list_lock. If dequeue() jumps in, it first locks urb->lock + * and then checks the list -- so as we would be acquiring in inverse + * order, we just drop the lock once we have the xfer and reacquire it + * later. + */ +void wa_urb_enqueue_run(struct work_struct *ws) +{ + struct wahc *wa = container_of(ws, struct wahc, xfer_work); + struct device *dev = &wa->usb_iface->dev; + struct wa_xfer *xfer, *next; + struct urb *urb; + + d_fnstart(3, dev, "(wa %p)\n", wa); + spin_lock_irq(&wa->xfer_list_lock); + list_for_each_entry_safe(xfer, next, &wa->xfer_delayed_list, + list_node) { + list_del_init(&xfer->list_node); + spin_unlock_irq(&wa->xfer_list_lock); + + urb = xfer->urb; + wa_urb_enqueue_b(xfer); + usb_put_urb(urb); /* taken when queuing */ + + spin_lock_irq(&wa->xfer_list_lock); + } + spin_unlock_irq(&wa->xfer_list_lock); + d_fnend(3, dev, "(wa %p) = void\n", wa); +} +EXPORT_SYMBOL_GPL(wa_urb_enqueue_run); + +/* + * Submit a transfer to the Wire Adapter in a delayed way + * + * The process of enqueuing involves possible sleeps() [see + * enqueue_b(), for the rpipe_get() and the mutex_lock()]. If we are + * in an atomic section, we defer the enqueue_b() call--else we call direct. + * + * @urb: We own a reference to it done by the HCI Linux USB stack that + * will be given up by calling usb_hcd_giveback_urb() or by + * returning error from this function -> ergo we don't have to + * refcount it. + */ +int wa_urb_enqueue(struct wahc *wa, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t gfp) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + struct wa_xfer *xfer; + unsigned long my_flags; + unsigned cant_sleep = irqs_disabled() | in_atomic(); + + d_fnstart(3, dev, "(wa %p ep %p urb %p [%d] gfp 0x%x)\n", + wa, ep, urb, urb->transfer_buffer_length, gfp); + + if (urb->transfer_buffer == NULL + && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + && urb->transfer_buffer_length != 0) { + dev_err(dev, "BUG? urb %p: NULL xfer buffer & NODMA\n", urb); + dump_stack(); + } + + result = -ENOMEM; + xfer = kzalloc(sizeof(*xfer), gfp); + if (xfer == NULL) + goto error_kmalloc; + + result = -ENOENT; + if (urb->status != -EINPROGRESS) /* cancelled */ + goto error_dequeued; /* before starting? */ + wa_xfer_init(xfer); + xfer->wa = wa_get(wa); + xfer->urb = urb; + xfer->gfp = gfp; + xfer->ep = ep; + urb->hcpriv = xfer; + d_printf(2, dev, "xfer %p urb %p pipe 0x%02x [%d bytes] %s %s %s\n", + xfer, urb, urb->pipe, urb->transfer_buffer_length, + urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP ? "dma" : "nodma", + urb->pipe & USB_DIR_IN ? "inbound" : "outbound", + cant_sleep ? "deferred" : "inline"); + if (cant_sleep) { + usb_get_urb(urb); + spin_lock_irqsave(&wa->xfer_list_lock, my_flags); + list_add_tail(&xfer->list_node, &wa->xfer_delayed_list); + spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags); + queue_work(wusbd, &wa->xfer_work); + } else { + wa_urb_enqueue_b(xfer); + } + d_fnend(3, dev, "(wa %p ep %p urb %p [%d] gfp 0x%x) = 0\n", + wa, ep, urb, urb->transfer_buffer_length, gfp); + return 0; + +error_dequeued: + kfree(xfer); +error_kmalloc: + d_fnend(3, dev, "(wa %p ep %p urb %p [%d] gfp 0x%x) = %d\n", + wa, ep, urb, urb->transfer_buffer_length, gfp, result); + return result; +} +EXPORT_SYMBOL_GPL(wa_urb_enqueue); + +/* + * Dequeue a URB and make sure uwb_hcd_giveback_urb() [completion + * handler] is called. + * + * Until a transfer goes successfully through wa_urb_enqueue() it + * needs to be dequeued with completion calling; when stuck in delayed + * or before wa_xfer_setup() is called, we need to do completion. + * + * not setup If there is no hcpriv yet, that means that that enqueue + * still had no time to set the xfer up. Because + * urb->status should be other than -EINPROGRESS, + * enqueue() will catch that and bail out. + * + * If the transfer has gone through setup, we just need to clean it + * up. If it has gone through submit(), we have to abort it [with an + * asynch request] and then make sure we cancel each segment. + * + */ +int wa_urb_dequeue(struct wahc *wa, struct urb *urb) +{ + struct device *dev = &wa->usb_iface->dev; + unsigned long flags, flags2; + struct wa_xfer *xfer; + struct wa_seg *seg; + struct wa_rpipe *rpipe; + unsigned cnt; + unsigned rpipe_ready = 0; + + d_fnstart(3, dev, "(wa %p, urb %p)\n", wa, urb); + + d_printf(1, dev, "xfer %p urb %p: aborting\n", urb->hcpriv, urb); + xfer = urb->hcpriv; + if (xfer == NULL) { + /* NOthing setup yet enqueue will see urb->status != + * -EINPROGRESS (by hcd layer) and bail out with + * error, no need to do completion + */ + BUG_ON(urb->status == -EINPROGRESS); + goto out; + } + spin_lock_irqsave(&xfer->lock, flags); + rpipe = xfer->ep->hcpriv; + /* Check the delayed list -> if there, release and complete */ + spin_lock_irqsave(&wa->xfer_list_lock, flags2); + if (!list_empty(&xfer->list_node) && xfer->seg == NULL) + goto dequeue_delayed; + spin_unlock_irqrestore(&wa->xfer_list_lock, flags2); + if (xfer->seg == NULL) /* still hasn't reached */ + goto out_unlock; /* setup(), enqueue_b() completes */ + /* Ok, the xfer is in flight already, it's been setup and submitted.*/ + __wa_xfer_abort(xfer); + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt]; + switch (seg->status) { + case WA_SEG_NOTREADY: + case WA_SEG_READY: + printk(KERN_ERR "xfer %p#%u: dequeue bad state %u\n", + xfer, cnt, seg->status); + WARN_ON(1); + break; + case WA_SEG_DELAYED: + seg->status = WA_SEG_ABORTED; + spin_lock_irqsave(&rpipe->seg_lock, flags2); + list_del(&seg->list_node); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + spin_unlock_irqrestore(&rpipe->seg_lock, flags2); + break; + case WA_SEG_SUBMITTED: + seg->status = WA_SEG_ABORTED; + usb_unlink_urb(&seg->urb); + if (xfer->is_inbound == 0) + usb_unlink_urb(seg->dto_urb); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_PENDING: + seg->status = WA_SEG_ABORTED; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_DTI_PENDING: + usb_unlink_urb(wa->dti_urb); + seg->status = WA_SEG_ABORTED; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_DONE: + case WA_SEG_ERROR: + case WA_SEG_ABORTED: + break; + } + } + xfer->result = urb->status; /* -ENOENT or -ECONNRESET */ + __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + d_fnend(3, dev, "(wa %p, urb %p) = 0\n", wa, urb); + return 0; + +out_unlock: + spin_unlock_irqrestore(&xfer->lock, flags); +out: + d_fnend(3, dev, "(wa %p, urb %p) = 0\n", wa, urb); + return 0; + +dequeue_delayed: + list_del_init(&xfer->list_node); + spin_unlock_irqrestore(&wa->xfer_list_lock, flags2); + xfer->result = urb->status; + spin_unlock_irqrestore(&xfer->lock, flags); + wa_xfer_giveback(xfer); + usb_put_urb(urb); /* we got a ref in enqueue() */ + d_fnend(3, dev, "(wa %p, urb %p) = 0\n", wa, urb); + return 0; +} +EXPORT_SYMBOL_GPL(wa_urb_dequeue); + +/* + * Translation from WA status codes (WUSB1.0 Table 8.15) to errno + * codes + * + * Positive errno values are internal inconsistencies and should be + * flagged louder. Negative are to be passed up to the user in the + * normal way. + * + * @status: USB WA status code -- high two bits are stripped. + */ +static int wa_xfer_status_to_errno(u8 status) +{ + int errno; + u8 real_status = status; + static int xlat[] = { + [WA_XFER_STATUS_SUCCESS] = 0, + [WA_XFER_STATUS_HALTED] = -EPIPE, + [WA_XFER_STATUS_DATA_BUFFER_ERROR] = -ENOBUFS, + [WA_XFER_STATUS_BABBLE] = -EOVERFLOW, + [WA_XFER_RESERVED] = EINVAL, + [WA_XFER_STATUS_NOT_FOUND] = 0, + [WA_XFER_STATUS_INSUFFICIENT_RESOURCE] = -ENOMEM, + [WA_XFER_STATUS_TRANSACTION_ERROR] = -EILSEQ, + [WA_XFER_STATUS_ABORTED] = -EINTR, + [WA_XFER_STATUS_RPIPE_NOT_READY] = EINVAL, + [WA_XFER_INVALID_FORMAT] = EINVAL, + [WA_XFER_UNEXPECTED_SEGMENT_NUMBER] = EINVAL, + [WA_XFER_STATUS_RPIPE_TYPE_MISMATCH] = EINVAL, + }; + status &= 0x3f; + + if (status == 0) + return 0; + if (status >= ARRAY_SIZE(xlat)) { + if (printk_ratelimit()) + printk(KERN_ERR "%s(): BUG? " + "Unknown WA transfer status 0x%02x\n", + __func__, real_status); + return -EINVAL; + } + errno = xlat[status]; + if (unlikely(errno > 0)) { + if (printk_ratelimit()) + printk(KERN_ERR "%s(): BUG? " + "Inconsistent WA status: 0x%02x\n", + __func__, real_status); + errno = -errno; + } + return errno; +} + +/* + * Process a xfer result completion message + * + * inbound transfers: need to schedule a DTI read + * + * FIXME: this functio needs to be broken up in parts + */ +static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + unsigned long flags; + u8 seg_idx; + struct wa_seg *seg; + struct wa_rpipe *rpipe; + struct wa_xfer_result *xfer_result = wa->xfer_result; + u8 done = 0; + u8 usb_status; + unsigned rpipe_ready = 0; + + d_fnstart(3, dev, "(wa %p xfer %p)\n", wa, xfer); + spin_lock_irqsave(&xfer->lock, flags); + seg_idx = xfer_result->bTransferSegment & 0x7f; + if (unlikely(seg_idx >= xfer->segs)) + goto error_bad_seg; + seg = xfer->seg[seg_idx]; + rpipe = xfer->ep->hcpriv; + usb_status = xfer_result->bTransferStatus; + d_printf(2, dev, "xfer %p#%u: bTransferStatus 0x%02x (seg %u)\n", + xfer, seg_idx, usb_status, seg->status); + if (seg->status == WA_SEG_ABORTED + || seg->status == WA_SEG_ERROR) /* already handled */ + goto segment_aborted; + if (seg->status == WA_SEG_SUBMITTED) /* ops, got here */ + seg->status = WA_SEG_PENDING; /* before wa_seg{_dto}_cb() */ + if (seg->status != WA_SEG_PENDING) { + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: Bad segment state %u\n", + xfer, seg_idx, seg->status); + seg->status = WA_SEG_PENDING; /* workaround/"fix" it */ + } + if (usb_status & 0x80) { + seg->result = wa_xfer_status_to_errno(usb_status); + dev_err(dev, "DTI: xfer %p#%u failed (0x%02x)\n", + xfer, seg->index, usb_status); + goto error_complete; + } + /* FIXME: we ignore warnings, tally them for stats */ + if (usb_status & 0x40) /* Warning?... */ + usb_status = 0; /* ... pass */ + if (xfer->is_inbound) { /* IN data phase: read to buffer */ + seg->status = WA_SEG_DTI_PENDING; + BUG_ON(wa->buf_in_urb->status == -EINPROGRESS); + if (xfer->is_dma) { + wa->buf_in_urb->transfer_dma = + xfer->urb->transfer_dma + + seg_idx * xfer->seg_size; + wa->buf_in_urb->transfer_flags + |= URB_NO_TRANSFER_DMA_MAP; + } else { + wa->buf_in_urb->transfer_buffer = + xfer->urb->transfer_buffer + + seg_idx * xfer->seg_size; + wa->buf_in_urb->transfer_flags + &= ~URB_NO_TRANSFER_DMA_MAP; + } + wa->buf_in_urb->transfer_buffer_length = + le32_to_cpu(xfer_result->dwTransferLength); + wa->buf_in_urb->context = seg; + result = usb_submit_urb(wa->buf_in_urb, GFP_ATOMIC); + if (result < 0) + goto error_submit_buf_in; + } else { + /* OUT data phase, complete it -- */ + seg->status = WA_SEG_DONE; + seg->result = le32_to_cpu(xfer_result->dwTransferLength); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + } + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + d_fnend(3, dev, "(wa %p xfer %p) = void\n", wa, xfer); + return; + + +error_submit_buf_in: + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: can't submit DTI data phase: %d\n", + xfer, seg_idx, result); + seg->result = result; +error_complete: + seg->status = WA_SEG_ERROR; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + __wa_xfer_abort(xfer); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + d_fnend(3, dev, "(wa %p xfer %p) = void [segment/DTI-submit error]\n", + wa, xfer); + return; + + +error_bad_seg: + spin_unlock_irqrestore(&xfer->lock, flags); + wa_urb_dequeue(wa, xfer->urb); + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: bad segment\n", xfer, seg_idx); + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + d_fnend(3, dev, "(wa %p xfer %p) = void [bad seg]\n", wa, xfer); + return; + + +segment_aborted: + /* nothing to do, as the aborter did the completion */ + spin_unlock_irqrestore(&xfer->lock, flags); + d_fnend(3, dev, "(wa %p xfer %p) = void [segment aborted]\n", + wa, xfer); + return; + +} + +/* + * Callback for the IN data phase + * + * If succesful transition state; otherwise, take a note of the + * error, mark this segment done and try completion. + * + * Note we don't access until we are sure that the transfer hasn't + * been cancelled (ECONNRESET, ENOENT), which could mean that + * seg->xfer could be already gone. + */ +static void wa_buf_in_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned rpipe_ready; + unsigned long flags; + u8 done = 0; + + d_fnstart(3, NULL, "(urb %p [%d])\n", urb, urb->status); + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + d_printf(2, dev, "xfer %p#%u: data in done (%zu bytes)\n", + xfer, seg->index, (size_t)urb->actual_length); + seg->status = WA_SEG_DONE; + seg->result = urb->actual_length; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: data in error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + __wa_xfer_abort(xfer); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } + d_fnend(3, NULL, "(urb %p [%d]) = void\n", urb, urb->status); +} + +/* + * Handle an incoming transfer result buffer + * + * Given a transfer result buffer, it completes the transfer (possibly + * scheduling and buffer in read) and then resubmits the DTI URB for a + * new transfer result read. + * + * + * The xfer_result DTI URB state machine + * + * States: OFF | RXR (Read-Xfer-Result) | RBI (Read-Buffer-In) + * + * We start in OFF mode, the first xfer_result notification [through + * wa_handle_notif_xfer()] moves us to RXR by posting the DTI-URB to + * read. + * + * We receive a buffer -- if it is not a xfer_result, we complain and + * repost the DTI-URB. If it is a xfer_result then do the xfer seg + * request accounting. If it is an IN segment, we move to RBI and post + * a BUF-IN-URB to the right buffer. The BUF-IN-URB callback will + * repost the DTI-URB and move to RXR state. if there was no IN + * segment, it will repost the DTI-URB. + * + * We go back to OFF when we detect a ENOENT or ESHUTDOWN (or too many + * errors) in the URBs. + */ +static void wa_xfer_result_cb(struct urb *urb) +{ + int result; + struct wahc *wa = urb->context; + struct device *dev = &wa->usb_iface->dev; + struct wa_xfer_result *xfer_result; + u32 xfer_id; + struct wa_xfer *xfer; + u8 usb_status; + + d_fnstart(3, dev, "(%p)\n", wa); + BUG_ON(wa->dti_urb != urb); + switch (wa->dti_urb->status) { + case 0: + /* We have a xfer result buffer; check it */ + d_printf(2, dev, "DTI: xfer result %d bytes at %p\n", + urb->actual_length, urb->transfer_buffer); + d_dump(3, dev, urb->transfer_buffer, urb->actual_length); + if (wa->dti_urb->actual_length != sizeof(*xfer_result)) { + dev_err(dev, "DTI Error: xfer result--bad size " + "xfer result (%d bytes vs %zu needed)\n", + urb->actual_length, sizeof(*xfer_result)); + break; + } + xfer_result = wa->xfer_result; + if (xfer_result->hdr.bLength != sizeof(*xfer_result)) { + dev_err(dev, "DTI Error: xfer result--" + "bad header length %u\n", + xfer_result->hdr.bLength); + break; + } + if (xfer_result->hdr.bNotifyType != WA_XFER_RESULT) { + dev_err(dev, "DTI Error: xfer result--" + "bad header type 0x%02x\n", + xfer_result->hdr.bNotifyType); + break; + } + usb_status = xfer_result->bTransferStatus & 0x3f; + if (usb_status == WA_XFER_STATUS_ABORTED + || usb_status == WA_XFER_STATUS_NOT_FOUND) + /* taken care of already */ + break; + xfer_id = xfer_result->dwTransferID; + xfer = wa_xfer_get_by_id(wa, xfer_id); + if (xfer == NULL) { + /* FIXME: transaction might have been cancelled */ + dev_err(dev, "DTI Error: xfer result--" + "unknown xfer 0x%08x (status 0x%02x)\n", + xfer_id, usb_status); + break; + } + wa_xfer_result_chew(wa, xfer); + wa_xfer_put(xfer); + break; + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: /* going away! */ + dev_dbg(dev, "DTI: going down! %d\n", urb->status); + goto out; + default: + /* Unknown error */ + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + goto out; + } + if (printk_ratelimit()) + dev_err(dev, "DTI: URB error %d\n", urb->status); + break; + } + /* Resubmit the DTI URB */ + result = usb_submit_urb(wa->dti_urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "DTI Error: Could not submit DTI URB (%d), " + "resetting\n", result); + wa_reset_all(wa); + } +out: + d_fnend(3, dev, "(%p) = void\n", wa); + return; +} + +/* + * Transfer complete notification + * + * Called from the notif.c code. We get a notification on EP2 saying + * that some endpoint has some transfer result data available. We are + * about to read it. + * + * To speed up things, we always have a URB reading the DTI URB; we + * don't really set it up and start it until the first xfer complete + * notification arrives, which is what we do here. + * + * Follow up in wa_xfer_result_cb(), as that's where the whole state + * machine starts. + * + * So here we just initialize the DTI URB for reading transfer result + * notifications and also the buffer-in URB, for reading buffers. Then + * we just submit the DTI URB. + * + * @wa shall be referenced + */ +void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + struct wa_notif_xfer *notif_xfer; + const struct usb_endpoint_descriptor *dti_epd = wa->dti_epd; + + d_fnstart(4, dev, "(%p, %p)\n", wa, notif_hdr); + notif_xfer = container_of(notif_hdr, struct wa_notif_xfer, hdr); + BUG_ON(notif_hdr->bNotifyType != WA_NOTIF_TRANSFER); + + if ((0x80 | notif_xfer->bEndpoint) != dti_epd->bEndpointAddress) { + /* FIXME: hardcoded limitation, adapt */ + dev_err(dev, "BUG: DTI ep is %u, not %u (hack me)\n", + notif_xfer->bEndpoint, dti_epd->bEndpointAddress); + goto error; + } + if (wa->dti_urb != NULL) /* DTI URB already started */ + goto out; + + wa->dti_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->dti_urb == NULL) { + dev_err(dev, "Can't allocate DTI URB\n"); + goto error_dti_urb_alloc; + } + usb_fill_bulk_urb( + wa->dti_urb, wa->usb_dev, + usb_rcvbulkpipe(wa->usb_dev, 0x80 | notif_xfer->bEndpoint), + wa->xfer_result, wa->xfer_result_size, + wa_xfer_result_cb, wa); + + wa->buf_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->buf_in_urb == NULL) { + dev_err(dev, "Can't allocate BUF-IN URB\n"); + goto error_buf_in_urb_alloc; + } + usb_fill_bulk_urb( + wa->buf_in_urb, wa->usb_dev, + usb_rcvbulkpipe(wa->usb_dev, 0x80 | notif_xfer->bEndpoint), + NULL, 0, wa_buf_in_cb, wa); + result = usb_submit_urb(wa->dti_urb, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "DTI Error: Could not submit DTI URB (%d), " + "resetting\n", result); + goto error_dti_urb_submit; + } +out: + d_fnend(4, dev, "(%p, %p) = void\n", wa, notif_hdr); + return; + +error_dti_urb_submit: + usb_put_urb(wa->buf_in_urb); +error_buf_in_urb_alloc: + usb_put_urb(wa->dti_urb); + wa->dti_urb = NULL; +error_dti_urb_alloc: +error: + wa_reset_all(wa); + d_fnend(4, dev, "(%p, %p) = void\n", wa, notif_hdr); + return; +} -- cgit v1.2.2 From d09318b8ab2eabb65b6fa0dc04dab1822846eabb Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:30 +0100 Subject: wusb: add HWA host controller driver Add a driver for Wireless USB host controllers connected via USB (a Host Wire Adapter or HWA). Signed-off-by: David Vrabel --- drivers/usb/Makefile | 1 + drivers/usb/host/Kconfig | 14 + drivers/usb/host/Makefile | 1 + drivers/usb/host/hwa-hc.c | 925 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 941 insertions(+) create mode 100644 drivers/usb/host/hwa-hc.c (limited to 'drivers/usb') diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 19ede32f0df1..8b7c419b876e 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_USB_UHCI_HCD) += host/ obj-$(CONFIG_USB_SL811_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ +obj-$(CONFIG_USB_HWA_HCD) += host/ obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index d3ba351398e2..515044129bb9 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -317,3 +317,17 @@ config USB_WHCI_HCD To compile this driver a module, choose M here: the module will be called "whci-hcd". + +config USB_HWA_HCD + tristate "Host Wire Adapter (HWA) driver" + depends on USB + select USB_WUSB + select UWB_HWA + help + This driver enables you to connect Wireless USB devices to + your system using a Host Wire Adaptor USB dongle. This is an + UWB Radio Controller and WUSB Host Controller connected to + your machine via USB (specified in WUSB1.0). + + To compile this driver a module, choose M here: the module + will be called "hwa-hc". diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index c90cf865ccf3..23be22224044 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o +obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c new file mode 100644 index 000000000000..64be4d88df11 --- /dev/null +++ b/drivers/usb/host/hwa-hc.c @@ -0,0 +1,925 @@ +/* + * Host Wire Adapter: + * Driver glue, HWA-specific functions, bridges to WAHC and WUSBHC + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * The HWA driver is a simple layer that forwards requests to the WAHC + * (Wire Adater Host Controller) or WUSBHC (Wireless USB Host + * Controller) layers. + * + * Host Wire Adapter is the 'WUSB 1.0 standard' name for Wireless-USB + * Host Controller that is connected to your system via USB (a USB + * dongle that implements a USB host...). There is also a Device Wired + * Adaptor, DWA (Wireless USB hub) that uses the same mechanism for + * transferring data (it is after all a USB host connected via + * Wireless USB), we have a common layer called Wire Adapter Host + * Controller that does all the hard work. The WUSBHC (Wireless USB + * Host Controller) is the part common to WUSB Host Controllers, the + * HWA and the PCI-based one, that is implemented following the WHCI + * spec. All these layers are implemented in ../wusbcore. + * + * The main functions are hwahc_op_urb_{en,de}queue(), that pass the + * job of converting a URB to a Wire Adapter + * + * Entry points: + * + * hwahc_driver_*() Driver initialization, registration and + * teardown. + * + * hwahc_probe() New device came up, create an instance for + * it [from device enumeration]. + * + * hwahc_disconnect() Remove device instance [from device + * enumeration]. + * + * [__]hwahc_op_*() Host-Wire-Adaptor specific functions for + * starting/stopping/etc (some might be made also + * DWA). + */ +#include +#include +#include +#include +#include +#include +#include +#include "../wusbcore/wa-hc.h" +#include "../wusbcore/wusbhc.h" + +#define D_LOCAL 0 +#include + +struct hwahc { + struct wusbhc wusbhc; /* has to be 1st */ + struct wahc wa; + u8 buffer[16]; /* for misc usb transactions */ +}; + +/** + * FIXME should be wusbhc + * + * NOTE: we need to cache the Cluster ID because later...there is no + * way to get it :) + */ +static int __hwahc_set_cluster_id(struct hwahc *hwahc, u8 cluster_id) +{ + int result; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_CLUSTER_ID, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + cluster_id, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Cannot set WUSB Cluster ID to 0x%02x: %d\n", + cluster_id, result); + else + wusbhc->cluster_id = cluster_id; + dev_info(dev, "Wireless USB Cluster ID set to 0x%02x\n", cluster_id); + return result; +} + +static int __hwahc_op_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_NUM_DNTS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + interval << 8 | slots, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + +/* + * Reset a WUSB host controller and wait for it to complete doing it. + * + * @usb_hcd: Pointer to WUSB Host Controller instance. + * + */ +static int hwahc_op_reset(struct usb_hcd *usb_hcd) +{ + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + wa_nep_disarm(&hwahc->wa); + result = __wa_set_feature(&hwahc->wa, WA_RESET); + if (result < 0) { + dev_err(dev, "error commanding HC to reset: %d\n", result); + goto error_unlock; + } + d_printf(3, dev, "reset: waiting for device to change state\n"); + result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0); + if (result < 0) { + dev_err(dev, "error waiting for HC to reset: %d\n", result); + goto error_unlock; + } +error_unlock: + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; +} + +/* + * FIXME: break this function up + */ +static int hwahc_op_start(struct usb_hcd *usb_hcd) +{ + u8 addr; + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + /* Set up a Host Info WUSB Information Element */ + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + result = -ENOSPC; + mutex_lock(&wusbhc->mutex); + /* Start the numbering from the top so that the bottom + * range of the unauth addr space is used for devices, + * the top for HCs; use 0xfe - RC# */ + addr = wusb_cluster_id_get(); + if (addr == 0) + goto error_cluster_id_get; + result = __hwahc_set_cluster_id(hwahc, addr); + if (result < 0) + goto error_set_cluster_id; + + result = wa_nep_arm(&hwahc->wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "cannot listen to notifications: %d\n", result); + goto error_stop; + } + usb_hcd->uses_new_polling = 1; + usb_hcd->poll_rh = 1; + usb_hcd->state = HC_STATE_RUNNING; + result = 0; +out: + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; + +error_stop: + __wa_stop(&hwahc->wa); +error_set_cluster_id: + wusb_cluster_id_put(wusbhc->cluster_id); +error_cluster_id_get: + goto out; + +} + +/* + * FIXME: break this function up + */ +static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + /* Set up a Host Info WUSB Information Element */ + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + result = -ENOSPC; + + result = __wa_set_feature(&hwahc->wa, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error commanding HC to start: %d\n", result); + goto error_stop; + } + result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error waiting for HC to start: %d\n", result); + goto error_stop; + } + result = 0; +out: + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; + +error_stop: + result = __wa_clear_feature(&hwahc->wa, WA_ENABLE); + goto out; +} + +static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + dev_err(wusbhc->dev, "%s (%p [%p], 0x%lx) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc, *(unsigned long *) &msg); + return -ENOSYS; +} + +static int hwahc_op_resume(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc); + return -ENOSYS; +} + +static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + /* Nothing for now */ + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return; +} + +/* + * No need to abort pipes, as when this is called, all the children + * has been disconnected and that has done it [through + * usb_disable_interface() -> usb_disable_endpoint() -> + * hwahc_op_ep_disable() - >rpipe_ep_disable()]. + */ +static void hwahc_op_stop(struct usb_hcd *usb_hcd) +{ + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + wusbhc_stop(wusbhc); + wa_nep_disarm(&hwahc->wa); + result = __wa_stop(&hwahc->wa); + wusb_cluster_id_put(wusbhc->cluster_id); + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return; +} + +static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc); + return -ENOSYS; +} + +static int hwahc_op_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, + gfp_t gfp) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + return wa_urb_enqueue(&hwahc->wa, urb->ep, urb, gfp); +} + +static int hwahc_op_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, + int status) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + return wa_urb_dequeue(&hwahc->wa, urb); +} + +/* + * Release resources allocated for an endpoint + * + * If there is an associated rpipe to this endpoint, go ahead and put it. + */ +static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + rpipe_ep_disable(&hwahc->wa, ep); +} + +/* + * Set the UWB MAS allocation for the WUSB cluster + * + * @stream_index: stream to use (-1 for cancelling the allocation) + * @mas: mas bitmap to use + */ +static int __hwahc_op_bwa_set(struct wusbhc *wusbhc, s8 stream_index, + const struct uwb_mas_bm *mas) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + u8 mas_le[UWB_NUM_MAS/8]; + + /* Set the stream index */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_STREAM_IDX, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + stream_index, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot set WUSB stream index: %d\n", result); + goto out; + } + uwb_mas_bm_copy_le(mas_le, mas); + /* Set the MAS allocation */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_WUSB_MAS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + mas_le, 32, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Cannot set WUSB MAS allocation: %d\n", result); +out: + return result; +} + +/* + * Add an IE to the host's MMC + * + * @interval: See WUSB1.0[8.5.3.1] + * @repeat_cnt: See WUSB1.0[8.5.3.1] + * @handle: See WUSB1.0[8.5.3.1] + * @wuie: Pointer to the header of the WUSB IE data to add. + * MUST BE allocated in a kmalloc buffer (no stack or + * vmalloc). + * + * NOTE: the format of the WUSB IEs for MMCs are different to the + * normal MBOA MAC IEs (IE Id + Length in MBOA MAC vs. Length + + * Id in WUSB IEs). Standards...you gotta love'em. + */ +static int __hwahc_op_mmcie_add(struct wusbhc *wusbhc, u8 interval, + u8 repeat_cnt, u8 handle, + struct wuie_hdr *wuie) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_ADD_MMC_IE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + interval << 8 | repeat_cnt, + handle << 8 | iface_no, + wuie, wuie->bLength, 1000 /* FIXME: arbitrary */); +} + +/* + * Remove an IE to the host's MMC + * + * @handle: See WUSB1.0[8.5.3.1] + */ +static int __hwahc_op_mmcie_rm(struct wusbhc *wusbhc, u8 handle) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_REMOVE_MMC_IE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, handle << 8 | iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + +/* + * Update device information for a given fake port + * + * @port_idx: Fake port to which device is connected (wusbhc index, not + * USB port number). + */ +static int __hwahc_op_dev_info_set(struct wusbhc *wusbhc, + struct wusb_dev *wusb_dev) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + struct hwa_dev_info *dev_info; + int ret; + + /* fill out the Device Info buffer and send it */ + dev_info = kzalloc(sizeof(struct hwa_dev_info), GFP_KERNEL); + if (!dev_info) + return -ENOMEM; + uwb_mas_bm_copy_le(dev_info->bmDeviceAvailability, + &wusb_dev->availability); + dev_info->bDeviceAddress = wusb_dev->addr; + + /* + * If the descriptors haven't been read yet, use a default PHY + * rate of 53.3 Mbit/s only. The correct value will be used + * when this will be called again as part of the + * authentication process (which occurs after the descriptors + * have been read). + */ + if (wusb_dev->wusb_cap_descr) + dev_info->wPHYRates = wusb_dev->wusb_cap_descr->wPHYRates; + else + dev_info->wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53); + + ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_DEV_INFO, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wusb_dev->port_idx << 8 | iface_no, + dev_info, sizeof(struct hwa_dev_info), + 1000 /* FIXME: arbitrary */); + kfree(dev_info); + return ret; +} + +/* + * Set host's idea of which encryption (and key) method to use when + * talking to ad evice on a given port. + * + * If key is NULL, it means disable encryption for that "virtual port" + * (used when we disconnect). + */ +static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *key, size_t key_size, + u8 key_idx) +{ + int result = -ENOMEM; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + struct usb_key_descriptor *keyd; + size_t keyd_len; + + keyd_len = sizeof(*keyd) + key_size; + keyd = kzalloc(keyd_len, GFP_KERNEL); + if (keyd == NULL) + return -ENOMEM; + + keyd->bLength = keyd_len; + keyd->bDescriptorType = USB_DT_KEY; + keyd->tTKID[0] = (tkid >> 0) & 0xff; + keyd->tTKID[1] = (tkid >> 8) & 0xff; + keyd->tTKID[2] = (tkid >> 16) & 0xff; + memcpy(keyd->bKeyData, key, key_size); + + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + USB_DT_KEY << 8 | key_idx, + port_idx << 8 | iface_no, + keyd, keyd_len, 1000 /* FIXME: arbitrary */); + + memset(keyd, 0, sizeof(*keyd)); /* clear keys etc. */ + kfree(keyd); + return result; +} + +/* + * Set host's idea of which encryption (and key) method to use when + * talking to ad evice on a given port. + * + * If key is NULL, it means disable encryption for that "virtual port" + * (used when we disconnect). + */ +static int __hwahc_op_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *key, size_t key_size) +{ + int result = -ENOMEM; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + u8 encryption_value; + + /* Tell the host which key to use to talk to the device */ + if (key) { + u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_PTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + result = __hwahc_dev_set_key(wusbhc, port_idx, tkid, + key, key_size, key_idx); + if (result < 0) + goto error_set_key; + encryption_value = wusbhc->ccm1_etd->bEncryptionValue; + } else { + /* FIXME: this should come from wusbhc->etd[UNSECURE].value */ + encryption_value = 0; + } + + /* Set the encryption type for commmunicating with the device */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_ENCRYPTION, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + encryption_value, port_idx << 8 | iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(wusbhc->dev, "Can't set host's WUSB encryption for " + "port index %u to %s (value %d): %d\n", port_idx, + wusb_et_name(wusbhc->ccm1_etd->bEncryptionType), + wusbhc->ccm1_etd->bEncryptionValue, result); +error_set_key: + return result; +} + +/* + * Set host's GTK key + */ +static int __hwahc_op_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *key, size_t key_size) +{ + u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + return __hwahc_dev_set_key(wusbhc, 0, tkid, key, key_size, key_idx); +} + +/* + * Get the Wire Adapter class-specific descriptor + * + * NOTE: this descriptor comes with the big bundled configuration + * descriptor that includes the interfaces' and endpoints', so + * we just look for it in the cached copy kept by the USB stack. + * + * NOTE2: We convert LE fields to CPU order. + */ +static int wa_fill_descr(struct wahc *wa) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + char *itr; + struct usb_device *usb_dev = wa->usb_dev; + struct usb_descriptor_header *hdr; + struct usb_wa_descriptor *wa_descr; + size_t itr_size, actconfig_idx; + + actconfig_idx = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[actconfig_idx]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + while (itr_size >= sizeof(*hdr)) { + hdr = (struct usb_descriptor_header *) itr; + d_printf(3, dev, "Extra device descriptor: " + "type %02x/%u bytes @ %zu (%zu left)\n", + hdr->bDescriptorType, hdr->bLength, + (itr - usb_dev->rawdescriptors[actconfig_idx]), + itr_size); + if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER) + goto found; + itr += hdr->bLength; + itr_size -= hdr->bLength; + } + dev_err(dev, "cannot find Wire Adapter Class descriptor\n"); + return -ENODEV; + +found: + result = -EINVAL; + if (hdr->bLength > itr_size) { /* is it available? */ + dev_err(dev, "incomplete Wire Adapter Class descriptor " + "(%zu bytes left, %u needed)\n", + itr_size, hdr->bLength); + goto error; + } + if (hdr->bLength < sizeof(*wa->wa_descr)) { + dev_err(dev, "short Wire Adapter Class descriptor\n"); + goto error; + } + wa->wa_descr = wa_descr = (struct usb_wa_descriptor *) hdr; + /* Make LE fields CPU order */ + wa_descr->bcdWAVersion = le16_to_cpu(wa_descr->bcdWAVersion); + wa_descr->wNumRPipes = le16_to_cpu(wa_descr->wNumRPipes); + wa_descr->wRPipeMaxBlock = le16_to_cpu(wa_descr->wRPipeMaxBlock); + if (wa_descr->bcdWAVersion > 0x0100) + dev_warn(dev, "Wire Adapter v%d.%d newer than groked v1.0\n", + wa_descr->bcdWAVersion & 0xff00 >> 8, + wa_descr->bcdWAVersion & 0x00ff); + result = 0; +error: + return result; +} + +static struct hc_driver hwahc_hc_driver = { + .description = "hwa-hcd", + .product_desc = "Wireless USB HWA host controller", + .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd), + .irq = NULL, /* FIXME */ + .flags = HCD_USB2, /* FIXME */ + .reset = hwahc_op_reset, + .start = hwahc_op_start, + .pci_suspend = hwahc_op_suspend, + .pci_resume = hwahc_op_resume, + .stop = hwahc_op_stop, + .get_frame_number = hwahc_op_get_frame_number, + .urb_enqueue = hwahc_op_urb_enqueue, + .urb_dequeue = hwahc_op_urb_dequeue, + .endpoint_disable = hwahc_op_endpoint_disable, + + .hub_status_data = wusbhc_rh_status_data, + .hub_control = wusbhc_rh_control, + .bus_suspend = wusbhc_rh_suspend, + .bus_resume = wusbhc_rh_resume, + .start_port_reset = wusbhc_rh_start_port_reset, +}; + +static int hwahc_security_create(struct hwahc *hwahc) +{ + int result; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct usb_device *usb_dev = hwahc->wa.usb_dev; + struct device *dev = &usb_dev->dev; + struct usb_security_descriptor *secd; + struct usb_encryption_descriptor *etd; + void *itr, *top; + size_t itr_size, needed, bytes; + u8 index; + char buf[64]; + + /* Find the host's security descriptors in the config descr bundle */ + index = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[index]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + top = itr + itr_size; + result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index], + le16_to_cpu(usb_dev->actconfig->desc.wTotalLength), + USB_DT_SECURITY, (void **) &secd); + if (result == -1) { + dev_warn(dev, "BUG? WUSB host has no security descriptors\n"); + return 0; + } + needed = sizeof(*secd); + if (top - (void *)secd < needed) { + dev_err(dev, "BUG? Not enough data to process security " + "descriptor header (%zu bytes left vs %zu needed)\n", + top - (void *) secd, needed); + return 0; + } + needed = le16_to_cpu(secd->wTotalLength); + if (top - (void *)secd < needed) { + dev_err(dev, "BUG? Not enough data to process security " + "descriptors (%zu bytes left vs %zu needed)\n", + top - (void *) secd, needed); + return 0; + } + /* Walk over the sec descriptors and store CCM1's on wusbhc */ + itr = (void *) secd + sizeof(*secd); + top = (void *) secd + le16_to_cpu(secd->wTotalLength); + index = 0; + bytes = 0; + while (itr < top) { + etd = itr; + if (top - itr < sizeof(*etd)) { + dev_err(dev, "BUG: bad host security descriptor; " + "not enough data (%zu vs %zu left)\n", + top - itr, sizeof(*etd)); + break; + } + if (etd->bLength < sizeof(*etd)) { + dev_err(dev, "BUG: bad host encryption descriptor; " + "descriptor is too short " + "(%zu vs %zu needed)\n", + (size_t)etd->bLength, sizeof(*etd)); + break; + } + itr += etd->bLength; + bytes += snprintf(buf + bytes, sizeof(buf) - bytes, + "%s (0x%02x) ", + wusb_et_name(etd->bEncryptionType), + etd->bEncryptionValue); + wusbhc->ccm1_etd = etd; + } + dev_info(dev, "supported encryption types: %s\n", buf); + if (wusbhc->ccm1_etd == NULL) { + dev_err(dev, "E: host doesn't support CCM-1 crypto\n"); + return 0; + } + /* Pretty print what we support */ + return 0; +} + +static void hwahc_security_release(struct hwahc *hwahc) +{ + /* nothing to do here so far... */ +} + +static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface) +{ + int result; + struct device *dev = &iface->dev; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct wahc *wa = &hwahc->wa; + struct usb_device *usb_dev = interface_to_usbdev(iface); + + wa->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */ + wa->usb_iface = usb_get_intf(iface); + wusbhc->dev = dev; + wusbhc->uwb_rc = uwb_rc_get_by_grandpa(iface->dev.parent); + if (wusbhc->uwb_rc == NULL) { + result = -ENODEV; + dev_err(dev, "Cannot get associated UWB Host Controller\n"); + goto error_rc_get; + } + result = wa_fill_descr(wa); /* Get the device descriptor */ + if (result < 0) + goto error_fill_descriptor; + if (wa->wa_descr->bNumPorts > USB_MAXCHILDREN) { + dev_err(dev, "FIXME: USB_MAXCHILDREN too low for WUSB " + "adapter (%u ports)\n", wa->wa_descr->bNumPorts); + wusbhc->ports_max = USB_MAXCHILDREN; + } else { + wusbhc->ports_max = wa->wa_descr->bNumPorts; + } + wusbhc->mmcies_max = wa->wa_descr->bNumMMCIEs; + wusbhc->start = __hwahc_op_wusbhc_start; + wusbhc->stop = __hwahc_op_wusbhc_stop; + wusbhc->mmcie_add = __hwahc_op_mmcie_add; + wusbhc->mmcie_rm = __hwahc_op_mmcie_rm; + wusbhc->dev_info_set = __hwahc_op_dev_info_set; + wusbhc->bwa_set = __hwahc_op_bwa_set; + wusbhc->set_num_dnts = __hwahc_op_set_num_dnts; + wusbhc->set_ptk = __hwahc_op_set_ptk; + wusbhc->set_gtk = __hwahc_op_set_gtk; + result = hwahc_security_create(hwahc); + if (result < 0) { + dev_err(dev, "Can't initialize security: %d\n", result); + goto error_security_create; + } + wa->wusb = wusbhc; /* FIXME: ugly, need to fix */ + result = wusbhc_create(&hwahc->wusbhc); + if (result < 0) { + dev_err(dev, "Can't create WUSB HC structures: %d\n", result); + goto error_wusbhc_create; + } + result = wa_create(&hwahc->wa, iface); + if (result < 0) + goto error_wa_create; + return 0; + +error_wa_create: + wusbhc_destroy(&hwahc->wusbhc); +error_wusbhc_create: + /* WA Descr fill allocs no resources */ +error_security_create: +error_fill_descriptor: + uwb_rc_put(wusbhc->uwb_rc); +error_rc_get: + usb_put_intf(iface); + usb_put_dev(usb_dev); + return result; +} + +static void hwahc_destroy(struct hwahc *hwahc) +{ + struct wusbhc *wusbhc = &hwahc->wusbhc; + + d_fnstart(1, NULL, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + __wa_destroy(&hwahc->wa); + wusbhc_destroy(&hwahc->wusbhc); + hwahc_security_release(hwahc); + hwahc->wusbhc.dev = NULL; + uwb_rc_put(wusbhc->uwb_rc); + usb_put_intf(hwahc->wa.usb_iface); + usb_put_dev(hwahc->wa.usb_dev); + mutex_unlock(&wusbhc->mutex); + d_fnend(1, NULL, "(hwahc %p) = void\n", hwahc); +} + +static void hwahc_init(struct hwahc *hwahc) +{ + wa_init(&hwahc->wa); +} + +static int hwahc_probe(struct usb_interface *usb_iface, + const struct usb_device_id *id) +{ + int result; + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct hwahc *hwahc; + struct device *dev = &usb_iface->dev; + + d_fnstart(4, dev, "(%p, %p)\n", usb_iface, id); + result = -ENOMEM; + usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa"); + if (usb_hcd == NULL) { + dev_err(dev, "unable to allocate instance\n"); + goto error_alloc; + } + usb_hcd->wireless = 1; + usb_hcd->flags |= HCD_FLAG_SAW_IRQ; + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + hwahc = container_of(wusbhc, struct hwahc, wusbhc); + hwahc_init(hwahc); + result = hwahc_create(hwahc, usb_iface); + if (result < 0) { + dev_err(dev, "Cannot initialize internals: %d\n", result); + goto error_hwahc_create; + } + result = usb_add_hcd(usb_hcd, 0, 0); + if (result < 0) { + dev_err(dev, "Cannot add HCD: %d\n", result); + goto error_add_hcd; + } + result = wusbhc_b_create(&hwahc->wusbhc); + if (result < 0) { + dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result); + goto error_wusbhc_b_create; + } + d_fnend(4, dev, "(%p, %p) = 0\n", usb_iface, id); + return 0; + +error_wusbhc_b_create: + usb_remove_hcd(usb_hcd); +error_add_hcd: + hwahc_destroy(hwahc); +error_hwahc_create: + usb_put_hcd(usb_hcd); +error_alloc: + d_fnend(4, dev, "(%p, %p) = %d\n", usb_iface, id, result); + return result; +} + +static void hwahc_disconnect(struct usb_interface *usb_iface) +{ + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct hwahc *hwahc; + + usb_hcd = usb_get_intfdata(usb_iface); + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + d_fnstart(1, NULL, "(hwahc %p [usb_iface %p])\n", hwahc, usb_iface); + wusbhc_b_destroy(&hwahc->wusbhc); + usb_remove_hcd(usb_hcd); + hwahc_destroy(hwahc); + usb_put_hcd(usb_hcd); + d_fnend(1, NULL, "(hwahc %p [usb_iface %p]) = void\n", hwahc, + usb_iface); +} + +/** USB device ID's that we handle */ +static struct usb_device_id hwahc_id_table[] = { + /* FIXME: use class labels for this */ + { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), }, + {}, +}; +MODULE_DEVICE_TABLE(usb, hwahc_id_table); + +static struct usb_driver hwahc_driver = { + .name = "hwa-hc", + .probe = hwahc_probe, + .disconnect = hwahc_disconnect, + .id_table = hwahc_id_table, +}; + +static int __init hwahc_driver_init(void) +{ + int result; + result = usb_register(&hwahc_driver); + if (result < 0) { + printk(KERN_ERR "WA-CDS: Cannot register USB driver: %d\n", + result); + goto error_usb_register; + } + return 0; + +error_usb_register: + return result; + +} +module_init(hwahc_driver_init); + +static void __exit hwahc_driver_exit(void) +{ + usb_deregister(&hwahc_driver); +} +module_exit(hwahc_driver_exit); + + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.2 From f51c23baedf191734364d3aae034d8c9c4cd8cf8 Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:31 +0100 Subject: wusb: fix error path for wusb_set_dev_addr() Error path for wusb_set_dev_addr() was handled incorrectly. Fix it by considering error only when return value is negative. Signed-off-by: Anderson Lizardo Signed-off-by: David Vrabel --- drivers/usb/wusbcore/devconnect.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c index f05f9b4d7751..30d7020e1c50 100644 --- a/drivers/usb/wusbcore/devconnect.c +++ b/drivers/usb/wusbcore/devconnect.c @@ -239,8 +239,8 @@ static struct wusb_dev *wusbhc_cack_add(struct wusbhc *wusbhc, "port %u\n", dev_addr, port_idx); result = wusb_set_dev_addr(wusbhc, wusb_dev, dev_addr); - if (result) - return NULL; + if (result < 0) + return NULL; } wusb_dev->entry_ts = jiffies; list_add_tail(&wusb_dev->cack_node, &wusbhc->cack_list); @@ -1301,7 +1301,7 @@ int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, u8 addr) wusb_dev->addr = addr; result = wusbhc->dev_info_set(wusbhc, wusb_dev); - if (result) + if (result < 0) dev_err(wusbhc->dev, "device %d: failed to set device " "address\n", wusb_dev->port_idx); else -- cgit v1.2.2 From b0a81328c287052cedf28e06d9b4648ad10b72d7 Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:32 +0100 Subject: wusb: fix bmRequestType for Abort RPipe request WUSB 1.0 (Table 8-4) mentions that Abort RPipe requests must have bmRequestType equal to 0x25, although current implementation sets bmRequestType to 0xa5. This patch fixes this typo. Signed-off-by: Anderson Lizardo Signed-off-by: David Vrabel --- drivers/usb/wusbcore/wa-rpipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c index bfe3752e7c9a..f18e4aae66e9 100644 --- a/drivers/usb/wusbcore/wa-rpipe.c +++ b/drivers/usb/wusbcore/wa-rpipe.c @@ -548,7 +548,7 @@ void rpipe_ep_disable(struct wahc *wa, struct usb_host_endpoint *ep) result = usb_control_msg( wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), USB_REQ_RPIPE_ABORT, - USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); if (result < 0 && result != -ENODEV /* dev is gone */) d_printf(1, dev, "(wa %p rpipe %u): abort failed: %d\n", -- cgit v1.2.2 From b60066c141997ac2e4ef08459b75638ae86ae781 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:40 +0100 Subject: uwb: add symlinks in sysfs between radio controllers and PALs Add a facility for PALs to have symlinks to their radio controller (and vice-versa) and make WUSB host controllers use this. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/pal.c | 3 +++ drivers/usb/wusbcore/wusbhc.c | 16 +++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/pal.c b/drivers/usb/wusbcore/pal.c index cc126b444734..7cc51e9905cf 100644 --- a/drivers/usb/wusbcore/pal.c +++ b/drivers/usb/wusbcore/pal.c @@ -26,6 +26,9 @@ int wusbhc_pal_register(struct wusbhc *wusbhc) { uwb_pal_init(&wusbhc->pal); + wusbhc->pal.name = "wusbhc"; + wusbhc->pal.device = wusbhc->usb_hcd.self.controller; + return uwb_pal_register(wusbhc->uwb_rc, &wusbhc->pal); } diff --git a/drivers/usb/wusbcore/wusbhc.c b/drivers/usb/wusbcore/wusbhc.c index 1149b1e59c86..07c63a31c799 100644 --- a/drivers/usb/wusbcore/wusbhc.c +++ b/drivers/usb/wusbcore/wusbhc.c @@ -192,13 +192,8 @@ int wusbhc_create(struct wusbhc *wusbhc) result = wusbhc_sec_create(wusbhc); if (result < 0) goto error_sec_create; - result = wusbhc_pal_register(wusbhc); - if (result < 0) - goto error_pal_register; return 0; -error_pal_register: - wusbhc_sec_destroy(wusbhc); error_sec_create: wusbhc_rh_destroy(wusbhc); error_rh_create: @@ -235,7 +230,14 @@ int wusbhc_b_create(struct wusbhc *wusbhc) dev_err(dev, "Cannot register WUSBHC attributes: %d\n", result); goto error_create_attr_group; } - /* Yep, I plan to add stuff here... */ + + result = wusbhc_pal_register(wusbhc); + if (result < 0) + goto error_pal_register; + return 0; + +error_pal_register: + sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); error_create_attr_group: return result; } @@ -243,13 +245,13 @@ 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_pal_unregister(wusbhc); wusbhc_sec_destroy(wusbhc); wusbhc_rh_destroy(wusbhc); wusbhc_devconnect_destroy(wusbhc); -- cgit v1.2.2 From f1fa035f2bcb2b03cb90249e05ec2ae6927a7302 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Wed, 17 Sep 2008 16:34:42 +0100 Subject: wusb: wusb-cbaf (CBA driver) sysfs ABI simplification Simplify the sysfs ABI of the wusb-cbaf (Cable Based Association) driver: use one value per file and cause the write of the CHID to fetch the CDID (instead of requiring a separate read). Update the example wusb-cbaf script to work with this revised ABI. Signed-off-by: Felipe Zimmerle Signed-off-by: David Vrabel --- drivers/usb/wusbcore/Kconfig | 23 ++ drivers/usb/wusbcore/Makefile | 8 +- drivers/usb/wusbcore/cbaf.c | 521 +++++++++++++++++++++++------------------- 3 files changed, 317 insertions(+), 235 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig index add077e5c5d0..4ea76693ad46 100644 --- a/drivers/usb/wusbcore/Kconfig +++ b/drivers/usb/wusbcore/Kconfig @@ -15,3 +15,26 @@ config USB_WUSB To compile this support select Y (built in). It is safe to select even if you don't have the hardware. + +config USB_WUSB_CBAF + tristate "Support WUSB Cable Based Association (CBA)" + depends on USB + help + Some WUSB devices support Cable Based Association. It's used to + enable the secure communication between the host and the + device. + + Enable this option if your WUSB device must to be connected + via wired USB before establishing a wireless link. + + It is safe to select even if you don't have a compatible + hardware. + +config USB_WUSB_CBAF_DEBUG + bool "Enable CBA debug messages" + depends on USB_WUSB_CBAF + help + Say Y here if you want the CBA to produce a bunch of debug messages + to the system log. Select this if you are having a problem with + CBA support and want to see more of what is going on. + diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile index 7a4d00724039..75f1ade66258 100644 --- a/drivers/usb/wusbcore/Makefile +++ b/drivers/usb/wusbcore/Makefile @@ -1,5 +1,7 @@ -obj-$(CONFIG_USB_WUSB) += wusbcore.o wusb-cbaf.o +obj-$(CONFIG_USB_WUSB) += wusbcore.o obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o +obj-$(CONFIG_USB_WUSB_CBAF) += wusb-cbaf.o + wusbcore-objs := \ crypto.o \ @@ -18,3 +20,7 @@ wusb-wa-objs := wa-hc.o \ wa-nep.o \ wa-rpipe.o \ wa-xfer.o + +ifeq ($(CONFIG_USB_WUSB_CBAF_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/usb/wusbcore/cbaf.c b/drivers/usb/wusbcore/cbaf.c index 584eabe274b3..ab4788d1785a 100644 --- a/drivers/usb/wusbcore/cbaf.c +++ b/drivers/usb/wusbcore/cbaf.c @@ -4,6 +4,7 @@ * * Copyright (C) 2006 Intel Corporation * Inaky Perez-Gonzalez + * 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 @@ -20,14 +21,13 @@ * 02110-1301, USA. * * - * WUSB devices have to be paired (authenticated in WUSB lingo) so + * 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 Authentication, devices - * that can connect via wired or wireless USB. First time you plug - * them with a cable, pairing is done between host and device and - * subsequent times, you can connect wirelessly without having to - * pair. That's the idea. + * 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 @@ -42,56 +42,49 @@ * * The process goes like this: * - * 1. device plugs, cbaf is loaded, notifications happen + * 1. Device plugs, cbaf is loaded, notifications happen. * - * 2. the connection manager sees a device with CBAF capability (the - * wusb_{host_info,cdid,cc} files are in /sys/device/blah/OURDEVICE). + * 2. The connection manager (CM) sees a device with CBAF capability + * (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE). * - * 3. CM (connection manager) writes the CHID (host ID) and a host - * name into the wusb_host_info file. This gets sent to the device. + * 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. CM cats the wusb_cdid file; this asks the device if it has any - * CDID associated to the CHDI we just wrote before. If it does, it - * is printed, along with the device 'friendly name' and the band - * groups the device supports. + * 4. The CM can verify that the device's supported band groups + * (wusb_device_band_groups) are compatible with the host. * - * 5. CM looks up its database + * 5. The CM reads the wusb_cdid file. * - * 5.1 If it has a matching CHID,CDID entry, the device has been - * authorized before (paired). Now we can optionally ask the user - * if he wants to allow the device to connect. Then we generate a - * new CDID and CK, send it to the device and update the database - * (writing to the wusb_cc file so they are uploaded to the device). + * 6. The CM looks up its database * - * 5.2 If the CDID is zero (or we didn't find a matching CDID in our - * database), we assume the device is not known. We ask the user - * if s/he wants to allow the device to be connected wirelessly - * to the system. If nope, nothing else is done (FIXME: maybe - * send a zero CDID to clean up our CHID?). If yes, we generate - * random CDID and CKs (and write them to the wusb_cc file so - * they are uploaded to the device). + * 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. device is unplugged + * 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). * - * When the device tries to connect wirelessly, it will present it's - * CDID to the WUSB host controller with ID CHID, which will query the - * database. If found, the host will (with a 4way 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 crypted. + * CMD may choose to prompt the user before associating with a new + * device. * + * 7. Device is unplugged. * - * NOTES ABOUT THE IMPLEMENTATION + * 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. * - * The descriptors sent back and forth use this horrible format from - * hell on which each field is actually a field ID, field length and - * then the field itself. How stupid can that get, taking into account - * the structures are defined by the spec?? oh well. - * - * - * FIXME: we don't provide a way to tell the device the pairing failed - * (ie: send a CC_DATA_FAIL). Should add some day. + * References: + * [WUSB-AM] Association Models Supplement to the Certified Wireless + * Universal Serial Bus Specification, version 1.0. */ #include #include @@ -105,9 +98,7 @@ #include #include -#undef D_LOCAL -#define D_LOCAL 6 -#include +#define CBA_NAME_LEN 0x40 /* [WUSB-AM] table 4-7 */ /* An instance of a Cable-Based-Association-Framework device */ struct cbaf { @@ -116,24 +107,27 @@ struct cbaf { void *buffer; size_t buffer_size; - struct wusb_ckhdid chid;/* Host Information */ - char host_name[65]; /* max length: - Assoc Models Suplement 1.0[T4-7] */ + struct wusb_ckhdid chid; + char host_name[CBA_NAME_LEN]; u16 host_band_groups; - struct wusb_ckhdid cdid;/* Device Information */ - char device_name[65]; /* max length: - Assoc Models Suplement 1.0[T4-7] */ + struct wusb_ckhdid cdid; + char device_name[CBA_NAME_LEN]; u16 device_band_groups; - struct wusb_ckhdid ck; /* Connection Key */ + + struct wusb_ckhdid ck; }; /* * Verify that a CBAF USB-interface has what we need * - * (like we care, we are going to fail the enumeration if not :) + * According to [WUSB-AM], CBA devices should provide at least two + * interfaces: + * - RETRIEVE_HOST_INFO + * - ASSOCIATE * - * FIXME: ugly function, need to split + * If the device doesn't provide these interfaces, we do not know how + * to deal with it. */ static int cbaf_check(struct cbaf *cbaf) { @@ -143,8 +137,7 @@ static int cbaf_check(struct cbaf *cbaf) struct wusb_cbaf_assoc_request *assoc_request; size_t assoc_size; void *itr, *top; - unsigned ar_index; - int ar_rhi_idx = -1, ar_assoc_idx = -1; + int ar_rhi = 0, ar_assoc = 0; result = usb_control_msg( cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), @@ -153,93 +146,91 @@ static int cbaf_check(struct cbaf *cbaf) 0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, cbaf->buffer, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); if (result < 0) { - dev_err(dev, "cannot get available association types: %d\n", + dev_err(dev, "Cannot get available association types: %d\n", result); - goto error_get_assoc_types; + return result; } + assoc_info = cbaf->buffer; if (result < sizeof(*assoc_info)) { - dev_err(dev, "not enough data to decode association info " + dev_err(dev, "Not enough data to decode association info " "header (%zu vs %zu bytes required)\n", (size_t)result, sizeof(*assoc_info)); - goto error_bad_header; + return result; } + assoc_size = le16_to_cpu(assoc_info->Length); if (result < assoc_size) { - dev_err(dev, "not enough data to decode association info " + dev_err(dev, "Not enough data to decode association info " "(%zu vs %zu bytes required)\n", (size_t)assoc_size, sizeof(*assoc_info)); - goto error_bad_data; + 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. */ - ar_index = 0; itr = cbaf->buffer + sizeof(*assoc_info); top = cbaf->buffer + assoc_size; - d_printf(1, dev, "Found %u association requests (%zu bytes)\n", + dev_dbg(dev, "Found %u association requests (%zu bytes)\n", assoc_info->NumAssociationRequests, assoc_size); + while (itr < top) { u16 ar_type, ar_subtype; u32 ar_size; const char *ar_name; assoc_request = itr; + if (top - itr < sizeof(*assoc_request)) { - dev_err(dev, "not enough data to decode associaton " + dev_err(dev, "Not enough data to decode associaton " "request (%zu vs %zu bytes needed)\n", top - itr, sizeof(*assoc_request)); break; } + ar_type = le16_to_cpu(assoc_request->AssociationTypeId); ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId); ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize); + ar_name = "unknown"; + switch (ar_type) { case AR_TYPE_WUSB: - /* Verify we have what is mandated by AMS1.0 */ + /* 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_idx = ar_index; + ar_name = "RETRIEVE_HOST_INFO"; + ar_rhi = 1; break; case AR_TYPE_WUSB_ASSOCIATE: /* send assoc data */ - ar_name = "associate"; - ar_assoc_idx = ar_index; + ar_name = "ASSOCIATE"; + ar_assoc = 1; break; - default: - ar_name = "unknown"; }; break; - default: - ar_name = "unknown"; }; - d_printf(1, dev, "association request #%02u: 0x%04x/%04x " + + 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); - ar_index++; } - if (ar_rhi_idx == -1) { + + if (!ar_rhi) { dev_err(dev, "Missing RETRIEVE_HOST_INFO association " "request\n"); - goto error_bad_reqs; + return -EINVAL; } - if (ar_assoc_idx == -1) { + if (!ar_assoc) { dev_err(dev, "Missing ASSOCIATE association request\n"); - goto error_bad_reqs; + return -EINVAL; } - return 0; -error_bad_header: -error_bad_data: -error_bad_reqs: -error_get_assoc_types: - return -EINVAL; + return 0; } static const struct wusb_cbaf_host_info cbaf_host_info_defaults = { @@ -256,6 +247,7 @@ static const struct wusb_cbaf_host_info cbaf_host_info_defaults = { 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; @@ -263,11 +255,11 @@ static int cbaf_send_host_info(struct cbaf *cbaf) *hi = cbaf_host_info_defaults; hi->CHID = cbaf->chid; hi->LangID = 0; /* FIXME: I guess... */ - strncpy(hi->HostFriendlyName, cbaf->host_name, - hi->HostFriendlyName_hdr.len); - hi->HostFriendlyName_hdr.len = - cpu_to_le16(strlen(hi->HostFriendlyName)); - hi_size = sizeof(*hi) + strlen(hi->HostFriendlyName); + 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, @@ -276,8 +268,47 @@ static int cbaf_send_host_info(struct cbaf *cbaf) hi, hi_size, 1000 /* FIXME: arbitrary */); } -/* Show current CHID info we have set from user space */ -static ssize_t cbaf_wusb_host_info_show(struct device *dev, +/* + * Get device's information (CDID) associated to CHID + * + * The device will return it's information (CDID, name, bandgroups) + * associated to the CHID we have set before, or 0 CDID and default + * name and bandgroup if no CHID set or unknown. + */ +static int cbaf_cdid_get(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_device_info *di; + size_t needed; + + di = cbaf->buffer; + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_REQUEST, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot request device information: %d\n", result); + return result; + } + + needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length); + if (result < needed) { + dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs " + "%zu bytes needed)\n", (size_t)result, needed); + return result; + } + + strlcpy(cbaf->device_name, di->DeviceFriendlyName, CBA_NAME_LEN); + cbaf->cdid = di->CDID; + cbaf->device_band_groups = le16_to_cpu(di->BandGroups); + + return 0; +} + +static ssize_t cbaf_wusb_chid_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -286,17 +317,10 @@ static ssize_t cbaf_wusb_host_info_show(struct device *dev, char pr_chid[WUSB_CKHDID_STRSIZE]; ckhdid_printf(pr_chid, sizeof(pr_chid), &cbaf->chid); - return scnprintf(buf, PAGE_SIZE, "CHID: %s\nName: %s\n", - pr_chid, cbaf->host_name); + return scnprintf(buf, PAGE_SIZE, "%s\n", pr_chid); } -/* - * Get a host info CHID from user space and send it to the device. - * - * The user can recover a CC from the device associated to that CHID - * by cat'ing wusb_connection_context. - */ -static ssize_t cbaf_wusb_host_info_store(struct device *dev, +static ssize_t cbaf_wusb_chid_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { @@ -308,8 +332,7 @@ static ssize_t cbaf_wusb_host_info_store(struct device *dev, "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " - "%02hhx %02hhx %02hhx %02hhx " - "%04hx %64s\n", + "%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], @@ -317,24 +340,79 @@ static ssize_t cbaf_wusb_host_info_store(struct device *dev, &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], - &cbaf->host_band_groups, cbaf->host_name); - if (result != 18) { - dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits, " - "a 16 bit hex band group mask " - "and a host name, got only %d)\n", (int)result); + &cbaf->chid.data[14], &cbaf->chid.data[15]); + + if (result != 16) return -EINVAL; - } + result = cbaf_send_host_info(cbaf); if (result < 0) - dev_err(dev, "Couldn't send host information to device: %d\n", - (int)result); - else - d_printf(1, dev, "HI sent, wusb_cc can be read now\n"); - return result < 0 ? result : size; + 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_info, 0600, cbaf_wusb_host_info_show, - cbaf_wusb_host_info_store); + +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, @@ -344,77 +422,72 @@ static const struct wusb_cbaf_device_info cbaf_device_info_defaults = { .DeviceFriendlyName_hdr = WUSB_AR_DeviceFriendlyName, }; -/* - * 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) +static ssize_t cbaf_wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) { - int result; - struct device *dev = &cbaf->usb_iface->dev; - struct wusb_cbaf_device_info *di; - size_t needed, dev_name_size; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_cdid[WUSB_CKHDID_STRSIZE]; - di = cbaf->buffer; - result = usb_control_msg( - cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), - CBAF_REQ_GET_ASSOCIATION_REQUEST, - USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, - 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, - di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); - if (result < 0) { - dev_err(dev, "Cannot request device information: %d\n", result); - goto error_req_di; - } - 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); - goto error_bad_di; - } - cbaf->cdid = di->CDID; - dev_name_size = le16_to_cpu(di->DeviceFriendlyName_hdr.len); - dev_name_size = dev_name_size > 65 - 1 ? 65 - 1 : dev_name_size; - memcpy(cbaf->device_name, di->DeviceFriendlyName, dev_name_size); - cbaf->device_name[dev_name_size] = 0; - cbaf->device_band_groups = le16_to_cpu(di->BandGroups); - result = 0; -error_req_di: -error_bad_di: - return result; + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid); + return scnprintf(buf, PAGE_SIZE, "%s\n", pr_cdid); } -/* - * Get device information and print it to sysfs - * - * See cbaf_cdid_get() - */ -static ssize_t cbaf_wusb_cdid_show(struct device *dev, - struct device_attribute *attr, char *buf) +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); - char pr_cdid[WUSB_CKHDID_STRSIZE]; + struct wusb_ckhdid cdid; - result = cbaf_cdid_get(cbaf); - if (result < 0) { - dev_err(dev, "Cannot read device information: %d\n", - (int)result); - goto error_get_di; - } - ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid); - result = scnprintf(buf, PAGE_SIZE, - "CDID: %s\nName: %s\nBand_groups: 0x%04x\n", - pr_cdid, cbaf->device_name, - cbaf->device_band_groups); -error_get_di: - return result; + 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_cdid, 0600, cbaf_wusb_cdid_show, NULL); +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, @@ -435,9 +508,7 @@ static const struct wusb_cbaf_cc_data_fail cbaf_cc_data_fail_defaults = { }; /* - * Send a new CC to the device - * - * So we update the CK and send the whole thing to the device + * Send a new CC to the device. */ static int cbaf_cc_upload(struct cbaf *cbaf) { @@ -452,30 +523,25 @@ static int cbaf_cc_upload(struct cbaf *cbaf) ccd->CDID = cbaf->cdid; ccd->CK = cbaf->ck; ccd->BandGroups = cpu_to_le16(cbaf->host_band_groups); + + dev_dbg(dev, "Trying to upload CC:\n"); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID); + dev_dbg(dev, " CHID %s\n", pr_cdid); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID); + dev_dbg(dev, " CDID %s\n", pr_cdid); + dev_dbg(dev, " Bandgroups 0x%04x\n", cbaf->host_band_groups); + result = usb_control_msg( cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), CBAF_REQ_SET_ASSOCIATION_RESPONSE, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, ccd, sizeof(*ccd), 1000 /* FIXME: arbitrary */); - d_printf(1, dev, "Uploaded CC:\n"); - ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID); - d_printf(1, dev, " CHID %s\n", pr_cdid); - ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID); - d_printf(1, dev, " CDID %s\n", pr_cdid); - ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CK); - d_printf(1, dev, " CK %s\n", pr_cdid); - d_printf(1, dev, " bandgroups 0x%04x\n", cbaf->host_band_groups); + return result; } -/* - * Send a new CC to the device - * - * We take the CDID and CK from user space, the rest from the info we - * set with host_info. - */ -static ssize_t cbaf_wusb_cc_store(struct device *dev, +static ssize_t cbaf_wusb_ck_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { @@ -484,23 +550,10 @@ static ssize_t cbaf_wusb_cc_store(struct device *dev, struct cbaf *cbaf = usb_get_intfdata(iface); result = sscanf(buf, - "CDID: %02hhx %02hhx %02hhx %02hhx " - "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " - "%02hhx %02hhx %02hhx %02hhx\n" - "CK: %02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " - "%02hhx %02hhx %02hhx %02hhx\n", - &cbaf->cdid.data[0] , &cbaf->cdid.data[1], - &cbaf->cdid.data[2] , &cbaf->cdid.data[3], - &cbaf->cdid.data[4] , &cbaf->cdid.data[5], - &cbaf->cdid.data[6] , &cbaf->cdid.data[7], - &cbaf->cdid.data[8] , &cbaf->cdid.data[9], - &cbaf->cdid.data[10], &cbaf->cdid.data[11], - &cbaf->cdid.data[12], &cbaf->cdid.data[13], - &cbaf->cdid.data[14], &cbaf->cdid.data[15], - + "%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], @@ -509,25 +562,25 @@ static ssize_t cbaf_wusb_cc_store(struct device *dev, &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 != 32) { - dev_err(dev, "Unrecognized CHID/CK (need 32 8-bit " - "hex digits, got only %d)\n", (int)result); + if (result != 16) return -EINVAL; - } + result = cbaf_cc_upload(cbaf); if (result < 0) - dev_err(dev, "Couldn't upload connection context: %d\n", - (int)result); - else - d_printf(1, dev, "Connection context uploaded\n"); - return result < 0 ? result : size; + return result; + + return size; } -static DEVICE_ATTR(wusb_cc, 0600, NULL, cbaf_wusb_cc_store); +static DEVICE_ATTR(wusb_ck, 0600, NULL, cbaf_wusb_ck_store); static struct attribute *cbaf_dev_attrs[] = { - &dev_attr_wusb_host_info.attr, + &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_cc.attr, + &dev_attr_wusb_device_name.attr, + &dev_attr_wusb_device_band_groups.attr, + &dev_attr_wusb_ck.attr, NULL, }; @@ -539,32 +592,33 @@ static struct attribute_group cbaf_dev_attr_group = { static int cbaf_probe(struct usb_interface *iface, const struct usb_device_id *id) { - int result; struct cbaf *cbaf; struct device *dev = &iface->dev; + int result = -ENOMEM; - result = -ENOMEM; cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL); - if (cbaf == NULL) { - dev_err(dev, "Unable to allocate instance\n"); + 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) + 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); - d_printf(2, dev, "CBA attached\n"); return 0; error_create_group: @@ -587,7 +641,6 @@ static void cbaf_disconnect(struct usb_interface *iface) /* paranoia: clean up crypto keys */ memset(cbaf, 0, sizeof(*cbaf)); kfree(cbaf); - d_printf(1, dev, "CBA detached\n"); } static struct usb_device_id cbaf_id_table[] = { -- cgit v1.2.2 From 6a7c3e464eb75310d011a6f2ea2953e6f5d91d55 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:43 +0100 Subject: uwb: depend on EXPERIMENTAL The UWB stack has some sysfs APIs that will change thus it's best marked as EXPERIMENTAL until these APIs are finalized. Signed-off-by: David Vrabel --- drivers/usb/host/Kconfig | 6 ++++-- drivers/usb/wusbcore/Kconfig | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 515044129bb9..72fb655e6033 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -307,7 +307,8 @@ config SUPERH_ON_CHIP_R8A66597 SH7366 and SH7723 processors. config USB_WHCI_HCD - tristate "Wireless USB Host Controller Interface (WHCI) driver" + tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)" + depends on EXPERIMENTAL depends on PCI && USB select USB_WUSB select UWB_WHCI @@ -319,7 +320,8 @@ config USB_WHCI_HCD will be called "whci-hcd". config USB_HWA_HCD - tristate "Host Wire Adapter (HWA) driver" + tristate "Host Wire Adapter (HWA) driver (EXPERIMENTAL)" + depends on EXPERIMENTAL depends on USB select USB_WUSB select UWB_HWA diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig index 4ea76693ad46..eb09a0a14a80 100644 --- a/drivers/usb/wusbcore/Kconfig +++ b/drivers/usb/wusbcore/Kconfig @@ -2,7 +2,8 @@ # Wireless USB Core configuration # config USB_WUSB - tristate "Enable Wireless USB extensions" + tristate "Enable Wireless USB extensions (EXPERIMENTAL)" + depends on EXPERIMENTAL depends on USB select UWB select CRYPTO -- cgit v1.2.2