// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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(¤t->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; }