Teardown first on ethtool -L scale-down: drain and deregister/destroy queues being removed before unmapping their buffer pools. Freeing pool state while stale correlators can still complete was the hang root cause; poll-path skip helpers alone cannot paper over that ownership hole. Strengthen correlator liveness (active pool + non-NULL backing arrays) and clear freed pool metadata so torn-down pools no longer look in-range. Restore schedule_work escalation for truly impossible correlators. Also implement incremental scale-up/down helpers, WRITE_ONCE num_rx_queues publication, rollback/CMO updates, and related resize error paths. After enable_irq on scale-up (and scale-down rollback unmask), kick NAPI if the RXQ already has pending work. After successful scale-down destroy, restart remaining queues so Q0 cannot stay masked with NAPI idle. On scale-up, raise CMO desired for the target RX queue count via ibmveth_desired_dma_for_rxqs() before dma_map / alloc / replenish. ibmveth_resize_rx_channels() rejects rx > 1 without MQ firmware (-EOPNOTSUPP) and validates rx_count within 1..IBMVETH_MAX_RX_QUEUES (-EINVAL). The set_channels wiring patch consumes this helper. Signed-off-by: Mingming Cao Reviewed-by: Dave Marquardt Tested-by: Shaik Abdulla --- Changes in v5: - resize / get_desired_dma read live count via get_num_rx_queues() - Scale-down / scale-up-fail: remask+sync after napi_disable (poll may re-arm while disable waits) - Series renumber: mailed v4 12/14 resize -> tip P14 (14->15) - Absorb kitchen-sink resize ownership: teardown-first scale-down (drain/deregister before unmap) + live-pool correlator_valid / schedule_work (cover has peel map) - Ordered num_rx_queues publish + publish-before-destroy - After unmask: kick if pending; enable_irq errno + rollback check; raise CMO before scale-up allocs - Scale-up enable_irq-fail: publish-down / napi_disable / drain / destroy - After scale-down destroy, restart remaining RX queues so Q0 cannot stay masked with NAPI idle - resize_rx_channels: reject rx>1 without MQ / validate 1..MAX; call before !opened early return (stash still tip P15) Changes in v4: - Copy pool->index when cloning buffer pools for incrementally added queues. - Scale-up order: publish -> replenish -> napi_enable -> enable_irq (vs older enable-before-publish drafts); mirror NAPI-before-unmask on set_real_num_rx rollback. - Linux-owned subordinate virq disposal; deregister is PHYP-only; dispose bound to MAX_RX_QUEUES; dispose on request_irq failure. - Drain-path smp_rmb() before harvest. - Mask PHYP before napi_disable/drain on scale-down and scale-up fail cleanup. - Replenish before re-enabling IRQ/NAPI on set_real_num_rx scale-down rollback. - Keep set_channels wiring as the following patch (same split as v3) but call resize_rx_channels() here when IFF_UP so the helper is not an unused static. drivers/net/ethernet/ibm/ibmveth.c | 682 +++++++++++++++++++++++++++-- 1 file changed, 646 insertions(+), 36 deletions(-) diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c index 1b58a3c6ce77..84f4a0deb0c5 100644 --- a/drivers/net/ethernet/ibm/ibmveth.c +++ b/drivers/net/ethernet/ibm/ibmveth.c @@ -696,6 +696,58 @@ ibmveth_cleanup_rx_interrupts(struct ibmveth_adapter *adapter) adapter->rx_irq_setup = false; } +/** + * ibmveth_setup_single_rx_interrupt - Setup interrupt for a single RX queue + * @adapter: ibmveth adapter structure + * @queue_idx: Queue index to setup + * + * Registers the IRQ handler for one queue. Used during incremental + * scale-up when adding new RX queues. The caller publishes the queue, + * replenishes buffers, enables NAPI, then unmasks PHYP delivery. + * + * Return: 0 on success, negative error code on failure + */ +static int +ibmveth_setup_single_rx_interrupt(struct ibmveth_adapter *adapter, + int queue_idx) +{ + struct net_device *netdev = adapter->netdev; + int rc; + + rc = request_irq(adapter->queue_irq[queue_idx], ibmveth_interrupt, + 0, netdev->name, &adapter->napi[queue_idx]); + if (rc) { + netdev_err(netdev, "request_irq() failed for queue %d: %d\n", + queue_idx, rc); + return rc; + } + + netdev_dbg(netdev, "Setup IRQ %d for queue %d\n", + adapter->queue_irq[queue_idx], queue_idx); + return 0; +} + +/** + * ibmveth_cleanup_single_rx_interrupt - Cleanup interrupt for a single RX queue + * @adapter: ibmveth adapter structure + * @queue_idx: Queue index to cleanup + * + * Frees the IRQ handler for one queue and releases the subordinate virq + * mapping. Used during incremental scale-down. + */ +static void +ibmveth_cleanup_single_rx_interrupt(struct ibmveth_adapter *adapter, + int queue_idx) +{ + if (adapter->queue_irq[queue_idx]) { + free_irq(adapter->queue_irq[queue_idx], + &adapter->napi[queue_idx]); + ibmveth_dispose_subordinate_irq_mapping(adapter, queue_idx); + netdev_dbg(adapter->netdev, + "Freed IRQ for queue %d\n", queue_idx); + } +} + /** * ibmveth_schedule_rx_queue - Mask PHYP IRQ and schedule NAPI for one RX queue * @adapter: ibmveth adapter structure @@ -1380,6 +1432,141 @@ ibmveth_free_buffer_pools(struct ibmveth_adapter *adapter) ibmveth_get_num_rx_queues(adapter)); } +/** + * ibmveth_alloc_single_rx_queue - Allocate resources for a single RX queue + * @adapter: ibmveth adapter structure + * @queue_idx: Queue index to allocate + * @rxq_entries: Number of RX queue entries + * + * Allocates buffer list, RX queue, and per-queue buffer pools for one queue. + * Used during incremental scale-up without affecting existing queues. + * + * Return: 0 on success, negative error code on failure + */ +static int +ibmveth_alloc_single_rx_queue(struct ibmveth_adapter *adapter, int queue_idx, + int rxq_entries) +{ + struct device *dev = &adapter->vdev->dev; + struct net_device *netdev = adapter->netdev; + int i, rc = -ENOMEM; + + adapter->buffer_list_addr[queue_idx] = + (void *)get_zeroed_page(GFP_KERNEL); + if (!adapter->buffer_list_addr[queue_idx]) { + netdev_err(netdev, "unable to allocate buffer list for queue %d\n", + queue_idx); + return -ENOMEM; + } + + adapter->rx_queue[queue_idx].queue_len = + sizeof(struct ibmveth_rx_q_entry) * rxq_entries; + adapter->rx_queue[queue_idx].queue_addr = + dma_alloc_coherent(dev, adapter->rx_queue[queue_idx].queue_len, + &adapter->rx_queue[queue_idx].queue_dma, + GFP_KERNEL); + if (!adapter->rx_queue[queue_idx].queue_addr) { + netdev_err(netdev, "unable to allocate RX queue for queue %d\n", + queue_idx); + goto out_free_buflist; + } + + adapter->buffer_list_dma[queue_idx] = + dma_map_single(dev, adapter->buffer_list_addr[queue_idx], + 4096, DMA_BIDIRECTIONAL); + if (dma_mapping_error(dev, adapter->buffer_list_dma[queue_idx])) { + netdev_err(netdev, "unable to map buffer list for queue %d\n", + queue_idx); + goto out_free_rxq; + } + + for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++) { + struct ibmveth_buff_pool *src = + &adapter->rx_buff_pool[0][i]; + struct ibmveth_buff_pool *dst = + &adapter->rx_buff_pool[queue_idx][i]; + + dst->size = src->size; + dst->index = src->index; + dst->buff_size = src->buff_size; + dst->threshold = src->threshold; + dst->active = src->active; + } + + rc = ibmveth_alloc_queue_buffer_pools(adapter, queue_idx); + if (rc) { + netdev_err(netdev, + "Failed to allocate buffer pools for queue %d\n", + queue_idx); + goto out_unmap_buflist; + } + + adapter->rx_queue[queue_idx].index = 0; + adapter->rx_queue[queue_idx].num_slots = rxq_entries; + adapter->rx_queue[queue_idx].toggle = 1; + spin_lock_init(&adapter->rx_queue[queue_idx].replenish_lock); + + netdev_dbg(netdev, + "Allocated queue %d: buffer_list @ %p (DMA: 0x%llx), rx_queue @ %p (DMA: 0x%llx), %d entries\n", + queue_idx, adapter->buffer_list_addr[queue_idx], + (unsigned long long)adapter->buffer_list_dma[queue_idx], + adapter->rx_queue[queue_idx].queue_addr, + (unsigned long long)adapter->rx_queue[queue_idx].queue_dma, + rxq_entries); + + return 0; + +out_unmap_buflist: + dma_unmap_single(dev, adapter->buffer_list_dma[queue_idx], + 4096, DMA_BIDIRECTIONAL); + adapter->buffer_list_dma[queue_idx] = 0; +out_free_rxq: + dma_free_coherent(dev, adapter->rx_queue[queue_idx].queue_len, + adapter->rx_queue[queue_idx].queue_addr, + adapter->rx_queue[queue_idx].queue_dma); + adapter->rx_queue[queue_idx].queue_addr = NULL; +out_free_buflist: + free_page((unsigned long)adapter->buffer_list_addr[queue_idx]); + adapter->buffer_list_addr[queue_idx] = NULL; + return rc; +} + +/** + * ibmveth_free_single_rx_queue - Free resources for a single RX queue + * @adapter: ibmveth adapter structure + * @queue_idx: Queue index to free + * + * Frees buffer list, RX queue, and per-queue buffer pools for one queue. + * Used during incremental scale-down without affecting remaining queues. + */ +static void +ibmveth_free_single_rx_queue(struct ibmveth_adapter *adapter, int queue_idx) +{ + struct device *dev = &adapter->vdev->dev; + + ibmveth_free_queue_buffer_pools(adapter, queue_idx); + + if (adapter->buffer_list_dma[queue_idx]) { + dma_unmap_single(dev, adapter->buffer_list_dma[queue_idx], + 4096, DMA_BIDIRECTIONAL); + adapter->buffer_list_dma[queue_idx] = 0; + } + + if (adapter->rx_queue[queue_idx].queue_addr) { + dma_free_coherent(dev, adapter->rx_queue[queue_idx].queue_len, + adapter->rx_queue[queue_idx].queue_addr, + adapter->rx_queue[queue_idx].queue_dma); + adapter->rx_queue[queue_idx].queue_addr = NULL; + } + + if (adapter->buffer_list_addr[queue_idx]) { + free_page((unsigned long)adapter->buffer_list_addr[queue_idx]); + adapter->buffer_list_addr[queue_idx] = NULL; + } + + netdev_dbg(adapter->netdev, "Freed queue %d resources\n", queue_idx); +} + static bool ibmveth_rxq_correlator_valid(struct ibmveth_adapter *adapter, int queue_index, u64 correlator) { @@ -1542,6 +1729,56 @@ static int ibmveth_rxq_harvest_buffer(struct ibmveth_adapter *adapter, return 0; } +/** + * ibmveth_drain_rx_queue - Drain pending buffers from an RX queue + * @adapter: ibmveth adapter structure + * @queue_index: Queue index to drain + * + * Recycles all pending buffers back to the per-queue buffer pools. + * Must be called with NAPI disabled for this queue. + * + * Return: Number of buffers drained + */ +static int +ibmveth_drain_rx_queue(struct ibmveth_adapter *adapter, int queue_index) +{ + struct net_device *netdev = adapter->netdev; + int drained = 0; + int limit = adapter->rx_queue[queue_index].num_slots; + int rc; + + netdev_dbg(netdev, "Draining RX queue %d (limit: %d slots)\n", + queue_index, limit); + + while (drained < limit && + ibmveth_rxq_pending_buffer(adapter, queue_index)) { + /* Match poll-side order before harvesting completion state. */ + smp_rmb(); + rc = ibmveth_rxq_harvest_buffer(adapter, queue_index, true); + if (rc) { + /* -EINVAL/-EFAULT already advanced past the slot. */ + if (rc == -EINVAL || rc == -EFAULT) { + drained++; + continue; + } + netdev_err(netdev, + "Failed to harvest buffer from queue %d during drain: %d\n", + queue_index, rc); + break; + } + drained++; + } + + if (drained > 0) + netdev_dbg(netdev, "Drained %d buffer(s) from RX queue %d\n", + drained, queue_index); + else + netdev_dbg(netdev, "No buffers to drain from RX queue %d\n", + queue_index); + + return drained; +} + static void ibmveth_free_tx_ltb(struct ibmveth_adapter *adapter, int idx) { if (!adapter->tx_ltb_ptr[idx]) @@ -1797,6 +2034,356 @@ ibmveth_register_single_rx_queue(struct ibmveth_adapter *adapter, return 0; } +/** + * ibmveth_deregister_single_rx_queue - Deregister one subordinate RX queue + * @adapter: ibmveth adapter structure + * @queue_idx: Queue index to deregister (1..N) + * + * Deregisters a single queue via H_FREE_LOGICAL_LAN_QUEUE. Linux IRQ handler + * teardown and subordinate virq mapping disposal are owned by interrupt + * cleanup helpers; queue 0 is freed only through ibmveth_free_all_queues() + * (H_FREE_LOGICAL_LAN). + */ +static void +ibmveth_deregister_single_rx_queue(struct ibmveth_adapter *adapter, + int queue_idx) +{ + unsigned long lpar_rc; + unsigned long ua = adapter->vdev->unit_address; + unsigned long qh = adapter->queue_handle[queue_idx]; + + if (!qh) + return; + + do { + lpar_rc = h_free_logical_lan_queue(ua, qh); + } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); + + adapter->hcall_stats.free_lan_queue++; + + if (lpar_rc != H_SUCCESS) { + netdev_err(adapter->netdev, + "h_free_logical_lan_queue failed for queue %d: rc=0x%lx\n", + queue_idx, lpar_rc); + } + + adapter->queue_handle[queue_idx] = 0; + + netdev_dbg(adapter->netdev, "Deregistered queue %d\n", queue_idx); +} + +/** + * ibmveth_destroy_subordinate_rx_queue - Tear down one subordinate RX queue + * @adapter: ibmveth adapter structure + * @queue_idx: Queue index to destroy (1..N) + * + * Deregister with PHYP before unmapping buffer pools so hypervisor buffer + * ownership is released while queue metadata is still valid. + */ +static void +ibmveth_destroy_subordinate_rx_queue(struct ibmveth_adapter *adapter, + int queue_idx) +{ + ibmveth_deregister_single_rx_queue(adapter, queue_idx); + ibmveth_cleanup_single_rx_interrupt(adapter, queue_idx); + ibmveth_free_single_rx_queue(adapter, queue_idx); +} + +/** + * ibmveth_desired_dma_for_rxqs - CMO entitlement for a given RX queue count + * @adapter: ibmveth adapter + * @rxqs: number of RX queues to size for + * + * Same math as ibmveth_get_desired_dma(), but uses @rxqs instead of the + * live adapter->num_rx_queues. Scale-up raises desired for the *target* + * count before allocating so vio_cmo_alloc cannot fail mid-resize. + * + * Return: bytes of IO memory desired for @rxqs RX queues + */ +static unsigned long +ibmveth_desired_dma_for_rxqs(struct ibmveth_adapter *adapter, + unsigned int rxqs) +{ + struct net_device *netdev = adapter->netdev; + struct iommu_table *tbl; + unsigned long ret; + int i, q; + + tbl = get_iommu_table_base(&adapter->vdev->dev); + + ret = IBMVETH_BUFF_LIST_SIZE * rxqs + IBMVETH_FILT_LIST_SIZE; + ret += IOMMU_PAGE_ALIGN(netdev->mtu, tbl); + ret += IOMMU_PAGE_ALIGN(IBMVETH_MAX_TX_BUF_SIZE, tbl); + + /* + * Pool metadata for queues 1+ is copied from queue 0 at open. + * Always size from pool 0 x @rxqs (valid at probe before that copy). + */ + for (q = 0; q < rxqs; q++) { + int rxqentries = 1; + + for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++) { + struct ibmveth_buff_pool *bpool = + &adapter->rx_buff_pool[0][i]; + + if (bpool->active) + ret += bpool->size * + IOMMU_PAGE_ALIGN(bpool->buff_size, tbl); + rxqentries += bpool->size; + } + + ret += IOMMU_PAGE_ALIGN(rxqentries * + sizeof(struct ibmveth_rx_q_entry), tbl); + } + + return ret; +} + +/** + * ibmveth_resize_rx_queues_incremental - Resize RX queue count incrementally + * @adapter: ibmveth adapter structure + * @new_count: Target number of RX queues + * @rxq_entries: Number of entries per RX queue + * + * Adds or removes RX queues without tearing down the entire adapter. + * Active queues continue receiving during scale-up; scale-down drains + * excess queues before deregistering them with the hypervisor. + * + * Return: 0 on success, negative error code on failure + */ +static int +ibmveth_resize_rx_queues_incremental(struct ibmveth_adapter *adapter, + int new_count, int rxq_entries) +{ + struct net_device *netdev = adapter->netdev; + int old_count = ibmveth_get_num_rx_queues(adapter); + int failed_queue; + int rc, i; + + if (old_count == new_count) { + netdev_dbg(netdev, "RX queue count unchanged (%d), nothing to do\n", + old_count); + return 0; + } + + if (new_count < 1 || new_count > IBMVETH_MAX_RX_QUEUES) { + netdev_err(netdev, "Invalid RX queue count %d (must be 1-%d)\n", + new_count, IBMVETH_MAX_RX_QUEUES); + return -EINVAL; + } + + netdev_info(netdev, "Incrementally resizing RX queues: %d to %d\n", + old_count, new_count); + + if (new_count > old_count) { + netdev_dbg(netdev, "Scale-up: adding queues %d-%d\n", + old_count, new_count - 1); + + /* + * Raise CMO desired for the target count before dma_map / + * dma_alloc_coherent / replenish (same order as change_mtu). + * Do not bump live num_rx_queues here — only entitlement. + */ + if (firmware_has_feature(FW_FEATURE_CMO)) { + unsigned long dma; + + dma = ibmveth_desired_dma_for_rxqs(adapter, new_count); + vio_cmo_set_dev_desired(adapter->vdev, dma); + } + + for (i = old_count; i < new_count; i++) { + rc = ibmveth_alloc_single_rx_queue(adapter, i, + rxq_entries); + if (rc) { + netdev_err(netdev, "Failed to allocate queue %d: %d\n", + i, rc); + goto cleanup_new_queues; + } + + rc = ibmveth_register_single_rx_queue(adapter, i); + if (rc) { + netdev_err(netdev, "Failed to register queue %d: %d\n", + i, rc); + ibmveth_free_single_rx_queue(adapter, i); + goto cleanup_new_queues; + } + + rc = ibmveth_setup_single_rx_interrupt(adapter, i); + if (rc) { + netdev_err(netdev, + "Failed to setup IRQ for queue %d: %d\n", + i, rc); + /* request_irq failed: mapped but no handler */ + ibmveth_dispose_subordinate_irq_mapping(adapter, + i); + ibmveth_deregister_single_rx_queue(adapter, i); + ibmveth_free_single_rx_queue(adapter, i); + goto cleanup_new_queues; + } + + /* + * Fully ready before PHYP delivery, matching open(): + * publish -> replenish -> napi_enable -> enable_irq. + * That way ibmveth_interrupt() cannot run on an + * unpublished, empty, or NAPI-disabled queue. + */ + ibmveth_publish_num_rx_queues(adapter, i + 1); + ibmveth_replenish_task(adapter, i); + napi_enable(&adapter->napi[i]); + + rc = ibmveth_enable_irq(adapter, i); + if (rc) { + netdev_err(netdev, + "Failed to enable IRQ for queue %d: %d\n", + i, rc); + /* + * Published, replenished, and NAPI-enabled, + * but PHYP never unmasked. Match scale-down / + * shared cleanup: drain posted buffers, then + * deregister before unmap via + * destroy_subordinate. + */ + ibmveth_publish_num_rx_queues(adapter, i); + napi_disable(&adapter->napi[i]); + ibmveth_drain_rx_queue(adapter, i); + synchronize_net(); + ibmveth_destroy_subordinate_rx_queue(adapter, + i); + /* enable_irq returns errno; keep -EIO. */ + rc = -EIO; + goto cleanup_new_queues; + } + ibmveth_kick_rx_queue_if_pending(adapter, i); + } + + rc = netif_set_real_num_rx_queues(netdev, new_count); + if (rc) { + netdev_err(netdev, "Failed to set real RX queues to %d: %d\n", + new_count, rc); + goto cleanup_new_queues; + } + } else { + netdev_dbg(netdev, "Scale-down: removing queues %d-%d\n", + new_count, old_count - 1); + + /* + * Mask PHYP before napi_disable so the handler cannot miss + * a mask while NAPI is already dead. An in-flight poll can + * still re-arm PHYP while napi_disable() waits, so remask + * and sync again after NAPI is stopped. Then drain and drop + * the live count before freeing so netpoll cannot walk dying + * queues (handler may still be registered until destroy). + */ + for (i = new_count; i < old_count; i++) { + ibmveth_disable_irq(adapter, i); + synchronize_irq(adapter->queue_irq[i]); + } + + for (i = new_count; i < old_count; i++) + napi_disable(&adapter->napi[i]); + + for (i = new_count; i < old_count; i++) { + ibmveth_disable_irq(adapter, i); + synchronize_irq(adapter->queue_irq[i]); + } + + for (i = new_count; i < old_count; i++) + ibmveth_drain_rx_queue(adapter, i); + + synchronize_net(); + + ibmveth_publish_num_rx_queues(adapter, new_count); + + rc = netif_set_real_num_rx_queues(netdev, new_count); + if (rc) { + netdev_err(netdev, "Failed to set real RX queues to %d: %d\n", + new_count, rc); + ibmveth_publish_num_rx_queues(adapter, old_count); + for (i = new_count; i < old_count; i++) { + int irq_rc; + + ibmveth_replenish_task(adapter, i); + /* START: NAPI before PHYP unmask. */ + napi_enable(&adapter->napi[i]); + irq_rc = ibmveth_enable_irq(adapter, i); + if (irq_rc) { + netdev_err(netdev, + "Failed to re-enable IRQ for queue %d during scale-down rollback (rc=%d), scheduling reset\n", + i, irq_rc); + schedule_work(&adapter->work); + continue; + } + ibmveth_kick_rx_queue_if_pending(adapter, i); + } + return rc; + } + + for (i = new_count; i < old_count; i++) + ibmveth_destroy_subordinate_rx_queue(adapter, i); + + /* Q0 (and any still-live queues) were not in the teardown + * loop. Scale-down can leave Q0 masked with NAPI idle — kick + * so ARP/RX does not die until rmmod (lab: -L rx 1). + */ + for (i = 0; i < new_count; i++) + ibmveth_restart_rx_queue(adapter, i); + } + + netdev_info(netdev, "Successfully resized to %d RX queues (incremental)\n", + ibmveth_get_num_rx_queues(adapter)); + + if (firmware_has_feature(FW_FEATURE_CMO)) + vio_cmo_set_dev_desired(adapter->vdev, + ibmveth_get_desired_dma(adapter->vdev)); + + return 0; + +cleanup_new_queues: + failed_queue = i; + if (failed_queue > old_count) + netdev_err(netdev, + "Scale-up failed at queue %d, cleaning up queues %d-%d\n", + failed_queue, old_count, failed_queue - 1); + else + netdev_err(netdev, + "Scale-up failed at queue %d, nothing to clean up\n", + failed_queue); + + for (i = old_count; i < failed_queue; i++) { + ibmveth_disable_irq(adapter, i); + synchronize_irq(adapter->queue_irq[i]); + } + + for (i = old_count; i < failed_queue; i++) + napi_disable(&adapter->napi[i]); + + /* Same remask as scale-down: poll may have re-armed during disable. */ + for (i = old_count; i < failed_queue; i++) { + ibmveth_disable_irq(adapter, i); + synchronize_irq(adapter->queue_irq[i]); + } + + for (i = old_count; i < failed_queue; i++) + ibmveth_drain_rx_queue(adapter, i); + + synchronize_net(); + + /* Drop the live count before freeing the half-added queues. */ + ibmveth_publish_num_rx_queues(adapter, old_count); + + for (i = old_count; i < failed_queue; i++) + ibmveth_destroy_subordinate_rx_queue(adapter, i); + + /* Roll CMO desired back to the surviving queue count. */ + if (firmware_has_feature(FW_FEATURE_CMO)) + vio_cmo_set_dev_desired(adapter->vdev, + ibmveth_get_desired_dma(adapter->vdev)); + + netdev_warn(netdev, "Keeping %d queues after scale-up failure\n", + old_count); + return rc; +} + /** * ibmveth_free_all_queues - Free all RX queues at once * @adapter: ibmveth adapter structure @@ -2535,6 +3122,55 @@ static void ibmveth_get_channels(struct net_device *netdev, channels->rx_count = ibmveth_get_num_rx_queues(adapter); } +/** + * ibmveth_resize_rx_channels - Validate and apply a new RX queue count + * @adapter: ibmveth adapter + * @goal_rx: requested RX queue count + * + * Rejects rx > 1 without MQ firmware (-EOPNOTSUPP) and rx outside + * 1..IBMVETH_MAX_RX_QUEUES (-EINVAL). When RX resources are not live + * (!opened), only validate — the caller stashes the desired count. + * When up, apply via ibmveth_resize_rx_queues_incremental(). + * + * Return: 0 or negative errno + */ +static int ibmveth_resize_rx_channels(struct ibmveth_adapter *adapter, + unsigned int goal_rx) +{ + struct net_device *netdev = adapter->netdev; + unsigned int old_rx = ibmveth_get_num_rx_queues(adapter); + int rxq_entries; + int rc; + + if (goal_rx > 1 && !adapter->multi_queue) { + netdev_err(netdev, + "Cannot resize to %u RX queues: multi-queue mode not supported by firmware\n", + goal_rx); + return -EOPNOTSUPP; + } + + if (goal_rx < 1 || goal_rx > IBMVETH_MAX_RX_QUEUES) { + netdev_err(netdev, + "Invalid RX queue count %u (must be 1-%d)\n", + goal_rx, IBMVETH_MAX_RX_QUEUES); + return -EINVAL; + } + + if (goal_rx == old_rx) + return 0; + + /* Down / failed-open: caller stashes. Do not allocate here. */ + if (!adapter->opened) + return 0; + + rxq_entries = adapter->rx_queue[0].num_slots; + rc = ibmveth_resize_rx_queues_incremental(adapter, goal_rx, + rxq_entries); + if (rc) + netdev_err(netdev, "Failed to resize RX queues: %d\n", rc); + return rc; +} + static int ibmveth_set_channels(struct net_device *netdev, struct ethtool_channels *channels) { @@ -2543,10 +3179,15 @@ static int ibmveth_set_channels(struct net_device *netdev, 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 + /* Validate RX (and resize when opened) before the down-path early + * return so MQ/range errors are not deferred to the wiring patch. + * RX stash + CMO while down still lands with that patch. */ - if (!(netdev->flags & IFF_UP)) + rc = ibmveth_resize_rx_channels(adapter, channels->rx_count); + if (rc) + return rc; + + if (!adapter->opened) return netif_set_real_num_tx_queues(netdev, goal); /* We have IBMVETH_MAX_QUEUES netdev_queue's allocated @@ -3281,8 +3922,6 @@ static unsigned long ibmveth_get_desired_dma(struct vio_dev *vdev) struct net_device *netdev = dev_get_drvdata(&vdev->dev); struct ibmveth_adapter *adapter; struct iommu_table *tbl; - unsigned long ret; - int i, q; tbl = get_iommu_table_base(&vdev->dev); @@ -3291,38 +3930,9 @@ static unsigned long ibmveth_get_desired_dma(struct vio_dev *vdev) return IOMMU_PAGE_ALIGN(IBMVETH_IO_ENTITLEMENT_DEFAULT, tbl); adapter = netdev_priv(netdev); + return ibmveth_desired_dma_for_rxqs(adapter, + ibmveth_get_num_rx_queues(adapter)); - /* One buffer list page per RX queue; filter list is shared. */ - ret = IBMVETH_BUFF_LIST_SIZE * ibmveth_get_num_rx_queues(adapter) + - IBMVETH_FILT_LIST_SIZE; - ret += IOMMU_PAGE_ALIGN(netdev->mtu, tbl); - /* add size of mapped tx buffers */ - ret += IOMMU_PAGE_ALIGN(IBMVETH_MAX_TX_BUF_SIZE, tbl); - - /* - * Pool metadata for queues 1+ is copied from queue 0 at open. - * Always size from pool 0 x num_rx_queues (valid at probe before that - * copy). - */ - for (q = 0; q < ibmveth_get_num_rx_queues(adapter); q++) { - int rxqentries = 1; - - for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++) { - struct ibmveth_buff_pool *bpool = - &adapter->rx_buff_pool[0][i]; - - if (bpool->active) - ret += bpool->size * - IOMMU_PAGE_ALIGN(bpool->buff_size, tbl); - rxqentries += bpool->size; - } - - /* add the size of the receive queue entries */ - ret += IOMMU_PAGE_ALIGN(rxqentries * - sizeof(struct ibmveth_rx_q_entry), tbl); - } - - return ret; } static int ibmveth_set_mac_addr(struct net_device *dev, void *p) -- 2.50.1 (Apple Git-155)