Message ID | 1331565738-18509-3-git-send-email-vapier@gentoo.org |
---|---|
State | Changes Requested |
Delegated to: | Simon Glass |
Headers | show |
Hi Mike, On Mon, Mar 12, 2012 at 8:22 AM, Mike Frysinger <vapier@gentoo.org> wrote: > This adds a SPI flash driver which simulates SPI flash clients. > Currently supports the bare min that U-Boot requires: you can > probe, read, erase, and write. Should be easy to extend to make > it behave more exactly like a real SPI flash, but this is good > enough to merge now. > > Signed-off-by: Mike Frysinger <vapier@gentoo.org> Looks really nice. I have a few nits below. > --- > v3 > - rearchitected on top of state/getopt support > > drivers/mtd/spi/Makefile | 1 + > drivers/mtd/spi/sandbox.c | 402 +++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 403 insertions(+), 0 deletions(-) > create mode 100644 drivers/mtd/spi/sandbox.c > > diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile > index 90f8392..fb37807 100644 > --- a/drivers/mtd/spi/Makefile > +++ b/drivers/mtd/spi/Makefile > @@ -33,6 +33,7 @@ COBJS-$(CONFIG_SPI_FLASH) += spi_flash.o > COBJS-$(CONFIG_SPI_FLASH_ATMEL) += atmel.o > COBJS-$(CONFIG_SPI_FLASH_EON) += eon.o > COBJS-$(CONFIG_SPI_FLASH_MACRONIX) += macronix.o > +COBJS-$(CONFIG_SPI_FLASH_SANDBOX) += sandbox.o > COBJS-$(CONFIG_SPI_FLASH_SPANSION) += spansion.o > COBJS-$(CONFIG_SPI_FLASH_SST) += sst.o > COBJS-$(CONFIG_SPI_FLASH_STMICRO) += stmicro.o > diff --git a/drivers/mtd/spi/sandbox.c b/drivers/mtd/spi/sandbox.c > new file mode 100644 > index 0000000..1cbe454 > --- /dev/null > +++ b/drivers/mtd/spi/sandbox.c > @@ -0,0 +1,402 @@ > +/* > + * Simulate a SPI flash > + * > + * Copyright (c) 2011-2012 The Chromium OS Authors. > + * See file CREDITS for list of people who contributed to this > + * project. > + * > + * Licensed under the GPL-2 or later. > + */ > + > +#include <common.h> > +#include <malloc.h> > +#include <spi.h> > +#include <os.h> > + > +#include <spi_flash.h> > +#include "spi_flash_internal.h" > + > +#include <asm/getopt.h> > +#include <asm/spi.h> > +#include <asm/state.h> > + > +/* > + * The different states that our SPI flash transitions between. > + * We need to keep track of this across multiple xfer calls since > + * the SPI bus could possibly call down into us multiple times. > + */ > +typedef enum { > + SF_CMD, /* default state -- we're awaiting a command */ > + SF_ID, /* read the flash's (jedec) ID code */ > + SF_ADDR, /* processing the offset in the flash to read/etc... */ > + SF_READ, /* reading data from the flash */ > + SF_WRITE, /* writing data to the flash, i.e. page programming */ > + SF_ERASE, /* erase the flash */ > + SF_READ_STATUS, /* read the flash's status register */ > +} sb_sf_state; > + > +static const char *sb_sf_state_name(sb_sf_state state) > +{ > + static const char * const states[] = { > + "CMD", "ID", "ADDR", "READ", "WRITE", "ERASE", "READ_STATUS", > + }; > + return states[state]; > +} > + > +/* Bits for the status register */ > +#define STAT_WIP (1 << 0) > +#define STAT_WEL (1 << 1) > + > +/* Assume all SPI flashes have 3 byte addresses since they do atm */ > +#define SF_ADDR_LEN 3 > + > +struct sb_spi_flash_erase_commands { > + u8 cmd; > + u32 size; > +}; > +#define IDCODE_LEN 5 > +#define MAX_ERASE_CMDS 2 > +struct sb_spi_flash_data { > + const char *name; > + u8 idcode[IDCODE_LEN]; > + u32 size; > + const struct sb_spi_flash_erase_commands erase_cmds[MAX_ERASE_CMDS]; > +}; > + > +/* Structure describing all the flashes we know how to emulate */ > +static const struct sb_spi_flash_data sb_sf_flashes[] = { > + { > + "M25P16", { 0x20, 0x20, 0x15 }, (2 * 1024 * 1024), > + { /* erase commands */ > + { 0xd8, (64 * 1024), }, /* sector */ > + { 0xc7, (2 * 1024 * 1024), }, /* bulk */ > + }, > + }, > +}; > + > +/* Used to quickly bulk erase backing store */ > +static u8 sb_sf_0xff[0x10000]; Ick, Does it really need to be so large? > + > +/* Internal state data for each SPI flash */ > +struct sb_spi_flash { > + /* > + * As we receive data over the SPI bus, our flash transitions > + * between states. For example, we start off in the SF_CMD > + * state where the first byte tells us what operation to perform > + * (such as read or write the flash). But the operation itself > + * can go through a few states such as first reading in the > + * offset in the flash to perform the requested operation. > + * Thus "state" stores the exact state that our machine is in > + * while "cmd" stores the overall command we're processing. > + */ > + sb_sf_state state; > + uint cmd; > + const void *cmd_data; > + /* Current position in the flash; used when reading/writing/etc... */ > + uint off; > + /* How many address bytes we've consumed */ > + uint addr_bytes, pad_addr_bytes; > + /* The current flash status (see STAT_XXX defines above) */ > + u8 status; > + /* Data describing the flash we're emulating */ > + const struct sb_spi_flash_data *data; > + /* The file on disk to serv up data from */ > + int fd; > +}; > + > +static int sb_sf_setup(void **priv, const char *spec) > +{ > + /* spec = idcode:file */ > + struct sb_spi_flash *sbsf; > + const char *file; > + size_t i, len, idname_len; > + const struct sb_spi_flash_data *data; > + > + file = strchr(spec, ':'); > + if (!file) > + goto error; > + idname_len = file - spec; > + ++file; > + > + for (i = 0; i < ARRAY_SIZE(sb_sf_flashes); ++i) { > + data = &sb_sf_flashes[i]; > + len = strlen(data->name); > + if (idname_len != len) > + continue; > + if (!memcmp(spec, data->name, len)) > + break; > + } > + if (i == ARRAY_SIZE(sb_sf_flashes)) { > + printf("sb_sf: unknown flash '%*s'\n", > + (int)idname_len, file); > + goto error; > + } > + > + if (sb_sf_0xff[0] == 0x00) > + memset(sb_sf_0xff, 0xff, sizeof(sb_sf_0xff)); > + > + sbsf = malloc(sizeof(*sbsf)); > + if (!sbsf) > + goto error; > + > + sbsf->fd = os_open(file, 02); > + if (sbsf->fd == -1) { > + free(sbsf); > + goto error; Prints incorrect error if the file couldn't be found/open. I wonder if we should have os_perror() ? > + } > + > + sbsf->data = data; > + > + *priv = sbsf; > + return 0; > + > + error: > + printf("sb_sf: unable to parse client spec\n"); > + return 1; > +} > + > +static void sb_sf_free(void *priv) > +{ > + struct sb_spi_flash *sbsf = priv; > + > + os_close(sbsf->fd); > + free(sbsf); > +} > + > +static void sb_sf_cs_activate(void *priv) > +{ > + struct sb_spi_flash *sbsf = priv; > + > + debug("sb_sf: CS activated; state is fresh!\n"); > + > + /* CS is asserted, so reset state */ > + sbsf->off = 0; > + sbsf->addr_bytes = 0; > + sbsf->pad_addr_bytes = 0; > + sbsf->state = sbsf->cmd = SF_CMD; > +} > + > +static void sb_sf_cs_deactivate(void *priv) > +{ > + debug("sb_sf: CS deactivated; cmd done processing!\n"); > +} > + > +/* Figure out what command this stream is telling us to do */ > +static int sb_sf_process_cmd(struct sb_spi_flash *sbsf, const u8 *rx, u8 *tx) > +{ > + sb_sf_state oldstate = sbsf->state; > + > + /* We need to output a byte for the cmd byte we just ate */ > + sb_spi_tristate(tx, 1); > + > + sbsf->cmd = rx[0]; > + switch (sbsf->cmd) { > + case CMD_READ_ID: > + sbsf->state = sbsf->cmd = SF_ID; > + break; > + case CMD_READ_ARRAY_FAST: > + sbsf->pad_addr_bytes = 1; > + case CMD_READ_ARRAY_SLOW: > + case CMD_PAGE_PROGRAM: > + state_addr: > + sbsf->state = SF_ADDR; > + break; > + case CMD_WRITE_DISABLE: > + debug(" write disabled\n"); > + sbsf->status &= ~STAT_WEL; > + break; > + case CMD_READ_STATUS: > + sbsf->state = SF_READ_STATUS; > + break; > + case CMD_WRITE_ENABLE: > + debug(" write enabled\n"); > + sbsf->status |= STAT_WEL; > + break; > + default: { > + size_t i; > + > + /* handle erase commands first */ > + for (i = 0; i < MAX_ERASE_CMDS; ++i) { > + const struct sb_spi_flash_erase_commands *erase_cmd = > + &sbsf->data->erase_cmds[i]; > + > + if (erase_cmd->cmd == 0x00) > + continue; > + if (sbsf->cmd != erase_cmd->cmd) > + continue; > + > + sbsf->cmd_data = erase_cmd; > + goto state_addr; > + } > + > + debug(" cmd unknown: %#x\n", sbsf->cmd); > + return 1; > + } > + } > + > + if (oldstate != sbsf->state) > + debug(" cmd: transition to %s state\n", > + sb_sf_state_name(sbsf->state)); > + > + return 0; > +} > + > +static int sb_sf_xfer(void *priv, const u8 *rx, u8 *tx, > + uint bytes) > +{ > + struct sb_spi_flash *sbsf = priv; > + uint cnt, pos = 0; > + > + debug("sb_sf: state:%x(%s) bytes:%u\n", sbsf->state, > + sb_sf_state_name(sbsf->state), bytes); > + > + if (sbsf->state == SF_CMD) { > + /* Figure out the initial state */ > + if (sb_sf_process_cmd(sbsf, rx, tx)) > + return 1; > + ++pos; > + } > + > + /* Process the remaining data */ > + while (pos < bytes) { > + switch (sbsf->state) { > + case SF_ID: { > + u8 id; > + > + debug(" id: off:%u tx:", sbsf->off); > + if (sbsf->off < IDCODE_LEN) > + id = sbsf->data->idcode[sbsf->off]; > + else > + id = 0; > + debug("%02x\n", id); > + tx[pos++] = id; > + ++sbsf->off; > + break; > + } > + case SF_ADDR: > + debug(" addr: bytes:%u rx:%02x ", > + sbsf->addr_bytes, rx[pos]); > + > + if (sbsf->addr_bytes++ < SF_ADDR_LEN) > + sbsf->off = (sbsf->off << 8) | rx[pos]; > + debug("addr:%06x\n", sbsf->off); > + > + sb_spi_tristate(&tx[pos++], 1); > + > + /* See if we're done processing */ > + if (sbsf->addr_bytes < SF_ADDR_LEN + sbsf->pad_addr_bytes) > + break; > + > + /* Next state! */ > + os_lseek(sbsf->fd, sbsf->off, OS_SEEK_SET); check error return > + switch (sbsf->cmd) { > + case CMD_READ_ARRAY_FAST: > + case CMD_READ_ARRAY_SLOW: > + sbsf->state = SF_READ; > + break; > + case CMD_PAGE_PROGRAM: > + sbsf->state = SF_WRITE; > + break; > + default: > + /* assume erase state ... */ > + sbsf->state = SF_ERASE; > + goto case_SF_ERASE; > + } > + debug(" cmd: transition to %s state\n", > + sb_sf_state_name(sbsf->state)); > + break; > + case SF_READ: > + /* > + * XXX: need to handle exotic behavior: > + * - reading past end of device > + */ > + > + cnt = bytes - pos; > + debug(" tx: read(%u)\n", cnt); > + pos += os_read(sbsf->fd, tx + pos, cnt); This can fail (return -1) > + break; > + case SF_READ_STATUS: > + debug(" read status: %#x\n", sbsf->status); > + cnt = bytes - pos; > + memset(tx + pos, sbsf->status, cnt); > + pos += cnt; > + break; > + case SF_WRITE: > + /* > + * XXX: need to handle exotic behavior: > + * - unaligned addresses > + * - more than a page (256) worth of data > + * - reading past end of device > + */ > + > + if (!(sbsf->status & STAT_WEL)) { > + puts("sb_sf: write enable not set before erase\n"); > + goto done; > + } > + > + cnt = bytes - pos; > + debug(" rx: write(%u)\n", cnt); > + sb_spi_tristate(&tx[pos], cnt); > + pos += os_write(sbsf->fd, rx + pos, cnt); error return > + sbsf->status &= ~STAT_WEL; > + break; > + case SF_ERASE: > + case_SF_ERASE: { > + const struct sb_spi_flash_erase_commands *erase_cmd = > + sbsf->cmd_data; > + > + if (!(sbsf->status & STAT_WEL)) { > + puts("sb_sf: write enable not set before erase\n"); > + goto done; > + } > + > + /* verify address is aligned */ > + if (sbsf->off & ~(erase_cmd->size - 1)) { > + debug(" sector erase: cmd:%#x needs align:%#x, but we got %#x\n", > + erase_cmd->cmd, erase_cmd->size, sbsf->off); > + sbsf->status &= ~STAT_WEL; > + goto done; > + } > + > + debug(" sector erase addr: %u\n", sbsf->off); > + > + cnt = bytes - pos; > + sb_spi_tristate(&tx[pos], cnt); > + pos += cnt; > + > + /* XXX: latch WIP in status, and delay before clearing it ? */ > + os_write(sbsf->fd, sb_sf_0xff, erase_cmd->size); Check error return? > + sbsf->status &= ~STAT_WEL; > + goto done; > + } > + default: > + debug(" ??? no idea what to do ???\n"); > + goto done; > + } > + } > + > + done: > + return pos == bytes ? 0 : 1; > +} > + > +static const struct sb_spi_emu_ops sb_sf_ops = { > + .setup = sb_sf_setup, > + .free = sb_sf_free, > + .cs_activate = sb_sf_cs_activate, > + .cs_deactivate = sb_sf_cs_deactivate, > + .xfer = sb_sf_xfer, > +}; > + > +static int sb_cmdline_cb_spi_sf(struct sandbox_state *state, const char *arg) > +{ > + unsigned long bus, cs; > + const char *spec = sb_spi_parse_spec(arg, &bus, &cs); > + > + if (!spec) > + return 1; > + > + state->spi[bus][cs][0] = &sb_sf_ops; > + state->spi[bus][cs][1] = spec; > + return 0; > +} > +SB_CMDLINE_OPT(spi_sf, 1, "connect a SPI flash: <bus>:<cs>:<id>:<file>"); > -- > 1.7.8.5 > Regards, Simon
On Thursday 15 March 2012 00:09:59 Simon Glass wrote: > On Mon, Mar 12, 2012 at 8:22 AM, Mike Frysinger wrote: > > +/* Used to quickly bulk erase backing store */ > > +static u8 sb_sf_0xff[0x10000]; > > Ick, Does it really need to be so large? in order to do a single write() for a single sector, yes. it also simplifies the code as i don't have to loop over some smaller size and keep track of how many bytes i've written until a sector is cleared. this is in the bss and it's 64KiB. i'm really not worried about your development system running Chrome getting low on RAM :). > > + sbsf->fd = os_open(file, 02); > > + if (sbsf->fd == -1) { > > + free(sbsf); > > + goto error; > > Prints incorrect error if the file couldn't be found/open. I wonder if > we should have os_perror() ? probably would be useful. we prob have to document that while os_perror() would work, strerror(errno) would not. the os_* funcs update the errno that the C library knows about (or *__errno_location() in glibc), but u-boot declares a local "errno" variable. so attempting to reference "errno" in u- boot code will always resolve to the local definition rather than the glibc one. os_perror() would work because it calls __errno_location() internally just like the other os_* funcs. > > + pos += os_read(sbsf->fd, tx + pos, cnt); > > This can fail (return -1) the ways in which read/write could fail aren't really possible for us, but i guess it won't hurt to assert() the values. -mike
Hi Mike, On Wed, Mar 14, 2012 at 10:24 PM, Mike Frysinger <vapier@gentoo.org> wrote: > On Thursday 15 March 2012 00:09:59 Simon Glass wrote: >> On Mon, Mar 12, 2012 at 8:22 AM, Mike Frysinger wrote: >> > +/* Used to quickly bulk erase backing store */ >> > +static u8 sb_sf_0xff[0x10000]; >> >> Ick, Does it really need to be so large? > > in order to do a single write() for a single sector, yes. it also simplifies > the code as i don't have to loop over some smaller size and keep track of how > many bytes i've written until a sector is cleared. > > this is in the bss and it's 64KiB. i'm really not worried about your > development system running Chrome getting low on RAM :). No, it's more that I think it is a bit grubby. But OK. > >> > + sbsf->fd = os_open(file, 02); >> > + if (sbsf->fd == -1) { >> > + free(sbsf); >> > + goto error; >> >> Prints incorrect error if the file couldn't be found/open. I wonder if >> we should have os_perror() ? > > probably would be useful. we prob have to document that while os_perror() > would work, strerror(errno) would not. the os_* funcs update the errno that > the C library knows about (or *__errno_location() in glibc), but u-boot > declares a local "errno" variable. so attempting to reference "errno" in u- > boot code will always resolve to the local definition rather than the glibc > one. os_perror() would work because it calls __errno_location() internally > just like the other os_* funcs. Yes a bit messy, let's leave it for now then. > >> > + pos += os_read(sbsf->fd, tx + pos, cnt); >> >> This can fail (return -1) > > the ways in which read/write could fail aren't really possible for us, but i > guess it won't hurt to assert() the values. Are you sure? What if the file has a read error? That could happen in the test setup. To me an assert is a bit ugly for this sort of thing. Also for test code we don't want it dying in the bowels of the test with an assert - better to return test failure so people can see what went wrong. Regards, Simon > -mike
diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile index 90f8392..fb37807 100644 --- a/drivers/mtd/spi/Makefile +++ b/drivers/mtd/spi/Makefile @@ -33,6 +33,7 @@ COBJS-$(CONFIG_SPI_FLASH) += spi_flash.o COBJS-$(CONFIG_SPI_FLASH_ATMEL) += atmel.o COBJS-$(CONFIG_SPI_FLASH_EON) += eon.o COBJS-$(CONFIG_SPI_FLASH_MACRONIX) += macronix.o +COBJS-$(CONFIG_SPI_FLASH_SANDBOX) += sandbox.o COBJS-$(CONFIG_SPI_FLASH_SPANSION) += spansion.o COBJS-$(CONFIG_SPI_FLASH_SST) += sst.o COBJS-$(CONFIG_SPI_FLASH_STMICRO) += stmicro.o diff --git a/drivers/mtd/spi/sandbox.c b/drivers/mtd/spi/sandbox.c new file mode 100644 index 0000000..1cbe454 --- /dev/null +++ b/drivers/mtd/spi/sandbox.c @@ -0,0 +1,402 @@ +/* + * Simulate a SPI flash + * + * Copyright (c) 2011-2012 The Chromium OS Authors. + * See file CREDITS for list of people who contributed to this + * project. + * + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <spi.h> +#include <os.h> + +#include <spi_flash.h> +#include "spi_flash_internal.h" + +#include <asm/getopt.h> +#include <asm/spi.h> +#include <asm/state.h> + +/* + * The different states that our SPI flash transitions between. + * We need to keep track of this across multiple xfer calls since + * the SPI bus could possibly call down into us multiple times. + */ +typedef enum { + SF_CMD, /* default state -- we're awaiting a command */ + SF_ID, /* read the flash's (jedec) ID code */ + SF_ADDR, /* processing the offset in the flash to read/etc... */ + SF_READ, /* reading data from the flash */ + SF_WRITE, /* writing data to the flash, i.e. page programming */ + SF_ERASE, /* erase the flash */ + SF_READ_STATUS, /* read the flash's status register */ +} sb_sf_state; + +static const char *sb_sf_state_name(sb_sf_state state) +{ + static const char * const states[] = { + "CMD", "ID", "ADDR", "READ", "WRITE", "ERASE", "READ_STATUS", + }; + return states[state]; +} + +/* Bits for the status register */ +#define STAT_WIP (1 << 0) +#define STAT_WEL (1 << 1) + +/* Assume all SPI flashes have 3 byte addresses since they do atm */ +#define SF_ADDR_LEN 3 + +struct sb_spi_flash_erase_commands { + u8 cmd; + u32 size; +}; +#define IDCODE_LEN 5 +#define MAX_ERASE_CMDS 2 +struct sb_spi_flash_data { + const char *name; + u8 idcode[IDCODE_LEN]; + u32 size; + const struct sb_spi_flash_erase_commands erase_cmds[MAX_ERASE_CMDS]; +}; + +/* Structure describing all the flashes we know how to emulate */ +static const struct sb_spi_flash_data sb_sf_flashes[] = { + { + "M25P16", { 0x20, 0x20, 0x15 }, (2 * 1024 * 1024), + { /* erase commands */ + { 0xd8, (64 * 1024), }, /* sector */ + { 0xc7, (2 * 1024 * 1024), }, /* bulk */ + }, + }, +}; + +/* Used to quickly bulk erase backing store */ +static u8 sb_sf_0xff[0x10000]; + +/* Internal state data for each SPI flash */ +struct sb_spi_flash { + /* + * As we receive data over the SPI bus, our flash transitions + * between states. For example, we start off in the SF_CMD + * state where the first byte tells us what operation to perform + * (such as read or write the flash). But the operation itself + * can go through a few states such as first reading in the + * offset in the flash to perform the requested operation. + * Thus "state" stores the exact state that our machine is in + * while "cmd" stores the overall command we're processing. + */ + sb_sf_state state; + uint cmd; + const void *cmd_data; + /* Current position in the flash; used when reading/writing/etc... */ + uint off; + /* How many address bytes we've consumed */ + uint addr_bytes, pad_addr_bytes; + /* The current flash status (see STAT_XXX defines above) */ + u8 status; + /* Data describing the flash we're emulating */ + const struct sb_spi_flash_data *data; + /* The file on disk to serv up data from */ + int fd; +}; + +static int sb_sf_setup(void **priv, const char *spec) +{ + /* spec = idcode:file */ + struct sb_spi_flash *sbsf; + const char *file; + size_t i, len, idname_len; + const struct sb_spi_flash_data *data; + + file = strchr(spec, ':'); + if (!file) + goto error; + idname_len = file - spec; + ++file; + + for (i = 0; i < ARRAY_SIZE(sb_sf_flashes); ++i) { + data = &sb_sf_flashes[i]; + len = strlen(data->name); + if (idname_len != len) + continue; + if (!memcmp(spec, data->name, len)) + break; + } + if (i == ARRAY_SIZE(sb_sf_flashes)) { + printf("sb_sf: unknown flash '%*s'\n", + (int)idname_len, file); + goto error; + } + + if (sb_sf_0xff[0] == 0x00) + memset(sb_sf_0xff, 0xff, sizeof(sb_sf_0xff)); + + sbsf = malloc(sizeof(*sbsf)); + if (!sbsf) + goto error; + + sbsf->fd = os_open(file, 02); + if (sbsf->fd == -1) { + free(sbsf); + goto error; + } + + sbsf->data = data; + + *priv = sbsf; + return 0; + + error: + printf("sb_sf: unable to parse client spec\n"); + return 1; +} + +static void sb_sf_free(void *priv) +{ + struct sb_spi_flash *sbsf = priv; + + os_close(sbsf->fd); + free(sbsf); +} + +static void sb_sf_cs_activate(void *priv) +{ + struct sb_spi_flash *sbsf = priv; + + debug("sb_sf: CS activated; state is fresh!\n"); + + /* CS is asserted, so reset state */ + sbsf->off = 0; + sbsf->addr_bytes = 0; + sbsf->pad_addr_bytes = 0; + sbsf->state = sbsf->cmd = SF_CMD; +} + +static void sb_sf_cs_deactivate(void *priv) +{ + debug("sb_sf: CS deactivated; cmd done processing!\n"); +} + +/* Figure out what command this stream is telling us to do */ +static int sb_sf_process_cmd(struct sb_spi_flash *sbsf, const u8 *rx, u8 *tx) +{ + sb_sf_state oldstate = sbsf->state; + + /* We need to output a byte for the cmd byte we just ate */ + sb_spi_tristate(tx, 1); + + sbsf->cmd = rx[0]; + switch (sbsf->cmd) { + case CMD_READ_ID: + sbsf->state = sbsf->cmd = SF_ID; + break; + case CMD_READ_ARRAY_FAST: + sbsf->pad_addr_bytes = 1; + case CMD_READ_ARRAY_SLOW: + case CMD_PAGE_PROGRAM: + state_addr: + sbsf->state = SF_ADDR; + break; + case CMD_WRITE_DISABLE: + debug(" write disabled\n"); + sbsf->status &= ~STAT_WEL; + break; + case CMD_READ_STATUS: + sbsf->state = SF_READ_STATUS; + break; + case CMD_WRITE_ENABLE: + debug(" write enabled\n"); + sbsf->status |= STAT_WEL; + break; + default: { + size_t i; + + /* handle erase commands first */ + for (i = 0; i < MAX_ERASE_CMDS; ++i) { + const struct sb_spi_flash_erase_commands *erase_cmd = + &sbsf->data->erase_cmds[i]; + + if (erase_cmd->cmd == 0x00) + continue; + if (sbsf->cmd != erase_cmd->cmd) + continue; + + sbsf->cmd_data = erase_cmd; + goto state_addr; + } + + debug(" cmd unknown: %#x\n", sbsf->cmd); + return 1; + } + } + + if (oldstate != sbsf->state) + debug(" cmd: transition to %s state\n", + sb_sf_state_name(sbsf->state)); + + return 0; +} + +static int sb_sf_xfer(void *priv, const u8 *rx, u8 *tx, + uint bytes) +{ + struct sb_spi_flash *sbsf = priv; + uint cnt, pos = 0; + + debug("sb_sf: state:%x(%s) bytes:%u\n", sbsf->state, + sb_sf_state_name(sbsf->state), bytes); + + if (sbsf->state == SF_CMD) { + /* Figure out the initial state */ + if (sb_sf_process_cmd(sbsf, rx, tx)) + return 1; + ++pos; + } + + /* Process the remaining data */ + while (pos < bytes) { + switch (sbsf->state) { + case SF_ID: { + u8 id; + + debug(" id: off:%u tx:", sbsf->off); + if (sbsf->off < IDCODE_LEN) + id = sbsf->data->idcode[sbsf->off]; + else + id = 0; + debug("%02x\n", id); + tx[pos++] = id; + ++sbsf->off; + break; + } + case SF_ADDR: + debug(" addr: bytes:%u rx:%02x ", + sbsf->addr_bytes, rx[pos]); + + if (sbsf->addr_bytes++ < SF_ADDR_LEN) + sbsf->off = (sbsf->off << 8) | rx[pos]; + debug("addr:%06x\n", sbsf->off); + + sb_spi_tristate(&tx[pos++], 1); + + /* See if we're done processing */ + if (sbsf->addr_bytes < SF_ADDR_LEN + sbsf->pad_addr_bytes) + break; + + /* Next state! */ + os_lseek(sbsf->fd, sbsf->off, OS_SEEK_SET); + switch (sbsf->cmd) { + case CMD_READ_ARRAY_FAST: + case CMD_READ_ARRAY_SLOW: + sbsf->state = SF_READ; + break; + case CMD_PAGE_PROGRAM: + sbsf->state = SF_WRITE; + break; + default: + /* assume erase state ... */ + sbsf->state = SF_ERASE; + goto case_SF_ERASE; + } + debug(" cmd: transition to %s state\n", + sb_sf_state_name(sbsf->state)); + break; + case SF_READ: + /* + * XXX: need to handle exotic behavior: + * - reading past end of device + */ + + cnt = bytes - pos; + debug(" tx: read(%u)\n", cnt); + pos += os_read(sbsf->fd, tx + pos, cnt); + break; + case SF_READ_STATUS: + debug(" read status: %#x\n", sbsf->status); + cnt = bytes - pos; + memset(tx + pos, sbsf->status, cnt); + pos += cnt; + break; + case SF_WRITE: + /* + * XXX: need to handle exotic behavior: + * - unaligned addresses + * - more than a page (256) worth of data + * - reading past end of device + */ + + if (!(sbsf->status & STAT_WEL)) { + puts("sb_sf: write enable not set before erase\n"); + goto done; + } + + cnt = bytes - pos; + debug(" rx: write(%u)\n", cnt); + sb_spi_tristate(&tx[pos], cnt); + pos += os_write(sbsf->fd, rx + pos, cnt); + sbsf->status &= ~STAT_WEL; + break; + case SF_ERASE: + case_SF_ERASE: { + const struct sb_spi_flash_erase_commands *erase_cmd = + sbsf->cmd_data; + + if (!(sbsf->status & STAT_WEL)) { + puts("sb_sf: write enable not set before erase\n"); + goto done; + } + + /* verify address is aligned */ + if (sbsf->off & ~(erase_cmd->size - 1)) { + debug(" sector erase: cmd:%#x needs align:%#x, but we got %#x\n", + erase_cmd->cmd, erase_cmd->size, sbsf->off); + sbsf->status &= ~STAT_WEL; + goto done; + } + + debug(" sector erase addr: %u\n", sbsf->off); + + cnt = bytes - pos; + sb_spi_tristate(&tx[pos], cnt); + pos += cnt; + + /* XXX: latch WIP in status, and delay before clearing it ? */ + os_write(sbsf->fd, sb_sf_0xff, erase_cmd->size); + sbsf->status &= ~STAT_WEL; + goto done; + } + default: + debug(" ??? no idea what to do ???\n"); + goto done; + } + } + + done: + return pos == bytes ? 0 : 1; +} + +static const struct sb_spi_emu_ops sb_sf_ops = { + .setup = sb_sf_setup, + .free = sb_sf_free, + .cs_activate = sb_sf_cs_activate, + .cs_deactivate = sb_sf_cs_deactivate, + .xfer = sb_sf_xfer, +}; + +static int sb_cmdline_cb_spi_sf(struct sandbox_state *state, const char *arg) +{ + unsigned long bus, cs; + const char *spec = sb_spi_parse_spec(arg, &bus, &cs); + + if (!spec) + return 1; + + state->spi[bus][cs][0] = &sb_sf_ops; + state->spi[bus][cs][1] = spec; + return 0; +} +SB_CMDLINE_OPT(spi_sf, 1, "connect a SPI flash: <bus>:<cs>:<id>:<file>");
This adds a SPI flash driver which simulates SPI flash clients. Currently supports the bare min that U-Boot requires: you can probe, read, erase, and write. Should be easy to extend to make it behave more exactly like a real SPI flash, but this is good enough to merge now. Signed-off-by: Mike Frysinger <vapier@gentoo.org> --- v3 - rearchitected on top of state/getopt support drivers/mtd/spi/Makefile | 1 + drivers/mtd/spi/sandbox.c | 402 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 403 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/spi/sandbox.c