1499ca137SKrzysztof Kozlowski /*
2673d8215SMichael Tokarev * Exynos4210 Pseudo Random Number Generator Emulation
3499ca137SKrzysztof Kozlowski *
4499ca137SKrzysztof Kozlowski * Copyright (c) 2017 Krzysztof Kozlowski <krzk@kernel.org>
5499ca137SKrzysztof Kozlowski *
6499ca137SKrzysztof Kozlowski * This program is free software; you can redistribute it and/or modify it
7499ca137SKrzysztof Kozlowski * under the terms of the GNU General Public License as published by the
8499ca137SKrzysztof Kozlowski * Free Software Foundation; either version 2 of the License, or
9499ca137SKrzysztof Kozlowski * (at your option) any later version.
10499ca137SKrzysztof Kozlowski *
11499ca137SKrzysztof Kozlowski * This program is distributed in the hope that it will be useful, but WITHOUT
12499ca137SKrzysztof Kozlowski * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13499ca137SKrzysztof Kozlowski * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14499ca137SKrzysztof Kozlowski * for more details.
15499ca137SKrzysztof Kozlowski *
16499ca137SKrzysztof Kozlowski * You should have received a copy of the GNU General Public License along
17499ca137SKrzysztof Kozlowski * with this program; if not, see <http://www.gnu.org/licenses/>.
18499ca137SKrzysztof Kozlowski */
19499ca137SKrzysztof Kozlowski
20499ca137SKrzysztof Kozlowski #include "qemu/osdep.h"
21499ca137SKrzysztof Kozlowski #include "hw/sysbus.h"
22d6454270SMarkus Armbruster #include "migration/vmstate.h"
23e688df6bSMarkus Armbruster #include "qapi/error.h"
24499ca137SKrzysztof Kozlowski #include "qemu/log.h"
25e8196d21SRichard Henderson #include "qemu/guest-random.h"
260b8fa32fSMarkus Armbruster #include "qemu/module.h"
27db1015e9SEduardo Habkost #include "qom/object.h"
28499ca137SKrzysztof Kozlowski
29499ca137SKrzysztof Kozlowski #define DEBUG_EXYNOS_RNG 0
30499ca137SKrzysztof Kozlowski
31499ca137SKrzysztof Kozlowski #define DPRINTF(fmt, ...) \
32499ca137SKrzysztof Kozlowski do { \
33499ca137SKrzysztof Kozlowski if (DEBUG_EXYNOS_RNG) { \
34499ca137SKrzysztof Kozlowski printf("exynos4210_rng: " fmt, ## __VA_ARGS__); \
35499ca137SKrzysztof Kozlowski } \
36499ca137SKrzysztof Kozlowski } while (0)
37499ca137SKrzysztof Kozlowski
38499ca137SKrzysztof Kozlowski #define TYPE_EXYNOS4210_RNG "exynos4210.rng"
398063396bSEduardo Habkost OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210RngState, EXYNOS4210_RNG)
40499ca137SKrzysztof Kozlowski
41499ca137SKrzysztof Kozlowski /*
42499ca137SKrzysztof Kozlowski * Exynos4220, PRNG, only polling mode is supported.
43499ca137SKrzysztof Kozlowski */
44499ca137SKrzysztof Kozlowski
45499ca137SKrzysztof Kozlowski /* RNG_CONTROL_1 register bitfields, reset value: 0x0 */
46499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_CONTROL_1_PRNG 0x8
47499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_CONTROL_1_START_INIT BIT(4)
48499ca137SKrzysztof Kozlowski /* RNG_STATUS register bitfields, reset value: 0x1 */
49499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS_PRNG_ERROR BIT(7)
50499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS_PRNG_DONE BIT(5)
51499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS_MSG_DONE BIT(4)
52499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS_PARTIAL_DONE BIT(3)
53499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS_PRNG_BUSY BIT(2)
54499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS_SEED_SETTING_DONE BIT(1)
55499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS_BUFFER_READY BIT(0)
56499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS_WRITE_MASK (EXYNOS4210_RNG_STATUS_PRNG_DONE \
57499ca137SKrzysztof Kozlowski | EXYNOS4210_RNG_STATUS_MSG_DONE \
58499ca137SKrzysztof Kozlowski | EXYNOS4210_RNG_STATUS_PARTIAL_DONE)
59499ca137SKrzysztof Kozlowski
60499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_CONTROL_1 0x0
61499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_STATUS 0x10
62499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_SEED_IN 0x140
63499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_SEED_IN_OFFSET(n) (EXYNOS4210_RNG_SEED_IN + (n * 0x4))
64499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_PRNG 0x160
65499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_PRNG_OFFSET(n) (EXYNOS4210_RNG_PRNG + (n * 0x4))
66499ca137SKrzysztof Kozlowski
67499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_PRNG_NUM 5
68499ca137SKrzysztof Kozlowski
69499ca137SKrzysztof Kozlowski #define EXYNOS4210_RNG_REGS_MEM_SIZE 0x200
70499ca137SKrzysztof Kozlowski
71db1015e9SEduardo Habkost struct Exynos4210RngState {
72499ca137SKrzysztof Kozlowski SysBusDevice parent_obj;
73499ca137SKrzysztof Kozlowski MemoryRegion iomem;
74499ca137SKrzysztof Kozlowski
75499ca137SKrzysztof Kozlowski int32_t randr_value[EXYNOS4210_RNG_PRNG_NUM];
76499ca137SKrzysztof Kozlowski /* bits from 0 to EXYNOS4210_RNG_PRNG_NUM if given seed register was set */
77499ca137SKrzysztof Kozlowski uint32_t seed_set;
78499ca137SKrzysztof Kozlowski
79499ca137SKrzysztof Kozlowski /* Register values */
80499ca137SKrzysztof Kozlowski uint32_t reg_control;
81499ca137SKrzysztof Kozlowski uint32_t reg_status;
82db1015e9SEduardo Habkost };
83499ca137SKrzysztof Kozlowski
exynos4210_rng_seed_ready(const Exynos4210RngState * s)84499ca137SKrzysztof Kozlowski static bool exynos4210_rng_seed_ready(const Exynos4210RngState *s)
85499ca137SKrzysztof Kozlowski {
86499ca137SKrzysztof Kozlowski uint32_t mask = MAKE_64BIT_MASK(0, EXYNOS4210_RNG_PRNG_NUM);
87499ca137SKrzysztof Kozlowski
88499ca137SKrzysztof Kozlowski /* Return true if all the seed-set bits are set. */
89499ca137SKrzysztof Kozlowski return (s->seed_set & mask) == mask;
90499ca137SKrzysztof Kozlowski }
91499ca137SKrzysztof Kozlowski
exynos4210_rng_set_seed(Exynos4210RngState * s,unsigned int i,uint64_t val)92499ca137SKrzysztof Kozlowski static void exynos4210_rng_set_seed(Exynos4210RngState *s, unsigned int i,
93499ca137SKrzysztof Kozlowski uint64_t val)
94499ca137SKrzysztof Kozlowski {
95499ca137SKrzysztof Kozlowski /*
96499ca137SKrzysztof Kozlowski * We actually ignore the seed and always generate true random numbers.
97499ca137SKrzysztof Kozlowski * Theoretically this should not match the device as Exynos has
98499ca137SKrzysztof Kozlowski * a Pseudo Random Number Generator but testing shown that it always
99499ca137SKrzysztof Kozlowski * generates random numbers regardless of the seed value.
100499ca137SKrzysztof Kozlowski */
101499ca137SKrzysztof Kozlowski s->seed_set |= BIT(i);
102499ca137SKrzysztof Kozlowski
103499ca137SKrzysztof Kozlowski /* If all seeds were written, update the status to reflect it */
104499ca137SKrzysztof Kozlowski if (exynos4210_rng_seed_ready(s)) {
105499ca137SKrzysztof Kozlowski s->reg_status |= EXYNOS4210_RNG_STATUS_SEED_SETTING_DONE;
106499ca137SKrzysztof Kozlowski } else {
107499ca137SKrzysztof Kozlowski s->reg_status &= ~EXYNOS4210_RNG_STATUS_SEED_SETTING_DONE;
108499ca137SKrzysztof Kozlowski }
109499ca137SKrzysztof Kozlowski }
110499ca137SKrzysztof Kozlowski
exynos4210_rng_run_engine(Exynos4210RngState * s)111499ca137SKrzysztof Kozlowski static void exynos4210_rng_run_engine(Exynos4210RngState *s)
112499ca137SKrzysztof Kozlowski {
113499ca137SKrzysztof Kozlowski Error *err = NULL;
114499ca137SKrzysztof Kozlowski
115499ca137SKrzysztof Kozlowski /* Seed set? */
116499ca137SKrzysztof Kozlowski if ((s->reg_status & EXYNOS4210_RNG_STATUS_SEED_SETTING_DONE) == 0) {
117499ca137SKrzysztof Kozlowski goto out;
118499ca137SKrzysztof Kozlowski }
119499ca137SKrzysztof Kozlowski
120499ca137SKrzysztof Kozlowski /* PRNG engine chosen? */
121499ca137SKrzysztof Kozlowski if ((s->reg_control & EXYNOS4210_RNG_CONTROL_1_PRNG) == 0) {
122499ca137SKrzysztof Kozlowski goto out;
123499ca137SKrzysztof Kozlowski }
124499ca137SKrzysztof Kozlowski
125499ca137SKrzysztof Kozlowski /* PRNG engine started? */
126499ca137SKrzysztof Kozlowski if ((s->reg_control & EXYNOS4210_RNG_CONTROL_1_START_INIT) == 0) {
127499ca137SKrzysztof Kozlowski goto out;
128499ca137SKrzysztof Kozlowski }
129499ca137SKrzysztof Kozlowski
130499ca137SKrzysztof Kozlowski /* Get randoms */
131e8196d21SRichard Henderson if (qemu_guest_getrandom(s->randr_value, sizeof(s->randr_value), &err)) {
132e8196d21SRichard Henderson error_report_err(err);
133e8196d21SRichard Henderson } else {
134499ca137SKrzysztof Kozlowski /* Notify that PRNG is ready */
135499ca137SKrzysztof Kozlowski s->reg_status |= EXYNOS4210_RNG_STATUS_PRNG_DONE;
136499ca137SKrzysztof Kozlowski }
137499ca137SKrzysztof Kozlowski
138499ca137SKrzysztof Kozlowski out:
139499ca137SKrzysztof Kozlowski /* Always clear start engine bit */
140499ca137SKrzysztof Kozlowski s->reg_control &= ~EXYNOS4210_RNG_CONTROL_1_START_INIT;
141499ca137SKrzysztof Kozlowski }
142499ca137SKrzysztof Kozlowski
exynos4210_rng_read(void * opaque,hwaddr offset,unsigned size)143499ca137SKrzysztof Kozlowski static uint64_t exynos4210_rng_read(void *opaque, hwaddr offset,
144499ca137SKrzysztof Kozlowski unsigned size)
145499ca137SKrzysztof Kozlowski {
146499ca137SKrzysztof Kozlowski Exynos4210RngState *s = (Exynos4210RngState *)opaque;
147499ca137SKrzysztof Kozlowski uint32_t val = 0;
148499ca137SKrzysztof Kozlowski
149499ca137SKrzysztof Kozlowski assert(size == 4);
150499ca137SKrzysztof Kozlowski
151499ca137SKrzysztof Kozlowski switch (offset) {
152499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_CONTROL_1:
153499ca137SKrzysztof Kozlowski val = s->reg_control;
154499ca137SKrzysztof Kozlowski break;
155499ca137SKrzysztof Kozlowski
156499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_STATUS:
157499ca137SKrzysztof Kozlowski val = s->reg_status;
158499ca137SKrzysztof Kozlowski break;
159499ca137SKrzysztof Kozlowski
160499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_PRNG_OFFSET(0):
161499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_PRNG_OFFSET(1):
162499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_PRNG_OFFSET(2):
163499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_PRNG_OFFSET(3):
164499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_PRNG_OFFSET(4):
165499ca137SKrzysztof Kozlowski val = s->randr_value[(offset - EXYNOS4210_RNG_PRNG_OFFSET(0)) / 4];
166499ca137SKrzysztof Kozlowski DPRINTF("returning random @0x%" HWADDR_PRIx ": 0x%" PRIx32 "\n",
167499ca137SKrzysztof Kozlowski offset, val);
168499ca137SKrzysztof Kozlowski break;
169499ca137SKrzysztof Kozlowski
170499ca137SKrzysztof Kozlowski default:
171499ca137SKrzysztof Kozlowski qemu_log_mask(LOG_GUEST_ERROR,
172499ca137SKrzysztof Kozlowski "%s: bad read offset 0x%" HWADDR_PRIx "\n",
173499ca137SKrzysztof Kozlowski __func__, offset);
174499ca137SKrzysztof Kozlowski }
175499ca137SKrzysztof Kozlowski
176499ca137SKrzysztof Kozlowski return val;
177499ca137SKrzysztof Kozlowski }
178499ca137SKrzysztof Kozlowski
exynos4210_rng_write(void * opaque,hwaddr offset,uint64_t val,unsigned size)179499ca137SKrzysztof Kozlowski static void exynos4210_rng_write(void *opaque, hwaddr offset,
180499ca137SKrzysztof Kozlowski uint64_t val, unsigned size)
181499ca137SKrzysztof Kozlowski {
182499ca137SKrzysztof Kozlowski Exynos4210RngState *s = (Exynos4210RngState *)opaque;
183499ca137SKrzysztof Kozlowski
184499ca137SKrzysztof Kozlowski assert(size == 4);
185499ca137SKrzysztof Kozlowski
186499ca137SKrzysztof Kozlowski switch (offset) {
187499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_CONTROL_1:
188499ca137SKrzysztof Kozlowski DPRINTF("RNG_CONTROL_1 = 0x%" PRIx64 "\n", val);
189499ca137SKrzysztof Kozlowski s->reg_control = val;
190499ca137SKrzysztof Kozlowski exynos4210_rng_run_engine(s);
191499ca137SKrzysztof Kozlowski break;
192499ca137SKrzysztof Kozlowski
193499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_STATUS:
194499ca137SKrzysztof Kozlowski /* For clearing status fields */
195499ca137SKrzysztof Kozlowski s->reg_status &= ~EXYNOS4210_RNG_STATUS_WRITE_MASK;
196499ca137SKrzysztof Kozlowski s->reg_status |= val & EXYNOS4210_RNG_STATUS_WRITE_MASK;
197499ca137SKrzysztof Kozlowski break;
198499ca137SKrzysztof Kozlowski
199499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_SEED_IN_OFFSET(0):
200499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_SEED_IN_OFFSET(1):
201499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_SEED_IN_OFFSET(2):
202499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_SEED_IN_OFFSET(3):
203499ca137SKrzysztof Kozlowski case EXYNOS4210_RNG_SEED_IN_OFFSET(4):
204499ca137SKrzysztof Kozlowski exynos4210_rng_set_seed(s,
205499ca137SKrzysztof Kozlowski (offset - EXYNOS4210_RNG_SEED_IN_OFFSET(0)) / 4,
206499ca137SKrzysztof Kozlowski val);
207499ca137SKrzysztof Kozlowski break;
208499ca137SKrzysztof Kozlowski
209499ca137SKrzysztof Kozlowski default:
210499ca137SKrzysztof Kozlowski qemu_log_mask(LOG_GUEST_ERROR,
211499ca137SKrzysztof Kozlowski "%s: bad write offset 0x%" HWADDR_PRIx "\n",
212499ca137SKrzysztof Kozlowski __func__, offset);
213499ca137SKrzysztof Kozlowski }
214499ca137SKrzysztof Kozlowski }
215499ca137SKrzysztof Kozlowski
216499ca137SKrzysztof Kozlowski static const MemoryRegionOps exynos4210_rng_ops = {
217499ca137SKrzysztof Kozlowski .read = exynos4210_rng_read,
218499ca137SKrzysztof Kozlowski .write = exynos4210_rng_write,
219499ca137SKrzysztof Kozlowski .endianness = DEVICE_NATIVE_ENDIAN,
220d338b5a8SZheyu Ma .valid.min_access_size = 4,
221d338b5a8SZheyu Ma .valid.max_access_size = 4,
222499ca137SKrzysztof Kozlowski };
223499ca137SKrzysztof Kozlowski
exynos4210_rng_reset(DeviceState * dev)224499ca137SKrzysztof Kozlowski static void exynos4210_rng_reset(DeviceState *dev)
225499ca137SKrzysztof Kozlowski {
226499ca137SKrzysztof Kozlowski Exynos4210RngState *s = EXYNOS4210_RNG(dev);
227499ca137SKrzysztof Kozlowski
228499ca137SKrzysztof Kozlowski s->reg_control = 0;
229499ca137SKrzysztof Kozlowski s->reg_status = EXYNOS4210_RNG_STATUS_BUFFER_READY;
230499ca137SKrzysztof Kozlowski memset(s->randr_value, 0, sizeof(s->randr_value));
231499ca137SKrzysztof Kozlowski s->seed_set = 0;
232499ca137SKrzysztof Kozlowski }
233499ca137SKrzysztof Kozlowski
exynos4210_rng_init(Object * obj)234499ca137SKrzysztof Kozlowski static void exynos4210_rng_init(Object *obj)
235499ca137SKrzysztof Kozlowski {
236499ca137SKrzysztof Kozlowski Exynos4210RngState *s = EXYNOS4210_RNG(obj);
237499ca137SKrzysztof Kozlowski SysBusDevice *dev = SYS_BUS_DEVICE(obj);
238499ca137SKrzysztof Kozlowski
239499ca137SKrzysztof Kozlowski memory_region_init_io(&s->iomem, obj, &exynos4210_rng_ops, s,
240499ca137SKrzysztof Kozlowski TYPE_EXYNOS4210_RNG, EXYNOS4210_RNG_REGS_MEM_SIZE);
241499ca137SKrzysztof Kozlowski sysbus_init_mmio(dev, &s->iomem);
242499ca137SKrzysztof Kozlowski }
243499ca137SKrzysztof Kozlowski
244499ca137SKrzysztof Kozlowski static const VMStateDescription exynos4210_rng_vmstate = {
245499ca137SKrzysztof Kozlowski .name = TYPE_EXYNOS4210_RNG,
246499ca137SKrzysztof Kozlowski .version_id = 1,
247499ca137SKrzysztof Kozlowski .minimum_version_id = 1,
248e4ea952fSRichard Henderson .fields = (const VMStateField[]) {
249499ca137SKrzysztof Kozlowski VMSTATE_INT32_ARRAY(randr_value, Exynos4210RngState,
250499ca137SKrzysztof Kozlowski EXYNOS4210_RNG_PRNG_NUM),
251499ca137SKrzysztof Kozlowski VMSTATE_UINT32(seed_set, Exynos4210RngState),
252499ca137SKrzysztof Kozlowski VMSTATE_UINT32(reg_status, Exynos4210RngState),
253499ca137SKrzysztof Kozlowski VMSTATE_UINT32(reg_control, Exynos4210RngState),
254499ca137SKrzysztof Kozlowski VMSTATE_END_OF_LIST()
255499ca137SKrzysztof Kozlowski }
256499ca137SKrzysztof Kozlowski };
257499ca137SKrzysztof Kozlowski
exynos4210_rng_class_init(ObjectClass * klass,const void * data)258*12d1a768SPhilippe Mathieu-Daudé static void exynos4210_rng_class_init(ObjectClass *klass, const void *data)
259499ca137SKrzysztof Kozlowski {
260499ca137SKrzysztof Kozlowski DeviceClass *dc = DEVICE_CLASS(klass);
261499ca137SKrzysztof Kozlowski
262e3d08143SPeter Maydell device_class_set_legacy_reset(dc, exynos4210_rng_reset);
263499ca137SKrzysztof Kozlowski dc->vmsd = &exynos4210_rng_vmstate;
264499ca137SKrzysztof Kozlowski }
265499ca137SKrzysztof Kozlowski
266499ca137SKrzysztof Kozlowski static const TypeInfo exynos4210_rng_info = {
267499ca137SKrzysztof Kozlowski .name = TYPE_EXYNOS4210_RNG,
268499ca137SKrzysztof Kozlowski .parent = TYPE_SYS_BUS_DEVICE,
269499ca137SKrzysztof Kozlowski .instance_size = sizeof(Exynos4210RngState),
270499ca137SKrzysztof Kozlowski .instance_init = exynos4210_rng_init,
271499ca137SKrzysztof Kozlowski .class_init = exynos4210_rng_class_init,
272499ca137SKrzysztof Kozlowski };
273499ca137SKrzysztof Kozlowski
exynos4210_rng_register(void)274499ca137SKrzysztof Kozlowski static void exynos4210_rng_register(void)
275499ca137SKrzysztof Kozlowski {
276499ca137SKrzysztof Kozlowski type_register_static(&exynos4210_rng_info);
277499ca137SKrzysztof Kozlowski }
278499ca137SKrzysztof Kozlowski
279499ca137SKrzysztof Kozlowski type_init(exynos4210_rng_register)
280