Add the 64-bit free running timer. Do not model the COMPARE register
(no IRQ generated).
This timer is used by Linux kernel and recently U-Boot:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/clocksource/bcm2835_timer.c?h=v3.7
https://github.com/u-boot/u-boot/blob/v2019.07/include/configs/rpi.h#L19
Datasheet used:
https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
---
v2:
- Add status/compare* registers
- Add vmstate and reset handler
checkpatch warning:
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
This is OK because the regex are:
F: hw/*/bcm283*
F: include/hw/*/bcm283*
---
hw/timer/Makefile.objs | 1 +
hw/timer/bcm2835_systmr.c | 166 ++++++++++++++++++++++++++++++
hw/timer/trace-events | 5 +
include/hw/timer/bcm2835_systmr.h | 33 ++++++
4 files changed, 205 insertions(+)
create mode 100644 hw/timer/bcm2835_systmr.c
create mode 100644 include/hw/timer/bcm2835_systmr.h
diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs
index 123d92c969..696cda5905 100644
--- a/hw/timer/Makefile.objs
+++ b/hw/timer/Makefile.objs
@@ -47,3 +47,4 @@ common-obj-$(CONFIG_SUN4V_RTC) += sun4v-rtc.o
common-obj-$(CONFIG_CMSDK_APB_TIMER) += cmsdk-apb-timer.o
common-obj-$(CONFIG_CMSDK_APB_DUALTIMER) += cmsdk-apb-dualtimer.o
common-obj-$(CONFIG_MSF2) += mss-timer.o
+common-obj-$(CONFIG_RASPI) += bcm2835_systmr.o
diff --git a/hw/timer/bcm2835_systmr.c b/hw/timer/bcm2835_systmr.c
new file mode 100644
index 0000000000..49b40b55f9
--- /dev/null
+++ b/hw/timer/bcm2835_systmr.c
@@ -0,0 +1,166 @@
+/*
+ * BCM2835 SYS timer emulation
+ *
+ * Copyright (C) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Datasheet: BCM2835 ARM Peripherals (C6357-M-1398)
+ * https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
+ *
+ * Only the free running 64-bit counter is implemented.
+ * The 4 COMPARE registers and the interruption are not implemented.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/timer.h"
+#include "hw/timer/bcm2835_systmr.h"
+#include "hw/registerfields.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+REG32(CTRL_STATUS, 0x00)
+REG32(COUNTER_LOW, 0x04)
+REG32(COUNTER_HIGH, 0x08)
+REG32(COMPARE0, 0x0c)
+REG32(COMPARE1, 0x10)
+REG32(COMPARE2, 0x14)
+REG32(COMPARE3, 0x18)
+
+static void bcm2835_systmr_update_irq(BCM2835SystemTimerState *s)
+{
+ bool enable = !!s->reg.status;
+
+ trace_bcm2835_systmr_irq(enable);
+ qemu_set_irq(s->irq, enable);
+}
+
+static void bcm2835_systmr_update_compare(BCM2835SystemTimerState *s,
+ unsigned timer_index)
+{
+ /* TODO fow now, since neither Linux nor U-boot use these timers. */
+ qemu_log_mask(LOG_UNIMP, "COMPARE register %u not implemented\n",
+ timer_index);
+}
+
+static uint64_t bcm2835_systmr_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
+ uint64_t r = 0;
+
+ switch (offset) {
+ case A_CTRL_STATUS:
+ r = s->reg.status;
+ break;
+ case A_COMPARE0 ... A_COMPARE3:
+ r = s->reg.compare[(offset - A_COMPARE0) >> 2];
+ break;
+ case A_COUNTER_LOW:
+ case A_COUNTER_HIGH:
+ /* Free running counter at 1MHz */
+ r = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
+ r >>= 8 * (offset - A_COUNTER_LOW);
+ r &= UINT32_MAX;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ }
+ trace_bcm2835_systmr_read(offset, r);
+
+ return r;
+}
+
+static void bcm2835_systmr_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
+
+ trace_bcm2835_systmr_write(offset, value);
+ switch (offset) {
+ case A_CTRL_STATUS:
+ s->reg.status &= ~value; /* Ack */
+ bcm2835_systmr_update_irq(s);
+ break;
+ case A_COMPARE0 ... A_COMPARE3:
+ s->reg.compare[(offset - A_COMPARE0) >> 2] = value;
+ bcm2835_systmr_update_compare(s, (offset - A_COMPARE0) >> 2);
+ break;
+ case A_COUNTER_LOW:
+ case A_COUNTER_HIGH:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only ofs 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps bcm2835_systmr_ops = {
+ .read = bcm2835_systmr_read,
+ .write = bcm2835_systmr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void bcm2835_systmr_reset(DeviceState *dev)
+{
+ BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
+
+ s->reg.status = 0;
+ for (size_t i = 0; i < ARRAY_SIZE(s->reg.compare); i++) {
+ s->reg.compare[i] = 0;
+ }
+}
+
+static void bcm2835_systmr_realize(DeviceState *dev, Error **errp)
+{
+ BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_systmr_ops,
+ s, "bcm2835-sys-timer", 0x20);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+}
+
+static const VMStateDescription bcm2835_systmr_vmstate = {
+ .name = "bcm2835_sys_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg.status, BCM2835SystemTimerState),
+ VMSTATE_UINT32_ARRAY(reg.compare, BCM2835SystemTimerState, 4),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_systmr_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = bcm2835_systmr_realize;
+ dc->reset = bcm2835_systmr_reset;
+ dc->vmsd = &bcm2835_systmr_vmstate;
+}
+
+static const TypeInfo bcm2835_systmr_info = {
+ .name = TYPE_BCM2835_SYSTIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835SystemTimerState),
+ .class_init = bcm2835_systmr_class_init,
+};
+
+static void bcm2835_systmr_register_types(void)
+{
+ type_register_static(&bcm2835_systmr_info);
+}
+
+type_init(bcm2835_systmr_register_types);
diff --git a/hw/timer/trace-events b/hw/timer/trace-events
index db02a9142c..0aa399ac69 100644
--- a/hw/timer/trace-events
+++ b/hw/timer/trace-events
@@ -87,3 +87,8 @@ pl031_read(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x"
pl031_write(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x"
pl031_alarm_raised(void) "alarm raised"
pl031_set_alarm(uint32_t ticks) "alarm set for %u ticks"
+
+# bcm2835_systmr.c
+bcm2835_systmr_irq(bool enable) "timer irq state %u"
+bcm2835_systmr_read(uint64_t offset, uint64_t data) "timer read: offset 0x%" PRIx64 " data 0x%" PRIx64
+bcm2835_systmr_write(uint64_t offset, uint64_t data) "timer write: offset 0x%" PRIx64 " data 0x%" PRIx64
diff --git a/include/hw/timer/bcm2835_systmr.h b/include/hw/timer/bcm2835_systmr.h
new file mode 100644
index 0000000000..c0bc5c8127
--- /dev/null
+++ b/include/hw/timer/bcm2835_systmr.h
@@ -0,0 +1,33 @@
+/*
+ * BCM2835 SYS timer emulation
+ *
+ * Copyright (c) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef BCM2835_SYSTIMER_H
+#define BCM2835_SYSTIMER_H
+
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+
+#define TYPE_BCM2835_SYSTIMER "bcm2835-sys-timer"
+#define BCM2835_SYSTIMER(obj) \
+ OBJECT_CHECK(BCM2835SystemTimerState, (obj), TYPE_BCM2835_SYSTIMER)
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ struct {
+ uint32_t status;
+ uint32_t compare[4];
+ } reg;
+} BCM2835SystemTimerState;
+
+#endif
--
2.21.0
On Sat, Oct 19, 2019 at 4:47 PM Philippe Mathieu-Daudé <f4bug@amsat.org> wrote:
>
> Add the 64-bit free running timer. Do not model the COMPARE register
> (no IRQ generated).
> This timer is used by Linux kernel and recently U-Boot:
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/clocksource/bcm2835_timer.c?h=v3.7
> https://github.com/u-boot/u-boot/blob/v2019.07/include/configs/rpi.h#L19
>
> Datasheet used:
> https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
>
> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
> ---
> v2:
> - Add status/compare* registers
> - Add vmstate and reset handler
>
> checkpatch warning:
> WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
> This is OK because the regex are:
>
> F: hw/*/bcm283*
> F: include/hw/*/bcm283*
> ---
> hw/timer/Makefile.objs | 1 +
> hw/timer/bcm2835_systmr.c | 166 ++++++++++++++++++++++++++++++
> hw/timer/trace-events | 5 +
> include/hw/timer/bcm2835_systmr.h | 33 ++++++
> 4 files changed, 205 insertions(+)
> create mode 100644 hw/timer/bcm2835_systmr.c
> create mode 100644 include/hw/timer/bcm2835_systmr.h
>
> diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs
> index 123d92c969..696cda5905 100644
> --- a/hw/timer/Makefile.objs
> +++ b/hw/timer/Makefile.objs
> @@ -47,3 +47,4 @@ common-obj-$(CONFIG_SUN4V_RTC) += sun4v-rtc.o
> common-obj-$(CONFIG_CMSDK_APB_TIMER) += cmsdk-apb-timer.o
> common-obj-$(CONFIG_CMSDK_APB_DUALTIMER) += cmsdk-apb-dualtimer.o
> common-obj-$(CONFIG_MSF2) += mss-timer.o
> +common-obj-$(CONFIG_RASPI) += bcm2835_systmr.o
> diff --git a/hw/timer/bcm2835_systmr.c b/hw/timer/bcm2835_systmr.c
> new file mode 100644
> index 0000000000..49b40b55f9
> --- /dev/null
> +++ b/hw/timer/bcm2835_systmr.c
> @@ -0,0 +1,166 @@
> +/*
> + * BCM2835 SYS timer emulation
> + *
> + * Copyright (C) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Datasheet: BCM2835 ARM Peripherals (C6357-M-1398)
> + * https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
> + *
> + * Only the free running 64-bit counter is implemented.
> + * The 4 COMPARE registers and the interruption are not implemented.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qemu/timer.h"
> +#include "hw/timer/bcm2835_systmr.h"
> +#include "hw/registerfields.h"
> +#include "migration/vmstate.h"
> +#include "trace.h"
> +
> +REG32(CTRL_STATUS, 0x00)
> +REG32(COUNTER_LOW, 0x04)
> +REG32(COUNTER_HIGH, 0x08)
> +REG32(COMPARE0, 0x0c)
> +REG32(COMPARE1, 0x10)
> +REG32(COMPARE2, 0x14)
> +REG32(COMPARE3, 0x18)
> +
> +static void bcm2835_systmr_update_irq(BCM2835SystemTimerState *s)
> +{
> + bool enable = !!s->reg.status;
> +
> + trace_bcm2835_systmr_irq(enable);
> + qemu_set_irq(s->irq, enable);
> +}
> +
> +static void bcm2835_systmr_update_compare(BCM2835SystemTimerState *s,
> + unsigned timer_index)
> +{
> + /* TODO fow now, since neither Linux nor U-boot use these timers. */
> + qemu_log_mask(LOG_UNIMP, "COMPARE register %u not implemented\n",
> + timer_index);
> +}
> +
> +static uint64_t bcm2835_systmr_read(void *opaque, hwaddr offset,
> + unsigned size)
> +{
> + BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
> + uint64_t r = 0;
> +
> + switch (offset) {
> + case A_CTRL_STATUS:
> + r = s->reg.status;
> + break;
> + case A_COMPARE0 ... A_COMPARE3:
> + r = s->reg.compare[(offset - A_COMPARE0) >> 2];
> + break;
> + case A_COUNTER_LOW:
> + case A_COUNTER_HIGH:
> + /* Free running counter at 1MHz */
> + r = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
> + r >>= 8 * (offset - A_COUNTER_LOW);
> + r &= UINT32_MAX;
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
> + __func__, offset);
> + break;
> + }
> + trace_bcm2835_systmr_read(offset, r);
> +
> + return r;
> +}
> +
> +static void bcm2835_systmr_write(void *opaque, hwaddr offset,
> + uint64_t value, unsigned size)
> +{
> + BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
> +
> + trace_bcm2835_systmr_write(offset, value);
> + switch (offset) {
> + case A_CTRL_STATUS:
> + s->reg.status &= ~value; /* Ack */
> + bcm2835_systmr_update_irq(s);
> + break;
> + case A_COMPARE0 ... A_COMPARE3:
> + s->reg.compare[(offset - A_COMPARE0) >> 2] = value;
> + bcm2835_systmr_update_compare(s, (offset - A_COMPARE0) >> 2);
> + break;
> + case A_COUNTER_LOW:
> + case A_COUNTER_HIGH:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only ofs 0x%" HWADDR_PRIx "\n",
> + __func__, offset);
> + break;
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
> + __func__, offset);
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps bcm2835_systmr_ops = {
> + .read = bcm2835_systmr_read,
> + .write = bcm2835_systmr_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .impl = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void bcm2835_systmr_reset(DeviceState *dev)
> +{
> + BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
> +
> + s->reg.status = 0;
> + for (size_t i = 0; i < ARRAY_SIZE(s->reg.compare); i++) {
> + s->reg.compare[i] = 0;
> + }
Can we use meset (or the glib version) here instead?
Otherwise:
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> +}
> +
> +static void bcm2835_systmr_realize(DeviceState *dev, Error **errp)
> +{
> + BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
> +
> + memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_systmr_ops,
> + s, "bcm2835-sys-timer", 0x20);
> + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
> + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
> +}
> +
> +static const VMStateDescription bcm2835_systmr_vmstate = {
> + .name = "bcm2835_sys_timer",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT32(reg.status, BCM2835SystemTimerState),
> + VMSTATE_UINT32_ARRAY(reg.compare, BCM2835SystemTimerState, 4),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void bcm2835_systmr_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + dc->realize = bcm2835_systmr_realize;
> + dc->reset = bcm2835_systmr_reset;
> + dc->vmsd = &bcm2835_systmr_vmstate;
> +}
> +
> +static const TypeInfo bcm2835_systmr_info = {
> + .name = TYPE_BCM2835_SYSTIMER,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(BCM2835SystemTimerState),
> + .class_init = bcm2835_systmr_class_init,
> +};
> +
> +static void bcm2835_systmr_register_types(void)
> +{
> + type_register_static(&bcm2835_systmr_info);
> +}
> +
> +type_init(bcm2835_systmr_register_types);
> diff --git a/hw/timer/trace-events b/hw/timer/trace-events
> index db02a9142c..0aa399ac69 100644
> --- a/hw/timer/trace-events
> +++ b/hw/timer/trace-events
> @@ -87,3 +87,8 @@ pl031_read(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x"
> pl031_write(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x"
> pl031_alarm_raised(void) "alarm raised"
> pl031_set_alarm(uint32_t ticks) "alarm set for %u ticks"
> +
> +# bcm2835_systmr.c
> +bcm2835_systmr_irq(bool enable) "timer irq state %u"
> +bcm2835_systmr_read(uint64_t offset, uint64_t data) "timer read: offset 0x%" PRIx64 " data 0x%" PRIx64
> +bcm2835_systmr_write(uint64_t offset, uint64_t data) "timer write: offset 0x%" PRIx64 " data 0x%" PRIx64
> diff --git a/include/hw/timer/bcm2835_systmr.h b/include/hw/timer/bcm2835_systmr.h
> new file mode 100644
> index 0000000000..c0bc5c8127
> --- /dev/null
> +++ b/include/hw/timer/bcm2835_systmr.h
> @@ -0,0 +1,33 @@
> +/*
> + * BCM2835 SYS timer emulation
> + *
> + * Copyright (c) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef BCM2835_SYSTIMER_H
> +#define BCM2835_SYSTIMER_H
> +
> +#include "hw/sysbus.h"
> +#include "hw/irq.h"
> +
> +#define TYPE_BCM2835_SYSTIMER "bcm2835-sys-timer"
> +#define BCM2835_SYSTIMER(obj) \
> + OBJECT_CHECK(BCM2835SystemTimerState, (obj), TYPE_BCM2835_SYSTIMER)
> +
> +typedef struct {
> + /*< private >*/
> + SysBusDevice parent_obj;
> +
> + /*< public >*/
> + MemoryRegion iomem;
> + qemu_irq irq;
> +
> + struct {
> + uint32_t status;
> + uint32_t compare[4];
> + } reg;
> +} BCM2835SystemTimerState;
> +
> +#endif
> --
> 2.21.0
>
>
On 10/21/19 10:31 PM, Alistair Francis wrote:
> On Sat, Oct 19, 2019 at 4:47 PM Philippe Mathieu-Daudé <f4bug@amsat.org> wrote:
>>
>> Add the 64-bit free running timer. Do not model the COMPARE register
>> (no IRQ generated).
>> This timer is used by Linux kernel and recently U-Boot:
>> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/clocksource/bcm2835_timer.c?h=v3.7
>> https://github.com/u-boot/u-boot/blob/v2019.07/include/configs/rpi.h#L19
>>
>> Datasheet used:
>> https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
>>
>> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
>> ---
>> v2:
>> - Add status/compare* registers
>> - Add vmstate and reset handler
>>
>> checkpatch warning:
>> WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
>> This is OK because the regex are:
>>
>> F: hw/*/bcm283*
>> F: include/hw/*/bcm283*
>> ---
>> hw/timer/Makefile.objs | 1 +
>> hw/timer/bcm2835_systmr.c | 166 ++++++++++++++++++++++++++++++
>> hw/timer/trace-events | 5 +
>> include/hw/timer/bcm2835_systmr.h | 33 ++++++
>> 4 files changed, 205 insertions(+)
>> create mode 100644 hw/timer/bcm2835_systmr.c
>> create mode 100644 include/hw/timer/bcm2835_systmr.h
>>
>> diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs
>> index 123d92c969..696cda5905 100644
>> --- a/hw/timer/Makefile.objs
>> +++ b/hw/timer/Makefile.objs
>> @@ -47,3 +47,4 @@ common-obj-$(CONFIG_SUN4V_RTC) += sun4v-rtc.o
>> common-obj-$(CONFIG_CMSDK_APB_TIMER) += cmsdk-apb-timer.o
>> common-obj-$(CONFIG_CMSDK_APB_DUALTIMER) += cmsdk-apb-dualtimer.o
>> common-obj-$(CONFIG_MSF2) += mss-timer.o
>> +common-obj-$(CONFIG_RASPI) += bcm2835_systmr.o
>> diff --git a/hw/timer/bcm2835_systmr.c b/hw/timer/bcm2835_systmr.c
>> new file mode 100644
>> index 0000000000..49b40b55f9
>> --- /dev/null
>> +++ b/hw/timer/bcm2835_systmr.c
>> @@ -0,0 +1,166 @@
>> +/*
>> + * BCM2835 SYS timer emulation
>> + *
>> + * Copyright (C) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + *
>> + * Datasheet: BCM2835 ARM Peripherals (C6357-M-1398)
>> + * https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
>> + *
>> + * Only the free running 64-bit counter is implemented.
>> + * The 4 COMPARE registers and the interruption are not implemented.
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qemu/log.h"
>> +#include "qemu/timer.h"
>> +#include "hw/timer/bcm2835_systmr.h"
>> +#include "hw/registerfields.h"
>> +#include "migration/vmstate.h"
>> +#include "trace.h"
>> +
>> +REG32(CTRL_STATUS, 0x00)
>> +REG32(COUNTER_LOW, 0x04)
>> +REG32(COUNTER_HIGH, 0x08)
>> +REG32(COMPARE0, 0x0c)
>> +REG32(COMPARE1, 0x10)
>> +REG32(COMPARE2, 0x14)
>> +REG32(COMPARE3, 0x18)
>> +
>> +static void bcm2835_systmr_update_irq(BCM2835SystemTimerState *s)
>> +{
>> + bool enable = !!s->reg.status;
>> +
>> + trace_bcm2835_systmr_irq(enable);
>> + qemu_set_irq(s->irq, enable);
>> +}
>> +
>> +static void bcm2835_systmr_update_compare(BCM2835SystemTimerState *s,
>> + unsigned timer_index)
>> +{
>> + /* TODO fow now, since neither Linux nor U-boot use these timers. */
>> + qemu_log_mask(LOG_UNIMP, "COMPARE register %u not implemented\n",
>> + timer_index);
>> +}
>> +
>> +static uint64_t bcm2835_systmr_read(void *opaque, hwaddr offset,
>> + unsigned size)
>> +{
>> + BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
>> + uint64_t r = 0;
>> +
>> + switch (offset) {
>> + case A_CTRL_STATUS:
>> + r = s->reg.status;
>> + break;
>> + case A_COMPARE0 ... A_COMPARE3:
>> + r = s->reg.compare[(offset - A_COMPARE0) >> 2];
>> + break;
>> + case A_COUNTER_LOW:
>> + case A_COUNTER_HIGH:
>> + /* Free running counter at 1MHz */
>> + r = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
>> + r >>= 8 * (offset - A_COUNTER_LOW);
>> + r &= UINT32_MAX;
>> + break;
>> + default:
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
>> + __func__, offset);
>> + break;
>> + }
>> + trace_bcm2835_systmr_read(offset, r);
>> +
>> + return r;
>> +}
>> +
>> +static void bcm2835_systmr_write(void *opaque, hwaddr offset,
>> + uint64_t value, unsigned size)
>> +{
>> + BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
>> +
>> + trace_bcm2835_systmr_write(offset, value);
>> + switch (offset) {
>> + case A_CTRL_STATUS:
>> + s->reg.status &= ~value; /* Ack */
>> + bcm2835_systmr_update_irq(s);
>> + break;
>> + case A_COMPARE0 ... A_COMPARE3:
>> + s->reg.compare[(offset - A_COMPARE0) >> 2] = value;
>> + bcm2835_systmr_update_compare(s, (offset - A_COMPARE0) >> 2);
>> + break;
>> + case A_COUNTER_LOW:
>> + case A_COUNTER_HIGH:
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only ofs 0x%" HWADDR_PRIx "\n",
>> + __func__, offset);
>> + break;
>> + default:
>> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
>> + __func__, offset);
>> + break;
>> + }
>> +}
>> +
>> +static const MemoryRegionOps bcm2835_systmr_ops = {
>> + .read = bcm2835_systmr_read,
>> + .write = bcm2835_systmr_write,
>> + .endianness = DEVICE_LITTLE_ENDIAN,
>> + .impl = {
>> + .min_access_size = 4,
>> + .max_access_size = 4,
>> + },
>> +};
>> +
>> +static void bcm2835_systmr_reset(DeviceState *dev)
>> +{
>> + BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
>> +
>> + s->reg.status = 0;
>> + for (size_t i = 0; i < ARRAY_SIZE(s->reg.compare); i++) {
>> + s->reg.compare[i] = 0;
>> + }
>
> Can we use meset (or the glib version) here instead?
My first thought was "I'd rather not" because I thought the
COUNTER registers weren't reinitialized on reset, but they
are, so OK. (there is no glib memset AFAIK).
>
> Otherwise:
>
> Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Thanks for reviewing this series!
>
> Alistair
>
>> +}
>> +
>> +static void bcm2835_systmr_realize(DeviceState *dev, Error **errp)
>> +{
>> + BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
>> +
>> + memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_systmr_ops,
>> + s, "bcm2835-sys-timer", 0x20);
>> + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
>> + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
>> +}
>> +
>> +static const VMStateDescription bcm2835_systmr_vmstate = {
>> + .name = "bcm2835_sys_timer",
>> + .version_id = 1,
>> + .minimum_version_id = 1,
>> + .fields = (VMStateField[]) {
>> + VMSTATE_UINT32(reg.status, BCM2835SystemTimerState),
>> + VMSTATE_UINT32_ARRAY(reg.compare, BCM2835SystemTimerState, 4),
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static void bcm2835_systmr_class_init(ObjectClass *klass, void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> + dc->realize = bcm2835_systmr_realize;
>> + dc->reset = bcm2835_systmr_reset;
>> + dc->vmsd = &bcm2835_systmr_vmstate;
>> +}
>> +
>> +static const TypeInfo bcm2835_systmr_info = {
>> + .name = TYPE_BCM2835_SYSTIMER,
>> + .parent = TYPE_SYS_BUS_DEVICE,
>> + .instance_size = sizeof(BCM2835SystemTimerState),
>> + .class_init = bcm2835_systmr_class_init,
>> +};
>> +
>> +static void bcm2835_systmr_register_types(void)
>> +{
>> + type_register_static(&bcm2835_systmr_info);
>> +}
>> +
>> +type_init(bcm2835_systmr_register_types);
>> diff --git a/hw/timer/trace-events b/hw/timer/trace-events
>> index db02a9142c..0aa399ac69 100644
>> --- a/hw/timer/trace-events
>> +++ b/hw/timer/trace-events
>> @@ -87,3 +87,8 @@ pl031_read(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x"
>> pl031_write(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x"
>> pl031_alarm_raised(void) "alarm raised"
>> pl031_set_alarm(uint32_t ticks) "alarm set for %u ticks"
>> +
>> +# bcm2835_systmr.c
>> +bcm2835_systmr_irq(bool enable) "timer irq state %u"
>> +bcm2835_systmr_read(uint64_t offset, uint64_t data) "timer read: offset 0x%" PRIx64 " data 0x%" PRIx64
>> +bcm2835_systmr_write(uint64_t offset, uint64_t data) "timer write: offset 0x%" PRIx64 " data 0x%" PRIx64
>> diff --git a/include/hw/timer/bcm2835_systmr.h b/include/hw/timer/bcm2835_systmr.h
>> new file mode 100644
>> index 0000000000..c0bc5c8127
>> --- /dev/null
>> +++ b/include/hw/timer/bcm2835_systmr.h
>> @@ -0,0 +1,33 @@
>> +/*
>> + * BCM2835 SYS timer emulation
>> + *
>> + * Copyright (c) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef BCM2835_SYSTIMER_H
>> +#define BCM2835_SYSTIMER_H
>> +
>> +#include "hw/sysbus.h"
>> +#include "hw/irq.h"
>> +
>> +#define TYPE_BCM2835_SYSTIMER "bcm2835-sys-timer"
>> +#define BCM2835_SYSTIMER(obj) \
>> + OBJECT_CHECK(BCM2835SystemTimerState, (obj), TYPE_BCM2835_SYSTIMER)
>> +
>> +typedef struct {
>> + /*< private >*/
>> + SysBusDevice parent_obj;
>> +
>> + /*< public >*/
>> + MemoryRegion iomem;
>> + qemu_irq irq;
>> +
>> + struct {
>> + uint32_t status;
>> + uint32_t compare[4];
>> + } reg;
>> +} BCM2835SystemTimerState;
>> +
>> +#endif
>> --
>> 2.21.0
>>
>>
© 2016 - 2025 Red Hat, Inc.