linux/drivers/uwb/wlp/wlp-lc.c

560 lines
15 KiB
C

/*
* WiMedia Logical Link Control Protocol (WLP)
*
* Copyright (C) 2005-2006 Intel Corporation
* Reinette Chatre <reinette.chatre@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
* FIXME: docs
*/
#include <linux/wlp.h>
#include "wlp-internal.h"
static
void wlp_neighbor_init(struct wlp_neighbor_e *neighbor)
{
INIT_LIST_HEAD(&neighbor->wssid);
}
/**
* Create area for device information storage
*
* wlp->mutex must be held
*/
int __wlp_alloc_device_info(struct wlp *wlp)
{
struct device *dev = &wlp->rc->uwb_dev.dev;
BUG_ON(wlp->dev_info != NULL);
wlp->dev_info = kzalloc(sizeof(struct wlp_device_info), GFP_KERNEL);
if (wlp->dev_info == NULL) {
dev_err(dev, "WLP: Unable to allocate memory for "
"device information.\n");
return -ENOMEM;
}
return 0;
}
/**
* Fill in device information using function provided by driver
*
* wlp->mutex must be held
*/
static
void __wlp_fill_device_info(struct wlp *wlp)
{
wlp->fill_device_info(wlp, wlp->dev_info);
}
/**
* Setup device information
*
* Allocate area for device information and populate it.
*
* wlp->mutex must be held
*/
int __wlp_setup_device_info(struct wlp *wlp)
{
int result;
struct device *dev = &wlp->rc->uwb_dev.dev;
result = __wlp_alloc_device_info(wlp);
if (result < 0) {
dev_err(dev, "WLP: Unable to allocate area for "
"device information.\n");
return result;
}
__wlp_fill_device_info(wlp);
return 0;
}
/**
* Remove information about neighbor stored temporarily
*
* Information learned during discovey should only be stored when the
* device enrolls in the neighbor's WSS. We do need to store this
* information temporarily in order to present it to the user.
*
* We are only interested in keeping neighbor WSS information if that
* neighbor is accepting enrollment.
*
* should be called with wlp->nbmutex held
*/
void wlp_remove_neighbor_tmp_info(struct wlp_neighbor_e *neighbor)
{
struct wlp_wssid_e *wssid_e, *next;
u8 keep;
if (!list_empty(&neighbor->wssid)) {
list_for_each_entry_safe(wssid_e, next, &neighbor->wssid,
node) {
if (wssid_e->info != NULL) {
keep = wssid_e->info->accept_enroll;
kfree(wssid_e->info);
wssid_e->info = NULL;
if (!keep) {
list_del(&wssid_e->node);
kfree(wssid_e);
}
}
}
}
if (neighbor->info != NULL) {
kfree(neighbor->info);
neighbor->info = NULL;
}
}
/*
* Populate WLP neighborhood cache with neighbor information
*
* A new neighbor is found. If it is discoverable then we add it to the
* neighborhood cache.
*
*/
static
int wlp_add_neighbor(struct wlp *wlp, struct uwb_dev *dev)
{
int result = 0;
int discoverable;
struct wlp_neighbor_e *neighbor;
/*
* FIXME:
* Use contents of WLP IE found in beacon cache to determine if
* neighbor is discoverable.
* The device does not support WLP IE yet so this still needs to be
* done. Until then we assume all devices are discoverable.
*/
discoverable = 1; /* will be changed when FIXME disappears */
if (discoverable) {
/* Add neighbor to cache for discovery */
neighbor = kzalloc(sizeof(*neighbor), GFP_KERNEL);
if (neighbor == NULL) {
dev_err(&dev->dev, "Unable to create memory for "
"new neighbor. \n");
result = -ENOMEM;
goto error_no_mem;
}
wlp_neighbor_init(neighbor);
uwb_dev_get(dev);
neighbor->uwb_dev = dev;
list_add(&neighbor->node, &wlp->neighbors);
}
error_no_mem:
return result;
}
/**
* Remove one neighbor from cache
*/
static
void __wlp_neighbor_release(struct wlp_neighbor_e *neighbor)
{
struct wlp_wssid_e *wssid_e, *next_wssid_e;
list_for_each_entry_safe(wssid_e, next_wssid_e,
&neighbor->wssid, node) {
list_del(&wssid_e->node);
kfree(wssid_e);
}
uwb_dev_put(neighbor->uwb_dev);
list_del(&neighbor->node);
kfree(neighbor);
}
/**
* Clear entire neighborhood cache.
*/
static
void __wlp_neighbors_release(struct wlp *wlp)
{
struct wlp_neighbor_e *neighbor, *next;
if (list_empty(&wlp->neighbors))
return;
list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) {
__wlp_neighbor_release(neighbor);
}
}
static
void wlp_neighbors_release(struct wlp *wlp)
{
mutex_lock(&wlp->nbmutex);
__wlp_neighbors_release(wlp);
mutex_unlock(&wlp->nbmutex);
}
/**
* Send D1 message to neighbor, receive D2 message
*
* @neighbor: neighbor to which D1 message will be sent
* @wss: if not NULL, it is an enrollment request for this WSS
* @wssid: if wss not NULL, this is the wssid of the WSS in which we
* want to enroll
*
* A D1/D2 exchange is done for one of two reasons: discovery or
* enrollment. If done for discovery the D1 message is sent to the neighbor
* and the contents of the D2 response is stored in a temporary cache.
* If done for enrollment the @wss and @wssid are provided also. In this
* case the D1 message is sent to the neighbor, the D2 response is parsed
* for enrollment of the WSS with wssid.
*
* &wss->mutex is held
*/
static
int wlp_d1d2_exchange(struct wlp *wlp, struct wlp_neighbor_e *neighbor,
struct wlp_wss *wss, struct wlp_uuid *wssid)
{
int result;
struct device *dev = &wlp->rc->uwb_dev.dev;
DECLARE_COMPLETION_ONSTACK(completion);
struct wlp_session session;
struct sk_buff *skb;
struct wlp_frame_assoc *resp;
struct uwb_dev_addr *dev_addr = &neighbor->uwb_dev->dev_addr;
mutex_lock(&wlp->mutex);
if (!wlp_uuid_is_set(&wlp->uuid)) {
dev_err(dev, "WLP: UUID is not set. Set via sysfs to "
"proceed.\n");
result = -ENXIO;
goto out;
}
/* Send D1 association frame */
result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_D1);
if (result < 0) {
dev_err(dev, "Unable to send D1 frame to neighbor "
"%02x:%02x (%d)\n", dev_addr->data[1],
dev_addr->data[0], result);
goto out;
}
/* Create session, wait for response */
session.exp_message = WLP_ASSOC_D2;
session.cb = wlp_session_cb;
session.cb_priv = &completion;
session.neighbor_addr = *dev_addr;
BUG_ON(wlp->session != NULL);
wlp->session = &session;
/* Wait for D2/F0 frame */
result = wait_for_completion_interruptible_timeout(&completion,
WLP_PER_MSG_TIMEOUT * HZ);
if (result == 0) {
result = -ETIMEDOUT;
dev_err(dev, "Timeout while sending D1 to neighbor "
"%02x:%02x.\n", dev_addr->data[1],
dev_addr->data[0]);
goto error_session;
}
if (result < 0) {
dev_err(dev, "Unable to discover/enroll neighbor %02x:%02x.\n",
dev_addr->data[1], dev_addr->data[0]);
goto error_session;
}
/* Parse message in session->data: it will be either D2 or F0 */
skb = session.data;
resp = (void *) skb->data;
if (resp->type == WLP_ASSOC_F0) {
result = wlp_parse_f0(wlp, skb);
if (result < 0)
dev_err(dev, "WLP: Unable to parse F0 from neighbor "
"%02x:%02x.\n", dev_addr->data[1],
dev_addr->data[0]);
result = -EINVAL;
goto error_resp_parse;
}
if (wss == NULL) {
/* Discovery */
result = wlp_parse_d2_frame_to_cache(wlp, skb, neighbor);
if (result < 0) {
dev_err(dev, "WLP: Unable to parse D2 message from "
"neighbor %02x:%02x for discovery.\n",
dev_addr->data[1], dev_addr->data[0]);
goto error_resp_parse;
}
} else {
/* Enrollment */
result = wlp_parse_d2_frame_to_enroll(wss, skb, neighbor,
wssid);
if (result < 0) {
dev_err(dev, "WLP: Unable to parse D2 message from "
"neighbor %02x:%02x for enrollment.\n",
dev_addr->data[1], dev_addr->data[0]);
goto error_resp_parse;
}
}
error_resp_parse:
kfree_skb(skb);
error_session:
wlp->session = NULL;
out:
mutex_unlock(&wlp->mutex);
return result;
}
/**
* Enroll into WSS of provided WSSID by using neighbor as registrar
*
* &wss->mutex is held
*/
int wlp_enroll_neighbor(struct wlp *wlp, struct wlp_neighbor_e *neighbor,
struct wlp_wss *wss, struct wlp_uuid *wssid)
{
int result = 0;
struct device *dev = &wlp->rc->uwb_dev.dev;
char buf[WLP_WSS_UUID_STRSIZE];
struct uwb_dev_addr *dev_addr = &neighbor->uwb_dev->dev_addr;
wlp_wss_uuid_print(buf, sizeof(buf), wssid);
result = wlp_d1d2_exchange(wlp, neighbor, wss, wssid);
if (result < 0) {
dev_err(dev, "WLP: D1/D2 message exchange for enrollment "
"failed. result = %d \n", result);
goto out;
}
if (wss->state != WLP_WSS_STATE_PART_ENROLLED) {
dev_err(dev, "WLP: Unable to enroll into WSS %s using "
"neighbor %02x:%02x. \n", buf,
dev_addr->data[1], dev_addr->data[0]);
result = -EINVAL;
goto out;
}
if (wss->secure_status == WLP_WSS_SECURE) {
dev_err(dev, "FIXME: need to complete secure enrollment.\n");
result = -EINVAL;
goto error;
} else {
wss->state = WLP_WSS_STATE_ENROLLED;
dev_dbg(dev, "WLP: Success Enrollment into unsecure WSS "
"%s using neighbor %02x:%02x. \n",
buf, dev_addr->data[1], dev_addr->data[0]);
}
out:
return result;
error:
wlp_wss_reset(wss);
return result;
}
/**
* Discover WSS information of neighbor's active WSS
*/
static
int wlp_discover_neighbor(struct wlp *wlp,
struct wlp_neighbor_e *neighbor)
{
return wlp_d1d2_exchange(wlp, neighbor, NULL, NULL);
}
/**
* Each neighbor in the neighborhood cache is discoverable. Discover it.
*
* Discovery is done through sending of D1 association frame and parsing
* the D2 association frame response. Only wssid from D2 will be included
* in neighbor cache, rest is just displayed to user and forgotten.
*
* The discovery is not done in parallel. This is simple and enables us to
* maintain only one association context.
*
* The discovery of one neighbor does not affect the other, but if the
* discovery of a neighbor fails it is removed from the neighborhood cache.
*/
static
int wlp_discover_all_neighbors(struct wlp *wlp)
{
int result = 0;
struct device *dev = &wlp->rc->uwb_dev.dev;
struct wlp_neighbor_e *neighbor, *next;
list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) {
result = wlp_discover_neighbor(wlp, neighbor);
if (result < 0) {
dev_err(dev, "WLP: Unable to discover neighbor "
"%02x:%02x, removing from neighborhood. \n",
neighbor->uwb_dev->dev_addr.data[1],
neighbor->uwb_dev->dev_addr.data[0]);
__wlp_neighbor_release(neighbor);
}
}
return result;
}
static int wlp_add_neighbor_helper(struct device *dev, void *priv)
{
struct wlp *wlp = priv;
struct uwb_dev *uwb_dev = to_uwb_dev(dev);
return wlp_add_neighbor(wlp, uwb_dev);
}
/**
* Discover WLP neighborhood
*
* Will send D1 association frame to all devices in beacon group that have
* discoverable bit set in WLP IE. D2 frames will be received, information
* displayed to user in @buf. Partial information (from D2 association
* frame) will be cached to assist with future association
* requests.
*
* The discovery of the WLP neighborhood is triggered by the user. This
* should occur infrequently and we thus free current cache and re-allocate
* memory if needed.
*
* If one neighbor fails during initial discovery (determining if it is a
* neighbor or not), we fail all - note that interaction with neighbor has
* not occured at this point so if a failure occurs we know something went wrong
* locally. We thus undo everything.
*/
ssize_t wlp_discover(struct wlp *wlp)
{
int result = 0;
struct device *dev = &wlp->rc->uwb_dev.dev;
mutex_lock(&wlp->nbmutex);
/* Clear current neighborhood cache. */
__wlp_neighbors_release(wlp);
/* Determine which devices in neighborhood. Repopulate cache. */
result = uwb_dev_for_each(wlp->rc, wlp_add_neighbor_helper, wlp);
if (result < 0) {
/* May have partial neighbor information, release all. */
__wlp_neighbors_release(wlp);
goto error_dev_for_each;
}
/* Discover the properties of devices in neighborhood. */
result = wlp_discover_all_neighbors(wlp);
/* In case of failure we still print our partial results. */
if (result < 0) {
dev_err(dev, "Unable to fully discover neighborhood. \n");
result = 0;
}
error_dev_for_each:
mutex_unlock(&wlp->nbmutex);
return result;
}
/**
* Handle events from UWB stack
*
* We handle events conservatively. If a neighbor goes off the air we
* remove it from the neighborhood. If an association process is in
* progress this function will block waiting for the nbmutex to become
* free. The association process will thus be allowed to complete before it
* is removed.
*/
static
void wlp_uwb_notifs_cb(void *_wlp, struct uwb_dev *uwb_dev,
enum uwb_notifs event)
{
struct wlp *wlp = _wlp;
struct device *dev = &wlp->rc->uwb_dev.dev;
struct wlp_neighbor_e *neighbor, *next;
int result;
switch (event) {
case UWB_NOTIF_ONAIR:
result = wlp_eda_create_node(&wlp->eda,
uwb_dev->mac_addr.data,
&uwb_dev->dev_addr);
if (result < 0)
dev_err(dev, "WLP: Unable to add new neighbor "
"%02x:%02x to EDA cache.\n",
uwb_dev->dev_addr.data[1],
uwb_dev->dev_addr.data[0]);
break;
case UWB_NOTIF_OFFAIR:
wlp_eda_rm_node(&wlp->eda, &uwb_dev->dev_addr);
mutex_lock(&wlp->nbmutex);
list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) {
if (neighbor->uwb_dev == uwb_dev)
__wlp_neighbor_release(neighbor);
}
mutex_unlock(&wlp->nbmutex);
break;
default:
dev_err(dev, "don't know how to handle event %d from uwb\n",
event);
}
}
static void wlp_channel_changed(struct uwb_pal *pal, int channel)
{
struct wlp *wlp = container_of(pal, struct wlp, pal);
if (channel < 0)
netif_carrier_off(wlp->ndev);
else
netif_carrier_on(wlp->ndev);
}
int wlp_setup(struct wlp *wlp, struct uwb_rc *rc, struct net_device *ndev)
{
int result;
BUG_ON(wlp->fill_device_info == NULL);
BUG_ON(wlp->xmit_frame == NULL);
BUG_ON(wlp->stop_queue == NULL);
BUG_ON(wlp->start_queue == NULL);
wlp->rc = rc;
wlp->ndev = ndev;
wlp_eda_init(&wlp->eda);/* Set up address cache */
wlp->uwb_notifs_handler.cb = wlp_uwb_notifs_cb;
wlp->uwb_notifs_handler.data = wlp;
uwb_notifs_register(rc, &wlp->uwb_notifs_handler);
uwb_pal_init(&wlp->pal);
wlp->pal.rc = rc;
wlp->pal.channel_changed = wlp_channel_changed;
result = uwb_pal_register(&wlp->pal);
if (result < 0)
uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler);
return result;
}
EXPORT_SYMBOL_GPL(wlp_setup);
void wlp_remove(struct wlp *wlp)
{
wlp_neighbors_release(wlp);
uwb_pal_unregister(&wlp->pal);
uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler);
wlp_eda_release(&wlp->eda);
mutex_lock(&wlp->mutex);
if (wlp->dev_info != NULL)
kfree(wlp->dev_info);
mutex_unlock(&wlp->mutex);
wlp->rc = NULL;
}
EXPORT_SYMBOL_GPL(wlp_remove);
/**
* wlp_reset_all - reset the WLP hardware
* @wlp: the WLP device to reset.
*
* This schedules a full hardware reset of the WLP device. The radio
* controller and any other PALs will also be reset.
*/
void wlp_reset_all(struct wlp *wlp)
{
uwb_rc_reset_all(wlp->rc);
}
EXPORT_SYMBOL_GPL(wlp_reset_all);