From 5792292d44d776137bf0b175e538f7e8c409cb42 Mon Sep 17 00:00:00 2001 From: Michael Field Date: Tue, 24 Feb 2026 13:43:33 -0800 Subject: [PATCH] Add manual spi mode support. Add new cmdline option, to be read by spidev transport, along with supporting code in spi transport. Will enable appliable spi mode, enabling the userspace driver to take advantage of high speed spi read & program. Does not poll SFDP to check for support, but will instead fail on hardware that does not support higher speeds. Signed-off-by: Michael Field --- README.md | 24 +++++++- examples/htool.c | 3 + examples/htool_spi.c | 15 +++++ transports/libhoth_spi.c | 127 +++++++++++++++++++++++++++++++++++---- transports/libhoth_spi.h | 12 ++++ 5 files changed, 167 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index cc790f2..d3dd5ec 100644 --- a/README.md +++ b/README.md @@ -75,12 +75,25 @@ Available subcommands: (append --help to subcommand for details) target reset on - Put the target device into reset. target reset off - Take the target device out of reset target reset pulse - Quickly put the target device in and out of reset + target debug enable - Enable target debug. + target debug disable - Disable target debug. + target debug get - Get target debug status. console - Open a console for communicating with the RoT or devices attached to the RoT. payload getstatus - Show the current payload update status + payload verify - Verify the staging side. payload status - Show payload status payload update - Perform payload update protocol for Titan images. + payload confirm - Finish a payload update confirmation. + payload get_timeout - Get the current payload update confirmation timeout. + payload timeout_enable - Enable/Disable the timeout mechanism for payload update confirm payload read - Read content of staging flash for Titan images. + payload erase - Erase a range on the staging side. + payload activate - Activate a payload side for next boot. + payload info all - Display detailed payload info for a Titan image, including region details. + payload info nonstatic - Print non-static regions in a Titan image. payload info - Display payload info for a Titan image. + payload mauv compiled - Display compiled MAUV for the payload. + payload mauv effective - Display effective MAUV for the payload. firmware_update update_from_flash_and_reset - Installs a firmware update from a bundle staged in the external flash. dfu update - Directly install a PIE-RoT fwupdate. dfu check - Check that the device is running firmware matching a fwupdate bundle. @@ -119,6 +132,7 @@ Available subcommands: (append --help to subcommand for details) storage read - Read from the controlled storage storage write - Write to the controlled storage storage delete - Delete from the controlled storage + gpio set_drive_strength - Set GPIO drive strength hello - A test function to send and receive an integer opentitan_version - Get OpenTitan version extract_ot_bundle - Get OpenTitan version @@ -143,6 +157,12 @@ Available subcommands: (append --help to subcommand for details) security get_attestation_pub_cert - Get the Attestation Public Cert security get_signed_attestation_pub_cert - Get the Signed Attestation Public Cert security attestation - Fetch attestation information, including tokens and certificates. + mauv compiled - Get compiled MAUV + mauv effective - Get effective MAUV + tpm get_mode - Get the current TPM mode. + tpm set_mode disabled - Set the TPM mode to DISABLED. + tpm set_mode tpm_spi - Set the TPM mode to TPM_SPI. + tpm set_mode spi_nor_mailbox - Set the TPM mode to SPI_NOR_MAILBOX. Global flags: --transport (default: "") @@ -161,8 +181,8 @@ Global flags: Maximum duration (in microseconds) to wait when SPI device indicates that it is busy --spidev_device_busy_wait_check_interval (default: "100") Interval duration (in microseconds) to wait before checking SPI device status again when it indicates that the device is busy - --quadmode (default: "auto") - Enable Quad SPI mode (auto|on|force|off). Default: auto + --spidev_mode (default: "single") + SPI mode toggles (single|dual|quad). --mtddev_path (default: "") The full MTD path of the RoT mailbox; for example '/dev/mtd0'. If unspecified, will attempt to detect the correct device automatically --mtddev_name (default: "hoth-mailbox") diff --git a/examples/htool.c b/examples/htool.c index 65a8c36..b11d34c 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -2010,6 +2010,9 @@ static const struct htool_param GLOBAL_FLAGS[] = { .default_value = "100", .desc = "Interval duration (in microseconds) to wait before checking SPI " "device status again when it indicates that the device is busy"}, + // TODO(michaelfield) : b/346345769 - enable spidev mode auto-detection + {HTOOL_FLAG_VALUE, .name = "spidev_mode", .default_value = "single", + .desc = "SPI mode toggles (single|dual|quad)."}, {HTOOL_FLAG_VALUE, .name = "mtddev_path", .default_value = "", .desc = "The full MTD path of the RoT mailbox; for example " "'/dev/mtd0'. If unspecified, will attempt to detect " diff --git a/examples/htool_spi.c b/examples/htool_spi.c index 198da09..26987b6 100644 --- a/examples/htool_spi.c +++ b/examples/htool_spi.c @@ -32,6 +32,7 @@ struct libhoth_device* htool_libhoth_spi_device(void) { int rv; const char* spidev_path_str; + const char* mode_str; uint32_t mailbox_location; bool atomic; uint32_t spidev_speed_hz; @@ -39,6 +40,7 @@ struct libhoth_device* htool_libhoth_spi_device(void) { uint32_t spidev_device_busy_wait_check_interval; rv = htool_get_param_string(htool_global_flags(), "spidev_path", &spidev_path_str) || + htool_get_param_string(htool_global_flags(), "spidev_mode", &mode_str) || htool_get_param_u32(htool_global_flags(), "mailbox_location", &mailbox_location) || htool_get_param_bool(htool_global_flags(), "spidev_atomic", &atomic) || @@ -54,6 +56,18 @@ struct libhoth_device* htool_libhoth_spi_device(void) { return NULL; } + enum libhoth_spi_mode operation_mode; + if (!strcmp(mode_str, "single")) { + operation_mode = LIBHOTH_SPI_MODE_SINGLE; + } else if (!strcmp(mode_str, "dual")) { + operation_mode = LIBHOTH_SPI_MODE_DUAL; + } else if (!strcmp(mode_str, "quad")) { + operation_mode = LIBHOTH_SPI_MODE_QUAD; + } else { + fprintf(stderr, "Invalid spidev mode: %s\n", mode_str); + return NULL; + } + if (strlen(spidev_path_str) <= 0) { fprintf(stderr, "Invalid spidev path: %s\n", spidev_path_str); return NULL; @@ -78,6 +92,7 @@ struct libhoth_device* htool_libhoth_spi_device(void) { .mailbox = mailbox_location, .atomic = atomic, .speed = spidev_speed_hz, + .operation_mode = operation_mode, .device_busy_wait_timeout = spidev_device_busy_wait_timeout, .device_busy_wait_check_interval = spidev_device_busy_wait_check_interval, .timeout_us = timeout_us, diff --git a/transports/libhoth_spi.c b/transports/libhoth_spi.c index 09070a8..b659629 100644 --- a/transports/libhoth_spi.c +++ b/transports/libhoth_spi.c @@ -35,6 +35,41 @@ #define DID_VID_ADDR 0xD40F00 +static uint8_t mode_to_nbits(enum libhoth_spi_mode mode) { + switch (mode) { + case LIBHOTH_SPI_MODE_DUAL: + return 2; + case LIBHOTH_SPI_MODE_QUAD: + return 4; + case LIBHOTH_SPI_MODE_SINGLE: + default: + return 1; + } +} + +static uint8_t mode_to_read_opcode(enum libhoth_spi_mode mode) { + switch (mode) { + case LIBHOTH_SPI_MODE_DUAL: + return SPI_NOR_OPCODE_DUAL_READ; + case LIBHOTH_SPI_MODE_QUAD: + return SPI_NOR_OPCODE_QUAD_READ; + case LIBHOTH_SPI_MODE_SINGLE: + default: + return SPI_NOR_OPCODE_SLOW_READ; + } +} + +static uint8_t mode_to_write_opcode(enum libhoth_spi_mode mode) { + switch (mode) { + case LIBHOTH_SPI_MODE_QUAD: + return SPI_NOR_OPCODE_QUAD_PAGE_PROGRAM; + case LIBHOTH_SPI_MODE_SINGLE: + case LIBHOTH_SPI_MODE_DUAL: + default: + return SPI_NOR_OPCODE_PAGE_PROGRAM; + } +} + static int spi_nor_address(uint8_t* buf, uint32_t address, bool address_mode_4b) { if (address_mode_4b) { @@ -128,7 +163,8 @@ static int spi_nor_write_enable(const int fd) { return LIBHOTH_OK; } -static int spi_nor_write(int fd, bool address_mode_4b, unsigned int address, +static int spi_nor_write(int fd, bool address_mode_4b, + enum libhoth_spi_mode mode, unsigned int address, const void* data, size_t data_len, uint32_t device_busy_wait_timeout, uint32_t device_busy_wait_check_interval) { @@ -147,7 +183,7 @@ static int spi_nor_write(int fd, bool address_mode_4b, unsigned int address, uint8_t rq_buf[5] = {0}; // 1 for command opcode, 4 (max) for address // Page Program OPCODE + Address - rq_buf[0] = SPI_NOR_OPCODE_PAGE_PROGRAM; + rq_buf[0] = mode_to_write_opcode(mode); int address_len = spi_nor_address(&rq_buf[1], address, address_mode_4b); xfer[0] = (struct spi_ioc_transfer){ .tx_buf = (unsigned long)rq_buf, @@ -163,6 +199,11 @@ static int spi_nor_write(int fd, bool address_mode_4b, unsigned int address, .len = chunk_send_size, }; + if (mode != LIBHOTH_SPI_MODE_SINGLE) { + uint8_t nbits = mode_to_nbits(mode); + xfer[1].tx_nbits = nbits; + } + status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer); if (status < 0) { return LIBHOTH_ERR_FAIL; @@ -180,7 +221,8 @@ static int spi_nor_write(int fd, bool address_mode_4b, unsigned int address, return LIBHOTH_OK; } -static int spi_nor_read(int fd, bool address_mode_4b, unsigned int address, +static int spi_nor_read(int fd, bool address_mode_4b, + enum libhoth_spi_mode mode, unsigned int address, void* data, size_t data_len) { if (fd < 0 || !data || !data_len) return LIBHOTH_ERR_INVALID_PARAMETER; @@ -188,7 +230,7 @@ static int spi_nor_read(int fd, bool address_mode_4b, unsigned int address, struct spi_ioc_transfer xfer[2] = {0}; // Read OPCODE and mailbox address - rd_request[0] = SPI_NOR_OPCODE_SLOW_READ; + rd_request[0] = mode_to_read_opcode(mode); int address_len = spi_nor_address(&rd_request[1], address, address_mode_4b); xfer[0] = (struct spi_ioc_transfer){ .tx_buf = (unsigned long)rd_request, @@ -201,6 +243,11 @@ static int spi_nor_read(int fd, bool address_mode_4b, unsigned int address, .len = data_len, }; + if (mode != LIBHOTH_SPI_MODE_SINGLE) { + uint8_t nbits = mode_to_nbits(mode); + xfer[1].rx_nbits = nbits; + } + int status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer); if (status < 0) { return LIBHOTH_ERR_FAIL; @@ -281,6 +328,7 @@ int libhoth_spi_open(const struct libhoth_spi_device_init_options* options, spi_dev->fd = fd; spi_dev->mailbox_address = options->mailbox; spi_dev->address_mode_4b = true; + spi_dev->mode = options->operation_mode; spi_dev->device_busy_wait_timeout = options->device_busy_wait_timeout; spi_dev->device_busy_wait_check_interval = options->device_busy_wait_check_interval; @@ -311,12 +359,52 @@ int libhoth_spi_open(const struct libhoth_spi_device_init_options* options, } } - if (options->mode) { - const uint8_t mode = (uint8_t)options->mode; - if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) { + uint32_t mode = options->mode; + if (ioctl(fd, SPI_IOC_RD_MODE32, &spi_dev->original_mode) < 0) { + status = LIBHOTH_ERR_FAIL; + goto err_out; + } + + // TODO(michaelfield): Readback the SFDP and check that quadmode is + // supported. If not, then we should fail unless the user explicitly + // requested to force quadmode. + + if (options->operation_mode == LIBHOTH_SPI_MODE_QUAD) { + // Quadmode spi needs to use 32-bit mode flags + mode |= (SPI_TX_QUAD | SPI_RX_QUAD); + + if (ioctl(fd, SPI_IOC_WR_MODE32, &mode) < 0) { status = LIBHOTH_ERR_FAIL; goto err_out; } + } else if (options->operation_mode == LIBHOTH_SPI_MODE_DUAL) { + // Dualmode spi needs to use 32-bit mode flags + mode |= (SPI_TX_DUAL | SPI_RX_DUAL); + + if (ioctl(fd, SPI_IOC_WR_MODE32, &mode) < 0) { + status = LIBHOTH_ERR_FAIL; + goto err_out; + } + } else { + // Set the mode anyways. + // There is a failure mode wherein a bad mode setting will stick. + // Even if mode is zero, we still want to write it. + if (ioctl(fd, SPI_IOC_WR_MODE32, &mode) < 0) { + status = LIBHOTH_ERR_FAIL; + goto err_out; + } + } + + // read back the mode, and verify that it is what we expect. + uint32_t read_mode; + if (ioctl(fd, SPI_IOC_RD_MODE32, &read_mode) < 0) { + status = LIBHOTH_ERR_FAIL; + goto err_out; + } + + if (read_mode != mode) { + status = LIBHOTH_ERR_FAIL; + goto err_out; } if (options->speed) { @@ -353,7 +441,7 @@ int libhoth_spi_send_request(struct libhoth_device* dev, const void* request, struct libhoth_spi_device* spi_dev = (struct libhoth_spi_device*)dev->user_ctx; - return spi_nor_write(spi_dev->fd, spi_dev->address_mode_4b, + return spi_nor_write(spi_dev->fd, spi_dev->address_mode_4b, spi_dev->mode, spi_dev->mailbox_address, request, request_size, spi_dev->device_busy_wait_timeout, spi_dev->device_busy_wait_check_interval); @@ -377,7 +465,7 @@ int libhoth_spi_receive_response(struct libhoth_device* dev, void* response, (struct libhoth_spi_device*)dev->user_ctx; // Read Header From Mailbox - status = spi_nor_read(spi_dev->fd, spi_dev->address_mode_4b, + status = spi_nor_read(spi_dev->fd, spi_dev->address_mode_4b, spi_dev->mode, spi_dev->mailbox_address, response, sizeof(struct hoth_host_response)); if (status != LIBHOTH_OK) { @@ -397,7 +485,7 @@ int libhoth_spi_receive_response(struct libhoth_device* dev, void* response, if (host_response.data_len > 0) { // Read remainder of data based on header length uint8_t* const data_start = (uint8_t*)response + total_bytes; - status = spi_nor_read(spi_dev->fd, spi_dev->address_mode_4b, + status = spi_nor_read(spi_dev->fd, spi_dev->address_mode_4b, spi_dev->mode, spi_dev->mailbox_address + total_bytes, data_start, host_response.data_len); if (status != LIBHOTH_OK) { @@ -467,7 +555,7 @@ int libhoth_spi_send_and_receive_response(struct libhoth_device* dev, // Page Program OPCODE + Mailbox Address uint8_t pp_buf[5] = {0}; - pp_buf[0] = SPI_NOR_OPCODE_PAGE_PROGRAM; + pp_buf[0] = mode_to_write_opcode(spi_dev->mode); int address_len = spi_nor_address(&pp_buf[1], address, address_mode_4b); xfer[1] = (struct spi_ioc_transfer){ .tx_buf = (unsigned long)pp_buf, @@ -481,11 +569,15 @@ int libhoth_spi_send_and_receive_response(struct libhoth_device* dev, .cs_change = 1, }; + if (spi_dev->mode != LIBHOTH_SPI_MODE_SINGLE) { + uint8_t nbits = mode_to_nbits(spi_dev->mode); + xfer[2].tx_nbits = nbits; + } // Wait for status register is handled by the spidev driver. // Read opcode + Mailbox Address uint8_t rd_buf[5] = {0}; - rd_buf[0] = SPI_NOR_OPCODE_SLOW_READ; + rd_buf[0] = mode_to_read_opcode(spi_dev->mode); address_len = spi_nor_address(&rd_buf[1], address, address_mode_4b); xfer[3] = (struct spi_ioc_transfer){ .tx_buf = (unsigned long)rd_buf, @@ -498,6 +590,11 @@ int libhoth_spi_send_and_receive_response(struct libhoth_device* dev, .len = max_response_size, }; + if (spi_dev->mode != LIBHOTH_SPI_MODE_SINGLE) { + uint8_t nbits = mode_to_nbits(spi_dev->mode); + xfer[4].rx_nbits = nbits; + } + int rc = LIBHOTH_OK; int status = ioctl(spi_dev->fd, SPI_IOC_MESSAGE(5), xfer); if (status < 0) { @@ -564,6 +661,12 @@ int libhoth_spi_close(struct libhoth_device* dev) { struct libhoth_spi_device* spi_dev = (struct libhoth_spi_device*)dev->user_ctx; + // Mode settings can be sticky! Restore the original mode to ensure any bad + // modesetting does not presist across processes. + if (ioctl(spi_dev->fd, SPI_IOC_WR_MODE32, &spi_dev->original_mode) < 0) { + // We can't do much here, but we should at least close the fd. + perror("Failed to restore SPI mode"); + } close(spi_dev->fd); free(dev->user_ctx); return LIBHOTH_OK; diff --git a/transports/libhoth_spi.h b/transports/libhoth_spi.h index f0dac8a..1840d7f 100644 --- a/transports/libhoth_spi.h +++ b/transports/libhoth_spi.h @@ -25,6 +25,12 @@ extern "C" { struct libhoth_device; +enum libhoth_spi_mode { + LIBHOTH_SPI_MODE_SINGLE = 0, + LIBHOTH_SPI_MODE_DUAL, + LIBHOTH_SPI_MODE_QUAD, +}; + struct libhoth_spi_device_init_options { // The device filepath to open const char* path; @@ -34,6 +40,7 @@ struct libhoth_spi_device_init_options { int mode; int speed; int atomic; + enum libhoth_spi_mode operation_mode; uint32_t device_busy_wait_timeout; uint32_t device_busy_wait_check_interval; uint32_t timeout_us; @@ -43,6 +50,8 @@ struct libhoth_spi_device { int fd; unsigned int mailbox_address; bool address_mode_4b; + enum libhoth_spi_mode mode; + uint32_t original_mode; void* buffered_request; size_t buffered_request_size; @@ -55,7 +64,10 @@ enum { SPI_NOR_OPCODE_READ_STATUS = 0x05, SPI_NOR_OPCODE_WRITE_ENABLE = 0x06, SPI_NOR_OPCODE_PAGE_PROGRAM = 0x02, + SPI_NOR_OPCODE_QUAD_PAGE_PROGRAM = 0x38, SPI_NOR_OPCODE_SLOW_READ = 0x03, + SPI_NOR_OPCODE_DUAL_READ = 0x3B, + SPI_NOR_OPCODE_QUAD_READ = 0x6B, SPI_NOR_FLASH_PAGE_SIZE = 256, // in bytes };