The mvneta driver uses the hardware Buffer Manager (BM) for RX buffer allocation. During suspend, mvneta disables its clock, causing BM to lose all buffer address state. On resume, mvneta_bm_port_init() re- attaches the BM pool to the NIC, but BM hardware returns stale/garbage buffer addresses. When NAPI poll processes these buffers, DMA cache sync hits an invalid virtual address causing a kernel panic: Unable to handle kernel paging request at virtual address b0000080 PC is at v7_dma_inv_range Call trace: v7_dma_inv_range from arch_sync_dma_for_cpu+0x94/0x158 arch_sync_dma_for_cpu from __dma_sync_single_for_cpu+0xc4/0x15c __dma_sync_single_for_cpu from mvneta_rx_swbm+0x6c8/0xf48 mvneta_rx_swbm from mvneta_poll+0x6fc/0x70c mvneta_poll from __napi_poll.constprop.0+0x2c/0x1e0 __napi_poll.constprop.0 from net_rx_action+0x160/0x2c4 net_rx_action from handle_softirqs+0xd8/0x2b8 handle_softirqs from run_ksoftirqd+0x30/0x94 run_ksoftirqd from smpboot_thread_fn+0x100/0x204 smpboot_thread_fn from kthread+0xf4/0x110 kthread from ret_from_fork+0x14/0x28 Fix by adding suspend/resume callbacks to the BM driver: - suspend: drain all buffers (with DMA unmapping), free the BPPE regions, and reset pool state to FREE before stopping BM and gating the clock. - resume: enable the clock, reinitialize BM defaults, and restore pool read/write pointers and size registers. Pool allocation and buffer refill are handled by mvneta_resume() through the normal mvneta_bm_port_init() path, which sees pools as FREE and performs full initialization identical to probe. Add a device_link (DL_FLAG_AUTOREMOVE_CONSUMER) in mvneta_probe to guarantee BM resumes before mvneta and suspends after mvneta. If the link cannot be created, fall back to SW buffer management to avoid a potential crash on resume due to unordered PM transitions. Signed-off-by: Yun Zhou --- v6: - Add mutex_destroy() before setting type = FREE in suspend, pairing with mutex_init() in mvneta_bm_pool_use() on resume. This avoids re-initializing an active lockdep class. - NULL-out bm_pool->virt_addr in mvneta_bm_pool_create() error paths to fix a pre-existing dangling pointer that could cause double-free in suspend. v5: - Call mvneta_bm_pool_disable() per pool before dma_free_coherent() in suspend, matching mvneta_bm_pool_destroy() ordering. This also ensures ENABLE_MASK is cleared before BM START on resume. - Guard dma_free_coherent() with if (bm_pool->virt_addr) to defend against a partially-failed mvneta_bm_pool_create() leaving a stale pointer. v4: - On device_link_add() failure, fall back to SW buffer management (destroy pools, put BM reference, clear bm_priv) instead of merely emitting a warning. Without the link, suspend/resume ordering is not guaranteed and the original crash can still occur. v3: - Restore per-pool POOL_SIZE_REG, POOL_READ_PTR_REG, and POOL_WRITE_PTR_REG in resume, since clock gating loses all BM register state. - Check device_link_add() return value and emit dev_warn on failure. - Replace SIMPLE_DEV_PM_OPS (deprecated) with DEFINE_SIMPLE_DEV_PM_OPS and pm_sleep_ptr(), removing the #ifdef CONFIG_PM_SLEEP guard. - Add dev_warn in suspend if not all buffers could be freed. v2: - Drain buffers via mvneta_bm_bufs_free() in suspend instead of only stopping BM and gating the clock. This ensures proper DMA unmapping and avoids buffer leaks. - Free the BPPE DMA-coherent region in suspend so that resume takes the full probe-time initialization path (alloc + fill), eliminating the need to modify mvneta_bm_pool_create(). - Reset pool type to MVNETA_BM_FREE in suspend so mvneta_bm_pool_use() correctly re-creates and refills pools on resume. - Check clk_prepare_enable() return value in resume. - Add device_link between mvneta (consumer) and mvneta_bm (supplier) to guarantee correct suspend/resume ordering. drivers/net/ethernet/marvell/mvneta.c | 18 ++++++ drivers/net/ethernet/marvell/mvneta_bm.c | 72 ++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 744d6585a949..543e566425c1 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -5678,6 +5678,24 @@ static int mvneta_probe(struct platform_device *pdev) "use SW buffer management\n"); mvneta_bm_put(pp->bm_priv); pp->bm_priv = NULL; + } else if (!device_link_add(&pdev->dev, + &pp->bm_priv->pdev->dev, + DL_FLAG_AUTOREMOVE_CONSUMER)) { + /* + * Link guarantees BM resumes before mvneta. + * Without it, BM may not be ready when + * mvneta_bm_port_init() runs on resume, + * causing stale buffer addresses and a crash. + * Fall back to SW management to be safe. + */ + dev_warn(&pdev->dev, + "failed to link to BM, use SW buffer management\n"); + mvneta_bm_pool_destroy(pp->bm_priv, + pp->pool_long, 1 << pp->id); + mvneta_bm_pool_destroy(pp->bm_priv, + pp->pool_short, 1 << pp->id); + mvneta_bm_put(pp->bm_priv); + pp->bm_priv = NULL; } } /* Set RX packet offset correction for platforms, whose diff --git a/drivers/net/ethernet/marvell/mvneta_bm.c b/drivers/net/ethernet/marvell/mvneta_bm.c index 6bb380494919..e0c693c0a910 100644 --- a/drivers/net/ethernet/marvell/mvneta_bm.c +++ b/drivers/net/ethernet/marvell/mvneta_bm.c @@ -129,6 +129,7 @@ static int mvneta_bm_pool_create(struct mvneta_bm *priv, if (!IS_ALIGNED((u32)bm_pool->virt_addr, MVNETA_BM_POOL_PTR_ALIGN)) { dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; dev_err(&pdev->dev, "BM pool %d is not %d bytes aligned\n", bm_pool->id, MVNETA_BM_POOL_PTR_ALIGN); return -ENOMEM; @@ -139,6 +140,7 @@ static int mvneta_bm_pool_create(struct mvneta_bm *priv, if (err < 0) { dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; return err; } @@ -477,6 +479,75 @@ static void mvneta_bm_remove(struct platform_device *pdev) clk_disable_unprepare(priv->clk); } +static int mvneta_bm_suspend(struct device *dev) +{ + struct mvneta_bm *priv = dev_get_drvdata(dev); + int i; + + /* Drain buffers and free pool resources while BM is still clocked */ + for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { + struct mvneta_bm_pool *bm_pool = &priv->bm_pools[i]; + int size_bytes; + + if (bm_pool->type == MVNETA_BM_FREE) + continue; + + mvneta_bm_bufs_free(priv, bm_pool, bm_pool->port_map); + if (bm_pool->hwbm_pool.buf_num) + dev_warn(&priv->pdev->dev, + "pool %d: %d buffers not freed\n", + bm_pool->id, bm_pool->hwbm_pool.buf_num); + + mvneta_bm_pool_disable(priv, bm_pool->id); + + if (bm_pool->virt_addr) { + size_bytes = sizeof(u32) * bm_pool->hwbm_pool.size; + dma_free_coherent(&priv->pdev->dev, size_bytes, + bm_pool->virt_addr, + bm_pool->phys_addr); + bm_pool->virt_addr = NULL; + } + /* + * Safe to destroy: device_link guarantees all mvneta ports + * have already suspended, so no hwbm_pool_add() can be in + * progress holding buf_lock. Pairs with mutex_init() in + * mvneta_bm_pool_use() on resume. + */ + mutex_destroy(&bm_pool->hwbm_pool.buf_lock); + bm_pool->type = MVNETA_BM_FREE; + } + + mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_STOP_MASK); + clk_disable_unprepare(priv->clk); + return 0; +} + +static int mvneta_bm_resume(struct device *dev) +{ + struct mvneta_bm *priv = dev_get_drvdata(dev); + int i, err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + + /* Reinitialize BM hardware; pools are refilled by mvneta_resume() */ + mvneta_bm_default_set(priv); + + /* Restore pool registers lost during clock gating */ + for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { + mvneta_bm_write(priv, MVNETA_BM_POOL_READ_PTR_REG(i), 0); + mvneta_bm_write(priv, MVNETA_BM_POOL_WRITE_PTR_REG(i), 0); + mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i), + priv->bm_pools[i].hwbm_pool.size); + } + + mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_START_MASK); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mvneta_bm_pm_ops, mvneta_bm_suspend, mvneta_bm_resume); + static const struct of_device_id mvneta_bm_match[] = { { .compatible = "marvell,armada-380-neta-bm" }, { } @@ -489,6 +560,7 @@ static struct platform_driver mvneta_bm_driver = { .driver = { .name = MVNETA_BM_DRIVER_NAME, .of_match_table = mvneta_bm_match, + .pm = pm_sleep_ptr(&mvneta_bm_pm_ops), }, }; -- 2.43.0