| // Copyright 2018 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 "perf-mon.h" |
| |
| #include <assert.h> |
| #include <lib/zircon-internal/device/cpu-trace/perf-mon.h> |
| #include <lib/zircon-internal/mtrace.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <cstdint> |
| #include <limits> |
| #include <memory> |
| |
| #include <ddk/debug.h> |
| #include <ddk/protocol/platform/device.h> |
| #include <ddktl/fidl.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| |
| #include "cpu-trace-private.h" |
| |
| namespace perfmon { |
| |
| int ComparePerfmonEventId(const void* ap, const void* bp) { |
| auto a = reinterpret_cast<const EventId*>(ap); |
| auto b = reinterpret_cast<const EventId*>(bp); |
| if (*a < *b) { |
| return -1; |
| } |
| if (*a > *b) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| uint16_t GetLargestEventId(const EventDetails* events, size_t count) { |
| uint16_t largest = 0; |
| |
| for (size_t i = 0; i < count; ++i) { |
| uint16_t id = events[i].id; |
| if (id > largest) { |
| largest = id; |
| } |
| } |
| |
| return largest; |
| } |
| |
| zx_status_t BuildEventMap(const EventDetails* events, size_t count, const uint16_t** out_event_map, |
| size_t* out_map_size) { |
| static_assert(kMaxEvent < std::numeric_limits<uint16_t>::max()); |
| |
| uint16_t largest_event_id = GetLargestEventId(events, count); |
| // See perf-mon.h: The full event id is split into two pieces: |
| // group type and id within that group. The event recorded in |
| // |EventDetails| is the id within the group. Each id must be in |
| // the range [1,PERFMON_MAX_EVENT]. ID 0 is reserved. |
| if (largest_event_id == 0 || largest_event_id > kMaxEvent) { |
| zxlogf(ERROR, "PMU: Corrupt event database"); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| fbl::AllocChecker ac; |
| size_t event_map_size = largest_event_id + 1; |
| zxlogf(TRACE, "PMU: %zu arch events", count); |
| zxlogf(TRACE, "PMU: arch event id range: 1-%zu", event_map_size); |
| auto event_map = std::unique_ptr<uint16_t[]>(new (&ac) uint16_t[event_map_size]{}); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| for (uint16_t i = 0; i < count; ++i) { |
| uint16_t id = events[i].id; |
| assert(id < event_map_size); |
| assert(event_map[id] == 0); |
| event_map[id] = i; |
| } |
| |
| *out_event_map = event_map.release(); |
| *out_map_size = event_map_size; |
| return ZX_OK; |
| } |
| |
| static void DumpHwProperties(const PmuHwProperties& props) { |
| zxlogf(INFO, "Performance Monitor Unit configuration for this chipset:"); |
| zxlogf(INFO, "PMU: version %u", props.common.pm_version); |
| zxlogf(INFO, "PMU: %u fixed events, width %u", props.common.max_num_fixed_events, |
| props.common.max_fixed_counter_width); |
| zxlogf(INFO, "PMU: %u programmable events, width %u", props.common.max_num_programmable_events, |
| props.common.max_programmable_counter_width); |
| zxlogf(INFO, "PMU: %u misc events, width %u", props.common.max_num_misc_events, |
| props.common.max_misc_counter_width); |
| #ifdef __x86_64__ |
| zxlogf(INFO, "PMU: perf_capabilities: 0x%lx", props.perf_capabilities); |
| zxlogf(INFO, "PMU: lbr_stack_size: %u", props.lbr_stack_size); |
| #endif |
| } |
| |
| PerfmonDevice::PerfmonDevice(zx_device_t* parent, zx::bti bti, perfmon::PmuHwProperties props, |
| mtrace_control_func_t* mtrace_control) |
| : DeviceType(parent), |
| bti_(std::move(bti)), |
| pmu_hw_properties_(props), |
| mtrace_control_(mtrace_control) {} |
| |
| zx_status_t PerfmonDevice::GetHwProperties(mtrace_control_func_t* mtrace_control, |
| PmuHwProperties* out_props) { |
| // Please do not use get_root_resource() in new code. See ZX-1467. |
| zx_handle_t resource = get_root_resource(); |
| zx_status_t status = mtrace_control(resource, MTRACE_KIND_PERFMON, MTRACE_PERFMON_GET_PROPERTIES, |
| 0, out_props, sizeof(*out_props)); |
| if (status != ZX_OK) { |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| zxlogf(INFO, "%s: No PM support", __func__); |
| } else { |
| zxlogf(INFO, "%s: Error %d fetching ipm properties", __func__, status); |
| } |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void PerfmonDevice::FreeBuffersForTrace(PmuPerTraceState* per_trace, uint32_t num_allocated) { |
| // Note: This may be called with partially allocated buffers. |
| assert(per_trace->buffers); |
| assert(num_allocated <= per_trace->num_buffers); |
| for (uint32_t i = 0; i < num_allocated; ++i) { |
| io_buffer_release(&per_trace->buffers[i]); |
| } |
| per_trace->buffers.reset(); |
| } |
| |
| void PerfmonDevice::PmuGetProperties(FidlPerfmonProperties* props) { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| props->api_version = fidl_perfmon::API_VERSION; |
| props->pm_version = pmu_hw_properties_.common.pm_version; |
| static_assert(perfmon::kMaxNumEvents == fidl_perfmon::MAX_NUM_EVENTS); |
| props->max_num_events = fidl_perfmon::MAX_NUM_EVENTS; |
| |
| // These numbers are for informational/debug purposes. There can be |
| // further restrictions and limitations. |
| // TODO(dje): Something more elaborate can wait for publishing them via |
| // some namespace. |
| props->max_num_fixed_events = pmu_hw_properties_.common.max_num_fixed_events; |
| props->max_fixed_counter_width = pmu_hw_properties_.common.max_fixed_counter_width; |
| props->max_num_programmable_events = pmu_hw_properties_.common.max_num_programmable_events; |
| props->max_programmable_counter_width = pmu_hw_properties_.common.max_programmable_counter_width; |
| props->max_num_misc_events = pmu_hw_properties_.common.max_num_misc_events; |
| props->max_misc_counter_width = pmu_hw_properties_.common.max_misc_counter_width; |
| |
| props->flags = fidl_perfmon::PropertyFlags(); |
| #ifdef __x86_64__ |
| if (pmu_hw_properties_.lbr_stack_size > 0) { |
| props->flags |= fidl_perfmon::PropertyFlags::HAS_LAST_BRANCH; |
| } |
| #endif |
| } |
| |
| zx_status_t PerfmonDevice::PmuInitialize(const FidlPerfmonAllocation* allocation) { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| if (per_trace_state_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| uint32_t num_cpus = zx_system_get_num_cpus(); |
| if (allocation->num_buffers != num_cpus) { // TODO(dje): for now |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (allocation->buffer_size_in_pages > kMaxPerTraceSpaceInPages) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AllocChecker ac; |
| auto per_trace = std::unique_ptr<PmuPerTraceState>(new (&ac) PmuPerTraceState{}); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| per_trace->buffers = std::unique_ptr<io_buffer_t[]>(new (&ac) io_buffer_t[num_cpus]); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| size_t buffer_size = allocation->buffer_size_in_pages * kPageSize; |
| uint32_t i = 0; |
| for (; i < num_cpus; ++i) { |
| zx_status_t status = |
| io_buffer_init(&per_trace->buffers[i], bti_.get(), buffer_size, IO_BUFFER_RW); |
| if (status != ZX_OK) { |
| break; |
| } |
| } |
| if (i != num_cpus) { |
| FreeBuffersForTrace(per_trace.get(), i); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| per_trace->num_buffers = allocation->num_buffers; |
| per_trace->buffer_size_in_pages = allocation->buffer_size_in_pages; |
| per_trace_state_ = std::move(per_trace); |
| return ZX_OK; |
| } |
| |
| void PerfmonDevice::PmuTerminate() { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| if (active_) { |
| PmuStop(); |
| } |
| |
| PmuPerTraceState* per_trace = per_trace_state_.get(); |
| if (per_trace) { |
| FreeBuffersForTrace(per_trace, per_trace->num_buffers); |
| per_trace_state_.reset(); |
| } |
| } |
| |
| zx_status_t PerfmonDevice::PmuGetAllocation(FidlPerfmonAllocation* allocation) { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| const PmuPerTraceState* per_trace = per_trace_state_.get(); |
| if (!per_trace) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| allocation->num_buffers = per_trace->num_buffers; |
| allocation->buffer_size_in_pages = per_trace->buffer_size_in_pages; |
| return ZX_OK; |
| } |
| |
| zx_status_t PerfmonDevice::PmuGetBufferHandle(uint32_t descriptor, zx_handle_t* out_handle) { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| const PmuPerTraceState* per_trace = per_trace_state_.get(); |
| if (!per_trace) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (descriptor >= per_trace->num_buffers) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_handle_t h; |
| zx_status_t status = |
| zx_handle_duplicate(per_trace->buffers[descriptor].vmo_handle, ZX_RIGHT_SAME_RIGHTS, &h); |
| if (status != ZX_OK) { |
| // This failure could be hard to debug. Give the user some help. |
| zxlogf(ERROR, "%s: Failed to duplicate %u buffer handle: %d", __func__, descriptor, status); |
| return status; |
| } |
| |
| *out_handle = h; |
| return ZX_OK; |
| } |
| |
| // Do an architecture-independent verification pass over |icfg|, |
| // and see if there's a timebase event. |
| static zx_status_t VerifyAndCheckTimebase(const FidlPerfmonConfig* icfg, PmuConfig* ocfg) { |
| unsigned ii; // ii: input index |
| for (ii = 0; ii < countof(icfg->events); ++ii) { |
| EventId id = icfg->events[ii].event; |
| if (id == kEventIdNone) { |
| break; |
| } |
| EventRate rate = icfg->events[ii].rate; |
| fidl_perfmon::EventConfigFlags flags = icfg->events[ii].flags; |
| |
| if (flags & fidl_perfmon::EventConfigFlags::IS_TIMEBASE) { |
| if (ocfg->timebase_event != kEventIdNone) { |
| zxlogf(ERROR, "%s: multiple timebases [%u]", __func__, ii); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| ocfg->timebase_event = icfg->events[ii].event; |
| } |
| |
| if (flags & fidl_perfmon::EventConfigFlags::COLLECT_PC) { |
| if (rate == 0) { |
| zxlogf(ERROR, "%s: PC flag requires own timebase, event [%u]", __func__, ii); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| if (flags & fidl_perfmon::EventConfigFlags::COLLECT_LAST_BRANCH) { |
| // Further verification is architecture specific. |
| if (icfg->events[ii].rate == 0) { |
| zxlogf(ERROR, "%s: Last branch requires own timebase, event [%u]", __func__, ii); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| } |
| |
| if (ii == 0) { |
| zxlogf(ERROR, "%s: No events provided", __func__); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Ensure there are no holes. |
| for (; ii < countof(icfg->events); ++ii) { |
| if (icfg->events[ii].event != kEventIdNone) { |
| zxlogf(ERROR, "%s: Hole at event [%u]", __func__, ii); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (icfg->events[ii].rate != 0) { |
| zxlogf(ERROR, "%s: Hole at rate [%u]", __func__, ii); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (icfg->events[ii].flags != fidl_perfmon::EventConfigFlags()) { |
| zxlogf(ERROR, "%s: Hole at flags [%u]", __func__, ii); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t PerfmonDevice::PmuStageConfig(const FidlPerfmonConfig* fidl_config) { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| if (active_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| PmuPerTraceState* per_trace = per_trace_state_.get(); |
| if (!per_trace) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // If we subsequently get an error, make sure any previous configuration |
| // can't be used. |
| per_trace->configured = false; |
| |
| const FidlPerfmonConfig* icfg = fidl_config; |
| |
| PmuConfig* ocfg = &per_trace->config; |
| *ocfg = {}; |
| |
| // Validate the config and convert it to our internal form. |
| // TODO(dje): Multiplexing support. |
| |
| StagingState staging_state{}; |
| StagingState* ss = &staging_state; |
| InitializeStagingState(ss); |
| |
| zx_status_t status = VerifyAndCheckTimebase(icfg, ocfg); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| unsigned ii; // ii: input index |
| for (ii = 0; ii < icfg->events.size(); ++ii) { |
| EventId id = icfg->events[ii].event; |
| zxlogf(TRACE, "%s: processing [%u] = %u", __func__, ii, id); |
| if (id == kEventIdNone) { |
| break; |
| } |
| unsigned group = GetEventIdGroup(id); |
| |
| switch (group) { |
| case kGroupFixed: |
| status = StageFixedConfig(icfg, ss, ii, ocfg); |
| if (status != ZX_OK) { |
| return status; |
| } |
| break; |
| case kGroupArch: |
| case kGroupModel: |
| status = StageProgrammableConfig(icfg, ss, ii, ocfg); |
| if (status != ZX_OK) { |
| return status; |
| } |
| break; |
| case kGroupMisc: |
| status = StageMiscConfig(icfg, ss, ii, ocfg); |
| if (status != ZX_OK) { |
| return status; |
| } |
| break; |
| default: |
| zxlogf(ERROR, "%s: Invalid event [%u] (bad group)", __func__, ii); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| // TODO(dje): Basic sanity check that some data will be collected. |
| |
| per_trace->fidl_config = *icfg; |
| per_trace->configured = true; |
| return ZX_OK; |
| } |
| |
| zx_status_t PerfmonDevice::PmuGetConfig(FidlPerfmonConfig* config) { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| const PmuPerTraceState* per_trace = per_trace_state_.get(); |
| if (!per_trace) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (!per_trace->configured) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| *config = per_trace->fidl_config; |
| return ZX_OK; |
| } |
| |
| zx_status_t PerfmonDevice::PmuStart() { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| if (active_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| PmuPerTraceState* per_trace = per_trace_state_.get(); |
| if (!per_trace) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (!per_trace->configured) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // Step 1: Get the configuration data into the kernel for use by START. |
| |
| #ifdef __x86_64__ |
| zxlogf(TRACE, "%s: global ctrl 0x%lx, fixed ctrl 0x%lx", __func__, |
| per_trace->config.global_ctrl, per_trace->config.fixed_ctrl); |
| |
| // Note: If only misc counters are enabled then |global_ctrl| will be zero. |
| #endif |
| |
| // Please do not use get_root_resource() in new code. See ZX-1467. |
| zx_handle_t resource = get_root_resource(); |
| |
| zx_status_t status = |
| mtrace_control_(resource, MTRACE_KIND_PERFMON, MTRACE_PERFMON_INIT, 0, nullptr, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| uint32_t num_cpus = zx_system_get_num_cpus(); |
| for (uint32_t cpu = 0; cpu < num_cpus; ++cpu) { |
| zx_pmu_buffer_t buffer; |
| io_buffer_t* io_buffer = &per_trace->buffers[cpu]; |
| buffer.vmo = io_buffer->vmo_handle; |
| status = mtrace_control_(resource, MTRACE_KIND_PERFMON, MTRACE_PERFMON_ASSIGN_BUFFER, cpu, |
| &buffer, sizeof(buffer)); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| } |
| |
| status = mtrace_control_(resource, MTRACE_KIND_PERFMON, MTRACE_PERFMON_STAGE_CONFIG, 0, |
| &per_trace->config, sizeof(per_trace->config)); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| // Step 2: Start data collection. |
| |
| status = mtrace_control_(resource, MTRACE_KIND_PERFMON, MTRACE_PERFMON_START, 0, nullptr, 0); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| active_ = true; |
| return ZX_OK; |
| |
| fail : { |
| [[maybe_unused]] zx_status_t status2 = |
| mtrace_control_(resource, MTRACE_KIND_PERFMON, MTRACE_PERFMON_FINI, 0, nullptr, 0); |
| assert(status2 == ZX_OK); |
| return status; |
| } |
| } |
| |
| void PerfmonDevice::PmuStop() { |
| zxlogf(TRACE, "%s called", __func__); |
| |
| PmuPerTraceState* per_trace = per_trace_state_.get(); |
| if (!per_trace) { |
| return; |
| } |
| |
| // Please do not use get_root_resource() in new code. See ZX-1467. |
| zx_handle_t resource = get_root_resource(); |
| [[maybe_unused]] zx_status_t status = |
| mtrace_control_(resource, MTRACE_KIND_PERFMON, MTRACE_PERFMON_STOP, 0, nullptr, 0); |
| assert(status == ZX_OK); |
| active_ = false; |
| status = mtrace_control_(resource, MTRACE_KIND_PERFMON, MTRACE_PERFMON_FINI, 0, nullptr, 0); |
| assert(status == ZX_OK); |
| } |
| |
| // Fidl interface. |
| |
| void PerfmonDevice::GetProperties(GetPropertiesCompleter::Sync completer) { |
| FidlPerfmonProperties props{}; |
| PmuGetProperties(&props); |
| completer.Reply(std::move(props)); |
| } |
| |
| void PerfmonDevice::Initialize(FidlPerfmonAllocation allocation, |
| InitializeCompleter::Sync completer) { |
| zx_status_t status = PmuInitialize(&allocation); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void PerfmonDevice::Terminate(TerminateCompleter::Sync completer) { |
| PmuTerminate(); |
| completer.Reply(); |
| } |
| |
| void PerfmonDevice::GetAllocation(GetAllocationCompleter::Sync completer) { |
| FidlPerfmonAllocation alloc{}; |
| zx_status_t status = PmuGetAllocation(&alloc); |
| completer.Reply(status != ZX_OK ? nullptr : fidl::unowned_ptr(&alloc)); |
| } |
| |
| void PerfmonDevice::StageConfig(FidlPerfmonConfig config, StageConfigCompleter::Sync completer) { |
| zx_status_t status = PmuStageConfig(&config); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void PerfmonDevice::GetConfig(GetConfigCompleter::Sync completer) { |
| FidlPerfmonConfig config{}; |
| zx_status_t status = PmuGetConfig(&config); |
| completer.Reply(status != ZX_OK ? nullptr : fidl::unowned_ptr(&config)); |
| } |
| |
| void PerfmonDevice::GetBufferHandle(uint32_t descriptor, GetBufferHandleCompleter::Sync completer) { |
| zx_handle_t handle; |
| zx_status_t status = PmuGetBufferHandle(descriptor, &handle); |
| completer.Reply(zx::vmo(status != ZX_OK ? ZX_HANDLE_INVALID : handle)); |
| } |
| |
| void PerfmonDevice::Start(StartCompleter::Sync completer) { |
| zx_status_t status = PmuStart(); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void PerfmonDevice::Stop(StopCompleter::Sync completer) { |
| PmuStop(); |
| completer.Reply(); |
| } |
| |
| // Devhost interface. |
| |
| zx_status_t PerfmonDevice::DdkOpen(zx_device_t** dev_out, uint32_t flags) { |
| if (opened_) { |
| return ZX_ERR_ALREADY_BOUND; |
| } |
| |
| opened_ = true; |
| return ZX_OK; |
| } |
| |
| zx_status_t PerfmonDevice::DdkClose(uint32_t flags) { |
| opened_ = false; |
| return ZX_OK; |
| } |
| |
| zx_status_t PerfmonDevice::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) { |
| DdkTransaction transaction(txn); |
| mtx_lock(&lock_); |
| fidl_perfmon::Controller::Dispatch(this, msg, &transaction); |
| mtx_unlock(&lock_); |
| |
| return transaction.Status(); |
| } |
| |
| void PerfmonDevice::DdkRelease() { |
| PmuStop(); |
| PmuTerminate(); |
| |
| delete this; |
| } |
| |
| } // namespace perfmon |
| |
| zx_status_t perfmon_bind(void* ctx, zx_device_t* parent) { |
| zx_status_t status = perfmon::PerfmonDevice::InitOnce(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| perfmon::PmuHwProperties props; |
| status = perfmon::PerfmonDevice::GetHwProperties(zx_mtrace_control, &props); |
| if (status != ZX_OK) { |
| return status; |
| } |
| DumpHwProperties(props); |
| |
| if (props.common.pm_version < perfmon::kMinPmVersion) { |
| zxlogf(INFO, "%s: PM version %u or above is required", __func__, perfmon::kMinPmVersion); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| 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<perfmon::PerfmonDevice>( |
| new (&ac) perfmon::PerfmonDevice(parent, std::move(bti), props, zx_mtrace_control)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| status = dev->DdkAdd("perfmon"); |
| 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; |
| } |