f51448543f
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>
450 lines
13 KiB
C
450 lines
13 KiB
C
/*
|
|
* 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);
|