diff --git a/Documentation/virt/aura-pv.rst b/Documentation/virt/aura-pv.rst new file mode 100644 index 0000000000000..8be7a5a92ec40 --- /dev/null +++ b/Documentation/virt/aura-pv.rst @@ -0,0 +1,27 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Aura PV-KVM discovery client +============================ + +``CONFIG_AURA_PV`` builds a small, opt-in diagnostic client for Aura's +physical-EL1 paravirtualization ABI. The client only probes the interface +when the device tree contains ``aurora,aura-kvm-pv-0``; ordinary boots do not +issue any Aura HVC calls. + +The module creates ``/dev/aura-pv`` and provides two ioctls from +``include/uapi/linux/aura_pv.h``: + +* ``AURA_PV_IOC_DISCOVER`` returns the negotiated ABI and feature bitmap. +* ``AURA_PV_IOC_RUN`` submits a bounded integer program (up to eight unsigned + additions) and returns the result plus host-register preservation sentinels. + +This is a bring-up ABI probe, not a KVM implementation. It does not claim +guest EL1 entry, stage-2 memory translation, virtual interrupt delivery, +timer preemption, or compatibility with ``/dev/kvm``. Those capabilities +remain disabled until Aura and Linux have a complete state-switch protocol. +The shared control block is exactly 4096 bytes so it works with both 4 KiB +and 16 KiB Linux page configurations. + +The module is intentionally not installed or enabled by default. A kernel +must advertise ``CONFIG_AURA_PV=m`` (or ``y``) and Aura must have been booted +with its PV mode enabled. diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index 25c4ee4183cc6..eb14d4d2d334d 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -138,3 +138,7 @@ CONFIG_DMABUF_HEAPS=y CONFIG_DMABUF_HEAPS_SYSTEM=y CONFIG_UDMABUF=y CONFIG_DETECT_HUNG_TASK=y + +# Opt-in Aura PV-KVM diagnostic client; no device without Aura DT advertisement. +CONFIG_VIRT_DRIVERS=y +CONFIG_AURA_PV=m diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig index 52eb7e4ba71f6..ec94daf708768 100644 --- a/drivers/virt/Kconfig +++ b/drivers/virt/Kconfig @@ -13,6 +13,14 @@ menuconfig VIRT_DRIVERS if VIRT_DRIVERS +config AURA_PV + tristate "Aura PV-KVM diagnostic client" + depends on ARM64 + default m + help + Build the opt-in Aura PV-KVM v0 discovery/RUN client. The module + binds only when Aura publishes its compatible DT node. + config VMGENID tristate "Virtual Machine Generation ID driver" default y diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile index f29901bd78205..4a52e5faf1700 100644 --- a/drivers/virt/Makefile +++ b/drivers/virt/Makefile @@ -4,6 +4,7 @@ # obj-$(CONFIG_FSL_HV_MANAGER) += fsl_hypervisor.o +obj-$(CONFIG_AURA_PV) += aura_pv.o obj-$(CONFIG_VMGENID) += vmgenid.o obj-y += vboxguest/ diff --git a/drivers/virt/aura_pv.c b/drivers/virt/aura_pv.c new file mode 100644 index 0000000000000..8b22e10c69b47 --- /dev/null +++ b/drivers/virt/aura_pv.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Opt-in Aura PV-KVM v0 diagnostic client. No device exists without the Aura DT node. */ +#include +#include +#include +#include +#include +#include +#include +#include + +struct aura_pv_page { + u32 major; + u32 minor; + u32 bytes; + u32 layout_id; + u64 request_seq; + u64 response_seq; + u64 input_dirty; + u64 output_valid; + u64 guest_magic; + u64 program_words; + u64 initial; + u64 result; + u64 program[AURA_PV_MAX_PROGRAM]; + u64 host_sctlr_before; + u64 host_sctlr_after; + u64 host_cntkctl_before; + u64 host_cntkctl_after; + u64 host_sentinel_status; + u64 exit_reason; + u64 exit_flags; + u8 reserved[AURA_PV_PAGE_BYTES - 200]; +} __packed __aligned(64); + +static_assert(sizeof(struct aura_pv_page) == AURA_PV_PAGE_BYTES); + +struct aura_pv_dev { + struct miscdevice misc; + struct aura_pv_caps caps; + bool available; +}; + +static struct aura_pv_dev aura_pv; + +static void aura_pv_hvc(unsigned long fid, unsigned long arg1, + unsigned long arg2, unsigned long arg3, + unsigned long *ret0, unsigned long *ret1, + unsigned long *ret2, unsigned long *ret3) +{ + register unsigned long x0 asm("x0") = fid; + register unsigned long x1 asm("x1") = arg1; + register unsigned long x2 asm("x2") = arg2; + register unsigned long x3 asm("x3") = arg3; + register unsigned long x19 asm("x19") = 0x1919191919191919ULL; + register unsigned long x20 asm("x20") = 0x2020202020202020ULL; + asm volatile("hvc #0" + : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3), "+r"(x19), + "+r"(x20) + : + : "memory"); + *ret0 = x0; + *ret1 = x1; + *ret2 = x2; + *ret3 = x3; +} + +static int aura_pv_discover(struct aura_pv_dev *dev) +{ + BUILD_BUG_ON(sizeof(struct aura_pv_page) != AURA_PV_PAGE_BYTES); + unsigned long r0, r1, r2, r3; + struct page *page; + struct aura_pv_caps *caps; + phys_addr_t pa; + int ret = -ENODEV; + + aura_pv_hvc(AURA_PV_FUNC_UID, 0, 0, 0, &r0, &r1, &r2, &r3); + if ((long)r0) + return (long)r0 == -38 ? -ENODEV : -EPROTO; + if (r1 != 0x4155524f52502b56ULL || r2 != 0x504b564d2d454c31ULL) + return -EPROTO; + aura_pv_hvc(AURA_PV_FUNC_VERSION, 0, 0, 0, &r0, &r1, &r2, &r3); + if ((long)r0 || r1 != AURA_PV_ABI_MAJOR || r2 != AURA_PV_ABI_MINOR) + return -EPROTO; + + page = alloc_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + caps = page_address(page); + memset(caps, 0, PAGE_SIZE); + pa = page_to_phys(page); + /* Aura's ABI block is exactly 4 KiB even when Linux uses 16 KiB pages. */ + aura_pv_hvc(AURA_PV_FUNC_GET_CAPS, pa, AURA_PV_PAGE_BYTES, 0, &r0, &r1, + &r2, &r3); + if (!(long)r0 && caps->layout_id == AURA_PV_LAYOUT_CAPS && + caps->bytes == sizeof(*caps)) { + memcpy(&dev->caps, caps, sizeof(dev->caps)); + dev->available = true; + ret = 0; + } else { + ret = -EPROTO; + } + __free_page(page); + return ret; +} + +static long aura_pv_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct aura_pv_dev *dev = file->private_data; + struct aura_pv_run req; + struct aura_pv_page *page; + unsigned long r0, r1, r2, r3; + phys_addr_t pa; + long status; + + if (!dev->available) + return -ENODEV; + if (cmd == AURA_PV_IOC_DISCOVER) + return copy_to_user((void __user *)arg, &dev->caps, + sizeof(dev->caps)) ? + -EFAULT : + 0; + if (cmd != AURA_PV_IOC_RUN) + return -ENOTTY; + if (copy_from_user(&req, (void __user *)arg, sizeof(req))) + return -EFAULT; + if (!req.request_seq || req.program_words == 0 || + req.program_words > AURA_PV_MAX_PROGRAM) + return -EINVAL; + + page = (struct aura_pv_page *)__get_free_page(GFP_KERNEL | __GFP_ZERO); + if (!page) + return -ENOMEM; + page->major = AURA_PV_ABI_MAJOR; + page->minor = AURA_PV_ABI_MINOR; + page->bytes = sizeof(*page); + page->layout_id = AURA_PV_LAYOUT_RUN; + page->request_seq = req.request_seq; + page->guest_magic = AURA_PV_GUEST_MAGIC; + page->program_words = req.program_words; + page->initial = req.initial; + memcpy(page->program, req.program, sizeof(req.program)); + pa = virt_to_phys(page); + aura_pv_hvc(AURA_PV_FUNC_RUN, pa, AURA_PV_PAGE_BYTES, req.request_seq, + &r0, &r1, &r2, &r3); + status = (long)r0; + if (!status && (page->response_seq != req.request_seq || + !(page->output_valid & AURA_PV_OUTPUT_RESULT))) + status = -EPROTO; + if (!status && (page->host_sentinel_status & BIT(2))) + status = -EIO; + req.status = status; + req.result = page->result; + req.response_seq = page->response_seq; + req.host_sentinel_status = page->host_sentinel_status; + free_page((unsigned long)page); + if (copy_to_user((void __user *)arg, &req, sizeof(req))) + return -EFAULT; + return status; +} + +static int aura_pv_open(struct inode *inode, struct file *file) +{ + file->private_data = &aura_pv; + return 0; +} + +static const struct file_operations aura_pv_fops = { + .owner = THIS_MODULE, + .open = aura_pv_open, + .unlocked_ioctl = aura_pv_ioctl, + .compat_ioctl = aura_pv_ioctl, +}; + +static int __init aura_pv_init(void) +{ + struct device_node *node; + int ret; + + node = of_find_compatible_node(NULL, NULL, "aurora,aura-kvm-pv-0"); + if (!node) + return -ENODEV; + of_node_put(node); + ret = aura_pv_discover(&aura_pv); + if (ret) + return ret; + aura_pv.misc.minor = MISC_DYNAMIC_MINOR; + aura_pv.misc.name = "aura-pv"; + aura_pv.misc.fops = &aura_pv_fops; + return misc_register(&aura_pv.misc); +} +module_init(aura_pv_init); + +static void __exit aura_pv_exit(void) +{ + misc_deregister(&aura_pv.misc); +} +module_exit(aura_pv_exit); + +MODULE_DESCRIPTION("Opt-in Aura PV-KVM v0 diagnostic client"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/aura_pv.h b/include/uapi/linux/aura_pv.h new file mode 100644 index 0000000000000..cdbfeb1f8749b --- /dev/null +++ b/include/uapi/linux/aura_pv.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_AURA_PV_H +#define _UAPI_LINUX_AURA_PV_H + +#include +#include +#include + +#define AURA_PV_ABI_MAJOR 0 +#define AURA_PV_ABI_MINOR 1 +#define AURA_PV_FUNC_UID 0xa000 +#define AURA_PV_FUNC_VERSION 0xa001 +#define AURA_PV_FUNC_GET_CAPS 0xa002 +#define AURA_PV_FUNC_RUN 0xa010 + +#define AURA_PV_STATUS_OK 0 +#define AURA_PV_FEATURE_BOUNDED_INT BIT_ULL(0) +#define AURA_PV_FEATURE_HOST_STATE BIT_ULL(1) +#define AURA_PV_GUEST_MAGIC 0x4155524150475630ULL +#define AURA_PV_LAYOUT_CAPS 0x43504130U +#define AURA_PV_LAYOUT_RUN 0x52554e30U +#define AURA_PV_OUTPUT_RESULT BIT_ULL(0) +#define AURA_PV_OUTPUT_HOST BIT_ULL(1) +#define AURA_PV_OUTPUT_ERROR BIT_ULL(2) +#define AURA_PV_MAX_PROGRAM 8 +#define AURA_PV_PAGE_BYTES 4096 + +struct aura_pv_caps { + __u32 major; + __u32 minor; + __u32 bytes; + __u32 layout_id; + __u64 features; + __u64 max_control_bytes; + __u64 shared_alignment; + __u64 vmid_bits; + __u64 ipa_bits; + __u64 s2_granule; + __u64 host_el; + __u64 reserved_base; + __u64 reserved_end; +}; + +struct aura_pv_run { + __u64 request_seq; + __u64 initial; + __u64 program[AURA_PV_MAX_PROGRAM]; + __u64 program_words; + __u64 result; + __u64 response_seq; + __u64 host_sentinel_status; + __s64 status; +}; + +#define AURA_PV_IOC_MAGIC 'P' +#define AURA_PV_IOC_DISCOVER _IOR(AURA_PV_IOC_MAGIC, 0, struct aura_pv_caps) +#define AURA_PV_IOC_RUN _IOWR(AURA_PV_IOC_MAGIC, 1, struct aura_pv_run) + +#endif diff --git a/tools/testing/selftests/aurora-apif/aura_pv.py b/tools/testing/selftests/aurora-apif/aura_pv.py new file mode 100755 index 0000000000000..4d8634e8cc6d9 --- /dev/null +++ b/tools/testing/selftests/aurora-apif/aura_pv.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Exercise the opt-in Aura PV-KVM discovery and bounded-run ABI.""" + +import errno +import fcntl +import os +import struct +import sys + + +IOC_NRBITS = 8 +IOC_TYPEBITS = 8 +IOC_SIZEBITS = 14 +IOC_DIRBITS = 2 +IOC_NRSHIFT = 0 +IOC_TYPESHIFT = IOC_NRSHIFT + IOC_NRBITS +IOC_SIZESHIFT = IOC_TYPESHIFT + IOC_TYPEBITS +IOC_DIRSHIFT = IOC_SIZESHIFT + IOC_SIZEBITS +IOC_READ = 2 +IOC_WRITE = 1 + + +def ioctl_number(direction, nr, size): + return ((direction << IOC_DIRSHIFT) | (ord("P") << IOC_TYPESHIFT) | + (nr << IOC_NRSHIFT) | (size << IOC_SIZESHIFT)) + + +DISCOVER = ioctl_number(IOC_READ, 0, 88) +RUN = ioctl_number(IOC_READ | IOC_WRITE, 1, 120) + + +def main(): + try: + fd = os.open("/dev/aura-pv", os.O_RDWR | os.O_CLOEXEC) + except FileNotFoundError: + print("SKIP: /dev/aura-pv is not present (Aura PV is opt-in)") + return 4 + try: + caps = bytearray(88) + fcntl.ioctl(fd, DISCOVER, caps, True) + ( + major, + minor, + size, + layout, + features, + max_bytes, + alignment, + vmid_bits, + ipa_bits, + granule, + host_el, + _, + _, + ) = struct.unpack("<4I9Q", caps) + if (major, minor, size, layout) != (0, 1, 88, 0x43504130): + raise AssertionError("unexpected Aura PV capabilities") + if not features & 1 or max_bytes != 4096 or alignment != 4096: + raise AssertionError("missing bounded shared-page capability") + if granule != 14 or host_el != 2 or not vmid_bits or not ipa_bits: + raise AssertionError("invalid stage-2 capability description") + + words = (1, 2, 3) + program = (*words, 0, 0, 0, 0, 0) + req = struct.pack("