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