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

Huacai Chen <[email protected]> Mon, 3 Aug 2026 16:01:30 +0800
Newsgroups dev.linux.lists.loongarch,org.kernel.vger.linux-crypto,org.kernel.vger.linux-kernel
Message-ID <CAAhV-H4V6NWDcw1nVZzaXKJF_QS0sz5jTsrnwbq-xki2mJLVtQ@mail.gmail.com>
Hi, Qunqin,

On Thu, Jul 30, 2026 at 4:41=E2=80=AFPM 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.

> -
>         /* 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_TI=
MEOUT_US);
>
> -       spin_unlock_irq(&se->dev_lock);
> -
>         return err;
>  }
>
> @@ -63,24 +59,40 @@ static int loongson_se_send_controller_cmd(struct loo=
ngson_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_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 starte=
d.
>          */
> -       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_engine(str=
uct 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.

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_engine(st=
ruct 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_SIZE;
>
> -       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_engine(st=
ruct 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 addr, 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_device=
 *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);
>
>         dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
>         if (device_property_read_u32(dev, "dmam_size", &se->dmam_size))
>                 return -ENODEV;
>
> +       if (se->dmam_size < (SE_ENGINE_MAX * 2 * SE_ENGINE_CMD_SIZE))
> +               return -EINVAL;
> +
>         se->dmam_base =3D dmam_alloc_coherent(dev, se->dmam_size, &paddr,=
 GFP_KERNEL);
>         if (!se->dmam_base)
>                 return -ENOMEM;
> @@ -217,21 +256,31 @@ static int loongson_se_probe(struct platform_device=
 *pdev)
>                 return PTR_ERR(se->base);
>
>         nr_irq =3D platform_irq_count(pdev);
> -       if (nr_irq <=3D 0)
> +       if (nr_irq =3D=3D 0)
>                 return -ENODEV;
> +       if (nr_irq < 0)
> +               return nr_irq;
>
>         writel(SE_INT_ALL, se->base + SE_S2LINT_CL);
>
>         for (i =3D 0; i < nr_irq; i++) {
>                 irq =3D platform_get_irq(pdev, i);
>                 err =3D devm_request_irq(dev, irq, se_irq_handler, IRQF_S=
HARED, "loongson-se", se);
> -               if (err)
> +               if (err) {
>                         dev_err(dev, "failed to request IRQ: %d\n", irq);
> +                       return err;
> +               }
>         }
>
>         writel(SE_INT_ALL, se->base + SE_S2LINT_EN);
>
>         err =3D loongson_se_init(se, paddr, se->dmam_size);
> +       if (err) {
> +               writel(0, se->base + SE_S2LINT_EN);
> +               return err;
> +       }
> +
> +       err =3D devm_add_action_or_reset(dev, loongson_se_stop, se);
>         if (err)
>                 return err;
>
> diff --git a/include/linux/mfd/loongson-se.h b/include/linux/mfd/loongson=
-se.h
> index 07afa0c252..8237ccab7b 100644
> --- a/include/linux/mfd/loongson-se.h
> +++ b/include/linux/mfd/loongson-se.h
> @@ -9,6 +9,7 @@
>  #define SE_SEND_CMD_REG_LEN            0x8
>  /* Controller command ID */
>  #define SE_CMD_START                   0x0
> +#define SE_CMD_STOP                    0x1
>  #define SE_CMD_SET_DMA                 0x3
>  #define SE_CMD_SET_ENGINE_CMDBUF       0x4
>
> --
> 2.47.3
>