[PATCH v3 4/4] net: ntb_netdev: Support ethtool channels for multi-queue
From: Koichiro Den
Date: Thu Mar 05 2026 - 10:58:55 EST
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@xxxxxxxxxxxxx>
---
Changes in v3:
- Remove redundant checks already handled by ethtool core.
- Split ntb_set_channels() into helper functions.
- Drop ntb_netdev_sync_subqueues(), which did more than necessary in
some call sites. Adjust the original call sites to perform only the
required operations.
drivers/net/ntb_netdev.c | 157 +++++++++++++++++++++++++++++++++++++--
1 file changed, 151 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c
index ac39652b0488..6397d898c991 100644
--- a/drivers/net/ntb_netdev.c
+++ b/drivers/net/ntb_netdev.c
@@ -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)
--
2.51.0