Re: [PATCH v5 2/2] mfd: loongson-se: Fix miscellaneous issues
Qunqin Zhao <[email protected]> Tue, 4 Aug 2026 10:08:59 +0800
| Newsgroups | org.kernel.vger.linux-crypto,dev.linux.lists.loongarch,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
在 2026/8/3 16:01, Huacai Chen 写道: > Hi, Qunqin, > > On Thu, Jul 30, 2026 at 4:41 PM Qunqin Zhao <[email protected]> wrote: >> Address multiple historical driver issues discovered by the Sashiko >> Automation system within the loongson_se_probe() initialization flow >> and the driver's interrupt service routines [1]. >> >> - Add an explicit bounds check in se_irq_handler() before accessing >> the engines array to prevent out-of-bounds memory writes. >> >> - Switch from devm_kmalloc() to devm_kzalloc() and explicitly >> initialize all engine completion structures in probe() to avoid a >> kernel panic from complete() dereferencing a NULL wait head when a >> spurious interrupt fires before child drivers call >> loongson_se_init_engine(). >> >> - Replace engine_init_lock with a broader cmd_lock mutex that >> serializes all command submissions, and move the lock into >> loongson_se_send_controller_cmd() and >> loongson_se_send_engine_cmd() to cover the full register write + >> poll + wait sequence. >> >> - Drop the spin_lock_irq from loongson_se_poll() so that interrupts >> are not disabled for up to 10 ms during the poll. Keep the >> readl_relaxed_poll_timeout_atomic() busy-wait to avoid scheduling >> latency for fast hardware completions. >> >> - Add reinit_completion() to loongson_se_send_controller_cmd() and >> loongson_se_send_engine_cmd() before waiting to prevent stale >> completions from falsely returning success after a signal >> interruption. >> >> - Fix EPROBE_DEFER handling: propagate the error directly from >> platform_irq_count() instead of overwriting it with ENODEV so that >> probe deferral works when the interrupt provider is not yet ready. >> >> - Validate dmam_size from firmware against the minimum required size >> to prevent command buffers from pointing outside the allocated >> DMA region. >> >> - Return the error code from devm_request_irq() instead of silently >> continuing to prevent an indefinite hang. >> >> - Disable hardware interrupts in the probe error path when >> loongson_se_init() fails to prevent an unhandled interrupt storm. >> >> - Add a loongson_se_stop() cleanup handler registered with >> devm_add_action_or_reset() to send SE_CMD_STOP to the controller >> and mask all interrupts during device removal. Using devres >> ensures that child MFD devices are unbound before the controller >> is stopped. The STOP command uses a non-interruptible wait to >> avoid leaving hardware running while DMA buffers are freed. >> >> - Zero-initialize the local controller command structure in >> loongson_se_init() to prevent uninitialized stack memory from >> being written to device registers. >> >> - Add the SE_CMD_STOP command definition. >> >> Link: https://lore.kernel.org/all/[email protected]/ [1] >> Fixes: e551fa3159e3 ("mfd: Add support for Loongson Security Engine chip controller") >> Signed-off-by: Qunqin Zhao <[email protected]> >> --- >> drivers/mfd/loongson-se.c | 87 ++++++++++++++++++++++++++------- >> include/linux/mfd/loongson-se.h | 1 + >> 2 files changed, 69 insertions(+), 19 deletions(-) >> >> diff --git a/drivers/mfd/loongson-se.c b/drivers/mfd/loongson-se.c >> index 7f552a8ee6..2c8afad1b0 100644 >> --- a/drivers/mfd/loongson-se.c >> +++ b/drivers/mfd/loongson-se.c >> @@ -28,7 +28,7 @@ struct loongson_se { >> void *dmam_base; >> int dmam_size; >> >> - struct mutex engine_init_lock; >> + struct mutex cmd_lock; >> struct loongson_se_engine engines[SE_ENGINE_MAX]; >> }; >> >> @@ -42,8 +42,6 @@ static int loongson_se_poll(struct loongson_se *se, u32 int_bit) >> u32 status; >> int err; >> >> - spin_lock_irq(&se->dev_lock); > You said you don't want to disable irq here, but I think the spinlock > is still needed. That means you should use spin_lock/spin_unlock to > replace spin_lock_irq/spin_unlock_irq. Since loongson_se_poll always executes in the thread context, is it better to use mutex_lock( mutex_lock(&se->cmd_lock) outside of loongson_se_poll) for synchronization? > >> - >> /* Notify the controller that the engine needs to be started */ >> writel(int_bit, se->base + SE_L2SINT_SET); >> >> @@ -52,8 +50,6 @@ static int loongson_se_poll(struct loongson_se *se, u32 int_bit) >> !(status & int_bit), >> 1, LOONGSON_ENGINE_CMD_TIMEOUT_US); >> >> - spin_unlock_irq(&se->dev_lock); >> - >> return err; >> } >> >> @@ -63,24 +59,40 @@ static int loongson_se_send_controller_cmd(struct loongson_se *se, >> u32 *send_cmd = (u32 *)cmd; >> int err, i; >> >> + mutex_lock(&se->cmd_lock); >> + >> + reinit_completion(&se->cmd_completion); >> + >> for (i = 0; i < SE_SEND_CMD_REG_LEN; i++) >> writel(send_cmd[i], se->base + SE_SEND_CMD_REG + i * 4); >> >> err = loongson_se_poll(se, SE_INT_CONTROLLER); >> if (err) >> - return err; >> + goto out; >> + >> + err = wait_for_completion_interruptible(&se->cmd_completion); >> >> - return wait_for_completion_interruptible(&se->cmd_completion); >> +out: >> + mutex_unlock(&se->cmd_lock); >> + return err; >> } >> >> int loongson_se_send_engine_cmd(struct loongson_se_engine *engine) >> { >> + int err; >> + >> + mutex_lock(&engine->se->cmd_lock); >> + >> + reinit_completion(&engine->completion); >> + >> /* >> * After engine initialization, the controller already knows >> * where to obtain engine commands from. Now all we need to >> * do is notify the controller that the engine needs to be started. >> */ >> - int err = loongson_se_poll(engine->se, BIT(engine->id)); >> + err = loongson_se_poll(engine->se, BIT(engine->id)); >> + >> + mutex_unlock(&engine->se->cmd_lock); >> >> if (err) >> return err; >> @@ -97,7 +109,7 @@ struct loongson_se_engine *loongson_se_init_engine(struct device *dev, int id) >> >> engine->se = se; >> engine->id = id; >> - init_completion(&engine->completion); >> + reinit_completion(&engine->completion); > I'm not sure, but I think loongson_se_init_engine() is only called at > init, so we need init_completion here. To prevent an spurious interrupt from completing an uninitialized object, all objects have already been fully initialized with init_completion during the probe stage. > > Huacai > >> /* Divide DMA memory equally among all engines */ >> engine->buffer_size = se->dmam_size / SE_ENGINE_MAX; >> @@ -113,8 +125,6 @@ struct loongson_se_engine *loongson_se_init_engine(struct device *dev, int id) >> engine->command = se->dmam_base + id * (2 * SE_ENGINE_CMD_SIZE); >> engine->command_ret = engine->command + SE_ENGINE_CMD_SIZE; >> >> - mutex_lock(&se->engine_init_lock); >> - >> /* Tell the controller where to find engine command */ >> cmd.command_id = SE_CMD_SET_ENGINE_CMDBUF; >> cmd.info[0] = id; >> @@ -124,8 +134,6 @@ struct loongson_se_engine *loongson_se_init_engine(struct device *dev, int id) >> if (loongson_se_send_controller_cmd(se, &cmd)) >> engine = NULL; >> >> - mutex_unlock(&se->engine_init_lock); >> - >> return engine; >> } >> EXPORT_SYMBOL_GPL(loongson_se_init_engine); >> @@ -155,7 +163,8 @@ static irqreturn_t se_irq_handler(int irq, void *dev_id) >> /* For engines */ >> while (int_status) { >> id = __ffs(int_status); >> - complete(&se->engines[id].completion); >> + if (id < SE_ENGINE_MAX) >> + complete(&se->engines[id].completion); >> int_status &= ~BIT(id); >> writel(BIT(id), se->base + SE_S2LINT_CL); >> } >> @@ -167,7 +176,7 @@ static irqreturn_t se_irq_handler(int irq, void *dev_id) >> >> static int loongson_se_init(struct loongson_se *se, dma_addr_t addr, int size) >> { >> - struct loongson_se_controller_cmd cmd; >> + struct loongson_se_controller_cmd cmd = {0}; >> int err; >> >> cmd.command_id = SE_CMD_START; >> @@ -188,6 +197,30 @@ static const struct mfd_cell engines[] = { >> { .name = "tpm_loongson" }, >> }; >> >> +static void loongson_se_stop(void *data) >> +{ >> + struct loongson_se *se = data; >> + struct loongson_se_controller_cmd cmd = {0}; >> + u32 *send_cmd = (u32 *)&cmd; >> + int i; >> + >> + mutex_lock(&se->cmd_lock); >> + >> + cmd.command_id = SE_CMD_STOP; >> + >> + reinit_completion(&se->cmd_completion); >> + >> + for (i = 0; i < SE_SEND_CMD_REG_LEN; i++) >> + writel(send_cmd[i], se->base + SE_SEND_CMD_REG + i * 4); >> + >> + if (!loongson_se_poll(se, SE_INT_CONTROLLER)) >> + wait_for_completion(&se->cmd_completion); >> + >> + writel(0, se->base + SE_S2LINT_EN); >> + >> + mutex_unlock(&se->cmd_lock); >> +} >> + >> static int loongson_se_probe(struct platform_device *pdev) >> { >> struct device *dev = &pdev->dev; >> @@ -195,19 +228,25 @@ static int loongson_se_probe(struct platform_device *pdev) >> int nr_irq, irq, err, i; >> dma_addr_t paddr; >> >> - se = devm_kmalloc(dev, sizeof(*se), GFP_KERNEL); >> + se = devm_kzalloc(dev, sizeof(*se), GFP_KERNEL); >> if (!se) >> return -ENOMEM; >> >> dev_set_drvdata(dev, se); >> init_completion(&se->cmd_completion); >> spin_lock_init(&se->dev_lock); >> - mutex_init(&se->engine_init_lock); >> + mutex_init(&se->cmd_lock); >> + >> + for (i = 0; i < SE_ENGINE_MAX; i++) >> + init_completion(&se->engines[i].completion); Thanks