blob: f54dfa08130da4b9bdd8a0b6cd75064df3354fea [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/*
* Dump the content of an OPAL trace
*
* Copyright 2013-2019 IBM Corp.
*/
#include <trace.h>
#include <err.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include "../../ccan/endian/endian.h"
#include "../../ccan/short_types/short_types.h"
#include "../../ccan/heap/heap.h"
#include "trace.h"
struct trace_entry {
int index;
union trace t;
struct list_node link;
};
static int follow;
static long poll_msecs;
static void *ezalloc(size_t size)
{
void *p;
p = calloc(size, 1);
if (!p)
err(1, "Allocating memory");
return p;
}
#define TB_HZ 512000000ul
static void display_header(const struct trace_hdr *h)
{
static u64 prev_ts;
u64 ts = be64_to_cpu(h->timestamp);
printf("[%5lu.%09lu,%d] (+%8lx) [%03x] : ",
ts / TB_HZ, /* match the usual skiboot log header */
ts % TB_HZ,
h->type, /* hey why not */
prev_ts ? (ts - prev_ts) % TB_HZ : 0, be16_to_cpu(h->cpu));
prev_ts = ts;
}
static void dump_fsp_event(struct trace_fsp_event *t)
{
printf("FSP_EVT [st=%d] ", be16_to_cpu(t->fsp_state));
switch(be16_to_cpu(t->event)) {
case TRACE_FSP_EVT_LINK_DOWN:
printf("LINK DOWN");
break;
case TRACE_FSP_EVT_DISR_CHG:
printf("DISR CHANGE (0x%08x)", be32_to_cpu(t->data[0]));
break;
case TRACE_FSP_EVT_SOFT_RR:
printf("SOFT R&R (DISR=0x%08x)", be32_to_cpu(t->data[0]));
break;
case TRACE_FSP_EVT_RR_COMPL:
printf("R&R COMPLETE");
break;
case TRACE_FSP_EVT_HDES_CHG:
printf("HDES CHANGE (0x%08x)", be32_to_cpu(t->data[0]));
break;
case TRACE_FSP_EVT_POLL_IRQ:
printf("%s HDIR=%08x CTL=%08x PSI_IRQ=%d",
t->data[0] ? "IRQ " : "POLL", be32_to_cpu(t->data[1]),
be32_to_cpu(t->data[2]), be32_to_cpu(t->data[3]));
break;
default:
printf("Unknown %d (d: %08x %08x %08x %08x)",
be16_to_cpu(t->event), be32_to_cpu(t->data[0]),
be32_to_cpu(t->data[1]), be32_to_cpu(t->data[2]),
be32_to_cpu(t->data[3]));
}
printf("\n");
}
static void dump_opal_call(struct trace_opal *t)
{
unsigned int i, n;
printf("OPAL CALL %"PRIu64, be64_to_cpu(t->token));
printf(" LR=0x%016"PRIx64" SP=0x%016"PRIx64,
be64_to_cpu(t->lr), be64_to_cpu(t->sp));
n = (t->hdr.len_div_8 * 8 - offsetof(union trace, opal.r3_to_11))
/ sizeof(u64);
for (i = 0; i < n; i++)
printf(" R%u=0x%016"PRIx64,
i+3, be64_to_cpu(t->r3_to_11[i]));
printf("\n");
}
static void dump_fsp_msg(struct trace_fsp_msg *t)
{
unsigned int i;
printf("FSP_MSG: CMD %u SEQ %u MOD %u SUB %u DLEN %u %s [",
be32_to_cpu(t->word0) & 0xFFFF,
be32_to_cpu(t->word0) >> 16,
be32_to_cpu(t->word1) >> 8,
be32_to_cpu(t->word1) & 0xFF,
t->dlen,
t->dir == TRACE_FSP_MSG_IN ? "IN" :
(t->dir == TRACE_FSP_MSG_OUT ? "OUT" : "UNKNOWN"));
for (i = 0; i < t->dlen; i++)
printf("%s%02x", i ? " " : "", t->data[i]);
printf("]\n");
}
static void dump_uart(struct trace_uart *t)
{
switch(t->ctx) {
case TRACE_UART_CTX_IRQ:
printf(": IRQ IRQEN=%d IN_CNT=%d\n",
!t->irq_state, be16_to_cpu(t->in_count));
break;
case TRACE_UART_CTX_POLL:
printf(": POLL IRQEN=%d IN_CNT=%d\n",
!t->irq_state, be16_to_cpu(t->in_count));
break;
case TRACE_UART_CTX_READ:
printf(": READ IRQEN=%d IN_CNT=%d READ=%d\n",
!t->irq_state, be16_to_cpu(t->in_count), t->cnt);
break;
default:
printf(": ???? IRQEN=%d IN_CNT=%d\n",
!t->irq_state, be16_to_cpu(t->in_count));
break;
}
}
static void dump_i2c(struct trace_i2c *t)
{
uint16_t type = be16_to_cpu(t->type);
printf("I2C: bus: %d dev: %02x len: %x ",
be16_to_cpu(t->bus),
be16_to_cpu(t->i2c_addr),
be16_to_cpu(t->size)
);
switch (type & 0x3) {
case 0:
printf("read");
break;
case 1:
printf("write");
break;
case 2:
printf("smbus read from %x", be16_to_cpu(t->smbus_reg));
break;
case 3:
printf("smbus write to %x", be16_to_cpu(t->smbus_reg));
break;
default:
printf("u wot?");
}
printf(", rc = %hd\n", (int16_t) be16_to_cpu(t->rc));
}
static void load_traces(struct trace_reader *trs, int count)
{
struct trace_entry *te;
union trace t;
int i;
for (i = 0; i < count; i++) {
while (trace_get(&t, &trs[i])) {
te = ezalloc(sizeof(struct trace_entry));
memcpy(&te->t, &t, sizeof(union trace));
te->index = i;
list_add_tail(&trs[i].traces, &te->link);
}
}
}
static void print_trace(union trace *t)
{
display_header(&t->hdr);
switch (t->hdr.type) {
case TRACE_REPEAT:
printf("REPEATS: %u times\n",
be16_to_cpu(t->repeat.num));
break;
case TRACE_OVERFLOW:
printf("**OVERFLOW**: %"PRIu64" bytes missed\n",
be64_to_cpu(t->overflow.bytes_missed));
break;
case TRACE_OPAL:
dump_opal_call(&t->opal);
break;
case TRACE_FSP_MSG:
dump_fsp_msg(&t->fsp_msg);
break;
case TRACE_FSP_EVENT:
dump_fsp_event(&t->fsp_evt);
break;
case TRACE_UART:
dump_uart(&t->uart);
break;
case TRACE_I2C:
dump_i2c(&t->i2c);
break;
default:
printf("UNKNOWN(%u) CPU %u length %u\n",
t->hdr.type, be16_to_cpu(t->hdr.cpu),
t->hdr.len_div_8 * 8);
}
}
/* Gives a min heap */
bool earlier_entry(const void *va, const void *vb)
{
struct trace_entry *a, *b;
a = (struct trace_entry *) va;
b = (struct trace_entry *) vb;
if (!a)
return false;
if (!b)
return true;
return be64_to_cpu(a->t.hdr.timestamp) < be64_to_cpu(b->t.hdr.timestamp);
}
static void display_traces(struct trace_reader *trs, int count)
{
struct trace_entry *current, *next;
struct heap *h;
int i;
h = heap_init(earlier_entry);
if (!h)
err(1, "Allocating memory");
for (i = 0; i < count; i++) {
current = list_pop(&trs[i].traces, struct trace_entry, link);
/* no need to add empty ones */
if (current)
heap_push(h, current);
}
while (h->len) {
current = heap_pop(h);
if (!current)
break;
print_trace(&current->t);
next = list_pop(&trs[current->index].traces, struct trace_entry,
link);
heap_push(h, next);
free(current);
}
heap_free(h);
}
/* Can't poll for 0 msec, so use 0 to signify failure */
static long get_mseconds(char *s)
{
char *end;
long ms;
errno = 0;
ms = strtol(s, &end, 10);
if (errno || *end || ms < 0)
return 0;
return ms;
}
static void usage(void)
{
errx(1, "Usage: dump_trace [-f [-s msecs]] file...");
}
int main(int argc, char *argv[])
{
struct trace_reader *trs;
struct trace_info *ti;
bool no_mmap = false;
struct stat sb;
int fd, opt, i;
poll_msecs = 1000;
while ((opt = getopt(argc, argv, "fs:")) != -1) {
switch (opt) {
case 'f':
follow++;
break;
case 's':
poll_msecs = get_mseconds(optarg);
if (follow && poll_msecs)
break;
/* fallthru */
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 1)
usage();
trs = ezalloc(sizeof(struct trace_reader) * argc);
for (i = 0; i < argc; i++) {
fd = open(argv[i], O_RDONLY);
if (fd < 0)
err(1, "Opening %s", argv[i]);
if (fstat(fd, &sb) < 0)
err(1, "Stating %s", argv[1]);
ti = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ti == MAP_FAILED) {
no_mmap = true;
ti = ezalloc(sb.st_size);
if (!ti)
err(1, "allocating memory for %s", argv[i]);
if (read(fd, ti, sb.st_size) == -1)
err(1, "reading from %s", argv[i]);
}
trs[i].tb = &ti->tb;
list_head_init(&trs[i].traces);
}
if (no_mmap) {
fprintf(stderr, "disabling follow mode: can't mmap() OPAL export files\n");
follow = 0;
}
do {
load_traces(trs, argc);
display_traces(trs, argc);
if (follow)
usleep(poll_msecs * 1000);
} while (follow);
return 0;
}