uwb: add the WiMedia LLC Protocol stack
authorReinette Chatre <reinette.chatre@intel.com>
Wed, 17 Sep 2008 15:34:16 +0000 (16:34 +0100)
committerDavid Vrabel <dv02@dv02pc01.europe.root.pri>
Wed, 17 Sep 2008 15:54:27 +0000 (16:54 +0100)
Add the generic code for the WiMedia Logical Link Control Protocol (WLP).

This has been split into several patches for easier review.

core (this patch):
  - everything else

messages:
  - WLP message construction/decode

wss:
  - Wireless Service Set support

build-system:
  - Kconfig and Kbuild files

Signed-off-by: David Vrabel <david.vrabel@csr.com>
drivers/uwb/wlp/driver.c [new file with mode: 0644]
drivers/uwb/wlp/eda.c [new file with mode: 0644]
drivers/uwb/wlp/sysfs.c [new file with mode: 0644]
drivers/uwb/wlp/txrx.c [new file with mode: 0644]
drivers/uwb/wlp/wlp-internal.h [new file with mode: 0644]
drivers/uwb/wlp/wlp-lc.c [new file with mode: 0644]

diff --git a/drivers/uwb/wlp/driver.c b/drivers/uwb/wlp/driver.c
new file mode 100644 (file)
index 0000000..cb8d699
--- /dev/null
@@ -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 (file)
index 0000000..cdfe8df
--- /dev/null
@@ -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",
+                       &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], &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/sysfs.c b/drivers/uwb/wlp/sysfs.c
new file mode 100644 (file)
index 0000000..1bb9b1f
--- /dev/null
@@ -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 (file)
index 0000000..c701bd1
--- /dev/null
@@ -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 (file)
index 0000000..1c94fab
--- /dev/null
@@ -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 (file)
index 0000000..0799402
--- /dev/null
@@ -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);