diff mbox

[v2,03/20] arm: add Faraday FTAPBBRG020 APB DMA support

Message ID 1359101996-11667-4-git-send-email-dantesu@gmail.com
State New
Headers show

Commit Message

Kuo-Jung Su Jan. 25, 2013, 8:19 a.m. UTC
From: Kuo-Jung Su <dantesu@faraday-tech.com>

The FTAPBBRG020 supports the DMA functions for the AHB-to-AHB,
AHB-to-APB, APB-to-AHB, and APB-to-APB transactions.

The DMA engine can support up to 4 DMA channels (A, B, C, and D)
and 15 handshaking channels. A DMA channel granted by the arbiter
block is the only channel starts transfers. Each DMA channel can
be programmed to one of the 15 handshaking channels in the hardware
handshake mode to act as the source device or act as the destination
device.

The main function of the hardware handshake mode is to provide an
indication of the device status. Users can also disable the hardware
handshake mode by programming the register when a DMA transfer is not
necessary of referring to the handshaking channels.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/ftapbbrg020.c |  502 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/ftapbbrg020.h |   41 +++++
 2 files changed, 543 insertions(+)
 create mode 100644 hw/ftapbbrg020.c
 create mode 100644 hw/ftapbbrg020.h

Comments

Paul Brook Jan. 25, 2013, 10:01 p.m. UTC | #1
> The FTAPBBRG020 supports the DMA functions for the AHB-to-AHB,
> AHB-to-APB, APB-to-AHB, and APB-to-APB transactions.

All the timer code in this file looks suspect.  As a general rule everything 
should be event driven and complete immediately (or at least schedule a BH for 
immediate action if recursion is a concern), not relying on a periodic timer 
interrupts.

> +        qemu_mod_timer(s->qtimer,
> +            qemu_get_clock_ns(vm_clock) + 1);

For all practical purposes this is going to happen immediately, so you should 
not be using a timer.

> +        qemu_mod_timer(s->qtimer,
> +            qemu_get_clock_ns(vm_clock) + (get_ticks_per_sec() >> 2));

Why 0.25 seconds?  Usually this sort of try-again-soon behavior means you've 
missed a trigger event somewhere else.

> +    if (!cpu_physical_memory_is_io(c->src)) {
> +        src_map = src_ptr = cpu_physical_memory_map(c->src, &src_len, 0);
> +    }
> +    if (!cpu_physical_memory_is_io(c->dst)) {
> +        dst_map = dst_ptr = cpu_physical_memory_map(c->dst, &dst_len, 1);
> +    }

cpu_physical_memory_map might not map the whole region you requested.  This 
will cause badness in the subsequent code.


I suspect a log of this code anc and should be shared with your other DMA 
controller, and probably several of the existing DMA controllers.

Paul
Kuo-Jung Su Jan. 28, 2013, 6:36 a.m. UTC | #2
2013/1/26 Paul Brook <paul@codesourcery.com>

> > The FTAPBBRG020 supports the DMA functions for the AHB-to-AHB,
> > AHB-to-APB, APB-to-AHB, and APB-to-APB transactions.
>
> All the timer code in this file looks suspect.  As a general rule
> everything
> should be event driven and complete immediately (or at least schedule a BH
> for
> immediate action if recursion is a concern), not relying on a periodic
> timer
> interrupts.
>

Agree,
I've cheated here, because I'm too lazy to checkout how to use mutex/thread
in QEMU,
I'll fix it later.


>
> > +        qemu_mod_timer(s->qtimer,
> > +            qemu_get_clock_ns(vm_clock) + 1);
>
> For all practical purposes this is going to happen immediately, so you
> should
> not be using a timer.
>
> > +        qemu_mod_timer(s->qtimer,
> > +            qemu_get_clock_ns(vm_clock) + (get_ticks_per_sec() >> 2));
>
> Why 0.25 seconds?  Usually this sort of try-again-soon behavior means
> you've
> missed a trigger event somewhere else.
>
>
Yes,
it's because I'm using timer instead of mutext/thread here,
so there might be some race conditions, and thus I added this stupid
work-around here.


> > +    if (!cpu_physical_memory_is_io(c->src)) {
> > +        src_map = src_ptr = cpu_physical_memory_map(c->src, &src_len,
> 0);
> > +    }
> > +    if (!cpu_physical_memory_is_io(c->dst)) {
> > +        dst_map = dst_ptr = cpu_physical_memory_map(c->dst, &dst_len,
> 1);
> > +    }
>
> cpu_physical_memory_map might not map the whole region you requested.  This
> will cause badness in the subsequent code.
>
>
My bad, I forgot this, I'll fix it later.


>
> I suspect a log of this code anc and should be shared with your other DMA
> controller, and probably several of the existing DMA controllers.
>
> Paul
>
diff mbox

Patch

diff --git a/hw/ftapbbrg020.c b/hw/ftapbbrg020.c
new file mode 100644
index 0000000..e32b022
--- /dev/null
+++ b/hw/ftapbbrg020.c
@@ -0,0 +1,502 @@ 
+/*
+ * QEMU model of the FTAPBBRG020 DMA Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is licensed under GNU GPL v2.
+ *
+ * Note: The FTAPBBRG020 DMA decreasing address mode is not implemented.
+ */
+
+#include "sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/blockdev.h"
+
+#include "ftapbbrg020.h"
+
+#define TYPE_FTAPBBRG020    "ftapbbrg020"
+
+typedef struct ftapbbrg020_state ftapbbrg020_state;
+
+typedef struct ftapbbrg020_chan {
+    ftapbbrg020_state *chip;
+
+    int id;
+    int burst;
+    int src_bw;
+    int src_stride;
+    int dst_bw;
+    int dst_stride;
+
+    /* HW register caches */
+    uint32_t src;
+    uint32_t dst;
+    uint32_t len;
+    uint32_t cmd;
+} ftapbbrg020_chan;
+
+typedef struct ftapbbrg020_state {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    qemu_irq irq;
+
+    ftapbbrg020_chan chan[4];
+    qemu_irq         ack[16];
+    uint32_t         req;
+
+    int busy;    /* Busy Channel ID */
+    QEMUTimer *qtimer;
+} ftapbbrg020_state;
+
+#define FTAPBBRG020(obj) \
+    OBJECT_CHECK(ftapbbrg020_state, obj, TYPE_FTAPBBRG020)
+
+static uint32_t ftapbbrg020_get_isr(ftapbbrg020_state *s)
+{
+    int i;
+    uint32_t isr = 0;
+    ftapbbrg020_chan *chan;
+
+    for (i = 0; i < 4; ++i) {
+        chan = s->chan + i;
+        isr |= (chan->cmd & CMD_INTR_STATUS);
+    }
+
+    return isr;
+}
+
+static void ftapbbrg020_update_irq(ftapbbrg020_state *s)
+{
+    uint32_t isr = ftapbbrg020_get_isr(s);
+
+    qemu_set_irq(s->irq, isr ? 1 : 0);
+}
+
+static void ftapbbrg020_chan_cmd_decode(ftapbbrg020_chan *c)
+{
+    uint32_t tmp;
+
+    /* 1. decode burst size */
+    c->burst = (c->cmd & CMD_BURST4) ? 4 : 1;
+
+    /* 2. decode source/destination width */
+    tmp = (c->cmd >> 20) & 0x03;
+    if (tmp > 2) {
+        tmp = 2;
+    }
+    c->src_bw = c->dst_bw = 8 << (2 - tmp);
+
+    /* 3. decode source address stride */
+    switch ((c->cmd >> 8) & 0x03) {
+    case 0:
+        c->src_stride = 0;
+        break;
+    case 1:
+        c->src_stride = c->src_bw >> 3;
+        break;
+    case 2:
+        c->src_stride = 2 * (c->src_bw >> 3);
+        break;
+    case 3:
+        c->src_stride = 4 * (c->src_bw >> 3);
+        break;
+    }
+
+    /* 4. decode destination address stride */
+    switch ((c->cmd >> 12) & 0x03) {
+    case 0:
+        c->dst_stride = 0;
+        break;
+    case 1:
+        c->dst_stride = c->dst_bw >> 3;
+        break;
+    case 2:
+        c->dst_stride = 2 * (c->dst_bw >> 3);
+        break;
+    case 3:
+        c->dst_stride = 4 * (c->dst_bw >> 3);
+        break;
+    }
+}
+
+static void ftapbbrg020_chan_start(ftapbbrg020_chan *c)
+{
+    ftapbbrg020_state *s = c->chip;
+    hwaddr src, dst, src_len, dst_len;
+    const uint8_t *src_map = NULL, *src_ptr = NULL;
+    uint8_t *dst_map = NULL, *dst_ptr = NULL;
+    uint8_t buf[4096];
+    int src_hs = 0, dst_hs = 0, len = 0;
+
+    if (!(c->cmd & CMD_START)) {
+        return;
+    }
+
+    s->busy = c->id;
+
+    /* DMA src/dst address */
+    src = c->src;
+    dst = c->dst;
+    /* DMA hardware handshake id */
+    src_hs = (c->cmd >> 24) & 0xf;
+    dst_hs = (c->cmd >> 16) & 0xf;
+    /* DMA src/dst data length */
+    src_len = c->src_stride ? (c->len * (c->src_bw >> 3)) : (c->src_bw >> 3);
+    dst_len = c->dst_stride ? (c->len * (c->dst_bw >> 3)) : (c->dst_bw >> 3);
+    if (!cpu_physical_memory_is_io(c->src)) {
+        src_map = src_ptr = cpu_physical_memory_map(c->src, &src_len, 0);
+    }
+    if (!cpu_physical_memory_is_io(c->dst)) {
+        dst_map = dst_ptr = cpu_physical_memory_map(c->dst, &dst_len, 1);
+    }
+
+    while (c->len > 0) {
+        /*
+         * Postpone this DMA action
+         * if the corresponding dma request is not asserted
+         */
+        if (src_hs && !(s->req & (1 << src_hs))) {
+            break;
+        }
+        if (dst_hs && !(s->req & (1 << dst_hs))) {
+            break;
+        }
+
+        len = MIN(sizeof(buf), c->burst * (c->src_bw >> 3));
+
+        /* load data from source into local buffer */
+        if (src_ptr) {
+            if (c->src_stride) {
+                /* increasing address mode */
+                memcpy(buf, src_ptr, len);
+                src += len;
+                src_ptr += len;
+            } else {
+                /* fixed address mode */
+                int i;
+                switch (c->src_bw) {
+                case 8:
+                    for (i = 0; i < len; i += 1) {
+                        *(uint8_t *)(buf + i) = *(const uint8_t *)src_ptr;
+                    }
+                    break;
+                case 16:
+                    for (i = 0; i < len; i += 2) {
+                        *(uint16_t *)(buf + i) = *(const uint16_t *)src_ptr;
+                    }
+                    break;
+                default:
+                    for (i = 0; i < len; i += 4) {
+                        *(uint32_t *)(buf + i) = *(const uint32_t *)src_ptr;
+                    }
+                    break;
+                }
+            }
+        } else {
+            uint32_t rl, stride = c->src_bw >> 3;
+            for (rl = 0; rl < len; rl += stride, src += c->src_stride) {
+                cpu_physical_memory_read(src, (uint64_t *)(buf + rl), stride);
+            }
+        }
+
+        /* DMA Hardware Handshake */
+        if (src_hs) {
+            qemu_set_irq(s->ack[src_hs], 1);
+        }
+
+        /* store data into destination from local buffer */
+        if (dst_ptr) {
+            if (c->dst_stride) {
+                /* increasing address mode */
+                memcpy(dst_ptr, buf, len);
+                dst += len;
+                dst_ptr += len;
+            } else {
+                /* fixed address mode */
+                int i;
+                switch (c->dst_bw) {
+                case 8:
+                    for (i = 0; i < len; i += 1) {
+                        *(uint8_t *)dst_ptr = *(uint8_t *)(buf + i);
+                    }
+                    break;
+                case 16:
+                    for (i = 0; i < len; i += 2) {
+                        *(uint16_t *)dst_ptr = *(uint16_t *)(buf + i);
+                    }
+                    break;
+                default:
+                    for (i = 0; i < len; i += 4) {
+                        *(uint32_t *)dst_ptr = *(uint32_t *)(buf + i);
+                    }
+                    break;
+                }
+            }
+        } else {
+            uint32_t wl, stride = c->dst_bw >> 3;
+            for (wl = 0; wl < len; wl += stride, dst += c->dst_stride) {
+                cpu_physical_memory_write(dst, (uint64_t *)(buf + wl), stride);
+            }
+        }
+
+        /* DMA Hardware Handshake */
+        if (dst_hs) {
+            qemu_set_irq(s->ack[dst_hs], 1);
+        }
+
+        /* update the channel transfer size */
+        c->len -= len / (c->src_bw >> 3);
+
+        if (c->len == 0) {
+            /* release the memory mappings */
+            if (src_map) {
+                cpu_physical_memory_unmap((void *)src_map,
+                                          src_len,
+                                          0,
+                                          (hwaddr)(src_ptr - src_map));
+                src_map = src_ptr = NULL;
+            }
+            if (dst_map) {
+                cpu_physical_memory_unmap(dst_map,
+                                          dst_len,
+                                          1,
+                                          (hwaddr)(dst_ptr - dst_map));
+                dst_map = dst_ptr = NULL;
+            }
+            /* update the channel transfer status */
+            if (c->cmd & CMD_INTR_FIN_EN) {
+                c->cmd |= CMD_INTR_FIN;
+                ftapbbrg020_update_irq(s);
+            }
+            /* clear start bit */
+            c->cmd &= 0xfffffffe;
+        }
+    }
+
+    /* release the memory mappings */
+    if (src_map) {
+        cpu_physical_memory_unmap((void *)src_map,
+                                  src_len,
+                                  0,
+                                  (hwaddr)(src_ptr - src_map));
+    }
+    if (dst_map) {
+        cpu_physical_memory_unmap(dst_map,
+                                  dst_len,
+                                  1,
+                                  (hwaddr)(dst_ptr - dst_map));
+    }
+
+    /* update src/dst address */
+    c->src = src;
+    c->dst = dst;
+
+    s->busy = -1;
+}
+
+static void ftapbbrg020_chan_reset(ftapbbrg020_chan *c)
+{
+    c->cmd = 0;
+    c->src = 0;
+    c->dst = 0;
+    c->len = 0;
+}
+
+static void ftapbbrg020_timer_tick(void *opaque)
+{
+    ftapbbrg020_state *s = FTAPBBRG020(opaque);
+    ftapbbrg020_chan  *c = NULL;
+    int i, jobs, done;
+
+    jobs = 0;
+    done = 0;
+    for (i = 0; i < 4; ++i) {
+        c = s->chan + i;
+        if (c->cmd & CMD_START) {
+            ++jobs;
+            ftapbbrg020_chan_start(c);
+            if (!(c->cmd & CMD_START)) {
+                ++done;
+            }
+        }
+    }
+
+    /* ToDo: Use mutex to skip this periodic checker */
+    if (jobs - done > 0) {
+        qemu_mod_timer(s->qtimer,
+            qemu_get_clock_ns(vm_clock) + 1);
+    } else {
+        qemu_mod_timer(s->qtimer,
+            qemu_get_clock_ns(vm_clock) + (get_ticks_per_sec() >> 2));
+    }
+}
+
+static void ftapbbrg020_handle_req(void *opaque, int line, int level)
+{
+    ftapbbrg020_state *s = FTAPBBRG020(opaque);
+
+    if (level) {
+        s->req |= (1 << line);
+    } else {
+        s->req &= ~(1 << line);
+        qemu_set_irq(s->ack[line], 0);
+    }
+}
+
+static void ftapbbrg020_chip_reset(ftapbbrg020_state *s)
+{
+    int i;
+
+    for (i = 0; i < 4; ++i) {
+        ftapbbrg020_chan_reset(s->chan + i);
+    }
+
+    /* We can assume our GPIO have been wired up now */
+    for (i = 0; i < 16; ++i) {
+        qemu_set_irq(s->ack[i], 0);
+    }
+    s->req = 0;
+}
+
+static uint64_t ftapbbrg020_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ftapbbrg020_state *s = FTAPBBRG020(opaque);
+    ftapbbrg020_chan  *c = NULL;
+    uint32_t ret = 0;
+
+    if (addr >= REG_CHAN_BASE(0) && addr < REG_CHAN_BASE(4)) {
+        c = s->chan + REG_CHAN_ID(addr);
+        switch (addr & 0x0f) {
+        case REG_CHAN_CMD:
+            return c->cmd;
+        case REG_CHAN_SRC:
+            return c->src;
+        case REG_CHAN_DST:
+            return c->dst;
+        case REG_CHAN_CYC:
+            return c->len;
+        }
+    } else {
+        switch (addr) {
+        case REG_REVISION:
+            return 0x00010800;
+        default:
+            break;
+        }
+    }
+
+    return ret;
+}
+
+static void ftapbbrg020_mem_write(void    *opaque,
+                                  hwaddr   addr,
+                                  uint64_t val,
+                                  unsigned size)
+{
+    ftapbbrg020_state *s = FTAPBBRG020(opaque);
+    ftapbbrg020_chan  *c = NULL;
+
+    if (addr >= REG_CHAN_BASE(0) && addr < REG_CHAN_BASE(4)) {
+        c = s->chan + REG_CHAN_ID(addr);
+        switch (addr & 0x0f) {
+        case REG_CHAN_CMD:
+            c->cmd = (uint32_t)val;
+            ftapbbrg020_update_irq(s);
+            if (c->cmd & 0x01) {
+                ftapbbrg020_chan_cmd_decode(c);
+                /* kick-off DMA engine */
+                qemu_mod_timer(s->qtimer, qemu_get_clock_ns(vm_clock) + 1);
+            }
+            break;
+        case REG_CHAN_SRC:
+            c->src = (uint32_t)val;
+            break;
+        case REG_CHAN_DST:
+            c->dst = (uint32_t)val;
+            break;
+        case REG_CHAN_CYC:
+            c->len = (uint32_t)val & 0x00ffffff;
+            break;
+        }
+    }
+}
+
+static const MemoryRegionOps ftapbbrg020_ops = {
+    .read  = ftapbbrg020_mem_read,
+    .write = ftapbbrg020_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+static void ftapbbrg020_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    ftapbbrg020_state *s = FTAPBBRG020(FROM_SYSBUS(ftapbbrg020_state, busdev));
+
+    ftapbbrg020_chip_reset(s);
+}
+
+static int ftapbbrg020_init(SysBusDevice *dev)
+{
+    ftapbbrg020_state *s = FTAPBBRG020(FROM_SYSBUS(ftapbbrg020_state, dev));
+    int i;
+
+    memory_region_init_io(&s->iomem,
+                          &ftapbbrg020_ops,
+                          s,
+                          TYPE_FTAPBBRG020,
+                          0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    qdev_init_gpio_in(&s->busdev.qdev, ftapbbrg020_handle_req, 16);
+    qdev_init_gpio_out(&s->busdev.qdev, s->ack, 16);
+
+    s->busy = -1;
+    s->qtimer = qemu_new_timer_ns(vm_clock, ftapbbrg020_timer_tick, s);
+    for (i = 0; i < 4; ++i) {
+        ftapbbrg020_chan *c = s->chan + i;
+        c->id   = i;
+        c->chip = s;
+    }
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftapbbrg020 = {
+    .name = TYPE_FTAPBBRG020,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void ftapbbrg020_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init     = ftapbbrg020_init;
+    dc->vmsd    = &vmstate_ftapbbrg020;
+    dc->reset   = ftapbbrg020_reset;
+    dc->no_user = 1;
+}
+
+static const TypeInfo ftapbbrg020_info = {
+    .name           = TYPE_FTAPBBRG020,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(ftapbbrg020_state),
+    .class_init     = ftapbbrg020_class_init,
+};
+
+static void ftapbbrg020_register_types(void)
+{
+    type_register_static(&ftapbbrg020_info);
+}
+
+type_init(ftapbbrg020_register_types)
diff --git a/hw/ftapbbrg020.h b/hw/ftapbbrg020.h
new file mode 100644
index 0000000..c5414a6
--- /dev/null
+++ b/hw/ftapbbrg020.h
@@ -0,0 +1,41 @@ 
+/*
+ * QEMU model of the FTAPBBRG020 DMA Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is licensed under GNU GPL.
+ *
+ * Note: The FTAPBBRG020 DMA decreasing address mode is not implemented.
+ */
+
+#ifndef __FTAPBBRG020_H
+#define __FTAPBBRG020_H
+
+#define REG_REVISION        0xC8
+
+/*
+ * Channel base address
+ * @ch: channle id (0 <= id <= 3)
+ *      i.e. 0: Channel A
+ *           1: Channel B
+ *           2: Channel C
+ *           3: Channel D
+ */
+#define REG_CHAN_ID(off)    (((off) - 0x80) >> 4)
+#define REG_CHAN_BASE(ch)   (0x80 + ((ch) << 4))
+
+#define REG_CHAN_SRC        0x00
+#define REG_CHAN_DST        0x04
+#define REG_CHAN_CYC        0x08
+#define REG_CHAN_CMD        0x0C
+
+#define CMD_START           (1 << 0)
+#define CMD_INTR_FIN        (1 << 1)
+#define CMD_INTR_FIN_EN     (1 << 2)
+#define CMD_BURST4          (1 << 3)
+#define CMD_INTR_ERR        (1 << 4)
+#define CMD_INTR_ERR_EN     (1 << 5)
+#define CMD_INTR_STATUS     (CMD_INTR_FIN | CMD_INTR_ERR)
+
+#endif    /* __FTAPBB020_H */