Statistics
| Branch: | Revision:

root / hw / soc_dma.c @ 1de7afc9

History | View | Annotate | Download (11.6 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 along
18
 * with this program; if not, see <http://www.gnu.org/licenses/>.
19
 */
20
#include "qemu-common.h"
21
#include "qemu/timer.h"
22
#include "soc_dma.h"
23

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

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

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

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

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

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

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

    
82
    struct soc_dma_ch_s ch[0];
83
};
84

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

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

    
93
static void soc_dma_ch_run(void *opaque)
94
{
95
    struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
96

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

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

    
107
static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
108
                hwaddr addr)
109
{
110
    struct memmap_entry_s *lo;
111
    int hi;
112

    
113
    lo = dma->memmap;
114
    hi = dma->memmap_size;
115

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

    
122
    return lo;
123
}
124

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

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

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

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

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

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

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

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

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

    
187
    ch->update = (dst != soc_dma_port_other);
188
}
189

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

    
204
void soc_dma_set_request(struct soc_dma_ch_s *ch, int level)
205
{
206
    struct dma_s *dma = (struct dma_s *) ch->dma;
207

    
208
    dma->enabled_count += level - ch->enable;
209

    
210
    if (level)
211
        dma->ch_enable_mask |= 1 << ch->num;
212
    else
213
        dma->ch_enable_mask &= ~(1 << ch->num);
214

    
215
    if (level != ch->enable) {
216
        soc_dma_ch_freq_update(dma);
217
        ch->enable = level;
218

    
219
        if (!ch->enable)
220
            qemu_del_timer(ch->timer);
221
        else if (!ch->running)
222
            soc_dma_ch_run(ch);
223
        else
224
            soc_dma_ch_schedule(ch, 1);
225
    }
226
}
227

    
228
void soc_dma_reset(struct soc_dma_s *soc)
229
{
230
    struct dma_s *s = (struct dma_s *) soc;
231

    
232
    s->soc.drqbmp = 0;
233
    s->ch_enable_mask = 0;
234
    s->enabled_count = 0;
235
    soc_dma_ch_freq_update(s);
236
}
237

    
238
/* TODO: take a functional-clock argument */
239
struct soc_dma_s *soc_dma_init(int n)
240
{
241
    int i;
242
    struct dma_s *s = g_malloc0(sizeof(*s) + n * sizeof(*s->ch));
243

    
244
    s->chnum = n;
245
    s->soc.ch = s->ch;
246
    for (i = 0; i < n; i ++) {
247
        s->ch[i].dma = &s->soc;
248
        s->ch[i].num = i;
249
        s->ch[i].timer = qemu_new_timer_ns(vm_clock, soc_dma_ch_run, &s->ch[i]);
250
    }
251

    
252
    soc_dma_reset(&s->soc);
253
    fifo_size = 0;
254

    
255
    return &s->soc;
256
}
257

    
258
void soc_dma_port_add_fifo(struct soc_dma_s *soc, hwaddr virt_base,
259
                soc_dma_io_t fn, void *opaque, int out)
260
{
261
    struct memmap_entry_s *entry;
262
    struct dma_s *dma = (struct dma_s *) soc;
263

    
264
    dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
265
                    (dma->memmap_size + 1));
266
    entry = soc_dma_lookup(dma, virt_base);
267

    
268
    if (dma->memmap_size) {
269
        if (entry->type == soc_dma_port_mem) {
270
            if (entry->addr <= virt_base &&
271
                            entry->addr + entry->u.mem.size > virt_base) {
272
                fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
273
                                " collides with RAM region at " TARGET_FMT_lx
274
                                "-" TARGET_FMT_lx "\n", __FUNCTION__,
275
                                (target_ulong) virt_base,
276
                                (target_ulong) entry->addr, (target_ulong)
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 " TARGET_FMT_lx
288
                                    " collides FIFO at " TARGET_FMT_lx "\n",
289
                                    __FUNCTION__, (target_ulong) virt_base,
290
                                    (target_ulong) entry->addr);
291
                    exit(-1);
292
                }
293

    
294
                entry ++;
295
            }
296

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

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

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

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

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

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

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

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

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

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