summaryrefslogtreecommitdiffstats
path: root/drivers/uwb/i1480/i1480u-wlp/tx.c
diff options
context:
space:
mode:
authorInaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>2008-09-17 17:34:21 +0200
committerDavid Vrabel <dv02@dv02pc01.europe.root.pri>2008-09-17 17:54:28 +0200
commita21b963aa4a98c645b1fa3799f2e4a6ebb6c974a (patch)
treebb6c927dea997cd1327c283f2969263475c21674 /drivers/uwb/i1480/i1480u-wlp/tx.c
parentuwb: add the i1480 DFU driver (diff)
downloadlinux-a21b963aa4a98c645b1fa3799f2e4a6ebb6c974a.tar.xz
linux-a21b963aa4a98c645b1fa3799f2e4a6ebb6c974a.zip
uwb: add the i1480 WLP driver
Add the driver for the WLP capability of the Intel i1480 device. Signed-off-by: David Vrabel <david.vrabel@csr.com>
Diffstat (limited to 'drivers/uwb/i1480/i1480u-wlp/tx.c')
-rw-r--r--drivers/uwb/i1480/i1480u-wlp/tx.c632
1 files changed, 632 insertions, 0 deletions
diff --git a/drivers/uwb/i1480/i1480u-wlp/tx.c b/drivers/uwb/i1480/i1480u-wlp/tx.c
new file mode 100644
index 000000000000..3426bfb68240
--- /dev/null
+++ b/drivers/uwb/i1480/i1480u-wlp/tx.c
@@ -0,0 +1,632 @@
+/*
+ * WUSB Wire Adapter: WLP interface
+ * Deal with TX (massaging data to transmit, handling it)
+ *
+ * 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.
+ *
+ *
+ * Transmission engine. Get an skb, create from that a WLP transmit
+ * context, add a WLP TX header (which we keep prefilled in the
+ * device's instance), fill out the target-specific fields and
+ * fire it.
+ *
+ * ROADMAP:
+ *
+ * Entry points:
+ *
+ * i1480u_tx_release(): called by i1480u_disconnect() to release
+ * pending tx contexts.
+ *
+ * i1480u_tx_cb(): callback for TX contexts (USB URBs)
+ * i1480u_tx_destroy():
+ *
+ * i1480u_tx_timeout(): called for timeout handling from the
+ * network stack.
+ *
+ * i1480u_hard_start_xmit(): called for transmitting an skb from
+ * the network stack. Will interact with WLP
+ * substack to verify and prepare frame.
+ * i1480u_xmit_frame(): actual transmission on hardware
+ *
+ * i1480u_tx_create() Creates TX context
+ * i1480u_tx_create_1() For packets in 1 fragment
+ * i1480u_tx_create_n() For packets in >1 fragments
+ *
+ * TODO:
+ *
+ * - FIXME: rewrite using usb_sg_*(), add asynch support to
+ * usb_sg_*(). It might not make too much sense as most of
+ * the times the MTU will be smaller than one page...
+ */
+
+#include "i1480u-wlp.h"
+#define D_LOCAL 5
+#include <linux/uwb/debug.h>
+
+enum {
+ /* This is only for Next and Last TX packets */
+ i1480u_MAX_PL_SIZE = i1480u_MAX_FRG_SIZE
+ - sizeof(struct untd_hdr_rst),
+};
+
+/** Free resources allocated to a i1480u tx context. */
+static
+void i1480u_tx_free(struct i1480u_tx *wtx)
+{
+ kfree(wtx->buf);
+ if (wtx->skb)
+ dev_kfree_skb_irq(wtx->skb);
+ usb_free_urb(wtx->urb);
+ kfree(wtx);
+}
+
+static
+void i1480u_tx_destroy(struct i1480u *i1480u, struct i1480u_tx *wtx)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&i1480u->tx_list_lock, flags); /* not active any more */
+ list_del(&wtx->list_node);
+ i1480u_tx_free(wtx);
+ spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
+}
+
+static
+void i1480u_tx_unlink_urbs(struct i1480u *i1480u)
+{
+ unsigned long flags;
+ struct i1480u_tx *wtx, *next;
+
+ spin_lock_irqsave(&i1480u->tx_list_lock, flags);
+ list_for_each_entry_safe(wtx, next, &i1480u->tx_list, list_node) {
+ usb_unlink_urb(wtx->urb);
+ }
+ spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
+}
+
+
+/**
+ * Callback for a completed tx USB URB.
+ *
+ * TODO:
+ *
+ * - FIXME: recover errors more gracefully
+ * - FIXME: handle NAKs (I dont think they come here) for flow ctl
+ */
+static
+void i1480u_tx_cb(struct urb *urb)
+{
+ struct i1480u_tx *wtx = urb->context;
+ struct i1480u *i1480u = wtx->i1480u;
+ struct net_device *net_dev = i1480u->net_dev;
+ struct device *dev = &i1480u->usb_iface->dev;
+ unsigned long flags;
+
+ switch (urb->status) {
+ case 0:
+ spin_lock_irqsave(&i1480u->lock, flags);
+ i1480u->stats.tx_packets++;
+ i1480u->stats.tx_bytes += urb->actual_length;
+ spin_unlock_irqrestore(&i1480u->lock, flags);
+ break;
+ case -ECONNRESET: /* Not an error, but a controlled situation; */
+ case -ENOENT: /* (we killed the URB)...so, no broadcast */
+ dev_dbg(dev, "notif endp: reset/noent %d\n", urb->status);
+ netif_stop_queue(net_dev);
+ break;
+ case -ESHUTDOWN: /* going away! */
+ dev_dbg(dev, "notif endp: down %d\n", urb->status);
+ netif_stop_queue(net_dev);
+ break;
+ default:
+ dev_err(dev, "TX: unknown URB status %d\n", urb->status);
+ if (edc_inc(&i1480u->tx_errors, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)) {
+ dev_err(dev, "TX: max acceptable errors exceeded."
+ "Reset device.\n");
+ netif_stop_queue(net_dev);
+ i1480u_tx_unlink_urbs(i1480u);
+ wlp_reset_all(&i1480u->wlp);
+ }
+ break;
+ }
+ i1480u_tx_destroy(i1480u, wtx);
+ if (atomic_dec_return(&i1480u->tx_inflight.count)
+ <= i1480u->tx_inflight.threshold
+ && netif_queue_stopped(net_dev)
+ && i1480u->tx_inflight.threshold != 0) {
+ if (d_test(2) && printk_ratelimit())
+ d_printf(2, dev, "Restart queue. \n");
+ netif_start_queue(net_dev);
+ atomic_inc(&i1480u->tx_inflight.restart_count);
+ }
+ return;
+}
+
+
+/**
+ * Given a buffer that doesn't fit in a single fragment, create an
+ * scatter/gather structure for delivery to the USB pipe.
+ *
+ * Implements functionality of i1480u_tx_create().
+ *
+ * @wtx: tx descriptor
+ * @skb: skb to send
+ * @gfp_mask: gfp allocation mask
+ * @returns: Pointer to @wtx if ok, NULL on error.
+ *
+ * Sorry, TOO LONG a function, but breaking it up is kind of hard
+ *
+ * This will break the buffer in chunks smaller than
+ * i1480u_MAX_FRG_SIZE (including the header) and add proper headers
+ * to each:
+ *
+ * 1st header \
+ * i1480 tx header | fragment 1
+ * fragment data /
+ * nxt header \ fragment 2
+ * fragment data /
+ * ..
+ * ..
+ * last header \ fragment 3
+ * last fragment data /
+ *
+ * This does not fill the i1480 TX header, it is left up to the
+ * caller to do that; you can get it from @wtx->wlp_tx_hdr.
+ *
+ * This function consumes the skb unless there is an error.
+ */
+static
+int i1480u_tx_create_n(struct i1480u_tx *wtx, struct sk_buff *skb,
+ gfp_t gfp_mask)
+{
+ int result;
+ void *pl;
+ size_t pl_size;
+
+ void *pl_itr, *buf_itr;
+ size_t pl_size_left, frgs, pl_size_1st, frg_pl_size = 0;
+ struct untd_hdr_1st *untd_hdr_1st;
+ struct wlp_tx_hdr *wlp_tx_hdr;
+ struct untd_hdr_rst *untd_hdr_rst;
+
+ wtx->skb = NULL;
+ pl = skb->data;
+ pl_itr = pl;
+ pl_size = skb->len;
+ pl_size_left = pl_size; /* payload size */
+ /* First fragment; fits as much as i1480u_MAX_FRG_SIZE minus
+ * the headers */
+ pl_size_1st = i1480u_MAX_FRG_SIZE
+ - sizeof(struct untd_hdr_1st) - sizeof(struct wlp_tx_hdr);
+ BUG_ON(pl_size_1st > pl_size);
+ pl_size_left -= pl_size_1st;
+ /* The rest have an smaller header (no i1480 TX header). We
+ * need to break up the payload in blocks smaller than
+ * i1480u_MAX_PL_SIZE (payload excluding header). */
+ frgs = (pl_size_left + i1480u_MAX_PL_SIZE - 1) / i1480u_MAX_PL_SIZE;
+ /* Allocate space for the new buffer. In this new buffer we'll
+ * place the headers followed by the data fragment, headers,
+ * data fragments, etc..
+ */
+ result = -ENOMEM;
+ wtx->buf_size = sizeof(*untd_hdr_1st)
+ + sizeof(*wlp_tx_hdr)
+ + frgs * sizeof(*untd_hdr_rst)
+ + pl_size;
+ wtx->buf = kmalloc(wtx->buf_size, gfp_mask);
+ if (wtx->buf == NULL)
+ goto error_buf_alloc;
+
+ buf_itr = wtx->buf; /* We got the space, let's fill it up */
+ /* Fill 1st fragment */
+ untd_hdr_1st = buf_itr;
+ buf_itr += sizeof(*untd_hdr_1st);
+ untd_hdr_set_type(&untd_hdr_1st->hdr, i1480u_PKT_FRAG_1ST);
+ untd_hdr_set_rx_tx(&untd_hdr_1st->hdr, 0);
+ untd_hdr_1st->hdr.len = cpu_to_le16(pl_size + sizeof(*wlp_tx_hdr));
+ untd_hdr_1st->fragment_len =
+ cpu_to_le16(pl_size_1st + sizeof(*wlp_tx_hdr));
+ memset(untd_hdr_1st->padding, 0, sizeof(untd_hdr_1st->padding));
+ /* Set up i1480 header info */
+ wlp_tx_hdr = wtx->wlp_tx_hdr = buf_itr;
+ buf_itr += sizeof(*wlp_tx_hdr);
+ /* Copy the first fragment */
+ memcpy(buf_itr, pl_itr, pl_size_1st);
+ pl_itr += pl_size_1st;
+ buf_itr += pl_size_1st;
+
+ /* Now do each remaining fragment */
+ result = -EINVAL;
+ while (pl_size_left > 0) {
+ d_printf(5, NULL, "ITR HDR: pl_size_left %zu buf_itr %zu\n",
+ pl_size_left, buf_itr - wtx->buf);
+ if (buf_itr + sizeof(*untd_hdr_rst) - wtx->buf
+ > wtx->buf_size) {
+ printk(KERN_ERR "BUG: no space for header\n");
+ goto error_bug;
+ }
+ d_printf(5, NULL, "ITR HDR 2: pl_size_left %zu buf_itr %zu\n",
+ pl_size_left, buf_itr - wtx->buf);
+ untd_hdr_rst = buf_itr;
+ buf_itr += sizeof(*untd_hdr_rst);
+ if (pl_size_left > i1480u_MAX_PL_SIZE) {
+ frg_pl_size = i1480u_MAX_PL_SIZE;
+ untd_hdr_set_type(&untd_hdr_rst->hdr, i1480u_PKT_FRAG_NXT);
+ } else {
+ frg_pl_size = pl_size_left;
+ untd_hdr_set_type(&untd_hdr_rst->hdr, i1480u_PKT_FRAG_LST);
+ }
+ d_printf(5, NULL,
+ "ITR PL: pl_size_left %zu buf_itr %zu frg_pl_size %zu\n",
+ pl_size_left, buf_itr - wtx->buf, frg_pl_size);
+ untd_hdr_set_rx_tx(&untd_hdr_rst->hdr, 0);
+ untd_hdr_rst->hdr.len = cpu_to_le16(frg_pl_size);
+ untd_hdr_rst->padding = 0;
+ if (buf_itr + frg_pl_size - wtx->buf
+ > wtx->buf_size) {
+ printk(KERN_ERR "BUG: no space for payload\n");
+ goto error_bug;
+ }
+ memcpy(buf_itr, pl_itr, frg_pl_size);
+ buf_itr += frg_pl_size;
+ pl_itr += frg_pl_size;
+ pl_size_left -= frg_pl_size;
+ d_printf(5, NULL,
+ "ITR PL 2: pl_size_left %zu buf_itr %zu frg_pl_size %zu\n",
+ pl_size_left, buf_itr - wtx->buf, frg_pl_size);
+ }
+ dev_kfree_skb_irq(skb);
+ return 0;
+
+error_bug:
+ printk(KERN_ERR
+ "BUG: skb %u bytes\n"
+ "BUG: frg_pl_size %zd i1480u_MAX_FRG_SIZE %u\n"
+ "BUG: buf_itr %zu buf_size %zu pl_size_left %zu\n",
+ skb->len,
+ frg_pl_size, i1480u_MAX_FRG_SIZE,
+ buf_itr - wtx->buf, wtx->buf_size, pl_size_left);
+
+ kfree(wtx->buf);
+error_buf_alloc:
+ return result;
+}
+
+
+/**
+ * Given a buffer that fits in a single fragment, fill out a @wtx
+ * struct for transmitting it down the USB pipe.
+ *
+ * Uses the fact that we have space reserved in front of the skbuff
+ * for hardware headers :]
+ *
+ * This does not fill the i1480 TX header, it is left up to the
+ * caller to do that; you can get it from @wtx->wlp_tx_hdr.
+ *
+ * @pl: pointer to payload data
+ * @pl_size: size of the payuload
+ *
+ * This function does not consume the @skb.
+ */
+static
+int i1480u_tx_create_1(struct i1480u_tx *wtx, struct sk_buff *skb,
+ gfp_t gfp_mask)
+{
+ struct untd_hdr_cmp *untd_hdr_cmp;
+ struct wlp_tx_hdr *wlp_tx_hdr;
+
+ wtx->buf = NULL;
+ wtx->skb = skb;
+ BUG_ON(skb_headroom(skb) < sizeof(*wlp_tx_hdr));
+ wlp_tx_hdr = (void *) __skb_push(skb, sizeof(*wlp_tx_hdr));
+ wtx->wlp_tx_hdr = wlp_tx_hdr;
+ BUG_ON(skb_headroom(skb) < sizeof(*untd_hdr_cmp));
+ untd_hdr_cmp = (void *) __skb_push(skb, sizeof(*untd_hdr_cmp));
+
+ untd_hdr_set_type(&untd_hdr_cmp->hdr, i1480u_PKT_FRAG_CMP);
+ untd_hdr_set_rx_tx(&untd_hdr_cmp->hdr, 0);
+ untd_hdr_cmp->hdr.len = cpu_to_le16(skb->len - sizeof(*untd_hdr_cmp));
+ untd_hdr_cmp->padding = 0;
+ return 0;
+}
+
+
+/**
+ * Given a skb to transmit, massage it to become palatable for the TX pipe
+ *
+ * This will break the buffer in chunks smaller than
+ * i1480u_MAX_FRG_SIZE and add proper headers to each.
+ *
+ * 1st header \
+ * i1480 tx header | fragment 1
+ * fragment data /
+ * nxt header \ fragment 2
+ * fragment data /
+ * ..
+ * ..
+ * last header \ fragment 3
+ * last fragment data /
+ *
+ * Each fragment will be always smaller or equal to i1480u_MAX_FRG_SIZE.
+ *
+ * If the first fragment is smaller than i1480u_MAX_FRG_SIZE, then the
+ * following is composed:
+ *
+ * complete header \
+ * i1480 tx header | single fragment
+ * packet data /
+ *
+ * We were going to use s/g support, but because the interface is
+ * synch and at the end there is plenty of overhead to do it, it
+ * didn't seem that worth for data that is going to be smaller than
+ * one page.
+ */
+static
+struct i1480u_tx *i1480u_tx_create(struct i1480u *i1480u,
+ struct sk_buff *skb, gfp_t gfp_mask)
+{
+ int result;
+ struct usb_endpoint_descriptor *epd;
+ int usb_pipe;
+ unsigned long flags;
+
+ struct i1480u_tx *wtx;
+ const size_t pl_max_size =
+ i1480u_MAX_FRG_SIZE - sizeof(struct untd_hdr_cmp)
+ - sizeof(struct wlp_tx_hdr);
+
+ wtx = kmalloc(sizeof(*wtx), gfp_mask);
+ if (wtx == NULL)
+ goto error_wtx_alloc;
+ wtx->urb = usb_alloc_urb(0, gfp_mask);
+ if (wtx->urb == NULL)
+ goto error_urb_alloc;
+ epd = &i1480u->usb_iface->cur_altsetting->endpoint[2].desc;
+ usb_pipe = usb_sndbulkpipe(i1480u->usb_dev, epd->bEndpointAddress);
+ /* Fits in a single complete packet or need to split? */
+ if (skb->len > pl_max_size) {
+ result = i1480u_tx_create_n(wtx, skb, gfp_mask);
+ if (result < 0)
+ goto error_create;
+ usb_fill_bulk_urb(wtx->urb, i1480u->usb_dev, usb_pipe,
+ wtx->buf, wtx->buf_size, i1480u_tx_cb, wtx);
+ } else {
+ result = i1480u_tx_create_1(wtx, skb, gfp_mask);
+ if (result < 0)
+ goto error_create;
+ usb_fill_bulk_urb(wtx->urb, i1480u->usb_dev, usb_pipe,
+ skb->data, skb->len, i1480u_tx_cb, wtx);
+ }
+ spin_lock_irqsave(&i1480u->tx_list_lock, flags);
+ list_add(&wtx->list_node, &i1480u->tx_list);
+ spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
+ return wtx;
+
+error_create:
+ kfree(wtx->urb);
+error_urb_alloc:
+ kfree(wtx);
+error_wtx_alloc:
+ return NULL;
+}
+
+/**
+ * Actual fragmentation and transmission of frame
+ *
+ * @wlp: WLP substack data structure
+ * @skb: To be transmitted
+ * @dst: Device address of destination
+ * @returns: 0 on success, <0 on failure
+ *
+ * This function can also be called directly (not just from
+ * hard_start_xmit), so we also check here if the interface is up before
+ * taking sending anything.
+ */
+int i1480u_xmit_frame(struct wlp *wlp, struct sk_buff *skb,
+ struct uwb_dev_addr *dst)
+{
+ int result = -ENXIO;
+ struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp);
+ struct device *dev = &i1480u->usb_iface->dev;
+ struct net_device *net_dev = i1480u->net_dev;
+ struct i1480u_tx *wtx;
+ struct wlp_tx_hdr *wlp_tx_hdr;
+ static unsigned char dev_bcast[2] = { 0xff, 0xff };
+#if 0
+ int lockup = 50;
+#endif
+
+ d_fnstart(6, dev, "(skb %p (%u), net_dev %p)\n", skb, skb->len,
+ net_dev);
+ BUG_ON(i1480u->wlp.rc == NULL);
+ if ((net_dev->flags & IFF_UP) == 0)
+ goto out;
+ result = -EBUSY;
+ if (atomic_read(&i1480u->tx_inflight.count) >= i1480u->tx_inflight.max) {
+ if (d_test(2) && printk_ratelimit())
+ d_printf(2, dev, "Max frames in flight "
+ "stopping queue.\n");
+ netif_stop_queue(net_dev);
+ goto error_max_inflight;
+ }
+ result = -ENOMEM;
+ wtx = i1480u_tx_create(i1480u, skb, GFP_ATOMIC);
+ if (unlikely(wtx == NULL)) {
+ if (printk_ratelimit())
+ dev_err(dev, "TX: no memory for WLP TX URB,"
+ "dropping packet (in flight %d)\n",
+ atomic_read(&i1480u->tx_inflight.count));
+ netif_stop_queue(net_dev);
+ goto error_wtx_alloc;
+ }
+ wtx->i1480u = i1480u;
+ /* Fill out the i1480 header; @i1480u->def_tx_hdr read without
+ * locking. We do so because they are kind of orthogonal to
+ * each other (and thus not changed in an atomic batch).
+ * The ETH header is right after the WLP TX header. */
+ wlp_tx_hdr = wtx->wlp_tx_hdr;
+ *wlp_tx_hdr = i1480u->options.def_tx_hdr;
+ wlp_tx_hdr->dstaddr = *dst;
+ if (!memcmp(&wlp_tx_hdr->dstaddr, dev_bcast, sizeof(dev_bcast))
+ && (wlp_tx_hdr_delivery_id_type(wlp_tx_hdr) & WLP_DRP)) {
+ /*Broadcast message directed to DRP host. Send as best effort
+ * on PCA. */
+ wlp_tx_hdr_set_delivery_id_type(wlp_tx_hdr, i1480u->options.pca_base_priority);
+ }
+
+#if 0
+ dev_info(dev, "TX delivering skb -> USB, %zu bytes\n", skb->len);
+ dump_bytes(dev, skb->data, skb->len > 72 ? 72 : skb->len);
+#endif
+#if 0
+ /* simulates a device lockup after every lockup# packets */
+ if (lockup && ((i1480u->stats.tx_packets + 1) % lockup) == 0) {
+ /* Simulate a dropped transmit interrupt */
+ net_dev->trans_start = jiffies;
+ netif_stop_queue(net_dev);
+ dev_err(dev, "Simulate lockup at %ld\n", jiffies);
+ return result;
+ }
+#endif
+
+ result = usb_submit_urb(wtx->urb, GFP_ATOMIC); /* Go baby */
+ if (result < 0) {
+ dev_err(dev, "TX: cannot submit URB: %d\n", result);
+ /* We leave the freeing of skb to calling function */
+ wtx->skb = NULL;
+ goto error_tx_urb_submit;
+ }
+ atomic_inc(&i1480u->tx_inflight.count);
+ net_dev->trans_start = jiffies;
+ d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len,
+ net_dev, result);
+ return result;
+
+error_tx_urb_submit:
+ i1480u_tx_destroy(i1480u, wtx);
+error_wtx_alloc:
+error_max_inflight:
+out:
+ d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len,
+ net_dev, result);
+ return result;
+}
+
+
+/**
+ * Transmit an skb Called when an skbuf has to be transmitted
+ *
+ * The skb is first passed to WLP substack to ensure this is a valid
+ * frame. If valid the device address of destination will be filled and
+ * the WLP header prepended to the skb. If this step fails we fake sending
+ * the frame, if we return an error the network stack will just keep trying.
+ *
+ * Broadcast frames inside a WSS needs to be treated special as multicast is
+ * not supported. A broadcast frame is sent as unicast to each member of the
+ * WSS - this is done by the WLP substack when it finds a broadcast frame.
+ * So, we test if the WLP substack took over the skb and only transmit it
+ * if it has not (been taken over).
+ *
+ * @net_dev->xmit_lock is held
+ */
+int i1480u_hard_start_xmit(struct sk_buff *skb, struct net_device *net_dev)
+{
+ int result;
+ struct i1480u *i1480u = netdev_priv(net_dev);
+ struct device *dev = &i1480u->usb_iface->dev;
+ struct uwb_dev_addr dst;
+
+ d_fnstart(6, dev, "(skb %p (%u), net_dev %p)\n", skb, skb->len,
+ net_dev);
+ BUG_ON(i1480u->wlp.rc == NULL);
+ if ((net_dev->flags & IFF_UP) == 0)
+ goto error;
+ result = wlp_prepare_tx_frame(dev, &i1480u->wlp, skb, &dst);
+ if (result < 0) {
+ dev_err(dev, "WLP verification of TX frame failed (%d). "
+ "Dropping packet.\n", result);
+ goto error;
+ } else if (result == 1) {
+ d_printf(6, dev, "WLP will transmit frame. \n");
+ /* trans_start time will be set when WLP actually transmits
+ * the frame */
+ goto out;
+ }
+ d_printf(6, dev, "Transmitting frame. \n");
+ result = i1480u_xmit_frame(&i1480u->wlp, skb, &dst);
+ if (result < 0) {
+ dev_err(dev, "Frame TX failed (%d).\n", result);
+ goto error;
+ }
+ d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len,
+ net_dev, result);
+ return NETDEV_TX_OK;
+error:
+ dev_kfree_skb_any(skb);
+ i1480u->stats.tx_dropped++;
+out:
+ d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len,
+ net_dev, result);
+ return NETDEV_TX_OK;
+}
+
+
+/**
+ * Called when a pkt transmission doesn't complete in a reasonable period
+ * Device reset may sleep - do it outside of interrupt context (delayed)
+ */
+void i1480u_tx_timeout(struct net_device *net_dev)
+{
+ struct i1480u *i1480u = netdev_priv(net_dev);
+
+ wlp_reset_all(&i1480u->wlp);
+}
+
+
+void i1480u_tx_release(struct i1480u *i1480u)
+{
+ unsigned long flags;
+ struct i1480u_tx *wtx, *next;
+ int count = 0, empty;
+
+ spin_lock_irqsave(&i1480u->tx_list_lock, flags);
+ list_for_each_entry_safe(wtx, next, &i1480u->tx_list, list_node) {
+ count++;
+ usb_unlink_urb(wtx->urb);
+ }
+ spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
+ count = count*10; /* i1480ut 200ms per unlinked urb (intervals of 20ms) */
+ /*
+ * We don't like this sollution too much (dirty as it is), but
+ * it is cheaper than putting a refcount on each i1480u_tx and
+ * i1480uting for all of them to go away...
+ *
+ * Called when no more packets can be added to tx_list
+ * so can i1480ut for it to be empty.
+ */
+ while (1) {
+ spin_lock_irqsave(&i1480u->tx_list_lock, flags);
+ empty = list_empty(&i1480u->tx_list);
+ spin_unlock_irqrestore(&i1480u->tx_list_lock, flags);
+ if (empty)
+ break;
+ count--;
+ BUG_ON(count == 0);
+ msleep(20);
+ }
+}