diff --git a/.github/workflows/make-test-swtpm.yml b/.github/workflows/make-test-swtpm.yml index a2e4bfa0..699f3638 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -310,10 +310,13 @@ jobs: wolftpm_config: --disable-provisioning --disable-fwtpm needs_swtpm: false - # SWTPM UART transport (compile-only; no UART peer in CI) + # SWTPM UART transport. No UART peer in CI, so the receive tests + # use a pseudo-terminal. make check is not usable here because + # unit.test needs a TPM. - name: swtpm-uart wolftpm_config: --enable-swtpm=uart --disable-fwtpm needs_swtpm: false + test_command: "./tests/swtpm_uart_unit.test" # Symmetric encryption - name: symmetric diff --git a/.gitignore b/.gitignore index 4220a7da..4a52fe06 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,8 @@ tests/fwtpm_unit.log tests/fwtpm_unit.test tests/fwtpm_hal_unit.test tests/fwtpm_hal_unit.log +tests/swtpm_uart_unit.test +tests/swtpm_uart_unit.log tests/fwtpm_check.sh.log tests/packet_auth.test tests/packet_auth.log diff --git a/configure.ac b/configure.ac index c90bdc20..8778b60c 100644 --- a/configure.ac +++ b/configure.ac @@ -1103,6 +1103,7 @@ AM_CONDITIONAL([BUILD_INFINEON], [test "x$ENABLED_INFINEON" != "xno"]) AM_CONDITIONAL([BUILD_INFINEON_SPDM], [test "x$BUILD_SPDM_INFINEON" = "xyes"]) AM_CONDITIONAL([BUILD_DEVTPM], [test "x$ENABLED_DEVTPM" = "xyes"]) AM_CONDITIONAL([BUILD_SWTPM], [test "x$ENABLED_SWTPM" = "xyes" || test "x$ENABLED_SWTPM" = "xuart"]) +AM_CONDITIONAL([BUILD_SWTPM_UART], [test "x$ENABLED_SWTPM" = "xuart"]) AM_CONDITIONAL([BUILD_WINAPI], [test "x$ENABLED_WINAPI" = "xyes"]) AM_CONDITIONAL([BUILD_NUVOTON], [test "x$ENABLED_NUVOTON" = "xyes"]) AM_CONDITIONAL([BUILD_NATIONS], [test "x$ENABLED_NATIONS" = "xyes"]) diff --git a/docs/system-interfaces.md b/docs/system-interfaces.md index 1b1a78fe..5638ffb2 100644 --- a/docs/system-interfaces.md +++ b/docs/system-interfaces.md @@ -54,8 +54,10 @@ Build options: * `WOLFTPM_SWTPM_UART`: use the UART serial transport (set automatically by `--enable-swtpm=uart`) * `TPM2_SWTPM_HOST`: the serial device path (default is `/dev/ttyACM0` on Linux and `/dev/cu.usbmodem` on macOS). It can be overridden at runtime with the `TPM2_SWTPM_HOST` environment variable. * `TPM2_SWTPM_PORT`: the baud rate (default is 115200) +* `WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS`: cumulative wall-clock timeout for a single receive (default is 60) +* `WOLFTPM_SWTPM_UART_VTIME`: per-read termios `VTIME`, in tenths of a second (default is 200, so 20 seconds) -The UART transport uses the same mssim protocol as the socket transport. The serial port is configured as 8N1 raw mode with no flow control. Like the socket transport, the serial port file descriptor is kept open across commands (no reconnect per command). Both transports close the connection during `wolfTPM2_Cleanup`. On the socket transport, any transmit or receive failure also closes the connection so the next command reconnects. The UART transport closes only when the per-command `TPM_SESSION_END` write fails. +The UART transport uses the same mssim protocol as the socket transport. The serial port is configured as 8N1 raw mode with no flow control. A receive waits for the device with `poll()` until `WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS` elapses, so a command taking longer than one `VTIME` period, such as RSA key generation on an MCU, still completes, while a device hangup fails immediately instead of waiting out the timeout. Like the socket transport, the serial port file descriptor is kept open across commands (no reconnect per command). Both transports close the connection during `wolfTPM2_Cleanup`. On the socket transport, any transmit or receive failure also closes the connection so the next command reconnects. The UART transport closes only when the per-command `TPM_SESSION_END` write fails. #### Security note: environment variable override diff --git a/src/tpm2_swtpm.c b/src/tpm2_swtpm.c index 1022e217..bb4a5169 100644 --- a/src/tpm2_swtpm.c +++ b/src/tpm2_swtpm.c @@ -68,16 +68,23 @@ #include #include #include +#include #ifndef O_CLOEXEC #define O_CLOEXEC 0 #endif #ifndef O_NOFOLLOW #define O_NOFOLLOW 0 #endif -/* Cumulative wall-clock timeout for SwTpmReceive. Defends against trickle - * senders that repeatedly reset the per-read VMIN/VTIME timer. */ +/* Cumulative wall-clock timeout for SwTpmReceive. Bounds the total wait for + * a response, which can exceed one VTIME period (e.g. RSA key generation on + * an MCU), and defends against trickle senders that repeatedly reset the + * per-read VMIN/VTIME timer. */ #ifndef WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS -#define WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS 30 +#define WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS 60 +#endif +/* Per-read timeout in tenths of a second, used with VMIN=0 */ +#ifndef WOLFTPM_SWTPM_UART_VTIME +#define WOLFTPM_SWTPM_UART_VTIME 200 #endif #endif @@ -186,6 +193,78 @@ static TPM_RC SwTpmTransmit(TPM2_CTX* ctx, const void* buffer, ssize_t bufSz) return rc; } +#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC) +/* Monotonic milliseconds. Returns 0 on success. */ +static int SwTpmUartNowMs(uint64_t* nowMs) +{ + int rc = -1; + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + *nowMs = ((uint64_t)ts.tv_sec * 1000) + + ((uint64_t)ts.tv_nsec / 1000000); + rc = 0; + } + return rc; +} + +/* Wait for the UART to become readable, bounded by the time left on the + * cumulative receive deadline. A hung-up TTY returns a zero-byte read with + * no delay, so a hangup is reported rather than retried. */ +static TPM_RC SwTpmUartWait(int fd, uint64_t deadlineMs) +{ + TPM_RC rc = TPM_RC_FAILURE; + struct pollfd pfd; + uint64_t nowMs = 0; + int prc; + + pfd.fd = fd; + pfd.events = POLLIN; + + for (;;) { + if (SwTpmUartNowMs(&nowMs) != 0) { + break; + } + if (nowMs >= deadlineMs) { + #ifdef DEBUG_WOLFTPM + printf("SwTpmReceive: cumulative UART timeout after %ds\n", + WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS); + #endif + break; + } + pfd.revents = 0; + prc = poll(&pfd, 1, (int)(deadlineMs - nowMs)); + if (prc < 0) { + if (errno == EINTR) { + continue; + } + #ifdef DEBUG_WOLFTPM + printf("Failed to poll TPM UART %d, got errno %d = %s\n", + fd, errno, strerror(errno)); + #endif + break; + } + if (prc == 0) { + #ifdef DEBUG_WOLFTPM + printf("SwTpmReceive: cumulative UART timeout after %ds\n", + WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS); + #endif + break; + } + if ((pfd.revents & POLLIN) != 0) { + rc = TPM_RC_SUCCESS; + break; + } + #ifdef DEBUG_WOLFTPM + printf("TPM UART hangup, poll events 0x%x\n", pfd.revents); + #endif + break; + } + + return rc; +} +#endif /* WOLFTPM_SWTPM_UART && CLOCK_MONOTONIC */ + static TPM_RC SwTpmReceive(TPM2_CTX* ctx, void* buffer, size_t rxSz) { TPM_RC rc = TPM_RC_SUCCESS; @@ -193,18 +272,34 @@ static TPM_RC SwTpmReceive(TPM2_CTX* ctx, void* buffer, size_t rxSz) size_t bytes_remaining = rxSz; char* ptr = (char*)buffer; #if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC) - struct timespec start, now; - int haveStart = 0; - if (clock_gettime(CLOCK_MONOTONIC, &start) == 0) { - haveStart = 1; - } + uint64_t nowMs = 0; + uint64_t deadlineMs = 0; + int haveDeadline = 0; #endif if (ctx == NULL || !SWTPM_FD_IS_VALID(ctx->tcpCtx.fd) || buffer == NULL) { return BAD_FUNC_ARG; } +#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC) + if (SwTpmUartNowMs(&nowMs) == 0) { + deadlineMs = nowMs + + ((uint64_t)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS * 1000); + haveDeadline = 1; + } +#endif + while (bytes_remaining > 0) { +#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC) + /* Wait within the cumulative deadline, so a response that takes + * longer than one VTIME period is not treated as EOF */ + if (haveDeadline) { + rc = SwTpmUartWait(ctx->tcpCtx.fd, deadlineMs); + if (rc != TPM_RC_SUCCESS) { + break; + } + } +#endif wrc = SWTPM_READ(ctx->tcpCtx.fd, ptr, bytes_remaining); if (wrc < 0) { if (SWTPM_SHOULD_RETRY()) { @@ -233,22 +328,6 @@ static TPM_RC SwTpmReceive(TPM2_CTX* ctx, void* buffer, size_t rxSz) wrc, bytes_remaining); #endif -#if defined(WOLFTPM_SWTPM_UART) && defined(CLOCK_MONOTONIC) - /* Enforce cumulative timeout so a trickle sender cannot reset - * VMIN/VTIME indefinitely. */ - if (haveStart && bytes_remaining > 0 && - clock_gettime(CLOCK_MONOTONIC, &now) == 0 && - (now.tv_sec - start.tv_sec) > - WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS) { - #ifdef DEBUG_WOLFTPM - printf("SwTpmReceive: cumulative UART timeout after %lds " - "(%zu bytes remaining)\n", - (long)(now.tv_sec - start.tv_sec), bytes_remaining); - #endif - rc = TPM_RC_FAILURE; - break; - } -#endif } return rc; @@ -359,12 +438,12 @@ static TPM_RC SwTpmConnect(TPM2_CTX* ctx, const char* host, const char* port) tty.c_oflag &= ~OPOST; tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); - /* Read with overall timeout. - * RSA key generation on embedded targets can take 10+ seconds, - * so use a generous timeout. With VMIN=0 and VTIME>0, read() - * returns after the timeout even if no byte is received. */ + /* Per-read timeout. With VMIN=0 and VTIME>0, read() returns after the + * timeout even if no byte is received; SwTpmReceive polls up to + * WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS, since RSA key generation on + * embedded targets can take longer than one period. */ tty.c_cc[VMIN] = 0; /* allow timeout without requiring first byte */ - tty.c_cc[VTIME] = 200; /* 20 second timeout (tenths of seconds) */ + tty.c_cc[VTIME] = WOLFTPM_SWTPM_UART_VTIME; if (tcsetattr(fd, TCSANOW, &tty) != 0) { close(fd); diff --git a/tests/include.am b/tests/include.am index a9c4cddb..f1833030 100644 --- a/tests/include.am +++ b/tests/include.am @@ -38,6 +38,24 @@ tests_fwtpm_unit_test_CPPFLAGS = \ tests_fwtpm_unit_test_LDADD = $(LIB_STATIC_ADD) endif +# SWTPM UART receive tests. Driven over a pseudo-terminal, so no hardware or +# simulator is needed, but make check cannot run in a UART configuration +# (unit.test needs a TPM), so the swtpm-uart CI job runs this directly. +# tpm2_swtpm.c is compiled in because TPM2_SWTPM_SendCommand is WOLFTPM_LOCAL. +if BUILD_SWTPM_UART +noinst_PROGRAMS += tests/swtpm_uart_unit.test +tests_swtpm_uart_unit_test_SOURCES = \ + tests/swtpm_uart_unit_tests.c \ + src/tpm2_swtpm.c \ + src/tpm2_packet.c \ + src/tpm2_util.c +# Shorten the deadline and the per-read period so the timeout case is quick +tests_swtpm_uart_unit_test_CFLAGS = \ + -DWOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS=3 \ + -DWOLFTPM_SWTPM_UART_VTIME=1 $(AM_CFLAGS) +tests_swtpm_uart_unit_test_LDADD = $(LIB_STATIC_ADD) +endif + if BUILD_FWTPM_TIS check_PROGRAMS += tests/fwtpm_hal_unit.test noinst_PROGRAMS += tests/fwtpm_hal_unit.test diff --git a/tests/swtpm_uart_unit_tests.c b/tests/swtpm_uart_unit_tests.c new file mode 100644 index 00000000..14257a08 --- /dev/null +++ b/tests/swtpm_uart_unit_tests.c @@ -0,0 +1,333 @@ +/* swtpm_uart_unit_tests.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM 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 3 of the License, or + * (at your option) any later version. + * + * wolfTPM 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Tests for the SWTPM UART receive path. A pseudo-terminal stands in for the + * serial device and a forked child plays the TPM, so neither hardware nor a + * simulator is required. tpm2_swtpm.c is compiled into this binary because + * TPM2_SWTPM_SendCommand is WOLFTPM_LOCAL. + * + * tests/include.am shortens the receive deadline and the per-read VTIME, so + * the timeout case does not take the production 60 seconds. */ + +/* posix_openpt, grantpt, unlockpt and ptsname. Other platforms declare them + * without a feature macro, and on macOS _XOPEN_SOURCE hides usleep. */ +#if defined(__linux__) + #ifndef _XOPEN_SOURCE + #define _XOPEN_SOURCE 600 + #endif + #ifndef _DEFAULT_SOURCE + #define _DEFAULT_SOURCE + #endif +#endif + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#if defined(WOLFTPM_SWTPM) && defined(WOLFTPM_SWTPM_UART) && \ + !defined(_WIN32) && defined(CLOCK_MONOTONIC) + +#include +#include +#include +#include +#include + +/* Kept in step with the defaults in src/tpm2_swtpm.c, for a build that does + * not override them. tests/include.am shortens both. */ +#ifndef WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS + #define WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS 60 +#endif +#ifndef WOLFTPM_SWTPM_UART_VTIME + #define WOLFTPM_SWTPM_UART_VTIME 200 +#endif + +/* How the child behaves once it has read the command */ +#define TEST_RSP_DELAYED 1 /* answer after several VTIME periods */ +#define TEST_RSP_SILENT 2 /* hold the line open and say nothing */ +#define TEST_RSP_HANGUP 3 /* drop the peer */ + +/* mssim framing: TPM_SEND_COMMAND, locality, command size */ +#define TEST_CMD_HDR_SZ 9 +#define TEST_CMD_SZ 12 +#define TEST_RSP_SZ 10 +/* response size, response, ack */ +#define TEST_RSP_WIRE_SZ (4 + TEST_RSP_SZ + 4) +/* Well inside the deadline, and many VTIME periods at the test's VTIME */ +#define TEST_DELAY_MS 1500 +/* A receive that waits on poll() costs no measurable CPU. Retrying a read + * instead burns a whole core, so this is what catches a busy loop. */ +#define TEST_MAX_CPU_SECS 0.5 + +static int gFailures = 0; + +static double TestNowSec(void) +{ + struct timespec ts; + double now = 0; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + now = (double)ts.tv_sec + ((double)ts.tv_nsec / 1000000000.0); + } + return now; +} + +/* CPU seconds consumed by this process so far */ +static double TestCpuSec(void) +{ + struct rusage ru; + double cpu = 0; + + if (getrusage(RUSAGE_SELF, &ru) == 0) { + cpu = (double)ru.ru_utime.tv_sec + + ((double)ru.ru_utime.tv_usec / 1000000.0) + + (double)ru.ru_stime.tv_sec + + ((double)ru.ru_stime.tv_usec / 1000000.0); + } + return cpu; +} + +/* Read exactly len bytes. Returns 0 on success. */ +static int TestReadFull(int fd, byte* buf, size_t len) +{ + ssize_t got; + int rc = 0; + + while (len > 0) { + got = read(fd, buf, len); + if (got <= 0) { + rc = -1; + break; + } + buf += got; + len -= (size_t)got; + } + return rc; +} + +/* Runs in the forked child, which is the only holder of the master side. */ +static void TestServeTpm(int master, int behavior) +{ + byte hdr[TEST_CMD_HDR_SZ]; + byte cmd[TEST_CMD_SZ]; + byte rsp[TEST_RSP_WIRE_SZ]; + UINT32 cmdSz; + + if (TestReadFull(master, hdr, sizeof(hdr)) != 0) { + _exit(1); + } + cmdSz = ((UINT32)hdr[5] << 24) | ((UINT32)hdr[6] << 16) | + ((UINT32)hdr[7] << 8) | (UINT32)hdr[8]; + if (cmdSz != TEST_CMD_SZ || TestReadFull(master, cmd, cmdSz) != 0) { + _exit(1); + } + + if (behavior == TEST_RSP_HANGUP) { + close(master); + _exit(0); + } + if (behavior == TEST_RSP_SILENT) { + /* Outlast the deadline without closing, so the client has to time + * out rather than see a hangup. */ + sleep(WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS + 2); + _exit(0); + } + + /* TEST_RSP_DELAYED: answer after far more than one VTIME period */ + usleep(TEST_DELAY_MS * 1000); + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[3] = TEST_RSP_SZ; /* response size */ + rsp[4] = 0x80; rsp[5] = 0x01; /* TPM_ST_NO_SESSIONS */ + rsp[9] = TEST_RSP_SZ; /* size inside the response header */ + /* the remaining bytes are TPM_RC_SUCCESS and a zero ack */ + if (write(master, rsp, sizeof(rsp)) != (ssize_t)sizeof(rsp)) { + _exit(1); + } + /* Stay open for the TPM_SESSION_END that follows the response */ + (void)TestReadFull(master, hdr, 4); + _exit(0); +} + +/* Drive one command over a pty and report what the client did. Returns the + * result code, with the wall-clock time in elapsed and the CPU time the call + * cost in cpu. */ +static int TestSendOverPty(int behavior, double* elapsed, double* cpu) +{ + TPM2_CTX ctx; + TPM2_Packet packet; + byte buf[256]; + char devPath[64]; + const char* ptsPath; + double start; + double cpuStart; + pid_t pid; + int master; + int status = 0; + int rc; + + *elapsed = 0; + *cpu = 0; + + master = posix_openpt(O_RDWR | O_NOCTTY); + if (master < 0 || grantpt(master) != 0 || unlockpt(master) != 0) { + printf("pty setup failed\n"); + return TPM_RC_FAILURE; + } + ptsPath = ptsname(master); + if (ptsPath == NULL) { + printf("ptsname failed\n"); + close(master); + return TPM_RC_FAILURE; + } + strncpy(devPath, ptsPath, sizeof(devPath) - 1); + devPath[sizeof(devPath) - 1] = '\0'; + + pid = fork(); + if (pid < 0) { + printf("fork failed\n"); + close(master); + return TPM_RC_FAILURE; + } + if (pid == 0) { + TestServeTpm(master, behavior); + _exit(1); + } + + /* The child now owns the master side, so closing it here means a child + * exit really does hang the line up. */ + close(master); + setenv("TPM2_SWTPM_HOST", devPath, 1); + + XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.tcpCtx.fd = -1; + XMEMSET(&packet, 0, sizeof(packet)); + XMEMSET(buf, 0, sizeof(buf)); + buf[0] = 0x80; buf[1] = 0x01; /* TPM_ST_NO_SESSIONS */ + buf[5] = TEST_CMD_SZ; + packet.buf = buf; + packet.size = (int)sizeof(buf); + packet.pos = TEST_CMD_SZ; + + start = TestNowSec(); + cpuStart = TestCpuSec(); + rc = TPM2_SWTPM_SendCommand(&ctx, &packet); + *elapsed = TestNowSec() - start; + *cpu = TestCpuSec() - cpuStart; + + TPM2_SwtpmClose(&ctx); + /* The silent child is still sleeping out its timer */ + kill(pid, SIGKILL); + waitpid(pid, &status, 0); + + return rc; +} + +static void TestCheck(const char* name, int pass, double elapsed, double cpu) +{ + printf("Test SWTPM UART: %-40s %s (%.2fs wall, %.2fs cpu)\n", name, + pass ? "Passed" : "FAILED", elapsed, cpu); + if (!pass) { + gFailures++; + } +} + +/* A response arriving many VTIME periods late must still be accepted. Before + * the poll based deadline, the first zero byte read was treated as EOF and + * any command slower than one period failed. */ +static void test_SwTpmUart_LateResponse(void) +{ + double elapsed = 0; + double cpu = 0; + int rc = TestSendOverPty(TEST_RSP_DELAYED, &elapsed, &cpu); + + TestCheck("late response accepted", + rc == TPM_RC_SUCCESS && + elapsed >= (((double)TEST_DELAY_MS / 1000.0) - 0.1) && + elapsed < (double)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS && + cpu < TEST_MAX_CPU_SECS, + elapsed, cpu); +} + +/* Silence must fail at the cumulative deadline, not at some multiple of it. */ +static void test_SwTpmUart_Timeout(void) +{ + double elapsed = 0; + double cpu = 0; + int rc = TestSendOverPty(TEST_RSP_SILENT, &elapsed, &cpu); + + TestCheck("silence times out at the deadline", + rc != TPM_RC_SUCCESS && + elapsed >= ((double)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS - 0.5) && + elapsed <= ((double)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS + 1.0) && + cpu < TEST_MAX_CPU_SECS, + elapsed, cpu); +} + +/* A hung up line returns a zero byte read with no delay. Retrying that read + * spins on the CPU until the deadline, so it has to fail promptly instead. */ +static void test_SwTpmUart_Hangup(void) +{ + double elapsed = 0; + double cpu = 0; + int rc = TestSendOverPty(TEST_RSP_HANGUP, &elapsed, &cpu); + + TestCheck("hangup fails promptly", + rc != TPM_RC_SUCCESS && + elapsed < ((double)WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS / 2.0) && + cpu < TEST_MAX_CPU_SECS, + elapsed, cpu); +} + +int main(void) +{ + printf("SWTPM UART tests, deadline %d secs, VTIME %d\n", + WOLFTPM_SWTPM_UART_RX_TIMEOUT_SECS, WOLFTPM_SWTPM_UART_VTIME); + + test_SwTpmUart_LateResponse(); + test_SwTpmUart_Timeout(); + test_SwTpmUart_Hangup(); + + if (gFailures != 0) { + printf("SWTPM UART tests: %d failed\n", gFailures); + return 1; + } + printf("SWTPM UART tests: all passed\n"); + return 0; +} + +#else /* !WOLFTPM_SWTPM_UART */ + +int main(void) +{ + printf("SWTPM UART tests skipped\n"); + return 0; +} + +#endif