diff options
Diffstat (limited to 'roms/skiboot/core/trace.c')
-rw-r--r-- | roms/skiboot/core/trace.c | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/roms/skiboot/core/trace.c b/roms/skiboot/core/trace.c new file mode 100644 index 000000000..561bd79e0 --- /dev/null +++ b/roms/skiboot/core/trace.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Trace various things into in-memory buffers + * + * Copyright 2013-2019 IBM Corp. + */ + +#include <trace.h> +#include <timebase.h> +#include <lock.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> +#include <cpu.h> +#include <device.h> +#include <libfdt.h> +#include <processor.h> +#include <skiboot.h> +#include <opal-api.h> +#include <debug_descriptor.h> +#include <nvram.h> + +#define DEBUG_TRACES + +#define MAX_SIZE sizeof(union trace) + +/* Smaller trace buffer for early booting */ +#define BOOT_TBUF_SZ 65536 +static struct { + struct trace_info trace_info; + char buf[BOOT_TBUF_SZ + MAX_SIZE]; +} boot_tracebuf __section(".data.boot_trace"); + +void init_boot_tracebuf(struct cpu_thread *boot_cpu) +{ + init_lock(&boot_tracebuf.trace_info.lock); + boot_tracebuf.trace_info.tb.buf_size = cpu_to_be64(BOOT_TBUF_SZ); + boot_tracebuf.trace_info.tb.max_size = cpu_to_be32(MAX_SIZE); + + boot_cpu->trace = &boot_tracebuf.trace_info; +} + +static size_t tracebuf_extra(void) +{ + /* We make room for the largest possible record */ + return TBUF_SZ + MAX_SIZE; +} + +/* To avoid bloating each entry, repeats are actually specific entries. + * tb->last points to the last (non-repeat) entry. */ +static bool handle_repeat(struct tracebuf *tb, const union trace *trace) +{ + struct trace_hdr *prev; + struct trace_repeat *rpt; + u32 len; + + prev = (void *)tb->buf + be64_to_cpu(tb->last) % be64_to_cpu(tb->buf_size); + + if (prev->type != trace->hdr.type + || prev->len_div_8 != trace->hdr.len_div_8 + || prev->cpu != trace->hdr.cpu) + return false; + + len = prev->len_div_8 << 3; + if (memcmp(prev + 1, &trace->hdr + 1, len - sizeof(*prev)) != 0) + return false; + + /* If they've consumed prev entry, don't repeat. */ + if (be64_to_cpu(tb->last) < be64_to_cpu(tb->start)) + return false; + + /* OK, it's a duplicate. Do we already have repeat? */ + if (be64_to_cpu(tb->last) + len != be64_to_cpu(tb->end)) { + u64 pos = be64_to_cpu(tb->last) + len; + /* FIXME: Reader is not protected from seeing this! */ + rpt = (void *)tb->buf + pos % be64_to_cpu(tb->buf_size); + assert(pos + rpt->len_div_8*8 == be64_to_cpu(tb->end)); + assert(rpt->type == TRACE_REPEAT); + + /* If this repeat entry is full, don't repeat. */ + if (be16_to_cpu(rpt->num) == 0xFFFF) + return false; + + rpt->num = cpu_to_be16(be16_to_cpu(rpt->num) + 1); + rpt->timestamp = trace->hdr.timestamp; + return true; + } + + /* + * Generate repeat entry: it's the smallest possible entry, so we + * must have eliminated old entries. + */ + assert(trace->hdr.len_div_8 * 8 >= sizeof(*rpt)); + + rpt = (void *)tb->buf + be64_to_cpu(tb->end) % be64_to_cpu(tb->buf_size); + rpt->timestamp = trace->hdr.timestamp; + rpt->type = TRACE_REPEAT; + rpt->len_div_8 = sizeof(*rpt) >> 3; + rpt->cpu = trace->hdr.cpu; + rpt->prev_len = cpu_to_be16(trace->hdr.len_div_8 << 3); + rpt->num = cpu_to_be16(1); + lwsync(); /* write barrier: complete repeat record before exposing */ + tb->end = cpu_to_be64(be64_to_cpu(tb->end) + sizeof(*rpt)); + return true; +} + +void trace_add(union trace *trace, u8 type, u16 len) +{ + struct trace_info *ti = this_cpu()->trace; + unsigned int tsz; + + trace->hdr.type = type; + trace->hdr.len_div_8 = (len + 7) >> 3; + + tsz = trace->hdr.len_div_8 << 3; + +#ifdef DEBUG_TRACES + assert(tsz >= sizeof(trace->hdr)); + assert(tsz <= sizeof(*trace)); + assert(trace->hdr.type != TRACE_REPEAT); + assert(trace->hdr.type != TRACE_OVERFLOW); +#endif + /* Skip traces not enabled in the debug descriptor */ + if (trace->hdr.type < (8 * sizeof(debug_descriptor.trace_mask)) && + !((1ul << trace->hdr.type) & be64_to_cpu(debug_descriptor.trace_mask))) + return; + + trace->hdr.timestamp = cpu_to_be64(mftb()); + trace->hdr.cpu = cpu_to_be16(this_cpu()->server_no); + + lock(&ti->lock); + + /* Throw away old entries before we overwrite them. */ + while ((be64_to_cpu(ti->tb.start) + be64_to_cpu(ti->tb.buf_size)) + < (be64_to_cpu(ti->tb.end) + tsz)) { + struct trace_hdr *hdr; + + hdr = (void *)ti->tb.buf + + be64_to_cpu(ti->tb.start) % be64_to_cpu(ti->tb.buf_size); + ti->tb.start = cpu_to_be64(be64_to_cpu(ti->tb.start) + + (hdr->len_div_8 << 3)); + } + + /* Must update ->start before we rewrite new entries. */ + lwsync(); /* write barrier */ + + /* Check for duplicates... */ + if (!handle_repeat(&ti->tb, trace)) { + /* This may go off end, and that's why ti->tb.buf is oversize */ + memcpy(ti->tb.buf + be64_to_cpu(ti->tb.end) % be64_to_cpu(ti->tb.buf_size), + trace, tsz); + ti->tb.last = ti->tb.end; + lwsync(); /* write barrier: write entry before exposing */ + ti->tb.end = cpu_to_be64(be64_to_cpu(ti->tb.end) + tsz); + } + unlock(&ti->lock); +} + +void trace_add_dt_props(void) +{ + uint64_t boot_buf_phys = (uint64_t) &boot_tracebuf.trace_info; + struct dt_node *exports, *traces; + unsigned int i; + fdt64_t *prop; + u64 tmask; + char tname[256]; + + exports = dt_find_by_path(opal_node, "firmware/exports"); + if (!exports) + return; + + /* + * nvram hack to put all the trace buffer exports in the exports + * node. This is useful if the kernel doesn't also export subnodes. + */ + if (nvram_query_safe("flat-trace-buf")) + traces = exports; + else + traces = dt_new(exports, "traces"); + + prop = malloc(sizeof(u64) * 2 * be32_to_cpu(debug_descriptor.num_traces)); + + for (i = 0; i < be32_to_cpu(debug_descriptor.num_traces); i++) { + uint64_t addr = be64_to_cpu(debug_descriptor.trace_phys[i]); + uint64_t size = be32_to_cpu(debug_descriptor.trace_size[i]); + uint32_t pir = be16_to_cpu(debug_descriptor.trace_pir[i]); + + prop[i * 2] = cpu_to_fdt64(addr); + prop[i * 2 + 1] = cpu_to_fdt64(size); + + if (addr == boot_buf_phys) + snprintf(tname, sizeof(tname), "boot-%x", pir); + else + snprintf(tname, sizeof(tname), "trace-%x", pir); + + dt_add_property_u64s(traces, tname, addr, size); + } + + dt_add_property(opal_node, "ibm,opal-traces", + prop, sizeof(u64) * 2 * i); + free(prop); + + tmask = (uint64_t)&debug_descriptor.trace_mask; + dt_add_property_u64(opal_node, "ibm,opal-trace-mask", tmask); +} + +static void trace_add_desc(struct trace_info *t, uint64_t size, uint16_t pir) +{ + unsigned int i = be32_to_cpu(debug_descriptor.num_traces); + + if (i >= DEBUG_DESC_MAX_TRACES) { + prerror("TRACE: Debug descriptor trace list full !\n"); + return; + } + + debug_descriptor.num_traces = cpu_to_be32(i + 1); + debug_descriptor.trace_phys[i] = cpu_to_be64((uint64_t)t); + debug_descriptor.trace_tce[i] = 0; /* populated later */ + debug_descriptor.trace_size[i] = cpu_to_be32(size); + debug_descriptor.trace_pir[i] = cpu_to_be16(pir); +} + +/* Allocate trace buffers once we know memory topology */ +void init_trace_buffers(void) +{ + struct cpu_thread *t; + struct trace_info *any = &boot_tracebuf.trace_info; + uint64_t size; + + /* Boot the boot trace in the debug descriptor */ + trace_add_desc(any, sizeof(boot_tracebuf), this_cpu()->pir); + + /* Allocate a trace buffer for each primary cpu. */ + for_each_cpu(t) { + if (t->is_secondary) + continue; + + /* Use a 64K alignment for TCE mapping */ + size = ALIGN_UP(sizeof(*t->trace) + tracebuf_extra(), 0x10000); + t->trace = local_alloc(t->chip_id, size, 0x10000); + if (t->trace) { + any = t->trace; + memset(t->trace, 0, size); + init_lock(&t->trace->lock); + t->trace->tb.max_size = cpu_to_be32(MAX_SIZE); + t->trace->tb.buf_size = cpu_to_be64(TBUF_SZ); + trace_add_desc(any, sizeof(t->trace->tb) + + tracebuf_extra(), t->pir); + } else + prerror("TRACE: cpu 0x%x allocation failed\n", t->pir); + } + + /* In case any allocations failed, share trace buffers. */ + for_each_cpu(t) { + if (!t->is_secondary && !t->trace) + t->trace = any; + } + + /* And copy those to the secondaries. */ + for_each_cpu(t) { + if (!t->is_secondary) + continue; + t->trace = t->primary->trace; + } +} |