Software APIs
otbn_mem_scramble_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 
7 #include "sw/device/lib/dif/dif_rv_core_ibex.h"
10 #include "sw/device/lib/testing/test_framework/check.h"
12 
13 static_assert(kDtOtbnCount >= 1,
14  "This test requires at least one OTBN instance");
15 // rv_core_ibex wrapper around the Ibex CPU provides additional functionality.
16 static_assert(kDtRvCoreIbexCount >= 1,
17  "This test requires at least one rv_core_ibex instance");
18 
19 static dt_otbn_t kTestOtbn = (dt_otbn_t)0;
20 static dt_rv_core_ibex_t kTestRvCoreIbex = (dt_rv_core_ibex_t)0;
21 
22 OTTF_DEFINE_TEST_CONFIG();
23 
24 typedef dif_result_t (*otbn_read_t)(const dif_otbn_t *otbn,
25  uint32_t offset_bytes, void *dest,
26  size_t len_bytes);
27 
28 typedef dif_result_t (*otbn_write_t)(const dif_otbn_t *otbn,
29  uint32_t offset_bytes, const void *src,
30  size_t len_bytes);
31 
32 enum {
33  /**
34  * Number of distinct addresses to check in each IMEM and DMEM.
35  */
36  kNumAddrs = 50,
37 
38  /**
39  * Minimum number of expected integrity errors in IMEM and DMEM after
40  * re-scrambling.
41  * Note that there are `2^32` valid code words and that each non-valid code
42  * word triggers an error. Therefore, the probability that a random 39-bit
43  * word triggers an error is: `(2^39 - 2^32)/ 2^39 = 127/128`. Then the
44  * probability that all `kNumAddrs` triggers an errors is
45  * `(127/128)^kNumAddrs` after re-scrambling.
46  *
47  * The Generic formula:
48  * (w-i)
49  * 127
50  * Pr(i) = -------- x (w choose i)
51  * w
52  * 128
53  * Where:
54  * w = The number of words tested.
55  * i = The number of words that may not generate errors.
56  * Pr(i) = Probability that i words will not generate an ECC error.
57  *
58  * So for i in (0..4):
59  *
60  * ``` Python
61  * from math import comb
62  * w = 50
63  * t = 0
64  * for i in range(5):
65  * p = ((127**(w-i))/(128**w)) * comb(w,i)
66  * t += p
67  * print(f'Pr({i}): { round(p, 4)},\tsum{{Pr(0-{i})}}: {round(t, 6)}')
68  * ```
69  * ```
70  * Pr(0): 0.6756, sum{Pr(0-0)}: 0.675597
71  * Pr(1): 0.266, sum{Pr(0-1)}: 0.94158
72  * Pr(2): 0.0513, sum{Pr(0-2)}: 0.992891
73  * Pr(3): 0.0065, sum{Pr(0-3)}: 0.999356
74  * Pr(4): 0.0006, sum{Pr(0-4)}: 0.999954
75  * ```
76  * So by choosing `(kNumAddrs - 2) = 48` as the threshold we will have a
77  * probability of `1 - 0.992891 = 0.71%` that this test will fail randomly due
78  * to ECC errors not being generated. That seems a reasonable number.
79  */
80  kNumIntgErrorsThreshold = kNumAddrs - 2,
81 };
82 static_assert(kNumAddrs == 50,
83  "kNumAddrs changed, so kEccErrorProbability should be "
84  "computed again");
85 
86 static volatile bool has_irq_fired;
87 
88 /**
89  * This overrides the default OTTF load integrity handler.
90  */
91 void ottf_load_integrity_error_handler(uint32_t *exc_info) {
92  has_irq_fired = true;
93 }
94 
95 /**
96  * Get `num` distinct random numbers in the range [0, `max`] from
97  * RV_CORE_IBEX_RND_DATA.
98  *
99  * @param ibex The Ibex DIF object.
100  * @param num The number of random numbers to get.
101  * @param[out] rnd_buf Pointer to the buffer to write the random numbers to.
102  * @param max The maximum random value returned.
103  */
104 static void get_rand_words(dif_rv_core_ibex_t *ibex, int num, uint32_t *rnd_buf,
105  uint32_t max) {
106  uint32_t rnd_word;
107  for (int i = 0; i < num; ++i) {
108  bool found = false;
109  while (found == false) {
110  // Get a new random number.
111  CHECK_DIF_OK(dif_rv_core_ibex_read_rnd_data(ibex, &rnd_word));
112  rnd_word = rnd_word % max;
113  // Check if the number is unique.
114  found = true;
115  for (int j = 0; j < i; ++j) {
116  if (rnd_buf[j] == rnd_word) {
117  // Start over.
118  found = false;
119  break;
120  }
121  }
122  }
123  // Add the number to the buffer.
124  rnd_buf[i] = rnd_word;
125  }
126 }
127 
128 /**
129  * Write values found at `word_addrs` to OTBN memory at addresses `word_addrs`.
130  *
131  * @param ctx The otbn context object.
132  * @param num The number of addresses to write.
133  * @param word_addrs The data to write and the word addresses to write to.
134  * @param otbn_write Pointer to the function to write the memory. It can be
135  * either `dif_otbn_imem_write` or `dif_otbn_dmem_write`.
136  */
137 static void otbn_write_mem_words(const dif_otbn_t *otbn, const int num,
138  const uint32_t *word_addrs,
139  otbn_write_t otbn_write) {
140  for (int i = 0; i < num; ++i) {
141  otbn_write(otbn, word_addrs[i] * sizeof(uint32_t), (void *)&word_addrs[i],
142  sizeof(uint32_t));
143  }
144 }
145 
146 /**
147  * Check whether the contents at addresses `word_addrs` of an OTBN memory match
148  * the reference data pointed at `word_addrs`.
149  *
150  * @param ctx The otbn context object.
151  * @param num The number of addresses to check.
152  * @param word_addrs The word addresses to check.
153  * @param otbn_read Pointer to the function to read the memory. It can be
154  * either `dif_otbn_imem_read` or `dif_otbn_dmem_read`.
155  * @param[out] num_matches Pointer to the number of observed matches.
156  * @param[out] num_intg_errors Pointer to the number of observed integrity
157  * errors.
158  */
159 static void otbn_check_mem_words(const dif_otbn_t *otbn, const int num,
160  const uint32_t *word_addrs,
161  otbn_read_t otbn_read, int *num_matches,
162  int *num_intg_errors) {
163  *num_matches = 0;
164  *num_intg_errors = 0;
165 
166  uint32_t word;
167  bool match;
168  for (int i = 0; i < num; ++i) {
169  // If the memory has been scrambled we expect to receive an IRQ due to the
170  // integrity error.
171  has_irq_fired = false;
172  otbn_read(otbn, word_addrs[i] * sizeof(uint32_t), (void *)&word,
173  sizeof(uint32_t));
174  match = (word_addrs[i] == word);
175  if (match) {
176  *num_matches += 1;
177  }
178  if (has_irq_fired) {
179  *num_intg_errors += 1;
180  }
181  // It is possible that the integrity bits after re-scrambling are still
182  // valid.
183  if ((match == false) && (has_irq_fired == false)) {
184  LOG_INFO(
185  "Mismatch without integrity error: Entry %i, address 0x%x, "
186  "data 0x%x",
187  i, word_addrs[i], word);
188  }
189  }
190 }
191 
192 bool test_main(void) {
193  // Init OTBN DIF.
194  dif_otbn_t otbn;
195  CHECK_DIF_OK(dif_otbn_init_from_dt(kTestOtbn, &otbn));
196 
197  // Init Ibex DIF.
198  dif_rv_core_ibex_t ibex;
199  CHECK_DIF_OK(dif_rv_core_ibex_init_from_dt(kTestRvCoreIbex, &ibex));
200 
201  uint32_t imem_offsets[kNumAddrs];
202  uint32_t dmem_offsets[kNumAddrs];
203  uint32_t max;
204  int num_matches_imem, num_intg_errors_imem;
205  int num_matches_dmem, num_intg_errors_dmem;
206 
207  // Get random address offsets to check.
208  max = (uint32_t)dif_otbn_get_imem_size_bytes(&otbn) / sizeof(uint32_t) - 1;
209  get_rand_words(&ibex, kNumAddrs, imem_offsets, max);
210  max = (uint32_t)dif_otbn_get_dmem_size_bytes(&otbn) / sizeof(uint32_t) - 1;
211  get_rand_words(&ibex, kNumAddrs, dmem_offsets, max);
212 
213  // Wait for OTBN to be idle.
214  CHECK_STATUS_OK(otbn_testutils_wait_for_done(&otbn, kDifOtbnErrBitsNoError));
215 
216  // Write random address offsets.
217  otbn_write_mem_words(&otbn, kNumAddrs, imem_offsets, dif_otbn_imem_write);
218  otbn_write_mem_words(&otbn, kNumAddrs, dmem_offsets, dif_otbn_dmem_write);
219 
220  // Read back and check random address offsets. All values must match, we must
221  // not see any integrity errors.
222  otbn_check_mem_words(&otbn, kNumAddrs, imem_offsets, dif_otbn_imem_read,
223  &num_matches_imem, &num_intg_errors_imem);
224  CHECK(num_matches_imem == kNumAddrs, "%i unexpected IMEM mismatches",
225  kNumAddrs - num_matches_imem);
226  CHECK(!num_intg_errors_imem, "%i unexpected IMEM integrity errors",
227  num_intg_errors_imem);
228 
229  otbn_check_mem_words(&otbn, kNumAddrs, dmem_offsets, dif_otbn_dmem_read,
230  &num_matches_dmem, &num_intg_errors_dmem);
231  CHECK(num_matches_dmem == kNumAddrs, "%i unexpected DMEM mismatches",
232  kNumAddrs - num_matches_dmem);
233  CHECK(!num_intg_errors_dmem, "%i unexpected DMEM integrity errors",
234  num_intg_errors_dmem);
235 
236  // Re-scramble IMEM and DMEM by fetching new scrambling keys from OTP.
237  CHECK_DIF_OK(dif_otbn_write_cmd(&otbn, kDifOtbnCmdSecWipeImem));
238  CHECK_STATUS_OK(otbn_testutils_wait_for_done(&otbn, kDifOtbnErrBitsNoError));
239  CHECK_DIF_OK(dif_otbn_write_cmd(&otbn, kDifOtbnCmdSecWipeDmem));
240  CHECK_STATUS_OK(otbn_testutils_wait_for_done(&otbn, kDifOtbnErrBitsNoError));
241 
242  // Read back and check random address offsets. We don't care about the values.
243  // "Most" reads should trigger integrity errors.
244  otbn_check_mem_words(&otbn, kNumAddrs, imem_offsets, dif_otbn_imem_read,
245  &num_matches_imem, &num_intg_errors_imem);
246  CHECK(num_intg_errors_imem >= kNumIntgErrorsThreshold,
247  "Expecting at least %i IMEM integrity errors, got %i",
248  kNumIntgErrorsThreshold, num_intg_errors_imem);
249  otbn_check_mem_words(&otbn, kNumAddrs, dmem_offsets, dif_otbn_dmem_read,
250  &num_matches_dmem, &num_intg_errors_dmem);
251  CHECK(num_intg_errors_dmem >= kNumIntgErrorsThreshold,
252  "Expecting at least %i DMEM integrity errors, got %i",
253  kNumIntgErrorsThreshold, num_intg_errors_dmem);
254 
255  return true;
256 }