Skip to content

Commit

Permalink
net/hyperv: Fix the stop/wake queue mechanism
Browse files Browse the repository at this point in the history
The ring buffer is only used to pass meta data for outbound packets. The
actual payload is accessed by DMA from the host. So the stop/wake queue
mechanism based on counting and comparing number of pages sent v.s. number
of pages in the ring buffer is wrong. Also, there is a race condition in
the stop/wake queue calls, which can stop xmit queue forever.

The new stop/wake queue mechanism is based on the actual bytes used by
outbound packets in the ring buffer. The check for number of outstanding
sends after stop queue prevents the race condition that can cause wake
queue happening earlier than stop queue.

Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: K. Y. Srinivasan <kys@microsoft.com>
Reported-by: Long Li <longli@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Haiyang Zhang authored and Greg Kroah-Hartman committed Dec 10, 2011
1 parent 9d41c5b commit 1d06825
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 26 deletions.
14 changes: 11 additions & 3 deletions drivers/net/hyperv/netvsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ static void netvsc_send_completion(struct hv_device *device,
nvsc_packet->completion.send.send_completion_ctx);

atomic_dec(&net_device->num_outstanding_sends);

if (netif_queue_stopped(ndev))
netif_wake_queue(ndev);
} else {
netdev_err(ndev, "Unknown send completion packet type- "
"%d received!!\n", nvsp_packet->hdr.msg_type);
Expand Down Expand Up @@ -485,11 +488,16 @@ int netvsc_send(struct hv_device *device,

}

if (ret != 0)
if (ret == 0) {
atomic_inc(&net_device->num_outstanding_sends);
} else if (ret == -EAGAIN) {
netif_stop_queue(ndev);
if (atomic_read(&net_device->num_outstanding_sends) < 1)
netif_wake_queue(ndev);
} else {
netdev_err(ndev, "Unable to send packet %p ret %d\n",
packet, ret);
else
atomic_inc(&net_device->num_outstanding_sends);
}

return ret;
}
Expand Down
24 changes: 1 addition & 23 deletions drivers/net/hyperv/netvsc_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,10 @@
struct net_device_context {
/* point back to our device context */
struct hv_device *device_ctx;
atomic_t avail;
struct delayed_work dwork;
};


#define PACKET_PAGES_LOWATER 8
/* Need this many pages to handle worst case fragmented packet */
#define PACKET_PAGES_HIWATER (MAX_SKB_FRAGS + 2)

static int ring_size = 128;
module_param(ring_size, int, S_IRUGO);
MODULE_PARM_DESC(ring_size, "Ring buffer size (# of pages)");
Expand Down Expand Up @@ -144,18 +139,8 @@ static void netvsc_xmit_completion(void *context)

kfree(packet);

if (skb) {
struct net_device *net = skb->dev;
struct net_device_context *net_device_ctx = netdev_priv(net);
unsigned int num_pages = skb_shinfo(skb)->nr_frags + 2;

if (skb)
dev_kfree_skb_any(skb);

atomic_add(num_pages, &net_device_ctx->avail);
if (atomic_read(&net_device_ctx->avail) >=
PACKET_PAGES_HIWATER)
netif_wake_queue(net);
}
}

static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
Expand All @@ -167,8 +152,6 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)

/* Add 1 for skb->data and additional one for RNDIS */
num_pages = skb_shinfo(skb)->nr_frags + 1 + 1;
if (num_pages > atomic_read(&net_device_ctx->avail))
return NETDEV_TX_BUSY;

/* Allocate a netvsc packet based on # of frags. */
packet = kzalloc(sizeof(struct hv_netvsc_packet) +
Expand Down Expand Up @@ -218,10 +201,6 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net)
if (ret == 0) {
net->stats.tx_bytes += skb->len;
net->stats.tx_packets++;

atomic_sub(num_pages, &net_device_ctx->avail);
if (atomic_read(&net_device_ctx->avail) < PACKET_PAGES_LOWATER)
netif_stop_queue(net);
} else {
/* we are shutting down or bus overloaded, just drop packet */
net->stats.tx_dropped++;
Expand Down Expand Up @@ -391,7 +370,6 @@ static int netvsc_probe(struct hv_device *dev,

net_device_ctx = netdev_priv(net);
net_device_ctx->device_ctx = dev;
atomic_set(&net_device_ctx->avail, ring_size);
hv_set_drvdata(dev, net);
INIT_DELAYED_WORK(&net_device_ctx->dwork, netvsc_send_garp);

Expand Down

0 comments on commit 1d06825

Please sign in to comment.