460 lines
9.8 KiB
C
460 lines
9.8 KiB
C
/* Copyright 2013-2014 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#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;
|
|
}
|