blob: cca4f4c738917f6091763aca5c434ce76d2ef43c [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// See the README.md in this directory for documentation.
#include <assert.h>
#include <cpuid.h>
#include <fuchsia/hardware/cpu/insntrace/llcpp/fidl.h>
#include <inttypes.h>
#include <lib/zircon-internal/device/cpu-trace/intel-pt.h>
#include <lib/zircon-internal/mtrace.h>
#include <lib/zx/bti.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/resource.h>
#include <zircon/types.h>
#include <memory>
#include <new>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/io-buffer.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/platform/device.h>
#include <ddktl/device.h>
#include <ddktl/fidl.h>
#include <fbl/alloc_checker.h>
namespace insntrace {
namespace fuchsia_insntrace = ::llcpp::fuchsia::hardware::cpu::insntrace;
// Shorten some long fidl names.
using BufferConfig = fuchsia_insntrace::BufferConfig;
using BufferState = fuchsia_insntrace::BufferState;
// This is defined in insntrace.fidl but not emitted.
using BufferDescriptor = uint32_t;
typedef struct ipt_per_trace_state {
// the cpu or thread this buffer is assigned to
// Which value to use is determined by the trace mode.
union {
uint32_t cpu;
zx_handle_t thread;
} owner;
// number of chunks, each 2^|chunk_order| pages in size
uint32_t num_chunks;
// log2 size of each chunk, in pages
uint32_t chunk_order;
// if true then the buffer is circular, otherwise tracing stops when the
// buffer fills
bool is_circular;
// true if allocated
bool allocated;
// true if buffer is assigned to a cpu/thread
bool assigned;
// number of ToPA tables needed
uint32_t num_tables;
// msrs
uint64_t ctl;
uint64_t status;
uint64_t output_base;
uint64_t output_mask_ptrs;
uint64_t cr3_match;
struct {
uint64_t a, b;
} addr_ranges[IPT_MAX_NUM_ADDR_RANGES];
// trace buffers and ToPA tables
// ToPA: Table of Physical Addresses
// A "trace buffer" is a set of N chunks.
std::unique_ptr<io_buffer_t[]> chunks;
std::unique_ptr<io_buffer_t[]> topas;
} ipt_per_trace_state_t;
// TODO(dje): add unbindable?
class InsntraceDevice;
using DeviceType = ddk::Device<InsntraceDevice, ddk::Openable, ddk::Closable, ddk::Messageable>;
class InsntraceDevice : public DeviceType, fuchsia_insntrace::Controller::Interface {
public:
explicit InsntraceDevice(zx_device_t* parent, zx::bti bti)
: DeviceType(parent), bti_(std::move(bti)) {}
~InsntraceDevice() = default;
void DdkRelease();
// Fidl handlers
zx_status_t IptInitialize(const fuchsia_insntrace::Allocation* allocation);
zx_status_t IptTerminate();
zx_status_t IptGetAllocation(fuchsia_insntrace::Allocation* config);
zx_status_t IptAllocateBuffer(const BufferConfig* config, BufferDescriptor* out_descriptor);
zx_status_t IptAssignThreadBuffer(BufferDescriptor descriptor, zx_handle_t thread);
zx_status_t IptReleaseThreadBuffer(BufferDescriptor descriptor, zx_handle_t thread);
zx_status_t IptGetBufferConfig(BufferDescriptor descriptor, BufferConfig* out_config);
zx_status_t IptGetBufferState(BufferDescriptor descriptor, BufferState* out_state);
zx_status_t IptGetChunkHandle(BufferDescriptor descriptor, uint32_t chunk_num,
zx_handle_t* out_handle);
zx_status_t IptFreeBuffer(BufferDescriptor descriptor);
zx_status_t IptStart();
zx_status_t IptStop();
// Fidl server interface implementation
void Initialize(fuchsia_insntrace::Allocation allocation,
InitializeCompleter::Sync& completer) override;
void Terminate(TerminateCompleter::Sync& completer) override;
void GetAllocation(GetAllocationCompleter::Sync& completer) override;
void AllocateBuffer(BufferConfig config, AllocateBufferCompleter::Sync& completer) override;
void AssignThreadBuffer(BufferDescriptor descriptor, zx::thread thread,
AssignThreadBufferCompleter::Sync& completer) override;
void ReleaseThreadBuffer(BufferDescriptor descriptor, zx::thread thread,
ReleaseThreadBufferCompleter::Sync& completer) override;
void GetBufferConfig(BufferDescriptor descriptor,
GetBufferConfigCompleter::Sync& completer) override;
void GetBufferState(BufferDescriptor descriptor,
GetBufferStateCompleter::Sync& completer) override;
void GetChunkHandle(BufferDescriptor descriptor, uint32_t chunk_num,
GetChunkHandleCompleter::Sync& completer) override;
void FreeBuffer(BufferDescriptor descriptor, FreeBufferCompleter::Sync& completer) override;
void Start(StartCompleter::Sync& completer) override;
void Stop(StopCompleter::Sync& completer) override;
// Device protocol implementation
zx_status_t DdkOpen(zx_device_t** dev_out, uint32_t flags);
zx_status_t DdkClose(uint32_t flags);
zx_status_t DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn);
private:
// Low level routines
void MakeTopa(ipt_per_trace_state_t* per_trace);
uint32_t ComputeTopaEntryCount(ipt_per_trace_state_t* per_trace);
size_t ComputeCaptureSize(const ipt_per_trace_state_t* per_trace);
zx_status_t X86PtAllocBuffer1(ipt_per_trace_state_t* per_trace, uint32_t num, uint32_t order,
bool is_circular);
void X86PtFreeBuffer1(ipt_per_trace_state_t* per_trace);
zx_status_t X86PtAllocBuffer(const BufferConfig* config, BufferDescriptor* out_descriptor);
zx_status_t X86PtAssignThreadBuffer(BufferDescriptor descriptor, zx_handle_t thread);
zx_status_t X86PtReleaseThreadBuffer(BufferDescriptor descriptor, zx_handle_t thread);
zx_status_t X86PtFreeBuffer(BufferDescriptor descriptor);
zx_status_t X86PtStageTraceData(zx_handle_t resource, BufferDescriptor descriptor);
zx_status_t X86PtGetTraceData(zx_handle_t resource, BufferDescriptor descriptor);
mtx_t lock_{};
// Only one open of this device is supported at a time. KISS for now.
bool opened_ = false;
// Once tracing has started various things are not allowed until it stops.
bool active_ = false;
zx_insntrace_trace_mode_t mode_ = IPT_MODE_CPU;
// # of entries in |per_trace_state|.
// When tracing by cpu, this is the max number of cpus.
// When tracing by thread, this is the max number of threads.
// TODO(dje): Add support for dynamically growing the vector.
uint16_t num_traces_ = 0;
// one entry for each trace
std::unique_ptr<ipt_per_trace_state_t[]> per_trace_state_;
zx::bti bti_;
};
static uint32_t ipt_config_family;
static uint32_t ipt_config_model;
static uint32_t ipt_config_stepping;
static uint32_t ipt_config_addr_cfg_max = 0;
static uint32_t ipt_config_mtc_freq_mask = 0;
static uint32_t ipt_config_cyc_thresh_mask = 0;
static uint32_t ipt_config_psb_freq_mask = 0;
static uint32_t ipt_config_num_addr_ranges = 0;
static uint32_t ipt_config_bus_freq = 0;
static bool ipt_config_supported = false;
static bool ipt_config_cr3_filtering = false;
static bool ipt_config_psb = false;
static bool ipt_config_ip_filtering = false;
static bool ipt_config_mtc = false;
static bool ipt_config_ptwrite = false;
static bool ipt_config_power_events = false;
static bool ipt_config_output_topa = false;
static bool ipt_config_output_topa_multi = false;
static bool ipt_config_output_single = false;
static bool ipt_config_output_transport = false;
static bool ipt_config_lip = false;
// maximum space, in bytes, for trace buffers (per cpu)
// This isn't necessarily
// MAX_NUM_CHUNKS * (1 << (MAX_CHUNK_ORDER + PAGE_SIZE_SHIFT)).
// Buffers have to be naturally aligned contiguous pages, but we can have
// a lot of them. Supporting large buffers and/or lots of them is for
// experimentation.
#define MAX_PER_TRACE_SPACE (256 * 1024 * 1024)
// maximum number of buffers
#define MAX_NUM_CHUNKS 4096
// maximum size of each buffer, in pages (1MB)
#define MAX_CHUNK_ORDER 8
#if PAGE_SIZE == 4096
#define PAGE_SIZE_SHIFT 12
#else
#error "unsupported page size"
#endif
#define BIT(x, b) ((x) & (1u << (b)))
// The userspace side of the driver
static zx_status_t InsntraceInitOnce() {
unsigned a, b, c, d, max_leaf;
max_leaf = __get_cpuid_max(0, nullptr);
if (max_leaf < 0x14) {
zxlogf(INFO, "IntelPT: No PT support");
return ZX_ERR_NOT_SUPPORTED;
}
__cpuid(1, a, b, c, d);
ipt_config_stepping = a & 0xf;
ipt_config_model = (a >> 4) & 0xf;
ipt_config_family = (a >> 8) & 0xf;
if (ipt_config_family == 0xf)
ipt_config_family += (a >> 20) & 0xff;
if (ipt_config_family == 6 || ipt_config_family == 0xf)
ipt_config_model += ((a >> 16) & 0xf) << 4;
__cpuid_count(0x07, 0, a, b, c, d);
if (!BIT(b, 25)) {
zxlogf(INFO, "IntelPT: No PT support");
return ZX_ERR_NOT_SUPPORTED;
}
ipt_config_supported = true;
__cpuid_count(0x14, 0, a, b, c, d);
if (BIT(b, 2))
ipt_config_addr_cfg_max = 2;
if (BIT(b, 1) && a >= 1) {
unsigned a1, b1, c1, d1;
__cpuid_count(0x14, 1, a1, b1, c1, d1);
ipt_config_mtc_freq_mask = (a1 >> 16) & 0xffff;
ipt_config_cyc_thresh_mask = b1 & 0xffff;
ipt_config_psb_freq_mask = (b1 >> 16) & 0xffff;
ipt_config_num_addr_ranges = a1 & 0x7;
}
if (max_leaf >= 0x15) {
unsigned a1 = 0, b1 = 0, c1 = 0, d1 = 0;
__cpuid(0x15, a1, b1, c1, d1);
if (a1 && b1)
ipt_config_bus_freq = static_cast<uint32_t>(1. / ((float)a1 / (float)b1));
}
ipt_config_cr3_filtering = !!BIT(b, 0);
ipt_config_psb = !!BIT(b, 1);
ipt_config_ip_filtering = !!BIT(b, 2);
ipt_config_mtc = !!BIT(b, 3);
ipt_config_ptwrite = !!BIT(b, 4);
ipt_config_power_events = !!BIT(b, 5);
ipt_config_output_topa = !!BIT(c, 0);
ipt_config_output_topa_multi = !!BIT(c, 1);
ipt_config_output_single = !!BIT(c, 2);
ipt_config_output_transport = !!BIT(c, 3);
ipt_config_lip = !!BIT(c, 31);
zxlogf(INFO, "Intel Processor Trace configuration for this chipset:");
// No need to print everything, but these are useful.
zxlogf(INFO, "mtc_freq_mask: 0x%x", ipt_config_mtc_freq_mask);
zxlogf(INFO, "cyc_thresh_mask: 0x%x", ipt_config_cyc_thresh_mask);
zxlogf(INFO, "psb_freq_mask: 0x%x", ipt_config_psb_freq_mask);
zxlogf(INFO, "num addr ranges: %u", ipt_config_num_addr_ranges);
return ZX_OK;
}
// Create the ToPA for the configured number of pages for |cpu|.
// A circular collection of buffers is set up, even if we're going to apply
// the stop bit to the last entry.
void InsntraceDevice::MakeTopa(ipt_per_trace_state_t* per_trace) {
const size_t run_len_log2 = per_trace->chunk_order;
assert(run_len_log2 + PAGE_SIZE_SHIFT <= IPT_TOPA_MAX_SHIFT);
assert(run_len_log2 + PAGE_SIZE_SHIFT >= IPT_TOPA_MIN_SHIFT);
uint32_t curr_table = 0;
uint32_t curr_idx = 0;
uint64_t* last_entry = nullptr;
// Note: An early version of this patch auto-computed the desired grouping
// of pages with sufficient alignment. If you find yourself needing this
// functionality again, see change 9470.
for (uint32_t i = 0; i < per_trace->num_chunks; ++i) {
io_buffer_t* buffer = &per_trace->chunks[i];
io_buffer_t* topa = &per_trace->topas[curr_table];
zx_paddr_t pa = io_buffer_phys(buffer);
uint64_t val =
IPT_TOPA_ENTRY_PHYS_ADDR(pa) | IPT_TOPA_ENTRY_SIZE(run_len_log2 + PAGE_SIZE_SHIFT);
auto table = reinterpret_cast<uint64_t*>(io_buffer_virt(topa));
table[curr_idx] = val;
last_entry = &table[curr_idx];
// Make sure we leave one at the end of the table for the END marker.
if (unlikely(curr_idx >= IPT_TOPA_MAX_TABLE_ENTRIES - 2)) {
curr_idx = 0;
curr_table++;
} else {
curr_idx++;
}
}
assert(curr_table + 1 == per_trace->num_tables ||
// If the last table is full curr_table will be the next one.
(curr_table == per_trace->num_tables && curr_idx == 0));
// Populate END entries for completed tables
// Assume the table is circular. We'll set the stop bit on the last
// entry later.
for (uint32_t i = 0; i < curr_table; ++i) {
io_buffer_t* this_table = &per_trace->topas[i];
io_buffer_t* next_table;
if (i == per_trace->num_tables - 1) {
next_table = &per_trace->topas[0];
} else {
next_table = &per_trace->topas[i + 1];
}
zx_paddr_t next_table_pa = io_buffer_phys(next_table);
uint64_t val = IPT_TOPA_ENTRY_PHYS_ADDR(next_table_pa) | IPT_TOPA_ENTRY_END;
auto table = reinterpret_cast<uint64_t*>(io_buffer_virt(this_table));
table[IPT_TOPA_MAX_TABLE_ENTRIES - 1] = val;
}
// Populate the END entry for a possibly non-full last table
if (curr_table < per_trace->num_tables) {
io_buffer_t* this_table = &per_trace->topas[curr_table];
io_buffer_t* first_table = &per_trace->topas[0];
zx_paddr_t first_table_pa = io_buffer_phys(first_table);
uint64_t val = IPT_TOPA_ENTRY_PHYS_ADDR(first_table_pa) | IPT_TOPA_ENTRY_END;
auto table = reinterpret_cast<uint64_t*>(io_buffer_virt(this_table));
table[curr_idx] = val;
}
// Add the STOP flag to the last non-END entry in the tables
assert(last_entry);
if (!per_trace->is_circular)
*last_entry |= IPT_TOPA_ENTRY_STOP;
}
// Compute the number of ToPA entries needed for the configured number of
// buffers.
// The output count includes the END entries across all needed tables.
uint32_t InsntraceDevice::ComputeTopaEntryCount(ipt_per_trace_state_t* per_trace) {
uint32_t num_entries = per_trace->num_chunks;
uint32_t num_end_entries =
(num_entries + IPT_TOPA_MAX_TABLE_ENTRIES - 2) / (IPT_TOPA_MAX_TABLE_ENTRIES - 1);
uint32_t result = num_entries + num_end_entries;
zxlogf(TRACE, "IPT: compute_topa_entry_count: num_entries: %u", num_entries);
zxlogf(TRACE, "IPT: compute_topa_entry_count: num_end_entries: %u", num_end_entries);
zxlogf(TRACE, "IPT: compute_topa_entry_count: total entries: %u", result);
return result;
}
// Walk the tables to discover how much data has been captured for |per_trace|.
// Note: If this is a circular buffer this is just where tracing stopped.
size_t InsntraceDevice::ComputeCaptureSize(const ipt_per_trace_state_t* per_trace) {
uint64_t curr_table_paddr = per_trace->output_base;
uint32_t curr_table_entry_idx = (uint32_t)per_trace->output_mask_ptrs >> 7;
uint32_t curr_entry_offset = (uint32_t)(per_trace->output_mask_ptrs >> 32);
zxlogf(TRACE, "IPT: compute_capture_size: trace %tu", per_trace - per_trace_state_.get());
zxlogf(TRACE,
"IPT: curr_table_paddr 0x%" PRIx64 ", curr_table_entry_idx %u, curr_entry_offset %u\n",
curr_table_paddr, curr_table_entry_idx, curr_entry_offset);
size_t total_size = 0;
for (uint32_t table = 0; table < per_trace->num_tables; ++table) {
// Get the physical address so that we can compare it with the value
// in output_base.
zx_paddr_t table_paddr = io_buffer_phys(&per_trace->topas[table]);
for (uint32_t entry = 0; entry < IPT_TOPA_MAX_TABLE_ENTRIES - 1; ++entry) {
if (table_paddr == curr_table_paddr && entry >= curr_table_entry_idx) {
total_size += curr_entry_offset;
return total_size;
}
auto table_ptr = reinterpret_cast<uint64_t*>(io_buffer_virt(&per_trace->topas[table]));
uint64_t topa_entry = table_ptr[entry];
total_size += 1UL << IPT_TOPA_ENTRY_EXTRACT_SIZE(topa_entry);
}
}
// Should be unreachable.
// TODO(dje): Later flag state as broken.
zxlogf(ERROR, "IPT: unexpectedly exited capture loop");
return 0;
}
zx_status_t InsntraceDevice::X86PtAllocBuffer1(ipt_per_trace_state_t* per_trace, uint32_t num,
uint32_t order, bool is_circular) {
zx_status_t status;
size_t chunk_pages = 1 << order;
fbl::AllocChecker ac;
per_trace->chunks = std::unique_ptr<io_buffer_t[]>(new (&ac) io_buffer_t[num]{});
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
for (uint32_t i = 0; i < num; ++i) {
// ToPA entries of size N must be aligned to N, too.
uint32_t alignment_log2 = PAGE_SIZE_SHIFT + order;
status = io_buffer_init_aligned(&per_trace->chunks[i], bti_.get(), chunk_pages * PAGE_SIZE,
alignment_log2, IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK)
return status;
// Keep track of allocated buffers as we go in case we later fail:
// we want to be able to free those that got allocated.
++per_trace->num_chunks;
// Catch bugs in io_buffer_init_aligned. If it doesn't give us a
// properly aligned buffer we'll get an "operational error" later.
// See Intel Vol3 36.2.6.2.
zx_paddr_t pa = io_buffer_phys(&per_trace->chunks[i]);
zx_paddr_t align_mask = (1ull << alignment_log2) - 1;
if (pa & align_mask) {
zxlogf(ERROR, "%s: WARNING: chunk has bad alignment: alignment %u, got 0x%" PRIx64 "",
__func__, alignment_log2, pa);
return ZX_ERR_INTERNAL;
}
}
assert(per_trace->num_chunks == num);
per_trace->chunk_order = order;
per_trace->is_circular = is_circular;
// TODO(dje): No need to allocate the max on the last table.
uint32_t entry_count = ComputeTopaEntryCount(per_trace);
uint32_t table_count =
(entry_count + IPT_TOPA_MAX_TABLE_ENTRIES - 1) / IPT_TOPA_MAX_TABLE_ENTRIES;
if (entry_count < 2) {
zxlogf(INFO, "IPT: INVALID ENTRY COUNT: %u", entry_count);
return ZX_ERR_INVALID_ARGS;
}
// Some early Processor Trace implementations only supported having a
// table with a single real entry and an END.
if (!ipt_config_output_topa_multi && entry_count > 2)
return ZX_ERR_NOT_SUPPORTED;
// Allocate Table(s) of Physical Addresses (ToPA) for each cpu.
per_trace->topas = std::unique_ptr<io_buffer_t[]>(new (&ac) io_buffer_t[table_count]{});
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
for (uint32_t i = 0; i < table_count; ++i) {
status = io_buffer_init(&per_trace->topas[i], bti_.get(),
sizeof(uint64_t) * IPT_TOPA_MAX_TABLE_ENTRIES,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK)
return ZX_ERR_NO_MEMORY;
// Keep track of allocated tables as we go in case we later fail:
// we want to be able to free those that got allocated.
++per_trace->num_tables;
}
assert(per_trace->num_tables == table_count);
MakeTopa(per_trace);
return ZX_OK;
}
void InsntraceDevice::X86PtFreeBuffer1(ipt_per_trace_state_t* per_trace) {
assert(!per_trace->assigned);
if (per_trace->chunks) {
for (uint32_t i = 0; i < per_trace->num_chunks; ++i) {
io_buffer_release(&per_trace->chunks[i]);
}
}
per_trace->chunks.reset();
if (per_trace->topas) {
for (uint32_t i = 0; i < per_trace->num_tables; ++i) {
io_buffer_release(&per_trace->topas[i]);
}
}
per_trace->topas.reset();
per_trace->allocated = false;
}
zx_status_t InsntraceDevice::X86PtAllocBuffer(const BufferConfig* config,
BufferDescriptor* out_descriptor) {
zxlogf(TRACE, "%s: num_chunks %u, chunk_order %u", __func__, config->num_chunks,
config->chunk_order);
if (config->num_chunks == 0 || config->num_chunks > MAX_NUM_CHUNKS)
return ZX_ERR_INVALID_ARGS;
if (config->chunk_order > MAX_CHUNK_ORDER)
return ZX_ERR_INVALID_ARGS;
size_t chunk_pages = 1 << config->chunk_order;
size_t nr_pages = config->num_chunks * chunk_pages;
size_t total_per_trace = nr_pages * PAGE_SIZE;
if (total_per_trace > MAX_PER_TRACE_SPACE)
return ZX_ERR_INVALID_ARGS;
uint64_t settable_ctl_mask =
(IPT_CTL_OS_ALLOWED_MASK | IPT_CTL_USER_ALLOWED_MASK | IPT_CTL_TSC_EN_MASK |
IPT_CTL_DIS_RETC_MASK | IPT_CTL_BRANCH_EN_MASK);
if (ipt_config_ptwrite)
settable_ctl_mask |= IPT_CTL_PTW_EN_MASK | IPT_CTL_FUP_ON_PTW_MASK;
if (ipt_config_cr3_filtering)
settable_ctl_mask |= IPT_CTL_CR3_FILTER_MASK;
if (ipt_config_mtc)
settable_ctl_mask |= IPT_CTL_MTC_EN_MASK | IPT_CTL_MTC_FREQ_MASK;
if (ipt_config_power_events)
settable_ctl_mask |= IPT_CTL_POWER_EVENT_EN_MASK;
if (ipt_config_ip_filtering) {
if (ipt_config_num_addr_ranges >= 1)
settable_ctl_mask |= IPT_CTL_ADDR0_MASK;
if (ipt_config_num_addr_ranges >= 2)
settable_ctl_mask |= IPT_CTL_ADDR1_MASK;
if (ipt_config_num_addr_ranges >= 3)
settable_ctl_mask |= IPT_CTL_ADDR2_MASK;
if (ipt_config_num_addr_ranges >= 4)
settable_ctl_mask |= IPT_CTL_ADDR3_MASK;
}
if (ipt_config_psb)
settable_ctl_mask |= (IPT_CTL_CYC_EN_MASK | IPT_CTL_PSB_FREQ_MASK | IPT_CTL_CYC_THRESH_MASK);
if ((config->ctl & ~settable_ctl_mask) != 0) {
zxlogf(ERROR, "bad ctl, requested 0x%" PRIx64 ", valid 0x%" PRIx64 "", config->ctl,
settable_ctl_mask);
return ZX_ERR_INVALID_ARGS;
}
uint32_t mtc_freq = (uint32_t)((config->ctl & IPT_CTL_MTC_FREQ_MASK) >> IPT_CTL_MTC_FREQ_SHIFT);
if (mtc_freq != 0 && ((1 << mtc_freq) & ipt_config_mtc_freq_mask) == 0) {
zxlogf(ERROR, "bad mtc_freq value, requested 0x%x, valid mask 0x%x", mtc_freq,
ipt_config_mtc_freq_mask);
return ZX_ERR_INVALID_ARGS;
}
uint32_t cyc_thresh =
(uint32_t)((config->ctl & IPT_CTL_CYC_THRESH_MASK) >> IPT_CTL_CYC_THRESH_SHIFT);
if (cyc_thresh != 0 && ((1 << cyc_thresh) & ipt_config_cyc_thresh_mask) == 0) {
zxlogf(ERROR, "bad cyc_thresh value, requested 0x%x, valid mask 0x%x", cyc_thresh,
ipt_config_cyc_thresh_mask);
return ZX_ERR_INVALID_ARGS;
}
uint32_t psb_freq = (uint32_t)((config->ctl & IPT_CTL_PSB_FREQ_MASK) >> IPT_CTL_PSB_FREQ_SHIFT);
if (psb_freq != 0 && ((1 << psb_freq) & ipt_config_psb_freq_mask) == 0) {
zxlogf(ERROR, "bad psb_freq value, requested 0x%x, valid mask 0x%x", psb_freq,
ipt_config_psb_freq_mask);
return ZX_ERR_INVALID_ARGS;
}
// Find an unallocated buffer entry.
BufferDescriptor descriptor;
for (descriptor = 0; descriptor < num_traces_; ++descriptor) {
if (!per_trace_state_[descriptor].allocated)
break;
}
if (descriptor == num_traces_)
return ZX_ERR_NO_RESOURCES;
ipt_per_trace_state_t* per_trace = &per_trace_state_[descriptor];
zx_status_t status =
X86PtAllocBuffer1(per_trace, config->num_chunks, config->chunk_order, config->is_circular);
if (status != ZX_OK) {
X86PtFreeBuffer1(per_trace);
return status;
}
per_trace->ctl = config->ctl;
per_trace->status = 0;
per_trace->output_base = io_buffer_phys(&per_trace->topas[0]);
per_trace->output_mask_ptrs = 0;
per_trace->cr3_match = config->address_space_match;
// TODO(dje): insntrace.fidl can't use vectors (yet) so the address ranges
// are individually spelled out.
static_assert(fuchsia_insntrace::MAX_NUM_ADDR_RANGES == 2);
static_assert(fuchsia_insntrace::MAX_NUM_ADDR_RANGES == IPT_MAX_NUM_ADDR_RANGES);
per_trace->addr_ranges[0].a = config->address_range_0.start;
per_trace->addr_ranges[0].b = config->address_range_0.end;
per_trace->addr_ranges[1].a = config->address_range_1.start;
per_trace->addr_ranges[1].b = config->address_range_1.end;
per_trace->allocated = true;
*out_descriptor = descriptor;
return ZX_OK;
}
zx_status_t InsntraceDevice::X86PtAssignThreadBuffer(BufferDescriptor descriptor,
zx_handle_t thread) {
zx_handle_close(thread);
// TODO(dje): Thread support is still work-in-progress.
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t InsntraceDevice::X86PtReleaseThreadBuffer(BufferDescriptor descriptor,
zx_handle_t thread) {
zx_handle_close(thread);
// TODO(dje): Thread support is still work-in-progress.
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t InsntraceDevice::X86PtFreeBuffer(BufferDescriptor descriptor) {
if (active_)
return ZX_ERR_BAD_STATE;
if (descriptor >= num_traces_)
return ZX_ERR_INVALID_ARGS;
assert(per_trace_state_);
ipt_per_trace_state_t* per_trace = &per_trace_state_[descriptor];
if (!per_trace->allocated)
return ZX_ERR_INVALID_ARGS;
if (per_trace->assigned)
return ZX_ERR_BAD_STATE;
X86PtFreeBuffer1(per_trace);
return ZX_OK;
}
zx_status_t InsntraceDevice::X86PtStageTraceData(zx_handle_t resource,
BufferDescriptor descriptor) {
if (descriptor >= num_traces_)
return ZX_ERR_INVALID_ARGS;
assert(per_trace_state_);
const ipt_per_trace_state_t* per_trace = &per_trace_state_[descriptor];
zx_x86_pt_regs_t regs;
regs.ctl = per_trace->ctl;
regs.ctl |= IPT_CTL_TOPA_MASK | IPT_CTL_TRACE_EN_MASK;
regs.status = per_trace->status;
regs.output_base = per_trace->output_base;
regs.output_mask_ptrs = per_trace->output_mask_ptrs;
regs.cr3_match = per_trace->cr3_match;
static_assert(sizeof(regs.addr_ranges) == sizeof(per_trace->addr_ranges),
"addr range size mismatch");
memcpy(regs.addr_ranges, per_trace->addr_ranges, sizeof(per_trace->addr_ranges));
return zx_mtrace_control(resource, MTRACE_KIND_INSNTRACE, MTRACE_INSNTRACE_STAGE_TRACE_DATA,
descriptor, &regs, sizeof(regs));
}
zx_status_t InsntraceDevice::X86PtGetTraceData(zx_handle_t resource, BufferDescriptor descriptor) {
if (descriptor >= num_traces_)
return ZX_ERR_INVALID_ARGS;
assert(per_trace_state_);
ipt_per_trace_state_t* per_trace = &per_trace_state_[descriptor];
zx_x86_pt_regs_t regs;
zx_status_t status =
zx_mtrace_control(resource, MTRACE_KIND_INSNTRACE, MTRACE_INSNTRACE_GET_TRACE_DATA,
descriptor, &regs, sizeof(regs));
if (status != ZX_OK)
return status;
per_trace->ctl = regs.ctl;
per_trace->status = regs.status;
per_trace->output_base = regs.output_base;
per_trace->output_mask_ptrs = regs.output_mask_ptrs;
per_trace->cr3_match = regs.cr3_match;
static_assert(sizeof(per_trace->addr_ranges) == sizeof(regs.addr_ranges),
"addr range size mismatch");
memcpy(per_trace->addr_ranges, regs.addr_ranges, sizeof(regs.addr_ranges));
return ZX_OK;
}
// fidl message handlers
zx_status_t InsntraceDevice::IptInitialize(const fuchsia_insntrace::Allocation* allocation) {
if (!ipt_config_supported)
return ZX_ERR_NOT_SUPPORTED;
// For now we only support ToPA, though there are no current plans to
// support anything else.
if (!ipt_config_output_topa)
return ZX_ERR_NOT_SUPPORTED;
if (per_trace_state_)
return ZX_ERR_BAD_STATE;
// TODO(dje): Until thread tracing is supported.
if (allocation->mode == fuchsia_insntrace::Mode::THREAD)
return ZX_ERR_NOT_SUPPORTED;
zx_insntrace_trace_mode_t internal_mode;
switch (allocation->mode) {
case fuchsia_insntrace::Mode::CPU:
internal_mode = IPT_MODE_CPU;
break;
case fuchsia_insntrace::Mode::THREAD:
internal_mode = IPT_MODE_THREAD;
break;
default:
return ZX_ERR_INVALID_ARGS;
}
if (allocation->num_traces > fuchsia_insntrace::MAX_NUM_TRACES)
return ZX_ERR_INVALID_ARGS;
if (internal_mode == IPT_MODE_CPU) {
// TODO(dje): KISS. No point in allowing anything else for now.
if (allocation->num_traces != zx_system_get_num_cpus())
return ZX_ERR_INVALID_ARGS;
}
fbl::AllocChecker ac;
auto per_trace_state = new (&ac) ipt_per_trace_state_t[allocation->num_traces]{};
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
per_trace_state_.reset(per_trace_state);
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
zx_handle_t resource = get_root_resource();
zx_insntrace_trace_config_t config{};
config.mode = internal_mode;
config.num_traces = allocation->num_traces;
zx_status_t status = zx_mtrace_control(resource, MTRACE_KIND_INSNTRACE,
MTRACE_INSNTRACE_ALLOC_TRACE, 0, &config, sizeof(config));
if (status != ZX_OK) {
per_trace_state_.reset();
return status;
}
mode_ = internal_mode;
num_traces_ = allocation->num_traces;
return ZX_OK;
}
zx_status_t InsntraceDevice::IptTerminate() {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
if (active_)
return ZX_ERR_BAD_STATE;
// Don't make any changes until we know it's going to work.
for (uint32_t i = 0; i < num_traces_; ++i) {
ipt_per_trace_state_t* per_trace = &per_trace_state_[i];
if (per_trace->assigned)
return ZX_ERR_BAD_STATE;
}
for (uint32_t i = 0; i < num_traces_; ++i) {
ipt_per_trace_state_t* per_trace = &per_trace_state_[i];
if (per_trace->allocated)
X86PtFreeBuffer1(per_trace);
}
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
zx_handle_t resource = get_root_resource();
zx_status_t status = zx_mtrace_control(resource, MTRACE_KIND_INSNTRACE,
MTRACE_INSNTRACE_FREE_TRACE, 0, nullptr, 0);
// TODO(dje): This really shouldn't fail. What to do?
// For now flag things as busted and prevent further use.
if (status != ZX_OK)
return ZX_OK;
per_trace_state_.reset();
return ZX_OK;
}
zx_status_t InsntraceDevice::IptGetAllocation(fuchsia_insntrace::Allocation* out_config) {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
switch (mode_) {
case IPT_MODE_CPU:
out_config->mode = fuchsia_insntrace::Mode::CPU;
break;
case IPT_MODE_THREAD:
out_config->mode = fuchsia_insntrace::Mode::THREAD;
break;
default:
__UNREACHABLE;
}
out_config->num_traces = num_traces_;
return ZX_OK;
}
zx_status_t InsntraceDevice::IptAllocateBuffer(const BufferConfig* config,
BufferDescriptor* out_descriptor) {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
return X86PtAllocBuffer(config, out_descriptor);
}
zx_status_t InsntraceDevice::IptAssignThreadBuffer(BufferDescriptor descriptor,
zx_handle_t thread) {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
return X86PtAssignThreadBuffer(descriptor, thread);
}
zx_status_t InsntraceDevice::IptReleaseThreadBuffer(BufferDescriptor descriptor,
zx_handle_t thread) {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
return X86PtReleaseThreadBuffer(descriptor, thread);
}
zx_status_t InsntraceDevice::IptGetBufferConfig(BufferDescriptor descriptor,
BufferConfig* out_config) {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
if (descriptor >= num_traces_)
return ZX_ERR_INVALID_ARGS;
const ipt_per_trace_state_t* per_trace = &per_trace_state_[descriptor];
if (!per_trace->allocated)
return ZX_ERR_INVALID_ARGS;
*out_config = {};
out_config->num_chunks = per_trace->num_chunks;
out_config->chunk_order = per_trace->chunk_order;
out_config->is_circular = per_trace->is_circular;
out_config->ctl = per_trace->ctl;
out_config->address_space_match = per_trace->cr3_match;
// TODO(dje): insntrace.fidl can't use vectors (yet) so the address ranges
// are individually spelled out.
static_assert(fuchsia_insntrace::MAX_NUM_ADDR_RANGES == 2);
static_assert(fuchsia_insntrace::MAX_NUM_ADDR_RANGES == IPT_MAX_NUM_ADDR_RANGES);
out_config->address_range_0.start = per_trace->addr_ranges[0].a;
out_config->address_range_0.end = per_trace->addr_ranges[0].b;
out_config->address_range_1.start = per_trace->addr_ranges[1].a;
out_config->address_range_1.end = per_trace->addr_ranges[1].b;
return ZX_OK;
}
zx_status_t InsntraceDevice::IptGetBufferState(BufferDescriptor descriptor,
BufferState* out_state) {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
// In thread-mode we need to get buffer info while tracing is active.
if (mode_ == IPT_MODE_CPU && active_)
return ZX_ERR_BAD_STATE;
if (descriptor >= num_traces_)
return ZX_ERR_INVALID_ARGS;
const ipt_per_trace_state_t* per_trace = &per_trace_state_[descriptor];
if (!per_trace->allocated)
return ZX_ERR_INVALID_ARGS;
// Note: If this is a circular buffer this is just where tracing stopped.
*out_state = {};
out_state->capture_end = ComputeCaptureSize(per_trace);
return ZX_OK;
}
zx_status_t InsntraceDevice::IptGetChunkHandle(BufferDescriptor descriptor, uint32_t chunk_num,
zx_handle_t* out_handle) {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
if (descriptor >= num_traces_)
return ZX_ERR_INVALID_ARGS;
const ipt_per_trace_state_t* per_trace = &per_trace_state_[descriptor];
if (!per_trace->allocated)
return ZX_ERR_INVALID_ARGS;
if (chunk_num >= per_trace->num_chunks)
return ZX_ERR_INVALID_ARGS;
zx_handle_t vmo_handle = per_trace->chunks[chunk_num].vmo_handle;
zx_info_handle_basic_t handle_info;
zx_status_t status = zx_object_get_info(vmo_handle, ZX_INFO_HANDLE_BASIC, &handle_info,
sizeof(handle_info), nullptr, nullptr);
if (status != ZX_OK) {
// This could only fail if vmo_handle is invalid.
printf("%s: WARNING: unexpected error reading vmo handle rights: %d/%s\n", __func__, status,
zx_status_get_string(status));
return status;
}
zx_rights_t allowed_rights = (ZX_RIGHT_TRANSFER | ZX_RIGHT_WAIT | ZX_RIGHT_INSPECT |
ZX_RIGHT_GET_PROPERTY | ZX_RIGHT_READ | ZX_RIGHT_MAP);
return zx_handle_duplicate(vmo_handle, handle_info.rights & allowed_rights, out_handle);
}
zx_status_t InsntraceDevice::IptFreeBuffer(BufferDescriptor descriptor) {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
return X86PtFreeBuffer(descriptor);
}
// Begin tracing.
// This is basically a nop in thread mode, it is still used for thread-mode
// for consistency and in case we some day need it to do something.
zx_status_t InsntraceDevice::IptStart() {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
if (active_)
return ZX_ERR_BAD_STATE;
if (mode_ != IPT_MODE_CPU)
return ZX_ERR_BAD_STATE;
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
zx_handle_t resource = get_root_resource();
zx_status_t status;
// In cpu-mode, until we support tracing particular cpus, auto-assign
// buffers to each cpu.
if (mode_ == IPT_MODE_CPU) {
// First verify a buffer has been allocated for each cpu,
// and not yet assigned.
for (uint32_t cpu = 0; cpu < num_traces_; ++cpu) {
const ipt_per_trace_state_t* per_trace = &per_trace_state_[cpu];
if (!per_trace->allocated)
return ZX_ERR_BAD_STATE;
if (per_trace->assigned)
return ZX_ERR_BAD_STATE;
}
for (uint32_t cpu = 0; cpu < num_traces_; ++cpu) {
status = X86PtStageTraceData(resource, cpu);
if (status != ZX_OK) {
// TODO(dje): Unstage ones already done.
return status;
}
ipt_per_trace_state_t* per_trace = &per_trace_state_[cpu];
per_trace->owner.cpu = cpu;
per_trace->assigned = true;
}
}
status =
zx_mtrace_control(resource, MTRACE_KIND_INSNTRACE, MTRACE_INSNTRACE_START, 0, nullptr, 0);
if (status != ZX_OK)
return status;
active_ = true;
return ZX_OK;
}
// Stop tracing.
// In thread-mode all buffers must be released first. That is how we know that
// if we return ZX_OK then all threads are no longer being traced. Otherwise,
// this is basically a nop in thread-mode.
zx_status_t InsntraceDevice::IptStop() {
if (!per_trace_state_)
return ZX_ERR_BAD_STATE;
if (!active_)
return ZX_ERR_BAD_STATE;
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
zx_handle_t resource = get_root_resource();
zx_status_t status =
zx_mtrace_control(resource, MTRACE_KIND_INSNTRACE, MTRACE_INSNTRACE_STOP, 0, nullptr, 0);
if (status != ZX_OK)
return status;
active_ = false;
// Until we support tracing individual cpus, auto-unassign the buffers
// in cpu-mode.
if (mode_ == IPT_MODE_CPU) {
for (uint32_t cpu = 0; cpu < num_traces_; ++cpu) {
status = X86PtGetTraceData(resource, cpu);
if (status != ZX_OK)
return status;
ipt_per_trace_state_t* per_trace = &per_trace_state_[cpu];
per_trace->assigned = false;
per_trace->owner.cpu = 0;
// If there was an operational error, report it.
if (per_trace->status & IPT_STATUS_ERROR_MASK) {
printf("%s: WARNING: operational error detected on cpu %d\n", __func__, cpu);
}
}
}
return ZX_OK;
}
// Fidl interface.
void InsntraceDevice::Initialize(fuchsia_insntrace::Allocation allocation,
InitializeCompleter::Sync& completer) {
zx_status_t status = IptInitialize(&allocation);
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void InsntraceDevice::Terminate(TerminateCompleter::Sync& completer) {
zx_status_t status = IptTerminate();
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void InsntraceDevice::GetAllocation(GetAllocationCompleter::Sync& completer) {
fuchsia_insntrace::Allocation config{};
zx_status_t status = IptGetAllocation(&config);
completer.Reply(status == ZX_OK ? fidl::unowned_ptr(&config) : nullptr);
}
void InsntraceDevice::AllocateBuffer(BufferConfig config,
AllocateBufferCompleter::Sync& completer) {
BufferDescriptor descriptor;
zx_status_t status = IptAllocateBuffer(&config, &descriptor);
if (status == ZX_OK) {
completer.ReplySuccess(descriptor);
} else {
completer.ReplyError(status);
}
}
void InsntraceDevice::AssignThreadBuffer(BufferDescriptor descriptor, zx::thread thread,
AssignThreadBufferCompleter::Sync& completer) {
zx_status_t status = IptAssignThreadBuffer(thread.release(), descriptor);
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void InsntraceDevice::ReleaseThreadBuffer(BufferDescriptor descriptor, zx::thread thread,
ReleaseThreadBufferCompleter::Sync& completer) {
zx_status_t status = IptReleaseThreadBuffer(thread.release(), descriptor);
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void InsntraceDevice::GetBufferConfig(BufferDescriptor descriptor,
GetBufferConfigCompleter::Sync& completer) {
BufferConfig config;
zx_status_t status = IptGetBufferConfig(descriptor, &config);
completer.Reply(status == ZX_OK ? fidl::unowned_ptr(&config) : nullptr);
}
void InsntraceDevice::GetBufferState(BufferDescriptor descriptor,
GetBufferStateCompleter::Sync& completer) {
BufferState state;
zx_status_t status = IptGetBufferState(descriptor, &state);
completer.Reply(status == ZX_OK ? fidl::unowned_ptr(&state) : nullptr);
}
void InsntraceDevice::GetChunkHandle(BufferDescriptor descriptor, uint32_t chunk_num,
GetChunkHandleCompleter::Sync& completer) {
zx_handle_t handle;
zx_status_t status = IptGetChunkHandle(descriptor, chunk_num, &handle);
completer.Reply(zx::vmo(status == ZX_OK ? handle : ZX_HANDLE_INVALID));
}
void InsntraceDevice::FreeBuffer(BufferDescriptor descriptor,
FreeBufferCompleter::Sync& completer) {
zx_status_t status = IptFreeBuffer(descriptor);
if (status == ZX_OK) {
completer.Reply();
}
}
void InsntraceDevice::Start(StartCompleter::Sync& completer) {
zx_status_t status = IptStart();
if (status == ZX_OK) {
completer.Reply();
}
}
void InsntraceDevice::Stop(StopCompleter::Sync& completer) {
zx_status_t status = IptStop();
if (status == ZX_OK) {
completer.Reply();
}
}
// Devhost interface.
zx_status_t InsntraceDevice::DdkOpen(zx_device_t** dev_out, uint32_t flags) {
if (opened_)
return ZX_ERR_ALREADY_BOUND;
opened_ = true;
return ZX_OK;
}
zx_status_t InsntraceDevice::DdkClose(uint32_t flags) {
opened_ = false;
return ZX_OK;
}
zx_status_t InsntraceDevice::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
mtx_lock(&lock_);
fuchsia_insntrace::Controller::Dispatch(this, msg, &transaction);
mtx_unlock(&lock_);
return transaction.Status();
}
void InsntraceDevice::DdkRelease() {
IptStop();
IptTerminate();
delete this;
}
} // namespace insntrace
zx_status_t insntrace_bind(void* ctx, zx_device_t* parent) {
zx_status_t status = insntrace::InsntraceInitOnce();
if (status != ZX_OK) {
return status;
}
pdev_protocol_t pdev;
status = device_get_protocol(parent, ZX_PROTOCOL_PDEV, &pdev);
if (status != ZX_OK) {
return status;
}
zx::bti bti;
status = pdev_get_bti(&pdev, 0, bti.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
fbl::AllocChecker ac;
auto dev = std::unique_ptr<insntrace::InsntraceDevice>(
new (&ac) insntrace::InsntraceDevice(parent, std::move(bti)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = dev->DdkAdd("insntrace");
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not add device: %d", __func__, status);
} else {
// devmgr owns the memory now
__UNUSED auto ptr = dev.release();
}
return status;
}