summaryrefslogtreecommitdiff
path: root/drivers/uwb/wlp/wlp-lc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/uwb/wlp/wlp-lc.c')
-rw-r--r--drivers/uwb/wlp/wlp-lc.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/drivers/uwb/wlp/wlp-lc.c b/drivers/uwb/wlp/wlp-lc.c
new file mode 100644
index 000000000000..0799402e73fb
--- /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);