blob: d384c66b6a79f24f6720ae2a342f2f350c8d33f7 [file] [log] [blame]
/* Copyright 2024 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "comm-host.h"
#include "ectool.h"
#include "misc_util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <endian.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
/* clang-format off */
const char cmd_pdc_trace_usage[] =
"\n\tCollect USB PDC messages\n"
"\t-h Usage help\n"
"\t-p <port> collect on USB-C port <port>|all|none|on|off "
"(default all)\n"
"\t-s send to stdout (default if no other destination)\n"
"\t-w <file> write to <file>";
/* clang-format on */
static void walk_entries(const uint8_t *data, size_t data_size,
bool with_stdout);
static FILE *pcap = NULL;
int cmd_pdc_trace(int argc, char *argv[])
{
struct ec_params_pdc_trace_msg_enable ep;
struct ec_response_pdc_trace_msg_enable er;
struct ec_response_pdc_trace_msg_get_entries *gr =
(ec_response_pdc_trace_msg_get_entries *)ec_inbuf;
int rv;
bool h_flag = false;
const char *p_flag = NULL;
bool s_flag = false;
const char *w_flag = NULL;
/*
* output traces to stdout unless another output destination
* has been requested.
*/
bool with_stdout = true;
int pdc_port = EC_PDC_TRACE_MSG_PORT_ALL;
int c;
optind = 0; /* reset previous getopt */
while ((c = getopt(argc, argv, "hp:sw:")) != -1) {
switch (c) {
case 'h':
h_flag = true;
break;
case 'p':
p_flag = optarg;
break;
case 's':
s_flag = true;
break;
case 'w':
w_flag = optarg;
with_stdout = false;
break;
default:
/* unexpected option */
return -1;
}
}
if (h_flag || optind != argc) {
fprintf(stderr, "Usage:%s\n", cmd_pdc_trace_usage);
return -1;
}
if (p_flag != NULL) {
if (strcmp(p_flag, "all") == 0 || strcmp(p_flag, "on") == 0) {
pdc_port = EC_PDC_TRACE_MSG_PORT_ALL;
} else if (strcmp(p_flag, "none") == 0 ||
strcmp(p_flag, "off") == 0) {
pdc_port = EC_PDC_TRACE_MSG_PORT_NONE;
} else {
char *s;
pdc_port = strtol(p_flag, &s, 0);
if ((s && *s != '\0') ||
(pdc_port < 0 || pdc_port > UINT8_MAX ||
pdc_port == EC_PDC_TRACE_MSG_PORT_ALL ||
pdc_port == EC_PDC_TRACE_MSG_PORT_NONE)) {
fprintf(stderr, "Bad port number: %s\n",
p_flag);
return -1;
}
}
}
if (pdc_port == EC_PDC_TRACE_MSG_PORT_NONE) {
ep.port = pdc_port;
rv = ec_command(EC_CMD_PDC_TRACE_MSG_ENABLE, 0, &ep, sizeof(ep),
&er, sizeof(er));
if (rv < 0)
return rv;
return 0;
}
if (w_flag != NULL) {
pcap = pdc_pcap_open(w_flag);
if (pcap == NULL)
return -1;
}
ep.port = pdc_port;
rv = ec_command(EC_CMD_PDC_TRACE_MSG_ENABLE, 0, &ep, sizeof(ep), &er,
sizeof(er));
if (rv < 0) {
pdc_pcap_close(pcap);
return rv;
}
if (pdc_port == EC_PDC_TRACE_MSG_PORT_ALL) {
printf("tracing all ports\n");
} else {
printf("tracing port C%u\n", pdc_port);
}
if (s_flag) {
with_stdout = true;
}
while (1) {
size_t payload_size;
rv = ec_command(EC_CMD_PDC_TRACE_MSG_GET_ENTRIES, 0, NULL, 0,
gr, ec_max_insize);
if (rv < 0)
break;
payload_size = gr->pl_size;
if (payload_size == 0) {
if (pcap != NULL)
fflush(pcap);
usleep(100 * 1000); /* 100 ms */
continue;
}
walk_entries(gr->payload, payload_size, with_stdout);
}
pdc_pcap_close(pcap);
/*
* Turn off tracing.
*/
ep.port = EC_PDC_TRACE_MSG_PORT_NONE;
rv = ec_command(EC_CMD_PDC_TRACE_MSG_ENABLE, 0, &ep, sizeof(ep), &er,
sizeof(er));
if (rv < 0)
return rv;
return rv;
}
/*
* format pcap entry
*/
/*
* PDC messages get a 5 byte header to provide additional context when
* decoding:
*
* byte 0: trace message sequence number
* byte 2: the Type-C port number
* byte 3: the direction of message (EC-RX vs. EC-TX)
* byte 4: message type for PDC chip type specific decoding
*
* This is essentially pdc_trace_msg_entry without the timestamp
* since PCAP entries have their own timestamp field.
*/
struct pcap_pdc_trace_msg_header {
uint16_t seq_num;
uint8_t port_num;
uint8_t direction;
uint8_t msg_type;
} __packed;
BUILD_ASSERT(sizeof(struct pcap_pdc_trace_msg_header) == 5);
static size_t trace_to_pcap(uint8_t *pcap_buf, size_t pcap_buf_size,
const struct pdc_trace_msg_entry *e)
{
size_t count;
const struct pcap_pdc_trace_msg_header th = {
.seq_num = e->seq_num,
.port_num = e->port_num,
.direction = e->direction,
.msg_type = e->msg_type,
};
count = MIN(e->pdc_data_size, pcap_buf_size - sizeof(th));
memcpy(pcap_buf, &th, sizeof(th));
memcpy(&pcap_buf[sizeof(th)], e->pdc_data, count);
return sizeof(th) + count;
}
/*
* Walk the sequence of trace entries returned by the EC and send them
* to all requested destinations.
*/
static void walk_entries(const uint8_t *const data, size_t data_size,
bool with_stdout)
{
uint8_t pcap_buf[500];
const struct pdc_trace_msg_entry *e;
size_t consumed_bytes = 0;
for (;;) {
size_t e_size;
if (consumed_bytes >= data_size)
break;
e = (struct pdc_trace_msg_entry *)(data + consumed_bytes);
e_size = sizeof(*e);
if (consumed_bytes + e_size > data_size) {
fprintf(stderr,
"entry header out of bounds (%zu+%zu) > %zu\n",
consumed_bytes, e_size, data_size);
break;
}
e_size += e->pdc_data_size;
if (consumed_bytes + e_size > data_size) {
fprintf(stderr, "entry out of bounds (%zu+%zu) > %zu\n",
consumed_bytes, e_size, data_size);
break;
}
if (with_stdout) {
printf("SEQ:%04x PORT:%u %s {\nbytes %u:", e->seq_num,
e->port_num, e->direction ? "OUT" : "IN",
e->pdc_data_size);
for (int i = 0; i < e->pdc_data_size; ++i)
printf(" %02x", e->pdc_data[i]);
printf("\n}\n");
}
if (pcap != NULL) {
size_t cc =
trace_to_pcap(pcap_buf, sizeof(pcap_buf), e);
if (pcap != NULL) {
struct timeval tv;
tv.tv_sec = e->time32_us / 1000000;
tv.tv_usec = e->time32_us % 1000000;
pdc_pcap_append(pcap, tv, pcap_buf, cc);
}
}
consumed_bytes += e_size;
}
}