xref: /openbmc/qemu/hw/dma/soc_dma.c (revision 84a3a53c)
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 "hw/arm/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_clock_get_ns(QEMU_CLOCK_VIRTUAL);
88     struct dma_s *dma = (struct dma_s *) ch->dma;
89 
90     timer_mod(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             timer_del(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 = timer_new_ns(QEMU_CLOCK_VIRTUAL, 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 %"PRIx64
273                                 " collides with RAM region at %"PRIx64
274                                 "-%"PRIx64 "\n", __func__,
275                                 virt_base, entry->addr,
276                                 (entry->addr + entry->u.mem.size));
277                 exit(-1);
278             }
279 
280             if (entry->addr <= virt_base)
281                 entry ++;
282         } else
283             while (entry < dma->memmap + dma->memmap_size &&
284                             entry->addr <= virt_base) {
285                 if (entry->addr == virt_base && entry->u.fifo.out == out) {
286                     fprintf(stderr, "%s: FIFO at %"PRIx64
287                                     " collides FIFO at %"PRIx64 "\n",
288                                     __func__, virt_base, entry->addr);
289                     exit(-1);
290                 }
291 
292                 entry ++;
293             }
294 
295         memmove(entry + 1, entry,
296                         (uint8_t *) (dma->memmap + dma->memmap_size ++) -
297                         (uint8_t *) entry);
298     } else
299         dma->memmap_size ++;
300 
301     entry->addr          = virt_base;
302     entry->type          = soc_dma_port_fifo;
303     entry->u.fifo.fn     = fn;
304     entry->u.fifo.opaque = opaque;
305     entry->u.fifo.out    = out;
306 }
307 
308 void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
309                 hwaddr virt_base, size_t size)
310 {
311     struct memmap_entry_s *entry;
312     struct dma_s *dma = (struct dma_s *) soc;
313 
314     dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
315                     (dma->memmap_size + 1));
316     entry = soc_dma_lookup(dma, virt_base);
317 
318     if (dma->memmap_size) {
319         if (entry->type == soc_dma_port_mem) {
320             if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
321                             (entry->addr <= virt_base &&
322                              entry->addr + entry->u.mem.size > virt_base)) {
323                 fprintf(stderr, "%s: RAM at %"PRIx64 "-%"PRIx64
324                                 " collides with RAM region at %"PRIx64
325                                 "-%"PRIx64 "\n", __func__,
326                                 virt_base, virt_base + size,
327                                 entry->addr, entry->addr + entry->u.mem.size);
328                 exit(-1);
329             }
330 
331             if (entry->addr <= virt_base)
332                 entry ++;
333         } else {
334             if (entry->addr >= virt_base &&
335                             entry->addr < virt_base + size) {
336                 fprintf(stderr, "%s: RAM at %"PRIx64 "-%"PRIx64
337                                 " collides with FIFO at %"PRIx64
338                                 "\n", __func__,
339                                 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