| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/lib/flash_ctrl.h" |
| |
| #include "sw/device/lib/base/log.h" |
| #include "sw/device/lib/base/memory.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/common.h" |
| #include "sw/device/lib/runtime/check.h" |
| #include "sw/device/lib/testing/test_main.h" |
| |
| #define CHECK_ARRAYS_EQ(xs, ys, len) \ |
| do { \ |
| uint32_t *xs_ = (xs); \ |
| uint32_t *ys_ = (ys); \ |
| size_t len_ = (len); \ |
| for (int i = 0; i < len_; ++i) { \ |
| CHECK(xs_[i] == ys_[i]); \ |
| } \ |
| } while (false) |
| |
| #define CHECK_EQZ(x) CHECK((x) == 0) |
| #define CHECK_NEZ(x) CHECK((x) != 0) |
| |
| /* |
| * Basic test of page erase / program / read functions |
| * Tests pages from both the data and info partitions |
| */ |
| static void test_basic_io(void) { |
| // setup default access for data partition |
| flash_default_region_access(/*rd_en=*/true, /*prog_en=*/true, |
| /*erase_en=*/true); |
| |
| // info partition has no default access, specifically setup a region |
| mp_region_t info_region = { |
| .num = 0x0, |
| .base = FLASH_PAGES_PER_BANK, |
| .size = 0x1, |
| .part = kInfoPartition, |
| .rd_en = true, |
| .prog_en = true, |
| .erase_en = true, |
| }; |
| flash_cfg_region(&info_region); |
| |
| uintptr_t flash_bank_1_addr = FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ; |
| mmio_region_t flash_bank_1 = mmio_region_from_addr(flash_bank_1_addr); |
| |
| // Test erasing flash data partition; this should turn the whole bank to all |
| // ones. |
| CHECK_EQZ(flash_page_erase(flash_bank_1_addr, kDataPartition)); |
| for (int i = 0; i < FLASH_WORDS_PER_PAGE; ++i) { |
| CHECK_EQZ(~mmio_region_read32(flash_bank_1, i * sizeof(uint32_t))); |
| } |
| |
| // Erasing flash info partition; this should turn the whole bank to all ones. |
| CHECK_EQZ(flash_page_erase(flash_bank_1_addr, kInfoPartition)); |
| |
| // Prepare an entire page of non-trivial data to program |
| // into flash. |
| uint32_t input_page[FLASH_WORDS_PER_PAGE]; |
| uint32_t output_page[FLASH_WORDS_PER_PAGE]; |
| memset(input_page, 0xa5, FLASH_WORDS_PER_PAGE * sizeof(uint32_t)); |
| for (int i = 0; i < FLASH_WORDS_PER_PAGE; i += 2) { |
| input_page[i] ^= 0xffffffff; |
| } |
| |
| // Attempt to live-program an entire page, where the overall |
| // payload is much larger than the internal flash FIFO. |
| CHECK_EQZ(flash_page_erase(flash_bank_1_addr, kDataPartition)); |
| CHECK_EQZ(flash_write(flash_bank_1_addr, kDataPartition, input_page, |
| FLASH_WORDS_PER_PAGE)); |
| CHECK_EQZ(flash_read(flash_bank_1_addr, kDataPartition, FLASH_WORDS_PER_PAGE, |
| output_page)); |
| CHECK_ARRAYS_EQ(output_page, input_page, FLASH_WORDS_PER_PAGE); |
| |
| // Check from host side also |
| for (int i = 0; i < FLASH_WORDS_PER_PAGE; i++) { |
| output_page[i] = mmio_region_read32(flash_bank_1, i * sizeof(uint32_t)); |
| } |
| CHECK_ARRAYS_EQ(output_page, input_page, FLASH_WORDS_PER_PAGE); |
| |
| // Similar check for info page |
| CHECK_EQZ(flash_page_erase(flash_bank_1_addr, kInfoPartition)); |
| CHECK_EQZ(flash_write(flash_bank_1_addr, kInfoPartition, input_page, |
| FLASH_WORDS_PER_PAGE)); |
| CHECK_EQZ(flash_read(flash_bank_1_addr, kInfoPartition, FLASH_WORDS_PER_PAGE, |
| output_page)); |
| CHECK_ARRAYS_EQ(output_page, input_page, FLASH_WORDS_PER_PAGE); |
| |
| uintptr_t flash_bank_0_last_page_addr = flash_bank_1_addr - FLASH_PAGE_SZ; |
| mmio_region_t flash_bank_0_last_page = |
| mmio_region_from_addr(flash_bank_0_last_page_addr); |
| CHECK_EQZ(flash_page_erase(flash_bank_0_last_page_addr, kDataPartition)); |
| for (int i = 0; i < FLASH_WORDS_PER_PAGE; ++i) { |
| CHECK_EQZ( |
| ~mmio_region_read32(flash_bank_0_last_page, i * sizeof(uint32_t))); |
| } |
| |
| CHECK_EQZ(flash_write(flash_bank_0_last_page_addr, kDataPartition, input_page, |
| FLASH_WORDS_PER_PAGE)); |
| CHECK_EQZ(flash_read(flash_bank_0_last_page_addr, kDataPartition, |
| FLASH_WORDS_PER_PAGE, output_page)); |
| |
| CHECK_ARRAYS_EQ(output_page, input_page, FLASH_WORDS_PER_PAGE); |
| } |
| |
| static void test_memory_protection(void) { |
| flash_default_region_access(/*rd_en=*/true, /*prog_en=*/true, |
| /*erase_en=*/true); |
| |
| // A memory protection region representing the first page of the second bank. |
| mp_region_t protection_region = { |
| .num = 0x0, |
| .base = FLASH_PAGES_PER_BANK, |
| .size = 0x1, |
| .part = kDataPartition, |
| .rd_en = true, |
| .prog_en = true, |
| .erase_en = true, |
| }; |
| |
| uintptr_t ok_region_start = |
| FLASH_MEM_BASE_ADDR + (protection_region.base * FLASH_PAGE_SZ); |
| uintptr_t ok_region_end = |
| ok_region_start + (protection_region.size * FLASH_PAGE_SZ); |
| mmio_region_t ok_region = mmio_region_from_addr(ok_region_start); |
| |
| uintptr_t bad_region_start = ok_region_end; |
| |
| // Erase good and bad regions. |
| CHECK_EQZ(flash_page_erase(ok_region_start, kDataPartition)); |
| CHECK_EQZ(flash_page_erase(bad_region_start, kDataPartition)); |
| |
| // Turn off flash access by default. |
| flash_default_region_access(/*rd_en=*/false, /*prog_en=*/false, |
| /*erase_en=*/false); |
| flash_cfg_region(&protection_region); |
| |
| // Attempt to perform a write. |
| uintptr_t region_boundary_start = bad_region_start - (FLASH_WORD_SZ * 2); |
| mmio_region_t region_boundary = mmio_region_from_addr(region_boundary_start); |
| |
| // Place half the words in the good region and half in the bad |
| uint32_t words[(FLASH_WORD_SZ * 2 * 2) / sizeof(uint32_t)]; |
| memset(words, 0xa5, ARRAYSIZE(words) * sizeof(uint32_t)); |
| for (int i = 0; i < ARRAYSIZE(words); ++i) { |
| words[i] += i; |
| } |
| |
| // Perform a partial write. |
| CHECK_NEZ(flash_write(region_boundary_start, kDataPartition, words, |
| ARRAYSIZE(words))); |
| // Words in the good region should still match, while words in the bad |
| // region should be all-ones, since we erased |
| for (int i = 0; i < ARRAYSIZE(words); ++i) { |
| uint32_t expected = 0xffffffff; |
| if (i < ARRAYSIZE(words) / 2) { |
| expected = words[i]; |
| } |
| CHECK(mmio_region_read32(region_boundary, i * sizeof(uint32_t)) == |
| expected); |
| } |
| |
| // Attempt to erase bad page, which should fail. |
| CHECK_NEZ(flash_page_erase(bad_region_start, kDataPartition)); |
| |
| // Attempt to erase the good page, which should succeed. |
| CHECK_EQZ(flash_page_erase(ok_region_start, kDataPartition)); |
| for (int i = 0; i < FLASH_WORDS_PER_PAGE; i++) { |
| CHECK_EQZ(~mmio_region_read32(ok_region, i * sizeof(uint32_t))); |
| } |
| } |
| |
| bool test_main(void) { |
| flash_init_block(); |
| |
| LOG_INFO("flash test!"); |
| |
| flash_cfg_bank_erase(FLASH_BANK_0, /*erase_en=*/true); |
| flash_cfg_bank_erase(FLASH_BANK_1, /*erase_en=*/true); |
| |
| test_basic_io(); |
| test_memory_protection(); |
| |
| flash_cfg_bank_erase(FLASH_BANK_0, /*erase_en=*/false); |
| flash_cfg_bank_erase(FLASH_BANK_1, /*erase_en=*/false); |
| |
| return true; |
| } |