]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: ntb_netdev: Support ethtool channels for multi-queue
authorKoichiro Den <den@valinux.co.jp>
Thu, 5 Mar 2026 15:56:39 +0000 (00:56 +0900)
committerJakub Kicinski <kuba@kernel.org>
Sat, 7 Mar 2026 03:15:20 +0000 (19:15 -0800)
Support dynamic queue pair addition/removal via ethtool channels.
Use the combined channel count to control the number of netdev TX/RX
queues, each corresponding to a ntb_transport queue pair.

When the number of queues is reduced, tear down and free the removed
ntb_transport queue pairs (not just deactivate them) so other
ntb_transport clients can reuse the freed resources.

When the number of queues is increased, create additional queue pairs up
to NTB_NETDEV_MAX_QUEUES (=64). The effective limit is determined by the
underlying ntb_transport implementation and NTB hardware resources (the
number of MWs), so set_channels may return -ENOSPC if no more QPs can be
allocated.

Keep the default at one queue pair to preserve the previous behavior.

Signed-off-by: Koichiro Den <den@valinux.co.jp>
Link: https://patch.msgid.link/20260305155639.1885517-5-den@valinux.co.jp
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/ntb_netdev.c

index ac39652b04881244cbc0bc112e3e86d112ce8cbf..6397d898c9915b6a4cf719bbeb8e1a71c3506f32 100644 (file)
@@ -284,6 +284,12 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data,
        }
 }
 
+static const struct ntb_queue_handlers ntb_netdev_handlers = {
+       .tx_handler = ntb_netdev_tx_handler,
+       .rx_handler = ntb_netdev_rx_handler,
+       .event_handler = ntb_netdev_event_handler,
+};
+
 static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb,
                                         struct net_device *ndev)
 {
@@ -492,16 +498,155 @@ static int ntb_get_link_ksettings(struct net_device *dev,
        return 0;
 }
 
+static void ntb_get_channels(struct net_device *ndev,
+                            struct ethtool_channels *channels)
+{
+       struct ntb_netdev *dev = netdev_priv(ndev);
+
+       channels->combined_count = dev->num_queues;
+       channels->max_combined = ndev->num_tx_queues;
+}
+
+static int ntb_inc_channels(struct net_device *ndev,
+                           unsigned int old, unsigned int new)
+{
+       struct ntb_netdev *dev = netdev_priv(ndev);
+       bool running = netif_running(ndev);
+       struct ntb_netdev_queue *queue;
+       unsigned int q, created;
+       int rc;
+
+       created = old;
+       for (q = old; q < new; q++) {
+               queue = &dev->queues[q];
+
+               queue->ntdev = dev;
+               queue->qid = q;
+               queue->qp = ntb_transport_create_queue(queue, dev->client_dev,
+                                                      &ntb_netdev_handlers);
+               if (!queue->qp) {
+                       rc = -ENOSPC;
+                       goto err_new;
+               }
+               created++;
+
+               if (!running)
+                       continue;
+
+               timer_setup(&queue->tx_timer, ntb_netdev_tx_timer, 0);
+
+               rc = ntb_netdev_queue_rx_fill(ndev, queue);
+               if (rc)
+                       goto err_new;
+
+               /*
+                * Carrier may already be on due to other QPs. Keep the new
+                * subqueue stopped until we get a Link Up event for this QP.
+                */
+               netif_stop_subqueue(ndev, q);
+       }
+
+       rc = netif_set_real_num_queues(ndev, new, new);
+       if (rc)
+               goto err_new;
+
+       dev->num_queues = new;
+
+       if (running)
+               for (q = old; q < new; q++)
+                       ntb_transport_link_up(dev->queues[q].qp);
+
+       return 0;
+
+err_new:
+       if (running) {
+               unsigned int rollback = created;
+
+               while (rollback-- > old) {
+                       queue = &dev->queues[rollback];
+                       ntb_transport_link_down(queue->qp);
+                       ntb_netdev_queue_rx_drain(queue);
+                       timer_delete_sync(&queue->tx_timer);
+               }
+       }
+       while (created-- > old) {
+               queue = &dev->queues[created];
+               ntb_transport_free_queue(queue->qp);
+               queue->qp = NULL;
+       }
+       return rc;
+}
+
+static int ntb_dec_channels(struct net_device *ndev,
+                           unsigned int old, unsigned int new)
+{
+       struct ntb_netdev *dev = netdev_priv(ndev);
+       bool running = netif_running(ndev);
+       struct ntb_netdev_queue *queue;
+       unsigned int q;
+       int rc;
+
+       if (running)
+               for (q = new; q < old; q++)
+                       netif_stop_subqueue(ndev, q);
+
+       rc = netif_set_real_num_queues(ndev, new, new);
+       if (rc)
+               goto err;
+
+       /* Publish new queue count before invalidating QP pointers */
+       dev->num_queues = new;
+
+       for (q = new; q < old; q++) {
+               queue = &dev->queues[q];
+
+               if (running) {
+                       ntb_transport_link_down(queue->qp);
+                       ntb_netdev_queue_rx_drain(queue);
+                       timer_delete_sync(&queue->tx_timer);
+               }
+
+               ntb_transport_free_queue(queue->qp);
+               queue->qp = NULL;
+       }
+
+       /*
+        * It might be the case that the removed queues are the only queues that
+        * were up, so see if the global carrier needs to change.
+        */
+       ntb_netdev_update_carrier(dev);
+       return 0;
+
+err:
+       if (running) {
+               for (q = new; q < old; q++)
+                       netif_wake_subqueue(ndev, q);
+       }
+       return rc;
+}
+
+static int ntb_set_channels(struct net_device *ndev,
+                           struct ethtool_channels *channels)
+{
+       struct ntb_netdev *dev = netdev_priv(ndev);
+       unsigned int new = channels->combined_count;
+       unsigned int old = dev->num_queues;
+
+       if (new == old)
+               return 0;
+
+       if (new < old)
+               return ntb_dec_channels(ndev, old, new);
+       else
+               return ntb_inc_channels(ndev, old, new);
+}
+
 static const struct ethtool_ops ntb_ethtool_ops = {
        .get_drvinfo = ntb_get_drvinfo,
        .get_link = ethtool_op_get_link,
        .get_link_ksettings = ntb_get_link_ksettings,
-};
-
-static const struct ntb_queue_handlers ntb_netdev_handlers = {
-       .tx_handler = ntb_netdev_tx_handler,
-       .rx_handler = ntb_netdev_rx_handler,
-       .event_handler = ntb_netdev_event_handler,
+       .get_channels = ntb_get_channels,
+       .set_channels = ntb_set_channels,
 };
 
 static int ntb_netdev_probe(struct device *client_dev)