| /* |
| * 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); |