| /* |
| * 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> |
| #define D_LOCAL 6 |
| #include <linux/uwb/debug.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) |
| { |
| struct device *dev = &wlp->rc->uwb_dev.dev; |
| |
| BUG_ON(wlp->fill_device_info == NULL); |
| d_printf(6, dev, "Retrieving device information " |
| "from device driver.\n"); |
| 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; |
| |
| d_fnstart(6, &dev->dev, "uwb %p \n", dev); |
| d_printf(6, &dev->dev, "Found neighbor device %02x:%02x \n", |
| dev->dev_addr.data[1], dev->dev_addr.data[0]); |
| /** |
| * 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: |
| d_fnend(6, &dev->dev, "uwb %p, result = %d \n", dev, result); |
| 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); |
| d_printf(6, dev, "Add placeholders into buffer next to " |
| "neighbor information we have (dev address).\n"); |
| 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; |
| d_printf(6, dev, "Received response to D1 frame. \n"); |
| d_dump(6, 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 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); |
| d_fnstart(6, dev, "wlp %p, neighbor %p, wss %p, wssid %p (%s)\n", |
| wlp, neighbor, wss, wssid, buf); |
| d_printf(6, dev, "Complete me.\n"); |
| 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; |
| d_printf(2, dev, "WLP: Success Enrollment into unsecure WSS " |
| "%s using neighbor %02x:%02x. \n", buf, |
| dev_addr->data[1], dev_addr->data[0]); |
| } |
| |
| d_fnend(6, dev, "wlp %p, neighbor %p, wss %p, wssid %p (%s)\n", |
| wlp, neighbor, wss, wssid, buf); |
| 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; |
| |
| d_fnstart(6, dev, "wlp %p \n", wlp); |
| 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); |
| d_fnend(6, dev, "wlp %p \n", wlp); |
| 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: |
| d_printf(6, dev, "UWB device %02x:%02x is onair\n", |
| uwb_dev->dev_addr.data[1], |
| uwb_dev->dev_addr.data[0]); |
| 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: |
| d_printf(6, dev, "UWB device %02x:%02x is offair\n", |
| uwb_dev->dev_addr.data[1], |
| uwb_dev->dev_addr.data[0]); |
| 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) { |
| d_printf(6, dev, "Removing device from " |
| "neighborhood.\n"); |
| __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); |
| } |
| } |
| |
| int wlp_setup(struct wlp *wlp, struct uwb_rc *rc) |
| { |
| struct device *dev = &rc->uwb_dev.dev; |
| int result; |
| |
| d_fnstart(6, dev, "wlp %p\n", wlp); |
| 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_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); |
| result = uwb_pal_register(rc, &wlp->pal); |
| if (result < 0) |
| uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler); |
| |
| d_fnend(6, dev, "wlp %p, result = %d\n", wlp, result); |
| return result; |
| } |
| EXPORT_SYMBOL_GPL(wlp_setup); |
| |
| void wlp_remove(struct wlp *wlp) |
| { |
| struct device *dev = &wlp->rc->uwb_dev.dev; |
| d_fnstart(6, dev, "wlp %p\n", wlp); |
| wlp_neighbors_release(wlp); |
| uwb_pal_unregister(wlp->rc, &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; |
| /* We have to use NULL here because this function can be called |
| * when the device disappeared. */ |
| d_fnend(6, NULL, "wlp %p\n", wlp); |
| } |
| 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); |