1
linux/drivers/uwb/wlp/wss-lc.c
Reinette Chatre 2f19204480 uwb: add WiMedia LLC Protocol stack (WSS)
Add the Wireless Service Set (WSS) code.

Signed-off-by: David Vrabel <david.vrabel@csr.com>
2008-09-17 16:54:27 +01:00

1056 lines
33 KiB
C

/*
* 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.
*
*
* Implementation of the WLP association protocol.
*
* FIXME: Docs
*
* A UWB network interface will configure a WSS through wlp_wss_setup() after
* the interface has been assigned a MAC address, typically after
* "ifconfig" has been called. When the interface goes down it should call
* wlp_wss_remove().
*
* When the WSS is ready for use the user interacts via sysfs to create,
* discover, and activate WSS.
*
* wlp_wss_enroll_activate()
*
* wlp_wss_create_activate()
* wlp_wss_set_wssid_hash()
* wlp_wss_comp_wssid_hash()
* wlp_wss_sel_bcast_addr()
* wlp_wss_sysfs_add()
*
* Called when no more references to WSS exist:
* wlp_wss_release()
* wlp_wss_reset()
*/
#include <linux/etherdevice.h> /* for is_valid_ether_addr */
#include <linux/skbuff.h>
#include <linux/wlp.h>
#define D_LOCAL 5
#include <linux/uwb/debug.h>
#include "wlp-internal.h"
size_t wlp_wss_key_print(char *buf, size_t bufsize, u8 *key)
{
size_t result;
result = scnprintf(buf, bufsize,
"%02x %02x %02x %02x %02x %02x "
"%02x %02x %02x %02x %02x %02x "
"%02x %02x %02x %02x",
key[0], key[1], key[2], key[3],
key[4], key[5], key[6], key[7],
key[8], key[9], key[10], key[11],
key[12], key[13], key[14], key[15]);
return result;
}
/**
* Compute WSSID hash
* WLP Draft 0.99 [7.2.1]
*
* The WSSID hash for a WSSID is the result of an octet-wise exclusive-OR
* of all octets in the WSSID.
*/
static
u8 wlp_wss_comp_wssid_hash(struct wlp_uuid *wssid)
{
return 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];
}
/**
* Select a multicast EUI-48 for the WSS broadcast address.
* WLP Draft 0.99 [7.2.1]
*
* Selected based on the WiMedia Alliance OUI, 00-13-88, within the WLP
* range, [01-13-88-00-01-00, 01-13-88-00-01-FF] inclusive.
*
* This address is currently hardcoded.
* FIXME?
*/
static
struct uwb_mac_addr wlp_wss_sel_bcast_addr(struct wlp_wss *wss)
{
struct uwb_mac_addr bcast = {
.data = { 0x01, 0x13, 0x88, 0x00, 0x01, 0x00 }
};
return bcast;
}
/**
* Clear the contents of the WSS structure - all except kobj, mutex, virtual
*
* We do not want to reinitialize - the internal kobj should not change as
* it still points to the parent received during setup. The mutex should
* remain also. We thus just reset values individually.
* The virutal address assigned to WSS will remain the same for the
* lifetime of the WSS. We only reset the fields that can change during its
* lifetime.
*/
void wlp_wss_reset(struct wlp_wss *wss)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
d_fnstart(5, dev, "wss (%p) \n", wss);
memset(&wss->wssid, 0, sizeof(wss->wssid));
wss->hash = 0;
memset(&wss->name[0], 0, sizeof(wss->name));
memset(&wss->bcast, 0, sizeof(wss->bcast));
wss->secure_status = WLP_WSS_UNSECURE;
memset(&wss->master_key[0], 0, sizeof(wss->master_key));
wss->tag = 0;
wss->state = WLP_WSS_STATE_NONE;
d_fnend(5, dev, "wss (%p) \n", wss);
}
/**
* Create sysfs infrastructure for WSS
*
* The WSS is configured to have the interface as parent (see wlp_wss_setup())
* a new sysfs directory that includes wssid as its name is created in the
* interface's sysfs directory. The group of files interacting with WSS are
* created also.
*/
static
int wlp_wss_sysfs_add(struct wlp_wss *wss, char *wssid_str)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
int result;
d_fnstart(5, dev, "wss (%p), wssid: %s\n", wss, wssid_str);
result = kobject_set_name(&wss->kobj, "wss-%s", wssid_str);
if (result < 0)
return result;
wss->kobj.ktype = &wss_ktype;
result = kobject_init_and_add(&wss->kobj,
&wss_ktype, wss->kobj.parent, "wlp");
if (result < 0) {
dev_err(dev, "WLP: Cannot register WSS kobject.\n");
goto error_kobject_register;
}
result = sysfs_create_group(&wss->kobj, &wss_attr_group);
if (result < 0) {
dev_err(dev, "WLP: Cannot register WSS attributes: %d\n",
result);
goto error_sysfs_create_group;
}
d_fnend(5, dev, "Completed. result = %d \n", result);
return 0;
error_sysfs_create_group:
kobject_put(&wss->kobj); /* will free name if needed */
return result;
error_kobject_register:
kfree(wss->kobj.name);
wss->kobj.name = NULL;
wss->kobj.ktype = NULL;
return result;
}
/**
* Release WSS
*
* No more references exist to this WSS. We should undo everything that was
* done in wlp_wss_create_activate() except removing the group. The group
* is not removed because an object can be unregistered before the group is
* created. We also undo any additional operations on the WSS after this
* (addition of members).
*
* If memory was allocated for the kobject's name then it will
* be freed by the kobject system during this time.
*
* The EDA cache is removed and reinitilized when the WSS is removed. We
* thus loose knowledge of members of this WSS at that time and need not do
* it here.
*/
void wlp_wss_release(struct kobject *kobj)
{
struct wlp_wss *wss = container_of(kobj, struct wlp_wss, kobj);
wlp_wss_reset(wss);
}
/**
* Enroll into a WSS using provided neighbor as registrar
*
* First search the neighborhood information to learn which neighbor is
* referred to, next proceed with enrollment.
*
* &wss->mutex is held
*/
static
int wlp_wss_enroll_target(struct wlp_wss *wss, struct wlp_uuid *wssid,
struct uwb_dev_addr *dest)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
struct wlp_neighbor_e *neighbor;
char buf[WLP_WSS_UUID_STRSIZE];
int result = -ENXIO;
struct uwb_dev_addr *dev_addr;
wlp_wss_uuid_print(buf, sizeof(buf), wssid);
d_fnstart(5, dev, "wss %p, wssid %s, registrar %02x:%02x \n",
wss, buf, dest->data[1], dest->data[0]);
mutex_lock(&wlp->nbmutex);
list_for_each_entry(neighbor, &wlp->neighbors, node) {
dev_addr = &neighbor->uwb_dev->dev_addr;
if (!memcmp(dest, dev_addr, sizeof(*dest))) {
d_printf(5, dev, "Neighbor %02x:%02x is valid, "
"enrolling. \n",
dev_addr->data[1], dev_addr->data[0]);
result = wlp_enroll_neighbor(wlp, neighbor, wss,
wssid);
break;
}
}
if (result == -ENXIO)
dev_err(dev, "WLP: Cannot find neighbor %02x:%02x. \n",
dest->data[1], dest->data[0]);
mutex_unlock(&wlp->nbmutex);
d_fnend(5, dev, "wss %p, wssid %s, registrar %02x:%02x, result %d \n",
wss, buf, dest->data[1], dest->data[0], result);
return result;
}
/**
* Enroll into a WSS previously discovered
*
* User provides WSSID of WSS, search for neighbor that has this WSS
* activated and attempt to enroll.
*
* &wss->mutex is held
*/
static
int wlp_wss_enroll_discovered(struct wlp_wss *wss, struct wlp_uuid *wssid)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
struct wlp_neighbor_e *neighbor;
struct wlp_wssid_e *wssid_e;
char buf[WLP_WSS_UUID_STRSIZE];
int result = -ENXIO;
wlp_wss_uuid_print(buf, sizeof(buf), wssid);
d_fnstart(5, dev, "wss %p, wssid %s \n", wss, buf);
mutex_lock(&wlp->nbmutex);
list_for_each_entry(neighbor, &wlp->neighbors, node) {
list_for_each_entry(wssid_e, &neighbor->wssid, node) {
if (!memcmp(wssid, &wssid_e->wssid, sizeof(*wssid))) {
d_printf(5, dev, "Found WSSID %s in neighbor "
"%02x:%02x cache. \n", buf,
neighbor->uwb_dev->dev_addr.data[1],
neighbor->uwb_dev->dev_addr.data[0]);
result = wlp_enroll_neighbor(wlp, neighbor,
wss, wssid);
if (result == 0) /* enrollment success */
goto out;
break;
}
}
}
out:
if (result == -ENXIO)
dev_err(dev, "WLP: Cannot find WSSID %s in cache. \n", buf);
mutex_unlock(&wlp->nbmutex);
d_fnend(5, dev, "wss %p, wssid %s, result %d \n", wss, buf, result);
return result;
}
/**
* Enroll into WSS with provided WSSID, registrar may be provided
*
* @wss: out WSS that will be enrolled
* @wssid: wssid of neighboring WSS that we want to enroll in
* @devaddr: registrar can be specified, will be broadcast (ff:ff) if any
* neighbor can be used as registrar.
*
* &wss->mutex is held
*/
static
int wlp_wss_enroll(struct wlp_wss *wss, struct wlp_uuid *wssid,
struct uwb_dev_addr *devaddr)
{
int result;
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
char buf[WLP_WSS_UUID_STRSIZE];
struct uwb_dev_addr bcast = {.data = {0xff, 0xff} };
wlp_wss_uuid_print(buf, sizeof(buf), wssid);
if (wss->state != WLP_WSS_STATE_NONE) {
dev_err(dev, "WLP: Already enrolled in WSS %s.\n", buf);
result = -EEXIST;
goto error;
}
if (!memcmp(&bcast, devaddr, sizeof(bcast))) {
d_printf(5, dev, "Request to enroll in discovered WSS "
"with WSSID %s \n", buf);
result = wlp_wss_enroll_discovered(wss, wssid);
} else {
d_printf(5, dev, "Request to enroll in WSSID %s with "
"registrar %02x:%02x\n", buf, devaddr->data[1],
devaddr->data[0]);
result = wlp_wss_enroll_target(wss, wssid, devaddr);
}
if (result < 0) {
dev_err(dev, "WLP: Unable to enroll into WSS %s, result %d \n",
buf, result);
goto error;
}
d_printf(2, dev, "Successfully enrolled into WSS %s \n", buf);
result = wlp_wss_sysfs_add(wss, buf);
if (result < 0) {
dev_err(dev, "WLP: Unable to set up sysfs for WSS kobject.\n");
wlp_wss_reset(wss);
}
error:
return result;
}
/**
* Activate given WSS
*
* Prior to activation a WSS must be enrolled. To activate a WSS a device
* includes the WSS hash in the WLP IE in its beacon in each superframe.
* WLP 0.99 [7.2.5].
*
* The WSS tag is also computed at this time. We only support one activated
* WSS so we can use the hash as a tag - there will never be a conflict.
*
* We currently only support one activated WSS so only one WSS hash is
* included in the WLP IE.
*/
static
int wlp_wss_activate(struct wlp_wss *wss)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
struct uwb_rc *uwb_rc = wlp->rc;
int result;
struct {
struct wlp_ie wlp_ie;
u8 hash; /* only include one hash */
} ie_data;
d_fnstart(5, dev, "Activating WSS %p. \n", wss);
BUG_ON(wss->state != WLP_WSS_STATE_ENROLLED);
wss->hash = wlp_wss_comp_wssid_hash(&wss->wssid);
wss->tag = wss->hash;
memset(&ie_data, 0, sizeof(ie_data));
ie_data.wlp_ie.hdr.element_id = UWB_IE_WLP;
ie_data.wlp_ie.hdr.length = sizeof(ie_data) - sizeof(struct uwb_ie_hdr);
wlp_ie_set_hash_length(&ie_data.wlp_ie, sizeof(ie_data.hash));
ie_data.hash = wss->hash;
result = uwb_rc_ie_add(uwb_rc, &ie_data.wlp_ie.hdr,
sizeof(ie_data));
if (result < 0) {
dev_err(dev, "WLP: Unable to add WLP IE to beacon. "
"result = %d.\n", result);
goto error_wlp_ie;
}
wss->state = WLP_WSS_STATE_ACTIVE;
result = 0;
error_wlp_ie:
d_fnend(5, dev, "Activating WSS %p, result = %d \n", wss, result);
return result;
}
/**
* Enroll in and activate WSS identified by provided WSSID
*
* The neighborhood cache should contain a list of all neighbors and the
* WSS they have activated. Based on that cache we search which neighbor we
* can perform the association process with. The user also has option to
* specify which neighbor it prefers as registrar.
* Successful enrollment is followed by activation.
* Successful activation will create the sysfs directory containing
* specific information regarding this WSS.
*/
int wlp_wss_enroll_activate(struct wlp_wss *wss, struct wlp_uuid *wssid,
struct uwb_dev_addr *devaddr)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
int result = 0;
char buf[WLP_WSS_UUID_STRSIZE];
d_fnstart(5, dev, "Enrollment and activation requested. \n");
mutex_lock(&wss->mutex);
result = wlp_wss_enroll(wss, wssid, devaddr);
if (result < 0) {
wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid);
dev_err(dev, "WLP: Enrollment into WSS %s failed.\n", buf);
goto error_enroll;
}
result = wlp_wss_activate(wss);
if (result < 0) {
dev_err(dev, "WLP: Unable to activate WSS. Undoing enrollment "
"result = %d \n", result);
/* Undo enrollment */
wlp_wss_reset(wss);
goto error_activate;
}
error_activate:
error_enroll:
mutex_unlock(&wss->mutex);
d_fnend(5, dev, "Completed. result = %d \n", result);
return result;
}
/**
* Create, enroll, and activate a new WSS
*
* @wssid: new wssid provided by user
* @name: WSS name requested by used.
* @sec_status: security status requested by user
*
* A user requested the creation of a new WSS. All operations are done
* locally. The new WSS will be stored locally, the hash will be included
* in the WLP IE, and the sysfs infrastructure for this WSS will be
* created.
*/
int wlp_wss_create_activate(struct wlp_wss *wss, struct wlp_uuid *wssid,
char *name, unsigned sec_status, unsigned accept)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
int result = 0;
char buf[WLP_WSS_UUID_STRSIZE];
d_fnstart(5, dev, "Request to create new WSS.\n");
result = wlp_wss_uuid_print(buf, sizeof(buf), wssid);
d_printf(5, dev, "Request to create WSS: WSSID=%s, name=%s, "
"sec_status=%u, accepting enrollment=%u \n",
buf, name, sec_status, accept);
if (!mutex_trylock(&wss->mutex)) {
dev_err(dev, "WLP: WLP association session in progress.\n");
return -EBUSY;
}
if (wss->state != WLP_WSS_STATE_NONE) {
dev_err(dev, "WLP: WSS already exists. Not creating new.\n");
result = -EEXIST;
goto out;
}
if (wss->kobj.parent == NULL) {
dev_err(dev, "WLP: WSS parent not ready. Is network interface "
"up?\n");
result = -ENXIO;
goto out;
}
if (sec_status == WLP_WSS_SECURE) {
dev_err(dev, "WLP: FIXME Creation of secure WSS not "
"supported yet.\n");
result = -EINVAL;
goto out;
}
wss->wssid = *wssid;
memcpy(wss->name, name, sizeof(wss->name));
wss->bcast = wlp_wss_sel_bcast_addr(wss);
wss->secure_status = sec_status;
wss->accept_enroll = accept;
/*wss->virtual_addr is initialized in call to wlp_wss_setup*/
/* sysfs infrastructure */
result = wlp_wss_sysfs_add(wss, buf);
if (result < 0) {
dev_err(dev, "Cannot set up sysfs for WSS kobject.\n");
wlp_wss_reset(wss);
goto out;
} else
result = 0;
wss->state = WLP_WSS_STATE_ENROLLED;
result = wlp_wss_activate(wss);
if (result < 0) {
dev_err(dev, "WLP: Unable to activate WSS. Undoing "
"enrollment\n");
wlp_wss_reset(wss);
goto out;
}
result = 0;
out:
mutex_unlock(&wss->mutex);
d_fnend(5, dev, "Completed. result = %d \n", result);
return result;
}
/**
* Determine if neighbor has WSS activated
*
* @returns: 1 if neighbor has WSS activated, zero otherwise
*
* This can be done in two ways:
* - send a C1 frame, parse C2/F0 response
* - examine the WLP IE sent by the neighbor
*
* The WLP IE is not fully supported in hardware so we use the C1/C2 frame
* exchange to determine if a WSS is activated. Using the WLP IE should be
* faster and should be used when it becomes possible.
*/
int wlp_wss_is_active(struct wlp *wlp, struct wlp_wss *wss,
struct uwb_dev_addr *dev_addr)
{
int result = 0;
struct device *dev = &wlp->rc->uwb_dev.dev;
char buf[WLP_WSS_UUID_STRSIZE];
DECLARE_COMPLETION_ONSTACK(completion);
struct wlp_session session;
struct sk_buff *skb;
struct wlp_frame_assoc *resp;
struct wlp_uuid wssid;
wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid);
d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n",
wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]);
mutex_lock(&wlp->mutex);
/* Send C1 association frame */
result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_C1);
if (result < 0) {
dev_err(dev, "Unable to send C1 frame to neighbor "
"%02x:%02x (%d)\n", dev_addr->data[1],
dev_addr->data[0], result);
result = 0;
goto out;
}
/* Create session, wait for response */
session.exp_message = WLP_ASSOC_C2;
session.cb = wlp_session_cb;
session.cb_priv = &completion;
session.neighbor_addr = *dev_addr;
BUG_ON(wlp->session != NULL);
wlp->session = &session;
/* Wait for C2/F0 frame */
result = wait_for_completion_interruptible_timeout(&completion,
WLP_PER_MSG_TIMEOUT * HZ);
if (result == 0) {
dev_err(dev, "Timeout while sending C1 to neighbor "
"%02x:%02x.\n", dev_addr->data[1],
dev_addr->data[0]);
goto out;
}
if (result < 0) {
dev_err(dev, "Unable to send C1 to neighbor %02x:%02x.\n",
dev_addr->data[1], dev_addr->data[0]);
result = 0;
goto out;
}
/* Parse message in session->data: it will be either C2 or F0 */
skb = session.data;
resp = (void *) skb->data;
d_printf(5, dev, "Received response to C1 frame. \n");
d_dump(5, 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 incoming F0 "
"frame from neighbor %02x:%02x.\n",
dev_addr->data[1], dev_addr->data[0]);
result = 0;
goto error_resp_parse;
}
/* WLP version and message type fields have already been parsed */
result = wlp_get_wssid(wlp, (void *)resp + sizeof(*resp), &wssid,
skb->len - sizeof(*resp));
if (result < 0) {
dev_err(dev, "WLP: unable to obtain WSSID from C2 frame.\n");
result = 0;
goto error_resp_parse;
}
if (!memcmp(&wssid, &wss->wssid, sizeof(wssid))) {
d_printf(5, dev, "WSSID in C2 frame matches local "
"active WSS.\n");
result = 1;
} else {
dev_err(dev, "WLP: Received a C2 frame without matching "
"WSSID.\n");
result = 0;
}
error_resp_parse:
kfree_skb(skb);
out:
wlp->session = NULL;
mutex_unlock(&wlp->mutex);
d_fnend(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n",
wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]);
return result;
}
/**
* Activate connection with neighbor by updating EDA cache
*
* @wss: local WSS to which neighbor wants to connect
* @dev_addr: neighbor's address
* @wssid: neighbor's WSSID - must be same as our WSS's WSSID
* @tag: neighbor's WSS tag used to identify frames transmitted by it
* @virt_addr: neighbor's virtual EUI-48
*/
static
int wlp_wss_activate_connection(struct wlp *wlp, struct wlp_wss *wss,
struct uwb_dev_addr *dev_addr,
struct wlp_uuid *wssid, u8 *tag,
struct uwb_mac_addr *virt_addr)
{
struct device *dev = &wlp->rc->uwb_dev.dev;
int result = 0;
char buf[WLP_WSS_UUID_STRSIZE];
wlp_wss_uuid_print(buf, sizeof(buf), wssid);
d_fnstart(5, dev, "wlp %p, wss %p, wssid %s, tag %u, virtual "
"%02x:%02x:%02x:%02x:%02x:%02x \n", wlp, wss, buf, *tag,
virt_addr->data[0], virt_addr->data[1], virt_addr->data[2],
virt_addr->data[3], virt_addr->data[4], virt_addr->data[5]);
if (!memcmp(wssid, &wss->wssid, sizeof(*wssid))) {
d_printf(5, dev, "WSSID from neighbor frame matches local "
"active WSS.\n");
/* Update EDA cache */
result = wlp_eda_update_node(&wlp->eda, dev_addr, wss,
(void *) virt_addr->data, *tag,
WLP_WSS_CONNECTED);
if (result < 0)
dev_err(dev, "WLP: Unable to update EDA cache "
"with new connected neighbor information.\n");
} else {
dev_err(dev, "WLP: Neighbor does not have matching "
"WSSID.\n");
result = -EINVAL;
}
d_fnend(5, dev, "wlp %p, wss %p, wssid %s, tag %u, virtual "
"%02x:%02x:%02x:%02x:%02x:%02x, result = %d \n",
wlp, wss, buf, *tag,
virt_addr->data[0], virt_addr->data[1], virt_addr->data[2],
virt_addr->data[3], virt_addr->data[4], virt_addr->data[5],
result);
return result;
}
/**
* Connect to WSS neighbor
*
* Use C3/C4 exchange to determine if neighbor has WSS activated and
* retrieve the WSS tag and virtual EUI-48 of the neighbor.
*/
static
int wlp_wss_connect_neighbor(struct wlp *wlp, struct wlp_wss *wss,
struct uwb_dev_addr *dev_addr)
{
int result;
struct device *dev = &wlp->rc->uwb_dev.dev;
char buf[WLP_WSS_UUID_STRSIZE];
struct wlp_uuid wssid;
u8 tag;
struct uwb_mac_addr virt_addr;
DECLARE_COMPLETION_ONSTACK(completion);
struct wlp_session session;
struct wlp_frame_assoc *resp;
struct sk_buff *skb;
wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid);
d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n",
wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]);
mutex_lock(&wlp->mutex);
/* Send C3 association frame */
result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_C3);
if (result < 0) {
dev_err(dev, "Unable to send C3 frame to neighbor "
"%02x:%02x (%d)\n", dev_addr->data[1],
dev_addr->data[0], result);
goto out;
}
/* Create session, wait for response */
session.exp_message = WLP_ASSOC_C4;
session.cb = wlp_session_cb;
session.cb_priv = &completion;
session.neighbor_addr = *dev_addr;
BUG_ON(wlp->session != NULL);
wlp->session = &session;
/* Wait for C4/F0 frame */
result = wait_for_completion_interruptible_timeout(&completion,
WLP_PER_MSG_TIMEOUT * HZ);
if (result == 0) {
dev_err(dev, "Timeout while sending C3 to neighbor "
"%02x:%02x.\n", dev_addr->data[1],
dev_addr->data[0]);
result = -ETIMEDOUT;
goto out;
}
if (result < 0) {
dev_err(dev, "Unable to send C3 to neighbor %02x:%02x.\n",
dev_addr->data[1], dev_addr->data[0]);
goto out;
}
/* Parse message in session->data: it will be either C4 or F0 */
skb = session.data;
resp = (void *) skb->data;
d_printf(5, dev, "Received response to C3 frame. \n");
d_dump(5, 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 incoming F0 "
"frame from neighbor %02x:%02x.\n",
dev_addr->data[1], dev_addr->data[0]);
result = -EINVAL;
goto error_resp_parse;
}
result = wlp_parse_c3c4_frame(wlp, skb, &wssid, &tag, &virt_addr);
if (result < 0) {
dev_err(dev, "WLP: Unable to parse C4 frame from neighbor.\n");
goto error_resp_parse;
}
result = wlp_wss_activate_connection(wlp, wss, dev_addr, &wssid, &tag,
&virt_addr);
if (result < 0) {
dev_err(dev, "WLP: Unable to activate connection to "
"neighbor %02x:%02x.\n", dev_addr->data[1],
dev_addr->data[0]);
goto error_resp_parse;
}
error_resp_parse:
kfree_skb(skb);
out:
/* Record that we unsuccessfully tried to connect to this neighbor */
if (result < 0)
wlp_eda_update_node_state(&wlp->eda, dev_addr,
WLP_WSS_CONNECT_FAILED);
wlp->session = NULL;
mutex_unlock(&wlp->mutex);
d_fnend(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n",
wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]);
return result;
}
/**
* Connect to neighbor with common WSS, send pending frame
*
* This function is scheduled when a frame is destined to a neighbor with
* which we do not have a connection. A copy of the EDA cache entry is
* provided - not the actual cache entry (because it is protected by a
* spinlock).
*
* First determine if neighbor has the same WSS activated, connect if it
* does. The C3/C4 exchange is dual purpose to determine if neighbor has
* WSS activated and proceed with the connection.
*
* The frame that triggered the connection setup is sent after connection
* setup.
*
* network queue is stopped - we need to restart when done
*
*/
static
void wlp_wss_connect_send(struct work_struct *ws)
{
struct wlp_assoc_conn_ctx *conn_ctx = container_of(ws,
struct wlp_assoc_conn_ctx,
ws);
struct wlp *wlp = conn_ctx->wlp;
struct sk_buff *skb = conn_ctx->skb;
struct wlp_eda_node *eda_entry = &conn_ctx->eda_entry;
struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr;
struct wlp_wss *wss = &wlp->wss;
int result;
struct device *dev = &wlp->rc->uwb_dev.dev;
char buf[WLP_WSS_UUID_STRSIZE];
mutex_lock(&wss->mutex);
wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid);
d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n",
wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]);
if (wss->state < WLP_WSS_STATE_ACTIVE) {
if (printk_ratelimit())
dev_err(dev, "WLP: Attempting to connect with "
"WSS that is not active or connected.\n");
dev_kfree_skb(skb);
goto out;
}
/* Establish connection - send C3 rcv C4 */
result = wlp_wss_connect_neighbor(wlp, wss, dev_addr);
if (result < 0) {
if (printk_ratelimit())
dev_err(dev, "WLP: Unable to establish connection "
"with neighbor %02x:%02x.\n",
dev_addr->data[1], dev_addr->data[0]);
dev_kfree_skb(skb);
goto out;
}
/* EDA entry changed, update the local copy being used */
result = wlp_copy_eda_node(&wlp->eda, dev_addr, eda_entry);
if (result < 0) {
if (printk_ratelimit())
dev_err(dev, "WLP: Cannot find EDA entry for "
"neighbor %02x:%02x \n",
dev_addr->data[1], dev_addr->data[0]);
}
result = wlp_wss_prep_hdr(wlp, eda_entry, skb);
if (result < 0) {
if (printk_ratelimit())
dev_err(dev, "WLP: Unable to prepare frame header for "
"transmission (neighbor %02x:%02x). \n",
dev_addr->data[1], dev_addr->data[0]);
dev_kfree_skb(skb);
goto out;
}
BUG_ON(wlp->xmit_frame == NULL);
result = wlp->xmit_frame(wlp, skb, dev_addr);
if (result < 0) {
if (printk_ratelimit())
dev_err(dev, "WLP: Unable to transmit frame: %d\n",
result);
if (result == -ENXIO)
dev_err(dev, "WLP: Is network interface up? \n");
/* We could try again ... */
dev_kfree_skb(skb);/*we need to free if tx fails */
}
out:
kfree(conn_ctx);
BUG_ON(wlp->start_queue == NULL);
wlp->start_queue(wlp);
mutex_unlock(&wss->mutex);
d_fnend(5, dev, "wlp %p, wss %p (wssid %s)\n", wlp, wss, buf);
}
/**
* Add WLP header to outgoing skb
*
* @eda_entry: pointer to neighbor's entry in the EDA cache
* @_skb: skb containing data destined to the neighbor
*/
int wlp_wss_prep_hdr(struct wlp *wlp, struct wlp_eda_node *eda_entry,
void *_skb)
{
struct device *dev = &wlp->rc->uwb_dev.dev;
int result = 0;
unsigned char *eth_addr = eda_entry->eth_addr;
struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr;
struct sk_buff *skb = _skb;
struct wlp_frame_std_abbrv_hdr *std_hdr;
d_fnstart(6, dev, "wlp %p \n", wlp);
if (eda_entry->state == WLP_WSS_CONNECTED) {
/* Add WLP header */
BUG_ON(skb_headroom(skb) < sizeof(*std_hdr));
std_hdr = (void *) __skb_push(skb, sizeof(*std_hdr));
std_hdr->hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID);
std_hdr->hdr.type = WLP_FRAME_STANDARD;
std_hdr->tag = eda_entry->wss->tag;
} else {
if (printk_ratelimit())
dev_err(dev, "WLP: Destination neighbor (Ethernet: "
"%02x:%02x:%02x:%02x:%02x:%02x, Dev: "
"%02x:%02x) is not connected. \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]);
result = -EINVAL;
}
d_fnend(6, dev, "wlp %p \n", wlp);
return result;
}
/**
* Prepare skb for neighbor: connect if not already and prep WLP header
*
* This function is called in interrupt context, but it needs to sleep. We
* temporarily stop the net queue to establish the WLP connection.
* Setup of the WLP connection and restart of queue is scheduled
* on the default work queue.
*
* run with eda->lock held (spinlock)
*/
int wlp_wss_connect_prep(struct wlp *wlp, struct wlp_eda_node *eda_entry,
void *_skb)
{
int result = 0;
struct device *dev = &wlp->rc->uwb_dev.dev;
struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr;
unsigned char *eth_addr = eda_entry->eth_addr;
struct sk_buff *skb = _skb;
struct wlp_assoc_conn_ctx *conn_ctx;
d_fnstart(5, dev, "wlp %p\n", wlp);
d_printf(5, dev, "To neighbor %02x:%02x with eth "
"%02x:%02x:%02x:%02x:%02x:%02x\n", dev_addr->data[1],
dev_addr->data[0], eth_addr[0], eth_addr[1], eth_addr[2],
eth_addr[3], eth_addr[4], eth_addr[5]);
if (eda_entry->state == WLP_WSS_UNCONNECTED) {
/* We don't want any more packets while we set up connection */
BUG_ON(wlp->stop_queue == NULL);
wlp->stop_queue(wlp);
conn_ctx = kmalloc(sizeof(*conn_ctx), GFP_ATOMIC);
if (conn_ctx == NULL) {
if (printk_ratelimit())
dev_err(dev, "WLP: Unable to allocate memory "
"for connection handling.\n");
result = -ENOMEM;
goto out;
}
conn_ctx->wlp = wlp;
conn_ctx->skb = skb;
conn_ctx->eda_entry = *eda_entry;
INIT_WORK(&conn_ctx->ws, wlp_wss_connect_send);
schedule_work(&conn_ctx->ws);
result = 1;
} else if (eda_entry->state == WLP_WSS_CONNECT_FAILED) {
/* Previous connection attempts failed, don't retry - see
* conditions for connection in WLP 0.99 [7.6.2] */
if (printk_ratelimit())
dev_err(dev, "Could not connect to neighbor "
"previously. Not retrying. \n");
result = -ENONET;
goto out;
} else { /* eda_entry->state == WLP_WSS_CONNECTED */
d_printf(5, dev, "Neighbor is connected, preparing frame.\n");
result = wlp_wss_prep_hdr(wlp, eda_entry, skb);
}
out:
d_fnend(5, dev, "wlp %p, result = %d \n", wlp, result);
return result;
}
/**
* Emulate broadcast: copy skb, send copy to neighbor (connect if not already)
*
* We need to copy skbs in the case where we emulate broadcast through
* unicast. We copy instead of clone because we are modifying the data of
* the frame after copying ... clones share data so we cannot emulate
* broadcast using clones.
*
* run with eda->lock held (spinlock)
*/
int wlp_wss_send_copy(struct wlp *wlp, struct wlp_eda_node *eda_entry,
void *_skb)
{
int result = -ENOMEM;
struct device *dev = &wlp->rc->uwb_dev.dev;
struct sk_buff *skb = _skb;
struct sk_buff *copy;
struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr;
d_fnstart(5, dev, "to neighbor %02x:%02x, skb (%p) \n",
dev_addr->data[1], dev_addr->data[0], skb);
copy = skb_copy(skb, GFP_ATOMIC);
if (copy == NULL) {
if (printk_ratelimit())
dev_err(dev, "WLP: Unable to copy skb for "
"transmission.\n");
goto out;
}
result = wlp_wss_connect_prep(wlp, eda_entry, copy);
if (result < 0) {
if (printk_ratelimit())
dev_err(dev, "WLP: Unable to connect/send skb "
"to neighbor.\n");
dev_kfree_skb_irq(copy);
goto out;
} else if (result == 1)
/* Frame will be transmitted separately */
goto out;
BUG_ON(wlp->xmit_frame == NULL);
result = wlp->xmit_frame(wlp, copy, dev_addr);
if (result < 0) {
if (printk_ratelimit())
dev_err(dev, "WLP: Unable to transmit frame: %d\n",
result);
if ((result == -ENXIO) && printk_ratelimit())
dev_err(dev, "WLP: Is network interface up? \n");
/* We could try again ... */
dev_kfree_skb_irq(copy);/*we need to free if tx fails */
}
out:
d_fnend(5, dev, "to neighbor %02x:%02x \n", dev_addr->data[1],
dev_addr->data[0]);
return result;
}
/**
* Setup WSS
*
* Should be called by network driver after the interface has been given a
* MAC address.
*/
int wlp_wss_setup(struct net_device *net_dev, struct wlp_wss *wss)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
int result = 0;
d_fnstart(5, dev, "wss (%p) \n", wss);
mutex_lock(&wss->mutex);
wss->kobj.parent = &net_dev->dev.kobj;
if (!is_valid_ether_addr(net_dev->dev_addr)) {
dev_err(dev, "WLP: Invalid MAC address. Cannot use for"
"virtual.\n");
result = -EINVAL;
goto out;
}
memcpy(wss->virtual_addr.data, net_dev->dev_addr,
sizeof(wss->virtual_addr.data));
out:
mutex_unlock(&wss->mutex);
d_fnend(5, dev, "wss (%p) \n", wss);
return result;
}
EXPORT_SYMBOL_GPL(wlp_wss_setup);
/**
* Remove WSS
*
* Called by client that configured WSS through wlp_wss_setup(). This
* function is called when client no longer needs WSS, eg. client shuts
* down.
*
* We remove the WLP IE from the beacon before initiating local cleanup.
*/
void wlp_wss_remove(struct wlp_wss *wss)
{
struct wlp *wlp = container_of(wss, struct wlp, wss);
struct device *dev = &wlp->rc->uwb_dev.dev;
d_fnstart(5, dev, "wss (%p) \n", wss);
mutex_lock(&wss->mutex);
if (wss->state == WLP_WSS_STATE_ACTIVE)
uwb_rc_ie_rm(wlp->rc, UWB_IE_WLP);
if (wss->state != WLP_WSS_STATE_NONE) {
sysfs_remove_group(&wss->kobj, &wss_attr_group);
kobject_put(&wss->kobj);
}
wss->kobj.parent = NULL;
memset(&wss->virtual_addr, 0, sizeof(wss->virtual_addr));
/* Cleanup EDA cache */
wlp_eda_release(&wlp->eda);
wlp_eda_init(&wlp->eda);
mutex_unlock(&wss->mutex);
d_fnend(5, dev, "wss (%p) \n", wss);
}
EXPORT_SYMBOL_GPL(wlp_wss_remove);