Merge commit 'origin/upstream' into juha-devel
[qemu] / hw / soc_dma.c
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, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 #include "qemu-common.h"
22 #include "qemu-timer.h"
23 #include "soc_dma.h"
24 #include "hw.h"
25
26 static 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 static 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 static 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 static void transfer_fifo2fifo(struct soc_dma_ch_s *ch)
51 {
52     if (ch->bytes > fifo_size)
53         fifo_buf = qemu_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     memset(s->soc.drqst, 0, sizeof(s->soc.drqst));
234     s->ch_enable_mask = 0;
235     s->enabled_count = 0;
236     soc_dma_ch_freq_update(s);
237 }
238
239 static void soc_dma_save_state(QEMUFile *f, void *opaque)
240 {
241     struct dma_s *s = (struct dma_s *)opaque;
242     int i;
243     
244     qemu_put_buffer(f, s->soc.drqst, sizeof(s->soc.drqst));
245     qemu_put_sbe64(f, s->soc.freq);
246     qemu_put_be64(f, s->ch_enable_mask);
247     qemu_put_sbe64(f, s->channel_freq);
248     qemu_put_sbe32(f, s->enabled_count);
249     for (i = 0; i < s->chnum; i++) {
250         qemu_put_timer(f, s->ch[i].timer);
251         qemu_put_sbe32(f, s->ch[i].enable);
252         qemu_put_sbe32(f, s->ch[i].update);
253         qemu_put_sbe32(f, s->ch[i].bytes);
254         qemu_put_sbe32(f, s->ch[i].type[0]);
255         qemu_put_sbe32(f, s->ch[i].type[1]);
256 #if TARGET_PHYS_ADDR_BITS == 32
257         qemu_put_be32(f, s->ch[i].vaddr[0]);
258         qemu_put_be32(f, s->ch[i].vaddr[1]);
259 #elif TARGET_PHYS_ADDR_BITS == 64
260         qemu_put_be64(f, s->ch[i].vaddr[0]);
261         qemu_put_be64(f, s->ch[i].vaddr[1]);
262 #else
263 #error TARGET_PHYS_ADDR_BITS undefined
264 #endif
265         qemu_put_sbe32(f, s->ch[i].running);
266     }
267 }
268
269 static int soc_dma_load_state(QEMUFile *f, void *opaque, int version_id)
270 {
271     struct dma_s *s = (struct dma_s *)opaque;
272     int i;
273     
274     if (version_id)
275         return -EINVAL;
276     
277     qemu_get_buffer(f, s->soc.drqst, sizeof(s->soc.drqst));
278     s->soc.freq = qemu_get_sbe64(f);
279     s->ch_enable_mask = qemu_get_be64(f);
280     s->channel_freq = qemu_get_sbe64(f);
281     s->enabled_count = qemu_get_sbe32(f);
282     for (i = 0; i < s->chnum; i++) {
283         qemu_get_timer(f, s->ch[i].timer);
284         s->ch[i].enable = qemu_get_sbe32(f);
285         s->ch[i].update = qemu_get_sbe32(f);
286         s->ch[i].bytes = qemu_get_sbe32(f);
287         s->ch[i].type[0] = qemu_get_sbe32(f);
288         s->ch[i].type[1] = qemu_get_sbe32(f);
289 #if TARGET_PHYS_ADDR_BITS == 32
290         s->ch[i].vaddr[0] = qemu_get_be32(f);
291         s->ch[i].vaddr[1] = qemu_get_be32(f);
292 #elif TARGET_PHYS_ADDR_BITS == 64
293         s->ch[i].vaddr[0] = qemu_get_be64(f);
294         s->ch[i].vaddr[1] = qemu_get_be64(f);
295 #else
296 #error TARGET_PHYS_ADDR_BITS undefined
297 #endif
298         s->ch[i].running = qemu_get_sbe32(f);
299         
300         soc_dma_ch_update(&s->ch[i]);
301     }
302     
303     return 0;
304 }
305
306 /* TODO: take a functional-clock argument */
307 struct soc_dma_s *soc_dma_init(int n)
308 {
309     int i;
310     struct dma_s *s = qemu_mallocz(sizeof(*s) + n * sizeof(*s->ch));
311
312     s->chnum = n;
313     s->soc.ch = s->ch;
314     for (i = 0; i < n; i ++) {
315         s->ch[i].dma = &s->soc;
316         s->ch[i].num = i;
317         s->ch[i].timer = qemu_new_timer(vm_clock, soc_dma_ch_run, &s->ch[i]);
318     }
319
320     soc_dma_reset(&s->soc);
321     fifo_size = 0;
322
323     register_savevm("soc_dma", -1, 0,
324                     soc_dma_save_state, soc_dma_load_state, s);
325     return &s->soc;
326 }
327
328 void soc_dma_port_add_fifo(struct soc_dma_s *soc, target_phys_addr_t virt_base,
329                 soc_dma_io_t fn, void *opaque, int out)
330 {
331     struct memmap_entry_s *entry;
332     struct dma_s *dma = (struct dma_s *) soc;
333
334     dma->memmap = qemu_realloc(dma->memmap, sizeof(*entry) *
335                     (dma->memmap_size + 1));
336     entry = soc_dma_lookup(dma, virt_base);
337
338     if (dma->memmap_size) {
339         if (entry->type == soc_dma_port_mem) {
340             if (entry->addr <= virt_base &&
341                             entry->addr + entry->u.mem.size > virt_base) {
342                 fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
343                                 " collides with RAM region at " TARGET_FMT_lx
344                                 "-" TARGET_FMT_lx "\n", __FUNCTION__,
345                                 (target_ulong) virt_base,
346                                 (target_ulong) entry->addr, (target_ulong)
347                                 (entry->addr + entry->u.mem.size));
348                 exit(-1);
349             }
350
351             if (entry->addr <= virt_base)
352                 entry ++;
353         } else
354             while (entry < dma->memmap + dma->memmap_size &&
355                             entry->addr <= virt_base) {
356                 if (entry->addr == virt_base && entry->u.fifo.out == out) {
357                     fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
358                                     " collides FIFO at " TARGET_FMT_lx "\n",
359                                     __FUNCTION__, (target_ulong) virt_base,
360                                     (target_ulong) entry->addr);
361                     exit(-1);
362                 }
363
364                 entry ++;
365             }
366
367         memmove(entry + 1, entry,
368                         (uint8_t *) (dma->memmap + dma->memmap_size ++) -
369                         (uint8_t *) entry);
370     } else
371         dma->memmap_size ++;
372
373     entry->addr          = virt_base;
374     entry->type          = soc_dma_port_fifo;
375     entry->u.fifo.fn     = fn;
376     entry->u.fifo.opaque = opaque;
377     entry->u.fifo.out    = out;
378 }
379
380 void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
381                 target_phys_addr_t virt_base, size_t size)
382 {
383     struct memmap_entry_s *entry;
384     struct dma_s *dma = (struct dma_s *) soc;
385
386     dma->memmap = qemu_realloc(dma->memmap, sizeof(*entry) *
387                     (dma->memmap_size + 1));
388     entry = soc_dma_lookup(dma, virt_base);
389
390     if (dma->memmap_size) {
391         if (entry->type == soc_dma_port_mem) {
392             if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
393                             (entry->addr <= virt_base &&
394                              entry->addr + entry->u.mem.size > virt_base)) {
395                 fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
396                                 " collides with RAM region at " TARGET_FMT_lx
397                                 "-" TARGET_FMT_lx "\n", __FUNCTION__,
398                                 (target_ulong) virt_base,
399                                 (target_ulong) (virt_base + size),
400                                 (target_ulong) entry->addr, (target_ulong)
401                                 (entry->addr + entry->u.mem.size));
402                 exit(-1);
403             }
404
405             if (entry->addr <= virt_base)
406                 entry ++;
407         } else {
408             if (entry->addr >= virt_base &&
409                             entry->addr < virt_base + size) {
410                 fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
411                                 " collides with FIFO at " TARGET_FMT_lx
412                                 "\n", __FUNCTION__,
413                                 (target_ulong) virt_base,
414                                 (target_ulong) (virt_base + size),
415                                 (target_ulong) entry->addr);
416                 exit(-1);
417             }
418
419             while (entry < dma->memmap + dma->memmap_size &&
420                             entry->addr <= virt_base)
421                 entry ++;
422         }
423
424         memmove(entry + 1, entry,
425                         (uint8_t *) (dma->memmap + dma->memmap_size ++) -
426                         (uint8_t *) entry);
427     } else
428         dma->memmap_size ++;
429
430     entry->addr          = virt_base;
431     entry->type          = soc_dma_port_mem;
432     entry->u.mem.base    = phys_base;
433     entry->u.mem.size    = size;
434 }
435
436 /* TODO: port removal for ports like PCMCIA memory */