diff options
-rw-r--r-- | test/tpm_test/Makefile | 56 | ||||
-rw-r--r-- | test/tpm_test/crypto_test.xml | 112 | ||||
-rw-r--r-- | test/tpm_test/ftdi_spi_tpm.c | 448 | ||||
-rw-r--r-- | test/tpm_test/ftdi_spi_tpm.h | 25 | ||||
-rw-r--r-- | test/tpm_test/ftdi_spi_tpm.i | 50 | ||||
-rw-r--r-- | test/tpm_test/mpsse.c | 723 | ||||
-rw-r--r-- | test/tpm_test/mpsse.h | 45 | ||||
-rw-r--r-- | test/tpm_test/support.c | 218 | ||||
-rw-r--r-- | test/tpm_test/support.h | 87 | ||||
-rwxr-xr-x | test/tpm_test/tpmtest.py | 364 |
10 files changed, 2128 insertions, 0 deletions
diff --git a/test/tpm_test/Makefile b/test/tpm_test/Makefile new file mode 100644 index 0000000000..e68c0f1c76 --- /dev/null +++ b/test/tpm_test/Makefile @@ -0,0 +1,56 @@ +# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +ifeq ($(V),) +Q := @ +else +Q := +endif + +obj = ../../build/tpm_test +src = . +SWIG = /usr/bin/swig + +CFLAGS = -fPIC +CFLAGS += -I /usr/include/python2.7 +CFLAGS += -DLIBFTDI1=1 +CFLAGS += -c + +TARGET = ftdi_spi_tpm +.PRECIOUS: $(obj)/ftdi_spi_tpm_wrap.c + +$(obj)/_$(TARGET).so: + +OBJS = $(obj)/$(TARGET).o $(obj)/$(TARGET)_wrap.o $(obj)/mpsse.o \ + $(obj)/support.o + +DEPS := $(OBJS:.o=.o.d) + +$(OBJS): | $(obj) + +$(obj)/%.o: $(obj)/%.c + @echo " CC $(notdir $@)" + $(Q)gcc $(CFLAGS) -o $@ $< + +$(obj)/%.o: $(src)/%.c + @echo " CC $(notdir $@)" + $(Q)gcc $(CFLAGS) -Wall -Werror -MMD -MF $@.d -o $@ $< + +$(obj)/_$(TARGET).so: $(OBJS) $(obj)/$(TARGET).py + @echo " LD $(notdir $@)" + $(Q)rm -f $@ + $(Q)ld -shared $(OBJS) -lftdi1 -o $@ + +$(obj)/%_wrap.c: $(src)/%.i + @echo " SWIG $(notdir $@)" + $(Q)swig -python -outdir $(obj) -o $@ $< + +clean: + @rm -rf $(obj)/ + +$(obj): + @echo " MKDIR $(obj)" + $(Q)mkdir -p $(obj) + +-include $(DEPS) diff --git a/test/tpm_test/crypto_test.xml b/test/tpm_test/crypto_test.xml new file mode 100644 index 0000000000..6de62d8bbb --- /dev/null +++ b/test/tpm_test/crypto_test.xml @@ -0,0 +1,112 @@ +<?xml version="1.0"?> +<!-- +Copyright 2015 The Chromium OS Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. + +This file describes test vectors for various encryption schemes. + +Each description is encapsulated in a 'crypto_test' element. This element must +have the name property set. The name starts witht the encryption scheme's name +(say AES or DES), delimited by a colon, and followed by a three character +encryption submode, if necessary (say ECB for AES). + +The rest of the attributes are self explanatory. The default format for the +clear_text element is ASCII, for the rest - hex. This default could be +overridded using the 'format' property. + +The ascii strings are stripped of leading and trailing whitespace and then +joined using space as a separator. Whitespace in hes strings is ignored. + +Hex values are interpreted as a set of 4 byte entities in network byte order. +Many of the crypto_test elements were borrowed from NIST test vectors. +--> +<crypto_tests> + <crypto_test name="AES:ECB common"> + <clear_text> + this is the text which will be encrypted if everything is going fine. + </clear_text> + <key>0123456789abcdef0123456789abcdef0123456789abcdef</key> + <cipher_text> + <!-- + Cipher text matches the case of the clear text padded with zeros to + the nearest block size. + --> + f90fe23d ce62d9ee 57178af0 d08604c6 + 7244ec3d 871879d8 6d81313f 10bb4c66 + 9fe08dda ccb36763 bde8b464 c9a9b012 + 9ff06d09 fbaee2a4 901cfe0d f0fee26c + 34b58f68 a9e27607 7bdd8e72 8b2b528b + </cipher_text> + </crypto_test> + <crypto_test name="AES:ECB128 1"> + <clear_text format="hex"> + 33221100 77665544 bbaa9988 ffeeddcc + </clear_text> + <key>03020100 07060504 0b0a0908 0f0e0d0c</key> + <cipher_text> + d8e0c469 30047b6a 80b7cdd8 5ac5b470 + </cipher_text> + </crypto_test> + <crypto_test name="AES:ECB192 1"> + <clear_text format="hex"> + 00000000 00000000 00000000 00000000 + </clear_text> + <key> + 6e0fd215 9f647ebc b1765bd9 badae607 + 948a7c96 297f7984 + </key> + <cipher_text> + 42184e8e 3d1a594e 76086f5b 94856ff1 + </cipher_text> + </crypto_test> + <crypto_test name="AES:ECB256 1"> + <clear_text format="hex"> + 00000000 00000000 00000000 00000000 + </clear_text> + <key> + 00000080 00000000 00000000 00000000 + 00000000 00000000 00000000 00000000 + </key> + <cipher_text> + cb6d5ae3 a001b219 8afabc1e 59572ba2 + </cipher_text> + </crypto_test> + <crypto_test name="AES:ECB256 2"> + <clear_text format="hex"> + 45249ff6 179b4fdf 7b412bad 10376ce6 + </clear_text> + <key> + 10eb3d60 be71ca15 f0ae732b 81777d85 + 072c351f d708613b a310982d f4df1409 + </key> + <cipher_text> + 7a4b3023 fff3f939 8f8d7d06 c7ec249e + </cipher_text> + </crypto_test> + <crypto_test name="AES:CTR128I 1"> + <clear_text format="hex"> + e2bec16b 969f402e 117e3de9 2a179373 + </clear_text> + <key> + 16157e2b a6d2ae28 8815f7ab 3c4fcf09 + </key> + <cipher_text> + 91614d87 26e320b6 6468ef1b ceb60d99 + </cipher_text> + <iv>f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc</iv> + </crypto_test> + <crypto_test name="AES:CTR256I 1"> + <clear_text format="hex"> + 13c31e60 a5895777 04f5a7b7 28d2f3bb + </clear_text> + <key> + 10eb3d60 be71ca15 f0ae732b 81777d85 + 072c351f d708613b a310982d f4df1409 + </key> + <cipher_text> + e2bec16b 969f402e 117e3de9 2a179373 + </cipher_text> + <iv>f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc</iv> + </crypto_test> +</crypto_tests> diff --git a/test/tpm_test/ftdi_spi_tpm.c b/test/tpm_test/ftdi_spi_tpm.c new file mode 100644 index 0000000000..ac028ef325 --- /dev/null +++ b/test/tpm_test/ftdi_spi_tpm.c @@ -0,0 +1,448 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <endian.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "ftdi_spi_tpm.h" + +static struct mpsse_context *mpsse_; +static unsigned locality_; /* Set at initialization. */ +static int ftdi_trace_enabled; + +/* Assorted TPM2 registers for interface type FIFO. */ +#define TPM_ACCESS_REG 0 +#define TPM_STS_REG 0x18 +#define TPM_DATA_FIFO_REG 0x24 +#define TPM_DID_VID_REG 0xf00 +#define TPM_RID_REG 0xf04 + +static struct swig_string_data empty_string_data = (struct swig_string_data){ + .size = 0, .data = NULL +}; + +/* Locality management bits (in TPM_ACCESS_REG). */ +enum TpmAccessBits { + tpmRegValidSts = (1 << 7), + activeLocality = (1 << 5), + requestUse = (1 << 1), + tpmEstablishment = (1 << 0), +}; + +enum TpmStsBits { + tpmFamilyShift = 26, + tpmFamilyMask = ((1 << 2) - 1), /* 2 bits wide. */ + tpmFamilyTPM2 = 1, + resetEstablishmentBit = (1 << 25), + commandCancel = (1 << 24), + burstCountShift = 8, + burstCountMask = ((1 << 16) - 1), /* 16 bits wide. */ + stsValid = (1 << 7), + commandReady = (1 << 6), + tpmGo = (1 << 5), + dataAvail = (1 << 4), + Expect = (1 << 3), + selfTestDone = (1 << 2), + responseRetry = (1 << 1), +}; + +enum { + false = 0, + true = 1 +}; + +/* + * SPI frame header for TPM transactions is 4 bytes in size, it is described + * in section "6.4.6 Spi Bit Protocol" of the TCG issued "TPM Profile (PTP) + * Specification Revision 00.43. + */ +struct SpiFrameHeader { + unsigned char body[4]; +}; + +void FtdiStop(void) +{ + if (mpsse_) + Close(mpsse_); + + mpsse_ = NULL; +} + +static void StartTransaction(int read_write, size_t bytes, unsigned addr) +{ + struct SpiFrameHeader header; + int i; + uint8_t flow_c; + char *transfer_data; + + /* + * give it 10 ms. TODO(vbendeb): remove this once cr50 SPS TPM driver + * performance is fixed. + */ + usleep(10000); + + /* + * The first byte of the frame header encodes the transaction type + * (read or write) and size (set to length - 1). + */ + header.body[0] = (read_write ? 0x80 : 0) | 0x40 | (bytes - 1); + + /* The rest of the frame header is the internal address in the TPM. */ + for (i = 0; i < 3; i++) + header.body[i + 1] = (addr >> (8 * (2 - i))) & 0xff; + + Start(mpsse_); + + transfer_data = + Transfer(mpsse_, (char *)header.body, sizeof(header.body)); + + /* + * The TCG TPM over SPI specification itroduces the notion of SPI flow + * control (Section "6.4.5 Flow Control" of the TCG issued "TPM + * Profile (PTP) Specification Revision 00.43). + * + * The slave (TPM device) expects each transaction to start with a 4 + * byte header trasmitted by master. If the slave needs to stall the + * transaction, it sets the MOSI bit to 0 during the last clock of the + * 4 byte header. In this case the master is supposed to start polling + * the line, byte at time, until the last bit in the received byte + * (transferred during the last clock of the byte) is set to 1. + */ + flow_c = transfer_data[3]; + free(transfer_data); + while (!(flow_c & 1)) { + transfer_data = Read(mpsse_, 1); + flow_c = transfer_data[0]; + free(transfer_data); + } +} + +static void trace_dump(const char *prefix, unsigned reg, size_t bytes, + const uint8_t *buffer) +{ + if (!ftdi_trace_enabled) + return; + printf("%s %2.2x:", prefix, reg); + if (bytes == 4) { + printf(" %8.8x\n", *(const uint32_t *)buffer); + } else { + int i; + + for (i = 0; i < bytes; i++) + printf(" %2.2x", buffer[i]); + printf("\n"); + } +} + +static int FtdiWriteReg(unsigned reg_number, size_t bytes, void *buffer) +{ + if (!mpsse_) + return false; + + trace_dump("W", reg_number, bytes, buffer); + StartTransaction(false, bytes, reg_number + locality_ * 0x10000); + Write(mpsse_, buffer, bytes); + Stop(mpsse_); + return true; +} + +static int FtdiReadReg(unsigned reg_number, size_t bytes, void *buffer) +{ + void *data; + + if (!mpsse_) + return false; + + StartTransaction(true, bytes, reg_number + locality_ * 0x10000); + data = Read(mpsse_, bytes); + if (data) + memcpy(buffer, data, bytes); + free(data); + Stop(mpsse_); + trace_dump("R", reg_number, bytes, buffer); + return true; +} + +static int ReadTpmSts(uint32_t *status) +{ + return FtdiReadReg(TPM_STS_REG, sizeof(*status), status); +} + +static int WriteTpmSts(uint32_t status) +{ + return FtdiWriteReg(TPM_STS_REG, sizeof(status), &status); +} + +static uint32_t GetBurstCount(void) +{ + uint32_t status; + + ReadTpmSts(&status); + return (status >> burstCountShift) & burstCountMask; +} + +int FtdiSpiInit(uint32_t freq, int enable_debug) +{ + uint32_t did_vid, status; + uint8_t cmd; + uint16_t vid; + + if (mpsse_) + return true; + + ftdi_trace_enabled = enable_debug; + + /* round frequency down to the closest 100KHz */ + freq = (freq / (100 * 1000)) * 100 * 1000; + + printf("Starting MPSSE at %d kHz\n", freq / 1000); + mpsse_ = MPSSE(freq, MSB, NULL); + if (!mpsse_) + return false; + + /* Reset the TPM using GPIOL0, issue a 100 ms long pulse. */ + PinLow(mpsse_, GPIOL0); + usleep(100000); + PinHigh(mpsse_, GPIOL0); + + FtdiReadReg(TPM_DID_VID_REG, sizeof(did_vid), &did_vid); + + vid = did_vid & 0xffff; + if ((vid != 0x15d1) && (vid != 0x1ae0)) { + fprintf(stderr, "unknown did_vid: %#x\n", did_vid); + return false; + } + + /* Try claiming locality zero. */ + FtdiReadReg(TPM_ACCESS_REG, sizeof(cmd), &cmd); + if ((cmd & (activeLocality & tpmRegValidSts)) == + (activeLocality & tpmRegValidSts)) { + /* + * Locality active - maybe reset line is not connected? + * Release the locality and try again + */ + cmd = activeLocality; + FtdiWriteReg(TPM_ACCESS_REG, sizeof(cmd), &cmd); + FtdiReadReg(TPM_ACCESS_REG, sizeof(cmd), &cmd); + } + + /* tpmEstablishment can be either set or not. */ + if ((cmd & ~tpmEstablishment) != tpmRegValidSts) { + fprintf(stderr, "invalid reset status: %#x\n", cmd); + return false; + } + cmd = requestUse; + FtdiWriteReg(TPM_ACCESS_REG, sizeof(cmd), &cmd); + FtdiReadReg(TPM_ACCESS_REG, sizeof(cmd), &cmd); + if ((cmd & ~tpmEstablishment) != (tpmRegValidSts | activeLocality)) { + fprintf(stderr, "failed to claim locality, status: %#x\n", cmd); + return false; + } + + ReadTpmSts(&status); + if (((status >> tpmFamilyShift) & tpmFamilyMask) != tpmFamilyTPM2) { + fprintf(stderr, "unexpected TPM family value, status: %#x\n", + status); + return false; + } + FtdiReadReg(TPM_RID_REG, sizeof(cmd), &cmd); + printf("Connected to device vid:did:rid of %4.4x:%4.4x:%2.2x\n", + did_vid & 0xffff, did_vid >> 16, cmd); + + return true; +} + +/* This is in seconds. */ +#define MAX_STATUS_TIMEOUT 120 +static int WaitForStatus(uint32_t statusMask, uint32_t statusExpected) +{ + uint32_t status; + time_t target_time; + static unsigned max_timeout; + + target_time = time(NULL) + MAX_STATUS_TIMEOUT; + do { + usleep(10000); + if (time(NULL) >= target_time) { + fprintf(stderr, "failed to get expected status %x\n", + statusExpected); + return false; + } + ReadTpmSts(&status); + } while ((status & statusMask) != statusExpected); + + /* Calculate time spent waiting */ + target_time = MAX_STATUS_TIMEOUT - target_time + time(NULL); + if (max_timeout < (unsigned)target_time) { + max_timeout = target_time; + printf("New max timeout: %d s\n", max_timeout); + } + + return true; +} + +static void SpinSpinner(void) +{ + static const char *spinner = "\\|/-"; + static int index; + + if (index > strlen(spinner)) + index = 0; + + fprintf(stdout, "%c[1D%c", 0x1b, spinner[index++]); + fflush(stdout); +} + +#define MAX_RESPONSE_SIZE 4096 +#define HEADER_SIZE 6 + +/* tpm_command points at a buffer 4096 bytes in size */ +struct swig_string_data FtdiSendCommandAndWait(char *tpm_command, + int command_size) +{ + uint32_t status; + uint32_t expected_status_bits; + size_t handled_so_far; + uint32_t payload_size; + char message[100]; + int offset = 0; + uint8_t *response; + + if (!mpsse_) { + fprintf(stderr, "attempt to use an uninitialized FTDI TPM!\n"); + return empty_string_data; + } + + response = malloc(MAX_RESPONSE_SIZE); + if (!response) { + fprintf(stderr, "attempt to use an uninitialized FTDI TPM!\n"); + return empty_string_data; + } + + handled_so_far = 0; + + WriteTpmSts(commandReady); + + memcpy(&payload_size, tpm_command + 2, sizeof(payload_size)); + payload_size = be32toh(payload_size); + offset += + snprintf(message, sizeof(message), "Message size %d", payload_size); + + /* + * No need to wait for the sts.Expect bit to be set, at least with the + * 15d1:001b and 1ae0:0028 devices. Let's just write the command into + * FIFO, make sure not to exceed the burst count. + */ + do { + uint32_t transaction_size; + uint32_t burst_count = GetBurstCount(); + + if (burst_count > 64) + burst_count = 64; + + transaction_size = command_size - handled_so_far; + if (transaction_size > burst_count) + transaction_size = burst_count; + + if (transaction_size) { + FtdiWriteReg(TPM_DATA_FIFO_REG, transaction_size, + tpm_command + handled_so_far); + handled_so_far += transaction_size; + } + } while (handled_so_far != command_size); + + /* And tell the device it can start processing it. */ + WriteTpmSts(tpmGo); + + expected_status_bits = stsValid | dataAvail; + if (!WaitForStatus(expected_status_bits, expected_status_bits)) { + size_t i; + + printf("Failed processing. %s:", message); + for (i = 0; i < command_size; i++) { + if (!(i % 16)) + printf("\n"); + printf(" %2.2x", tpm_command[i]); + } + printf("\n"); + return empty_string_data; + } + + /* + * The tpm_command is ready, let's read it. + * + * First we read the FIFO payload header, to see how much data to + * expect. The header size is fixed to six bytes, the total payload + * size is stored in network order in the last four bytes of the + * header. + */ + FtdiReadReg(TPM_DATA_FIFO_REG, HEADER_SIZE, response); + handled_so_far = HEADER_SIZE; + + /* Figure out the total payload size. */ + memcpy(&payload_size, response + 2, sizeof(payload_size)); + payload_size = be32toh(payload_size); + + if (ftdi_trace_enabled) + printf("%s response size %d\n\n", message, payload_size); + else + SpinSpinner(); + + if (payload_size > MAX_RESPONSE_SIZE) + return empty_string_data; + /* + * Let's read all but the last byte in the FIFO to make sure the + * status register is showing correct flow control bits: 'more data' + * until the last byte and then 'no more data' once the last byte is + * read. + */ + payload_size = payload_size - 1; + do { + uint32_t transaction_size; + uint32_t burst_count = GetBurstCount(); + + if (burst_count > 64) + burst_count = 64; + + transaction_size = payload_size - handled_so_far; + if (transaction_size > burst_count) + transaction_size = burst_count; + + if (transaction_size) { + FtdiReadReg(TPM_DATA_FIFO_REG, transaction_size, + response + handled_so_far); + handled_so_far += transaction_size; + } + } while (handled_so_far != payload_size); + + /* Verify that there is still data to come. */ + ReadTpmSts(&status); + if ((status & expected_status_bits) != expected_status_bits) { + fprintf(stderr, "unexpected status %#x\n", status); + return empty_string_data; + } + + FtdiReadReg(TPM_DATA_FIFO_REG, 1, response + handled_so_far); + + /* Verify that 'data available' is not asseretd any more. */ + ReadTpmSts(&status); + if ((status & expected_status_bits) != stsValid) { + fprintf(stderr, "unexpected status %#x\n", status); + return empty_string_data; + } + + /* Move the TPM back to idle state. */ + WriteTpmSts(commandReady); + + handled_so_far++; + + return (struct swig_string_data) { + .size = handled_so_far, .data = response}; +} diff --git a/test/tpm_test/ftdi_spi_tpm.h b/test/tpm_test/ftdi_spi_tpm.h new file mode 100644 index 0000000000..5393d4cd86 --- /dev/null +++ b/test/tpm_test/ftdi_spi_tpm.h @@ -0,0 +1,25 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __EC_TEST_TPM_TEST_FTDI_SPI_TPM_H +#define __EC_TEST_TPM_TEST_FTDI_SPI_TPM_H + +#include "mpsse.h" + +/* + * This structure allows to convert string representation between C and + * Python. + */ +struct swig_string_data { + int size; + uint8_t *data; +}; + +int FtdiSpiInit(uint32_t freq, int enable_debug); +void FtdiStop(void); +struct swig_string_data FtdiSendCommandAndWait(char *tpm_command, + int command_size); + +#endif /* ! __EC_TEST_TPM_TEST_FTDI_SPI_TPM_H */ diff --git a/test/tpm_test/ftdi_spi_tpm.i b/test/tpm_test/ftdi_spi_tpm.i new file mode 100644 index 0000000000..c9cc2fc535 --- /dev/null +++ b/test/tpm_test/ftdi_spi_tpm.i @@ -0,0 +1,50 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +%module ftdi_spi_tpm +typedef unsigned uint32_t; +typedef unsigned char uint8_t; + +%{ +typedef struct swig_string_data +{ + int size; + char *data; +} swig_string_data; + +extern int FtdiSpiInit(uint32_t freq, int enable_debug); +extern void FtdiStop(void); +extern swig_string_data FtdiSendCommandAndWait(char *tpm_command, + int command_size); +%} + +%typemap(in) (char *tpm_command, int command_size) +{ + if(!PyString_Check($input)) + { + PyErr_SetString(PyExc_ValueError, "String value required"); + return NULL; + } + + $1 = PyString_AsString($input); + $2 = PyString_Size($input); +} + +%typemap(out) swig_string_data +{ + $result = PyString_FromStringAndSize($1.data, $1.size); + free($1.data); +} + +typedef struct swig_string_data +{ + int size; + char *data; +} swig_string_data; + +extern int FtdiSpiInit(uint32_t freq, int enable_debug); +extern void FtdiStop(void); +extern swig_string_data FtdiSendCommandAndWait(char *tpm_command, + int command_size); diff --git a/test/tpm_test/mpsse.c b/test/tpm_test/mpsse.c new file mode 100644 index 0000000000..9b8d97e8e3 --- /dev/null +++ b/test/tpm_test/mpsse.c @@ -0,0 +1,723 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Based on Craig Heffner's version of Dec 27 2011, published on + * https://github.com/devttys0/libmpsse + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#if LIBFTDI1 == 1 +#include <libftdi1/ftdi.h> +#else +#include <ftdi.h> +#endif + +#include "mpsse.h" +#include "support.h" + +/* FTDI interfaces */ +enum interface { + IFACE_ANY = INTERFACE_ANY, + IFACE_A = INTERFACE_A, + IFACE_B = INTERFACE_B, + IFACE_C = INTERFACE_C, + IFACE_D = INTERFACE_D +}; + +enum mpsse_commands { + INVALID_COMMAND = 0xAB, + ENABLE_ADAPTIVE_CLOCK = 0x96, + DISABLE_ADAPTIVE_CLOCK = 0x97, + ENABLE_3_PHASE_CLOCK = 0x8C, + DISABLE_3_PHASE_CLOCK = 0x8D, + TCK_X5 = 0x8A, + TCK_D5 = 0x8B, + CLOCK_N_CYCLES = 0x8E, + CLOCK_N8_CYCLES = 0x8F, + PULSE_CLOCK_IO_HIGH = 0x94, + PULSE_CLOCK_IO_LOW = 0x95, + CLOCK_N8_CYCLES_IO_HIGH = 0x9C, + CLOCK_N8_CYCLES_IO_LOW = 0x9D, + TRISTATE_IO = 0x9E, +}; + +/* Common clock rates */ +enum clock_rates { + ONE_HUNDRED_KHZ = 100000, + FOUR_HUNDRED_KHZ = 400000, + ONE_MHZ = 1000000, + TWO_MHZ = 2000000, + FIVE_MHZ = 5000000, + SIX_MHZ = 6000000, + TEN_MHZ = 10000000, + TWELVE_MHZ = 12000000, + FIFTEEN_MHZ = 15000000, + THIRTY_MHZ = 30000000, + SIXTY_MHZ = 60000000 +}; + +#define NULL_CONTEXT_ERROR_MSG "NULL MPSSE context pointer!" +#define SPI_TRANSFER_SIZE 512 +#define SPI_RW_SIZE (63 * 1024) +#define SETUP_DELAY 25000 +#define LATENCY_MS 2 +#define USB_TIMEOUT 120000 +#define CHUNK_SIZE 65535 +#define MAX_SETUP_COMMANDS 10 + +/* SK and CS are high, GPIO1 is reset on the FPGA hookup, all others low */ +#define DEFAULT_PORT (SK | CS | GPIO1) +/* SK/DO/CS and GPIOs are outputs, DI is an input */ +#define DEFAULT_TRIS (SK | DO | CS | GPIO0 | GPIO1 | GPIO2 | GPIO3) + +static struct vid_pid { + int vid; + int pid; + char *description; +} supported_devices[] = { + { + 0x0403, 0x6010, "FT2232 Future Technology Devices International, Ltd"}, + { + 0x0403, 0x6011, "FT4232 Future Technology Devices International, Ltd"}, + { + 0x0403, 0x6014, + "FT232H Future Technology Devices International, Ltd"}, + /* These devices are based on FT2232 chips, but have not been tested. */ + { + 0x0403, 0x8878, "Bus Blaster v2 (channel A)"}, { + 0x0403, 0x8879, "Bus Blaster v2 (channel B)"}, { + 0x0403, 0xBDC8, "Turtelizer JTAG/RS232 Adapter A"}, { + 0x0403, 0xCFF8, "Amontec JTAGkey"}, { + 0x0403, 0x8A98, "TIAO Multi Protocol Adapter"}, { + 0x15BA, 0x0003, "Olimex Ltd. OpenOCD JTAG"}, { + 0x15BA, 0x0004, "Olimex Ltd. OpenOCD JTAG TINY"}, { + 0, 0, NULL} +}; + +/* + * Enables or disables flushing of the FTDI chip's RX buffers after each read + * operation. Flushing is disable by default. + * + * @mpsse - MPSSE context pointer. + * @tf - Set to 1 to enable flushing, or 0 to disable flushing. + * + * Returns void. + */ +static void FlushAfterRead(struct mpsse_context *mpsse, int tf) +{ + mpsse->flush_after_read = tf; +} + +/* + * Enable / disable internal loopback. + * + * @mpsse - MPSSE context pointer. + * @enable - Zero to disable loopback, 1 to enable loopback. + * + * Returns MPSSE_OK on success. + * Returns MPSSE_FAIL on failure. + */ +static int SetLoopback(struct mpsse_context *mpsse, int enable) +{ + unsigned char buf[1] = { 0 }; + int retval = MPSSE_FAIL; + + if (is_valid_context(mpsse)) { + if (enable) + buf[0] = LOOPBACK_START; + else + buf[0] = LOOPBACK_END; + + retval = raw_write(mpsse, buf, 1); + } + + return retval; +} + +/* + * Sets the appropriate divisor for the desired clock frequency. + * + * @mpsse - MPSSE context pointer. + * @freq - Desired clock frequency in hertz. + * + * Returns MPSSE_OK on success. + * Returns MPSSE_FAIL on failure. + */ +static int SetClock(struct mpsse_context *mpsse, uint32_t freq) +{ + int retval = MPSSE_FAIL; + uint32_t system_clock = 0; + uint16_t divisor = 0; + unsigned char buf[CMD_SIZE] = { 0 }; + + /* + * Do not call is_valid_context() here, as the FTDI chip may not be + * completely configured when SetClock is called + */ + if (!mpsse) + return retval; + + if (freq > SIX_MHZ) { + buf[0] = TCK_X5; + system_clock = SIXTY_MHZ; + } else { + buf[0] = TCK_D5; + system_clock = TWELVE_MHZ; + } + + if (raw_write(mpsse, buf, 1) == MPSSE_OK) { + if (freq <= 0) + divisor = 0xFFFF; + else + divisor = freq2div(system_clock, freq); + + buf[0] = TCK_DIVISOR; + buf[1] = (divisor & 0xFF); + buf[2] = ((divisor >> 8) & 0xFF); + + if (raw_write(mpsse, buf, 3) == MPSSE_OK) { + mpsse->clock = div2freq(system_clock, divisor); + retval = MPSSE_OK; + } + } + + return retval; +} + +/* + * Sets the appropriate transmit and receive commands based on the requested + * mode and byte order. + * + * @mpsse - MPSSE context pointer. + * @endianness - MPSSE_MSB or MPSSE_LSB. + * + * Returns MPSSE_OK on success. + * Returns MPSSE_FAIL on failure. + */ +static int SetMode(struct mpsse_context *mpsse, int endianness) +{ + int retval = MPSSE_OK, i = 0, setup_commands_size = 0; + unsigned char buf[CMD_SIZE] = { 0 }; + unsigned char setup_commands[CMD_SIZE * MAX_SETUP_COMMANDS] = { 0 }; + + /* + * Do not call is_valid_context() here, as the FTDI chip may not be + * completely configured when SetMode is called + */ + if (!mpsse) + return MPSSE_FAIL; + + /* Read and write commands need to include endianness */ + mpsse->tx = MPSSE_DO_WRITE | endianness; + mpsse->rx = MPSSE_DO_READ | endianness; + mpsse->txrx = MPSSE_DO_WRITE | MPSSE_DO_READ | endianness; + + /* + * Clock, data out, chip select pins are outputs; all others are + * inputs. + */ + mpsse->tris = DEFAULT_TRIS; + + /* Clock and chip select pins idle high; all others are low */ + mpsse->pidle = mpsse->pstart = mpsse->pstop = DEFAULT_PORT; + + /* During reads and writes the chip select pin is brought low */ + mpsse->pstart &= ~CS; + + /* Disable FTDI internal loopback */ + SetLoopback(mpsse, 0); + + /* Ensure adaptive clock is disabled */ + setup_commands[setup_commands_size++] = DISABLE_ADAPTIVE_CLOCK; + + switch (mpsse->mode) { + case SPI0: + /* SPI mode 0 clock idles low */ + mpsse->pidle &= ~SK; + mpsse->pstart &= ~SK; + mpsse->pstop &= ~SK; + + /* + * SPI mode 0 propogates data on the falling edge and read + * data on the rising edge of the clock + */ + mpsse->tx |= MPSSE_WRITE_NEG; + mpsse->rx &= ~MPSSE_READ_NEG; + mpsse->txrx |= MPSSE_WRITE_NEG; + mpsse->txrx &= ~MPSSE_READ_NEG; + break; + default: + fprintf(stderr, "%s:%d attempt to set an unsupported mode %d\n", + __func__, __LINE__, mpsse->mode); + retval = MPSSE_FAIL; + } + + /* Send any setup commands to the chip */ + if ((retval == MPSSE_OK) && (setup_commands_size > 0)) + retval = raw_write(mpsse, setup_commands, setup_commands_size); + + if (retval == MPSSE_OK) { + /* Set the idle pin states */ + set_bits_low(mpsse, mpsse->pidle); + + /* All GPIO pins are outputs, set low */ + mpsse->trish = 0xFF; + mpsse->gpioh = 0x00; + + buf[i++] = SET_BITS_HIGH; + buf[i++] = mpsse->gpioh; + buf[i++] = mpsse->trish; + + retval = raw_write(mpsse, buf, i); + } + + return retval; +} + +/* + * Open device by VID/PID/index + * + * @vid - Device vendor ID. + * @pid - Device product ID. + * @freq - Clock frequency to use for the specified mode. + * @endianness - Specifies how data is clocked in/out (MSB, LSB). + * @interface - FTDI interface to use (IFACE_A - IFACE_D). + * @description - Device product description (set to NULL if not needed). + * @serial - Device serial number (set to NULL if not needed). + * @index - Device index (set to 0 if not needed). + * + * Returns a pointer to an MPSSE context structure. + * On success, mpsse->open will be set to 1. + * On failure, mpsse->open will be set to 0. + */ +static struct mpsse_context *OpenIndex(int vid, + int pid, + int freq, + int endianness, + int interface, + const char *description, + const char *serial, int index) +{ + int status = 0; + struct mpsse_context *mpsse = NULL; + enum modes mode = SPI0; /* Let's use this mode at all times. */ + + mpsse = malloc(sizeof(struct mpsse_context)); + if (!mpsse) + return NULL; + + memset(mpsse, 0, sizeof(struct mpsse_context)); + + /* Legacy; flushing is no longer needed, so disable it by default. */ + FlushAfterRead(mpsse, 0); + + /* ftdilib initialization */ + if (ftdi_init(&mpsse->ftdi)) { + fprintf(stderr, "%s:%d failed to initialize FTDI\n", + __func__, __LINE__); + free(mpsse); + return NULL; + } + + mpsse->ftdi_initialized = 1; + + /* Set the FTDI interface */ + ftdi_set_interface(&mpsse->ftdi, interface); + + /* Try opening the specified device */ + if (ftdi_usb_open_desc_index + (&mpsse->ftdi, vid, pid, description, serial, index)) { + Close(mpsse); + return NULL; + } + + mpsse->mode = mode; + mpsse->vid = vid; + mpsse->pid = pid; + mpsse->status = STOPPED; + mpsse->endianness = endianness; + mpsse->xsize = SPI_RW_SIZE; + + status |= ftdi_usb_reset(&mpsse->ftdi); + status |= ftdi_set_latency_timer(&mpsse->ftdi, LATENCY_MS); + status |= ftdi_write_data_set_chunksize(&mpsse->ftdi, CHUNK_SIZE); + status |= ftdi_read_data_set_chunksize(&mpsse->ftdi, CHUNK_SIZE); + status |= ftdi_set_bitmode(&mpsse->ftdi, 0, BITMODE_RESET); + + if (status) { + fprintf(stderr, + "%s:%d failed setting basic config for %4.4x:%4.4x\n", + __func__, __LINE__, vid, pid); + Close(mpsse); + return NULL; + } + /* Set the read and write timeout periods */ + set_timeouts(mpsse, USB_TIMEOUT); + + ftdi_set_bitmode(&mpsse->ftdi, 0, BITMODE_MPSSE); + + if ((SetClock(mpsse, freq) != MPSSE_OK) + || (SetMode(mpsse, endianness) != MPSSE_OK)) { + fprintf(stderr, + "%s:%d failed setting clock/mode for %4.4x:%4.4x\n", + __func__, __LINE__, vid, pid); + Close(mpsse); + return NULL; + } + + mpsse->open = 1; + + /* Give the chip a few mS to initialize */ + usleep(SETUP_DELAY); + + /* + * Not all FTDI chips support all the commands that SetMode may have + * sent. This clears out any errors from unsupported commands that + * might have been sent during set up. + */ + ftdi_usb_purge_buffers(&mpsse->ftdi); + + return mpsse; +} + +/* + * Opens and initializes the first FTDI device found. + * + * @freq - Clock frequency to use for the specified mode. + * @endianness - Specifies how data is clocked in/out (MSB, LSB). + * @serial - Serial number of the USB device (NULL if not needed). + * + * Returns a pointer to an MPSSE context structure. + * On success, mpsse->open will be set to 1. + * On failure, mpsse->open will be set to 0. + */ +struct mpsse_context *MPSSE(int freq, int endianness, const char *serial) +{ + int i = 0; + struct mpsse_context *mpsse = NULL; + + for (i = 0; supported_devices[i].vid != 0; i++) { + mpsse = OpenIndex(supported_devices[i].vid, + supported_devices[i].pid, freq, endianness, + IFACE_A, NULL, serial, 0); + if (!mpsse) + continue; + + if (mpsse->open) { + mpsse->description = supported_devices[i].description; + break; + } + /* + * If there is another device still left to try, free + * the context pointer and try again + */ + if (supported_devices[i + 1].vid != 0) { + Close(mpsse); + mpsse = NULL; + } + } + + return mpsse; +} + +/* + * Closes the device, deinitializes libftdi, and frees the MPSSE context + * pointer. + * + * @mpsse - MPSSE context pointer. + * + * Returns void. + */ + +void Close(struct mpsse_context *mpsse) +{ + if (!mpsse) + return; + + if (mpsse->open) { + ftdi_usb_close(&mpsse->ftdi); + ftdi_set_bitmode(&mpsse->ftdi, 0, BITMODE_RESET); + } + + if (mpsse->ftdi_initialized) + ftdi_deinit(&mpsse->ftdi); + + free(mpsse); +} + +/* + * Retrieves the last error string from libftdi. + * + * @mpsse - MPSSE context pointer. + * + * Returns a pointer to the last error string. + */ +const char *ErrorString(struct mpsse_context *mpsse) +{ + if (mpsse) + return ftdi_get_error_string(&mpsse->ftdi); + + return NULL_CONTEXT_ERROR_MSG; +} + +/* + * Send data start condition. + * + * @mpsse - MPSSE context pointer. + * + * Returns MPSSE_OK on success. + * Returns MPSSE_FAIL on failure. + */ +int Start(struct mpsse_context *mpsse) +{ + int status; + + if (!is_valid_context(mpsse)) { + mpsse->status = STOPPED; + return MPSSE_FAIL; + } + + /* Set the start condition */ + status = set_bits_low(mpsse, mpsse->pstart); + + if (status == MPSSE_OK) + mpsse->status = STARTED; + + return status; +} + +/* + * Send data out via the selected serial protocol. + * + * @mpsse - MPSSE context pointer. + * @data - Buffer of data to send. + * @size - Size of data. + * + * Returns MPSSE_OK on success. + * Returns MPSSE_FAIL on failure. + */ +int Write(struct mpsse_context *mpsse, char *data, int size) +{ + int n = 0; + + if (!is_valid_context(mpsse)) + return MPSSE_FAIL; + + if (!mpsse->mode) + return MPSSE_FAIL; + + while (n < size) { + unsigned char *buf; + int retval, buf_size, txsize; + + txsize = size - n; + if (txsize > mpsse->xsize) + txsize = mpsse->xsize; + + buf = build_block_buffer(mpsse, mpsse->tx, + (unsigned char *)(data + n), + txsize, &buf_size); + if (!buf) + return MPSSE_FAIL; + + retval = raw_write(mpsse, buf, buf_size); + n += txsize; + free(buf); + + if (retval != MPSSE_OK) + return retval; + + } + + return MPSSE_OK; +} + +/* Performs a read. For internal use only; see Read() and ReadBits(). */ +static char *InternalRead(struct mpsse_context *mpsse, int size) +{ + unsigned char *buf; + int n = 0; + + if (!is_valid_context(mpsse)) + return NULL; + + if (!mpsse->mode) + return NULL; + buf = malloc(size); + + if (!buf) + return NULL; + + while (n < size) { + int rxsize, data_size, retval; + unsigned char *data; + unsigned char sbuf[SPI_RW_SIZE] = { 0 }; + + rxsize = size - n; + if (rxsize > mpsse->xsize) + rxsize = mpsse->xsize; + + data = build_block_buffer(mpsse, mpsse->rx, + sbuf, rxsize, &data_size); + if (!data) { + free(buf); + return NULL; + } + + retval = raw_write(mpsse, data, data_size); + free(data); + + if (retval != MPSSE_OK) { + free(buf); + return NULL; + } + n += raw_read(mpsse, buf + n, rxsize); + } + + return (char *)buf; +} + +/* + * Reads data over the selected serial protocol. + * + * @mpsse - MPSSE context pointer. + * @size - Number of bytes to read. + * + * Returns a pointer to the read data on success. + * Returns NULL on failure. + */ +char *Read(struct mpsse_context *mpsse, int size) +{ + char *buf = NULL; + + buf = InternalRead(mpsse, size); + return buf; +} + +/* + * Reads and writes data over the selected serial protocol (SPI only). + * + * @mpsse - MPSSE context pointer. + * @data - Buffer containing bytes to write. + * @size - Number of bytes to transfer. + * + * Returns a pointer to the read data on success. + * Returns NULL on failure. + */ +char *Transfer(struct mpsse_context *mpsse, char *data, int size) +{ + unsigned char *txdata = NULL, *buf = NULL; + int n = 0, data_size = 0, rxsize = 0, retval = MPSSE_OK; + + if (!is_valid_context(mpsse)) + return NULL; + + buf = malloc(size); + if (!buf) + return NULL; + + while (n < size) { + /* + * When sending and recieving, FTDI chips don't seem to like + * large data blocks. Limit the size of each block to + * SPI_TRANSFER_SIZE + */ + rxsize = size - n; + if (rxsize > SPI_TRANSFER_SIZE) + rxsize = SPI_TRANSFER_SIZE; + + txdata = build_block_buffer(mpsse, mpsse->txrx, + (unsigned char *)(data + n), + rxsize, &data_size); + if (!txdata) { + retval = MPSSE_FAIL; + break; + } + retval = raw_write(mpsse, txdata, data_size); + free(txdata); + + if (retval != MPSSE_OK) + break; + + n += raw_read(mpsse, (buf + n), rxsize); + } + + if (retval != MPSSE_OK) + return NULL; + + return (char *)buf; +} + +/* + * Send data stop condition. + * + * @mpsse - MPSSE context pointer. + * + * Returns MPSSE_OK on success. + * Returns MPSSE_FAIL on failure. + */ +int Stop(struct mpsse_context *mpsse) +{ + int retval = MPSSE_OK; + + if (is_valid_context(mpsse)) { + /* Send the stop condition */ + retval |= set_bits_low(mpsse, mpsse->pstop); + + if (retval == MPSSE_OK) { + /* Restore the pins to their idle states */ + retval |= set_bits_low(mpsse, mpsse->pidle); + } + + mpsse->status = STOPPED; + } else { + retval = MPSSE_FAIL; + mpsse->status = STOPPED; + } + + return retval; +} + +/* + * Sets the specified pin high. + * + * @mpsse - MPSSE context pointer. + * @pin - Pin number to set high. + * + * Returns MPSSE_OK on success. + * Returns MPSSE_FAIL on failure. + */ +int PinHigh(struct mpsse_context *mpsse, int pin) +{ + int retval = MPSSE_FAIL; + + if (is_valid_context(mpsse)) + retval = gpio_write(mpsse, pin, HIGH); + + return retval; +} + +/* + * Sets the specified pin low. + * + * @mpsse - MPSSE context pointer. + * @pin - Pin number to set low. + * + * Returns MPSSE_OK on success. + * Returns MPSSE_FAIL on failure. + */ +int PinLow(struct mpsse_context *mpsse, int pin) +{ + int retval = MPSSE_FAIL; + + if (is_valid_context(mpsse)) + retval = gpio_write(mpsse, pin, LOW); + + return retval; +} diff --git a/test/tpm_test/mpsse.h b/test/tpm_test/mpsse.h new file mode 100644 index 0000000000..2925dfe27a --- /dev/null +++ b/test/tpm_test/mpsse.h @@ -0,0 +1,45 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Based on Craig Heffner's version of Dec 27 2011, published on + * https://github.com/devttys0/libmpsse + */ + +#ifndef __EC_TEST_TPM_TEST_MPSSE_H +#define __EC_TEST_TPM_TEST_MPSSE_H + +#define MPSSE_OK 0 +#define MPSSE_FAIL -1 + +#define MSB 0x00 +#define LSB 0x08 + +enum gpio_pins { + GPIOL0 = 0, + GPIOL1 = 1, + GPIOL2 = 2, + GPIOL3 = 3, + GPIOH0 = 4, + GPIOH1 = 5, + GPIOH2 = 6, + GPIOH3 = 7, + GPIOH4 = 8, + GPIOH5 = 9, + GPIOH6 = 10, + GPIOH7 = 11 +}; + +struct mpsse_context; + +int Write(struct mpsse_context *mpsse, char *data, int size); +int Stop(struct mpsse_context *mpsse); +char *Transfer(struct mpsse_context *mpsse, char *data, int size); +char *Read(struct mpsse_context *mpsse, int size); +struct mpsse_context *MPSSE(int freq, int endianness, const char *serial); +void Close(struct mpsse_context *mpsse); +int PinHigh(struct mpsse_context *mpsse, int pin); +int PinLow(struct mpsse_context *mpsse, int pin); +int Start(struct mpsse_context *mpsse); + +#endif /* ! __EC_TEST_TPM_TEST_MPSSE_H */ diff --git a/test/tpm_test/support.c b/test/tpm_test/support.c new file mode 100644 index 0000000000..fbb4d0b6ab --- /dev/null +++ b/test/tpm_test/support.c @@ -0,0 +1,218 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Based on Craig Heffner's version of Dec 27 2011, published on + * https://github.com/devttys0/libmpsse + * + * Internal functions used by libmpsse. + */ + +#include <string.h> +#include <stdlib.h> + +#if LIBFTDI1 == 1 +#include <libftdi1/ftdi.h> +#else +#include <ftdi.h> +#endif + +#include "support.h" + +/* Write data to the FTDI chip */ +int raw_write(struct mpsse_context *mpsse, unsigned char *buf, int size) +{ + int retval = MPSSE_FAIL; + + if (mpsse->mode && (ftdi_write_data(&mpsse->ftdi, buf, size) == size)) + retval = MPSSE_OK; + + return retval; +} + +/* Read data from the FTDI chip */ +int raw_read(struct mpsse_context *mpsse, unsigned char *buf, int size) +{ + int n = 0, r = 0; + + if (!mpsse->mode) + return 0; + + while (n < size) { + r = ftdi_read_data(&mpsse->ftdi, buf, size); + if (r < 0) + break; + n += r; + } + + if (mpsse->flush_after_read) { + /* + * Make sure the buffers are cleared after a read or + * subsequent reads may fail. Is this needed anymore? + * It slows down repetitive read operations by ~8%. + */ + ftdi_usb_purge_rx_buffer(&mpsse->ftdi); + } + + return n; +} + +/* Sets the read and write timeout periods for bulk usb data transfers. */ +void set_timeouts(struct mpsse_context *mpsse, int timeout) +{ + if (mpsse->mode) { + mpsse->ftdi.usb_read_timeout = timeout; + mpsse->ftdi.usb_write_timeout = timeout; + } +} + +/* Convert a frequency to a clock divisor */ +uint16_t freq2div(uint32_t system_clock, uint32_t freq) +{ + return (((system_clock / freq) / 2) - 1); +} + +/* Convert a clock divisor to a frequency */ +uint32_t div2freq(uint32_t system_clock, uint16_t div) +{ + return (system_clock / ((1 + div) * 2)); +} + +/* Builds a buffer of commands + data blocks */ +unsigned char *build_block_buffer(struct mpsse_context *mpsse, + uint8_t cmd, + unsigned char *data, int size, int *buf_size) +{ + unsigned char *buf = NULL; + int i = 0, j = 0, k = 0, dsize = 0, num_blocks = 0, total_size = + 0, xfer_size = 0; + uint16_t rsize = 0; + + *buf_size = 0; + + /* Data block size is 1 in I2C, or when in bitmode */ + if (mpsse->mode == I2C || (cmd & MPSSE_BITMODE)) + xfer_size = 1; + else + xfer_size = mpsse->xsize; + + num_blocks = (size / xfer_size); + if (size % xfer_size) + num_blocks++; + + /* + * The total size of the data will be the data size + the write + * command + */ + total_size = size + (CMD_SIZE * num_blocks); + + buf = malloc(total_size); + if (!buf) + return NULL; + + memset(buf, 0, total_size); + + for (j = 0; j < num_blocks; j++) { + dsize = size - k; + if (dsize > xfer_size) + dsize = xfer_size; + + /* The reported size of this block is block size - 1 */ + rsize = dsize - 1; + + /* Copy in the command for this block */ + buf[i++] = cmd; + buf[i++] = (rsize & 0xFF); + if (!(cmd & MPSSE_BITMODE)) + buf[i++] = ((rsize >> 8) & 0xFF); + + /* On a write, copy the data to transmit after the command */ + if (cmd == mpsse->tx || cmd == mpsse->txrx) { + + memcpy(buf + i, data + k, dsize); + + /* i == offset into buf */ + i += dsize; + /* k == offset into data */ + k += dsize; + } + } + + *buf_size = i; + + return buf; +} + +/* Set the low bit pins high/low */ +int set_bits_low(struct mpsse_context *mpsse, int port) +{ + char buf[CMD_SIZE] = { 0 }; + + buf[0] = SET_BITS_LOW; + buf[1] = port; + buf[2] = mpsse->tris; + + return raw_write(mpsse, (unsigned char *)&buf, sizeof(buf)); +} + +/* Set the high bit pins high/low */ +int set_bits_high(struct mpsse_context *mpsse, int port) +{ + char buf[CMD_SIZE] = { 0 }; + + buf[0] = SET_BITS_HIGH; + buf[1] = port; + buf[2] = mpsse->trish; + + return raw_write(mpsse, (unsigned char *)&buf, sizeof(buf)); +} + +/* Set the GPIO pins high/low */ +int gpio_write(struct mpsse_context *mpsse, int pin, int direction) +{ + int retval = MPSSE_FAIL; + + /* + * The first four pins can't be changed unless we are in a stopped + * status + */ + if (pin < NUM_GPIOL_PINS && mpsse->status == STOPPED) { + /* Convert pin number (0-3) to the corresponding pin bit */ + pin = (GPIO0 << pin); + + if (direction == HIGH) { + mpsse->pstart |= pin; + mpsse->pidle |= pin; + mpsse->pstop |= pin; + } else { + mpsse->pstart &= ~pin; + mpsse->pidle &= ~pin; + mpsse->pstop &= ~pin; + } + + retval = set_bits_low(mpsse, mpsse->pstop); + } else if (pin >= NUM_GPIOL_PINS && pin < NUM_GPIO_PINS) { + /* Convert pin number (4 - 11) to the corresponding pin bit */ + pin -= NUM_GPIOL_PINS; + + if (direction == HIGH) + mpsse->gpioh |= (1 << pin); + else + mpsse->gpioh &= ~(1 << pin); + + retval = set_bits_high(mpsse, mpsse->gpioh); + } + + return retval; +} + +/* Checks if a given MPSSE context is valid. */ +int is_valid_context(struct mpsse_context *mpsse) +{ + int retval = 0; + + if (mpsse != NULL && mpsse->open) + retval = 1; + + return retval; +} diff --git a/test/tpm_test/support.h b/test/tpm_test/support.h new file mode 100644 index 0000000000..77316582cc --- /dev/null +++ b/test/tpm_test/support.h @@ -0,0 +1,87 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Based on Craig Heffner's version of Dec 27 2011, published on + * https://github.com/devttys0/libmpsse + */ + +#ifndef __EC_TEST_TPM_TEST_SUPPORT_H +#define __EC_TEST_TPM_TEST_SUPPORT_H + +#include "mpsse.h" + +#define CMD_SIZE 3 +#define NUM_GPIOL_PINS 4 +#define NUM_GPIO_PINS 12 +#define LOW 0 +#define HIGH 1 + +/* Supported MPSSE modes */ +enum modes { + SPI0 = 1, + SPI1 = 2, + SPI2 = 3, + SPI3 = 4, + I2C = 5, + GPIO = 6, + BITBANG = 7, +}; + +enum low_bits_status { + STARTED, + STOPPED +}; + +enum pins { + SK = 1, + DO = 2, + DI = 4, + CS = 8, + GPIO0 = 16, + GPIO1 = 32, + GPIO2 = 64, + GPIO3 = 128 +}; + +struct mpsse_context { + char *description; + struct ftdi_context ftdi; + enum modes mode; + enum low_bits_status status; + int flush_after_read; + int vid; + int pid; + int clock; + int xsize; + int open; + int ftdi_initialized; + int endianness; + uint8_t tris; + uint8_t pstart; + uint8_t pstop; + uint8_t pidle; + uint8_t gpioh; + uint8_t trish; + uint8_t bitbang; + uint8_t tx; + uint8_t rx; + uint8_t txrx; + uint8_t tack; + uint8_t rack; +}; + +int raw_write(struct mpsse_context *mpsse, unsigned char *buf, int size); +int raw_read(struct mpsse_context *mpsse, unsigned char *buf, int size); +void set_timeouts(struct mpsse_context *mpsse, int timeout); +uint16_t freq2div(uint32_t system_clock, uint32_t freq); +uint32_t div2freq(uint32_t system_clock, uint16_t div); +unsigned char *build_block_buffer(struct mpsse_context *mpsse, + uint8_t cmd, + unsigned char *data, int size, int *buf_size); +int set_bits_high(struct mpsse_context *mpsse, int port); +int set_bits_low(struct mpsse_context *mpsse, int port); +int gpio_write(struct mpsse_context *mpsse, int pin, int direction); +int is_valid_context(struct mpsse_context *mpsse); + +#endif /* ! __EC_TEST_TPM_TEST_SUPPORT_H */ diff --git a/test/tpm_test/tpmtest.py b/test/tpm_test/tpmtest.py new file mode 100755 index 0000000000..c2056c01c7 --- /dev/null +++ b/test/tpm_test/tpmtest.py @@ -0,0 +1,364 @@ +#!/usr/bin/python +# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module for testing TPM, using both conventional and extended commands.""" + +from __future__ import print_function + +import os +import struct +import sys +import traceback +import xml.etree.ElementTree as ET + +# Suppressing pylint warning about an import not at the top of the file. The +# path needs to be set *before* the last import. +# pylint: disable=C6204 +root_dir = os.path.dirname(os.path.abspath(sys.argv[0])) +sys.path.append(os.path.join(root_dir, '..', '..', 'build', 'tpm_test')) +import ftdi_spi_tpm + +# Basic crypto operations +DECRYPT = 0 +ENCRYPT = 1 + +# Extension command for dcypto testing +EXT_CMD = 0xbaccd00a + +# Extension subcommands for encryption types +AES = 0 + +if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(): + cursor_back = '\x1b[1D' # Move one space to the left. +else: + cursor_back = '' + + +class TpmError(Exception): + pass + + +class TPM(object): + """TPM accessor class. + + Object of this class allows to send valid and extended TPM commands (using + the command() method. The wrap_command/unwrap_response methods provide a + means of encapsulating extended commands in proper TPM data packets, as well + as extracting extended command responses. + + Attributes: + _handle: a ftdi_spi_tpm object, a USB/FTDI/SPI driver which allows + communicate with a TPM connected over USB dongle. + """ + + HEADER_FMT = '>H2IH' + STARTUP_CMD = '80 01 00 00 00 0c 00 00 01 44 00 00' + STARTUP_RSP = ('80 01 00 00 00 0a 00 00 00 00', + '80 01 00 00 00 0a 00 00 01 00') + + def __init__(self, freq=800*1000, debug_mode=False): + self._handle = ftdi_spi_tpm + if not self._handle.FtdiSpiInit(freq, debug_mode): + raise TpmError() + response = self.command(''.join('%c' % int('0x%s' % x, 16) + for x in self.STARTUP_CMD.split())) + if ' '.join('%2.2x' % ord(x) for x in response) not in self.STARTUP_RSP: + raise TpmError('init failed') + + def validate(self, data_blob, response_mode=False): + """Check if a data blob complies with TPM command/response header format.""" + (tag, size, cmd_code, _) = struct.unpack_from( + self.HEADER_FMT, data_blob + ' ') + prefix = 'Misformatted blob: ' + if tag not in (0x8001, 0x8002): + raise TpmError(prefix + 'bad tag value 0x%4.4x' % tag) + if size != len(data_blob): + raise TpmError(prefix + 'size mismatch: header %d, actual %d' % + (size, len(data_blob))) + if size > 4096: + raise TpmError(prefix + 'invalid size %d' % size) + if response_mode: + return + if cmd_code >= 0x11f and cmd_code <= 0x18f: + return # This is a valid command + if cmd_code == EXT_CMD: + return # This is an extension command + + raise TpmError(prefix + 'invalid command code 0x%x' % cmd_code) + + def command(self, cmd_data): + # Verify command header + self.validate(cmd_data) + response = self._handle.FtdiSendCommandAndWait(cmd_data) + self.validate(response, response_mode=True) + return response + + def wrap_ext_command(self, cmd_code, subcmd_code, cmd_body): + return struct.pack(self.HEADER_FMT, 0x8001, + len(cmd_body) + struct.calcsize(self.HEADER_FMT), + cmd_code, subcmd_code) + cmd_body + + def unwrap_ext_response(self, expected_cmd, expected_subcmd, response): + """Verify basic validity and strip off TPM extended command header. + + Get the response generated by the device, as it came off the wire, verify + that header fields match expectations, then strip off the extension + command header and return the payload to the caller. + + Args: + expected_cmd: an int, up to 32 bits, expected value in the + command/response field of the header. expected_subcmd, response + expected_subcmd: an int, up to 16 bits in size, the extension command + this response is supposed to be for. + response: a binary string, the actual response received over the wire. + Returns: + the binary string of the response payload, if validation succeeded. + Raises: + TpmError: in case there are any validation problems, the error message + describes the problem. + """ + header_size = struct.calcsize(self.HEADER_FMT) + tag, size, cmd, subcmd = struct.unpack(self.HEADER_FMT, + response[:header_size]) + if tag != 0x8001: + raise TpmError('Wrong response tag: %4.4x' % tag) + if cmd != expected_cmd: + raise TpmError('Unexpected response command field: %8.8x' % cmd) + if subcmd != expected_subcmd: + raise TpmError('Unexpected response subcommand field: %2.2x' % + subcmd) + if size != len(response): + raise TpmError('Size mismatch: header %d, actual %d' % ( + size, len(response))) + return response[header_size:] + + +def hex_dump(binstr): + """Convert string into its hex representation.""" + dump_lines = ['',] + i = 0 + while i < len(binstr): + strsize = min(16, len(binstr) - i) + hexstr = ' '.join('%2.2x' % ord(x) for x in binstr[i:i+strsize]) + dump_lines.append(hexstr) + i += strsize + dump_lines.append('') + return '\n'.join(dump_lines) + + +def get_attribute(tdesc, attr_name, required=True): + """Retrieve an attribute value from an XML node. + + Args: + + tdesc: an Element of the ElementTree, a test descriptor containing + necessary information to run a single encryption/description + session. + attr_name: a string, the name of the attribute to retrieve. + required: a Boolean, if True - the attribute must be present in the + descriptor, otherwise it is considered optional + Returns: + The attribute value as a string (ascii or binary) + Raises: + TpmError: on various format errors, or in case a required attribute is not + found, the error message describes the problem. + + """ + # Fields stored in hex format by default. + default_hex = ('cipher_text', 'iv', 'key') + + data = tdesc.find(attr_name) + if data is None: + if required: + raise TpmError('node "%s" does not have attribute "%s"' % + (tdesc.get('name'), attr_name)) + return '' + + # Attribute is present, does it have to be decoded from hex? + cell_format = data.get('format') + if not cell_format: + if attr_name in default_hex: + cell_format = 'hex' + else: + cell_format = 'ascii' + elif cell_format not in ('hex', 'ascii'): + raise TpmError('%s:%s, unrecognizable format "%s"' % + (tdesc.get('name'), attr_name, cell_format)) + + text = ' '.join(x.strip() for x in data.text.splitlines() if x) + if cell_format == 'ascii': + return text + + # Drop spaces from hex representation. + text = text.replace(' ', '') + if len(text) & 3: + raise TpmError('%s:%s %swrong hex number size' % + (tdesc.get('name'), attr_name, hex_dump(text))) + # Convert text to binary + value = '' + for x in range(len(text)/8): + try: + value += struct.pack('<I', int('0x%s' % text[8*x:8*(x+1)], 16)) + except ValueError: + raise TpmError('%s:%s %swrong hex value' % + (tdesc.get('name'), attr_name, hex_dump(text))) + return value + + +class CryptoD(object): + """A helper object to contain an encryption scheme description. + + Attributes: + subcmd: a 16 bit max integer, the extension subcommand to be used with + this encryption scheme. + sumbodes: an optional dictionary, the keys are strings, names of the + encryption scheme submodes, the values are integers to be included in + the appropriate subcommand fields to communicat the submode to the + device. + """ + + def __init__(self, subcommand, submodes=None): + self.subcmd = subcommand + if not submodes: + submodes = {} + self.submodes = submodes + +SUPPORTED_MODES = { + 'AES': CryptoD(AES, { + 'ECB': 0, + 'CTR': 1, + 'CBC': 2, + 'GCM': 3 + }), +} + + +def crypto_run(node_name, op_type, key, iv, in_text, out_text, tpm, debug_mode): + """Perform a basic operation(encrypt or decrypt). + + This function creates an extended command with the requested parameters, + sends it to the device, and then compares the response to the expected + value. + + Args: + node_name: a string, the name of the XML node this data comes from. The + format of the name is "<enc type>:<submode> ....", where <enc type> is + the major encryption mode (say AED or DES) and submode - a variant of + the major scheme, if exists. + + op_type: an int, encodes the operation to perform (encrypt/decrypt), passed + directly to the device as a field in the extended command + key: a binary string + iv: a binary string, might be empty + in_text: a binary string, the input of the encrypt/decrypt operation + out_text: a binary string, might be empty, the expected output of the + operation. Note that it could be shorter than actual output (padded to + integer number of blocks), in which case only its length of bytes is + compared debug_mode: a Boolean, if True - enables tracing on the console + tpm: a TPM object to send extended commands to an initialized TPM + debug_mode: a Boolean, if True - this function and the FTDI driver + generate debug messated on the console. + + Returns: + The actual binary string, result of the operation, if the + comparison with the expected value was successful. + + Raises: + TpmError: in case there were problems parsing the node name, or verifying + the operation results. + """ + mode_name, submode_name = node_name.split(':') + submode_name = submode_name[:3].upper() + + mode = SUPPORTED_MODES.get(mode_name.upper()) + if not mode: + raise TpmError('unrecognizable mode in node "%s"' % node_name) + + submode = mode.submodes.get(submode_name, 0) + cmd = '%c' % op_type # Encrypt or decrypt + cmd += '%c' % submode # A particular type of a generic algorithm. + cmd += '%c' % len(key) + cmd += key + cmd += '%c' % len(iv) + if iv: + cmd += iv + cmd += struct.pack('>H', len(in_text)) + cmd += in_text + if debug_mode: + print('%d:%d cmd size' % (op_type, mode.subcmd), len(cmd), hex_dump(cmd)) + wrapped_response = tpm.command(tpm.wrap_ext_command( + EXT_CMD, mode.subcmd, cmd)) + real_out_text = tpm.unwrap_ext_response( + EXT_CMD, mode.subcmd, wrapped_response) + if out_text: + if len(real_out_text) > len(out_text): + real_out_text = real_out_text[:len(out_text)] # Ignore padding + if real_out_text != out_text: + if debug_mode: + print('Out text mismatch in node %s:\n' % node_name) + else: + raise TpmError('Out text mismatch in node %s, operation %d:\n' + 'In text:%sExpected out text:%sReal out text:%s' % ( + node_name, op_type, + hex_dump(in_text), + hex_dump(out_text), + hex_dump(real_out_text))) + return real_out_text + + +def crypto_test(tdesc, tpm, debug_mode): + """Perform a single test described in the xml file. + + The xml node contains all pertinent information about the test inputs and + outputs. + + Args: + tdesc: an Element of the ElementTree, a test descriptor containing + necessary information to run a single encryption/description + session. + tpm: a TPM object to send extended commands to an initialized TPM + debug_mode: a Boolean, if True - this function and the FTDI driver + generate debug messated on the console. + Raises: + TpmError: on various execution errors, the details are included in the + error message. + """ + node_name = tdesc.get('name') + key = get_attribute(tdesc, 'key') + if len(key) not in (16, 24, 32): + raise TpmError('wrong key size "%s:%s"' % ( + node_name, + ''.join('%2.2x' % ord(x) for x in key))) + iv = get_attribute(tdesc, 'iv', required=False) + if iv and len(iv) != 16: + raise TpmError('wrong iv size "%s:%s"' % ( + node_name, + ''.join('%2.2x' % ord(x) for x in iv))) + clear_text = get_attribute(tdesc, 'clear_text') + if debug_mode: + print('clear text size', len(clear_text)) + cipher_text = get_attribute(tdesc, 'cipher_text', required=False) + real_cipher_text = crypto_run(node_name, ENCRYPT, key, iv, + clear_text, cipher_text, tpm, debug_mode) + crypto_run(node_name, DECRYPT, key, iv, real_cipher_text, + clear_text, tpm, debug_mode) + print(cursor_back + 'SUCCESS: %s' % node_name) + + +if __name__ == '__main__': + tree = ET.parse(os.path.join(root_dir, 'crypto_test.xml')) + root = tree.getroot() + try: + debug_needed = len(sys.argv) == 2 and sys.argv[1] == '-d' + t = TPM(debug_mode=debug_needed) + + for child in root: + crypto_test(child, t, debug_needed) + except TpmError as e: + print() + print(e) + if debug_needed: + traceback.print_exc() + sys.exit(1) |