mirror of
https://review.coreboot.org/flashrom.git
synced 2025-07-01 14:11:15 +02:00
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>
This commit is contained in:

committed by
Anastasia Klimchuk

parent
c3b89597fc
commit
81c21880a3
@ -107,6 +107,7 @@ struct io_mock {
|
||||
|
||||
/* 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);
|
||||
|
@ -48,6 +48,8 @@ mocks = [
|
||||
'-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 @@ mocks = [
|
||||
'-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',
|
||||
|
219
tests/spidriver.c
Normal file
219
tests/spidriver.c
Normal file
@ -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 */
|
@ -139,6 +139,34 @@ int __wrap___open64_2(const char *pathname, int flags, ...)
|
||||
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 @@ unsigned int __wrap_INL(unsigned short port)
|
||||
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 @@ int main(int argc, char *argv[])
|
||||
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);
|
||||
|
||||
|
@ -71,6 +71,7 @@ void realtek_mst_basic_lifecycle_test_success(void **state);
|
||||
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);
|
||||
|
@ -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 __wrap_open(const char *pathname, int flags, ...);
|
||||
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 @@ void __wrap_OUTW(unsigned short value, unsigned short port);
|
||||
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);
|
||||
|
Reference in New Issue
Block a user