mhi: core: Add support to offload MHI register write to worker thread

When PCIe endpoint enters L1SS sleep and mhi client on Host tries to
queue a transfer request endpoint takes more than 6ms to come back to
L0 state. This can cause CPU stall if MHI register write is followed
by a write memory barrier. This can cause other tasks to get blocked.
In order to prevent this add register write offload API
mhi_write_reg_offload() which would queue the write and handle write
request from worker thread in AMSS execution environment.

Change-Id: I0a8b06d2ba96d9beb32fa31564aa0cbb26c885e6
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
This commit is contained in:
Hemant Kumar 2019-09-24 19:24:27 -07:00
commit d167ac3bf6
4 changed files with 83 additions and 2 deletions

View file

@ -819,6 +819,8 @@ void mhi_destroy_timesync(struct mhi_controller *mhi_cntrl);
int mhi_create_sysfs(struct mhi_controller *mhi_cntrl);
void mhi_destroy_sysfs(struct mhi_controller *mhi_cntrl);
int mhi_early_notify_device(struct device *dev, void *data);
void mhi_write_reg_offload(struct mhi_controller *mhi_cntrl,
void __iomem *base, u32 offset, u32 val);
/* timesync log support */
static inline void mhi_timesync_log(struct mhi_controller *mhi_cntrl)
@ -912,6 +914,8 @@ void mhi_rddm_prepare(struct mhi_controller *mhi_cntrl,
struct image_info *img_info);
int mhi_prepare_channel(struct mhi_controller *mhi_cntrl,
struct mhi_chan *mhi_chan);
void mhi_reset_reg_write_q(struct mhi_controller *mhi_cntrl);
void mhi_force_reg_write(struct mhi_controller *mhi_cntrl);
/* isr handlers */
irqreturn_t mhi_msi_handlr(int irq_number, void *dev);

View file

@ -89,6 +89,45 @@ int mhi_get_capability_offset(struct mhi_controller *mhi_cntrl,
return -ENXIO;
}
void mhi_force_reg_write(struct mhi_controller *mhi_cntrl)
{
if (mhi_cntrl->offload_wq)
flush_work(&mhi_cntrl->reg_write_work);
}
void mhi_reset_reg_write_q(struct mhi_controller *mhi_cntrl)
{
cancel_work_sync(&mhi_cntrl->reg_write_work);
memset(mhi_cntrl->reg_write_q, 0,
sizeof(struct reg_write_info) * REG_WRITE_QUEUE_LEN);
mhi_cntrl->read_idx = 0;
atomic_set(&mhi_cntrl->write_idx, -1);
}
static void mhi_reg_write_enqueue(struct mhi_controller *mhi_cntrl,
void __iomem *reg_addr, u32 val)
{
u32 q_index = atomic_inc_return(&mhi_cntrl->write_idx);
q_index = q_index & (REG_WRITE_QUEUE_LEN - 1);
MHI_ASSERT(mhi_cntrl->reg_write_q[q_index].valid, "queue full idx %d",
q_index);
mhi_cntrl->reg_write_q[q_index].reg_addr = reg_addr;
mhi_cntrl->reg_write_q[q_index].val = val;
mhi_cntrl->reg_write_q[q_index].valid = true;
}
void mhi_write_reg_offload(struct mhi_controller *mhi_cntrl,
void __iomem *base,
u32 offset,
u32 val)
{
mhi_reg_write_enqueue(mhi_cntrl, base + offset, val);
queue_work(mhi_cntrl->offload_wq, &mhi_cntrl->reg_write_work);
}
void mhi_write_reg(struct mhi_controller *mhi_cntrl,
void __iomem *base,
u32 offset,

View file

@ -167,8 +167,8 @@ void mhi_set_mhi_state(struct mhi_controller *mhi_cntrl,
mhi_write_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
MHICTRL_RESET_MASK, MHICTRL_RESET_SHIFT, 1);
} else {
mhi_write_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
MHICTRL_MHISTATE_MASK, MHICTRL_MHISTATE_SHIFT, state);
mhi_cntrl->write_reg(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
(state << MHICTRL_MHISTATE_SHIFT));
}
}
@ -481,6 +481,12 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl)
wake_up_all(&mhi_cntrl->state_event);
/* offload register write if supported */
if (mhi_cntrl->offload_wq) {
mhi_reset_reg_write_q(mhi_cntrl);
mhi_cntrl->write_reg = mhi_write_reg_offload;
}
/* force MHI to be in M0 state before continuing */
ret = __mhi_device_get_sync(mhi_cntrl);
if (ret)
@ -556,6 +562,12 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
TO_MHI_STATE_STR(mhi_cntrl->dev_state),
to_mhi_pm_state_str(transition_state));
/* restore async write call back */
mhi_cntrl->write_reg = mhi_write_reg;
if (mhi_cntrl->offload_wq)
mhi_reset_reg_write_q(mhi_cntrl);
/* We must notify MHI control driver so it can clean up first */
if (transition_state == MHI_PM_SYS_ERR_PROCESS)
mhi_cntrl->status_cb(mhi_cntrl, mhi_cntrl->priv_data,
@ -1001,6 +1013,8 @@ void mhi_control_error(struct mhi_controller *mhi_cntrl)
goto exit_control_error;
}
mhi_cntrl->dev_state = MHI_STATE_SYS_ERR;
/* notify waiters to bail out early since MHI has entered ERROR state */
wake_up_all(&mhi_cntrl->state_event);
@ -1444,6 +1458,8 @@ int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl)
mhi_trigger_resume(mhi_cntrl);
read_unlock_bh(&mhi_cntrl->pm_lock);
mhi_force_reg_write(mhi_cntrl);
ret = wait_event_timeout(mhi_cntrl->state_event,
mhi_cntrl->pm_state == MHI_PM_M0 ||
MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),

View file

@ -13,6 +13,8 @@ struct bhi_vec_entry;
struct mhi_timesync;
struct mhi_buf_info;
#define REG_WRITE_QUEUE_LEN 1024
/**
* enum MHI_CB - MHI callback
* @MHI_CB_IDLE: MHI entered idle state
@ -185,6 +187,19 @@ struct file_info {
u32 rem_seg_len;
};
/**
* struct reg_write_info - offload reg write info
* @reg_addr - register address
* @val - value to be written to register
* @chan - channel number
* @valid - entry is valid or not
*/
struct reg_write_info {
void __iomem *reg_addr;
u32 val;
bool valid;
};
/**
* struct mhi_controller - Master controller structure for external modem
* @dev: Device associated with this controller
@ -380,6 +395,13 @@ struct mhi_controller {
void *log_buf;
struct dentry *dentry;
struct dentry *parent;
/* for reg write offload */
struct workqueue_struct *offload_wq;
struct work_struct reg_write_work;
struct reg_write_info *reg_write_q;
atomic_t write_idx;
u32 read_idx;
};
/**