diff options
Diffstat (limited to 'drivers/net/xen-netback/xenbus.c')
-rw-r--r-- | drivers/net/xen-netback/xenbus.c | 101 |
1 files changed, 89 insertions, 12 deletions
diff --git a/drivers/net/xen-netback/xenbus.c b/drivers/net/xen-netback/xenbus.c index 358602f55afa..96c63dc2509e 100644 --- a/drivers/net/xen-netback/xenbus.c +++ b/drivers/net/xen-netback/xenbus.c @@ -160,6 +160,12 @@ static int netback_probe(struct xenbus_device *dev, if (err) pr_debug("Error writing feature-split-event-channels\n"); + /* Multi-queue support: This is an optional feature. */ + err = xenbus_printf(XBT_NIL, dev->nodename, + "multi-queue-max-queues", "%u", xenvif_max_queues); + if (err) + pr_debug("Error writing multi-queue-max-queues\n"); + err = xenbus_switch_state(dev, XenbusStateInitWait); if (err) goto fail; @@ -490,9 +496,25 @@ static void connect(struct backend_info *be) struct xenbus_device *dev = be->dev; unsigned long credit_bytes, credit_usec; unsigned int queue_index; - unsigned int requested_num_queues = 1; + unsigned int requested_num_queues; struct xenvif_queue *queue; + /* Check whether the frontend requested multiple queues + * and read the number requested. + */ + err = xenbus_scanf(XBT_NIL, dev->otherend, + "multi-queue-num-queues", + "%u", &requested_num_queues); + if (err < 0) { + requested_num_queues = 1; /* Fall back to single queue */ + } else if (requested_num_queues > xenvif_max_queues) { + /* buggy or malicious guest */ + xenbus_dev_fatal(dev, err, + "guest requested %u queues, exceeding the maximum of %u.", + requested_num_queues, xenvif_max_queues); + return; + } + err = xen_net_read_mac(dev, be->vif->fe_dev_addr); if (err) { xenbus_dev_fatal(dev, err, "parsing %s/mac", dev->nodename); @@ -502,6 +524,7 @@ static void connect(struct backend_info *be) xen_net_read_rate(dev, &credit_bytes, &credit_usec); read_xenbus_vif_flags(be); + /* Use the number of queues requested by the frontend */ be->vif->queues = vzalloc(requested_num_queues * sizeof(struct xenvif_queue)); rtnl_lock(); @@ -516,14 +539,33 @@ static void connect(struct backend_info *be) be->vif->dev->name, queue->id); err = xenvif_init_queue(queue); - if (err) + if (err) { + /* xenvif_init_queue() cleans up after itself on + * failure, but we need to clean up any previously + * initialised queues. Set num_queues to i so that + * earlier queues can be destroyed using the regular + * disconnect logic. + */ + rtnl_lock(); + netif_set_real_num_tx_queues(be->vif->dev, queue_index); + rtnl_unlock(); goto err; + } queue->remaining_credit = credit_bytes; err = connect_rings(be, queue); - if (err) + if (err) { + /* connect_rings() cleans up after itself on failure, + * but we need to clean up after xenvif_init_queue() here, + * and also clean up any previously initialised queues. + */ + xenvif_deinit_queue(queue); + rtnl_lock(); + netif_set_real_num_tx_queues(be->vif->dev, queue_index); + rtnl_unlock(); goto err; + } } xenvif_carrier_on(be->vif); @@ -540,6 +582,8 @@ static void connect(struct backend_info *be) return; err: + if (be->vif->dev->real_num_tx_queues > 0) + xenvif_disconnect(be->vif); /* Clean up existing queues */ vfree(be->vif->queues); be->vif->queues = NULL; rtnl_lock(); @@ -552,32 +596,62 @@ err: static int connect_rings(struct backend_info *be, struct xenvif_queue *queue) { struct xenbus_device *dev = be->dev; + unsigned int num_queues = queue->vif->dev->real_num_tx_queues; unsigned long tx_ring_ref, rx_ring_ref; unsigned int tx_evtchn, rx_evtchn; int err; + char *xspath; + size_t xspathsize; + const size_t xenstore_path_ext_size = 11; /* sufficient for "/queue-NNN" */ + + /* If the frontend requested 1 queue, or we have fallen back + * to single queue due to lack of frontend support for multi- + * queue, expect the remaining XenStore keys in the toplevel + * directory. Otherwise, expect them in a subdirectory called + * queue-N. + */ + if (num_queues == 1) { + xspath = kzalloc(strlen(dev->otherend) + 1, GFP_KERNEL); + if (!xspath) { + xenbus_dev_fatal(dev, -ENOMEM, + "reading ring references"); + return -ENOMEM; + } + strcpy(xspath, dev->otherend); + } else { + xspathsize = strlen(dev->otherend) + xenstore_path_ext_size; + xspath = kzalloc(xspathsize, GFP_KERNEL); + if (!xspath) { + xenbus_dev_fatal(dev, -ENOMEM, + "reading ring references"); + return -ENOMEM; + } + snprintf(xspath, xspathsize, "%s/queue-%u", dev->otherend, + queue->id); + } - err = xenbus_gather(XBT_NIL, dev->otherend, + err = xenbus_gather(XBT_NIL, xspath, "tx-ring-ref", "%lu", &tx_ring_ref, "rx-ring-ref", "%lu", &rx_ring_ref, NULL); if (err) { xenbus_dev_fatal(dev, err, "reading %s/ring-ref", - dev->otherend); - return err; + xspath); + goto err; } /* Try split event channels first, then single event channel. */ - err = xenbus_gather(XBT_NIL, dev->otherend, + err = xenbus_gather(XBT_NIL, xspath, "event-channel-tx", "%u", &tx_evtchn, "event-channel-rx", "%u", &rx_evtchn, NULL); if (err < 0) { - err = xenbus_scanf(XBT_NIL, dev->otherend, + err = xenbus_scanf(XBT_NIL, xspath, "event-channel", "%u", &tx_evtchn); if (err < 0) { xenbus_dev_fatal(dev, err, "reading %s/event-channel(-tx/rx)", - dev->otherend); - return err; + xspath); + goto err; } rx_evtchn = tx_evtchn; } @@ -590,10 +664,13 @@ static int connect_rings(struct backend_info *be, struct xenvif_queue *queue) "mapping shared-frames %lu/%lu port tx %u rx %u", tx_ring_ref, rx_ring_ref, tx_evtchn, rx_evtchn); - return err; + goto err; } - return 0; + err = 0; +err: /* Regular return falls through with err == 0 */ + kfree(xspath); + return err; } static int read_xenbus_vif_flags(struct backend_info *be) |