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