aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/uwb/wlp/txrx.c
blob: 86a853b84119d54febd7c895683f42a78235b991 (plain) (tree)



























                                                                      
 
                         
 
  















                                                                          
 




                                                                              
                       





                              



                                                               



                                                                    



                                                               









                                                                      

 
  



















                                                                           




































                                                                                


                       

 
  

















                                                                               





























                                                                              


                      
  













                                                                               





                                                                       



































                                                                            











                                                          




                                     
  




















































                                                                               
                                                       










                                                                             









                                                                             


                                        
/*
 * WiMedia Logical Link Control Protocol (WLP)
 * Message exchange infrastructure
 *
 * 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.
 *
 *
 * FIXME: Docs
 *
 */

#include <linux/etherdevice.h>
#include <linux/wlp.h>

#include "wlp-internal.h"

/*
 * Direct incoming association msg to correct parsing routine
 *
 * We only expect D1, E1, C1, C3 messages as new. All other incoming
 * association messages should form part of an established session that is
 * handled elsewhere.
 * The handling of these messages often require calling sleeping functions
 * - this cannot be done in interrupt context. We use the kernel's
 * workqueue to handle these messages.
 */
static
void wlp_direct_assoc_frame(struct wlp *wlp, struct sk_buff *skb,
			   struct uwb_dev_addr *src)
{
	struct device *dev = &wlp->rc->uwb_dev.dev;
	struct wlp_frame_assoc *assoc = (void *) skb->data;
	struct wlp_assoc_frame_ctx *frame_ctx;

	frame_ctx = kmalloc(sizeof(*frame_ctx), GFP_ATOMIC);
	if (frame_ctx == NULL) {
		dev_err(dev, "WLP: Unable to allocate memory for association "
			"frame handling.\n");
		kfree_skb(skb);
		return;
	}
	frame_ctx->wlp = wlp;
	frame_ctx->skb = skb;
	frame_ctx->src = *src;
	switch (assoc->type) {
	case WLP_ASSOC_D1:
		INIT_WORK(&frame_ctx->ws, wlp_handle_d1_frame);
		schedule_work(&frame_ctx->ws);
		break;
	case WLP_ASSOC_E1:
		kfree_skb(skb); /* Temporary until we handle it */
		kfree(frame_ctx); /* Temporary until we handle it */
		break;
	case WLP_ASSOC_C1:
		INIT_WORK(&frame_ctx->ws, wlp_handle_c1_frame);
		schedule_work(&frame_ctx->ws);
		break;
	case WLP_ASSOC_C3:
		INIT_WORK(&frame_ctx->ws, wlp_handle_c3_frame);
		schedule_work(&frame_ctx->ws);
		break;
	default:
		dev_err(dev, "Received unexpected association frame. "
			"Type = %d \n", assoc->type);
		kfree_skb(skb);
		kfree(frame_ctx);
		break;
	}
}

/*
 * Process incoming association frame
 *
 * Although it could be possible to deal with some incoming association
 * messages without creating a new session we are keeping things simple. We
 * do not accept new association messages if there is a session in progress
 * and the messages do not belong to that session.
 *
 * If an association message arrives that causes the creation of a session
 * (WLP_ASSOC_E1) while we are in the process of creating a session then we
 * rely on the neighbor mutex to protect the data. That is, the new session
 * will not be started until the previous is completed.
 */
static
void wlp_receive_assoc_frame(struct wlp *wlp, struct sk_buff *skb,
			     struct uwb_dev_addr *src)
{
	struct device *dev = &wlp->rc->uwb_dev.dev;
	struct wlp_frame_assoc *assoc = (void *) skb->data;
	struct wlp_session *session = wlp->session;
	u8 version;

	if (wlp_get_version(wlp, &assoc->version, &version,
			    sizeof(assoc->version)) < 0)
		goto error;
	if (version != WLP_VERSION) {
		dev_err(dev, "Unsupported WLP version in association "
			"message.\n");
		goto error;
	}
	if (session != NULL) {
		/* Function that created this session is still holding the
		 * &wlp->mutex to protect this session. */
		if (assoc->type == session->exp_message ||
		    assoc->type == WLP_ASSOC_F0) {
			if (!memcmp(&session->neighbor_addr, src,
				   sizeof(*src))) {
				session->data = skb;
				(session->cb)(wlp);
			} else {
				dev_err(dev, "Received expected message from "
					"unexpected source.  Expected message "
					"%d or F0 from %02x:%02x, but received "
					"it from %02x:%02x. Dropping.\n",
					session->exp_message,
					session->neighbor_addr.data[1],
					session->neighbor_addr.data[0],
					src->data[1], src->data[0]);
				goto error;
			}
		} else {
			dev_err(dev, "Association already in progress. "
				"Dropping.\n");
			goto error;
		}
	} else {
		wlp_direct_assoc_frame(wlp, skb, src);
	}
	return;
error:
	kfree_skb(skb);
}

/*
 * Verify incoming frame is from connected neighbor, prep to pass to WLP client
 *
 * Verification proceeds according to WLP 0.99 [7.3.1]. The source address
 * is used to determine which neighbor is sending the frame and the WSS tag
 * is used to know to which WSS the frame belongs (we only support one WSS
 * so this test is straight forward).
 * With the WSS found we need to ensure that we are connected before
 * allowing the exchange of data frames.
 */
static
int wlp_verify_prep_rx_frame(struct wlp *wlp, struct sk_buff *skb,
			     struct uwb_dev_addr *src)
{
	struct device *dev = &wlp->rc->uwb_dev.dev;
	int result = -EINVAL;
	struct wlp_eda_node eda_entry;
	struct wlp_frame_std_abbrv_hdr *hdr = (void *) skb->data;

	/*verify*/
	result = wlp_copy_eda_node(&wlp->eda, src, &eda_entry);
	if (result < 0) {
		if (printk_ratelimit())
			dev_err(dev, "WLP: Incoming frame is from unknown "
				"neighbor %02x:%02x.\n", src->data[1],
				src->data[0]);
		goto out;
	}
	if (hdr->tag != eda_entry.tag) {
		if (printk_ratelimit())
			dev_err(dev, "WLP: Tag of incoming frame from "
				"%02x:%02x does not match expected tag. "
				"Received 0x%02x, expected 0x%02x. \n",
				src->data[1], src->data[0], hdr->tag,
				eda_entry.tag);
		result = -EINVAL;
		goto out;
	}
	if (eda_entry.state != WLP_WSS_CONNECTED) {
		if (printk_ratelimit())
			dev_err(dev, "WLP: Incoming frame from "
				"%02x:%02x does is not from connected WSS.\n",
				src->data[1], src->data[0]);
		result = -EINVAL;
		goto out;
	}
	/*prep*/
	skb_pull(skb, sizeof(*hdr));
out:
	return result;
}

/*
 * Receive a WLP frame from device
 *
 * @returns: 1 if calling function should free the skb
 *           0 if it successfully handled skb and freed it
 *           0 if error occured, will free skb in this case
 */
int wlp_receive_frame(struct device *dev, struct wlp *wlp, struct sk_buff *skb,
		      struct uwb_dev_addr *src)
{
	unsigned len = skb->len;
	void *ptr = skb->data;
	struct wlp_frame_hdr *hdr;
	int result = 0;

	if (len < sizeof(*hdr)) {
		dev_err(dev, "Not enough data to parse WLP header.\n");
		result = -EINVAL;
		goto out;
	}
	hdr = ptr;
	if (le16_to_cpu(hdr->mux_hdr) != WLP_PROTOCOL_ID) {
		dev_err(dev, "Not a WLP frame type.\n");
		result = -EINVAL;
		goto out;
	}
	switch (hdr->type) {
	case WLP_FRAME_STANDARD:
		if (len < sizeof(struct wlp_frame_std_abbrv_hdr)) {
			dev_err(dev, "Not enough data to parse Standard "
				"WLP header.\n");
			goto out;
		}
		result = wlp_verify_prep_rx_frame(wlp, skb, src);
		if (result < 0) {
			if (printk_ratelimit())
				dev_err(dev, "WLP: Verification of frame "
					"from neighbor %02x:%02x failed.\n",
					src->data[1], src->data[0]);
			goto out;
		}
		result = 1;
		break;
	case WLP_FRAME_ABBREVIATED:
		dev_err(dev, "Abbreviated frame received. FIXME?\n");
		kfree_skb(skb);
		break;
	case WLP_FRAME_CONTROL:
		dev_err(dev, "Control frame received. FIXME?\n");
		kfree_skb(skb);
		break;
	case WLP_FRAME_ASSOCIATION:
		if (len < sizeof(struct wlp_frame_assoc)) {
			dev_err(dev, "Not enough data to parse Association "
				"WLP header.\n");
			goto out;
		}
		wlp_receive_assoc_frame(wlp, skb, src);
		break;
	default:
		dev_err(dev, "Invalid frame received.\n");
		result = -EINVAL;
		break;
	}
out:
	if (result < 0) {
		kfree_skb(skb);
		result = 0;
	}
	return result;
}
EXPORT_SYMBOL_GPL(wlp_receive_frame);


/*
 * Verify frame from network stack, prepare for further transmission
 *
 * @skb:   the socket buffer that needs to be prepared for transmission (it
 *         is in need of a WLP header). If this is a broadcast frame we take
 *         over the entire transmission.
 *         If it is a unicast the WSS connection should already be established
 *         and transmission will be done by the calling function.
 * @dst:   On return this will contain the device address to which the
 *         frame is destined.
 * @returns: 0 on success no tx : WLP header sucessfully applied to skb buffer,
 *                                calling function can proceed with tx
 *           1 on success with tx : WLP will take over transmission of this
 *                                  frame
 *           <0 on error
 *
 * The network stack (WLP client) is attempting to transmit a frame. We can
 * only transmit data if a local WSS is at least active (connection will be
 * done here if this is a broadcast frame and neighbor also has the WSS
 * active).
 *
 * The frame can be either broadcast or unicast. Broadcast in a WSS is
 * supported via multicast, but we don't support multicast yet (until
 * devices start to support MAB IEs). If a broadcast frame needs to be
 * transmitted it is treated as a unicast frame to each neighbor. In this
 * case the WLP takes over transmission of the skb and returns 1
 * to the caller to indicate so. Also, in this case, if a neighbor has the
 * same WSS activated but is not connected then the WSS connection will be
 * done at this time. The neighbor's virtual address will be learned at
 * this time.
 *
 * The destination address in a unicast frame is the virtual address of the
 * neighbor. This address only becomes known when a WSS connection is
 * established. We thus rely on a broadcast frame to trigger the setup of
 * WSS connections to all neighbors before we are able to send unicast
 * frames to them. This seems reasonable as IP would usually use ARP first
 * before any unicast frames are sent.
 *
 * If we are already connected to the neighbor (neighbor's virtual address
 * is known) we just prepare the WLP header and the caller will continue to
 * send the frame.
 *
 * A failure in this function usually indicates something that cannot be
 * fixed automatically. So, if this function fails (@return < 0) the calling
 * function should not retry to send the frame as it will very likely keep
 * failing.
 *
 */
int wlp_prepare_tx_frame(struct device *dev, struct wlp *wlp,
			 struct sk_buff *skb, struct uwb_dev_addr *dst)
{
	int result = -EINVAL;
	struct ethhdr *eth_hdr = (void *) skb->data;

	if (is_multicast_ether_addr(eth_hdr->h_dest)) {
		result = wlp_eda_for_each(&wlp->eda, wlp_wss_send_copy, skb);
		if (result < 0) {
			if (printk_ratelimit())
				dev_err(dev, "Unable to handle broadcast "
					"frame from WLP client.\n");
			goto out;
		}
		dev_kfree_skb_irq(skb);
		result = 1;
		/* Frame will be transmitted by WLP. */
	} else {
		result = wlp_eda_for_virtual(&wlp->eda, eth_hdr->h_dest, dst,
					     wlp_wss_prep_hdr, skb);
		if (unlikely(result < 0)) {
			if (printk_ratelimit())
				dev_err(dev, "Unable to prepare "
					"skb for transmission. \n");
			goto out;
		}
	}
out:
	return result;
}
EXPORT_SYMBOL_GPL(wlp_prepare_tx_frame);