123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- /*
- * On-chip DMA controller framework.
- *
- * Copyright (C) 2008 Nokia Corporation
- * Written by Andrzej Zaborowski <andrew@openedhand.com>
- *
- * 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; either version 2 or
- * (at your option) version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * 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 "qemu/osdep.h"
- #include "qemu/error-report.h"
- #include "qemu/timer.h"
- #include "hw/arm/soc_dma.h"
- static void transfer_mem2mem(struct soc_dma_ch_s *ch)
- {
- memcpy(ch->paddr[0], ch->paddr[1], ch->bytes);
- ch->paddr[0] += ch->bytes;
- ch->paddr[1] += ch->bytes;
- }
- static void transfer_mem2fifo(struct soc_dma_ch_s *ch)
- {
- ch->io_fn[1](ch->io_opaque[1], ch->paddr[0], ch->bytes);
- ch->paddr[0] += ch->bytes;
- }
- static void transfer_fifo2mem(struct soc_dma_ch_s *ch)
- {
- ch->io_fn[0](ch->io_opaque[0], ch->paddr[1], ch->bytes);
- ch->paddr[1] += ch->bytes;
- }
- /* This is further optimisable but isn't very important because often
- * DMA peripherals forbid this kind of transfers and even when they don't,
- * oprating systems may not need to use them. */
- static void *fifo_buf;
- static int fifo_size;
- static void transfer_fifo2fifo(struct soc_dma_ch_s *ch)
- {
- if (ch->bytes > fifo_size)
- fifo_buf = g_realloc(fifo_buf, fifo_size = ch->bytes);
- /* Implement as transfer_fifo2linear + transfer_linear2fifo. */
- ch->io_fn[0](ch->io_opaque[0], fifo_buf, ch->bytes);
- ch->io_fn[1](ch->io_opaque[1], fifo_buf, ch->bytes);
- }
- struct dma_s {
- struct soc_dma_s soc;
- int chnum;
- uint64_t ch_enable_mask;
- int64_t channel_freq;
- int enabled_count;
- struct memmap_entry_s {
- enum soc_dma_port_type type;
- hwaddr addr;
- union {
- struct {
- void *opaque;
- soc_dma_io_t fn;
- int out;
- } fifo;
- struct {
- void *base;
- size_t size;
- } mem;
- } u;
- } *memmap;
- int memmap_size;
- struct soc_dma_ch_s ch[];
- };
- static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, int delay_bytes)
- {
- int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- struct dma_s *dma = (struct dma_s *) ch->dma;
- timer_mod(ch->timer, now + delay_bytes / dma->channel_freq);
- }
- static void soc_dma_ch_run(void *opaque)
- {
- struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
- ch->running = 1;
- ch->dma->setup_fn(ch);
- ch->transfer_fn(ch);
- ch->running = 0;
- if (ch->enable)
- soc_dma_ch_schedule(ch, ch->bytes);
- ch->bytes = 0;
- }
- static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
- hwaddr addr)
- {
- struct memmap_entry_s *lo;
- int hi;
- lo = dma->memmap;
- hi = dma->memmap_size;
- while (hi > 1) {
- hi /= 2;
- if (lo[hi].addr <= addr)
- lo += hi;
- }
- return lo;
- }
- static inline enum soc_dma_port_type soc_dma_ch_update_type(
- struct soc_dma_ch_s *ch, int port)
- {
- struct dma_s *dma = (struct dma_s *) ch->dma;
- struct memmap_entry_s *entry = soc_dma_lookup(dma, ch->vaddr[port]);
- if (entry->type == soc_dma_port_fifo) {
- while (entry < dma->memmap + dma->memmap_size &&
- entry->u.fifo.out != port)
- entry ++;
- if (entry->addr != ch->vaddr[port] || entry->u.fifo.out != port)
- return soc_dma_port_other;
- if (ch->type[port] != soc_dma_access_const)
- return soc_dma_port_other;
- ch->io_fn[port] = entry->u.fifo.fn;
- ch->io_opaque[port] = entry->u.fifo.opaque;
- return soc_dma_port_fifo;
- } else if (entry->type == soc_dma_port_mem) {
- if (entry->addr > ch->vaddr[port] ||
- entry->addr + entry->u.mem.size <= ch->vaddr[port])
- return soc_dma_port_other;
- /* TODO: support constant memory address for source port as used for
- * drawing solid rectangles by PalmOS(R). */
- if (ch->type[port] != soc_dma_access_const)
- return soc_dma_port_other;
- ch->paddr[port] = (uint8_t *) entry->u.mem.base +
- (ch->vaddr[port] - entry->addr);
- /* TODO: save bytes left to the end of the mapping somewhere so we
- * can check we're not reading beyond it. */
- return soc_dma_port_mem;
- } else
- return soc_dma_port_other;
- }
- void soc_dma_ch_update(struct soc_dma_ch_s *ch)
- {
- enum soc_dma_port_type src, dst;
- src = soc_dma_ch_update_type(ch, 0);
- if (src == soc_dma_port_other) {
- ch->update = 0;
- ch->transfer_fn = ch->dma->transfer_fn;
- return;
- }
- dst = soc_dma_ch_update_type(ch, 1);
- /* TODO: use src and dst as array indices. */
- if (src == soc_dma_port_mem && dst == soc_dma_port_mem)
- ch->transfer_fn = transfer_mem2mem;
- else if (src == soc_dma_port_mem && dst == soc_dma_port_fifo)
- ch->transfer_fn = transfer_mem2fifo;
- else if (src == soc_dma_port_fifo && dst == soc_dma_port_mem)
- ch->transfer_fn = transfer_fifo2mem;
- else if (src == soc_dma_port_fifo && dst == soc_dma_port_fifo)
- ch->transfer_fn = transfer_fifo2fifo;
- else
- ch->transfer_fn = ch->dma->transfer_fn;
- ch->update = (dst != soc_dma_port_other);
- }
- static void soc_dma_ch_freq_update(struct dma_s *s)
- {
- if (s->enabled_count)
- /* We completely ignore channel priorities and stuff */
- s->channel_freq = s->soc.freq / s->enabled_count;
- else {
- /* TODO: Signal that we want to disable the functional clock and let
- * the platform code decide what to do with it, i.e. check that
- * auto-idle is enabled in the clock controller and if we are stopping
- * the clock, do the same with any parent clocks that had only one
- * user keeping them on and auto-idle enabled. */
- }
- }
- void soc_dma_set_request(struct soc_dma_ch_s *ch, int level)
- {
- struct dma_s *dma = (struct dma_s *) ch->dma;
- dma->enabled_count += level - ch->enable;
- if (level)
- dma->ch_enable_mask |= (uint64_t)1 << ch->num;
- else
- dma->ch_enable_mask &= ~((uint64_t)1 << ch->num);
- if (level != ch->enable) {
- soc_dma_ch_freq_update(dma);
- ch->enable = level;
- if (!ch->enable)
- timer_del(ch->timer);
- else if (!ch->running)
- soc_dma_ch_run(ch);
- else
- soc_dma_ch_schedule(ch, 1);
- }
- }
- void soc_dma_reset(struct soc_dma_s *soc)
- {
- struct dma_s *s = (struct dma_s *) soc;
- s->soc.drqbmp = 0;
- s->ch_enable_mask = 0;
- s->enabled_count = 0;
- soc_dma_ch_freq_update(s);
- }
- /* TODO: take a functional-clock argument */
- struct soc_dma_s *soc_dma_init(int n)
- {
- int i;
- struct dma_s *s = g_malloc0(sizeof(*s) + n * sizeof(*s->ch));
- s->chnum = n;
- s->soc.ch = s->ch;
- for (i = 0; i < n; i ++) {
- s->ch[i].dma = &s->soc;
- s->ch[i].num = i;
- s->ch[i].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, soc_dma_ch_run, &s->ch[i]);
- }
- soc_dma_reset(&s->soc);
- fifo_size = 0;
- return &s->soc;
- }
- void soc_dma_port_add_fifo(struct soc_dma_s *soc, hwaddr virt_base,
- soc_dma_io_t fn, void *opaque, int out)
- {
- struct memmap_entry_s *entry;
- struct dma_s *dma = (struct dma_s *) soc;
- dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
- (dma->memmap_size + 1));
- entry = soc_dma_lookup(dma, virt_base);
- if (dma->memmap_size) {
- if (entry->type == soc_dma_port_mem) {
- if (entry->addr <= virt_base &&
- entry->addr + entry->u.mem.size > virt_base) {
- error_report("%s: FIFO at %"PRIx64
- " collides with RAM region at %"PRIx64
- "-%"PRIx64, __func__,
- virt_base, entry->addr,
- (entry->addr + entry->u.mem.size));
- exit(-1);
- }
- if (entry->addr <= virt_base)
- entry ++;
- } else
- while (entry < dma->memmap + dma->memmap_size &&
- entry->addr <= virt_base) {
- if (entry->addr == virt_base && entry->u.fifo.out == out) {
- error_report("%s: FIFO at %"PRIx64
- " collides FIFO at %"PRIx64,
- __func__, virt_base, entry->addr);
- exit(-1);
- }
- entry ++;
- }
- memmove(entry + 1, entry,
- (uint8_t *) (dma->memmap + dma->memmap_size ++) -
- (uint8_t *) entry);
- } else
- dma->memmap_size ++;
- entry->addr = virt_base;
- entry->type = soc_dma_port_fifo;
- entry->u.fifo.fn = fn;
- entry->u.fifo.opaque = opaque;
- entry->u.fifo.out = out;
- }
- void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
- hwaddr virt_base, size_t size)
- {
- struct memmap_entry_s *entry;
- struct dma_s *dma = (struct dma_s *) soc;
- dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
- (dma->memmap_size + 1));
- entry = soc_dma_lookup(dma, virt_base);
- if (dma->memmap_size) {
- if (entry->type == soc_dma_port_mem) {
- if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
- (entry->addr <= virt_base &&
- entry->addr + entry->u.mem.size > virt_base)) {
- error_report("%s: RAM at %"PRIx64 "-%"PRIx64
- " collides with RAM region at %"PRIx64
- "-%"PRIx64, __func__,
- virt_base, virt_base + size,
- entry->addr, entry->addr + entry->u.mem.size);
- exit(-1);
- }
- if (entry->addr <= virt_base)
- entry ++;
- } else {
- if (entry->addr >= virt_base &&
- entry->addr < virt_base + size) {
- error_report("%s: RAM at %"PRIx64 "-%"PRIx64
- " collides with FIFO at %"PRIx64,
- __func__, virt_base, virt_base + size,
- entry->addr);
- exit(-1);
- }
- while (entry < dma->memmap + dma->memmap_size &&
- entry->addr <= virt_base)
- entry ++;
- }
- memmove(entry + 1, entry,
- (uint8_t *) (dma->memmap + dma->memmap_size ++) -
- (uint8_t *) entry);
- } else
- dma->memmap_size ++;
- entry->addr = virt_base;
- entry->type = soc_dma_port_mem;
- entry->u.mem.base = phys_base;
- entry->u.mem.size = size;
- }
- /* TODO: port removal for ports like PCMCIA memory */
|