[PATCH] tpm: Add TPM2_GetTestResult command support

Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com --- cmd/tpm-v2.c | 60 +++++++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 47 ++++++++++++++++++- include/tpm-v2.h | 23 ++++++++++ lib/tpm-v2.c | 82 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 50 +++++++++++++++++++++ 5 files changed, 261 insertions(+), 1 deletion(-)
diff --git a/cmd/tpm-v2.c b/cmd/tpm-v2.c index d93b83ada9..c6bec7387a 100644 --- a/cmd/tpm-v2.c +++ b/cmd/tpm-v2.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2018 Bootlin * Author: Miquel Raynal miquel.raynal@bootlin.com + * Copyright (C) 2023 Infineon Technologies AG */
#include <common.h> @@ -13,6 +14,8 @@ #include <tpm-v2.h> #include "tpm-user-utils.h"
+#define TEST_RESULT_DATA_BUFFER_SIZE 256 + static int do_tpm2_startup(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { @@ -356,6 +359,57 @@ static int do_tpm_pcr_setauthvalue(struct cmd_tbl *cmdtp, int flag, key, key_sz)); }
+static int do_tpm2_get_test_result(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret; + u8 data[TEST_RESULT_DATA_BUFFER_SIZE]; + size_t data_len = TEST_RESULT_DATA_BUFFER_SIZE; + u32 test_result; + u32 rc; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (argc != 1) + return CMD_RET_USAGE; + + rc = tpm2_get_test_result(dev, data, &data_len, &test_result); + if (rc) + goto out; + + printf("Test Result:\n\t0x%08X", test_result); + switch (test_result) { + case 0x0000: + printf("\tTPM2_RC_SUCCESS\n"); + break; + case 0x0101: + printf("\tTPM2_RC_FAILURE\n"); + break; + case 0x0153: + printf("\tTPM2_RC_NEEDS_TEST\n"); + break; + case 0x090A: + printf("\tTPM2_RC_TESTING\n"); + break; + } + + if (!data_len) { + printf("No Test Result Data available\n"); + goto out; + } + + printf("Test Result Data of Self Test:\n\t0x"); + for (int i = 0; i < data_len; i++) + printf("%02X", data[i]); + printf("\n"); + +out: + return report_return_code(rc); +} + static struct cmd_tbl tpm2_commands[] = { U_BOOT_CMD_MKENT(device, 0, 1, do_tpm_device, "", ""), U_BOOT_CMD_MKENT(info, 0, 1, do_tpm_info, "", ""), @@ -374,6 +428,8 @@ static struct cmd_tbl tpm2_commands[] = { do_tpm_pcr_setauthpolicy, "", ""), U_BOOT_CMD_MKENT(pcr_setauthvalue, 0, 1, do_tpm_pcr_setauthvalue, "", ""), + U_BOOT_CMD_MKENT(get_test_result, 0, 1, + do_tpm2_get_test_result, "", ""), };
struct cmd_tbl *get_tpm2_commands(unsigned int *size) @@ -447,4 +503,8 @@ U_BOOT_CMD(tpm2, CONFIG_SYS_MAXARGS, 1, do_tpm, "Issue a TPMv2.x command", " <pcr>: index of the PCR\n" " <key>: secret to protect the access of PCR #<pcr>\n" " <password>: optional password of the PLATFORM hierarchy\n" +"get_test_result\n" +" Show manufacturer-specific information regarding the results of a\n" +" self-test and an indication of the test status.\n" + ); diff --git a/drivers/tpm/tpm2_tis_sandbox.c b/drivers/tpm/tpm2_tis_sandbox.c index e4004cfcca..17979cd33a 100644 --- a/drivers/tpm/tpm2_tis_sandbox.c +++ b/drivers/tpm/tpm2_tis_sandbox.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2018, Bootlin * Author: Miquel Raynal miquel.raynal@bootlin.com + * Copyright (C) 2023 Infineon Technologies AG */
#include <common.h> @@ -231,6 +232,7 @@ static int sandbox_tpm2_check_session(struct udevice *dev, u32 command, u16 tag, case TPM2_CC_SELF_TEST: case TPM2_CC_GET_CAPABILITY: case TPM2_CC_PCR_READ: + case TPM2_CC_GET_TEST_RESULT: if (tag != TPM2_ST_NO_SESSIONS) { printf("No session required for command 0x%x\n", command); @@ -364,6 +366,13 @@ static int sandbox_tpm2_check_readyness(struct udevice *dev, int command) if (!tpm->startup_done) return TPM2_RC_INITIALIZE;
+ break; + case TPM2_CC_GET_TEST_RESULT: + if (!tpm->init_done || !tpm->startup_done) + return TPM2_RC_INITIALIZE; + if (!tpm->tests_done) + return TPM2_RC_NEEDS_TEST; + break; default: /* Skip this, since the startup may have happened in SPL @@ -458,7 +467,7 @@ static int sandbox_tpm2_xfer(struct udevice *dev, const u8 *sendbuf, command = get_unaligned_be32(sent); sent += sizeof(command); rc = sandbox_tpm2_check_readyness(dev, command); - if (rc) { + if (rc && rc != TPM2_RC_NEEDS_TEST) { sandbox_tpm2_fill_buf(recv, recv_len, tag, rc); return 0; } @@ -778,6 +787,42 @@ static int sandbox_tpm2_xfer(struct udevice *dev, const u8 *sendbuf, *recv_len = 12; memset(recvbuf, '\0', *recv_len); break; + + case TPM2_CC_GET_TEST_RESULT: { + u32 testresult = 0; + u8 data[10] = { 0 }; + + /* Check readiness */ + testresult = sandbox_tpm2_check_readyness(dev, command); + + /* Write tag */ + put_unaligned_be16(tag, recv); + recv += sizeof(tag); + + /* Ignore length for now */ + recv += sizeof(u32); + + /* Write return code */ + put_unaligned_be32(rc, recv); + recv += sizeof(rc); + + /* Write manufacturer-specific test result data */ + memcpy(recv, data, sizeof(data)); + recv += sizeof(data); + + /* Write test result */ + put_unaligned_be32(testresult, recv); + recv += sizeof(testresult); + + /* Add trailing \0 */ + *recv = '\0'; + + /* Write response length */ + *recv_len = recv - recvbuf; + put_unaligned_be32(*recv_len, recvbuf + sizeof(tag)); + + break; + } default: printf("TPM2 command %02x unknown in Sandbox\n", command); rc = TPM2_RC_COMMAND_CODE; diff --git a/include/tpm-v2.h b/include/tpm-v2.h index 2b6980e441..f6ec430316 100644 --- a/include/tpm-v2.h +++ b/include/tpm-v2.h @@ -3,6 +3,7 @@ * Defines APIs and structures that allow software to interact with a * TPM2 device * + * Copyright (C) 2023 Infineon Technologies AG * Copyright (c) 2020 Linaro * Copyright (c) 2018 Bootlin * @@ -276,6 +277,7 @@ enum tpm2_handles { * @TPM2_CC_DAM_PARAMETERS: TPM2_DictionaryAttackParameters(). * @TPM2_CC_GET_CAPABILITY: TPM2_GetCapibility(). * @TPM2_CC_GET_RANDOM: TPM2_GetRandom(). + * @TPM2_CC_GET_TEST_RESULT: TPM2_GetTestResult(). * @TPM2_CC_PCR_READ: TPM2_PCR_Read(). * @TPM2_CC_PCR_EXTEND: TPM2_PCR_Extend(). * @TPM2_CC_PCR_SETAUTHVAL: TPM2_PCR_SetAuthValue(). @@ -296,6 +298,7 @@ enum tpm2_command_codes { TPM2_CC_NV_READ = 0x014E, TPM2_CC_GET_CAPABILITY = 0x017A, TPM2_CC_GET_RANDOM = 0x017B, + TPM2_CC_GET_TEST_RESULT = 0x017C, TPM2_CC_PCR_READ = 0x017E, TPM2_CC_PCR_EXTEND = 0x0182, TPM2_CC_PCR_SETAUTHVAL = 0x0183, @@ -706,4 +709,24 @@ u32 tpm2_enable_nvcommits(struct udevice *dev, uint vendor_cmd, */ u32 tpm2_auto_start(struct udevice *dev);
+/** + * Issue a TPM2_GetTestResult command. + * + * @dev TPM device + * @data output buffer for manufacturer-specific test result data + * @data_len input/output parameter + * input: size of data buffer, output: size of test result data + * @test_result output parameter: test result response code: + * TPM2_RC_NEEDS_TEST, if TPM2 self-test has not been executed and + * a testable function has not been tested + * TPM2_RC_TESTING, if TPM2 self-test is in progress. + * TPM2_RC_SUCCESS, if testing of all functions is complete without + * functional failures. + * TPM2_RC_FAILURE, if any test failed. + * + * Return: code of the operation + */ +u32 tpm2_get_test_result(struct udevice *dev, void *data, size_t *data_len, + u32 *test_result); + #endif /* __TPM_V2_H */ diff --git a/lib/tpm-v2.c b/lib/tpm-v2.c index 9ab5b46df1..a3a29048e3 100644 --- a/lib/tpm-v2.c +++ b/lib/tpm-v2.c @@ -2,12 +2,14 @@ /* * Copyright (c) 2018 Bootlin * Author: Miquel Raynal miquel.raynal@bootlin.com + * Copyright (C) 2023 Infineon Technologies AG */
#include <common.h> #include <dm.h> #include <tpm-common.h> #include <tpm-v2.h> +#include <asm/unaligned.h> #include <linux/bitops.h> #include "tpm-utils.h"
@@ -742,3 +744,83 @@ u32 tpm2_enable_nvcommits(struct udevice *dev, uint vendor_cmd,
return 0; } + +u32 tpm2_get_test_result(struct udevice *dev, void *data, + size_t *data_len, u32 *test_result) +{ + const u8 command_v2[COMMAND_BUFFER_SIZE] = { + /* header 10 bytes */ + tpm_u16(TPM2_ST_NO_SESSIONS), /* TAG */ + tpm_u32(10), /* Length */ + tpm_u32(TPM2_CC_GET_TEST_RESULT), /* Command code */ + }; + + int ret; + u8 response[COMMAND_BUFFER_SIZE]; + size_t response_len = COMMAND_BUFFER_SIZE; + u32 response_size; /* Size of response bytestream */ + unsigned int response_size_off; + unsigned int data_off; + unsigned int test_result_off; + size_t data_buff_len; + + ret = tpm_sendrecv_command(dev, command_v2, response, &response_len); + + if (ret) { + log_debug("Returned on: ret = tpm_sendrecv_command()\n"); + return ret; + } + + /* + * Get responseSize from return bytestream, switch endianness: big->little + * In the response buffer, the responseSize is located after: + * tag (u16) + */ + response_size_off = sizeof(u16); + response_size = get_unaligned_be32(response + response_size_off); + + if (response_len < 10) { + log_debug + ("Returned on plausibility check: response_len < 10\n"); + return -EINVAL; + } + + if (response_size > response_len) { + log_debug + ("Returned on plausibility check: response_size > response_len\n\t" + "Response Size: %d\n\tResponse Length: %ld\n", + response_size, response_len); + return -E2BIG; + } + + /* + * Copy the test result data to buffer, transfer data_len + * In the response buffer, the test result data is located after: + * tag (u16), response size (u32), response code (u32). + */ + data_off = sizeof(u16) + sizeof(u32) + sizeof(u32); + data_buff_len = *data_len; + *data_len = response_size - data_off - sizeof(u32); + + /* Checks, if the reserved data buffer size is insufficient */ + if (*data_len > data_buff_len) { + log_debug + ("Returned on plausibility check: data_len > data_buff_len\n\t" + "Data Length: %ld\n\tData Buffer Length: %ld\n\t", + *data_len, data_buff_len); + return -E2BIG; + } + + memcpy(data, &response[data_off], *data_len); + + /* + * Get testResult from return bytestream, switch endianness: big->little + * In the response buffer, the test result is located after: + * tag (u16), response size (u32), response code (u32), + * test result data (*data_len). + */ + test_result_off = data_off + *data_len; + *test_result = get_unaligned_be32(response + test_result_off); + + return 0; +} diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index d2ad6f9e73..aad1d7a55b 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2018, Bootlin # Author: Miquel Raynal miquel.raynal@bootlin.com +# Copyright (C) 2023 Infineon Technologies AG
import os.path import pytest @@ -316,3 +317,52 @@ def test_tpm2_cleanup(u_boot_console): """Ensure the TPM is cleared from password or test related configuration."""
force_init(u_boot_console, True) + +@pytest.mark.buildconfigspec('cmd_tpm_v2') +def test_tpm2_get_test_result(u_boot_console): + """Execute a TPM_GetTestResult command. + + Ask the TPM to get the test result of the self test. + Display the Test Result and Test Result Data. + + Expected default return value of tpm2_get_test_result, if the TPM has not been initialized: + - TPM2_RC_INITIALIZE = TPM2_RC_VER1 + 0x0000 = 0x00000100. + + Expected default value for test_result: + - TPM_RC_NEEDS_TEST = 0x00000153, if tpm2 self_test has not been executed. + - TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures. + + There is no expected default value for the test result data because it would depend on the chip + used. The test result data is therefore not tested. + """ + if is_sandbox(u_boot_console): + u_boot_console.restart_uboot() + + skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False) + if skip_test: + pytest.skip('skip TPM device test') + + read_res = u_boot_console.run_command('tpm2 get_test_result') + output = u_boot_console.run_command('echo $?') + assert 'Error: 256' in read_res + + u_boot_console.run_command('tpm2 init') + read_res = u_boot_console.run_command('tpm2 get_test_result') + output = u_boot_console.run_command('echo $?') + assert 'Error: 256' in read_res + + u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR') + read_res = u_boot_console.run_command('tpm2 get_test_result') + output = u_boot_console.run_command('echo $?') + assert output.endswith('0') + assert 'Test Result:\r\r\n\t0x00000153\tTPM2_RC_NEEDS_TEST' in read_res + """ Assert console output. Test Result Data can not be tested, as it is vendor specific""" + assert 'Test Result Data of Self Test:\r\r\n\t0x' in read_res + + u_boot_console.run_command('tpm2 self_test full') + read_res = u_boot_console.run_command('tpm2 get_test_result') + output = u_boot_console.run_command('echo $?') + assert output.endswith('0') + assert 'Test Result:\r\r\n\t0x00000000\tTPM2_RC_SUCCESS' in read_res + """ Assert console output. Test Result Data can not be tested, as it is vendor specific""" + assert 'Test Result Data of Self Test:\r\r\n\t0x' in read_res

Hi Julia,
On Mon, 3 Jul 2023 at 14:03, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com
cmd/tpm-v2.c | 60 +++++++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 47 ++++++++++++++++++- include/tpm-v2.h | 23 ++++++++++ lib/tpm-v2.c | 82 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 50 +++++++++++++++++++++ 5 files changed, 261 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass sjg@chromium.org
See below
[..]
diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index d2ad6f9e73..aad1d7a55b 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2018, Bootlin # Author: Miquel Raynal miquel.raynal@bootlin.com +# Copyright (C) 2023 Infineon Technologies AG
import os.path import pytest @@ -316,3 +317,52 @@ def test_tpm2_cleanup(u_boot_console): """Ensure the TPM is cleared from password or test related configuration."""
force_init(u_boot_console, True)
+@pytest.mark.buildconfigspec('cmd_tpm_v2') +def test_tpm2_get_test_result(u_boot_console):
- """Execute a TPM_GetTestResult command.
- Ask the TPM to get the test result of the self test.
- Display the Test Result and Test Result Data.
- Expected default return value of tpm2_get_test_result, if the TPM has not been initialized:
- TPM2_RC_INITIALIZE = TPM2_RC_VER1 + 0x0000 = 0x00000100.
- Expected default value for test_result:
- TPM_RC_NEEDS_TEST = 0x00000153, if tpm2 self_test has not been executed.
- TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures.
- There is no expected default value for the test result data because it would depend on the chip
- used. The test result data is therefore not tested.
- """
- if is_sandbox(u_boot_console):
u_boot_console.restart_uboot()
We should get rid of this somehow. We don't want sandbox rebooting inthe middle of a test. It makes debugging painful, apart from anything else. What TPM state needs to be reset?
Looking at tpm2_tis_sandbox.c it is probably the s_state variable. The TPM state can be preserved across runs and is stored in the state file.
But if the state file is not being used (no -s argument) then the TPM should be reset each time DM is brought back up, i.e. between every test.
So, do we even need this reset?
Regards, Simon

Hi Simon,
On Mon, 3 Jul 2023 at 15:31, Simon Glass sjg@chromium.org wrote:
Hi Julia,
On Mon, 3 Jul 2023 at 14:03, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com
cmd/tpm-v2.c | 60 +++++++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 47 ++++++++++++++++++- include/tpm-v2.h | 23 ++++++++++ lib/tpm-v2.c | 82 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 50 +++++++++++++++++++++ 5 files changed, 261 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass sjg@chromium.org
See below
[..]
diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index d2ad6f9e73..aad1d7a55b 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2018, Bootlin # Author: Miquel Raynal miquel.raynal@bootlin.com +# Copyright (C) 2023 Infineon Technologies AG
import os.path import pytest @@ -316,3 +317,52 @@ def test_tpm2_cleanup(u_boot_console): """Ensure the TPM is cleared from password or test related configuration."""
force_init(u_boot_console, True)
+@pytest.mark.buildconfigspec('cmd_tpm_v2') +def test_tpm2_get_test_result(u_boot_console):
- """Execute a TPM_GetTestResult command.
- Ask the TPM to get the test result of the self test.
- Display the Test Result and Test Result Data.
- Expected default return value of tpm2_get_test_result, if the TPM has not been initialized:
- TPM2_RC_INITIALIZE = TPM2_RC_VER1 + 0x0000 = 0x00000100.
- Expected default value for test_result:
- TPM_RC_NEEDS_TEST = 0x00000153, if tpm2 self_test has not been executed.
- TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures.
- There is no expected default value for the test result data because it would depend on the chip
- used. The test result data is therefore not tested.
- """
- if is_sandbox(u_boot_console):
u_boot_console.restart_uboot()
We should get rid of this somehow. We don't want sandbox rebooting inthe middle of a test. It makes debugging painful, apart from anything else. What TPM state needs to be reset?
Looking at tpm2_tis_sandbox.c it is probably the s_state variable. The TPM state can be preserved across runs and is stored in the state file.
But if the state file is not being used (no -s argument) then the TPM should be reset each time DM is brought back up, i.e. between every test.
So, do we even need this reset?
Regards, Simon
In my understanding, shutting down and restarting U-Boot with restart_uboot() at the beginning of a test is beneficial, as it puts sandbox back into a known state for the test. This allows all tests to run in parallel, since no test depends on another.
In the test_tpm2_get_test_result test specifically, the reset is needed to test the behaviour of the do_tpm2_get_test_result() function, when the tpm has not already been initialized and the TPM self_test has not been executed. Performing restart_uboot() is essential to test for error code output, as sandbox always returns "TPM2_RC_SUCCESS" when the restart is not performed, making the test fail.
restart_uboot() is performed at the beginning of all tests in test_tpm2.py, either on its own, or as part of the tpm2_sandbox_init() command.
Regards, Julia

Hi Julia,
On Wed, 5 Jul 2023 at 00:55, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Hi Simon,
On Mon, 3 Jul 2023 at 15:31, Simon Glass sjg@chromium.org wrote:
Hi Julia,
On Mon, 3 Jul 2023 at 14:03, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com
cmd/tpm-v2.c | 60 +++++++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 47 ++++++++++++++++++- include/tpm-v2.h | 23 ++++++++++ lib/tpm-v2.c | 82 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 50 +++++++++++++++++++++ 5 files changed, 261 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass sjg@chromium.org
See below
[..]
diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index d2ad6f9e73..aad1d7a55b 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2018, Bootlin # Author: Miquel Raynal miquel.raynal@bootlin.com +# Copyright (C) 2023 Infineon Technologies AG
import os.path import pytest @@ -316,3 +317,52 @@ def test_tpm2_cleanup(u_boot_console): """Ensure the TPM is cleared from password or test related configuration."""
force_init(u_boot_console, True)
+@pytest.mark.buildconfigspec('cmd_tpm_v2') +def test_tpm2_get_test_result(u_boot_console):
- """Execute a TPM_GetTestResult command.
- Ask the TPM to get the test result of the self test.
- Display the Test Result and Test Result Data.
- Expected default return value of tpm2_get_test_result, if the TPM has not been initialized:
- TPM2_RC_INITIALIZE = TPM2_RC_VER1 + 0x0000 = 0x00000100.
- Expected default value for test_result:
- TPM_RC_NEEDS_TEST = 0x00000153, if tpm2 self_test has not been executed.
- TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures.
- There is no expected default value for the test result data because it would depend on the chip
- used. The test result data is therefore not tested.
- """
- if is_sandbox(u_boot_console):
u_boot_console.restart_uboot()
We should get rid of this somehow. We don't want sandbox rebooting inthe middle of a test. It makes debugging painful, apart from anything else. What TPM state needs to be reset?
Looking at tpm2_tis_sandbox.c it is probably the s_state variable. The TPM state can be preserved across runs and is stored in the state file.
But if the state file is not being used (no -s argument) then the TPM should be reset each time DM is brought back up, i.e. between every test.
So, do we even need this reset?
Regards, Simon
In my understanding, shutting down and restarting U-Boot with restart_uboot() at the beginning of a test is beneficial, as it puts sandbox back into a known state for the test. This allows all tests to run in parallel, since no test depends on another.
Actually with sandbox, we just restart the driver model each time. You can see this logic in test-main.c
In the test_tpm2_get_test_result test specifically, the reset is needed to test the behaviour of the do_tpm2_get_test_result() function, when the tpm has not already been initialized and the TPM self_test has not been executed. Performing restart_uboot() is essential to test for error code output, as sandbox always returns "TPM2_RC_SUCCESS" when the restart is not performed, making the test fail.
restart_uboot() is performed at the beginning of all tests in test_tpm2.py, either on its own, or as part of the tpm2_sandbox_init() command.
Yes, we should fix this. No other subsystem has this problem. I am wondering what is actually going on? The tpm must be keeping state outside of driver model. Perhaps we just need to call a TPM function to clear that state, in state_reset_for_test().
Regards, Simon

Hi Simon,
On Thu, 6 Jul 2023 at 09:57, Simon Glass sjg@chromium.org wrote:
Hi Julia,
On Wed, 5 Jul 2023 at 00:55, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Hi Simon,
On Mon, 3 Jul 2023 at 15:31, Simon Glass sjg@chromium.org wrote:
Hi Julia,
On Mon, 3 Jul 2023 at 14:03, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com
cmd/tpm-v2.c | 60 +++++++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 47 ++++++++++++++++++- include/tpm-v2.h | 23 ++++++++++ lib/tpm-v2.c | 82 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 50 +++++++++++++++++++++ 5 files changed, 261 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass sjg@chromium.org
See below
[..]
diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index d2ad6f9e73..aad1d7a55b 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2018, Bootlin # Author: Miquel Raynal miquel.raynal@bootlin.com +# Copyright (C) 2023 Infineon Technologies AG
import os.path import pytest @@ -316,3 +317,52 @@ def test_tpm2_cleanup(u_boot_console): """Ensure the TPM is cleared from password or test related configuration."""
force_init(u_boot_console, True)
+@pytest.mark.buildconfigspec('cmd_tpm_v2') +def test_tpm2_get_test_result(u_boot_console):
- """Execute a TPM_GetTestResult command.
- Ask the TPM to get the test result of the self test.
- Display the Test Result and Test Result Data.
- Expected default return value of tpm2_get_test_result, if the TPM has not been initialized:
- TPM2_RC_INITIALIZE = TPM2_RC_VER1 + 0x0000 = 0x00000100.
- Expected default value for test_result:
- TPM_RC_NEEDS_TEST = 0x00000153, if tpm2 self_test has not been executed.
- TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures.
- There is no expected default value for the test result data because it would depend on the chip
- used. The test result data is therefore not tested.
- """
- if is_sandbox(u_boot_console):
u_boot_console.restart_uboot()
We should get rid of this somehow. We don't want sandbox rebooting inthe middle of a test. It makes debugging painful, apart from anything else. What TPM state needs to be reset?
Looking at tpm2_tis_sandbox.c it is probably the s_state variable. The TPM state can be preserved across runs and is stored in the state file.
But if the state file is not being used (no -s argument) then the TPM should be reset each time DM is brought back up, i.e. between every test.
So, do we even need this reset?
In my understanding, shutting down and restarting U-Boot with restart_uboot() at the beginning of a test is beneficial, as it puts sandbox back into a known state for the test. This allows all tests to run in parallel, since no test depends on another.
Actually with sandbox, we just restart the driver model each time. You can see this logic in test-main.c
In the test_tpm2_get_test_result test specifically, the reset is needed to test the behaviour of the do_tpm2_get_test_result() function, when the tpm has not already been initialized and the TPM self_test has not been executed. Performing restart_uboot() is essential to test for error code output, as sandbox always returns "TPM2_RC_SUCCESS" when the restart is not performed, making the test fail.
restart_uboot() is performed at the beginning of all tests in test_tpm2.py, either on its own, or as part of the tpm2_sandbox_init() command.
Yes, we should fix this. No other subsystem has this problem. I am wondering what is actually going on? The tpm must be keeping state outside of driver model. Perhaps we just need to call a TPM function to clear that state, in state_reset_for_test().
After looking at the code again, I agree that rebooting sandbox at the beginning of every test is not optimal. Currently, I do not have an in-depth understanding of the needed changes and their full impact. Taking into account the current changes to tpm functions, such as the new startup sequence, I would prefer to retain the restart for now.
I will send a new version of the patch shortly, where I will move the tests relying on the restart into an additional patch, to be merged independently.
Regards, Julia

Hi Julia,
On Wed, 2 Aug 2023 at 02:28, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Hi Simon,
On Thu, 6 Jul 2023 at 09:57, Simon Glass sjg@chromium.org wrote:
Hi Julia,
On Wed, 5 Jul 2023 at 00:55, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Hi Simon,
On Mon, 3 Jul 2023 at 15:31, Simon Glass sjg@chromium.org wrote:
Hi Julia,
On Mon, 3 Jul 2023 at 14:03, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com
cmd/tpm-v2.c | 60 +++++++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 47 ++++++++++++++++++- include/tpm-v2.h | 23 ++++++++++ lib/tpm-v2.c | 82 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 50 +++++++++++++++++++++ 5 files changed, 261 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass sjg@chromium.org
See below
[..]
diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index d2ad6f9e73..aad1d7a55b 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2018, Bootlin # Author: Miquel Raynal miquel.raynal@bootlin.com +# Copyright (C) 2023 Infineon Technologies AG
import os.path import pytest @@ -316,3 +317,52 @@ def test_tpm2_cleanup(u_boot_console): """Ensure the TPM is cleared from password or test related configuration."""
force_init(u_boot_console, True)
+@pytest.mark.buildconfigspec('cmd_tpm_v2') +def test_tpm2_get_test_result(u_boot_console):
- """Execute a TPM_GetTestResult command.
- Ask the TPM to get the test result of the self test.
- Display the Test Result and Test Result Data.
- Expected default return value of tpm2_get_test_result, if the TPM has not been initialized:
- TPM2_RC_INITIALIZE = TPM2_RC_VER1 + 0x0000 = 0x00000100.
- Expected default value for test_result:
- TPM_RC_NEEDS_TEST = 0x00000153, if tpm2 self_test has not been executed.
- TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures.
- There is no expected default value for the test result data because it would depend on the chip
- used. The test result data is therefore not tested.
- """
- if is_sandbox(u_boot_console):
u_boot_console.restart_uboot()
We should get rid of this somehow. We don't want sandbox rebooting inthe middle of a test. It makes debugging painful, apart from anything else. What TPM state needs to be reset?
Looking at tpm2_tis_sandbox.c it is probably the s_state variable. The TPM state can be preserved across runs and is stored in the state file.
But if the state file is not being used (no -s argument) then the TPM should be reset each time DM is brought back up, i.e. between every test.
So, do we even need this reset?
In my understanding, shutting down and restarting U-Boot with restart_uboot() at the beginning of a test is beneficial, as it puts sandbox back into a known state for the test. This allows all tests to run in parallel, since no test depends on another.
Actually with sandbox, we just restart the driver model each time. You can see this logic in test-main.c
In the test_tpm2_get_test_result test specifically, the reset is needed to test the behaviour of the do_tpm2_get_test_result() function, when the tpm has not already been initialized and the TPM self_test has not been executed. Performing restart_uboot() is essential to test for error code output, as sandbox always returns "TPM2_RC_SUCCESS" when the restart is not performed, making the test fail.
restart_uboot() is performed at the beginning of all tests in test_tpm2.py, either on its own, or as part of the tpm2_sandbox_init() command.
Yes, we should fix this. No other subsystem has this problem. I am wondering what is actually going on? The tpm must be keeping state outside of driver model. Perhaps we just need to call a TPM function to clear that state, in state_reset_for_test().
After looking at the code again, I agree that rebooting sandbox at the beginning of every test is not optimal. Currently, I do not have an in-depth understanding of the needed changes and their full impact. Taking into account the current changes to tpm functions, such as the new startup sequence, I would prefer to retain the restart for now.
I will send a new version of the patch shortly, where I will move the tests relying on the restart into an additional patch, to be merged independently.
OK that sounds good, thank you. With that final patch, I should be able to figure out what is going wrong, so it is not needed.
Regards, Simon

Hi Julia,
Apologies for the late response, I was on vacation.
On Mon, 3 Jul 2023 at 16:03, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
What the code does is quite obvious, can you please elaborate on why we need this applied?
[...]
+#define TEST_RESULT_DATA_BUFFER_SIZE 256
static int do_tpm2_startup(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { @@ -356,6 +359,57 @@ static int do_tpm_pcr_setauthvalue(struct cmd_tbl *cmdtp, int flag, key, key_sz)); }
+static int do_tpm2_get_test_result(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
+{
struct udevice *dev;
int ret;
u8 data[TEST_RESULT_DATA_BUFFER_SIZE];
size_t data_len = TEST_RESULT_DATA_BUFFER_SIZE;
= sizeof(data)
u32 test_result;
u32 rc;
ret = get_tpm(&dev);
if (ret)
return ret;
if (argc != 1)
return CMD_RET_USAGE;
Can you move this one above the get_tpm?
rc = tpm2_get_test_result(dev, data, &data_len, &test_result);
if (rc)
goto out;
printf("Test Result:\n\t0x%08X", test_result);
switch (test_result) {
case 0x0000:
We have enum tpm2_return_codes. Please add the missing cases there and use that instead of magic values
printf("\tTPM2_RC_SUCCESS\n");
break;
case 0x0101:
printf("\tTPM2_RC_FAILURE\n");
break;
case 0x0153:
printf("\tTPM2_RC_NEEDS_TEST\n");
break;
case 0x090A:
printf("\tTPM2_RC_TESTING\n");
break;
}
if (!data_len) {
printf("No Test Result Data available\n");
goto out;
}
printf("Test Result Data of Self Test:\n\t0x");
for (int i = 0; i < data_len; i++)
printf("%02X", data[i]);
printf("\n");
+out:
return report_return_code(rc);
+}
static struct cmd_tbl tpm2_commands[] = { U_BOOT_CMD_MKENT(device, 0, 1, do_tpm_device, "", ""), U_BOOT_CMD_MKENT(info, 0, 1, do_tpm_info, "", ""), @@ -374,6 +428,8 @@ static struct cmd_tbl tpm2_commands[] = { do_tpm_pcr_setauthpolicy, "", ""), U_BOOT_CMD_MKENT(pcr_setauthvalue, 0, 1, do_tpm_pcr_setauthvalue, "", ""),
U_BOOT_CMD_MKENT(get_test_result, 0, 1,
do_tpm2_get_test_result, "", ""),
};
struct cmd_tbl *get_tpm2_commands(unsigned int *size) @@ -447,4 +503,8 @@ U_BOOT_CMD(tpm2, CONFIG_SYS_MAXARGS, 1, do_tpm, "Issue a TPMv2.x command", " <pcr>: index of the PCR\n" " <key>: secret to protect the access of PCR #<pcr>\n" " <password>: optional password of the PLATFORM hierarchy\n" +"get_test_result\n" +" Show manufacturer-specific information regarding the results of a\n" +" self-test and an indication of the test status.\n"
); diff --git a/drivers/tpm/tpm2_tis_sandbox.c b/drivers/tpm/tpm2_tis_sandbox.c index e4004cfcca..17979cd33a 100644 --- a/drivers/tpm/tpm2_tis_sandbox.c +++ b/drivers/tpm/tpm2_tis_sandbox.c @@ -2,6 +2,7 @@ /*
- Copyright (c) 2018, Bootlin
- Author: Miquel Raynal miquel.raynal@bootlin.com
*/
- Copyright (C) 2023 Infineon Technologies AG
#include <common.h> @@ -231,6 +232,7 @@ static int sandbox_tpm2_check_session(struct udevice *dev, u32 command, u16 tag, case TPM2_CC_SELF_TEST: case TPM2_CC_GET_CAPABILITY: case TPM2_CC_PCR_READ:
case TPM2_CC_GET_TEST_RESULT: if (tag != TPM2_ST_NO_SESSIONS) { printf("No session required for command 0x%x\n", command);
@@ -364,6 +366,13 @@ static int sandbox_tpm2_check_readyness(struct udevice *dev, int command) if (!tpm->startup_done) return TPM2_RC_INITIALIZE;
break;
case TPM2_CC_GET_TEST_RESULT:
if (!tpm->init_done || !tpm->startup_done)
return TPM2_RC_INITIALIZE;
if (!tpm->tests_done)
return TPM2_RC_NEEDS_TEST;
break; default: /* Skip this, since the startup may have happened in SPL
@@ -458,7 +467,7 @@ static int sandbox_tpm2_xfer(struct udevice *dev, const u8 *sendbuf, command = get_unaligned_be32(sent); sent += sizeof(command); rc = sandbox_tpm2_check_readyness(dev, command);
if (rc) {
if (rc && rc != TPM2_RC_NEEDS_TEST) { sandbox_tpm2_fill_buf(recv, recv_len, tag, rc); return 0; }
@@ -778,6 +787,42 @@ static int sandbox_tpm2_xfer(struct udevice *dev, const u8 *sendbuf, *recv_len = 12; memset(recvbuf, '\0', *recv_len); break;
case TPM2_CC_GET_TEST_RESULT: {
u32 testresult = 0;
u8 data[10] = { 0 };
/* Check readiness */
testresult = sandbox_tpm2_check_readyness(dev, command);
/* Write tag */
put_unaligned_be16(tag, recv);
recv += sizeof(tag);
/* Ignore length for now */
recv += sizeof(u32);
/* Write return code */
put_unaligned_be32(rc, recv);
recv += sizeof(rc);
/* Write manufacturer-specific test result data */
memcpy(recv, data, sizeof(data));
recv += sizeof(data);
/* Write test result */
put_unaligned_be32(testresult, recv);
recv += sizeof(testresult);
/* Add trailing \0 */
*recv = '\0';
/* Write response length */
*recv_len = recv - recvbuf;
put_unaligned_be32(*recv_len, recvbuf + sizeof(tag));
break;
} default: printf("TPM2 command %02x unknown in Sandbox\n", command); rc = TPM2_RC_COMMAND_CODE;
diff --git a/include/tpm-v2.h b/include/tpm-v2.h index 2b6980e441..f6ec430316 100644 --- a/include/tpm-v2.h +++ b/include/tpm-v2.h @@ -3,6 +3,7 @@
- Defines APIs and structures that allow software to interact with a
- TPM2 device
- Copyright (C) 2023 Infineon Technologies AG
- Copyright (c) 2020 Linaro
- Copyright (c) 2018 Bootlin
@@ -276,6 +277,7 @@ enum tpm2_handles {
- @TPM2_CC_DAM_PARAMETERS: TPM2_DictionaryAttackParameters().
- @TPM2_CC_GET_CAPABILITY: TPM2_GetCapibility().
- @TPM2_CC_GET_RANDOM: TPM2_GetRandom().
- @TPM2_CC_GET_TEST_RESULT: TPM2_GetTestResult().
- @TPM2_CC_PCR_READ: TPM2_PCR_Read().
- @TPM2_CC_PCR_EXTEND: TPM2_PCR_Extend().
- @TPM2_CC_PCR_SETAUTHVAL: TPM2_PCR_SetAuthValue().
@@ -296,6 +298,7 @@ enum tpm2_command_codes { TPM2_CC_NV_READ = 0x014E, TPM2_CC_GET_CAPABILITY = 0x017A, TPM2_CC_GET_RANDOM = 0x017B,
TPM2_CC_GET_TEST_RESULT = 0x017C, TPM2_CC_PCR_READ = 0x017E, TPM2_CC_PCR_EXTEND = 0x0182, TPM2_CC_PCR_SETAUTHVAL = 0x0183,
@@ -706,4 +709,24 @@ u32 tpm2_enable_nvcommits(struct udevice *dev, uint vendor_cmd, */ u32 tpm2_auto_start(struct udevice *dev);
+/**
- Issue a TPM2_GetTestResult command.
- @dev TPM device
- @data output buffer for manufacturer-specific test result data
- @data_len input/output parameter
input: size of data buffer, output: size of test result data
- @test_result output parameter: test result response code:
TPM2_RC_NEEDS_TEST, if TPM2 self-test has not been executed and
a testable function has not been tested
TPM2_RC_TESTING, if TPM2 self-test is in progress.
TPM2_RC_SUCCESS, if testing of all functions is complete without
functional failures.
TPM2_RC_FAILURE, if any test failed.
- Return: code of the operation
- */
+u32 tpm2_get_test_result(struct udevice *dev, void *data, size_t *data_len,
u32 *test_result);
#endif /* __TPM_V2_H */ diff --git a/lib/tpm-v2.c b/lib/tpm-v2.c index 9ab5b46df1..a3a29048e3 100644 --- a/lib/tpm-v2.c +++ b/lib/tpm-v2.c @@ -2,12 +2,14 @@ /*
- Copyright (c) 2018 Bootlin
- Author: Miquel Raynal miquel.raynal@bootlin.com
*/
- Copyright (C) 2023 Infineon Technologies AG
#include <common.h> #include <dm.h> #include <tpm-common.h> #include <tpm-v2.h> +#include <asm/unaligned.h> #include <linux/bitops.h> #include "tpm-utils.h"
@@ -742,3 +744,83 @@ u32 tpm2_enable_nvcommits(struct udevice *dev, uint vendor_cmd,
return 0;
}
+u32 tpm2_get_test_result(struct udevice *dev, void *data,
size_t *data_len, u32 *test_result)
+{
const u8 command_v2[COMMAND_BUFFER_SIZE] = {
/* header 10 bytes */
tpm_u16(TPM2_ST_NO_SESSIONS), /* TAG */
tpm_u32(10), /* Length */
tpm_u32(TPM2_CC_GET_TEST_RESULT), /* Command code */
};
int ret;
u8 response[COMMAND_BUFFER_SIZE];
size_t response_len = COMMAND_BUFFER_SIZE;
u32 response_size; /* Size of response bytestream */
reposnse_size and response_len is a bit confusing. Please find some names that indicate the first one is the reserved size while the second is the actual
unsigned int response_size_off;
unsigned int data_off;
unsigned int test_result_off;
size_t data_buff_len;
ret = tpm_sendrecv_command(dev, command_v2, response, &response_len);
if (ret) {
log_debug("Returned on: ret = tpm_sendrecv_command()\n");
return ret;
Isn't this an error? Can we use return log_msg_ret("test", ret); or something along those lines?
}
/*
* Get responseSize from return bytestream, switch endianness: big->little
* In the response buffer, the responseSize is located after:
* tag (u16)
*/
response_size_off = sizeof(u16);
Keep the comment and just do response + sizeof(u16)
response_size = get_unaligned_be32(response + response_size_off);
if (response_len < 10) {
log_debug
("Returned on plausibility check: response_len < 10\n");
Why is this an error?
return -EINVAL;
}
if (response_size > response_len) {
log_debug
("Returned on plausibility check: response_size > response_len\n\t"
Can you remove this line and just report the size mismatch? It should be obvious why it's wrong.
"Response Size: %d\n\tResponse Length: %ld\n",
response_size, response_len);
return -E2BIG;
}
/*
* Copy the test result data to buffer, transfer data_len
* In the response buffer, the test result data is located after:
* tag (u16), response size (u32), response code (u32).
*/
data_off = sizeof(u16) + sizeof(u32) + sizeof(u32);
data_buff_len = *data_len;
*data_len = response_size - data_off - sizeof(u32);
/* Checks, if the reserved data buffer size is insufficient */
if (*data_len > data_buff_len) {
log_debug
("Returned on plausibility check: data_len > data_buff_len\n\t"
"Data Length: %ld\n\tData Buffer Length: %ld\n\t",
*data_len, data_buff_len);
return -E2BIG;
}
memcpy(data, &response[data_off], *data_len);
/*
* Get testResult from return bytestream, switch endianness: big->little
* In the response buffer, the test result is located after:
* tag (u16), response size (u32), response code (u32),
* test result data (*data_len).
*/
test_result_off = data_off + *data_len;
*test_result = get_unaligned_be32(response + test_result_off);
return 0;
+}
[...]
Thanks /Ilias
participants (3)
-
Ilias Apalodimas
-
Julia Daxenberger
-
Simon Glass