diff --git a/arch/arm64/Kbuild b/arch/arm64/Kbuild index 5bfbf7d79c99..b03e545c9ddf 100644 --- a/arch/arm64/Kbuild +++ b/arch/arm64/Kbuild @@ -4,6 +4,7 @@ obj-$(CONFIG_KVM) += kvm/ obj-$(CONFIG_XEN) += xen/ obj-$(subst m,y,$(CONFIG_HYPERV)) += hyperv/ obj-$(CONFIG_CRYPTO) += crypto/ +obj-$(CONFIG_GUNYAH_DRIVERS) += gunyah/ # for cleaning subdir- += boot diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 1652a9800ebe..ff44433811d6 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -2186,6 +2186,8 @@ source "drivers/acpi/Kconfig" source "arch/arm64/kvm/Kconfig" +source "arch/arm64/gunyah/Kconfig" + if CRYPTO source "arch/arm64/crypto/Kconfig" endif # CRYPTO diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index a2fccbaf4829..3f64d193daba 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -209,6 +209,15 @@ config ARCH_QCOM help This enables support for the ARMv8 based Qualcomm chipsets. +config ARCH_QTI_VM + bool "Enable Virtual Machines Support for Qualcomm Technologies, Inc." + depends on ARCH_QCOM + help + This enables support for the Qualcomm Technologies, Inc.'s + Virtual Machines. If you wish to build a kernel that doesn't + require VM support or if you are unsure, + say 'N' here. + config ARCH_PINEAPPLE bool "Enable support for Qualcomm Technologies, Inc. Pineapple" depends on ARCH_QCOM diff --git a/arch/arm64/gunyah/Kconfig b/arch/arm64/gunyah/Kconfig new file mode 100644 index 000000000000..327bfa3cad16 --- /dev/null +++ b/arch/arm64/gunyah/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "Gunyah arm64 drivers" +config GH_ARM64_DRV + tristate "Gunyah arm64 support" + depends on ARM64 + help + Gunyah drivers need arch specific support. Add support for + ARM64 features required by Gunyah virtualizaton drivers. All + arm64 specific features like GIC and reset reasons are handled + by drivers under this config. +endmenu + diff --git a/arch/arm64/gunyah/Makefile b/arch/arm64/gunyah/Makefile new file mode 100644 index 000000000000..2ef4ed957bc5 --- /dev/null +++ b/arch/arm64/gunyah/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_GH_ARM64_DRV) += gh_arm_drv.o +gh_arm_drv-y := gh_arm.o irq.o reset.o diff --git a/arch/arm64/gunyah/gh_arm.c b/arch/arm64/gunyah/gh_arm.c new file mode 100644 index 000000000000..62919e19584e --- /dev/null +++ b/arch/arm64/gunyah/gh_arm.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * + */ + +#include + +static int __init gh_arm_init(void) +{ + return 0; +} +module_init(gh_arm_init); + +static void __exit gh_arm_exit(void) +{ +} +module_exit(gh_arm_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah ARM64 Driver"); diff --git a/arch/arm64/gunyah/irq.c b/arch/arm64/gunyah/irq.c new file mode 100644 index 000000000000..25f6d104bdb1 --- /dev/null +++ b/arch/arm64/gunyah/irq.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include + +#include +#include + +#define GIC_V3_SPI_MAX 1019 + +#define GH_RM_NO_IRQ_ALLOC -1 + +#define IRQ_OFFSET 32 + +static DEFINE_IDR(gh_rm_free_virq_idr); + +/** + * gh_get_irq: Get a Linux IRQ from a Gunyah-compatible vIRQ + * @virq: Gunyah-compatible vIRQ + * @type: IRQ trigger type (IRQ_TYPE_EDGE_RISING) + * @fw_handle: fw node handle + * + * Returns the mapped Linux IRQ# at Gunyah's IRQ domain (i.e. GIC SPI) + */ +int gh_get_irq(u32 virq, u32 type, struct fwnode_handle *fw_handle) +{ + struct irq_fwspec fwspec = {}; + + if (virq < IRQ_OFFSET || virq >= GIC_V3_SPI_MAX) { + pr_warn("%s: expecting an SPI from RM, but got GIC IRQ %d\n", + __func__, virq); + } + + fwspec.fwnode = fw_handle; + fwspec.param_count = 3; + fwspec.param[0] = GIC_SPI; + fwspec.param[1] = virq - IRQ_OFFSET; + fwspec.param[2] = type; + + return irq_create_fwspec_mapping(&fwspec); +} +EXPORT_SYMBOL(gh_get_irq); + +/** + * gh_get_virq: Allocate a new IRQ if RM-VM hasn't already done already + * @base_virq: The base virtual IRQ number. + * @virq: The virtual IRQ number. + * + * Returns Gunyah compatible vIRQ to bind to. + */ +int gh_get_virq(int base_virq, int virq) +{ + int ret; + + /* Get the next free vIRQ. + * Subtract IRQ_OFFSET from the base virq to get the base SPI. + * + * Assoiate the address of the idr variable itself as a lookup + * ptr. This will help us to free the virq later. + */ + ret = virq = idr_alloc(&gh_rm_free_virq_idr, + &gh_rm_free_virq_idr, + base_virq - IRQ_OFFSET, + GIC_V3_SPI_MAX, GFP_KERNEL); + if (ret < 0) + return ret; + + /* Add IRQ_OFFSET offset to make interrupt as hwirq */ + virq += IRQ_OFFSET; + + return virq; +} +EXPORT_SYMBOL(gh_get_virq); + +/** + * gh_put_virq: Deallocates a vIRQ. + * @irq: The IRQ number. + * + * Returns 0 on success and EINVAL if no IRQ was found. + */ +int gh_put_virq(int virq) +{ + void *idr_ptr; + int virq_num; + + virq_num = virq - IRQ_OFFSET; + /* If the idr_find() returns a valid ptr, it means that the + * virq was allocated by the kernel itself and not by hyp. + * Release the IRQ and free the allocation if that's true. + */ + idr_ptr = idr_find(&gh_rm_free_virq_idr, virq_num); + if (idr_ptr) { + idr_remove(&gh_rm_free_virq_idr, virq_num); + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL(gh_put_virq); + +/** + * gh_put_irq: Deallocate an Linux IRQ. + * @irq: The IRQ number. + * + * Returns 0 on success and EINVAL if no IRQ was found. + */ +int gh_put_irq(int irq) +{ + struct irq_data *irq_data; + + if (irq <= 0) + return -EINVAL; + + irq_data = irq_get_irq_data(irq); + if (!irq_data) + return -EINVAL; + + irq_dispose_mapping(irq); + + return gh_put_virq(irq_data->hwirq); +} +EXPORT_SYMBOL(gh_put_irq); diff --git a/arch/arm64/gunyah/reset.c b/arch/arm64/gunyah/reset.c new file mode 100644 index 000000000000..57e73de7bfe6 --- /dev/null +++ b/arch/arm64/gunyah/reset.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * + */ + +#include +#include "reset.h" + +/** + * gh_arch_validate_vm_exited_notif: Validate the arch specific exit + * reason and provide a generic reason for further use. + * @buff_size: Size of the buffer containing the exit reason + * @hdr_size: Size of the header + * @vm_exited_payload: Struct of exit_reason + * + * If the exit reason is not valid or has an incorrect size, -EINVAL is + * returned, 0 otherwise and also provides a generic reason for exit + * which can be used by drivers. + */ +int gh_arch_validate_vm_exited_notif(size_t buff_size, size_t hdr_size, + struct gh_rm_notif_vm_exited_payload *vm_exited_payload) +{ + size_t min_buf_sz = hdr_size | sizeof(*vm_exited_payload); + + switch (vm_exited_payload->exit_type) { + case GH_RM_VM_EXIT_TYPE_PSCI_SYSTEM_RESET2: + if ((vm_exited_payload->exit_reason_size != + MAX_EXIT_REASON_SIZE) || + (buff_size != min_buf_sz + + sizeof(struct gh_vm_exit_reason_psci_sys_reset2))) { + pr_err("%s: Invalid size for type PSCI_SYSTEM_RESET2: %u\n", + __func__, buff_size - hdr_size); + return -EINVAL; + } + vm_exited_payload->exit_type = GH_RM_VM_EXIT_TYPE_SYSTEM_RESET; + fallthrough; + case GH_RM_VM_EXIT_TYPE_PSCI_SYSTEM_RESET: + vm_exited_payload->exit_type = GH_RM_VM_EXIT_TYPE_SYSTEM_RESET; + break; + case GH_RM_VM_EXIT_TYPE_PSCI_SYSTEM_OFF: + vm_exited_payload->exit_type = GH_RM_VM_EXIT_TYPE_SYSTEM_OFF; + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gh_arch_validate_vm_exited_notif); + diff --git a/arch/arm64/gunyah/reset.h b/arch/arm64/gunyah/reset.h new file mode 100644 index 000000000000..988429e789dc --- /dev/null +++ b/arch/arm64/gunyah/reset.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * + */ + +#ifndef __RESET_H +#define __RESET_H + +#define GH_RM_VM_EXIT_TYPE_PSCI_SYSTEM_OFF 1 +#define GH_RM_VM_EXIT_TYPE_PSCI_SYSTEM_RESET 2 +#define GH_RM_VM_EXIT_TYPE_PSCI_SYSTEM_RESET2 3 + +/* GH_RM_VM_EXIT_TYPE_PSCI_SYSTEM_RESET2 */ +struct gh_vm_exit_reason_psci_sys_reset2 { + u16 exit_flags; + /* GH_PSCI_SYS_RESET2_EXIT_FLAG_* are bit representations. + * It follows similar flags model as that of VM_EXIT, but + * only if the vendor_reset field in the struct is set + */ +#define GH_PSCI_SYS_RESET2_EXIT_FLAG_TYPE 0x1 +#define GH_PSCI_SYS_RESET2_POWEROFF 0 /* Value at bit:0 */ +#define GH_PSCI_SYS_RESET2_RESTART 1 /* Value at bit:0 */ +#define GH_PSCI_SYS_RESET2_EXIT_FLAG_SYSTEM 0x2 +#define GH_PSCI_SYS_RESET2_EXIT_FLAG_WARM 0x4 +#define GH_PSCI_SYS_RESET2_EXIT_FLAG_DUMP 0x8 + + u8 exit_code; + /* Exit codes. + * It follows similar flags model as that of VM_EXIT, but + * only if the vendor_reset field in the struct is set + */ +#define GH_PSCI_SYS_RESET2_CODE_NORMAL 0 +#define GH_PSCI_SYS_RESET2_SOFTWARE_ERR 1 +#define GH_PSCI_SYS_RESET2_BUS_ERR 2 +#define GH_PSCI_SYS_RESET2_DEVICE_ERR 3 + + u8 reserved:7; + + /* If the vendor_reset is set, the above flags and codes apply. + * Else, the entire exit_reason struct is 0, which qualifies as + * PSCI_SYSTEM_WARM_RESET. Hence, first check this field before + * checking others. + */ + u8 vendor_reset:1; +} __packed; + +#endif diff --git a/arch/arm64/include/asm/gunyah/hcall.h b/arch/arm64/include/asm/gunyah/hcall.h new file mode 100644 index 000000000000..2365bb231ccf --- /dev/null +++ b/arch/arm64/include/asm/gunyah/hcall.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ +#ifndef __ASM_GH_HCALL_H +#define __ASM_GH_HCALL_H + +#include + +#include + +/** + * _gh_hcall: Performs an AArch64-specific call into hypervisor using Gunyah ABI + * @hcall_num: Hypercall function ID to invoke + * @args: Hypercall argument registers + * @resp: Pointer to location to store response + */ +static inline int _gh_hcall(const gh_hcall_fnid_t hcall_num, + const struct gh_hcall_args args, + struct gh_hcall_resp *resp) +{ + uint64_t _x18; + + register uint64_t _x0 asm("x0") = args.arg0; + register uint64_t _x1 asm("x1") = args.arg1; + register uint64_t _x2 asm("x2") = args.arg2; + register uint64_t _x3 asm("x3") = args.arg3; + register uint64_t _x4 asm("x4") = args.arg4; + register uint64_t _x5 asm("x5") = args.arg5; + register uint64_t _x6 asm("x6") = args.arg6; + register uint64_t _x7 asm("x7") = args.arg7; + + asm volatile ( +#if IS_ENABLED(CONFIG_SHADOW_CALL_STACK) + "str x18, [%[_x18]]\n" +#endif + "hvc %[num]\n" +#if IS_ENABLED(CONFIG_SHADOW_CALL_STACK) + "ldr x18, [%[_x18]]\n" + "str xzr, [%[_x18]]\n" +#endif + : "+r"(_x0), "+r"(_x1), "+r"(_x2), "+r"(_x3), "+r"(_x4), + "+r"(_x5), "+r"(_x6), "+r"(_x7) + : [num] "i" (hcall_num), [_x18] "r"(&_x18) + : "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", +#if !IS_ENABLED(CONFIG_SHADOW_CALL_STACK) + "x18", +#endif + "memory" + ); + + resp->resp0 = _x0; + resp->resp1 = _x1; + resp->resp2 = _x2; + resp->resp3 = _x3; + resp->resp4 = _x4; + resp->resp5 = _x5; + resp->resp6 = _x6; + resp->resp7 = _x7; + + return _x0; +} + +#endif