Skip to content

Commit

Permalink
Add support for reading IDCODE and testing BYPASS via JTAG from a dev…
Browse files Browse the repository at this point in the history
…ice connected to target (#65)
  • Loading branch information
xorptr authored Jun 27, 2024
1 parent 6f22a10 commit ddb327a
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
39 changes: 39 additions & 0 deletions examples/host_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_<op>_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_<op>_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
Expand Down
43 changes: 43 additions & 0 deletions examples/htool.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
},
{},
};

Expand Down
207 changes: 207 additions & 0 deletions examples/htool_jtag.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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;
}
34 changes: 34 additions & 0 deletions examples/htool_jtag.h
Original file line number Diff line number Diff line change
@@ -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_
1 change: 1 addition & 0 deletions examples/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit ddb327a

Please sign in to comment.