Software APIs
dif_rv_timer.c
1 // Copyright lowRISC contributors.
2 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3 // SPDX-License-Identifier: Apache-2.0
4 
6 
7 #include <assert.h>
8 #include <stddef.h>
9 
11 
12 #include "rv_timer_regs.h" // Generated.
13 
14 static_assert(RV_TIMER_PARAM_N_HARTS > 0,
15  "RV Timer must support at least one hart.");
16 static_assert(RV_TIMER_PARAM_N_TIMERS > 0,
17  "RV Timer must support at least one timer per hart.");
18 
19 /**
20  * The factor to multiply by to find the registers for the Nth hart.
21  *
22  * Given the hart N (zero-indexed), its timer registers are found at
23  * the memory address
24  * base_addr + ((N + 1) * kHartRegisterSpacing)
25  *
26  * The function `reg_for_hart()` can be used to compute the offset from
27  * `base` for the Nth hart for a particular repeated hardware register.
28  */
29 static const ptrdiff_t kHartRegisterSpacing = 0x100;
30 
31 /**
32  * Returns the MMIO offset for the register `reg_offset`, for the zero-indexed
33  * `hart`.
34  */
35 static ptrdiff_t reg_for_hart(uint32_t hart, ptrdiff_t reg_offset) {
36  return kHartRegisterSpacing * hart + reg_offset;
37 }
38 
39 /**
40  * A naive implementation of the Euclidean algorithm by repeated remainder.
41  */
42 static uint64_t euclidean_gcd(uint64_t a, uint64_t b) {
43  // TODO: The below 64-bit divide/remaider should be replaced with more
44  // space-efficient polyfills at some point.
45  while (b != 0) {
46  uint64_t old_b = b;
47  b = a % b;
48  a = old_b;
49  }
50 
51  return a;
52 }
53 
55  uint64_t clock_freq, uint64_t counter_freq,
57  if (out == NULL) {
58  return kDifBadArg;
59  }
60 
61  // We have the following relation:
62  // counter_freq = clock_freq * (step / (prescale + 1))
63  // We can solve for the individual parts as
64  // prescale = clock_freq / gcd - 1
65  // step = counter_freq / gcd
66  uint64_t gcd = euclidean_gcd(clock_freq, counter_freq);
67 
68  uint64_t prescale = clock_freq / gcd - 1;
69  uint64_t step = counter_freq / gcd;
70 
71  if (prescale > RV_TIMER_CFG0_PRESCALE_MASK ||
72  step > RV_TIMER_CFG0_STEP_MASK) {
73  return kDifBadArg;
74  }
75 
76  out->prescale = (uint16_t)prescale;
77  out->tick_step = (uint8_t)step;
78 
79  return kDifOk;
80 }
81 
83  uint32_t hart_id,
85  if (timer == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS) {
86  return kDifBadArg;
87  }
88 
89  uint32_t config_value = 0;
90  config_value = bitfield_field32_write(
91  config_value, RV_TIMER_CFG0_PRESCALE_FIELD, params.prescale);
92  config_value = bitfield_field32_write(config_value, RV_TIMER_CFG0_STEP_FIELD,
93  params.tick_step);
95  reg_for_hart(hart_id, RV_TIMER_CFG0_REG_OFFSET),
96  config_value);
97 
98  return kDifOk;
99 }
100 
102  uint32_t hart_id,
103  dif_toggle_t state) {
104  if (timer == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS) {
105  return kDifBadArg;
106  }
107 
108  switch (state) {
109  case kDifToggleEnabled:
111  RV_TIMER_CTRL_REG_OFFSET, hart_id);
112  break;
113  case kDifToggleDisabled:
115  RV_TIMER_CTRL_REG_OFFSET, hart_id);
116  break;
117  default:
118  return kDifBadArg;
119  }
120 
121  return kDifOk;
122 }
123 
125  uint32_t hart_id, uint64_t *out) {
126  if (timer == NULL || out == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS) {
127  return kDifBadArg;
128  }
129 
130  // We need to read a monotonically increasing, volatile uint64. To do so,
131  // we first read the upper half, then the lower half. Then, we check if the
132  // upper half reads the same value again. If it doesn't, it means that the
133  // lower half overflowed and we need to re-take the measurement.
134  while (true) {
135  uint32_t upper = mmio_region_read32(
136  timer->base_addr,
137  reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET));
138  uint32_t lower = mmio_region_read32(
139  timer->base_addr,
140  reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET));
141 
142  uint32_t overflow_check = mmio_region_read32(
143  timer->base_addr,
144  reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET));
145 
146  if (upper == overflow_check) {
147  *out = (((uint64_t)upper) << 32) | lower;
148  return kDifOk;
149  }
150  }
151 }
152 
154  uint32_t hart_id, uint64_t count) {
155  if (timer == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS) {
156  return kDifBadArg;
157  }
158 
159  // Disable the counter.
160  uint32_t ctrl_reg =
161  mmio_region_read32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET);
162  uint32_t ctrl_reg_cleared = bitfield_bit32_write(ctrl_reg, hart_id, false);
163  mmio_region_write32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET,
164  ctrl_reg_cleared);
165 
166  // Write the new count.
167  uint32_t lower_count = count;
168  uint32_t upper_count = count >> 32;
170  reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET),
171  lower_count);
173  reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET),
174  upper_count);
175 
176  // Re-enable the counter (if it was previously enabled).
177  mmio_region_write32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET, ctrl_reg);
178 
179  return kDifOk;
180 }
181 
182 dif_result_t dif_rv_timer_arm(const dif_rv_timer_t *timer, uint32_t hart_id,
183  uint32_t comp_id, uint64_t threshold) {
184  if (timer == NULL || hart_id >= RV_TIMER_PARAM_N_HARTS ||
185  comp_id >= RV_TIMER_PARAM_N_TIMERS) {
186  return kDifBadArg;
187  }
188 
189  uint32_t lower = threshold;
190  uint32_t upper = threshold >> 32;
191 
192  ptrdiff_t lower_reg =
193  reg_for_hart(hart_id, RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) +
194  (sizeof(uint64_t) * comp_id);
195  ptrdiff_t upper_reg =
196  reg_for_hart(hart_id, RV_TIMER_COMPARE_UPPER0_0_REG_OFFSET) +
197  (sizeof(uint64_t) * comp_id);
198 
199  // First, set the upper register to the largest value possible without setting
200  // off the alarm; this way, we can set the lower register without setting
201  // off the alarm.
202  mmio_region_write32(timer->base_addr, upper_reg, UINT32_MAX);
203 
204  // This can't set off the alarm because of the value we set above.
205  mmio_region_write32(timer->base_addr, lower_reg, lower);
206  // Finish writing the new value; this may set off an alarm immediately.
207  mmio_region_write32(timer->base_addr, upper_reg, upper);
208 
209  return kDifOk;
210 }
211 
212 /**
213  * The number of comparators that the IP instantiation this library is compiled
214  * against has. In other words, this tells us how far the statically known IRQ
215  * register constants are from the start of the comparators, which influences
216  * the computation in `irq_reg_for_hart()`.
217  */
218 static const ptrdiff_t kComparatorsInReggenHeader =
219  (RV_TIMER_INTR_ENABLE0_REG_OFFSET - RV_TIMER_COMPARE_LOWER0_0_REG_OFFSET) /
220  sizeof(uint64_t);
221 
222 /**
223  * Computes the appropriate register for a particular interrupt register, given
224  * a number of comparators.
225  *
226  * Currently, all IRQ registers are placed after the comparator registers,
227  * so the register offsets given by HW are not useable. The offsets need to be
228  * compensated for by a factor of `kComparatorsInReggenHeader` double-words..
229  *
230  * We also do not handle the case when comparator_count > 32, which would cause
231  * multiple interrupt registers to be generated.
232  */
233 static ptrdiff_t irq_reg_for_hart(uint32_t hart_id, uint32_t comparators,
234  ptrdiff_t reg_offset) {
235  // Note that it is completely valid for this value to be negative: if this
236  // library is built for hardware with a large number of compartors, this value
237  // is necessarially negative when used with hardware with a small number of
238  // comparators.
239  ptrdiff_t extra_comparator_offset =
240  sizeof(uint64_t) * (comparators - kComparatorsInReggenHeader);
241  return reg_for_hart(hart_id, reg_offset) + extra_comparator_offset;
242 }
243 
245  if (timer == NULL) {
246  return kDifBadArg;
247  }
248 
249  // Disable all counters.
250  mmio_region_write32(timer->base_addr, RV_TIMER_CTRL_REG_OFFSET, 0x0);
251 
252  for (uint32_t hart_id = 0; hart_id < RV_TIMER_PARAM_N_HARTS; ++hart_id) {
253  // Clear and disable all interrupts.
254  ptrdiff_t irq_status = irq_reg_for_hart(hart_id, RV_TIMER_PARAM_N_TIMERS,
255  RV_TIMER_INTR_STATE0_REG_OFFSET);
256  ptrdiff_t irq_enable = irq_reg_for_hart(hart_id, RV_TIMER_PARAM_N_TIMERS,
257  RV_TIMER_INTR_ENABLE0_REG_OFFSET);
258  mmio_region_write32(timer->base_addr, irq_enable, 0x0);
259  mmio_region_write32(timer->base_addr, irq_status, UINT32_MAX);
260 
261  // Reset all comparators to their default all-ones state.
262  for (uint32_t comp_id = 0; comp_id < RV_TIMER_PARAM_N_TIMERS; ++comp_id) {
263  dif_result_t error =
264  dif_rv_timer_arm(timer, hart_id, comp_id, UINT64_MAX);
265  if (error != kDifOk) {
266  return error;
267  }
268  }
269 
270  // Reset the counter to zero.
272  timer->base_addr,
273  reg_for_hart(hart_id, RV_TIMER_TIMER_V_LOWER0_REG_OFFSET), 0x0);
275  timer->base_addr,
276  reg_for_hart(hart_id, RV_TIMER_TIMER_V_UPPER0_REG_OFFSET), 0x0);
277  }
278 
279  return kDifOk;
280 }