blob: cb48e5e98f4ef2b2517a007c414ccc11fbea7062 [file] [log] [blame]
#include <linux/arm-smccc.h>
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/percpu-defs.h>
#include <linux/sched/clock.h>
#include <linux/memblock.h>
#include <asm/kvm_host.h>
#include <asm/kvm_hyptrace.h>
#include <asm/kvm_hypevents.h>
struct trace_buf {
unsigned long va;
int order;
int flags;
};
#define TRACE_BUF_MEMBLOCK (1 << 0)
#define TRACE_BUF_RB_INIT (1 << 1) /* hyp_trace_rb init by hyp */
static DEFINE_MUTEX(mutex);
static DEFINE_PER_CPU(struct trace_buf, trace_buf) = { .va = 0, .flags = 0 };
static u64 events_on;
static bool hyp_tracing_is_on;
static unsigned long __trace_buf_prealloc[8] = {0};
/*
* TODO: to be replaced by dynamic allocation after hyptrace is plugged to
* host trace_events.
*/
void __init hyp_trace_buf_preallocate(void)
{
int cpu;
/*
* Ahem ... this is init _before_ the CPUs are online and
* the cpu_possible_mask is therefore empty here...
*
* ...But it's just for quick debug right? So let's just allocate what
* we need for 8 CPUs and move on.
*/
for (cpu = 0; cpu < 8; cpu++) {
phys_addr_t base =
memblock_phys_alloc(PAGE_SIZE << 13, PAGE_SIZE);
if (!base) {
pr_warn("Failed to pre-allocate hyp_trace for cpu %d\n", cpu);
continue;
}
__trace_buf_prealloc[cpu] = (unsigned long)phys_to_virt(base);
pr_info("Reserved %lu MiB at 0x%lx for hyp_trace CPU %d\n",
PAGE_SIZE >> 7, __trace_buf_prealloc[cpu], cpu);
}
}
static int hyp_trace_buf_use_preallocation(void)
{
int cpu;
for_each_possible_cpu(cpu) {
struct trace_buf *buf = per_cpu_ptr(&trace_buf, cpu);
if (cpu >= 8)
break;
buf->va = __trace_buf_prealloc[cpu];
buf->order = 13;
buf->flags |= TRACE_BUF_MEMBLOCK;
}
return 0;
}
late_initcall(hyp_trace_buf_use_preallocation);
static void hyp_trace_free_buf(void)
{
int cpu;
for_each_possible_cpu(cpu) {
struct trace_buf *buf = per_cpu_ptr(&trace_buf, cpu);
if (!buf->va || buf->flags & TRACE_BUF_MEMBLOCK)
continue;
free_page(buf->va);
buf->va = 0;
buf->order = 0;
}
}
static int hyp_trace_start(void)
{
struct hyp_trace_rb_args *args;
struct trace_buf *buf;
unsigned int order;
int cpu, ret = 0;
mutex_lock(&mutex);
if (hyp_tracing_is_on)
goto end;
/* First buffer used to store the address of all others */
buf = per_cpu_ptr(&trace_buf, 0);
if (!buf->va) {
args = (struct hyp_trace_rb_args *)__get_free_page(GFP_KERNEL);
buf->va = (unsigned long)args;
buf->order = 0;
order = 0;
} else {
args = (struct hyp_trace_rb_args *)buf->va;
order = buf->order;
}
if (!args) {
ret = -ENOMEM;
goto end;
}
/* The first buffer embedding the arguments is too small */
if (sizeof(*args) > PAGE_SIZE << order) {
ret = -EINVAL;
goto end;
}
for_each_possible_cpu(cpu) {
buf = per_cpu_ptr(&trace_buf, cpu);
if (!buf->va) {
buf->va = (unsigned long)__get_free_page(GFP_KERNEL);
buf->order = 0;
}
if (!buf->va) {
ret = -ENOMEM;
goto end;
}
args->kern_va[cpu] = buf->va;
args->order[cpu] = buf->order;
}
args->events = events_on;
ret = kvm_call_hyp_nvhe(__pkvm_start_tracing, args, order);
if (!ret) {
hyp_tracing_is_on = true;
for_each_possible_cpu(cpu) {
buf = per_cpu_ptr(&trace_buf, cpu);
if (buf->va)
buf->flags |= TRACE_BUF_RB_INIT;
}
}
end:
if (ret)
hyp_trace_free_buf();
mutex_unlock(&mutex);
return ret;
}
static void hyp_trace_stop(void)
{
mutex_lock(&mutex);
if (!hyp_tracing_is_on)
goto end;
kvm_call_hyp_nvhe(__pkvm_stop_tracing);
hyp_tracing_is_on = false;
end:
mutex_unlock(&mutex);
}
static ssize_t
hyp_tracing_on(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
char c;
int err;
if (cnt != 2)
return -EINVAL;
if (get_user(c, ubuf))
return -EFAULT;
switch (c) {
case '1':
err = hyp_trace_start();
if (err)
return err;
break;
case '0':
hyp_trace_stop();
break;
default:
return -EINVAL;
}
return cnt;
}
static const struct file_operations hyp_tracing_on_fops = {
.write = hyp_tracing_on,
};
struct trace_buf_iterator {
unsigned long read_idx[NR_CPUS];
int *pfn_to_cpu;
int min_pfn;
int max_pfn;
};
static void trace_buf_iterator_init(struct trace_buf_iterator *it)
{
int i, cpu, min_pfn = INT_MAX, max_pfn = INT_MIN, num_pfn = 0;
for_each_possible_cpu(cpu) {
struct trace_buf *buf = per_cpu_ptr(&trace_buf, cpu);
int pfn_start, pfn_end;
if (!buf->va)
continue;
pfn_start = virt_to_pfn(buf->va);
pfn_end = pfn_start + (1 << buf->order) - 1;
min_pfn = min(min_pfn, pfn_start);
max_pfn = max(max_pfn, pfn_end);
}
/* No trace_buf has been init. */
if (max_pfn == INT_MIN)
return;
it->min_pfn = min_pfn;
it->max_pfn = max_pfn;
num_pfn = it->max_pfn - it->min_pfn + 1;
it->pfn_to_cpu = kmalloc_array(num_pfn, sizeof(int), GFP_KERNEL);
for (i = 0; i < num_pfn; i++)
it->pfn_to_cpu[i] = -1;
for_each_possible_cpu(cpu) {
struct trace_buf *buf = per_cpu_ptr(&trace_buf, cpu);
int pfn = virt_to_pfn(buf->va);
int pfn_end = pfn + (1 << buf->order) - 1;
for (; pfn <= pfn_end; pfn++)
it->pfn_to_cpu[pfn - min_pfn] = cpu;
}
}
static void trace_buf_iterator_clear(struct trace_buf_iterator *it)
{
kfree(it->pfn_to_cpu);
}
static int trace_buf_va_to_cpu(struct trace_buf_iterator *it, void *buf_va)
{
int pfn = virt_to_pfn(buf_va);
if (pfn > it->max_pfn || pfn < it->min_pfn)
return -1;
return it->pfn_to_cpu[pfn - it->min_pfn];
}
static void *__ht_next(struct seq_file *m, loff_t *pos)
{
struct trace_buf_iterator *it = m->private;
struct hyp_trace_evt *next_evt = NULL;
int cpu, next_evt_cpu;
for_each_possible_cpu(cpu) {
struct trace_buf *buf = per_cpu_ptr(&trace_buf, cpu);
struct hyp_trace_rb *rb = (struct hyp_trace_rb *)buf->va;
struct hyp_trace_evt *evt;
int write_idx;
if (!rb || !(buf->flags & TRACE_BUF_RB_INIT))
continue;
write_idx = __hyp_trace_rb_next_idx(rb);
evt = rb->events + it->read_idx[cpu];
if (it->read_idx[cpu] >= write_idx)
continue;
if (next_evt && evt->timestamp >= next_evt->timestamp)
continue;
next_evt = evt;
next_evt_cpu = cpu;
}
if (next_evt)
it->read_idx[next_evt_cpu]++;
return next_evt;
}
static void *ht_start(struct seq_file *m, loff_t *pos)
{
if (*pos == 0)
return SEQ_START_TOKEN;
return __ht_next(m, pos);
}
static void *ht_next(struct seq_file *m, void *v, loff_t *pos)
{
(*pos)++;
return ht_start(m, pos);
}
static void ht_stop(struct seq_file *m, void *v) { }
static void ht_print_cpu(struct seq_file *m, struct hyp_trace_evt *evt_raw)
{
struct trace_buf_iterator *it = m->private;
int cpu = trace_buf_va_to_cpu(it, evt_raw);
seq_printf(m, "[%03d] ", cpu);
}
static void ht_print_time(struct seq_file *m, struct hyp_trace_evt *evt_raw)
{
unsigned long long t = __cnt_to_sched_clock(evt_raw->timestamp);
unsigned long secs, usecs_rem;
do_div(t, 1000);
usecs_rem = do_div(t, USEC_PER_SEC);
secs = (unsigned long)t;
seq_printf(m, "[%5lu.%06lu] ", secs, usecs_rem);
}
static int ht_show(struct seq_file *m, void *v)
{
struct hyp_trace_evt *evt_raw = v;
if (v == SEQ_START_TOKEN) {
int cpu;
seq_printf(m, "Tracing is %s\n", hyp_tracing_is_on ? "ON" : "OFF");
for_each_possible_cpu(cpu) {
struct trace_buf *buf = per_cpu_ptr(&trace_buf, cpu);
struct hyp_trace_rb *rb = (struct hyp_trace_rb *)buf->va;
if (!rb || !(buf->flags & TRACE_BUF_RB_INIT))
continue;
seq_printf(m, "CPU%d: write_idx=%d num_events=%d max_events=%llu",
cpu,
atomic_read(&rb->hdr.write_idx),
__hyp_trace_rb_next_idx(rb),
__hyp_trace_rb_max_entries(rb));
if (atomic_read(&rb->hdr.write_idx) > __hyp_trace_rb_max_entries(rb))
seq_puts(m, " WARNING: EVENTS LOST");
seq_puts(m, "\n");
}
return 0;
}
ht_print_time(m, evt_raw);
ht_print_cpu(m, evt_raw);
switch (evt_raw->id) {
case HYP_EVT_ENTER: {
struct trace_hyp_format_hyp_enter *evt =
(struct trace_hyp_format_hyp_enter *)&evt_raw->args;
seq_printf(m, "hyp_enter: esr=0x%016llx x0=0x%016llx vmid=%u\n",
evt->esr, evt->x0, evt->vmid);
break;
} case HYP_EVT_EXIT: {
seq_puts(m, "hyp_exit\n");
break;
} case HYP_EVT_POP_MEMCACHE: {
struct trace_hyp_format_pop_hyp_memcache *evt =
(struct trace_hyp_format_pop_hyp_memcache *)&evt_raw->args;
seq_printf(m, "pop_hyp_memcache: mc=0x%llx paddr=0x%llx nr_pages=%u\n",
evt->mc, evt->paddr, evt->nr_pages);
break;
} case HYP_EVT_PUSH_MEMCACHE: {
struct trace_hyp_format_push_hyp_memcache *evt =
(struct trace_hyp_format_push_hyp_memcache *)&evt_raw->args;
seq_printf(m, "push_hyp_memcache: mc=0x%llx paddr=0x%llx nr_pages=%u\n",
evt->mc, evt->paddr, evt->nr_pages);
break;
} case HYP_EVT_COALESCED_BLOCK: {
struct trace_hyp_format_hyp_coalesced *evt =
(struct trace_hyp_format_hyp_coalesced *)&evt_raw->args;
seq_printf(m, "hyp_coalesced: start=0x%016llx end=0x%016llx level=%u\n",
evt->addr, evt->end, evt->level);
break;
} default:
seq_printf(m, "UNKNOWN HYP EVENT ID:%d\n", evt_raw->id);
}
return 0;
}
static const struct seq_operations hyp_trace_ops = {
.start = ht_start,
.next = ht_next,
.stop = ht_stop,
.show = ht_show,
};
static int hyp_trace_open(struct inode *inode, struct file *file)
{
int ret = seq_open(file, &hyp_trace_ops);
struct seq_file *m = file->private_data;
struct trace_buf_iterator *it;
if (ret)
return ret;
mutex_lock(&mutex);
it = kzalloc(sizeof(*it), GFP_KERNEL);
trace_buf_iterator_init(it);
m->private = it;
return 0;
}
int hyp_trace_release(struct inode *inode, struct file *file)
{
struct trace_buf_iterator *it = ((struct seq_file *)file->private_data)->private;
trace_buf_iterator_clear(it);
kfree(it);
mutex_unlock(&mutex);
return seq_release(inode, file);
}
static const struct file_operations hyp_trace_fops = {
.open = hyp_trace_open,
.read = seq_read,
.llseek = seq_lseek,
.release = hyp_trace_release,
};
static void hyp_create_trace_debugfs(struct dentry *parent)
{
if (!debugfs_create_file("trace", 0600, parent,
NULL, &hyp_trace_fops))
pr_warn("Failed create hyp-tracing/trace\n");
}
static ssize_t
hyp_events_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
char c;
unsigned long event_id = (unsigned long)((struct seq_file *)filp->private_data)->private;
if (cnt != 2)
return -EINVAL;
if (get_user(c, ubuf))
return -EFAULT;
switch (c) {
case '1':
events_on |= (1 << event_id);
break;
case '0':
events_on &= ~(1 << event_id);
break;
default:
return -EINVAL;
}
return cnt;
}
static int hyp_events_show(struct seq_file *m, void *v)
{
unsigned long event_id = (unsigned long)m->private;
seq_printf(m, "%d\n", (events_on & (1 << event_id)) ? 1 : 0);
return 0;
}
static int hyp_events_open(struct inode *inode, struct file *filp)
{
return single_open(filp, hyp_events_show, inode->i_private);
}
static const struct file_operations hyp_events_fops = {
.open = hyp_events_open,
.write = hyp_events_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void hyp_create_events_debugfs(struct dentry *parent)
{
struct dentry *d = debugfs_create_dir("events", parent);
if (!d) {
pr_err("Failed to create debugfs dir events\n");
return;
}
#undef __ARM64_KVM_HYPEVENTS_H_
#undef HYP_EVENT
#define HYP_EVENT(__name, __id, __proto, __struct, __assign) \
debugfs_create_file(#__name, 0700, d, (void *)__id, &hyp_events_fops)
#include <asm/kvm_hypevents.h>
}
static int __init hyp_tracing_debugfs(void)
{
struct dentry *debugfs_folder, *d;
debugfs_folder = debugfs_create_dir("hyp-tracing", NULL);
if (!debugfs_folder) {
pr_err("Failed to create debugfs folder hyp-tracing\n");
return -ENODEV;
}
d = debugfs_create_file("tracing_on", 0700, debugfs_folder, NULL, &hyp_tracing_on_fops);
if (!d) {
pr_err("Failed to create file hyp-trace/tracing_on\n");
return -ENODEV;
}
hyp_create_events_debugfs(debugfs_folder);
hyp_create_trace_debugfs(debugfs_folder);
return 0;
}
late_initcall(hyp_tracing_debugfs);