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"
14#include "sw/device/lib/testing/flash_ctrl_testutils.h"
15#include "sw/device/lib/testing/test_framework/check.h"
16#include "sw/device/silicon_creator/lib/drivers/flash_ctrl.h"
17
19
20// Chunk size for readback verification — small enough to live on the stack.
21enum { kNvmMaxWordCount = 16 };
22
23// Physical location of a logical NVM info page.
24typedef struct {
25 uint32_t page_id;
26 uint32_t bank;
27 uint32_t partition_id;
29
30// Mapping from nvm_info_page_t to physical flash parameters.
31// Update this table when switching to a different NVM technology.
32// clang-format off
33static const nvm_page_phys_t kPageMap[] = {
34 // Bank 0, pages 0-9
35 [kNvmInfoPageFactoryId] = {.page_id = 0, .bank = 0, .partition_id = 0},
36 [kNvmInfoPageCreatorSecret] = {.page_id = 1, .bank = 0, .partition_id = 0},
37 [kNvmInfoPageOwnerSecret] = {.page_id = 2, .bank = 0, .partition_id = 0},
38 [kNvmInfoPageWaferAuthSecret] = {.page_id = 3, .bank = 0, .partition_id = 0},
39 [kNvmInfoPageAttestationKeySeeds] = {.page_id = 4, .bank = 0, .partition_id = 0},
40 [kNvmInfoPageOwnerReserved0] = {.page_id = 5, .bank = 0, .partition_id = 0},
41 [kNvmInfoPageOwnerReserved1] = {.page_id = 6, .bank = 0, .partition_id = 0},
42 [kNvmInfoPageOwnerReserved2] = {.page_id = 7, .bank = 0, .partition_id = 0},
43 [kNvmInfoPageOwnerReserved3] = {.page_id = 8, .bank = 0, .partition_id = 0},
44 [kNvmInfoPageFactoryCerts] = {.page_id = 9, .bank = 0, .partition_id = 0},
45 // Bank 1, pages 0-9
46 [kNvmInfoPageBootData0] = {.page_id = 0, .bank = 1, .partition_id = 0},
47 [kNvmInfoPageBootData1] = {.page_id = 1, .bank = 1, .partition_id = 0},
48 [kNvmInfoPageOwnerSlot0] = {.page_id = 2, .bank = 1, .partition_id = 0},
49 [kNvmInfoPageOwnerSlot1] = {.page_id = 3, .bank = 1, .partition_id = 0},
50 [kNvmInfoPageCreatorReserved0] = {.page_id = 4, .bank = 1, .partition_id = 0},
51 [kNvmInfoPageOwnerReserved4] = {.page_id = 5, .bank = 1, .partition_id = 0},
52 [kNvmInfoPageOwnerReserved5] = {.page_id = 6, .bank = 1, .partition_id = 0},
53 [kNvmInfoPageOwnerReserved6] = {.page_id = 7, .bank = 1, .partition_id = 0},
54 [kNvmInfoPageOwnerReserved7] = {.page_id = 8, .bank = 1, .partition_id = 0},
55 [kNvmInfoPageDiceCerts] = {.page_id = 9, .bank = 1, .partition_id = 0},
56};
57// clang-format on
58
59const nvm_page_perms_t kPageReadOnly = {.read = kMultiBitBool4True,
60 .write = kMultiBitBool4False,
61 .erase = kMultiBitBool4False};
62
63const nvm_page_perms_t kPageReadWrite = {.read = kMultiBitBool4True,
64 .write = kMultiBitBool4True,
65 .erase = kMultiBitBool4True};
66
67const nvm_page_perms_t kPageWriteOnly = {.read = kMultiBitBool4False,
68 .write = kMultiBitBool4True,
69 .erase = kMultiBitBool4True};
70
71const nvm_page_cfg_t kPageScrambleCfg = {.scrambling = kMultiBitBool4True,
72 .ecc = kMultiBitBool4True,
73 .he = kMultiBitBool4False};
74
75const nvm_page_cfg_t kPagePlainCfg = {.scrambling = kMultiBitBool4False,
76 .ecc = kMultiBitBool4True,
77 .he = kMultiBitBool4False};
78
79const nvm_page_cfg_t kPageRawCfg = {.scrambling = kMultiBitBool4False,
80 .ecc = kMultiBitBool4False,
81 .he = kMultiBitBool4False};
82
83static status_t dif_flash_state_init(dif_flash_ctrl_state_t *flash) {
84 TRY(dif_flash_ctrl_init_state(
85 flash, mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR)));
86 return OK_STATUS();
87}
88
89status_t nvm_testutils_wait_for_init(void) {
91 TRY(dif_flash_state_init(&flash));
92 TRY(flash_ctrl_testutils_wait_for_init(&flash));
93 return OK_STATUS();
94}
95
96static dif_flash_ctrl_info_region_t phys_to_region(const nvm_page_phys_t *p) {
98 region.bank = p->bank;
99 region.partition_id = p->partition_id;
100 region.page = p->page_id;
101 return region;
102}
103
104static status_t info_page_get_props(dif_flash_ctrl_state_t *flash,
105 const nvm_page_phys_t *p,
107 TRY(dif_flash_ctrl_get_info_region_properties(flash, phys_to_region(p),
108 props));
109 return OK_STATUS();
110}
111
112static status_t info_page_set_props(dif_flash_ctrl_state_t *flash,
113 const nvm_page_phys_t *p,
115 dif_flash_ctrl_info_region_t region = phys_to_region(p);
116 TRY(dif_flash_ctrl_set_info_region_properties(flash, region, props));
117 TRY(dif_flash_ctrl_set_info_region_enablement(flash, region,
119 return OK_STATUS();
120}
121
122status_t nvm_testutils_rom_init(uint32_t otp_flash_default_cfg) {
124 TRY(dif_flash_state_init(&flash));
125 TRY(dif_flash_ctrl_start_controller_init(&flash));
126 TRY(flash_ctrl_testutils_wait_for_init(&flash));
127 if (otp_flash_default_cfg != 0) {
129 TRY(dif_flash_ctrl_get_default_region_properties(&flash, &props));
130 props.scramble_en = bitfield_field32_read(otp_flash_default_cfg,
131 FLASH_CTRL_OTP_FIELD_SCRAMBLING);
132 props.ecc_en =
133 bitfield_field32_read(otp_flash_default_cfg, FLASH_CTRL_OTP_FIELD_ECC);
134 props.high_endurance_en =
135 bitfield_field32_read(otp_flash_default_cfg, FLASH_CTRL_OTP_FIELD_HE);
136 TRY(dif_flash_ctrl_set_default_region_properties(&flash, props));
137 }
138 TRY(dif_flash_ctrl_set_flash_enablement(&flash, kDifToggleEnabled));
139#ifdef OPENTITAN_IS_EARLGREY
140 TRY(dif_flash_ctrl_set_exec_enablement(&flash, kDifToggleEnabled));
141#endif
142 return OK_STATUS();
143}
144
145status_t nvm_testutils_info_page_setup(nvm_info_page_t page,
146 nvm_page_perms_t perms,
147 nvm_page_cfg_t cfg) {
148 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
149 const nvm_page_phys_t p = kPageMap[page];
151 TRY(dif_flash_state_init(&flash));
153 .rd_en = perms.read,
154 .prog_en = perms.write,
155 .erase_en = perms.erase,
156 .scramble_en = cfg.scrambling,
157 .ecc_en = cfg.ecc,
158 .high_endurance_en = cfg.he,
159 };
160 TRY(info_page_set_props(&flash, &p, props));
161 return OK_STATUS();
162}
163
164status_t nvm_testutils_info_page_set(nvm_info_page_t page,
165 nvm_page_perms_t perms,
166 nvm_page_cfg_t cfg) {
167 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
168 const nvm_page_phys_t p = kPageMap[page];
170 TRY(dif_flash_state_init(&flash));
172 TRY(info_page_get_props(&flash, &p, &props));
173 if (perms.read == kMultiBitBool4True) {
174 props.rd_en = kMultiBitBool4True;
175 }
176 if (perms.write == kMultiBitBool4True) {
177 props.prog_en = kMultiBitBool4True;
178 }
179 if (perms.erase == kMultiBitBool4True) {
180 props.erase_en = kMultiBitBool4True;
181 }
182 if (cfg.scrambling == kMultiBitBool4True) {
183 props.scramble_en = kMultiBitBool4True;
184 }
185 if (cfg.ecc == kMultiBitBool4True) {
186 props.ecc_en = kMultiBitBool4True;
187 }
188 if (cfg.he == kMultiBitBool4True) {
189 props.high_endurance_en = kMultiBitBool4True;
190 }
191 TRY(info_page_set_props(&flash, &p, props));
192 return OK_STATUS();
193}
194
195status_t nvm_testutils_info_page_clear(nvm_info_page_t page,
196 nvm_page_perms_t perms,
197 nvm_page_cfg_t cfg) {
198 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
199 const nvm_page_phys_t p = kPageMap[page];
201 TRY(dif_flash_state_init(&flash));
203 TRY(info_page_get_props(&flash, &p, &props));
204 if (perms.read == kMultiBitBool4True) {
205 props.rd_en = kMultiBitBool4False;
206 }
207 if (perms.write == kMultiBitBool4True) {
208 props.prog_en = kMultiBitBool4False;
209 }
210 if (perms.erase == kMultiBitBool4True) {
211 props.erase_en = kMultiBitBool4False;
212 }
213 if (cfg.scrambling == kMultiBitBool4True) {
214 props.scramble_en = kMultiBitBool4False;
215 }
216 if (cfg.ecc == kMultiBitBool4True) {
217 props.ecc_en = kMultiBitBool4False;
218 }
219 if (cfg.he == kMultiBitBool4True) {
220 props.high_endurance_en = kMultiBitBool4False;
221 }
222 TRY(info_page_set_props(&flash, &p, props));
223 return OK_STATUS();
224}
225
226status_t nvm_testutils_write_info_page(nvm_info_page_t page,
227 uint32_t byte_offset,
228 const uint32_t *data, size_t word_count,
229 bool erase_before_write, bool readback) {
230 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
231 const nvm_page_phys_t p = kPageMap[page];
232 uint32_t address = p.page_id * NVM_BYTES_PER_PAGE + byte_offset;
233
235 TRY(dif_flash_state_init(&flash));
236
237 if (erase_before_write) {
238 TRY(flash_ctrl_testutils_erase_and_write_page(
239 &flash, address, p.partition_id, data, kDifFlashCtrlPartitionTypeInfo,
240 word_count));
241 } else {
242 TRY(flash_ctrl_testutils_write(&flash, address, p.partition_id, data,
243 kDifFlashCtrlPartitionTypeInfo, word_count));
244 }
245 if (readback) {
246 uint32_t rb_buf[kNvmMaxWordCount];
247 size_t remaining = word_count;
248 size_t chunk_offset = 0;
249 while (remaining > 0) {
250 size_t chunk =
251 remaining < kNvmMaxWordCount ? remaining : kNvmMaxWordCount;
252 TRY(flash_ctrl_testutils_read(
253 &flash, address + chunk_offset * sizeof(uint32_t), p.partition_id,
254 rb_buf, kDifFlashCtrlPartitionTypeInfo, chunk, 0));
255 TRY_CHECK(
256 memcmp(data + chunk_offset, rb_buf, chunk * sizeof(uint32_t)) == 0,
257 "NVM write readback mismatch at page %d offset %d", page,
258 byte_offset + chunk_offset * sizeof(uint32_t));
259 chunk_offset += chunk;
260 remaining -= chunk;
261 }
262 }
263 return OK_STATUS();
264}
265
266status_t nvm_testutils_read_info_page(nvm_info_page_t page,
267 uint32_t byte_offset, uint32_t *data,
268 size_t word_count) {
269 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
270 const nvm_page_phys_t p = kPageMap[page];
271 uint32_t address = p.page_id * NVM_BYTES_PER_PAGE + byte_offset;
272
274 TRY(dif_flash_state_init(&flash));
275
276 TRY(flash_ctrl_testutils_read(&flash, address, p.partition_id, data,
277 kDifFlashCtrlPartitionTypeInfo, word_count, 0));
278 return OK_STATUS();
279}
280
281status_t nvm_testutils_info_page_lock(nvm_info_page_t page, bool lock) {
282 if (!lock) {
283 return OK_STATUS();
284 }
285 TRY_CHECK(page < ARRAYSIZE(kPageMap), "invalid page %d", page);
286 const nvm_page_phys_t p = kPageMap[page];
288 TRY(dif_flash_state_init(&flash));
289 TRY(dif_flash_ctrl_lock_info_region_properties(&flash, phys_to_region(&p)));
290 return OK_STATUS();
291}
292
293status_t nvm_testutils_data_region_setup(uint32_t region, uint32_t base,
294 uint32_t size, nvm_page_perms_t perms,
295 nvm_page_cfg_t cfg) {
297 TRY(dif_flash_state_init(&flash));
299 .base = base,
300 .size = size,
301 .properties =
302 {
303 .rd_en = perms.read,
304 .prog_en = perms.write,
305 .erase_en = perms.erase,
306 .scramble_en = cfg.scrambling,
307 .ecc_en = cfg.ecc,
308 .high_endurance_en = cfg.he,
309 },
310 };
311 TRY(dif_flash_ctrl_set_data_region_properties(&flash, region, config));
312 TRY(dif_flash_ctrl_set_data_region_enablement(&flash, region,
314 return OK_STATUS();
315}
316
317status_t nvm_testutils_data_region_lock(uint32_t region, bool lock) {
318 if (!lock) {
319 return OK_STATUS();
320 }
322 TRY(dif_flash_state_init(&flash));
323 TRY(dif_flash_ctrl_lock_data_region_properties(&flash, region));
324 return OK_STATUS();
325}
326
327status_t nvm_testutils_show_faults(void) {
329 static const dt_flash_ctrl_t kFlashCtrlDt = 0;
330 TRY(dif_flash_ctrl_init_state_from_dt(&flash, kFlashCtrlDt));
331 TRY(flash_ctrl_testutils_show_faults(&flash));
332 return OK_STATUS();
333}
334
335status_t nvm_testutils_default_region_setup(nvm_page_perms_t perms,
336 nvm_page_cfg_t cfg) {
338 TRY(dif_flash_state_init(&flash));
340 .rd_en = perms.read,
341 .prog_en = perms.write,
342 .erase_en = perms.erase,
343 .scramble_en = cfg.scrambling,
344 .ecc_en = cfg.ecc,
345 .high_endurance_en = cfg.he,
346 };
347 TRY(dif_flash_ctrl_set_default_region_properties(&flash, props));
348 return OK_STATUS();
349}