net: mvneta_bm: add suspend/resume support to prevent crash after resume

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 <yun.zhou@windriver.com>
Link: https://patch.msgid.link/20260630060311.4072140-1-yun.zhou@windriver.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Yun Zhou
2026-06-30 14:03:11 +08:00
committed by Paolo Abeni
parent 15fede6ca5
commit 140be217df
2 changed files with 90 additions and 0 deletions

View File

@@ -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

View File

@@ -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),
},
};