1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// See the README.md in this directory for documentation.
6
7#include <assert.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <threads.h>
12
13#include <ddk/binding.h>
14#include <ddk/device.h>
15#include <ddk/driver.h>
16#include <ddk/protocol/platform-defs.h>
17#include <ddk/protocol/platform-device.h>
18
19#include <zircon/syscalls.h>
20#include <zircon/syscalls/resource.h>
21#include <zircon/types.h>
22
23#include "cpu-trace-private.h"
24
25static zx_status_t cpu_trace_open(void* ctx, zx_device_t** dev_out, uint32_t flags) {
26    cpu_trace_device_t* dev = ctx;
27    if (dev->opened)
28        return ZX_ERR_ALREADY_BOUND;
29
30    dev->opened = true;
31    return ZX_OK;
32}
33
34static zx_status_t cpu_trace_close(void* ctx, uint32_t flags) {
35    cpu_trace_device_t* dev = ctx;
36
37    dev->opened = false;
38    return ZX_OK;
39}
40
41static zx_status_t cpu_trace_ioctl(void* ctx, uint32_t op,
42                                   const void* cmd, size_t cmdlen,
43                                   void* reply, size_t replymax,
44                                   size_t* out_actual) {
45    cpu_trace_device_t* dev = ctx;
46
47    mtx_lock(&dev->lock);
48
49    ssize_t result;
50    switch (IOCTL_FAMILY(op)) {
51#ifdef __x86_64__
52        case IOCTL_FAMILY_CPUPERF:
53            result = cpuperf_ioctl(dev, op, cmd, cmdlen,
54                                   reply, replymax, out_actual);
55            break;
56        case IOCTL_FAMILY_INSNTRACE:
57            result = insntrace_ioctl(dev, op, cmd, cmdlen,
58                                     reply, replymax, out_actual);
59            break;
60#endif
61        default:
62            result = ZX_ERR_INVALID_ARGS;
63            break;
64    }
65
66    mtx_unlock(&dev->lock);
67
68    return result;
69}
70
71static void cpu_trace_release(void* ctx) {
72    cpu_trace_device_t* dev = ctx;
73
74#ifdef __x86_64__
75    insntrace_release(dev);
76    cpuperf_release(dev);
77#endif
78
79    zx_handle_close(dev->bti);
80    free(dev);
81}
82
83static zx_protocol_device_t cpu_trace_device_proto = {
84    .version = DEVICE_OPS_VERSION,
85    .open = cpu_trace_open,
86    .close = cpu_trace_close,
87    .ioctl = cpu_trace_ioctl,
88    .release = cpu_trace_release,
89};
90
91static zx_status_t cpu_trace_bind(void* ctx, zx_device_t* parent) {
92#ifdef __x86_64__
93    insntrace_init_once();
94    cpuperf_init_once();
95#endif
96
97    platform_device_protocol_t pdev;
98    zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &pdev);
99    if (status != ZX_OK) {
100        return status;
101    }
102
103    cpu_trace_device_t* dev = calloc(1, sizeof(*dev));
104    if (!dev) {
105        return ZX_ERR_NO_MEMORY;
106    }
107
108    status = pdev_get_bti(&pdev, 0, &dev->bti);
109    if (status != ZX_OK) {
110        goto fail;
111    }
112
113    device_add_args_t args = {
114        .version = DEVICE_ADD_ARGS_VERSION,
115        .name = "cpu-trace",
116        .ctx = dev,
117        .ops = &cpu_trace_device_proto,
118    };
119
120    if ((status = device_add(parent, &args, NULL)) < 0) {
121        goto fail;
122    }
123
124    return ZX_OK;
125
126fail:
127    zx_handle_close(dev->bti);
128    free(dev);
129    return status;
130}
131
132static zx_driver_ops_t cpu_trace_driver_ops = {
133    .version = DRIVER_OPS_VERSION,
134    .bind = cpu_trace_bind,
135};
136
137ZIRCON_DRIVER_BEGIN(cpu_trace, cpu_trace_driver_ops, "zircon", "0.1", 4)
138    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
139    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_INTEL),
140    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
141    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_INTEL_CPU_TRACE),
142ZIRCON_DRIVER_END(cpu_trace)
143