Anastasia Klimchuk has submitted this change. ( https://review.coreboot.org/c/flashrom/+/86411?usp=email )
Change subject: spidriver: Add support for the Excamera Labs SPIDriver programmer ......................................................................
spidriver: Add support for the Excamera Labs SPIDriver programmer
This is a SPI hardware interface with a display (https://spidriver.com/), connected as an FT230X USB serial device at a fixed baud rate of 460800.
Firmware: https://github.com/jamesbowman/spidriver Protocol: https://github.com/jamesbowman/spidriver/blob/master/protocol.md
Most of the implementation is copied from the Bus Pirate programmer.
Tested with a SPIDriver v2 by reading FM25Q128A flash memory on Linux.
Change-Id: I07b23c1146d4ad3606b54a1e8dc8030cf4ebf57b Signed-off-by: Simon Arlott flashrom@octiron.net Reviewed-on: https://review.coreboot.org/c/flashrom/+/86411 Tested-by: build bot (Jenkins) no-reply@coreboot.org Reviewed-by: Peter Marheine pmarheine@chromium.org Reviewed-by: Anastasia Klimchuk aklm@chromium.org --- M doc/classic_cli_manpage.rst M doc/release_notes/devel.rst M include/programmer.h M meson.build M meson_options.txt M programmer_table.c A spidriver.c A subprojects/.gitignore M test_build.sh M tests/io_mock.h M tests/meson.build A tests/spidriver.c M tests/tests.c M tests/tests.h M tests/wraps.h 15 files changed, 659 insertions(+), 4 deletions(-)
Approvals: Peter Marheine: Looks good to me, approved build bot (Jenkins): Verified Anastasia Klimchuk: Looks good to me, approved
diff --git a/doc/classic_cli_manpage.rst b/doc/classic_cli_manpage.rst index cf80556..043bc15 100644 --- a/doc/classic_cli_manpage.rst +++ b/doc/classic_cli_manpage.rst @@ -356,6 +356,7 @@ * ``mediatek_i2c_spi`` (for SPI flash ROMs attached to some Mediatek display devices accessible over I2C) * ``dirtyjtag_spi`` (for SPI flash ROMs attached to DirtyJTAG-compatible devices) * ``asm106x`` (for SPI flash ROMs attached to asm106x PCI SATA controllers) + * ``spidriver`` (for SPI flash ROMs attached to an Excamera Labs SPIDriver)
Some programmers have optional or mandatory parameters which are described in detail in the **PROGRAMMER-SPECIFIC INFORMATION** section. Support for some programmers can be disabled at compile time. @@ -1434,6 +1435,32 @@ mechanism in the driver to positively identify that a given I2C bus is actually connected to a supported device.
+spidriver programmer +^^^^^^^^^^^^^^^^^^^^ + +This is a SPI hardware interface with a display (https://spidriver.com/), +connected as an FT230X USB serial device at a fixed baud rate of 460800. + +Firmware: https://github.com/jamesbowman/spidriver + +A required ``dev`` parameter specifies the Excamera Labs SPIDriver device node +and an optional ``mode`` parameter specifies the mode of the SPI bus. The +parameter delimiter is a comma. Syntax is:: + + flashrom -p spidriver:dev=/dev/device,mode=0 + +where ``mode`` can be ``0``, ``1``, ``2`` or ``3``. The default is mode 0. +Setting the SPI mode requires version 2 of the device firmware. + +An optional A and/or B parameter specifies the state of the SPIDriver A and B pins. +This may be used to drive the A or B pins high or low before a transfer. +Syntax is:: + + flashrom -p spidriver:a=state,b=state + +where ``state`` can be ``high`` or ``low``. The default ``state`` is ``high``. + + EXAMPLES --------
@@ -1512,7 +1539,7 @@
* needs TCP access to the network or userspace access to a serial port
-* buspirate_spi +* buspirate_spi, spidriver
* needs userspace access to a serial port
@@ -1533,7 +1560,7 @@ * have to be run as superuser/root * need raw access permission
-* serprog, buspirate_spi, dediprog, usbblaster_spi, ft2232_spi, pickit2_spi, ch341a_spi, digilent_spi, dirtyjtag_spi +* serprog, buspirate_spi, dediprog, usbblaster_spi, ft2232_spi, pickit2_spi, ch341a_spi, digilent_spi, dirtyjtag_spi, spidriver
* can be run as normal user on most operating systems if appropriate device permissions are set
diff --git a/doc/release_notes/devel.rst b/doc/release_notes/devel.rst index 4defe52..2a2870d 100644 --- a/doc/release_notes/devel.rst +++ b/doc/release_notes/devel.rst @@ -64,3 +64,8 @@ This new API fixes limitations with the old one where most users would need to define their own global state to track progress, and it was impossible to fix that issue while maintaining binary compatibility without adding a new API. + +Programmer updates +------------------ + +* spidriver: Add support for the Excamera Labs SPIDriver diff --git a/include/programmer.h b/include/programmer.h index e9c132e..b2c9683 100644 --- a/include/programmer.h +++ b/include/programmer.h @@ -97,6 +97,7 @@ extern const struct programmer_entry programmer_satamv; extern const struct programmer_entry programmer_satasii; extern const struct programmer_entry programmer_serprog; +extern const struct programmer_entry programmer_spidriver; extern const struct programmer_entry programmer_stlinkv3_spi; extern const struct programmer_entry programmer_usbblaster_spi; extern const struct programmer_entry programmer_dirtyjtag_spi; diff --git a/meson.build b/meson.build index 6c8d3d3..ba17fef 100644 --- a/meson.build +++ b/meson.build @@ -542,6 +542,13 @@ 'srcs' : files('serprog.c', 'serial.c', custom_baud_c), 'flags' : [ '-DCONFIG_SERPROG=1' ], }, + 'spidriver' : { + 'systems' : systems_serial, + 'groups' : [ group_serial, group_external ], + 'srcs' : files('spidriver.c', 'serial.c', custom_baud_c), + 'test_srcs' : files('tests/spidriver.c'), + 'flags' : [ '-DCONFIG_SPIDRIVER=1' ], + }, 'stlinkv3_spi' : { 'deps' : [ libusb1 ], 'groups' : [ group_usb, group_external ], diff --git a/meson_options.txt b/meson_options.txt index 318a419..43b5117 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -14,7 +14,8 @@ 'gfxnvidia', 'internal', 'it8212', 'jlink_spi', 'linux_mtd', 'linux_spi', 'mediatek_i2c_spi', 'mstarddc_spi', 'ni845x_spi', 'nic3com', 'nicintel', 'nicintel_eeprom', 'nicintel_spi', 'nicnatsemi', 'nicrealtek', 'ogp_spi', 'parade_lspcon', 'pickit2_spi', 'pony_spi', 'raiden_debug_spi', - 'rayer_spi', 'realtek_mst_i2c_spi', 'satamv', 'satasii', 'serprog', 'stlinkv3_spi', 'usbblaster_spi', + 'rayer_spi', 'realtek_mst_i2c_spi', 'satamv', 'satasii', 'serprog', 'spidriver', 'stlinkv3_spi', + 'usbblaster_spi', ], description: 'Active programmers') option('llvm_cov', type : 'feature', value : 'disabled', description : 'build for llvm code coverage') option('man-pages', type : 'feature', value : 'auto', description : 'build the man-page for classic_cli') diff --git a/programmer_table.c b/programmer_table.c index 79acd77..6d28dd1 100644 --- a/programmer_table.c +++ b/programmer_table.c @@ -179,6 +179,10 @@ #if CONFIG_DIRTYJTAG_SPI == 1 &programmer_dirtyjtag_spi, #endif + +#if CONFIG_SPIDRIVER == 1 + &programmer_spidriver, +#endif };
const size_t programmer_table_size = ARRAY_SIZE(programmer_table); diff --git a/spidriver.c b/spidriver.c new file mode 100644 index 0000000..8999468 --- /dev/null +++ b/spidriver.c @@ -0,0 +1,336 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2009, 2010, 2011, 2012 Carl-Daniel Hailfinger + * Copyright 2025 Simon Arlott + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Website: https://spidriver.com/ + * Firmware: https://github.com/jamesbowman/spidriver + * Protocol: https://github.com/jamesbowman/spidriver/blob/master/protocol.md + */ + +#include <assert.h> +#include <stdio.h> +#include <strings.h> +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> +#include "flash.h" +#include "programmer.h" +#include "spi.h" + +static int spidriver_serialport_setup(char *dev) +{ + /* 460800bps, 8 databits, no parity, 1 stopbit */ + sp_fd = sp_openserport(dev, 460800); + if (sp_fd == SER_INV_FD) + return 1; + return 0; +} + +static int spidriver_sendrecv(unsigned char *buf, unsigned int writecnt, + unsigned int readcnt) +{ + unsigned int i; + int ret = 0; + + msg_pspew("%s: write %i, read %i ", __func__, writecnt, readcnt); + if (!writecnt && !readcnt) { + msg_perr("Zero length command!\n"); + return 1; + } + if (writecnt) + msg_pspew("Sending"); + for (i = 0; i < writecnt; i++) + msg_pspew(" 0x%02x", buf[i]); + if (writecnt) + ret = serialport_write(buf, writecnt); + if (ret) + return ret; + if (readcnt) + ret = serialport_read(buf, readcnt); + if (ret) + return ret; + if (readcnt) + msg_pspew(", receiving"); + for (i = 0; i < readcnt; i++) + msg_pspew(" 0x%02x", buf[i]); + msg_pspew("\n"); + return 0; +} + +/* Sending multiple commands too quickly usually fails, so use echo to wait for + * each command to complete before sending the next one. + */ +static int spidriver_send_command(const struct flashctx *flash, unsigned int writecnt, + unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr) +{ + int ret = 0; + + { + unsigned char buf[1 + 2]; + unsigned int i = 0; + + /* Assert CS# */ + buf[i++] = 's'; + + /* Echo */ + buf[i++] = 'e'; + buf[i++] = 'S'; + + if ((ret = spidriver_sendrecv(buf, i, 1))) { + msg_perr("Communication error after writing %u reading %u\n", writecnt, readcnt); + return SPI_GENERIC_ERROR; + } + + if (buf[0] != 'S') { + msg_perr("Communication error, unexpected select echo response %u\n", buf[0]); + return SPI_GENERIC_ERROR; + } + } + + while (writecnt > 0) { + unsigned char buf[1 + 64 + 2]; + unsigned int i = 0; + unsigned int len = writecnt > 64 ? 64 : writecnt; + + /* Write */ + i = 0; + buf[i++] = 0xc0 - 1 + len; + memcpy(&buf[i], writearr, len); + i += len; + writearr += len; + writecnt -= len; + + /* Echo */ + buf[i++] = 'e'; + buf[i++] = 'W'; + + if ((ret = spidriver_sendrecv(buf, i, 1))) { + msg_perr("Communication error writing %u\n", len); + return SPI_GENERIC_ERROR; + } + + if (buf[0] != 'W') { + msg_perr("Communication error, unexpected write echo response %u\n", buf[0]); + return SPI_GENERIC_ERROR; + } + } + + while (readcnt > 0) { + unsigned char buf[1 + 64]; + unsigned int i = 0; + unsigned int len = readcnt > 64 ? 64 : readcnt; + + /* Read and write */ + i = 0; + buf[i++] = 0x80 - 1 + len; + memset(&buf[i], 0, len); + i += len; + + if ((ret = spidriver_sendrecv(buf, i, len))) { + msg_perr("Communication error reading %u\n", len); + return SPI_GENERIC_ERROR; + } + + memcpy(readarr, buf, len); + readarr += len; + readcnt -= len; + } + + { + unsigned char buf[1 + 2]; + unsigned int i = 0; + + /* De-assert CS# */ + buf[i++] = 'u'; + + /* Echo */ + buf[i++] = 'e'; + buf[i++] = 'U'; + + if ((ret = spidriver_sendrecv(buf, i, 1))) { + msg_perr("Communication error after writing %u reading %u\n", writecnt, readcnt); + return SPI_GENERIC_ERROR; + } + + if (buf[0] != 'U') { + msg_perr("Communication error, unexpected unselect echo response %u\n", buf[0]); + return SPI_GENERIC_ERROR; + } + } + + return ret; +} + +static struct spi_master spi_master_spidriver = { + .features = SPI_MASTER_4BA, + .max_data_read = MAX_DATA_READ_UNLIMITED, + .max_data_write = MAX_DATA_WRITE_UNLIMITED, + .command = spidriver_send_command, + .read = default_spi_read, + .write_256 = default_spi_write_256, + .shutdown = serialport_shutdown, +}; + +static int spidriver_spi_init(const struct programmer_cfg *cfg) +{ + char *tmp; + char *dev; + unsigned long fw_version = 0; + int ret = 0; + long mode = 0; + bool a = true; + bool b = true; + size_t i; + + dev = extract_programmer_param_str(cfg, "dev"); + if (dev && !strlen(dev)) { + free(dev); + dev = NULL; + } + if (!dev) { + msg_perr("No serial device given. Use flashrom -p spidriver:dev=/dev/ttyUSB0\n"); + return 1; + } + + tmp = extract_programmer_param_str(cfg, "mode"); + if (tmp) { + mode = strtol(tmp, NULL, 10); + if (mode < 0 || mode > 3) { + msg_perr("Error: Invalid SPI mode %ld\nValid values are 0, 1, 2 or 3\n", mode); + return 1; + } + } + free(tmp); + + tmp = extract_programmer_param_str(cfg, "a"); + if (tmp) { + if (strcasecmp("high", tmp) == 0) { + ; /* Default */ + } else if (strcasecmp("low", tmp) == 0) { + a = false; + } else { + msg_perr("Error: Invalid A state %s\nValid values are "high" or "low"\n", tmp); + return 1; + } + } + free(tmp); + + tmp = extract_programmer_param_str(cfg, "b"); + if (tmp) { + if (strcasecmp("high", tmp) == 0) { + ; /* Default */ + } else if (strcasecmp("low", tmp) == 0) { + b = false; + } else { + msg_perr("Error: Invalid B state %s\nValid values are "high" or "low"\n", tmp); + return 1; + } + } + free(tmp); + + ret = spidriver_serialport_setup(dev); + free(dev); + if (ret) + return ret; + + /* Largest message is: 1 byte command (tx), 80 byte response plus 1 for + * string null termination (rx). + */ + unsigned char buf[80 + 1]; + + /* Flush any in-progress transfer with 64 zero bytes. */ + i = 64; + memset(buf, 0, i); + if ((ret = spidriver_sendrecv(buf, i, 0))) + goto init_err_cleanup_exit; + + default_delay(1400); /* Enough time to receive 64 bytes at 460800bps */ + sp_flush_incoming(); + + memset(buf, 0, 81); + i = 0; + buf[i++] = '?'; + if ((ret = spidriver_sendrecv(buf, i, 80))) + goto init_err_cleanup_exit; + + /* [spidriver2 AAAAAAAA 000000002 5.190 000 21.9 1 1 1 ffff 0 ] */ + /* <version> <serial> <uptime> ^^^^^ ^^^ ^^^^ ^ ^ ^ ^^^^ ^ */ + /* (seconds) | | | | | | | | */ + /* | | | | | | | ` SPI mode (0-3) */ + /* | | | | | | ` CCITT CRC */ + /* | | | | | ` Chip select */ + /* | | | | ` "B" signal */ + /* | | | ` "A" signal */ + /* | | ` Temperature */ + /* | ` Current */ + /* ` Voltage */ + if (buf[0] != '[' || buf[79] != ']' || !strcmp((char*)&buf[1], "spidriver")) { + msg_perr("Invalid status response: %s\n", buf); + ret = 1; + goto init_err_cleanup_exit; + } + + msg_pdbg("Status: %s\n", buf); + msg_pdbg("Detected SPIDriver hardware "); + + if (!strchr("0123456789", buf[10])) { + msg_pdbg("(unknown version number format)"); + } else { + fw_version = strtoul((char*)&buf[10], &tmp, 10); + msg_pdbg("v%lu", fw_version); + } + msg_pdbg("\n"); + + /* De-assert CS#, configure A and B signals */ + i = 0; + buf[i++] = 'u'; + buf[i++] = 'a'; + buf[i++] = a ? 1 : 0; + buf[i++] = 'b'; + buf[i++] = b ? 1 : 0; + msg_pdbg("Raising CS#\n"); + msg_pdbg("Driving A %s\n", a ? "high" : "low"); + msg_pdbg("Driving B %s\n", b ? "high" : "low"); + if ((ret = spidriver_sendrecv(buf, i, 0))) + goto init_err_cleanup_exit; + + if (fw_version >= 2) { + /* Set SPI mode */ + i = 0; + buf[i++] = 'm'; + buf[i++] = mode & 0xFF; + if ((ret = spidriver_sendrecv(buf, i, 0))) + goto init_err_cleanup_exit; + } else if (mode != 0) { + msg_perr("Error: SPI mode %ld not supported by version %lu hardware\n", mode, fw_version); + ret = 1; + goto init_err_cleanup_exit; + } + + return register_spi_master(&spi_master_spidriver, NULL); + +init_err_cleanup_exit: + serialport_shutdown(NULL); + return ret; +} + +const struct programmer_entry programmer_spidriver = { + .name = "spidriver", + .type = OTHER, + .devs.note = "SPIDriver\n", + .init = spidriver_spi_init, +}; diff --git a/subprojects/.gitignore b/subprojects/.gitignore new file mode 100644 index 0000000..b5273c7 --- /dev/null +++ b/subprojects/.gitignore @@ -0,0 +1,2 @@ +/packagecache/ +/cmocka-*/ diff --git a/test_build.sh b/test_build.sh index 1470e08..3b6aa98 100755 --- a/test_build.sh +++ b/test_build.sh @@ -11,7 +11,8 @@ gfxnvidia internal it8212 jlink_spi linux_mtd linux_spi parade_lspcon \ mediatek_i2c_spi mstarddc_spi nic3com nicintel nicintel_eeprom nicintel_spi \ nicnatsemi nicrealtek ogp_spi pickit2_spi pony_spi raiden_debug_spi rayer_spi \ - realtek_mst_i2c_spi satamv satasii serprog stlinkv3_spi usbblaster_spi asm106x" + realtek_mst_i2c_spi satamv satasii serprog spidriver stlinkv3_spi usbblaster_spi\ + asm106x"
if [ "$(basename "${CC}")" = "ccc-analyzer" ] || [ -n "${COVERITY_OUTPUT}" ]; then diff --git a/tests/io_mock.h b/tests/io_mock.h index 04b0ef5..d250f38 100644 --- a/tests/io_mock.h +++ b/tests/io_mock.h @@ -107,6 +107,7 @@
/* POSIX File I/O */ int (*iom_open)(void *state, const char *pathname, int flags, mode_t mode); + int (*iom_fcntl)(void *state, int fd, unsigned long cmd, va_list args); int (*iom_ioctl)(void *state, int fd, unsigned long request, va_list args); int (*iom_read)(void *state, int fd, void *buf, size_t sz); int (*iom_write)(void *state, int fd, const void *buf, size_t sz); diff --git a/tests/meson.build b/tests/meson.build index e788b80..66c9e10 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -48,6 +48,8 @@ '-Wl,--wrap=open', '-Wl,--wrap=open64', '-Wl,--wrap=__open64_2', + '-Wl,--wrap=fcntl', + '-Wl,--wrap=fcntl64', '-Wl,--wrap=ioctl', '-Wl,--wrap=read', '-Wl,--wrap=write', @@ -82,6 +84,8 @@ '-Wl,--wrap=INW', '-Wl,--wrap=OUTL', '-Wl,--wrap=INL', + '-Wl,--wrap=tcgetattr', + '-Wl,--wrap=tcsetattr', '-Wl,--wrap=usb_dev_get_by_vid_pid_number', '-Wl,--wrap=libusb_init', '-Wl,--wrap=libusb_set_debug', diff --git a/tests/spidriver.c b/tests/spidriver.c new file mode 100644 index 0000000..597895a --- /dev/null +++ b/tests/spidriver.c @@ -0,0 +1,219 @@ +/* + * This file is part of the flashrom project. + * + * Copyright 2025 Simon Arlott + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "lifecycle.h" + +#if CONFIG_SPIDRIVER == 1 +#define SPIDRIVER_TEST_DEBUG 0 + +struct spidriver_state { + // most recent command + unsigned char state; + + unsigned char input[256]; // for read() responses + size_t in_len; // available data to read + size_t in_pos; // remaining SPI read count + unsigned char output[256]; // incoming SPI writes + size_t out_pos; // SPI write position in buffer + size_t out_len; // remaining SPI write count + + // chip select + bool cs; + size_t cs_count; + + // probe detected + bool probe; + size_t cs_probe; +}; + +static int spidriver_read(void *state, int fd, void *buf, size_t sz) +{ + struct spidriver_state *ts = state; + + assert_int_equal(fd, MOCK_FD); + if (SPIDRIVER_TEST_DEBUG) + printf("read: %zu\n", sz); + + sz = min(sz, ts->in_len); + + if (sz > 0) { + memcpy(buf, ts->input, sz); + memmove(ts->input, &ts->input[sz], sizeof(ts->input) - sz); + ts->in_len = 0; + return sz; + } else { + return -1; + } +} + +static int spidriver_write(void *state, int fd, const void *buf, size_t sz) +{ + struct spidriver_state *ts = state; + + assert_int_equal(fd, MOCK_FD); + if (SPIDRIVER_TEST_DEBUG) + printf("write: %zu\n", sz); + + for (size_t i = 0; i < sz; i++) { + unsigned char c = ((const char *)buf)[i]; + bool first = ts->state == 0; + + if (first) + ts->state = c; + + if (SPIDRIVER_TEST_DEBUG) + printf("c=%02X first=%d state=%02X\n", c, first, ts->state); + + switch (ts->state) { + case '?': + assert_int_equal(ts->in_len, 0); + snprintf((char *)ts->input, sizeof(ts->input), + "[spidriver2 AAAAAAAA 000000002 5.190 000" + " 21.9 1 1 1 ffff 0 ]"); + ts->in_len = 80; + ts->state = 0; + break; + + case 0: + break; + + case 'm': + case 'a': + case 'b': + if (!first) + ts->state = 0; + break; + + case 's': + if (SPIDRIVER_TEST_DEBUG) + printf("select\n"); + ts->cs = true; + ts->cs_count++; + ts->state = 0; + break; + + case 'u': + if (SPIDRIVER_TEST_DEBUG) + printf("unselect\n"); + ts->cs = false; + ts->state = 0; + break; + + case 'e': + if (!first) { + if (SPIDRIVER_TEST_DEBUG) + printf("echo %02X\n", c); + + assert_int_equal(ts->in_len, 0); + snprintf((char *)ts->input, sizeof(ts->input), "%c", c); + ts->in_len = 1; + ts->state = 0; + } + break; + + case 0x80 ... 0xbf: + if (first) { + ts->in_pos = c - 0x80 + 1; + if (SPIDRIVER_TEST_DEBUG) + printf("SPI read begin %zu\n", ts->in_pos); + + if (ts->probe) { + if (SPIDRIVER_TEST_DEBUG) + printf("probe response\n"); + + assert_int_equal(ts->in_pos, 3); + assert_true(ts->cs); + // Must not have lowered CS after write + assert_int_equal(ts->cs_count, ts->cs_probe); + + assert_int_equal(ts->in_len, 0); + ts->input[0] = 0xEF; /* WINBOND_NEX_ID */ + ts->input[1] = 0x40; /* WINBOND_NEX_W25Q128_V left byte */ + ts->input[2] = 0x18; /* WINBOND_NEX_W25Q128_V right byte */ + } else { + assert_int_equal(ts->in_len, 0); + memset(ts->input, 0, ts->in_pos); + } + continue; + } else if (ts->in_pos > 0) { + assert_int_equal(c, 0); + ts->in_pos--; + ts->in_len++; + } + + if (ts->in_pos == 0) { + if (SPIDRIVER_TEST_DEBUG) + printf("SPI read finished\n"); + ts->probe = false; + ts->state = 0; + } + break; + + case 0xc0 ... 0xff: + if (first) { + assert_int_equal(ts->out_len, 0); + ts->out_len = c - 0xc0 + 1; + ts->out_pos = 0; + if (SPIDRIVER_TEST_DEBUG) + printf("SPI write begin %zu\n", ts->out_len); + continue; + } else if (ts->out_len > 0) { + ts->output[ts->out_pos++] = c; + ts->out_len--; + } + + if (ts->out_len == 0) { + if (SPIDRIVER_TEST_DEBUG) + printf("SPI write finished\n"); + assert_true(ts->cs); + if (ts->out_pos == 1 && ts->output[0] == JEDEC_RDID) { + if (SPIDRIVER_TEST_DEBUG) + printf("probe detected\n"); + ts->probe = true; + ts->cs_probe = ts->cs_count; + } + ts->state = 0; + } + break; + + default: + fail_msg("Unsupported command 0x%02X", ts->state); + break; + } + } + + return sz; +} + +void spidriver_probe_lifecycle_test_success(void **state) +{ + struct spidriver_state ts = {}; + struct io_mock_fallback_open_state spidriver_fallback_open_state = { + .noc = 0, + .paths = { "/dev/null", NULL }, + .flags = { O_RDWR | O_NOCTTY | O_NDELAY }, + }; + const struct io_mock spidriver_io = { + .state = &ts, + .iom_read = spidriver_read, + .iom_write = spidriver_write, + .fallback_open_state = &spidriver_fallback_open_state, + }; + + run_probe_lifecycle(state, &spidriver_io, &programmer_spidriver, "dev=/dev/null", "W25Q128.V"); +} +#else + SKIP_TEST(spidriver_probe_lifecycle_test_success) +#endif /* CONFIG_SPIDRIVER */ diff --git a/tests/tests.c b/tests/tests.c index 72b4868..bf5903d 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -139,6 +139,34 @@ return mock_open(pathname, flags, (mode_t) mode); }
+int __wrap_fcntl(int fd, int cmd, ...) +{ + LOG_ME; + if (get_io() && get_io()->iom_ioctl) { + va_list args; + int out; + va_start(args, cmd); + out = get_io()->iom_fcntl(get_io()->state, fd, cmd, args); + va_end(args); + return out; + } + return 0; +} + +int __wrap_fcntl64(int fd, int cmd, ...) +{ + LOG_ME; + if (get_io() && get_io()->iom_ioctl) { + va_list args; + int out; + va_start(args, cmd); + out = get_io()->iom_fcntl(get_io()->state, fd, cmd, args); + va_end(args); + return out; + } + return 0; +} + int __wrap_ioctl(int fd, unsigned long int request, ...) { LOG_ME; @@ -387,6 +415,18 @@ return 0; }
+int __wrap_tcgetattr(int fd, struct termios *termios_p) +{ + LOG_ME; + return 0; +} + +int __wrap_tcsetattr(int fd, int optional_actions, const struct termios *termios_p) +{ + LOG_ME; + return 0; +} + static void *doing_nothing(void *vargp) { return NULL; } @@ -480,6 +520,7 @@ cmocka_unit_test(realtek_mst_no_allow_brick_test_success), cmocka_unit_test(ch341a_spi_basic_lifecycle_test_success), cmocka_unit_test(ch341a_spi_probe_lifecycle_test_success), + cmocka_unit_test(spidriver_probe_lifecycle_test_success), }; ret |= cmocka_run_group_tests_name("lifecycle.c tests", lifecycle_tests, NULL, NULL);
diff --git a/tests/tests.h b/tests/tests.h index 85c4f52..7cf3271 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -71,6 +71,7 @@ void realtek_mst_no_allow_brick_test_success(void **state); void ch341a_spi_basic_lifecycle_test_success(void **state); void ch341a_spi_probe_lifecycle_test_success(void **state); +void spidriver_probe_lifecycle_test_success(void **state);
/* layout.c */ void included_regions_dont_overlap_test_success(void **state); diff --git a/tests/wraps.h b/tests/wraps.h index 089d992..bb4801a 100644 --- a/tests/wraps.h +++ b/tests/wraps.h @@ -20,6 +20,7 @@ #include "flash.h"
struct programmer_cfg; /* defined in programmer.h */ +struct termios;
char *__wrap_strdup(const char *s); void __wrap_physunmap(void *virt_addr, size_t len); @@ -32,6 +33,8 @@ int __real_open(const char *pathname, int flags, ...); int __wrap_open64(const char *pathname, int flags, ...); int __wrap___open64_2(const char *pathname, int flags, ...); +int __wrap_fcntl(int fd, int cmd, ...); +int __wrap_fcntl64(int fd, int cmd, ...); int __wrap_ioctl(int fd, unsigned long int request, ...); int __wrap_write(int fd, const void *buf, size_t sz); int __wrap_read(int fd, void *buf, size_t sz); @@ -72,6 +75,8 @@ unsigned short __wrap_INW(unsigned short port); void __wrap_OUTL(unsigned int value, unsigned short port); unsigned int __wrap_INL(unsigned short port); +int __wrap_tcgetattr(int fd, struct termios *termios_p); +int __wrap_tcsetattr(int fd, int optional_actions, const struct termios *termios_p); int __wrap_spi_send_command(const struct flashctx *flash, unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr);