xref: /openbmc/qemu/hw/dma/soc_dma.c (revision c3a68dfd)
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/osdep.h"
21 #include "qemu/error-report.h"
22 #include "qemu/timer.h"
23 #include "hw/arm/soc_dma.h"
24 
transfer_mem2mem(struct soc_dma_ch_s * ch)25 static void transfer_mem2mem(struct soc_dma_ch_s *ch)
26 {
27     memcpy(ch->paddr[0], ch->paddr[1], ch->bytes);
28     ch->paddr[0] += ch->bytes;
29     ch->paddr[1] += ch->bytes;
30 }
31 
transfer_mem2fifo(struct soc_dma_ch_s * ch)32 static void transfer_mem2fifo(struct soc_dma_ch_s *ch)
33 {
34     ch->io_fn[1](ch->io_opaque[1], ch->paddr[0], ch->bytes);
35     ch->paddr[0] += ch->bytes;
36 }
37 
transfer_fifo2mem(struct soc_dma_ch_s * ch)38 static void transfer_fifo2mem(struct soc_dma_ch_s *ch)
39 {
40     ch->io_fn[0](ch->io_opaque[0], ch->paddr[1], ch->bytes);
41     ch->paddr[1] += ch->bytes;
42 }
43 
44 /* This is further optimisable but isn't very important because often
45  * DMA peripherals forbid this kind of transfers and even when they don't,
46  * oprating systems may not need to use them.  */
47 static void *fifo_buf;
48 static int fifo_size;
transfer_fifo2fifo(struct soc_dma_ch_s * ch)49 static void transfer_fifo2fifo(struct soc_dma_ch_s *ch)
50 {
51     if (ch->bytes > fifo_size)
52         fifo_buf = g_realloc(fifo_buf, fifo_size = ch->bytes);
53 
54     /* Implement as transfer_fifo2linear + transfer_linear2fifo.  */
55     ch->io_fn[0](ch->io_opaque[0], fifo_buf, ch->bytes);
56     ch->io_fn[1](ch->io_opaque[1], fifo_buf, ch->bytes);
57 }
58 
59 struct dma_s {
60     struct soc_dma_s soc;
61     int chnum;
62     uint64_t ch_enable_mask;
63     int64_t channel_freq;
64     int enabled_count;
65 
66     struct memmap_entry_s {
67         enum soc_dma_port_type type;
68         hwaddr addr;
69         union {
70            struct {
71                void *opaque;
72                soc_dma_io_t fn;
73                int out;
74            } fifo;
75            struct {
76                void *base;
77                size_t size;
78            } mem;
79         } u;
80     } *memmap;
81     int memmap_size;
82 
83     struct soc_dma_ch_s ch[];
84 };
85 
soc_dma_ch_schedule(struct soc_dma_ch_s * ch,int delay_bytes)86 static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, int delay_bytes)
87 {
88     int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
89     struct dma_s *dma = (struct dma_s *) ch->dma;
90 
91     timer_mod(ch->timer, now + delay_bytes / dma->channel_freq);
92 }
93 
soc_dma_ch_run(void * opaque)94 static void soc_dma_ch_run(void *opaque)
95 {
96     struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
97 
98     ch->running = 1;
99     ch->dma->setup_fn(ch);
100     ch->transfer_fn(ch);
101     ch->running = 0;
102 
103     if (ch->enable)
104         soc_dma_ch_schedule(ch, ch->bytes);
105     ch->bytes = 0;
106 }
107 
soc_dma_lookup(struct dma_s * dma,hwaddr addr)108 static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
109                 hwaddr addr)
110 {
111     struct memmap_entry_s *lo;
112     int hi;
113 
114     lo = dma->memmap;
115     hi = dma->memmap_size;
116 
117     while (hi > 1) {
118         hi /= 2;
119         if (lo[hi].addr <= addr)
120             lo += hi;
121     }
122 
123     return lo;
124 }
125 
soc_dma_ch_update_type(struct soc_dma_ch_s * ch,int port)126 static inline enum soc_dma_port_type soc_dma_ch_update_type(
127                 struct soc_dma_ch_s *ch, int port)
128 {
129     struct dma_s *dma = (struct dma_s *) ch->dma;
130     struct memmap_entry_s *entry = soc_dma_lookup(dma, ch->vaddr[port]);
131 
132     if (entry->type == soc_dma_port_fifo) {
133         while (entry < dma->memmap + dma->memmap_size &&
134                         entry->u.fifo.out != port)
135             entry ++;
136         if (entry->addr != ch->vaddr[port] || entry->u.fifo.out != port)
137             return soc_dma_port_other;
138 
139         if (ch->type[port] != soc_dma_access_const)
140             return soc_dma_port_other;
141 
142         ch->io_fn[port] = entry->u.fifo.fn;
143         ch->io_opaque[port] = entry->u.fifo.opaque;
144         return soc_dma_port_fifo;
145     } else if (entry->type == soc_dma_port_mem) {
146         if (entry->addr > ch->vaddr[port] ||
147                         entry->addr + entry->u.mem.size <= ch->vaddr[port])
148             return soc_dma_port_other;
149 
150         /* TODO: support constant memory address for source port as used for
151          * drawing solid rectangles by PalmOS(R).  */
152         if (ch->type[port] != soc_dma_access_const)
153             return soc_dma_port_other;
154 
155         ch->paddr[port] = (uint8_t *) entry->u.mem.base +
156                 (ch->vaddr[port] - entry->addr);
157         /* TODO: save bytes left to the end of the mapping somewhere so we
158          * can check we're not reading beyond it.  */
159         return soc_dma_port_mem;
160     } else
161         return soc_dma_port_other;
162 }
163 
soc_dma_ch_update(struct soc_dma_ch_s * ch)164 void soc_dma_ch_update(struct soc_dma_ch_s *ch)
165 {
166     enum soc_dma_port_type src, dst;
167 
168     src = soc_dma_ch_update_type(ch, 0);
169     if (src == soc_dma_port_other) {
170         ch->update = 0;
171         ch->transfer_fn = ch->dma->transfer_fn;
172         return;
173     }
174     dst = soc_dma_ch_update_type(ch, 1);
175 
176     /* TODO: use src and dst as array indices.  */
177     if (src == soc_dma_port_mem && dst == soc_dma_port_mem)
178         ch->transfer_fn = transfer_mem2mem;
179     else if (src == soc_dma_port_mem && dst == soc_dma_port_fifo)
180         ch->transfer_fn = transfer_mem2fifo;
181     else if (src == soc_dma_port_fifo && dst == soc_dma_port_mem)
182         ch->transfer_fn = transfer_fifo2mem;
183     else if (src == soc_dma_port_fifo && dst == soc_dma_port_fifo)
184         ch->transfer_fn = transfer_fifo2fifo;
185     else
186         ch->transfer_fn = ch->dma->transfer_fn;
187 
188     ch->update = (dst != soc_dma_port_other);
189 }
190 
soc_dma_ch_freq_update(struct dma_s * s)191 static void soc_dma_ch_freq_update(struct dma_s *s)
192 {
193     if (s->enabled_count)
194         /* We completely ignore channel priorities and stuff */
195         s->channel_freq = s->soc.freq / s->enabled_count;
196     else {
197         /* TODO: Signal that we want to disable the functional clock and let
198          * the platform code decide what to do with it, i.e. check that
199          * auto-idle is enabled in the clock controller and if we are stopping
200          * the clock, do the same with any parent clocks that had only one
201          * user keeping them on and auto-idle enabled.  */
202     }
203 }
204 
soc_dma_set_request(struct soc_dma_ch_s * ch,int level)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 |= (uint64_t)1 << ch->num;
213     else
214         dma->ch_enable_mask &= ~((uint64_t)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             timer_del(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 
soc_dma_reset(struct soc_dma_s * soc)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 */
soc_dma_init(int n)240 struct soc_dma_s *soc_dma_init(int n)
241 {
242     int i;
243     struct dma_s *s = g_malloc0(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 = timer_new_ns(QEMU_CLOCK_VIRTUAL, 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 
soc_dma_port_add_fifo(struct soc_dma_s * soc,hwaddr virt_base,soc_dma_io_t fn,void * opaque,int out)259 void soc_dma_port_add_fifo(struct soc_dma_s *soc, hwaddr 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 = g_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                 error_report("%s: FIFO at %"PRIx64
274                              " collides with RAM region at %"PRIx64
275                              "-%"PRIx64, __func__,
276                              virt_base, entry->addr,
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                     error_report("%s: FIFO at %"PRIx64
288                                  " collides FIFO at %"PRIx64,
289                                  __func__, virt_base, entry->addr);
290                     exit(-1);
291                 }
292 
293                 entry ++;
294             }
295 
296         memmove(entry + 1, entry,
297                         (uint8_t *) (dma->memmap + dma->memmap_size ++) -
298                         (uint8_t *) entry);
299     } else
300         dma->memmap_size ++;
301 
302     entry->addr          = virt_base;
303     entry->type          = soc_dma_port_fifo;
304     entry->u.fifo.fn     = fn;
305     entry->u.fifo.opaque = opaque;
306     entry->u.fifo.out    = out;
307 }
308 
soc_dma_port_add_mem(struct soc_dma_s * soc,uint8_t * phys_base,hwaddr virt_base,size_t size)309 void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
310                 hwaddr virt_base, size_t size)
311 {
312     struct memmap_entry_s *entry;
313     struct dma_s *dma = (struct dma_s *) soc;
314 
315     dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
316                     (dma->memmap_size + 1));
317     entry = soc_dma_lookup(dma, virt_base);
318 
319     if (dma->memmap_size) {
320         if (entry->type == soc_dma_port_mem) {
321             if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
322                             (entry->addr <= virt_base &&
323                              entry->addr + entry->u.mem.size > virt_base)) {
324                 error_report("%s: RAM at %"PRIx64 "-%"PRIx64
325                              " collides with RAM region at %"PRIx64
326                              "-%"PRIx64, __func__,
327                              virt_base, virt_base + size,
328                              entry->addr, entry->addr + entry->u.mem.size);
329                 exit(-1);
330             }
331 
332             if (entry->addr <= virt_base)
333                 entry ++;
334         } else {
335             if (entry->addr >= virt_base &&
336                             entry->addr < virt_base + size) {
337                 error_report("%s: RAM at %"PRIx64 "-%"PRIx64
338                              " collides with FIFO at %"PRIx64,
339                              __func__, virt_base, virt_base + size,
340                              entry->addr);
341                 exit(-1);
342             }
343 
344             while (entry < dma->memmap + dma->memmap_size &&
345                             entry->addr <= virt_base)
346                 entry ++;
347         }
348 
349         memmove(entry + 1, entry,
350                         (uint8_t *) (dma->memmap + dma->memmap_size ++) -
351                         (uint8_t *) entry);
352     } else
353         dma->memmap_size ++;
354 
355     entry->addr          = virt_base;
356     entry->type          = soc_dma_port_mem;
357     entry->u.mem.base    = phys_base;
358     entry->u.mem.size    = size;
359 }
360 
361 /* TODO: port removal for ports like PCMCIA memory */
362