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