diff options
Diffstat (limited to 'arch/arm/common/pl330.c')
-rw-r--r-- | arch/arm/common/pl330.c | 248 |
1 files changed, 169 insertions, 79 deletions
diff --git a/arch/arm/common/pl330.c b/arch/arm/common/pl330.c index 67abef5..6147b24 100644 --- a/arch/arm/common/pl330.c +++ b/arch/arm/common/pl330.c @@ -221,6 +221,17 @@ */ #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) @@ -290,10 +301,8 @@ struct pl330_thread { struct pl330_dmac *dmac; /* Only two at a time */ struct _pl330_req req[2]; - /* Index of the last enqueued request */ + /* Index of the last submitted request */ unsigned lstenq; - /* Index of the last submitted request or -1 if the DMA is stopped */ - int req_running; }; enum pl330_dmac_state { @@ -769,22 +778,6 @@ 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; @@ -843,6 +836,31 @@ 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; @@ -874,22 +892,17 @@ 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; - 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; - } + 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; /* Return if no request */ if (!req || !req->r) @@ -920,8 +933,6 @@ static bool _trigger(struct pl330_thread *thrd) /* Only manager can execute GO */ _execute_DBGINSN(thrd, insn, true); - thrd->req_running = idx; - return true; } @@ -1105,6 +1116,54 @@ static inline int _loop(unsigned dry_run, u8 buf[], return off; } +/* Returns bytes consumed and updates bursts */ +static inline int _loop_infiniteloop(unsigned dry_run, u8 buf[], + unsigned long bursts, const struct _xfer_spec *pxs, int ev) +{ + int cyc, off; + unsigned lcnt0, lcnt1, ljmp0, ljmp1, ljmpfe; + struct _arg_LPEND lpend; + + off = 0; + ljmpfe = off; + lcnt0 = pxs->r->infiniteloop; + lcnt1 = 256; + cyc = bursts/256; + + /* forever loop */ + off += _emit_MOV(dry_run, &buf[off], SAR, pxs->x->src_addr); + off += _emit_MOV(dry_run, &buf[off], DAR, pxs->x->dst_addr); + + /* loop0 */ + off += _emit_LP(dry_run, &buf[off], 0, lcnt0); + ljmp0 = off; + + /* loop1 */ + off += _emit_LP(dry_run, &buf[off], 1, lcnt1); + ljmp1 = off; + off += _bursts(dry_run, &buf[off], pxs, cyc); + lpend.cond = ALWAYS; + lpend.forever = false; + lpend.loop = 1; + lpend.bjump = off - ljmp1; + off += _emit_LPEND(dry_run, &buf[off], &lpend); + off += _emit_SEV(dry_run, &buf[off], ev); + + lpend.cond = ALWAYS; + lpend.forever = false; + lpend.loop = 0; + lpend.bjump = off - ljmp0; + off += _emit_LPEND(dry_run, &buf[off], &lpend); + + lpend.cond = ALWAYS; + lpend.forever = true; + lpend.loop = 1; + lpend.bjump = off - ljmpfe; + off += _emit_LPEND(dry_run, &buf[off], &lpend); + + return off; +} + static inline int _setup_loops(unsigned dry_run, u8 buf[], const struct _xfer_spec *pxs) { @@ -1139,6 +1198,20 @@ static inline int _setup_xfer(unsigned dry_run, u8 buf[], return off; } +static inline int _setup_xfer_infiniteloop(unsigned dry_run, u8 buf[], + const struct _xfer_spec *pxs, int ev) +{ + struct pl330_xfer *x = pxs->x; + u32 ccr = pxs->ccr; + unsigned long bursts = BYTE_TO_BURST(x->bytes, ccr); + int off = 0; + + /* Setup Loop(s) */ + off += _loop_infiniteloop(dry_run, &buf[off], bursts, pxs, ev); + + return off; +} + /* * A req is a sequence of one or more xfer units. * Returns the number of bytes taken to setup the MC for the req. @@ -1157,22 +1230,33 @@ static int _setup_req(unsigned dry_run, struct pl330_thread *thrd, off += _emit_MOV(dry_run, &buf[off], CCR, pxs->ccr); x = pxs->r->x; - do { + + if (!pxs->r->infiniteloop) { + do { + /* Error if xfer length is not aligned at burst size */ + if (x->bytes % (BRST_SIZE(pxs->ccr) + * BRST_LEN(pxs->ccr))) + return -EINVAL; + + pxs->x = x; + off += _setup_xfer(dry_run, &buf[off], pxs); + + x = x->next; + } while (x); + + /* DMASEV peripheral/event */ + off += _emit_SEV(dry_run, &buf[off], thrd->ev); + /* DMAEND */ + off += _emit_END(dry_run, &buf[off]); + } else { /* Error if xfer length is not aligned at burst size */ if (x->bytes % (BRST_SIZE(pxs->ccr) * BRST_LEN(pxs->ccr))) return -EINVAL; pxs->x = x; - off += _setup_xfer(dry_run, &buf[off], pxs); - - x = x->next; - } while (x); - - /* DMASEV peripheral/event */ - off += _emit_SEV(dry_run, &buf[off], thrd->ev); - /* DMAEND */ - off += _emit_END(dry_run, &buf[off]); - + off += _setup_xfer_infiniteloop + (dry_run, &buf[off], pxs, thrd->ev); + } return off; } @@ -1200,8 +1284,8 @@ static inline u32 _prepare_ccr(const struct pl330_reqcfg *rqc) ccr |= (rqc->brst_size << CC_SRCBRSTSIZE_SHFT); ccr |= (rqc->brst_size << CC_DSTBRSTSIZE_SHFT); - ccr |= (rqc->scctl << CC_SRCCCTRL_SHFT); - ccr |= (rqc->dcctl << CC_DSTCCTRL_SHFT); + ccr |= (rqc->dcctl << CC_SRCCCTRL_SHFT); + ccr |= (rqc->scctl << CC_DSTCCTRL_SHFT); ccr |= (rqc->swap << CC_SWAP_SHFT); @@ -1270,17 +1354,17 @@ int pl330_submit_req(void *ch_id, struct pl330_req *r) goto xfer_exit; } - /* Prefer Secure Channel */ - if (!_manager_ns(thrd)) - r->cfg->nonsecure = 0; - else - r->cfg->nonsecure = 1; - /* Use last settings, if not provided */ - if (r->cfg) + if (r->cfg) { + /* Prefer Secure Channel */ + if (!_manager_ns(thrd)) + r->cfg->nonsecure = 0; + else + r->cfg->nonsecure = 1; ccr = _prepare_ccr(r->cfg); - else + } else { ccr = readl(regs + CC(thrd->id)); + } /* If this req doesn't have valid xfer settings */ if (!_is_valid(ccr)) { @@ -1371,8 +1455,8 @@ static void pl330_dotask(unsigned long data) thrd->req[0].r = NULL; thrd->req[1].r = NULL; - mark_free(thrd, 0); - mark_free(thrd, 1); + MARK_FREE(&thrd->req[0]); + MARK_FREE(&thrd->req[1]); /* Clear the reset flag */ pl330->dmac_tbd.reset_chan &= ~(1 << i); @@ -1448,17 +1532,25 @@ int pl330_update(const struct pl330_info *pi) id = pl330->events[ev]; + if (id == -1) /* Released? */ + continue; + thrd = &pl330->channels[id]; - active = thrd->req_running; - if (active == -1) /* Aborted */ + active = _thrd_active(thrd); + if (!active) /* Aborted */ continue; + active -= 1; + rqdone = &thrd->req[active]; - mark_free(thrd, active); - /* Get going again ASAP */ - _start(thrd); + if (!rqdone->r->infiniteloop) { + MARK_FREE(rqdone); + + /* Get going again ASAP */ + _start(thrd); + } /* For now, just make a list of callbacks to be done */ list_add_tail(&rqdone->rqd, &pl330->req_done); @@ -1502,7 +1594,6 @@ int pl330_chan_ctrl(void *ch_id, enum pl330_chan_op op) return -EINVAL; pl330 = thrd->dmac; - active = thrd->req_running; spin_lock_irqsave(&pl330->lock, flags); @@ -1513,24 +1604,28 @@ 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, 0); - mark_free(thrd, 1); + MARK_FREE(&thrd->req[0]); + MARK_FREE(&thrd->req[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 == -1) + if (!active) break; + active--; + thrd->req[active].r = NULL; - mark_free(thrd, active); + MARK_FREE(&thrd->req[active]); /* Start the next */ case PL330_OP_START: - if ((active == -1) && !_start(thrd)) + if (!_start(thrd)) ret = -EIO; break; @@ -1571,13 +1666,14 @@ int pl330_chan_status(void *ch_id, struct pl330_chanstatus *pstatus) else pstatus->faulting = false; - active = thrd->req_running; + active = _thrd_active(thrd); - if (active == -1) { + if (!active) { /* 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; @@ -1606,11 +1702,6 @@ static inline int _alloc_event(struct pl330_thread *thrd) return -1; } -static bool _chan_ns(const struct pl330_info *pi, int i) -{ - return pi->pcfg.irq_ns & (1 << i); -} - /* Upon success, returns IdentityToken for the * allocated channel, NULL otherwise. */ @@ -1635,16 +1726,15 @@ void *pl330_request_channel(const struct pl330_info *pi) for (i = 0; i < chans; i++) { thrd = &pl330->channels[i]; - if ((thrd->free) && (!_manager_ns(thrd) || - _chan_ns(pi, i))) { + if (thrd->free) { thrd->ev = _alloc_event(thrd); if (thrd->ev >= 0) { thrd->free = false; thrd->lstenq = 1; thrd->req[0].r = NULL; - mark_free(thrd, 0); + MARK_FREE(&thrd->req[0]); thrd->req[1].r = NULL; - mark_free(thrd, 1); + MARK_FREE(&thrd->req[1]); break; } } @@ -1750,14 +1840,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, 0); + MARK_FREE(&thrd->req[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, 1); + MARK_FREE(&thrd->req[1]); } static int dmac_alloc_threads(struct pl330_dmac *pl330) |