master
c 293 lines 8.82 KB
Raw
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 "qemu/log.h"
24 #include "system/physmem.h"
25 #include "hw/dma/soc_dma.h"
26
27 static void transfer_mem2mem(struct soc_dma_ch_s *ch)
28 {
29 /*
30 * Memory-to-memory transfer: do the whole thing in one go. The
31 * hardware spec says that it is invalid to program the OMAP DMA
32 * controller with addresses that don't match the port (i.e. to
33 * ask for a transfer to/from a memory port with a physaddr that
34 * isn't within that port range) and that if you do then the
35 * transfer continues and memory can be corrupted. So we can map
36 * both source and destination, and treat short mappings and
37 * failed mappings as a guest error.
38 */
39 hwaddr srclen = ch->bytes;
40 hwaddr dstlen = ch->bytes;
41 hwaddr srcaddr = ch->vaddr[0];
42 hwaddr dstaddr = ch->vaddr[1];
43 void *srcmem, *dstmem;
44 hwaddr xferlen = 0;
45
46 srcmem = physical_memory_map(srcaddr, &srclen, false);
47 if (!srcmem) {
48 qemu_log_mask(LOG_GUEST_ERROR,
49 "soc_dma mem2mem transfer: could not map source; "
50 "guest error programming source port/address\n");
51 return;
52 }
53
54 dstmem = physical_memory_map(dstaddr, &dstlen, true);
55 if (!dstmem) {
56 qemu_log_mask(LOG_GUEST_ERROR,
57 "soc_dma mem2mem transfer: could not map destination; "
58 "guest error programming destination port/address\n");
59 goto unmap_src;
60 }
61
62 xferlen = MIN(srclen, dstlen);
63 if (xferlen < ch->bytes) {
64 qemu_log_mask(LOG_GUEST_ERROR,
65 "soc_dma mem2mem transfer: could not transfer all data; "
66 "guest error programming src or destination addresses\n");
67 /* Continue to transfer whatever did fit in the port window */
68 }
69
70 memmove(dstmem, srcmem, xferlen);
71
72 physical_memory_unmap(dstmem, dstlen, true, xferlen);
73 unmap_src:
74 physical_memory_unmap(srcmem, srclen, false, xferlen);
75 }
76
77 struct dma_s {
78 struct soc_dma_s soc;
79 int chnum;
80 uint64_t ch_enable_mask;
81 int64_t channel_freq;
82 int enabled_count;
83
84 struct memmap_entry_s {
85 enum soc_dma_port_type type;
86 hwaddr addr;
87 struct {
88 size_t size;
89 } mem;
90 } *memmap;
91 int memmap_size;
92
93 struct soc_dma_ch_s ch[];
94 };
95
96 static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, uint64_t delay_bytes)
97 {
98 int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
99 struct dma_s *dma = (struct dma_s *) ch->dma;
100
101 /*
102 * Worst case delay bytes is only slightly larger than fits into
103 * a 32-bit integer, so this won't overflow.
104 */
105 timer_mod(ch->timer, now + delay_bytes / dma->channel_freq);
106 }
107
108 static void soc_dma_ch_run(void *opaque)
109 {
110 struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
111
112 ch->running = 1;
113 ch->dma->setup_fn(ch);
114 ch->transfer_fn(ch);
115 ch->running = 0;
116
117 if (ch->enable)
118 soc_dma_ch_schedule(ch, ch->bytes);
119 ch->bytes = 0;
120 }
121
122 static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
123 hwaddr addr)
124 {
125 struct memmap_entry_s *lo;
126 int hi;
127
128 lo = dma->memmap;
129 hi = dma->memmap_size;
130
131 while (hi > 1) {
132 hi /= 2;
133 if (lo[hi].addr <= addr)
134 lo += hi;
135 }
136
137 return lo;
138 }
139
140 static inline enum soc_dma_port_type soc_dma_ch_update_type(
141 struct soc_dma_ch_s *ch, int port)
142 {
143 struct dma_s *dma = (struct dma_s *) ch->dma;
144 struct memmap_entry_s *entry = soc_dma_lookup(dma, ch->vaddr[port]);
145
146 if (entry->type == soc_dma_port_mem) {
147 if (entry->addr > ch->vaddr[port] ||
148 entry->addr + entry->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 return soc_dma_port_mem;
157 } else
158 return soc_dma_port_other;
159 }
160
161 void soc_dma_ch_update(struct soc_dma_ch_s *ch)
162 {
163 enum soc_dma_port_type src, dst;
164
165 src = soc_dma_ch_update_type(ch, 0);
166 dst = soc_dma_ch_update_type(ch, 1);
167 if (src == soc_dma_port_other || dst == soc_dma_port_other) {
168 ch->update = 0;
169 ch->transfer_fn = ch->dma->transfer_fn;
170 } else {
171 ch->update = 1;
172 ch->transfer_fn = transfer_mem2mem;
173 }
174 }
175
176 static void soc_dma_ch_freq_update(struct dma_s *s)
177 {
178 if (s->enabled_count)
179 /* We completely ignore channel priorities and stuff */
180 s->channel_freq = s->soc.freq / s->enabled_count;
181 else {
182 /* TODO: Signal that we want to disable the functional clock and let
183 * the platform code decide what to do with it, i.e. check that
184 * auto-idle is enabled in the clock controller and if we are stopping
185 * the clock, do the same with any parent clocks that had only one
186 * user keeping them on and auto-idle enabled. */
187 }
188 }
189
190 void soc_dma_set_request(struct soc_dma_ch_s *ch, int level)
191 {
192 struct dma_s *dma = (struct dma_s *) ch->dma;
193
194 dma->enabled_count += level - ch->enable;
195
196 if (level)
197 dma->ch_enable_mask |= (uint64_t)1 << ch->num;
198 else
199 dma->ch_enable_mask &= ~((uint64_t)1 << ch->num);
200
201 if (level != ch->enable) {
202 soc_dma_ch_freq_update(dma);
203 ch->enable = level;
204
205 if (!ch->enable)
206 timer_del(ch->timer);
207 else if (!ch->running)
208 soc_dma_ch_run(ch);
209 else
210 soc_dma_ch_schedule(ch, 1);
211 }
212 }
213
214 void soc_dma_reset(struct soc_dma_s *soc)
215 {
216 struct dma_s *s = (struct dma_s *) soc;
217
218 s->soc.drqbmp = 0;
219 s->ch_enable_mask = 0;
220 s->enabled_count = 0;
221 soc_dma_ch_freq_update(s);
222 }
223
224 /* TODO: take a functional-clock argument */
225 struct soc_dma_s *soc_dma_init(int n)
226 {
227 int i;
228 struct dma_s *s = g_malloc0(sizeof(*s) + n * sizeof(*s->ch));
229
230 s->chnum = n;
231 s->soc.ch = s->ch;
232 for (i = 0; i < n; i ++) {
233 s->ch[i].dma = &s->soc;
234 s->ch[i].num = i;
235 s->ch[i].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, soc_dma_ch_run, &s->ch[i]);
236 }
237
238 soc_dma_reset(&s->soc);
239
240 return &s->soc;
241 }
242
243 void soc_dma_port_add_mem(struct soc_dma_s *soc, hwaddr virt_base, size_t size)
244 {
245 struct memmap_entry_s *entry;
246 struct dma_s *dma = (struct dma_s *) soc;
247
248 dma->memmap = g_realloc(dma->memmap, sizeof(*entry) *
249 (dma->memmap_size + 1));
250 entry = soc_dma_lookup(dma, virt_base);
251
252 if (dma->memmap_size) {
253 if (entry->type == soc_dma_port_mem) {
254 if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
255 (entry->addr <= virt_base &&
256 entry->addr + entry->mem.size > virt_base)) {
257 error_report("%s: RAM at %"PRIx64 "-%"PRIx64
258 " collides with RAM region at %"PRIx64
259 "-%"PRIx64, __func__,
260 virt_base, virt_base + size,
261 entry->addr, entry->addr + entry->mem.size);
262 exit(-1);
263 }
264
265 if (entry->addr <= virt_base)
266 entry ++;
267 } else {
268 if (entry->addr >= virt_base &&
269 entry->addr < virt_base + size) {
270 error_report("%s: RAM at %"PRIx64 "-%"PRIx64
271 " collides with FIFO at %"PRIx64,
272 __func__, virt_base, virt_base + size,
273 entry->addr);
274 exit(-1);
275 }
276
277 while (entry < dma->memmap + dma->memmap_size &&
278 entry->addr <= virt_base)
279 entry ++;
280 }
281
282 memmove(entry + 1, entry,
283 (uint8_t *) (dma->memmap + dma->memmap_size ++) -
284 (uint8_t *) entry);
285 } else
286 dma->memmap_size ++;
287
288 entry->addr = virt_base;
289 entry->type = soc_dma_port_mem;
290 entry->mem.size = size;
291 }
292
293 /* TODO: port removal for ports like PCMCIA memory */