aboutsummaryrefslogtreecommitdiff
path: root/hw/misc/bcm2835_mbox.c
blob: 77d2d80706c03a1ffea76aa0e08066ec22c2403a (plain)
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
340
341
342
343
344
345
/*
 * Raspberry Pi emulation (c) 2012 Gregory Estrade
 * This code is licensed under the GNU GPLv2 and later.
 *
 * 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
 */

#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 = (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,
    .minimum_version_id_old = 1,
    .fields      = (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;
    Error *err = NULL;

    obj = object_property_get_link(OBJECT(dev), "mbox-mr", &err);
    if (obj == NULL) {
        error_setg(errp, "%s: required mbox-mr link not found: %s",
                   __func__, error_get_pretty(err));
        return;
    }

    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;
    dc->reset = bcm2835_mbox_reset;
    dc->vmsd = &vmstate_bcm2835_mbox;
}

static 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)