// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2023 Google LLC */ #include #include #include #include #include #include "hyp_trace.h" #define HYP_EVENT_NAME_MAX 32 struct hyp_event { struct trace_event_call *call; char name[HYP_EVENT_NAME_MAX]; bool *enabled; }; #define HYP_EVENT(__name, __proto, __struct, __assign, __printk) \ HYP_EVENT_FORMAT(__name, __struct); \ enum print_line_t hyp_event_trace_##__name(struct trace_iterator *iter, \ int flags, struct trace_event *event) \ { \ struct ht_iterator *ht_iter = (struct ht_iterator *)iter; \ struct trace_hyp_format_##__name __maybe_unused *__entry = \ (struct trace_hyp_format_##__name *)ht_iter->ent; \ trace_seq_puts(&ht_iter->seq, #__name); \ trace_seq_putc(&ht_iter->seq, ' '); \ trace_seq_printf(&ht_iter->seq, __printk); \ trace_seq_putc(&ht_iter->seq, '\n'); \ return TRACE_TYPE_HANDLED; \ } #include #undef he_field #define he_field(_type, _item) \ { \ .type = #_type, .name = #_item, \ .size = sizeof(_type), .align = __alignof__(_type), \ .is_signed = is_signed_type(_type), \ }, #undef HYP_EVENT #define HYP_EVENT(__name, __proto, __struct, __assign, __printk) \ static struct trace_event_fields hyp_event_fields_##__name[] = { \ __struct \ {} \ }; \ #undef __ARM64_KVM_HYPEVENTS_H_ #include #undef HYP_EVENT #undef HE_PRINTK #define __entry REC #define HE_PRINTK(fmt, args...) "\"" fmt "\", " __stringify(args) #define HYP_EVENT(__name, __proto, __struct, __assign, __printk) \ static char hyp_event_print_fmt_##__name[] = __printk; \ static struct trace_event_functions hyp_event_funcs_##__name = { \ .trace = &hyp_event_trace_##__name, \ }; \ static struct trace_event_class hyp_event_class_##__name = { \ .system = "nvhe-hypervisor", \ .fields_array = hyp_event_fields_##__name, \ .fields = LIST_HEAD_INIT(hyp_event_class_##__name.fields),\ }; \ static struct trace_event_call hyp_event_call_##__name = { \ .class = &hyp_event_class_##__name, \ .event.funcs = &hyp_event_funcs_##__name, \ .print_fmt = hyp_event_print_fmt_##__name, \ }; \ static bool hyp_event_enabled_##__name; \ struct hyp_event __section("_hyp_events") hyp_event_##__name = { \ .name = #__name, \ .call = &hyp_event_call_##__name, \ .enabled = &hyp_event_enabled_##__name, \ } #undef __ARM64_KVM_HYPEVENTS_H_ #include extern struct hyp_event __hyp_events_start[]; extern struct hyp_event __hyp_events_end[]; /* hyp_event section used by the hypervisor */ extern struct hyp_event_id __hyp_event_ids_start[]; extern struct hyp_event_id __hyp_event_ids_end[]; static struct hyp_event *find_hyp_event(const char *name) { struct hyp_event *event = __hyp_events_start; for (; (unsigned long)event < (unsigned long)__hyp_events_end; event++) { if (!strncmp(name, event->name, HYP_EVENT_NAME_MAX)) return event; } return NULL; } static int enable_hyp_event(struct hyp_event *event, bool enable) { unsigned short id = event->call->event.type; int ret; if (enable == *event->enabled) return 0; ret = kvm_call_hyp_nvhe(__pkvm_enable_event, id, enable); if (ret) return ret; *event->enabled = enable; return 0; } static ssize_t hyp_event_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { struct seq_file *seq_file = (struct seq_file *)filp->private_data; struct hyp_event *evt = (struct hyp_event *)seq_file->private; bool enabling; int ret; char c; if (!cnt || cnt > 2) return -EINVAL; if (get_user(c, ubuf)) return -EFAULT; switch (c) { case '1': enabling = true; break; case '0': enabling = false; break; default: return -EINVAL; } ret = enable_hyp_event(evt, enabling); if (ret) return ret; return cnt; } static int hyp_event_show(struct seq_file *m, void *v) { struct hyp_event *evt = (struct hyp_event *)m->private; /* lock ?? Ain't no time for that ! */ seq_printf(m, "%d\n", *evt->enabled); return 0; } static int hyp_event_open(struct inode *inode, struct file *filp) { return single_open(filp, hyp_event_show, inode->i_private); } static const struct file_operations hyp_event_fops = { .open = hyp_event_open, .write = hyp_event_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int hyp_event_id_show(struct seq_file *m, void *v) { struct hyp_event *evt = (struct hyp_event *)m->private; seq_printf(m, "%d\n", evt->call->event.type); return 0; } static int hyp_event_id_open(struct inode *inode, struct file *filp) { return single_open(filp, hyp_event_id_show, inode->i_private); } static const struct file_operations hyp_event_id_fops = { .open = hyp_event_id_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int hyp_event_format_show(struct seq_file *m, void *v) { struct hyp_event *evt = (struct hyp_event *)m->private; struct trace_event_fields *field; unsigned int offset = sizeof(struct hyp_entry_hdr); seq_printf(m, "name: %s\n", evt->name); seq_printf(m, "ID: %d\n", evt->call->event.type); seq_puts(m, "format:\n\tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n"); seq_puts(m, "\n"); field = &evt->call->class->fields_array[0]; while (field->name) { seq_printf(m, "\tfield:%s %s;\toffset:%u;\tsize:%u;\tsigned:%d;\n", field->type, field->name, offset, field->size, !!field->is_signed); offset += field->size; field++; } if (field != &evt->call->class->fields_array[0]) seq_puts(m, "\n"); seq_printf(m, "print fmt: %s\n", evt->call->print_fmt); return 0; } static int hyp_event_format_open(struct inode *inode, struct file *file) { return single_open(file, hyp_event_format_show, inode->i_private); } static const struct file_operations hyp_event_format_fops = { .open = hyp_event_format_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int hyp_header_page_show(struct seq_file *m, void *v) { struct buffer_data_page bpage; seq_printf(m, "\tfield: u64 timestamp;\t" "offset:0;\tsize:%lu;\tsigned:%d;\n", sizeof(bpage.time_stamp), is_signed_type(u64)); seq_printf(m, "\tfield: local_t commit;\t" "offset:%lu;\tsize:%lu;\tsigned:%d;\n", offsetof(typeof(bpage), commit), sizeof(bpage.commit), is_signed_type(long)); seq_printf(m, "\tfield: char data;\t" "offset:%lu;\tsize:%lu;\tsigned:%d;\n", offsetof(typeof(bpage), data), BUF_EXT_PAGE_SIZE, is_signed_type(char)); return 0; } static int hyp_header_page_open(struct inode *inode, struct file *file) { return single_open(file, hyp_header_page_show, NULL); } static const struct file_operations hyp_header_page_fops = { .open = hyp_header_page_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static char early_events[COMMAND_LINE_SIZE]; static __init int setup_hyp_event_early(char *str) { strscpy(early_events, str, COMMAND_LINE_SIZE); return 1; } __setup("hyp_event=", setup_hyp_event_early); bool kvm_hyp_events_enable_early(void) { char *token, *buf = early_events; bool enabled = false; while (true) { token = strsep(&buf, ","); if (!token) break; if (*token) { struct hyp_event *event; int ret; event = find_hyp_event(token); if (event) { ret = enable_hyp_event(event, true); if (ret) pr_warn("Couldn't enable hyp event %s:%d\n", token, ret); else enabled = true; } else { pr_warn("Couldn't find hyp event %s\n", token); } } if (buf) *(buf - 1) = ','; } return enabled; } void kvm_hyp_init_events_tracefs(struct dentry *parent) { struct hyp_event *event = __hyp_events_start; struct dentry *d, *event_dir; parent = tracefs_create_dir("events", parent); if (!parent) { pr_err("Failed to create tracefs folder for hyp events\n"); return; } d = tracefs_create_file("header_page", 0400, parent, NULL, &hyp_header_page_fops); if (!d) pr_err("Failed to create events/header_page\n"); parent = tracefs_create_dir("hyp", parent); if (!parent) { pr_err("Failed to create tracefs folder for hyp events\n"); return; } for (; (unsigned long)event < (unsigned long)__hyp_events_end; event++) { event_dir = tracefs_create_dir(event->name, parent); if (!event_dir) { pr_err("Failed to create events/hyp/%s\n", event->name); continue; } d = tracefs_create_file("enable", 0700, event_dir, (void *)event, &hyp_event_fops); if (!d) pr_err("Failed to create events/hyp/%s/enable\n", event->name); d = tracefs_create_file("id", 0400, event_dir, (void *)event, &hyp_event_id_fops); if (!d) pr_err("Failed to create events/hyp/%s/id\n", event->name); d = tracefs_create_file("format", 0400, event_dir, (void *)event, &hyp_event_format_fops); if (!d) pr_err("Failed to create events/hyp/%s/format\n", event->name); } } /* * Register hyp events and write their id into the hyp section _hyp_event_ids. */ int kvm_hyp_init_events(void) { struct hyp_event *event = __hyp_events_start; struct hyp_event_id *hyp_event_id = __hyp_event_ids_start; int ret, err = -ENODEV; /* TODO: BUILD_BUG nr events host side / hyp side */ for (; (unsigned long)event < (unsigned long)__hyp_events_end; event++, hyp_event_id++) { event->call->name = event->name; ret = register_trace_event(&event->call->event); if (!ret) { pr_warn("Couldn't register trace event for %s\n", event->name); continue; } /* * Both the host and the hypervisor relies on the same hyp event * declarations from kvm_hypevents.h. We have then a 1:1 * mapping. */ hyp_event_id->id = ret; err = 0; } return err; }