diff --git a/README.md b/README.md index b01372a..a8962d5 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,60 @@ This repository contains a libraries and example programs for interacting with a hoth-class root of trust. -# Build via meson +## Prerequisites +To build libhoth, you will need `meson`, `ninja`, and several development libraries. On Ubuntu/Debian, you can install them with: + +```bash +sudo apt-get update +sudo apt-get install meson libusb-1.0-0-dev libsystemd-dev libcap-dev libgtest-dev +``` + +## Build via Meson + +```bash +meson build +ninja -C build +./build/examples/htool +``` + +### Running Tests (Meson) + +```bash +meson test -C build +``` + +### Optional: D-Bus Backend (Meson) + +To enable the D-Bus backend: + +```bash +meson build -Ddbus_backend=true +ninja -C build ``` -$ meson build -$ (cd build && ninja) -$ build/examples/htool + +## Build via Bazel + +```bash +bazel build //... +./bazel-bin/examples/htool ``` -# Build via Bazel +### Running Tests (Bazel) +```bash +bazel test //... ``` -$ bazel build ... -$ bazel-bin/examples/htool + +### Optional: D-Bus Backend (Bazel) + +To enable the D-Bus backend: + +```bash +bazel build --define dbus_backend=true //examples:htool ``` -# examples/htool +## examples/htool htool is a command line tool for performing basic actions against a hoth RoT. @@ -31,34 +69,41 @@ Available subcommands: (append --help to subcommand for details) show chipinfo - Return details about this specific RoT chip. spi read - Read from SPI flash into a file spi update - Write a file to SPI flash (erase + program). - 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 - console - Open a console for communicating with the RoT or devices attached to the RoT. - payload status - Show payload status + spi passthrough on/off - Enable/Disable SPS->SPI passthrough. + target reset on/off/pulse - Control the target device reset pin. + console - Open a console for communicating with the RoT. + payload status/update/read/info - Manage Titan payload images. + dfu update/check - Directly install or verify PIE-RoT firmware updates. flash_spi_info - Get SPI NOR flash info. - statistics - Show statistics + statistics - Show statistics. get_panic - Retrieve or clear the stored panic record. - authz_record read - Read the current authorization record - authz_record erase - Erase the current authorization record - authz_record build - Build an empty authorization record for the chip - authz_record set - Upload an authorization record to the chip + authz_record read/erase/build/set - Manage authorization records. + i2c detect/read/write/muxctrl - Perform I2C transactions or control I2C mux. + rot_usb muxctrl - Control USB mux select. + jtag read_idcode/test_bypass/verify_pld - Perform JTAG operations. + storage read/write/delete - Manage controlled storage. Global flags: --transport (default: "") - The method of connecting to the RoT; for example 'spidev'/'usb'/'mtd' + The method of connecting to the RoT; for example 'spidev'/'usb'/'mtd'/'dbus' --usb_loc (default: "") The full bus-portlist location of the RoT; for example '1-10.4.4.1'. --usb_product (default: "") If there is a single USB RoT with this substring in the USB product string, use it. --spidev_path (default: "") The full SPIDEV path of the RoT; for example '/dev/spidev0.0'. + --spidev_atomic (default: "false") + If true, force spidev to use a single atomic ioctl. --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 + The full MTD path of the RoT mailbox; for example '/dev/mtd0'. --mtddev_name (default: "hoth-mailbox") - The MTD name of the RoT mailbox; for example 'hoth-mailbox'. + The MTD name of the RoT mailbox. --mailbox_location (default: "0") - The location of the mailbox on the RoT, for 'spidev' or 'mtd' transports; for example '0x900000'. + The location of the mailbox on the RoT. + --dbus_hoth_id (default: "") + The hoth ID associated with the RoT's hothd service. + --connect_timeout (default: "10s") + Maximum duration to retry opening a busy transport. ``` ``` @@ -72,6 +117,7 @@ Usage: spi update -s --start (default: "0") start address -v --verify (default: "true") + -a --address_mode (default: "3B/4B") $ echo "Hello world" > /tmp/hello $ htool spi update -s 0x5000 /tmp/hello @@ -85,6 +131,7 @@ Usage: spi read -s --start (default: "0") start address -n --length + -a --address_mode (default: "3B/4B") $ htool spi read -s 0x5000 -n 16 /dev/stdout | hexdump -C Reading: 100% - 0kB / 0kB 514 kB/sec; 0.0 s remaining @@ -101,5 +148,10 @@ Usage: console Drive the UART's TX net even if Hoth isn't sure whether some other device else is driving it. Only use this option if you are CERTAIN there is no debugging hardware attached. -h --history (default: "false") Include data bufferred before the current time. + -n --onlcr (default: "false") + Translate received "\n" to "\r\n". + -b --baud_rate (default: "0") + -s --snapshot (default: "false") + Print a snapshot of most recent console messages. ``` diff --git a/examples/htool.c b/examples/htool.c index 59a9881..84a5abc 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -1846,6 +1846,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 };