mirror of
https://review.coreboot.org/flashrom.git
synced 2025-04-26 22:52:34 +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:
parent
c3b89597fc
commit
81c21880a3
@ -356,6 +356,7 @@ All operations involving any chip access (probe/read/write/...) require the ``-p
|
||||
* ``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 @@ and must explicitly be set to ``yes`` in order for the programmer to operate. Th
|
||||
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 @@ REQUIREMENTS
|
||||
|
||||
* 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 @@ REQUIREMENTS
|
||||
* 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
|
||||
|
||||
|
@ -64,3 +64,8 @@ should be used from now on.
|
||||
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
|
||||
|
@ -97,6 +97,7 @@ extern const struct programmer_entry programmer_realtek_mst_i2c_spi;
|
||||
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;
|
||||
|
@ -542,6 +542,13 @@ programmer = {
|
||||
'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 ],
|
||||
|
@ -14,7 +14,8 @@ option('programmer', type : 'array', value : ['auto'], choices : [
|
||||
'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')
|
||||
|
@ -179,6 +179,10 @@ const struct programmer_entry *const programmer_table[] = {
|
||||
#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);
|
||||
|
336
spidriver.c
Normal file
336
spidriver.c
Normal file
@ -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,
|
||||
};
|
2
subprojects/.gitignore
vendored
Normal file
2
subprojects/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/packagecache/
|
||||
/cmocka-*/
|
@ -11,7 +11,8 @@ meson_programmer_opts="all auto group_ftdi group_i2c group_jlink group_pci group
|
||||
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
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user