Software APIs
flash_ctrl_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 
12 #include "sw/device/lib/testing/flash_ctrl_testutils.h"
13 #include "sw/device/lib/testing/test_framework/check.h"
15 
17 
18 #define CHECK_EQZ(x) CHECK((x) == 0)
19 #define CHECK_NEZ(x) CHECK((x) != 0)
20 
21 static dif_flash_ctrl_device_info_t flash_info;
22 #define FLASH_WORD_SZ flash_info.bytes_per_word
23 #define FLASH_PAGE_SZ flash_info.bytes_per_page
24 #define FLASH_UINT32_WORDS_PER_PAGE \
25  (FLASH_PAGE_SZ / FLASH_WORD_SZ) * (FLASH_WORD_SZ / sizeof(uint32_t))
26 #define FLASH_PAGES_PER_BANK flash_info.data_pages
27 
28 static dif_flash_ctrl_state_t flash;
29 
30 static uint32_t flash_region_index;
31 static uint32_t flash_page_to_test;
32 
33 /*
34  * Basic test of page erase / program / read functions. Tests pages from both
35  * the data and info partitions.
36  */
37 static void test_basic_io(void) {
38  // The info partitions have no default access. Specifically set up a region.
39  // rom_ext locks the access of certain info pages.
40  // Select info page 5 to avoid uncessary conflict.
41  dif_flash_ctrl_info_region_t info_region = {
42  .bank = 1, .partition_id = 0, .page = 5};
43  dif_flash_ctrl_region_properties_t region_properties = {
44  .rd_en = kMultiBitBool4True,
45  .prog_en = kMultiBitBool4True,
46  .erase_en = kMultiBitBool4True,
47  .scramble_en = kMultiBitBool4True,
48  .ecc_en = kMultiBitBool4True,
49  .high_endurance_en = kMultiBitBool4False};
50 
51  CHECK_DIF_OK(dif_flash_ctrl_set_info_region_properties(&flash, info_region,
52  region_properties));
53  CHECK_DIF_OK(dif_flash_ctrl_set_info_region_enablement(&flash, info_region,
55 
56  // Also set up data region to enable scrambling.
57  region_properties.rd_en = kMultiBitBool4True;
58  region_properties.prog_en = kMultiBitBool4True;
59  region_properties.erase_en = kMultiBitBool4True;
60 
62  .base = flash_page_to_test, .size = 0x1, .properties = region_properties};
63 
65  &flash, flash_region_index, data_region));
67  &flash, flash_region_index, kDifToggleEnabled));
68 
69  uint32_t flash_test_page_addr = data_region.base * FLASH_PAGE_SZ;
70  uint32_t bank1_info5_addr =
71  FLASH_PAGES_PER_BANK * FLASH_PAGE_SZ + FLASH_PAGE_SZ * 5;
72 
73  mmio_region_t flash_test_page = mmio_region_from_addr(
74  TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR + (uintptr_t)flash_test_page_addr);
75 
76  // Test erasing flash data partition; this should turn the whole bank to all
77  // ones.
78  CHECK_STATUS_OK(flash_ctrl_testutils_erase_page(
79  &flash, flash_test_page_addr,
80  /*partition_id=*/0, kDifFlashCtrlPartitionTypeData));
81 
82  for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; ++i) {
83  CHECK_EQZ(
84  ~mmio_region_read32(flash_test_page, i * (ptrdiff_t)sizeof(uint32_t)));
85  }
86 
87  // Erasing flash info partition 5; this should turn one page to all ones.
88  CHECK_STATUS_OK(flash_ctrl_testutils_erase_page(
89  &flash, bank1_info5_addr,
90  /*partition_id=*/0, kDifFlashCtrlPartitionTypeInfo));
91 
92  // Prepare an entire page of non-trivial data to program into flash.
93  uint32_t input_page[FLASH_UINT32_WORDS_PER_PAGE];
94  uint32_t output_page[FLASH_UINT32_WORDS_PER_PAGE];
95  CHECK(sizeof(input_page) == flash_info.bytes_per_page,
96  "Unexpected buffer size. got: %d, want: %d", sizeof(input_page),
97  flash_info.bytes_per_page);
98  CHECK(sizeof(output_page) == flash_info.bytes_per_page,
99  "Unexpected buffer size. got: %d, want: %d", sizeof(input_page),
100  flash_info.bytes_per_page);
101  memset(input_page, 0xa5, FLASH_UINT32_WORDS_PER_PAGE * sizeof(uint32_t));
102  for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; i += 2) {
103  input_page[i] ^= 0xffffffff;
104  }
105 
106  // Attempt to live-program an entire page, where the overall payload is much
107  // larger than the internal flash FIFO.
108  CHECK_STATUS_OK(flash_ctrl_testutils_erase_and_write_page(
109  &flash, flash_test_page_addr, /*partition_id=*/0, input_page,
110  kDifFlashCtrlPartitionTypeData, FLASH_UINT32_WORDS_PER_PAGE));
111  CHECK_STATUS_OK(flash_ctrl_testutils_read(
112  &flash, flash_test_page_addr, /*partition_id=*/0, output_page,
113  kDifFlashCtrlPartitionTypeData, FLASH_UINT32_WORDS_PER_PAGE,
114  /*delay=*/0));
115  CHECK_ARRAYS_EQ(output_page, input_page, FLASH_UINT32_WORDS_PER_PAGE);
116 
117  // Check from host side also.
118  for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; i++) {
119  output_page[i] =
120  mmio_region_read32(flash_test_page, i * (ptrdiff_t)sizeof(uint32_t));
121  }
122  CHECK_ARRAYS_EQ(output_page, input_page, FLASH_UINT32_WORDS_PER_PAGE);
123 
124  // Similar check for info page.
125  CHECK_STATUS_OK(flash_ctrl_testutils_erase_and_write_page(
126  &flash, bank1_info5_addr, /*partition_id=*/0, input_page,
127  kDifFlashCtrlPartitionTypeInfo, FLASH_UINT32_WORDS_PER_PAGE));
128  CHECK_STATUS_OK(flash_ctrl_testutils_read(
129  &flash, bank1_info5_addr, /*partition_id=*/0, output_page,
130  kDifFlashCtrlPartitionTypeInfo, FLASH_UINT32_WORDS_PER_PAGE,
131  /*delay=*/0));
132  CHECK_ARRAYS_EQ(output_page, input_page, FLASH_UINT32_WORDS_PER_PAGE);
133 
134  // If current platform uses sramble, you can't turn it off once you write your
135  // test with scrambled form. Add if / else to make sure not to revert
136  // scrambled region by default.
137  if (kBootStage == kBootStageOwner) {
138  CHECK_STATUS_OK(flash_ctrl_testutils_default_region_access(
139  &flash, /*rd_en=*/true, /*prog_en=*/true, /*erase_en=*/true,
140  /*scramble_en=*/true, /*ecc_en=*/true, /*high_endurance_en=*/false));
141  } else {
142  CHECK_STATUS_OK(flash_ctrl_testutils_default_region_access(
143  &flash, /*rd_en=*/true, /*prog_en=*/true, /*erase_en=*/true,
144  /*scramble_en=*/false, /*ecc_en=*/false, /*high_endurance_en=*/false));
145  }
146 
147  // Perform similar test on the last page of the first bank.
148  uint32_t flash_bank_0_last_page_addr =
149  (FLASH_PAGES_PER_BANK - 1) * FLASH_PAGE_SZ;
150  mmio_region_t flash_bank_0_last_page =
152  (uintptr_t)flash_bank_0_last_page_addr);
153  CHECK_STATUS_OK(flash_ctrl_testutils_erase_page(
154  &flash, flash_bank_0_last_page_addr,
155  /*partition_id=*/0, kDifFlashCtrlPartitionTypeData));
156  for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; ++i) {
157  CHECK_EQZ(~mmio_region_read32(flash_bank_0_last_page,
158  i * (ptrdiff_t)sizeof(uint32_t)));
159  }
160 
161  CHECK_STATUS_OK(flash_ctrl_testutils_write(
162  &flash, flash_bank_0_last_page_addr, /*partition_id=*/0, input_page,
163  kDifFlashCtrlPartitionTypeData, FLASH_UINT32_WORDS_PER_PAGE));
164  CHECK_STATUS_OK(flash_ctrl_testutils_read(
165  &flash, flash_bank_0_last_page_addr,
166  /*partition_id=*/0, output_page, kDifFlashCtrlPartitionTypeData,
167  FLASH_UINT32_WORDS_PER_PAGE, /*delay=*/0));
168 
169  CHECK_ARRAYS_EQ(output_page, input_page, FLASH_UINT32_WORDS_PER_PAGE);
170 }
171 
172 static void test_memory_protection(void) {
174  CHECK_DIF_OK(dif_flash_ctrl_init_state(
176 
177  // Set up default access for data partitions.
178  // If current platform uses scramble, you can't turn it off once you write
179  // your test with scrambled form. Add if / else to make sure not to revert
180  // scrambled region by default.
181  if (kBootStage == kBootStageOwner) {
182  CHECK_STATUS_OK(flash_ctrl_testutils_default_region_access(
183  &flash, /*rd_en=*/true, /*prog_en=*/true, /*erase_en=*/true,
184  /*scramble_en=*/true, /*ecc_en=*/true, /*high_endurance_en=*/false));
185  } else {
186  CHECK_STATUS_OK(flash_ctrl_testutils_default_region_access(
187  &flash, /*rd_en=*/true, /*prog_en=*/true, /*erase_en=*/true,
188  /*scramble_en=*/false, /*ecc_en=*/false, /*high_endurance_en=*/false));
189  }
190 
191  // A memory protection region representing the first page of the second bank.
192  dif_flash_ctrl_region_properties_t protected_properties = {
193  .rd_en = kMultiBitBool4True,
194  .prog_en = kMultiBitBool4True,
195  .erase_en = kMultiBitBool4True,
196  .scramble_en = kMultiBitBool4False,
197  .ecc_en = kMultiBitBool4True,
198  .high_endurance_en = kMultiBitBool4False};
199 
200  dif_flash_ctrl_data_region_properties_t protected_region = {
201  .base = flash_page_to_test,
202  .size = 0x1,
203  .properties = protected_properties};
204 
205  uintptr_t ok_region_start = TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR +
206  (protected_region.base * FLASH_PAGE_SZ);
207  uintptr_t ok_region_end =
208  ok_region_start + (protected_region.size * FLASH_PAGE_SZ);
209  mmio_region_t ok_region = mmio_region_from_addr(ok_region_start);
210 
211  uintptr_t bad_region_start = ok_region_end;
212 
213  // Erase good and bad regions.
214  CHECK_STATUS_OK(flash_ctrl_testutils_erase_page(
215  &flash, ok_region_start,
216  /*partition_id=*/0, kDifFlashCtrlPartitionTypeData));
217  CHECK_STATUS_OK(flash_ctrl_testutils_erase_page(
218  &flash, bad_region_start,
219  /*partition_id=*/0, kDifFlashCtrlPartitionTypeData));
220 
221  // Turn off flash access by default.
222  if (kBootStage == kBootStageOwner) {
223  CHECK_STATUS_OK(flash_ctrl_testutils_default_region_access(
224  &flash, /*rd_en=*/false, /*prog_en=*/false, /*erase_en=*/false,
225  /*scramble_en=*/true, /*ecc_en=*/true, /*high_endurance_en=*/false));
226  } else {
227  CHECK_STATUS_OK(flash_ctrl_testutils_default_region_access(
228  &flash, /*rd_en=*/false, /*prog_en=*/false, /*erase_en=*/false,
229  /*scramble_en=*/false, /*ecc_en=*/false, /*high_endurance_en=*/false));
230  }
231 
232  // Enable protected region for access.
234  &flash, flash_region_index, protected_region));
235 
236  // Attempt to perform a write.
237  uintptr_t region_boundary_start = bad_region_start - (FLASH_WORD_SZ * 2);
238  mmio_region_t region_boundary = mmio_region_from_addr(region_boundary_start);
239 
240  // Place half the words in the good region and half in the bad.
241  uint32_t words[(FLASH_WORD_SZ * 2 * 2) / sizeof(uint32_t)];
242  memset(words, 0xa5, ARRAYSIZE(words) * sizeof(uint32_t));
243  for (uint32_t i = 0; i < ARRAYSIZE(words); ++i) {
244  words[i] += i;
245  }
246 
247  // Perform a partial write.
248  CHECK(status_err(flash_ctrl_testutils_write(
249  &flash, region_boundary_start, /*partition_id=*/0, words,
250  kDifFlashCtrlPartitionTypeData, ARRAYSIZE(words))));
251  // Words in the good region should still match, while words in the bad region
252  // should be all-ones, since we erased.
253  for (int i = 0; i < ARRAYSIZE(words); ++i) {
254  uint32_t expected = 0xffffffff;
255  if (i < ARRAYSIZE(words) / 2) {
256  expected = words[i];
257  }
258  CHECK(mmio_region_read32(region_boundary,
259  i * (ptrdiff_t)sizeof(uint32_t)) == expected);
260  }
261 
262  // Attempt to erase bad page, which should fail.
263  CHECK(status_err(flash_ctrl_testutils_erase_page(
264  &flash, bad_region_start,
265  /*partition_id=*/0, kDifFlashCtrlPartitionTypeData)));
266 
267  // Attempt to erase the good page, which should succeed.
268  CHECK_STATUS_OK(flash_ctrl_testutils_erase_page(
269  &flash, ok_region_start,
270  /*partition_id=*/0, kDifFlashCtrlPartitionTypeData));
271  for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; i++) {
272  CHECK_EQZ(~mmio_region_read32(ok_region, i * (ptrdiff_t)sizeof(uint32_t)));
273  }
274 }
275 
276 OTTF_DEFINE_TEST_CONFIG();
277 
278 bool test_main(void) {
279  flash_info = dif_flash_ctrl_get_device_info();
280 
281  // ROM_EXT will use 2 regions to configure access to first 0x20 pages of each
282  // bank. Therefore skip these pages when running as owner stage.
283  if (kBootStage == kBootStageOwner) {
284  flash_region_index = 2;
285  flash_page_to_test = FLASH_PAGES_PER_BANK + 0x20;
286  } else {
287  flash_region_index = 0;
288  flash_page_to_test = FLASH_PAGES_PER_BANK;
289  }
290 
291  CHECK_DIF_OK(dif_flash_ctrl_init_state(
293  CHECK_STATUS_OK(flash_ctrl_testutils_wait_for_init(&flash));
294 
295  LOG_INFO("flash test!");
296 
297  CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash, /*bank=*/0,
299  CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash, /*bank=*/1,
301  test_basic_io();
302  test_memory_protection();
303 
304  CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash, /*bank=*/0,
306  CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash, /*bank=*/1,
308  return true;
309 }