diff options
author | Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> | 2008-09-17 19:34:07 +0400 |
---|---|---|
committer | David Vrabel <dv02@dv02pc01.europe.root.pri> | 2008-09-17 19:54:24 +0400 |
commit | 0612edfd95ffe92201a2267e9e1b0fc68becf76d (patch) | |
tree | c637b7760d9d7859475264ef2cdf71622428ba45 | |
parent | 183b9b592a622a7719ee38e275fd7ff3aaf74d0d (diff) | |
download | linux-0612edfd95ffe92201a2267e9e1b0fc68becf76d.tar.xz |
uwb: add the UWB stack (radio controller interface)
Add the UWB radio controller interface (URCI) support.
Signed-off-by: David Vrabel <david.vrabel@csr.com>
-rw-r--r-- | drivers/uwb/est.c | 485 | ||||
-rw-r--r-- | drivers/uwb/neh.c | 616 | ||||
-rw-r--r-- | drivers/uwb/reset.c | 362 |
3 files changed, 1463 insertions, 0 deletions
diff --git a/drivers/uwb/est.c b/drivers/uwb/est.c new file mode 100644 index 000000000000..1667afb704d3 --- /dev/null +++ b/drivers/uwb/est.c @@ -0,0 +1,485 @@ +/* + * 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? */ + if (printk_ratelimit()) + 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? */ + if (printk_ratelimit()) + 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) { + if (printk_ratelimit()) + 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; + } + /* FIXME: downgrade to _dbg() */ + if (printk_ratelimit()) + dev_err(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/neh.c b/drivers/uwb/neh.c new file mode 100644 index 000000000000..91b61480e1ee --- /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 if (printk_ratelimit()) + 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/reset.c b/drivers/uwb/reset.c new file mode 100644 index 000000000000..8de856fa7958 --- /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); |