| /* |
| * 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/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 = (const 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; |
| device_class_set_legacy_reset(dc, 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); |