[PATCH] ARM: PL330: Fix driver freeze

Jassi Brar jaswinder.singh at linaro.org
Fri Dec 16 01:27:49 EST 2011


On 12 December 2011 00:57, Javi Merino <javi.merino at arm.com> wrote:
> Add a req_running field to the pl330_thread to track which request (if
> any) has been submitted to the DMA.  This mechanism replaces the old
> one in which we tried to guess the same by looking at the PC of the
> DMA, which could prevent the driver from sending more requests if it
> didn't guess correctly.
>
> Signed-off-by: Javi Merino <javi.merino at arm.com>
> Cc: Jassi Brar <jaswinder.singh at linaro.org>
> ---
>  arch/arm/common/pl330.c |  116 ++++++++++++++++++++---------------------------
>  1 files changed, 49 insertions(+), 67 deletions(-)
>
> diff --git a/arch/arm/common/pl330.c b/arch/arm/common/pl330.c
> index f407a6b..8d8df74 100644
> --- a/arch/arm/common/pl330.c
> +++ b/arch/arm/common/pl330.c
> @@ -221,17 +221,6 @@
>  */
>  #define MCODE_BUFF_PER_REQ     256
>
> -/*
> - * Mark a _pl330_req as free.
> - * We do it by writing DMAEND as the first instruction
> - * because no valid request is going to have DMAEND as
> - * its first instruction to execute.
> - */
> -#define MARK_FREE(req) do { \
> -                               _emit_END(0, (req)->mc_cpu); \
> -                               (req)->mc_len = 0; \
> -                       } while (0)
> -
>  /* If the _pl330_req is available to the client */
>  #define IS_FREE(req)   (*((u8 *)((req)->mc_cpu)) == CMD_DMAEND)
>
> @@ -301,8 +290,10 @@ struct pl330_thread {
>        struct pl330_dmac *dmac;
>        /* Only two at a time */
>        struct _pl330_req req[2];
> -       /* Index of the last submitted request */
> +       /* Index of the last enqueued request */
>        unsigned lstenq;
> +       /* Index of the last submitted request or -1 if the DMA is stopped */
> +       int req_running;
>  };
>
>  enum pl330_dmac_state {
> @@ -778,6 +769,22 @@ static inline void _execute_DBGINSN(struct pl330_thread *thrd,
>        writel(0, regs + DBGCMD);
>  }
>
> +/*
> + * Mark a _pl330_req as free.
> + * We do it by writing DMAEND as the first instruction
> + * because no valid request is going to have DMAEND as
> + * its first instruction to execute.
> + */
> +static void mark_free(struct pl330_thread *thrd, int idx)
> +{
> +       struct _pl330_req *req = &thrd->req[idx];
> +
> +       _emit_END(0, req->mc_cpu);
> +       req->mc_len = 0;
> +
> +       thrd->req_running = -1;
> +}
> +
>  static inline u32 _state(struct pl330_thread *thrd)
>  {
>        void __iomem *regs = thrd->dmac->pinfo->base;
> @@ -836,31 +843,6 @@ static inline u32 _state(struct pl330_thread *thrd)
>        }
>  }
>
> -/* If the request 'req' of thread 'thrd' is currently active */
> -static inline bool _req_active(struct pl330_thread *thrd,
> -               struct _pl330_req *req)
> -{
> -       void __iomem *regs = thrd->dmac->pinfo->base;
> -       u32 buf = req->mc_bus, pc = readl(regs + CPC(thrd->id));
> -
> -       if (IS_FREE(req))
> -               return false;
> -
> -       return (pc >= buf && pc <= buf + req->mc_len) ? true : false;
> -}
> -
> -/* Returns 0 if the thread is inactive, ID of active req + 1 otherwise */
> -static inline unsigned _thrd_active(struct pl330_thread *thrd)
> -{
> -       if (_req_active(thrd, &thrd->req[0]))
> -               return 1; /* First req active */
> -
> -       if (_req_active(thrd, &thrd->req[1]))
> -               return 2; /* Second req active */
> -
> -       return 0;
> -}
> -
>  static void _stop(struct pl330_thread *thrd)
>  {
>        void __iomem *regs = thrd->dmac->pinfo->base;
> @@ -892,17 +874,22 @@ static bool _trigger(struct pl330_thread *thrd)
>        struct _arg_GO go;
>        unsigned ns;
>        u8 insn[6] = {0, 0, 0, 0, 0, 0};
> +       int idx;
>
>        /* Return if already ACTIVE */
>        if (_state(thrd) != PL330_STATE_STOPPED)
>                return true;
>
> -       if (!IS_FREE(&thrd->req[1 - thrd->lstenq]))
> -               req = &thrd->req[1 - thrd->lstenq];
> -       else if (!IS_FREE(&thrd->req[thrd->lstenq]))
> -               req = &thrd->req[thrd->lstenq];
> -       else
> -               req = NULL;
> +       idx = 1 - thrd->lstenq;
> +       if (!IS_FREE(&thrd->req[idx]))
> +               req = &thrd->req[idx];
> +       else {
> +               idx = thrd->lstenq;
> +               if (!IS_FREE(&thrd->req[idx]))
> +                       req = &thrd->req[idx];
> +               else
> +                       req = NULL;
> +       }
>
>        /* Return if no request */
>        if (!req || !req->r)
> @@ -933,6 +920,8 @@ static bool _trigger(struct pl330_thread *thrd)
>        /* Only manager can execute GO */
>        _execute_DBGINSN(thrd, insn, true);
>
> +       thrd->req_running = idx;
> +
>        return true;
>  }
>
> @@ -1382,8 +1371,8 @@ static void pl330_dotask(unsigned long data)
>
>                        thrd->req[0].r = NULL;
>                        thrd->req[1].r = NULL;
> -                       MARK_FREE(&thrd->req[0]);
> -                       MARK_FREE(&thrd->req[1]);
> +                       mark_free(thrd, 0);
> +                       mark_free(thrd, 1);
>
>                        /* Clear the reset flag */
>                        pl330->dmac_tbd.reset_chan &= ~(1 << i);
> @@ -1461,14 +1450,12 @@ int pl330_update(const struct pl330_info *pi)
>
>                        thrd = &pl330->channels[id];
>
> -                       active = _thrd_active(thrd);
> -                       if (!active) /* Aborted */
> +                       active = thrd->req_running;
> +                       if (active == -1) /* Aborted */
>                                continue;
>
> -                       active -= 1;
> -
>                        rqdone = &thrd->req[active];
> -                       MARK_FREE(rqdone);
> +                       mark_free(thrd, active);
>
>                        /* Get going again ASAP */
>                        _start(thrd);
> @@ -1509,7 +1496,7 @@ int pl330_chan_ctrl(void *ch_id, enum pl330_chan_op op)
>        struct pl330_thread *thrd = ch_id;
>        struct pl330_dmac *pl330;
>        unsigned long flags;
> -       int ret = 0, active;
> +       int ret = 0, active = thrd->req_running;
>
>        if (!thrd || thrd->free || thrd->dmac->state == DYING)
>                return -EINVAL;
> @@ -1525,28 +1512,24 @@ int pl330_chan_ctrl(void *ch_id, enum pl330_chan_op op)
>
>                thrd->req[0].r = NULL;
>                thrd->req[1].r = NULL;
> -               MARK_FREE(&thrd->req[0]);
> -               MARK_FREE(&thrd->req[1]);
> +               mark_free(thrd, 0);
> +               mark_free(thrd, 1);
>                break;
>
>        case PL330_OP_ABORT:
> -               active = _thrd_active(thrd);
> -
>                /* Make sure the channel is stopped */
>                _stop(thrd);
>
>                /* ABORT is only for the active req */
> -               if (!active)
> +               if (active == -1)
>                        break;
>
> -               active--;
> -
>                thrd->req[active].r = NULL;
> -               MARK_FREE(&thrd->req[active]);
> +               mark_free(thrd, active);
>
>                /* Start the next */
>        case PL330_OP_START:
> -               if (!_thrd_active(thrd) && !_start(thrd))
> +               if ((active == -1) && !_start(thrd))
>                        ret = -EIO;
>                break;
>
> @@ -1587,14 +1570,13 @@ int pl330_chan_status(void *ch_id, struct pl330_chanstatus *pstatus)
>        else
>                pstatus->faulting = false;
>
> -       active = _thrd_active(thrd);
> +       active = thrd->req_running;
>
> -       if (!active) {
> +       if (active == -1) {
>                /* Indicate that the thread is not running */
>                pstatus->top_req = NULL;
>                pstatus->wait_req = NULL;
>        } else {
> -               active--;
>                pstatus->top_req = thrd->req[active].r;
>                pstatus->wait_req = !IS_FREE(&thrd->req[1 - active])
>                                        ? thrd->req[1 - active].r : NULL;
> @@ -1659,9 +1641,9 @@ void *pl330_request_channel(const struct pl330_info *pi)
>                                thrd->free = false;
>                                thrd->lstenq = 1;
>                                thrd->req[0].r = NULL;
> -                               MARK_FREE(&thrd->req[0]);
> +                               mark_free(thrd, 0);
>                                thrd->req[1].r = NULL;
> -                               MARK_FREE(&thrd->req[1]);
> +                               mark_free(thrd, 1);
>                                break;
>                        }
>                }
> @@ -1767,14 +1749,14 @@ static inline void _reset_thread(struct pl330_thread *thrd)
>        thrd->req[0].mc_bus = pl330->mcode_bus
>                                + (thrd->id * pi->mcbufsz);
>        thrd->req[0].r = NULL;
> -       MARK_FREE(&thrd->req[0]);
> +       mark_free(thrd, 0);
>
>        thrd->req[1].mc_cpu = thrd->req[0].mc_cpu
>                                + pi->mcbufsz / 2;
>        thrd->req[1].mc_bus = thrd->req[0].mc_bus
>                                + pi->mcbufsz / 2;
>        thrd->req[1].r = NULL;
> -       MARK_FREE(&thrd->req[1]);
> +       mark_free(thrd, 1);
>  }
>
>  static int dmac_alloc_threads(struct pl330_dmac *pl330)
> --
Seems ok, except that 'unsigned lstenq' and 'int req_running' occupy
real estate more than they should for their purpose. Though otherwise
the necessary helpers would bloat the code too.

Hoping you have done testing of enough corner cases...
Acked-by: Jassi Brar <jaswinder.singh at linaro.org>

Thanks.



More information about the linux-arm-kernel mailing list