mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Make the hypervisor reset either the whole tracing buffer or a specific ring-buffer, on remotes/hypervisor/trace or per_cpu/<cpu>/trace write access. Signed-off-by: Vincent Donnefort <vdonnefort@google.com> Link: https://patch.msgid.link/20260309162516.2623589-27-vdonnefort@google.com Signed-off-by: Marc Zyngier <maz@kernel.org>
307 lines
6.8 KiB
C
307 lines
6.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2025 Google LLC
|
|
* Author: Vincent Donnefort <vdonnefort@google.com>
|
|
*/
|
|
|
|
#include <nvhe/clock.h>
|
|
#include <nvhe/mem_protect.h>
|
|
#include <nvhe/mm.h>
|
|
#include <nvhe/trace.h>
|
|
|
|
#include <asm/percpu.h>
|
|
#include <asm/kvm_mmu.h>
|
|
#include <asm/local.h>
|
|
|
|
#include "simple_ring_buffer.c"
|
|
|
|
static DEFINE_PER_CPU(struct simple_rb_per_cpu, __simple_rbs);
|
|
|
|
static struct hyp_trace_buffer {
|
|
struct simple_rb_per_cpu __percpu *simple_rbs;
|
|
void *bpages_backing_start;
|
|
size_t bpages_backing_size;
|
|
hyp_spinlock_t lock;
|
|
} trace_buffer = {
|
|
.simple_rbs = &__simple_rbs,
|
|
.lock = __HYP_SPIN_LOCK_UNLOCKED,
|
|
};
|
|
|
|
static bool hyp_trace_buffer_loaded(struct hyp_trace_buffer *trace_buffer)
|
|
{
|
|
return trace_buffer->bpages_backing_size > 0;
|
|
}
|
|
|
|
void *tracing_reserve_entry(unsigned long length)
|
|
{
|
|
return simple_ring_buffer_reserve(this_cpu_ptr(trace_buffer.simple_rbs), length,
|
|
trace_clock());
|
|
}
|
|
|
|
void tracing_commit_entry(void)
|
|
{
|
|
simple_ring_buffer_commit(this_cpu_ptr(trace_buffer.simple_rbs));
|
|
}
|
|
|
|
static int __admit_host_mem(void *start, u64 size)
|
|
{
|
|
if (!PAGE_ALIGNED(start) || !PAGE_ALIGNED(size) || !size)
|
|
return -EINVAL;
|
|
|
|
if (!is_protected_kvm_enabled())
|
|
return 0;
|
|
|
|
return __pkvm_host_donate_hyp(hyp_virt_to_pfn(start), size >> PAGE_SHIFT);
|
|
}
|
|
|
|
static void __release_host_mem(void *start, u64 size)
|
|
{
|
|
if (!is_protected_kvm_enabled())
|
|
return;
|
|
|
|
WARN_ON(__pkvm_hyp_donate_host(hyp_virt_to_pfn(start), size >> PAGE_SHIFT));
|
|
}
|
|
|
|
static int hyp_trace_buffer_load_bpage_backing(struct hyp_trace_buffer *trace_buffer,
|
|
struct hyp_trace_desc *desc)
|
|
{
|
|
void *start = (void *)kern_hyp_va(desc->bpages_backing_start);
|
|
size_t size = desc->bpages_backing_size;
|
|
int ret;
|
|
|
|
ret = __admit_host_mem(start, size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
memset(start, 0, size);
|
|
|
|
trace_buffer->bpages_backing_start = start;
|
|
trace_buffer->bpages_backing_size = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hyp_trace_buffer_unload_bpage_backing(struct hyp_trace_buffer *trace_buffer)
|
|
{
|
|
void *start = trace_buffer->bpages_backing_start;
|
|
size_t size = trace_buffer->bpages_backing_size;
|
|
|
|
if (!size)
|
|
return;
|
|
|
|
memset(start, 0, size);
|
|
|
|
__release_host_mem(start, size);
|
|
|
|
trace_buffer->bpages_backing_start = 0;
|
|
trace_buffer->bpages_backing_size = 0;
|
|
}
|
|
|
|
static void *__pin_shared_page(unsigned long kern_va)
|
|
{
|
|
void *va = kern_hyp_va((void *)kern_va);
|
|
|
|
if (!is_protected_kvm_enabled())
|
|
return va;
|
|
|
|
return hyp_pin_shared_mem(va, va + PAGE_SIZE) ? NULL : va;
|
|
}
|
|
|
|
static void __unpin_shared_page(void *va)
|
|
{
|
|
if (!is_protected_kvm_enabled())
|
|
return;
|
|
|
|
hyp_unpin_shared_mem(va, va + PAGE_SIZE);
|
|
}
|
|
|
|
static void hyp_trace_buffer_unload(struct hyp_trace_buffer *trace_buffer)
|
|
{
|
|
int cpu;
|
|
|
|
hyp_assert_lock_held(&trace_buffer->lock);
|
|
|
|
if (!hyp_trace_buffer_loaded(trace_buffer))
|
|
return;
|
|
|
|
for (cpu = 0; cpu < hyp_nr_cpus; cpu++)
|
|
simple_ring_buffer_unload_mm(per_cpu_ptr(trace_buffer->simple_rbs, cpu),
|
|
__unpin_shared_page);
|
|
|
|
hyp_trace_buffer_unload_bpage_backing(trace_buffer);
|
|
}
|
|
|
|
static int hyp_trace_buffer_load(struct hyp_trace_buffer *trace_buffer,
|
|
struct hyp_trace_desc *desc)
|
|
{
|
|
struct simple_buffer_page *bpages;
|
|
struct ring_buffer_desc *rb_desc;
|
|
int ret, cpu;
|
|
|
|
hyp_assert_lock_held(&trace_buffer->lock);
|
|
|
|
if (hyp_trace_buffer_loaded(trace_buffer))
|
|
return -EINVAL;
|
|
|
|
ret = hyp_trace_buffer_load_bpage_backing(trace_buffer, desc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
bpages = trace_buffer->bpages_backing_start;
|
|
for_each_ring_buffer_desc(rb_desc, cpu, &desc->trace_buffer_desc) {
|
|
ret = simple_ring_buffer_init_mm(per_cpu_ptr(trace_buffer->simple_rbs, cpu),
|
|
bpages, rb_desc, __pin_shared_page,
|
|
__unpin_shared_page);
|
|
if (ret)
|
|
break;
|
|
|
|
bpages += rb_desc->nr_page_va;
|
|
}
|
|
|
|
if (ret)
|
|
hyp_trace_buffer_unload(trace_buffer);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool hyp_trace_desc_validate(struct hyp_trace_desc *desc, size_t desc_size)
|
|
{
|
|
struct ring_buffer_desc *rb_desc;
|
|
unsigned int cpu;
|
|
size_t nr_bpages;
|
|
void *desc_end;
|
|
|
|
/*
|
|
* Both desc_size and bpages_backing_size are untrusted host-provided
|
|
* values. We rely on __pkvm_host_donate_hyp() to enforce their validity.
|
|
*/
|
|
desc_end = (void *)desc + desc_size;
|
|
nr_bpages = desc->bpages_backing_size / sizeof(struct simple_buffer_page);
|
|
|
|
for_each_ring_buffer_desc(rb_desc, cpu, &desc->trace_buffer_desc) {
|
|
/* Can we read nr_page_va? */
|
|
if ((void *)rb_desc + struct_size(rb_desc, page_va, 0) > desc_end)
|
|
return false;
|
|
|
|
/* Overflow desc? */
|
|
if ((void *)rb_desc + struct_size(rb_desc, page_va, rb_desc->nr_page_va) > desc_end)
|
|
return false;
|
|
|
|
/* Overflow bpages backing memory? */
|
|
if (nr_bpages < rb_desc->nr_page_va)
|
|
return false;
|
|
|
|
if (cpu >= hyp_nr_cpus)
|
|
return false;
|
|
|
|
if (cpu != rb_desc->cpu)
|
|
return false;
|
|
|
|
nr_bpages -= rb_desc->nr_page_va;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int __tracing_load(unsigned long desc_hva, size_t desc_size)
|
|
{
|
|
struct hyp_trace_desc *desc = (struct hyp_trace_desc *)kern_hyp_va(desc_hva);
|
|
int ret;
|
|
|
|
ret = __admit_host_mem(desc, desc_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!hyp_trace_desc_validate(desc, desc_size))
|
|
goto err_release_desc;
|
|
|
|
hyp_spin_lock(&trace_buffer.lock);
|
|
|
|
ret = hyp_trace_buffer_load(&trace_buffer, desc);
|
|
|
|
hyp_spin_unlock(&trace_buffer.lock);
|
|
|
|
err_release_desc:
|
|
__release_host_mem(desc, desc_size);
|
|
return ret;
|
|
}
|
|
|
|
void __tracing_unload(void)
|
|
{
|
|
hyp_spin_lock(&trace_buffer.lock);
|
|
hyp_trace_buffer_unload(&trace_buffer);
|
|
hyp_spin_unlock(&trace_buffer.lock);
|
|
}
|
|
|
|
int __tracing_enable(bool enable)
|
|
{
|
|
int cpu, ret = enable ? -EINVAL : 0;
|
|
|
|
hyp_spin_lock(&trace_buffer.lock);
|
|
|
|
if (!hyp_trace_buffer_loaded(&trace_buffer))
|
|
goto unlock;
|
|
|
|
for (cpu = 0; cpu < hyp_nr_cpus; cpu++)
|
|
simple_ring_buffer_enable_tracing(per_cpu_ptr(trace_buffer.simple_rbs, cpu),
|
|
enable);
|
|
|
|
ret = 0;
|
|
|
|
unlock:
|
|
hyp_spin_unlock(&trace_buffer.lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __tracing_swap_reader(unsigned int cpu)
|
|
{
|
|
int ret = -ENODEV;
|
|
|
|
if (cpu >= hyp_nr_cpus)
|
|
return -EINVAL;
|
|
|
|
hyp_spin_lock(&trace_buffer.lock);
|
|
|
|
if (hyp_trace_buffer_loaded(&trace_buffer))
|
|
ret = simple_ring_buffer_swap_reader_page(
|
|
per_cpu_ptr(trace_buffer.simple_rbs, cpu));
|
|
|
|
hyp_spin_unlock(&trace_buffer.lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void __tracing_update_clock(u32 mult, u32 shift, u64 epoch_ns, u64 epoch_cyc)
|
|
{
|
|
int cpu;
|
|
|
|
/* After this loop, all CPUs are observing the new bank... */
|
|
for (cpu = 0; cpu < hyp_nr_cpus; cpu++) {
|
|
struct simple_rb_per_cpu *simple_rb = per_cpu_ptr(trace_buffer.simple_rbs, cpu);
|
|
|
|
while (READ_ONCE(simple_rb->status) == SIMPLE_RB_WRITING)
|
|
;
|
|
}
|
|
|
|
/* ...we can now override the old one and swap. */
|
|
trace_clock_update(mult, shift, epoch_ns, epoch_cyc);
|
|
}
|
|
|
|
int __tracing_reset(unsigned int cpu)
|
|
{
|
|
int ret = -ENODEV;
|
|
|
|
if (cpu >= hyp_nr_cpus)
|
|
return -EINVAL;
|
|
|
|
hyp_spin_lock(&trace_buffer.lock);
|
|
|
|
if (hyp_trace_buffer_loaded(&trace_buffer))
|
|
ret = simple_ring_buffer_reset(per_cpu_ptr(trace_buffer.simple_rbs, cpu));
|
|
|
|
hyp_spin_unlock(&trace_buffer.lock);
|
|
|
|
return ret;
|
|
}
|