summaryrefslogtreecommitdiff
path: root/hw/misc/bcm2835_rng.c
blob: d0c4e64e88d59480fdb69705e43ea605c1213f6d (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
/*
 * BCM2835 Random Number Generator emulation
 *
 * Copyright (C) 2017 Marcin Chojnacki <marcinch7@gmail.com>
 *
 * 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 "qemu/log.h"
#include "qemu/guest-random.h"
#include "qemu/module.h"
#include "hw/misc/bcm2835_rng.h"
#include "migration/vmstate.h"

static uint32_t get_random_bytes(void)
{
    uint32_t res;

    /*
     * On failure we don't want to return the guest a non-random
     * value in case they're really using it for cryptographic
     * purposes, so the best we can do is die here.
     * This shouldn't happen unless something's broken.
     * In theory we could implement this device's full FIFO
     * and interrupt semantics and then just stop filling the
     * FIFO. That's a lot of work, though, so we assume any
     * errors are systematic problems and trust that if we didn't
     * fail as the guest inited then we won't fail later on
     * mid-run.
     */
    qemu_guest_getrandom_nofail(&res, sizeof(res));
    return res;
}

static uint64_t bcm2835_rng_read(void *opaque, hwaddr offset,
                                 unsigned size)
{
    BCM2835RngState *s = (BCM2835RngState *)opaque;
    uint32_t res = 0;

    assert(size == 4);

    switch (offset) {
    case 0x0:    /* rng_ctrl */
        res = s->rng_ctrl;
        break;
    case 0x4:    /* rng_status */
        res = s->rng_status | (1 << 24);
        break;
    case 0x8:    /* rng_data */
        res = get_random_bytes();
        break;

    default:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "bcm2835_rng_read: Bad offset %x\n",
                      (int)offset);
        res = 0;
        break;
    }

    return res;
}

static void bcm2835_rng_write(void *opaque, hwaddr offset,
                              uint64_t value, unsigned size)
{
    BCM2835RngState *s = (BCM2835RngState *)opaque;

    assert(size == 4);

    switch (offset) {
    case 0x0:    /* rng_ctrl */
        s->rng_ctrl = value;
        break;
    case 0x4:    /* rng_status */
        /* we shouldn't let the guest write to bits [31..20] */
        s->rng_status &= ~0xFFFFF;        /* clear 20 lower bits */
        s->rng_status |= value & 0xFFFFF; /* set them to new value */
        break;

    default:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "bcm2835_rng_write: Bad offset %x\n",
                      (int)offset);
        break;
    }
}

static const MemoryRegionOps bcm2835_rng_ops = {
    .read = bcm2835_rng_read,
    .write = bcm2835_rng_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

static const VMStateDescription vmstate_bcm2835_rng = {
    .name = TYPE_BCM2835_RNG,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32(rng_ctrl, BCM2835RngState),
        VMSTATE_UINT32(rng_status, BCM2835RngState),
        VMSTATE_END_OF_LIST()
    }
};

static void bcm2835_rng_init(Object *obj)
{
    BCM2835RngState *s = BCM2835_RNG(obj);

    memory_region_init_io(&s->iomem, obj, &bcm2835_rng_ops, s,
                          TYPE_BCM2835_RNG, 0x10);
    sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
}

static void bcm2835_rng_reset(DeviceState *dev)
{
    BCM2835RngState *s = BCM2835_RNG(dev);

    s->rng_ctrl = 0;
    s->rng_status = 0;
}

static void bcm2835_rng_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->reset = bcm2835_rng_reset;
    dc->vmsd = &vmstate_bcm2835_rng;
}

static TypeInfo bcm2835_rng_info = {
    .name          = TYPE_BCM2835_RNG,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(BCM2835RngState),
    .class_init    = bcm2835_rng_class_init,
    .instance_init = bcm2835_rng_init,
};

static void bcm2835_rng_register_types(void)
{
    type_register_static(&bcm2835_rng_info);
}

type_init(bcm2835_rng_register_types)