summaryrefslogtreecommitdiffstats
path: root/hw/misc/npcm7xx_rng.c
blob: b01df7cdb257d4059a9b082bb71f6543deff64f7 (plain) (blame)
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
/*
 * Nuvoton NPCM7xx Random Number Generator.
 *
 * Copyright 2020 Google LLC
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 */

#include "qemu/osdep.h"

#include "hw/misc/npcm7xx_rng.h"
#include "migration/vmstate.h"
#include "qemu/bitops.h"
#include "qemu/guest-random.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/units.h"

#include "trace.h"

#define NPCM7XX_RNG_REGS_SIZE   (4 * KiB)

#define NPCM7XX_RNGCS           (0x00)
#define NPCM7XX_RNGCS_CLKP(rv)      extract32(rv, 2, 4)
#define NPCM7XX_RNGCS_DVALID        BIT(1)
#define NPCM7XX_RNGCS_RNGE          BIT(0)

#define NPCM7XX_RNGD            (0x04)
#define NPCM7XX_RNGMODE         (0x08)
#define NPCM7XX_RNGMODE_NORMAL      (0x02)

static bool npcm7xx_rng_is_enabled(NPCM7xxRNGState *s)
{
    return (s->rngcs & NPCM7XX_RNGCS_RNGE) &&
        (s->rngmode == NPCM7XX_RNGMODE_NORMAL);
}

static uint64_t npcm7xx_rng_read(void *opaque, hwaddr offset, unsigned size)
{
    NPCM7xxRNGState *s = opaque;
    uint64_t value = 0;

    switch (offset) {
    case NPCM7XX_RNGCS:
        /*
         * If the RNG is enabled, but we don't have any valid random data, try
         * obtaining some and update the DVALID bit accordingly.
         */
        if (!npcm7xx_rng_is_enabled(s)) {
            s->rngcs &= ~NPCM7XX_RNGCS_DVALID;
        } else if (!(s->rngcs & NPCM7XX_RNGCS_DVALID)) {
            uint8_t byte = 0;

            if (qemu_guest_getrandom(&byte, sizeof(byte), NULL) == 0) {
                s->rngd = byte;
                s->rngcs |= NPCM7XX_RNGCS_DVALID;
            }
        }
        value = s->rngcs;
        break;
    case NPCM7XX_RNGD:
        if (npcm7xx_rng_is_enabled(s) && s->rngcs & NPCM7XX_RNGCS_DVALID) {
            s->rngcs &= ~NPCM7XX_RNGCS_DVALID;
            value = s->rngd;
            s->rngd = 0;
        }
        break;
    case NPCM7XX_RNGMODE:
        value = s->rngmode;
        break;

    default:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "%s: read from invalid offset 0x%" HWADDR_PRIx "\n",
                      DEVICE(s)->canonical_path, offset);
        break;
    }

    trace_npcm7xx_rng_read(offset, value, size);

    return value;
}

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

    trace_npcm7xx_rng_write(offset, value, size);

    switch (offset) {
    case NPCM7XX_RNGCS:
        s->rngcs &= NPCM7XX_RNGCS_DVALID;
        s->rngcs |= value & ~NPCM7XX_RNGCS_DVALID;
        break;
    case NPCM7XX_RNGD:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "%s: write to read-only register @ 0x%" HWADDR_PRIx "\n",
                      DEVICE(s)->canonical_path, offset);
        break;
    case NPCM7XX_RNGMODE:
        s->rngmode = value;
        break;
    default:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "%s: write to invalid offset 0x%" HWADDR_PRIx "\n",
                      DEVICE(s)->canonical_path, offset);
        break;
    }
}

static const MemoryRegionOps npcm7xx_rng_ops = {
    .read = npcm7xx_rng_read,
    .write = npcm7xx_rng_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = {
        .min_access_size = 1,
        .max_access_size = 4,
        .unaligned = false,
    },
};

static void npcm7xx_rng_enter_reset(Object *obj, ResetType type)
{
    NPCM7xxRNGState *s = NPCM7XX_RNG(obj);

    s->rngcs = 0;
    s->rngd = 0;
    s->rngmode = 0;
}

static void npcm7xx_rng_init(Object *obj)
{
    NPCM7xxRNGState *s = NPCM7XX_RNG(obj);

    memory_region_init_io(&s->iomem, obj, &npcm7xx_rng_ops, s, "regs",
                          NPCM7XX_RNG_REGS_SIZE);
    sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
}

static const VMStateDescription vmstate_npcm7xx_rng = {
    .name = "npcm7xx-rng",
    .version_id = 0,
    .minimum_version_id = 0,
    .fields = (VMStateField[]) {
        VMSTATE_UINT8(rngcs, NPCM7xxRNGState),
        VMSTATE_UINT8(rngd, NPCM7xxRNGState),
        VMSTATE_UINT8(rngmode, NPCM7xxRNGState),
        VMSTATE_END_OF_LIST(),
    },
};

static void npcm7xx_rng_class_init(ObjectClass *klass, void *data)
{
    ResettableClass *rc = RESETTABLE_CLASS(klass);
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->desc = "NPCM7xx Random Number Generator";
    dc->vmsd = &vmstate_npcm7xx_rng;
    rc->phases.enter = npcm7xx_rng_enter_reset;
}

static const TypeInfo npcm7xx_rng_types[] = {
    {
        .name = TYPE_NPCM7XX_RNG,
        .parent = TYPE_SYS_BUS_DEVICE,
        .instance_size = sizeof(NPCM7xxRNGState),
        .class_init = npcm7xx_rng_class_init,
        .instance_init = npcm7xx_rng_init,
    },
};
DEFINE_TYPES(npcm7xx_rng_types);