Re: [PATCH v5 2/2] mfd: loongson-se: Fix miscellaneous issues

Huacai Chen <[email protected]> Wed, 5 Aug 2026 21:40:44 +0800
Newsgroups org.kernel.vger.linux-crypto,dev.linux.lists.loongarch,org.kernel.vger.linux-kernel
Message-ID <CAAhV-H7xAOK4m2D-x23gOgYnxhn4pteb0x5rfd7GtT=YHOXgfQ@mail.gmail.com>
On Tue, Aug 4, 2026 at 10:29=E2=80=AFPM Huacai Chen <[email protected]>=
 wrote:
>
> On Tue, Aug 4, 2026 at 10:09=E2=80=AFAM Qunqin Zhao <zhaoqunqin@loongson.=
cn> wrote:
> >
> >
> > =E5=9C=A8 2026/8/3 16:01, Huacai Chen =E5=86=99=E9=81=93:
> > > Hi, Qunqin,
> > >
> > > On Thu, Jul 30, 2026 at 4:41=E2=80=AFPM Qunqin Zhao <zhaoqunqin@loong=
son.cn> 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 tha=
t
> > >>    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]=
m/ [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?
> If all callers have used mutex_lock(&se->cmd_lock), then spinlock is
> unnecessary.
After some thinking, maybe the AI suggestion is wrong, we shouldn't
use mutex to replace spinlock here.

Yes, loongson_se_poll() is executed in process context, but there
isn't a rule "don't use spinlock in process context".

The real rules are:
1. Keep critical sections as short as possible.
2. Don't use sleepable functions in spinlock critical sections.

writel() and readl_relaxed_poll_timeout_atomic() are not sleepable
functions, so spinlock is just fine.


Huacai

>
> > >
> > >> -
> > >>          /* Notify the controller that the engine needs to be starte=
d */
> > >>          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(struc=
t loongson_se *se,
> > >>          u32 *send_cmd =3D (u32 *)cmd;
> > >>          int err, i;
> > >>
> > >> +       mutex_lock(&se->cmd_lock);
> > >> +
> > >> +       reinit_completion(&se->cmd_completion);
> > >> +
> > >>          for (i =3D 0; i < SE_SEND_CMD_REG_LEN; i++)
> > >>                  writel(send_cmd[i], se->base + SE_SEND_CMD_REG + i =
* 4);
> > >>
> > >>          err =3D loongson_se_poll(se, SE_INT_CONTROLLER);
> > >>          if (err)
> > >> -               return err;
> > >> +               goto out;
> > >> +
> > >> +       err =3D wait_for_completion_interruptible(&se->cmd_completio=
n);
> > >>
> > >> -       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 know=
s
> > >>           * 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 =3D loongson_se_poll(engine->se, BIT(engine->id));
> > >> +       err =3D 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_engin=
e(struct device *dev, int id)
> > >>
> > >>          engine->se =3D se;
> > >>          engine->id =3D 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 objec=
t,
> > all objects have already been fully initialized with init_completion du=
ring the probe stage.
> In my opinion, if a function can be called multiple times, we need
> reinit_completion(), if it is only called for probe, we need
> init_completion(), and loongson_se_init_engine() looks like the later
> case.
>
> Huacai
>
> >
> > >
> > > Huacai
> > >
> > >>          /* Divide DMA memory equally among all engines */
> > >>          engine->buffer_size =3D se->dmam_size / SE_ENGINE_MAX;
> > >> @@ -113,8 +125,6 @@ struct loongson_se_engine *loongson_se_init_engi=
ne(struct device *dev, int id)
> > >>          engine->command =3D se->dmam_base + id * (2 * SE_ENGINE_CMD=
_SIZE);
> > >>          engine->command_ret =3D engine->command + SE_ENGINE_CMD_SIZ=
E;
> > >>
> > >> -       mutex_lock(&se->engine_init_lock);
> > >> -
> > >>          /* Tell the controller where to find engine command */
> > >>          cmd.command_id =3D SE_CMD_SET_ENGINE_CMDBUF;
> > >>          cmd.info[0] =3D id;
> > >> @@ -124,8 +134,6 @@ struct loongson_se_engine *loongson_se_init_engi=
ne(struct device *dev, int id)
> > >>          if (loongson_se_send_controller_cmd(se, &cmd))
> > >>                  engine =3D 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 =3D __ffs(int_status);
> > >> -               complete(&se->engines[id].completion);
> > >> +               if (id < SE_ENGINE_MAX)
> > >> +                       complete(&se->engines[id].completion);
> > >>                  int_status &=3D ~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 add=
r, int size)
> > >>   {
> > >> -       struct loongson_se_controller_cmd cmd;
> > >> +       struct loongson_se_controller_cmd cmd =3D {0};
> > >>          int err;
> > >>
> > >>          cmd.command_id =3D SE_CMD_START;
> > >> @@ -188,6 +197,30 @@ static const struct mfd_cell engines[] =3D {
> > >>          { .name =3D "tpm_loongson" },
> > >>   };
> > >>
> > >> +static void loongson_se_stop(void *data)
> > >> +{
> > >> +       struct loongson_se *se =3D data;
> > >> +       struct loongson_se_controller_cmd cmd =3D {0};
> > >> +       u32 *send_cmd =3D (u32 *)&cmd;
> > >> +       int i;
> > >> +
> > >> +       mutex_lock(&se->cmd_lock);
> > >> +
> > >> +       cmd.command_id =3D SE_CMD_STOP;
> > >> +
> > >> +       reinit_completion(&se->cmd_completion);
> > >> +
> > >> +       for (i =3D 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 =3D &pdev->dev;
> > >> @@ -195,19 +228,25 @@ static int loongson_se_probe(struct platform_d=
evice *pdev)
> > >>          int nr_irq, irq, err, i;
> > >>          dma_addr_t paddr;
> > >>
> > >> -       se =3D devm_kmalloc(dev, sizeof(*se), GFP_KERNEL);
> > >> +       se =3D 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 =3D 0; i < SE_ENGINE_MAX; i++)
> > >> +               init_completion(&se->engines[i].completion);
> > Thanks
> >