1
0
mirror of https://review.coreboot.org/flashrom.git synced 2025-10-27 19:32:11 +01:00

nv_sma_spi: Add Nvidia SMA Programmer

Add initial support for System Management Agent (SMA) programmer.
SMA is a SOC which is working as a side band management on Nvidia
server board. One of its functionality is to flash firmware to other
components.

Test:
1. Build flashrom with this change.
2. Run operation: erase, write, read
3. All operations completed with expected performance.

NV_SMA_SPI has been tested with the following SPI flash models:

w25r128jw
w25r64jv
w25q16v

Change-Id: I6b2522788db3dcee2b30faff29f605cede8c0eaf
Co-Developed-by: Gilbert Chen <gilbertc@nvidia.com>
Co-Developed-by: Willie Thai <wthai@nvidia.com>
Signed-off-by: Willie Thai <wthai@nvidia.com>
Signed-off-by: Gilbert Chen <gilbertc@nvidia.com>
Reviewed-on: https://review.coreboot.org/c/flashrom/+/88816
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:
Willie Thai
2025-08-18 12:43:40 +00:00
committed by Anastasia Klimchuk
parent c54d068418
commit db43ab2989
17 changed files with 943 additions and 4 deletions

View File

@@ -181,6 +181,12 @@ M: Miklos Marton <martonmiklosqdev@gmail.com>
S: Maintained S: Maintained
F: ni845x_spi.c F: ni845x_spi.c
NVIDIA SMA SPI
M: Willie Thai <wthai@nvidia.com>
M: Gilbert Chen <gilbertc@nvidia.com>
S: Maintained
F: nv_sma_spi.c
RAIDEN DEBUG SPI RAIDEN DEBUG SPI
M: Nikolai Artemiev <nartemiev@google.com> M: Nikolai Artemiev <nartemiev@google.com>
S: Maintained S: Maintained

View File

@@ -357,6 +357,7 @@ All operations involving any chip access (probe/read/write/...) require the ``-p
* ``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) * ``spidriver`` (for SPI flash ROMs attached to an Excamera Labs SPIDriver)
* ``nv_sma_spi`` (for SPI flash ROMs attached to an Nvidia System Management Agent)
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.
@@ -1460,6 +1461,24 @@ Syntax is::
where ``state`` can be ``high`` or ``low``. The default ``state`` is ``high``. where ``state`` can be ``high`` or ``low``. The default ``state`` is ``high``.
nv_sma_spi programmer
^^^^^^^^^^^^^^^^^^^^^
The Optional ``cs`` parameter can be used to switch which chip select number is used. This allows connecting multiple
chips at once and selecting which one to flash.::
flashrom -p nv_sma_spi:cs=0
The optional ``bus`` and ``devnum`` parameters indicate which Nvidia System Management Agent is selected when multiple
SMAs are present. Both parameters must be specified simultaneously.::
flashrom -p nv_sma_spi:bus=0,devnum=1
The optional ``spispeed`` parameter sets the target frequency in Hz. The Nvidia System Management Agent selects the closest
supported frequency based on its capabilities. If the spispeed is not provided, the device default frequncy is used. An example
to set target frequency at 15MHz.::
flashrom -p nv_sma_spi:spispeed=15000000
EXAMPLES EXAMPLES
-------- --------
@@ -1547,7 +1566,7 @@ REQUIREMENTS
* need access to the respective USB device via libusb API version 1.0 * need access to the respective USB device via libusb API version 1.0
* ch341a_spi, dediprog * ch341a_spi, dediprog, nv_sma_spi
* need access to the respective USB device via libusb API version 1.0 * need access to the respective USB device via libusb API version 1.0

View File

@@ -37,3 +37,8 @@ range, especially if using different names for those regions.
If you are flashing multiple regions or ones that partially overlap with If you are flashing multiple regions or ones that partially overlap with
read-only parts then that could result in flashrom failing in the read-only parts then that could result in flashrom failing in the
middle, leaving you in unknown state. middle, leaving you in unknown state.
New programmers
===============
* Nvidia System Management Agent

View File

@@ -79,6 +79,7 @@ extern const struct programmer_entry programmer_nicintel_eeprom;
extern const struct programmer_entry programmer_nicintel_spi; extern const struct programmer_entry programmer_nicintel_spi;
extern const struct programmer_entry programmer_nicnatsemi; extern const struct programmer_entry programmer_nicnatsemi;
extern const struct programmer_entry programmer_nicrealtek; extern const struct programmer_entry programmer_nicrealtek;
extern const struct programmer_entry programmer_nv_sma_spi;
extern const struct programmer_entry programmer_ogp_spi; extern const struct programmer_entry programmer_ogp_spi;
extern const struct programmer_entry programmer_pickit2_spi; extern const struct programmer_entry programmer_pickit2_spi;
extern const struct programmer_entry programmer_pony_spi; extern const struct programmer_entry programmer_pony_spi;

View File

@@ -469,6 +469,13 @@ programmer = {
'test_srcs' : files('tests/nicrealtek.c'), 'test_srcs' : files('tests/nicrealtek.c'),
'flags' : [ '-DCONFIG_NICREALTEK=1' ], 'flags' : [ '-DCONFIG_NICREALTEK=1' ],
}, },
'nv_sma_spi' : {
'deps' : [ libusb1 ],
'groups' : [ group_usb, group_external ],
'srcs' : files('nv_sma_spi.c', 'usb_device.c'),
'test_srcs' : files('tests/nv_sma_spi.c'),
'flags' : [ '-DCONFIG_NV_SMA_SPI=1' ],
},
'ogp_spi' : { 'ogp_spi' : {
'systems' : systems_hwaccess, 'systems' : systems_hwaccess,
'deps' : [ libpci ], 'deps' : [ libpci ],

View File

@@ -13,7 +13,7 @@ option('programmer', type : 'array', value : ['auto'], choices : [
'developerbox_spi', 'digilent_spi', 'dirtyjtag_spi', 'drkaiser', 'dummy', 'ft2232_spi', 'developerbox_spi', 'digilent_spi', 'dirtyjtag_spi', 'drkaiser', 'dummy', 'ft2232_spi',
'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', 'nv_sma_spi', 'ogp_spi', 'parade_lspcon', 'pickit2_spi', 'pony_spi', 'raiden_debug_spi',
'rayer_spi', 'realtek_mst_i2c_spi', 'satamv', 'satasii', 'serprog', 'spidriver', 'stlinkv3_spi', 'rayer_spi', 'realtek_mst_i2c_spi', 'satamv', 'satasii', 'serprog', 'spidriver', 'stlinkv3_spi',
'usbblaster_spi', 'usbblaster_spi',
], description: 'Active programmers') ], description: 'Active programmers')

732
nv_sma_spi.c Normal file
View File

@@ -0,0 +1,732 @@
/*
* This file is part of the flashrom project.
*
* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: 2025 NVIDIA CORPORATION
*/
#include <string.h>
#include <stdlib.h>
#include <libusb.h>
#include <errno.h>
#include "platform.h"
#include "programmer.h"
#include "flash.h"
#include "usb_device.h"
/* This is common flashrom timeout for usb for 1 second. It works for erasing and programming 256 bytes */
#define USB_TIMEOUT 1000
#define NV_SMA_HEADER_LEN 4 /* channel_id(1byte) + cmd(1byte) + len(2bytes) */
#define NV_SMA_CH_OFFSET 0
#define NV_SMA_CMD_OFFSET 1
#define NV_SMA_LEN_OFFSET 2
/* External commands */
#define NV_SMA_CMD_CONFIG 0x00
#define NV_SMA_CMD_READ 0x01
#define NV_SMA_CMD_WRITE 0x02
#define NV_SMA_CMD_WRITE_READ 0x03
#define NV_SMA_CMD_POSTED_WRITE 0x04
#define NV_SMA_CMD_WRITE_RESP_LEN 5
#define NV_SMA_CMD_WRITE_RESP_STATUS_OFFSET 4
#define NV_SMA_CS_ASSERT 0x20
#define NV_SMA_CS_DEASSERT 0x10
#define NV_SMA_CS0 0x00
#define NV_SMA_CS1 0x40
#define NV_SMA_CS2 0x80
#define NV_SMA_CS3 0xC0
/* USB interface class/subclass/protocol for NV SMA SPI */
#define NV_SMA_INTERFACE_CLASS 0xFF /* Vendor Specific */
#define NV_SMA_INTERFACE_SUBCLASS 0x3F /* Nvidia assigned class */
#define NV_SMA_INTERFACE_PROTOCOL 0x01 /* Protocol v1 */
/* The USB descriptor says the max transfer size is 512 bytes,
* leaving 508 bytes for data as the channel + command + length take up 4 bytes
*/
#define NV_SMA_PACKET_SIZE 512
#define NV_SMA_MAX_DATA_LEN (NV_SMA_PACKET_SIZE - NV_SMA_HEADER_LEN)
struct nv_sma_spi_data {
struct libusb_device_handle *handle;
int interface;
uint8_t cs_bits;
uint8_t write_ep;
uint8_t read_ep;
};
static const struct dev_entry devs_nv_sma_spi[] = {
{0x0955, 0xcf11, OK, "Nvidia SMA", "USB To SPI"},
{0}
};
static int nv_sma_spi_shutdown(void *data)
{
struct nv_sma_spi_data *nv_sma_data = data;
int spi_interface = nv_sma_data->interface;
libusb_release_interface(nv_sma_data->handle, spi_interface);
libusb_attach_kernel_driver(nv_sma_data->handle, spi_interface);
libusb_close(nv_sma_data->handle);
libusb_exit(NULL);
free(data);
return 0;
}
static int nv_sma_write(struct nv_sma_spi_data *nv_sma_data, unsigned int writecnt,
const uint8_t *writearr, uint8_t cs_ctrl)
{
unsigned int data_len;
int packet_len;
int transferred;
int ret;
uint8_t resp_buf[NV_SMA_CMD_WRITE_RESP_LEN] = {0};
uint8_t buffer[NV_SMA_PACKET_SIZE] = {0};
unsigned int bytes_written = 0;
bool asserted = false;
while (bytes_written < writecnt) {
data_len = min(NV_SMA_MAX_DATA_LEN, writecnt - bytes_written );
packet_len = data_len + NV_SMA_HEADER_LEN;
buffer[NV_SMA_CMD_OFFSET] = NV_SMA_CMD_WRITE;
buffer[NV_SMA_CMD_OFFSET] |= nv_sma_data->cs_bits;
if (cs_ctrl & NV_SMA_CS_ASSERT && !asserted) {
buffer[NV_SMA_CMD_OFFSET] |= NV_SMA_CS_ASSERT;
asserted = true;
}
if (cs_ctrl & NV_SMA_CS_DEASSERT && (bytes_written + data_len) >= writecnt)
buffer[NV_SMA_CMD_OFFSET] |= NV_SMA_CS_DEASSERT;
buffer[NV_SMA_LEN_OFFSET] = (data_len) & 0xFF;
buffer[NV_SMA_LEN_OFFSET+1] = ((data_len) & 0xFF00) >> 8;
memcpy(buffer + NV_SMA_HEADER_LEN, writearr + bytes_written, data_len);
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->write_ep, buffer,
packet_len, &transferred, USB_TIMEOUT);
if (ret < 0 || transferred != packet_len) {
msg_perr("Could not send write command\n");
return -1;
}
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->read_ep, resp_buf,
NV_SMA_CMD_WRITE_RESP_LEN, &transferred, USB_TIMEOUT);
if (ret < 0 || transferred < NV_SMA_CMD_WRITE_RESP_LEN) {
msg_perr("Could not receive write command response\n");
return -1;
}
if (resp_buf[NV_SMA_CMD_WRITE_RESP_STATUS_OFFSET] != 0) {
msg_perr("recv error status=%d\n", resp_buf[NV_SMA_CMD_WRITE_RESP_STATUS_OFFSET]);
return -1;
}
bytes_written += data_len;
}
return 0;
}
static int nv_sma_read(struct nv_sma_spi_data *nv_sma_data, unsigned int readcnt,
uint8_t *readarr, uint8_t cs_ctrl)
{
uint8_t *read_ptr = readarr;
int ret;
int transferred;
unsigned int bytes_read = 0;
uint8_t buffer[NV_SMA_PACKET_SIZE] = {0};
uint8_t command_buf[8] = {
[0] = 0x01, /* reserved field for channel id, fixed to 0x01 */
[1] = NV_SMA_CMD_READ,
[2] = 4,
[3] = 0,
[4] = readcnt & 0xFF,
[5] = (readcnt & 0xFF00) >> 8,
[6] = (readcnt & 0xFF0000) >> 16,
[7] = (readcnt & 0xFF000000) >> 24
};
command_buf[NV_SMA_CMD_OFFSET] |= nv_sma_data->cs_bits;
if (cs_ctrl & NV_SMA_CS_ASSERT)
command_buf[NV_SMA_CMD_OFFSET] |= NV_SMA_CS_ASSERT;
if (cs_ctrl & NV_SMA_CS_DEASSERT)
command_buf[NV_SMA_CMD_OFFSET] |= NV_SMA_CS_DEASSERT;
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->write_ep, command_buf,
sizeof(command_buf), &transferred, USB_TIMEOUT);
if (ret < 0 || transferred != sizeof(command_buf)) {
msg_perr("Could not send read command\n");
return -1;
}
while (bytes_read < readcnt) {
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->read_ep, buffer,
NV_SMA_PACKET_SIZE, &transferred, USB_TIMEOUT);
if (ret < 0) {
msg_perr("Could not read data\n");
return -1;
}
if (transferred > NV_SMA_PACKET_SIZE) {
msg_perr("libusb bug: bytes received overflowed buffer\n");
return -1;
}
/* Response: u8 channel, u8 command, u16 data length, then the data that was read */
if (transferred < NV_SMA_HEADER_LEN) {
msg_perr("NV_SMA returned an invalid response to read command\n");
return -1;
}
int nv_sma_data_length = read_le16(buffer, NV_SMA_LEN_OFFSET);
if (transferred - NV_SMA_HEADER_LEN < nv_sma_data_length) {
msg_perr("NV_SMA returned less data than data length header indicates\n");
return -1;
}
bytes_read += nv_sma_data_length;
if (bytes_read > readcnt) {
msg_perr("NV_SMA returned more bytes than requested\n");
return -1;
}
memcpy(read_ptr, buffer + NV_SMA_HEADER_LEN, nv_sma_data_length);
read_ptr += nv_sma_data_length;
}
return 0;
}
static int nv_sma_posted_write(struct nv_sma_spi_data *nv_sma_data, unsigned int writecnt,
const unsigned char *writearr)
{
unsigned int data_len;
int packet_len;
int transferred;
int ret;
uint8_t buffer[NV_SMA_PACKET_SIZE] = {0};
int bytes_written = 0;
if (writecnt > NV_SMA_MAX_DATA_LEN) {
/* the API cannot handle such long msg */
msg_pspew("%s: invalid msg len: %i (max:%i)", __func__, writecnt, NV_SMA_MAX_DATA_LEN);
return -1;
}
data_len = writecnt;
packet_len = data_len + NV_SMA_HEADER_LEN;
buffer[NV_SMA_CMD_OFFSET] = NV_SMA_CS_ASSERT | NV_SMA_CS_DEASSERT | NV_SMA_CMD_POSTED_WRITE;
buffer[NV_SMA_CMD_OFFSET] |= nv_sma_data->cs_bits;
buffer[NV_SMA_LEN_OFFSET] = (data_len) & 0xFF;
buffer[NV_SMA_LEN_OFFSET + 1] = ((data_len) & 0xFF00) >> 8;
memcpy(buffer + NV_SMA_HEADER_LEN, writearr, writecnt);
bytes_written = packet_len;
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->write_ep, buffer,
packet_len, &transferred, USB_TIMEOUT);
if (ret < 0 || transferred != bytes_written) {
msg_perr("Could not send write read command\n");
return -1;
}
return 0;
}
static int nv_sma_write_read(struct nv_sma_spi_data *nv_sma_data, unsigned int writecnt,
const unsigned char *writearr,
unsigned int readcnt, unsigned char *readarr)
{
unsigned int data_len;
int packet_len;
int transferred;
int ret;
uint8_t resp_buf[NV_SMA_PACKET_SIZE] = {0};
uint8_t buffer[NV_SMA_PACKET_SIZE] = {0};
int bytes_written = 0;
if (writecnt + readcnt > NV_SMA_MAX_DATA_LEN) {
/* the API cannot handle such long msg */
msg_pspew("%s: invalid msg len: %i (max:%i)", __func__, writecnt, NV_SMA_MAX_DATA_LEN);
return -1;
}
data_len = writecnt + readcnt;
packet_len = data_len + NV_SMA_HEADER_LEN;
buffer[NV_SMA_CMD_OFFSET] = NV_SMA_CS_ASSERT | NV_SMA_CS_DEASSERT | NV_SMA_CMD_WRITE_READ;
buffer[NV_SMA_CMD_OFFSET] |= nv_sma_data->cs_bits;
buffer[NV_SMA_LEN_OFFSET] = (data_len) & 0xFF;
buffer[NV_SMA_LEN_OFFSET + 1] = ((data_len) & 0xFF00) >> 8;
memcpy(buffer + NV_SMA_HEADER_LEN, writearr, writecnt);
bytes_written = packet_len;
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->write_ep, buffer,
packet_len, &transferred, USB_TIMEOUT);
if (ret < 0 || transferred != bytes_written) {
msg_perr("Could not send write read command\n");
return -1;
}
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->read_ep, resp_buf,
sizeof(resp_buf), NULL, USB_TIMEOUT);
if (ret < 0) {
msg_perr("Could not receive write read command response\n");
return -1;
}
memcpy(readarr, resp_buf + writecnt + NV_SMA_HEADER_LEN, readcnt);
return 0;
}
static int nv_sma_spi_send_command(const struct flashctx *flash, unsigned int writecnt,
unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr)
{
struct nv_sma_spi_data *nv_sma_data = flash->mst->spi.data;
int ret = 0;
uint8_t cs_ctrl = 0;
if (writecnt + readcnt < NV_SMA_MAX_DATA_LEN) {
if (readcnt > 0) {
/* use OUT_IN commands for the length of DO and DI data can fit in single USB URB */
ret = nv_sma_write_read(nv_sma_data, writecnt, writearr, readcnt, readarr);
if (ret < 0) {
msg_perr("NV_SMA write/read error\n");
return -1;
}
}
else {
/* use posted write command for the length of DO can fit in single USB URB */
ret = nv_sma_posted_write(nv_sma_data, writecnt, writearr);
if (ret < 0) {
msg_perr("NV_SMA posted write error\n");
return -1;
}
}
}
else {
if (writecnt) {
cs_ctrl = NV_SMA_CS_ASSERT; /* assert cs before write */
if (readcnt == 0)
cs_ctrl |= NV_SMA_CS_DEASSERT;
ret = nv_sma_write(nv_sma_data, writecnt, writearr, cs_ctrl);
if (ret < 0) {
msg_perr("NV_SMA write error\n");
return -1;
}
}
if (readcnt) {
cs_ctrl = NV_SMA_CS_DEASSERT; /* de-assert cs after read */
if (writecnt == 0)
cs_ctrl |= NV_SMA_CS_ASSERT;
ret = nv_sma_read(nv_sma_data, readcnt, readarr, cs_ctrl);
if (ret < 0) {
msg_perr("NV_SMA read error\n");
return -1;
}
}
}
msg_pspew("%s: write %i, read %i ", __func__, writecnt, readcnt);
return 0;
}
static int32_t nv_sma_spi_config(struct nv_sma_spi_data *nv_sma_data, uint32_t spispeed_hz)
{
int32_t ret;
int transferred;
uint8_t buffer[16] = {
[0] = 0x0,
[1] = NV_SMA_CMD_CONFIG,
[2] = (sizeof(buffer) - 4) & 0xFF,
[3] = ((sizeof(buffer) - 4) & 0xFF00) >> 8,
/* Store frequency as 32-bit little endian at bytes 4-7 */
[4] = (spispeed_hz & 0xFF), /* LSB */
[5] = ((spispeed_hz >> 8) & 0xFF),
[6] = ((spispeed_hz >> 16) & 0xFF),
[7] = ((spispeed_hz >> 24) & 0xFF) /* MSB */
};
uint8_t response[NV_SMA_PACKET_SIZE] = {0}; /* Response buffer */
/* flush out IN EP */
do {
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->read_ep, response,
sizeof(response), &transferred, USB_TIMEOUT);
if (ret < 0) {
break;
}
} while (transferred > 0);
msg_pdbg("Requesting SPI frequency: %u Hz\n", spispeed_hz);
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->write_ep, buffer,
sizeof(buffer), NULL, USB_TIMEOUT);
if (ret < 0) {
msg_perr("Could not configure SPI interface\n");
return ret;
}
/* Read the configuration response */
ret = libusb_bulk_transfer(nv_sma_data->handle, nv_sma_data->read_ep, response,
sizeof(response), &transferred, USB_TIMEOUT);
if (ret < 0) {
msg_perr("Could not receive configure SPI command response\n");
return ret;
}
/* Extract actual frequency from response bytes 4-7 (little-endian) */
if (transferred >= 8) {
uint32_t actual_freq = response[4] |
(response[5] << 8) |
(response[6] << 16) |
(response[7] << 24);
if (spispeed_hz == 0) {
/* No frequency specified, just show the actual device default */
msg_pinfo("SPI frequency using device default: %u Hz\n", actual_freq);
} else {
msg_pinfo("SPI frequency configured: requested=%u Hz, actual=%u Hz\n",
spispeed_hz, actual_freq);
/* Warn if actual frequency differs significantly from requested */
if (actual_freq != spispeed_hz) {
int32_t diff_percent = ((int64_t)(actual_freq - spispeed_hz) * 100) / spispeed_hz;
if (diff_percent < 0) diff_percent = -diff_percent;
if (diff_percent > 10)
msg_pwarn("Note: Actual frequency differs by %d%% from requested\n", diff_percent);
}
}
} else {
msg_pdbg("Response too short to extract frequency (received %d bytes)\n", transferred);
}
return ret;
}
static const struct spi_master spi_master_nv_sma_spi = {
.features = SPI_MASTER_4BA,
.max_data_read = MAX_DATA_READ_UNLIMITED,
.max_data_write = MAX_DATA_WRITE_UNLIMITED,
.command = nv_sma_spi_send_command,
.read = default_spi_read,
.write_256 = default_spi_write_256,
.write_aai = default_spi_write_aai,
.shutdown = nv_sma_spi_shutdown,
};
/* Function to discover interface by class/subclass/protocol and endpoints */
static int discover_interface_and_endpoints(struct libusb_device_handle *handle,
int *interface_num,
uint8_t *write_ep, uint8_t *read_ep)
{
struct libusb_device *dev = libusb_get_device(handle);
struct libusb_config_descriptor *config;
int ret;
ret = libusb_get_active_config_descriptor(dev, &config);
if (ret != 0) {
msg_perr("Failed to get config descriptor: %s\n", libusb_error_name(ret));
return -1;
}
*interface_num = -1;
*write_ep = 0;
*read_ep = 0;
/* Search for interface with matching class/subclass/protocol */
for (int i = 0; i < config->bNumInterfaces; i++) {
const struct libusb_interface *interface = &config->interface[i];
for (int j = 0; j < interface->num_altsetting; j++) {
const struct libusb_interface_descriptor *altsetting = &interface->altsetting[j];
/* Check if this interface matches our class/subclass/protocol */
if (altsetting->bInterfaceClass != NV_SMA_INTERFACE_CLASS ||
altsetting->bInterfaceSubClass != NV_SMA_INTERFACE_SUBCLASS ||
altsetting->bInterfaceProtocol != NV_SMA_INTERFACE_PROTOCOL) {
continue;
}
msg_pdbg("Found NV SMA SPI interface: %d (class=0x%02x, subclass=0x%02x, protocol=0x%02x)\n",
altsetting->bInterfaceNumber,
altsetting->bInterfaceClass,
altsetting->bInterfaceSubClass,
altsetting->bInterfaceProtocol);
*interface_num = altsetting->bInterfaceNumber;
/* Scan endpoints in this interface */
for (int k = 0; k < altsetting->bNumEndpoints; k++) {
const struct libusb_endpoint_descriptor *endpoint = &altsetting->endpoint[k];
uint8_t ep_addr = endpoint->bEndpointAddress;
uint8_t ep_type = endpoint->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK;
/* We're looking for bulk endpoints */
if (ep_type != LIBUSB_TRANSFER_TYPE_BULK)
continue;
/* Check direction: bit 7 set = IN (device to host) */
if (ep_addr & LIBUSB_ENDPOINT_IN) {
if (*read_ep == 0) {
*read_ep = ep_addr;
msg_pdbg("Found bulk IN endpoint: 0x%02x\n", ep_addr);
}
} else {
if (*write_ep == 0) {
*write_ep = ep_addr;
msg_pdbg("Found bulk OUT endpoint: 0x%02x\n", ep_addr);
}
}
}
/* If we found the interface and both endpoints, we're done */
if (*write_ep != 0 && *read_ep != 0) {
libusb_free_config_descriptor(config);
return 0;
}
}
}
libusb_free_config_descriptor(config);
if (*interface_num == -1) {
msg_perr("Failed to find NV SMA SPI interface (class=0x%02x, subclass=0x%02x, protocol=0x%02x)\n",
NV_SMA_INTERFACE_CLASS, NV_SMA_INTERFACE_SUBCLASS, NV_SMA_INTERFACE_PROTOCOL);
return -1;
}
if (*write_ep == 0 || *read_ep == 0) {
msg_perr("Failed to find required bulk endpoints on interface %d\n", *interface_num);
return -1;
}
return 0;
}
/* Largely copied from ch341a_spi.c */
static int nv_sma_spi_init(const struct programmer_cfg *cfg)
{
char *arg;
uint16_t vid = devs_nv_sma_spi[0].vendor_id;
uint16_t pid = 0;
int index = 0;
uint32_t freq_hz = 0; /* Use device default frequency */
struct nv_sma_spi_data *nv_sma_data = calloc(1, sizeof(*nv_sma_data));
if (!nv_sma_data) {
msg_perr("Could not allocate space for SPI data\n");
return 1;
}
int32_t ret = libusb_init(NULL);
if (ret < 0) {
msg_perr("Could not initialize libusb!\n");
free(nv_sma_data);
return 1;
}
/* Enable information, warning, and error messages (only). */
#if LIBUSB_API_VERSION < 0x01000106
libusb_set_debug(NULL, 3);
#else
libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
#endif
char *bus_str = extract_programmer_param_str(cfg, "bus");
char *devnum_str = extract_programmer_param_str(cfg, "devnum");
if ((bus_str && !devnum_str) || (!bus_str && devnum_str)) {
msg_perr("Error: Both 'bus' and 'devnum' parameters must be specified together.\n");
free(bus_str);
free(devnum_str);
free(nv_sma_data);
return 1;
}
long bus_num = 0;
long dev_num = 0;
if (bus_str && devnum_str) {
char *endptr;
errno = 0;
bus_num = strtol(bus_str, &endptr, 10);
if (errno != 0 || bus_str == endptr || *endptr != '\0' || bus_num < 0) {
msg_perr("Error: Invalid bus number: '%s'.\n", bus_str);
free(bus_str);
free(devnum_str);
free(nv_sma_data);
return 1;
}
errno = 0;
dev_num = strtol(devnum_str, &endptr, 10);
if (errno != 0 || devnum_str == endptr || *endptr != '\0' || dev_num < 0) {
msg_perr("Error: Invalid device number: '%s'.\n", devnum_str);
free(bus_str);
free(devnum_str);
free(nv_sma_data);
return 1;
}
msg_pinfo("Looking for Nvidia SMA at bus %ld, device %ld.\n", bus_num, dev_num);
free(bus_str);
free(devnum_str);
}
while (devs_nv_sma_spi[index].vendor_id != 0) {
vid = devs_nv_sma_spi[index].vendor_id;
/* fixme: remove pid check if subclass is accepted globaly for NVIDIA CORPORATION */
pid = devs_nv_sma_spi[index].device_id;
if (bus_str && devnum_str) {
/* Select by bus and devnum */
struct usb_match match;
struct usb_device *found_device = NULL;
usb_match_init(cfg, &match);
usb_match_value_default(&match.vid, vid);
usb_match_value_default(&match.pid, pid);
usb_match_value_default(&match.bus, bus_num);
usb_match_value_default(&match.address, dev_num);
ret = usb_device_find(&match, &found_device);
if (ret != 0) {
msg_perr("Failed to find devices\n");
free(bus_str);
free(devnum_str);
free(nv_sma_data);
return ret;
}
ret = LIBUSB(libusb_open(found_device->device, &nv_sma_data->handle));
usb_device_free(found_device);
if (ret != 0) {
msg_perr("Failed to open device\n");
free(bus_str);
free(devnum_str);
free(nv_sma_data);
return ret;
}
}
else
/* Default behavior - open first device found */
nv_sma_data->handle = libusb_open_device_with_vid_pid(NULL, vid, pid);
if (nv_sma_data->handle)
break;
index++;
}
if (!nv_sma_data->handle) {
msg_perr("Couldn't find Nvidia System Management Agent.\n");
free(nv_sma_data);
return 1;
}
/* Discover interface and endpoints by class/subclass/protocol */
ret = discover_interface_and_endpoints(nv_sma_data->handle,
&nv_sma_data->interface,
&nv_sma_data->write_ep,
&nv_sma_data->read_ep);
if (ret != 0) {
msg_perr("Failed to discover NV SMA SPI interface and endpoints\n");
goto error_exit;
}
msg_pinfo("Using interface %d with endpoints: write=0x%02x, read=0x%02x\n",
nv_sma_data->interface, nv_sma_data->write_ep, nv_sma_data->read_ep);
ret = libusb_detach_kernel_driver(nv_sma_data->handle, nv_sma_data->interface);
if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND)
msg_pwarn("Cannot detach the existing USB driver. Claiming the interface may fail. %s\n",
libusb_error_name(ret));
ret = libusb_claim_interface(nv_sma_data->handle, nv_sma_data->interface);
if (ret != 0) {
msg_perr("Failed to claim interface %d: '%s'\n", nv_sma_data->interface, libusb_error_name(ret));
goto error_exit;
}
struct libusb_device *dev;
if (!(dev = libusb_get_device(nv_sma_data->handle))) {
msg_perr("Failed to get device from device handle.\n");
goto error_exit;
}
struct libusb_device_descriptor desc;
ret = libusb_get_device_descriptor(dev, &desc);
if (ret < 0) {
msg_perr("Failed to get device descriptor: '%s'\n", libusb_error_name(ret));
goto error_exit;
}
msg_pdbg("Device revision is %d.%01d.%01d\n",
(desc.bcdDevice >> 8) & 0x00FF,
(desc.bcdDevice >> 4) & 0x000F,
(desc.bcdDevice >> 0) & 0x000F);
/* select CS pin - default to CS0 if not specified */
nv_sma_data->cs_bits = NV_SMA_CS0; /* Default to CS0 bits */
arg = extract_programmer_param_str(cfg, "cs");
if (arg) {
if (!strcasecmp(arg, "0")) {
nv_sma_data->cs_bits = NV_SMA_CS0;
msg_pdbg("Using chip select CS0\n");
} else if (!strcasecmp(arg, "1")) {
nv_sma_data->cs_bits = NV_SMA_CS1;
msg_pdbg("Using chip select CS1\n");
} else if (!strcasecmp(arg, "2")) {
nv_sma_data->cs_bits = NV_SMA_CS2;
msg_pdbg("Using chip select CS2\n");
} else if (!strcasecmp(arg, "3")) {
nv_sma_data->cs_bits = NV_SMA_CS3;
msg_pdbg("Using chip select CS3\n");
} else {
msg_perr("Invalid chip select pin specified: '%s'. Valid values are 0, 1, 2, or 3.\n", arg);
free(arg);
return 1;
}
free(arg);
} else {
msg_pdbg("No CS specified, defaulting to CS0\n");
}
/* set NV_SMA SPI frequency */
arg = extract_programmer_param_str(cfg, "spispeed");
if (arg) {
char *endptr;
errno = 0;
long freq_input = strtol(arg, &endptr, 10);
if (errno != 0 || arg == endptr || *endptr != '\0' || freq_input <= 0) {
msg_perr("Error: Invalid frequency value: '%s'. "
"Please specify frequency in Hz (e.g., 15000000 for 15MHz).\n", arg);
free(arg);
goto error_exit;
}
freq_hz = (uint32_t)freq_input;
/* Validate frequency range - typical SPI range */
if (freq_hz > 60000000)
msg_pwarn("Warning: Frequency %u Hz exceeds typical maximum 60MHz.\n", freq_hz);
free(arg);
}
if (nv_sma_spi_config(nv_sma_data, freq_hz) < 0)
goto error_exit;
return register_spi_master(&spi_master_nv_sma_spi, nv_sma_data);
error_exit:
nv_sma_spi_shutdown(nv_sma_data);
return 1;
}
const struct programmer_entry programmer_nv_sma_spi = {
.name = "nv_sma_spi",
.type = USB,
.devs.dev = devs_nv_sma_spi,
.init = nv_sma_spi_init,
};

View File

@@ -175,6 +175,10 @@ const struct programmer_entry *const programmer_table[] = {
#if CONFIG_SPIDRIVER == 1 #if CONFIG_SPIDRIVER == 1
&programmer_spidriver, &programmer_spidriver,
#endif #endif
#if CONFIG_NV_SMA_SPI == 1
&programmer_nv_sma_spi,
#endif
}; };
const size_t programmer_table_size = ARRAY_SIZE(programmer_table); const size_t programmer_table_size = ARRAY_SIZE(programmer_table);

View File

@@ -10,7 +10,7 @@ meson_programmer_opts="all auto group_ftdi group_i2c group_jlink group_pci group
developerbox_spi digilent_spi dirtyjtag_spi drkaiser dummy ft2232_spi \ developerbox_spi digilent_spi dirtyjtag_spi drkaiser dummy ft2232_spi \
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 nv_sma_spi ogp_spi pickit2_spi pony_spi raiden_debug_spi rayer_spi \
realtek_mst_i2c_spi satamv satasii serprog spidriver stlinkv3_spi usbblaster_spi\ realtek_mst_i2c_spi satamv satasii serprog spidriver stlinkv3_spi usbblaster_spi\
asm106x" asm106x"

View File

@@ -81,6 +81,8 @@ struct io_mock {
int (*libusb_submit_transfer)(void *state, struct libusb_transfer *transfer); int (*libusb_submit_transfer)(void *state, struct libusb_transfer *transfer);
void (*libusb_free_transfer)(void *state, struct libusb_transfer *transfer); void (*libusb_free_transfer)(void *state, struct libusb_transfer *transfer);
int (*libusb_handle_events_timeout)(void *state, libusb_context *ctx, struct timeval *tv); int (*libusb_handle_events_timeout)(void *state, libusb_context *ctx, struct timeval *tv);
int (*libusb_bulk_transfer)(void *state, libusb_device_handle *devh, unsigned char endpoint,
unsigned char *data, int length, int *actual_length, unsigned int timeout);
/* 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);

View File

@@ -118,6 +118,14 @@ int __wrap_libusb_get_config_descriptor(
return 0; return 0;
} }
int __wrap_libusb_get_active_config_descriptor(libusb_device *dev, struct libusb_config_descriptor **config)
{
LOG_ME;
if (get_io() && get_io()->libusb_get_config_descriptor)
return get_io()->libusb_get_config_descriptor(get_io()->state, dev, 0, config);
return 0;
}
void __wrap_libusb_free_config_descriptor(struct libusb_config_descriptor *config) void __wrap_libusb_free_config_descriptor(struct libusb_config_descriptor *config)
{ {
LOG_ME; LOG_ME;
@@ -155,6 +163,16 @@ int __wrap_libusb_control_transfer(libusb_device_handle *devh, uint8_t bmRequest
return 0; return 0;
} }
int __wrap_libusb_bulk_transfer(libusb_device_handle *devh, unsigned char endpoint,
unsigned char *data, int length, int *actual_length, unsigned int timeout)
{
LOG_ME;
if (get_io() && get_io()->libusb_bulk_transfer)
return get_io()->libusb_bulk_transfer(get_io()->state, devh, endpoint, data,
length, actual_length, timeout);
return 0;
}
int __wrap_libusb_release_interface(libusb_device_handle *devh, int interface_number) int __wrap_libusb_release_interface(libusb_device_handle *devh, int interface_number)
{ {
LOG_ME; LOG_ME;

View File

@@ -29,6 +29,7 @@ uint8_t __wrap_libusb_get_device_address(libusb_device *dev);
int __wrap_libusb_get_device_descriptor(libusb_device *dev, struct libusb_device_descriptor *desc); int __wrap_libusb_get_device_descriptor(libusb_device *dev, struct libusb_device_descriptor *desc);
int __wrap_libusb_get_config_descriptor( int __wrap_libusb_get_config_descriptor(
libusb_device *dev, uint8_t config_index, struct libusb_config_descriptor **config); libusb_device *dev, uint8_t config_index, struct libusb_config_descriptor **config);
int __wrap_libusb_get_active_config_descriptor(libusb_device *dev, struct libusb_config_descriptor **config);
void __wrap_libusb_free_config_descriptor(struct libusb_config_descriptor *config); void __wrap_libusb_free_config_descriptor(struct libusb_config_descriptor *config);
int __wrap_libusb_get_configuration(libusb_device_handle *devh, int *config); int __wrap_libusb_get_configuration(libusb_device_handle *devh, int *config);
int __wrap_libusb_set_configuration(libusb_device_handle *devh, int config); int __wrap_libusb_set_configuration(libusb_device_handle *devh, int config);
@@ -36,6 +37,8 @@ int __wrap_libusb_claim_interface(libusb_device_handle *devh, int interface_numb
int __wrap_libusb_control_transfer(libusb_device_handle *devh, uint8_t bmRequestType, int __wrap_libusb_control_transfer(libusb_device_handle *devh, uint8_t bmRequestType,
uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data,
uint16_t wLength, unsigned int timeout); uint16_t wLength, unsigned int timeout);
int __wrap_libusb_bulk_transfer(libusb_device_handle *devh, unsigned char endpoint,
unsigned char *data, int length, int *actual_length, unsigned int timeout);
int __wrap_libusb_release_interface(libusb_device_handle *devh, int interface_number); int __wrap_libusb_release_interface(libusb_device_handle *devh, int interface_number);
void __wrap_libusb_close(libusb_device_handle *devh); void __wrap_libusb_close(libusb_device_handle *devh);
libusb_device *__wrap_libusb_ref_device(libusb_device *dev); libusb_device *__wrap_libusb_ref_device(libusb_device *dev);

View File

@@ -95,11 +95,13 @@ mocks = [
'-Wl,--wrap=libusb_get_device_address', '-Wl,--wrap=libusb_get_device_address',
'-Wl,--wrap=libusb_get_device_descriptor', '-Wl,--wrap=libusb_get_device_descriptor',
'-Wl,--wrap=libusb_get_config_descriptor', '-Wl,--wrap=libusb_get_config_descriptor',
'-Wl,--wrap=libusb_get_active_config_descriptor',
'-Wl,--wrap=libusb_free_config_descriptor', '-Wl,--wrap=libusb_free_config_descriptor',
'-Wl,--wrap=libusb_get_configuration', '-Wl,--wrap=libusb_get_configuration',
'-Wl,--wrap=libusb_set_configuration', '-Wl,--wrap=libusb_set_configuration',
'-Wl,--wrap=libusb_claim_interface', '-Wl,--wrap=libusb_claim_interface',
'-Wl,--wrap=libusb_control_transfer', '-Wl,--wrap=libusb_control_transfer',
'-Wl,--wrap=libusb_bulk_transfer',
'-Wl,--wrap=libusb_release_interface', '-Wl,--wrap=libusb_release_interface',
'-Wl,--wrap=libusb_ref_device', '-Wl,--wrap=libusb_ref_device',
'-Wl,--wrap=libusb_unref_device', '-Wl,--wrap=libusb_unref_device',

138
tests/nv_sma_spi.c Normal file
View File

@@ -0,0 +1,138 @@
/*
* This file is part of the flashrom project.
*
* SPDX-License-Identifier: GPL-2.0-only
* SPDX-FileCopyrightText: 2025 NVIDIA CORPORATION
*/
#include <stdlib.h>
#include <string.h>
#include "lifecycle.h"
#if CONFIG_NV_SMA_SPI == 1
/* Constants from nv_sma_spi.c */
#define NV_SMA_INTERFACE_CLASS 0xFF /* Vendor Specific */
#define NV_SMA_INTERFACE_SUBCLASS 0x3F /* Nvidia assigned class */
#define NV_SMA_INTERFACE_PROTOCOL 0x01 /* Protocol v1 */
static ssize_t nv_sma_spi_libusb_get_device_list(void *state, libusb_context *ctx, libusb_device ***list)
{
*list = calloc(1, sizeof(**list));
assert_non_null(*list);
/*
* libusb_device is opaque type, it is tossed around between libusb functions but always
* stays opaque to the caller.
* Given that all libusb functions are mocked in tests, and nv_sma_spi test is mocking
* only one device, we don't need to initialise libusb_device.
*/
return 1;
}
static void nv_sma_spi_libusb_free_device_list(void *state, libusb_device **list, int unref_devices)
{
free(list);
}
static int nv_sma_spi_libusb_get_device_descriptor(
void *state, libusb_device *dev, struct libusb_device_descriptor *desc)
{
desc->idVendor = 0x0955; /* NVIDIA_VID */
desc->idProduct = 0xcf11; /* NV_SMA_PID */
desc->bNumConfigurations = 1;
desc->bcdDevice = 0x0100; /* Device version 1.0.0 */
return 0;
}
static int nv_sma_spi_libusb_get_config_descriptor(
void *state, libusb_device *dev, uint8_t config_index, struct libusb_config_descriptor **config)
{
*config = calloc(1, sizeof(**config));
assert_non_null(*config);
struct libusb_endpoint_descriptor *tmp_endpoint = calloc(2, sizeof(*tmp_endpoint));
assert_non_null(tmp_endpoint);
struct libusb_interface_descriptor *tmp_interface_desc = calloc(1, sizeof(*tmp_interface_desc));
assert_non_null(tmp_interface_desc);
struct libusb_interface *tmp_interface = calloc(1, sizeof(*tmp_interface));
assert_non_null(tmp_interface);
/* OUT endpoint (write) */
tmp_endpoint[0].bEndpointAddress = 0x01;
tmp_endpoint[0].bmAttributes = 0x02; /* Bulk transfer */
/* IN endpoint (read) */
tmp_endpoint[1].bEndpointAddress = 0x81;
tmp_endpoint[1].bmAttributes = 0x02; /* Bulk transfer */
tmp_interface_desc->bInterfaceClass = NV_SMA_INTERFACE_CLASS;
tmp_interface_desc->bInterfaceSubClass = NV_SMA_INTERFACE_SUBCLASS;
tmp_interface_desc->bInterfaceProtocol = NV_SMA_INTERFACE_PROTOCOL;
tmp_interface_desc->bInterfaceNumber = 0;
tmp_interface_desc->bNumEndpoints = 2; /* in_endpoint and out_endpoint */
tmp_interface_desc->endpoint = tmp_endpoint;
tmp_interface->num_altsetting = 1;
tmp_interface->altsetting = tmp_interface_desc;
(*config)->bConfigurationValue = 0;
(*config)->bNumInterfaces = 1;
(*config)->interface = tmp_interface;
return 0;
}
static void nv_sma_spi_libusb_free_config_descriptor(void *state, struct libusb_config_descriptor *config)
{
free((void *)config->interface->altsetting->endpoint);
free((void *)config->interface->altsetting);
free((void *)config->interface);
free(config);
}
static int nv_sma_spi_libusb_bulk_transfer(void *state, libusb_device_handle *devh, unsigned char endpoint,
unsigned char *data, int length, int *actual_length, unsigned int timeout)
{
if (data && length == 512) {
int all_zero = 1;
for (int i = 0; i < 512; ++i) {
if (data[i] != 0) {
all_zero = 0;
break;
}
}
if (all_zero) {
if (actual_length)
*actual_length = 0;
return 0;
}
}
else if (actual_length)
*actual_length = length;
return 0;
}
void nv_sma_spi_basic_lifecycle_test_success(void **state)
{
struct io_mock_fallback_open_state nv_sma_spi_fallback_open_state = {
.noc = 0,
.paths = { NULL },
};
const struct io_mock nv_sma_spi_io = {
.libusb_get_device_list = nv_sma_spi_libusb_get_device_list,
.libusb_free_device_list = nv_sma_spi_libusb_free_device_list,
.libusb_get_device_descriptor = nv_sma_spi_libusb_get_device_descriptor,
.libusb_get_config_descriptor = nv_sma_spi_libusb_get_config_descriptor,
.libusb_free_config_descriptor = nv_sma_spi_libusb_free_config_descriptor,
.libusb_bulk_transfer = nv_sma_spi_libusb_bulk_transfer,
.fallback_open_state = &nv_sma_spi_fallback_open_state,
};
run_basic_lifecycle(state, &nv_sma_spi_io, &programmer_nv_sma_spi, "");
}
#else
SKIP_TEST(nv_sma_spi_basic_lifecycle_test_success)
#endif /* CONFIG_NV_SMA_SPI */

View File

@@ -528,6 +528,7 @@ int main(int argc, char *argv[])
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), cmocka_unit_test(spidriver_probe_lifecycle_test_success),
cmocka_unit_test(nv_sma_spi_basic_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);

View File

@@ -77,6 +77,7 @@ 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); void spidriver_probe_lifecycle_test_success(void **state);
void nv_sma_spi_basic_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);

View File

@@ -17,7 +17,7 @@
#ifndef _USB_UNITTESTS_H_ #ifndef _USB_UNITTESTS_H_
#define _USB_UNITTESTS_H_ #define _USB_UNITTESTS_H_
#if CONFIG_RAIDEN_DEBUG_SPI == 1 || CONFIG_DEDIPROG == 1 || CONFIG_CH341A_SPI == 1 #if CONFIG_RAIDEN_DEBUG_SPI == 1 || CONFIG_DEDIPROG == 1 || CONFIG_CH341A_SPI == 1 || CONFIG_NV_SMA_SPI == 1
#include <libusb.h> #include <libusb.h>