mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
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;
|
||||
|
||||
if (test_bit(EVENT_RX_PAUSED, &dev->flags)) {
|
||||
skb_queue_tail(&dev->rxq_pause, skb);
|
||||
return;
|
||||
}
|
||||
|
||||
skb->protocol = eth_type_trans (skb, dev->net);
|
||||
dev->net->stats.rx_packets++;
|
||||
dev->net->stats.rx_bytes += skb->len;
|
||||
|
@ -525,6 +530,41 @@ static void intr_complete (struct urb *urb)
|
|||
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
|
||||
|
@ -623,6 +663,8 @@ int usbnet_stop (struct net_device *net)
|
|||
|
||||
usb_kill_urb(dev->interrupt);
|
||||
|
||||
usbnet_purge_paused_rxq(dev);
|
||||
|
||||
/* deferred work (task, timer, softirq) must also stop.
|
||||
* can't flush_scheduled_work() until we drop rtnl (later),
|
||||
* else workers could deadlock; so make workers a NOP.
|
||||
|
@ -1113,7 +1155,6 @@ static void usbnet_bh (unsigned long param)
|
|||
}
|
||||
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* 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->txq);
|
||||
skb_queue_head_init (&dev->done);
|
||||
skb_queue_head_init(&dev->rxq_pause);
|
||||
dev->bh.func = usbnet_bh;
|
||||
dev->bh.data = (unsigned long) dev;
|
||||
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)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -2328,6 +2335,8 @@ static void rndis_wlan_worker(struct work_struct *work)
|
|||
memcpy(evt.ap_addr.sa_data, bssid, ETH_ALEN);
|
||||
wireless_send_event(usbdev->net, SIOCGIWAP, &evt, NULL);
|
||||
}
|
||||
|
||||
usbnet_resume_rx(usbdev);
|
||||
}
|
||||
|
||||
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) {
|
||||
case RNDIS_STATUS_MEDIA_CONNECT:
|
||||
usbnet_pause_rx(usbdev);
|
||||
|
||||
devinfo(usbdev, "media connect");
|
||||
|
||||
/* queue work to avoid recursive calls into rndis_command */
|
||||
|
|
|
@ -53,6 +53,7 @@ struct usbnet {
|
|||
struct sk_buff_head rxq;
|
||||
struct sk_buff_head txq;
|
||||
struct sk_buff_head done;
|
||||
struct sk_buff_head rxq_pause;
|
||||
struct urb *interrupt;
|
||||
struct tasklet_struct bh;
|
||||
|
||||
|
@ -63,6 +64,7 @@ struct usbnet {
|
|||
# define EVENT_RX_MEMORY 2
|
||||
# define EVENT_STS_SPLIT 3
|
||||
# define EVENT_LINK_RESET 4
|
||||
# define EVENT_RX_PAUSED 5
|
||||
};
|
||||
|
||||
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_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_set_settings (struct net_device *net, struct ethtool_cmd *cmd);
|
||||
extern u32 usbnet_get_link (struct net_device *net);
|
||||
|
|
Loading…
Reference in a new issue