| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/lib/common.h" |
| #include "sw/lib/flash_ctrl.h" |
| #include "sw/lib/gpio.h" |
| #include "sw/lib/uart.h" |
| |
| /** |
| * Delay loop executing within 8 cycles on ibex |
| */ |
| static void delay_loop_ibex(unsigned long loops) { |
| int out; /* only to notify compiler of modifications to |loops| */ |
| asm volatile( |
| "1: nop \n" // 1 cycle |
| " nop \n" // 1 cycle |
| " nop \n" // 1 cycle |
| " nop \n" // 1 cycle |
| " addi %1, %1, -1 \n" // 1 cycle |
| " bnez %1, 1b \n" // 3 cycles |
| : "=&r"(out) |
| : "0"(loops)); |
| } |
| |
| static int usleep_ibex(unsigned long usec) { |
| unsigned long usec_cycles; |
| usec_cycles = CLK_FIXED_FREQ_HZ * usec / 1000 / 1000 / 8; |
| |
| delay_loop_ibex(usec_cycles); |
| return 0; |
| } |
| |
| static int usleep(unsigned long usec) { return usleep_ibex(usec); } |
| |
| static void break_on_error(uint32_t error) { |
| if (error) { |
| // inifinitely fetch instructions, will flag an assertion error |
| uart_send_str("FAIL!\r\n"); |
| while (1) { |
| usleep(100); |
| } |
| } |
| } |
| |
| /* Returns 1 if |a| and |b| are equal. */ |
| int check_arr_eq(const uint32_t *a, const uint32_t *b, uint32_t len) { |
| for (int i = 0; i < len; ++i) { |
| if (a[i] != b[i]) { |
| return 0; |
| } |
| } |
| return 1; |
| } |
| |
| int main(int argc, char **argv) { |
| uint32_t i, iteration; |
| uint32_t prog_array[FLASH_WORDS_PER_PAGE]; |
| uint32_t rd_array[FLASH_WORDS_PER_PAGE]; |
| uint32_t test_addr; |
| uint32_t bank1_addr = FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ; |
| uint32_t bank0_last_page = |
| FLASH_MEM_BASE_ADDR + (FLASH_PAGES_PER_BANK - 1) * FLASH_PAGE_SZ; |
| |
| uart_init(UART_BAUD_RATE); |
| flash_init_block(); |
| |
| // enable all access |
| flash_cfg_bank_erase(FLASH_BANK_0, /*erase_en=*/true); |
| flash_cfg_bank_erase(FLASH_BANK_1, /*erase_en=*/true); |
| flash_default_region_access(1, 1, 1); |
| break_on_error(flash_page_erase(bank1_addr)); |
| flash_write_scratch_reg(0xFACEDEAD); |
| // read flash back via host to ensure everything is cleared |
| for (i = 0; i < FLASH_WORDS_PER_PAGE; i++) { |
| if (REG32(bank1_addr + i * 4) != 0xFFFFFFFF) { |
| flash_write_scratch_reg(0xDEADBEEF); |
| break_on_error(1); |
| } |
| } |
| |
| // do 4K programming |
| // employ the live programming method where overall payload >> flash fifo size |
| for (i = 0; i < ARRAYSIZE(prog_array); i++) { |
| prog_array[i] = (i % 2) ? 0xA5A5A5A5 : 0x5A5A5A5A; |
| } |
| |
| // initialize test regions |
| break_on_error(flash_page_erase(bank1_addr)); |
| break_on_error(flash_page_erase(bank0_last_page)); |
| for (iteration = 0; iteration < 2; iteration++) { |
| test_addr = iteration ? bank1_addr : bank0_last_page; |
| break_on_error(flash_write(test_addr, prog_array, ARRAYSIZE(prog_array))); |
| break_on_error(flash_read(test_addr, ARRAYSIZE(rd_array), rd_array)); |
| break_on_error(!check_arr_eq(rd_array, prog_array, ARRAYSIZE(rd_array))); |
| } |
| |
| ///////////////////////////////////////////////////////////// |
| // Begin flash memory protection testing |
| ///////////////////////////////////////////////////////////// |
| uint32_t region_base_page = FLASH_PAGES_PER_BANK; |
| uint32_t region_size = 1; |
| uint32_t good_addr_start = |
| FLASH_MEM_BASE_ADDR + region_base_page * FLASH_PAGE_SZ; |
| uint32_t good_addr_end = good_addr_start + region_size * FLASH_PAGE_SZ - 1; |
| uint32_t bad_addr_start = |
| good_addr_end + 1; // this is always aligned to a page |
| uint32_t good_words = 3; |
| uint32_t bad_words = 3; |
| uint32_t chk_addr = bad_addr_start - (FLASH_WORD_SZ * good_words); |
| |
| mp_region_t region0 = {.num = 0, |
| .base = region_base_page, |
| .size = region_size, |
| .rd_en = 1, |
| .prog_en = 1, |
| .erase_en = 1}; |
| |
| // initialize good and bad regions. |
| break_on_error(flash_page_erase(good_addr_start)); |
| break_on_error(flash_page_erase(bad_addr_start)); |
| |
| // turn off default region all access |
| flash_default_region_access(0, 0, 0); |
| flash_cfg_region(®ion0); |
| |
| // expect write to fail. |
| for (uint32_t i = 0; i < good_words + bad_words; i++) { |
| prog_array[i] = 0xA5A5A5A5 + i; |
| } |
| break_on_error(!flash_write(chk_addr, prog_array, good_words + bad_words)); |
| |
| // the good words should match |
| for (uint32_t i = 0; i < good_words; i++) { |
| if (REG32(chk_addr + i * 4) != prog_array[i]) { |
| break_on_error(1); |
| } |
| } |
| |
| // the bad word contents should not have gone through |
| for (uint32_t i = good_words; i < good_words + bad_words; i++) { |
| if (REG32(chk_addr + i * 4) != 0xFFFFFFFF) { |
| break_on_error(1); |
| } |
| } |
| |
| // attempt to erase bad page, should error |
| break_on_error(!flash_page_erase(bad_addr_start)); |
| |
| // erase the good page |
| break_on_error(flash_page_erase(good_addr_start)); |
| |
| // double check erase results |
| for (uint32_t i = 0; i < FLASH_WORDS_PER_PAGE; i++) { |
| if (REG32(good_addr_start + i * 4) != 0xFFFFFFFF) { |
| break_on_error(1); |
| } |
| } |
| |
| flash_cfg_bank_erase(FLASH_BANK_0, /*erase_en=*/false); |
| flash_cfg_bank_erase(FLASH_BANK_1, /*erase_en=*/false); |
| |
| // cleanly terminate execution |
| uart_send_str("PASS!\r\n"); |
| __asm__ volatile("wfi;"); |
| } |