]>
Commit | Line | Data |
---|---|---|
99494e69 AB |
1 | /* |
2 | * Raspberry Pi emulation (c) 2012 Gregory Estrade | |
3 | * This code is licensed under the GNU GPLv2 and later. | |
4 | * | |
5 | * This file models the system mailboxes, which are used for | |
6 | * communication with low-bandwidth GPU peripherals. Refs: | |
7 | * https://github.com/raspberrypi/firmware/wiki/Mailboxes | |
8 | * https://github.com/raspberrypi/firmware/wiki/Accessing-mailboxes | |
9 | */ | |
10 | ||
c964b660 | 11 | #include "qemu/osdep.h" |
99494e69 AB |
12 | #include "hw/misc/bcm2835_mbox.h" |
13 | ||
14 | #define MAIL0_PEEK 0x90 | |
15 | #define MAIL0_SENDER 0x94 | |
16 | #define MAIL1_STATUS 0xb8 | |
17 | ||
18 | /* Mailbox status register */ | |
19 | #define MAIL0_STATUS 0x98 | |
20 | #define ARM_MS_FULL 0x80000000 | |
21 | #define ARM_MS_EMPTY 0x40000000 | |
22 | #define ARM_MS_LEVEL 0x400000FF /* Max. value depends on mailbox depth */ | |
23 | ||
24 | /* MAILBOX config/status register */ | |
25 | #define MAIL0_CONFIG 0x9c | |
26 | /* ANY write to this register clears the error bits! */ | |
27 | #define ARM_MC_IHAVEDATAIRQEN 0x00000001 /* mbox irq enable: has data */ | |
28 | #define ARM_MC_IHAVESPACEIRQEN 0x00000002 /* mbox irq enable: has space */ | |
29 | #define ARM_MC_OPPISEMPTYIRQEN 0x00000004 /* mbox irq enable: Opp is empty */ | |
30 | #define ARM_MC_MAIL_CLEAR 0x00000008 /* mbox clear write 1, then 0 */ | |
31 | #define ARM_MC_IHAVEDATAIRQPEND 0x00000010 /* mbox irq pending: has space */ | |
32 | #define ARM_MC_IHAVESPACEIRQPEND 0x00000020 /* mbox irq pending: Opp is empty */ | |
33 | #define ARM_MC_OPPISEMPTYIRQPEND 0x00000040 /* mbox irq pending */ | |
34 | /* Bit 7 is unused */ | |
35 | #define ARM_MC_ERRNOOWN 0x00000100 /* error : none owner read from mailbox */ | |
36 | #define ARM_MC_ERROVERFLW 0x00000200 /* error : write to fill mailbox */ | |
37 | #define ARM_MC_ERRUNDRFLW 0x00000400 /* error : read from empty mailbox */ | |
38 | ||
39 | static void mbox_update_status(BCM2835Mbox *mb) | |
40 | { | |
41 | mb->status &= ~(ARM_MS_EMPTY | ARM_MS_FULL); | |
42 | if (mb->count == 0) { | |
43 | mb->status |= ARM_MS_EMPTY; | |
44 | } else if (mb->count == MBOX_SIZE) { | |
45 | mb->status |= ARM_MS_FULL; | |
46 | } | |
47 | } | |
48 | ||
49 | static void mbox_reset(BCM2835Mbox *mb) | |
50 | { | |
51 | int n; | |
52 | ||
53 | mb->count = 0; | |
54 | mb->config = 0; | |
55 | for (n = 0; n < MBOX_SIZE; n++) { | |
56 | mb->reg[n] = MBOX_INVALID_DATA; | |
57 | } | |
58 | mbox_update_status(mb); | |
59 | } | |
60 | ||
61 | static uint32_t mbox_pull(BCM2835Mbox *mb, int index) | |
62 | { | |
63 | int n; | |
64 | uint32_t val; | |
65 | ||
66 | assert(mb->count > 0); | |
67 | assert(index < mb->count); | |
68 | ||
69 | val = mb->reg[index]; | |
70 | for (n = index + 1; n < mb->count; n++) { | |
71 | mb->reg[n - 1] = mb->reg[n]; | |
72 | } | |
73 | mb->count--; | |
74 | mb->reg[mb->count] = MBOX_INVALID_DATA; | |
75 | ||
76 | mbox_update_status(mb); | |
77 | ||
78 | return val; | |
79 | } | |
80 | ||
81 | static void mbox_push(BCM2835Mbox *mb, uint32_t val) | |
82 | { | |
83 | assert(mb->count < MBOX_SIZE); | |
84 | mb->reg[mb->count++] = val; | |
85 | mbox_update_status(mb); | |
86 | } | |
87 | ||
88 | static void bcm2835_mbox_update(BCM2835MboxState *s) | |
89 | { | |
90 | uint32_t value; | |
91 | bool set; | |
92 | int n; | |
93 | ||
94 | s->mbox_irq_disabled = true; | |
95 | ||
96 | /* Get pending responses and put them in the vc->arm mbox, | |
97 | * as long as it's not full | |
98 | */ | |
99 | for (n = 0; n < MBOX_CHAN_COUNT; n++) { | |
100 | while (s->available[n] && !(s->mbox[0].status & ARM_MS_FULL)) { | |
eab71394 | 101 | value = ldl_le_phys(&s->mbox_as, n << MBOX_AS_CHAN_SHIFT); |
99494e69 AB |
102 | assert(value != MBOX_INVALID_DATA); /* Pending interrupt but no data */ |
103 | mbox_push(&s->mbox[0], value); | |
104 | } | |
105 | } | |
106 | ||
107 | /* TODO (?): Try to push pending requests from the arm->vc mbox */ | |
108 | ||
109 | /* Re-enable calls from the IRQ routine */ | |
110 | s->mbox_irq_disabled = false; | |
111 | ||
112 | /* Update ARM IRQ status */ | |
113 | set = false; | |
114 | s->mbox[0].config &= ~ARM_MC_IHAVEDATAIRQPEND; | |
115 | if (!(s->mbox[0].status & ARM_MS_EMPTY)) { | |
116 | s->mbox[0].config |= ARM_MC_IHAVEDATAIRQPEND; | |
117 | if (s->mbox[0].config & ARM_MC_IHAVEDATAIRQEN) { | |
118 | set = true; | |
119 | } | |
120 | } | |
121 | qemu_set_irq(s->arm_irq, set); | |
122 | } | |
123 | ||
124 | static void bcm2835_mbox_set_irq(void *opaque, int irq, int level) | |
125 | { | |
126 | BCM2835MboxState *s = opaque; | |
127 | ||
128 | s->available[irq] = level; | |
129 | ||
130 | /* avoid recursively calling bcm2835_mbox_update when the interrupt | |
131 | * status changes due to the ldl_phys call within that function | |
132 | */ | |
133 | if (!s->mbox_irq_disabled) { | |
134 | bcm2835_mbox_update(s); | |
135 | } | |
136 | } | |
137 | ||
138 | static uint64_t bcm2835_mbox_read(void *opaque, hwaddr offset, unsigned size) | |
139 | { | |
140 | BCM2835MboxState *s = opaque; | |
141 | uint32_t res = 0; | |
142 | ||
143 | offset &= 0xff; | |
144 | ||
145 | switch (offset) { | |
146 | case 0x80 ... 0x8c: /* MAIL0_READ */ | |
147 | if (s->mbox[0].status & ARM_MS_EMPTY) { | |
148 | res = MBOX_INVALID_DATA; | |
149 | } else { | |
150 | res = mbox_pull(&s->mbox[0], 0); | |
151 | } | |
152 | break; | |
153 | ||
154 | case MAIL0_PEEK: | |
155 | res = s->mbox[0].reg[0]; | |
156 | break; | |
157 | ||
158 | case MAIL0_SENDER: | |
159 | break; | |
160 | ||
161 | case MAIL0_STATUS: | |
162 | res = s->mbox[0].status; | |
163 | break; | |
164 | ||
165 | case MAIL0_CONFIG: | |
166 | res = s->mbox[0].config; | |
167 | break; | |
168 | ||
169 | case MAIL1_STATUS: | |
170 | res = s->mbox[1].status; | |
171 | break; | |
172 | ||
173 | default: | |
174 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", | |
175 | __func__, offset); | |
176 | return 0; | |
177 | } | |
178 | ||
179 | bcm2835_mbox_update(s); | |
180 | ||
181 | return res; | |
182 | } | |
183 | ||
184 | static void bcm2835_mbox_write(void *opaque, hwaddr offset, | |
185 | uint64_t value, unsigned size) | |
186 | { | |
187 | BCM2835MboxState *s = opaque; | |
188 | hwaddr childaddr; | |
189 | uint8_t ch; | |
190 | ||
191 | offset &= 0xff; | |
192 | ||
193 | switch (offset) { | |
194 | case MAIL0_SENDER: | |
195 | break; | |
196 | ||
197 | case MAIL0_CONFIG: | |
198 | s->mbox[0].config &= ~ARM_MC_IHAVEDATAIRQEN; | |
199 | s->mbox[0].config |= value & ARM_MC_IHAVEDATAIRQEN; | |
200 | break; | |
201 | ||
202 | case 0xa0 ... 0xac: /* MAIL1_WRITE */ | |
203 | if (s->mbox[1].status & ARM_MS_FULL) { | |
204 | /* Mailbox full */ | |
205 | qemu_log_mask(LOG_GUEST_ERROR, "%s: mailbox full\n", __func__); | |
206 | } else { | |
207 | ch = value & 0xf; | |
208 | if (ch < MBOX_CHAN_COUNT) { | |
209 | childaddr = ch << MBOX_AS_CHAN_SHIFT; | |
eab71394 | 210 | if (ldl_le_phys(&s->mbox_as, childaddr + MBOX_AS_PENDING)) { |
99494e69 AB |
211 | /* Child busy, push delayed. Push it in the arm->vc mbox */ |
212 | mbox_push(&s->mbox[1], value); | |
213 | } else { | |
214 | /* Push it directly to the child device */ | |
eab71394 | 215 | stl_le_phys(&s->mbox_as, childaddr, value); |
99494e69 AB |
216 | } |
217 | } else { | |
218 | /* Invalid channel number */ | |
219 | qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid channel %u\n", | |
220 | __func__, ch); | |
221 | } | |
222 | } | |
223 | break; | |
224 | ||
225 | default: | |
226 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", | |
227 | __func__, offset); | |
228 | return; | |
229 | } | |
230 | ||
231 | bcm2835_mbox_update(s); | |
232 | } | |
233 | ||
234 | static const MemoryRegionOps bcm2835_mbox_ops = { | |
235 | .read = bcm2835_mbox_read, | |
236 | .write = bcm2835_mbox_write, | |
237 | .endianness = DEVICE_NATIVE_ENDIAN, | |
238 | .valid.min_access_size = 4, | |
239 | .valid.max_access_size = 4, | |
240 | }; | |
241 | ||
242 | /* vmstate of a single mailbox */ | |
243 | static const VMStateDescription vmstate_bcm2835_mbox_box = { | |
244 | .name = TYPE_BCM2835_MBOX "_box", | |
245 | .version_id = 1, | |
246 | .minimum_version_id = 1, | |
247 | .fields = (VMStateField[]) { | |
248 | VMSTATE_UINT32_ARRAY(reg, BCM2835Mbox, MBOX_SIZE), | |
249 | VMSTATE_UINT32(count, BCM2835Mbox), | |
250 | VMSTATE_UINT32(status, BCM2835Mbox), | |
251 | VMSTATE_UINT32(config, BCM2835Mbox), | |
252 | VMSTATE_END_OF_LIST() | |
253 | } | |
254 | }; | |
255 | ||
256 | /* vmstate of the entire device */ | |
257 | static const VMStateDescription vmstate_bcm2835_mbox = { | |
258 | .name = TYPE_BCM2835_MBOX, | |
259 | .version_id = 1, | |
260 | .minimum_version_id = 1, | |
261 | .minimum_version_id_old = 1, | |
262 | .fields = (VMStateField[]) { | |
263 | VMSTATE_BOOL_ARRAY(available, BCM2835MboxState, MBOX_CHAN_COUNT), | |
264 | VMSTATE_STRUCT_ARRAY(mbox, BCM2835MboxState, 2, 1, | |
265 | vmstate_bcm2835_mbox_box, BCM2835Mbox), | |
266 | VMSTATE_END_OF_LIST() | |
267 | } | |
268 | }; | |
269 | ||
270 | static void bcm2835_mbox_init(Object *obj) | |
271 | { | |
272 | BCM2835MboxState *s = BCM2835_MBOX(obj); | |
273 | ||
274 | memory_region_init_io(&s->iomem, obj, &bcm2835_mbox_ops, s, | |
275 | TYPE_BCM2835_MBOX, 0x400); | |
276 | sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); | |
277 | sysbus_init_irq(SYS_BUS_DEVICE(s), &s->arm_irq); | |
278 | qdev_init_gpio_in(DEVICE(s), bcm2835_mbox_set_irq, MBOX_CHAN_COUNT); | |
279 | } | |
280 | ||
281 | static void bcm2835_mbox_reset(DeviceState *dev) | |
282 | { | |
283 | BCM2835MboxState *s = BCM2835_MBOX(dev); | |
284 | int n; | |
285 | ||
286 | mbox_reset(&s->mbox[0]); | |
287 | mbox_reset(&s->mbox[1]); | |
288 | s->mbox_irq_disabled = false; | |
289 | for (n = 0; n < MBOX_CHAN_COUNT; n++) { | |
290 | s->available[n] = false; | |
291 | } | |
292 | } | |
293 | ||
294 | static void bcm2835_mbox_realize(DeviceState *dev, Error **errp) | |
295 | { | |
296 | BCM2835MboxState *s = BCM2835_MBOX(dev); | |
297 | Object *obj; | |
298 | Error *err = NULL; | |
299 | ||
300 | obj = object_property_get_link(OBJECT(dev), "mbox-mr", &err); | |
301 | if (obj == NULL) { | |
302 | error_setg(errp, "%s: required mbox-mr link not found: %s", | |
303 | __func__, error_get_pretty(err)); | |
304 | return; | |
305 | } | |
306 | ||
307 | s->mbox_mr = MEMORY_REGION(obj); | |
308 | address_space_init(&s->mbox_as, s->mbox_mr, NULL); | |
309 | bcm2835_mbox_reset(dev); | |
310 | } | |
311 | ||
312 | static void bcm2835_mbox_class_init(ObjectClass *klass, void *data) | |
313 | { | |
314 | DeviceClass *dc = DEVICE_CLASS(klass); | |
315 | ||
316 | dc->realize = bcm2835_mbox_realize; | |
317 | dc->reset = bcm2835_mbox_reset; | |
318 | dc->vmsd = &vmstate_bcm2835_mbox; | |
319 | } | |
320 | ||
321 | static TypeInfo bcm2835_mbox_info = { | |
322 | .name = TYPE_BCM2835_MBOX, | |
323 | .parent = TYPE_SYS_BUS_DEVICE, | |
324 | .instance_size = sizeof(BCM2835MboxState), | |
325 | .class_init = bcm2835_mbox_class_init, | |
326 | .instance_init = bcm2835_mbox_init, | |
327 | }; | |
328 | ||
329 | static void bcm2835_mbox_register_types(void) | |
330 | { | |
331 | type_register_static(&bcm2835_mbox_info); | |
332 | } | |
333 | ||
334 | type_init(bcm2835_mbox_register_types) |