From 22568e8f13a42ab69b7d624741768cbf15dded74 Mon Sep 17 00:00:00 2001 From: Qingtao Cao Date: Wed, 3 Jun 2026 11:06:11 +1000 Subject: [PATCH 01/12] bus: mhi: core: Fix sys error transition latency Bring forward the idea to fix the power down latency in mhi_pm_disable_transition() further to mhi_pm_sys_error_transition() so that the transition into system error (triggered by AT!RESET) in the modems won't have to return only after the timeout of up to 24 seconds. Once the device gets reset, there is no guarantee that it will send an interrupt to indicate the state transition. So polling is the sensible option here. Signed-off-by: Qingtao Cao [mani: commit log] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Krishna Chaitanya Chundru Link: https://patch.msgid.link/20260603011333.3306102-2-qingtao.cao.au@gmail.com --- drivers/bus/mhi/host/pm.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c index f799503c8f36..9d29f1591a45 100644 --- a/drivers/bus/mhi/host/pm.c +++ b/drivers/bus/mhi/host/pm.c @@ -651,21 +651,13 @@ static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl) /* Trigger MHI RESET so that the device will not access host memory */ if (reset_device) { - u32 in_reset = -1; - unsigned long timeout = msecs_to_jiffies(mhi_cntrl->timeout_ms); - dev_dbg(dev, "Triggering MHI Reset in device\n"); mhi_set_mhi_state(mhi_cntrl, MHI_STATE_RESET); /* Wait for the reset bit to be cleared by the device */ - ret = wait_event_timeout(mhi_cntrl->state_event, - mhi_read_reg_field(mhi_cntrl, - mhi_cntrl->regs, - MHICTRL, - MHICTRL_RESET_MASK, - &in_reset) || - !in_reset, timeout); - if (!ret || in_reset) { + ret = mhi_poll_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL, + MHICTRL_RESET_MASK, 0, 25000, mhi_cntrl->timeout_ms); + if (ret) { dev_err(dev, "Device failed to exit MHI Reset state\n"); write_lock_irq(&mhi_cntrl->pm_lock); cur_state = mhi_tryset_pm_state(mhi_cntrl, From 6f12862600bb70e599a614d706a095ea5f8f9858 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Wed, 3 Jun 2026 15:51:42 -0400 Subject: [PATCH 02/12] bus: mhi: ep: Fix device refcount leak in the error path of MHI device creation mhi_ep_create_device() takes one device reference for the UL channel and another for the DL channel after allocating the transfer device. These references are normally released by mhi_ep_destroy_device() before the device itself is removed. If dev_set_name() or device_add() fails, the error path currently drops only one reference. The remaining channel references keep the device from being released and leave the channels associated with a device that was never registered. Route both failures through a common unwind path that drops the DL channel reference, the UL channel reference, and the initial reference from device_initialize(). Fixes: 297c77a0f273 ("bus: mhi: ep: Add support for creating and destroying MHI EP devices") Signed-off-by: Yuho Choi Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260603195142.2189386-1-dbgh9129@gmail.com --- drivers/bus/mhi/ep/main.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c index 9db2a2a2c913..43d680778704 100644 --- a/drivers/bus/mhi/ep/main.c +++ b/drivers/bus/mhi/ep/main.c @@ -1341,14 +1341,19 @@ static int mhi_ep_create_device(struct mhi_ep_cntrl *mhi_cntrl, u32 ch_id) ret = dev_set_name(&mhi_dev->dev, "%s_%s", dev_name(&mhi_cntrl->mhi_dev->dev), mhi_dev->name); - if (ret) { - put_device(&mhi_dev->dev); - return ret; - } + if (ret) + goto err_put_channels; ret = device_add(&mhi_dev->dev); if (ret) - put_device(&mhi_dev->dev); + goto err_put_channels; + + return 0; + +err_put_channels: + put_device(&mhi_dev->dev); /* DL channel reference */ + put_device(&mhi_dev->dev); /* UL channel reference */ + put_device(&mhi_dev->dev); /* device_initialize() reference */ return ret; } From ad7a9a2d295c0525e0f4ba1b85045d160458e8b3 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 10 Jun 2026 20:47:52 +0530 Subject: [PATCH 03/12] MAINTAINERS: Add Jeff Hugo as the Reviewer of MHI bus Jeff has been active in reviewing the MHI patches and he volunteered to become an official reviewer. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260610151752.9373-1-manivannan.sadhasivam@oss.qualcomm.com --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..a4b89bd497ad 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17463,6 +17463,7 @@ F: arch/arm64/boot/dts/marvell/armada-3720-uDPU.* MHI BUS M: Manivannan Sadhasivam +R: Jeff Hugo L: mhi@lists.linux.dev L: linux-arm-msm@vger.kernel.org S: Maintained From 24f4423cbc89548def2b05ae86de6175086dbf94 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 23 Jun 2026 16:51:34 +0200 Subject: [PATCH 04/12] bus: mhi: host: Flush the posted write after writing to MHI_SOC_RESET_REQ_OFFSET mhi_soc_reset() tries to reset the device by writing to the MHI_SOC_RESET_REQ_OFFSET register. But it doesn't do a read-back to ensure that the write gets flushed to the device before returning to the caller. This may lead to the delay (if implemented) on the caller to be insufficient, if the posted write doesn't reach the device before the delay. So add a read-back after writing to the MHI_SOC_RESET_REQ_OFFSET register. Fixes: b5a8d233a588 ("bus: mhi: core: Add device hardware reset support") Reported-by: Alex Williamson Closes: https://lore.kernel.org/linux-pci/20260622160822.09350246@shazbot.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Reviewed-by: Jeff Hugo Link: https://patch.msgid.link/20260623145134.43976-1-manivannan.sadhasivam@oss.qualcomm.com --- drivers/bus/mhi/host/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c index 53c0ffe30070..4d458396233a 100644 --- a/drivers/bus/mhi/host/main.c +++ b/drivers/bus/mhi/host/main.c @@ -170,6 +170,9 @@ EXPORT_SYMBOL_GPL(mhi_get_mhi_state); void mhi_soc_reset(struct mhi_controller *mhi_cntrl) { + int __maybe_unused ret; + u32 tmp; + if (mhi_cntrl->reset) { mhi_cntrl->reset(mhi_cntrl); return; @@ -178,6 +181,9 @@ void mhi_soc_reset(struct mhi_controller *mhi_cntrl) /* Generic MHI SoC reset */ mhi_write_reg(mhi_cntrl, mhi_cntrl->regs, MHI_SOC_RESET_REQ_OFFSET, MHI_SOC_RESET_REQ); + /* Flush the posted write to the device (ignore return value) */ + ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs, MHI_SOC_RESET_REQ_OFFSET, + &tmp); } EXPORT_SYMBOL_GPL(mhi_soc_reset); From 753aa72545ce0b2503eca784f35062d0bf5418d7 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 10 Jul 2026 13:51:20 +0200 Subject: [PATCH 05/12] bus: mhi: host: Add support for devices with no M3 state MHI bus transitions the device into M3 state during suspend and back to M0 state during resume. But due to hardware issues, some devices do not support M3 state. To support these devices properly, MHI bus needs to skip transitioning the device to M3 during suspend and back to M0 during resume. For this purpose, introduce the 'mhi_cntrl->no_m3' flag and allow it to be set by the MHI controller drivers. Once set, this flag lets the MHI bus skip transitioning the device to M3/M0 during suspend/resume. But, simply skipping suspend/resume for such devices is not sufficient, as it leaves the MHI host in M0 state with device access enabled. Client drivers that do not implement PM callbacks (for instance, the non-freezable rx_refill worker in mhi_net driver) could then keep ringing channel doorbells and issue MMIO to the device even after the controller driver has disabled it and moved it to D3 during its own suspend, resulting in access to a powered down device. So instead of skipping the entire suspend/resume operation, run the full host suspend/resume sequence but without the device-side M state handshake. During suspend, only transition the host to M3 without sending the MHICTRL M3 command or waiting for the device M3 event. During resume, bring the host back to M0 through mhi_pm_m0_transition() without sending the MHICTRL M0 command. With the host in M3, all device access is gated by MHI_DB_ACCESS_VALID() and MHI_REG_ACCESS_VALID(), so any transfer queued by the clients during suspend is deferred until resume, where mhi_pm_m0_transition() rings the pending doorbells. Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/host/pm.c | 57 ++++++++++++++++++++++++++++----------- include/linux/mhi.h | 2 ++ 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c index 9d29f1591a45..ced83fbb1a51 100644 --- a/drivers/bus/mhi/host/pm.c +++ b/drivers/bus/mhi/host/pm.c @@ -914,22 +914,39 @@ int mhi_pm_suspend(struct mhi_controller *mhi_cntrl) return -EIO; } - /* Set MHI to M3 and wait for completion */ - mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M3); - write_unlock_irq(&mhi_cntrl->pm_lock); - dev_dbg(dev, "Waiting for M3 completion\n"); + /* + * For devices without M3 support, just set the host state to M3. This + * host transition is needed to prevent the client drivers from + * accessing the device during suspend. + */ + if (mhi_cntrl->no_m3) { + new_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3); + write_unlock_irq(&mhi_cntrl->pm_lock); + if (new_state != MHI_PM_M3) { + dev_err(dev, + "Error setting to PM state: %s from: %s\n", + to_mhi_pm_state_str(MHI_PM_M3), + to_mhi_pm_state_str(mhi_cntrl->pm_state)); + return -EIO; + } + } else { + /* Set MHI to M3 and wait for completion */ + mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M3); + write_unlock_irq(&mhi_cntrl->pm_lock); + dev_dbg(dev, "Waiting for M3 completion\n"); - ret = wait_event_timeout(mhi_cntrl->state_event, - mhi_cntrl->dev_state == MHI_STATE_M3 || - MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state), - msecs_to_jiffies(mhi_cntrl->timeout_ms)); + ret = wait_event_timeout(mhi_cntrl->state_event, + mhi_cntrl->dev_state == MHI_STATE_M3 || + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state), + msecs_to_jiffies(mhi_cntrl->timeout_ms)); - if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { - dev_err(dev, - "Did not enter M3 state, MHI state: %s, PM state: %s\n", - mhi_state_str(mhi_cntrl->dev_state), - to_mhi_pm_state_str(mhi_cntrl->pm_state)); - return -EIO; + if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { + dev_err(dev, + "Did not enter M3 state, MHI state: %s, PM state: %s\n", + mhi_state_str(mhi_cntrl->dev_state), + to_mhi_pm_state_str(mhi_cntrl->pm_state)); + return -EIO; + } } /* Notify clients about entering LPM */ @@ -961,7 +978,8 @@ static int __mhi_pm_resume(struct mhi_controller *mhi_cntrl, bool force) if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) return -EIO; - if (mhi_get_mhi_state(mhi_cntrl) != MHI_STATE_M3) { + if (!mhi_cntrl->no_m3 && + mhi_get_mhi_state(mhi_cntrl) != MHI_STATE_M3) { dev_warn(dev, "Resuming from non M3 state (%s)\n", mhi_state_str(mhi_get_mhi_state(mhi_cntrl))); if (!force) @@ -987,6 +1005,15 @@ static int __mhi_pm_resume(struct mhi_controller *mhi_cntrl, bool force) return -EIO; } + /* + * For devices without M3 support, just move the host back to M0 + * directly. + */ + if (mhi_cntrl->no_m3) { + write_unlock_irq(&mhi_cntrl->pm_lock); + return mhi_pm_m0_transition(mhi_cntrl); + } + /* Set MHI to M0 and wait for completion */ mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M0); write_unlock_irq(&mhi_cntrl->pm_lock); diff --git a/include/linux/mhi.h b/include/linux/mhi.h index fb3ba639f4f8..0d60058bf5ae 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -374,6 +374,7 @@ struct mhi_controller_config { * @bounce_buf: Use of bounce buffer * @fbc_download: MHI host needs to do complete image transfer (optional) * @wake_set: Device wakeup set flag + * @no_m3: Device doesn't support M3 state * @irq_flags: irq flags passed to request_irq (optional) * @mru: the default MRU for the MHI device * @@ -459,6 +460,7 @@ struct mhi_controller { bool bounce_buf; bool fbc_download; bool wake_set; + bool no_m3; unsigned long irq_flags; u32 mru; }; From 0936a8780ec2d239993c6cec988834fc9779fc01 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 9 Jul 2026 09:26:49 +0200 Subject: [PATCH 06/12] bus: mhi: host: pci_generic: Set 'mhi_cntrl->no_m3' flag Commit 0494cf9793b7 ("bus: mhi: host: pci_generic: Disable runtime PM for QDU100") added the 'no_m3' flag to indicate that the QDU100 device doesn't support M3 state and used this flag to skip runtime PM. But it didn't prevent the MHI bus from transitioning the device to M3 during system suspend. So set 'mhi_cntrl->no_m3' flag based on the local 'info->no_m3' flag to indicate MHI bus that this device doesn't support M3 state so that it can skip the transition. Cc: stable+noautosel@kernel.org # depends on the 'mhi_cntrl->no_m3' flag addition Fixes: 0494cf9793b7 ("bus: mhi: host: pci_generic: Disable runtime PM for QDU100") Reported-by: Krishna Chaitanya Chundru Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/host/pci_generic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index 0d0d9c7ffa4b..7e340d6112bf 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -1395,6 +1395,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(dma_data_width); mhi_cntrl->fw_image = info->fw; mhi_cntrl->edl_image = info->edl; + mhi_cntrl->no_m3 = info->no_m3; mhi_cntrl->read_reg = mhi_pci_read_reg; mhi_cntrl->write_reg = mhi_pci_write_reg; From 5fe63c1bba57492c97cceb34c35e276d3c81e4fe Mon Sep 17 00:00:00 2001 From: Slark Xiao Date: Fri, 26 Jun 2026 11:46:33 +0800 Subject: [PATCH 07/12] bus: mhi: pci_generic: Add SAHARA channel support for Foxconn products Add SAHARA channel to support capturing crash dump (ramdump) using the in-kernel sahara client driver. Signed-off-by: Slark Xiao [mani: commit log] Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/host/pci_generic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index 7e340d6112bf..b636e2c23b4d 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -491,6 +491,8 @@ static const struct mhi_pci_dev_info mhi_quectel_rm5xx_info = { static const struct mhi_channel_config mhi_foxconn_sdx55_channels[] = { MHI_CHANNEL_CONFIG_UL(0, "LOOPBACK", 32, 0), MHI_CHANNEL_CONFIG_DL(1, "LOOPBACK", 32, 0), + MHI_CHANNEL_CONFIG_UL_SBL(2, "SAHARA", 32, 0), + MHI_CHANNEL_CONFIG_DL_SBL(3, "SAHARA", 32, 0), MHI_CHANNEL_CONFIG_UL(4, "DIAG", 32, 1), MHI_CHANNEL_CONFIG_DL(5, "DIAG", 32, 1), MHI_CHANNEL_CONFIG_UL(12, "MBIM", 32, 0), @@ -506,6 +508,8 @@ static const struct mhi_channel_config mhi_foxconn_sdx55_channels[] = { static const struct mhi_channel_config mhi_foxconn_sdx61_channels[] = { MHI_CHANNEL_CONFIG_UL(0, "LOOPBACK", 32, 0), MHI_CHANNEL_CONFIG_DL(1, "LOOPBACK", 32, 0), + MHI_CHANNEL_CONFIG_UL_SBL(2, "SAHARA", 32, 0), + MHI_CHANNEL_CONFIG_DL_SBL(3, "SAHARA", 32, 0), MHI_CHANNEL_CONFIG_UL(4, "DIAG", 32, 1), MHI_CHANNEL_CONFIG_DL(5, "DIAG", 32, 1), MHI_CHANNEL_CONFIG_UL(12, "MBIM", 32, 0), From 0d5b9e66591d4e2a4376ac82c8cda889a29ba3ee Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Tue, 7 Jul 2026 14:51:00 -0400 Subject: [PATCH 08/12] bus: mhi: host: Fix controller cleanup on EDL sysfs failure mhi_register_controller() adds the controller device before creating the optional trigger_edl sysfs file. If sysfs_create_file() fails, the error path only drops the device reference and leaves the device registered. Hence, call device_del() in the error path before put_device(). Fixes: 17553ba8e19d ("bus: mhi: host: Add sysfs entry to force device to enter EDL") Signed-off-by: Yuho Choi Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/host/init.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index 0a728ca2c494..0eeee26893f3 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -1030,7 +1030,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, if (mhi_cntrl->edl_trigger) { ret = sysfs_create_file(&mhi_dev->dev.kobj, &dev_attr_trigger_edl.attr); if (ret) - goto err_release_dev; + goto err_del_dev; } mhi_cntrl->mhi_dev = mhi_dev; @@ -1039,6 +1039,8 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, return 0; +err_del_dev: + device_del(&mhi_dev->dev); err_release_dev: put_device(&mhi_dev->dev); error_setup_irq: From abe5c5f1ad5575d04e53a91d5c2ead2bb50292a7 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 19 Jul 2026 15:32:31 -0700 Subject: [PATCH 09/12] bus: mhi: Clean up some kernel-doc warnings Clean up some kernel-doc comments and warnings: - use correct format for struct members - add one struct member description - add one function parameter description - mark one enum as private - add a leading '*' on one kernel-doc line Fixes these warnings: Warning: include/linux/mhi.h:108 struct member 'target_link_speed' not described in 'mhi_link_info' Warning: include/linux/mhi.h:108 struct member 'target_link_width' not described in 'mhi_link_info' Warning: ../include/linux/mhi.h:159 Enum value 'MHI_STATE_MAX' not described in enum 'mhi_state' Warning: ../include/linux/mhi.h:212 bad line: for UL channels, multiple of 8 ring elements for DL channels Warning: ../include/linux/mhi.h:236 struct member 'wake_capable' not described in 'mhi_channel_config' Warning: ../include/linux/mhi.h:449 struct member 'M0' not described in 'mhi_controller' Warning: ../include/linux/mhi.h:449 struct member 'M2' not described in 'mhi_controller' Warning: ../include/linux/mhi.h:449 struct member 'M3' not described in 'mhi_controller' Warning: ../include/linux/mhi.h:528 struct member 'id_table' not described in 'mhi_driver' Warning: ../include/linux/mhi.h:543 function parameter 'mhi_cntrl' not described in 'mhi_free_controller' Signed-off-by: Randy Dunlap Signed-off-by: Manivannan Sadhasivam --- include/linux/mhi.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/include/linux/mhi.h b/include/linux/mhi.h index 0d60058bf5ae..ca24ca6c6a48 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -116,8 +116,8 @@ struct image_info { /** * struct mhi_link_info - BW requirement - * target_link_speed - Link speed as defined by TLS bits in LinkControl reg - * target_link_width - Link width as defined by NLW bits in LinkStatus reg + * @target_link_speed: Link speed as defined by TLS bits in LinkControl reg + * @target_link_width: Link width as defined by NLW bits in LinkStatus reg */ struct mhi_link_info { unsigned int target_link_speed; @@ -172,6 +172,7 @@ enum mhi_state { MHI_STATE_M3_FAST = 0x6, MHI_STATE_BHI = 0x7, MHI_STATE_SYS_ERR = 0xFF, + /* private: */ MHI_STATE_MAX, }; @@ -226,12 +227,12 @@ enum mhi_db_brst_mode { * @type: Channel type * @ee_mask: Execution Environment mask for this channel * @pollcfg: Polling configuration for burst mode. 0 is default. milliseconds - for UL channels, multiple of 8 ring elements for DL channels + * for UL channels, multiple of 8 ring elements for DL channels * @doorbell: Doorbell mode * @lpm_notify: The channel master requires low power mode notifications * @offload_channel: The client manages the channel completely * @doorbell_mode_switch: Channel switches to doorbell mode on M0 transition - * @wake-capable: Channel capable of waking up the system + * @wake_capable: Channel capable of waking up the system */ struct mhi_channel_config { char *name; @@ -349,7 +350,9 @@ struct mhi_controller_config { * @dev_state: MHI device state * @dev_wake: Device wakeup count * @pending_pkts: Pending packets for the controller - * @M0, M2, M3: Counters to track number of device MHI state changes + * @M0: Counter to track number of device MHI state changes + * @M2: Counter to track number of device MHI state changes + * @M3: Counter to track number of device MHI state changes * @transition_list: List of MHI state transitions * @transition_lock: Lock for protecting MHI state transition list * @wlock: Lock for protecting device wakeup @@ -508,6 +511,7 @@ struct mhi_result { /** * struct mhi_driver - Structure representing a MHI client driver + * @id_table: table of MHI channel names that a driver supports * @probe: CB function for client driver probe function * @remove: CB function for client driver remove function * @ul_xfer_cb: CB function for UL data transfer @@ -539,6 +543,7 @@ struct mhi_controller *mhi_alloc_controller(void); /** * mhi_free_controller - Free the MHI Controller structure + * @mhi_cntrl: MHI controller to free * Free the mhi_controller structure which was previously allocated */ void mhi_free_controller(struct mhi_controller *mhi_cntrl); From 7cc5ddce0a622359eecf16d97080dc96edf13b52 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 22 Jul 2026 07:54:44 +0200 Subject: [PATCH 10/12] bus: mhi: ep: Add mhi_cntrl->flush_async() callback to flush the async read/write MHI EP stack makes use of the MHI controller drivers like MHI EPF to do read/write to the host memory. And that driver is free to use mechanisms like DMA to offload the read/write operations. So if DMA is used for offload, then there is no guarantee that those DMA operations would be completed by the time mhi_ep_remove() gets called. This can lead to UAF (Use-After-Free) issues as the DMA callback can trigger xfer_cb() even after mhi_ep_remove() has returned. So to fix this issue, introduce the mhi_cntrl->flush_async() callback and call it in mhi_ep_remove() to drain all the in-flight async transfers before disconnecting the channels. The completion handlers keep triggering xfer_cb() as long as it is set. So flushing the transfers after notifying the client about the disconnect (-ENOTCONN) would still let a success callback slip through afterwards and lead to the same UAF. So disable the channels first to prevent new transfers, then flush the in-flight transfers so that their completions are delivered while xfer_cb() is still valid and only then notify the disconnect and clear xfer_cb(). Fixes: 2547beb00ddb ("bus: mhi: ep: Add support for async DMA read operation") Fixes: ee08acb58fe4 ("bus: mhi: ep: Add support for async DMA write operation") Reviewed-by: Frank Li Cc: stable+noautosel@kernel.org # Needs dmaengine driver fix as well Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/ep/main.c | 18 +++++++++++++++++- include/linux/mhi_ep.h | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c index 43d680778704..73663e7c1120 100644 --- a/drivers/bus/mhi/ep/main.c +++ b/drivers/bus/mhi/ep/main.c @@ -1620,6 +1620,7 @@ static void mhi_ep_remove(struct device *dev) { struct mhi_ep_device *mhi_dev = to_mhi_ep_device(dev); struct mhi_ep_driver *mhi_drv = to_mhi_ep_driver(dev->driver); + struct mhi_ep_cntrl *mhi_cntrl = mhi_dev->mhi_cntrl; struct mhi_result result = {}; struct mhi_ep_chan *mhi_chan; int dir; @@ -1628,6 +1629,22 @@ static void mhi_ep_remove(struct device *dev) if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER) return; + /* Disable the channels to prevent new transfers */ + for (dir = 0; dir < 2; dir++) { + mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan; + + if (!mhi_chan) + continue; + + mutex_lock(&mhi_chan->lock); + mhi_chan->state = MHI_CH_STATE_DISABLED; + mutex_unlock(&mhi_chan->lock); + } + + /* Flush in-flight transfers before notifying disconnect */ + if (mhi_cntrl->flush_async) + mhi_cntrl->flush_async(mhi_cntrl); + /* Disconnect the channels associated with the driver */ for (dir = 0; dir < 2; dir++) { mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan; @@ -1643,7 +1660,6 @@ static void mhi_ep_remove(struct device *dev) mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); } - mhi_chan->state = MHI_CH_STATE_DISABLED; mhi_chan->xfer_cb = NULL; mutex_unlock(&mhi_chan->lock); } diff --git a/include/linux/mhi_ep.h b/include/linux/mhi_ep.h index 7b40fc8cbe77..f6383a57a872 100644 --- a/include/linux/mhi_ep.h +++ b/include/linux/mhi_ep.h @@ -107,6 +107,7 @@ struct mhi_ep_buf_info { * @write_sync: CB function for writing to host memory synchronously * @read_async: CB function for reading from host memory asynchronously * @write_async: CB function for writing to host memory asynchronously + * @flush_async: CB function for flushing asynchronous read/writes * @mhi_state: MHI Endpoint state * @max_chan: Maximum channels supported by the endpoint controller * @mru: MRU (Maximum Receive Unit) value of the endpoint controller @@ -164,6 +165,7 @@ struct mhi_ep_cntrl { int (*write_sync)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info); int (*read_async)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info); int (*write_async)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info); + void (*flush_async)(struct mhi_ep_cntrl *mhi_cntrl); enum mhi_state mhi_state; From 5993e10b82a4bc5b034870dae788a7eb5ce62e93 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 22 Jul 2026 07:54:45 +0200 Subject: [PATCH 11/12] bus: mhi: ep: Flush async transfers before notifying disconnect in mhi_ep_abort_transfer() mhi_ep_abort_transfer() notifies the client drivers about the channel disconnect using -ENOTCONN and only then flushes the ring workqueue to drain the in-flight transfers. But the async DMA transfers issued by the ring workers can still complete after the notification. And the completion handlers trigger the client xfer_cb() as long as it is set. So a transfer completing during the flush can deliver a success callback to the client even after it has been notified about the disconnect. This can lead to UAF (Use-After-Free) issues as the client can free its per-transfer resources in response to the -ENOTCONN notification and the trailing success callback would then reference the freed resources. So to fix this issue, disable all the channels first to prevent new transfers and then drain both the ring workqueue and the in-flight async transfers before notifying the disconnect. The completion and queue paths bail out once the channel state is not MHI_CH_STATE_RUNNING, so disabling the channels upfront makes sure that no new transfer sneaks in during the drain and all the pending completions are delivered while xfer_cb() is still valid. Reviewed-by: Frank Li Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/ep/main.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c index 73663e7c1120..038b47158f0e 100644 --- a/drivers/bus/mhi/ep/main.c +++ b/drivers/bus/mhi/ep/main.c @@ -1027,26 +1027,37 @@ static void mhi_ep_abort_transfer(struct mhi_ep_cntrl *mhi_cntrl) struct mhi_ep_chan *mhi_chan; int i; - /* Stop all the channels */ + /* Disable all the channels to prevent new transfers */ + for (i = 0; i < mhi_cntrl->max_chan; i++) { + mhi_chan = &mhi_cntrl->mhi_chan[i]; + if (!mhi_chan->ring.started) + continue; + + mutex_lock(&mhi_chan->lock); + mhi_chan->state = MHI_CH_STATE_DISABLED; + mutex_unlock(&mhi_chan->lock); + } + + /* Drain ring workers and in-flight transfers before notifying disconnect */ + flush_workqueue(mhi_cntrl->wq); + if (mhi_cntrl->flush_async) + mhi_cntrl->flush_async(mhi_cntrl); + + /* Send channel disconnect status to client drivers */ for (i = 0; i < mhi_cntrl->max_chan; i++) { mhi_chan = &mhi_cntrl->mhi_chan[i]; if (!mhi_chan->ring.started) continue; mutex_lock(&mhi_chan->lock); - /* Send channel disconnect status to client drivers */ if (mhi_chan->xfer_cb) { result.transaction_status = -ENOTCONN; result.bytes_xferd = 0; mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); } - - mhi_chan->state = MHI_CH_STATE_DISABLED; mutex_unlock(&mhi_chan->lock); } - flush_workqueue(mhi_cntrl->wq); - /* Destroy devices associated with all channels */ device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_ep_destroy_device); From 9656bcd4c321a799148d00dd830ce7ebf20011da Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 22 Jul 2026 07:54:46 +0200 Subject: [PATCH 12/12] PCI: epf-mhi: Implement mhi_cntrl->flush_async() to flush DMA read/write The MHI core needs to make sure that all the current DMA transactions are completed before removing the channels. So implement the mhi_cntrl->flush_async() callback by first making sure all the in-flight DMA operations are completed and then flushing the DMA workqueue. Reviewed-by: Frank Li Signed-off-by: Manivannan Sadhasivam --- drivers/pci/endpoint/functions/pci-epf-mhi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/pci/endpoint/functions/pci-epf-mhi.c b/drivers/pci/endpoint/functions/pci-epf-mhi.c index 7f5326925ed5..8d2d9d01cfd2 100644 --- a/drivers/pci/endpoint/functions/pci-epf-mhi.c +++ b/drivers/pci/endpoint/functions/pci-epf-mhi.c @@ -644,6 +644,15 @@ err_unlock: return ret; } +static void pci_epf_mhi_edma_flush_async(struct mhi_ep_cntrl *mhi_cntrl) +{ + struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl); + + dmaengine_synchronize(epf_mhi->dma_chan_rx); + dmaengine_synchronize(epf_mhi->dma_chan_tx); + flush_workqueue(epf_mhi->dma_wq); +} + struct epf_dma_filter { struct device *dev; u32 dma_mask; @@ -812,6 +821,7 @@ static int pci_epf_mhi_link_up(struct pci_epf *epf) mhi_cntrl->write_sync = pci_epf_mhi_edma_write; mhi_cntrl->read_async = pci_epf_mhi_edma_read_async; mhi_cntrl->write_async = pci_epf_mhi_edma_write_async; + mhi_cntrl->flush_async = pci_epf_mhi_edma_flush_async; } /* Register the MHI EP controller */