diff options
Diffstat (limited to 'drivers/uwb/wlp')
-rw-r--r-- | drivers/uwb/wlp/driver.c | 43 | ||||
-rw-r--r-- | drivers/uwb/wlp/eda.c | 449 | ||||
-rw-r--r-- | drivers/uwb/wlp/sysfs.c | 709 | ||||
-rw-r--r-- | drivers/uwb/wlp/txrx.c | 374 | ||||
-rw-r--r-- | drivers/uwb/wlp/wlp-internal.h | 228 | ||||
-rw-r--r-- | drivers/uwb/wlp/wlp-lc.c | 585 |
6 files changed, 2388 insertions, 0 deletions
diff --git a/drivers/uwb/wlp/driver.c b/drivers/uwb/wlp/driver.c new file mode 100644 index 00000000000..cb8d699b6a6 --- /dev/null +++ b/drivers/uwb/wlp/driver.c @@ -0,0 +1,43 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Life cycle of WLP substack + * + * FIXME: Docs + */ + +#include <linux/module.h> + +static int __init wlp_subsys_init(void) +{ + return 0; +} +module_init(wlp_subsys_init); + +static void __exit wlp_subsys_exit(void) +{ + return; +} +module_exit(wlp_subsys_exit); + +MODULE_AUTHOR("Reinette Chatre <reinette.chatre@intel.com>"); +MODULE_DESCRIPTION("WiMedia Logical Link Control Protocol (WLP)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/wlp/eda.c b/drivers/uwb/wlp/eda.c new file mode 100644 index 00000000000..cdfe8dfc434 --- /dev/null +++ b/drivers/uwb/wlp/eda.c @@ -0,0 +1,449 @@ +/* + * WUSB Wire Adapter: WLP interface + * Ethernet to device address cache + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We need to be able to map ethernet addresses to device addresses + * and back because there is not explicit relationship between the eth + * addresses used in the ETH frames and the device addresses (no, it + * would not have been simpler to force as ETH address the MBOA MAC + * address...no, not at all :). + * + * A device has one MBOA MAC address and one device address. It is possible + * for a device to have more than one virtual MAC address (although a + * virtual address can be the same as the MBOA MAC address). The device + * address is guaranteed to be unique among the devices in the extended + * beacon group (see ECMA 17.1.1). We thus use the device address as index + * to this cache. We do allow searching based on virtual address as this + * is how Ethernet frames will be addressed. + * + * We need to support virtual EUI-48. Although, right now the virtual + * EUI-48 will always be the same as the MAC SAP address. The EDA cache + * entry thus contains a MAC SAP address as well as the virtual address + * (used to map the network stack address to a neighbor). When we move + * to support more than one virtual MAC on a host then this organization + * will have to change. Perhaps a neighbor has a list of WSSs, each with a + * tag and virtual EUI-48. + * + * On data transmission + * it is used to determine if the neighbor is connected and what WSS it + * belongs to. With this we know what tag to add to the WLP frame. Storing + * the WSS in the EDA cache may be overkill because we only support one + * WSS. Hopefully we will support more than one WSS at some point. + * On data reception it is used to determine the WSS based on + * the tag and address of the transmitting neighbor. + */ + +#define D_LOCAL 5 +#include <linux/netdevice.h> +#include <linux/uwb/debug.h> +#include <linux/etherdevice.h> +#include <linux/wlp.h> +#include "wlp-internal.h" + + +/* FIXME: cache is not purged, only on device close */ + +/* FIXME: does not scale, change to dynamic array */ + +/* + * Initialize the EDA cache + * + * @returns 0 if ok, < 0 errno code on error + * + * Call when the interface is being brought up + * + * NOTE: Keep it as a separate function as the implementation will + * change and be more complex. + */ +void wlp_eda_init(struct wlp_eda *eda) +{ + INIT_LIST_HEAD(&eda->cache); + spin_lock_init(&eda->lock); +} + +/* + * Release the EDA cache + * + * @returns 0 if ok, < 0 errno code on error + * + * Called when the interface is brought down + */ +void wlp_eda_release(struct wlp_eda *eda) +{ + unsigned long flags; + struct wlp_eda_node *itr, *next; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry_safe(itr, next, &eda->cache, list_node) { + list_del(&itr->list_node); + kfree(itr); + } + spin_unlock_irqrestore(&eda->lock, flags); +} + +/* + * Add an address mapping + * + * @returns 0 if ok, < 0 errno code on error + * + * An address mapping is initially created when the neighbor device is seen + * for the first time (it is "onair"). At this time the neighbor is not + * connected or associated with a WSS so we only populate the Ethernet and + * Device address fields. + * + */ +int wlp_eda_create_node(struct wlp_eda *eda, + const unsigned char eth_addr[ETH_ALEN], + const struct uwb_dev_addr *dev_addr) +{ + int result = 0; + struct wlp_eda_node *itr; + unsigned long flags; + + BUG_ON(dev_addr == NULL || eth_addr == NULL); + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + printk(KERN_ERR "EDA cache already contains entry " + "for neighbor %02x:%02x\n", + dev_addr->data[1], dev_addr->data[0]); + result = -EEXIST; + goto out_unlock; + } + } + itr = kzalloc(sizeof(*itr), GFP_ATOMIC); + if (itr != NULL) { + memcpy(itr->eth_addr, eth_addr, sizeof(itr->eth_addr)); + itr->dev_addr = *dev_addr; + list_add(&itr->list_node, &eda->cache); + } else + result = -ENOMEM; +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Remove entry from EDA cache + * + * This is done when the device goes off air. + */ +void wlp_eda_rm_node(struct wlp_eda *eda, const struct uwb_dev_addr *dev_addr) +{ + struct wlp_eda_node *itr, *next; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry_safe(itr, next, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + list_del(&itr->list_node); + kfree(itr); + break; + } + } + spin_unlock_irqrestore(&eda->lock, flags); +} + +/* + * Update an address mapping + * + * @returns 0 if ok, < 0 errno code on error + */ +int wlp_eda_update_node(struct wlp_eda *eda, + const struct uwb_dev_addr *dev_addr, + struct wlp_wss *wss, + const unsigned char virt_addr[ETH_ALEN], + const u8 tag, const enum wlp_wss_connect state) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + /* Found it, update it */ + itr->wss = wss; + memcpy(itr->virt_addr, virt_addr, + sizeof(itr->virt_addr)); + itr->tag = tag; + itr->state = state; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Update only state field of an address mapping + * + * @returns 0 if ok, < 0 errno code on error + */ +int wlp_eda_update_node_state(struct wlp_eda *eda, + const struct uwb_dev_addr *dev_addr, + const enum wlp_wss_connect state) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + /* Found it, update it */ + itr->state = state; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Return contents of EDA cache entry + * + * @dev_addr: index to EDA cache + * @eda_entry: pointer to where contents of EDA cache will be copied + */ +int wlp_copy_eda_node(struct wlp_eda *eda, struct uwb_dev_addr *dev_addr, + struct wlp_eda_node *eda_entry) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + *eda_entry = *itr; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Execute function for every element in the cache + * + * @function: function to execute on element of cache (must be atomic) + * @priv: private data of function + * @returns: result of first function that failed, or last function + * executed if no function failed. + * + * Stop executing when function returns error for any element in cache. + * + * IMPORTANT: We are using a spinlock here: the function executed on each + * element has to be atomic. + */ +int wlp_eda_for_each(struct wlp_eda *eda, wlp_eda_for_each_f function, + void *priv) +{ + int result = 0; + struct wlp *wlp = container_of(eda, struct wlp, eda); + struct wlp_eda_node *entry; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(entry, &eda->cache, list_node) { + result = (*function)(wlp, entry, priv); + if (result < 0) + break; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Execute function for single element in the cache (return dev addr) + * + * @virt_addr: index into EDA cache used to determine which element to + * execute the function on + * @dev_addr: device address of element in cache will be returned using + * @dev_addr + * @function: function to execute on element of cache (must be atomic) + * @priv: private data of function + * @returns: result of function + * + * IMPORTANT: We are using a spinlock here: the function executed on the + * element has to be atomic. + */ +int wlp_eda_for_virtual(struct wlp_eda *eda, + const unsigned char virt_addr[ETH_ALEN], + struct uwb_dev_addr *dev_addr, + wlp_eda_for_each_f function, + void *priv) +{ + int result = 0; + struct wlp *wlp = container_of(eda, struct wlp, eda); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_eda_node *itr; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(itr->virt_addr, virt_addr, + sizeof(itr->virt_addr))) { + d_printf(6, dev, "EDA: looking for " + "%02x:%02x:%02x:%02x:%02x:%02x hit %02x:%02x " + "wss %p tag 0x%02x state %u\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5], + itr->dev_addr.data[1], + itr->dev_addr.data[0], itr->wss, + itr->tag, itr->state); + result = (*function)(wlp, itr, priv); + *dev_addr = itr->dev_addr; + found = 1; + break; + } else + d_printf(6, dev, "EDA: looking for " + "%02x:%02x:%02x:%02x:%02x:%02x " + "against " + "%02x:%02x:%02x:%02x:%02x:%02x miss\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5], + itr->virt_addr[0], itr->virt_addr[1], + itr->virt_addr[2], itr->virt_addr[3], + itr->virt_addr[4], itr->virt_addr[5]); + } + if (!found) { + if (printk_ratelimit()) + dev_err(dev, "EDA: Eth addr %02x:%02x:%02x" + ":%02x:%02x:%02x not found.\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5]); + result = -ENODEV; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +static const char *__wlp_wss_connect_state[] = { "WLP_WSS_UNCONNECTED", + "WLP_WSS_CONNECTED", + "WLP_WSS_CONNECT_FAILED", +}; + +static const char *wlp_wss_connect_state_str(unsigned id) +{ + if (id >= ARRAY_SIZE(__wlp_wss_connect_state)) + return "unknown WSS connection state"; + return __wlp_wss_connect_state[id]; +} + +/* + * View EDA cache from user space + * + * A debugging feature to give user visibility into the EDA cache. Also + * used to display members of WSS to user (called from wlp_wss_members_show()) + */ +ssize_t wlp_eda_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + struct wlp_eda_node *entry; + unsigned long flags; + struct wlp_eda *eda = &wlp->eda; + spin_lock_irqsave(&eda->lock, flags); + result = scnprintf(buf, PAGE_SIZE, "#eth_addr dev_addr wss_ptr " + "tag state virt_addr\n"); + list_for_each_entry(entry, &eda->cache, list_node) { + result += scnprintf(buf + result, PAGE_SIZE - result, + "%02x:%02x:%02x:%02x:%02x:%02x %02x:%02x " + "%p 0x%02x %s " + "%02x:%02x:%02x:%02x:%02x:%02x\n", + entry->eth_addr[0], entry->eth_addr[1], + entry->eth_addr[2], entry->eth_addr[3], + entry->eth_addr[4], entry->eth_addr[5], + entry->dev_addr.data[1], + entry->dev_addr.data[0], entry->wss, + entry->tag, + wlp_wss_connect_state_str(entry->state), + entry->virt_addr[0], entry->virt_addr[1], + entry->virt_addr[2], entry->virt_addr[3], + entry->virt_addr[4], entry->virt_addr[5]); + if (result >= PAGE_SIZE) + break; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} +EXPORT_SYMBOL_GPL(wlp_eda_show); + +/* + * Add new EDA cache entry based on user input in sysfs + * + * Should only be used for debugging. + * + * The WSS is assumed to be the only WSS supported. This needs to be + * redesigned when we support more than one WSS. + */ +ssize_t wlp_eda_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + struct wlp_eda *eda = &wlp->eda; + u8 eth_addr[6]; + struct uwb_dev_addr dev_addr; + u8 tag; + unsigned state; + + result = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx " + "%02hhx:%02hhx %02hhx %u\n", + ð_addr[0], ð_addr[1], + ð_addr[2], ð_addr[3], + ð_addr[4], ð_addr[5], + &dev_addr.data[1], &dev_addr.data[0], &tag, &state); + switch (result) { + case 6: /* no dev addr specified -- remove entry NOT IMPLEMENTED */ + /*result = wlp_eda_rm(eda, eth_addr, &dev_addr);*/ + result = -ENOSYS; + break; + case 10: + state = state >= 1 ? 1 : 0; + result = wlp_eda_create_node(eda, eth_addr, &dev_addr); + if (result < 0 && result != -EEXIST) + goto error; + /* Set virtual addr to be same as MAC */ + result = wlp_eda_update_node(eda, &dev_addr, &wlp->wss, + eth_addr, tag, state); + if (result < 0) + goto error; + break; + default: /* bad format */ + result = -EINVAL; + } +error: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_eda_store); diff --git a/drivers/uwb/wlp/sysfs.c b/drivers/uwb/wlp/sysfs.c new file mode 100644 index 00000000000..1bb9b1f97d4 --- /dev/null +++ b/drivers/uwb/wlp/sysfs.c @@ -0,0 +1,709 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * sysfs functions + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre <reinette.chatre@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: Docs + * + */ + +#include <linux/wlp.h> +#include "wlp-internal.h" + +static +size_t wlp_wss_wssid_e_print(char *buf, size_t bufsize, + struct wlp_wssid_e *wssid_e) +{ + size_t used = 0; + used += scnprintf(buf, bufsize, " WSS: "); + used += wlp_wss_uuid_print(buf + used, bufsize - used, + &wssid_e->wssid); + + if (wssid_e->info != NULL) { + used += scnprintf(buf + used, bufsize - used, " "); + used += uwb_mac_addr_print(buf + used, bufsize - used, + &wssid_e->info->bcast); + used += scnprintf(buf + used, bufsize - used, " %u %u %s\n", + wssid_e->info->accept_enroll, + wssid_e->info->sec_status, + wssid_e->info->name); + } + return used; +} + +/** + * Print out information learned from neighbor discovery + * + * Some fields being printed may not be included in the device discovery + * information (it is not mandatory). We are thus careful how the + * information is printed to ensure it is clear to the user what field is + * being referenced. + * The information being printed is for one time use - temporary storage is + * cleaned after it is printed. + * + * Ideally sysfs output should be on one line. The information printed here + * contain a few strings so it will be hard to parse if they are all + * printed on the same line - without agreeing on a standard field + * separator. + */ +static +ssize_t wlp_wss_neighborhood_print_remove(struct wlp *wlp, char *buf, + size_t bufsize) +{ + size_t used = 0; + struct wlp_neighbor_e *neighb; + struct wlp_wssid_e *wssid_e; + + mutex_lock(&wlp->nbmutex); + used = scnprintf(buf, bufsize, "#Neighbor information\n" + "#uuid dev_addr\n" + "# Device Name:\n# Model Name:\n# Manufacturer:\n" + "# Model Nr:\n# Serial:\n" + "# Pri Dev type: CategoryID OUI OUISubdiv " + "SubcategoryID\n" + "# WSS: WSSID WSS_name accept_enroll sec_status " + "bcast\n" + "# WSS: WSSID WSS_name accept_enroll sec_status " + "bcast\n\n"); + list_for_each_entry(neighb, &wlp->neighbors, node) { + if (bufsize - used <= 0) + goto out; + used += wlp_wss_uuid_print(buf + used, bufsize - used, + &neighb->uuid); + buf[used++] = ' '; + used += uwb_dev_addr_print(buf + used, bufsize - used, + &neighb->uwb_dev->dev_addr); + if (neighb->info != NULL) + used += scnprintf(buf + used, bufsize - used, + "\n Device Name: %s\n" + " Model Name: %s\n" + " Manufacturer:%s \n" + " Model Nr: %s\n" + " Serial: %s\n" + " Pri Dev type: " + "%u %02x:%02x:%02x %u %u\n", + neighb->info->name, + neighb->info->model_name, + neighb->info->manufacturer, + neighb->info->model_nr, + neighb->info->serial, + neighb->info->prim_dev_type.category, + neighb->info->prim_dev_type.OUI[0], + neighb->info->prim_dev_type.OUI[1], + neighb->info->prim_dev_type.OUI[2], + neighb->info->prim_dev_type.OUIsubdiv, + neighb->info->prim_dev_type.subID); + list_for_each_entry(wssid_e, &neighb->wssid, node) { + used += wlp_wss_wssid_e_print(buf + used, + bufsize - used, + wssid_e); + } + buf[used++] = '\n'; + wlp_remove_neighbor_tmp_info(neighb); + } + + +out: + mutex_unlock(&wlp->nbmutex); + return used; +} + + +/** + * Show properties of all WSS in neighborhood. + * + * Will trigger a complete discovery of WSS activated by this device and + * its neighbors. + */ +ssize_t wlp_neighborhood_show(struct wlp *wlp, char *buf) +{ + wlp_discover(wlp); + return wlp_wss_neighborhood_print_remove(wlp, buf, PAGE_SIZE); +} +EXPORT_SYMBOL_GPL(wlp_neighborhood_show); + +static +ssize_t __wlp_wss_properties_show(struct wlp_wss *wss, char *buf, + size_t bufsize) +{ + ssize_t result; + + result = wlp_wss_uuid_print(buf, bufsize, &wss->wssid); + result += scnprintf(buf + result, bufsize - result, " "); + result += uwb_mac_addr_print(buf + result, bufsize - result, + &wss->bcast); + result += scnprintf(buf + result, bufsize - result, + " 0x%02x %u ", wss->hash, wss->secure_status); + result += wlp_wss_key_print(buf + result, bufsize - result, + wss->master_key); + result += scnprintf(buf + result, bufsize - result, " 0x%02x ", + wss->tag); + result += uwb_mac_addr_print(buf + result, bufsize - result, + &wss->virtual_addr); + result += scnprintf(buf + result, bufsize - result, " %s", wss->name); + result += scnprintf(buf + result, bufsize - result, + "\n\n#WSSID\n#WSS broadcast address\n" + "#WSS hash\n#WSS secure status\n" + "#WSS master key\n#WSS local tag\n" + "#WSS local virtual EUI-48\n#WSS name\n"); + return result; +} + +/** + * Show which WSS is activated. + */ +ssize_t wlp_wss_activate_show(struct wlp_wss *wss, char *buf) +{ + int result = 0; + + if (mutex_lock_interruptible(&wss->mutex)) + goto out; + if (wss->state >= WLP_WSS_STATE_ACTIVE) + result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE); + else + result = scnprintf(buf, PAGE_SIZE, "No local WSS active.\n"); + result += scnprintf(buf + result, PAGE_SIZE - result, + "\n\n" + "# echo WSSID SECURE_STATUS ACCEPT_ENROLLMENT " + "NAME #create new WSS\n" + "# echo WSSID [DEV ADDR] #enroll in and activate " + "existing WSS, can request registrar\n" + "#\n" + "# WSSID is a 16 byte hex array. Eg. 12 A3 3B ... \n" + "# SECURE_STATUS 0 - unsecure, 1 - secure (default)\n" + "# ACCEPT_ENROLLMENT 0 - no, 1 - yes (default)\n" + "# NAME is the text string identifying the WSS\n" + "# DEV ADDR is the device address of neighbor " + "that should be registrar. Eg. 32:AB\n"); + + mutex_unlock(&wss->mutex); +out: + return result; + +} +EXPORT_SYMBOL_GPL(wlp_wss_activate_show); + +/** + * Create/activate a new WSS or enroll/activate in neighboring WSS + * + * The user can provide the WSSID of a WSS in which it wants to enroll. + * Only the WSSID is necessary if the WSS have been discovered before. If + * the WSS has not been discovered before, or the user wants to use a + * particular neighbor as its registrar, then the user can also provide a + * device address or the neighbor that will be used as registrar. + * + * A new WSS is created when the user provides a WSSID, secure status, and + * WSS name. + */ +ssize_t wlp_wss_activate_store(struct wlp_wss *wss, + const char *buf, size_t size) +{ + ssize_t result = -EINVAL; + struct wlp_uuid wssid; + struct uwb_dev_addr dev; + struct uwb_dev_addr bcast = {.data = {0xff, 0xff} }; + char name[65]; + unsigned sec_status, accept; + memset(name, 0, sizeof(name)); + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx:%02hhx", + &wssid.data[0] , &wssid.data[1], + &wssid.data[2] , &wssid.data[3], + &wssid.data[4] , &wssid.data[5], + &wssid.data[6] , &wssid.data[7], + &wssid.data[8] , &wssid.data[9], + &wssid.data[10], &wssid.data[11], + &wssid.data[12], &wssid.data[13], + &wssid.data[14], &wssid.data[15], + &dev.data[1], &dev.data[0]); + if (result == 16 || result == 17) { + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%u %u %64c", + &wssid.data[0] , &wssid.data[1], + &wssid.data[2] , &wssid.data[3], + &wssid.data[4] , &wssid.data[5], + &wssid.data[6] , &wssid.data[7], + &wssid.data[8] , &wssid.data[9], + &wssid.data[10], &wssid.data[11], + &wssid.data[12], &wssid.data[13], + &wssid.data[14], &wssid.data[15], + &sec_status, &accept, name); + if (result == 16) + result = wlp_wss_enroll_activate(wss, &wssid, &bcast); + else if (result == 19) { + sec_status = sec_status == 0 ? 0 : 1; + accept = accept == 0 ? 0 : 1; + /* We read name using %c, so the newline needs to be + * removed */ + if (strlen(name) != sizeof(name) - 1) + name[strlen(name) - 1] = '\0'; + result = wlp_wss_create_activate(wss, &wssid, name, + sec_status, accept); + } else + result = -EINVAL; + } else if (result == 18) + result = wlp_wss_enroll_activate(wss, &wssid, &dev); + else + result = -EINVAL; + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_wss_activate_store); + +/** + * Show the UUID of this host + */ +ssize_t wlp_uuid_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + + mutex_lock(&wlp->mutex); + result = wlp_wss_uuid_print(buf, PAGE_SIZE, &wlp->uuid); + buf[result++] = '\n'; + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_uuid_show); + +/** + * Store a new UUID for this host + * + * According to the spec this should be encoded as an octet string in the + * order the octets are shown in string representation in RFC 4122 (WLP + * 0.99 [Table 6]) + * + * We do not check value provided by user. + */ +ssize_t wlp_uuid_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + struct wlp_uuid uuid; + + mutex_lock(&wlp->mutex); + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx ", + &uuid.data[0] , &uuid.data[1], + &uuid.data[2] , &uuid.data[3], + &uuid.data[4] , &uuid.data[5], + &uuid.data[6] , &uuid.data[7], + &uuid.data[8] , &uuid.data[9], + &uuid.data[10], &uuid.data[11], + &uuid.data[12], &uuid.data[13], + &uuid.data[14], &uuid.data[15]); + if (result != 16) { + result = -EINVAL; + goto error; + } + wlp->uuid = uuid; +error: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_uuid_store); + +/** + * Show contents of members of device information structure + */ +#define wlp_dev_info_show(type) \ +ssize_t wlp_dev_##type##_show(struct wlp *wlp, char *buf) \ +{ \ + ssize_t result = 0; \ + mutex_lock(&wlp->mutex); \ + if (wlp->dev_info == NULL) { \ + result = __wlp_setup_device_info(wlp); \ + if (result < 0) \ + goto out; \ + } \ + result = scnprintf(buf, PAGE_SIZE, "%s\n", wlp->dev_info->type);\ +out: \ + mutex_unlock(&wlp->mutex); \ + return result; \ +} \ +EXPORT_SYMBOL_GPL(wlp_dev_##type##_show); + +wlp_dev_info_show(name) +wlp_dev_info_show(model_name) +wlp_dev_info_show(model_nr) +wlp_dev_info_show(manufacturer) +wlp_dev_info_show(serial) + +/** + * Store contents of members of device information structure + */ +#define wlp_dev_info_store(type, len) \ +ssize_t wlp_dev_##type##_store(struct wlp *wlp, const char *buf, size_t size)\ +{ \ + ssize_t result; \ + char format[10]; \ + mutex_lock(&wlp->mutex); \ + if (wlp->dev_info == NULL) { \ + result = __wlp_alloc_device_info(wlp); \ + if (result < 0) \ + goto out; \ + } \ + memset(wlp->dev_info->type, 0, sizeof(wlp->dev_info->type)); \ + sprintf(format, "%%%uc", len); \ + result = sscanf(buf, format, wlp->dev_info->type); \ +out: \ + mutex_unlock(&wlp->mutex); \ + return result < 0 ? result : size; \ +} \ +EXPORT_SYMBOL_GPL(wlp_dev_##type##_store); + +wlp_dev_info_store(name, 32) +wlp_dev_info_store(manufacturer, 64) +wlp_dev_info_store(model_name, 32) +wlp_dev_info_store(model_nr, 32) +wlp_dev_info_store(serial, 32) + +static +const char *__wlp_dev_category[] = { + [WLP_DEV_CAT_COMPUTER] = "Computer", + [WLP_DEV_CAT_INPUT] = "Input device", + [WLP_DEV_CAT_PRINT_SCAN_FAX_COPIER] = "Printer, scanner, FAX, or " + "Copier", + [WLP_DEV_CAT_CAMERA] = "Camera", + [WLP_DEV_CAT_STORAGE] = "Storage Network", + [WLP_DEV_CAT_INFRASTRUCTURE] = "Infrastructure", + [WLP_DEV_CAT_DISPLAY] = "Display", + [WLP_DEV_CAT_MULTIM] = "Multimedia device", + [WLP_DEV_CAT_GAMING] = "Gaming device", + [WLP_DEV_CAT_TELEPHONE] = "Telephone", + [WLP_DEV_CAT_OTHER] = "Other", +}; + +static +const char *wlp_dev_category_str(unsigned cat) +{ + if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE) + || cat == WLP_DEV_CAT_OTHER) + return __wlp_dev_category[cat]; + return "unknown category"; +} + +ssize_t wlp_dev_prim_category_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%s\n", + wlp_dev_category_str(wlp->dev_info->prim_dev_type.category)); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_category_show); + +ssize_t wlp_dev_prim_category_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + u16 cat; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%hu", &cat); + if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE) + || cat == WLP_DEV_CAT_OTHER) + wlp->dev_info->prim_dev_type.category = cat; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_category_store); + +ssize_t wlp_dev_prim_OUI_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%02x:%02x:%02x\n", + wlp->dev_info->prim_dev_type.OUI[0], + wlp->dev_info->prim_dev_type.OUI[1], + wlp->dev_info->prim_dev_type.OUI[2]); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_show); + +ssize_t wlp_dev_prim_OUI_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + u8 OUI[3]; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%hhx:%hhx:%hhx", + &OUI[0], &OUI[1], &OUI[2]); + if (result != 3) { + result = -EINVAL; + goto out; + } else + memcpy(wlp->dev_info->prim_dev_type.OUI, OUI, sizeof(OUI)); +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_store); + + +ssize_t wlp_dev_prim_OUI_sub_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%u\n", + wlp->dev_info->prim_dev_type.OUIsubdiv); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_show); + +ssize_t wlp_dev_prim_OUI_sub_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + unsigned sub; + u8 max_sub = ~0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%u", &sub); + if (sub <= max_sub) + wlp->dev_info->prim_dev_type.OUIsubdiv = sub; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_store); + +ssize_t wlp_dev_prim_subcat_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%u\n", + wlp->dev_info->prim_dev_type.subID); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_show); + +ssize_t wlp_dev_prim_subcat_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + unsigned sub; + __le16 max_sub = ~0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%u", &sub); + if (sub <= max_sub) + wlp->dev_info->prim_dev_type.subID = sub; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_store); + +/** + * Subsystem implementation for interaction with individual WSS via sysfs + * + * Followed instructions for subsystem in Documentation/filesystems/sysfs.txt |