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: Jussi Kivilinna <jussi.kivilinna@mbnet.fi> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
parent
d4de9532fd
commit
7834ddbcc7
3 changed files with 61 additions and 2 deletions
|
@ -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)
|
||||||
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)
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
/*-------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* USB Device Driver support
|
* USB Device Driver support
|
||||||
|
@ -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,
|
||||||
|
|
||||||
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 @@ get_bssid:
|
||||||
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)
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
# 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);
|
||||||
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);
|
||||||
|
|
Loading…
Add table
Reference in a new issue