[PATCH v2 3/6] net: mhi_net: Hold runtime PM during active data path operations
Loic Poulain
loic.poulain at oss.qualcomm.com
Fri May 22 13:09:08 PDT 2026
Hi Krishna,
On Fri, May 22, 2026 at 12:01 PM Krishna Chaitanya Chundru
<krishna.chundru at oss.qualcomm.com> wrote:
>
> The mhi_net driver does not coordinate with runtime PM, which allows the
> underlying MHI controller to be runtime-suspended while transmit, receive,
> or RX buffer refill operations are in progress. This can lead to stalled
> transfers or failed queueing once runtime PM is enabled in the MHI core.
>
> Add runtime PM reference counting to the mhi_net data path to keep the
> controller active for the duration of TX, RX, and buffer management
> operations. Enable runtime PM during probe and take/release references
> explicitly around these critical paths.
>
> Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru at oss.qualcomm.com>
> ---
> drivers/net/mhi_net.c | 39 +++++++++++++++++++++++++++++++++++++++
> 1 file changed, 39 insertions(+)
>
> diff --git a/drivers/net/mhi_net.c b/drivers/net/mhi_net.c
> index ae169929a9d8..5d7f9ccdb17b 100644
> --- a/drivers/net/mhi_net.c
> +++ b/drivers/net/mhi_net.c
> @@ -9,6 +9,7 @@
> #include <linux/mod_devicetable.h>
> #include <linux/module.h>
> #include <linux/netdevice.h>
> +#include <linux/pm_runtime.h>
> #include <linux/skbuff.h>
> #include <linux/u64_stats_sync.h>
>
> @@ -76,11 +77,19 @@ static netdev_tx_t mhi_ndo_xmit(struct sk_buff *skb, struct net_device *ndev)
> struct mhi_device *mdev = mhi_netdev->mdev;
> int err;
>
> + err = pm_runtime_get(&mdev->dev);
> + if (err < 0 && err != -EINPROGRESS) {
> + dev_err(&mdev->dev, "pm_runtime_get failed %d\n", err);
> + pm_runtime_put_noidle(&mdev->dev);
> + goto exit_drop;
> + }
> +
I am wondering what the value is in pushing this PM responsibility to
each individual MHI client driver and requiring every MHI operation to
be bracketed with runtime PM handling.
What does the client driver know here that the MHI core itself cannot
handle centrally? It feels like ensuring the controller is
runtime-active during transfer could be handled generically in the
framework instead of duplicating the same logic in every client.
> err = mhi_queue_skb(mdev, DMA_TO_DEVICE, skb, skb->len, MHI_EOT);
> if (unlikely(err)) {
> net_err_ratelimited("%s: Failed to queue TX buf (%d)\n",
> ndev->name, err);
> dev_kfree_skb_any(skb);
> + pm_runtime_put(&mdev->dev);
> goto exit_drop;
> }
>
> @@ -251,6 +260,7 @@ static void mhi_net_ul_callback(struct mhi_device *mhi_dev,
> /* MHI layer stopping/resetting the UL channel */
> if (mhi_res->transaction_status == -ENOTCONN) {
> u64_stats_update_end(&mhi_netdev->stats.tx_syncp);
> + pm_runtime_put(&mdev->dev);
> return;
> }
>
> @@ -261,6 +271,7 @@ static void mhi_net_ul_callback(struct mhi_device *mhi_dev,
> }
> u64_stats_update_end(&mhi_netdev->stats.tx_syncp);
>
> + pm_runtime_put(&mdev->dev);
> if (netif_queue_stopped(ndev) && !mhi_queue_is_full(mdev, DMA_TO_DEVICE))
> netif_wake_queue(ndev);
> }
> @@ -277,6 +288,12 @@ static void mhi_net_rx_refill_work(struct work_struct *work)
>
> size = mhi_netdev->mru ? mhi_netdev->mru : READ_ONCE(ndev->mtu);
>
> + err = pm_runtime_resume_and_get(&mdev->dev);
> + if (err) {
> + dev_err(&mdev->dev, "pm_runtime_resume_and_get failed %d\n", err);
> + return;
> + }
> +
> while (!mhi_queue_is_full(mdev, DMA_FROM_DEVICE)) {
> skb = netdev_alloc_skb(ndev, size);
> if (unlikely(!skb))
> @@ -296,6 +313,7 @@ static void mhi_net_rx_refill_work(struct work_struct *work)
> cond_resched();
> }
>
> + pm_runtime_put(&mdev->dev);
> /* If we're still starved of rx buffers, reschedule later */
> if (mhi_get_free_desc_count(mdev, DMA_FROM_DEVICE) == mhi_netdev->rx_queue_sz)
> schedule_delayed_work(&mhi_netdev->rx_refill, HZ / 2);
> @@ -362,20 +380,41 @@ static int mhi_net_probe(struct mhi_device *mhi_dev,
>
> SET_NETDEV_DEV(ndev, &mhi_dev->dev);
>
> + pm_runtime_no_callbacks(&mhi_dev->dev);
> + err = devm_pm_runtime_set_active_enabled(&mhi_dev->dev);
> + if (err)
> + return err;
> +
> + err = pm_runtime_resume_and_get(&mhi_dev->dev);
> + if (err) {
> + dev_err(&mhi_dev->dev, "pm_runtime_resume_and_get failed %d\n", err);
> + return err;
> + }
> +
> err = mhi_net_newlink(mhi_dev, ndev);
> if (err) {
> free_netdev(ndev);
> + pm_runtime_put(&mhi_dev->dev);
> return err;
> }
>
> + pm_runtime_put(&mhi_dev->dev);
> return 0;
> }
>
> static void mhi_net_remove(struct mhi_device *mhi_dev)
> {
> struct mhi_net_dev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev);
> + int err;
> +
> + err = pm_runtime_resume_and_get(&mhi_dev->dev);
> + if (err)
> + dev_err(&mhi_dev->dev, "pm_runtime_resume_and_get failed %d\n", err);
>
> mhi_net_dellink(mhi_dev, mhi_netdev->ndev);
> +
> + if (!err)
> + pm_runtime_put(&mhi_dev->dev);
> }
>
> static const struct mhi_device_info mhi_hwip0 = {
>
> --
> 2.34.1
>
More information about the ath11k
mailing list