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