Patchwork [v4,1/2] pl330: initial version

login
register
mail settings
Submitter Peter A. G. Crosthwaite
Date June 18, 2012, 12:42 a.m.
Message ID <15126b43916ee3b1db4eafe3b0fabfcbfc8c0584.1339979922.git.peter.crosthwaite@petalogix.com>
Download mbox | patch
Permalink /patch/165358/
State New
Headers show

Comments

Peter A. G. Crosthwaite - June 18, 2012, 12:42 a.m.
Device model for Primecell PL330 dma controller.

Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
Signed-off-by: Kirill Batuzov <batuzovk@ispras.ru>
---
changed from v3:
rebased against Makefile refactor
changed from v2 (in order of diff appearance):
GPL version changed to v2 or later
Register and field names changed to match TRM
id field corrected
PL330ChanState enum corrected
target_phys_addr_t usages removed
identifier camel casing and underscoring fixed
data fifo reworked (uses head ptr and a occupancy count rather than head & tail ptrs)
pl330_insn_from_queue->pl330_queue_remove_insn
Numerous typos in comments corrected as per review
g_malloc0(sizeof(a) * b) -> g_new
pl330_fifo_inc: changed to accept PL330Fifo struct as argument (rather than buf_size)
pl330_fifo_push/get: rollback on overflow mechanism replaced with pre-check
pl330_queue_find_insn: fixed ugly if statement whitespace (PMM review)
pl330_dmaaddh: fixed arugment reversal
pl330_dmaaddh: asserted on !manager
"ns": change to be and behave like a boolean globally
pl330_dmald: force num=1 when PL330_SINGLE
pl330_dma_kill: put in correct alphabet order
pl330_dmarmb/wmb:implemented
pl330_exec_insn: asserted insn->size not too big
pl330_exec_insn: enabled stall mechanism outside exec state (needed for rmb/wmb)
pl330_debug_exec: assert fault type on a fault
pl330_iomem_write: removed aligmnent assertion
pl330_iomem_write: corrected ev_status and irq_status logic
pl330_iomem_read_imp: fixed some incorrect bitfield positions (PMM review)
pl330_reset: stopped timer
pl330_init: removed redundant reset
changed from v1:
GPLv2 license
some code formatting fixes

 hw/arm/Makefile.objs |    1 +
 hw/pl330.c           | 1411 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1412 insertions(+), 0 deletions(-)
 create mode 100644 hw/pl330.c
Mitsyanko Igor - June 18, 2012, 2:33 p.m.
Hi Peter, sorry for not properly reviewing your patch for such a long 
time, I'll try to do this as soon as possible. Right now I have a few 
small coments


On 06/18/2012 04:42 AM, Peter A. G. Crosthwaite wrote:
> Device model for Primecell PL330 dma controller.
>
> Signed-off-by: Peter A. G. Crosthwaite<peter.crosthwaite@petalogix.com>
> Signed-off-by: Kirill Batuzov<batuzovk@ispras.ru>
> ---
> [..snip..]
> +static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t chan_id;
> +    uint8_t ns;
> +    uint32_t pc;
> +    PL330Chan *s;
> +
> +    DB_PRINT("\n");
> +
> +    if (!ch->is_manager) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
According to description its more likely to cause UNDEF_INSTR here, not 
OPERAND_INVALID
> +        return;
> +    }
> +    ns = !!(opcode&  2);
> [..snip..]
> +
> +static Property pl330_properties[] = {
> +    DEFINE_PROP_UINT32("cfg0", PL330, cfg[0], 0),
> +    DEFINE_PROP_UINT32("cfg1", PL330, cfg[1], 0),
> +    DEFINE_PROP_UINT32("cfg2", PL330, cfg[2], 0),
> +    DEFINE_PROP_UINT32("cfg3", PL330, cfg[3], 0),
> +    DEFINE_PROP_UINT32("cfg4", PL330, cfg[4], 0),
> +    DEFINE_PROP_UINT32("cfg5", PL330, cfg[5], 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void pl330_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +
> +    k->init = pl330_init;
> +    dc->reset = pl330_reset;
> +    dc->props = pl330_properties;
> +}
> +
> +static TypeInfo pl330_info = {
> +    .name           = "pl330",
> +    .parent         = TYPE_SYS_BUS_DEVICE,
> +    .instance_size  = sizeof(PL330),
> +    .class_init      = pl330_class_init,
> +};
> +
I think Andreas requires all static TypeInfos to have const qualifier 
and their names to comply with "<device>_type_info" naming convention. 
I'm not sure about this though.
> +static void pl330_register_types(void)
> +{
> +    type_register_static(&pl330_info);
> +}
> +
> +type_init(pl330_register_types)

And it still has no save/load support, it is really mandatory for all 
new devices. I can recall that one of the maintainers wrote a while ago 
that every device at least needs to mark itself as non-migratable, if it 
doesn't implement a proper vmstate.

We used this PL330 implementation to transfer sound data in our emulated 
exynos-based system. It works, but very slow, because the way real 
hardware performs data transfers is not optimal for emulation.

Tested by: Igor Mitsyanko <i.mitsyanko@samsung.com>
Peter A. G. Crosthwaite - June 19, 2012, 6:40 a.m.
On Tue, Jun 19, 2012 at 12:33 AM, Igor Mitsyanko
<i.mitsyanko@samsung.com> wrote:
>
> Hi Peter, sorry for not properly reviewing your patch for such a long time,
> I'll try to do this as soon as possible. Right now I have a few small
> coments
>
>
>
> On 06/18/2012 04:42 AM, Peter A. G. Crosthwaite wrote:
>>
>> Device model for Primecell PL330 dma controller.
>>
>> Signed-off-by: Peter A. G. Crosthwaite<peter.crosthwaite@petalogix.com>
>> Signed-off-by: Kirill Batuzov<batuzovk@ispras.ru>
>> ---
>> [..snip..]
>>
>> +static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint8_t chan_id;
>> +    uint8_t ns;
>> +    uint32_t pc;
>> +    PL330Chan *s;
>> +
>> +    DB_PRINT("\n");
>> +
>> +    if (!ch->is_manager) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>
> According to description its more likely to cause UNDEF_INSTR here, not
> OPERAND_INVALID

Ok

>>
>> +        return;
>> +    }
>> +    ns = !!(opcode&  2);
>> [..snip..]
>>
>> +
>> +static Property pl330_properties[] = {
>> +    DEFINE_PROP_UINT32("cfg0", PL330, cfg[0], 0),
>> +    DEFINE_PROP_UINT32("cfg1", PL330, cfg[1], 0),
>> +    DEFINE_PROP_UINT32("cfg2", PL330, cfg[2], 0),
>> +    DEFINE_PROP_UINT32("cfg3", PL330, cfg[3], 0),
>> +    DEFINE_PROP_UINT32("cfg4", PL330, cfg[4], 0),
>> +    DEFINE_PROP_UINT32("cfg5", PL330, cfg[5], 0),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void pl330_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
>> +
>> +    k->init = pl330_init;
>> +    dc->reset = pl330_reset;
>> +    dc->props = pl330_properties;
>> +}
>> +
>> +static TypeInfo pl330_info = {
>> +    .name           = "pl330",
>> +    .parent         = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size  = sizeof(PL330),
>> +    .class_init      = pl330_class_init,
>> +};
>> +
>
> I think Andreas requires all static TypeInfos to have const qualifier and
> their names to comply with "<device>_type_info" naming convention. I'm not
> sure about this though.
>

Ok

>> +static void pl330_register_types(void)
>> +{
>> +    type_register_static(&pl330_info);
>> +}
>> +
>> +type_init(pl330_register_types)
>
>
> And it still has no save/load support, it is really mandatory for all new
> devices. I can recall that one of the maintainers wrote a while ago that
> every device at least needs to mark itself as non-migratable, if it doesn't
> implement a proper vmstate.
>
Ok, ccing  Andreas

> We used this PL330 implementation to transfer sound data in our emulated
> exynos-based system. It works, but very slow, because the way real hardware
> performs data transfers is not optimal for emulation.
>

Thats another battle for another day,

> Tested by: Igor Mitsyanko <i.mitsyanko@samsung.com>
>

Sweet,

Ill roll a V5 soon, but im guessing PMM will do a review cycle here as
well, so ill give it a few days.

Regards,
Peter
Andreas Färber - June 19, 2012, 10:17 a.m.
Am 19.06.2012 08:40, schrieb Peter Crosthwaite:
> On Tue, Jun 19, 2012 at 12:33 AM, Igor Mitsyanko
> <i.mitsyanko@samsung.com> wrote:
>>
>> Hi Peter, sorry for not properly reviewing your patch for such a long time,
>> I'll try to do this as soon as possible. Right now I have a few small
>> coments
>>
>>
>>
>> On 06/18/2012 04:42 AM, Peter A. G. Crosthwaite wrote:
>>>
>>> Device model for Primecell PL330 dma controller.
>>>
>>> Signed-off-by: Peter A. G. Crosthwaite<peter.crosthwaite@petalogix.com>
>>> Signed-off-by: Kirill Batuzov<batuzovk@ispras.ru>
>>> ---
>>> [..snip..]
>>>
>>> +static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>>> len)
>>> +{
>>> +    uint8_t chan_id;
>>> +    uint8_t ns;
>>> +    uint32_t pc;
>>> +    PL330Chan *s;
>>> +
>>> +    DB_PRINT("\n");
>>> +
>>> +    if (!ch->is_manager) {
>>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>>
>> According to description its more likely to cause UNDEF_INSTR here, not
>> OPERAND_INVALID
> 
> Ok
> 
>>>
>>> +        return;
>>> +    }
>>> +    ns = !!(opcode&  2);
>>> [..snip..]
>>>
>>> +
>>> +static Property pl330_properties[] = {
>>> +    DEFINE_PROP_UINT32("cfg0", PL330, cfg[0], 0),
>>> +    DEFINE_PROP_UINT32("cfg1", PL330, cfg[1], 0),
>>> +    DEFINE_PROP_UINT32("cfg2", PL330, cfg[2], 0),
>>> +    DEFINE_PROP_UINT32("cfg3", PL330, cfg[3], 0),
>>> +    DEFINE_PROP_UINT32("cfg4", PL330, cfg[4], 0),
>>> +    DEFINE_PROP_UINT32("cfg5", PL330, cfg[5], 0),
>>> +    DEFINE_PROP_END_OF_LIST(),
>>> +};
>>> +
>>> +static void pl330_class_init(ObjectClass *klass, void *data)
>>> +{
>>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>>> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
>>> +
>>> +    k->init = pl330_init;
>>> +    dc->reset = pl330_reset;
>>> +    dc->props = pl330_properties;
>>> +}
>>> +
>>> +static TypeInfo pl330_info = {
>>> +    .name           = "pl330",
>>> +    .parent         = TYPE_SYS_BUS_DEVICE,
>>> +    .instance_size  = sizeof(PL330),
>>> +    .class_init      = pl330_class_init,
>>> +};
>>> +
>>
>> I think Andreas requires all static TypeInfos to have const qualifier and
>> their names to comply with "<device>_type_info" naming convention. I'm not
>> sure about this though.
>>
> 
> Ok

Yes, the lack of const in uses such as these has historic reasons and
keeps propagating. If you touch it anyway, ..._type_info would be more
self-describing but not a hard requirement.
Thanks for keeping eyes open, Igor. :)

>>> +static void pl330_register_types(void)
>>> +{
>>> +    type_register_static(&pl330_info);
>>> +}
>>> +
>>> +type_init(pl330_register_types)
>>
>>
>> And it still has no save/load support, it is really mandatory for all new
>> devices. I can recall that one of the maintainers wrote a while ago that
>> every device at least needs to mark itself as non-migratable, if it doesn't
>> implement a proper vmstate.
>>
> Ok, ccing  Andreas

Not my requirement but Peter's (cc'ing). Usually it's really trivial
adding a handful of fields to the VMSD, so I can understand though.

Regards,
Andreas

>> We used this PL330 implementation to transfer sound data in our emulated
>> exynos-based system. It works, but very slow, because the way real hardware
>> performs data transfers is not optimal for emulation.
>>
> 
> Thats another battle for another day,
> 
>> Tested by: Igor Mitsyanko <i.mitsyanko@samsung.com>
>>
> 
> Sweet,
> 
> Ill roll a V5 soon, but im guessing PMM will do a review cycle here as
> well, so ill give it a few days.
> 
> Regards,
> Peter
Peter Maydell - June 19, 2012, 10:55 a.m.
On 19 June 2012 11:17, Andreas Färber <afaerber@suse.de> wrote:
> Am 19.06.2012 08:40, schrieb Peter Crosthwaite:
>> On Tue, Jun 19, 2012 at 12:33 AM, Igor Mitsyanko
>>> And it still has no save/load support, it is really mandatory for all new
>>> devices. I can recall that one of the maintainers wrote a while ago that
>>> every device at least needs to mark itself as non-migratable, if it doesn't
>>> implement a proper vmstate.
>>>
>> Ok, ccing  Andreas
>
> Not my requirement but Peter's (cc'ing). Usually it's really trivial
> adding a handful of fields to the VMSD, so I can understand though.

Yes, it's not difficult and there's no easy way to detect "attempted
to migrate a machine using a device that failed to implement save/load"
so I prefer to be strict about not letting new devices into the tree
that don't implement this. (Proper implementation, please, not just
marking the device as non-migratable.)

-- PMM
Peter Maydell - June 28, 2012, 4:31 p.m.
On 18 June 2012 01:42, Peter A. G. Crosthwaite
<peter.crosthwaite@petalogix.com> wrote:
> Device model for Primecell PL330 dma controller.
>
> Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
> Signed-off-by: Kirill Batuzov <batuzovk@ispras.ru>
> ---
> changed from v3:
> rebased against Makefile refactor
> changed from v2 (in order of diff appearance):
> GPL version changed to v2 or later
> Register and field names changed to match TRM
> id field corrected
> PL330ChanState enum corrected
> target_phys_addr_t usages removed
> identifier camel casing and underscoring fixed
> data fifo reworked (uses head ptr and a occupancy count rather than head & tail ptrs)
> pl330_insn_from_queue->pl330_queue_remove_insn
> Numerous typos in comments corrected as per review
> g_malloc0(sizeof(a) * b) -> g_new
> pl330_fifo_inc: changed to accept PL330Fifo struct as argument (rather than buf_size)
> pl330_fifo_push/get: rollback on overflow mechanism replaced with pre-check
> pl330_queue_find_insn: fixed ugly if statement whitespace (PMM review)
> pl330_dmaaddh: fixed arugment reversal
> pl330_dmaaddh: asserted on !manager
> "ns": change to be and behave like a boolean globally
> pl330_dmald: force num=1 when PL330_SINGLE
> pl330_dma_kill: put in correct alphabet order
> pl330_dmarmb/wmb:implemented
> pl330_exec_insn: asserted insn->size not too big
> pl330_exec_insn: enabled stall mechanism outside exec state (needed for rmb/wmb)
> pl330_debug_exec: assert fault type on a fault
> pl330_iomem_write: removed aligmnent assertion
> pl330_iomem_write: corrected ev_status and irq_status logic
> pl330_iomem_read_imp: fixed some incorrect bitfield positions (PMM review)
> pl330_reset: stopped timer
> pl330_init: removed redundant reset
> changed from v1:
> GPLv2 license
> some code formatting fixes
>
>  hw/arm/Makefile.objs |    1 +
>  hw/pl330.c           | 1411 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 1412 insertions(+), 0 deletions(-)
>  create mode 100644 hw/pl330.c
>
> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
> index a0ff6a6..b28e5b7 100644
> --- a/hw/arm/Makefile.objs
> +++ b/hw/arm/Makefile.objs
> @@ -7,6 +7,7 @@ obj-y += cadence_ttc.o
>  obj-y += cadence_gem.o
>  obj-y += xilinx_zynq.o zynq_slcr.o
>  obj-y += arm_gic.o
> +obj-y += pl330.o
>  obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
>  obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
>  obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
> diff --git a/hw/pl330.c b/hw/pl330.c
> new file mode 100644
> index 0000000..bc6bc35
> --- /dev/null
> +++ b/hw/pl330.c
> @@ -0,0 +1,1411 @@
> +/*
> + * ARM PrimeCell PL330 DMA Controller
> + *
> + * Copyright (c) 2009 Samsung Electronics.
> + * Contributed by Kirill Batuzov <batuzovk@ispras.ru>
> + * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
> + * Copyright (c) 2012 PetaLogix Pty Ltd.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; version 2 or later.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "sysbus.h"
> +#include "qemu-timer.h"
> +
> +#ifdef PL330_ERR_DEBUG
> +#define DB_PRINT(...) do { \
> +    fprintf(stderr,  ": %s: ", __func__); \
> +    fprintf(stderr, ## __VA_ARGS__); \
> +    } while (0);
> +#else
> +    #define DB_PRINT(...)
> +#endif
> +
> +#define PL330_CHAN_NUM              8
> +#define PL330_PERIPH_NUM            32
> +#define PL330_MAX_BURST_LEN         128
> +#define PL330_INSN_MAXSIZE          6
> +
> +#define PL330_FIFO_OK               0
> +#define PL330_FIFO_STALL            1
> +#define PL330_FIFO_ERR              (-1)
> +
> +#define PL330_FAULT_UNDEF_INSTR             (1 <<  0)
> +#define PL330_FAULT_OPERAND_INVALID         (1 <<  1)
> +#define PL330_FAULT_DMAGO_ERR               (1 <<  4)
> +#define PL330_FAULT_EVENT_ERR               (1 <<  5)
> +#define PL330_FAULT_CH_PERIPH_ERR           (1 <<  6)
> +#define PL330_FAULT_CH_RDWR_ERR             (1 <<  7)
> +#define PL330_FAULT_ST_DATA_UNAVAILABLE     (1 << 12)
> +#define PL330_FAULT_FIFOEMPTY_ERR           (1 << 13)
> +#define PL330_FAULT_INSTR_FETCH_ERR         (1 << 16)
> +#define PL330_FAULT_DATA_WRITE_ERR          (1 << 17)
> +#define PL330_FAULT_DATA_READ_ERR           (1 << 18)
> +#define PL330_FAULT_DBG_INSTR               (1 << 30)
> +#define PL330_FAULT_LOCKUP_ERR              (1 << 31)
> +
> +#define PL330_UNTAGGED              0xff
> +
> +#define PL330_SINGLE                0x0
> +#define PL330_BURST                 0x1
> +
> +#define PL330_WATCHDOG_LIMIT        1024
> +
> +/* IOMEM mapped registers */
> +#define PL330_REG_DSR               0x000
> +#define PL330_REG_DPC               0x004
> +#define PL330_REG_INTEN             0x020
> +#define PL330_REG_INT_EVENT_RIS     0x024
> +#define PL330_REG_INTMIS            0x028
> +#define PL330_REG_INTCLR            0x02C
> +#define PL330_REG_FSRD              0x030
> +#define PL330_REG_FSRC              0x034
> +#define PL330_REG_FTRD              0x038
> +#define PL330_REG_FTR_BASE          0x040
> +#define PL330_REG_CSR_BASE          0x100
> +#define PL330_REG_CPC_BASE          0x104
> +#define PL330_REG_CHANCTRL          0x400
> +#define PL330_REG_DBGSTATUS         0xD00
> +#define PL330_REG_DBGCMD            0xD04
> +#define PL330_REG_DBGINST0          0xD08
> +#define PL330_REG_DBGINST1          0xD0C
> +#define PL330_REG_CR0_BASE          0xE00
> +#define PL330_REG_PERIPH_ID         0xFE0
> +
> +#define PL330_IOMEM_SIZE    0x1000
> +
> +static const uint32_t pl330_id[] = {
> +    0x30, 0x13, 0x04, 0x00, 0x0D, 0xF0, 0x05, 0xB1
> +};

This is claiming to be an r0p0, which is an odd choice.
The current most recent version is r1p1, which would be
0x24 where you have 0x04.

> +/* DMA chanel states as they are described in PL330 Technical Reference Manual

"channel"

> + * Most of them will not be used in emulation.
> + */
> +typedef enum  {
> +    pl330_chan_stopped = 0,
> +    pl330_chan_executing = 1,
> +    pl330_chan_cache_miss = 2,
> +    pl330_chan_updating_pc = 3,
> +    pl330_chan_waiting_event = 4,
> +    pl330_chan_at_barrier = 5,
> +    pl330_chan_queue_busy = 6,
> +    pl330_chan_waiting_periph = 7,
> +    pl330_chan_killing = 8,
> +    pl330_chan_completing = 9,
> +    pl330_chan_fault_completing = 14,
> +    pl330_chan_fault = 15,
> +} PL330ChanState;
> +
> +typedef struct PL330 PL330;
> +
> +typedef struct PL330Chan {
> +    uint32_t src;
> +    uint32_t dst;
> +    uint32_t pc;
> +    uint32_t control;
> +    uint32_t status;
> +    uint32_t lc[2];
> +    uint32_t fault_type;
> +
> +    bool ns;
> +    bool is_manager;
> +    uint8_t request_flag;
> +    uint8_t wakeup;
> +    uint8_t wfp_sbp;
> +
> +    PL330ChanState state;
> +    uint8_t stall;
> +
> +    PL330 *parent;
> +    uint8_t tag;
> +    uint32_t watchdog_timer;
> +} PL330Chan;
> +
> +typedef struct PL330Fifo {
> +    uint8_t *buf;
> +    uint8_t *tag;
> +    int s, n;

Some of these single-letter struct member names make the code
a bit opaque...

> +    int buf_size;
> +} PL330Fifo;
> +
> +typedef struct PL330QueueEntry {
> +    PL330Chan *c;
> +    target_phys_addr_t addr;

This should probably not be a target_phys_addr_t -- it is
part of the device's internal state and needs to be modelled at
the correct fixed width. (I have patches pending which switch
target_phys_addr_t from 32 to 64 bit for ARM.)

> +    int len;
> +    int n;
> +    int inc;
> +    int z;
> +    uint8_t tag;
> +    uint8_t seqn;
> +} PL330QueueEntry;
> +
> +typedef struct PL330Queue {
> +    PL330QueueEntry *queue;
> +    int queue_size;
> +    uint8_t *lo_seqn;
> +    uint8_t *hi_seqn;
> +} PL330Queue;
> +
> +struct PL330 {
> +    SysBusDevice busdev;
> +    MemoryRegion iomem;
> +
> +    PL330Chan manager;
> +    PL330Chan *chan;
> +    PL330Fifo fifo;
> +    PL330Queue read_queue;
> +    PL330Queue write_queue;
> +
> +    /* Config registers. cfg[5] = CfgDn. */
> +    uint32_t inten;
> +    uint32_t int_status;
> +    uint32_t ev_status;
> +    uint32_t cfg[6];
> +    uint32_t dbg[2];
> +    uint8_t debug_status;
> +
> +    qemu_irq irq_abort;
> +    qemu_irq *irq;
> +
> +    uint8_t num_faulting;
> +
> +    int chan_num;
> +    int periph_num;
> +    unsigned int event_num;
> +
> +    QEMUTimer *timer; /* is used for restore dma. */
> +    int8_t periph_busy[PL330_PERIPH_NUM];
> +};
> +
> +typedef struct PL330InsnDesc {
> +    /* OPCODE of the instruction */
> +    uint8_t opcode;
> +    /* Mask so we can select several sibling instructions, such as
> +       DMALD, DMALDS and DMALDB */
> +    uint8_t opmask;
> +    /* Size of instruction in bytes */
> +    uint8_t size;
> +    /* Interpreter */
> +    void (*exec)(PL330Chan *, uint8_t opcode, uint8_t *args, int len);
> +} PL330InsnDesc;
> +
> +
> +/* MFIFO Implementation
> + *
> + * MFIFO is implemented as a cyclic buffer of BUF_SIZE size. Tagged bytes are
> + * stored in this buffer. Data is stored in BUF field, tags - in the
> + * corresponding array elements of TAG field.
> + */
> +
> +/* Initialize queue. */
> +static void pl330_fifo_init(PL330Fifo *s, uint32_t size)
> +{
> +    s->buf = g_malloc0(size);
> +    s->tag = g_malloc0(size);
> +    s->buf_size = size;
> +}
> +
> +/* Cyclic increment */
> +static inline int pl330_fifo_inc(PL330Fifo *s, int x)
> +{
> +    return (x + 1) % s->buf_size;
> +}
> +
> +/* Number of empty bytes in MFIFO */
> +static inline int pl330_fifo_num_free(PL330Fifo *s)
> +{
> +    return s->buf_size - s->n;
> +}
> +
> +/* Push LEN bytes of data stored in BUF to MFIFO and tag it with TAG.
> + * Zero returned on success, PL330_FIFO_STALL if there is no enough free
> + * space in MFIFO to store requested amount of data. If push was unsuccessful
> + * no data is stored to MFIFO.
> + */
> +static int pl330_fifo_push(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
> +{
> +    int i;
> +
> +    if (s->buf_size - s->n < len) {
> +        return PL330_FIFO_STALL;
> +    }
> +    for (i = 0; i < len; i++) {
> +        int push_idx = (s->s + i) % s->buf_size;
> +        s->buf[push_idx] = buf[i];
> +        s->tag[push_idx] = tag;
> +    }
> +    s->n += len;
> +    return PL330_FIFO_OK;
> +}
> +
> +/* Get LEN bytes of data from MFIFO and store it to BUF. Tag value of each
> + * byte is verified. Zero returned on success, PL330_FIFO_ERR on tag mismatch
> + * and PL330_FIFO_STALL if there is no enough data in MFIFO. If get was
> + * unsuccessful no data is removed from MFIFO.
> + */
> +static int pl330_fifo_get(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
> +{
> +    int i;
> +
> +    if (s->n < len) {
> +        return PL330_FIFO_STALL;
> +    }
> +    for (i = 0; i < len; i++) {
> +        if (s->tag[s->s] == tag) {
> +            int get_idx = (s->s + i) % s->buf_size;
> +            buf[i] = s->buf[get_idx];
> +        } else { /* Tag mismatch - Rollback transaction */
> +            return PL330_FIFO_ERR;
> +        }
> +    }
> +    s->s = (s->s + len) % s->buf_size;
> +    s->n -= len;
> +    return PL330_FIFO_OK;
> +}
> +
> +/* Reset MFIFO. This completely erases all data in it. */
> +static inline void pl330_fifo_reset(PL330Fifo *s)
> +{
> +    s->s = 0;
> +    s->n = 0;
> +}
> +
> +/* Return tag of the first byte stored in MFIFO. If MFIFO is empty
> +   PL330_UNTAGGED is returned. */
> +static inline uint8_t pl330_fifo_tag(PL330Fifo *s)
> +{
> +    return (!s->n) ? PL330_UNTAGGED : s->tag[s->s];
> +}
> +
> +/* Returns non-zero if tag TAG is present in fifo or zero otherwise */
> +static int pl330_fifo_has_tag(PL330Fifo *s, uint8_t tag)
> +{
> +    int i, n;
> +
> +    i = s->s;
> +    for (n = 0; n < s->n; n++) {
> +        if (s->tag[i] == tag) {
> +            return 1;
> +        }
> +        i = pl330_fifo_inc(s, i);
> +    }
> +    return 0;
> +}
> +
> +/* Remove all entry tagged with TAG from MFIFO */

"entries"

> +static void pl330_fifo_tagged_remove(PL330Fifo *s, uint8_t tag)
> +{
> +    int i, t, n;
> +
> +    t = i = s->s;
> +    for (n = 0; n < s->n; n++) {
> +        if (s->tag[i] != tag) {
> +            s->buf[t] = s->buf[i];
> +            s->tag[t] = s->tag[i];
> +            t = pl330_fifo_inc(s, t);
> +        } else {
> +            s->n = s->n - 1;
> +        }
> +        i = pl330_fifo_inc(s, i);
> +    }
> +}
> +
> +/* Read-Write Queue implementation
> + *
> + * A Read-Write Queue stores up to QUEUE_SIZE instructions (loads or stores).
> + * Each instruction is described by source (for loads) or destination (for
> + * stores) address ADDR, width of data to be loaded/stored LEN, number of
> + * stores/loads to be performed N, INC bit, Z bit and TAG to identify channel
> + * this instruction belongs to. Queue does not store any information about
> + * nature of the instruction: is it load or store. PL330 has different queues
> + * for loads and stores so this is already known at the top level where it
> + * matters.
> + *
> + * Queue works as FIFO for instructions with equivalent tags, but can issue
> + * instructions with different tags in arbitrary order. SEQN field attached to
> + * each instruction helps to achieve this. For each TAG queue contains
> + * instructions with consecutive SEQN values ranging from LO_SEQN[TAG] to
> + * HI_SEQN[TAG]-1 inclusive. SEQN is 8-bit unsigned integer, so SEQN=255 is
> + * followed by SEQN=0.
> + *
> + * Z bit indicates that zeroes should be stored. No MFIFO fetches are performed
> + * in this case.
> + */
> +
> +static void pl330_queue_reset(PL330Queue *s)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        s->queue[i].tag = PL330_UNTAGGED;
> +    }
> +}
> +
> +/* Initialize queue */
> +static void pl330_queue_init(PL330Queue *s, int size, int channum)
> +{
> +    s->queue = (PL330QueueEntry *)g_new0(PL330QueueEntry, size);
> +    s->lo_seqn = (uint8_t *)g_malloc0(channum);
> +    s->hi_seqn = (uint8_t *)g_malloc0(channum);
> +    s->queue_size = size;
> +}
> +
> +/* Returns pointer to an empty slot or NULL if queue is full */
> +static PL330QueueEntry *pl330_queue_find_empty(PL330Queue *s)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag == PL330_UNTAGGED) {
> +            return &s->queue[i];
> +        }
> +    }
> +    return NULL;
> +}
> +
> +/* Put instruction to queue.
> +   Return value:
> +     - zero - OK
> +     - non-zero - queue is full */

This comment isn't in the same
/* Text
 * Text
 * Text
 */
style as the others.

> +static int pl330_queue_put_insn(PL330Queue *s, target_phys_addr_t addr,
> +                               int len, int n, int inc, int z, uint8_t tag,
> +                               PL330Chan *c)
> +{
> +    PL330QueueEntry *entry = pl330_queue_find_empty(s);
> +
> +    if (!entry) {
> +        return 1;
> +    }
> +    entry->c = c;
> +    entry->tag = tag;
> +    entry->addr = addr;
> +    entry->len = len;
> +    entry->n = n;
> +    entry->z = z;
> +    entry->inc = inc;
> +    entry->seqn = s->hi_seqn[tag];
> +    s->hi_seqn[tag]++;
> +    return 0;
> +}
> +
> +/* Returns a pointer to queue slot containing instruction which satisfies
> +   following conditions:
> +    - it has valid tag value (not PL330_UNTAGGED)
> +    - it can be issued without violating queue logic (see above)
> +    - if TAG argument is not PL330_UNTAGGED this instruction has tag value
> +      equivalent to the argument TAG value.
> +   If such instruction cannot be found NULL is returned. */
> +static PL330QueueEntry *pl330_queue_find_insn(PL330Queue *s, uint8_t tag)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag != PL330_UNTAGGED) {
> +            if (s->queue[i].seqn == s->lo_seqn[s->queue[i].tag] &&
> +                    (s->queue[i].tag == tag || tag == PL330_UNTAGGED ||
> +                    s->queue[i].z)) {
> +                return &s->queue[i];
> +            }
> +        }
> +    }
> +    return NULL;
> +}
> +
> +/* Removes instruction from queue. */
> +static inline void pl330_queue_remove_insn(PL330Queue *s, PL330QueueEntry *e)
> +{
> +    s->lo_seqn[e->tag]++;
> +    e->tag = PL330_UNTAGGED;
> +}
> +
> +/* Removes all instructions tagged with TAG from queue. */
> +static inline void pl330_queue_remove_tagged(PL330Queue *s, uint8_t tag)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag == tag) {
> +            s->queue[i].tag = PL330_UNTAGGED;
> +        }
> +    }
> +}
> +
> +
> +/* DMA instruction execution engine */
> +
> +/* Moves DMA channel to the FAULT state and updates it's status. */
> +static inline void pl330_fault(PL330Chan *ch, uint32_t flags)
> +{
> +    DB_PRINT("ch: %p, flags: %x\n", ch, flags);
> +    ch->fault_type |= flags;
> +    if (ch->state == pl330_chan_fault) {
> +        return;
> +    }
> +    ch->state = pl330_chan_fault;
> +    ch->parent->num_faulting++;
> +    if (ch->parent->num_faulting == 1) {
> +        DB_PRINT("abort interrupt raised\n");
> +        qemu_irq_raise(ch->parent->irq_abort);
> +    }
> +}
> +
> +/*
> + * For information about instructions see PL330 Technical Reference Manual.
> + *
> + * Arguments:
> + *   CH - channel executing the instruction
> + *   OPCODE - opcode
> + *   ARGS - array of 8-bit arguments
> + *   LEN - number of elements in ARGS array
> + */
> +static void pl330_dmaaddh(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint16_t im = (((uint16_t)args[1]) << 8) | ((uint16_t)args[0]);
> +    uint8_t ra = (opcode >> 1) & 1;
> +
> +    if (ch->is_manager) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ra) {
> +        ch->dst += im;
> +    } else {
> +        ch->src += im;
> +    }
> +}
> +
> +static void pl330_dmaend(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    if (ch->state == pl330_chan_executing && !ch->is_manager) {
> +        /* Wait for all transfers to complete */
> +        if (pl330_fifo_has_tag(&ch->parent->fifo, ch->tag) ||
> +            pl330_queue_find_insn(&ch->parent->read_queue, ch->tag) != NULL ||
> +            pl330_queue_find_insn(&ch->parent->write_queue, ch->tag) != NULL) {
> +
> +            ch->stall = 1;
> +            return;
> +        }
> +    }
> +    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
> +    ch->state = pl330_chan_stopped;
> +}
> +
> +static void pl330_dmaflushp(PL330Chan *ch, uint8_t opcode,
> +                                            uint8_t *args, int len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    /* Do nothing */
> +}
> +
> +static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t chan_id;
> +    uint8_t ns;
> +    uint32_t pc;
> +    PL330Chan *s;
> +
> +    DB_PRINT("\n");
> +
> +    if (!ch->is_manager) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ns = !!(opcode & 2);
> +    chan_id = args[0] & 7;
> +    if ((args[0] >> 3)) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (chan_id >= ch->parent->chan_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    pc = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
> +         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
> +    if (ch->parent->chan[chan_id].state != pl330_chan_stopped) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !ns) {
> +        pl330_fault(ch, PL330_FAULT_DMAGO_ERR);
> +        return;
> +    }
> +    s = &ch->parent->chan[chan_id];
> +    s->ns = ns;
> +    s->pc = pc;
> +    s->state = pl330_chan_executing;
> +}
> +
> +static void pl330_dmald(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint32_t size, num, inc;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    if (bs == 1 && ch->request_flag == PL330_SINGLE) {
> +        num = 1;
> +    } else {
> +        num = ((ch->control >> 4) & 0xf) + 1;
> +    }
> +    size = (uint32_t)1 << ((ch->control >> 1) & 0x7);
> +    inc = ch->control & 1;
> +    ch->src &= ~(size - 1);
> +    ch->stall = pl330_queue_put_insn(&ch->parent->read_queue, ch->src,
> +                                    size, num, inc, 0, ch->tag, ch);
> +    if (inc & !ch->stall) {
> +        ch->src += size * num;
> +    }
> +}
> +
> +static void pl330_dmaldp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    pl330_dmald(ch, opcode, args, len);
> +}
> +
> +static void pl330_dmalp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t lc = (opcode & 2) >> 1;
> +
> +    ch->lc[lc] = args[0];
> +}
> +
> +static void pl330_dmakill(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    if (ch->state == pl330_chan_fault ||
> +        ch->state == pl330_chan_fault_completing) {
> +        /* This is the only way for a channel to leave the faulting state */
> +        ch->fault_type = 0;
> +        ch->parent->num_faulting--;
> +        if (ch->parent->num_faulting == 0) {
> +            DB_PRINT("abort interrupt lowered\n");
> +            qemu_irq_lower(ch->parent->irq_abort);
> +        }
> +    }
> +    ch->state = pl330_chan_killing;
> +    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
> +    ch->state = pl330_chan_stopped;
> +}
> +
> +static void pl330_dmalpend(PL330Chan *ch, uint8_t opcode,
> +                                    uint8_t *args, int len)
> +{
> +    uint8_t nf = (opcode & 0x10) >> 4;
> +    uint8_t bs = opcode & 3;
> +    uint8_t lc = (opcode & 4) >> 2;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    if (!nf || ch->lc[lc]) {
> +        if (nf) {
> +            ch->lc[lc]--;
> +        }
> +        ch->pc -= args[0];
> +        ch->pc -= len + 1;
> +        /* "ch->pc -= args[0] + len + 1" is incorrect when args[0] == 256 */
> +    }
> +}
> +
> +
> +static void pl330_dmamov(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t rd = args[0] & 7;
> +    uint32_t im;
> +
> +    if ((args[0] >> 3)) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    im = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
> +         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
> +    switch (rd) {
> +    case 0:
> +        ch->src = im;
> +        break;
> +    case 1:
> +        ch->control = im;
> +        break;
> +    case 2:
> +        ch->dst = im;
> +        break;
> +    default:
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +}
> +
> +static void pl330_dmanop(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    /* NOP is NOP. */
> +}
> +
> +static void pl330_dmarmb(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +   if (pl330_queue_find_insn(&ch->parent->read_queue, ch->tag)) {
> +        ch->state = pl330_chan_at_barrier;
> +        ch->stall = 1;
> +        return;
> +    }
> +}
> +
> +static void pl330_dmasev(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t ev_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ev_id = (args[0] >> 3) & 0x1f;
> +    if (ev_id >= ch->parent->event_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
> +        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
> +        return;
> +    }
> +    if (ch->parent->inten & (1 << ev_id)) {
> +        ch->parent->int_status |= (1 << ev_id);
> +        DB_PRINT("event interrupt raised %d\n", ev_id);
> +        qemu_irq_raise(ch->parent->irq[ev_id]);
> +    } else {
> +        ch->parent->ev_status |= (1 << ev_id);
> +    }
> +}
> +
> +static void pl330_dmast(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint32_t size, num, inc;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    num = ((ch->control >> 18) & 0xf) + 1;
> +    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
> +    inc = (ch->control >> 14) & 1;
> +    ch->dst &= ~(size - 1);
> +    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
> +                                    size, num, inc, 0, ch->tag, ch);
> +    if (inc) {
> +        ch->dst += size * num;
> +    }
> +}
> +
> +static void pl330_dmastp(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    pl330_dmast(ch, opcode, args, len);
> +}
> +
> +static void pl330_dmastz(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint32_t size, num, inc;
> +
> +    num = ((ch->control >> 18) & 0xf) + 1;
> +    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
> +    inc = (ch->control >> 14) & 1;
> +    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
> +                                    size, num, inc, 1, ch->tag, ch);
> +    if (inc) {
> +        ch->dst += size * num;
> +    }
> +}
> +
> +static void pl330_dmawfe(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t ev_id;
> +
> +    if (args[0] & 5) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ev_id = (args[0] >> 3) & 0x1f;
> +    if (ev_id >= ch->parent->event_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
> +        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
> +        return;
> +    }
> +    ch->wakeup = ev_id;
> +    ch->state = pl330_chan_waiting_event;
> +    if (ch->parent->inten & ch->parent->ev_status & 1 << ev_id) {
> +        ch->parent->ev_status &= ~(1 << ev_id);
> +    } else {
> +        ch->stall = 1;
> +    }
> +}
> +
> +static void pl330_dmawfp(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    switch (bs) {
> +    case 0: /* S */
> +        ch->request_flag = PL330_SINGLE;
> +        ch->wfp_sbp = 0;
> +        break;
> +    case 1: /* P */
> +        ch->request_flag = PL330_BURST;
> +        ch->wfp_sbp = 2;
> +        break;
> +    case 2: /* B */
> +        ch->request_flag = PL330_BURST;
> +        ch->wfp_sbp = 1;
> +        break;
> +    default:
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +
> +    if (ch->parent->periph_busy[periph_id]) {
> +        ch->state = pl330_chan_waiting_periph;
> +        ch->stall = 1;
> +    } else if (ch->state == pl330_chan_waiting_periph) {
> +        ch->state = pl330_chan_executing;
> +    }
> +}
> +
> +static void pl330_dmawmb(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    if (pl330_queue_find_insn(&ch->parent->write_queue, ch->tag)) {
> +        ch->state = pl330_chan_at_barrier;
> +        ch->stall = 1;
> +        return;
> +    }
> +}
> +
> +/* NULL terminated array of the instruction descriptions. */
> +static const PL330InsnDesc insn_desc[] = {
> +    { .opcode = 0x54, .opmask = 0xFD, .size = 3, .exec = pl330_dmaaddh, },
> +    { .opcode = 0x00, .opmask = 0xFF, .size = 1, .exec = pl330_dmaend, },
> +    { .opcode = 0x35, .opmask = 0xFF, .size = 2, .exec = pl330_dmaflushp, },
> +    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
> +    { .opcode = 0x04, .opmask = 0xFC, .size = 1, .exec = pl330_dmald, },
> +    { .opcode = 0x25, .opmask = 0xFD, .size = 2, .exec = pl330_dmaldp, },
> +    { .opcode = 0x20, .opmask = 0xFD, .size = 2, .exec = pl330_dmalp, },
> +    /* dmastp  must be before dmalpend in this list, because their maps
> +     * are overlapping
> +     */
> +    { .opcode = 0x29, .opmask = 0xFD, .size = 2, .exec = pl330_dmastp, },
> +    { .opcode = 0x28, .opmask = 0xE8, .size = 2, .exec = pl330_dmalpend, },
> +    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
> +    { .opcode = 0xBC, .opmask = 0xFF, .size = 6, .exec = pl330_dmamov, },
> +    { .opcode = 0x18, .opmask = 0xFF, .size = 1, .exec = pl330_dmanop, },
> +    { .opcode = 0x12, .opmask = 0xFF, .size = 1, .exec = pl330_dmarmb, },
> +    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
> +    { .opcode = 0x08, .opmask = 0xFC, .size = 1, .exec = pl330_dmast, },
> +    { .opcode = 0x0C, .opmask = 0xFF, .size = 1, .exec = pl330_dmastz, },
> +    { .opcode = 0x36, .opmask = 0xFF, .size = 2, .exec = pl330_dmawfe, },
> +    { .opcode = 0x30, .opmask = 0xFC, .size = 2, .exec = pl330_dmawfp, },
> +    { .opcode = 0x13, .opmask = 0xFF, .size = 1, .exec = pl330_dmawmb, },
> +    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
> +};
> +
> +/* Instructions which can be issued via debug registers. */
> +static const PL330InsnDesc debug_insn_desc[] = {
> +    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
> +    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
> +    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
> +    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
> +};
> +
> +static inline const PL330InsnDesc *pl330_fetch_insn(PL330Chan *ch)
> +{
> +    uint8_t opcode;
> +    int i;
> +
> +    cpu_physical_memory_read(ch->pc, &opcode, 1);
> +    for (i = 0; insn_desc[i].size; i++) {
> +        if ((opcode & insn_desc[i].opmask) == insn_desc[i].opcode) {
> +            return &insn_desc[i];
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static inline void pl330_exec_insn(PL330Chan *ch, const PL330InsnDesc *insn)
> +{
> +    uint8_t buf[PL330_INSN_MAXSIZE];
> +
> +    assert(insn->size <= PL330_INSN_MAXSIZE);
> +    cpu_physical_memory_read(ch->pc, buf, insn->size);
> +    insn->exec(ch, buf[0], &buf[1], insn->size - 1);
> +}
> +
> +static inline void pl330_update_pc(PL330Chan *ch,
> +                                   const PL330InsnDesc *insn)
> +{
> +    ch->pc += insn->size;
> +}
> +
> +/* Try to execute current instruction in channel CH. Number of executed
> +   instructions is returned (0 or 1). */
> +static int pl330_chan_exec(PL330Chan *ch)
> +{
> +    const PL330InsnDesc *insn;
> +
> +    if (ch->state != pl330_chan_executing &&
> +            ch->state != pl330_chan_waiting_periph &&
> +            ch->state != pl330_chan_at_barrier &&
> +            ch->state != pl330_chan_waiting_event) {
> +        DB_PRINT("%d\n", ch->state);
> +        return 0;
> +    }
> +    ch->stall = 0;
> +    insn = pl330_fetch_insn(ch);
> +    if (!insn) {
> +        DB_PRINT("pl330 undefined instruction\n");
> +        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR);
> +        return 0;
> +    }
> +    pl330_exec_insn(ch, insn);
> +    if (!ch->stall) {
> +        pl330_update_pc(ch, insn);
> +        ch->watchdog_timer = 0;
> +        return 1;
> +    /* WDT only active in exec state */
> +    } else if (ch->state == pl330_chan_executing) {
> +        ch->watchdog_timer++;
> +        if (ch->watchdog_timer >= PL330_WATCHDOG_LIMIT) {
> +            pl330_fault(ch, PL330_FAULT_LOCKUP_ERR);
> +        }
> +    }
> +    return 0;
> +}
> +
> +/* Try to execute 1 instruction in each channel, one instruction from read
> +   queue and one instruction from write queue. Number of successfully executed
> +   instructions is returned. */
> +static int pl330_exec_cycle(PL330Chan *channel)
> +{
> +    PL330 *s = channel->parent;
> +    PL330QueueEntry *q;
> +    int i;
> +    int num_exec = 0;
> +    int fifo_res = 0;
> +    uint8_t buf[PL330_MAX_BURST_LEN];
> +
> +    /* Execute one instruction in each channel */
> +    num_exec += pl330_chan_exec(channel);
> +
> +    /* Execute one instruction from read queue */
> +    q = pl330_queue_find_insn(&s->read_queue, PL330_UNTAGGED);
> +    if (q != NULL && q->len <= pl330_fifo_num_free(&s->fifo)) {
> +        cpu_physical_memory_read(q->addr, buf, q->len);
> +        fifo_res = pl330_fifo_push(&s->fifo, buf, q->len, q->tag);
> +        if (fifo_res == PL330_FIFO_OK) {
> +            if (q->inc) {
> +                q->addr += q->len;
> +            }
> +            q->n--;
> +            if (!q->n) {
> +                pl330_queue_remove_insn(&s->read_queue, q);
> +            }
> +            num_exec++;
> +        }
> +    }
> +
> +    /* Execute one instruction from write queue. */
> +    q = pl330_queue_find_insn(&s->write_queue, pl330_fifo_tag(&s->fifo));
> +    if (q != NULL) {
> +        if (q->z) {
> +            for (i = 0; i < q->len; i++) {
> +                buf[i] = 0;
> +            }
> +        } else {
> +            fifo_res = pl330_fifo_get(&s->fifo, buf, q->len, q->tag);
> +        }
> +        if (fifo_res == PL330_FIFO_OK || q->z) {
> +            cpu_physical_memory_write(q->addr, buf, q->len);
> +            if (q->inc) {
> +                q->addr += q->len;
> +            }
> +            num_exec++;
> +        } else if (fifo_res == PL330_FIFO_STALL) {
> +            pl330_fault(q->c, PL330_FAULT_FIFOEMPTY_ERR);
> +        }
> +        q->n--;
> +        if (!q->n) {
> +            pl330_queue_remove_insn(&s->write_queue, q);
> +        }
> +    }
> +
> +    return num_exec;
> +}
> +
> +static int pl330_exec_channel(PL330Chan *channel)
> +{
> +    int insr_exec = 0;
> +
> +    /* TODO: Is it all right to execute everything or should we do per-cycle
> +       simulation? */
> +    while (pl330_exec_cycle(channel)) {
> +        insr_exec++;
> +    }
> +
> +    /* Detect deadlock */
> +    if (channel->state == pl330_chan_executing) {
> +        pl330_fault(channel, PL330_FAULT_LOCKUP_ERR);
> +    }
> +    /* Situation when one of the queues has deadlocked but all channels
> +     * have finished their programs should be impossible.
> +     */
> +
> +    return insr_exec;
> +}
> +
> +
> +static inline void pl330_exec(PL330 *s)
> +{
> +    DB_PRINT("\n");
> +    int i, insr_exec;
> +    do {
> +        insr_exec = pl330_exec_channel(&s->manager);
> +
> +        for (i = 0; i < s->chan_num; i++) {
> +            insr_exec += pl330_exec_channel(&s->chan[i]);
> +        }
> +    } while (insr_exec);
> +}
> +
> +static void pl330_exec_cycle_timer(void *opaque)
> +{
> +    PL330 *s = (PL330 *)opaque;
> +    pl330_exec(s);
> +}
> +
> +/* Stop or restore dma operations */
> +static void pl330_dma_stop_irq(void *opaque, int irq, int level)
> +{
> +    PL330 *s = (PL330 *)opaque;
> +
> +    if (s->periph_busy[irq] != level) {
> +        s->periph_busy[irq] = level;
> +        qemu_mod_timer(s->timer, qemu_get_clock_ns(vm_clock));
> +    }
> +}
> +
> +static void pl330_debug_exec(PL330 *s)
> +{
> +    uint8_t args[5];
> +    uint8_t opcode;
> +    uint8_t chan_id;
> +    int i;
> +    PL330Chan *ch;
> +    const PL330InsnDesc *insn;
> +
> +    s->debug_status = 1;
> +    chan_id = (s->dbg[0] >>  8) & 0x07;
> +    opcode  = (s->dbg[0] >> 16) & 0xff;
> +    args[0] = (s->dbg[0] >> 24) & 0xff;
> +    args[1] = (s->dbg[1] >>  0) & 0xff;
> +    args[2] = (s->dbg[1] >>  8) & 0xff;
> +    args[3] = (s->dbg[1] >> 16) & 0xff;
> +    args[4] = (s->dbg[1] >> 24) & 0xff;
> +    DB_PRINT("chan id: %d\n", chan_id);
> +    if (s->dbg[0] & 1) {
> +        ch = &s->chan[chan_id];
> +    } else {
> +        ch = &s->manager;
> +    }
> +    insn = NULL;
> +    for (i = 0; debug_insn_desc[i].size; i++) {
> +        if ((opcode & debug_insn_desc[i].opmask) == debug_insn_desc[i].opcode) {
> +            insn = &debug_insn_desc[i];
> +        }
> +    }
> +    if (!insn) {
> +        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR | PL330_FAULT_DBG_INSTR);
> +        return ;
> +    }
> +    ch->stall = 0;
> +    insn->exec(ch, opcode, args, insn->size - 1);
> +    if (ch->fault_type) {
> +        ch->fault_type |= PL330_FAULT_DBG_INSTR;
> +    }
> +    if (ch->stall) {
> +        hw_error("pl330: stall of debug instruction not implemented\n");
> +    }
> +    s->debug_status = 0;
> +}
> +
> +
> +/* IOMEM mapped registers */
> +
> +static void pl330_iomem_write(void *opaque, target_phys_addr_t offset,
> +                              uint64_t value, unsigned size)
> +{
> +    PL330 *s = (PL330 *) opaque;
> +    uint32_t i;
> +
> +    DB_PRINT("addr: %08x data: %08x\n", offset, (unsigned)value);
> +
> +    switch (offset) {
> +    case PL330_REG_INTEN:
> +        s->inten = value;
> +        break;
> +    case PL330_REG_INTCLR:
> +        for (i = 0; i < s->event_num; i++) {
> +            if (s->int_status & s->inten & value & (1 << i)) {
> +                DB_PRINT("event interrupt lowered %d\n", i);
> +                qemu_irq_lower(s->irq[i]);
> +            }
> +        }
> +        s->ev_status &= ~(value & s->inten);
> +        s->int_status &= ~(value & ~s->inten);
> +        break;
> +    case PL330_REG_DBGCMD:
> +        if ((value & 3) == 0) {
> +            pl330_debug_exec(s);
> +            pl330_exec(s);
> +        } else {
> +            hw_error("pl330: write of illegal value %u for offset "
> +                     TARGET_FMT_plx "\n", (unsigned)value, offset);
> +        }
> +        break;
> +    case PL330_REG_DBGINST0:
> +        DB_PRINT("s->dbg[0] = %08x\n", (unsigned)value);
> +        s->dbg[0] = value;
> +        break;
> +    case PL330_REG_DBGINST1:
> +        DB_PRINT("s->dbg[1] = %08x\n", (unsigned)value);
> +        s->dbg[1] = value;
> +        break;
> +    default:
> +        hw_error("pl330: bad write offset " TARGET_FMT_plx "\n", offset);
> +        break;
> +    }
> +}
> +
> +static inline uint32_t pl330_iomem_read_imp(void *opaque,
> +        target_phys_addr_t offset)
> +{
> +    PL330 *s = (PL330 *) opaque;
> +    int chan_id;
> +    int i;
> +    uint32_t res;
> +
> +    if (offset & 3) {
> +        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +    }
> +
> +    if (offset >= PL330_REG_PERIPH_ID && offset < PL330_REG_PERIPH_ID + 32) {
> +        return pl330_id[(offset - PL330_REG_PERIPH_ID) >> 2];
> +    }
> +    if (offset >= PL330_REG_CR0_BASE && offset < PL330_REG_CR0_BASE + 24) {
> +        return s->cfg[(offset - PL330_REG_CR0_BASE) >> 2];
> +    }
> +    if (offset >= PL330_REG_CHANCTRL && offset < PL330_REG_DBGSTATUS) {
> +        offset -= PL330_REG_CHANCTRL;
> +        chan_id = offset >> 5;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +        }
> +        switch (offset & 0x1f) {
> +        case 0x00:
> +            return s->chan[chan_id].src;
> +        case 0x04:
> +            return s->chan[chan_id].dst;
> +        case 0x08:
> +            return s->chan[chan_id].control;
> +        case 0x0C:
> +            return s->chan[chan_id].lc[0];
> +        case 0x10:
> +            return s->chan[chan_id].lc[1];
> +        default:
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +        }
> +    }
> +    if (offset >= PL330_REG_CSR_BASE && offset < 0x400) {
> +        offset -= PL330_REG_CSR_BASE;
> +        chan_id = offset >> 3;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +        }
> +        switch ((offset >> 2) & 1) {
> +        case 0x0:
> +            res = (s->chan[chan_id].ns << 21) |
> +                    (s->chan[chan_id].wakeup << 4) |
> +                    (s->chan[chan_id].state) |
> +                    (s->chan[chan_id].wfp_sbp << 14);
> +            return res;
> +        case 0x1:
> +            return s->chan[chan_id].pc;
> +        default:
> +            hw_error("pl330: read error\n");
> +        }
> +    }
> +    if (offset >= PL330_REG_FTR_BASE && offset < 0x100) {
> +        offset -= PL330_REG_FTR_BASE;
> +        chan_id = offset >> 2;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +        }
> +        return s->chan[chan_id].fault_type;
> +    }
> +    switch (offset) {
> +    case PL330_REG_DSR:
> +        return (s->manager.ns << 9) | (s->manager.wakeup << 4) |
> +            (s->manager.state & 0xf);
> +    case PL330_REG_DPC:
> +        return s->manager.pc;
> +    case PL330_REG_INTEN:
> +        return s->inten;
> +    case PL330_REG_INT_EVENT_RIS:
> +        return s->ev_status;
> +    case PL330_REG_INTMIS:
> +        return s->int_status;
> +    case PL330_REG_INTCLR:
> +        /* Documentation says that we can't read this register
> +         * but linux kernel does it */
> +        return 0;
> +    case PL330_REG_FSRD:
> +        return s->manager.state ? 1 : 0;
> +    case PL330_REG_FSRC:
> +        res = 0;
> +        for (i = 0; i < s->chan_num; i++) {
> +            if (s->chan[i].state == pl330_chan_fault ||
> +                s->chan[i].state == pl330_chan_fault_completing) {
> +                res |= 1 << i;
> +            }
> +        }
> +        return res;
> +    case PL330_REG_FTRD:
> +        return s->manager.fault_type;
> +    case PL330_REG_DBGSTATUS:
> +        return s->debug_status;
> +    default:
> +        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +    }
> +    return 0;
> +}
> +
> +static uint64_t pl330_iomem_read(void *opaque, target_phys_addr_t offset,
> +        unsigned size)
> +{
> +    int ret = pl330_iomem_read_imp(opaque, offset);
> +    DB_PRINT("addr: %08x data: %08x\n", offset, ret);
> +    return ret;
> +}
> +
> +static const MemoryRegionOps pl330_ops = {
> +    .read = pl330_iomem_read,
> +    .write = pl330_iomem_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +/* Controller logic and initialization */
> +
> +static void pl330_chan_reset(PL330Chan *ch)
> +{
> +    ch->src = 0;
> +    ch->dst = 0;
> +    ch->pc = 0;
> +    ch->state = pl330_chan_stopped;
> +    ch->watchdog_timer = 0;
> +    ch->stall = 0;
> +    ch->control = 0;
> +    ch->status = 0;
> +    ch->fault_type = 0;
> +}
> +
> +static void pl330_reset(DeviceState *d)
> +{
> +    int i;
> +    PL330 *s = FROM_SYSBUS(PL330, sysbus_from_qdev(d));
> +
> +    s->inten = 0;
> +    s->int_status = 0;
> +    s->ev_status = 0;
> +    s->debug_status = 0;
> +    s->num_faulting = 0;
> +    pl330_fifo_reset(&s->fifo);
> +    pl330_queue_reset(&s->read_queue);
> +    pl330_queue_reset(&s->write_queue);
> +
> +    for (i = 0; i < s->chan_num; i++) {
> +        pl330_chan_reset(&s->chan[i]);
> +    }
> +    for (i = 0; i < s->periph_num; i++) {
> +        s->periph_busy[i] = 0;
> +    }
> +
> +    qemu_del_timer(s->timer);
> +}
> +
> +static int pl330_init(SysBusDevice *dev)
> +{
> +    int i;
> +    PL330 *s = FROM_SYSBUS(PL330, dev);
> +
> +    sysbus_init_irq(dev, &s->irq_abort);
> +    memory_region_init_io(&s->iomem, &pl330_ops, s, "dma", PL330_IOMEM_SIZE);
> +    sysbus_init_mmio(dev, &s->iomem);
> +
> +    s->timer = qemu_new_timer_ns(vm_clock, pl330_exec_cycle_timer, s);
> +
> +    s->chan_num = ((s->cfg[0] >> 4) & 7) + 1;
> +    s->chan = g_new0(PL330Chan, s->chan_num);
> +        DB_PRINT("manager@%p\n", &s->manager);
> +    for (i = 0; i < s->chan_num; i++) {
> +        DB_PRINT("channel %d@%p\n", i, &s->chan[i]);
> +        s->chan[i].parent = s;
> +        s->chan[i].tag = (uint8_t)i;
> +    }
> +    s->manager.parent = s;
> +    s->manager.tag = s->chan_num;
> +    s->manager.ns = (s->cfg[0] >> 2) & 1;
> +    s->manager.is_manager = 1;
> +    if (s->cfg[0] & 1) {
> +        s->periph_num = ((s->cfg[0] >> 12) & 0x1f) + 1;
> +    } else {
> +        s->periph_num = 0;
> +    }
> +    s->event_num = ((s->cfg[0] >> 17) & 0x1f) + 1;
> +
> +    s->irq = g_new0(qemu_irq, s->event_num);
> +    for (i = 0; i < s->event_num; i++) {
> +        sysbus_init_irq(dev, &s->irq[i]);
> +    }
> +
> +    qdev_init_gpio_in(&dev->qdev, pl330_dma_stop_irq, PL330_PERIPH_NUM);
> +
> +    pl330_queue_init(&s->read_queue, ((s->cfg[5] >> 16) & 0xf) + 1,
> +                                                        s->chan_num);
> +    pl330_queue_init(&s->write_queue, ((s->cfg[5] >> 8) & 0xf) + 1,
> +                                                        s->chan_num);
> +    pl330_fifo_init(&s->fifo, ((s->cfg[5] >> 20) & 0x1ff) + 1);
> +
> +    return 0;
> +}
> +
> +static Property pl330_properties[] = {
> +    DEFINE_PROP_UINT32("cfg0", PL330, cfg[0], 0),
> +    DEFINE_PROP_UINT32("cfg1", PL330, cfg[1], 0),
> +    DEFINE_PROP_UINT32("cfg2", PL330, cfg[2], 0),
> +    DEFINE_PROP_UINT32("cfg3", PL330, cfg[3], 0),
> +    DEFINE_PROP_UINT32("cfg4", PL330, cfg[4], 0),
> +    DEFINE_PROP_UINT32("cfg5", PL330, cfg[5], 0),
> +    DEFINE_PROP_END_OF_LIST(),

I think it would be much more user-friendly for the properties
to be the actual configuration values (number of channels,
number of interrupts etc etc), not the CFG register values.
The CFG registers are merely how the device exposes its
configuration to software. (You don't configure the hardware
by specifying CFG register values either.)


> +};
> +
> +static void pl330_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +
> +    k->init = pl330_init;
> +    dc->reset = pl330_reset;
> +    dc->props = pl330_properties;
> +}
> +
> +static TypeInfo pl330_info = {
> +    .name           = "pl330",
> +    .parent         = TYPE_SYS_BUS_DEVICE,
> +    .instance_size  = sizeof(PL330),
> +    .class_init      = pl330_class_init,
> +};
> +
> +static void pl330_register_types(void)
> +{
> +    type_register_static(&pl330_info);
> +}
> +
> +type_init(pl330_register_types)
> --
> 1.7.3.2

-- PMM
Mitsyanko Igor - July 3, 2012, 11:30 a.m.
Hi Peter, here's a few review comments for your patch

On 06/18/2012 04:42 AM, Peter A. G. Crosthwaite wrote:
> Device model for Primecell PL330 dma controller.
>
> Signed-off-by: Peter A. G. Crosthwaite<peter.crosthwaite@petalogix.com>
> Signed-off-by: Kirill Batuzov<batuzovk@ispras.ru>
> ---
> changed from v3:
> rebased against Makefile refactor
> changed from v2 (in order of diff appearance):
> GPL version changed to v2 or later
> Register and field names changed to match TRM
> id field corrected
> PL330ChanState enum corrected
> target_phys_addr_t usages removed
> identifier camel casing and underscoring fixed
> data fifo reworked (uses head ptr and a occupancy count rather than head & tail ptrs)
> pl330_insn_from_queue->pl330_queue_remove_insn
> Numerous typos in comments corrected as per review
> g_malloc0(sizeof(a) * b) -> g_new
> pl330_fifo_inc: changed to accept PL330Fifo struct as argument (rather than buf_size)
> pl330_fifo_push/get: rollback on overflow mechanism replaced with pre-check
> pl330_queue_find_insn: fixed ugly if statement whitespace (PMM review)
> pl330_dmaaddh: fixed arugment reversal
> pl330_dmaaddh: asserted on !manager
> "ns": change to be and behave like a boolean globally
> pl330_dmald: force num=1 when PL330_SINGLE
> pl330_dma_kill: put in correct alphabet order
> pl330_dmarmb/wmb:implemented
> pl330_exec_insn: asserted insn->size not too big
> pl330_exec_insn: enabled stall mechanism outside exec state (needed for rmb/wmb)
> pl330_debug_exec: assert fault type on a fault
> pl330_iomem_write: removed aligmnent assertion
> pl330_iomem_write: corrected ev_status and irq_status logic
> pl330_iomem_read_imp: fixed some incorrect bitfield positions (PMM review)
> pl330_reset: stopped timer
> pl330_init: removed redundant reset
> changed from v1:
> GPLv2 license
> some code formatting fixes
>
>   hw/arm/Makefile.objs |    1 +
>   hw/pl330.c           | 1411 ++++++++++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 1412 insertions(+), 0 deletions(-)
>   create mode 100644 hw/pl330.c
>
> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
> index a0ff6a6..b28e5b7 100644
> --- a/hw/arm/Makefile.objs
> +++ b/hw/arm/Makefile.objs
> @@ -7,6 +7,7 @@ obj-y += cadence_ttc.o
>   obj-y += cadence_gem.o
>   obj-y += xilinx_zynq.o zynq_slcr.o
>   obj-y += arm_gic.o
> +obj-y += pl330.o
>   obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
>   obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
>   obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
> diff --git a/hw/pl330.c b/hw/pl330.c
> new file mode 100644
> index 0000000..bc6bc35
> --- /dev/null
> +++ b/hw/pl330.c
> @@ -0,0 +1,1411 @@
> +/*
> + * ARM PrimeCell PL330 DMA Controller
> + *
> + * Copyright (c) 2009 Samsung Electronics.
> + * Contributed by Kirill Batuzov<batuzovk@ispras.ru>
> + * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
> + * Copyright (c) 2012 PetaLogix Pty Ltd.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; version 2 or later.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see<http://www.gnu.org/licenses/>.
> + */
> +
> +#include "sysbus.h"
> +#include "qemu-timer.h"
> +
> +#ifdef PL330_ERR_DEBUG
> +#define DB_PRINT(...) do { \
> +    fprintf(stderr,  ": %s: ", __func__); \
> +    fprintf(stderr, ## __VA_ARGS__); \
> +    } while (0);
> +#else
> +    #define DB_PRINT(...)
> +#endif
> +
> +#define PL330_CHAN_NUM              8
> +#define PL330_PERIPH_NUM            32
> +#define PL330_MAX_BURST_LEN         128
> +#define PL330_INSN_MAXSIZE          6
> +
> +#define PL330_FIFO_OK               0
> +#define PL330_FIFO_STALL            1
> +#define PL330_FIFO_ERR              (-1)
> +
> +#define PL330_FAULT_UNDEF_INSTR             (1 <<  0)
> +#define PL330_FAULT_OPERAND_INVALID         (1 <<  1)
> +#define PL330_FAULT_DMAGO_ERR               (1 <<  4)
> +#define PL330_FAULT_EVENT_ERR               (1 <<  5)
> +#define PL330_FAULT_CH_PERIPH_ERR           (1 <<  6)
> +#define PL330_FAULT_CH_RDWR_ERR             (1 <<  7)
> +#define PL330_FAULT_ST_DATA_UNAVAILABLE     (1 << 12)
> +#define PL330_FAULT_FIFOEMPTY_ERR           (1 << 13)
> +#define PL330_FAULT_INSTR_FETCH_ERR         (1 << 16)
> +#define PL330_FAULT_DATA_WRITE_ERR          (1 << 17)
> +#define PL330_FAULT_DATA_READ_ERR           (1 << 18)
> +#define PL330_FAULT_DBG_INSTR               (1 << 30)
> +#define PL330_FAULT_LOCKUP_ERR              (1 << 31)
> +
> +#define PL330_UNTAGGED              0xff
> +
> +#define PL330_SINGLE                0x0
> +#define PL330_BURST                 0x1
> +
> +#define PL330_WATCHDOG_LIMIT        1024
> +
> +/* IOMEM mapped registers */
> +#define PL330_REG_DSR               0x000
> +#define PL330_REG_DPC               0x004
> +#define PL330_REG_INTEN             0x020
> +#define PL330_REG_INT_EVENT_RIS     0x024
> +#define PL330_REG_INTMIS            0x028
> +#define PL330_REG_INTCLR            0x02C
> +#define PL330_REG_FSRD              0x030
> +#define PL330_REG_FSRC              0x034
> +#define PL330_REG_FTRD              0x038
> +#define PL330_REG_FTR_BASE          0x040
> +#define PL330_REG_CSR_BASE          0x100
> +#define PL330_REG_CPC_BASE          0x104
> +#define PL330_REG_CHANCTRL          0x400
> +#define PL330_REG_DBGSTATUS         0xD00
> +#define PL330_REG_DBGCMD            0xD04
> +#define PL330_REG_DBGINST0          0xD08
> +#define PL330_REG_DBGINST1          0xD0C
> +#define PL330_REG_CR0_BASE          0xE00
> +#define PL330_REG_PERIPH_ID         0xFE0
> +
> +#define PL330_IOMEM_SIZE    0x1000
> +
> +static const uint32_t pl330_id[] = {
> +    0x30, 0x13, 0x04, 0x00, 0x0D, 0xF0, 0x05, 0xB1
> +};
> +
> +/* DMA chanel states as they are described in PL330 Technical Reference Manual
> + * Most of them will not be used in emulation.
> + */
> +typedef enum  {
> +    pl330_chan_stopped = 0,
> +    pl330_chan_executing = 1,
> +    pl330_chan_cache_miss = 2,
> +    pl330_chan_updating_pc = 3,
> +    pl330_chan_waiting_event = 4,
> +    pl330_chan_at_barrier = 5,
> +    pl330_chan_queue_busy = 6,
> +    pl330_chan_waiting_periph = 7,
> +    pl330_chan_killing = 8,
> +    pl330_chan_completing = 9,
> +    pl330_chan_fault_completing = 14,
> +    pl330_chan_fault = 15,
> +} PL330ChanState;
> +
> +typedef struct PL330 PL330;
> +
> +typedef struct PL330Chan {
> +    uint32_t src;
> +    uint32_t dst;
> +    uint32_t pc;
> +    uint32_t control;
> +    uint32_t status;
> +    uint32_t lc[2];
> +    uint32_t fault_type;
> +
> +    bool ns;
> +    bool is_manager;
> +    uint8_t request_flag;
> +    uint8_t wakeup;
> +    uint8_t wfp_sbp;
> +
> +    PL330ChanState state;
> +    uint8_t stall;
> +
> +    PL330 *parent;
> +    uint8_t tag;
> +    uint32_t watchdog_timer;
> +} PL330Chan;
> +
> +typedef struct PL330Fifo {
> +    uint8_t *buf;
> +    uint8_t *tag;
> +    int s, n;
> +    int buf_size;
> +} PL330Fifo;
> +
> +typedef struct PL330QueueEntry {
> +    PL330Chan *c;
> +    target_phys_addr_t addr;
> +    int len;
> +    int n;
> +    int inc;
> +    int z;
> +    uint8_t tag;
> +    uint8_t seqn;
> +} PL330QueueEntry;
> +
> +typedef struct PL330Queue {
> +    PL330QueueEntry *queue;
> +    int queue_size;
> +    uint8_t *lo_seqn;
> +    uint8_t *hi_seqn;
> +} PL330Queue;
> +
> +struct PL330 {
> +    SysBusDevice busdev;
> +    MemoryRegion iomem;
> +
> +    PL330Chan manager;
> +    PL330Chan *chan;
> +    PL330Fifo fifo;
> +    PL330Queue read_queue;
> +    PL330Queue write_queue;
> +
> +    /* Config registers. cfg[5] = CfgDn. */
> +    uint32_t inten;
> +    uint32_t int_status;
> +    uint32_t ev_status;
> +    uint32_t cfg[6];
> +    uint32_t dbg[2];
> +    uint8_t debug_status;
> +
> +    qemu_irq irq_abort;
> +    qemu_irq *irq;
> +
> +    uint8_t num_faulting;
> +
> +    int chan_num;
> +    int periph_num;
> +    unsigned int event_num;
> +
> +    QEMUTimer *timer; /* is used for restore dma. */
> +    int8_t periph_busy[PL330_PERIPH_NUM];
> +};
> +
> +typedef struct PL330InsnDesc {
> +    /* OPCODE of the instruction */
> +    uint8_t opcode;
> +    /* Mask so we can select several sibling instructions, such as
> +       DMALD, DMALDS and DMALDB */
> +    uint8_t opmask;
> +    /* Size of instruction in bytes */
> +    uint8_t size;
> +    /* Interpreter */
> +    void (*exec)(PL330Chan *, uint8_t opcode, uint8_t *args, int len);
> +} PL330InsnDesc;
> +
> +
> +/* MFIFO Implementation
> + *
> + * MFIFO is implemented as a cyclic buffer of BUF_SIZE size. Tagged bytes are
> + * stored in this buffer. Data is stored in BUF field, tags - in the
> + * corresponding array elements of TAG field.
> + */
> +
> +/* Initialize queue. */
> +static void pl330_fifo_init(PL330Fifo *s, uint32_t size)
> +{
> +    s->buf = g_malloc0(size);
> +    s->tag = g_malloc0(size);
> +    s->buf_size = size;
> +}
> +
> +/* Cyclic increment */
> +static inline int pl330_fifo_inc(PL330Fifo *s, int x)
> +{
> +    return (x + 1) % s->buf_size;
> +}
> +
> +/* Number of empty bytes in MFIFO */
> +static inline int pl330_fifo_num_free(PL330Fifo *s)
> +{
> +    return s->buf_size - s->n;
> +}
> +
> +/* Push LEN bytes of data stored in BUF to MFIFO and tag it with TAG.
> + * Zero returned on success, PL330_FIFO_STALL if there is no enough free
> + * space in MFIFO to store requested amount of data. If push was unsuccessful
> + * no data is stored to MFIFO.
> + */
> +static int pl330_fifo_push(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
> +{
> +    int i;
> +
> +    if (s->buf_size - s->n < len) {
> +        return PL330_FIFO_STALL;
> +    }
> +    for (i = 0; i < len; i++) {
> +        int push_idx = (s->s + i) % s->buf_size;
> +        s->buf[push_idx] = buf[i];
> +        s->tag[push_idx] = tag;
> +    }
> +    s->n += len;
> +    return PL330_FIFO_OK;
> +}
> +
> +/* Get LEN bytes of data from MFIFO and store it to BUF. Tag value of each
> + * byte is verified. Zero returned on success, PL330_FIFO_ERR on tag mismatch
> + * and PL330_FIFO_STALL if there is no enough data in MFIFO. If get was
> + * unsuccessful no data is removed from MFIFO.
> + */
> +static int pl330_fifo_get(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
> +{
> +    int i;
> +
> +    if (s->n < len) {
> +        return PL330_FIFO_STALL;
> +    }
> +    for (i = 0; i < len; i++) {
> +        if (s->tag[s->s] == tag) {
> +            int get_idx = (s->s + i) % s->buf_size;
> +            buf[i] = s->buf[get_idx];
> +        } else { /* Tag mismatch - Rollback transaction */
> +            return PL330_FIFO_ERR;
> +        }
> +    }
> +    s->s = (s->s + len) % s->buf_size;
> +    s->n -= len;
> +    return PL330_FIFO_OK;
> +}
> +
> +/* Reset MFIFO. This completely erases all data in it. */
> +static inline void pl330_fifo_reset(PL330Fifo *s)
> +{
> +    s->s = 0;
> +    s->n = 0;
> +}
> +
> +/* Return tag of the first byte stored in MFIFO. If MFIFO is empty
> +   PL330_UNTAGGED is returned. */
> +static inline uint8_t pl330_fifo_tag(PL330Fifo *s)
> +{
> +    return (!s->n) ? PL330_UNTAGGED : s->tag[s->s];
> +}
> +
> +/* Returns non-zero if tag TAG is present in fifo or zero otherwise */
> +static int pl330_fifo_has_tag(PL330Fifo *s, uint8_t tag)
> +{
> +    int i, n;
> +
> +    i = s->s;
> +    for (n = 0; n < s->n; n++) {
> +        if (s->tag[i] == tag) {
> +            return 1;
> +        }
> +        i = pl330_fifo_inc(s, i);
> +    }
> +    return 0;
> +}
> +
> +/* Remove all entry tagged with TAG from MFIFO */
> +static void pl330_fifo_tagged_remove(PL330Fifo *s, uint8_t tag)
> +{
> +    int i, t, n;
> +
> +    t = i = s->s;
> +    for (n = 0; n < s->n; n++) {
> +        if (s->tag[i] != tag) {
> +            s->buf[t] = s->buf[i];
> +            s->tag[t] = s->tag[i];
> +            t = pl330_fifo_inc(s, t);
> +        } else {
> +            s->n = s->n - 1;
> +        }
> +        i = pl330_fifo_inc(s, i);
> +    }
> +}
> +
> +/* Read-Write Queue implementation
> + *
> + * A Read-Write Queue stores up to QUEUE_SIZE instructions (loads or stores).
> + * Each instruction is described by source (for loads) or destination (for
> + * stores) address ADDR, width of data to be loaded/stored LEN, number of
> + * stores/loads to be performed N, INC bit, Z bit and TAG to identify channel
> + * this instruction belongs to. Queue does not store any information about
> + * nature of the instruction: is it load or store. PL330 has different queues
> + * for loads and stores so this is already known at the top level where it
> + * matters.
> + *
> + * Queue works as FIFO for instructions with equivalent tags, but can issue
> + * instructions with different tags in arbitrary order. SEQN field attached to
> + * each instruction helps to achieve this. For each TAG queue contains
> + * instructions with consecutive SEQN values ranging from LO_SEQN[TAG] to
> + * HI_SEQN[TAG]-1 inclusive. SEQN is 8-bit unsigned integer, so SEQN=255 is
> + * followed by SEQN=0.
> + *
> + * Z bit indicates that zeroes should be stored. No MFIFO fetches are performed
> + * in this case.
> + */
> +
> +static void pl330_queue_reset(PL330Queue *s)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        s->queue[i].tag = PL330_UNTAGGED;
> +    }
> +}
> +
> +/* Initialize queue */
> +static void pl330_queue_init(PL330Queue *s, int size, int channum)
> +{
> +    s->queue = (PL330QueueEntry *)g_new0(PL330QueueEntry, size);
> +    s->lo_seqn = (uint8_t *)g_malloc0(channum);
> +    s->hi_seqn = (uint8_t *)g_malloc0(channum);
> +    s->queue_size = size;
> +}
> +
> +/* Returns pointer to an empty slot or NULL if queue is full */
> +static PL330QueueEntry *pl330_queue_find_empty(PL330Queue *s)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag == PL330_UNTAGGED) {
> +            return &s->queue[i];
> +        }
> +    }
> +    return NULL;
> +}
> +
> +/* Put instruction to queue.
> +   Return value:
> +     - zero - OK
> +     - non-zero - queue is full */
> +static int pl330_queue_put_insn(PL330Queue *s, target_phys_addr_t addr,
> +                               int len, int n, int inc, int z, uint8_t tag,
> +                               PL330Chan *c)
> +{
> +    PL330QueueEntry *entry = pl330_queue_find_empty(s);
> +
> +    if (!entry) {
> +        return 1;
> +    }
> +    entry->c = c;
> +    entry->tag = tag;
> +    entry->addr = addr;
> +    entry->len = len;
> +    entry->n = n;
> +    entry->z = z;
> +    entry->inc = inc;
> +    entry->seqn = s->hi_seqn[tag];
> +    s->hi_seqn[tag]++;
> +    return 0;
> +}
> +
> +/* Returns a pointer to queue slot containing instruction which satisfies
> +   following conditions:
> +    - it has valid tag value (not PL330_UNTAGGED)
> +    - it can be issued without violating queue logic (see above)
> +    - if TAG argument is not PL330_UNTAGGED this instruction has tag value
> +      equivalent to the argument TAG value.
> +   If such instruction cannot be found NULL is returned. */
> +static PL330QueueEntry *pl330_queue_find_insn(PL330Queue *s, uint8_t tag)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag != PL330_UNTAGGED) {
> +            if (s->queue[i].seqn == s->lo_seqn[s->queue[i].tag] &&
> +                    (s->queue[i].tag == tag || tag == PL330_UNTAGGED ||
> +                    s->queue[i].z)) {
> +                return &s->queue[i];
> +            }
> +        }
> +    }
> +    return NULL;
> +}
> +
> +/* Removes instruction from queue. */
> +static inline void pl330_queue_remove_insn(PL330Queue *s, PL330QueueEntry *e)
> +{
> +    s->lo_seqn[e->tag]++;
> +    e->tag = PL330_UNTAGGED;
> +}
> +
> +/* Removes all instructions tagged with TAG from queue. */
> +static inline void pl330_queue_remove_tagged(PL330Queue *s, uint8_t tag)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag == tag) {
> +            s->queue[i].tag = PL330_UNTAGGED;
> +        }
> +    }
> +}
> +
> +
> +/* DMA instruction execution engine */
> +
> +/* Moves DMA channel to the FAULT state and updates it's status. */
> +static inline void pl330_fault(PL330Chan *ch, uint32_t flags)
> +{
> +    DB_PRINT("ch: %p, flags: %x\n", ch, flags);
> +    ch->fault_type |= flags;
> +    if (ch->state == pl330_chan_fault) {
> +        return;
> +    }
> +    ch->state = pl330_chan_fault;
> +    ch->parent->num_faulting++;
> +    if (ch->parent->num_faulting == 1) {
> +        DB_PRINT("abort interrupt raised\n");
> +        qemu_irq_raise(ch->parent->irq_abort);
> +    }
> +}
> +
> +/*
> + * For information about instructions see PL330 Technical Reference Manual.
> + *
> + * Arguments:
> + *   CH - channel executing the instruction
> + *   OPCODE - opcode
> + *   ARGS - array of 8-bit arguments
> + *   LEN - number of elements in ARGS array
> + */
> +static void pl330_dmaaddh(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint16_t im = (((uint16_t)args[1]) << 8) | ((uint16_t)args[0]);
> +    uint8_t ra = (opcode >> 1) & 1;
> +
> +    if (ch->is_manager) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
more likely to cause PL330_FAULT_UNDEF_INSTR
> +        return;
> +    }
> +    if (ra) {
> +        ch->dst += im;
> +    } else {
> +        ch->src += im;
> +    }
> +}
> +
> +static void pl330_dmaend(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    if (ch->state == pl330_chan_executing && !ch->is_manager) {
> +        /* Wait for all transfers to complete */
> +        if (pl330_fifo_has_tag(&ch->parent->fifo, ch->tag) ||
> +            pl330_queue_find_insn(&ch->parent->read_queue, ch->tag) != NULL ||
> +            pl330_queue_find_insn(&ch->parent->write_queue, ch->tag) != NULL) {
> +
> +            ch->stall = 1;
> +            return;
> +        }
> +    }
> +    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
> +    ch->state = pl330_chan_stopped;
> +}
> +
> +static void pl330_dmaflushp(PL330Chan *ch, uint8_t opcode,
> +                                            uint8_t *args, int len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    /* Do nothing */
> +}
> +
> +static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t chan_id;
> +    uint8_t ns;
> +    uint32_t pc;
> +    PL330Chan *s;
> +
> +    DB_PRINT("\n");
> +
> +    if (!ch->is_manager) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ns = !!(opcode & 2);
> +    chan_id = args[0] & 7;
> +    if ((args[0] >> 3)) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (chan_id >= ch->parent->chan_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    pc = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
> +         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
> +    if (ch->parent->chan[chan_id].state != pl330_chan_stopped) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !ns) {
> +        pl330_fault(ch, PL330_FAULT_DMAGO_ERR);
> +        return;
> +    }
> +    s = &ch->parent->chan[chan_id];
> +    s->ns = ns;
> +    s->pc = pc;
> +    s->state = pl330_chan_executing;
> +}
> +
> +static void pl330_dmald(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint32_t size, num, inc;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    if (bs == 1 && ch->request_flag == PL330_SINGLE) {
> +        num = 1;
> +    } else {
> +        num = ((ch->control >> 4) & 0xf) + 1;
> +    }
> +    size = (uint32_t)1 << ((ch->control >> 1) & 0x7);
> +    inc = ch->control & 1;
> +    ch->src &= ~(size - 1);
Why are you doing that here, I couldn't find anywhere in TRM that PL330 
automatically aligns
addresses when executing instructions. The only note I've found is that 
fixed transfers from unaligned addresses are considered a programming error.
> +    ch->stall = pl330_queue_put_insn(&ch->parent->read_queue, ch->src,
> +                                    size, num, inc, 0, ch->tag, ch);
> +    if (inc & !ch->stall) {
> +        ch->src += size * num;
> +    }
> +}
> +
> +static void pl330_dmaldp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    pl330_dmald(ch, opcode, args, len);
> +}
> +
> +static void pl330_dmalp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t lc = (opcode & 2) >> 1;
> +
> +    ch->lc[lc] = args[0];
> +}
> +
> +static void pl330_dmakill(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    if (ch->state == pl330_chan_fault ||
> +        ch->state == pl330_chan_fault_completing) {
> +        /* This is the only way for a channel to leave the faulting state */
> +        ch->fault_type = 0;
> +        ch->parent->num_faulting--;
> +        if (ch->parent->num_faulting == 0) {
> +            DB_PRINT("abort interrupt lowered\n");
> +            qemu_irq_lower(ch->parent->irq_abort);
> +        }
> +    }
> +    ch->state = pl330_chan_killing;
> +    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
> +    ch->state = pl330_chan_stopped;
> +}
> +
> +static void pl330_dmalpend(PL330Chan *ch, uint8_t opcode,
> +                                    uint8_t *args, int len)
> +{
> +    uint8_t nf = (opcode & 0x10) >> 4;
> +    uint8_t bs = opcode & 3;
> +    uint8_t lc = (opcode & 4) >> 2;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    if (!nf || ch->lc[lc]) {
> +        if (nf) {
> +            ch->lc[lc]--;
> +        }
> +        ch->pc -= args[0];
> +        ch->pc -= len + 1;
> +        /* "ch->pc -= args[0] + len + 1" is incorrect when args[0] == 256 */
> +    }
> +}
> +
> +
> +static void pl330_dmamov(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t rd = args[0] & 7;
> +    uint32_t im;
> +
> +    if ((args[0] >> 3)) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    im = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
> +         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
> +    switch (rd) {
> +    case 0:
> +        ch->src = im;
> +        break;
> +    case 1:
> +        ch->control = im;
> +        break;
> +    case 2:
> +        ch->dst = im;
> +        break;
> +    default:
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +}
> +
> +static void pl330_dmanop(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    /* NOP is NOP. */
> +}
> +
> +static void pl330_dmarmb(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +   if (pl330_queue_find_insn(&ch->parent->read_queue, ch->tag)) {
> +        ch->state = pl330_chan_at_barrier;
> +        ch->stall = 1;
> +        return;
> +    }
> +}
> +
> +static void pl330_dmasev(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t ev_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ev_id = (args[0] >> 3) & 0x1f;
> +    if (ev_id >= ch->parent->event_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
> +        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
> +        return;
> +    }
> +    if (ch->parent->inten & (1 << ev_id)) {
> +        ch->parent->int_status |= (1 << ev_id);
> +        DB_PRINT("event interrupt raised %d\n", ev_id);
> +        qemu_irq_raise(ch->parent->irq[ev_id]);
> +    } else {
> +        ch->parent->ev_status |= (1 << ev_id);
Shouldn't you check if any threads were waiting for that event and move 
them to execution state if they were?

> +    }
> +}
> +
> +static void pl330_dmast(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint32_t size, num, inc;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    num = ((ch->control >> 18) & 0xf) + 1;
DMAC ignores dst_burst_len field in ch->control if condition

(bs == 1 && ch->request_flag == PL330_SINGLE)

is true
> +    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
> +    inc = (ch->control >> 14) & 1;
> +    ch->dst &= ~(size - 1);
> +    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
> +                                    size, num, inc, 0, ch->tag, ch);
> +    if (inc) {
> +        ch->dst += size * num;
> +    }
> +}
> +
> +static void pl330_dmastp(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    pl330_dmast(ch, opcode, args, len);
> +}
> +
> +static void pl330_dmastz(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint32_t size, num, inc;
> +
> +    num = ((ch->control >> 18) & 0xf) + 1;
> +    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
> +    inc = (ch->control >> 14) & 1;
> +    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
> +                                    size, num, inc, 1, ch->tag, ch);
> +    if (inc) {
> +        ch->dst += size * num;
> +    }
> +}
> +
> +static void pl330_dmawfe(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t ev_id;
> +
> +    if (args[0] & 5) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ev_id = (args[0] >> 3) & 0x1f;
> +    if (ev_id >= ch->parent->event_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
> +        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
> +        return;
> +    }
> +    ch->wakeup = ev_id;
> +    ch->state = pl330_chan_waiting_event;
> +    if (ch->parent->inten & ch->parent->ev_status & 1 << ev_id) {
> +        ch->parent->ev_status &= ~(1 << ev_id);
ev_status should be cleared if corresponding bit in inten is 0, not 1. 
And I think channel thread
should reenter execution state if that event was already pending in 
ev_status?

Overall, I don't see that DMAWFE and DMASEV are working correctly here, 
maybe because I'm missing something and misunderstand what ch->stall is 
supposed to be used for, but still its not such a big issue.
<http://slovari.yandex.ru/unnecessary/en-ru/LingvoUniversal/#lingvo/>
> +    } else {
> +        ch->stall = 1;
> +    }
> +}
> +
> +static void pl330_dmawfp(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    switch (bs) {
> +    case 0: /* S */
> +        ch->request_flag = PL330_SINGLE;
> +        ch->wfp_sbp = 0;
> +        break;
> +    case 1: /* P */
> +        ch->request_flag = PL330_BURST;
> +        ch->wfp_sbp = 2;
> +        break;
> +    case 2: /* B */
> +        ch->request_flag = PL330_BURST;
> +        ch->wfp_sbp = 1;
> +        break;
> +    default:
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +
> +    if (ch->parent->periph_busy[periph_id]) {
> +        ch->state = pl330_chan_waiting_periph;
> +        ch->stall = 1;
> +    } else if (ch->state == pl330_chan_waiting_periph) {
> +        ch->state = pl330_chan_executing;
> +    }
> +}
> +
> +static void pl330_dmawmb(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    if (pl330_queue_find_insn(&ch->parent->write_queue, ch->tag)) {
> +        ch->state = pl330_chan_at_barrier;
> +        ch->stall = 1;
> +        return;
> +    }
> +}
> +
> +/* NULL terminated array of the instruction descriptions. */
> +static const PL330InsnDesc insn_desc[] = {
> +    { .opcode = 0x54, .opmask = 0xFD, .size = 3, .exec = pl330_dmaaddh, },
> +    { .opcode = 0x00, .opmask = 0xFF, .size = 1, .exec = pl330_dmaend, },
> +    { .opcode = 0x35, .opmask = 0xFF, .size = 2, .exec = pl330_dmaflushp, },
> +    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
> +    { .opcode = 0x04, .opmask = 0xFC, .size = 1, .exec = pl330_dmald, },
> +    { .opcode = 0x25, .opmask = 0xFD, .size = 2, .exec = pl330_dmaldp, },
> +    { .opcode = 0x20, .opmask = 0xFD, .size = 2, .exec = pl330_dmalp, },
> +    /* dmastp  must be before dmalpend in this list, because their maps
> +     * are overlapping
> +     */
> +    { .opcode = 0x29, .opmask = 0xFD, .size = 2, .exec = pl330_dmastp, },
> +    { .opcode = 0x28, .opmask = 0xE8, .size = 2, .exec = pl330_dmalpend, },
> +    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
> +    { .opcode = 0xBC, .opmask = 0xFF, .size = 6, .exec = pl330_dmamov, },
> +    { .opcode = 0x18, .opmask = 0xFF, .size = 1, .exec = pl330_dmanop, },
> +    { .opcode = 0x12, .opmask = 0xFF, .size = 1, .exec = pl330_dmarmb, },
> +    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
> +    { .opcode = 0x08, .opmask = 0xFC, .size = 1, .exec = pl330_dmast, },
> +    { .opcode = 0x0C, .opmask = 0xFF, .size = 1, .exec = pl330_dmastz, },
> +    { .opcode = 0x36, .opmask = 0xFF, .size = 2, .exec = pl330_dmawfe, },
> +    { .opcode = 0x30, .opmask = 0xFC, .size = 2, .exec = pl330_dmawfp, },
> +    { .opcode = 0x13, .opmask = 0xFF, .size = 1, .exec = pl330_dmawmb, },
> +    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
> +};
> +
> +/* Instructions which can be issued via debug registers. */
> +static const PL330InsnDesc debug_insn_desc[] = {
> +    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
> +    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
> +    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
> +    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
> +};
> +
> +static inline const PL330InsnDesc *pl330_fetch_insn(PL330Chan *ch)
> +{
> +    uint8_t opcode;
> +    int i;
> +
> +    cpu_physical_memory_read(ch->pc, &opcode, 1);
> +    for (i = 0; insn_desc[i].size; i++) {
> +        if ((opcode & insn_desc[i].opmask) == insn_desc[i].opcode) {
> +            return &insn_desc[i];
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static inline void pl330_exec_insn(PL330Chan *ch, const PL330InsnDesc *insn)
> +{
> +    uint8_t buf[PL330_INSN_MAXSIZE];
> +
> +    assert(insn->size <= PL330_INSN_MAXSIZE);
> +    cpu_physical_memory_read(ch->pc, buf, insn->size);
> +    insn->exec(ch, buf[0], &buf[1], insn->size - 1);
> +}
> +
> +static inline void pl330_update_pc(PL330Chan *ch,
> +                                   const PL330InsnDesc *insn)
> +{
> +    ch->pc += insn->size;
> +}
> +
> +/* Try to execute current instruction in channel CH. Number of executed
> +   instructions is returned (0 or 1). */
> +static int pl330_chan_exec(PL330Chan *ch)
> +{
> +    const PL330InsnDesc *insn;
> +
> +    if (ch->state != pl330_chan_executing &&
> +            ch->state != pl330_chan_waiting_periph &&
> +            ch->state != pl330_chan_at_barrier &&
> +            ch->state != pl330_chan_waiting_event) {
> +        DB_PRINT("%d\n", ch->state);
> +        return 0;
> +    }
> +    ch->stall = 0;
> +    insn = pl330_fetch_insn(ch);
> +    if (!insn) {
> +        DB_PRINT("pl330 undefined instruction\n");
> +        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR);
> +        return 0;
> +    }
> +    pl330_exec_insn(ch, insn);
> +    if (!ch->stall) {
> +        pl330_update_pc(ch, insn);
> +        ch->watchdog_timer = 0;
> +        return 1;
> +    /* WDT only active in exec state */
> +    } else if (ch->state == pl330_chan_executing) {
> +        ch->watchdog_timer++;
> +        if (ch->watchdog_timer >= PL330_WATCHDOG_LIMIT) {
> +            pl330_fault(ch, PL330_FAULT_LOCKUP_ERR);
> +        }
> +    }
> +    return 0;
> +}
> +
> +/* Try to execute 1 instruction in each channel, one instruction from read
> +   queue and one instruction from write queue. Number of successfully executed
> +   instructions is returned. */
> +static int pl330_exec_cycle(PL330Chan *channel)
> +{
> +    PL330 *s = channel->parent;
> +    PL330QueueEntry *q;
> +    int i;
> +    int num_exec = 0;
> +    int fifo_res = 0;
> +    uint8_t buf[PL330_MAX_BURST_LEN];
> +
> +    /* Execute one instruction in each channel */
> +    num_exec += pl330_chan_exec(channel);
> +
> +    /* Execute one instruction from read queue */
> +    q = pl330_queue_find_insn(&s->read_queue, PL330_UNTAGGED);
> +    if (q != NULL && q->len <= pl330_fifo_num_free(&s->fifo)) {
> +        cpu_physical_memory_read(q->addr, buf, q->len);
> +        fifo_res = pl330_fifo_push(&s->fifo, buf, q->len, q->tag);
> +        if (fifo_res == PL330_FIFO_OK) {
> +            if (q->inc) {
> +                q->addr += q->len;
> +            }
> +            q->n--;
> +            if (!q->n) {
> +                pl330_queue_remove_insn(&s->read_queue, q);
> +            }
> +            num_exec++;
> +        }
> +    }
> +
> +    /* Execute one instruction from write queue. */
> +    q = pl330_queue_find_insn(&s->write_queue, pl330_fifo_tag(&s->fifo));
> +    if (q != NULL) {
> +        if (q->z) {
> +            for (i = 0; i < q->len; i++) {
> +                buf[i] = 0;
> +            }
> +        } else {
> +            fifo_res = pl330_fifo_get(&s->fifo, buf, q->len, q->tag);
> +        }
> +        if (fifo_res == PL330_FIFO_OK || q->z) {
> +            cpu_physical_memory_write(q->addr, buf, q->len);
> +            if (q->inc) {
> +                q->addr += q->len;
> +            }
> +            num_exec++;
> +        } else if (fifo_res == PL330_FIFO_STALL) {
> +            pl330_fault(q->c, PL330_FAULT_FIFOEMPTY_ERR);
> +        }
> +        q->n--;
> +        if (!q->n) {
> +            pl330_queue_remove_insn(&s->write_queue, q);
> +        }
> +    }
> +
> +    return num_exec;
> +}
> +
> +static int pl330_exec_channel(PL330Chan *channel)
> +{
> +    int insr_exec = 0;
> +
> +    /* TODO: Is it all right to execute everything or should we do per-cycle
> +       simulation? */
> +    while (pl330_exec_cycle(channel)) {
> +        insr_exec++;
> +    }
> +
> +    /* Detect deadlock */
> +    if (channel->state == pl330_chan_executing) {
> +        pl330_fault(channel, PL330_FAULT_LOCKUP_ERR);
> +    }
> +    /* Situation when one of the queues has deadlocked but all channels
> +     * have finished their programs should be impossible.
> +     */
> +
> +    return insr_exec;
> +}
> +
> +
> +static inline void pl330_exec(PL330 *s)
> +{
> +    DB_PRINT("\n");
> +    int i, insr_exec;
> +    do {
> +        insr_exec = pl330_exec_channel(&s->manager);
> +
> +        for (i = 0; i < s->chan_num; i++) {
> +            insr_exec += pl330_exec_channel(&s->chan[i]);
> +        }
> +    } while (insr_exec);
> +}
> +
> +static void pl330_exec_cycle_timer(void *opaque)
> +{
> +    PL330 *s = (PL330 *)opaque;
> +    pl330_exec(s);
> +}
> +
> +/* Stop or restore dma operations */
> +static void pl330_dma_stop_irq(void *opaque, int irq, int level)
> +{
> +    PL330 *s = (PL330 *)opaque;
> +
> +    if (s->periph_busy[irq] != level) {
> +        s->periph_busy[irq] = level;
> +        qemu_mod_timer(s->timer, qemu_get_clock_ns(vm_clock));
> +    }
> +}
> +
> +static void pl330_debug_exec(PL330 *s)
> +{
> +    uint8_t args[5];
> +    uint8_t opcode;
> +    uint8_t chan_id;
> +    int i;
> +    PL330Chan *ch;
> +    const PL330InsnDesc *insn;
> +
> +    s->debug_status = 1;
> +    chan_id = (s->dbg[0] >>  8) & 0x07;
> +    opcode  = (s->dbg[0] >> 16) & 0xff;
> +    args[0] = (s->dbg[0] >> 24) & 0xff;
> +    args[1] = (s->dbg[1] >>  0) & 0xff;
> +    args[2] = (s->dbg[1] >>  8) & 0xff;
> +    args[3] = (s->dbg[1] >> 16) & 0xff;
> +    args[4] = (s->dbg[1] >> 24) & 0xff;
> +    DB_PRINT("chan id: %d\n", chan_id);
> +    if (s->dbg[0] & 1) {
> +        ch = &s->chan[chan_id];
> +    } else {
> +        ch = &s->manager;
> +    }
> +    insn = NULL;
> +    for (i = 0; debug_insn_desc[i].size; i++) {
> +        if ((opcode & debug_insn_desc[i].opmask) == debug_insn_desc[i].opcode) {
> +            insn = &debug_insn_desc[i];
> +        }
> +    }
> +    if (!insn) {
> +        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR | PL330_FAULT_DBG_INSTR);
> +        return ;
> +    }
> +    ch->stall = 0;
> +    insn->exec(ch, opcode, args, insn->size - 1);
> +    if (ch->fault_type) {
> +        ch->fault_type |= PL330_FAULT_DBG_INSTR;
> +    }
> +    if (ch->stall) {
> +        hw_error("pl330: stall of debug instruction not implemented\n");
> +    }
> +    s->debug_status = 0;
> +}
> +
> +
> +/* IOMEM mapped registers */
> +
> +static void pl330_iomem_write(void *opaque, target_phys_addr_t offset,
> +                              uint64_t value, unsigned size)
> +{
> +    PL330 *s = (PL330 *) opaque;
> +    uint32_t i;
> +
> +    DB_PRINT("addr: %08x data: %08x\n", offset, (unsigned)value);
> +
> +    switch (offset) {
> +    case PL330_REG_INTEN:
> +        s->inten = value;
> +        break;
> +    case PL330_REG_INTCLR:
> +        for (i = 0; i < s->event_num; i++) {
> +            if (s->int_status & s->inten & value & (1 << i)) {
> +                DB_PRINT("event interrupt lowered %d\n", i);
> +                qemu_irq_lower(s->irq[i]);
> +            }
> +        }
> +        s->ev_status &= ~(value & s->inten);
> +        s->int_status &= ~(value & ~s->inten);
second inversion shouldn't be here I think
> +        break;
> +    case PL330_REG_DBGCMD:
> +        if ((value & 3) == 0) {
> +            pl330_debug_exec(s);
> +            pl330_exec(s);
> +        } else {
> +            hw_error("pl330: write of illegal value %u for offset "
> +                     TARGET_FMT_plx "\n", (unsigned)value, offset);
> +        }
> +        break;
> +    case PL330_REG_DBGINST0:
> +        DB_PRINT("s->dbg[0] = %08x\n", (unsigned)value);
> +        s->dbg[0] = value;
> +        break;
> +    case PL330_REG_DBGINST1:
> +        DB_PRINT("s->dbg[1] = %08x\n", (unsigned)value);
> +        s->dbg[1] = value;
> +        break;
> +    default:
> +        hw_error("pl330: bad write offset " TARGET_FMT_plx "\n", offset);
> +        break;
> +    }
> +}
> +
> +static inline uint32_t pl330_iomem_read_imp(void *opaque,
> +        target_phys_addr_t offset)
> +{
> +    PL330 *s = (PL330 *) opaque;
> +    int chan_id;
> +    int i;
> +    uint32_t res;
> +
> +    if (offset & 3) {
> +        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +    }
> +
> +    if (offset >= PL330_REG_PERIPH_ID && offset < PL330_REG_PERIPH_ID + 32) {
> +        return pl330_id[(offset - PL330_REG_PERIPH_ID) >> 2];
> +    }
> +    if (offset >= PL330_REG_CR0_BASE && offset < PL330_REG_CR0_BASE + 24) {
> +        return s->cfg[(offset - PL330_REG_CR0_BASE) >> 2];
> +    }
> +    if (offset >= PL330_REG_CHANCTRL && offset < PL330_REG_DBGSTATUS) {
> +        offset -= PL330_REG_CHANCTRL;
> +        chan_id = offset >> 5;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +        }
> +        switch (offset & 0x1f) {
> +        case 0x00:
> +            return s->chan[chan_id].src;
> +        case 0x04:
> +            return s->chan[chan_id].dst;
> +        case 0x08:
> +            return s->chan[chan_id].control;
> +        case 0x0C:
> +            return s->chan[chan_id].lc[0];
> +        case 0x10:
> +            return s->chan[chan_id].lc[1];
> +        default:
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +        }
> +    }
> +    if (offset >= PL330_REG_CSR_BASE && offset < 0x400) {
> +        offset -= PL330_REG_CSR_BASE;
> +        chan_id = offset >> 3;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +        }
> +        switch ((offset >> 2) & 1) {
> +        case 0x0:
> +            res = (s->chan[chan_id].ns << 21) |
> +                    (s->chan[chan_id].wakeup << 4) |
> +                    (s->chan[chan_id].state) |
> +                    (s->chan[chan_id].wfp_sbp << 14);
> +            return res;
> +        case 0x1:
> +            return s->chan[chan_id].pc;
> +        default:
> +            hw_error("pl330: read error\n");
> +        }
> +    }
> +    if (offset >= PL330_REG_FTR_BASE && offset < 0x100) {
> +        offset -= PL330_REG_FTR_BASE;
> +        chan_id = offset >> 2;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +        }
> +        return s->chan[chan_id].fault_type;
> +    }
> +    switch (offset) {
> +    case PL330_REG_DSR:
> +        return (s->manager.ns << 9) | (s->manager.wakeup << 4) |
> +            (s->manager.state & 0xf);
> +    case PL330_REG_DPC:
> +        return s->manager.pc;
> +    case PL330_REG_INTEN:
> +        return s->inten;
> +    case PL330_REG_INT_EVENT_RIS:
> +        return s->ev_status;
> +    case PL330_REG_INTMIS:
> +        return s->int_status;
> +    case PL330_REG_INTCLR:
> +        /* Documentation says that we can't read this register
> +         * but linux kernel does it */
> +        return 0;
> +    case PL330_REG_FSRD:
> +        return s->manager.state ? 1 : 0;
> +    case PL330_REG_FSRC:
> +        res = 0;
> +        for (i = 0; i < s->chan_num; i++) {
> +            if (s->chan[i].state == pl330_chan_fault ||
> +                s->chan[i].state == pl330_chan_fault_completing) {
> +                res |= 1 << i;
> +            }
> +        }
> +        return res;
> +    case PL330_REG_FTRD:
> +        return s->manager.fault_type;
> +    case PL330_REG_DBGSTATUS:
> +        return s->debug_status;
> +    default:
> +        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +    }
> +    return 0;
> +}
> +
> +static uint64_t pl330_iomem_read(void *opaque, target_phys_addr_t offset,
> +        unsigned size)
> +{
> +    int ret = pl330_iomem_read_imp(opaque, offset);
> +    DB_PRINT("addr: %08x data: %08x\n", offset, ret);
> +    return ret;
> +}
> +
> +static const MemoryRegionOps pl330_ops = {
> +    .read = pl330_iomem_read,
> +    .write = pl330_iomem_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +/* Controller logic and initialization */
> +
> +static void pl330_chan_reset(PL330Chan *ch)
> +{
> +    ch->src = 0;
> +    ch->dst = 0;
> +    ch->pc = 0;
> +    ch->state = pl330_chan_stopped;
> +    ch->watchdog_timer = 0;
> +    ch->stall = 0;
> +    ch->control = 0;
> +    ch->status = 0;
> +    ch->fault_type = 0;
> +}
> +
> +static void pl330_reset(DeviceState *d)
> +{
> +    int i;
> +    PL330 *s = FROM_SYSBUS(PL330, sysbus_from_qdev(d));
> +
> +    s->inten = 0;
> +    s->int_status = 0;
> +    s->ev_status = 0;
> +    s->debug_status = 0;
> +    s->num_faulting = 0;
> +    pl330_fifo_reset(&s->fifo);
> +    pl330_queue_reset(&s->read_queue);
> +    pl330_queue_reset(&s->write_queue);
> +
> +    for (i = 0; i < s->chan_num; i++) {
> +        pl330_chan_reset(&s->chan[i]);
> +    }
> +    for (i = 0; i < s->periph_num; i++) {
> +        s->periph_busy[i] = 0;
> +    }
> +
> +    qemu_del_timer(s->timer);
> +}
> +
> +static int pl330_init(SysBusDevice *dev)
> +{
> +    int i;
> +    PL330 *s = FROM_SYSBUS(PL330, dev);
> +
> +    sysbus_init_irq(dev, &s->irq_abort);
> +    memory_region_init_io(&s->iomem, &pl330_ops, s, "dma", PL330_IOMEM_SIZE);
> +    sysbus_init_mmio(dev, &s->iomem);
> +
> +    s->timer = qemu_new_timer_ns(vm_clock, pl330_exec_cycle_timer, s);
> +
> +    s->chan_num = ((s->cfg[0] >> 4) & 7) + 1;
> +    s->chan = g_new0(PL330Chan, s->chan_num);
> +        DB_PRINT("manager@%p\n", &s->manager);
> +    for (i = 0; i < s->chan_num; i++) {
> +        DB_PRINT("channel %d@%p\n", i, &s->chan[i]);
> +        s->chan[i].parent = s;
> +        s->chan[i].tag = (uint8_t)i;
> +    }
> +    s->manager.parent = s;
> +    s->manager.tag = s->chan_num;
> +    s->manager.ns = (s->cfg[0] >> 2) & 1;
> +    s->manager.is_manager = 1;
> +    if (s->cfg[0] & 1) {
> +        s->periph_num = ((s->cfg[0] >> 12) & 0x1f) + 1;
> +    } else {
> +        s->periph_num = 0;
> +    }
> +    s->event_num = ((s->cfg[0] >> 17) & 0x1f) + 1;
> +
> +    s->irq = g_new0(qemu_irq, s->event_num);
> +    for (i = 0; i < s->event_num; i++) {
> +        sysbus_init_irq(dev, &s->irq[i]);
> +    }
> +
> +    qdev_init_gpio_in(&dev->qdev, pl330_dma_stop_irq, PL330_PERIPH_NUM);
> +
> +    pl330_queue_init(&s->read_queue, ((s->cfg[5] >> 16) & 0xf) + 1,
> +                                                        s->chan_num);
> +    pl330_queue_init(&s->write_queue, ((s->cfg[5] >> 8) & 0xf) + 1,
> +                                                        s->chan_num);
> +    pl330_fifo_init(&s->fifo, ((s->cfg[5] >> 20) & 0x1ff) + 1);
> +
> +    return 0;
> +}
> +
> +static Property pl330_properties[] = {
> +    DEFINE_PROP_UINT32("cfg0", PL330, cfg[0], 0),
> +    DEFINE_PROP_UINT32("cfg1", PL330, cfg[1], 0),
> +    DEFINE_PROP_UINT32("cfg2", PL330, cfg[2], 0),
> +    DEFINE_PROP_UINT32("cfg3", PL330, cfg[3], 0),
> +    DEFINE_PROP_UINT32("cfg4", PL330, cfg[4], 0),
> +    DEFINE_PROP_UINT32("cfg5", PL330, cfg[5], 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void pl330_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +
> +    k->init = pl330_init;
> +    dc->reset = pl330_reset;
> +    dc->props = pl330_properties;
> +}
> +
> +static TypeInfo pl330_info = {
> +    .name           = "pl330",
> +    .parent         = TYPE_SYS_BUS_DEVICE,
> +    .instance_size  = sizeof(PL330),
> +    .class_init      = pl330_class_init,
> +};
> +
> +static void pl330_register_types(void)
> +{
> +    type_register_static(&pl330_info);
> +}
> +
> +type_init(pl330_register_types)
Peter A. G. Crosthwaite - July 5, 2012, 4:44 a.m.
On Tue, Jul 3, 2012 at 9:30 PM, Igor Mitsyanko <i.mitsyanko@samsung.com> wrote:
> Hi Peter, here's a few review comments for your patch
>
>
> On 06/18/2012 04:42 AM, Peter A. G. Crosthwaite wrote:
>
> Device model for Primecell PL330 dma controller.
>
> Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
> Signed-off-by: Kirill Batuzov <batuzovk@ispras.ru>
> ---
> changed from v3:
> rebased against Makefile refactor
> changed from v2 (in order of diff appearance):
> GPL version changed to v2 or later
> Register and field names changed to match TRM
> id field corrected
> PL330ChanState enum corrected
> target_phys_addr_t usages removed
> identifier camel casing and underscoring fixed
> data fifo reworked (uses head ptr and a occupancy count rather than head &
> tail ptrs)
> pl330_insn_from_queue->pl330_queue_remove_insn
> Numerous typos in comments corrected as per review
> g_malloc0(sizeof(a) * b) -> g_new
> pl330_fifo_inc: changed to accept PL330Fifo struct as argument (rather than
> buf_size)
> pl330_fifo_push/get: rollback on overflow mechanism replaced with pre-check
> pl330_queue_find_insn: fixed ugly if statement whitespace (PMM review)
> pl330_dmaaddh: fixed arugment reversal
> pl330_dmaaddh: asserted on !manager
> "ns": change to be and behave like a boolean globally
> pl330_dmald: force num=1 when PL330_SINGLE
> pl330_dma_kill: put in correct alphabet order
> pl330_dmarmb/wmb:implemented
> pl330_exec_insn: asserted insn->size not too big
> pl330_exec_insn: enabled stall mechanism outside exec state (needed for
> rmb/wmb)
> pl330_debug_exec: assert fault type on a fault
> pl330_iomem_write: removed aligmnent assertion
> pl330_iomem_write: corrected ev_status and irq_status logic
> pl330_iomem_read_imp: fixed some incorrect bitfield positions (PMM review)
> pl330_reset: stopped timer
> pl330_init: removed redundant reset
> changed from v1:
> GPLv2 license
> some code formatting fixes
>
>  hw/arm/Makefile.objs |    1 +
>  hw/pl330.c           | 1411
> ++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 1412 insertions(+), 0 deletions(-)
>  create mode 100644 hw/pl330.c
>
> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
> index a0ff6a6..b28e5b7 100644
> --- a/hw/arm/Makefile.objs
> +++ b/hw/arm/Makefile.objs
> @@ -7,6 +7,7 @@ obj-y += cadence_ttc.o
>  obj-y += cadence_gem.o
>  obj-y += xilinx_zynq.o zynq_slcr.o
>  obj-y += arm_gic.o
> +obj-y += pl330.o
>  obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
>  obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
>  obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
> diff --git a/hw/pl330.c b/hw/pl330.c
> new file mode 100644
> index 0000000..bc6bc35
> --- /dev/null
> +++ b/hw/pl330.c
> @@ -0,0 +1,1411 @@
> +/*
> + * ARM PrimeCell PL330 DMA Controller
> + *
> + * Copyright (c) 2009 Samsung Electronics.
> + * Contributed by Kirill Batuzov <batuzovk@ispras.ru>
> + * Copyright (c) 2012 Peter A.G. Crosthwaite
> (peter.crosthwaite@petalogix.com)
> + * Copyright (c) 2012 PetaLogix Pty Ltd.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; version 2 or later.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "sysbus.h"
> +#include "qemu-timer.h"
> +
> +#ifdef PL330_ERR_DEBUG
> +#define DB_PRINT(...) do { \
> +    fprintf(stderr,  ": %s: ", __func__); \
> +    fprintf(stderr, ## __VA_ARGS__); \
> +    } while (0);
> +#else
> +    #define DB_PRINT(...)
> +#endif
> +
> +#define PL330_CHAN_NUM              8
> +#define PL330_PERIPH_NUM            32
> +#define PL330_MAX_BURST_LEN         128
> +#define PL330_INSN_MAXSIZE          6
> +
> +#define PL330_FIFO_OK               0
> +#define PL330_FIFO_STALL            1
> +#define PL330_FIFO_ERR              (-1)
> +
> +#define PL330_FAULT_UNDEF_INSTR             (1 <<  0)
> +#define PL330_FAULT_OPERAND_INVALID         (1 <<  1)
> +#define PL330_FAULT_DMAGO_ERR               (1 <<  4)
> +#define PL330_FAULT_EVENT_ERR               (1 <<  5)
> +#define PL330_FAULT_CH_PERIPH_ERR           (1 <<  6)
> +#define PL330_FAULT_CH_RDWR_ERR             (1 <<  7)
> +#define PL330_FAULT_ST_DATA_UNAVAILABLE     (1 << 12)
> +#define PL330_FAULT_FIFOEMPTY_ERR           (1 << 13)
> +#define PL330_FAULT_INSTR_FETCH_ERR         (1 << 16)
> +#define PL330_FAULT_DATA_WRITE_ERR          (1 << 17)
> +#define PL330_FAULT_DATA_READ_ERR           (1 << 18)
> +#define PL330_FAULT_DBG_INSTR               (1 << 30)
> +#define PL330_FAULT_LOCKUP_ERR              (1 << 31)
> +
> +#define PL330_UNTAGGED              0xff
> +
> +#define PL330_SINGLE                0x0
> +#define PL330_BURST                 0x1
> +
> +#define PL330_WATCHDOG_LIMIT        1024
> +
> +/* IOMEM mapped registers */
> +#define PL330_REG_DSR               0x000
> +#define PL330_REG_DPC               0x004
> +#define PL330_REG_INTEN             0x020
> +#define PL330_REG_INT_EVENT_RIS     0x024
> +#define PL330_REG_INTMIS            0x028
> +#define PL330_REG_INTCLR            0x02C
> +#define PL330_REG_FSRD              0x030
> +#define PL330_REG_FSRC              0x034
> +#define PL330_REG_FTRD              0x038
> +#define PL330_REG_FTR_BASE          0x040
> +#define PL330_REG_CSR_BASE          0x100
> +#define PL330_REG_CPC_BASE          0x104
> +#define PL330_REG_CHANCTRL          0x400
> +#define PL330_REG_DBGSTATUS         0xD00
> +#define PL330_REG_DBGCMD            0xD04
> +#define PL330_REG_DBGINST0          0xD08
> +#define PL330_REG_DBGINST1          0xD0C
> +#define PL330_REG_CR0_BASE          0xE00
> +#define PL330_REG_PERIPH_ID         0xFE0
> +
> +#define PL330_IOMEM_SIZE    0x1000
> +
> +static const uint32_t pl330_id[] = {
> +    0x30, 0x13, 0x04, 0x00, 0x0D, 0xF0, 0x05, 0xB1
> +};
> +
> +/* DMA chanel states as they are described in PL330 Technical Reference
> Manual
> + * Most of them will not be used in emulation.
> + */
> +typedef enum  {
> +    pl330_chan_stopped = 0,
> +    pl330_chan_executing = 1,
> +    pl330_chan_cache_miss = 2,
> +    pl330_chan_updating_pc = 3,
> +    pl330_chan_waiting_event = 4,
> +    pl330_chan_at_barrier = 5,
> +    pl330_chan_queue_busy = 6,
> +    pl330_chan_waiting_periph = 7,
> +    pl330_chan_killing = 8,
> +    pl330_chan_completing = 9,
> +    pl330_chan_fault_completing = 14,
> +    pl330_chan_fault = 15,
> +} PL330ChanState;
> +
> +typedef struct PL330 PL330;
> +
> +typedef struct PL330Chan {
> +    uint32_t src;
> +    uint32_t dst;
> +    uint32_t pc;
> +    uint32_t control;
> +    uint32_t status;
> +    uint32_t lc[2];
> +    uint32_t fault_type;
> +
> +    bool ns;
> +    bool is_manager;
> +    uint8_t request_flag;
> +    uint8_t wakeup;
> +    uint8_t wfp_sbp;
> +
> +    PL330ChanState state;
> +    uint8_t stall;
> +
> +    PL330 *parent;
> +    uint8_t tag;
> +    uint32_t watchdog_timer;
> +} PL330Chan;
> +
> +typedef struct PL330Fifo {
> +    uint8_t *buf;
> +    uint8_t *tag;
> +    int s, n;
> +    int buf_size;
> +} PL330Fifo;
> +
> +typedef struct PL330QueueEntry {
> +    PL330Chan *c;
> +    target_phys_addr_t addr;
> +    int len;
> +    int n;
> +    int inc;
> +    int z;
> +    uint8_t tag;
> +    uint8_t seqn;
> +} PL330QueueEntry;
> +
> +typedef struct PL330Queue {
> +    PL330QueueEntry *queue;
> +    int queue_size;
> +    uint8_t *lo_seqn;
> +    uint8_t *hi_seqn;
> +} PL330Queue;
> +
> +struct PL330 {
> +    SysBusDevice busdev;
> +    MemoryRegion iomem;
> +
> +    PL330Chan manager;
> +    PL330Chan *chan;
> +    PL330Fifo fifo;
> +    PL330Queue read_queue;
> +    PL330Queue write_queue;
> +
> +    /* Config registers. cfg[5] = CfgDn. */
> +    uint32_t inten;
> +    uint32_t int_status;
> +    uint32_t ev_status;
> +    uint32_t cfg[6];
> +    uint32_t dbg[2];
> +    uint8_t debug_status;
> +
> +    qemu_irq irq_abort;
> +    qemu_irq *irq;
> +
> +    uint8_t num_faulting;
> +
> +    int chan_num;
> +    int periph_num;
> +    unsigned int event_num;
> +
> +    QEMUTimer *timer; /* is used for restore dma. */
> +    int8_t periph_busy[PL330_PERIPH_NUM];
> +};
> +
> +typedef struct PL330InsnDesc {
> +    /* OPCODE of the instruction */
> +    uint8_t opcode;
> +    /* Mask so we can select several sibling instructions, such as
> +       DMALD, DMALDS and DMALDB */
> +    uint8_t opmask;
> +    /* Size of instruction in bytes */
> +    uint8_t size;
> +    /* Interpreter */
> +    void (*exec)(PL330Chan *, uint8_t opcode, uint8_t *args, int len);
> +} PL330InsnDesc;
> +
> +
> +/* MFIFO Implementation
> + *
> + * MFIFO is implemented as a cyclic buffer of BUF_SIZE size. Tagged bytes
> are
> + * stored in this buffer. Data is stored in BUF field, tags - in the
> + * corresponding array elements of TAG field.
> + */
> +
> +/* Initialize queue. */
> +static void pl330_fifo_init(PL330Fifo *s, uint32_t size)
> +{
> +    s->buf = g_malloc0(size);
> +    s->tag = g_malloc0(size);
> +    s->buf_size = size;
> +}
> +
> +/* Cyclic increment */
> +static inline int pl330_fifo_inc(PL330Fifo *s, int x)
> +{
> +    return (x + 1) % s->buf_size;
> +}
> +
> +/* Number of empty bytes in MFIFO */
> +static inline int pl330_fifo_num_free(PL330Fifo *s)
> +{
> +    return s->buf_size - s->n;
> +}
> +
> +/* Push LEN bytes of data stored in BUF to MFIFO and tag it with TAG.
> + * Zero returned on success, PL330_FIFO_STALL if there is no enough free
> + * space in MFIFO to store requested amount of data. If push was
> unsuccessful
> + * no data is stored to MFIFO.
> + */
> +static int pl330_fifo_push(PL330Fifo *s, uint8_t *buf, int len, uint8_t
> tag)
> +{
> +    int i;
> +
> +    if (s->buf_size - s->n < len) {
> +        return PL330_FIFO_STALL;
> +    }
> +    for (i = 0; i < len; i++) {
> +        int push_idx = (s->s + i) % s->buf_size;
> +        s->buf[push_idx] = buf[i];
> +        s->tag[push_idx] = tag;
> +    }
> +    s->n += len;
> +    return PL330_FIFO_OK;
> +}
> +
> +/* Get LEN bytes of data from MFIFO and store it to BUF. Tag value of each
> + * byte is verified. Zero returned on success, PL330_FIFO_ERR on tag
> mismatch
> + * and PL330_FIFO_STALL if there is no enough data in MFIFO. If get was
> + * unsuccessful no data is removed from MFIFO.
> + */
> +static int pl330_fifo_get(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
> +{
> +    int i;
> +
> +    if (s->n < len) {
> +        return PL330_FIFO_STALL;
> +    }
> +    for (i = 0; i < len; i++) {
> +        if (s->tag[s->s] == tag) {
> +            int get_idx = (s->s + i) % s->buf_size;
> +            buf[i] = s->buf[get_idx];
> +        } else { /* Tag mismatch - Rollback transaction */
> +            return PL330_FIFO_ERR;
> +        }
> +    }
> +    s->s = (s->s + len) % s->buf_size;
> +    s->n -= len;
> +    return PL330_FIFO_OK;
> +}
> +
> +/* Reset MFIFO. This completely erases all data in it. */
> +static inline void pl330_fifo_reset(PL330Fifo *s)
> +{
> +    s->s = 0;
> +    s->n = 0;
> +}
> +
> +/* Return tag of the first byte stored in MFIFO. If MFIFO is empty
> +   PL330_UNTAGGED is returned. */
> +static inline uint8_t pl330_fifo_tag(PL330Fifo *s)
> +{
> +    return (!s->n) ? PL330_UNTAGGED : s->tag[s->s];
> +}
> +
> +/* Returns non-zero if tag TAG is present in fifo or zero otherwise */
> +static int pl330_fifo_has_tag(PL330Fifo *s, uint8_t tag)
> +{
> +    int i, n;
> +
> +    i = s->s;
> +    for (n = 0; n < s->n; n++) {
> +        if (s->tag[i] == tag) {
> +            return 1;
> +        }
> +        i = pl330_fifo_inc(s, i);
> +    }
> +    return 0;
> +}
> +
> +/* Remove all entry tagged with TAG from MFIFO */
> +static void pl330_fifo_tagged_remove(PL330Fifo *s, uint8_t tag)
> +{
> +    int i, t, n;
> +
> +    t = i = s->s;
> +    for (n = 0; n < s->n; n++) {
> +        if (s->tag[i] != tag) {
> +            s->buf[t] = s->buf[i];
> +            s->tag[t] = s->tag[i];
> +            t = pl330_fifo_inc(s, t);
> +        } else {
> +            s->n = s->n - 1;
> +        }
> +        i = pl330_fifo_inc(s, i);
> +    }
> +}
> +
> +/* Read-Write Queue implementation
> + *
> + * A Read-Write Queue stores up to QUEUE_SIZE instructions (loads or
> stores).
> + * Each instruction is described by source (for loads) or destination (for
> + * stores) address ADDR, width of data to be loaded/stored LEN, number of
> + * stores/loads to be performed N, INC bit, Z bit and TAG to identify
> channel
> + * this instruction belongs to. Queue does not store any information about
> + * nature of the instruction: is it load or store. PL330 has different
> queues
> + * for loads and stores so this is already known at the top level where it
> + * matters.
> + *
> + * Queue works as FIFO for instructions with equivalent tags, but can issue
> + * instructions with different tags in arbitrary order. SEQN field attached
> to
> + * each instruction helps to achieve this. For each TAG queue contains
> + * instructions with consecutive SEQN values ranging from LO_SEQN[TAG] to
> + * HI_SEQN[TAG]-1 inclusive. SEQN is 8-bit unsigned integer, so SEQN=255 is
> + * followed by SEQN=0.
> + *
> + * Z bit indicates that zeroes should be stored. No MFIFO fetches are
> performed
> + * in this case.
> + */
> +
> +static void pl330_queue_reset(PL330Queue *s)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        s->queue[i].tag = PL330_UNTAGGED;
> +    }
> +}
> +
> +/* Initialize queue */
> +static void pl330_queue_init(PL330Queue *s, int size, int channum)
> +{
> +    s->queue = (PL330QueueEntry *)g_new0(PL330QueueEntry, size);
> +    s->lo_seqn = (uint8_t *)g_malloc0(channum);
> +    s->hi_seqn = (uint8_t *)g_malloc0(channum);
> +    s->queue_size = size;
> +}
> +
> +/* Returns pointer to an empty slot or NULL if queue is full */
> +static PL330QueueEntry *pl330_queue_find_empty(PL330Queue *s)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag == PL330_UNTAGGED) {
> +            return &s->queue[i];
> +        }
> +    }
> +    return NULL;
> +}
> +
> +/* Put instruction to queue.
> +   Return value:
> +     - zero - OK
> +     - non-zero - queue is full */
> +static int pl330_queue_put_insn(PL330Queue *s, target_phys_addr_t addr,
> +                               int len, int n, int inc, int z, uint8_t tag,
> +                               PL330Chan *c)
> +{
> +    PL330QueueEntry *entry = pl330_queue_find_empty(s);
> +
> +    if (!entry) {
> +        return 1;
> +    }
> +    entry->c = c;
> +    entry->tag = tag;
> +    entry->addr = addr;
> +    entry->len = len;
> +    entry->n = n;
> +    entry->z = z;
> +    entry->inc = inc;
> +    entry->seqn = s->hi_seqn[tag];
> +    s->hi_seqn[tag]++;
> +    return 0;
> +}
> +
> +/* Returns a pointer to queue slot containing instruction which satisfies
> +   following conditions:
> +    - it has valid tag value (not PL330_UNTAGGED)
> +    - it can be issued without violating queue logic (see above)
> +    - if TAG argument is not PL330_UNTAGGED this instruction has tag value
> +      equivalent to the argument TAG value.
> +   If such instruction cannot be found NULL is returned. */
> +static PL330QueueEntry *pl330_queue_find_insn(PL330Queue *s, uint8_t tag)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag != PL330_UNTAGGED) {
> +            if (s->queue[i].seqn == s->lo_seqn[s->queue[i].tag] &&
> +                    (s->queue[i].tag == tag || tag == PL330_UNTAGGED ||
> +                    s->queue[i].z)) {
> +                return &s->queue[i];
> +            }
> +        }
> +    }
> +    return NULL;
> +}
> +
> +/* Removes instruction from queue. */
> +static inline void pl330_queue_remove_insn(PL330Queue *s, PL330QueueEntry
> *e)
> +{
> +    s->lo_seqn[e->tag]++;
> +    e->tag = PL330_UNTAGGED;
> +}
> +
> +/* Removes all instructions tagged with TAG from queue. */
> +static inline void pl330_queue_remove_tagged(PL330Queue *s, uint8_t tag)
> +{
> +    int i;
> +
> +    for (i = 0; i < s->queue_size; i++) {
> +        if (s->queue[i].tag == tag) {
> +            s->queue[i].tag = PL330_UNTAGGED;
> +        }
> +    }
> +}
> +
> +
> +/* DMA instruction execution engine */
> +
> +/* Moves DMA channel to the FAULT state and updates it's status. */
> +static inline void pl330_fault(PL330Chan *ch, uint32_t flags)
> +{
> +    DB_PRINT("ch: %p, flags: %x\n", ch, flags);
> +    ch->fault_type |= flags;
> +    if (ch->state == pl330_chan_fault) {
> +        return;
> +    }
> +    ch->state = pl330_chan_fault;
> +    ch->parent->num_faulting++;
> +    if (ch->parent->num_faulting == 1) {
> +        DB_PRINT("abort interrupt raised\n");
> +        qemu_irq_raise(ch->parent->irq_abort);
> +    }
> +}
> +
> +/*
> + * For information about instructions see PL330 Technical Reference Manual.
> + *
> + * Arguments:
> + *   CH - channel executing the instruction
> + *   OPCODE - opcode
> + *   ARGS - array of 8-bit arguments
> + *   LEN - number of elements in ARGS array
> + */
> +static void pl330_dmaaddh(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    uint16_t im = (((uint16_t)args[1]) << 8) | ((uint16_t)args[0]);
> +    uint8_t ra = (opcode >> 1) & 1;
> +
> +    if (ch->is_manager) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>
> more likely to cause PL330_FAULT_UNDEF_INSTR
>

Ok

> +        return;
> +    }
> +    if (ra) {
> +        ch->dst += im;
> +    } else {
> +        ch->src += im;
> +    }
> +}
> +
> +static void pl330_dmaend(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    if (ch->state == pl330_chan_executing && !ch->is_manager) {
> +        /* Wait for all transfers to complete */
> +        if (pl330_fifo_has_tag(&ch->parent->fifo, ch->tag) ||
> +            pl330_queue_find_insn(&ch->parent->read_queue, ch->tag) != NULL
> ||
> +            pl330_queue_find_insn(&ch->parent->write_queue, ch->tag) !=
> NULL) {
> +
> +            ch->stall = 1;
> +            return;
> +        }
> +    }
> +    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
> +    ch->state = pl330_chan_stopped;
> +}
> +
> +static void pl330_dmaflushp(PL330Chan *ch, uint8_t opcode,
> +                                            uint8_t *args, int len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    /* Do nothing */
> +}
> +
> +static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    uint8_t chan_id;
> +    uint8_t ns;
> +    uint32_t pc;
> +    PL330Chan *s;
> +
> +    DB_PRINT("\n");
> +
> +    if (!ch->is_manager) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ns = !!(opcode & 2);
> +    chan_id = args[0] & 7;
> +    if ((args[0] >> 3)) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (chan_id >= ch->parent->chan_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    pc = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
> +         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
> +    if (ch->parent->chan[chan_id].state != pl330_chan_stopped) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !ns) {
> +        pl330_fault(ch, PL330_FAULT_DMAGO_ERR);
> +        return;
> +    }
> +    s = &ch->parent->chan[chan_id];
> +    s->ns = ns;
> +    s->pc = pc;
> +    s->state = pl330_chan_executing;
> +}
> +
> +static void pl330_dmald(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint32_t size, num, inc;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    if (bs == 1 && ch->request_flag == PL330_SINGLE) {
> +        num = 1;
> +    } else {
> +        num = ((ch->control >> 4) & 0xf) + 1;
> +    }
> +    size = (uint32_t)1 << ((ch->control >> 1) & 0x7);
> +    inc = ch->control & 1;
> +    ch->src &= ~(size - 1);
>
> Why are you doing that here, I couldn't find anywhere in TRM that PL330
> automatically aligns
> addresses when executing instructions. The only note I've found is that
> fixed transfers from unaligned addresses are considered a programming error.
>

Determined by experiment with my real hardware. Annoyingly, I have a
test case that does this.

> +    ch->stall = pl330_queue_put_insn(&ch->parent->read_queue, ch->src,
> +                                    size, num, inc, 0, ch->tag, ch);
> +    if (inc & !ch->stall) {
> +        ch->src += size * num;
> +    }
> +}
> +
> +static void pl330_dmaldp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    pl330_dmald(ch, opcode, args, len);
> +}
> +
> +static void pl330_dmalp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    uint8_t lc = (opcode & 2) >> 1;
> +
> +    ch->lc[lc] = args[0];
> +}
> +
> +static void pl330_dmakill(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    if (ch->state == pl330_chan_fault ||
> +        ch->state == pl330_chan_fault_completing) {
> +        /* This is the only way for a channel to leave the faulting state
> */
> +        ch->fault_type = 0;
> +        ch->parent->num_faulting--;
> +        if (ch->parent->num_faulting == 0) {
> +            DB_PRINT("abort interrupt lowered\n");
> +            qemu_irq_lower(ch->parent->irq_abort);
> +        }
> +    }
> +    ch->state = pl330_chan_killing;
> +    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
> +    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
> +    ch->state = pl330_chan_stopped;
> +}
> +
> +static void pl330_dmalpend(PL330Chan *ch, uint8_t opcode,
> +                                    uint8_t *args, int len)
> +{
> +    uint8_t nf = (opcode & 0x10) >> 4;
> +    uint8_t bs = opcode & 3;
> +    uint8_t lc = (opcode & 4) >> 2;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    if (!nf || ch->lc[lc]) {
> +        if (nf) {
> +            ch->lc[lc]--;
> +        }
> +        ch->pc -= args[0];
> +        ch->pc -= len + 1;
> +        /* "ch->pc -= args[0] + len + 1" is incorrect when args[0] == 256
> */
> +    }
> +}
> +
> +
> +static void pl330_dmamov(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    uint8_t rd = args[0] & 7;
> +    uint32_t im;
> +
> +    if ((args[0] >> 3)) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    im = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
> +         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
> +    switch (rd) {
> +    case 0:
> +        ch->src = im;
> +        break;
> +    case 1:
> +        ch->control = im;
> +        break;
> +    case 2:
> +        ch->dst = im;
> +        break;
> +    default:
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +}
> +
> +static void pl330_dmanop(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    /* NOP is NOP. */
> +}
> +
> +static void pl330_dmarmb(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +   if (pl330_queue_find_insn(&ch->parent->read_queue, ch->tag)) {
> +        ch->state = pl330_chan_at_barrier;
> +        ch->stall = 1;
> +        return;
> +    }
> +}
> +
> +static void pl330_dmasev(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    uint8_t ev_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ev_id = (args[0] >> 3) & 0x1f;
> +    if (ev_id >= ch->parent->event_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
> +        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
> +        return;
> +    }
> +    if (ch->parent->inten & (1 << ev_id)) {
> +        ch->parent->int_status |= (1 << ev_id);
> +        DB_PRINT("event interrupt raised %d\n", ev_id);
> +        qemu_irq_raise(ch->parent->irq[ev_id]);
> +    } else {
> +        ch->parent->ev_status |= (1 << ev_id);
>
> Shouldn't you check if any threads were waiting for that event and move them
> to execution state if they were?
>

No, because dma_wfe polls ev_status so just setting it is enough.

>
> +    }
> +}
> +
> +static void pl330_dmast(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
> len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint32_t size, num, inc;
> +
> +    if (bs == 2) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
> +        /* Perform NOP */
> +        return;
> +    }
> +    num = ((ch->control >> 18) & 0xf) + 1;
>
> DMAC ignores dst_burst_len field in ch->control if condition
>
> (bs == 1 && ch->request_flag == PL330_SINGLE)
>
> is true
>

Cant follow your comment. Can you elaborate?

> +    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
> +    inc = (ch->control >> 14) & 1;
> +    ch->dst &= ~(size - 1);
> +    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
> +                                    size, num, inc, 0, ch->tag, ch);
> +    if (inc) {
> +        ch->dst += size * num;
> +    }
> +}
> +
> +static void pl330_dmastp(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    pl330_dmast(ch, opcode, args, len);
> +}
> +
> +static void pl330_dmastz(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint32_t size, num, inc;
> +
> +    num = ((ch->control >> 18) & 0xf) + 1;
> +    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
> +    inc = (ch->control >> 14) & 1;
> +    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
> +                                    size, num, inc, 1, ch->tag, ch);
> +    if (inc) {
> +        ch->dst += size * num;
> +    }
> +}
> +
> +static void pl330_dmawfe(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t ev_id;
> +
> +    if (args[0] & 5) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    ev_id = (args[0] >> 3) & 0x1f;
> +    if (ev_id >= ch->parent->event_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
> +        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
> +        return;
> +    }
> +    ch->wakeup = ev_id;
> +    ch->state = pl330_chan_waiting_event;
> +    if (ch->parent->inten & ch->parent->ev_status & 1 << ev_id) {
> +        ch->parent->ev_status &= ~(1 << ev_id);
>
> ev_status should be cleared if corresponding bit in inten is 0, not 1.

ACK

 And I
> think channel thread
> should reenter execution state if that event was already pending in
> ev_status?
>

It does :). Note that ch->stall is else'd to this condition, so if the
event is triggered dmawfe is us-stalled (reentering the exec state).

> Overall, I don't see that DMAWFE and DMASEV are working correctly here,
> maybe because I'm missing something and misunderstand what ch->stall is
> supposed to be used for, but still its not such a big issue.
>
> +    } else {
> +        ch->stall = 1;
> +    }
> +}
> +
> +static void pl330_dmawfp(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    uint8_t bs = opcode & 3;
> +    uint8_t periph_id;
> +
> +    if (args[0] & 7) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    periph_id = (args[0] >> 3) & 0x1f;
> +    if (periph_id >= ch->parent->periph_num) {
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
> +        return;
> +    }
> +    switch (bs) {
> +    case 0: /* S */
> +        ch->request_flag = PL330_SINGLE;
> +        ch->wfp_sbp = 0;
> +        break;
> +    case 1: /* P */
> +        ch->request_flag = PL330_BURST;
> +        ch->wfp_sbp = 2;
> +        break;
> +    case 2: /* B */
> +        ch->request_flag = PL330_BURST;
> +        ch->wfp_sbp = 1;
> +        break;
> +    default:
> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
> +        return;
> +    }
> +
> +    if (ch->parent->periph_busy[periph_id]) {
> +        ch->state = pl330_chan_waiting_periph;
> +        ch->stall = 1;
> +    } else if (ch->state == pl330_chan_waiting_periph) {
> +        ch->state = pl330_chan_executing;
> +    }
> +}
> +
> +static void pl330_dmawmb(PL330Chan *ch, uint8_t opcode,
> +                         uint8_t *args, int len)
> +{
> +    if (pl330_queue_find_insn(&ch->parent->write_queue, ch->tag)) {
> +        ch->state = pl330_chan_at_barrier;
> +        ch->stall = 1;
> +        return;
> +    }
> +}
> +
> +/* NULL terminated array of the instruction descriptions. */
> +static const PL330InsnDesc insn_desc[] = {
> +    { .opcode = 0x54, .opmask = 0xFD, .size = 3, .exec = pl330_dmaaddh, },
> +    { .opcode = 0x00, .opmask = 0xFF, .size = 1, .exec = pl330_dmaend, },
> +    { .opcode = 0x35, .opmask = 0xFF, .size = 2, .exec = pl330_dmaflushp,
> },
> +    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
> +    { .opcode = 0x04, .opmask = 0xFC, .size = 1, .exec = pl330_dmald, },
> +    { .opcode = 0x25, .opmask = 0xFD, .size = 2, .exec = pl330_dmaldp, },
> +    { .opcode = 0x20, .opmask = 0xFD, .size = 2, .exec = pl330_dmalp, },
> +    /* dmastp  must be before dmalpend in this list, because their maps
> +     * are overlapping
> +     */
> +    { .opcode = 0x29, .opmask = 0xFD, .size = 2, .exec = pl330_dmastp, },
> +    { .opcode = 0x28, .opmask = 0xE8, .size = 2, .exec = pl330_dmalpend, },
> +    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
> +    { .opcode = 0xBC, .opmask = 0xFF, .size = 6, .exec = pl330_dmamov, },
> +    { .opcode = 0x18, .opmask = 0xFF, .size = 1, .exec = pl330_dmanop, },
> +    { .opcode = 0x12, .opmask = 0xFF, .size = 1, .exec = pl330_dmarmb, },
> +    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
> +    { .opcode = 0x08, .opmask = 0xFC, .size = 1, .exec = pl330_dmast, },
> +    { .opcode = 0x0C, .opmask = 0xFF, .size = 1, .exec = pl330_dmastz, },
> +    { .opcode = 0x36, .opmask = 0xFF, .size = 2, .exec = pl330_dmawfe, },
> +    { .opcode = 0x30, .opmask = 0xFC, .size = 2, .exec = pl330_dmawfp, },
> +    { .opcode = 0x13, .opmask = 0xFF, .size = 1, .exec = pl330_dmawmb, },
> +    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
> +};
> +
> +/* Instructions which can be issued via debug registers. */
> +static const PL330InsnDesc debug_insn_desc[] = {
> +    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
> +    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
> +    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
> +    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
> +};
> +
> +static inline const PL330InsnDesc *pl330_fetch_insn(PL330Chan *ch)
> +{
> +    uint8_t opcode;
> +    int i;
> +
> +    cpu_physical_memory_read(ch->pc, &opcode, 1);
> +    for (i = 0; insn_desc[i].size; i++) {
> +        if ((opcode & insn_desc[i].opmask) == insn_desc[i].opcode) {
> +            return &insn_desc[i];
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static inline void pl330_exec_insn(PL330Chan *ch, const PL330InsnDesc
> *insn)
> +{
> +    uint8_t buf[PL330_INSN_MAXSIZE];
> +
> +    assert(insn->size <= PL330_INSN_MAXSIZE);
> +    cpu_physical_memory_read(ch->pc, buf, insn->size);
> +    insn->exec(ch, buf[0], &buf[1], insn->size - 1);
> +}
> +
> +static inline void pl330_update_pc(PL330Chan *ch,
> +                                   const PL330InsnDesc *insn)
> +{
> +    ch->pc += insn->size;
> +}
> +
> +/* Try to execute current instruction in channel CH. Number of executed
> +   instructions is returned (0 or 1). */
> +static int pl330_chan_exec(PL330Chan *ch)
> +{
> +    const PL330InsnDesc *insn;
> +
> +    if (ch->state != pl330_chan_executing &&
> +            ch->state != pl330_chan_waiting_periph &&
> +            ch->state != pl330_chan_at_barrier &&
> +            ch->state != pl330_chan_waiting_event) {
> +        DB_PRINT("%d\n", ch->state);
> +        return 0;
> +    }
> +    ch->stall = 0;
> +    insn = pl330_fetch_insn(ch);
> +    if (!insn) {
> +        DB_PRINT("pl330 undefined instruction\n");
> +        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR);
> +        return 0;
> +    }
> +    pl330_exec_insn(ch, insn);
> +    if (!ch->stall) {
> +        pl330_update_pc(ch, insn);
> +        ch->watchdog_timer = 0;
> +        return 1;
> +    /* WDT only active in exec state */
> +    } else if (ch->state == pl330_chan_executing) {
> +        ch->watchdog_timer++;
> +        if (ch->watchdog_timer >= PL330_WATCHDOG_LIMIT) {
> +            pl330_fault(ch, PL330_FAULT_LOCKUP_ERR);
> +        }
> +    }
> +    return 0;
> +}
> +
> +/* Try to execute 1 instruction in each channel, one instruction from read
> +   queue and one instruction from write queue. Number of successfully
> executed
> +   instructions is returned. */
> +static int pl330_exec_cycle(PL330Chan *channel)
> +{
> +    PL330 *s = channel->parent;
> +    PL330QueueEntry *q;
> +    int i;
> +    int num_exec = 0;
> +    int fifo_res = 0;
> +    uint8_t buf[PL330_MAX_BURST_LEN];
> +
> +    /* Execute one instruction in each channel */
> +    num_exec += pl330_chan_exec(channel);
> +
> +    /* Execute one instruction from read queue */
> +    q = pl330_queue_find_insn(&s->read_queue, PL330_UNTAGGED);
> +    if (q != NULL && q->len <= pl330_fifo_num_free(&s->fifo)) {
> +        cpu_physical_memory_read(q->addr, buf, q->len);
> +        fifo_res = pl330_fifo_push(&s->fifo, buf, q->len, q->tag);
> +        if (fifo_res == PL330_FIFO_OK) {
> +            if (q->inc) {
> +                q->addr += q->len;
> +            }
> +            q->n--;
> +            if (!q->n) {
> +                pl330_queue_remove_insn(&s->read_queue, q);
> +            }
> +            num_exec++;
> +        }
> +    }
> +
> +    /* Execute one instruction from write queue. */
> +    q = pl330_queue_find_insn(&s->write_queue, pl330_fifo_tag(&s->fifo));
> +    if (q != NULL) {
> +        if (q->z) {
> +            for (i = 0; i < q->len; i++) {
> +                buf[i] = 0;
> +            }
> +        } else {
> +            fifo_res = pl330_fifo_get(&s->fifo, buf, q->len, q->tag);
> +        }
> +        if (fifo_res == PL330_FIFO_OK || q->z) {
> +            cpu_physical_memory_write(q->addr, buf, q->len);
> +            if (q->inc) {
> +                q->addr += q->len;
> +            }
> +            num_exec++;
> +        } else if (fifo_res == PL330_FIFO_STALL) {
> +            pl330_fault(q->c, PL330_FAULT_FIFOEMPTY_ERR);
> +        }
> +        q->n--;
> +        if (!q->n) {
> +            pl330_queue_remove_insn(&s->write_queue, q);
> +        }
> +    }
> +
> +    return num_exec;
> +}
> +
> +static int pl330_exec_channel(PL330Chan *channel)
> +{
> +    int insr_exec = 0;
> +
> +    /* TODO: Is it all right to execute everything or should we do
> per-cycle
> +       simulation? */
> +    while (pl330_exec_cycle(channel)) {
> +        insr_exec++;
> +    }
> +
> +    /* Detect deadlock */
> +    if (channel->state == pl330_chan_executing) {
> +        pl330_fault(channel, PL330_FAULT_LOCKUP_ERR);
> +    }
> +    /* Situation when one of the queues has deadlocked but all channels
> +     * have finished their programs should be impossible.
> +     */
> +
> +    return insr_exec;
> +}
> +
> +
> +static inline void pl330_exec(PL330 *s)
> +{
> +    DB_PRINT("\n");
> +    int i, insr_exec;
> +    do {
> +        insr_exec = pl330_exec_channel(&s->manager);
> +
> +        for (i = 0; i < s->chan_num; i++) {
> +            insr_exec += pl330_exec_channel(&s->chan[i]);
> +        }
> +    } while (insr_exec);
> +}
> +
> +static void pl330_exec_cycle_timer(void *opaque)
> +{
> +    PL330 *s = (PL330 *)opaque;
> +    pl330_exec(s);
> +}
> +
> +/* Stop or restore dma operations */
> +static void pl330_dma_stop_irq(void *opaque, int irq, int level)
> +{
> +    PL330 *s = (PL330 *)opaque;
> +
> +    if (s->periph_busy[irq] != level) {
> +        s->periph_busy[irq] = level;
> +        qemu_mod_timer(s->timer, qemu_get_clock_ns(vm_clock));
> +    }
> +}
> +
> +static void pl330_debug_exec(PL330 *s)
> +{
> +    uint8_t args[5];
> +    uint8_t opcode;
> +    uint8_t chan_id;
> +    int i;
> +    PL330Chan *ch;
> +    const PL330InsnDesc *insn;
> +
> +    s->debug_status = 1;
> +    chan_id = (s->dbg[0] >>  8) & 0x07;
> +    opcode  = (s->dbg[0] >> 16) & 0xff;
> +    args[0] = (s->dbg[0] >> 24) & 0xff;
> +    args[1] = (s->dbg[1] >>  0) & 0xff;
> +    args[2] = (s->dbg[1] >>  8) & 0xff;
> +    args[3] = (s->dbg[1] >> 16) & 0xff;
> +    args[4] = (s->dbg[1] >> 24) & 0xff;
> +    DB_PRINT("chan id: %d\n", chan_id);
> +    if (s->dbg[0] & 1) {
> +        ch = &s->chan[chan_id];
> +    } else {
> +        ch = &s->manager;
> +    }
> +    insn = NULL;
> +    for (i = 0; debug_insn_desc[i].size; i++) {
> +        if ((opcode & debug_insn_desc[i].opmask) ==
> debug_insn_desc[i].opcode) {
> +            insn = &debug_insn_desc[i];
> +        }
> +    }
> +    if (!insn) {
> +        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR | PL330_FAULT_DBG_INSTR);
> +        return ;
> +    }
> +    ch->stall = 0;
> +    insn->exec(ch, opcode, args, insn->size - 1);
> +    if (ch->fault_type) {
> +        ch->fault_type |= PL330_FAULT_DBG_INSTR;
> +    }
> +    if (ch->stall) {
> +        hw_error("pl330: stall of debug instruction not implemented\n");
> +    }
> +    s->debug_status = 0;
> +}
> +
> +
> +/* IOMEM mapped registers */
> +
> +static void pl330_iomem_write(void *opaque, target_phys_addr_t offset,
> +                              uint64_t value, unsigned size)
> +{
> +    PL330 *s = (PL330 *) opaque;
> +    uint32_t i;
> +
> +    DB_PRINT("addr: %08x data: %08x\n", offset, (unsigned)value);
> +
> +    switch (offset) {
> +    case PL330_REG_INTEN:
> +        s->inten = value;
> +        break;
> +    case PL330_REG_INTCLR:
> +        for (i = 0; i < s->event_num; i++) {
> +            if (s->int_status & s->inten & value & (1 << i)) {
> +                DB_PRINT("event interrupt lowered %d\n", i);
> +                qemu_irq_lower(s->irq[i]);
> +            }
> +        }
> +        s->ev_status &= ~(value & s->inten);
> +        s->int_status &= ~(value & ~s->inten);
>
> second inversion shouldn't be here I think
>

ACK - Igor, my test vector has no interrupts, Are you able to zip up
and send my your exynos test vectorif its touching interrupt
behaviour?

> +        break;
> +    case PL330_REG_DBGCMD:
> +        if ((value & 3) == 0) {
> +            pl330_debug_exec(s);
> +            pl330_exec(s);
> +        } else {
> +            hw_error("pl330: write of illegal value %u for offset "
> +                     TARGET_FMT_plx "\n", (unsigned)value, offset);
> +        }
> +        break;
> +    case PL330_REG_DBGINST0:
> +        DB_PRINT("s->dbg[0] = %08x\n", (unsigned)value);
> +        s->dbg[0] = value;
> +        break;
> +    case PL330_REG_DBGINST1:
> +        DB_PRINT("s->dbg[1] = %08x\n", (unsigned)value);
> +        s->dbg[1] = value;
> +        break;
> +    default:
> +        hw_error("pl330: bad write offset " TARGET_FMT_plx "\n", offset);
> +        break;
> +    }
> +}
> +
> +static inline uint32_t pl330_iomem_read_imp(void *opaque,
> +        target_phys_addr_t offset)
> +{
> +    PL330 *s = (PL330 *) opaque;
> +    int chan_id;
> +    int i;
> +    uint32_t res;
> +
> +    if (offset & 3) {
> +        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +    }
> +
> +    if (offset >= PL330_REG_PERIPH_ID && offset < PL330_REG_PERIPH_ID + 32)
> {
> +        return pl330_id[(offset - PL330_REG_PERIPH_ID) >> 2];
> +    }
> +    if (offset >= PL330_REG_CR0_BASE && offset < PL330_REG_CR0_BASE + 24) {
> +        return s->cfg[(offset - PL330_REG_CR0_BASE) >> 2];
> +    }
> +    if (offset >= PL330_REG_CHANCTRL && offset < PL330_REG_DBGSTATUS) {
> +        offset -= PL330_REG_CHANCTRL;
> +        chan_id = offset >> 5;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n",
> offset);
> +        }
> +        switch (offset & 0x1f) {
> +        case 0x00:
> +            return s->chan[chan_id].src;
> +        case 0x04:
> +            return s->chan[chan_id].dst;
> +        case 0x08:
> +            return s->chan[chan_id].control;
> +        case 0x0C:
> +            return s->chan[chan_id].lc[0];
> +        case 0x10:
> +            return s->chan[chan_id].lc[1];
> +        default:
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n",
> offset);
> +        }
> +    }
> +    if (offset >= PL330_REG_CSR_BASE && offset < 0x400) {
> +        offset -= PL330_REG_CSR_BASE;
> +        chan_id = offset >> 3;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n",
> offset);
> +        }
> +        switch ((offset >> 2) & 1) {
> +        case 0x0:
> +            res = (s->chan[chan_id].ns << 21) |
> +                    (s->chan[chan_id].wakeup << 4) |
> +                    (s->chan[chan_id].state) |
> +                    (s->chan[chan_id].wfp_sbp << 14);
> +            return res;
> +        case 0x1:
> +            return s->chan[chan_id].pc;
> +        default:
> +            hw_error("pl330: read error\n");
> +        }
> +    }
> +    if (offset >= PL330_REG_FTR_BASE && offset < 0x100) {
> +        offset -= PL330_REG_FTR_BASE;
> +        chan_id = offset >> 2;
> +        if (chan_id >= s->chan_num) {
> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n",
> offset);
> +        }
> +        return s->chan[chan_id].fault_type;
> +    }
> +    switch (offset) {
> +    case PL330_REG_DSR:
> +        return (s->manager.ns << 9) | (s->manager.wakeup << 4) |
> +            (s->manager.state & 0xf);
> +    case PL330_REG_DPC:
> +        return s->manager.pc;
> +    case PL330_REG_INTEN:
> +        return s->inten;
> +    case PL330_REG_INT_EVENT_RIS:
> +        return s->ev_status;
> +    case PL330_REG_INTMIS:
> +        return s->int_status;
> +    case PL330_REG_INTCLR:
> +        /* Documentation says that we can't read this register
> +         * but linux kernel does it */
> +        return 0;
> +    case PL330_REG_FSRD:
> +        return s->manager.state ? 1 : 0;
> +    case PL330_REG_FSRC:
> +        res = 0;
> +        for (i = 0; i < s->chan_num; i++) {
> +            if (s->chan[i].state == pl330_chan_fault ||
> +                s->chan[i].state == pl330_chan_fault_completing) {
> +                res |= 1 << i;
> +            }
> +        }
> +        return res;
> +    case PL330_REG_FTRD:
> +        return s->manager.fault_type;
> +    case PL330_REG_DBGSTATUS:
> +        return s->debug_status;
> +    default:
> +        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
> +    }
> +    return 0;
> +}
> +
> +static uint64_t pl330_iomem_read(void *opaque, target_phys_addr_t offset,
> +        unsigned size)
> +{
> +    int ret = pl330_iomem_read_imp(opaque, offset);
> +    DB_PRINT("addr: %08x data: %08x\n", offset, ret);
> +    return ret;
> +}
> +
> +static const MemoryRegionOps pl330_ops = {
> +    .read = pl330_iomem_read,
> +    .write = pl330_iomem_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +/* Controller logic and initialization */
> +
> +static void pl330_chan_reset(PL330Chan *ch)
> +{
> +    ch->src = 0;
> +    ch->dst = 0;
> +    ch->pc = 0;
> +    ch->state = pl330_chan_stopped;
> +    ch->watchdog_timer = 0;
> +    ch->stall = 0;
> +    ch->control = 0;
> +    ch->status = 0;
> +    ch->fault_type = 0;
> +}
> +
> +static void pl330_reset(DeviceState *d)
> +{
> +    int i;
> +    PL330 *s = FROM_SYSBUS(PL330, sysbus_from_qdev(d));
> +
> +    s->inten = 0;
> +    s->int_status = 0;
> +    s->ev_status = 0;
> +    s->debug_status = 0;
> +    s->num_faulting = 0;
> +    pl330_fifo_reset(&s->fifo);
> +    pl330_queue_reset(&s->read_queue);
> +    pl330_queue_reset(&s->write_queue);
> +
> +    for (i = 0; i < s->chan_num; i++) {
> +        pl330_chan_reset(&s->chan[i]);
> +    }
> +    for (i = 0; i < s->periph_num; i++) {
> +        s->periph_busy[i] = 0;
> +    }
> +
> +    qemu_del_timer(s->timer);
> +}
> +
> +static int pl330_init(SysBusDevice *dev)
> +{
> +    int i;
> +    PL330 *s = FROM_SYSBUS(PL330, dev);
> +
> +    sysbus_init_irq(dev, &s->irq_abort);
> +    memory_region_init_io(&s->iomem, &pl330_ops, s, "dma",
> PL330_IOMEM_SIZE);
> +    sysbus_init_mmio(dev, &s->iomem);
> +
> +    s->timer = qemu_new_timer_ns(vm_clock, pl330_exec_cycle_timer, s);
> +
> +    s->chan_num = ((s->cfg[0] >> 4) & 7) + 1;
> +    s->chan = g_new0(PL330Chan, s->chan_num);
> +        DB_PRINT("manager@%p\n", &s->manager);
> +    for (i = 0; i < s->chan_num; i++) {
> +        DB_PRINT("channel %d@%p\n", i, &s->chan[i]);
> +        s->chan[i].parent = s;
> +        s->chan[i].tag = (uint8_t)i;
> +    }
> +    s->manager.parent = s;
> +    s->manager.tag = s->chan_num;
> +    s->manager.ns = (s->cfg[0] >> 2) & 1;
> +    s->manager.is_manager = 1;
> +    if (s->cfg[0] & 1) {
> +        s->periph_num = ((s->cfg[0] >> 12) & 0x1f) + 1;
> +    } else {
> +        s->periph_num = 0;
> +    }
> +    s->event_num = ((s->cfg[0] >> 17) & 0x1f) + 1;
> +
> +    s->irq = g_new0(qemu_irq, s->event_num);
> +    for (i = 0; i < s->event_num; i++) {
> +        sysbus_init_irq(dev, &s->irq[i]);
> +    }
> +
> +    qdev_init_gpio_in(&dev->qdev, pl330_dma_stop_irq, PL330_PERIPH_NUM);
> +
> +    pl330_queue_init(&s->read_queue, ((s->cfg[5] >> 16) & 0xf) + 1,
> +                                                        s->chan_num);
> +    pl330_queue_init(&s->write_queue, ((s->cfg[5] >> 8) & 0xf) + 1,
> +                                                        s->chan_num);
> +    pl330_fifo_init(&s->fifo, ((s->cfg[5] >> 20) & 0x1ff) + 1);
> +
> +    return 0;
> +}
> +
> +static Property pl330_properties[] = {
> +    DEFINE_PROP_UINT32("cfg0", PL330, cfg[0], 0),
> +    DEFINE_PROP_UINT32("cfg1", PL330, cfg[1], 0),
> +    DEFINE_PROP_UINT32("cfg2", PL330, cfg[2], 0),
> +    DEFINE_PROP_UINT32("cfg3", PL330, cfg[3], 0),
> +    DEFINE_PROP_UINT32("cfg4", PL330, cfg[4], 0),
> +    DEFINE_PROP_UINT32("cfg5", PL330, cfg[5], 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void pl330_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +
> +    k->init = pl330_init;
> +    dc->reset = pl330_reset;
> +    dc->props = pl330_properties;
> +}
> +
> +static TypeInfo pl330_info = {
> +    .name           = "pl330",
> +    .parent         = TYPE_SYS_BUS_DEVICE,
> +    .instance_size  = sizeof(PL330),
> +    .class_init      = pl330_class_init,
> +};
> +
> +static void pl330_register_types(void)
> +{
> +    type_register_static(&pl330_info);
> +}
> +
> +type_init(pl330_register_types)
>
>
>

Regards,
Peter
Mitsyanko Igor - July 5, 2012, 5:51 p.m.
On 07/05/2012 08:44 AM, Peter Crosthwaite wrote:
> On Tue, Jul 3, 2012 at 9:30 PM, Igor Mitsyanko <i.mitsyanko@samsung.com> wrote:
>> Hi Peter, here's a few review comments for your patch
>>
>>
>> On 06/18/2012 04:42 AM, Peter A. G. Crosthwaite wrote:
>>
>> Device model for Primecell PL330 dma controller.
>>
>> Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
>> Signed-off-by: Kirill Batuzov <batuzovk@ispras.ru>
>> ---
>> changed from v3:
>> rebased against Makefile refactor
>> changed from v2 (in order of diff appearance):
>> GPL version changed to v2 or later
>> Register and field names changed to match TRM
>> id field corrected
>> PL330ChanState enum corrected
>> target_phys_addr_t usages removed
>> identifier camel casing and underscoring fixed
>> data fifo reworked (uses head ptr and a occupancy count rather than head &
>> tail ptrs)
>> pl330_insn_from_queue->pl330_queue_remove_insn
>> Numerous typos in comments corrected as per review
>> g_malloc0(sizeof(a) * b) -> g_new
>> pl330_fifo_inc: changed to accept PL330Fifo struct as argument (rather than
>> buf_size)
>> pl330_fifo_push/get: rollback on overflow mechanism replaced with pre-check
>> pl330_queue_find_insn: fixed ugly if statement whitespace (PMM review)
>> pl330_dmaaddh: fixed arugment reversal
>> pl330_dmaaddh: asserted on !manager
>> "ns": change to be and behave like a boolean globally
>> pl330_dmald: force num=1 when PL330_SINGLE
>> pl330_dma_kill: put in correct alphabet order
>> pl330_dmarmb/wmb:implemented
>> pl330_exec_insn: asserted insn->size not too big
>> pl330_exec_insn: enabled stall mechanism outside exec state (needed for
>> rmb/wmb)
>> pl330_debug_exec: assert fault type on a fault
>> pl330_iomem_write: removed aligmnent assertion
>> pl330_iomem_write: corrected ev_status and irq_status logic
>> pl330_iomem_read_imp: fixed some incorrect bitfield positions (PMM review)
>> pl330_reset: stopped timer
>> pl330_init: removed redundant reset
>> changed from v1:
>> GPLv2 license
>> some code formatting fixes
>>
>>   hw/arm/Makefile.objs |    1 +
>>   hw/pl330.c           | 1411
>> ++++++++++++++++++++++++++++++++++++++++++++++++++
>>   2 files changed, 1412 insertions(+), 0 deletions(-)
>>   create mode 100644 hw/pl330.c
>>
>> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
>> index a0ff6a6..b28e5b7 100644
>> --- a/hw/arm/Makefile.objs
>> +++ b/hw/arm/Makefile.objs
>> @@ -7,6 +7,7 @@ obj-y += cadence_ttc.o
>>   obj-y += cadence_gem.o
>>   obj-y += xilinx_zynq.o zynq_slcr.o
>>   obj-y += arm_gic.o
>> +obj-y += pl330.o
>>   obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
>>   obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
>>   obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
>> diff --git a/hw/pl330.c b/hw/pl330.c
>> new file mode 100644
>> index 0000000..bc6bc35
>> --- /dev/null
>> +++ b/hw/pl330.c
>> @@ -0,0 +1,1411 @@
>> +/*
>> + * ARM PrimeCell PL330 DMA Controller
>> + *
>> + * Copyright (c) 2009 Samsung Electronics.
>> + * Contributed by Kirill Batuzov <batuzovk@ispras.ru>
>> + * Copyright (c) 2012 Peter A.G. Crosthwaite
>> (peter.crosthwaite@petalogix.com)
>> + * Copyright (c) 2012 PetaLogix Pty Ltd.
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License
>> + * as published by the Free Software Foundation; version 2 or later.
>> + *
>> + * You should have received a copy of the GNU General Public License along
>> + * with this program; if not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include "sysbus.h"
>> +#include "qemu-timer.h"
>> +
>> +#ifdef PL330_ERR_DEBUG
>> +#define DB_PRINT(...) do { \
>> +    fprintf(stderr,  ": %s: ", __func__); \
>> +    fprintf(stderr, ## __VA_ARGS__); \
>> +    } while (0);
>> +#else
>> +    #define DB_PRINT(...)
>> +#endif
>> +
>> +#define PL330_CHAN_NUM              8
>> +#define PL330_PERIPH_NUM            32
>> +#define PL330_MAX_BURST_LEN         128
>> +#define PL330_INSN_MAXSIZE          6
>> +
>> +#define PL330_FIFO_OK               0
>> +#define PL330_FIFO_STALL            1
>> +#define PL330_FIFO_ERR              (-1)
>> +
>> +#define PL330_FAULT_UNDEF_INSTR             (1 <<  0)
>> +#define PL330_FAULT_OPERAND_INVALID         (1 <<  1)
>> +#define PL330_FAULT_DMAGO_ERR               (1 <<  4)
>> +#define PL330_FAULT_EVENT_ERR               (1 <<  5)
>> +#define PL330_FAULT_CH_PERIPH_ERR           (1 <<  6)
>> +#define PL330_FAULT_CH_RDWR_ERR             (1 <<  7)
>> +#define PL330_FAULT_ST_DATA_UNAVAILABLE     (1 << 12)
>> +#define PL330_FAULT_FIFOEMPTY_ERR           (1 << 13)
>> +#define PL330_FAULT_INSTR_FETCH_ERR         (1 << 16)
>> +#define PL330_FAULT_DATA_WRITE_ERR          (1 << 17)
>> +#define PL330_FAULT_DATA_READ_ERR           (1 << 18)
>> +#define PL330_FAULT_DBG_INSTR               (1 << 30)
>> +#define PL330_FAULT_LOCKUP_ERR              (1 << 31)
>> +
>> +#define PL330_UNTAGGED              0xff
>> +
>> +#define PL330_SINGLE                0x0
>> +#define PL330_BURST                 0x1
>> +
>> +#define PL330_WATCHDOG_LIMIT        1024
>> +
>> +/* IOMEM mapped registers */
>> +#define PL330_REG_DSR               0x000
>> +#define PL330_REG_DPC               0x004
>> +#define PL330_REG_INTEN             0x020
>> +#define PL330_REG_INT_EVENT_RIS     0x024
>> +#define PL330_REG_INTMIS            0x028
>> +#define PL330_REG_INTCLR            0x02C
>> +#define PL330_REG_FSRD              0x030
>> +#define PL330_REG_FSRC              0x034
>> +#define PL330_REG_FTRD              0x038
>> +#define PL330_REG_FTR_BASE          0x040
>> +#define PL330_REG_CSR_BASE          0x100
>> +#define PL330_REG_CPC_BASE          0x104
>> +#define PL330_REG_CHANCTRL          0x400
>> +#define PL330_REG_DBGSTATUS         0xD00
>> +#define PL330_REG_DBGCMD            0xD04
>> +#define PL330_REG_DBGINST0          0xD08
>> +#define PL330_REG_DBGINST1          0xD0C
>> +#define PL330_REG_CR0_BASE          0xE00
>> +#define PL330_REG_PERIPH_ID         0xFE0
>> +
>> +#define PL330_IOMEM_SIZE    0x1000
>> +
>> +static const uint32_t pl330_id[] = {
>> +    0x30, 0x13, 0x04, 0x00, 0x0D, 0xF0, 0x05, 0xB1
>> +};
>> +
>> +/* DMA chanel states as they are described in PL330 Technical Reference
>> Manual
>> + * Most of them will not be used in emulation.
>> + */
>> +typedef enum  {
>> +    pl330_chan_stopped = 0,
>> +    pl330_chan_executing = 1,
>> +    pl330_chan_cache_miss = 2,
>> +    pl330_chan_updating_pc = 3,
>> +    pl330_chan_waiting_event = 4,
>> +    pl330_chan_at_barrier = 5,
>> +    pl330_chan_queue_busy = 6,
>> +    pl330_chan_waiting_periph = 7,
>> +    pl330_chan_killing = 8,
>> +    pl330_chan_completing = 9,
>> +    pl330_chan_fault_completing = 14,
>> +    pl330_chan_fault = 15,
>> +} PL330ChanState;
>> +
>> +typedef struct PL330 PL330;
>> +
>> +typedef struct PL330Chan {
>> +    uint32_t src;
>> +    uint32_t dst;
>> +    uint32_t pc;
>> +    uint32_t control;
>> +    uint32_t status;
>> +    uint32_t lc[2];
>> +    uint32_t fault_type;
>> +
>> +    bool ns;
>> +    bool is_manager;
>> +    uint8_t request_flag;
>> +    uint8_t wakeup;
>> +    uint8_t wfp_sbp;
>> +
>> +    PL330ChanState state;
>> +    uint8_t stall;
>> +
>> +    PL330 *parent;
>> +    uint8_t tag;
>> +    uint32_t watchdog_timer;
>> +} PL330Chan;
>> +
>> +typedef struct PL330Fifo {
>> +    uint8_t *buf;
>> +    uint8_t *tag;
>> +    int s, n;
>> +    int buf_size;
>> +} PL330Fifo;
>> +
>> +typedef struct PL330QueueEntry {
>> +    PL330Chan *c;
>> +    target_phys_addr_t addr;
>> +    int len;
>> +    int n;
>> +    int inc;
>> +    int z;
>> +    uint8_t tag;
>> +    uint8_t seqn;
>> +} PL330QueueEntry;
>> +
>> +typedef struct PL330Queue {
>> +    PL330QueueEntry *queue;
>> +    int queue_size;
>> +    uint8_t *lo_seqn;
>> +    uint8_t *hi_seqn;
>> +} PL330Queue;
>> +
>> +struct PL330 {
>> +    SysBusDevice busdev;
>> +    MemoryRegion iomem;
>> +
>> +    PL330Chan manager;
>> +    PL330Chan *chan;
>> +    PL330Fifo fifo;
>> +    PL330Queue read_queue;
>> +    PL330Queue write_queue;
>> +
>> +    /* Config registers. cfg[5] = CfgDn. */
>> +    uint32_t inten;
>> +    uint32_t int_status;
>> +    uint32_t ev_status;
>> +    uint32_t cfg[6];
>> +    uint32_t dbg[2];
>> +    uint8_t debug_status;
>> +
>> +    qemu_irq irq_abort;
>> +    qemu_irq *irq;
>> +
>> +    uint8_t num_faulting;
>> +
>> +    int chan_num;
>> +    int periph_num;
>> +    unsigned int event_num;
>> +
>> +    QEMUTimer *timer; /* is used for restore dma. */
>> +    int8_t periph_busy[PL330_PERIPH_NUM];
>> +};
>> +
>> +typedef struct PL330InsnDesc {
>> +    /* OPCODE of the instruction */
>> +    uint8_t opcode;
>> +    /* Mask so we can select several sibling instructions, such as
>> +       DMALD, DMALDS and DMALDB */
>> +    uint8_t opmask;
>> +    /* Size of instruction in bytes */
>> +    uint8_t size;
>> +    /* Interpreter */
>> +    void (*exec)(PL330Chan *, uint8_t opcode, uint8_t *args, int len);
>> +} PL330InsnDesc;
>> +
>> +
>> +/* MFIFO Implementation
>> + *
>> + * MFIFO is implemented as a cyclic buffer of BUF_SIZE size. Tagged bytes
>> are
>> + * stored in this buffer. Data is stored in BUF field, tags - in the
>> + * corresponding array elements of TAG field.
>> + */
>> +
>> +/* Initialize queue. */
>> +static void pl330_fifo_init(PL330Fifo *s, uint32_t size)
>> +{
>> +    s->buf = g_malloc0(size);
>> +    s->tag = g_malloc0(size);
>> +    s->buf_size = size;
>> +}
>> +
>> +/* Cyclic increment */
>> +static inline int pl330_fifo_inc(PL330Fifo *s, int x)
>> +{
>> +    return (x + 1) % s->buf_size;
>> +}
>> +
>> +/* Number of empty bytes in MFIFO */
>> +static inline int pl330_fifo_num_free(PL330Fifo *s)
>> +{
>> +    return s->buf_size - s->n;
>> +}
>> +
>> +/* Push LEN bytes of data stored in BUF to MFIFO and tag it with TAG.
>> + * Zero returned on success, PL330_FIFO_STALL if there is no enough free
>> + * space in MFIFO to store requested amount of data. If push was
>> unsuccessful
>> + * no data is stored to MFIFO.
>> + */
>> +static int pl330_fifo_push(PL330Fifo *s, uint8_t *buf, int len, uint8_t
>> tag)
>> +{
>> +    int i;
>> +
>> +    if (s->buf_size - s->n < len) {
>> +        return PL330_FIFO_STALL;
>> +    }
>> +    for (i = 0; i < len; i++) {
>> +        int push_idx = (s->s + i) % s->buf_size;
>> +        s->buf[push_idx] = buf[i];
>> +        s->tag[push_idx] = tag;
>> +    }
>> +    s->n += len;
>> +    return PL330_FIFO_OK;
>> +}
>> +
>> +/* Get LEN bytes of data from MFIFO and store it to BUF. Tag value of each
>> + * byte is verified. Zero returned on success, PL330_FIFO_ERR on tag
>> mismatch
>> + * and PL330_FIFO_STALL if there is no enough data in MFIFO. If get was
>> + * unsuccessful no data is removed from MFIFO.
>> + */
>> +static int pl330_fifo_get(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
>> +{
>> +    int i;
>> +
>> +    if (s->n < len) {
>> +        return PL330_FIFO_STALL;
>> +    }
>> +    for (i = 0; i < len; i++) {
>> +        if (s->tag[s->s] == tag) {
>> +            int get_idx = (s->s + i) % s->buf_size;
>> +            buf[i] = s->buf[get_idx];
>> +        } else { /* Tag mismatch - Rollback transaction */
>> +            return PL330_FIFO_ERR;
>> +        }
>> +    }
>> +    s->s = (s->s + len) % s->buf_size;
>> +    s->n -= len;
>> +    return PL330_FIFO_OK;
>> +}
>> +
>> +/* Reset MFIFO. This completely erases all data in it. */
>> +static inline void pl330_fifo_reset(PL330Fifo *s)
>> +{
>> +    s->s = 0;
>> +    s->n = 0;
>> +}
>> +
>> +/* Return tag of the first byte stored in MFIFO. If MFIFO is empty
>> +   PL330_UNTAGGED is returned. */
>> +static inline uint8_t pl330_fifo_tag(PL330Fifo *s)
>> +{
>> +    return (!s->n) ? PL330_UNTAGGED : s->tag[s->s];
>> +}
>> +
>> +/* Returns non-zero if tag TAG is present in fifo or zero otherwise */
>> +static int pl330_fifo_has_tag(PL330Fifo *s, uint8_t tag)
>> +{
>> +    int i, n;
>> +
>> +    i = s->s;
>> +    for (n = 0; n < s->n; n++) {
>> +        if (s->tag[i] == tag) {
>> +            return 1;
>> +        }
>> +        i = pl330_fifo_inc(s, i);
>> +    }
>> +    return 0;
>> +}
>> +
>> +/* Remove all entry tagged with TAG from MFIFO */
>> +static void pl330_fifo_tagged_remove(PL330Fifo *s, uint8_t tag)
>> +{
>> +    int i, t, n;
>> +
>> +    t = i = s->s;
>> +    for (n = 0; n < s->n; n++) {
>> +        if (s->tag[i] != tag) {
>> +            s->buf[t] = s->buf[i];
>> +            s->tag[t] = s->tag[i];
>> +            t = pl330_fifo_inc(s, t);
>> +        } else {
>> +            s->n = s->n - 1;
>> +        }
>> +        i = pl330_fifo_inc(s, i);
>> +    }
>> +}
>> +
>> +/* Read-Write Queue implementation
>> + *
>> + * A Read-Write Queue stores up to QUEUE_SIZE instructions (loads or
>> stores).
>> + * Each instruction is described by source (for loads) or destination (for
>> + * stores) address ADDR, width of data to be loaded/stored LEN, number of
>> + * stores/loads to be performed N, INC bit, Z bit and TAG to identify
>> channel
>> + * this instruction belongs to. Queue does not store any information about
>> + * nature of the instruction: is it load or store. PL330 has different
>> queues
>> + * for loads and stores so this is already known at the top level where it
>> + * matters.
>> + *
>> + * Queue works as FIFO for instructions with equivalent tags, but can issue
>> + * instructions with different tags in arbitrary order. SEQN field attached
>> to
>> + * each instruction helps to achieve this. For each TAG queue contains
>> + * instructions with consecutive SEQN values ranging from LO_SEQN[TAG] to
>> + * HI_SEQN[TAG]-1 inclusive. SEQN is 8-bit unsigned integer, so SEQN=255 is
>> + * followed by SEQN=0.
>> + *
>> + * Z bit indicates that zeroes should be stored. No MFIFO fetches are
>> performed
>> + * in this case.
>> + */
>> +
>> +static void pl330_queue_reset(PL330Queue *s)
>> +{
>> +    int i;
>> +
>> +    for (i = 0; i < s->queue_size; i++) {
>> +        s->queue[i].tag = PL330_UNTAGGED;
>> +    }
>> +}
>> +
>> +/* Initialize queue */
>> +static void pl330_queue_init(PL330Queue *s, int size, int channum)
>> +{
>> +    s->queue = (PL330QueueEntry *)g_new0(PL330QueueEntry, size);
>> +    s->lo_seqn = (uint8_t *)g_malloc0(channum);
>> +    s->hi_seqn = (uint8_t *)g_malloc0(channum);
>> +    s->queue_size = size;
>> +}
>> +
>> +/* Returns pointer to an empty slot or NULL if queue is full */
>> +static PL330QueueEntry *pl330_queue_find_empty(PL330Queue *s)
>> +{
>> +    int i;
>> +
>> +    for (i = 0; i < s->queue_size; i++) {
>> +        if (s->queue[i].tag == PL330_UNTAGGED) {
>> +            return &s->queue[i];
>> +        }
>> +    }
>> +    return NULL;
>> +}
>> +
>> +/* Put instruction to queue.
>> +   Return value:
>> +     - zero - OK
>> +     - non-zero - queue is full */
>> +static int pl330_queue_put_insn(PL330Queue *s, target_phys_addr_t addr,
>> +                               int len, int n, int inc, int z, uint8_t tag,
>> +                               PL330Chan *c)
>> +{
>> +    PL330QueueEntry *entry = pl330_queue_find_empty(s);
>> +
>> +    if (!entry) {
>> +        return 1;
>> +    }
>> +    entry->c = c;
>> +    entry->tag = tag;
>> +    entry->addr = addr;
>> +    entry->len = len;
>> +    entry->n = n;
>> +    entry->z = z;
>> +    entry->inc = inc;
>> +    entry->seqn = s->hi_seqn[tag];
>> +    s->hi_seqn[tag]++;
>> +    return 0;
>> +}
>> +
>> +/* Returns a pointer to queue slot containing instruction which satisfies
>> +   following conditions:
>> +    - it has valid tag value (not PL330_UNTAGGED)
>> +    - it can be issued without violating queue logic (see above)
>> +    - if TAG argument is not PL330_UNTAGGED this instruction has tag value
>> +      equivalent to the argument TAG value.
>> +   If such instruction cannot be found NULL is returned. */
>> +static PL330QueueEntry *pl330_queue_find_insn(PL330Queue *s, uint8_t tag)
>> +{
>> +    int i;
>> +
>> +    for (i = 0; i < s->queue_size; i++) {
>> +        if (s->queue[i].tag != PL330_UNTAGGED) {
>> +            if (s->queue[i].seqn == s->lo_seqn[s->queue[i].tag] &&
>> +                    (s->queue[i].tag == tag || tag == PL330_UNTAGGED ||
>> +                    s->queue[i].z)) {
>> +                return &s->queue[i];
>> +            }
>> +        }
>> +    }
>> +    return NULL;
>> +}
>> +
>> +/* Removes instruction from queue. */
>> +static inline void pl330_queue_remove_insn(PL330Queue *s, PL330QueueEntry
>> *e)
>> +{
>> +    s->lo_seqn[e->tag]++;
>> +    e->tag = PL330_UNTAGGED;
>> +}
>> +
>> +/* Removes all instructions tagged with TAG from queue. */
>> +static inline void pl330_queue_remove_tagged(PL330Queue *s, uint8_t tag)
>> +{
>> +    int i;
>> +
>> +    for (i = 0; i < s->queue_size; i++) {
>> +        if (s->queue[i].tag == tag) {
>> +            s->queue[i].tag = PL330_UNTAGGED;
>> +        }
>> +    }
>> +}
>> +
>> +
>> +/* DMA instruction execution engine */
>> +
>> +/* Moves DMA channel to the FAULT state and updates it's status. */
>> +static inline void pl330_fault(PL330Chan *ch, uint32_t flags)
>> +{
>> +    DB_PRINT("ch: %p, flags: %x\n", ch, flags);
>> +    ch->fault_type |= flags;
>> +    if (ch->state == pl330_chan_fault) {
>> +        return;
>> +    }
>> +    ch->state = pl330_chan_fault;
>> +    ch->parent->num_faulting++;
>> +    if (ch->parent->num_faulting == 1) {
>> +        DB_PRINT("abort interrupt raised\n");
>> +        qemu_irq_raise(ch->parent->irq_abort);
>> +    }
>> +}
>> +
>> +/*
>> + * For information about instructions see PL330 Technical Reference Manual.
>> + *
>> + * Arguments:
>> + *   CH - channel executing the instruction
>> + *   OPCODE - opcode
>> + *   ARGS - array of 8-bit arguments
>> + *   LEN - number of elements in ARGS array
>> + */
>> +static void pl330_dmaaddh(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint16_t im = (((uint16_t)args[1]) << 8) | ((uint16_t)args[0]);
>> +    uint8_t ra = (opcode >> 1) & 1;
>> +
>> +    if (ch->is_manager) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>>
>> more likely to cause PL330_FAULT_UNDEF_INSTR
>>
> Ok
>
>> +        return;
>> +    }
>> +    if (ra) {
>> +        ch->dst += im;
>> +    } else {
>> +        ch->src += im;
>> +    }
>> +}
>> +
>> +static void pl330_dmaend(PL330Chan *ch, uint8_t opcode,
>> +                         uint8_t *args, int len)
>> +{
>> +    if (ch->state == pl330_chan_executing && !ch->is_manager) {
>> +        /* Wait for all transfers to complete */
>> +        if (pl330_fifo_has_tag(&ch->parent->fifo, ch->tag) ||
>> +            pl330_queue_find_insn(&ch->parent->read_queue, ch->tag) != NULL
>> ||
>> +            pl330_queue_find_insn(&ch->parent->write_queue, ch->tag) !=
>> NULL) {
>> +
>> +            ch->stall = 1;
>> +            return;
>> +        }
>> +    }
>> +    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
>> +    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
>> +    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
>> +    ch->state = pl330_chan_stopped;
>> +}
>> +
>> +static void pl330_dmaflushp(PL330Chan *ch, uint8_t opcode,
>> +                                            uint8_t *args, int len)
>> +{
>> +    uint8_t periph_id;
>> +
>> +    if (args[0] & 7) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    periph_id = (args[0] >> 3) & 0x1f;
>> +    if (periph_id >= ch->parent->periph_num) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
>> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
>> +        return;
>> +    }
>> +    /* Do nothing */
>> +}
>> +
>> +static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint8_t chan_id;
>> +    uint8_t ns;
>> +    uint32_t pc;
>> +    PL330Chan *s;
>> +
>> +    DB_PRINT("\n");
>> +
>> +    if (!ch->is_manager) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    ns = !!(opcode & 2);
>> +    chan_id = args[0] & 7;
>> +    if ((args[0] >> 3)) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if (chan_id >= ch->parent->chan_num) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    pc = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
>> +         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
>> +    if (ch->parent->chan[chan_id].state != pl330_chan_stopped) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if (ch->ns && !ns) {
>> +        pl330_fault(ch, PL330_FAULT_DMAGO_ERR);
>> +        return;
>> +    }
>> +    s = &ch->parent->chan[chan_id];
>> +    s->ns = ns;
>> +    s->pc = pc;
>> +    s->state = pl330_chan_executing;
>> +}
>> +
>> +static void pl330_dmald(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint8_t bs = opcode & 3;
>> +    uint32_t size, num, inc;
>> +
>> +    if (bs == 2) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
>> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
>> +        /* Perform NOP */
>> +        return;
>> +    }
>> +    if (bs == 1 && ch->request_flag == PL330_SINGLE) {
>> +        num = 1;
>> +    } else {
>> +        num = ((ch->control >> 4) & 0xf) + 1;
>> +    }
>> +    size = (uint32_t)1 << ((ch->control >> 1) & 0x7);
>> +    inc = ch->control & 1;
>> +    ch->src &= ~(size - 1);
>>
>> Why are you doing that here, I couldn't find anywhere in TRM that PL330
>> automatically aligns
>> addresses when executing instructions. The only note I've found is that
>> fixed transfers from unaligned addresses are considered a programming error.
>>
> Determined by experiment with my real hardware. Annoyingly, I have a
> test case that does this.
>
>> +    ch->stall = pl330_queue_put_insn(&ch->parent->read_queue, ch->src,
>> +                                    size, num, inc, 0, ch->tag, ch);
>> +    if (inc & !ch->stall) {
>> +        ch->src += size * num;
>> +    }
>> +}
>> +
>> +static void pl330_dmaldp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint8_t periph_id;
>> +
>> +    if (args[0] & 7) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    periph_id = (args[0] >> 3) & 0x1f;
>> +    if (periph_id >= ch->parent->periph_num) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
>> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
>> +        return;
>> +    }
>> +    pl330_dmald(ch, opcode, args, len);
>> +}
>> +
>> +static void pl330_dmalp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint8_t lc = (opcode & 2) >> 1;
>> +
>> +    ch->lc[lc] = args[0];
>> +}
>> +
>> +static void pl330_dmakill(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    if (ch->state == pl330_chan_fault ||
>> +        ch->state == pl330_chan_fault_completing) {
>> +        /* This is the only way for a channel to leave the faulting state
>> */
>> +        ch->fault_type = 0;
>> +        ch->parent->num_faulting--;
>> +        if (ch->parent->num_faulting == 0) {
>> +            DB_PRINT("abort interrupt lowered\n");
>> +            qemu_irq_lower(ch->parent->irq_abort);
>> +        }
>> +    }
>> +    ch->state = pl330_chan_killing;
>> +    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
>> +    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
>> +    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
>> +    ch->state = pl330_chan_stopped;
>> +}
>> +
>> +static void pl330_dmalpend(PL330Chan *ch, uint8_t opcode,
>> +                                    uint8_t *args, int len)
>> +{
>> +    uint8_t nf = (opcode & 0x10) >> 4;
>> +    uint8_t bs = opcode & 3;
>> +    uint8_t lc = (opcode & 4) >> 2;
>> +
>> +    if (bs == 2) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
>> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
>> +        /* Perform NOP */
>> +        return;
>> +    }
>> +    if (!nf || ch->lc[lc]) {
>> +        if (nf) {
>> +            ch->lc[lc]--;
>> +        }
>> +        ch->pc -= args[0];
>> +        ch->pc -= len + 1;
>> +        /* "ch->pc -= args[0] + len + 1" is incorrect when args[0] == 256
>> */
>> +    }
>> +}
>> +
>> +
>> +static void pl330_dmamov(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint8_t rd = args[0] & 7;
>> +    uint32_t im;
>> +
>> +    if ((args[0] >> 3)) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    im = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
>> +         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
>> +    switch (rd) {
>> +    case 0:
>> +        ch->src = im;
>> +        break;
>> +    case 1:
>> +        ch->control = im;
>> +        break;
>> +    case 2:
>> +        ch->dst = im;
>> +        break;
>> +    default:
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +}
>> +
>> +static void pl330_dmanop(PL330Chan *ch, uint8_t opcode,
>> +                         uint8_t *args, int len)
>> +{
>> +    /* NOP is NOP. */
>> +}
>> +
>> +static void pl330_dmarmb(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +   if (pl330_queue_find_insn(&ch->parent->read_queue, ch->tag)) {
>> +        ch->state = pl330_chan_at_barrier;
>> +        ch->stall = 1;
>> +        return;
>> +    }
>> +}
>> +
>> +static void pl330_dmasev(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint8_t ev_id;
>> +
>> +    if (args[0] & 7) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    ev_id = (args[0] >> 3) & 0x1f;
>> +    if (ev_id >= ch->parent->event_num) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
>> +        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
>> +        return;
>> +    }
>> +    if (ch->parent->inten & (1 << ev_id)) {
>> +        ch->parent->int_status |= (1 << ev_id);
>> +        DB_PRINT("event interrupt raised %d\n", ev_id);
>> +        qemu_irq_raise(ch->parent->irq[ev_id]);
>> +    } else {
>> +        ch->parent->ev_status |= (1 << ev_id);
>>
>> Shouldn't you check if any threads were waiting for that event and move them
>> to execution state if they were?
>>
> No, because dma_wfe polls ev_status so just setting it is enough.

Right, now I see how all this actually works)

>
>> +    }
>> +}
>> +
>> +static void pl330_dmast(PL330Chan *ch, uint8_t opcode, uint8_t *args, int
>> len)
>> +{
>> +    uint8_t bs = opcode & 3;
>> +    uint32_t size, num, inc;
>> +
>> +    if (bs == 2) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
>> +        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
>> +        /* Perform NOP */
>> +        return;
>> +    }
>> +    num = ((ch->control >> 18) & 0xf) + 1;
>>
>> DMAC ignores dst_burst_len field in ch->control if condition
>>
>> (bs == 1 && ch->request_flag == PL330_SINGLE)
>>
>> is true
>>
> Cant follow your comment. Can you elaborate?
Yeah, I was talking about a note on page 4-18 of PL330 TRM which states 
that if request_flag is zero
then DMASTS ignores dst_burst_len field of CHANNEL_CONTROL register and 
always performs
transfer with a burst length of one (obviously).
I guess there is a possibility that even when DMAC executes DMASTS 
instruction, dst_burst_len field still could contain non-0 value which 
would result in non-single but burst DMA transfer.

>> +    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
>> +    inc = (ch->control >> 14) & 1;
>> +    ch->dst &= ~(size - 1);
>> +    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
>> +                                    size, num, inc, 0, ch->tag, ch);
>> +    if (inc) {
>> +        ch->dst += size * num;
>> +    }
>> +}
>> +
>> +static void pl330_dmastp(PL330Chan *ch, uint8_t opcode,
>> +                         uint8_t *args, int len)
>> +{
>> +    uint8_t periph_id;
>> +
>> +    if (args[0] & 7) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    periph_id = (args[0] >> 3) & 0x1f;
>> +    if (periph_id >= ch->parent->periph_num) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
>> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
>> +        return;
>> +    }
>> +    pl330_dmast(ch, opcode, args, len);
>> +}
>> +
>> +static void pl330_dmastz(PL330Chan *ch, uint8_t opcode,
>> +                         uint8_t *args, int len)
>> +{
>> +    uint32_t size, num, inc;
>> +
>> +    num = ((ch->control >> 18) & 0xf) + 1;
>> +    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
>> +    inc = (ch->control >> 14) & 1;
>> +    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
>> +                                    size, num, inc, 1, ch->tag, ch);
>> +    if (inc) {
>> +        ch->dst += size * num;
>> +    }
>> +}
>> +
>> +static void pl330_dmawfe(PL330Chan *ch, uint8_t opcode,
>> +                         uint8_t *args, int len)
>> +{
>> +    uint8_t ev_id;
>> +
>> +    if (args[0] & 5) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    ev_id = (args[0] >> 3) & 0x1f;
>> +    if (ev_id >= ch->parent->event_num) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
>> +        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
>> +        return;
>> +    }
>> +    ch->wakeup = ev_id;
>> +    ch->state = pl330_chan_waiting_event;
>> +    if (ch->parent->inten & ch->parent->ev_status & 1 << ev_id) {
>> +        ch->parent->ev_status &= ~(1 << ev_id);
>>
>> ev_status should be cleared if corresponding bit in inten is 0, not 1.
> ACK
>
>   And I
>> think channel thread
>> should reenter execution state if that event was already pending in
>> ev_status?
>>
> It does :). Note that ch->stall is else'd to this condition, so if the
> event is triggered dmawfe is us-stalled (reentering the exec state).
>
>> Overall, I don't see that DMAWFE and DMASEV are working correctly here,
>> maybe because I'm missing something and misunderstand what ch->stall is
>> supposed to be used for, but still its not such a big issue.
>>
>> +    } else {
>> +        ch->stall = 1;
>> +    }
>> +}
>> +
>> +static void pl330_dmawfp(PL330Chan *ch, uint8_t opcode,
>> +                         uint8_t *args, int len)
>> +{
>> +    uint8_t bs = opcode & 3;
>> +    uint8_t periph_id;
>> +
>> +    if (args[0] & 7) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    periph_id = (args[0] >> 3) & 0x1f;
>> +    if (periph_id >= ch->parent->periph_num) {
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
>> +        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
>> +        return;
>> +    }
>> +    switch (bs) {
>> +    case 0: /* S */
>> +        ch->request_flag = PL330_SINGLE;
>> +        ch->wfp_sbp = 0;
>> +        break;
>> +    case 1: /* P */
>> +        ch->request_flag = PL330_BURST;
>> +        ch->wfp_sbp = 2;
>> +        break;
>> +    case 2: /* B */
>> +        ch->request_flag = PL330_BURST;
>> +        ch->wfp_sbp = 1;
>> +        break;
>> +    default:
>> +        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
>> +        return;
>> +    }
>> +
>> +    if (ch->parent->periph_busy[periph_id]) {
>> +        ch->state = pl330_chan_waiting_periph;
>> +        ch->stall = 1;
>> +    } else if (ch->state == pl330_chan_waiting_periph) {
>> +        ch->state = pl330_chan_executing;
>> +    }
>> +}
>> +
>> +static void pl330_dmawmb(PL330Chan *ch, uint8_t opcode,
>> +                         uint8_t *args, int len)
>> +{
>> +    if (pl330_queue_find_insn(&ch->parent->write_queue, ch->tag)) {
>> +        ch->state = pl330_chan_at_barrier;
>> +        ch->stall = 1;
>> +        return;
>> +    }
>> +}
>> +
>> +/* NULL terminated array of the instruction descriptions. */
>> +static const PL330InsnDesc insn_desc[] = {
>> +    { .opcode = 0x54, .opmask = 0xFD, .size = 3, .exec = pl330_dmaaddh, },
>> +    { .opcode = 0x00, .opmask = 0xFF, .size = 1, .exec = pl330_dmaend, },
>> +    { .opcode = 0x35, .opmask = 0xFF, .size = 2, .exec = pl330_dmaflushp,
>> },
>> +    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
>> +    { .opcode = 0x04, .opmask = 0xFC, .size = 1, .exec = pl330_dmald, },
>> +    { .opcode = 0x25, .opmask = 0xFD, .size = 2, .exec = pl330_dmaldp, },
>> +    { .opcode = 0x20, .opmask = 0xFD, .size = 2, .exec = pl330_dmalp, },
>> +    /* dmastp  must be before dmalpend in this list, because their maps
>> +     * are overlapping
>> +     */
>> +    { .opcode = 0x29, .opmask = 0xFD, .size = 2, .exec = pl330_dmastp, },
>> +    { .opcode = 0x28, .opmask = 0xE8, .size = 2, .exec = pl330_dmalpend, },
>> +    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
>> +    { .opcode = 0xBC, .opmask = 0xFF, .size = 6, .exec = pl330_dmamov, },
>> +    { .opcode = 0x18, .opmask = 0xFF, .size = 1, .exec = pl330_dmanop, },
>> +    { .opcode = 0x12, .opmask = 0xFF, .size = 1, .exec = pl330_dmarmb, },
>> +    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
>> +    { .opcode = 0x08, .opmask = 0xFC, .size = 1, .exec = pl330_dmast, },
>> +    { .opcode = 0x0C, .opmask = 0xFF, .size = 1, .exec = pl330_dmastz, },
>> +    { .opcode = 0x36, .opmask = 0xFF, .size = 2, .exec = pl330_dmawfe, },
>> +    { .opcode = 0x30, .opmask = 0xFC, .size = 2, .exec = pl330_dmawfp, },
>> +    { .opcode = 0x13, .opmask = 0xFF, .size = 1, .exec = pl330_dmawmb, },
>> +    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
>> +};
>> +
>> +/* Instructions which can be issued via debug registers. */
>> +static const PL330InsnDesc debug_insn_desc[] = {
>> +    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
>> +    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
>> +    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
>> +    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
>> +};
>> +
>> +static inline const PL330InsnDesc *pl330_fetch_insn(PL330Chan *ch)
>> +{
>> +    uint8_t opcode;
>> +    int i;
>> +
>> +    cpu_physical_memory_read(ch->pc, &opcode, 1);
>> +    for (i = 0; insn_desc[i].size; i++) {
>> +        if ((opcode & insn_desc[i].opmask) == insn_desc[i].opcode) {
>> +            return &insn_desc[i];
>> +        }
>> +    }
>> +    return NULL;
>> +}
>> +
>> +static inline void pl330_exec_insn(PL330Chan *ch, const PL330InsnDesc
>> *insn)
>> +{
>> +    uint8_t buf[PL330_INSN_MAXSIZE];
>> +
>> +    assert(insn->size <= PL330_INSN_MAXSIZE);
>> +    cpu_physical_memory_read(ch->pc, buf, insn->size);
>> +    insn->exec(ch, buf[0], &buf[1], insn->size - 1);
>> +}
>> +
>> +static inline void pl330_update_pc(PL330Chan *ch,
>> +                                   const PL330InsnDesc *insn)
>> +{
>> +    ch->pc += insn->size;
>> +}
>> +
>> +/* Try to execute current instruction in channel CH. Number of executed
>> +   instructions is returned (0 or 1). */
>> +static int pl330_chan_exec(PL330Chan *ch)
>> +{
>> +    const PL330InsnDesc *insn;
>> +
>> +    if (ch->state != pl330_chan_executing &&
>> +            ch->state != pl330_chan_waiting_periph &&
>> +            ch->state != pl330_chan_at_barrier &&
>> +            ch->state != pl330_chan_waiting_event) {
>> +        DB_PRINT("%d\n", ch->state);
>> +        return 0;
>> +    }
>> +    ch->stall = 0;
>> +    insn = pl330_fetch_insn(ch);
>> +    if (!insn) {
>> +        DB_PRINT("pl330 undefined instruction\n");
>> +        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR);
>> +        return 0;
>> +    }
>> +    pl330_exec_insn(ch, insn);
>> +    if (!ch->stall) {
>> +        pl330_update_pc(ch, insn);
>> +        ch->watchdog_timer = 0;
>> +        return 1;
>> +    /* WDT only active in exec state */
>> +    } else if (ch->state == pl330_chan_executing) {
>> +        ch->watchdog_timer++;
>> +        if (ch->watchdog_timer >= PL330_WATCHDOG_LIMIT) {
>> +            pl330_fault(ch, PL330_FAULT_LOCKUP_ERR);
>> +        }
>> +    }
>> +    return 0;
>> +}
>> +
>> +/* Try to execute 1 instruction in each channel, one instruction from read
>> +   queue and one instruction from write queue. Number of successfully
>> executed
>> +   instructions is returned. */
>> +static int pl330_exec_cycle(PL330Chan *channel)
>> +{
>> +    PL330 *s = channel->parent;
>> +    PL330QueueEntry *q;
>> +    int i;
>> +    int num_exec = 0;
>> +    int fifo_res = 0;
>> +    uint8_t buf[PL330_MAX_BURST_LEN];
>> +
>> +    /* Execute one instruction in each channel */
>> +    num_exec += pl330_chan_exec(channel);
>> +
>> +    /* Execute one instruction from read queue */
>> +    q = pl330_queue_find_insn(&s->read_queue, PL330_UNTAGGED);
>> +    if (q != NULL && q->len <= pl330_fifo_num_free(&s->fifo)) {
>> +        cpu_physical_memory_read(q->addr, buf, q->len);
>> +        fifo_res = pl330_fifo_push(&s->fifo, buf, q->len, q->tag);
>> +        if (fifo_res == PL330_FIFO_OK) {
>> +            if (q->inc) {
>> +                q->addr += q->len;
>> +            }
>> +            q->n--;
>> +            if (!q->n) {
>> +                pl330_queue_remove_insn(&s->read_queue, q);
>> +            }
>> +            num_exec++;
>> +        }
>> +    }
>> +
>> +    /* Execute one instruction from write queue. */
>> +    q = pl330_queue_find_insn(&s->write_queue, pl330_fifo_tag(&s->fifo));
>> +    if (q != NULL) {
>> +        if (q->z) {
>> +            for (i = 0; i < q->len; i++) {
>> +                buf[i] = 0;
>> +            }
>> +        } else {
>> +            fifo_res = pl330_fifo_get(&s->fifo, buf, q->len, q->tag);
>> +        }
>> +        if (fifo_res == PL330_FIFO_OK || q->z) {
>> +            cpu_physical_memory_write(q->addr, buf, q->len);
>> +            if (q->inc) {
>> +                q->addr += q->len;
>> +            }
>> +            num_exec++;
>> +        } else if (fifo_res == PL330_FIFO_STALL) {
>> +            pl330_fault(q->c, PL330_FAULT_FIFOEMPTY_ERR);
>> +        }
>> +        q->n--;
>> +        if (!q->n) {
>> +            pl330_queue_remove_insn(&s->write_queue, q);
>> +        }
>> +    }
>> +
>> +    return num_exec;
>> +}
>> +
>> +static int pl330_exec_channel(PL330Chan *channel)
>> +{
>> +    int insr_exec = 0;
>> +
>> +    /* TODO: Is it all right to execute everything or should we do
>> per-cycle
>> +       simulation? */
>> +    while (pl330_exec_cycle(channel)) {
>> +        insr_exec++;
>> +    }
>> +
>> +    /* Detect deadlock */
>> +    if (channel->state == pl330_chan_executing) {
>> +        pl330_fault(channel, PL330_FAULT_LOCKUP_ERR);
>> +    }
>> +    /* Situation when one of the queues has deadlocked but all channels
>> +     * have finished their programs should be impossible.
>> +     */
>> +
>> +    return insr_exec;
>> +}
>> +
>> +
>> +static inline void pl330_exec(PL330 *s)
>> +{
>> +    DB_PRINT("\n");
>> +    int i, insr_exec;
>> +    do {
>> +        insr_exec = pl330_exec_channel(&s->manager);
>> +
>> +        for (i = 0; i < s->chan_num; i++) {
>> +            insr_exec += pl330_exec_channel(&s->chan[i]);
>> +        }
>> +    } while (insr_exec);
>> +}
>> +
>> +static void pl330_exec_cycle_timer(void *opaque)
>> +{
>> +    PL330 *s = (PL330 *)opaque;
>> +    pl330_exec(s);
>> +}
>> +
>> +/* Stop or restore dma operations */
>> +static void pl330_dma_stop_irq(void *opaque, int irq, int level)
>> +{
>> +    PL330 *s = (PL330 *)opaque;
>> +
>> +    if (s->periph_busy[irq] != level) {
>> +        s->periph_busy[irq] = level;
>> +        qemu_mod_timer(s->timer, qemu_get_clock_ns(vm_clock));
>> +    }
>> +}
>> +
>> +static void pl330_debug_exec(PL330 *s)
>> +{
>> +    uint8_t args[5];
>> +    uint8_t opcode;
>> +    uint8_t chan_id;
>> +    int i;
>> +    PL330Chan *ch;
>> +    const PL330InsnDesc *insn;
>> +
>> +    s->debug_status = 1;
>> +    chan_id = (s->dbg[0] >>  8) & 0x07;
>> +    opcode  = (s->dbg[0] >> 16) & 0xff;
>> +    args[0] = (s->dbg[0] >> 24) & 0xff;
>> +    args[1] = (s->dbg[1] >>  0) & 0xff;
>> +    args[2] = (s->dbg[1] >>  8) & 0xff;
>> +    args[3] = (s->dbg[1] >> 16) & 0xff;
>> +    args[4] = (s->dbg[1] >> 24) & 0xff;
>> +    DB_PRINT("chan id: %d\n", chan_id);
>> +    if (s->dbg[0] & 1) {
>> +        ch = &s->chan[chan_id];
>> +    } else {
>> +        ch = &s->manager;
>> +    }
>> +    insn = NULL;
>> +    for (i = 0; debug_insn_desc[i].size; i++) {
>> +        if ((opcode & debug_insn_desc[i].opmask) ==
>> debug_insn_desc[i].opcode) {
>> +            insn = &debug_insn_desc[i];
>> +        }
>> +    }
>> +    if (!insn) {
>> +        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR | PL330_FAULT_DBG_INSTR);
>> +        return ;
>> +    }
>> +    ch->stall = 0;
>> +    insn->exec(ch, opcode, args, insn->size - 1);
>> +    if (ch->fault_type) {
>> +        ch->fault_type |= PL330_FAULT_DBG_INSTR;
>> +    }
>> +    if (ch->stall) {
>> +        hw_error("pl330: stall of debug instruction not implemented\n");
>> +    }
>> +    s->debug_status = 0;
>> +}
>> +
>> +
>> +/* IOMEM mapped registers */
>> +
>> +static void pl330_iomem_write(void *opaque, target_phys_addr_t offset,
>> +                              uint64_t value, unsigned size)
>> +{
>> +    PL330 *s = (PL330 *) opaque;
>> +    uint32_t i;
>> +
>> +    DB_PRINT("addr: %08x data: %08x\n", offset, (unsigned)value);
>> +
>> +    switch (offset) {
>> +    case PL330_REG_INTEN:
>> +        s->inten = value;
>> +        break;
>> +    case PL330_REG_INTCLR:
>> +        for (i = 0; i < s->event_num; i++) {
>> +            if (s->int_status & s->inten & value & (1 << i)) {
>> +                DB_PRINT("event interrupt lowered %d\n", i);
>> +                qemu_irq_lower(s->irq[i]);
>> +            }
>> +        }
>> +        s->ev_status &= ~(value & s->inten);
>> +        s->int_status &= ~(value & ~s->inten);
>>
>> second inversion shouldn't be here I think
>>
> ACK - Igor, my test vector has no interrupts, Are you able to zip up
> and send my your exynos test vectorif its touching interrupt
> behaviour?

We only tested DMAC by connecting audio codec model to its peripheral 
request inputs, this model
doesn't use DMAC interrupts either.

>
>> +        break;
>> +    case PL330_REG_DBGCMD:
>> +        if ((value & 3) == 0) {
>> +            pl330_debug_exec(s);
>> +            pl330_exec(s);
>> +        } else {
>> +            hw_error("pl330: write of illegal value %u for offset "
>> +                     TARGET_FMT_plx "\n", (unsigned)value, offset);
>> +        }
>> +        break;
>> +    case PL330_REG_DBGINST0:
>> +        DB_PRINT("s->dbg[0] = %08x\n", (unsigned)value);
>> +        s->dbg[0] = value;
>> +        break;
>> +    case PL330_REG_DBGINST1:
>> +        DB_PRINT("s->dbg[1] = %08x\n", (unsigned)value);
>> +        s->dbg[1] = value;
>> +        break;
>> +    default:
>> +        hw_error("pl330: bad write offset " TARGET_FMT_plx "\n", offset);
>> +        break;
>> +    }
>> +}
>> +
>> +static inline uint32_t pl330_iomem_read_imp(void *opaque,
>> +        target_phys_addr_t offset)
>> +{
>> +    PL330 *s = (PL330 *) opaque;
>> +    int chan_id;
>> +    int i;
>> +    uint32_t res;
>> +
>> +    if (offset & 3) {
>> +        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
>> +    }
>> +
>> +    if (offset >= PL330_REG_PERIPH_ID && offset < PL330_REG_PERIPH_ID + 32)
>> {
>> +        return pl330_id[(offset - PL330_REG_PERIPH_ID) >> 2];
>> +    }
>> +    if (offset >= PL330_REG_CR0_BASE && offset < PL330_REG_CR0_BASE + 24) {
>> +        return s->cfg[(offset - PL330_REG_CR0_BASE) >> 2];
>> +    }
>> +    if (offset >= PL330_REG_CHANCTRL && offset < PL330_REG_DBGSTATUS) {
>> +        offset -= PL330_REG_CHANCTRL;
>> +        chan_id = offset >> 5;
>> +        if (chan_id >= s->chan_num) {
>> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n",
>> offset);
>> +        }
>> +        switch (offset & 0x1f) {
>> +        case 0x00:
>> +            return s->chan[chan_id].src;
>> +        case 0x04:
>> +            return s->chan[chan_id].dst;
>> +        case 0x08:
>> +            return s->chan[chan_id].control;
>> +        case 0x0C:
>> +            return s->chan[chan_id].lc[0];
>> +        case 0x10:
>> +            return s->chan[chan_id].lc[1];
>> +        default:
>> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n",
>> offset);
>> +        }
>> +    }
>> +    if (offset >= PL330_REG_CSR_BASE && offset < 0x400) {
>> +        offset -= PL330_REG_CSR_BASE;
>> +        chan_id = offset >> 3;
>> +        if (chan_id >= s->chan_num) {
>> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n",
>> offset);
>> +        }
>> +        switch ((offset >> 2) & 1) {
>> +        case 0x0:
>> +            res = (s->chan[chan_id].ns << 21) |
>> +                    (s->chan[chan_id].wakeup << 4) |
>> +                    (s->chan[chan_id].state) |
>> +                    (s->chan[chan_id].wfp_sbp << 14);
>> +            return res;
>> +        case 0x1:
>> +            return s->chan[chan_id].pc;
>> +        default:
>> +            hw_error("pl330: read error\n");
>> +        }
>> +    }
>> +    if (offset >= PL330_REG_FTR_BASE && offset < 0x100) {
>> +        offset -= PL330_REG_FTR_BASE;
>> +        chan_id = offset >> 2;
>> +        if (chan_id >= s->chan_num) {
>> +            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n",
>> offset);
>> +        }
>> +        return s->chan[chan_id].fault_type;
>> +    }
>> +    switch (offset) {
>> +    case PL330_REG_DSR:
>> +        return (s->manager.ns << 9) | (s->manager.wakeup << 4) |
>> +            (s->manager.state & 0xf);
>> +    case PL330_REG_DPC:
>> +        return s->manager.pc;
>> +    case PL330_REG_INTEN:
>> +        return s->inten;
>> +    case PL330_REG_INT_EVENT_RIS:
>> +        return s->ev_status;
>> +    case PL330_REG_INTMIS:
>> +        return s->int_status;
>> +    case PL330_REG_INTCLR:
>> +        /* Documentation says that we can't read this register
>> +         * but linux kernel does it */
>> +        return 0;
>> +    case PL330_REG_FSRD:
>> +        return s->manager.state ? 1 : 0;
>> +    case PL330_REG_FSRC:
>> +        res = 0;
>> +        for (i = 0; i < s->chan_num; i++) {
>> +            if (s->chan[i].state == pl330_chan_fault ||
>> +                s->chan[i].state == pl330_chan_fault_completing) {
>> +                res |= 1 << i;
>> +            }
>> +        }
>> +        return res;
>> +    case PL330_REG_FTRD:
>> +        return s->manager.fault_type;
>> +    case PL330_REG_DBGSTATUS:
>> +        return s->debug_status;
>> +    default:
>> +        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
>> +    }
>> +    return 0;
>> +}
>> +
>> +static uint64_t pl330_iomem_read(void *opaque, target_phys_addr_t offset,
>> +        unsigned size)
>> +{
>> +    int ret = pl330_iomem_read_imp(opaque, offset);
>> +    DB_PRINT("addr: %08x data: %08x\n", offset, ret);
>> +    return ret;
>> +}
>> +
>> +static const MemoryRegionOps pl330_ops = {
>> +    .read = pl330_iomem_read,
>> +    .write = pl330_iomem_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +};
>> +
>> +/* Controller logic and initialization */
>> +
>> +static void pl330_chan_reset(PL330Chan *ch)
>> +{
>> +    ch->src = 0;
>> +    ch->dst = 0;
>> +    ch->pc = 0;
>> +    ch->state = pl330_chan_stopped;
>> +    ch->watchdog_timer = 0;
>> +    ch->stall = 0;
>> +    ch->control = 0;
>> +    ch->status = 0;
>> +    ch->fault_type = 0;
>> +}
>> +
>> +static void pl330_reset(DeviceState *d)
>> +{
>> +    int i;
>> +    PL330 *s = FROM_SYSBUS(PL330, sysbus_from_qdev(d));
>> +
>> +    s->inten = 0;
>> +    s->int_status = 0;
>> +    s->ev_status = 0;
>> +    s->debug_status = 0;
>> +    s->num_faulting = 0;
>> +    pl330_fifo_reset(&s->fifo);
>> +    pl330_queue_reset(&s->read_queue);
>> +    pl330_queue_reset(&s->write_queue);
>> +
>> +    for (i = 0; i < s->chan_num; i++) {
>> +        pl330_chan_reset(&s->chan[i]);
>> +    }
>> +    for (i = 0; i < s->periph_num; i++) {
>> +        s->periph_busy[i] = 0;
>> +    }
>> +
>> +    qemu_del_timer(s->timer);
>> +}
>> +
>> +static int pl330_init(SysBusDevice *dev)
>> +{
>> +    int i;
>> +    PL330 *s = FROM_SYSBUS(PL330, dev);
>> +
>> +    sysbus_init_irq(dev, &s->irq_abort);
>> +    memory_region_init_io(&s->iomem, &pl330_ops, s, "dma",
>> PL330_IOMEM_SIZE);
>> +    sysbus_init_mmio(dev, &s->iomem);
>> +
>> +    s->timer = qemu_new_timer_ns(vm_clock, pl330_exec_cycle_timer, s);
>> +
>> +    s->chan_num = ((s->cfg[0] >> 4) & 7) + 1;
>> +    s->chan = g_new0(PL330Chan, s->chan_num);
>> +        DB_PRINT("manager@%p\n", &s->manager);
>> +    for (i = 0; i < s->chan_num; i++) {
>> +        DB_PRINT("channel %d@%p\n", i, &s->chan[i]);
>> +        s->chan[i].parent = s;
>> +        s->chan[i].tag = (uint8_t)i;
>> +    }
>> +    s->manager.parent = s;
>> +    s->manager.tag = s->chan_num;
>> +    s->manager.ns = (s->cfg[0] >> 2) & 1;
>> +    s->manager.is_manager = 1;
>> +    if (s->cfg[0] & 1) {
>> +        s->periph_num = ((s->cfg[0] >> 12) & 0x1f) + 1;
>> +    } else {
>> +        s->periph_num = 0;
>> +    }
>> +    s->event_num = ((s->cfg[0] >> 17) & 0x1f) + 1;
>> +
>> +    s->irq = g_new0(qemu_irq, s->event_num);
>> +    for (i = 0; i < s->event_num; i++) {
>> +        sysbus_init_irq(dev, &s->irq[i]);
>> +    }
>> +
>> +    qdev_init_gpio_in(&dev->qdev, pl330_dma_stop_irq, PL330_PERIPH_NUM);
>> +
>> +    pl330_queue_init(&s->read_queue, ((s->cfg[5] >> 16) & 0xf) + 1,
>> +                                                        s->chan_num);
>> +    pl330_queue_init(&s->write_queue, ((s->cfg[5] >> 8) & 0xf) + 1,
>> +                                                        s->chan_num);
>> +    pl330_fifo_init(&s->fifo, ((s->cfg[5] >> 20) & 0x1ff) + 1);
>> +
>> +    return 0;
>> +}
>> +
>> +static Property pl330_properties[] = {
>> +    DEFINE_PROP_UINT32("cfg0", PL330, cfg[0], 0),
>> +    DEFINE_PROP_UINT32("cfg1", PL330, cfg[1], 0),
>> +    DEFINE_PROP_UINT32("cfg2", PL330, cfg[2], 0),
>> +    DEFINE_PROP_UINT32("cfg3", PL330, cfg[3], 0),
>> +    DEFINE_PROP_UINT32("cfg4", PL330, cfg[4], 0),
>> +    DEFINE_PROP_UINT32("cfg5", PL330, cfg[5], 0),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void pl330_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
>> +
>> +    k->init = pl330_init;
>> +    dc->reset = pl330_reset;
>> +    dc->props = pl330_properties;
>> +}
>> +
>> +static TypeInfo pl330_info = {
>> +    .name           = "pl330",
>> +    .parent         = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size  = sizeof(PL330),
>> +    .class_init      = pl330_class_init,
>> +};
>> +
>> +static void pl330_register_types(void)
>> +{
>> +    type_register_static(&pl330_info);
>> +}
>> +
>> +type_init(pl330_register_types)
>>
>>
>>
> Regards,
> Peter
>
Peter A. G. Crosthwaite - July 6, 2012, 1:33 a.m.
>>>
>> ACK - Igor, my test vector has no interrupts, Are you able to zip up
>> and send my your exynos test vectorif its touching interrupt
>> behaviour?
>
>
> We only tested DMAC by connecting audio codec model to its peripheral
> request inputs, this model
> doesn't use DMAC interrupts either.
>

Ok, Ill get it tested under Zynq Linux.

Regards,
Peter

Patch

diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index a0ff6a6..b28e5b7 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -7,6 +7,7 @@  obj-y += cadence_ttc.o
 obj-y += cadence_gem.o
 obj-y += xilinx_zynq.o zynq_slcr.o
 obj-y += arm_gic.o
+obj-y += pl330.o
 obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
 obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
diff --git a/hw/pl330.c b/hw/pl330.c
new file mode 100644
index 0000000..bc6bc35
--- /dev/null
+++ b/hw/pl330.c
@@ -0,0 +1,1411 @@ 
+/*
+ * ARM PrimeCell PL330 DMA Controller
+ *
+ * Copyright (c) 2009 Samsung Electronics.
+ * Contributed by Kirill Batuzov <batuzovk@ispras.ru>
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 or later.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysbus.h"
+#include "qemu-timer.h"
+
+#ifdef PL330_ERR_DEBUG
+#define DB_PRINT(...) do { \
+    fprintf(stderr,  ": %s: ", __func__); \
+    fprintf(stderr, ## __VA_ARGS__); \
+    } while (0);
+#else
+    #define DB_PRINT(...)
+#endif
+
+#define PL330_CHAN_NUM              8
+#define PL330_PERIPH_NUM            32
+#define PL330_MAX_BURST_LEN         128
+#define PL330_INSN_MAXSIZE          6
+
+#define PL330_FIFO_OK               0
+#define PL330_FIFO_STALL            1
+#define PL330_FIFO_ERR              (-1)
+
+#define PL330_FAULT_UNDEF_INSTR             (1 <<  0)
+#define PL330_FAULT_OPERAND_INVALID         (1 <<  1)
+#define PL330_FAULT_DMAGO_ERR               (1 <<  4)
+#define PL330_FAULT_EVENT_ERR               (1 <<  5)
+#define PL330_FAULT_CH_PERIPH_ERR           (1 <<  6)
+#define PL330_FAULT_CH_RDWR_ERR             (1 <<  7)
+#define PL330_FAULT_ST_DATA_UNAVAILABLE     (1 << 12)
+#define PL330_FAULT_FIFOEMPTY_ERR           (1 << 13)
+#define PL330_FAULT_INSTR_FETCH_ERR         (1 << 16)
+#define PL330_FAULT_DATA_WRITE_ERR          (1 << 17)
+#define PL330_FAULT_DATA_READ_ERR           (1 << 18)
+#define PL330_FAULT_DBG_INSTR               (1 << 30)
+#define PL330_FAULT_LOCKUP_ERR              (1 << 31)
+
+#define PL330_UNTAGGED              0xff
+
+#define PL330_SINGLE                0x0
+#define PL330_BURST                 0x1
+
+#define PL330_WATCHDOG_LIMIT        1024
+
+/* IOMEM mapped registers */
+#define PL330_REG_DSR               0x000
+#define PL330_REG_DPC               0x004
+#define PL330_REG_INTEN             0x020
+#define PL330_REG_INT_EVENT_RIS     0x024
+#define PL330_REG_INTMIS            0x028
+#define PL330_REG_INTCLR            0x02C
+#define PL330_REG_FSRD              0x030
+#define PL330_REG_FSRC              0x034
+#define PL330_REG_FTRD              0x038
+#define PL330_REG_FTR_BASE          0x040
+#define PL330_REG_CSR_BASE          0x100
+#define PL330_REG_CPC_BASE          0x104
+#define PL330_REG_CHANCTRL          0x400
+#define PL330_REG_DBGSTATUS         0xD00
+#define PL330_REG_DBGCMD            0xD04
+#define PL330_REG_DBGINST0          0xD08
+#define PL330_REG_DBGINST1          0xD0C
+#define PL330_REG_CR0_BASE          0xE00
+#define PL330_REG_PERIPH_ID         0xFE0
+
+#define PL330_IOMEM_SIZE    0x1000
+
+static const uint32_t pl330_id[] = {
+    0x30, 0x13, 0x04, 0x00, 0x0D, 0xF0, 0x05, 0xB1
+};
+
+/* DMA chanel states as they are described in PL330 Technical Reference Manual
+ * Most of them will not be used in emulation.
+ */
+typedef enum  {
+    pl330_chan_stopped = 0,
+    pl330_chan_executing = 1,
+    pl330_chan_cache_miss = 2,
+    pl330_chan_updating_pc = 3,
+    pl330_chan_waiting_event = 4,
+    pl330_chan_at_barrier = 5,
+    pl330_chan_queue_busy = 6,
+    pl330_chan_waiting_periph = 7,
+    pl330_chan_killing = 8,
+    pl330_chan_completing = 9,
+    pl330_chan_fault_completing = 14,
+    pl330_chan_fault = 15,
+} PL330ChanState;
+
+typedef struct PL330 PL330;
+
+typedef struct PL330Chan {
+    uint32_t src;
+    uint32_t dst;
+    uint32_t pc;
+    uint32_t control;
+    uint32_t status;
+    uint32_t lc[2];
+    uint32_t fault_type;
+
+    bool ns;
+    bool is_manager;
+    uint8_t request_flag;
+    uint8_t wakeup;
+    uint8_t wfp_sbp;
+
+    PL330ChanState state;
+    uint8_t stall;
+
+    PL330 *parent;
+    uint8_t tag;
+    uint32_t watchdog_timer;
+} PL330Chan;
+
+typedef struct PL330Fifo {
+    uint8_t *buf;
+    uint8_t *tag;
+    int s, n;
+    int buf_size;
+} PL330Fifo;
+
+typedef struct PL330QueueEntry {
+    PL330Chan *c;
+    target_phys_addr_t addr;
+    int len;
+    int n;
+    int inc;
+    int z;
+    uint8_t tag;
+    uint8_t seqn;
+} PL330QueueEntry;
+
+typedef struct PL330Queue {
+    PL330QueueEntry *queue;
+    int queue_size;
+    uint8_t *lo_seqn;
+    uint8_t *hi_seqn;
+} PL330Queue;
+
+struct PL330 {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    PL330Chan manager;
+    PL330Chan *chan;
+    PL330Fifo fifo;
+    PL330Queue read_queue;
+    PL330Queue write_queue;
+
+    /* Config registers. cfg[5] = CfgDn. */
+    uint32_t inten;
+    uint32_t int_status;
+    uint32_t ev_status;
+    uint32_t cfg[6];
+    uint32_t dbg[2];
+    uint8_t debug_status;
+
+    qemu_irq irq_abort;
+    qemu_irq *irq;
+
+    uint8_t num_faulting;
+
+    int chan_num;
+    int periph_num;
+    unsigned int event_num;
+
+    QEMUTimer *timer; /* is used for restore dma. */
+    int8_t periph_busy[PL330_PERIPH_NUM];
+};
+
+typedef struct PL330InsnDesc {
+    /* OPCODE of the instruction */
+    uint8_t opcode;
+    /* Mask so we can select several sibling instructions, such as
+       DMALD, DMALDS and DMALDB */
+    uint8_t opmask;
+    /* Size of instruction in bytes */
+    uint8_t size;
+    /* Interpreter */
+    void (*exec)(PL330Chan *, uint8_t opcode, uint8_t *args, int len);
+} PL330InsnDesc;
+
+
+/* MFIFO Implementation
+ *
+ * MFIFO is implemented as a cyclic buffer of BUF_SIZE size. Tagged bytes are
+ * stored in this buffer. Data is stored in BUF field, tags - in the
+ * corresponding array elements of TAG field.
+ */
+
+/* Initialize queue. */
+static void pl330_fifo_init(PL330Fifo *s, uint32_t size)
+{
+    s->buf = g_malloc0(size);
+    s->tag = g_malloc0(size);
+    s->buf_size = size;
+}
+
+/* Cyclic increment */
+static inline int pl330_fifo_inc(PL330Fifo *s, int x)
+{
+    return (x + 1) % s->buf_size;
+}
+
+/* Number of empty bytes in MFIFO */
+static inline int pl330_fifo_num_free(PL330Fifo *s)
+{
+    return s->buf_size - s->n;
+}
+
+/* Push LEN bytes of data stored in BUF to MFIFO and tag it with TAG.
+ * Zero returned on success, PL330_FIFO_STALL if there is no enough free
+ * space in MFIFO to store requested amount of data. If push was unsuccessful
+ * no data is stored to MFIFO.
+ */
+static int pl330_fifo_push(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
+{
+    int i;
+
+    if (s->buf_size - s->n < len) {
+        return PL330_FIFO_STALL;
+    }
+    for (i = 0; i < len; i++) {
+        int push_idx = (s->s + i) % s->buf_size;
+        s->buf[push_idx] = buf[i];
+        s->tag[push_idx] = tag;
+    }
+    s->n += len;
+    return PL330_FIFO_OK;
+}
+
+/* Get LEN bytes of data from MFIFO and store it to BUF. Tag value of each
+ * byte is verified. Zero returned on success, PL330_FIFO_ERR on tag mismatch
+ * and PL330_FIFO_STALL if there is no enough data in MFIFO. If get was
+ * unsuccessful no data is removed from MFIFO.
+ */
+static int pl330_fifo_get(PL330Fifo *s, uint8_t *buf, int len, uint8_t tag)
+{
+    int i;
+
+    if (s->n < len) {
+        return PL330_FIFO_STALL;
+    }
+    for (i = 0; i < len; i++) {
+        if (s->tag[s->s] == tag) {
+            int get_idx = (s->s + i) % s->buf_size;
+            buf[i] = s->buf[get_idx];
+        } else { /* Tag mismatch - Rollback transaction */
+            return PL330_FIFO_ERR;
+        }
+    }
+    s->s = (s->s + len) % s->buf_size;
+    s->n -= len;
+    return PL330_FIFO_OK;
+}
+
+/* Reset MFIFO. This completely erases all data in it. */
+static inline void pl330_fifo_reset(PL330Fifo *s)
+{
+    s->s = 0;
+    s->n = 0;
+}
+
+/* Return tag of the first byte stored in MFIFO. If MFIFO is empty
+   PL330_UNTAGGED is returned. */
+static inline uint8_t pl330_fifo_tag(PL330Fifo *s)
+{
+    return (!s->n) ? PL330_UNTAGGED : s->tag[s->s];
+}
+
+/* Returns non-zero if tag TAG is present in fifo or zero otherwise */
+static int pl330_fifo_has_tag(PL330Fifo *s, uint8_t tag)
+{
+    int i, n;
+
+    i = s->s;
+    for (n = 0; n < s->n; n++) {
+        if (s->tag[i] == tag) {
+            return 1;
+        }
+        i = pl330_fifo_inc(s, i);
+    }
+    return 0;
+}
+
+/* Remove all entry tagged with TAG from MFIFO */
+static void pl330_fifo_tagged_remove(PL330Fifo *s, uint8_t tag)
+{
+    int i, t, n;
+
+    t = i = s->s;
+    for (n = 0; n < s->n; n++) {
+        if (s->tag[i] != tag) {
+            s->buf[t] = s->buf[i];
+            s->tag[t] = s->tag[i];
+            t = pl330_fifo_inc(s, t);
+        } else {
+            s->n = s->n - 1;
+        }
+        i = pl330_fifo_inc(s, i);
+    }
+}
+
+/* Read-Write Queue implementation
+ *
+ * A Read-Write Queue stores up to QUEUE_SIZE instructions (loads or stores).
+ * Each instruction is described by source (for loads) or destination (for
+ * stores) address ADDR, width of data to be loaded/stored LEN, number of
+ * stores/loads to be performed N, INC bit, Z bit and TAG to identify channel
+ * this instruction belongs to. Queue does not store any information about
+ * nature of the instruction: is it load or store. PL330 has different queues
+ * for loads and stores so this is already known at the top level where it
+ * matters.
+ *
+ * Queue works as FIFO for instructions with equivalent tags, but can issue
+ * instructions with different tags in arbitrary order. SEQN field attached to
+ * each instruction helps to achieve this. For each TAG queue contains
+ * instructions with consecutive SEQN values ranging from LO_SEQN[TAG] to
+ * HI_SEQN[TAG]-1 inclusive. SEQN is 8-bit unsigned integer, so SEQN=255 is
+ * followed by SEQN=0.
+ *
+ * Z bit indicates that zeroes should be stored. No MFIFO fetches are performed
+ * in this case.
+ */
+
+static void pl330_queue_reset(PL330Queue *s)
+{
+    int i;
+
+    for (i = 0; i < s->queue_size; i++) {
+        s->queue[i].tag = PL330_UNTAGGED;
+    }
+}
+
+/* Initialize queue */
+static void pl330_queue_init(PL330Queue *s, int size, int channum)
+{
+    s->queue = (PL330QueueEntry *)g_new0(PL330QueueEntry, size);
+    s->lo_seqn = (uint8_t *)g_malloc0(channum);
+    s->hi_seqn = (uint8_t *)g_malloc0(channum);
+    s->queue_size = size;
+}
+
+/* Returns pointer to an empty slot or NULL if queue is full */
+static PL330QueueEntry *pl330_queue_find_empty(PL330Queue *s)
+{
+    int i;
+
+    for (i = 0; i < s->queue_size; i++) {
+        if (s->queue[i].tag == PL330_UNTAGGED) {
+            return &s->queue[i];
+        }
+    }
+    return NULL;
+}
+
+/* Put instruction to queue.
+   Return value:
+     - zero - OK
+     - non-zero - queue is full */
+static int pl330_queue_put_insn(PL330Queue *s, target_phys_addr_t addr,
+                               int len, int n, int inc, int z, uint8_t tag,
+                               PL330Chan *c)
+{
+    PL330QueueEntry *entry = pl330_queue_find_empty(s);
+
+    if (!entry) {
+        return 1;
+    }
+    entry->c = c;
+    entry->tag = tag;
+    entry->addr = addr;
+    entry->len = len;
+    entry->n = n;
+    entry->z = z;
+    entry->inc = inc;
+    entry->seqn = s->hi_seqn[tag];
+    s->hi_seqn[tag]++;
+    return 0;
+}
+
+/* Returns a pointer to queue slot containing instruction which satisfies
+   following conditions:
+    - it has valid tag value (not PL330_UNTAGGED)
+    - it can be issued without violating queue logic (see above)
+    - if TAG argument is not PL330_UNTAGGED this instruction has tag value
+      equivalent to the argument TAG value.
+   If such instruction cannot be found NULL is returned. */
+static PL330QueueEntry *pl330_queue_find_insn(PL330Queue *s, uint8_t tag)
+{
+    int i;
+
+    for (i = 0; i < s->queue_size; i++) {
+        if (s->queue[i].tag != PL330_UNTAGGED) {
+            if (s->queue[i].seqn == s->lo_seqn[s->queue[i].tag] &&
+                    (s->queue[i].tag == tag || tag == PL330_UNTAGGED ||
+                    s->queue[i].z)) {
+                return &s->queue[i];
+            }
+        }
+    }
+    return NULL;
+}
+
+/* Removes instruction from queue. */
+static inline void pl330_queue_remove_insn(PL330Queue *s, PL330QueueEntry *e)
+{
+    s->lo_seqn[e->tag]++;
+    e->tag = PL330_UNTAGGED;
+}
+
+/* Removes all instructions tagged with TAG from queue. */
+static inline void pl330_queue_remove_tagged(PL330Queue *s, uint8_t tag)
+{
+    int i;
+
+    for (i = 0; i < s->queue_size; i++) {
+        if (s->queue[i].tag == tag) {
+            s->queue[i].tag = PL330_UNTAGGED;
+        }
+    }
+}
+
+
+/* DMA instruction execution engine */
+
+/* Moves DMA channel to the FAULT state and updates it's status. */
+static inline void pl330_fault(PL330Chan *ch, uint32_t flags)
+{
+    DB_PRINT("ch: %p, flags: %x\n", ch, flags);
+    ch->fault_type |= flags;
+    if (ch->state == pl330_chan_fault) {
+        return;
+    }
+    ch->state = pl330_chan_fault;
+    ch->parent->num_faulting++;
+    if (ch->parent->num_faulting == 1) {
+        DB_PRINT("abort interrupt raised\n");
+        qemu_irq_raise(ch->parent->irq_abort);
+    }
+}
+
+/*
+ * For information about instructions see PL330 Technical Reference Manual.
+ *
+ * Arguments:
+ *   CH - channel executing the instruction
+ *   OPCODE - opcode
+ *   ARGS - array of 8-bit arguments
+ *   LEN - number of elements in ARGS array
+ */
+static void pl330_dmaaddh(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    uint16_t im = (((uint16_t)args[1]) << 8) | ((uint16_t)args[0]);
+    uint8_t ra = (opcode >> 1) & 1;
+
+    if (ch->is_manager) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (ra) {
+        ch->dst += im;
+    } else {
+        ch->src += im;
+    }
+}
+
+static void pl330_dmaend(PL330Chan *ch, uint8_t opcode,
+                         uint8_t *args, int len)
+{
+    if (ch->state == pl330_chan_executing && !ch->is_manager) {
+        /* Wait for all transfers to complete */
+        if (pl330_fifo_has_tag(&ch->parent->fifo, ch->tag) ||
+            pl330_queue_find_insn(&ch->parent->read_queue, ch->tag) != NULL ||
+            pl330_queue_find_insn(&ch->parent->write_queue, ch->tag) != NULL) {
+
+            ch->stall = 1;
+            return;
+        }
+    }
+    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
+    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
+    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
+    ch->state = pl330_chan_stopped;
+}
+
+static void pl330_dmaflushp(PL330Chan *ch, uint8_t opcode,
+                                            uint8_t *args, int len)
+{
+    uint8_t periph_id;
+
+    if (args[0] & 7) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    periph_id = (args[0] >> 3) & 0x1f;
+    if (periph_id >= ch->parent->periph_num) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
+        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
+        return;
+    }
+    /* Do nothing */
+}
+
+static void pl330_dmago(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    uint8_t chan_id;
+    uint8_t ns;
+    uint32_t pc;
+    PL330Chan *s;
+
+    DB_PRINT("\n");
+
+    if (!ch->is_manager) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    ns = !!(opcode & 2);
+    chan_id = args[0] & 7;
+    if ((args[0] >> 3)) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (chan_id >= ch->parent->chan_num) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    pc = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
+         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
+    if (ch->parent->chan[chan_id].state != pl330_chan_stopped) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (ch->ns && !ns) {
+        pl330_fault(ch, PL330_FAULT_DMAGO_ERR);
+        return;
+    }
+    s = &ch->parent->chan[chan_id];
+    s->ns = ns;
+    s->pc = pc;
+    s->state = pl330_chan_executing;
+}
+
+static void pl330_dmald(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    uint8_t bs = opcode & 3;
+    uint32_t size, num, inc;
+
+    if (bs == 2) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
+        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
+        /* Perform NOP */
+        return;
+    }
+    if (bs == 1 && ch->request_flag == PL330_SINGLE) {
+        num = 1;
+    } else {
+        num = ((ch->control >> 4) & 0xf) + 1;
+    }
+    size = (uint32_t)1 << ((ch->control >> 1) & 0x7);
+    inc = ch->control & 1;
+    ch->src &= ~(size - 1);
+    ch->stall = pl330_queue_put_insn(&ch->parent->read_queue, ch->src,
+                                    size, num, inc, 0, ch->tag, ch);
+    if (inc & !ch->stall) {
+        ch->src += size * num;
+    }
+}
+
+static void pl330_dmaldp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    uint8_t periph_id;
+
+    if (args[0] & 7) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    periph_id = (args[0] >> 3) & 0x1f;
+    if (periph_id >= ch->parent->periph_num) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
+        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
+        return;
+    }
+    pl330_dmald(ch, opcode, args, len);
+}
+
+static void pl330_dmalp(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    uint8_t lc = (opcode & 2) >> 1;
+
+    ch->lc[lc] = args[0];
+}
+
+static void pl330_dmakill(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    if (ch->state == pl330_chan_fault ||
+        ch->state == pl330_chan_fault_completing) {
+        /* This is the only way for a channel to leave the faulting state */
+        ch->fault_type = 0;
+        ch->parent->num_faulting--;
+        if (ch->parent->num_faulting == 0) {
+            DB_PRINT("abort interrupt lowered\n");
+            qemu_irq_lower(ch->parent->irq_abort);
+        }
+    }
+    ch->state = pl330_chan_killing;
+    pl330_fifo_tagged_remove(&ch->parent->fifo, ch->tag);
+    pl330_queue_remove_tagged(&ch->parent->read_queue, ch->tag);
+    pl330_queue_remove_tagged(&ch->parent->write_queue, ch->tag);
+    ch->state = pl330_chan_stopped;
+}
+
+static void pl330_dmalpend(PL330Chan *ch, uint8_t opcode,
+                                    uint8_t *args, int len)
+{
+    uint8_t nf = (opcode & 0x10) >> 4;
+    uint8_t bs = opcode & 3;
+    uint8_t lc = (opcode & 4) >> 2;
+
+    if (bs == 2) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
+        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
+        /* Perform NOP */
+        return;
+    }
+    if (!nf || ch->lc[lc]) {
+        if (nf) {
+            ch->lc[lc]--;
+        }
+        ch->pc -= args[0];
+        ch->pc -= len + 1;
+        /* "ch->pc -= args[0] + len + 1" is incorrect when args[0] == 256 */
+    }
+}
+
+
+static void pl330_dmamov(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    uint8_t rd = args[0] & 7;
+    uint32_t im;
+
+    if ((args[0] >> 3)) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    im = (((uint32_t)args[4]) << 24) | (((uint32_t)args[3]) << 16) |
+         (((uint32_t)args[2]) << 8)  | (((uint32_t)args[1]));
+    switch (rd) {
+    case 0:
+        ch->src = im;
+        break;
+    case 1:
+        ch->control = im;
+        break;
+    case 2:
+        ch->dst = im;
+        break;
+    default:
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+}
+
+static void pl330_dmanop(PL330Chan *ch, uint8_t opcode,
+                         uint8_t *args, int len)
+{
+    /* NOP is NOP. */
+}
+
+static void pl330_dmarmb(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+   if (pl330_queue_find_insn(&ch->parent->read_queue, ch->tag)) {
+        ch->state = pl330_chan_at_barrier;
+        ch->stall = 1;
+        return;
+    }
+}
+
+static void pl330_dmasev(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    uint8_t ev_id;
+
+    if (args[0] & 7) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    ev_id = (args[0] >> 3) & 0x1f;
+    if (ev_id >= ch->parent->event_num) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
+        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
+        return;
+    }
+    if (ch->parent->inten & (1 << ev_id)) {
+        ch->parent->int_status |= (1 << ev_id);
+        DB_PRINT("event interrupt raised %d\n", ev_id);
+        qemu_irq_raise(ch->parent->irq[ev_id]);
+    } else {
+        ch->parent->ev_status |= (1 << ev_id);
+    }
+}
+
+static void pl330_dmast(PL330Chan *ch, uint8_t opcode, uint8_t *args, int len)
+{
+    uint8_t bs = opcode & 3;
+    uint32_t size, num, inc;
+
+    if (bs == 2) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if ((bs == 1 && ch->request_flag == PL330_BURST) ||
+        (bs == 3 && ch->request_flag == PL330_SINGLE)) {
+        /* Perform NOP */
+        return;
+    }
+    num = ((ch->control >> 18) & 0xf) + 1;
+    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
+    inc = (ch->control >> 14) & 1;
+    ch->dst &= ~(size - 1);
+    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
+                                    size, num, inc, 0, ch->tag, ch);
+    if (inc) {
+        ch->dst += size * num;
+    }
+}
+
+static void pl330_dmastp(PL330Chan *ch, uint8_t opcode,
+                         uint8_t *args, int len)
+{
+    uint8_t periph_id;
+
+    if (args[0] & 7) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    periph_id = (args[0] >> 3) & 0x1f;
+    if (periph_id >= ch->parent->periph_num) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
+        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
+        return;
+    }
+    pl330_dmast(ch, opcode, args, len);
+}
+
+static void pl330_dmastz(PL330Chan *ch, uint8_t opcode,
+                         uint8_t *args, int len)
+{
+    uint32_t size, num, inc;
+
+    num = ((ch->control >> 18) & 0xf) + 1;
+    size = (uint32_t)1 << ((ch->control >> 15) & 0x7);
+    inc = (ch->control >> 14) & 1;
+    ch->stall = pl330_queue_put_insn(&ch->parent->write_queue, ch->dst,
+                                    size, num, inc, 1, ch->tag, ch);
+    if (inc) {
+        ch->dst += size * num;
+    }
+}
+
+static void pl330_dmawfe(PL330Chan *ch, uint8_t opcode,
+                         uint8_t *args, int len)
+{
+    uint8_t ev_id;
+
+    if (args[0] & 5) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    ev_id = (args[0] >> 3) & 0x1f;
+    if (ev_id >= ch->parent->event_num) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (ch->ns && !(ch->parent->cfg[3] & (1 << ev_id))) {
+        pl330_fault(ch, PL330_FAULT_EVENT_ERR);
+        return;
+    }
+    ch->wakeup = ev_id;
+    ch->state = pl330_chan_waiting_event;
+    if (ch->parent->inten & ch->parent->ev_status & 1 << ev_id) {
+        ch->parent->ev_status &= ~(1 << ev_id);
+    } else {
+        ch->stall = 1;
+    }
+}
+
+static void pl330_dmawfp(PL330Chan *ch, uint8_t opcode,
+                         uint8_t *args, int len)
+{
+    uint8_t bs = opcode & 3;
+    uint8_t periph_id;
+
+    if (args[0] & 7) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    periph_id = (args[0] >> 3) & 0x1f;
+    if (periph_id >= ch->parent->periph_num) {
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+    if (ch->ns && !(ch->parent->cfg[4] & (1 << periph_id))) {
+        pl330_fault(ch, PL330_FAULT_CH_PERIPH_ERR);
+        return;
+    }
+    switch (bs) {
+    case 0: /* S */
+        ch->request_flag = PL330_SINGLE;
+        ch->wfp_sbp = 0;
+        break;
+    case 1: /* P */
+        ch->request_flag = PL330_BURST;
+        ch->wfp_sbp = 2;
+        break;
+    case 2: /* B */
+        ch->request_flag = PL330_BURST;
+        ch->wfp_sbp = 1;
+        break;
+    default:
+        pl330_fault(ch, PL330_FAULT_OPERAND_INVALID);
+        return;
+    }
+
+    if (ch->parent->periph_busy[periph_id]) {
+        ch->state = pl330_chan_waiting_periph;
+        ch->stall = 1;
+    } else if (ch->state == pl330_chan_waiting_periph) {
+        ch->state = pl330_chan_executing;
+    }
+}
+
+static void pl330_dmawmb(PL330Chan *ch, uint8_t opcode,
+                         uint8_t *args, int len)
+{
+    if (pl330_queue_find_insn(&ch->parent->write_queue, ch->tag)) {
+        ch->state = pl330_chan_at_barrier;
+        ch->stall = 1;
+        return;
+    }
+}
+
+/* NULL terminated array of the instruction descriptions. */
+static const PL330InsnDesc insn_desc[] = {
+    { .opcode = 0x54, .opmask = 0xFD, .size = 3, .exec = pl330_dmaaddh, },
+    { .opcode = 0x00, .opmask = 0xFF, .size = 1, .exec = pl330_dmaend, },
+    { .opcode = 0x35, .opmask = 0xFF, .size = 2, .exec = pl330_dmaflushp, },
+    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
+    { .opcode = 0x04, .opmask = 0xFC, .size = 1, .exec = pl330_dmald, },
+    { .opcode = 0x25, .opmask = 0xFD, .size = 2, .exec = pl330_dmaldp, },
+    { .opcode = 0x20, .opmask = 0xFD, .size = 2, .exec = pl330_dmalp, },
+    /* dmastp  must be before dmalpend in this list, because their maps
+     * are overlapping
+     */
+    { .opcode = 0x29, .opmask = 0xFD, .size = 2, .exec = pl330_dmastp, },
+    { .opcode = 0x28, .opmask = 0xE8, .size = 2, .exec = pl330_dmalpend, },
+    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
+    { .opcode = 0xBC, .opmask = 0xFF, .size = 6, .exec = pl330_dmamov, },
+    { .opcode = 0x18, .opmask = 0xFF, .size = 1, .exec = pl330_dmanop, },
+    { .opcode = 0x12, .opmask = 0xFF, .size = 1, .exec = pl330_dmarmb, },
+    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
+    { .opcode = 0x08, .opmask = 0xFC, .size = 1, .exec = pl330_dmast, },
+    { .opcode = 0x0C, .opmask = 0xFF, .size = 1, .exec = pl330_dmastz, },
+    { .opcode = 0x36, .opmask = 0xFF, .size = 2, .exec = pl330_dmawfe, },
+    { .opcode = 0x30, .opmask = 0xFC, .size = 2, .exec = pl330_dmawfp, },
+    { .opcode = 0x13, .opmask = 0xFF, .size = 1, .exec = pl330_dmawmb, },
+    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
+};
+
+/* Instructions which can be issued via debug registers. */
+static const PL330InsnDesc debug_insn_desc[] = {
+    { .opcode = 0xA0, .opmask = 0xFD, .size = 6, .exec = pl330_dmago, },
+    { .opcode = 0x01, .opmask = 0xFF, .size = 1, .exec = pl330_dmakill, },
+    { .opcode = 0x34, .opmask = 0xFF, .size = 2, .exec = pl330_dmasev, },
+    { .opcode = 0x00, .opmask = 0x00, .size = 0, .exec = NULL, }
+};
+
+static inline const PL330InsnDesc *pl330_fetch_insn(PL330Chan *ch)
+{
+    uint8_t opcode;
+    int i;
+
+    cpu_physical_memory_read(ch->pc, &opcode, 1);
+    for (i = 0; insn_desc[i].size; i++) {
+        if ((opcode & insn_desc[i].opmask) == insn_desc[i].opcode) {
+            return &insn_desc[i];
+        }
+    }
+    return NULL;
+}
+
+static inline void pl330_exec_insn(PL330Chan *ch, const PL330InsnDesc *insn)
+{
+    uint8_t buf[PL330_INSN_MAXSIZE];
+
+    assert(insn->size <= PL330_INSN_MAXSIZE);
+    cpu_physical_memory_read(ch->pc, buf, insn->size);
+    insn->exec(ch, buf[0], &buf[1], insn->size - 1);
+}
+
+static inline void pl330_update_pc(PL330Chan *ch,
+                                   const PL330InsnDesc *insn)
+{
+    ch->pc += insn->size;
+}
+
+/* Try to execute current instruction in channel CH. Number of executed
+   instructions is returned (0 or 1). */
+static int pl330_chan_exec(PL330Chan *ch)
+{
+    const PL330InsnDesc *insn;
+
+    if (ch->state != pl330_chan_executing &&
+            ch->state != pl330_chan_waiting_periph &&
+            ch->state != pl330_chan_at_barrier &&
+            ch->state != pl330_chan_waiting_event) {
+        DB_PRINT("%d\n", ch->state);
+        return 0;
+    }
+    ch->stall = 0;
+    insn = pl330_fetch_insn(ch);
+    if (!insn) {
+        DB_PRINT("pl330 undefined instruction\n");
+        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR);
+        return 0;
+    }
+    pl330_exec_insn(ch, insn);
+    if (!ch->stall) {
+        pl330_update_pc(ch, insn);
+        ch->watchdog_timer = 0;
+        return 1;
+    /* WDT only active in exec state */
+    } else if (ch->state == pl330_chan_executing) {
+        ch->watchdog_timer++;
+        if (ch->watchdog_timer >= PL330_WATCHDOG_LIMIT) {
+            pl330_fault(ch, PL330_FAULT_LOCKUP_ERR);
+        }
+    }
+    return 0;
+}
+
+/* Try to execute 1 instruction in each channel, one instruction from read
+   queue and one instruction from write queue. Number of successfully executed
+   instructions is returned. */
+static int pl330_exec_cycle(PL330Chan *channel)
+{
+    PL330 *s = channel->parent;
+    PL330QueueEntry *q;
+    int i;
+    int num_exec = 0;
+    int fifo_res = 0;
+    uint8_t buf[PL330_MAX_BURST_LEN];
+
+    /* Execute one instruction in each channel */
+    num_exec += pl330_chan_exec(channel);
+
+    /* Execute one instruction from read queue */
+    q = pl330_queue_find_insn(&s->read_queue, PL330_UNTAGGED);
+    if (q != NULL && q->len <= pl330_fifo_num_free(&s->fifo)) {
+        cpu_physical_memory_read(q->addr, buf, q->len);
+        fifo_res = pl330_fifo_push(&s->fifo, buf, q->len, q->tag);
+        if (fifo_res == PL330_FIFO_OK) {
+            if (q->inc) {
+                q->addr += q->len;
+            }
+            q->n--;
+            if (!q->n) {
+                pl330_queue_remove_insn(&s->read_queue, q);
+            }
+            num_exec++;
+        }
+    }
+
+    /* Execute one instruction from write queue. */
+    q = pl330_queue_find_insn(&s->write_queue, pl330_fifo_tag(&s->fifo));
+    if (q != NULL) {
+        if (q->z) {
+            for (i = 0; i < q->len; i++) {
+                buf[i] = 0;
+            }
+        } else {
+            fifo_res = pl330_fifo_get(&s->fifo, buf, q->len, q->tag);
+        }
+        if (fifo_res == PL330_FIFO_OK || q->z) {
+            cpu_physical_memory_write(q->addr, buf, q->len);
+            if (q->inc) {
+                q->addr += q->len;
+            }
+            num_exec++;
+        } else if (fifo_res == PL330_FIFO_STALL) {
+            pl330_fault(q->c, PL330_FAULT_FIFOEMPTY_ERR);
+        }
+        q->n--;
+        if (!q->n) {
+            pl330_queue_remove_insn(&s->write_queue, q);
+        }
+    }
+
+    return num_exec;
+}
+
+static int pl330_exec_channel(PL330Chan *channel)
+{
+    int insr_exec = 0;
+
+    /* TODO: Is it all right to execute everything or should we do per-cycle
+       simulation? */
+    while (pl330_exec_cycle(channel)) {
+        insr_exec++;
+    }
+
+    /* Detect deadlock */
+    if (channel->state == pl330_chan_executing) {
+        pl330_fault(channel, PL330_FAULT_LOCKUP_ERR);
+    }
+    /* Situation when one of the queues has deadlocked but all channels
+     * have finished their programs should be impossible.
+     */
+
+    return insr_exec;
+}
+
+
+static inline void pl330_exec(PL330 *s)
+{
+    DB_PRINT("\n");
+    int i, insr_exec;
+    do {
+        insr_exec = pl330_exec_channel(&s->manager);
+
+        for (i = 0; i < s->chan_num; i++) {
+            insr_exec += pl330_exec_channel(&s->chan[i]);
+        }
+    } while (insr_exec);
+}
+
+static void pl330_exec_cycle_timer(void *opaque)
+{
+    PL330 *s = (PL330 *)opaque;
+    pl330_exec(s);
+}
+
+/* Stop or restore dma operations */
+static void pl330_dma_stop_irq(void *opaque, int irq, int level)
+{
+    PL330 *s = (PL330 *)opaque;
+
+    if (s->periph_busy[irq] != level) {
+        s->periph_busy[irq] = level;
+        qemu_mod_timer(s->timer, qemu_get_clock_ns(vm_clock));
+    }
+}
+
+static void pl330_debug_exec(PL330 *s)
+{
+    uint8_t args[5];
+    uint8_t opcode;
+    uint8_t chan_id;
+    int i;
+    PL330Chan *ch;
+    const PL330InsnDesc *insn;
+
+    s->debug_status = 1;
+    chan_id = (s->dbg[0] >>  8) & 0x07;
+    opcode  = (s->dbg[0] >> 16) & 0xff;
+    args[0] = (s->dbg[0] >> 24) & 0xff;
+    args[1] = (s->dbg[1] >>  0) & 0xff;
+    args[2] = (s->dbg[1] >>  8) & 0xff;
+    args[3] = (s->dbg[1] >> 16) & 0xff;
+    args[4] = (s->dbg[1] >> 24) & 0xff;
+    DB_PRINT("chan id: %d\n", chan_id);
+    if (s->dbg[0] & 1) {
+        ch = &s->chan[chan_id];
+    } else {
+        ch = &s->manager;
+    }
+    insn = NULL;
+    for (i = 0; debug_insn_desc[i].size; i++) {
+        if ((opcode & debug_insn_desc[i].opmask) == debug_insn_desc[i].opcode) {
+            insn = &debug_insn_desc[i];
+        }
+    }
+    if (!insn) {
+        pl330_fault(ch, PL330_FAULT_UNDEF_INSTR | PL330_FAULT_DBG_INSTR);
+        return ;
+    }
+    ch->stall = 0;
+    insn->exec(ch, opcode, args, insn->size - 1);
+    if (ch->fault_type) {
+        ch->fault_type |= PL330_FAULT_DBG_INSTR;
+    }
+    if (ch->stall) {
+        hw_error("pl330: stall of debug instruction not implemented\n");
+    }
+    s->debug_status = 0;
+}
+
+
+/* IOMEM mapped registers */
+
+static void pl330_iomem_write(void *opaque, target_phys_addr_t offset,
+                              uint64_t value, unsigned size)
+{
+    PL330 *s = (PL330 *) opaque;
+    uint32_t i;
+
+    DB_PRINT("addr: %08x data: %08x\n", offset, (unsigned)value);
+
+    switch (offset) {
+    case PL330_REG_INTEN:
+        s->inten = value;
+        break;
+    case PL330_REG_INTCLR:
+        for (i = 0; i < s->event_num; i++) {
+            if (s->int_status & s->inten & value & (1 << i)) {
+                DB_PRINT("event interrupt lowered %d\n", i);
+                qemu_irq_lower(s->irq[i]);
+            }
+        }
+        s->ev_status &= ~(value & s->inten);
+        s->int_status &= ~(value & ~s->inten);
+        break;
+    case PL330_REG_DBGCMD:
+        if ((value & 3) == 0) {
+            pl330_debug_exec(s);
+            pl330_exec(s);
+        } else {
+            hw_error("pl330: write of illegal value %u for offset "
+                     TARGET_FMT_plx "\n", (unsigned)value, offset);
+        }
+        break;
+    case PL330_REG_DBGINST0:
+        DB_PRINT("s->dbg[0] = %08x\n", (unsigned)value);
+        s->dbg[0] = value;
+        break;
+    case PL330_REG_DBGINST1:
+        DB_PRINT("s->dbg[1] = %08x\n", (unsigned)value);
+        s->dbg[1] = value;
+        break;
+    default:
+        hw_error("pl330: bad write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+static inline uint32_t pl330_iomem_read_imp(void *opaque,
+        target_phys_addr_t offset)
+{
+    PL330 *s = (PL330 *) opaque;
+    int chan_id;
+    int i;
+    uint32_t res;
+
+    if (offset & 3) {
+        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
+    }
+
+    if (offset >= PL330_REG_PERIPH_ID && offset < PL330_REG_PERIPH_ID + 32) {
+        return pl330_id[(offset - PL330_REG_PERIPH_ID) >> 2];
+    }
+    if (offset >= PL330_REG_CR0_BASE && offset < PL330_REG_CR0_BASE + 24) {
+        return s->cfg[(offset - PL330_REG_CR0_BASE) >> 2];
+    }
+    if (offset >= PL330_REG_CHANCTRL && offset < PL330_REG_DBGSTATUS) {
+        offset -= PL330_REG_CHANCTRL;
+        chan_id = offset >> 5;
+        if (chan_id >= s->chan_num) {
+            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
+        }
+        switch (offset & 0x1f) {
+        case 0x00:
+            return s->chan[chan_id].src;
+        case 0x04:
+            return s->chan[chan_id].dst;
+        case 0x08:
+            return s->chan[chan_id].control;
+        case 0x0C:
+            return s->chan[chan_id].lc[0];
+        case 0x10:
+            return s->chan[chan_id].lc[1];
+        default:
+            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
+        }
+    }
+    if (offset >= PL330_REG_CSR_BASE && offset < 0x400) {
+        offset -= PL330_REG_CSR_BASE;
+        chan_id = offset >> 3;
+        if (chan_id >= s->chan_num) {
+            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
+        }
+        switch ((offset >> 2) & 1) {
+        case 0x0:
+            res = (s->chan[chan_id].ns << 21) |
+                    (s->chan[chan_id].wakeup << 4) |
+                    (s->chan[chan_id].state) |
+                    (s->chan[chan_id].wfp_sbp << 14);
+            return res;
+        case 0x1:
+            return s->chan[chan_id].pc;
+        default:
+            hw_error("pl330: read error\n");
+        }
+    }
+    if (offset >= PL330_REG_FTR_BASE && offset < 0x100) {
+        offset -= PL330_REG_FTR_BASE;
+        chan_id = offset >> 2;
+        if (chan_id >= s->chan_num) {
+            hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
+        }
+        return s->chan[chan_id].fault_type;
+    }
+    switch (offset) {
+    case PL330_REG_DSR:
+        return (s->manager.ns << 9) | (s->manager.wakeup << 4) |
+            (s->manager.state & 0xf);
+    case PL330_REG_DPC:
+        return s->manager.pc;
+    case PL330_REG_INTEN:
+        return s->inten;
+    case PL330_REG_INT_EVENT_RIS:
+        return s->ev_status;
+    case PL330_REG_INTMIS:
+        return s->int_status;
+    case PL330_REG_INTCLR:
+        /* Documentation says that we can't read this register
+         * but linux kernel does it */
+        return 0;
+    case PL330_REG_FSRD:
+        return s->manager.state ? 1 : 0;
+    case PL330_REG_FSRC:
+        res = 0;
+        for (i = 0; i < s->chan_num; i++) {
+            if (s->chan[i].state == pl330_chan_fault ||
+                s->chan[i].state == pl330_chan_fault_completing) {
+                res |= 1 << i;
+            }
+        }
+        return res;
+    case PL330_REG_FTRD:
+        return s->manager.fault_type;
+    case PL330_REG_DBGSTATUS:
+        return s->debug_status;
+    default:
+        hw_error("pl330: bad read offset " TARGET_FMT_plx "\n", offset);
+    }
+    return 0;
+}
+
+static uint64_t pl330_iomem_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    int ret = pl330_iomem_read_imp(opaque, offset);
+    DB_PRINT("addr: %08x data: %08x\n", offset, ret);
+    return ret;
+}
+
+static const MemoryRegionOps pl330_ops = {
+    .read = pl330_iomem_read,
+    .write = pl330_iomem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/* Controller logic and initialization */
+
+static void pl330_chan_reset(PL330Chan *ch)
+{
+    ch->src = 0;
+    ch->dst = 0;
+    ch->pc = 0;
+    ch->state = pl330_chan_stopped;
+    ch->watchdog_timer = 0;
+    ch->stall = 0;
+    ch->control = 0;
+    ch->status = 0;
+    ch->fault_type = 0;
+}
+
+static void pl330_reset(DeviceState *d)
+{
+    int i;
+    PL330 *s = FROM_SYSBUS(PL330, sysbus_from_qdev(d));
+
+    s->inten = 0;
+    s->int_status = 0;
+    s->ev_status = 0;
+    s->debug_status = 0;
+    s->num_faulting = 0;
+    pl330_fifo_reset(&s->fifo);
+    pl330_queue_reset(&s->read_queue);
+    pl330_queue_reset(&s->write_queue);
+
+    for (i = 0; i < s->chan_num; i++) {
+        pl330_chan_reset(&s->chan[i]);
+    }
+    for (i = 0; i < s->periph_num; i++) {
+        s->periph_busy[i] = 0;
+    }
+
+    qemu_del_timer(s->timer);
+}
+
+static int pl330_init(SysBusDevice *dev)
+{
+    int i;
+    PL330 *s = FROM_SYSBUS(PL330, dev);
+
+    sysbus_init_irq(dev, &s->irq_abort);
+    memory_region_init_io(&s->iomem, &pl330_ops, s, "dma", PL330_IOMEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    s->timer = qemu_new_timer_ns(vm_clock, pl330_exec_cycle_timer, s);
+
+    s->chan_num = ((s->cfg[0] >> 4) & 7) + 1;
+    s->chan = g_new0(PL330Chan, s->chan_num);
+        DB_PRINT("manager@%p\n", &s->manager);
+    for (i = 0; i < s->chan_num; i++) {
+        DB_PRINT("channel %d@%p\n", i, &s->chan[i]);
+        s->chan[i].parent = s;
+        s->chan[i].tag = (uint8_t)i;
+    }
+    s->manager.parent = s;
+    s->manager.tag = s->chan_num;
+    s->manager.ns = (s->cfg[0] >> 2) & 1;
+    s->manager.is_manager = 1;
+    if (s->cfg[0] & 1) {
+        s->periph_num = ((s->cfg[0] >> 12) & 0x1f) + 1;
+    } else {
+        s->periph_num = 0;
+    }
+    s->event_num = ((s->cfg[0] >> 17) & 0x1f) + 1;
+
+    s->irq = g_new0(qemu_irq, s->event_num);
+    for (i = 0; i < s->event_num; i++) {
+        sysbus_init_irq(dev, &s->irq[i]);
+    }
+
+    qdev_init_gpio_in(&dev->qdev, pl330_dma_stop_irq, PL330_PERIPH_NUM);
+
+    pl330_queue_init(&s->read_queue, ((s->cfg[5] >> 16) & 0xf) + 1,
+                                                        s->chan_num);
+    pl330_queue_init(&s->write_queue, ((s->cfg[5] >> 8) & 0xf) + 1,
+                                                        s->chan_num);
+    pl330_fifo_init(&s->fifo, ((s->cfg[5] >> 20) & 0x1ff) + 1);
+
+    return 0;
+}
+
+static Property pl330_properties[] = {
+    DEFINE_PROP_UINT32("cfg0", PL330, cfg[0], 0),
+    DEFINE_PROP_UINT32("cfg1", PL330, cfg[1], 0),
+    DEFINE_PROP_UINT32("cfg2", PL330, cfg[2], 0),
+    DEFINE_PROP_UINT32("cfg3", PL330, cfg[3], 0),
+    DEFINE_PROP_UINT32("cfg4", PL330, cfg[4], 0),
+    DEFINE_PROP_UINT32("cfg5", PL330, cfg[5], 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pl330_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = pl330_init;
+    dc->reset = pl330_reset;
+    dc->props = pl330_properties;
+}
+
+static TypeInfo pl330_info = {
+    .name           = "pl330",
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(PL330),
+    .class_init      = pl330_class_init,
+};
+
+static void pl330_register_types(void)
+{
+    type_register_static(&pl330_info);
+}
+
+type_init(pl330_register_types)