summaryrefslogblamecommitdiff
path: root/drivers/uwb/i1480/i1480u-wlp/rx.c
blob: 34f4cf9a7d3414a1a83c6837abbdedf5e9cda593 (plain) (tree)





































































                                                                            
  























































                                                                                  
                                                    
























                                                                
                                   









                                                         
                                    









                                                         
                                               













                                                                           




                                                                           











                                                             
  


























































































































































































                                                                                          
  














































































                                                                               
/*
 * WUSB Wire Adapter: WLP interface
 * Driver for the Linux Network stack.
 *
 * Copyright (C) 2005-2006 Intel Corporation
 * Inaky Perez-Gonzalez <inaky.perez-gonzalez@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.
 *
 *
 * i1480u's RX handling is simple. i1480u will send the received
 * network packets broken up in fragments; 1 to N fragments make a
 * packet, we assemble them together and deliver the packet with netif_rx().
 *
 * Beacuse each USB transfer is a *single* fragment (except when the
 * transfer contains a first fragment), each URB called thus
 * back contains one or two fragments. So we queue N URBs, each with its own
 * fragment buffer. When a URB is done, we process it (adding to the
 * current skb from the fragment buffer until complete). Once
 * processed, we requeue the URB. There is always a bunch of URBs
 * ready to take data, so the intergap should be minimal.
 *
 * An URB's transfer buffer is the data field of a socket buffer. This
 * reduces copying as data can be passed directly to network layer. If a
 * complete packet or 1st fragment is received the URB's transfer buffer is
 * taken away from it and used to send data to the network layer. In this
 * case a new transfer buffer is allocated to the URB before being requeued.
 * If a "NEXT" or "LAST" fragment is received, the fragment contents is
 * appended to the RX packet under construction and the transfer buffer
 * is reused. To be able to use this buffer to assemble complete packets
 * we set each buffer's size to that of the MAX ethernet packet that can
 * be received. There is thus room for improvement in memory usage.
 *
 * When the max tx fragment size increases, we should be able to read
 * data into the skbs directly with very simple code.
 *
 * ROADMAP:
 *
 *   ENTRY POINTS:
 *
 *     i1480u_rx_setup(): setup RX context [from i1480u_open()]
 *
 *     i1480u_rx_release(): release RX context [from i1480u_stop()]
 *
 *     i1480u_rx_cb(): called when the RX USB URB receives a
 *                     packet. It removes the header and pushes it up
 *                     the Linux netdev stack with netif_rx().
 *
 *       i1480u_rx_buffer()
 *         i1480u_drop() and i1480u_fix()
 *         i1480u_skb_deliver
 *
 */

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include "i1480u-wlp.h"

/*
 * Setup the RX context
 *
 * Each URB is provided with a transfer_buffer that is the data field
 * of a new socket buffer.
 */
int i1480u_rx_setup(struct i1480u *i1480u)
{
	int result, cnt;
	struct device *dev = &i1480u->usb_iface->dev;
	struct net_device *net_dev = i1480u->net_dev;
	struct usb_endpoint_descriptor *epd;
	struct sk_buff *skb;

	/* Alloc RX stuff */
	i1480u->rx_skb = NULL;	/* not in process of receiving packet */
	result = -ENOMEM;
	epd = &i1480u->usb_iface->cur_altsetting->endpoint[1].desc;
	for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) {
		struct i1480u_rx_buf *rx_buf = &i1480u->rx_buf[cnt];
		rx_buf->i1480u = i1480u;
		skb = dev_alloc_skb(i1480u_MAX_RX_PKT_SIZE);
		if (!skb) {
			dev_err(dev,
				"RX: cannot allocate RX buffer %d\n", cnt);
			result = -ENOMEM;
			goto error;
		}
		skb->dev = net_dev;
		skb->ip_summed = CHECKSUM_NONE;
		skb_reserve(skb, 2);
		rx_buf->data = skb;
		rx_buf->urb = usb_alloc_urb(0, GFP_KERNEL);
		if (unlikely(rx_buf->urb == NULL)) {
			dev_err(dev, "RX: cannot allocate URB %d\n", cnt);
			result = -ENOMEM;
			goto error;
		}
		usb_fill_bulk_urb(rx_buf->urb, i1480u->usb_dev,
			  usb_rcvbulkpipe(i1480u->usb_dev, epd->bEndpointAddress),
			  rx_buf->data->data, i1480u_MAX_RX_PKT_SIZE - 2,
			  i1480u_rx_cb, rx_buf);
		result = usb_submit_urb(rx_buf->urb, GFP_NOIO);
		if (unlikely(result < 0)) {
			dev_err(dev, "RX: cannot submit URB %d: %d\n",
				cnt, result);
			goto error;
		}
	}
	return 0;

error:
	i1480u_rx_release(i1480u);
	return result;
}


/* Release resources associated to the rx context */
void i1480u_rx_release(struct i1480u *i1480u)
{
	int cnt;
	for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) {
		if (i1480u->rx_buf[cnt].data)
			dev_kfree_skb(i1480u->rx_buf[cnt].data);
		if (i1480u->rx_buf[cnt].urb) {
			usb_kill_urb(i1480u->rx_buf[cnt].urb);
			usb_free_urb(i1480u->rx_buf[cnt].urb);
		}
	}
	if (i1480u->rx_skb != NULL)
		dev_kfree_skb(i1480u->rx_skb);
}

static
void i1480u_rx_unlink_urbs(struct i1480u *i1480u)
{
	int cnt;
	for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) {
		if (i1480u->rx_buf[cnt].urb)
			usb_unlink_urb(i1480u->rx_buf[cnt].urb);
	}
}

/* Fix an out-of-sequence packet */
#define i1480u_fix(i1480u, msg...)			\
do {							\
	if (printk_ratelimit())				\
		dev_err(&i1480u->usb_iface->dev, msg);	\
	dev_kfree_skb_irq(i1480u->rx_skb);		\
	i1480u->rx_skb = NULL;				\
	i1480u->rx_untd_pkt_size = 0;			\
} while (0)


/* Drop an out-of-sequence packet */
#define i1480u_drop(i1480u, msg...)			\
do {							\
	if (printk_ratelimit())				\
		dev_err(&i1480u->usb_iface->dev, msg);	\
	i1480u->stats.rx_dropped++;			\
} while (0)




/* Finalizes setting up the SKB and delivers it
 *
 * We first pass the incoming frame to WLP substack for verification. It
 * may also be a WLP association frame in which case WLP will take over the
 * processing. If WLP does not take it over it will still verify it, if the
 * frame is invalid the skb will be freed by WLP and we will not continue
 * parsing.
 * */
static
void i1480u_skb_deliver(struct i1480u *i1480u)
{
	int should_parse;
	struct net_device *net_dev = i1480u->net_dev;
	struct device *dev = &i1480u->usb_iface->dev;

	should_parse = wlp_receive_frame(dev, &i1480u->wlp, i1480u->rx_skb,
					 &i1480u->rx_srcaddr);
	if (!should_parse)
		goto out;
	i1480u->rx_skb->protocol = eth_type_trans(i1480u->rx_skb, net_dev);
	i1480u->stats.rx_packets++;
	i1480u->stats.rx_bytes += i1480u->rx_untd_pkt_size;
	net_dev->last_rx = jiffies;
	/* FIXME: flow control: check netif_rx() retval */

	netif_rx(i1480u->rx_skb);		/* deliver */
out:
	i1480u->rx_skb = NULL;
	i1480u->rx_untd_pkt_size = 0;
}


/*
 * Process a buffer of data received from the USB RX endpoint
 *
 * First fragment arrives with next or last fragment. All other fragments
 * arrive alone.
 *
 * /me hates long functions.
 */
static
void i1480u_rx_buffer(struct i1480u_rx_buf *rx_buf)
{
	unsigned pkt_completed = 0;	/* !0 when we got all pkt fragments */
	size_t untd_hdr_size, untd_frg_size;
	size_t i1480u_hdr_size;
	struct wlp_rx_hdr *i1480u_hdr = NULL;

	struct i1480u *i1480u = rx_buf->i1480u;
	struct sk_buff *skb = rx_buf->data;
	int size_left = rx_buf->urb->actual_length;
	void *ptr = rx_buf->urb->transfer_buffer; /* also rx_buf->data->data */
	struct untd_hdr *untd_hdr;

	struct net_device *net_dev = i1480u->net_dev;
	struct device *dev = &i1480u->usb_iface->dev;
	struct sk_buff *new_skb;

#if 0
	dev_fnstart(dev,
		    "(i1480u %p ptr %p size_left %zu)\n", i1480u, ptr, size_left);
	dev_err(dev, "RX packet, %zu bytes\n", size_left);
	dump_bytes(dev, ptr, size_left);
#endif
	i1480u_hdr_size = sizeof(struct wlp_rx_hdr);

	while (size_left > 0) {
		if (pkt_completed) {
			i1480u_drop(i1480u, "RX: fragment follows completed"
					 "packet in same buffer. Dropping\n");
			break;
		}
		untd_hdr = ptr;
		if (size_left < sizeof(*untd_hdr)) {	/*  Check the UNTD header */
			i1480u_drop(i1480u, "RX: short UNTD header! Dropping\n");
			goto out;
		}
		if (unlikely(untd_hdr_rx_tx(untd_hdr) == 0)) {	/* Paranoia: TX set? */
			i1480u_drop(i1480u, "RX: TX bit set! Dropping\n");
			goto out;
		}
		switch (untd_hdr_type(untd_hdr)) {	/* Check the UNTD header type */
		case i1480u_PKT_FRAG_1ST: {
			struct untd_hdr_1st *untd_hdr_1st = (void *) untd_hdr;
			dev_dbg(dev, "1st fragment\n");
			untd_hdr_size = sizeof(struct untd_hdr_1st);
			if (i1480u->rx_skb != NULL)
				i1480u_fix(i1480u, "RX: 1st fragment out of "
					"sequence! Fixing\n");
			if (size_left < untd_hdr_size + i1480u_hdr_size) {
				i1480u_drop(i1480u, "RX: short 1st fragment! "
					"Dropping\n");
				goto out;
			}
			i1480u->rx_untd_pkt_size = le16_to_cpu(untd_hdr->len)
						 - i1480u_hdr_size;
			untd_frg_size = le16_to_cpu(untd_hdr_1st->fragment_len);
			if (size_left < untd_hdr_size + untd_frg_size) {
				i1480u_drop(i1480u,
					    "RX: short payload! Dropping\n");
				goto out;
			}
			i1480u->rx_skb = skb;
			i1480u_hdr = (void *) untd_hdr_1st + untd_hdr_size;
			i1480u->rx_srcaddr = i1480u_hdr->srcaddr;
			skb_put(i1480u->rx_skb, untd_hdr_size + untd_frg_size);
			skb_pull(i1480u->rx_skb, untd_hdr_size + i1480u_hdr_size);
			stats_add_sample(&i1480u->lqe_stats, (s8) i1480u_hdr->LQI - 7);
			stats_add_sample(&i1480u->rssi_stats, i1480u_hdr->RSSI + 18);
			rx_buf->data = NULL; /* need to create new buffer */
			break;
		}
		case i1480u_PKT_FRAG_NXT: {
			dev_dbg(dev, "nxt fragment\n");
			untd_hdr_size = sizeof(struct untd_hdr_rst);
			if (i1480u->rx_skb == NULL) {
				i1480u_drop(i1480u, "RX: next fragment out of "
					    "sequence! Dropping\n");
				goto out;
			}
			if (size_left < untd_hdr_size) {
				i1480u_drop(i1480u, "RX: short NXT fragment! "
					    "Dropping\n");
				goto out;
			}
			untd_frg_size = le16_to_cpu(untd_hdr->len);
			if (size_left < untd_hdr_size + untd_frg_size) {
				i1480u_drop(i1480u,
					    "RX: short payload! Dropping\n");
				goto out;
			}
			memmove(skb_put(i1480u->rx_skb, untd_frg_size),
					ptr + untd_hdr_size, untd_frg_size);
			break;
		}
		case i1480u_PKT_FRAG_LST: {
			dev_dbg(dev, "Lst fragment\n");
			untd_hdr_size = sizeof(struct untd_hdr_rst);
			if (i1480u->rx_skb == NULL) {
				i1480u_drop(i1480u, "RX: last fragment out of "
					    "sequence! Dropping\n");
				goto out;
			}
			if (size_left < untd_hdr_size) {
				i1480u_drop(i1480u, "RX: short LST fragment! "
					    "Dropping\n");
				goto out;
			}
			untd_frg_size = le16_to_cpu(untd_hdr->len);
			if (size_left < untd_frg_size + untd_hdr_size) {
				i1480u_drop(i1480u,
					    "RX: short payload! Dropping\n");
				goto out;
			}
			memmove(skb_put(i1480u->rx_skb, untd_frg_size),
					ptr + untd_hdr_size, untd_frg_size);
			pkt_completed = 1;
			break;
		}
		case i1480u_PKT_FRAG_CMP: {
			dev_dbg(dev, "cmp fragment\n");
			untd_hdr_size = sizeof(struct untd_hdr_cmp);
			if (i1480u->rx_skb != NULL)
				i1480u_fix(i1480u, "RX: fix out-of-sequence CMP"
					   " fragment!\n");
			if (size_left < untd_hdr_size + i1480u_hdr_size) {
				i1480u_drop(i1480u, "RX: short CMP fragment! "
					    "Dropping\n");
				goto out;
			}
			i1480u->rx_untd_pkt_size = le16_to_cpu(untd_hdr->len);
			untd_frg_size = i1480u->rx_untd_pkt_size;
			if (size_left < i1480u->rx_untd_pkt_size + untd_hdr_size) {
				i1480u_drop(i1480u,
					    "RX: short payload! Dropping\n");
				goto out;
			}
			i1480u->rx_skb = skb;
			i1480u_hdr = (void *) untd_hdr + untd_hdr_size;
			i1480u->rx_srcaddr = i1480u_hdr->srcaddr;
			stats_add_sample(&i1480u->lqe_stats, (s8) i1480u_hdr->LQI - 7);
			stats_add_sample(&i1480u->rssi_stats, i1480u_hdr->RSSI + 18);
			skb_put(i1480u->rx_skb, untd_hdr_size + i1480u->rx_untd_pkt_size);
			skb_pull(i1480u->rx_skb, untd_hdr_size + i1480u_hdr_size);
			rx_buf->data = NULL;	/* for hand off skb to network stack */
			pkt_completed = 1;
			i1480u->rx_untd_pkt_size -= i1480u_hdr_size; /* accurate stat */
			break;
		}
		default:
			i1480u_drop(i1480u, "RX: unknown packet type %u! "
				    "Dropping\n", untd_hdr_type(untd_hdr));
			goto out;
		}
		size_left -= untd_hdr_size + untd_frg_size;
		if (size_left > 0)
			ptr += untd_hdr_size + untd_frg_size;
	}
	if (pkt_completed)
		i1480u_skb_deliver(i1480u);
out:
	/* recreate needed RX buffers*/
	if (rx_buf->data == NULL) {
		/* buffer is being used to receive packet, create new */
		new_skb = dev_alloc_skb(i1480u_MAX_RX_PKT_SIZE);
		if (!new_skb) {
			if (printk_ratelimit())
				dev_err(dev,
				"RX: cannot allocate RX buffer\n");
		} else {
			new_skb->dev = net_dev;
			new_skb->ip_summed = CHECKSUM_NONE;
			skb_reserve(new_skb, 2);
			rx_buf->data = new_skb;
		}
	}
	return;
}


/*
 * Called when an RX URB has finished receiving or has found some kind
 * of error condition.
 *
 * LIMITATIONS:
 *
 *  - We read USB-transfers, each transfer contains a SINGLE fragment
 *    (can contain a complete packet, or a 1st, next, or last fragment
 *    of a packet).
 *    Looks like a transfer can contain more than one fragment (07/18/06)
 *
 *  - Each transfer buffer is the size of the maximum packet size (minus
 *    headroom), i1480u_MAX_PKT_SIZE - 2
 *
 *  - We always read the full USB-transfer, no partials.
 *
 *  - Each transfer is read directly into a skb. This skb will be used to
 *    send data to the upper layers if it is the first fragment or a complete
 *    packet. In the other cases the data will be copied from the skb to
 *    another skb that is being prepared for the upper layers from a prev
 *    first fragment.
 *
 * It is simply too much of a pain. Gosh, there should be a unified
 * SG infrastructure for *everything* [so that I could declare a SG
 * buffer, pass it to USB for receiving, append some space to it if
 * I wish, receive more until I have the whole chunk, adapt
 * pointers on each fragment to remove hardware headers and then
 * attach that to an skbuff and netif_rx()].
 */
void i1480u_rx_cb(struct urb *urb)
{
	int result;
	int do_parse_buffer = 1;
	struct i1480u_rx_buf *rx_buf = urb->context;
	struct i1480u *i1480u = rx_buf->i1480u;
	struct device *dev = &i1480u->usb_iface->dev;
	unsigned long flags;
	u8 rx_buf_idx = rx_buf - i1480u->rx_buf;

	switch (urb->status) {
	case 0:
		break;
	case -ECONNRESET:	/* Not an error, but a controlled situation; */
	case -ENOENT:		/* (we killed the URB)...so, no broadcast */
	case -ESHUTDOWN:	/* going away! */
		dev_err(dev, "RX URB[%u]: goind down %d\n",
			rx_buf_idx, urb->status);
		goto error;
	default:
		dev_err(dev, "RX URB[%u]: unknown status %d\n",
			rx_buf_idx, urb->status);
		if (edc_inc(&i1480u->rx_errors, EDC_MAX_ERRORS,
					EDC_ERROR_TIMEFRAME)) {
			dev_err(dev, "RX: max acceptable errors exceeded,"
					" resetting device.\n");
			i1480u_rx_unlink_urbs(i1480u);
			wlp_reset_all(&i1480u->wlp);
			goto error;
		}
		do_parse_buffer = 0;
		break;
	}
	spin_lock_irqsave(&i1480u->lock, flags);
	/* chew the data fragments, extract network packets */
	if (do_parse_buffer) {
		i1480u_rx_buffer(rx_buf);
		if (rx_buf->data) {
			rx_buf->urb->transfer_buffer = rx_buf->data->data;
			result = usb_submit_urb(rx_buf->urb, GFP_ATOMIC);
			if (result < 0) {
				dev_err(dev, "RX URB[%u]: cannot submit %d\n",
					rx_buf_idx, result);
			}
		}
	}
	spin_unlock_irqrestore(&i1480u->lock, flags);
error:
	return;
}