diff --git a/examples/BUILD b/examples/BUILD index b243424..4ba5823 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -56,6 +56,8 @@ cc_binary( "htool_constants.h", "htool_i2c.c", "htool_i2c.h", + "htool_jtag.h", + "htool_jtag.c", "htool_mtd.c", "htool_panic.c", "htool_panic.h", diff --git a/examples/host_commands.h b/examples/host_commands.h index 5bbd8af..5d5b3dc 100644 --- a/examples/host_commands.h +++ b/examples/host_commands.h @@ -718,6 +718,45 @@ struct ec_response_target_control { uint16_t status; } __attribute__((packed, aligned(4))); +#define EC_PRV_CMD_HOTH_JTAG_OPERATION (0x0048) +// Amount of bytes to send and receive for testing JTAG device in BYPASS mode +#define EC_JTAG_TEST_BYPASS_PATTERN_LEN (64) + +enum ec_jtag_operation { + EC_JTAG_OP_UNDEFINED = 0, + EC_JTAG_OP_READ_IDCODE = 1, + EC_JTAG_OP_TEST_BYPASS = 2, +}; + +struct ec_request_jtag_operation { + // Integer divisor for JTAG clock. Clock frequency used is ~ + // `(48/(clk_idiv+1))` MHz. + // This can be used to limit the max JTAG peripheral clock frequency - higher + // `clk_idiv` => lower the clock frequency. + uint16_t clk_idiv; + uint8_t operation; // `enum ec_jtag_operation` + uint8_t reserved0; // pad to 4-byte boundary + // Request data (if present) follows. See `struct + // ec_request_jtag__operation` +} __attribute__((packed, aligned(4))); + +struct ec_request_jtag_test_bypass_operation { + // Test pattern to send over TDI with `EC_JTAG_OP_TEST_BYPASS` + uint8_t tdi_pattern[EC_JTAG_TEST_BYPASS_PATTERN_LEN]; +}; + +// Separate response structures for each operation. Naming convention: `struct +// ec_response_jtag__operation` + +struct ec_response_jtag_read_idcode_operation { + uint32_t idcode; +} __attribute__((packed, aligned(4))); + +struct ec_response_jtag_test_bypass_operation { + // Pattern captured over TDO with `EC_JTAG_OP_TEST_BYPASS` + uint8_t tdo_pattern[EC_JTAG_TEST_BYPASS_PATTERN_LEN]; +} __attribute__((packed, aligned(4))); + #ifdef __cplusplus } #endif diff --git a/examples/htool.c b/examples/htool.c index fd0a870..7b0902a 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -35,6 +35,7 @@ #include "htool_cmd.h" #include "htool_console.h" #include "htool_i2c.h" +#include "htool_jtag.h" #include "htool_panic.h" #include "htool_payload.h" #include "htool_payload_update.h" @@ -962,6 +963,48 @@ static const struct htool_cmd CMDS[] = { .params = (const struct htool_param[]){{}}, .func = command_raw_host_command, }, + { + .verbs = (const char*[]){"jtag", JTAG_READ_IDCODE_CMD_STR, NULL}, + .desc = "Read IDCODE for a device over JTAG. Assumes only a single " + "device in chain", + .params = + (const struct htool_param[]){ + {.type = HTOOL_FLAG_VALUE, + .ch = 'd', + .name = "clk_idiv", + .default_value = "47", + .desc = "Divisor to use for JTAG clock (TCK). A value of `n` " + "sets the max clock rate to `(48/(n+1))` MHz. Default " + "value of 47 sets the clock frequency to 1MHz"}, + {}}, + .func = command_jtag_operation_run, + }, + { + .verbs = (const char*[]){"jtag", JTAG_TEST_BYPASS_CMD_STR, NULL}, + .desc = "Send test pattern of 64 bytes to JTAG device in BYPASS mode. " + "Assumes only a single device in chain", + .params = + (const struct htool_param[]){ + {.type = HTOOL_FLAG_VALUE, + .ch = 'd', + .name = "clk_idiv", + .default_value = "47", + .desc = "Divisor to use for JTAG clock (TCK). A value of `n` " + "sets the max clock frequency to `(48/(n+1))` MHz. " + "Default value of 47 sets the clock frequency to " + "1MHz"}, + // Default value for `tdi_bytes` is defined where function + // stored in `func` is defined + {.type = HTOOL_POSITIONAL, + .name = "tdi_bytes", + // Empty string used as placeholder to detect when no + // value was provided at command line + .default_value = "", + .desc = "64 bytes (space separated) to send over TDI " + "when the JTAG device is in BYPASS mode"}, + {}}, + .func = command_jtag_operation_run, + }, {}, }; diff --git a/examples/htool_jtag.c b/examples/htool_jtag.c new file mode 100644 index 0000000..ad60ebd --- /dev/null +++ b/examples/htool_jtag.c @@ -0,0 +1,207 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "htool_jtag.h" + +#include +#include +#include +#include + +#include "host_commands.h" +#include "htool.h" +#include "htool_cmd.h" + +char JTAG_TEST_BYPASS_PATTERN_DEFAULT_VALUE[] = + // PRBS9 with '0' bit added at the beginning to make it exactly 64 bytes + "0x42 0x30 0x9c 0xab 0xd 0xe9 0xb9 0x14 0x2b 0x4f 0xd9 0x25 0xbf 0x26 0xa6 " + "0x60 0x31 0x94 0x69 0x7f 0x45 0x8e 0xb2 0xcf 0x1f 0x74 0x1a 0xdb 0xb0 " + "0x5a 0xfa 0xa8 0x14 0xaf 0x2e 0xe0 0x73 0xa4 0xf5 0xd4 0x48 0x67 0xb 0xdb " + "0x34 0x3b 0xc3 0xfe 0xf 0x7c 0x5c 0xc8 0x25 0x3b 0x47 0x9f 0x36 0x2a 0x47 " + "0x1b 0x57 0x13 0x11 0x0"; + +static int jtag_read_idcode(struct libhoth_device *dev, + const struct htool_invocation *inv) { + uint32_t clk_idiv; + + if (htool_get_param_u32(inv, "clk_idiv", &clk_idiv)) { + return -1; + } + + if (clk_idiv > UINT16_MAX) { + fprintf(stderr, "Clock divisor value too large. Expected <= %u\n", + UINT16_MAX); + return -1; + } + + const struct ec_request_jtag_operation request = { + .clk_idiv = (uint16_t)clk_idiv, + .operation = EC_JTAG_OP_READ_IDCODE, + }; + + struct ec_response_jtag_read_idcode_operation response; + size_t response_length = 0; + int ret = htool_exec_hostcmd( + dev, EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_JTAG_OPERATION, + /*version=*/0, &request, sizeof(request), &response, sizeof(response), + &response_length); + + if (ret != 0) { + fprintf(stderr, "HOTH_JTAG_OPERATION error code: %d\n", ret); + return -1; + } + + if (response_length != sizeof(response)) { + fprintf(stderr, + "HOTH_JTAG_OPERATION expected exactly %zu reseponse bytes, got %zu", + sizeof(response), response_length); + return -1; + } + + printf("IDCODE: 0x%08x\n", response.idcode); + return 0; +} + +// Parse exactly `expected_bytes_count` byte tokens from `param_value` string +// into `byte_sequence`. +// If fewer than `expected_bytes_count` byte tokens are present in +// `param_value`, return code indicates error. +// If more than `expected_bytes_count` byte tokens are present in `param_value`, +// return code indicates error. +static int parse_string_param_into_byte_sequence( + char *param_value, uint8_t *const byte_sequence, + const size_t expected_bytes_count) { + char *saveptr = NULL; + char *token = strtok_r(param_value, " ", &saveptr); + for (size_t i = 0; i < expected_bytes_count; i++) { + if (token == NULL) { + fprintf(stderr, "Expected %zu bytes, found %zu\n", expected_bytes_count, + i); + return -1; + } + char *endptr = NULL; + unsigned long int parsed_value = strtoul(token, &endptr, 0); + if ((token == endptr) || (parsed_value > UINT8_MAX) || (*endptr != '\0')) { + fprintf(stderr, "Invalid byte token '%s'\n", token); + return -1; + } + byte_sequence[i] = (uint8_t)parsed_value; + token = strtok_r(NULL, " ", &saveptr); + } + + // Check the last token after parsing `expected_bytes_count` tokens + if (token != NULL) { + fprintf(stderr, + "Expected %zu bytes, but found more starting at token '%s'\n", + expected_bytes_count, token); + return -1; + } + + return 0; +} + +static int jtag_test_bypass(struct libhoth_device *dev, + const struct htool_invocation *inv) { + char *tdi_bytes = NULL; + uint32_t clk_idiv; + + if (htool_get_param_u32(inv, "clk_idiv", &clk_idiv) || + htool_get_param_string(inv, "tdi_bytes", (const char **)&tdi_bytes)) { + return -1; + } + + if (tdi_bytes[0] == '\0') { + // Empty string indicates use default value since no value was provided on + // command line + tdi_bytes = JTAG_TEST_BYPASS_PATTERN_DEFAULT_VALUE; + } + + if (clk_idiv > UINT16_MAX) { + fprintf(stderr, "Clock divisor value too large. Expected <= %u\n", + UINT16_MAX); + return -1; + } + + struct { + struct ec_request_jtag_operation operation; + struct ec_request_jtag_test_bypass_operation params; + } __attribute__((packed, aligned(4))) request = { + .operation = + { + .clk_idiv = (uint16_t)clk_idiv, + .operation = EC_JTAG_OP_TEST_BYPASS, + }, + }; + + int ret = parse_string_param_into_byte_sequence( + tdi_bytes, request.params.tdi_pattern, EC_JTAG_TEST_BYPASS_PATTERN_LEN); + if (ret != 0) { + return ret; + } + + printf("Sending TDI values: "); + for (uint8_t i = 0; i < EC_JTAG_TEST_BYPASS_PATTERN_LEN; i++) { + printf("0x%02x ", request.params.tdi_pattern[i]); + } + printf("\n"); + + struct ec_response_jtag_test_bypass_operation response; + size_t response_len = 0; + ret = htool_exec_hostcmd( + dev, EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_JTAG_OPERATION, + /*version=*/0, &request, sizeof(request), &response, sizeof(response), + &response_len); + + if (ret != 0) { + fprintf(stderr, "HOTH_JTAG_OPERATION error code: %d\n", ret); + return -1; + } + + if (response_len != sizeof(response)) { + fprintf(stderr, + "HOTH_JTAG_OPERATION expected exactly %zu response bytes, got %zu", + sizeof(response), response_len); + return -1; + } + + bool tdo_matches_tdi = true; + printf("Captured TDO values: "); + for (uint8_t i = 0; i < EC_JTAG_TEST_BYPASS_PATTERN_LEN; i++) { + if (response.tdo_pattern[i] != request.params.tdi_pattern[i]) { + tdo_matches_tdi = false; + } + printf("0x%02x ", response.tdo_pattern[i]); + } + printf("\n"); + + printf("Captured TDO bytes match sent TDI bytes? %s\n", + tdo_matches_tdi ? "YES" : "NO"); + return 0; +} + +int command_jtag_operation_run(const struct htool_invocation *inv) { + struct libhoth_device *dev = htool_libhoth_device(); + if (!dev) { + return -1; + } + + if (strncmp(inv->cmd->verbs[1], JTAG_READ_IDCODE_CMD_STR, + sizeof(JTAG_READ_IDCODE_CMD_STR)) == 0) { + return jtag_read_idcode(dev, inv); + } else if (strncmp(inv->cmd->verbs[1], JTAG_TEST_BYPASS_CMD_STR, + sizeof(JTAG_TEST_BYPASS_CMD_STR)) == 0) { + return jtag_test_bypass(dev, inv); + } + return -1; +} diff --git a/examples/htool_jtag.h b/examples/htool_jtag.h new file mode 100644 index 0000000..5485759 --- /dev/null +++ b/examples/htool_jtag.h @@ -0,0 +1,34 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBHOTH_EXAMPLES_HTOOL_JTAG_H_ +#define LIBHOTH_EXAMPLES_HTOOL_JTAG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define JTAG_READ_IDCODE_CMD_STR "read_idcode" +#define JTAG_TEST_BYPASS_CMD_STR "test_bypass" + +// Forward declaration +struct htool_invocation; + +int command_jtag_operation_run(const struct htool_invocation* inv); + +#ifdef __cplusplus +} +#endif + +#endif // LIBHOTH_EXAMPLES_HTOOL_JTAG_H_ diff --git a/examples/meson.build b/examples/meson.build index d45f4b5..3dc982b 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -28,6 +28,7 @@ executable( 'htool_console.c', 'htool_dbus.c', 'htool_i2c.c', + 'htool_jtag.c', 'htool_mtd.c', 'htool_panic.c', 'htool_payload.c',