Software APIs
aon_timer_irq_test.c
1 // Copyright lowRISC contributors (OpenTitan project).
2 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3 // SPDX-License-Identifier: Apache-2.0
4 
5 #include <assert.h>
6 #include <limits.h>
7 #include <stdbool.h>
8 #include <stdint.h>
9 
10 #include "dt/dt_aon_timer.h" // Generated
11 #include "dt/dt_rv_core_ibex.h" // Generated
12 #include "dt/dt_rv_plic.h" // Generated
13 #include "dt/dt_rv_timer.h" // Generated
19 #include "sw/device/lib/runtime/irq.h"
21 #include "sw/device/lib/testing/aon_timer_testutils.h"
22 #include "sw/device/lib/testing/rand_testutils.h"
23 #include "sw/device/lib/testing/rv_plic_testutils.h"
24 #include "sw/device/lib/testing/test_framework/FreeRTOSConfig.h"
25 #include "sw/device/lib/testing/test_framework/check.h"
27 
28 OTTF_DEFINE_TEST_CONFIG();
29 
30 static const uint32_t kPlicTarget = 0;
31 static const uint32_t kTickFreqHz = 1000 * 1000; // 1Mhz / 1us
32 static dif_rv_core_ibex_t rv_core_ibex;
33 static dt_rv_core_ibex_t kRvCoreIbexDt = kDtRvCoreIbex;
34 static dif_aon_timer_t aon_timer;
35 static dt_aon_timer_t kAonTimerDt = kDtAonTimerAon;
36 static dif_rv_timer_t rv_timer;
37 static dt_rv_timer_t kRvTimerDt = kDtRvTimer;
38 static dif_rv_plic_t plic;
39 static dt_rv_plic_t kRvPlicDt = kDtRvPlic;
40 
41 static volatile dt_aon_timer_irq_t irq;
42 static volatile uint64_t irq_tick;
43 
44 // TODO:(lowrisc/opentitan#9984): Add timing API to the test framework
45 /**
46  * Initialize the rv timer to count the tick.
47  *
48  * The `ibex_mcycle_read` function uses the `mcycle` register to count
49  * instructions cycles executed and measure time elapsed between two instants,
50  * however it stops counting when the `wfi` is called. As this test is based on
51  * the `wfi` instruction the best approach then to measure the time elapsed is
52  * to use the mtime register, which is basically attached to rv_timer in the
53  * opentitan.
54  * https://docs.opentitan.org/hw/ip/rv_timer/doc/
55  *
56  * This is fine due to the test running in a single thread of execution,
57  * however, care should be taken in case it changes. OTTF configures the
58  * timer in vPortSetupTimerInterrupt, and re-initialising it inside the test
59  * could potentially break or cause unexpected behaviour of the test framework.
60  */
61 static_assert(configUSE_PREEMPTION == 0,
62  "rv_timer may be initialized already by FreeRtos");
63 
64 static void tick_init(void) {
65  CHECK_DIF_OK(dif_rv_timer_init_from_dt(kRvTimerDt, &rv_timer));
66 
67  CHECK_DIF_OK(dif_rv_timer_reset(&rv_timer));
68 
69  // Compute and set tick parameters (i.e., step, prescale, etc.).
70  dif_rv_timer_tick_params_t tick_params;
72  kTickFreqHz, &tick_params));
73 
74  CHECK_DIF_OK(
75  dif_rv_timer_set_tick_params(&rv_timer, kPlicTarget, tick_params));
76 
77  CHECK_DIF_OK(dif_rv_timer_counter_set_enabled(&rv_timer, kPlicTarget,
79 }
80 
81 /**
82  * Read the current rv timer count/tick.
83  *
84  * @return The current rv timer count.
85  */
86 static uint64_t tick_count_get(void) {
87  uint64_t tick = 0;
88  CHECK_DIF_OK(dif_rv_timer_counter_read(&rv_timer, kPlicTarget, &tick));
89  return tick;
90 }
91 
92 /**
93  * Execute the aon timer interrupt test.
94  */
95 static void execute_test(dif_aon_timer_t *aon_timer, uint64_t irq_time_us,
96  dt_aon_timer_irq_t expected_irq) {
97  // The interrupt time should be `irq_time_us ±5%`.
98  uint64_t variation = udiv64_slow(irq_time_us * 5, 100, NULL);
99  CHECK(variation > 0);
100  uint64_t sleep_range_h = irq_time_us + variation;
101  uint64_t sleep_range_l = irq_time_us - variation;
102 
103  // Add 1500 cpu cycles of overhead to cover irq handling.
104  sleep_range_h += udiv64_slow(1500 * 1000000, kClockFreqCpuHz, NULL);
105 
106  uint64_t count_cycles = 0;
107  CHECK_STATUS_OK(aon_timer_testutils_get_aon_cycles_64_from_us(irq_time_us,
108  &count_cycles));
109  LOG_INFO("Setting interrupt for %u us (%u cycles)", (uint32_t)irq_time_us,
110  (uint32_t)count_cycles);
111 
112  // TRY_CHECK(count_cycles <= UINT32_MAX,
113  // "The desired wake-up count 0x%08x%08x cannot fit into the 32 bits
114  // " "watchdog timer counter", (count_cycles >> 32),
115  // (uint32_t)count_cycles);
116 
117  // Set the default value to an invalid value.
118  irq = kDtAonTimerIrqCount;
119  if (expected_irq == kDtAonTimerIrqWkupTimerExpired) {
120  // Setup the wake up interrupt.
121  CHECK_STATUS_OK(aon_timer_testutils_wakeup_config(aon_timer, count_cycles));
122  } else {
123  // Setup the wdog bark interrupt.
124  CHECK_STATUS_OK(aon_timer_testutils_watchdog_config(
125  aon_timer,
126  /*bark_cycles=*/(uint32_t)count_cycles,
127  /*bite_cycles=*/(uint32_t)count_cycles * 4,
128  /*pause_in_sleep=*/false));
129  }
130  // Capture the current tick to measure the time the IRQ will take.
131  uint32_t start_tick = (uint32_t)tick_count_get();
132 
133  ATOMIC_WAIT_FOR_INTERRUPT(irq != kDtAonTimerIrqCount);
134 
135  uint32_t time_elapsed = (uint32_t)irq_tick - start_tick;
136  CHECK(time_elapsed <= sleep_range_h && time_elapsed >= sleep_range_l,
137  "Timer took %u usec which is not in the range %u usec and %u usec",
138  (uint32_t)time_elapsed, (uint32_t)sleep_range_l,
139  (uint32_t)sleep_range_h);
140 
141  CHECK(irq == expected_irq, "Interrupt type incorrect: exp = %d, obs = %d",
142  kDtAonTimerIrqWkupTimerExpired, irq);
143 
144  LOG_INFO("Test completed in %u us", (uint32_t)irq_time_us);
145 }
146 
147 /**
148  * External interrupt handler.
149  */
150 bool ottf_handle_irq(uint32_t *exc_info, dt_instance_id_t devid,
151  dif_rv_plic_irq_id_t irq_id) {
152  if (devid != dt_aon_timer_instance_id(kAonTimerDt)) {
153  return false;
154  }
155  // Calc the elapsed time since the activation of the IRQ.
156  irq_tick = tick_count_get();
157 
158  irq = dt_aon_timer_irq_from_plic_id(kAonTimerDt, irq_id);
159 
160  if (irq == kDtAonTimerIrqWkupTimerExpired) {
161  CHECK_DIF_OK(dif_aon_timer_wakeup_stop(&aon_timer));
162  } else if (irq == kDtAonTimerIrqWdogTimerBark) {
163  CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon_timer));
164  }
165 
166  CHECK_DIF_OK(dif_aon_timer_irq_acknowledge(&aon_timer, irq));
167  return true;
168 }
169 
170 /**
171  * OTTF external NMI internal IRQ handler.
172  * The ROM configures the watchdog to generates a NMI at bark, so we clean the
173  * NMI and wait the external irq handler next.
174  */
175 void ottf_external_nmi_handler(void) {
176  bool is_pending;
177  // The watchdog bark external interrupt is also connected to the NMI input
178  // of rv_core_ibex. We therefore expect the interrupt to be pending on the
179  // peripheral side (the check is done later in the test function).
180  CHECK_DIF_OK(dif_aon_timer_irq_is_pending(
181  &aon_timer, kDtAonTimerIrqWdogTimerBark, &is_pending));
182  CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon_timer));
183  // In order to handle the NMI we need to acknowledge the interrupt status
184  // bit it at the peripheral side.
185  CHECK_DIF_OK(
186  dif_aon_timer_irq_acknowledge(&aon_timer, kDtAonTimerIrqWdogTimerBark));
187 
188  CHECK_DIF_OK(dif_rv_core_ibex_clear_nmi_state(&rv_core_ibex,
189  kDifRvCoreIbexNmiSourceAll));
190 }
191 
192 bool test_main(void) {
193  // Enable global and external IRQ at Ibex.
194  irq_global_ctrl(true);
195  irq_external_ctrl(true);
196 
197  // Initialize the rv timer to compute the tick.
198  tick_init();
199 
200  // Initialize aon timer.
201  CHECK_DIF_OK(dif_aon_timer_init_from_dt(kAonTimerDt, &aon_timer));
202 
203  CHECK_DIF_OK(dif_rv_core_ibex_init_from_dt(kRvCoreIbexDt, &rv_core_ibex));
204 
205  // Initialize the PLIC.
206  CHECK_DIF_OK(dif_rv_plic_init_from_dt(kRvPlicDt, &plic));
207 
208  // Enable all the AON interrupts used in this test.
209  rv_plic_testutils_irq_range_enable(
210  &plic, kPlicTarget,
211  dt_aon_timer_irq_to_plic_id(kAonTimerDt, kDtAonTimerIrqWkupTimerExpired),
212  dt_aon_timer_irq_to_plic_id(kAonTimerDt, kDtAonTimerIrqWdogTimerBark));
213 
214  // Executing the test using random time bounds calculated from the clock
215  // frequency to make sure the aon timer is generating the interrupt after the
216  // chosen time and there's no error in the reference time measurement. This
217  // calculation is required as the various platforms used for testing have
218  // differing clocks frequencies. A minimum amount of cycles is required for
219  // the interrupt to note the elapsed time so the test fails with an
220  // unacceptable time variance when the sleep time is too low.
221  enum {
222  kMinCycles = 30 * 1000,
223  kMaxCycles = 45 * 1000,
224  };
225  uint64_t low_time_range =
226  udiv64_slow(kMinCycles * (uint64_t)1000000, kClockFreqCpuHz, NULL);
227  uint64_t high_time_range =
228  udiv64_slow(kMaxCycles * (uint64_t)1000000, kClockFreqCpuHz, NULL);
229 
230  // no error in the reference time measurement.
231  uint64_t irq_time = rand_testutils_gen32_range((uint32_t)low_time_range,
232  (uint32_t)high_time_range);
233  execute_test(&aon_timer, irq_time,
234  /*expected_irq=*/kDtAonTimerIrqWkupTimerExpired);
235 
236  irq_time = rand_testutils_gen32_range((uint32_t)low_time_range,
237  (uint32_t)high_time_range);
238  execute_test(&aon_timer, irq_time,
239  /*expected_irq=*/kDtAonTimerIrqWdogTimerBark);
240 
241  return true;
242 }