From b0eaa6a4e4bb039700dd752bf8779121601dda80 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Thu, 7 Nov 2024 11:31:17 +0530 Subject: [PATCH 1/3] msm: ep_pcie: Wake host in D3cold handling if wake is pending In below sequence where device requested for inband pme but host still proceed with #PERST assertion the ep_pcie_core_wakeup_host_internal is called to toggle wake gpio. Event Sequence: - Received a wakeup_host event in D3hot. - wakeup host internal api called -> inband pme issued - host_wake_pending set to 1 - host is in process of issuing a perst assert before the inband pme is processed - disable endpoint is called -> checks for host wake pending and and calls ep_pcie_core_wakeup_host_internal. - ep_pcie_core_wakeup_host_internal will return without doing a wakeup because of host wake pending check in it. Set the host_wake_pending flag to 0 before calling to make sure ep_pcie_core_wakeup_host_internal is executed to toggle WAKE. Change-Id: I533b7ee58ea941d9a865fc560677aa9a8daa431c Signed-off-by: Sumit Kumar --- drivers/platform/msm/ep_pcie/ep_pcie_core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/platform/msm/ep_pcie/ep_pcie_core.c b/drivers/platform/msm/ep_pcie/ep_pcie_core.c index c8e23ac53d2f..cef10b457073 100644 --- a/drivers/platform/msm/ep_pcie/ep_pcie_core.c +++ b/drivers/platform/msm/ep_pcie/ep_pcie_core.c @@ -2179,6 +2179,11 @@ int ep_pcie_core_disable_endpoint(void) if (atomic_read(&dev->host_wake_pending)) { EP_PCIE_DBG(dev, "PCIe V%d: wake pending, init wakeup\n", dev->rev); + /* + * Clear the wake pending otherwise ep_pcie_core_wakeup_host_internal + * will return without WAKE toggle + */ + atomic_set(&dev->host_wake_pending, 0); ep_pcie_core_wakeup_host_internal(EP_PCIE_EVENT_PM_D3_COLD); } From a92f0801342cc994313500c29ffa62c0d4b49c2c Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Mon, 7 Oct 2024 11:41:50 +0530 Subject: [PATCH 2/3] msm: mhi_dev: Handle host wakeup in M3/D0 When a MHI WAKE request (that is request to bring MHI from M3 to M0) is received while device is in D0, this request is being dropped as there is no way to notify host about this event. This is leading to failure at client drivers as they unable to wakeup mhi and perform write operation. Fix the issue by waiting for D3hot in MHI before sending the wake request: - If M0 is received while waiting, exit the function. - If D3hot/D3cold is received, send the wake request. - Else return failure. Increase the timeout value of mhi_dev_write_channel() from 2s to 2.5s to accommodate the waiting time for D state transition to D3hot/D3cold. Change-Id: Idd58bb664d31bc1e5615119284c6cba924fe17b6 Signed-off-by: Sumit Kumar --- drivers/platform/msm/mhi_dev/mhi.c | 2 +- drivers/platform/msm/mhi_dev/mhi_sm.c | 34 ++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/drivers/platform/msm/mhi_dev/mhi.c b/drivers/platform/msm/mhi_dev/mhi.c index d7d9ecad8d73..141ce1157e9c 100644 --- a/drivers/platform/msm/mhi_dev/mhi.c +++ b/drivers/platform/msm/mhi_dev/mhi.c @@ -39,7 +39,7 @@ /* Wait time on the device for Host to set BHI_INTVEC */ #define MHI_BHI_INTVEC_MAX_CNT 200 #define MHI_BHI_INTVEC_WAIT_MS 50 -#define MHI_WAKEUP_TIMEOUT_CNT 20 +#define MHI_WAKEUP_TIMEOUT_CNT 25 #define MHI_MASK_CH_EV_LEN 32 #define MHI_RING_CMD_ID 0 #define MHI_RING_PRIMARY_EVT_ID 1 diff --git a/drivers/platform/msm/mhi_dev/mhi_sm.c b/drivers/platform/msm/mhi_dev/mhi_sm.c index 89190bfeadfc..ba8c9a19136e 100644 --- a/drivers/platform/msm/mhi_dev/mhi_sm.c +++ b/drivers/platform/msm/mhi_dev/mhi_sm.c @@ -12,6 +12,7 @@ #include "mhi_hwio.h" #include "mhi_sm.h" #include +#include #define MHI_SM_DBG(fmt, args...) \ mhi_log(MHI_MSG_DBG, fmt, ##args) @@ -28,6 +29,8 @@ #define PCIE_EP_TIMER_US 500000000 #define MHI_IPA_DISABLE_DELAY_MS 10 #define MHI_IPA_DISABLE_COUNTER 20 +/* Maximum wait time for D state transitions to D3hot */ +#define M3_DO_WAKEUP_TIMEOUT_MS 2500 static inline const char *mhi_sm_dev_event_str(enum mhi_dev_event state) @@ -734,7 +737,7 @@ exit: * mhi_sm_wakeup_host() - wakeup MHI-host *@event: MHI state chenge event * - * Sends wekup event to MHI-host via EP-PCIe, in case MHI is in M3 state. + * Sends wakeup event to MHI-host via EP-PCIe, in case MHI is in M3 state. * * Return: 0:success * negative: failure @@ -742,6 +745,7 @@ exit: static int mhi_sm_wakeup_host(enum mhi_dev_event event) { int res = 0; + int timeout = 0; enum ep_pcie_event pcie_event; MHI_SM_FUNC_ENTRY(); @@ -753,9 +757,33 @@ static int mhi_sm_wakeup_host(enum mhi_dev_event event) MHI_SM_ERR("Failed switching to M0 state\n"); } else if (mhi_sm_ctx->mhi_state == MHI_DEV_M3_STATE) { /* - * Check and send D3_HOT to enable waking up the host - * using inband PME. + * Handle host wakeup in M3 + D0 states. + * + * When a MHI WAKE request is received while device is in D0, + * wait for D3 and wakeup the host using inband PME. + * If the MHI state changes to M0 while waiting for D3, + * exit, since both MHI and the device are in active state. */ + if (mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D0_STATE) { + timeout = ktime_add_ms(ktime_get(), M3_DO_WAKEUP_TIMEOUT_MS); + while (1) { + /* Received M0 */ + if (mhi_sm_ctx->mhi_state == MHI_DEV_M0_STATE) + goto exit; + /* Received D3 state */ + if (mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D3_HOT_STATE || + mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D3_COLD_STATE) + goto wakeup_host; + if (ktime_after(ktime_get(), timeout)) { + MHI_SM_ERR(mhi->vf_id, + "M3, D0 wakeup host is not supported %d\n", res); + goto exit; + } + usleep_range(1000, 2000); + } + } +wakeup_host: + /* Received D3hot or D3cold, send the wakeup request */ if (mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D3_HOT_STATE) pcie_event = EP_PCIE_EVENT_PM_D3_HOT; else From 90f5b1c30a0a376efc5a35d816618c48fea0f9b9 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Fri, 3 Jan 2025 17:04:10 +0530 Subject: [PATCH 3/3] msm: mhi_dev: Workqueue to handle host wakeup in M3+D0 After the change ("msm: mhi_dev: Handle host wakeup in M3/D0"), if a wakeup host request is received during M3+D0, wait for D3 hot/cold before sending the host wakeup request inside a mutex lock. Regression: In mhi_sm_dev_event_manager, the mhi_sm_ctx->mhi_state_lock mutex is held while calling mhi_sm_wakeup_host. This means waiting for mhi_sm_ctx->d_state to transition to MHI_SM_EP_PCIE_D3_HOT_STATE or MHI_SM_EP_PCIE_D3_COLD_STATE with the lock held. This prevents mhi_sm_pcie_event_manager from processing the D3 hot/cold event because it also waits for the same lock. Create a separate workqueue to wait for D3 hot/cold before waking up the host if a wakeup request is received in M3+D0. Change-Id: I1d3eff63b1968f5ded0d0c84a0fa71e893d74d45 Signed-off-by: Sumit Kumar --- drivers/platform/msm/mhi_dev/mhi_sm.c | 118 +++++++++++++++++--------- drivers/platform/msm/mhi_dev/mhi_sm.h | 1 - 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/drivers/platform/msm/mhi_dev/mhi_sm.c b/drivers/platform/msm/mhi_dev/mhi_sm.c index ba8c9a19136e..e8d9300c7812 100644 --- a/drivers/platform/msm/mhi_dev/mhi_sm.c +++ b/drivers/platform/msm/mhi_dev/mhi_sm.c @@ -32,6 +32,8 @@ /* Maximum wait time for D state transitions to D3hot */ #define M3_DO_WAKEUP_TIMEOUT_MS 2500 +static void wait_d3_and_wakeup(struct work_struct *work); +static int mhi_dev_sm_get_mhi_pcie_states(uint32_t *mstate, uint32_t *dstate); static inline const char *mhi_sm_dev_event_str(enum mhi_dev_event state) { @@ -238,6 +240,8 @@ struct mhi_sm_dev { struct mutex mhi_state_lock; bool syserr_occurred; struct workqueue_struct *mhi_sm_wq; + struct workqueue_struct *mhi_wake_wq; + struct work_struct mhi_wake_work; atomic_t pending_device_events; atomic_t pending_pcie_events; struct mhi_sm_stats stats; @@ -742,10 +746,9 @@ exit: * Return: 0:success * negative: failure */ -static int mhi_sm_wakeup_host(enum mhi_dev_event event) +static int mhi_sm_wakeup_host(void) { int res = 0; - int timeout = 0; enum ep_pcie_event pcie_event; MHI_SM_FUNC_ENTRY(); @@ -757,33 +760,10 @@ static int mhi_sm_wakeup_host(enum mhi_dev_event event) MHI_SM_ERR("Failed switching to M0 state\n"); } else if (mhi_sm_ctx->mhi_state == MHI_DEV_M3_STATE) { /* - * Handle host wakeup in M3 + D0 states. - * - * When a MHI WAKE request is received while device is in D0, - * wait for D3 and wakeup the host using inband PME. - * If the MHI state changes to M0 while waiting for D3, - * exit, since both MHI and the device are in active state. + * Check and send D3_HOT to enable waking up the host + * using inband PME if the host is in D3_HOT state, otherwise + * send D3_COLD to wake up the host. */ - if (mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D0_STATE) { - timeout = ktime_add_ms(ktime_get(), M3_DO_WAKEUP_TIMEOUT_MS); - while (1) { - /* Received M0 */ - if (mhi_sm_ctx->mhi_state == MHI_DEV_M0_STATE) - goto exit; - /* Received D3 state */ - if (mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D3_HOT_STATE || - mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D3_COLD_STATE) - goto wakeup_host; - if (ktime_after(ktime_get(), timeout)) { - MHI_SM_ERR(mhi->vf_id, - "M3, D0 wakeup host is not supported %d\n", res); - goto exit; - } - usleep_range(1000, 2000); - } - } -wakeup_host: - /* Received D3hot or D3cold, send the wakeup request */ if (mhi_sm_ctx->d_state == MHI_SM_EP_PCIE_D3_HOT_STATE) pcie_event = EP_PCIE_EVENT_PM_D3_HOT; else @@ -934,9 +914,7 @@ static void mhi_sm_dev_event_manager(struct work_struct *work) break; case MHI_DEV_EVENT_HW_ACC_WAKEUP: case MHI_DEV_EVENT_CORE_WAKEUP: - res = mhi_sm_wakeup_host(chg_event->event); - if (res) - MHI_SM_ERR("Failed to wakeup MHI host\n"); + queue_work(mhi_sm_ctx->mhi_wake_wq, &mhi_sm_ctx->mhi_wake_work); break; case MHI_DEV_EVENT_CTRL_TRIG: case MHI_DEV_EVENT_M1_STATE: @@ -1147,9 +1125,19 @@ int mhi_dev_sm_init(struct mhi_dev *mhi_dev) if (!mhi_sm_ctx->mhi_sm_wq) { MHI_SM_ERR("Failed to create singlethread_workqueue: sm_wq\n"); res = -ENOMEM; - goto fail_init_wq; + goto fail_init_sm_wq; } + if (!mhi_sm_ctx->mhi_wake_wq) + mhi_sm_ctx->mhi_wake_wq = alloc_workqueue( + "mhi_wake_wq", WQ_HIGHPRI | WQ_UNBOUND, 1); + if (!mhi_sm_ctx->mhi_wake_wq) { + MHI_SM_ERR("Failed to create singlethread_workqueue: wake_wq\n"); + res = -ENOMEM; + goto fail_init_wake_wq; + } + INIT_WORK(&mhi_sm_ctx->mhi_wake_work, wait_d3_and_wakeup); + mutex_init(&mhi_sm_ctx->mhi_state_lock); mhi_sm_ctx->mhi_dev = mhi_dev; mhi_sm_ctx->mhi_state = MHI_DEV_RESET_STATE; @@ -1162,7 +1150,10 @@ int mhi_dev_sm_init(struct mhi_dev *mhi_dev) MHI_SM_FUNC_EXIT(); return 0; -fail_init_wq: +fail_init_wake_wq: + flush_workqueue(mhi_sm_ctx->mhi_sm_wq); + destroy_workqueue(mhi_sm_ctx->mhi_sm_wq); +fail_init_sm_wq: mhi_sm_ctx = NULL; mhi_sm_debugfs_destroy(); return res; @@ -1190,20 +1181,20 @@ int mhi_dev_sm_exit(struct mhi_dev *mhi_dev) EXPORT_SYMBOL(mhi_dev_sm_exit); /** - * mhi_dev_sm_get_mhi_state() -Get current MHI state. + * mhi_dev_sm_get_mhi_pcie_states() -Get current MHI and Pcie states. * @state: return param * - * Returns the current MHI state of the state machine. + * Returns the current MHI and PCIe states of the state machine. * * Return: 0 success * -EINVAL: invalid param * -EFAULT: state machine isn't initialized */ -int mhi_dev_sm_get_mhi_state(enum mhi_dev_state *state) +static int mhi_dev_sm_get_mhi_pcie_states(uint32_t *mstate, uint32_t *dstate) { MHI_SM_FUNC_ENTRY(); - if (!state) { + if (!mstate || !dstate) { MHI_SM_ERR("Fail: Null argument\n"); return -EINVAL; } @@ -1211,15 +1202,60 @@ int mhi_dev_sm_get_mhi_state(enum mhi_dev_state *state) MHI_SM_ERR("Fail: MHI SM is not initialized\n"); return -EFAULT; } - *state = mhi_sm_ctx->mhi_state; + mutex_lock(&mhi_sm_ctx->mhi_state_lock); + *mstate = mhi_sm_ctx->mhi_state; + *dstate = mhi_sm_ctx->d_state; + mutex_unlock(&mhi_sm_ctx->mhi_state_lock); MHI_SM_DBG("state machine states are: %s and %s\n", - mhi_sm_mstate_str(*state), - mhi_sm_dstate_str(mhi_sm_ctx->d_state)); + mhi_sm_mstate_str(*mstate), + mhi_sm_dstate_str(*dstate)); MHI_SM_FUNC_EXIT(); return 0; } -EXPORT_SYMBOL(mhi_dev_sm_get_mhi_state); + +static void wait_d3_and_wakeup(struct work_struct *work) +{ + struct mhi_sm_dev *mhi_sm_ctx = container_of(work, struct mhi_sm_dev, mhi_wake_work); + enum mhi_dev_state mstate; + enum mhi_sm_ep_pcie_state dstate; + ktime_t timeout = 0; + + if (mhi_dev_sm_get_mhi_pcie_states(&mstate, &dstate)) { + MHI_SM_ERR("Unable to read states\n"); + return; + } + /* + * Handle host wakeup in M3 + D0 states. + * When a MHI WAKE request is received while device is in D0, + * wait for D3 and wakeup the host using inband PME. + * If the MHI state changes to M0 while waiting for D3, + * exit, since both MHI and the device are in active state + */ + if (dstate == MHI_SM_EP_PCIE_D0_STATE) { + timeout = ktime_add_ms(ktime_get(), M3_DO_WAKEUP_TIMEOUT_MS); + while (1) { + mhi_dev_sm_get_mhi_pcie_states(&mstate, &dstate); + if (mstate == MHI_DEV_M0_STATE) { + MHI_SM_DBG("M0 state received\n"); + return; + } + if (dstate == MHI_SM_EP_PCIE_D3_HOT_STATE || + dstate == MHI_SM_EP_PCIE_D3_COLD_STATE) { + MHI_SM_DBG("D3 state received\n"); + goto send_host_wakeup; + } + if (ktime_after(ktime_get(), timeout)) { + MHI_SM_ERR("Neither received D3 nor M0 in stipulated time\n"); + return; + } + usleep_range(1000, 2000); + } + } +send_host_wakeup: + if (dstate == MHI_SM_EP_PCIE_D3_HOT_STATE || dstate == MHI_SM_EP_PCIE_D3_COLD_STATE) + mhi_sm_wakeup_host(); +} /** * mhi_dev_sm_set_ready() -Set MHI state to ready. diff --git a/drivers/platform/msm/mhi_dev/mhi_sm.h b/drivers/platform/msm/mhi_dev/mhi_sm.h index 80ed0086472f..24e6daf46777 100644 --- a/drivers/platform/msm/mhi_dev/mhi_sm.h +++ b/drivers/platform/msm/mhi_dev/mhi_sm.h @@ -42,7 +42,6 @@ int mhi_dev_sm_init(struct mhi_dev *dev); int mhi_dev_sm_exit(struct mhi_dev *dev); int mhi_dev_sm_set_ready(void); int mhi_dev_notify_sm_event(enum mhi_dev_event event); -int mhi_dev_sm_get_mhi_state(enum mhi_dev_state *state); int mhi_dev_sm_syserr(void); void mhi_dev_sm_pcie_handler(struct ep_pcie_notify *notify);