ibmveth: Ethtool set queue support
authorNick Child <nnac123@linux.ibm.com>
Wed, 28 Sep 2022 21:43:50 +0000 (16:43 -0500)
committerDavid S. Miller <davem@davemloft.net>
Fri, 30 Sep 2022 11:40:22 +0000 (12:40 +0100)
Implement channel management functions to allow dynamic addition and
removal of transmit queues. The `ethtool --show-channels` and
`ethtool --set-channels` commands can be used to get and set the
number of queues, respectively. Allow the ability to add as many
transmit queues as available processors but never allow more than the
hard maximum of 16. The number of receive queues is one and cannot be
modified.

Depending on whether the requested number of queues is larger or
smaller than the current value, either allocate or free long term
buffers. Since long term buffer construction and destruction can
occur in two different areas, from either channel set requests or
device open/close, define functions for performing this work. If
allocation of a new buffer fails, then attempt to revert back to the
previous number of queues.

Signed-off-by: Nick Child <nnac123@linux.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/ibm/ibmveth.c

index 7abd67c..3b14dc9 100644 (file)
@@ -141,6 +141,13 @@ static inline int ibmveth_rxq_csum_good(struct ibmveth_adapter *adapter)
        return ibmveth_rxq_flags(adapter) & IBMVETH_RXQ_CSUM_GOOD;
 }
 
+static unsigned int ibmveth_real_max_tx_queues(void)
+{
+       unsigned int n_cpu = num_online_cpus();
+
+       return min(n_cpu, IBMVETH_MAX_QUEUES);
+}
+
 /* setup the initial settings for a buffer pool */
 static void ibmveth_init_buffer_pool(struct ibmveth_buff_pool *pool,
                                     u32 pool_index, u32 pool_size,
@@ -456,6 +463,38 @@ static void ibmveth_rxq_harvest_buffer(struct ibmveth_adapter *adapter)
        }
 }
 
+static void ibmveth_free_tx_ltb(struct ibmveth_adapter *adapter, int idx)
+{
+       dma_unmap_single(&adapter->vdev->dev, adapter->tx_ltb_dma[idx],
+                        adapter->tx_ltb_size, DMA_TO_DEVICE);
+       kfree(adapter->tx_ltb_ptr[idx]);
+       adapter->tx_ltb_ptr[idx] = NULL;
+}
+
+static int ibmveth_allocate_tx_ltb(struct ibmveth_adapter *adapter, int idx)
+{
+       adapter->tx_ltb_ptr[idx] = kzalloc(adapter->tx_ltb_size,
+                                          GFP_KERNEL);
+       if (!adapter->tx_ltb_ptr[idx]) {
+               netdev_err(adapter->netdev,
+                          "unable to allocate tx long term buffer\n");
+               return -ENOMEM;
+       }
+       adapter->tx_ltb_dma[idx] = dma_map_single(&adapter->vdev->dev,
+                                                 adapter->tx_ltb_ptr[idx],
+                                                 adapter->tx_ltb_size,
+                                                 DMA_TO_DEVICE);
+       if (dma_mapping_error(&adapter->vdev->dev, adapter->tx_ltb_dma[idx])) {
+               netdev_err(adapter->netdev,
+                          "unable to DMA map tx long term buffer\n");
+               kfree(adapter->tx_ltb_ptr[idx]);
+               adapter->tx_ltb_ptr[idx] = NULL;
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
 static int ibmveth_register_logical_lan(struct ibmveth_adapter *adapter,
         union ibmveth_buf_desc rxq_desc, u64 mac_address)
 {
@@ -538,22 +577,9 @@ static int ibmveth_open(struct net_device *netdev)
                goto out_unmap_buffer_list;
        }
 
-       for (i = 0; i < IBMVETH_MAX_QUEUES; i++) {
-               adapter->tx_ltb_ptr[i] = kzalloc(adapter->tx_ltb_size,
-                                                GFP_KERNEL);
-               if (!adapter->tx_ltb_ptr[i]) {
-                       netdev_err(netdev,
-                                  "unable to allocate transmit long term buffer\n");
-                       goto out_free_tx_ltb_ptrs;
-               }
-               adapter->tx_ltb_dma[i] = dma_map_single(dev,
-                                                       adapter->tx_ltb_ptr[i],
-                                                       adapter->tx_ltb_size,
-                                                       DMA_TO_DEVICE);
-               if (dma_mapping_error(dev, adapter->tx_ltb_dma[i])) {
-                       netdev_err(netdev, "unable to DMA map transmit long term buffer\n");
-                       goto out_unmap_tx_dma;
-               }
+       for (i = 0; i < netdev->real_num_tx_queues; i++) {
+               if (ibmveth_allocate_tx_ltb(adapter, i))
+                       goto out_free_tx_ltb;
        }
 
        adapter->rx_queue.index = 0;
@@ -632,14 +658,9 @@ out_unmap_filter_list:
        dma_unmap_single(dev, adapter->filter_list_dma, 4096,
                         DMA_BIDIRECTIONAL);
 
-out_unmap_tx_dma:
-       kfree(adapter->tx_ltb_ptr[i]);
-
-out_free_tx_ltb_ptrs:
+out_free_tx_ltb:
        while (--i >= 0) {
-               dma_unmap_single(dev, adapter->tx_ltb_dma[i],
-                                adapter->tx_ltb_size, DMA_TO_DEVICE);
-               kfree(adapter->tx_ltb_ptr[i]);
+               ibmveth_free_tx_ltb(adapter, i);
        }
 
 out_unmap_buffer_list:
@@ -704,11 +725,8 @@ static int ibmveth_close(struct net_device *netdev)
                        ibmveth_free_buffer_pool(adapter,
                                                 &adapter->rx_buff_pool[i]);
 
-       for (i = 0; i < IBMVETH_MAX_QUEUES; i++) {
-               dma_unmap_single(dev, adapter->tx_ltb_dma[i],
-                                adapter->tx_ltb_size, DMA_TO_DEVICE);
-               kfree(adapter->tx_ltb_ptr[i]);
-       }
+       for (i = 0; i < netdev->real_num_tx_queues; i++)
+               ibmveth_free_tx_ltb(adapter, i);
 
        netdev_dbg(netdev, "close complete\n");
 
@@ -974,6 +992,69 @@ static void ibmveth_get_ethtool_stats(struct net_device *dev,
                data[i] = IBMVETH_GET_STAT(adapter, ibmveth_stats[i].offset);
 }
 
+static void ibmveth_get_channels(struct net_device *netdev,
+                                struct ethtool_channels *channels)
+{
+       channels->max_tx = ibmveth_real_max_tx_queues();
+       channels->tx_count = netdev->real_num_tx_queues;
+
+       channels->max_rx = netdev->real_num_rx_queues;
+       channels->rx_count = netdev->real_num_rx_queues;
+}
+
+static int ibmveth_set_channels(struct net_device *netdev,
+                               struct ethtool_channels *channels)
+{
+       struct ibmveth_adapter *adapter = netdev_priv(netdev);
+       unsigned int old = netdev->real_num_tx_queues,
+                    goal = channels->tx_count;
+       int rc, i;
+
+       /* If ndo_open has not been called yet then don't allocate, just set
+        * desired netdev_queue's and return
+        */
+       if (!(netdev->flags & IFF_UP))
+               return netif_set_real_num_tx_queues(netdev, goal);
+
+       /* We have IBMVETH_MAX_QUEUES netdev_queue's allocated
+        * but we may need to alloc/free the ltb's.
+        */
+       netif_tx_stop_all_queues(netdev);
+
+       /* Allocate any queue that we need */
+       for (i = old; i < goal; i++) {
+               if (adapter->tx_ltb_ptr[i])
+                       continue;
+
+               rc = ibmveth_allocate_tx_ltb(adapter, i);
+               if (!rc)
+                       continue;
+
+               /* if something goes wrong, free everything we just allocated */
+               netdev_err(netdev, "Failed to allocate more tx queues, returning to %d queues\n",
+                          old);
+               goal = old;
+               old = i;
+               break;
+       }
+       rc = netif_set_real_num_tx_queues(netdev, goal);
+       if (rc) {
+               netdev_err(netdev, "Failed to set real tx queues, returning to %d queues\n",
+                          old);
+               goal = old;
+               old = i;
+       }
+       /* Free any that are no longer needed */
+       for (i = old; i > goal; i--) {
+               if (adapter->tx_ltb_ptr[i - 1])
+                       ibmveth_free_tx_ltb(adapter, i - 1);
+       }
+
+       netif_tx_wake_all_queues(netdev);
+
+       return rc;
+}
+
 static const struct ethtool_ops netdev_ethtool_ops = {
        .get_drvinfo                     = netdev_get_drvinfo,
        .get_link                        = ethtool_op_get_link,
@@ -982,6 +1063,8 @@ static const struct ethtool_ops netdev_ethtool_ops = {
        .get_ethtool_stats               = ibmveth_get_ethtool_stats,
        .get_link_ksettings              = ibmveth_get_link_ksettings,
        .set_link_ksettings              = ibmveth_set_link_ksettings,
+       .get_channels                    = ibmveth_get_channels,
+       .set_channels                    = ibmveth_set_channels
 };
 
 static int ibmveth_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
@@ -1609,7 +1692,6 @@ static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id)
        }
 
        netdev = alloc_etherdev_mqs(sizeof(struct ibmveth_adapter), IBMVETH_MAX_QUEUES, 1);
-
        if (!netdev)
                return -ENOMEM;
 
@@ -1675,7 +1757,16 @@ static int ibmveth_probe(struct vio_dev *dev, const struct vio_device_id *id)
                        kobject_uevent(kobj, KOBJ_ADD);
        }
 
+       rc = netif_set_real_num_tx_queues(netdev, ibmveth_real_max_tx_queues());
+       if (rc) {
+               netdev_dbg(netdev, "failed to set number of tx queues rc=%d\n",
+                          rc);
+               free_netdev(netdev);
+               return rc;
+       }
        adapter->tx_ltb_size = PAGE_ALIGN(IBMVETH_MAX_TX_BUF_SIZE);
+       for (i = 0; i < IBMVETH_MAX_QUEUES; i++)
+               adapter->tx_ltb_ptr[i] = NULL;
 
        netdev_dbg(netdev, "adapter @ 0x%p\n", adapter);
        netdev_dbg(netdev, "registering netdev...\n");