Software APIs
nvm_testutils.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 "sw/device/lib/testing/nvm_testutils.h"
6
7#include <string.h>
8
12#include "sw/device/lib/base/multibits.h"
13#include "sw/device/lib/testing/test_framework/check.h"
14
15#if defined(USE_RRAM)
17#include "sw/device/lib/testing/rram_ctrl_testutils.h"
18// RRAM only exists on Earlgrey.
20#else
22#include "sw/device/lib/testing/flash_ctrl_testutils.h"
23#include "sw/device/silicon_creator/lib/drivers/flash_ctrl.h"
24#if defined(OPENTITAN_IS_EARLGREY)
26#elif defined(OPENTITAN_IS_ENGLISHBREAKFAST)
28#else
29#error \
30 "nvm_testutils.c requires OPENTITAN_IS_EARLGREY or OPENTITAN_IS_ENGLISHBREAKFAST"
31#endif
32#endif // USE_RRAM
33
34// Chunk size for readback verification — small enough to live on the stack.
35enum { kNvmMaxWordCount = 16 };
36
37#if defined(USE_RRAM)
38// RRAM controller implementation
39
40const nvm_page_perms_t kPageReadOnly = {.read = kMultiBitBool4True,
41 .write = kMultiBitBool4False};
42
43const nvm_page_perms_t kPageReadWrite = {.read = kMultiBitBool4True,
44 .write = kMultiBitBool4True};
45
46const nvm_page_perms_t kPageWriteOnly = {.read = kMultiBitBool4False,
47 .write = kMultiBitBool4True};
48
49const nvm_page_cfg_t kPageScrambleCfg = {.scrambling = kMultiBitBool4True,
50 .ecc = kMultiBitBool4True};
51
52const nvm_page_cfg_t kPagePlainCfg = {.scrambling = kMultiBitBool4False,
53 .ecc = kMultiBitBool4True};
54
55const nvm_page_cfg_t kPageRawCfg = {.scrambling = kMultiBitBool4False,
56 .ecc = kMultiBitBool4False};
57
58static status_t dif_rram_state_init(dif_rram_ctrl_state_t *rram) {
59 TRY(dif_rram_ctrl_init_state(
60 rram, mmio_region_from_addr(TOP_EARLGREY_RRAM_CTRL_CORE_BASE_ADDR)));
61 return OK_STATUS();
62}
63
64status_t nvm_testutils_wait_for_init(void) {
66 TRY(dif_rram_state_init(&rram));
67 dif_rram_ctrl_phy_status_t phy_status;
68 do {
69 TRY(dif_rram_ctrl_get_phy_status(&rram, &phy_status));
70 } while (!phy_status.phy_init_done);
71 return OK_STATUS();
72}
73
74status_t nvm_testutils_rom_init(uint32_t otp_nvm_default_cfg) {
76 TRY(dif_rram_state_init(&rram));
77 TRY(dif_rram_ctrl_start_controller_init(&rram));
79 do {
80 TRY(dif_rram_ctrl_get_status(&rram, &status));
81 } while (!status.controller_init_done);
82
83 // Unlike for flash_ctrl, where the memory protection only gates the
84 // controller's own transaction-based (FIFO) reads/writes, the memory
85 // protection regions of rram_ctrl also gate the direct read path. Hence,
86 // RD_EN needs to be set in order to be able to read from RRAM.
88 TRY(dif_rram_ctrl_get_default_region_properties(&rram, &props));
89 props.rd_en = kMultiBitBool4True;
90 if (otp_nvm_default_cfg != 0) {
91 // RRAM has no high-endurance concept, so RRAM_CTRL_OTP_FIELD_HE is not
92 // applied here.
93 props.scramble_en = bitfield_field32_read(otp_nvm_default_cfg,
94 RRAM_CTRL_OTP_FIELD_SCRAMBLING);
95 props.ecc_en =
96 bitfield_field32_read(otp_nvm_default_cfg, RRAM_CTRL_OTP_FIELD_ECC);
97 }
98 TRY(dif_rram_ctrl_set_default_region_properties(&rram, props));
99 TRY(dif_rram_ctrl_set_rram_enablement(&rram, kDifToggleEnabled));
100 TRY(dif_rram_ctrl_set_exec_enablement(&rram, kDifToggleEnabled));
101 return OK_STATUS();
102}
103
104static status_t rram_info_page_set_props(
105 dif_rram_ctrl_state_t *rram, uint32_t page_id,
107 return rram_ctrl_testutils_info_region_setup_properties(rram, page_id, props,
108 /*offset=*/NULL);
109}
110
111// RRAM writes must be 16-byte aligned and a multiple of 4 words (see
112// dif_rram_ctrl_start()). Write aligned interior blocks directly; fall back
113// to the single-word helper for any misaligned leading/trailing words.
114static status_t rram_write_words(
115 dif_rram_ctrl_state_t *rram, uint32_t addr, const uint32_t *data,
116 size_t word_count, dif_rram_ctrl_partition_type_t partition_type) {
117 enum { kRramWriteWords = 4, kRramWriteBytes = kRramWriteWords * 4 };
118 size_t remaining_words = word_count;
119 const uint32_t *src = data;
120 while (remaining_words > 0) {
121 if (addr % kRramWriteBytes == 0 && remaining_words >= kRramWriteWords) {
122 size_t full_words = (remaining_words / kRramWriteWords) * kRramWriteWords;
123 TRY(rram_ctrl_testutils_write(rram, addr, src, partition_type,
124 (uint32_t)full_words));
125 addr += (uint32_t)(full_words * sizeof(uint32_t));
126 src += full_words;
127 remaining_words -= full_words;
128 continue;
129 }
130 TRY(rram_ctrl_testutils_write_word(rram, addr, src, partition_type));
131 addr += sizeof(uint32_t);
132 src++;
133 remaining_words--;
134 }
135 return OK_STATUS();
136}
137
138static status_t rram_relocated_region_enable(dif_rram_ctrl_state_t *rram,
139 const rram_ctrl_info_page_t *p,
140 nvm_page_cfg_t cfg) {
142 .rd_en = kMultiBitBool4True,
143 .wr_en = kMultiBitBool4True,
144 .scramble_en = cfg.scrambling,
145 .ecc_en = cfg.ecc,
146 };
147 // `CreatorReserved0` lives in Region A instead of Region B, shared by
148 // every other relocated page; see the comment on `kRramCtrlEmulRegionA` in
149 // rram_ctrl.h. Each region's count covers its whole reserved window (not
150 // just the pages currently named in the table), matching
151 // `nvm_ctrl_init()`'s grant -- except Region B's excludes its OTP tail.
152 uint32_t base = kRramCtrlEmulPageBaseB;
153 uint32_t region = kRramCtrlEmulRegionB;
154 uint32_t count =
155 kRramCtrlEmulPageEndB - kRramCtrlOtpPageCount - kRramCtrlEmulPageBaseB;
156 if (p->page_id >= kRramCtrlEmulPageBaseA &&
157 p->page_id < kRramCtrlEmulPageEndA) {
158 base = kRramCtrlEmulPageBaseA;
159 region = kRramCtrlEmulRegionA;
160 count = kRramCtrlEmulPageEndA - kRramCtrlEmulPageBaseA;
161 }
162 return rram_ctrl_testutils_data_region_setup_properties(
163 rram, base, region, count, properties, /*offset=*/NULL);
164}
165
166status_t nvm_testutils_info_page_setup(nvm_info_page_t page,
167 nvm_page_perms_t perms,
168 nvm_page_cfg_t cfg) {
169 const rram_ctrl_info_page_t *p = nvm_ctrl_rram_page_info(page);
171 TRY(dif_rram_state_init(&rram));
172 if (p->emulated) {
173 // `perms` is ignored: the shared region is always readable/writable.
174 return rram_relocated_region_enable(&rram, p, cfg);
175 }
176 // RRAM has no separate erase permission or high-endurance concept, so
177 // `perms.erase` and `cfg.he` are ignored here.
179 .rd_en = perms.read,
180 .wr_en = perms.write,
181 .scramble_en = cfg.scrambling,
182 .ecc_en = cfg.ecc,
183 };
184 return rram_info_page_set_props(&rram, p->page_id, props);
185}
186
187status_t nvm_testutils_write_info_page(nvm_info_page_t page,
188 uint32_t byte_offset,
189 const uint32_t *data, size_t word_count,
190 bool erase_before_write, bool readback) {
191 const rram_ctrl_info_page_t *p = nvm_ctrl_rram_page_info(page);
192 // RRAM has no hardware erase step, but callers requesting
193 // `erase_before_write` still expect the rest of the page to come back to a
194 // known blank (all-ones) state first, not just have this write's bytes
195 // land on top of whatever was there before.
196 if (erase_before_write) {
197 TRY(nvm_ctrl_info_erase(page));
198 }
199
201 TRY(dif_rram_state_init(&rram));
202 dif_rram_ctrl_device_info_t info = dif_rram_ctrl_get_device_info();
203 dif_rram_ctrl_partition_type_t partition_type =
204 !p->emulated ? kDifRramCtrlPartitionTypeInfo
205 : kDifRramCtrlPartitionTypeData;
206 uint32_t address = p->page_id * info.bytes_per_page + byte_offset;
207 TRY(rram_write_words(&rram, address, data, word_count, partition_type));
208
209 if (readback) {
210 uint32_t rb_buf[kNvmMaxWordCount];
211 size_t remaining = word_count;
212 size_t chunk_offset = 0;
213 while (remaining > 0) {
214 size_t chunk =
215 remaining < kNvmMaxWordCount ? remaining : kNvmMaxWordCount;
216 TRY(rram_ctrl_testutils_read(&rram,
217 address + chunk_offset * sizeof(uint32_t),
218 rb_buf, partition_type, (uint32_t)chunk,
219 /*delay_micros=*/0));
220 TRY_CHECK_ARRAYS_EQ(rb_buf, data + chunk_offset, chunk,
221 "NVM write readback mismatch at page %d offset %d",
222 page, byte_offset + chunk_offset * sizeof(uint32_t));
223 chunk_offset += chunk;
224 remaining -= chunk;
225 }
226 }
227 return OK_STATUS();
228}
229
230status_t nvm_testutils_read_info_page(nvm_info_page_t page,
231 uint32_t byte_offset, uint32_t *data,
232 size_t word_count) {
233 const rram_ctrl_info_page_t *p = nvm_ctrl_rram_page_info(page);
235 TRY(dif_rram_state_init(&rram));
236 dif_rram_ctrl_device_info_t info = dif_rram_ctrl_get_device_info();
237 dif_rram_ctrl_partition_type_t partition_type =
238 !p->emulated ? kDifRramCtrlPartitionTypeInfo
239 : kDifRramCtrlPartitionTypeData;
240 uint32_t address = p->page_id * info.bytes_per_page + byte_offset;
241 return rram_ctrl_testutils_read(&rram, address, data, partition_type,
242 (uint32_t)word_count, /*delay_micros=*/0);
243}
244
245status_t nvm_testutils_info_page_lock(nvm_info_page_t page, bool lock) {
246 if (!lock) {
247 return OK_STATUS();
248 }
249 const rram_ctrl_info_page_t *p = nvm_ctrl_rram_page_info(page);
250 if (p->emulated) {
251 // The shared relocated-page region is intentionally never locked (see
252 // TODO above): locking it would freeze every relocated page at once.
253 return OK_STATUS();
254 }
256 TRY(dif_rram_state_init(&rram));
257 dif_rram_ctrl_info_region_t region = {.page = p->page_id};
258 TRY(dif_rram_ctrl_lock_info_region_properties(&rram, region));
259 return OK_STATUS();
260}
261
262status_t nvm_testutils_info_page_print(nvm_info_page_t page) {
263 const rram_ctrl_info_page_t *p = nvm_ctrl_rram_page_info(page);
265 TRY(dif_rram_state_init(&rram));
266 dif_rram_ctrl_info_region_t region = {.page = p->page_id};
268 bool locked;
269 if (p->emulated) {
270 // Emulated pages aren't real info regions at the DIF level: they're
271 // backed by a data-partition MP region instead -- `kRramCtrlEmulRegionB`
272 // for most of them, or `kRramCtrlEmulRegionA` for `CreatorReserved0`
273 // (see the comment on the latter in rram_ctrl.h). Query that instead,
274 // but keep printing under the page's own label.
275 uint32_t data_region = p->page_id >= kRramCtrlEmulPageBaseA &&
276 p->page_id < kRramCtrlEmulPageEndA
277 ? kRramCtrlEmulRegionA
278 : kRramCtrlEmulRegionB;
280 TRY(dif_rram_ctrl_get_data_region_properties(&rram, data_region,
281 &data_props));
282 TRY(dif_rram_ctrl_data_region_is_locked(&rram, data_region, &locked));
283 props = data_props.properties;
284 } else {
285 TRY(dif_rram_ctrl_get_info_region_properties(&rram, region, &props));
286 TRY(dif_rram_ctrl_info_region_is_locked(&rram, region, &locked));
287 }
288 rram_ctrl_testutils_info_region_print(region, &props, locked);
289 return OK_STATUS();
290}
291
292// `base`/`size` are in units of `NVM_BYTES_PER_PAGE`, i.e. RRAM's own page
293// size; callers must not compute them from flash-specific page-size
294// constants.
295status_t nvm_testutils_data_region_setup(uint32_t region, uint32_t base,
296 uint32_t size, nvm_page_perms_t perms,
297 nvm_page_cfg_t cfg) {
299 TRY(dif_rram_state_init(&rram));
300 // RRAM has no separate erase permission or high-endurance concept, so
301 // `perms.erase` and `cfg.he` are ignored here.
303 .base = base,
304 .size = size,
305 .properties =
306 {
307 .rd_en = perms.read,
308 .wr_en = perms.write,
309 .scramble_en = cfg.scrambling,
310 .ecc_en = cfg.ecc,
311 },
312 };
313 TRY(dif_rram_ctrl_set_data_region_properties(&rram, region, config));
314 TRY(dif_rram_ctrl_set_data_region_enablement(&rram, region,
316 return OK_STATUS();
317}
318
319status_t nvm_testutils_data_region_lock(uint32_t region, bool lock) {
320 if (!lock) {
321 return OK_STATUS();
322 }
324 TRY(dif_rram_state_init(&rram));
325 TRY(dif_rram_ctrl_lock_data_region_properties(&rram, region));
326 return OK_STATUS();
327}
328
329status_t nvm_testutils_data_write(uint32_t byte_address, const uint32_t *data,
330 size_t word_count, bool erase_before_write) {
331 // RRAM has no erase step; writes overwrite in place.
332 (void)erase_before_write;
333
335 TRY(dif_rram_state_init(&rram));
336 return rram_write_words(&rram, byte_address, data, word_count,
337 kDifRramCtrlPartitionTypeData);
338}
339
340status_t nvm_testutils_set_exec_enablement(bool enable) {
342 TRY(dif_rram_state_init(&rram));
343 TRY(dif_rram_ctrl_set_exec_enablement(
344 &rram, enable ? kDifToggleEnabled : kDifToggleDisabled));
345 return OK_STATUS();
346}
347
348status_t nvm_testutils_show_faults(void) {
350 TRY(dif_rram_state_init(&rram));
351 dif_rram_ctrl_faults_t faults;
352 TRY(dif_rram_ctrl_get_faults(&rram, &faults));
353#define LOG_IF_FIELD_SET(_struct, _field) \
354 if (_struct._field) { \
355 LOG_INFO("Rram_ctrl fault status has " #_field); \
356 }
357 LOG_IF_FIELD_SET(faults, lcmgr_operation_error);
358 LOG_IF_FIELD_SET(faults, lcmgr_memory_protection_error);
359 LOG_IF_FIELD_SET(faults, lcmgr_read_error);
360 LOG_IF_FIELD_SET(faults, lcmgr_write_error);
361 LOG_IF_FIELD_SET(faults, otp_operation_error);
362 LOG_IF_FIELD_SET(faults, otp_memory_protection_error);
363 LOG_IF_FIELD_SET(faults, otp_read_error);
364 LOG_IF_FIELD_SET(faults, otp_write_error);
365#undef LOG_IF_FIELD_SET
366 return OK_STATUS();
367}
368
369status_t nvm_testutils_default_region_setup(nvm_page_perms_t perms,
370 nvm_page_cfg_t cfg) {
372 TRY(dif_rram_state_init(&rram));
373 // RRAM has no separate erase permission or high-endurance concept, so
374 // `perms.erase` and `cfg.he` are ignored here.
376 .rd_en = perms.read,
377 .wr_en = perms.write,
378 .scramble_en = cfg.scrambling,
379 .ecc_en = cfg.ecc,
380 };
381 TRY(dif_rram_ctrl_set_default_region_properties(&rram, props));
382 return OK_STATUS();
383}
384
385status_t nvm_testutils_default_region_get(nvm_page_perms_t *perms,
386 nvm_page_cfg_t *cfg) {
388 TRY(dif_rram_state_init(&rram));
390 TRY(dif_rram_ctrl_get_default_region_properties(&rram, &props));
391 if (perms != NULL) {
392 perms->read = (uint32_t)props.rd_en;
393 perms->write = (uint32_t)props.wr_en;
394 // RRAM has no separate erase permission.
395 perms->erase = kMultiBitBool4False;
396 }
397 if (cfg != NULL) {
398 cfg->scrambling = (uint32_t)props.scramble_en;
399 cfg->ecc = (uint32_t)props.ecc_en;
400 // RRAM has no high-endurance concept.
401 cfg->he = kMultiBitBool4False;
402 }
403 return OK_STATUS();
404}
405
406#else
407// Flash controller implementation
408
409const nvm_page_perms_t kPageReadOnly = {.read = kMultiBitBool4True,
410 .write = kMultiBitBool4False,
411 .erase = kMultiBitBool4False};
412
413const nvm_page_perms_t kPageReadWrite = {.read = kMultiBitBool4True,
414 .write = kMultiBitBool4True,
415 .erase = kMultiBitBool4True};
416
417const nvm_page_perms_t kPageWriteOnly = {.read = kMultiBitBool4False,
418 .write = kMultiBitBool4True,
419 .erase = kMultiBitBool4True};
420
421const nvm_page_cfg_t kPageScrambleCfg = {.scrambling = kMultiBitBool4True,
422 .ecc = kMultiBitBool4True,
423 .he = kMultiBitBool4False};
424
425const nvm_page_cfg_t kPagePlainCfg = {.scrambling = kMultiBitBool4False,
426 .ecc = kMultiBitBool4True,
427 .he = kMultiBitBool4False};
428
429const nvm_page_cfg_t kPageRawCfg = {.scrambling = kMultiBitBool4False,
430 .ecc = kMultiBitBool4False,
431 .he = kMultiBitBool4False};
432
433// Physical location of a logical NVM info page.
434typedef struct {
435 uint32_t page_id;
436 uint32_t bank;
437 uint32_t partition_id;
439
440// Mapping from nvm_info_page_t to physical flash parameters.
441// Update this table when switching to a different NVM technology.
442// clang-format off
443static const nvm_page_phys_t kPageMap[] = {
444 // Bank 0, pages 0-9
445 [kNvmInfoPageFactoryId] = {.page_id = 0, .bank = 0, .partition_id = 0},
446 [kNvmInfoPageCreatorSecret] = {.page_id = 1, .bank = 0, .partition_id = 0},
447 [kNvmInfoPageOwnerSecret] = {.page_id = 2, .bank = 0, .partition_id = 0},
448 [kNvmInfoPageWaferAuthSecret] = {.page_id = 3, .bank = 0, .partition_id = 0},
449 [kNvmInfoPageAttestationKeySeeds] = {.page_id = 4, .bank = 0, .partition_id = 0},
450 [kNvmInfoPageOwnerReserved0] = {.page_id = 5, .bank = 0, .partition_id = 0},
451 [kNvmInfoPageOwnerReserved1] = {.page_id = 6, .bank = 0, .partition_id = 0},
452 [kNvmInfoPageOwnerReserved2] = {.page_id = 7, .bank = 0, .partition_id = 0},
453 [kNvmInfoPageOwnerReserved3] = {.page_id = 8, .bank = 0, .partition_id = 0},
454 [kNvmInfoPageFactoryCerts] = {.page_id = 9, .bank = 0, .partition_id = 0},
455 // Bank 1, pages 0-9
456 [kNvmInfoPageBootData0] = {.page_id = 0, .bank = 1, .partition_id = 0},
457 [kNvmInfoPageBootData1] = {.page_id = 1, .bank = 1, .partition_id = 0},
458 [kNvmInfoPageOwnerSlot0] = {.page_id = 2, .bank = 1, .partition_id = 0},
459 [kNvmInfoPageOwnerSlot1] = {.page_id = 3, .bank = 1, .partition_id = 0},
460 [kNvmInfoPageCreatorReserved0] = {.page_id = 4, .bank = 1, .partition_id = 0},
461 [kNvmInfoPageOwnerReserved4] = {.page_id = 5, .bank = 1, .partition_id = 0},
462 [kNvmInfoPageOwnerReserved5] = {.page_id = 6, .bank = 1, .partition_id = 0},
463 [kNvmInfoPageOwnerReserved6] = {.page_id = 7, .bank = 1, .partition_id = 0},
464 [kNvmInfoPageOwnerReserved7] = {.page_id = 8, .bank = 1, .partition_id = 0},
465 [kNvmInfoPageDiceCerts] = {.page_id = 9, .bank = 1, .partition_id = 0},
466};
467// clang-format on
468
469static status_t dif_flash_state_init(dif_flash_ctrl_state_t *flash) {
470#if defined(OPENTITAN_IS_EARLGREY)
471 TRY(dif_flash_ctrl_init_state(
472 flash, mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR)));
473#elif defined(OPENTITAN_IS_ENGLISHBREAKFAST)
474 TRY(dif_flash_ctrl_init_state(
475 flash,
476 mmio_region_from_addr(TOP_ENGLISHBREAKFAST_FLASH_CTRL_CORE_BASE_ADDR)));
477#endif
478 return OK_STATUS();
479}
480
481status_t nvm_testutils_wait_for_init(void) {
483 TRY(dif_flash_state_init(&flash));
484 TRY(flash_ctrl_testutils_wait_for_init(&flash));
485 return OK_STATUS();
486}
487
488static dif_flash_ctrl_info_region_t phys_to_region(const nvm_page_phys_t *p) {
490 region.bank = p->bank;
491 region.partition_id = p->partition_id;
492 region.page = p->page_id;
493 return region;
494}
495
496static status_t info_page_set_props(dif_flash_ctrl_state_t *flash,
497 const nvm_page_phys_t *p,
499 dif_flash_ctrl_info_region_t region = phys_to_region(p);
500 TRY(dif_flash_ctrl_set_info_region_properties(flash, region, props));
501 TRY(dif_flash_ctrl_set_info_region_enablement(flash, region,
503 return OK_STATUS();
504}
505
506status_t nvm_testutils_rom_init(uint32_t otp_nvm_default_cfg) {
508 TRY(dif_flash_state_init(&flash));
509 TRY(dif_flash_ctrl_start_controller_init(&flash));
510 TRY(flash_ctrl_testutils_wait_for_init(&flash));
511 if (otp_nvm_default_cfg != 0) {
513 TRY(dif_flash_ctrl_get_default_region_properties(&flash, &props));
514 props.scramble_en = bitfield_field32_read(otp_nvm_default_cfg,
515 FLASH_CTRL_OTP_FIELD_SCRAMBLING);
516 props.ecc_en =
517 bitfield_field32_read(otp_nvm_default_cfg, FLASH_CTRL_OTP_FIELD_ECC);
518 props.high_endurance_en =
519 bitfield_field32_read(otp_nvm_default_cfg, FLASH_CTRL_OTP_FIELD_HE);
520 TRY(dif_flash_ctrl_set_default_region_properties(&flash, props));
521 }
522 TRY(dif_flash_ctrl_set_flash_enablement(&flash, kDifToggleEnabled));
523#ifdef OPENTITAN_IS_EARLGREY
524 TRY(dif_flash_ctrl_set_exec_enablement(&flash, kDifToggleEnabled));
525#endif
526 return OK_STATUS();
527}
528
529status_t nvm_testutils_info_page_setup(nvm_info_page_t page,
530 nvm_page_perms_t perms,
531 nvm_page_cfg_t cfg) {
532 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
533 const nvm_page_phys_t p = kPageMap[page];
535 TRY(dif_flash_state_init(&flash));
537 .rd_en = perms.read,
538 .prog_en = perms.write,
539 .erase_en = perms.erase,
540 .scramble_en = cfg.scrambling,
541 .ecc_en = cfg.ecc,
542 .high_endurance_en = cfg.he,
543 };
544 TRY(info_page_set_props(&flash, &p, props));
545 return OK_STATUS();
546}
547
548status_t nvm_testutils_write_info_page(nvm_info_page_t page,
549 uint32_t byte_offset,
550 const uint32_t *data, size_t word_count,
551 bool erase_before_write, bool readback) {
552 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
553 const nvm_page_phys_t p = kPageMap[page];
554 uint32_t address = p.page_id * NVM_BYTES_PER_PAGE + byte_offset;
555
557 TRY(dif_flash_state_init(&flash));
558
559 if (erase_before_write) {
560 TRY(flash_ctrl_testutils_erase_and_write_page(
561 &flash, address, p.partition_id, data, kDifFlashCtrlPartitionTypeInfo,
562 word_count));
563 } else {
564 TRY(flash_ctrl_testutils_write(&flash, address, p.partition_id, data,
565 kDifFlashCtrlPartitionTypeInfo, word_count));
566 }
567 if (readback) {
568 uint32_t rb_buf[kNvmMaxWordCount];
569 size_t remaining = word_count;
570 size_t chunk_offset = 0;
571 while (remaining > 0) {
572 size_t chunk =
573 remaining < kNvmMaxWordCount ? remaining : kNvmMaxWordCount;
574 TRY(flash_ctrl_testutils_read(
575 &flash, address + chunk_offset * sizeof(uint32_t), p.partition_id,
576 rb_buf, kDifFlashCtrlPartitionTypeInfo, chunk, 0));
577 TRY_CHECK_ARRAYS_EQ(rb_buf, data + chunk_offset, chunk,
578 "NVM write readback mismatch at page %d offset %d",
579 page, byte_offset + chunk_offset * sizeof(uint32_t));
580 chunk_offset += chunk;
581 remaining -= chunk;
582 }
583 }
584 return OK_STATUS();
585}
586
587status_t nvm_testutils_read_info_page(nvm_info_page_t page,
588 uint32_t byte_offset, uint32_t *data,
589 size_t word_count) {
590 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
591 const nvm_page_phys_t p = kPageMap[page];
592 uint32_t address = p.page_id * NVM_BYTES_PER_PAGE + byte_offset;
593
595 TRY(dif_flash_state_init(&flash));
596
597 TRY(flash_ctrl_testutils_read(&flash, address, p.partition_id, data,
598 kDifFlashCtrlPartitionTypeInfo, word_count, 0));
599 return OK_STATUS();
600}
601
602status_t nvm_testutils_info_page_lock(nvm_info_page_t page, bool lock) {
603 if (!lock) {
604 return OK_STATUS();
605 }
606 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
607 const nvm_page_phys_t p = kPageMap[page];
609 TRY(dif_flash_state_init(&flash));
610 TRY(dif_flash_ctrl_lock_info_region_properties(&flash, phys_to_region(&p)));
611 return OK_STATUS();
612}
613
614status_t nvm_testutils_info_page_print(nvm_info_page_t page) {
615 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
616 const nvm_page_phys_t p = kPageMap[page];
618 TRY(dif_flash_state_init(&flash));
619 dif_flash_ctrl_info_region_t region = phys_to_region(&p);
621 bool locked;
622 TRY(dif_flash_ctrl_get_info_region_properties(&flash, region, &props));
623 TRY(dif_flash_ctrl_info_region_is_locked(&flash, region, &locked));
624 flash_ctrl_testutils_info_region_print(region, &props, locked);
625 return OK_STATUS();
626}
627
628status_t nvm_testutils_data_region_setup(uint32_t region, uint32_t base,
629 uint32_t size, nvm_page_perms_t perms,
630 nvm_page_cfg_t cfg) {
632 TRY(dif_flash_state_init(&flash));
634 .base = base,
635 .size = size,
636 .properties =
637 {
638 .rd_en = perms.read,
639 .prog_en = perms.write,
640 .erase_en = perms.erase,
641 .scramble_en = cfg.scrambling,
642 .ecc_en = cfg.ecc,
643 .high_endurance_en = cfg.he,
644 },
645 };
646 TRY(dif_flash_ctrl_set_data_region_properties(&flash, region, config));
647 TRY(dif_flash_ctrl_set_data_region_enablement(&flash, region,
649 return OK_STATUS();
650}
651
652status_t nvm_testutils_data_region_lock(uint32_t region, bool lock) {
653 if (!lock) {
654 return OK_STATUS();
655 }
657 TRY(dif_flash_state_init(&flash));
658 TRY(dif_flash_ctrl_lock_data_region_properties(&flash, region));
659 return OK_STATUS();
660}
661
662status_t nvm_testutils_data_write(uint32_t byte_address, const uint32_t *data,
663 size_t word_count, bool erase_before_write) {
665 TRY(dif_flash_state_init(&flash));
666 if (erase_before_write) {
667 TRY(flash_ctrl_testutils_erase_and_write_page(
668 &flash, byte_address, /*partition_id=*/0, data,
669 kDifFlashCtrlPartitionTypeData, (uint32_t)word_count));
670 } else {
671 TRY(flash_ctrl_testutils_write(&flash, byte_address, /*partition_id=*/0,
672 data, kDifFlashCtrlPartitionTypeData,
673 (uint32_t)word_count));
674 }
675 return OK_STATUS();
676}
677
678status_t nvm_testutils_set_exec_enablement(bool enable) {
680 TRY(dif_flash_state_init(&flash));
681 TRY(dif_flash_ctrl_set_exec_enablement(
682 &flash, enable ? kDifToggleEnabled : kDifToggleDisabled));
683 return OK_STATUS();
684}
685
686status_t nvm_testutils_show_faults(void) {
688 static const dt_flash_ctrl_t kFlashCtrlDt = 0;
689 TRY(dif_flash_ctrl_init_state_from_dt(&flash, kFlashCtrlDt));
690 TRY(flash_ctrl_testutils_show_faults(&flash));
691 return OK_STATUS();
692}
693
694status_t nvm_testutils_default_region_setup(nvm_page_perms_t perms,
695 nvm_page_cfg_t cfg) {
697 TRY(dif_flash_state_init(&flash));
699 .rd_en = perms.read,
700 .prog_en = perms.write,
701 .erase_en = perms.erase,
702 .scramble_en = cfg.scrambling,
703 .ecc_en = cfg.ecc,
704 .high_endurance_en = cfg.he,
705 };
706 TRY(dif_flash_ctrl_set_default_region_properties(&flash, props));
707 return OK_STATUS();
708}
709
710status_t nvm_testutils_default_region_get(nvm_page_perms_t *perms,
711 nvm_page_cfg_t *cfg) {
713 TRY(dif_flash_state_init(&flash));
715 TRY(dif_flash_ctrl_get_default_region_properties(&flash, &props));
716 if (perms != NULL) {
717 perms->read = (uint32_t)props.rd_en;
718 perms->write = (uint32_t)props.prog_en;
719 perms->erase = (uint32_t)props.erase_en;
720 }
721 if (cfg != NULL) {
722 cfg->scrambling = (uint32_t)props.scramble_en;
723 cfg->ecc = (uint32_t)props.ecc_en;
724 cfg->he = (uint32_t)props.high_endurance_en;
725 }
726 return OK_STATUS();
727}
728
729#endif // USE_RRAM