From mboxrd@z Thu Jan 1 00:00:00 1970 From: Christophe Ricard Date: Mon, 24 Aug 2015 22:23:35 +0200 Subject: [U-Boot] [PATCH v2 26/28] dm: tpm: Add a 'tpmtest' command In-Reply-To: <1440289904-31280-27-git-send-email-sjg@chromium.org> References: <1440289904-31280-1-git-send-email-sjg@chromium.org> <1440289904-31280-27-git-send-email-sjg@chromium.org> Message-ID: <55DB7D47.90909@gmail.com> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de Hi Simon, Acked-by: Christophe Ricard I will try those tpm tests. Best Regards Christophe On 23/08/2015 02:31, Simon Glass wrote: > These tests come from Chrome OS code. They are not particularly tidy but can > be useful for checking that the TPM is behaving correctly. Some knowledge of > TPM operation is required to use these. > > Signed-off-by: Simon Glass > --- > > Changes in v2: > - Add new patch with a 'tpmtest' command > > common/Kconfig | 10 + > common/Makefile | 1 + > common/cmd_tpm_test.c | 565 ++++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 576 insertions(+) > create mode 100644 common/cmd_tpm_test.c > > diff --git a/common/Kconfig b/common/Kconfig > index bacc4e0..2c42b8e 100644 > --- a/common/Kconfig > +++ b/common/Kconfig > @@ -635,6 +635,16 @@ config CMD_TPM > command requires a suitable TPM on your board and the correct driver > must be enabled. > > +config CMD_TPM_TEST > + bool "Enable the 'tpm test' command" > + depends on CMD_TPM > + help > + This provides a a series of tests to confirm that the TPM is working > + correctly. The tests cover initialisation, non-volatile RAM, extend, > + global lock and checking that timing is within expectations. The > + tests pass correctly on Infineon TPMs but may need to be adjusted > + for other devices. > + > endmenu > > endmenu > diff --git a/common/Makefile b/common/Makefile > index dc82433..f4ba878 100644 > --- a/common/Makefile > +++ b/common/Makefile > @@ -169,6 +169,7 @@ obj-$(CONFIG_CMD_TIME) += cmd_time.o > obj-$(CONFIG_CMD_TRACE) += cmd_trace.o > obj-$(CONFIG_SYS_HUSH_PARSER) += cmd_test.o > obj-$(CONFIG_CMD_TPM) += cmd_tpm.o > +obj-$(CONFIG_CMD_TPM_TEST) += cmd_tpm_test.o > obj-$(CONFIG_CMD_TSI148) += cmd_tsi148.o > obj-$(CONFIG_CMD_UBI) += cmd_ubi.o > obj-$(CONFIG_CMD_UBIFS) += cmd_ubifs.o > diff --git a/common/cmd_tpm_test.c b/common/cmd_tpm_test.c > new file mode 100644 > index 0000000..2bef5a1 > --- /dev/null > +++ b/common/cmd_tpm_test.c > @@ -0,0 +1,565 @@ > +/* > + * Copyright (c) 2015 Google, Inc > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#include > +#include > +#include > +#include > + > +/* Prints error and returns on failure */ > +#define TPM_CHECK(tpm_command) do { \ > + uint32_t result; \ > + \ > + result = (tpm_command); \ > + if (result != TPM_SUCCESS) { \ > + printf("TEST FAILED: line %d: " #tpm_command ": 0x%x\n", \ > + __LINE__, result); \ > + return result; \ > + } \ > +} while (0) > + > +#define INDEX0 0xda70 > +#define INDEX1 0xda71 > +#define INDEX2 0xda72 > +#define INDEX3 0xda73 > +#define INDEX_INITIALISED 0xda80 > +#define PHYS_PRESENCE 4 > +#define PRESENCE 8 > + > +static uint32_t TlclStartupIfNeeded(void) > +{ > + uint32_t result = tpm_startup(TPM_ST_CLEAR); > + > + return result == TPM_INVALID_POSTINIT ? TPM_SUCCESS : result; > +} > + > +static int test_timer(void) > +{ > + printf("get_timer(0) = %lu\n", get_timer(0)); > + return 0; > +} > + > +static uint32_t tpm_get_flags(uint8_t *disable, uint8_t *deactivated, > + uint8_t *nvlocked) > +{ > + struct tpm_permanent_flags pflags; > + uint32_t result; > + > + result = tpm_get_permanent_flags(&pflags); > + if (result) > + return result; > + if (disable) > + *disable = pflags.disable; > + if (deactivated) > + *deactivated = pflags.deactivated; > + if (nvlocked) > + *nvlocked = pflags.nv_locked; > + debug("TPM: Got flags disable=%d, deactivated=%d, nvlocked=%d\n", > + pflags.disable, pflags.deactivated, pflags.nv_locked); > + > + return 0; > +} > + > +static uint32_t tpm_set_global_lock(void) > +{ > + uint32_t x; > + > + debug("TPM: Set global lock\n"); > + return tpm_nv_write_value(INDEX0, (uint8_t *)&x, 0); > +} > + > +static uint32_t tpm_nv_write_value_lock(uint32_t index) > +{ > + debug("TPM: Write lock 0x%x\n", index); > + > + return tpm_nv_write_value(index, NULL, 0); > +} > + > +static uint32_t tpm_nv_set_locked(void) > +{ > + debug("TPM: Set NV locked\n"); > + > + return tpm_nv_define_space(TPM_NV_INDEX_LOCK, 0, 0); > +} > + > +static int tpm_is_owned(void) > +{ > + uint8_t response[TPM_PUBEK_SIZE]; > + uint32_t result; > + > + result = tpm_read_pubek(response, sizeof(response)); > + > + return result != TPM_SUCCESS; > +} > + > +static int test_early_extend(void) > +{ > + uint8_t value_in[20]; > + uint8_t value_out[20]; > + > + printf("Testing earlyextend ..."); > + tpm_init(); > + TPM_CHECK(tpm_startup(TPM_ST_CLEAR)); > + TPM_CHECK(tpm_continue_self_test()); > + TPM_CHECK(tpm_extend(1, value_in, value_out)); > + printf("done\n"); > + return 0; > +} > + > +static int test_early_nvram(void) > +{ > + uint32_t x; > + > + printf("Testing earlynvram ..."); > + tpm_init(); > + TPM_CHECK(tpm_startup(TPM_ST_CLEAR)); > + TPM_CHECK(tpm_continue_self_test()); > + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); > + TPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x))); > + printf("done\n"); > + return 0; > +} > + > +static int test_early_nvram2(void) > +{ > + uint32_t x; > + > + printf("Testing earlynvram2 ..."); > + tpm_init(); > + TPM_CHECK(tpm_startup(TPM_ST_CLEAR)); > + TPM_CHECK(tpm_continue_self_test()); > + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); > + TPM_CHECK(tpm_nv_write_value(INDEX0, (uint8_t *)&x, sizeof(x))); > + printf("done\n"); > + return 0; > +} > + > +static int test_enable(void) > +{ > + uint8_t disable = 0, deactivated = 0; > + > + printf("Testing enable ...\n"); > + tpm_init(); > + TPM_CHECK(TlclStartupIfNeeded()); > + TPM_CHECK(tpm_self_test_full()); > + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); > + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); > + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); > + TPM_CHECK(tpm_physical_enable()); > + TPM_CHECK(tpm_physical_set_deactivated(0)); > + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); > + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); > + if (disable == 1 || deactivated == 1) > + printf("\tfailed to enable or activate\n"); > + printf("\tdone\n"); > + return 0; > +} > + > +#define reboot() do { \ > + printf("\trebooting...\n"); \ > + reset_cpu(0); \ > +} while (0) > + > +static int test_fast_enable(void) > +{ > + uint8_t disable = 0, deactivated = 0; > + int i; > + > + printf("Testing fastenable ...\n"); > + tpm_init(); > + TPM_CHECK(TlclStartupIfNeeded()); > + TPM_CHECK(tpm_self_test_full()); > + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); > + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); > + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); > + for (i = 0; i < 2; i++) { > + TPM_CHECK(tpm_force_clear()); > + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); > + printf("\tdisable is %d, deactivated is %d\n", disable, > + deactivated); > + assert(disable == 1 && deactivated == 1); > + TPM_CHECK(tpm_physical_enable()); > + TPM_CHECK(tpm_physical_set_deactivated(0)); > + TPM_CHECK(tpm_get_flags(&disable, &deactivated, NULL)); > + printf("\tdisable is %d, deactivated is %d\n", disable, > + deactivated); > + assert(disable == 0 && deactivated == 0); > + } > + printf("\tdone\n"); > + return 0; > +} > + > +static int test_global_lock(void) > +{ > + uint32_t zero = 0; > + uint32_t result; > + uint32_t x; > + > + printf("Testing globallock ...\n"); > + tpm_init(); > + TPM_CHECK(TlclStartupIfNeeded()); > + TPM_CHECK(tpm_self_test_full()); > + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); > + TPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x))); > + TPM_CHECK(tpm_nv_write_value(INDEX0, (uint8_t *)&zero, > + sizeof(uint32_t))); > + TPM_CHECK(tpm_nv_read_value(INDEX1, (uint8_t *)&x, sizeof(x))); > + TPM_CHECK(tpm_nv_write_value(INDEX1, (uint8_t *)&zero, > + sizeof(uint32_t))); > + TPM_CHECK(tpm_set_global_lock()); > + /* Verifies that write to index0 fails */ > + x = 1; > + result = tpm_nv_write_value(INDEX0, (uint8_t *)&x, sizeof(x)); > + assert(result == TPM_AREA_LOCKED); > + TPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x))); > + assert(x == 0); > + /* Verifies that write to index1 is still possible */ > + x = 2; > + TPM_CHECK(tpm_nv_write_value(INDEX1, (uint8_t *)&x, sizeof(x))); > + TPM_CHECK(tpm_nv_read_value(INDEX1, (uint8_t *)&x, sizeof(x))); > + assert(x == 2); > + /* Turns off PP */ > + tpm_tsc_physical_presence(PHYS_PRESENCE); > + /* Verifies that write to index1 fails */ > + x = 3; > + result = tpm_nv_write_value(INDEX1, (uint8_t *)&x, sizeof(x)); > + assert(result == TPM_BAD_PRESENCE); > + TPM_CHECK(tpm_nv_read_value(INDEX1, (uint8_t *)&x, sizeof(x))); > + assert(x == 2); > + printf("\tdone\n"); > + return 0; > +} > + > +static int test_lock(void) > +{ > + printf("Testing lock ...\n"); > + tpm_init(); > + tpm_startup(TPM_ST_CLEAR); > + tpm_self_test_full(); > + tpm_tsc_physical_presence(PRESENCE); > + tpm_nv_write_value_lock(INDEX0); > + printf("\tLocked 0x%x\n", INDEX0); > + printf("\tdone\n"); > + return 0; > +} > + > +static void initialise_spaces(void) > +{ > + uint32_t zero = 0; > + uint32_t perm = TPM_NV_PER_WRITE_STCLEAR | TPM_NV_PER_PPWRITE; > + > + printf("\tInitialising spaces\n"); > + tpm_nv_set_locked(); /* useful only the first time */ > + tpm_nv_define_space(INDEX0, perm, 4); > + tpm_nv_write_value(INDEX0, (uint8_t *)&zero, 4); > + tpm_nv_define_space(INDEX1, perm, 4); > + tpm_nv_write_value(INDEX1, (uint8_t *)&zero, 4); > + tpm_nv_define_space(INDEX2, perm, 4); > + tpm_nv_write_value(INDEX2, (uint8_t *)&zero, 4); > + tpm_nv_define_space(INDEX3, perm, 4); > + tpm_nv_write_value(INDEX3, (uint8_t *)&zero, 4); > + perm = TPM_NV_PER_READ_STCLEAR | TPM_NV_PER_WRITE_STCLEAR | > + TPM_NV_PER_PPWRITE; > + tpm_nv_define_space(INDEX_INITIALISED, perm, 1); > +} > + > +static int test_readonly(void) > +{ > + uint8_t c; > + uint32_t index_0, index_1, index_2, index_3; > + int read0, read1, read2, read3; > + > + printf("Testing readonly ...\n"); > + tpm_init(); > + tpm_startup(TPM_ST_CLEAR); > + tpm_self_test_full(); > + tpm_tsc_physical_presence(PRESENCE); > + /* > + * Checks if initialisation has completed by trying to read-lock a > + * space that's created at the end of initialisation > + */ > + if (tpm_nv_read_value(INDEX_INITIALISED, &c, 0) == TPM_BADINDEX) { > + /* The initialisation did not complete */ > + initialise_spaces(); > + } > + > + /* Checks if spaces are OK or messed up */ > + read0 = tpm_nv_read_value(INDEX0, (uint8_t *)&index_0, sizeof(index_0)); > + read1 = tpm_nv_read_value(INDEX1, (uint8_t *)&index_1, sizeof(index_1)); > + read2 = tpm_nv_read_value(INDEX2, (uint8_t *)&index_2, sizeof(index_2)); > + read3 = tpm_nv_read_value(INDEX3, (uint8_t *)&index_3, sizeof(index_3)); > + if (read0 || read1 || read2 || read3) { > + printf("Invalid contents\n"); > + return 0; > + } > + > + /* > + * Writes space, and locks it. Then attempts to write again. > + * I really wish I could use the imperative. > + */ > + index_0 += 1; > + if (tpm_nv_write_value(INDEX0, (uint8_t *)&index_0, sizeof(index_0) != > + TPM_SUCCESS)) { > + error("\tcould not write index 0\n"); > + } > + tpm_nv_write_value_lock(INDEX0); > + if (tpm_nv_write_value(INDEX0, (uint8_t *)&index_0, sizeof(index_0)) == > + TPM_SUCCESS) > + error("\tindex 0 is not locked\n"); > + > + printf("\tdone\n"); > + return 0; > +} > + > +static int test_redefine_unowned(void) > +{ > + uint32_t perm; > + uint32_t result; > + uint32_t x; > + > + printf("Testing redefine_unowned ..."); > + tpm_init(); > + TPM_CHECK(TlclStartupIfNeeded()); > + TPM_CHECK(tpm_self_test_full()); > + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); > + assert(!tpm_is_owned()); > + > + /* Ensures spaces exist. */ > + TPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x))); > + TPM_CHECK(tpm_nv_read_value(INDEX1, (uint8_t *)&x, sizeof(x))); > + > + /* Redefines spaces a couple of times. */ > + perm = TPM_NV_PER_PPWRITE | TPM_NV_PER_GLOBALLOCK; > + TPM_CHECK(tpm_nv_define_space(INDEX0, perm, 2 * sizeof(uint32_t))); > + TPM_CHECK(tpm_nv_define_space(INDEX0, perm, sizeof(uint32_t))); > + perm = TPM_NV_PER_PPWRITE; > + TPM_CHECK(tpm_nv_define_space(INDEX1, perm, 2 * sizeof(uint32_t))); > + TPM_CHECK(tpm_nv_define_space(INDEX1, perm, sizeof(uint32_t))); > + > + /* Sets the global lock */ > + tpm_set_global_lock(); > + > + /* Verifies that index0 cannot be redefined */ > + result = tpm_nv_define_space(INDEX0, perm, sizeof(uint32_t)); > + assert(result == TPM_AREA_LOCKED); > + > + /* Checks that index1 can */ > + TPM_CHECK(tpm_nv_define_space(INDEX1, perm, 2 * sizeof(uint32_t))); > + TPM_CHECK(tpm_nv_define_space(INDEX1, perm, sizeof(uint32_t))); > + > + /* Turns off PP */ > + tpm_tsc_physical_presence(PHYS_PRESENCE); > + > + /* Verifies that neither index0 nor index1 can be redefined */ > + result = tpm_nv_define_space(INDEX0, perm, sizeof(uint32_t)); > + assert(result == TPM_BAD_PRESENCE); > + result = tpm_nv_define_space(INDEX1, perm, sizeof(uint32_t)); > + assert(result == TPM_BAD_PRESENCE); > + > + printf("done\n"); > + return 0; > +} > + > +#define PERMPPGL (TPM_NV_PER_PPWRITE | TPM_NV_PER_GLOBALLOCK) > +#define PERMPP TPM_NV_PER_PPWRITE > + > +static int test_space_perm(void) > +{ > + uint32_t perm; > + > + printf("Testing spaceperm ..."); > + tpm_init(); > + TPM_CHECK(TlclStartupIfNeeded()); > + TPM_CHECK(tpm_continue_self_test()); > + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); > + TPM_CHECK(tpm_get_permissions(INDEX0, &perm)); > + assert((perm & PERMPPGL) == PERMPPGL); > + TPM_CHECK(tpm_get_permissions(INDEX1, &perm)); > + assert((perm & PERMPP) == PERMPP); > + printf("done\n"); > + return 0; > +} > + > +static int test_startup(void) > +{ > + uint32_t result; > + printf("Testing startup ...\n"); > + > + tpm_init(); > + result = tpm_startup(TPM_ST_CLEAR); > + if (result != 0 && result != TPM_INVALID_POSTINIT) > + printf("\ttpm startup failed with 0x%x\n", result); > + result = tpm_get_flags(NULL, NULL, NULL); > + if (result != 0) > + printf("\ttpm getflags failed with 0x%x\n", result); > + printf("\texecuting SelfTestFull\n"); > + tpm_self_test_full(); > + result = tpm_get_flags(NULL, NULL, NULL); > + if (result != 0) > + printf("\ttpm getflags failed with 0x%x\n", result); > + printf("\tdone\n"); > + return 0; > +} > + > +/* > + * Runs [op] and ensures it returns success and doesn't run longer than > + * [time_limit] in milliseconds. > + */ > +#define TTPM_CHECK(op, time_limit) do { \ > + ulong start, time; \ > + uint32_t __result; \ > + \ > + start = get_timer(0); \ > + __result = op; \ > + if (__result != TPM_SUCCESS) { \ > + printf("\t" #op ": error 0x%x\n", __result); \ > + return -1; \ > + } \ > + time = get_timer(start); \ > + printf("\t" #op ": %lu ms\n", time); \ > + if (time > (ulong)time_limit) { \ > + printf("\t" #op " exceeded " #time_limit " ms\n"); \ > + } \ > +} while (0) > + > + > +static int test_timing(void) > +{ > + uint32_t x; > + uint8_t in[20], out[20]; > + > + printf("Testing timing ..."); > + tpm_init(); > + TTPM_CHECK(TlclStartupIfNeeded(), 50); > + TTPM_CHECK(tpm_continue_self_test(), 100); > + TTPM_CHECK(tpm_self_test_full(), 1000); > + TTPM_CHECK(tpm_tsc_physical_presence(PRESENCE), 100); > + TTPM_CHECK(tpm_nv_write_value(INDEX0, (uint8_t *)&x, sizeof(x)), 100); > + TTPM_CHECK(tpm_nv_read_value(INDEX0, (uint8_t *)&x, sizeof(x)), 100); > + TTPM_CHECK(tpm_extend(0, in, out), 200); > + TTPM_CHECK(tpm_set_global_lock(), 50); > + TTPM_CHECK(tpm_tsc_physical_presence(PHYS_PRESENCE), 100); > + printf("done\n"); > + return 0; > +} > + > +#define TPM_MAX_NV_WRITES_NOOWNER 64 > + > +static int test_write_limit(void) > +{ > + printf("Testing writelimit ...\n"); > + int i; > + uint32_t result; > + > + tpm_init(); > + TPM_CHECK(TlclStartupIfNeeded()); > + TPM_CHECK(tpm_self_test_full()); > + TPM_CHECK(tpm_tsc_physical_presence(PRESENCE)); > + TPM_CHECK(tpm_force_clear()); > + TPM_CHECK(tpm_physical_enable()); > + TPM_CHECK(tpm_physical_set_deactivated(0)); > + > + for (i = 0; i < TPM_MAX_NV_WRITES_NOOWNER + 2; i++) { > + printf("\twriting %d\n", i); > + result = tpm_nv_write_value(INDEX0, (uint8_t *)&i, sizeof(i)); > + switch (result) { > + case TPM_SUCCESS: > + break; > + case TPM_MAXNVWRITES: > + assert(i >= TPM_MAX_NV_WRITES_NOOWNER); > + default: > + error("\tunexpected error code %d (0x%x)\n", > + result, result); > + } > + } > + > + /* Reset write count */ > + TPM_CHECK(tpm_force_clear()); > + TPM_CHECK(tpm_physical_enable()); > + TPM_CHECK(tpm_physical_set_deactivated(0)); > + > + /* Try writing again. */ > + TPM_CHECK(tpm_nv_write_value(INDEX0, (uint8_t *)&i, sizeof(i))); > + printf("\tdone\n"); > + return 0; > +} > + > +#define VOIDTEST(XFUNC) \ > + int do_test_##XFUNC(cmd_tbl_t *cmd_tbl, int flag, int argc, \ > + char * const argv[]) \ > + { \ > + return test_##XFUNC(); \ > + } > + > +#define VOIDENT(XNAME) \ > + U_BOOT_CMD_MKENT(XNAME, 0, 1, do_test_##XNAME, "", ""), > + > +VOIDTEST(early_extend) > +VOIDTEST(early_nvram) > +VOIDTEST(early_nvram2) > +VOIDTEST(enable) > +VOIDTEST(fast_enable) > +VOIDTEST(global_lock) > +VOIDTEST(lock) > +VOIDTEST(readonly) > +VOIDTEST(redefine_unowned) > +VOIDTEST(space_perm) > +VOIDTEST(startup) > +VOIDTEST(timing) > +VOIDTEST(write_limit) > +VOIDTEST(timer) > + > +static cmd_tbl_t cmd_cros_tpm_sub[] = { > + VOIDENT(early_extend) > + VOIDENT(early_nvram) > + VOIDENT(early_nvram2) > + VOIDENT(enable) > + VOIDENT(fast_enable) > + VOIDENT(global_lock) > + VOIDENT(lock) > + VOIDENT(readonly) > + VOIDENT(redefine_unowned) > + VOIDENT(space_perm) > + VOIDENT(startup) > + VOIDENT(timing) > + VOIDENT(write_limit) > + VOIDENT(timer) > +}; > + > +static int do_tpmtest(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) > +{ > + cmd_tbl_t *c; > + > + printf("argc = %d, argv = ", argc); > + do { > + int i = 0; > + > + for (i = 0; i < argc; i++) > + printf(" %s", argv[i]); > + printf("\n------\n"); > + } while (0); > + argc--; > + argv++; > + c = find_cmd_tbl(argv[0], cmd_cros_tpm_sub, > + ARRAY_SIZE(cmd_cros_tpm_sub)); > + return c ? c->cmd(cmdtp, flag, argc, argv) : cmd_usage(cmdtp); > +} > + > +U_BOOT_CMD(tpmtest, 2, 1, do_tpmtest, "TPM tests", > + "\n\tearly_extend\n" > + "\tearly_nvram\n" > + "\tearly_nvram2\n" > + "\tenable\n" > + "\tfast_enable\n" > + "\tglobal_lock\n" > + "\tlock\n" > + "\treadonly\n" > + "\tredefine_unowned\n" > + "\tspace_perm\n" > + "\tstartup\n" > + "\ttiming\n" > + "\twrite_limit\n"); > +