From 41ded75b517eb123e8055a89c0af2354e9a3976e Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Thu, 11 Jun 2020 18:26:07 -0700 Subject: [PATCH 1/3] mhi: core: Add extra checks for RDDM event handling If the device enters RDDM when the host is in a fast/DRV suspended state, MHI host may end up skipping the check from BHI interrupt handling as a host resume may remain pending for a while. Once the host resume is triggered by the controller, MHI host can either walk the primary event ring to discover the RDDM EE event or check the execution environment post the DRV resume and notify the controller of the error condition as fallbacks to the original method of using the BHI interrupt. Change-Id: I93b678981a5ffd640c1dce15b90bf7bcd2e24971 Signed-off-by: Bhaumik Bhatt --- drivers/bus/mhi/core/mhi_main.c | 16 ++++++++++++++++ drivers/bus/mhi/core/mhi_pm.c | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/drivers/bus/mhi/core/mhi_main.c b/drivers/bus/mhi/core/mhi_main.c index cb0ba6b6e4f3..b2c98d9890df 100644 --- a/drivers/bus/mhi/core/mhi_main.c +++ b/drivers/bus/mhi/core/mhi_main.c @@ -1259,6 +1259,22 @@ int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl, st = MHI_ST_TRANSITION_MISSION_MODE; break; case MHI_EE_RDDM: + if (mhi_cntrl->ee == MHI_EE_RDDM || + mhi_cntrl->power_down) + break; + + MHI_ERR("RDDM event occurred!\n"); + write_lock_irq(&mhi_cntrl->pm_lock); + mhi_cntrl->ee = MHI_EE_RDDM; + write_unlock_irq(&mhi_cntrl->pm_lock); + + /* notify critical clients */ + mhi_control_error(mhi_cntrl); + + mhi_cntrl->status_cb(mhi_cntrl, + mhi_cntrl->priv_data, + MHI_CB_EE_RDDM); + wake_up_all(&mhi_cntrl->state_event); break; default: MHI_ERR("Unhandled EE event:%s\n", diff --git a/drivers/bus/mhi/core/mhi_pm.c b/drivers/bus/mhi/core/mhi_pm.c index a54903307b8d..08d33c9aa1e9 100644 --- a/drivers/bus/mhi/core/mhi_pm.c +++ b/drivers/bus/mhi/core/mhi_pm.c @@ -1488,6 +1488,26 @@ int mhi_pm_fast_resume(struct mhi_controller *mhi_cntrl, bool notify_client) return -EIO; } + if (mhi_cntrl->rddm_supported) { + if (mhi_get_exec_env(mhi_cntrl) == MHI_EE_RDDM && + !mhi_cntrl->power_down) { + mhi_cntrl->ee = MHI_EE_RDDM; + write_unlock_irq(&mhi_cntrl->pm_lock); + + MHI_ERR("RDDM event occurred!\n"); + + /* notify critical clients with early notifications */ + mhi_control_error(mhi_cntrl); + + mhi_cntrl->status_cb(mhi_cntrl, mhi_cntrl->priv_data, + MHI_CB_EE_RDDM); + wake_up_all(&mhi_cntrl->state_event); + + tasklet_enable(&mhi_cntrl->mhi_event->task); + goto exit_pm_fast_resume; + } + } + /* restore the states */ mhi_cntrl->pm_state = mhi_cntrl->saved_pm_state; mhi_cntrl->dev_state = mhi_cntrl->saved_dev_state; @@ -1531,6 +1551,7 @@ int mhi_pm_fast_resume(struct mhi_controller *mhi_cntrl, bool notify_client) /* schedules worker if any special purpose events need to be handled */ mhi_special_events_pending(mhi_cntrl); +exit_pm_fast_resume: MHI_LOG("Exit with pm_state:%s dev_state:%s\n", to_mhi_pm_state_str(mhi_cntrl->pm_state), TO_MHI_STATE_STR(mhi_cntrl->dev_state)); From b101ff3a368379370e66fc28aad40f7817fa6210 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Thu, 20 Feb 2020 15:03:38 -0800 Subject: [PATCH 2/3] mhi: core: block unbind from userspace till channel reset MHI clients can unbind driver from userspace while a channel is active with ongoing transfers. As we can only do host side clean-up of the channel without sending a reset channel command to the device, a kernel panic could occur due to incoming packets if we proceed. Wait until the client unprepares the channel or device goes to an error state. Change-Id: I400cbf799d96fb7bc05df4c568593e12e9dc41f1 Signed-off-by: Bhaumik Bhatt --- drivers/bus/mhi/core/mhi_init.c | 47 +++++++++++++++++++++++++++------ drivers/bus/mhi/core/mhi_main.c | 14 +++++++--- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/drivers/bus/mhi/core/mhi_init.c b/drivers/bus/mhi/core/mhi_init.c index 6f07329d19a5..8738f760cf25 100644 --- a/drivers/bus/mhi/core/mhi_init.c +++ b/drivers/bus/mhi/core/mhi_init.c @@ -1967,7 +1967,8 @@ static int mhi_driver_remove(struct device *dev) MHI_CH_STATE_DISABLED, MHI_CH_STATE_DISABLED }; - int dir; + int dir, ret; + bool interrupted = false; /* control device has no work to do */ if (mhi_dev->dev_type == MHI_CONTROLLER_TYPE) @@ -1975,11 +1976,11 @@ static int mhi_driver_remove(struct device *dev) MHI_LOG("Removing device for chan:%s\n", mhi_dev->chan_name); - /* reset both channels */ + /* move both channels to suspended state and disallow processing */ for (dir = 0; dir < 2; dir++) { mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan; - if (!mhi_chan) + if (!mhi_chan || mhi_chan->offload_ch) continue; /* wake all threads waiting for completion */ @@ -1988,15 +1989,45 @@ static int mhi_driver_remove(struct device *dev) complete_all(&mhi_chan->completion); write_unlock_irq(&mhi_chan->lock); - /* move channel state to disable, no more processing */ mutex_lock(&mhi_chan->mutex); + write_lock_irq(&mhi_chan->lock); + if (mhi_chan->ch_state != MHI_CH_STATE_DISABLED) { + ch_state[dir] = mhi_chan->ch_state; + mhi_chan->ch_state = MHI_CH_STATE_SUSPENDED; + } + write_unlock_irq(&mhi_chan->lock); + mutex_unlock(&mhi_chan->mutex); + } + + /* wait for each channel to close and reset both channels */ + for (dir = 0; dir < 2; dir++) { + mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan; + + if (!mhi_chan || mhi_chan->offload_ch) + continue; + + /* unbind request from userspace, wait for channel reset */ + if (!(mhi_cntrl->power_down || + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) && + ch_state[dir] != MHI_CH_STATE_DISABLED && !interrupted) { + MHI_ERR("Channel %s busy, wait for it to be reset\n", + mhi_dev->chan_name); + ret = wait_event_interruptible(mhi_cntrl->state_event, + mhi_chan->ch_state == MHI_CH_STATE_DISABLED || + MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)); + if (unlikely(ret)) + interrupted = true; + } + + /* update channel state as an error can exit above wait */ + mutex_lock(&mhi_chan->mutex); + write_lock_irq(&mhi_chan->lock); ch_state[dir] = mhi_chan->ch_state; - mhi_chan->ch_state = MHI_CH_STATE_SUSPENDED; write_unlock_irq(&mhi_chan->lock); - /* reset the channel */ - if (!mhi_chan->offload_ch) + /* reset channel if it was left enabled */ + if (ch_state[dir] != MHI_CH_STATE_DISABLED) mhi_reset_chan(mhi_cntrl, mhi_chan); mutex_unlock(&mhi_chan->mutex); @@ -2014,7 +2045,7 @@ static int mhi_driver_remove(struct device *dev) mutex_lock(&mhi_chan->mutex); - if (ch_state[dir] == MHI_CH_STATE_ENABLED && + if (ch_state[dir] != MHI_CH_STATE_DISABLED && !mhi_chan->offload_ch) mhi_deinit_chan_ctxt(mhi_cntrl, mhi_chan); diff --git a/drivers/bus/mhi/core/mhi_main.c b/drivers/bus/mhi/core/mhi_main.c index b2c98d9890df..bdf9305c3ea2 100644 --- a/drivers/bus/mhi/core/mhi_main.c +++ b/drivers/bus/mhi/core/mhi_main.c @@ -2053,19 +2053,22 @@ static void __mhi_unprepare_channel(struct mhi_controller *mhi_cntrl, { int ret; bool in_mission_mode = false; + bool notify = false; MHI_LOG("Entered: unprepare channel:%d\n", mhi_chan->chan); /* no more processing events for this channel */ mutex_lock(&mhi_chan->mutex); write_lock_irq(&mhi_chan->lock); - if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED) { + if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED && + mhi_chan->ch_state != MHI_CH_STATE_SUSPENDED) { MHI_LOG("chan:%d is already disabled\n", mhi_chan->chan); write_unlock_irq(&mhi_chan->lock); mutex_unlock(&mhi_chan->mutex); return; } - + if (mhi_chan->ch_state == MHI_CH_STATE_SUSPENDED) + notify = true; mhi_chan->ch_state = MHI_CH_STATE_DISABLED; write_unlock_irq(&mhi_chan->lock); @@ -2106,6 +2109,10 @@ error_invalid_state: if (!mhi_chan->offload_ch) { mhi_reset_chan(mhi_cntrl, mhi_chan); mhi_deinit_chan_ctxt(mhi_cntrl, mhi_chan); + + /* notify waiters to proceed with unbinding channel */ + if (notify) + wake_up_all(&mhi_cntrl->state_event); } MHI_LOG("chan:%d successfully resetted\n", mhi_chan->chan); mutex_unlock(&mhi_chan->mutex); @@ -2376,7 +2383,8 @@ static int mhi_update_channel_state(struct mhi_controller *mhi_cntrl, mhi_chan->chan, cmd == MHI_CMD_START_CHAN ? "START" : "STOP"); /* if channel is not active state state do not allow to state change */ - if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED) { + if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED && + mhi_chan->ch_state != MHI_CH_STATE_SUSPENDED) { ret = -EINVAL; MHI_LOG("channel:%d is not in active state, ch_state%d\n", mhi_chan->chan, mhi_chan->ch_state); From 5e803bd9f5d5dce75bb482f6741ceac9d89992cf Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 11 May 2020 14:04:42 -0700 Subject: [PATCH 3/3] mhi: cntrl: qcom: Add support for fatal error processing MHI controller needs to handle the fatal error callback issued by the bus in case reset separation needs to be supported. This state will occur as a SYS ERROR state with the device being in PBL. Perform a full power cycle and issue an MHI RESET during power down. Change-Id: I467943924302ba773c8f6d0c4ad09b51faaeb490 Signed-off-by: Bhaumik Bhatt --- drivers/bus/mhi/controllers/mhi_qcom.c | 21 +++++++++++++++++++++ drivers/bus/mhi/controllers/mhi_qcom.h | 1 + 2 files changed, 22 insertions(+) diff --git a/drivers/bus/mhi/controllers/mhi_qcom.c b/drivers/bus/mhi/controllers/mhi_qcom.c index cd252739c3a2..e57bdd0e377c 100644 --- a/drivers/bus/mhi/controllers/mhi_qcom.c +++ b/drivers/bus/mhi/controllers/mhi_qcom.c @@ -525,6 +525,22 @@ static int mhi_qcom_power_up(struct mhi_controller *mhi_cntrl) return ret; } +static void mhi_qcom_fatal_worker(struct work_struct *work) +{ + struct mhi_dev *mhi_dev = container_of(work, struct mhi_dev, + fatal_worker); + struct device *dev = &mhi_dev->pci_dev->dev; + struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); + int ret; + + mhi_power_down(mhi_cntrl, true); + + ret = mhi_qcom_power_up(mhi_cntrl); + if (ret) + MHI_ERR("Power up failure after SYS ERROR in PBL, ret:%d\n", + ret); +} + static int mhi_runtime_get(struct mhi_controller *mhi_cntrl, void *priv) { struct mhi_dev *mhi_dev = priv; @@ -569,6 +585,10 @@ static void mhi_status_cb(struct mhi_controller *mhi_cntrl, pm_runtime_put(dev); mhi_arch_mission_mode_enter(mhi_cntrl); break; + case MHI_CB_FATAL_ERROR: + MHI_CNTRL_ERR("Perform power cycle due to SYS ERROR in PBL\n"); + schedule_work(&mhi_dev->fatal_worker); + break; default: MHI_CNTRL_LOG("Unhandled cb:0x%x\n", reason); } @@ -782,6 +802,7 @@ static struct mhi_controller *mhi_register_controller(struct pci_dev *pci_dev) goto error_register; INIT_WORK(&mhi_cntrl->reg_write_work, mhi_reg_write_work); + INIT_WORK(&mhi_dev->fatal_worker, mhi_qcom_fatal_worker); mhi_cntrl->reg_write_q = kcalloc(REG_WRITE_QUEUE_LEN, sizeof(*mhi_cntrl->reg_write_q), diff --git a/drivers/bus/mhi/controllers/mhi_qcom.h b/drivers/bus/mhi/controllers/mhi_qcom.h index 3b5e74b912c4..2fc3ab727b21 100644 --- a/drivers/bus/mhi/controllers/mhi_qcom.h +++ b/drivers/bus/mhi/controllers/mhi_qcom.h @@ -63,6 +63,7 @@ struct mhi_dev { dma_addr_t iova_start; dma_addr_t iova_stop; enum mhi_suspend_mode suspend_mode; + struct work_struct fatal_worker; /* hardware info */ u32 serial_num;