diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2021-03-10 13:57:31 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2021-03-10 13:57:31 +0000 |
commit | 5c6295a45b4fceac913c11abc62488c49c02b9fd (patch) | |
tree | 30e5cfdd69dd343a7a6e7dce459c6121fcedb55c /hw/timer | |
parent | a557b00469bca61a058fc1db4855503cac1c3219 (diff) | |
parent | 81b3ddaf8772ec6f88d372e52f9b433cfa46bc46 (diff) |
Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20210310' into staging
target-arm queue:
* Add new mps3-an547 board
* target/arm: Restrict v7A TCG cpus to TCG accel
* Implement a Xilinx CSU DMA model
* hw/timer/renesas_tmr: Fix use of uninitialized data in read_tcnt()
# gpg: Signature made Wed 10 Mar 2021 13:56:20 GMT
# gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE
# gpg: issuer "peter.maydell@linaro.org"
# gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate]
# gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate]
# gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate]
# Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE
* remotes/pmaydell/tags/pull-target-arm-20210310: (54 commits)
hw/timer/renesas_tmr: Fix use of uninitialized data in read_tcnt()
hw/timer/renesas_tmr: Prefix constants for CSS values with CSS_
hw/ssi: xilinx_spips: Remove DMA related dead codes from zynqmp_spips
hw/ssi: xilinx_spips: Clean up coding convention issues
hw/arm: xlnx-zynqmp: Connect a Xilinx CSU DMA module for QSPI
hw/arm: xlnx-zynqmp: Clean up coding convention issues
hw/dma: Implement a Xilinx CSU DMA model
target/arm: Restrict v7A TCG cpus to TCG accel
tests/qtest/sse-timer-test: Test counter scaling changes
tests/qtest/sse-timer-test: Test the system timer
tests/qtest/sse-timer-test: Add simple test of the SSE counter
docs/system/arm/mps2.rst: Document the new mps3-an547 board
hw/arm/mps2-tz: Add new mps3-an547 board
hw/arm/mps2-tz: Make initsvtor0 setting board-specific
hw/arm/mps2-tz: Support running APB peripherals on different clock
hw/misc/mps2-scc: Implement changes for AN547
hw/misc/mps2-fpgaio: Support AN547 DBGCTRL register
hw/misc/mps2-fpgaio: Fold counters subsection into main vmstate
hw/arm/mps2-tz: Make UART overflow IRQ board-specific
hw/arm/armsse: Add SSE-300 support
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw/timer')
-rw-r--r-- | hw/timer/Kconfig | 6 | ||||
-rw-r--r-- | hw/timer/cmsdk-apb-dualtimer.c | 5 | ||||
-rw-r--r-- | hw/timer/cmsdk-apb-timer.c | 4 | ||||
-rw-r--r-- | hw/timer/meson.build | 2 | ||||
-rw-r--r-- | hw/timer/npcm7xx_timer.c | 6 | ||||
-rw-r--r-- | hw/timer/renesas_tmr.c | 33 | ||||
-rw-r--r-- | hw/timer/sse-counter.c | 474 | ||||
-rw-r--r-- | hw/timer/sse-timer.c | 470 | ||||
-rw-r--r-- | hw/timer/trace-events | 12 |
9 files changed, 994 insertions, 18 deletions
diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig index 18936ef55b..bac2511715 100644 --- a/hw/timer/Kconfig +++ b/hw/timer/Kconfig @@ -46,5 +46,11 @@ config RENESAS_TMR config RENESAS_CMT bool +config SSE_COUNTER + bool + +config SSE_TIMER + bool + config AVR_TIMER16 bool diff --git a/hw/timer/cmsdk-apb-dualtimer.c b/hw/timer/cmsdk-apb-dualtimer.c index ef49f5852d..d4a509c798 100644 --- a/hw/timer/cmsdk-apb-dualtimer.c +++ b/hw/timer/cmsdk-apb-dualtimer.c @@ -449,7 +449,7 @@ static void cmsdk_apb_dualtimer_reset(DeviceState *dev) s->timeritop = 0; } -static void cmsdk_apb_dualtimer_clk_update(void *opaque) +static void cmsdk_apb_dualtimer_clk_update(void *opaque, ClockEvent event) { CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(opaque); int i; @@ -478,7 +478,8 @@ static void cmsdk_apb_dualtimer_init(Object *obj) sysbus_init_irq(sbd, &s->timermod[i].timerint); } s->timclk = qdev_init_clock_in(DEVICE(s), "TIMCLK", - cmsdk_apb_dualtimer_clk_update, s); + cmsdk_apb_dualtimer_clk_update, s, + ClockUpdate); } static void cmsdk_apb_dualtimer_realize(DeviceState *dev, Error **errp) diff --git a/hw/timer/cmsdk-apb-timer.c b/hw/timer/cmsdk-apb-timer.c index ee51ce3369..68aa1a7636 100644 --- a/hw/timer/cmsdk-apb-timer.c +++ b/hw/timer/cmsdk-apb-timer.c @@ -204,7 +204,7 @@ static void cmsdk_apb_timer_reset(DeviceState *dev) ptimer_transaction_commit(s->timer); } -static void cmsdk_apb_timer_clk_update(void *opaque) +static void cmsdk_apb_timer_clk_update(void *opaque, ClockEvent event) { CMSDKAPBTimer *s = CMSDK_APB_TIMER(opaque); @@ -223,7 +223,7 @@ static void cmsdk_apb_timer_init(Object *obj) sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->timerint); s->pclk = qdev_init_clock_in(DEVICE(s), "pclk", - cmsdk_apb_timer_clk_update, s); + cmsdk_apb_timer_clk_update, s, ClockUpdate); } static void cmsdk_apb_timer_realize(DeviceState *dev, Error **errp) diff --git a/hw/timer/meson.build b/hw/timer/meson.build index 26c2701fd7..a429792b08 100644 --- a/hw/timer/meson.build +++ b/hw/timer/meson.build @@ -32,6 +32,8 @@ softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_timer.c')) softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_systmr.c')) softmmu_ss.add(when: 'CONFIG_SH_TIMER', if_true: files('sh_timer.c')) softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_timer.c')) +softmmu_ss.add(when: 'CONFIG_SSE_COUNTER', if_true: files('sse-counter.c')) +softmmu_ss.add(when: 'CONFIG_SSE_TIMER', if_true: files('sse-timer.c')) softmmu_ss.add(when: 'CONFIG_STM32F2XX_TIMER', if_true: files('stm32f2xx_timer.c')) softmmu_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_timer.c')) diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c index 36e2c07db2..32f5e021f8 100644 --- a/hw/timer/npcm7xx_timer.c +++ b/hw/timer/npcm7xx_timer.c @@ -138,8 +138,8 @@ static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count) /* Convert a time interval in nanoseconds to a timer cycle count. */ static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns) { - return ns / clock_ticks_to_ns(t->ctrl->clock, - npcm7xx_tcsr_prescaler(t->tcsr)); + return clock_ns_to_ticks(t->ctrl->clock, ns) / + npcm7xx_tcsr_prescaler(t->tcsr); } static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t) @@ -627,7 +627,7 @@ static void npcm7xx_timer_init(Object *obj) sysbus_init_mmio(sbd, &s->iomem); qdev_init_gpio_out_named(dev, &w->reset_signal, NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1); - s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL); + s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL, 0); } static const VMStateDescription vmstate_npcm7xx_base_timer = { diff --git a/hw/timer/renesas_tmr.c b/hw/timer/renesas_tmr.c index e03a8155b2..eed39917fe 100644 --- a/hw/timer/renesas_tmr.c +++ b/hw/timer/renesas_tmr.c @@ -46,8 +46,10 @@ REG8(TCCR, 10) FIELD(TCCR, CSS, 3, 2) FIELD(TCCR, TMRIS, 7, 1) -#define INTERNAL 0x01 -#define CASCADING 0x03 +#define CSS_EXTERNAL 0x00 +#define CSS_INTERNAL 0x01 +#define CSS_INVALID 0x02 +#define CSS_CASCADING 0x03 #define CCLR_A 0x01 #define CCLR_B 0x02 @@ -72,7 +74,7 @@ static void update_events(RTMRState *tmr, int ch) /* event not happened */ return ; } - if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CASCADING) { + if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CSS_CASCADING) { /* cascading mode */ if (ch == 1) { tmr->next[ch] = none; @@ -130,23 +132,32 @@ static uint16_t read_tcnt(RTMRState *tmr, unsigned size, int ch) if (delta > 0) { tmr->tick = now; - if (FIELD_EX8(tmr->tccr[1], TCCR, CSS) == INTERNAL) { + switch (FIELD_EX8(tmr->tccr[1], TCCR, CSS)) { + case CSS_INTERNAL: /* timer1 count update */ elapsed = elapsed_time(tmr, 1, delta); if (elapsed >= 0x100) { ovf = elapsed >> 8; } tcnt[1] = tmr->tcnt[1] + (elapsed & 0xff); + break; + case CSS_INVALID: /* guest error to have set this */ + case CSS_EXTERNAL: /* QEMU doesn't implement these */ + case CSS_CASCADING: + tcnt[1] = tmr->tcnt[1]; + break; } switch (FIELD_EX8(tmr->tccr[0], TCCR, CSS)) { - case INTERNAL: + case CSS_INTERNAL: elapsed = elapsed_time(tmr, 0, delta); tcnt[0] = tmr->tcnt[0] + elapsed; break; - case CASCADING: - if (ovf > 0) { - tcnt[0] = tmr->tcnt[0] + ovf; - } + case CSS_CASCADING: + tcnt[0] = tmr->tcnt[0] + ovf; + break; + case CSS_INVALID: /* guest error to have set this */ + case CSS_EXTERNAL: /* QEMU doesn't implement this */ + tcnt[0] = tmr->tcnt[0]; break; } } else { @@ -330,7 +341,7 @@ static uint16_t issue_event(RTMRState *tmr, int ch, int sz, qemu_irq_pulse(tmr->cmia[ch]); } if (sz == 8 && ch == 0 && - FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CASCADING) { + FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CSS_CASCADING) { tmr->tcnt[1]++; timer_events(tmr, 1); } @@ -362,7 +373,7 @@ static void timer_events(RTMRState *tmr, int ch) uint16_t tcnt; tmr->tcnt[ch] = read_tcnt(tmr, 1, ch); - if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CASCADING) { + if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CSS_CASCADING) { tmr->tcnt[ch] = issue_event(tmr, ch, 8, tmr->tcnt[ch], tmr->tcora[ch], 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); diff --git a/hw/timer/sse-timer.c b/hw/timer/sse-timer.c new file mode 100644 index 0000000000..8dbe6ac651 --- /dev/null +++ b/hw/timer/sse-timer.c @@ -0,0 +1,470 @@ +/* + * Arm SSE Subsystem System Timer + * + * 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 timer" which is documented in + * the Arm SSE-123 Example Subsystem Technical Reference Manual: + * https://developer.arm.com/documentation/101370/latest/ + * + * The timer is based around a simple 64-bit incrementing counter + * (readable from CNTPCT_HI/LO). The timer fires when + * Counter - CompareValue >= 0. + * The CompareValue is guest-writable, via CNTP_CVAL_HI/LO. + * CNTP_TVAL is an alternative view of the CompareValue defined by + * TimerValue = CompareValue[31:0] - Counter[31:0] + * which can be both read and written. + * This part is similar to the generic timer in an Arm A-class CPU. + * + * The timer also has a separate auto-increment timer. When this + * timer is enabled, then the AutoIncrValue is set to: + * AutoIncrValue = Reload + Counter + * and this timer fires when + * Counter - AutoIncrValue >= 0 + * at which point, an interrupt is generated and the new AutoIncrValue + * is calculated. + * When the auto-increment timer is enabled, interrupt generation + * via the compare/timervalue registers is disabled. + */ +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "trace.h" +#include "hw/timer/sse-timer.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 "hw/qdev-properties.h" +#include "migration/vmstate.h" + +REG32(CNTPCT_LO, 0x0) +REG32(CNTPCT_HI, 0x4) +REG32(CNTFRQ, 0x10) +REG32(CNTP_CVAL_LO, 0x20) +REG32(CNTP_CVAL_HI, 0x24) +REG32(CNTP_TVAL, 0x28) +REG32(CNTP_CTL, 0x2c) + FIELD(CNTP_CTL, ENABLE, 0, 1) + FIELD(CNTP_CTL, IMASK, 1, 1) + FIELD(CNTP_CTL, ISTATUS, 2, 1) +REG32(CNTP_AIVAL_LO, 0x40) +REG32(CNTP_AIVAL_HI, 0x44) +REG32(CNTP_AIVAL_RELOAD, 0x48) +REG32(CNTP_AIVAL_CTL, 0x4c) + FIELD(CNTP_AIVAL_CTL, EN, 0, 1) + FIELD(CNTP_AIVAL_CTL, CLR, 1, 1) +REG32(CNTP_CFG, 0x50) + FIELD(CNTP_CFG, AIVAL, 0, 4) +#define R_CNTP_CFG_AIVAL_IMPLEMENTED 1 +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 timer_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0xb7, 0xb0, 0x0b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + +static bool sse_is_autoinc(SSETimer *s) +{ + return (s->cntp_aival_ctl & R_CNTP_AIVAL_CTL_EN_MASK) != 0; +} + +static bool sse_enabled(SSETimer *s) +{ + return (s->cntp_ctl & R_CNTP_CTL_ENABLE_MASK) != 0; +} + +static uint64_t sse_cntpct(SSETimer *s) +{ + /* Return the CNTPCT value for the current time */ + return sse_counter_for_timestamp(s->counter, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); +} + +static bool sse_timer_status(SSETimer *s) +{ + /* + * Return true if timer condition is met. This is used for both + * the CNTP_CTL.ISTATUS bit and for whether (unless masked) we + * assert our IRQ. + * The documentation is unclear about the behaviour of ISTATUS when + * in autoincrement mode; we assume that it follows CNTP_AIVAL_CTL.CLR + * (ie whether the autoincrement timer is asserting the interrupt). + */ + if (!sse_enabled(s)) { + return false; + } + + if (sse_is_autoinc(s)) { + return s->cntp_aival_ctl & R_CNTP_AIVAL_CTL_CLR_MASK; + } else { + return sse_cntpct(s) >= s->cntp_cval; + } +} + +static void sse_update_irq(SSETimer *s) +{ + bool irqstate = (!(s->cntp_ctl & R_CNTP_CTL_IMASK_MASK) && + sse_timer_status(s)); + + qemu_set_irq(s->irq, irqstate); +} + +static void sse_set_timer(SSETimer *s, uint64_t nexttick) +{ + /* Set the timer to expire at nexttick */ + uint64_t expiry = sse_counter_tick_to_time(s->counter, nexttick); + + if (expiry <= INT64_MAX) { + timer_mod_ns(&s->timer, expiry); + } else { + /* + * nexttick is so far in the future that it would overflow the + * signed 64-bit range of a QEMUTimer. Since timer_mod_ns() + * expiry times are absolute, not relative, we are never going + * to be able to set the timer to this value, so we must just + * assume that guest execution can never run so long that it + * reaches the theoretical point when the timer fires. + * This is also the code path for "counter is not running", + * which is signalled by expiry == UINT64_MAX. + */ + timer_del(&s->timer); + } +} + +static void sse_recalc_timer(SSETimer *s) +{ + /* Recalculate the normal timer */ + uint64_t count, nexttick; + + if (sse_is_autoinc(s)) { + return; + } + + if (!sse_enabled(s)) { + timer_del(&s->timer); + return; + } + + count = sse_cntpct(s); + + if (count >= s->cntp_cval) { + /* + * Timer condition already met. In theory we have a transition when + * the count rolls back over to 0, but that is so far in the future + * that it is not representable as a timer_mod() expiry, so in + * fact sse_set_timer() will always just delete the timer. + */ + nexttick = UINT64_MAX; + } else { + /* Next transition is when count hits cval */ + nexttick = s->cntp_cval; + } + sse_set_timer(s, nexttick); + sse_update_irq(s); +} + +static void sse_autoinc(SSETimer *s) +{ + /* Auto-increment the AIVAL, and set the timer accordingly */ + s->cntp_aival = sse_cntpct(s) + s->cntp_aival_reload; + sse_set_timer(s, s->cntp_aival); +} + +static void sse_timer_cb(void *opaque) +{ + SSETimer *s = SSE_TIMER(opaque); + + if (sse_is_autoinc(s)) { + uint64_t count = sse_cntpct(s); + + if (count >= s->cntp_aival) { + /* Timer condition met, set CLR and do another autoinc */ + s->cntp_aival_ctl |= R_CNTP_AIVAL_CTL_CLR_MASK; + s->cntp_aival = count + s->cntp_aival_reload; + } + sse_set_timer(s, s->cntp_aival); + sse_update_irq(s); + } else { + sse_recalc_timer(s); + } +} + +static uint64_t sse_timer_read(void *opaque, hwaddr offset, unsigned size) +{ + SSETimer *s = SSE_TIMER(opaque); + uint64_t r; + + switch (offset) { + case A_CNTPCT_LO: + r = extract64(sse_cntpct(s), 0, 32); + break; + case A_CNTPCT_HI: + r = extract64(sse_cntpct(s), 32, 32); + break; + case A_CNTFRQ: + r = s->cntfrq; + break; + case A_CNTP_CVAL_LO: + r = extract64(s->cntp_cval, 0, 32); + break; + case A_CNTP_CVAL_HI: + r = extract64(s->cntp_cval, 32, 32); + break; + case A_CNTP_TVAL: + r = extract64(s->cntp_cval - sse_cntpct(s), 0, 32); + break; + case A_CNTP_CTL: + r = s->cntp_ctl; + if (sse_timer_status(s)) { + r |= R_CNTP_CTL_ISTATUS_MASK; + } + break; + case A_CNTP_AIVAL_LO: + r = extract64(s->cntp_aival, 0, 32); + break; + case A_CNTP_AIVAL_HI: + r = extract64(s->cntp_aival, 32, 32); + break; + case A_CNTP_AIVAL_RELOAD: + r = s->cntp_aival_reload; + break; + case A_CNTP_AIVAL_CTL: + /* + * All the bits of AIVAL_CTL are documented as WO, but this is probably + * a documentation error. We implement them as readable. + */ + r = s->cntp_aival_ctl; + break; + case A_CNTP_CFG: + r = R_CNTP_CFG_AIVAL_IMPLEMENTED << R_CNTP_CFG_AIVAL_SHIFT; + break; + case A_PID4 ... A_CID3: + r = timer_id[(offset - A_PID4) / 4]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Timer read: bad offset 0x%x", + (unsigned) offset); + r = 0; + break; + } + + trace_sse_timer_read(offset, r, size); + return r; +} + +static void sse_timer_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + SSETimer *s = SSE_TIMER(opaque); + + trace_sse_timer_write(offset, value, size); + + switch (offset) { + case A_CNTFRQ: + s->cntfrq = value; + break; + case A_CNTP_CVAL_LO: + s->cntp_cval = deposit64(s->cntp_cval, 0, 32, value); + sse_recalc_timer(s); + break; + case A_CNTP_CVAL_HI: + s->cntp_cval = deposit64(s->cntp_cval, 32, 32, value); + sse_recalc_timer(s); + break; + case A_CNTP_TVAL: + s->cntp_cval = sse_cntpct(s) + sextract64(value, 0, 32); + sse_recalc_timer(s); + break; + case A_CNTP_CTL: + { + uint32_t old_ctl = s->cntp_ctl; + value &= R_CNTP_CTL_ENABLE_MASK | R_CNTP_CTL_IMASK_MASK; + s->cntp_ctl = value; + if ((old_ctl ^ s->cntp_ctl) & R_CNTP_CTL_ENABLE_MASK) { + if (sse_enabled(s)) { + if (sse_is_autoinc(s)) { + sse_autoinc(s); + } else { + sse_recalc_timer(s); + } + } + } + sse_update_irq(s); + break; + } + case A_CNTP_AIVAL_RELOAD: + s->cntp_aival_reload = value; + break; + case A_CNTP_AIVAL_CTL: + { + uint32_t old_ctl = s->cntp_aival_ctl; + + /* EN bit is writeable; CLR bit is write-0-to-clear, write-1-ignored */ + s->cntp_aival_ctl &= ~R_CNTP_AIVAL_CTL_EN_MASK; + s->cntp_aival_ctl |= value & R_CNTP_AIVAL_CTL_EN_MASK; + if (!(value & R_CNTP_AIVAL_CTL_CLR_MASK)) { + s->cntp_aival_ctl &= ~R_CNTP_AIVAL_CTL_CLR_MASK; + } + if ((old_ctl ^ s->cntp_aival_ctl) & R_CNTP_AIVAL_CTL_EN_MASK) { + /* Auto-increment toggled on/off */ + if (sse_enabled(s)) { + if (sse_is_autoinc(s)) { + sse_autoinc(s); + } else { + sse_recalc_timer(s); + } + } + } + sse_update_irq(s); + break; + } + case A_CNTPCT_LO: + case A_CNTPCT_HI: + case A_CNTP_CFG: + case A_CNTP_AIVAL_LO: + case A_CNTP_AIVAL_HI: + case A_PID4 ... A_CID3: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Timer write: write to RO offset 0x%x\n", + (unsigned)offset); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Timer write: bad offset 0x%x\n", + (unsigned)offset); + break; + } +} + +static const MemoryRegionOps sse_timer_ops = { + .read = sse_timer_read, + .write = sse_timer_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void sse_timer_reset(DeviceState *dev) +{ + SSETimer *s = SSE_TIMER(dev); + + trace_sse_timer_reset(); + + timer_del(&s->timer); + s->cntfrq = 0; + s->cntp_ctl = 0; + s->cntp_cval = 0; + s->cntp_aival = 0; + s->cntp_aival_ctl = 0; + s->cntp_aival_reload = 0; +} + +static void sse_timer_counter_callback(Notifier *notifier, void *data) +{ + SSETimer *s = container_of(notifier, SSETimer, counter_notifier); + + /* System counter told us we need to recalculate */ + if (sse_enabled(s)) { + if (sse_is_autoinc(s)) { + sse_set_timer(s, s->cntp_aival); + } else { + sse_recalc_timer(s); + } + } +} + +static void sse_timer_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + SSETimer *s = SSE_TIMER(obj); + + memory_region_init_io(&s->iomem, obj, &sse_timer_ops, + s, "sse-timer", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static void sse_timer_realize(DeviceState *dev, Error **errp) +{ + SSETimer *s = SSE_TIMER(dev); + + if (!s->counter) { + error_setg(errp, "counter property was not set"); + } + + s->counter_notifier.notify = sse_timer_counter_callback; + sse_counter_register_consumer(s->counter, &s->counter_notifier); + + timer_init_ns(&s->timer, QEMU_CLOCK_VIRTUAL, sse_timer_cb, s); +} + +static const VMStateDescription sse_timer_vmstate = { + .name = "sse-timer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_TIMER(timer, SSETimer), + VMSTATE_UINT32(cntfrq, SSETimer), + VMSTATE_UINT32(cntp_ctl, SSETimer), + VMSTATE_UINT64(cntp_cval, SSETimer), + VMSTATE_UINT64(cntp_aival, SSETimer), + VMSTATE_UINT32(cntp_aival_ctl, SSETimer), + VMSTATE_UINT32(cntp_aival_reload, SSETimer), + VMSTATE_END_OF_LIST() + } +}; + +static Property sse_timer_properties[] = { + DEFINE_PROP_LINK("counter", SSETimer, counter, TYPE_SSE_COUNTER, SSECounter *), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sse_timer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = sse_timer_realize; + dc->vmsd = &sse_timer_vmstate; + dc->reset = sse_timer_reset; + device_class_set_props(dc, sse_timer_properties); +} + +static const TypeInfo sse_timer_info = { + .name = TYPE_SSE_TIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SSETimer), + .instance_init = sse_timer_init, + .class_init = sse_timer_class_init, +}; + +static void sse_timer_register_types(void) +{ + type_register_static(&sse_timer_info); +} + +type_init(sse_timer_register_types); diff --git a/hw/timer/trace-events b/hw/timer/trace-events index 7a4326d956..f8b9db25c2 100644 --- a/hw/timer/trace-events +++ b/hw/timer/trace-events @@ -93,3 +93,15 @@ avr_timer16_interrupt_count(uint8_t cnt) "count: %u" avr_timer16_interrupt_overflow(const char *reason) "overflow: %s" avr_timer16_next_alarm(uint64_t delay_ns) "next alarm: %" PRIu64 " ns from now" avr_timer16_clksrc_update(uint64_t freq_hz, uint64_t period_ns, uint64_t delay_s) "timer frequency: %" PRIu64 " Hz, period: %" PRIu64 " ns (%" PRId64 " us)" + +# sse_counter.c +sse_counter_control_read(uint64_t offset, uint64_t data, unsigned size) "SSE system counter control frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_counter_control_write(uint64_t offset, uint64_t data, unsigned size) "SSE system counter control framen write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_counter_status_read(uint64_t offset, uint64_t data, unsigned size) "SSE system counter status frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_counter_status_write(uint64_t offset, uint64_t data, unsigned size) "SSE system counter status frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_counter_reset(void) "SSE system counter: reset" + +# sse_timer.c +sse_timer_read(uint64_t offset, uint64_t data, unsigned size) "SSE system timer read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_timer_write(uint64_t offset, uint64_t data, unsigned size) "SSE system timer write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_timer_reset(void) "SSE system timer: reset" |