| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| /* Copyright 2013-2017 IBM Corp. */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <libflash/libflash.h> |
| #include <libflash/libflash-priv.h> |
| |
| #include "../libflash.c" |
| #include "../ecc.c" |
| |
| #define __unused __attribute__((unused)) |
| |
| #define ERR(fmt...) fprintf(stderr, fmt) |
| |
| /* Flash commands */ |
| #define CMD_PP 0x02 |
| #define CMD_READ 0x03 |
| #define CMD_WRDI 0x04 |
| #define CMD_RDSR 0x05 |
| #define CMD_WREN 0x06 |
| #define CMD_SE 0x20 |
| #define CMD_RDSCUR 0x2b |
| #define CMD_BE32K 0x52 |
| #define CMD_CE 0x60 |
| #define CMD_RDID 0x9f |
| #define CMD_EN4B 0xb7 |
| #define CMD_BE 0xd8 |
| #define CMD_RDDPB 0xe0 |
| #define CMD_RDSPB 0xe2 |
| #define CMD_EX4B 0xe9 |
| |
| /* Flash status bits */ |
| #define STAT_WIP 0x01 |
| #define STAT_WEN 0x02 |
| |
| static uint8_t *sim_image; |
| static uint32_t sim_image_sz = 0x100000; |
| static uint32_t sim_index; |
| static uint32_t sim_addr; |
| static uint32_t sim_er_size; |
| static uint8_t sim_sr; |
| static bool sim_fl_4b; |
| static bool sim_ct_4b; |
| |
| static enum sim_state { |
| sim_state_idle, |
| sim_state_rdid, |
| sim_state_rdsr, |
| sim_state_read_addr, |
| sim_state_read_data, |
| sim_state_write_addr, |
| sim_state_write_data, |
| sim_state_erase_addr, |
| sim_state_erase_done, |
| } sim_state; |
| |
| /* |
| * Simulated flash & controller |
| */ |
| static int sim_start_cmd(uint8_t cmd) |
| { |
| if (sim_state != sim_state_idle) { |
| ERR("SIM: Command %02x in wrong state %d\n", cmd, sim_state); |
| return -1; |
| } |
| |
| sim_index = 0; |
| sim_addr = 0; |
| |
| switch(cmd) { |
| case CMD_RDID: |
| sim_state = sim_state_rdid; |
| break; |
| case CMD_RDSR: |
| sim_state = sim_state_rdsr; |
| break; |
| case CMD_EX4B: |
| sim_fl_4b = false; |
| break; |
| case CMD_EN4B: |
| sim_fl_4b = true; |
| break; |
| case CMD_WREN: |
| sim_sr |= STAT_WEN; |
| break; |
| case CMD_READ: |
| sim_state = sim_state_read_addr; |
| if (sim_ct_4b != sim_fl_4b) |
| ERR("SIM: 4b mode mismatch in READ !\n"); |
| break; |
| case CMD_PP: |
| sim_state = sim_state_write_addr; |
| if (sim_ct_4b != sim_fl_4b) |
| ERR("SIM: 4b mode mismatch in PP !\n"); |
| if (!(sim_sr & STAT_WEN)) |
| ERR("SIM: PP without WEN, ignoring... \n"); |
| break; |
| case CMD_SE: |
| case CMD_BE32K: |
| case CMD_BE: |
| if (sim_ct_4b != sim_fl_4b) |
| ERR("SIM: 4b mode mismatch in SE/BE !\n"); |
| if (!(sim_sr & STAT_WEN)) |
| ERR("SIM: SE/BE without WEN, ignoring... \n"); |
| sim_state = sim_state_erase_addr; |
| switch(cmd) { |
| case CMD_SE: sim_er_size = 0x1000; break; |
| case CMD_BE32K: sim_er_size = 0x8000; break; |
| case CMD_BE: sim_er_size = 0x10000; break; |
| } |
| break; |
| case CMD_CE: |
| if (!(sim_sr & STAT_WEN)) { |
| ERR("SIM: CE without WEN, ignoring... \n"); |
| break; |
| } |
| memset(sim_image, 0xff, sim_image_sz); |
| sim_sr |= STAT_WIP; |
| sim_sr &= ~STAT_WEN; |
| break; |
| default: |
| ERR("SIM: Unsupported command %02x\n", cmd); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static void sim_end_cmd(void) |
| { |
| /* For write and sector/block erase, set WIP & clear WEN here */ |
| if (sim_state == sim_state_write_data) { |
| sim_sr |= STAT_WIP; |
| sim_sr &= ~STAT_WEN; |
| } |
| sim_state = sim_state_idle; |
| } |
| |
| static bool sim_do_address(const uint8_t **buf, uint32_t *len) |
| { |
| uint8_t asize = sim_fl_4b ? 4 : 3; |
| const uint8_t *p = *buf; |
| |
| while(*len) { |
| sim_addr = (sim_addr << 8) | *(p++); |
| *buf = p; |
| *len = *len - 1; |
| sim_index++; |
| if (sim_index >= asize) |
| return true; |
| } |
| return false; |
| } |
| |
| static int sim_wbytes(const void *buf, uint32_t len) |
| { |
| const uint8_t *b = buf; |
| bool addr_complete; |
| |
| again: |
| switch(sim_state) { |
| case sim_state_read_addr: |
| addr_complete = sim_do_address(&b, &len); |
| if (addr_complete) { |
| sim_state = sim_state_read_data; |
| sim_index = 0; |
| if (len) |
| goto again; |
| } |
| break; |
| case sim_state_write_addr: |
| addr_complete = sim_do_address(&b, &len); |
| if (addr_complete) { |
| sim_state = sim_state_write_data; |
| sim_index = 0; |
| if (len) |
| goto again; |
| } |
| break; |
| case sim_state_write_data: |
| if (!(sim_sr & STAT_WEN)) |
| break; |
| while(len--) { |
| uint8_t c = *(b++); |
| if (sim_addr >= sim_image_sz) { |
| ERR("SIM: Write past end of flash\n"); |
| return -1; |
| } |
| /* Flash write only clears bits */ |
| sim_image[sim_addr] &= c; |
| sim_addr = (sim_addr & 0xffffff00) | |
| ((sim_addr + 1) & 0xff); |
| } |
| break; |
| case sim_state_erase_addr: |
| if (!(sim_sr & STAT_WEN)) |
| break; |
| addr_complete = sim_do_address(&b, &len); |
| if (addr_complete) { |
| memset(sim_image + sim_addr, 0xff, sim_er_size); |
| sim_sr |= STAT_WIP; |
| sim_sr &= ~STAT_WEN; |
| sim_state = sim_state_erase_done; |
| } |
| break; |
| default: |
| ERR("SIM: Write in wrong state %d\n", sim_state); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int sim_rbytes(void *buf, uint32_t len) |
| { |
| uint8_t *b = buf; |
| |
| switch(sim_state) { |
| case sim_state_rdid: |
| while(len--) { |
| switch(sim_index) { |
| case 0: |
| *(b++) = 0x55; |
| break; |
| case 1: |
| *(b++) = 0xaa; |
| break; |
| case 2: |
| *(b++) = 0x55; |
| break; |
| default: |
| ERR("SIM: RDID index %d\n", sim_index); |
| *(b++) = 0; |
| break; |
| } |
| sim_index++; |
| } |
| break; |
| case sim_state_rdsr: |
| while(len--) { |
| *(b++) = sim_sr; |
| if (sim_index > 0) |
| ERR("SIM: RDSR index %d\n", sim_index); |
| sim_index++; |
| |
| /* If WIP was 1, clear it, ie, simulate write/erase |
| * completion |
| */ |
| sim_sr &= ~STAT_WIP; |
| } |
| break; |
| case sim_state_read_data: |
| while(len--) { |
| if (sim_addr >= sim_image_sz) { |
| ERR("SIM: Read past end of flash\n"); |
| return -1; |
| } |
| *(b++) = sim_image[sim_addr++]; |
| } |
| break; |
| default: |
| ERR("SIM: Read in wrong state %d\n", sim_state); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int sim_send_addr(uint32_t addr) |
| { |
| const void *ap; |
| |
| /* Layout address MSB first in memory */ |
| addr = cpu_to_be32(addr); |
| |
| /* Send the right amount of bytes */ |
| ap = (char *)&addr; |
| |
| if (sim_ct_4b) |
| return sim_wbytes(ap, 4); |
| else |
| return sim_wbytes(ap + 1, 3); |
| } |
| |
| static int sim_cmd_rd(struct spi_flash_ctrl *ctrl __unused, uint8_t cmd, |
| bool has_addr, uint32_t addr, void *buffer, |
| uint32_t size) |
| { |
| int rc; |
| |
| rc = sim_start_cmd(cmd); |
| if (rc) |
| goto bail; |
| if (has_addr) { |
| rc = sim_send_addr(addr); |
| if (rc) |
| goto bail; |
| } |
| if (buffer && size) |
| rc = sim_rbytes(buffer, size); |
| bail: |
| sim_end_cmd(); |
| return rc; |
| } |
| |
| static int sim_cmd_wr(struct spi_flash_ctrl *ctrl __unused, uint8_t cmd, |
| bool has_addr, uint32_t addr, const void *buffer, |
| uint32_t size) |
| { |
| int rc; |
| |
| rc = sim_start_cmd(cmd); |
| if (rc) |
| goto bail; |
| if (has_addr) { |
| rc = sim_send_addr(addr); |
| if (rc) |
| goto bail; |
| } |
| if (buffer && size) |
| rc = sim_wbytes(buffer, size); |
| bail: |
| sim_end_cmd(); |
| return rc; |
| } |
| |
| static int sim_set_4b(struct spi_flash_ctrl *ctrl __unused, bool enable) |
| { |
| sim_ct_4b = enable; |
| |
| return 0; |
| } |
| |
| static int sim_read(struct spi_flash_ctrl *ctrl __unused, uint32_t pos, |
| void *buf, uint32_t len) |
| { |
| if (sim_ct_4b != sim_fl_4b) |
| ERR("SIM: 4b mode mismatch in autoread !\n"); |
| if ((pos + len) < pos) |
| return -1; |
| if ((pos + len) > sim_image_sz) |
| return -1; |
| memcpy(buf, sim_image + pos, len); |
| return 0; |
| }; |
| |
| struct spi_flash_ctrl sim_ctrl = { |
| .cmd_wr = sim_cmd_wr, |
| .cmd_rd = sim_cmd_rd, |
| .set_4b = sim_set_4b, |
| .read = sim_read, |
| }; |
| |
| int main(void) |
| { |
| struct blocklevel_device *bl; |
| uint64_t total_size; |
| uint32_t erase_granule; |
| const char *name; |
| uint16_t *test; |
| struct ecc64 *ecc_test; |
| uint64_t *test64; |
| int i, rc; |
| |
| sim_image = malloc(sim_image_sz); |
| memset(sim_image, 0xff, sim_image_sz); |
| test = malloc(0x10000 * 2); |
| |
| rc = flash_init(&sim_ctrl, &bl, NULL); |
| if (rc) { |
| ERR("flash_init failed with err %d\n", rc); |
| exit(1); |
| } |
| rc = flash_get_info(bl, &name, &total_size, &erase_granule); |
| if (rc) { |
| ERR("flash_get_info failed with err %d\n", rc); |
| exit(1); |
| } |
| |
| /* Make up a test pattern */ |
| for (i=0; i<0x10000;i++) |
| test[i] = cpu_to_be16(i); |
| |
| /* Write 64k of stuff at 0 and at 128k */ |
| printf("Writing test patterns...\n"); |
| flash_smart_write(bl, 0, test, 0x10000); |
| flash_smart_write(bl, 0x20000, test, 0x10000); |
| |
| /* Write "Hello world" straddling the 64k boundary */ |
| #define HW "Hello World" |
| printf("Writing test string...\n"); |
| flash_smart_write(bl, 0xfffc, HW, sizeof(HW)); |
| |
| /* Check result */ |
| if (memcmp(sim_image + 0xfffc, HW, sizeof(HW))) { |
| ERR("Test string mismatch !\n"); |
| exit(1); |
| } |
| printf("Test string pass\n"); |
| if (memcmp(sim_image, test, 0xfffc)) { |
| ERR("Test pattern mismatch !\n"); |
| exit(1); |
| } |
| printf("Test pattern pass\n"); |
| |
| printf("Test ECC interfaces\n"); |
| flash_smart_write_corrected(bl, 0, test, 0x10000, 1); |
| ecc_test = (struct ecc64 *)sim_image; |
| test64 = (uint64_t *)test; |
| for (i = 0; i < 0x10000 / sizeof(*ecc_test); i++) { |
| if (test64[i] != ecc_test[i].data) { |
| ERR("flash_smart_write_corrected() pattern missmatch at %d: 0x%016lx vs 0x%016lx\n", |
| i, test64[i], ecc_test[i].data); |
| exit(1); |
| } |
| if (ecc_test[i].ecc != eccgenerate(be64toh(test64[i]))) { |
| ERR("ECCs don't match 0x%02x vs 0x%02x\n", ecc_test[i].ecc, eccgenerate(test64[i])); |
| exit(1); |
| } |
| } |
| printf("Test ECC interface pass\n"); |
| |
| printf("Test ECC erase\n"); |
| if (flash_erase(bl, 0, 0x10000) != 0) { |
| ERR("flash_erase didn't return 0\n"); |
| exit(1); |
| } |
| |
| for (i = 0; i < 0x10000 / sizeof(*ecc_test); i++) { |
| uint8_t zero = 0; |
| if (ecc_test[i].data != 0xFFFFFFFFFFFFFFFF) { |
| ERR("Data not properly cleared at %d\n", i); |
| exit(1); |
| } |
| rc = flash_write(bl, i * sizeof(*ecc_test) + 8, &zero, 1, 0); |
| if (rc || ecc_test[i].ecc != 0) { |
| ERR("Cleared data not correctly ECCed: 0x%02x (0x%016lx) expecting 0 at %d\n", ecc_test[i].ecc, ecc_test[i].data, i); |
| exit(1); |
| } |
| } |
| printf("Test ECC erase pass\n"); |
| |
| flash_exit(bl); |
| free(test); |
| |
| return 0; |
| } |