1
0
mirror of https://review.coreboot.org/flashrom.git synced 2025-04-27 15:12:36 +02:00

Add support for STLINK V3 debugger/programmer via its SPI bridge

Change-Id: Icffab87ac8f2c570187ed753ec70f054541873a4
Signed-off-by: Miklós Márton <martonmiklosqdev@gmail.com>
Reviewed-on: https://review.coreboot.org/c/flashrom/+/34661
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Nico Huber <nico.h@gmx.de>
This commit is contained in:
Miklós Márton 2019-08-01 19:14:10 +02:00 committed by Nico Huber
parent 728062f7ff
commit 324929c3d7
6 changed files with 590 additions and 0 deletions

View File

@ -190,6 +190,11 @@ UNSUPPORTED_FEATURES += CONFIG_CH341A_SPI=yes
else
override CONFIG_CH341A_SPI = no
endif
ifeq ($(CONFIG_STLINKV3_SPI), yes)
UNSUPPORTED_FEATURES += CONFIG_STLINKV3_SPI=yes
else
override CONFIG_STLINKV3_SPI = no
endif
# libjaylink is also not available for DOS
ifeq ($(CONFIG_JLINK_SPI), yes)
UNSUPPORTED_FEATURES += CONFIG_JLINK_SPI=yes
@ -366,6 +371,11 @@ UNSUPPORTED_FEATURES += CONFIG_PICKIT2_SPI=yes
else
override CONFIG_PICKIT2_SPI = no
endif
ifeq ($(CONFIG_STLINKV3_SPI), yes)
UNSUPPORTED_FEATURES += CONFIG_STLINKV3_SPI=yes
else
override CONFIG_STLINKV3_SPI = no
endif
ifeq ($(CONFIG_CH341A_SPI), yes)
UNSUPPORTED_FEATURES += CONFIG_CH341A_SPI=yes
else
@ -631,6 +641,9 @@ CONFIG_MSTARDDC_SPI ?= no
# Always enable PICkit2 SPI dongles for now.
CONFIG_PICKIT2_SPI ?= yes
# Always enable STLink V3
CONFIG_STLINKV3_SPI ?= yes
# Always enable dummy tracing for now.
CONFIG_DUMMY ?= yes
@ -709,6 +722,7 @@ override CONFIG_DEDIPROG = no
override CONFIG_DIGILENT_SPI = no
override CONFIG_DEVELOPERBOX_SPI = no
override CONFIG_PICKIT2_SPI = no
override CONFIG_STLINKV3_SPI = no
endif
ifeq ($(CONFIG_ENABLE_LIBPCI_PROGRAMMERS), no)
override CONFIG_INTERNAL = no
@ -876,6 +890,12 @@ PROGRAMMER_OBJS += pickit2_spi.o
NEED_LIBUSB1 += CONFIG_PICKIT2_SPI
endif
ifeq ($(CONFIG_STLINKV3_SPI), yes)
FEATURE_CFLAGS += -D'CONFIG_STLINKV3_SPI=1'
PROGRAMMER_OBJS += stlinkv3_spi.o
NEED_LIBUSB1 += CONFIG_STLINKV3_SPI
endif
ifneq ($(NEED_LIBFTDI), )
FTDILIBS := $(call debug_shell,[ -n "$(PKG_CONFIG_LIBDIR)" ] && export PKG_CONFIG_LIBDIR="$(PKG_CONFIG_LIBDIR)" ; $(PKG_CONFIG) --libs libftdi1 || $(PKG_CONFIG) --libs libftdi || printf "%s" "-lftdi -lusb")
FEATURE_CFLAGS += $(call debug_shell,grep -q "FT232H := yes" .features && printf "%s" "-D'HAVE_FT232H=1'")

View File

@ -343,6 +343,8 @@ bitbanging adapter)
.sp
.BR "* ni845x_spi" " (for SPI flash ROMs attached to National Instruments USB-8451 or USB-8452)"
.sp
.BR "* stlinkv3_spi" " (for SPI flash ROMs attached to STMicroelectronics STLINK V3 devices)"
.sp
Some programmers have optional or mandatory parameters which are described
in detail in the
.B PROGRAMMER-SPECIFIC INFORMATION
@ -1260,6 +1262,31 @@ The SPI speed can be selected by using the
syntax where \fBfrequency\fP is the SPI clock frequency in kHz.
The maximum speed depends on the device in use.
.SS
.BR "stlinkv3_spi " programmer
.IP
This module supports SPI flash programming through the STMicroelectronics
STLINK V3 programmer/debugger's SPI bridge interface
.sp
.B " flashrom \-p stlinkv3_spi"
.sp
If there is more than one compatible device connected, you can select which one
should be used by specifying its serial number with the
.sp
.B " flashrom \-p stlinkv3_spi:serial=number"
.sp
syntax where
.B number
is the serial number of the device (which can be found for example in the
output of lsusb -v).
.sp
The SPI speed can be selected by using the
.sp
.B " flashrom \-p stlinkv3_spi:spispeed=frequency"
.sp
syntax where \fBfrequency\fP is the SPI clock frequency in kHz.
If the passed frequency is not supported by the adapter the nearest lower
supported frequency will be used.
.SS
.SH EXAMPLES
To back up and update your BIOS, run

View File

@ -460,6 +460,19 @@ const struct programmer_entry programmer_table[] = {
.delay = internal_delay,
},
#endif
#if CONFIG_STLINKV3_SPI == 1
{
.name = "stlinkv3_spi",
.type = USB,
.devs.dev = devs_stlinkv3_spi,
.init = stlinkv3_spi_init,
.map_flash_region = fallback_map,
.unmap_flash_region = fallback_unmap,
.delay = internal_delay,
},
#endif
{0}, /* This entry corresponds to PROGRAMMER_INVALID. */
};

View File

@ -61,6 +61,7 @@ config_satamv = get_option('config_satamv')
config_satasii = get_option('config_satasii')
config_serprog = get_option('config_serprog')
config_usbblaster_spi = get_option('config_usbblaster_spi')
config_stlinkv3_spi = get_option('config_stlinkv3_spi')
cargs = []
deps = []
@ -271,6 +272,10 @@ if config_usbblaster_spi
srcs += 'usbblaster_spi.c'
cargs += '-DCONFIG_USBBLASTER_SPI=1'
endif
if config_stlinkv3_spi
srcs += 'stlinkv3_spi.c'
cargs += '-DCONFIG_STLINKV3_SPI=1'
endif
# bitbanging SPI infrastructure
if config_bitbang_spi

View File

@ -123,6 +123,9 @@ enum programmer {
#endif
#if CONFIG_NI845X_SPI == 1
PROGRAMMER_NI845X_SPI,
#endif
#if CONFIG_STLINKV3_SPI == 1
PROGRAMMER_STLINKV3_SPI,
#endif
PROGRAMMER_INVALID /* This must always be the last entry. */
};
@ -501,6 +504,12 @@ int pickit2_spi_init(void);
extern const struct dev_entry devs_pickit2_spi[];
#endif
/* stlinkv3_spi.c */
#if CONFIG_STLINKV3_SPI == 1
int stlinkv3_spi_init(void);
extern const struct dev_entry devs_stlinkv3_spi[];
#endif
/* rayer_spi.c */
#if CONFIG_RAYER_SPI == 1
int rayer_spi_init(void);

516
stlinkv3_spi.c Normal file
View File

@ -0,0 +1,516 @@
/*
* This file is part of the flashrom project.
*
* Copyright (C) 2019 Miklós Márton martonmiklosqdev@gmail.com
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
/*
* Driver for programming SPI flash chips using the SPI port
* of the STMicroelectronics's STLINK-V3 programmer/debugger.
*
* The implementation is inspired by the ST's STLINK-V3-BRIDGE C++ API:
* https://www.st.com/en/development-tools/stlink-v3-bridge.html
*/
#include "flash.h"
#include "programmer.h"
#include "spi.h"
#include <libusb.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
enum fw_version_check_result {
FW_VERSION_OK,
FW_VERSION_OLD,
};
enum spi_prescaler {
SPI_BAUDRATEPRESCALER_2 = 0,
SPI_BAUDRATEPRESCALER_4 = 1,
SPI_BAUDRATEPRESCALER_8 = 2,
SPI_BAUDRATEPRESCALER_16 = 3,
SPI_BAUDRATEPRESCALER_32 = 4,
SPI_BAUDRATEPRESCALER_64 = 5,
SPI_BAUDRATEPRESCALER_128 = 6,
SPI_BAUDRATEPRESCALER_256 = 7
};
enum spi_dir {
SPI_DIRECTION_2LINES_FULLDUPLEX = 0,
SPI_DIRECTION_2LINES_RXONLY = 1,
SPI_DIRECTION_1LINE_RX = 2,
SPI_DIRECTION_1LINE_TX = 3
};
enum spi_mode {
SPI_MODE_SLAVE = 0,
SPI_MODE_MASTER = 1
};
enum spi_datasize {
SPI_DATASIZE_16B = 0,
SPI_DATASIZE_8B = 1
};
enum spi_cpol {
SPI_CPOL_LOW = 0,
SPI_CPOL_HIGH = 1
};
enum spi_cpha {
SPI_CPHA_1EDGE = 0,
SPI_CPHA_2EDGE = 1
};
enum spi_firstbit {
SPI_FIRSTBIT_LSB = 0,
SPI_FIRSTBIT_MSB = 1
};
// ST calls the Chip select (CS) NSS == Negated Slave Select
enum spi_nss {
SPI_NSS_SOFT = 0,
SPI_NSS_HARD = 1
};
enum spi_nss_level {
SPI_NSS_LOW = 0,
SPI_NSS_HIGH = 1
};
#define ST_GETVERSION_EXT 0xFB
#define STLINK_BRIDGE_COMMAND 0xFC
#define STLINK_BRIDGE_CLOSE 0x01
#define STLINK_BRIDGE_GET_RWCMD_STATUS 0x02
#define STLINK_BRIDGE_GET_CLOCK 0x03
#define STLINK_BRIDGE_INIT_SPI 0x20
#define STLINK_BRIDGE_WRITE_SPI 0x21
#define STLINK_BRIDGE_READ_SPI 0x22
#define STLINK_BRIDGE_CS_SPI 0x23
#define STLINK_BRIDGE_SPI_ERROR 0x02
#define STLINK_SPI_COM 0x02
#define STLINK_EP_OUT 0x06
#define STLINK_EP_IN 0x86
#define FIRST_COMPATIBLE_BRIDGE_FW_VERSION 3
#define USB_TIMEOUT_IN_MS 5000
const struct dev_entry devs_stlinkv3_spi[] = {
{0x0483, 0x374F, OK, "STMicroelectronics", "STLINK-V3"},
{0}
};
static struct libusb_context *usb_ctx;
static libusb_device_handle *stlinkv3_handle;
static int stlinkv3_command(uint8_t *command, size_t command_length,
uint8_t *answer, size_t answer_length, const char *command_name)
{
int actual_length = 0;
int rc = libusb_bulk_transfer(stlinkv3_handle, STLINK_EP_OUT,
command, command_length,
&actual_length, USB_TIMEOUT_IN_MS);
if (rc != LIBUSB_TRANSFER_COMPLETED || (size_t)actual_length != command_length) {
msg_perr("Failed to issue the %s command: '%s'\n",
command_name,
libusb_error_name(rc));
return -1;
}
rc = libusb_bulk_transfer(stlinkv3_handle, STLINK_EP_IN,
answer, answer_length,
&actual_length, USB_TIMEOUT_IN_MS);
if (rc != LIBUSB_TRANSFER_COMPLETED || (size_t)actual_length != answer_length) {
msg_perr("Failed to get %s answer: '%s'\n",
command_name,
libusb_error_name(rc));
return -1;
}
return 0;
}
/**
* @param[out] bridge_input_clk Current input frequency in kHz of the given com.
*/
static int stlinkv3_get_clk(uint32_t *bridge_input_clk)
{
uint8_t command[16];
uint8_t answer[12];
if (bridge_input_clk == NULL)
return -1;
memset(command, 0, sizeof(command));
command[0] = STLINK_BRIDGE_COMMAND;
command[1] = STLINK_BRIDGE_GET_CLOCK;
command[2] = STLINK_SPI_COM;
if (stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "STLINK_BRIDGE_GET_CLOCK") != 0)
return -1;
*bridge_input_clk = (uint32_t)answer[4]
| (uint32_t)answer[5]<<8
| (uint32_t)answer[6]<<16
| (uint32_t)answer[7]<<24;
return 0;
}
static int stlinkv3_spi_calc_prescaler(uint16_t reqested_freq_in_kHz,
enum spi_prescaler *prescaler,
uint16_t *calculated_freq_in_kHz)
{
uint32_t bridge_clk_in_kHz;
uint32_t calculated_prescaler = 1;
uint16_t prescaler_value;
if (stlinkv3_get_clk(&bridge_clk_in_kHz))
return -1;
calculated_prescaler = bridge_clk_in_kHz/reqested_freq_in_kHz;
// Apply a smaller frequency if not exact
if (calculated_prescaler <= 2) {
*prescaler = SPI_BAUDRATEPRESCALER_2;
prescaler_value = 2;
} else if (calculated_prescaler <= 4) {
*prescaler = SPI_BAUDRATEPRESCALER_4;
prescaler_value = 4;
} else if (calculated_prescaler <= 8) {
*prescaler = SPI_BAUDRATEPRESCALER_8;
prescaler_value = 8;
} else if (calculated_prescaler <= 16) {
*prescaler = SPI_BAUDRATEPRESCALER_16;
prescaler_value = 16;
} else if (calculated_prescaler <= 32) {
*prescaler = SPI_BAUDRATEPRESCALER_32;
prescaler_value = 32;
} else if (calculated_prescaler <= 64) {
*prescaler = SPI_BAUDRATEPRESCALER_64;
prescaler_value = 64;
} else if (calculated_prescaler <= 128) {
*prescaler = SPI_BAUDRATEPRESCALER_128;
prescaler_value = 128;
} else if (calculated_prescaler <= 256) {
*prescaler = SPI_BAUDRATEPRESCALER_256;
prescaler_value = 256;
} else {
// smaller frequency not possible
*prescaler = SPI_BAUDRATEPRESCALER_256;
prescaler_value = 256;
}
*calculated_freq_in_kHz = bridge_clk_in_kHz / prescaler_value;
return 0;
}
static int stlinkv3_check_version(enum fw_version_check_result *result)
{
uint8_t answer[12];
uint8_t command[16];
memset(command, 0, sizeof(command));
command[0] = ST_GETVERSION_EXT;
command[1] = 0x80;
if (stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "ST_GETVERSION_EXT") != 0)
return -1;
msg_pinfo("Connected to STLink V3 with bridge FW version: %d\n", answer[4]);
*result = answer[4] >= FIRST_COMPATIBLE_BRIDGE_FW_VERSION
? FW_VERSION_OK
: FW_VERSION_OLD;
return 0;
}
static int stlinkv3_spi_open(uint16_t reqested_freq_in_kHz)
{
uint8_t command[16];
uint8_t answer[2];
uint16_t SCK_freq_in_kHz;
enum spi_prescaler prescaler;
enum fw_version_check_result fw_check_result;
if (stlinkv3_check_version(&fw_check_result)) {
msg_perr("Failed to query FW version");
return -1;
}
if (fw_check_result != FW_VERSION_OK) {
msg_pinfo("Your STLink V3 has too old version of the bridge interface\n"
"Please update the firmware with the STSW-LINK007 which can be downloaded from here:\n"
"https://www.st.com/en/development-tools/stsw-link007.html");
return -1;
}
if (stlinkv3_spi_calc_prescaler(reqested_freq_in_kHz,
&prescaler,
&SCK_freq_in_kHz)) {
msg_perr("Failed to calculate SPI clock prescaler");
return -1;
}
msg_pinfo("SCK frequency set to %d kHz\n", SCK_freq_in_kHz);
memset(command, 0, sizeof(command));
command[0] = STLINK_BRIDGE_COMMAND;
command[1] = STLINK_BRIDGE_INIT_SPI;
command[2] = SPI_DIRECTION_2LINES_FULLDUPLEX;
command[3] = (SPI_MODE_MASTER
| (SPI_CPHA_1EDGE << 1)
| (SPI_CPOL_LOW << 2)
| (SPI_FIRSTBIT_MSB << 3));
command[4] = SPI_DATASIZE_8B;
command[5] = SPI_NSS_SOFT;
command[6] = (uint8_t)prescaler;
return stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "STLINK_BRIDGE_INIT_SPI");
}
static int stlinkv3_get_last_readwrite_status(uint32_t *status)
{
uint8_t command[16];
uint16_t answer[4];
memset(command, 0, sizeof(command));
command[0] = STLINK_BRIDGE_COMMAND;
command[1] = STLINK_BRIDGE_GET_RWCMD_STATUS;
if (stlinkv3_command(command, sizeof(command),
(uint8_t *)answer, sizeof(answer),
"STLINK_BRIDGE_GET_RWCMD_STATUS") != 0)
return -1;
*status = (uint32_t)answer[2] | (uint32_t)answer[3]<<16;
return 0;
}
static int stlinkv3_spi_set_SPI_NSS(enum spi_nss_level nss_level)
{
uint8_t command[16];
uint8_t answer[2];
memset(command, 0, sizeof(command));
command[0] = STLINK_BRIDGE_COMMAND;
command[1] = STLINK_BRIDGE_CS_SPI;
command[2] = (uint8_t) (nss_level);
if (stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "STLINK_BRIDGE_CS_SPI") != 0)
return -1;
return 0;
}
static int stlinkv3_spi_transmit(struct flashctx *flash,
unsigned int write_cnt,
unsigned int read_cnt,
const unsigned char *write_arr,
unsigned char *read_arr)
{
uint8_t command[16];
int rc = 0;
int actual_length = 0;
uint32_t rw_status = 0;
if (stlinkv3_spi_set_SPI_NSS(SPI_NSS_LOW)) {
msg_perr("Failed to set the NSS pin to low\n");
return -1;
}
memset(command, 0, sizeof(command));
command[0] = STLINK_BRIDGE_COMMAND;
command[1] = STLINK_BRIDGE_WRITE_SPI;
command[2] = (uint8_t)write_cnt;
command[3] = (uint8_t)(write_cnt >> 8);
for (unsigned int i = 0; (i < 8) && (i < write_cnt); i++)
command[4+i] = write_arr[i];
rc = libusb_bulk_transfer(stlinkv3_handle, STLINK_EP_OUT,
command, sizeof(command),
&actual_length, USB_TIMEOUT_IN_MS);
if (rc != LIBUSB_TRANSFER_COMPLETED || actual_length != sizeof(command)) {
msg_perr("Failed to issue the STLINK_BRIDGE_WRITE_SPI command: '%s'\n",
libusb_error_name(rc));
goto transmit_err;
}
if (write_cnt > 8) {
rc = libusb_bulk_transfer(stlinkv3_handle,
STLINK_EP_OUT,
(unsigned char *)&write_arr[8],
(unsigned int)(write_cnt - 8),
&actual_length,
USB_TIMEOUT_IN_MS);
if (rc != LIBUSB_TRANSFER_COMPLETED || (unsigned int)actual_length != (write_cnt - 8)) {
msg_perr("Failed to send the data after the STLINK_BRIDGE_WRITE_SPI command: '%s'\n",
libusb_error_name(rc));
goto transmit_err;
}
}
if (stlinkv3_get_last_readwrite_status(&rw_status))
return -1;
if (rw_status != 0) {
msg_perr("SPI read/write failure: %d\n", rw_status);
goto transmit_err;
}
if (read_cnt) {
command[1] = STLINK_BRIDGE_READ_SPI;
command[2] = (uint8_t)read_cnt;
command[3] = (uint8_t)(read_cnt >> 8);
rc = libusb_bulk_transfer(stlinkv3_handle, STLINK_EP_OUT,
command, sizeof(command),
&actual_length, USB_TIMEOUT_IN_MS);
if (rc != LIBUSB_TRANSFER_COMPLETED || (unsigned int)actual_length != sizeof(command)) {
msg_perr("Failed to issue the STLINK_BRIDGE_READ_SPI command: '%s'\n",
libusb_error_name(rc));
goto transmit_err;
}
rc = libusb_bulk_transfer(stlinkv3_handle,
STLINK_EP_IN,
(unsigned char *)read_arr,
(int)read_cnt,
&actual_length,
USB_TIMEOUT_IN_MS);
if (rc != LIBUSB_TRANSFER_COMPLETED || (unsigned int)actual_length != read_cnt) {
msg_perr("Failed to retrive the STLINK_BRIDGE_READ_SPI answer: '%s'\n",
libusb_error_name(rc));
goto transmit_err;
}
}
if (stlinkv3_get_last_readwrite_status(&rw_status))
goto transmit_err;
if (rw_status != 0) {
msg_perr("SPI read/write failure: %d\n", rw_status);
goto transmit_err;
}
if (stlinkv3_spi_set_SPI_NSS(SPI_NSS_HIGH)) {
msg_perr("Failed to set the NSS pin to high\n");
return -1;
}
return 0;
transmit_err:
if (stlinkv3_spi_set_SPI_NSS(SPI_NSS_HIGH))
msg_perr("Failed to set the NSS pin to high\n");
return -1;
}
static int stlinkv3_spi_shutdown(void *data)
{
uint8_t command[16];
uint8_t answer[2];
memset(command, 0, sizeof(command));
command[0] = STLINK_BRIDGE_COMMAND;
command[1] = STLINK_BRIDGE_CLOSE;
command[2] = STLINK_SPI_COM;
stlinkv3_command(command, sizeof(command), answer, sizeof(answer), "STLINK_BRIDGE_CLOSE");
libusb_close(stlinkv3_handle);
libusb_exit(usb_ctx);
return 0;
}
static const struct spi_master spi_programmer_stlinkv3 = {
.max_data_read = UINT16_MAX,
.max_data_write = UINT16_MAX,
.command = stlinkv3_spi_transmit,
.multicommand = default_spi_send_multicommand,
.read = default_spi_read,
.write_256 = default_spi_write_256,
.write_aai = default_spi_write_aai,
};
int stlinkv3_spi_init(void)
{
uint16_t sck_freq_kHz = 1000; // selecting 1 MHz SCK is a good bet
char *speed_str = NULL;
char *serialno = NULL;
char *endptr = NULL;
libusb_init(&usb_ctx);
if (!usb_ctx) {
msg_perr("Could not initialize libusb!\n");
return 1;
}
serialno = extract_programmer_param("serial");
if (serialno)
msg_pdbg("Opening STLINK-V3 with serial: %s\n", serialno);
stlinkv3_handle = usb_dev_get_by_vid_pid_serial(usb_ctx,
devs_stlinkv3_spi[0].vendor_id,
devs_stlinkv3_spi[0].device_id,
serialno);
if (!stlinkv3_handle) {
if (serialno)
msg_perr("No STLINK-V3 seems to be connected with serial %s\n", serialno);
else
msg_perr("Could not find any connected STLINK-V3\n");
free(serialno);
goto err_exit;
}
free(serialno);
speed_str = extract_programmer_param("spispeed");
if (speed_str) {
sck_freq_kHz = strtoul(speed_str, &endptr, 0);
if (*endptr) {
msg_perr("The spispeed parameter passed with invalid format: %s\n",
speed_str);
msg_perr("Please pass the parameter with a simple number in kHz\n");
return -1;
}
free(speed_str);
}
if (stlinkv3_spi_open(sck_freq_kHz))
goto err_exit;
if (register_shutdown(stlinkv3_spi_shutdown, NULL))
goto err_exit;
if (register_spi_master(&spi_programmer_stlinkv3))
goto err_exit;
return 0;
err_exit:
libusb_exit(usb_ctx);
return 1;
}