| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright 2014, Michael Ellerman, IBM Corp. |
| */ |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| |
| #include "trace.h" |
| |
| |
| struct trace_buffer *trace_buffer_allocate(u64 size) |
| { |
| struct trace_buffer *tb; |
| |
| if (size < sizeof(*tb)) { |
| fprintf(stderr, "Error: trace buffer too small\n"); |
| return NULL; |
| } |
| |
| tb = mmap(NULL, size, PROT_READ | PROT_WRITE, |
| MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); |
| if (tb == MAP_FAILED) { |
| perror("mmap"); |
| return NULL; |
| } |
| |
| tb->size = size; |
| tb->tail = tb->data; |
| tb->overflow = false; |
| |
| return tb; |
| } |
| |
| static bool trace_check_bounds(struct trace_buffer *tb, void *p) |
| { |
| return p < ((void *)tb + tb->size); |
| } |
| |
| static bool trace_check_alloc(struct trace_buffer *tb, void *p) |
| { |
| /* |
| * If we ever overflowed don't allow any more input. This prevents us |
| * from dropping a large item and then later logging a small one. The |
| * buffer should just stop when overflow happened, not be patchy. If |
| * you're overflowing, make your buffer bigger. |
| */ |
| if (tb->overflow) |
| return false; |
| |
| if (!trace_check_bounds(tb, p)) { |
| tb->overflow = true; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void *trace_alloc(struct trace_buffer *tb, int bytes) |
| { |
| void *p, *newtail; |
| |
| p = tb->tail; |
| newtail = tb->tail + bytes; |
| if (!trace_check_alloc(tb, newtail)) |
| return NULL; |
| |
| tb->tail = newtail; |
| |
| return p; |
| } |
| |
| static struct trace_entry *trace_alloc_entry(struct trace_buffer *tb, int payload_size) |
| { |
| struct trace_entry *e; |
| |
| e = trace_alloc(tb, sizeof(*e) + payload_size); |
| if (e) |
| e->length = payload_size; |
| |
| return e; |
| } |
| |
| int trace_log_reg(struct trace_buffer *tb, u64 reg, u64 value) |
| { |
| struct trace_entry *e; |
| u64 *p; |
| |
| e = trace_alloc_entry(tb, sizeof(reg) + sizeof(value)); |
| if (!e) |
| return -ENOSPC; |
| |
| e->type = TRACE_TYPE_REG; |
| p = (u64 *)e->data; |
| *p++ = reg; |
| *p++ = value; |
| |
| return 0; |
| } |
| |
| int trace_log_counter(struct trace_buffer *tb, u64 value) |
| { |
| struct trace_entry *e; |
| u64 *p; |
| |
| e = trace_alloc_entry(tb, sizeof(value)); |
| if (!e) |
| return -ENOSPC; |
| |
| e->type = TRACE_TYPE_COUNTER; |
| p = (u64 *)e->data; |
| *p++ = value; |
| |
| return 0; |
| } |
| |
| int trace_log_string(struct trace_buffer *tb, char *str) |
| { |
| struct trace_entry *e; |
| char *p; |
| int len; |
| |
| len = strlen(str); |
| |
| /* We NULL terminate to make printing easier */ |
| e = trace_alloc_entry(tb, len + 1); |
| if (!e) |
| return -ENOSPC; |
| |
| e->type = TRACE_TYPE_STRING; |
| p = (char *)e->data; |
| memcpy(p, str, len); |
| p += len; |
| *p = '\0'; |
| |
| return 0; |
| } |
| |
| int trace_log_indent(struct trace_buffer *tb) |
| { |
| struct trace_entry *e; |
| |
| e = trace_alloc_entry(tb, 0); |
| if (!e) |
| return -ENOSPC; |
| |
| e->type = TRACE_TYPE_INDENT; |
| |
| return 0; |
| } |
| |
| int trace_log_outdent(struct trace_buffer *tb) |
| { |
| struct trace_entry *e; |
| |
| e = trace_alloc_entry(tb, 0); |
| if (!e) |
| return -ENOSPC; |
| |
| e->type = TRACE_TYPE_OUTDENT; |
| |
| return 0; |
| } |
| |
| static void trace_print_header(int seq, int prefix) |
| { |
| printf("%*s[%d]: ", prefix, "", seq); |
| } |
| |
| static char *trace_decode_reg(int reg) |
| { |
| switch (reg) { |
| case 769: return "SPRN_MMCR2"; break; |
| case 770: return "SPRN_MMCRA"; break; |
| case 779: return "SPRN_MMCR0"; break; |
| case 804: return "SPRN_EBBHR"; break; |
| case 805: return "SPRN_EBBRR"; break; |
| case 806: return "SPRN_BESCR"; break; |
| case 800: return "SPRN_BESCRS"; break; |
| case 801: return "SPRN_BESCRSU"; break; |
| case 802: return "SPRN_BESCRR"; break; |
| case 803: return "SPRN_BESCRRU"; break; |
| case 771: return "SPRN_PMC1"; break; |
| case 772: return "SPRN_PMC2"; break; |
| case 773: return "SPRN_PMC3"; break; |
| case 774: return "SPRN_PMC4"; break; |
| case 775: return "SPRN_PMC5"; break; |
| case 776: return "SPRN_PMC6"; break; |
| case 780: return "SPRN_SIAR"; break; |
| case 781: return "SPRN_SDAR"; break; |
| case 768: return "SPRN_SIER"; break; |
| } |
| |
| return NULL; |
| } |
| |
| static void trace_print_reg(struct trace_entry *e) |
| { |
| u64 *p, *reg, *value; |
| char *name; |
| |
| p = (u64 *)e->data; |
| reg = p++; |
| value = p; |
| |
| name = trace_decode_reg(*reg); |
| if (name) |
| printf("register %-10s = 0x%016llx\n", name, *value); |
| else |
| printf("register %lld = 0x%016llx\n", *reg, *value); |
| } |
| |
| static void trace_print_counter(struct trace_entry *e) |
| { |
| u64 *value; |
| |
| value = (u64 *)e->data; |
| printf("counter = %lld\n", *value); |
| } |
| |
| static void trace_print_string(struct trace_entry *e) |
| { |
| char *str; |
| |
| str = (char *)e->data; |
| puts(str); |
| } |
| |
| #define BASE_PREFIX 2 |
| #define PREFIX_DELTA 8 |
| |
| static void trace_print_entry(struct trace_entry *e, int seq, int *prefix) |
| { |
| switch (e->type) { |
| case TRACE_TYPE_REG: |
| trace_print_header(seq, *prefix); |
| trace_print_reg(e); |
| break; |
| case TRACE_TYPE_COUNTER: |
| trace_print_header(seq, *prefix); |
| trace_print_counter(e); |
| break; |
| case TRACE_TYPE_STRING: |
| trace_print_header(seq, *prefix); |
| trace_print_string(e); |
| break; |
| case TRACE_TYPE_INDENT: |
| trace_print_header(seq, *prefix); |
| puts("{"); |
| *prefix += PREFIX_DELTA; |
| break; |
| case TRACE_TYPE_OUTDENT: |
| *prefix -= PREFIX_DELTA; |
| if (*prefix < BASE_PREFIX) |
| *prefix = BASE_PREFIX; |
| trace_print_header(seq, *prefix); |
| puts("}"); |
| break; |
| default: |
| trace_print_header(seq, *prefix); |
| printf("entry @ %p type %d\n", e, e->type); |
| break; |
| } |
| } |
| |
| void trace_buffer_print(struct trace_buffer *tb) |
| { |
| struct trace_entry *e; |
| int i, prefix; |
| void *p; |
| |
| printf("Trace buffer dump:\n"); |
| printf(" address %p \n", tb); |
| printf(" tail %p\n", tb->tail); |
| printf(" size %llu\n", tb->size); |
| printf(" overflow %s\n", tb->overflow ? "TRUE" : "false"); |
| printf(" Content:\n"); |
| |
| p = tb->data; |
| |
| i = 0; |
| prefix = BASE_PREFIX; |
| |
| while (trace_check_bounds(tb, p) && p < tb->tail) { |
| e = p; |
| |
| trace_print_entry(e, i, &prefix); |
| |
| i++; |
| p = (void *)e + sizeof(*e) + e->length; |
| } |
| } |
| |
| void trace_print_location(struct trace_buffer *tb) |
| { |
| printf("Trace buffer 0x%llx bytes @ %p\n", tb->size, tb); |
| } |