Browse Source
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4920 c046a42c-6fe2-441c-8c8c-71466251a162stable-0.10
7 changed files with 896 additions and 164 deletions
@ -0,0 +1,366 @@ |
|||
/*
|
|||
* 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, write to the Free Software |
|||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
|||
* MA 02111-1307 USA |
|||
*/ |
|||
#include "qemu-common.h" |
|||
#include "qemu-timer.h" |
|||
#include "soc_dma.h" |
|||
|
|||
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; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
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; |
|||
void transfer_fifo2fifo(struct soc_dma_ch_s *ch) |
|||
{ |
|||
if (ch->bytes < fifo_size) |
|||
fifo_buf = 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; |
|||
target_phys_addr_t 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[0]; |
|||
}; |
|||
|
|||
static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, int delay_bytes) |
|||
{ |
|||
int64_t now = qemu_get_clock(vm_clock); |
|||
struct dma_s *dma = (struct dma_s *) ch->dma; |
|||
|
|||
qemu_mod_timer(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, |
|||
target_phys_addr_t 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 |= 1 << ch->num; |
|||
else |
|||
dma->ch_enable_mask &= ~(1 << ch->num); |
|||
|
|||
if (level != ch->enable) { |
|||
soc_dma_ch_freq_update(dma); |
|||
ch->enable = level; |
|||
|
|||
if (!ch->enable) |
|||
qemu_del_timer(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 = qemu_mallocz(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 = qemu_new_timer(vm_clock, soc_dma_ch_run, &s->ch[i]); |
|||
} |
|||
|
|||
soc_dma_reset(&s->soc); |
|||
|
|||
return &s->soc; |
|||
} |
|||
|
|||
void soc_dma_port_add_fifo(struct soc_dma_s *soc, target_phys_addr_t 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 = 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) { |
|||
fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx |
|||
" collides with RAM region at " TARGET_FMT_lx |
|||
"-" TARGET_FMT_lx "\n", __FUNCTION__, |
|||
(target_ulong) virt_base, |
|||
(target_ulong) entry->addr, (target_ulong) |
|||
(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) { |
|||
fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx |
|||
" collides FIFO at " TARGET_FMT_lx "\n", |
|||
__FUNCTION__, (target_ulong) virt_base, |
|||
(target_ulong) 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, |
|||
target_phys_addr_t virt_base, size_t size) |
|||
{ |
|||
struct memmap_entry_s *entry; |
|||
struct dma_s *dma = (struct dma_s *) soc; |
|||
|
|||
dma->memmap = 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)) { |
|||
fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx |
|||
" collides with RAM region at " TARGET_FMT_lx |
|||
"-" TARGET_FMT_lx "\n", __FUNCTION__, |
|||
(target_ulong) virt_base, |
|||
(target_ulong) (virt_base + size), |
|||
(target_ulong) entry->addr, (target_ulong) |
|||
(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) { |
|||
fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx |
|||
" collides with FIFO at " TARGET_FMT_lx |
|||
"\n", __FUNCTION__, |
|||
(target_ulong) virt_base, |
|||
(target_ulong) (virt_base + size), |
|||
(target_ulong) 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 */ |
|||
@ -0,0 +1,115 @@ |
|||
/*
|
|||
* 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, write to the Free Software |
|||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
|||
* MA 02111-1307 USA |
|||
*/ |
|||
|
|||
struct soc_dma_s; |
|||
struct soc_dma_ch_s; |
|||
typedef void (*soc_dma_io_t)(void *opaque, uint8_t *buf, int len); |
|||
typedef void (*soc_dma_transfer_t)(struct soc_dma_ch_s *ch); |
|||
|
|||
enum soc_dma_port_type { |
|||
soc_dma_port_mem, |
|||
soc_dma_port_fifo, |
|||
soc_dma_port_other, |
|||
}; |
|||
|
|||
enum soc_dma_access_type { |
|||
soc_dma_access_const, |
|||
soc_dma_access_linear, |
|||
soc_dma_access_other, |
|||
}; |
|||
|
|||
struct soc_dma_ch_s { |
|||
/* Private */ |
|||
struct soc_dma_s *dma; |
|||
int num; |
|||
QEMUTimer *timer; |
|||
|
|||
/* Set by soc_dma.c */ |
|||
int enable; |
|||
int update; |
|||
|
|||
/* This should be set by dma->setup_fn(). */ |
|||
int bytes; |
|||
/* Initialised by the DMA module, call soc_dma_ch_update after writing. */ |
|||
enum soc_dma_access_type type[2]; |
|||
target_phys_addr_t vaddr[2]; /* Updated by .transfer_fn(). */ |
|||
/* Private */ |
|||
void *paddr[2]; |
|||
soc_dma_io_t io_fn[2]; |
|||
void *io_opaque[2]; |
|||
|
|||
int running; |
|||
soc_dma_transfer_t transfer_fn; |
|||
|
|||
/* Set and used by the DMA module. */ |
|||
void *opaque; |
|||
}; |
|||
|
|||
struct soc_dma_s { |
|||
/* Following fields are set by the SoC DMA module and can be used
|
|||
* by anybody. */ |
|||
uint64_t drqbmp; /* Is zeroed by soc_dma_reset() */ |
|||
qemu_irq *drq; |
|||
void *opaque; |
|||
int64_t freq; |
|||
soc_dma_transfer_t transfer_fn; |
|||
soc_dma_transfer_t setup_fn; |
|||
/* Set by soc_dma_init() for use by the DMA module. */ |
|||
struct soc_dma_ch_s *ch; |
|||
}; |
|||
|
|||
/* Call to activate or stop a DMA channel. */ |
|||
void soc_dma_set_request(struct soc_dma_ch_s *ch, int level); |
|||
/* Call after every write to one of the following fields and before
|
|||
* calling soc_dma_set_request(ch, 1): |
|||
* ch->type[0...1], |
|||
* ch->vaddr[0...1], |
|||
* ch->paddr[0...1], |
|||
* or after a soc_dma_port_add_fifo() or soc_dma_port_add_mem(). */ |
|||
void soc_dma_ch_update(struct soc_dma_ch_s *ch); |
|||
|
|||
/* The SoC should call this when the DMA module is being reset. */ |
|||
void soc_dma_reset(struct soc_dma_s *s); |
|||
struct soc_dma_s *soc_dma_init(int n); |
|||
|
|||
void soc_dma_port_add_fifo(struct soc_dma_s *dma, target_phys_addr_t virt_base, |
|||
soc_dma_io_t fn, void *opaque, int out); |
|||
void soc_dma_port_add_mem(struct soc_dma_s *dma, uint8_t *phys_base, |
|||
target_phys_addr_t virt_base, size_t size); |
|||
|
|||
static inline void soc_dma_port_add_fifo_in(struct soc_dma_s *dma, |
|||
target_phys_addr_t virt_base, soc_dma_io_t fn, void *opaque) |
|||
{ |
|||
return soc_dma_port_add_fifo(dma, virt_base, fn, opaque, 0); |
|||
} |
|||
|
|||
static inline void soc_dma_port_add_fifo_out(struct soc_dma_s *dma, |
|||
target_phys_addr_t virt_base, soc_dma_io_t fn, void *opaque) |
|||
{ |
|||
return soc_dma_port_add_fifo(dma, virt_base, fn, opaque, 1); |
|||
} |
|||
|
|||
static inline void soc_dma_port_add_mem_ram(struct soc_dma_s *dma, |
|||
ram_addr_t offset, target_phys_addr_t virt_base, size_t size) |
|||
{ |
|||
return soc_dma_port_add_mem(dma, phys_ram_base + offset, virt_base, size); |
|||
} |
|||
Loading…
Reference in new issue