root/arch/arm64/kvm/hyp_trace.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2025 Google LLC
 * Author: Vincent Donnefort <vdonnefort@google.com>
 */

#include <linux/cpumask.h>
#include <linux/trace_remote.h>
#include <linux/tracefs.h>
#include <linux/simple_ring_buffer.h>

#include <asm/arch_timer.h>
#include <asm/kvm_host.h>
#include <asm/kvm_hyptrace.h>
#include <asm/kvm_mmu.h>

#include "hyp_trace.h"

/* Same 10min used by clocksource when width is more than 32-bits */
#define CLOCK_MAX_CONVERSION_S  600
/*
 * Time to give for the clock init. Long enough to get a good mult/shift
 * estimation. Short enough to not delay the tracing start too much.
 */
#define CLOCK_INIT_MS           100
/*
 * Time between clock checks. Must be small enough to catch clock deviation when
 * it is still tiny.
 */
#define CLOCK_UPDATE_MS         500

static struct hyp_trace_clock {
        u64                     cycles;
        u64                     cyc_overflow64;
        u64                     boot;
        u32                     mult;
        u32                     shift;
        struct delayed_work     work;
        struct completion       ready;
} hyp_clock;

static void __hyp_clock_work(struct work_struct *work)
{
        struct delayed_work *dwork = to_delayed_work(work);
        struct hyp_trace_clock *hyp_clock;
        struct system_time_snapshot snap;
        u64 rate, delta_cycles;
        u64 boot, delta_boot;

        hyp_clock = container_of(dwork, struct hyp_trace_clock, work);

        ktime_get_snapshot_id(CLOCK_BOOTTIME, &snap);
        boot = ktime_to_ns(snap.systime);

        delta_boot = boot - hyp_clock->boot;
        delta_cycles = snap.cycles - hyp_clock->cycles;

        /* Compare hyp clock with the kernel boot clock */
        if (hyp_clock->mult) {
                u64 err, cur = delta_cycles;

                if (WARN_ON_ONCE(cur >= hyp_clock->cyc_overflow64)) {
                        __uint128_t tmp = (__uint128_t)cur * hyp_clock->mult;

                        cur = tmp >> hyp_clock->shift;
                } else {
                        cur *= hyp_clock->mult;
                        cur >>= hyp_clock->shift;
                }
                cur += hyp_clock->boot;

                err = abs_diff(cur, boot);
                /* No deviation, only update epoch if necessary */
                if (!err) {
                        if (delta_cycles >= (hyp_clock->cyc_overflow64 >> 1))
                                goto fast_forward;

                        goto resched;
                }

                /* Warn if the error is above tracing precision (1us) */
                if (err > NSEC_PER_USEC)
                        pr_warn_ratelimited("hyp trace clock off by %lluus\n",
                                            err / NSEC_PER_USEC);
        }

        rate = div64_u64(delta_cycles * NSEC_PER_SEC, delta_boot);

        clocks_calc_mult_shift(&hyp_clock->mult, &hyp_clock->shift,
                               rate, NSEC_PER_SEC, CLOCK_MAX_CONVERSION_S);

        /* Add a comfortable 50% margin */
        hyp_clock->cyc_overflow64 = (U64_MAX / hyp_clock->mult) >> 1;

fast_forward:
        hyp_clock->cycles = snap.cycles;
        hyp_clock->boot = boot;
        kvm_call_hyp_nvhe(__tracing_update_clock, hyp_clock->mult,
                          hyp_clock->shift, hyp_clock->boot, hyp_clock->cycles);
        complete(&hyp_clock->ready);

resched:
        schedule_delayed_work(&hyp_clock->work,
                              msecs_to_jiffies(CLOCK_UPDATE_MS));
}

static void hyp_trace_clock_enable(struct hyp_trace_clock *hyp_clock, bool enable)
{
        struct system_time_snapshot snap;

        if (!enable) {
                cancel_delayed_work_sync(&hyp_clock->work);
                return;
        }

        ktime_get_snapshot_id(CLOCK_BOOTTIME, &snap);

        hyp_clock->boot = ktime_to_ns(snap.systime);
        hyp_clock->cycles = snap.cycles;
        hyp_clock->mult = 0;

        init_completion(&hyp_clock->ready);
        INIT_DELAYED_WORK(&hyp_clock->work, __hyp_clock_work);
        schedule_delayed_work(&hyp_clock->work, msecs_to_jiffies(CLOCK_INIT_MS));
        wait_for_completion(&hyp_clock->ready);
}

/* Access to this struct within the trace_remote_callbacks are protected by the trace_remote lock */
static struct hyp_trace_buffer {
        struct hyp_trace_desc   *desc;
        size_t                  desc_size;
} trace_buffer;

static int __map_hyp(void *start, size_t size)
{
        if (is_protected_kvm_enabled())
                return 0;

        return create_hyp_mappings(start, start + size, PAGE_HYP);
}

static int __share_page(unsigned long va)
{
        return kvm_share_hyp((void *)va, (void *)va + 1);
}

static void __unshare_page(unsigned long va)
{
        kvm_unshare_hyp((void *)va, (void *)va + 1);
}

static int hyp_trace_buffer_alloc_bpages_backing(struct hyp_trace_buffer *trace_buffer, size_t size)
{
        int nr_bpages = (PAGE_ALIGN(size) / PAGE_SIZE) + 1;
        size_t backing_size;
        void *start;
        int ret;

        backing_size = PAGE_ALIGN(sizeof(struct simple_buffer_page) * nr_bpages *
                                  num_possible_cpus());

        start = alloc_pages_exact(backing_size, GFP_KERNEL_ACCOUNT);
        if (!start)
                return -ENOMEM;

        ret = __map_hyp(start, backing_size);
        if (ret) {
                free_pages_exact(start, backing_size);
                return ret;
        }

        trace_buffer->desc->bpages_backing_start = (unsigned long)start;
        trace_buffer->desc->bpages_backing_size = backing_size;

        return ret;
}

static void hyp_trace_buffer_free_bpages_backing(struct hyp_trace_buffer *trace_buffer)
{
        free_pages_exact((void *)trace_buffer->desc->bpages_backing_start,
                         trace_buffer->desc->bpages_backing_size);
}

static void hyp_trace_buffer_unshare_hyp(struct hyp_trace_buffer *trace_buffer, int last_cpu)
{
        struct ring_buffer_desc *rb_desc;
        int cpu, p;

        for_each_ring_buffer_desc(rb_desc, cpu, &trace_buffer->desc->trace_buffer_desc) {
                if (cpu > last_cpu)
                        break;

                __unshare_page(rb_desc->meta_va);
                for (p = 0; p < rb_desc->nr_page_va; p++)
                        __unshare_page(rb_desc->page_va[p]);
        }
}

static int hyp_trace_buffer_share_hyp(struct hyp_trace_buffer *trace_buffer)
{
        struct ring_buffer_desc *rb_desc;
        int cpu, p, ret = 0;

        for_each_ring_buffer_desc(rb_desc, cpu, &trace_buffer->desc->trace_buffer_desc) {
                ret = __share_page(rb_desc->meta_va);
                if (ret)
                        break;

                for (p = 0; p < rb_desc->nr_page_va; p++) {
                        ret = __share_page(rb_desc->page_va[p]);
                        if (ret)
                                break;
                }

                if (ret) {
                        while (--p >= 0)
                                __unshare_page(rb_desc->page_va[p]);
                        __unshare_page(rb_desc->meta_va);
                        break;
                }
        }

        if (ret)
                hyp_trace_buffer_unshare_hyp(trace_buffer, --cpu);

        return ret;
}

static struct trace_buffer_desc *hyp_trace_load(unsigned long size, void *priv)
{
        struct hyp_trace_buffer *trace_buffer = priv;
        size_t desc_size, tb_desc_size;
        struct hyp_trace_desc *desc;
        int ret;

        if (WARN_ON(trace_buffer->desc))
                return ERR_PTR(-EINVAL);

        tb_desc_size = trace_buffer_desc_size(size, num_possible_cpus());
        desc_size = size_add(tb_desc_size, offsetof(struct hyp_trace_desc, trace_buffer_desc));
        if (desc_size == SIZE_MAX)
                return ERR_PTR(-E2BIG);

        desc_size = PAGE_ALIGN(desc_size);
        if (!desc_size)
                return ERR_PTR(-E2BIG);

        desc = (struct hyp_trace_desc *)alloc_pages_exact(desc_size, GFP_KERNEL);
        if (!desc)
                return ERR_PTR(-ENOMEM);

        ret = __map_hyp(desc, desc_size);
        if (ret)
                goto err_free_desc;

        trace_buffer->desc = desc;
        trace_buffer->desc_size = desc_size;

        ret = hyp_trace_buffer_alloc_bpages_backing(trace_buffer, size);
        if (ret)
                goto err_free_desc;

        ret = trace_remote_alloc_buffer(&desc->trace_buffer_desc, tb_desc_size, size,
                                        cpu_possible_mask);
        if (ret)
                goto err_free_backing;

        ret = hyp_trace_buffer_share_hyp(trace_buffer);
        if (ret)
                goto err_free_buffer;

        ret = kvm_call_hyp_nvhe(__tracing_load, (unsigned long)desc, desc_size);
        if (ret)
                goto err_unload_pages;

        return &desc->trace_buffer_desc;

err_unload_pages:
        hyp_trace_buffer_unshare_hyp(trace_buffer, INT_MAX);

err_free_buffer:
        trace_remote_free_buffer(&desc->trace_buffer_desc);

err_free_backing:
        hyp_trace_buffer_free_bpages_backing(trace_buffer);

err_free_desc:
        free_pages_exact(desc, desc_size);
        trace_buffer->desc = NULL;

        return ERR_PTR(ret);
}

static void hyp_trace_unload(struct trace_buffer_desc *desc, void *priv)
{
        struct hyp_trace_buffer *trace_buffer = priv;

        if (WARN_ON(desc != &trace_buffer->desc->trace_buffer_desc))
                return;

        kvm_call_hyp_nvhe(__tracing_unload);
        hyp_trace_buffer_unshare_hyp(trace_buffer, INT_MAX);
        trace_remote_free_buffer(desc);
        hyp_trace_buffer_free_bpages_backing(trace_buffer);
        free_pages_exact(trace_buffer->desc, trace_buffer->desc_size);
        trace_buffer->desc = NULL;
        trace_buffer->desc_size = 0;
}

static int hyp_trace_enable_tracing(bool enable, void *priv)
{
        int ret;

        hyp_trace_clock_enable(&hyp_clock, enable);

        ret = kvm_call_hyp_nvhe(__tracing_enable, enable);
        if (ret)
                hyp_trace_clock_enable(&hyp_clock, !enable);

        return ret;
}

static int hyp_trace_swap_reader_page(unsigned int cpu, void *priv)
{
        return kvm_call_hyp_nvhe(__tracing_swap_reader, cpu);
}

static int hyp_trace_reset(unsigned int cpu, void *priv)
{
        return kvm_call_hyp_nvhe(__tracing_reset, cpu);
}

static int hyp_trace_enable_event(unsigned short id, bool enable, void *priv)
{
        struct hyp_event_id *event_id = lm_alias(&__hyp_event_ids_start[id]);
        struct page *page;
        atomic_t *enabled;
        void *map;

        if (is_protected_kvm_enabled())
                return kvm_call_hyp_nvhe(__tracing_enable_event, id, enable);

        enabled = &event_id->enabled;
        page = virt_to_page(enabled);
        map = vmap(&page, 1, VM_MAP, PAGE_KERNEL);
        if (!map)
                return -ENOMEM;

        enabled = map + offset_in_page(enabled);
        atomic_set(enabled, enable);

        vunmap(map);

        return 0;
}

static int hyp_trace_clock_show(struct seq_file *m, void *v)
{
        seq_puts(m, "[boot]\n");

        return 0;
}
DEFINE_SHOW_ATTRIBUTE(hyp_trace_clock);

static ssize_t hyp_trace_write_event_write(struct file *f, const char __user *ubuf,
                                           size_t cnt, loff_t *pos)
{
        unsigned long val;
        int ret;

        ret = kstrtoul_from_user(ubuf, cnt, 10, &val);
        if (ret)
                return ret;

        kvm_call_hyp_nvhe(__tracing_write_event, val);

        return cnt;
}

static const struct file_operations hyp_trace_write_event_fops = {
        .write  = hyp_trace_write_event_write,
};

static int hyp_trace_init_tracefs(struct dentry *d, void *priv)
{
        if (!tracefs_create_file("write_event", 0200, d, NULL, &hyp_trace_write_event_fops))
                return -ENOMEM;

        return tracefs_create_file("trace_clock", 0440, d, NULL, &hyp_trace_clock_fops) ?
                0 : -ENOMEM;
}

static struct trace_remote_callbacks trace_remote_callbacks = {
        .init                   = hyp_trace_init_tracefs,
        .load_trace_buffer      = hyp_trace_load,
        .unload_trace_buffer    = hyp_trace_unload,
        .enable_tracing         = hyp_trace_enable_tracing,
        .swap_reader_page       = hyp_trace_swap_reader_page,
        .reset                  = hyp_trace_reset,
        .enable_event           = hyp_trace_enable_event,
};

static const char *__hyp_enter_exit_reason_str(u8 reason);

#include <asm/kvm_define_hypevents.h>

static const char *__hyp_enter_exit_reason_str(u8 reason)
{
        static const char strs[][12] = {
                "smc",
                "hvc",
                "sys",
                "psci",
                "host_abort",
                "guest_exit",
                "eret_host",
                "eret_guest",
                "unknown",
        };

        return strs[min(reason, HYP_REASON_UNKNOWN)];
}

static void __init hyp_trace_init_events(void)
{
        struct hyp_event_id *hyp_event_id = __hyp_event_ids_start;
        struct remote_event *event = __hyp_events_start;
        int id = 0;

        /* Events on both sides hypervisor are sorted */
        for (; event < __hyp_events_end; event++, hyp_event_id++, id++)
                event->id = hyp_event_id->id = id;
}

int __init kvm_hyp_trace_init(void)
{
        int cpu;

        if (is_kernel_in_hyp_mode())
                return 0;

        for_each_possible_cpu(cpu) {
                const struct arch_timer_erratum_workaround *wa =
                        per_cpu(timer_unstable_counter_workaround, cpu);

                if (IS_ENABLED(CONFIG_ARM_ARCH_TIMER_OOL_WORKAROUND) &&
                    wa && wa->read_cntvct_el0) {
                        pr_warn("hyp trace can't handle CNTVCT workaround '%s'\n", wa->desc);
                        return -EOPNOTSUPP;
                }
        }

        hyp_trace_init_events();

        return trace_remote_register("hypervisor", &trace_remote_callbacks, &trace_buffer,
                                     __hyp_events_start, __hyp_events_end - __hyp_events_start);
}