[sw] Introduce a DIF for rv_timer
Signed-off-by: Miguel Young de la Sota <mcyoung@google.com>
diff --git a/sw/device/lib/dif/dif_rv_timer.c b/sw/device/lib/dif/dif_rv_timer.c
new file mode 100644
index 0000000..ac88029
--- /dev/null
+++ b/sw/device/lib/dif/dif_rv_timer.c
@@ -0,0 +1,380 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/dif/dif_rv_timer.h"
+
+#include <stddef.h>
+
+#include "sw/device/lib/base/bitfield.h"
+#include "rv_timer_regs.h" // Generated.
+
+/**
+ * The factor to multiply by to find the registers for the Nth hart.
+ *
+ * Given the hart N (zero-indexed), its timer registers are found at
+ * the memory address
+ * base_addr + ((N + 1) * kHartRegisterSpacing)
+ *
+ * The function `reg_for_hart()` can be used to compute the offset from
+ * `base` for the Nth hart for a particular repeated hardware register.
+ */
+static const ptrdiff_t kHartRegisterSpacing = 0x100;
+
+/**
+ * Returns the MMIO offset for the register `reg_offset`, for the zero-indexed
+ * `hart`.
+ */
+static ptrdiff_t reg_for_hart(uint32_t hart, ptrdiff_t reg_offset) {
+ return kHartRegisterSpacing * hart + reg_offset;
+}
+
+dif_rv_timer_result_t dif_rv_timer_init(mmio_region_t base_addr,
+ dif_rv_timer_config_t config,
+ dif_rv_timer_t *timer_out) {
+ if (timer_out == NULL || config.hart_count < 1 ||
+ config.comparator_count < 1) {
+ return kDifRvTimerBadArg;
+ }
+
+ timer_out->base_addr = base_addr;
+ timer_out->config = config;
+
+ return dif_rv_timer_reset(timer_out);
+}
+
+/**
+ * A naive implementation of the Euclidean algorithm by repeated remainder.
+ */
+static uint64_t euclidean_gcd(uint64_t a, uint64_t b) {
+ // TODO: The below 64-bit divide/remaider should be replaced with more
+ // space-efficient polyfills at some point.
+ while (b != 0) {
+ uint64_t old_b = b;
+ b = a % b;
+ a = old_b;
+ }
+
+ return a;
+}
+
+dif_rv_timer_approximate_tick_params_result_t
+dif_rv_timer_approximate_tick_params(uint64_t clock_freq, uint64_t counter_freq,
+ dif_rv_timer_tick_params_t *out) {
+ if (out == NULL) {
+ return kDifRvTimerApproximateTickParamsBadArg;
+ }
+
+ // We have the following relation:
+ // counter_freq = clock_freq * (step / (prescale + 1))
+ // We can solve for the individual parts as
+ // prescale = clock_freq / gcd - 1
+ // step = counter_freq / gcd
+ uint64_t gcd = euclidean_gcd(clock_freq, counter_freq);
+
+ uint64_t prescale = clock_freq / gcd - 1;
+ uint64_t step = counter_freq / gcd;
+
+ if (prescale > RV_TIMER_CFG0_PRESCALE_MASK ||
+ step > RV_TIMER_CFG0_STEP_MASK) {
+ return kDifRvTimerApproximateTickParamsUnrepresentable;
+ }
+
+ out->prescale = (uint16_t)prescale;
+ out->tick_step = (uint8_t)step;
+
+ return kDifRvTimerApproximateTickParamsOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_set_tick_params(
+ const dif_rv_timer_t *timer, uint32_t hart_id,
+ dif_rv_timer_tick_params_t params) {
+ if (timer == NULL || hart_id >= timer->config.hart_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ uint32_t config_value = 0;
+ config_value = bitfield_set_field32(
+ config_value, (bitfield_field32_t){
+ .mask = RV_TIMER_CFG0_PRESCALE_MASK,
+ .index = RV_TIMER_CFG0_PRESCALE_OFFSET,
+ .value = params.prescale,
+ });
+ config_value =
+ bitfield_set_field32(config_value, (bitfield_field32_t){
+ .mask = RV_TIMER_CFG0_STEP_MASK,
+ .index = RV_TIMER_CFG0_STEP_OFFSET,
+ .value = params.tick_step,
+ });
+ mmio_region_write32(timer->base_addr,
+ reg_for_hart(hart_id, RV_TIMER_CFG0_REG_OFFSET),
+ config_value);
+
+ return kDifRvTimerOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_counter_set_enabled(
+ const dif_rv_timer_t *timer, uint32_t hart_id,
+ dif_rv_timer_enabled_t state) {
+ if (timer == NULL || hart_id >= timer->config.hart_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ switch (state) {
+ case kDifRvTimerEnabled:
+ mmio_region_nonatomic_set_bit32(timer->base_addr,
+ RV_TIMER_CTRL_REG_OFFSET, hart_id);
+ break;
+ case kDifRvTimerDisabled:
+ mmio_region_nonatomic_clear_bit32(timer->base_addr,
+ RV_TIMER_CTRL_REG_OFFSET, hart_id);
+ break;
+ default:
+ return kDifRvTimerBadArg;
+ }
+
+ return kDifRvTimerOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_counter_read(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint64_t *out) {
+ if (timer == NULL || out == NULL || hart_id >= timer->config.hart_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ // We need to read a monotonically increasing, volatile uint64. To do so,
+ // we first read the upper half, then the lower half. Then, we check if the
+ // upper half reads the same value again. If it doesn't, it means that the
+ // lower half overflowed and we need to re-take the measurement.
+ while (true) {
+ uint32_t upper = mmio_region_read32(
+ timer->base_addr,
+ reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET));
+ uint32_t lower = mmio_region_read32(
+ timer->base_addr,
+ reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET));
+
+ uint32_t overflow_check = mmio_region_read32(
+ timer->base_addr,
+ reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET));
+
+ if (upper == overflow_check) {
+ *out = (((uint64_t)upper) << 32) | lower;
+ return kDifRvTimerOk;
+ }
+ }
+}
+
+dif_rv_timer_result_t dif_rv_timer_arm(const dif_rv_timer_t *timer,
+ uint32_t hart_id, uint32_t comp_id,
+ uint64_t threshold) {
+ if (timer == NULL || hart_id >= timer->config.hart_count ||
+ comp_id >= timer->config.comparator_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ uint32_t lower = threshold;
+ uint32_t upper = threshold >> 32;
+
+ ptrdiff_t lower_reg =
+ reg_for_hart(hart_id, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) +
+ (sizeof(uint64_t) * comp_id);
+ ptrdiff_t upper_reg =
+ reg_for_hart(hart_id, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) +
+ (sizeof(uint64_t) * comp_id);
+
+ // First, set the upper register to the largest value possible without setting
+ // off the alarm; this way, we can set the lower register without setting
+ // off the alarm.
+ mmio_region_write32(timer->base_addr, upper_reg, UINT32_MAX);
+
+ // This can't set off the alarm because of the value we set above.
+ mmio_region_write32(timer->base_addr, lower_reg, lower);
+ // Finish writing the new value; this may set off an alarm immediately.
+ mmio_region_write32(timer->base_addr, upper_reg, upper);
+
+ return kDifRvTimerOk;
+}
+
+/**
+ * The number of comparators that the IP instantiation this library is compiled
+ * against has. In other words, this tells us how far the statically known IRQ
+ * register constants are from the start of the comparators, which influences
+ * the computation in `irq_reg_for_hart()`.
+ */
+static const ptrdiff_t kComparatorsInReggenHeader =
+ (RV_TIMER_INTR_ENABLE0_REG_OFFSET - RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) /
+ sizeof(uint64_t);
+
+/**
+ * Computes the appropriate register for a particular interrupt register, given
+ * a number of comparators.
+ *
+ * Currently, all IRQ registers are placed after the comparator registers,
+ * so the register offsets given by HW are not useable. The offsets need to be
+ * compensated for by a factor of `kComparatorsInReggenHeader` double-words..
+ *
+ * We also do not handle the case when comparator_count > 32, which would cause
+ * multiple interrupt registers to be generated.
+ */
+static ptrdiff_t irq_reg_for_hart(uint32_t hart_id, uint32_t comparators,
+ ptrdiff_t reg_offset) {
+ // Note that it is completely valid for this value to be negative: if this
+ // library is built for hardware with a large number of compartors, this value
+ // is necessarially negative when used with hardware with a small number of
+ // comparators.
+ ptrdiff_t extra_comparator_offset =
+ sizeof(uint64_t) * (comparators - kComparatorsInReggenHeader);
+ return reg_for_hart(hart_id, reg_offset) + extra_comparator_offset;
+}
+
+dif_rv_timer_result_t dif_rv_timer_irq_enable(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t comp_id,
+ dif_rv_timer_enabled_t state) {
+ if (timer == NULL || hart_id >= timer->config.hart_count ||
+ comp_id >= timer->config.comparator_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
+ RV_TIMER_INTR_ENABLE0_REG_OFFSET);
+
+ switch (state) {
+ case kDifRvTimerEnabled:
+ mmio_region_nonatomic_set_bit32(timer->base_addr, reg, comp_id);
+ break;
+ case kDifRvTimerDisabled:
+ mmio_region_nonatomic_clear_bit32(timer->base_addr, reg, comp_id);
+ break;
+ default:
+ return kDifRvTimerBadArg;
+ }
+
+ return kDifRvTimerOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_irq_get(const dif_rv_timer_t *timer,
+ uint32_t hart_id, uint32_t comp_id,
+ bool *flag_out) {
+ if (timer == NULL || flag_out == NULL ||
+ hart_id >= timer->config.hart_count ||
+ comp_id >= timer->config.comparator_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
+ RV_TIMER_INTR_STATE0_REG_OFFSET);
+
+ *flag_out = mmio_region_get_bit32(timer->base_addr, reg, comp_id);
+
+ return kDifRvTimerOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_irq_clear(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t comp_id) {
+ if (timer == NULL || hart_id >= timer->config.hart_count ||
+ comp_id >= timer->config.comparator_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
+ RV_TIMER_INTR_STATE0_REG_OFFSET);
+
+ mmio_region_write32(timer->base_addr, reg, 1 << comp_id);
+
+ return kDifRvTimerOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_irq_disable(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t *state) {
+ if (timer == NULL || hart_id >= timer->config.hart_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
+ RV_TIMER_INTR_ENABLE0_REG_OFFSET);
+
+ if (state != NULL) {
+ *state = mmio_region_read32(timer->base_addr, reg);
+ }
+
+ mmio_region_write32(timer->base_addr, reg, 0);
+
+ return kDifRvTimerOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_irq_restore(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t state) {
+ if (timer == NULL || hart_id >= timer->config.hart_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
+ RV_TIMER_INTR_ENABLE0_REG_OFFSET);
+
+ mmio_region_write32(timer->base_addr, reg, state);
+
+ return kDifRvTimerOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_irq_force(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t comp_id) {
+ if (timer == NULL || hart_id >= timer->config.hart_count ||
+ comp_id >= timer->config.comparator_count) {
+ return kDifRvTimerBadArg;
+ }
+
+ ptrdiff_t reg = irq_reg_for_hart(hart_id, timer->config.comparator_count,
+ RV_TIMER_INTR_TEST0_REG_OFFSET);
+
+ mmio_region_write32(timer->base_addr, reg, 1 << comp_id);
+
+ return kDifRvTimerOk;
+}
+
+dif_rv_timer_result_t dif_rv_timer_reset(const dif_rv_timer_t *timer) {
+ if (timer == NULL) {
+ return kDifRvTimerBadArg;
+ }
+
+ // Disable all counters.
+ mmio_region_write32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET, 0x0);
+
+ for (uint32_t hart_id = 0; hart_id < timer->config.hart_count; ++hart_id) {
+ // Clear and disable all interrupts.
+ ptrdiff_t irq_status =
+ irq_reg_for_hart(hart_id, timer->config.comparator_count,
+ RV_TIMER_INTR_STATE0_REG_OFFSET);
+ ptrdiff_t irq_enable =
+ irq_reg_for_hart(hart_id, timer->config.comparator_count,
+ RV_TIMER_INTR_ENABLE0_REG_OFFSET);
+ mmio_region_write32(timer->base_addr, irq_enable, 0x0);
+ mmio_region_write32(timer->base_addr, irq_status, UINT32_MAX);
+
+ // Reset all comparators to their default all-ones state.
+ for (uint32_t comp_id = 0; comp_id < timer->config.comparator_count;
+ ++comp_id) {
+ dif_rv_timer_result_t error =
+ dif_rv_timer_arm(timer, hart_id, comp_id, UINT64_MAX);
+ if (error != kDifRvTimerOk) {
+ return error;
+ }
+ }
+
+ // Reset the counter to zero.
+ mmio_region_write32(
+ timer->base_addr,
+ reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
+ mmio_region_write32(
+ timer->base_addr,
+ reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
+ }
+
+ return kDifRvTimerOk;
+}
diff --git a/sw/device/lib/dif/dif_rv_timer.h b/sw/device/lib/dif/dif_rv_timer.h
new file mode 100644
index 0000000..c27c070
--- /dev/null
+++ b/sw/device/lib/dif/dif_rv_timer.h
@@ -0,0 +1,323 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_DIF_RV_TIMER_H_
+#define OPENTITAN_SW_DEVICE_LIB_DIF_DIF_RV_TIMER_H_
+
+#include <stdint.h>
+
+#include "sw/device/lib/base/mmio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/**
+ * Represents an "enabled" state for a timer.
+ */
+typedef enum dif_rv_timer_enabled {
+ kDifRvTimerDisabled = 0,
+ kDifRvTimerEnabled,
+} dif_rv_timer_enabled_t;
+
+/**
+ * Represents timekeeping parameters for a particular timer.
+ */
+typedef struct dif_rv_timer_tick_params {
+ /**
+ * The prescaler value is the period of the timer tick in clock cycles,
+ * minus one. That is,
+ *
+ * prescale = clock_freq * tick_period - 1
+ *
+ * with |clock_freq| and |tick_period| given in units of hertz and seconds,
+ * respectively.
+ *
+ * For example, if the clock frequency is 50 MHz, and the desired tick
+ * period is is 1 microsecond, i.e, a tick frequency of 1 MHz, then the
+ * prescaler should be:
+ *
+ * (50 * 10^6) * (1 * 10^-6) - 1 = 49
+ *
+ * However, since |tick_period| is very small, it is much more convenient to
+ * work with |tick_freq|, its inverse, which will be an integer number of
+ * hertz. In particular,
+ *
+ * prescale = (clock_freq / tick_freq) - 1
+ *
+ * This value is declared as a uint16_t, but only the lowest 12 bits are
+ * actually used.
+ */
+ uint16_t prescale;
+
+ /**
+ * The amount to increment the timer counter at each tick.
+ */
+ uint8_t tick_step;
+} dif_rv_timer_tick_params_t;
+
+/**
+ * Represents the result of an RV timer operation..
+ */
+typedef enum dif_rv_timer_result {
+ kDifRvTimerOk = 0,
+ kDifRvTimerBadArg = 1,
+ kDifRvTimerError = 2,
+} dif_rv_timer_result_t;
+
+/**
+ * Errors returned by `dif_rv_timer_approximate_tick_params()`.
+ */
+typedef enum dif_rv_timer_approximate_tick_params_result {
+ kDifRvTimerApproximateTickParamsOk = kDifRvTimerOk,
+ kDifRvTimerApproximateTickParamsBadArg = kDifRvTimerBadArg,
+ kDifRvTimerApproximateTickParamsError = kDifRvTimerError,
+ /**
+ * Indicates that the requested counter frequency cannot be represented
+ * without loss in precission.
+ */
+ kDifRvTimerApproximateTickParamsUnrepresentable,
+} dif_rv_timer_approximate_tick_params_result_t;
+
+/**
+ * Generates an aproximate `dif_rv_timer_tick_params_t` given the device
+ * clock frequency and desired counter frequency (both given in Hertz).
+ *
+ * For the purposes of this function, "counter frequency" is the frequency
+ * at which software would observe a timer counter to increase. If the
+ * clock has insufficient resolution, high counter frequencies may set a
+ * larger value for `tick_step`. For example, if the clock ticks at 50kHz,
+ * but we want a counter that seems to tick every microsecond (1MHz),
+ * we can achieve this with a prescale of 0 (so that there is a tick per
+ * clock cycle) and a tick step of 20 (since 20 * 50kHz = 1MHz).
+ *
+ * The return value of this function is only an approximation, and the
+ * actual counter frequency ultimately depends on the accuracy of the
+ * clock. The function will return an error if it cannot produce an acceptably
+ * accurate counter frequency using the given clock resolution.
+ *
+ * @param clock_freq The device clock frequency, in Hertz.
+ * @param counter_freq The desired counter frequency, in Hertz.
+ * @param out Tick parameters that will approximately produce the desired
+ * counter frequency.
+ * @return The result of the operation.
+ */
+dif_rv_timer_approximate_tick_params_result_t
+dif_rv_timer_approximate_tick_params(uint64_t clock_freq, uint64_t counter_freq,
+ dif_rv_timer_tick_params_t *out);
+/**
+ * Configuration for initializing the RISC-V timer device.
+ */
+typedef struct dif_rv_timer_config {
+ /**
+ * The number of harts that this device supports time counters for.
+ * Must be at least one.
+ *
+ * This value is determined entirely by the hardware configuration.
+ */
+ uint32_t hart_count;
+
+ /**
+ * The number of comparators (i.e, timers that can be set) associated
+ * with each hart. Must be at least one.
+ *
+ * This value is determined entirely by the hardware configuration.
+ */
+ uint32_t comparator_count;
+} dif_rv_timer_config_t;
+
+/**
+ * State for a RISC-V timer, associated with a particular hardware thread.
+ *
+ * Its member variables should be considered private, and are only provided so
+ * that callers can allocate it.
+ */
+typedef struct dif_rv_timer {
+ mmio_region_t base_addr;
+ dif_rv_timer_config_t config;
+} dif_rv_timer_t;
+
+/**
+ * Initialize a RISC-V timer device with the given configuration.
+ *
+ * This function will deactivate all counters and reset all timers, which should
+ * each be configured and turned on manually after this function returns.
+ *
+ * @param base_addr MMIO region for the device hardware registers.
+ * @param config Configuration for initializing a particular timer.
+ * @param timer_out Out param for the timer device.
+ * @return The result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_init(mmio_region_t base_addr,
+ dif_rv_timer_config_t config,
+ dif_rv_timer_t *timer_out);
+
+/**
+ * Completely resets a timer device, disabling all IRQs, counters, and
+ * comparators.
+ *
+ * @param timer A timer device.
+ * @return The result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_reset(const dif_rv_timer_t *timer);
+
+/**
+ * Configures the tick params for a particular hart's counter.
+ *
+ * This function should not be called when `hart_id`'s counter is enabled; it is
+ * the caller's responsibility to assert this precondition.
+ * The function `dif_rv_timer_approximate_tick_params()` can be used to generate
+ * tick parameter values.
+ *
+ * @param timer A timer device.
+ * @param hart_id The hart to configure.
+ * @param params The timing parameters.
+ * @return The result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_set_tick_params(
+ const dif_rv_timer_t *timer, uint32_t hart_id,
+ dif_rv_timer_tick_params_t params);
+
+/**
+ * Starts or stops a particular hart's counter.
+ *
+ * While a counter is enabled, the counter value will increase each tick, but
+ * its timekeeping values cannot be reconfigured.
+ *
+ * @param timer A timer device.
+ * @param hart_id The hart counter to enable/disable.
+ * @param state The new enablement state.
+ * @return The result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_counter_set_enabled(
+ const dif_rv_timer_t *timer, uint32_t hart_id,
+ dif_rv_timer_enabled_t state);
+
+/**
+ * Reads the current value on a particlar hart's timer.
+ *
+ * @param timer A timer device.
+ * @param hart_id The hart counter to read.
+ * @param out Out param for the counter value.
+ * @return The result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_counter_read(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint64_t *out);
+
+/**
+ * Arms the timer to go off once the counter value is greater than
+ * or equal to `threshold`, by setting up the given comparator.
+ *
+ * Beware that the following naive implementation of setting an alarm
+ * contains a bug:
+ * uint64_t time;
+ * dif_rv_timer_counter_read(my_timer, kMyHart, &time);
+ * time += kSomeDuration; // (*)
+ * dif_rv_timer_arm(my_timer, kMyHart, kMyComp, time);
+ *
+ * If `time` wraps around when performing the addition, an interrupt will be
+ * fired immediately upon calling `dif_rv_timer_arm`. Care should be taken to
+ * perform saturating addition at (*), so that the interrupt is fired when the
+ * timer value wraps around; this way, the interrupt handler can re-arm the
+ * timer for the rest of the duration.
+ *
+ * This function makes no effort to protect the caller from setting alarms in
+ * the past that would immediately fire an interrupt. It is the caller's
+ * responsibility to read the current counter value and pick a reasonable alarm
+ * threshold.
+ *
+ * @param timer A timer device.
+ * @param hart_id The hart counter to arm against.
+ * @param comp_id The comparator to set up.
+ * @param threshold The value to go off at.
+ * @return The result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_arm(const dif_rv_timer_t *timer,
+ uint32_t hart_id, uint32_t comp_id,
+ uint64_t threshold);
+
+/**
+ * Enables or disables a particular timer's IRQ. That is, this function controls
+ * whether or not an IRQ is fired when the timer reaches its configured
+ * threshold.
+ *
+ * @param timer A timer device.
+ * @param hart_id The hart counter associated with the timer.
+ * @param comp_id The comparator associated with the timer.
+ * @param state The desired status.
+ * @return the result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_irq_enable(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t comp_id,
+ dif_rv_timer_enabled_t state);
+
+/**
+ * Returns whether the IRQ for a particular timer is currently being serviced.
+ *
+ * @param timer A timer device.
+ * @param hart_id The hart counter associated with the timer.
+ * @param comp_id The comparator associated with the timer.
+ * @param flag_out Out-param for the interrupt status.
+ * @return the result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_irq_get(const dif_rv_timer_t *timer,
+ uint32_t hart_id, uint32_t comp_id,
+ bool *flag_out);
+
+/**
+ * Clears the IRQ for a particular timer.
+ *
+ * @param timer A timer device.
+ * @param hart_id The hart counter associated with the timer.
+ * @param comp_id The comparator associated with the timer.
+ * @return the result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_irq_clear(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t comp_id);
+
+/**
+ * Forces the IRQ for a particular timer to fire.
+ *
+ * @param timer A timer device.
+ * @param hart_id The hart counter associated with the timer.
+ * @param comp_id The comparator associated with the timer.
+ * @return the result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_irq_force(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t comp_id);
+
+/**
+ * Disables all IRQs for a particular hart.
+ *
+ * Optionally returns a `state` value containing the previous itnerrupt state.
+ * `state` may be NULL. See `dif_rv_timer_irq_restore()`.
+ *
+ * @param timer a timer device.
+ * @param state out param for IRQ state information.
+ * @return the result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_irq_disable(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t *state);
+
+/**
+ * Restores IRQ state for a particular hart.
+ *
+ * @param timer a timer device.
+ * @param state IRQ state information to restore.
+ * @return the result of the operation.
+ */
+dif_rv_timer_result_t dif_rv_timer_irq_restore(const dif_rv_timer_t *timer,
+ uint32_t hart_id,
+ uint32_t state);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
+
+#endif // OPENTITAN_SW_DEVICE_LIB_DIF_DIF_RV_TIMER_H_
diff --git a/sw/device/lib/dif/meson.build b/sw/device/lib/dif/meson.build
index 3f684d3..e46d354 100644
--- a/sw/device/lib/dif/meson.build
+++ b/sw/device/lib/dif/meson.build
@@ -56,6 +56,20 @@
)
)
+# RISC-V Timer DIF library (dif_rv_timer)
+dif_rv_timer = declare_dependency(
+ link_with: static_library(
+ 'dif_rv_timer_ot',
+ sources: [
+ hw_ip_rv_timer_reg_h,
+ 'dif_rv_timer.c',
+ ],
+ dependencies: [
+ sw_lib_mmio
+ ],
+ )
+)
+
# I2C DIF library
sw_lib_dif_i2c = declare_dependency(
link_with: static_library(
diff --git a/sw/device/tests/dif/dif_rv_timer_unittest.cc b/sw/device/tests/dif/dif_rv_timer_unittest.cc
new file mode 100644
index 0000000..ac72d4c
--- /dev/null
+++ b/sw/device/tests/dif/dif_rv_timer_unittest.cc
@@ -0,0 +1,625 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <cstring>
+#include <limits>
+#include <ostream>
+#include <stdint.h>
+
+#include "sw/device/lib/dif/dif_rv_timer.h"
+#include "rv_timer_regs.h" // Generated.
+
+#include "gtest/gtest.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/testing/mock_mmio.h"
+
+// We define global namespace == and << to make `dif_i2c_timing_params_t` work
+// nicely with EXPECT_EQ.
+bool operator==(dif_rv_timer_tick_params_t a, dif_rv_timer_tick_params_t b) {
+ return a.prescale == b.prescale && a.tick_step == b.tick_step;
+}
+
+std::ostream &operator<<(std::ostream &os,
+ const dif_rv_timer_tick_params_t ¶ms) {
+ // Note that `tick_step` is actually a `char`, so it doesn't print correctly.
+ auto step = static_cast<uint32_t>(params.tick_step);
+ return os << "{ .prescale = " << params.prescale << ", .tick_step = " << step
+ << " }";
+}
+
+namespace dif_rv_timer_unittest {
+namespace {
+using ::mock_mmio::LeInt;
+using ::mock_mmio::MmioTest;
+using ::mock_mmio::MockDevice;
+
+constexpr uint32_t kFastClockSpeed = 2'000'000'000; // 2 GHz
+constexpr uint32_t kClockSpeed = 50'000'000; // 50 MHz
+constexpr uint32_t kSlowClockSpeed = 50; // 50 Hz
+
+constexpr uint32_t kSlowTimer = 1'000'000; // 1 MHz
+constexpr uint32_t kFastTimer = 120'000'000; // 120 MHz
+constexpr uint32_t kSluggishTimer = 3; // 3 Hz
+
+TEST(ApproximateParamsTest, Baseline) {
+ // The timer frequency devices the clock speed, so their quotient minus 1 is
+ // the prescale.
+ dif_rv_timer_tick_params_t params, expected = {
+ .prescale = 49, .tick_step = 1,
+ };
+ EXPECT_EQ(
+ dif_rv_timer_approximate_tick_params(kClockSpeed, kSlowTimer, ¶ms),
+ kDifRvTimerOk);
+ EXPECT_EQ(params, expected);
+}
+
+TEST(ApproximateParamsTest, WithStep) {
+ // 50 MHz / 5 is 10 MHz; multiplied by 12, we get 120 MHz.
+ dif_rv_timer_tick_params_t params, expected = {
+ .prescale = 4, .tick_step = 12,
+ };
+ EXPECT_EQ(
+ dif_rv_timer_approximate_tick_params(kClockSpeed, kFastTimer, ¶ms),
+ kDifRvTimerOk);
+ EXPECT_EQ(params, expected);
+}
+
+TEST(ApproximateParamsTest, UnrepresenableTooSlow) {
+ // This frequency is unrepresentable; the GCD of the clock and timer
+ // frequencies is 1, so the prescale is the clock speed, which does not fit in
+ // a u12.
+ dif_rv_timer_tick_params_t params;
+ EXPECT_EQ(dif_rv_timer_approximate_tick_params(kFastClockSpeed,
+ kSluggishTimer, ¶ms),
+ kDifRvTimerApproximateTickParamsUnrepresentable);
+}
+
+TEST(ApproximateParamsTest, UnrepresenableTooFast) {
+ // This freqncy is unrepresentable; the GCD is 50, meaning that the step must
+ // be 2'400'000, which does not fit into a u8.
+ dif_rv_timer_tick_params_t params;
+ EXPECT_EQ(dif_rv_timer_approximate_tick_params(kSlowClockSpeed, kFastTimer,
+ ¶ms),
+ kDifRvTimerApproximateTickParamsUnrepresentable);
+}
+
+TEST(ApproximateParamsTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_approximate_tick_params(kSlowClockSpeed, kFastTimer,
+ nullptr),
+ kDifRvTimerBadArg);
+}
+
+class TimerTest : public testing::Test, public MmioTest {
+ protected:
+ dif_rv_timer_t MakeTimer(dif_rv_timer_config_t config) {
+ return {
+ .base_addr = dev().region(), .config = config,
+ };
+ }
+};
+
+// Copy of `reg_for_hart()` in the C translation unit under test.
+ptrdiff_t RegForHart(uint32_t hart, ptrdiff_t reg_offset) {
+ return 0x100 * hart + reg_offset;
+}
+
+// Copy of `irq_reg_for_hart()` in the C translation unit under test.
+ptrdiff_t IrqRegForHart(uint32_t hart, uint32_t comparators,
+ ptrdiff_t reg_offset) {
+ auto base = RegForHart(hart, reg_offset);
+ auto extra_comparator_offset = sizeof(uint64_t) * (comparators - 1);
+ return base + extra_comparator_offset;
+}
+
+constexpr uint32_t kAllOnes = std::numeric_limits<uint32_t>::max();
+
+class InitTest : public TimerTest {};
+
+TEST_F(InitTest, OneEach) {
+ EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0);
+
+ EXPECT_WRITE32(IrqRegForHart(0, 1, RV_TIMER_INTR_ENABLE0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(IrqRegForHart(0, 1, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ kAllOnes);
+
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET), kAllOnes);
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET), kAllOnes);
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET), kAllOnes);
+
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
+
+ dif_rv_timer timer;
+ EXPECT_EQ(dif_rv_timer_init(dev().region(),
+ {
+ .hart_count = 1, .comparator_count = 1,
+ },
+ &timer),
+ kDifRvTimerOk);
+}
+
+TEST_F(InitTest, FourCmps) {
+ EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0);
+
+ EXPECT_WRITE32(IrqRegForHart(0, 4, RV_TIMER_INTR_ENABLE0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(IrqRegForHart(0, 4, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ kAllOnes);
+
+ for (ptrdiff_t cmp_offset = 0; cmp_offset < 32; cmp_offset += 8) {
+ EXPECT_WRITE32(
+ RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ EXPECT_WRITE32(
+ RegForHart(0, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ EXPECT_WRITE32(
+ RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ }
+
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
+
+ dif_rv_timer timer;
+ EXPECT_EQ(dif_rv_timer_init(dev().region(),
+ {
+ .hart_count = 1, .comparator_count = 4,
+ },
+ &timer),
+ kDifRvTimerOk);
+}
+
+TEST_F(InitTest, FourEach) {
+ EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0);
+
+ for (uint32_t hart = 0; hart < 4; ++hart) {
+ EXPECT_WRITE32(IrqRegForHart(hart, 4, RV_TIMER_INTR_ENABLE0_REG_OFFSET),
+ 0x0);
+ EXPECT_WRITE32(IrqRegForHart(hart, 4, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ kAllOnes);
+
+ for (ptrdiff_t cmp_offset = 0; cmp_offset < 32; cmp_offset += 8) {
+ EXPECT_WRITE32(
+ RegForHart(hart, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ EXPECT_WRITE32(
+ RegForHart(hart, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ EXPECT_WRITE32(
+ RegForHart(hart, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ }
+
+ EXPECT_WRITE32(RegForHart(hart, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(RegForHart(hart, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
+ }
+
+ dif_rv_timer timer;
+ EXPECT_EQ(dif_rv_timer_init(dev().region(),
+ {
+ .hart_count = 4, .comparator_count = 4,
+ },
+ &timer),
+ kDifRvTimerOk);
+}
+
+TEST_F(InitTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_init(dev().region(),
+ {
+ .hart_count = 1, .comparator_count = 1,
+ },
+ nullptr),
+ kDifRvTimerBadArg);
+}
+
+TEST_F(InitTest, NoHartNoTimers) {
+ dif_rv_timer_t timer;
+ EXPECT_EQ(dif_rv_timer_init(dev().region(),
+ {
+ .hart_count = 0, .comparator_count = 1,
+ },
+ &timer),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_init(dev().region(),
+ {
+ .hart_count = 1, .comparator_count = 0,
+ },
+ &timer),
+ kDifRvTimerBadArg);
+}
+
+class ResetTest : public TimerTest {};
+
+TEST_F(ResetTest, OneEach) {
+ EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0);
+
+ EXPECT_WRITE32(IrqRegForHart(0, 1, RV_TIMER_INTR_ENABLE0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(IrqRegForHart(0, 1, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ kAllOnes);
+
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET), kAllOnes);
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET), kAllOnes);
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET), kAllOnes);
+
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
+
+ auto timer = MakeTimer({1, 1});
+ EXPECT_EQ(dif_rv_timer_reset(&timer), kDifRvTimerOk);
+}
+
+TEST_F(ResetTest, FourCmps) {
+ EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0);
+
+ EXPECT_WRITE32(IrqRegForHart(0, 4, RV_TIMER_INTR_ENABLE0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(IrqRegForHart(0, 4, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ kAllOnes);
+
+ for (ptrdiff_t cmp_offset = 0; cmp_offset < 32; cmp_offset += 8) {
+ EXPECT_WRITE32(
+ RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ EXPECT_WRITE32(
+ RegForHart(0, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ EXPECT_WRITE32(
+ RegForHart(0, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ }
+
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
+
+ auto timer = MakeTimer({1, 4});
+ EXPECT_EQ(dif_rv_timer_reset(&timer), kDifRvTimerOk);
+}
+
+TEST_F(ResetTest, FourSix) {
+ EXPECT_WRITE32(RV_TIMER_CTRL_REG_OFFSET, 0x0);
+
+ for (uint32_t hart = 0; hart < 4; ++hart) {
+ EXPECT_WRITE32(IrqRegForHart(hart, 6, RV_TIMER_INTR_ENABLE0_REG_OFFSET),
+ 0x0);
+ EXPECT_WRITE32(IrqRegForHart(hart, 6, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ kAllOnes);
+
+ for (ptrdiff_t cmp_offset = 0; cmp_offset < 48; cmp_offset += 8) {
+ EXPECT_WRITE32(
+ RegForHart(hart, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ EXPECT_WRITE32(
+ RegForHart(hart, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ EXPECT_WRITE32(
+ RegForHart(hart, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + cmp_offset,
+ kAllOnes);
+ }
+
+ EXPECT_WRITE32(RegForHart(hart, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
+ EXPECT_WRITE32(RegForHart(hart, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
+ }
+
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_reset(&timer), kDifRvTimerOk);
+}
+
+TEST_F(ResetTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_reset(nullptr), kDifRvTimerBadArg);
+}
+
+class SetParamsTest : public TimerTest {};
+
+TEST_F(SetParamsTest, Baseline) {
+ EXPECT_WRITE32(
+ RegForHart(2, RV_TIMER_CFG0_REG_OFFSET),
+ {{RV_TIMER_CFG0_PRESCALE_OFFSET, 400}, {RV_TIMER_CFG0_STEP_OFFSET, 25}});
+
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_set_tick_params(&timer, 2,
+ {.prescale = 400, .tick_step = 25}),
+ kDifRvTimerOk);
+}
+
+TEST_F(SetParamsTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_set_tick_params(nullptr, 2,
+ {.prescale = 400, .tick_step = 25}),
+ kDifRvTimerBadArg);
+}
+
+TEST_F(SetParamsTest, BadHart) {
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_set_tick_params(&timer, 4,
+ {.prescale = 400, .tick_step = 25}),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_set_tick_params(&timer, 500,
+ {.prescale = 400, .tick_step = 25}),
+ kDifRvTimerBadArg);
+}
+
+class TimerEnableTest : public TimerTest {};
+
+TEST_F(TimerEnableTest, Baseline) {
+ EXPECT_MASK32(RV_TIMER_CTRL_REG_OFFSET,
+ {{/*offset=*/1, /*mask=*/1, /*value=*/1}});
+ EXPECT_MASK32(RV_TIMER_CTRL_REG_OFFSET,
+ {{/*offset=*/3, /*mask=*/1, /*value=*/0}});
+
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_counter_set_enabled(&timer, 1, kDifRvTimerEnabled),
+ kDifRvTimerOk);
+ EXPECT_EQ(dif_rv_timer_counter_set_enabled(&timer, 3, kDifRvTimerDisabled),
+ kDifRvTimerOk);
+}
+
+TEST_F(TimerEnableTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_counter_set_enabled(nullptr, 1, kDifRvTimerEnabled),
+ kDifRvTimerBadArg);
+}
+
+TEST_F(TimerEnableTest, BadHart) {
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_counter_set_enabled(&timer, 4, kDifRvTimerEnabled),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_counter_set_enabled(&timer, 5, kDifRvTimerDisabled),
+ kDifRvTimerBadArg);
+}
+
+class CounterReadTest : public TimerTest {};
+
+TEST_F(CounterReadTest, Baseline) {
+ EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0222);
+ EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0333'0333);
+ EXPECT_READ32(RegForHart(0, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0222);
+
+ auto timer = MakeTimer({4, 6});
+ uint64_t value;
+ EXPECT_EQ(dif_rv_timer_counter_read(&timer, 0, &value), kDifRvTimerOk);
+ EXPECT_EQ(value, 0x0222'0222'0333'0333);
+}
+
+TEST_F(CounterReadTest, Overflow) {
+ EXPECT_READ32(RegForHart(1, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0222);
+ EXPECT_READ32(RegForHart(1, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0333'0333);
+ EXPECT_READ32(RegForHart(1, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0223);
+
+ EXPECT_READ32(RegForHart(1, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0223);
+ EXPECT_READ32(RegForHart(1, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0333'0444);
+ EXPECT_READ32(RegForHart(1, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0222'0223);
+
+ auto timer = MakeTimer({4, 6});
+ uint64_t value;
+ EXPECT_EQ(dif_rv_timer_counter_read(&timer, 1, &value), kDifRvTimerOk);
+ EXPECT_EQ(value, 0x0222'0223'0333'0444);
+}
+
+TEST_F(CounterReadTest, NullArgs) {
+ auto timer = MakeTimer({4, 6});
+ uint64_t value;
+ EXPECT_EQ(dif_rv_timer_counter_read(nullptr, 2, &value), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_counter_read(&timer, 2, nullptr), kDifRvTimerBadArg);
+}
+
+TEST_F(CounterReadTest, BadHart) {
+ auto timer = MakeTimer({4, 6});
+ uint64_t value;
+ EXPECT_EQ(dif_rv_timer_counter_read(&timer, 4, &value), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_counter_read(&timer, 5, &value), kDifRvTimerBadArg);
+}
+
+class ArmTest : public TimerTest {};
+
+TEST_F(ArmTest, Baseline) {
+ // Note: 16 = 2 * sizeof(uint64_t), since these need to be the registers
+ // for the third (index 2) comparator.
+ auto lower_reg = RegForHart(1, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) + 16;
+ auto upper_reg = RegForHart(1, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) + 16;
+
+ EXPECT_WRITE32(upper_reg, kAllOnes);
+ EXPECT_WRITE32(lower_reg, 0x0444'0555);
+ EXPECT_WRITE32(upper_reg, 0x0222'0333);
+
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_arm(&timer, 1, 2, 0x0222'0333'0444'0555),
+ kDifRvTimerOk);
+}
+
+TEST_F(ArmTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_arm(nullptr, 1, 2, 0x0222'0333'0444'0555),
+ kDifRvTimerBadArg);
+}
+
+TEST_F(ArmTest, BadHartBadComp) {
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_arm(&timer, 4, 2, 0x0222'0333'0444'0555),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_arm(&timer, 5, 2, 0x0222'0333'0444'0555),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_arm(&timer, 1, 6, 0x0222'0333'0444'0555),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_arm(&timer, 1, 7, 0x0222'0333'0444'0555),
+ kDifRvTimerBadArg);
+}
+
+class IrqEnableTest : public TimerTest {};
+
+TEST_F(IrqEnableTest, Baseline) {
+ EXPECT_MASK32(IrqRegForHart(1, 6, RV_TIMER_INTR_ENABLE0_REG_OFFSET),
+ {{/*offset=*/2, /*mask=*/1, /*value=*/1}});
+ EXPECT_MASK32(IrqRegForHart(3, 6, RV_TIMER_INTR_ENABLE0_REG_OFFSET),
+ {{/*offset=*/4, /*mask=*/1, /*value=*/0}});
+
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_irq_enable(&timer, 1, 2, kDifRvTimerEnabled),
+ kDifRvTimerOk);
+ EXPECT_EQ(dif_rv_timer_irq_enable(&timer, 3, 4, kDifRvTimerDisabled),
+ kDifRvTimerOk);
+}
+
+TEST_F(IrqEnableTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_irq_enable(nullptr, 1, 2, kDifRvTimerEnabled),
+ kDifRvTimerBadArg);
+}
+
+TEST_F(IrqEnableTest, BadHartBadComp) {
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_irq_enable(&timer, 4, 2, kDifRvTimerEnabled),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_enable(&timer, 5, 4, kDifRvTimerDisabled),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_enable(&timer, 1, 6, kDifRvTimerEnabled),
+ kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_enable(&timer, 3, 7, kDifRvTimerDisabled),
+ kDifRvTimerBadArg);
+}
+
+class IrqGetTest : public TimerTest {};
+
+TEST_F(IrqGetTest, Baseline) {
+ EXPECT_READ32(IrqRegForHart(1, 6, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ {
+ {/*offset=*/0, /*value=*/1},
+ {/*offset=*/2, /*value=*/1},
+ {/*offset=*/5, /*value=*/1},
+ });
+ EXPECT_READ32(IrqRegForHart(3, 6, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ {
+ {/*offset=*/0, /*value=*/1},
+ {/*offset=*/2, /*value=*/1},
+ {/*offset=*/5, /*value=*/1},
+ });
+
+ auto timer = MakeTimer({4, 6});
+ bool flag;
+ EXPECT_EQ(dif_rv_timer_irq_get(&timer, 1, 2, &flag), kDifRvTimerOk);
+ EXPECT_TRUE(flag);
+ EXPECT_EQ(dif_rv_timer_irq_get(&timer, 3, 4, &flag), kDifRvTimerOk);
+ EXPECT_FALSE(flag);
+}
+
+TEST_F(IrqGetTest, NullArgs) {
+ auto timer = MakeTimer({4, 6});
+ bool flag;
+ EXPECT_EQ(dif_rv_timer_irq_get(nullptr, 1, 2, &flag), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_get(&timer, 3, 4, nullptr), kDifRvTimerBadArg);
+}
+
+TEST_F(IrqGetTest, BadHartBadComp) {
+ auto timer = MakeTimer({4, 6});
+ bool flag;
+ EXPECT_EQ(dif_rv_timer_irq_get(&timer, 4, 2, &flag), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_get(&timer, 5, 4, &flag), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_get(&timer, 1, 6, &flag), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_get(&timer, 3, 7, &flag), kDifRvTimerBadArg);
+}
+
+class IrqClearTest : public TimerTest {};
+
+TEST_F(IrqClearTest, Baseline) {
+ EXPECT_WRITE32(IrqRegForHart(1, 6, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ {
+ {/*offset=*/2, /*value=*/1},
+ });
+ EXPECT_WRITE32(IrqRegForHart(3, 6, RV_TIMER_INTR_STATE0_REG_OFFSET),
+ {
+ {/*offset=*/4, /*value=*/1},
+ });
+
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_irq_clear(&timer, 1, 2), kDifRvTimerOk);
+ EXPECT_EQ(dif_rv_timer_irq_clear(&timer, 3, 4), kDifRvTimerOk);
+}
+
+TEST_F(IrqClearTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_irq_clear(nullptr, 1, 2), kDifRvTimerBadArg);
+}
+
+TEST_F(IrqClearTest, BadHartBadComp) {
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_irq_clear(&timer, 4, 2), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_clear(&timer, 5, 4), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_clear(&timer, 1, 6), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_clear(&timer, 3, 7), kDifRvTimerBadArg);
+}
+
+class IrqDisableTest : public TimerTest {};
+
+TEST_F(IrqDisableTest, Baseline) {
+ EXPECT_READ32(IrqRegForHart(1, 6, RV_TIMER_INTR_ENABLE0_REG_OFFSET),
+ 0b101010);
+ EXPECT_WRITE32(IrqRegForHart(1, 6, RV_TIMER_INTR_ENABLE0_REG_OFFSET), 0);
+
+ EXPECT_WRITE32(IrqRegForHart(3, 6, RV_TIMER_INTR_ENABLE0_REG_OFFSET), 0);
+
+ auto timer = MakeTimer({4, 6});
+ uint32_t state;
+ EXPECT_EQ(dif_rv_timer_irq_disable(&timer, 1, &state), kDifRvTimerOk);
+ EXPECT_EQ(state, 0b101010);
+ EXPECT_EQ(dif_rv_timer_irq_disable(&timer, 3, nullptr), kDifRvTimerOk);
+}
+
+TEST_F(IrqDisableTest, NullArgs) {
+ uint32_t state;
+ EXPECT_EQ(dif_rv_timer_irq_disable(nullptr, 1, &state), kDifRvTimerBadArg);
+}
+
+TEST_F(IrqDisableTest, BadHart) {
+ auto timer = MakeTimer({4, 6});
+ uint32_t state;
+ EXPECT_EQ(dif_rv_timer_irq_disable(&timer, 4, &state), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_disable(&timer, 5, &state), kDifRvTimerBadArg);
+}
+
+class IrqRestoreTest : public TimerTest {};
+
+TEST_F(IrqRestoreTest, Baseline) {
+ EXPECT_WRITE32(IrqRegForHart(1, 6, RV_TIMER_INTR_ENABLE0_REG_OFFSET),
+ 0b101010);
+ EXPECT_WRITE32(IrqRegForHart(3, 6, RV_TIMER_INTR_ENABLE0_REG_OFFSET),
+ 0b011011);
+
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_irq_restore(&timer, 1, 0b101010), kDifRvTimerOk);
+ EXPECT_EQ(dif_rv_timer_irq_restore(&timer, 3, 0b011011), kDifRvTimerOk);
+}
+
+TEST_F(IrqRestoreTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_irq_restore(nullptr, 1, 0), kDifRvTimerBadArg);
+}
+
+TEST_F(IrqRestoreTest, BadHart) {
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_irq_restore(&timer, 4, 0), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_restore(&timer, 5, 0), kDifRvTimerBadArg);
+}
+
+class IrqForceTest : public TimerTest {};
+
+TEST_F(IrqForceTest, Baseline) {
+ EXPECT_WRITE32(IrqRegForHart(1, 6, RV_TIMER_INTR_TEST0_REG_OFFSET),
+ {
+ {/*offset=*/2, /*value=*/1},
+ });
+ EXPECT_WRITE32(IrqRegForHart(3, 6, RV_TIMER_INTR_TEST0_REG_OFFSET),
+ {
+ {/*offset=*/4, /*value=*/1},
+ });
+
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_irq_force(&timer, 1, 2), kDifRvTimerOk);
+ EXPECT_EQ(dif_rv_timer_irq_force(&timer, 3, 4), kDifRvTimerOk);
+}
+
+TEST_F(IrqForceTest, NullArgs) {
+ EXPECT_EQ(dif_rv_timer_irq_force(nullptr, 1, 2), kDifRvTimerBadArg);
+}
+
+TEST_F(IrqForceTest, BadHartBadComp) {
+ auto timer = MakeTimer({4, 6});
+ EXPECT_EQ(dif_rv_timer_irq_force(&timer, 4, 2), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_force(&timer, 5, 4), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_force(&timer, 1, 6), kDifRvTimerBadArg);
+ EXPECT_EQ(dif_rv_timer_irq_force(&timer, 3, 7), kDifRvTimerBadArg);
+}
+} // namespace
+} // namespace dif_rv_timer_unittest
diff --git a/sw/device/tests/dif/meson.build b/sw/device/tests/dif/meson.build
index 000d02a..bf293c5 100644
--- a/sw/device/tests/dif/meson.build
+++ b/sw/device/tests/dif/meson.build
@@ -82,6 +82,22 @@
cpp_args: ['-DMOCK_MMIO'],
))
+test('dif_rv_timer_unittest', executable(
+ 'dif_rv_timer_unittest',
+ sources: [
+ hw_ip_rv_timer_reg_h,
+ meson.source_root() / 'sw/device/lib/dif/dif_rv_timer.c',
+ 'dif_rv_timer_unittest.cc',
+ ],
+ dependencies: [
+ sw_vendor_gtest,
+ sw_lib_testing_mock_mmio,
+ ],
+ native: true,
+ c_args: ['-DMOCK_MMIO'],
+ cpp_args: ['-DMOCK_MMIO'],
+))
+
dif_plic_sanitytest_lib = declare_dependency(
link_with: static_library(
'dif_plic_sanitytest_lib',