From 0e37d4c2c631b0e94b7e891a5b37650d9bbd143c Mon Sep 17 00:00:00 2001 From: Christophe Ricard Date: Thu, 21 Jan 2016 23:19:13 +0100 Subject: [PATCH 01/36] tpm: Fix fault in case CONFIG_DM_TPM is set without any TPM In case CONFIG_DM_TPM was set without any TPM chipset configured a fault was generated (NULL pointer access). Reviewed-by: Simon Glass Signed-off-by: Christophe Ricard --- cmd/tpm.c | 2 +- lib/tpm.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/tpm.c b/cmd/tpm.c index add6bfb416..6edf3e9dc3 100644 --- a/cmd/tpm.c +++ b/cmd/tpm.c @@ -448,7 +448,7 @@ static int get_tpm(struct udevice **devp) int rc; rc = uclass_first_device(UCLASS_TPM, devp); - if (rc) { + if (rc || !*devp) { printf("Could not find TPM (ret=%d)\n", rc); return CMD_RET_FAILURE; } diff --git a/lib/tpm.c b/lib/tpm.c index 8a62216274..f428d454fb 100644 --- a/lib/tpm.c +++ b/lib/tpm.c @@ -262,7 +262,7 @@ int tpm_init(void) struct udevice *dev; err = uclass_first_device(UCLASS_TPM, &dev); - if (err) + if (err || !dev) return err; return tpm_open(dev); } From ca5bc1bc107c0413f96a6d79b4e95278a37c0cce Mon Sep 17 00:00:00 2001 From: Christophe Ricard Date: Thu, 21 Jan 2016 23:19:14 +0100 Subject: [PATCH 02/36] tpm: tpm_tis_lpc: fix typo TPM_TIS_LPC is connected to the LPC bus, not I2C. Reviewed-by: Simon Glass Signed-off-by: Christophe Ricard --- drivers/tpm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tpm/Kconfig b/drivers/tpm/Kconfig index 31b35f75c9..5a75f85b03 100644 --- a/drivers/tpm/Kconfig +++ b/drivers/tpm/Kconfig @@ -50,7 +50,7 @@ config TPM_TIS_LPC bool "Enable support for Infineon SLB9635/45 TPMs on LPC" depends on TPM && X86 help - This driver supports Infineon TPM devices connected on the I2C bus. + This driver supports Infineon TPM devices connected on the LPC bus. The usual tpm operations and the 'tpm' command can be used to talk to the device using the standard TPM Interface Specification (TIS) protocol From 1259dcd79c70f97a1606b4b06190c8fa7a1810d1 Mon Sep 17 00:00:00 2001 From: Christophe Ricard Date: Thu, 21 Jan 2016 23:27:12 +0100 Subject: [PATCH 03/36] tpm: Rename tpm_tis_infineon.h to tpm_tis.h and move infineon specific stuff in tpm_infineon.c I2C protocol is not standardize for TPM 1.2. TIS prococol is define by the Trusted Computing Group and potentially available on several TPMs. tpm_tis_infineon.h header is not generic enough. Rename tpm_tis_infineon.h to tpm_tis.h and move infineon specific defines/variables to tpm_tis_infineon.c Reviewed-by: Simon Glass Signed-off-by: Christophe Ricard --- drivers/tpm/{tpm_tis_infineon.h => tpm_tis.h} | 17 +---------------- drivers/tpm/tpm_tis_infineon.c | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 17 deletions(-) rename drivers/tpm/{tpm_tis_infineon.h => tpm_tis.h} (86%) diff --git a/drivers/tpm/tpm_tis_infineon.h b/drivers/tpm/tpm_tis.h similarity index 86% rename from drivers/tpm/tpm_tis_infineon.h rename to drivers/tpm/tpm_tis.h index 3b510d101e..25b152b321 100644 --- a/drivers/tpm/tpm_tis_infineon.h +++ b/drivers/tpm/tpm_tis.h @@ -37,18 +37,12 @@ enum tpm_timeout { #define TPM_RSP_SIZE_BYTE 2 #define TPM_RSP_RC_BYTE 6 -enum i2c_chip_type { - SLB9635, - SLB9645, - UNKNOWN, -}; - struct tpm_chip { int is_open; int locality; u32 vend_dev; unsigned long timeout_a, timeout_b, timeout_c, timeout_d; /* msec */ - enum i2c_chip_type chip_type; + ulong chip_type; }; struct tpm_input_header { @@ -134,13 +128,4 @@ enum tis_status { TPM_STS_DATA_EXPECT = 0x08, }; -/* expected value for DIDVID register */ -#define TPM_TIS_I2C_DID_VID_9635 0x000b15d1L -#define TPM_TIS_I2C_DID_VID_9645 0x001a15d1L - -#define TPM_ACCESS(l) (0x0000 | ((l) << 4)) -#define TPM_STS(l) (0x0001 | ((l) << 4)) -#define TPM_DATA_FIFO(l) (0x0005 | ((l) << 4)) -#define TPM_DID_VID(l) (0x0006 | ((l) << 4)) - #endif diff --git a/drivers/tpm/tpm_tis_infineon.c b/drivers/tpm/tpm_tis_infineon.c index f57c32837b..a4b6741676 100644 --- a/drivers/tpm/tpm_tis_infineon.c +++ b/drivers/tpm/tpm_tis_infineon.c @@ -30,17 +30,32 @@ #include #include -#include "tpm_tis_infineon.h" +#include "tpm_tis.h" #include "tpm_internal.h" DECLARE_GLOBAL_DATA_PTR; +enum i2c_chip_type { + SLB9635, + SLB9645, + UNKNOWN, +}; + +/* expected value for DIDVID register */ +#define TPM_TIS_I2C_DID_VID_9635 0x000b15d1L +#define TPM_TIS_I2C_DID_VID_9645 0x001a15d1L + static const char * const chip_name[] = { [SLB9635] = "slb9635tt", [SLB9645] = "slb9645tt", [UNKNOWN] = "unknown/fallback to slb9635", }; +#define TPM_ACCESS(l) (0x0000 | ((l) << 4)) +#define TPM_STS(l) (0x0001 | ((l) << 4)) +#define TPM_DATA_FIFO(l) (0x0005 | ((l) << 4)) +#define TPM_DID_VID(l) (0x0006 | ((l) << 4)) + /* * tpm_tis_i2c_read() - read from TPM register * @addr: register address to read from From 3aa74088d4d3cedcfed403fea8eb75831021959a Mon Sep 17 00:00:00 2001 From: Christophe Ricard Date: Thu, 21 Jan 2016 23:27:13 +0100 Subject: [PATCH 04/36] tpm: st33zp24: Add tpm st33zp24 support with i2c Add support for TPM ST33ZP24 family with i2c. For i2c we are relying only on DM_I2C. Reviewed-by: Simon Glass Signed-off-by: Christophe Ricard --- README | 7 + drivers/tpm/Kconfig | 9 + drivers/tpm/Makefile | 1 + drivers/tpm/tpm_tis_st33zp24_i2c.c | 543 +++++++++++++++++++++++++++++ 4 files changed, 560 insertions(+) create mode 100644 drivers/tpm/tpm_tis_st33zp24_i2c.c diff --git a/README b/README index 864c7cccc3..ec73a4e5d9 100644 --- a/README +++ b/README @@ -1423,6 +1423,13 @@ The following options need to be configured: CONFIG_TPM_TIS_I2C_BURST_LIMITATION Define the burst count bytes upper limit + CONFIG_TPM_ST33ZP24 + Support for STMicroelectronics TPM devices. Requires DM_TPM support. + + CONFIG_TPM_ST33ZP24_I2C + Support for STMicroelectronics ST33ZP24 I2C devices. + Requires TPM_ST33ZP24 and I2C. + CONFIG_TPM_ATMEL_TWI Support for Atmel TWI TPM device. Requires I2C support. diff --git a/drivers/tpm/Kconfig b/drivers/tpm/Kconfig index 5a75f85b03..9432160667 100644 --- a/drivers/tpm/Kconfig +++ b/drivers/tpm/Kconfig @@ -64,4 +64,13 @@ config TPM_AUTH_SESSIONS TPM_LoadKey2 and TPM_GetPubKey are provided. Both features are available using the 'tpm' command, too. +config TPM_ST33ZP24_I2C + bool "STMicroelectronics ST33ZP24 I2C TPM" + depends on TPM && DM_I2C + ---help--- + This driver supports STMicroelectronics TPM devices connected on the I2C bus. + The usual tpm operations and the 'tpm' command can be used to talk + to the device using the standard TPM Interface Specification (TIS) + protocol + endmenu diff --git a/drivers/tpm/Makefile b/drivers/tpm/Makefile index 1d49e952ae..cb066d78d0 100644 --- a/drivers/tpm/Makefile +++ b/drivers/tpm/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_TPM_ATMEL_TWI) += tpm_atmel_twi.o obj-$(CONFIG_TPM_TIS_INFINEON) += tpm_tis_infineon.o obj-$(CONFIG_TPM_TIS_LPC) += tpm_tis_lpc.o obj-$(CONFIG_TPM_TIS_SANDBOX) += tpm_tis_sandbox.o +obj-$(CONFIG_TPM_ST33ZP24_I2C) += tpm_tis_st33zp24_i2c.o diff --git a/drivers/tpm/tpm_tis_st33zp24_i2c.c b/drivers/tpm/tpm_tis_st33zp24_i2c.c new file mode 100644 index 0000000000..9e4829fea2 --- /dev/null +++ b/drivers/tpm/tpm_tis_st33zp24_i2c.c @@ -0,0 +1,543 @@ +/* + * STMicroelectronics TPM ST33ZP24 I2C UBOOT driver + * + * Copyright (C) 2016 STMicroelectronics + * + * Description: Device driver for ST33ZP24 I2C TPM TCG. + * + * This device driver implements the TPM interface as defined in + * the TCG TPM Interface Spec version 1.21, revision 1.0 and the + * STMicroelectronics Protocol Stack Specification version 1.2.0. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpm_tis.h" +#include "tpm_internal.h" + +#define TPM_ACCESS 0x0 +#define TPM_STS 0x18 +#define TPM_DATA_FIFO 0x24 + +#define LOCALITY0 0 + +#define TPM_DUMMY_BYTE 0xAA +#define TPM_ST33ZP24_I2C_SLAVE_ADDR 0x13 + +#define TPM_WRITE_DIRECTION 0x80 + +/* + * st33zp24_i2c_write8_reg + * Send byte to the TIS register according to the ST33ZP24 I2C protocol. + * @param: tpm_register, the tpm tis register where the data should be written + * @param: tpm_data, the tpm_data to write inside the tpm_register + * @param: tpm_size, The length of the data + * @return: Number of byte written successfully else an error code. + */ +static int st33zp24_i2c_write8_reg(struct udevice *dev, u8 tpm_register, + const u8 *tpm_data, size_t tpm_size) +{ + struct tpm_chip_priv *chip_priv = dev_get_uclass_priv(dev); + + chip_priv->buf[0] = tpm_register; + memcpy(chip_priv->buf + 1, tpm_data, tpm_size); + + return dm_i2c_write(dev, 0, chip_priv->buf, tpm_size + 1); +} + +/* +* st33zp24_i2c_read8_reg +* Recv byte from the TIS register according to the ST33ZP24 I2C protocol. +* @param: tpm_register, the tpm tis register where the data should be read +* @param: tpm_data, the TPM response +* @param: tpm_size, tpm TPM response size to read. +* @return: Number of byte read successfully else an error code. +*/ +static int st33zp24_i2c_read8_reg(struct udevice *dev, u8 tpm_register, + u8 *tpm_data, size_t tpm_size) +{ + int status; + u8 data; + + data = TPM_DUMMY_BYTE; + status = st33zp24_i2c_write8_reg(dev, tpm_register, &data, 1); + if (status < 0) + return status; + + return dm_i2c_read(dev, 0, tpm_data, tpm_size); +} + +/* + * st33zp24_i2c_write + * Send byte to the TIS register according to the ST33ZP24 I2C protocol. + * @param: phy_id, the phy description + * @param: tpm_register, the tpm tis register where the data should be written + * @param: tpm_data, the tpm_data to write inside the tpm_register + * @param: tpm_size, the length of the data + * @return: number of byte written successfully: should be one if success. + */ +static int st33zp24_i2c_write(struct udevice *dev, u8 tpm_register, + const u8 *tpm_data, size_t tpm_size) +{ + return st33zp24_i2c_write8_reg(dev, tpm_register | TPM_WRITE_DIRECTION, + tpm_data, tpm_size); +} + +/* + * st33zp24_i2c_read + * Recv byte from the TIS register according to the ST33ZP24 I2C protocol. + * @param: phy_id, the phy description + * @param: tpm_register, the tpm tis register where the data should be read + * @param: tpm_data, the TPM response + * @param: tpm_size, tpm TPM response size to read. + * @return: number of byte read successfully: should be one if success. + */ +static int st33zp24_i2c_read(struct udevice *dev, u8 tpm_register, + u8 *tpm_data, size_t tpm_size) +{ + return st33zp24_i2c_read8_reg(dev, tpm_register, tpm_data, tpm_size); +} + +/* + * st33zp24_i2c_release_locality release the active locality + * @param: chip, the tpm chip description. + */ +static void st33zp24_i2c_release_locality(struct udevice *dev) +{ + u8 data = TPM_ACCESS_ACTIVE_LOCALITY; + + st33zp24_i2c_write(dev, TPM_ACCESS, &data, 1); +} + +/* + * st33zp24_i2c_check_locality if the locality is active + * @param: chip, the tpm chip description + * @return: the active locality or -EACCES. + */ +static int st33zp24_i2c_check_locality(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + u8 data; + u8 status; + + status = st33zp24_i2c_read(dev, TPM_ACCESS, &data, 1); + if (!status && (data & + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) + return chip->locality; + + return -EACCES; +} + +/* + * st33zp24_i2c_request_locality request the TPM locality + * @param: chip, the chip description + * @return: the active locality or negative value. + */ +static int st33zp24_i2c_request_locality(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + unsigned long start, stop; + long ret; + u8 data; + + if (st33zp24_i2c_check_locality(dev) == chip->locality) + return chip->locality; + + data = TPM_ACCESS_REQUEST_USE; + ret = st33zp24_i2c_write(dev, TPM_ACCESS, &data, 1); + if (ret < 0) + return ret; + + /* wait for locality activated */ + start = get_timer(0); + stop = chip->timeout_a; + do { + if (st33zp24_i2c_check_locality(dev) >= 0) + return chip->locality; + udelay(TPM_TIMEOUT_MS * 1000); + } while (get_timer(start) < stop); + + return -EACCES; +} + +/* + * st33zp24_i2c_status return the TPM_STS register + * @param: chip, the tpm chip description + * @return: the TPM_STS register value. + */ +static u8 st33zp24_i2c_status(struct udevice *dev) +{ + u8 data; + + st33zp24_i2c_read(dev, TPM_STS, &data, 1); + + return data; +} + +/* + * st33zp24_i2c_get_burstcount return the burstcount address 0x19 0x1A + * @param: chip, the chip description + * return: the burstcount or -TPM_DRIVER_ERR in case of error. + */ +static int st33zp24_i2c_get_burstcount(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + unsigned long start, stop; + int burstcnt, status; + u8 tpm_reg, temp; + + /* wait for burstcount */ + start = get_timer(0); + stop = chip->timeout_d; + do { + tpm_reg = TPM_STS + 1; + status = st33zp24_i2c_read(dev, tpm_reg, &temp, 1); + if (status < 0) + return -EBUSY; + + tpm_reg = TPM_STS + 2; + burstcnt = temp; + status = st33zp24_i2c_read(dev, tpm_reg, &temp, 1); + if (status < 0) + return -EBUSY; + + burstcnt |= temp << 8; + if (burstcnt) + return burstcnt; + udelay(TIS_SHORT_TIMEOUT_MS * 1000); + } while (get_timer(start) < stop); + + return -EBUSY; +} + +/* + * st33zp24_i2c_cancel, cancel the current command execution or + * set STS to COMMAND READY. + * @param: chip, tpm_chip description. + */ +static void st33zp24_i2c_cancel(struct udevice *dev) +{ + u8 data; + + data = TPM_STS_COMMAND_READY; + st33zp24_i2c_write(dev, TPM_STS, &data, 1); +} + +/* + * st33zp24_i2c_wait_for_stat wait for a TPM_STS value + * @param: chip, the tpm chip description + * @param: mask, the value mask to wait + * @param: timeout, the timeout + * @param: status, + * @return: the tpm status, 0 if success, -ETIME if timeout is reached. + */ +static int st33zp24_i2c_wait_for_stat(struct udevice *dev, u8 mask, + unsigned long timeout, int *status) +{ + unsigned long start, stop; + + /* Check current status */ + *status = st33zp24_i2c_status(dev); + if ((*status & mask) == mask) + return 0; + + start = get_timer(0); + stop = timeout; + do { + udelay(TPM_TIMEOUT_MS * 1000); + *status = st33zp24_i2c_status(dev); + if ((*status & mask) == mask) + return 0; + } while (get_timer(start) < stop); + + return -ETIME; +} + +/* + * st33zp24_i2c_recv_data receive data + * @param: chip, the tpm chip description + * @param: buf, the buffer where the data are received + * @param: count, the number of data to receive + * @return: the number of bytes read from TPM FIFO. + */ +static int st33zp24_i2c_recv_data(struct udevice *dev, u8 *buf, size_t count) +{ + struct tpm_chip *chip = dev_get_priv(dev); + int size = 0, burstcnt, len, ret, status; + + while (size < count && + st33zp24_i2c_wait_for_stat(dev, TPM_STS_DATA_AVAIL | TPM_STS_VALID, + chip->timeout_c, &status) == 0) { + burstcnt = st33zp24_i2c_get_burstcount(dev); + if (burstcnt < 0) + return burstcnt; + len = min_t(int, burstcnt, count - size); + ret = st33zp24_i2c_read(dev, TPM_DATA_FIFO, buf + size, len); + if (ret < 0) + return ret; + + size += len; + } + + return size; +} + +/* + * st33zp24_i2c_recv received TPM response through TPM phy. + * @param: chip, tpm_chip description. + * @param: buf, the buffer to store data. + * @param: count, the number of bytes that can received (sizeof buf). + * @return: Returns zero in case of success else -EIO. + */ +static int st33zp24_i2c_recv(struct udevice *dev, u8 *buf, size_t count) +{ + struct tpm_chip *chip = dev_get_priv(dev); + int size, expected; + + if (!chip) + return -ENODEV; + + if (count < TPM_HEADER_SIZE) { + size = -EIO; + goto out; + } + + size = st33zp24_i2c_recv_data(dev, buf, TPM_HEADER_SIZE); + if (size < TPM_HEADER_SIZE) { + debug("TPM error, unable to read header\n"); + goto out; + } + + expected = get_unaligned_be32(buf + 2); + if (expected > count) { + size = -EIO; + goto out; + } + + size += st33zp24_i2c_recv_data(dev, &buf[TPM_HEADER_SIZE], + expected - TPM_HEADER_SIZE); + if (size < expected) { + debug("TPM error, unable to read remaining bytes of result\n"); + size = -EIO; + goto out; + } + +out: + st33zp24_i2c_cancel(dev); + st33zp24_i2c_release_locality(dev); + + return size; +} + +/* + * st33zp24_i2c_send send TPM commands through TPM phy. + * @param: chip, tpm_chip description. + * @param: buf, the buffer to send. + * @param: len, the number of bytes to send. + * @return: Returns zero in case of success else the negative error code. + */ +static int st33zp24_i2c_send(struct udevice *dev, const u8 *buf, size_t len) +{ + struct tpm_chip *chip = dev_get_priv(dev); + u32 i, size; + int burstcnt, ret, status; + u8 data, tpm_stat; + + if (!chip) + return -ENODEV; + if (len < TPM_HEADER_SIZE) + return -EIO; + + ret = st33zp24_i2c_request_locality(dev); + if (ret < 0) + return ret; + + tpm_stat = st33zp24_i2c_status(dev); + if ((tpm_stat & TPM_STS_COMMAND_READY) == 0) { + st33zp24_i2c_cancel(dev); + if (st33zp24_i2c_wait_for_stat(dev, TPM_STS_COMMAND_READY, + chip->timeout_b, &status) < 0) { + ret = -ETIME; + goto out_err; + } + } + + for (i = 0; i < len - 1;) { + burstcnt = st33zp24_i2c_get_burstcount(dev); + if (burstcnt < 0) + return burstcnt; + + size = min_t(int, len - i - 1, burstcnt); + ret = st33zp24_i2c_write(dev, TPM_DATA_FIFO, buf + i, size); + if (ret < 0) + goto out_err; + + i += size; + } + + tpm_stat = st33zp24_i2c_status(dev); + if ((tpm_stat & TPM_STS_DATA_EXPECT) == 0) { + ret = -EIO; + goto out_err; + } + + ret = st33zp24_i2c_write(dev, TPM_DATA_FIFO, buf + len - 1, 1); + if (ret < 0) + goto out_err; + + tpm_stat = st33zp24_i2c_status(dev); + if ((tpm_stat & TPM_STS_DATA_EXPECT) != 0) { + ret = -EIO; + goto out_err; + } + + data = TPM_STS_GO; + ret = st33zp24_i2c_write(dev, TPM_STS, &data, 1); + if (ret < 0) + goto out_err; + + return len; + +out_err: + st33zp24_i2c_cancel(dev); + st33zp24_i2c_release_locality(dev); + + return ret; +} + +static int st33zp24_i2c_cleanup(struct udevice *dev) +{ + st33zp24_i2c_cancel(dev); + /* + * The TPM needs some time to clean up here, + * so we sleep rather than keeping the bus busy + */ + mdelay(2); + st33zp24_i2c_release_locality(dev); + + return 0; +} + +static int st33zp24_i2c_init(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + + chip->is_open = 1; + + /* Default timeouts - these could move to the device tree */ + chip->timeout_a = TIS_SHORT_TIMEOUT_MS; + chip->timeout_b = TIS_LONG_TIMEOUT_MS; + chip->timeout_c = TIS_SHORT_TIMEOUT_MS; + chip->timeout_d = TIS_SHORT_TIMEOUT_MS; + + chip->locality = LOCALITY0; + + /* + * A timeout query to TPM can be placed here. + * Standard timeout values are used so far + */ + + return 0; +} + +static int st33zp24_i2c_open(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + int rc; + + debug("%s: start\n", __func__); + if (chip->is_open) + return -EBUSY; + + rc = st33zp24_i2c_init(dev); + if (rc < 0) + chip->is_open = 0; + + return rc; +} + +static int st33zp24_i2c_close(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + + if (chip->is_open) { + st33zp24_i2c_release_locality(dev); + chip->is_open = 0; + chip->vend_dev = 0; + } + + return 0; +} + +static int st33zp24_i2c_get_desc(struct udevice *dev, char *buf, int size) +{ + struct tpm_chip *chip = dev_get_priv(dev); + + if (size < 50) + return -ENOSPC; + + return snprintf(buf, size, "1.2 TPM (%s, chip type %s device-id 0x%x)", + chip->is_open ? "open" : "closed", + dev->name, + chip->vend_dev >> 16); +} + +static const struct tpm_ops st33zp24_i2c_tpm_ops = { + .open = st33zp24_i2c_open, + .close = st33zp24_i2c_close, + .recv = st33zp24_i2c_recv, + .send = st33zp24_i2c_send, + .cleanup = st33zp24_i2c_cleanup, + .get_desc = st33zp24_i2c_get_desc, +}; + +static int st33zp24_i2c_probe(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + + /* Default timeouts */ + chip->timeout_a = TIS_SHORT_TIMEOUT_MS; + chip->timeout_b = TIS_LONG_TIMEOUT_MS; + chip->timeout_c = TIS_SHORT_TIMEOUT_MS; + chip->timeout_d = TIS_SHORT_TIMEOUT_MS; + + chip->locality = LOCALITY0; + + i2c_set_chip_offset_len(dev, 0); + + debug("ST33ZP24 I2C TPM from STMicroelectronics found\n"); + + return 0; +} + +static int st33zp24_i2c_remove(struct udevice *dev) +{ + st33zp24_i2c_release_locality(dev); + + return 0; +} + +static const struct udevice_id st33zp24_i2c_ids[] = { + { .compatible = "st,st33zp24-i2c" }, + { } +}; + +U_BOOT_DRIVER(st33zp24_i2c) = { + .name = "st33zp24-i2c", + .id = UCLASS_TPM, + .of_match = of_match_ptr(st33zp24_i2c_ids), + .probe = st33zp24_i2c_probe, + .remove = st33zp24_i2c_remove, + .ops = &st33zp24_i2c_tpm_ops, + .priv_auto_alloc_size = sizeof(struct tpm_chip), +}; From b75fdc11ebcd3607f840a00363679a3a5cbc8da4 Mon Sep 17 00:00:00 2001 From: Christophe Ricard Date: Thu, 21 Jan 2016 23:27:14 +0100 Subject: [PATCH 05/36] tpm: st33zp24: Add tpm st33zp24 spi support Add support for TPM ST33ZP24 spi. The ST33ZP24 does have a spi interface. The transport protocol is proprietary. For spi we are relying only on DM_SPI. Reviewed-by: Simon Glass Signed-off-by: Christophe Ricard --- README | 4 + drivers/tpm/Kconfig | 9 + drivers/tpm/Makefile | 1 + drivers/tpm/tpm_tis_st33zp24_spi.c | 672 +++++++++++++++++++++++++++++ 4 files changed, 686 insertions(+) create mode 100644 drivers/tpm/tpm_tis_st33zp24_spi.c diff --git a/README b/README index ec73a4e5d9..c7c9e0a9e3 100644 --- a/README +++ b/README @@ -1430,6 +1430,10 @@ The following options need to be configured: Support for STMicroelectronics ST33ZP24 I2C devices. Requires TPM_ST33ZP24 and I2C. + CONFIG_TPM_ST33ZP24_SPI + Support for STMicroelectronics ST33ZP24 SPI devices. + Requires TPM_ST33ZP24 and SPI. + CONFIG_TPM_ATMEL_TWI Support for Atmel TWI TPM device. Requires I2C support. diff --git a/drivers/tpm/Kconfig b/drivers/tpm/Kconfig index 9432160667..9a7b7f535f 100644 --- a/drivers/tpm/Kconfig +++ b/drivers/tpm/Kconfig @@ -73,4 +73,13 @@ config TPM_ST33ZP24_I2C to the device using the standard TPM Interface Specification (TIS) protocol +config TPM_ST33ZP24_SPI + bool "STMicroelectronics ST33ZP24 SPI TPM" + depends on TPM && DM_SPI + ---help--- + This driver supports STMicroelectronics TPM devices connected on the SPI bus. + The usual tpm operations and the 'tpm' command can be used to talk + to the device using the standard TPM Interface Specification (TIS) + protocol + endmenu diff --git a/drivers/tpm/Makefile b/drivers/tpm/Makefile index cb066d78d0..c42a93f267 100644 --- a/drivers/tpm/Makefile +++ b/drivers/tpm/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_TPM_TIS_INFINEON) += tpm_tis_infineon.o obj-$(CONFIG_TPM_TIS_LPC) += tpm_tis_lpc.o obj-$(CONFIG_TPM_TIS_SANDBOX) += tpm_tis_sandbox.o obj-$(CONFIG_TPM_ST33ZP24_I2C) += tpm_tis_st33zp24_i2c.o +obj-$(CONFIG_TPM_ST33ZP24_SPI) += tpm_tis_st33zp24_spi.o diff --git a/drivers/tpm/tpm_tis_st33zp24_spi.c b/drivers/tpm/tpm_tis_st33zp24_spi.c new file mode 100644 index 0000000000..417bbf1c69 --- /dev/null +++ b/drivers/tpm/tpm_tis_st33zp24_spi.c @@ -0,0 +1,672 @@ +/* + * STMicroelectronics TPM ST33ZP24 SPI UBOOT driver + * + * Copyright (C) 2016 STMicroelectronics + * + * Description: Device driver for ST33ZP24 SPI TPM TCG. + * + * This device driver implements the TPM interface as defined in + * the TCG TPM Interface Spec version 1.21, revision 1.0 and the + * STMicroelectronics Protocol Stack Specification version 1.2.0. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpm_tis.h" +#include "tpm_internal.h" + +#define TPM_ACCESS 0x0 +#define TPM_STS 0x18 +#define TPM_DATA_FIFO 0x24 + +#define LOCALITY0 0 + +#define TPM_DATA_FIFO 0x24 +#define TPM_INTF_CAPABILITY 0x14 + +#define TPM_DUMMY_BYTE 0x00 +#define TPM_WRITE_DIRECTION 0x80 + +#define MAX_SPI_LATENCY 15 +#define LOCALITY0 0 + +#define ST33ZP24_OK 0x5A +#define ST33ZP24_UNDEFINED_ERR 0x80 +#define ST33ZP24_BADLOCALITY 0x81 +#define ST33ZP24_TISREGISTER_UKNOWN 0x82 +#define ST33ZP24_LOCALITY_NOT_ACTIVATED 0x83 +#define ST33ZP24_HASH_END_BEFORE_HASH_START 0x84 +#define ST33ZP24_BAD_COMMAND_ORDER 0x85 +#define ST33ZP24_INCORECT_RECEIVED_LENGTH 0x86 +#define ST33ZP24_TPM_FIFO_OVERFLOW 0x89 +#define ST33ZP24_UNEXPECTED_READ_FIFO 0x8A +#define ST33ZP24_UNEXPECTED_WRITE_FIFO 0x8B +#define ST33ZP24_CMDRDY_SET_WHEN_PROCESSING_HASH_END 0x90 +#define ST33ZP24_DUMMY_BYTES 0x00 + +/* + * TPM command can be up to 2048 byte, A TPM response can be up to + * 1024 byte. + * Between command and response, there are latency byte (up to 15 + * usually on st33zp24 2 are enough). + * + * Overall when sending a command and expecting an answer we need if + * worst case: + * 2048 (for the TPM command) + 1024 (for the TPM answer). We need + * some latency byte before the answer is available (max 15). + * We have 2048 + 1024 + 15. + */ +#define ST33ZP24_SPI_BUFFER_SIZE (TPM_BUFSIZE + (TPM_BUFSIZE / 2) +\ + MAX_SPI_LATENCY) + +struct st33zp24_spi_phy { + int latency; + + u8 tx_buf[ST33ZP24_SPI_BUFFER_SIZE]; + u8 rx_buf[ST33ZP24_SPI_BUFFER_SIZE]; +}; + +static int st33zp24_spi_status_to_errno(u8 code) +{ + switch (code) { + case ST33ZP24_OK: + return 0; + case ST33ZP24_UNDEFINED_ERR: + case ST33ZP24_BADLOCALITY: + case ST33ZP24_TISREGISTER_UKNOWN: + case ST33ZP24_LOCALITY_NOT_ACTIVATED: + case ST33ZP24_HASH_END_BEFORE_HASH_START: + case ST33ZP24_BAD_COMMAND_ORDER: + case ST33ZP24_UNEXPECTED_READ_FIFO: + case ST33ZP24_UNEXPECTED_WRITE_FIFO: + case ST33ZP24_CMDRDY_SET_WHEN_PROCESSING_HASH_END: + return -EPROTO; + case ST33ZP24_INCORECT_RECEIVED_LENGTH: + case ST33ZP24_TPM_FIFO_OVERFLOW: + return -EMSGSIZE; + case ST33ZP24_DUMMY_BYTES: + return -ENOSYS; + } + return code; +} + +/* + * st33zp24_spi_send + * Send byte to TPM register according to the ST33ZP24 SPI protocol. + * @param: tpm, the chip description + * @param: tpm_register, the tpm tis register where the data should be written + * @param: tpm_data, the tpm_data to write inside the tpm_register + * @param: tpm_size, The length of the data + * @return: should be zero if success else a negative error code. + */ +static int st33zp24_spi_write(struct udevice *dev, u8 tpm_register, + const u8 *tpm_data, size_t tpm_size) +{ + int total_length = 0, ret; + struct spi_slave *slave = dev_get_parent_priv(dev); + struct st33zp24_spi_phy *phy = dev_get_platdata(dev); + + u8 *tx_buf = (u8 *)phy->tx_buf; + u8 *rx_buf = phy->rx_buf; + + tx_buf[total_length++] = TPM_WRITE_DIRECTION | LOCALITY0; + tx_buf[total_length++] = tpm_register; + + if (tpm_size > 0 && tpm_register == TPM_DATA_FIFO) { + tx_buf[total_length++] = tpm_size >> 8; + tx_buf[total_length++] = tpm_size; + } + memcpy(tx_buf + total_length, tpm_data, tpm_size); + total_length += tpm_size; + + memset(tx_buf + total_length, TPM_DUMMY_BYTE, phy->latency); + + total_length += phy->latency; + + ret = spi_claim_bus(slave); + if (ret < 0) + return ret; + + ret = spi_xfer(slave, total_length * 8, tx_buf, rx_buf, + SPI_XFER_BEGIN | SPI_XFER_END); + if (ret < 0) + return ret; + + spi_release_bus(slave); + + if (ret == 0) + ret = rx_buf[total_length - 1]; + + return st33zp24_spi_status_to_errno(ret); +} + +/* + * spi_st33zp24_spi_read8_reg + * Recv byte from the TIS register according to the ST33ZP24 SPI protocol. + * @param: tpm, the chip description + * @param: tpm_loc, the locality to read register from + * @param: tpm_register, the tpm tis register where the data should be read + * @param: tpm_data, the TPM response + * @param: tpm_size, tpm TPM response size to read. + * @return: should be zero if success else a negative error code. + */ +static u8 st33zp24_spi_read8_reg(struct udevice *dev, u8 tpm_register, + u8 *tpm_data, size_t tpm_size) +{ + int total_length = 0, ret; + struct spi_slave *slave = dev_get_parent_priv(dev); + struct st33zp24_spi_phy *phy = dev_get_platdata(dev); + + u8 *tx_buf = (u8 *)phy->tx_buf; + u8 *rx_buf = phy->rx_buf; + + /* Pre-Header */ + tx_buf[total_length++] = LOCALITY0; + tx_buf[total_length++] = tpm_register; + + memset(&tx_buf[total_length], TPM_DUMMY_BYTE, + phy->latency + tpm_size); + total_length += phy->latency + tpm_size; + + ret = spi_claim_bus(slave); + if (ret < 0) + return 0; + + ret = spi_xfer(slave, total_length * 8, tx_buf, rx_buf, + SPI_XFER_BEGIN | SPI_XFER_END); + if (ret < 0) + return 0; + + spi_release_bus(slave); + + if (tpm_size > 0 && ret == 0) { + ret = rx_buf[total_length - tpm_size - 1]; + memcpy(tpm_data, rx_buf + total_length - tpm_size, tpm_size); + } + return ret; +} + +/* + * st33zp24_spi_recv + * Recv byte from the TIS register according to the ST33ZP24 SPI protocol. + * @param: phy_id, the phy description + * @param: tpm_register, the tpm tis register where the data should be read + * @param: tpm_data, the TPM response + * @param: tpm_size, tpm TPM response size to read. + * @return: number of byte read successfully: should be one if success. + */ +static int st33zp24_spi_read(struct udevice *dev, u8 tpm_register, + u8 *tpm_data, size_t tpm_size) +{ + int ret; + + ret = st33zp24_spi_read8_reg(dev, tpm_register, tpm_data, tpm_size); + if (!st33zp24_spi_status_to_errno(ret)) + return tpm_size; + + return ret; +} + +static int st33zp24_spi_evaluate_latency(struct udevice *dev) +{ + int latency = 1, status = 0; + u8 data = 0; + struct st33zp24_spi_phy *phy = dev_get_platdata(dev); + + while (!status && latency < MAX_SPI_LATENCY) { + phy->latency = latency; + status = st33zp24_spi_read8_reg(dev, TPM_INTF_CAPABILITY, + &data, 1); + latency++; + } + if (status < 0) + return status; + if (latency == MAX_SPI_LATENCY) + return -ENODEV; + + return latency - 1; +} + +/* + * st33zp24_spi_release_locality release the active locality + * @param: chip, the tpm chip description. + */ +static void st33zp24_spi_release_locality(struct udevice *dev) +{ + u8 data = TPM_ACCESS_ACTIVE_LOCALITY; + + st33zp24_spi_write(dev, TPM_ACCESS, &data, 1); +} + +/* + * st33zp24_spi_check_locality if the locality is active + * @param: chip, the tpm chip description + * @return: the active locality or -EACCES. + */ +static int st33zp24_spi_check_locality(struct udevice *dev) +{ + u8 data; + u8 status; + struct tpm_chip *chip = dev_get_priv(dev); + + status = st33zp24_spi_read(dev, TPM_ACCESS, &data, 1); + if (status && (data & + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) + return chip->locality; + + return -EACCES; +} + +/* + * st33zp24_spi_request_locality request the TPM locality + * @param: chip, the chip description + * @return: the active locality or negative value. + */ +static int st33zp24_spi_request_locality(struct udevice *dev) +{ + unsigned long start, stop; + long ret; + u8 data; + struct tpm_chip *chip = dev_get_priv(dev); + + if (st33zp24_spi_check_locality(dev) == chip->locality) + return chip->locality; + + data = TPM_ACCESS_REQUEST_USE; + ret = st33zp24_spi_write(dev, TPM_ACCESS, &data, 1); + if (ret < 0) + return ret; + + /* wait for locality activated */ + start = get_timer(0); + stop = chip->timeout_a; + do { + if (st33zp24_spi_check_locality(dev) >= 0) + return chip->locality; + udelay(TPM_TIMEOUT_MS * 1000); + } while (get_timer(start) < stop); + + return -EACCES; +} + +/* + * st33zp24_spi_status return the TPM_STS register + * @param: chip, the tpm chip description + * @return: the TPM_STS register value. + */ +static u8 st33zp24_spi_status(struct udevice *dev) +{ + u8 data; + + st33zp24_spi_read(dev, TPM_STS, &data, 1); + return data; +} + +/* + * st33zp24_spi_get_burstcount return the burstcount address 0x19 0x1A + * @param: chip, the chip description + * return: the burstcount or -TPM_DRIVER_ERR in case of error. + */ +static int st33zp24_spi_get_burstcount(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + unsigned long start, stop; + int burstcnt, status; + u8 tpm_reg, temp; + + /* wait for burstcount */ + start = get_timer(0); + stop = chip->timeout_d; + do { + tpm_reg = TPM_STS + 1; + status = st33zp24_spi_read(dev, tpm_reg, &temp, 1); + if (status < 0) + return -EBUSY; + + tpm_reg = TPM_STS + 2; + burstcnt = temp; + status = st33zp24_spi_read(dev, tpm_reg, &temp, 1); + if (status < 0) + return -EBUSY; + + burstcnt |= temp << 8; + if (burstcnt) + return burstcnt; + udelay(TIS_SHORT_TIMEOUT_MS * 1000); + } while (get_timer(start) < stop); + + return -EBUSY; +} + +/* + * st33zp24_spi_cancel, cancel the current command execution or + * set STS to COMMAND READY. + * @param: chip, tpm_chip description. + */ +static void st33zp24_spi_cancel(struct udevice *dev) +{ + u8 data; + + data = TPM_STS_COMMAND_READY; + st33zp24_spi_write(dev, TPM_STS, &data, 1); +} + +/* + * st33zp24_spi_wait_for_stat wait for a TPM_STS value + * @param: chip, the tpm chip description + * @param: mask, the value mask to wait + * @param: timeout, the timeout + * @param: status, + * @return: the tpm status, 0 if success, -ETIME if timeout is reached. + */ +static int st33zp24_spi_wait_for_stat(struct udevice *dev, u8 mask, + unsigned long timeout, int *status) +{ + unsigned long start, stop; + + /* Check current status */ + *status = st33zp24_spi_status(dev); + if ((*status & mask) == mask) + return 0; + + start = get_timer(0); + stop = timeout; + do { + udelay(TPM_TIMEOUT_MS * 1000); + *status = st33zp24_spi_status(dev); + if ((*status & mask) == mask) + return 0; + } while (get_timer(start) < stop); + + return -ETIME; +} + +/* + * st33zp24_spi_recv_data receive data + * @param: chip, the tpm chip description + * @param: buf, the buffer where the data are received + * @param: count, the number of data to receive + * @return: the number of bytes read from TPM FIFO. + */ +static int st33zp24_spi_recv_data(struct udevice *dev, u8 *buf, size_t count) +{ + struct tpm_chip *chip = dev_get_priv(dev); + int size = 0, burstcnt, len, ret, status; + + while (size < count && + st33zp24_spi_wait_for_stat(dev, TPM_STS_DATA_AVAIL | TPM_STS_VALID, + chip->timeout_c, &status) == 0) { + burstcnt = st33zp24_spi_get_burstcount(dev); + if (burstcnt < 0) + return burstcnt; + len = min_t(int, burstcnt, count - size); + ret = st33zp24_spi_read(dev, TPM_DATA_FIFO, buf + size, len); + if (ret < 0) + return ret; + + size += len; + } + return size; +} + +/* + * st33zp24_spi_recv received TPM response through TPM phy. + * @param: chip, tpm_chip description. + * @param: buf, the buffer to store data. + * @param: count, the number of bytes that can received (sizeof buf). + * @return: Returns zero in case of success else -EIO. + */ +static int st33zp24_spi_recv(struct udevice *dev, u8 *buf, size_t count) +{ + struct tpm_chip *chip = dev_get_priv(dev); + int size, expected; + + if (!chip) + return -ENODEV; + + if (count < TPM_HEADER_SIZE) { + size = -EIO; + goto out; + } + + size = st33zp24_spi_recv_data(dev, buf, TPM_HEADER_SIZE); + if (size < TPM_HEADER_SIZE) { + debug("TPM error, unable to read header\n"); + goto out; + } + + expected = get_unaligned_be32(buf + 2); + if (expected > count) { + size = -EIO; + goto out; + } + + size += st33zp24_spi_recv_data(dev, &buf[TPM_HEADER_SIZE], + expected - TPM_HEADER_SIZE); + if (size < expected) { + debug("TPM error, unable to read remaining bytes of result\n"); + size = -EIO; + goto out; + } + +out: + st33zp24_spi_cancel(dev); + st33zp24_spi_release_locality(dev); + + return size; +} + +/* + * st33zp24_spi_send send TPM commands through TPM phy. + * @param: chip, tpm_chip description. + * @param: buf, the buffer to send. + * @param: len, the number of bytes to send. + * @return: Returns zero in case of success else the negative error code. + */ +static int st33zp24_spi_send(struct udevice *dev, const u8 *buf, size_t len) +{ + struct tpm_chip *chip = dev_get_priv(dev); + u32 i, size; + int burstcnt, ret, status; + u8 data, tpm_stat; + + if (!chip) + return -ENODEV; + if (len < TPM_HEADER_SIZE) + return -EIO; + + ret = st33zp24_spi_request_locality(dev); + if (ret < 0) + return ret; + + tpm_stat = st33zp24_spi_status(dev); + if ((tpm_stat & TPM_STS_COMMAND_READY) == 0) { + st33zp24_spi_cancel(dev); + if (st33zp24_spi_wait_for_stat(dev, TPM_STS_COMMAND_READY, + chip->timeout_b, &status) < 0) { + ret = -ETIME; + goto out_err; + } + } + + for (i = 0; i < len - 1;) { + burstcnt = st33zp24_spi_get_burstcount(dev); + if (burstcnt < 0) + return burstcnt; + + size = min_t(int, len - i - 1, burstcnt); + ret = st33zp24_spi_write(dev, TPM_DATA_FIFO, buf + i, size); + if (ret < 0) + goto out_err; + + i += size; + } + + tpm_stat = st33zp24_spi_status(dev); + if ((tpm_stat & TPM_STS_DATA_EXPECT) == 0) { + ret = -EIO; + goto out_err; + } + + ret = st33zp24_spi_write(dev, TPM_DATA_FIFO, buf + len - 1, 1); + if (ret < 0) + goto out_err; + + tpm_stat = st33zp24_spi_status(dev); + if ((tpm_stat & TPM_STS_DATA_EXPECT) != 0) { + ret = -EIO; + goto out_err; + } + + data = TPM_STS_GO; + ret = st33zp24_spi_write(dev, TPM_STS, &data, 1); + if (ret < 0) + goto out_err; + + return len; + +out_err: + st33zp24_spi_cancel(dev); + st33zp24_spi_release_locality(dev); + + return ret; +} + +static int st33zp24_spi_cleanup(struct udevice *dev) +{ + st33zp24_spi_cancel(dev); + /* + * The TPM needs some time to clean up here, + * so we sleep rather than keeping the bus busy + */ + mdelay(2); + st33zp24_spi_release_locality(dev); + + return 0; +} + +static int st33zp24_spi_init(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + struct st33zp24_spi_phy *phy = dev_get_platdata(dev); + + chip->is_open = 1; + + /* Default timeouts - these could move to the device tree */ + chip->timeout_a = TIS_SHORT_TIMEOUT_MS; + chip->timeout_b = TIS_LONG_TIMEOUT_MS; + chip->timeout_c = TIS_SHORT_TIMEOUT_MS; + chip->timeout_d = TIS_SHORT_TIMEOUT_MS; + + chip->locality = LOCALITY0; + + phy->latency = st33zp24_spi_evaluate_latency(dev); + if (phy->latency <= 0) + return -ENODEV; + + /* + * A timeout query to TPM can be placed here. + * Standard timeout values are used so far + */ + + return 0; +} + +static int st33zp24_spi_open(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + int rc; + + debug("%s: start\n", __func__); + if (chip->is_open) + return -EBUSY; + + rc = st33zp24_spi_init(dev); + if (rc < 0) + chip->is_open = 0; + + return rc; +} + +static int st33zp24_spi_close(struct udevice *dev) +{ + struct tpm_chip *chip = dev_get_priv(dev); + + if (chip->is_open) { + st33zp24_spi_release_locality(dev); + chip->is_open = 0; + chip->vend_dev = 0; + } + + return 0; +} + +static int st33zp24_spi_get_desc(struct udevice *dev, char *buf, int size) +{ + struct tpm_chip *chip = dev_get_priv(dev); + + if (size < 50) + return -ENOSPC; + + return snprintf(buf, size, "1.2 TPM (%s, chip type %s device-id 0x%x)", + chip->is_open ? "open" : "closed", + dev->name, + chip->vend_dev >> 16); +} + +const struct tpm_ops st33zp24_spi_tpm_ops = { + .open = st33zp24_spi_open, + .close = st33zp24_spi_close, + .recv = st33zp24_spi_recv, + .send = st33zp24_spi_send, + .cleanup = st33zp24_spi_cleanup, + .get_desc = st33zp24_spi_get_desc, +}; + +static int st33zp24_spi_probe(struct udevice *dev) +{ + struct tpm_chip_priv *uc_priv = dev_get_uclass_priv(dev); + + uc_priv->duration_ms[TPM_SHORT] = TIS_SHORT_TIMEOUT_MS; + uc_priv->duration_ms[TPM_MEDIUM] = TIS_LONG_TIMEOUT_MS; + uc_priv->duration_ms[TPM_LONG] = TIS_LONG_TIMEOUT_MS; + uc_priv->retry_time_ms = TPM_TIMEOUT_MS; + + debug("ST33ZP24 SPI TPM from STMicroelectronics found\n"); + + return 0; +} + +static int st33zp24_spi_remove(struct udevice *dev) +{ + st33zp24_spi_release_locality(dev); + + return 0; +} + +static const struct udevice_id st33zp24_spi_ids[] = { + { .compatible = "st,st33zp24-spi" }, + { } +}; + +U_BOOT_DRIVER(st33zp24_spi_spi) = { + .name = "st33zp24-spi", + .id = UCLASS_TPM, + .of_match = of_match_ptr(st33zp24_spi_ids), + .probe = st33zp24_spi_probe, + .remove = st33zp24_spi_remove, + .ops = &st33zp24_spi_tpm_ops, + .priv_auto_alloc_size = sizeof(struct tpm_chip), + .platdata_auto_alloc_size = sizeof(struct st33zp24_spi_phy), +}; From d314e247e1aede35cdfe448ad9262edc0d90a9ba Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:07 -0700 Subject: [PATCH 06/36] test/py: fix timeout to be absolute Currently, Spawn.expect() imposes its timeout solely upon receipt of new data, not on its overall operation. In theory, this could cause the timeout not to fire if U-Boot continually generated output that did not match the expected patterns. Fix the code to additionally impose a timeout on overall operation, which is the intended mode of operation. Signed-off-by: Stephen Warren Reviewed-by: Lukasz Majewski Acked-by: Simon Glass --- test/py/u_boot_spawn.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/py/u_boot_spawn.py b/test/py/u_boot_spawn.py index 1baee63df2..df4c67597c 100644 --- a/test/py/u_boot_spawn.py +++ b/test/py/u_boot_spawn.py @@ -122,6 +122,7 @@ class Spawn(object): if type(patterns[pi]) == type(''): patterns[pi] = re.compile(patterns[pi]) + tstart_s = time.time() try: while True: earliest_m = None @@ -142,7 +143,11 @@ class Spawn(object): self.after = self.buf[pos:posafter] self.buf = self.buf[posafter:] return earliest_pi - events = self.poll.poll(self.timeout) + tnow_s = time.time() + tdelta_ms = (tnow_s - tstart_s) * 1000 + if tdelta_ms > self.timeout: + raise Timeout() + events = self.poll.poll(self.timeout - tdelta_ms) if not events: raise Timeout() c = os.read(self.fd, 1024) From 636f38d83a7e0e6ca076ae65e086c800337fb3a3 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:08 -0700 Subject: [PATCH 07/36] test/py: move U-Boot respawn trigger to the test core Prior to this change, U-Boot was lazilly (re-)spawned if/when a test attempted to interact with it, and no active connection existed. This approach was simple, yet had the disadvantage that U-Boot might be spawned in the middle of a test function, e.g. after the test had already performed actions such as creating data files, etc. In that case, this could cause the log to contain the sequence (1) some test logs, (2) U-Boot's boot process, (3) the rest of that test's logs. This isn't optimally readable. This issue will affect the upcoming DFU and enhanced UMS tests. This change converts u_boot_console to be a function-scoped fixture, so that pytest attempts to re-create the object for each test invocation. This allows the fixture factory function to ensure that U-Boot is spawned prior to every test. In practice, the same object is returned each time so there is essentially no additional overhead due to this change. This allows us to remove: - The explicit ensure_spawned() call from test_sleep, since the core now ensures that the spawn happens before the test code is executed. - The laxy calls to ensure_spawned() in the u_boot_console_* implementations. The one downside is that test_env's "state_ttest_env" fixture must be converted to a function-scoped fixture too, since a module-scoped fixture cannot use a function-scoped fixture. To avoid overhead, we use the same trick of returning the same object each time. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/conftest.py | 3 ++- test/py/tests/test_env.py | 8 ++++++-- test/py/tests/test_sandbox_exit.py | 2 -- test/py/tests/test_sleep.py | 4 ---- test/py/u_boot_console_base.py | 2 -- test/py/u_boot_console_sandbox.py | 1 - 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/test/py/conftest.py b/test/py/conftest.py index e1674dfce0..38aa3f922a 100644 --- a/test/py/conftest.py +++ b/test/py/conftest.py @@ -227,7 +227,7 @@ def pytest_generate_tests(metafunc): vals = subconfig.get(fn + 's', []) metafunc.parametrize(fn, vals) -@pytest.fixture(scope='session') +@pytest.fixture(scope='function') def u_boot_console(request): '''Generate the value of a test's u_boot_console fixture. @@ -238,6 +238,7 @@ def u_boot_console(request): The fixture value. ''' + console.ensure_spawned() return console tests_not_run = set() diff --git a/test/py/tests/test_env.py b/test/py/tests/test_env.py index a3e8dd3033..557c3afe5c 100644 --- a/test/py/tests/test_env.py +++ b/test/py/tests/test_env.py @@ -77,11 +77,15 @@ class StateTestEnv(object): return var n += 1 -@pytest.fixture(scope='module') +ste = None +@pytest.fixture(scope='function') def state_test_env(u_boot_console): '''pytest fixture to provide a StateTestEnv object to tests.''' - return StateTestEnv(u_boot_console) + global ste + if not ste: + ste = StateTestEnv(u_boot_console) + return ste def unset_var(state_test_env, var): '''Unset an environment variable. diff --git a/test/py/tests/test_sandbox_exit.py b/test/py/tests/test_sandbox_exit.py index 2aa8eb4abc..1ec3607eb2 100644 --- a/test/py/tests/test_sandbox_exit.py +++ b/test/py/tests/test_sandbox_exit.py @@ -13,7 +13,6 @@ def test_reset(u_boot_console): u_boot_console.run_command('reset', wait_for_prompt=False) assert(u_boot_console.validate_exited()) - u_boot_console.ensure_spawned() @pytest.mark.boardspec('sandbox') def test_ctrl_c(u_boot_console): @@ -21,4 +20,3 @@ def test_ctrl_c(u_boot_console): u_boot_console.kill(signal.SIGINT) assert(u_boot_console.validate_exited()) - u_boot_console.ensure_spawned() diff --git a/test/py/tests/test_sleep.py b/test/py/tests/test_sleep.py index 64f1ddf9a0..437b6bb9fe 100644 --- a/test/py/tests/test_sleep.py +++ b/test/py/tests/test_sleep.py @@ -9,10 +9,6 @@ def test_sleep(u_boot_console): '''Test the sleep command, and validate that it sleeps for approximately the correct amount of time.''' - # Do this before we time anything, to make sure U-Boot is already running. - # Otherwise, the system boot time is included in the time measurement. - u_boot_console.ensure_spawned() - # 3s isn't too long, but is enough to cross a few second boundaries. sleep_time = 3 tstart = time.time() diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index 520f9a9e9f..10fe3dbdd3 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -144,8 +144,6 @@ class ConsoleBase(object): command string and emitted the subsequent command prompts. ''' - self.ensure_spawned() - if self.at_prompt and \ self.at_prompt_logevt != self.logstream.logfile.cur_evt: self.logstream.write(self.prompt, implicit=True) diff --git a/test/py/u_boot_console_sandbox.py b/test/py/u_boot_console_sandbox.py index 88b137e8c3..eb84150a1e 100644 --- a/test/py/u_boot_console_sandbox.py +++ b/test/py/u_boot_console_sandbox.py @@ -51,7 +51,6 @@ class ConsoleSandbox(ConsoleBase): Nothing. ''' - self.ensure_spawned() self.log.action('kill %d' % sig) self.p.kill(sig) From c10eb9d39fb54f435dbd632f71c760e4a887a015 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:09 -0700 Subject: [PATCH 08/36] test/py: drain console log at the end of any failed test Tests may fail for a number of reasons, and in particular for reasons other than a timeout waiting for U-Boot to print expected data. If the last operation that a failed test performs is not waiting for U-Boot to print something, then any trailing output from U-Boot during that test's operation will not be logged as part of that test, but rather either along with the next test, or even thrown away, potentiall hiding clues re: the test failure reason. Solve this by explicitly draining (and hence logging) the U-Boot output in the case of failed tests. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/conftest.py | 1 + test/py/u_boot_console_base.py | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/test/py/conftest.py b/test/py/conftest.py index 38aa3f922a..c1f19cee65 100644 --- a/test/py/conftest.py +++ b/test/py/conftest.py @@ -386,6 +386,7 @@ def pytest_runtest_protocol(item, nextitem): skipped = report if failed: + console.drain_console() tests_failed.add(item.name) elif skipped: tests_skipped.add(item.name) diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index 10fe3dbdd3..418a26bb8e 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -14,6 +14,7 @@ import os import pytest import re import sys +import u_boot_spawn # Regexes for text we expect U-Boot to send to the console. pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}-[^\r\n]*)') @@ -213,6 +214,43 @@ class ConsoleBase(object): self.run_command(chr(3), wait_for_echo=False, send_nl=False) + def drain_console(self): + '''Read from and log the U-Boot console for a short time. + + U-Boot's console output is only logged when the test code actively + waits for U-Boot to emit specific data. There are cases where tests + can fail without doing this. For example, if a test asks U-Boot to + enable USB device mode, then polls until a host-side device node + exists. In such a case, it is useful to log U-Boot's console output + in case U-Boot printed clues as to why the host-side even did not + occur. This function will do that. + + Args: + None. + + Returns: + Nothing. + ''' + + # If we are already not connected to U-Boot, there's nothing to drain. + # This should only happen when a previous call to run_command() or + # wait_for() failed (and hence the output has already been logged), or + # the system is shutting down. + if not self.p: + return + + orig_timeout = self.p.timeout + try: + # Drain the log for a relatively short time. + self.p.timeout = 1000 + # Wait for something U-Boot will likely never send. This will + # cause the console output to be read and logged. + self.p.expect(['This should never match U-Boot output']) + except u_boot_spawn.Timeout: + pass + finally: + self.p.timeout = orig_timeout + def ensure_spawned(self): '''Ensure a connection to a correctly running U-Boot instance. From 783cbcd3604088e9ff12b552fc209b3696c0e2b6 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:10 -0700 Subject: [PATCH 09/36] test/py: log when tests send CTRL-C Write a note to the log file when a test sends CTRL-C to U-Boot. This makes it easier to follow what's happening in the logs, especially since U-Boot doesn't echo the character back to its output, so there's no other signal of what's going on. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/u_boot_console_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index 418a26bb8e..433bec6e9f 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -212,6 +212,7 @@ class ConsoleBase(object): Nothing. ''' + self.log.action('Sending Ctrl-C') self.run_command(chr(3), wait_for_echo=False, send_nl=False) def drain_console(self): From 3f2faf7327ae7cf1a78097a8089f91e3f2aa8652 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:11 -0700 Subject: [PATCH 10/36] test/py: optionally ignore errors from shell commands Sometimes it's useful to run shell commands and ignore any errors. One example might be cleanup logic; if a test-case experiences an error, the cleanup logic might experience an error too, and we don't want that error to mask the original error, so we want to ignore the subsequent error. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/multiplexed_log.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/py/multiplexed_log.py b/test/py/multiplexed_log.py index 48f2b51de1..5059bbfb99 100644 --- a/test/py/multiplexed_log.py +++ b/test/py/multiplexed_log.py @@ -106,13 +106,17 @@ class RunAndLog(object): '''Clean up any resources managed by this object.''' pass - def run(self, cmd, cwd=None): + def run(self, cmd, cwd=None, ignore_errors=False): '''Run a command as a sub-process, and log the results. Args: cmd: The command to execute. cwd: The directory to run the command in. Can be None to use the current directory. + ignore_errors: Indicate whether to ignore errors. If True, the + function will simply return if the command cannot be executed + or exits with an error code, otherwise an exception will be + raised if such problems occur. Returns: Nothing. @@ -148,7 +152,7 @@ class RunAndLog(object): exception = e if output and not output.endswith('\n'): output += '\n' - if exit_status and not exception: + if exit_status and not exception and not ignore_errors: exception = Exception('Exit code: ' + str(exit_status)) if exception: output += str(exception) + '\n' From 76b4693928920d7c30fa935b3c46a02b637a29e1 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:12 -0700 Subject: [PATCH 11/36] test/py: add various utility code Add various common utility functions. These will be used by a forthcoming re-written UMS test, and a brand-new DFU test. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/u_boot_console_base.py | 19 ++++ test/py/u_boot_utils.py | 171 +++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 test/py/u_boot_utils.py diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index 433bec6e9f..06f61f9871 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -215,6 +215,25 @@ class ConsoleBase(object): self.log.action('Sending Ctrl-C') self.run_command(chr(3), wait_for_echo=False, send_nl=False) + def wait_for(self, text): + '''Wait for a pattern to be emitted by U-Boot. + + This is useful when a long-running command such as "dfu" is executing, + and it periodically emits some text that should show up at a specific + location in the log file. + + Args: + text: The text to wait for; either a string (containing raw text, + not a regular expression) or an re object. + + Returns: + Nothing. + ''' + + if type(text) == type(''): + text = re.escape(text) + self.p.expect([text]) + def drain_console(self): '''Read from and log the U-Boot console for a short time. diff --git a/test/py/u_boot_utils.py b/test/py/u_boot_utils.py new file mode 100644 index 0000000000..539af618db --- /dev/null +++ b/test/py/u_boot_utils.py @@ -0,0 +1,171 @@ +# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. +# +# SPDX-License-Identifier: GPL-2.0 + +# Utility code shared across multiple tests. + +import hashlib +import os +import os.path +import sys +import time + +def md5sum_data(data): + '''Calculate the MD5 hash of some data. + + Args: + data: The data to hash. + + Returns: + The hash of the data, as a binary string. + ''' + + h = hashlib.md5() + h.update(data) + return h.digest() + +def md5sum_file(fn, max_length=None): + '''Calculate the MD5 hash of the contents of a file. + + Args: + fn: The filename of the file to hash. + max_length: The number of bytes to hash. If the file has more + bytes than this, they will be ignored. If None or omitted, the + entire file will be hashed. + + Returns: + The hash of the file content, as a binary string. + ''' + + with open(fn, 'rb') as fh: + if max_length: + params = [max_length] + else: + params = [] + data = fh.read(*params) + return md5sum_data(data) + +class PersistentRandomFile(object): + '''Generate and store information about a persistent file containing + random data.''' + + def __init__(self, u_boot_console, fn, size): + '''Create or process the persistent file. + + If the file does not exist, it is generated. + + If the file does exist, its content is hashed for later comparison. + + These files are always located in the "persistent data directory" of + the current test run. + + Args: + u_boot_console: A console connection to U-Boot. + fn: The filename (without path) to create. + size: The desired size of the file in bytes. + + Returns: + Nothing. + ''' + + self.fn = fn + + self.abs_fn = u_boot_console.config.persistent_data_dir + '/' + fn + + if os.path.exists(self.abs_fn): + u_boot_console.log.action('Persistent data file ' + self.abs_fn + + ' already exists') + self.content_hash = md5sum_file(self.abs_fn) + else: + u_boot_console.log.action('Generating ' + self.abs_fn + + ' (random, persistent, %d bytes)' % size) + data = os.urandom(size) + with open(self.abs_fn, 'wb') as fh: + fh.write(data) + self.content_hash = md5sum_data(data) + +def attempt_to_open_file(fn): + '''Attempt to open a file, without throwing exceptions. + + Any errors (exceptions) that occur during the attempt to open the file + are ignored. This is useful in order to test whether a file (in + particular, a device node) exists and can be successfully opened, in order + to poll for e.g. USB enumeration completion. + + Args: + fn: The filename to attempt to open. + + Returns: + An open file handle to the file, or None if the file could not be + opened. + ''' + + try: + return open(fn, 'rb') + except: + return None + +def wait_until_open_succeeds(fn): + '''Poll until a file can be opened, or a timeout occurs. + + Continually attempt to open a file, and return when this succeeds, or + raise an exception after a timeout. + + Args: + fn: The filename to attempt to open. + + Returns: + An open file handle to the file. + ''' + + for i in xrange(100): + fh = attempt_to_open_file(fn) + if fh: + return fh + time.sleep(0.1) + raise Exception('File could not be opened') + +def wait_until_file_open_fails(fn, ignore_errors): + '''Poll until a file cannot be opened, or a timeout occurs. + + Continually attempt to open a file, and return when this fails, or + raise an exception after a timeout. + + Args: + fn: The filename to attempt to open. + ignore_errors: Indicate whether to ignore timeout errors. If True, the + function will simply return if a timeout occurs, otherwise an + exception will be raised. + + Returns: + Nothing. + ''' + + for i in xrange(100): + fh = attempt_to_open_file(fn) + if not fh: + return + fh.close() + time.sleep(0.1) + if ignore_errors: + return + raise Exception('File can still be opened') + +def run_and_log(u_boot_console, cmd, ignore_errors=False): + '''Run a command and log its output. + + Args: + u_boot_console: A console connection to U-Boot. + cmd: The command to run, as an array of argv[]. + ignore_errors: Indicate whether to ignore errors. If True, the function + will simply return if the command cannot be executed or exits with + an error code, otherwise an exception will be raised if such + problems occur. + + Returns: + Nothing. + ''' + + runner = u_boot_console.log.get_runner(cmd[0], sys.stdout) + runner.run(cmd, ignore_errors=ignore_errors) + runner.close() From d054f4c2cb56c49c04277cbc5be477c92baea25f Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:13 -0700 Subject: [PATCH 12/36] test/py: ums: add filesystem-based testing Enhance the UMS test to optionally mount a partition and read/write a file to it, validating that the content written and read back are identical. This enhancement is backwards-compatible; old boardenv contents that don't define the new configuration data will cause the test code to perform as before. test/ums/ is deleted since the Python test now performs the same testing that it did. The code is also re-written to make use of the recently added utility module, and split it up into nested functions so the overall logic of the test process can be followed more easily without the details cluttering the code. Signed-off-by: Stephen Warren Acked-by: Lukasz Majewski Acked-by: Simon Glass --- test/py/tests/test_ums.py | 238 ++++++++++++++++++++++++++++-------- test/ums/README | 30 ----- test/ums/ums_gadget_test.sh | 183 --------------------------- 3 files changed, 189 insertions(+), 262 deletions(-) delete mode 100644 test/ums/README delete mode 100755 test/ums/ums_gadget_test.sh diff --git a/test/py/tests/test_ums.py b/test/py/tests/test_ums.py index a137221c7a..f482cfeca1 100644 --- a/test/py/tests/test_ums.py +++ b/test/py/tests/test_ums.py @@ -2,13 +2,17 @@ # # SPDX-License-Identifier: GPL-2.0 -# Test U-Boot's "ums" command. At present, this test only ensures that a UMS -# device can be enumerated by the host/test machine. In the future, this test -# should be enhanced to validate disk IO. +# Test U-Boot's "ums" command. The test starts UMS in U-Boot, waits for USB +# device enumeration on the host, reads a small block of data from the UMS +# block device, optionally mounts a partition and performs filesystem-based +# read/write tests, and finally aborts the "ums" command in U-Boot. import os +import os.path import pytest +import re import time +import u_boot_utils ''' Note: This test relies on: @@ -17,13 +21,36 @@ a) boardenv_* to contain configuration values to define which USB ports are available for testing. Without this, this test will be automatically skipped. For example: +# Leave this list empty if you have no block_devs below with writable +# partitions defined. +env__mount_points = ( + "/mnt/ubtest-mnt-p2371-2180-na", +) + env__usb_dev_ports = ( - {'tgt_usb_ctlr': '0', 'host_ums_dev_node': '/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0'}, + { + "tgt_usb_ctlr": "0", + "host_ums_dev_node": "/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0", + }, ) env__block_devs = ( - {'type': 'mmc', 'id': '0'}, # eMMC; always present - {'type': 'mmc', 'id': '1'}, # SD card; present since I plugged one in + # eMMC; always present + { + "type": "mmc", + "id": "0", + # The following two properties are optional. + # If present, the partition will be mounted and a file written-to and + # read-from it. If missing, only a simple block read test will be + # performed. + "writable_fs_partition": 1, + "writable_fs_subdir": "tmp/", + }, + # SD card; present since I plugged one in + { + "type": "mmc", + "id": "1" + }, ) b) udev rules to set permissions on devices nodes, so that sudo is not @@ -34,47 +61,42 @@ ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="66 (You may wish to change the group ID instead of setting the permissions wide open. All that matters is that the user ID running the test can access the device.) + +c) /etc/fstab entries to allow the block device to be mounted without requiring +root permissions. For example: + +/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0-part1 /mnt/ubtest-mnt-p2371-2180-na ext4 noauto,user,nosuid,nodev + +This entry is only needed if any block_devs above contain a +writable_fs_partition value. ''' -def open_ums_device(host_ums_dev_node): - '''Attempt to open a device node, returning either the opened file handle, - or None on any error.''' - - try: - return open(host_ums_dev_node, 'rb') - except: - return None - -def wait_for_ums_device(host_ums_dev_node): - '''Continually attempt to open the device node exported by the "ums" - command, and either return the opened file handle, or raise an exception - after a timeout.''' - - for i in xrange(100): - fh = open_ums_device(host_ums_dev_node) - if fh: - return fh - time.sleep(0.1) - raise Exception('UMS device did not appear') - -def wait_for_ums_device_gone(host_ums_dev_node): - '''Continually attempt to open the device node exported by the "ums" - command, and either return once the device has disappeared, or raise an - exception if it does not before a timeout occurs.''' - - for i in xrange(100): - fh = open_ums_device(host_ums_dev_node) - if not fh: - return - fh.close() - time.sleep(0.1) - raise Exception('UMS device did not disappear') - @pytest.mark.buildconfigspec('cmd_usb_mass_storage') def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): '''Test the "ums" command; the host system must be able to enumerate a UMS - device when "ums" is running, and this device must disappear when "ums" is - aborted.''' + device when "ums" is running, block and optionally file I/O are tested, + and this device must disappear when "ums" is aborted. + + Args: + u_boot_console: A U-Boot console connection. + env__usb_dev_port: The single USB device-mode port specification on + which to run the test. See the file-level comment above for + details of the format. + env__block_devs: The list of block devices that the target U-Boot + device has attached. See the file-level comment above for details + of the format. + + Returns: + Nothing. + ''' + + have_writable_fs_partition = 'writable_fs_partition' in env__block_devs[0] + if not have_writable_fs_partition: + # If 'writable_fs_subdir' is missing, we'll skip all parts of the + # testing which mount filesystems. + u_boot_console.log.warning( + 'boardenv missing "writable_fs_partition"; ' + + 'UMS testing will be limited.') tgt_usb_ctlr = env__usb_dev_port['tgt_usb_ctlr'] host_ums_dev_node = env__usb_dev_port['host_ums_dev_node'] @@ -84,11 +106,129 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): # device list here. We'll test each block device somewhere else. tgt_dev_type = env__block_devs[0]['type'] tgt_dev_id = env__block_devs[0]['id'] + if have_writable_fs_partition: + mount_point = u_boot_console.config.env['env__mount_points'][0] + mount_subdir = env__block_devs[0]['writable_fs_subdir'] + part_num = env__block_devs[0]['writable_fs_partition'] + host_ums_part_node = '%s-part%d' % (host_ums_dev_node, part_num) + else: + host_ums_part_node = host_ums_dev_node - cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id) - u_boot_console.run_command('ums 0 mmc 0', wait_for_prompt=False) - fh = wait_for_ums_device(host_ums_dev_node) - fh.read(4096) - fh.close() - u_boot_console.ctrlc() - wait_for_ums_device_gone(host_ums_dev_node) + test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 'ums.bin', + 1024 * 1024); + if have_writable_fs_partition: + mounted_test_fn = mount_point + '/' + mount_subdir + test_f.fn + + def start_ums(): + '''Start U-Boot's ums shell command. + + This also waits for the host-side USB enumeration process to complete. + + Args: + None. + + Returns: + Nothing. + ''' + + u_boot_console.log.action( + 'Starting long-running U-Boot ums shell command') + cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id) + u_boot_console.run_command(cmd, wait_for_prompt=False) + u_boot_console.wait_for(re.compile('UMS: LUN.*[\r\n]')) + fh = u_boot_utils.wait_until_open_succeeds(host_ums_part_node) + u_boot_console.log.action('Reading raw data from UMS device') + fh.read(4096) + fh.close() + + def mount(): + '''Mount the block device that U-Boot exports. + + Args: + None. + + Returns: + Nothing. + ''' + + u_boot_console.log.action('Mounting exported UMS device') + cmd = ('/bin/mount', host_ums_part_node) + u_boot_utils.run_and_log(u_boot_console, cmd) + + def umount(ignore_errors): + '''Unmount the block device that U-Boot exports. + + Args: + ignore_errors: Ignore any errors. This is useful if an error has + already been detected, and the code is performing best-effort + cleanup. In this case, we do not want to mask the original + error by "honoring" any new errors. + + Returns: + Nothing. + ''' + + u_boot_console.log.action('Unmounting UMS device') + cmd = ('/bin/umount', host_ums_part_node) + u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors) + + def stop_ums(ignore_errors): + '''Stop U-Boot's ums shell command from executing. + + This also waits for the host-side USB de-enumeration process to + complete. + + Args: + ignore_errors: Ignore any errors. This is useful if an error has + already been detected, and the code is performing best-effort + cleanup. In this case, we do not want to mask the original + error by "honoring" any new errors. + + Returns: + Nothing. + ''' + + u_boot_console.log.action( + 'Stopping long-running U-Boot ums shell command') + u_boot_console.ctrlc() + u_boot_utils.wait_until_file_open_fails(host_ums_part_node, + ignore_errors) + + ignore_cleanup_errors = True + try: + start_ums() + if not have_writable_fs_partition: + # Skip filesystem-based testing if not configured + return + try: + mount() + u_boot_console.log.action('Writing test file via UMS') + cmd = ('rm', '-f', mounted_test_fn) + u_boot_utils.run_and_log(u_boot_console, cmd) + if os.path.exists(mounted_test_fn): + raise Exception('Could not rm target UMS test file') + cmd = ('cp', test_f.abs_fn, mounted_test_fn) + u_boot_utils.run_and_log(u_boot_console, cmd) + ignore_cleanup_errors = False + finally: + umount(ignore_errors=ignore_cleanup_errors) + finally: + stop_ums(ignore_errors=ignore_cleanup_errors) + + ignore_cleanup_errors = True + try: + start_ums() + try: + mount() + u_boot_console.log.action('Reading test file back via UMS') + read_back_hash = u_boot_utils.md5sum_file(mounted_test_fn) + cmd = ('rm', '-f', mounted_test_fn) + u_boot_utils.run_and_log(u_boot_console, cmd) + ignore_cleanup_errors = False + finally: + umount(ignore_errors=ignore_cleanup_errors) + finally: + stop_ums(ignore_errors=ignore_cleanup_errors) + + written_hash = test_f.content_hash + assert(written_hash == read_back_hash) diff --git a/test/ums/README b/test/ums/README deleted file mode 100644 index c80fbfefbf..0000000000 --- a/test/ums/README +++ /dev/null @@ -1,30 +0,0 @@ -UMS test script. - -ums_gadget_test.sh -================== - -Example usage: -1. On the target: - create UMS exportable partitions (with e.g. gpt write), or specify a - partition number (PART_NUM) as "-" to use the entire device - ums 0 mmc 0 -2. On the host: - sudo test/ums/ums_gadget_test.sh VID PID PART_NUM [-f FILE_SYSTEM] [test_file] - e.g. sudo test/ums/ums_gadget_test.sh 0525 a4a5 6 -f vfat ./dat_14M.img - -... where: - VID - UMS device USB Vendor ID - PID - UMS device USB Product ID - PART_NUM - is the partition number on which UMS operates or "-" to use the - whole device - -Information about available partitions on the target one can read with using -the 'mmc part' or 'part list' commands. - -The partition num (PART_NUM) can be specified as '-' for using the whole device. - -The [-f FILE_SYSTEM] optional switch allows for formatting target partition to -FILE_SYSTEM. - -The last, optional [test_file] parameter is for specifying the exact test file -to use. diff --git a/test/ums/ums_gadget_test.sh b/test/ums/ums_gadget_test.sh deleted file mode 100755 index 9da486b266..0000000000 --- a/test/ums/ums_gadget_test.sh +++ /dev/null @@ -1,183 +0,0 @@ -#! /bin/bash - -# Copyright (C) 2014 Samsung Electronics -# Lukasz Majewski -# -# UMS operation test script -# -# SPDX-License-Identifier: GPL-2.0+ - -clear - -COLOUR_RED="\33[31m" -COLOUR_GREEN="\33[32m" -COLOUR_ORANGE="\33[33m" -COLOUR_DEFAULT="\33[0m" - -DIR=./ -SUFFIX=img -RCV_DIR=rcv/ -LOG_FILE=./log/log-`date +%d-%m-%Y_%H-%M-%S` - -cd `dirname $0` -../dfu/dfu_gadget_test_init.sh 33M 97M - -cleanup () { - rm -rf $RCV_DIR $MNT_DIR -} - -control_c() -# run if user hits control-c -{ - echo -en "\n*** CTRL+C ***\n" - umount $MNT_DIR - cleanup - exit 0 -} - -# trap keyboard interrupt (control-c) -trap control_c SIGINT - -die () { - printf " $COLOUR_RED FAILED $COLOUR_DEFAULT \n" - cleanup - exit 1 -} - -calculate_md5sum () { - MD5SUM=`md5sum $1` - MD5SUM=`echo $MD5SUM | cut -d ' ' -f1` - echo "md5sum:"$MD5SUM -} - -ums_test_file () { - printf "$COLOUR_GREEN========================================================================================= $COLOUR_DEFAULT\n" - printf "File:$COLOUR_GREEN %s $COLOUR_DEFAULT\n" $1 - - mount /dev/$MEM_DEV $MNT_DIR - if [ -f $MNT_DIR/dat_* ]; then - rm $MNT_DIR/dat_* - fi - - cp ./$1 $MNT_DIR - - while true; do - umount $MNT_DIR > /dev/null 2>&1 - if [ $? -eq 0 ]; then - break - fi - printf "$COLOUR_ORANGE\tSleeping to wait for umount...$COLOUR_DEFAULT\n" - sleep 1 - done - - echo -n "TX: " - calculate_md5sum $1 - - MD5_TX=$MD5SUM - sleep 1 - N_FILE=$DIR$RCV_DIR${1:2}"_rcv" - - mount /dev/$MEM_DEV $MNT_DIR - cp $MNT_DIR/$1 $N_FILE || die $? - rm $MNT_DIR/$1 - umount $MNT_DIR - - echo -n "RX: " - calculate_md5sum $N_FILE - MD5_RX=$MD5SUM - - if [ "$MD5_TX" == "$MD5_RX" ]; then - printf " $COLOUR_GREEN -------> OK $COLOUR_DEFAULT \n" - else - printf " $COLOUR_RED -------> FAILED $COLOUR_DEFAULT \n" - cleanup - exit 1 - fi -} - -printf "$COLOUR_GREEN========================================================================================= $COLOUR_DEFAULT\n" -echo "U-boot UMS test program" - -if [ $EUID -ne 0 ]; then - echo "You must be root to do this." 1>&2 - exit 100 -fi - -if [ $# -lt 3 ]; then - echo "Wrong number of arguments" - echo "Example:" - echo "sudo ./ums_gadget_test.sh VID PID PART_NUM [-f ext4] [test_file]" - die -fi - -MNT_DIR="/mnt/tmp-ums-test" - -VID=$1; shift -PID=$1; shift -PART_NUM=$1; shift - -if [ "$1" == "-f" ]; then - shift - FS_TO_FORMAT=$1; shift -fi - -TEST_FILE=$1 - -for f in `find /sys -type f -name idProduct`; do - d=`dirname ${f}` - if [ `cat ${d}/idVendor` != "${VID}" ]; then - continue - fi - if [ `cat ${d}/idProduct` != "${PID}" ]; then - continue - fi - USB_DEV=${d} - break -done - -if [ -z "${USB_DEV}" ]; then - echo "Connect target" - echo "e.g. ums 0 mmc 0" - exit 1 -fi - -MEM_DEV=`find $USB_DEV -type d -name "sd[a-z]" | awk -F/ '{print $(NF)}' -` - -mkdir -p $RCV_DIR -if [ ! -d $MNT_DIR ]; then - mkdir -p $MNT_DIR -fi - -if [ "$PART_NUM" == "-" ]; then - PART_NUM="" -fi -MEM_DEV=$MEM_DEV$PART_NUM - -if [ -n "$FS_TO_FORMAT" ]; then - echo -n "Formatting partition /dev/$MEM_DEV to $FS_TO_FORMAT" - mkfs -t $FS_TO_FORMAT /dev/$MEM_DEV > /dev/null 2>&1 - if [ $? -eq 0 ]; then - printf " $COLOUR_GREEN DONE $COLOUR_DEFAULT \n" - else - die - fi -fi - -printf "Mount: /dev/$MEM_DEV \n" - -if [ -n "$TEST_FILE" ]; then - if [ ! -e $TEST_FILE ]; then - echo "No file: $TEST_FILE" - die - fi - ums_test_file $TEST_FILE -else - for file in $DIR*.$SUFFIX - do - ums_test_file $file - done -fi - -cleanup - -exit 0 From f5d196d03e5f3529be8b86d350e507390dbee22f Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 22 Jan 2016 12:30:14 -0700 Subject: [PATCH 13/36] test/py: add DFU test Add a test of DFU functionality to the Python test suite. The test starts DFU in U-Boot, waits for USB device enumeration on the host, executes dfu-util multiple times to test various transfer sizes, many of which trigger USB driver edge cases, and finally aborts the DFU command in U-Boot. This test mirrors the functionality previously available via the shell scripts in test/dfu, and hence those are removed too. Signed-off-by: Stephen Warren Acked-by: Lukasz Majewski Acked-by: Simon Glass --- test/dfu/README | 44 ------ test/dfu/dfu_gadget_test.sh | 108 ------------- test/dfu/dfu_gadget_test_init.sh | 45 ------ test/py/tests/test_dfu.py | 262 +++++++++++++++++++++++++++++++ 4 files changed, 262 insertions(+), 197 deletions(-) delete mode 100644 test/dfu/README delete mode 100755 test/dfu/dfu_gadget_test.sh delete mode 100755 test/dfu/dfu_gadget_test_init.sh create mode 100644 test/py/tests/test_dfu.py diff --git a/test/dfu/README b/test/dfu/README deleted file mode 100644 index 408d559421..0000000000 --- a/test/dfu/README +++ /dev/null @@ -1,44 +0,0 @@ -DFU TEST CASE DESCRIPTION: - -The prerequisites for running this script are assured by -dfu_gadget_test_init.sh, which is automatically invoked by dfu_gadget_test.sh. -In this file user is able to generate their own set of test files by altering -the default set of TEST_FILES_SIZES variable. -The dfu_gadget_test_init.sh would generate test images only if they are not -already generated. - -On the target device, environment variable "dfu_alt_info" must contain at -least: - - dfu_test.bin fat 0 6;dfudummy.bin fat 0 6 - -Depending on your device, you may need to replace "fat" with -"ext4", and "6" with the relevant partition number. For reference please -consult the config file for TRATS/TRATS2 devices -(../../include/configs/trats{2}.h) - -One can use fat, ext4 or any other supported file system supported by U-Boot. -These can be created by exporting storage devices via UMS (ums 0 mmc 0) and -using standard tools on host (like mkfs.ext4). - -Example usage: -1. On the target: - setenv dfu_alt_info dfu_test.bin fat 0 6\;dfudummy.bin fat 0 6 - dfu 0 mmc 0 -2. On the host: - test/dfu/dfu_gadget_test.sh X Y [test file name] [usb device vendor:product] - e.g. test/dfu/dfu_gadget_test.sh 0 1 - or - e.g. test/dfu/dfu_gadget_test.sh 0 1 ./dat_960.img - or - e.g. test/dfu/dfu_gadget_test.sh 0 1 0451:d022 - or - e.g. test/dfu/dfu_gadget_test.sh 0 1 ./dat_960.img 0451:d022 - -... where X and Y are dfu_test.bin's and dfudummy.bin's alt setting numbers. -They can be obtained from dfu-util -l or $dfu_alt_info. -It is also possible to pass optional [test file name] to force the script to -test one particular file. -If many DFU devices are connected, it may be useful to filter on USB -vendor/product ID (0451:d022). -One can get them by running "lsusb" command on a host PC. diff --git a/test/dfu/dfu_gadget_test.sh b/test/dfu/dfu_gadget_test.sh deleted file mode 100755 index 9c7942257b..0000000000 --- a/test/dfu/dfu_gadget_test.sh +++ /dev/null @@ -1,108 +0,0 @@ -#! /bin/bash - -# Copyright (C) 2014 Samsung Electronics -# Lukasz Majewski -# -# Script fixes, enhancements and testing: -# Stephen Warren -# -# DFU operation test script -# -# SPDX-License-Identifier: GPL-2.0+ - -set -e # any command return if not equal to zero -clear - -COLOUR_RED="\33[31m" -COLOUR_GREEN="\33[32m" -COLOUR_DEFAULT="\33[0m" - -DIR=./ -SUFFIX=img -RCV_DIR=rcv/ -LOG_FILE=./log/log-`date +%d-%m-%Y_%H-%M-%S` - -cd `dirname $0` -./dfu_gadget_test_init.sh - -cleanup () { - rm -rf $DIR$RCV_DIR -} - -die () { - printf " $COLOUR_RED FAILED $COLOUR_DEFAULT \n" - cleanup - exit 1 -} - -calculate_md5sum () { - MD5SUM=`md5sum $1` - MD5SUM=`echo $MD5SUM | cut -d ' ' -f1` - echo "md5sum:"$MD5SUM -} - -dfu_test_file () { - printf "$COLOUR_GREEN ========================================================================================= $COLOUR_DEFAULT\n" - printf "File:$COLOUR_GREEN %s $COLOUR_DEFAULT\n" $1 - - dfu-util $USB_DEV -D $1 -a $TARGET_ALT_SETTING >> $LOG_FILE 2>&1 || die $? - - echo -n "TX: " - calculate_md5sum $1 - - MD5_TX=$MD5SUM - - dfu-util $USB_DEV -D ${DIR}/dfudummy.bin -a $TARGET_ALT_SETTING_B >> $LOG_FILE 2>&1 || die $? - - N_FILE=$DIR$RCV_DIR${1:2}"_rcv" - - dfu-util $USB_DEV -U $N_FILE -a $TARGET_ALT_SETTING >> $LOG_FILE 2>&1 || die $? - - echo -n "RX: " - calculate_md5sum $N_FILE - MD5_RX=$MD5SUM - - if [ "$MD5_TX" == "$MD5_RX" ]; then - printf " $COLOUR_GREEN -------> OK $COLOUR_DEFAULT \n" - else - printf " $COLOUR_RED -------> FAILED $COLOUR_DEFAULT \n" - cleanup - exit 1 - fi - -} - -printf "$COLOUR_GREEN========================================================================================= $COLOUR_DEFAULT\n" -echo "DFU EP0 transmission test program" -echo "Trouble shoot -> disable DBG (even the KERN_DEBUG) in the UDC driver" -echo "@ -> TRATS2 # dfu 0 mmc 0" -cleanup -mkdir -p $DIR$RCV_DIR -touch $LOG_FILE - -if [ $# -eq 0 ] -then - printf " $COLOUR_RED Please pass alt setting number!! $COLOUR_DEFAULT \n" - exit 0 -fi - -TARGET_ALT_SETTING=$1 -TARGET_ALT_SETTING_B=$2 - -file=$3 -[[ $3 == *':'* ]] && USB_DEV="-d $3" && file="" -[ $# -eq 4 ] && USB_DEV="-d $4" - -if [ -n "$file" ] -then - dfu_test_file $file -else - for f in $DIR*.$SUFFIX - do - dfu_test_file $f - done -fi - -cleanup - -exit 0 diff --git a/test/dfu/dfu_gadget_test_init.sh b/test/dfu/dfu_gadget_test_init.sh deleted file mode 100755 index 640628eecb..0000000000 --- a/test/dfu/dfu_gadget_test_init.sh +++ /dev/null @@ -1,45 +0,0 @@ -#! /bin/bash - -# Copyright (C) 2014 Samsung Electronics -# Lukasz Majewski -# -# Script fixes, enhancements and testing: -# Stephen Warren -# -# Script for test files generation -# -# SPDX-License-Identifier: GPL-2.0+ - -set -e # any command return if not equal to zero -clear - -COLOUR_RED="\33[31m" -COLOUR_GREEN="\33[32m" -COLOUR_DEFAULT="\33[0m" - -LOG_DIR="./log" - -if [ $# -eq 0 ]; then - TEST_FILES_SIZES="63 64 65 127 128 129 4095 4096 4097 959 960 961 1048575 1048576 8M" -else - TEST_FILES_SIZES=$@ -fi - -printf "Init script for generating data necessary for DFU test script" - -if [ ! -d $LOG_DIR ]; then - `mkdir $LOG_DIR` -fi - -for size in $TEST_FILES_SIZES -do - FILE="./dat_$size.img" - if [ ! -f $FILE ]; then - dd if=/dev/urandom of="./dat_$size.img" bs=$size count=1 > /dev/null 2>&1 || exit $? - fi -done -dd if=/dev/urandom of="./dfudummy.bin" bs=1024 count=1 > /dev/null 2>&1 || exit $? - -printf "$COLOUR_GREEN OK $COLOUR_DEFAULT \n" - -exit 0 diff --git a/test/py/tests/test_dfu.py b/test/py/tests/test_dfu.py new file mode 100644 index 0000000000..cc4b8d8e04 --- /dev/null +++ b/test/py/tests/test_dfu.py @@ -0,0 +1,262 @@ +# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. +# +# SPDX-License-Identifier: GPL-2.0 + +# Test U-Boot's "dfu" command. The test starts DFU in U-Boot, waits for USB +# device enumeration on the host, executes dfu-util multiple times to test +# various transfer sizes, many of which trigger USB driver edge cases, and +# finally aborts the "dfu" command in U-Boot. + +import os +import os.path +import pytest +import u_boot_utils + +''' +Note: This test relies on: + +a) boardenv_* to contain configuration values to define which USB ports are +available for testing. Without this, this test will be automatically skipped. +For example: + +env__usb_dev_ports = ( + { + "tgt_usb_ctlr": "0", + "host_usb_dev_node": "/dev/usbdev-p2371-2180", + # This parameter is optional /if/ you only have a single board + # attached to your host at a time. + "host_usb_port_path": "3-13", + }, +) + +env__dfu_configs = ( + # eMMC, partition 1 + { + "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1", + "cmd_params": "mmc 0", + }, +) +b) udev rules to set permissions on devices nodes, so that sudo is not +required. For example: + +ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666" + +(You may wish to change the group ID instead of setting the permissions wide +open. All that matters is that the user ID running the test can access the +device.) +''' + +# The set of file sizes to test. These values trigger various edge-cases such +# as one less than, equal to, and one greater than typical USB max packet +# sizes, and similar boundary conditions. +test_sizes = ( + 64 - 1, + 64, + 64 + 1, + 128 - 1, + 128, + 128 + 1, + 960 - 1, + 960, + 960 + 1, + 4096 - 1, + 4096, + 4096 + 1, + 1024 * 1024 - 1, + 1024 * 1024, + 8 * 1024 * 1024, +) + +first_usb_dev_port = None + +@pytest.mark.buildconfigspec('cmd_dfu') +def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): + '''Test the "dfu" command; the host system must be able to enumerate a USB + device when "dfu" is running, various DFU transfers are tested, and the + USB device must disappear when "dfu" is aborted. + + Args: + u_boot_console: A U-Boot console connection. + env__usb_dev_port: The single USB device-mode port specification on + which to run the test. See the file-level comment above for + details of the format. + env__dfu_config: The single DFU (memory region) configuration on which + to run the test. See the file-level comment above for details + of the format. + + Returns: + Nothing. + ''' + + def start_dfu(): + '''Start U-Boot's dfu shell command. + + This also waits for the host-side USB enumeration process to complete. + + Args: + None. + + Returns: + Nothing. + ''' + + u_boot_console.log.action( + 'Starting long-running U-Boot dfu shell command') + + cmd = 'setenv dfu_alt_info "%s"' % env__dfu_config['alt_info'] + u_boot_console.run_command(cmd) + + cmd = 'dfu 0 ' + env__dfu_config['cmd_params'] + u_boot_console.run_command(cmd, wait_for_prompt=False) + u_boot_console.log.action('Waiting for DFU USB device to appear') + fh = u_boot_utils.wait_until_open_succeeds( + env__usb_dev_port['host_usb_dev_node']) + fh.close() + + def stop_dfu(ignore_errors): + '''Stop U-Boot's dfu shell command from executing. + + This also waits for the host-side USB de-enumeration process to + complete. + + Args: + ignore_errors: Ignore any errors. This is useful if an error has + already been detected, and the code is performing best-effort + cleanup. In this case, we do not want to mask the original + error by "honoring" any new errors. + + Returns: + Nothing. + ''' + + try: + u_boot_console.log.action( + 'Stopping long-running U-Boot dfu shell command') + u_boot_console.ctrlc() + u_boot_console.log.action( + 'Waiting for DFU USB device to disappear') + u_boot_utils.wait_until_file_open_fails( + env__usb_dev_port['host_usb_dev_node'], ignore_errors) + except: + if not ignore_errors: + raise + + def run_dfu_util(alt_setting, fn, up_dn_load_arg): + '''Invoke dfu-util on the host. + + Args: + alt_setting: The DFU "alternate setting" identifier to interact + with. + fn: The host-side file name to transfer. + up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or + download operation should be performed. + + Returns: + Nothing. + ''' + + cmd = ['dfu-util', '-a', str(alt_setting), up_dn_load_arg, fn] + if 'host_usb_port_path' in env__usb_dev_port: + cmd += ['-p', env__usb_dev_port['host_usb_port_path']] + u_boot_utils.run_and_log(u_boot_console, cmd) + u_boot_console.wait_for('Ctrl+C to exit ...') + + def dfu_write(alt_setting, fn): + '''Write a file to the target board using DFU. + + Args: + alt_setting: The DFU "alternate setting" identifier to interact + with. + fn: The host-side file name to transfer. + + Returns: + Nothing. + ''' + + run_dfu_util(alt_setting, fn, '-D') + + def dfu_read(alt_setting, fn): + '''Read a file from the target board using DFU. + + Args: + alt_setting: The DFU "alternate setting" identifier to interact + with. + fn: The host-side file name to transfer. + + Returns: + Nothing. + ''' + + # dfu-util fails reads/uploads if the host file already exists + if os.path.exists(fn): + os.remove(fn) + run_dfu_util(alt_setting, fn, '-U') + + def dfu_write_read_check(size): + '''Test DFU transfers of a specific size of data + + This function first writes data to the board then reads it back and + compares the written and read back data. Measures are taken to avoid + certain types of false positives. + + Args: + size: The data size to test. + + Returns: + Nothing. + ''' + + test_f = u_boot_utils.PersistentRandomFile(u_boot_console, + 'dfu_%d.bin' % size, size) + readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin' + + u_boot_console.log.action('Writing test data to DFU primary ' + + 'altsetting') + dfu_write(0, test_f.abs_fn) + + u_boot_console.log.action('Writing dummy data to DFU secondary ' + + 'altsetting to clear DFU buffers') + dfu_write(1, dummy_f.abs_fn) + + u_boot_console.log.action('Reading DFU primary altsetting for ' + + 'comparison') + dfu_read(0, readback_fn) + + u_boot_console.log.action('Comparing written and read data') + written_hash = test_f.content_hash + read_back_hash = u_boot_utils.md5sum_file(readback_fn, size) + assert(written_hash == read_back_hash) + + # This test may be executed against multiple USB ports. The test takes a + # long time, so we don't want to do the whole thing each time. Instead, + # execute the full test on the first USB port, and perform a very limited + # test on other ports. In the limited case, we solely validate that the + # host PC can enumerate the U-Boot USB device. + global first_usb_dev_port + if not first_usb_dev_port: + first_usb_dev_port = env__usb_dev_port + if env__usb_dev_port == first_usb_dev_port: + sizes = test_sizes + else: + sizes = [] + + dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console, + 'dfu_dummy.bin', 1024) + + ignore_cleanup_errors = True + try: + start_dfu() + + u_boot_console.log.action( + 'Overwriting DFU primary altsetting with dummy data') + dfu_write(0, dummy_f.abs_fn) + + for size in sizes: + with u_boot_console.log.section("Data size %d" % size): + dfu_write_read_check(size) + # Make the status of each sub-test obvious. If the test didn't + # pass, an exception was thrown so this code isn't executed. + u_boot_console.log.status_pass('OK') + ignore_cleanup_errors = False + finally: + stop_dfu(ignore_cleanup_errors) From 05266103349e8409f5fbd55197c75c7bb58f575d Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Thu, 21 Jan 2016 16:05:30 -0700 Subject: [PATCH 14/36] test/py: move find_ram_base() into u_boot_utils find_ram_base() is a shared utility function, not a core part of the U-Boot console interaction. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/tests/test_md.py | 5 +++-- test/py/u_boot_console_base.py | 37 --------------------------------- test/py/u_boot_utils.py | 38 ++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/test/py/tests/test_md.py b/test/py/tests/test_md.py index 94603c7df6..32cce4f15c 100644 --- a/test/py/tests/test_md.py +++ b/test/py/tests/test_md.py @@ -4,13 +4,14 @@ # SPDX-License-Identifier: GPL-2.0 import pytest +import u_boot_utils @pytest.mark.buildconfigspec('cmd_memory') def test_md(u_boot_console): '''Test that md reads memory as expected, and that memory can be modified using the mw command.''' - ram_base = u_boot_console.find_ram_base() + ram_base = u_boot_utils.find_ram_base(u_boot_console) addr = '%08x' % ram_base val = 'a5f09876' expected_response = addr + ': ' + val @@ -26,7 +27,7 @@ def test_md_repeat(u_boot_console): '''Test command repeat (via executing an empty command) operates correctly for "md"; the command must repeat and dump an incrementing address.''' - ram_base = u_boot_console.find_ram_base() + ram_base = u_boot_utils.find_ram_base(u_boot_console) addr_base = '%08x' % ram_base words = 0x10 addr_repeat = '%08x' % (ram_base + (words * 4)) diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index 06f61f9871..51163bc0db 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -86,7 +86,6 @@ class ConsoleBase(object): self.at_prompt = False self.at_prompt_logevt = None - self.ram_base = None def close(self): '''Terminate the connection to the U-Boot console. @@ -378,39 +377,3 @@ class ConsoleBase(object): ''' return ConsoleDisableCheck(self, check_type) - - def find_ram_base(self): - '''Find the running U-Boot's RAM location. - - Probe the running U-Boot to determine the address of the first bank - of RAM. This is useful for tests that test reading/writing RAM, or - load/save files that aren't associated with some standard address - typically represented in an environment variable such as - ${kernel_addr_r}. The value is cached so that it only needs to be - actively read once. - - Args: - None. - - Returns: - The address of U-Boot's first RAM bank, as an integer. - ''' - - if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': - pytest.skip('bdinfo command not supported') - if self.ram_base == -1: - pytest.skip('Previously failed to find RAM bank start') - if self.ram_base is not None: - return self.ram_base - - with self.log.section('find_ram_base'): - response = self.run_command('bdinfo') - for l in response.split('\n'): - if '-> start' in l: - self.ram_base = int(l.split('=')[1].strip(), 16) - break - if self.ram_base is None: - self.ram_base = -1 - raise Exception('Failed to find RAM bank start in `bdinfo`') - - return self.ram_base diff --git a/test/py/u_boot_utils.py b/test/py/u_boot_utils.py index 539af618db..522390a207 100644 --- a/test/py/u_boot_utils.py +++ b/test/py/u_boot_utils.py @@ -169,3 +169,41 @@ def run_and_log(u_boot_console, cmd, ignore_errors=False): runner = u_boot_console.log.get_runner(cmd[0], sys.stdout) runner.run(cmd, ignore_errors=ignore_errors) runner.close() + +ram_base = None +def find_ram_base(u_boot_console): + '''Find the running U-Boot's RAM location. + + Probe the running U-Boot to determine the address of the first bank + of RAM. This is useful for tests that test reading/writing RAM, or + load/save files that aren't associated with some standard address + typically represented in an environment variable such as + ${kernel_addr_r}. The value is cached so that it only needs to be + actively read once. + + Args: + u_boot_console: A console connection to U-Boot. + + Returns: + The address of U-Boot's first RAM bank, as an integer. + ''' + + global ram_base + if u_boot_console.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': + pytest.skip('bdinfo command not supported') + if ram_base == -1: + pytest.skip('Previously failed to find RAM bank start') + if ram_base is not None: + return ram_base + + with u_boot_console.log.section('find_ram_base'): + response = u_boot_console.run_command('bdinfo') + for l in response.split('\n'): + if '-> start' in l: + ram_base = int(l.split('=')[1].strip(), 16) + break + if ram_base is None: + ram_base = -1 + raise Exception('Failed to find RAM bank start in `bdinfo`') + + return ram_base From e5bb279f826fb6967a554f6aaf6c8bf86b23cde2 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Thu, 21 Jan 2016 16:05:31 -0700 Subject: [PATCH 15/36] test/py: add a networking test This tests: - dhcp (if indicated by boardenv file). - Static IP network setup (if provided by boardenv file). - Ping. - TFTP get. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/tests/test_net.py | 153 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 test/py/tests/test_net.py diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py new file mode 100644 index 0000000000..c73854ea74 --- /dev/null +++ b/test/py/tests/test_net.py @@ -0,0 +1,153 @@ +# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. +# +# SPDX-License-Identifier: GPL-2.0 + +# Test various network-related functionality, such as the dhcp, ping, and +# tftpboot commands. + +import pytest +import u_boot_utils + +''' +Note: This test relies on boardenv_* containing configuration values to define +which the network environment available for testing. Without this, this test +will be automatically skipped. + +For example: + +# Any commands that need to be executed prior to testing, +# to get the network hardware into an operational state. +# +# If no commands are required, this variable may be omitted, or set to an +# empty list. +env__net_pre_commands = [ + "usb start", +] + +# True if a DHCP server is attached to the network, and should be tested. +# If DHCP testing is not possible or desired, this variable may be omitted or +# set to False. +env__net_dhcp_server = True + +# A list of environment variables that should be set in order to configure a +# static IP. If solely relying on DHCP, this variable may be omitted or set to +# an empty list. +env__net_static_env_vars = [ + ("ipaddr", "10.0.0.100"), + ("netmask", "255.255.255.0"), + ("serverip", "10.0.0.1"), +] + +# Details regarding a file that may be read from a TFTP server. This variable +# may be omitted or set to None if TFTP testing is not possible or desired. +env__net_tftp_readable_file = { + "fn": "ubtest-readable.bin", + "size": 5058624, + "crc32": "c2244b26", +} +''' + +net_set_up = False + +def test_net_pre_commands(u_boot_console): + '''Execute any commands required to enable network hardware. + + These commands are provided by the boardenv_* file; see the comment at the + beginning of this file. + ''' + + cmds = u_boot_console.config.env.get('env__net_pre_commands', None) + if not cmds: + pytest.skip('No network pre-commands defined') + + for cmd in cmds: + u_boot_console.run_command(cmd) + +@pytest.mark.buildconfigspec('cmd_dhcp') +def test_net_dhcp(u_boot_console): + '''Test the dhcp command. + + The boardenv_* file may be used to enable/disable this test; see the + comment at the beginning of this file. + ''' + + test_dhcp = u_boot_console.config.env.get('env__net_dhcp_server', False) + if not test_dhcp: + pytest.skip('No DHCP server available') + + u_boot_console.run_command('setenv autoload no') + output = u_boot_console.run_command('dhcp') + assert 'DHCP client bound to address ' in output + + global net_set_up + net_set_up = True + +@pytest.mark.buildconfigspec('net') +def test_net_setup_static(u_boot_console): + '''Set up a static IP configuration. + + The configuration is provided by the boardenv_* file; see the comment at + the beginning of this file. + ''' + + env_vars = u_boot_console.config.env.get('env__net_static_env_vars', None) + if not env_vars: + pytest.skip('No static network configuration is defined') + + for (var, val) in env_vars: + u_boot_console.run_command('setenv %s %s' % (var, val)) + + global net_set_up + net_set_up = True + +@pytest.mark.buildconfigspec('cmd_ping') +def test_net_ping(u_boot_console): + '''Test the ping command. + + The $serverip (as set up by either test_net_dhcp or test_net_setup_static) + is pinged. The test validates that the host is alive, as reported by the + ping command's output. + ''' + + if not net_set_up: + pytest.skip("Network not initialized") + + output = u_boot_console.run_command('ping $serverip') + assert 'is alive' in output + +@pytest.mark.buildconfigspec('cmd_net') +def test_net_tftpboot(u_boot_console): + '''Test the tftpboot command. + + A file is downloaded from the TFTP server, its size and optionally its + CRC32 are validated. + + The details of the file to download are provided by the boardenv_* file; + see the comment at the beginning of this file. + ''' + + if not net_set_up: + pytest.skip("Network not initialized") + + f = u_boot_console.config.env.get('env__net_tftp_readable_file', None) + if not f: + pytest.skip('No TFTP readable file to read') + + addr = u_boot_utils.find_ram_base(u_boot_console) + fn = f['fn'] + output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn)) + expected_text = 'Bytes transferred = ' + sz = f.get('size', None) + if sz: + expected_text += '%d' % sz + assert expected_text in output + + expected_crc = f.get('crc32', None) + if not expected_crc: + return + + if u_boot_console.config.buildconfig.get('config_cmd_crc32', 'n') != 'y': + return + + output = u_boot_console.run_command('crc32 %x $filesize' % addr) + assert expected_crc in output From c6db965f67c01c9ac6570eed690103f3ea30b3c4 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 25 Jan 2016 14:58:42 -0700 Subject: [PATCH 16/36] dm: Remove device_probe_child() This function is not used as the use case for it did not eventuate. Remove it to avoid confusion. Signed-off-by: Simon Glass Reviewed-by: Tom Rini Reviewed-by: Bin Meng --- drivers/core/device.c | 9 +-------- include/dm/device-internal.h | 13 ------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/drivers/core/device.c b/drivers/core/device.c index f5def35b5b..cb24a617ce 100644 --- a/drivers/core/device.c +++ b/drivers/core/device.c @@ -223,7 +223,7 @@ static void *alloc_priv(int size, uint flags) return priv; } -int device_probe_child(struct udevice *dev, void *parent_priv) +int device_probe(struct udevice *dev) { const struct driver *drv; int size = 0; @@ -270,8 +270,6 @@ int device_probe_child(struct udevice *dev, void *parent_priv) ret = -ENOMEM; goto fail; } - if (parent_priv) - memcpy(dev->parent_priv, parent_priv, size); } ret = device_probe(dev->parent); @@ -349,11 +347,6 @@ fail: return ret; } -int device_probe(struct udevice *dev) -{ - return device_probe_child(dev, NULL); -} - void *dev_get_platdata(struct udevice *dev) { if (!dev) { diff --git a/include/dm/device-internal.h b/include/dm/device-internal.h index 9388870d0c..b348ad5231 100644 --- a/include/dm/device-internal.h +++ b/include/dm/device-internal.h @@ -65,19 +65,6 @@ int device_bind_by_name(struct udevice *parent, bool pre_reloc_only, */ int device_probe(struct udevice *dev); -/** - * device_probe() - Probe a child device, activating it - * - * Activate a device so that it is ready for use. All its parents are probed - * first. The child is provided with parent data if parent_priv is not NULL. - * - * @dev: Pointer to device to probe - * @parent_priv: Pointer to parent data. If non-NULL then this is provided to - * the child. - * @return 0 if OK, -ve on error - */ -int device_probe_child(struct udevice *dev, void *parent_priv); - /** * device_remove() - Remove a device, de-activating it * From e787a58fe2544497bbc75066e0bc62868c7c4e65 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Mon, 25 Jan 2016 15:07:58 -0700 Subject: [PATCH 17/36] test/py: make crash detection more robust test/py contains logic to detect the target crashing and rebooting by searching the console output for a U-Boot signon message, which will presumably be emitted when the system boots after the crash/reset. Currently, this logic only searches for the exact signon message that was printed by the U-Boot version under test, upon the assumption that binary is written into flash, and hence will be the version booted after any reset. However, this is not a valid assumption; some test setups download the U-Boot-under-test into RAM and boot it from there, and in such a scenario an arbitrary U-Boot version may be located in flash and hence run after any reset. Fix the reset detection logic to match any U-Boot signon message. This prevents false negatives. Signed-off-by: Stephen Warren Acked-by: Simon Glass --- test/py/u_boot_console_base.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index 51163bc0db..bb834b0d34 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -150,12 +150,11 @@ class ConsoleBase(object): bad_patterns = [] bad_pattern_ids = [] - if (self.disable_check_count['spl_signon'] == 0 and - self.u_boot_spl_signon): - bad_patterns.append(self.u_boot_spl_signon_escaped) + if (self.disable_check_count['spl_signon'] == 0): + bad_patterns.append(pattern_u_boot_spl_signon) bad_pattern_ids.append('SPL signon') if self.disable_check_count['main_signon'] == 0: - bad_patterns.append(self.u_boot_main_signon_escaped) + bad_patterns.append(pattern_u_boot_main_signon) bad_pattern_ids.append('U-Boot main signon') if self.disable_check_count['unknown_command'] == 0: bad_patterns.append(pattern_unknown_command) @@ -299,18 +298,13 @@ class ConsoleBase(object): self.p.logfile_read = self.logstream if self.config.buildconfig.get('CONFIG_SPL', False) == 'y': self.p.expect([pattern_u_boot_spl_signon]) - self.u_boot_spl_signon = self.p.after - self.u_boot_spl_signon_escaped = re.escape(self.p.after) - else: - self.u_boot_spl_signon = None self.p.expect([pattern_u_boot_main_signon]) - self.u_boot_main_signon = self.p.after - self.u_boot_main_signon_escaped = re.escape(self.p.after) - build_idx = self.u_boot_main_signon.find(', Build:') + signon = self.p.after + build_idx = signon.find(', Build:') if build_idx == -1: - self.u_boot_version_string = self.u_boot_main_signon + self.u_boot_version_string = signon else: - self.u_boot_version_string = self.u_boot_main_signon[:build_idx] + self.u_boot_version_string = signon[:build_idx] while True: match = self.p.expect([self.prompt_escaped, pattern_stop_autoboot_prompt]) From dd8204de157e10c080aa2cdc0f24bcb2e4ac73dd Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 10:59:42 -0700 Subject: [PATCH 18/36] ARM: tegra: shut down USB device controller at boot When loading U-Boot into RAM over USB protocols using tools such as tegrarcm or L4T's exec-uboot.sh/tegraflash.py, Tegra's USB device mode controller is initialized and enumerated by the host PC running the tool. Unfortunately, these tools do not shut down the USB controller before executing the downloaded code, and so the host PC does not "de-enumerate" the USB device. This patch implements optional code to shut down the USB controller when U-Boot boots to avoid leaving a stale USB device present. Signed-off-by: Stephen Warren Reviewed-by: Simon Glass --- arch/arm/mach-tegra/Kconfig | 13 +++++++++++++ arch/arm/mach-tegra/board2.c | 10 +++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index 48a387c957..0b2852c4da 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -52,6 +52,19 @@ config TEGRA210 endchoice +config TEGRA_DISCONNECT_UDC_ON_BOOT + bool "Disconnect USB device mode controller on boot" + default y + help + When loading U-Boot into RAM over USB protocols using tools such as + tegrarcm or L4T's exec-uboot.sh/tegraflash.py, Tegra's USB device + mode controller is initialized and enumerated by the host PC running + the tool. Unfortunately, these tools do not shut down the USB + controller before executing the downloaded code, and so the host PC + does not "de-enumerate" the USB device. This option shuts down the + USB controller when U-Boot boots to avoid leaving a stale USB device + present. + config SYS_MALLOC_F_LEN default 0x1800 diff --git a/arch/arm/mach-tegra/board2.c b/arch/arm/mach-tegra/board2.c index a650abd731..60e19c8387 100644 --- a/arch/arm/mach-tegra/board2.c +++ b/arch/arm/mach-tegra/board2.c @@ -34,8 +34,8 @@ #ifdef CONFIG_TEGRA_CLOCK_SCALING #include #endif -#ifdef CONFIG_USB_EHCI_TEGRA #include +#ifdef CONFIG_USB_EHCI_TEGRA #include #endif #ifdef CONFIG_TEGRA_MMC @@ -201,6 +201,14 @@ void gpio_early_init(void) __attribute__((weak, alias("__gpio_early_init"))); int board_early_init_f(void) { +#if defined(CONFIG_TEGRA_DISCONNECT_UDC_ON_BOOT) +#define USBCMD_FS2 (1 << 15) + { + struct usb_ctlr *usbctlr = (struct usb_ctlr *)0x7d000000; + writel(USBCMD_FS2, &usbctlr->usb_cmd); + } +#endif + /* Do any special system timer/TSC setup */ #if defined(CONFIG_TEGRA_SUPPORT_NON_SECURE) if (!tegra_cpu_is_non_secure()) From be1df82656b15663dee9216a320dc177c7d9c6c4 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 10:59:43 -0700 Subject: [PATCH 19/36] test/py: dfu: error out if USB device already exists The DFU test requests U-Boot configure its USB controller in device mode, then waits for the host machine to enumerate the USB device and create a device node for it. However, this wait can be fooled if the USB device node already exists before the test starts, e.g. if some previous software stack already configured the USB controller into device mode and never de-configured it. This "previous software stack" could even be another test/py test, if U-Boot's own USB teardown does not operate correctly. If this happens, dfu-util may be run before U-Boot is ready to serve DFU commands, which may cause false test failures. Enhance the dfu test to fail if the device node exists before it is expected to. Signed-off-by: Stephen Warren Reviewed-by: Simon Glass --- test/py/tests/test_dfu.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/py/tests/test_dfu.py b/test/py/tests/test_dfu.py index cc4b8d8e04..6c1a363b02 100644 --- a/test/py/tests/test_dfu.py +++ b/test/py/tests/test_dfu.py @@ -100,6 +100,12 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): Nothing. ''' + fh = u_boot_utils.attempt_to_open_file( + env__usb_dev_port['host_usb_dev_node']) + if fh: + fh.close() + raise Exception('USB device present before dfu command invoked') + u_boot_console.log.action( 'Starting long-running U-Boot dfu shell command') From e578b92cdb378df0f09065e3222fe8620867a57a Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 11:10:11 -0700 Subject: [PATCH 20/36] Implement "pci enum" command for CONFIG_DM_PCI With CONFIG_DM_PCI enabled, PCI buses are not enumerated at boot, as they are without that config option enabled. No command exists to enumerate the PCI buses. Hence, unless some board-specific code causes PCI enumeration, PCI-based Ethernet devices are not detected, and network access is not available. This patch implements "pci enum" in the CONFIG_DM_PCI case, thus giving a mechanism whereby PCI can be enumerated. do_pci()'s handling of case 'e' is moved into a single location before the dev variable is assigned, in order to skip calculation of dev. The enum sub-command doesn't need the dev value, and skipping its calculation avoids an irrelevant error being printed. Using a command to initialize PCI like this has a disadvantage relative to enumerating PCI at boot. In particular, Ethernet devices are not probed during PCI enumeration, but only when used. This defers setting variables such as ethact, ethaddr, etc. until the first network-related command is executed. Hopefully this will not cause further issues. Perhaps in the long term, we need a "net start/enum" command too? Signed-off-by: Stephen Warren Reviewed-by: Simon Glass Reviewed-by: Bin Meng --- cmd/pci.c | 18 +++++------------- drivers/pci/pci-uclass.c | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/cmd/pci.c b/cmd/pci.c index 8094d3380f..2f4978af9f 100644 --- a/cmd/pci.c +++ b/cmd/pci.c @@ -578,9 +578,10 @@ static int do_pci(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) if ((bdf = get_pci_dev(argv[2])) == -1) return 1; break; -#ifdef CONFIG_CMD_PCI_ENUM +#if defined(CONFIG_CMD_PCI_ENUM) || defined(CONFIG_DM_PCI) case 'e': - break; + pci_init(); + return 0; #endif default: /* scan bus */ value = 1; /* short listing */ @@ -621,15 +622,6 @@ static int do_pci(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) break; case 'd': /* display */ return pci_cfg_display(dev, addr, size, value); -#ifdef CONFIG_CMD_PCI_ENUM - case 'e': -# ifdef CONFIG_DM_PCI - printf("This command is not yet supported with driver model\n"); -# else - pci_init(); -# endif - break; -#endif case 'n': /* next */ if (argc < 4) goto usage; @@ -665,9 +657,9 @@ static int do_pci(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) static char pci_help_text[] = "[bus] [long]\n" " - short or long list of PCI devices on bus 'bus'\n" -#ifdef CONFIG_CMD_PCI_ENUM +#if defined(CONFIG_CMD_PCI_ENUM) || defined(CONFIG_DM_PCI) "pci enum\n" - " - re-enumerate PCI buses\n" + " - Enumerate PCI buses\n" #endif "pci header b.d.f\n" " - show header of PCI device 'bus.device.function'\n" diff --git a/drivers/pci/pci-uclass.c b/drivers/pci/pci-uclass.c index 61292d72bd..d01bfc12e4 100644 --- a/drivers/pci/pci-uclass.c +++ b/drivers/pci/pci-uclass.c @@ -1241,3 +1241,18 @@ U_BOOT_DRIVER(pci_generic_drv) = { .id = UCLASS_PCI_GENERIC, .of_match = pci_generic_ids, }; + +void pci_init(void) +{ + struct udevice *bus; + + /* + * Enumerate all known controller devices. Enumeration has the side- + * effect of probing them, so PCIe devices will be enumerated too. + */ + for (uclass_first_device(UCLASS_PCI, &bus); + bus; + uclass_next_device(&bus)) { + ; + } +} From 3483b75d94716191b354d046b4ed3f322500a3ee Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 11:10:12 -0700 Subject: [PATCH 21/36] distro bootcmd: make net boot only optionally start USB Currently, the distro boot commands always enumerate USB devices before performing network operations. However, depending on the board and end- user configuration, network devices may not be attached to USB, and so enumerating USB may not be necessary. Enhance the scripts to make this step optional, so that the user can decrease boot time if they don't need USB. This change is performed by moving the "usb start" invocation into a standalone variable. If the user desires, they can replace that variable's value with some no-op command such as "true" instead. Booting from a USB storage device always needs to enumerate USB devices, so this action is still hard-coded. Signed-off-by: Stephen Warren Reviewed-by: Simon Glass --- doc/README.distro | 7 +++++++ include/config_distro_bootcmd.h | 11 ++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/doc/README.distro b/doc/README.distro index 9e4722a86e..019903ea7d 100644 --- a/doc/README.distro +++ b/doc/README.distro @@ -341,6 +341,13 @@ scan_dev_for_scripts: If you want to disable boot.scr on all disks, set the value to something innocuous, e.g. setenv scan_dev_for_scripts true. +boot_net_usb_start: + + If you want to prevent USB enumeration by distro boot commands which execute + network operations, set the value to something innocuous, e.g. setenv + boot_net_usb_start true. This would be useful if you know your Ethernet + device is not attached to USB, and you wish to increase boot speed by + avoiding unnecessary actions. Interactively booting from a specific device at the u-boot prompt ================================================================= diff --git a/include/config_distro_bootcmd.h b/include/config_distro_bootcmd.h index 66264ce3b4..c027d86439 100644 --- a/include/config_distro_bootcmd.h +++ b/include/config_distro_bootcmd.h @@ -140,15 +140,16 @@ #endif #ifdef CONFIG_CMD_USB -#define BOOTENV_RUN_USB_INIT "usb start; " +#define BOOTENV_RUN_NET_USB_START "run boot_net_usb_start; " #define BOOTENV_SHARED_USB \ + "boot_net_usb_start=usb start\0" \ "usb_boot=" \ - BOOTENV_RUN_USB_INIT \ + "usb start; " \ BOOTENV_SHARED_BLKDEV_BODY(usb) #define BOOTENV_DEV_USB BOOTENV_DEV_BLKDEV #define BOOTENV_DEV_NAME_USB BOOTENV_DEV_NAME_BLKDEV #else -#define BOOTENV_RUN_USB_INIT +#define BOOTENV_RUN_NET_USB_START #define BOOTENV_SHARED_USB #define BOOTENV_DEV_USB \ BOOT_TARGET_DEVICES_references_USB_without_CONFIG_CMD_USB @@ -159,7 +160,7 @@ #if defined(CONFIG_CMD_DHCP) #define BOOTENV_DEV_DHCP(devtypeu, devtypel, instance) \ "bootcmd_dhcp=" \ - BOOTENV_RUN_USB_INIT \ + BOOTENV_RUN_NET_USB_START \ "if dhcp ${scriptaddr} ${boot_script_dhcp}; then " \ "source ${scriptaddr}; " \ "fi\0" @@ -175,7 +176,7 @@ #if defined(CONFIG_CMD_DHCP) && defined(CONFIG_CMD_PXE) #define BOOTENV_DEV_PXE(devtypeu, devtypel, instance) \ "bootcmd_pxe=" \ - BOOTENV_RUN_USB_INIT \ + BOOTENV_RUN_NET_USB_START \ "dhcp; " \ "if pxe get; then " \ "pxe boot; " \ From 986691fb97bc245be517a9db1297cfa71dd865cf Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 11:10:13 -0700 Subject: [PATCH 22/36] distro bootcmd: enumerate PCI before network operations The PCI bus must be enumerated before PCI devices, such as Ethernet devices, are known to U-Boot. Enhance the distro boot commands to perform PCI enumeration when needed. Signed-off-by: Stephen Warren Reviewed-by: Simon Glass --- doc/README.distro | 8 ++++++++ include/config_distro_bootcmd.h | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/doc/README.distro b/doc/README.distro index 019903ea7d..3715c8c3ba 100644 --- a/doc/README.distro +++ b/doc/README.distro @@ -349,6 +349,14 @@ boot_net_usb_start: device is not attached to USB, and you wish to increase boot speed by avoiding unnecessary actions. +boot_net_pci_enum: + + If you want to prevent PCI enumeration by distro boot commands which execute + network operations, set the value to something innocuous, e.g. setenv + boot_net_pci_enum true. This would be useful if you know your Ethernet + device is not attached to PCI, and you wish to increase boot speed by + avoiding unnecessary actions. + Interactively booting from a specific device at the u-boot prompt ================================================================= diff --git a/include/config_distro_bootcmd.h b/include/config_distro_bootcmd.h index c027d86439..37c6b438e2 100644 --- a/include/config_distro_bootcmd.h +++ b/include/config_distro_bootcmd.h @@ -139,6 +139,15 @@ BOOT_TARGET_DEVICES_references_IDE_without_CONFIG_CMD_IDE #endif +#if defined(CONFIG_CMD_PCI_ENUM) || defined(CONFIG_DM_PCI) +#define BOOTENV_RUN_NET_PCI_ENUM "run boot_net_pci_enum; " +#define BOOTENV_SHARED_PCI \ + "boot_net_pci_enum=pci enum\0" +#else +#define BOOTENV_RUN_NET_PCI_ENUM +#define BOOTENV_SHARED_PCI +#endif + #ifdef CONFIG_CMD_USB #define BOOTENV_RUN_NET_USB_START "run boot_net_usb_start; " #define BOOTENV_SHARED_USB \ @@ -161,6 +170,7 @@ #define BOOTENV_DEV_DHCP(devtypeu, devtypel, instance) \ "bootcmd_dhcp=" \ BOOTENV_RUN_NET_USB_START \ + BOOTENV_RUN_NET_PCI_ENUM \ "if dhcp ${scriptaddr} ${boot_script_dhcp}; then " \ "source ${scriptaddr}; " \ "fi\0" @@ -177,6 +187,7 @@ #define BOOTENV_DEV_PXE(devtypeu, devtypel, instance) \ "bootcmd_pxe=" \ BOOTENV_RUN_NET_USB_START \ + BOOTENV_RUN_NET_PCI_ENUM \ "dhcp; " \ "if pxe get; then " \ "pxe boot; " \ @@ -200,6 +211,7 @@ #define BOOTENV \ BOOTENV_SHARED_HOST \ BOOTENV_SHARED_MMC \ + BOOTENV_SHARED_PCI \ BOOTENV_SHARED_USB \ BOOTENV_SHARED_SATA \ BOOTENV_SHARED_SCSI \ From 56382a81f38bed423791d7b80e95c1f65bd83b9b Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 11:10:14 -0700 Subject: [PATCH 23/36] test/py: make net test aware of USB and PCI enumeration The existing net test executes a list of commands supplied by boardenv variable env__net_pre_commands. The idea was that boardenv would know whether the Ethernet device was attached to USB, PCI, ... and hence was the best place to put any commands required to probe the device. However, this approach doesn't scale well when attempting to use a single boardenv across multiple branches of U-Boot, some of which require "pci enum" to enumerate PCI and others of which don't, or don't /yet/ simply because various upstream changes haven't been merged down. This patch updates the test to require that the boardenv state which HW features are required for Ethernet to work, and lets the test itself map that knowledge to the set of commands to execute. Since this mapping is part of the test script, which is part of the U-Boot code/branch, this approach is more scalable. It also feels cleaner, since again boardenv is only providing data, rather than test logic. Signed-off-by: Stephen Warren Reviewed-by: Simon Glass --- test/py/tests/test_net.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index c73854ea74..9c46551e8c 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -15,14 +15,15 @@ will be automatically skipped. For example: -# Any commands that need to be executed prior to testing, -# to get the network hardware into an operational state. -# -# If no commands are required, this variable may be omitted, or set to an -# empty list. -env__net_pre_commands = [ - "usb start", -] +# Boolean indicating whether the Ethernet device is attached to USB, and hence +# USB enumeration needs to be performed prior to network tests. +# This variable may be omitted if its value is False. +env__net_uses_usb = False + +# Boolean indicating whether the Ethernet device is attached to PCI, and hence +# PCI enumeration needs to be performed prior to network tests. +# This variable may be omitted if its value is False. +env__net_uses_pci = True # True if a DHCP server is attached to the network, and should be tested. # If DHCP testing is not possible or desired, this variable may be omitted or @@ -56,12 +57,13 @@ def test_net_pre_commands(u_boot_console): beginning of this file. ''' - cmds = u_boot_console.config.env.get('env__net_pre_commands', None) - if not cmds: - pytest.skip('No network pre-commands defined') + init_usb = u_boot_console.config.env.get('env__net_uses_usb', False) + if init_usb: + u_boot_console.run_command('usb start') - for cmd in cmds: - u_boot_console.run_command(cmd) + init_pci = u_boot_console.config.env.get('env__net_uses_pci', False) + if init_pci: + u_boot_console.run_command('pci enum') @pytest.mark.buildconfigspec('cmd_dhcp') def test_net_dhcp(u_boot_console): From e8debf394fbba594fcfc267c61f8c6bbca395b06 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 13:41:30 -0700 Subject: [PATCH 24/36] test/py: use " for docstrings Python's coding style docs indicate to use " not ' for docstrings. test/py has other violations of the coding style docs, since the docs specify a stranger style than I would expect, but nobody has complained about those yet:-) Signed-off-by: Stephen Warren Reviewed-by: Simon Glass --- test/py/conftest.py | 44 +++++----- test/py/multiplexed_log.py | 122 +++++++++++++------------- test/py/tests/test_000_version.py | 2 +- test/py/tests/test_dfu.py | 32 +++---- test/py/tests/test_env.py | 54 ++++++------ test/py/tests/test_help.py | 2 +- test/py/tests/test_hush_if_test.py | 10 +-- test/py/tests/test_md.py | 8 +- test/py/tests/test_net.py | 24 ++--- test/py/tests/test_sandbox_exit.py | 4 +- test/py/tests/test_shell_basics.py | 10 +-- test/py/tests/test_sleep.py | 4 +- test/py/tests/test_ums.py | 24 ++--- test/py/tests/test_unknown_cmd.py | 4 +- test/py/u_boot_console_base.py | 48 +++++----- test/py/u_boot_console_exec_attach.py | 12 +-- test/py/u_boot_console_sandbox.py | 20 ++--- test/py/u_boot_spawn.py | 30 +++---- test/py/u_boot_utils.py | 36 ++++---- 19 files changed, 245 insertions(+), 245 deletions(-) diff --git a/test/py/conftest.py b/test/py/conftest.py index c1f19cee65..a4e54c66ce 100644 --- a/test/py/conftest.py +++ b/test/py/conftest.py @@ -29,7 +29,7 @@ log = None console = None def mkdir_p(path): - '''Create a directory path. + """Create a directory path. This includes creating any intermediate/parent directories. Any errors caused due to already extant directories are ignored. @@ -39,7 +39,7 @@ def mkdir_p(path): Returns: Nothing. - ''' + """ try: os.makedirs(path) @@ -50,14 +50,14 @@ def mkdir_p(path): raise def pytest_addoption(parser): - '''pytest hook: Add custom command-line options to the cmdline parser. + """pytest hook: Add custom command-line options to the cmdline parser. Args: parser: The pytest command-line parser. Returns: Nothing. - ''' + """ parser.addoption('--build-dir', default=None, help='U-Boot build directory (O=)') @@ -73,14 +73,14 @@ def pytest_addoption(parser): help='Compile U-Boot before running tests') def pytest_configure(config): - '''pytest hook: Perform custom initialization at startup time. + """pytest hook: Perform custom initialization at startup time. Args: config: The pytest configuration. Returns: Nothing. - ''' + """ global log global console @@ -190,7 +190,7 @@ def pytest_configure(config): console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig) def pytest_generate_tests(metafunc): - '''pytest hook: parameterize test functions based on custom rules. + """pytest hook: parameterize test functions based on custom rules. If a test function takes parameter(s) (fixture names) of the form brd__xxx or env__xxx, the brd and env configuration dictionaries are consulted to @@ -202,7 +202,7 @@ def pytest_generate_tests(metafunc): Returns: Nothing. - ''' + """ subconfigs = { 'brd': console.config.brd, @@ -229,14 +229,14 @@ def pytest_generate_tests(metafunc): @pytest.fixture(scope='function') def u_boot_console(request): - '''Generate the value of a test's u_boot_console fixture. + """Generate the value of a test's u_boot_console fixture. Args: request: The pytest request. Returns: The fixture value. - ''' + """ console.ensure_spawned() return console @@ -247,7 +247,7 @@ tests_skipped = set() tests_passed = set() def pytest_itemcollected(item): - '''pytest hook: Called once for each test found during collection. + """pytest hook: Called once for each test found during collection. This enables our custom result analysis code to see the list of all tests that should eventually be run. @@ -257,12 +257,12 @@ def pytest_itemcollected(item): Returns: Nothing. - ''' + """ tests_not_run.add(item.name) def cleanup(): - '''Clean up all global state. + """Clean up all global state. Executed (via atexit) once the entire test process is complete. This includes logging the status of all tests, and the identity of any failed @@ -273,7 +273,7 @@ def cleanup(): Returns: Nothing. - ''' + """ if console: console.close() @@ -295,7 +295,7 @@ def cleanup(): atexit.register(cleanup) def setup_boardspec(item): - '''Process any 'boardspec' marker for a test. + """Process any 'boardspec' marker for a test. Such a marker lists the set of board types that a test does/doesn't support. If tests are being executed on an unsupported board, the test is @@ -306,7 +306,7 @@ def setup_boardspec(item): Returns: Nothing. - ''' + """ mark = item.get_marker('boardspec') if not mark: @@ -323,7 +323,7 @@ def setup_boardspec(item): pytest.skip('board not supported') def setup_buildconfigspec(item): - '''Process any 'buildconfigspec' marker for a test. + """Process any 'buildconfigspec' marker for a test. Such a marker lists some U-Boot configuration feature that the test requires. If tests are being executed on an U-Boot build that doesn't @@ -334,7 +334,7 @@ def setup_buildconfigspec(item): Returns: Nothing. - ''' + """ mark = item.get_marker('buildconfigspec') if not mark: @@ -344,7 +344,7 @@ def setup_buildconfigspec(item): pytest.skip('.config feature not enabled') def pytest_runtest_setup(item): - '''pytest hook: Configure (set up) a test item. + """pytest hook: Configure (set up) a test item. Called once for each test to perform any custom configuration. This hook is used to skip the test if certain conditions apply. @@ -354,14 +354,14 @@ def pytest_runtest_setup(item): Returns: Nothing. - ''' + """ log.start_section(item.name) setup_boardspec(item) setup_buildconfigspec(item) def pytest_runtest_protocol(item, nextitem): - '''pytest hook: Called to execute a test. + """pytest hook: Called to execute a test. This hook wraps the standard pytest runtestprotocol() function in order to acquire visibility into, and record, each test function's result. @@ -372,7 +372,7 @@ def pytest_runtest_protocol(item, nextitem): Returns: A list of pytest reports (test result data). - ''' + """ reports = runtestprotocol(item, nextitem=nextitem) failed = None diff --git a/test/py/multiplexed_log.py b/test/py/multiplexed_log.py index 5059bbfb99..c2a3b89536 100644 --- a/test/py/multiplexed_log.py +++ b/test/py/multiplexed_log.py @@ -14,12 +14,12 @@ import subprocess mod_dir = os.path.dirname(os.path.abspath(__file__)) class LogfileStream(object): - '''A file-like object used to write a single logical stream of data into + """A file-like object used to write a single logical stream of data into a multiplexed log file. Objects of this type should be created by factory - functions in the Logfile class rather than directly.''' + functions in the Logfile class rather than directly.""" def __init__(self, logfile, name, chained_file): - '''Initialize a new object. + """Initialize a new object. Args: logfile: The Logfile object to log to. @@ -29,26 +29,26 @@ class LogfileStream(object): Returns: Nothing. - ''' + """ self.logfile = logfile self.name = name self.chained_file = chained_file def close(self): - '''Dummy function so that this class is "file-like". + """Dummy function so that this class is "file-like". Args: None. Returns: Nothing. - ''' + """ pass def write(self, data, implicit=False): - '''Write data to the log stream. + """Write data to the log stream. Args: data: The data to write tot he file. @@ -60,33 +60,33 @@ class LogfileStream(object): Returns: Nothing. - ''' + """ self.logfile.write(self, data, implicit) if self.chained_file: self.chained_file.write(data) def flush(self): - '''Flush the log stream, to ensure correct log interleaving. + """Flush the log stream, to ensure correct log interleaving. Args: None. Returns: Nothing. - ''' + """ self.logfile.flush() if self.chained_file: self.chained_file.flush() class RunAndLog(object): - '''A utility object used to execute sub-processes and log their output to + """A utility object used to execute sub-processes and log their output to a multiplexed log file. Objects of this type should be created by factory - functions in the Logfile class rather than directly.''' + functions in the Logfile class rather than directly.""" def __init__(self, logfile, name, chained_file): - '''Initialize a new object. + """Initialize a new object. Args: logfile: The Logfile object to log to. @@ -96,18 +96,18 @@ class RunAndLog(object): Returns: Nothing. - ''' + """ self.logfile = logfile self.name = name self.chained_file = chained_file def close(self): - '''Clean up any resources managed by this object.''' + """Clean up any resources managed by this object.""" pass def run(self, cmd, cwd=None, ignore_errors=False): - '''Run a command as a sub-process, and log the results. + """Run a command as a sub-process, and log the results. Args: cmd: The command to execute. @@ -120,7 +120,7 @@ class RunAndLog(object): Returns: Nothing. - ''' + """ msg = "+" + " ".join(cmd) + "\n" if self.chained_file: @@ -163,13 +163,13 @@ class RunAndLog(object): raise exception class SectionCtxMgr(object): - '''A context manager for Python's "with" statement, which allows a certain + """A context manager for Python's "with" statement, which allows a certain portion of test code to be logged to a separate section of the log file. Objects of this type should be created by factory functions in the Logfile - class rather than directly.''' + class rather than directly.""" def __init__(self, log, marker): - '''Initialize a new object. + """Initialize a new object. Args: log: The Logfile object to log to. @@ -177,7 +177,7 @@ class SectionCtxMgr(object): Returns: Nothing. - ''' + """ self.log = log self.marker = marker @@ -189,18 +189,18 @@ class SectionCtxMgr(object): self.log.end_section(self.marker) class Logfile(object): - '''Generates an HTML-formatted log file containing multiple streams of - data, each represented in a well-delineated/-structured fashion.''' + """Generates an HTML-formatted log file containing multiple streams of + data, each represented in a well-delineated/-structured fashion.""" def __init__(self, fn): - '''Initialize a new object. + """Initialize a new object. Args: fn: The filename to write to. Returns: Nothing. - ''' + """ self.f = open(fn, "wt") self.last_stream = None @@ -217,7 +217,7 @@ class Logfile(object): """) def close(self): - '''Close the log file. + """Close the log file. After calling this function, no more data may be written to the log. @@ -226,7 +226,7 @@ class Logfile(object): Returns: Nothing. - ''' + """ self.f.write("""\ @@ -241,7 +241,7 @@ class Logfile(object): "".join(chr(c) for c in range(127, 256))) def _escape(self, data): - '''Render data format suitable for inclusion in an HTML document. + """Render data format suitable for inclusion in an HTML document. This includes HTML-escaping certain characters, and translating control characters to a hexadecimal representation. @@ -251,7 +251,7 @@ class Logfile(object): Returns: An escaped version of the data. - ''' + """ data = data.replace(chr(13), "") data = "".join((c in self._nonprint) and ("%%%02x" % ord(c)) or @@ -260,14 +260,14 @@ class Logfile(object): return data def _terminate_stream(self): - '''Write HTML to the log file to terminate the current stream's data. + """Write HTML to the log file to terminate the current stream's data. Args: None. Returns: Nothing. - ''' + """ self.cur_evt += 1 if not self.last_stream: @@ -280,7 +280,7 @@ class Logfile(object): self.last_stream = None def _note(self, note_type, msg): - '''Write a note or one-off message to the log file. + """Write a note or one-off message to the log file. Args: note_type: The type of note. This must be a value supported by the @@ -289,7 +289,7 @@ class Logfile(object): Returns: Nothing. - ''' + """ self._terminate_stream() self.f.write("
\n
")
@@ -297,14 +297,14 @@ class Logfile(object):
         self.f.write("\n
\n") def start_section(self, marker): - '''Begin a new nested section in the log file. + """Begin a new nested section in the log file. Args: marker: The name of the section that is starting. Returns: Nothing. - ''' + """ self._terminate_stream() self.blocks.append(marker) @@ -314,7 +314,7 @@ class Logfile(object): "\">Section: " + blk_path + "\n") def end_section(self, marker): - '''Terminate the current nested section in the log file. + """Terminate the current nested section in the log file. This function validates proper nesting of start_section() and end_section() calls. If a mismatch is found, an exception is raised. @@ -324,7 +324,7 @@ class Logfile(object): Returns: Nothing. - ''' + """ if (not self.blocks) or (marker != self.blocks[-1]): raise Exception("Block nesting mismatch: \"%s\" \"%s\"" % @@ -337,7 +337,7 @@ class Logfile(object): self.blocks.pop() def section(self, marker): - '''Create a temporary section in the log file. + """Create a temporary section in the log file. This function creates a context manager for Python's "with" statement, which allows a certain portion of test code to be logged to a separate @@ -352,96 +352,96 @@ class Logfile(object): Returns: A context manager object. - ''' + """ return SectionCtxMgr(self, marker) def error(self, msg): - '''Write an error note to the log file. + """Write an error note to the log file. Args: msg: A message describing the error. Returns: Nothing. - ''' + """ self._note("error", msg) def warning(self, msg): - '''Write an warning note to the log file. + """Write an warning note to the log file. Args: msg: A message describing the warning. Returns: Nothing. - ''' + """ self._note("warning", msg) def info(self, msg): - '''Write an informational note to the log file. + """Write an informational note to the log file. Args: msg: An informational message. Returns: Nothing. - ''' + """ self._note("info", msg) def action(self, msg): - '''Write an action note to the log file. + """Write an action note to the log file. Args: msg: A message describing the action that is being logged. Returns: Nothing. - ''' + """ self._note("action", msg) def status_pass(self, msg): - '''Write a note to the log file describing test(s) which passed. + """Write a note to the log file describing test(s) which passed. Args: msg: A message describing passed test(s). Returns: Nothing. - ''' + """ self._note("status-pass", msg) def status_skipped(self, msg): - '''Write a note to the log file describing skipped test(s). + """Write a note to the log file describing skipped test(s). Args: msg: A message describing passed test(s). Returns: Nothing. - ''' + """ self._note("status-skipped", msg) def status_fail(self, msg): - '''Write a note to the log file describing failed test(s). + """Write a note to the log file describing failed test(s). Args: msg: A message describing passed test(s). Returns: Nothing. - ''' + """ self._note("status-fail", msg) def get_stream(self, name, chained_file=None): - '''Create an object to log a single stream's data into the log file. + """Create an object to log a single stream's data into the log file. This creates a "file-like" object that can be written to in order to write a single stream's data to the log file. The implementation will @@ -456,12 +456,12 @@ class Logfile(object): Returns: A file-like object. - ''' + """ return LogfileStream(self, name, chained_file) def get_runner(self, name, chained_file=None): - '''Create an object that executes processes and logs their output. + """Create an object that executes processes and logs their output. Args: name: The name of this sub-process. @@ -470,12 +470,12 @@ class Logfile(object): Returns: A RunAndLog object. - ''' + """ return RunAndLog(self, name, chained_file) def write(self, stream, data, implicit=False): - '''Write stream data into the log file. + """Write stream data into the log file. This function should only be used by instances of LogfileStream or RunAndLog. @@ -491,7 +491,7 @@ class Logfile(object): Returns: Nothing. - ''' + """ if stream != self.last_stream: self._terminate_stream() @@ -507,13 +507,13 @@ class Logfile(object): self.last_stream = stream def flush(self): - '''Flush the log stream, to ensure correct log interleaving. + """Flush the log stream, to ensure correct log interleaving. Args: None. Returns: Nothing. - ''' + """ self.f.flush() diff --git a/test/py/tests/test_000_version.py b/test/py/tests/test_000_version.py index d262f0534e..43a02e74f7 100644 --- a/test/py/tests/test_000_version.py +++ b/test/py/tests/test_000_version.py @@ -9,7 +9,7 @@ # command prompt. def test_version(u_boot_console): - '''Test that the "version" command prints the U-Boot version.''' + """Test that the "version" command prints the U-Boot version.""" # "version" prints the U-Boot sign-on message. This is usually considered # an error, so that any unexpected reboot causes an error. Here, this diff --git a/test/py/tests/test_dfu.py b/test/py/tests/test_dfu.py index 6c1a363b02..0f69eef66b 100644 --- a/test/py/tests/test_dfu.py +++ b/test/py/tests/test_dfu.py @@ -12,7 +12,7 @@ import os.path import pytest import u_boot_utils -''' +""" Note: This test relies on: a) boardenv_* to contain configuration values to define which USB ports are @@ -44,7 +44,7 @@ ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="66 (You may wish to change the group ID instead of setting the permissions wide open. All that matters is that the user ID running the test can access the device.) -''' +""" # The set of file sizes to test. These values trigger various edge-cases such # as one less than, equal to, and one greater than typical USB max packet @@ -71,7 +71,7 @@ first_usb_dev_port = None @pytest.mark.buildconfigspec('cmd_dfu') def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): - '''Test the "dfu" command; the host system must be able to enumerate a USB + """Test the "dfu" command; the host system must be able to enumerate a USB device when "dfu" is running, various DFU transfers are tested, and the USB device must disappear when "dfu" is aborted. @@ -86,10 +86,10 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): Returns: Nothing. - ''' + """ def start_dfu(): - '''Start U-Boot's dfu shell command. + """Start U-Boot's dfu shell command. This also waits for the host-side USB enumeration process to complete. @@ -98,7 +98,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): Returns: Nothing. - ''' + """ fh = u_boot_utils.attempt_to_open_file( env__usb_dev_port['host_usb_dev_node']) @@ -120,7 +120,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): fh.close() def stop_dfu(ignore_errors): - '''Stop U-Boot's dfu shell command from executing. + """Stop U-Boot's dfu shell command from executing. This also waits for the host-side USB de-enumeration process to complete. @@ -133,7 +133,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): Returns: Nothing. - ''' + """ try: u_boot_console.log.action( @@ -148,7 +148,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): raise def run_dfu_util(alt_setting, fn, up_dn_load_arg): - '''Invoke dfu-util on the host. + """Invoke dfu-util on the host. Args: alt_setting: The DFU "alternate setting" identifier to interact @@ -159,7 +159,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): Returns: Nothing. - ''' + """ cmd = ['dfu-util', '-a', str(alt_setting), up_dn_load_arg, fn] if 'host_usb_port_path' in env__usb_dev_port: @@ -168,7 +168,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): u_boot_console.wait_for('Ctrl+C to exit ...') def dfu_write(alt_setting, fn): - '''Write a file to the target board using DFU. + """Write a file to the target board using DFU. Args: alt_setting: The DFU "alternate setting" identifier to interact @@ -177,12 +177,12 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): Returns: Nothing. - ''' + """ run_dfu_util(alt_setting, fn, '-D') def dfu_read(alt_setting, fn): - '''Read a file from the target board using DFU. + """Read a file from the target board using DFU. Args: alt_setting: The DFU "alternate setting" identifier to interact @@ -191,7 +191,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): Returns: Nothing. - ''' + """ # dfu-util fails reads/uploads if the host file already exists if os.path.exists(fn): @@ -199,7 +199,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): run_dfu_util(alt_setting, fn, '-U') def dfu_write_read_check(size): - '''Test DFU transfers of a specific size of data + """Test DFU transfers of a specific size of data This function first writes data to the board then reads it back and compares the written and read back data. Measures are taken to avoid @@ -210,7 +210,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): Returns: Nothing. - ''' + """ test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 'dfu_%d.bin' % size, size) diff --git a/test/py/tests/test_env.py b/test/py/tests/test_env.py index 557c3afe5c..651303f8cd 100644 --- a/test/py/tests/test_env.py +++ b/test/py/tests/test_env.py @@ -10,34 +10,34 @@ import pytest # FIXME: This might be useful for other tests; # perhaps refactor it into ConsoleBase or some other state object? class StateTestEnv(object): - '''Container that represents the state of all U-Boot environment variables. + """Container that represents the state of all U-Boot environment variables. This enables quick determination of existant/non-existant variable names. - ''' + """ def __init__(self, u_boot_console): - '''Initialize a new StateTestEnv object. + """Initialize a new StateTestEnv object. Args: u_boot_console: A U-Boot console. Returns: Nothing. - ''' + """ self.u_boot_console = u_boot_console self.get_env() self.set_var = self.get_non_existent_var() def get_env(self): - '''Read all current environment variables from U-Boot. + """Read all current environment variables from U-Boot. Args: None. Returns: Nothing. - ''' + """ response = self.u_boot_console.run_command('printenv') self.env = {} @@ -48,27 +48,27 @@ class StateTestEnv(object): self.env[var] = value def get_existent_var(self): - '''Return the name of an environment variable that exists. + """Return the name of an environment variable that exists. Args: None. Returns: The name of an environment variable. - ''' + """ for var in self.env: return var def get_non_existent_var(self): - '''Return the name of an environment variable that does not exist. + """Return the name of an environment variable that does not exist. Args: None. Returns: The name of an environment variable. - ''' + """ n = 0 while True: @@ -80,7 +80,7 @@ class StateTestEnv(object): ste = None @pytest.fixture(scope='function') def state_test_env(u_boot_console): - '''pytest fixture to provide a StateTestEnv object to tests.''' + """pytest fixture to provide a StateTestEnv object to tests.""" global ste if not ste: @@ -88,7 +88,7 @@ def state_test_env(u_boot_console): return ste def unset_var(state_test_env, var): - '''Unset an environment variable. + """Unset an environment variable. This both executes a U-Boot shell command and updates a StateTestEnv object. @@ -99,14 +99,14 @@ def unset_var(state_test_env, var): Returns: Nothing. - ''' + """ state_test_env.u_boot_console.run_command('setenv %s' % var) if var in state_test_env.env: del state_test_env.env[var] def set_var(state_test_env, var, value): - '''Set an environment variable. + """Set an environment variable. This both executes a U-Boot shell command and updates a StateTestEnv object. @@ -118,26 +118,26 @@ def set_var(state_test_env, var, value): Returns: Nothing. - ''' + """ state_test_env.u_boot_console.run_command('setenv %s "%s"' % (var, value)) state_test_env.env[var] = value def validate_empty(state_test_env, var): - '''Validate that a variable is not set, using U-Boot shell commands. + """Validate that a variable is not set, using U-Boot shell commands. Args: var: The variable name to test. Returns: Nothing. - ''' + """ response = state_test_env.u_boot_console.run_command('echo $%s' % var) assert response == '' def validate_set(state_test_env, var, value): - '''Validate that a variable is set, using U-Boot shell commands. + """Validate that a variable is set, using U-Boot shell commands. Args: var: The variable name to test. @@ -145,7 +145,7 @@ def validate_set(state_test_env, var, value): Returns: Nothing. - ''' + """ # echo does not preserve leading, internal, or trailing whitespace in the # value. printenv does, and hence allows more complete testing. @@ -153,20 +153,20 @@ def validate_set(state_test_env, var, value): assert response == ('%s=%s' % (var, value)) def test_env_echo_exists(state_test_env): - '''Test echoing a variable that exists.''' + """Test echoing a variable that exists.""" var = state_test_env.get_existent_var() value = state_test_env.env[var] validate_set(state_test_env, var, value) def test_env_echo_non_existent(state_test_env): - '''Test echoing a variable that doesn't exist.''' + """Test echoing a variable that doesn't exist.""" var = state_test_env.set_var validate_empty(state_test_env, var) def test_env_printenv_non_existent(state_test_env): - '''Test printenv error message for non-existant variables.''' + """Test printenv error message for non-existant variables.""" var = state_test_env.set_var c = state_test_env.u_boot_console @@ -175,14 +175,14 @@ def test_env_printenv_non_existent(state_test_env): assert(response == '## Error: "%s" not defined' % var) def test_env_unset_non_existent(state_test_env): - '''Test unsetting a nonexistent variable.''' + """Test unsetting a nonexistent variable.""" var = state_test_env.get_non_existent_var() unset_var(state_test_env, var) validate_empty(state_test_env, var) def test_env_set_non_existent(state_test_env): - '''Test set a non-existant variable.''' + """Test set a non-existant variable.""" var = state_test_env.set_var value = 'foo' @@ -190,7 +190,7 @@ def test_env_set_non_existent(state_test_env): validate_set(state_test_env, var, value) def test_env_set_existing(state_test_env): - '''Test setting an existant variable.''' + """Test setting an existant variable.""" var = state_test_env.set_var value = 'bar' @@ -198,14 +198,14 @@ def test_env_set_existing(state_test_env): validate_set(state_test_env, var, value) def test_env_unset_existing(state_test_env): - '''Test unsetting a variable.''' + """Test unsetting a variable.""" var = state_test_env.set_var unset_var(state_test_env, var) validate_empty(state_test_env, var) def test_env_expansion_spaces(state_test_env): - '''Test expanding a variable that contains a space in its value.''' + """Test expanding a variable that contains a space in its value.""" var_space = None var_test = None diff --git a/test/py/tests/test_help.py b/test/py/tests/test_help.py index 894f3b5f17..420090cf0d 100644 --- a/test/py/tests/test_help.py +++ b/test/py/tests/test_help.py @@ -4,6 +4,6 @@ # SPDX-License-Identifier: GPL-2.0 def test_help(u_boot_console): - '''Test that the "help" command can be executed.''' + """Test that the "help" command can be executed.""" u_boot_console.run_command('help') diff --git a/test/py/tests/test_hush_if_test.py b/test/py/tests/test_hush_if_test.py index cf4c3aeeb7..8b88425a65 100644 --- a/test/py/tests/test_hush_if_test.py +++ b/test/py/tests/test_hush_if_test.py @@ -95,7 +95,7 @@ subtests = ( ) def exec_hush_if(u_boot_console, expr, result): - '''Execute a shell "if" command, and validate its result.''' + """Execute a shell "if" command, and validate its result.""" cmd = 'if ' + expr + '; then echo true; else echo false; fi' response = u_boot_console.run_command(cmd) @@ -103,7 +103,7 @@ def exec_hush_if(u_boot_console, expr, result): @pytest.mark.buildconfigspec('sys_hush_parser') def test_hush_if_test_setup(u_boot_console): - '''Set up environment variables used during the "if" tests.''' + """Set up environment variables used during the "if" tests.""" u_boot_console.run_command('setenv ut_var_nonexistent') u_boot_console.run_command('setenv ut_var_exists 1') @@ -111,13 +111,13 @@ def test_hush_if_test_setup(u_boot_console): @pytest.mark.buildconfigspec('sys_hush_parser') @pytest.mark.parametrize('expr,result', subtests) def test_hush_if_test(u_boot_console, expr, result): - '''Test a single "if test" condition.''' + """Test a single "if test" condition.""" exec_hush_if(u_boot_console, expr, result) @pytest.mark.buildconfigspec('sys_hush_parser') def test_hush_if_test_teardown(u_boot_console): - '''Clean up environment variables used during the "if" tests.''' + """Clean up environment variables used during the "if" tests.""" u_boot_console.run_command('setenv ut_var_exists') @@ -126,7 +126,7 @@ def test_hush_if_test_teardown(u_boot_console): # Of those, only UMS currently allows file removal though. @pytest.mark.boardspec('sandbox') def test_hush_if_test_host_file_exists(u_boot_console): - '''Test the "if test -e" shell command.''' + """Test the "if test -e" shell command.""" test_file = u_boot_console.config.result_dir + \ '/creating_this_file_breaks_u_boot_tests' diff --git a/test/py/tests/test_md.py b/test/py/tests/test_md.py index 32cce4f15c..5fe25826b3 100644 --- a/test/py/tests/test_md.py +++ b/test/py/tests/test_md.py @@ -8,8 +8,8 @@ import u_boot_utils @pytest.mark.buildconfigspec('cmd_memory') def test_md(u_boot_console): - '''Test that md reads memory as expected, and that memory can be modified - using the mw command.''' + """Test that md reads memory as expected, and that memory can be modified + using the mw command.""" ram_base = u_boot_utils.find_ram_base(u_boot_console) addr = '%08x' % ram_base @@ -24,8 +24,8 @@ def test_md(u_boot_console): @pytest.mark.buildconfigspec('cmd_memory') def test_md_repeat(u_boot_console): - '''Test command repeat (via executing an empty command) operates correctly - for "md"; the command must repeat and dump an incrementing address.''' + """Test command repeat (via executing an empty command) operates correctly + for "md"; the command must repeat and dump an incrementing address.""" ram_base = u_boot_utils.find_ram_base(u_boot_console) addr_base = '%08x' % ram_base diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 9c46551e8c..68eedde7c8 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -8,7 +8,7 @@ import pytest import u_boot_utils -''' +""" Note: This test relies on boardenv_* containing configuration values to define which the network environment available for testing. Without this, this test will be automatically skipped. @@ -46,16 +46,16 @@ env__net_tftp_readable_file = { "size": 5058624, "crc32": "c2244b26", } -''' +""" net_set_up = False def test_net_pre_commands(u_boot_console): - '''Execute any commands required to enable network hardware. + """Execute any commands required to enable network hardware. These commands are provided by the boardenv_* file; see the comment at the beginning of this file. - ''' + """ init_usb = u_boot_console.config.env.get('env__net_uses_usb', False) if init_usb: @@ -67,11 +67,11 @@ def test_net_pre_commands(u_boot_console): @pytest.mark.buildconfigspec('cmd_dhcp') def test_net_dhcp(u_boot_console): - '''Test the dhcp command. + """Test the dhcp command. The boardenv_* file may be used to enable/disable this test; see the comment at the beginning of this file. - ''' + """ test_dhcp = u_boot_console.config.env.get('env__net_dhcp_server', False) if not test_dhcp: @@ -86,11 +86,11 @@ def test_net_dhcp(u_boot_console): @pytest.mark.buildconfigspec('net') def test_net_setup_static(u_boot_console): - '''Set up a static IP configuration. + """Set up a static IP configuration. The configuration is provided by the boardenv_* file; see the comment at the beginning of this file. - ''' + """ env_vars = u_boot_console.config.env.get('env__net_static_env_vars', None) if not env_vars: @@ -104,12 +104,12 @@ def test_net_setup_static(u_boot_console): @pytest.mark.buildconfigspec('cmd_ping') def test_net_ping(u_boot_console): - '''Test the ping command. + """Test the ping command. The $serverip (as set up by either test_net_dhcp or test_net_setup_static) is pinged. The test validates that the host is alive, as reported by the ping command's output. - ''' + """ if not net_set_up: pytest.skip("Network not initialized") @@ -119,14 +119,14 @@ def test_net_ping(u_boot_console): @pytest.mark.buildconfigspec('cmd_net') def test_net_tftpboot(u_boot_console): - '''Test the tftpboot command. + """Test the tftpboot command. A file is downloaded from the TFTP server, its size and optionally its CRC32 are validated. The details of the file to download are provided by the boardenv_* file; see the comment at the beginning of this file. - ''' + """ if not net_set_up: pytest.skip("Network not initialized") diff --git a/test/py/tests/test_sandbox_exit.py b/test/py/tests/test_sandbox_exit.py index 1ec3607eb2..d1aa3083f4 100644 --- a/test/py/tests/test_sandbox_exit.py +++ b/test/py/tests/test_sandbox_exit.py @@ -9,14 +9,14 @@ import signal @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('reset') def test_reset(u_boot_console): - '''Test that the "reset" command exits sandbox process.''' + """Test that the "reset" command exits sandbox process.""" u_boot_console.run_command('reset', wait_for_prompt=False) assert(u_boot_console.validate_exited()) @pytest.mark.boardspec('sandbox') def test_ctrl_c(u_boot_console): - '''Test that sending SIGINT to sandbox causes it to exit.''' + """Test that sending SIGINT to sandbox causes it to exit.""" u_boot_console.kill(signal.SIGINT) assert(u_boot_console.validate_exited()) diff --git a/test/py/tests/test_shell_basics.py b/test/py/tests/test_shell_basics.py index 719ce611d7..32e5f83015 100644 --- a/test/py/tests/test_shell_basics.py +++ b/test/py/tests/test_shell_basics.py @@ -5,13 +5,13 @@ # Test basic shell functionality, such as commands separate by semi-colons. def test_shell_execute(u_boot_console): - '''Test any shell command.''' + """Test any shell command.""" response = u_boot_console.run_command('echo hello') assert response.strip() == 'hello' def test_shell_semicolon_two(u_boot_console): - '''Test two shell commands separate by a semi-colon.''' + """Test two shell commands separate by a semi-colon.""" cmd = 'echo hello; echo world' response = u_boot_console.run_command(cmd) @@ -19,8 +19,8 @@ def test_shell_semicolon_two(u_boot_console): assert response.index('hello') < response.index('world') def test_shell_semicolon_three(u_boot_console): - '''Test three shell commands separate by a semi-colon, with variable - expansion dependencies between them.''' + """Test three shell commands separate by a semi-colon, with variable + expansion dependencies between them.""" cmd = 'setenv list 1; setenv list ${list}2; setenv list ${list}3; ' + \ 'echo ${list}' @@ -29,7 +29,7 @@ def test_shell_semicolon_three(u_boot_console): u_boot_console.run_command('setenv list') def test_shell_run(u_boot_console): - '''Test the "run" shell command.''' + """Test the "run" shell command.""" u_boot_console.run_command('setenv foo \"setenv monty 1; setenv python 2\"') u_boot_console.run_command('run foo') diff --git a/test/py/tests/test_sleep.py b/test/py/tests/test_sleep.py index 437b6bb9fe..74add891c3 100644 --- a/test/py/tests/test_sleep.py +++ b/test/py/tests/test_sleep.py @@ -6,8 +6,8 @@ import pytest import time def test_sleep(u_boot_console): - '''Test the sleep command, and validate that it sleeps for approximately - the correct amount of time.''' + """Test the sleep command, and validate that it sleeps for approximately + the correct amount of time.""" # 3s isn't too long, but is enough to cross a few second boundaries. sleep_time = 3 diff --git a/test/py/tests/test_ums.py b/test/py/tests/test_ums.py index f482cfeca1..21d40a9725 100644 --- a/test/py/tests/test_ums.py +++ b/test/py/tests/test_ums.py @@ -14,7 +14,7 @@ import re import time import u_boot_utils -''' +""" Note: This test relies on: a) boardenv_* to contain configuration values to define which USB ports are @@ -69,11 +69,11 @@ root permissions. For example: This entry is only needed if any block_devs above contain a writable_fs_partition value. -''' +""" @pytest.mark.buildconfigspec('cmd_usb_mass_storage') def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): - '''Test the "ums" command; the host system must be able to enumerate a UMS + """Test the "ums" command; the host system must be able to enumerate a UMS device when "ums" is running, block and optionally file I/O are tested, and this device must disappear when "ums" is aborted. @@ -88,7 +88,7 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): Returns: Nothing. - ''' + """ have_writable_fs_partition = 'writable_fs_partition' in env__block_devs[0] if not have_writable_fs_partition: @@ -120,7 +120,7 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): mounted_test_fn = mount_point + '/' + mount_subdir + test_f.fn def start_ums(): - '''Start U-Boot's ums shell command. + """Start U-Boot's ums shell command. This also waits for the host-side USB enumeration process to complete. @@ -129,7 +129,7 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): Returns: Nothing. - ''' + """ u_boot_console.log.action( 'Starting long-running U-Boot ums shell command') @@ -142,21 +142,21 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): fh.close() def mount(): - '''Mount the block device that U-Boot exports. + """Mount the block device that U-Boot exports. Args: None. Returns: Nothing. - ''' + """ u_boot_console.log.action('Mounting exported UMS device') cmd = ('/bin/mount', host_ums_part_node) u_boot_utils.run_and_log(u_boot_console, cmd) def umount(ignore_errors): - '''Unmount the block device that U-Boot exports. + """Unmount the block device that U-Boot exports. Args: ignore_errors: Ignore any errors. This is useful if an error has @@ -166,14 +166,14 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): Returns: Nothing. - ''' + """ u_boot_console.log.action('Unmounting UMS device') cmd = ('/bin/umount', host_ums_part_node) u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors) def stop_ums(ignore_errors): - '''Stop U-Boot's ums shell command from executing. + """Stop U-Boot's ums shell command from executing. This also waits for the host-side USB de-enumeration process to complete. @@ -186,7 +186,7 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): Returns: Nothing. - ''' + """ u_boot_console.log.action( 'Stopping long-running U-Boot ums shell command') diff --git a/test/py/tests/test_unknown_cmd.py b/test/py/tests/test_unknown_cmd.py index 2de93e0026..c27ab49d55 100644 --- a/test/py/tests/test_unknown_cmd.py +++ b/test/py/tests/test_unknown_cmd.py @@ -4,8 +4,8 @@ # SPDX-License-Identifier: GPL-2.0 def test_unknown_command(u_boot_console): - '''Test that executing an unknown command causes U-Boot to print an - error.''' + """Test that executing an unknown command causes U-Boot to print an + error.""" # The "unknown command" error is actively expected here, # so error detection for it is disabled. diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py index bb834b0d34..c500fb3ae5 100644 --- a/test/py/u_boot_console_base.py +++ b/test/py/u_boot_console_base.py @@ -24,12 +24,12 @@ pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'') pattern_error_notification = re.compile('## Error: ') class ConsoleDisableCheck(object): - '''Context manager (for Python's with statement) that temporarily disables + """Context manager (for Python's with statement) that temporarily disables the specified console output error check. This is useful when deliberately executing a command that is known to trigger one of the error checks, in order to test that the error condition is actually raised. This class is used internally by ConsoleBase::disable_check(); it is not intended for - direct usage.''' + direct usage.""" def __init__(self, console, check_type): self.console = console @@ -42,13 +42,13 @@ class ConsoleDisableCheck(object): self.console.disable_check_count[self.check_type] -= 1 class ConsoleBase(object): - '''The interface through which test functions interact with the U-Boot + """The interface through which test functions interact with the U-Boot console. This primarily involves executing shell commands, capturing their results, and checking for common error conditions. Some common utilities - are also provided too.''' + are also provided too.""" def __init__(self, log, config, max_fifo_fill): - '''Initialize a U-Boot console connection. + """Initialize a U-Boot console connection. Can only usefully be called by sub-classes. @@ -65,7 +65,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ self.log = log self.config = config @@ -88,7 +88,7 @@ class ConsoleBase(object): self.at_prompt_logevt = None def close(self): - '''Terminate the connection to the U-Boot console. + """Terminate the connection to the U-Boot console. This function is only useful once all interaction with U-Boot is complete. Once this function is called, data cannot be sent to or @@ -99,7 +99,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ if self.p: self.p.close() @@ -107,7 +107,7 @@ class ConsoleBase(object): def run_command(self, cmd, wait_for_echo=True, send_nl=True, wait_for_prompt=True): - '''Execute a command via the U-Boot console. + """Execute a command via the U-Boot console. The command is always sent to U-Boot. @@ -142,7 +142,7 @@ class ConsoleBase(object): The output from U-Boot during command execution. In other words, the text U-Boot emitted between the point it echod the command string and emitted the subsequent command prompts. - ''' + """ if self.at_prompt and \ self.at_prompt_logevt != self.logstream.logfile.cur_evt: @@ -198,7 +198,7 @@ class ConsoleBase(object): raise def ctrlc(self): - '''Send a CTRL-C character to U-Boot. + """Send a CTRL-C character to U-Boot. This is useful in order to stop execution of long-running synchronous commands such as "ums". @@ -208,13 +208,13 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ self.log.action('Sending Ctrl-C') self.run_command(chr(3), wait_for_echo=False, send_nl=False) def wait_for(self, text): - '''Wait for a pattern to be emitted by U-Boot. + """Wait for a pattern to be emitted by U-Boot. This is useful when a long-running command such as "dfu" is executing, and it periodically emits some text that should show up at a specific @@ -226,14 +226,14 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ if type(text) == type(''): text = re.escape(text) self.p.expect([text]) def drain_console(self): - '''Read from and log the U-Boot console for a short time. + """Read from and log the U-Boot console for a short time. U-Boot's console output is only logged when the test code actively waits for U-Boot to emit specific data. There are cases where tests @@ -248,7 +248,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ # If we are already not connected to U-Boot, there's nothing to drain. # This should only happen when a previous call to run_command() or @@ -270,7 +270,7 @@ class ConsoleBase(object): self.p.timeout = orig_timeout def ensure_spawned(self): - '''Ensure a connection to a correctly running U-Boot instance. + """Ensure a connection to a correctly running U-Boot instance. This may require spawning a new Sandbox process or resetting target hardware, as defined by the implementation sub-class. @@ -282,7 +282,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ if self.p: return @@ -320,7 +320,7 @@ class ConsoleBase(object): raise def cleanup_spawn(self): - '''Shut down all interaction with the U-Boot instance. + """Shut down all interaction with the U-Boot instance. This is used when an error is detected prior to re-establishing a connection with a fresh U-Boot instance. @@ -332,7 +332,7 @@ class ConsoleBase(object): Returns: Nothing. - ''' + """ try: if self.p: @@ -342,7 +342,7 @@ class ConsoleBase(object): self.p = None def validate_version_string_in_text(self, text): - '''Assert that a command's output includes the U-Boot signon message. + """Assert that a command's output includes the U-Boot signon message. This is primarily useful for validating the "version" command without duplicating the signon text regex in a test function. @@ -352,12 +352,12 @@ class ConsoleBase(object): Returns: Nothing. An exception is raised if the validation fails. - ''' + """ assert(self.u_boot_version_string in text) def disable_check(self, check_type): - '''Temporarily disable an error check of U-Boot's output. + """Temporarily disable an error check of U-Boot's output. Create a new context manager (for use with the "with" statement) which temporarily disables a particular console output error check. @@ -368,6 +368,6 @@ class ConsoleBase(object): Returns: A context manager object. - ''' + """ return ConsoleDisableCheck(self, check_type) diff --git a/test/py/u_boot_console_exec_attach.py b/test/py/u_boot_console_exec_attach.py index 0ca9e7c178..19520cb3b9 100644 --- a/test/py/u_boot_console_exec_attach.py +++ b/test/py/u_boot_console_exec_attach.py @@ -11,15 +11,15 @@ from u_boot_spawn import Spawn from u_boot_console_base import ConsoleBase class ConsoleExecAttach(ConsoleBase): - '''Represents a physical connection to a U-Boot console, typically via a + """Represents a physical connection to a U-Boot console, typically via a serial port. This implementation executes a sub-process to attach to the console, expecting that the stdin/out of the sub-process will be forwarded to/from the physical hardware. This approach isolates the test infra- structure from the user-/installation-specific details of how to - communicate with, and the identity of, serial ports etc.''' + communicate with, and the identity of, serial ports etc.""" def __init__(self, log, config): - '''Initialize a U-Boot console connection. + """Initialize a U-Boot console connection. Args: log: A multiplexed_log.Logfile instance. @@ -27,7 +27,7 @@ class ConsoleExecAttach(ConsoleBase): Returns: Nothing. - ''' + """ # The max_fifo_fill value might need tweaking per-board/-SoC? # 1 would be safe anywhere, but is very slow (a pexpect issue?). @@ -42,7 +42,7 @@ class ConsoleExecAttach(ConsoleBase): runner.close() def get_spawn(self): - '''Connect to a fresh U-Boot instance. + """Connect to a fresh U-Boot instance. The target board is reset, so that U-Boot begins running from scratch. @@ -51,7 +51,7 @@ class ConsoleExecAttach(ConsoleBase): Returns: A u_boot_spawn.Spawn object that is attached to U-Boot. - ''' + """ args = [self.config.board_type, self.config.board_identity] s = Spawn(['u-boot-test-console'] + args) diff --git a/test/py/u_boot_console_sandbox.py b/test/py/u_boot_console_sandbox.py index eb84150a1e..267f8b0650 100644 --- a/test/py/u_boot_console_sandbox.py +++ b/test/py/u_boot_console_sandbox.py @@ -10,11 +10,11 @@ from u_boot_spawn import Spawn from u_boot_console_base import ConsoleBase class ConsoleSandbox(ConsoleBase): - '''Represents a connection to a sandbox U-Boot console, executed as a sub- - process.''' + """Represents a connection to a sandbox U-Boot console, executed as a sub- + process.""" def __init__(self, log, config): - '''Initialize a U-Boot console connection. + """Initialize a U-Boot console connection. Args: log: A multiplexed_log.Logfile instance. @@ -22,12 +22,12 @@ class ConsoleSandbox(ConsoleBase): Returns: Nothing. - ''' + """ super(ConsoleSandbox, self).__init__(log, config, max_fifo_fill=1024) def get_spawn(self): - '''Connect to a fresh U-Boot instance. + """Connect to a fresh U-Boot instance. A new sandbox process is created, so that U-Boot begins running from scratch. @@ -37,25 +37,25 @@ class ConsoleSandbox(ConsoleBase): Returns: A u_boot_spawn.Spawn object that is attached to U-Boot. - ''' + """ return Spawn([self.config.build_dir + '/u-boot']) def kill(self, sig): - '''Send a specific Unix signal to the sandbox process. + """Send a specific Unix signal to the sandbox process. Args: sig: The Unix signal to send to the process. Returns: Nothing. - ''' + """ self.log.action('kill %d' % sig) self.p.kill(sig) def validate_exited(self): - '''Determine whether the sandbox process has exited. + """Determine whether the sandbox process has exited. If required, this function waits a reasonable time for the process to exit. @@ -65,7 +65,7 @@ class ConsoleSandbox(ConsoleBase): Returns: Boolean indicating whether the process has exited. - ''' + """ p = self.p self.p = None diff --git a/test/py/u_boot_spawn.py b/test/py/u_boot_spawn.py index df4c67597c..d508075990 100644 --- a/test/py/u_boot_spawn.py +++ b/test/py/u_boot_spawn.py @@ -12,23 +12,23 @@ import select import time class Timeout(Exception): - '''An exception sub-class that indicates that a timeout occurred.''' + """An exception sub-class that indicates that a timeout occurred.""" pass class Spawn(object): - '''Represents the stdio of a freshly created sub-process. Commands may be + """Represents the stdio of a freshly created sub-process. Commands may be sent to the process, and responses waited for. - ''' + """ def __init__(self, args): - '''Spawn (fork/exec) the sub-process. + """Spawn (fork/exec) the sub-process. Args: args: array of processs arguments. argv[0] is the command to execute. Returns: Nothing. - ''' + """ self.waited = False self.buf = '' @@ -56,26 +56,26 @@ class Spawn(object): self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL) def kill(self, sig): - '''Send unix signal "sig" to the child process. + """Send unix signal "sig" to the child process. Args: sig: The signal number to send. Returns: Nothing. - ''' + """ os.kill(self.pid, sig) def isalive(self): - '''Determine whether the child process is still running. + """Determine whether the child process is still running. Args: None. Returns: Boolean indicating whether process is alive. - ''' + """ if self.waited: return False @@ -88,19 +88,19 @@ class Spawn(object): return False def send(self, data): - '''Send data to the sub-process's stdin. + """Send data to the sub-process's stdin. Args: data: The data to send to the process. Returns: Nothing. - ''' + """ os.write(self.fd, data) def expect(self, patterns): - '''Wait for the sub-process to emit specific data. + """Wait for the sub-process to emit specific data. This function waits for the process to emit one pattern from the supplied list of patterns, or for a timeout to occur. @@ -116,7 +116,7 @@ class Spawn(object): Notable exceptions: Timeout, if the process did not emit any of the patterns within the expected time. - ''' + """ for pi in xrange(len(patterns)): if type(patterns[pi]) == type(''): @@ -161,7 +161,7 @@ class Spawn(object): self.logfile_read.flush() def close(self): - '''Close the stdio connection to the sub-process. + """Close the stdio connection to the sub-process. This also waits a reasonable time for the sub-process to stop running. @@ -170,7 +170,7 @@ class Spawn(object): Returns: Nothing. - ''' + """ os.close(self.fd) for i in xrange(100): diff --git a/test/py/u_boot_utils.py b/test/py/u_boot_utils.py index 522390a207..72d24e42aa 100644 --- a/test/py/u_boot_utils.py +++ b/test/py/u_boot_utils.py @@ -11,21 +11,21 @@ import sys import time def md5sum_data(data): - '''Calculate the MD5 hash of some data. + """Calculate the MD5 hash of some data. Args: data: The data to hash. Returns: The hash of the data, as a binary string. - ''' + """ h = hashlib.md5() h.update(data) return h.digest() def md5sum_file(fn, max_length=None): - '''Calculate the MD5 hash of the contents of a file. + """Calculate the MD5 hash of the contents of a file. Args: fn: The filename of the file to hash. @@ -35,7 +35,7 @@ def md5sum_file(fn, max_length=None): Returns: The hash of the file content, as a binary string. - ''' + """ with open(fn, 'rb') as fh: if max_length: @@ -46,11 +46,11 @@ def md5sum_file(fn, max_length=None): return md5sum_data(data) class PersistentRandomFile(object): - '''Generate and store information about a persistent file containing - random data.''' + """Generate and store information about a persistent file containing + random data.""" def __init__(self, u_boot_console, fn, size): - '''Create or process the persistent file. + """Create or process the persistent file. If the file does not exist, it is generated. @@ -66,7 +66,7 @@ class PersistentRandomFile(object): Returns: Nothing. - ''' + """ self.fn = fn @@ -85,7 +85,7 @@ class PersistentRandomFile(object): self.content_hash = md5sum_data(data) def attempt_to_open_file(fn): - '''Attempt to open a file, without throwing exceptions. + """Attempt to open a file, without throwing exceptions. Any errors (exceptions) that occur during the attempt to open the file are ignored. This is useful in order to test whether a file (in @@ -98,7 +98,7 @@ def attempt_to_open_file(fn): Returns: An open file handle to the file, or None if the file could not be opened. - ''' + """ try: return open(fn, 'rb') @@ -106,7 +106,7 @@ def attempt_to_open_file(fn): return None def wait_until_open_succeeds(fn): - '''Poll until a file can be opened, or a timeout occurs. + """Poll until a file can be opened, or a timeout occurs. Continually attempt to open a file, and return when this succeeds, or raise an exception after a timeout. @@ -116,7 +116,7 @@ def wait_until_open_succeeds(fn): Returns: An open file handle to the file. - ''' + """ for i in xrange(100): fh = attempt_to_open_file(fn) @@ -126,7 +126,7 @@ def wait_until_open_succeeds(fn): raise Exception('File could not be opened') def wait_until_file_open_fails(fn, ignore_errors): - '''Poll until a file cannot be opened, or a timeout occurs. + """Poll until a file cannot be opened, or a timeout occurs. Continually attempt to open a file, and return when this fails, or raise an exception after a timeout. @@ -139,7 +139,7 @@ def wait_until_file_open_fails(fn, ignore_errors): Returns: Nothing. - ''' + """ for i in xrange(100): fh = attempt_to_open_file(fn) @@ -152,7 +152,7 @@ def wait_until_file_open_fails(fn, ignore_errors): raise Exception('File can still be opened') def run_and_log(u_boot_console, cmd, ignore_errors=False): - '''Run a command and log its output. + """Run a command and log its output. Args: u_boot_console: A console connection to U-Boot. @@ -164,7 +164,7 @@ def run_and_log(u_boot_console, cmd, ignore_errors=False): Returns: Nothing. - ''' + """ runner = u_boot_console.log.get_runner(cmd[0], sys.stdout) runner.run(cmd, ignore_errors=ignore_errors) @@ -172,7 +172,7 @@ def run_and_log(u_boot_console, cmd, ignore_errors=False): ram_base = None def find_ram_base(u_boot_console): - '''Find the running U-Boot's RAM location. + """Find the running U-Boot's RAM location. Probe the running U-Boot to determine the address of the first bank of RAM. This is useful for tests that test reading/writing RAM, or @@ -186,7 +186,7 @@ def find_ram_base(u_boot_console): Returns: The address of U-Boot's first RAM bank, as an integer. - ''' + """ global ram_base if u_boot_console.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': From a2ec560647e90250183e379799db957970cb260e Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Tue, 26 Jan 2016 13:41:31 -0700 Subject: [PATCH 25/36] test/py: Quote consistency When converting test/py from " to ', I missed a few places (or added a few inconsistencies later). Fix these. Note that only quotes in code are converted; double-quotes in comments and HTML are left as-is, since English and HTML use " not '. Signed-off-by: Stephen Warren Reviewed-by: Simon Glass --- test/py/multiplexed_log.py | 68 +++++++++++++++--------------- test/py/test.py | 8 ++-- test/py/tests/test_dfu.py | 2 +- test/py/tests/test_net.py | 4 +- test/py/tests/test_shell_basics.py | 2 +- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/test/py/multiplexed_log.py b/test/py/multiplexed_log.py index c2a3b89536..fd3a9231a8 100644 --- a/test/py/multiplexed_log.py +++ b/test/py/multiplexed_log.py @@ -122,7 +122,7 @@ class RunAndLog(object): Nothing. """ - msg = "+" + " ".join(cmd) + "\n" + msg = '+' + ' '.join(cmd) + '\n' if self.chained_file: self.chained_file.write(msg) self.logfile.write(self, msg) @@ -202,19 +202,19 @@ class Logfile(object): Nothing. """ - self.f = open(fn, "wt") + self.f = open(fn, 'wt') self.last_stream = None self.blocks = [] self.cur_evt = 1 - shutil.copy(mod_dir + "/multiplexed_log.css", os.path.dirname(fn)) - self.f.write("""\ + shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn)) + self.f.write('''\ -""") +''') def close(self): """Close the log file. @@ -228,17 +228,17 @@ class Logfile(object): Nothing. """ - self.f.write("""\ + self.f.write('''\ -""") +''') self.f.close() # The set of characters that should be represented as hexadecimal codes in # the log file. - _nonprint = ("%" + "".join(chr(c) for c in range(0, 32) if c not in (9, 10)) + - "".join(chr(c) for c in range(127, 256))) + _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) + + ''.join(chr(c) for c in range(127, 256))) def _escape(self, data): """Render data format suitable for inclusion in an HTML document. @@ -253,8 +253,8 @@ class Logfile(object): An escaped version of the data. """ - data = data.replace(chr(13), "") - data = "".join((c in self._nonprint) and ("%%%02x" % ord(c)) or + data = data.replace(chr(13), '') + data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or c for c in data) data = cgi.escape(data) return data @@ -272,11 +272,11 @@ class Logfile(object): self.cur_evt += 1 if not self.last_stream: return - self.f.write("\n") - self.f.write("
End stream: " + - self.last_stream.name + "
\n") - self.f.write("\n") + self.f.write('\n') + self.f.write('
End stream: ' + + self.last_stream.name + '
\n') + self.f.write('\n') self.last_stream = None def _note(self, note_type, msg): @@ -292,9 +292,9 @@ class Logfile(object): """ self._terminate_stream() - self.f.write("
\n
")
+        self.f.write('
\n
')
         self.f.write(self._escape(msg))
-        self.f.write("\n
\n") + self.f.write('\n
\n') def start_section(self, marker): """Begin a new nested section in the log file. @@ -308,10 +308,10 @@ class Logfile(object): self._terminate_stream() self.blocks.append(marker) - blk_path = "/".join(self.blocks) - self.f.write("
\n") - self.f.write("
Section: " + blk_path + "
\n") + blk_path = '/'.join(self.blocks) + self.f.write('
\n') + self.f.write('
Section: ' + blk_path + '
\n') def end_section(self, marker): """Terminate the current nested section in the log file. @@ -327,13 +327,13 @@ class Logfile(object): """ if (not self.blocks) or (marker != self.blocks[-1]): - raise Exception("Block nesting mismatch: \"%s\" \"%s\"" % - (marker, "/".join(self.blocks))) + raise Exception('Block nesting mismatch: "%s" "%s"' % + (marker, '/'.join(self.blocks))) self._terminate_stream() - blk_path = "/".join(self.blocks) - self.f.write("
End section: " + blk_path + "
\n") - self.f.write("
\n") + blk_path = '/'.join(self.blocks) + self.f.write('
End section: ' + blk_path + '
\n') + self.f.write('
\n') self.blocks.pop() def section(self, marker): @@ -495,15 +495,15 @@ class Logfile(object): if stream != self.last_stream: self._terminate_stream() - self.f.write("
\n" % stream.name) - self.f.write("
Stream: " + stream.name + "
\n") - self.f.write("
")
+            self.f.write('
\n' % stream.name) + self.f.write('
Stream: ' + stream.name + '
\n') + self.f.write('
')
         if implicit:
-            self.f.write("")
+            self.f.write('')
         self.f.write(self._escape(data))
         if implicit:
-            self.f.write("")
+            self.f.write('')
         self.last_stream = stream
 
     def flush(self):
diff --git a/test/py/test.py b/test/py/test.py
index 9c23898774..95671d4737 100755
--- a/test/py/test.py
+++ b/test/py/test.py
@@ -16,17 +16,17 @@ import sys
 sys.argv.pop(0)
 
 # argv; py.test test_directory_name user-supplied-arguments
-args = ["py.test", os.path.dirname(__file__) + "/tests"]
+args = ['py.test', os.path.dirname(__file__) + '/tests']
 args.extend(sys.argv)
 
 try:
-    os.execvp("py.test", args)
+    os.execvp('py.test', args)
 except:
     # Log full details of any exception for detailed analysis
     import traceback
     traceback.print_exc()
     # Hint to the user that they likely simply haven't installed the required
     # dependencies.
-    print >>sys.stderr, """
+    print >>sys.stderr, '''
 exec(py.test) failed; perhaps you are missing some dependencies?
-See test/py/README.md for the list."""
+See test/py/README.md for the list.'''
diff --git a/test/py/tests/test_dfu.py b/test/py/tests/test_dfu.py
index 0f69eef66b..c09b90278d 100644
--- a/test/py/tests/test_dfu.py
+++ b/test/py/tests/test_dfu.py
@@ -258,7 +258,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config):
         dfu_write(0, dummy_f.abs_fn)
 
         for size in sizes:
-            with u_boot_console.log.section("Data size %d" % size):
+            with u_boot_console.log.section('Data size %d' % size):
                 dfu_write_read_check(size)
                 # Make the status of each sub-test obvious. If the test didn't
                 # pass, an exception was thrown so this code isn't executed.
diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py
index 68eedde7c8..07393eb1fd 100644
--- a/test/py/tests/test_net.py
+++ b/test/py/tests/test_net.py
@@ -112,7 +112,7 @@ def test_net_ping(u_boot_console):
     """
 
     if not net_set_up:
-        pytest.skip("Network not initialized")
+        pytest.skip('Network not initialized')
 
     output = u_boot_console.run_command('ping $serverip')
     assert 'is alive' in output
@@ -129,7 +129,7 @@ def test_net_tftpboot(u_boot_console):
     """
 
     if not net_set_up:
-        pytest.skip("Network not initialized")
+        pytest.skip('Network not initialized')
 
     f = u_boot_console.config.env.get('env__net_tftp_readable_file', None)
     if not f:
diff --git a/test/py/tests/test_shell_basics.py b/test/py/tests/test_shell_basics.py
index 32e5f83015..702e5e27e0 100644
--- a/test/py/tests/test_shell_basics.py
+++ b/test/py/tests/test_shell_basics.py
@@ -31,7 +31,7 @@ def test_shell_semicolon_three(u_boot_console):
 def test_shell_run(u_boot_console):
     """Test the "run" shell command."""
 
-    u_boot_console.run_command('setenv foo \"setenv monty 1; setenv python 2\"')
+    u_boot_console.run_command('setenv foo "setenv monty 1; setenv python 2"')
     u_boot_console.run_command('run foo')
     response = u_boot_console.run_command('echo $monty')
     assert response.strip() == '1'

From d20e5e976f70bd2e230787091a88278dfe6e5192 Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Tue, 26 Jan 2016 15:26:04 -0700
Subject: [PATCH 26/36] test/py: Provide custom IDs when parametrizing tests

When pytest generates the name for parametrized tests, simple parameter
values (ints, strings) get used directly, but more complex values such
as dicts are not handled. This yields test names such as:

    dfu[env__usb_dev_port0-env__dfu_config0]
    dfu[env__usb_dev_port0-env__dfu_config1]

Add some code to extract a custom fixture ID from the fixture values, so
that we end up with meaningful names such as:

    dfu[micro_b-emmc]
    dfu[devport2-ram]

If the boardenv file doesn't define custom names, the code falls back to
the old algorithm.

Signed-off-by: Stephen Warren 
Reviewed-by: Simon Glass 
---
 test/py/conftest.py       | 8 +++++++-
 test/py/tests/test_dfu.py | 3 +++
 test/py/tests/test_ums.py | 3 +++
 3 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/test/py/conftest.py b/test/py/conftest.py
index a4e54c66ce..9c9426aebe 100644
--- a/test/py/conftest.py
+++ b/test/py/conftest.py
@@ -225,7 +225,13 @@ def pytest_generate_tests(metafunc):
             # ... otherwise, see if there's a key that contains a list of
             # values to use instead.
             vals = subconfig.get(fn + 's', [])
-        metafunc.parametrize(fn, vals)
+        def fixture_id(index, val):
+            try:
+                return val["fixture_id"]
+            except:
+                return fn + str(index)
+        ids = [fixture_id(index, val) for (index, val) in enumerate(vals)]
+        metafunc.parametrize(fn, vals, ids=ids)
 
 @pytest.fixture(scope='function')
 def u_boot_console(request):
diff --git a/test/py/tests/test_dfu.py b/test/py/tests/test_dfu.py
index c09b90278d..bb70008af3 100644
--- a/test/py/tests/test_dfu.py
+++ b/test/py/tests/test_dfu.py
@@ -21,6 +21,7 @@ For example:
 
 env__usb_dev_ports = (
     {
+        "fixture_id": "micro_b",
         "tgt_usb_ctlr": "0",
         "host_usb_dev_node": "/dev/usbdev-p2371-2180",
         # This parameter is optional /if/ you only have a single board
@@ -32,10 +33,12 @@ env__usb_dev_ports = (
 env__dfu_configs = (
     # eMMC, partition 1
     {
+        "fixture_id": "emmc",
         "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1",
         "cmd_params": "mmc 0",
     },
 )
+
 b) udev rules to set permissions on devices nodes, so that sudo is not
 required. For example:
 
diff --git a/test/py/tests/test_ums.py b/test/py/tests/test_ums.py
index 21d40a9725..8c3ee2b053 100644
--- a/test/py/tests/test_ums.py
+++ b/test/py/tests/test_ums.py
@@ -29,6 +29,7 @@ env__mount_points = (
 
 env__usb_dev_ports = (
     {
+        "fixture_id": "micro_b",
         "tgt_usb_ctlr": "0",
         "host_ums_dev_node": "/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0",
     },
@@ -37,6 +38,7 @@ env__usb_dev_ports = (
 env__block_devs = (
     # eMMC; always present
     {
+        "fixture_id": "emmc",
         "type": "mmc",
         "id": "0",
         # The following two properties are optional.
@@ -48,6 +50,7 @@ env__block_devs = (
     },
     # SD card; present since I plugged one in
     {
+        "fixture_id": "sd",
         "type": "mmc",
         "id": "1"
     },

From 26e1beccbeb82ce7a4713ad899eb34b795228891 Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Wed, 27 Jan 2016 23:57:46 -0700
Subject: [PATCH 27/36] test/dm: clear unit test failure count each run

The ut command prints a test failure count each time it is executed.
This is stored in a global variable which is never reset. Consequently,
the printed failure count accumulates across runs. Fix this by clearing
the counter each time "ut" is invoked.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/dm/test-main.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/dm/test-main.c b/test/dm/test-main.c
index 91bdda83ab..f2e0048143 100644
--- a/test/dm/test-main.c
+++ b/test/dm/test-main.c
@@ -81,6 +81,8 @@ static int dm_test_main(const char *test_name)
 	struct unit_test *test;
 	int run_count;
 
+	uts->fail_count = 0;
+
 	/*
 	 * If we have no device tree, or it only has a root node, then these
 	 * tests clearly aren't going to work...

From 44ac762b1437b274daf9e6f2dc924088b6332a54 Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Wed, 27 Jan 2016 23:57:47 -0700
Subject: [PATCH 28/36] test/py: fix spawn.expect multiple match handling

Multiple patterns may be passed to spawn.expect(). The pattern which
matches at the earliest position should be designated as the match. This
aspect works correctly. When multiple patterns match at the same position,
priority should be given the the earliest entry in the list of patterns.
This aspect does not work correctly. This patch fixes it.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/py/u_boot_spawn.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/py/u_boot_spawn.py b/test/py/u_boot_spawn.py
index d508075990..7451455671 100644
--- a/test/py/u_boot_spawn.py
+++ b/test/py/u_boot_spawn.py
@@ -132,7 +132,7 @@ class Spawn(object):
                     m = pattern.search(self.buf)
                     if not m:
                         continue
-                    if earliest_m and m.start() > earliest_m.start():
+                    if earliest_m and m.start() >= earliest_m.start():
                         continue
                     earliest_m = m
                     earliest_pi = pi

From e4119ebb6dabf9bc84914b648f81c2f1b8fe76e1 Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Wed, 27 Jan 2016 23:57:48 -0700
Subject: [PATCH 29/36] test.py: calculate bad patterns on change only

A future patch will use the bad_patterns array in multiple places. Rather
than duplicating the code to calculate it, or even sharing it in a
function and simply calling it redundantly when nothing has changed, only
re-calculate the list when some change is made to it. This reduces work.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/py/u_boot_console_base.py | 49 +++++++++++++++++-----------------
 1 file changed, 25 insertions(+), 24 deletions(-)

diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py
index c500fb3ae5..efb06cad0a 100644
--- a/test/py/u_boot_console_base.py
+++ b/test/py/u_boot_console_base.py
@@ -23,6 +23,17 @@ pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
 pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
 pattern_error_notification = re.compile('## Error: ')
 
+PAT_ID = 0
+PAT_RE = 1
+
+bad_pattern_defs = (
+    ('spl_signon', pattern_u_boot_spl_signon),
+    ('main_signon', pattern_u_boot_main_signon),
+    ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
+    ('unknown_command', pattern_unknown_command),
+    ('error_notification', pattern_error_notification),
+)
+
 class ConsoleDisableCheck(object):
     """Context manager (for Python's with statement) that temporarily disables
     the specified console output error check. This is useful when deliberately
@@ -37,9 +48,11 @@ class ConsoleDisableCheck(object):
 
     def __enter__(self):
         self.console.disable_check_count[self.check_type] += 1
+        self.console.eval_bad_patterns()
 
     def __exit__(self, extype, value, traceback):
         self.console.disable_check_count[self.check_type] -= 1
+        self.console.eval_bad_patterns()
 
 class ConsoleBase(object):
     """The interface through which test functions interact with the U-Boot
@@ -77,16 +90,18 @@ class ConsoleBase(object):
         self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
         self.prompt_escaped = re.escape(self.prompt)
         self.p = None
-        self.disable_check_count = {
-            'spl_signon': 0,
-            'main_signon': 0,
-            'unknown_command': 0,
-            'error_notification': 0,
-        }
+        self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
+        self.eval_bad_patterns()
 
         self.at_prompt = False
         self.at_prompt_logevt = None
 
+    def eval_bad_patterns(self):
+        self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
+            if self.disable_check_count[pat[PAT_ID]] == 0]
+        self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
+            if self.disable_check_count[pat[PAT_ID]] == 0]
+
     def close(self):
         """Terminate the connection to the U-Boot console.
 
@@ -148,20 +163,6 @@ class ConsoleBase(object):
                 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
             self.logstream.write(self.prompt, implicit=True)
 
-        bad_patterns = []
-        bad_pattern_ids = []
-        if (self.disable_check_count['spl_signon'] == 0):
-            bad_patterns.append(pattern_u_boot_spl_signon)
-            bad_pattern_ids.append('SPL signon')
-        if self.disable_check_count['main_signon'] == 0:
-            bad_patterns.append(pattern_u_boot_main_signon)
-            bad_pattern_ids.append('U-Boot main signon')
-        if self.disable_check_count['unknown_command'] == 0:
-            bad_patterns.append(pattern_unknown_command)
-            bad_pattern_ids.append('Unknown command')
-        if self.disable_check_count['error_notification'] == 0:
-            bad_patterns.append(pattern_error_notification)
-            bad_pattern_ids.append('Error notification')
         try:
             self.at_prompt = False
             if send_nl:
@@ -175,18 +176,18 @@ class ConsoleBase(object):
                     continue
                 chunk = re.escape(chunk)
                 chunk = chunk.replace('\\\n', '[\r\n]')
-                m = self.p.expect([chunk] + bad_patterns)
+                m = self.p.expect([chunk] + self.bad_patterns)
                 if m != 0:
                     self.at_prompt = False
                     raise Exception('Bad pattern found on console: ' +
-                                    bad_pattern_ids[m - 1])
+                                    self.bad_pattern_ids[m - 1])
             if not wait_for_prompt:
                 return
-            m = self.p.expect([self.prompt_escaped] + bad_patterns)
+            m = self.p.expect([self.prompt_escaped] + self.bad_patterns)
             if m != 0:
                 self.at_prompt = False
                 raise Exception('Bad pattern found on console: ' +
-                                bad_pattern_ids[m - 1])
+                                self.bad_pattern_ids[m - 1])
             self.at_prompt = True
             self.at_prompt_logevt = self.logstream.logfile.cur_evt
             # Only strip \r\n; space/TAB might be significant if testing

From 0c6189b5d60a2b0fcec65f3513bad7eee289da3f Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Wed, 27 Jan 2016 23:57:49 -0700
Subject: [PATCH 30/36] test/py: check for bad patterns everywhere we wait

Currently, bad patterns are only honored when executing a shell command.
Other cases, such as the initial boot-up of U-Boot or when interacting
with command output rather than gathering all output prior to the shell
prompt, do not currently look for bad patterns in console output. This
patch makes sure that bad patterns are honored everywhere.

One benefit of this change is that if U-Boot sandbox fails to start up,
the error message it emits can be caught immediately, rather than relying
on a (long) timeout when waiting for the expected signon message and/or
command prompt.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/py/u_boot_console_base.py | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py
index efb06cad0a..71a00e8633 100644
--- a/test/py/u_boot_console_base.py
+++ b/test/py/u_boot_console_base.py
@@ -231,7 +231,10 @@ class ConsoleBase(object):
 
         if type(text) == type(''):
             text = re.escape(text)
-        self.p.expect([text])
+        m = self.p.expect([text] + self.bad_patterns)
+        if m != 0:
+            raise Exception('Bad pattern found on console: ' +
+                            self.bad_pattern_ids[m - 1])
 
     def drain_console(self):
         """Read from and log the U-Boot console for a short time.
@@ -298,8 +301,14 @@ class ConsoleBase(object):
             self.p.timeout = 30000
             self.p.logfile_read = self.logstream
             if self.config.buildconfig.get('CONFIG_SPL', False) == 'y':
-                self.p.expect([pattern_u_boot_spl_signon])
-            self.p.expect([pattern_u_boot_main_signon])
+                m = self.p.expect([pattern_u_boot_spl_signon] + self.bad_patterns)
+                if m != 0:
+                    raise Exception('Bad pattern found on console: ' +
+                                    self.bad_pattern_ids[m - 1])
+            m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
+            if m != 0:
+                raise Exception('Bad pattern found on console: ' +
+                                self.bad_pattern_ids[m - 1])
             signon = self.p.after
             build_idx = signon.find(', Build:')
             if build_idx == -1:
@@ -307,12 +316,15 @@ class ConsoleBase(object):
             else:
                 self.u_boot_version_string = signon[:build_idx]
             while True:
-                match = self.p.expect([self.prompt_escaped,
-                                       pattern_stop_autoboot_prompt])
-                if match == 1:
+                m = self.p.expect([self.prompt_escaped,
+                    pattern_stop_autoboot_prompt] + self.bad_patterns)
+                if m == 0:
+                    break
+                if m == 1:
                     self.p.send(chr(3)) # CTRL-C
                     continue
-                break
+                raise Exception('Bad pattern found on console: ' +
+                                self.bad_pattern_ids[m - 2])
             self.at_prompt = True
             self.at_prompt_logevt = self.logstream.logfile.cur_evt
         except Exception as ex:

From 9129d9f5fd73a8bcdca7489b3ed2418a8b1416e2 Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Wed, 27 Jan 2016 23:57:50 -0700
Subject: [PATCH 31/36] test/py: detect another "bad pattern" in console output

Many error situations in U-Boot print the message:
    ### ERROR ### Please RESET the board ###

Add this to the list of bad patterns the test system detects. One
practical advantage of this change is to detect the case where sandbox
is told to use a particular DTB file, and the file cannot be opened.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/py/u_boot_console_base.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py
index 71a00e8633..392f8cb885 100644
--- a/test/py/u_boot_console_base.py
+++ b/test/py/u_boot_console_base.py
@@ -22,6 +22,7 @@ pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}-[^\r\n]*)')
 pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
 pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
 pattern_error_notification = re.compile('## Error: ')
+pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
 
 PAT_ID = 0
 PAT_RE = 1
@@ -32,6 +33,7 @@ bad_pattern_defs = (
     ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
     ('unknown_command', pattern_unknown_command),
     ('error_notification', pattern_error_notification),
+    ('error_please_reset', pattern_error_please_reset),
 )
 
 class ConsoleDisableCheck(object):

From 78b39cc3e19e698c04c2417ed5f79e324c90595e Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Wed, 27 Jan 2016 23:57:51 -0700
Subject: [PATCH 32/36] test/py: correctly log xfail/xpass tests

Tests can complete in passed, skipped, xpass, xfailed, or failed, states.
Currently the U-Boot log generation code doesn't handle the xfailed or
xpass states since they aren't used. Add support for the remaining states.
Without this, tests that xfail end up being reported as skipped.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/py/conftest.py         | 58 ++++++++++++++++++++++++-------------
 test/py/multiplexed_log.css |  8 +++++
 test/py/multiplexed_log.py  | 30 +++++++++++++++++--
 3 files changed, 73 insertions(+), 23 deletions(-)

diff --git a/test/py/conftest.py b/test/py/conftest.py
index 9c9426aebe..3e162cafcc 100644
--- a/test/py/conftest.py
+++ b/test/py/conftest.py
@@ -249,6 +249,8 @@ def u_boot_console(request):
 
 tests_not_run = set()
 tests_failed = set()
+tests_xpassed = set()
+tests_xfailed = set()
 tests_skipped = set()
 tests_passed = set()
 
@@ -289,6 +291,14 @@ def cleanup():
             log.status_skipped('%d skipped' % len(tests_skipped))
             for test in tests_skipped:
                 log.status_skipped('... ' + test)
+        if tests_xpassed:
+            log.status_xpass('%d xpass' % len(tests_xpassed))
+            for test in tests_xpassed:
+                log.status_xpass('... ' + test)
+        if tests_xfailed:
+            log.status_xfail('%d xfail' % len(tests_xfailed))
+            for test in tests_xfailed:
+                log.status_xfail('... ' + test)
         if tests_failed:
             log.status_fail('%d failed' % len(tests_failed))
             for test in tests_failed:
@@ -381,34 +391,42 @@ def pytest_runtest_protocol(item, nextitem):
     """
 
     reports = runtestprotocol(item, nextitem=nextitem)
-    failed = None
-    skipped = None
+
+    failure_cleanup = False
+    test_list = tests_passed
+    msg = 'OK'
+    msg_log = log.status_pass
     for report in reports:
         if report.outcome == 'failed':
-            failed = report
+            if hasattr(report, 'wasxfail'):
+                test_list = tests_xpassed
+                msg = 'XPASSED'
+                msg_log = log.status_xpass
+            else:
+                failure_cleanup = True
+                test_list = tests_failed
+                msg = 'FAILED:\n' + str(report.longrepr)
+                msg_log = log.status_fail
             break
         if report.outcome == 'skipped':
-            if not skipped:
-                skipped = report
+            if hasattr(report, 'wasxfail'):
+                failure_cleanup = True
+                test_list = tests_xfailed
+                msg = 'XFAILED:\n' + str(report.longrepr)
+                msg_log = log.status_xfail
+                break
+            test_list = tests_skipped
+            msg = 'SKIPPED:\n' + str(report.longrepr)
+            msg_log = log.status_skipped
 
-    if failed:
+    if failure_cleanup:
         console.drain_console()
-        tests_failed.add(item.name)
-    elif skipped:
-        tests_skipped.add(item.name)
-    else:
-        tests_passed.add(item.name)
+
+    test_list.add(item.name)
     tests_not_run.remove(item.name)
 
     try:
-        if failed:
-            msg = 'FAILED:\n' + str(failed.longrepr)
-            log.status_fail(msg)
-        elif skipped:
-            msg = 'SKIPPED:\n' + str(skipped.longrepr)
-            log.status_skipped(msg)
-        else:
-            log.status_pass('OK')
+        msg_log(msg)
     except:
         # If something went wrong with logging, it's better to let the test
         # process continue, which may report other exceptions that triggered
@@ -424,7 +442,7 @@ def pytest_runtest_protocol(item, nextitem):
 
     log.end_section(item.name)
 
-    if failed:
+    if failure_cleanup:
         console.cleanup_spawn()
 
     return reports
diff --git a/test/py/multiplexed_log.css b/test/py/multiplexed_log.css
index 50f7b90929..f6240d52da 100644
--- a/test/py/multiplexed_log.css
+++ b/test/py/multiplexed_log.css
@@ -83,6 +83,14 @@ pre {
     color: #ffff00
 }
 
+.status-xfail {
+    color: #ff7f00
+}
+
+.status-xpass {
+    color: #ff7f00
+}
+
 .status-fail {
     color: #ff0000
 }
diff --git a/test/py/multiplexed_log.py b/test/py/multiplexed_log.py
index fd3a9231a8..69a577e577 100644
--- a/test/py/multiplexed_log.py
+++ b/test/py/multiplexed_log.py
@@ -408,7 +408,7 @@ class Logfile(object):
         """Write a note to the log file describing test(s) which passed.
 
         Args:
-            msg: A message describing passed test(s).
+            msg: A message describing the passed test(s).
 
         Returns:
             Nothing.
@@ -420,7 +420,7 @@ class Logfile(object):
         """Write a note to the log file describing skipped test(s).
 
         Args:
-            msg: A message describing passed test(s).
+            msg: A message describing the skipped test(s).
 
         Returns:
             Nothing.
@@ -428,11 +428,35 @@ class Logfile(object):
 
         self._note("status-skipped", msg)
 
+    def status_xfail(self, msg):
+        """Write a note to the log file describing xfailed test(s).
+
+        Args:
+            msg: A message describing the xfailed test(s).
+
+        Returns:
+            Nothing.
+        """
+
+        self._note("status-xfail", msg)
+
+    def status_xpass(self, msg):
+        """Write a note to the log file describing xpassed test(s).
+
+        Args:
+            msg: A message describing the xpassed test(s).
+
+        Returns:
+            Nothing.
+        """
+
+        self._note("status-xpass", msg)
+
     def status_fail(self, msg):
         """Write a note to the log file describing failed test(s).
 
         Args:
-            msg: A message describing passed test(s).
+            msg: A message describing the failed test(s).
 
         Returns:
             Nothing.

From 77bcb22d77d1b1975c166755b012e0c0c1abb50a Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Wed, 27 Jan 2016 23:57:52 -0700
Subject: [PATCH 33/36] test/py: pass test DTB to sandbox

This is required for at least "ut dm" to operate correctly.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/py/u_boot_console_sandbox.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/test/py/u_boot_console_sandbox.py b/test/py/u_boot_console_sandbox.py
index 267f8b0650..bbf41e788b 100644
--- a/test/py/u_boot_console_sandbox.py
+++ b/test/py/u_boot_console_sandbox.py
@@ -39,7 +39,12 @@ class ConsoleSandbox(ConsoleBase):
             A u_boot_spawn.Spawn object that is attached to U-Boot.
         """
 
-        return Spawn([self.config.build_dir + '/u-boot'])
+        cmd = [
+            self.config.build_dir + '/u-boot',
+            '-d',
+            self.config.build_dir + '/arch/sandbox/dts/test.dtb'
+        ]
+        return Spawn(cmd)
 
     def kill(self, sig):
         """Send a specific Unix signal to the sandbox process.

From d27f2fc1e19c70d529932cf5725259dded19d5fd Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Wed, 27 Jan 2016 23:57:53 -0700
Subject: [PATCH 34/36] test/py: run sandbox in source directory

Some unit tests expect the cwd of the sandbox process to be the root
of the source tree. Ensure that requirement is met.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/py/u_boot_console_sandbox.py | 2 +-
 test/py/u_boot_spawn.py           | 8 ++++++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/test/py/u_boot_console_sandbox.py b/test/py/u_boot_console_sandbox.py
index bbf41e788b..a7263f30b8 100644
--- a/test/py/u_boot_console_sandbox.py
+++ b/test/py/u_boot_console_sandbox.py
@@ -44,7 +44,7 @@ class ConsoleSandbox(ConsoleBase):
             '-d',
             self.config.build_dir + '/arch/sandbox/dts/test.dtb'
         ]
-        return Spawn(cmd)
+        return Spawn(cmd, cwd=self.config.source_dir)
 
     def kill(self, sig):
         """Send a specific Unix signal to the sandbox process.
diff --git a/test/py/u_boot_spawn.py b/test/py/u_boot_spawn.py
index 7451455671..0f52d3e945 100644
--- a/test/py/u_boot_spawn.py
+++ b/test/py/u_boot_spawn.py
@@ -20,11 +20,13 @@ class Spawn(object):
     sent to the process, and responses waited for.
     """
 
-    def __init__(self, args):
+    def __init__(self, args, cwd=None):
         """Spawn (fork/exec) the sub-process.
 
         Args:
-            args: array of processs arguments. argv[0] is the command to execute.
+            args: array of processs arguments. argv[0] is the command to
+              execute.
+            cwd: the directory to run the process in, or None for no change.
 
         Returns:
             Nothing.
@@ -44,6 +46,8 @@ class Spawn(object):
                 # run under "go" (www.go.cd). Perhaps this happens under any
                 # background (non-interactive) system?
                 signal.signal(signal.SIGHUP, signal.SIG_DFL)
+                if cwd:
+                    os.chdir(cwd)
                 os.execvp(args[0], args)
             except:
                 print 'CHILD EXECEPTION:'

From db261f0076109b79c813ff27b9ce30e2e9d05221 Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Thu, 28 Jan 2016 10:18:03 -0700
Subject: [PATCH 35/36] test/py: fix a couple typos in comments

s/updata/update/.

Signed-off-by: Stephen Warren 
Reviewed-by: Simon Glass 
---
 test/py/tests/test_env.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/py/tests/test_env.py b/test/py/tests/test_env.py
index 651303f8cd..c41aa5a9d9 100644
--- a/test/py/tests/test_env.py
+++ b/test/py/tests/test_env.py
@@ -94,7 +94,7 @@ def unset_var(state_test_env, var):
     object.
 
     Args:
-        state_test_env: The StateTestEnv object to updata.
+        state_test_env: The StateTestEnv object to update.
         var: The variable name to unset.
 
     Returns:
@@ -112,7 +112,7 @@ def set_var(state_test_env, var, value):
     object.
 
     Args:
-        state_test_env: The StateTestEnv object to updata.
+        state_test_env: The StateTestEnv object to update.
         var: The variable name to set.
         value: The value to set the variable to.
 

From 26db3a617b38cc1bed1ce100381d2c4ccbb55e42 Mon Sep 17 00:00:00 2001
From: Stephen Warren 
Date: Thu, 28 Jan 2016 13:14:16 -0700
Subject: [PATCH 36/36] test/py: dfu: allow boardenv to specify test sizes

Allow the env__dfu_configs boardenv data to specify the set of DFU
transfer sizes to test. Manually specifying test sizes is useful if you
wish to test multiple DFU configurations (e.g. SD card ext4 filesystem, SD
card whole raw partition, RAM, etc.), but don't want to test every
single transfer size on each, to avoid bloating the overall time taken by
testing. If the boardenv doesn't specify a set of sizes, the built-in list
is used as a default, preserving backwards-compatibility.

Signed-off-by: Stephen Warren 
Acked-by: Simon Glass 
---
 test/py/tests/test_dfu.py | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/test/py/tests/test_dfu.py b/test/py/tests/test_dfu.py
index bb70008af3..093e8d0678 100644
--- a/test/py/tests/test_dfu.py
+++ b/test/py/tests/test_dfu.py
@@ -36,6 +36,14 @@ env__dfu_configs = (
         "fixture_id": "emmc",
         "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1",
         "cmd_params": "mmc 0",
+        # This value is optional.
+        # If present, it specified the set of transfer sizes tested.
+        # If missing, a default list of sizes will be used, which covers
+        #   various useful corner cases.
+        # Manually specifying test sizes is useful if you wish to test 4 DFU
+        # configurations, but don't want to test every single transfer size
+        # on each, to avoid bloating the overall time taken by testing.
+        "test_sizes": (63, 64, 65),
     },
 )
 
@@ -52,7 +60,7 @@ device.)
 # The set of file sizes to test. These values trigger various edge-cases such
 # as one less than, equal to, and one greater than typical USB max packet
 # sizes, and similar boundary conditions.
-test_sizes = (
+test_sizes_default = (
     64 - 1,
     64,
     64 + 1,
@@ -245,7 +253,7 @@ def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config):
     if not first_usb_dev_port:
         first_usb_dev_port = env__usb_dev_port
     if env__usb_dev_port == first_usb_dev_port:
-        sizes = test_sizes
+        sizes = env__dfu_config.get('test_sizes', test_sizes_default)
     else:
         sizes = []