/* * WiMedia Logical Link Control Protocol (WLP) * * Copyright (C) 2005-2006 Intel Corporation * Reinette Chatre * * 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 #define D_LOCAL 6 #include #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); wlp->pal.rc = rc; result = uwb_pal_register(&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->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);