diff options
Diffstat (limited to 'drivers')
87 files changed, 31511 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index d19b6f5a110..d38f43f593d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -78,6 +78,8 @@ source "drivers/hid/Kconfig" source "drivers/usb/Kconfig" +source "drivers/uwb/Kconfig" + source "drivers/mmc/Kconfig" source "drivers/memstick/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 46c8681a07f..cadc64fe8f6 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -100,3 +100,4 @@ obj-$(CONFIG_SSB) += ssb/ obj-$(CONFIG_VIRTIO) += virtio/ obj-$(CONFIG_REGULATOR) += regulator/ obj-$(CONFIG_STAGING) += staging/ +obj-$(CONFIG_UWB) += uwb/ diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index bcefbddeba5..c23a9857ee6 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 a419c42e880..8b7c419b876 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -16,9 +16,12 @@ obj-$(CONFIG_USB_UHCI_HCD) += host/ obj-$(CONFIG_USB_SL811_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ +obj-$(CONFIG_USB_HWA_HCD) += host/ obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ +obj-$(CONFIG_USB_WUSB) += wusbcore/ + obj-$(CONFIG_USB_ACM) += class/ obj-$(CONFIG_USB_PRINTER) += class/ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 228797e54f9..72fb655e603 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -305,3 +305,31 @@ config SUPERH_ON_CHIP_R8A66597 help This driver enables support for the on-chip R8A66597 in the SH7366 and SH7723 processors. + +config USB_WHCI_HCD + tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on PCI && USB + select USB_WUSB + select UWB_WHCI + help + A driver for PCI-based Wireless USB Host Controllers that are + compliant with the WHCI specification. + + To compile this driver a module, choose M here: the module + will be called "whci-hcd". + +config USB_HWA_HCD + tristate "Host Wire Adapter (HWA) driver (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on USB + select USB_WUSB + select UWB_HWA + help + This driver enables you to connect Wireless USB devices to + your system using a Host Wire Adaptor USB dongle. This is an + UWB Radio Controller and WUSB Host Controller connected to + your machine via USB (specified in WUSB1.0). + + To compile this driver a module, choose M here: the module + will be called "hwa-hc". diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index f1edda2dcfd..23be2222404 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 @@ -19,3 +21,4 @@ obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o +obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c new file mode 100644 index 00000000000..64be4d88df1 --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * The HWA driver is a simple layer that forwards requests to the WAHC + * (Wire Adater Host Controller) or WUSBHC (Wireless USB Host + * Controller) layers. + * + * Host Wire Adapter is the 'WUSB 1.0 standard' name for Wireless-USB + * Host Controller that is connected to your system via USB (a USB + * dongle that implements a USB host...). There is also a Device Wired + * Adaptor, DWA (Wireless USB hub) that uses the same mechanism for + * transferring data (it is after all a USB host connected via + * Wireless USB), we have a common layer called Wire Adapter Host + * Controller that does all the hard work. The WUSBHC (Wireless USB + * Host Controller) is the part common to WUSB Host Controllers, the + * HWA and the PCI-based one, that is implemented following the WHCI + * spec. All these layers are implemented in ../wusbcore. + * + * The main functions are hwahc_op_urb_{en,de}queue(), that pass the + * job of converting a URB to a Wire Adapter + * + * Entry points: + * + * hwahc_driver_*() Driver initialization, registration and + * teardown. + * + * hwahc_probe() New device came up, create an instance for + * it [from device enumeration]. + * + * hwahc_disconnect() Remove device instance [from device + * enumeration]. + * + * [__]hwahc_op_*() Host-Wire-Adaptor specific functions for + * starting/stopping/etc (some might be made also + * DWA). + */ +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/completion.h> +#include "../wusbcore/wa-hc.h" +#include "../wusbcore/wusbhc.h" + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +struct hwahc { + struct wusbhc wusbhc; /* has to be 1st */ + struct wahc wa; + u8 buffer[16]; /* for misc usb transactions */ +}; + +/** + * FIXME should be wusbhc + * + * NOTE: we need to cache the Cluster ID because later...there is no + * way to get it :) + */ +static int __hwahc_set_cluster_id(struct hwahc *hwahc, u8 cluster_id) +{ + int result; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_CLUSTER_ID, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + cluster_id, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Cannot set WUSB Cluster ID to 0x%02x: %d\n", + cluster_id, result); + else + wusbhc->cluster_id = cluster_id; + dev_info(dev, "Wireless USB Cluster ID set to 0x%02x\n", cluster_id); + return result; +} + +static int __hwahc_op_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_NUM_DNTS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + interval << 8 | slots, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + +/* + * Reset a WUSB host controller and wait for it to complete doing it. + * + * @usb_hcd: Pointer to WUSB Host Controller instance. + * + */ +static int hwahc_op_reset(struct usb_hcd *usb_hcd) +{ + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + wa_nep_disarm(&hwahc->wa); + result = __wa_set_feature(&hwahc->wa, WA_RESET); + if (result < 0) { + dev_err(dev, "error commanding HC to reset: %d\n", result); + goto error_unlock; + } + d_printf(3, dev, "reset: waiting for device to change state\n"); + result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0); + if (result < 0) { + dev_err(dev, "error waiting for HC to reset: %d\n", result); + goto error_unlock; + } +error_unlock: + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; +} + +/* + * FIXME: break this function up + */ +static int hwahc_op_start(struct usb_hcd *usb_hcd) +{ + u8 addr; + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + /* Set up a Host Info WUSB Information Element */ + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + result = -ENOSPC; + mutex_lock(&wusbhc->mutex); + /* Start the numbering from the top so that the bottom + * range of the unauth addr space is used for devices, + * the top for HCs; use 0xfe - RC# */ + addr = wusb_cluster_id_get(); + if (addr == 0) + goto error_cluster_id_get; + result = __hwahc_set_cluster_id(hwahc, addr); + if (result < 0) + goto error_set_cluster_id; + + result = wa_nep_arm(&hwahc->wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "cannot listen to notifications: %d\n", result); + goto error_stop; + } + usb_hcd->uses_new_polling = 1; + usb_hcd->poll_rh = 1; + usb_hcd->state = HC_STATE_RUNNING; + result = 0; +out: + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; + +error_stop: + __wa_stop(&hwahc->wa); +error_set_cluster_id: + wusb_cluster_id_put(wusbhc->cluster_id); +error_cluster_id_get: + goto out; + +} + +/* + * FIXME: break this function up + */ +static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + /* Set up a Host Info WUSB Information Element */ + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + result = -ENOSPC; + + result = __wa_set_feature(&hwahc->wa, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error commanding HC to start: %d\n", result); + goto error_stop; + } + result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error waiting for HC to start: %d\n", result); + goto error_stop; + } + result = 0; +out: + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; + +error_stop: + result = __wa_clear_feature(&hwahc->wa, WA_ENABLE); + goto out; +} + +static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + dev_err(wusbhc->dev, "%s (%p [%p], 0x%lx) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc, *(unsigned long *) &msg); + return -ENOSYS; +} + +static int hwahc_op_resume(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc); + return -ENOSYS; +} + +static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + /* Nothing for now */ + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return; +} + +/* + * No need to abort pipes, as when this is called, all the children + * has been disconnected and that has done it [through + * usb_disable_interface() -> usb_disable_endpoint() -> + * hwahc_op_ep_disable() - >rpipe_ep_disable()]. + */ +static void hwahc_op_stop(struct usb_hcd *usb_hcd) +{ + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + wusbhc_stop(wusbhc); + wa_nep_disarm(&hwahc->wa); + result = __wa_stop(&hwahc->wa); + wusb_cluster_id_put(wusbhc->cluster_id); + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return; +} + +static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc); + return -ENOSYS; +} + +static int hwahc_op_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, + gfp_t gfp) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + return wa_urb_enqueue(&hwahc->wa, urb->ep, urb, gfp); +} + +static int hwahc_op_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, + int status) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + return wa_urb_dequeue(&hwahc->wa, urb); +} + +/* + * Release resources allocated for an endpoint + * + * If there is an associated rpipe to this endpoint, go ahead and put it. + */ +static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + rpipe_ep_disable(&hwahc->wa, ep); +} + +/* + * Set the UWB MAS allocation for the WUSB cluster + * + * @stream_index: stream to use (-1 for cancelling the allocation) + * @mas: mas bitmap to use + */ +static int __hwahc_op_bwa_set(struct wusbhc *wusbhc, s8 stream_index, + const struct uwb_mas_bm *mas) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + u8 mas_le[UWB_NUM_MAS/8]; + + /* Set the stream index */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_STREAM_IDX, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + stream_index, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot set WUSB stream index: %d\n", result); + goto out; + } + uwb_mas_bm_copy_le(mas_le, mas); + /* Set the MAS allocation */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_WUSB_MAS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + mas_le, 32, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Cannot set WUSB MAS allocation: %d\n", result); +out: + return result; +} + +/* + * Add an IE to the host's MMC + * + * @interval: See WUSB1.0[8.5.3.1] + * @repeat_cnt: See WUSB1.0[8.5.3.1] + * @handle: See WUSB1.0[8.5.3.1] + * @wuie: Pointer to the header of the WUSB IE data to add. + * MUST BE allocated in a kmalloc buffer (no stack or + * vmalloc). + * + * NOTE: the format of the WUSB IEs for MMCs are different to the + * normal MBOA MAC IEs (IE Id + Length in MBOA MAC vs. Length + + * Id in WUSB IEs). Standards...you gotta love'em. + */ +static int __hwahc_op_mmcie_add(struct wusbhc *wusbhc, u8 interval, + u8 repeat_cnt, u8 handle, + struct wuie_hdr *wuie) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_ADD_MMC_IE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + interval << 8 | repeat_cnt, + handle << 8 | iface_no, + wuie, wuie->bLength, 1000 /* FIXME: arbitrary */); +} + +/* + * Remove an IE to the host's MMC + * + * @handle: See WUSB1.0[8.5.3.1] + */ +static int __hwahc_op_mmcie_rm(struct wusbhc *wusbhc, u8 handle) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_REMOVE_MMC_IE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, handle << 8 | iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + +/* + * Update device information for a given fake port + * + * @port_idx: Fake port to which device is connected (wusbhc index, not + * USB port number). + */ +static int __hwahc_op_dev_info_set(struct wusbhc *wusbhc, + struct wusb_dev *wusb_dev) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + struct hwa_dev_info *dev_info; + int ret; + + /* fill out the Device Info buffer and send it */ + dev_info = kzalloc(sizeof(struct hwa_dev_info), GFP_KERNEL); + if (!dev_info) + return -ENOMEM; + uwb_mas_bm_copy_le(dev_info->bmDeviceAvailability, + &wusb_dev->availability); + dev_info->bDeviceAddress = wusb_dev->addr; + + /* + * If the descriptors haven't been read yet, use a default PHY + * rate of 53.3 Mbit/s only. The correct value will be used + * when this will be called again as part of the + * authentication process (which occurs after the descriptors + * have been read). + */ + if (wusb_dev->wusb_cap_descr) + dev_info->wPHYRates = wusb_dev->wusb_cap_descr->wPHYRates; + else + dev_info->wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53); + + ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_DEV_INFO, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wusb_dev->port_idx << 8 | iface_no, + dev_info, sizeof(struct hwa_dev_info), + 1000 /* FIXME: arbitrary */); + kfree(dev_info); + return ret; +} + +/* + * Set host's idea of which encryption (and key) method to use when + * talking to ad evice on a given port. + * + * If key is NULL, it means disable encryption for that "virtual port" + * (used when we disconnect). + */ +static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *key, size_t key_size, + u8 key_idx) +{ + int result = -ENOMEM; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + struct usb_key_descriptor *keyd; + size_t keyd_len; + + keyd_len = sizeof(*keyd) + key_size; + keyd = kzalloc(keyd_len, GFP_KERNEL); + if (keyd == NULL) + return -ENOMEM; + + keyd->bLength = keyd_len; + keyd->bDescriptorType = USB_DT_KEY; + keyd->tTKID[0] = (tkid >> 0) & 0xff; + keyd->tTKID[1] = (tkid >> 8) & 0xff; + keyd->tTKID[2] = (tkid >> 16) & 0xff; + memcpy(keyd->bKeyData, key, key_size); + + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + USB_DT_KEY << 8 | key_idx, + port_idx << 8 | iface_no, + keyd, keyd_len, 1000 /* FIXME: arbitrary */); + + memset(keyd, 0, sizeof(*keyd)); /* clear keys etc. */ + kfree(keyd); + return result; +} + +/* + * Set host's idea of which encryption (and key) method to use when + * talking to ad evice on a given port. + * + * If key is NULL, it means disable encryption for that "virtual port" + * (used when we disconnect). + */ +static int __hwahc_op_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *key, size_t key_size) +{ + int result = -ENOMEM; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + u8 encryption_value; + + /* Tell the host which key to use to talk to the device */ + if (key) { + u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_PTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + result = __hwahc_dev_set_key(wusbhc, port_idx, tkid, + key, key_size, key_idx); + if (result < 0) + goto error_set_key; + encryption_value = wusbhc->ccm1_etd->bEncryptionValue; + } else { + /* FIXME: this should come from wusbhc->etd[UNSECURE].value */ + encryption_value = 0; + } + + /* Set the encryption type for commmunicating with the device */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_ENCRYPTION, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + encryption_value, port_idx << 8 | iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(wusbhc->dev, "Can't set host's WUSB encryption for " + "port index %u to %s (value %d): %d\n", port_idx, + wusb_et_name(wusbhc->ccm1_etd->bEncryptionType), + wusbhc->ccm1_etd->bEncryptionValue, result); +error_set_key: + return result; +} + +/* + * Set host's GTK key + */ +static int __hwahc_op_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *key, size_t key_size) +{ + u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + return __hwahc_dev_set_key(wusbhc, 0, tkid, key, key_size, key_idx); +} + +/* + * Get the Wire Adapter class-specific descriptor + * + * NOTE: this descriptor comes with the big bundled configuration + * descriptor that includes the interfaces' and endpoints', so + * we just look for it in the cached copy kept by the USB stack. + * + * NOTE2: We convert LE fields to CPU order. + */ +static int wa_fill_descr(struct wahc *wa) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + char *itr; + struct usb_device *usb_dev = wa->usb_dev; + struct usb_descriptor_header *hdr; + struct usb_wa_descriptor *wa_descr; + size_t itr_size, actconfig_idx; + + actconfig_idx = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[actconfig_idx]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + while (itr_size >= sizeof(*hdr)) { + hdr = (struct usb_descriptor_header *) itr; + d_printf(3, dev, "Extra device descriptor: " + "type %02x/%u bytes @ %zu (%zu left)\n", + hdr->bDescriptorType, hdr->bLength, + (itr - usb_dev->rawdescriptors[actconfig_idx]), + itr_size); + if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER) + goto found; + itr += hdr->bLength; + itr_size -= hdr->bLength; + } + dev_err(dev, "cannot find Wire Adapter Class descriptor\n"); + return -ENODEV; + +found: + result = -EINVAL; + if (hdr->bLength > itr_size) { /* is it available? */ + dev_err(dev, "incomplete Wire Adapter Class descriptor " + "(%zu bytes left, %u needed)\n", + itr_size, hdr->bLength); + goto error; + } + if (hdr->bLength < sizeof(*wa->wa_descr)) { + dev_err(dev, "short Wire Adapter Class descriptor\n"); + goto error; + } + wa->wa_descr = wa_descr = (struct usb_wa_descriptor *) hdr; + /* Make LE fields CPU order */ + wa_descr->bcdWAVersion = le16_to_cpu(wa_descr->bcdWAVersion); + wa_descr->wNumRPipes = le16_to_cpu(wa_descr->wNumRPipes); + wa_descr->wRPipeMaxBlock = le16_to_cpu(wa_descr->wRPipeMaxBlock); + if (wa_descr->bcdWAVersion > 0x0100) + dev_warn(dev, "Wire Adapter v%d.%d newer than groked v1.0\n", + wa_descr->bcdWAVersion & 0xff00 >> 8, + wa_descr->bcdWAVersion & 0x00ff); + result = 0; +error: + return result; +} + +static struct hc_driver hwahc_hc_driver = { + .description = "hwa-hcd", + .product_desc = "Wireless USB HWA host controller", + .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd), + .irq = NULL, /* FIXME */ + .flags = HCD_USB2, /* FIXME */ + .reset = hwahc_op_reset, + .start = hwahc_op_start, + .pci_suspend = hwahc_op_suspend, + .pci_resume = hwahc_op_resume, + .stop = hwahc_op_stop, + .get_frame_number = hwahc_op_get_frame_number, + .urb_enqueue = hwahc_op_urb_enqueue, + .urb_dequeue = hwahc_op_urb_dequeue, + .endpoint_disable = hwahc_op_endpoint_disable, + + .hub_status_data = wusbhc_rh_status_data, + .hub_control = wusbhc_rh_control, + .bus_suspend = wusbhc_rh_suspend, + .bus_resume = wusbhc_rh_resume, + .start_port_reset = wusbhc_rh_start_port_reset, +}; + +static int hwahc_security_create(struct hwahc *hwahc) +{ + int result; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct usb_device *usb_dev = hwahc->wa.usb_dev; + struct device *dev = &usb_dev->dev; + struct usb_security_descriptor *secd; + struct usb_encryption_descriptor *etd; + void *itr, *top; + size_t itr_size, needed, bytes; + u8 index; + char buf[64]; + + /* Find the host's security descriptors in the config descr bundle */ + index = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[index]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + top = itr + itr_size; + result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index], + le16_to_cpu(usb_dev->actconfig->desc.wTotalLength), + USB_DT_SECURITY, (void **) &secd); + if (result == -1) { + dev_warn(dev, "BUG? WUSB host has no security descriptors\n"); + return 0; + } + needed = sizeof(*secd); + if (top - (void *)secd < needed) { + dev_err(dev, "BUG? Not enough data to process security " + "descriptor header (%zu bytes left vs %zu needed)\n", + top - (void *) secd, needed); + return 0; + } + needed = le16_to_cpu(secd->wTotalLength); + if (top - (void *)secd < needed) { + dev_err(dev, "BUG? Not enough data to process security " + "descriptors (%zu bytes left vs %zu needed)\n", + top - (void *) secd, needed); + return 0; + } + /* Walk over the sec descriptors and store CCM1's on wusbhc */ + itr = (void *) secd + sizeof(*secd); + top = (void *) secd + le16_to_cpu(secd->wTotalLength); + index = 0; + bytes = 0; + while (itr < top) { + etd = itr; + if (top - itr < sizeof(*etd)) { + dev_err(dev, "BUG: bad host security descriptor; " + "not enough data (%zu vs %zu left)\n", + top - itr, sizeof(*etd)); + break; + } + if (etd->bLength < sizeof(*etd)) { + dev_err(dev, "BUG: bad host encryption descriptor; " + "descriptor is too short " + "(%zu vs %zu needed)\n", + (size_t)etd->bLength, sizeof(*etd)); + break; + } + itr += etd->bLength; + bytes += snprintf(buf + bytes, sizeof(buf) - bytes, + "%s (0x%02x) ", + wusb_et_name(etd->bEncryptionType), + etd->bEncryptionValue); + wusbhc->ccm1_etd = etd; + } + dev_info(dev, "supported encryption types: %s\n", buf); + if (wusbhc->ccm1_etd == NULL) { + dev_err(dev, "E: host doesn't support CCM-1 crypto\n"); + return 0; + } + /* Pretty print what we support */ + return 0; +} + +static void hwahc_security_release(struct hwahc *hwahc) +{ + /* nothing to do here so far... */ +} + +static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface) +{ + int result; + struct device *dev = &iface->dev; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct wahc *wa = &hwahc->wa; + struct usb_device *usb_dev = interface_to_usbdev(iface); + + wa->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */ + wa->usb_iface = usb_get_intf(iface); + wusbhc->dev = dev; + wusbhc->uwb_rc = uwb_rc_get_by_grandpa(iface->dev.parent); + if (wusbhc->uwb_rc == NULL) { + result = -ENODEV; + dev_err(dev, "Cannot get associated UWB Host Controller\n"); + goto error_rc_get; + } + result = wa_fill_descr(wa); /* Get the device descriptor */ + if (result < 0) + goto error_fill_descriptor; + if (wa->wa_descr->bNumPorts > USB_MAXCHILDREN) { + dev_err(dev, "FIXME: USB_MAXCHILDREN too low for WUSB " + "adapter (%u ports)\n", wa->wa_descr->bNumPorts); + wusbhc->ports_max = USB_MAXCHILDREN; + } else { + wusbhc->ports_max = wa->wa_descr->bNumPorts; + } + wusbhc->mmcies_max = wa->wa_descr->bNumMMCIEs; + wusbhc->start = __hwahc_op_wusbhc_start; + wusbhc->stop = __hwahc_op_wusbhc_stop; + wusbhc->mmcie_add = __hwahc_op_mmcie_add; + wusbhc->mmcie_rm = __hwahc_op_mmcie_rm; + wusbhc->dev_info_set = __hwahc_op_dev_info_set; + wusbhc->bwa_set = __hwahc_op_bwa_set; + wusbhc->set_num_dnts = __hwahc_op_set_num_dnts; + wusbhc->set_ptk = __hwahc_op_set_ptk; + wusbhc->set_gtk = __hwahc_op_set_gtk; + result = hwahc_security_create(hwahc); + if (result < 0) { + dev_err(dev, "Can't initialize security: %d\n", result); + goto error_security_create; + } + wa->wusb = wusbhc; /* FIXME: ugly, need to fix */ + result = wusbhc_create(&hwahc->wusbhc); + if (result < 0) { + dev_err(dev, "Can't create WUSB HC structures: %d\n", result); + goto error_wusbhc_create; + } + result = wa_create(&hwahc->wa, iface); + if (result < 0) + goto error_wa_create; + return 0; + +error_wa_create: + wusbhc_destroy(&hwahc->wusbhc); +error_wusbhc_create: + /* WA Descr fill allocs no resources */ +error_security_create: +error_fill_descriptor: + uwb_rc_put(wusbhc->uwb_rc); +error_rc_get: + usb_put_intf(iface); + usb_put_dev(usb_dev); + return result; +} + +static void hwahc_destroy(struct hwahc *hwahc) +{ + struct wusbhc *wusbhc = &hwahc->wusbhc; + + d_fnstart(1, NULL, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + __wa_destroy(&hwahc->wa); + wusbhc_destroy(&hwahc->wusbhc); + hwahc_security_release(hwahc); + hwahc->wusbhc.dev = NULL; + uwb_rc_put(wusbhc->uwb_rc); + usb_put_intf(hwahc->wa.usb_iface); + usb_put_dev(hwahc->wa.usb_dev); + mutex_unlock(&wusbhc->mutex); + d_fnend(1, NULL, "(hwahc %p) = void\n", hwahc); +} + +static void hwahc_init(struct hwahc *hwahc) +{ + wa_init(&hwahc->wa); +} + +static int hwahc_probe(struct usb_interface *usb_iface, + const struct usb_device_id *id) +{ + int result; + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct hwahc *hwahc; + struct device *dev = &usb_iface->dev; + + d_fnstart(4, dev, "(%p, %p)\n", usb_iface, id); + result = -ENOMEM; + usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa"); + if (usb_hcd == NULL) { + dev_err(dev, "unable to allocate instance\n"); + goto error_alloc; + } + usb_hcd->wireless = 1; + usb_hcd->flags |= HCD_FLAG_SAW_IRQ; + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + hwahc = container_of(wusbhc, struct hwahc, wusbhc); + hwahc_init(hwahc); + result = hwahc_create(hwahc, usb_iface); + if (result < 0) { + dev_err(dev, "Cannot initialize internals: %d\n", result); + goto error_hwahc_create; + } + result = usb_add_hcd(usb_hcd, 0, 0); + if (result < 0) { + dev_err(dev, "Cannot add HCD: %d\n", result); + goto error_add_hcd; + } + result = wusbhc_b_create(&hwahc->wusbhc); + if (result < 0) { + dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result); + goto error_wusbhc_b_create; + } + d_fnend(4, dev, "(%p, %p) = 0\n", usb_iface, id); + return 0; + +error_wusbhc_b_create: + usb_remove_hcd(usb_hcd); +error_add_hcd: + hwahc_destroy(hwahc); +error_hwahc_create: + usb_put_hcd(usb_hcd); +error_alloc: + d_fnend(4, dev, "(%p, %p) = %d\n", usb_iface, id, result); + return result; +} + +static void hwahc_disconnect(struct usb_interface *usb_iface) +{ + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct hwahc *hwahc; + + usb_hcd = usb_get_intfdata(usb_iface); + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + d_fnstart(1, NULL, "(hwahc %p [usb_iface %p])\n", hwahc, usb_iface); + wusbhc_b_destroy(&hwahc->wusbhc); + usb_remove_hcd(usb_hcd); + hwahc_destroy(hwahc); + usb_put_hcd(usb_hcd); + d_fnend(1, NULL, "(hwahc %p [usb_iface %p]) = void\n", hwahc, + usb_iface); +} + +/** USB device ID's that we handle */ +static struct usb_device_id hwahc_id_table[] = { + /* FIXME: use class labels for this */ + { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), }, + {}, +}; +MODULE_DEVICE_TABLE(usb, hwahc_id_table); + +static struct usb_driver hwahc_driver = { + .name = "hwa-hc", + .probe = hwahc_probe, + .disconnect = hwahc_disconnect, + .id_table = hwahc_id_table, +}; + +static int __init hwahc_driver_init(void) +{ + int result; + result = usb_register(&hwahc_driver); + if (result < 0) { + printk(KERN_ERR "WA-CDS: Cannot register USB driver: %d\n", + result); + goto error_usb_register; + } + return 0; + +error_usb_register: + return result; + +} +module_init(hwahc_driver_init); + +static void __exit hwahc_driver_exit(void) +{ + usb_deregister(&hwahc_driver); +} +module_exit(hwahc_driver_exit); + + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild new file mode 100644 index 00000000000..26a3871ea0f --- /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 00000000000..4d7078e5057 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/uwb/umc.h> +#include <linux/usb.h> +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 4 +static void dump_asl(struct whc *whc, const char *tag) +{ + struct device *dev = &whc->umc->dev; + struct whc_qset *qset; + + d_printf(4, dev, "ASL %s\n", tag); + + list_for_each_entry(qset, &whc->async_list, list_node) { + dump_qset(qset, dev); + } +} +#else +static inline void dump_asl(struct whc *whc, const char *tag) +{ +} +#endif + + +static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset, + struct whc_qset **next, struct whc_qset **prev) +{ + struct list_head *n, *p; + + BUG_ON(list_empty(&whc->async_list)); + + n = qset->list_node.next; + if (n == &whc->async_list) + n = n->next; + p = qset->list_node.prev; + if (p == &whc->async_list) + p = p->prev; + + *next = container_of(n, struct whc_qset, list_node); + *prev = container_of(p, struct whc_qset, list_node); + +} + +static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset) +{ + list_move(&qset->list_node, &whc->async_list); + qset->in_sw_list = true; +} + +static void asl_qset_insert(struct whc *whc, struct whc_qset *qset) +{ + struct whc_qset *next, *prev; + + qset_clear(whc, qset); + + /* Link into ASL. */ + qset_get_next_prev(whc, qset, &next, &prev); + whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma); + whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma); + qset->in_hw_list = true; +} + +static void asl_qset_remove(struct whc *whc, struct whc_qset *qset) +{ + struct whc_qset *prev, *next; + + qset_get_next_prev(whc, qset, &next, &prev); + + list_move(&qset->list_node, &whc->async_removed_list); + qset->in_sw_list = false; + + /* + * No more qsets in the ASL? The caller must stop the ASL as + * it's no longer valid. + */ + if (list_empty(&whc->async_list)) + return; + + /* Remove from ASL. */ + whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma); + qset->in_hw_list = false; +} + +/** + * process_qset - process any recently inactivated or halted qTDs in a + * qset. + * + * After inactive qTDs are removed, new qTDs can be added if the + * urb queue still contains URBs. + * + * Returns any additional WUSBCMD bits for the ASL sync command (i.e., + * WUSBCMD_ASYNC_QSET_RM if a halted qset was removed). + */ +static uint32_t process_qset(struct whc *whc, struct whc_qset *qset) +{ + enum whc_update update = 0; + uint32_t status = 0; + + while (qset->ntds) { + struct whc_qtd *td; + int t; + + t = qset->td_start; + td = &qset->qtd[qset->td_start]; + status = le32_to_cpu(td->status); + + /* + * Nothing to do with a still active qTD. + */ + if (status & QTD_STS_ACTIVE) + break; + + if (status & QTD_STS_HALTED) { + /* Ug, an error. */ + process_halted_qtd(whc, qset, td); + goto done; + } + + /* Mmm, a completed qTD. */ + process_inactive_qtd(whc, qset, td); + } + + update |= qset_add_qtds(whc, qset); + +done: + /* + * Remove this qset from the ASL if requested, but only if has + * no qTDs. + */ + if (qset->remove && qset->ntds == 0) { + asl_qset_remove(whc, qset); + update |= WHC_UPDATE_REMOVED; + } + return update; +} + +void asl_start(struct whc *whc) +{ + struct whc_qset *qset; + + qset = list_first_entry(&whc->async_list, struct whc_qset, list_node); + + le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR); + + whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED, + 1000, "start ASL"); +} + +void asl_stop(struct whc *whc) +{ + whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_ASYNC_SCHED, 0, + 1000, "stop ASL"); +} + +void asl_update(struct whc *whc, uint32_t wusbcmd) +{ + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + wait_event(whc->async_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0); +} + +/** + * scan_async_work - scan the ASL for qsets to process. + * + * Process each qset in the ASL in turn and then signal the WHC that + * the ASL has been updated. + * + * Then start, stop or update the asynchronous schedule as required. + */ +void scan_async_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, async_work); + struct whc_qset *qset, *t; + enum whc_update update = 0; + + spin_lock_irq(&whc->lock); + + dump_asl(whc, "before processing"); + + /* + * Transerve the software list backwards so new qsets can be + * safely inserted into the ASL without making it non-circular. + */ + list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) { + if (!qset->in_hw_list) { + asl_qset_insert(whc, qset); + update |= WHC_UPDATE_ADDED; + } + + update |= process_qset(whc, qset); + } + + dump_asl(whc, "after processing"); + + spin_unlock_irq(&whc->lock); + + if (update) { + uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB; + if (update & WHC_UPDATE_REMOVED) + wusbcmd |= WUSBCMD_ASYNC_QSET_RM; + asl_update(whc, wusbcmd); + } + + /* + * Now that the ASL is updated, complete the removal of any + * removed qsets. + */ + spin_lock(&whc->lock); + + list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) { + qset_remove_complete(whc, qset); + } + + spin_unlock(&whc->lock); +} + +/** + * asl_urb_enqueue - queue an URB onto the asynchronous list (ASL). + * @whc: the WHCI host controller + * @urb: the URB to enqueue + * @mem_flags: flags for any memory allocations + * + * The qset for the endpoint is obtained and the urb queued on to it. + * + * Work is scheduled to update the hardware's view of the ASL. + */ +int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags) +{ + struct whc_qset *qset; + int err; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + qset = get_qset(whc, urb, GFP_ATOMIC); + if (qset == NULL) + err = -ENOMEM; + else + err = qset_add_urb(whc, qset, urb, GFP_ATOMIC); + if (!err) { + usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb); + if (!qset->in_sw_list) + asl_qset_insert_begin(whc, qset); + } + + spin_unlock_irqrestore(&whc->lock, flags); + + if (!err) + queue_work(whc->workqueue, &whc->async_work); + + return 0; +} + +/** + * asl_urb_dequeue - remove an URB (qset) from the async list. + * @whc: the WHCI host controller + * @urb: the URB to dequeue + * @status: the current status of the URB + * + * URBs that do yet have qTDs can simply be removed from the software + * queue, otherwise the qset must be removed from the ASL so the qTDs + * can be removed. + */ +int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status) +{ + struct whc_urb *wurb = urb->hcpriv; + struct whc_qset *qset = wurb->qset; + struct whc_std *std, *t; + int ret; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status); + if (ret < 0) + goto out; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(whc, std); + else + std->qtd = NULL; /* so this std is re-added when the qset is */ + } + + asl_qset_remove(whc, qset); + wurb->status = status; + wurb->is_async = true; + queue_work(whc->workqueue, &wurb->dequeue_work); + +out: + spin_unlock_irqrestore(&whc->lock, flags); + + return ret; +} + +/** + * asl_qset_delete - delete a qset from the ASL + */ +void asl_qset_delete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 1; + queue_work(whc->workqueue, &whc->async_work); + qset_delete(whc, qset); +} + +/** + * asl_init - initialize the asynchronous schedule list + * + * A dummy qset with no qTDs is added to the ASL to simplify removing + * qsets (no need to stop the ASL when the last qset is removed). + */ +int asl_init(struct whc *whc) +{ + struct whc_qset *qset; + + qset = qset_alloc(whc, GFP_KERNEL); + if (qset == NULL) + return -ENOMEM; + + asl_qset_insert_begin(whc, qset); + asl_qset_insert(whc, qset); + + return 0; +} + +/** + * asl_clean_up - free ASL resources + * + * The ASL is stopped and empty except for the dummy qset. + */ +void asl_clean_up(struct whc *whc) +{ + struct whc_qset *qset; + + if (!list_empty(&whc->async_list)) { + qset = list_first_entry(&whc->async_list, struct whc_qset, list_node); + list_del(&qset->list_node); + qset_free(whc, qset); + } +} diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c new file mode 100644 index 00000000000..ef3ad4dca94 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/uwb/umc.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +/* + * One time initialization. + * + * Nothing to do here. + */ +static int whc_reset(struct usb_hcd *usb_hcd) +{ + return 0; +} + +/* + * Start the wireless host controller. + * + * Start device notification. + * + * Put hc into run state, set DNTS parameters. + */ +static int whc_start(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + u8 bcid; + int ret; + + mutex_lock(&wusbhc->mutex); + + le_writel(WUSBINTR_GEN_CMD_DONE + | WUSBINTR_HOST_ERR + | WUSBINTR_ASYNC_SCHED_SYNCED + | WUSBINTR_DNTS_INT + | WUSBINTR_ERR_INT + | WUSBINTR_INT, + whc->base + WUSBINTR); + + /* set cluster ID */ + bcid = wusb_cluster_id_get(); + ret = whc_set_cluster_id(whc, bcid); + if (ret < 0) + goto out; + wusbhc->cluster_id = bcid; + + /* start HC */ + whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN); + + usb_hcd->uses_new_polling = 1; + usb_hcd->poll_rh = 1; + usb_hcd->state = HC_STATE_RUNNING; + +out: + mutex_unlock(&wusbhc->mutex); + return ret; +} + + +/* + * Stop the wireless host controller. + * + * Stop device notification. + * + * Wait for pending transfer to stop? Put hc into stop state? + */ +static void whc_stop(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + + mutex_lock(&wusbhc->mutex); + + wusbhc_stop(wusbhc); + + /* stop HC */ + le_writel(0, whc->base + WUSBINTR); + whc_write_wusbcmd(whc, WUSBCMD_RUN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_HCHALTED, WUSBSTS_HCHALTED, + 100, "HC to halt"); + + wusb_cluster_id_put(wusbhc->cluster_id); + + mutex_unlock(&wusbhc->mutex); +} + +static int whc_get_frame_number(struct usb_hcd *usb_hcd) +{ + /* Frame numbers are not applicable to WUSB. */ + return -ENOSYS; +} + + +/* + * Queue an URB to the ASL or PZL + */ +static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + ret = pzl_urb_enqueue(whc, urb, mem_flags); + break; + case PIPE_ISOCHRONOUS: + dev_err(&whc->umc->dev, "isochronous transfers unsupported\n"); + ret = -ENOTSUPP; + break; + case PIPE_CONTROL: + case PIPE_BULK: + default: + ret = asl_urb_enqueue(whc, urb, mem_flags); + break; + }; + + return ret; +} + +/* + * Remove a queued URB from the ASL or PZL. + */ +static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + ret = pzl_urb_dequeue(whc, urb, status); + break; + case PIPE_ISOCHRONOUS: + ret = -ENOTSUPP; + break; + case PIPE_CONTROL: + case PIPE_BULK: + default: + ret = asl_urb_dequeue(whc, urb, status); + break; + }; + + return ret; +} + +/* + * Wait for all URBs to the endpoint to be completed, then delete the + * qset. + */ +static void whc_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + struct whc_qset *qset; + + qset = ep->hcpriv; + if (qset) { + ep->hcpriv = NULL; + if (usb_endpoint_xfer_bulk(&ep->desc) + || usb_endpoint_xfer_control(&ep->desc)) + asl_qset_delete(whc, qset); + else + pzl_qset_delete(whc, qset); + } +} + +static struct hc_driver whc_hc_driver = { + .description = "whci-hcd", + .product_desc = "Wireless host controller", + .hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd), + .irq = whc_int_handler, + .flags = HCD_USB2, + + .reset = whc_reset, + .start = whc_start, + .stop = whc_stop, + .get_frame_number = whc_get_frame_number, + .urb_enqueue = whc_urb_enqueue, + .urb_dequeue = whc_urb_dequeue, + .endpoint_disable = whc_endpoint_disable, + + .hub_status_data = wusbhc_rh_status_data, + .hub_control = wusbhc_rh_control, + .bus_suspend = wusbhc_rh_suspend, + .bus_resume = wusbhc_rh_resume, + .start_port_reset = wusbhc_rh_start_port_reset, +}; + +static int whc_probe(struct umc_dev *umc) +{ + int ret = -ENOMEM; + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc = NULL; + struct whc *whc = NULL; + struct device *dev = &umc->dev; + + usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci"); + if (usb_hcd == NULL) { + dev_err(dev, "unable to create hcd\n"); + goto error; + } + + usb_hcd->wireless = 1; + + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + whc = wusbhc_to_whc(wusbhc); + whc->umc = umc; + + ret = whc_init(whc); + if (ret) + goto error; + + wusbhc->dev = dev; + wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent); + if (!wusbhc->uwb_rc) { + ret = -ENODEV; + dev_err(dev, "cannot get radio controller\n"); + goto error; + } + + if (whc->n_devices > USB_MAXCHILDREN) { + dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n", + whc->n_devices); + wusbhc->ports_max = USB_MAXCHILDREN; + } else + wusbhc->ports_max = whc->n_devices; + wusbhc->mmcies_max = whc->n_mmc_ies; + wusbhc->start = whc_wusbhc_start; + wusbhc->stop = whc_wusbhc_stop; + wusbhc->mmcie_add = whc_mmcie_add; + wusbhc->mmcie_rm = whc_mmcie_rm; + wusbhc->dev_info_set = whc_dev_info_set; + wusbhc->bwa_set = whc_bwa_set; + wusbhc->set_num_dnts = whc_set_num_dnts; + wusbhc->set_ptk = whc_set_ptk; + wusbhc->set_gtk = whc_set_gtk; + + ret = wusbhc_create(wusbhc); + if (ret) + goto error_wusbhc_create; + + ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED); + if (ret) { + dev_err(dev, "cannot add HCD: %d\n", ret); + goto error_usb_add_hcd; + } + + ret = wusbhc_b_create(wusbhc); + if (ret) { + dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret); + goto error_wusbhc_b_create; + } + + return 0; + +error_wusbhc_b_create: + usb_remove_hcd(usb_hcd); +error_usb_add_hcd: + wusbhc_destroy(wusbhc); +error_wusbhc_create: + uwb_rc_put(wusbhc->uwb_rc); +error: + whc_clean_up(whc); + if (usb_hcd) + usb_put_hcd(usb_hcd); + return ret; +} + + +static void whc_remove(struct umc_dev *umc) +{ + struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev); + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + + if (usb_hcd) { + wusbhc_b_destroy(wusbhc); + usb_remove_hcd(usb_hcd); + wusbhc_destroy(wusbhc); + uwb_rc_put(wusbhc->uwb_rc); + whc_clean_up(whc); + usb_put_hcd(usb_hcd); + } +} + +static struct umc_driver whci_hc_driver = { + .name = "whci-hcd", + .cap_id = UMC_CAP_ID_WHCI_WUSB_HC, + .probe = whc_probe, + .remove = whc_remove, +}; + +static int __init whci_hc_driver_init(void) +{ + return umc_driver_register(&whci_hc_driver); +} +module_init(whci_hc_driver_init); + +static void __exit whci_hc_driver_exit(void) +{ + umc_driver_unregister(&whci_hc_driver); +} +module_exit(whci_hc_driver_exit); + +/* PCI device ID's that we handle (so it gets loaded) */ +static struct pci_device_id whci_hcd_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, + { /* empty last entry */ } +}; +MODULE_DEVICE_TABLE(pci, whci_hcd_id_table); + +MODULE_DESCRIPTION("WHCI Wireless USB host controller driver"); +MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c new file mode 100644 index 00000000000..ac86e59c122 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/uwb/umc.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val) +{ + unsigned long flags; + u32 cmd; + + spin_lock_irqsave(&whc->lock, flags); + + cmd = le_readl(whc->base + WUSBCMD); + cmd = (cmd & ~mask) | val; + le_writel(cmd, whc->base + WUSBCMD); + + spin_unlock_irqrestore(&whc->lock, flags); +} + +/** + * whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register + * @whc: the WHCI HC + * @cmd: command to start. + * @params: parameters for the command (the WUSBGENCMDPARAMS register value). + * @addr: pointer to any data for the command (may be NULL). + * @len: length of the data (if any). + */ +int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len) +{ + unsigned long flags; + dma_addr_t dma_addr; + int t; + + mutex_lock(&whc->mutex); + + /* Wait for previous command to complete. */ + t = wait_event_timeout(whc->cmd_wq, + (le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0, + WHC_GENCMD_TIMEOUT_MS); + if (t == 0) { + dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n", + le_readl(whc->base + WUSBGENCMDSTS), + le_readl(whc->base + WUSBGENCMDPARAMS)); + return -ETIMEDOUT; + } + + if (addr) { + memcpy(whc->gen_cmd_buf, addr, len); + dma_addr = whc->gen_cmd_buf_dma; + } else + dma_addr = 0; + + /* Poke registers to start cmd. */ + spin_lock_irqsave(&whc->lock, flags); + + le_writel(params, whc->base + WUSBGENCMDPARAMS); + le_writeq(dma_addr, whc->base + WUSBGENADDR); + + le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd, + whc->base + WUSBGENCMDSTS); + + spin_unlock_irqrestore(&whc->lock, flags); + + mutex_unlock(&whc->mutex); + + return 0; +} diff --git a/drivers/usb/host/whci/init.c b/drivers/usb/host/whci/init.c new file mode 100644 index 00000000000..34a783cb013 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/uwb/umc.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +/* + * Reset the host controller. + */ +static void whc_hw_reset(struct whc *whc) +{ + le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD); + whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0, + 100, "reset"); +} + +static void whc_hw_init_di_buf(struct whc *whc) +{ + int d; + + /* Disable all entries in the Device Information buffer. */ + for (d = 0; d < whc->n_devices; d++) + whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE; + + le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR); +} + +static void whc_hw_init_dn_buf(struct whc *whc) +{ + /* Clear the Device Notification buffer to ensure the V (valid) + * bits are clear. */ + memset(whc->dn_buf, 0, 4096); + + le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR); +} + +int whc_init(struct whc *whc) +{ + u32 whcsparams; + int ret, i; + resource_size_t start, len; + + spin_lock_init(&whc->lock); + mutex_init(&whc->mutex); + init_waitqueue_head(&whc->cmd_wq); + init_waitqueue_head(&whc->async_list_wq); + init_waitqueue_head(&whc->periodic_list_wq); + whc->workqueue = create_singlethread_workqueue(dev_name(&whc->umc->dev)); + if (whc->workqueue == NULL) { + ret = -ENOMEM; + goto error; + } + INIT_WORK(&whc->dn_work, whc_dn_work); + + INIT_WORK(&whc->async_work, scan_async_work); + INIT_LIST_HEAD(&whc->async_list); + INIT_LIST_HEAD(&whc->async_removed_list); + + INIT_WORK(&whc->periodic_work, scan_periodic_work); + for (i = 0; i < 5; i++) + INIT_LIST_HEAD(&whc->periodic_list[i]); + INIT_LIST_HEAD(&whc->periodic_removed_list); + + /* Map HC registers. */ + start = whc->umc->resource.start; + len = whc->umc->resource.end - start + 1; + if (!request_mem_region(start, len, "whci-hc")) { + dev_err(&whc->umc->dev, "can't request HC region\n"); + ret = -EBUSY; + goto error; + } + whc->base_phys = start; + whc->base = ioremap(start, len); + if (!whc->base) { + dev_err(&whc->umc->dev, "ioremap\n"); + ret = -ENOMEM; + goto error; + } + + whc_hw_reset(whc); + + /* Read maximum number of devices, keys and MMC IEs. */ + whcsparams = le_readl(whc->base + WHCSPARAMS); + whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams); + whc->n_keys = WHCSPARAMS_TO_N_KEYS(whcsparams); + whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams); + + dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n", + whc->n_devices, whc->n_keys, whc->n_mmc_ies); + + whc->qset_pool = dma_pool_create("qset", &whc->umc->dev, + sizeof(struct whc_qset), 64, 0); + if (whc->qset_pool == NULL) { + ret = -ENOMEM; + goto error; + } + + ret = asl_init(whc); + if (ret < 0) + goto error; + ret = pzl_init(whc); + if (ret < 0) + goto error; + + /* Allocate and initialize a buffer for generic commands, the + Device Information buffer, and the Device Notification + buffer. */ + + whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN, + &whc->gen_cmd_buf_dma, GFP_KERNEL); + if (whc->gen_cmd_buf == NULL) { + ret = -ENOMEM; + goto error; + } + + whc->dn_buf = dma_alloc_coherent(&whc->umc->dev, + sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES, + &whc->dn_buf_dma, GFP_KERNEL); + if (!whc->dn_buf) { + ret = -ENOMEM; + goto error; + } + whc_hw_init_dn_buf(whc); + + whc->di_buf = dma_alloc_coherent(&whc->umc->dev, + sizeof(struct di_buf_entry) * whc->n_devices, + &whc->di_buf_dma, GFP_KERNEL); + if (!whc->di_buf) { + ret = -ENOMEM; + goto error; + } + whc_hw_init_di_buf(whc); + + return 0; + +error: + whc_clean_up(whc); + return ret; +} + +void whc_clean_up(struct whc *whc) +{ + resource_size_t len; + + if (whc->di_buf) + dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices, + whc->di_buf, whc->di_buf_dma); + if (whc->dn_buf) + dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES, + whc->dn_buf, whc->dn_buf_dma); + if (whc->gen_cmd_buf) + dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN, + whc->gen_cmd_buf, whc->gen_cmd_buf_dma); + + pzl_clean_up(whc); + asl_clean_up(whc); + + if (whc->qset_pool) + dma_pool_destroy(whc->qset_pool); + + len = whc->umc->resource.end - whc->umc->resource.start + 1; + if (whc->base) + iounmap(whc->base); + if (whc->base_phys) + release_mem_region(whc->base_phys, len); + + if (whc->workqueue) + destroy_workqueue(whc->workqueue); +} diff --git a/drivers/usb/host/whci/int.c b/drivers/usb/host/whci/int.c new file mode 100644 index 00000000000..fce01174aa9 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/uwb/umc.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +static void transfer_done(struct whc *whc) +{ + queue_work(whc->workqueue, &whc->async_work); + queue_work(whc->workqueue, &whc->periodic_work); +} + +irqreturn_t whc_int_handler(struct usb_hcd *hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 sts; + + sts = le_readl(whc->base + WUSBSTS); + if (!(sts & WUSBSTS_INT_MASK)) + return IRQ_NONE; + le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS); + + if (sts & WUSBSTS_GEN_CMD_DONE) + wake_up(&whc->cmd_wq); + + if (sts & WUSBSTS_HOST_ERR) + dev_err(&whc->umc->dev, "FIXME: host system error\n"); + + if (sts & WUSBSTS_ASYNC_SCHED_SYNCED) + wake_up(&whc->async_list_wq); + + if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED) + wake_up(&whc->periodic_list_wq); + + if (sts & WUSBSTS_DNTS_INT) + queue_work(whc->workqueue, &whc->dn_work); + + /* + * A transfer completed (see [WHCI] section 4.7.1.2 for when + * this occurs). + */ + if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT)) + transfer_done(whc); + + return IRQ_HANDLED; +} + +static int process_dn_buf(struct whc *whc) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + struct dn_buf_entry *dn; + int processed = 0; + + for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) { + if (dn->status & WHC_DN_STATUS_VALID) { + wusbhc_handle_dn(wusbhc, dn->src_addr, + (struct wusb_dn_hdr *)dn->dn_data, + dn->msg_size); + dn->status &= ~WHC_DN_STATUS_VALID; + processed++; + } + } + return processed; +} + +void whc_dn_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, dn_work); + int processed; + + do { + processed = process_dn_buf(whc); + } while (processed); +} diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c new file mode 100644 index 00000000000..8d62df0c330 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/uwb/umc.h> +#include <linux/usb.h> +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 4 +static void dump_pzl(struct whc *whc, const char *tag) +{ + struct device *dev = &whc->umc->dev; + struct whc_qset *qset; + int period = 0; + + d_printf(4, dev, "PZL %s\n", tag); + + for (period = 0; period < 5; period++) { + d_printf(4, dev, "Period %d\n", period); + list_for_each_entry(qset, &whc->periodic_list[period], list_node) { + dump_qset(qset, dev); + } + } +} +#else +static inline void dump_pzl(struct whc *whc, const char *tag) +{ +} +#endif + +static void update_pzl_pointers(struct whc *whc, int period, u64 addr) +{ + switch (period) { + case 0: + whc_qset_set_link_ptr(&whc->pz_list[0], addr); + whc_qset_set_link_ptr(&whc->pz_list[2], addr); + whc_qset_set_link_ptr(&whc->pz_list[4], addr); + whc_qset_set_link_ptr(&whc->pz_list[6], addr); + whc_qset_set_link_ptr(&whc->pz_list[8], addr); + whc_qset_set_link_ptr(&whc->pz_list[10], addr); + whc_qset_set_link_ptr(&whc->pz_list[12], addr); + whc_qset_set_link_ptr(&whc->pz_list[14], addr); + break; + case 1: + whc_qset_set_link_ptr(&whc->pz_list[1], addr); + whc_qset_set_link_ptr(&whc->pz_list[5], addr); + whc_qset_set_link_ptr(&whc->pz_list[9], addr); + whc_qset_set_link_ptr(&whc->pz_list[13], addr); + break; + case 2: + whc_qset_set_link_ptr(&whc->pz_list[3], addr); + whc_qset_set_link_ptr(&whc->pz_list[11], addr); + break; + case 3: + whc_qset_set_link_ptr(&whc->pz_list[7], addr); + break; + case 4: + whc_qset_set_link_ptr(&whc->pz_list[15], addr); + break; + } +} + +/* + * Return the 'period' to use for this qset. The minimum interval for + * the endpoint is used so whatever urbs are submitted the device is + * polled often enough. + */ +static int qset_get_period(struct whc *whc, struct whc_qset *qset) +{ + uint8_t bInterval = qset->ep->desc.bInterval; + + if (bInterval < 6) + bInterval = 6; + if (bInterval > 10) + bInterval = 10; + return bInterval - 6; +} + +static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset) +{ + int period; + + period = qset_get_period(whc, qset); + + qset_clear(whc, qset); + list_move(&qset->list_node, &whc->periodic_list[period]); + qset->in_sw_list = true; +} + +static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset) +{ + list_move(&qset->list_node, &whc->periodic_removed_list); + qset->in_hw_list = false; + qset->in_sw_list = false; +} + +/** + * pzl_process_qset - process any recently inactivated or halted qTDs + * in a qset. + * + * After inactive qTDs are removed, new qTDs can be added if the + * urb queue still contains URBs. + * + * Returns the schedule updates required. + */ +static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset) +{ + enum whc_update update = 0; + uint32_t status = 0; + + while (qset->ntds) { + struct whc_qtd *td; + int t; + + t = qset->td_start; + td = &qset->qtd[qset->td_start]; + status = le32_to_cpu(td->status); + + /* + * Nothing to do with a still active qTD. + */ + if (status & QTD_STS_ACTIVE) + break; + + if (status & QTD_STS_HALTED) { + /* Ug, an error. */ + process_halted_qtd(whc, qset, td); + goto done; + } + + /* Mmm, a completed qTD. */ + process_inactive_qtd(whc, qset, td); + } + + update |= qset_add_qtds(whc, qset); + +done: + /* + * If there are no qTDs in this qset, remove it from the PZL. + */ + if (qset->remove && qset->ntds == 0) { + pzl_qset_remove(whc, qset); + update |= WHC_UPDATE_REMOVED; + } + + return update; +} + +/** + * pzl_start - start the periodic schedule + * @whc: the WHCI host controller + * + * The PZL must be valid (e.g., all entries in the list should have + * the T bit set). + */ +void pzl_start(struct whc *whc) +{ + le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE); + + whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED, + 1000, "start PZL"); +} + +/** + * pzl_stop - stop the periodic schedule + * @whc: the WHCI host controller + */ +void pzl_stop(struct whc *whc) +{ + whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_PERIODIC_SCHED, 0, + 1000, "stop PZL"); +} + +void pzl_update(struct whc *whc, uint32_t wusbcmd) +{ + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + wait_event(whc->periodic_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0); +} + +static void update_pzl_hw_view(struct whc *whc) +{ + struct whc_qset *qset, *t; + int period; + u64 tmp_qh = 0; + + for (period = 0; period < 5; period++) { + list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { + whc_qset_set_link_ptr(&qset->qh.link, tmp_qh); + tmp_qh = qset->qset_dma; + qset->in_hw_list = true; + } + update_pzl_pointers(whc, period, tmp_qh); + } +} + +/** + * scan_periodic_work - scan the PZL for qsets to process. + * + * Process each qset in the PZL in turn and then signal the WHC that + * the PZL has been updated. + * + * Then start, stop or update the periodic schedule as required. + */ +void scan_periodic_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, periodic_work); + struct whc_qset *qset, *t; + enum whc_update update = 0; + int period; + + spin_lock_irq(&whc->lock); + + dump_pzl(whc, "before processing"); + + for (period = 4; period >= 0; period--) { + list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { + if (!qset->in_hw_list) + update |= WHC_UPDATE_ADDED; + update |= pzl_process_qset(whc, qset); + } + } + + if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED)) + update_pzl_hw_view(whc); + + dump_pzl(whc, "after processing"); + + spin_unlock_irq(&whc->lock); + + if (update) { + uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB; + if (update & WHC_UPDATE_REMOVED) + wusbcmd |= WUSBCMD_PERIODIC_QSET_RM; + pzl_update(whc, wusbcmd); + } + + /* + * Now that the PZL is updated, complete the removal of any + * removed qsets. + */ + spin_lock(&whc->lock); + + list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) { + qset_remove_complete(whc, qset); + } + + spin_unlock(&whc->lock); +} + +/** + * pzl_urb_enqueue - queue an URB onto the periodic list (PZL) + * @whc: the WHCI host controller + * @urb: the URB to enqueue + * @mem_flags: flags for any memory allocations + * + * The qset for the endpoint is obtained and the urb queued on to it. + * + * Work is scheduled to update the hardware's view of the PZL. + */ +int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags) +{ + struct whc_qset *qset; + int err; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + qset = get_qset(whc, urb, GFP_ATOMIC); + if (qset == NULL) + err = -ENOMEM; + else + err = qset_add_urb(whc, qset, urb, GFP_ATOMIC); + if (!err) { + usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb); + if (!qset->in_sw_list) + qset_insert_in_sw_list(whc, qset); + } + + spin_unlock_irqrestore(&whc->lock, flags); + + if (!err) + queue_work(whc->workqueue, &whc->periodic_work); + + return 0; +} + +/** + * pzl_urb_dequeue - remove an URB (qset) from the periodic list + * @whc: the WHCI host controller + * @urb: the URB to dequeue + * @status: the current status of the URB + * + * URBs that do yet have qTDs can simply be removed from the software + * queue, otherwise the qset must be removed so the qTDs can be safely + * removed. + */ +int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status) +{ + struct whc_urb *wurb = urb->hcpriv; + struct whc_qset *qset = wurb->qset; + struct whc_std *std, *t; + int ret; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status); + if (ret < 0) + goto out; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(whc, std); + else + std->qtd = NULL; /* so this std is re-added when the qset is */ + } + + pzl_qset_remove(whc, qset); + wurb->status = status; + wurb->is_async = false; + queue_work(whc->workqueue, &wurb->dequeue_work); + +out: + spin_unlock_irqrestore(&whc->lock, flags); + + return ret; +} + +/** + * pzl_qset_delete - delete a qset from the PZL + */ +void pzl_qset_delete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 1; + queue_work(whc->workqueue, &whc->periodic_work); + qset_delete(whc, qset); +} + + +/** + * pzl_init - initialize the periodic zone list + * @whc: the WHCI host controller + */ +int pzl_init(struct whc *whc) +{ + int i; + + whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16, + &whc->pz_list_dma, GFP_KERNEL); + if (whc->pz_list == NULL) + return -ENOMEM; + + /* Set T bit on all elements in PZL. */ + for (i = 0; i < 16; i++) + whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T); + + le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE); + + return 0; +} + +/** + * pzl_clean_up - free PZL resources + * @whc: the WHCI host controller + * + * The PZL is stopped and empty. + */ +void pzl_clean_up(struct whc *whc) +{ + if (whc->pz_list) + dma_free_coherent(&whc->umc->dev, sizeof(u64) * 16, whc->pz_list, + whc->pz_list_dma); +} diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c new file mode 100644 index 00000000000..0420037d2e1 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/uwb/umc.h> +#include <linux/usb.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +void dump_qset(struct whc_qset *qset, struct device *dev) +{ + struct whc_std *std; + struct urb *urb = NULL; + int i; + + dev_dbg(dev, "qset %08x\n", (u32)qset->qset_dma); + dev_dbg(dev, " -> %08x\n", (u32)qset->qh.link); + dev_dbg(dev, " info: %08x %08x %08x\n", + qset->qh.info1, qset->qh.info2, qset->qh.info3); + dev_dbg(dev, " sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count); + dev_dbg(dev, " TD: sts: %08x opts: %08x\n", + qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options); + + for (i = 0; i < WHCI_QSET_TD_MAX; i++) { + dev_dbg(dev, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n", + i == qset->td_start ? 'S' : ' ', + i == qset->td_end ? 'E' : ' ', + i, qset->qtd[i].status, qset->qtd[i].options, + (u32)qset->qtd[i].page_list_ptr); + } + dev_dbg(dev, " ntds: %d\n", qset->ntds); + list_for_each_entry(std, &qset->stds, list_node) { + if (urb != std->urb) { + urb = std->urb; + dev_dbg(dev, " urb %p transferred: %d bytes\n", urb, + urb->actual_length); + } + if (std->qtd) + dev_dbg(dev, " sTD[%td]: %zu bytes @ %08x\n", + std->qtd - &qset->qtd[0], + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + else + dev_dbg(dev, " sTD[-]: %zd bytes @ %08x\n", + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + } +} + +struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags) +{ + struct whc_qset *qset; + dma_addr_t dma; + + qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma); + if (qset == NULL) + return NULL; + memset(qset, 0, sizeof(struct whc_qset)); + + qset->qset_dma = dma; + qset->whc = whc; + + INIT_LIST_HEAD(&qset->list_node); + INIT_LIST_HEAD(&qset->stds); + + return qset; +} + +/** + * qset_fill_qh - fill the static endpoint state in a qset's QHead + * @qset: the qset whose QH needs initializing with static endpoint + * state + * @urb: an urb for a transfer to this endpoint + */ +static void qset_fill_qh(struct whc_qset *qset, struct urb *urb) +{ + struct usb_device *usb_dev = urb->dev; + struct usb_wireless_ep_comp_descriptor *epcd; + bool is_out; + + is_out = usb_pipeout(urb->pipe); + + epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra; + + if (epcd) { + qset->max_seq = epcd->bMaxSequence; + qset->max_burst = epcd->bMaxBurst; + } else { + qset->max_seq = 2; + qset->max_burst = 1; + } + + qset->qh.info1 = cpu_to_le32( + QH_INFO1_EP(usb_pipeendpoint(urb->pipe)) + | (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN) + | usb_pipe_to_qh_type(urb->pipe) + | QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum)) + | QH_INFO1_MAX_PKT_LEN(usb_maxpacket(urb->dev, urb->pipe, is_out)) + ); + qset->qh.info2 = cpu_to_le32( + QH_INFO2_BURST(qset->max_burst) + | QH_INFO2_DBP(0) + | QH_INFO2_MAX_COUNT(3) + | QH_INFO2_MAX_RETRY(3) + | QH_INFO2_MAX_SEQ(qset->max_seq - 1) + ); + /* FIXME: where can we obtain these Tx parameters from? Why + * doesn't the chip know what Tx power to use? It knows the Rx + * strength and can presumably guess the Tx power required + * from that? */ + qset->qh.info3 = cpu_to_le32( + QH_INFO3_TX_RATE_53_3 + | QH_INFO3_TX_PWR(0) /* 0 == max power */ + ); +} + +/** + * qset_clear - clear fields in a qset so it may be reinserted into a + * schedule + */ +void qset_clear(struct whc *whc, struct whc_qset *qset) +{ + qset->td_start = qset->td_end = qset->ntds = 0; + qset->remove = 0; + + qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T); + qset->qh.status = cpu_to_le16(QH_STATUS_ICUR(qset->td_start)); + qset->qh.err_count = 0; + qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1); + qset->qh.scratch[0] = 0; + qset->qh.scratch[1] = 0; + qset->qh.scratch[2] = 0; + + memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay)); + + init_completion(&qset->remove_complete); +} + +/** + * get_qset - get the qset for an async endpoint + * + * A new qset is created if one does not already exist. + */ +struct whc_qset *get_qset(struct whc *whc, struct urb *urb, + gfp_t mem_flags) +{ + struct whc_qset *qset; + + qset = urb->ep->hcpriv; + if (qset == NULL) { + qset = qset_alloc(whc, mem_flags); + if (qset == NULL) + return NULL; + + qset->ep = urb->ep; + urb->ep->hcpriv = qset; + qset_fill_qh(qset, urb); + } + return qset; +} + +void qset_remove_complete(struct whc *whc, struct whc_qset *qset) +{ + list_del_init(&qset->list_node); + complete(&qset->remove_complete); +} + +/** + * qset_add_qtds - add qTDs for an URB to a qset + * + * Returns true if the list (ASL/PZL) must be updated because (for a + * WHCI 0.95 controller) an activated qTD was pointed to be iCur. + */ +enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset) +{ + struct whc_std *std; + enum whc_update update = 0; + + list_for_each_entry(std, &qset->stds, list_node) { + struct whc_qtd *qtd; + uint32_t status; + + if (qset->ntds >= WHCI_QSET_TD_MAX + || (qset->pause_after_urb && std->urb != qset->pause_after_urb)) + break; + + if (std->qtd) + continue; /* already has a qTD */ + + qtd = std->qtd = &qset->qtd[qset->td_end]; + + /* Fill in setup bytes for control transfers. */ + if (usb_pipecontrol(std->urb->pipe)) + memcpy(qtd->setup, std->urb->setup_packet, 8); + + status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len); + + if (whc_std_last(std) && usb_pipeout(std->urb->pipe)) + status |= QTD_STS_LAST_PKT; + + /* + * For an IN transfer the iAlt field should be set so + * the h/w will automatically advance to the next + * transfer. However, if there are 8 or more TDs + * remaining in this transfer then iAlt cannot be set + * as it could point to somewhere in this transfer. + */ + if (std->ntds_remaining < WHCI_QSET_TD_MAX) { + int ialt; + ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX; + status |= QTD_STS_IALT(ialt); + } else if (usb_pipein(std->urb->pipe)) + qset->pause_after_urb = std->urb; + + if (std->num_pointers) + qtd->options = cpu_to_le32(QTD_OPT_IOC); + else + qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL); + qtd->page_list_ptr = cpu_to_le64(std->dma_addr); + + qtd->status = cpu_to_le32(status); + + if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end) + update = WHC_UPDATE_UPDATED; + + if (++qset->td_end >= WHCI_QSET_TD_MAX) + qset->td_end = 0; + qset->ntds++; + } + + return update; +} + +/** + * qset_remove_qtd - remove the first qTD from a qset. + * + * The qTD might be still active (if it's part of a IN URB that + * resulted in a short read) so ensure it's deactivated. + */ +static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset) +{ + qset->qtd[qset->td_start].status = 0; + + if (++qset->td_start >= WHCI_QSET_TD_MAX) + qset->td_start = 0; + qset->ntds--; +} + +/** + * qset_free_std - remove an sTD and free it. + * @whc: the WHCI host controller + * @std: the sTD to remove and free. + */ +void qset_free_std(struct whc *whc, struct whc_std *std) +{ + list_del(&std->list_node); + if (std->num_pointers) { + dma_unmap_single(whc->wusbhc.dev, std->dma_addr, + std->num_pointers * sizeof(struct whc_page_list_entry), + DMA_TO_DEVICE); + kfree(std->pl_virt); + } + + kfree(std); +} + +/** + * qset_remove_qtds - remove an URB's qTDs (and sTDs). + */ +static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset, + struct urb *urb) +{ + struct whc_std *std, *t; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb != urb) + break; + if (std->qtd != NULL) + qset_remove_qtd(whc, qset); + qset_free_std(whc, std); + } +} + +/** + * qset_free_stds - free any remaining sTDs for an URB. + */ +static void qset_free_stds(struct whc_qset *qset, struct urb *urb) +{ + struct whc_std *std, *t; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(qset->whc, std); + } +} + +static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags) +{ + dma_addr_t dma_addr = std->dma_addr; + dma_addr_t sp, ep; + size_t std_len = std->len; + size_t pl_len; + int p; + + sp = ALIGN(dma_addr, WHCI_PAGE_SIZE); + ep = dma_addr + std_len; + std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE); + + pl_len = std->num_pointers * sizeof(struct whc_page_list_entry); + std->pl_virt = kmalloc(pl_len, mem_flags); + if (std->pl_virt == NULL) + return -ENOMEM; + std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE); + + for (p = 0; p < std->num_pointers; p++) { + std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr); + dma_addr = ALIGN(dma_addr + WHCI_PAGE_SIZE, WHCI_PAGE_SIZE); + } + + return 0; +} + +/** + * urb_dequeue_work - executes asl/pzl update and gives back the urb to the system. + */ +static void urb_dequeue_work(struct work_struct *work) +{ + struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work); + struct whc_qset *qset = wurb->qset; + struct whc *whc = qset->whc; + unsigned long flags; + + if (wurb->is_async == true) + asl_update(whc, WUSBCMD_ASYNC_UPDATED + | WUSBCMD_ASYNC_SYNCED_DB + | WUSBCMD_ASYNC_QSET_RM); + else + pzl_update(whc, WUSBCMD_PERIODIC_UPDATED + | WUSBCMD_PERIODIC_SYNCED_DB + | WUSBCMD_PERIODIC_QSET_RM); + + spin_lock_irqsave(&whc->lock, flags); + qset_remove_urb(whc, qset, wurb->urb, wurb->status); + spin_unlock_irqrestore(&whc->lock, flags); +} + +/** + * qset_add_urb - add an urb to the qset's queue. + * + * The URB is chopped into sTDs, one for each qTD that will required. + * At least one qTD (and sTD) is required even if the transfer has no + * data (e.g., for some control transfers). + */ +int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags) +{ + struct whc_urb *wurb; + int remaining = urb->transfer_buffer_length; + u64 transfer_dma = urb->transfer_dma; + int ntds_remaining; + + ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE); + if (ntds_remaining == 0) + ntds_remaining = 1; + + wurb = kzalloc(sizeof(struct whc_urb), mem_flags); + if (wurb == NULL) + goto err_no_mem; + urb->hcpriv = wurb; + wurb->qset = qset; + wurb->urb = urb; + INIT_WORK(&wurb->dequeue_work, urb_dequeue_work); + + while (ntds_remaining) { + struct whc_std *std; + size_t std_len; + + std = kmalloc(sizeof(struct whc_std), mem_flags); + if (std == NULL) + goto err_no_mem; + + std_len = remaining; + if (std_len > QTD_MAX_XFER_SIZE) + std_len = QTD_MAX_XFER_SIZE; + + std->urb = urb; + std->dma_addr = transfer_dma; + std->len = std_len; + std->ntds_remaining = ntds_remaining; + std->qtd = NULL; + + INIT_LIST_HEAD(&std->list_node); + list_add_tail(&std->list_node, &qset->stds); + + if (std_len > WHCI_PAGE_SIZE) { + if (qset_fill_page_list(whc, std, mem_flags) < 0) + goto err_no_mem; + } else + std->num_pointers = 0; + + ntds_remaining--; + remaining -= std_len; + transfer_dma += std_len; + } + + return 0; + +err_no_mem: + qset_free_stds(qset, urb); + return -ENOMEM; +} + +/** + * qset_remove_urb - remove an URB from the urb queue. + * + * The URB is returned to the USB subsystem. + */ +void qset_remove_urb(struct whc *whc, struct whc_qset *qset, + struct urb *urb, int status) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + struct whc_urb *wurb = urb->hcpriv; + + usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb); + /* Drop the lock as urb->complete() may enqueue another urb. */ + spin_unlock(&whc->lock); + wusbhc_giveback_urb(wusbhc, urb, status); + spin_lock(&whc->lock); + + kfree(wurb); +} + +/** + * get_urb_status_from_qtd - get the completed urb status from qTD status + * @urb: completed urb + * @status: qTD status + */ +static int get_urb_status_from_qtd(struct urb *urb, u32 status) +{ + if (status & QTD_STS_HALTED) { + if (status & QTD_STS_DBE) + return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM; + else if (status & QTD_STS_BABBLE) + return -EOVERFLOW; + else if (status & QTD_STS_RCE) + return -ETIME; + return -EPIPE; + } + if (usb_pipein(urb->pipe) + && (urb->transfer_flags & URB_SHORT_NOT_OK) + && urb->actual_length < urb->transfer_buffer_length) + return -EREMOTEIO; + return 0; +} + +/** + * process_inactive_qtd - process an inactive (but not halted) qTD. + * + * Update the urb with the transfer bytes from the qTD, if the urb is + * completely transfered or (in the case of an IN only) the LPF is + * set, then the transfer is complete and the urb should be returned + * to the system. + */ +void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd) +{ + struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node); + struct urb *urb = std->urb; + uint32_t status; + bool complete; + + status = le32_to_cpu(qtd->status); + + urb->actual_length += std->len - QTD_STS_TO_LEN(status); + + if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT)) + complete = true; + else + complete = whc_std_last(std); + + qset_remove_qtd(whc, qset); + qset_free_std(whc, std); + + /* + * Transfers for this URB are complete? Then return it to the + * USB subsystem. + */ + if (complete) { + qset_remove_qtds(whc, qset, urb); + qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status)); + + /* + * If iAlt isn't valid then the hardware didn't + * advance iCur. Adjust the start and end pointers to + * match iCur. + */ + if (!(status & QTD_STS_IALT_VALID)) + qset->td_start = qset->td_end + = QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status)); + qset->pause_after_urb = NULL; + } +} + +/** + * process_halted_qtd - process a qset with a halted qtd + * + * Remove all the qTDs for the failed URB and return the failed URB to + * the USB subsystem. Then remove all other qTDs so the qset can be + * removed. + * + * FIXME: this is the point where rate adaptation can be done. If a + * transfer failed because it exceeded the maximum number of retries + * then it could be reactivated with a slower rate without having to + * remove the qset. + */ +void process_halted_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd) +{ + struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node); + struct urb *urb = std->urb; + int urb_status; + + urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status)); + + qset_remove_qtds(whc, qset, urb); + qset_remove_urb(whc, qset, urb, urb_status); + + list_for_each_entry(std, &qset->stds, list_node) { + if (qset->ntds == 0) + break; + qset_remove_qtd(whc, qset); + std->qtd = NULL; + } + + qset->remove = 1; +} + +void qset_free(struct whc *whc, struct whc_qset *qset) +{ + dma_pool_free(whc->qset_pool, qset, qset->qset_dma); +} + +/** + * qset_delete - wait for a qset to be unused, then free it. + */ +void qset_delete(struct whc *whc, struct whc_qset *qset) +{ + wait_for_completion(&qset->remove_complete); + qset_free(whc, qset); +} diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h new file mode 100644 index 00000000000..1d2a53bd39f --- /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 <linux/uwb/whci.h> +#include <linux/workqueue.h> + +#include "whci-hc.h" + +/* Generic command timeout. */ +#define WHC_GENCMD_TIMEOUT_MS 100 + + +struct whc { + struct wusbhc wusbhc; + struct umc_dev *umc; + + resource_size_t base_phys; + void __iomem *base; + int irq; + + u8 n_devices; + u8 n_keys; + u8 n_mmc_ies; + + u64 *pz_list; + struct dn_buf_entry *dn_buf; + struct di_buf_entry *di_buf; + dma_addr_t pz_list_dma; + dma_addr_t dn_buf_dma; + dma_addr_t di_buf_dma; + + spinlock_t lock; + struct mutex mutex; + + void * gen_cmd_buf; + dma_addr_t gen_cmd_buf_dma; + wait_queue_head_t cmd_wq; + + struct workqueue_struct *workqueue; + struct work_struct dn_work; + + struct dma_pool *qset_pool; + + struct list_head async_list; + struct list_head async_removed_list; + wait_queue_head_t async_list_wq; + struct work_struct async_work; + + struct list_head periodic_list[5]; + struct list_head periodic_removed_list; + wait_queue_head_t periodic_list_wq; + struct work_struct periodic_work; +}; + +#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc)) + +/** + * struct whc_std - a software TD. + * @urb: the URB this sTD is for. + * @offset: start of the URB's data for this TD. + * @len: the length of data in the associated TD. + * @ntds_remaining: number of TDs (starting from this one) in this transfer. + * + * Queued URBs may require more TDs than are available in a qset so we + * use a list of these "software TDs" (sTDs) to hold per-TD data. + */ +struct whc_std { + struct urb *urb; + size_t len; + int ntds_remaining; + struct whc_qtd *qtd; + + struct list_head list_node; + int num_pointers; + dma_addr_t dma_addr; + struct whc_page_list_entry *pl_virt; +}; + +/** + * struct whc_urb - per URB host controller structure. + * @urb: the URB this struct is for. + * @qset: the qset associated to the URB. + * @dequeue_work: the work to remove the URB when dequeued. + * @is_async: the URB belongs to async sheduler or not. + * @status: the status to be returned when calling wusbhc_giveback_urb. + */ +struct whc_urb { + struct urb *urb; + struct whc_qset *qset; + struct work_struct dequeue_work; + bool is_async; + int status; +}; + +/** + * whc_std_last - is this sTD the URB's last? + * @std: the sTD to check. + */ +static inline bool whc_std_last(struct whc_std *std) +{ + return std->ntds_remaining <= 1; +} + +enum whc_update { + WHC_UPDATE_ADDED = 0x01, + WHC_UPDATE_REMOVED = 0x02, + WHC_UPDATE_UPDATED = 0x04, +}; + +/* init.c */ +int whc_init(struct whc *whc); +void whc_clean_up(struct whc *whc); + +/* hw.c */ +void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val); +int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len); + +/* wusb.c */ +int whc_wusbhc_start(struct wusbhc *wusbhc); +void whc_wusbhc_stop(struct wusbhc *wusbhc); +int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie); +int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle); +int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm); +int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev); +int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots); +int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *ptk, size_t key_size); +int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *gtk, size_t key_size); +int whc_set_cluster_id(struct whc *whc, u8 bcid); + +/* int.c */ +irqreturn_t whc_int_handler(struct usb_hcd *hcd); +void whc_dn_work(struct work_struct *work); + +/* asl.c */ +void asl_start(struct whc *whc); +void asl_stop(struct whc *whc); +int asl_init(struct whc *whc); +void asl_clean_up(struct whc *whc); +int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags); +int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status); +void asl_qset_delete(struct whc *whc, struct whc_qset *qset); +void scan_async_work(struct work_struct *work); + +/* pzl.c */ +int pzl_init(struct whc *whc); +void pzl_clean_up(struct whc *whc); +void pzl_start(struct whc *whc); +void pzl_stop(struct whc *whc); +int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags); +int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status); +void pzl_qset_delete(struct whc *whc, struct whc_qset *qset); +void scan_periodic_work(struct work_struct *work); + +/* qset.c */ +struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags); +void qset_free(struct whc *whc, struct whc_qset *qset); +struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags); +void qset_delete(struct whc *whc, struct whc_qset *qset); +void qset_clear(struct whc *whc, struct whc_qset *qset); +int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags); +void qset_free_std(struct whc *whc, struct whc_std *std); +void qset_remove_urb(struct whc *whc, struct whc_qset *qset, + struct urb *urb, int status); +void process_halted_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd); +void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd); +enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset); +void qset_remove_complete(struct whc *whc, struct whc_qset *qset); +void dump_qset(struct whc_qset *qset, struct device *dev); +void pzl_update(struct whc *whc, uint32_t wusbcmd); +void asl_update(struct whc *whc, uint32_t wusbcmd); + +#endif /* #ifndef __WHCD_H */ diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h new file mode 100644 index 00000000000..bff1eb7a35c --- /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 <linux/list.h> + +/** + * WHCI_PAGE_SIZE - page size use by WHCI + * + * WHCI assumes that host system uses pages of 4096 octets. + */ +#define WHCI_PAGE_SIZE 4096 + + +/** + * QTD_MAX_TXFER_SIZE - max number of bytes to transfer with a single + * qtd. + * + * This is 2^20 - 1. + */ +#define QTD_MAX_XFER_SIZE 1048575 + + +/** + * struct whc_qtd - Queue Element Transfer Descriptors (qTD) + * + * This describes the data for a bulk, control or interrupt transfer. + * + * [WHCI] section 3.2.4 + */ +struct whc_qtd { + __le32 status; /*< remaining transfer len and transfer status */ + __le32 options; + __le64 page_list_ptr; /*< physical pointer to data buffer page list*/ + __u8 setup[8]; /*< setup data for control transfers */ +} __attribute__((packed)); + +#define QTD_STS_ACTIVE (1 << 31) /* enable execution of transaction */ +#define QTD_STS_HALTED (1 << 30) /* transfer halted */ +#define QTD_STS_DBE (1 << 29) /* data buffer error */ +#define QTD_STS_BABBLE (1 << 28) /* babble detected */ +#define QTD_STS_RCE (1 << 27) /* retry count exceeded */ +#define QTD_STS_LAST_PKT (1 << 26) /* set Last Packet Flag in WUSB header */ +#define QTD_STS_INACTIVE (1 << 25) /* queue set is marked inactive */ +#define QTD_STS_IALT_VALID (1 << 23) /* iAlt field is valid */ +#define QTD_STS_IALT(i) (QTD_STS_IALT_VALID | ((i) << 20)) /* iAlt field */ +#define QTD_STS_LEN(l) ((l) << 0) /* transfer length */ +#define QTD_STS_TO_LEN(s) ((s) & 0x000fffff) + +#define QTD_OPT_IOC (1 << 1) /* page_list_ptr points to buffer directly */ +#define QTD_OPT_SMALL (1 << 0) /* interrupt on complete */ + +/** + * struct whc_itd - Isochronous Queue Element Transfer Descriptors (iTD) + * + * This describes the data and other parameters for an isochronous + * transfer. + * + * [WHCI] section 3.2.5 + */ +struct whc_itd { + __le16 presentation_time; /*< presentation time for OUT transfers */ + __u8 num_segments; /*< number of data segments in segment list */ + __u8 status; /*< command execution status */ + __le32 options; /*< misc transfer options */ + __le64 page_list_ptr; /*< physical pointer to data buffer page list */ + __le64 seg_list_ptr; /*< physical pointer to segment list */ +} __attribute__((packed)); + +#define ITD_STS_ACTIVE (1 << 7) /* enable execution of transaction */ +#define ITD_STS_DBE (1 << 5) /* data buffer error */ +#define ITD_STS_BABBLE (1 << 4) /* babble detected */ +#define ITD_STS_INACTIVE (1 << 1) /* queue set is marked inactive */ + +#define ITD_OPT_IOC (1 << 1) /* interrupt on complete */ +#define ITD_OPT_SMALL (1 << 0) /* page_list_ptr points to buffer directly */ + +/** + * Page list entry. + * + * A TD's page list must contain sufficient page list entries for the + * total data length in the TD. + * + * [WHCI] section 3.2.4.3 + */ +struct whc_page_list_entry { + __le64 buf_ptr; /*< physical pointer to buffer */ +} __attribute__((packed)); + +/** + * struct whc_seg_list_entry - Segment list entry. + * + * Describes a portion of the data buffer described in the containing + * qTD's page list. + * + * seg_ptr = qtd->page_list_ptr[qtd->seg_list_ptr[seg].idx].buf_ptr + * + qtd->seg_list_ptr[seg].offset; + * + * Segments can't cross page boundries. + * + * [WHCI] section 3.2.5.5 + */ +struct whc_seg_list_entry { + __le16 len; /*< segment length */ + __u8 idx; /*< index into page list */ + __u8 status; /*< segment status */ + __le16 offset; /*< 12 bit offset into page */ +} __attribute__((packed)); + +/** + * struct whc_qhead - endpoint and status information for a qset. + * + * [WHCI] section 3.2.6 + */ +struct whc_qhead { + __le64 link; /*< next qset in list */ + __le32 info1; + __le32 info2; + __le32 info3; + __le16 status; + __le16 err_count; /*< transaction error count */ + __le32 cur_window; + __le32 scratch[3]; /*< h/w scratch area */ + union { + struct whc_qtd qtd; + struct whc_itd itd; + } overlay; +} __attribute__((packed)); + +#define QH_LINK_PTR_MASK (~0x03Full) +#define QH_LINK_PTR(ptr) ((ptr) & QH_LINK_PTR_MASK) +#define QH_LINK_IQS (1 << 4) /* isochronous queue set */ +#define QH_LINK_NTDS(n) (((n) - 1) << 1) /* number of TDs in queue set */ +#define QH_LINK_T (1 << 0) /* last queue set in periodic schedule list */ + +#define QH_INFO1_EP(e) ((e) << 0) /* endpoint number */ +#define QH_INFO1_DIR_IN (1 << 4) /* IN transfer */ +#define QH_INFO1_DIR_OUT (0 << 4) /* OUT transfer */ +#define QH_INFO1_TR_TYPE_CTRL (0x0 << 5) /* control transfer */ +#define QH_INFO1_TR_TYPE_ISOC (0x1 << 5) /* isochronous transfer */ +#define QH_INFO1_TR_TYPE_BULK (0x2 << 5) /* bulk transfer */ +#define QH_INFO1_TR_TYPE_INT (0x3 << 5) /* interrupt */ +#define QH_INFO1_TR_TYPE_LP_INT (0x7 << 5) /* low power interrupt */ +#define QH_INFO1_DEV_INFO_IDX(i) ((i) << 8) /* index into device info buffer */ +#define QH_INFO1_SET_INACTIVE (1 << 15) /* set inactive after transfer */ +#define QH_INFO1_MAX_PKT_LEN(l) ((l) << 16) /* maximum packet length */ + +#define QH_INFO2_BURST(b) ((b) << 0) /* maximum burst length */ +#define QH_INFO2_DBP(p) ((p) << 5) /* data burst policy (see [WUSB] table 5-7) */ +#define QH_INFO2_MAX_COUNT(c) ((c) << 8) /* max isoc/int pkts per zone */ +#define QH_INFO2_RQS (1 << 15) /* reactivate queue set */ +#define QH_INFO2_MAX_RETRY(r) ((r) << 16) /* maximum transaction retries */ +#define QH_INFO2_MAX_SEQ(s) ((s) << 20) /* maximum sequence number */ +#define QH_INFO3_MAX_DELAY(d) ((d) << 0) /* maximum stream delay in 125 us units (isoc only) */ +#define QH_INFO3_INTERVAL(i) ((i) << 16) /* segment interval in 125 us units (isoc only) */ + +#define QH_INFO3_TX_RATE_53_3 (0 << 24) +#define QH_INFO3_TX_RATE_80 (1 << 24) +#define QH_INFO3_TX_RATE_106_7 (2 << 24) +#define QH_INFO3_TX_RATE_160 (3 << 24) +#define QH_INFO3_TX_RATE_200 (4 << 24) +#define QH_INFO3_TX_RATE_320 (5 << 24) +#define QH_INFO3_TX_RATE_400 (6 << 24) +#define QH_INFO3_TX_RATE_480 (7 << 24) +#define QH_INFO3_TX_PWR(p) ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */ + +#define QH_STATUS_FLOW_CTRL (1 << 15) +#define QH_STATUS_ICUR(i) ((i) << 5) +#define QH_STATUS_TO_ICUR(s) (((s) >> 5) & 0x7) + +/** + * usb_pipe_to_qh_type - USB core pipe type to QH transfer type + * + * Returns the QH type field for a USB core pipe type. + */ +static inline unsigned usb_pipe_to_qh_type(unsigned pipe) +{ + static const unsigned type[] = { + [PIPE_ISOCHRONOUS] = QH_INFO1_TR_TYPE_ISOC, + [PIPE_INTERRUPT] = QH_INFO1_TR_TYPE_INT, + [PIPE_CONTROL] = QH_INFO1_TR_TYPE_CTRL, + [PIPE_BULK] = QH_INFO1_TR_TYPE_BULK, + }; + return type[usb_pipetype(pipe)]; +} + +/** + * Maxiumum number of TDs in a qset. + */ +#define WHCI_QSET_TD_MAX 8 + +/** + * struct whc_qset - WUSB data transfers to a specific endpoint + * @qh: the QHead of this qset + * @qtd: up to 8 qTDs (for qsets for control, bulk and interrupt + * transfers) + * @itd: up to 8 iTDs (for qsets for isochronous transfers) + * @qset_dma: DMA address for this qset + * @whc: WHCI HC this qset is for + * @ep: endpoint + * @stds: list of sTDs queued to this qset + * @ntds: number of qTDs queued (not necessarily the same as nTDs + * field in the QH) + * @td_start: index of the first qTD in the list + * @td_end: index of next free qTD in the list (provided + * ntds < WHCI_QSET_TD_MAX) + * + * Queue Sets (qsets) are added to the asynchronous schedule list + * (ASL) or the periodic zone list (PZL). + * + * qsets may contain up to 8 TDs (either qTDs or iTDs as appropriate). + * Each TD may refer to at most 1 MiB of data. If a single transfer + * has > 8MiB of data, TDs can be reused as they are completed since + * the TD list is used as a circular buffer. Similarly, several + * (smaller) transfers may be queued in a qset. + * + * WHCI controllers may cache portions of the qsets in the ASL and + * PZL, requiring the WHCD to inform the WHC that the lists have been + * updated (fields changed or qsets inserted or removed). For safe + * insertion and removal of qsets from the lists the schedule must be + * stopped to avoid races in updating the QH link pointers. + * + * Since the HC is free to execute qsets in any order, all transfers + * to an endpoint should use the same qset to ensure transfers are + * executed in the order they're submitted. + * + * [WHCI] section 3.2.3 + */ +struct whc_qset { + struct whc_qhead qh; + union { + struct whc_qtd qtd[WHCI_QSET_TD_MAX]; + struct whc_itd itd[WHCI_QSET_TD_MAX]; + }; + + /* private data for WHCD */ + dma_addr_t qset_dma; + struct whc *whc; + struct usb_host_endpoint *ep; + struct list_head stds; + int ntds; + int td_start; + int td_end; + struct list_head list_node; + unsigned in_sw_list:1; + unsigned in_hw_list:1; + unsigned remove:1; + struct urb *pause_after_urb; + struct completion remove_complete; + int max_burst; + int max_seq; +}; + +static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target) +{ + if (target) + *ptr = (*ptr & ~(QH_LINK_PTR_MASK | QH_LINK_T)) | QH_LINK_PTR(target); + else + *ptr = QH_LINK_T; +} + +/** + * struct di_buf_entry - Device Information (DI) buffer entry. + * + * There's one of these per connected device. + */ +struct di_buf_entry { + __le32 availability_info[8]; /*< MAS availability information, one MAS per bit */ + __le32 addr_sec_info; /*< addressing and security info */ + __le32 reserved[7]; +} __attribute__((packed)); + +#define WHC_DI_SECURE (1 << 31) +#define WHC_DI_DISABLE (1 << 30) +#define WHC_DI_KEY_IDX(k) ((k) << 8) +#define WHC_DI_KEY_IDX_MASK 0x0000ff00 +#define WHC_DI_DEV_ADDR(a) ((a) << 0) +#define WHC_DI_DEV_ADDR_MASK 0x000000ff + +/** + * struct dn_buf_entry - Device Notification (DN) buffer entry. + * + * [WHCI] section 3.2.8 + */ +struct dn_buf_entry { + __u8 msg_size; /*< number of octets of valid DN data */ + __u8 reserved1; + __u8 src_addr; /*< source address */ + __u8 status; /*< buffer entry status */ + __le32 tkid; /*< TKID for source device, valid if secure bit is set */ + __u8 dn_data[56]; /*< up to 56 octets of DN data */ +} __attribute__((packed)); + +#define WHC_DN_STATUS_VALID (1 << 7) /* buffer entry is valid */ +#define WHC_DN_STATUS_SECURE (1 << 6) /* notification received using secure frame */ + +#define WHC_N_DN_ENTRIES (4096 / sizeof(struct dn_buf_entry)) + +/* The Add MMC IE WUSB Generic Command may take up to 256 bytes of + data. [WHCI] section 2.4.7. */ +#define WHC_GEN_CMD_DATA_LEN 256 + +/* + * HC registers. + * + * [WHCI] section 2.4 + */ + +#define WHCIVERSION 0x00 + +#define WHCSPARAMS 0x04 +# define WHCSPARAMS_TO_N_MMC_IES(p) (((p) >> 16) & 0xff) +# define WHCSPARAMS_TO_N_KEYS(p) (((p) >> 8) & 0xff) +# define WHCSPARAMS_TO_N_DEVICES(p) (((p) >> 0) & 0x7f) + +#define WUSBCMD 0x08 +# define WUSBCMD_BCID(b) ((b) << 16) +# define WUSBCMD_BCID_MASK (0xff << 16) +# define WUSBCMD_ASYNC_QSET_RM (1 << 12) +# define WUSBCMD_PERIODIC_QSET_RM (1 << 11) +# define WUSBCMD_WUSBSI(s) ((s) << 8) +# define WUSBCMD_WUSBSI_MASK (0x7 << 8) +# define WUSBCMD_ASYNC_SYNCED_DB (1 << 7) +# define WUSBCMD_PERIODIC_SYNCED_DB (1 << 6) +# define WUSBCMD_ASYNC_UPDATED (1 << 5) +# define WUSBCMD_PERIODIC_UPDATED (1 << 4) +# define WUSBCMD_ASYNC_EN (1 << 3) +# define WUSBCMD_PERIODIC_EN (1 << 2) +# define WUSBCMD_WHCRESET (1 << 1) +# define WUSBCMD_RUN (1 << 0) + +#define WUSBSTS 0x0c +# define WUSBSTS_ASYNC_SCHED (1 << 15) +# define WUSBSTS_PERIODIC_SCHED (1 << 14) +# define WUSBSTS_DNTS_SCHED (1 << 13) +# define WUSBSTS_HCHALTED (1 << 12) +# define WUSBSTS_GEN_CMD_DONE (1 << 9) +# define WUSBSTS_CHAN_TIME_ROLLOVER (1 << 8) +# define WUSBSTS_DNTS_OVERFLOW (1 << 7) +# define WUSBSTS_BPST_ADJUSTMENT_CHANGED (1 << 6) +# define WUSBSTS_HOST_ERR (1 << 5) +# define WUSBSTS_ASYNC_SCHED_SYNCED (1 << 4) +# define WUSBSTS_PERIODIC_SCHED_SYNCED (1 << 3) +# define WUSBSTS_DNTS_INT (1 << 2) +# define WUSBSTS_ERR_INT (1 << 1) +# define WUSBSTS_INT (1 << 0) +# define WUSBSTS_INT_MASK 0x3ff + +#define WUSBINTR 0x10 +# define WUSBINTR_GEN_CMD_DONE (1 << 9) +# define WUSBINTR_CHAN_TIME_ROLLOVER (1 << 8) +# define WUSBINTR_DNTS_OVERFLOW (1 << 7) +# define WUSBINTR_BPST_ADJUSTMENT_CHANGED (1 << 6) +# define WUSBINTR_HOST_ERR (1 << 5) +# define WUSBINTR_ASYNC_SCHED_SYNCED (1 << 4) +# define WUSBINTR_PERIODIC_SCHED_SYNCED (1 << 3) +# define WUSBINTR_DNTS_INT (1 << 2) +# define WUSBINTR_ERR_INT (1 << 1) +# define WUSBINTR_INT (1 << 0) +# define WUSBINTR_ALL 0x3ff + +#define WUSBGENCMDSTS 0x14 +# define WUSBGENCMDSTS_ACTIVE (1 << 31) +# define WUSBGENCMDSTS_ERROR (1 << 24) +# define WUSBGENCMDSTS_IOC (1 << 23) +# define WUSBGENCMDSTS_MMCIE_ADD 0x01 +# define WUSBGENCMDSTS_MMCIE_RM 0x02 +# define WUSBGENCMDSTS_SET_MAS 0x03 +# define WUSBGENCMDSTS_CHAN_STOP 0x04 +# define WUSBGENCMDSTS_RWP_EN 0x05 + +#define WUSBGENCMDPARAMS 0x18 +#define WUSBGENADDR 0x20 +#define WUSBASYNCLISTADDR 0x28 +#define WUSBDNTSBUFADDR 0x30 +#define WUSBDEVICEINFOADDR 0x38 + +#define WUSBSETSECKEYCMD 0x40 +# define WUSBSETSECKEYCMD_SET (1 << 31) +# define WUSBSETSECKEYCMD_ERASE (1 << 30) +# define WUSBSETSECKEYCMD_GTK (1 << 8) +# define WUSBSETSECKEYCMD_IDX(i) ((i) << 0) + +#define WUSBTKID 0x44 +#define WUSBSECKEY 0x48 +#define WUSBPERIODICLISTBASE 0x58 +#define WUSBMASINDEX 0x60 + +#define WUSBDNTSCTRL 0x64 +# define WUSBDNTSCTRL_ACTIVE (1 << 31) +# define WUSBDNTSCTRL_INTERVAL(i) ((i) << 8) +# define WUSBDNTSCTRL_SLOTS(s) ((s) << 0) + +#define WUSBTIME 0x68 +#define WUSBBPST 0x6c +#define WUSBDIBUPDATED 0x70 + +#endif /* #ifndef _WHCI_WHCI_HC_H */ diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c new file mode 100644 index 00000000000..66e4ddcd961 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/uwb/umc.h> +#define D_LOCAL 1 +#include <linux/uwb/debug.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 1 +static void dump_di(struct whc *whc, int idx) +{ + struct di_buf_entry *di = &whc->di_buf[idx]; + struct device *dev = &whc->umc->dev; + char buf[128]; + + bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS); + + d_printf(1, dev, "DI[%d]\n", idx); + d_printf(1, dev, " availability: %s\n", buf); + d_printf(1, dev, " %c%c key idx: %d dev addr: %d\n", + (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ', + (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ', + (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8, + (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK)); +} +#else +static inline void dump_di(struct whc *whc, int idx) +{ +} +#endif + +static int whc_update_di(struct whc *whc, int idx) +{ + int offset = idx / 32; + u32 bit = 1 << (idx % 32); + + dump_di(whc, idx); + + le_writel(bit, whc->base + WUSBDIBUPDATED + offset); + + return whci_wait_for(&whc->umc->dev, + whc->base + WUSBDIBUPDATED + offset, bit, 0, + 100, "DI update"); +} + +/* + * WHCI starts and stops MMCs based on there being a valid GTK so + * these need only start/stop the asynchronous and periodic schedules. + */ + +int whc_wusbhc_start(struct wusbhc *wusbhc) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + asl_start(whc); + pzl_start(whc); + + return 0; +} + +void whc_wusbhc_stop(struct wusbhc *wusbhc) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + pzl_stop(whc); + asl_stop(whc); +} + +int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 params; + + params = (interval << 24) + | (repeat_cnt << 16) + | (wuie->bLength << 8) + | handle; + + return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength); +} + +int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 params; + + params = handle; + + return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0); +} + +int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + if (stream_index >= 0) + whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index)); + + return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm)); +} + +int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + int idx = wusb_dev->port_idx; + struct di_buf_entry *di = &whc->di_buf[idx]; + int ret; + + mutex_lock(&whc->mutex); + + uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability); + di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK); + di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr); + + ret = whc_update_di(whc, idx); + + mutex_unlock(&whc->mutex); + + return ret; +} + +/* + * Set the number of Device Notification Time Slots (DNTS) and enable + * device notifications. + */ +int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 dntsctrl; + + dntsctrl = WUSBDNTSCTRL_ACTIVE + | WUSBDNTSCTRL_INTERVAL(interval) + | WUSBDNTSCTRL_SLOTS(slots); + + le_writel(dntsctrl, whc->base + WUSBDNTSCTRL); + + return 0; +} + +static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid, + const void *key, size_t key_size, bool is_gtk) +{ + uint32_t setkeycmd; + uint32_t seckey[4]; + int i; + int ret; + + memcpy(seckey, key, key_size); + setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index); + if (is_gtk) + setkeycmd |= WUSBSETSECKEYCMD_GTK; + + le_writel(tkid, whc->base + WUSBTKID); + for (i = 0; i < 4; i++) + le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i); + le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD); + + ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD, + WUSBSETSECKEYCMD_SET, 0, 100, "set key"); + + return ret; +} + +/** + * whc_set_ptk - set the PTK to use for a device. + * + * The index into the key table for this PTK is the same as the + * device's port index. + */ +int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *ptk, size_t key_size) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + struct di_buf_entry *di = &whc->di_buf[port_idx]; + int ret; + + mutex_lock(&whc->mutex); + + if (ptk) { + ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false); + if (ret) + goto out; + + di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK; + di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx); + } else + di->addr_sec_info &= ~WHC_DI_SECURE; + + ret = whc_update_di(whc, port_idx); +out: + mutex_unlock(&whc->mutex); + return ret; +} + +/** + * whc_set_gtk - set the GTK for subsequent broadcast packets + * + * The GTK is stored in the last entry in the key table (the previous + * N_DEVICES entries are for the per-device PTKs). + */ +int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *gtk, size_t key_size) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + mutex_lock(&whc->mutex); + + ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true); + + mutex_unlock(&whc->mutex); + + return ret; +} + +int whc_set_cluster_id(struct whc *whc, u8 bcid) +{ + whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid)); + return 0; +} diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig new file mode 100644 index 00000000000..eb09a0a14a8 --- /dev/null +++ b/drivers/usb/wusbcore/Kconfig @@ -0,0 +1,41 @@ +# +# Wireless USB Core configuration +# +config USB_WUSB + tristate "Enable Wireless USB extensions (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on USB + select UWB + select CRYPTO + select CRYPTO_BLKCIPHER + select CRYPTO_CBC + select CRYPTO_MANAGER + select CRYPTO_AES + help + Enable the host-side support for Wireless USB. + + To compile this support select Y (built in). It is safe to + select even if you don't have the hardware. + +config USB_WUSB_CBAF + tristate "Support WUSB Cable Based Association (CBA)" + depends on USB + help + Some WUSB devices support Cable Based Association. It's used to + enable the secure communication between the host and the + device. + + Enable this option if your WUSB device must to be connected + via wired USB before establishing a wireless link. + + It is safe to select even if you don't have a compatible + hardware. + +config USB_WUSB_CBAF_DEBUG + bool "Enable CBA debug messages" + depends on USB_WUSB_CBAF + help + Say Y here if you want the CBA to produce a bunch of debug messages + to the system log. Select this if you are having a problem with + CBA support and want to see more of what is going on. + diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile new file mode 100644 index 00000000000..75f1ade6625 --- /dev/null +++ b/drivers/usb/wusbcore/Makefile @@ -0,0 +1,26 @@ +obj-$(CONFIG_USB_WUSB) += wusbcore.o +obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o +obj-$(CONFIG_USB_WUSB_CBAF) += wusb-cbaf.o + + +wusbcore-objs := \ + crypto.o \ + devconnect.o \ + dev-sysfs.o \ + mmc.o \ + pal.o \ + rh.o \ + reservation.o \ + security.o \ + wusbhc.o + +wusb-cbaf-objs := cbaf.o + +wusb-wa-objs := wa-hc.o \ + wa-nep.o \ + wa-rpipe.o \ + wa-xfer.o + +ifeq ($(CONFIG_USB_WUSB_CBAF_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/usb/wusbcore/cbaf.c b/drivers/usb/wusbcore/cbaf.c new file mode 100644 index 00000000000..ab4788d1785 --- /dev/null +++ b/drivers/usb/wusbcore/cbaf.c @@ -0,0 +1,673 @@ +/* + * Wireless USB - Cable Based Association + * + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * WUSB devices have to be paired (associated in WUSB lingo) so + * that they can connect to the system. + * + * One way of pairing is using CBA-Cable Based Association. First + * time you plug the device with a cable, association is done between + * host and device and subsequent times, you can connect wirelessly + * without having to associate again. That's the idea. + * + * This driver does nothing Earth shattering. It just provides an + * interface to chat with the wire-connected device so we can get a + * CDID (device ID) that might have been previously associated to a + * CHID (host ID) and to set up a new <CHID,CDID,CK> triplet + * (connection context), with the CK being the secret, or connection + * key. This is the pairing data. + * + * When a device with the CBA capability connects, the probe routine + * just creates a bunch of sysfs files that a user space enumeration + * manager uses to allow it to connect wirelessly to the system or not. + * + * The process goes like this: + * + * 1. Device plugs, cbaf is loaded, notifications happen. + * + * 2. The connection manager (CM) sees a device with CBAF capability + * (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE). + * + * 3. The CM writes the host name, supported band groups, and the CHID + * (host ID) into the wusb_host_name, wusb_host_band_groups and + * wusb_chid files. These get sent to the device and the CDID (if + * any) for this host is requested. + * + * 4. The CM can verify that the device's supported band groups + * (wusb_device_band_groups) are compatible with the host. + * + * 5. The CM reads the wusb_cdid file. + * + * 6. The CM looks up its database + * + * 6.1 If it has a matching CHID,CDID entry, the device has been + * authorized before (paired) and nothing further needs to be + * done. + * + * 6.2 If the CDID is zero (or the CM doesn't find a matching CDID in + * its database), the device is assumed to be not known. The CM + * may associate the host with device by: writing a randomly + * generated CDID to wusb_cdid and then a random CK to wusb_ck + * (this uploads the new CC to the device). + * + * CMD may choose to prompt the user before associating with a new + * device. + * + * 7. Device is unplugged. + * + * When the device tries to connect wirelessly, it will present its + * CDID to the WUSB host controller. The CM will query the + * database. If the CHID/CDID pair found, it will (with a 4-way + * handshake) challenge the device to demonstrate it has the CK secret + * key (from our database) without actually exchanging it. Once + * satisfied, crypto keys are derived from the CK, the device is + * connected and all communication is encrypted. + * + * References: + * [WUSB-AM] Association Models Supplement to the Certified Wireless + * Universal Serial Bus Specification, version 1.0. + */ +#include <linux/module.h> +#include <linux/ctype.h> +#include <linux/version.h> +#include <linux/usb.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/random.h> +#include <linux/mutex.h> +#include <linux/uwb.h> +#include <linux/usb/wusb.h> +#include <linux/usb/association.h> + +#define CBA_NAME_LEN 0x40 /* [WUSB-AM] table 4-7 */ + +/* An instance of a Cable-Based-Association-Framework device */ +struct cbaf { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + void *buffer; + size_t buffer_size; + + struct wusb_ckhdid chid; + char host_name[CBA_NAME_LEN]; + u16 host_band_groups; + + struct wusb_ckhdid cdid; + char device_name[CBA_NAME_LEN]; + u16 device_band_groups; + + struct wusb_ckhdid ck; +}; + +/* + * Verify that a CBAF USB-interface has what we need + * + * According to [WUSB-AM], CBA devices should provide at least two + * interfaces: + * - RETRIEVE_HOST_INFO + * - ASSOCIATE + * + * If the device doesn't provide these interfaces, we do not know how + * to deal with it. + */ +static int cbaf_check(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_assoc_info *assoc_info; + struct wusb_cbaf_assoc_request *assoc_request; + size_t assoc_size; + void *itr, *top; + int ar_rhi = 0, ar_assoc = 0; + + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_INFORMATION, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + cbaf->buffer, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot get available association types: %d\n", + result); + return result; + } + + assoc_info = cbaf->buffer; + if (result < sizeof(*assoc_info)) { + dev_err(dev, "Not enough data to decode association info " + "header (%zu vs %zu bytes required)\n", + (size_t)result, sizeof(*assoc_info)); + return result; + } + + assoc_size = le16_to_cpu(assoc_info->Length); + if (result < assoc_size) { + dev_err(dev, "Not enough data to decode association info " + "(%zu vs %zu bytes required)\n", + (size_t)assoc_size, sizeof(*assoc_info)); + return result; + } + /* + * From now on, we just verify, but won't error out unless we + * don't find the AR_TYPE_WUSB_{RETRIEVE_HOST_INFO,ASSOCIATE} + * types. + */ + itr = cbaf->buffer + sizeof(*assoc_info); + top = cbaf->buffer + assoc_size; + dev_dbg(dev, "Found %u association requests (%zu bytes)\n", + assoc_info->NumAssociationRequests, assoc_size); + + while (itr < top) { + u16 ar_type, ar_subtype; + u32 ar_size; + const char *ar_name; + + assoc_request = itr; + + if (top - itr < sizeof(*assoc_request)) { + dev_err(dev, "Not enough data to decode associaton " + "request (%zu vs %zu bytes needed)\n", + top - itr, sizeof(*assoc_request)); + break; + } + + ar_type = le16_to_cpu(assoc_request->AssociationTypeId); + ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId); + ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize); + ar_name = "unknown"; + + switch (ar_type) { + case AR_TYPE_WUSB: + /* Verify we have what is mandated by [WUSB-AM]. */ + switch (ar_subtype) { + case AR_TYPE_WUSB_RETRIEVE_HOST_INFO: + ar_name = "RETRIEVE_HOST_INFO"; + ar_rhi = 1; + break; + case AR_TYPE_WUSB_ASSOCIATE: + /* send assoc data */ + ar_name = "ASSOCIATE"; + ar_assoc = 1; + break; + }; + break; + }; + + dev_dbg(dev, "Association request #%02u: 0x%04x/%04x " + "(%zu bytes): %s\n", + assoc_request->AssociationDataIndex, ar_type, + ar_subtype, (size_t)ar_size, ar_name); + + itr += sizeof(*assoc_request); + } + + if (!ar_rhi) { + dev_err(dev, "Missing RETRIEVE_HOST_INFO association " + "request\n"); + return -EINVAL; + } + if (!ar_assoc) { + dev_err(dev, "Missing ASSOCIATE association request\n"); + return -EINVAL; + } + + return 0; +} + +static const struct wusb_cbaf_host_info cbaf_host_info_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB), + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_RETRIEVE_HOST_INFO), + .CHID_hdr = WUSB_AR_CHID, + .LangID_hdr = WUSB_AR_LangID, + .HostFriendlyName_hdr = WUSB_AR_HostFriendlyName, +}; + +/* Send WUSB host information (CHID and name) to a CBAF device */ +static int cbaf_send_host_info(struct cbaf *cbaf) +{ + struct wusb_cbaf_host_info *hi; + size_t name_len; + size_t hi_size; + + hi = cbaf->buffer; + memset(hi, 0, sizeof(*hi)); + *hi = cbaf_host_info_defaults; + hi->CHID = cbaf->chid; + hi->LangID = 0; /* FIXME: I guess... */ + strlcpy(hi->HostFriendlyName, cbaf->host_name, CBA_NAME_LEN); + name_len = strlen(cbaf->host_name); + hi->HostFriendlyName_hdr.len = cpu_to_le16(name_len); + hi_size = sizeof(*hi) + name_len; + + return usb_control_msg(cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_SET_ASSOCIATION_RESPONSE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0101, + cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + hi, hi_size, 1000 /* FIXME: arbitrary */); +} + +/* + * Get device's information (CDID) associated to CHID + * + * The device will return it's information (CDID, name, bandgroups) + * associated to the CHID we have set before, or 0 CDID and default + * name and bandgroup if no CHID set or unknown. + */ +static int cbaf_cdid_get(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_device_info *di; + size_t needed; + + di = cbaf->buffer; + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_REQUEST, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot request device information: %d\n", result); + return result; + } + + needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length); + if (result < needed) { + dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs " + "%zu bytes needed)\n", (size_t)result, needed); + return result; + } + + strlcpy(cbaf->device_name, di->DeviceFriendlyName, CBA_NAME_LEN); + cbaf->cdid = di->CDID; + cbaf->device_band_groups = le16_to_cpu(di->BandGroups); + + return 0; +} + +static ssize_t cbaf_wusb_chid_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_chid[WUSB_CKHDID_STRSIZE]; + + ckhdid_printf(pr_chid, sizeof(pr_chid), &cbaf->chid); + return scnprintf(buf, PAGE_SIZE, "%s\n", pr_chid); +} + +static ssize_t cbaf_wusb_chid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx", + &cbaf->chid.data[0] , &cbaf->chid.data[1], + &cbaf->chid.data[2] , &cbaf->chid.data[3], + &cbaf->chid.data[4] , &cbaf->chid.data[5], + &cbaf->chid.data[6] , &cbaf->chid.data[7], + &cbaf->chid.data[8] , &cbaf->chid.data[9], + &cbaf->chid.data[10], &cbaf->chid.data[11], + &cbaf->chid.data[12], &cbaf->chid.data[13], + &cbaf->chid.data[14], &cbaf->chid.data[15]); + + if (result != 16) + return -EINVAL; + + result = cbaf_send_host_info(cbaf); + if (result < 0) + return result; + result = cbaf_cdid_get(cbaf); + if (result < 0) + return -result; + return size; +} +static DEVICE_ATTR(wusb_chid, 0600, cbaf_wusb_chid_show, cbaf_wusb_chid_store); + +static ssize_t cbaf_wusb_host_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->host_name); +} + +static ssize_t cbaf_wusb_host_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, "%63s", cbaf->host_name); + if (result != 1) + return -EINVAL; + + return size; +} +static DEVICE_ATTR(wusb_host_name, 0600, cbaf_wusb_host_name_show, + cbaf_wusb_host_name_store); + +static ssize_t cbaf_wusb_host_band_groups_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->host_band_groups); +} + +static ssize_t cbaf_wusb_host_band_groups_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + u16 band_groups = 0; + + result = sscanf(buf, "%04hx", &band_groups); + if (result != 1) + return -EINVAL; + + cbaf->host_band_groups = band_groups; + + return size; +} + +static DEVICE_ATTR(wusb_host_band_groups, 0600, + cbaf_wusb_host_band_groups_show, + cbaf_wusb_host_band_groups_store); + +static const struct wusb_cbaf_device_info cbaf_device_info_defaults = { + .Length_hdr = WUSB_AR_Length, + .CDID_hdr = WUSB_AR_CDID, + .BandGroups_hdr = WUSB_AR_BandGroups, + .LangID_hdr = WUSB_AR_LangID, + .DeviceFriendlyName_hdr = WUSB_AR_DeviceFriendlyName, +}; + +static ssize_t cbaf_wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_cdid[WUSB_CKHDID_STRSIZE]; + + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid); + return scnprintf(buf, PAGE_SIZE, "%s\n", pr_cdid); +} + +static ssize_t cbaf_wusb_cdid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + struct wusb_ckhdid cdid; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx", + &cdid.data[0] , &cdid.data[1], + &cdid.data[2] , &cdid.data[3], + &cdid.data[4] , &cdid.data[5], + &cdid.data[6] , &cdid.data[7], + &cdid.data[8] , &cdid.data[9], + &cdid.data[10], &cdid.data[11], + &cdid.data[12], &cdid.data[13], + &cdid.data[14], &cdid.data[15]); + if (result != 16) + return -EINVAL; + + cbaf->cdid = cdid; + + return size; +} +static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_show, cbaf_wusb_cdid_store); + +static ssize_t cbaf_wusb_device_band_groups_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->device_band_groups); +} + +static DEVICE_ATTR(wusb_device_band_groups, 0600, + cbaf_wusb_device_band_groups_show, + NULL); + +static ssize_t cbaf_wusb_device_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->device_name); +} +static DEVICE_ATTR(wusb_device_name, 0600, cbaf_wusb_device_name_show, NULL); + +static const struct wusb_cbaf_cc_data cbaf_cc_data_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB), + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_ASSOCIATE), + .Length_hdr = WUSB_AR_Length, + .Length = cpu_to_le32(sizeof(struct wusb_cbaf_cc_data)), + .ConnectionContext_hdr = WUSB_AR_ConnectionContext, + .BandGroups_hdr = WUSB_AR_BandGroups, +}; + +static const struct wusb_cbaf_cc_data_fail cbaf_cc_data_fail_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .Length_hdr = WUSB_AR_Length, + .AssociationStatus_hdr = WUSB_AR_AssociationStatus, +}; + +/* + * Send a new CC to the device. + */ +static int cbaf_cc_upload(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_cc_data *ccd; + char pr_cdid[WUSB_CKHDID_STRSIZE]; + + ccd = cbaf->buffer; + *ccd = cbaf_cc_data_defaults; + ccd->CHID = cbaf->chid; + ccd->CDID = cbaf->cdid; + ccd->CK = cbaf->ck; + ccd->BandGroups = cpu_to_le16(cbaf->host_band_groups); + + dev_dbg(dev, "Trying to upload CC:\n"); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID); + dev_dbg(dev, " CHID %s\n", pr_cdid); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID); + dev_dbg(dev, " CDID %s\n", pr_cdid); + dev_dbg(dev, " Bandgroups 0x%04x\n", cbaf->host_band_groups); + + result = usb_control_msg( + cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_SET_ASSOCIATION_RESPONSE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + ccd, sizeof(*ccd), 1000 /* FIXME: arbitrary */); + + return result; +} + +static ssize_t cbaf_wusb_ck_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx", + &cbaf->ck.data[0] , &cbaf->ck.data[1], + &cbaf->ck.data[2] , &cbaf->ck.data[3], + &cbaf->ck.data[4] , &cbaf->ck.data[5], + &cbaf->ck.data[6] , &cbaf->ck.data[7], + &cbaf->ck.data[8] , &cbaf->ck.data[9], + &cbaf->ck.data[10], &cbaf->ck.data[11], + &cbaf->ck.data[12], &cbaf->ck.data[13], + &cbaf->ck.data[14], &cbaf->ck.data[15]); + if (result != 16) + return -EINVAL; + + result = cbaf_cc_upload(cbaf); + if (result < 0) + return result; + + return size; +} +static DEVICE_ATTR(wusb_ck, 0600, NULL, cbaf_wusb_ck_store); + +static struct attribute *cbaf_dev_attrs[] = { + &dev_attr_wusb_host_name.attr, + &dev_attr_wusb_host_band_groups.attr, + &dev_attr_wusb_chid.attr, + &dev_attr_wusb_cdid.attr, + &dev_attr_wusb_device_name.attr, + &dev_attr_wusb_device_band_groups.attr, + &dev_attr_wusb_ck.attr, + NULL, +}; + +static struct attribute_group cbaf_dev_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = cbaf_dev_attrs, +}; + +static int cbaf_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct cbaf *cbaf; + struct device *dev = &iface->dev; + int result = -ENOMEM; + + cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL); + if (cbaf == NULL) + goto error_kzalloc; + cbaf->buffer = kmalloc(512, GFP_KERNEL); + if (cbaf->buffer == NULL) + goto error_kmalloc_buffer; + + cbaf->buffer_size = 512; + cbaf->usb_dev = usb_get_dev(interface_to_usbdev(iface)); + cbaf->usb_iface = usb_get_intf(iface); + result = cbaf_check(cbaf); + if (result < 0) { + dev_err(dev, "This device is not WUSB-CBAF compliant" + "and is not supported yet.\n"); + goto error_check; + } + + result = sysfs_create_group(&dev->kobj, &cbaf_dev_attr_group); + if (result < 0) { + dev_err(dev, "Can't register sysfs attr group: %d\n", result); + goto error_create_group; + } + usb_set_intfdata(iface, cbaf); + return 0; + +error_create_group: +error_check: + kfree(cbaf->buffer); +error_kmalloc_buffer: + kfree(cbaf); +error_kzalloc: + return result; +} + +static void cbaf_disconnect(struct usb_interface *iface) +{ + struct cbaf *cbaf = usb_get_intfdata(iface); + struct device *dev = &iface->dev; + sysfs_remove_group(&dev->kobj, &cbaf_dev_attr_group); + usb_set_intfdata(iface, NULL); + usb_put_intf(iface); + kfree(cbaf->buffer); + /* paranoia: clean up crypto keys */ + memset(cbaf, 0, sizeof(*cbaf)); + kfree(cbaf); +} + +static struct usb_device_id cbaf_id_table[] = { + { USB_INTERFACE_INFO(0xef, 0x03, 0x01), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, cbaf_id_table); + +static struct usb_driver cbaf_driver = { + .name = "wusb-cbaf", + .id_table = cbaf_id_table, + .probe = cbaf_probe, + .disconnect = cbaf_disconnect, +}; + +static int __init cbaf_driver_init(void) +{ + return usb_register(&cbaf_driver); +} +module_init(cbaf_driver_init); + +static void __exit cbaf_driver_exit(void) +{ + usb_deregister(&cbaf_driver); +} +module_exit(cbaf_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("Wireless USB Cable Based Association"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/crypto.c b/drivers/usb/wusbcore/crypto.c new file mode 100644 index 00000000000..c36c4389baa --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We don't do any encryption here; we use the Linux Kernel's AES-128 + * crypto modules to construct keys and payload blocks in a way + * defined by WUSB1.0[6]. Check the erratas, as typos are are patched + * there. + * + * Thanks a zillion to John Keys for his help and clarifications over + * the designed-by-a-committee text. + * + * So the idea is that there is this basic Pseudo-Random-Function + * defined in WUSB1.0[6.5] which is the core of everything. It works + * by tweaking some blocks, AES crypting them and then xoring + * something else with them (this seems to be called CBC(AES) -- can + * you tell I know jack about crypto?). So we just funnel it into the + * Linux Crypto API. + * + * We leave a crypto test module so we can verify that vectors match, + * every now and then. + * + * Block size: 16 bytes -- AES seems to do things in 'block sizes'. I + * am learning a lot... + * + * Conveniently, some data structures that need to be + * funneled through AES are...16 bytes in size! + */ + +#include <linux/crypto.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/uwb.h> +#include <linux/usb/wusb.h> +#include <linux/scatterlist.h> +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + + +/* + * Block of data, as understood by AES-CCM + * + * The code assumes this structure is nothing but a 16 byte array + * (packed in a struct to avoid common mess ups that I usually do with + * arrays and enforcing type checking). + */ +struct aes_ccm_block { + u8 data[16]; +} __attribute__((packed)); + +/* + * Counter-mode Blocks (WUSB1.0[6.4]) + * + * According to CCM (or so it seems), for the purpose of calculating + * the MIC, the message is broken in N counter-mode blocks, B0, B1, + * ... BN. + * + * B0 contains flags, the CCM nonce and l(m). + * + * B1 contains l(a), the MAC header, the encryption offset and padding. + * + * If EO is nonzero, additional blocks are built from payload bytes + * until EO is exahusted (FIXME: padding to 16 bytes, I guess). The + * padding is not xmitted. + */ + +/* WUSB1.0[T6.4] */ +struct aes_ccm_b0 { + u8 flags; /* 0x59, per CCM spec */ + struct aes_ccm_nonce ccm_nonce; + __be16 lm; +} __attribute__((packed)); + +/* WUSB1.0[T6.5] */ +struct aes_ccm_b1 { + __be16 la; + u8 mac_header[10]; + __le16 eo; + u8 security_reserved; /* This is always zero */ + u8 padding; /* 0 */ +} __attribute__((packed)); + +/* + * Encryption Blocks (WUSB1.0[6.4.4]) + * + * CCM uses Ax blocks to generate a keystream with which the MIC and + * the message's payload are encoded. A0 always encrypts/decrypts the + * MIC. Ax (x>0) are used for the sucesive payload blocks. + * + * The x is the counter, and is increased for each block. + */ +struct aes_ccm_a { + u8 flags; /* 0x01, per CCM spec */ + struct aes_ccm_nonce ccm_nonce; + __be16 counter; /* Value of x */ +} __attribute__((packed)); + +static void bytewise_xor(void *_bo, const void *_bi1, const void *_bi2, + size_t size) +{ + u8 *bo = _bo; + const u8 *bi1 = _bi1, *bi2 = _bi2; + size_t itr; + for (itr = 0; itr < size; itr++) + bo[itr] = bi1[itr] ^ bi2[itr]; +} + +/* + * CC-MAC function WUSB1.0[6.5] + * + * Take a data string and produce the encrypted CBC Counter-mode MIC + * + * Note the names for most function arguments are made to (more or + * less) match those used in the pseudo-function definition given in + * WUSB1.0[6.5]. + * + * @tfm_cbc: CBC(AES) blkcipher handle (initialized) + * + * @tfm_aes: AES cipher handle (initialized) + * + * @mic: buffer for placing the computed MIC (Message Integrity + * Code). This is exactly 8 bytes, and we expect the buffer to + * be at least eight bytes in length. + * + * @key: 128 bit symmetric key + * + * @n: CCM nonce + * + * @a: ASCII string, 14 bytes long (I guess zero padded if needed; + * we use exactly 14 bytes). + * + * @b: data stream to be processed; cannot be a global or const local + * (will confuse the scatterlists) + * + * @blen: size of b... + * + * Still not very clear how this is done, but looks like this: we + * create block B0 (as WUSB1.0[6.5] says), then we AES-crypt it with + * @key. We bytewise xor B0 with B1 (1) and AES-crypt that. Then we + * take the payload and divide it in blocks (16 bytes), xor them with + * the previous crypto result (16 bytes) and crypt it, repeat the next + * block with the output of the previous one, rinse wash (I guess this + * is what AES CBC mode means...but I truly have no idea). So we use + * the CBC(AES) blkcipher, that does precisely that. The IV (Initial + * Vector) is 16 bytes and is set to zero, so + * + * See rfc3610. Linux crypto has a CBC implementation, but the + * documentation is scarce, to say the least, and the example code is + * so intricated that is difficult to understand how things work. Most + * of this is guess work -- bite me. + * + * (1) Created as 6.5 says, again, using as l(a) 'Blen + 14', and + * using the 14 bytes of @a to fill up + * b1.{mac_header,e0,security_reserved,padding}. + * + * NOTE: The definiton of l(a) in WUSB1.0[6.5] vs the definition of + * l(m) is orthogonal, they bear no relationship, so it is not + * in conflict with the parameter's relation that + * WUSB1.0[6.4.2]) defines. + * + * NOTE: WUSB1.0[A.1]: Host Nonce is missing a nibble? (1e); fixed in + * first errata released on 2005/07. + * + * NOTE: we need to clean IV to zero at each invocation to make sure + * we start with a fresh empty Initial Vector, so that the CBC + * works ok. + * + * NOTE: blen is not aligned to a block size, we'll pad zeros, that's + * what sg[4] is for. Maybe there is a smarter way to do this. + */ +static int wusb_ccm_mac(struct crypto_blkcipher *tfm_cbc, + struct crypto_cipher *tfm_aes, void *mic, + const struct aes_ccm_nonce *n, + const struct aes_ccm_label *a, const void *b, + size_t blen) +{ + int result = 0; + struct blkcipher_desc desc; + struct aes_ccm_b0 b0; + struct aes_ccm_b1 b1; + struct aes_ccm_a ax; + struct scatterlist sg[4], sg_dst; + void *iv, *dst_buf; + size_t ivsize, dst_size; + const u8 bzero[16] = { 0 }; + size_t zero_padding; + + d_fnstart(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, " + "n %p, a %p, b %p, blen %zu)\n", + tfm_cbc, tfm_aes, mic, n, a, b, blen); + /* + * These checks should be compile time optimized out + * ensure @a fills b1's mac_header and following fields + */ + WARN_ON(sizeof(*a) != sizeof(b1) - sizeof(b1.la)); + WARN_ON(sizeof(b0) != sizeof(struct aes_ccm_block)); + WARN_ON(sizeof(b1) != sizeof(struct aes_ccm_block)); + WARN_ON(sizeof(ax) != sizeof(struct aes_ccm_block)); + + result = -ENOMEM; + zero_padding = sizeof(struct aes_ccm_block) + - blen % sizeof(struct aes_ccm_block); + zero_padding = blen % sizeof(struct aes_ccm_block); + if (zero_padding) + zero_padding = sizeof(struct aes_ccm_block) - zero_padding; + dst_size = blen + sizeof(b0) + sizeof(b1) + zero_padding; + dst_buf = kzalloc(dst_size, GFP_KERNEL); + if (dst_buf == NULL) { + printk(KERN_ERR "E: can't alloc destination buffer\n"); + goto error_dst_buf; + } + + iv = crypto_blkcipher_crt(tfm_cbc)->iv; + ivsize = crypto_blkcipher_ivsize(tfm_cbc); + memset(iv, 0, ivsize); + + /* Setup B0 */ + b0.flags = 0x59; /* Format B0 */ + b0.ccm_nonce = *n; + b0.lm = cpu_to_be16(0); /* WUSB1.0[6.5] sez l(m) is 0 */ + + /* Setup B1 + * + * The WUSB spec is anything but clear! WUSB1.0[6.5] + * says that to initialize B1 from A with 'l(a) = blen + + * 14'--after clarification, it means to use A's contents + * for MAC Header, EO, sec reserved and padding. + */ + b1.la = cpu_to_be16(blen + 14); + memcpy(&b1.mac_header, a, sizeof(*a)); + + d_printf(4, NULL, "I: B0 (%zu bytes)\n", sizeof(b0)); + d_dump(4, NULL, &b0, sizeof(b0)); + d_printf(4, NULL, "I: B1 (%zu bytes)\n", sizeof(b1)); + d_dump(4, NULL, &b1, sizeof(b1)); + d_printf(4, NULL, "I: B (%zu bytes)\n", blen); + d_dump(4, NULL, b, blen); + d_printf(4, NULL, "I: B 0-padding (%zu bytes)\n", zero_padding); + d_printf(4, NULL, "D: IV before crypto (%zu)\n", ivsize); + d_dump(4, NULL, iv, ivsize); + + sg_init_table(sg, ARRAY_SIZE(sg)); + sg_set_buf(&sg[0], &b0, sizeof(b0)); + sg_set_buf(&sg[1], &b1, sizeof(b1)); + sg_set_buf(&sg[2], b, blen); + /* 0 if well behaved :) */ + sg_set_buf(&sg[3], bzero, zero_padding); + sg_init_one(&sg_dst, dst_buf, dst_size); + + desc.tfm = tfm_cbc; + desc.flags = 0; + result = crypto_blkcipher_encrypt(&desc, &sg_dst, sg, dst_size); + if (result < 0) { + printk(KERN_ERR "E: can't compute CBC-MAC tag (MIC): %d\n", + result); + goto error_cbc_crypt; + } + d_printf(4, NULL, "D: MIC tag\n"); + d_dump(4, NULL, iv, ivsize); + + /* Now we crypt the MIC Tag (*iv) with Ax -- values per WUSB1.0[6.5] + * The procedure is to AES crypt the A0 block and XOR the MIC + * Tag agains it; we only do the first 8 bytes and place it + * directly in the destination buffer. + * + * POS Crypto API: size is assumed to be AES's block size. + * Thanks for documenting it -- tip taken from airo.c + */ + ax.flags = 0x01; /* as per WUSB 1.0 spec */ + ax.ccm_nonce = *n; + ax.counter = 0; + crypto_cipher_encrypt_one(tfm_aes, (void *)&ax, (void *)&ax); + bytewise_xor(mic, &ax, iv, 8); + d_printf(4, NULL, "D: CTR[MIC]\n"); + d_dump(4, NULL, &ax, 8); + d_printf(4, NULL, "D: CCM-MIC tag\n"); + d_dump(4, NULL, mic, 8); + result = 8; +error_cbc_crypt: + kfree(dst_buf); +error_dst_buf: + d_fnend(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, " + "n %p, a %p, b %p, blen %zu)\n", + tfm_cbc, tfm_aes, mic, n, a, b, blen); + return result; +} + +/* + * WUSB Pseudo Random Function (WUSB1.0[6.5]) + * + * @b: buffer to the source data; cannot be a global or const local + * (will confuse the scatterlists) + */ +ssize_t wusb_prf(void *out, size_t out_size, + const u8 key[16], const struct aes_ccm_nonce *_n, + const struct aes_ccm_label *a, + const void *b, size_t blen, size_t len) +{ + ssize_t result, bytes = 0, bitr; + struct aes_ccm_nonce n = *_n; + struct crypto_blkcipher *tfm_cbc; + struct crypto_cipher *tfm_aes; + u64 sfn = 0; + __le64 sfn_le; + + d_fnstart(3, NULL, "(out %p, out_size %zu, key %p, _n %p, " + "a %p, b %p, blen %zu, len %zu)\n", out, out_size, + key, _n, a, b, blen, len); + + tfm_cbc = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm_cbc)) { + result = PTR_ERR(tfm_cbc); + printk(KERN_ERR "E: can't load CBC(AES): %d\n", (int)result); + goto error_alloc_cbc; + } + result = crypto_blkcipher_setkey(tfm_cbc, key, 16); + if (result < 0) { + printk(KERN_ERR "E: can't set CBC key: %d\n", (int)result); + goto error_setkey_cbc; + } + + tfm_aes = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm_aes)) { + result = PTR_ERR(tfm_aes); + printk(KERN_ERR "E: can't load AES: %d\n", (int)result); + goto error_alloc_aes; + } + result = crypto_cipher_setkey(tfm_aes, key, 16); + if (result < 0) { + printk(KERN_ERR "E: can't set AES key: %d\n", (int)result); + goto error_setkey_aes; + } + + for (bitr = 0; bitr < (len + 63) / 64; bitr++) { + sfn_le = cpu_to_le64(sfn++); + memcpy(&n.sfn, &sfn_le, sizeof(n.sfn)); /* n.sfn++... */ + result = wusb_ccm_mac(tfm_cbc, tfm_aes, out + bytes, + &n, a, b, blen); + if (result < 0) + goto error_ccm_mac; + bytes += result; + } + result = bytes; +error_ccm_mac: +error_setkey_aes: + crypto_free_cipher(tfm_aes); +error_alloc_aes: +error_setkey_cbc: + crypto_free_blkcipher(tfm_cbc); +error_alloc_cbc: + d_fnend(3, NULL, "(out %p, out_size %zu, key %p, _n %p, " + "a %p, b %p, blen %zu, len %zu) = %d\n", out, out_size, + key, _n, a, b, blen, len, (int)bytes); + return result; +} + +/* WUSB1.0[A.2] test vectors */ +static const u8 stv_hsmic_key[16] = { + 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d, + 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f +}; + +static const struct aes_ccm_nonce stv_hsmic_n = { + .sfn = { 0 }, + .tkid = { 0x76, 0x98, 0x01, }, + .dest_addr = { .data = { 0xbe, 0x00 } }, + .src_addr = { .data = { 0x76, 0x98 } }, +}; + +/* + * Out-of-band MIC Generation verification code + * + */ +static int wusb_oob_mic_verify(void) +{ + int result; + u8 mic[8]; + /* WUSB1.0[A.2] test vectors + * + * Need to keep it in the local stack as GCC 4.1.3something + * messes up and generates noise. + */ + struct usb_handshake stv_hsmic_hs = { + .bMessageNumber = 2, + .bStatus = 00, + .tTKID = { 0x76, 0x98, 0x01 }, + .bReserved = 00, + .CDID = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f }, + .nonce = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f }, + .MIC = { 0x75, 0x6a, 0x97, 0x51, 0x0c, 0x8c, + 0x14, 0x7b } , + }; + size_t hs_size; + + result = wusb_oob_mic(mic, stv_hsmic_key, &stv_hsmic_n, &stv_hsmic_hs); + if (result < 0) + printk(KERN_ERR "E: WUSB OOB MIC test: failed: %d\n", result); + else if (memcmp(stv_hsmic_hs.MIC, mic, sizeof(mic))) { + printk(KERN_ERR "E: OOB MIC test: " + "mismatch between MIC result and WUSB1.0[A2]\n"); + hs_size = sizeof(stv_hsmic_hs) - sizeof(stv_hsmic_hs.MIC); + printk(KERN_ERR "E: Handshake2 in: (%zu bytes)\n", hs_size); + dump_bytes(NULL, &stv_hsmic_hs, hs_size); + printk(KERN_ERR "E: CCM Nonce in: (%zu bytes)\n", + sizeof(stv_hsmic_n)); + dump_bytes(NULL, &stv_hsmic_n, sizeof(stv_hsmic_n)); + printk(KERN_ERR "E: MIC out:\n"); + dump_bytes(NULL, mic, sizeof(mic)); + printk(KERN_ERR "E: MIC out (from WUSB1.0[A.2]):\n"); + dump_bytes(NULL, stv_hsmic_hs.MIC, sizeof(stv_hsmic_hs.MIC)); + result = -EINVAL; + } else + result = 0; + return result; +} + +/* + * Test vectors for Key derivation + * + * These come from WUSB1.0[6.5.1], the vectors in WUSB1.0[A.1] + * (errata corrected in 2005/07). + */ +static const u8 stv_key_a1[16] __attribute__ ((__aligned__(4))) = { + 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, + 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f +}; + +static const struct aes_ccm_nonce stv_keydvt_n_a1 = { + .sfn = { 0 }, + .tkid = { 0x76, 0x98, 0x01, }, + .dest_addr = { .data = { 0xbe, 0x00 } }, + .src_addr = { .data = { 0x76, 0x98 } }, +}; + +static const struct wusb_keydvt_out stv_keydvt_out_a1 = { + .kck = { + 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d, + 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f + }, + .ptk = { + 0xc8, 0x70, 0x62, 0x82, 0xb6, 0x7c, 0xe9, 0x06, + 0x7b, 0xc5, 0x25, 0x69, 0xf2, 0x36, 0x61, 0x2d + } +}; + +/* + * Performa a test to make sure we match the vectors defined in + * WUSB1.0[A.1](Errata2006/12) + */ +static int wusb_key_derive_verify(void) +{ + int result = 0; + struct wusb_keydvt_out keydvt_out; + /* These come from WUSB1.0[A.1] + 2006/12 errata + * NOTE: can't make this const or global -- somehow it seems + * the scatterlists for crypto get confused and we get + * bad data. There is no doc on this... */ + struct wusb_keydvt_in stv_keydvt_in_a1 = { + .hnonce = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }, + .dnonce = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f + } + }; + + result = wusb_key_derive(&keydvt_out, stv_key_a1, &stv_keydvt_n_a1, + &stv_keydvt_in_a1); + if (result < 0) + printk(KERN_ERR "E: WUSB key derivation test: " + "derivation failed: %d\n", result); + if (memcmp(&stv_keydvt_out_a1, &keydvt_out, sizeof(keydvt_out))) { + printk(KERN_ERR "E: WUSB key derivation test: " + "mismatch between key derivation result " + "and WUSB1.0[A1] Errata 2006/12\n"); + printk(KERN_ERR "E: keydvt in: key (%zu bytes)\n", + sizeof(stv_key_a1)); + dump_bytes(NULL, stv_key_a1, sizeof(stv_key_a1)); + printk(KERN_ERR "E: keydvt in: nonce (%zu bytes)\n", + sizeof(stv_keydvt_n_a1)); + dump_bytes(NULL, &stv_keydvt_n_a1, sizeof(stv_keydvt_n_a1)); + printk(KERN_ERR "E: keydvt in: hnonce & dnonce (%zu bytes)\n", + sizeof(stv_keydvt_in_a1)); + dump_bytes(NULL, &stv_keydvt_in_a1, sizeof(stv_keydvt_in_a1)); + printk(KERN_ERR "E: keydvt out: KCK\n"); + dump_bytes(NULL, &keydvt_out.kck, sizeof(keydvt_out.kck)); + printk(KERN_ERR "E: keydvt out: PTK\n"); + dump_bytes(NULL, &keydvt_out.ptk, sizeof(keydvt_out.ptk)); + result = -EINVAL; + } else + result = 0; + return result; +} + +/* + * Initialize crypto system + * + * FIXME: we do nothing now, other than verifying. Later on we'll + * cache the encryption stuff, so that's why we have a separate init. + */ +int wusb_crypto_init(void) +{ + int result; + + result = wusb_key_derive_verify(); + if (result < 0) + return result; + return wusb_oob_mic_verify(); +} + +void wusb_crypto_exit(void) +{ + /* FIXME: free cached crypto transforms */ +} diff --git a/drivers/usb/wusbcore/dev-sysfs.c b/drivers/usb/wusbcore/dev-sysfs.c new file mode 100644 index 00000000000..7897a19652e --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Get them out of the way... + */ + +#include <linux/jiffies.h> +#include <linux/ctype.h> +#include <linux/workqueue.h> +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 4 +#include <linux/uwb/debug.h> + +static ssize_t wusb_disconnect_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_device *usb_dev; + struct wusbhc *wusbhc; + unsigned command; + u8 port_idx; + + if (sscanf(buf, "%u", &command) != 1) + return -EINVAL; + if (command == 0) + return size; + usb_dev = to_usb_device(dev); + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return -ENODEV; + + mutex_lock(&wusbhc->mutex); + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + __wusbhc_dev_disable(wusbhc, port_idx); + mutex_unlock(&wusbhc->mutex); + wusbhc_put(wusbhc); + return size; +} +static DEVICE_ATTR(wusb_disconnect, 0200, NULL, wusb_disconnect_store); + +static ssize_t wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t result; + struct wusb_dev *wusb_dev; + + wusb_dev = wusb_dev_get_by_usb_dev(to_usb_device(dev)); + if (wusb_dev == NULL) + return -ENODEV; + result = ckhdid_printf(buf, PAGE_SIZE, &wusb_dev->cdid); + strcat(buf, "\n"); + wusb_dev_put(wusb_dev); + return result + 1; +} +static DEVICE_ATTR(wusb_cdid, 0444, wusb_cdid_show, NULL); + +static ssize_t wusb_ck_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int result; + struct usb_device *usb_dev; + struct wusbhc *wusbhc; + struct wusb_ckhdid ck; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &ck.data[0] , &ck.data[1], + &ck.data[2] , &ck.data[3], + &ck.data[4] , &ck.data[5], + &ck.data[6] , &ck.data[7], + &ck.data[8] , &ck.data[9], + &ck.data[10], &ck.data[11], + &ck.data[12], &ck.data[13], + &ck.data[14], &ck.data[15]); + if (result != 16) + return -EINVAL; + + usb_dev = to_usb_device(dev); + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return -ENODEV; + result = wusb_dev_4way_handshake(wusbhc, usb_dev->wusb_dev, &ck); + memset(&ck, 0, sizeof(ck)); + wusbhc_put(wusbhc); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_ck, 0200, NULL, wusb_ck_store); + +static struct attribute *wusb_dev_attrs[] = { + &dev_attr_wusb_disconnect.attr, + &dev_attr_wusb_cdid.attr, + &dev_attr_wusb_ck.attr, + NULL, +}; + +static struct attribute_group wusb_dev_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wusb_dev_attrs, +}; + +int wusb_dev_sysfs_add(struct wusbhc *wusbhc, struct usb_device *usb_dev, + struct wusb_dev *wusb_dev) +{ + int result = sysfs_create_group(&usb_dev->dev.kobj, + &wusb_dev_attr_group); + struct device *dev = &usb_dev->dev; + if (result < 0) + dev_err(dev, "Cannot register WUSB-dev attributes: %d\n", + result); + return result; +} + +void wusb_dev_sysfs_rm(struct wusb_dev *wusb_dev) +{ + struct usb_device *usb_dev = wusb_dev->usb_dev; + if (usb_dev) + sysfs_remove_group(&usb_dev->dev.kobj, &wusb_dev_attr_group); +} diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c new file mode 100644 index 00000000000..f45d777bef3 --- /dev/null +++ b/drivers/usb/wusbcore/devconnect.c @@ -0,0 +1,1297 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * Device Connect handling + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * FIXME: this file needs to be broken up, it's grown too big + * + * + * WUSB1.0[7.1, 7.5.1, ] + * + * WUSB device connection is kind of messy. Some background: + * + * When a device wants to connect it scans the UWB radio channels + * looking for a WUSB Channel; a WUSB channel is defined by MMCs + * (Micro Managed Commands or something like that) [see + * Design-overview for more on this] . + * + * So, device scans the radio, finds MMCs and thus a host and checks + * when the next DNTS is. It sends a Device Notification Connect + * (DN_Connect); the host picks it up (through nep.c and notif.c, ends + * up in wusb_devconnect_ack(), which creates a wusb_dev structure in + * wusbhc->port[port_number].wusb_dev), assigns an unauth address + * to the device (this means from 0x80 to 0xfe) and sends, in the MMC + * a Connect Ack Information Element (ConnAck IE). + * + * So now the device now has a WUSB address. From now on, we use + * that to talk to it in the RPipes. + * + * ASSUMPTIONS: + * + * - We use the the as device address the port number where it is + * connected (port 0 doesn't exist). For unauth, it is 128 + that. + * + * ROADMAP: + * + * This file contains the logic for doing that--entry points: + * + * wusb_devconnect_ack() Ack a device until _acked() called. + * Called by notif.c:wusb_handle_dn_connect() + * when a DN_Connect is received. + * + * wusbhc_devconnect_auth() Called by rh.c:wusbhc_rh_port_reset() when + * doing the device connect sequence. + * + * wusb_devconnect_acked() Ack done, release resources. + * + * wusb_handle_dn_alive() Called by notif.c:wusb_handle_dn() + * for processing a DN_Alive pong from a device. + * + * wusb_handle_dn_disconnect()Called by notif.c:wusb_handle_dn() to + * process a disconenct request from a + * device. + * + * wusb_dev_reset() Called by rh.c:wusbhc_rh_port_reset() when + * resetting a device. + * + * __wusb_dev_disable() Called by rh.c:wusbhc_rh_clear_port_feat() when + * disabling a port. + * + * wusb_devconnect_create() Called when creating the host by + * lc.c:wusbhc_create(). + * + * wusb_devconnect_destroy() Cleanup called removing the host. Called + * by lc.c:wusbhc_destroy(). + * + * Each Wireless USB host maintains a list of DN_Connect requests + * (actually we maintain a list of pending Connect Acks, the + * wusbhc->ca_list). + * + * LIFE CYCLE OF port->wusb_dev + * + * Before the @wusbhc structure put()s the reference it owns for + * port->wusb_dev [and clean the wusb_dev pointer], it needs to + * lock @wusbhc->mutex. + */ + +#include <linux/jiffies.h> +#include <linux/ctype.h> +#include <linux/workqueue.h> +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 1 +#include <linux/uwb/debug.h> + +static void wusbhc_devconnect_acked_work(struct work_struct *work); + +static void wusb_dev_free(struct wusb_dev *wusb_dev) +{ + if (wusb_dev) { + kfree(wusb_dev->set_gtk_req); + usb_free_urb(wusb_dev->set_gtk_urb); + kfree(wusb_dev); + } +} + +static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc) +{ + struct wusb_dev *wusb_dev; + struct urb *urb; + struct usb_ctrlrequest *req; + + wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL); + if (wusb_dev == NULL) + goto err; + + wusb_dev->wusbhc = wusbhc; + + INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) + goto err; + + req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (req == NULL) + goto err; + + req->bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req->bRequest = USB_REQ_SET_DESCRIPTOR; + req->wValue = cpu_to_le16(USB_DT_KEY << 8 | wusbhc->gtk_index); + req->wIndex = 0; + req->wLength = cpu_to_le16(wusbhc->gtk.descr.bLength); + + wusb_dev->set_gtk_urb = urb; + wusb_dev->set_gtk_req = req; + + return wusb_dev; +err: + wusb_dev_free(wusb_dev); + return NULL; +} + + +/* + * Using the Connect-Ack list, fill out the @wusbhc Connect-Ack WUSB IE + * properly so that it can be added to the MMC. + * + * We just get the @wusbhc->ca_list and fill out the first four ones or + * less (per-spec WUSB1.0[7.5, before T7-38). If the ConnectAck WUSB + * IE is not allocated, we alloc it. + * + * @wusbhc->mutex must be taken + */ +static void wusbhc_fill_cack_ie(struct wusbhc *wusbhc) +{ + unsigned cnt; + struct wusb_dev *dev_itr; + struct wuie_connect_ack *cack_ie; + + cack_ie = &wusbhc->cack_ie; + cnt = 0; + list_for_each_entry(dev_itr, &wusbhc->cack_list, cack_node) { + cack_ie->blk[cnt].CDID = dev_itr->cdid; + cack_ie->blk[cnt].bDeviceAddress = dev_itr->addr; + if (++cnt >= WUIE_ELT_MAX) + break; + } + cack_ie->hdr.bLength = sizeof(cack_ie->hdr) + + cnt * sizeof(cack_ie->blk[0]); +} + +/* + * Register a new device that wants to connect + * + * A new device wants to connect, so we add it to the Connect-Ack + * list. We give it an address in the unauthorized range (bit 8 set); + * user space will have to drive authorization further on. + * + * @dev_addr: address to use for the device (which is also the port + * number). + * + * @wusbhc->mutex must be taken + */ +static struct wusb_dev *wusbhc_cack_add(struct wusbhc *wusbhc, + struct wusb_dn_connect *dnc, + const char *pr_cdid, u8 port_idx) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + int new_connection = wusb_dn_connect_new_connection(dnc); + u8 dev_addr; + int result; + + /* 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) + return NULL; + wusb_dev_init(wusb_dev); + wusb_dev->cdid = dnc->CDID; + wusb_dev->port_idx = port_idx; + + /* + * Devices are always available within the cluster reservation + * and since the hardware will take the intersection of the + * per-device availability and the cluster reservation, the + * per-device availability can simply be set to always + * available. + */ + bitmap_fill(wusb_dev->availability.bm, UWB_NUM_MAS); + + /* FIXME: handle reconnects instead of assuming connects are + always new. */ + if (1 && new_connection == 0) + new_connection = 1; + if (new_connection) { + dev_addr = (port_idx + 2) | WUSB_DEV_ADDR_UNAUTH; + + dev_info(dev, "Connecting new WUSB device to address %u, " + "port %u\n", dev_addr, port_idx); + + result = wusb_set_dev_addr(wusbhc, wusb_dev, dev_addr); + if (result < 0) + return NULL; + } + wusb_dev->entry_ts = jiffies; + list_add_tail(&wusb_dev->cack_node, &wusbhc->cack_list); + wusbhc->cack_count++; + wusbhc_fill_cack_ie(wusbhc); + 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)) == 0) + 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) +{ + 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; + + 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) + wusbhc_mmcie_set(wusbhc, 10, 5, &ie->hdr); + else if (old_keep_alives != 0) + wusbhc_mmcie_rm(wusbhc, &ie->hdr); +} + +/* + * Do a run through all devices checking for timeouts + */ +static void wusbhc_keep_alive_run(struct work_struct *ws) +{ + struct delayed_work *dw = + container_of(ws, struct delayed_work, work); + struct wusbhc *wusbhc = + container_of(dw, struct wusbhc, keep_alive_timer); + + d_fnstart(5, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + if (wusbhc->active) { + mutex_lock(&wusbhc->mutex); + __wusbhc_keep_alive(wusbhc); + mutex_unlock(&wusbhc->mutex); + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (wusbhc->trust_timeout * CONFIG_HZ)/1000/2); + } + d_fnend(5, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); + return; +} + +/* + * Find the wusb_dev from its device address. + * + * The device can be found directly from the address (see + * wusb_cack_add() for where the device address is set to port_idx + * +2), except when the address is zero. + */ +static struct wusb_dev *wusbhc_find_dev_by_addr(struct wusbhc *wusbhc, u8 addr) +{ + int p; + + if (addr == 0xff) /* unconnected */ + return NULL; + + if (addr > 0) { + int port = (addr & ~0x80) - 2; + if (port < 0 || port >= wusbhc->ports_max) + return NULL; + return wusb_port_by_idx(wusbhc, port)->wusb_dev; + } + + /* Look for the device with address 0. */ + for (p = 0; p < wusbhc->ports_max; p++) { + struct wusb_dev *wusb_dev = wusb_port_by_idx(wusbhc, p)->wusb_dev; + if (wusb_dev && wusb_dev->addr == addr) + return wusb_dev; + } + return NULL; +} + +/* + * Handle a DN_Alive notification (WUSB1.0[7.6.1]) + * + * This just updates the device activity timestamp and then refreshes + * the keep alive IE. + * + * @wusbhc shall be referenced and unlocked + */ +static void wusbhc_handle_dn_alive(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + + d_printf(2, dev, "DN ALIVE: device 0x%02x pong\n", wusb_dev->addr); + + mutex_lock(&wusbhc->mutex); + wusb_dev->entry_ts = jiffies; + __wusbhc_keep_alive(wusbhc); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Handle a DN_Connect notification (WUSB1.0[7.6.1]) + * + * @wusbhc + * @pkt_hdr + * @size: Size of the buffer where the notification resides; if the + * notification data suggests there should be more data than + * available, an error will be signaled and the whole buffer + * consumed. + * + * @wusbhc->mutex shall be held + */ +static void wusbhc_handle_dn_connect(struct wusbhc *wusbhc, + struct wusb_dn_hdr *dn_hdr, + size_t size) +{ + struct device *dev = wusbhc->dev; + struct wusb_dn_connect *dnc; + char pr_cdid[WUSB_CKHDID_STRSIZE]; + static const char *beacon_behaviour[] = { + "reserved", + "self-beacon", + "directed-beacon", + "no-beacon" + }; + + d_fnstart(3, dev, "(%p, %p, %zu)\n", wusbhc, dn_hdr, size); + if (size < sizeof(*dnc)) { + dev_err(dev, "DN CONNECT: short notification (%zu < %zu)\n", + size, sizeof(*dnc)); + goto out; + } + + dnc = container_of(dn_hdr, struct wusb_dn_connect, hdr); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &dnc->CDID); + dev_info(dev, "DN CONNECT: device %s @ %x (%s) wants to %s\n", + pr_cdid, + wusb_dn_connect_prev_dev_addr(dnc), + beacon_behaviour[wusb_dn_connect_beacon_behavior(dnc)], + wusb_dn_connect_new_connection(dnc) ? "connect" : "reconnect"); + /* ACK the connect */ + wusbhc_devconnect_ack(wusbhc, dnc, pr_cdid); +out: + d_fnend(3, dev, "(%p, %p, %zu) = void\n", + wusbhc, dn_hdr, size); + return; +} + +/* + * Handle a DN_Disconnect notification (WUSB1.0[7.6.1]) + * + * Device is going down -- do the disconnect. + * + * @wusbhc shall be referenced and unlocked + */ +static void wusbhc_handle_dn_disconnect(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + + dev_info(dev, "DN DISCONNECT: device 0x%02x going down\n", wusb_dev->addr); + + mutex_lock(&wusbhc->mutex); + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, wusb_dev->port_idx)); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Reset a WUSB device on a HWA + * + * @wusbhc + * @port_idx Index of the port where the device is + * + * In Wireless USB, a reset is more or less equivalent to a full + * disconnect; so we just do a full disconnect and send the device a + * Device Reset IE (WUSB1.0[7.5.11]) giving it a few millisecs (6 MMCs). + * + * @wusbhc should be refcounted and unlocked + */ +int wusbhc_dev_reset(struct wusbhc *wusbhc, u8 port_idx) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wuie_reset *ie; + + d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx); + mutex_lock(&wusbhc->mutex); + result = 0; + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev == NULL) { + /* reset no device? ignore */ + dev_dbg(dev, "RESET: no device at port %u, ignoring\n", + port_idx); + goto error_unlock; + } + result = -ENOMEM; + ie = kzalloc(sizeof(*ie), GFP_KERNEL); + if (ie == NULL) + goto error_unlock; + ie->hdr.bLength = sizeof(ie->hdr) + sizeof(ie->CDID); + ie->hdr.bIEIdentifier = WUIE_ID_RESET_DEVICE; + ie->CDID = wusb_dev->cdid; + result = wusbhc_mmcie_set(wusbhc, 0xff, 6, &ie->hdr); + if (result < 0) { + dev_err(dev, "RESET: cant's set MMC: %d\n", result); + goto error_kfree; + } + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + + /* 120ms, hopefully 6 MMCs (FIXME) */ + msleep(120); + wusbhc_mmcie_rm(wusbhc, &ie->hdr); +error_kfree: + kfree(ie); +error_unlock: + mutex_unlock(&wusbhc->mutex); + d_fnend(3, dev, "(%p, %u) = %d\n", wusbhc, port_idx, result); + return result; +} + +/* + * Handle a Device Notification coming a host + * + * The Device Notification comes from a host (HWA, DWA or WHCI) + * wrapped in a set of headers. Somebody else has peeled off those + * headers for us and we just get one Device Notifications. + * + * Invalid DNs (e.g., too short) are discarded. + * + * @wusbhc shall be referenced + * + * FIXMES: + * - implement priorities as in WUSB1.0[Table 7-55]? + */ +void wusbhc_handle_dn(struct wusbhc *wusbhc, u8 srcaddr, + struct wusb_dn_hdr *dn_hdr, size_t size) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + + d_fnstart(3, dev, "(%p, %p)\n", wusbhc, dn_hdr); + + if (size < sizeof(struct wusb_dn_hdr)) { + dev_err(dev, "DN data shorter than DN header (%d < %d)\n", + (int)size, (int)sizeof(struct wusb_dn_hdr)); + goto out; + } + + wusb_dev = wusbhc_find_dev_by_addr(wusbhc, srcaddr); + if (wusb_dev == NULL && dn_hdr->bType != WUSB_DN_CONNECT) { + dev_dbg(dev, "ignoring DN %d from unconnected device %02x\n", + dn_hdr->bType, srcaddr); + goto out; + } + + switch (dn_hdr->bType) { + case WUSB_DN_CONNECT: + wusbhc_handle_dn_connect(wusbhc, dn_hdr, size); + break; + case WUSB_DN_ALIVE: + wusbhc_handle_dn_alive(wusbhc, wusb_dev); + break; + case WUSB_DN_DISCONNECT: + wusbhc_handle_dn_disconnect(wusbhc, wusb_dev); + break; + case WUSB_DN_MASAVAILCHANGED: + case WUSB_DN_RWAKE: + case WUSB_DN_SLEEP: + /* FIXME: handle these DNs. */ + break; + case WUSB_DN_EPRDY: + /* The hardware handles these. */ + break; + default: + dev_warn(dev, "unknown DN %u (%d octets) from %u\n", + dn_hdr->bType, (int)size, srcaddr); + } +out: + d_fnend(3, dev, "(%p, %p) = void\n", wusbhc, dn_hdr); + return; +} +EXPORT_SYMBOL_GPL(wusbhc_handle_dn); + +/* + * Disconnect a WUSB device from a the cluster + * + * @wusbhc + * @port Fake port where the device is (wusbhc index, not USB port number). + * + * In Wireless USB, a disconnect is basically telling the device he is + * being disconnected and forgetting about him. + * + * We send the device a Device Disconnect IE (WUSB1.0[7.5.11]) for 100 + * ms and then keep going. + * + * We don't do much in case of error; we always pretend we disabled + * the port and disconnected the device. If physically the request + * didn't get there (many things can fail in the way there), the stack + * will reject the device's communication attempts. + * + * @wusbhc should be refcounted and locked + */ +void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port_idx) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wuie_disconnect *ie; + + d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx); + result = 0; + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev == NULL) { + /* reset no device? ignore */ + dev_dbg(dev, "DISCONNECT: no device at port %u, ignoring\n", + port_idx); + goto error; + } + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + + result = -ENOMEM; + ie = kzalloc(sizeof(*ie), GFP_KERNEL); + if (ie == NULL) + goto error; + ie->hdr.bLength = sizeof(*ie); + ie->hdr.bIEIdentifier = WUIE_ID_DEVICE_DISCONNECT; + ie->bDeviceAddress = wusb_dev->addr; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &ie->hdr); + if (result < 0) { + dev_err(dev, "DISCONNECT: can't set MMC: %d\n", result); + goto error_kfree; + } + + /* 120ms, hopefully 6 MMCs */ + msleep(100); + wusbhc_mmcie_rm(wusbhc, &ie->hdr); +error_kfree: + kfree(ie); +error: + d_fnend(3, dev, "(%p, %u) = %d\n", wusbhc, port_idx, result); + return; +} + +static void wusb_cap_descr_printf(const unsigned level, struct device *dev, + const struct usb_wireless_cap_descriptor *wcd) +{ + d_printf(level, dev, + "WUSB Capability Descriptor\n" + " bDevCapabilityType 0x%02x\n" + " bmAttributes 0x%02x\n" + " wPhyRates 0x%04x\n" + " bmTFITXPowerInfo 0x%02x\n" + " bmFFITXPowerInfo 0x%02x\n" + " bmBandGroup 0x%04x\n" + " bReserved 0x%02x\n", + wcd->bDevCapabilityType, + wcd->bmAttributes, + le16_to_cpu(wcd->wPHYRates), + wcd->bmTFITXPowerInfo, + wcd->bmFFITXPowerInfo, + wcd->bmBandGroup, + wcd->bReserved); +} + +/* + * Walk over the BOS descriptor, verify and grok it + * + * @usb_dev: referenced + * @wusb_dev: referenced and unlocked + * + * The BOS descriptor is defined at WUSB1.0[7.4.1], and it defines a + * "flexible" way to wrap all kinds of descriptors inside an standard + * descriptor (wonder why they didn't use normal descriptors, + * btw). Not like they lack code. + * + * At the end we go to look for the WUSB Device Capabilities + * (WUSB1.0[7.4.1.1]) that is wrapped in a device capability descriptor + * that is part of the BOS descriptor set. That tells us what does the + * device support (dual role, beacon type, UWB PHY rates). + */ +static int wusb_dev_bos_grok(struct usb_device *usb_dev, + struct wusb_dev *wusb_dev, + struct usb_bos_descriptor *bos, size_t desc_size) +{ + ssize_t result; + struct device *dev = &usb_dev->dev; + void *itr, *top; + + /* Walk over BOS capabilities, verify them */ + itr = (void *)bos + sizeof(*bos); + top = itr + desc_size - sizeof(*bos); + while (itr < top) { + struct usb_dev_cap_header *cap_hdr = itr; + size_t cap_size; + u8 cap_type; + if (top - itr < sizeof(*cap_hdr)) { + dev_err(dev, "Device BUG? premature end of BOS header " + "data [offset 0x%02x]: only %zu bytes left\n", + (int)(itr - (void *)bos), top - itr); + result = -ENOSPC; + goto error_bad_cap; + } + cap_size = cap_hdr->bLength; + cap_type = cap_hdr->bDevCapabilityType; + d_printf(4, dev, "BOS Capability: 0x%02x (%zu bytes)\n", + cap_type, cap_size); + if (cap_size == 0) + break; + if (cap_size > top - itr) { + dev_err(dev, "Device BUG? premature end of BOS data " + "[offset 0x%02x cap %02x %zu bytes]: " + "only %zu bytes left\n", + (int)(itr - (void *)bos), + cap_type, cap_size, top - itr); + result = -EBADF; + goto error_bad_cap; + } + d_dump(3, dev, itr, cap_size); + switch (cap_type) { + case USB_CAP_TYPE_WIRELESS_USB: + if (cap_size != sizeof(*wusb_dev->wusb_cap_descr)) + dev_err(dev, "Device BUG? WUSB Capability " + "descriptor is %zu bytes vs %zu " + "needed\n", cap_size, + sizeof(*wusb_dev->wusb_cap_descr)); + else { + wusb_dev->wusb_cap_descr = itr; + wusb_cap_descr_printf(3, dev, itr); + } + break; + default: + dev_err(dev, "BUG? Unknown BOS capability 0x%02x " + "(%zu bytes) at offset 0x%02x\n", cap_type, + cap_size, (int)(itr - (void *)bos)); + } + itr += cap_size; + } + result = 0; +error_bad_cap: + return result; +} + +/* + * Add information from the BOS descriptors to the device + * + * @usb_dev: referenced + * @wusb_dev: referenced and unlocked + * + * So what we do is we alloc a space for the BOS descriptor of 64 + * bytes; read the first four bytes which include the wTotalLength + * field (WUSB1.0[T7-26]) and if it fits in those 64 bytes, read the + * whole thing. If not we realloc to that size. + * + * Then we call the groking function, that will fill up + * wusb_dev->wusb_cap_descr, which is what we'll need later on. + */ +static int wusb_dev_bos_add(struct usb_device *usb_dev, + struct wusb_dev *wusb_dev) +{ + ssize_t result; + struct device *dev = &usb_dev->dev; + struct usb_bos_descriptor *bos; + size_t alloc_size = 32, desc_size = 4; + + bos = kmalloc(alloc_size, GFP_KERNEL); + if (bos == NULL) + return -ENOMEM; + result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size); + if (result < 4) { + dev_err(dev, "Can't get BOS descriptor or too short: %zd\n", + result); + goto error_get_descriptor; + } + desc_size = le16_to_cpu(bos->wTotalLength); + if (desc_size >= alloc_size) { + kfree(bos); + alloc_size = desc_size; + bos = kmalloc(alloc_size, GFP_KERNEL); + if (bos == NULL) + return -ENOMEM; + } + result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size); + if (result < 0 || result != desc_size) { + dev_err(dev, "Can't get BOS descriptor or too short (need " + "%zu bytes): %zd\n", desc_size, result); + goto error_get_descriptor; + } + if (result < sizeof(*bos) + || le16_to_cpu(bos->wTotalLength) != desc_size) { + dev_err(dev, "Can't get BOS descriptor or too short (need " + "%zu bytes): %zd\n", desc_size, result); + goto error_get_descriptor; + } + d_printf(2, dev, "Got BOS descriptor %zd bytes, %u capabilities\n", + result, bos->bNumDeviceCaps); + d_dump(2, dev, bos, result); + result = wusb_dev_bos_grok(usb_dev, wusb_dev, bos, result); + if (result < 0) + goto error_bad_bos; + wusb_dev->bos = bos; + return 0; + +error_bad_bos: +error_get_descriptor: + kfree(bos); + wusb_dev->wusb_cap_descr = NULL; + return result; +} + +static void wusb_dev_bos_rm(struct wusb_dev *wusb_dev) +{ + kfree(wusb_dev->bos); + wusb_dev->wusb_cap_descr = NULL; +}; + +static struct usb_wireless_cap_descriptor wusb_cap_descr_default = { + .bLength = sizeof(wusb_cap_descr_default), + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_CAP_TYPE_WIRELESS_USB, + + .bmAttributes = USB_WIRELESS_BEACON_NONE, + .wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53), + .bmTFITXPowerInfo = 0, + .bmFFITXPowerInfo = 0, + .bmBandGroup = cpu_to_le16(0x0001), /* WUSB1.0[7.4.1] bottom */ + .bReserved = 0 +}; + +/* + * USB stack's device addition Notifier Callback + * + * Called from drivers/usb/core/hub.c when a new device is added; we + * use this hook to perform certain WUSB specific setup work on the + * new device. As well, it is the first time we can connect the + * wusb_dev and the usb_dev. So we note it down in wusb_dev and take a + * reference that we'll drop. + * + * First we need to determine if the device is a WUSB device (else we + * ignore it). For that we use the speed setting (USB_SPEED_VARIABLE) + * [FIXME: maybe we'd need something more definitive]. If so, we track + * it's usb_busd and from there, the WUSB HC. + * + * Because all WUSB HCs are contained in a 'struct wusbhc', voila, we + * get the wusbhc for the device. + * + * We have a reference on @usb_dev (as we are called at the end of its + * enumeration). + * + * NOTE: @usb_dev locked + */ +static void wusb_dev_add_ncb(struct usb_device *usb_dev) +{ + int result = 0; + struct wusb_dev *wusb_dev; + struct wusbhc *wusbhc; + struct device *dev = &usb_dev->dev; + u8 port_idx; + + if (usb_dev->wusb == 0 || usb_dev->devnum == 1) + return; /* skip non wusb and wusb RHs */ + + d_fnstart(3, dev, "(usb_dev %p)\n", usb_dev); + + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + goto error_nodev; + mutex_lock(&wusbhc->mutex); + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev); + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + mutex_unlock(&wusbhc->mutex); + if (wusb_dev == NULL) + goto error_nodev; + wusb_dev->usb_dev = usb_get_dev(usb_dev); + usb_dev->wusb_dev = wusb_dev_get(wusb_dev); + result = wusb_dev_sec_add(wusbhc, usb_dev, wusb_dev); + if (result < 0) { + dev_err(dev, "Cannot enable security: %d\n", result); + goto error_sec_add; + } + /* Now query the device for it's BOS and attach it to wusb_dev */ + result = wusb_dev_bos_add(usb_dev, wusb_dev); + if (result < 0) { + dev_err(dev, "Cannot get BOS descriptors: %d\n", result); + goto error_bos_add; + } + result = wusb_dev_sysfs_add(wusbhc, usb_dev, wusb_dev); + if (result < 0) + goto error_add_sysfs; +out: + wusb_dev_put(wusb_dev); + wusbhc_put(wusbhc); +error_nodev: + d_fnend(3, dev, "(usb_dev %p) = void\n", usb_dev); + return; + + wusb_dev_sysfs_rm(wusb_dev); +error_add_sysfs: + wusb_dev_bos_rm(wusb_dev); +error_bos_add: + wusb_dev_sec_rm(wusb_dev); +error_sec_add: + mutex_lock(&wusbhc->mutex); + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + mutex_unlock(&wusbhc->mutex); + goto out; +} + +/* + * Undo all the steps done at connection by the notifier callback + * + * NOTE: @usb_dev locked + */ +static void wusb_dev_rm_ncb(struct usb_device *usb_dev) +{ + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + + if (usb_dev->wusb == 0 || usb_dev->devnum == 1) + return; /* skip non wusb and wusb RHs */ + + wusb_dev_sysfs_rm(wusb_dev); + wusb_dev_bos_rm(wusb_dev); + wusb_dev_sec_rm(wusb_dev); + wusb_dev->usb_dev = NULL; + usb_dev->wusb_dev = NULL; + wusb_dev_put(wusb_dev); + usb_put_dev(usb_dev); +} + +/* + * Handle notifications from the USB stack (notifier call back) + * + * This is called when the USB stack does a + * usb_{bus,device}_{add,remove}() so we can do WUSB specific + * handling. It is called with [for the case of + * USB_DEVICE_{ADD,REMOVE} with the usb_dev locked. + */ +int wusb_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv) +{ + int result = NOTIFY_OK; + + switch (val) { + case USB_DEVICE_ADD: + wusb_dev_add_ncb(priv); + break; + case USB_DEVICE_REMOVE: + wusb_dev_rm_ncb(priv); + break; + case USB_BUS_ADD: + /* ignore (for now) */ + case USB_BUS_REMOVE: + break; + default: + WARN_ON(1); + result = NOTIFY_BAD; + }; + return result; +} + +/* + * Return a referenced wusb_dev given a @wusbhc and @usb_dev + */ +struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *wusbhc, + struct usb_device *usb_dev) +{ + struct wusb_dev *wusb_dev; + u8 port_idx; + + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + BUG_ON(port_idx > wusbhc->ports_max); + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev != NULL) /* ops, device is gone */ + wusb_dev_get(wusb_dev); + return wusb_dev; +} +EXPORT_SYMBOL_GPL(__wusb_dev_get_by_usb_dev); + +void wusb_dev_destroy(struct kref *_wusb_dev) +{ + struct wusb_dev *wusb_dev + = container_of(_wusb_dev, struct wusb_dev, refcnt); + list_del_init(&wusb_dev->cack_node); + wusb_dev_free(wusb_dev); + d_fnend(1, NULL, "%s (wusb_dev %p) = void\n", __func__, wusb_dev); +} +EXPORT_SYMBOL_GPL(wusb_dev_destroy); + +/* + * Create all the device connect handling infrastructure + * + * This is basically the device info array, Connect Acknowledgement + * (cack) lists, keep-alive timers (and delayed work thread). + */ +int wusbhc_devconnect_create(struct wusbhc *wusbhc) +{ + d_fnstart(3, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + + wusbhc->keep_alive_ie.hdr.bIEIdentifier = WUIE_ID_KEEP_ALIVE; + wusbhc->keep_alive_ie.hdr.bLength = sizeof(wusbhc->keep_alive_ie.hdr); + INIT_DELAYED_WORK(&wusbhc->keep_alive_timer, wusbhc_keep_alive_run); + + wusbhc->cack_ie.hdr.bIEIdentifier = WUIE_ID_CONNECTACK; + wusbhc->cack_ie.hdr.bLength = sizeof(wusbhc->cack_ie.hdr); + INIT_LIST_HEAD(&wusbhc->cack_list); + + d_fnend(3, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); + return 0; +} + +/* + * Release all resources taken by the devconnect stuff + */ +void wusbhc_devconnect_destroy(struct wusbhc *wusbhc) +{ + d_fnstart(3, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + d_fnend(3, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); +} + +/* + * wusbhc_devconnect_start - start accepting device connections + * @wusbhc: the WUSB HC + * + * Sets the Host Info IE to accept all new connections. + * + * FIXME: This also enables the keep alives but this is not necessary + * until there are connected and authenticated devices. + */ +int wusbhc_devconnect_start(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid) +{ + struct device *dev = wusbhc->dev; + struct wuie_host_info *hi; + int result; + + hi = kzalloc(sizeof(*hi), GFP_KERNEL); + if (hi == NULL) + return -ENOMEM; + + hi->hdr.bLength = sizeof(*hi); + hi->hdr.bIEIdentifier = WUIE_ID_HOST_INFO; + hi->attributes = cpu_to_le16((wusbhc->rsv->stream << 3) | WUIE_HI_CAP_ALL); + hi->CHID = *chid; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &hi->hdr); + if (result < 0) { + dev_err(dev, "Cannot add Host Info MMCIE: %d\n", result); + goto error_mmcie_set; + } + wusbhc->wuie_host_info = hi; + + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (wusbhc->trust_timeout*CONFIG_HZ)/1000/2); + + return 0; + +error_mmcie_set: + kfree(hi); + return result; +} + +/* + * wusbhc_devconnect_stop - stop managing connected devices + * @wusbhc: the WUSB HC + * + * Removes the Host Info IE and stops the keep alives. + * + * FIXME: should this disconnect all devices? + */ +void wusbhc_devconnect_stop(struct wusbhc *wusbhc) +{ + cancel_delayed_work_sync(&wusbhc->keep_alive_timer); + WARN_ON(!list_empty(&wusbhc->cack_list)); + + wusbhc_mmcie_rm(wusbhc, &wusbhc->wuie_host_info->hdr); + kfree(wusbhc->wuie_host_info); + wusbhc->wuie_host_info = NULL; +} + +/* + * wusb_set_dev_addr - set the WUSB device address used by the host + * @wusbhc: the WUSB HC the device is connect to + * @wusb_dev: the WUSB device + * @addr: new device address + */ +int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, u8 addr) +{ + int result; + + wusb_dev->addr = addr; + result = wusbhc->dev_info_set(wusbhc, wusb_dev); + if (result < 0) + dev_err(wusbhc->dev, "device %d: failed to set device " + "address\n", wusb_dev->port_idx); + else + dev_info(wusbhc->dev, "device %d: %s addr %u\n", + wusb_dev->port_idx, + (addr & WUSB_DEV_ADDR_UNAUTH) ? "unauth" : "auth", + wusb_dev->addr); + + return result; +} diff --git a/drivers/usb/wusbcore/mmc.c b/drivers/usb/wusbcore/mmc.c new file mode 100644 index 00000000000..cfa77a01ceb --- /dev/null +++ b/drivers/usb/wusbcore/mmc.c @@ -0,0 +1,321 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * MMC (Microscheduled Management Command) handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * WUIEs and MMC IEs...well, they are almost the same at the end. MMC + * IEs are Wireless USB IEs that go into the MMC period...[what is + * that? look in Design-overview.txt]. + * + * + * This is a simple subsystem to keep track of which IEs are being + * sent by the host in the MMC period. + * + * For each WUIE we ask to send, we keep it in an array, so we can + * request its removal later, or replace the content. They are tracked + * by pointer, so be sure to use the same pointer if you want to + * remove it or update the contents. + * + * FIXME: + * - add timers that autoremove intervalled IEs? + */ +#include <linux/usb/wusb.h> +#include "wusbhc.h" + +/* Initialize the MMCIEs handling mechanism */ +int wusbhc_mmcie_create(struct wusbhc *wusbhc) +{ + u8 mmcies = wusbhc->mmcies_max; + wusbhc->mmcie = kcalloc(mmcies, sizeof(wusbhc->mmcie[0]), GFP_KERNEL); + if (wusbhc->mmcie == NULL) + return -ENOMEM; + mutex_init(&wusbhc->mmcie_mutex); + return 0; +} + +/* Release resources used by the MMCIEs handling mechanism */ +void wusbhc_mmcie_destroy(struct wusbhc *wusbhc) +{ + kfree(wusbhc->mmcie); +} + +/* + * Add or replace an MMC Wireless USB IE. + * + * @interval: See WUSB1.0[8.5.3.1] + * @repeat_cnt: See WUSB1.0[8.5.3.1] + * @handle: See WUSB1.0[8.5.3.1] + * @wuie: Pointer to the header of the WUSB IE data to add. + * MUST BE allocated in a kmalloc buffer (no stack or + * vmalloc). + * THE CALLER ALWAYS OWNS THE POINTER (we don't free it + * on remove, we just forget about it). + * @returns: 0 if ok, < 0 errno code on error. + * + * Goes over the *whole* @wusbhc->mmcie array looking for (a) the + * first free spot and (b) if @wuie is already in the array (aka: + * transmitted in the MMCs) the spot were it is. + * + * If present, we "overwrite it" (update). + * + * + * NOTE: Need special ordering rules -- see below WUSB1.0 Table 7-38. + * The host uses the handle as the 'sort' index. We + * allocate the last one always for the WUIE_ID_HOST_INFO, and + * the rest, first come first serve in inverse order. + * + * Host software must make sure that it adds the other IEs in + * the right order... the host hardware is responsible for + * placing the WCTA IEs in the right place with the other IEs + * set by host software. + * + * NOTE: we can access wusbhc->wa_descr without locking because it is + * read only. + */ +int wusbhc_mmcie_set(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + struct wuie_hdr *wuie) +{ + int result = -ENOBUFS; + unsigned handle, itr; + + /* Search a handle, taking into account the ordering */ + mutex_lock(&wusbhc->mmcie_mutex); + switch (wuie->bIEIdentifier) { + case WUIE_ID_HOST_INFO: + /* Always last */ + handle = wusbhc->mmcies_max - 1; + break; + case WUIE_ID_ISOCH_DISCARD: + dev_err(wusbhc->dev, "Special ordering case for WUIE ID 0x%x " + "unimplemented\n", wuie->bIEIdentifier); + result = -ENOSYS; + goto error_unlock; + default: + /* search for it or find the last empty slot */ + handle = ~0; + for (itr = 0; itr < wusbhc->mmcies_max - 1; itr++) { + if (wusbhc->mmcie[itr] == wuie) { + handle = itr; + break; + } + if (wusbhc->mmcie[itr] == NULL) + handle = itr; + } + if (handle == ~0) + goto error_unlock; + } + result = (wusbhc->mmcie_add)(wusbhc, interval, repeat_cnt, handle, + wuie); + if (result >= 0) + wusbhc->mmcie[handle] = wuie; +error_unlock: + mutex_unlock(&wusbhc->mmcie_mutex); + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_mmcie_set); + +/* + * Remove an MMC IE previously added with wusbhc_mmcie_set() + * + * @wuie Pointer used to add the WUIE + */ +void wusbhc_mmcie_rm(struct wusbhc *wusbhc, struct wuie_hdr *wuie) +{ + int result; + unsigned handle, itr; + + mutex_lock(&wusbhc->mmcie_mutex); + for (itr = 0; itr < wusbhc->mmcies_max; itr++) { + if (wusbhc->mmcie[itr] == wuie) { + handle = itr; + goto found; + } + } + mutex_unlock(&wusbhc->mmcie_mutex); + return; + +found: + result = (wusbhc->mmcie_rm)(wusbhc, handle); + if (result == 0) + wusbhc->mmcie[itr] = NULL; + mutex_unlock(&wusbhc->mmcie_mutex); +} +EXPORT_SYMBOL_GPL(wusbhc_mmcie_rm); + +/* + * wusbhc_start - start transmitting MMCs and accepting connections + * @wusbhc: the HC to start + * @chid: the CHID to use for this host + * + * Establishes a cluster reservation, enables device connections, and + * starts MMCs with appropriate DNTS parameters. + */ +int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid) +{ + int result; + struct device *dev = wusbhc->dev; + + WARN_ON(wusbhc->wuie_host_info != NULL); + + result = wusbhc_rsv_establish(wusbhc); + if (result < 0) { + dev_err(dev, "cannot establish cluster reservation: %d\n", + result); + goto error_rsv_establish; + } + + result = wusbhc_devconnect_start(wusbhc, chid); + if (result < 0) { + dev_err(dev, "error enabling device connections: %d\n", result); + goto error_devconnect_start; + } + + result = wusbhc_sec_start(wusbhc); + if (result < 0) { + dev_err(dev, "error starting security in the HC: %d\n", result); + goto error_sec_start; + } + /* FIXME: the choice of the DNTS parameters is somewhat + * arbitrary */ + result = wusbhc->set_num_dnts(wusbhc, 0, 15); + if (result < 0) { + dev_err(dev, "Cannot set DNTS parameters: %d\n", result); + goto error_set_num_dnts; + } + result = wusbhc->start(wusbhc); + if (result < 0) { + dev_err(dev, "error starting wusbch: %d\n", result); + goto error_wusbhc_start; + } + wusbhc->active = 1; + return 0; + +error_wusbhc_start: + wusbhc_sec_stop(wusbhc); +error_set_num_dnts: +error_sec_start: + wusbhc_devconnect_stop(wusbhc); +error_devconnect_start: + wusbhc_rsv_terminate(wusbhc); +error_rsv_establish: + return result; +} + +/* + * Disconnect all from the WUSB Channel + * + * Send a Host Disconnect IE in the MMC, wait, don't send it any more + */ +static int __wusbhc_host_disconnect_ie(struct wusbhc *wusbhc) +{ + int result = -ENOMEM; + struct wuie_host_disconnect *host_disconnect_ie; + might_sleep(); + host_disconnect_ie = kmalloc(sizeof(*host_disconnect_ie), GFP_KERNEL); + if (host_disconnect_ie == NULL) + goto error_alloc; + host_disconnect_ie->hdr.bLength = sizeof(*host_disconnect_ie); + host_disconnect_ie->hdr.bIEIdentifier = WUIE_ID_HOST_DISCONNECT; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &host_disconnect_ie->hdr); + if (result < 0) + goto error_mmcie_set; + + /* WUSB1.0[8.5.3.1 & 7.5.2] */ + msleep(100); + wusbhc_mmcie_rm(wusbhc, &host_disconnect_ie->hdr); +error_mmcie_set: + kfree(host_disconnect_ie); +error_alloc: + return result; +} + +/* + * wusbhc_stop - stop transmitting MMCs + * @wusbhc: the HC to stop + * + * Send a Host Disconnect IE, wait, remove all the MMCs (stop sending MMCs). + * + * If we can't allocate a Host Stop IE, screw it, we don't notify the + * devices we are disconnecting... + */ +void wusbhc_stop(struct wusbhc *wusbhc) +{ + if (wusbhc->active) { + wusbhc->active = 0; + wusbhc->stop(wusbhc); + wusbhc_sec_stop(wusbhc); + __wusbhc_host_disconnect_ie(wusbhc); + wusbhc_devconnect_stop(wusbhc); + wusbhc_rsv_terminate(wusbhc); + } +} +EXPORT_SYMBOL_GPL(wusbhc_stop); + +/* + * Change the CHID in a WUSB Channel + * + * If it is just a new CHID, send a Host Disconnect IE and then change + * the CHID IE. + */ +static int __wusbhc_chid_change(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid) +{ + int result = -ENOSYS; + struct device *dev = wusbhc->dev; + dev_err(dev, "%s() not implemented yet\n", __func__); + return result; + + BUG_ON(wusbhc->wuie_host_info == NULL); + __wusbhc_host_disconnect_ie(wusbhc); + wusbhc->wuie_host_info->CHID = *chid; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->wuie_host_info->hdr); + if (result < 0) + dev_err(dev, "Can't update Host Info WUSB IE: %d\n", result); + return result; +} + +/* + * Set/reset/update a new CHID + * + * Depending on the previous state of the MMCs, start, stop or change + * the sent MMC. This effectively switches the host controller on and + * off (radio wise). + */ +int wusbhc_chid_set(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid) +{ + int result = 0; + + if (memcmp(chid, &wusb_ckhdid_zero, sizeof(chid)) == 0) + chid = NULL; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + if (chid) + result = __wusbhc_chid_change(wusbhc, chid); + else + wusbhc_stop(wusbhc); + } else { + if (chid) + wusbhc_start(wusbhc, chid); + } + mutex_unlock(&wusbhc->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_chid_set); diff --git a/drivers/usb/wusbcore/pal.c b/drivers/usb/wusbcore/pal.c new file mode 100644 index 00000000000..7cc51e9905c --- /dev/null +++ b/drivers/usb/wusbcore/pal.c @@ -0,0 +1,42 @@ +/* + * Wireless USB Host Controller + * UWB Protocol Adaptation Layer (PAL) glue. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "wusbhc.h" + +/** + * wusbhc_pal_register - register the WUSB HC as a UWB PAL + * @wusbhc: the WUSB HC + */ +int wusbhc_pal_register(struct wusbhc *wusbhc) +{ + uwb_pal_init(&wusbhc->pal); + + wusbhc->pal.name = "wusbhc"; + wusbhc->pal.device = wusbhc->usb_hcd.self.controller; + + return uwb_pal_register(wusbhc->uwb_rc, &wusbhc->pal); +} + +/** + * wusbhc_pal_register - unregister the WUSB HC as a UWB PAL + * @wusbhc: the WUSB HC + */ +void wusbhc_pal_unregister(struct wusbhc *wusbhc) +{ + uwb_pal_unregister(wusbhc->uwb_rc, &wusbhc->pal); +} diff --git a/drivers/usb/wusbcore/reservation.c b/drivers/usb/wusbcore/reservation.c new file mode 100644 index 00000000000..fc63e77ded2 --- /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 <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/uwb.h> + +#include "wusbhc.h" + +/* + * WUSB cluster reservations are multicast reservations with the + * broadcast cluster ID (BCID) as the target DevAddr. + * + * FIXME: consider adjusting the reservation depending on what devices + * are attached. + */ + +static int wusbhc_bwa_set(struct wusbhc *wusbhc, u8 stream, + const struct uwb_mas_bm *mas) +{ + if (mas == NULL) + mas = &uwb_mas_bm_zero; + return wusbhc->bwa_set(wusbhc, stream, mas); +} + +/** + * wusbhc_rsv_complete_cb - WUSB HC reservation complete callback + * @rsv: the reservation + * + * Either set or clear the HC's view of the reservation. + * + * FIXME: when a reservation is denied the HC should be stopped. + */ +static void wusbhc_rsv_complete_cb(struct uwb_rsv *rsv) +{ + struct wusbhc *wusbhc = rsv->pal_priv; + struct device *dev = wusbhc->dev; + char buf[72]; + + switch (rsv->state) { + case UWB_RSV_STATE_O_ESTABLISHED: + bitmap_scnprintf(buf, sizeof(buf), rsv->mas.bm, UWB_NUM_MAS); + dev_dbg(dev, "established reservation: %s\n", buf); + wusbhc_bwa_set(wusbhc, rsv->stream, &rsv->mas); + break; + case UWB_RSV_STATE_NONE: + dev_dbg(dev, "removed reservation\n"); + wusbhc_bwa_set(wusbhc, 0, NULL); + wusbhc->rsv = NULL; + break; + default: + dev_dbg(dev, "unexpected reservation state: %d\n", rsv->state); + break; + } +} + + +/** + * wusbhc_rsv_establish - establish a reservation for the cluster + * @wusbhc: the WUSB HC requesting a bandwith reservation + */ +int wusbhc_rsv_establish(struct wusbhc *wusbhc) +{ + struct uwb_rc *rc = wusbhc->uwb_rc; + struct uwb_rsv *rsv; + struct uwb_dev_addr bcid; + int ret; + + rsv = uwb_rsv_create(rc, wusbhc_rsv_complete_cb, wusbhc); + if (rsv == NULL) + return -ENOMEM; + + bcid.data[0] = wusbhc->cluster_id; + bcid.data[1] = 0; + + rsv->owner = &rc->uwb_dev; + rsv->target.type = UWB_RSV_TARGET_DEVADDR; + rsv->target.devaddr = bcid; + rsv->type = UWB_DRP_TYPE_PRIVATE; + rsv->max_mas = 256; + rsv->min_mas = 16; /* one MAS per zone? */ + rsv->sparsity = 16; /* at least one MAS in each zone? */ + rsv->is_multicast = true; + + ret = uwb_rsv_establish(rsv); + if (ret == 0) + wusbhc->rsv = rsv; + else + uwb_rsv_destroy(rsv); + return ret; +} + + +/** + * wusbhc_rsv_terminate - terminate any cluster reservation + * @wusbhc: the WUSB host whose reservation is to be terminated + */ +void wusbhc_rsv_terminate(struct wusbhc *wusbhc) +{ + if (wusbhc->rsv) + uwb_rsv_terminate(wusbhc->rsv); +} diff --git a/drivers/usb/wusbcore/rh.c b/drivers/usb/wusbcore/rh.c new file mode 100644 index 00000000000..267a6432510 --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We fake a root hub that has fake ports (as many as simultaneous + * devices the Wireless USB Host Controller can deal with). For each + * port we keep an state in @wusbhc->port[index] identical to the one + * specified in the USB2.0[ch11] spec and some extra device + * information that complements the one in 'struct usb_device' (as + * this lacs a hcpriv pointer). + * + * Note this is common to WHCI and HWA host controllers. + * + * Through here we enable most of the state changes that the USB stack + * will use to connect or disconnect devices. We need to do some + * forced adaptation of Wireless USB device states vs. wired: + * + * USB: WUSB: + * + * Port Powered-off port slot n/a + * Powered-on port slot available + * Disconnected port slot available + * Connected port slot assigned device + * device sent DN_Connect + * device was authenticated + * Enabled device is authenticated, transitioned + * from unauth -> auth -> default address + * -> enabled + * Reset disconnect + * Disable disconnect + * + * This maps the standard USB port states with the WUSB device states + * so we can fake ports without having to modify the USB stack. + * + * FIXME: this process will change in the future + * + * + * ENTRY POINTS + * + * Our entry points into here are, as in hcd.c, the USB stack root hub + * ops defined in the usb_hcd struct: + * + * wusbhc_rh_status_data() Provide hub and port status data bitmap + * + * wusbhc_rh_control() Execution of all the major requests + * you can do to a hub (Set|Clear + * features, get descriptors, status, etc). + * + * wusbhc_rh_[suspend|resume]() That + * + * wusbhc_rh_start_port_reset() ??? unimplemented + */ +#include "wusbhc.h" + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +/* + * Reset a fake port + * + * This can be called to reset a port from any other state or to reset + * it when connecting. In Wireless USB they are different; when doing + * a new connect that involves going over the authentication. When + * just reseting, its a different story. + * + * The Linux USB stack resets a port twice before it considers it + * enabled, so we have to detect and ignore that. + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Supposedly we are the only thread accesing @wusbhc->port; in any + * case, maybe we should move the mutex locking from + * wusbhc_devconnect_auth() to here. + * + * @port_idx refers to the wusbhc's port index, not the USB port number + */ +static int wusbhc_rh_port_reset(struct wusbhc *wusbhc, u8 port_idx) +{ + int result = 0; + struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx); + + d_fnstart(3, wusbhc->dev, "(wusbhc %p port_idx %u)\n", + wusbhc, port_idx); + if (port->reset_count == 0) { + wusbhc_devconnect_auth(wusbhc, port_idx); + port->reset_count++; + } else if (port->reset_count == 1) + /* see header */ + d_printf(2, wusbhc->dev, "Ignoring second reset on port_idx " + "%u\n", port_idx); + else + result = wusbhc_dev_reset(wusbhc, port_idx); + d_fnend(3, wusbhc->dev, "(wusbhc %p port_idx %u) = %d\n", + wusbhc, port_idx, result); + return result; +} + +/* + * Return the hub change status bitmap + * + * The bits in the change status bitmap are cleared when a + * ClearPortFeature request is issued (USB2.0[11.12.3,11.12.4]. + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * WARNING!! This gets called from atomic context; we cannot get the + * mutex--the only race condition we can find is some bit + * changing just after we copy it, which shouldn't be too + * big of a problem [and we can't make it an spinlock + * because other parts need to take it and sleep] . + * + * @usb_hcd is refcounted, so it won't dissapear under us + * and before killing a host, the polling of the root hub + * would be stopped anyway. + */ +int wusbhc_rh_status_data(struct usb_hcd *usb_hcd, char *_buf) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + size_t cnt, size; + unsigned long *buf = (unsigned long *) _buf; + + d_fnstart(1, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + /* WE DON'T LOCK, see comment */ + size = wusbhc->ports_max + 1 /* hub bit */; + size = (size + 8 - 1) / 8; /* round to bytes */ + for (cnt = 0; cnt < wusbhc->ports_max; cnt++) + if (wusb_port_by_idx(wusbhc, cnt)->change) + set_bit(cnt + 1, buf); + else + clear_bit(cnt + 1, buf); + d_fnend(1, wusbhc->dev, "(wusbhc %p) %u, buffer:\n", wusbhc, (int)size); + d_dump(1, wusbhc->dev, _buf, size); + return size; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_status_data); + +/* + * Return the hub's desciptor + * + * NOTE: almost cut and paste from ehci-hub.c + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked + */ +static int wusbhc_rh_get_hub_descr(struct wusbhc *wusbhc, u16 wValue, + u16 wIndex, + struct usb_hub_descriptor *descr, + u16 wLength) +{ + u16 temp = 1 + (wusbhc->ports_max / 8); + u8 length = 7 + 2 * temp; + + if (wLength < length) + return -ENOSPC; + descr->bDescLength = 7 + 2 * temp; + descr->bDescriptorType = 0x29; /* HUB type */ + descr->bNbrPorts = wusbhc->ports_max; + descr->wHubCharacteristics = cpu_to_le16( + 0x00 /* All ports power at once */ + | 0x00 /* not part of compound device */ + | 0x10 /* No overcurrent protection */ + | 0x00 /* 8 FS think time FIXME ?? */ + | 0x00); /* No port indicators */ + descr->bPwrOn2PwrGood = 0; + descr->bHubContrCurrent = 0; + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&descr->bitmap[0], 0, temp); + memset(&descr->bitmap[temp], 0xff, temp); + return 0; +} + +/* + * Clear a hub feature + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Nothing to do, so no locking needed ;) + */ +static int wusbhc_rh_clear_hub_feat(struct wusbhc *wusbhc, u16 feature) +{ + int result; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(%p, feature 0x%04u)\n", wusbhc, feature); + switch (feature) { + case C_HUB_LOCAL_POWER: + /* FIXME: maybe plug bit 0 to the power input status, + * if any? + * see wusbhc_rh_get_hub_status() */ + case C_HUB_OVER_CURRENT: + result = 0; + break; + default: + result = -EPIPE; + } + d_fnend(4, dev, "(%p, feature 0x%04u), %d\n", wusbhc, feature, result); + return result; +} + +/* + * Return hub status (it is always zero...) + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Nothing to do, so no locking needed ;) + */ +static int wusbhc_rh_get_hub_status(struct wusbhc *wusbhc, u32 *buf, + u16 wLength) +{ + /* FIXME: maybe plug bit 0 to the power input status (if any)? */ + *buf = 0; + return 0; +} + +/* + * Set a port feature + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_set_port_feat(struct wusbhc *wusbhc, u16 feature, + u8 selector, u8 port_idx) +{ + int result = -EINVAL; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d)\n", + feature, selector, port_idx); + + if (port_idx > wusbhc->ports_max) + goto error; + + switch (feature) { + /* According to USB2.0[11.24.2.13]p2, these features + * are not required to be implemented. */ + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_RESET: + result = 0; + break; + + case USB_PORT_FEAT_POWER: + /* No such thing, but we fake it works */ + mutex_lock(&wusbhc->mutex); + wusb_port_by_idx(wusbhc, port_idx)->status |= USB_PORT_STAT_POWER; + mutex_unlock(&wusbhc->mutex); + result = 0; + break; + case USB_PORT_FEAT_RESET: + result = wusbhc_rh_port_reset(wusbhc, port_idx); + break; + case USB_PORT_FEAT_ENABLE: + case USB_PORT_FEAT_SUSPEND: + dev_err(dev, "(port_idx %d) set feat %d/%d UNIMPLEMENTED\n", + port_idx, feature, selector); + result = -ENOSYS; + break; + default: + dev_err(dev, "(port_idx %d) set feat %d/%d UNKNOWN\n", + port_idx, feature, selector); + result = -EPIPE; + break; + } +error: + d_fnend(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d) = %d\n", + feature, selector, port_idx, result); + return result; +} + +/* + * Clear a port feature... + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_clear_port_feat(struct wusbhc *wusbhc, u16 feature, + u8 selector, u8 port_idx) +{ + int result = -EINVAL; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d)\n", + wusbhc, feature, selector, port_idx); + + if (port_idx > wusbhc->ports_max) + goto error; + + mutex_lock(&wusbhc->mutex); + result = 0; + switch (feature) { + case USB_PORT_FEAT_POWER: /* fake port always on */ + /* According to USB2.0[11.24.2.7.1.4], no need to implement? */ + case USB_PORT_FEAT_C_OVER_CURRENT: + break; + case USB_PORT_FEAT_C_RESET: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_RESET; + break; + case USB_PORT_FEAT_C_CONNECTION: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_CONNECTION; + break; + case USB_PORT_FEAT_ENABLE: + __wusbhc_dev_disable(wusbhc, port_idx); + break; + case USB_PORT_FEAT_C_ENABLE: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_ENABLE; + break; + case USB_PORT_FEAT_SUSPEND: + case USB_PORT_FEAT_C_SUSPEND: + case 0xffff: /* ??? FIXME */ + dev_err(dev, "(port_idx %d) Clear feat %d/%d UNIMPLEMENTED\n", + port_idx, feature, selector); + /* dump_stack(); */ + result = -ENOSYS; + break; + default: + dev_err(dev, "(port_idx %d) Clear feat %d/%d UNKNOWN\n", + port_idx, feature, selector); + result = -EPIPE; + break; + } + mutex_unlock(&wusbhc->mutex); +error: + d_fnend(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d) = " + "%d\n", wusbhc, feature, selector, port_idx, result); + return result; +} + +/* + * Return the port's status + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_get_port_status(struct wusbhc *wusbhc, u16 port_idx, + u32 *_buf, u16 wLength) +{ + int result = -EINVAL; + u16 *buf = (u16 *) _buf; + + d_fnstart(1, wusbhc->dev, "(wusbhc %p port_idx %u wLength %u)\n", + wusbhc, port_idx, wLength); + if (port_idx > wusbhc->ports_max) + goto error; + mutex_lock(&wusbhc->mutex); + buf[0] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->status); + buf[1] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->change); + result = 0; + mutex_unlock(&wusbhc->mutex); +error: + d_fnend(1, wusbhc->dev, "(wusbhc %p) = %d, buffer:\n", wusbhc, result); + d_dump(1, wusbhc->dev, _buf, wLength); + return result; +} + +/* + * Entry point for Root Hub operations + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +int wusbhc_rh_control(struct usb_hcd *usb_hcd, u16 reqntype, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + int result = -ENOSYS; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + + switch (reqntype) { + case GetHubDescriptor: + result = wusbhc_rh_get_hub_descr( + wusbhc, wValue, wIndex, + (struct usb_hub_descriptor *) buf, wLength); + break; + case ClearHubFeature: + result = wusbhc_rh_clear_hub_feat(wusbhc, wValue); + break; + case GetHubStatus: + result = wusbhc_rh_get_hub_status(wusbhc, (u32 *)buf, wLength); + break; + + case SetPortFeature: + result = wusbhc_rh_set_port_feat(wusbhc, wValue, wIndex >> 8, + (wIndex & 0xff) - 1); + break; + case ClearPortFeature: + result = wusbhc_rh_clear_port_feat(wusbhc, wValue, wIndex >> 8, + (wIndex & 0xff) - 1); + break; + case GetPortStatus: + result = wusbhc_rh_get_port_status(wusbhc, wIndex - 1, + (u32 *)buf, wLength); + break; + + case SetHubFeature: + default: + dev_err(wusbhc->dev, "%s (%p [%p], %x, %x, %x, %p, %x) " + "UNIMPLEMENTED\n", __func__, usb_hcd, wusbhc, reqntype, + wValue, wIndex, buf, wLength); + /* dump_stack(); */ + result = -ENOSYS; + } + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_control); + +int wusbhc_rh_suspend(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, wusbhc); + /* dump_stack(); */ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_suspend); + +int wusbhc_rh_resume(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, wusbhc); + /* dump_stack(); */ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_resume); + +int wusbhc_rh_start_port_reset(struct usb_hcd *usb_hcd, unsigned port_idx) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p], port_idx %u) UNIMPLEMENTED\n", + __func__, usb_hcd, wusbhc, port_idx); + WARN_ON(1); + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_start_port_reset); + +static void wusb_port_init(struct wusb_port *port) +{ + port->status |= USB_PORT_STAT_HIGH_SPEED; +} + +/* + * Alloc fake port specific fields and status. + */ +int wusbhc_rh_create(struct wusbhc *wusbhc) +{ + int result = -ENOMEM; + size_t port_size, itr; + port_size = wusbhc->ports_max * sizeof(wusbhc->port[0]); + wusbhc->port = kzalloc(port_size, GFP_KERNEL); + if (wusbhc->port == NULL) + goto error_port_alloc; + for (itr = 0; itr < wusbhc->ports_max; itr++) + wusb_port_init(&wusbhc->port[itr]); + result = 0; +error_port_alloc: + return result; +} + +void wusbhc_rh_destroy(struct wusbhc *wusbhc) +{ + kfree(wusbhc->port); +} diff --git a/drivers/usb/wusbcore/security.c b/drivers/usb/wusbcore/security.c new file mode 100644 index 00000000000..a101cad6a8d --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ +#include <linux/types.h> +#include <linux/usb/ch9.h> +#include <linux/random.h> +#include "wusbhc.h" + +/* + * DEBUG & SECURITY WARNING!!!! + * + * If you enable this past 1, the debug code will weaken the + * cryptographic safety of the system (on purpose, for debugging). + * + * Weaken means: + * we print secret keys and intermediate values all the way, + */ +#undef D_LOCAL +#define D_LOCAL 2 +#include <linux/uwb/debug.h> + +static void wusbhc_set_gtk_callback(struct urb *urb); +static void wusbhc_gtk_rekey_done_work(struct work_struct *work); + +int wusbhc_sec_create(struct wusbhc *wusbhc) +{ + wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data); + wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY; + wusbhc->gtk.descr.bReserved = 0; + + wusbhc->gtk_index = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + INIT_WORK(&wusbhc->gtk_rekey_done_work, wusbhc_gtk_rekey_done_work); + + return 0; +} + + +/* Called when the HC is destroyed */ +void wusbhc_sec_destroy(struct wusbhc *wusbhc) +{ +} + + +/** + * wusbhc_next_tkid - generate a new, currently unused, TKID + * @wusbhc: the WUSB host controller + * @wusb_dev: the device whose PTK the TKID is for + * (or NULL for a TKID for a GTK) + * + * The generated TKID consist of two parts: the device's authenicated + * address (or 0 or a GTK); and an incrementing number. This ensures + * that TKIDs cannot be shared between devices and by the time the + * incrementing number wraps around the older TKIDs will no longer be + * in use (a maximum of two keys may be active at any one time). + */ +static u32 wusbhc_next_tkid(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + u32 *tkid; + u32 addr; + + if (wusb_dev == NULL) { + tkid = &wusbhc->gtk_tkid; + addr = 0; + } else { + tkid = &wusb_port_by_idx(wusbhc, wusb_dev->port_idx)->ptk_tkid; + addr = wusb_dev->addr & 0x7f; + } + + *tkid = (addr << 8) | ((*tkid + 1) & 0xff); + + return *tkid; +} + +static void wusbhc_generate_gtk(struct wusbhc *wusbhc) +{ + const size_t key_size = sizeof(wusbhc->gtk.data); + u32 tkid; + + tkid = wusbhc_next_tkid(wusbhc, NULL); + + wusbhc->gtk.descr.tTKID[0] = (tkid >> 0) & 0xff; + wusbhc->gtk.descr.tTKID[1] = (tkid >> 8) & 0xff; + wusbhc->gtk.descr.tTKID[2] = (tkid >> 16) & 0xff; + + get_random_bytes(wusbhc->gtk.descr.bKeyData, key_size); +} + +/** + * wusbhc_sec_start - start the security management process + * @wusbhc: the WUSB host controller + * + * Generate and set an initial GTK on the host controller. + * + * Called when the HC is started. + */ +int wusbhc_sec_start(struct wusbhc *wusbhc) +{ + const size_t key_size = sizeof(wusbhc->gtk.data); + int result; + + wusbhc_generate_gtk(wusbhc); + + result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, + &wusbhc->gtk.descr.bKeyData, key_size); + if (result < 0) + dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n", + result); + + return result; +} + +/** + * wusbhc_sec_stop - stop the security management process + * @wusbhc: the WUSB host controller + * + * Wait for any pending GTK rekeys to stop. + */ +void wusbhc_sec_stop(struct wusbhc *wusbhc) +{ + cancel_work_sync(&wusbhc->gtk_rekey_done_work); +} + + +/** @returns encryption type name */ +const char *wusb_et_name(u8 x) +{ + switch (x) { + case USB_ENC_TYPE_UNSECURE: return "unsecure"; + case USB_ENC_TYPE_WIRED: return "wired"; + case USB_ENC_TYPE_CCM_1: return "CCM-1"; + case USB_ENC_TYPE_RSA_1: return "RSA-1"; + default: return "unknown"; + } +} +EXPORT_SYMBOL_GPL(wusb_et_name); + +/* + * Set the device encryption method + * + * We tell the device which encryption method to use; we do this when + * setting up the device's security. + */ +static int wusb_dev_set_encryption(struct usb_device *usb_dev, int value) +{ + int result; + struct device *dev = &usb_dev->dev; + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + + if (value) { + value = wusb_dev->ccm1_etd.bEncryptionValue; + } else { + /* FIXME: should be wusb_dev->etd[UNSECURE].bEncryptionValue */ + value = 0; + } + /* Set device's */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ENCRYPTION, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + value, 0, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Can't set device's WUSB encryption to " + "%s (value %d): %d\n", + wusb_et_name(wusb_dev->ccm1_etd.bEncryptionType), + wusb_dev->ccm1_etd.bEncryptionValue, result); + return result; +} + +/* + * Set the GTK to be used by a device. + * + * The device must be authenticated. + */ +static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct usb_device *usb_dev = wusb_dev->usb_dev; + + return usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_DT_KEY << 8 | wusbhc->gtk_index, 0, + &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, + 1000); +} + + +/* FIXME: prototype for adding security */ +int wusb_dev_sec_add(struct wusbhc *wusbhc, + struct usb_device *usb_dev, struct wusb_dev *wusb_dev) +{ + int result, bytes, secd_size; + struct device *dev = &usb_dev->dev; + struct usb_security_descriptor secd; + const struct usb_encryption_descriptor *etd, *ccm1_etd = NULL; + void *secd_buf; + const void *itr, *top; + char buf[64]; + + d_fnstart(3, dev, "(usb_dev %p, wusb_dev %p)\n", usb_dev, wusb_dev); + result = usb_get_descriptor(usb_dev, USB_DT_SECURITY, + 0, &secd, sizeof(secd)); + if (result < sizeof(secd)) { + dev_err(dev, "Can't read security descriptor or " + "not enough data: %d\n", result); + goto error_secd; + } + secd_size = le16_to_cpu(secd.wTotalLength); + d_printf(5, dev, "got %d bytes of sec descriptor, total is %d\n", + result, secd_size); + secd_buf = kmalloc(secd_size, GFP_KERNEL); + if (secd_buf == NULL) { + dev_err(dev, "Can't allocate space for security descriptors\n"); + goto error_secd_alloc; + } + result = usb_get_descriptor(usb_dev, USB_DT_SECURITY, + 0, secd_buf, secd_size); + if (result < secd_size) { + dev_err(dev, "Can't read security descriptor or " + "not enough data: %d\n", result); + goto error_secd_all; + } + d_printf(5, dev, "got %d bytes of sec descriptors\n", result); + bytes = 0; + itr = secd_buf + sizeof(secd); + top = secd_buf + result; + while (itr < top) { + etd = itr; + if (top - itr < sizeof(*etd)) { + dev_err(dev, "BUG: bad device security descriptor; " + "not enough data (%zu vs %zu bytes left)\n", + top - itr, sizeof(*etd)); + break; + } + if (etd->bLength < sizeof(*etd)) { + dev_err(dev, "BUG: bad device encryption descriptor; " + "descriptor is too short " + "(%u vs %zu needed)\n", + etd->bLength, sizeof(*etd)); + break; + } + itr += etd->bLength; + bytes += snprintf(buf + bytes, sizeof(buf) - bytes, + "%s (0x%02x/%02x) ", + wusb_et_name(etd->bEncryptionType), + etd->bEncryptionValue, etd->bAuthKeyIndex); + if (etd->bEncryptionType == USB_ENC_TYPE_CCM_1) + ccm1_etd = etd; + } + /* This code only supports CCM1 as of now. */ + /* FIXME: user has to choose which sec mode to use? + * In theory we want CCM */ + if (ccm1_etd == NULL) { + dev_err(dev, "WUSB device doesn't support CCM1 encryption, " + "can't use!\n"); + result = -EINVAL; + goto error_no_ccm1; + } + wusb_dev->ccm1_etd = *ccm1_etd; + dev_info(dev, "supported encryption: %s; using %s (0x%02x/%02x)\n", + buf, wusb_et_name(ccm1_etd->bEncryptionType), + ccm1_etd->bEncryptionValue, ccm1_etd->bAuthKeyIndex); + result = 0; + kfree(secd_buf); +out: + d_fnend(3, dev, "(usb_dev %p, wusb_dev %p) = %d\n", + usb_dev, wusb_dev, result); + return result; + + +error_no_ccm1: +error_secd_all: + kfree(secd_buf); +error_secd_alloc: +error_secd: + goto out; +} + +void wusb_dev_sec_rm(struct wusb_dev *wusb_dev) +{ + /* Nothing so far */ +} + +static void hs_printk(unsigned level, struct device *dev, + struct usb_handshake *hs) +{ + d_printf(level, dev, + " bMessageNumber: %u\n" + " bStatus: %u\n" + " tTKID: %02x %02x %02x\n" + " CDID: %02x %02x %02x %02x %02x %02x %02x %02x\n" + " %02x %02x %02x %02x %02x %02x %02x %02x\n" + " nonce: %02x %02x %02x %02x %02x %02x %02x %02x\n" + " %02x %02x %02x %02x %02x %02x %02x %02x\n" + " MIC: %02x %02x %02x %02x %02x %02x %02x %02x\n", + hs->bMessageNumber, hs->bStatus, + hs->tTKID[2], hs->tTKID[1], hs->tTKID[0], + hs->CDID[0], hs->CDID[1], hs->CDID[2], hs->CDID[3], + hs->CDID[4], hs->CDID[5], hs->CDID[6], hs->CDID[7], + hs->CDID[8], hs->CDID[9], hs->CDID[10], hs->CDID[11], + hs->CDID[12], hs->CDID[13], hs->CDID[14], hs->CDID[15], + hs->nonce[0], hs->nonce[1], hs->nonce[2], hs->nonce[3], + hs->nonce[4], hs->nonce[5], hs->nonce[6], hs->nonce[7], + hs->nonce[8], hs->nonce[9], hs->nonce[10], hs->nonce[11], + hs->nonce[12], hs->nonce[13], hs->nonce[14], hs->nonce[15], + hs->MIC[0], hs->MIC[1], hs->MIC[2], hs->MIC[3], + hs->MIC[4], hs->MIC[5], hs->MIC[6], hs->MIC[7]); +} + +/** + * Update the address of an unauthenticated WUSB device + * + * Once we have successfully authenticated, we take it to addr0 state + * and then to a normal address. + * + * Before the device's address (as known by it) was usb_dev->devnum | + * 0x80 (unauthenticated address). With this we update it to usb_dev->devnum. + */ +static int wusb_dev_update_address(struct wusbhc *wusbhc, + struct wusb_dev *wusb_dev) +{ + int result = -ENOMEM; + struct usb_device *usb_dev = wusb_dev->usb_dev; + struct device *dev = &usb_dev->dev; + u8 new_address = wusb_dev->addr & 0x7F; + + /* Set address 0 */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ADDRESS, 0, + 0, 0, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "auth failed: can't set address 0: %d\n", + result); + goto error_addr0; + } + result = wusb_set_dev_addr(wusbhc, wusb_dev, 0); + if (result < 0) + goto error_addr0; + usb_ep0_reinit(usb_dev); + + /* Set new (authenticated) address. */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ADDRESS, 0, + new_address, 0, NULL, 0, + 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "auth failed: can't set address %u: %d\n", + new_address, result); + goto error_addr; + } + result = wusb_set_dev_addr(wusbhc, wusb_dev, new_address); + if (result < 0) + goto error_addr; + usb_ep0_reinit(usb_dev); + usb_dev->authenticated = 1; +error_addr: +error_addr0: + return result; +} + +/* + * + * + */ +/* FIXME: split and cleanup */ +int wusb_dev_4way_handshake(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, + struct wusb_ckhdid *ck) +{ + int result = -ENOMEM; + struct usb_device *usb_dev = wusb_dev->usb_dev; + struct device *dev = &usb_dev->dev; + u32 tkid; + __le32 tkid_le; + struct usb_handshake *hs; + struct aes_ccm_nonce ccm_n; + u8 mic[8]; + struct wusb_keydvt_in keydvt_in; + struct wusb_keydvt_out keydvt_out; + + hs = kzalloc(3*sizeof(hs[0]), GFP_KERNEL); + if (hs == NULL) { + dev_err(dev, "can't allocate handshake data\n"); + goto error_kzalloc; + } + + /* We need to turn encryption before beginning the 4way + * hshake (WUSB1.0[.3.2.2]) */ + result = wusb_dev_set_encryption(usb_dev, 1); + if (result < 0) + goto error_dev_set_encryption; + + tkid = wusbhc_next_tkid(wusbhc, wusb_dev); + tkid_le = cpu_to_le32(tkid); + + hs[0].bMessageNumber = 1; + hs[0].bStatus = 0; + memcpy(hs[0].tTKID, &tkid_le, sizeof(hs[0].tTKID)); + hs[0].bReserved = 0; + memcpy(hs[0].CDID, &wusb_dev->cdid, sizeof(hs[0].CDID)); + get_random_bytes(&hs[0].nonce, sizeof(hs[0].nonce)); + memset(hs[0].MIC, 0, sizeof(hs[0].MIC)); /* Per WUSB1.0[T7-22] */ + + d_printf(1, dev, "I: sending hs1:\n"); + hs_printk(2, dev, &hs[0]); + + result = usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_HANDSHAKE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 1, 0, &hs[0], sizeof(hs[0]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake1: request failed: %d\n", result); + goto error_hs1; + } + + /* Handshake 2, from the device -- need to verify fields */ + result = usb_control_msg( + usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_GET_HANDSHAKE, + USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 2, 0, &hs[1], sizeof(hs[1]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake2: request failed: %d\n", result); + goto error_hs2; + } + d_printf(1, dev, "got HS2:\n"); + hs_printk(2, dev, &hs[1]); + + result = -EINVAL; + if (hs[1].bMessageNumber != 2) { + dev_err(dev, "Handshake2 failed: bad message number %u\n", + hs[1].bMessageNumber); + goto error_hs2; + } + if (hs[1].bStatus != 0) { + dev_err(dev, "Handshake2 failed: bad status %u\n", + hs[1].bStatus); + goto error_hs2; + } + if (memcmp(hs[0].tTKID, hs[1].tTKID, sizeof(hs[0].tTKID))) { + dev_err(dev, "Handshake2 failed: TKID mismatch " + "(#1 0x%02x%02x%02x vs #2 0x%02x%02x%02x)\n", + hs[0].tTKID[0], hs[0].tTKID[1], hs[0].tTKID[2], + hs[1].tTKID[0], hs[1].tTKID[1], hs[1].tTKID[2]); + goto error_hs2; + } + if (memcmp(hs[0].CDID, hs[1].CDID, sizeof(hs[0].CDID))) { + dev_err(dev, "Handshake2 failed: CDID mismatch\n"); + goto error_hs2; + } + + /* Setup the CCM nonce */ + memset(&ccm_n.sfn, 0, sizeof(ccm_n.sfn)); /* Per WUSB1.0[6.5.2] */ + memcpy(ccm_n.tkid, &tkid_le, sizeof(ccm_n.tkid)); + ccm_n.src_addr = wusbhc->uwb_rc->uwb_dev.dev_addr; + ccm_n.dest_addr.data[0] = wusb_dev->addr; + ccm_n.dest_addr.data[1] = 0; + + /* Derive the KCK and PTK from CK, the CCM, H and D nonces */ + memcpy(keydvt_in.hnonce, hs[0].nonce, sizeof(keydvt_in.hnonce)); + memcpy(keydvt_in.dnonce, hs[1].nonce, sizeof(keydvt_in.dnonce)); + result = wusb_key_derive(&keydvt_out, ck->data, &ccm_n, &keydvt_in); + if (result < 0) { + dev_err(dev, "Handshake2 failed: cannot derive keys: %d\n", + result); + goto error_hs2; + } + d_printf(2, dev, "KCK:\n"); + d_dump(2, dev, keydvt_out.kck, sizeof(keydvt_out.kck)); + d_printf(2, dev, "PTK:\n"); + d_dump(2, dev, keydvt_out.ptk, sizeof(keydvt_out.ptk)); + + /* Compute MIC and verify it */ + result = wusb_oob_mic(mic, keydvt_out.kck, &ccm_n, &hs[1]); + if (result < 0) { + dev_err(dev, "Handshake2 failed: cannot compute MIC: %d\n", + result); + goto error_hs2; + } + + d_printf(2, dev, "MIC:\n"); + d_dump(2, dev, mic, sizeof(mic)); + if (memcmp(hs[1].MIC, mic, sizeof(hs[1].MIC))) { + dev_err(dev, "Handshake2 failed: MIC mismatch\n"); + goto error_hs2; + } + + /* Send Handshake3 */ + hs[2].bMessageNumber = 3; + hs[2].bStatus = 0; + memcpy(hs[2].tTKID, &tkid_le, sizeof(hs[2].tTKID)); + hs[2].bReserved = 0; + memcpy(hs[2].CDID, &wusb_dev->cdid, sizeof(hs[2].CDID)); + memcpy(hs[2].nonce, hs[0].nonce, sizeof(hs[2].nonce)); + result = wusb_oob_mic(hs[2].MIC, keydvt_out.kck, &ccm_n, &hs[2]); + if (result < 0) { + dev_err(dev, "Handshake3 failed: cannot compute MIC: %d\n", + result); + goto error_hs2; + } + + d_printf(1, dev, "I: sending hs3:\n"); + hs_printk(2, dev, &hs[2]); + + result = usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_HANDSHAKE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 3, 0, &hs[2], sizeof(hs[2]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake3: request failed: %d\n", result); + goto error_hs3; + } + + d_printf(1, dev, "I: turning on encryption on host for device\n"); + d_dump(2, dev, keydvt_out.ptk, sizeof(keydvt_out.ptk)); + result = wusbhc->set_ptk(wusbhc, wusb_dev->port_idx, tkid, + keydvt_out.ptk, sizeof(keydvt_out.ptk)); + if (result < 0) + goto error_wusbhc_set_ptk; + + d_printf(1, dev, "I: setting a GTK\n"); + result = wusb_dev_set_gtk(wusbhc, wusb_dev); + if (result < 0) { + dev_err(dev, "Set GTK for device: request failed: %d\n", + result); + goto error_wusbhc_set_gtk; + } + + /* Update the device's address from unauth to auth */ + if (usb_dev->authenticated == 0) { + d_printf(1, dev, "I: updating addres to auth from non-auth\n"); + result = wusb_dev_update_address(wusbhc, wusb_dev); + if (result < 0) + goto error_dev_update_address; + } + result = 0; + d_printf(1, dev, "I: 4way handshke done, device authenticated\n"); + +error_dev_update_address: +error_wusbhc_set_gtk: +error_wusbhc_set_ptk: +error_hs3: +error_hs2: +error_hs1: + memset(hs, 0, 3*sizeof(hs[0])); + memset(&keydvt_out, 0, sizeof(keydvt_out)); + memset(&keydvt_in, 0, sizeof(keydvt_in)); + memset(&ccm_n, 0, sizeof(ccm_n)); + memset(mic, 0, sizeof(mic)); + if (result < 0) { + /* error path */ + wusb_dev_set_encryption(usb_dev, 0); + } +error_dev_set_encryption: + kfree(hs); +error_kzalloc: + return result; +} + +/* + * Once all connected and authenticated devices have received the new + * GTK, switch the host to using it. + */ +static void wusbhc_gtk_rekey_done_work(struct work_struct *work) +{ + struct wusbhc *wusbhc = container_of(work, struct wusbhc, gtk_rekey_done_work); + size_t key_size = sizeof(wusbhc->gtk.data); + + mutex_lock(&wusbhc->mutex); + + if (--wusbhc->pending_set_gtks == 0) + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); + + mutex_unlock(&wusbhc->mutex); +} + +static void wusbhc_set_gtk_callback(struct urb *urb) +{ + struct wusbhc *wusbhc = urb->context; + + queue_work(wusbd, &wusbhc->gtk_rekey_done_work); +} + +/** + * wusbhc_gtk_rekey - generate and distribute a new GTK + * @wusbhc: the WUSB host controller + * + * Generate a new GTK and distribute it to all connected and + * authenticated devices. When all devices have the new GTK, the host + * starts using it. + * + * This must be called after every device disconnect (see [WUSB] + * section 6.2.11.2). + */ +void wusbhc_gtk_rekey(struct wusbhc *wusbhc) +{ + static const size_t key_size = sizeof(wusbhc->gtk.data); + int p; + + wusbhc_generate_gtk(wusbhc); + + for (p = 0; p < wusbhc->ports_max; p++) { + struct wusb_dev *wusb_dev; + + wusb_dev = wusbhc->port[p].wusb_dev; + if (!wusb_dev || !wusb_dev->usb_dev | !wusb_dev->usb_dev->authenticated) + continue; + + usb_fill_control_urb(wusb_dev->set_gtk_urb, wusb_dev->usb_dev, + usb_sndctrlpipe(wusb_dev->usb_dev, 0), + (void *)wusb_dev->set_gtk_req, + &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, + wusbhc_set_gtk_callback, wusbhc); + if (usb_submit_urb(wusb_dev->set_gtk_urb, GFP_KERNEL) == 0) + wusbhc->pending_set_gtks++; + } + if (wusbhc->pending_set_gtks == 0) + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); +} diff --git a/drivers/usb/wusbcore/wa-hc.c b/drivers/usb/wusbcore/wa-hc.c new file mode 100644 index 00000000000..9d04722415b --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ +#include "wusbhc.h" +#include "wa-hc.h" + +/** + * Assumes + * + * wa->usb_dev and wa->usb_iface initialized and refcounted, + * wa->wa_descr initialized. + */ +int wa_create(struct wahc *wa, struct usb_interface *iface) +{ + int result; + struct device *dev = &iface->dev; + + result = wa_rpipes_create(wa); + if (result < 0) + goto error_rpipes_create; + /* Fill up Data Transfer EP pointers */ + wa->dti_epd = &iface->cur_altsetting->endpoint[1].desc; + wa->dto_epd = &iface->cur_altsetting->endpoint[2].desc; + wa->xfer_result_size = le16_to_cpu(wa->dti_epd->wMaxPacketSize); + wa->xfer_result = kmalloc(wa->xfer_result_size, GFP_KERNEL); + if (wa->xfer_result == NULL) + goto error_xfer_result_alloc; + result = wa_nep_create(wa, iface); + if (result < 0) { + dev_err(dev, "WA-CDS: can't initialize notif endpoint: %d\n", + result); + goto error_nep_create; + } + return 0; + +error_nep_create: + kfree(wa->xfer_result); +error_xfer_result_alloc: + wa_rpipes_destroy(wa); +error_rpipes_create: + return result; +} +EXPORT_SYMBOL_GPL(wa_create); + + +void __wa_destroy(struct wahc *wa) +{ + if (wa->dti_urb) { + usb_kill_urb(wa->dti_urb); + usb_put_urb(wa->dti_urb); + usb_kill_urb(wa->buf_in_urb); + usb_put_urb(wa->buf_in_urb); + } + kfree(wa->xfer_result); + wa_nep_destroy(wa); + wa_rpipes_destroy(wa); +} +EXPORT_SYMBOL_GPL(__wa_destroy); + +/** + * wa_reset_all - reset the WA device + * @wa: the WA to be reset + * + * For HWAs the radio controller and all other PALs are also reset. + */ +void wa_reset_all(struct wahc *wa) +{ + /* FIXME: assuming HWA. */ + wusbhc_reset_all(wa->wusb); +} + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("Wireless USB Wire Adapter core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/wa-hc.h b/drivers/usb/wusbcore/wa-hc.h new file mode 100644 index 00000000000..586d350cdb4 --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver implements a USB Host Controller (struct usb_hcd) for a + * Wireless USB Host Controller based on the Wireless USB 1.0 + * Host-Wire-Adapter specification (in layman terms, a USB-dongle that + * implements a Wireless USB host). + * + * Check out the Design-overview.txt file in the source documentation + * for other details on the implementation. + * + * Main blocks: + * + * driver glue with the driver API, workqueue daemon + * + * lc RC instance life cycle management (create, destroy...) + * + * hcd glue with the USB API Host Controller Interface API. + * + * nep Notification EndPoint managent: collect notifications + * and queue them with the workqueue daemon. + * + * Handle notifications as coming from the NEP. Sends them + * off others to their respective modules (eg: connect, + * disconnect and reset go to devconnect). + * + * rpipe Remote Pipe management; rpipe is what we use to write + * to an endpoint on a WUSB device that is connected to a + * HWA RC. + * + * xfer Transfer managment -- this is all the code that gets a + * buffer and pushes it to a device (or viceversa). * + * + * Some day a lot of this code will be shared between this driver and + * the drivers for DWA (xfer, rpipe). + * + * All starts at driver.c:hwahc_probe(), when one of this guys is + * connected. hwahc_disconnect() stops it. + * + * During operation, the main driver is devices connecting or + * disconnecting. They cause the HWA RC to send notifications into + * nep.c:hwahc_nep_cb() that will dispatch them to + * notif.c:wa_notif_dispatch(). From there they will fan to cause + * device connects, disconnects, etc. + * + * Note much of the activity is difficult to follow. For example a + * device connect goes to devconnect, which will cause the "fake" root + * hub port to show a connect and stop there. Then khubd will notice + * and call into the rh.c:hwahc_rc_port_reset() code to authenticate + * the device (and this might require user intervention) and enable + * the port. + * + * We also have a timer workqueue going from devconnect.c that + * schedules in hwahc_devconnect_create(). + * + * The rest of the traffic is in the usual entry points of a USB HCD, + * which are hooked up in driver.c:hwahc_rc_driver, and defined in + * hcd.c. + */ + +#ifndef __HWAHC_INTERNAL_H__ +#define __HWAHC_INTERNAL_H__ + +#include <linux/completion.h> +#include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/uwb.h> +#include <linux/usb/wusb.h> +#include <linux/usb/wusb-wa.h> + +struct wusbhc; +struct wahc; +extern void wa_urb_enqueue_run(struct work_struct *ws); + +/** + * RPipe instance + * + * @descr's fields are kept in LE, as we need to send it back and + * forth. + * + * @wa is referenced when set + * + * @segs_available is the number of requests segments that still can + * be submitted to the controller without overloading + * it. It is initialized to descr->wRequests when + * aiming. + * + * A rpipe supports a max of descr->wRequests at the same time; before + * submitting seg_lock has to be taken. If segs_avail > 0, then we can + * submit; if not, we have to queue them. + */ +struct wa_rpipe { + struct kref refcnt; + struct usb_rpipe_descriptor descr; + struct usb_host_endpoint *ep; + struct wahc *wa; + spinlock_t seg_lock; + struct list_head seg_list; + atomic_t segs_available; + u8 buffer[1]; /* For reads/writes on USB */ +}; + + +/** + * Instance of a HWA Host Controller + * + * Except where a more specific lock/mutex applies or atomic, all + * fields protected by @mutex. + * + * @wa_descr Can be accessed without locking because it is in + * the same area where the device descriptors were + * read, so it is guaranteed to exist umodified while + * the device exists. + * + * Endianess has been converted to CPU's. + * + * @nep_* can be accessed without locking as its processing is + * serialized; we submit a NEP URB and it comes to + * hwahc_nep_cb(), which won't issue another URB until it is + * done processing it. + * + * @xfer_list: + * + * List of active transfers to verify existence from a xfer id + * gotten from the xfer result message. Can't use urb->list because + * it goes by endpoint, and we don't know the endpoint at the time + * when we get the xfer result message. We can't really rely on the + * pointer (will have to change for 64 bits) as the xfer id is 32 bits. + * + * @xfer_delayed_list: List of transfers that need to be started + * (with a workqueue, because they were + * submitted from an atomic context). + * + * FIXME: this needs to be layered up: a wusbhc layer (for sharing + * comonalities with WHCI), a wa layer (for sharing + * comonalities with DWA-RC). + */ +struct wahc { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + + /* HC to deliver notifications */ + union { + struct wusbhc *wusb; + struct dwahc *dwa; + }; + + const struct usb_endpoint_descriptor *dto_epd, *dti_epd; + const struct usb_wa_descriptor *wa_descr; + + struct urb *nep_urb; /* Notification EndPoint [lockless] */ + struct edc nep_edc; + void *nep_buffer; + size_t nep_buffer_size; + + atomic_t notifs_queued; + + u16 rpipes; + unsigned long *rpipe_bm; /* rpipe usage bitmap */ + spinlock_t rpipe_bm_lock; /* protect rpipe_bm */ + struct mutex rpipe_mutex; /* assigning resources to endpoints */ + + struct urb *dti_urb; /* URB for reading xfer results */ + struct urb *buf_in_urb; /* URB for reading data in */ + struct edc dti_edc; /* DTI error density counter */ + struct wa_xfer_result *xfer_result; /* real size = dti_ep maxpktsize */ + size_t xfer_result_size; + + s32 status; /* For reading status */ + + struct list_head xfer_list; + struct list_head xfer_delayed_list; + spinlock_t xfer_list_lock; + struct work_struct xfer_work; + atomic_t xfer_id_count; +}; + + +extern int wa_create(struct wahc *wa, struct usb_interface *iface); +extern void __wa_destroy(struct wahc *wa); +void wa_reset_all(struct wahc *wa); + + +/* Miscellaneous constants */ +enum { + /** Max number of EPROTO errors we tolerate on the NEP in a + * period of time */ + HWAHC_EPROTO_MAX = 16, + /** Period of time for EPROTO errors (in jiffies) */ + HWAHC_EPROTO_PERIOD = 4 * HZ, +}; + + +/* Notification endpoint handling */ +extern int wa_nep_create(struct wahc *, struct usb_interface *); +extern void wa_nep_destroy(struct wahc *); + +static inline int wa_nep_arm(struct wahc *wa, gfp_t gfp_mask) +{ + struct urb *urb = wa->nep_urb; + urb->transfer_buffer = wa->nep_buffer; + urb->transfer_buffer_length = wa->nep_buffer_size; + return usb_submit_urb(urb, gfp_mask); +} + +static inline void wa_nep_disarm(struct wahc *wa) +{ + usb_kill_urb(wa->nep_urb); +} + + +/* RPipes */ +static inline void wa_rpipe_init(struct wahc *wa) +{ + spin_lock_init(&wa->rpipe_bm_lock); + mutex_init(&wa->rpipe_mutex); +} + +static inline void wa_init(struct wahc *wa) +{ + edc_init(&wa->nep_edc); + atomic_set(&wa->notifs_queued, 0); + wa_rpipe_init(wa); + edc_init(&wa->dti_edc); + INIT_LIST_HEAD(&wa->xfer_list); + INIT_LIST_HEAD(&wa->xfer_delayed_list); + spin_lock_init(&wa->xfer_list_lock); + INIT_WORK(&wa->xfer_work, wa_urb_enqueue_run); + atomic_set(&wa->xfer_id_count, 1); +} + +/** + * Destroy a pipe (when refcount drops to zero) + * + * Assumes it has been moved to the "QUIESCING" state. + */ +struct wa_xfer; +extern void rpipe_destroy(struct kref *_rpipe); +static inline +void __rpipe_get(struct wa_rpipe *rpipe) +{ + kref_get(&rpipe->refcnt); +} +extern int rpipe_get_by_ep(struct wahc *, struct usb_host_endpoint *, + struct urb *, gfp_t); +static inline void rpipe_put(struct wa_rpipe *rpipe) +{ + kref_put(&rpipe->refcnt, rpipe_destroy); + +} +extern void rpipe_ep_disable(struct wahc *, struct usb_host_endpoint *); +extern int wa_rpipes_create(struct wahc *); +extern void wa_rpipes_destroy(struct wahc *); +static inline void rpipe_avail_dec(struct wa_rpipe *rpipe) +{ + atomic_dec(&rpipe->segs_available); +} + +/** + * Returns true if the rpipe is ready to submit more segments. + */ +static inline int rpipe_avail_inc(struct wa_rpipe *rpipe) +{ + return atomic_inc_return(&rpipe->segs_available) > 0 + && !list_empty(&rpipe->seg_list); +} + + +/* Transferring data */ +extern int wa_urb_enqueue(struct wahc *, struct usb_host_endpoint *, + struct urb *, gfp_t); +extern int wa_urb_dequeue(struct wahc *, struct urb *); +extern void wa_handle_notif_xfer(struct wahc *, struct wa_notif_hdr *); + + +/* Misc + * + * FIXME: Refcounting for the actual @hwahc object is not correct; I + * mean, this should be refcounting on the HCD underneath, but + * it is not. In any case, the semantics for HCD refcounting + * are *weird*...on refcount reaching zero it just frees + * it...no RC specific function is called...unless I miss + * something. + * + * FIXME: has to go away in favour of an 'struct' hcd based sollution + */ +static inline struct wahc *wa_get(struct wahc *wa) +{ + usb_get_intf(wa->usb_iface); + return wa; +} + +static inline void wa_put(struct wahc *wa) +{ + usb_put_intf(wa->usb_iface); +} + + +static inline int __wa_feature(struct wahc *wa, unsigned op, u16 feature) +{ + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + op ? USB_REQ_SET_FEATURE : USB_REQ_CLEAR_FEATURE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + feature, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + + +static inline int __wa_set_feature(struct wahc *wa, u16 feature) +{ + return __wa_feature(wa, 1, feature); +} + + +static inline int __wa_clear_feature(struct wahc *wa, u16 feature) +{ + return __wa_feature(wa, 0, feature); +} + + +/** + * Return the status of a Wire Adapter + * + * @wa: Wire Adapter instance + * @returns < 0 errno code on error, or status bitmap as described + * in WUSB1.0[8.3.1.6]. + * + * NOTE: need malloc, some arches don't take USB from the stack + */ +static inline +s32 __wa_get_status(struct wahc *wa) +{ + s32 result; + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_GET_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + &wa->status, sizeof(wa->status), + 1000 /* FIXME: arbitrary */); + if (result >= 0) + result = wa->status; + return result; +} + + +/** + * Waits until the Wire Adapter's status matches @mask/@value + * + * @wa: Wire Adapter instance. + * @returns < 0 errno code on error, otherwise status. + * + * Loop until the WAs status matches the mask and value (status & mask + * == value). Timeout if it doesn't happen. + * + * FIXME: is there an official specification on how long status + * changes can take? + */ +static inline s32 __wa_wait_status(struct wahc *wa, u32 mask, u32 value) +{ + s32 result; + unsigned loops = 10; + do { + msleep(50); + result = __wa_get_status(wa); + if ((result & mask) == value) + break; + if (loops-- == 0) { + result = -ETIMEDOUT; + break; + } + } while (result >= 0); + return result; +} + + +/** Command @hwahc to stop, @returns 0 if ok, < 0 errno code on error */ +static inline int __wa_stop(struct wahc *wa) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + + result = __wa_clear_feature(wa, WA_ENABLE); + if (result < 0 && result != -ENODEV) { + dev_err(dev, "error commanding HC to stop: %d\n", result); + goto out; + } + result = __wa_wait_status(wa, WA_ENABLE, 0); + if (result < 0 && result != -ENODEV) + dev_err(dev, "error waiting for HC to stop: %d\n", result); +out: + return 0; +} + + +#endif /* #ifndef __HWAHC_INTERNAL_H__ */ diff --git a/drivers/usb/wusbcore/wa-nep.c b/drivers/usb/wusbcore/wa-nep.c new file mode 100644 index 00000000000..3f542990c73 --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This part takes care of getting the notification from the hw + * only and dispatching through wusbwad into + * wa_notif_dispatch. Handling is done there. + * + * WA notifications are limited in size; most of them are three or + * four bytes long, and the longest is the HWA Device Notification, + * which would not exceed 38 bytes (DNs are limited in payload to 32 + * bytes plus 3 bytes header (WUSB1.0[7.6p2]), plus 3 bytes HWA + * header (WUSB1.0[8.5.4.2]). + * + * It is not clear if more than one Device Notification can be packed + * in a HWA Notification, I assume no because of the wording in + * WUSB1.0[8.5.4.2]. In any case, the bigger any notification could + * get is 256 bytes (as the bLength field is a byte). + * + * So what we do is we have this buffer and read into it; when a + * notification arrives we schedule work to a specific, single thread + * workqueue (so notifications are serialized) and copy the + * notification data. After scheduling the work, we rearm the read from + * the notification endpoint. + * + * Entry points here are: + * + * wa_nep_[create|destroy]() To initialize/release this subsystem + * + * wa_nep_cb() Callback for the notification + * endpoint; when data is ready, this + * does the dispatching. + */ +#include <linux/workqueue.h> +#include <linux/ctype.h> +#include <linux/uwb/debug.h> +#include "wa-hc.h" +#include "wusbhc.h" + +/* Structure for queueing notifications to the workqueue */ +struct wa_notif_work { + struct work_struct work; + struct wahc *wa; + size_t size; + u8 data[]; +}; + +/* + * Process incoming notifications from the WA's Notification EndPoint + * [the wuswad daemon, basically] + * + * @_nw: Pointer to a descriptor which has the pointer to the + * @wa, the size of the buffer and the work queue + * structure (so we can free all when done). + * @returns 0 if ok, < 0 errno code on error. + * + * All notifications follow the same format; they need to start with a + * 'struct wa_notif_hdr' header, so it is easy to parse through + * them. We just break the buffer in individual notifications (the + * standard doesn't say if it can be done or is forbidden, so we are + * cautious) and dispatch each. + * + * So the handling layers are is: + * + * WA specific notification (from NEP) + * Device Notification Received -> wa_handle_notif_dn() + * WUSB Device notification generic handling + * BPST Adjustment -> wa_handle_notif_bpst_adj() + * ... -> ... + * + * @wa has to be referenced + */ +static void wa_notif_dispatch(struct work_struct *ws) +{ + void *itr; + u8 missing = 0; + struct wa_notif_work *nw = container_of(ws, struct wa_notif_work, work); + struct wahc *wa = nw->wa; + struct wa_notif_hdr *notif_hdr; + size_t size; + + struct device *dev = &wa->usb_iface->dev; + +#if 0 + /* FIXME: need to check for this??? */ + if (usb_hcd->state == HC_STATE_QUIESCING) /* Going down? */ + goto out; /* screw it */ +#endif + atomic_dec(&wa->notifs_queued); /* Throttling ctl */ + dev = &wa->usb_iface->dev; + size = nw->size; + itr = nw->data; + + while (size) { + if (size < sizeof(*notif_hdr)) { + missing = sizeof(*notif_hdr) - size; + goto exhausted_buffer; + } + notif_hdr = itr; + if (size < notif_hdr->bLength) + goto exhausted_buffer; + itr += notif_hdr->bLength; + size -= notif_hdr->bLength; + /* Dispatch the notification [don't use itr or size!] */ + switch (notif_hdr->bNotifyType) { + case HWA_NOTIF_DN: { + struct hwa_notif_dn *hwa_dn; + hwa_dn = container_of(notif_hdr, struct hwa_notif_dn, + hdr); + wusbhc_handle_dn(wa->wusb, hwa_dn->bSourceDeviceAddr, + hwa_dn->dndata, + notif_hdr->bLength - sizeof(*hwa_dn)); + break; + } + case WA_NOTIF_TRANSFER: + wa_handle_notif_xfer(wa, notif_hdr); + break; + case DWA_NOTIF_RWAKE: + case DWA_NOTIF_PORTSTATUS: + case HWA_NOTIF_BPST_ADJ: + /* FIXME: unimplemented WA NOTIFs */ + /* fallthru */ + default: + if (printk_ratelimit()) { + dev_err(dev, "HWA: unknown notification 0x%x, " + "%zu bytes; discarding\n", + notif_hdr->bNotifyType, + (size_t)notif_hdr->bLength); + dump_bytes(dev, notif_hdr, 16); + } + break; + } + } +out: + wa_put(wa); + kfree(nw); + return; + + /* THIS SHOULD NOT HAPPEN + * + * Buffer exahusted with partial data remaining; just warn and + * discard the data, as this should not happen. + */ +exhausted_buffer: + if (!printk_ratelimit()) + goto out; + dev_warn(dev, "HWA: device sent short notification, " + "%d bytes missing; discarding %d bytes.\n", + missing, (int)size); + dump_bytes(dev, itr, size); + goto out; +} + +/* + * Deliver incoming WA notifications to the wusbwa workqueue + * + * @wa: Pointer the Wire Adapter Controller Data Streaming + * instance (part of an 'struct usb_hcd'). + * @size: Size of the received buffer + * @returns 0 if ok, < 0 errno code on error. + * + * The input buffer is @wa->nep_buffer, with @size bytes + * (guaranteed to fit in the allocated space, + * @wa->nep_buffer_size). + */ +static int wa_nep_queue(struct wahc *wa, size_t size) +{ + int result = 0; + struct device *dev = &wa->usb_iface->dev; + struct wa_notif_work *nw; + + /* dev_fnstart(dev, "(wa %p, size %zu)\n", wa, size); */ + BUG_ON(size > wa->nep_buffer_size); + if (size == 0) + goto out; + if (atomic_read(&wa->notifs_queued) > 200) { + if (printk_ratelimit()) + dev_err(dev, "Too many notifications queued, " + "throttling back\n"); + goto out; + } + nw = kzalloc(sizeof(*nw) + size, GFP_ATOMIC); + if (nw == NULL) { + if (printk_ratelimit()) + dev_err(dev, "No memory to queue notification\n"); + goto out; + } + INIT_WORK(&nw->work, wa_notif_dispatch); + nw->wa = wa_get(wa); + nw->size = size; + memcpy(nw->data, wa->nep_buffer, size); + atomic_inc(&wa->notifs_queued); /* Throttling ctl */ + queue_work(wusbd, &nw->work); +out: + /* dev_fnend(dev, "(wa %p, size %zu) = result\n", wa, size, result); */ + return result; +} + +/* + * Callback for the notification event endpoint + * + * Check's that everything is fine and then passes the data to be + * queued to the workqueue. + */ +static void wa_nep_cb(struct urb *urb) +{ + int result; + struct wahc *wa = urb->context; + struct device *dev = &wa->usb_iface->dev; + + switch (result = urb->status) { + case 0: + result = wa_nep_queue(wa, urb->actual_length); + if (result < 0) + dev_err(dev, "NEP: unable to process notification(s): " + "%d\n", result); + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: + dev_dbg(dev, "NEP: going down %d\n", urb->status); + goto out; + default: /* On general errors, we retry unless it gets ugly */ + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "NEP: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + goto out; + } + dev_err(dev, "NEP: URB error %d\n", urb->status); + } + result = wa_nep_arm(wa, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "NEP: cannot submit URB: %d\n", result); + wa_reset_all(wa); + } +out: + return; +} + +/* + * Initialize @wa's notification and event's endpoint stuff + * + * This includes the allocating the read buffer, the context ID + * allocation bitmap, the URB and submitting the URB. + */ +int wa_nep_create(struct wahc *wa, struct usb_interface *iface) +{ + int result; + struct usb_endpoint_descriptor *epd; + struct usb_device *usb_dev = interface_to_usbdev(iface); + struct device *dev = &iface->dev; + + edc_init(&wa->nep_edc); + epd = &iface->cur_altsetting->endpoint[0].desc; + wa->nep_buffer_size = 1024; + wa->nep_buffer = kmalloc(wa->nep_buffer_size, GFP_KERNEL); + if (wa->nep_buffer == NULL) { + dev_err(dev, "Unable to allocate notification's read buffer\n"); + goto error_nep_buffer; + } + wa->nep_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->nep_urb == NULL) { + dev_err(dev, "Unable to allocate notification URB\n"); + goto error_urb_alloc; + } + usb_fill_int_urb(wa->nep_urb, usb_dev, + usb_rcvintpipe(usb_dev, epd->bEndpointAddress), + wa->nep_buffer, wa->nep_buffer_size, + wa_nep_cb, wa, epd->bInterval); + result = wa_nep_arm(wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "Cannot submit notification URB: %d\n", result); + goto error_nep_arm; + } + return 0; + +error_nep_arm: + usb_free_urb(wa->nep_urb); +error_urb_alloc: + kfree(wa->nep_buffer); +error_nep_buffer: + return -ENOMEM; +} + +void wa_nep_destroy(struct wahc *wa) +{ + wa_nep_disarm(wa); + usb_free_urb(wa->nep_urb); + kfree(wa->nep_buffer); +} diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c new file mode 100644 index 00000000000..f18e4aae66e --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * RPIPE + * + * Targetted at different downstream endpoints + * + * Descriptor: use to config the remote pipe. + * + * The number of blocks could be dynamic (wBlocks in descriptor is + * 0)--need to schedule them then. + * + * Each bit in wa->rpipe_bm represents if an rpipe is being used or + * not. Rpipes are represented with a 'struct wa_rpipe' that is + * attached to the hcpriv member of a 'struct usb_host_endpoint'. + * + * When you need to xfer data to an endpoint, you get an rpipe for it + * with wa_ep_rpipe_get(), which gives you a reference to the rpipe + * and keeps a single one (the first one) with the endpoint. When you + * are done transferring, you drop that reference. At the end the + * rpipe is always allocated and bound to the endpoint. There it might + * be recycled when not used. + * + * Addresses: + * + * We use a 1:1 mapping mechanism between port address (0 based + * index, actually) and the address. The USB stack knows about this. + * + * USB Stack port number 4 (1 based) + * WUSB code port index 3 (0 based) + * USB Addresss 5 (2 based -- 0 is for default, 1 for root hub) + * + * Now, because we don't use the concept as default address exactly + * like the (wired) USB code does, we need to kind of skip it. So we + * never take addresses from the urb->pipe, but from the + * urb->dev->devnum, to make sure that we always have the right + * destination address. + */ +#include <linux/init.h> +#include <asm/atomic.h> +#include <linux/bitmap.h> +#include "wusbhc.h" +#include "wa-hc.h" + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + + +static int __rpipe_get_descr(struct wahc *wa, + struct usb_rpipe_descriptor *descr, u16 index) +{ + ssize_t result; + struct device *dev = &wa->usb_iface->dev; + + /* Get the RPIPE descriptor -- we cannot use the usb_get_descriptor() + * function because the arguments are different. + */ + d_printf(1, dev, "rpipe %u: get descr\n", index); + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DT_RPIPE<<8, index, descr, sizeof(*descr), + 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "rpipe %u: get descriptor failed: %d\n", + index, (int)result); + goto error; + } + if (result < sizeof(*descr)) { + dev_err(dev, "rpipe %u: got short descriptor " + "(%zd vs %zd bytes needed)\n", + index, result, sizeof(*descr)); + result = -EINVAL; + goto error; + } + result = 0; + +error: + return result; +} + +/* + * + * The descriptor is assumed to be properly initialized (ie: you got + * it through __rpipe_get_descr()). + */ +static int __rpipe_set_descr(struct wahc *wa, + struct usb_rpipe_descriptor *descr, u16 index) +{ + ssize_t result; + struct device *dev = &wa->usb_iface->dev; + + /* we cannot use the usb_get_descriptor() function because the + * arguments are different. + */ + d_printf(1, dev, "rpipe %u: set descr\n", index); + result = usb_control_msg( + wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DT_RPIPE<<8, index, descr, sizeof(*descr), + HZ / 10); + if (result < 0) { + dev_err(dev, "rpipe %u: set descriptor failed: %d\n", + index, (int)result); + goto error; + } + if (result < sizeof(*descr)) { + dev_err(dev, "rpipe %u: sent short descriptor " + "(%zd vs %zd bytes required)\n", + index, result, sizeof(*descr)); + result = -EINVAL; + goto error; + } + result = 0; + +error: + return result; + +} + +static void rpipe_init(struct wa_rpipe *rpipe) +{ + kref_init(&rpipe->refcnt); + spin_lock_init(&rpipe->seg_lock); + INIT_LIST_HEAD(&rpipe->seg_list); +} + +static unsigned rpipe_get_idx(struct wahc *wa, unsigned rpipe_idx) +{ + unsigned long flags; + + spin_lock_irqsave(&wa->rpipe_bm_lock, flags); + rpipe_idx = find_next_zero_bit(wa->rpipe_bm, wa->rpipes, rpipe_idx); + if (rpipe_idx < wa->rpipes) + set_bit(rpipe_idx, wa->rpipe_bm); + spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags); + + return rpipe_idx; +} + +static void rpipe_put_idx(struct wahc *wa, unsigned rpipe_idx) +{ + unsigned long flags; + + spin_lock_irqsave(&wa->rpipe_bm_lock, flags); + clear_bit(rpipe_idx, wa->rpipe_bm); + spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags); +} + +void rpipe_destroy(struct kref *_rpipe) +{ + struct wa_rpipe *rpipe = container_of(_rpipe, struct wa_rpipe, refcnt); + u8 index = le16_to_cpu(rpipe->descr.wRPipeIndex); + d_fnstart(1, NULL, "(rpipe %p %u)\n", rpipe, index); + if (rpipe->ep) + rpipe->ep->hcpriv = NULL; + rpipe_put_idx(rpipe->wa, index); + wa_put(rpipe->wa); + kfree(rpipe); + d_fnend(1, NULL, "(rpipe %p %u)\n", rpipe, index); +} +EXPORT_SYMBOL_GPL(rpipe_destroy); + +/* + * Locate an idle rpipe, create an structure for it and return it + * + * @wa is referenced and unlocked + * @crs enum rpipe_attr, required endpoint characteristics + * + * The rpipe can be used only sequentially (not in parallel). + * + * The rpipe is moved into the "ready" state. + */ +static int rpipe_get_idle(struct wa_rpipe **prpipe, struct wahc *wa, u8 crs, + gfp_t gfp) +{ + int result; + unsigned rpipe_idx; + struct wa_rpipe *rpipe; + struct device *dev = &wa->usb_iface->dev; + + d_fnstart(3, dev, "(wa %p crs 0x%02x)\n", wa, crs); + rpipe = kzalloc(sizeof(*rpipe), gfp); + if (rpipe == NULL) + return -ENOMEM; + rpipe_init(rpipe); + + /* Look for an idle pipe */ + for (rpipe_idx = 0; rpipe_idx < wa->rpipes; rpipe_idx++) { + rpipe_idx = rpipe_get_idx(wa, rpipe_idx); + if (rpipe_idx >= wa->rpipes) /* no more pipes :( */ + break; + result = __rpipe_get_descr(wa, &rpipe->descr, rpipe_idx); + if (result < 0) + dev_err(dev, "Can't get descriptor for rpipe %u: %d\n", + rpipe_idx, result); + else if ((rpipe->descr.bmCharacteristics & crs) != 0) + goto found; + rpipe_put_idx(wa, rpipe_idx); + } + *prpipe = NULL; + kfree(rpipe); + d_fnend(3, dev, "(wa %p crs 0x%02x) = -ENXIO\n", wa, crs); + return -ENXIO; + +found: + set_bit(rpipe_idx, wa->rpipe_bm); + rpipe->wa = wa_get(wa); + *prpipe = rpipe; + d_fnstart(3, dev, "(wa %p crs 0x%02x) = 0\n", wa, crs); + return 0; +} + +static int __rpipe_reset(struct wahc *wa, unsigned index) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + + d_printf(1, dev, "rpipe %u: reset\n", index); + result = usb_control_msg( + wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_RPIPE_RESET, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "rpipe %u: reset failed: %d\n", + index, result); + return result; +} + +/* + * Fake companion descriptor for ep0 + * + * See WUSB1.0[7.4.4], most of this is zero for bulk/int/ctl + */ +static struct usb_wireless_ep_comp_descriptor epc0 = { + .bLength = sizeof(epc0), + .bDescriptorType = USB_DT_WIRELESS_ENDPOINT_COMP, +/* .bMaxBurst = 1, */ + .bMaxSequence = 31, +}; + +/* + * Look for EP companion descriptor + * + * Get there, look for Inara in the endpoint's extra descriptors + */ +static struct usb_wireless_ep_comp_descriptor *rpipe_epc_find( + struct device *dev, struct usb_host_endpoint *ep) +{ + void *itr; + size_t itr_size; + struct usb_descriptor_header *hdr; + struct usb_wireless_ep_comp_descriptor *epcd; + + d_fnstart(3, dev, "(ep %p)\n", ep); + if (ep->desc.bEndpointAddress == 0) { + epcd = &epc0; + goto out; + } + itr = ep->extra; + itr_size = ep->extralen; + epcd = NULL; + while (itr_size > 0) { + if (itr_size < sizeof(*hdr)) { + dev_err(dev, "HW Bug? ep 0x%02x: extra descriptors " + "at offset %zu: only %zu bytes left\n", + ep->desc.bEndpointAddress, + itr - (void *) ep->extra, itr_size); + break; + } + hdr = itr; + if (hdr->bDescriptorType == USB_DT_WIRELESS_ENDPOINT_COMP) { + epcd = itr; + break; + } + if (hdr->bLength > itr_size) { + dev_err(dev, "HW Bug? ep 0x%02x: extra descriptor " + "at offset %zu (type 0x%02x) " + "length %d but only %zu bytes left\n", + ep->desc.bEndpointAddress, + itr - (void *) ep->extra, hdr->bDescriptorType, + hdr->bLength, itr_size); + break; + } + itr += hdr->bLength; + itr_size -= hdr->bDescriptorType; + } +out: + d_fnend(3, dev, "(ep %p) = %p\n", ep, epcd); + return epcd; +} + +/* + * Aim an rpipe to its device & endpoint destination + * + * Make sure we change the address to unauthenticathed if the device + * is WUSB and it is not authenticated. + */ +static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa, + struct usb_host_endpoint *ep, struct urb *urb, gfp_t gfp) +{ + int result = -ENOMSG; /* better code for lack of companion? */ + struct device *dev = &wa->usb_iface->dev; + struct usb_device *usb_dev = urb->dev; + struct usb_wireless_ep_comp_descriptor *epcd; + u8 unauth; + + d_fnstart(3, dev, "(rpipe %p wa %p ep %p, urb %p)\n", + rpipe, wa, ep, urb); + epcd = rpipe_epc_find(dev, ep); + if (epcd == NULL) { + dev_err(dev, "ep 0x%02x: can't find companion descriptor\n", + ep->desc.bEndpointAddress); + goto error; + } + unauth = usb_dev->wusb && !usb_dev->authenticated ? 0x80 : 0; + __rpipe_reset(wa, le16_to_cpu(rpipe->descr.wRPipeIndex)); + atomic_set(&rpipe->segs_available, le16_to_cpu(rpipe->descr.wRequests)); + /* FIXME: block allocation system; request with queuing and timeout */ + /* FIXME: compute so seg_size > ep->maxpktsize */ + rpipe->descr.wBlocks = cpu_to_le16(16); /* given */ + /* ep0 maxpktsize is 0x200 (WUSB1.0[4.8.1]) */ + rpipe->descr.wMaxPacketSize = cpu_to_le16(ep->desc.wMaxPacketSize); + rpipe->descr.bHSHubAddress = 0; /* reserved: zero */ + rpipe->descr.bHSHubPort = wusb_port_no_to_idx(urb->dev->portnum); + /* FIXME: use maximum speed as supported or recommended by device */ + rpipe->descr.bSpeed = usb_pipeendpoint(urb->pipe) == 0 ? + UWB_PHY_RATE_53 : UWB_PHY_RATE_200; + d_printf(2, dev, "addr %u (0x%02x) rpipe #%u ep# %u speed %d\n", + urb->dev->devnum, urb->dev->devnum | unauth, + le16_to_cpu(rpipe->descr.wRPipeIndex), + usb_pipeendpoint(urb->pipe), rpipe->descr.bSpeed); + /* see security.c:wusb_update_address() */ + if (unlikely(urb->dev->devnum == 0x80)) + rpipe->descr.bDeviceAddress = 0; + else + rpipe->descr.bDeviceAddress = urb->dev->devnum | unauth; + rpipe->descr.bEndpointAddress = ep->desc.bEndpointAddress; + /* FIXME: bDataSequence */ + rpipe->descr.bDataSequence = 0; + /* FIXME: dwCurrentWindow */ + rpipe->descr.dwCurrentWindow = cpu_to_le32(1); + /* FIXME: bMaxDataSequence */ + rpipe->descr.bMaxDataSequence = epcd->bMaxSequence - 1; + rpipe->descr.bInterval = ep->desc.bInterval; + /* FIXME: bOverTheAirInterval */ + rpipe->descr.bOverTheAirInterval = 0; /* 0 if not isoc */ + /* FIXME: xmit power & preamble blah blah */ + rpipe->descr.bmAttribute = ep->desc.bmAttributes & 0x03; + /* rpipe->descr.bmCharacteristics RO */ + /* FIXME: bmRetryOptions */ + rpipe->descr.bmRetryOptions = 15; + /* FIXME: use for assessing link quality? */ + rpipe->descr.wNumTransactionErrors = 0; + result = __rpipe_set_descr(wa, &rpipe->descr, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + if (result < 0) { + dev_err(dev, "Cannot aim rpipe: %d\n", result); + goto error; + } + result = 0; +error: + d_fnend(3, dev, "(rpipe %p wa %p ep %p urb %p) = %d\n", + rpipe, wa, ep, urb, result); + return result; +} + +/* + * Check an aimed rpipe to make sure it points to where we want + * + * We use bit 19 of the Linux USB pipe bitmap for unauth vs auth + * space; when it is like that, we or 0x80 to make an unauth address. + */ +static int rpipe_check_aim(const struct wa_rpipe *rpipe, const struct wahc *wa, + const struct usb_host_endpoint *ep, + const struct urb *urb, gfp_t gfp) +{ + int result = 0; /* better code for lack of companion? */ + struct device *dev = &wa->usb_iface->dev; + struct usb_device *usb_dev = urb->dev; + u8 unauth = (usb_dev->wusb && !usb_dev->authenticated) ? 0x80 : 0; + u8 portnum = wusb_port_no_to_idx(urb->dev->portnum); + + d_fnstart(3, dev, "(rpipe %p wa %p ep %p, urb %p)\n", + rpipe, wa, ep, urb); +#define AIM_CHECK(rdf, val, text) \ + do { \ + if (rpipe->descr.rdf != (val)) { \ + dev_err(dev, \ + "rpipe aim discrepancy: " #rdf " " text "\n", \ + rpipe->descr.rdf, (val)); \ + result = -EINVAL; \ + WARN_ON(1); \ + } \ + } while (0) + AIM_CHECK(wMaxPacketSize, cpu_to_le16(ep->desc.wMaxPacketSize), + "(%u vs %u)"); + AIM_CHECK(bHSHubPort, portnum, "(%u vs %u)"); + AIM_CHECK(bSpeed, usb_pipeendpoint(urb->pipe) == 0 ? + UWB_PHY_RATE_53 : UWB_PHY_RATE_200, + "(%u vs %u)"); + AIM_CHECK(bDeviceAddress, urb->dev->devnum | unauth, "(%u vs %u)"); + AIM_CHECK(bEndpointAddress, ep->desc.bEndpointAddress, "(%u vs %u)"); + AIM_CHECK(bInterval, ep->desc.bInterval, "(%u vs %u)"); + AIM_CHECK(bmAttribute, ep->desc.bmAttributes & 0x03, "(%u vs %u)"); +#undef AIM_CHECK + return result; +} + +#ifndef CONFIG_BUG +#define CONFIG_BUG 0 +#endif + +/* + * Make sure there is an rpipe allocated for an endpoint + * + * If already allocated, we just refcount it; if not, we get an + * idle one, aim it to the right location and take it. + * + * Attaches to ep->hcpriv and rpipe->ep to ep. + */ +int rpipe_get_by_ep(struct wahc *wa, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t gfp) +{ + int result = 0; + struct device *dev = &wa->usb_iface->dev; + struct wa_rpipe *rpipe; + u8 eptype; + + d_fnstart(3, dev, "(wa %p ep %p urb %p gfp 0x%08x)\n", wa, ep, urb, + gfp); + mutex_lock(&wa->rpipe_mutex); + rpipe = ep->hcpriv; + if (rpipe != NULL) { + if (CONFIG_BUG == 1) { + result = rpipe_check_aim(rpipe, wa, ep, urb, gfp); + if (result < 0) + goto error; + } + __rpipe_get(rpipe); + d_printf(2, dev, "ep 0x%02x: reusing rpipe %u\n", + ep->desc.bEndpointAddress, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + } else { + /* hmm, assign idle rpipe, aim it */ + result = -ENOBUFS; + eptype = ep->desc.bmAttributes & 0x03; + result = rpipe_get_idle(&rpipe, wa, 1 << eptype, gfp); + if (result < 0) + goto error; + result = rpipe_aim(rpipe, wa, ep, urb, gfp); + if (result < 0) { + rpipe_put(rpipe); + goto error; + } + ep->hcpriv = rpipe; + rpipe->ep = ep; + __rpipe_get(rpipe); /* for caching into ep->hcpriv */ + d_printf(2, dev, "ep 0x%02x: using rpipe %u\n", + ep->desc.bEndpointAddress, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + } + d_dump(4, dev, &rpipe->descr, sizeof(rpipe->descr)); +error: + mutex_unlock(&wa->rpipe_mutex); + d_fnend(3, dev, "(wa %p ep %p urb %p gfp 0x%08x)\n", wa, ep, urb, gfp); + return result; +} + +/* + * Allocate the bitmap for each rpipe. + */ +int wa_rpipes_create(struct wahc *wa) +{ + wa->rpipes = wa->wa_descr->wNumRPipes; + wa->rpipe_bm = kzalloc(BITS_TO_LONGS(wa->rpipes)*sizeof(unsigned long), + GFP_KERNEL); + if (wa->rpipe_bm == NULL) + return -ENOMEM; + return 0; +} + +void wa_rpipes_destroy(struct wahc *wa) +{ + struct device *dev = &wa->usb_iface->dev; + d_fnstart(3, dev, "(wa %p)\n", wa); + if (!bitmap_empty(wa->rpipe_bm, wa->rpipes)) { + char buf[256]; + WARN_ON(1); + bitmap_scnprintf(buf, sizeof(buf), wa->rpipe_bm, wa->rpipes); + dev_err(dev, "BUG: pipes not released on exit: %s\n", buf); + } + kfree(wa->rpipe_bm); + d_fnend(3, dev, "(wa %p)\n", wa); +} + +/* + * Release resources allocated for an endpoint + * + * If there is an associated rpipe to this endpoint, Abort any pending + * transfers and put it. If the rpipe ends up being destroyed, + * __rpipe_destroy() will cleanup ep->hcpriv. + * + * This is called before calling hcd->stop(), so you don't need to do + * anything else in there. + */ +void rpipe_ep_disable(struct wahc *wa, struct usb_host_endpoint *ep) +{ + struct device *dev = &wa->usb_iface->dev; + struct wa_rpipe *rpipe; + d_fnstart(2, dev, "(wa %p ep %p)\n", wa, ep); + mutex_lock(&wa->rpipe_mutex); + rpipe = ep->hcpriv; + if (rpipe != NULL) { + unsigned rc = atomic_read(&rpipe->refcnt.refcount); + int result; + u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex); + + if (rc != 1) + d_printf(1, dev, "(wa %p ep %p) rpipe %p refcnt %u\n", + wa, ep, rpipe, rc); + + d_printf(1, dev, "rpipe %u: abort\n", index); + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_RPIPE_ABORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0 && result != -ENODEV /* dev is gone */) + d_printf(1, dev, "(wa %p rpipe %u): abort failed: %d\n", + wa, index, result); + rpipe_put(rpipe); + } + mutex_unlock(&wa->rpipe_mutex); + d_fnend(2, dev, "(wa %p ep %p)\n", wa, ep); + return; +} +EXPORT_SYMBOL_GPL(rpipe_ep_disable); diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c new file mode 100644 index 00000000000..c038635d1c6 --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * 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 <linux/init.h> +#include <linux/spinlock.h> +#include <linux/hash.h> +#include "wa-hc.h" +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 0 /* 0 disabled, > 0 different levels... */ +#include <linux/uwb/debug.h> + +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 = kcalloc(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; +} diff --git a/drivers/usb/wusbcore/wusbhc.c b/drivers/usb/wusbcore/wusbhc.c new file mode 100644 index 00000000000..07c63a31c79 --- /dev/null +++ b/drivers/usb/wusbcore/wusbhc.c @@ -0,0 +1,418 @@ +/* + * Wireless USB Host Controller + * sysfs glue, wusbcore module support and life cycle management + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Creation/destruction of wusbhc is split in two parts; that that + * doesn't require the HCD to be added (wusbhc_{create,destroy}) and + * the one that requires (phase B, wusbhc_b_{create,destroy}). + * + * This is so because usb_add_hcd() will start the HC, and thus, all + * the HC specific stuff has to be already initialiazed (like sysfs + * thingies). + */ +#include <linux/device.h> +#include <linux/module.h> +#include "wusbhc.h" + +/** + * Extract the wusbhc that corresponds to a USB Host Controller class device + * + * WARNING! Apply only if @dev is that of a + * wusbhc.usb_hcd.self->class_dev; otherwise, you loose. + */ +static struct wusbhc *usbhc_dev_to_wusbhc(struct device *dev) +{ + struct usb_bus *usb_bus = dev_get_drvdata(dev); + struct usb_hcd *usb_hcd = bus_to_hcd(usb_bus); + return usb_hcd_to_wusbhc(usb_hcd); +} + +/* + * Show & store the current WUSB trust timeout + * + * We don't do locking--it is an 'atomic' value. + * + * The units that we store/show are always MILLISECONDS. However, the + * value of trust_timeout is jiffies. + */ +static ssize_t wusb_trust_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", wusbhc->trust_timeout); +} + +static ssize_t wusb_trust_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + ssize_t result = -ENOSYS; + unsigned trust_timeout; + + result = sscanf(buf, "%u", &trust_timeout); + if (result != 1) { + result = -EINVAL; + goto out; + } + /* FIXME: maybe we should check for range validity? */ + wusbhc->trust_timeout = trust_timeout; + cancel_delayed_work(&wusbhc->keep_alive_timer); + flush_workqueue(wusbd); + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (trust_timeout * CONFIG_HZ)/1000/2); +out: + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_trust_timeout, 0644, wusb_trust_timeout_show, + wusb_trust_timeout_store); + +/* + * Show & store the current WUSB CHID + */ +static ssize_t wusb_chid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + ssize_t result = 0; + + if (wusbhc->wuie_host_info != NULL) + result += ckhdid_printf(buf, PAGE_SIZE, + &wusbhc->wuie_host_info->CHID); + return result; +} + +/* + * Store a new CHID + * + * This will (FIXME) trigger many changes. + * + * - Send an all zeros CHID and it will stop the controller + * - Send a non-zero CHID and it will start it + * (unless it was started, it will just change the CHID, + * diconnecting all devices first). + * + * So first we scan the MMC we are sent and then we act on it. We + * read it in the same format as we print it, an ASCII string of 16 + * hex bytes. + * + * See wusbhc_chid_set() for more info. + */ +static ssize_t wusb_chid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + struct wusb_ckhdid chid; + ssize_t result; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &chid.data[0] , &chid.data[1] , + &chid.data[2] , &chid.data[3] , + &chid.data[4] , &chid.data[5] , + &chid.data[6] , &chid.data[7] , + &chid.data[8] , &chid.data[9] , + &chid.data[10], &chid.data[11], + &chid.data[12], &chid.data[13], + &chid.data[14], &chid.data[15]); + if (result != 16) { + dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits): " + "%d\n", (int)result); + return -EINVAL; + } + result = wusbhc_chid_set(wusbhc, &chid); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_chid, 0644, wusb_chid_show, wusb_chid_store); + +/* Group all the WUSBHC attributes */ +static struct attribute *wusbhc_attrs[] = { + &dev_attr_wusb_trust_timeout.attr, + &dev_attr_wusb_chid.attr, + NULL, +}; + +static struct attribute_group wusbhc_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wusbhc_attrs, +}; + +/* + * Create a wusbhc instance + * + * NOTEs: + * + * - assumes *wusbhc has been zeroed and wusbhc->usb_hcd has been + * initialized but not added. + * + * - fill out ports_max, mmcies_max and mmcie_{add,rm} before calling. + * + * - fill out wusbhc->uwb_rc and refcount it before calling + * - fill out the wusbhc->sec_modes array + */ +int wusbhc_create(struct wusbhc *wusbhc) +{ + int result = 0; + + wusbhc->trust_timeout = WUSB_TRUST_TIMEOUT_MS; + mutex_init(&wusbhc->mutex); + result = wusbhc_mmcie_create(wusbhc); + if (result < 0) + goto error_mmcie_create; + result = wusbhc_devconnect_create(wusbhc); + if (result < 0) + goto error_devconnect_create; + result = wusbhc_rh_create(wusbhc); + if (result < 0) + goto error_rh_create; + result = wusbhc_sec_create(wusbhc); + if (result < 0) + goto error_sec_create; + return 0; + +error_sec_create: + wusbhc_rh_destroy(wusbhc); +error_rh_create: + wusbhc_devconnect_destroy(wusbhc); +error_devconnect_create: + wusbhc_mmcie_destroy(wusbhc); +error_mmcie_create: + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_create); + +static inline struct kobject *wusbhc_kobj(struct wusbhc *wusbhc) +{ + return &wusbhc->usb_hcd.self.controller->kobj; +} + +/* + * Phase B of a wusbhc instance creation + * + * Creates fields that depend on wusbhc->usb_hcd having been + * added. This is where we create the sysfs files in + * /sys/class/usb_host/usb_hostX/. + * + * NOTE: Assumes wusbhc->usb_hcd has been already added by the upper + * layer (hwahc or whci) + */ +int wusbhc_b_create(struct wusbhc *wusbhc) +{ + int result = 0; + struct device *dev = wusbhc->usb_hcd.self.controller; + + result = sysfs_create_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); + if (result < 0) { + dev_err(dev, "Cannot register WUSBHC attributes: %d\n", result); + goto error_create_attr_group; + } + + result = wusbhc_pal_register(wusbhc); + if (result < 0) + goto error_pal_register; + return 0; + +error_pal_register: + sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); +error_create_attr_group: + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_b_create); + +void wusbhc_b_destroy(struct wusbhc *wusbhc) +{ + wusbhc_pal_unregister(wusbhc); + sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); +} +EXPORT_SYMBOL_GPL(wusbhc_b_destroy); + +void wusbhc_destroy(struct wusbhc *wusbhc) +{ + wusbhc_sec_destroy(wusbhc); + wusbhc_rh_destroy(wusbhc); + wusbhc_devconnect_destroy(wusbhc); + wusbhc_mmcie_destroy(wusbhc); +} +EXPORT_SYMBOL_GPL(wusbhc_destroy); + +struct workqueue_struct *wusbd; +EXPORT_SYMBOL_GPL(wusbd); + +/* + * WUSB Cluster ID allocation map + * + * Each WUSB bus in a channel is identified with a Cluster Id in the + * unauth address pace (WUSB1.0[4.3]). We take the range 0xe0 to 0xff + * (that's space for 31 WUSB controllers, as 0xff can't be taken). We + * start taking from 0xff, 0xfe, 0xfd... (hence the += or -= 0xff). + * + * For each one we taken, we pin it in the bitap + */ +#define CLUSTER_IDS 32 +static DECLARE_BITMAP(wusb_cluster_id_table, CLUSTER_IDS); +static DEFINE_SPINLOCK(wusb_cluster_ids_lock); + +/* + * Get a WUSB Cluster ID + * + * Need to release with wusb_cluster_id_put() when done w/ it. + */ +/* FIXME: coordinate with the choose_addres() from the USB stack */ +/* we want to leave the top of the 128 range for cluster addresses and + * the bottom for device addresses (as we map them one on one with + * ports). */ +u8 wusb_cluster_id_get(void) +{ + u8 id; + spin_lock(&wusb_cluster_ids_lock); + id = find_first_zero_bit(wusb_cluster_id_table, CLUSTER_IDS); + if (id > CLUSTER_IDS) { + id = 0; + goto out; + } + set_bit(id, wusb_cluster_id_table); + id = (u8) 0xff - id; +out: + spin_unlock(&wusb_cluster_ids_lock); + return id; + +} +EXPORT_SYMBOL_GPL(wusb_cluster_id_get); + +/* + * Release a WUSB Cluster ID + * + * Obtained it with wusb_cluster_id_get() + */ +void wusb_cluster_id_put(u8 id) +{ + id = 0xff - id; + BUG_ON(id >= CLUSTER_IDS); + spin_lock(&wusb_cluster_ids_lock); + WARN_ON(!test_bit(id, wusb_cluster_id_table)); + clear_bit(id, wusb_cluster_id_table); + spin_unlock(&wusb_cluster_ids_lock); +} +EXPORT_SYMBOL_GPL(wusb_cluster_id_put); + +/** + * wusbhc_giveback_urb - return an URB to the USB core + * @wusbhc: the host controller the URB is from. + * @urb: the URB. + * @status: the URB's status. + * + * Return an URB to the USB core doing some additional WUSB specific + * processing. + * + * - After a successful transfer, update the trust timeout timestamp + * for the WUSB device. + * + * - [WUSB] sections 4.13 and 7.5.1 specifies the stop retrasmittion + * condition for the WCONNECTACK_IE is that the host has observed + * the associated device responding to a control transfer. + */ +void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, int status) +{ + struct wusb_dev *wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); + + if (status == 0) { + wusb_dev->entry_ts = jiffies; + + /* wusbhc_devconnect_acked() can't be called from from + atomic context so defer it to a work queue. */ + if (!list_empty(&wusb_dev->cack_node)) + queue_work(wusbd, &wusb_dev->devconnect_acked_work); + } + + usb_hcd_giveback_urb(&wusbhc->usb_hcd, urb, status); +} +EXPORT_SYMBOL_GPL(wusbhc_giveback_urb); + +/** + * wusbhc_reset_all - reset the HC hardware + * @wusbhc: the host controller to reset. + * + * Request a full hardware reset of the chip. This will also reset + * the radio controller and any other PALs. + */ +void wusbhc_reset_all(struct wusbhc *wusbhc) +{ + uwb_rc_reset_all(wusbhc->uwb_rc); +} +EXPORT_SYMBOL_GPL(wusbhc_reset_all); + +static struct notifier_block wusb_usb_notifier = { + .notifier_call = wusb_usb_ncb, + .priority = INT_MAX /* Need to be called first of all */ +}; + +static int __init wusbcore_init(void) +{ + int result; + result = wusb_crypto_init(); + if (result < 0) + goto error_crypto_init; + /* WQ is singlethread because we need to serialize notifications */ + wusbd = create_singlethread_workqueue("wusbd"); + if (wusbd == NULL) { + result = -ENOMEM; + printk(KERN_ERR "WUSB-core: Cannot create wusbd workqueue\n"); + goto error_wusbd_create; + } + usb_register_notify(&wusb_usb_notifier); + bitmap_zero(wusb_cluster_id_table, CLUSTER_IDS); + set_bit(0, wusb_cluster_id_table); /* reserve Cluster ID 0xff */ + return 0; + +error_wusbd_create: + wusb_crypto_exit(); +error_crypto_init: + return result; + +} +module_init(wusbcore_init); + +static void __exit wusbcore_exit(void) +{ + clear_bit(0, wusb_cluster_id_table); + if (!bitmap_empty(wusb_cluster_id_table, CLUSTER_IDS)) { + char buf[256]; + bitmap_scnprintf(buf, sizeof(buf), wusb_cluster_id_table, + CLUSTER_IDS); + printk(KERN_ERR "BUG: WUSB Cluster IDs not released " + "on exit: %s\n", buf); + WARN_ON(1); + } + usb_unregister_notify(&wusb_usb_notifier); + destroy_workqueue(wusbd); + wusb_crypto_exit(); +} +module_exit(wusbcore_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("Wireless USB core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/wusbhc.h b/drivers/usb/wusbcore/wusbhc.h new file mode 100644 index 00000000000..d0c132434f1 --- /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 <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver implements parts common to all Wireless USB Host + * Controllers (struct wusbhc, embedding a struct usb_hcd) and is used + * by: + * + * - hwahc: HWA, USB-dongle that implements a Wireless USB host + * controller, (Wireless USB 1.0 Host-Wire-Adapter specification). + * + * - whci: WHCI, a PCI card with a wireless host controller + * (Wireless Host Controller Interface 1.0 specification). + * + * Check out the Design-overview.txt file in the source documentation + * for other details on the implementation. + * + * Main blocks: + * + * rh Root Hub emulation (part of the HCD glue) + * + * devconnect Handle all the issues related to device connection, + * authentication, disconnection, timeout, reseting, + * keepalives, etc. + * + * mmc MMC IE broadcasting handling + * + * A host controller driver just initializes its stuff and as part of + * that, creates a 'struct wusbhc' instance that handles all the + * common WUSB mechanisms. Links in the function ops that are specific + * to it and then registers the host controller. Ready to run. + */ + +#ifndef __WUSBHC_H__ +#define __WUSBHC_H__ + +#include <linux/usb.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/kref.h> +#include <linux/workqueue.h> +/* FIXME: Yes, I know: BAD--it's not my fault the USB HC iface is not + * public */ +#include <linux/../../drivers/usb/core/hcd.h> +#include <linux/uwb.h> +#include <linux/usb/wusb.h> + + +/** + * Wireless USB device + * + * Describe a WUSB device connected to the cluster. This struct + * belongs to the 'struct wusb_port' it is attached to and it is + * responsible for putting and clearing the pointer to it. + * + * Note this "complements" the 'struct usb_device' that the usb_hcd + * keeps for each connected USB device. However, it extends some + * information that is not available (there is no hcpriv ptr in it!) + * *and* most importantly, it's life cycle is different. It is created + * as soon as we get a DN_Connect (connect request notification) from + * the device through the WUSB host controller; the USB stack doesn't + * create the device until we authenticate it. FIXME: this will + * change. + * + * @bos: This is allocated when the BOS descriptors are read from + * the device and freed upon the wusb_dev struct dying. + * @wusb_cap_descr: points into @bos, and has been verified to be size + * safe. + */ +struct wusb_dev { + struct kref refcnt; + struct wusbhc *wusbhc; + struct list_head cack_node; /* Connect-Ack list */ + u8 port_idx; + u8 addr; + u8 beacon_type:4; + struct usb_encryption_descriptor ccm1_etd; + struct wusb_ckhdid cdid; + unsigned long entry_ts; + struct usb_bos_descriptor *bos; + struct usb_wireless_cap_descriptor *wusb_cap_descr; + struct uwb_mas_bm availability; + struct work_struct devconnect_acked_work; + struct urb *set_gtk_urb; + struct usb_ctrlrequest *set_gtk_req; + struct usb_device *usb_dev; +}; + +#define WUSB_DEV_ADDR_UNAUTH 0x80 + +static inline void wusb_dev_init(struct wusb_dev *wusb_dev) +{ + kref_init(&wusb_dev->refcnt); + /* no need to init the cack_node */ +} + +extern void wusb_dev_destroy(struct kref *_wusb_dev); + +static inline struct wusb_dev *wusb_dev_get(struct wusb_dev *wusb_dev) +{ + kref_get(&wusb_dev->refcnt); + return wusb_dev; +} + +static inline void wusb_dev_put(struct wusb_dev *wusb_dev) +{ + kref_put(&wusb_dev->refcnt, wusb_dev_destroy); +} + +/** + * Wireless USB Host Controlller root hub "fake" ports + * (state and device information) + * + * Wireless USB is wireless, so there are no ports; but we + * fake'em. Each RC can connect a max of devices at the same time + * (given in the Wireless Adapter descriptor, bNumPorts or WHCI's + * caps), referred to in wusbhc->ports_max. + * + * See rh.c for more information. + * + * The @status and @change use the same bits as in USB2.0[11.24.2.7], + * so we don't have to do much when getting the port's status. + * + * WUSB1.0[7.1], USB2.0[11.24.2.7.1,fig 11-10], + * include/linux/usb_ch9.h (#define USB_PORT_STAT_*) + */ +struct wusb_port { + u16 status; + u16 change; + struct wusb_dev *wusb_dev; /* connected device's info */ + unsigned reset_count; + u32 ptk_tkid; +}; + +/** + * WUSB Host Controller specifics + * + * All fields that are common to all Wireless USB controller types + * (HWA and WHCI) are grouped here. Host Controller + * functions/operations that only deal with general Wireless USB HC + * issues use this data type to refer to the host. + * + * @usb_hcd Instantiation of a USB host controller + * (initialized by upper layer [HWA=HC or WHCI]. + * + * @dev Device that implements this; initialized by the + * upper layer (HWA-HC, WHCI...); this device should + * have a refcount. + * + * @trust_timeout After this time without hearing for device + * activity, we consider the device gone and we have to + * re-authenticate. + * + * Can be accessed w/o locking--however, read to a + * local variable then use. + * + * @chid WUSB Cluster Host ID: this is supposed to be a + * unique value that doesn't change across reboots (so + * that your devices do not require re-association). + * + * Read/Write protected by @mutex + * + * @dev_info This array has ports_max elements. It is used to + * give the HC information about the WUSB devices (see + * 'struct wusb_dev_info'). + * + * For HWA we need to allocate it in heap; for WHCI it + * needs to be permanently mapped, so we keep it for + * both and make it easy. Call wusbhc->dev_info_set() + * to update an entry. + * + * @ports_max Number of simultaneous device connections (fake + * ports) this HC will take. Read-only. + * + * @port Array of port status for each fake root port. Guaranteed to + * always be the same lenght during device existence + * [this allows for some unlocked but referenced reading]. + * + * @mmcies_max Max number of Information Elements this HC can send + * in its MMC. Read-only. + * + * @mmcie_add HC specific operation (WHCI or HWA) for adding an + * MMCIE. + * + * @mmcie_rm HC specific operation (WHCI or HWA) for removing an + * MMCIE. + * + * @enc_types Array which describes the encryptions methods + * supported by the host as described in WUSB1.0 -- + * one entry per supported method. As of WUSB1.0 there + * is only four methods, we make space for eight just in + * case they decide to add some more (and pray they do + * it in sequential order). if 'enc_types[enc_method] + * != 0', then it is supported by the host. enc_method + * is USB_ENC_TYPE*. + * + * @set_ptk: Set the PTK and enable encryption for a device. Or, if + * the supplied key is NULL, disable encryption for that + * device. + * + * @set_gtk: Set the GTK to be used for all future broadcast packets + * (i.e., MMCs). With some hardware, setting the GTK may start + * MMC transmission. + * + * NOTE: + * + * - If wusb_dev->usb_dev is not NULL, then usb_dev is valid + * (wusb_dev has a refcount on it). Likewise, if usb_dev->wusb_dev + * is not NULL, usb_dev->wusb_dev is valid (usb_dev keeps a + * refcount on it). + * + * Most of the times when you need to use it, it will be non-NULL, + * so there is no real need to check for it (wusb_dev will + * dissapear before usb_dev). + * + * - The following fields need to be filled out before calling + * wusbhc_create(): ports_max, mmcies_max, mmcie_{add,rm}. + * + * - there is no wusbhc_init() method, we do everything in + * wusbhc_create(). + * + * - Creation is done in two phases, wusbhc_create() and + * wusbhc_create_b(); b are the parts that need to be called after + * calling usb_hcd_add(&wusbhc->usb_hcd). + */ +struct wusbhc { + struct usb_hcd usb_hcd; /* HAS TO BE 1st */ + struct device *dev; + struct uwb_rc *uwb_rc; + struct uwb_pal pal; + + unsigned trust_timeout; /* in jiffies */ + struct wuie_host_info *wuie_host_info; /* Includes CHID */ + + struct mutex mutex; /* locks everything else */ + u16 cluster_id; /* Wireless USB Cluster ID */ + struct wusb_port *port; /* Fake port status handling */ + struct wusb_dev_info *dev_info; /* for Set Device Info mgmt */ + u8 ports_max; + unsigned active:1; /* currently xmit'ing MMCs */ + struct wuie_keep_alive keep_alive_ie; /* protected by mutex */ + struct delayed_work keep_alive_timer; + struct list_head cack_list; /* Connect acknowledging */ + size_t cack_count; /* protected by 'mutex' */ + struct wuie_connect_ack cack_ie; + struct uwb_rsv *rsv; /* cluster bandwidth reservation */ + + struct mutex mmcie_mutex; /* MMC WUIE handling */ + struct wuie_hdr **mmcie; /* WUIE array */ + u8 mmcies_max; + /* FIXME: make wusbhc_ops? */ + int (*start)(struct wusbhc *wusbhc); + void (*stop)(struct wusbhc *wusbhc); + int (*mmcie_add)(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie); + int (*mmcie_rm)(struct wusbhc *wusbhc, u8 handle); + int (*dev_info_set)(struct wusbhc *, struct wusb_dev *wusb_dev); + int (*bwa_set)(struct wusbhc *wusbhc, s8 stream_index, + const struct uwb_mas_bm *); + int (*set_ptk)(struct wusbhc *wusbhc, u8 port_idx, + u32 tkid, const void *key, size_t key_size); + int (*set_gtk)(struct wusbhc *wusbhc, + u32 tkid, const void *key, size_t key_size); + int (*set_num_dnts)(struct wusbhc *wusbhc, u8 interval, u8 slots); + + struct { + struct usb_key_descriptor descr; + u8 data[16]; /* GTK key data */ + } __attribute__((packed)) gtk; + u8 gtk_index; + u32 gtk_tkid; + struct work_struct gtk_rekey_done_work; + int pending_set_gtks; + + struct usb_encryption_descriptor *ccm1_etd; +}; + +#define usb_hcd_to_wusbhc(u) container_of((u), struct wusbhc, usb_hcd) + + +extern int wusbhc_create(struct wusbhc *); +extern int wusbhc_b_create(struct wusbhc *); +extern void wusbhc_b_destroy(struct wusbhc *); +extern void wusbhc_destroy(struct wusbhc *); +extern int wusb_dev_sysfs_add(struct wusbhc *, struct usb_device *, + struct wusb_dev *); +extern void wusb_dev_sysfs_rm(struct wusb_dev *); +extern int wusbhc_sec_create(struct wusbhc *); +extern int wusbhc_sec_start(struct wusbhc *); +extern void wusbhc_sec_stop(struct wusbhc *); +extern void wusbhc_sec_destroy(struct wusbhc *); +extern void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, + int status); +void wusbhc_reset_all(struct wusbhc *wusbhc); + +int wusbhc_pal_register(struct wusbhc *wusbhc); +void wusbhc_pal_unregister(struct wusbhc *wusbhc); + +/* + * Return @usb_dev's @usb_hcd (properly referenced) or NULL if gone + * + * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr) + * + * This is a safe assumption as @usb_dev->bus is referenced all the + * time during the @usb_dev life cycle. + */ +static inline struct usb_hcd *usb_hcd_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct usb_hcd *usb_hcd; + usb_hcd = container_of(usb_dev->bus, struct usb_hcd, self); + return usb_get_hcd(usb_hcd); +} + +/* + * Increment the reference count on a wusbhc. + * + * @wusbhc's life cycle is identical to that of the underlying usb_hcd. + */ +static inline struct wusbhc *wusbhc_get(struct wusbhc *wusbhc) +{ + return usb_get_hcd(&wusbhc->usb_hcd) ? wusbhc : NULL; +} + +/* + * Return the wusbhc associated to a @usb_dev + * + * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr) + * + * @returns: wusbhc for @usb_dev; NULL if the @usb_dev is being torn down. + * WARNING: referenced at the usb_hcd level, unlocked + * + * FIXME: move offline + */ +static inline struct wusbhc *wusbhc_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct wusbhc *wusbhc = NULL; + struct usb_hcd *usb_hcd; + if (usb_dev->devnum > 1 && !usb_dev->wusb) { + /* but root hubs */ + dev_err(&usb_dev->dev, "devnum %d wusb %d\n", usb_dev->devnum, + usb_dev->wusb); + BUG_ON(usb_dev->devnum > 1 && !usb_dev->wusb); + } + usb_hcd = usb_hcd_get_by_usb_dev(usb_dev); + if (usb_hcd == NULL) + return NULL; + BUG_ON(usb_hcd->wireless == 0); + return wusbhc = usb_hcd_to_wusbhc(usb_hcd); +} + + +static inline void wusbhc_put(struct wusbhc *wusbhc) +{ + usb_put_hcd(&wusbhc->usb_hcd); +} + +int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid); +void wusbhc_stop(struct wusbhc *wusbhc); +extern int wusbhc_chid_set(struct wusbhc *, const struct wusb_ckhdid *); + +/* Device connect handling */ +extern int wusbhc_devconnect_create(struct wusbhc *); +extern void wusbhc_devconnect_destroy(struct wusbhc *); +extern int wusbhc_devconnect_start(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid); +extern void wusbhc_devconnect_stop(struct wusbhc *wusbhc); +extern int wusbhc_devconnect_auth(struct wusbhc *, u8); +extern void wusbhc_handle_dn(struct wusbhc *, u8 srcaddr, + struct wusb_dn_hdr *dn_hdr, size_t size); +extern int wusbhc_dev_reset(struct wusbhc *wusbhc, u8 port); +extern void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port); +extern int wusb_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv); +extern int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, + u8 addr); + +/* Wireless USB fake Root Hub methods */ +extern int wusbhc_rh_create(struct wusbhc *); +extern void wusbhc_rh_destroy(struct wusbhc *); + +extern int wusbhc_rh_status_data(struct usb_hcd *, char *); +extern int wusbhc_rh_control(struct usb_hcd *, u16, u16, u16, char *, u16); +extern int wusbhc_rh_suspend(struct usb_hcd *); +extern int wusbhc_rh_resume(struct usb_hcd *); +extern int wusbhc_rh_start_port_reset(struct usb_hcd *, unsigned); + +/* MMC handling */ +extern int wusbhc_mmcie_create(struct wusbhc *); +extern void wusbhc_mmcie_destroy(struct wusbhc *); +extern int wusbhc_mmcie_set(struct wusbhc *, u8 interval, u8 repeat_cnt, + struct wuie_hdr *); +extern void wusbhc_mmcie_rm(struct wusbhc *, struct wuie_hdr *); + +/* Bandwidth reservation */ +int wusbhc_rsv_establish(struct wusbhc *wusbhc); +void wusbhc_rsv_terminate(struct wusbhc *wusbhc); + +/* + * I've always said + * I wanted a wedding in a church... + * + * but lately I've been thinking about + * the Botanical Gardens. + * + * We could do it by the tulips. + * It'll be beautiful + * + * --Security! + */ +extern int wusb_dev_sec_add(struct wusbhc *, struct usb_device *, + struct wusb_dev *); +extern void wusb_dev_sec_rm(struct wusb_dev *) ; +extern int wusb_dev_4way_handshake(struct wusbhc *, struct wusb_dev *, + struct wusb_ckhdid *ck); +void wusbhc_gtk_rekey(struct wusbhc *wusbhc); + + +/* WUSB Cluster ID handling */ +extern u8 wusb_cluster_id_get(void); +extern void wusb_cluster_id_put(u8); + +/* + * wusb_port_by_idx - return the port associated to a zero-based port index + * + * NOTE: valid without locking as long as wusbhc is referenced (as the + * number of ports doesn't change). The data pointed to has to + * be verified though :) + */ +static inline struct wusb_port *wusb_port_by_idx(struct wusbhc *wusbhc, + u8 port_idx) +{ + return &wusbhc->port[port_idx]; +} + +/* + * wusb_port_no_to_idx - Convert port number (per usb_dev->portnum) to + * a port_idx. + * + * USB stack USB ports are 1 based!! + * + * NOTE: only valid for WUSB devices!!! + */ +static inline u8 wusb_port_no_to_idx(u8 port_no) +{ + return port_no - 1; +} + +extern struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *, + struct usb_device *); + +/* + * Return a referenced wusb_dev given a @usb_dev + * + * Returns NULL if the usb_dev is being torn down. + * + * FIXME: move offline + */ +static inline +struct wusb_dev *wusb_dev_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct wusbhc *wusbhc; + struct wusb_dev *wusb_dev; + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return NULL; + mutex_lock(&wusbhc->mutex); + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev); + mutex_unlock(&wusbhc->mutex); + wusbhc_put(wusbhc); + return wusb_dev; +} + +/* Misc */ + +extern struct workqueue_struct *wusbd; +#endif /* #ifndef __WUSBHC_H__ */ diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig new file mode 100644 index 00000000000..ca783127af3 --- /dev/null +++ b/drivers/uwb/Kconfig @@ -0,0 +1,90 @@ +# +# UWB device configuration +# + +menuconfig UWB + tristate "Ultra Wideband devices (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on PCI + default n + help + UWB is a high-bandwidth, low-power, point-to-point radio + technology using a wide spectrum (3.1-10.6GHz). It is + optimized for in-room use (480Mbps at 2 meters, 110Mbps at + 10m). It serves as the transport layer for other protocols, + such as Wireless USB (WUSB), IP (WLP) and upcoming + Bluetooth and 1394 + + The topology is peer to peer; however, higher level + protocols (such as WUSB) might impose a master/slave + relationship. + + Say Y here if your computer has UWB radio controllers (USB or PCI) + based. You will need to enable the radio controllers + below. It is ok to select all of them, no harm done. + + For more help check the UWB and WUSB related files in + <file:Documentation/usb/>. + + To compile the UWB stack as a module, choose M here. + +if UWB + +config UWB_HWA + tristate "UWB Radio Control driver for WUSB-compliant USB dongles (HWA)" + depends on USB + help + This driver enables the radio controller for HWA USB + devices. HWA stands for Host Wire Adapter, and it is a UWB + Radio Controller connected to your system via USB. Most of + them come with a Wireless USB host controller also. + + To compile this driver select Y (built in) or M (module). It + is safe to select any even if you do not have the hardware. + +config UWB_WHCI + tristate "UWB Radio Control driver for WHCI-compliant cards" + depends on PCI + help + This driver enables the radio controller for WHCI cards. + + WHCI is an specification developed by Intel + (http://www.intel.com/technology/comms/wusb/whci.htm) much + in the spirit of USB's EHCI, but for UWB and Wireless USB + radio/host controllers connected via memmory mapping (eg: + PCI). Most of these cards come also with a Wireless USB host + controller. + + To compile this driver select Y (built in) or M (module). It + is safe to select any even if you do not have the hardware. + +config UWB_WLP + tristate "Support WiMedia Link Protocol (Ethernet/IP over UWB)" + depends on UWB && NET + help + This is a common library for drivers that implement + networking over UWB. + +config UWB_I1480U + tristate "Support for Intel Wireless UWB Link 1480 HWA" + depends on UWB_HWA + select FW_LOADER + help + This driver enables support for the i1480 when connected via + USB. It consists of a firmware uploader that will enable it + to behave as an HWA device. + + To compile this driver select Y (built in) or M (module). It + is safe to select any even if you do not have the hardware. + +config UWB_I1480U_WLP + tristate "Support for Intel Wireless UWB Link 1480 HWA's WLP interface" + depends on UWB_I1480U && UWB_WLP && NET + help + This driver enables WLP support for the i1480 when connected via + USB. WLP is the WiMedia Link Protocol, or IP over UWB. + + To compile this driver select Y (built in) or M (module). It + is safe to select any even if you don't have the hardware. + +endif # UWB diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile new file mode 100644 index 00000000000..257e6908304 --- /dev/null +++ b/drivers/uwb/Makefile @@ -0,0 +1,29 @@ +obj-$(CONFIG_UWB) += uwb.o +obj-$(CONFIG_UWB_WLP) += wlp/ +obj-$(CONFIG_UWB_WHCI) += umc.o whci.o whc-rc.o +obj-$(CONFIG_UWB_HWA) += hwa-rc.o +obj-$(CONFIG_UWB_I1480U) += i1480/ + +uwb-objs := \ + address.o \ + beacon.o \ + driver.o \ + drp.o \ + drp-avail.o \ + drp-ie.o \ + est.o \ + ie.o \ + lc-dev.o \ + lc-rc.o \ + neh.o \ + pal.o \ + reset.o \ + rsv.o \ + scan.o \ + uwb-debug.o \ + uwbd.o + +umc-objs := \ + umc-bus.o \ + umc-dev.o \ + umc-drv.o diff --git a/drivers/uwb/address.c b/drivers/uwb/address.c new file mode 100644 index 00000000000..1664ae5f170 --- /dev/null +++ b/drivers/uwb/address.c @@ -0,0 +1,374 @@ +/* + * Ultra Wide Band + * Address management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/random.h> +#include <linux/etherdevice.h> +#include <linux/uwb/debug.h> +#include "uwb-internal.h" + + +/** Device Address Management command */ +struct uwb_rc_cmd_dev_addr_mgmt { + struct uwb_rccb rccb; + u8 bmOperationType; + u8 baAddr[6]; +} __attribute__((packed)); + + +/** + * Low level command for setting/getting UWB radio's addresses + * + * @hwarc: HWA Radio Control interface instance + * @bmOperationType: + * Set/get, MAC/DEV (see WUSB1.0[8.6.2.2]) + * @baAddr: address buffer--assumed to have enough data to hold + * the address type requested. + * @reply: Pointer to reply buffer (can be stack allocated) + * @returns: 0 if ok, < 0 errno code on error. + * + * @cmd has to be allocated because USB cannot grok USB or vmalloc + * buffers depending on your combination of host architecture. + */ +static +int uwb_rc_dev_addr_mgmt(struct uwb_rc *rc, + u8 bmOperationType, const u8 *baAddr, + struct uwb_rc_evt_dev_addr_mgmt *reply) +{ + int result; + struct uwb_rc_cmd_dev_addr_mgmt *cmd; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + cmd->rccb.bCommandType = UWB_RC_CET_GENERAL; + cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_DEV_ADDR_MGMT); + cmd->bmOperationType = bmOperationType; + if (baAddr) { + size_t size = 0; + switch (bmOperationType >> 1) { + case 0: size = 2; break; + case 1: size = 6; break; + default: BUG(); + } + memcpy(cmd->baAddr, baAddr, size); + } + reply->rceb.bEventType = UWB_RC_CET_GENERAL; + reply->rceb.wEvent = UWB_RC_CMD_DEV_ADDR_MGMT; + result = uwb_rc_cmd(rc, "DEV-ADDR-MGMT", + &cmd->rccb, sizeof(*cmd), + &reply->rceb, sizeof(*reply)); + if (result < 0) + goto error_cmd; + if (result < sizeof(*reply)) { + dev_err(&rc->uwb_dev.dev, + "DEV-ADDR-MGMT: not enough data replied: " + "%d vs %zu bytes needed\n", result, sizeof(*reply)); + result = -ENOMSG; + } else if (reply->bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "DEV-ADDR-MGMT: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply->bResultCode), + reply->bResultCode); + result = -EIO; + } else + result = 0; +error_cmd: + kfree(cmd); +error_kzalloc: + return result; +} + + +/** + * Set the UWB RC MAC or device address. + * + * @rc: UWB Radio Controller + * @_addr: Pointer to address to write [assumed to be either a + * 'struct uwb_mac_addr *' or a 'struct uwb_dev_addr *']. + * @type: Type of address to set (UWB_ADDR_DEV or UWB_ADDR_MAC). + * @returns: 0 if ok, < 0 errno code on error. + * + * Some anal retentivity here: even if both 'struct + * uwb_{dev,mac}_addr' have the actual byte array in the same offset + * and I could just pass _addr to hwarc_cmd_dev_addr_mgmt(), I prefer + * to use some syntatic sugar in case someday we decide to change the + * format of the structs. The compiler will optimize it out anyway. + */ +static int uwb_rc_addr_set(struct uwb_rc *rc, + const void *_addr, enum uwb_addr_type type) +{ + int result; + u8 bmOperationType = 0x1; /* Set address */ + const struct uwb_dev_addr *dev_addr = _addr; + const struct uwb_mac_addr *mac_addr = _addr; + struct uwb_rc_evt_dev_addr_mgmt reply; + const u8 *baAddr; + + result = -EINVAL; + switch (type) { + case UWB_ADDR_DEV: + baAddr = dev_addr->data; + break; + case UWB_ADDR_MAC: + baAddr = mac_addr->data; + bmOperationType |= 0x2; + break; + default: + return result; + } + return uwb_rc_dev_addr_mgmt(rc, bmOperationType, baAddr, &reply); +} + + +/** + * Get the UWB radio's MAC or device address. + * + * @rc: UWB Radio Controller + * @_addr: Where to write the address data [assumed to be either a + * 'struct uwb_mac_addr *' or a 'struct uwb_dev_addr *']. + * @type: Type of address to get (UWB_ADDR_DEV or UWB_ADDR_MAC). + * @returns: 0 if ok (and *_addr set), < 0 errno code on error. + * + * See comment in uwb_rc_addr_set() about anal retentivity in the + * type handling of the address variables. + */ +static int uwb_rc_addr_get(struct uwb_rc *rc, + void *_addr, enum uwb_addr_type type) +{ + int result; + u8 bmOperationType = 0x0; /* Get address */ + struct uwb_rc_evt_dev_addr_mgmt evt; + struct uwb_dev_addr *dev_addr = _addr; + struct uwb_mac_addr *mac_addr = _addr; + u8 *baAddr; + + result = -EINVAL; + switch (type) { + case UWB_ADDR_DEV: + baAddr = dev_addr->data; + break; + case UWB_ADDR_MAC: + bmOperationType |= 0x2; + baAddr = mac_addr->data; + break; + default: + return result; + } + result = uwb_rc_dev_addr_mgmt(rc, bmOperationType, baAddr, &evt); + if (result == 0) + switch (type) { + case UWB_ADDR_DEV: + memcpy(&dev_addr->data, evt.baAddr, + sizeof(dev_addr->data)); + break; + case UWB_ADDR_MAC: + memcpy(&mac_addr->data, evt.baAddr, + sizeof(mac_addr->data)); + break; + default: /* shut gcc up */ + BUG(); + } + return result; +} + + +/** Get @rc's MAC address to @addr */ +int uwb_rc_mac_addr_get(struct uwb_rc *rc, + struct uwb_mac_addr *addr) { + return uwb_rc_addr_get(rc, addr, UWB_ADDR_MAC); +} +EXPORT_SYMBOL_GPL(uwb_rc_mac_addr_get); + + +/** Get @rc's device address to @addr */ +int uwb_rc_dev_addr_get(struct uwb_rc *rc, + struct uwb_dev_addr *addr) { + return uwb_rc_addr_get(rc, addr, UWB_ADDR_DEV); +} +EXPORT_SYMBOL_GPL(uwb_rc_dev_addr_get); + + +/** Set @rc's address to @addr */ +int uwb_rc_mac_addr_set(struct uwb_rc *rc, + const struct uwb_mac_addr *addr) +{ + int result = -EINVAL; + mutex_lock(&rc->uwb_dev.mutex); + result = uwb_rc_addr_set(rc, addr, UWB_ADDR_MAC); + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + + +/** Set @rc's address to @addr */ +int uwb_rc_dev_addr_set(struct uwb_rc *rc, + const struct uwb_dev_addr *addr) +{ + int result = -EINVAL; + mutex_lock(&rc->uwb_dev.mutex); + result = uwb_rc_addr_set(rc, addr, UWB_ADDR_DEV); + rc->uwb_dev.dev_addr = *addr; + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +/* Returns !0 if given address is already assigned to device. */ +int __uwb_mac_addr_assigned_check(struct device *dev, void *_addr) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_mac_addr *addr = _addr; + + if (!uwb_mac_addr_cmp(addr, &uwb_dev->mac_addr)) + return !0; + return 0; +} + +/* Returns !0 if given address is already assigned to device. */ +int __uwb_dev_addr_assigned_check(struct device *dev, void *_addr) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_dev_addr *addr = _addr; + if (!uwb_dev_addr_cmp(addr, &uwb_dev->dev_addr)) + return !0; + return 0; +} + +/** + * uwb_dev_addr_assign - assigned a generated DevAddr to a radio controller + * @rc: the (local) radio controller device requiring a new DevAddr + * + * A new DevAddr is required when: + * - first setting up a radio controller + * - if the hardware reports a DevAddr conflict + * + * The DevAddr is randomly generated in the generated DevAddr range + * [0x100, 0xfeff]. The number of devices in a beacon group is limited + * by mMaxBPLength (96) so this address space will never be exhausted. + * + * [ECMA-368] 17.1.1, 17.16. + */ +int uwb_rc_dev_addr_assign(struct uwb_rc *rc) +{ + struct uwb_dev_addr new_addr; + + do { + get_random_bytes(new_addr.data, sizeof(new_addr.data)); + } while (new_addr.data[0] == 0x00 || new_addr.data[0] == 0xff + || __uwb_dev_addr_assigned(rc, &new_addr)); + + return uwb_rc_dev_addr_set(rc, &new_addr); +} + +/** + * uwbd_evt_handle_rc_dev_addr_conflict - handle a DEV_ADDR_CONFLICT event + * @evt: the DEV_ADDR_CONFLICT notification from the radio controller + * + * A new (non-conflicting) DevAddr is assigned to the radio controller. + * + * [ECMA-368] 17.1.1.1. + */ +int uwbd_evt_handle_rc_dev_addr_conflict(struct uwb_event *evt) +{ + struct uwb_rc *rc = evt->rc; + + return uwb_rc_dev_addr_assign(rc); +} + +/* + * Print the 48-bit EUI MAC address of the radio controller when + * reading /sys/class/uwb_rc/XX/mac_address + */ +static ssize_t uwb_rc_mac_addr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + struct uwb_mac_addr addr; + ssize_t result; + + mutex_lock(&rc->uwb_dev.mutex); + result = uwb_rc_addr_get(rc, &addr, UWB_ADDR_MAC); + mutex_unlock(&rc->uwb_dev.mutex); + if (result >= 0) { + result = uwb_mac_addr_print(buf, UWB_ADDR_STRSIZE, &addr); + buf[result++] = '\n'; + } + return result; +} + +/* + * Parse a 48 bit address written to /sys/class/uwb_rc/XX/mac_address + * and if correct, set it. + */ +static ssize_t uwb_rc_mac_addr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + struct uwb_mac_addr addr; + ssize_t result; + + result = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\n", + &addr.data[0], &addr.data[1], &addr.data[2], + &addr.data[3], &addr.data[4], &addr.data[5]); + if (result != 6) { + result = -EINVAL; + goto out; + } + if (is_multicast_ether_addr(addr.data)) { + dev_err(&rc->uwb_dev.dev, "refusing to set multicast " + "MAC address %s\n", buf); + result = -EINVAL; + goto out; + } + result = uwb_rc_mac_addr_set(rc, &addr); + if (result == 0) + rc->uwb_dev.mac_addr = addr; +out: + return result < 0 ? result : size; +} +DEVICE_ATTR(mac_address, S_IRUGO | S_IWUSR, uwb_rc_mac_addr_show, uwb_rc_mac_addr_store); + +/** Print @addr to @buf, @return bytes written */ +size_t __uwb_addr_print(char *buf, size_t buf_size, const unsigned char *addr, + int type) +{ + size_t result; + if (type) + result = scnprintf(buf, buf_size, + "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], + addr[3], addr[4], addr[5]); + else + result = scnprintf(buf, buf_size, "%02x:%02x", + addr[1], addr[0]); + return result; +} +EXPORT_SYMBOL_GPL(__uwb_addr_print); diff --git a/drivers/uwb/beacon.c b/drivers/uwb/beacon.c new file mode 100644 index 00000000000..46b18eec502 --- /dev/null +++ b/drivers/uwb/beacon.c @@ -0,0 +1,642 @@ +/* + * Ultra Wide Band + * Beacon management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kdev_t.h> +#include "uwb-internal.h" + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +/** Start Beaconing command structure */ +struct uwb_rc_cmd_start_beacon { + struct uwb_rccb rccb; + __le16 wBPSTOffset; + u8 bChannelNumber; +} __attribute__((packed)); + + +static int uwb_rc_start_beacon(struct uwb_rc *rc, u16 bpst_offset, u8 channel) +{ + int result; + struct uwb_rc_cmd_start_beacon *cmd; + struct uwb_rc_evt_confirm reply; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + cmd->rccb.bCommandType = UWB_RC_CET_GENERAL; + cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_START_BEACON); + cmd->wBPSTOffset = cpu_to_le16(bpst_offset); + cmd->bChannelNumber = channel; + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_START_BEACON; + result = uwb_rc_cmd(rc, "START-BEACON", &cmd->rccb, sizeof(*cmd), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "START-BEACON: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + } +error_cmd: + kfree(cmd); + return result; +} + +static int uwb_rc_stop_beacon(struct uwb_rc *rc) +{ + int result; + struct uwb_rccb *cmd; + struct uwb_rc_evt_confirm reply; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + cmd->bCommandType = UWB_RC_CET_GENERAL; + cmd->wCommand = cpu_to_le16(UWB_RC_CMD_STOP_BEACON); + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_STOP_BEACON; + result = uwb_rc_cmd(rc, "STOP-BEACON", cmd, sizeof(*cmd), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "STOP-BEACON: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + } +error_cmd: + kfree(cmd); + return result; +} + +/* + * Start/stop beacons + * + * @rc: UWB Radio Controller to operate on + * @channel: UWB channel on which to beacon (WUSB[table + * 5-12]). If -1, stop beaconing. + * @bpst_offset: Beacon Period Start Time offset; FIXME-do zero + * + * According to WHCI 0.95 [4.13.6] the driver will only receive the RCEB + * of a SET IE command after the device sent the first beacon that includes + * the IEs specified in the SET IE command. So, after we start beaconing we + * check if there is anything in the IE cache and call the SET IE command + * if needed. + */ +int uwb_rc_beacon(struct uwb_rc *rc, int channel, unsigned bpst_offset) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + + mutex_lock(&rc->uwb_dev.mutex); + if (channel < 0) + channel = -1; + if (channel == -1) + result = uwb_rc_stop_beacon(rc); + else { + /* channel >= 0...dah */ + result = uwb_rc_start_beacon(rc, bpst_offset, channel); + if (result < 0) + goto out_up; + if (le16_to_cpu(rc->ies->wIELength) > 0) { + result = uwb_rc_set_ie(rc, rc->ies); + if (result < 0) { + dev_err(dev, "Cannot set new IE on device: " + "%d\n", result); + result = uwb_rc_stop_beacon(rc); + channel = -1; + bpst_offset = 0; + } else + result = 0; + } + } + + if (result < 0) + goto out_up; + rc->beaconing = channel; + + uwb_notify(rc, NULL, uwb_bg_joined(rc) ? UWB_NOTIF_BG_JOIN : UWB_NOTIF_BG_LEAVE); + +out_up: + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +/* + * Beacon cache + * + * The purpose of this is to speed up the lookup of becon information + * when a new beacon arrives. The UWB Daemon uses it also to keep a + * tab of which devices are in radio distance and which not. When a + * device's beacon stays present for more than a certain amount of + * time, it is considered a new, usable device. When a beacon ceases + * to be received for a certain amount of time, it is considered that + * the device is gone. + * + * FIXME: use an allocator for the entries + * FIXME: use something faster for search than a list + */ + +struct uwb_beca uwb_beca = { + .list = LIST_HEAD_INIT(uwb_beca.list), + .mutex = __MUTEX_INITIALIZER(uwb_beca.mutex) +}; + + +void uwb_bce_kfree(struct kref *_bce) +{ + struct uwb_beca_e *bce = container_of(_bce, struct uwb_beca_e, refcnt); + + kfree(bce->be); + kfree(bce); +} + + +/* Find a beacon by dev addr in the cache */ +static +struct uwb_beca_e *__uwb_beca_find_bydev(const struct uwb_dev_addr *dev_addr) +{ + struct uwb_beca_e *bce, *next; + list_for_each_entry_safe(bce, next, &uwb_beca.list, node) { + d_printf(6, NULL, "looking for addr %02x:%02x in %02x:%02x\n", + dev_addr->data[0], dev_addr->data[1], + bce->dev_addr.data[0], bce->dev_addr.data[1]); + if (!memcmp(&bce->dev_addr, dev_addr, sizeof(bce->dev_addr))) + goto out; + } + bce = NULL; +out: + return bce; +} + +/* Find a beacon by dev addr in the cache */ +static +struct uwb_beca_e *__uwb_beca_find_bymac(const struct uwb_mac_addr *mac_addr) +{ + struct uwb_beca_e *bce, *next; + list_for_each_entry_safe(bce, next, &uwb_beca.list, node) { + if (!memcmp(bce->mac_addr, mac_addr->data, + sizeof(struct uwb_mac_addr))) + goto out; + } + bce = NULL; +out: + return bce; +} + +/** + * uwb_dev_get_by_devaddr - get a UWB device with a specific DevAddr + * @rc: the radio controller that saw the device + * @devaddr: DevAddr of the UWB device to find + * + * There may be more than one matching device (in the case of a + * DevAddr conflict), but only the first one is returned. + */ +struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc, + const struct uwb_dev_addr *devaddr) +{ + struct uwb_dev *found = NULL; + struct uwb_beca_e *bce; + + mutex_lock(&uwb_beca.mutex); + bce = __uwb_beca_find_bydev(devaddr); + if (bce) + found = uwb_dev_try_get(rc, bce->uwb_dev); + mutex_unlock(&uwb_beca.mutex); + + return found; +} + +/** + * uwb_dev_get_by_macaddr - get a UWB device with a specific EUI-48 + * @rc: the radio controller that saw the device + * @devaddr: EUI-48 of the UWB device to find + */ +struct uwb_dev *uwb_dev_get_by_macaddr(struct uwb_rc *rc, + const struct uwb_mac_addr *macaddr) +{ + struct uwb_dev *found = NULL; + struct uwb_beca_e *bce; + + mutex_lock(&uwb_beca.mutex); + bce = __uwb_beca_find_bymac(macaddr); + if (bce) + found = uwb_dev_try_get(rc, bce->uwb_dev); + mutex_unlock(&uwb_beca.mutex); + + return found; +} + +/* Initialize a beacon cache entry */ +static void uwb_beca_e_init(struct uwb_beca_e *bce) +{ + mutex_init(&bce->mutex); + kref_init(&bce->refcnt); + stats_init(&bce->lqe_stats); + stats_init(&bce->rssi_stats); +} + +/* + * Add a beacon to the cache + * + * @be: Beacon event information + * @bf: Beacon frame (part of b, really) + * @ts_jiffies: Timestamp (in jiffies) when the beacon was received + */ +struct uwb_beca_e *__uwb_beca_add(struct uwb_rc_evt_beacon *be, + struct uwb_beacon_frame *bf, + unsigned long ts_jiffies) +{ + struct uwb_beca_e *bce; + + bce = kzalloc(sizeof(*bce), GFP_KERNEL); + if (bce == NULL) + return NULL; + uwb_beca_e_init(bce); + bce->ts_jiffies = ts_jiffies; + bce->uwb_dev = NULL; + list_add(&bce->node, &uwb_beca.list); + return bce; +} + +/* + * Wipe out beacon entries that became stale + * + * Remove associated devicest too. + */ +void uwb_beca_purge(void) +{ + struct uwb_beca_e *bce, *next; + unsigned long expires; + + mutex_lock(&uwb_beca.mutex); + list_for_each_entry_safe(bce, next, &uwb_beca.list, node) { + expires = bce->ts_jiffies + msecs_to_jiffies(beacon_timeout_ms); + if (time_after(jiffies, expires)) { + uwbd_dev_offair(bce); + list_del(&bce->node); + uwb_bce_put(bce); + } + } + mutex_unlock(&uwb_beca.mutex); +} + +/* Clean up the whole beacon cache. Called on shutdown */ +void uwb_beca_release(void) +{ + struct uwb_beca_e *bce, *next; + mutex_lock(&uwb_beca.mutex); + list_for_each_entry_safe(bce, next, &uwb_beca.list, node) { + list_del(&bce->node); + uwb_bce_put(bce); + } + mutex_unlock(&uwb_beca.mutex); +} + +static void uwb_beacon_print(struct uwb_rc *rc, struct uwb_rc_evt_beacon *be, + struct uwb_beacon_frame *bf) +{ + char macbuf[UWB_ADDR_STRSIZE]; + char devbuf[UWB_ADDR_STRSIZE]; + char dstbuf[UWB_ADDR_STRSIZE]; + + uwb_mac_addr_print(macbuf, sizeof(macbuf), &bf->Device_Identifier); + uwb_dev_addr_print(devbuf, sizeof(devbuf), &bf->hdr.SrcAddr); + uwb_dev_addr_print(dstbuf, sizeof(dstbuf), &bf->hdr.DestAddr); + dev_info(&rc->uwb_dev.dev, + "BEACON from %s to %s (ch%u offset %u slot %u MAC %s)\n", + devbuf, dstbuf, be->bChannelNumber, be->wBPSTOffset, + bf->Beacon_Slot_Number, macbuf); +} + +/* + * @bce: beacon cache entry, referenced + */ +ssize_t uwb_bce_print_IEs(struct uwb_dev *uwb_dev, struct uwb_beca_e *bce, + char *buf, size_t size) +{ + ssize_t result = 0; + struct uwb_rc_evt_beacon *be; + struct uwb_beacon_frame *bf; + struct uwb_buf_ctx ctx = { + .buf = buf, + .bytes = 0, + .size = size + }; + + mutex_lock(&bce->mutex); + be = bce->be; + if (be == NULL) + goto out; + bf = (void *) be->BeaconInfo; + uwb_ie_for_each(uwb_dev, uwb_ie_dump_hex, &ctx, + bf->IEData, be->wBeaconInfoLength - sizeof(*bf)); + result = ctx.bytes; +out: + mutex_unlock(&bce->mutex); + return result; +} + +/* + * Verify that the beacon event, frame and IEs are ok + */ +static int uwb_verify_beacon(struct uwb_rc *rc, struct uwb_event *evt, + struct uwb_rc_evt_beacon *be) +{ + int result = -EINVAL; + struct uwb_beacon_frame *bf; + struct device *dev = &rc->uwb_dev.dev; + + /* Is there enough data to decode a beacon frame? */ + if (evt->notif.size < sizeof(*be) + sizeof(*bf)) { + dev_err(dev, "BEACON event: Not enough data to decode " + "(%zu vs %zu bytes needed)\n", evt->notif.size, + sizeof(*be) + sizeof(*bf)); + goto error; + } + /* FIXME: make sure beacon frame IEs are fine and that the whole thing + * is consistent */ + result = 0; +error: + return result; +} + +/* + * Handle UWB_RC_EVT_BEACON events + * + * We check the beacon cache to see how the received beacon fares. If + * is there already we refresh the timestamp. If not we create a new + * entry. + * + * According to the WHCI and WUSB specs, only one beacon frame is + * allowed per notification block, so we don't bother about scanning + * for more. + */ +int uwbd_evt_handle_rc_beacon(struct uwb_event *evt) +{ + int result = -EINVAL; + struct uwb_rc *rc; + struct uwb_rc_evt_beacon *be; + struct uwb_beacon_frame *bf; + struct uwb_beca_e *bce; + unsigned long last_ts; + + rc = evt->rc; + be = container_of(evt->notif.rceb, struct uwb_rc_evt_beacon, rceb); + result = uwb_verify_beacon(rc, evt, be); + if (result < 0) + return result; + + /* FIXME: handle alien beacons. */ + if (be->bBeaconType == UWB_RC_BEACON_TYPE_OL_ALIEN || + be->bBeaconType == UWB_RC_BEACON_TYPE_NOL_ALIEN) { + return -ENOSYS; + } + + bf = (struct uwb_beacon_frame *) be->BeaconInfo; + + /* + * Drop beacons from devices with a NULL EUI-48 -- they cannot + * be uniquely identified. + * + * It's expected that these will all be WUSB devices and they + * have a WUSB specific connection method so ignoring them + * here shouldn't be a problem. + */ + if (uwb_mac_addr_bcast(&bf->Device_Identifier)) + return 0; + + mutex_lock(&uwb_beca.mutex); + bce = __uwb_beca_find_bymac(&bf->Device_Identifier); + if (bce == NULL) { + /* Not in there, a new device is pinging */ + uwb_beacon_print(evt->rc, be, bf); + bce = __uwb_beca_add(be, bf, evt->ts_jiffies); + if (bce == NULL) { + mutex_unlock(&uwb_beca.mutex); + return -ENOMEM; + } + } + mutex_unlock(&uwb_beca.mutex); + + mutex_lock(&bce->mutex); + /* purge old beacon data */ + kfree(bce->be); + + last_ts = bce->ts_jiffies; + + /* Update commonly used fields */ + bce->ts_jiffies = evt->ts_jiffies; + bce->be = be; + bce->dev_addr = bf->hdr.SrcAddr; + bce->mac_addr = &bf->Device_Identifier; + be->wBPSTOffset = le16_to_cpu(be->wBPSTOffset); + be->wBeaconInfoLength = le16_to_cpu(be->wBeaconInfoLength); + stats_add_sample(&bce->lqe_stats, be->bLQI - 7); + stats_add_sample(&bce->rssi_stats, be->bRSSI + 18); + + /* + * This might be a beacon from a new device. + */ + if (bce->uwb_dev == NULL) + uwbd_dev_onair(evt->rc, bce); + + mutex_unlock(&bce->mutex); + + return 1; /* we keep the event data */ +} + +/* + * Handle UWB_RC_EVT_BEACON_SIZE events + * + * XXXXX + */ +int uwbd_evt_handle_rc_beacon_size(struct uwb_event *evt) +{ + int result = -EINVAL; + struct device *dev = &evt->rc->uwb_dev.dev; + struct uwb_rc_evt_beacon_size *bs; + + /* Is there enough data to decode the event? */ + if (evt->notif.size < sizeof(*bs)) { + dev_err(dev, "BEACON SIZE notification: Not enough data to " + "decode (%zu vs %zu bytes needed)\n", + evt->notif.size, sizeof(*bs)); + goto error; + } + bs = container_of(evt->notif.rceb, struct uwb_rc_evt_beacon_size, rceb); + if (0) + dev_info(dev, "Beacon size changed to %u bytes " + "(FIXME: action?)\n", le16_to_cpu(bs->wNewBeaconSize)); + else { + /* temporary hack until we do something with this message... */ + static unsigned count; + if (++count % 1000 == 0) + dev_info(dev, "Beacon size changed %u times " + "(FIXME: action?)\n", count); + } + result = 0; +error: + return result; +} + +/** + * uwbd_evt_handle_rc_bp_slot_change - handle a BP_SLOT_CHANGE event + * @evt: the BP_SLOT_CHANGE notification from the radio controller + * + * If the event indicates that no beacon period slots were available + * then radio controller has transitioned to a non-beaconing state. + * Otherwise, simply save the current beacon slot. + */ +int uwbd_evt_handle_rc_bp_slot_change(struct uwb_event *evt) +{ + struct uwb_rc *rc = evt->rc; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_evt_bp_slot_change *bpsc; + + if (evt->notif.size < sizeof(*bpsc)) { + dev_err(dev, "BP SLOT CHANGE event: Not enough data\n"); + return -EINVAL; + } + bpsc = container_of(evt->notif.rceb, struct uwb_rc_evt_bp_slot_change, rceb); + + mutex_lock(&rc->uwb_dev.mutex); + if (uwb_rc_evt_bp_slot_change_no_slot(bpsc)) { + dev_info(dev, "stopped beaconing: No free slots in BP\n"); + rc->beaconing = -1; + } else + rc->uwb_dev.beacon_slot = uwb_rc_evt_bp_slot_change_slot_num(bpsc); + mutex_unlock(&rc->uwb_dev.mutex); + + return 0; +} + +/** + * Handle UWB_RC_EVT_BPOIE_CHANGE events + * + * XXXXX + */ +struct uwb_ie_bpo { + struct uwb_ie_hdr hdr; + u8 bp_length; + u8 data[]; +} __attribute__((packed)); + +int uwbd_evt_handle_rc_bpoie_change(struct uwb_event *evt) +{ + int result = -EINVAL; + struct device *dev = &evt->rc->uwb_dev.dev; + struct uwb_rc_evt_bpoie_change *bpoiec; + struct uwb_ie_bpo *bpoie; + static unsigned count; /* FIXME: this is a temp hack */ + size_t iesize; + + /* Is there enough data to decode it? */ + if (evt->notif.size < sizeof(*bpoiec)) { + dev_err(dev, "BPOIEC notification: Not enough data to " + "decode (%zu vs %zu bytes needed)\n", + evt->notif.size, sizeof(*bpoiec)); + goto error; + } + bpoiec = container_of(evt->notif.rceb, struct uwb_rc_evt_bpoie_change, rceb); + iesize = le16_to_cpu(bpoiec->wBPOIELength); + if (iesize < sizeof(*bpoie)) { + dev_err(dev, "BPOIEC notification: Not enough IE data to " + "decode (%zu vs %zu bytes needed)\n", + iesize, sizeof(*bpoie)); + goto error; + } + if (++count % 1000 == 0) /* Lame placeholder */ + dev_info(dev, "BPOIE: %u changes received\n", count); + /* + * FIXME: At this point we should go over all the IEs in the + * bpoiec->BPOIE array and act on each. + */ + result = 0; +error: + return result; +} + +/** + * uwb_bg_joined - is the RC in a beacon group? + * @rc: the radio controller + * + * Returns true if the radio controller is in a beacon group (even if + * it's the sole member). + */ +int uwb_bg_joined(struct uwb_rc *rc) +{ + return rc->beaconing != -1; +} +EXPORT_SYMBOL_GPL(uwb_bg_joined); + +/* + * Print beaconing state. + */ +static ssize_t uwb_rc_beacon_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + ssize_t result; + + mutex_lock(&rc->uwb_dev.mutex); + result = sprintf(buf, "%d\n", rc->beaconing); + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +/* + * Start beaconing on the specified channel, or stop beaconing. + * + * The BPST offset of when to start searching for a beacon group to + * join may be specified. + */ +static ssize_t uwb_rc_beacon_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + int channel; + unsigned bpst_offset = 0; + ssize_t result = -EINVAL; + + result = sscanf(buf, "%d %u\n", &channel, &bpst_offset); + if (result >= 1) + result = uwb_rc_beacon(rc, channel, bpst_offset); + + return result < 0 ? result : size; +} +DEVICE_ATTR(beacon, S_IRUGO | S_IWUSR, uwb_rc_beacon_show, uwb_rc_beacon_store); diff --git a/drivers/uwb/driver.c b/drivers/uwb/driver.c new file mode 100644 index 00000000000..521cdeb8497 --- /dev/null +++ b/drivers/uwb/driver.c @@ -0,0 +1,144 @@ +/* + * Ultra Wide Band + * Driver initialization, etc + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * Life cycle: FIXME: explain + * + * UWB radio controller: + * + * 1. alloc a uwb_rc, zero it + * 2. call uwb_rc_init() on it to set it up + ops (won't do any + * kind of allocation) + * 3. register (now it is owned by the UWB stack--deregister before + * freeing/destroying). + * 4. It lives on it's own now (UWB stack handles)--when it + * disconnects, call unregister() + * 5. free it. + * + * Make sure you have a reference to the uwb_rc before calling + * any of the UWB API functions. + * + * TODO: + * + * 1. Locking and life cycle management is crappy still. All entry + * points to the UWB HCD API assume you have a reference on the + * uwb_rc structure and that it won't go away. They mutex lock it + * before doing anything. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kdev_t.h> +#include <linux/random.h> +#include <linux/uwb/debug.h> +#include "uwb-internal.h" + + +/* UWB stack attributes (or 'global' constants) */ + + +/** + * If a beacon dissapears for longer than this, then we consider the + * device who was represented by that beacon to be gone. + * + * ECMA-368[17.2.3, last para] establishes that a device must not + * consider a device to be its neighbour if he doesn't receive a beacon + * for more than mMaxLostBeacons. mMaxLostBeacons is defined in + * ECMA-368[17.16] as 3; because we can get only one beacon per + * superframe, that'd be 3 * 65ms = 195 ~ 200 ms. Let's give it time + * for jitter and stuff and make it 500 ms. + */ +unsigned long beacon_timeout_ms = 500; + +static +ssize_t beacon_timeout_ms_show(struct class *class, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%lu\n", beacon_timeout_ms); +} + +static +ssize_t beacon_timeout_ms_store(struct class *class, + const char *buf, size_t size) +{ + unsigned long bt; + ssize_t result; + result = sscanf(buf, "%lu", &bt); + if (result != 1) + return -EINVAL; + beacon_timeout_ms = bt; + return size; +} + +static struct class_attribute uwb_class_attrs[] = { + __ATTR(beacon_timeout_ms, S_IWUSR | S_IRUGO, + beacon_timeout_ms_show, beacon_timeout_ms_store), + __ATTR_NULL, +}; + +/** Device model classes */ +struct class uwb_rc_class = { + .name = "uwb_rc", + .class_attrs = uwb_class_attrs, +}; + + +static int __init uwb_subsys_init(void) +{ + int result = 0; + + result = uwb_est_create(); + if (result < 0) { + printk(KERN_ERR "uwb: Can't initialize EST subsystem\n"); + goto error_est_init; + } + + result = class_register(&uwb_rc_class); + if (result < 0) + goto error_uwb_rc_class_register; + uwbd_start(); + uwb_dbg_init(); + return 0; + +error_uwb_rc_class_register: + uwb_est_destroy(); +error_est_init: + return result; +} +module_init(uwb_subsys_init); + +static void __exit uwb_subsys_exit(void) +{ + uwb_dbg_exit(); + uwbd_stop(); + class_unregister(&uwb_rc_class); + uwb_est_destroy(); + return; +} +module_exit(uwb_subsys_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("Ultra Wide Band core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/drp-avail.c b/drivers/uwb/drp-avail.c new file mode 100644 index 00000000000..3febd855280 --- /dev/null +++ b/drivers/uwb/drp-avail.c @@ -0,0 +1,288 @@ +/* + * Ultra Wide Band + * DRP availability management + * + * Copyright (C) 2005-2006 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * + * Manage DRP Availability (the MAS available for DRP + * reservations). Thus: + * + * - Handle DRP Availability Change notifications + * + * - Allow the reservation manager to indicate MAS reserved/released + * by local (owned by/targeted at the radio controller) + * reservations. + * + * - Based on the two sources above, generate a DRP Availability IE to + * be included in the beacon. + * + * See also the documentation for struct uwb_drp_avail. + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/bitmap.h> +#include "uwb-internal.h" + +/** + * uwb_drp_avail_init - initialize an RC's MAS availability + * + * All MAS are available initially. The RC will inform use which + * slots are used for the BP (it may change in size). + */ +void uwb_drp_avail_init(struct uwb_rc *rc) +{ + bitmap_fill(rc->drp_avail.global, UWB_NUM_MAS); + bitmap_fill(rc->drp_avail.local, UWB_NUM_MAS); + bitmap_fill(rc->drp_avail.pending, UWB_NUM_MAS); +} + +/* + * Determine MAS available for new local reservations. + * + * avail = global & local & pending + */ +static void uwb_drp_available(struct uwb_rc *rc, struct uwb_mas_bm *avail) +{ + bitmap_and(avail->bm, rc->drp_avail.global, rc->drp_avail.local, UWB_NUM_MAS); + bitmap_and(avail->bm, avail->bm, rc->drp_avail.pending, UWB_NUM_MAS); +} + +/** + * uwb_drp_avail_reserve_pending - reserve MAS for a new reservation + * @rc: the radio controller + * @mas: the MAS to reserve + * + * Returns 0 on success, or -EBUSY if the MAS requested aren't available. + */ +int uwb_drp_avail_reserve_pending(struct uwb_rc *rc, struct uwb_mas_bm *mas) +{ + struct uwb_mas_bm avail; + + uwb_drp_available(rc, &avail); + if (!bitmap_subset(mas->bm, avail.bm, UWB_NUM_MAS)) + return -EBUSY; + + bitmap_andnot(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS); + return 0; +} + +/** + * uwb_drp_avail_reserve - reserve MAS for an established reservation + * @rc: the radio controller + * @mas: the MAS to reserve + */ +void uwb_drp_avail_reserve(struct uwb_rc *rc, struct uwb_mas_bm *mas) +{ + bitmap_or(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS); + bitmap_andnot(rc->drp_avail.local, rc->drp_avail.local, mas->bm, UWB_NUM_MAS); + rc->drp_avail.ie_valid = false; +} + +/** + * uwb_drp_avail_release - release MAS from a pending or established reservation + * @rc: the radio controller + * @mas: the MAS to release + */ +void uwb_drp_avail_release(struct uwb_rc *rc, struct uwb_mas_bm *mas) +{ + bitmap_or(rc->drp_avail.local, rc->drp_avail.local, mas->bm, UWB_NUM_MAS); + bitmap_or(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS); + rc->drp_avail.ie_valid = false; +} + +/** + * uwb_drp_avail_ie_update - update the DRP Availability IE + * @rc: the radio controller + * + * avail = global & local + */ +void uwb_drp_avail_ie_update(struct uwb_rc *rc) +{ + struct uwb_mas_bm avail; + + bitmap_and(avail.bm, rc->drp_avail.global, rc->drp_avail.local, UWB_NUM_MAS); + + rc->drp_avail.ie.hdr.element_id = UWB_IE_DRP_AVAILABILITY; + rc->drp_avail.ie.hdr.length = UWB_NUM_MAS / 8; + uwb_mas_bm_copy_le(rc->drp_avail.ie.bmp, &avail); + rc->drp_avail.ie_valid = true; +} + +/** + * Create an unsigned long from a buffer containing a byte stream. + * + * @array: pointer to buffer + * @itr: index of buffer from where we start + * @len: the buffer's remaining size may not be exact multiple of + * sizeof(unsigned long), @len is the length of buffer that needs + * to be converted. This will be sizeof(unsigned long) or smaller + * (BUG if not). If it is smaller then we will pad the remaining + * space of the result with zeroes. + */ +static +unsigned long get_val(u8 *array, size_t itr, size_t len) +{ + unsigned long val = 0; + size_t top = itr + len; + + BUG_ON(len > sizeof(val)); + + while (itr < top) { + val <<= 8; + val |= array[top - 1]; + top--; + } + val <<= 8 * (sizeof(val) - len); /* padding */ + return val; +} + +/** + * Initialize bitmap from data buffer. + * + * The bitmap to be converted could come from a IE, for example a + * DRP Availability IE. + * From ECMA-368 1.0 [16.8.7]: " + * octets: 1 1 N * (0 to 32) + * Element ID Length (=N) DRP Availability Bitmap + * + * The DRP Availability Bitmap field is up to 256 bits long, one + * bit for each MAS in the superframe, where the least-significant + * bit of the field corresponds to the first MAS in the superframe + * and successive bits correspond to successive MASs." + * + * The DRP Availability bitmap is in octets from 0 to 32, so octet + * 32 contains bits for MAS 1-8, etc. If the bitmap is smaller than 32 + * octets, the bits in octets not included at the end of the bitmap are + * treated as zero. In this case (when the bitmap is smaller than 32 + * octets) the MAS represented range from MAS 1 to MAS (size of bitmap) + * with the last octet still containing bits for MAS 1-8, etc. + * + * For example: + * F00F0102 03040506 0708090A 0B0C0D0E 0F010203 + * ^^^^ + * |||| + * |||| + * |||\LSB of byte is MAS 9 + * ||\MSB of byte is MAS 16 + * |\LSB of first byte is MAS 1 + * \ MSB of byte is MAS 8 + * + * An example of this encoding can be found in ECMA-368 Annex-D [Table D.11] + * + * The resulting bitmap will have the following mapping: + * bit position 0 == MAS 1 + * bit position 1 == MAS 2 + * ... + * bit position (UWB_NUM_MAS - 1) == MAS UWB_NUM_MAS + * + * @bmp_itr: pointer to bitmap (can be declared with DECLARE_BITMAP) + * @buffer: pointer to buffer containing bitmap data in big endian + * format (MSB first) + * @buffer_size:number of bytes with which bitmap should be initialized + */ +static +void buffer_to_bmp(unsigned long *bmp_itr, void *_buffer, + size_t buffer_size) +{ + u8 *buffer = _buffer; + size_t itr, len; + unsigned long val; + + itr = 0; + while (itr < buffer_size) { + len = buffer_size - itr >= sizeof(val) ? + sizeof(val) : buffer_size - itr; + val = get_val(buffer, itr, len); + bmp_itr[itr / sizeof(val)] = val; + itr += sizeof(val); + } +} + + +/** + * Extract DRP Availability bitmap from the notification. + * + * The notification that comes in contains a bitmap of (UWB_NUM_MAS / 8) bytes + * We convert that to our internal representation. + */ +static +int uwbd_evt_get_drp_avail(struct uwb_event *evt, unsigned long *bmp) +{ + struct device *dev = &evt->rc->uwb_dev.dev; + struct uwb_rc_evt_drp_avail *drp_evt; + int result = -EINVAL; + + /* Is there enough data to decode the event? */ + if (evt->notif.size < sizeof(*drp_evt)) { + dev_err(dev, "DRP Availability Change: Not enough " + "data to decode event [%zu bytes, %zu " + "needed]\n", evt->notif.size, sizeof(*drp_evt)); + goto error; + } + drp_evt = container_of(evt->notif.rceb, struct uwb_rc_evt_drp_avail, rceb); + buffer_to_bmp(bmp, drp_evt->bmp, UWB_NUM_MAS/8); + result = 0; +error: + return result; +} + + +/** + * Process an incoming DRP Availability notification. + * + * @evt: Event information (packs the actual event data, which + * radio controller it came to, etc). + * + * @returns: 0 on success (so uwbd() frees the event buffer), < 0 + * on error. + * + * According to ECMA-368 1.0 [16.8.7], bits set to ONE indicate that + * the MAS slot is available, bits set to ZERO indicate that the slot + * is busy. + * + * So we clear available slots, we set used slots :) + * + * The notification only marks non-availability based on the BP and + * received DRP IEs that are not for this radio controller. A copy of + * this bitmap is needed to generate the real availability (which + * includes local and pending reservations). + * + * The DRP Availability IE that this radio controller emits will need + * to be updated. + */ +int uwbd_evt_handle_rc_drp_avail(struct uwb_event *evt) +{ + int result; + struct uwb_rc *rc = evt->rc; + DECLARE_BITMAP(bmp, UWB_NUM_MAS); + + result = uwbd_evt_get_drp_avail(evt, bmp); + if (result < 0) + return result; + + mutex_lock(&rc->rsvs_mutex); + bitmap_copy(rc->drp_avail.global, bmp, UWB_NUM_MAS); + rc->drp_avail.ie_valid = false; + mutex_unlock(&rc->rsvs_mutex); + + uwb_rsv_sched_update(rc); + + return 0; +} diff --git a/drivers/uwb/drp-ie.c b/drivers/uwb/drp-ie.c new file mode 100644 index 00000000000..882724c5f12 --- /dev/null +++ b/drivers/uwb/drp-ie.c @@ -0,0 +1,232 @@ +/* + * UWB DRP IE management. + * + * Copyright (C) 2005-2006 Intel Corporation + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/random.h> +#include <linux/uwb.h> + +#include "uwb-internal.h" + +/* + * Allocate a DRP IE. + * + * To save having to free/allocate a DRP IE when its MAS changes, + * enough memory is allocated for the maxiumum number of DRP + * allocation fields. This gives an overhead per reservation of up to + * (UWB_NUM_ZONES - 1) * 4 = 60 octets. + */ +static struct uwb_ie_drp *uwb_drp_ie_alloc(void) +{ + struct uwb_ie_drp *drp_ie; + unsigned tiebreaker; + + drp_ie = kzalloc(sizeof(struct uwb_ie_drp) + + UWB_NUM_ZONES * sizeof(struct uwb_drp_alloc), + GFP_KERNEL); + if (drp_ie) { + drp_ie->hdr.element_id = UWB_IE_DRP; + + get_random_bytes(&tiebreaker, sizeof(unsigned)); + uwb_ie_drp_set_tiebreaker(drp_ie, tiebreaker & 1); + } + return drp_ie; +} + + +/* + * Fill a DRP IE's allocation fields from a MAS bitmap. + */ +static void uwb_drp_ie_from_bm(struct uwb_ie_drp *drp_ie, + struct uwb_mas_bm *mas) +{ + int z, i, num_fields = 0, next = 0; + struct uwb_drp_alloc *zones; + __le16 current_bmp; + DECLARE_BITMAP(tmp_bmp, UWB_NUM_MAS); + DECLARE_BITMAP(tmp_mas_bm, UWB_MAS_PER_ZONE); + + zones = drp_ie->allocs; + + bitmap_copy(tmp_bmp, mas->bm, UWB_NUM_MAS); + + /* Determine unique MAS bitmaps in zones from bitmap. */ + for (z = 0; z < UWB_NUM_ZONES; z++) { + bitmap_copy(tmp_mas_bm, tmp_bmp, UWB_MAS_PER_ZONE); + if (bitmap_weight(tmp_mas_bm, UWB_MAS_PER_ZONE) > 0) { + bool found = false; + current_bmp = (__le16) *tmp_mas_bm; + for (i = 0; i < next; i++) { + if (current_bmp == zones[i].mas_bm) { + zones[i].zone_bm |= 1 << z; + found = true; + break; + } + } + if (!found) { + num_fields++; + zones[next].zone_bm = 1 << z; + zones[next].mas_bm = current_bmp; + next++; + } + } + bitmap_shift_right(tmp_bmp, tmp_bmp, UWB_MAS_PER_ZONE, UWB_NUM_MAS); + } + + /* Store in format ready for transmission (le16). */ + for (i = 0; i < num_fields; i++) { + drp_ie->allocs[i].zone_bm = cpu_to_le16(zones[i].zone_bm); + drp_ie->allocs[i].mas_bm = cpu_to_le16(zones[i].mas_bm); + } + + drp_ie->hdr.length = sizeof(struct uwb_ie_drp) - sizeof(struct uwb_ie_hdr) + + num_fields * sizeof(struct uwb_drp_alloc); +} + +/** + * uwb_drp_ie_update - update a reservation's DRP IE + * @rsv: the reservation + */ +int uwb_drp_ie_update(struct uwb_rsv *rsv) +{ + struct device *dev = &rsv->rc->uwb_dev.dev; + struct uwb_ie_drp *drp_ie; + int reason_code, status; + + switch (rsv->state) { + case UWB_RSV_STATE_NONE: + kfree(rsv->drp_ie); + rsv->drp_ie = NULL; + return 0; + case UWB_RSV_STATE_O_INITIATED: + reason_code = UWB_DRP_REASON_ACCEPTED; + status = 0; + break; + case UWB_RSV_STATE_O_PENDING: + reason_code = UWB_DRP_REASON_ACCEPTED; + status = 0; + break; + case UWB_RSV_STATE_O_MODIFIED: + reason_code = UWB_DRP_REASON_MODIFIED; + status = 1; + break; + case UWB_RSV_STATE_O_ESTABLISHED: + reason_code = UWB_DRP_REASON_ACCEPTED; + status = 1; + break; + case UWB_RSV_STATE_T_ACCEPTED: + reason_code = UWB_DRP_REASON_ACCEPTED; + status = 1; + break; + case UWB_RSV_STATE_T_DENIED: + reason_code = UWB_DRP_REASON_DENIED; + status = 0; + break; + default: + dev_dbg(dev, "rsv with unhandled state (%d)\n", rsv->state); + return -EINVAL; + } + + if (rsv->drp_ie == NULL) { + rsv->drp_ie = uwb_drp_ie_alloc(); + if (rsv->drp_ie == NULL) + return -ENOMEM; + } + drp_ie = rsv->drp_ie; + + uwb_ie_drp_set_owner(drp_ie, uwb_rsv_is_owner(rsv)); + uwb_ie_drp_set_status(drp_ie, status); + uwb_ie_drp_set_reason_code(drp_ie, reason_code); + uwb_ie_drp_set_stream_index(drp_ie, rsv->stream); + uwb_ie_drp_set_type(drp_ie, rsv->type); + + if (uwb_rsv_is_owner(rsv)) { + switch (rsv->target.type) { + case UWB_RSV_TARGET_DEV: + drp_ie->dev_addr = rsv->target.dev->dev_addr; + break; + case UWB_RSV_TARGET_DEVADDR: + drp_ie->dev_addr = rsv->target.devaddr; + break; + } + } else + drp_ie->dev_addr = rsv->owner->dev_addr; + + uwb_drp_ie_from_bm(drp_ie, &rsv->mas); + + rsv->ie_valid = true; + return 0; +} + +/* + * Set MAS bits from given MAS bitmap in a single zone of large bitmap. + * + * We are given a zone id and the MAS bitmap of bits that need to be set in + * this zone. Note that this zone may already have bits set and this only + * adds settings - we cannot simply assign the MAS bitmap contents to the + * zone contents. We iterate over the the bits (MAS) in the zone and set the + * bits that are set in the given MAS bitmap. + */ +static +void uwb_drp_ie_single_zone_to_bm(struct uwb_mas_bm *bm, u8 zone, u16 mas_bm) +{ + int mas; + u16 mas_mask; + + for (mas = 0; mas < UWB_MAS_PER_ZONE; mas++) { + mas_mask = 1 << mas; + if (mas_bm & mas_mask) + set_bit(zone * UWB_NUM_ZONES + mas, bm->bm); + } +} + +/** + * uwb_drp_ie_zones_to_bm - convert DRP allocation fields to a bitmap + * @mas: MAS bitmap that will be populated to correspond to the + * allocation fields in the DRP IE + * @drp_ie: the DRP IE that contains the allocation fields. + * + * The input format is an array of MAS allocation fields (16 bit Zone + * bitmap, 16 bit MAS bitmap) as described in [ECMA-368] section + * 16.8.6. The output is a full 256 bit MAS bitmap. + * + * We go over all the allocation fields, for each allocation field we + * know which zones are impacted. We iterate over all the zones + * impacted and call a function that will set the correct MAS bits in + * each zone. + */ +void uwb_drp_ie_to_bm(struct uwb_mas_bm *bm, const struct uwb_ie_drp *drp_ie) +{ + int numallocs = (drp_ie->hdr.length - 4) / 4; + const struct uwb_drp_alloc *alloc; + int cnt; + u16 zone_bm, mas_bm; + u8 zone; + u16 zone_mask; + + for (cnt = 0; cnt < numallocs; cnt++) { + alloc = &drp_ie->allocs[cnt]; + zone_bm = le16_to_cpu(alloc->zone_bm); + mas_bm = le16_to_cpu(alloc->mas_bm); + for (zone = 0; zone < UWB_NUM_ZONES; zone++) { + zone_mask = 1 << zone; + if (zone_bm & zone_mask) + uwb_drp_ie_single_zone_to_bm(bm, zone, mas_bm); + } + } +} diff --git a/drivers/uwb/drp.c b/drivers/uwb/drp.c new file mode 100644 index 00000000000..c0b1e5e2bd0 --- /dev/null +++ b/drivers/uwb/drp.c @@ -0,0 +1,461 @@ +/* + * Ultra Wide Band + * Dynamic Reservation Protocol handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/delay.h> +#include "uwb-internal.h" + +/** + * Construct and send the SET DRP IE + * + * @rc: UWB Host controller + * @returns: >= 0 number of bytes still available in the beacon + * < 0 errno code on error. + * + * See WUSB[8.6.2.7]: The host must set all the DRP IEs that it wants the + * device to include in its beacon at the same time. We thus have to + * traverse all reservations and include the DRP IEs of all PENDING + * and NEGOTIATED reservations in a SET DRP command for transmission. + * + * A DRP Availability IE is appended. + * + * rc->uwb_dev.mutex is held + * + * FIXME We currently ignore the returned value indicating the remaining space + * in beacon. This could be used to deny reservation requests earlier if + * determined that they would cause the beacon space to be exceeded. + */ +static +int uwb_rc_gen_send_drp_ie(struct uwb_rc *rc) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_cmd_set_drp_ie *cmd; + struct uwb_rc_evt_set_drp_ie reply; + struct uwb_rsv *rsv; + int num_bytes = 0; + u8 *IEDataptr; + + result = -ENOMEM; + /* First traverse all reservations to determine memory needed. */ + list_for_each_entry(rsv, &rc->reservations, rc_node) { + if (rsv->drp_ie != NULL) + num_bytes += rsv->drp_ie->hdr.length + 2; + } + num_bytes += sizeof(rc->drp_avail.ie); + cmd = kzalloc(sizeof(*cmd) + num_bytes, GFP_KERNEL); + if (cmd == NULL) + goto error; + cmd->rccb.bCommandType = UWB_RC_CET_GENERAL; + cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SET_DRP_IE); + cmd->wIELength = num_bytes; + IEDataptr = (u8 *)&cmd->IEData[0]; + + /* Next traverse all reservations to place IEs in allocated memory. */ + list_for_each_entry(rsv, &rc->reservations, rc_node) { + if (rsv->drp_ie != NULL) { + memcpy(IEDataptr, rsv->drp_ie, + rsv->drp_ie->hdr.length + 2); + IEDataptr += rsv->drp_ie->hdr.length + 2; + } + } + memcpy(IEDataptr, &rc->drp_avail.ie, sizeof(rc->drp_avail.ie)); + + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_SET_DRP_IE; + result = uwb_rc_cmd(rc, "SET-DRP-IE", &cmd->rccb, + sizeof(*cmd) + num_bytes, &reply.rceb, + sizeof(reply)); + if (result < 0) + goto error_cmd; + result = le16_to_cpu(reply.wRemainingSpace); + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, "SET-DRP-IE: command execution " + "failed: %s (%d). RemainingSpace in beacon " + "= %d\n", uwb_rc_strerror(reply.bResultCode), + reply.bResultCode, result); + result = -EIO; + } else { + dev_dbg(dev, "SET-DRP-IE sent. RemainingSpace in beacon " + "= %d.\n", result); + result = 0; + } +error_cmd: + kfree(cmd); +error: + return result; + +} +/** + * Send all DRP IEs associated with this host + * + * @returns: >= 0 number of bytes still available in the beacon + * < 0 errno code on error. + * + * As per the protocol we obtain the host controller device lock to access + * bandwidth structures. + */ +int uwb_rc_send_all_drp_ie(struct uwb_rc *rc) +{ + int result; + + mutex_lock(&rc->uwb_dev.mutex); + result = uwb_rc_gen_send_drp_ie(rc); + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +void uwb_drp_handle_timeout(struct uwb_rsv *rsv) +{ + struct device *dev = &rsv->rc->uwb_dev.dev; + + dev_dbg(dev, "reservation timeout in state %s (%d)\n", + uwb_rsv_state_str(rsv->state), rsv->state); + + switch (rsv->state) { + case UWB_RSV_STATE_O_INITIATED: + if (rsv->is_multicast) { + uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED); + return; + } + break; + case UWB_RSV_STATE_O_ESTABLISHED: + if (rsv->is_multicast) + return; + break; + default: + break; + } + uwb_rsv_remove(rsv); +} + +/* + * Based on the DRP IE, transition a target reservation to a new + * state. + */ +static void uwb_drp_process_target(struct uwb_rc *rc, struct uwb_rsv *rsv, + struct uwb_ie_drp *drp_ie) +{ + struct device *dev = &rc->uwb_dev.dev; + int status; + enum uwb_drp_reason reason_code; + + status = uwb_ie_drp_status(drp_ie); + reason_code = uwb_ie_drp_reason_code(drp_ie); + + if (status) { + switch (reason_code) { + case UWB_DRP_REASON_ACCEPTED: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_ACCEPTED); + break; + case UWB_DRP_REASON_MODIFIED: + dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n", + reason_code, status); + break; + default: + dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n", + reason_code, status); + } + } else { + switch (reason_code) { + case UWB_DRP_REASON_ACCEPTED: + /* New reservations are handled in uwb_rsv_find(). */ + break; + case UWB_DRP_REASON_DENIED: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + break; + case UWB_DRP_REASON_CONFLICT: + case UWB_DRP_REASON_MODIFIED: + dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n", + reason_code, status); + break; + default: + dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n", + reason_code, status); + } + } +} + +/* + * Based on the DRP IE, transition an owner reservation to a new + * state. + */ +static void uwb_drp_process_owner(struct uwb_rc *rc, struct uwb_rsv *rsv, + struct uwb_ie_drp *drp_ie) +{ + struct device *dev = &rc->uwb_dev.dev; + int status; + enum uwb_drp_reason reason_code; + + status = uwb_ie_drp_status(drp_ie); + reason_code = uwb_ie_drp_reason_code(drp_ie); + + if (status) { + switch (reason_code) { + case UWB_DRP_REASON_ACCEPTED: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED); + break; + case UWB_DRP_REASON_MODIFIED: + dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n", + reason_code, status); + break; + default: + dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n", + reason_code, status); + } + } else { + switch (reason_code) { + case UWB_DRP_REASON_PENDING: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_PENDING); + break; + case UWB_DRP_REASON_DENIED: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + break; + case UWB_DRP_REASON_CONFLICT: + case UWB_DRP_REASON_MODIFIED: + dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n", + reason_code, status); + break; + default: + dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n", + reason_code, status); + } + } +} + +/* + * Process a received DRP IE, it's either for a reservation owned by + * the RC or targeted at it (or it's for a WUSB cluster reservation). + */ +static void uwb_drp_process(struct uwb_rc *rc, struct uwb_dev *src, + struct uwb_ie_drp *drp_ie) +{ + struct uwb_rsv *rsv; + + rsv = uwb_rsv_find(rc, src, drp_ie); + if (!rsv) { + /* + * No reservation? It's either for a recently + * terminated reservation; or the DRP IE couldn't be + * processed (e.g., an invalid IE or out of memory). + */ + return; + } + + /* + * Do nothing with DRP IEs for reservations that have been + * terminated. + */ + if (rsv->state == UWB_RSV_STATE_NONE) { + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + return; + } + + if (uwb_ie_drp_owner(drp_ie)) + uwb_drp_process_target(rc, rsv, drp_ie); + else + uwb_drp_process_owner(rc, rsv, drp_ie); +} + + +/* + * Process all the DRP IEs (both DRP IEs and the DRP Availability IE) + * from a device. + */ +static +void uwb_drp_process_all(struct uwb_rc *rc, struct uwb_rc_evt_drp *drp_evt, + size_t ielen, struct uwb_dev *src_dev) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_ie_hdr *ie_hdr; + void *ptr; + + ptr = drp_evt->ie_data; + for (;;) { + ie_hdr = uwb_ie_next(&ptr, &ielen); + if (!ie_hdr) + break; + + switch (ie_hdr->element_id) { + case UWB_IE_DRP_AVAILABILITY: + /* FIXME: does something need to be done with this? */ + break; + case UWB_IE_DRP: + uwb_drp_process(rc, src_dev, (struct uwb_ie_drp *)ie_hdr); + break; + default: + dev_warn(dev, "unexpected IE in DRP notification\n"); + break; + } + } + + if (ielen > 0) + dev_warn(dev, "%d octets remaining in DRP notification\n", + (int)ielen); +} + + +/* + * Go through all the DRP IEs and find the ones that conflict with our + * reservations. + * + * FIXME: must resolve the conflict according the the rules in + * [ECMA-368]. + */ +static +void uwb_drp_process_conflict_all(struct uwb_rc *rc, struct uwb_rc_evt_drp *drp_evt, + size_t ielen, struct uwb_dev *src_dev) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_ie_hdr *ie_hdr; + struct uwb_ie_drp *drp_ie; + void *ptr; + + ptr = drp_evt->ie_data; + for (;;) { + ie_hdr = uwb_ie_next(&ptr, &ielen); + if (!ie_hdr) + break; + + drp_ie = container_of(ie_hdr, struct uwb_ie_drp, hdr); + + /* FIXME: check if this DRP IE conflicts. */ + } + + if (ielen > 0) + dev_warn(dev, "%d octets remaining in DRP notification\n", + (int)ielen); +} + + +/* + * Terminate all reservations owned by, or targeted at, 'uwb_dev'. + */ +static void uwb_drp_terminate_all(struct uwb_rc *rc, struct uwb_dev *uwb_dev) +{ + struct uwb_rsv *rsv; + + list_for_each_entry(rsv, &rc->reservations, rc_node) { + if (rsv->owner == uwb_dev + || (rsv->target.type == UWB_RSV_TARGET_DEV && rsv->target.dev == uwb_dev)) + uwb_rsv_remove(rsv); + } +} + + +/** + * uwbd_evt_handle_rc_drp - handle a DRP_IE event + * @evt: the DRP_IE event from the radio controller + * + * This processes DRP notifications from the radio controller, either + * initiating a new reservation or transitioning an existing + * reservation into a different state. + * + * DRP notifications can occur for three different reasons: + * + * - UWB_DRP_NOTIF_DRP_IE_RECVD: one or more DRP IEs with the RC as + * the target or source have been recieved. + * + * These DRP IEs could be new or for an existing reservation. + * + * If the DRP IE for an existing reservation ceases to be to + * recieved for at least mMaxLostBeacons, the reservation should be + * considered to be terminated. Note that the TERMINATE reason (see + * below) may not always be signalled (e.g., the remote device has + * two or more reservations established with the RC). + * + * - UWB_DRP_NOTIF_CONFLICT: DRP IEs from any device in the beacon + * group conflict with the RC's reservations. + * + * - UWB_DRP_NOTIF_TERMINATE: DRP IEs are no longer being received + * from a device (i.e., it's terminated all reservations). + * + * Only the software state of the reservations is changed; the setting + * of the radio controller's DRP IEs is done after all the events in + * an event buffer are processed. This saves waiting multiple times + * for the SET_DRP_IE command to complete. + */ +int uwbd_evt_handle_rc_drp(struct uwb_event *evt) +{ + struct device *dev = &evt->rc->uwb_dev.dev; + struct uwb_rc *rc = evt->rc; + struct uwb_rc_evt_drp *drp_evt; + size_t ielength, bytes_left; + struct uwb_dev_addr src_addr; + struct uwb_dev *src_dev; + int reason; + + /* Is there enough data to decode the event (and any IEs in + its payload)? */ + if (evt->notif.size < sizeof(*drp_evt)) { + dev_err(dev, "DRP event: Not enough data to decode event " + "[%zu bytes left, %zu needed]\n", + evt->notif.size, sizeof(*drp_evt)); + return 0; + } + bytes_left = evt->notif.size - sizeof(*drp_evt); + drp_evt = container_of(evt->notif.rceb, struct uwb_rc_evt_drp, rceb); + ielength = le16_to_cpu(drp_evt->ie_length); + if (bytes_left != ielength) { + dev_err(dev, "DRP event: Not enough data in payload [%zu" + "bytes left, %zu declared in the event]\n", + bytes_left, ielength); + return 0; + } + + memcpy(src_addr.data, &drp_evt->src_addr, sizeof(src_addr)); + src_dev = uwb_dev_get_by_devaddr(rc, &src_addr); + if (!src_dev) { + /* + * A DRP notification from an unrecognized device. + * + * This is probably from a WUSB device that doesn't + * have an EUI-48 and therefore doesn't show up in the + * UWB device database. It's safe to simply ignore + * these. + */ + return 0; + } + + mutex_lock(&rc->rsvs_mutex); + + reason = uwb_rc_evt_drp_reason(drp_evt); + + switch (reason) { + case UWB_DRP_NOTIF_DRP_IE_RCVD: + uwb_drp_process_all(rc, drp_evt, ielength, src_dev); + break; + case UWB_DRP_NOTIF_CONFLICT: + uwb_drp_process_conflict_all(rc, drp_evt, ielength, src_dev); + break; + case UWB_DRP_NOTIF_TERMINATE: + uwb_drp_terminate_all(rc, src_dev); + break; + default: + dev_warn(dev, "ignored DRP event with reason code: %d\n", reason); + break; + } + + mutex_unlock(&rc->rsvs_mutex); + + uwb_dev_put(src_dev); + return 0; +} diff --git a/drivers/uwb/est.c b/drivers/uwb/est.c new file mode 100644 index 00000000000..5fe566b7c84 --- /dev/null +++ b/drivers/uwb/est.c @@ -0,0 +1,477 @@ +/* + * Ultra Wide Band Radio Control + * Event Size Tables management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * Infrastructure, code and data tables for guessing the size of + * events received on the notification endpoints of UWB radio + * controllers. + * + * You define a table of events and for each, its size and how to get + * the extra size. + * + * ENTRY POINTS: + * + * uwb_est_{init/destroy}(): To initialize/release the EST subsystem. + * + * uwb_est_[u]register(): To un/register event size tables + * uwb_est_grow() + * + * uwb_est_find_size(): Get the size of an event + * uwb_est_get_size() + */ +#include <linux/spinlock.h> +#define D_LOCAL 0 +#include <linux/uwb/debug.h> +#include "uwb-internal.h" + + +struct uwb_est { + u16 type_event_high; + u16 vendor, product; + u8 entries; + const struct uwb_est_entry *entry; +}; + + +static struct uwb_est *uwb_est; +static u8 uwb_est_size; +static u8 uwb_est_used; +static DEFINE_RWLOCK(uwb_est_lock); + +/** + * WUSB Standard Event Size Table, HWA-RC interface + * + * Sizes for events and notifications type 0 (general), high nibble 0. + */ +static +struct uwb_est_entry uwb_est_00_00xx[] = { + [UWB_RC_EVT_IE_RCV] = { + .size = sizeof(struct uwb_rc_evt_ie_rcv), + .offset = 1 + offsetof(struct uwb_rc_evt_ie_rcv, wIELength), + }, + [UWB_RC_EVT_BEACON] = { + .size = sizeof(struct uwb_rc_evt_beacon), + .offset = 1 + offsetof(struct uwb_rc_evt_beacon, wBeaconInfoLength), + }, + [UWB_RC_EVT_BEACON_SIZE] = { + .size = sizeof(struct uwb_rc_evt_beacon_size), + }, + [UWB_RC_EVT_BPOIE_CHANGE] = { + .size = sizeof(struct uwb_rc_evt_bpoie_change), + .offset = 1 + offsetof(struct uwb_rc_evt_bpoie_change, + wBPOIELength), + }, + [UWB_RC_EVT_BP_SLOT_CHANGE] = { + .size = sizeof(struct uwb_rc_evt_bp_slot_change), + }, + [UWB_RC_EVT_BP_SWITCH_IE_RCV] = { + .size = sizeof(struct uwb_rc_evt_bp_switch_ie_rcv), + .offset = 1 + offsetof(struct uwb_rc_evt_bp_switch_ie_rcv, wIELength), + }, + [UWB_RC_EVT_DEV_ADDR_CONFLICT] = { + .size = sizeof(struct uwb_rc_evt_dev_addr_conflict), + }, + [UWB_RC_EVT_DRP_AVAIL] = { + .size = sizeof(struct uwb_rc_evt_drp_avail) + }, + [UWB_RC_EVT_DRP] = { + .size = sizeof(struct uwb_rc_evt_drp), + .offset = 1 + offsetof(struct uwb_rc_evt_drp, ie_length), + }, + [UWB_RC_EVT_BP_SWITCH_STATUS] = { + .size = sizeof(struct uwb_rc_evt_bp_switch_status), + }, + [UWB_RC_EVT_CMD_FRAME_RCV] = { + .size = sizeof(struct uwb_rc_evt_cmd_frame_rcv), + .offset = 1 + offsetof(struct uwb_rc_evt_cmd_frame_rcv, dataLength), + }, + [UWB_RC_EVT_CHANNEL_CHANGE_IE_RCV] = { + .size = sizeof(struct uwb_rc_evt_channel_change_ie_rcv), + .offset = 1 + offsetof(struct uwb_rc_evt_channel_change_ie_rcv, wIELength), + }, + [UWB_RC_CMD_CHANNEL_CHANGE] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_DEV_ADDR_MGMT] = { + .size = sizeof(struct uwb_rc_evt_dev_addr_mgmt) }, + [UWB_RC_CMD_GET_IE] = { + .size = sizeof(struct uwb_rc_evt_get_ie), + .offset = 1 + offsetof(struct uwb_rc_evt_get_ie, wIELength), + }, + [UWB_RC_CMD_RESET] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SCAN] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SET_BEACON_FILTER] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SET_DRP_IE] = { + .size = sizeof(struct uwb_rc_evt_set_drp_ie), + }, + [UWB_RC_CMD_SET_IE] = { + .size = sizeof(struct uwb_rc_evt_set_ie), + }, + [UWB_RC_CMD_SET_NOTIFICATION_FILTER] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SET_TX_POWER] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SLEEP] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_START_BEACON] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_STOP_BEACON] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_BP_MERGE] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SEND_COMMAND_FRAME] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SET_ASIE_NOTIF] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, +}; + +static +struct uwb_est_entry uwb_est_01_00xx[] = { + [UWB_RC_DAA_ENERGY_DETECTED] = { + .size = sizeof(struct uwb_rc_evt_daa_energy_detected), + }, + [UWB_RC_SET_DAA_ENERGY_MASK] = { + .size = sizeof(struct uwb_rc_evt_set_daa_energy_mask), + }, + [UWB_RC_SET_NOTIFICATION_FILTER_EX] = { + .size = sizeof(struct uwb_rc_evt_set_notification_filter_ex), + }, +}; + +/** + * Initialize the EST subsystem + * + * Register the standard tables also. + * + * FIXME: tag init + */ +int uwb_est_create(void) +{ + int result; + + uwb_est_size = 2; + uwb_est_used = 0; + uwb_est = kzalloc(uwb_est_size * sizeof(uwb_est[0]), GFP_KERNEL); + if (uwb_est == NULL) + return -ENOMEM; + + result = uwb_est_register(UWB_RC_CET_GENERAL, 0, 0xffff, 0xffff, + uwb_est_00_00xx, ARRAY_SIZE(uwb_est_00_00xx)); + if (result < 0) + goto out; + result = uwb_est_register(UWB_RC_CET_EX_TYPE_1, 0, 0xffff, 0xffff, + uwb_est_01_00xx, ARRAY_SIZE(uwb_est_01_00xx)); +out: + return result; +} + + +/** Clean it up */ +void uwb_est_destroy(void) +{ + kfree(uwb_est); + uwb_est = NULL; + uwb_est_size = uwb_est_used = 0; +} + + +/** + * Double the capacity of the EST table + * + * @returns 0 if ok, < 0 errno no error. + */ +static +int uwb_est_grow(void) +{ + size_t actual_size = uwb_est_size * sizeof(uwb_est[0]); + void *new = kmalloc(2 * actual_size, GFP_ATOMIC); + if (new == NULL) + return -ENOMEM; + memcpy(new, uwb_est, actual_size); + memset(new + actual_size, 0, actual_size); + kfree(uwb_est); + uwb_est = new; + uwb_est_size *= 2; + return 0; +} + + +/** + * Register an event size table + * + * Makes room for it if the table is full, and then inserts it in the + * right position (entries are sorted by type, event_high, vendor and + * then product). + * + * @vendor: vendor code for matching against the device (0x0000 and + * 0xffff mean any); use 0x0000 to force all to match without + * checking possible vendor specific ones, 0xfffff to match + * after checking vendor specific ones. + * + * @product: product code from that vendor; same matching rules, use + * 0x0000 for not allowing vendor specific matches, 0xffff + * for allowing. + * + * This arragement just makes the tables sort differenty. Because the + * table is sorted by growing type-event_high-vendor-product, a zero + * vendor will match before than a 0x456a vendor, that will match + * before a 0xfffff vendor. + * + * @returns 0 if ok, < 0 errno on error (-ENOENT if not found). + */ +/* FIXME: add bus type to vendor/product code */ +int uwb_est_register(u8 type, u8 event_high, u16 vendor, u16 product, + const struct uwb_est_entry *entry, size_t entries) +{ + unsigned long flags; + unsigned itr; + u16 type_event_high; + int result = 0; + + write_lock_irqsave(&uwb_est_lock, flags); + if (uwb_est_used == uwb_est_size) { + result = uwb_est_grow(); + if (result < 0) + goto out; + } + /* Find the right spot to insert it in */ + type_event_high = type << 8 | event_high; + for (itr = 0; itr < uwb_est_used; itr++) + if (uwb_est[itr].type_event_high < type + && uwb_est[itr].vendor < vendor + && uwb_est[itr].product < product) + break; + + /* Shift others to make room for the new one? */ + if (itr < uwb_est_used) + memmove(&uwb_est[itr+1], &uwb_est[itr], uwb_est_used - itr); + uwb_est[itr].type_event_high = type << 8 | event_high; + uwb_est[itr].vendor = vendor; + uwb_est[itr].product = product; + uwb_est[itr].entry = entry; + uwb_est[itr].entries = entries; + uwb_est_used++; +out: + write_unlock_irqrestore(&uwb_est_lock, flags); + return result; +} +EXPORT_SYMBOL_GPL(uwb_est_register); + + +/** + * Unregister an event size table + * + * This just removes the specified entry and moves the ones after it + * to fill in the gap. This is needed to keep the list sorted; no + * reallocation is done to reduce the size of the table. + * + * We unregister by all the data we used to register instead of by + * pointer to the @entry array because we might have used the same + * table for a bunch of IDs (for example). + * + * @returns 0 if ok, < 0 errno on error (-ENOENT if not found). + */ +int uwb_est_unregister(u8 type, u8 event_high, u16 vendor, u16 product, + const struct uwb_est_entry *entry, size_t entries) +{ + unsigned long flags; + unsigned itr; + struct uwb_est est_cmp = { + .type_event_high = type << 8 | event_high, + .vendor = vendor, + .product = product, + .entry = entry, + .entries = entries + }; + write_lock_irqsave(&uwb_est_lock, flags); + for (itr = 0; itr < uwb_est_used; itr++) + if (!memcmp(&uwb_est[itr], &est_cmp, sizeof(est_cmp))) + goto found; + write_unlock_irqrestore(&uwb_est_lock, flags); + return -ENOENT; + +found: + if (itr < uwb_est_used - 1) /* Not last one? move ones above */ + memmove(&uwb_est[itr], &uwb_est[itr+1], uwb_est_used - itr - 1); + uwb_est_used--; + write_unlock_irqrestore(&uwb_est_lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(uwb_est_unregister); + + +/** + * Get the size of an event from a table + * + * @rceb: pointer to the buffer with the event + * @rceb_size: size of the area pointed to by @rceb in bytes. + * @returns: > 0 Size of the event + * -ENOSPC An area big enough was not provided to look + * ahead into the event's guts and guess the size. + * -EINVAL Unknown event code (wEvent). + * + * This will look at the received RCEB and guess what is the total + * size. For variable sized events, it will look further ahead into + * their length field to see how much data should be read. + * + * Note this size is *not* final--the neh (Notification/Event Handle) + * might specificy an extra size to add. + */ +static +ssize_t uwb_est_get_size(struct uwb_rc *uwb_rc, struct uwb_est *est, + u8 event_low, const struct uwb_rceb *rceb, + size_t rceb_size) +{ + unsigned offset; + ssize_t size; + struct device *dev = &uwb_rc->uwb_dev.dev; + const struct uwb_est_entry *entry; + + size = -ENOENT; + if (event_low >= est->entries) { /* in range? */ + dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: event %u out of range\n", + est, est->type_event_high, est->vendor, est->product, + est->entries, event_low); + goto out; + } + size = -ENOENT; + entry = &est->entry[event_low]; + if (entry->size == 0 && entry->offset == 0) { /* unknown? */ + dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: event %u unknown\n", + est, est->type_event_high, est->vendor, est->product, + est->entries, event_low); + goto out; + } + offset = entry->offset; /* extra fries with that? */ + if (offset == 0) + size = entry->size; + else { + /* Ops, got an extra size field at 'offset'--read it */ + const void *ptr = rceb; + size_t type_size = 0; + offset--; + size = -ENOSPC; /* enough data for more? */ + switch (entry->type) { + case UWB_EST_16: type_size = sizeof(__le16); break; + case UWB_EST_8: type_size = sizeof(u8); break; + default: BUG(); + } + if (offset + type_size > rceb_size) { + dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: " + "not enough data to read extra size\n", + est, est->type_event_high, est->vendor, + est->product, est->entries); + goto out; + } + size = entry->size; + ptr += offset; + switch (entry->type) { + case UWB_EST_16: size += le16_to_cpu(*(__le16 *)ptr); break; + case UWB_EST_8: size += *(u8 *)ptr; break; + default: BUG(); + } + } +out: + return size; +} + + +/** + * Guesses the size of a WA event + * + * @rceb: pointer to the buffer with the event + * @rceb_size: size of the area pointed to by @rceb in bytes. + * @returns: > 0 Size of the event + * -ENOSPC An area big enough was not provided to look + * ahead into the event's guts and guess the size. + * -EINVAL Unknown event code (wEvent). + * + * This will look at the received RCEB and guess what is the total + * size by checking all the tables registered with + * uwb_est_register(). For variable sized events, it will look further + * ahead into their length field to see how much data should be read. + * + * Note this size is *not* final--the neh (Notification/Event Handle) + * might specificy an extra size to add or replace. + */ +ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb, + size_t rceb_size) +{ + /* FIXME: add vendor/product data */ + ssize_t size; + struct device *dev = &rc->uwb_dev.dev; + unsigned long flags; + unsigned itr; + u16 type_event_high, event; + u8 *ptr = (u8 *) rceb; + + read_lock_irqsave(&uwb_est_lock, flags); + d_printf(2, dev, "Size query for event 0x%02x/%04x/%02x," + " buffer size %ld\n", + (unsigned) rceb->bEventType, + (unsigned) le16_to_cpu(rceb->wEvent), + (unsigned) rceb->bEventContext, + (long) rceb_size); + size = -ENOSPC; + if (rceb_size < sizeof(*rceb)) + goto out; + event = le16_to_cpu(rceb->wEvent); + type_event_high = rceb->bEventType << 8 | (event & 0xff00) >> 8; + for (itr = 0; itr < uwb_est_used; itr++) { + d_printf(3, dev, "Checking EST 0x%04x/%04x/%04x\n", + uwb_est[itr].type_event_high, uwb_est[itr].vendor, + uwb_est[itr].product); + if (uwb_est[itr].type_event_high != type_event_high) + continue; + size = uwb_est_get_size(rc, &uwb_est[itr], + event & 0x00ff, rceb, rceb_size); + /* try more tables that might handle the same type */ + if (size != -ENOENT) + goto out; + } + dev_dbg(dev, "event 0x%02x/%04x/%02x: no handlers available; " + "RCEB %02x %02x %02x %02x\n", + (unsigned) rceb->bEventType, + (unsigned) le16_to_cpu(rceb->wEvent), + (unsigned) rceb->bEventContext, + ptr[0], ptr[1], ptr[2], ptr[3]); + size = -ENOENT; +out: + read_unlock_irqrestore(&uwb_est_lock, flags); + return size; +} +EXPORT_SYMBOL_GPL(uwb_est_find_size); diff --git a/drivers/uwb/hwa-rc.c b/drivers/uwb/hwa-rc.c new file mode 100644 index 00000000000..3d26fa0f8ae --- /dev/null +++ b/drivers/uwb/hwa-rc.c @@ -0,0 +1,926 @@ +/* + * WUSB Host Wire Adapter: Radio Control Interface (WUSB[8.6]) + * Radio Control command/event transport + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Initialize the Radio Control interface Driver. + * + * For each device probed, creates an 'struct hwarc' which contains + * just the representation of the UWB Radio Controller, and the logic + * for reading notifications and passing them to the UWB Core. + * + * So we initialize all of those, register the UWB Radio Controller + * and setup the notification/event handle to pipe the notifications + * to the UWB management Daemon. + * + * Command and event filtering. + * + * This is the driver for the Radio Control Interface described in WUSB + * 1.0. The core UWB module assumes that all drivers are compliant to the + * WHCI 0.95 specification. We thus create a filter that parses all + * incoming messages from the (WUSB 1.0) device and manipulate them to + * conform to the WHCI 0.95 specification. Similarly, outgoing messages + * are parsed and manipulated to conform to the WUSB 1.0 compliant messages + * that the device expects. Only a few messages are affected: + * Affected events: + * UWB_RC_EVT_BEACON + * UWB_RC_EVT_BP_SLOT_CHANGE + * UWB_RC_EVT_DRP_AVAIL + * UWB_RC_EVT_DRP + * Affected commands: + * UWB_RC_CMD_SCAN + * UWB_RC_CMD_SET_DRP_IE + * + * + * + */ +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/usb/wusb.h> +#include <linux/usb/wusb-wa.h> +#include <linux/uwb.h> +#include "uwb-internal.h" +#define D_LOCAL 1 +#include <linux/uwb/debug.h> + +/* The device uses commands and events from the WHCI specification, although + * reporting itself as WUSB compliant. */ +#define WUSB_QUIRK_WHCI_CMD_EVT 0x01 + +/** + * Descriptor for an instance of the UWB Radio Control Driver that + * attaches to the RCI interface of the Host Wired Adapter. + * + * Unless there is a lock specific to the 'data members', all access + * is protected by uwb_rc->mutex. + * + * The NEEP (Notification/Event EndPoint) URB (@neep_urb) writes to + * @rd_buffer. Note there is no locking because it is perfectly (heh!) + * serialized--probe() submits an URB, callback is called, processes + * the data (synchronously), submits another URB, and so on. There is + * no concurrent access to the buffer. + */ +struct hwarc { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + struct uwb_rc *uwb_rc; /* UWB host controller */ + struct urb *neep_urb; /* Notification endpoint handling */ + struct edc neep_edc; + void *rd_buffer; /* NEEP read buffer */ +}; + + +/* Beacon received notification (WUSB 1.0 [8.6.3.2]) */ +struct uwb_rc_evt_beacon_WUSB_0100 { + struct uwb_rceb rceb; + u8 bChannelNumber; + __le16 wBPSTOffset; + u8 bLQI; + u8 bRSSI; + __le16 wBeaconInfoLength; + u8 BeaconInfo[]; +} __attribute__((packed)); + +/** + * Filter WUSB 1.0 BEACON RCV notification to be WHCI 0.95 + * + * @header: the incoming event + * @buf_size: size of buffer containing incoming event + * @new_size: size of event after filtering completed + * + * The WHCI 0.95 spec has a "Beacon Type" field. This value is unknown at + * the time we receive the beacon from WUSB so we just set it to + * UWB_RC_BEACON_TYPE_NEIGHBOR as a default. + * The solution below allocates memory upon receipt of every beacon from a + * WUSB device. This will deteriorate performance. What is the right way to + * do this? + */ +static +int hwarc_filter_evt_beacon_WUSB_0100(struct uwb_rc *rc, + struct uwb_rceb **header, + const size_t buf_size, + size_t *new_size) +{ + struct uwb_rc_evt_beacon_WUSB_0100 *be; + struct uwb_rc_evt_beacon *newbe; + size_t bytes_left, ielength; + struct device *dev = &rc->uwb_dev.dev; + + be = container_of(*header, struct uwb_rc_evt_beacon_WUSB_0100, rceb); + bytes_left = buf_size; + if (bytes_left < sizeof(*be)) { + dev_err(dev, "Beacon Received Notification: Not enough data " + "to decode for filtering (%zu vs %zu bytes needed)\n", + bytes_left, sizeof(*be)); + return -EINVAL; + } + bytes_left -= sizeof(*be); + ielength = le16_to_cpu(be->wBeaconInfoLength); + if (bytes_left < ielength) { + dev_err(dev, "Beacon Received Notification: Not enough data " + "to decode IEs (%zu vs %zu bytes needed)\n", + bytes_left, ielength); + return -EINVAL; + } + newbe = kzalloc(sizeof(*newbe) + ielength, GFP_ATOMIC); + if (newbe == NULL) + return -ENOMEM; + newbe->rceb = be->rceb; + newbe->bChannelNumber = be->bChannelNumber; + newbe->bBeaconType = UWB_RC_BEACON_TYPE_NEIGHBOR; + newbe->wBPSTOffset = be->wBPSTOffset; + newbe->bLQI = be->bLQI; + newbe->bRSSI = be->bRSSI; + newbe->wBeaconInfoLength = be->wBeaconInfoLength; + memcpy(newbe->BeaconInfo, be->BeaconInfo, ielength); + *header = &newbe->rceb; + *new_size = sizeof(*newbe) + ielength; + return 1; /* calling function will free memory */ +} + + +/* DRP Availability change notification (WUSB 1.0 [8.6.3.8]) */ +struct uwb_rc_evt_drp_avail_WUSB_0100 { + struct uwb_rceb rceb; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/** + * Filter WUSB 1.0 DRP AVAILABILITY CHANGE notification to be WHCI 0.95 + * + * @header: the incoming event + * @buf_size: size of buffer containing incoming event + * @new_size: size of event after filtering completed + */ +static +int hwarc_filter_evt_drp_avail_WUSB_0100(struct uwb_rc *rc, + struct uwb_rceb **header, + const size_t buf_size, + size_t *new_size) +{ + struct uwb_rc_evt_drp_avail_WUSB_0100 *da; + struct uwb_rc_evt_drp_avail *newda; + struct uwb_ie_hdr *ie_hdr; + size_t bytes_left, ielength; + struct device *dev = &rc->uwb_dev.dev; + + + da = container_of(*header, struct uwb_rc_evt_drp_avail_WUSB_0100, rceb); + bytes_left = buf_size; + if (bytes_left < sizeof(*da)) { + dev_err(dev, "Not enough data to decode DRP Avail " + "Notification for filtering. Expected %zu, " + "received %zu.\n", (size_t)sizeof(*da), bytes_left); + return -EINVAL; + } + bytes_left -= sizeof(*da); + ielength = le16_to_cpu(da->wIELength); + if (bytes_left < ielength) { + dev_err(dev, "DRP Avail Notification filter: IE length " + "[%zu bytes] does not match actual length " + "[%zu bytes].\n", ielength, bytes_left); + return -EINVAL; + } + if (ielength < sizeof(*ie_hdr)) { + dev_err(dev, "DRP Avail Notification filter: Not enough " + "data to decode IE [%zu bytes, %zu needed]\n", + ielength, sizeof(*ie_hdr)); + return -EINVAL; + } + ie_hdr = (void *) da->IEData; + if (ie_hdr->length > 32) { + dev_err(dev, "DRP Availability Change event has unexpected " + "length for filtering. Expected < 32 bytes, " + "got %zu bytes.\n", (size_t)ie_hdr->length); + return -EINVAL; + } + newda = kzalloc(sizeof(*newda), GFP_ATOMIC); + if (newda == NULL) + return -ENOMEM; + newda->rceb = da->rceb; + memcpy(newda->bmp, (u8 *) ie_hdr + sizeof(*ie_hdr), ie_hdr->length); + *header = &newda->rceb; + *new_size = sizeof(*newda); + return 1; /* calling function will free memory */ +} + + +/* DRP notification (WUSB 1.0 [8.6.3.9]) */ +struct uwb_rc_evt_drp_WUSB_0100 { + struct uwb_rceb rceb; + struct uwb_dev_addr wSrcAddr; + u8 bExplicit; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/** + * Filter WUSB 1.0 DRP Notification to be WHCI 0.95 + * + * @header: the incoming event + * @buf_size: size of buffer containing incoming event + * @new_size: size of event after filtering completed + * + * It is hard to manage DRP reservations without having a Reason code. + * Unfortunately there is none in the WUSB spec. We just set the default to + * DRP IE RECEIVED. + * We do not currently use the bBeaconSlotNumber value, so we set this to + * zero for now. + */ +static +int hwarc_filter_evt_drp_WUSB_0100(struct uwb_rc *rc, + struct uwb_rceb **header, + const size_t buf_size, + size_t *new_size) +{ + struct uwb_rc_evt_drp_WUSB_0100 *drpev; + struct uwb_rc_evt_drp *newdrpev; + size_t bytes_left, ielength; + struct device *dev = &rc->uwb_dev.dev; + + drpev = container_of(*header, struct uwb_rc_evt_drp_WUSB_0100, rceb); + bytes_left = buf_size; + if (bytes_left < sizeof(*drpev)) { + dev_err(dev, "Not enough data to decode DRP Notification " + "for filtering. Expected %zu, received %zu.\n", + (size_t)sizeof(*drpev), bytes_left); + return -EINVAL; + } + ielength = le16_to_cpu(drpev->wIELength); + bytes_left -= sizeof(*drpev); + if (bytes_left < ielength) { + dev_err(dev, "DRP Notification filter: header length [%zu " + "bytes] does not match actual length [%zu " + "bytes].\n", ielength, bytes_left); + return -EINVAL; + } + newdrpev = kzalloc(sizeof(*newdrpev) + ielength, GFP_ATOMIC); + if (newdrpev == NULL) + return -ENOMEM; + newdrpev->rceb = drpev->rceb; + newdrpev->src_addr = drpev->wSrcAddr; + newdrpev->reason = UWB_DRP_NOTIF_DRP_IE_RCVD; + newdrpev->beacon_slot_number = 0; + newdrpev->ie_length = drpev->wIELength; + memcpy(newdrpev->ie_data, drpev->IEData, ielength); + *header = &newdrpev->rceb; + *new_size = sizeof(*newdrpev) + ielength; + return 1; /* calling function will free memory */ +} + + +/* Scan Command (WUSB 1.0 [8.6.2.5]) */ +struct uwb_rc_cmd_scan_WUSB_0100 { + struct uwb_rccb rccb; + u8 bChannelNumber; + u8 bScanState; +} __attribute__((packed)); + +/** + * Filter WHCI 0.95 SCAN command to be WUSB 1.0 SCAN command + * + * @header: command sent to device (compliant to WHCI 0.95) + * @size: size of command sent to device + * + * We only reduce the size by two bytes because the WUSB 1.0 scan command + * does not have the last field (wStarttime). Also, make sure we don't send + * the device an unexpected scan type. + */ +static +int hwarc_filter_cmd_scan_WUSB_0100(struct uwb_rc *rc, + struct uwb_rccb **header, + size_t *size) +{ + struct uwb_rc_cmd_scan *sc; + + sc = container_of(*header, struct uwb_rc_cmd_scan, rccb); + + if (sc->bScanState == UWB_SCAN_ONLY_STARTTIME) + sc->bScanState = UWB_SCAN_ONLY; + /* Don't send the last two bytes. */ + *size -= 2; + return 0; +} + + +/* SET DRP IE command (WUSB 1.0 [8.6.2.7]) */ +struct uwb_rc_cmd_set_drp_ie_WUSB_0100 { + struct uwb_rccb rccb; + u8 bExplicit; + __le16 wIELength; + struct uwb_ie_drp IEData[]; +} __attribute__((packed)); + +/** + * Filter WHCI 0.95 SET DRP IE command to be WUSB 1.0 SET DRP IE command + * + * @header: command sent to device (compliant to WHCI 0.95) + * @size: size of command sent to device + * + * WUSB has an extra bExplicit field - we assume always explicit + * negotiation so this field is set. The command expected by the device is + * thus larger than the one prepared by the driver so we need to + * reallocate memory to accommodate this. + * We trust the driver to send us the correct data so no checking is done + * on incoming data - evn though it is variable length. + */ +static +int hwarc_filter_cmd_set_drp_ie_WUSB_0100(struct uwb_rc *rc, + struct uwb_rccb **header, + size_t *size) +{ + struct uwb_rc_cmd_set_drp_ie *orgcmd; + struct uwb_rc_cmd_set_drp_ie_WUSB_0100 *cmd; + size_t ielength; + + orgcmd = container_of(*header, struct uwb_rc_cmd_set_drp_ie, rccb); + ielength = le16_to_cpu(orgcmd->wIELength); + cmd = kzalloc(sizeof(*cmd) + ielength, GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + cmd->rccb = orgcmd->rccb; + cmd->bExplicit = 0; + cmd->wIELength = orgcmd->wIELength; + memcpy(cmd->IEData, orgcmd->IEData, ielength); + *header = &cmd->rccb; + *size = sizeof(*cmd) + ielength; + return 1; /* calling function will free memory */ +} + + +/** + * Filter data from WHCI driver to WUSB device + * + * @header: WHCI 0.95 compliant command from driver + * @size: length of command + * + * The routine managing commands to the device (uwb_rc_cmd()) will call the + * filtering function pointer (if it exists) before it passes any data to + * the device. At this time the command has been formatted according to + * WHCI 0.95 and is ready to be sent to the device. + * + * The filter function will be provided with the current command and its + * length. The function will manipulate the command if necessary and + * potentially reallocate memory for a command that needed more memory that + * the given command. If new memory was created the function will return 1 + * to indicate to the calling function that the memory need to be freed + * when not needed any more. The size will contain the new length of the + * command. + * If memory has not been allocated we rely on the original mechanisms to + * free the memory of the command - even when we reduce the value of size. + */ +static +int hwarc_filter_cmd_WUSB_0100(struct uwb_rc *rc, struct uwb_rccb **header, + size_t *size) +{ + int result; + struct uwb_rccb *rccb = *header; + int cmd = le16_to_cpu(rccb->wCommand); + switch (cmd) { + case UWB_RC_CMD_SCAN: + result = hwarc_filter_cmd_scan_WUSB_0100(rc, header, size); + break; + case UWB_RC_CMD_SET_DRP_IE: + result = hwarc_filter_cmd_set_drp_ie_WUSB_0100(rc, header, size); + break; + default: + result = -ENOANO; + break; + } + return result; +} + + +/** + * Filter data from WHCI driver to WUSB device + * + * @header: WHCI 0.95 compliant command from driver + * @size: length of command + * + * Filter commands based on which protocol the device supports. The WUSB + * errata should be the same as WHCI 0.95 so we do not filter that here - + * only WUSB 1.0. + */ +static +int hwarc_filter_cmd(struct uwb_rc *rc, struct uwb_rccb **header, + size_t *size) +{ + int result = -ENOANO; + if (rc->version == 0x0100) + result = hwarc_filter_cmd_WUSB_0100(rc, header, size); + return result; +} + + +/** + * Compute return value as sum of incoming value and value at given offset + * + * @rceb: event for which we compute the size, it contains a variable + * length field. + * @core_size: size of the "non variable" part of the event + * @offset: place in event where the length of the variable part is stored + * @buf_size: total length of buffer in which event arrived - we need to make + * sure we read the offset in memory that is still part of the event + */ +static +ssize_t hwarc_get_event_size(struct uwb_rc *rc, const struct uwb_rceb *rceb, + size_t core_size, size_t offset, + const size_t buf_size) +{ + ssize_t size = -ENOSPC; + const void *ptr = rceb; + size_t type_size = sizeof(__le16); + struct device *dev = &rc->uwb_dev.dev; + + if (offset + type_size >= buf_size) { + dev_err(dev, "Not enough data to read extra size of event " + "0x%02x/%04x/%02x, only got %zu bytes.\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext, buf_size); + goto out; + } + ptr += offset; + size = core_size + le16_to_cpu(*(__le16 *)ptr); +out: + return size; +} + + +/* Beacon slot change notification (WUSB 1.0 [8.6.3.5]) */ +struct uwb_rc_evt_bp_slot_change_WUSB_0100 { + struct uwb_rceb rceb; + u8 bSlotNumber; +} __attribute__((packed)); + + +/** + * Filter data from WUSB device to WHCI driver + * + * @header: incoming event + * @buf_size: size of buffer in which event arrived + * @_event_size: actual size of event in the buffer + * @new_size: size of event after filtered + * + * We don't know how the buffer is constructed - there may be more than one + * event in it so buffer length does not determine event length. We first + * determine the expected size of the incoming event. This value is passed + * back only if the actual filtering succeeded (so we know the computed + * expected size is correct). This value will be zero if + * the event did not need any filtering. + * + * WHCI interprets the BP Slot Change event's data differently than + * WUSB. The event sizes are exactly the same. The data field + * indicates the new beacon slot in which a RC is transmitting its + * beacon. The maximum value of this is 96 (wMacBPLength ECMA-368 + * 17.16 (Table 117)). We thus know that the WUSB value will not set + * the bit bNoSlot, so we don't really do anything (placeholder). + */ +static +int hwarc_filter_event_WUSB_0100(struct uwb_rc *rc, struct uwb_rceb **header, + const size_t buf_size, size_t *_real_size, + size_t *_new_size) +{ + int result = -ENOANO; + struct uwb_rceb *rceb = *header; + int event = le16_to_cpu(rceb->wEvent); + size_t event_size; + size_t core_size, offset; + + if (rceb->bEventType != UWB_RC_CET_GENERAL) + goto out; + switch (event) { + case UWB_RC_EVT_BEACON: + core_size = sizeof(struct uwb_rc_evt_beacon_WUSB_0100); + offset = offsetof(struct uwb_rc_evt_beacon_WUSB_0100, + wBeaconInfoLength); + event_size = hwarc_get_event_size(rc, rceb, core_size, + offset, buf_size); + if (event_size < 0) + goto out; + *_real_size = event_size; + result = hwarc_filter_evt_beacon_WUSB_0100(rc, header, + buf_size, _new_size); + break; + case UWB_RC_EVT_BP_SLOT_CHANGE: + *_new_size = *_real_size = + sizeof(struct uwb_rc_evt_bp_slot_change_WUSB_0100); + result = 0; + break; + + case UWB_RC_EVT_DRP_AVAIL: + core_size = sizeof(struct uwb_rc_evt_drp_avail_WUSB_0100); + offset = offsetof(struct uwb_rc_evt_drp_avail_WUSB_0100, + wIELength); + event_size = hwarc_get_event_size(rc, rceb, core_size, + offset, buf_size); + if (event_size < 0) + goto out; + *_real_size = event_size; + result = hwarc_filter_evt_drp_avail_WUSB_0100( + rc, header, buf_size, _new_size); + break; + + case UWB_RC_EVT_DRP: + core_size = sizeof(struct uwb_rc_evt_drp_WUSB_0100); + offset = offsetof(struct uwb_rc_evt_drp_WUSB_0100, wIELength); + event_size = hwarc_get_event_size(rc, rceb, core_size, + offset, buf_size); + if (event_size < 0) + goto out; + *_real_size = event_size; + result = hwarc_filter_evt_drp_WUSB_0100(rc, header, + buf_size, _new_size); + break; + + default: + break; + } +out: + return result; +} + +/** + * Filter data from WUSB device to WHCI driver + * + * @header: incoming event + * @buf_size: size of buffer in which event arrived + * @_event_size: actual size of event in the buffer + * @_new_size: size of event after filtered + * + * Filter events based on which protocol the device supports. The WUSB + * errata should be the same as WHCI 0.95 so we do not filter that here - + * only WUSB 1.0. + * + * If we don't handle it, we return -ENOANO (why the weird error code? + * well, so if I get it, I can pinpoint in the code that raised + * it...after all, not too many places use the higher error codes). + */ +static +int hwarc_filter_event(struct uwb_rc *rc, struct uwb_rceb **header, + const size_t buf_size, size_t *_real_size, + size_t *_new_size) +{ + int result = -ENOANO; + if (rc->version == 0x0100) + result = hwarc_filter_event_WUSB_0100( + rc, header, buf_size, _real_size, _new_size); + return result; +} + + +/** + * Execute an UWB RC command on HWA + * + * @rc: Instance of a Radio Controller that is a HWA + * @cmd: Buffer containing the RCCB and payload to execute + * @cmd_size: Size of the command buffer. + * + * NOTE: rc's mutex has to be locked + */ +static +int hwarc_cmd(struct uwb_rc *uwb_rc, const struct uwb_rccb *cmd, size_t cmd_size) +{ + struct hwarc *hwarc = uwb_rc->priv; + return usb_control_msg( + hwarc->usb_dev, usb_sndctrlpipe(hwarc->usb_dev, 0), + WA_EXEC_RC_CMD, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, hwarc->usb_iface->cur_altsetting->desc.bInterfaceNumber, + (void *) cmd, cmd_size, 100 /* FIXME: this is totally arbitrary */); +} + +static +int hwarc_reset(struct uwb_rc *uwb_rc) +{ + struct hwarc *hwarc = uwb_rc->priv; + return usb_reset_device(hwarc->usb_dev); +} + +/** + * Callback for the notification and event endpoint + * + * Check's that everything is fine and then passes the read data to + * the notification/event handling mechanism (neh). + */ +static +void hwarc_neep_cb(struct urb *urb) +{ + struct hwarc *hwarc = urb->context; + struct usb_interface *usb_iface = hwarc->usb_iface; + struct device *dev = &usb_iface->dev; + int result; + + switch (result = urb->status) { + case 0: + d_printf(3, dev, "NEEP: receive stat %d, %zu bytes\n", + urb->status, (size_t)urb->actual_length); + uwb_rc_neh_grok(hwarc->uwb_rc, urb->transfer_buffer, + urb->actual_length); + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + d_printf(2, dev, "NEEP: URB reset/noent %d\n", urb->status); + goto out; + case -ESHUTDOWN: /* going away! */ + d_printf(2, dev, "NEEP: URB down %d\n", urb->status); + goto out; + default: /* On general errors, retry unless it gets ugly */ + if (edc_inc(&hwarc->neep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) + goto error_exceeded; + dev_err(dev, "NEEP: URB error %d\n", urb->status); + } + result = usb_submit_urb(urb, GFP_ATOMIC); + d_printf(3, dev, "NEEP: submit %d\n", result); + if (result < 0) { + dev_err(dev, "NEEP: Can't resubmit URB (%d) resetting device\n", + result); + goto error; + } +out: + return; + +error_exceeded: + dev_err(dev, "NEEP: URB max acceptable errors " + "exceeded, resetting device\n"); +error: + uwb_rc_neh_error(hwarc->uwb_rc, result); + uwb_rc_reset_all(hwarc->uwb_rc); + return; +} + +static void hwarc_init(struct hwarc *hwarc) +{ + edc_init(&hwarc->neep_edc); +} + +/** + * Initialize the notification/event endpoint stuff + * + * Note this is effectively a parallel thread; it knows that + * hwarc->uwb_rc always exists because the existence of a 'hwarc' + * means that there is a reverence on the hwarc->uwb_rc (see + * _probe()), and thus _neep_cb() can execute safely. + */ +static int hwarc_neep_init(struct uwb_rc *rc) +{ + struct hwarc *hwarc = rc->priv; + struct usb_interface *iface = hwarc->usb_iface; + struct usb_device *usb_dev = interface_to_usbdev(iface); + struct device *dev = &iface->dev; + int result; + struct usb_endpoint_descriptor *epd; + + epd = &iface->cur_altsetting->endpoint[0].desc; + hwarc->rd_buffer = (void *) __get_free_page(GFP_KERNEL); + if (hwarc->rd_buffer == NULL) { + dev_err(dev, "Unable to allocate notification's read buffer\n"); + goto error_rd_buffer; + } + hwarc->neep_urb = usb_alloc_urb(0, GFP_KERNEL); + if (hwarc->neep_urb == NULL) { + dev_err(dev, "Unable to allocate notification URB\n"); + goto error_urb_alloc; + } + usb_fill_int_urb(hwarc->neep_urb, usb_dev, + usb_rcvintpipe(usb_dev, epd->bEndpointAddress), + hwarc->rd_buffer, PAGE_SIZE, + hwarc_neep_cb, hwarc, epd->bInterval); + result = usb_submit_urb(hwarc->neep_urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "Cannot submit notification URB: %d\n", result); + goto error_neep_submit; + } + return 0; + +error_neep_submit: + usb_free_urb(hwarc->neep_urb); +error_urb_alloc: + free_page((unsigned long)hwarc->rd_buffer); +error_rd_buffer: + return -ENOMEM; +} + + +/** Clean up all the notification endpoint resources */ +static void hwarc_neep_release(struct uwb_rc *rc) +{ + struct hwarc *hwarc = rc->priv; + + usb_kill_urb(hwarc->neep_urb); + usb_free_urb(hwarc->neep_urb); + free_page((unsigned long)hwarc->rd_buffer); +} + +/** + * Get the version from class-specific descriptor + * + * NOTE: this descriptor comes with the big bundled configuration + * descriptor that includes the interfaces' and endpoints', so + * we just look for it in the cached copy kept by the USB stack. + * + * NOTE2: We convert LE fields to CPU order. + */ +static int hwarc_get_version(struct uwb_rc *rc) +{ + int result; + + struct hwarc *hwarc = rc->priv; + struct uwb_rc_control_intf_class_desc *descr; + struct device *dev = &rc->uwb_dev.dev; + struct usb_device *usb_dev = hwarc->usb_dev; + char *itr; + struct usb_descriptor_header *hdr; + size_t itr_size, actconfig_idx; + u16 version; + + actconfig_idx = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[actconfig_idx]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + while (itr_size >= sizeof(*hdr)) { + hdr = (struct usb_descriptor_header *) itr; + d_printf(3, dev, "Extra device descriptor: " + "type %02x/%u bytes @ %zu (%zu left)\n", + hdr->bDescriptorType, hdr->bLength, + (itr - usb_dev->rawdescriptors[actconfig_idx]), + itr_size); + if (hdr->bDescriptorType == USB_DT_CS_RADIO_CONTROL) + goto found; + itr += hdr->bLength; + itr_size -= hdr->bLength; + } + dev_err(dev, "cannot find Radio Control Interface Class descriptor\n"); + return -ENODEV; + +found: + result = -EINVAL; + if (hdr->bLength > itr_size) { /* is it available? */ + dev_err(dev, "incomplete Radio Control Interface Class " + "descriptor (%zu bytes left, %u needed)\n", + itr_size, hdr->bLength); + goto error; + } + if (hdr->bLength < sizeof(*descr)) { + dev_err(dev, "short Radio Control Interface Class " + "descriptor\n"); + goto error; + } + descr = (struct uwb_rc_control_intf_class_desc *) hdr; + /* Make LE fields CPU order */ + version = __le16_to_cpu(descr->bcdRCIVersion); + if (version != 0x0100) { + dev_err(dev, "Device reports protocol version 0x%04x. We " + "do not support that. \n", version); + result = -EINVAL; + goto error; + } + rc->version = version; + d_printf(3, dev, "Device supports WUSB protocol version 0x%04x \n", + rc->version); + result = 0; +error: + return result; +} + +/* + * By creating a 'uwb_rc', we have a reference on it -- that reference + * is the one we drop when we disconnect. + * + * No need to switch altsettings; according to WUSB1.0[8.6.1.1], there + * is only one altsetting allowed. + */ +static int hwarc_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + int result; + struct uwb_rc *uwb_rc; + struct hwarc *hwarc; + struct device *dev = &iface->dev; + + result = -ENOMEM; + uwb_rc = uwb_rc_alloc(); + if (uwb_rc == NULL) { + dev_err(dev, "unable to allocate RC instance\n"); + goto error_rc_alloc; + } + hwarc = kzalloc(sizeof(*hwarc), GFP_KERNEL); + if (hwarc == NULL) { + dev_err(dev, "unable to allocate HWA RC instance\n"); + goto error_alloc; + } + hwarc_init(hwarc); + hwarc->usb_dev = usb_get_dev(interface_to_usbdev(iface)); + hwarc->usb_iface = usb_get_intf(iface); + hwarc->uwb_rc = uwb_rc; + + uwb_rc->owner = THIS_MODULE; + uwb_rc->start = hwarc_neep_init; + uwb_rc->stop = hwarc_neep_release; + uwb_rc->cmd = hwarc_cmd; + uwb_rc->reset = hwarc_reset; + if (id->driver_info & WUSB_QUIRK_WHCI_CMD_EVT) { + uwb_rc->filter_cmd = NULL; + uwb_rc->filter_event = NULL; + } else { + uwb_rc->filter_cmd = hwarc_filter_cmd; + uwb_rc->filter_event = hwarc_filter_event; + } + + result = uwb_rc_add(uwb_rc, dev, hwarc); + if (result < 0) + goto error_rc_add; + result = hwarc_get_version(uwb_rc); + if (result < 0) { + dev_err(dev, "cannot retrieve version of RC \n"); + goto error_get_version; + } + usb_set_intfdata(iface, hwarc); + return 0; + +error_get_version: + uwb_rc_rm(uwb_rc); +error_rc_add: + usb_put_intf(iface); + usb_put_dev(hwarc->usb_dev); +error_alloc: + uwb_rc_put(uwb_rc); +error_rc_alloc: + return result; +} + +static void hwarc_disconnect(struct usb_interface *iface) +{ + struct hwarc *hwarc = usb_get_intfdata(iface); + struct uwb_rc *uwb_rc = hwarc->uwb_rc; + + usb_set_intfdata(hwarc->usb_iface, NULL); + uwb_rc_rm(uwb_rc); + usb_put_intf(hwarc->usb_iface); + usb_put_dev(hwarc->usb_dev); + d_printf(1, &hwarc->usb_iface->dev, "freed hwarc %p\n", hwarc); + kfree(hwarc); + uwb_rc_put(uwb_rc); /* when creating the device, refcount = 1 */ +} + +/** USB device ID's that we handle */ +static struct usb_device_id hwarc_id_table[] = { + /* D-Link DUB-1210 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3d02, 0xe0, 0x01, 0x02), + .driver_info = WUSB_QUIRK_WHCI_CMD_EVT }, + /* Intel i1480 (using firmware 1.3PA2-20070828) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x8086, 0x0c3b, 0xe0, 0x01, 0x02), + .driver_info = WUSB_QUIRK_WHCI_CMD_EVT }, + /* Generic match for the Radio Control interface */ + { USB_INTERFACE_INFO(0xe0, 0x01, 0x02), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, hwarc_id_table); + +static struct usb_driver hwarc_driver = { + .name = "hwa-rc", + .probe = hwarc_probe, + .disconnect = hwarc_disconnect, + .id_table = hwarc_id_table, +}; + +static int __init hwarc_driver_init(void) +{ + int result; + result = usb_register(&hwarc_driver); + if (result < 0) + printk(KERN_ERR "HWA-RC: Cannot register USB driver: %d\n", + result); + return result; + +} +module_init(hwarc_driver_init); + +static void __exit hwarc_driver_exit(void) +{ + usb_deregister(&hwarc_driver); +} +module_exit(hwarc_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("Host Wireless Adapter Radio Control Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/i1480/Makefile b/drivers/uwb/i1480/Makefile new file mode 100644 index 00000000000..212bbc7d4c3 --- /dev/null +++ b/drivers/uwb/i1480/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_UWB_I1480U) += dfu/ i1480-est.o +obj-$(CONFIG_UWB_I1480U_WLP) += i1480u-wlp/ diff --git a/drivers/uwb/i1480/dfu/Makefile b/drivers/uwb/i1480/dfu/Makefile new file mode 100644 index 00000000000..bd1b9f25424 --- /dev/null +++ b/drivers/uwb/i1480/dfu/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_UWB_I1480U) += i1480-dfu-usb.o + +i1480-dfu-usb-objs := \ + dfu.o \ + mac.o \ + phy.o \ + usb.o + + diff --git a/drivers/uwb/i1480/dfu/dfu.c b/drivers/uwb/i1480/dfu/dfu.c new file mode 100644 index 00000000000..9097b3b3038 --- /dev/null +++ b/drivers/uwb/i1480/dfu/dfu.c @@ -0,0 +1,217 @@ +/* + * Intel Wireless UWB Link 1480 + * Main driver + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Common code for firmware upload used by the USB and PCI version; + * i1480_fw_upload() takes a device descriptor and uses the function + * pointers it provides to upload firmware and prepare the PHY. + * + * As well, provides common functions used by the rest of the code. + */ +#include "i1480-dfu.h" +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/device.h> +#include <linux/uwb.h> +#include <linux/random.h> + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +/** + * i1480_rceb_check - Check RCEB for expected field values + * @i1480: pointer to device for which RCEB is being checked + * @rceb: RCEB being checked + * @cmd: which command the RCEB is related to + * @context: expected context + * @expected_type: expected event type + * @expected_event: expected event + * + * If @cmd is NULL, do not print error messages, but still return an error + * code. + * + * Return 0 if @rceb matches the expected values, -EINVAL otherwise. + */ +int i1480_rceb_check(const struct i1480 *i1480, const struct uwb_rceb *rceb, + const char *cmd, u8 context, u8 expected_type, + unsigned expected_event) +{ + int result = 0; + struct device *dev = i1480->dev; + if (rceb->bEventContext != context) { + if (cmd) + dev_err(dev, "%s: unexpected context id 0x%02x " + "(expected 0x%02x)\n", cmd, + rceb->bEventContext, context); + result = -EINVAL; + } + if (rceb->bEventType != expected_type) { + if (cmd) + dev_err(dev, "%s: unexpected event type 0x%02x " + "(expected 0x%02x)\n", cmd, + rceb->bEventType, expected_type); + result = -EINVAL; + } + if (le16_to_cpu(rceb->wEvent) != expected_event) { + if (cmd) + dev_err(dev, "%s: unexpected event 0x%04x " + "(expected 0x%04x)\n", cmd, + le16_to_cpu(rceb->wEvent), expected_event); + result = -EINVAL; + } + return result; +} +EXPORT_SYMBOL_GPL(i1480_rceb_check); + + +/** + * Execute a Radio Control Command + * + * Command data has to be in i1480->cmd_buf. + * + * @returns size of the reply data filled in i1480->evt_buf or < 0 errno + * code on error. + */ +ssize_t i1480_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size, + size_t reply_size) +{ + ssize_t result; + struct uwb_rceb *reply = i1480->evt_buf; + struct uwb_rccb *cmd = i1480->cmd_buf; + u16 expected_event = reply->wEvent; + u8 expected_type = reply->bEventType; + u8 context; + + d_fnstart(3, i1480->dev, "(%p, %s, %zu)\n", i1480, cmd_name, cmd_size); + init_completion(&i1480->evt_complete); + i1480->evt_result = -EINPROGRESS; + do { + get_random_bytes(&context, 1); + } while (context == 0x00 || context == 0xff); + cmd->bCommandContext = context; + result = i1480->cmd(i1480, cmd_name, cmd_size); + if (result < 0) + goto error; + /* wait for the callback to report a event was received */ + result = wait_for_completion_interruptible_timeout( + &i1480->evt_complete, HZ); + if (result == 0) { + result = -ETIMEDOUT; + goto error; + } + if (result < 0) + goto error; + result = i1480->evt_result; + if (result < 0) { + dev_err(i1480->dev, "%s: command reply reception failed: %zd\n", + cmd_name, result); + goto error; + } + /* + * Firmware versions >= 1.4.12224 for IOGear GUWA100U generate a + * spurious notification after firmware is downloaded. So check whether + * the receibed RCEB is such notification before assuming that the + * command has failed. + */ + if (i1480_rceb_check(i1480, i1480->evt_buf, NULL, + 0, 0xfd, 0x0022) == 0) { + /* Now wait for the actual RCEB for this command. */ + result = i1480->wait_init_done(i1480); + if (result < 0) + goto error; + result = i1480->evt_result; + } + if (result != reply_size) { + dev_err(i1480->dev, "%s returned only %zu bytes, %zu expected\n", + cmd_name, result, reply_size); + result = -EINVAL; + goto error; + } + /* Verify we got the right event in response */ + result = i1480_rceb_check(i1480, i1480->evt_buf, cmd_name, context, + expected_type, expected_event); +error: + d_fnend(3, i1480->dev, "(%p, %s, %zu) = %zd\n", + i1480, cmd_name, cmd_size, result); + return result; +} +EXPORT_SYMBOL_GPL(i1480_cmd); + + +static +int i1480_print_state(struct i1480 *i1480) +{ + int result; + u32 *buf = (u32 *) i1480->cmd_buf; + + result = i1480->read(i1480, 0x80080000, 2 * sizeof(*buf)); + if (result < 0) { + dev_err(i1480->dev, "cannot read U & L states: %d\n", result); + goto error; + } + dev_info(i1480->dev, "state U 0x%08x, L 0x%08x\n", buf[0], buf[1]); +error: + return result; +} + + +/* + * PCI probe, firmware uploader + * + * _mac_fw_upload() will call rc_setup(), which needs an rc_release(). + */ +int i1480_fw_upload(struct i1480 *i1480) +{ + int result; + + result = i1480_pre_fw_upload(i1480); /* PHY pre fw */ + if (result < 0 && result != -ENOENT) { + i1480_print_state(i1480); + goto error; + } + result = i1480_mac_fw_upload(i1480); /* MAC fw */ + if (result < 0) { + if (result == -ENOENT) + dev_err(i1480->dev, "Cannot locate MAC FW file '%s'\n", + i1480->mac_fw_name); + else + i1480_print_state(i1480); + goto error; + } + result = i1480_phy_fw_upload(i1480); /* PHY fw */ + if (result < 0 && result != -ENOENT) { + i1480_print_state(i1480); + goto error_rc_release; + } + /* + * FIXME: find some reliable way to check whether firmware is running + * properly. Maybe use some standard request that has no side effects? + */ + dev_info(i1480->dev, "firmware uploaded successfully\n"); +error_rc_release: + if (i1480->rc_release) + i1480->rc_release(i1480); + result = 0; +error: + return result; +} +EXPORT_SYMBOL_GPL(i1480_fw_upload); diff --git a/drivers/uwb/i1480/dfu/i1480-dfu.h b/drivers/uwb/i1480/dfu/i1480-dfu.h new file mode 100644 index 00000000000..46f45e800f3 --- /dev/null +++ b/drivers/uwb/i1480/dfu/i1480-dfu.h @@ -0,0 +1,260 @@ +/* + * i1480 Device Firmware Upload + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver is the firmware uploader for the Intel Wireless UWB + * Link 1480 device (both in the USB and PCI incarnations). + * + * The process is quite simple: we stop the device, write the firmware + * to its memory and then restart it. Wait for the device to let us + * know it is done booting firmware. Ready. + * + * We might have to upload before or after a phy firmware (which might + * be done in two methods, using a normal firmware image or through + * the MPI port). + * + * Because USB and PCI use common methods, we just make ops out of the + * common operations (read, write, wait_init_done and cmd) and + * implement them in usb.c and pci.c. + * + * The flow is (some parts omitted): + * + * i1480_{usb,pci}_probe() On enumerate/discovery + * i1480_fw_upload() + * i1480_pre_fw_upload() + * __mac_fw_upload() + * fw_hdrs_load() + * mac_fw_hdrs_push() + * i1480->write() [i1480_{usb,pci}_write()] + * i1480_fw_cmp() + * i1480->read() [i1480_{usb,pci}_read()] + * i1480_mac_fw_upload() + * __mac_fw_upload() + * i1480->setup(() + * i1480->wait_init_done() + * i1480_cmd_reset() + * i1480->cmd() [i1480_{usb,pci}_cmd()] + * ... + * i1480_phy_fw_upload() + * request_firmware() + * i1480_mpi_write() + * i1480->cmd() [i1480_{usb,pci}_cmd()] + * + * Once the probe function enumerates the device and uploads the + * firmware, we just exit with -ENODEV, as we don't really want to + * attach to the device. + */ +#ifndef __i1480_DFU_H__ +#define __i1480_DFU_H__ + +#include <linux/uwb/spec.h> +#include <linux/types.h> +#include <linux/completion.h> + +#define i1480_FW_UPLOAD_MODE_MASK (cpu_to_le32(0x00000018)) + +#if i1480_FW > 0x00000302 +#define i1480_RCEB_EXTENDED +#endif + +struct uwb_rccb; +struct uwb_rceb; + +/* + * Common firmware upload handlers + * + * Normally you embed this struct in another one specific to your hw. + * + * @write Write to device's memory from buffer. + * @read Read from device's memory to i1480->evt_buf. + * @setup Setup device after basic firmware is uploaded + * @wait_init_done + * Wait for the device to send a notification saying init + * is done. + * @cmd FOP for issuing the command to the hardware. The + * command data is contained in i1480->cmd_buf and the size + * is supplied as an argument. The command replied is put + * in i1480->evt_buf and the size in i1480->evt_result (or if + * an error, a < 0 errno code). + * + * @cmd_buf Memory buffer used to send commands to the device. + * Allocated by the upper layers i1480_fw_upload(). + * Size has to be @buf_size. + * @evt_buf Memory buffer used to place the async notifications + * received by the hw. Allocated by the upper layers + * i1480_fw_upload(). + * Size has to be @buf_size. + * @cmd_complete + * Low level driver uses this to notify code waiting afor + * an event that the event has arrived and data is in + * i1480->evt_buf (and size/result in i1480->evt_result). + * @hw_rev + * Use this value to activate dfu code to support new revisions + * of hardware. i1480_init() sets this to a default value. + * It should be updated by the USB and PCI code. + */ +struct i1480 { + struct device *dev; + + int (*write)(struct i1480 *, u32 addr, const void *, size_t); + int (*read)(struct i1480 *, u32 addr, size_t); + int (*rc_setup)(struct i1480 *); + void (*rc_release)(struct i1480 *); + int (*wait_init_done)(struct i1480 *); + int (*cmd)(struct i1480 *, const char *cmd_name, size_t cmd_size); + const char *pre_fw_name; + const char *mac_fw_name; + const char *mac_fw_name_deprecate; /* FIXME: Will go away */ + const char *phy_fw_name; + u8 hw_rev; + + size_t buf_size; /* size of both evt_buf and cmd_buf */ + void *evt_buf, *cmd_buf; + ssize_t evt_result; + struct completion evt_complete; +}; + +static inline +void i1480_init(struct i1480 *i1480) +{ + i1480->hw_rev = 1; + init_completion(&i1480->evt_complete); +} + +extern int i1480_fw_upload(struct i1480 *); +extern int i1480_pre_fw_upload(struct i1480 *); +extern int i1480_mac_fw_upload(struct i1480 *); +extern int i1480_phy_fw_upload(struct i1480 *); +extern ssize_t i1480_cmd(struct i1480 *, const char *, size_t, size_t); +extern int i1480_rceb_check(const struct i1480 *, + const struct uwb_rceb *, const char *, u8, + u8, unsigned); + +enum { + /* Vendor specific command type */ + i1480_CET_VS1 = 0xfd, + /* i1480 commands */ + i1480_CMD_SET_IP_MAS = 0x000e, + i1480_CMD_GET_MAC_PHY_INFO = 0x0003, + i1480_CMD_MPI_WRITE = 0x000f, + i1480_CMD_MPI_READ = 0x0010, + /* i1480 events */ +#if i1480_FW > 0x00000302 + i1480_EVT_CONFIRM = 0x0002, + i1480_EVT_RM_INIT_DONE = 0x0101, + i1480_EVT_DEV_ADD = 0x0103, + i1480_EVT_DEV_RM = 0x0104, + i1480_EVT_DEV_ID_CHANGE = 0x0105, + i1480_EVT_GET_MAC_PHY_INFO = i1480_CMD_GET_MAC_PHY_INFO, +#else + i1480_EVT_CONFIRM = 0x0002, + i1480_EVT_RM_INIT_DONE = 0x0101, + i1480_EVT_DEV_ADD = 0x0103, + i1480_EVT_DEV_RM = 0x0104, + i1480_EVT_DEV_ID_CHANGE = 0x0105, + i1480_EVT_GET_MAC_PHY_INFO = i1480_EVT_CONFIRM, +#endif +}; + + +struct i1480_evt_confirm { + struct uwb_rceb rceb; +#ifdef i1480_RCEB_EXTENDED + __le16 wParamLength; +#endif + u8 bResultCode; +} __attribute__((packed)); + + +struct i1480_rceb { + struct uwb_rceb rceb; +#ifdef i1480_RCEB_EXTENDED + __le16 wParamLength; +#endif +} __attribute__((packed)); + + +/** + * Get MAC & PHY Information confirm event structure + * + * Confirm event returned by the command. + */ +struct i1480_evt_confirm_GMPI { +#if i1480_FW > 0x00000302 + struct uwb_rceb rceb; + __le16 wParamLength; + __le16 status; + u8 mac_addr[6]; /* EUI-64 bit IEEE address [still 8 bytes?] */ + u8 dev_addr[2]; + __le16 mac_fw_rev; /* major = v >> 8; minor = v & 0xff */ + u8 hw_rev; + u8 phy_vendor; + u8 phy_rev; /* major v = >> 8; minor = v & 0xff */ + __le16 mac_caps; + u8 phy_caps[3]; + u8 key_stores; + __le16 mcast_addr_stores; + u8 sec_mode_supported; +#else + struct uwb_rceb rceb; + u8 status; + u8 mac_addr[8]; /* EUI-64 bit IEEE address [still 8 bytes?] */ + u8 dev_addr[2]; + __le16 mac_fw_rev; /* major = v >> 8; minor = v & 0xff */ + __le16 phy_fw_rev; /* major v = >> 8; minor = v & 0xff */ + __le16 mac_caps; + u8 phy_caps; + u8 key_stores; + __le16 mcast_addr_stores; + u8 sec_mode_supported; +#endif +} __attribute__((packed)); + + +struct i1480_cmd_mpi_write { + struct uwb_rccb rccb; + __le16 size; + u8 data[]; +}; + + +struct i1480_cmd_mpi_read { + struct uwb_rccb rccb; + __le16 size; + struct { + u8 page, offset; + } __attribute__((packed)) data[]; +} __attribute__((packed)); + + +struct i1480_evt_mpi_read { + struct uwb_rceb rceb; +#ifdef i1480_RCEB_EXTENDED + __le16 wParamLength; +#endif + u8 bResultCode; + __le16 size; + struct { + u8 page, offset, value; + } __attribute__((packed)) data[]; +} __attribute__((packed)); + + +#endif /* #ifndef __i1480_DFU_H__ */ diff --git a/drivers/uwb/i1480/dfu/mac.c b/drivers/uwb/i1480/dfu/mac.c new file mode 100644 index 00000000000..2e4d8f07c16 --- /dev/null +++ b/drivers/uwb/i1480/dfu/mac.c @@ -0,0 +1,527 @@ +/* + * Intel Wireless UWB Link 1480 + * MAC Firmware upload implementation + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Implementation of the code for parsing the firmware file (extract + * the headers and binary code chunks) in the fw_*() functions. The + * code to upload pre and mac firmwares is the same, so it uses a + * common entry point in __mac_fw_upload(), which uses the i1480 + * function pointers to push the firmware to the device. + */ +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/uwb.h> +#include "i1480-dfu.h" + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +/* + * Descriptor for a continuous segment of MAC fw data + */ +struct fw_hdr { + unsigned long address; + size_t length; + const u32 *bin; + struct fw_hdr *next; +}; + + +/* Free a chain of firmware headers */ +static +void fw_hdrs_free(struct fw_hdr *hdr) +{ + struct fw_hdr *next; + + while (hdr) { + next = hdr->next; + kfree(hdr); + hdr = next; + } +} + + +/* Fill a firmware header descriptor from a memory buffer */ +static +int fw_hdr_load(struct i1480 *i1480, struct fw_hdr *hdr, unsigned hdr_cnt, + const char *_data, const u32 *data_itr, const u32 *data_top) +{ + size_t hdr_offset = (const char *) data_itr - _data; + size_t remaining_size = (void *) data_top - (void *) data_itr; + if (data_itr + 2 > data_top) { + dev_err(i1480->dev, "fw hdr #%u/%zu: EOF reached in header at " + "offset %zu, limit %zu\n", + hdr_cnt, hdr_offset, + (const char *) data_itr + 2 - _data, + (const char *) data_top - _data); + return -EINVAL; + } + hdr->next = NULL; + hdr->address = le32_to_cpu(*data_itr++); + hdr->length = le32_to_cpu(*data_itr++); + hdr->bin = data_itr; + if (hdr->length > remaining_size) { + dev_err(i1480->dev, "fw hdr #%u/%zu: EOF reached in data; " + "chunk too long (%zu bytes), only %zu left\n", + hdr_cnt, hdr_offset, hdr->length, remaining_size); + return -EINVAL; + } + return 0; +} + + +/** + * Get a buffer where the firmware is supposed to be and create a + * chain of headers linking them together. + * + * @phdr: where to place the pointer to the first header (headers link + * to the next via the @hdr->next ptr); need to free the whole + * chain when done. + * + * @_data: Pointer to the data buffer. + * + * @_data_size: Size of the data buffer (bytes); data size has to be a + * multiple of 4. Function will fail if not. + * + * Goes over the whole binary blob; reads the first chunk and creates + * a fw hdr from it (which points to where the data is in @_data and + * the length of the chunk); then goes on to the next chunk until + * done. Each header is linked to the next. + */ +static +int fw_hdrs_load(struct i1480 *i1480, struct fw_hdr **phdr, + const char *_data, size_t data_size) +{ + int result; + unsigned hdr_cnt = 0; + u32 *data = (u32 *) _data, *data_itr, *data_top; + struct fw_hdr *hdr, **prev_hdr = phdr; + + result = -EINVAL; + /* Check size is ok and pointer is aligned */ + if (data_size % sizeof(u32) != 0) + goto error; + if ((unsigned long) _data % sizeof(u16) != 0) + goto error; + *phdr = NULL; + data_itr = data; + data_top = (u32 *) (_data + data_size); + while (data_itr < data_top) { + result = -ENOMEM; + hdr = kmalloc(sizeof(*hdr), GFP_KERNEL); + if (hdr == NULL) { + dev_err(i1480->dev, "Cannot allocate fw header " + "for chunk #%u\n", hdr_cnt); + goto error_alloc; + } + result = fw_hdr_load(i1480, hdr, hdr_cnt, + _data, data_itr, data_top); + if (result < 0) + goto error_load; + data_itr += 2 + hdr->length; + *prev_hdr = hdr; + prev_hdr = &hdr->next; + hdr_cnt++; + }; + *prev_hdr = NULL; + return 0; + +error_load: + kfree(hdr); +error_alloc: + fw_hdrs_free(*phdr); +error: + return result; +} + + +/** + * Compares a chunk of fw with one in the devices's memory + * + * @i1480: Device instance + * @hdr: Pointer to the firmware chunk + * @returns: 0 if equal, < 0 errno on error. If > 0, it is the offset + * where the difference was found (plus one). + * + * Kind of dirty and simplistic, but does the trick in both the PCI + * and USB version. We do a quick[er] memcmp(), and if it fails, we do + * a byte-by-byte to find the offset. + */ +static +ssize_t i1480_fw_cmp(struct i1480 *i1480, struct fw_hdr *hdr) +{ + ssize_t result = 0; + u32 src_itr = 0, cnt; + size_t size = hdr->length*sizeof(hdr->bin[0]); + size_t chunk_size; + u8 *bin = (u8 *) hdr->bin; + + while (size > 0) { + chunk_size = size < i1480->buf_size ? size : i1480->buf_size; + result = i1480->read(i1480, hdr->address + src_itr, chunk_size); + if (result < 0) { + dev_err(i1480->dev, "error reading for verification: " + "%zd\n", result); + goto error; + } + if (memcmp(i1480->cmd_buf, bin + src_itr, result)) { + u8 *buf = i1480->cmd_buf; + d_printf(2, i1480->dev, + "original data @ %p + %u, %zu bytes\n", + bin, src_itr, result); + d_dump(4, i1480->dev, bin + src_itr, result); + for (cnt = 0; cnt < result; cnt++) + if (bin[src_itr + cnt] != buf[cnt]) { + dev_err(i1480->dev, "byte failed at " + "src_itr %u cnt %u [0x%02x " + "vs 0x%02x]\n", src_itr, cnt, + bin[src_itr + cnt], buf[cnt]); + result = src_itr + cnt + 1; + goto cmp_failed; + } + } + src_itr += result; + size -= result; + } + result = 0; +error: +cmp_failed: + return result; +} + + +/** + * Writes firmware headers to the device. + * + * @prd: PRD instance + * @hdr: Processed firmware + * @returns: 0 if ok, < 0 errno on error. + */ +static +int mac_fw_hdrs_push(struct i1480 *i1480, struct fw_hdr *hdr, + const char *fw_name, const char *fw_tag) +{ + struct device *dev = i1480->dev; + ssize_t result = 0; + struct fw_hdr *hdr_itr; + int verif_retry_count; + + d_fnstart(3, dev, "(%p, %p)\n", i1480, hdr); + /* Now, header by header, push them to the hw */ + for (hdr_itr = hdr; hdr_itr != NULL; hdr_itr = hdr_itr->next) { + verif_retry_count = 0; +retry: + dev_dbg(dev, "fw chunk (%zu @ 0x%08lx)\n", + hdr_itr->length * sizeof(hdr_itr->bin[0]), + hdr_itr->address); + result = i1480->write(i1480, hdr_itr->address, hdr_itr->bin, + hdr_itr->length*sizeof(hdr_itr->bin[0])); + if (result < 0) { + dev_err(dev, "%s fw '%s': write failed (%zuB @ 0x%lx):" + " %zd\n", fw_tag, fw_name, + hdr_itr->length * sizeof(hdr_itr->bin[0]), + hdr_itr->address, result); + break; + } + result = i1480_fw_cmp(i1480, hdr_itr); + if (result < 0) { + dev_err(dev, "%s fw '%s': verification read " + "failed (%zuB @ 0x%lx): %zd\n", + fw_tag, fw_name, + hdr_itr->length * sizeof(hdr_itr->bin[0]), + hdr_itr->address, result); + break; + } + if (result > 0) { /* Offset where it failed + 1 */ + result--; + dev_err(dev, "%s fw '%s': WARNING: verification " + "failed at 0x%lx: retrying\n", + fw_tag, fw_name, hdr_itr->address + result); + if (++verif_retry_count < 3) + goto retry; /* write this block again! */ + dev_err(dev, "%s fw '%s': verification failed at 0x%lx: " + "tried %d times\n", fw_tag, fw_name, + hdr_itr->address + result, verif_retry_count); + result = -EINVAL; + break; + } + } + d_fnend(3, dev, "(%zd)\n", result); + return result; +} + + +/** Puts the device in firmware upload mode.*/ +static +int mac_fw_upload_enable(struct i1480 *i1480) +{ + int result; + u32 reg = 0x800000c0; + u32 *buffer = (u32 *)i1480->cmd_buf; + + if (i1480->hw_rev > 1) + reg = 0x8000d0d4; + result = i1480->read(i1480, reg, sizeof(u32)); + if (result < 0) + goto error_cmd; + *buffer &= ~i1480_FW_UPLOAD_MODE_MASK; + result = i1480->write(i1480, reg, buffer, sizeof(u32)); + if (result < 0) + goto error_cmd; + return 0; +error_cmd: + dev_err(i1480->dev, "can't enable fw upload mode: %d\n", result); + return result; +} + + +/** Gets the device out of firmware upload mode. */ +static +int mac_fw_upload_disable(struct i1480 *i1480) +{ + int result; + u32 reg = 0x800000c0; + u32 *buffer = (u32 *)i1480->cmd_buf; + + if (i1480->hw_rev > 1) + reg = 0x8000d0d4; + result = i1480->read(i1480, reg, sizeof(u32)); + if (result < 0) + goto error_cmd; + *buffer |= i1480_FW_UPLOAD_MODE_MASK; + result = i1480->write(i1480, reg, buffer, sizeof(u32)); + if (result < 0) + goto error_cmd; + return 0; +error_cmd: + dev_err(i1480->dev, "can't disable fw upload mode: %d\n", result); + return result; +} + + + +/** + * Generic function for uploading a MAC firmware. + * + * @i1480: Device instance + * @fw_name: Name of firmware file to upload. + * @fw_tag: Name of the firmware type (for messages) + * [eg: MAC, PRE] + * @do_wait: Wait for device to emit initialization done message (0 + * for PRE fws, 1 for MAC fws). + * @returns: 0 if ok, < 0 errno on error. + */ +static +int __mac_fw_upload(struct i1480 *i1480, const char *fw_name, + const char *fw_tag) +{ + int result; + const struct firmware *fw; + struct fw_hdr *fw_hdrs; + + d_fnstart(3, i1480->dev, "(%p, %s, %s)\n", i1480, fw_name, fw_tag); + result = request_firmware(&fw, fw_name, i1480->dev); + if (result < 0) /* Up to caller to complain on -ENOENT */ + goto out; + d_printf(3, i1480->dev, "%s fw '%s': uploading\n", fw_tag, fw_name); + result = fw_hdrs_load(i1480, &fw_hdrs, fw->data, fw->size); + if (result < 0) { + dev_err(i1480->dev, "%s fw '%s': failed to parse firmware " + "file: %d\n", fw_tag, fw_name, result); + goto out_release; + } + result = mac_fw_upload_enable(i1480); + if (result < 0) + goto out_hdrs_release; + result = mac_fw_hdrs_push(i1480, fw_hdrs, fw_name, fw_tag); + mac_fw_upload_disable(i1480); +out_hdrs_release: + if (result >= 0) + dev_info(i1480->dev, "%s fw '%s': uploaded\n", fw_tag, fw_name); + else + dev_err(i1480->dev, "%s fw '%s': failed to upload (%d), " + "power cycle device\n", fw_tag, fw_name, result); + fw_hdrs_free(fw_hdrs); +out_release: + release_firmware(fw); +out: + d_fnend(3, i1480->dev, "(%p, %s, %s) = %d\n", i1480, fw_name, fw_tag, + result); + return result; +} + + +/** + * Upload a pre-PHY firmware + * + */ +int i1480_pre_fw_upload(struct i1480 *i1480) +{ + int result; + result = __mac_fw_upload(i1480, i1480->pre_fw_name, "PRE"); + if (result == 0) + msleep(400); + return result; +} + + +/** + * Reset a the MAC and PHY + * + * @i1480: Device's instance + * @returns: 0 if ok, < 0 errno code on error + * + * We put the command on kmalloc'ed memory as some arches cannot do + * USB from the stack. The reply event is copied from an stage buffer, + * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details. + * + * We issue the reset to make sure the UWB controller reinits the PHY; + * this way we can now if the PHY init went ok. + */ +static +int i1480_cmd_reset(struct i1480 *i1480) +{ + int result; + struct uwb_rccb *cmd = (void *) i1480->cmd_buf; + struct i1480_evt_reset { + struct uwb_rceb rceb; + u8 bResultCode; + } __attribute__((packed)) *reply = (void *) i1480->evt_buf; + + result = -ENOMEM; + cmd->bCommandType = UWB_RC_CET_GENERAL; + cmd->wCommand = cpu_to_le16(UWB_RC_CMD_RESET); + reply->rceb.bEventType = UWB_RC_CET_GENERAL; + reply->rceb.wEvent = UWB_RC_CMD_RESET; + result = i1480_cmd(i1480, "RESET", sizeof(*cmd), sizeof(*reply)); + if (result < 0) + goto out; + if (reply->bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(i1480->dev, "RESET: command execution failed: %u\n", + reply->bResultCode); + result = -EIO; + } +out: + return result; + +} + + +/* Wait for the MAC FW to start running */ +static +int i1480_fw_is_running_q(struct i1480 *i1480) +{ + int cnt = 0; + int result; + u32 *val = (u32 *) i1480->cmd_buf; + + d_fnstart(3, i1480->dev, "(i1480 %p)\n", i1480); + for (cnt = 0; cnt < 10; cnt++) { + msleep(100); + result = i1480->read(i1480, 0x80080000, 4); + if (result < 0) { + dev_err(i1480->dev, "Can't read 0x8008000: %d\n", result); + goto out; + } + if (*val == 0x55555555UL) /* fw running? cool */ + goto out; + } + dev_err(i1480->dev, "Timed out waiting for fw to start\n"); + result = -ETIMEDOUT; +out: + d_fnend(3, i1480->dev, "(i1480 %p) = %d\n", i1480, result); + return result; + +} + + +/** + * Upload MAC firmware, wait for it to start + * + * @i1480: Device instance + * @fw_name: Name of the file that contains the firmware + * + * This has to be called after the pre fw has been uploaded (if + * there is any). + */ +int i1480_mac_fw_upload(struct i1480 *i1480) +{ + int result = 0, deprecated_name = 0; + struct i1480_rceb *rcebe = (void *) i1480->evt_buf; + + d_fnstart(3, i1480->dev, "(%p)\n", i1480); + result = __mac_fw_upload(i1480, i1480->mac_fw_name, "MAC"); + if (result == -ENOENT) { + result = __mac_fw_upload(i1480, i1480->mac_fw_name_deprecate, + "MAC"); + deprecated_name = 1; + } + if (result < 0) + return result; + if (deprecated_name == 1) + dev_warn(i1480->dev, + "WARNING: firmware file name %s is deprecated, " + "please rename to %s\n", + i1480->mac_fw_name_deprecate, i1480->mac_fw_name); + result = i1480_fw_is_running_q(i1480); + if (result < 0) + goto error_fw_not_running; + result = i1480->rc_setup ? i1480->rc_setup(i1480) : 0; + if (result < 0) { + dev_err(i1480->dev, "Cannot setup after MAC fw upload: %d\n", + result); + goto error_setup; + } + result = i1480->wait_init_done(i1480); /* wait init'on */ + if (result < 0) { + dev_err(i1480->dev, "MAC fw '%s': Initialization timed out " + "(%d)\n", i1480->mac_fw_name, result); + goto error_init_timeout; + } + /* verify we got the right initialization done event */ + if (i1480->evt_result != sizeof(*rcebe)) { + dev_err(i1480->dev, "MAC fw '%s': initialization event returns " + "wrong size (%zu bytes vs %zu needed)\n", + i1480->mac_fw_name, i1480->evt_result, sizeof(*rcebe)); + dump_bytes(i1480->dev, rcebe, min(i1480->evt_result, (ssize_t)32)); + goto error_size; + } + result = -EIO; + if (i1480_rceb_check(i1480, &rcebe->rceb, NULL, 0, i1480_CET_VS1, + i1480_EVT_RM_INIT_DONE) < 0) { + dev_err(i1480->dev, "wrong initialization event 0x%02x/%04x/%02x " + "received; expected 0x%02x/%04x/00\n", + rcebe->rceb.bEventType, le16_to_cpu(rcebe->rceb.wEvent), + rcebe->rceb.bEventContext, i1480_CET_VS1, + i1480_EVT_RM_INIT_DONE); + goto error_init_timeout; + } + result = i1480_cmd_reset(i1480); + if (result < 0) + dev_err(i1480->dev, "MAC fw '%s': MBOA reset failed (%d)\n", + i1480->mac_fw_name, result); +error_fw_not_running: +error_init_timeout: +error_size: +error_setup: + d_fnend(3, i1480->dev, "(i1480 %p) = %d\n", i1480, result); + return result; +} diff --git a/drivers/uwb/i1480/dfu/phy.c b/drivers/uwb/i1480/dfu/phy.c new file mode 100644 index 00000000000..3b1a87de8e6 --- /dev/null +++ b/drivers/uwb/i1480/dfu/phy.c @@ -0,0 +1,203 @@ +/* + * Intel Wireless UWB Link 1480 + * PHY parameters upload + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Code for uploading the PHY parameters to the PHY through the UWB + * Radio Control interface. + * + * We just send the data through the MPI interface using HWA-like + * commands and then reset the PHY to make sure it is ok. + */ +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/usb/wusb.h> +#include "i1480-dfu.h" + + +/** + * Write a value array to an address of the MPI interface + * + * @i1480: Device descriptor + * @data: Data array to write + * @size: Size of the data array + * @returns: 0 if ok, < 0 errno code on error. + * + * The data array is organized into pairs: + * + * ADDRESS VALUE + * + * ADDRESS is BE 16 bit unsigned, VALUE 8 bit unsigned. Size thus has + * to be a multiple of three. + */ +static +int i1480_mpi_write(struct i1480 *i1480, const void *data, size_t size) +{ + int result; + struct i1480_cmd_mpi_write *cmd = i1480->cmd_buf; + struct i1480_evt_confirm *reply = i1480->evt_buf; + + BUG_ON(size > 480); + result = -ENOMEM; + cmd->rccb.bCommandType = i1480_CET_VS1; + cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_WRITE); + cmd->size = cpu_to_le16(size); + memcpy(cmd->data, data, size); + reply->rceb.bEventType = i1480_CET_VS1; + reply->rceb.wEvent = i1480_CMD_MPI_WRITE; + result = i1480_cmd(i1480, "MPI-WRITE", sizeof(*cmd) + size, sizeof(*reply)); + if (result < 0) + goto out; + if (reply->bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(i1480->dev, "MPI-WRITE: command execution failed: %d\n", + reply->bResultCode); + result = -EIO; + } +out: + return result; +} + + +/** + * Read a value array to from an address of the MPI interface + * + * @i1480: Device descriptor + * @data: where to place the read array + * @srcaddr: Where to read from + * @size: Size of the data read array + * @returns: 0 if ok, < 0 errno code on error. + * + * The command data array is organized into pairs ADDR0 ADDR1..., and + * the returned data in ADDR0 VALUE0 ADDR1 VALUE1... + * + * We generate the command array to be a sequential read and then + * rearrange the result. + * + * We use the i1480->cmd_buf for the command, i1480->evt_buf for the reply. + * + * As the reply has to fit in 512 bytes (i1480->evt_buffer), the max amount + * of values we can read is (512 - sizeof(*reply)) / 3 + */ +static +int i1480_mpi_read(struct i1480 *i1480, u8 *data, u16 srcaddr, size_t size) +{ + int result; + struct i1480_cmd_mpi_read *cmd = i1480->cmd_buf; + struct i1480_evt_mpi_read *reply = i1480->evt_buf; + unsigned cnt; + + memset(i1480->cmd_buf, 0x69, 512); + memset(i1480->evt_buf, 0x69, 512); + + BUG_ON(size > (i1480->buf_size - sizeof(*reply)) / 3); + result = -ENOMEM; + cmd->rccb.bCommandType = i1480_CET_VS1; + cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_READ); + cmd->size = cpu_to_le16(3*size); + for (cnt = 0; cnt < size; cnt++) { + cmd->data[cnt].page = (srcaddr + cnt) >> 8; + cmd->data[cnt].offset = (srcaddr + cnt) & 0xff; + } + reply->rceb.bEventType = i1480_CET_VS1; + reply->rceb.wEvent = i1480_CMD_MPI_READ; + result = i1480_cmd(i1480, "MPI-READ", sizeof(*cmd) + 2*size, + sizeof(*reply) + 3*size); + if (result < 0) + goto out; + if (reply->bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(i1480->dev, "MPI-READ: command execution failed: %d\n", + reply->bResultCode); + result = -EIO; + } + for (cnt = 0; cnt < size; cnt++) { + if (reply->data[cnt].page != (srcaddr + cnt) >> 8) + dev_err(i1480->dev, "MPI-READ: page inconsistency at " + "index %u: expected 0x%02x, got 0x%02x\n", cnt, + (srcaddr + cnt) >> 8, reply->data[cnt].page); + if (reply->data[cnt].offset != ((srcaddr + cnt) & 0x00ff)) + dev_err(i1480->dev, "MPI-READ: offset inconsistency at " + "index %u: expected 0x%02x, got 0x%02x\n", cnt, + (srcaddr + cnt) & 0x00ff, + reply->data[cnt].offset); + data[cnt] = reply->data[cnt].value; + } + result = 0; +out: + return result; +} + + +/** + * Upload a PHY firmware, wait for it to start + * + * @i1480: Device instance + * @fw_name: Name of the file that contains the firmware + * + * We assume the MAC fw is up and running. This means we can use the + * MPI interface to write the PHY firmware. Once done, we issue an + * MBOA Reset, which will force the MAC to reset and reinitialize the + * PHY. If that works, we are ready to go. + * + * Max packet size for the MPI write is 512, so the max buffer is 480 + * (which gives us 160 byte triads of MSB, LSB and VAL for the data). + */ +int i1480_phy_fw_upload(struct i1480 *i1480) +{ + int result; + const struct firmware *fw; + const char *data_itr, *data_top; + const size_t MAX_BLK_SIZE = 480; /* 160 triads */ + size_t data_size; + u8 phy_stat; + + result = request_firmware(&fw, i1480->phy_fw_name, i1480->dev); + if (result < 0) + goto out; + /* Loop writing data in chunks as big as possible until done. */ + for (data_itr = fw->data, data_top = data_itr + fw->size; + data_itr < data_top; data_itr += MAX_BLK_SIZE) { + data_size = min(MAX_BLK_SIZE, (size_t) (data_top - data_itr)); + result = i1480_mpi_write(i1480, data_itr, data_size); + if (result < 0) + goto error_mpi_write; + } + /* Read MPI page 0, offset 6; if 0, PHY was initialized correctly. */ + result = i1480_mpi_read(i1480, &phy_stat, 0x0006, 1); + if (result < 0) { + dev_err(i1480->dev, "PHY: can't get status: %d\n", result); + goto error_mpi_status; + } + if (phy_stat != 0) { + result = -ENODEV; + dev_info(i1480->dev, "error, PHY not ready: %u\n", phy_stat); + goto error_phy_status; + } + dev_info(i1480->dev, "PHY fw '%s': uploaded\n", i1480->phy_fw_name); +error_phy_status: +error_mpi_status: +error_mpi_write: + release_firmware(fw); + if (result < 0) + dev_err(i1480->dev, "PHY fw '%s': failed to upload (%d), " + "power cycle device\n", i1480->phy_fw_name, result); +out: + return result; +} diff --git a/drivers/uwb/i1480/dfu/usb.c b/drivers/uwb/i1480/dfu/usb.c new file mode 100644 index 00000000000..98eeeff051a --- /dev/null +++ b/drivers/uwb/i1480/dfu/usb.c @@ -0,0 +1,500 @@ +/* + * Intel Wireless UWB Link 1480 + * USB SKU firmware upload implementation + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver will prepare the i1480 device to behave as a real + * Wireless USB HWA adaptor by uploading the firmware. + * + * When the device is connected or driver is loaded, i1480_usb_probe() + * is called--this will allocate and initialize the device structure, + * fill in the pointers to the common functions (read, write, + * wait_init_done and cmd for HWA command execution) and once that is + * done, call the common firmware uploading routine. Then clean up and + * return -ENODEV, as we don't attach to the device. + * + * The rest are the basic ops we implement that the fw upload code + * uses to do its job. All the ops in the common code are i1480->NAME, + * the functions are i1480_usb_NAME(). + */ +#include <linux/module.h> +#include <linux/version.h> +#include <linux/usb.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/uwb.h> +#include <linux/usb/wusb.h> +#include <linux/usb/wusb-wa.h> +#include "i1480-dfu.h" + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + + +struct i1480_usb { + struct i1480 i1480; + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + struct urb *neep_urb; /* URB for reading from EP1 */ +}; + + +static +void i1480_usb_init(struct i1480_usb *i1480_usb) +{ + i1480_init(&i1480_usb->i1480); +} + + +static +int i1480_usb_create(struct i1480_usb *i1480_usb, struct usb_interface *iface) +{ + struct usb_device *usb_dev = interface_to_usbdev(iface); + int result = -ENOMEM; + + i1480_usb->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */ + i1480_usb->usb_iface = usb_get_intf(iface); + usb_set_intfdata(iface, i1480_usb); /* Bind the driver to iface0 */ + i1480_usb->neep_urb = usb_alloc_urb(0, GFP_KERNEL); + if (i1480_usb->neep_urb == NULL) + goto error; + return 0; + +error: + usb_set_intfdata(iface, NULL); + usb_put_intf(iface); + usb_put_dev(usb_dev); + return result; +} + + +static +void i1480_usb_destroy(struct i1480_usb *i1480_usb) +{ + usb_kill_urb(i1480_usb->neep_urb); + usb_free_urb(i1480_usb->neep_urb); + usb_set_intfdata(i1480_usb->usb_iface, NULL); + usb_put_intf(i1480_usb->usb_iface); + usb_put_dev(i1480_usb->usb_dev); +} + + +/** + * Write a buffer to a memory address in the i1480 device + * + * @i1480: i1480 instance + * @memory_address: + * Address where to write the data buffer to. + * @buffer: Buffer to the data + * @size: Size of the buffer [has to be < 512]. + * @returns: 0 if ok, < 0 errno code on error. + * + * Data buffers to USB cannot be on the stack or in vmalloc'ed areas, + * so we copy it to the local i1480 buffer before proceeding. In any + * case, we have a max size we can send, soooo. + */ +static +int i1480_usb_write(struct i1480 *i1480, u32 memory_address, + const void *buffer, size_t size) +{ + int result = 0; + struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480); + size_t buffer_size, itr = 0; + + d_fnstart(3, i1480->dev, "(%p, 0x%08x, %p, %zu)\n", + i1480, memory_address, buffer, size); + BUG_ON(size & 0x3); /* Needs to be a multiple of 4 */ + while (size > 0) { + buffer_size = size < i1480->buf_size ? size : i1480->buf_size; + memcpy(i1480->cmd_buf, buffer + itr, buffer_size); + result = usb_control_msg( + i1480_usb->usb_dev, usb_sndctrlpipe(i1480_usb->usb_dev, 0), + 0xf0, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + cpu_to_le16(memory_address & 0xffff), + cpu_to_le16((memory_address >> 16) & 0xffff), + i1480->cmd_buf, buffer_size, 100 /* FIXME: arbitrary */); + if (result < 0) + break; + d_printf(3, i1480->dev, + "wrote @ 0x%08x %u bytes (of %zu bytes requested)\n", + memory_address, result, buffer_size); + d_dump(4, i1480->dev, i1480->cmd_buf, result); + itr += result; + memory_address += result; + size -= result; + } + d_fnend(3, i1480->dev, "(%p, 0x%08x, %p, %zu) = %d\n", + i1480, memory_address, buffer, size, result); + return result; +} + + +/** + * Read a block [max size 512] of the device's memory to @i1480's buffer. + * + * @i1480: i1480 instance + * @memory_address: + * Address where to read from. + * @size: Size to read. Smaller than or equal to 512. + * @returns: >= 0 number of bytes written if ok, < 0 errno code on error. + * + * NOTE: if the memory address or block is incorrect, you might get a + * stall or a different memory read. Caller has to verify the + * memory address and size passed back in the @neh structure. + */ +static +int i1480_usb_read(struct i1480 *i1480, u32 addr, size_t size) +{ + ssize_t result = 0, bytes = 0; + size_t itr, read_size = i1480->buf_size; + struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480); + + d_fnstart(3, i1480->dev, "(%p, 0x%08x, %zu)\n", + i1480, addr, size); + BUG_ON(size > i1480->buf_size); + BUG_ON(size & 0x3); /* Needs to be a multiple of 4 */ + BUG_ON(read_size > 512); + + if (addr >= 0x8000d200 && addr < 0x8000d400) /* Yeah, HW quirk */ + read_size = 4; + + for (itr = 0; itr < size; itr += read_size) { + size_t itr_addr = addr + itr; + size_t itr_size = min(read_size, size - itr); + result = usb_control_msg( + i1480_usb->usb_dev, usb_rcvctrlpipe(i1480_usb->usb_dev, 0), + 0xf0, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + cpu_to_le16(itr_addr & 0xffff), + cpu_to_le16((itr_addr >> 16) & 0xffff), + i1480->cmd_buf + itr, itr_size, + 100 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(i1480->dev, "%s: USB read error: %zd\n", + __func__, result); + goto out; + } + if (result != itr_size) { + result = -EIO; + dev_err(i1480->dev, + "%s: partial read got only %zu bytes vs %zu expected\n", + __func__, result, itr_size); + goto out; + } + bytes += result; + } + result = bytes; +out: + d_fnend(3, i1480->dev, "(%p, 0x%08x, %zu) = %zd\n", + i1480, addr, size, result); + if (result > 0) + d_dump(4, i1480->dev, i1480->cmd_buf, result); + return result; +} + + +/** + * Callback for reads on the notification/event endpoint + * + * Just enables the completion read handler. + */ +static +void i1480_usb_neep_cb(struct urb *urb) +{ + struct i1480 *i1480 = urb->context; + struct device *dev = i1480->dev; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + dev_dbg(dev, "NEEP: reset/noent %d\n", urb->status); + break; + case -ESHUTDOWN: /* going away! */ + dev_dbg(dev, "NEEP: down %d\n", urb->status); + break; + default: + dev_err(dev, "NEEP: unknown status %d\n", urb->status); + break; + } + i1480->evt_result = urb->actual_length; + complete(&i1480->evt_complete); + return; +} + + +/** + * Wait for the MAC FW to initialize + * + * MAC FW sends a 0xfd/0101/00 notification to EP1 when done + * initializing. Get that notification into i1480->evt_buf; upper layer + * will verify it. + * + * Set i1480->evt_result with the result of getting the event or its + * size (if succesful). + * + * Delivers the data directly to i1480->evt_buf + */ +static +int i1480_usb_wait_init_done(struct i1480 *i1480) +{ + int result; + struct device *dev = i1480->dev; + struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480); + struct usb_endpoint_descriptor *epd; + + d_fnstart(3, dev, "(%p)\n", i1480); + init_completion(&i1480->evt_complete); + i1480->evt_result = -EINPROGRESS; + epd = &i1480_usb->usb_iface->cur_altsetting->endpoint[0].desc; + usb_fill_int_urb(i1480_usb->neep_urb, i1480_usb->usb_dev, + usb_rcvintpipe(i1480_usb->usb_dev, epd->bEndpointAddress), + i1480->evt_buf, i1480->buf_size, + i1480_usb_neep_cb, i1480, epd->bInterval); + result = usb_submit_urb(i1480_usb->neep_urb, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "init done: cannot submit NEEP read: %d\n", + result); + goto error_submit; + } + /* Wait for the USB callback to get the data */ + result = wait_for_completion_interruptible_timeout( + &i1480->evt_complete, HZ); + if (result <= 0) { + result = result == 0 ? -ETIMEDOUT : result; + goto error_wait; + } + usb_kill_urb(i1480_usb->neep_urb); + d_fnend(3, dev, "(%p) = 0\n", i1480); + return 0; + +error_wait: + usb_kill_urb(i1480_usb->neep_urb); +error_submit: + i1480->evt_result = result; + d_fnend(3, dev, "(%p) = %d\n", i1480, result); + return result; +} + + +/** + * Generic function for issuing commands to the i1480 + * + * @i1480: i1480 instance + * @cmd_name: Name of the command (for error messages) + * @cmd: Pointer to command buffer + * @cmd_size: Size of the command buffer + * @reply: Buffer for the reply event + * @reply_size: Expected size back (including RCEB); the reply buffer + * is assumed to be as big as this. + * @returns: >= 0 size of the returned event data if ok, + * < 0 errno code on error. + * + * Arms the NE handle, issues the command to the device and checks the + * basics of the reply event. + */ +static +int i1480_usb_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size) +{ + int result; + struct device *dev = i1480->dev; + struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480); + struct usb_endpoint_descriptor *epd; + struct uwb_rccb *cmd = i1480->cmd_buf; + u8 iface_no; + + d_fnstart(3, dev, "(%p, %s, %zu)\n", i1480, cmd_name, cmd_size); + /* Post a read on the notification & event endpoint */ + iface_no = i1480_usb->usb_iface->cur_altsetting->desc.bInterfaceNumber; + epd = &i1480_usb->usb_iface->cur_altsetting->endpoint[0].desc; + usb_fill_int_urb( + i1480_usb->neep_urb, i1480_usb->usb_dev, + usb_rcvintpipe(i1480_usb->usb_dev, epd->bEndpointAddress), + i1480->evt_buf, i1480->buf_size, + i1480_usb_neep_cb, i1480, epd->bInterval); + result = usb_submit_urb(i1480_usb->neep_urb, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "%s: cannot submit NEEP read: %d\n", + cmd_name, result); + goto error_submit_ep1; + } + /* Now post the command on EP0 */ + result = usb_control_msg( + i1480_usb->usb_dev, usb_sndctrlpipe(i1480_usb->usb_dev, 0), + WA_EXEC_RC_CMD, + USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS, + 0, iface_no, + cmd, cmd_size, + 100 /* FIXME: this is totally arbitrary */); + if (result < 0) { + dev_err(dev, "%s: control request failed: %d\n", + cmd_name, result); + goto error_submit_ep0; + } + d_fnend(3, dev, "(%p, %s, %zu) = %d\n", + i1480, cmd_name, cmd_size, result); + return result; + +error_submit_ep0: + usb_kill_urb(i1480_usb->neep_urb); +error_submit_ep1: + d_fnend(3, dev, "(%p, %s, %zu) = %d\n", + i1480, cmd_name, cmd_size, result); + return result; +} + + +/* + * Probe a i1480 device for uploading firmware. + * + * We attach only to interface #0, which is the radio control interface. + */ +static +int i1480_usb_probe(struct usb_interface *iface, const struct usb_device_id *id) +{ + struct i1480_usb *i1480_usb; + struct i1480 *i1480; + struct device *dev = &iface->dev; + int result; + + result = -ENODEV; + if (iface->cur_altsetting->desc.bInterfaceNumber != 0) { + dev_dbg(dev, "not attaching to iface %d\n", + iface->cur_altsetting->desc.bInterfaceNumber); + goto error; + } + if (iface->num_altsetting > 1 + && interface_to_usbdev(iface)->descriptor.idProduct == 0xbabe) { + /* Need altsetting #1 [HW QUIRK] or EP1 won't work */ + result = usb_set_interface(interface_to_usbdev(iface), 0, 1); + if (result < 0) + dev_warn(dev, + "can't set altsetting 1 on iface 0: %d\n", + result); + } + + result = -ENOMEM; + i1480_usb = kzalloc(sizeof(*i1480_usb), GFP_KERNEL); + if (i1480_usb == NULL) { + dev_err(dev, "Unable to allocate instance\n"); + goto error; + } + i1480_usb_init(i1480_usb); + + i1480 = &i1480_usb->i1480; + i1480->buf_size = 512; + i1480->cmd_buf = kmalloc(2 * i1480->buf_size, GFP_KERNEL); + if (i1480->cmd_buf == NULL) { + dev_err(dev, "Cannot allocate transfer buffers\n"); + result = -ENOMEM; + goto error_buf_alloc; + } + i1480->evt_buf = i1480->cmd_buf + i1480->buf_size; + + result = i1480_usb_create(i1480_usb, iface); + if (result < 0) { + dev_err(dev, "Cannot create instance: %d\n", result); + goto error_create; + } + + /* setup the fops and upload the firmare */ + i1480->pre_fw_name = "i1480-pre-phy-0.0.bin"; + i1480->mac_fw_name = "i1480-usb-0.0.bin"; + i1480->mac_fw_name_deprecate = "ptc-0.0.bin"; + i1480->phy_fw_name = "i1480-phy-0.0.bin"; + i1480->dev = &iface->dev; + i1480->write = i1480_usb_write; + i1480->read = i1480_usb_read; + i1480->rc_setup = NULL; + i1480->wait_init_done = i1480_usb_wait_init_done; + i1480->cmd = i1480_usb_cmd; + + result = i1480_fw_upload(&i1480_usb->i1480); /* the real thing */ + if (result >= 0) { + usb_reset_device(i1480_usb->usb_dev); + result = -ENODEV; /* we don't want to bind to the iface */ + } + i1480_usb_destroy(i1480_usb); +error_create: + kfree(i1480->cmd_buf); +error_buf_alloc: + kfree(i1480_usb); +error: + return result; +} + +#define i1480_USB_DEV(v, p) \ +{ \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE \ + | USB_DEVICE_ID_MATCH_DEV_INFO \ + | USB_DEVICE_ID_MATCH_INT_INFO, \ + .idVendor = (v), \ + .idProduct = (p), \ + .bDeviceClass = 0xff, \ + .bDeviceSubClass = 0xff, \ + .bDeviceProtocol = 0xff, \ + .bInterfaceClass = 0xff, \ + .bInterfaceSubClass = 0xff, \ + .bInterfaceProtocol = 0xff, \ +} + + +/** USB device ID's that we handle */ +static struct usb_device_id i1480_usb_id_table[] = { + i1480_USB_DEV(0x8086, 0xdf3b), + i1480_USB_DEV(0x15a9, 0x0005), + i1480_USB_DEV(0x07d1, 0x3802), + i1480_USB_DEV(0x050d, 0x305a), + i1480_USB_DEV(0x3495, 0x3007), + {}, +}; +MODULE_DEVICE_TABLE(usb, i1480_usb_id_table); + + +static struct usb_driver i1480_dfu_driver = { + .name = "i1480-dfu-usb", + .id_table = i1480_usb_id_table, + .probe = i1480_usb_probe, + .disconnect = NULL, +}; + + +/* + * Initialize the i1480 DFU driver. + * + * We also need to register our function for guessing event sizes. + */ +static int __init i1480_dfu_driver_init(void) +{ + return usb_register(&i1480_dfu_driver); +} +module_init(i1480_dfu_driver_init); + + +static void __exit i1480_dfu_driver_exit(void) +{ + usb_deregister(&i1480_dfu_driver); +} +module_exit(i1480_dfu_driver_exit); + + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("Intel Wireless UWB Link 1480 firmware uploader for USB"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/i1480/i1480-est.c b/drivers/uwb/i1480/i1480-est.c new file mode 100644 index 00000000000..7bf8c6febae --- /dev/null +++ b/drivers/uwb/i1480/i1480-est.c @@ -0,0 +1,99 @@ +/* + * Intel Wireless UWB Link 1480 + * Event Size tables for Wired Adaptors + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/uwb.h> +#include "dfu/i1480-dfu.h" + + +/** Event size table for wEvents 0x00XX */ +static struct uwb_est_entry i1480_est_fd00[] = { + /* Anybody expecting this response has to use + * neh->extra_size to specify the real size that will + * come back. */ + [i1480_EVT_CONFIRM] = { .size = sizeof(struct i1480_evt_confirm) }, + [i1480_CMD_SET_IP_MAS] = { .size = sizeof(struct i1480_evt_confirm) }, +#ifdef i1480_RCEB_EXTENDED + [0x09] = { + .size = sizeof(struct i1480_rceb), + .offset = 1 + offsetof(struct i1480_rceb, wParamLength), + }, +#endif +}; + +/** Event size table for wEvents 0x01XX */ +static struct uwb_est_entry i1480_est_fd01[] = { + [0xff & i1480_EVT_RM_INIT_DONE] = { .size = sizeof(struct i1480_rceb) }, + [0xff & i1480_EVT_DEV_ADD] = { .size = sizeof(struct i1480_rceb) + 9 }, + [0xff & i1480_EVT_DEV_RM] = { .size = sizeof(struct i1480_rceb) + 9 }, + [0xff & i1480_EVT_DEV_ID_CHANGE] = { + .size = sizeof(struct i1480_rceb) + 2 }, +}; + +static int i1480_est_init(void) +{ + int result = uwb_est_register(i1480_CET_VS1, 0x00, 0x8086, 0x0c3b, + i1480_est_fd00, + ARRAY_SIZE(i1480_est_fd00)); + if (result < 0) { + printk(KERN_ERR "Can't register EST table fd00: %d\n", result); + return result; + } + result = uwb_est_register(i1480_CET_VS1, 0x01, 0x8086, 0x0c3b, + i1480_est_fd01, ARRAY_SIZE(i1480_est_fd01)); + if (result < 0) { + printk(KERN_ERR "Can't register EST table fd01: %d\n", result); + return result; + } + return 0; +} +module_init(i1480_est_init); + +static void i1480_est_exit(void) +{ + uwb_est_unregister(i1480_CET_VS1, 0x00, 0x8086, 0x0c3b, + i1480_est_fd00, ARRAY_SIZE(i1480_est_fd00)); + uwb_est_unregister(i1480_CET_VS1, 0x01, 0x8086, 0x0c3b, + i1480_est_fd01, ARRAY_SIZE(i1480_est_fd01)); +} +module_exit(i1480_est_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("i1480's Vendor Specific Event Size Tables"); +MODULE_LICENSE("GPL"); + +/** + * USB device ID's that we handle + * + * [so we are loaded when this kind device is connected] + */ +static struct usb_device_id i1480_est_id_table[] = { + { USB_DEVICE(0x8086, 0xdf3b), }, + { USB_DEVICE(0x8086, 0x0c3b), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, i1480_est_id_table); diff --git a/drivers/uwb/i1480/i1480-wlp.h b/drivers/uwb/i1480/i1480-wlp.h new file mode 100644 index 00000000000..18a8b0e4567 --- /dev/null +++ b/drivers/uwb/i1480/i1480-wlp.h @@ -0,0 +1,200 @@ +/* + * Intel 1480 Wireless UWB Link + * WLP specific definitions + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#ifndef __i1480_wlp_h__ +#define __i1480_wlp_h__ + +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/uwb.h> +#include <linux/if_ether.h> +#include <asm/byteorder.h> + +/* New simplified header format? */ +#undef WLP_HDR_FMT_2 /* FIXME: rename */ + +/** + * Values of the Delivery ID & Type field when PCA or DRP + * + * The Delivery ID & Type field in the WLP TX header indicates whether + * the frame is PCA or DRP. This is done based on the high level bit of + * this field. + * We use this constant to test if the traffic is PCA or DRP as follows: + * if (wlp_tx_hdr_delivery_id_type(wlp_tx_hdr) & WLP_DRP) + * this is DRP traffic + * else + * this is PCA traffic + */ +enum deliver_id_type_bit { + WLP_DRP = 8, +}; + +/** + * WLP TX header + * + * Indicates UWB/WLP-specific transmission parameters for a network + * packet. + */ +struct wlp_tx_hdr { + /* dword 0 */ + struct uwb_dev_addr dstaddr; + u8 key_index; + u8 mac_params; + /* dword 1 */ + u8 phy_params; +#ifndef WLP_HDR_FMT_2 + u8 reserved; + __le16 oui01; /* FIXME: not so sure if __le16 or u8[2] */ + /* dword 2 */ + u8 oui2; /* if all LE, it could be merged */ + __le16 prid; +#endif +} __attribute__((packed)); + +static inline int wlp_tx_hdr_delivery_id_type(const struct wlp_tx_hdr *hdr) +{ + return hdr->mac_params & 0x0f; +} + +static inline int wlp_tx_hdr_ack_policy(const struct wlp_tx_hdr *hdr) +{ + return (hdr->mac_params >> 4) & 0x07; +} + +static inline int wlp_tx_hdr_rts_cts(const struct wlp_tx_hdr *hdr) +{ + return (hdr->mac_params >> 7) & 0x01; +} + +static inline void wlp_tx_hdr_set_delivery_id_type(struct wlp_tx_hdr *hdr, int id) +{ + hdr->mac_params = (hdr->mac_params & ~0x0f) | id; +} + +static inline void wlp_tx_hdr_set_ack_policy(struct wlp_tx_hdr *hdr, + enum uwb_ack_pol policy) +{ + hdr->mac_params = (hdr->mac_params & ~0x70) | (policy << 4); +} + +static inline void wlp_tx_hdr_set_rts_cts(struct wlp_tx_hdr *hdr, int rts_cts) +{ + hdr->mac_params = (hdr->mac_params & ~0x80) | (rts_cts << 7); +} + +static inline enum uwb_phy_rate wlp_tx_hdr_phy_rate(const struct wlp_tx_hdr *hdr) +{ + return hdr->phy_params & 0x0f; +} + +static inline int wlp_tx_hdr_tx_power(const struct wlp_tx_hdr *hdr) +{ + return (hdr->phy_params >> 4) & 0x0f; +} + +static inline void wlp_tx_hdr_set_phy_rate(struct wlp_tx_hdr *hdr, enum uwb_phy_rate rate) +{ + hdr->phy_params = (hdr->phy_params & ~0x0f) | rate; +} + +static inline void wlp_tx_hdr_set_tx_power(struct wlp_tx_hdr *hdr, int pwr) +{ + hdr->phy_params = (hdr->phy_params & ~0xf0) | (pwr << 4); +} + + +/** + * WLP RX header + * + * Provides UWB/WLP-specific transmission data for a received + * network packet. + */ +struct wlp_rx_hdr { + /* dword 0 */ + struct uwb_dev_addr dstaddr; + struct uwb_dev_addr srcaddr; + /* dword 1 */ + u8 LQI; + s8 RSSI; + u8 reserved3; +#ifndef WLP_HDR_FMT_2 + u8 oui0; + /* dword 2 */ + __le16 oui12; + __le16 prid; +#endif +} __attribute__((packed)); + + +/** User configurable options for WLP */ +struct wlp_options { + struct mutex mutex; /* access to user configurable options*/ + struct wlp_tx_hdr def_tx_hdr; /* default tx hdr */ + u8 pca_base_priority; + u8 bw_alloc; /*index into bw_allocs[] for PCA/DRP reservations*/ +}; + + +static inline +void wlp_options_init(struct wlp_options *options) +{ + mutex_init(&options->mutex); + wlp_tx_hdr_set_ack_policy(&options->def_tx_hdr, UWB_ACK_INM); + wlp_tx_hdr_set_rts_cts(&options->def_tx_hdr, 1); + /* FIXME: default to phy caps */ + wlp_tx_hdr_set_phy_rate(&options->def_tx_hdr, UWB_PHY_RATE_480); +#ifndef WLP_HDR_FMT_2 + options->def_tx_hdr.prid = cpu_to_le16(0x0000); +#endif +} + + +/* sysfs helpers */ + +extern ssize_t uwb_pca_base_priority_store(struct wlp_options *, + const char *, size_t); +extern ssize_t uwb_pca_base_priority_show(const struct wlp_options *, char *); +extern ssize_t uwb_bw_alloc_store(struct wlp_options *, const char *, size_t); +extern ssize_t uwb_bw_alloc_show(const struct wlp_options *, char *); +extern ssize_t uwb_ack_policy_store(struct wlp_options *, + const char *, size_t); +extern ssize_t uwb_ack_policy_show(const struct wlp_options *, char *); +extern ssize_t uwb_rts_cts_store(struct wlp_options *, const char *, size_t); +extern ssize_t uwb_rts_cts_show(const struct wlp_options *, char *); +extern ssize_t uwb_phy_rate_store(struct wlp_options *, const char *, size_t); +extern ssize_t uwb_phy_rate_show(const struct wlp_options *, char *); + + +/** Simple bandwidth allocation (temporary and too simple) */ +struct wlp_bw_allocs { + const char *name; + struct { + u8 mask, stream; + } tx, rx; +}; + + +#endif /* #ifndef __i1480_wlp_h__ */ diff --git a/drivers/uwb/i1480/i1480u-wlp/Makefile b/drivers/uwb/i1480/i1480u-wlp/Makefile new file mode 100644 index 00000000000..fe6709b8e68 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_UWB_I1480U_WLP) += i1480u-wlp.o + +i1480u-wlp-objs := \ + lc.o \ + netdev.o \ + rx.o \ + sysfs.o \ + tx.o diff --git a/drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h b/drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h new file mode 100644 index 00000000000..5f1b2951bb8 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h @@ -0,0 +1,284 @@ +/* + * Intel 1480 Wireless UWB Link USB + * Header formats, constants, general internal interfaces + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This is not an standard interface. + * + * FIXME: docs + * + * i1480u-wlp is pretty simple: two endpoints, one for tx, one for + * rx. rx is polled. Network packets (ethernet, whatever) are wrapped + * in i1480 TX or RX headers (for sending over the air), and these + * packets are wrapped in UNTD headers (for sending to the WLP UWB + * controller). + * + * UNTD packets (UNTD hdr + i1480 hdr + network packet) packets + * cannot be bigger than i1480u_MAX_FRG_SIZE. When this happens, the + * i1480 packet is broken in chunks/packets: + * + * UNTD-1st.hdr + i1480.hdr + payload + * UNTD-next.hdr + payload + * ... + * UNTD-last.hdr + payload + * + * so that each packet is smaller or equal than i1480u_MAX_FRG_SIZE. + * + * All HW structures and bitmaps are little endian, so we need to play + * ugly tricks when defining bitfields. Hoping for the day GCC + * implements __attribute__((endian(1234))). + * + * FIXME: ROADMAP to the whole implementation + */ + +#ifndef __i1480u_wlp_h__ +#define __i1480u_wlp_h__ + +#include <linux/usb.h> +#include <linux/netdevice.h> +#include <linux/uwb.h> /* struct uwb_rc, struct uwb_notifs_handler */ +#include <linux/wlp.h> +#include "../i1480-wlp.h" + +#undef i1480u_FLOW_CONTROL /* Enable flow control code */ + +/** + * Basic flow control + */ +enum { + i1480u_TX_INFLIGHT_MAX = 1000, + i1480u_TX_INFLIGHT_THRESHOLD = 100, +}; + +/** Maximum size of a transaction that we can tx/rx */ +enum { + /* Maximum packet size computed as follows: max UNTD header (8) + + * i1480 RX header (8) + max Ethernet header and payload (4096) + + * Padding added by skb_reserve (2) to make post Ethernet payload + * start on 16 byte boundary*/ + i1480u_MAX_RX_PKT_SIZE = 4114, + i1480u_MAX_FRG_SIZE = 512, + i1480u_RX_BUFS = 9, +}; + + +/** + * UNTD packet type + * + * We need to fragment any payload whose UNTD packet is going to be + * bigger than i1480u_MAX_FRG_SIZE. + */ +enum i1480u_pkt_type { + i1480u_PKT_FRAG_1ST = 0x1, + i1480u_PKT_FRAG_NXT = 0x0, + i1480u_PKT_FRAG_LST = 0x2, + i1480u_PKT_FRAG_CMP = 0x3 +}; +enum { + i1480u_PKT_NONE = 0x4, +}; + +/** USB Network Transfer Descriptor - common */ +struct untd_hdr { + u8 type; + __le16 len; +} __attribute__((packed)); + +static inline enum i1480u_pkt_type untd_hdr_type(const struct untd_hdr *hdr) +{ + return hdr->type & 0x03; +} + +static inline int untd_hdr_rx_tx(const struct untd_hdr *hdr) +{ + return (hdr->type >> 2) & 0x01; +} + +static inline void untd_hdr_set_type(struct untd_hdr *hdr, enum i1480u_pkt_type type) +{ + hdr->type = (hdr->type & ~0x03) | type; +} + +static inline void untd_hdr_set_rx_tx(struct untd_hdr *hdr, int rx_tx) +{ + hdr->type = (hdr->type & ~0x04) | (rx_tx << 2); +} + + +/** + * USB Network Transfer Descriptor - Complete Packet + * + * This is for a packet that is smaller (header + payload) than + * i1480u_MAX_FRG_SIZE. + * + * @hdr.total_len is the size of the payload; the payload doesn't + * count this header nor the padding, but includes the size of i1480 + * header. + */ +struct untd_hdr_cmp { + struct untd_hdr hdr; + u8 padding; +} __attribute__((packed)); + + +/** + * USB Network Transfer Descriptor - First fragment + * + * @hdr.len is the size of the *whole packet* (excluding UNTD + * headers); @fragment_len is the size of the payload (excluding UNTD + * headers, but including i1480 headers). + */ +struct untd_hdr_1st { + struct untd_hdr hdr; + __le16 fragment_len; + u8 padding[3]; +} __attribute__((packed)); + + +/** + * USB Network Transfer Descriptor - Next / Last [Rest] + * + * @hdr.len is the size of the payload, not including headrs. + */ +struct untd_hdr_rst { + struct untd_hdr hdr; + u8 padding; +} __attribute__((packed)); + + +/** + * Transmission context + * + * Wraps all the stuff needed to track a pending/active tx + * operation. + */ +struct i1480u_tx { + struct list_head list_node; + struct i1480u *i1480u; + struct urb *urb; + + struct sk_buff *skb; + struct wlp_tx_hdr *wlp_tx_hdr; + + void *buf; /* if NULL, no new buf was used */ + size_t buf_size; +}; + +/** + * Basic flow control + * + * We maintain a basic flow control counter. "count" how many TX URBs are + * outstanding. Only allow "max" + * TX URBs to be outstanding. If this value is reached the queue will be + * stopped. The queue will be restarted when there are + * "threshold" URBs outstanding. + * Maintain a counter of how many time the TX queue needed to be restarted + * due to the "max" being exceeded and the "threshold" reached again. The + * timestamp "restart_ts" is to keep track from when the counter was last + * queried (see sysfs handling of file wlp_tx_inflight). + */ +struct i1480u_tx_inflight { + atomic_t count; + unsigned long max; + unsigned long threshold; + unsigned long restart_ts; + atomic_t restart_count; +}; + +/** + * Instance of a i1480u WLP interface + * + * Keeps references to the USB device that wraps it, as well as it's + * interface and associated UWB host controller. As well, it also + * keeps a link to the netdevice for integration into the networking + * stack. + * We maintian separate error history for the tx and rx endpoints because + * the implementation does not rely on locking - having one shared + * structure between endpoints may cause problems. Adding locking to the + * implementation will have higher cost than adding a separate structure. + */ +struct i1480u { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + struct net_device *net_dev; + + spinlock_t lock; + struct net_device_stats stats; + + /* RX context handling */ + struct sk_buff *rx_skb; + struct uwb_dev_addr rx_srcaddr; + size_t rx_untd_pkt_size; + struct i1480u_rx_buf { + struct i1480u *i1480u; /* back pointer */ + struct urb *urb; + struct sk_buff *data; /* i1480u_MAX_RX_PKT_SIZE each */ + } rx_buf[i1480u_RX_BUFS]; /* N bufs */ + + spinlock_t tx_list_lock; /* TX context */ + struct list_head tx_list; + u8 tx_stream; + + struct stats lqe_stats, rssi_stats; /* radio statistics */ + + /* Options we can set from sysfs */ + struct wlp_options options; + struct uwb_notifs_handler uwb_notifs_handler; + struct edc tx_errors; + struct edc rx_errors; + struct wlp wlp; +#ifdef i1480u_FLOW_CONTROL + struct urb *notif_urb; + struct edc notif_edc; /* error density counter */ + u8 notif_buffer[1]; +#endif + struct i1480u_tx_inflight tx_inflight; +}; + +/* Internal interfaces */ +extern void i1480u_rx_cb(struct urb *urb); +extern int i1480u_rx_setup(struct i1480u *); +extern void i1480u_rx_release(struct i1480u *); +extern void i1480u_tx_release(struct i1480u *); +extern int i1480u_xmit_frame(struct wlp *, struct sk_buff *, + struct uwb_dev_addr *); +extern void i1480u_stop_queue(struct wlp *); +extern void i1480u_start_queue(struct wlp *); +extern int i1480u_sysfs_setup(struct i1480u *); +extern void i1480u_sysfs_release(struct i1480u *); + +/* netdev interface */ +extern int i1480u_open(struct net_device *); +extern int i1480u_stop(struct net_device *); +extern int i1480u_hard_start_xmit(struct sk_buff *, struct net_device *); +extern void i1480u_tx_timeout(struct net_device *); +extern int i1480u_set_config(struct net_device *, struct ifmap *); +extern struct net_device_stats *i1480u_get_stats(struct net_device *); +extern int i1480u_change_mtu(struct net_device *, int); +extern void i1480u_uwb_notifs_cb(void *, struct uwb_dev *, enum uwb_notifs); + +/* bandwidth allocation callback */ +extern void i1480u_bw_alloc_cb(struct uwb_rsv *); + +/* Sys FS */ +extern struct attribute_group i1480u_wlp_attr_group; + +#endif /* #ifndef __i1480u_wlp_h__ */ diff --git a/drivers/uwb/i1480/i1480u-wlp/lc.c b/drivers/uwb/i1480/i1480u-wlp/lc.c new file mode 100644 index 00000000000..737d60cd5b7 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/lc.c @@ -0,0 +1,421 @@ +/* + * WUSB Wire Adapter: WLP interface + * Driver for the Linux Network stack. + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * This implements a very simple network driver for the WLP USB + * device that is associated to a UWB (Ultra Wide Band) host. + * + * This is seen as an interface of a composite device. Once the UWB + * host has an association to another WLP capable device, the + * networking interface (aka WLP) can start to send packets back and + * forth. + * + * Limitations: + * + * - Hand cranked; can't ifup the interface until there is an association + * + * - BW allocation very simplistic [see i1480u_mas_set() and callees]. + * + * + * ROADMAP: + * + * ENTRY POINTS (driver model): + * + * i1480u_driver_{exit,init}(): initialization of the driver. + * + * i1480u_probe(): called by the driver code when a device + * matching 'i1480u_id_table' is connected. + * + * This allocs a netdev instance, inits with + * i1480u_add(), then registers_netdev(). + * i1480u_init() + * i1480u_add() + * + * i1480u_disconnect(): device has been disconnected/module + * is being removed. + * i1480u_rm() + */ +#include <linux/version.h> +#include <linux/if_arp.h> +#include <linux/etherdevice.h> +#include <linux/uwb/debug.h> +#include "i1480u-wlp.h" + + + +static inline +void i1480u_init(struct i1480u *i1480u) +{ + /* nothing so far... doesn't it suck? */ + spin_lock_init(&i1480u->lock); + INIT_LIST_HEAD(&i1480u->tx_list); + spin_lock_init(&i1480u->tx_list_lock); + wlp_options_init(&i1480u->options); + edc_init(&i1480u->tx_errors); + edc_init(&i1480u->rx_errors); +#ifdef i1480u_FLOW_CONTROL + edc_init(&i1480u->notif_edc); +#endif + stats_init(&i1480u->lqe_stats); + stats_init(&i1480u->rssi_stats); + wlp_init(&i1480u->wlp); +} + +/** + * Fill WLP device information structure + * + * The structure will contain a few character arrays, each ending with a + * null terminated string. Each string has to fit (excluding terminating + * character) into a specified range obtained from the WLP substack. + * + * It is still not clear exactly how this device information should be + * obtained. Until we find out we use the USB device descriptor as backup, some + * information elements have intuitive mappings, other not. + */ +static +void i1480u_fill_device_info(struct wlp *wlp, struct wlp_device_info *dev_info) +{ + struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp); + struct usb_device *usb_dev = i1480u->usb_dev; + /* Treat device name and model name the same */ + if (usb_dev->descriptor.iProduct) { + usb_string(usb_dev, usb_dev->descriptor.iProduct, + dev_info->name, sizeof(dev_info->name)); + usb_string(usb_dev, usb_dev->descriptor.iProduct, + dev_info->model_name, sizeof(dev_info->model_name)); + } + if (usb_dev->descriptor.iManufacturer) + usb_string(usb_dev, usb_dev->descriptor.iManufacturer, + dev_info->manufacturer, + sizeof(dev_info->manufacturer)); + scnprintf(dev_info->model_nr, sizeof(dev_info->model_nr), "%04x", + __le16_to_cpu(usb_dev->descriptor.bcdDevice)); + if (usb_dev->descriptor.iSerialNumber) + usb_string(usb_dev, usb_dev->descriptor.iSerialNumber, + dev_info->serial, sizeof(dev_info->serial)); + /* FIXME: where should we obtain category? */ + dev_info->prim_dev_type.category = cpu_to_le16(WLP_DEV_CAT_OTHER); + /* FIXME: Complete OUI and OUIsubdiv attributes */ +} + +#ifdef i1480u_FLOW_CONTROL +/** + * Callback for the notification endpoint + * + * This mostly controls the xon/xoff protocol. In case of hard error, + * we stop the queue. If not, we always retry. + */ +static +void i1480u_notif_cb(struct urb *urb, struct pt_regs *regs) +{ + struct i1480u *i1480u = urb->context; + struct usb_interface *usb_iface = i1480u->usb_iface; + struct device *dev = &usb_iface->dev; + int result; + + switch (urb->status) { + case 0: /* Got valid data, do xon/xoff */ + switch (i1480u->notif_buffer[0]) { + case 'N': + dev_err(dev, "XOFF STOPPING queue at %lu\n", jiffies); + netif_stop_queue(i1480u->net_dev); + break; + case 'A': + dev_err(dev, "XON STARTING queue at %lu\n", jiffies); + netif_start_queue(i1480u->net_dev); + break; + default: + dev_err(dev, "NEP: unknown data 0x%02hhx\n", + i1480u->notif_buffer[0]); + } + break; + case -ECONNRESET: /* Controlled situation ... */ + case -ENOENT: /* we killed the URB... */ + dev_err(dev, "NEP: URB reset/noent %d\n", urb->status); + goto error; + case -ESHUTDOWN: /* going away! */ + dev_err(dev, "NEP: URB down %d\n", urb->status); + goto error; + default: /* Retry unless it gets ugly */ + if (edc_inc(&i1480u->notif_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "NEP: URB max acceptable errors " + "exceeded; resetting device\n"); + goto error_reset; + } + dev_err(dev, "NEP: URB error %d\n", urb->status); + break; + } + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "NEP: Can't resubmit URB: %d; resetting device\n", + result); + goto error_reset; + } + return; + +error_reset: + wlp_reset_all(&i1480-wlp); +error: + netif_stop_queue(i1480u->net_dev); + return; +} +#endif + +static +int i1480u_add(struct i1480u *i1480u, struct usb_interface *iface) +{ + int result = -ENODEV; + struct wlp *wlp = &i1480u->wlp; + struct usb_device *usb_dev = interface_to_usbdev(iface); + struct net_device *net_dev = i1480u->net_dev; + struct uwb_rc *rc; + struct uwb_dev *uwb_dev; +#ifdef i1480u_FLOW_CONTROL + struct usb_endpoint_descriptor *epd; +#endif + + i1480u->usb_dev = usb_get_dev(usb_dev); + i1480u->usb_iface = iface; + rc = uwb_rc_get_by_grandpa(&i1480u->usb_dev->dev); + if (rc == NULL) { + dev_err(&iface->dev, "Cannot get associated UWB Radio " + "Controller\n"); + goto out; + } + wlp->xmit_frame = i1480u_xmit_frame; + wlp->fill_device_info = i1480u_fill_device_info; + wlp->stop_queue = i1480u_stop_queue; + wlp->start_queue = i1480u_start_queue; + result = wlp_setup(wlp, rc); + if (result < 0) { + dev_err(&iface->dev, "Cannot setup WLP\n"); + goto error_wlp_setup; + } + result = 0; + ether_setup(net_dev); /* make it an etherdevice */ + uwb_dev = &rc->uwb_dev; + /* FIXME: hookup address change notifications? */ + + memcpy(net_dev->dev_addr, uwb_dev->mac_addr.data, + sizeof(net_dev->dev_addr)); + + net_dev->hard_header_len = sizeof(struct untd_hdr_cmp) + + sizeof(struct wlp_tx_hdr) + + WLP_DATA_HLEN + + ETH_HLEN; + net_dev->mtu = 3500; + net_dev->tx_queue_len = 20; /* FIXME: maybe use 1000? */ + +/* net_dev->flags &= ~IFF_BROADCAST; FIXME: BUG in firmware */ + /* FIXME: multicast disabled */ + net_dev->flags &= ~IFF_MULTICAST; + net_dev->features &= ~NETIF_F_SG; + net_dev->features &= ~NETIF_F_FRAGLIST; + /* All NETIF_F_*_CSUM disabled */ + net_dev->features |= NETIF_F_HIGHDMA; + net_dev->watchdog_timeo = 5*HZ; /* FIXME: a better default? */ + + net_dev->open = i1480u_open; + net_dev->stop = i1480u_stop; + net_dev->hard_start_xmit = i1480u_hard_start_xmit; + net_dev->tx_timeout = i1480u_tx_timeout; + net_dev->get_stats = i1480u_get_stats; + net_dev->set_config = i1480u_set_config; + net_dev->change_mtu = i1480u_change_mtu; + +#ifdef i1480u_FLOW_CONTROL + /* Notification endpoint setup (submitted when we open the device) */ + i1480u->notif_urb = usb_alloc_urb(0, GFP_KERNEL); + if (i1480u->notif_urb == NULL) { + dev_err(&iface->dev, "Unable to allocate notification URB\n"); + result = -ENOMEM; + goto error_urb_alloc; + } + epd = &iface->cur_altsetting->endpoint[0].desc; + usb_fill_int_urb(i1480u->notif_urb, usb_dev, + usb_rcvintpipe(usb_dev, epd->bEndpointAddress), + i1480u->notif_buffer, sizeof(i1480u->notif_buffer), + i1480u_notif_cb, i1480u, epd->bInterval); + +#endif + + i1480u->tx_inflight.max = i1480u_TX_INFLIGHT_MAX; + i1480u->tx_inflight.threshold = i1480u_TX_INFLIGHT_THRESHOLD; + i1480u->tx_inflight.restart_ts = jiffies; + usb_set_intfdata(iface, i1480u); + return result; + +#ifdef i1480u_FLOW_CONTROL +error_urb_alloc: +#endif + wlp_remove(wlp); +error_wlp_setup: + uwb_rc_put(rc); +out: + usb_put_dev(i1480u->usb_dev); + return result; +} + +static void i1480u_rm(struct i1480u *i1480u) +{ + struct uwb_rc *rc = i1480u->wlp.rc; + usb_set_intfdata(i1480u->usb_iface, NULL); +#ifdef i1480u_FLOW_CONTROL + usb_kill_urb(i1480u->notif_urb); + usb_free_urb(i1480u->notif_urb); +#endif + wlp_remove(&i1480u->wlp); + uwb_rc_put(rc); + usb_put_dev(i1480u->usb_dev); +} + +/** Just setup @net_dev's i1480u private data */ +static void i1480u_netdev_setup(struct net_device *net_dev) +{ + struct i1480u *i1480u = netdev_priv(net_dev); + /* Initialize @i1480u */ + memset(i1480u, 0, sizeof(*i1480u)); + i1480u_init(i1480u); +} + +/** + * Probe a i1480u interface and register it + * + * @iface: USB interface to link to + * @id: USB class/subclass/protocol id + * @returns: 0 if ok, < 0 errno code on error. + * + * Does basic housekeeping stuff and then allocs a netdev with space + * for the i1480u data. Initializes, registers in i1480u, registers in + * netdev, ready to go. + */ +static int i1480u_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + int result; + struct net_device *net_dev; + struct device *dev = &iface->dev; + struct i1480u *i1480u; + + /* Allocate instance [calls i1480u_netdev_setup() on it] */ + result = -ENOMEM; + net_dev = alloc_netdev(sizeof(*i1480u), "wlp%d", i1480u_netdev_setup); + if (net_dev == NULL) { + dev_err(dev, "no memory for network device instance\n"); + goto error_alloc_netdev; + } + SET_NETDEV_DEV(net_dev, dev); + i1480u = netdev_priv(net_dev); + i1480u->net_dev = net_dev; + result = i1480u_add(i1480u, iface); /* Now setup all the wlp stuff */ + if (result < 0) { + dev_err(dev, "cannot add i1480u device: %d\n", result); + goto error_i1480u_add; + } + result = register_netdev(net_dev); /* Okey dokey, bring it up */ + if (result < 0) { + dev_err(dev, "cannot register network device: %d\n", result); + goto error_register_netdev; + } + i1480u_sysfs_setup(i1480u); + if (result < 0) + goto error_sysfs_init; + return 0; + +error_sysfs_init: + unregister_netdev(net_dev); +error_register_netdev: + i1480u_rm(i1480u); +error_i1480u_add: + free_netdev(net_dev); +error_alloc_netdev: + return result; +} + + +/** + * Disconect a i1480u from the system. + * + * i1480u_stop() has been called before, so al the rx and tx contexts + * have been taken down already. Make sure the queue is stopped, + * unregister netdev and i1480u, free and kill. + */ +static void i1480u_disconnect(struct usb_interface *iface) +{ + struct i1480u *i1480u; + struct net_device *net_dev; + + i1480u = usb_get_intfdata(iface); + net_dev = i1480u->net_dev; + netif_stop_queue(net_dev); +#ifdef i1480u_FLOW_CONTROL + usb_kill_urb(i1480u->notif_urb); +#endif + i1480u_sysfs_release(i1480u); + unregister_netdev(net_dev); + i1480u_rm(i1480u); + free_netdev(net_dev); +} + +static struct usb_device_id i1480u_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE \ + | USB_DEVICE_ID_MATCH_DEV_INFO \ + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x8086, + .idProduct = 0x0c3b, + .bDeviceClass = 0xef, + .bDeviceSubClass = 0x02, + .bDeviceProtocol = 0x02, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + }, + {}, +}; +MODULE_DEVICE_TABLE(usb, i1480u_id_table); + +static struct usb_driver i1480u_driver = { + .name = KBUILD_MODNAME, + .probe = i1480u_probe, + .disconnect = i1480u_disconnect, + .id_table = i1480u_id_table, +}; + +static int __init i1480u_driver_init(void) +{ + return usb_register(&i1480u_driver); +} +module_init(i1480u_driver_init); + + +static void __exit i1480u_driver_exit(void) +{ + usb_deregister(&i1480u_driver); +} +module_exit(i1480u_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("i1480 Wireless UWB Link WLP networking for USB"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/i1480/i1480u-wlp/netdev.c b/drivers/uwb/i1480/i1480u-wlp/netdev.c new file mode 100644 index 00000000000..8802ac43d87 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/netdev.c @@ -0,0 +1,368 @@ +/* + * WUSB Wire Adapter: WLP interface + * Driver for the Linux Network stack. + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * Implementation of the netdevice linkage (except tx and rx related stuff). + * + * ROADMAP: + * + * ENTRY POINTS (Net device): + * + * i1480u_open(): Called when we ifconfig up the interface; + * associates to a UWB host controller, reserves + * bandwidth (MAS), sets up RX USB URB and starts + * the queue. + * + * i1480u_stop(): Called when we ifconfig down a interface; + * reverses _open(). + * + * i1480u_set_config(): + */ + +#include <linux/if_arp.h> +#include <linux/etherdevice.h> +#include <linux/uwb/debug.h> +#include "i1480u-wlp.h" + +struct i1480u_cmd_set_ip_mas { + struct uwb_rccb rccb; + struct uwb_dev_addr addr; + u8 stream; + u8 owner; + u8 type; /* enum uwb_drp_type */ + u8 baMAS[32]; +} __attribute__((packed)); + + +static +int i1480u_set_ip_mas( + struct uwb_rc *rc, + const struct uwb_dev_addr *dstaddr, + u8 stream, u8 owner, u8 type, unsigned long *mas) +{ + + int result; + struct i1480u_cmd_set_ip_mas *cmd; + struct uwb_rc_evt_confirm reply; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + cmd->rccb.bCommandType = 0xfd; + cmd->rccb.wCommand = cpu_to_le16(0x000e); + cmd->addr = *dstaddr; + cmd->stream = stream; + cmd->owner = owner; + cmd->type = type; + if (mas == NULL) + memset(cmd->baMAS, 0x00, sizeof(cmd->baMAS)); + else + memcpy(cmd->baMAS, mas, sizeof(cmd->baMAS)); + reply.rceb.bEventType = 0xfd; + reply.rceb.wEvent = cpu_to_le16(0x000e); + result = uwb_rc_cmd(rc, "SET-IP-MAS", &cmd->rccb, sizeof(*cmd), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_FAIL) { + dev_err(&rc->uwb_dev.dev, + "SET-IP-MAS: command execution failed: %d\n", + reply.bResultCode); + result = -EIO; + } +error_cmd: + kfree(cmd); +error_kzalloc: + return result; +} + +/* + * Inform a WLP interface of a MAS reservation + * + * @rc is assumed refcnted. + */ +/* FIXME: detect if remote device is WLP capable? */ +static int i1480u_mas_set_dev(struct uwb_dev *uwb_dev, struct uwb_rc *rc, + u8 stream, u8 owner, u8 type, unsigned long *mas) +{ + int result = 0; + struct device *dev = &rc->uwb_dev.dev; + + result = i1480u_set_ip_mas(rc, &uwb_dev->dev_addr, stream, owner, + type, mas); + if (result < 0) { + char rcaddrbuf[UWB_ADDR_STRSIZE], devaddrbuf[UWB_ADDR_STRSIZE]; + uwb_dev_addr_print(rcaddrbuf, sizeof(rcaddrbuf), + &rc->uwb_dev.dev_addr); + uwb_dev_addr_print(devaddrbuf, sizeof(devaddrbuf), + &uwb_dev->dev_addr); + dev_err(dev, "Set IP MAS (%s to %s) failed: %d\n", + rcaddrbuf, devaddrbuf, result); + } + return result; +} + +/** + * Called by bandwidth allocator when change occurs in reservation. + * + * @rsv: The reservation that is being established, modified, or + * terminated. + * + * When a reservation is established, modified, or terminated the upper layer + * (WLP here) needs set/update the currently available Media Access Slots + * that can be use for IP traffic. + * + * Our action taken during failure depends on how the reservation is being + * changed: + * - if reservation is being established we do nothing if we cannot set the + * new MAS to be used + * - if reservation is being terminated we revert back to PCA whether the + * SET IP MAS command succeeds or not. + */ +void i1480u_bw_alloc_cb(struct uwb_rsv *rsv) +{ + int result = 0; + struct i1480u *i1480u = rsv->pal_priv; + struct device *dev = &i1480u->usb_iface->dev; + struct uwb_dev *target_dev = rsv->target.dev; + struct uwb_rc *rc = i1480u->wlp.rc; + u8 stream = rsv->stream; + int type = rsv->type; + int is_owner = rsv->owner == &rc->uwb_dev; + unsigned long *bmp = rsv->mas.bm; + + dev_err(dev, "WLP callback called - sending set ip mas\n"); + /*user cannot change options while setting configuration*/ + mutex_lock(&i1480u->options.mutex); + switch (rsv->state) { + case UWB_RSV_STATE_T_ACCEPTED: + case UWB_RSV_STATE_O_ESTABLISHED: + result = i1480u_mas_set_dev(target_dev, rc, stream, is_owner, + type, bmp); + if (result < 0) { + dev_err(dev, "MAS reservation failed: %d\n", result); + goto out; + } + if (is_owner) { + wlp_tx_hdr_set_delivery_id_type(&i1480u->options.def_tx_hdr, + WLP_DRP | stream); + wlp_tx_hdr_set_rts_cts(&i1480u->options.def_tx_hdr, 0); + } + break; + case UWB_RSV_STATE_NONE: + /* revert back to PCA */ + result = i1480u_mas_set_dev(target_dev, rc, stream, is_owner, + type, bmp); + if (result < 0) + dev_err(dev, "MAS reservation failed: %d\n", result); + /* Revert to PCA even though SET IP MAS failed. */ + wlp_tx_hdr_set_delivery_id_type(&i1480u->options.def_tx_hdr, + i1480u->options.pca_base_priority); + wlp_tx_hdr_set_rts_cts(&i1480u->options.def_tx_hdr, 1); + break; + default: + dev_err(dev, "unexpected WLP reservation state: %s (%d).\n", + uwb_rsv_state_str(rsv->state), rsv->state); + break; + } +out: + mutex_unlock(&i1480u->options.mutex); + return; +} + +/** + * + * Called on 'ifconfig up' + */ +int i1480u_open(struct net_device *net_dev) +{ + int result; + struct i1480u *i1480u = netdev_priv(net_dev); + struct wlp *wlp = &i1480u->wlp; + struct uwb_rc *rc; + struct device *dev = &i1480u->usb_iface->dev; + + rc = wlp->rc; + result = i1480u_rx_setup(i1480u); /* Alloc RX stuff */ + if (result < 0) + goto error_rx_setup; + netif_wake_queue(net_dev); +#ifdef i1480u_FLOW_CONTROL + result = usb_submit_urb(i1480u->notif_urb, GFP_KERNEL);; + if (result < 0) { + dev_err(dev, "Can't submit notification URB: %d\n", result); + goto error_notif_urb_submit; + } +#endif + i1480u->uwb_notifs_handler.cb = i1480u_uwb_notifs_cb; + i1480u->uwb_notifs_handler.data = i1480u; + if (uwb_bg_joined(rc)) + netif_carrier_on(net_dev); + else + netif_carrier_off(net_dev); + uwb_notifs_register(rc, &i1480u->uwb_notifs_handler); + /* Interface is up with an address, now we can create WSS */ + result = wlp_wss_setup(net_dev, &wlp->wss); + if (result < 0) { + dev_err(dev, "Can't create WSS: %d. \n", result); + goto error_notif_deregister; + } + return 0; +error_notif_deregister: + uwb_notifs_deregister(rc, &i1480u->uwb_notifs_handler); +#ifdef i1480u_FLOW_CONTROL +error_notif_urb_submit: +#endif + netif_stop_queue(net_dev); + i1480u_rx_release(i1480u); +error_rx_setup: + return result; +} + + +/** + * Called on 'ifconfig down' + */ +int i1480u_stop(struct net_device *net_dev) +{ + struct i1480u *i1480u = netdev_priv(net_dev); + struct wlp *wlp = &i1480u->wlp; + struct uwb_rc *rc = wlp->rc; + + BUG_ON(wlp->rc == NULL); + wlp_wss_remove(&wlp->wss); + uwb_notifs_deregister(rc, &i1480u->uwb_notifs_handler); + netif_carrier_off(net_dev); +#ifdef i1480u_FLOW_CONTROL + usb_kill_urb(i1480u->notif_urb); +#endif + netif_stop_queue(net_dev); + i1480u_rx_release(i1480u); + i1480u_tx_release(i1480u); + return 0; +} + + +/** Report statistics */ +struct net_device_stats *i1480u_get_stats(struct net_device *net_dev) +{ + struct i1480u *i1480u = netdev_priv(net_dev); + return &i1480u->stats; +} + + +/** + * + * Change the interface config--we probably don't have to do anything. + */ +int i1480u_set_config(struct net_device *net_dev, struct ifmap *map) +{ + int result; + struct i1480u *i1480u = netdev_priv(net_dev); + BUG_ON(i1480u->wlp.rc == NULL); + result = 0; + return result; +} + +/** + * Change the MTU of the interface + */ +int i1480u_change_mtu(struct net_device *net_dev, int mtu) +{ + static union { + struct wlp_tx_hdr tx; + struct wlp_rx_hdr rx; + } i1480u_all_hdrs; + + if (mtu < ETH_HLEN) /* We encap eth frames */ + return -ERANGE; + if (mtu > 4000 - sizeof(i1480u_all_hdrs)) + return -ERANGE; + net_dev->mtu = mtu; + return 0; +} + + +/** + * Callback function to handle events from UWB + * When we see other devices we know the carrier is ok, + * if we are the only device in the beacon group we set the carrier + * state to off. + * */ +void i1480u_uwb_notifs_cb(void *data, struct uwb_dev *uwb_dev, + enum uwb_notifs event) +{ + struct i1480u *i1480u = data; + struct net_device *net_dev = i1480u->net_dev; + struct device *dev = &i1480u->usb_iface->dev; + switch (event) { + case UWB_NOTIF_BG_JOIN: + netif_carrier_on(net_dev); + dev_info(dev, "Link is up\n"); + break; + case UWB_NOTIF_BG_LEAVE: + netif_carrier_off(net_dev); + dev_info(dev, "Link is down\n"); + break; + default: + dev_err(dev, "don't know how to handle event %d from uwb\n", + event); + } +} + +/** + * Stop the network queue + * + * Enable WLP substack to stop network queue. We also set the flow control + * threshold at this time to prevent the flow control from restarting the + * queue. + * + * we are loosing the current threshold value here ... FIXME? + */ +void i1480u_stop_queue(struct wlp *wlp) +{ + struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp); + struct net_device *net_dev = i1480u->net_dev; + i1480u->tx_inflight.threshold = 0; + netif_stop_queue(net_dev); +} + +/** + * Start the network queue + * + * Enable WLP substack to start network queue. Also re-enable the flow + * control to manage the queue again. + * + * We re-enable the flow control by storing the default threshold in the + * flow control threshold. This means that if the user modified the + * threshold before the queue was stopped and restarted that information + * will be lost. FIXME? + */ +void i1480u_start_queue(struct wlp *wlp) +{ + struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp); + struct net_device *net_dev = i1480u->net_dev; + i1480u->tx_inflight.threshold = i1480u_TX_INFLIGHT_THRESHOLD; + netif_start_queue(net_dev); +} diff --git a/drivers/uwb/i1480/i1480u-wlp/rx.c b/drivers/uwb/i1480/i1480u-wlp/rx.c new file mode 100644 index 00000000000..9fc035354a7 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/rx.c @@ -0,0 +1,486 @@ +/* + * WUSB Wire Adapter: WLP interface + * Driver for the Linux Network stack. + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * i1480u's RX handling is simple. i1480u will send the received + * network packets broken up in fragments; 1 to N fragments make a + * packet, we assemble them together and deliver the packet with netif_rx(). + * + * Beacuse each USB transfer is a *single* fragment (except when the + * transfer contains a first fragment), each URB called thus + * back contains one or two fragments. So we queue N URBs, each with its own + * fragment buffer. When a URB is done, we process it (adding to the + * current skb from the fragment buffer until complete). Once + * processed, we requeue the URB. There is always a bunch of URBs + * ready to take data, so the intergap should be minimal. + * + * An URB's transfer buffer is the data field of a socket buffer. This + * reduces copying as data can be passed directly to network layer. If a + * complete packet or 1st fragment is received the URB's transfer buffer is + * taken away from it and used to send data to the network layer. In this + * case a new transfer buffer is allocated to the URB before being requeued. + * If a "NEXT" or "LAST" fragment is received, the fragment contents is + * appended to the RX packet under construction and the transfer buffer + * is reused. To be able to use this buffer to assemble complete packets + * we set each buffer's size to that of the MAX ethernet packet that can + * be received. There is thus room for improvement in memory usage. + * + * When the max tx fragment size increases, we should be able to read + * data into the skbs directly with very simple code. + * + * ROADMAP: + * + * ENTRY POINTS: + * + * i1480u_rx_setup(): setup RX context [from i1480u_open()] + * + * i1480u_rx_release(): release RX context [from i1480u_stop()] + * + * i1480u_rx_cb(): called when the RX USB URB receives a + * packet. It removes the header and pushes it up + * the Linux netdev stack with netif_rx(). + * + * i1480u_rx_buffer() + * i1480u_drop() and i1480u_fix() + * i1480u_skb_deliver + * + */ + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include "i1480u-wlp.h" + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + + +/** + * Setup the RX context + * + * Each URB is provided with a transfer_buffer that is the data field + * of a new socket buffer. + */ +int i1480u_rx_setup(struct i1480u *i1480u) +{ + int result, cnt; + struct device *dev = &i1480u->usb_iface->dev; + struct net_device *net_dev = i1480u->net_dev; + struct usb_endpoint_descriptor *epd; + struct sk_buff *skb; + + /* Alloc RX stuff */ + i1480u->rx_skb = NULL; /* not in process of receiving packet */ + result = -ENOMEM; + epd = &i1480u->usb_iface->cur_altsetting->endpoint[1].desc; + for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) { + struct i1480u_rx_buf *rx_buf = &i1480u->rx_buf[cnt]; + rx_buf->i1480u = i1480u; + skb = dev_alloc_skb(i1480u_MAX_RX_PKT_SIZE); + if (!skb) { + dev_err(dev, + "RX: cannot allocate RX buffer %d\n", cnt); + result = -ENOMEM; + goto error; + } + skb->dev = net_dev; + skb->ip_summed = CHECKSUM_NONE; + skb_reserve(skb, 2); + rx_buf->data = skb; + rx_buf->urb = usb_alloc_urb(0, GFP_KERNEL); + if (unlikely(rx_buf->urb == NULL)) { + dev_err(dev, "RX: cannot allocate URB %d\n", cnt); + result = -ENOMEM; + goto error; + } + usb_fill_bulk_urb(rx_buf->urb, i1480u->usb_dev, + usb_rcvbulkpipe(i1480u->usb_dev, epd->bEndpointAddress), + rx_buf->data->data, i1480u_MAX_RX_PKT_SIZE - 2, + i1480u_rx_cb, rx_buf); + result = usb_submit_urb(rx_buf->urb, GFP_NOIO); + if (unlikely(result < 0)) { + dev_err(dev, "RX: cannot submit URB %d: %d\n", + cnt, result); + goto error; + } + } + return 0; + +error: + i1480u_rx_release(i1480u); + return result; +} + + +/** Release resources associated to the rx context */ +void i1480u_rx_release(struct i1480u *i1480u) +{ + int cnt; + for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) { + if (i1480u->rx_buf[cnt].data) + dev_kfree_skb(i1480u->rx_buf[cnt].data); + if (i1480u->rx_buf[cnt].urb) { + usb_kill_urb(i1480u->rx_buf[cnt].urb); + usb_free_urb(i1480u->rx_buf[cnt].urb); + } + } + if (i1480u->rx_skb != NULL) + dev_kfree_skb(i1480u->rx_skb); +} + +static +void i1480u_rx_unlink_urbs(struct i1480u *i1480u) +{ + int cnt; + for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) { + if (i1480u->rx_buf[cnt].urb) + usb_unlink_urb(i1480u->rx_buf[cnt].urb); + } +} + +/** Fix an out-of-sequence packet */ +#define i1480u_fix(i1480u, msg...) \ +do { \ + if (printk_ratelimit()) \ + dev_err(&i1480u->usb_iface->dev, msg); \ + dev_kfree_skb_irq(i1480u->rx_skb); \ + i1480u->rx_skb = NULL; \ + i1480u->rx_untd_pkt_size = 0; \ +} while (0) + + +/** Drop an out-of-sequence packet */ +#define i1480u_drop(i1480u, msg...) \ +do { \ + if (printk_ratelimit()) \ + dev_err(&i1480u->usb_iface->dev, msg); \ + i1480u->stats.rx_dropped++; \ +} while (0) + + + + +/** Finalizes setting up the SKB and delivers it + * + * We first pass the incoming frame to WLP substack for verification. It + * may also be a WLP association frame in which case WLP will take over the + * processing. If WLP does not take it over it will still verify it, if the + * frame is invalid the skb will be freed by WLP and we will not continue + * parsing. + * */ +static +void i1480u_skb_deliver(struct i1480u *i1480u) +{ + int should_parse; + struct net_device *net_dev = i1480u->net_dev; + struct device *dev = &i1480u->usb_iface->dev; + + d_printf(6, dev, "RX delivered pre skb(%p), %u bytes\n", + i1480u->rx_skb, i1480u->rx_skb->len); + d_dump(7, dev, i1480u->rx_skb->data, i1480u->rx_skb->len); + should_parse = wlp_receive_frame(dev, &i1480u->wlp, i1480u->rx_skb, + &i1480u->rx_srcaddr); + if (!should_parse) + goto out; + i1480u->rx_skb->protocol = eth_type_trans(i1480u->rx_skb, net_dev); + d_printf(5, dev, "RX delivered skb(%p), %u bytes\n", + i1480u->rx_skb, i1480u->rx_skb->len); + d_dump(7, dev, i1480u->rx_skb->data, + i1480u->rx_skb->len > 72 ? 72 : i1480u->rx_skb->len); + i1480u->stats.rx_packets++; + i1480u->stats.rx_bytes += i1480u->rx_untd_pkt_size; + net_dev->last_rx = jiffies; + /* FIXME: flow control: check netif_rx() retval */ + + netif_rx(i1480u->rx_skb); /* deliver */ +out: + i1480u->rx_skb = NULL; + i1480u->rx_untd_pkt_size = 0; +} + + +/** + * Process a buffer of data received from the USB RX endpoint + * + * First fragment arrives with next or last fragment. All other fragments + * arrive alone. + * + * /me hates long functions. + */ +static +void i1480u_rx_buffer(struct i1480u_rx_buf *rx_buf) +{ + unsigned pkt_completed = 0; /* !0 when we got all pkt fragments */ + size_t untd_hdr_size, untd_frg_size; + size_t i1480u_hdr_size; + struct wlp_rx_hdr *i1480u_hdr = NULL; + + struct i1480u *i1480u = rx_buf->i1480u; + struct sk_buff *skb = rx_buf->data; + int size_left = rx_buf->urb->actual_length; + void *ptr = rx_buf->urb->transfer_buffer; /* also rx_buf->data->data */ + struct untd_hdr *untd_hdr; + + struct net_device *net_dev = i1480u->net_dev; + struct device *dev = &i1480u->usb_iface->dev; + struct sk_buff *new_skb; + +#if 0 + dev_fnstart(dev, + "(i1480u %p ptr %p size_left %zu)\n", i1480u, ptr, size_left); + dev_err(dev, "RX packet, %zu bytes\n", size_left); + dump_bytes(dev, ptr, size_left); +#endif + i1480u_hdr_size = sizeof(struct wlp_rx_hdr); + + while (size_left > 0) { + if (pkt_completed) { + i1480u_drop(i1480u, "RX: fragment follows completed" + "packet in same buffer. Dropping\n"); + break; + } + untd_hdr = ptr; + if (size_left < sizeof(*untd_hdr)) { /* Check the UNTD header */ + i1480u_drop(i1480u, "RX: short UNTD header! Dropping\n"); + goto out; + } + if (unlikely(untd_hdr_rx_tx(untd_hdr) == 0)) { /* Paranoia: TX set? */ + i1480u_drop(i1480u, "RX: TX bit set! Dropping\n"); + goto out; + } + switch (untd_hdr_type(untd_hdr)) { /* Check the UNTD header type */ + case i1480u_PKT_FRAG_1ST: { + struct untd_hdr_1st *untd_hdr_1st = (void *) untd_hdr; + dev_dbg(dev, "1st fragment\n"); + untd_hdr_size = sizeof(struct untd_hdr_1st); + if (i1480u->rx_skb != NULL) + i1480u_fix(i1480u, "RX: 1st fragment out of " + "sequence! Fixing\n"); + if (size_left < untd_hdr_size + i1480u_hdr_size) { + i1480u_drop(i1480u, "RX: short 1st fragment! " + "Dropping\n"); + goto out; + } + i1480u->rx_untd_pkt_size = le16_to_cpu(untd_hdr->len) + - i1480u_hdr_size; + untd_frg_size = le16_to_cpu(untd_hdr_1st->fragment_len); + if (size_left < untd_hdr_size + untd_frg_size) { + i1480u_drop(i1480u, + "RX: short payload! Dropping\n"); + goto out; + } + i1480u->rx_skb = skb; + i1480u_hdr = (void *) untd_hdr_1st + untd_hdr_size; + i1480u->rx_srcaddr = i1480u_hdr->srcaddr; + skb_put(i1480u->rx_skb, untd_hdr_size + untd_frg_size); + skb_pull(i1480u->rx_skb, untd_hdr_size + i1480u_hdr_size); + stats_add_sample(&i1480u->lqe_stats, (s8) i1480u_hdr->LQI - 7); + stats_add_sample(&i1480u->rssi_stats, i1480u_hdr->RSSI + 18); + rx_buf->data = NULL; /* need to create new buffer */ + break; + } + case i1480u_PKT_FRAG_NXT: { + dev_dbg(dev, "nxt fragment\n"); + untd_hdr_size = sizeof(struct untd_hdr_rst); + if (i1480u->rx_skb == NULL) { + i1480u_drop(i1480u, "RX: next fragment out of " + "sequence! Dropping\n"); + goto out; + } + if (size_left < untd_hdr_size) { + i1480u_drop(i1480u, "RX: short NXT fragment! " + "Dropping\n"); + goto out; + } + untd_frg_size = le16_to_cpu(untd_hdr->len); + if (size_left < untd_hdr_size + untd_frg_size) { + i1480u_drop(i1480u, + "RX: short payload! Dropping\n"); + goto out; + } + memmove(skb_put(i1480u->rx_skb, untd_frg_size), + ptr + untd_hdr_size, untd_frg_size); + break; + } + case i1480u_PKT_FRAG_LST: { + dev_dbg(dev, "Lst fragment\n"); + untd_hdr_size = sizeof(struct untd_hdr_rst); + if (i1480u->rx_skb == NULL) { + i1480u_drop(i1480u, "RX: last fragment out of " + "sequence! Dropping\n"); + goto out; + } + if (size_left < untd_hdr_size) { + i1480u_drop(i1480u, "RX: short LST fragment! " + "Dropping\n"); + goto out; + } + untd_frg_size = le16_to_cpu(untd_hdr->len); + if (size_left < untd_frg_size + untd_hdr_size) { + i1480u_drop(i1480u, + "RX: short payload! Dropping\n"); + goto out; + } + memmove(skb_put(i1480u->rx_skb, untd_frg_size), + ptr + untd_hdr_size, untd_frg_size); + pkt_completed = 1; + break; + } + case i1480u_PKT_FRAG_CMP: { + dev_dbg(dev, "cmp fragment\n"); + untd_hdr_size = sizeof(struct untd_hdr_cmp); + if (i1480u->rx_skb != NULL) + i1480u_fix(i1480u, "RX: fix out-of-sequence CMP" + " fragment!\n"); + if (size_left < untd_hdr_size + i1480u_hdr_size) { + i1480u_drop(i1480u, "RX: short CMP fragment! " + "Dropping\n"); + goto out; + } + i1480u->rx_untd_pkt_size = le16_to_cpu(untd_hdr->len); + untd_frg_size = i1480u->rx_untd_pkt_size; + if (size_left < i1480u->rx_untd_pkt_size + untd_hdr_size) { + i1480u_drop(i1480u, + "RX: short payload! Dropping\n"); + goto out; + } + i1480u->rx_skb = skb; + i1480u_hdr = (void *) untd_hdr + untd_hdr_size; + i1480u->rx_srcaddr = i1480u_hdr->srcaddr; + stats_add_sample(&i1480u->lqe_stats, (s8) i1480u_hdr->LQI - 7); + stats_add_sample(&i1480u->rssi_stats, i1480u_hdr->RSSI + 18); + skb_put(i1480u->rx_skb, untd_hdr_size + i1480u->rx_untd_pkt_size); + skb_pull(i1480u->rx_skb, untd_hdr_size + i1480u_hdr_size); + rx_buf->data = NULL; /* for hand off skb to network stack */ + pkt_completed = 1; + i1480u->rx_untd_pkt_size -= i1480u_hdr_size; /* accurate stat */ + break; + } + default: + i1480u_drop(i1480u, "RX: unknown packet type %u! " + "Dropping\n", untd_hdr_type(untd_hdr)); + goto out; + } + size_left -= untd_hdr_size + untd_frg_size; + if (size_left > 0) + ptr += untd_hdr_size + untd_frg_size; + } + if (pkt_completed) + i1480u_skb_deliver(i1480u); +out: + /* recreate needed RX buffers*/ + if (rx_buf->data == NULL) { + /* buffer is being used to receive packet, create new */ + new_skb = dev_alloc_skb(i1480u_MAX_RX_PKT_SIZE); + if (!new_skb) { + if (printk_ratelimit()) + dev_err(dev, + "RX: cannot allocate RX buffer\n"); + } else { + new_skb->dev = net_dev; + new_skb->ip_summed = CHECKSUM_NONE; + skb_reserve(new_skb, 2); + rx_buf->data = new_skb; + } + } + return; +} + + +/** + * Called when an RX URB has finished receiving or has found some kind + * of error condition. + * + * LIMITATIONS: + * + * - We read USB-transfers, each transfer contains a SINGLE fragment + * (can contain a complete packet, or a 1st, next, or last fragment + * of a packet). + * Looks like a transfer can contain more than one fragment (07/18/06) + * + * - Each transfer buffer is the size of the maximum packet size (minus + * headroom), i1480u_MAX_PKT_SIZE - 2 + * + * - We always read the full USB-transfer, no partials. + * + * - Each transfer is read directly into a skb. This skb will be used to + * send data to the upper layers if it is the first fragment or a complete + * packet. In the other cases the data will be copied from the skb to + * another skb that is being prepared for the upper layers from a prev + * first fragment. + * + * It is simply too much of a pain. Gosh, there should be a unified + * SG infrastructure for *everything* [so that I could declare a SG + * buffer, pass it to USB for receiving, append some space to it if + * I wish, receive more until I have the whole chunk, adapt + * pointers on each fragment to remove hardware headers and then + * attach that to an skbuff and netif_rx()]. + */ +void i1480u_rx_cb(struct urb *urb) +{ + int result; + int do_parse_buffer = 1; + struct i1480u_rx_buf *rx_buf = urb->context; + struct i1480u *i1480u = rx_buf->i1480u; + struct device *dev = &i1480u->usb_iface->dev; + unsigned long flags; + u8 rx_buf_idx = rx_buf - i1480u->rx_buf; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: /* going away! */ + dev_err(dev, "RX URB[%u]: goind down %d\n", + rx_buf_idx, urb->status); + goto error; + default: + dev_err(dev, "RX URB[%u]: unknown status %d\n", + rx_buf_idx, urb->status); + if (edc_inc(&i1480u->rx_errors, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "RX: max acceptable errors exceeded," + " resetting device.\n"); + i1480u_rx_unlink_urbs(i1480u); + wlp_reset_all(&i1480u->wlp); + goto error; + } + do_parse_buffer = 0; + break; + } + spin_lock_irqsave(&i1480u->lock, flags); + /* chew the data fragments, extract network packets */ + if (do_parse_buffer) { + i1480u_rx_buffer(rx_buf); + if (rx_buf->data) { + rx_buf->urb->transfer_buffer = rx_buf->data->data; + result = usb_submit_urb(rx_buf->urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "RX URB[%u]: cannot submit %d\n", + rx_buf_idx, result); + } + } + } + spin_unlock_irqrestore(&i1480u->lock, flags); +error: + return; +} + diff --git a/drivers/uwb/i1480/i1480u-wlp/sysfs.c b/drivers/uwb/i1480/i1480u-wlp/sysfs.c new file mode 100644 index 00000000000..a1d8ca6ac93 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/sysfs.c @@ -0,0 +1,408 @@ +/* + * WUSB Wire Adapter: WLP interface + * Sysfs interfaces + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/uwb/debug.h> +#include <linux/device.h> +#include "i1480u-wlp.h" + + +/** + * + * @dev: Class device from the net_device; assumed refcnted. + * + * Yes, I don't lock--we assume it is refcounted and I am getting a + * single byte value that is kind of atomic to read. + */ +ssize_t uwb_phy_rate_show(const struct wlp_options *options, char *buf) +{ + return sprintf(buf, "%u\n", + wlp_tx_hdr_phy_rate(&options->def_tx_hdr)); +} +EXPORT_SYMBOL_GPL(uwb_phy_rate_show); + + +ssize_t uwb_phy_rate_store(struct wlp_options *options, + const char *buf, size_t size) +{ + ssize_t result; + unsigned rate; + + result = sscanf(buf, "%u\n", &rate); + if (result != 1) { + result = -EINVAL; + goto out; + } + result = -EINVAL; + if (rate >= UWB_PHY_RATE_INVALID) + goto out; + wlp_tx_hdr_set_phy_rate(&options->def_tx_hdr, rate); + result = 0; +out: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(uwb_phy_rate_store); + + +ssize_t uwb_rts_cts_show(const struct wlp_options *options, char *buf) +{ + return sprintf(buf, "%u\n", + wlp_tx_hdr_rts_cts(&options->def_tx_hdr)); +} +EXPORT_SYMBOL_GPL(uwb_rts_cts_show); + + +ssize_t uwb_rts_cts_store(struct wlp_options *options, + const char *buf, size_t size) +{ + ssize_t result; + unsigned value; + + result = sscanf(buf, "%u\n", &value); + if (result != 1) { + result = -EINVAL; + goto out; + } + result = -EINVAL; + wlp_tx_hdr_set_rts_cts(&options->def_tx_hdr, !!value); + result = 0; +out: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(uwb_rts_cts_store); + + +ssize_t uwb_ack_policy_show(const struct wlp_options *options, char *buf) +{ + return sprintf(buf, "%u\n", + wlp_tx_hdr_ack_policy(&options->def_tx_hdr)); +} +EXPORT_SYMBOL_GPL(uwb_ack_policy_show); + + +ssize_t uwb_ack_policy_store(struct wlp_options *options, + const char *buf, size_t size) +{ + ssize_t result; + unsigned value; + + result = sscanf(buf, "%u\n", &value); + if (result != 1 || value > UWB_ACK_B_REQ) { + result = -EINVAL; + goto out; + } + wlp_tx_hdr_set_ack_policy(&options->def_tx_hdr, value); + result = 0; +out: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(uwb_ack_policy_store); + + +/** + * Show the PCA base priority. + * + * We can access without locking, as the value is (for now) orthogonal + * to other values. + */ +ssize_t uwb_pca_base_priority_show(const struct wlp_options *options, + char *buf) +{ + return sprintf(buf, "%u\n", + options->pca_base_priority); +} +EXPORT_SYMBOL_GPL(uwb_pca_base_priority_show); + + +/** + * Set the PCA base priority. + * + * We can access without locking, as the value is (for now) orthogonal + * to other values. + */ +ssize_t uwb_pca_base_priority_store(struct wlp_options *options, + const char *buf, size_t size) +{ + ssize_t result = -EINVAL; + u8 pca_base_priority; + + result = sscanf(buf, "%hhu\n", &pca_base_priority); + if (result != 1) { + result = -EINVAL; + goto out; + } + result = -EINVAL; + if (pca_base_priority >= 8) + goto out; + options->pca_base_priority = pca_base_priority; + /* Update TX header if we are currently using PCA. */ + if (result >= 0 && (wlp_tx_hdr_delivery_id_type(&options->def_tx_hdr) & WLP_DRP) == 0) + wlp_tx_hdr_set_delivery_id_type(&options->def_tx_hdr, options->pca_base_priority); + result = 0; +out: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(uwb_pca_base_priority_store); + +/** + * Show current inflight values + * + * Will print the current MAX and THRESHOLD values for the basic flow + * control. In addition it will report how many times the TX queue needed + * to be restarted since the last time this query was made. + */ +static ssize_t wlp_tx_inflight_show(struct i1480u_tx_inflight *inflight, + char *buf) +{ + ssize_t result; + unsigned long sec_elapsed = (jiffies - inflight->restart_ts)/HZ; + unsigned long restart_count = atomic_read(&inflight->restart_count); + + result = scnprintf(buf, PAGE_SIZE, "%lu %lu %d %lu %lu %lu\n" + "#read: threshold max inflight_count restarts " + "seconds restarts/sec\n" + "#write: threshold max\n", + inflight->threshold, inflight->max, + atomic_read(&inflight->count), + restart_count, sec_elapsed, + sec_elapsed == 0 ? 0 : restart_count/sec_elapsed); + inflight->restart_ts = jiffies; + atomic_set(&inflight->restart_count, 0); + return result; +} + +static +ssize_t wlp_tx_inflight_store(struct i1480u_tx_inflight *inflight, + const char *buf, size_t size) +{ + unsigned long in_threshold, in_max; + ssize_t result; + result = sscanf(buf, "%lu %lu", &in_threshold, &in_max); + if (result != 2) + return -EINVAL; + if (in_max <= in_threshold) + return -EINVAL; + inflight->max = in_max; + inflight->threshold = in_threshold; + return size; +} +/* + * Glue (or function adaptors) for accesing info on sysfs + * + * [we need this indirection because the PCI driver does almost the + * same] + * + * Linux 2.6.21 changed how 'struct netdevice' does attributes (from + * having a 'struct class_dev' to having a 'struct device'). That is + * quite of a pain. + * + * So we try to abstract that here. i1480u_SHOW() and i1480u_STORE() + * create adaptors for extracting the 'struct i1480u' from a 'struct + * dev' and calling a function for doing a sysfs operation (as we have + * them factorized already). i1480u_ATTR creates the attribute file + * (CLASS_DEVICE_ATTR or DEVICE_ATTR) and i1480u_ATTR_NAME produces a + * class_device_attr_NAME or device_attr_NAME (for group registration). + */ +#include <linux/version.h> + +#define i1480u_SHOW(name, fn, param) \ +static ssize_t i1480u_show_##name(struct device *dev, \ + struct device_attribute *attr,\ + char *buf) \ +{ \ + struct i1480u *i1480u = netdev_priv(to_net_dev(dev)); \ + return fn(&i1480u->param, buf); \ +} + +#define i1480u_STORE(name, fn, param) \ +static ssize_t i1480u_store_##name(struct device *dev, \ + struct device_attribute *attr,\ + const char *buf, size_t size)\ +{ \ + struct i1480u *i1480u = netdev_priv(to_net_dev(dev)); \ + return fn(&i1480u->param, buf, size); \ +} + +#define i1480u_ATTR(name, perm) static DEVICE_ATTR(name, perm, \ + i1480u_show_##name,\ + i1480u_store_##name) + +#define i1480u_ATTR_SHOW(name) static DEVICE_ATTR(name, \ + S_IRUGO, \ + i1480u_show_##name, NULL) + +#define i1480u_ATTR_NAME(a) (dev_attr_##a) + + +/* + * Sysfs adaptors + */ +i1480u_SHOW(uwb_phy_rate, uwb_phy_rate_show, options); +i1480u_STORE(uwb_phy_rate, uwb_phy_rate_store, options); +i1480u_ATTR(uwb_phy_rate, S_IRUGO | S_IWUSR); + +i1480u_SHOW(uwb_rts_cts, uwb_rts_cts_show, options); +i1480u_STORE(uwb_rts_cts, uwb_rts_cts_store, options); +i1480u_ATTR(uwb_rts_cts, S_IRUGO | S_IWUSR); + +i1480u_SHOW(uwb_ack_policy, uwb_ack_policy_show, options); +i1480u_STORE(uwb_ack_policy, uwb_ack_policy_store, options); +i1480u_ATTR(uwb_ack_policy, S_IRUGO | S_IWUSR); + +i1480u_SHOW(uwb_pca_base_priority, uwb_pca_base_priority_show, options); +i1480u_STORE(uwb_pca_base_priority, uwb_pca_base_priority_store, options); +i1480u_ATTR(uwb_pca_base_priority, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_eda, wlp_eda_show, wlp); +i1480u_STORE(wlp_eda, wlp_eda_store, wlp); +i1480u_ATTR(wlp_eda, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_uuid, wlp_uuid_show, wlp); +i1480u_STORE(wlp_uuid, wlp_uuid_store, wlp); +i1480u_ATTR(wlp_uuid, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_name, wlp_dev_name_show, wlp); +i1480u_STORE(wlp_dev_name, wlp_dev_name_store, wlp); +i1480u_ATTR(wlp_dev_name, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_manufacturer, wlp_dev_manufacturer_show, wlp); +i1480u_STORE(wlp_dev_manufacturer, wlp_dev_manufacturer_store, wlp); +i1480u_ATTR(wlp_dev_manufacturer, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_model_name, wlp_dev_model_name_show, wlp); +i1480u_STORE(wlp_dev_model_name, wlp_dev_model_name_store, wlp); +i1480u_ATTR(wlp_dev_model_name, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_model_nr, wlp_dev_model_nr_show, wlp); +i1480u_STORE(wlp_dev_model_nr, wlp_dev_model_nr_store, wlp); +i1480u_ATTR(wlp_dev_model_nr, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_serial, wlp_dev_serial_show, wlp); +i1480u_STORE(wlp_dev_serial, wlp_dev_serial_store, wlp); +i1480u_ATTR(wlp_dev_serial, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_prim_category, wlp_dev_prim_category_show, wlp); +i1480u_STORE(wlp_dev_prim_category, wlp_dev_prim_category_store, wlp); +i1480u_ATTR(wlp_dev_prim_category, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_prim_OUI, wlp_dev_prim_OUI_show, wlp); +i1480u_STORE(wlp_dev_prim_OUI, wlp_dev_prim_OUI_store, wlp); +i1480u_ATTR(wlp_dev_prim_OUI, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_prim_OUI_sub, wlp_dev_prim_OUI_sub_show, wlp); +i1480u_STORE(wlp_dev_prim_OUI_sub, wlp_dev_prim_OUI_sub_store, wlp); +i1480u_ATTR(wlp_dev_prim_OUI_sub, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_prim_subcat, wlp_dev_prim_subcat_show, wlp); +i1480u_STORE(wlp_dev_prim_subcat, wlp_dev_prim_subcat_store, wlp); +i1480u_ATTR(wlp_dev_prim_subcat, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_neighborhood, wlp_neighborhood_show, wlp); +i1480u_ATTR_SHOW(wlp_neighborhood); + +i1480u_SHOW(wss_activate, wlp_wss_activate_show, wlp.wss); +i1480u_STORE(wss_activate, wlp_wss_activate_store, wlp.wss); +i1480u_ATTR(wss_activate, S_IRUGO | S_IWUSR); + +/* + * Show the (min, max, avg) Line Quality Estimate (LQE, in dB) as over + * the last 256 received WLP frames (ECMA-368 13.3). + * + * [the -7dB that have to be substracted from the LQI to make the LQE + * are already taken into account]. + */ +i1480u_SHOW(wlp_lqe, stats_show, lqe_stats); +i1480u_STORE(wlp_lqe, stats_store, lqe_stats); +i1480u_ATTR(wlp_lqe, S_IRUGO | S_IWUSR); + +/* + * Show the Receive Signal Strength Indicator averaged over all the + * received WLP frames (ECMA-368 13.3). Still is not clear what + * this value is, but is kind of a percentage of the signal strength + * at the antenna. + */ +i1480u_SHOW(wlp_rssi, stats_show, rssi_stats); +i1480u_STORE(wlp_rssi, stats_store, rssi_stats); +i1480u_ATTR(wlp_rssi, S_IRUGO | S_IWUSR); + +/** + * We maintain a basic flow control counter. "count" how many TX URBs are + * outstanding. Only allow "max" + * TX URBs to be outstanding. If this value is reached the queue will be + * stopped. The queue will be restarted when there are + * "threshold" URBs outstanding. + */ +i1480u_SHOW(wlp_tx_inflight, wlp_tx_inflight_show, tx_inflight); +i1480u_STORE(wlp_tx_inflight, wlp_tx_inflight_store, tx_inflight); +i1480u_ATTR(wlp_tx_inflight, S_IRUGO | S_IWUSR); + +static struct attribute *i1480u_attrs[] = { + &i1480u_ATTR_NAME(uwb_phy_rate).attr, + &i1480u_ATTR_NAME(uwb_rts_cts).attr, + &i1480u_ATTR_NAME(uwb_ack_policy).attr, + &i1480u_ATTR_NAME(uwb_pca_base_priority).attr, + &i1480u_ATTR_NAME(wlp_lqe).attr, + &i1480u_ATTR_NAME(wlp_rssi).attr, + &i1480u_ATTR_NAME(wlp_eda).attr, + &i1480u_ATTR_NAME(wlp_uuid).attr, + &i1480u_ATTR_NAME(wlp_dev_name).attr, + &i1480u_ATTR_NAME(wlp_dev_manufacturer).attr, + &i1480u_ATTR_NAME(wlp_dev_model_name).attr, + &i1480u_ATTR_NAME(wlp_dev_model_nr).attr, + &i1480u_ATTR_NAME(wlp_dev_serial).attr, + &i1480u_ATTR_NAME(wlp_dev_prim_category).attr, + &i1480u_ATTR_NAME(wlp_dev_prim_OUI).attr, + &i1480u_ATTR_NAME(wlp_dev_prim_OUI_sub).attr, + &i1480u_ATTR_NAME(wlp_dev_prim_subcat).attr, + &i1480u_ATTR_NAME(wlp_neighborhood).attr, + &i1480u_ATTR_NAME(wss_activate).attr, + &i1480u_ATTR_NAME(wlp_tx_inflight).attr, + NULL, +}; + +static struct attribute_group i1480u_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = i1480u_attrs, +}; + +int i1480u_sysfs_setup(struct i1480u *i1480u) +{ + int result; + struct device *dev = &i1480u->usb_iface->dev; + result = sysfs_create_group(&i1480u->net_dev->dev.kobj, + &i1480u_attr_group); + if (result < 0) + dev_err(dev, "cannot initialize sysfs attributes: %d\n", + result); + return result; +} + + +void i1480u_sysfs_release(struct i1480u *i1480u) +{ + sysfs_remove_group(&i1480u->net_dev->dev.kobj, + &i1480u_attr_group); +} diff --git a/drivers/uwb/i1480/i1480u-wlp/tx.c b/drivers/uwb/i1480/i1480u-wlp/tx.c new file mode 100644 index 00000000000..3426bfb6824 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/tx.c @@ -0,0 +1,632 @@ +/* + * WUSB Wire Adapter: WLP interface + * Deal with TX (massaging data to transmit, handling it) + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Transmission engine. Get an skb, create from that a WLP transmit + * context, add a WLP TX header (which we keep prefilled in the + * device's instance), fill out the target-specific fields and + * fire it. + * + * ROADMAP: + * + * Entry points: + * + * i1480u_tx_release(): called by i1480u_disconnect() to release + * pending tx contexts. + * + * i1480u_tx_cb(): callback for TX contexts (USB URBs) + * i1480u_tx_destroy(): + * + * i1480u_tx_timeout(): called for timeout handling from the + * network stack. + * + * i1480u_hard_start_xmit(): called for transmitting an skb from + * the network stack. Will interact with WLP + * substack to verify and prepare frame. + * i1480u_xmit_frame(): actual transmission on hardware + * + * i1480u_tx_create() Creates TX context + * i1480u_tx_create_1() For packets in 1 fragment + * i1480u_tx_create_n() For packets in >1 fragments + * + * TODO: + * + * - FIXME: rewrite using usb_sg_*(), add asynch support to + * usb_sg_*(). It might not make too much sense as most of + * the times the MTU will be smaller than one page... + */ + +#include "i1480u-wlp.h" +#define D_LOCAL 5 +#include <linux/uwb/debug.h> + +enum { + /* This is only for Next and Last TX packets */ + i1480u_MAX_PL_SIZE = i1480u_MAX_FRG_SIZE + - sizeof(struct untd_hdr_rst), +}; + +/** Free resources allocated to a i1480u tx context. */ +static +void i1480u_tx_free(struct i1480u_tx *wtx) +{ + kfree(wtx->buf); + if (wtx->skb) + dev_kfree_skb_irq(wtx->skb); + usb_free_urb(wtx->urb); + kfree(wtx); +} + +static +void i1480u_tx_destroy(struct i1480u *i1480u, struct i1480u_tx *wtx) +{ + unsigned long flags; + spin_lock_irqsave(&i1480u->tx_list_lock, flags); /* not active any more */ + list_del(&wtx->list_node); + i1480u_tx_free(wtx); + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); +} + +static +void i1480u_tx_unlink_urbs(struct i1480u *i1480u) +{ + unsigned long flags; + struct i1480u_tx *wtx, *next; + + spin_lock_irqsave(&i1480u->tx_list_lock, flags); + list_for_each_entry_safe(wtx, next, &i1480u->tx_list, list_node) { + usb_unlink_urb(wtx->urb); + } + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); +} + + +/** + * Callback for a completed tx USB URB. + * + * TODO: + * + * - FIXME: recover errors more gracefully + * - FIXME: handle NAKs (I dont think they come here) for flow ctl + */ +static +void i1480u_tx_cb(struct urb *urb) +{ + struct i1480u_tx *wtx = urb->context; + struct i1480u *i1480u = wtx->i1480u; + struct net_device *net_dev = i1480u->net_dev; + struct device *dev = &i1480u->usb_iface->dev; + unsigned long flags; + + switch (urb->status) { + case 0: + spin_lock_irqsave(&i1480u->lock, flags); + i1480u->stats.tx_packets++; + i1480u->stats.tx_bytes += urb->actual_length; + spin_unlock_irqrestore(&i1480u->lock, flags); + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + dev_dbg(dev, "notif endp: reset/noent %d\n", urb->status); + netif_stop_queue(net_dev); + break; + case -ESHUTDOWN: /* going away! */ + dev_dbg(dev, "notif endp: down %d\n", urb->status); + netif_stop_queue(net_dev); + break; + default: + dev_err(dev, "TX: unknown URB status %d\n", urb->status); + if (edc_inc(&i1480u->tx_errors, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "TX: max acceptable errors exceeded." + "Reset device.\n"); + netif_stop_queue(net_dev); + i1480u_tx_unlink_urbs(i1480u); + wlp_reset_all(&i1480u->wlp); + } + break; + } + i1480u_tx_destroy(i1480u, wtx); + if (atomic_dec_return(&i1480u->tx_inflight.count) + <= i1480u->tx_inflight.threshold + && netif_queue_stopped(net_dev) + && i1480u->tx_inflight.threshold != 0) { + if (d_test(2) && printk_ratelimit()) + d_printf(2, dev, "Restart queue. \n"); + netif_start_queue(net_dev); + atomic_inc(&i1480u->tx_inflight.restart_count); + } + return; +} + + +/** + * Given a buffer that doesn't fit in a single fragment, create an + * scatter/gather structure for delivery to the USB pipe. + * + * Implements functionality of i1480u_tx_create(). + * + * @wtx: tx descriptor + * @skb: skb to send + * @gfp_mask: gfp allocation mask + * @returns: Pointer to @wtx if ok, NULL on error. + * + * Sorry, TOO LONG a function, but breaking it up is kind of hard + * + * This will break the buffer in chunks smaller than + * i1480u_MAX_FRG_SIZE (including the header) and add proper headers + * to each: + * + * 1st header \ + * i1480 tx header | fragment 1 + * fragment data / + * nxt header \ fragment 2 + * fragment data / + * .. + * .. + * last header \ fragment 3 + * last fragment data / + * + * This does not fill the i1480 TX header, it is left up to the + * caller to do that; you can get it from @wtx->wlp_tx_hdr. + * + * This function consumes the skb unless there is an error. + */ +static +int i1480u_tx_create_n(struct i1480u_tx *wtx, struct sk_buff *skb, + gfp_t gfp_mask) +{ + int result; + void *pl; + size_t pl_size; + + void *pl_itr, *buf_itr; + size_t pl_size_left, frgs, pl_size_1st, frg_pl_size = 0; + struct untd_hdr_1st *untd_hdr_1st; + struct wlp_tx_hdr *wlp_tx_hdr; + struct untd_hdr_rst *untd_hdr_rst; + + wtx->skb = NULL; + pl = skb->data; + pl_itr = pl; + pl_size = skb->len; + pl_size_left = pl_size; /* payload size */ + /* First fragment; fits as much as i1480u_MAX_FRG_SIZE minus + * the headers */ + pl_size_1st = i1480u_MAX_FRG_SIZE + - sizeof(struct untd_hdr_1st) - sizeof(struct wlp_tx_hdr); + BUG_ON(pl_size_1st > pl_size); + pl_size_left -= pl_size_1st; + /* The rest have an smaller header (no i1480 TX header). We + * need to break up the payload in blocks smaller than + * i1480u_MAX_PL_SIZE (payload excluding header). */ + frgs = (pl_size_left + i1480u_MAX_PL_SIZE - 1) / i1480u_MAX_PL_SIZE; + /* Allocate space for the new buffer. In this new buffer we'll + * place the headers followed by the data fragment, headers, + * data fragments, etc.. + */ + result = -ENOMEM; + wtx->buf_size = sizeof(*untd_hdr_1st) + + sizeof(*wlp_tx_hdr) + + frgs * sizeof(*untd_hdr_rst) + + pl_size; + wtx->buf = kmalloc(wtx->buf_size, gfp_mask); + if (wtx->buf == NULL) + goto error_buf_alloc; + + buf_itr = wtx->buf; /* We got the space, let's fill it up */ + /* Fill 1st fragment */ + untd_hdr_1st = buf_itr; + buf_itr += sizeof(*untd_hdr_1st); + untd_hdr_set_type(&untd_hdr_1st->hdr, i1480u_PKT_FRAG_1ST); + untd_hdr_set_rx_tx(&untd_hdr_1st->hdr, 0); + untd_hdr_1st->hdr.len = cpu_to_le16(pl_size + sizeof(*wlp_tx_hdr)); + untd_hdr_1st->fragment_len = + cpu_to_le16(pl_size_1st + sizeof(*wlp_tx_hdr)); + memset(untd_hdr_1st->padding, 0, sizeof(untd_hdr_1st->padding)); + /* Set up i1480 header info */ + wlp_tx_hdr = wtx->wlp_tx_hdr = buf_itr; + buf_itr += sizeof(*wlp_tx_hdr); + /* Copy the first fragment */ + memcpy(buf_itr, pl_itr, pl_size_1st); + pl_itr += pl_size_1st; + buf_itr += pl_size_1st; + + /* Now do each remaining fragment */ + result = -EINVAL; + while (pl_size_left > 0) { + d_printf(5, NULL, "ITR HDR: pl_size_left %zu buf_itr %zu\n", + pl_size_left, buf_itr - wtx->buf); + if (buf_itr + sizeof(*untd_hdr_rst) - wtx->buf + > wtx->buf_size) { + printk(KERN_ERR "BUG: no space for header\n"); + goto error_bug; + } + d_printf(5, NULL, "ITR HDR 2: pl_size_left %zu buf_itr %zu\n", + pl_size_left, buf_itr - wtx->buf); + untd_hdr_rst = buf_itr; + buf_itr += sizeof(*untd_hdr_rst); + if (pl_size_left > i1480u_MAX_PL_SIZE) { + frg_pl_size = i1480u_MAX_PL_SIZE; + untd_hdr_set_type(&untd_hdr_rst->hdr, i1480u_PKT_FRAG_NXT); + } else { + frg_pl_size = pl_size_left; + untd_hdr_set_type(&untd_hdr_rst->hdr, i1480u_PKT_FRAG_LST); + } + d_printf(5, NULL, + "ITR PL: pl_size_left %zu buf_itr %zu frg_pl_size %zu\n", + pl_size_left, buf_itr - wtx->buf, frg_pl_size); + untd_hdr_set_rx_tx(&untd_hdr_rst->hdr, 0); + untd_hdr_rst->hdr.len = cpu_to_le16(frg_pl_size); + untd_hdr_rst->padding = 0; + if (buf_itr + frg_pl_size - wtx->buf + > wtx->buf_size) { + printk(KERN_ERR "BUG: no space for payload\n"); + goto error_bug; + } + memcpy(buf_itr, pl_itr, frg_pl_size); + buf_itr += frg_pl_size; + pl_itr += frg_pl_size; + pl_size_left -= frg_pl_size; + d_printf(5, NULL, + "ITR PL 2: pl_size_left %zu buf_itr %zu frg_pl_size %zu\n", + pl_size_left, buf_itr - wtx->buf, frg_pl_size); + } + dev_kfree_skb_irq(skb); + return 0; + +error_bug: + printk(KERN_ERR + "BUG: skb %u bytes\n" + "BUG: frg_pl_size %zd i1480u_MAX_FRG_SIZE %u\n" + "BUG: buf_itr %zu buf_size %zu pl_size_left %zu\n", + skb->len, + frg_pl_size, i1480u_MAX_FRG_SIZE, + buf_itr - wtx->buf, wtx->buf_size, pl_size_left); + + kfree(wtx->buf); +error_buf_alloc: + return result; +} + + +/** + * Given a buffer that fits in a single fragment, fill out a @wtx + * struct for transmitting it down the USB pipe. + * + * Uses the fact that we have space reserved in front of the skbuff + * for hardware headers :] + * + * This does not fill the i1480 TX header, it is left up to the + * caller to do that; you can get it from @wtx->wlp_tx_hdr. + * + * @pl: pointer to payload data + * @pl_size: size of the payuload + * + * This function does not consume the @skb. + */ +static +int i1480u_tx_create_1(struct i1480u_tx *wtx, struct sk_buff *skb, + gfp_t gfp_mask) +{ + struct untd_hdr_cmp *untd_hdr_cmp; + struct wlp_tx_hdr *wlp_tx_hdr; + + wtx->buf = NULL; + wtx->skb = skb; + BUG_ON(skb_headroom(skb) < sizeof(*wlp_tx_hdr)); + wlp_tx_hdr = (void *) __skb_push(skb, sizeof(*wlp_tx_hdr)); + wtx->wlp_tx_hdr = wlp_tx_hdr; + BUG_ON(skb_headroom(skb) < sizeof(*untd_hdr_cmp)); + untd_hdr_cmp = (void *) __skb_push(skb, sizeof(*untd_hdr_cmp)); + + untd_hdr_set_type(&untd_hdr_cmp->hdr, i1480u_PKT_FRAG_CMP); + untd_hdr_set_rx_tx(&untd_hdr_cmp->hdr, 0); + untd_hdr_cmp->hdr.len = cpu_to_le16(skb->len - sizeof(*untd_hdr_cmp)); + untd_hdr_cmp->padding = 0; + return 0; +} + + +/** + * Given a skb to transmit, massage it to become palatable for the TX pipe + * + * This will break the buffer in chunks smaller than + * i1480u_MAX_FRG_SIZE and add proper headers to each. + * + * 1st header \ + * i1480 tx header | fragment 1 + * fragment data / + * nxt header \ fragment 2 + * fragment data / + * .. + * .. + * last header \ fragment 3 + * last fragment data / + * + * Each fragment will be always smaller or equal to i1480u_MAX_FRG_SIZE. + * + * If the first fragment is smaller than i1480u_MAX_FRG_SIZE, then the + * following is composed: + * + * complete header \ + * i1480 tx header | single fragment + * packet data / + * + * We were going to use s/g support, but because the interface is + * synch and at the end there is plenty of overhead to do it, it + * didn't seem that worth for data that is going to be smaller than + * one page. + */ +static +struct i1480u_tx *i1480u_tx_create(struct i1480u *i1480u, + struct sk_buff *skb, gfp_t gfp_mask) +{ + int result; + struct usb_endpoint_descriptor *epd; + int usb_pipe; + unsigned long flags; + + struct i1480u_tx *wtx; + const size_t pl_max_size = + i1480u_MAX_FRG_SIZE - sizeof(struct untd_hdr_cmp) + - sizeof(struct wlp_tx_hdr); + + wtx = kmalloc(sizeof(*wtx), gfp_mask); + if (wtx == NULL) + goto error_wtx_alloc; + wtx->urb = usb_alloc_urb(0, gfp_mask); + if (wtx->urb == NULL) + goto error_urb_alloc; + epd = &i1480u->usb_iface->cur_altsetting->endpoint[2].desc; + usb_pipe = usb_sndbulkpipe(i1480u->usb_dev, epd->bEndpointAddress); + /* Fits in a single complete packet or need to split? */ + if (skb->len > pl_max_size) { + result = i1480u_tx_create_n(wtx, skb, gfp_mask); + if (result < 0) + goto error_create; + usb_fill_bulk_urb(wtx->urb, i1480u->usb_dev, usb_pipe, + wtx->buf, wtx->buf_size, i1480u_tx_cb, wtx); + } else { + result = i1480u_tx_create_1(wtx, skb, gfp_mask); + if (result < 0) + goto error_create; + usb_fill_bulk_urb(wtx->urb, i1480u->usb_dev, usb_pipe, + skb->data, skb->len, i1480u_tx_cb, wtx); + } + spin_lock_irqsave(&i1480u->tx_list_lock, flags); + list_add(&wtx->list_node, &i1480u->tx_list); + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); + return wtx; + +error_create: + kfree(wtx->urb); +error_urb_alloc: + kfree(wtx); +error_wtx_alloc: + return NULL; +} + +/** + * Actual fragmentation and transmission of frame + * + * @wlp: WLP substack data structure + * @skb: To be transmitted + * @dst: Device address of destination + * @returns: 0 on success, <0 on failure + * + * This function can also be called directly (not just from + * hard_start_xmit), so we also check here if the interface is up before + * taking sending anything. + */ +int i1480u_xmit_frame(struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *dst) +{ + int result = -ENXIO; + struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp); + struct device *dev = &i1480u->usb_iface->dev; + struct net_device *net_dev = i1480u->net_dev; + struct i1480u_tx *wtx; + struct wlp_tx_hdr *wlp_tx_hdr; + static unsigned char dev_bcast[2] = { 0xff, 0xff }; +#if 0 + int lockup = 50; +#endif + + d_fnstart(6, dev, "(skb %p (%u), net_dev %p)\n", skb, skb->len, + net_dev); + BUG_ON(i1480u->wlp.rc == NULL); + if ((net_dev->flags & IFF_UP) == 0) + goto out; + result = -EBUSY; + if (atomic_read(&i1480u->tx_inflight.count) >= i1480u->tx_inflight.max) { + if (d_test(2) && printk_ratelimit()) + d_printf(2, dev, "Max frames in flight " + "stopping queue.\n"); + netif_stop_queue(net_dev); + goto error_max_inflight; + } + result = -ENOMEM; + wtx = i1480u_tx_create(i1480u, skb, GFP_ATOMIC); + if (unlikely(wtx == NULL)) { + if (printk_ratelimit()) + dev_err(dev, "TX: no memory for WLP TX URB," + "dropping packet (in flight %d)\n", + atomic_read(&i1480u->tx_inflight.count)); + netif_stop_queue(net_dev); + goto error_wtx_alloc; + } + wtx->i1480u = i1480u; + /* Fill out the i1480 header; @i1480u->def_tx_hdr read without + * locking. We do so because they are kind of orthogonal to + * each other (and thus not changed in an atomic batch). + * The ETH header is right after the WLP TX header. */ + wlp_tx_hdr = wtx->wlp_tx_hdr; + *wlp_tx_hdr = i1480u->options.def_tx_hdr; + wlp_tx_hdr->dstaddr = *dst; + if (!memcmp(&wlp_tx_hdr->dstaddr, dev_bcast, sizeof(dev_bcast)) + && (wlp_tx_hdr_delivery_id_type(wlp_tx_hdr) & WLP_DRP)) { + /*Broadcast message directed to DRP host. Send as best effort + * on PCA. */ + wlp_tx_hdr_set_delivery_id_type(wlp_tx_hdr, i1480u->options.pca_base_priority); + } + +#if 0 + dev_info(dev, "TX delivering skb -> USB, %zu bytes\n", skb->len); + dump_bytes(dev, skb->data, skb->len > 72 ? 72 : skb->len); +#endif +#if 0 + /* simulates a device lockup after every lockup# packets */ + if (lockup && ((i1480u->stats.tx_packets + 1) % lockup) == 0) { + /* Simulate a dropped transmit interrupt */ + net_dev->trans_start = jiffies; + netif_stop_queue(net_dev); + dev_err(dev, "Simulate lockup at %ld\n", jiffies); + return result; + } +#endif + + result = usb_submit_urb(wtx->urb, GFP_ATOMIC); /* Go baby */ + if (result < 0) { + dev_err(dev, "TX: cannot submit URB: %d\n", result); + /* We leave the freeing of skb to calling function */ + wtx->skb = NULL; + goto error_tx_urb_submit; + } + atomic_inc(&i1480u->tx_inflight.count); + net_dev->trans_start = jiffies; + d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len, + net_dev, result); + return result; + +error_tx_urb_submit: + i1480u_tx_destroy(i1480u, wtx); +error_wtx_alloc: +error_max_inflight: +out: + d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len, + net_dev, result); + return result; +} + + +/** + * Transmit an skb Called when an skbuf has to be transmitted + * + * The skb is first passed to WLP substack to ensure this is a valid + * frame. If valid the device address of destination will be filled and + * the WLP header prepended to the skb. If this step fails we fake sending + * the frame, if we return an error the network stack will just keep trying. + * + * Broadcast frames inside a WSS needs to be treated special as multicast is + * not supported. A broadcast frame is sent as unicast to each member of the + * WSS - this is done by the WLP substack when it finds a broadcast frame. + * So, we test if the WLP substack took over the skb and only transmit it + * if it has not (been taken over). + * + * @net_dev->xmit_lock is held + */ +int i1480u_hard_start_xmit(struct sk_buff *skb, struct net_device *net_dev) +{ + int result; + struct i1480u *i1480u = netdev_priv(net_dev); + struct device *dev = &i1480u->usb_iface->dev; + struct uwb_dev_addr dst; + + d_fnstart(6, dev, "(skb %p (%u), net_dev %p)\n", skb, skb->len, + net_dev); + BUG_ON(i1480u->wlp.rc == NULL); + if ((net_dev->flags & IFF_UP) == 0) + goto error; + result = wlp_prepare_tx_frame(dev, &i1480u->wlp, skb, &dst); + if (result < 0) { + dev_err(dev, "WLP verification of TX frame failed (%d). " + "Dropping packet.\n", result); + goto error; + } else if (result == 1) { + d_printf(6, dev, "WLP will transmit frame. \n"); + /* trans_start time will be set when WLP actually transmits + * the frame */ + goto out; + } + d_printf(6, dev, "Transmitting frame. \n"); + result = i1480u_xmit_frame(&i1480u->wlp, skb, &dst); + if (result < 0) { + dev_err(dev, "Frame TX failed (%d).\n", result); + goto error; + } + d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len, + net_dev, result); + return NETDEV_TX_OK; +error: + dev_kfree_skb_any(skb); + i1480u->stats.tx_dropped++; +out: + d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len, + net_dev, result); + return NETDEV_TX_OK; +} + + +/** + * Called when a pkt transmission doesn't complete in a reasonable period + * Device reset may sleep - do it outside of interrupt context (delayed) + */ +void i1480u_tx_timeout(struct net_device *net_dev) +{ + struct i1480u *i1480u = netdev_priv(net_dev); + + wlp_reset_all(&i1480u->wlp); +} + + +void i1480u_tx_release(struct i1480u *i1480u) +{ + unsigned long flags; + struct i1480u_tx *wtx, *next; + int count = 0, empty; + + spin_lock_irqsave(&i1480u->tx_list_lock, flags); + list_for_each_entry_safe(wtx, next, &i1480u->tx_list, list_node) { + count++; + usb_unlink_urb(wtx->urb); + } + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); + count = count*10; /* i1480ut 200ms per unlinked urb (intervals of 20ms) */ + /* + * We don't like this sollution too much (dirty as it is), but + * it is cheaper than putting a refcount on each i1480u_tx and + * i1480uting for all of them to go away... + * + * Called when no more packets can be added to tx_list + * so can i1480ut for it to be empty. + */ + while (1) { + spin_lock_irqsave(&i1480u->tx_list_lock, flags); + empty = list_empty(&i1480u->tx_list); + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); + if (empty) + break; + count--; + BUG_ON(count == 0); + msleep(20); + } +} diff --git a/drivers/uwb/ie.c b/drivers/uwb/ie.c new file mode 100644 index 00000000000..cf6f3d152b9 --- /dev/null +++ b/drivers/uwb/ie.c @@ -0,0 +1,541 @@ +/* + * Ultra Wide Band + * Information Element Handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include "uwb-internal.h" +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +/** + * uwb_ie_next - get the next IE in a buffer + * @ptr: start of the buffer containing the IE data + * @len: length of the buffer + * + * Both @ptr and @len are updated so subsequent calls to uwb_ie_next() + * will get the next IE. + * + * NULL is returned (and @ptr and @len will not be updated) if there + * are no more IEs in the buffer or the buffer is too short. + */ +struct uwb_ie_hdr *uwb_ie_next(void **ptr, size_t *len) +{ + struct uwb_ie_hdr *hdr; + size_t ie_len; + + if (*len < sizeof(struct uwb_ie_hdr)) + return NULL; + + hdr = *ptr; + ie_len = sizeof(struct uwb_ie_hdr) + hdr->length; + + if (*len < ie_len) + return NULL; + + *ptr += ie_len; + *len -= ie_len; + + return hdr; +} +EXPORT_SYMBOL_GPL(uwb_ie_next); + +/** + * Get the IEs that a radio controller is sending in its beacon + * + * @uwb_rc: UWB Radio Controller + * @returns: Size read from the system + * + * We don't need to lock the uwb_rc's mutex because we don't modify + * anything. Once done with the iedata buffer, call + * uwb_rc_ie_release(iedata). Don't call kfree on it. + */ +ssize_t uwb_rc_get_ie(struct uwb_rc *uwb_rc, struct uwb_rc_evt_get_ie **pget_ie) +{ + ssize_t result; + struct device *dev = &uwb_rc->uwb_dev.dev; + struct uwb_rccb *cmd = NULL; + struct uwb_rceb *reply = NULL; + struct uwb_rc_evt_get_ie *get_ie; + + d_fnstart(3, dev, "(%p, %p)\n", uwb_rc, pget_ie); + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + cmd->bCommandType = UWB_RC_CET_GENERAL; + cmd->wCommand = cpu_to_le16(UWB_RC_CMD_GET_IE); + result = uwb_rc_vcmd(uwb_rc, "GET_IE", cmd, sizeof(*cmd), + UWB_RC_CET_GENERAL, UWB_RC_CMD_GET_IE, + &reply); + if (result < 0) + goto error_cmd; + get_ie = container_of(reply, struct uwb_rc_evt_get_ie, rceb); + if (result < sizeof(*get_ie)) { + dev_err(dev, "not enough data returned for decoding GET IE " + "(%zu bytes received vs %zu needed)\n", + result, sizeof(*get_ie)); + result = -EINVAL; + } else if (result < sizeof(*get_ie) + le16_to_cpu(get_ie->wIELength)) { + dev_err(dev, "not enough data returned for decoding GET IE " + "payload (%zu bytes received vs %zu needed)\n", result, + sizeof(*get_ie) + le16_to_cpu(get_ie->wIELength)); + result = -EINVAL; + } else + *pget_ie = get_ie; +error_cmd: + kfree(cmd); +error_kzalloc: + d_fnend(3, dev, "(%p, %p) = %d\n", uwb_rc, pget_ie, (int)result); + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_get_ie); + + +/* + * Given a pointer to an IE, print it in ASCII/hex followed by a new line + * + * @ie_hdr: pointer to the IE header. Length is in there, and it is + * guaranteed that the ie_hdr->length bytes following it are + * safely accesible. + * + * @_data: context data passed from uwb_ie_for_each(), an struct output_ctx + */ +int uwb_ie_dump_hex(struct uwb_dev *uwb_dev, const struct uwb_ie_hdr *ie_hdr, + size_t offset, void *_ctx) +{ + struct uwb_buf_ctx *ctx = _ctx; + const u8 *pl = (void *)(ie_hdr + 1); + u8 pl_itr; + + ctx->bytes += scnprintf(ctx->buf + ctx->bytes, ctx->size - ctx->bytes, + "%02x %02x ", (unsigned) ie_hdr->element_id, + (unsigned) ie_hdr->length); + pl_itr = 0; + while (pl_itr < ie_hdr->length && ctx->bytes < ctx->size) + ctx->bytes += scnprintf(ctx->buf + ctx->bytes, + ctx->size - ctx->bytes, + "%02x ", (unsigned) pl[pl_itr++]); + if (ctx->bytes < ctx->size) + ctx->buf[ctx->bytes++] = '\n'; + return 0; +} +EXPORT_SYMBOL_GPL(uwb_ie_dump_hex); + + +/** + * Verify that a pointer in a buffer points to valid IE + * + * @start: pointer to start of buffer in which IE appears + * @itr: pointer to IE inside buffer that will be verified + * @top: pointer to end of buffer + * + * @returns: 0 if IE is valid, <0 otherwise + * + * Verification involves checking that the buffer can contain a + * header and the amount of data reported in the IE header can be found in + * the buffer. + */ +static +int uwb_rc_ie_verify(struct uwb_dev *uwb_dev, const void *start, + const void *itr, const void *top) +{ + struct device *dev = &uwb_dev->dev; + const struct uwb_ie_hdr *ie_hdr; + + if (top - itr < sizeof(*ie_hdr)) { + dev_err(dev, "Bad IE: no data to decode header " + "(%zu bytes left vs %zu needed) at offset %zu\n", + top - itr, sizeof(*ie_hdr), itr - start); + return -EINVAL; + } + ie_hdr = itr; + itr += sizeof(*ie_hdr); + if (top - itr < ie_hdr->length) { + dev_err(dev, "Bad IE: not enough data for payload " + "(%zu bytes left vs %zu needed) at offset %zu\n", + top - itr, (size_t)ie_hdr->length, + (void *)ie_hdr - start); + return -EINVAL; + } + return 0; +} + + +/** + * Walk a buffer filled with consecutive IE's a buffer + * + * @uwb_dev: UWB device this IEs belong to (for err messages mainly) + * + * @fn: function to call with each IE; if it returns 0, we keep + * traversing the buffer. If it returns !0, we'll stop and return + * that value. + * + * @data: pointer passed to @fn + * + * @buf: buffer where the consecutive IEs are located + * + * @size: size of @buf + * + * Each IE is checked for basic correctness (there is space left for + * the header and the payload). If that test is failed, we stop + * processing. For every good IE, @fn is called. + */ +ssize_t uwb_ie_for_each(struct uwb_dev *uwb_dev, uwb_ie_f fn, void *data, + const void *buf, size_t size) +{ + ssize_t result = 0; + const struct uwb_ie_hdr *ie_hdr; + const void *itr = buf, *top = itr + size; + + while (itr < top) { + if (uwb_rc_ie_verify(uwb_dev, buf, itr, top) != 0) + break; + ie_hdr = itr; + itr += sizeof(*ie_hdr) + ie_hdr->length; + result = fn(uwb_dev, ie_hdr, itr - buf, data); + if (result != 0) + break; + } + return result; +} +EXPORT_SYMBOL_GPL(uwb_ie_for_each); + + +/** + * Replace all IEs currently being transmitted by a device + * + * @cmd: pointer to the SET-IE command with the IEs to set + * @size: size of @buf + */ +int uwb_rc_set_ie(struct uwb_rc *rc, struct uwb_rc_cmd_set_ie *cmd) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_evt_set_ie reply; + + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_SET_IE; + result = uwb_rc_cmd(rc, "SET-IE", &cmd->rccb, + sizeof(*cmd) + le16_to_cpu(cmd->wIELength), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + else if (result != sizeof(reply)) { + dev_err(dev, "SET-IE: not enough data to decode reply " + "(%d bytes received vs %zu needed)\n", + result, sizeof(reply)); + result = -EIO; + } else if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(dev, "SET-IE: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + } else + result = 0; +error_cmd: + return result; +} + +/** + * Determine by IE id if IE is host settable + * WUSB 1.0 [8.6.2.8 Table 8.85] + * + * EXCEPTION: + * All but UWB_IE_WLP appears in Table 8.85 from WUSB 1.0. Setting this IE + * is required for the WLP substack to perform association with its WSS so + * we hope that the WUSB spec will be changed to reflect this. + */ +static +int uwb_rc_ie_is_host_settable(enum uwb_ie element_id) +{ + if (element_id == UWB_PCA_AVAILABILITY || + element_id == UWB_BP_SWITCH_IE || + element_id == UWB_MAC_CAPABILITIES_IE || + element_id == UWB_PHY_CAPABILITIES_IE || + element_id == UWB_APP_SPEC_PROBE_IE || + element_id == UWB_IDENTIFICATION_IE || + element_id == UWB_MASTER_KEY_ID_IE || + element_id == UWB_IE_WLP || + element_id == UWB_APP_SPEC_IE) + return 1; + return 0; +} + + +/** + * Extract Host Settable IEs from IE + * + * @ie_data: pointer to buffer containing all IEs + * @size: size of buffer + * + * @returns: length of buffer that only includes host settable IEs + * + * Given a buffer of IEs we move all Host Settable IEs to front of buffer + * by overwriting the IEs that are not Host Settable. + * Buffer length is adjusted accordingly. + */ +static +ssize_t uwb_rc_parse_host_settable_ie(struct uwb_dev *uwb_dev, + void *ie_data, size_t size) +{ + size_t new_len = size; + struct uwb_ie_hdr *ie_hdr; + size_t ie_length; + void *itr = ie_data, *top = itr + size; + + while (itr < top) { + if (uwb_rc_ie_verify(uwb_dev, ie_data, itr, top) != 0) + break; + ie_hdr = itr; + ie_length = sizeof(*ie_hdr) + ie_hdr->length; + if (uwb_rc_ie_is_host_settable(ie_hdr->element_id)) { + itr += ie_length; + } else { + memmove(itr, itr + ie_length, top - (itr + ie_length)); + new_len -= ie_length; + top -= ie_length; + } + } + return new_len; +} + + +/* Cleanup the whole IE management subsystem */ +void uwb_rc_ie_init(struct uwb_rc *uwb_rc) +{ + mutex_init(&uwb_rc->ies_mutex); +} + + +/** + * Set up cache for host settable IEs currently being transmitted + * + * First we just call GET-IE to get the current IEs being transmitted + * (or we workaround and pretend we did) and (because the format is + * the same) reuse that as the IE cache (with the command prefix, as + * explained in 'struct uwb_rc'). + * + * @returns: size of cache created + */ +ssize_t uwb_rc_ie_setup(struct uwb_rc *uwb_rc) +{ + struct device *dev = &uwb_rc->uwb_dev.dev; + ssize_t result; + size_t capacity; + struct uwb_rc_evt_get_ie *ie_info; + + d_fnstart(3, dev, "(%p)\n", uwb_rc); + mutex_lock(&uwb_rc->ies_mutex); + result = uwb_rc_get_ie(uwb_rc, &ie_info); + if (result < 0) + goto error_get_ie; + capacity = result; + d_printf(5, dev, "Got IEs %zu bytes (%zu long at %p)\n", result, + (size_t)le16_to_cpu(ie_info->wIELength), ie_info); + + /* Remove IEs that host should not set. */ + result = uwb_rc_parse_host_settable_ie(&uwb_rc->uwb_dev, + ie_info->IEData, le16_to_cpu(ie_info->wIELength)); + if (result < 0) + goto error_parse; + d_printf(5, dev, "purged non-settable IEs to %zu bytes\n", result); + uwb_rc->ies = (void *) ie_info; + uwb_rc->ies->rccb.bCommandType = UWB_RC_CET_GENERAL; + uwb_rc->ies->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SET_IE); + uwb_rc->ies_capacity = capacity; + d_printf(5, dev, "IE cache at %p %zu bytes, %zu capacity\n", + ie_info, result, capacity); + result = 0; +error_parse: +error_get_ie: + mutex_unlock(&uwb_rc->ies_mutex); + d_fnend(3, dev, "(%p) = %zu\n", uwb_rc, result); + return result; +} + + +/* Cleanup the whole IE management subsystem */ +void uwb_rc_ie_release(struct uwb_rc *uwb_rc) +{ + kfree(uwb_rc->ies); + uwb_rc->ies = NULL; + uwb_rc->ies_capacity = 0; +} + + +static +int __acc_size(struct uwb_dev *uwb_dev, const struct uwb_ie_hdr *ie_hdr, + size_t offset, void *_ctx) +{ + size_t *acc_size = _ctx; + *acc_size += sizeof(*ie_hdr) + ie_hdr->length; + d_printf(6, &uwb_dev->dev, "new acc size %zu\n", *acc_size); + return 0; +} + + +/** + * Add a new IE to IEs currently being transmitted by device + * + * @ies: the buffer containing the new IE or IEs to be added to + * the device's beacon. The buffer will be verified for + * consistence (meaning the headers should be right) and + * consistent with the buffer size. + * @size: size of @ies (in bytes, total buffer size) + * @returns: 0 if ok, <0 errno code on error + * + * According to WHCI 0.95 [4.13.6] the driver will only receive the RCEB + * after the device sent the first beacon that includes the IEs specified + * in the SET IE command. We thus cannot send this command if the device is + * not beaconing. Instead, a SET IE command will be sent later right after + * we start beaconing. + * + * Setting an IE on the device will overwrite all current IEs in device. So + * we take the current IEs being transmitted by the device, append the + * new one, and call SET IE with all the IEs needed. + * + * The local IE cache will only be updated with the new IE if SET IE + * completed successfully. + */ +int uwb_rc_ie_add(struct uwb_rc *uwb_rc, + const struct uwb_ie_hdr *ies, size_t size) +{ + int result = 0; + struct device *dev = &uwb_rc->uwb_dev.dev; + struct uwb_rc_cmd_set_ie *new_ies; + size_t ies_size, total_size, acc_size = 0; + + if (uwb_rc->ies == NULL) + return -ESHUTDOWN; + uwb_ie_for_each(&uwb_rc->uwb_dev, __acc_size, &acc_size, ies, size); + if (acc_size != size) { + dev_err(dev, "BUG: bad IEs, misconstructed headers " + "[%zu bytes reported vs %zu calculated]\n", + size, acc_size); + WARN_ON(1); + return -EINVAL; + } + mutex_lock(&uwb_rc->ies_mutex); + ies_size = le16_to_cpu(uwb_rc->ies->wIELength); + total_size = sizeof(*uwb_rc->ies) + ies_size; + if (total_size + size > uwb_rc->ies_capacity) { + d_printf(4, dev, "Reallocating IE cache from %p capacity %zu " + "to capacity %zu\n", uwb_rc->ies, uwb_rc->ies_capacity, + total_size + size); + new_ies = kzalloc(total_size + size, GFP_KERNEL); + if (new_ies == NULL) { + dev_err(dev, "No memory for adding new IE\n"); + result = -ENOMEM; + goto error_alloc; + } + memcpy(new_ies, uwb_rc->ies, total_size); + uwb_rc->ies_capacity = total_size + size; + kfree(uwb_rc->ies); + uwb_rc->ies = new_ies; + d_printf(4, dev, "New IE cache at %p capacity %zu\n", + uwb_rc->ies, uwb_rc->ies_capacity); + } + memcpy((void *)uwb_rc->ies + total_size, ies, size); + uwb_rc->ies->wIELength = cpu_to_le16(ies_size + size); + if (uwb_rc->beaconing != -1) { + result = uwb_rc_set_ie(uwb_rc, uwb_rc->ies); + if (result < 0) { + dev_err(dev, "Cannot set new IE on device: %d\n", + result); + uwb_rc->ies->wIELength = cpu_to_le16(ies_size); + } else + result = 0; + } + d_printf(4, dev, "IEs now occupy %hu bytes of %zu capacity at %p\n", + le16_to_cpu(uwb_rc->ies->wIELength), uwb_rc->ies_capacity, + uwb_rc->ies); +error_alloc: + mutex_unlock(&uwb_rc->ies_mutex); + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_ie_add); + + +/* + * Remove an IE from internal cache + * + * We are dealing with our internal IE cache so no need to verify that the + * IEs are valid (it has been done already). + * + * Should be called with ies_mutex held + * + * We do not break out once an IE is found in the cache. It is currently + * possible to have more than one IE with the same ID included in the + * beacon. We don't reallocate, we just mark the size smaller. + */ +static +int uwb_rc_ie_cache_rm(struct uwb_rc *uwb_rc, enum uwb_ie to_remove) +{ + struct uwb_ie_hdr *ie_hdr; + size_t new_len = le16_to_cpu(uwb_rc->ies->wIELength); + void *itr = uwb_rc->ies->IEData; + void *top = itr + new_len; + + while (itr < top) { + ie_hdr = itr; + if (ie_hdr->element_id != to_remove) { + itr += sizeof(*ie_hdr) + ie_hdr->length; + } else { + int ie_length; + ie_length = sizeof(*ie_hdr) + ie_hdr->length; + if (top - itr != ie_length) + memmove(itr, itr + ie_length, top - itr + ie_length); + top -= ie_length; + new_len -= ie_length; + } + } + uwb_rc->ies->wIELength = cpu_to_le16(new_len); + return 0; +} + + +/** + * Remove an IE currently being transmitted by device + * + * @element_id: id of IE to be removed from device's beacon + */ +int uwb_rc_ie_rm(struct uwb_rc *uwb_rc, enum uwb_ie element_id) +{ + struct device *dev = &uwb_rc->uwb_dev.dev; + int result; + + if (uwb_rc->ies == NULL) + return -ESHUTDOWN; + mutex_lock(&uwb_rc->ies_mutex); + result = uwb_rc_ie_cache_rm(uwb_rc, element_id); + if (result < 0) + dev_err(dev, "Cannot remove IE from cache.\n"); + if (uwb_rc->beaconing != -1) { + result = uwb_rc_set_ie(uwb_rc, uwb_rc->ies); + if (result < 0) + dev_err(dev, "Cannot set new IE on device.\n"); + } + mutex_unlock(&uwb_rc->ies_mutex); + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_ie_rm); diff --git a/drivers/uwb/lc-dev.c b/drivers/uwb/lc-dev.c new file mode 100644 index 00000000000..15f856c9689 --- /dev/null +++ b/drivers/uwb/lc-dev.c @@ -0,0 +1,492 @@ +/* + * Ultra Wide Band + * Life cycle of devices + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kdev_t.h> +#include <linux/random.h> +#include "uwb-internal.h" + +#define D_LOCAL 1 +#include <linux/uwb/debug.h> + + +/* We initialize addresses to 0xff (invalid, as it is bcast) */ +static inline void uwb_dev_addr_init(struct uwb_dev_addr *addr) +{ + memset(&addr->data, 0xff, sizeof(addr->data)); +} + +static inline void uwb_mac_addr_init(struct uwb_mac_addr *addr) +{ + memset(&addr->data, 0xff, sizeof(addr->data)); +} + +/* @returns !0 if a device @addr is a broadcast address */ +static inline int uwb_dev_addr_bcast(const struct uwb_dev_addr *addr) +{ + static const struct uwb_dev_addr bcast = { .data = { 0xff, 0xff } }; + return !uwb_dev_addr_cmp(addr, &bcast); +} + +/* + * Add callback @new to be called when an event occurs in @rc. + */ +int uwb_notifs_register(struct uwb_rc *rc, struct uwb_notifs_handler *new) +{ + if (mutex_lock_interruptible(&rc->notifs_chain.mutex)) + return -ERESTARTSYS; + list_add(&new->list_node, &rc->notifs_chain.list); + mutex_unlock(&rc->notifs_chain.mutex); + return 0; +} +EXPORT_SYMBOL_GPL(uwb_notifs_register); + +/* + * Remove event handler (callback) + */ +int uwb_notifs_deregister(struct uwb_rc *rc, struct uwb_notifs_handler *entry) +{ + if (mutex_lock_interruptible(&rc->notifs_chain.mutex)) + return -ERESTARTSYS; + list_del(&entry->list_node); + mutex_unlock(&rc->notifs_chain.mutex); + return 0; +} +EXPORT_SYMBOL_GPL(uwb_notifs_deregister); + +/* + * Notify all event handlers of a given event on @rc + * + * We are called with a valid reference to the device, or NULL if the + * event is not for a particular event (e.g., a BG join event). + */ +void uwb_notify(struct uwb_rc *rc, struct uwb_dev *uwb_dev, enum uwb_notifs event) +{ + struct uwb_notifs_handler *handler; + if (mutex_lock_interruptible(&rc->notifs_chain.mutex)) + return; + if (!list_empty(&rc->notifs_chain.list)) { + list_for_each_entry(handler, &rc->notifs_chain.list, list_node) { + handler->cb(handler->data, uwb_dev, event); + } + } + mutex_unlock(&rc->notifs_chain.mutex); +} + +/* + * Release the backing device of a uwb_dev that has been dynamically allocated. + */ +static void uwb_dev_sys_release(struct device *dev) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + + d_fnstart(4, NULL, "(dev %p uwb_dev %p)\n", dev, uwb_dev); + uwb_bce_put(uwb_dev->bce); + d_printf(0, &uwb_dev->dev, "uwb_dev %p freed\n", uwb_dev); + memset(uwb_dev, 0x69, sizeof(*uwb_dev)); + kfree(uwb_dev); + d_fnend(4, NULL, "(dev %p uwb_dev %p) = void\n", dev, uwb_dev); +} + +/* + * Initialize a UWB device instance + * + * Alloc, zero and call this function. + */ +void uwb_dev_init(struct uwb_dev *uwb_dev) +{ + mutex_init(&uwb_dev->mutex); + device_initialize(&uwb_dev->dev); + uwb_dev->dev.release = uwb_dev_sys_release; + uwb_dev_addr_init(&uwb_dev->dev_addr); + uwb_mac_addr_init(&uwb_dev->mac_addr); + bitmap_fill(uwb_dev->streams, UWB_NUM_GLOBAL_STREAMS); +} + +static ssize_t uwb_dev_EUI_48_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + char addr[UWB_ADDR_STRSIZE]; + + uwb_mac_addr_print(addr, sizeof(addr), &uwb_dev->mac_addr); + return sprintf(buf, "%s\n", addr); +} +static DEVICE_ATTR(EUI_48, S_IRUGO, uwb_dev_EUI_48_show, NULL); + +static ssize_t uwb_dev_DevAddr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + char addr[UWB_ADDR_STRSIZE]; + + uwb_dev_addr_print(addr, sizeof(addr), &uwb_dev->dev_addr); + return sprintf(buf, "%s\n", addr); +} +static DEVICE_ATTR(DevAddr, S_IRUGO, uwb_dev_DevAddr_show, NULL); + +/* + * Show the BPST of this device. + * + * Calculated from the receive time of the device's beacon and it's + * slot number. + */ +static ssize_t uwb_dev_BPST_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce; + struct uwb_beacon_frame *bf; + u16 bpst; + + bce = uwb_dev->bce; + mutex_lock(&bce->mutex); + bf = (struct uwb_beacon_frame *)bce->be->BeaconInfo; + bpst = bce->be->wBPSTOffset + - (u16)(bf->Beacon_Slot_Number * UWB_BEACON_SLOT_LENGTH_US); + mutex_unlock(&bce->mutex); + + return sprintf(buf, "%d\n", bpst); +} +static DEVICE_ATTR(BPST, S_IRUGO, uwb_dev_BPST_show, NULL); + +/* + * Show the IEs a device is beaconing + * + * We need to access the beacon cache, so we just lock it really + * quick, print the IEs and unlock. + * + * We have a reference on the cache entry, so that should be + * quite safe. + */ +static ssize_t uwb_dev_IEs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + + return uwb_bce_print_IEs(uwb_dev, uwb_dev->bce, buf, PAGE_SIZE); +} +static DEVICE_ATTR(IEs, S_IRUGO | S_IWUSR, uwb_dev_IEs_show, NULL); + +static ssize_t uwb_dev_LQE_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce = uwb_dev->bce; + size_t result; + + mutex_lock(&bce->mutex); + result = stats_show(&uwb_dev->bce->lqe_stats, buf); + mutex_unlock(&bce->mutex); + return result; +} + +static ssize_t uwb_dev_LQE_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce = uwb_dev->bce; + ssize_t result; + + mutex_lock(&bce->mutex); + result = stats_store(&uwb_dev->bce->lqe_stats, buf, size); + mutex_unlock(&bce->mutex); + return result; +} +static DEVICE_ATTR(LQE, S_IRUGO | S_IWUSR, uwb_dev_LQE_show, uwb_dev_LQE_store); + +static ssize_t uwb_dev_RSSI_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce = uwb_dev->bce; + size_t result; + + mutex_lock(&bce->mutex); + result = stats_show(&uwb_dev->bce->rssi_stats, buf); + mutex_unlock(&bce->mutex); + return result; +} + +static ssize_t uwb_dev_RSSI_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce = uwb_dev->bce; + ssize_t result; + + mutex_lock(&bce->mutex); + result = stats_store(&uwb_dev->bce->rssi_stats, buf, size); + mutex_unlock(&bce->mutex); + return result; +} +static DEVICE_ATTR(RSSI, S_IRUGO | S_IWUSR, uwb_dev_RSSI_show, uwb_dev_RSSI_store); + + +static struct attribute *dev_attrs[] = { + &dev_attr_EUI_48.attr, + &dev_attr_DevAddr.attr, + &dev_attr_BPST.attr, + &dev_attr_IEs.attr, + &dev_attr_LQE.attr, + &dev_attr_RSSI.attr, + NULL, +}; + +static struct attribute_group dev_attr_group = { + .attrs = dev_attrs, +}; + +static struct attribute_group *groups[] = { + &dev_attr_group, + NULL, +}; + +/** + * Device SYSFS registration + * + * + */ +static int __uwb_dev_sys_add(struct uwb_dev *uwb_dev, struct device *parent_dev) +{ + int result; + struct device *dev; + + d_fnstart(4, NULL, "(uwb_dev %p parent_dev %p)\n", uwb_dev, parent_dev); + BUG_ON(parent_dev == NULL); + + dev = &uwb_dev->dev; + /* Device sysfs files are only useful for neighbor devices not + local radio controllers. */ + if (&uwb_dev->rc->uwb_dev != uwb_dev) + dev->groups = groups; + dev->parent = parent_dev; + dev_set_drvdata(dev, uwb_dev); + + result = device_add(dev); + d_fnend(4, NULL, "(uwb_dev %p parent_dev %p) = %d\n", uwb_dev, parent_dev, result); + return result; +} + + +static void __uwb_dev_sys_rm(struct uwb_dev *uwb_dev) +{ + d_fnstart(4, NULL, "(uwb_dev %p)\n", uwb_dev); + dev_set_drvdata(&uwb_dev->dev, NULL); + device_del(&uwb_dev->dev); + d_fnend(4, NULL, "(uwb_dev %p) = void\n", uwb_dev); +} + + +/** + * Register and initialize a new UWB device + * + * Did you call uwb_dev_init() on it? + * + * @parent_rc: is the parent radio controller who has the link to the + * device. When registering the UWB device that is a UWB + * Radio Controller, we point back to it. + * + * If registering the device that is part of a radio, caller has set + * rc->uwb_dev->dev. Otherwise it is to be left NULL--a new one will + * be allocated. + */ +int uwb_dev_add(struct uwb_dev *uwb_dev, struct device *parent_dev, + struct uwb_rc *parent_rc) +{ + int result; + struct device *dev; + + BUG_ON(uwb_dev == NULL); + BUG_ON(parent_dev == NULL); + BUG_ON(parent_rc == NULL); + + mutex_lock(&uwb_dev->mutex); + dev = &uwb_dev->dev; + uwb_dev->rc = parent_rc; + result = __uwb_dev_sys_add(uwb_dev, parent_dev); + if (result < 0) + printk(KERN_ERR "UWB: unable to register dev %s with sysfs: %d\n", + dev_name(dev), result); + mutex_unlock(&uwb_dev->mutex); + return result; +} + + +void uwb_dev_rm(struct uwb_dev *uwb_dev) +{ + mutex_lock(&uwb_dev->mutex); + __uwb_dev_sys_rm(uwb_dev); + mutex_unlock(&uwb_dev->mutex); +} + + +static +int __uwb_dev_try_get(struct device *dev, void *__target_uwb_dev) +{ + struct uwb_dev *target_uwb_dev = __target_uwb_dev; + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + if (uwb_dev == target_uwb_dev) { + uwb_dev_get(uwb_dev); + return 1; + } else + return 0; +} + + +/** + * Given a UWB device descriptor, validate and refcount it + * + * @returns NULL if the device does not exist or is quiescing; the ptr to + * it otherwise. + */ +struct uwb_dev *uwb_dev_try_get(struct uwb_rc *rc, struct uwb_dev *uwb_dev) +{ + if (uwb_dev_for_each(rc, __uwb_dev_try_get, uwb_dev)) + return uwb_dev; + else + return NULL; +} +EXPORT_SYMBOL_GPL(uwb_dev_try_get); + + +/** + * Remove a device from the system [grunt for other functions] + */ +int __uwb_dev_offair(struct uwb_dev *uwb_dev, struct uwb_rc *rc) +{ + struct device *dev = &uwb_dev->dev; + char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE]; + + d_fnstart(3, NULL, "(dev %p [uwb_dev %p], uwb_rc %p)\n", dev, uwb_dev, rc); + uwb_mac_addr_print(macbuf, sizeof(macbuf), &uwb_dev->mac_addr); + uwb_dev_addr_print(devbuf, sizeof(devbuf), &uwb_dev->dev_addr); + dev_info(dev, "uwb device (mac %s dev %s) disconnected from %s %s\n", + macbuf, devbuf, + rc ? rc->uwb_dev.dev.parent->bus->name : "n/a", + rc ? dev_name(rc->uwb_dev.dev.parent) : ""); + uwb_dev_rm(uwb_dev); + uwb_dev_put(uwb_dev); /* for the creation in _onair() */ + d_fnend(3, NULL, "(dev %p [uwb_dev %p], uwb_rc %p) = 0\n", dev, uwb_dev, rc); + return 0; +} + + +/** + * A device went off the air, clean up after it! + * + * This is called by the UWB Daemon (through the beacon purge function + * uwb_bcn_cache_purge) when it is detected that a device has been in + * radio silence for a while. + * + * If this device is actually a local radio controller we don't need + * to go through the offair process, as it is not registered as that. + * + * NOTE: uwb_bcn_cache.mutex is held! + */ +void uwbd_dev_offair(struct uwb_beca_e *bce) +{ + struct uwb_dev *uwb_dev; + + uwb_dev = bce->uwb_dev; + if (uwb_dev) { + uwb_notify(uwb_dev->rc, uwb_dev, UWB_NOTIF_OFFAIR); + __uwb_dev_offair(uwb_dev, uwb_dev->rc); + } +} + + +/** + * A device went on the air, start it up! + * + * This is called by the UWB Daemon when it is detected that a device + * has popped up in the radio range of the radio controller. + * + * It will just create the freaking device, register the beacon and + * stuff and yatla, done. + * + * + * NOTE: uwb_beca.mutex is held, bce->mutex is held + */ +void uwbd_dev_onair(struct uwb_rc *rc, struct uwb_beca_e *bce) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_dev *uwb_dev; + char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE]; + + uwb_mac_addr_print(macbuf, sizeof(macbuf), bce->mac_addr); + uwb_dev_addr_print(devbuf, sizeof(devbuf), &bce->dev_addr); + uwb_dev = kzalloc(sizeof(struct uwb_dev), GFP_KERNEL); + if (uwb_dev == NULL) { + dev_err(dev, "new device %s: Cannot allocate memory\n", + macbuf); + return; + } + uwb_dev_init(uwb_dev); /* This sets refcnt to one, we own it */ + uwb_dev->mac_addr = *bce->mac_addr; + uwb_dev->dev_addr = bce->dev_addr; + dev_set_name(&uwb_dev->dev, macbuf); + result = uwb_dev_add(uwb_dev, &rc->uwb_dev.dev, rc); + if (result < 0) { + dev_err(dev, "new device %s: cannot instantiate device\n", + macbuf); + goto error_dev_add; + } + /* plug the beacon cache */ + bce->uwb_dev = uwb_dev; + uwb_dev->bce = bce; + uwb_bce_get(bce); /* released in uwb_dev_sys_release() */ + dev_info(dev, "uwb device (mac %s dev %s) connected to %s %s\n", + macbuf, devbuf, rc->uwb_dev.dev.parent->bus->name, + dev_name(rc->uwb_dev.dev.parent)); + uwb_notify(rc, uwb_dev, UWB_NOTIF_ONAIR); + return; + +error_dev_add: + kfree(uwb_dev); + return; +} + +/** + * Iterate over the list of UWB devices, calling a @function on each + * + * See docs for bus_for_each().... + * + * @rc: radio controller for the devices. + * @function: function to call. + * @priv: data to pass to @function. + * @returns: 0 if no invocation of function() returned a value + * different to zero. That value otherwise. + */ +int uwb_dev_for_each(struct uwb_rc *rc, uwb_dev_for_each_f function, void *priv) +{ + return device_for_each_child(&rc->uwb_dev.dev, priv, function); +} +EXPORT_SYMBOL_GPL(uwb_dev_for_each); diff --git a/drivers/uwb/lc-rc.c b/drivers/uwb/lc-rc.c new file mode 100644 index 00000000000..ee5772f00d4 --- /dev/null +++ b/drivers/uwb/lc-rc.c @@ -0,0 +1,495 @@ +/* + * Ultra Wide Band + * Life cycle of radio controllers + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * A UWB radio controller is also a UWB device, so it embeds one... + * + * List of RCs comes from the 'struct class uwb_rc_class'. + */ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/random.h> +#include <linux/kdev_t.h> +#include <linux/etherdevice.h> +#include <linux/usb.h> + +#define D_LOCAL 1 +#include <linux/uwb/debug.h> +#include "uwb-internal.h" + +static int uwb_rc_index_match(struct device *dev, void *data) +{ + int *index = data; + struct uwb_rc *rc = dev_get_drvdata(dev); + + if (rc->index == *index) + return 1; + return 0; +} + +static struct uwb_rc *uwb_rc_find_by_index(int index) +{ + struct device *dev; + struct uwb_rc *rc = NULL; + + dev = class_find_device(&uwb_rc_class, NULL, &index, uwb_rc_index_match); + if (dev) + rc = dev_get_drvdata(dev); + return rc; +} + +static int uwb_rc_new_index(void) +{ + int index = 0; + + for (;;) { + if (!uwb_rc_find_by_index(index)) + return index; + if (++index < 0) + index = 0; + } +} + +/** + * Release the backing device of a uwb_rc that has been dynamically allocated. + */ +static void uwb_rc_sys_release(struct device *dev) +{ + struct uwb_dev *uwb_dev = container_of(dev, struct uwb_dev, dev); + struct uwb_rc *rc = container_of(uwb_dev, struct uwb_rc, uwb_dev); + + uwb_rc_neh_destroy(rc); + uwb_rc_ie_release(rc); + d_printf(1, dev, "freed uwb_rc %p\n", rc); + kfree(rc); +} + + +void uwb_rc_init(struct uwb_rc *rc) +{ + struct uwb_dev *uwb_dev = &rc->uwb_dev; + + uwb_dev_init(uwb_dev); + rc->uwb_dev.dev.class = &uwb_rc_class; + rc->uwb_dev.dev.release = uwb_rc_sys_release; + uwb_rc_neh_create(rc); + rc->beaconing = -1; + rc->scan_type = UWB_SCAN_DISABLED; + INIT_LIST_HEAD(&rc->notifs_chain.list); + mutex_init(&rc->notifs_chain.mutex); + uwb_drp_avail_init(rc); + uwb_rc_ie_init(rc); + uwb_rsv_init(rc); + uwb_rc_pal_init(rc); +} +EXPORT_SYMBOL_GPL(uwb_rc_init); + + +struct uwb_rc *uwb_rc_alloc(void) +{ + struct uwb_rc *rc; + rc = kzalloc(sizeof(*rc), GFP_KERNEL); + if (rc == NULL) + return NULL; + uwb_rc_init(rc); + return rc; +} +EXPORT_SYMBOL_GPL(uwb_rc_alloc); + +static struct attribute *rc_attrs[] = { + &dev_attr_mac_address.attr, + &dev_attr_scan.attr, + &dev_attr_beacon.attr, + NULL, +}; + +static struct attribute_group rc_attr_group = { + .attrs = rc_attrs, +}; + +/* + * Registration of sysfs specific stuff + */ +static int uwb_rc_sys_add(struct uwb_rc *rc) +{ + return sysfs_create_group(&rc->uwb_dev.dev.kobj, &rc_attr_group); +} + + +static void __uwb_rc_sys_rm(struct uwb_rc *rc) +{ + sysfs_remove_group(&rc->uwb_dev.dev.kobj, &rc_attr_group); +} + +/** + * uwb_rc_mac_addr_setup - get an RC's EUI-48 address or set it + * @rc: the radio controller. + * + * If the EUI-48 address is 00:00:00:00:00:00 or FF:FF:FF:FF:FF:FF + * then a random locally administered EUI-48 is generated and set on + * the device. The probability of address collisions is sufficiently + * unlikely (1/2^40 = 9.1e-13) that they're not checked for. + */ +static +int uwb_rc_mac_addr_setup(struct uwb_rc *rc) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_dev *uwb_dev = &rc->uwb_dev; + char devname[UWB_ADDR_STRSIZE]; + struct uwb_mac_addr addr; + + result = uwb_rc_mac_addr_get(rc, &addr); + if (result < 0) { + dev_err(dev, "cannot retrieve UWB EUI-48 address: %d\n", result); + return result; + } + + if (uwb_mac_addr_unset(&addr) || uwb_mac_addr_bcast(&addr)) { + addr.data[0] = 0x02; /* locally adminstered and unicast */ + get_random_bytes(&addr.data[1], sizeof(addr.data)-1); + + result = uwb_rc_mac_addr_set(rc, &addr); + if (result < 0) { + uwb_mac_addr_print(devname, sizeof(devname), &addr); + dev_err(dev, "cannot set EUI-48 address %s: %d\n", + devname, result); + return result; + } + } + uwb_dev->mac_addr = addr; + return 0; +} + + + +static int uwb_rc_setup(struct uwb_rc *rc) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + + result = uwb_rc_reset(rc); + if (result < 0) { + dev_err(dev, "cannot reset UWB radio: %d\n", result); + goto error; + } + result = uwb_rc_mac_addr_setup(rc); + if (result < 0) { + dev_err(dev, "cannot setup UWB MAC address: %d\n", result); + goto error; + } + result = uwb_rc_dev_addr_assign(rc); + if (result < 0) { + dev_err(dev, "cannot assign UWB DevAddr: %d\n", result); + goto error; + } + result = uwb_rc_ie_setup(rc); + if (result < 0) { + dev_err(dev, "cannot setup IE subsystem: %d\n", result); + goto error_ie_setup; + } + result = uwb_rsv_setup(rc); + if (result < 0) { + dev_err(dev, "cannot setup reservation subsystem: %d\n", result); + goto error_rsv_setup; + } + uwb_dbg_add_rc(rc); + return 0; + +error_rsv_setup: + uwb_rc_ie_release(rc); +error_ie_setup: +error: + return result; +} + + +/** + * Register a new UWB radio controller + * + * Did you call uwb_rc_init() on your rc? + * + * We assume that this is being called with a > 0 refcount on + * it [through ops->{get|put}_device(). We'll take our own, though. + * + * @parent_dev is our real device, the one that provides the actual UWB device + */ +int uwb_rc_add(struct uwb_rc *rc, struct device *parent_dev, void *priv) +{ + int result; + struct device *dev; + char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE]; + + rc->index = uwb_rc_new_index(); + + dev = &rc->uwb_dev.dev; + dev_set_name(dev, "uwb%d", rc->index); + + rc->priv = priv; + + result = rc->start(rc); + if (result < 0) + goto error_rc_start; + + result = uwb_rc_setup(rc); + if (result < 0) { + dev_err(dev, "cannot setup UWB radio controller: %d\n", result); + goto error_rc_setup; + } + + result = uwb_dev_add(&rc->uwb_dev, parent_dev, rc); + if (result < 0 && result != -EADDRNOTAVAIL) + goto error_dev_add; + + result = uwb_rc_sys_add(rc); + if (result < 0) { + dev_err(parent_dev, "cannot register UWB radio controller " + "dev attributes: %d\n", result); + goto error_sys_add; + } + + uwb_mac_addr_print(macbuf, sizeof(macbuf), &rc->uwb_dev.mac_addr); + uwb_dev_addr_print(devbuf, sizeof(devbuf), &rc->uwb_dev.dev_addr); + dev_info(dev, + "new uwb radio controller (mac %s dev %s) on %s %s\n", + macbuf, devbuf, parent_dev->bus->name, dev_name(parent_dev)); + rc->ready = 1; + return 0; + +error_sys_add: + uwb_dev_rm(&rc->uwb_dev); +error_dev_add: +error_rc_setup: + rc->stop(rc); + uwbd_flush(rc); +error_rc_start: + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_add); + + +static int uwb_dev_offair_helper(struct device *dev, void *priv) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + + return __uwb_dev_offair(uwb_dev, uwb_dev->rc); +} + +/* + * Remove a Radio Controller; stop beaconing/scanning, disconnect all children + */ +void uwb_rc_rm(struct uwb_rc *rc) +{ + rc->ready = 0; + + uwb_dbg_del_rc(rc); + uwb_rsv_cleanup(rc); + uwb_rc_ie_rm(rc, UWB_IDENTIFICATION_IE); + if (rc->beaconing >= 0) + uwb_rc_beacon(rc, -1, 0); + if (rc->scan_type != UWB_SCAN_DISABLED) + uwb_rc_scan(rc, rc->scanning, UWB_SCAN_DISABLED, 0); + uwb_rc_reset(rc); + + rc->stop(rc); + uwbd_flush(rc); + + uwb_dev_lock(&rc->uwb_dev); + rc->priv = NULL; + rc->cmd = NULL; + uwb_dev_unlock(&rc->uwb_dev); + mutex_lock(&uwb_beca.mutex); + uwb_dev_for_each(rc, uwb_dev_offair_helper, NULL); + __uwb_rc_sys_rm(rc); + mutex_unlock(&uwb_beca.mutex); + uwb_dev_rm(&rc->uwb_dev); +} +EXPORT_SYMBOL_GPL(uwb_rc_rm); + +static int find_rc_try_get(struct device *dev, void *data) +{ + struct uwb_rc *target_rc = data; + struct uwb_rc *rc = dev_get_drvdata(dev); + + if (rc == NULL) { + WARN_ON(1); + return 0; + } + if (rc == target_rc) { + if (rc->ready == 0) + return 0; + else + return 1; + } + return 0; +} + +/** + * Given a radio controller descriptor, validate and refcount it + * + * @returns NULL if the rc does not exist or is quiescing; the ptr to + * it otherwise. + */ +struct uwb_rc *__uwb_rc_try_get(struct uwb_rc *target_rc) +{ + struct device *dev; + struct uwb_rc *rc = NULL; + + dev = class_find_device(&uwb_rc_class, NULL, target_rc, + find_rc_try_get); + if (dev) { + rc = dev_get_drvdata(dev); + __uwb_rc_get(rc); + } + return rc; +} +EXPORT_SYMBOL_GPL(__uwb_rc_try_get); + +/* + * RC get for external refcount acquirers... + * + * Increments the refcount of the device and it's backend modules + */ +static inline struct uwb_rc *uwb_rc_get(struct uwb_rc *rc) +{ + if (rc->ready == 0) + return NULL; + uwb_dev_get(&rc->uwb_dev); + return rc; +} + +static int find_rc_grandpa(struct device *dev, void *data) +{ + struct device *grandpa_dev = data; + struct uwb_rc *rc = dev_get_drvdata(dev); + + if (rc->uwb_dev.dev.parent->parent == grandpa_dev) { + rc = uwb_rc_get(rc); + return 1; + } + return 0; +} + +/** + * Locate and refcount a radio controller given a common grand-parent + * + * @grandpa_dev Pointer to the 'grandparent' device structure. + * @returns NULL If the rc does not exist or is quiescing; the ptr to + * it otherwise, properly referenced. + * + * The Radio Control interface (or the UWB Radio Controller) is always + * an interface of a device. The parent is the interface, the + * grandparent is the device that encapsulates the interface. + * + * There is no need to lock around as the "grandpa" would be + * refcounted by the target, and to remove the referemes, the + * uwb_rc_class->sem would have to be taken--we hold it, ergo we + * should be safe. + */ +struct uwb_rc *uwb_rc_get_by_grandpa(const struct device *grandpa_dev) +{ + struct device *dev; + struct uwb_rc *rc = NULL; + + dev = class_find_device(&uwb_rc_class, NULL, (void *)grandpa_dev, + find_rc_grandpa); + if (dev) + rc = dev_get_drvdata(dev); + return rc; +} +EXPORT_SYMBOL_GPL(uwb_rc_get_by_grandpa); + +/** + * Find a radio controller by device address + * + * @returns the pointer to the radio controller, properly referenced + */ +static int find_rc_dev(struct device *dev, void *data) +{ + struct uwb_dev_addr *addr = data; + struct uwb_rc *rc = dev_get_drvdata(dev); + + if (rc == NULL) { + WARN_ON(1); + return 0; + } + if (!uwb_dev_addr_cmp(&rc->uwb_dev.dev_addr, addr)) { + rc = uwb_rc_get(rc); + return 1; + } + return 0; +} + +struct uwb_rc *uwb_rc_get_by_dev(const struct uwb_dev_addr *addr) +{ + struct device *dev; + struct uwb_rc *rc = NULL; + + dev = class_find_device(&uwb_rc_class, NULL, (void *)addr, + find_rc_dev); + if (dev) + rc = dev_get_drvdata(dev); + + return rc; +} +EXPORT_SYMBOL_GPL(uwb_rc_get_by_dev); + +/** + * Drop a reference on a radio controller + * + * This is the version that should be done by entities external to the + * UWB Radio Control stack (ie: clients of the API). + */ +void uwb_rc_put(struct uwb_rc *rc) +{ + __uwb_rc_put(rc); +} +EXPORT_SYMBOL_GPL(uwb_rc_put); + +/* + * + * + */ +ssize_t uwb_rc_print_IEs(struct uwb_rc *uwb_rc, char *buf, size_t size) +{ + ssize_t result; + struct uwb_rc_evt_get_ie *ie_info; + struct uwb_buf_ctx ctx; + + result = uwb_rc_get_ie(uwb_rc, &ie_info); + if (result < 0) + goto error_get_ie; + ctx.buf = buf; + ctx.size = size; + ctx.bytes = 0; + uwb_ie_for_each(&uwb_rc->uwb_dev, uwb_ie_dump_hex, &ctx, + ie_info->IEData, result - sizeof(*ie_info)); + result = ctx.bytes; + kfree(ie_info); +error_get_ie: + return result; +} + diff --git a/drivers/uwb/neh.c b/drivers/uwb/neh.c new file mode 100644 index 00000000000..9b4eb64327a --- /dev/null +++ b/drivers/uwb/neh.c @@ -0,0 +1,616 @@ +/* + * WUSB Wire Adapter: Radio Control Interface (WUSB[8]) + * Notification and Event Handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * The RC interface of the Host Wire Adapter (USB dongle) or WHCI PCI + * card delivers a stream of notifications and events to the + * notification end event endpoint or area. This code takes care of + * getting a buffer with that data, breaking it up in separate + * notifications and events and then deliver those. + * + * Events are answers to commands and they carry a context ID that + * associates them to the command. Notifications are that, + * notifications, they come out of the blue and have a context ID of + * zero. Think of the context ID kind of like a handler. The + * uwb_rc_neh_* code deals with managing context IDs. + * + * This is why you require a handle to operate on a UWB host. When you + * open a handle a context ID is assigned to you. + * + * So, as it is done is: + * + * 1. Add an event handler [uwb_rc_neh_add()] (assigns a ctx id) + * 2. Issue command [rc->cmd(rc, ...)] + * 3. Arm the timeout timer [uwb_rc_neh_arm()] + * 4, Release the reference to the neh [uwb_rc_neh_put()] + * 5. Wait for the callback + * 6. Command result (RCEB) is passed to the callback + * + * If (2) fails, you should remove the handle [uwb_rc_neh_rm()] + * instead of arming the timer. + * + * Handles are for using in *serialized* code, single thread. + * + * When the notification/event comes, the IRQ handler/endpoint + * callback passes the data read to uwb_rc_neh_grok() which will break + * it up in a discrete series of events, look up who is listening for + * them and execute the pertinent callbacks. + * + * If the reader detects an error while reading the data stream, call + * uwb_rc_neh_error(). + * + * CONSTRAINTS/ASSUMPTIONS: + * + * - Most notifications/events are small (less thank .5k), copying + * around is ok. + * + * - Notifications/events are ALWAYS smaller than PAGE_SIZE + * + * - Notifications/events always come in a single piece (ie: a buffer + * will always contain entire notifications/events). + * + * - we cannot know in advance how long each event is (because they + * lack a length field in their header--smart move by the standards + * body, btw). So we need a facility to get the event size given the + * header. This is what the EST code does (notif/Event Size + * Tables), check nest.c--as well, you can associate the size to + * the handle [w/ neh->extra_size()]. + * + * - Most notifications/events are fixed size; only a few are variable + * size (NEST takes care of that). + * + * - Listeners of events expect them, so they usually provide a + * buffer, as they know the size. Listeners to notifications don't, + * so we allocate their buffers dynamically. + */ +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/err.h> + +#include "uwb-internal.h" +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +/* + * UWB Radio Controller Notification/Event Handle + * + * Represents an entity waiting for an event coming from the UWB Radio + * Controller with a given context id (context) and type (evt_type and + * evt). On reception of the notification/event, the callback (cb) is + * called with the event. + * + * If the timer expires before the event is received, the callback is + * called with -ETIMEDOUT as the event size. + */ +struct uwb_rc_neh { + struct kref kref; + + struct uwb_rc *rc; + u8 evt_type; + __le16 evt; + u8 context; + uwb_rc_cmd_cb_f cb; + void *arg; + + struct timer_list timer; + struct list_head list_node; +}; + +static void uwb_rc_neh_timer(unsigned long arg); + +static void uwb_rc_neh_release(struct kref *kref) +{ + struct uwb_rc_neh *neh = container_of(kref, struct uwb_rc_neh, kref); + + kfree(neh); +} + +static void uwb_rc_neh_get(struct uwb_rc_neh *neh) +{ + kref_get(&neh->kref); +} + +/** + * uwb_rc_neh_put - release reference to a neh + * @neh: the neh + */ +void uwb_rc_neh_put(struct uwb_rc_neh *neh) +{ + kref_put(&neh->kref, uwb_rc_neh_release); +} + + +/** + * Assigns @neh a context id from @rc's pool + * + * @rc: UWB Radio Controller descriptor; @rc->neh_lock taken + * @neh: Notification/Event Handle + * @returns 0 if context id was assigned ok; < 0 errno on error (if + * all the context IDs are taken). + * + * (assumes @wa is locked). + * + * NOTE: WUSB spec reserves context ids 0x00 for notifications and + * 0xff is invalid, so they must not be used. Initialization + * fills up those two in the bitmap so they are not allocated. + * + * We spread the allocation around to reduce the posiblity of two + * consecutive opened @neh's getting the same context ID assigned (to + * avoid surprises with late events that timed out long time ago). So + * first we search from where @rc->ctx_roll is, if not found, we + * search from zero. + */ +static +int __uwb_rc_ctx_get(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + int result; + result = find_next_zero_bit(rc->ctx_bm, UWB_RC_CTX_MAX, + rc->ctx_roll++); + if (result < UWB_RC_CTX_MAX) + goto found; + result = find_first_zero_bit(rc->ctx_bm, UWB_RC_CTX_MAX); + if (result < UWB_RC_CTX_MAX) + goto found; + return -ENFILE; +found: + set_bit(result, rc->ctx_bm); + neh->context = result; + return 0; +} + + +/** Releases @neh's context ID back to @rc (@rc->neh_lock is locked). */ +static +void __uwb_rc_ctx_put(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + struct device *dev = &rc->uwb_dev.dev; + if (neh->context == 0) + return; + if (test_bit(neh->context, rc->ctx_bm) == 0) { + dev_err(dev, "context %u not set in bitmap\n", + neh->context); + WARN_ON(1); + } + clear_bit(neh->context, rc->ctx_bm); + neh->context = 0; +} + +/** + * uwb_rc_neh_add - add a neh for a radio controller command + * @rc: the radio controller + * @cmd: the radio controller command + * @expected_type: the type of the expected response event + * @expected_event: the expected event ID + * @cb: callback for when the event is received + * @arg: argument for the callback + * + * Creates a neh and adds it to the list of those waiting for an + * event. A context ID will be assigned to the command. + */ +struct uwb_rc_neh *uwb_rc_neh_add(struct uwb_rc *rc, struct uwb_rccb *cmd, + u8 expected_type, u16 expected_event, + uwb_rc_cmd_cb_f cb, void *arg) +{ + int result; + unsigned long flags; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_neh *neh; + + neh = kzalloc(sizeof(*neh), GFP_KERNEL); + if (neh == NULL) { + result = -ENOMEM; + goto error_kzalloc; + } + + kref_init(&neh->kref); + INIT_LIST_HEAD(&neh->list_node); + init_timer(&neh->timer); + neh->timer.function = uwb_rc_neh_timer; + neh->timer.data = (unsigned long)neh; + + neh->rc = rc; + neh->evt_type = expected_type; + neh->evt = cpu_to_le16(expected_event); + neh->cb = cb; + neh->arg = arg; + + spin_lock_irqsave(&rc->neh_lock, flags); + result = __uwb_rc_ctx_get(rc, neh); + if (result >= 0) { + cmd->bCommandContext = neh->context; + list_add_tail(&neh->list_node, &rc->neh_list); + uwb_rc_neh_get(neh); + } + spin_unlock_irqrestore(&rc->neh_lock, flags); + if (result < 0) + goto error_ctx_get; + + return neh; + +error_ctx_get: + kfree(neh); +error_kzalloc: + dev_err(dev, "cannot open handle to radio controller: %d\n", result); + return ERR_PTR(result); +} + +static void __uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + del_timer(&neh->timer); + __uwb_rc_ctx_put(rc, neh); + list_del(&neh->list_node); +} + +/** + * uwb_rc_neh_rm - remove a neh. + * @rc: the radio controller + * @neh: the neh to remove + * + * Remove an active neh immediately instead of waiting for the event + * (or a time out). + */ +void uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + unsigned long flags; + + spin_lock_irqsave(&rc->neh_lock, flags); + __uwb_rc_neh_rm(rc, neh); + spin_unlock_irqrestore(&rc->neh_lock, flags); + + uwb_rc_neh_put(neh); +} + +/** + * uwb_rc_neh_arm - arm an event handler timeout timer + * + * @rc: UWB Radio Controller + * @neh: Notification/event handler for @rc + * + * The timer is only armed if the neh is active. + */ +void uwb_rc_neh_arm(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + unsigned long flags; + + spin_lock_irqsave(&rc->neh_lock, flags); + if (neh->context) + mod_timer(&neh->timer, + jiffies + msecs_to_jiffies(UWB_RC_CMD_TIMEOUT_MS)); + spin_unlock_irqrestore(&rc->neh_lock, flags); +} + +static void uwb_rc_neh_cb(struct uwb_rc_neh *neh, struct uwb_rceb *rceb, size_t size) +{ + (*neh->cb)(neh->rc, neh->arg, rceb, size); + uwb_rc_neh_put(neh); +} + +static bool uwb_rc_neh_match(struct uwb_rc_neh *neh, const struct uwb_rceb *rceb) +{ + return neh->evt_type == rceb->bEventType + && neh->evt == rceb->wEvent + && neh->context == rceb->bEventContext; +} + +/** + * Find the handle waiting for a RC Radio Control Event + * + * @rc: UWB Radio Controller + * @rceb: Pointer to the RCEB buffer + * @event_size: Pointer to the size of the RCEB buffer. Might be + * adjusted to take into account the @neh->extra_size + * settings. + * + * If the listener has no buffer (NULL buffer), one is allocated for + * the right size (the amount of data received). @neh->ptr will point + * to the event payload, which always starts with a 'struct + * uwb_rceb'. kfree() it when done. + */ +static +struct uwb_rc_neh *uwb_rc_neh_lookup(struct uwb_rc *rc, + const struct uwb_rceb *rceb) +{ + struct uwb_rc_neh *neh = NULL, *h; + unsigned long flags; + + spin_lock_irqsave(&rc->neh_lock, flags); + + list_for_each_entry(h, &rc->neh_list, list_node) { + if (uwb_rc_neh_match(h, rceb)) { + neh = h; + break; + } + } + + if (neh) + __uwb_rc_neh_rm(rc, neh); + + spin_unlock_irqrestore(&rc->neh_lock, flags); + + return neh; +} + + +/** + * Process notifications coming from the radio control interface + * + * @rc: UWB Radio Control Interface descriptor + * @neh: Notification/Event Handler @neh->ptr points to + * @uwb_evt->buffer. + * + * This function is called by the event/notif handling subsystem when + * notifications arrive (hwarc_probe() arms a notification/event handle + * that calls back this function for every received notification; this + * function then will rearm itself). + * + * Notification data buffers are dynamically allocated by the NEH + * handling code in neh.c [uwb_rc_neh_lookup()]. What is actually + * allocated is space to contain the notification data. + * + * Buffers are prefixed with a Radio Control Event Block (RCEB) as + * defined by the WUSB Wired-Adapter Radio Control interface. We + * just use it for the notification code. + * + * On each case statement we just transcode endianess of the different + * fields. We declare a pointer to a RCI definition of an event, and + * then to a UWB definition of the same event (which are the same, + * remember). Event if we use different pointers + */ +static +void uwb_rc_notif(struct uwb_rc *rc, struct uwb_rceb *rceb, ssize_t size) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_event *uwb_evt; + + if (size == -ESHUTDOWN) + return; + if (size < 0) { + dev_err(dev, "ignoring event with error code %zu\n", + size); + return; + } + + uwb_evt = kzalloc(sizeof(*uwb_evt), GFP_ATOMIC); + if (unlikely(uwb_evt == NULL)) { + dev_err(dev, "no memory to queue event 0x%02x/%04x/%02x\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext); + return; + } + uwb_evt->rc = __uwb_rc_get(rc); /* will be put by uwbd's uwbd_event_handle() */ + uwb_evt->ts_jiffies = jiffies; + uwb_evt->type = UWB_EVT_TYPE_NOTIF; + uwb_evt->notif.size = size; + uwb_evt->notif.rceb = rceb; + + switch (le16_to_cpu(rceb->wEvent)) { + /* Trap some vendor specific events + * + * FIXME: move this to handling in ptc-est, where we + * register a NULL event handler for these two guys + * using the Intel IDs. + */ + case 0x0103: + dev_info(dev, "FIXME: DEVICE ADD\n"); + return; + case 0x0104: + dev_info(dev, "FIXME: DEVICE RM\n"); + return; + default: + break; + } + + uwbd_event_queue(uwb_evt); +} + +static void uwb_rc_neh_grok_event(struct uwb_rc *rc, struct uwb_rceb *rceb, size_t size) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_neh *neh; + struct uwb_rceb *notif; + + if (rceb->bEventContext == 0) { + notif = kmalloc(size, GFP_ATOMIC); + if (notif) { + memcpy(notif, rceb, size); + uwb_rc_notif(rc, notif, size); + } else + dev_err(dev, "event 0x%02x/%04x/%02x (%zu bytes): no memory\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext, size); + } else { + neh = uwb_rc_neh_lookup(rc, rceb); + if (neh) + uwb_rc_neh_cb(neh, rceb, size); + else + dev_warn(dev, "event 0x%02x/%04x/%02x (%zu bytes): nobody cared\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext, size); + } +} + +/** + * Given a buffer with one or more UWB RC events/notifications, break + * them up and dispatch them. + * + * @rc: UWB Radio Controller + * @buf: Buffer with the stream of notifications/events + * @buf_size: Amount of data in the buffer + * + * Note each notification/event starts always with a 'struct + * uwb_rceb', so the minimum size if 4 bytes. + * + * The device may pass us events formatted differently than expected. + * These are first filtered, potentially creating a new event in a new + * memory location. If a new event is created by the filter it is also + * freed here. + * + * For each notif/event, tries to guess the size looking at the EST + * tables, then looks for a neh that is waiting for that event and if + * found, copies the payload to the neh's buffer and calls it back. If + * not, the data is ignored. + * + * Note that if we can't find a size description in the EST tables, we + * still might find a size in the 'neh' handle in uwb_rc_neh_lookup(). + * + * Assumptions: + * + * @rc->neh_lock is NOT taken + * + * We keep track of various sizes here: + * size: contains the size of the buffer that is processed for the + * incoming event. this buffer may contain events that are not + * formatted as WHCI. + * real_size: the actual space taken by this event in the buffer. + * We need to keep track of the real size of an event to be able to + * advance the buffer correctly. + * event_size: the size of the event as expected by the core layer + * [OR] the size of the event after filtering. if the filtering + * created a new event in a new memory location then this is + * effectively the size of a new event buffer + */ +void uwb_rc_neh_grok(struct uwb_rc *rc, void *buf, size_t buf_size) +{ + struct device *dev = &rc->uwb_dev.dev; + void *itr; + struct uwb_rceb *rceb; + size_t size, real_size, event_size; + int needtofree; + + d_fnstart(3, dev, "(rc %p buf %p %zu buf_size)\n", rc, buf, buf_size); + d_printf(2, dev, "groking event block: %zu bytes\n", buf_size); + itr = buf; + size = buf_size; + while (size > 0) { + if (size < sizeof(*rceb)) { + dev_err(dev, "not enough data in event buffer to " + "process incoming events (%zu left, minimum is " + "%zu)\n", size, sizeof(*rceb)); + break; + } + + rceb = itr; + if (rc->filter_event) { + needtofree = rc->filter_event(rc, &rceb, size, + &real_size, &event_size); + if (needtofree < 0 && needtofree != -ENOANO) { + dev_err(dev, "BUG: Unable to filter event " + "(0x%02x/%04x/%02x) from " + "device. \n", rceb->bEventType, + le16_to_cpu(rceb->wEvent), + rceb->bEventContext); + break; + } + } else + needtofree = -ENOANO; + /* do real processing if there was no filtering or the + * filtering didn't act */ + if (needtofree == -ENOANO) { + ssize_t ret = uwb_est_find_size(rc, rceb, size); + if (ret < 0) + break; + if (ret > size) { + dev_err(dev, "BUG: hw sent incomplete event " + "0x%02x/%04x/%02x (%zd bytes), only got " + "%zu bytes. We don't handle that.\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext, ret, size); + break; + } + real_size = event_size = ret; + } + uwb_rc_neh_grok_event(rc, rceb, event_size); + + if (needtofree == 1) + kfree(rceb); + + itr += real_size; + size -= real_size; + d_printf(2, dev, "consumed %zd bytes, %zu left\n", + event_size, size); + } + d_fnend(3, dev, "(rc %p buf %p %zu buf_size) = void\n", rc, buf, buf_size); +} +EXPORT_SYMBOL_GPL(uwb_rc_neh_grok); + + +/** + * The entity that reads from the device notification/event channel has + * detected an error. + * + * @rc: UWB Radio Controller + * @error: Errno error code + * + */ +void uwb_rc_neh_error(struct uwb_rc *rc, int error) +{ + struct uwb_rc_neh *neh, *next; + unsigned long flags; + + BUG_ON(error >= 0); + spin_lock_irqsave(&rc->neh_lock, flags); + list_for_each_entry_safe(neh, next, &rc->neh_list, list_node) { + __uwb_rc_neh_rm(rc, neh); + uwb_rc_neh_cb(neh, NULL, error); + } + spin_unlock_irqrestore(&rc->neh_lock, flags); +} +EXPORT_SYMBOL_GPL(uwb_rc_neh_error); + + +static void uwb_rc_neh_timer(unsigned long arg) +{ + struct uwb_rc_neh *neh = (struct uwb_rc_neh *)arg; + struct uwb_rc *rc = neh->rc; + unsigned long flags; + + spin_lock_irqsave(&rc->neh_lock, flags); + __uwb_rc_neh_rm(rc, neh); + spin_unlock_irqrestore(&rc->neh_lock, flags); + + uwb_rc_neh_cb(neh, NULL, -ETIMEDOUT); +} + +/** Initializes the @rc's neh subsystem + */ +void uwb_rc_neh_create(struct uwb_rc *rc) +{ + spin_lock_init(&rc->neh_lock); + INIT_LIST_HEAD(&rc->neh_list); + set_bit(0, rc->ctx_bm); /* 0 is reserved (see [WUSB] table 8-65) */ + set_bit(0xff, rc->ctx_bm); /* and 0xff is invalid */ + rc->ctx_roll = 1; +} + + +/** Release's the @rc's neh subsystem */ +void uwb_rc_neh_destroy(struct uwb_rc *rc) +{ + unsigned long flags; + struct uwb_rc_neh *neh, *next; + + spin_lock_irqsave(&rc->neh_lock, flags); + list_for_each_entry_safe(neh, next, &rc->neh_list, list_node) { + __uwb_rc_neh_rm(rc, neh); + uwb_rc_neh_put(neh); + } + spin_unlock_irqrestore(&rc->neh_lock, flags); +} diff --git a/drivers/uwb/pal.c b/drivers/uwb/pal.c new file mode 100644 index 00000000000..1afb38eacb9 --- /dev/null +++ b/drivers/uwb/pal.c @@ -0,0 +1,91 @@ +/* + * UWB PAL support. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/uwb.h> + +#include "uwb-internal.h" + +/** + * uwb_pal_init - initialize a UWB PAL + * @pal: the PAL to initialize + */ +void uwb_pal_init(struct uwb_pal *pal) +{ + INIT_LIST_HEAD(&pal->node); +} +EXPORT_SYMBOL_GPL(uwb_pal_init); + +/** + * uwb_pal_register - register a UWB PAL + * @rc: the radio controller the PAL will be using + * @pal: the PAL + * + * The PAL must be initialized with uwb_pal_init(). + */ +int uwb_pal_register(struct uwb_rc *rc, struct uwb_pal *pal) +{ + int ret; + + if (pal->device) { + ret = sysfs_create_link(&pal->device->kobj, + &rc->uwb_dev.dev.kobj, "uwb_rc"); + if (ret < 0) + return ret; + ret = sysfs_create_link(&rc->uwb_dev.dev.kobj, + &pal->device->kobj, pal->name); + if (ret < 0) { + sysfs_remove_link(&pal->device->kobj, "uwb_rc"); + return ret; + } + } + + spin_lock(&rc->pal_lock); + list_add(&pal->node, &rc->pals); + spin_unlock(&rc->pal_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(uwb_pal_register); + +/** + * uwb_pal_register - unregister a UWB PAL + * @rc: the radio controller the PAL was using + * @pal: the PAL + */ +void uwb_pal_unregister(struct uwb_rc *rc, struct uwb_pal *pal) +{ + spin_lock(&rc->pal_lock); + list_del(&pal->node); + spin_unlock(&rc->pal_lock); + + if (pal->device) { + sysfs_remove_link(&rc->uwb_dev.dev.kobj, pal->name); + sysfs_remove_link(&pal->device->kobj, "uwb_rc"); + } +} +EXPORT_SYMBOL_GPL(uwb_pal_unregister); + +/** + * uwb_rc_pal_init - initialize the PAL related parts of a radio controller + * @rc: the radio controller + */ +void uwb_rc_pal_init(struct uwb_rc *rc) +{ + spin_lock_init(&rc->pal_lock); + INIT_LIST_HEAD(&rc->pals); +} diff --git a/drivers/uwb/reset.c b/drivers/uwb/reset.c new file mode 100644 index 00000000000..8de856fa795 --- /dev/null +++ b/drivers/uwb/reset.c @@ -0,0 +1,362 @@ +/* + * Ultra Wide Band + * UWB basic command support and radio reset + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: + * + * - docs + * + * - Now we are serializing (using the uwb_dev->mutex) the command + * execution; it should be parallelized as much as possible some + * day. + */ +#include <linux/kernel.h> +#include <linux/err.h> + +#include "uwb-internal.h" +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +/** + * Command result codes (WUSB1.0[T8-69]) + */ +static +const char *__strerror[] = { + "success", + "failure", + "hardware failure", + "no more slots", + "beacon is too large", + "invalid parameter", + "unsupported power level", + "time out (wa) or invalid ie data (whci)", + "beacon size exceeded", + "cancelled", + "invalid state", + "invalid size", + "ack not recieved", + "no more asie notification", +}; + + +/** Return a string matching the given error code */ +const char *uwb_rc_strerror(unsigned code) +{ + if (code == 255) + return "time out"; + if (code >= ARRAY_SIZE(__strerror)) + return "unknown error"; + return __strerror[code]; +} + +int uwb_rc_cmd_async(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + u8 expected_type, u16 expected_event, + uwb_rc_cmd_cb_f cb, void *arg) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_neh *neh; + int needtofree = 0; + int result; + + uwb_dev_lock(&rc->uwb_dev); /* Protect against rc->priv being removed */ + if (rc->priv == NULL) { + uwb_dev_unlock(&rc->uwb_dev); + return -ESHUTDOWN; + } + + if (rc->filter_cmd) { + needtofree = rc->filter_cmd(rc, &cmd, &cmd_size); + if (needtofree < 0 && needtofree != -ENOANO) { + dev_err(dev, "%s: filter error: %d\n", + cmd_name, needtofree); + uwb_dev_unlock(&rc->uwb_dev); + return needtofree; + } + } + + neh = uwb_rc_neh_add(rc, cmd, expected_type, expected_event, cb, arg); + if (IS_ERR(neh)) { + result = PTR_ERR(neh); + goto out; + } + + result = rc->cmd(rc, cmd, cmd_size); + uwb_dev_unlock(&rc->uwb_dev); + if (result < 0) + uwb_rc_neh_rm(rc, neh); + else + uwb_rc_neh_arm(rc, neh); + uwb_rc_neh_put(neh); +out: + if (needtofree == 1) + kfree(cmd); + return result < 0 ? result : 0; +} +EXPORT_SYMBOL_GPL(uwb_rc_cmd_async); + +struct uwb_rc_cmd_done_params { + struct completion completion; + struct uwb_rceb *reply; + ssize_t reply_size; +}; + +static void uwb_rc_cmd_done(struct uwb_rc *rc, void *arg, + struct uwb_rceb *reply, ssize_t reply_size) +{ + struct uwb_rc_cmd_done_params *p = (struct uwb_rc_cmd_done_params *)arg; + + if (reply_size > 0) { + if (p->reply) + reply_size = min(p->reply_size, reply_size); + else + p->reply = kmalloc(reply_size, GFP_ATOMIC); + + if (p->reply) + memcpy(p->reply, reply, reply_size); + else + reply_size = -ENOMEM; + } + p->reply_size = reply_size; + complete(&p->completion); +} + + +/** + * Generic function for issuing commands to the Radio Control Interface + * + * @rc: UWB Radio Control descriptor + * @cmd_name: Name of the command being issued (for error messages) + * @cmd: Pointer to rccb structure containing the command; + * normally you embed this structure as the first member of + * the full command structure. + * @cmd_size: Size of the whole command buffer pointed to by @cmd. + * @reply: Pointer to where to store the reply + * @reply_size: @reply's size + * @expected_type: Expected type in the return event + * @expected_event: Expected event code in the return event + * @preply: Here a pointer to where the event data is received will + * be stored. Once done with the data, free with kfree(). + * + * This function is generic; it works for commands that return a fixed + * and known size or for commands that return a variable amount of data. + * + * If a buffer is provided, that is used, although it could be chopped + * to the maximum size of the buffer. If the buffer is NULL, then one + * be allocated in *preply with the whole contents of the reply. + * + * @rc needs to be referenced + */ +static +ssize_t __uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + struct uwb_rceb *reply, size_t reply_size, + u8 expected_type, u16 expected_event, + struct uwb_rceb **preply) +{ + ssize_t result = 0; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_cmd_done_params params; + + init_completion(¶ms.completion); + params.reply = reply; + params.reply_size = reply_size; + + result = uwb_rc_cmd_async(rc, cmd_name, cmd, cmd_size, + expected_type, expected_event, + uwb_rc_cmd_done, ¶ms); + if (result) + return result; + + wait_for_completion(¶ms.completion); + + if (preply) + *preply = params.reply; + + if (params.reply_size < 0) + dev_err(dev, "%s: confirmation event 0x%02x/%04x/%02x " + "reception failed: %d\n", cmd_name, + expected_type, expected_event, cmd->bCommandContext, + (int)params.reply_size); + return params.reply_size; +} + + +/** + * Generic function for issuing commands to the Radio Control Interface + * + * @rc: UWB Radio Control descriptor + * @cmd_name: Name of the command being issued (for error messages) + * @cmd: Pointer to rccb structure containing the command; + * normally you embed this structure as the first member of + * the full command structure. + * @cmd_size: Size of the whole command buffer pointed to by @cmd. + * @reply: Pointer to the beginning of the confirmation event + * buffer. Normally bigger than an 'struct hwarc_rceb'. + * You need to fill out reply->bEventType and reply->wEvent (in + * cpu order) as the function will use them to verify the + * confirmation event. + * @reply_size: Size of the reply buffer + * + * The function checks that the length returned in the reply is at + * least as big as @reply_size; if not, it will be deemed an error and + * -EIO returned. + * + * @rc needs to be referenced + */ +ssize_t uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + struct uwb_rceb *reply, size_t reply_size) +{ + struct device *dev = &rc->uwb_dev.dev; + ssize_t result; + + result = __uwb_rc_cmd(rc, cmd_name, + cmd, cmd_size, reply, reply_size, + reply->bEventType, reply->wEvent, NULL); + + if (result > 0 && result < reply_size) { + dev_err(dev, "%s: not enough data returned for decoding reply " + "(%zu bytes received vs at least %zu needed)\n", + cmd_name, result, reply_size); + result = -EIO; + } + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_cmd); + + +/** + * Generic function for issuing commands to the Radio Control + * Interface that return an unknown amount of data + * + * @rc: UWB Radio Control descriptor + * @cmd_name: Name of the command being issued (for error messages) + * @cmd: Pointer to rccb structure containing the command; + * normally you embed this structure as the first member of + * the full command structure. + * @cmd_size: Size of the whole command buffer pointed to by @cmd. + * @expected_type: Expected type in the return event + * @expected_event: Expected event code in the return event + * @preply: Here a pointer to where the event data is received will + * be stored. Once done with the data, free with kfree(). + * + * The function checks that the length returned in the reply is at + * least as big as a 'struct uwb_rceb *'; if not, it will be deemed an + * error and -EIO returned. + * + * @rc needs to be referenced + */ +ssize_t uwb_rc_vcmd(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + u8 expected_type, u16 expected_event, + struct uwb_rceb **preply) +{ + return __uwb_rc_cmd(rc, cmd_name, cmd, cmd_size, NULL, 0, + expected_type, expected_event, preply); +} +EXPORT_SYMBOL_GPL(uwb_rc_vcmd); + + +/** + * Reset a UWB Host Controller (and all radio settings) + * + * @rc: Host Controller descriptor + * @returns: 0 if ok, < 0 errno code on error + * + * We put the command on kmalloc'ed memory as some arches cannot do + * USB from the stack. The reply event is copied from an stage buffer, + * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details. + */ +int uwb_rc_reset(struct uwb_rc *rc) +{ + int result = -ENOMEM; + struct uwb_rc_evt_confirm reply; + struct uwb_rccb *cmd; + size_t cmd_size = sizeof(*cmd); + + mutex_lock(&rc->uwb_dev.mutex); + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + cmd->bCommandType = UWB_RC_CET_GENERAL; + cmd->wCommand = cpu_to_le16(UWB_RC_CMD_RESET); + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_RESET; + result = uwb_rc_cmd(rc, "RESET", cmd, cmd_size, + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "RESET: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + } +error_cmd: + kfree(cmd); +error_kzalloc: + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +int uwbd_msg_handle_reset(struct uwb_event *evt) +{ + struct uwb_rc *rc = evt->rc; + int ret; + + /* Need to prevent the RC hardware module going away while in + the rc->reset() call. */ + if (!try_module_get(rc->owner)) + return 0; + + dev_info(&rc->uwb_dev.dev, "resetting radio controller\n"); + ret = rc->reset(rc); + if (ret) + dev_err(&rc->uwb_dev.dev, "failed to reset hardware: %d\n", ret); + + module_put(rc->owner); + return ret; +} + +/** + * uwb_rc_reset_all - request a reset of the radio controller and PALs + * @rc: the radio controller of the hardware device to be reset. + * + * The full hardware reset of the radio controller and all the PALs + * will be scheduled. + */ +void uwb_rc_reset_all(struct uwb_rc *rc) +{ + struct uwb_event *evt; + + evt = kzalloc(sizeof(struct uwb_event), GFP_ATOMIC); + if (unlikely(evt == NULL)) + return; + + evt->rc = __uwb_rc_get(rc); /* will be put by uwbd's uwbd_event_handle() */ + evt->ts_jiffies = jiffies; + evt->type = UWB_EVT_TYPE_MSG; + evt->message = UWB_EVT_MSG_RESET; + + uwbd_event_queue(evt); +} +EXPORT_SYMBOL_GPL(uwb_rc_reset_all); diff --git a/drivers/uwb/rsv.c b/drivers/uwb/rsv.c new file mode 100644 index 00000000000..bae16204576 --- /dev/null +++ b/drivers/uwb/rsv.c @@ -0,0 +1,680 @@ +/* + * UWB reservation management. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/uwb.h> + +#include "uwb-internal.h" + +static void uwb_rsv_timer(unsigned long arg); + +static const char *rsv_states[] = { + [UWB_RSV_STATE_NONE] = "none", + [UWB_RSV_STATE_O_INITIATED] = "initiated", + [UWB_RSV_STATE_O_PENDING] = "pending", + [UWB_RSV_STATE_O_MODIFIED] = "modified", + [UWB_RSV_STATE_O_ESTABLISHED] = "established", + [UWB_RSV_STATE_T_ACCEPTED] = "accepted", + [UWB_RSV_STATE_T_DENIED] = "denied", + [UWB_RSV_STATE_T_PENDING] = "pending", +}; + +static const char *rsv_types[] = { + [UWB_DRP_TYPE_ALIEN_BP] = "alien-bp", + [UWB_DRP_TYPE_HARD] = "hard", + [UWB_DRP_TYPE_SOFT] = "soft", + [UWB_DRP_TYPE_PRIVATE] = "private", + [UWB_DRP_TYPE_PCA] = "pca", +}; + +/** + * uwb_rsv_state_str - return a string for a reservation state + * @state: the reservation state. + */ +const char *uwb_rsv_state_str(enum uwb_rsv_state state) +{ + if (state < UWB_RSV_STATE_NONE || state >= UWB_RSV_STATE_LAST) + return "unknown"; + return rsv_states[state]; +} +EXPORT_SYMBOL_GPL(uwb_rsv_state_str); + +/** + * uwb_rsv_type_str - return a string for a reservation type + * @type: the reservation type + */ +const char *uwb_rsv_type_str(enum uwb_drp_type type) +{ + if (type < UWB_DRP_TYPE_ALIEN_BP || type > UWB_DRP_TYPE_PCA) + return "invalid"; + return rsv_types[type]; +} +EXPORT_SYMBOL_GPL(uwb_rsv_type_str); + +static void uwb_rsv_dump(struct uwb_rsv *rsv) +{ + struct device *dev = &rsv->rc->uwb_dev.dev; + struct uwb_dev_addr devaddr; + char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE]; + + uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr); + if (rsv->target.type == UWB_RSV_TARGET_DEV) + devaddr = rsv->target.dev->dev_addr; + else + devaddr = rsv->target.devaddr; + uwb_dev_addr_print(target, sizeof(target), &devaddr); + + dev_dbg(dev, "rsv %s -> %s: %s\n", owner, target, uwb_rsv_state_str(rsv->state)); +} + +/* + * Get a free stream index for a reservation. + * + * If the target is a DevAddr (e.g., a WUSB cluster reservation) then + * the stream is allocated from a pool of per-RC stream indexes, + * otherwise a unique stream index for the target is selected. + */ +static int uwb_rsv_get_stream(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + unsigned long *streams_bm; + int stream; + + switch (rsv->target.type) { + case UWB_RSV_TARGET_DEV: + streams_bm = rsv->target.dev->streams; + break; + case UWB_RSV_TARGET_DEVADDR: + streams_bm = rc->uwb_dev.streams; + break; + default: + return -EINVAL; + } + + stream = find_first_zero_bit(streams_bm, UWB_NUM_STREAMS); + if (stream >= UWB_NUM_STREAMS) + return -EBUSY; + + rsv->stream = stream; + set_bit(stream, streams_bm); + + return 0; +} + +static void uwb_rsv_put_stream(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + unsigned long *streams_bm; + + switch (rsv->target.type) { + case UWB_RSV_TARGET_DEV: + streams_bm = rsv->target.dev->streams; + break; + case UWB_RSV_TARGET_DEVADDR: + streams_bm = rc->uwb_dev.streams; + break; + default: + return; + } + + clear_bit(rsv->stream, streams_bm); +} + +/* + * Generate a MAS allocation with a single row component. + */ +static void uwb_rsv_gen_alloc_row(struct uwb_mas_bm *mas, + int first_mas, int mas_per_zone, + int zs, int ze) +{ + struct uwb_mas_bm col; + int z; + + bitmap_zero(mas->bm, UWB_NUM_MAS); + bitmap_zero(col.bm, UWB_NUM_MAS); + bitmap_fill(col.bm, mas_per_zone); + bitmap_shift_left(col.bm, col.bm, first_mas + zs * UWB_MAS_PER_ZONE, UWB_NUM_MAS); + + for (z = zs; z <= ze; z++) { + bitmap_or(mas->bm, mas->bm, col.bm, UWB_NUM_MAS); + bitmap_shift_left(col.bm, col.bm, UWB_MAS_PER_ZONE, UWB_NUM_MAS); + } +} + +/* + * Allocate some MAS for this reservation based on current local + * availability, the reservation parameters (max_mas, min_mas, + * sparsity), and the WiMedia rules for MAS allocations. + * + * Returns -EBUSY is insufficient free MAS are available. + * + * FIXME: to simplify this, only safe reservations with a single row + * component in zones 1 to 15 are tried (zone 0 is skipped to avoid + * problems with the MAS reserved for the BP). + * + * [ECMA-368] section B.2. + */ +static int uwb_rsv_alloc_mas(struct uwb_rsv *rsv) +{ + static const int safe_mas_in_row[UWB_NUM_ZONES] = { + 8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 2, 1, + }; + int n, r; + struct uwb_mas_bm mas; + bool found = false; + + /* + * Search all valid safe allocations until either: too few MAS + * are available; or the smallest allocation with sufficient + * MAS is found. + * + * The top of the zones are preferred, so space for larger + * allocations is available in the bottom of the zone (e.g., a + * 15 MAS allocation should start in row 14 leaving space for + * a 120 MAS allocation at row 0). + */ + for (n = safe_mas_in_row[0]; n >= 1; n--) { + int num_mas; + + num_mas = n * (UWB_NUM_ZONES - 1); + if (num_mas < rsv->min_mas) + break; + if (found && num_mas < rsv->max_mas) + break; + + for (r = UWB_MAS_PER_ZONE-1; r >= 0; r--) { + if (safe_mas_in_row[r] < n) + continue; + uwb_rsv_gen_alloc_row(&mas, r, n, 1, UWB_NUM_ZONES); + if (uwb_drp_avail_reserve_pending(rsv->rc, &mas) == 0) { + found = true; + break; + } + } + } + + if (!found) + return -EBUSY; + + bitmap_copy(rsv->mas.bm, mas.bm, UWB_NUM_MAS); + return 0; +} + +static void uwb_rsv_stroke_timer(struct uwb_rsv *rsv) +{ + int sframes = UWB_MAX_LOST_BEACONS; + + /* + * Multicast reservations can become established within 1 + * super frame and should not be terminated if no response is + * received. + */ + if (rsv->is_multicast) { + if (rsv->state == UWB_RSV_STATE_O_INITIATED) + sframes = 1; + if (rsv->state == UWB_RSV_STATE_O_ESTABLISHED) + sframes = 0; + } + + rsv->expired = false; + if (sframes > 0) { + /* + * Add an additional 2 superframes to account for the + * time to send the SET DRP IE command. + */ + unsigned timeout_us = (sframes + 2) * UWB_SUPERFRAME_LENGTH_US; + mod_timer(&rsv->timer, jiffies + usecs_to_jiffies(timeout_us)); + } else + del_timer(&rsv->timer); +} + +/* + * Update a reservations state, and schedule an update of the + * transmitted DRP IEs. + */ +static void uwb_rsv_state_update(struct uwb_rsv *rsv, + enum uwb_rsv_state new_state) +{ + rsv->state = new_state; + rsv->ie_valid = false; + + uwb_rsv_dump(rsv); + + uwb_rsv_stroke_timer(rsv); + uwb_rsv_sched_update(rsv->rc); +} + +static void uwb_rsv_callback(struct uwb_rsv *rsv) +{ + if (rsv->callback) + rsv->callback(rsv); +} + +void uwb_rsv_set_state(struct uwb_rsv *rsv, enum uwb_rsv_state new_state) +{ + if (rsv->state == new_state) { + switch (rsv->state) { + case UWB_RSV_STATE_O_ESTABLISHED: + case UWB_RSV_STATE_T_ACCEPTED: + case UWB_RSV_STATE_NONE: + uwb_rsv_stroke_timer(rsv); + break; + default: + /* Expecting a state transition so leave timer + as-is. */ + break; + } + return; + } + + switch (new_state) { + case UWB_RSV_STATE_NONE: + uwb_drp_avail_release(rsv->rc, &rsv->mas); + uwb_rsv_put_stream(rsv); + uwb_rsv_state_update(rsv, UWB_RSV_STATE_NONE); + uwb_rsv_callback(rsv); + break; + case UWB_RSV_STATE_O_INITIATED: + uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_INITIATED); + break; + case UWB_RSV_STATE_O_PENDING: + uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_PENDING); + break; + case UWB_RSV_STATE_O_ESTABLISHED: + uwb_drp_avail_reserve(rsv->rc, &rsv->mas); + uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_ESTABLISHED); + uwb_rsv_callback(rsv); + break; + case UWB_RSV_STATE_T_ACCEPTED: + uwb_drp_avail_reserve(rsv->rc, &rsv->mas); + uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_ACCEPTED); + uwb_rsv_callback(rsv); + break; + case UWB_RSV_STATE_T_DENIED: + uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_DENIED); + break; + default: + dev_err(&rsv->rc->uwb_dev.dev, "unhandled state: %s (%d)\n", + uwb_rsv_state_str(new_state), new_state); + } +} + +static struct uwb_rsv *uwb_rsv_alloc(struct uwb_rc *rc) +{ + struct uwb_rsv *rsv; + + rsv = kzalloc(sizeof(struct uwb_rsv), GFP_KERNEL); + if (!rsv) + return NULL; + + INIT_LIST_HEAD(&rsv->rc_node); + INIT_LIST_HEAD(&rsv->pal_node); + init_timer(&rsv->timer); + rsv->timer.function = uwb_rsv_timer; + rsv->timer.data = (unsigned long)rsv; + + rsv->rc = rc; + + return rsv; +} + +static void uwb_rsv_free(struct uwb_rsv *rsv) +{ + uwb_dev_put(rsv->owner); + if (rsv->target.type == UWB_RSV_TARGET_DEV) + uwb_dev_put(rsv->target.dev); + kfree(rsv); +} + +/** + * uwb_rsv_create - allocate and initialize a UWB reservation structure + * @rc: the radio controller + * @cb: callback to use when the reservation completes or terminates + * @pal_priv: data private to the PAL to be passed in the callback + * + * The callback is called when the state of the reservation changes from: + * + * - pending to accepted + * - pending to denined + * - accepted to terminated + * - pending to terminated + */ +struct uwb_rsv *uwb_rsv_create(struct uwb_rc *rc, uwb_rsv_cb_f cb, void *pal_priv) +{ + struct uwb_rsv *rsv; + + rsv = uwb_rsv_alloc(rc); + if (!rsv) + return NULL; + + rsv->callback = cb; + rsv->pal_priv = pal_priv; + + return rsv; +} +EXPORT_SYMBOL_GPL(uwb_rsv_create); + +void uwb_rsv_remove(struct uwb_rsv *rsv) +{ + if (rsv->state != UWB_RSV_STATE_NONE) + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + del_timer_sync(&rsv->timer); + list_del(&rsv->rc_node); + uwb_rsv_free(rsv); +} + +/** + * uwb_rsv_destroy - free a UWB reservation structure + * @rsv: the reservation to free + * + * The reservation will be terminated if it is pending or established. + */ +void uwb_rsv_destroy(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + + mutex_lock(&rc->rsvs_mutex); + uwb_rsv_remove(rsv); + mutex_unlock(&rc->rsvs_mutex); +} +EXPORT_SYMBOL_GPL(uwb_rsv_destroy); + +/** + * usb_rsv_establish - start a reservation establishment + * @rsv: the reservation + * + * The PAL should fill in @rsv's owner, target, type, max_mas, + * min_mas, sparsity and is_multicast fields. If the target is a + * uwb_dev it must be referenced. + * + * The reservation's callback will be called when the reservation is + * accepted, denied or times out. + */ +int uwb_rsv_establish(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + int ret; + + mutex_lock(&rc->rsvs_mutex); + + ret = uwb_rsv_get_stream(rsv); + if (ret) + goto out; + + ret = uwb_rsv_alloc_mas(rsv); + if (ret) { + uwb_rsv_put_stream(rsv); + goto out; + } + + list_add_tail(&rsv->rc_node, &rc->reservations); + rsv->owner = &rc->uwb_dev; + uwb_dev_get(rsv->owner); + uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_INITIATED); +out: + mutex_unlock(&rc->rsvs_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(uwb_rsv_establish); + +/** + * uwb_rsv_modify - modify an already established reservation + * @rsv: the reservation to modify + * @max_mas: new maximum MAS to reserve + * @min_mas: new minimum MAS to reserve + * @sparsity: new sparsity to use + * + * FIXME: implement this once there are PALs that use it. + */ +int uwb_rsv_modify(struct uwb_rsv *rsv, int max_mas, int min_mas, int sparsity) +{ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(uwb_rsv_modify); + +/** + * uwb_rsv_terminate - terminate an established reservation + * @rsv: the reservation to terminate + * + * A reservation is terminated by removing the DRP IE from the beacon, + * the other end will consider the reservation to be terminated when + * it does not see the DRP IE for at least mMaxLostBeacons. + * + * If applicable, the reference to the target uwb_dev will be released. + */ +void uwb_rsv_terminate(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + + mutex_lock(&rc->rsvs_mutex); + + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + + mutex_unlock(&rc->rsvs_mutex); +} +EXPORT_SYMBOL_GPL(uwb_rsv_terminate); + +/** + * uwb_rsv_accept - accept a new reservation from a peer + * @rsv: the reservation + * @cb: call back for reservation changes + * @pal_priv: data to be passed in the above call back + * + * Reservation requests from peers are denied unless a PAL accepts it + * by calling this function. + */ +void uwb_rsv_accept(struct uwb_rsv *rsv, uwb_rsv_cb_f cb, void *pal_priv) +{ + rsv->callback = cb; + rsv->pal_priv = pal_priv; + rsv->state = UWB_RSV_STATE_T_ACCEPTED; +} +EXPORT_SYMBOL_GPL(uwb_rsv_accept); + +/* + * Is a received DRP IE for this reservation? + */ +static bool uwb_rsv_match(struct uwb_rsv *rsv, struct uwb_dev *src, + struct uwb_ie_drp *drp_ie) +{ + struct uwb_dev_addr *rsv_src; + int stream; + + stream = uwb_ie_drp_stream_index(drp_ie); + + if (rsv->stream != stream) + return false; + + switch (rsv->target.type) { + case UWB_RSV_TARGET_DEVADDR: + return rsv->stream == stream; + case UWB_RSV_TARGET_DEV: + if (uwb_ie_drp_owner(drp_ie)) + rsv_src = &rsv->owner->dev_addr; + else + rsv_src = &rsv->target.dev->dev_addr; + return uwb_dev_addr_cmp(&src->dev_addr, rsv_src) == 0; + } + return false; +} + +static struct uwb_rsv *uwb_rsv_new_target(struct uwb_rc *rc, + struct uwb_dev *src, + struct uwb_ie_drp *drp_ie) +{ + struct uwb_rsv *rsv; + struct uwb_pal *pal; + enum uwb_rsv_state state; + + rsv = uwb_rsv_alloc(rc); + if (!rsv) + return NULL; + + rsv->rc = rc; + rsv->owner = src; + uwb_dev_get(rsv->owner); + rsv->target.type = UWB_RSV_TARGET_DEV; + rsv->target.dev = &rc->uwb_dev; + rsv->type = uwb_ie_drp_type(drp_ie); + rsv->stream = uwb_ie_drp_stream_index(drp_ie); + set_bit(rsv->stream, rsv->owner->streams); + uwb_drp_ie_to_bm(&rsv->mas, drp_ie); + + /* + * See if any PALs are interested in this reservation. If not, + * deny the request. + */ + rsv->state = UWB_RSV_STATE_T_DENIED; + spin_lock(&rc->pal_lock); + list_for_each_entry(pal, &rc->pals, node) { + if (pal->new_rsv) + pal->new_rsv(rsv); + if (rsv->state == UWB_RSV_STATE_T_ACCEPTED) + break; + } + spin_unlock(&rc->pal_lock); + + list_add_tail(&rsv->rc_node, &rc->reservations); + state = rsv->state; + rsv->state = UWB_RSV_STATE_NONE; + uwb_rsv_set_state(rsv, state); + + return rsv; +} + +/** + * uwb_rsv_find - find a reservation for a received DRP IE. + * @rc: the radio controller + * @src: source of the DRP IE + * @drp_ie: the DRP IE + * + * If the reservation cannot be found and the DRP IE is from a peer + * attempting to establish a new reservation, create a new reservation + * and add it to the list. + */ +struct uwb_rsv *uwb_rsv_find(struct uwb_rc *rc, struct uwb_dev *src, + struct uwb_ie_drp *drp_ie) +{ + struct uwb_rsv *rsv; + + list_for_each_entry(rsv, &rc->reservations, rc_node) { + if (uwb_rsv_match(rsv, src, drp_ie)) + return rsv; + } + + if (uwb_ie_drp_owner(drp_ie)) + return uwb_rsv_new_target(rc, src, drp_ie); + + return NULL; +} + +/* + * Go through all the reservations and check for timeouts and (if + * necessary) update their DRP IEs. + * + * FIXME: look at building the SET_DRP_IE command here rather than + * having to rescan the list in uwb_rc_send_all_drp_ie(). + */ +static bool uwb_rsv_update_all(struct uwb_rc *rc) +{ + struct uwb_rsv *rsv, *t; + bool ie_updated = false; + + list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) { + if (rsv->expired) + uwb_drp_handle_timeout(rsv); + if (!rsv->ie_valid) { + uwb_drp_ie_update(rsv); + ie_updated = true; + } + } + + return ie_updated; +} + +void uwb_rsv_sched_update(struct uwb_rc *rc) +{ + queue_work(rc->rsv_workq, &rc->rsv_update_work); +} + +/* + * Update DRP IEs and, if necessary, the DRP Availability IE and send + * the updated IEs to the radio controller. + */ +static void uwb_rsv_update_work(struct work_struct *work) +{ + struct uwb_rc *rc = container_of(work, struct uwb_rc, rsv_update_work); + bool ie_updated; + + mutex_lock(&rc->rsvs_mutex); + + ie_updated = uwb_rsv_update_all(rc); + + if (!rc->drp_avail.ie_valid) { + uwb_drp_avail_ie_update(rc); + ie_updated = true; + } + + if (ie_updated) + uwb_rc_send_all_drp_ie(rc); + + mutex_unlock(&rc->rsvs_mutex); +} + +static void uwb_rsv_timer(unsigned long arg) +{ + struct uwb_rsv *rsv = (struct uwb_rsv *)arg; + + rsv->expired = true; + uwb_rsv_sched_update(rsv->rc); +} + +void uwb_rsv_init(struct uwb_rc *rc) +{ + INIT_LIST_HEAD(&rc->reservations); + mutex_init(&rc->rsvs_mutex); + INIT_WORK(&rc->rsv_update_work, uwb_rsv_update_work); + + bitmap_complement(rc->uwb_dev.streams, rc->uwb_dev.streams, UWB_NUM_STREAMS); +} + +int uwb_rsv_setup(struct uwb_rc *rc) +{ + char name[16]; + + snprintf(name, sizeof(name), "%s_rsvd", dev_name(&rc->uwb_dev.dev)); + rc->rsv_workq = create_singlethread_workqueue(name); + if (rc->rsv_workq == NULL) + return -ENOMEM; + + return 0; +} + +void uwb_rsv_cleanup(struct uwb_rc *rc) +{ + struct uwb_rsv *rsv, *t; + + mutex_lock(&rc->rsvs_mutex); + list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) { + uwb_rsv_remove(rsv); + } + mutex_unlock(&rc->rsvs_mutex); + + cancel_work_sync(&rc->rsv_update_work); + destroy_workqueue(rc->rsv_workq); +} diff --git a/drivers/uwb/scan.c b/drivers/uwb/scan.c new file mode 100644 index 00000000000..2d270748f32 --- /dev/null +++ b/drivers/uwb/scan.c @@ -0,0 +1,133 @@ +/* + * Ultra Wide Band + * Scanning management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * + * FIXME: docs + * FIXME: there are issues here on how BEACON and SCAN on USB RCI deal + * with each other. Currently seems that START_BEACON while + * SCAN_ONLY will cancel the scan, so we need to update the + * state here. Clarification request sent by email on + * 10/05/2005. + * 10/28/2005 No clear answer heard--maybe we'll hack the API + * so that when we start beaconing, if the HC is + * scanning in a mode not compatible with beaconing + * we just fail. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include "uwb-internal.h" + + +/** + * Start/stop scanning in a radio controller + * + * @rc: UWB Radio Controlller + * @channel: Channel to scan; encodings in WUSB1.0[Table 5.12] + * @type: Type of scanning to do. + * @bpst_offset: value at which to start scanning (if type == + * UWB_SCAN_ONLY_STARTTIME) + * @returns: 0 if ok, < 0 errno code on error + * + * We put the command on kmalloc'ed memory as some arches cannot do + * USB from the stack. The reply event is copied from an stage buffer, + * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details. + */ +int uwb_rc_scan(struct uwb_rc *rc, + unsigned channel, enum uwb_scan_type type, + unsigned bpst_offset) +{ + int result; + struct uwb_rc_cmd_scan *cmd; + struct uwb_rc_evt_confirm reply; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + mutex_lock(&rc->uwb_dev.mutex); + cmd->rccb.bCommandType = UWB_RC_CET_GENERAL; + cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SCAN); + cmd->bChannelNumber = channel; + cmd->bScanState = type; + cmd->wStartTime = cpu_to_le16(bpst_offset); + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_SCAN; + result = uwb_rc_cmd(rc, "SCAN", &cmd->rccb, sizeof(*cmd), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "SCAN: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + goto error_cmd; + } + rc->scanning = channel; + rc->scan_type = type; +error_cmd: + mutex_unlock(&rc->uwb_dev.mutex); + kfree(cmd); +error_kzalloc: + return result; +} + +/* + * Print scanning state + */ +static ssize_t uwb_rc_scan_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + ssize_t result; + + mutex_lock(&rc->uwb_dev.mutex); + result = sprintf(buf, "%d %d\n", rc->scanning, rc->scan_type); + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +/* + * + */ +static ssize_t uwb_rc_scan_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + unsigned channel; + unsigned type; + unsigned bpst_offset = 0; + ssize_t result = -EINVAL; + + result = sscanf(buf, "%u %u %u\n", &channel, &type, &bpst_offset); + if (result >= 2 && type < UWB_SCAN_TOP) + result = uwb_rc_scan(rc, channel, type, bpst_offset); + + return result < 0 ? result : size; +} + +/** Radio Control sysfs interface (declaration) */ +DEVICE_ATTR(scan, S_IRUGO | S_IWUSR, uwb_rc_scan_show, uwb_rc_scan_store); diff --git a/drivers/uwb/umc-bus.c b/drivers/uwb/umc-bus.c new file mode 100644 index 00000000000..2d8d62d9f53 --- /dev/null +++ b/drivers/uwb/umc-bus.c @@ -0,0 +1,218 @@ +/* + * Bus for UWB Multi-interface Controller capabilities. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GNU GPL v2. + */ +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/workqueue.h> +#include <linux/uwb/umc.h> +#include <linux/pci.h> + +static int umc_bus_unbind_helper(struct device *dev, void *data) +{ + struct device *parent = data; + + if (dev->parent == parent && dev->driver) + device_release_driver(dev); + return 0; +} + +/** + * umc_controller_reset - reset the whole UMC controller + * @umc: the UMC device for the radio controller. + * + * Drivers will be unbound from all UMC devices belonging to the + * controller and then the radio controller will be rebound. The + * radio controller is expected to do a full hardware reset when it is + * probed. + * + * If this is called while a probe() or remove() is in progress it + * will return -EAGAIN and not perform the reset. + */ +int umc_controller_reset(struct umc_dev *umc) +{ + struct device *parent = umc->dev.parent; + int ret; + + if (down_trylock(&parent->sem)) + return -EAGAIN; + bus_for_each_dev(&umc_bus_type, NULL, parent, umc_bus_unbind_helper); + ret = device_attach(&umc->dev); + if (ret == 1) + ret = 0; + up(&parent->sem); + + return ret; +} +EXPORT_SYMBOL_GPL(umc_controller_reset); + +/** + * umc_match_pci_id - match a UMC driver to a UMC device's parent PCI device. + * @umc_drv: umc driver with match_data pointing to a zero-terminated + * table of pci_device_id's. + * @umc: umc device whose parent is to be matched. + */ +int umc_match_pci_id(struct umc_driver *umc_drv, struct umc_dev *umc) +{ + const struct pci_device_id *id_table = umc_drv->match_data; + struct pci_dev *pci; + + if (umc->dev.parent->bus != &pci_bus_type) + return 0; + + pci = to_pci_dev(umc->dev.parent); + return pci_match_id(id_table, pci) != NULL; +} +EXPORT_SYMBOL_GPL(umc_match_pci_id); + +static int umc_bus_rescan_helper(struct device *dev, void *data) +{ + int ret = 0; + + if (!dev->driver) + ret = device_attach(dev); + + return ret < 0 ? ret : 0; +} + +static void umc_bus_rescan(void) +{ + int err; + + /* + * We can't use bus_rescan_devices() here as it deadlocks when + * it tries to retake the dev->parent semaphore. + */ + err = bus_for_each_dev(&umc_bus_type, NULL, NULL, umc_bus_rescan_helper); + if (err < 0) + printk(KERN_WARNING "%s: rescan of bus failed: %d\n", + KBUILD_MODNAME, err); +} + +static int umc_bus_match(struct device *dev, struct device_driver *drv) +{ + struct umc_dev *umc = to_umc_dev(dev); + struct umc_driver *umc_driver = to_umc_driver(drv); + + if (umc->cap_id == umc_driver->cap_id) { + if (umc_driver->match) + return umc_driver->match(umc_driver, umc); + else + return 1; + } + return 0; +} + +static int umc_device_probe(struct device *dev) +{ + struct umc_dev *umc; + struct umc_driver *umc_driver; + int err; + + umc_driver = to_umc_driver(dev->driver); + umc = to_umc_dev(dev); + + get_device(dev); + err = umc_driver->probe(umc); + if (err) + put_device(dev); + else + umc_bus_rescan(); + + return err; +} + +static int umc_device_remove(struct device *dev) +{ + struct umc_dev *umc; + struct umc_driver *umc_driver; + + umc_driver = to_umc_driver(dev->driver); + umc = to_umc_dev(dev); + + umc_driver->remove(umc); + put_device(dev); + return 0; +} + +static int umc_device_suspend(struct device *dev, pm_message_t state) +{ + struct umc_dev *umc; + struct umc_driver *umc_driver; + int err = 0; + + umc = to_umc_dev(dev); + + if (dev->driver) { + umc_driver = to_umc_driver(dev->driver); + if (umc_driver->suspend) + err = umc_driver->suspend(umc, state); + } + return err; +} + +static int umc_device_resume(struct device *dev) +{ + struct umc_dev *umc; + struct umc_driver *umc_driver; + int err = 0; + + umc = to_umc_dev(dev); + + if (dev->driver) { + umc_driver = to_umc_driver(dev->driver); + if (umc_driver->resume) + err = umc_driver->resume(umc); + } + return err; +} + +static ssize_t capability_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct umc_dev *umc = to_umc_dev(dev); + + return sprintf(buf, "0x%02x\n", umc->cap_id); +} + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct umc_dev *umc = to_umc_dev(dev); + + return sprintf(buf, "0x%04x\n", umc->version); +} + +static struct device_attribute umc_dev_attrs[] = { + __ATTR_RO(capability_id), + __ATTR_RO(version), + __ATTR_NULL, +}; + +struct bus_type umc_bus_type = { + .name = "umc", + .match = umc_bus_match, + .probe = umc_device_probe, + .remove = umc_device_remove, + .suspend = umc_device_suspend, + .resume = umc_device_resume, + .dev_attrs = umc_dev_attrs, +}; +EXPORT_SYMBOL_GPL(umc_bus_type); + +static int __init umc_bus_init(void) +{ + return bus_register(&umc_bus_type); +} +module_init(umc_bus_init); + +static void __exit umc_bus_exit(void) +{ + bus_unregister(&umc_bus_type); +} +module_exit(umc_bus_exit); + +MODULE_DESCRIPTION("UWB Multi-interface Controller capability bus"); +MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/umc-dev.c b/drivers/uwb/umc-dev.c new file mode 100644 index 00000000000..aa44e1c1a10 --- /dev/null +++ b/drivers/uwb/umc-dev.c @@ -0,0 +1,104 @@ +/* + * UWB Multi-interface Controller device management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GNU GPL v2. + */ +#include <linux/kernel.h> +#include <linux/uwb/umc.h> +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +static void umc_device_release(struct device *dev) +{ + struct umc_dev *umc = to_umc_dev(dev); + + kfree(umc); +} + +/** + * umc_device_create - allocate a child UMC device + * @parent: parent of the new UMC device. + * @n: index of the new device. + * + * The new UMC device will have a bus ID of the parent with '-n' + * appended. + */ +struct umc_dev *umc_device_create(struct device *parent, int n) +{ + struct umc_dev *umc; + + umc = kzalloc(sizeof(struct umc_dev), GFP_KERNEL); + if (umc) { + snprintf(umc->dev.bus_id, sizeof(umc->dev.bus_id), "%s-%d", + parent->bus_id, n); + umc->dev.parent = parent; + umc->dev.bus = &umc_bus_type; + umc->dev.release = umc_device_release; + + umc->dev.dma_mask = parent->dma_mask; + } + return umc; +} +EXPORT_SYMBOL_GPL(umc_device_create); + +/** + * umc_device_register - register a UMC device + * @umc: pointer to the UMC device + * + * The memory resource for the UMC device is acquired and the device + * registered with the system. + */ +int umc_device_register(struct umc_dev *umc) +{ + int err; + + d_fnstart(3, &umc->dev, "(umc_dev %p)\n", umc); + + err = request_resource(umc->resource.parent, &umc->resource); + if (err < 0) { + dev_err(&umc->dev, "can't allocate resource range " + "%016Lx to %016Lx: %d\n", + (unsigned long long)umc->resource.start, + (unsigned long long)umc->resource.end, + err); + goto error_request_resource; + } + + err = device_register(&umc->dev); + if (err < 0) + goto error_device_register; + d_fnend(3, &umc->dev, "(umc_dev %p) = 0\n", umc); + return 0; + +error_device_register: + release_resource(&umc->resource); +error_request_resource: + d_fnend(3, &umc->dev, "(umc_dev %p) = %d\n", umc, err); + return err; +} +EXPORT_SYMBOL_GPL(umc_device_register); + +/** + * umc_device_unregister - unregister a UMC device + * @umc: pointer to the UMC device + * + * First we unregister the device, make sure the driver can do it's + * resource release thing and then we try to release any left over + * resources. We take a ref to the device, to make sure it doesn't + * dissapear under our feet. + */ +void umc_device_unregister(struct umc_dev *umc) +{ + struct device *dev; + if (!umc) + return; + dev = get_device(&umc->dev); + d_fnstart(3, dev, "(umc_dev %p)\n", umc); + device_unregister(&umc->dev); + release_resource(&umc->resource); + d_fnend(3, dev, "(umc_dev %p) = void\n", umc); + put_device(dev); +} +EXPORT_SYMBOL_GPL(umc_device_unregister); diff --git a/drivers/uwb/umc-drv.c b/drivers/uwb/umc-drv.c new file mode 100644 index 00000000000..367b5eb85d6 --- /dev/null +++ b/drivers/uwb/umc-drv.c @@ -0,0 +1,31 @@ +/* + * UWB Multi-interface Controller driver management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GNU GPL v2. + */ +#include <linux/kernel.h> +#include <linux/uwb/umc.h> + +int __umc_driver_register(struct umc_driver *umc_drv, struct module *module, + const char *mod_name) +{ + umc_drv->driver.name = umc_drv->name; + umc_drv->driver.owner = module; + umc_drv->driver.mod_name = mod_name; + umc_drv->driver.bus = &umc_bus_type; + + return driver_register(&umc_drv->driver); +} +EXPORT_SYMBOL_GPL(__umc_driver_register); + +/** + * umc_driver_register - unregister a UMC capabiltity driver. + * @umc_drv: pointer to the driver. + */ +void umc_driver_unregister(struct umc_driver *umc_drv) +{ + driver_unregister(&umc_drv->driver); +} +EXPORT_SYMBOL_GPL(umc_driver_unregister); diff --git a/drivers/uwb/uwb-debug.c b/drivers/uwb/uwb-debug.c new file mode 100644 index 00000000000..6d232c35d07 --- /dev/null +++ b/drivers/uwb/uwb-debug.c @@ -0,0 +1,367 @@ +/* + * Ultra Wide Band + * Debug support + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: doc + */ + +#include <linux/spinlock.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/notifier.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/seq_file.h> + +#include <linux/uwb/debug-cmd.h> +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +#include "uwb-internal.h" + +void dump_bytes(struct device *dev, const void *_buf, size_t rsize) +{ + const char *buf = _buf; + char line[32]; + size_t offset = 0; + int cnt, cnt2; + for (cnt = 0; cnt < rsize; cnt += 8) { + size_t rtop = rsize - cnt < 8 ? rsize - cnt : 8; + for (offset = cnt2 = 0; cnt2 < rtop; cnt2++) { + offset += scnprintf(line + offset, sizeof(line) - offset, + "%02x ", buf[cnt + cnt2] & 0xff); + } + if (dev) + dev_info(dev, "%s\n", line); + else + printk(KERN_INFO "%s\n", line); + } +} +EXPORT_SYMBOL_GPL(dump_bytes); + +/* + * Debug interface + * + * Per radio controller debugfs files (in uwb/uwbN/): + * + * command: Flexible command interface (see <linux/uwb/debug-cmd.h>). + * + * reservations: information on reservations. + * + * accept: Set to true (Y or 1) to accept reservation requests from + * peers. + * + * drp_avail: DRP availability information. + */ + +struct uwb_dbg { + struct uwb_pal pal; + + u32 accept; + struct list_head rsvs; + + struct dentry *root_d; + struct dentry *command_f; + struct dentry *reservations_f; + struct dentry *accept_f; + struct dentry *drp_avail_f; +}; + +static struct dentry *root_dir; + +static void uwb_dbg_rsv_cb(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_dev_addr devaddr; + char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE]; + + uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr); + if (rsv->target.type == UWB_RSV_TARGET_DEV) + devaddr = rsv->target.dev->dev_addr; + else + devaddr = rsv->target.devaddr; + uwb_dev_addr_print(target, sizeof(target), &devaddr); + + dev_dbg(dev, "debug: rsv %s -> %s: %s\n", + owner, target, uwb_rsv_state_str(rsv->state)); +} + +static int cmd_rsv_establish(struct uwb_rc *rc, + struct uwb_dbg_cmd_rsv_establish *cmd) +{ + struct uwb_mac_addr macaddr; + struct uwb_rsv *rsv; + struct uwb_dev *target; + int ret; + + memcpy(&macaddr, cmd->target, sizeof(macaddr)); + target = uwb_dev_get_by_macaddr(rc, &macaddr); + if (target == NULL) + return -ENODEV; + + rsv = uwb_rsv_create(rc, uwb_dbg_rsv_cb, NULL); + if (rsv == NULL) { + uwb_dev_put(target); + return -ENOMEM; + } + + rsv->owner = &rc->uwb_dev; + rsv->target.type = UWB_RSV_TARGET_DEV; + rsv->target.dev = target; + rsv->type = cmd->type; + rsv->max_mas = cmd->max_mas; + rsv->min_mas = cmd->min_mas; + rsv->sparsity = cmd->sparsity; + + ret = uwb_rsv_establish(rsv); + if (ret) + uwb_rsv_destroy(rsv); + else + list_add_tail(&rsv->pal_node, &rc->dbg->rsvs); + + return ret; +} + +static int cmd_rsv_terminate(struct uwb_rc *rc, + struct uwb_dbg_cmd_rsv_terminate *cmd) +{ + struct uwb_rsv *rsv, *found = NULL; + int i = 0; + + list_for_each_entry(rsv, &rc->dbg->rsvs, pal_node) { + if (i == cmd->index) { + found = rsv; + break; + } + } + if (!found) + return -EINVAL; + + list_del(&found->pal_node); + uwb_rsv_terminate(found); + + return 0; +} + +static int command_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static ssize_t command_write(struct file *file, const char __user *buf, + size_t len, loff_t *off) +{ + struct uwb_rc *rc = file->private_data; + struct uwb_dbg_cmd cmd; + int ret; + + if (len != sizeof(struct uwb_dbg_cmd)) + return -EINVAL; + + if (copy_from_user(&cmd, buf, len) != 0) + return -EFAULT; + + switch (cmd.type) { + case UWB_DBG_CMD_RSV_ESTABLISH: + ret = cmd_rsv_establish(rc, &cmd.rsv_establish); + break; + case UWB_DBG_CMD_RSV_TERMINATE: + ret = cmd_rsv_terminate(rc, &cmd.rsv_terminate); + break; + default: + return -EINVAL; + } + + return ret < 0 ? ret : len; +} + +static struct file_operations command_fops = { + .open = command_open, + .write = command_write, + .read = NULL, + .llseek = no_llseek, + .owner = THIS_MODULE, +}; + +static int reservations_print(struct seq_file *s, void *p) +{ + struct uwb_rc *rc = s->private; + struct uwb_rsv *rsv; + + mutex_lock(&rc->rsvs_mutex); + + list_for_each_entry(rsv, &rc->reservations, rc_node) { + struct uwb_dev_addr devaddr; + char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE]; + bool is_owner; + char buf[72]; + + uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr); + if (rsv->target.type == UWB_RSV_TARGET_DEV) { + devaddr = rsv->target.dev->dev_addr; + is_owner = &rc->uwb_dev == rsv->owner; + } else { + devaddr = rsv->target.devaddr; + is_owner = true; + } + uwb_dev_addr_print(target, sizeof(target), &devaddr); + + seq_printf(s, "%c %s -> %s: %s\n", + is_owner ? 'O' : 'T', + owner, target, uwb_rsv_state_str(rsv->state)); + seq_printf(s, " stream: %d type: %s\n", + rsv->stream, uwb_rsv_type_str(rsv->type)); + bitmap_scnprintf(buf, sizeof(buf), rsv->mas.bm, UWB_NUM_MAS); + seq_printf(s, " %s\n", buf); + } + + mutex_unlock(&rc->rsvs_mutex); + + return 0; +} + +static int reservations_open(struct inode *inode, struct file *file) +{ + return single_open(file, reservations_print, inode->i_private); +} + +static struct file_operations reservations_fops = { + .open = reservations_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int drp_avail_print(struct seq_file *s, void *p) +{ + struct uwb_rc *rc = s->private; + char buf[72]; + + bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.global, UWB_NUM_MAS); + seq_printf(s, "global: %s\n", buf); + bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.local, UWB_NUM_MAS); + seq_printf(s, "local: %s\n", buf); + bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.pending, UWB_NUM_MAS); + seq_printf(s, "pending: %s\n", buf); + + return 0; +} + +static int drp_avail_open(struct inode *inode, struct file *file) +{ + return single_open(file, drp_avail_print, inode->i_private); +} + +static struct file_operations drp_avail_fops = { + .open = drp_avail_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static void uwb_dbg_new_rsv(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + + if (rc->dbg->accept) + uwb_rsv_accept(rsv, uwb_dbg_rsv_cb, NULL); +} + +/** + * uwb_dbg_add_rc - add a debug interface for a radio controller + * @rc: the radio controller + */ +void uwb_dbg_add_rc(struct uwb_rc *rc) +{ + rc->dbg = kzalloc(sizeof(struct uwb_dbg), GFP_KERNEL); + if (rc->dbg == NULL) + return; + + INIT_LIST_HEAD(&rc->dbg->rsvs); + + uwb_pal_init(&rc->dbg->pal); + rc->dbg->pal.new_rsv = uwb_dbg_new_rsv; + uwb_pal_register(rc, &rc->dbg->pal); + if (root_dir) { + rc->dbg->root_d = debugfs_create_dir(dev_name(&rc->uwb_dev.dev), + root_dir); + rc->dbg->command_f = debugfs_create_file("command", 0200, + rc->dbg->root_d, rc, + &command_fops); + rc->dbg->reservations_f = debugfs_create_file("reservations", 0444, + rc->dbg->root_d, rc, + &reservations_fops); + rc->dbg->accept_f = debugfs_create_bool("accept", 0644, + rc->dbg->root_d, + &rc->dbg->accept); + rc->dbg->drp_avail_f = debugfs_create_file("drp_avail", 0444, + rc->dbg->root_d, rc, + &drp_avail_fops); + } +} + +/** + * uwb_dbg_add_rc - remove a radio controller's debug interface + * @rc: the radio controller + */ +void uwb_dbg_del_rc(struct uwb_rc *rc) +{ + struct uwb_rsv *rsv, *t; + + if (rc->dbg == NULL) + return; + + list_for_each_entry_safe(rsv, t, &rc->dbg->rsvs, pal_node) { + uwb_rsv_destroy(rsv); + } + + uwb_pal_unregister(rc, &rc->dbg->pal); + + if (root_dir) { + debugfs_remove(rc->dbg->drp_avail_f); + debugfs_remove(rc->dbg->accept_f); + debugfs_remove(rc->dbg->reservations_f); + debugfs_remove(rc->dbg->command_f); + debugfs_remove(rc->dbg->root_d); + } +} + +/** + * uwb_dbg_exit - initialize the debug interface sub-module + */ +void uwb_dbg_init(void) +{ + root_dir = debugfs_create_dir("uwb", NULL); +} + +/** + * uwb_dbg_exit - clean-up the debug interface sub-module + */ +void uwb_dbg_exit(void) +{ + debugfs_remove(root_dir); +} diff --git a/drivers/uwb/uwb-internal.h b/drivers/uwb/uwb-internal.h new file mode 100644 index 00000000000..2ad307d1296 --- /dev/null +++ b/drivers/uwb/uwb-internal.h @@ -0,0 +1,305 @@ +/* + * Ultra Wide Band + * UWB internal API + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * This contains most of the internal API for UWB. This is stuff used + * across the stack that of course, is of no interest to the rest. + * + * Some parts might end up going public (like uwb_rc_*())... + */ + +#ifndef __UWB_INTERNAL_H__ +#define __UWB_INTERNAL_H__ + +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/uwb.h> +#include <linux/mutex.h> + +struct uwb_beca_e; + +/* General device API */ +extern void uwb_dev_init(struct uwb_dev *uwb_dev); +extern int __uwb_dev_offair(struct uwb_dev *, struct uwb_rc *); +extern int uwb_dev_add(struct uwb_dev *uwb_dev, struct device *parent_dev, + struct uwb_rc *parent_rc); +extern void uwb_dev_rm(struct uwb_dev *uwb_dev); +extern void uwbd_dev_onair(struct uwb_rc *, struct uwb_beca_e *); +extern void uwbd_dev_offair(struct uwb_beca_e *); +void uwb_notify(struct uwb_rc *rc, struct uwb_dev *uwb_dev, enum uwb_notifs event); + +/* General UWB Radio Controller Internal API */ +extern struct uwb_rc *__uwb_rc_try_get(struct uwb_rc *); +static inline struct uwb_rc *__uwb_rc_get(struct uwb_rc *rc) +{ + uwb_dev_get(&rc->uwb_dev); + return rc; +} + +static inline void __uwb_rc_put(struct uwb_rc *rc) +{ + uwb_dev_put(&rc->uwb_dev); +} + +extern int uwb_rc_reset(struct uwb_rc *rc); +extern int uwb_rc_beacon(struct uwb_rc *rc, + int channel, unsigned bpst_offset); +extern int uwb_rc_scan(struct uwb_rc *rc, + unsigned channel, enum uwb_scan_type type, + unsigned bpst_offset); +extern int uwb_rc_send_all_drp_ie(struct uwb_rc *rc); +extern ssize_t uwb_rc_print_IEs(struct uwb_rc *rc, char *, size_t); +extern void uwb_rc_ie_init(struct uwb_rc *); +extern void uwb_rc_ie_init(struct uwb_rc *); +extern ssize_t uwb_rc_ie_setup(struct uwb_rc *); +extern void uwb_rc_ie_release(struct uwb_rc *); +extern int uwb_rc_ie_add(struct uwb_rc *, + const struct uwb_ie_hdr *, size_t); +extern int uwb_rc_ie_rm(struct uwb_rc *, enum uwb_ie); + +extern const char *uwb_rc_strerror(unsigned code); + +/* + * Time to wait for a response to an RC command. + * + * Some commands can take a long time to response. e.g., START_BEACON + * may scan for several superframes before joining an existing beacon + * group and this can take around 600 ms. + */ +#define UWB_RC_CMD_TIMEOUT_MS 1000 /* ms */ + +/* + * Notification/Event Handlers + */ + +struct uwb_rc_neh; + +void uwb_rc_neh_create(struct uwb_rc *rc); +void uwb_rc_neh_destroy(struct uwb_rc *rc); + +struct uwb_rc_neh *uwb_rc_neh_add(struct uwb_rc *rc, struct uwb_rccb *cmd, + u8 expected_type, u16 expected_event, + uwb_rc_cmd_cb_f cb, void *arg); +void uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh); +void uwb_rc_neh_arm(struct uwb_rc *rc, struct uwb_rc_neh *neh); +void uwb_rc_neh_put(struct uwb_rc_neh *neh); + +/* Event size tables */ +extern int uwb_est_create(void); +extern void uwb_est_destroy(void); + + +/* + * UWB Events & management daemon + */ + +/** + * enum uwb_event_type - types of UWB management daemon events + * + * The UWB management daemon (uwbd) can receive two types of events: + * UWB_EVT_TYPE_NOTIF - notification from the radio controller. + * UWB_EVT_TYPE_MSG - a simple message. + */ +enum uwb_event_type { + UWB_EVT_TYPE_NOTIF, + UWB_EVT_TYPE_MSG, +}; + +/** + * struct uwb_event_notif - an event for a radio controller notification + * @size: Size of the buffer (ie: Guaranteed to contain at least + * a full 'struct uwb_rceb') + * @rceb: Pointer to a kmalloced() event payload + */ +struct uwb_event_notif { + size_t size; + struct uwb_rceb *rceb; +}; + +/** + * enum uwb_event_message - an event for a message for asynchronous processing + * + * UWB_EVT_MSG_RESET - reset the radio controller and all PAL hardware. + */ +enum uwb_event_message { + UWB_EVT_MSG_RESET, +}; + +/** + * UWB Event + * @rc: Radio controller that emitted the event (referenced) + * @ts_jiffies: Timestamp, when was it received + * @type: This event's type. + */ +struct uwb_event { + struct list_head list_node; + struct uwb_rc *rc; + unsigned long ts_jiffies; + enum uwb_event_type type; + union { + struct uwb_event_notif notif; + enum uwb_event_message message; + }; +}; + +extern void uwbd_start(void); +extern void uwbd_stop(void); +extern struct uwb_event *uwb_event_alloc(size_t, gfp_t gfp_mask); +extern void uwbd_event_queue(struct uwb_event *); +void uwbd_flush(struct uwb_rc *rc); + +/* UWB event handlers */ +extern int uwbd_evt_handle_rc_beacon(struct uwb_event *); +extern int uwbd_evt_handle_rc_beacon_size(struct uwb_event *); +extern int uwbd_evt_handle_rc_bpoie_change(struct uwb_event *); +extern int uwbd_evt_handle_rc_bp_slot_change(struct uwb_event *); +extern int uwbd_evt_handle_rc_drp(struct uwb_event *); +extern int uwbd_evt_handle_rc_drp_avail(struct uwb_event *); + +int uwbd_msg_handle_reset(struct uwb_event *evt); + + +/* + * Address management + */ +int uwb_rc_dev_addr_assign(struct uwb_rc *rc); +int uwbd_evt_handle_rc_dev_addr_conflict(struct uwb_event *evt); + +/* + * UWB Beacon Cache + * + * Each beacon we received is kept in a cache--when we receive that + * beacon consistently, that means there is a new device that we have + * to add to the system. + */ + +extern unsigned long beacon_timeout_ms; + +/** Beacon cache list */ +struct uwb_beca { + struct list_head list; + size_t entries; + struct mutex mutex; +}; + +extern struct uwb_beca uwb_beca; + +/** + * Beacon cache entry + * + * @jiffies_refresh: last time a beacon was received that refreshed + * this cache entry. + * @uwb_dev: device connected to this beacon. This pointer is not + * safe, you need to get it with uwb_dev_try_get() + * + * @hits: how many time we have seen this beacon since last time we + * cleared it + */ +struct uwb_beca_e { + struct mutex mutex; + struct kref refcnt; + struct list_head node; + struct uwb_mac_addr *mac_addr; + struct uwb_dev_addr dev_addr; + u8 hits; + unsigned long ts_jiffies; + struct uwb_dev *uwb_dev; + struct uwb_rc_evt_beacon *be; + struct stats lqe_stats, rssi_stats; /* radio statistics */ +}; +struct uwb_beacon_frame; +extern ssize_t uwb_bce_print_IEs(struct uwb_dev *, struct uwb_beca_e *, + char *, size_t); +extern struct uwb_beca_e *__uwb_beca_add(struct uwb_rc_evt_beacon *, + struct uwb_beacon_frame *, + unsigned long); + +extern void uwb_bce_kfree(struct kref *_bce); +static inline void uwb_bce_get(struct uwb_beca_e *bce) +{ + kref_get(&bce->refcnt); +} +static inline void uwb_bce_put(struct uwb_beca_e *bce) +{ + kref_put(&bce->refcnt, uwb_bce_kfree); +} +extern void uwb_beca_purge(void); +extern void uwb_beca_release(void); + +struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc, + const struct uwb_dev_addr *devaddr); +struct uwb_dev *uwb_dev_get_by_macaddr(struct uwb_rc *rc, + const struct uwb_mac_addr *macaddr); + +/* -- UWB Sysfs representation */ +extern struct class uwb_rc_class; +extern struct device_attribute dev_attr_mac_address; +extern struct device_attribute dev_attr_beacon; +extern struct device_attribute dev_attr_scan; + +/* -- DRP Bandwidth allocator: bandwidth allocations, reservations, DRP */ +void uwb_rsv_init(struct uwb_rc *rc); +int uwb_rsv_setup(struct uwb_rc *rc); +void uwb_rsv_cleanup(struct uwb_rc *rc); + +void uwb_rsv_set_state(struct uwb_rsv *rsv, enum uwb_rsv_state new_state); +void uwb_rsv_remove(struct uwb_rsv *rsv); +struct uwb_rsv *uwb_rsv_find(struct uwb_rc *rc, struct uwb_dev *src, + struct uwb_ie_drp *drp_ie); +void uwb_rsv_sched_update(struct uwb_rc *rc); + +void uwb_drp_handle_timeout(struct uwb_rsv *rsv); +int uwb_drp_ie_update(struct uwb_rsv *rsv); +void uwb_drp_ie_to_bm(struct uwb_mas_bm *bm, const struct uwb_ie_drp *drp_ie); + +void uwb_drp_avail_init(struct uwb_rc *rc); +int uwb_drp_avail_reserve_pending(struct uwb_rc *rc, struct uwb_mas_bm *mas); +void uwb_drp_avail_reserve(struct uwb_rc *rc, struct uwb_mas_bm *mas); +void uwb_drp_avail_release(struct uwb_rc *rc, struct uwb_mas_bm *mas); +void uwb_drp_avail_ie_update(struct uwb_rc *rc); + +/* -- PAL support */ +void uwb_rc_pal_init(struct uwb_rc *rc); + +/* -- Misc */ + +extern ssize_t uwb_mac_frame_hdr_print(char *, size_t, + const struct uwb_mac_frame_hdr *); + +/* -- Debug interface */ +void uwb_dbg_init(void); +void uwb_dbg_exit(void); +void uwb_dbg_add_rc(struct uwb_rc *rc); +void uwb_dbg_del_rc(struct uwb_rc *rc); + +/* Workarounds for version specific stuff */ + +static inline void uwb_dev_lock(struct uwb_dev *uwb_dev) +{ + down(&uwb_dev->dev.sem); +} + +static inline void uwb_dev_unlock(struct uwb_dev *uwb_dev) +{ + up(&uwb_dev->dev.sem); +} + +#endif /* #ifndef __UWB_INTERNAL_H__ */ diff --git a/drivers/uwb/uwbd.c b/drivers/uwb/uwbd.c new file mode 100644 index 00000000000..78908416e42 --- /dev/null +++ b/drivers/uwb/uwbd.c @@ -0,0 +1,410 @@ +/* + * Ultra Wide Band + * Neighborhood Management Daemon + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This daemon takes care of maintaing information that describes the + * UWB neighborhood that the radios in this machine can see. It also + * keeps a tab of which devices are visible, makes sure each HC sits + * on a different channel to avoid interfering, etc. + * + * Different drivers (radio controller, device, any API in general) + * communicate with this daemon through an event queue. Daemon wakes + * up, takes a list of events and handles them one by one; handling + * function is extracted from a table based on the event's type and + * subtype. Events are freed only if the handling function says so. + * + * . Lock protecting the event list has to be an spinlock and locked + * with IRQSAVE because it might be called from an interrupt + * context (ie: when events arrive and the notification drops + * down from the ISR). + * + * . UWB radio controller drivers queue events to the daemon using + * uwbd_event_queue(). They just get the event, chew it to make it + * look like UWBD likes it and pass it in a buffer allocated with + * uwb_event_alloc(). + * + * EVENTS + * + * Events have a type, a subtype, a lenght, some other stuff and the + * data blob, which depends on the event. The header is 'struct + * uwb_event'; for payloads, see 'struct uwbd_evt_*'. + * + * EVENT HANDLER TABLES + * + * To find a handling function for an event, the type is used to index + * a subtype-table in the type-table. The subtype-table is indexed + * with the subtype to get the function that handles the event. Start + * with the main type-table 'uwbd_evt_type_handler'. + * + * DEVICES + * + * Devices are created when a bunch of beacons have been received and + * it is stablished that the device has stable radio presence. CREATED + * only, not configured. Devices are ONLY configured when an + * Application-Specific IE Probe is receieved, in which the device + * declares which Protocol ID it groks. Then the device is CONFIGURED + * (and the driver->probe() stuff of the device model is invoked). + * + * Devices are considered disconnected when a certain number of + * beacons are not received in an amount of time. + * + * Handler functions are called normally uwbd_evt_handle_*(). + */ + +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/freezer.h> +#include "uwb-internal.h" + +#define D_LOCAL 1 +#include <linux/uwb/debug.h> + + +/** + * UWBD Event handler function signature + * + * Return !0 if the event needs not to be freed (ie the handler + * takes/took care of it). 0 means the daemon code will free the + * event. + * + * @evt->rc is already referenced and guaranteed to exist. See + * uwb_evt_handle(). + */ +typedef int (*uwbd_evt_handler_f)(struct uwb_event *); + +/** + * Properties of a UWBD event + * + * @handler: the function that will handle this event + * @name: text name of event + */ +struct uwbd_event { + uwbd_evt_handler_f handler; + const char *name; +}; + +/** Table of handlers for and properties of the UWBD Radio Control Events */ +static +struct uwbd_event uwbd_events[] = { + [UWB_RC_EVT_BEACON] = { + .handler = uwbd_evt_handle_rc_beacon, + .name = "BEACON_RECEIVED" + }, + [UWB_RC_EVT_BEACON_SIZE] = { + .handler = uwbd_evt_handle_rc_beacon_size, + .name = "BEACON_SIZE_CHANGE" + }, + [UWB_RC_EVT_BPOIE_CHANGE] = { + .handler = uwbd_evt_handle_rc_bpoie_change, + .name = "BPOIE_CHANGE" + }, + [UWB_RC_EVT_BP_SLOT_CHANGE] = { + .handler = uwbd_evt_handle_rc_bp_slot_change, + .name = "BP_SLOT_CHANGE" + }, + [UWB_RC_EVT_DRP_AVAIL] = { + .handler = uwbd_evt_handle_rc_drp_avail, + .name = "DRP_AVAILABILITY_CHANGE" + }, + [UWB_RC_EVT_DRP] = { + .handler = uwbd_evt_handle_rc_drp, + .name = "DRP" + }, + [UWB_RC_EVT_DEV_ADDR_CONFLICT] = { + .handler = uwbd_evt_handle_rc_dev_addr_conflict, + .name = "DEV_ADDR_CONFLICT", + }, +}; + + + +struct uwbd_evt_type_handler { + const char *name; + struct uwbd_event *uwbd_events; + size_t size; +}; + +#define UWBD_EVT_TYPE_HANDLER(n,a) { \ + .name = (n), \ + .uwbd_events = (a), \ + .size = sizeof(a)/sizeof((a)[0]) \ +} + + +/** Table of handlers for each UWBD Event type. */ +static +struct uwbd_evt_type_handler uwbd_evt_type_handlers[] = { + [UWB_RC_CET_GENERAL] = UWBD_EVT_TYPE_HANDLER("RC", uwbd_events) +}; + +static const +size_t uwbd_evt_type_handlers_len = + sizeof(uwbd_evt_type_handlers) / sizeof(uwbd_evt_type_handlers[0]); + +static const struct uwbd_event uwbd_message_handlers[] = { + [UWB_EVT_MSG_RESET] = { + .handler = uwbd_msg_handle_reset, + .name = "reset", + }, +}; + +static DEFINE_MUTEX(uwbd_event_mutex); + +/** + * Handle an URC event passed to the UWB Daemon + * + * @evt: the event to handle + * @returns: 0 if the event can be kfreed, !0 on the contrary + * (somebody else took ownership) [coincidentally, returning + * a <0 errno code will free it :)]. + * + * Looks up the two indirection tables (one for the type, one for the + * subtype) to decide which function handles it and then calls the + * handler. + * + * The event structure passed to the event handler has the radio + * controller in @evt->rc referenced. The reference will be dropped + * once the handler returns, so if it needs it for longer (async), + * it'll need to take another one. + */ +static +int uwbd_event_handle_urc(struct uwb_event *evt) +{ + struct uwbd_evt_type_handler *type_table; + uwbd_evt_handler_f handler; + u8 type, context; + u16 event; + + type = evt->notif.rceb->bEventType; + event = le16_to_cpu(evt->notif.rceb->wEvent); + context = evt->notif.rceb->bEventContext; + + if (type > uwbd_evt_type_handlers_len) { + printk(KERN_ERR "UWBD: event type %u: unknown (too high)\n", type); + return -EINVAL; + } + type_table = &uwbd_evt_type_handlers[type]; + if (type_table->uwbd_events == NULL) { + printk(KERN_ERR "UWBD: event type %u: unknown\n", type); + return -EINVAL; + } + if (event > type_table->size) { + printk(KERN_ERR "UWBD: event %s[%u]: unknown (too high)\n", + type_table->name, event); + return -EINVAL; + } + handler = type_table->uwbd_events[event].handler; + if (handler == NULL) { + printk(KERN_ERR "UWBD: event %s[%u]: unknown\n", type_table->name, event); + return -EINVAL; + } + return (*handler)(evt); +} + +static void uwbd_event_handle_message(struct uwb_event *evt) +{ + struct uwb_rc *rc; + int result; + + rc = evt->rc; + + if (evt->message < 0 || evt->message >= ARRAY_SIZE(uwbd_message_handlers)) { + dev_err(&rc->uwb_dev.dev, "UWBD: invalid message type %d\n", evt->message); + return; + } + + /* If this is a reset event we need to drop the + * uwbd_event_mutex or it deadlocks when the reset handler + * attempts to flush the uwbd events. */ + if (evt->message == UWB_EVT_MSG_RESET) + mutex_unlock(&uwbd_event_mutex); + + result = uwbd_message_handlers[evt->message].handler(evt); + if (result < 0) + dev_err(&rc->uwb_dev.dev, "UWBD: '%s' message failed: %d\n", + uwbd_message_handlers[evt->message].name, result); + + if (evt->message == UWB_EVT_MSG_RESET) + mutex_lock(&uwbd_event_mutex); +} + +static void uwbd_event_handle(struct uwb_event *evt) +{ + struct uwb_rc *rc; + int should_keep; + + rc = evt->rc; + + if (rc->ready) { + switch (evt->type) { + case UWB_EVT_TYPE_NOTIF: + should_keep = uwbd_event_handle_urc(evt); + if (should_keep <= 0) + kfree(evt->notif.rceb); + break; + case UWB_EVT_TYPE_MSG: + uwbd_event_handle_message(evt); + break; + default: + dev_err(&rc->uwb_dev.dev, "UWBD: invalid event type %d\n", evt->type); + break; + } + } + + __uwb_rc_put(rc); /* for the __uwb_rc_get() in uwb_rc_notif_cb() */ +} +/* The UWB Daemon */ + + +/** Daemon's PID: used to decide if we can queue or not */ +static int uwbd_pid; +/** Daemon's task struct for managing the kthread */ +static struct task_struct *uwbd_task; +/** Daemon's waitqueue for waiting for new events */ +static DECLARE_WAIT_QUEUE_HEAD(uwbd_wq); +/** Daemon's list of events; we queue/dequeue here */ +static struct list_head uwbd_event_list = LIST_HEAD_INIT(uwbd_event_list); +/** Daemon's list lock to protect concurent access */ +static DEFINE_SPINLOCK(uwbd_event_list_lock); + + +/** + * UWB Daemon + * + * Listens to all UWB notifications and takes care to track the state + * of the UWB neighboorhood for the kernel. When we do a run, we + * spinlock, move the list to a private copy and release the + * lock. Hold it as little as possible. Not a conflict: it is + * guaranteed we own the events in the private list. + * + * FIXME: should change so we don't have a 1HZ timer all the time, but + * only if there are devices. + */ +static int uwbd(void *unused) +{ + unsigned long flags; + struct list_head list = LIST_HEAD_INIT(list); + struct uwb_event *evt, *nxt; + int should_stop = 0; + while (1) { + wait_event_interruptible_timeout( + uwbd_wq, + !list_empty(&uwbd_event_list) + || (should_stop = kthread_should_stop()), + HZ); + if (should_stop) + break; + try_to_freeze(); + + mutex_lock(&uwbd_event_mutex); + spin_lock_irqsave(&uwbd_event_list_lock, flags); + list_splice_init(&uwbd_event_list, &list); + spin_unlock_irqrestore(&uwbd_event_list_lock, flags); + list_for_each_entry_safe(evt, nxt, &list, list_node) { + list_del(&evt->list_node); + uwbd_event_handle(evt); + kfree(evt); + } + mutex_unlock(&uwbd_event_mutex); + + uwb_beca_purge(); /* Purge devices that left */ + } + return 0; +} + + +/** Start the UWB daemon */ +void uwbd_start(void) +{ + uwbd_task = kthread_run(uwbd, NULL, "uwbd"); + if (uwbd_task == NULL) + printk(KERN_ERR "UWB: Cannot start management daemon; " + "UWB won't work\n"); + else + uwbd_pid = uwbd_task->pid; +} + +/* Stop the UWB daemon and free any unprocessed events */ +void uwbd_stop(void) +{ + unsigned long flags; + struct uwb_event *evt, *nxt; + kthread_stop(uwbd_task); + spin_lock_irqsave(&uwbd_event_list_lock, flags); + uwbd_pid = 0; + list_for_each_entry_safe(evt, nxt, &uwbd_event_list, list_node) { + if (evt->type == UWB_EVT_TYPE_NOTIF) + kfree(evt->notif.rceb); + kfree(evt); + } + spin_unlock_irqrestore(&uwbd_event_list_lock, flags); + uwb_beca_release(); +} + +/* + * Queue an event for the management daemon + * + * When some lower layer receives an event, it uses this function to + * push it forward to the UWB daemon. + * + * Once you pass the event, you don't own it any more, but the daemon + * does. It will uwb_event_free() it when done, so make sure you + * uwb_event_alloc()ed it or bad things will happen. + * + * If the daemon is not running, we just free the event. + */ +void uwbd_event_queue(struct uwb_event *evt) +{ + unsigned long flags; + spin_lock_irqsave(&uwbd_event_list_lock, flags); + if (uwbd_pid != 0) { + list_add(&evt->list_node, &uwbd_event_list); + wake_up_all(&uwbd_wq); + } else { + __uwb_rc_put(evt->rc); + if (evt->type == UWB_EVT_TYPE_NOTIF) + kfree(evt->notif.rceb); + kfree(evt); + } + spin_unlock_irqrestore(&uwbd_event_list_lock, flags); + return; +} + +void uwbd_flush(struct uwb_rc *rc) +{ + struct uwb_event *evt, *nxt; + + mutex_lock(&uwbd_event_mutex); + + spin_lock_irq(&uwbd_event_list_lock); + list_for_each_entry_safe(evt, nxt, &uwbd_event_list, list_node) { + if (evt->rc == rc) { + __uwb_rc_put(rc); + list_del(&evt->list_node); + if (evt->type == UWB_EVT_TYPE_NOTIF) + kfree(evt->notif.rceb); + kfree(evt); + } + } + spin_unlock_irq(&uwbd_event_list_lock); + + mutex_unlock(&uwbd_event_mutex); +} diff --git a/drivers/uwb/whc-rc.c b/drivers/uwb/whc-rc.c new file mode 100644 index 00000000000..1711deadb11 --- /dev/null +++ b/drivers/uwb/whc-rc.c @@ -0,0 +1,520 @@ +/* + * Wireless Host Controller: Radio Control Interface (WHCI v0.95[2.3]) + * Radio Control command/event transport to the UWB stack + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Initialize and hook up the Radio Control interface. + * + * For each device probed, creates an 'struct whcrc' which contains + * just the representation of the UWB Radio Controller, and the logic + * for reading notifications and passing them to the UWB Core. + * + * So we initialize all of those, register the UWB Radio Controller + * and setup the notification/event handle to pipe the notifications + * to the UWB management Daemon. + * + * Once uwb_rc_add() is called, the UWB stack takes control, resets + * the radio and readies the device to take commands the UWB + * API/user-space. + * + * Note this driver is just a transport driver; the commands are + * formed at the UWB stack and given to this driver who will deliver + * them to the hw and transfer the replies/notifications back to the + * UWB stack through the UWB daemon (UWBD). + */ +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/uwb.h> +#include <linux/uwb/whci.h> +#include <linux/uwb/umc.h> +#include "uwb-internal.h" + +#define D_LOCAL 0 +#include <linux/uwb/debug.h> + +/** + * Descriptor for an instance of the UWB Radio Control Driver that + * attaches to the URC interface of the WHCI PCI card. + * + * Unless there is a lock specific to the 'data members', all access + * is protected by uwb_rc->mutex. + */ +struct whcrc { + struct umc_dev *umc_dev; + struct uwb_rc *uwb_rc; /* UWB host controller */ + + unsigned long area; + void __iomem *rc_base; + size_t rc_len; + spinlock_t irq_lock; + + void *evt_buf, *cmd_buf; + dma_addr_t evt_dma_buf, cmd_dma_buf; + wait_queue_head_t cmd_wq; + struct work_struct event_work; +}; + +/** + * Execute an UWB RC command on WHCI/RC + * + * @rc: Instance of a Radio Controller that is a whcrc + * @cmd: Buffer containing the RCCB and payload to execute + * @cmd_size: Size of the command buffer. + * + * We copy the command into whcrc->cmd_buf (as it is pretty and + * aligned`and physically contiguous) and then press the right keys in + * the controller's URCCMD register to get it to read it. We might + * have to wait for the cmd_sem to be open to us. + * + * NOTE: rc's mutex has to be locked + */ +static int whcrc_cmd(struct uwb_rc *uwb_rc, + const struct uwb_rccb *cmd, size_t cmd_size) +{ + int result = 0; + struct whcrc *whcrc = uwb_rc->priv; + struct device *dev = &whcrc->umc_dev->dev; + u32 urccmd; + + d_fnstart(3, dev, "(%p, %p, %zu)\n", uwb_rc, cmd, cmd_size); + might_sleep(); + + if (cmd_size >= 4096) { + result = -E2BIG; + goto error; + } + + /* + * If the URC is halted, then the hardware has reset itself. + * Attempt to recover by restarting the device and then return + * an error as it's likely that the current command isn't + * valid for a newly started RC. + */ + if (le_readl(whcrc->rc_base + URCSTS) & URCSTS_HALTED) { + dev_err(dev, "requesting reset of halted radio controller\n"); + uwb_rc_reset_all(uwb_rc); + result = -EIO; + goto error; + } + + result = wait_event_timeout(whcrc->cmd_wq, + !(le_readl(whcrc->rc_base + URCCMD) & URCCMD_ACTIVE), HZ/2); + if (result == 0) { + dev_err(dev, "device is not ready to execute commands\n"); + result = -ETIMEDOUT; + goto error; + } + + memmove(whcrc->cmd_buf, cmd, cmd_size); + le_writeq(whcrc->cmd_dma_buf, whcrc->rc_base + URCCMDADDR); + + spin_lock(&whcrc->irq_lock); + urccmd = le_readl(whcrc->rc_base + URCCMD); + urccmd &= ~(URCCMD_EARV | URCCMD_SIZE_MASK); + le_writel(urccmd | URCCMD_ACTIVE | URCCMD_IWR | cmd_size, + whcrc->rc_base + URCCMD); + spin_unlock(&whcrc->irq_lock); + +error: + d_fnend(3, dev, "(%p, %p, %zu) = %d\n", + uwb_rc, cmd, cmd_size, result); + return result; +} + +static int whcrc_reset(struct uwb_rc *rc) +{ + struct whcrc *whcrc = rc->priv; + + return umc_controller_reset(whcrc->umc_dev); +} + +/** + * Reset event reception mechanism and tell hw we are ready to get more + * + * We have read all the events in the event buffer, so we are ready to + * reset it to the beginning. + * + * This is only called during initialization or after an event buffer + * has been retired. This means we can be sure that event processing + * is disabled and it's safe to update the URCEVTADDR register. + * + * There's no need to wait for the event processing to start as the + * URC will not clear URCCMD_ACTIVE until (internal) event buffer + * space is available. + */ +static +void whcrc_enable_events(struct whcrc *whcrc) +{ + struct device *dev = &whcrc->umc_dev->dev; + u32 urccmd; + + d_fnstart(4, dev, "(whcrc %p)\n", whcrc); + + le_writeq(whcrc->evt_dma_buf, whcrc->rc_base + URCEVTADDR); + + spin_lock(&whcrc->irq_lock); + urccmd = le_readl(whcrc->rc_base + URCCMD) & ~URCCMD_ACTIVE; + le_writel(urccmd | URCCMD_EARV, whcrc->rc_base + URCCMD); + spin_unlock(&whcrc->irq_lock); + + d_fnend(4, dev, "(whcrc %p) = void\n", whcrc); +} + +static void whcrc_event_work(struct work_struct *work) +{ + struct whcrc *whcrc = container_of(work, struct whcrc, event_work); + struct device *dev = &whcrc->umc_dev->dev; + size_t size; + u64 urcevtaddr; + + urcevtaddr = le_readq(whcrc->rc_base + URCEVTADDR); + size = urcevtaddr & URCEVTADDR_OFFSET_MASK; + + d_printf(3, dev, "received %zu octet event\n", size); + d_dump(4, dev, whcrc->evt_buf, size > 32 ? 32 : size); + + uwb_rc_neh_grok(whcrc->uwb_rc, whcrc->evt_buf, size); + whcrc_enable_events(whcrc); +} + +/** + * Catch interrupts? + * + * We ack inmediately (and expect the hw to do the right thing and + * raise another IRQ if things have changed :) + */ +static +irqreturn_t whcrc_irq_cb(int irq, void *_whcrc) +{ + struct whcrc *whcrc = _whcrc; + struct device *dev = &whcrc->umc_dev->dev; + u32 urcsts; + + urcsts = le_readl(whcrc->rc_base + URCSTS); + if (!(urcsts & URCSTS_INT_MASK)) + return IRQ_NONE; + le_writel(urcsts & URCSTS_INT_MASK, whcrc->rc_base + URCSTS); + + d_printf(4, dev, "acked 0x%08x, urcsts 0x%08x\n", + le_readl(whcrc->rc_base + URCSTS), urcsts); + + if (urcsts & URCSTS_HSE) { + dev_err(dev, "host system error -- hardware halted\n"); + /* FIXME: do something sensible here */ + goto out; + } + if (urcsts & URCSTS_ER) { + d_printf(3, dev, "ER: event ready\n"); + schedule_work(&whcrc->event_work); + } + if (urcsts & URCSTS_RCI) { + d_printf(3, dev, "RCI: ready to execute another command\n"); + wake_up_all(&whcrc->cmd_wq); + } +out: + return IRQ_HANDLED; +} + + +/** + * Initialize a UMC RC interface: map regions, get (shared) IRQ + */ +static +int whcrc_setup_rc_umc(struct whcrc *whcrc) +{ + int result = 0; + struct device *dev = &whcrc->umc_dev->dev; + struct umc_dev *umc_dev = whcrc->umc_dev; + + whcrc->area = umc_dev->resource.start; + whcrc->rc_len = umc_dev->resource.end - umc_dev->resource.start + 1; + result = -EBUSY; + if (request_mem_region(whcrc->area, whcrc->rc_len, KBUILD_MODNAME) + == NULL) { + dev_err(dev, "can't request URC region (%zu bytes @ 0x%lx): %d\n", + whcrc->rc_len, whcrc->area, result); + goto error_request_region; + } + + whcrc->rc_base = ioremap_nocache(whcrc->area, whcrc->rc_len); + if (whcrc->rc_base == NULL) { + dev_err(dev, "can't ioremap registers (%zu bytes @ 0x%lx): %d\n", + whcrc->rc_len, whcrc->area, result); + goto error_ioremap_nocache; + } + + result = request_irq(umc_dev->irq, whcrc_irq_cb, IRQF_SHARED, + KBUILD_MODNAME, whcrc); + if (result < 0) { + dev_err(dev, "can't allocate IRQ %d: %d\n", + umc_dev->irq, result); + goto error_request_irq; + } + + result = -ENOMEM; + whcrc->cmd_buf = dma_alloc_coherent(&umc_dev->dev, PAGE_SIZE, + &whcrc->cmd_dma_buf, GFP_KERNEL); + if (whcrc->cmd_buf == NULL) { + dev_err(dev, "Can't allocate cmd transfer buffer\n"); + goto error_cmd_buffer; + } + + whcrc->evt_buf = dma_alloc_coherent(&umc_dev->dev, PAGE_SIZE, + &whcrc->evt_dma_buf, GFP_KERNEL); + if (whcrc->evt_buf == NULL) { + dev_err(dev, "Can't allocate evt transfer buffer\n"); + goto error_evt_buffer; + } + d_printf(3, dev, "UWB RC Interface: %zu bytes at 0x%p, irq %u\n", + whcrc->rc_len, whcrc->rc_base, umc_dev->irq); + return 0; + +error_evt_buffer: + dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->cmd_buf, + whcrc->cmd_dma_buf); +error_cmd_buffer: + free_irq(umc_dev->irq, whcrc); +error_request_irq: + iounmap(whcrc->rc_base); +error_ioremap_nocache: + release_mem_region(whcrc->area, whcrc->rc_len); +error_request_region: + return result; +} + + +/** + * Release RC's UMC resources + */ +static +void whcrc_release_rc_umc(struct whcrc *whcrc) +{ + struct umc_dev *umc_dev = whcrc->umc_dev; + + dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->evt_buf, + whcrc->evt_dma_buf); + dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->cmd_buf, + whcrc->cmd_dma_buf); + free_irq(umc_dev->irq, whcrc); + iounmap(whcrc->rc_base); + release_mem_region(whcrc->area, whcrc->rc_len); +} + + +/** + * whcrc_start_rc - start a WHCI radio controller + * @whcrc: the radio controller to start + * + * Reset the UMC device, start the radio controller, enable events and + * finally enable interrupts. + */ +static int whcrc_start_rc(struct uwb_rc *rc) +{ + struct whcrc *whcrc = rc->priv; + int result = 0; + struct device *dev = &whcrc->umc_dev->dev; + unsigned long start, duration; + + /* Reset the thing */ + le_writel(URCCMD_RESET, whcrc->rc_base + URCCMD); + if (d_test(3)) + start = jiffies; + if (whci_wait_for(dev, whcrc->rc_base + URCCMD, URCCMD_RESET, 0, + 5000, "device to reset at init") < 0) { + result = -EBUSY; + goto error; + } else if (d_test(3)) { + duration = jiffies - start; + if (duration > msecs_to_jiffies(40)) + dev_err(dev, "Device took %ums to " + "reset. MAX expected: 40ms\n", + jiffies_to_msecs(duration)); + } + + /* Set the event buffer, start the controller (enable IRQs later) */ + le_writel(0, whcrc->rc_base + URCINTR); + le_writel(URCCMD_RS, whcrc->rc_base + URCCMD); + result = -ETIMEDOUT; + if (d_test(3)) + start = jiffies; + if (whci_wait_for(dev, whcrc->rc_base + URCSTS, URCSTS_HALTED, 0, + 5000, "device to start") < 0) + goto error; + if (d_test(3)) { + duration = jiffies - start; + if (duration > msecs_to_jiffies(40)) + dev_err(dev, "Device took %ums to start. " + "MAX expected: 40ms\n", + jiffies_to_msecs(duration)); + } + whcrc_enable_events(whcrc); + result = 0; + le_writel(URCINTR_EN_ALL, whcrc->rc_base + URCINTR); +error: + return result; +} + + +/** + * whcrc_stop_rc - stop a WHCI radio controller + * @whcrc: the radio controller to stop + * + * Disable interrupts and cancel any pending event processing work + * before clearing the Run/Stop bit. + */ +static +void whcrc_stop_rc(struct uwb_rc *rc) +{ + struct whcrc *whcrc = rc->priv; + struct umc_dev *umc_dev = whcrc->umc_dev; + + le_writel(0, whcrc->rc_base + URCINTR); + cancel_work_sync(&whcrc->event_work); + + le_writel(0, whcrc->rc_base + URCCMD); + whci_wait_for(&umc_dev->dev, whcrc->rc_base + URCSTS, + URCSTS_HALTED, 0, 40, "URCSTS.HALTED"); +} + +static void whcrc_init(struct whcrc *whcrc) +{ + spin_lock_init(&whcrc->irq_lock); + init_waitqueue_head(&whcrc->cmd_wq); + INIT_WORK(&whcrc->event_work, whcrc_event_work); +} + +/** + * Initialize the radio controller. + * + * NOTE: we setup whcrc->uwb_rc before calling uwb_rc_add(); in the + * IRQ handler we use that to determine if the hw is ready to + * handle events. Looks like a race condition, but it really is + * not. + */ +static +int whcrc_probe(struct umc_dev *umc_dev) +{ + int result; + struct uwb_rc *uwb_rc; + struct whcrc *whcrc; + struct device *dev = &umc_dev->dev; + + d_fnstart(3, dev, "(umc_dev %p)\n", umc_dev); + result = -ENOMEM; + uwb_rc = uwb_rc_alloc(); + if (uwb_rc == NULL) { + dev_err(dev, "unable to allocate RC instance\n"); + goto error_rc_alloc; + } + whcrc = kzalloc(sizeof(*whcrc), GFP_KERNEL); + if (whcrc == NULL) { + dev_err(dev, "unable to allocate WHC-RC instance\n"); + goto error_alloc; + } + whcrc_init(whcrc); + whcrc->umc_dev = umc_dev; + + result = whcrc_setup_rc_umc(whcrc); + if (result < 0) { + dev_err(dev, "Can't setup RC UMC interface: %d\n", result); + goto error_setup_rc_umc; + } + whcrc->uwb_rc = uwb_rc; + + uwb_rc->owner = THIS_MODULE; + uwb_rc->cmd = whcrc_cmd; + uwb_rc->reset = whcrc_reset; + uwb_rc->start = whcrc_start_rc; + uwb_rc->stop = whcrc_stop_rc; + + result = uwb_rc_add(uwb_rc, dev, whcrc); + if (result < 0) + goto error_rc_add; + umc_set_drvdata(umc_dev, whcrc); + d_fnend(3, dev, "(umc_dev %p) = 0\n", umc_dev); + return 0; + +error_rc_add: + whcrc_release_rc_umc(whcrc); +error_setup_rc_umc: + kfree(whcrc); +error_alloc: + uwb_rc_put(uwb_rc); +error_rc_alloc: + d_fnend(3, dev, "(umc_dev %p) = %d\n", umc_dev, result); + return result; +} + +/** + * Clean up the radio control resources + * + * When we up the command semaphore, everybody possibly held trying to + * execute a command should be granted entry and then they'll see the + * host is quiescing and up it (so it will chain to the next waiter). + * This should not happen (in any case), as we can only remove when + * there are no handles open... + */ +static void whcrc_remove(struct umc_dev *umc_dev) +{ + struct whcrc *whcrc = umc_get_drvdata(umc_dev); + struct uwb_rc *uwb_rc = whcrc->uwb_rc; + + umc_set_drvdata(umc_dev, NULL); + uwb_rc_rm(uwb_rc); + whcrc_release_rc_umc(whcrc); + kfree(whcrc); + uwb_rc_put(uwb_rc); + d_printf(1, &umc_dev->dev, "freed whcrc %p\n", whcrc); +} + +/* PCI device ID's that we handle [so it gets loaded] */ +static struct pci_device_id whcrc_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, + { /* empty last entry */ } +}; +MODULE_DEVICE_TABLE(pci, whcrc_id_table); + +static struct umc_driver whcrc_driver = { + .name = "whc-rc", + .cap_id = UMC_CAP_ID_WHCI_RC, + .probe = whcrc_probe, + .remove = whcrc_remove, +}; + +static int __init whcrc_driver_init(void) +{ + return umc_driver_register(&whcrc_driver); +} +module_init(whcrc_driver_init); + +static void __exit whcrc_driver_exit(void) +{ + umc_driver_unregister(&whcrc_driver); +} +module_exit(whcrc_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>"); +MODULE_DESCRIPTION("Wireless Host Controller Radio Control Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/whci.c b/drivers/uwb/whci.c new file mode 100644 index 00000000000..3df2388f908 --- /dev/null +++ b/drivers/uwb/whci.c @@ -0,0 +1,269 @@ +/* + * WHCI UWB Multi-interface Controller enumerator. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GNU GPL v2. + */ +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/uwb/whci.h> +#include <linux/uwb/umc.h> + +struct whci_card { + struct pci_dev *pci; + void __iomem *uwbbase; + u8 n_caps; + struct umc_dev *devs[0]; +}; + + +/* Fix faulty HW :( */ +static +u64 whci_capdata_quirks(struct whci_card *card, u64 capdata) +{ + u64 capdata_orig = capdata; + struct pci_dev *pci_dev = card->pci; + if (pci_dev->vendor == PCI_VENDOR_ID_INTEL + && (pci_dev->device == 0x0c3b || pci_dev->device == 0004) + && pci_dev->class == 0x0d1010) { + switch (UWBCAPDATA_TO_CAP_ID(capdata)) { + /* WLP capability has 0x100 bytes of aperture */ + case 0x80: + capdata |= 0x40 << 8; break; + /* WUSB capability has 0x80 bytes of aperture + * and ID is 1 */ + case 0x02: + capdata &= ~0xffff; + capdata |= 0x2001; + break; + } + } + if (capdata_orig != capdata) + dev_warn(&pci_dev->dev, + "PCI v%04x d%04x c%06x#%02x: " + "corrected capdata from %016Lx to %016Lx\n", + pci_dev->vendor, pci_dev->device, pci_dev->class, + (unsigned)UWBCAPDATA_TO_CAP_ID(capdata), + (unsigned long long)capdata_orig, + (unsigned long long)capdata); + return capdata; +} + + +/** + * whci_wait_for - wait for a WHCI register to be set + * + * Polls (for at most @max_ms ms) until '*@reg & @mask == @result'. + */ +int whci_wait_for(struct device *dev, u32 __iomem *reg, u32 mask, u32 result, + unsigned long max_ms, const char *tag) +{ + unsigned t = 0; + u32 val; + for (;;) { + val = le_readl(reg); + if ((val & mask) == result) + break; + msleep(10); + if (t >= max_ms) { + dev_err(dev, "timed out waiting for %s ", tag); + return -ETIMEDOUT; + } + t += 10; + } + return 0; +} +EXPORT_SYMBOL_GPL(whci_wait_for); + + +/* + * NOTE: the capinfo and capdata registers are slightly different + * (size and cap-id fields). So for cap #0, we need to fill + * in. Size comes from the size of the register block + * (statically calculated); cap_id comes from nowhere, we use + * zero, that is reserved, for the radio controller, because + * none was defined at the spec level. + */ +static int whci_add_cap(struct whci_card *card, int n) +{ + struct umc_dev *umc; + u64 capdata; + int bar, err; + + umc = umc_device_create(&card->pci->dev, n); + if (umc == NULL) + return -ENOMEM; + + capdata = le_readq(card->uwbbase + UWBCAPDATA(n)); + + bar = UWBCAPDATA_TO_BAR(capdata) << 1; + + capdata = whci_capdata_quirks(card, capdata); + /* Capability 0 is the radio controller. It's size is 32 + * bytes (WHCI0.95[2.3, T2-9]). */ + umc->version = UWBCAPDATA_TO_VERSION(capdata); + umc->cap_id = n == 0 ? 0 : UWBCAPDATA_TO_CAP_ID(capdata); + umc->bar = bar; + umc->resource.start = pci_resource_start(card->pci, bar) + + UWBCAPDATA_TO_OFFSET(capdata); + umc->resource.end = umc->resource.start + + (n == 0 ? 0x20 : UWBCAPDATA_TO_SIZE(capdata)) - 1; + umc->resource.name = umc->dev.bus_id; + umc->resource.flags = card->pci->resource[bar].flags; + umc->resource.parent = &card->pci->resource[bar]; + umc->irq = card->pci->irq; + + err = umc_device_register(umc); + if (err < 0) + goto error; + card->devs[n] = umc; + return 0; + +error: + kfree(umc); + return err; +} + +static void whci_del_cap(struct whci_card *card, int n) +{ + struct umc_dev *umc = card->devs[n]; + + if (umc != NULL) + umc_device_unregister(umc); +} + +static int whci_n_caps(struct pci_dev *pci) +{ + void __iomem *uwbbase; + u64 capinfo; + + uwbbase = pci_iomap(pci, 0, 8); + if (!uwbbase) + return -ENOMEM; + capinfo = le_readq(uwbbase + UWBCAPINFO); + pci_iounmap(pci, uwbbase); + + return UWBCAPINFO_TO_N_CAPS(capinfo); +} + +static int whci_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + struct whci_card *card; + int err, n_caps, n; + + err = pci_enable_device(pci); + if (err < 0) + goto error; + pci_enable_msi(pci); + pci_set_master(pci); + err = -ENXIO; + if (!pci_set_dma_mask(pci, DMA_64BIT_MASK)) + pci_set_consistent_dma_mask(pci, DMA_64BIT_MASK); + else if (!pci_set_dma_mask(pci, DMA_32BIT_MASK)) + pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK); + else + goto error_dma; + + err = n_caps = whci_n_caps(pci); + if (n_caps < 0) + goto error_ncaps; + + err = -ENOMEM; + card = kzalloc(sizeof(struct whci_card) + + sizeof(struct whci_dev *) * (n_caps + 1), + GFP_KERNEL); + if (card == NULL) + goto error_kzalloc; + card->pci = pci; + card->n_caps = n_caps; + + err = -EBUSY; + if (!request_mem_region(pci_resource_start(pci, 0), + UWBCAPDATA_SIZE(card->n_caps), + "whci (capability data)")) + goto error_request_memregion; + err = -ENOMEM; + card->uwbbase = pci_iomap(pci, 0, UWBCAPDATA_SIZE(card->n_caps)); + if (!card->uwbbase) + goto error_iomap; + + /* Add each capability. */ + for (n = 0; n <= card->n_caps; n++) { + err = whci_add_cap(card, n); + if (err < 0 && n == 0) { + dev_err(&pci->dev, "cannot bind UWB radio controller:" + " %d\n", err); + goto error_bind; + } + if (err < 0) + dev_warn(&pci->dev, "warning: cannot bind capability " + "#%u: %d\n", n, err); + } + pci_set_drvdata(pci, card); + return 0; + +error_bind: + pci_iounmap(pci, card->uwbbase); +error_iomap: + release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps)); +error_request_memregion: + kfree(card); +error_kzalloc: +error_ncaps: +error_dma: + pci_disable_msi(pci); + pci_disable_device(pci); +error: + return err; +} + +static void whci_remove(struct pci_dev *pci) +{ + struct whci_card *card = pci_get_drvdata(pci); + int n; + + pci_set_drvdata(pci, NULL); + /* Unregister each capability in reverse (so the master device + * is unregistered last). */ + for (n = card->n_caps; n >= 0 ; n--) + whci_del_cap(card, n); + pci_iounmap(pci, card->uwbbase); + release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps)); + kfree(card); + pci_disable_msi(pci); + pci_disable_device(pci); +} + +static struct pci_device_id whci_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, whci_id_table); + + +static struct pci_driver whci_driver = { + .name = "whci", + .id_table = whci_id_table, + .probe = whci_probe, + .remove = whci_remove, +}; + +static int __init whci_init(void) +{ + return pci_register_driver(&whci_driver); +} + +static void __exit whci_exit(void) +{ + pci_unregister_driver(&whci_driver); +} + +module_init(whci_init); +module_exit(whci_exit); + +MODULE_DESCRIPTION("WHCI UWB Multi-interface Controller enumerator"); +MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/wlp/Makefile b/drivers/uwb/wlp/Makefile new file mode 100644 index 00000000000..c72c11db5b1 --- /dev/null +++ b/drivers/uwb/wlp/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_UWB_WLP) := wlp.o + +wlp-objs := \ + driver.o \ + eda.o \ + messages.o \ + sysfs.o \ + txrx.o \ + wlp-lc.o \ + wss-lc.o diff --git a/drivers/uwb/wlp/driver.c b/drivers/uwb/wlp/driver.c new file mode 100644 index 00000000000..cb8d699b6a6 --- /dev/null +++ b/drivers/uwb/wlp/driver.c @@ -0,0 +1,43 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Life cycle of WLP substack + * + * FIXME: Docs + */ + +#include <linux/module.h> + +static int __init wlp_subsys_init(void) +{ + return 0; +} +module_init(wlp_subsys_init); + +static void __exit wlp_subsys_exit(void) +{ + return; +} +module_exit(wlp_subsys_exit); + +MODULE_AUTHOR("Reinette Chatre <reinette.chatre@intel.com>"); +MODULE_DESCRIPTION("WiMedia Logical Link Control Protocol (WLP)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/wlp/eda.c b/drivers/uwb/wlp/eda.c new file mode 100644 index 00000000000..cdfe8dfc434 --- /dev/null +++ b/drivers/uwb/wlp/eda.c @@ -0,0 +1,449 @@ +/* + * WUSB Wire Adapter: WLP interface + * Ethernet to device address cache + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We need to be able to map ethernet addresses to device addresses + * and back because there is not explicit relationship between the eth + * addresses used in the ETH frames and the device addresses (no, it + * would not have been simpler to force as ETH address the MBOA MAC + * address...no, not at all :). + * + * A device has one MBOA MAC address and one device address. It is possible + * for a device to have more than one virtual MAC address (although a + * virtual address can be the same as the MBOA MAC address). The device + * address is guaranteed to be unique among the devices in the extended + * beacon group (see ECMA 17.1.1). We thus use the device address as index + * to this cache. We do allow searching based on virtual address as this + * is how Ethernet frames will be addressed. + * + * We need to support virtual EUI-48. Although, right now the virtual + * EUI-48 will always be the same as the MAC SAP address. The EDA cache + * entry thus contains a MAC SAP address as well as the virtual address + * (used to map the network stack address to a neighbor). When we move + * to support more than one virtual MAC on a host then this organization + * will have to change. Perhaps a neighbor has a list of WSSs, each with a + * tag and virtual EUI-48. + * + * On data transmission + * it is used to determine if the neighbor is connected and what WSS it + * belongs to. With this we know what tag to add to the WLP frame. Storing + * the WSS in the EDA cache may be overkill because we only support one + * WSS. Hopefully we will support more than one WSS at some point. + * On data reception it is used to determine the WSS based on + * the tag and address of the transmitting neighbor. + */ + +#define D_LOCAL 5 +#include <linux/netdevice.h> +#include <linux/uwb/debug.h> +#include <linux/etherdevice.h> +#include <linux/wlp.h> +#include "wlp-internal.h" + + +/* FIXME: cache is not purged, only on device close */ + +/* FIXME: does not scale, change to dynamic array */ + +/* + * Initialize the EDA cache + * + * @returns 0 if ok, < 0 errno code on error + * + * Call when the interface is being brought up + * + * NOTE: Keep it as a separate function as the implementation will + * change and be more complex. + */ +void wlp_eda_init(struct wlp_eda *eda) +{ + INIT_LIST_HEAD(&eda->cache); + spin_lock_init(&eda->lock); +} + +/* + * Release the EDA cache + * + * @returns 0 if ok, < 0 errno code on error + * + * Called when the interface is brought down + */ +void wlp_eda_release(struct wlp_eda *eda) +{ + unsigned long flags; + struct wlp_eda_node *itr, *next; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry_safe(itr, next, &eda->cache, list_node) { + list_del(&itr->list_node); + kfree(itr); + } + spin_unlock_irqrestore(&eda->lock, flags); +} + +/* + * Add an address mapping + * + * @returns 0 if ok, < 0 errno code on error + * + * An address mapping is initially created when the neighbor device is seen + * for the first time (it is "onair"). At this time the neighbor is not + * connected or associated with a WSS so we only populate the Ethernet and + * Device address fields. + * + */ +int wlp_eda_create_node(struct wlp_eda *eda, + const unsigned char eth_addr[ETH_ALEN], + const struct uwb_dev_addr *dev_addr) +{ + int result = 0; + struct wlp_eda_node *itr; + unsigned long flags; + + BUG_ON(dev_addr == NULL || eth_addr == NULL); + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + printk(KERN_ERR "EDA cache already contains entry " + "for neighbor %02x:%02x\n", + dev_addr->data[1], dev_addr->data[0]); + result = -EEXIST; + goto out_unlock; + } + } + itr = kzalloc(sizeof(*itr), GFP_ATOMIC); + if (itr != NULL) { + memcpy(itr->eth_addr, eth_addr, sizeof(itr->eth_addr)); + itr->dev_addr = *dev_addr; + list_add(&itr->list_node, &eda->cache); + } else + result = -ENOMEM; +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Remove entry from EDA cache + * + * This is done when the device goes off air. + */ +void wlp_eda_rm_node(struct wlp_eda *eda, const struct uwb_dev_addr *dev_addr) +{ + struct wlp_eda_node *itr, *next; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry_safe(itr, next, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + list_del(&itr->list_node); + kfree(itr); + break; + } + } + spin_unlock_irqrestore(&eda->lock, flags); +} + +/* + * Update an address mapping + * + * @returns 0 if ok, < 0 errno code on error + */ +int wlp_eda_update_node(struct wlp_eda *eda, + const struct uwb_dev_addr *dev_addr, + struct wlp_wss *wss, + const unsigned char virt_addr[ETH_ALEN], + const u8 tag, const enum wlp_wss_connect state) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + /* Found it, update it */ + itr->wss = wss; + memcpy(itr->virt_addr, virt_addr, + sizeof(itr->virt_addr)); + itr->tag = tag; + itr->state = state; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Update only state field of an address mapping + * + * @returns 0 if ok, < 0 errno code on error + */ +int wlp_eda_update_node_state(struct wlp_eda *eda, + const struct uwb_dev_addr *dev_addr, + const enum wlp_wss_connect state) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + /* Found it, update it */ + itr->state = state; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Return contents of EDA cache entry + * + * @dev_addr: index to EDA cache + * @eda_entry: pointer to where contents of EDA cache will be copied + */ +int wlp_copy_eda_node(struct wlp_eda *eda, struct uwb_dev_addr *dev_addr, + struct wlp_eda_node *eda_entry) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + *eda_entry = *itr; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Execute function for every element in the cache + * + * @function: function to execute on element of cache (must be atomic) + * @priv: private data of function + * @returns: result of first function that failed, or last function + * executed if no function failed. + * + * Stop executing when function returns error for any element in cache. + * + * IMPORTANT: We are using a spinlock here: the function executed on each + * element has to be atomic. + */ +int wlp_eda_for_each(struct wlp_eda *eda, wlp_eda_for_each_f function, + void *priv) +{ + int result = 0; + struct wlp *wlp = container_of(eda, struct wlp, eda); + struct wlp_eda_node *entry; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(entry, &eda->cache, list_node) { + result = (*function)(wlp, entry, priv); + if (result < 0) + break; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Execute function for single element in the cache (return dev addr) + * + * @virt_addr: index into EDA cache used to determine which element to + * execute the function on + * @dev_addr: device address of element in cache will be returned using + * @dev_addr + * @function: function to execute on element of cache (must be atomic) + * @priv: private data of function + * @returns: result of function + * + * IMPORTANT: We are using a spinlock here: the function executed on the + * element has to be atomic. + */ +int wlp_eda_for_virtual(struct wlp_eda *eda, + const unsigned char virt_addr[ETH_ALEN], + struct uwb_dev_addr *dev_addr, + wlp_eda_for_each_f function, + void *priv) +{ + int result = 0; + struct wlp *wlp = container_of(eda, struct wlp, eda); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_eda_node *itr; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(itr->virt_addr, virt_addr, + sizeof(itr->virt_addr))) { + d_printf(6, dev, "EDA: looking for " + "%02x:%02x:%02x:%02x:%02x:%02x hit %02x:%02x " + "wss %p tag 0x%02x state %u\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5], + itr->dev_addr.data[1], + itr->dev_addr.data[0], itr->wss, + itr->tag, itr->state); + result = (*function)(wlp, itr, priv); + *dev_addr = itr->dev_addr; + found = 1; + break; + } else + d_printf(6, dev, "EDA: looking for " + "%02x:%02x:%02x:%02x:%02x:%02x " + "against " + "%02x:%02x:%02x:%02x:%02x:%02x miss\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5], + itr->virt_addr[0], itr->virt_addr[1], + itr->virt_addr[2], itr->virt_addr[3], + itr->virt_addr[4], itr->virt_addr[5]); + } + if (!found) { + if (printk_ratelimit()) + dev_err(dev, "EDA: Eth addr %02x:%02x:%02x" + ":%02x:%02x:%02x not found.\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5]); + result = -ENODEV; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +static const char *__wlp_wss_connect_state[] = { "WLP_WSS_UNCONNECTED", + "WLP_WSS_CONNECTED", + "WLP_WSS_CONNECT_FAILED", +}; + +static const char *wlp_wss_connect_state_str(unsigned id) +{ + if (id >= ARRAY_SIZE(__wlp_wss_connect_state)) + return "unknown WSS connection state"; + return __wlp_wss_connect_state[id]; +} + +/* + * View EDA cache from user space + * + * A debugging feature to give user visibility into the EDA cache. Also + * used to display members of WSS to user (called from wlp_wss_members_show()) + */ +ssize_t wlp_eda_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + struct wlp_eda_node *entry; + unsigned long flags; + struct wlp_eda *eda = &wlp->eda; + spin_lock_irqsave(&eda->lock, flags); + result = scnprintf(buf, PAGE_SIZE, "#eth_addr dev_addr wss_ptr " + "tag state virt_addr\n"); + list_for_each_entry(entry, &eda->cache, list_node) { + result += scnprintf(buf + result, PAGE_SIZE - result, + "%02x:%02x:%02x:%02x:%02x:%02x %02x:%02x " + "%p 0x%02x %s " + "%02x:%02x:%02x:%02x:%02x:%02x\n", + entry->eth_addr[0], entry->eth_addr[1], + entry->eth_addr[2], entry->eth_addr[3], + entry->eth_addr[4], entry->eth_addr[5], + entry->dev_addr.data[1], + entry->dev_addr.data[0], entry->wss, + entry->tag, + wlp_wss_connect_state_str(entry->state), + entry->virt_addr[0], entry->virt_addr[1], + entry->virt_addr[2], entry->virt_addr[3], + entry->virt_addr[4], entry->virt_addr[5]); + if (result >= PAGE_SIZE) + break; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} +EXPORT_SYMBOL_GPL(wlp_eda_show); + +/* + * Add new EDA cache entry based on user input in sysfs + * + * Should only be used for debugging. + * + * The WSS is assumed to be the only WSS supported. This needs to be + * redesigned when we support more than one WSS. + */ +ssize_t wlp_eda_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + struct wlp_eda *eda = &wlp->eda; + u8 eth_addr[6]; + struct uwb_dev_addr dev_addr; + u8 tag; + unsigned state; + + result = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx " + "%02hhx:%02hhx %02hhx %u\n", + ð_addr[0], ð_addr[1], + ð_addr[2], ð_addr[3], + ð_addr[4], ð_addr[5], + &dev_addr.data[1], &dev_addr.data[0], &tag, &state); + switch (result) { + case 6: /* no dev addr specified -- remove entry NOT IMPLEMENTED */ + /*result = wlp_eda_rm(eda, eth_addr, &dev_addr);*/ + result = -ENOSYS; + break; + case 10: + state = state >= 1 ? 1 : 0; + result = wlp_eda_create_node(eda, eth_addr, &dev_addr); + if (result < 0 && result != -EEXIST) + goto error; + /* Set virtual addr to be same as MAC */ + result = wlp_eda_update_node(eda, &dev_addr, &wlp->wss, + eth_addr, tag, state); + if (result < 0) + goto error; + break; + default: /* bad format */ + result = -EINVAL; + } +error: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_eda_store); diff --git a/drivers/uwb/wlp/messages.c b/drivers/uwb/wlp/messages.c new file mode 100644 index 00000000000..a64cb824171 --- /dev/null +++ b/drivers/uwb/wlp/messages.c @@ -0,0 +1,1946 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * Message construction and parsing + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include <linux/wlp.h> +#define D_LOCAL 6 +#include <linux/uwb/debug.h> +#include "wlp-internal.h" + +static +const char *__wlp_assoc_frame[] = { + [WLP_ASSOC_D1] = "WLP_ASSOC_D1", + [WLP_ASSOC_D2] = "WLP_ASSOC_D2", + [WLP_ASSOC_M1] = "WLP_ASSOC_M1", + [WLP_ASSOC_M2] = "WLP_ASSOC_M2", + [WLP_ASSOC_M3] = "WLP_ASSOC_M3", + [WLP_ASSOC_M4] = "WLP_ASSOC_M4", + [WLP_ASSOC_M5] = "WLP_ASSOC_M5", + [WLP_ASSOC_M6] = "WLP_ASSOC_M6", + [WLP_ASSOC_M7] = "WLP_ASSOC_M7", + [WLP_ASSOC_M8] = "WLP_ASSOC_M8", + [WLP_ASSOC_F0] = "WLP_ASSOC_F0", + [WLP_ASSOC_E1] = "WLP_ASSOC_E1", + [WLP_ASSOC_E2] = "WLP_ASSOC_E2", + [WLP_ASSOC_C1] = "WLP_ASSOC_C1", + [WLP_ASSOC_C2] = "WLP_ASSOC_C2", + [WLP_ASSOC_C3] = "WLP_ASSOC_C3", + [WLP_ASSOC_C4] = "WLP_ASSOC_C4", +}; + +static const char *wlp_assoc_frame_str(unsigned id) +{ + if (id >= ARRAY_SIZE(__wlp_assoc_frame)) + return "unknown association frame"; + return __wlp_assoc_frame[id]; +} + +static const char *__wlp_assc_error[] = { + "none", + "Authenticator Failure", + "Rogue activity suspected", + "Device busy", + "Setup Locked", + "Registrar not ready", + "Invalid WSS selection", + "Message timeout", + "Enrollment session timeout", + "Device password invalid", + "Unsupported version", + "Internal error", + "Undefined error", + "Numeric comparison failure", + "Waiting for user input", +}; + +static const char *wlp_assc_error_str(unsigned id) +{ + if (id >= ARRAY_SIZE(__wlp_assc_error)) + return "unknown WLP association error"; + return __wlp_assc_error[id]; +} + +static inline void wlp_set_attr_hdr(struct wlp_attr_hdr *hdr, unsigned type, + size_t len) +{ + hdr->type = cpu_to_le16(type); + hdr->length = cpu_to_le16(len); +} + +/* + * Populate fields of a constant sized attribute + * + * @returns: total size of attribute including size of new value + * + * We have two instances of this function (wlp_pset and wlp_set): one takes + * the value as a parameter, the other takes a pointer to the value as + * parameter. They thus only differ in how the value is assigned to the + * attribute. + * + * We use sizeof(*attr) - sizeof(struct wlp_attr_hdr) instead of + * sizeof(type) to be able to use this same code for the structures that + * contain 8bit enum values and be able to deal with pointer types. + */ +#define wlp_set(type, type_code, name) \ +static size_t wlp_set_##name(struct wlp_attr_##name *attr, type value) \ +{ \ + d_fnstart(6, NULL, "(attribute %p)\n", attr); \ + wlp_set_attr_hdr(&attr->hdr, type_code, \ + sizeof(*attr) - sizeof(struct wlp_attr_hdr)); \ + attr->name = value; \ + d_dump(6, NULL, attr, sizeof(*attr)); \ + d_fnend(6, NULL, "(attribute %p)\n", attr); \ + return sizeof(*attr); \ +} + +#define wlp_pset(type, type_code, name) \ +static size_t wlp_set_##name(struct wlp_attr_##name *attr, type value) \ +{ \ + d_fnstart(6, NULL, "(attribute %p)\n", attr); \ + wlp_set_attr_hdr(&attr->hdr, type_code, \ + sizeof(*attr) - sizeof(struct wlp_attr_hdr)); \ + attr->name = *value; \ + d_dump(6, NULL, attr, sizeof(*attr)); \ + d_fnend(6, NULL, "(attribute %p)\n", attr); \ + return sizeof(*attr); \ +} + +/** + * Populate fields of a variable attribute + * + * @returns: total size of attribute including size of new value + * + * Provided with a pointer to the memory area reserved for the + * attribute structure, the field is populated with the value. The + * reserved memory has to contain enough space for the value. + */ +#define wlp_vset(type, type_code, name) \ +static size_t wlp_set_##name(struct wlp_attr_##name *attr, type value, \ + size_t len) \ +{ \ + d_fnstart(6, NULL, "(attribute %p)\n", attr); \ + wlp_set_attr_hdr(&attr->hdr, type_code, len); \ + memcpy(attr->name, value, len); \ + d_dump(6, NULL, attr, sizeof(*attr) + len); \ + d_fnend(6, NULL, "(attribute %p)\n", attr); \ + return sizeof(*attr) + len; \ +} + +wlp_vset(char *, WLP_ATTR_DEV_NAME, dev_name) +wlp_vset(char *, WLP_ATTR_MANUF, manufacturer) +wlp_set(enum wlp_assoc_type, WLP_ATTR_MSG_TYPE, msg_type) +wlp_vset(char *, WLP_ATTR_MODEL_NAME, model_name) +wlp_vset(char *, WLP_ATTR_MODEL_NR, model_nr) +wlp_vset(char *, WLP_ATTR_SERIAL, serial) +wlp_vset(char *, WLP_ATTR_WSS_NAME, wss_name) +wlp_pset(struct wlp_uuid *, WLP_ATTR_UUID_E, uuid_e) +wlp_pset(struct wlp_uuid *, WLP_ATTR_UUID_R, uuid_r) +wlp_pset(struct wlp_uuid *, WLP_ATTR_WSSID, wssid) +wlp_pset(struct wlp_dev_type *, WLP_ATTR_PRI_DEV_TYPE, prim_dev_type) +/*wlp_pset(struct wlp_dev_type *, WLP_ATTR_SEC_DEV_TYPE, sec_dev_type)*/ +wlp_set(u8, WLP_ATTR_WLP_VER, version) +wlp_set(enum wlp_assc_error, WLP_ATTR_WLP_ASSC_ERR, wlp_assc_err) +wlp_set(enum wlp_wss_sel_mthd, WLP_ATTR_WSS_SEL_MTHD, wss_sel_mthd) +wlp_set(u8, WLP_ATTR_ACC_ENRL, accept_enrl) +wlp_set(u8, WLP_ATTR_WSS_SEC_STAT, wss_sec_status) +wlp_pset(struct uwb_mac_addr *, WLP_ATTR_WSS_BCAST, wss_bcast) +wlp_pset(struct wlp_nonce *, WLP_ATTR_ENRL_NONCE, enonce) +wlp_pset(struct wlp_nonce *, WLP_ATTR_REG_NONCE, rnonce) +wlp_set(u8, WLP_ATTR_WSS_TAG, wss_tag) +wlp_pset(struct uwb_mac_addr *, WLP_ATTR_WSS_VIRT, wss_virt) + +/** + * Fill in the WSS information attributes + * + * We currently only support one WSS, and this is assumed in this function + * that can populate only one WSS information attribute. + */ +static size_t wlp_set_wss_info(struct wlp_attr_wss_info *attr, + struct wlp_wss *wss) +{ + size_t datalen; + void *ptr = attr->wss_info; + size_t used = sizeof(*attr); + d_fnstart(6, NULL, "(attribute %p)\n", attr); + datalen = sizeof(struct wlp_wss_info) + strlen(wss->name); + wlp_set_attr_hdr(&attr->hdr, WLP_ATTR_WSS_INFO, datalen); + used = wlp_set_wssid(ptr, &wss->wssid); + used += wlp_set_wss_name(ptr + used, wss->name, strlen(wss->name)); + used += wlp_set_accept_enrl(ptr + used, wss->accept_enroll); + used += wlp_set_wss_sec_status(ptr + used, wss->secure_status); + used += wlp_set_wss_bcast(ptr + used, &wss->bcast); + d_dump(6, NULL, attr, sizeof(*attr) + datalen); + d_fnend(6, NULL, "(attribute %p, used %d)\n", + attr, (int)(sizeof(*attr) + used)); + return sizeof(*attr) + used; +} + +/** + * Verify attribute header + * + * @hdr: Pointer to attribute header that will be verified. + * @type: Expected attribute type. + * @len: Expected length of attribute value (excluding header). + * + * Most attribute values have a known length even when they do have a + * length field. This knowledge can be used via this function to verify + * that the length field matches the expected value. + */ +static int wlp_check_attr_hdr(struct wlp *wlp, struct wlp_attr_hdr *hdr, + enum wlp_attr_type type, unsigned len) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + + if (le16_to_cpu(hdr->type) != type) { + dev_err(dev, "WLP: unexpected header type. Expected " + "%u, got %u.\n", type, le16_to_cpu(hdr->type)); + return -EINVAL; + } + if (le16_to_cpu(hdr->length) != len) { + dev_err(dev, "WLP: unexpected length in header. Expected " + "%u, got %u.\n", len, le16_to_cpu(hdr->length)); + return -EINVAL; + } + return 0; +} + +/** + * Check if header of WSS information attribute valid + * + * @returns: length of WSS attributes (value of length attribute field) if + * valid WSS information attribute found + * -ENODATA if no WSS information attribute found + * -EIO other error occured + * + * The WSS information attribute is optional. The function will be provided + * with a pointer to data that could _potentially_ be a WSS information + * attribute. If a valid WSS information attribute is found it will return + * 0, if no WSS information attribute is found it will return -ENODATA, and + * another error will be returned if it is a WSS information attribute, but + * some parsing failure occured. + */ +static int wlp_check_wss_info_attr_hdr(struct wlp *wlp, + struct wlp_attr_hdr *hdr, size_t buflen) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + size_t len; + int result = 0; + + if (buflen < sizeof(*hdr)) { + dev_err(dev, "WLP: Not enough space in buffer to parse" + " WSS information attribute header.\n"); + result = -EIO; + goto out; + } + if (le16_to_cpu(hdr->type) != WLP_ATTR_WSS_INFO) { + /* WSS information is optional */ + result = -ENODATA; + goto out; + } + len = le16_to_cpu(hdr->length); + if (buflen < sizeof(*hdr) + len) { + dev_err(dev, "WLP: Not enough space in buffer to parse " + "variable data. Got %d, expected %d.\n", + (int)buflen, (int)(sizeof(*hdr) + len)); + result = -EIO; + goto out; + } + result = len; +out: + return result; +} + + +/** + * Get value of attribute from fixed size attribute field. + * + * @attr: Pointer to attribute field. + * @value: Pointer to variable in which attribute value will be placed. + * @buflen: Size of buffer in which attribute field (including header) + * can be found. + * @returns: Amount of given buffer consumed by parsing for this attribute. + * + * The size and type of the value is known by the type of the attribute. + */ +#define wlp_get(type, type_code, name) \ +ssize_t wlp_get_##name(struct wlp *wlp, struct wlp_attr_##name *attr, \ + type *value, ssize_t buflen) \ +{ \ + struct device *dev = &wlp->rc->uwb_dev.dev; \ + if (buflen < 0) \ + return -EINVAL; \ + if (buflen < sizeof(*attr)) { \ + dev_err(dev, "WLP: Not enough space in buffer to parse" \ + " attribute field. Need %d, received %zu\n", \ + (int)sizeof(*attr), buflen); \ + return -EIO; \ + } \ + if (wlp_check_attr_hdr(wlp, &attr->hdr, type_code, \ + sizeof(attr->name)) < 0) { \ + dev_err(dev, "WLP: Header verification failed. \n"); \ + return -EINVAL; \ + } \ + *value = attr->name; \ + return sizeof(*attr); \ +} + +#define wlp_get_sparse(type, type_code, name) \ + static wlp_get(type, type_code, name) + +/** + * Get value of attribute from variable sized attribute field. + * + * @max: The maximum size of this attribute. This value is dictated by + * the maximum value from the WLP specification. + * + * @attr: Pointer to attribute field. + * @value: Pointer to variable that will contain the value. The memory + * must already have been allocated for this value. + * @buflen: Size of buffer in which attribute field (including header) + * can be found. + * @returns: Amount of given bufferconsumed by parsing for this attribute. + */ +#define wlp_vget(type_val, type_code, name, max) \ +static ssize_t wlp_get_##name(struct wlp *wlp, \ + struct wlp_attr_##name *attr, \ + type_val *value, ssize_t buflen) \ +{ \ + struct device *dev = &wlp->rc->uwb_dev.dev; \ + size_t len; \ + if (buflen < 0) \ + return -EINVAL; \ + if (buflen < sizeof(*attr)) { \ + dev_err(dev, "WLP: Not enough space in buffer to parse" \ + " header.\n"); \ + return -EIO; \ + } \ + if (le16_to_cpu(attr->hdr.type) != type_code) { \ + dev_err(dev, "WLP: Unexpected attribute type. Got %u, " \ + "expected %u.\n", le16_to_cpu(attr->hdr.type), \ + type_code); \ + return -EINVAL; \ + } \ + len = le16_to_cpu(attr->hdr.length); \ + if (len > max) { \ + dev_err(dev, "WLP: Attribute larger than maximum " \ + "allowed. Received %zu, max is %d.\n", len, \ + (int)max); \ + return -EFBIG; \ + } \ + if (buflen < sizeof(*attr) + len) { \ + dev_err(dev, "WLP: Not enough space in buffer to parse "\ + "variable data.\n"); \ + return -EIO; \ + } \ + memcpy(value, (void *) attr + sizeof(*attr), len); \ + return sizeof(*attr) + len; \ +} + +wlp_get(u8, WLP_ATTR_WLP_VER, version) +wlp_get_sparse(enum wlp_wss_sel_mthd, WLP_ATTR_WSS_SEL_MTHD, wss_sel_mthd) +wlp_get_sparse(struct wlp_dev_type, WLP_ATTR_PRI_DEV_TYPE, prim_dev_type) +wlp_get_sparse(enum wlp_assc_error, WLP_ATTR_WLP_ASSC_ERR, wlp_assc_err) +wlp_get_sparse(struct wlp_uuid, WLP_ATTR_UUID_E, uuid_e) +wlp_get_sparse(struct wlp_uuid, WLP_ATTR_UUID_R, uuid_r) +wlp_get(struct wlp_uuid, WLP_ATTR_WSSID, wssid) +wlp_get_sparse(u8, WLP_ATTR_ACC_ENRL, accept_enrl) +wlp_get_sparse(u8, WLP_ATTR_WSS_SEC_STAT, wss_sec_status) +wlp_get_sparse(struct uwb_mac_addr, WLP_ATTR_WSS_BCAST, wss_bcast) +wlp_get_sparse(u8, WLP_ATTR_WSS_TAG, wss_tag) +wlp_get_sparse(struct uwb_mac_addr, WLP_ATTR_WSS_VIRT, wss_virt) +wlp_get_sparse(struct wlp_nonce, WLP_ATTR_ENRL_NONCE, enonce) +wlp_get_sparse(struct wlp_nonce, WLP_ATTR_REG_NONCE, rnonce) + +/* The buffers for the device info attributes can be found in the + * wlp_device_info struct. These buffers contain one byte more than the + * max allowed by the spec - this is done to be able to add the + * terminating \0 for user display. This terminating byte is not required + * in the actual attribute field (because it has a length field) so the + * maximum allowed for this value is one less than its size in the + * structure. + */ +wlp_vget(char, WLP_ATTR_WSS_NAME, wss_name, + FIELD_SIZEOF(struct wlp_wss, name) - 1) +wlp_vget(char, WLP_ATTR_DEV_NAME, dev_name, + FIELD_SIZEOF(struct wlp_device_info, name) - 1) +wlp_vget(char, WLP_ATTR_MANUF, manufacturer, + FIELD_SIZEOF(struct wlp_device_info, manufacturer) - 1) +wlp_vget(char, WLP_ATTR_MODEL_NAME, model_name, + FIELD_SIZEOF(struct wlp_device_info, model_name) - 1) +wlp_vget(char, WLP_ATTR_MODEL_NR, model_nr, + FIELD_SIZEOF(struct wlp_device_info, model_nr) - 1) +wlp_vget(char, WLP_ATTR_SERIAL, serial, + FIELD_SIZEOF(struct wlp_device_info, serial) - 1) + +/** + * Retrieve WSS Name, Accept enroll, Secure status, Broadcast from WSS info + * + * @attr: pointer to WSS name attribute in WSS information attribute field + * @info: structure that will be populated with data from WSS information + * field (WSS name, Accept enroll, secure status, broadcast address) + * @buflen: size of buffer + * + * Although the WSSID attribute forms part of the WSS info attribute it is + * retrieved separately and stored in a different location. + */ +static ssize_t wlp_get_wss_info_attrs(struct wlp *wlp, + struct wlp_attr_hdr *attr, + struct wlp_wss_tmp_info *info, + ssize_t buflen) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + void *ptr = attr; + size_t used = 0; + ssize_t result = -EINVAL; + + d_printf(6, dev, "WLP: WSS info: Retrieving WSS name\n"); + result = wlp_get_wss_name(wlp, ptr, info->name, buflen); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS name from " + "WSS info in D2 message.\n"); + goto error_parse; + } + used += result; + d_printf(6, dev, "WLP: WSS info: Retrieving accept enroll\n"); + result = wlp_get_accept_enrl(wlp, ptr + used, &info->accept_enroll, + buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain accepting " + "enrollment from WSS info in D2 message.\n"); + goto error_parse; + } + if (info->accept_enroll != 0 && info->accept_enroll != 1) { + dev_err(dev, "WLP: invalid value for accepting " + "enrollment in D2 message.\n"); + result = -EINVAL; + goto error_parse; + } + used += result; + d_printf(6, dev, "WLP: WSS info: Retrieving secure status\n"); + result = wlp_get_wss_sec_status(wlp, ptr + used, &info->sec_status, + buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain secure " + "status from WSS info in D2 message.\n"); + goto error_parse; + } + if (info->sec_status != 0 && info->sec_status != 1) { + dev_err(dev, "WLP: invalid value for secure " + "status in D2 message.\n"); + result = -EINVAL; + goto error_parse; + } + used += result; + d_printf(6, dev, "WLP: WSS info: Retrieving broadcast\n"); + result = wlp_get_wss_bcast(wlp, ptr + used, &info->bcast, + buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain broadcast " + "address from WSS info in D2 message.\n"); + goto error_parse; + } + used += result; + result = used; +error_parse: + return result; +} + +/** + * Create a new WSSID entry for the neighbor, allocate temporary storage + * + * Each neighbor can have many WSS active. We maintain a list of WSSIDs + * advertised by neighbor. During discovery we also cache information about + * these WSS in temporary storage. + * + * The temporary storage will be removed after it has been used (eg. + * displayed to user), the wssid element will be removed from the list when + * the neighbor is rediscovered or when it disappears. + */ +static struct wlp_wssid_e *wlp_create_wssid_e(struct wlp *wlp, + struct wlp_neighbor_e *neighbor) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_wssid_e *wssid_e; + + wssid_e = kzalloc(sizeof(*wssid_e), GFP_KERNEL); + if (wssid_e == NULL) { + dev_err(dev, "WLP: unable to allocate memory " + "for WSS information.\n"); + goto error_alloc; + } + wssid_e->info = kzalloc(sizeof(struct wlp_wss_tmp_info), GFP_KERNEL); + if (wssid_e->info == NULL) { + dev_err(dev, "WLP: unable to allocate memory " + "for temporary WSS information.\n"); + kfree(wssid_e); + wssid_e = NULL; + goto error_alloc; + } + list_add(&wssid_e->node, &neighbor->wssid); +error_alloc: + return wssid_e; +} + +/** + * Parse WSS information attribute + * + * @attr: pointer to WSS information attribute header + * @buflen: size of buffer in which WSS information attribute appears + * @wssid: will place wssid from WSS info attribute in this location + * @wss_info: will place other information from WSS information attribute + * in this location + * + * memory for @wssid and @wss_info must be allocated when calling this + */ +static ssize_t wlp_get_wss_info(struct wlp *wlp, struct wlp_attr_wss_info *attr, + size_t buflen, struct wlp_uuid *wssid, + struct wlp_wss_tmp_info *wss_info) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + ssize_t result; + size_t len; + size_t used = 0; + void *ptr; + + result = wlp_check_wss_info_attr_hdr(wlp, (struct wlp_attr_hdr *)attr, + buflen); + if (result < 0) + goto out; + len = result; + used = sizeof(*attr); + ptr = attr; + d_printf(6, dev, "WLP: WSS info: Retrieving WSSID\n"); + result = wlp_get_wssid(wlp, ptr + used, wssid, buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSSID from WSS info.\n"); + goto out; + } + used += result; + result = wlp_get_wss_info_attrs(wlp, ptr + used, wss_info, + buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS information " + "from WSS information attributes. \n"); + goto out; + } + used += result; + if (len + sizeof(*attr) != used) { + dev_err(dev, "WLP: Amount of data parsed does not " + "match length field. Parsed %zu, length " + "field %zu. \n", used, len); + result = -EINVAL; + goto out; + } + result = used; + d_printf(6, dev, "WLP: Successfully parsed WLP information " + "attribute. used %zu bytes\n", used); +out: + return result; +} + +/** + * Retrieve WSS info from association frame + * + * @attr: pointer to WSS information attribute + * @neighbor: ptr to neighbor being discovered, NULL if enrollment in + * progress + * @wss: ptr to WSS being enrolled in, NULL if discovery in progress + * @buflen: size of buffer in which WSS information appears + * + * The WSS information attribute appears in the D2 association message. + * This message is used in two ways: to discover all neighbors or to enroll + * into a WSS activated by a neighbor. During discovery we only want to + * store the WSS info in a cache, to be deleted right after it has been + * used (eg. displayed to the user). During enrollment we store the WSS + * information for the lifetime of enrollment. + * + * During discovery we are interested in all WSS information, during + * enrollment we are only interested in the WSS being enrolled in. Even so, + * when in enrollment we keep parsing the message after finding the WSS of + * interest, this simplifies the calling routine in that it can be sure + * that all WSS information attributes have been parsed out of the message. + * + * Association frame is process with nbmutex held. The list access is safe. + */ +static ssize_t wlp_get_all_wss_info(struct wlp *wlp, + struct wlp_attr_wss_info *attr, + struct wlp_neighbor_e *neighbor, + struct wlp_wss *wss, ssize_t buflen) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + size_t used = 0; + ssize_t result = -EINVAL; + struct wlp_attr_wss_info *cur; + struct wlp_uuid wssid; + struct wlp_wss_tmp_info wss_info; + unsigned enroll; /* 0 - discovery to cache, 1 - enrollment */ + struct wlp_wssid_e *wssid_e; + char buf[WLP_WSS_UUID_STRSIZE]; + + d_fnstart(6, dev, "wlp %p, attr %p, neighbor %p, wss %p, buflen %d \n", + wlp, attr, neighbor, wss, (int)buflen); + if (buflen < 0) + goto out; + + if (neighbor != NULL && wss == NULL) + enroll = 0; /* discovery */ + else if (wss != NULL && neighbor == NULL) + enroll = 1; /* enrollment */ + else + goto out; + + cur = attr; + while (buflen - used > 0) { + memset(&wss_info, 0, sizeof(wss_info)); + cur = (void *)cur + used; + result = wlp_get_wss_info(wlp, cur, buflen - used, &wssid, + &wss_info); + if (result == -ENODATA) { + result = used; + goto out; + } else if (result < 0) { + dev_err(dev, "WLP: Unable to parse WSS information " + "from WSS information attribute. \n"); + result = -EINVAL; + goto error_parse; + } + if (enroll && !memcmp(&wssid, &wss->wssid, sizeof(wssid))) { + if (wss_info.accept_enroll != 1) { + dev_err(dev, "WLP: Requested WSS does " + "not accept enrollment.\n"); + result = -EINVAL; + goto out; + } + memcpy(wss->name, wss_info.name, sizeof(wss->name)); + wss->bcast = wss_info.bcast; + wss->secure_status = wss_info.sec_status; + wss->accept_enroll = wss_info.accept_enroll; + wss->state = WLP_WSS_STATE_PART_ENROLLED; + wlp_wss_uuid_print(buf, sizeof(buf), &wssid); + d_printf(2, dev, "WLP: Found WSS %s. Enrolling.\n", + buf); + } else { + wssid_e = wlp_create_wssid_e(wlp, neighbor); + if (wssid_e == NULL) { + dev_err(dev, "WLP: Cannot create new WSSID " + "entry for neighbor %02x:%02x.\n", + neighbor->uwb_dev->dev_addr.data[1], + neighbor->uwb_dev->dev_addr.data[0]); + result = -ENOMEM; + goto out; + } + wssid_e->wssid = wssid; + *wssid_e->info = wss_info; + } + used += result; + } + result = used; +error_parse: + if (result < 0 && !enroll) /* this was a discovery */ + wlp_remove_neighbor_tmp_info(neighbor); +out: + d_fnend(6, dev, "wlp %p, attr %p, neighbor %p, wss %p, buflen %d, " + "result %d \n", wlp, attr, neighbor, wss, (int)buflen, + (int)result); + return result; + +} + +/** + * Parse WSS information attributes into cache for discovery + * + * @attr: the first WSS information attribute in message + * @neighbor: the neighbor whose cache will be populated + * @buflen: size of the input buffer + */ +static ssize_t wlp_get_wss_info_to_cache(struct wlp *wlp, + struct wlp_attr_wss_info *attr, + struct wlp_neighbor_e *neighbor, + ssize_t buflen) +{ + return wlp_get_all_wss_info(wlp, attr, neighbor, NULL, buflen); +} + +/** + * Parse WSS information attributes into WSS struct for enrollment + * + * @attr: the first WSS information attribute in message + * @wss: the WSS that will be enrolled + * @buflen: size of the input buffer + */ +static ssize_t wlp_get_wss_info_to_enroll(struct wlp *wlp, + struct wlp_attr_wss_info *attr, + struct wlp_wss *wss, ssize_t buflen) +{ + return wlp_get_all_wss_info(wlp, attr, NULL, wss, buflen); +} + +/** + * Construct a D1 association frame + * + * We use the radio control functions to determine the values of the device + * properties. These are of variable length and the total space needed is + * tallied first before we start constructing the message. The radio + * control functions return strings that are terminated with \0. This + * character should not be included in the message (there is a length field + * accompanying it in the attribute). + */ +static int wlp_build_assoc_d1(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + struct wlp_device_info *info; + size_t used = 0; + struct wlp_frame_assoc *_d1; + struct sk_buff *_skb; + void *d1_itr; + + d_fnstart(6, dev, "wlp %p\n", wlp); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) { + dev_err(dev, "WLP: Unable to setup device " + "information for D1 message.\n"); + goto error; + } + } + info = wlp->dev_info; + d_printf(6, dev, "Local properties:\n" + "Device name (%d bytes): %s\n" + "Model name (%d bytes): %s\n" + "Manufacturer (%d bytes): %s\n" + "Model number (%d bytes): %s\n" + "Serial number (%d bytes): %s\n" + "Primary device type: \n" + " Category: %d \n" + " OUI: %02x:%02x:%02x \n" + " OUI Subdivision: %u \n", + (int)strlen(info->name), info->name, + (int)strlen(info->model_name), info->model_name, + (int)strlen(info->manufacturer), info->manufacturer, + (int)strlen(info->model_nr), info->model_nr, + (int)strlen(info->serial), info->serial, + info->prim_dev_type.category, + info->prim_dev_type.OUI[0], info->prim_dev_type.OUI[1], + info->prim_dev_type.OUI[2], info->prim_dev_type.OUIsubdiv); + _skb = dev_alloc_skb(sizeof(*_d1) + + sizeof(struct wlp_attr_uuid_e) + + sizeof(struct wlp_attr_wss_sel_mthd) + + sizeof(struct wlp_attr_dev_name) + + strlen(info->name) + + sizeof(struct wlp_attr_manufacturer) + + strlen(info->manufacturer) + + sizeof(struct wlp_attr_model_name) + + strlen(info->model_name) + + sizeof(struct wlp_attr_model_nr) + + strlen(info->model_nr) + + sizeof(struct wlp_attr_serial) + + strlen(info->serial) + + sizeof(struct wlp_attr_prim_dev_type) + + sizeof(struct wlp_attr_wlp_assc_err)); + if (_skb == NULL) { + dev_err(dev, "WLP: Cannot allocate memory for association " + "message.\n"); + result = -ENOMEM; + goto error; + } + _d1 = (void *) _skb->data; + d_printf(6, dev, "D1 starts at %p \n", _d1); + _d1->hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + _d1->hdr.type = WLP_FRAME_ASSOCIATION; + _d1->type = WLP_ASSOC_D1; + + wlp_set_version(&_d1->version, WLP_VERSION); + wlp_set_msg_type(&_d1->msg_type, WLP_ASSOC_D1); + d1_itr = _d1->attr; + used = wlp_set_uuid_e(d1_itr, &wlp->uuid); + used += wlp_set_wss_sel_mthd(d1_itr + used, WLP_WSS_REG_SELECT); + used += wlp_set_dev_name(d1_itr + used, info->name, + strlen(info->name)); + used += wlp_set_manufacturer(d1_itr + used, info->manufacturer, + strlen(info->manufacturer)); + used += wlp_set_model_name(d1_itr + used, info->model_name, + strlen(info->model_name)); + used += wlp_set_model_nr(d1_itr + used, info->model_nr, + strlen(info->model_nr)); + used += wlp_set_serial(d1_itr + used, info->serial, + strlen(info->serial)); + used += wlp_set_prim_dev_type(d1_itr + used, &info->prim_dev_type); + used += wlp_set_wlp_assc_err(d1_itr + used, WLP_ASSOC_ERROR_NONE); + skb_put(_skb, sizeof(*_d1) + used); + d_printf(6, dev, "D1 message:\n"); + d_dump(6, dev, _d1, sizeof(*_d1) + + sizeof(struct wlp_attr_uuid_e) + + sizeof(struct wlp_attr_wss_sel_mthd) + + sizeof(struct wlp_attr_dev_name) + + strlen(info->name) + + sizeof(struct wlp_attr_manufacturer) + + strlen(info->manufacturer) + + sizeof(struct wlp_attr_model_name) + + strlen(info->model_name) + + sizeof(struct wlp_attr_model_nr) + + strlen(info->model_nr) + + sizeof(struct wlp_attr_serial) + + strlen(info->serial) + + sizeof(struct wlp_attr_prim_dev_type) + + sizeof(struct wlp_attr_wlp_assc_err)); + *skb = _skb; +error: + d_fnend(6, dev, "wlp %p, result = %d\n", wlp, result); + return result; +} + +/** + * Construct a D2 association frame + * + * We use the radio control functions to determine the values of the device + * properties. These are of variable length and the total space needed is + * tallied first before we start constructing the message. The radio + * control functions return strings that are terminated with \0. This + * character should not be included in the message (there is a length field + * accompanying it in the attribute). + */ +static +int wlp_build_assoc_d2(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb, struct wlp_uuid *uuid_e) +{ + + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + struct wlp_device_info *info; + size_t used = 0; + struct wlp_frame_assoc *_d2; + struct sk_buff *_skb; + void *d2_itr; + size_t mem_needed; + + d_fnstart(6, dev, "wlp %p\n", wlp); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) { + dev_err(dev, "WLP: Unable to setup device " + "information for D2 message.\n"); + goto error; + } + } + info = wlp->dev_info; + d_printf(6, dev, "Local properties:\n" + "Device name (%d bytes): %s\n" + "Model name (%d bytes): %s\n" + "Manufacturer (%d bytes): %s\n" + "Model number (%d bytes): %s\n" + "Serial number (%d bytes): %s\n" + "Primary device type: \n" + " Category: %d \n" + " OUI: %02x:%02x:%02x \n" + " OUI Subdivision: %u \n", + (int)strlen(info->name), info->name, + (int)strlen(info->model_name), info->model_name, + (int)strlen(info->manufacturer), info->manufacturer, + (int)strlen(info->model_nr), info->model_nr, + (int)strlen(info->serial), info->serial, + info->prim_dev_type.category, + info->prim_dev_type.OUI[0], info->prim_dev_type.OUI[1], + info->prim_dev_type.OUI[2], info->prim_dev_type.OUIsubdiv); + mem_needed = sizeof(*_d2) + + sizeof(struct wlp_attr_uuid_e) + + sizeof(struct wlp_attr_uuid_r) + + sizeof(struct wlp_attr_dev_name) + + strlen(info->name) + + sizeof(struct wlp_attr_manufacturer) + + strlen(info->manufacturer) + + sizeof(struct wlp_attr_model_name) + + strlen(info->model_name) + + sizeof(struct wlp_attr_model_nr) + + strlen(info->model_nr) + + sizeof(struct wlp_attr_serial) + + strlen(info->serial) + + sizeof(struct wlp_attr_prim_dev_type) + + sizeof(struct wlp_attr_wlp_assc_err); + if (wlp->wss.state >= WLP_WSS_STATE_ACTIVE) + mem_needed += sizeof(struct wlp_attr_wss_info) + + sizeof(struct wlp_wss_info) + + strlen(wlp->wss.name); + _skb = dev_alloc_skb(mem_needed); + if (_skb == NULL) { + dev_err(dev, "WLP: Cannot allocate memory for association " + "message.\n"); + result = -ENOMEM; + goto error; + } + _d2 = (void *) _skb->data; + d_printf(6, dev, "D2 starts at %p \n", _d2); + _d2->hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + _d2->hdr.type = WLP_FRAME_ASSOCIATION; + _d2->type = WLP_ASSOC_D2; + + wlp_set_version(&_d2->version, WLP_VERSION); + wlp_set_msg_type(&_d2->msg_type, WLP_ASSOC_D2); + d2_itr = _d2->attr; + used = wlp_set_uuid_e(d2_itr, uuid_e); + used += wlp_set_uuid_r(d2_itr + used, &wlp->uuid); + if (wlp->wss.state >= WLP_WSS_STATE_ACTIVE) + used += wlp_set_wss_info(d2_itr + used, &wlp->wss); + used += wlp_set_dev_name(d2_itr + used, info->name, + strlen(info->name)); + used += wlp_set_manufacturer(d2_itr + used, info->manufacturer, + strlen(info->manufacturer)); + used += wlp_set_model_name(d2_itr + used, info->model_name, + strlen(info->model_name)); + used += wlp_set_model_nr(d2_itr + used, info->model_nr, + strlen(info->model_nr)); + used += wlp_set_serial(d2_itr + used, info->serial, + strlen(info->serial)); + used += wlp_set_prim_dev_type(d2_itr + used, &info->prim_dev_type); + used += wlp_set_wlp_assc_err(d2_itr + used, WLP_ASSOC_ERROR_NONE); + skb_put(_skb, sizeof(*_d2) + used); + d_printf(6, dev, "D2 message:\n"); + d_dump(6, dev, _d2, mem_needed); + *skb = _skb; +error: + d_fnend(6, dev, "wlp %p, result = %d\n", wlp, result); + return result; +} + +/** + * Allocate memory for and populate fields of F0 association frame + * + * Currently (while focusing on unsecure enrollment) we ignore the + * nonce's that could be placed in the message. Only the error field is + * populated by the value provided by the caller. + */ +static +int wlp_build_assoc_f0(struct wlp *wlp, struct sk_buff **skb, + enum wlp_assc_error error) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = -ENOMEM; + struct { + struct wlp_frame_assoc f0_hdr; + struct wlp_attr_enonce enonce; + struct wlp_attr_rnonce rnonce; + struct wlp_attr_wlp_assc_err assc_err; + } *f0; + struct sk_buff *_skb; + struct wlp_nonce tmp; + + d_fnstart(6, dev, "wlp %p\n", wlp); + _skb = dev_alloc_skb(sizeof(*f0)); + if (_skb == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for F0 " + "association frame. \n"); + goto error_alloc; + } + f0 = (void *) _skb->data; + d_printf(6, dev, "F0 starts at %p \n", f0); + f0->f0_hdr.hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + f0->f0_hdr.hdr.type = WLP_FRAME_ASSOCIATION; + f0->f0_hdr.type = WLP_ASSOC_F0; + wlp_set_version(&f0->f0_hdr.version, WLP_VERSION); + wlp_set_msg_type(&f0->f0_hdr.msg_type, WLP_ASSOC_F0); + memset(&tmp, 0, sizeof(tmp)); + wlp_set_enonce(&f0->enonce, &tmp); + wlp_set_rnonce(&f0->rnonce, &tmp); + wlp_set_wlp_assc_err(&f0->assc_err, error); + skb_put(_skb, sizeof(*f0)); + *skb = _skb; + result = 0; +error_alloc: + d_fnend(6, dev, "wlp %p, result %d \n", wlp, result); + return result; +} + +/** + * Parse F0 frame + * + * We just retrieve the values and print it as an error to the user. + * Calling function already knows an error occured (F0 indicates error), so + * we just parse the content as debug for higher layers. + */ +int wlp_parse_f0(struct wlp *wlp, struct sk_buff *skb) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *f0 = (void *) skb->data; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + ssize_t result; + struct wlp_nonce enonce, rnonce; + enum wlp_assc_error assc_err; + char enonce_buf[WLP_WSS_NONCE_STRSIZE]; + char rnonce_buf[WLP_WSS_NONCE_STRSIZE]; + + used = sizeof(*f0); + result = wlp_get_enonce(wlp, ptr + used, &enonce, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Enrollee nonce " + "attribute from F0 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_rnonce(wlp, ptr + used, &rnonce, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Registrar nonce " + "attribute from F0 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wlp_assc_err(wlp, ptr + used, &assc_err, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WLP Association error " + "attribute from F0 message.\n"); + goto error_parse; + } + wlp_wss_nonce_print(enonce_buf, sizeof(enonce_buf), &enonce); + wlp_wss_nonce_print(rnonce_buf, sizeof(rnonce_buf), &rnonce); + dev_err(dev, "WLP: Received F0 error frame from neighbor. Enrollee " + "nonce: %s, Registrar nonce: %s, WLP Association error: %s.\n", + enonce_buf, rnonce_buf, wlp_assc_error_str(assc_err)); + result = 0; +error_parse: + return result; +} + +/** + * Retrieve variable device information from association message + * + * The device information parsed is not required in any message. This + * routine will thus not fail if an attribute is not present. + * The attributes are expected in a certain order, even if all are not + * present. The "attribute type" value is used to ensure the attributes + * are parsed in the correct order. + * + * If an error is encountered during parsing the function will return an + * error code, when this happens the given device_info structure may be + * partially filled. + */ +static +int wlp_get_variable_info(struct wlp *wlp, void *data, + struct wlp_device_info *dev_info, ssize_t len) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + size_t used = 0; + struct wlp_attr_hdr *hdr; + ssize_t result = 0; + unsigned last = 0; + + while (len - used > 0) { + if (len - used < sizeof(*hdr)) { + dev_err(dev, "WLP: Partial data in frame, cannot " + "parse. \n"); + goto error_parse; + } + hdr = data + used; + switch (le16_to_cpu(hdr->type)) { + case WLP_ATTR_MANUF: + if (last >= WLP_ATTR_MANUF) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_manufacturer(wlp, data + used, + dev_info->manufacturer, + len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain " + "Manufacturer attribute from D1 " + "message.\n"); + goto error_parse; + } + last = WLP_ATTR_MANUF; + used += result; + break; + case WLP_ATTR_MODEL_NAME: + if (last >= WLP_ATTR_MODEL_NAME) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_model_name(wlp, data + used, + dev_info->model_name, + len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain Model " + "name attribute from D1 message.\n"); + goto error_parse; + } + last = WLP_ATTR_MODEL_NAME; + used += result; + break; + case WLP_ATTR_MODEL_NR: + if (last >= WLP_ATTR_MODEL_NR) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_model_nr(wlp, data + used, + dev_info->model_nr, + len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain Model " + "number attribute from D1 message.\n"); + goto error_parse; + } + last = WLP_ATTR_MODEL_NR; + used += result; + break; + case WLP_ATTR_SERIAL: + if (last >= WLP_ATTR_SERIAL) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_serial(wlp, data + used, + dev_info->serial, len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain Serial " + "number attribute from D1 message.\n"); + goto error_parse; + } + last = WLP_ATTR_SERIAL; + used += result; + break; + case WLP_ATTR_PRI_DEV_TYPE: + if (last >= WLP_ATTR_PRI_DEV_TYPE) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_prim_dev_type(wlp, data + used, + &dev_info->prim_dev_type, + len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain Primary " + "device type attribute from D1 " + "message.\n"); + goto error_parse; + } + dev_info->prim_dev_type.category = + le16_to_cpu(dev_info->prim_dev_type.category); + dev_info->prim_dev_type.subID = + le16_to_cpu(dev_info->prim_dev_type.subID); + last = WLP_ATTR_PRI_DEV_TYPE; + used += result; + break; + default: + /* This is not variable device information. */ + goto out; + break; + } + } +out: + return used; +error_parse: + return -EINVAL; +} + +/** + * Parse incoming D1 frame, populate attribute values + * + * Caller provides pointers to memory already allocated for attributes + * expected in the D1 frame. These variables will be populated. + */ +static +int wlp_parse_d1_frame(struct wlp *wlp, struct sk_buff *skb, + struct wlp_uuid *uuid_e, + enum wlp_wss_sel_mthd *sel_mthd, + struct wlp_device_info *dev_info, + enum wlp_assc_error *assc_err) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *d1 = (void *) skb->data; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + ssize_t result; + + used = sizeof(*d1); + result = wlp_get_uuid_e(wlp, ptr + used, uuid_e, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-E attribute from D1 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wss_sel_mthd(wlp, ptr + used, sel_mthd, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS selection method " + "from D1 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_dev_name(wlp, ptr + used, dev_info->name, + len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Name from D1 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_variable_info(wlp, ptr + used, dev_info, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Information from " + "D1 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wlp_assc_err(wlp, ptr + used, assc_err, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WLP Association Error " + "Information from D1 message.\n"); + goto error_parse; + } + result = 0; +error_parse: + return result; +} +/** + * Handle incoming D1 frame + * + * The frame has already been verified to contain an Association header with + * the correct version number. Parse the incoming frame, construct and send + * a D2 frame in response. + * + * It is not clear what to do with most fields in the incoming D1 frame. We + * retrieve and discard the information here for now. + */ +void wlp_handle_d1_frame(struct work_struct *ws) +{ + struct wlp_assoc_frame_ctx *frame_ctx = container_of(ws, + struct wlp_assoc_frame_ctx, + ws); + struct wlp *wlp = frame_ctx->wlp; + struct wlp_wss *wss = &wlp->wss; + struct sk_buff *skb = frame_ctx->skb; + struct uwb_dev_addr *src = &frame_ctx->src; + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_uuid uuid_e; + enum wlp_wss_sel_mthd sel_mthd = 0; + struct wlp_device_info dev_info; + enum wlp_assc_error assc_err; + char uuid[WLP_WSS_UUID_STRSIZE]; + struct sk_buff *resp = NULL; + + /* Parse D1 frame */ + d_fnstart(6, dev, "WLP: handle D1 frame. wlp = %p, skb = %p\n", + wlp, skb); + mutex_lock(&wss->mutex); + mutex_lock(&wlp->mutex); /* to access wlp->uuid */ + memset(&dev_info, 0, sizeof(dev_info)); + result = wlp_parse_d1_frame(wlp, skb, &uuid_e, &sel_mthd, &dev_info, + &assc_err); + if (result < 0) { + dev_err(dev, "WLP: Unable to parse incoming D1 frame.\n"); + kfree_skb(skb); + goto out; + } + wlp_wss_uuid_print(uuid, sizeof(uuid), &uuid_e); + d_printf(6, dev, "From D1 frame:\n" + "UUID-E: %s\n" + "Selection method: %d\n" + "Device name (%d bytes): %s\n" + "Model name (%d bytes): %s\n" + "Manufacturer (%d bytes): %s\n" + "Model number (%d bytes): %s\n" + "Serial number (%d bytes): %s\n" + "Primary device type: \n" + " Category: %d \n" + " OUI: %02x:%02x:%02x \n" + " OUI Subdivision: %u \n", + uuid, sel_mthd, + (int)strlen(dev_info.name), dev_info.name, + (int)strlen(dev_info.model_name), dev_info.model_name, + (int)strlen(dev_info.manufacturer), dev_info.manufacturer, + (int)strlen(dev_info.model_nr), dev_info.model_nr, + (int)strlen(dev_info.serial), dev_info.serial, + dev_info.prim_dev_type.category, + dev_info.prim_dev_type.OUI[0], + dev_info.prim_dev_type.OUI[1], + dev_info.prim_dev_type.OUI[2], + dev_info.prim_dev_type.OUIsubdiv); + + kfree_skb(skb); + if (!wlp_uuid_is_set(&wlp->uuid)) { + dev_err(dev, "WLP: UUID is not set. Set via sysfs to " + "proceed. Respong to D1 message with error F0.\n"); + result = wlp_build_assoc_f0(wlp, &resp, + WLP_ASSOC_ERROR_NOT_READY); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct F0 message.\n"); + goto out; + } + } else { + /* Construct D2 frame */ + result = wlp_build_assoc_d2(wlp, wss, &resp, &uuid_e); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct D2 message.\n"); + goto out; + } + } + /* Send D2 frame */ + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, resp, src); + if (result < 0) { + dev_err(dev, "WLP: Unable to transmit D2 association " + "message: %d\n", result); + if (result == -ENXIO) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb_any(resp); /* we need to free if tx fails */ + } +out: + kfree(frame_ctx); + mutex_unlock(&wlp->mutex); + mutex_unlock(&wss->mutex); + d_fnend(6, dev, "WLP: handle D1 frame. wlp = %p\n", wlp); +} + +/** + * Parse incoming D2 frame, create and populate temporary cache + * + * @skb: socket buffer in which D2 frame can be found + * @neighbor: the neighbor that sent the D2 frame + * + * Will allocate memory for temporary storage of information learned during + * discovery. + */ +int wlp_parse_d2_frame_to_cache(struct wlp *wlp, struct sk_buff *skb, + struct wlp_neighbor_e *neighbor) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *d2 = (void *) skb->data; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + ssize_t result; + struct wlp_uuid uuid_e; + struct wlp_device_info *nb_info; + enum wlp_assc_error assc_err; + + used = sizeof(*d2); + result = wlp_get_uuid_e(wlp, ptr + used, &uuid_e, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-E attribute from D2 " + "message.\n"); + goto error_parse; + } + if (memcmp(&uuid_e, &wlp->uuid, sizeof(uuid_e))) { + dev_err(dev, "WLP: UUID-E in incoming D2 does not match " + "local UUID sent in D1. \n"); + goto error_parse; + } + used += result; + result = wlp_get_uuid_r(wlp, ptr + used, &neighbor->uuid, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-R attribute from D2 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wss_info_to_cache(wlp, ptr + used, neighbor, + len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS information " + "from D2 message.\n"); + goto error_parse; + } + used += result; + neighbor->info = kzalloc(sizeof(struct wlp_device_info), GFP_KERNEL); + if (neighbor->info == NULL) { + dev_err(dev, "WLP: cannot allocate memory to store device " + "info.\n"); + result = -ENOMEM; + goto error_parse; + } + nb_info = neighbor->info; + result = wlp_get_dev_name(wlp, ptr + used, nb_info->name, + len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Name from D2 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_variable_info(wlp, ptr + used, nb_info, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Information from " + "D2 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wlp_assc_err(wlp, ptr + used, &assc_err, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WLP Association Error " + "Information from D2 message.\n"); + goto error_parse; + } + if (assc_err != WLP_ASSOC_ERROR_NONE) { + dev_err(dev, "WLP: neighbor device returned association " + "error %d\n", assc_err); + result = -EINVAL; + goto error_parse; + } + result = 0; +error_parse: + if (result < 0) + wlp_remove_neighbor_tmp_info(neighbor); + return result; +} + +/** + * Parse incoming D2 frame, populate attribute values of WSS bein enrolled in + * + * @wss: our WSS that will be enrolled + * @skb: socket buffer in which D2 frame can be found + * @neighbor: the neighbor that sent the D2 frame + * @wssid: the wssid of the WSS in which we want to enroll + * + * Forms part of enrollment sequence. We are trying to enroll in WSS with + * @wssid by using @neighbor as registrar. A D1 message was sent to + * @neighbor and now we need to parse the D2 response. The neighbor's + * response is searched for the requested WSS and if found (and it accepts + * enrollment), we store the information. + */ +int wlp_parse_d2_frame_to_enroll(struct wlp_wss *wss, struct sk_buff *skb, + struct wlp_neighbor_e *neighbor, + struct wlp_uuid *wssid) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + ssize_t result; + struct wlp_uuid uuid_e; + struct wlp_uuid uuid_r; + struct wlp_device_info nb_info; + enum wlp_assc_error assc_err; + char uuid_bufA[WLP_WSS_UUID_STRSIZE]; + char uuid_bufB[WLP_WSS_UUID_STRSIZE]; + + used = sizeof(struct wlp_frame_assoc); + result = wlp_get_uuid_e(wlp, ptr + used, &uuid_e, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-E attribute from D2 " + "message.\n"); + goto error_parse; + } + if (memcmp(&uuid_e, &wlp->uuid, sizeof(uuid_e))) { + dev_err(dev, "WLP: UUID-E in incoming D2 does not match " + "local UUID sent in D1. \n"); + goto error_parse; + } + used += result; + result = wlp_get_uuid_r(wlp, ptr + used, &uuid_r, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-R attribute from D2 " + "message.\n"); + goto error_parse; + } + if (memcmp(&uuid_r, &neighbor->uuid, sizeof(uuid_r))) { + wlp_wss_uuid_print(uuid_bufA, sizeof(uuid_bufA), + &neighbor->uuid); + wlp_wss_uuid_print(uuid_bufB, sizeof(uuid_bufB), &uuid_r); + dev_err(dev, "WLP: UUID of neighbor does not match UUID " + "learned during discovery. Originally discovered: %s, " + "now from D2 message: %s\n", uuid_bufA, uuid_bufB); + result = -EINVAL; + goto error_parse; + } + used += result; + wss->wssid = *wssid; + result = wlp_get_wss_info_to_enroll(wlp, ptr + used, wss, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS information " + "from D2 message.\n"); + goto error_parse; + } + if (wss->state != WLP_WSS_STATE_PART_ENROLLED) { + dev_err(dev, "WLP: D2 message did not contain information " + "for successful enrollment. \n"); + result = -EINVAL; + goto error_parse; + } + used += result; + /* Place device information on stack to continue parsing of message */ + result = wlp_get_dev_name(wlp, ptr + used, nb_info.name, + len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Name from D2 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_variable_info(wlp, ptr + used, &nb_info, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Information from " + "D2 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wlp_assc_err(wlp, ptr + used, &assc_err, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WLP Association Error " + "Information from D2 message.\n"); + goto error_parse; + } + if (assc_err != WLP_ASSOC_ERROR_NONE) { + dev_err(dev, "WLP: neighbor device returned association " + "error %d\n", assc_err); + if (wss->state == WLP_WSS_STATE_PART_ENROLLED) { + dev_err(dev, "WLP: Enrolled in WSS (should not " + "happen according to spec). Undoing. \n"); + wlp_wss_reset(wss); + } + result = -EINVAL; + goto error_parse; + } + result = 0; +error_parse: + return result; +} + +/** + * Parse C3/C4 frame into provided variables + * + * @wssid: will point to copy of wssid retrieved from C3/C4 frame + * @tag: will point to copy of tag retrieved from C3/C4 frame + * @virt_addr: will point to copy of virtual address retrieved from C3/C4 + * frame. + * + * Calling function has to allocate memory for these values. + * + * skb contains a valid C3/C4 frame, return the individual fields of this + * frame in the provided variables. + */ +int wlp_parse_c3c4_frame(struct wlp *wlp, struct sk_buff *skb, + struct wlp_uuid *wssid, u8 *tag, + struct uwb_mac_addr *virt_addr) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + char buf[WLP_WSS_UUID_STRSIZE]; + struct wlp_frame_assoc *assoc = ptr; + + d_fnstart(6, dev, "wlp %p, skb %p \n", wlp, skb); + used = sizeof(*assoc); + result = wlp_get_wssid(wlp, ptr + used, wssid, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSSID attribute from " + "%s message.\n", wlp_assoc_frame_str(assoc->type)); + goto error_parse; + } + used += result; + result = wlp_get_wss_tag(wlp, ptr + used, tag, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS tag attribute from " + "%s message.\n", wlp_assoc_frame_str(assoc->type)); + goto error_parse; + } + used += result; + result = wlp_get_wss_virt(wlp, ptr + used, virt_addr, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS virtual address " + "attribute from %s message.\n", + wlp_assoc_frame_str(assoc->type)); + goto error_parse; + } + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_printf(6, dev, "WLP: parsed: WSSID %s, tag 0x%02x, virt " + "%02x:%02x:%02x:%02x:%02x:%02x \n", buf, *tag, + virt_addr->data[0], virt_addr->data[1], virt_addr->data[2], + virt_addr->data[3], virt_addr->data[4], virt_addr->data[5]); + +error_parse: + d_fnend(6, dev, "wlp %p, skb %p, result = %d \n", wlp, skb, result); + return result; +} + +/** + * Allocate memory for and populate fields of C1 or C2 association frame + * + * The C1 and C2 association frames appear identical - except for the type. + */ +static +int wlp_build_assoc_c1c2(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb, enum wlp_assoc_type type) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = -ENOMEM; + struct { + struct wlp_frame_assoc c_hdr; + struct wlp_attr_wssid wssid; + } *c; + struct sk_buff *_skb; + + d_fnstart(6, dev, "wlp %p, wss %p \n", wlp, wss); + _skb = dev_alloc_skb(sizeof(*c)); + if (_skb == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for C1/C2 " + "association frame. \n"); + goto error_alloc; + } + c = (void *) _skb->data; + d_printf(6, dev, "C1/C2 starts at %p \n", c); + c->c_hdr.hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + c->c_hdr.hdr.type = WLP_FRAME_ASSOCIATION; + c->c_hdr.type = type; + wlp_set_version(&c->c_hdr.version, WLP_VERSION); + wlp_set_msg_type(&c->c_hdr.msg_type, type); + wlp_set_wssid(&c->wssid, &wss->wssid); + skb_put(_skb, sizeof(*c)); + d_printf(6, dev, "C1/C2 message:\n"); + d_dump(6, dev, c, sizeof(*c)); + *skb = _skb; + result = 0; +error_alloc: + d_fnend(6, dev, "wlp %p, wss %p, result %d \n", wlp, wss, result); + return result; +} + + +static +int wlp_build_assoc_c1(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + return wlp_build_assoc_c1c2(wlp, wss, skb, WLP_ASSOC_C1); +} + +static +int wlp_build_assoc_c2(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + return wlp_build_assoc_c1c2(wlp, wss, skb, WLP_ASSOC_C2); +} + + +/** + * Allocate memory for and populate fields of C3 or C4 association frame + * + * The C3 and C4 association frames appear identical - except for the type. + */ +static +int wlp_build_assoc_c3c4(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb, enum wlp_assoc_type type) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = -ENOMEM; + struct { + struct wlp_frame_assoc c_hdr; + struct wlp_attr_wssid wssid; + struct wlp_attr_wss_tag wss_tag; + struct wlp_attr_wss_virt wss_virt; + } *c; + struct sk_buff *_skb; + + d_fnstart(6, dev, "wlp %p, wss %p \n", wlp, wss); + _skb = dev_alloc_skb(sizeof(*c)); + if (_skb == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for C3/C4 " + "association frame. \n"); + goto error_alloc; + } + c = (void *) _skb->data; + d_printf(6, dev, "C3/C4 starts at %p \n", c); + c->c_hdr.hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + c->c_hdr.hdr.type = WLP_FRAME_ASSOCIATION; + c->c_hdr.type = type; + wlp_set_version(&c->c_hdr.version, WLP_VERSION); + wlp_set_msg_type(&c->c_hdr.msg_type, type); + wlp_set_wssid(&c->wssid, &wss->wssid); + wlp_set_wss_tag(&c->wss_tag, wss->tag); + wlp_set_wss_virt(&c->wss_virt, &wss->virtual_addr); + skb_put(_skb, sizeof(*c)); + d_printf(6, dev, "C3/C4 message:\n"); + d_dump(6, dev, c, sizeof(*c)); + *skb = _skb; + result = 0; +error_alloc: + d_fnend(6, dev, "wlp %p, wss %p, result %d \n", wlp, wss, result); + return result; +} + +static +int wlp_build_assoc_c3(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + return wlp_build_assoc_c3c4(wlp, wss, skb, WLP_ASSOC_C3); +} + +static +int wlp_build_assoc_c4(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + return wlp_build_assoc_c3c4(wlp, wss, skb, WLP_ASSOC_C4); +} + + +#define wlp_send_assoc(type, id) \ +static int wlp_send_assoc_##type(struct wlp *wlp, struct wlp_wss *wss, \ + struct uwb_dev_addr *dev_addr) \ +{ \ + struct device *dev = &wlp->rc->uwb_dev.dev; \ + int result; \ + struct sk_buff *skb = NULL; \ + d_fnstart(6, dev, "wlp %p, wss %p, neighbor: %02x:%02x\n", \ + wlp, wss, dev_addr->data[1], dev_addr->data[0]); \ + d_printf(6, dev, "WLP: Constructing %s frame. \n", \ + wlp_assoc_frame_str(id)); \ + /* Build the frame */ \ + result = wlp_build_assoc_##type(wlp, wss, &skb); \ + if (result < 0) { \ + dev_err(dev, "WLP: Unable to construct %s association " \ + "frame: %d\n", wlp_assoc_frame_str(id), result);\ + goto error_build_assoc; \ + } \ + /* Send the frame */ \ + d_printf(6, dev, "Transmitting %s frame to %02x:%02x \n", \ + wlp_assoc_frame_str(id), \ + dev_addr->data[1], dev_addr->data[0]); \ + BUG_ON(wlp->xmit_frame == NULL); \ + result = wlp->xmit_frame(wlp, skb, dev_addr); \ + if (result < 0) { \ + dev_err(dev, "WLP: Unable to transmit %s association " \ + "message: %d\n", wlp_assoc_frame_str(id), \ + result); \ + if (result == -ENXIO) \ + dev_err(dev, "WLP: Is network interface " \ + "up? \n"); \ + goto error_xmit; \ + } \ + return 0; \ +error_xmit: \ + /* We could try again ... */ \ + dev_kfree_skb_any(skb);/*we need to free if tx fails*/ \ +error_build_assoc: \ + d_fnend(6, dev, "wlp %p, wss %p, neighbor: %02x:%02x\n", \ + wlp, wss, dev_addr->data[1], dev_addr->data[0]); \ + return result; \ +} + +wlp_send_assoc(d1, WLP_ASSOC_D1) +wlp_send_assoc(c1, WLP_ASSOC_C1) +wlp_send_assoc(c3, WLP_ASSOC_C3) + +int wlp_send_assoc_frame(struct wlp *wlp, struct wlp_wss *wss, + struct uwb_dev_addr *dev_addr, + enum wlp_assoc_type type) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + switch (type) { + case WLP_ASSOC_D1: + result = wlp_send_assoc_d1(wlp, wss, dev_addr); + break; + case WLP_ASSOC_C1: + result = wlp_send_assoc_c1(wlp, wss, dev_addr); + break; + case WLP_ASSOC_C3: + result = wlp_send_assoc_c3(wlp, wss, dev_addr); + break; + default: + dev_err(dev, "WLP: Received request to send unknown " + "association message.\n"); + result = -EINVAL; + break; + } + return result; +} + +/** + * Handle incoming C1 frame + * + * The frame has already been verified to contain an Association header with + * the correct version number. Parse the incoming frame, construct and send + * a C2 frame in response. + */ +void wlp_handle_c1_frame(struct work_struct *ws) +{ + struct wlp_assoc_frame_ctx *frame_ctx = container_of(ws, + struct wlp_assoc_frame_ctx, + ws); + struct wlp *wlp = frame_ctx->wlp; + struct wlp_wss *wss = &wlp->wss; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *c1 = (void *) frame_ctx->skb->data; + unsigned int len = frame_ctx->skb->len; + struct uwb_dev_addr *src = &frame_ctx->src; + int result; + struct wlp_uuid wssid; + char buf[WLP_WSS_UUID_STRSIZE]; + struct sk_buff *resp = NULL; + + /* Parse C1 frame */ + d_fnstart(6, dev, "WLP: handle C1 frame. wlp = %p, c1 = %p\n", + wlp, c1); + mutex_lock(&wss->mutex); + result = wlp_get_wssid(wlp, (void *)c1 + sizeof(*c1), &wssid, + len - sizeof(*c1)); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSSID from C1 frame.\n"); + goto out; + } + wlp_wss_uuid_print(buf, sizeof(buf), &wssid); + d_printf(6, dev, "Received C1 frame with WSSID %s \n", buf); + if (!memcmp(&wssid, &wss->wssid, sizeof(wssid)) + && wss->state == WLP_WSS_STATE_ACTIVE) { + d_printf(6, dev, "WSSID from C1 frame is known locally " + "and is active\n"); + /* Construct C2 frame */ + result = wlp_build_assoc_c2(wlp, wss, &resp); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct C2 message.\n"); + goto out; + } + } else { + d_printf(6, dev, "WSSID from C1 frame is not known locally " + "or is not active\n"); + /* Construct F0 frame */ + result = wlp_build_assoc_f0(wlp, &resp, WLP_ASSOC_ERROR_INV); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct F0 message.\n"); + goto out; + } + } + /* Send C2 frame */ + d_printf(6, dev, "Transmitting response (C2/F0) frame to %02x:%02x \n", + src->data[1], src->data[0]); + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, resp, src); + if (result < 0) { + dev_err(dev, "WLP: Unable to transmit response association " + "message: %d\n", result); + if (result == -ENXIO) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb_any(resp); /* we need to free if tx fails */ + } +out: + kfree_skb(frame_ctx->skb); + kfree(frame_ctx); + mutex_unlock(&wss->mutex); + d_fnend(6, dev, "WLP: handle C1 frame. wlp = %p\n", wlp); +} + +/** + * Handle incoming C3 frame + * + * The frame has already been verified to contain an Association header with + * the correct version number. Parse the incoming frame, construct and send + * a C4 frame in response. If the C3 frame identifies a WSS that is locally + * active then we connect to this neighbor (add it to our EDA cache). + */ +void wlp_handle_c3_frame(struct work_struct *ws) +{ + struct wlp_assoc_frame_ctx *frame_ctx = container_of(ws, + struct wlp_assoc_frame_ctx, + ws); + struct wlp *wlp = frame_ctx->wlp; + struct wlp_wss *wss = &wlp->wss; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct sk_buff *skb = frame_ctx->skb; + struct uwb_dev_addr *src = &frame_ctx->src; + int result; + char buf[WLP_WSS_UUID_STRSIZE]; + struct sk_buff *resp = NULL; + struct wlp_uuid wssid; + u8 tag; + struct uwb_mac_addr virt_addr; + + /* Parse C3 frame */ + d_fnstart(6, dev, "WLP: handle C3 frame. wlp = %p, skb = %p\n", + wlp, skb); + mutex_lock(&wss->mutex); + result = wlp_parse_c3c4_frame(wlp, skb, &wssid, &tag, &virt_addr); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain values from C3 frame.\n"); + goto out; + } + wlp_wss_uuid_print(buf, sizeof(buf), &wssid); + d_printf(6, dev, "Received C3 frame with WSSID %s \n", buf); + if (!memcmp(&wssid, &wss->wssid, sizeof(wssid)) + && wss->state >= WLP_WSS_STATE_ACTIVE) { + d_printf(6, dev, "WSSID from C3 frame is known locally " + "and is active\n"); + result = wlp_eda_update_node(&wlp->eda, src, wss, + (void *) virt_addr.data, tag, + WLP_WSS_CONNECTED); + if (result < 0) { + dev_err(dev, "WLP: Unable to update EDA cache " + "with new connected neighbor information.\n"); + result = wlp_build_assoc_f0(wlp, &resp, + WLP_ASSOC_ERROR_INT); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct F0 " + "message.\n"); + goto out; + } + } else { + wss->state = WLP_WSS_STATE_CONNECTED; + /* Construct C4 frame */ + result = wlp_build_assoc_c4(wlp, wss, &resp); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct C4 " + "message.\n"); + goto out; + } + } + } else { + d_printf(6, dev, "WSSID from C3 frame is not known locally " + "or is not active\n"); + /* Construct F0 frame */ + result = wlp_build_assoc_f0(wlp, &resp, WLP_ASSOC_ERROR_INV); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct F0 message.\n"); + goto out; + } + } + /* Send C4 frame */ + d_printf(6, dev, "Transmitting response (C4/F0) frame to %02x:%02x \n", + src->data[1], src->data[0]); + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, resp, src); + if (result < 0) { + dev_err(dev, "WLP: Unable to transmit response association " + "message: %d\n", result); + if (result == -ENXIO) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb_any(resp); /* we need to free if tx fails */ + } +out: + kfree_skb(frame_ctx->skb); + kfree(frame_ctx); + mutex_unlock(&wss->mutex); + d_fnend(6, dev, "WLP: handle C3 frame. wlp = %p, skb = %p\n", + wlp, skb); +} + + diff --git a/drivers/uwb/wlp/sysfs.c b/drivers/uwb/wlp/sysfs.c new file mode 100644 index 00000000000..1bb9b1f97d4 --- /dev/null +++ b/drivers/uwb/wlp/sysfs.c @@ -0,0 +1,709 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * sysfs functions + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: Docs + * + */ + +#include <linux/wlp.h> +#include "wlp-internal.h" + +static +size_t wlp_wss_wssid_e_print(char *buf, size_t bufsize, + struct wlp_wssid_e *wssid_e) +{ + size_t used = 0; + used += scnprintf(buf, bufsize, " WSS: "); + used += wlp_wss_uuid_print(buf + used, bufsize - used, + &wssid_e->wssid); + + if (wssid_e->info != NULL) { + used += scnprintf(buf + used, bufsize - used, " "); + used += uwb_mac_addr_print(buf + used, bufsize - used, + &wssid_e->info->bcast); + used += scnprintf(buf + used, bufsize - used, " %u %u %s\n", + wssid_e->info->accept_enroll, + wssid_e->info->sec_status, + wssid_e->info->name); + } + return used; +} + +/** + * Print out information learned from neighbor discovery + * + * Some fields being printed may not be included in the device discovery + * information (it is not mandatory). We are thus careful how the + * information is printed to ensure it is clear to the user what field is + * being referenced. + * The information being printed is for one time use - temporary storage is + * cleaned after it is printed. + * + * Ideally sysfs output should be on one line. The information printed here + * contain a few strings so it will be hard to parse if they are all + * printed on the same line - without agreeing on a standard field + * separator. + */ +static +ssize_t wlp_wss_neighborhood_print_remove(struct wlp *wlp, char *buf, + size_t bufsize) +{ + size_t used = 0; + struct wlp_neighbor_e *neighb; + struct wlp_wssid_e *wssid_e; + + mutex_lock(&wlp->nbmutex); + used = scnprintf(buf, bufsize, "#Neighbor information\n" + "#uuid dev_addr\n" + "# Device Name:\n# Model Name:\n# Manufacturer:\n" + "# Model Nr:\n# Serial:\n" + "# Pri Dev type: CategoryID OUI OUISubdiv " + "SubcategoryID\n" + "# WSS: WSSID WSS_name accept_enroll sec_status " + "bcast\n" + "# WSS: WSSID WSS_name accept_enroll sec_status " + "bcast\n\n"); + list_for_each_entry(neighb, &wlp->neighbors, node) { + if (bufsize - used <= 0) + goto out; + used += wlp_wss_uuid_print(buf + used, bufsize - used, + &neighb->uuid); + buf[used++] = ' '; + used += uwb_dev_addr_print(buf + used, bufsize - used, + &neighb->uwb_dev->dev_addr); + if (neighb->info != NULL) + used += scnprintf(buf + used, bufsize - used, + "\n Device Name: %s\n" + " Model Name: %s\n" + " Manufacturer:%s \n" + " Model Nr: %s\n" + " Serial: %s\n" + " Pri Dev type: " + "%u %02x:%02x:%02x %u %u\n", + neighb->info->name, + neighb->info->model_name, + neighb->info->manufacturer, + neighb->info->model_nr, + neighb->info->serial, + neighb->info->prim_dev_type.category, + neighb->info->prim_dev_type.OUI[0], + neighb->info->prim_dev_type.OUI[1], + neighb->info->prim_dev_type.OUI[2], + neighb->info->prim_dev_type.OUIsubdiv, + neighb->info->prim_dev_type.subID); + list_for_each_entry(wssid_e, &neighb->wssid, node) { + used += wlp_wss_wssid_e_print(buf + used, + bufsize - used, + wssid_e); + } + buf[used++] = '\n'; + wlp_remove_neighbor_tmp_info(neighb); + } + + +out: + mutex_unlock(&wlp->nbmutex); + return used; +} + + +/** + * Show properties of all WSS in neighborhood. + * + * Will trigger a complete discovery of WSS activated by this device and + * its neighbors. + */ +ssize_t wlp_neighborhood_show(struct wlp *wlp, char *buf) +{ + wlp_discover(wlp); + return wlp_wss_neighborhood_print_remove(wlp, buf, PAGE_SIZE); +} +EXPORT_SYMBOL_GPL(wlp_neighborhood_show); + +static +ssize_t __wlp_wss_properties_show(struct wlp_wss *wss, char *buf, + size_t bufsize) +{ + ssize_t result; + + result = wlp_wss_uuid_print(buf, bufsize, &wss->wssid); + result += scnprintf(buf + result, bufsize - result, " "); + result += uwb_mac_addr_print(buf + result, bufsize - result, + &wss->bcast); + result += scnprintf(buf + result, bufsize - result, + " 0x%02x %u ", wss->hash, wss->secure_status); + result += wlp_wss_key_print(buf + result, bufsize - result, + wss->master_key); + result += scnprintf(buf + result, bufsize - result, " 0x%02x ", + wss->tag); + result += uwb_mac_addr_print(buf + result, bufsize - result, + &wss->virtual_addr); + result += scnprintf(buf + result, bufsize - result, " %s", wss->name); + result += scnprintf(buf + result, bufsize - result, + "\n\n#WSSID\n#WSS broadcast address\n" + "#WSS hash\n#WSS secure status\n" + "#WSS master key\n#WSS local tag\n" + "#WSS local virtual EUI-48\n#WSS name\n"); + return result; +} + +/** + * Show which WSS is activated. + */ +ssize_t wlp_wss_activate_show(struct wlp_wss *wss, char *buf) +{ + int result = 0; + + if (mutex_lock_interruptible(&wss->mutex)) + goto out; + if (wss->state >= WLP_WSS_STATE_ACTIVE) + result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE); + else + result = scnprintf(buf, PAGE_SIZE, "No local WSS active.\n"); + result += scnprintf(buf + result, PAGE_SIZE - result, + "\n\n" + "# echo WSSID SECURE_STATUS ACCEPT_ENROLLMENT " + "NAME #create new WSS\n" + "# echo WSSID [DEV ADDR] #enroll in and activate " + "existing WSS, can request registrar\n" + "#\n" + "# WSSID is a 16 byte hex array. Eg. 12 A3 3B ... \n" + "# SECURE_STATUS 0 - unsecure, 1 - secure (default)\n" + "# ACCEPT_ENROLLMENT 0 - no, 1 - yes (default)\n" + "# NAME is the text string identifying the WSS\n" + "# DEV ADDR is the device address of neighbor " + "that should be registrar. Eg. 32:AB\n"); + + mutex_unlock(&wss->mutex); +out: + return result; + +} +EXPORT_SYMBOL_GPL(wlp_wss_activate_show); + +/** + * Create/activate a new WSS or enroll/activate in neighboring WSS + * + * The user can provide the WSSID of a WSS in which it wants to enroll. + * Only the WSSID is necessary if the WSS have been discovered before. If + * the WSS has not been discovered before, or the user wants to use a + * particular neighbor as its registrar, then the user can also provide a + * device address or the neighbor that will be used as registrar. + * + * A new WSS is created when the user provides a WSSID, secure status, and + * WSS name. + */ +ssize_t wlp_wss_activate_store(struct wlp_wss *wss, + const char *buf, size_t size) +{ + ssize_t result = -EINVAL; + struct wlp_uuid wssid; + struct uwb_dev_addr dev; + struct uwb_dev_addr bcast = {.data = {0xff, 0xff} }; + char name[65]; + unsigned sec_status, accept; + memset(name, 0, sizeof(name)); + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx:%02hhx", + &wssid.data[0] , &wssid.data[1], + &wssid.data[2] , &wssid.data[3], + &wssid.data[4] , &wssid.data[5], + &wssid.data[6] , &wssid.data[7], + &wssid.data[8] , &wssid.data[9], + &wssid.data[10], &wssid.data[11], + &wssid.data[12], &wssid.data[13], + &wssid.data[14], &wssid.data[15], + &dev.data[1], &dev.data[0]); + if (result == 16 || result == 17) { + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%u %u %64c", + &wssid.data[0] , &wssid.data[1], + &wssid.data[2] , &wssid.data[3], + &wssid.data[4] , &wssid.data[5], + &wssid.data[6] , &wssid.data[7], + &wssid.data[8] , &wssid.data[9], + &wssid.data[10], &wssid.data[11], + &wssid.data[12], &wssid.data[13], + &wssid.data[14], &wssid.data[15], + &sec_status, &accept, name); + if (result == 16) + result = wlp_wss_enroll_activate(wss, &wssid, &bcast); + else if (result == 19) { + sec_status = sec_status == 0 ? 0 : 1; + accept = accept == 0 ? 0 : 1; + /* We read name using %c, so the newline needs to be + * removed */ + if (strlen(name) != sizeof(name) - 1) + name[strlen(name) - 1] = '\0'; + result = wlp_wss_create_activate(wss, &wssid, name, + sec_status, accept); + } else + result = -EINVAL; + } else if (result == 18) + result = wlp_wss_enroll_activate(wss, &wssid, &dev); + else + result = -EINVAL; + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_wss_activate_store); + +/** + * Show the UUID of this host + */ +ssize_t wlp_uuid_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + + mutex_lock(&wlp->mutex); + result = wlp_wss_uuid_print(buf, PAGE_SIZE, &wlp->uuid); + buf[result++] = '\n'; + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_uuid_show); + +/** + * Store a new UUID for this host + * + * According to the spec this should be encoded as an octet string in the + * order the octets are shown in string representation in RFC 4122 (WLP + * 0.99 [Table 6]) + * + * We do not check value provided by user. + */ +ssize_t wlp_uuid_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + struct wlp_uuid uuid; + + mutex_lock(&wlp->mutex); + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx ", + &uuid.data[0] , &uuid.data[1], + &uuid.data[2] , &uuid.data[3], + &uuid.data[4] , &uuid.data[5], + &uuid.data[6] , &uuid.data[7], + &uuid.data[8] , &uuid.data[9], + &uuid.data[10], &uuid.data[11], + &uuid.data[12], &uuid.data[13], + &uuid.data[14], &uuid.data[15]); + if (result != 16) { + result = -EINVAL; + goto error; + } + wlp->uuid = uuid; +error: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_uuid_store); + +/** + * Show contents of members of device information structure + */ +#define wlp_dev_info_show(type) \ +ssize_t wlp_dev_##type##_show(struct wlp *wlp, char *buf) \ +{ \ + ssize_t result = 0; \ + mutex_lock(&wlp->mutex); \ + if (wlp->dev_info == NULL) { \ + result = __wlp_setup_device_info(wlp); \ + if (result < 0) \ + goto out; \ + } \ + result = scnprintf(buf, PAGE_SIZE, "%s\n", wlp->dev_info->type);\ +out: \ + mutex_unlock(&wlp->mutex); \ + return result; \ +} \ +EXPORT_SYMBOL_GPL(wlp_dev_##type##_show); + +wlp_dev_info_show(name) +wlp_dev_info_show(model_name) +wlp_dev_info_show(model_nr) +wlp_dev_info_show(manufacturer) +wlp_dev_info_show(serial) + +/** + * Store contents of members of device information structure + */ +#define wlp_dev_info_store(type, len) \ +ssize_t wlp_dev_##type##_store(struct wlp *wlp, const char *buf, size_t size)\ +{ \ + ssize_t result; \ + char format[10]; \ + mutex_lock(&wlp->mutex); \ + if (wlp->dev_info == NULL) { \ + result = __wlp_alloc_device_info(wlp); \ + if (result < 0) \ + goto out; \ + } \ + memset(wlp->dev_info->type, 0, sizeof(wlp->dev_info->type)); \ + sprintf(format, "%%%uc", len); \ + result = sscanf(buf, format, wlp->dev_info->type); \ +out: \ + mutex_unlock(&wlp->mutex); \ + return result < 0 ? result : size; \ +} \ +EXPORT_SYMBOL_GPL(wlp_dev_##type##_store); + +wlp_dev_info_store(name, 32) +wlp_dev_info_store(manufacturer, 64) +wlp_dev_info_store(model_name, 32) +wlp_dev_info_store(model_nr, 32) +wlp_dev_info_store(serial, 32) + +static +const char *__wlp_dev_category[] = { + [WLP_DEV_CAT_COMPUTER] = "Computer", + [WLP_DEV_CAT_INPUT] = "Input device", + [WLP_DEV_CAT_PRINT_SCAN_FAX_COPIER] = "Printer, scanner, FAX, or " + "Copier", + [WLP_DEV_CAT_CAMERA] = "Camera", + [WLP_DEV_CAT_STORAGE] = "Storage Network", + [WLP_DEV_CAT_INFRASTRUCTURE] = "Infrastructure", + [WLP_DEV_CAT_DISPLAY] = "Display", + [WLP_DEV_CAT_MULTIM] = "Multimedia device", + [WLP_DEV_CAT_GAMING] = "Gaming device", + [WLP_DEV_CAT_TELEPHONE] = "Telephone", + [WLP_DEV_CAT_OTHER] = "Other", +}; + +static +const char *wlp_dev_category_str(unsigned cat) +{ + if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE) + || cat == WLP_DEV_CAT_OTHER) + return __wlp_dev_category[cat]; + return "unknown category"; +} + +ssize_t wlp_dev_prim_category_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%s\n", + wlp_dev_category_str(wlp->dev_info->prim_dev_type.category)); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_category_show); + +ssize_t wlp_dev_prim_category_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + u16 cat; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%hu", &cat); + if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE) + || cat == WLP_DEV_CAT_OTHER) + wlp->dev_info->prim_dev_type.category = cat; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_category_store); + +ssize_t wlp_dev_prim_OUI_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%02x:%02x:%02x\n", + wlp->dev_info->prim_dev_type.OUI[0], + wlp->dev_info->prim_dev_type.OUI[1], + wlp->dev_info->prim_dev_type.OUI[2]); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_show); + +ssize_t wlp_dev_prim_OUI_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + u8 OUI[3]; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%hhx:%hhx:%hhx", + &OUI[0], &OUI[1], &OUI[2]); + if (result != 3) { + result = -EINVAL; + goto out; + } else + memcpy(wlp->dev_info->prim_dev_type.OUI, OUI, sizeof(OUI)); +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_store); + + +ssize_t wlp_dev_prim_OUI_sub_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%u\n", + wlp->dev_info->prim_dev_type.OUIsubdiv); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_show); + +ssize_t wlp_dev_prim_OUI_sub_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + unsigned sub; + u8 max_sub = ~0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%u", &sub); + if (sub <= max_sub) + wlp->dev_info->prim_dev_type.OUIsubdiv = sub; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_store); + +ssize_t wlp_dev_prim_subcat_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%u\n", + wlp->dev_info->prim_dev_type.subID); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_show); + +ssize_t wlp_dev_prim_subcat_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + unsigned sub; + __le16 max_sub = ~0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%u", &sub); + if (sub <= max_sub) + wlp->dev_info->prim_dev_type.subID = sub; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_store); + +/** + * Subsystem implementation for interaction with individual WSS via sysfs + * + * Followed instructions for subsystem in Documentation/filesystems/sysfs.txt + */ + +#define kobj_to_wlp_wss(obj) container_of(obj, struct wlp_wss, kobj) +#define attr_to_wlp_wss_attr(_attr) \ + container_of(_attr, struct wlp_wss_attribute, attr) + +/** + * Sysfs subsystem: forward read calls + * + * Sysfs operation for forwarding read call to the show method of the + * attribute owner + */ +static +ssize_t wlp_wss_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct wlp_wss_attribute *wss_attr = attr_to_wlp_wss_attr(attr); + struct wlp_wss *wss = kobj_to_wlp_wss(kobj); + ssize_t ret = -EIO; + + if (wss_attr->show) + ret = wss_attr->show(wss, buf); + return ret; +} +/** + * Sysfs subsystem: forward write calls + * + * Sysfs operation for forwarding write call to the store method of the + * attribute owner + */ +static +ssize_t wlp_wss_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct wlp_wss_attribute *wss_attr = attr_to_wlp_wss_attr(attr); + struct wlp_wss *wss = kobj_to_wlp_wss(kobj); + ssize_t ret = -EIO; + + if (wss_attr->store) + ret = wss_attr->store(wss, buf, count); + return ret; +} + +static +struct sysfs_ops wss_sysfs_ops = { + .show = wlp_wss_attr_show, + .store = wlp_wss_attr_store, +}; + +struct kobj_type wss_ktype = { + .release = wlp_wss_release, + .sysfs_ops = &wss_sysfs_ops, +}; + + +/** + * Sysfs files for individual WSS + */ + +/** + * Print static properties of this WSS + * + * The name of a WSS may not be null teminated. It's max size is 64 bytes + * so we copy it to a larger array just to make sure we print sane data. + */ +static ssize_t wlp_wss_properties_show(struct wlp_wss *wss, char *buf) +{ + int result = 0; + + if (mutex_lock_interruptible(&wss->mutex)) + goto out; + result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE); + mutex_unlock(&wss->mutex); +out: + return result; +} +WSS_ATTR(properties, S_IRUGO, wlp_wss_properties_show, NULL); + +/** + * Print all connected members of this WSS + * The EDA cache contains all members of WSS neighborhood. + */ +static ssize_t wlp_wss_members_show(struct wlp_wss *wss, char *buf) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + return wlp_eda_show(wlp, buf); +} +WSS_ATTR(members, S_IRUGO, wlp_wss_members_show, NULL); + +static +const char *__wlp_strstate[] = { + "none", + "partially enrolled", + "enrolled", + "active", + "connected", +}; + +static const char *wlp_wss_strstate(unsigned state) +{ + if (state >= ARRAY_SIZE(__wlp_strstate)) + return "unknown state"; + return __wlp_strstate[state]; +} + +/* + * Print current state of this WSS + */ +static ssize_t wlp_wss_state_show(struct wlp_wss *wss, char *buf) +{ + int result = 0; + + if (mutex_lock_interruptible(&wss->mutex)) + goto out; + result = scnprintf(buf, PAGE_SIZE, "%s\n", + wlp_wss_strstate(wss->state)); + mutex_unlock(&wss->mutex); +out: + return result; +} +WSS_ATTR(state, S_IRUGO, wlp_wss_state_show, NULL); + + +static +struct attribute *wss_attrs[] = { + &wss_attr_properties.attr, + &wss_attr_members.attr, + &wss_attr_state.attr, + NULL, +}; + +struct attribute_group wss_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wss_attrs, +}; diff --git a/drivers/uwb/wlp/txrx.c b/drivers/uwb/wlp/txrx.c new file mode 100644 index 00000000000..c701bd1a288 --- /dev/null +++ b/drivers/uwb/wlp/txrx.c @@ -0,0 +1,374 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * Message exchange infrastructure + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: Docs + * + */ + +#include <linux/etherdevice.h> +#include <linux/wlp.h> +#define D_LOCAL 5 +#include <linux/uwb/debug.h> +#include "wlp-internal.h" + + +/** + * Direct incoming association msg to correct parsing routine + * + * We only expect D1, E1, C1, C3 messages as new. All other incoming + * association messages should form part of an established session that is + * handled elsewhere. + * The handling of these messages often require calling sleeping functions + * - this cannot be done in interrupt context. We use the kernel's + * workqueue to handle these messages. + */ +static +void wlp_direct_assoc_frame(struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *src) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *assoc = (void *) skb->data; + struct wlp_assoc_frame_ctx *frame_ctx; + d_fnstart(5, dev, "wlp %p, skb %p\n", wlp, skb); + frame_ctx = kmalloc(sizeof(*frame_ctx), GFP_ATOMIC); + if (frame_ctx == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for association " + "frame handling.\n"); + kfree_skb(skb); + goto out; + } + frame_ctx->wlp = wlp; + frame_ctx->skb = skb; + frame_ctx->src = *src; + switch (assoc->type) { + case WLP_ASSOC_D1: + d_printf(5, dev, "Received a D1 frame.\n"); + INIT_WORK(&frame_ctx->ws, wlp_handle_d1_frame); + schedule_work(&frame_ctx->ws); + break; + case WLP_ASSOC_E1: + d_printf(5, dev, "Received a E1 frame. FIXME?\n"); + kfree_skb(skb); /* Temporary until we handle it */ + kfree(frame_ctx); /* Temporary until we handle it */ + break; + case WLP_ASSOC_C1: + d_printf(5, dev, "Received a C1 frame.\n"); + INIT_WORK(&frame_ctx->ws, wlp_handle_c1_frame); + schedule_work(&frame_ctx->ws); + break; + case WLP_ASSOC_C3: + d_printf(5, dev, "Received a C3 frame.\n"); + INIT_WORK(&frame_ctx->ws, wlp_handle_c3_frame); + schedule_work(&frame_ctx->ws); + break; + default: + dev_err(dev, "Received unexpected association frame. " + "Type = %d \n", assoc->type); + kfree_skb(skb); + kfree(frame_ctx); + break; + } +out: + d_fnend(5, dev, "wlp %p\n", wlp); +} + +/** + * Process incoming association frame + * + * Although it could be possible to deal with some incoming association + * messages without creating a new session we are keeping things simple. We + * do not accept new association messages if there is a session in progress + * and the messages do not belong to that session. + * + * If an association message arrives that causes the creation of a session + * (WLP_ASSOC_E1) while we are in the process of creating a session then we + * rely on the neighbor mutex to protect the data. That is, the new session + * will not be started until the previous is completed. + */ +static +void wlp_receive_assoc_frame(struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *src) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *assoc = (void *) skb->data; + struct wlp_session *session = wlp->session; + u8 version; + d_fnstart(5, dev, "wlp %p, skb %p\n", wlp, skb); + + if (wlp_get_version(wlp, &assoc->version, &version, + sizeof(assoc->version)) < 0) + goto error; + if (version != WLP_VERSION) { + dev_err(dev, "Unsupported WLP version in association " + "message.\n"); + goto error; + } + if (session != NULL) { + /* Function that created this session is still holding the + * &wlp->mutex to protect this session. */ + if (assoc->type == session->exp_message || + assoc->type == WLP_ASSOC_F0) { + if (!memcmp(&session->neighbor_addr, src, + sizeof(*src))) { + session->data = skb; + (session->cb)(wlp); + } else { + dev_err(dev, "Received expected message from " + "unexpected source. Expected message " + "%d or F0 from %02x:%02x, but received " + "it from %02x:%02x. Dropping.\n", + session->exp_message, + session->neighbor_addr.data[1], + session->neighbor_addr.data[0], + src->data[1], src->data[0]); + goto error; + } + } else { + dev_err(dev, "Association already in progress. " + "Dropping.\n"); + goto error; + } + } else { + wlp_direct_assoc_frame(wlp, skb, src); + } + d_fnend(5, dev, "wlp %p\n", wlp); + return; +error: + kfree_skb(skb); + d_fnend(5, dev, "wlp %p\n", wlp); +} + +/** + * Verify incoming frame is from connected neighbor, prep to pass to WLP client + * + * Verification proceeds according to WLP 0.99 [7.3.1]. The source address + * is used to determine which neighbor is sending the frame and the WSS tag + * is used to know to which WSS the frame belongs (we only support one WSS + * so this test is straight forward). + * With the WSS found we need to ensure that we are connected before + * allowing the exchange of data frames. + */ +static +int wlp_verify_prep_rx_frame(struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *src) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = -EINVAL; + struct wlp_eda_node eda_entry; + struct wlp_frame_std_abbrv_hdr *hdr = (void *) skb->data; + + d_fnstart(6, dev, "wlp %p, skb %p \n", wlp, skb); + /*verify*/ + result = wlp_copy_eda_node(&wlp->eda, src, &eda_entry); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Incoming frame is from unknown " + "neighbor %02x:%02x.\n", src->data[1], + src->data[0]); + goto out; + } + if (hdr->tag != eda_entry.tag) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Tag of incoming frame from " + "%02x:%02x does not match expected tag. " + "Received 0x%02x, expected 0x%02x. \n", + src->data[1], src->data[0], hdr->tag, + eda_entry.tag); + result = -EINVAL; + goto out; + } + if (eda_entry.state != WLP_WSS_CONNECTED) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Incoming frame from " + "%02x:%02x does is not from connected WSS.\n", + src->data[1], src->data[0]); + result = -EINVAL; + goto out; + } + /*prep*/ + skb_pull(skb, sizeof(*hdr)); +out: + d_fnend(6, dev, "wlp %p, skb %p, result = %d \n", wlp, skb, result); + return result; +} + +/** + * Receive a WLP frame from device + * + * @returns: 1 if calling function should free the skb + * 0 if it successfully handled skb and freed it + * 0 if error occured, will free skb in this case + */ +int wlp_receive_frame(struct device *dev, struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *src) +{ + unsigned len = skb->len; + void *ptr = skb->data; + struct wlp_frame_hdr *hdr; + int result = 0; + + d_fnstart(6, dev, "skb (%p), len (%u)\n", skb, len); + if (len < sizeof(*hdr)) { + dev_err(dev, "Not enough data to parse WLP header.\n"); + result = -EINVAL; + goto out; + } + hdr = ptr; + d_dump(6, dev, hdr, sizeof(*hdr)); + if (le16_to_cpu(hdr->mux_hdr) != WLP_PROTOCOL_ID) { + dev_err(dev, "Not a WLP frame type.\n"); + result = -EINVAL; + goto out; + } + switch (hdr->type) { + case WLP_FRAME_STANDARD: + if (len < sizeof(struct wlp_frame_std_abbrv_hdr)) { + dev_err(dev, "Not enough data to parse Standard " + "WLP header.\n"); + goto out; + } + result = wlp_verify_prep_rx_frame(wlp, skb, src); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Verification of frame " + "from neighbor %02x:%02x failed.\n", + src->data[1], src->data[0]); + goto out; + } + result = 1; + break; + case WLP_FRAME_ABBREVIATED: + dev_err(dev, "Abbreviated frame received. FIXME?\n"); + kfree_skb(skb); + break; + case WLP_FRAME_CONTROL: + dev_err(dev, "Control frame received. FIXME?\n"); + kfree_skb(skb); + break; + case WLP_FRAME_ASSOCIATION: + if (len < sizeof(struct wlp_frame_assoc)) { + dev_err(dev, "Not enough data to parse Association " + "WLP header.\n"); + goto out; + } + d_printf(5, dev, "Association frame received.\n"); + wlp_receive_assoc_frame(wlp, skb, src); + break; + default: + dev_err(dev, "Invalid frame received.\n"); + result = -EINVAL; + break; + } +out: + if (result < 0) { + kfree_skb(skb); + result = 0; + } + d_fnend(6, dev, "skb (%p)\n", skb); + return result; +} +EXPORT_SYMBOL_GPL(wlp_receive_frame); + + +/** + * Verify frame from network stack, prepare for further transmission + * + * @skb: the socket buffer that needs to be prepared for transmission (it + * is in need of a WLP header). If this is a broadcast frame we take + * over the entire transmission. + * If it is a unicast the WSS connection should already be established + * and transmission will be done by the calling function. + * @dst: On return this will contain the device address to which the + * frame is destined. + * @returns: 0 on success no tx : WLP header sucessfully applied to skb buffer, + * calling function can proceed with tx + * 1 on success with tx : WLP will take over transmission of this + * frame + * <0 on error + * + * The network stack (WLP client) is attempting to transmit a frame. We can + * only transmit data if a local WSS is at least active (connection will be + * done here if this is a broadcast frame and neighbor also has the WSS + * active). + * + * The frame can be either broadcast or unicast. Broadcast in a WSS is + * supported via multicast, but we don't support multicast yet (until + * devices start to support MAB IEs). If a broadcast frame needs to be + * transmitted it is treated as a unicast frame to each neighbor. In this + * case the WLP takes over transmission of the skb and returns 1 + * to the caller to indicate so. Also, in this case, if a neighbor has the + * same WSS activated but is not connected then the WSS connection will be + * done at this time. The neighbor's virtual address will be learned at + * this time. + * + * The destination address in a unicast frame is the virtual address of the + * neighbor. This address only becomes known when a WSS connection is + * established. We thus rely on a broadcast frame to trigger the setup of + * WSS connections to all neighbors before we are able to send unicast + * frames to them. This seems reasonable as IP would usually use ARP first + * before any unicast frames are sent. + * + * If we are already connected to the neighbor (neighbor's virtual address + * is known) we just prepare the WLP header and the caller will continue to + * send the frame. + * + * A failure in this function usually indicates something that cannot be + * fixed automatically. So, if this function fails (@return < 0) the calling + * function should not retry to send the frame as it will very likely keep + * failing. + * + */ +int wlp_prepare_tx_frame(struct device *dev, struct wlp *wlp, + struct sk_buff *skb, struct uwb_dev_addr *dst) +{ + int result = -EINVAL; + struct ethhdr *eth_hdr = (void *) skb->data; + + d_fnstart(6, dev, "wlp (%p), skb (%p) \n", wlp, skb); + if (is_broadcast_ether_addr(eth_hdr->h_dest)) { + d_printf(6, dev, "WLP: handling broadcast frame. \n"); + result = wlp_eda_for_each(&wlp->eda, wlp_wss_send_copy, skb); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "Unable to handle broadcast " + "frame from WLP client.\n"); + goto out; + } + dev_kfree_skb_irq(skb); + result = 1; + /* Frame will be transmitted by WLP. */ + } else { + d_printf(6, dev, "WLP: handling unicast frame. \n"); + result = wlp_eda_for_virtual(&wlp->eda, eth_hdr->h_dest, dst, + wlp_wss_prep_hdr, skb); + if (unlikely(result < 0)) { + if (printk_ratelimit()) + dev_err(dev, "Unable to prepare " + "skb for transmission. \n"); + goto out; + } + } +out: + d_fnend(6, dev, "wlp (%p), skb (%p). result = %d \n", wlp, skb, result); + return result; +} +EXPORT_SYMBOL_GPL(wlp_prepare_tx_frame); diff --git a/drivers/uwb/wlp/wlp-internal.h b/drivers/uwb/wlp/wlp-internal.h new file mode 100644 index 00000000000..1c94fabfb1a --- /dev/null +++ b/drivers/uwb/wlp/wlp-internal.h @@ -0,0 +1,228 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * Internal API + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + */ + +#ifndef __WLP_INTERNAL_H__ +#define __WLP_INTERNAL_H__ + +/** + * State of WSS connection + * + * A device needs to connect to a neighbor in an activated WSS before data + * can be transmitted. The spec also distinguishes between a new connection + * attempt and a connection attempt after previous connection attempts. The + * state WLP_WSS_CONNECT_FAILED is used for this scenario. See WLP 0.99 + * [7.2.6] + */ +enum wlp_wss_connect { + WLP_WSS_UNCONNECTED = 0, + WLP_WSS_CONNECTED, + WLP_WSS_CONNECT_FAILED, +}; + +extern struct kobj_type wss_ktype; +extern struct attribute_group wss_attr_group; + +extern int uwb_rc_ie_add(struct uwb_rc *, const struct uwb_ie_hdr *, size_t); +extern int uwb_rc_ie_rm(struct uwb_rc *, enum uwb_ie); + + +/* This should be changed to a dynamic array where entries are sorted + * by eth_addr and search is done in a binary form + * + * Although thinking twice about it: this technologie's maximum reach + * is 10 meters...unless you want to pack too much stuff in around + * your radio controller/WLP device, the list will probably not be + * too big. + * + * In any case, there is probably some data structure in the kernel + * than we could reused for that already. + * + * The below structure is really just good while we support one WSS per + * host. + */ +struct wlp_eda_node { + struct list_head list_node; + unsigned char eth_addr[ETH_ALEN]; + struct uwb_dev_addr dev_addr; + struct wlp_wss *wss; + unsigned char virt_addr[ETH_ALEN]; + u8 tag; + enum wlp_wss_connect state; +}; + +typedef int (*wlp_eda_for_each_f)(struct wlp *, struct wlp_eda_node *, void *); + +extern void wlp_eda_init(struct wlp_eda *); +extern void wlp_eda_release(struct wlp_eda *); +extern int wlp_eda_create_node(struct wlp_eda *, + const unsigned char eth_addr[ETH_ALEN], + const struct uwb_dev_addr *); +extern void wlp_eda_rm_node(struct wlp_eda *, const struct uwb_dev_addr *); +extern int wlp_eda_update_node(struct wlp_eda *, + const struct uwb_dev_addr *, + struct wlp_wss *, + const unsigned char virt_addr[ETH_ALEN], + const u8, const enum wlp_wss_connect); +extern int wlp_eda_update_node_state(struct wlp_eda *, + const struct uwb_dev_addr *, + const enum wlp_wss_connect); + +extern int wlp_copy_eda_node(struct wlp_eda *, struct uwb_dev_addr *, + struct wlp_eda_node *); +extern int wlp_eda_for_each(struct wlp_eda *, wlp_eda_for_each_f , void *); +extern int wlp_eda_for_virtual(struct wlp_eda *, + const unsigned char eth_addr[ETH_ALEN], + struct uwb_dev_addr *, + wlp_eda_for_each_f , void *); + + +extern void wlp_remove_neighbor_tmp_info(struct wlp_neighbor_e *); + +extern size_t wlp_wss_key_print(char *, size_t, u8 *); + +/* Function called when no more references to WSS exists */ +extern void wlp_wss_release(struct kobject *); + +extern void wlp_wss_reset(struct wlp_wss *); +extern int wlp_wss_create_activate(struct wlp_wss *, struct wlp_uuid *, + char *, unsigned, unsigned); +extern int wlp_wss_enroll_activate(struct wlp_wss *, struct wlp_uuid *, + struct uwb_dev_addr *); +extern ssize_t wlp_discover(struct wlp *); + +extern int wlp_enroll_neighbor(struct wlp *, struct wlp_neighbor_e *, + struct wlp_wss *, struct wlp_uuid *); +extern int wlp_wss_is_active(struct wlp *, struct wlp_wss *, + struct uwb_dev_addr *); + +struct wlp_assoc_conn_ctx { + struct work_struct ws; + struct wlp *wlp; + struct sk_buff *skb; + struct wlp_eda_node eda_entry; +}; + + +extern int wlp_wss_connect_prep(struct wlp *, struct wlp_eda_node *, void *); +extern int wlp_wss_send_copy(struct wlp *, struct wlp_eda_node *, void *); + + +/* Message handling */ +struct wlp_assoc_frame_ctx { + struct work_struct ws; + struct wlp *wlp; + struct sk_buff *skb; + struct uwb_dev_addr src; +}; + +extern int wlp_wss_prep_hdr(struct wlp *, struct wlp_eda_node *, void *); +extern void wlp_handle_d1_frame(struct work_struct *); +extern int wlp_parse_d2_frame_to_cache(struct wlp *, struct sk_buff *, + struct wlp_neighbor_e *); +extern int wlp_parse_d2_frame_to_enroll(struct wlp_wss *, struct sk_buff *, + struct wlp_neighbor_e *, + struct wlp_uuid *); +extern void wlp_handle_c1_frame(struct work_struct *); +extern void wlp_handle_c3_frame(struct work_struct *); +extern int wlp_parse_c3c4_frame(struct wlp *, struct sk_buff *, + struct wlp_uuid *, u8 *, + struct uwb_mac_addr *); +extern int wlp_parse_f0(struct wlp *, struct sk_buff *); +extern int wlp_send_assoc_frame(struct wlp *, struct wlp_wss *, + struct uwb_dev_addr *, enum wlp_assoc_type); +extern ssize_t wlp_get_version(struct wlp *, struct wlp_attr_version *, + u8 *, ssize_t); +extern ssize_t wlp_get_wssid(struct wlp *, struct wlp_attr_wssid *, + struct wlp_uuid *, ssize_t); +extern int __wlp_alloc_device_info(struct wlp *); +extern int __wlp_setup_device_info(struct wlp *); + +extern struct wlp_wss_attribute wss_attribute_properties; +extern struct wlp_wss_attribute wss_attribute_members; +extern struct wlp_wss_attribute wss_attribute_state; + +static inline +size_t wlp_wss_uuid_print(char *buf, size_t bufsize, struct wlp_uuid *uuid) +{ + size_t result; + + result = scnprintf(buf, bufsize, + "%02x:%02x:%02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x", + uuid->data[0], uuid->data[1], + uuid->data[2], uuid->data[3], + uuid->data[4], uuid->data[5], + uuid->data[6], uuid->data[7], + uuid->data[8], uuid->data[9], + uuid->data[10], uuid->data[11], + uuid->data[12], uuid->data[13], + uuid->data[14], uuid->data[15]); + return result; +} + +/** + * FIXME: How should a nonce be displayed? + */ +static inline +size_t wlp_wss_nonce_print(char *buf, size_t bufsize, struct wlp_nonce *nonce) +{ + size_t result; + + result = scnprintf(buf, bufsize, + "%02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x", + nonce->data[0], nonce->data[1], + nonce->data[2], nonce->data[3], + nonce->data[4], nonce->data[5], + nonce->data[6], nonce->data[7], + nonce->data[8], nonce->data[9], + nonce->data[10], nonce->data[11], + nonce->data[12], nonce->data[13], + nonce->data[14], nonce->data[15]); + return result; +} + + +static inline +void wlp_session_cb(struct wlp *wlp) +{ + struct completion *completion = wlp->session->cb_priv; + complete(completion); +} + +static inline +int wlp_uuid_is_set(struct wlp_uuid *uuid) +{ + struct wlp_uuid zero_uuid = { .data = { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} }; + + if (!memcmp(uuid, &zero_uuid, sizeof(*uuid))) + return 0; + return 1; +} + +#endif /* __WLP_INTERNAL_H__ */ diff --git a/drivers/uwb/wlp/wlp-lc.c b/drivers/uwb/wlp/wlp-lc.c new file mode 100644 index 00000000000..0799402e73f --- /dev/null +++ b/drivers/uwb/wlp/wlp-lc.c @@ -0,0 +1,585 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * + * Copyright (C) 2005-2006 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include <linux/wlp.h> +#define D_LOCAL 6 +#include <linux/uwb/debug.h> +#include "wlp-internal.h" + + +static +void wlp_neighbor_init(struct wlp_neighbor_e *neighbor) +{ + INIT_LIST_HEAD(&neighbor->wssid); +} + +/** + * Create area for device information storage + * + * wlp->mutex must be held + */ +int __wlp_alloc_device_info(struct wlp *wlp) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + BUG_ON(wlp->dev_info != NULL); + wlp->dev_info = kzalloc(sizeof(struct wlp_device_info), GFP_KERNEL); + if (wlp->dev_info == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for " + "device information.\n"); + return -ENOMEM; + } + return 0; +} + + +/** + * Fill in device information using function provided by driver + * + * wlp->mutex must be held + */ +static +void __wlp_fill_device_info(struct wlp *wlp) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + + BUG_ON(wlp->fill_device_info == NULL); + d_printf(6, dev, "Retrieving device information " + "from device driver.\n"); + wlp->fill_device_info(wlp, wlp->dev_info); +} + +/** + * Setup device information + * + * Allocate area for device information and populate it. + * + * wlp->mutex must be held + */ +int __wlp_setup_device_info(struct wlp *wlp) +{ + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + + result = __wlp_alloc_device_info(wlp); + if (result < 0) { + dev_err(dev, "WLP: Unable to allocate area for " + "device information.\n"); + return result; + } + __wlp_fill_device_info(wlp); + return 0; +} + +/** + * Remove information about neighbor stored temporarily + * + * Information learned during discovey should only be stored when the + * device enrolls in the neighbor's WSS. We do need to store this + * information temporarily in order to present it to the user. + * + * We are only interested in keeping neighbor WSS information if that + * neighbor is accepting enrollment. + * + * should be called with wlp->nbmutex held + */ +void wlp_remove_neighbor_tmp_info(struct wlp_neighbor_e *neighbor) +{ + struct wlp_wssid_e *wssid_e, *next; + u8 keep; + if (!list_empty(&neighbor->wssid)) { + list_for_each_entry_safe(wssid_e, next, &neighbor->wssid, + node) { + if (wssid_e->info != NULL) { + keep = wssid_e->info->accept_enroll; + kfree(wssid_e->info); + wssid_e->info = NULL; + if (!keep) { + list_del(&wssid_e->node); + kfree(wssid_e); + } + } + } + } + if (neighbor->info != NULL) { + kfree(neighbor->info); + neighbor->info = NULL; + } +} + +/** + * Populate WLP neighborhood cache with neighbor information + * + * A new neighbor is found. If it is discoverable then we add it to the + * neighborhood cache. + * + */ +static +int wlp_add_neighbor(struct wlp *wlp, struct uwb_dev *dev) +{ + int result = 0; + int discoverable; + struct wlp_neighbor_e *neighbor; + + d_fnstart(6, &dev->dev, "uwb %p \n", dev); + d_printf(6, &dev->dev, "Found neighbor device %02x:%02x \n", + dev->dev_addr.data[1], dev->dev_addr.data[0]); + /** + * FIXME: + * Use contents of WLP IE found in beacon cache to determine if + * neighbor is discoverable. + * The device does not support WLP IE yet so this still needs to be + * done. Until then we assume all devices are discoverable. + */ + discoverable = 1; /* will be changed when FIXME disappears */ + if (discoverable) { + /* Add neighbor to cache for discovery */ + neighbor = kzalloc(sizeof(*neighbor), GFP_KERNEL); + if (neighbor == NULL) { + dev_err(&dev->dev, "Unable to create memory for " + "new neighbor. \n"); + result = -ENOMEM; + goto error_no_mem; + } + wlp_neighbor_init(neighbor); + uwb_dev_get(dev); + neighbor->uwb_dev = dev; + list_add(&neighbor->node, &wlp->neighbors); + } +error_no_mem: + d_fnend(6, &dev->dev, "uwb %p, result = %d \n", dev, result); + return result; +} + +/** + * Remove one neighbor from cache + */ +static +void __wlp_neighbor_release(struct wlp_neighbor_e *neighbor) +{ + struct wlp_wssid_e *wssid_e, *next_wssid_e; + + list_for_each_entry_safe(wssid_e, next_wssid_e, + &neighbor->wssid, node) { + list_del(&wssid_e->node); + kfree(wssid_e); + } + uwb_dev_put(neighbor->uwb_dev); + list_del(&neighbor->node); + kfree(neighbor); +} + +/** + * Clear entire neighborhood cache. + */ +static +void __wlp_neighbors_release(struct wlp *wlp) +{ + struct wlp_neighbor_e *neighbor, *next; + if (list_empty(&wlp->neighbors)) + return; + list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) { + __wlp_neighbor_release(neighbor); + } +} + +static +void wlp_neighbors_release(struct wlp *wlp) +{ + mutex_lock(&wlp->nbmutex); + __wlp_neighbors_release(wlp); + mutex_unlock(&wlp->nbmutex); +} + + + +/** + * Send D1 message to neighbor, receive D2 message + * + * @neighbor: neighbor to which D1 message will be sent + * @wss: if not NULL, it is an enrollment request for this WSS + * @wssid: if wss not NULL, this is the wssid of the WSS in which we + * want to enroll + * + * A D1/D2 exchange is done for one of two reasons: discovery or + * enrollment. If done for discovery the D1 message is sent to the neighbor + * and the contents of the D2 response is stored in a temporary cache. + * If done for enrollment the @wss and @wssid are provided also. In this + * case the D1 message is sent to the neighbor, the D2 response is parsed + * for enrollment of the WSS with wssid. + * + * &wss->mutex is held + */ +static +int wlp_d1d2_exchange(struct wlp *wlp, struct wlp_neighbor_e *neighbor, + struct wlp_wss *wss, struct wlp_uuid *wssid) +{ + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + DECLARE_COMPLETION_ONSTACK(completion); + struct wlp_session session; + struct sk_buff *skb; + struct wlp_frame_assoc *resp; + struct uwb_dev_addr *dev_addr = &neighbor->uwb_dev->dev_addr; + + mutex_lock(&wlp->mutex); + if (!wlp_uuid_is_set(&wlp->uuid)) { + dev_err(dev, "WLP: UUID is not set. Set via sysfs to " + "proceed.\n"); + result = -ENXIO; + goto out; + } + /* Send D1 association frame */ + result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_D1); + if (result < 0) { + dev_err(dev, "Unable to send D1 frame to neighbor " + "%02x:%02x (%d)\n", dev_addr->data[1], + dev_addr->data[0], result); + d_printf(6, dev, "Add placeholders into buffer next to " + "neighbor information we have (dev address).\n"); + goto out; + } + /* Create session, wait for response */ + session.exp_message = WLP_ASSOC_D2; + session.cb = wlp_session_cb; + session.cb_priv = &completion; + session.neighbor_addr = *dev_addr; + BUG_ON(wlp->session != NULL); + wlp->session = &session; + /* Wait for D2/F0 frame */ + result = wait_for_completion_interruptible_timeout(&completion, + WLP_PER_MSG_TIMEOUT * HZ); + if (result == 0) { + result = -ETIMEDOUT; + dev_err(dev, "Timeout while sending D1 to neighbor " + "%02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + goto error_session; + } + if (result < 0) { + dev_err(dev, "Unable to discover/enroll neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + goto error_session; + } + /* Parse message in session->data: it will be either D2 or F0 */ + skb = session.data; + resp = (void *) skb->data; + d_printf(6, dev, "Received response to D1 frame. \n"); + d_dump(6, dev, skb->data, skb->len > 72 ? 72 : skb->len); + + if (resp->type == WLP_ASSOC_F0) { + result = wlp_parse_f0(wlp, skb); + if (result < 0) + dev_err(dev, "WLP: Unable to parse F0 from neighbor " + "%02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + result = -EINVAL; + goto error_resp_parse; + } + if (wss == NULL) { + /* Discovery */ + result = wlp_parse_d2_frame_to_cache(wlp, skb, neighbor); + if (result < 0) { + dev_err(dev, "WLP: Unable to parse D2 message from " + "neighbor %02x:%02x for discovery.\n", + dev_addr->data[1], dev_addr->data[0]); + goto error_resp_parse; + } + } else { + /* Enrollment */ + result = wlp_parse_d2_frame_to_enroll(wss, skb, neighbor, + wssid); + if (result < 0) { + dev_err(dev, "WLP: Unable to parse D2 message from " + "neighbor %02x:%02x for enrollment.\n", + dev_addr->data[1], dev_addr->data[0]); + goto error_resp_parse; + } + } +error_resp_parse: + kfree_skb(skb); +error_session: + wlp->session = NULL; +out: + mutex_unlock(&wlp->mutex); + return result; +} + +/** + * Enroll into WSS of provided WSSID by using neighbor as registrar + * + * &wss->mutex is held + */ +int wlp_enroll_neighbor(struct wlp *wlp, struct wlp_neighbor_e *neighbor, + struct wlp_wss *wss, struct wlp_uuid *wssid) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + struct uwb_dev_addr *dev_addr = &neighbor->uwb_dev->dev_addr; + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_fnstart(6, dev, "wlp %p, neighbor %p, wss %p, wssid %p (%s)\n", + wlp, neighbor, wss, wssid, buf); + d_printf(6, dev, "Complete me.\n"); + result = wlp_d1d2_exchange(wlp, neighbor, wss, wssid); + if (result < 0) { + dev_err(dev, "WLP: D1/D2 message exchange for enrollment " + "failed. result = %d \n", result); + goto out; + } + if (wss->state != WLP_WSS_STATE_PART_ENROLLED) { + dev_err(dev, "WLP: Unable to enroll into WSS %s using " + "neighbor %02x:%02x. \n", buf, + dev_addr->data[1], dev_addr->data[0]); + result = -EINVAL; + goto out; + } + if (wss->secure_status == WLP_WSS_SECURE) { + dev_err(dev, "FIXME: need to complete secure enrollment.\n"); + result = -EINVAL; + goto error; + } else { + wss->state = WLP_WSS_STATE_ENROLLED; + d_printf(2, dev, "WLP: Success Enrollment into unsecure WSS " + "%s using neighbor %02x:%02x. \n", buf, + dev_addr->data[1], dev_addr->data[0]); + } + + d_fnend(6, dev, "wlp %p, neighbor %p, wss %p, wssid %p (%s)\n", + wlp, neighbor, wss, wssid, buf); +out: + return result; +error: + wlp_wss_reset(wss); + return result; +} + +/** + * Discover WSS information of neighbor's active WSS + */ +static +int wlp_discover_neighbor(struct wlp *wlp, + struct wlp_neighbor_e *neighbor) +{ + return wlp_d1d2_exchange(wlp, neighbor, NULL, NULL); +} + + +/** + * Each neighbor in the neighborhood cache is discoverable. Discover it. + * + * Discovery is done through sending of D1 association frame and parsing + * the D2 association frame response. Only wssid from D2 will be included + * in neighbor cache, rest is just displayed to user and forgotten. + * + * The discovery is not done in parallel. This is simple and enables us to + * maintain only one association context. + * + * The discovery of one neighbor does not affect the other, but if the + * discovery of a neighbor fails it is removed from the neighborhood cache. + */ +static +int wlp_discover_all_neighbors(struct wlp *wlp) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_neighbor_e *neighbor, *next; + + list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) { + result = wlp_discover_neighbor(wlp, neighbor); + if (result < 0) { + dev_err(dev, "WLP: Unable to discover neighbor " + "%02x:%02x, removing from neighborhood. \n", + neighbor->uwb_dev->dev_addr.data[1], + neighbor->uwb_dev->dev_addr.data[0]); + __wlp_neighbor_release(neighbor); + } + } + return result; +} + +static int wlp_add_neighbor_helper(struct device *dev, void *priv) +{ + struct wlp *wlp = priv; + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + + return wlp_add_neighbor(wlp, uwb_dev); +} + +/** + * Discover WLP neighborhood + * + * Will send D1 association frame to all devices in beacon group that have + * discoverable bit set in WLP IE. D2 frames will be received, information + * displayed to user in @buf. Partial information (from D2 association + * frame) will be cached to assist with future association + * requests. + * + * The discovery of the WLP neighborhood is triggered by the user. This + * should occur infrequently and we thus free current cache and re-allocate + * memory if needed. + * + * If one neighbor fails during initial discovery (determining if it is a + * neighbor or not), we fail all - note that interaction with neighbor has + * not occured at this point so if a failure occurs we know something went wrong + * locally. We thus undo everything. + */ +ssize_t wlp_discover(struct wlp *wlp) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + + d_fnstart(6, dev, "wlp %p \n", wlp); + mutex_lock(&wlp->nbmutex); + /* Clear current neighborhood cache. */ + __wlp_neighbors_release(wlp); + /* Determine which devices in neighborhood. Repopulate cache. */ + result = uwb_dev_for_each(wlp->rc, wlp_add_neighbor_helper, wlp); + if (result < 0) { + /* May have partial neighbor information, release all. */ + __wlp_neighbors_release(wlp); + goto error_dev_for_each; + } + /* Discover the properties of devices in neighborhood. */ + result = wlp_discover_all_neighbors(wlp); + /* In case of failure we still print our partial results. */ + if (result < 0) { + dev_err(dev, "Unable to fully discover neighborhood. \n"); + result = 0; + } +error_dev_for_each: + mutex_unlock(&wlp->nbmutex); + d_fnend(6, dev, "wlp %p \n", wlp); + return result; +} + +/** + * Handle events from UWB stack + * + * We handle events conservatively. If a neighbor goes off the air we + * remove it from the neighborhood. If an association process is in + * progress this function will block waiting for the nbmutex to become + * free. The association process will thus be allowed to complete before it + * is removed. + */ +static +void wlp_uwb_notifs_cb(void *_wlp, struct uwb_dev *uwb_dev, + enum uwb_notifs event) +{ + struct wlp *wlp = _wlp; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_neighbor_e *neighbor, *next; + int result; + switch (event) { + case UWB_NOTIF_ONAIR: + d_printf(6, dev, "UWB device %02x:%02x is onair\n", + uwb_dev->dev_addr.data[1], + uwb_dev->dev_addr.data[0]); + result = wlp_eda_create_node(&wlp->eda, + uwb_dev->mac_addr.data, + &uwb_dev->dev_addr); + if (result < 0) + dev_err(dev, "WLP: Unable to add new neighbor " + "%02x:%02x to EDA cache.\n", + uwb_dev->dev_addr.data[1], + uwb_dev->dev_addr.data[0]); + break; + case UWB_NOTIF_OFFAIR: + d_printf(6, dev, "UWB device %02x:%02x is offair\n", + uwb_dev->dev_addr.data[1], + uwb_dev->dev_addr.data[0]); + wlp_eda_rm_node(&wlp->eda, &uwb_dev->dev_addr); + mutex_lock(&wlp->nbmutex); + list_for_each_entry_safe(neighbor, next, &wlp->neighbors, + node) { + if (neighbor->uwb_dev == uwb_dev) { + d_printf(6, dev, "Removing device from " + "neighborhood.\n"); + __wlp_neighbor_release(neighbor); + } + } + mutex_unlock(&wlp->nbmutex); + break; + default: + dev_err(dev, "don't know how to handle event %d from uwb\n", + event); + } +} + +int wlp_setup(struct wlp *wlp, struct uwb_rc *rc) +{ + struct device *dev = &rc->uwb_dev.dev; + int result; + + d_fnstart(6, dev, "wlp %p\n", wlp); + BUG_ON(wlp->fill_device_info == NULL); + BUG_ON(wlp->xmit_frame == NULL); + BUG_ON(wlp->stop_queue == NULL); + BUG_ON(wlp->start_queue == NULL); + wlp->rc = rc; + wlp_eda_init(&wlp->eda);/* Set up address cache */ + wlp->uwb_notifs_handler.cb = wlp_uwb_notifs_cb; + wlp->uwb_notifs_handler.data = wlp; + uwb_notifs_register(rc, &wlp->uwb_notifs_handler); + + uwb_pal_init(&wlp->pal); + result = uwb_pal_register(rc, &wlp->pal); + if (result < 0) + uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler); + + d_fnend(6, dev, "wlp %p, result = %d\n", wlp, result); + return result; +} +EXPORT_SYMBOL_GPL(wlp_setup); + +void wlp_remove(struct wlp *wlp) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + d_fnstart(6, dev, "wlp %p\n", wlp); + wlp_neighbors_release(wlp); + uwb_pal_unregister(wlp->rc, &wlp->pal); + uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler); + wlp_eda_release(&wlp->eda); + mutex_lock(&wlp->mutex); + if (wlp->dev_info != NULL) + kfree(wlp->dev_info); + mutex_unlock(&wlp->mutex); + wlp->rc = NULL; + /* We have to use NULL here because this function can be called + * when the device disappeared. */ + d_fnend(6, NULL, "wlp %p\n", wlp); +} +EXPORT_SYMBOL_GPL(wlp_remove); + +/** + * wlp_reset_all - reset the WLP hardware + * @wlp: the WLP device to reset. + * + * This schedules a full hardware reset of the WLP device. The radio + * controller and any other PALs will also be reset. + */ +void wlp_reset_all(struct wlp *wlp) +{ + uwb_rc_reset_all(wlp->rc); +} +EXPORT_SYMBOL_GPL(wlp_reset_all); diff --git a/drivers/uwb/wlp/wss-lc.c b/drivers/uwb/wlp/wss-lc.c new file mode 100644 index 00000000000..96b18c9bd6e --- /dev/null +++ b/drivers/uwb/wlp/wss-lc.c @@ -0,0 +1,1055 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Implementation of the WLP association protocol. + * + * FIXME: Docs + * + * A UWB network interface will configure a WSS through wlp_wss_setup() after + * the interface has been assigned a MAC address, typically after + * "ifconfig" has been called. When the interface goes down it should call + * wlp_wss_remove(). + * + * When the WSS is ready for use the user interacts via sysfs to create, + * discover, and activate WSS. + * + * wlp_wss_enroll_activate() + * + * wlp_wss_create_activate() + * wlp_wss_set_wssid_hash() + * wlp_wss_comp_wssid_hash() + * wlp_wss_sel_bcast_addr() + * wlp_wss_sysfs_add() + * + * Called when no more references to WSS exist: + * wlp_wss_release() + * wlp_wss_reset() + */ + +#include <linux/etherdevice.h> /* for is_valid_ether_addr */ +#include <linux/skbuff.h> +#include <linux/wlp.h> +#define D_LOCAL 5 +#include <linux/uwb/debug.h> +#include "wlp-internal.h" + + +size_t wlp_wss_key_print(char *buf, size_t bufsize, u8 *key) +{ + size_t result; + + result = scnprintf(buf, bufsize, + "%02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x", + key[0], key[1], key[2], key[3], + key[4], key[5], key[6], key[7], + key[8], key[9], key[10], key[11], + key[12], key[13], key[14], key[15]); + return result; +} + +/** + * Compute WSSID hash + * WLP Draft 0.99 [7.2.1] + * + * The WSSID hash for a WSSID is the result of an octet-wise exclusive-OR + * of all octets in the WSSID. + */ +static +u8 wlp_wss_comp_wssid_hash(struct wlp_uuid *wssid) +{ + return wssid->data[0] ^ wssid->data[1] ^ wssid->data[2] + ^ wssid->data[3] ^ wssid->data[4] ^ wssid->data[5] + ^ wssid->data[6] ^ wssid->data[7] ^ wssid->data[8] + ^ wssid->data[9] ^ wssid->data[10] ^ wssid->data[11] + ^ wssid->data[12] ^ wssid->data[13] ^ wssid->data[14] + ^ wssid->data[15]; +} + +/** + * Select a multicast EUI-48 for the WSS broadcast address. + * WLP Draft 0.99 [7.2.1] + * + * Selected based on the WiMedia Alliance OUI, 00-13-88, within the WLP + * range, [01-13-88-00-01-00, 01-13-88-00-01-FF] inclusive. + * + * This address is currently hardcoded. + * FIXME? + */ +static +struct uwb_mac_addr wlp_wss_sel_bcast_addr(struct wlp_wss *wss) +{ + struct uwb_mac_addr bcast = { + .data = { 0x01, 0x13, 0x88, 0x00, 0x01, 0x00 } + }; + return bcast; +} + +/** + * Clear the contents of the WSS structure - all except kobj, mutex, virtual + * + * We do not want to reinitialize - the internal kobj should not change as + * it still points to the parent received during setup. The mutex should + * remain also. We thus just reset values individually. + * The virutal address assigned to WSS will remain the same for the + * lifetime of the WSS. We only reset the fields that can change during its + * lifetime. + */ +void wlp_wss_reset(struct wlp_wss *wss) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + d_fnstart(5, dev, "wss (%p) \n", wss); + memset(&wss->wssid, 0, sizeof(wss->wssid)); + wss->hash = 0; + memset(&wss->name[0], 0, sizeof(wss->name)); + memset(&wss->bcast, 0, sizeof(wss->bcast)); + wss->secure_status = WLP_WSS_UNSECURE; + memset(&wss->master_key[0], 0, sizeof(wss->master_key)); + wss->tag = 0; + wss->state = WLP_WSS_STATE_NONE; + d_fnend(5, dev, "wss (%p) \n", wss); +} + +/** + * Create sysfs infrastructure for WSS + * + * The WSS is configured to have the interface as parent (see wlp_wss_setup()) + * a new sysfs directory that includes wssid as its name is created in the + * interface's sysfs directory. The group of files interacting with WSS are + * created also. + */ +static +int wlp_wss_sysfs_add(struct wlp_wss *wss, char *wssid_str) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + int result; + + d_fnstart(5, dev, "wss (%p), wssid: %s\n", wss, wssid_str); + result = kobject_set_name(&wss->kobj, "wss-%s", wssid_str); + if (result < 0) + return result; + wss->kobj.ktype = &wss_ktype; + result = kobject_init_and_add(&wss->kobj, + &wss_ktype, wss->kobj.parent, "wlp"); + if (result < 0) { + dev_err(dev, "WLP: Cannot register WSS kobject.\n"); + goto error_kobject_register; + } + result = sysfs_create_group(&wss->kobj, &wss_attr_group); + if (result < 0) { + dev_err(dev, "WLP: Cannot register WSS attributes: %d\n", + result); + goto error_sysfs_create_group; + } + d_fnend(5, dev, "Completed. result = %d \n", result); + return 0; +error_sysfs_create_group: + + kobject_put(&wss->kobj); /* will free name if needed */ + return result; +error_kobject_register: + kfree(wss->kobj.name); + wss->kobj.name = NULL; + wss->kobj.ktype = NULL; + return result; +} + + +/** + * Release WSS + * + * No more references exist to this WSS. We should undo everything that was + * done in wlp_wss_create_activate() except removing the group. The group + * is not removed because an object can be unregistered before the group is + * created. We also undo any additional operations on the WSS after this + * (addition of members). + * + * If memory was allocated for the kobject's name then it will + * be freed by the kobject system during this time. + * + * The EDA cache is removed and reinitilized when the WSS is removed. We + * thus loose knowledge of members of this WSS at that time and need not do + * it here. + */ +void wlp_wss_release(struct kobject *kobj) +{ + struct wlp_wss *wss = container_of(kobj, struct wlp_wss, kobj); + + wlp_wss_reset(wss); +} + +/** + * Enroll into a WSS using provided neighbor as registrar + * + * First search the neighborhood information to learn which neighbor is + * referred to, next proceed with enrollment. + * + * &wss->mutex is held + */ +static +int wlp_wss_enroll_target(struct wlp_wss *wss, struct wlp_uuid *wssid, + struct uwb_dev_addr *dest) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_neighbor_e *neighbor; + char buf[WLP_WSS_UUID_STRSIZE]; + int result = -ENXIO; + struct uwb_dev_addr *dev_addr; + + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_fnstart(5, dev, "wss %p, wssid %s, registrar %02x:%02x \n", + wss, buf, dest->data[1], dest->data[0]); + mutex_lock(&wlp->nbmutex); + list_for_each_entry(neighbor, &wlp->neighbors, node) { + dev_addr = &neighbor->uwb_dev->dev_addr; + if (!memcmp(dest, dev_addr, sizeof(*dest))) { + d_printf(5, dev, "Neighbor %02x:%02x is valid, " + "enrolling. \n", + dev_addr->data[1], dev_addr->data[0]); + result = wlp_enroll_neighbor(wlp, neighbor, wss, + wssid); + break; + } + } + if (result == -ENXIO) + dev_err(dev, "WLP: Cannot find neighbor %02x:%02x. \n", + dest->data[1], dest->data[0]); + mutex_unlock(&wlp->nbmutex); + d_fnend(5, dev, "wss %p, wssid %s, registrar %02x:%02x, result %d \n", + wss, buf, dest->data[1], dest->data[0], result); + return result; +} + +/** + * Enroll into a WSS previously discovered + * + * User provides WSSID of WSS, search for neighbor that has this WSS + * activated and attempt to enroll. + * + * &wss->mutex is held + */ +static +int wlp_wss_enroll_discovered(struct wlp_wss *wss, struct wlp_uuid *wssid) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_neighbor_e *neighbor; + struct wlp_wssid_e *wssid_e; + char buf[WLP_WSS_UUID_STRSIZE]; + int result = -ENXIO; + + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_fnstart(5, dev, "wss %p, wssid %s \n", wss, buf); + mutex_lock(&wlp->nbmutex); + list_for_each_entry(neighbor, &wlp->neighbors, node) { + list_for_each_entry(wssid_e, &neighbor->wssid, node) { + if (!memcmp(wssid, &wssid_e->wssid, sizeof(*wssid))) { + d_printf(5, dev, "Found WSSID %s in neighbor " + "%02x:%02x cache. \n", buf, + neighbor->uwb_dev->dev_addr.data[1], + neighbor->uwb_dev->dev_addr.data[0]); + result = wlp_enroll_neighbor(wlp, neighbor, + wss, wssid); + if (result == 0) /* enrollment success */ + goto out; + break; + } + } + } +out: + if (result == -ENXIO) + dev_err(dev, "WLP: Cannot find WSSID %s in cache. \n", buf); + mutex_unlock(&wlp->nbmutex); + d_fnend(5, dev, "wss %p, wssid %s, result %d \n", wss, buf, result); + return result; +} + +/** + * Enroll into WSS with provided WSSID, registrar may be provided + * + * @wss: out WSS that will be enrolled + * @wssid: wssid of neighboring WSS that we want to enroll in + * @devaddr: registrar can be specified, will be broadcast (ff:ff) if any + * neighbor can be used as registrar. + * + * &wss->mutex is held + */ +static +int wlp_wss_enroll(struct wlp_wss *wss, struct wlp_uuid *wssid, + struct uwb_dev_addr *devaddr) +{ + int result; + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + struct uwb_dev_addr bcast = {.data = {0xff, 0xff} }; + + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + if (wss->state != WLP_WSS_STATE_NONE) { + dev_err(dev, "WLP: Already enrolled in WSS %s.\n", buf); + result = -EEXIST; + goto error; + } + if (!memcmp(&bcast, devaddr, sizeof(bcast))) { + d_printf(5, dev, "Request to enroll in discovered WSS " + "with WSSID %s \n", buf); + result = wlp_wss_enroll_discovered(wss, wssid); + } else { + d_printf(5, dev, "Request to enroll in WSSID %s with " + "registrar %02x:%02x\n", buf, devaddr->data[1], + devaddr->data[0]); + result = wlp_wss_enroll_target(wss, wssid, devaddr); + } + if (result < 0) { + dev_err(dev, "WLP: Unable to enroll into WSS %s, result %d \n", + buf, result); + goto error; + } + d_printf(2, dev, "Successfully enrolled into WSS %s \n", buf); + result = wlp_wss_sysfs_add(wss, buf); + if (result < 0) { + dev_err(dev, "WLP: Unable to set up sysfs for WSS kobject.\n"); + wlp_wss_reset(wss); + } +error: + return result; + +} + +/** + * Activate given WSS + * + * Prior to activation a WSS must be enrolled. To activate a WSS a device + * includes the WSS hash in the WLP IE in its beacon in each superframe. + * WLP 0.99 [7.2.5]. + * + * The WSS tag is also computed at this time. We only support one activated + * WSS so we can use the hash as a tag - there will never be a conflict. + * + * We currently only support one activated WSS so only one WSS hash is + * included in the WLP IE. + */ +static +int wlp_wss_activate(struct wlp_wss *wss) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct uwb_rc *uwb_rc = wlp->rc; + int result; + struct { + struct wlp_ie wlp_ie; + u8 hash; /* only include one hash */ + } ie_data; + + d_fnstart(5, dev, "Activating WSS %p. \n", wss); + BUG_ON(wss->state != WLP_WSS_STATE_ENROLLED); + wss->hash = wlp_wss_comp_wssid_hash(&wss->wssid); + wss->tag = wss->hash; + memset(&ie_data, 0, sizeof(ie_data)); + ie_data.wlp_ie.hdr.element_id = UWB_IE_WLP; + ie_data.wlp_ie.hdr.length = sizeof(ie_data) - sizeof(struct uwb_ie_hdr); + wlp_ie_set_hash_length(&ie_data.wlp_ie, sizeof(ie_data.hash)); + ie_data.hash = wss->hash; + result = uwb_rc_ie_add(uwb_rc, &ie_data.wlp_ie.hdr, + sizeof(ie_data)); + if (result < 0) { + dev_err(dev, "WLP: Unable to add WLP IE to beacon. " + "result = %d.\n", result); + goto error_wlp_ie; + } + wss->state = WLP_WSS_STATE_ACTIVE; + result = 0; +error_wlp_ie: + d_fnend(5, dev, "Activating WSS %p, result = %d \n", wss, result); + return result; +} + +/** + * Enroll in and activate WSS identified by provided WSSID + * + * The neighborhood cache should contain a list of all neighbors and the + * WSS they have activated. Based on that cache we search which neighbor we + * can perform the association process with. The user also has option to + * specify which neighbor it prefers as registrar. + * Successful enrollment is followed by activation. + * Successful activation will create the sysfs directory containing + * specific information regarding this WSS. + */ +int wlp_wss_enroll_activate(struct wlp_wss *wss, struct wlp_uuid *wssid, + struct uwb_dev_addr *devaddr) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + char buf[WLP_WSS_UUID_STRSIZE]; + + d_fnstart(5, dev, "Enrollment and activation requested. \n"); + mutex_lock(&wss->mutex); + result = wlp_wss_enroll(wss, wssid, devaddr); + if (result < 0) { + wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid); + dev_err(dev, "WLP: Enrollment into WSS %s failed.\n", buf); + goto error_enroll; + } + result = wlp_wss_activate(wss); + if (result < 0) { + dev_err(dev, "WLP: Unable to activate WSS. Undoing enrollment " + "result = %d \n", result); + /* Undo enrollment */ + wlp_wss_reset(wss); + goto error_activate; + } +error_activate: +error_enroll: + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "Completed. result = %d \n", result); + return result; +} + +/** + * Create, enroll, and activate a new WSS + * + * @wssid: new wssid provided by user + * @name: WSS name requested by used. + * @sec_status: security status requested by user + * + * A user requested the creation of a new WSS. All operations are done + * locally. The new WSS will be stored locally, the hash will be included + * in the WLP IE, and the sysfs infrastructure for this WSS will be + * created. + */ +int wlp_wss_create_activate(struct wlp_wss *wss, struct wlp_uuid *wssid, + char *name, unsigned sec_status, unsigned accept) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + char buf[WLP_WSS_UUID_STRSIZE]; + d_fnstart(5, dev, "Request to create new WSS.\n"); + result = wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_printf(5, dev, "Request to create WSS: WSSID=%s, name=%s, " + "sec_status=%u, accepting enrollment=%u \n", + buf, name, sec_status, accept); + if (!mutex_trylock(&wss->mutex)) { + dev_err(dev, "WLP: WLP association session in progress.\n"); + return -EBUSY; + } + if (wss->state != WLP_WSS_STATE_NONE) { + dev_err(dev, "WLP: WSS already exists. Not creating new.\n"); + result = -EEXIST; + goto out; + } + if (wss->kobj.parent == NULL) { + dev_err(dev, "WLP: WSS parent not ready. Is network interface " + "up?\n"); + result = -ENXIO; + goto out; + } + if (sec_status == WLP_WSS_SECURE) { + dev_err(dev, "WLP: FIXME Creation of secure WSS not " + "supported yet.\n"); + result = -EINVAL; + goto out; + } + wss->wssid = *wssid; + memcpy(wss->name, name, sizeof(wss->name)); + wss->bcast = wlp_wss_sel_bcast_addr(wss); + wss->secure_status = sec_status; + wss->accept_enroll = accept; + /*wss->virtual_addr is initialized in call to wlp_wss_setup*/ + /* sysfs infrastructure */ + result = wlp_wss_sysfs_add(wss, buf); + if (result < 0) { + dev_err(dev, "Cannot set up sysfs for WSS kobject.\n"); + wlp_wss_reset(wss); + goto out; + } else + result = 0; + wss->state = WLP_WSS_STATE_ENROLLED; + result = wlp_wss_activate(wss); + if (result < 0) { + dev_err(dev, "WLP: Unable to activate WSS. Undoing " + "enrollment\n"); + wlp_wss_reset(wss); + goto out; + } + result = 0; +out: + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "Completed. result = %d \n", result); + return result; +} + +/** + * Determine if neighbor has WSS activated + * + * @returns: 1 if neighbor has WSS activated, zero otherwise + * + * This can be done in two ways: + * - send a C1 frame, parse C2/F0 response + * - examine the WLP IE sent by the neighbor + * + * The WLP IE is not fully supported in hardware so we use the C1/C2 frame + * exchange to determine if a WSS is activated. Using the WLP IE should be + * faster and should be used when it becomes possible. + */ +int wlp_wss_is_active(struct wlp *wlp, struct wlp_wss *wss, + struct uwb_dev_addr *dev_addr) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + DECLARE_COMPLETION_ONSTACK(completion); + struct wlp_session session; + struct sk_buff *skb; + struct wlp_frame_assoc *resp; + struct wlp_uuid wssid; + + wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid); + d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + mutex_lock(&wlp->mutex); + /* Send C1 association frame */ + result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_C1); + if (result < 0) { + dev_err(dev, "Unable to send C1 frame to neighbor " + "%02x:%02x (%d)\n", dev_addr->data[1], + dev_addr->data[0], result); + result = 0; + goto out; + } + /* Create session, wait for response */ + session.exp_message = WLP_ASSOC_C2; + session.cb = wlp_session_cb; + session.cb_priv = &completion; + session.neighbor_addr = *dev_addr; + BUG_ON(wlp->session != NULL); + wlp->session = &session; + /* Wait for C2/F0 frame */ + result = wait_for_completion_interruptible_timeout(&completion, + WLP_PER_MSG_TIMEOUT * HZ); + if (result == 0) { + dev_err(dev, "Timeout while sending C1 to neighbor " + "%02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + goto out; + } + if (result < 0) { + dev_err(dev, "Unable to send C1 to neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + result = 0; + goto out; + } + /* Parse message in session->data: it will be either C2 or F0 */ + skb = session.data; + resp = (void *) skb->data; + d_printf(5, dev, "Received response to C1 frame. \n"); + d_dump(5, dev, skb->data, skb->len > 72 ? 72 : skb->len); + if (resp->type == WLP_ASSOC_F0) { + result = wlp_parse_f0(wlp, skb); + if (result < 0) + dev_err(dev, "WLP: unable to parse incoming F0 " + "frame from neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + result = 0; + goto error_resp_parse; + } + /* WLP version and message type fields have already been parsed */ + result = wlp_get_wssid(wlp, (void *)resp + sizeof(*resp), &wssid, + skb->len - sizeof(*resp)); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSSID from C2 frame.\n"); + result = 0; + goto error_resp_parse; + } + if (!memcmp(&wssid, &wss->wssid, sizeof(wssid))) { + d_printf(5, dev, "WSSID in C2 frame matches local " + "active WSS.\n"); + result = 1; + } else { + dev_err(dev, "WLP: Received a C2 frame without matching " + "WSSID.\n"); + result = 0; + } +error_resp_parse: + kfree_skb(skb); +out: + wlp->session = NULL; + mutex_unlock(&wlp->mutex); + d_fnend(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + return result; +} + +/** + * Activate connection with neighbor by updating EDA cache + * + * @wss: local WSS to which neighbor wants to connect + * @dev_addr: neighbor's address + * @wssid: neighbor's WSSID - must be same as our WSS's WSSID + * @tag: neighbor's WSS tag used to identify frames transmitted by it + * @virt_addr: neighbor's virtual EUI-48 + */ +static +int wlp_wss_activate_connection(struct wlp *wlp, struct wlp_wss *wss, + struct uwb_dev_addr *dev_addr, + struct wlp_uuid *wssid, u8 *tag, + struct uwb_mac_addr *virt_addr) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + char buf[WLP_WSS_UUID_STRSIZE]; + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_fnstart(5, dev, "wlp %p, wss %p, wssid %s, tag %u, virtual " + "%02x:%02x:%02x:%02x:%02x:%02x \n", wlp, wss, buf, *tag, + virt_addr->data[0], virt_addr->data[1], virt_addr->data[2], + virt_addr->data[3], virt_addr->data[4], virt_addr->data[5]); + + if (!memcmp(wssid, &wss->wssid, sizeof(*wssid))) { + d_printf(5, dev, "WSSID from neighbor frame matches local " + "active WSS.\n"); + /* Update EDA cache */ + result = wlp_eda_update_node(&wlp->eda, dev_addr, wss, + (void *) virt_addr->data, *tag, + WLP_WSS_CONNECTED); + if (result < 0) + dev_err(dev, "WLP: Unable to update EDA cache " + "with new connected neighbor information.\n"); + } else { + dev_err(dev, "WLP: Neighbor does not have matching " + "WSSID.\n"); + result = -EINVAL; + } + + d_fnend(5, dev, "wlp %p, wss %p, wssid %s, tag %u, virtual " + "%02x:%02x:%02x:%02x:%02x:%02x, result = %d \n", + wlp, wss, buf, *tag, + virt_addr->data[0], virt_addr->data[1], virt_addr->data[2], + virt_addr->data[3], virt_addr->data[4], virt_addr->data[5], + result); + + return result; +} + +/** + * Connect to WSS neighbor + * + * Use C3/C4 exchange to determine if neighbor has WSS activated and + * retrieve the WSS tag and virtual EUI-48 of the neighbor. + */ +static +int wlp_wss_connect_neighbor(struct wlp *wlp, struct wlp_wss *wss, + struct uwb_dev_addr *dev_addr) +{ + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + struct wlp_uuid wssid; + u8 tag; + struct uwb_mac_addr virt_addr; + DECLARE_COMPLETION_ONSTACK(completion); + struct wlp_session session; + struct wlp_frame_assoc *resp; + struct sk_buff *skb; + + wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid); + d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + mutex_lock(&wlp->mutex); + /* Send C3 association frame */ + result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_C3); + if (result < 0) { + dev_err(dev, "Unable to send C3 frame to neighbor " + "%02x:%02x (%d)\n", dev_addr->data[1], + dev_addr->data[0], result); + goto out; + } + /* Create session, wait for response */ + session.exp_message = WLP_ASSOC_C4; + session.cb = wlp_session_cb; + session.cb_priv = &completion; + session.neighbor_addr = *dev_addr; + BUG_ON(wlp->session != NULL); + wlp->session = &session; + /* Wait for C4/F0 frame */ + result = wait_for_completion_interruptible_timeout(&completion, + WLP_PER_MSG_TIMEOUT * HZ); + if (result == 0) { + dev_err(dev, "Timeout while sending C3 to neighbor " + "%02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + result = -ETIMEDOUT; + goto out; + } + if (result < 0) { + dev_err(dev, "Unable to send C3 to neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + goto out; + } + /* Parse message in session->data: it will be either C4 or F0 */ + skb = session.data; + resp = (void *) skb->data; + d_printf(5, dev, "Received response to C3 frame. \n"); + d_dump(5, dev, skb->data, skb->len > 72 ? 72 : skb->len); + if (resp->type == WLP_ASSOC_F0) { + result = wlp_parse_f0(wlp, skb); + if (result < 0) + dev_err(dev, "WLP: unable to parse incoming F0 " + "frame from neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + result = -EINVAL; + goto error_resp_parse; + } + result = wlp_parse_c3c4_frame(wlp, skb, &wssid, &tag, &virt_addr); + if (result < 0) { + dev_err(dev, "WLP: Unable to parse C4 frame from neighbor.\n"); + goto error_resp_parse; + } + result = wlp_wss_activate_connection(wlp, wss, dev_addr, &wssid, &tag, + &virt_addr); + if (result < 0) { + dev_err(dev, "WLP: Unable to activate connection to " + "neighbor %02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + goto error_resp_parse; + } +error_resp_parse: + kfree_skb(skb); +out: + /* Record that we unsuccessfully tried to connect to this neighbor */ + if (result < 0) + wlp_eda_update_node_state(&wlp->eda, dev_addr, + WLP_WSS_CONNECT_FAILED); + wlp->session = NULL; + mutex_unlock(&wlp->mutex); + d_fnend(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + return result; +} + +/** + * Connect to neighbor with common WSS, send pending frame + * + * This function is scheduled when a frame is destined to a neighbor with + * which we do not have a connection. A copy of the EDA cache entry is + * provided - not the actual cache entry (because it is protected by a + * spinlock). + * + * First determine if neighbor has the same WSS activated, connect if it + * does. The C3/C4 exchange is dual purpose to determine if neighbor has + * WSS activated and proceed with the connection. + * + * The frame that triggered the connection setup is sent after connection + * setup. + * + * network queue is stopped - we need to restart when done + * + */ +static +void wlp_wss_connect_send(struct work_struct *ws) +{ + struct wlp_assoc_conn_ctx *conn_ctx = container_of(ws, + struct wlp_assoc_conn_ctx, + ws); + struct wlp *wlp = conn_ctx->wlp; + struct sk_buff *skb = conn_ctx->skb; + struct wlp_eda_node *eda_entry = &conn_ctx->eda_entry; + struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr; + struct wlp_wss *wss = &wlp->wss; + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + + mutex_lock(&wss->mutex); + wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid); + d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + if (wss->state < WLP_WSS_STATE_ACTIVE) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Attempting to connect with " + "WSS that is not active or connected.\n"); + dev_kfree_skb(skb); + goto out; + } + /* Establish connection - send C3 rcv C4 */ + result = wlp_wss_connect_neighbor(wlp, wss, dev_addr); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to establish connection " + "with neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + dev_kfree_skb(skb); + goto out; + } + /* EDA entry changed, update the local copy being used */ + result = wlp_copy_eda_node(&wlp->eda, dev_addr, eda_entry); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Cannot find EDA entry for " + "neighbor %02x:%02x \n", + dev_addr->data[1], dev_addr->data[0]); + } + result = wlp_wss_prep_hdr(wlp, eda_entry, skb); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to prepare frame header for " + "transmission (neighbor %02x:%02x). \n", + dev_addr->data[1], dev_addr->data[0]); + dev_kfree_skb(skb); + goto out; + } + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, skb, dev_addr); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to transmit frame: %d\n", + result); + if (result == -ENXIO) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb(skb);/*we need to free if tx fails */ + } +out: + kfree(conn_ctx); + BUG_ON(wlp->start_queue == NULL); + wlp->start_queue(wlp); + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "wlp %p, wss %p (wssid %s)\n", wlp, wss, buf); +} + +/** + * Add WLP header to outgoing skb + * + * @eda_entry: pointer to neighbor's entry in the EDA cache + * @_skb: skb containing data destined to the neighbor + */ +int wlp_wss_prep_hdr(struct wlp *wlp, struct wlp_eda_node *eda_entry, + void *_skb) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + unsigned char *eth_addr = eda_entry->eth_addr; + struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr; + struct sk_buff *skb = _skb; + struct wlp_frame_std_abbrv_hdr *std_hdr; + + d_fnstart(6, dev, "wlp %p \n", wlp); + if (eda_entry->state == WLP_WSS_CONNECTED) { + /* Add WLP header */ + BUG_ON(skb_headroom(skb) < sizeof(*std_hdr)); + std_hdr = (void *) __skb_push(skb, sizeof(*std_hdr)); + std_hdr->hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + std_hdr->hdr.type = WLP_FRAME_STANDARD; + std_hdr->tag = eda_entry->wss->tag; + } else { + if (printk_ratelimit()) + dev_err(dev, "WLP: Destination neighbor (Ethernet: " + "%02x:%02x:%02x:%02x:%02x:%02x, Dev: " + "%02x:%02x) is not connected. \n", eth_addr[0], + eth_addr[1], eth_addr[2], eth_addr[3], + eth_addr[4], eth_addr[5], dev_addr->data[1], + dev_addr->data[0]); + result = -EINVAL; + } + d_fnend(6, dev, "wlp %p \n", wlp); + return result; +} + + +/** + * Prepare skb for neighbor: connect if not already and prep WLP header + * + * This function is called in interrupt context, but it needs to sleep. We + * temporarily stop the net queue to establish the WLP connection. + * Setup of the WLP connection and restart of queue is scheduled + * on the default work queue. + * + * run with eda->lock held (spinlock) + */ +int wlp_wss_connect_prep(struct wlp *wlp, struct wlp_eda_node *eda_entry, + void *_skb) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr; + unsigned char *eth_addr = eda_entry->eth_addr; + struct sk_buff *skb = _skb; + struct wlp_assoc_conn_ctx *conn_ctx; + + d_fnstart(5, dev, "wlp %p\n", wlp); + d_printf(5, dev, "To neighbor %02x:%02x with eth " + "%02x:%02x:%02x:%02x:%02x:%02x\n", dev_addr->data[1], + dev_addr->data[0], eth_addr[0], eth_addr[1], eth_addr[2], + eth_addr[3], eth_addr[4], eth_addr[5]); + if (eda_entry->state == WLP_WSS_UNCONNECTED) { + /* We don't want any more packets while we set up connection */ + BUG_ON(wlp->stop_queue == NULL); + wlp->stop_queue(wlp); + conn_ctx = kmalloc(sizeof(*conn_ctx), GFP_ATOMIC); + if (conn_ctx == NULL) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to allocate memory " + "for connection handling.\n"); + result = -ENOMEM; + goto out; + } + conn_ctx->wlp = wlp; + conn_ctx->skb = skb; + conn_ctx->eda_entry = *eda_entry; + INIT_WORK(&conn_ctx->ws, wlp_wss_connect_send); + schedule_work(&conn_ctx->ws); + result = 1; + } else if (eda_entry->state == WLP_WSS_CONNECT_FAILED) { + /* Previous connection attempts failed, don't retry - see + * conditions for connection in WLP 0.99 [7.6.2] */ + if (printk_ratelimit()) + dev_err(dev, "Could not connect to neighbor " + "previously. Not retrying. \n"); + result = -ENONET; + goto out; + } else { /* eda_entry->state == WLP_WSS_CONNECTED */ + d_printf(5, dev, "Neighbor is connected, preparing frame.\n"); + result = wlp_wss_prep_hdr(wlp, eda_entry, skb); + } +out: + d_fnend(5, dev, "wlp %p, result = %d \n", wlp, result); + return result; +} + +/** + * Emulate broadcast: copy skb, send copy to neighbor (connect if not already) + * + * We need to copy skbs in the case where we emulate broadcast through + * unicast. We copy instead of clone because we are modifying the data of + * the frame after copying ... clones share data so we cannot emulate + * broadcast using clones. + * + * run with eda->lock held (spinlock) + */ +int wlp_wss_send_copy(struct wlp *wlp, struct wlp_eda_node *eda_entry, + void *_skb) +{ + int result = -ENOMEM; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct sk_buff *skb = _skb; + struct sk_buff *copy; + struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr; + + d_fnstart(5, dev, "to neighbor %02x:%02x, skb (%p) \n", + dev_addr->data[1], dev_addr->data[0], skb); + copy = skb_copy(skb, GFP_ATOMIC); + if (copy == NULL) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to copy skb for " + "transmission.\n"); + goto out; + } + result = wlp_wss_connect_prep(wlp, eda_entry, copy); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to connect/send skb " + "to neighbor.\n"); + dev_kfree_skb_irq(copy); + goto out; + } else if (result == 1) + /* Frame will be transmitted separately */ + goto out; + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, copy, dev_addr); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to transmit frame: %d\n", + result); + if ((result == -ENXIO) && printk_ratelimit()) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb_irq(copy);/*we need to free if tx fails */ + } +out: + d_fnend(5, dev, "to neighbor %02x:%02x \n", dev_addr->data[1], + dev_addr->data[0]); + return result; +} + + +/** + * Setup WSS + * + * Should be called by network driver after the interface has been given a + * MAC address. + */ +int wlp_wss_setup(struct net_device *net_dev, struct wlp_wss *wss) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + d_fnstart(5, dev, "wss (%p) \n", wss); + mutex_lock(&wss->mutex); + wss->kobj.parent = &net_dev->dev.kobj; + if (!is_valid_ether_addr(net_dev->dev_addr)) { + dev_err(dev, "WLP: Invalid MAC address. Cannot use for" + "virtual.\n"); + result = -EINVAL; + goto out; + } + memcpy(wss->virtual_addr.data, net_dev->dev_addr, + sizeof(wss->virtual_addr.data)); +out: + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "wss (%p) \n", wss); + return result; +} +EXPORT_SYMBOL_GPL(wlp_wss_setup); + +/** + * Remove WSS + * + * Called by client that configured WSS through wlp_wss_setup(). This + * function is called when client no longer needs WSS, eg. client shuts + * down. + * + * We remove the WLP IE from the beacon before initiating local cleanup. + */ +void wlp_wss_remove(struct wlp_wss *wss) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + d_fnstart(5, dev, "wss (%p) \n", wss); + mutex_lock(&wss->mutex); + if (wss->state == WLP_WSS_STATE_ACTIVE) + uwb_rc_ie_rm(wlp->rc, UWB_IE_WLP); + if (wss->state != WLP_WSS_STATE_NONE) { + sysfs_remove_group(&wss->kobj, &wss_attr_group); + kobject_put(&wss->kobj); + } + wss->kobj.parent = NULL; + memset(&wss->virtual_addr, 0, sizeof(wss->virtual_addr)); + /* Cleanup EDA cache */ + wlp_eda_release(&wlp->eda); + wlp_eda_init(&wlp->eda); + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "wss (%p) \n", wss); +} +EXPORT_SYMBOL_GPL(wlp_wss_remove); |