From 90f5b1c30a0a376efc5a35d816618c48fea0f9b9 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Fri, 3 Jan 2025 17:04:10 +0530 Subject: [PATCH] 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);