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)
|
* ``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)
|
* ``dirtyjtag_spi`` (for SPI flash ROMs attached to DirtyJTAG-compatible devices)
|
||||||
* ``asm106x`` (for SPI flash ROMs attached to asm106x PCI SATA controllers)
|
* ``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
|
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.
|
**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.
|
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
|
EXAMPLES
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@ -1512,7 +1539,7 @@ REQUIREMENTS
|
|||||||
|
|
||||||
* needs TCP access to the network or userspace access to a serial port
|
* 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
|
* needs userspace access to a serial port
|
||||||
|
|
||||||
@ -1533,7 +1560,7 @@ REQUIREMENTS
|
|||||||
* have to be run as superuser/root
|
* have to be run as superuser/root
|
||||||
* need raw access permission
|
* 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
|
* 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
|
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
|
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.
|
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_satamv;
|
||||||
extern const struct programmer_entry programmer_satasii;
|
extern const struct programmer_entry programmer_satasii;
|
||||||
extern const struct programmer_entry programmer_serprog;
|
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_stlinkv3_spi;
|
||||||
extern const struct programmer_entry programmer_usbblaster_spi;
|
extern const struct programmer_entry programmer_usbblaster_spi;
|
||||||
extern const struct programmer_entry programmer_dirtyjtag_spi;
|
extern const struct programmer_entry programmer_dirtyjtag_spi;
|
||||||
|
@ -542,6 +542,13 @@ programmer = {
|
|||||||
'srcs' : files('serprog.c', 'serial.c', custom_baud_c),
|
'srcs' : files('serprog.c', 'serial.c', custom_baud_c),
|
||||||
'flags' : [ '-DCONFIG_SERPROG=1' ],
|
'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' : {
|
'stlinkv3_spi' : {
|
||||||
'deps' : [ libusb1 ],
|
'deps' : [ libusb1 ],
|
||||||
'groups' : [ group_usb, group_external ],
|
'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',
|
'gfxnvidia', 'internal', 'it8212', 'jlink_spi', 'linux_mtd', 'linux_spi', 'mediatek_i2c_spi',
|
||||||
'mstarddc_spi', 'ni845x_spi', 'nic3com', 'nicintel', 'nicintel_eeprom', 'nicintel_spi', 'nicnatsemi',
|
'mstarddc_spi', 'ni845x_spi', 'nic3com', 'nicintel', 'nicintel_eeprom', 'nicintel_spi', 'nicnatsemi',
|
||||||
'nicrealtek', 'ogp_spi', 'parade_lspcon', 'pickit2_spi', 'pony_spi', 'raiden_debug_spi',
|
'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')
|
], description: 'Active programmers')
|
||||||
option('llvm_cov', type : 'feature', value : 'disabled', description : 'build for llvm code coverage')
|
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')
|
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
|
#if CONFIG_DIRTYJTAG_SPI == 1
|
||||||
&programmer_dirtyjtag_spi,
|
&programmer_dirtyjtag_spi,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_SPIDRIVER == 1
|
||||||
|
&programmer_spidriver,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
const size_t programmer_table_size = ARRAY_SIZE(programmer_table);
|
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 \
|
gfxnvidia internal it8212 jlink_spi linux_mtd linux_spi parade_lspcon \
|
||||||
mediatek_i2c_spi mstarddc_spi nic3com nicintel nicintel_eeprom nicintel_spi \
|
mediatek_i2c_spi mstarddc_spi nic3com nicintel nicintel_eeprom nicintel_spi \
|
||||||
nicnatsemi nicrealtek ogp_spi pickit2_spi pony_spi raiden_debug_spi rayer_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
|
if [ "$(basename "${CC}")" = "ccc-analyzer" ] || [ -n "${COVERITY_OUTPUT}" ]; then
|
||||||
|
@ -107,6 +107,7 @@ struct io_mock {
|
|||||||
|
|
||||||
/* POSIX File I/O */
|
/* POSIX File I/O */
|
||||||
int (*iom_open)(void *state, const char *pathname, int flags, mode_t mode);
|
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_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_read)(void *state, int fd, void *buf, size_t sz);
|
||||||
int (*iom_write)(void *state, int fd, const 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=open',
|
||||||
'-Wl,--wrap=open64',
|
'-Wl,--wrap=open64',
|
||||||
'-Wl,--wrap=__open64_2',
|
'-Wl,--wrap=__open64_2',
|
||||||
|
'-Wl,--wrap=fcntl',
|
||||||
|
'-Wl,--wrap=fcntl64',
|
||||||
'-Wl,--wrap=ioctl',
|
'-Wl,--wrap=ioctl',
|
||||||
'-Wl,--wrap=read',
|
'-Wl,--wrap=read',
|
||||||
'-Wl,--wrap=write',
|
'-Wl,--wrap=write',
|
||||||
@ -82,6 +84,8 @@ mocks = [
|
|||||||
'-Wl,--wrap=INW',
|
'-Wl,--wrap=INW',
|
||||||
'-Wl,--wrap=OUTL',
|
'-Wl,--wrap=OUTL',
|
||||||
'-Wl,--wrap=INL',
|
'-Wl,--wrap=INL',
|
||||||
|
'-Wl,--wrap=tcgetattr',
|
||||||
|
'-Wl,--wrap=tcsetattr',
|
||||||
'-Wl,--wrap=usb_dev_get_by_vid_pid_number',
|
'-Wl,--wrap=usb_dev_get_by_vid_pid_number',
|
||||||
'-Wl,--wrap=libusb_init',
|
'-Wl,--wrap=libusb_init',
|
||||||
'-Wl,--wrap=libusb_set_debug',
|
'-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);
|
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, ...)
|
int __wrap_ioctl(int fd, unsigned long int request, ...)
|
||||||
{
|
{
|
||||||
LOG_ME;
|
LOG_ME;
|
||||||
@ -387,6 +415,18 @@ unsigned int __wrap_INL(unsigned short port)
|
|||||||
return 0;
|
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) {
|
static void *doing_nothing(void *vargp) {
|
||||||
return NULL;
|
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(realtek_mst_no_allow_brick_test_success),
|
||||||
cmocka_unit_test(ch341a_spi_basic_lifecycle_test_success),
|
cmocka_unit_test(ch341a_spi_basic_lifecycle_test_success),
|
||||||
cmocka_unit_test(ch341a_spi_probe_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);
|
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 realtek_mst_no_allow_brick_test_success(void **state);
|
||||||
void ch341a_spi_basic_lifecycle_test_success(void **state);
|
void ch341a_spi_basic_lifecycle_test_success(void **state);
|
||||||
void ch341a_spi_probe_lifecycle_test_success(void **state);
|
void ch341a_spi_probe_lifecycle_test_success(void **state);
|
||||||
|
void spidriver_probe_lifecycle_test_success(void **state);
|
||||||
|
|
||||||
/* layout.c */
|
/* layout.c */
|
||||||
void included_regions_dont_overlap_test_success(void **state);
|
void included_regions_dont_overlap_test_success(void **state);
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "flash.h"
|
#include "flash.h"
|
||||||
|
|
||||||
struct programmer_cfg; /* defined in programmer.h */
|
struct programmer_cfg; /* defined in programmer.h */
|
||||||
|
struct termios;
|
||||||
|
|
||||||
char *__wrap_strdup(const char *s);
|
char *__wrap_strdup(const char *s);
|
||||||
void __wrap_physunmap(void *virt_addr, size_t len);
|
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 __real_open(const char *pathname, int flags, ...);
|
||||||
int __wrap_open64(const char *pathname, int flags, ...);
|
int __wrap_open64(const char *pathname, int flags, ...);
|
||||||
int __wrap___open64_2(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_ioctl(int fd, unsigned long int request, ...);
|
||||||
int __wrap_write(int fd, const void *buf, size_t sz);
|
int __wrap_write(int fd, const void *buf, size_t sz);
|
||||||
int __wrap_read(int fd, 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);
|
unsigned short __wrap_INW(unsigned short port);
|
||||||
void __wrap_OUTL(unsigned int value, unsigned short port);
|
void __wrap_OUTL(unsigned int value, unsigned short port);
|
||||||
unsigned int __wrap_INL(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,
|
int __wrap_spi_send_command(const struct flashctx *flash,
|
||||||
unsigned int writecnt, unsigned int readcnt,
|
unsigned int writecnt, unsigned int readcnt,
|
||||||
const unsigned char *writearr, unsigned char *readarr);
|
const unsigned char *writearr, unsigned char *readarr);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user