Statistics
| Branch: | Revision:

root / hw / soc_dma.c @ d4066479

History | View | Annotate | Download (11.7 kB)

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
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20
 * MA 02111-1307 USA
21
 */
22
#include "qemu-common.h"
23
#include "qemu-timer.h"
24
#include "soc_dma.h"
25

    
26
void transfer_mem2mem(struct soc_dma_ch_s *ch)
27
{
28
    memcpy(ch->paddr[0], ch->paddr[1], ch->bytes);
29
    ch->paddr[0] += ch->bytes;
30
    ch->paddr[1] += ch->bytes;
31
}
32

    
33
void transfer_mem2fifo(struct soc_dma_ch_s *ch)
34
{
35
    ch->io_fn[1](ch->io_opaque[1], ch->paddr[0], ch->bytes);
36
    ch->paddr[0] += ch->bytes;
37
}
38

    
39
void transfer_fifo2mem(struct soc_dma_ch_s *ch)
40
{
41
    ch->io_fn[0](ch->io_opaque[0], ch->paddr[1], ch->bytes);
42
    ch->paddr[1] += ch->bytes;
43
}
44

    
45
/* This is further optimisable but isn't very important because often
46
 * DMA peripherals forbid this kind of transfers and even when they don't,
47
 * oprating systems may not need to use them.  */
48
static void *fifo_buf;
49
static int fifo_size;
50
void transfer_fifo2fifo(struct soc_dma_ch_s *ch)
51
{
52
    if (ch->bytes > fifo_size)
53
        fifo_buf = realloc(fifo_buf, fifo_size = ch->bytes);
54

    
55
    /* Implement as transfer_fifo2linear + transfer_linear2fifo.  */
56
    ch->io_fn[0](ch->io_opaque[0], fifo_buf, ch->bytes);
57
    ch->io_fn[1](ch->io_opaque[1], fifo_buf, ch->bytes);
58
}
59

    
60
struct dma_s {
61
    struct soc_dma_s soc;
62
    int chnum;
63
    uint64_t ch_enable_mask;
64
    int64_t channel_freq;
65
    int enabled_count;
66

    
67
    struct memmap_entry_s {
68
        enum soc_dma_port_type type;
69
        target_phys_addr_t addr;
70
        union {
71
           struct {
72
               void *opaque;
73
               soc_dma_io_t fn;
74
               int out;
75
           } fifo;
76
           struct {
77
               void *base;
78
               size_t size;
79
           } mem;
80
        } u;
81
    } *memmap;
82
    int memmap_size;
83

    
84
    struct soc_dma_ch_s ch[0];
85
};
86

    
87
static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, int delay_bytes)
88
{
89
    int64_t now = qemu_get_clock(vm_clock);
90
    struct dma_s *dma = (struct dma_s *) ch->dma;
91

    
92
    qemu_mod_timer(ch->timer, now + delay_bytes / dma->channel_freq);
93
}
94

    
95
static void soc_dma_ch_run(void *opaque)
96
{
97
    struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
98

    
99
    ch->running = 1;
100
    ch->dma->setup_fn(ch);
101
    ch->transfer_fn(ch);
102
    ch->running = 0;
103

    
104
    if (ch->enable)
105
        soc_dma_ch_schedule(ch, ch->bytes);
106
    ch->bytes = 0;
107
}
108

    
109
static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
110
                target_phys_addr_t addr)
111
{
112
    struct memmap_entry_s *lo;
113
    int hi;
114

    
115
    lo = dma->memmap;
116
    hi = dma->memmap_size;
117

    
118
    while (hi > 1) {
119
        hi /= 2;
120
        if (lo[hi].addr <= addr)
121
            lo += hi;
122
    }
123

    
124
    return lo;
125
}
126

    
127
static inline enum soc_dma_port_type soc_dma_ch_update_type(
128
                struct soc_dma_ch_s *ch, int port)
129
{
130
    struct dma_s *dma = (struct dma_s *) ch->dma;
131
    struct memmap_entry_s *entry = soc_dma_lookup(dma, ch->vaddr[port]);
132

    
133
    if (entry->type == soc_dma_port_fifo) {
134
        while (entry < dma->memmap + dma->memmap_size &&
135
                        entry->u.fifo.out != port)
136
            entry ++;
137
        if (entry->addr != ch->vaddr[port] || entry->u.fifo.out != port)
138
            return soc_dma_port_other;
139

    
140
        if (ch->type[port] != soc_dma_access_const)
141
            return soc_dma_port_other;
142

    
143
        ch->io_fn[port] = entry->u.fifo.fn;
144
        ch->io_opaque[port] = entry->u.fifo.opaque;
145
        return soc_dma_port_fifo;
146
    } else if (entry->type == soc_dma_port_mem) {
147
        if (entry->addr > ch->vaddr[port] ||
148
                        entry->addr + entry->u.mem.size <= ch->vaddr[port])
149
            return soc_dma_port_other;
150

    
151
        /* TODO: support constant memory address for source port as used for
152
         * drawing solid rectangles by PalmOS(R).  */
153
        if (ch->type[port] != soc_dma_access_const)
154
            return soc_dma_port_other;
155

    
156
        ch->paddr[port] = (uint8_t *) entry->u.mem.base +
157
                (ch->vaddr[port] - entry->addr);
158
        /* TODO: save bytes left to the end of the mapping somewhere so we
159
         * can check we're not reading beyond it.  */
160
        return soc_dma_port_mem;
161
    } else
162
        return soc_dma_port_other;
163
}
164

    
165
void soc_dma_ch_update(struct soc_dma_ch_s *ch)
166
{
167
    enum soc_dma_port_type src, dst;
168

    
169
    src = soc_dma_ch_update_type(ch, 0);
170
    if (src == soc_dma_port_other) {
171
        ch->update = 0;
172
        ch->transfer_fn = ch->dma->transfer_fn;
173
        return;
174
    }
175
    dst = soc_dma_ch_update_type(ch, 1);
176

    
177
    /* TODO: use src and dst as array indices.  */
178
    if (src == soc_dma_port_mem && dst == soc_dma_port_mem)
179
        ch->transfer_fn = transfer_mem2mem;
180
    else if (src == soc_dma_port_mem && dst == soc_dma_port_fifo)
181
        ch->transfer_fn = transfer_mem2fifo;
182
    else if (src == soc_dma_port_fifo && dst == soc_dma_port_mem)
183
        ch->transfer_fn = transfer_fifo2mem;
184
    else if (src == soc_dma_port_fifo && dst == soc_dma_port_fifo)
185
        ch->transfer_fn = transfer_fifo2fifo;
186
    else
187
        ch->transfer_fn = ch->dma->transfer_fn;
188

    
189
    ch->update = (dst != soc_dma_port_other);
190
}
191

    
192
static void soc_dma_ch_freq_update(struct dma_s *s)
193
{
194
    if (s->enabled_count)
195
        /* We completely ignore channel priorities and stuff */
196
        s->channel_freq = s->soc.freq / s->enabled_count;
197
    else
198
        /* TODO: Signal that we want to disable the functional clock and let
199
         * the platform code decide what to do with it, i.e. check that
200
         * auto-idle is enabled in the clock controller and if we are stopping
201
         * the clock, do the same with any parent clocks that had only one
202
         * user keeping them on and auto-idle enabled.  */;
203
}
204

    
205
void 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
            qemu_del_timer(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

    
229
void 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 */
240
struct soc_dma_s *soc_dma_init(int n)
241
{
242
    int i;
243
    struct dma_s *s = qemu_mallocz(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 = qemu_new_timer(vm_clock, 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

    
259
void soc_dma_port_add_fifo(struct soc_dma_s *soc, target_phys_addr_t 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 = 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 " TARGET_FMT_lx
274
                                " collides with RAM region at " TARGET_FMT_lx
275
                                "-" TARGET_FMT_lx "\n", __FUNCTION__,
276
                                (target_ulong) virt_base,
277
                                (target_ulong) entry->addr, (target_ulong)
278
                                (entry->addr + entry->u.mem.size));
279
                exit(-1);
280
            }
281

    
282
            if (entry->addr <= virt_base)
283
                entry ++;
284
        } else
285
            while (entry < dma->memmap + dma->memmap_size &&
286
                            entry->addr <= virt_base) {
287
                if (entry->addr == virt_base && entry->u.fifo.out == out) {
288
                    fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
289
                                    " collides FIFO at " TARGET_FMT_lx "\n",
290
                                    __FUNCTION__, (target_ulong) virt_base,
291
                                    (target_ulong) entry->addr);
292
                    exit(-1);
293
                }
294

    
295
                entry ++;
296
            }
297

    
298
        memmove(entry + 1, entry,
299
                        (uint8_t *) (dma->memmap + dma->memmap_size ++) -
300
                        (uint8_t *) entry);
301
    } else
302
        dma->memmap_size ++;
303

    
304
    entry->addr          = virt_base;
305
    entry->type          = soc_dma_port_fifo;
306
    entry->u.fifo.fn     = fn;
307
    entry->u.fifo.opaque = opaque;
308
    entry->u.fifo.out    = out;
309
}
310

    
311
void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
312
                target_phys_addr_t virt_base, size_t size)
313
{
314
    struct memmap_entry_s *entry;
315
    struct dma_s *dma = (struct dma_s *) soc;
316

    
317
    dma->memmap = realloc(dma->memmap, sizeof(*entry) *
318
                    (dma->memmap_size + 1));
319
    entry = soc_dma_lookup(dma, virt_base);
320

    
321
    if (dma->memmap_size) {
322
        if (entry->type == soc_dma_port_mem) {
323
            if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
324
                            (entry->addr <= virt_base &&
325
                             entry->addr + entry->u.mem.size > virt_base)) {
326
                fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
327
                                " collides with RAM region at " TARGET_FMT_lx
328
                                "-" TARGET_FMT_lx "\n", __FUNCTION__,
329
                                (target_ulong) virt_base,
330
                                (target_ulong) (virt_base + size),
331
                                (target_ulong) entry->addr, (target_ulong)
332
                                (entry->addr + entry->u.mem.size));
333
                exit(-1);
334
            }
335

    
336
            if (entry->addr <= virt_base)
337
                entry ++;
338
        } else {
339
            if (entry->addr >= virt_base &&
340
                            entry->addr < virt_base + size) {
341
                fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
342
                                " collides with FIFO at " TARGET_FMT_lx
343
                                "\n", __FUNCTION__,
344
                                (target_ulong) virt_base,
345
                                (target_ulong) (virt_base + size),
346
                                (target_ulong) entry->addr);
347
                exit(-1);
348
            }
349

    
350
            while (entry < dma->memmap + dma->memmap_size &&
351
                            entry->addr <= virt_base)
352
                entry ++;
353
        }
354

    
355
        memmove(entry + 1, entry,
356
                        (uint8_t *) (dma->memmap + dma->memmap_size ++) -
357
                        (uint8_t *) entry);
358
    } else
359
        dma->memmap_size ++;
360

    
361
    entry->addr          = virt_base;
362
    entry->type          = soc_dma_port_mem;
363
    entry->u.mem.base    = phys_base;
364
    entry->u.mem.size    = size;
365
}
366

    
367
/* TODO: port removal for ports like PCMCIA memory */