diff options
Diffstat (limited to 'hw/timer/sse-counter.c')
-rw-r--r-- | hw/timer/sse-counter.c | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/hw/timer/sse-counter.c b/hw/timer/sse-counter.c new file mode 100644 index 0000000000..0384051f15 --- /dev/null +++ b/hw/timer/sse-counter.c @@ -0,0 +1,474 @@ +/* + * Arm SSE Subsystem System Counter + * + * Copyright (c) 2020 Linaro Limited + * Written by Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or + * (at your option) any later version. + */ + +/* + * This is a model of the "System counter" which is documented in + * the Arm SSE-123 Example Subsystem Technical Reference Manual: + * https://developer.arm.com/documentation/101370/latest/ + * + * The system counter is a non-stop 64-bit up-counter. It provides + * this count value to other devices like the SSE system timer, + * which are driven by this system timestamp rather than directly + * from a clock. Internally to the counter the count is actually + * 88-bit precision (64.24 fixed point), with a programmable scale factor. + * + * The hardware has the optional feature that it supports dynamic + * clock switching, where two clock inputs are connected, and which + * one is used is selected via a CLKSEL input signal. Since the + * users of this device in QEMU don't use this feature, we only model + * the HWCLKSW=0 configuration. + */ +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "trace.h" +#include "hw/timer/sse-counter.h" +#include "hw/sysbus.h" +#include "hw/irq.h" +#include "hw/registerfields.h" +#include "hw/clock.h" +#include "hw/qdev-clock.h" +#include "migration/vmstate.h" + +/* Registers in the control frame */ +REG32(CNTCR, 0x0) + FIELD(CNTCR, EN, 0, 1) + FIELD(CNTCR, HDBG, 1, 1) + FIELD(CNTCR, SCEN, 2, 1) + FIELD(CNTCR, INTRMASK, 3, 1) + FIELD(CNTCR, PSLVERRDIS, 4, 1) + FIELD(CNTCR, INTRCLR, 5, 1) +/* + * Although CNTCR defines interrupt-related bits, the counter doesn't + * appear to actually have an interrupt output. So INTRCLR is + * effectively a RAZ/WI bit, as are the reserved bits [31:6]. + */ +#define CNTCR_VALID_MASK (R_CNTCR_EN_MASK | R_CNTCR_HDBG_MASK | \ + R_CNTCR_SCEN_MASK | R_CNTCR_INTRMASK_MASK | \ + R_CNTCR_PSLVERRDIS_MASK) +REG32(CNTSR, 0x4) +REG32(CNTCV_LO, 0x8) +REG32(CNTCV_HI, 0xc) +REG32(CNTSCR, 0x10) /* Aliased with CNTSCR0 */ +REG32(CNTID, 0x1c) + FIELD(CNTID, CNTSC, 0, 4) + FIELD(CNTID, CNTCS, 16, 1) + FIELD(CNTID, CNTSELCLK, 17, 2) + FIELD(CNTID, CNTSCR_OVR, 19, 1) +REG32(CNTSCR0, 0xd0) +REG32(CNTSCR1, 0xd4) + +/* Registers in the status frame */ +REG32(STATUS_CNTCV_LO, 0x0) +REG32(STATUS_CNTCV_HI, 0x4) + +/* Standard ID registers, present in both frames */ +REG32(PID4, 0xFD0) +REG32(PID5, 0xFD4) +REG32(PID6, 0xFD8) +REG32(PID7, 0xFDC) +REG32(PID0, 0xFE0) +REG32(PID1, 0xFE4) +REG32(PID2, 0xFE8) +REG32(PID3, 0xFEC) +REG32(CID0, 0xFF0) +REG32(CID1, 0xFF4) +REG32(CID2, 0xFF8) +REG32(CID3, 0xFFC) + +/* PID/CID values */ +static const int control_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0xba, 0xb0, 0x0b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + +static const int status_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0xbb, 0xb0, 0x0b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + +static void sse_counter_notify_users(SSECounter *s) +{ + /* + * Notify users of the count timestamp that they may + * need to recalculate. + */ + notifier_list_notify(&s->notifier_list, NULL); +} + +static bool sse_counter_enabled(SSECounter *s) +{ + return (s->cntcr & R_CNTCR_EN_MASK) != 0; +} + +uint64_t sse_counter_tick_to_time(SSECounter *s, uint64_t tick) +{ + if (!sse_counter_enabled(s)) { + return UINT64_MAX; + } + + tick -= s->ticks_then; + + if (s->cntcr & R_CNTCR_SCEN_MASK) { + /* Adjust the tick count to account for the scale factor */ + tick = muldiv64(tick, 0x01000000, s->cntscr0); + } + + return s->ns_then + clock_ticks_to_ns(s->clk, tick); +} + +void sse_counter_register_consumer(SSECounter *s, Notifier *notifier) +{ + /* + * For the moment we assume that both we and the devices + * which consume us last for the life of the simulation, + * and so there is no mechanism for removing a notifier. + */ + notifier_list_add(&s->notifier_list, notifier); +} + +uint64_t sse_counter_for_timestamp(SSECounter *s, uint64_t now) +{ + /* Return the CNTCV value for a particular timestamp (clock ns value). */ + uint64_t ticks; + + if (!sse_counter_enabled(s)) { + /* Counter is disabled and does not increment */ + return s->ticks_then; + } + + ticks = clock_ns_to_ticks(s->clk, now - s->ns_then); + if (s->cntcr & R_CNTCR_SCEN_MASK) { + /* + * Scaling is enabled. The CNTSCR value is the amount added to + * the underlying 88-bit counter for every tick of the + * underlying clock; CNTCV is the top 64 bits of that full + * 88-bit value. Multiplying the tick count by CNTSCR tells us + * how much the full 88-bit counter has moved on; we then + * divide that by 0x01000000 to find out how much the 64-bit + * visible portion has advanced. muldiv64() gives us the + * necessary at-least-88-bit precision for the intermediate + * result. + */ + ticks = muldiv64(ticks, s->cntscr0, 0x01000000); + } + return s->ticks_then + ticks; +} + +static uint64_t sse_cntcv(SSECounter *s) +{ + /* Return the CNTCV value for the current time */ + return sse_counter_for_timestamp(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); +} + +static void sse_write_cntcv(SSECounter *s, uint32_t value, unsigned startbit) +{ + /* + * Write one 32-bit half of the counter value; startbit is the + * bit position of this half in the 64-bit word, either 0 or 32. + */ + uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + uint64_t cntcv = sse_counter_for_timestamp(s, now); + + cntcv = deposit64(cntcv, startbit, 32, value); + s->ticks_then = cntcv; + s->ns_then = now; + sse_counter_notify_users(s); +} + +static uint64_t sse_counter_control_read(void *opaque, hwaddr offset, + unsigned size) +{ + SSECounter *s = SSE_COUNTER(opaque); + uint64_t r; + + switch (offset) { + case A_CNTCR: + r = s->cntcr; + break; + case A_CNTSR: + /* + * The only bit here is DBGH, indicating that the counter has been + * halted via the Halt-on-Debug signal. We don't implement halting + * debug, so the whole register always reads as zero. + */ + r = 0; + break; + case A_CNTCV_LO: + r = extract64(sse_cntcv(s), 0, 32); + break; + case A_CNTCV_HI: + r = extract64(sse_cntcv(s), 32, 32); + break; + case A_CNTID: + /* + * For our implementation: + * - CNTSCR can only be written when CNTCR.EN == 0 + * - HWCLKSW=0, so selected clock is always CLK0 + * - counter scaling is implemented + */ + r = (1 << R_CNTID_CNTSELCLK_SHIFT) | (1 << R_CNTID_CNTSC_SHIFT); + break; + case A_CNTSCR: + case A_CNTSCR0: + r = s->cntscr0; + break; + case A_CNTSCR1: + /* If HWCLKSW == 0, CNTSCR1 is RAZ/WI */ + r = 0; + break; + case A_PID4 ... A_CID3: + r = control_id[(offset - A_PID4) / 4]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter control frame read: bad offset 0x%x", + (unsigned)offset); + r = 0; + break; + } + + trace_sse_counter_control_read(offset, r, size); + return r; +} + +static void sse_counter_control_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + SSECounter *s = SSE_COUNTER(opaque); + + trace_sse_counter_control_write(offset, value, size); + + switch (offset) { + case A_CNTCR: + /* + * Although CNTCR defines interrupt-related bits, the counter doesn't + * appear to actually have an interrupt output. So INTRCLR is + * effectively a RAZ/WI bit, as are the reserved bits [31:6]. + * The documentation does not explicitly say so, but we assume + * that changing the scale factor while the counter is enabled + * by toggling CNTCR.SCEN has the same behaviour (making the counter + * value UNKNOWN) as changing it by writing to CNTSCR, and so we + * don't need to try to recalculate for that case. + */ + value &= CNTCR_VALID_MASK; + if ((value ^ s->cntcr) & R_CNTCR_EN_MASK) { + /* + * Whether the counter is being enabled or disabled, the + * required action is the same: sync the (ns_then, ticks_then) + * tuple. + */ + uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ticks_then = sse_counter_for_timestamp(s, now); + s->ns_then = now; + sse_counter_notify_users(s); + } + s->cntcr = value; + break; + case A_CNTCV_LO: + sse_write_cntcv(s, value, 0); + break; + case A_CNTCV_HI: + sse_write_cntcv(s, value, 32); + break; + case A_CNTSCR: + case A_CNTSCR0: + /* + * If the scale registers are changed when the counter is enabled, + * the count value becomes UNKNOWN. So we don't try to recalculate + * anything here but only do it on a write to CNTCR.EN. + */ + s->cntscr0 = value; + break; + case A_CNTSCR1: + /* If HWCLKSW == 0, CNTSCR1 is RAZ/WI */ + break; + case A_CNTSR: + case A_CNTID: + case A_PID4 ... A_CID3: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter control frame: write to RO offset 0x%x\n", + (unsigned)offset); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter control frame: write to bad offset 0x%x\n", + (unsigned)offset); + break; + } +} + +static uint64_t sse_counter_status_read(void *opaque, hwaddr offset, + unsigned size) +{ + SSECounter *s = SSE_COUNTER(opaque); + uint64_t r; + + switch (offset) { + case A_STATUS_CNTCV_LO: + r = extract64(sse_cntcv(s), 0, 32); + break; + case A_STATUS_CNTCV_HI: + r = extract64(sse_cntcv(s), 32, 32); + break; + case A_PID4 ... A_CID3: + r = status_id[(offset - A_PID4) / 4]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter status frame read: bad offset 0x%x", + (unsigned)offset); + r = 0; + break; + } + + trace_sse_counter_status_read(offset, r, size); + return r; +} + +static void sse_counter_status_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + trace_sse_counter_status_write(offset, value, size); + + switch (offset) { + case A_STATUS_CNTCV_LO: + case A_STATUS_CNTCV_HI: + case A_PID4 ... A_CID3: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter status frame: write to RO offset 0x%x\n", + (unsigned)offset); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter status frame: write to bad offset 0x%x\n", + (unsigned)offset); + break; + } +} + +static const MemoryRegionOps sse_counter_control_ops = { + .read = sse_counter_control_read, + .write = sse_counter_control_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static const MemoryRegionOps sse_counter_status_ops = { + .read = sse_counter_status_read, + .write = sse_counter_status_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void sse_counter_reset(DeviceState *dev) +{ + SSECounter *s = SSE_COUNTER(dev); + + trace_sse_counter_reset(); + + s->cntcr = 0; + s->cntscr0 = 0x01000000; + s->ns_then = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ticks_then = 0; +} + +static void sse_clk_callback(void *opaque, ClockEvent event) +{ + SSECounter *s = SSE_COUNTER(opaque); + uint64_t now; + + switch (event) { + case ClockPreUpdate: + /* + * Before the clock period updates, set (ticks_then, ns_then) + * to the current time and tick count (as calculated with + * the old clock period). + */ + if (sse_counter_enabled(s)) { + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ticks_then = sse_counter_for_timestamp(s, now); + s->ns_then = now; + } + break; + case ClockUpdate: + sse_counter_notify_users(s); + break; + default: + break; + } +} + +static void sse_counter_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + SSECounter *s = SSE_COUNTER(obj); + + notifier_list_init(&s->notifier_list); + + s->clk = qdev_init_clock_in(DEVICE(obj), "CLK", sse_clk_callback, s, + ClockPreUpdate | ClockUpdate); + memory_region_init_io(&s->control_mr, obj, &sse_counter_control_ops, + s, "sse-counter-control", 0x1000); + memory_region_init_io(&s->status_mr, obj, &sse_counter_status_ops, + s, "sse-counter-status", 0x1000); + sysbus_init_mmio(sbd, &s->control_mr); + sysbus_init_mmio(sbd, &s->status_mr); +} + +static void sse_counter_realize(DeviceState *dev, Error **errp) +{ + SSECounter *s = SSE_COUNTER(dev); + + if (!clock_has_source(s->clk)) { + error_setg(errp, "SSE system counter: CLK must be connected"); + return; + } +} + +static const VMStateDescription sse_counter_vmstate = { + .name = "sse-counter", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clk, SSECounter), + VMSTATE_END_OF_LIST() + } +}; + +static void sse_counter_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = sse_counter_realize; + dc->vmsd = &sse_counter_vmstate; + dc->reset = sse_counter_reset; +} + +static const TypeInfo sse_counter_info = { + .name = TYPE_SSE_COUNTER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SSECounter), + .instance_init = sse_counter_init, + .class_init = sse_counter_class_init, +}; + +static void sse_counter_register_types(void) +{ + type_register_static(&sse_counter_info); +} + +type_init(sse_counter_register_types); |