qemu/hw/dma/soc_dma.c
<<
>>
Prefs
   1/*
   2 * On-chip DMA controller framework.
   3 *
   4 * Copyright (C) 2008 Nokia Corporation
   5 * Written by Andrzej Zaborowski <andrew@openedhand.com>
   6 *
   7 * This program is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU General Public License as
   9 * published by the Free Software Foundation; either version 2 or
  10 * (at your option) version 3 of the License.
  11 *
  12 * This program is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License along
  18 * with this program; if not, see <http://www.gnu.org/licenses/>.
  19 */
  20#include "qemu/osdep.h"
  21#include "qemu-common.h"
  22#include "qemu/timer.h"
  23#include "hw/arm/soc_dma.h"
  24
  25static void transfer_mem2mem(struct soc_dma_ch_s *ch)
  26{
  27    memcpy(ch->paddr[0], ch->paddr[1], ch->bytes);
  28    ch->paddr[0] += ch->bytes;
  29    ch->paddr[1] += ch->bytes;
  30}
  31
  32static void transfer_mem2fifo(struct soc_dma_ch_s *ch)
  33{
  34    ch->io_fn[1](ch->io_opaque[1], ch->paddr[0], ch->bytes);
  35    ch->paddr[0] += ch->bytes;
  36}
  37
  38static void transfer_fifo2mem(struct soc_dma_ch_s *ch)
  39{
  40    ch->io_fn[0](ch->io_opaque[0], ch->paddr[1], ch->bytes);
  41    ch->paddr[1] += ch->bytes;
  42}
  43
  44/* This is further optimisable but isn't very important because often
  45 * DMA peripherals forbid this kind of transfers and even when they don't,
  46 * oprating systems may not need to use them.  */
  47static void *fifo_buf;
  48static int fifo_size;
  49static void transfer_fifo2fifo(struct soc_dma_ch_s *ch)
  50{
  51    if (ch->bytes > fifo_size)
  52        fifo_buf = g_realloc(fifo_buf, fifo_size = ch->bytes);
  53
  54    /* Implement as transfer_fifo2linear + transfer_linear2fifo.  */
  55    ch->io_fn[0](ch->io_opaque[0], fifo_buf, ch->bytes);
  56    ch->io_fn[1](ch->io_opaque[1], fifo_buf, ch->bytes);
  57}
  58
  59struct dma_s {
  60    struct soc_dma_s soc;
  61    int chnum;
  62    uint64_t ch_enable_mask;
  63    int64_t channel_freq;
  64    int enabled_count;
  65
  66    struct memmap_entry_s {
  67        enum soc_dma_port_type type;
  68        hwaddr addr;
  69        union {
  70           struct {
  71               void *opaque;
  72               soc_dma_io_t fn;
  73               int out;
  74           } fifo;
  75           struct {
  76               void *base;
  77               size_t size;
  78           } mem;
  79        } u;
  80    } *memmap;
  81    int memmap_size;
  82
  83    struct soc_dma_ch_s ch[0];
  84};
  85
  86static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, int delay_bytes)
  87{
  88    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
  89    struct dma_s *dma = (struct dma_s *) ch->dma;
  90
  91    timer_mod(ch->timer, now + delay_bytes / dma->channel_freq);
  92}
  93
  94static void soc_dma_ch_run(void *opaque)
  95{
  96    struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
  97
  98    ch->running = 1;
  99    ch->dma->setup_fn(ch);
 100    ch->transfer_fn(ch);
 101    ch->running = 0;
 102
 103    if (ch->enable)
 104        soc_dma_ch_schedule(ch, ch->bytes);
 105    ch->bytes = 0;
 106}
 107
 108static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
 109                hwaddr addr)
 110{
 111    struct memmap_entry_s *lo;
 112    int hi;
 113
 114    lo = dma->memmap;
 115    hi = dma->memmap_size;
 116
 117    while (hi > 1) {
 118        hi /= 2;
 119        if (lo[hi].addr <= addr)
 120            lo += hi;
 121    }
 122
 123    return lo;
 124}
 125
 126static inline enum soc_dma_port_type soc_dma_ch_update_type(
 127                struct soc_dma_ch_s *ch, int port)
 128{
 129    struct dma_s *dma = (struct dma_s *) ch->dma;
 130    struct memmap_entry_s *entry = soc_dma_lookup(dma, ch->vaddr[port]);
 131
 132    if (entry->type == soc_dma_port_fifo) {
 133        while (entry < dma->memmap + dma->memmap_size &&
 134                        entry->u.fifo.out != port)
 135            entry ++;
 136        if (entry->addr != ch->vaddr[port] || entry->u.fifo.out != port)
 137            return soc_dma_port_other;
 138
 139        if (ch->type[port] != soc_dma_access_const)
 140            return soc_dma_port_other;
 141
 142        ch->io_fn[port] = entry->u.fifo.fn;
 143        ch->io_opaque[port] = entry->u.fifo.opaque;
 144        return soc_dma_port_fifo;
 145    } else if (entry->type == soc_dma_port_mem) {
 146        if (entry->addr > ch->vaddr[port] ||
 147                        entry->addr + entry->u.mem.size <= ch->vaddr[port])
 148            return soc_dma_port_other;
 149
 150        /* TODO: support constant memory address for source port as used for
 151         * drawing solid rectangles by PalmOS(R).  */
 152        if (ch->type[port] != soc_dma_access_const)
 153            return soc_dma_port_other;
 154
 155        ch->paddr[port] = (uint8_t *) entry->u.mem.base +
 156                (ch->vaddr[port] - entry->addr);
 157        /* TODO: save bytes left to the end of the mapping somewhere so we
 158         * can check we're not reading beyond it.  */
 159        return soc_dma_port_mem;
 160    } else
 161        return soc_dma_port_other;
 162}
 163
 164void soc_dma_ch_update(struct soc_dma_ch_s *ch)
 165{
 166    enum soc_dma_port_type src, dst;
 167
 168    src = soc_dma_ch_update_type(ch, 0);
 169    if (src == soc_dma_port_other) {
 170        ch->update = 0;
 171        ch->transfer_fn = ch->dma->transfer_fn;
 172        return;
 173    }
 174    dst = soc_dma_ch_update_type(ch, 1);
 175
 176    /* TODO: use src and dst as array indices.  */
 177    if (src == soc_dma_port_mem && dst == soc_dma_port_mem)
 178        ch->transfer_fn = transfer_mem2mem;
 179    else if (src == soc_dma_port_mem && dst == soc_dma_port_fifo)
 180        ch->transfer_fn = transfer_mem2fifo;
 181    else if (src == soc_dma_port_fifo && dst == soc_dma_port_mem)
 182        ch->transfer_fn = transfer_fifo2mem;
 183    else if (src == soc_dma_port_fifo && dst == soc_dma_port_fifo)
 184        ch->transfer_fn = transfer_fifo2fifo;
 185    else
 186        ch->transfer_fn = ch->dma->transfer_fn;
 187
 188    ch->update = (dst != soc_dma_port_other);
 189}
 190
 191static void soc_dma_ch_freq_update(struct dma_s *s)
 192{
 193    if (s->enabled_count)
 194        /* We completely ignore channel priorities and stuff */
 195        s->channel_freq = s->soc.freq / s->enabled_count;
 196    else {
 197        /* TODO: Signal that we want to disable the functional clock and let
 198         * the platform code decide what to do with it, i.e. check that
 199         * auto-idle is enabled in the clock controller and if we are stopping
 200         * the clock, do the same with any parent clocks that had only one
 201         * user keeping them on and auto-idle enabled.  */
 202    }
 203}
 204
 205void soc_dma_set_request(struct soc_dma_ch_s *ch, int level)
 206{
 207    struct dma_s *dma = (struct dma_s *) ch->dma;
 208
 209    dma->enabled_count += level - ch->enable;
 210
 211    if (level)
 212        dma->ch_enable_mask |= 1 << ch->num;
 213    else
 214        dma->ch_enable_mask &= ~(1 << ch->num);
 215
 216    if (level != ch->enable) {
 217        soc_dma_ch_freq_update(dma);
 218        ch->enable = level;
 219
 220        if (!ch->enable)
 221            timer_del(ch->timer);
 222        else if (!ch->running)
 223            soc_dma_ch_run(ch);
 224        else
 225            soc_dma_ch_schedule(ch, 1);
 226    }
 227}
 228
 229void soc_dma_reset(struct soc_dma_s *soc)
 230{
 231    struct dma_s *s = (struct dma_s *) soc;
 232
 233    s->soc.drqbmp = 0;
 234    s->ch_enable_mask = 0;
 235    s->enabled_count = 0;
 236    soc_dma_ch_freq_update(s);
 237}
 238
 239/* TODO: take a functional-clock argument */
 240struct soc_dma_s *soc_dma_init(int n)
 241{
 242    int i;
 243    struct dma_s *s = g_malloc0(sizeof(*s) + n * sizeof(*s->ch));
 244
 245    s->chnum = n;
 246    s->soc.ch = s->ch;
 247    for (i = 0; i < n; i ++) {
 248        s->ch[i].dma = &s->soc;
 249        s->ch[i].num = i;
 250        s->ch[i].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, soc_dma_ch_run, &s->ch[i]);
 251    }
 252
 253    soc_dma_reset(&s->soc);
 254    fifo_size = 0;
 255
 256    return &s->soc;
 257}
 258
 259void soc_dma_port_add_fifo(struct soc_dma_s *soc, hwaddr virt_base,
 260                soc_dma_io_t fn, void *opaque, int out)
 261{
 262    struct memmap_entry_s *entry;
 263    struct dma_s *dma = (struct dma_s *) soc;
 264
 265    dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
 266                    (dma->memmap_size + 1));
 267    entry = soc_dma_lookup(dma, virt_base);
 268
 269    if (dma->memmap_size) {
 270        if (entry->type == soc_dma_port_mem) {
 271            if (entry->addr <= virt_base &&
 272                            entry->addr + entry->u.mem.size > virt_base) {
 273                fprintf(stderr, "%s: FIFO at %"PRIx64
 274                                " collides with RAM region at %"PRIx64
 275                                "-%"PRIx64 "\n", __func__,
 276                                virt_base, entry->addr,
 277                                (entry->addr + entry->u.mem.size));
 278                exit(-1);
 279            }
 280
 281            if (entry->addr <= virt_base)
 282                entry ++;
 283        } else
 284            while (entry < dma->memmap + dma->memmap_size &&
 285                            entry->addr <= virt_base) {
 286                if (entry->addr == virt_base && entry->u.fifo.out == out) {
 287                    fprintf(stderr, "%s: FIFO at %"PRIx64
 288                                    " collides FIFO at %"PRIx64 "\n",
 289                                    __func__, virt_base, entry->addr);
 290                    exit(-1);
 291                }
 292
 293                entry ++;
 294            }
 295
 296        memmove(entry + 1, entry,
 297                        (uint8_t *) (dma->memmap + dma->memmap_size ++) -
 298                        (uint8_t *) entry);
 299    } else
 300        dma->memmap_size ++;
 301
 302    entry->addr          = virt_base;
 303    entry->type          = soc_dma_port_fifo;
 304    entry->u.fifo.fn     = fn;
 305    entry->u.fifo.opaque = opaque;
 306    entry->u.fifo.out    = out;
 307}
 308
 309void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
 310                hwaddr virt_base, size_t size)
 311{
 312    struct memmap_entry_s *entry;
 313    struct dma_s *dma = (struct dma_s *) soc;
 314
 315    dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
 316                    (dma->memmap_size + 1));
 317    entry = soc_dma_lookup(dma, virt_base);
 318
 319    if (dma->memmap_size) {
 320        if (entry->type == soc_dma_port_mem) {
 321            if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
 322                            (entry->addr <= virt_base &&
 323                             entry->addr + entry->u.mem.size > virt_base)) {
 324                fprintf(stderr, "%s: RAM at %"PRIx64 "-%"PRIx64
 325                                " collides with RAM region at %"PRIx64
 326                                "-%"PRIx64 "\n", __func__,
 327                                virt_base, virt_base + size,
 328                                entry->addr, entry->addr + entry->u.mem.size);
 329                exit(-1);
 330            }
 331
 332            if (entry->addr <= virt_base)
 333                entry ++;
 334        } else {
 335            if (entry->addr >= virt_base &&
 336                            entry->addr < virt_base + size) {
 337                fprintf(stderr, "%s: RAM at %"PRIx64 "-%"PRIx64
 338                                " collides with FIFO at %"PRIx64
 339                                "\n", __func__,
 340                                virt_base, virt_base + size,
 341                                entry->addr);
 342                exit(-1);
 343            }
 344
 345            while (entry < dma->memmap + dma->memmap_size &&
 346                            entry->addr <= virt_base)
 347                entry ++;
 348        }
 349
 350        memmove(entry + 1, entry,
 351                        (uint8_t *) (dma->memmap + dma->memmap_size ++) -
 352                        (uint8_t *) entry);
 353    } else
 354        dma->memmap_size ++;
 355
 356    entry->addr          = virt_base;
 357    entry->type          = soc_dma_port_mem;
 358    entry->u.mem.base    = phys_base;
 359    entry->u.mem.size    = size;
 360}
 361
 362/* TODO: port removal for ports like PCMCIA memory */
 363