From 54a5ba13a9ffe7d25fc649b0fa9f8314734c8ccc Mon Sep 17 00:00:00 2001 From: Marcin Chojnacki Date: Tue, 28 Feb 2017 12:08:13 +0000 Subject: target-arm: Implement BCM2835 hardware RNG Recent vanilla Raspberry Pi kernels started to make use of the hardware random number generator in BCM2835 SoC. As a result, those kernels wouldn't work anymore under QEMU but rather just freeze during the boot process. This patch implements a trivial BCM2835 compatible RNG, and adds it as a peripheral to BCM2835 platform, which allows to boot a vanilla Raspberry Pi kernel under Qemu. Changes since v1: * Prevented guest from writing [31..20] bits in rng_status * Removed redundant minimum_version_id_old * Added field entries for the state * Changed realize function to reset Signed-off-by: Marcin Chojnacki Message-id: 20170210210857.47893-1-marcinch7@gmail.com Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/misc/Makefile.objs | 1 + hw/misc/bcm2835_rng.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 hw/misc/bcm2835_rng.c (limited to 'hw/misc') diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index 898e4ccfb1..57a4406581 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -42,6 +42,7 @@ obj-$(CONFIG_OMAP) += omap_sdrc.o obj-$(CONFIG_OMAP) += omap_tap.o obj-$(CONFIG_RASPI) += bcm2835_mbox.o obj-$(CONFIG_RASPI) += bcm2835_property.o +obj-$(CONFIG_RASPI) += bcm2835_rng.o obj-$(CONFIG_SLAVIO) += slavio_misc.o obj-$(CONFIG_ZYNQ) += zynq_slcr.o obj-$(CONFIG_ZYNQ) += zynq-xadc.o diff --git a/hw/misc/bcm2835_rng.c b/hw/misc/bcm2835_rng.c new file mode 100644 index 0000000000..2242bc5eb5 --- /dev/null +++ b/hw/misc/bcm2835_rng.c @@ -0,0 +1,124 @@ +/* + * BCM2835 Random Number Generator emulation + * + * Copyright (C) 2017 Marcin Chojnacki + * + * 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 "hw/misc/bcm2835_rng.h" + +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 = rand(); + 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) -- cgit v1.2.3 From 373442ea3a96249991cfad3fc0e83fca5f8558d5 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Tue, 28 Feb 2017 12:08:14 +0000 Subject: bcm2835_rng: Use qcrypto_random_bytes() rather than rand() Switch to using qcrypto_random_bytes() rather than rand() as our source of randomness for the BCM2835 RNG. If qcrypto_random_bytes() fails, 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 a fatal error. This shouldn't happen unless something's broken, though. 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, and doesn't really give a very nice diagnostic to the user since the guest will just seem to hang. Signed-off-by: Peter Maydell Reviewed-by: Daniel P. Berrange --- hw/misc/bcm2835_rng.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'hw/misc') diff --git a/hw/misc/bcm2835_rng.c b/hw/misc/bcm2835_rng.c index 2242bc5eb5..4d62143b24 100644 --- a/hw/misc/bcm2835_rng.c +++ b/hw/misc/bcm2835_rng.c @@ -9,8 +9,33 @@ #include "qemu/osdep.h" #include "qemu/log.h" +#include "qapi/error.h" +#include "crypto/random.h" #include "hw/misc/bcm2835_rng.h" +static uint32_t get_random_bytes(void) +{ + uint32_t res; + Error *err = NULL; + + if (qcrypto_random_bytes((uint8_t *)&res, sizeof(res), &err) < 0) { + /* 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. + */ + error_report_err(err); + exit(1); + } + return res; +} + static uint64_t bcm2835_rng_read(void *opaque, hwaddr offset, unsigned size) { @@ -27,7 +52,7 @@ static uint64_t bcm2835_rng_read(void *opaque, hwaddr offset, res = s->rng_status | (1 << 24); break; case 0x8: /* rng_data */ - res = rand(); + res = get_random_bytes(); break; default: -- cgit v1.2.3 From 1e0228fd20aa46ac1f02cffee946cdd4ffaf8b96 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 28 Feb 2017 12:08:20 +0000 Subject: hw/arm/exynos: Fix Linux kernel division by zero for PLLs Without any clock controller, the Linux kernel was hitting division by zero during boot or with clk_summary: [ 0.000000] [] (unwind_backtrace) from [] (show_stack+0x10/0x14) [ 0.000000] [] (show_stack) from [] (dump_stack+0x88/0x9c) [ 0.000000] [] (dump_stack) from [] (Ldiv0+0x8/0x10) [ 0.000000] [] (Ldiv0) from [] (samsung_pll45xx_recalc_rate+0x58/0x74) [ 0.000000] [] (samsung_pll45xx_recalc_rate) from [] (clk_register+0x39c/0x63c) [ 0.000000] [] (clk_register) from [] (samsung_clk_register_pll+0x2e0/0x3d4) [ 0.000000] [] (samsung_clk_register_pll) from [] (exynos4_clk_init+0x1b0/0x5e4) [ 0.000000] [] (exynos4_clk_init) from [] (of_clk_init+0x17c/0x210) [ 0.000000] [] (of_clk_init) from [] (time_init+0x24/0x2c) [ 0.000000] [] (time_init) from [] (start_kernel+0x24c/0x38c) [ 0.000000] [] (start_kernel) from [<4020807c>] (0x4020807c) Provide stub for clock controller returning reset values for PLLs. Signed-off-by: Krzysztof Kozlowski Message-id: 20170226200142.31169-1-krzk@kernel.org Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/misc/Makefile.objs | 2 +- hw/misc/exynos4210_clk.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 hw/misc/exynos4210_clk.c (limited to 'hw/misc') diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index 57a4406581..c8b489390f 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -26,7 +26,7 @@ obj-$(CONFIG_IVSHMEM) += ivshmem.o obj-$(CONFIG_REALVIEW) += arm_sysctl.o obj-$(CONFIG_NSERIES) += cbus.o obj-$(CONFIG_ECCMEMCTL) += eccmemctl.o -obj-$(CONFIG_EXYNOS4) += exynos4210_pmu.o +obj-$(CONFIG_EXYNOS4) += exynos4210_pmu.o exynos4210_clk.o obj-$(CONFIG_IMX) += imx_ccm.o obj-$(CONFIG_IMX) += imx31_ccm.o obj-$(CONFIG_IMX) += imx25_ccm.o diff --git a/hw/misc/exynos4210_clk.c b/hw/misc/exynos4210_clk.c new file mode 100644 index 0000000000..81862c0ada --- /dev/null +++ b/hw/misc/exynos4210_clk.c @@ -0,0 +1,164 @@ +/* + * Exynos4210 Clock Controller Emulation + * + * Copyright (c) 2017 Krzysztof Kozlowski + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "qemu/log.h" + +#define TYPE_EXYNOS4210_CLK "exynos4210.clk" +#define EXYNOS4210_CLK(obj) \ + OBJECT_CHECK(Exynos4210ClkState, (obj), TYPE_EXYNOS4210_CLK) + +#define CLK_PLL_LOCKED BIT(29) + +#define EXYNOS4210_CLK_REGS_MEM_SIZE 0x15104 + +typedef struct Exynos4210Reg { + const char *name; /* for debug only */ + uint32_t offset; + uint32_t reset_value; +} Exynos4210Reg; + +/* Clock controller register base: 0x10030000 */ +static const Exynos4210Reg exynos4210_clk_regs[] = { + {"EPLL_LOCK", 0xc010, 0x00000fff}, + {"VPLL_LOCK", 0xc020, 0x00000fff}, + {"EPLL_CON0", 0xc110, 0x00300301 | CLK_PLL_LOCKED}, + {"EPLL_CON1", 0xc114, 0x00000000}, + {"VPLL_CON0", 0xc120, 0x00240201 | CLK_PLL_LOCKED}, + {"VPLL_CON1", 0xc124, 0x66010464}, + {"APLL_LOCK", 0x14000, 0x00000fff}, + {"MPLL_LOCK", 0x14004, 0x00000fff}, + {"APLL_CON0", 0x14100, 0x00c80601 | CLK_PLL_LOCKED}, + {"APLL_CON1", 0x14104, 0x0000001c}, + {"MPLL_CON0", 0x14108, 0x00c80601 | CLK_PLL_LOCKED}, + {"MPLL_CON1", 0x1410c, 0x0000001c}, +}; + +#define EXYNOS4210_REGS_NUM ARRAY_SIZE(exynos4210_clk_regs) + +typedef struct Exynos4210ClkState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t reg[EXYNOS4210_REGS_NUM]; +} Exynos4210ClkState; + +static uint64_t exynos4210_clk_read(void *opaque, hwaddr offset, + unsigned size) +{ + const Exynos4210ClkState *s = (Exynos4210ClkState *)opaque; + const Exynos4210Reg *regs = exynos4210_clk_regs; + unsigned int i; + + for (i = 0; i < EXYNOS4210_REGS_NUM; i++) { + if (regs->offset == offset) { + return s->reg[i]; + } + regs++; + } + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read offset 0x%04x\n", + __func__, (uint32_t)offset); + return 0; +} + +static void exynos4210_clk_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + Exynos4210ClkState *s = (Exynos4210ClkState *)opaque; + const Exynos4210Reg *regs = exynos4210_clk_regs; + unsigned int i; + + for (i = 0; i < EXYNOS4210_REGS_NUM; i++) { + if (regs->offset == offset) { + s->reg[i] = val; + return; + } + regs++; + } + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write offset 0x%04x\n", + __func__, (uint32_t)offset); +} + +static const MemoryRegionOps exynos4210_clk_ops = { + .read = exynos4210_clk_read, + .write = exynos4210_clk_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false + } +}; + +static void exynos4210_clk_reset(DeviceState *dev) +{ + Exynos4210ClkState *s = EXYNOS4210_CLK(dev); + unsigned int i; + + /* Set default values for registers */ + for (i = 0; i < EXYNOS4210_REGS_NUM; i++) { + s->reg[i] = exynos4210_clk_regs[i].reset_value; + } +} + +static void exynos4210_clk_init(Object *obj) +{ + Exynos4210ClkState *s = EXYNOS4210_CLK(obj); + SysBusDevice *dev = SYS_BUS_DEVICE(obj); + + /* memory mapping */ + memory_region_init_io(&s->iomem, obj, &exynos4210_clk_ops, s, + TYPE_EXYNOS4210_CLK, EXYNOS4210_CLK_REGS_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); +} + +static const VMStateDescription exynos4210_clk_vmstate = { + .name = TYPE_EXYNOS4210_CLK, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(reg, Exynos4210ClkState, EXYNOS4210_REGS_NUM), + VMSTATE_END_OF_LIST() + } +}; + +static void exynos4210_clk_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = exynos4210_clk_reset; + dc->vmsd = &exynos4210_clk_vmstate; +} + +static const TypeInfo exynos4210_clk_info = { + .name = TYPE_EXYNOS4210_CLK, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210ClkState), + .instance_init = exynos4210_clk_init, + .class_init = exynos4210_clk_class_init, +}; + +static void exynos4210_clk_register(void) +{ + qemu_log_mask(LOG_GUEST_ERROR, "Clock init\n"); + type_register_static(&exynos4210_clk_info); +} + +type_init(exynos4210_clk_register) -- cgit v1.2.3