Commit 7834ddbc authored by Jussi Kivilinna's avatar Jussi Kivilinna Committed by John W. Linville

usbnet: add rx queue pausing

Add rx queue pausing to usbnet. This is needed by rndis_wlan so that it can
control rx queue and prevent received packets from being send forward before
rndis_wlan receives and handles 'media connect'-indication. Without this
establishing WPA connections is hard and fail often.

[v2] - removed unneeded use of skb_clone

Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarJussi Kivilinna <jussi.kivilinna@mbnet.fi>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent d4de9532
...@@ -233,6 +233,11 @@ void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb) ...@@ -233,6 +233,11 @@ void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb)
{ {
int status; int status;
if (test_bit(EVENT_RX_PAUSED, &dev->flags)) {
skb_queue_tail(&dev->rxq_pause, skb);
return;
}
skb->protocol = eth_type_trans (skb, dev->net); skb->protocol = eth_type_trans (skb, dev->net);
dev->net->stats.rx_packets++; dev->net->stats.rx_packets++;
dev->net->stats.rx_bytes += skb->len; dev->net->stats.rx_bytes += skb->len;
...@@ -525,6 +530,41 @@ static void intr_complete (struct urb *urb) ...@@ -525,6 +530,41 @@ static void intr_complete (struct urb *urb)
deverr(dev, "intr resubmit --> %d", status); deverr(dev, "intr resubmit --> %d", status);
} }
/*-------------------------------------------------------------------------*/
void usbnet_pause_rx(struct usbnet *dev)
{
set_bit(EVENT_RX_PAUSED, &dev->flags);
if (netif_msg_rx_status(dev))
devdbg(dev, "paused rx queue enabled");
}
EXPORT_SYMBOL_GPL(usbnet_pause_rx);
void usbnet_resume_rx(struct usbnet *dev)
{
struct sk_buff *skb;
int num = 0;
clear_bit(EVENT_RX_PAUSED, &dev->flags);
while ((skb = skb_dequeue(&dev->rxq_pause)) != NULL) {
usbnet_skb_return(dev, skb);
num++;
}
tasklet_schedule(&dev->bh);
if (netif_msg_rx_status(dev))
devdbg(dev, "paused rx queue disabled, %d skbs requeued", num);
}
EXPORT_SYMBOL_GPL(usbnet_resume_rx);
void usbnet_purge_paused_rxq(struct usbnet *dev)
{
skb_queue_purge(&dev->rxq_pause);
}
EXPORT_SYMBOL_GPL(usbnet_purge_paused_rxq);
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
// unlink pending rx/tx; completion handlers do all other cleanup // unlink pending rx/tx; completion handlers do all other cleanup
...@@ -623,6 +663,8 @@ int usbnet_stop (struct net_device *net) ...@@ -623,6 +663,8 @@ int usbnet_stop (struct net_device *net)
usb_kill_urb(dev->interrupt); usb_kill_urb(dev->interrupt);
usbnet_purge_paused_rxq(dev);
/* deferred work (task, timer, softirq) must also stop. /* deferred work (task, timer, softirq) must also stop.
* can't flush_scheduled_work() until we drop rtnl (later), * can't flush_scheduled_work() until we drop rtnl (later),
* else workers could deadlock; so make workers a NOP. * else workers could deadlock; so make workers a NOP.
...@@ -1113,7 +1155,6 @@ static void usbnet_bh (unsigned long param) ...@@ -1113,7 +1155,6 @@ static void usbnet_bh (unsigned long param)
} }
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* USB Device Driver support * USB Device Driver support
...@@ -1210,6 +1251,7 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod) ...@@ -1210,6 +1251,7 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
skb_queue_head_init (&dev->rxq); skb_queue_head_init (&dev->rxq);
skb_queue_head_init (&dev->txq); skb_queue_head_init (&dev->txq);
skb_queue_head_init (&dev->done); skb_queue_head_init (&dev->done);
skb_queue_head_init(&dev->rxq_pause);
dev->bh.func = usbnet_bh; dev->bh.func = usbnet_bh;
dev->bh.data = (unsigned long) dev; dev->bh.data = (unsigned long) dev;
INIT_WORK (&dev->kevent, kevent); INIT_WORK (&dev->kevent, kevent);
......
...@@ -1764,8 +1764,15 @@ static int rndis_iw_set_essid(struct net_device *dev, ...@@ -1764,8 +1764,15 @@ static int rndis_iw_set_essid(struct net_device *dev,
if (!wrqu->essid.flags || length == 0) if (!wrqu->essid.flags || length == 0)
return disassociate(usbdev, 1); return disassociate(usbdev, 1);
else else {
/* Pause and purge rx queue, so we don't pass packets before
* 'media connect'-indication.
*/
usbnet_pause_rx(usbdev);
usbnet_purge_paused_rxq(usbdev);
return set_essid(usbdev, &ssid); return set_essid(usbdev, &ssid);
}
} }
...@@ -2328,6 +2335,8 @@ static void rndis_wlan_worker(struct work_struct *work) ...@@ -2328,6 +2335,8 @@ static void rndis_wlan_worker(struct work_struct *work)
memcpy(evt.ap_addr.sa_data, bssid, ETH_ALEN); memcpy(evt.ap_addr.sa_data, bssid, ETH_ALEN);
wireless_send_event(usbdev->net, SIOCGIWAP, &evt, NULL); wireless_send_event(usbdev->net, SIOCGIWAP, &evt, NULL);
} }
usbnet_resume_rx(usbdev);
} }
if (test_and_clear_bit(WORK_LINK_DOWN, &priv->work_pending)) { if (test_and_clear_bit(WORK_LINK_DOWN, &priv->work_pending)) {
...@@ -2541,6 +2550,8 @@ static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen) ...@@ -2541,6 +2550,8 @@ static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen)
switch (msg->status) { switch (msg->status) {
case RNDIS_STATUS_MEDIA_CONNECT: case RNDIS_STATUS_MEDIA_CONNECT:
usbnet_pause_rx(usbdev);
devinfo(usbdev, "media connect"); devinfo(usbdev, "media connect");
/* queue work to avoid recursive calls into rndis_command */ /* queue work to avoid recursive calls into rndis_command */
......
...@@ -53,6 +53,7 @@ struct usbnet { ...@@ -53,6 +53,7 @@ struct usbnet {
struct sk_buff_head rxq; struct sk_buff_head rxq;
struct sk_buff_head txq; struct sk_buff_head txq;
struct sk_buff_head done; struct sk_buff_head done;
struct sk_buff_head rxq_pause;
struct urb *interrupt; struct urb *interrupt;
struct tasklet_struct bh; struct tasklet_struct bh;
...@@ -63,6 +64,7 @@ struct usbnet { ...@@ -63,6 +64,7 @@ struct usbnet {
# define EVENT_RX_MEMORY 2 # define EVENT_RX_MEMORY 2
# define EVENT_STS_SPLIT 3 # define EVENT_STS_SPLIT 3
# define EVENT_LINK_RESET 4 # define EVENT_LINK_RESET 4
# define EVENT_RX_PAUSED 5
}; };
static inline struct usb_driver *driver_of(struct usb_interface *intf) static inline struct usb_driver *driver_of(struct usb_interface *intf)
...@@ -190,6 +192,10 @@ extern void usbnet_defer_kevent (struct usbnet *, int); ...@@ -190,6 +192,10 @@ extern void usbnet_defer_kevent (struct usbnet *, int);
extern void usbnet_skb_return (struct usbnet *, struct sk_buff *); extern void usbnet_skb_return (struct usbnet *, struct sk_buff *);
extern void usbnet_unlink_rx_urbs(struct usbnet *); extern void usbnet_unlink_rx_urbs(struct usbnet *);
extern void usbnet_pause_rx(struct usbnet *);
extern void usbnet_resume_rx(struct usbnet *);
extern void usbnet_purge_paused_rxq(struct usbnet *);
extern int usbnet_get_settings (struct net_device *net, struct ethtool_cmd *cmd); extern int usbnet_get_settings (struct net_device *net, struct ethtool_cmd *cmd);
extern int usbnet_set_settings (struct net_device *net, struct ethtool_cmd *cmd); extern int usbnet_set_settings (struct net_device *net, struct ethtool_cmd *cmd);
extern u32 usbnet_get_link (struct net_device *net); extern u32 usbnet_get_link (struct net_device *net);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment