Currently in ath11k we keep the firmware running on the WLAN device when the
network interface (wlan0) is down. The problem is that this will break
hibernation, obviously the firmware can't be running after the whole system is
powered off. To power down the ath11k firmware for suspend/hibernation some
changes both in MHI subsystem and ath11k are needed.
This patchset fixes a longstanding bug report about broken hibernation support:
https://bugzilla.kernel.org/show_bug.cgi?id=214649
There already is an RFC version which has been tested by multiple users with
positive results:
https://patchwork.kernel.org/project/linux-wireless/cover/[email protected]/
Basically the RFC version adds two APIs to MHI stack: with the first one ath11k
is able to keep MHI devices when going to suspend/hibernation, getting us rid of
the probe deferral issue when resume back. while with the second one ath11k could
manually prepare/unprepare MHI channels by itself, which is needed because QRTR
doesn't probe those channels automatically in this case.
Mani, the MHI maintainer, firstly doesn't like that version and insists that an
MHI device should be destroyed when suspend/hibernation, according to his
understanding on device driver model. See
https://lore.kernel.org/mhi/[email protected]/
After a long discussion Mani thought we might need a new PM callback with which
ath11k is able to wait until kernel unblocks device probe and thus MHI channels
get probed. So we came to the kernel PM list and there Mani realized that his
understanding is not correct so he finally agrees to keep MHI device during
suspend/hibernation. See
https://lore.kernel.org/all/[email protected]/
Mani also pointed out that an MHI controller driver (ath11k here) should not touch
MHI channels directly because those channels are managed by the corresponding MHI
client driver (QRTR here). To address this, we come up with this version.
Compared with that RFC version, this version adds PM callbacks in QRTR module:
suspend callback unprepares MHI channels during suspend and resume callback
prepares those channels during resume. In this way ath11k doesn't need to do
unprepare/prepare work by itself so those two APIs added in RFC version are
removed now.
The power down/up procedure requires a specific sequence in which PM callbacks
of wiphy, ath11k and QRTR are called, this is achieved by exploiting the
child-father relationship between their device struct, and also the PM framework
which separates whole suspend/resume process into several stages. Details in
patch [3/3].
Depends on:
wifi: ath11k: rearrange IRQ enable/disable in reset path
wifi: ath11k: remove MHI LOOPBACK channels
wifi: ath11k: do not dump SRNG statistics during resume
wifi: ath11k: fix warning on DMA ring capabilities event
wifi: ath11k: thermal: don't try to register multiple times
Baochen Qiang (3):
bus: mhi: host: add mhi_power_down_no_destroy()
net: qrtr: support suspend/hibernation
wifi: ath11k: support hibernation
drivers/bus/mhi/host/internal.h | 4 +-
drivers/bus/mhi/host/pm.c | 36 +++++++--
drivers/net/wireless/ath/ath11k/ahb.c | 6 +-
drivers/net/wireless/ath/ath11k/core.c | 105 +++++++++++++++++--------
drivers/net/wireless/ath/ath11k/core.h | 6 +-
drivers/net/wireless/ath/ath11k/hif.h | 14 +++-
drivers/net/wireless/ath/ath11k/mhi.c | 12 ++-
drivers/net/wireless/ath/ath11k/mhi.h | 5 +-
drivers/net/wireless/ath/ath11k/pci.c | 44 +++++++++--
drivers/net/wireless/ath/ath11k/qmi.c | 2 +-
include/linux/mhi.h | 15 +++-
net/qrtr/mhi.c | 29 +++++++
12 files changed, 218 insertions(+), 60 deletions(-)
base-commit: 707e306f3573fa321ae197d77366578e4566cff5
prerequisite-patch-id: d3f76112f9a55195c71459e0edf3a4ecf8af9181
prerequisite-patch-id: 340d15aad1d3c1c3c93d9d996e1c96226c8bad8f
prerequisite-patch-id: 98cdd37a68df4f651a065145e946d92c43be799e
prerequisite-patch-id: a19ed13af0c894b7f9b22cedc99029991f48e47c
prerequisite-patch-id: b5ad50fae6167c7c2b60cc063a8d460f7ddd4997
--
2.25.1
ath11k fails to resume:
ath11k_pci 0000:06:00.0: timeout while waiting for restart complete
This happens because when calling mhi_sync_power_up() the MHI subsystem
eventually calls device_add() from mhi_create_devices() but the device
creation is deferred:
mhi mhi0_IPCR: Driver qcom_mhi_qrtr force probe deferral
The reason for deferring device creation is explained in dpm_prepare():
/*
* It is unsafe if probing of devices will happen during suspend or
* hibernation and system behavior will be unpredictable in this case.
* So, let's prohibit device's probing here and defer their probes
* instead. The normal behavior will be restored in dpm_complete().
*/
Because the device probe is deferred, the qcom_mhi_qrtr_probe() is not
called and thus MHI channels are not prepared:
So what this means that QRTR is not delivering messages and the QMI connection
is not working between ath11k and the firmware, resulting a failure in firmware
initialization.
To fix this add new function mhi_power_down_no_destroy() which doesn't destroy
the devices for channels during power down. This way we avoid probe defer issue
and finally can get ath11k hibernation working with the following patches.
Actually there is an RFC version of this change and it gets positive results
from multiple users. Firstly Mani doesn't like this idea and insists that an
MHI device should be destroyed when going to suspend/hibernation, see
https://lore.kernel.org/mhi/[email protected]/
Then Mani changed his mind after a further discussion with kernel PM guys,
see
https://lore.kernel.org/all/[email protected]/
So we come up with the regular version and it is almost identical with that RFC
version.
Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
Signed-off-by: Kalle Valo <[email protected]>
Signed-off-by: Baochen Qiang <[email protected]>
---
drivers/bus/mhi/host/internal.h | 4 +++-
drivers/bus/mhi/host/pm.c | 36 +++++++++++++++++++++++++++------
include/linux/mhi.h | 15 +++++++++++++-
3 files changed, 47 insertions(+), 8 deletions(-)
diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h
index 091244cf17c6..8ce4aec56425 100644
--- a/drivers/bus/mhi/host/internal.h
+++ b/drivers/bus/mhi/host/internal.h
@@ -86,6 +86,7 @@ enum dev_st_transition {
DEV_ST_TRANSITION_FP,
DEV_ST_TRANSITION_SYS_ERR,
DEV_ST_TRANSITION_DISABLE,
+ DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE,
DEV_ST_TRANSITION_MAX,
};
@@ -96,7 +97,8 @@ enum dev_st_transition {
dev_st_trans(MISSION_MODE, "MISSION MODE") \
dev_st_trans(FP, "FLASH PROGRAMMER") \
dev_st_trans(SYS_ERR, "SYS ERROR") \
- dev_st_trans_end(DISABLE, "DISABLE")
+ dev_st_trans(DISABLE, "DISABLE") \
+ dev_st_trans_end(DISABLE_DESTROY_DEVICE, "DISABLE (DESTROY DEVICE)")
extern const char * const dev_state_tran_str[DEV_ST_TRANSITION_MAX];
#define TO_DEV_STATE_TRANS_STR(state) (((state) >= DEV_ST_TRANSITION_MAX) ? \
diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c
index 8b40d3f01acc..5686d32f7458 100644
--- a/drivers/bus/mhi/host/pm.c
+++ b/drivers/bus/mhi/host/pm.c
@@ -468,7 +468,8 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl)
}
/* Handle shutdown transitions */
-static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl)
+static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
+ bool destroy_device)
{
enum mhi_pm_state cur_state;
struct mhi_event *mhi_event;
@@ -530,8 +531,10 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl)
dev_dbg(dev, "Waiting for all pending threads to complete\n");
wake_up_all(&mhi_cntrl->state_event);
- dev_dbg(dev, "Reset all active channels and remove MHI devices\n");
- device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
+ if (destroy_device) {
+ dev_dbg(dev, "Reset all active channels and remove MHI devices\n");
+ device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
+ }
mutex_lock(&mhi_cntrl->pm_mutex);
@@ -821,7 +824,10 @@ void mhi_pm_st_worker(struct work_struct *work)
mhi_pm_sys_error_transition(mhi_cntrl);
break;
case DEV_ST_TRANSITION_DISABLE:
- mhi_pm_disable_transition(mhi_cntrl);
+ mhi_pm_disable_transition(mhi_cntrl, false);
+ break;
+ case DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE:
+ mhi_pm_disable_transition(mhi_cntrl, true);
break;
default:
break;
@@ -1175,7 +1181,8 @@ int mhi_async_power_up(struct mhi_controller *mhi_cntrl)
}
EXPORT_SYMBOL_GPL(mhi_async_power_up);
-void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
+static void __mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful,
+ bool destroy_device)
{
enum mhi_pm_state cur_state, transition_state;
struct device *dev = &mhi_cntrl->mhi_dev->dev;
@@ -1211,15 +1218,32 @@ void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
write_unlock_irq(&mhi_cntrl->pm_lock);
mutex_unlock(&mhi_cntrl->pm_mutex);
- mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_DISABLE);
+ if (destroy_device)
+ mhi_queue_state_transition(mhi_cntrl,
+ DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE);
+ else
+ mhi_queue_state_transition(mhi_cntrl,
+ DEV_ST_TRANSITION_DISABLE);
/* Wait for shutdown to complete */
flush_work(&mhi_cntrl->st_worker);
disable_irq(mhi_cntrl->irq[0]);
}
+
+void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
+{
+ __mhi_power_down(mhi_cntrl, graceful, true);
+}
EXPORT_SYMBOL_GPL(mhi_power_down);
+void mhi_power_down_no_destroy(struct mhi_controller *mhi_cntrl,
+ bool graceful)
+{
+ __mhi_power_down(mhi_cntrl, graceful, false);
+}
+EXPORT_SYMBOL_GPL(mhi_power_down_no_destroy);
+
int mhi_sync_power_up(struct mhi_controller *mhi_cntrl)
{
int ret = mhi_async_power_up(mhi_cntrl);
diff --git a/include/linux/mhi.h b/include/linux/mhi.h
index 474d32cb0520..39a6a944a52c 100644
--- a/include/linux/mhi.h
+++ b/include/linux/mhi.h
@@ -647,12 +647,25 @@ int mhi_async_power_up(struct mhi_controller *mhi_cntrl);
int mhi_sync_power_up(struct mhi_controller *mhi_cntrl);
/**
- * mhi_power_down - Start MHI power down sequence
+ * mhi_power_down - Start MHI power down sequence. See also
+ * mhi_power_down_no_destroy() which is a variant of this for
+ * suspend/hibernation.
+ *
* @mhi_cntrl: MHI controller
* @graceful: Link is still accessible, so do a graceful shutdown process
*/
void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful);
+/**
+ * mhi_power_down_no_destroy - Start MHI power down sequence but don't destroy
+ * struct devices for channels. This is a variant for mhi_power_down() and
+ * would be useful in suspend/hibernation.
+ *
+ * @mhi_cntrl: MHI controller
+ * @graceful: Link is still accessible, so do a graceful shutdown process
+ */
+void mhi_power_down_no_destroy(struct mhi_controller *mhi_cntrl, bool graceful);
+
/**
* mhi_unprepare_after_power_down - Free any allocated memory after power down
* @mhi_cntrl: MHI controller
--
2.25.1
MHI devices may not be destroyed during suspend/hibernation, so need
to unprepare/prepare MHI channels throughout the transition.
The RFC version adds new API to MHI stack with which an MHI controller
driver can do unprepare/prepare directly by itself, see
https://patchwork.kernel.org/project/linux-wireless/patch/[email protected]/
Although it works well Mani pointed out that the design is not good
because MHI channels are managed by MHI client driver thus should not
be touched by others. See the discussion
https://lore.kernel.org/mhi/[email protected]/
This version changes to add suspend/resume callbacks to achieve the
same purpose. The suspend callback is called in the late suspend stage,
this means MHI channels are still alive at suspend stage, and that makes
it possible for an MHI controller driver to communicate with others over
those channels at suspend stage. While the resume callback is called in
the early resume stage, for a similar reason.
Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
Signed-off-by: Baochen Qiang <[email protected]>
---
net/qrtr/mhi.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c
index 9ced13c0627a..b54a6c2113e9 100644
--- a/net/qrtr/mhi.c
+++ b/net/qrtr/mhi.c
@@ -118,6 +118,32 @@ static const struct mhi_device_id qcom_mhi_qrtr_id_table[] = {
};
MODULE_DEVICE_TABLE(mhi, qcom_mhi_qrtr_id_table);
+static int qcom_mhi_qrtr_pm_suspend_late(struct device *dev)
+{
+ struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
+
+ mhi_unprepare_from_transfer(mhi_dev);
+
+ return 0;
+}
+
+static int qcom_mhi_qrtr_pm_resume_early(struct device *dev)
+{
+ struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
+ int rc;
+
+ rc = mhi_prepare_for_transfer_autoqueue(mhi_dev);
+ if (rc)
+ dev_err(dev, "failed to prepare for autoqueue transfer %d\n", rc);
+
+ return rc;
+}
+
+static const struct dev_pm_ops __maybe_unused qcom_mhi_qrtr_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(qcom_mhi_qrtr_pm_suspend_late,
+ qcom_mhi_qrtr_pm_resume_early)
+};
+
static struct mhi_driver qcom_mhi_qrtr_driver = {
.probe = qcom_mhi_qrtr_probe,
.remove = qcom_mhi_qrtr_remove,
@@ -126,6 +152,9 @@ static struct mhi_driver qcom_mhi_qrtr_driver = {
.id_table = qcom_mhi_qrtr_id_table,
.driver = {
.name = "qcom_mhi_qrtr",
+#ifdef CONFIG_PM
+ .pm = &qcom_mhi_qrtr_pm_ops,
+#endif
},
};
--
2.25.1
Now that all infrastructure is in place and ath11k is fixed to handle all the
corner cases, power down the ath11k firmware during suspend and power it back
up during resume. This fixes the problem when using hibernation with ath11k PCI
devices.
For suspend, two conditions needs to be satisfied:
1. since MHI channel unprepare would be done in late suspend stage,
ath11k needs to get all QMI-dependent things done before that stage.
2. and because unprepare MHI channels requires a working MHI stack,
ath11k is not allowed to call mhi_power_down() until that finishes.
So the original suspend callback is separated into two parts: the first part
handles all QMI-dependent things in suspend callback; while the second part
powers down MHI in suspend_late callback. This is valid because kernel calls
ath11k's suspend callback before all suspend_late callbacks, making the first
condition happy. And because MHI devices are children of ath11k device
(ab->dev), kernel guarantees that ath11k's suspend_late callback is called
after QRTR's suspend_late callback, this satisfies the second condition.
Above analysis also applies to resume process. so the original resume
callback is separated into two parts: the first part powers up MHI stack
in resume_early callback, this guarantees MHI stack is working when
QRTR tries to prepare MHI channels (kernel calls QRTR's resume_early callback
after ath11k's resume_early callback, due to the child-father relationship);
the second part waits for the completion of restart, which won't fail now
since MHI channels are ready for use by QMI.
Another notable change is in power down path, we tell mhi_power_down() to not
to destroy MHI devices, making it possible for QRTR to help unprepare/prepare
MHI channels, and finally get us rid of the probe-defer issue when resume.
Also change related code due to interface changes.
Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
Tested-by: Takashi Iwai <[email protected]>
Signed-off-by: Kalle Valo <[email protected]>
Signed-off-by: Baochen Qiang <[email protected]>
---
drivers/net/wireless/ath/ath11k/ahb.c | 6 +-
drivers/net/wireless/ath/ath11k/core.c | 105 +++++++++++++++++--------
drivers/net/wireless/ath/ath11k/core.h | 6 +-
drivers/net/wireless/ath/ath11k/hif.h | 14 +++-
drivers/net/wireless/ath/ath11k/mhi.c | 12 ++-
drivers/net/wireless/ath/ath11k/mhi.h | 5 +-
drivers/net/wireless/ath/ath11k/pci.c | 44 +++++++++--
drivers/net/wireless/ath/ath11k/qmi.c | 2 +-
8 files changed, 142 insertions(+), 52 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c
index 7c0a23517949..60b4c2800a33 100644
--- a/drivers/net/wireless/ath/ath11k/ahb.c
+++ b/drivers/net/wireless/ath/ath11k/ahb.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/module.h>
@@ -413,7 +413,7 @@ static int ath11k_ahb_power_up(struct ath11k_base *ab)
return ret;
}
-static void ath11k_ahb_power_down(struct ath11k_base *ab)
+static void ath11k_ahb_power_down(struct ath11k_base *ab, bool is_suspend)
{
struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab);
@@ -1256,7 +1256,7 @@ static void ath11k_ahb_remove(struct platform_device *pdev)
struct ath11k_base *ab = platform_get_drvdata(pdev);
if (test_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags)) {
- ath11k_ahb_power_down(ab);
+ ath11k_ahb_power_down(ab, false);
ath11k_debugfs_soc_destroy(ab);
ath11k_qmi_deinit_service(ab);
goto qmi_fail;
diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
index c78bce19bd75..8533bf3174d2 100644
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -894,12 +894,6 @@ int ath11k_core_suspend(struct ath11k_base *ab)
return ret;
}
- ret = ath11k_wow_enable(ab);
- if (ret) {
- ath11k_warn(ab, "failed to enable wow during suspend: %d\n", ret);
- return ret;
- }
-
ret = ath11k_dp_rx_pktlog_stop(ab, false);
if (ret) {
ath11k_warn(ab, "failed to stop dp rx pktlog during suspend: %d\n",
@@ -910,24 +904,80 @@ int ath11k_core_suspend(struct ath11k_base *ab)
ath11k_ce_stop_shadow_timers(ab);
ath11k_dp_stop_shadow_timers(ab);
+ /* PM framework skips suspend_late/resume_early callbacks
+ * if other devices report errors in their suspend callbacks.
+ * However ath11k_core_resume() would still be called because
+ * here we return success thus kernel put us on dpm_suspended_list.
+ * Since we won't go through a power down/up cycle, there is
+ * no chance to call complete(&ab->restart_completed) in
+ * ath11k_core_restart(), making ath11k_core_resume() timeout.
+ * So call it here to avoid this issue. This also works in case
+ * no error happens thus suspend_late/resume_early get called,
+ * because it will be reinitialized in ath11k_core_resume_early().
+ */
+ complete(&ab->restart_completed);
+
+ return 0;
+}
+EXPORT_SYMBOL(ath11k_core_suspend);
+
+int ath11k_core_suspend_late(struct ath11k_base *ab)
+{
+ struct ath11k_pdev *pdev;
+ struct ath11k *ar;
+
+ if (!ab->hw_params.supports_suspend)
+ return -EOPNOTSUPP;
+
+ /* so far single_pdev_only chips have supports_suspend as true
+ * and only the first pdev is valid.
+ */
+ pdev = ath11k_core_get_single_pdev(ab);
+ ar = pdev->ar;
+ if (!ar || ar->state != ATH11K_STATE_OFF)
+ return 0;
+
ath11k_hif_irq_disable(ab);
ath11k_hif_ce_irq_disable(ab);
- ret = ath11k_hif_suspend(ab);
- if (ret) {
- ath11k_warn(ab, "failed to suspend hif: %d\n", ret);
- return ret;
- }
+ ath11k_hif_power_down(ab, true);
return 0;
}
-EXPORT_SYMBOL(ath11k_core_suspend);
+EXPORT_SYMBOL(ath11k_core_suspend_late);
+
+int ath11k_core_resume_early(struct ath11k_base *ab)
+{
+ int ret;
+ struct ath11k_pdev *pdev;
+ struct ath11k *ar;
+
+ if (!ab->hw_params.supports_suspend)
+ return -EOPNOTSUPP;
+
+ /* so far signle_pdev_only chips have supports_suspend as true
+ * and only the first pdev is valid.
+ */
+ pdev = ath11k_core_get_single_pdev(ab);
+ ar = pdev->ar;
+ if (!ar || ar->state != ATH11K_STATE_OFF)
+ return 0;
+
+ reinit_completion(&ab->restart_completed);
+ ret = ath11k_hif_power_up(ab);
+ if (ret)
+ ath11k_warn(ab, "failed to power up hif during resume: %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(ath11k_core_resume_early);
int ath11k_core_resume(struct ath11k_base *ab)
{
int ret;
struct ath11k_pdev *pdev;
struct ath11k *ar;
+ long time_left;
if (!ab->hw_params.supports_suspend)
return -EOPNOTSUPP;
@@ -940,29 +990,19 @@ int ath11k_core_resume(struct ath11k_base *ab)
if (!ar || ar->state != ATH11K_STATE_OFF)
return 0;
- ret = ath11k_hif_resume(ab);
- if (ret) {
- ath11k_warn(ab, "failed to resume hif during resume: %d\n", ret);
- return ret;
+ time_left = wait_for_completion_timeout(&ab->restart_completed,
+ ATH11K_RESET_TIMEOUT_HZ);
+ if (time_left == 0) {
+ ath11k_warn(ab, "timeout while waiting for restart complete");
+ return -ETIMEDOUT;
}
- ath11k_hif_ce_irq_enable(ab);
- ath11k_hif_irq_enable(ab);
-
ret = ath11k_dp_rx_pktlog_start(ab);
- if (ret) {
+ if (ret)
ath11k_warn(ab, "failed to start rx pktlog during resume: %d\n",
ret);
- return ret;
- }
-
- ret = ath11k_wow_wakeup(ab);
- if (ret) {
- ath11k_warn(ab, "failed to wakeup wow during resume: %d\n", ret);
- return ret;
- }
- return 0;
+ return ret;
}
EXPORT_SYMBOL(ath11k_core_resume);
@@ -2060,6 +2100,8 @@ static void ath11k_core_restart(struct work_struct *work)
if (!ab->is_reset)
ath11k_core_post_reconfigure_recovery(ab);
+
+ complete(&ab->restart_completed);
}
static void ath11k_core_reset(struct work_struct *work)
@@ -2129,7 +2171,7 @@ static void ath11k_core_reset(struct work_struct *work)
ath11k_hif_irq_disable(ab);
ath11k_hif_ce_irq_disable(ab);
- ath11k_hif_power_down(ab);
+ ath11k_hif_power_down(ab, false);
ath11k_hif_power_up(ab);
ath11k_dbg(ab, ATH11K_DBG_BOOT, "reset started\n");
@@ -2202,7 +2244,7 @@ void ath11k_core_deinit(struct ath11k_base *ab)
mutex_unlock(&ab->core_lock);
- ath11k_hif_power_down(ab);
+ ath11k_hif_power_down(ab, false);
ath11k_mac_destroy(ab);
ath11k_core_soc_destroy(ab);
ath11k_fw_destroy(ab);
@@ -2255,6 +2297,7 @@ struct ath11k_base *ath11k_core_alloc(struct device *dev, size_t priv_size,
timer_setup(&ab->rx_replenish_retry, ath11k_ce_rx_replenish_retry, 0);
init_completion(&ab->htc_suspend);
init_completion(&ab->wow.wakeup_completed);
+ init_completion(&ab->restart_completed);
ab->dev = dev;
ab->hif.bus = bus;
diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h
index b3fb74a226fb..a20c29e3a227 100644
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef ATH11K_CORE_H
@@ -1033,6 +1033,8 @@ struct ath11k_base {
DECLARE_BITMAP(fw_features, ATH11K_FW_FEATURE_COUNT);
} fw;
+ struct completion restart_completed;
+
#ifdef CONFIG_NL80211_TESTMODE
struct {
u32 data_pos;
@@ -1232,8 +1234,10 @@ void ath11k_core_free_bdf(struct ath11k_base *ab, struct ath11k_board_data *bd);
int ath11k_core_check_dt(struct ath11k_base *ath11k);
int ath11k_core_check_smbios(struct ath11k_base *ab);
void ath11k_core_halt(struct ath11k *ar);
+int ath11k_core_resume_early(struct ath11k_base *ab);
int ath11k_core_resume(struct ath11k_base *ab);
int ath11k_core_suspend(struct ath11k_base *ab);
+int ath11k_core_suspend_late(struct ath11k_base *ab);
void ath11k_core_pre_reconfigure_recovery(struct ath11k_base *ab);
bool ath11k_core_coldboot_cal_support(struct ath11k_base *ab);
diff --git a/drivers/net/wireless/ath/ath11k/hif.h b/drivers/net/wireless/ath/ath11k/hif.h
index 877a4073fed6..c4c6cc09c7c1 100644
--- a/drivers/net/wireless/ath/ath11k/hif.h
+++ b/drivers/net/wireless/ath/ath11k/hif.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
- * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _HIF_H_
@@ -18,7 +18,7 @@ struct ath11k_hif_ops {
int (*start)(struct ath11k_base *ab);
void (*stop)(struct ath11k_base *ab);
int (*power_up)(struct ath11k_base *ab);
- void (*power_down)(struct ath11k_base *ab);
+ void (*power_down)(struct ath11k_base *ab, bool is_suspend);
int (*suspend)(struct ath11k_base *ab);
int (*resume)(struct ath11k_base *ab);
int (*map_service_to_pipe)(struct ath11k_base *ab, u16 service_id,
@@ -67,12 +67,18 @@ static inline void ath11k_hif_irq_disable(struct ath11k_base *ab)
static inline int ath11k_hif_power_up(struct ath11k_base *ab)
{
+ if (!ab->hif.ops->power_up)
+ return -EOPNOTSUPP;
+
return ab->hif.ops->power_up(ab);
}
-static inline void ath11k_hif_power_down(struct ath11k_base *ab)
+static inline void ath11k_hif_power_down(struct ath11k_base *ab, bool is_suspend)
{
- ab->hif.ops->power_down(ab);
+ if (!ab->hif.ops->power_down)
+ return;
+
+ ab->hif.ops->power_down(ab, is_suspend);
}
static inline int ath11k_hif_suspend(struct ath11k_base *ab)
diff --git a/drivers/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c
index 3de7fa6f88d0..4029f6debd82 100644
--- a/drivers/net/wireless/ath/ath11k/mhi.c
+++ b/drivers/net/wireless/ath/ath11k/mhi.c
@@ -442,9 +442,17 @@ int ath11k_mhi_start(struct ath11k_pci *ab_pci)
return 0;
}
-void ath11k_mhi_stop(struct ath11k_pci *ab_pci)
+void ath11k_mhi_stop(struct ath11k_pci *ab_pci, bool is_suspend)
{
- mhi_power_down(ab_pci->mhi_ctrl, true);
+ /* During suspend we need to use mhi_power_down_no_destroy()
+ * workaround, otherwise ath11k_core_resume() will timeout
+ * during resume.
+ */
+ if (is_suspend)
+ mhi_power_down_no_destroy(ab_pci->mhi_ctrl, true);
+ else
+ mhi_power_down(ab_pci->mhi_ctrl, true);
+
mhi_unprepare_after_power_down(ab_pci->mhi_ctrl);
}
diff --git a/drivers/net/wireless/ath/ath11k/mhi.h b/drivers/net/wireless/ath/ath11k/mhi.h
index f81fba2644a4..2d567705e732 100644
--- a/drivers/net/wireless/ath/ath11k/mhi.h
+++ b/drivers/net/wireless/ath/ath11k/mhi.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _ATH11K_MHI_H
#define _ATH11K_MHI_H
@@ -18,7 +18,7 @@
#define MHICTRL_RESET_MASK 0x2
int ath11k_mhi_start(struct ath11k_pci *ar_pci);
-void ath11k_mhi_stop(struct ath11k_pci *ar_pci);
+void ath11k_mhi_stop(struct ath11k_pci *ar_pci, bool is_suspend);
int ath11k_mhi_register(struct ath11k_pci *ar_pci);
void ath11k_mhi_unregister(struct ath11k_pci *ar_pci);
void ath11k_mhi_set_mhictrl_reset(struct ath11k_base *ab);
@@ -26,5 +26,4 @@ void ath11k_mhi_clear_vector(struct ath11k_base *ab);
int ath11k_mhi_suspend(struct ath11k_pci *ar_pci);
int ath11k_mhi_resume(struct ath11k_pci *ar_pci);
-
#endif
diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
index be9d2c69cc41..8d63b84d1261 100644
--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -638,7 +638,7 @@ static int ath11k_pci_power_up(struct ath11k_base *ab)
return 0;
}
-static void ath11k_pci_power_down(struct ath11k_base *ab)
+static void ath11k_pci_power_down(struct ath11k_base *ab, bool is_suspend)
{
struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
@@ -649,7 +649,7 @@ static void ath11k_pci_power_down(struct ath11k_base *ab)
ath11k_pci_msi_disable(ab_pci);
- ath11k_mhi_stop(ab_pci);
+ ath11k_mhi_stop(ab_pci, is_suspend);
clear_bit(ATH11K_FLAG_DEVICE_INIT_DONE, &ab->dev_flags);
ath11k_pci_sw_reset(ab_pci->ab, false);
}
@@ -970,7 +970,7 @@ static void ath11k_pci_remove(struct pci_dev *pdev)
ath11k_pci_set_irq_affinity_hint(ab_pci, NULL);
if (test_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags)) {
- ath11k_pci_power_down(ab);
+ ath11k_pci_power_down(ab, false);
ath11k_debugfs_soc_destroy(ab);
ath11k_qmi_deinit_service(ab);
goto qmi_fail;
@@ -998,7 +998,7 @@ static void ath11k_pci_shutdown(struct pci_dev *pdev)
struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
ath11k_pci_set_irq_affinity_hint(ab_pci, NULL);
- ath11k_pci_power_down(ab);
+ ath11k_pci_power_down(ab, false);
}
static __maybe_unused int ath11k_pci_pm_suspend(struct device *dev)
@@ -1035,9 +1035,39 @@ static __maybe_unused int ath11k_pci_pm_resume(struct device *dev)
return ret;
}
-static SIMPLE_DEV_PM_OPS(ath11k_pci_pm_ops,
- ath11k_pci_pm_suspend,
- ath11k_pci_pm_resume);
+static __maybe_unused int ath11k_pci_pm_suspend_late(struct device *dev)
+{
+ struct ath11k_base *ab = dev_get_drvdata(dev);
+ int ret;
+
+ ret = ath11k_core_suspend_late(ab);
+ if (ret)
+ ath11k_warn(ab, "failed to late suspend core: %d\n", ret);
+
+ /* Similar to ath11k_pci_pm_suspend(), we return success here
+ * even error happens, to allow system suspend/hibernation survive.
+ */
+ return 0;
+}
+
+static __maybe_unused int ath11k_pci_pm_resume_early(struct device *dev)
+{
+ struct ath11k_base *ab = dev_get_drvdata(dev);
+ int ret;
+
+ ret = ath11k_core_resume_early(ab);
+ if (ret)
+ ath11k_warn(ab, "failed to early resume core: %d\n", ret);
+
+ return ret;
+}
+
+static const struct dev_pm_ops __maybe_unused ath11k_pci_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ath11k_pci_pm_suspend,
+ ath11k_pci_pm_resume)
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(ath11k_pci_pm_suspend_late,
+ ath11k_pci_pm_resume_early)
+};
static struct pci_driver ath11k_pci_driver = {
.name = "ath11k_pci",
diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c
index 5006f81f779b..d4a243b64f6c 100644
--- a/drivers/net/wireless/ath/ath11k/qmi.c
+++ b/drivers/net/wireless/ath/ath11k/qmi.c
@@ -2877,7 +2877,7 @@ int ath11k_qmi_fwreset_from_cold_boot(struct ath11k_base *ab)
}
/* reset the firmware */
- ath11k_hif_power_down(ab);
+ ath11k_hif_power_down(ab, false);
ath11k_hif_power_up(ab);
ath11k_dbg(ab, ATH11K_DBG_QMI, "exit wait for cold boot done\n");
return 0;
--
2.25.1
On 2/20/2024 7:00 PM, Baochen Qiang wrote:
> ath11k fails to resume:
>
> ath11k_pci 0000:06:00.0: timeout while waiting for restart complete
>
> This happens because when calling mhi_sync_power_up() the MHI subsystem
> eventually calls device_add() from mhi_create_devices() but the device
> creation is deferred:
>
> mhi mhi0_IPCR: Driver qcom_mhi_qrtr force probe deferral
>
> The reason for deferring device creation is explained in dpm_prepare():
>
> /*
> * It is unsafe if probing of devices will happen during suspend or
> * hibernation and system behavior will be unpredictable in this case.
> * So, let's prohibit device's probing here and defer their probes
> * instead. The normal behavior will be restored in dpm_complete().
> */
>
> Because the device probe is deferred, the qcom_mhi_qrtr_probe() is not
> called and thus MHI channels are not prepared:
>
> So what this means that QRTR is not delivering messages and the QMI connection
> is not working between ath11k and the firmware, resulting a failure in firmware
> initialization.
>
> To fix this add new function mhi_power_down_no_destroy() which doesn't destroy
> the devices for channels during power down. This way we avoid probe defer issue
> and finally can get ath11k hibernation working with the following patches.
>
> Actually there is an RFC version of this change and it gets positive results
> from multiple users. Firstly Mani doesn't like this idea and insists that an
> MHI device should be destroyed when going to suspend/hibernation, see
>
> https://lore.kernel.org/mhi/[email protected]/
>
> Then Mani changed his mind after a further discussion with kernel PM guys,
> see
>
> https://lore.kernel.org/all/[email protected]/
>
> So we come up with the regular version and it is almost identical with that RFC
> version.
>
> Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
>
> Signed-off-by: Kalle Valo <[email protected]>
> Signed-off-by: Baochen Qiang <[email protected]>
Reviewed-by: Jeff Johnson <[email protected]>
On 2/20/2024 7:00 PM, Baochen Qiang wrote:
> MHI devices may not be destroyed during suspend/hibernation, so need
> to unprepare/prepare MHI channels throughout the transition.
>
> The RFC version adds new API to MHI stack with which an MHI controller
> driver can do unprepare/prepare directly by itself, see
>
> https://patchwork.kernel.org/project/linux-wireless/patch/[email protected]/
>
> Although it works well Mani pointed out that the design is not good
> because MHI channels are managed by MHI client driver thus should not
> be touched by others. See the discussion
>
> https://lore.kernel.org/mhi/[email protected]/
>
> This version changes to add suspend/resume callbacks to achieve the
> same purpose. The suspend callback is called in the late suspend stage,
> this means MHI channels are still alive at suspend stage, and that makes
> it possible for an MHI controller driver to communicate with others over
> those channels at suspend stage. While the resume callback is called in
> the early resume stage, for a similar reason.
>
> Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
>
> Signed-off-by: Baochen Qiang <[email protected]>
> ---
> net/qrtr/mhi.c | 29 +++++++++++++++++++++++++++++
> 1 file changed, 29 insertions(+)
>
> diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c
> index 9ced13c0627a..b54a6c2113e9 100644
> --- a/net/qrtr/mhi.c
> +++ b/net/qrtr/mhi.c
> @@ -118,6 +118,32 @@ static const struct mhi_device_id qcom_mhi_qrtr_id_table[] = {
> };
> MODULE_DEVICE_TABLE(mhi, qcom_mhi_qrtr_id_table);
>
> +static int qcom_mhi_qrtr_pm_suspend_late(struct device *dev)
Don't your new functions also need to be annotated as __maybe_unused?
> +{
> + struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
> +
> + mhi_unprepare_from_transfer(mhi_dev);
> +
> + return 0;
> +}
> +
> +static int qcom_mhi_qrtr_pm_resume_early(struct device *dev)
> +{
> + struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
> + int rc;
> +
> + rc = mhi_prepare_for_transfer_autoqueue(mhi_dev);
> + if (rc)
> + dev_err(dev, "failed to prepare for autoqueue transfer %d\n", rc);
> +
> + return rc;
> +}
> +
> +static const struct dev_pm_ops __maybe_unused qcom_mhi_qrtr_pm_ops = {
this does not need to be __maybe_unused, see below
> + SET_LATE_SYSTEM_SLEEP_PM_OPS(qcom_mhi_qrtr_pm_suspend_late,
> + qcom_mhi_qrtr_pm_resume_early)
> +};
> +
> static struct mhi_driver qcom_mhi_qrtr_driver = {
> .probe = qcom_mhi_qrtr_probe,
> .remove = qcom_mhi_qrtr_remove,
> @@ -126,6 +152,9 @@ static struct mhi_driver qcom_mhi_qrtr_driver = {
> .id_table = qcom_mhi_qrtr_id_table,
> .driver = {
> .name = "qcom_mhi_qrtr",
> +#ifdef CONFIG_PM
conditional compilation isn't necessary here since the 'pm' member is
always present
> + .pm = &qcom_mhi_qrtr_pm_ops,
> +#endif
> },
> };
>
On 2/20/2024 7:00 PM, Baochen Qiang wrote:
> Now that all infrastructure is in place and ath11k is fixed to handle all the
> corner cases, power down the ath11k firmware during suspend and power it back
> up during resume. This fixes the problem when using hibernation with ath11k PCI
> devices.
>
> For suspend, two conditions needs to be satisfied:
> 1. since MHI channel unprepare would be done in late suspend stage,
> ath11k needs to get all QMI-dependent things done before that stage.
> 2. and because unprepare MHI channels requires a working MHI stack,
> ath11k is not allowed to call mhi_power_down() until that finishes.
> So the original suspend callback is separated into two parts: the first part
> handles all QMI-dependent things in suspend callback; while the second part
> powers down MHI in suspend_late callback. This is valid because kernel calls
> ath11k's suspend callback before all suspend_late callbacks, making the first
> condition happy. And because MHI devices are children of ath11k device
> (ab->dev), kernel guarantees that ath11k's suspend_late callback is called
> after QRTR's suspend_late callback, this satisfies the second condition.
>
> Above analysis also applies to resume process. so the original resume
> callback is separated into two parts: the first part powers up MHI stack
> in resume_early callback, this guarantees MHI stack is working when
> QRTR tries to prepare MHI channels (kernel calls QRTR's resume_early callback
> after ath11k's resume_early callback, due to the child-father relationship);
> the second part waits for the completion of restart, which won't fail now
> since MHI channels are ready for use by QMI.
>
> Another notable change is in power down path, we tell mhi_power_down() to not
> to destroy MHI devices, making it possible for QRTR to help unprepare/prepare
> MHI channels, and finally get us rid of the probe-defer issue when resume.
>
> Also change related code due to interface changes.
>
> Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
>
> Tested-by: Takashi Iwai <[email protected]>
> Signed-off-by: Kalle Valo <[email protected]>
> Signed-off-by: Baochen Qiang <[email protected]>
Reviewed-by: Jeff Johnson <[email protected]>
On 2/22/2024 2:24 AM, Jeff Johnson wrote:
> On 2/20/2024 7:00 PM, Baochen Qiang wrote:
>> MHI devices may not be destroyed during suspend/hibernation, so need
>> to unprepare/prepare MHI channels throughout the transition.
>>
>> The RFC version adds new API to MHI stack with which an MHI controller
>> driver can do unprepare/prepare directly by itself, see
>>
>> https://patchwork.kernel.org/project/linux-wireless/patch/[email protected]/
>>
>> Although it works well Mani pointed out that the design is not good
>> because MHI channels are managed by MHI client driver thus should not
>> be touched by others. See the discussion
>>
>> https://lore.kernel.org/mhi/[email protected]/
>>
>> This version changes to add suspend/resume callbacks to achieve the
>> same purpose. The suspend callback is called in the late suspend stage,
>> this means MHI channels are still alive at suspend stage, and that makes
>> it possible for an MHI controller driver to communicate with others over
>> those channels at suspend stage. While the resume callback is called in
>> the early resume stage, for a similar reason.
>>
>> Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
>>
>> Signed-off-by: Baochen Qiang <[email protected]>
>> ---
>> net/qrtr/mhi.c | 29 +++++++++++++++++++++++++++++
>> 1 file changed, 29 insertions(+)
>>
>> diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c
>> index 9ced13c0627a..b54a6c2113e9 100644
>> --- a/net/qrtr/mhi.c
>> +++ b/net/qrtr/mhi.c
>> @@ -118,6 +118,32 @@ static const struct mhi_device_id qcom_mhi_qrtr_id_table[] = {
>> };
>> MODULE_DEVICE_TABLE(mhi, qcom_mhi_qrtr_id_table);
>>
>> +static int qcom_mhi_qrtr_pm_suspend_late(struct device *dev)
>
> Don't your new functions also need to be annotated as __maybe_unused?
OK, will add __maybe_unused in next version.
>
>> +{
>> + struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
>> +
>> + mhi_unprepare_from_transfer(mhi_dev);
>> +
>> + return 0;
>> +}
>> +
>> +static int qcom_mhi_qrtr_pm_resume_early(struct device *dev)
>> +{
>> + struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
>> + int rc;
>> +
>> + rc = mhi_prepare_for_transfer_autoqueue(mhi_dev);
>> + if (rc)
>> + dev_err(dev, "failed to prepare for autoqueue transfer %d\n", rc);
>> +
>> + return rc;
>> +}
>> +
>> +static const struct dev_pm_ops __maybe_unused qcom_mhi_qrtr_pm_ops = {
>
> this does not need to be __maybe_unused, see below
>
>> + SET_LATE_SYSTEM_SLEEP_PM_OPS(qcom_mhi_qrtr_pm_suspend_late,
>> + qcom_mhi_qrtr_pm_resume_early)
>> +};
>> +
>> static struct mhi_driver qcom_mhi_qrtr_driver = {
>> .probe = qcom_mhi_qrtr_probe,
>> .remove = qcom_mhi_qrtr_remove,
>> @@ -126,6 +152,9 @@ static struct mhi_driver qcom_mhi_qrtr_driver = {
>> .id_table = qcom_mhi_qrtr_id_table,
>> .driver = {
>> .name = "qcom_mhi_qrtr",
>> +#ifdef CONFIG_PM
>
> conditional compilation isn't necessary here since the 'pm' member is
> always present
Sure, will remove that in next version.
>
>> + .pm = &qcom_mhi_qrtr_pm_ops,
>> +#endif
>> },
>> };
>>
>
On 2/21/2024 11:00 AM, Baochen Qiang wrote:
> Currently in ath11k we keep the firmware running on the WLAN device when the
> network interface (wlan0) is down. The problem is that this will break
> hibernation, obviously the firmware can't be running after the whole system is
> powered off. To power down the ath11k firmware for suspend/hibernation some
> changes both in MHI subsystem and ath11k are needed.
>
> This patchset fixes a longstanding bug report about broken hibernation support:
>
> https://bugzilla.kernel.org/show_bug.cgi?id=214649
>
> There already is an RFC version which has been tested by multiple users with
> positive results:
>
> https://patchwork.kernel.org/project/linux-wireless/cover/[email protected]/
>
> Basically the RFC version adds two APIs to MHI stack: with the first one ath11k
> is able to keep MHI devices when going to suspend/hibernation, getting us rid of
> the probe deferral issue when resume back. while with the second one ath11k could
> manually prepare/unprepare MHI channels by itself, which is needed because QRTR
> doesn't probe those channels automatically in this case.
>
> Mani, the MHI maintainer, firstly doesn't like that version and insists that an
> MHI device should be destroyed when suspend/hibernation, according to his
> understanding on device driver model. See
>
> https://lore.kernel.org/mhi/[email protected]/
>
> After a long discussion Mani thought we might need a new PM callback with which
> ath11k is able to wait until kernel unblocks device probe and thus MHI channels
> get probed. So we came to the kernel PM list and there Mani realized that his
> understanding is not correct so he finally agrees to keep MHI device during
> suspend/hibernation. See
>
> https://lore.kernel.org/all/[email protected]/
>
> Mani also pointed out that an MHI controller driver (ath11k here) should not touch
> MHI channels directly because those channels are managed by the corresponding MHI
> client driver (QRTR here). To address this, we come up with this version.
>
> Compared with that RFC version, this version adds PM callbacks in QRTR module:
> suspend callback unprepares MHI channels during suspend and resume callback
> prepares those channels during resume. In this way ath11k doesn't need to do
> unprepare/prepare work by itself so those two APIs added in RFC version are
> removed now.
>
> The power down/up procedure requires a specific sequence in which PM callbacks
> of wiphy, ath11k and QRTR are called, this is achieved by exploiting the
> child-father relationship between their device struct, and also the PM framework
> which separates whole suspend/resume process into several stages. Details in
> patch [3/3].
>
> Depends on:
> wifi: ath11k: rearrange IRQ enable/disable in reset path
> wifi: ath11k: remove MHI LOOPBACK channels
> wifi: ath11k: do not dump SRNG statistics during resume
> wifi: ath11k: fix warning on DMA ring capabilities event
> wifi: ath11k: thermal: don't try to register multiple times
>
> Baochen Qiang (3):
> bus: mhi: host: add mhi_power_down_no_destroy()
> net: qrtr: support suspend/hibernation
> wifi: ath11k: support hibernation
>
> drivers/bus/mhi/host/internal.h | 4 +-
> drivers/bus/mhi/host/pm.c | 36 +++++++--
> drivers/net/wireless/ath/ath11k/ahb.c | 6 +-
> drivers/net/wireless/ath/ath11k/core.c | 105 +++++++++++++++++--------
> drivers/net/wireless/ath/ath11k/core.h | 6 +-
> drivers/net/wireless/ath/ath11k/hif.h | 14 +++-
> drivers/net/wireless/ath/ath11k/mhi.c | 12 ++-
> drivers/net/wireless/ath/ath11k/mhi.h | 5 +-
> drivers/net/wireless/ath/ath11k/pci.c | 44 +++++++++--
> drivers/net/wireless/ath/ath11k/qmi.c | 2 +-
> include/linux/mhi.h | 15 +++-
> net/qrtr/mhi.c | 29 +++++++
> 12 files changed, 218 insertions(+), 60 deletions(-)
>
>
> base-commit: 707e306f3573fa321ae197d77366578e4566cff5
> prerequisite-patch-id: d3f76112f9a55195c71459e0edf3a4ecf8af9181
> prerequisite-patch-id: 340d15aad1d3c1c3c93d9d996e1c96226c8bad8f
> prerequisite-patch-id: 98cdd37a68df4f651a065145e946d92c43be799e
> prerequisite-patch-id: a19ed13af0c894b7f9b22cedc99029991f48e47c
> prerequisite-patch-id: b5ad50fae6167c7c2b60cc063a8d460f7ddd4997
Hi Mani, may I have your comments on this series?
On Wed, Feb 21, 2024 at 11:00:24AM +0800, Baochen Qiang wrote:
> ath11k fails to resume:
>
> ath11k_pci 0000:06:00.0: timeout while waiting for restart complete
>
> This happens because when calling mhi_sync_power_up() the MHI subsystem
> eventually calls device_add() from mhi_create_devices() but the device
> creation is deferred:
>
> mhi mhi0_IPCR: Driver qcom_mhi_qrtr force probe deferral
>
> The reason for deferring device creation is explained in dpm_prepare():
>
> /*
> * It is unsafe if probing of devices will happen during suspend or
> * hibernation and system behavior will be unpredictable in this case.
> * So, let's prohibit device's probing here and defer their probes
> * instead. The normal behavior will be restored in dpm_complete().
> */
>
> Because the device probe is deferred, the qcom_mhi_qrtr_probe() is not
> called and thus MHI channels are not prepared:
>
> So what this means that QRTR is not delivering messages and the QMI connection
> is not working between ath11k and the firmware, resulting a failure in firmware
> initialization.
>
> To fix this add new function mhi_power_down_no_destroy() which doesn't destroy
> the devices for channels during power down. This way we avoid probe defer issue
> and finally can get ath11k hibernation working with the following patches.
>
Upto this line is the actual commit message and below should be moved to the
comments section of the patch.
> Actually there is an RFC version of this change and it gets positive results
> from multiple users. Firstly Mani doesn't like this idea and insists that an
> MHI device should be destroyed when going to suspend/hibernation, see
>
> https://lore.kernel.org/mhi/[email protected]/
>
> Then Mani changed his mind after a further discussion with kernel PM guys,
> see
>
> https://lore.kernel.org/all/[email protected]/
>
> So we come up with the regular version and it is almost identical with that RFC
> version.
>
> Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
>
> Signed-off-by: Kalle Valo <[email protected]>
> Signed-off-by: Baochen Qiang <[email protected]>
> ---
> drivers/bus/mhi/host/internal.h | 4 +++-
> drivers/bus/mhi/host/pm.c | 36 +++++++++++++++++++++++++++------
> include/linux/mhi.h | 15 +++++++++++++-
> 3 files changed, 47 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h
> index 091244cf17c6..8ce4aec56425 100644
> --- a/drivers/bus/mhi/host/internal.h
> +++ b/drivers/bus/mhi/host/internal.h
> @@ -86,6 +86,7 @@ enum dev_st_transition {
> DEV_ST_TRANSITION_FP,
> DEV_ST_TRANSITION_SYS_ERR,
> DEV_ST_TRANSITION_DISABLE,
> + DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE,
> DEV_ST_TRANSITION_MAX,
> };
>
> @@ -96,7 +97,8 @@ enum dev_st_transition {
> dev_st_trans(MISSION_MODE, "MISSION MODE") \
> dev_st_trans(FP, "FLASH PROGRAMMER") \
> dev_st_trans(SYS_ERR, "SYS ERROR") \
> - dev_st_trans_end(DISABLE, "DISABLE")
> + dev_st_trans(DISABLE, "DISABLE") \
> + dev_st_trans_end(DISABLE_DESTROY_DEVICE, "DISABLE (DESTROY DEVICE)")
>
> extern const char * const dev_state_tran_str[DEV_ST_TRANSITION_MAX];
> #define TO_DEV_STATE_TRANS_STR(state) (((state) >= DEV_ST_TRANSITION_MAX) ? \
> diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c
> index 8b40d3f01acc..5686d32f7458 100644
> --- a/drivers/bus/mhi/host/pm.c
> +++ b/drivers/bus/mhi/host/pm.c
> @@ -468,7 +468,8 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl)
> }
>
> /* Handle shutdown transitions */
> -static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl)
> +static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
> + bool destroy_device)
> {
> enum mhi_pm_state cur_state;
> struct mhi_event *mhi_event;
> @@ -530,8 +531,10 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl)
> dev_dbg(dev, "Waiting for all pending threads to complete\n");
> wake_up_all(&mhi_cntrl->state_event);
>
> - dev_dbg(dev, "Reset all active channels and remove MHI devices\n");
> - device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
I'd be nice to add a comment here on why destroying the device is optional.
> + if (destroy_device) {
> + dev_dbg(dev, "Reset all active channels and remove MHI devices\n");
> + device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
> + }
>
> mutex_lock(&mhi_cntrl->pm_mutex);
>
> @@ -821,7 +824,10 @@ void mhi_pm_st_worker(struct work_struct *work)
> mhi_pm_sys_error_transition(mhi_cntrl);
> break;
> case DEV_ST_TRANSITION_DISABLE:
> - mhi_pm_disable_transition(mhi_cntrl);
> + mhi_pm_disable_transition(mhi_cntrl, false);
> + break;
> + case DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE:
> + mhi_pm_disable_transition(mhi_cntrl, true);
> break;
> default:
> break;
> @@ -1175,7 +1181,8 @@ int mhi_async_power_up(struct mhi_controller *mhi_cntrl)
> }
> EXPORT_SYMBOL_GPL(mhi_async_power_up);
>
> -void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
> +static void __mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful,
> + bool destroy_device)
> {
> enum mhi_pm_state cur_state, transition_state;
> struct device *dev = &mhi_cntrl->mhi_dev->dev;
> @@ -1211,15 +1218,32 @@ void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
> write_unlock_irq(&mhi_cntrl->pm_lock);
> mutex_unlock(&mhi_cntrl->pm_mutex);
>
> - mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_DISABLE);
> + if (destroy_device)
> + mhi_queue_state_transition(mhi_cntrl,
> + DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE);
> + else
> + mhi_queue_state_transition(mhi_cntrl,
> + DEV_ST_TRANSITION_DISABLE);
>
> /* Wait for shutdown to complete */
> flush_work(&mhi_cntrl->st_worker);
>
> disable_irq(mhi_cntrl->irq[0]);
> }
> +
> +void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
> +{
> + __mhi_power_down(mhi_cntrl, graceful, true);
> +}
> EXPORT_SYMBOL_GPL(mhi_power_down);
>
> +void mhi_power_down_no_destroy(struct mhi_controller *mhi_cntrl,
How about "mhi_power_down_keep_dev"? Not the best of the API naming suggestion,
but it reflects what the API does.
> + bool graceful)
> +{
> + __mhi_power_down(mhi_cntrl, graceful, false);
> +}
> +EXPORT_SYMBOL_GPL(mhi_power_down_no_destroy);
> +
> int mhi_sync_power_up(struct mhi_controller *mhi_cntrl)
> {
> int ret = mhi_async_power_up(mhi_cntrl);
> diff --git a/include/linux/mhi.h b/include/linux/mhi.h
> index 474d32cb0520..39a6a944a52c 100644
> --- a/include/linux/mhi.h
> +++ b/include/linux/mhi.h
> @@ -647,12 +647,25 @@ int mhi_async_power_up(struct mhi_controller *mhi_cntrl);
> int mhi_sync_power_up(struct mhi_controller *mhi_cntrl);
>
> /**
> - * mhi_power_down - Start MHI power down sequence
> + * mhi_power_down - Start MHI power down sequence. See also
How about?
/**
* mhi_power_down - Power down the MHI device and also destroy the
* 'struct device' for the channels associated with it.
...
* See also mhi_power_down_keep_dev() which is a variant of
* this API that keeps the 'struct device' for channels (useful during
* suspend/hibernation).
*/
> + * mhi_power_down_no_destroy() which is a variant of this for
> + * suspend/hibernation.
> + *
> * @mhi_cntrl: MHI controller
> * @graceful: Link is still accessible, so do a graceful shutdown process
> */
> void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful);
>
> +/**
> + * mhi_power_down_no_destroy - Start MHI power down sequence but don't destroy
> + * struct devices for channels. This is a variant for mhi_power_down() and
> + * would be useful in suspend/hibernation.
> + *
/**
* mhi_power_down_keep_dev - Power down the MHI device but keep the
* 'struct device' for the channels
* associated with it.
...
* This is a variant of 'mhi_power_down' and useful in scenarios such as
* suspend/hibernation where destroying of the 'struct device' is not
* needed.
*/
- Mani
> + * @mhi_cntrl: MHI controller
> + * @graceful: Link is still accessible, so do a graceful shutdown process
> + */
> +void mhi_power_down_no_destroy(struct mhi_controller *mhi_cntrl, bool graceful);
> +
> /**
> * mhi_unprepare_after_power_down - Free any allocated memory after power down
> * @mhi_cntrl: MHI controller
> --
> 2.25.1
>
>
--
மணிவண்ணன் சதாசிவம்
On Wed, Feb 21, 2024 at 11:00:25AM +0800, Baochen Qiang wrote:
> MHI devices may not be destroyed during suspend/hibernation, so need
> to unprepare/prepare MHI channels throughout the transition.
>
Again, below content should go to the comment section, not in the commit
message.
Please add only relevant content in the commit message.
- Mani
> The RFC version adds new API to MHI stack with which an MHI controller
> driver can do unprepare/prepare directly by itself, see
>
> https://patchwork.kernel.org/project/linux-wireless/patch/[email protected]/
>
> Although it works well Mani pointed out that the design is not good
> because MHI channels are managed by MHI client driver thus should not
> be touched by others. See the discussion
>
> https://lore.kernel.org/mhi/[email protected]/
>
> This version changes to add suspend/resume callbacks to achieve the
> same purpose. The suspend callback is called in the late suspend stage,
> this means MHI channels are still alive at suspend stage, and that makes
> it possible for an MHI controller driver to communicate with others over
> those channels at suspend stage. While the resume callback is called in
> the early resume stage, for a similar reason.
>
> Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
>
> Signed-off-by: Baochen Qiang <[email protected]>
> ---
> net/qrtr/mhi.c | 29 +++++++++++++++++++++++++++++
> 1 file changed, 29 insertions(+)
>
> diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c
> index 9ced13c0627a..b54a6c2113e9 100644
> --- a/net/qrtr/mhi.c
> +++ b/net/qrtr/mhi.c
> @@ -118,6 +118,32 @@ static const struct mhi_device_id qcom_mhi_qrtr_id_table[] = {
> };
> MODULE_DEVICE_TABLE(mhi, qcom_mhi_qrtr_id_table);
>
> +static int qcom_mhi_qrtr_pm_suspend_late(struct device *dev)
> +{
> + struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
> +
> + mhi_unprepare_from_transfer(mhi_dev);
> +
> + return 0;
> +}
> +
> +static int qcom_mhi_qrtr_pm_resume_early(struct device *dev)
> +{
> + struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
> + int rc;
> +
> + rc = mhi_prepare_for_transfer_autoqueue(mhi_dev);
> + if (rc)
> + dev_err(dev, "failed to prepare for autoqueue transfer %d\n", rc);
> +
> + return rc;
> +}
> +
> +static const struct dev_pm_ops __maybe_unused qcom_mhi_qrtr_pm_ops = {
> + SET_LATE_SYSTEM_SLEEP_PM_OPS(qcom_mhi_qrtr_pm_suspend_late,
> + qcom_mhi_qrtr_pm_resume_early)
> +};
> +
> static struct mhi_driver qcom_mhi_qrtr_driver = {
> .probe = qcom_mhi_qrtr_probe,
> .remove = qcom_mhi_qrtr_remove,
> @@ -126,6 +152,9 @@ static struct mhi_driver qcom_mhi_qrtr_driver = {
> .id_table = qcom_mhi_qrtr_id_table,
> .driver = {
> .name = "qcom_mhi_qrtr",
> +#ifdef CONFIG_PM
> + .pm = &qcom_mhi_qrtr_pm_ops,
> +#endif
> },
> };
>
> --
> 2.25.1
>
>
--
மணிவண்ணன் சதாசிவம்
On 2/26/2024 8:15 PM, Manivannan Sadhasivam wrote:
> On Wed, Feb 21, 2024 at 11:00:24AM +0800, Baochen Qiang wrote:
>> ath11k fails to resume:
>>
>> ath11k_pci 0000:06:00.0: timeout while waiting for restart complete
>>
>> This happens because when calling mhi_sync_power_up() the MHI subsystem
>> eventually calls device_add() from mhi_create_devices() but the device
>> creation is deferred:
>>
>> mhi mhi0_IPCR: Driver qcom_mhi_qrtr force probe deferral
>>
>> The reason for deferring device creation is explained in dpm_prepare():
>>
>> /*
>> * It is unsafe if probing of devices will happen during suspend or
>> * hibernation and system behavior will be unpredictable in this case.
>> * So, let's prohibit device's probing here and defer their probes
>> * instead. The normal behavior will be restored in dpm_complete().
>> */
>>
>> Because the device probe is deferred, the qcom_mhi_qrtr_probe() is not
>> called and thus MHI channels are not prepared:
>>
>> So what this means that QRTR is not delivering messages and the QMI connection
>> is not working between ath11k and the firmware, resulting a failure in firmware
>> initialization.
>>
>> To fix this add new function mhi_power_down_no_destroy() which doesn't destroy
>> the devices for channels during power down. This way we avoid probe defer issue
>> and finally can get ath11k hibernation working with the following patches.
>>
>
> Upto this line is the actual commit message and below should be moved to the
> comments section of the patch.
I would like to remove below info since they are included in the cover
letter already. And even keeping them in the comment section won't make
them visible after patch got merged.
>
>> Actually there is an RFC version of this change and it gets positive results
>> from multiple users. Firstly Mani doesn't like this idea and insists that an
>> MHI device should be destroyed when going to suspend/hibernation, see
>>
>> https://lore.kernel.org/mhi/[email protected]/
>>
>> Then Mani changed his mind after a further discussion with kernel PM guys,
>> see
>>
>> https://lore.kernel.org/all/[email protected]/
>>
>> So we come up with the regular version and it is almost identical with that RFC
>> version.
>>
>> Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3.6510.30
>>
>> Signed-off-by: Kalle Valo <[email protected]>
>> Signed-off-by: Baochen Qiang <[email protected]>
>> ---
>> drivers/bus/mhi/host/internal.h | 4 +++-
>> drivers/bus/mhi/host/pm.c | 36 +++++++++++++++++++++++++++------
>> include/linux/mhi.h | 15 +++++++++++++-
>> 3 files changed, 47 insertions(+), 8 deletions(-)
>>
>> diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h
>> index 091244cf17c6..8ce4aec56425 100644
>> --- a/drivers/bus/mhi/host/internal.h
>> +++ b/drivers/bus/mhi/host/internal.h
>> @@ -86,6 +86,7 @@ enum dev_st_transition {
>> DEV_ST_TRANSITION_FP,
>> DEV_ST_TRANSITION_SYS_ERR,
>> DEV_ST_TRANSITION_DISABLE,
>> + DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE,
>> DEV_ST_TRANSITION_MAX,
>> };
>>
>> @@ -96,7 +97,8 @@ enum dev_st_transition {
>> dev_st_trans(MISSION_MODE, "MISSION MODE") \
>> dev_st_trans(FP, "FLASH PROGRAMMER") \
>> dev_st_trans(SYS_ERR, "SYS ERROR") \
>> - dev_st_trans_end(DISABLE, "DISABLE")
>> + dev_st_trans(DISABLE, "DISABLE") \
>> + dev_st_trans_end(DISABLE_DESTROY_DEVICE, "DISABLE (DESTROY DEVICE)")
>>
>> extern const char * const dev_state_tran_str[DEV_ST_TRANSITION_MAX];
>> #define TO_DEV_STATE_TRANS_STR(state) (((state) >= DEV_ST_TRANSITION_MAX) ? \
>> diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c
>> index 8b40d3f01acc..5686d32f7458 100644
>> --- a/drivers/bus/mhi/host/pm.c
>> +++ b/drivers/bus/mhi/host/pm.c
>> @@ -468,7 +468,8 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl)
>> }
>>
>> /* Handle shutdown transitions */
>> -static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl)
>> +static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
>> + bool destroy_device)
>> {
>> enum mhi_pm_state cur_state;
>> struct mhi_event *mhi_event;
>> @@ -530,8 +531,10 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl)
>> dev_dbg(dev, "Waiting for all pending threads to complete\n");
>> wake_up_all(&mhi_cntrl->state_event);
>>
>> - dev_dbg(dev, "Reset all active channels and remove MHI devices\n");
>> - device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
>
> I'd be nice to add a comment here on why destroying the device is optional.
>
>> + if (destroy_device) {
>> + dev_dbg(dev, "Reset all active channels and remove MHI devices\n");
>> + device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
>> + }
>>
>> mutex_lock(&mhi_cntrl->pm_mutex);
>>
>> @@ -821,7 +824,10 @@ void mhi_pm_st_worker(struct work_struct *work)
>> mhi_pm_sys_error_transition(mhi_cntrl);
>> break;
>> case DEV_ST_TRANSITION_DISABLE:
>> - mhi_pm_disable_transition(mhi_cntrl);
>> + mhi_pm_disable_transition(mhi_cntrl, false);
>> + break;
>> + case DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE:
>> + mhi_pm_disable_transition(mhi_cntrl, true);
>> break;
>> default:
>> break;
>> @@ -1175,7 +1181,8 @@ int mhi_async_power_up(struct mhi_controller *mhi_cntrl)
>> }
>> EXPORT_SYMBOL_GPL(mhi_async_power_up);
>>
>> -void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
>> +static void __mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful,
>> + bool destroy_device)
>> {
>> enum mhi_pm_state cur_state, transition_state;
>> struct device *dev = &mhi_cntrl->mhi_dev->dev;
>> @@ -1211,15 +1218,32 @@ void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
>> write_unlock_irq(&mhi_cntrl->pm_lock);
>> mutex_unlock(&mhi_cntrl->pm_mutex);
>>
>> - mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_DISABLE);
>> + if (destroy_device)
>> + mhi_queue_state_transition(mhi_cntrl,
>> + DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE);
>> + else
>> + mhi_queue_state_transition(mhi_cntrl,
>> + DEV_ST_TRANSITION_DISABLE);
>>
>> /* Wait for shutdown to complete */
>> flush_work(&mhi_cntrl->st_worker);
>>
>> disable_irq(mhi_cntrl->irq[0]);
>> }
>> +
>> +void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
>> +{
>> + __mhi_power_down(mhi_cntrl, graceful, true);
>> +}
>> EXPORT_SYMBOL_GPL(mhi_power_down);
>>
>> +void mhi_power_down_no_destroy(struct mhi_controller *mhi_cntrl,
>
> How about "mhi_power_down_keep_dev"? Not the best of the API naming suggestion,
> but it reflects what the API does.
>
>> + bool graceful)
>> +{
>> + __mhi_power_down(mhi_cntrl, graceful, false);
>> +}
>> +EXPORT_SYMBOL_GPL(mhi_power_down_no_destroy);
>> +
>> int mhi_sync_power_up(struct mhi_controller *mhi_cntrl)
>> {
>> int ret = mhi_async_power_up(mhi_cntrl);
>> diff --git a/include/linux/mhi.h b/include/linux/mhi.h
>> index 474d32cb0520..39a6a944a52c 100644
>> --- a/include/linux/mhi.h
>> +++ b/include/linux/mhi.h
>> @@ -647,12 +647,25 @@ int mhi_async_power_up(struct mhi_controller *mhi_cntrl);
>> int mhi_sync_power_up(struct mhi_controller *mhi_cntrl);
>>
>> /**
>> - * mhi_power_down - Start MHI power down sequence
>> + * mhi_power_down - Start MHI power down sequence. See also
>
> How about?
>
> /**
> * mhi_power_down - Power down the MHI device and also destroy the
> * 'struct device' for the channels associated with it.
>
> ...
>
> * See also mhi_power_down_keep_dev() which is a variant of
> * this API that keeps the 'struct device' for channels (useful during
> * suspend/hibernation).
> */
>
>> + * mhi_power_down_no_destroy() which is a variant of this for
>> + * suspend/hibernation.
>> + *
>> * @mhi_cntrl: MHI controller
>> * @graceful: Link is still accessible, so do a graceful shutdown process
>> */
>> void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful);
>>
>> +/**
>> + * mhi_power_down_no_destroy - Start MHI power down sequence but don't destroy
>> + * struct devices for channels. This is a variant for mhi_power_down() and
>> + * would be useful in suspend/hibernation.
>> + *
>
> /**
> * mhi_power_down_keep_dev - Power down the MHI device but keep the
> * 'struct device' for the channels
> * associated with it.
>
> ...
>
> * This is a variant of 'mhi_power_down' and useful in scenarios such as
> * suspend/hibernation where destroying of the 'struct device' is not
> * needed.
> */
>
> - Mani
>
>> + * @mhi_cntrl: MHI controller
>> + * @graceful: Link is still accessible, so do a graceful shutdown process
>> + */
>> +void mhi_power_down_no_destroy(struct mhi_controller *mhi_cntrl, bool graceful);
>> +
>> /**
>> * mhi_unprepare_after_power_down - Free any allocated memory after power down
>> * @mhi_cntrl: MHI controller
>> --
>> 2.25.1
>>
>>
>