Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for reading IDCODE and testing BYPASS via JTAG #65

Merged
merged 1 commit into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading