265 lines
7.5 KiB
C
265 lines
7.5 KiB
C
// 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;
|
|
}
|
|
}
|