diff mbox series

[v5,03/12] virt: geniezone: Add GenieZone hypervisor support

Message ID 20230727080005.14474-4-yi-de.wu@mediatek.com (mailing list archive)
State New, archived
Headers show
Series GenieZone hypervisor drivers | expand

Commit Message

Yi-De Wu July 27, 2023, 7:59 a.m. UTC
From: "Yingshiuan Pan" <yingshiuan.pan@mediatek.com>

GenieZone is MediaTek hypervisor solution, and it is running in EL2
stand alone as a type-I hypervisor. This patch exports a set of ioctl
interfaces for userspace VMM (e.g., crosvm) to operate guest VMs
lifecycle (creation and destroy) on GenieZone.

Signed-off-by: Yingshiuan Pan <yingshiuan.pan@mediatek.com>
Signed-off-by: Jerry Wang <ze-yu.wang@mediatek.com>
Signed-off-by: Liju Chen <liju-clr.chen@mediatek.com>
Signed-off-by: Yi-De Wu <yi-de.wu@mediatek.com>
---
 MAINTAINERS                             |   6 +
 arch/arm64/Kbuild                       |   1 +
 arch/arm64/geniezone/Makefile           |   9 +
 arch/arm64/geniezone/gzvm_arch_common.h |  68 ++++
 arch/arm64/geniezone/vm.c               | 212 +++++++++++++
 arch/arm64/include/uapi/asm/gzvm_arch.h |  20 ++
 drivers/virt/Kconfig                    |   2 +
 drivers/virt/geniezone/Kconfig          |  16 +
 drivers/virt/geniezone/Makefile         |  10 +
 drivers/virt/geniezone/gzvm_main.c      | 143 +++++++++
 drivers/virt/geniezone/gzvm_vm.c        | 400 ++++++++++++++++++++++++
 include/linux/gzvm_drv.h                |  90 ++++++
 include/uapi/asm-generic/Kbuild         |   1 +
 include/uapi/asm-generic/gzvm_arch.h    |  10 +
 include/uapi/linux/gzvm.h               |  76 +++++
 15 files changed, 1064 insertions(+)
 create mode 100644 arch/arm64/geniezone/Makefile
 create mode 100644 arch/arm64/geniezone/gzvm_arch_common.h
 create mode 100644 arch/arm64/geniezone/vm.c
 create mode 100644 arch/arm64/include/uapi/asm/gzvm_arch.h
 create mode 100644 drivers/virt/geniezone/Kconfig
 create mode 100644 drivers/virt/geniezone/Makefile
 create mode 100644 drivers/virt/geniezone/gzvm_main.c
 create mode 100644 drivers/virt/geniezone/gzvm_vm.c
 create mode 100644 include/linux/gzvm_drv.h
 create mode 100644 include/uapi/asm-generic/gzvm_arch.h
 create mode 100644 include/uapi/linux/gzvm.h

Comments

Eugen Hristev July 27, 2023, 8:51 a.m. UTC | #1
Hi Yi-De,

On 7/27/23 10:59, Yi-De Wu wrote:
> From: "Yingshiuan Pan" <yingshiuan.pan@mediatek.com>
> 
> GenieZone is MediaTek hypervisor solution, and it is running in EL2
> stand alone as a type-I hypervisor. This patch exports a set of ioctl
> interfaces for userspace VMM (e.g., crosvm) to operate guest VMs
> lifecycle (creation and destroy) on GenieZone.
> 
> Signed-off-by: Yingshiuan Pan <yingshiuan.pan@mediatek.com>
> Signed-off-by: Jerry Wang <ze-yu.wang@mediatek.com>
> Signed-off-by: Liju Chen <liju-clr.chen@mediatek.com>
> Signed-off-by: Yi-De Wu <yi-de.wu@mediatek.com>
> ---
>   MAINTAINERS                             |   6 +
>   arch/arm64/Kbuild                       |   1 +
>   arch/arm64/geniezone/Makefile           |   9 +
>   arch/arm64/geniezone/gzvm_arch_common.h |  68 ++++
>   arch/arm64/geniezone/vm.c               | 212 +++++++++++++
>   arch/arm64/include/uapi/asm/gzvm_arch.h |  20 ++
>   drivers/virt/Kconfig                    |   2 +
>   drivers/virt/geniezone/Kconfig          |  16 +
>   drivers/virt/geniezone/Makefile         |  10 +
>   drivers/virt/geniezone/gzvm_main.c      | 143 +++++++++
>   drivers/virt/geniezone/gzvm_vm.c        | 400 ++++++++++++++++++++++++
>   include/linux/gzvm_drv.h                |  90 ++++++
>   include/uapi/asm-generic/Kbuild         |   1 +
>   include/uapi/asm-generic/gzvm_arch.h    |  10 +
>   include/uapi/linux/gzvm.h               |  76 +++++
>   15 files changed, 1064 insertions(+)
>   create mode 100644 arch/arm64/geniezone/Makefile
>   create mode 100644 arch/arm64/geniezone/gzvm_arch_common.h
>   create mode 100644 arch/arm64/geniezone/vm.c
>   create mode 100644 arch/arm64/include/uapi/asm/gzvm_arch.h
>   create mode 100644 drivers/virt/geniezone/Kconfig
>   create mode 100644 drivers/virt/geniezone/Makefile
>   create mode 100644 drivers/virt/geniezone/gzvm_main.c
>   create mode 100644 drivers/virt/geniezone/gzvm_vm.c
>   create mode 100644 include/linux/gzvm_drv.h
>   create mode 100644 include/uapi/asm-generic/gzvm_arch.h
>   create mode 100644 include/uapi/linux/gzvm.h
> 

I have a feeling this patch is a bit big, and could help review if it's 
split into chunks of smaller size.

> diff --git a/MAINTAINERS b/MAINTAINERS
> index bfbfdb790446..b91d41dd2f2f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8747,6 +8747,12 @@ M:	Ze-Yu Wang <ze-yu.wang@mediatek.com>
>   M:	Yi-De Wu <yi-de.wu@mediatek.com>
>   F:	Documentation/devicetree/bindings/hypervisor/mediatek,geniezone-hyp.yaml
>   F:	Documentation/virt/geniezone/
> +F:	arch/arm64/geniezone/
> +F:	arch/arm64/include/uapi/asm/gzvm_arch.h
> +F:	drivers/virt/geniezone/
> +F:	include/linux/gzvm_drv.h
> +F	include/uapi/asm-generic/gzvm_arch.h
> +F:	include/uapi/linux/gzvm.h
>   
>   GENWQE (IBM Generic Workqueue Card)
>   M:	Frank Haverkamp <haver@linux.ibm.com>
> diff --git a/arch/arm64/Kbuild b/arch/arm64/Kbuild
> index 5bfbf7d79c99..0c3cca572919 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_MTK_GZVM)	+= geniezone/
>   
>   # for cleaning
>   subdir- += boot
> diff --git a/arch/arm64/geniezone/Makefile b/arch/arm64/geniezone/Makefile
> new file mode 100644
> index 000000000000..2957898cdd05
> --- /dev/null
> +++ b/arch/arm64/geniezone/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Main Makefile for gzvm, this one includes drivers/virt/geniezone/Makefile
> +#
> +include $(srctree)/drivers/virt/geniezone/Makefile
> +
> +gzvm-y += vm.o
> +
> +obj-$(CONFIG_MTK_GZVM) += gzvm.o
> diff --git a/arch/arm64/geniezone/gzvm_arch_common.h b/arch/arm64/geniezone/gzvm_arch_common.h
> new file mode 100644
> index 000000000000..fdb95d619102
> --- /dev/null
> +++ b/arch/arm64/geniezone/gzvm_arch_common.h
> @@ -0,0 +1,68 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#ifndef __GZVM_ARCH_COMMON_H__
> +#define __GZVM_ARCH_COMMON_H__
> +
> +#include <linux/arm-smccc.h>
> +
> +enum {
> +	GZVM_FUNC_CREATE_VM = 0,
> +	GZVM_FUNC_DESTROY_VM = 1,
> +	GZVM_FUNC_CREATE_VCPU = 2,
> +	GZVM_FUNC_DESTROY_VCPU = 3,
> +	GZVM_FUNC_SET_MEMREGION = 4,
> +	GZVM_FUNC_RUN = 5,
> +	GZVM_FUNC_GET_ONE_REG = 8,
> +	GZVM_FUNC_SET_ONE_REG = 9,
> +	GZVM_FUNC_IRQ_LINE = 10,
> +	GZVM_FUNC_CREATE_DEVICE = 11,
> +	GZVM_FUNC_PROBE = 12,
> +	GZVM_FUNC_ENABLE_CAP = 13,
> +	NR_GZVM_FUNC,
> +};
> +
> +#define SMC_ENTITY_MTK			59
> +#define GZVM_FUNCID_START		(0x1000)
> +#define GZVM_HCALL_ID(func)						\
> +	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,	\
> +			   SMC_ENTITY_MTK, (GZVM_FUNCID_START + (func)))
> +
> +#define MT_HVC_GZVM_CREATE_VM		GZVM_HCALL_ID(GZVM_FUNC_CREATE_VM)
> +#define MT_HVC_GZVM_DESTROY_VM		GZVM_HCALL_ID(GZVM_FUNC_DESTROY_VM)
> +#define MT_HVC_GZVM_CREATE_VCPU		GZVM_HCALL_ID(GZVM_FUNC_CREATE_VCPU)
> +#define MT_HVC_GZVM_DESTROY_VCPU	GZVM_HCALL_ID(GZVM_FUNC_DESTROY_VCPU)
> +#define MT_HVC_GZVM_SET_MEMREGION	GZVM_HCALL_ID(GZVM_FUNC_SET_MEMREGION)
> +#define MT_HVC_GZVM_RUN			GZVM_HCALL_ID(GZVM_FUNC_RUN)
> +#define MT_HVC_GZVM_GET_ONE_REG		GZVM_HCALL_ID(GZVM_FUNC_GET_ONE_REG)
> +#define MT_HVC_GZVM_SET_ONE_REG		GZVM_HCALL_ID(GZVM_FUNC_SET_ONE_REG)
> +#define MT_HVC_GZVM_IRQ_LINE		GZVM_HCALL_ID(GZVM_FUNC_IRQ_LINE)
> +#define MT_HVC_GZVM_CREATE_DEVICE	GZVM_HCALL_ID(GZVM_FUNC_CREATE_DEVICE)
> +#define MT_HVC_GZVM_PROBE		GZVM_HCALL_ID(GZVM_FUNC_PROBE)
> +#define MT_HVC_GZVM_ENABLE_CAP		GZVM_HCALL_ID(GZVM_FUNC_ENABLE_CAP)
> +
> +/**
> + * gzvm_hypcall_wrapper() - the wrapper for hvc calls
> + * @a0-a7: arguments passed in registers 0 to 7
> + * @res: result values from registers 0 to 3
> + *
> + * Return: The wrapper helps caller to convert geniezone errno to Linux errno.
> + */
> +static inline int gzvm_hypcall_wrapper(unsigned long a0, unsigned long a1,
> +				       unsigned long a2, unsigned long a3,
> +				       unsigned long a4, unsigned long a5,
> +				       unsigned long a6, unsigned long a7,
> +				       struct arm_smccc_res *res)
> +{
> +	arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res);
> +	return gzvm_err_to_errno(res->a0);
> +}
> +
> +static inline u16 get_vmid_from_tuple(unsigned int tuple)
> +{
> +	return (u16)(tuple >> 16);
> +}
> +
> +#endif /* __GZVM_ARCH_COMMON_H__ */
> diff --git a/arch/arm64/geniezone/vm.c b/arch/arm64/geniezone/vm.c
> new file mode 100644
> index 000000000000..e35751b21821
> --- /dev/null
> +++ b/arch/arm64/geniezone/vm.c
> @@ -0,0 +1,212 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#include <asm/sysreg.h>
> +#include <linux/arm-smccc.h>
> +#include <linux/err.h>
> +#include <linux/uaccess.h>
> +
> +#include <linux/gzvm.h>
> +#include <linux/gzvm_drv.h>
> +#include "gzvm_arch_common.h"
> +
> +#define PAR_PA47_MASK ((((1UL << 48) - 1) >> 12) << 12)
> +
> +int gzvm_arch_probe(void)
> +{
> +	struct arm_smccc_res res;
> +
> +	arm_smccc_hvc(MT_HVC_GZVM_PROBE, 0, 0, 0, 0, 0, 0, 0, &res);
> +	if (res.a0 == 0)
> +		return 0;

I would see the error path as a particular case here, e.g.

if (res.a0)
	return -ENXIO;

and on the usual path return success.

(as you already do below in some functions)...

> +
> +	return -ENXIO;
> +}
> +
> +int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size,
> +			    phys_addr_t region)
> +{
> +	struct arm_smccc_res res;
> +
> +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_SET_MEMREGION, vm_id,
> +				    buf_size, region, 0, 0, 0, 0, &res);
> +}
> +
> +static int gzvm_cap_arm_vm_ipa_size(void __user *argp)
> +{
> +	__u64 value = CONFIG_ARM64_PA_BITS;
> +
> +	if (copy_to_user(argp, &value, sizeof(__u64)))
> +		return -EFAULT;

... e.g. here.

> +
> +	return 0;
> +}
> +
> +int gzvm_arch_check_extension(struct gzvm *gzvm, __u64 cap, void __user *argp)
> +{
> +	int ret = -EOPNOTSUPP;
> +
> +	switch (cap) {
> +	case GZVM_CAP_ARM_PROTECTED_VM: {
> +		__u64 success = 1;
> +
> +		if (copy_to_user(argp, &success, sizeof(__u64)))
> +			return -EFAULT;
> +		ret = 0;
> +		break;
> +	}
> +	case GZVM_CAP_ARM_VM_IPA_SIZE: {
> +		ret = gzvm_cap_arm_vm_ipa_size(argp);
> +		break;
> +	}
> +	default:
> +		ret = -EOPNOTSUPP;

you already initialized ret to -EOPNOTSUPP, why don't you initialize it 
with 0, and just set it as error code here, and avoid setting it to 0 on 
the success case above.

> +	}
> +
> +	return ret;
> +}
> +
> +/**
> + * gzvm_arch_create_vm() - create vm
> + * @vm_type: VM type. Only supports Linux VM now.
> + *
> + * Return:
> + * * positive value	- VM ID
> + * * -ENOMEM		- Memory not enough for storing VM data
> + */
> +int gzvm_arch_create_vm(unsigned long vm_type)
> +{
> +	struct arm_smccc_res res;
> +	int ret;
> +
> +	ret = gzvm_hypcall_wrapper(MT_HVC_GZVM_CREATE_VM, vm_type, 0, 0, 0, 0,
> +				   0, 0, &res);
> +
> +	if (ret == 0)
> +		return res.a1;
> +	else
> +		return ret;
> +}
> +
> +int gzvm_arch_destroy_vm(u16 vm_id)
> +{
> +	struct arm_smccc_res res;
> +
> +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_DESTROY_VM, vm_id, 0, 0, 0, 0,
> +				    0, 0, &res);
> +}
> +
> +static int gzvm_vm_arch_enable_cap(struct gzvm *gzvm,
> +				   struct gzvm_enable_cap *cap,
> +				   struct arm_smccc_res *res)
> +{
> +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_ENABLE_CAP, gzvm->vm_id,
> +				    cap->cap, cap->args[0], cap->args[1],
> +				    cap->args[2], cap->args[3], cap->args[4],
> +				    res);
> +}
> +
> +/**
> + * gzvm_vm_ioctl_get_pvmfw_size() - Get pvmfw size from hypervisor, return
> + *				    in x1, and return to userspace in args
> + * @gzvm: Pointer to struct gzvm.
> + * @cap: Pointer to struct gzvm_enable_cap.
> + * @argp: Pointer to struct gzvm_enable_cap in user space.
> + *
> + * Return:
> + * * 0			- Succeed
> + * * -EINVAL		- Hypervisor return invalid results
> + * * -EFAULT		- Fail to copy back to userspace buffer
> + */
> +static int gzvm_vm_ioctl_get_pvmfw_size(struct gzvm *gzvm,
> +					struct gzvm_enable_cap *cap,
> +					void __user *argp)
> +{
> +	struct arm_smccc_res res = {0};
> +
> +	if (gzvm_vm_arch_enable_cap(gzvm, cap, &res) != 0)
> +		return -EINVAL;
> +
> +	cap->args[1] = res.a1;
> +	if (copy_to_user(argp, cap, sizeof(*cap)))
> +		return -EFAULT;
> +
> +	return 0;
> +}
> +
> +/**
> + * gzvm_vm_ioctl_cap_pvm() - Proceed GZVM_CAP_ARM_PROTECTED_VM's subcommands
> + * @gzvm: Pointer to struct gzvm.
> + * @cap: Pointer to struct gzvm_enable_cap.
> + * @argp: Pointer to struct gzvm_enable_cap in user space.
> + *
> + * Return:
> + * * 0			- Succeed
> + * * -EINVAL		- Invalid subcommand or arguments
> + */
> +static int gzvm_vm_ioctl_cap_pvm(struct gzvm *gzvm,
> +				 struct gzvm_enable_cap *cap,
> +				 void __user *argp)
> +{
> +	int ret = -EINVAL;

This initialization appears redundant as you always rewrite ret to a new 
value below

> +	struct arm_smccc_res res = {0};
> +
> +	switch (cap->args[0]) {
> +	case GZVM_CAP_ARM_PVM_SET_PVMFW_IPA:
> +		fallthrough;
> +	case GZVM_CAP_ARM_PVM_SET_PROTECTED_VM:
> +		ret = gzvm_vm_arch_enable_cap(gzvm, cap, &res);
> +		break;
> +	case GZVM_CAP_ARM_PVM_GET_PVMFW_SIZE:
> +		ret = gzvm_vm_ioctl_get_pvmfw_size(gzvm, cap, argp);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm,
> +				  struct gzvm_enable_cap *cap,
> +				  void __user *argp)
> +{
> +	int ret = -EINVAL;
same here
> +
> +	switch (cap->cap) {
> +	case GZVM_CAP_ARM_PROTECTED_VM:
> +		ret = gzvm_vm_ioctl_cap_pvm(gzvm, cap, argp);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +/**
> + * gzvm_hva_to_pa_arch() - converts hva to pa with arch-specific way
> + * @hva: Host virtual address.
> + *
> + * Return: 0 if translation error

This is a bit misleading, if you look at the code, you return 0 if the 
bit SYS_PAR_EL1_F is present, but also return 0 if bit PAR_PA47_MASK is 
not present. Are those situations identical ?

Also, it's a bit strange to return 0 for an error case.

> + */
> +u64 gzvm_hva_to_pa_arch(u64 hva)
> +{
> +	u64 par;
> +	unsigned long flags;
> +
> +	local_irq_save(flags);
> +	asm volatile("at s1e1r, %0" :: "r" (hva));
> +	isb();
> +	par = read_sysreg_par();
> +	local_irq_restore(flags);
> +
> +	if (par & SYS_PAR_EL1_F)
> +		return 0;
> +
> +	return par & PAR_PA47_MASK;
> +}
> diff --git a/arch/arm64/include/uapi/asm/gzvm_arch.h b/arch/arm64/include/uapi/asm/gzvm_arch.h
> new file mode 100644
> index 000000000000..847bb627a65d
> --- /dev/null
> +++ b/arch/arm64/include/uapi/asm/gzvm_arch.h
> @@ -0,0 +1,20 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#ifndef __GZVM_ARCH_H__
> +#define __GZVM_ARCH_H__
> +
> +#include <linux/types.h>
> +
> +#define GZVM_CAP_ARM_VM_IPA_SIZE	165
> +#define GZVM_CAP_ARM_PROTECTED_VM	0xffbadab1
> +
> +/* sub-commands put in args[0] for GZVM_CAP_ARM_PROTECTED_VM */
> +#define GZVM_CAP_ARM_PVM_SET_PVMFW_IPA		0
> +#define GZVM_CAP_ARM_PVM_GET_PVMFW_SIZE		1
> +/* GZVM_CAP_ARM_PVM_SET_PROTECTED_VM only sets protected but not load pvmfw */
> +#define GZVM_CAP_ARM_PVM_SET_PROTECTED_VM	2
> +
> +#endif /* __GZVM_ARCH_H__ */
> diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
> index f79ab13a5c28..9bbf0bdf672c 100644
> --- a/drivers/virt/Kconfig
> +++ b/drivers/virt/Kconfig
> @@ -54,4 +54,6 @@ source "drivers/virt/coco/sev-guest/Kconfig"
>   
>   source "drivers/virt/coco/tdx-guest/Kconfig"
>   
> +source "drivers/virt/geniezone/Kconfig"
> +
>   endif
> diff --git a/drivers/virt/geniezone/Kconfig b/drivers/virt/geniezone/Kconfig
> new file mode 100644
> index 000000000000..2643fb8913cc
> --- /dev/null
> +++ b/drivers/virt/geniezone/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config MTK_GZVM
> +	tristate "GenieZone Hypervisor driver for guest VM operation"
> +	depends on ARM64

if only mediatek SoC is supported, should it depend on it here ?

> +	help
> +	  This driver, gzvm, enables to run guest VMs on MTK GenieZone
> +	  hypervisor. It exports kvm-like interfaces for VMM (e.g., crosvm) in
> +	  order to operate guest VMs on GenieZone hypervisor.
> +
> +	  GenieZone hypervisor now only supports MediaTek SoC and arm64
> +	  architecture.
> +
> +	  Select M if you want it be built as a module (gzvm.ko).
> +
> +	  If unsure, say N.
> diff --git a/drivers/virt/geniezone/Makefile b/drivers/virt/geniezone/Makefile
> new file mode 100644
> index 000000000000..066efddc0b9c
> --- /dev/null
> +++ b/drivers/virt/geniezone/Makefile
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Makefile for GenieZone driver, this file should be include in arch's
> +# to avoid two ko being generated.
> +#
> +
> +GZVM_DIR ?= ../../../drivers/virt/geniezone
> +
> +gzvm-y := $(GZVM_DIR)/gzvm_main.o $(GZVM_DIR)/gzvm_vm.o
> +
> diff --git a/drivers/virt/geniezone/gzvm_main.c b/drivers/virt/geniezone/gzvm_main.c
> new file mode 100644
> index 000000000000..b629b41a0cd9
> --- /dev/null
> +++ b/drivers/virt/geniezone/gzvm_main.c
> @@ -0,0 +1,143 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#include <linux/anon_inodes.h>
> +#include <linux/device.h>
> +#include <linux/file.h>
> +#include <linux/kdev_t.h>
> +#include <linux/miscdevice.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/gzvm_drv.h>
> +
> +/**
> + * gzvm_err_to_errno() - Convert geniezone return value to standard errno
> + *
> + * @err: Return value from geniezone function return
> + *
> + * Return: Standard errno
> + */
> +int gzvm_err_to_errno(unsigned long err)
> +{
> +	int gz_err = (int)err;
> +
> +	switch (gz_err) {
> +	case 0:
> +		return 0;
> +	case ERR_NO_MEMORY:
> +		return -ENOMEM;
> +	case ERR_NOT_SUPPORTED:
> +		return -EOPNOTSUPP;
> +	case ERR_NOT_IMPLEMENTED:
> +		return -EOPNOTSUPP;
> +	case ERR_FAULT:
> +		return -EFAULT;
> +	default:
> +		break;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +/**
> + * gzvm_dev_ioctl_check_extension() - Check if given capability is support
> + *				      or not
> + *
> + * @gzvm: Pointer to struct gzvm
> + * @args: Pointer in u64 from userspace
> + *
> + * Return:
> + * * 0			- Support, no error
Supported ?

> + * * -EOPNOTSUPP	- Not support
> + * * -EFAULT		- Failed to get data from userspace
> + */
> +long gzvm_dev_ioctl_check_extension(struct gzvm *gzvm, unsigned long args)
> +{
> +	__u64 cap;
> +	void __user *argp = (void __user *)args;
> +
> +	if (copy_from_user(&cap, argp, sizeof(uint64_t)))
> +		return -EFAULT;
> +	return gzvm_arch_check_extension(gzvm, cap, argp);
> +}
> +
> +static long gzvm_dev_ioctl(struct file *filp, unsigned int cmd,
> +			   unsigned long user_args)
> +{
> +	long ret = -ENOTTY;
again redundant initializations
> +
> +	switch (cmd) {
> +	case GZVM_CREATE_VM:
> +		ret = gzvm_dev_ioctl_create_vm(user_args);
> +		break;
> +	case GZVM_CHECK_EXTENSION:
> +		if (!user_args)
> +			return -EINVAL;
> +		ret = gzvm_dev_ioctl_check_extension(NULL, user_args);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct file_operations gzvm_chardev_ops = {
> +	.unlocked_ioctl = gzvm_dev_ioctl,
> +	.llseek		= noop_llseek,
> +};
> +
> +static struct miscdevice gzvm_dev = {
> +	.minor = MISC_DYNAMIC_MINOR,
> +	.name = KBUILD_MODNAME,
> +	.fops = &gzvm_chardev_ops,
> +};
> +
> +static int gzvm_drv_probe(struct platform_device *pdev)
> +{
> +	int ret;
> +
> +	if (gzvm_arch_probe() != 0) {
> +		dev_err(&pdev->dev, "Not found available conduit\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = misc_register(&gzvm_dev);

return misc_register(...) ?

> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int gzvm_drv_remove(struct platform_device *pdev)
> +{
> +	gzvm_destroy_all_vms();
> +	misc_deregister(&gzvm_dev);
> +	return 0;
> +}
> +
> +static const struct of_device_id gzvm_of_match[] = {
> +	{ .compatible = "mediatek,geniezone-hyp", },
> +	{/* sentinel */},
> +};
> +
> +static struct platform_driver gzvm_driver = {
> +	.probe = gzvm_drv_probe,
> +	.remove = gzvm_drv_remove,
> +	.driver = {
> +		.name = KBUILD_MODNAME,
> +		.owner = THIS_MODULE,
> +		.of_match_table = gzvm_of_match,
> +	},
> +};
> +
> +module_platform_driver(gzvm_driver);
> +
> +MODULE_DEVICE_TABLE(of, gzvm_of_match);
> +MODULE_AUTHOR("MediaTek");
> +MODULE_DESCRIPTION("GenieZone interface for VMM");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/virt/geniezone/gzvm_vm.c b/drivers/virt/geniezone/gzvm_vm.c
> new file mode 100644
> index 000000000000..ee751369fd4b
> --- /dev/null
> +++ b/drivers/virt/geniezone/gzvm_vm.c
> @@ -0,0 +1,400 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#include <linux/anon_inodes.h>
> +#include <linux/file.h>
> +#include <linux/kdev_t.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/gzvm_drv.h>
> +
> +static DEFINE_MUTEX(gzvm_list_lock);
> +static LIST_HEAD(gzvm_list);
> +
> +/**
> + * hva_to_pa_fast() - converts hva to pa in generic fast way
> + * @hva: Host virtual address.
> + *
> + * Return: 0 if translation error
> + */
> +static u64 hva_to_pa_fast(u64 hva)
> +{
> +	struct page *page[1];
> +
> +	u64 pfn;
> +
> +	if (get_user_page_fast_only(hva, 0, page)) {
> +		pfn = page_to_phys(page[0]);
> +		put_page((struct page *)page);
> +		return pfn;
> +	} else {
you can remove the 'else' and just return 0 here as you return pfn in 
the if(true) case.

> +		return 0;
> +	}
> +}
> +
> +/**
> + * hva_to_pa_slow() - note that this function may sleep
> + * @hva: Host virtual address.
> + *
> + * Return: 0 if translation error
> + */
> +static u64 hva_to_pa_slow(u64 hva)
> +{
> +	struct page *page;
> +	int npages;
> +	u64 pfn;
> +
> +	npages = get_user_pages_unlocked(hva, 1, &page, 0);
> +	if (npages != 1)
> +		return 0;
> +
> +	pfn = page_to_phys(page);
> +	put_page(page);
> +
> +	return pfn;
> +}
> +
> +static u64 gzvm_gfn_to_hva_memslot(struct gzvm_memslot *memslot, u64 gfn)
> +{
> +	u64 offset = gfn - memslot->base_gfn;
> +
> +	return memslot->userspace_addr + offset * PAGE_SIZE;
> +}
> +
> +static u64 __gzvm_gfn_to_pfn_memslot(struct gzvm_memslot *memslot, u64 gfn)
> +{
> +	u64 hva, pa;
> +
> +	hva = gzvm_gfn_to_hva_memslot(memslot, gfn);
> +
> +	pa = gzvm_hva_to_pa_arch(hva);
> +	if (pa != 0)
> +		return PHYS_PFN(pa);
> +
> +	pa = hva_to_pa_fast(hva);
> +	if (pa)
> +		return PHYS_PFN(pa);
> +
> +	pa = hva_to_pa_slow(hva);
> +	if (pa)
> +		return PHYS_PFN(pa);
> +
> +	return 0;
> +}
> +
> +/**
> + * gzvm_gfn_to_pfn_memslot() - Translate gfn (guest ipa) to pfn (host pa),
> + *			       result is in @pfn
> + * @memslot: Pointer to struct gzvm_memslot.
> + * @gfn: Guest frame number.
> + * @pfn: Host page frame number.
> + *
> + * Return:
> + * * 0			- Succeed
> + * * -EFAULT		- Failed to convert
> + */
> +static int gzvm_gfn_to_pfn_memslot(struct gzvm_memslot *memslot, u64 gfn,
> +				   u64 *pfn)
> +{
> +	u64 __pfn;
> +
> +	if (!memslot)
> +		return -EFAULT;
> +
> +	__pfn = __gzvm_gfn_to_pfn_memslot(memslot, gfn);
> +	if (__pfn == 0) {
> +		*pfn = 0;
> +		return -EFAULT;
> +	}
> +
> +	*pfn = __pfn;
> +
> +	return 0;
> +}
> +
> +/**
> + * fill_constituents() - Populate pa to buffer until full
> + * @consti: Pointer to struct mem_region_addr_range.
> + * @consti_cnt: Constituent count.
> + * @max_nr_consti: Maximum number of constituent count.
> + * @gfn: Guest frame number.
> + * @total_pages: Total page numbers.
> + * @slot: Pointer to struct gzvm_memslot.
> + *
> + * Return: how many pages we've fill in, negative if error
> + */
> +static int fill_constituents(struct mem_region_addr_range *consti,
> +			     int *consti_cnt, int max_nr_consti, u64 gfn,
> +			     u32 total_pages, struct gzvm_memslot *slot)
> +{
> +	u64 pfn, prev_pfn, gfn_end;
> +	int nr_pages = 1;
> +	int i = 0;
> +
> +	if (unlikely(total_pages == 0))
> +		return -EINVAL;
> +	gfn_end = gfn + total_pages;
> +
> +	/* entry 0 */
> +	if (gzvm_gfn_to_pfn_memslot(slot, gfn, &pfn) != 0)
> +		return -EFAULT;
> +	consti[0].address = PFN_PHYS(pfn);
> +	consti[0].pg_cnt = 1;
> +	gfn++;
> +	prev_pfn = pfn;
> +
> +	while (i < max_nr_consti && gfn < gfn_end) {
> +		if (gzvm_gfn_to_pfn_memslot(slot, gfn, &pfn) != 0)
> +			return -EFAULT;
> +		if (pfn == (prev_pfn + 1)) {
> +			consti[i].pg_cnt++;
> +		} else {
> +			i++;
> +			if (i >= max_nr_consti)
> +				break;
> +			consti[i].address = PFN_PHYS(pfn);
> +			consti[i].pg_cnt = 1;
> +		}
> +		prev_pfn = pfn;
> +		gfn++;
> +		nr_pages++;
> +	}
> +	if (i != max_nr_consti)
> +		i++;
> +	*consti_cnt = i;
> +
> +	return nr_pages;
> +}
> +
> +/* register_memslot_addr_range() - Register memory region to GZ */
> +static int
> +register_memslot_addr_range(struct gzvm *gzvm, struct gzvm_memslot *memslot)
> +{
> +	struct gzvm_memory_region_ranges *region;
> +	u32 buf_size;
> +	int max_nr_consti, remain_pages;
> +	u64 gfn, gfn_end;
> +
> +	buf_size = PAGE_SIZE * 2;
> +	region = alloc_pages_exact(buf_size, GFP_KERNEL);
> +	if (!region)
> +		return -ENOMEM;
> +	max_nr_consti = (buf_size - sizeof(*region)) /
> +			sizeof(struct mem_region_addr_range);
> +
> +	region->slot = memslot->slot_id;
> +	remain_pages = memslot->npages;
> +	gfn = memslot->base_gfn;
> +	gfn_end = gfn + remain_pages;
> +	while (gfn < gfn_end) {
> +		int nr_pages;
> +
> +		nr_pages = fill_constituents(region->constituents,
> +					     &region->constituent_cnt,
> +					     max_nr_consti, gfn,
> +					     remain_pages, memslot);
> +		if (nr_pages < 0) {
> +			pr_err("Failed to fill constituents\n");
> +			free_pages_exact(region, buf_size);
> +			return nr_pages;
> +		}
> +		region->gpa = PFN_PHYS(gfn);
> +		region->total_pages = nr_pages;
> +
> +		remain_pages -= nr_pages;
> +		gfn += nr_pages;
> +
> +		if (gzvm_arch_set_memregion(gzvm->vm_id, buf_size,
> +					    virt_to_phys(region))) {
> +			pr_err("Failed to register memregion to hypervisor\n");
> +			free_pages_exact(region, buf_size);
> +			return -EFAULT;
> +		}
> +	}
> +	free_pages_exact(region, buf_size);
> +	return 0;
> +}
> +
> +/**
> + * gzvm_vm_ioctl_set_memory_region() - Set memory region of guest
> + * @gzvm: Pointer to struct gzvm.
> + * @mem: Input memory region from user.
> + *
> + * Return:
> + * * -EXIO		- memslot is out-of-range
> + * * -EFAULT		- Cannot find corresponding vma
> + * * -EINVAL		- region size and vma size does not match
I assume 0 for success ?

> + */
> +static int
> +gzvm_vm_ioctl_set_memory_region(struct gzvm *gzvm,
> +				struct gzvm_userspace_memory_region *mem)
> +{
> +	struct vm_area_struct *vma;
> +	struct gzvm_memslot *memslot;
> +	unsigned long size;
> +	__u32 slot;
> +
> +	slot = mem->slot;
> +	if (slot >= GZVM_MAX_MEM_REGION)
> +		return -ENXIO;
> +	memslot = &gzvm->memslot[slot];
> +
> +	vma = vma_lookup(gzvm->mm, mem->userspace_addr);
> +	if (!vma)
> +		return -EFAULT;
> +
> +	size = vma->vm_end - vma->vm_start;
> +	if (size != mem->memory_size)
> +		return -EINVAL;
> +
> +	memslot->base_gfn = __phys_to_pfn(mem->guest_phys_addr);
> +	memslot->npages = size >> PAGE_SHIFT;
> +	memslot->userspace_addr = mem->userspace_addr;
> +	memslot->vma = vma;
> +	memslot->flags = mem->flags;
> +	memslot->slot_id = mem->slot;
> +	return register_memslot_addr_range(gzvm, memslot);
> +}
> +
> +static int gzvm_vm_ioctl_enable_cap(struct gzvm *gzvm,
> +				    struct gzvm_enable_cap *cap,
> +				    void __user *argp)
> +{
> +	return gzvm_vm_ioctl_arch_enable_cap(gzvm, cap, argp);
> +}
> +
> +/* gzvm_vm_ioctl() - Ioctl handler of VM FD */
> +static long gzvm_vm_ioctl(struct file *filp, unsigned int ioctl,
> +			  unsigned long arg)
> +{
> +	long ret = -ENOTTY;
appears to be redundant

> +	void __user *argp = (void __user *)arg;
> +	struct gzvm *gzvm = filp->private_data;
> +
> +	switch (ioctl) {
> +	case GZVM_CHECK_EXTENSION: {
> +		ret = gzvm_dev_ioctl_check_extension(gzvm, arg);
> +		break;
> +	}
> +	case GZVM_SET_USER_MEMORY_REGION: {
> +		struct gzvm_userspace_memory_region userspace_mem;
> +
> +		if (copy_from_user(&userspace_mem, argp, sizeof(userspace_mem))) {
> +			ret = -EFAULT;
> +			goto out;
> +		}
> +		ret = gzvm_vm_ioctl_set_memory_region(gzvm, &userspace_mem);
> +		break;
> +	}
> +	case GZVM_ENABLE_CAP: {
> +		struct gzvm_enable_cap cap;
> +
> +		if (copy_from_user(&cap, argp, sizeof(cap))) {
> +			ret = -EFAULT;
> +			goto out;
> +		}
> +		ret = gzvm_vm_ioctl_enable_cap(gzvm, &cap, argp);
> +		break;
> +	}
> +	default:
> +		ret = -ENOTTY;
> +	}
> +out:
> +	return ret;
> +}
> +
> +static void gzvm_destroy_vm(struct gzvm *gzvm)
> +{
> +	pr_debug("VM-%u is going to be destroyed\n", gzvm->vm_id);
> +
> +	mutex_lock(&gzvm->lock);
> +
> +	gzvm_arch_destroy_vm(gzvm->vm_id);
> +
> +	mutex_lock(&gzvm_list_lock);
> +	list_del(&gzvm->vm_list);
> +	mutex_unlock(&gzvm_list_lock);
> +
> +	mutex_unlock(&gzvm->lock);
> +
> +	kfree(gzvm);
> +}
> +
> +static int gzvm_vm_release(struct inode *inode, struct file *filp)
> +{
> +	struct gzvm *gzvm = filp->private_data;
> +
> +	gzvm_destroy_vm(gzvm);
> +	return 0;
> +}
> +
> +static const struct file_operations gzvm_vm_fops = {
> +	.release        = gzvm_vm_release,
> +	.unlocked_ioctl = gzvm_vm_ioctl,
> +	.llseek		= noop_llseek,
> +};
> +
> +static struct gzvm *gzvm_create_vm(unsigned long vm_type)
> +{
> +	int ret;
> +	struct gzvm *gzvm;
> +
> +	gzvm = kzalloc(sizeof(*gzvm), GFP_KERNEL);
> +	if (!gzvm)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ret = gzvm_arch_create_vm(vm_type);
> +	if (ret < 0) {
> +		kfree(gzvm);
> +		return ERR_PTR(ret);
> +	}
> +
> +	gzvm->vm_id = ret;
> +	gzvm->mm = current->mm;
> +	mutex_init(&gzvm->lock);
> +
> +	mutex_lock(&gzvm_list_lock);
> +	list_add(&gzvm->vm_list, &gzvm_list);
> +	mutex_unlock(&gzvm_list_lock);
> +
> +	pr_debug("VM-%u is created\n", gzvm->vm_id);
> +
> +	return gzvm;
> +}
> +
> +/**
> + * gzvm_dev_ioctl_create_vm - Create vm fd
> + * @vm_type: VM type. Only supports Linux VM now.
> + *
> + * Return: fd of vm, negative if error
> + */
> +int gzvm_dev_ioctl_create_vm(unsigned long vm_type)
> +{
> +	struct gzvm *gzvm;
> +
> +	gzvm = gzvm_create_vm(vm_type);
> +	if (IS_ERR(gzvm))
> +		return PTR_ERR(gzvm);
> +
> +	return anon_inode_getfd("gzvm-vm", &gzvm_vm_fops, gzvm,
> +			       O_RDWR | O_CLOEXEC);
> +}
> +
> +void gzvm_destroy_all_vms(void)
> +{
> +	struct gzvm *gzvm, *tmp;
> +
> +	mutex_lock(&gzvm_list_lock);
> +	if (list_empty(&gzvm_list))
> +		goto out;
> +
> +	list_for_each_entry_safe(gzvm, tmp, &gzvm_list, vm_list)
> +		gzvm_destroy_vm(gzvm);
> +
> +out:
> +	mutex_unlock(&gzvm_list_lock);
> +}
> diff --git a/include/linux/gzvm_drv.h b/include/linux/gzvm_drv.h
> new file mode 100644
> index 000000000000..4fd52fcbd5a8
> --- /dev/null
> +++ b/include/linux/gzvm_drv.h
> @@ -0,0 +1,90 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#ifndef __GZVM_DRV_H__
> +#define __GZVM_DRV_H__
> +
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/gzvm.h>
> +
> +#define GZVM_VCPU_MMAP_SIZE  PAGE_SIZE
> +#define INVALID_VM_ID   0xffff
> +
> +/*
> + * These are the efinitions of APIs between GenieZone hypervisor and driver,
typo: definitions

> + * there's no need to be visible to uapi. Furthermore, We need GenieZone
We doesn't have to be capital
> + * specific error code in order to map to Linux errno
> + */
> +#define NO_ERROR                (0)
> +#define ERR_NO_MEMORY           (-5)
> +#define ERR_NOT_SUPPORTED       (-24)
> +#define ERR_NOT_IMPLEMENTED     (-27)
> +#define ERR_FAULT               (-40)
> +
> +/*
> + * The following data structures are for data transferring between driver and
> + * hypervisor, and they're aligned with hypervisor definitions
> + */
> +#define GZVM_MAX_VCPUS		 8
> +#define GZVM_MAX_MEM_REGION	10
> +
> +/* struct mem_region_addr_range - Identical to ffa memory constituent */
> +struct mem_region_addr_range {
> +	/* the base IPA of the constituent memory region, aligned to 4 kiB */
> +	__u64 address;
> +	/* the number of 4 kiB pages in the constituent memory region. */
> +	__u32 pg_cnt;
> +	__u32 reserved;
> +};
> +
> +struct gzvm_memory_region_ranges {
> +	__u32 slot;
> +	__u32 constituent_cnt;
> +	__u64 total_pages;
> +	__u64 gpa;
> +	struct mem_region_addr_range constituents[];
> +};
> +
> +/* struct gzvm_memslot - VM's memory slot descriptor */
> +struct gzvm_memslot {
> +	u64 base_gfn;			/* begin of guest page frame */
> +	unsigned long npages;		/* number of pages this slot covers */
> +	unsigned long userspace_addr;	/* corresponding userspace va */
> +	struct vm_area_struct *vma;	/* vma related to this userspace addr */
> +	u32 flags;
> +	u32 slot_id;
> +};
> +
> +struct gzvm {
> +	/* userspace tied to this vm */
> +	struct mm_struct *mm;
> +	struct gzvm_memslot memslot[GZVM_MAX_MEM_REGION];
> +	/* lock for list_add*/
> +	struct mutex lock;
> +	struct list_head vm_list;
> +	u16 vm_id;
> +};
> +
> +long gzvm_dev_ioctl_check_extension(struct gzvm *gzvm, unsigned long args);
> +int gzvm_dev_ioctl_create_vm(unsigned long vm_type);
> +
> +int gzvm_err_to_errno(unsigned long err);
> +
> +void gzvm_destroy_all_vms(void);
> +
> +/* arch-dependant functions */
> +int gzvm_arch_probe(void);
> +int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size,
> +			    phys_addr_t region);
> +int gzvm_arch_check_extension(struct gzvm *gzvm, __u64 cap, void __user *argp);
> +int gzvm_arch_create_vm(unsigned long vm_type);
> +int gzvm_arch_destroy_vm(u16 vm_id);
> +int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm,
> +				  struct gzvm_enable_cap *cap,
> +				  void __user *argp);
> +u64 gzvm_hva_to_pa_arch(u64 hva);
> +
> +#endif /* __GZVM_DRV_H__ */
> diff --git a/include/uapi/asm-generic/Kbuild b/include/uapi/asm-generic/Kbuild
> index ebb180aac74e..5af115a3c1a8 100644
> --- a/include/uapi/asm-generic/Kbuild
> +++ b/include/uapi/asm-generic/Kbuild
> @@ -34,3 +34,4 @@ mandatory-y += termbits.h
>   mandatory-y += termios.h
>   mandatory-y += types.h
>   mandatory-y += unistd.h
> +mandatory-y += gzvm_arch.h
> diff --git a/include/uapi/asm-generic/gzvm_arch.h b/include/uapi/asm-generic/gzvm_arch.h
> new file mode 100644
> index 000000000000..c4cc12716c91
> --- /dev/null
> +++ b/include/uapi/asm-generic/gzvm_arch.h
> @@ -0,0 +1,10 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#ifndef __ASM_GENERIC_GZVM_ARCH_H
> +#define __ASM_GENERIC_GZVM_ARCH_H
> +/* geniezone only supports aarch64 platform for now */
> +
> +#endif /* __ASM_GENERIC_GZVM_ARCH_H */
> diff --git a/include/uapi/linux/gzvm.h b/include/uapi/linux/gzvm.h
> new file mode 100644
> index 000000000000..99730c142b0e
> --- /dev/null
> +++ b/include/uapi/linux/gzvm.h
> @@ -0,0 +1,76 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +/**
> + * DOC: UAPI of GenieZone Hypervisor
> + *
> + * This file declares common data structure shared among user space,
> + * kernel space, and GenieZone hypervisor.
> + */
> +#ifndef __GZVM_H__
> +#define __GZVM_H__
> +
> +#include <linux/const.h>
> +#include <linux/types.h>
> +#include <linux/ioctl.h>
> +
> +#include <asm/gzvm_arch.h>
> +
> +/* GZVM ioctls */
> +#define GZVM_IOC_MAGIC			0x92	/* gz */
> +
> +/* ioctls for /dev/gzvm fds */
> +#define GZVM_CREATE_VM             _IO(GZVM_IOC_MAGIC,   0x01) /* Returns a Geniezone VM fd */
> +
> +/*
> + * Check if the given capability is supported or not.
> + * The argument is capability. Ex. GZVM_CAP_ARM_PROTECTED_VM or GZVM_CAP_ARM_VM_IPA_SIZE
> + * return is 0 (supported, no error)
> + * return is -EOPNOTSUPP (unsupported)
> + * return is -EFAULT (failed to get the argument from userspace)
> + */
> +#define GZVM_CHECK_EXTENSION       _IO(GZVM_IOC_MAGIC,   0x03)
> +
> +/* ioctls for VM fds */
> +/* for GZVM_SET_MEMORY_REGION */
> +struct gzvm_memory_region {
> +	__u32 slot;
> +	__u32 flags;
> +	__u64 guest_phys_addr;
> +	__u64 memory_size; /* bytes */
> +};
> +
> +#define GZVM_SET_MEMORY_REGION     _IOW(GZVM_IOC_MAGIC,  0x40, \
> +					struct gzvm_memory_region)
> +
> +/* for GZVM_SET_USER_MEMORY_REGION */
> +struct gzvm_userspace_memory_region {
> +	__u32 slot;
> +	__u32 flags;
> +	__u64 guest_phys_addr;
> +	/* bytes */
> +	__u64 memory_size;
> +	/* start of the userspace allocated memory */
> +	__u64 userspace_addr;
> +};
> +
> +#define GZVM_SET_USER_MEMORY_REGION _IOW(GZVM_IOC_MAGIC, 0x46, \
> +					 struct gzvm_userspace_memory_region)
> +
> +/* for GZVM_ENABLE_CAP */
> +struct gzvm_enable_cap {
> +	/* in */
> +	__u64 cap;
> +	/**
> +	 * we have total 5 (8 - 3) registers can be used for
which can be used ?

> +	 * additional args
> +	 */
> +	__u64 args[5];
> +};
> +
> +#define GZVM_ENABLE_CAP            _IOW(GZVM_IOC_MAGIC,  0xa3, \
> +					struct gzvm_enable_cap)
> +
> +#endif /* __GZVM_H__ */


Regards,

Eugen
AngeloGioacchino Del Regno July 27, 2023, 10:08 a.m. UTC | #2
Il 27/07/23 09:59, Yi-De Wu ha scritto:
> From: "Yingshiuan Pan" <yingshiuan.pan@mediatek.com>
> 
> GenieZone is MediaTek hypervisor solution, and it is running in EL2
> stand alone as a type-I hypervisor. This patch exports a set of ioctl
> interfaces for userspace VMM (e.g., crosvm) to operate guest VMs
> lifecycle (creation and destroy) on GenieZone.
> 
> Signed-off-by: Yingshiuan Pan <yingshiuan.pan@mediatek.com>
> Signed-off-by: Jerry Wang <ze-yu.wang@mediatek.com>
> Signed-off-by: Liju Chen <liju-clr.chen@mediatek.com>
> Signed-off-by: Yi-De Wu <yi-de.wu@mediatek.com>
> ---
>   MAINTAINERS                             |   6 +
>   arch/arm64/Kbuild                       |   1 +
>   arch/arm64/geniezone/Makefile           |   9 +
>   arch/arm64/geniezone/gzvm_arch_common.h |  68 ++++
>   arch/arm64/geniezone/vm.c               | 212 +++++++++++++
>   arch/arm64/include/uapi/asm/gzvm_arch.h |  20 ++
>   drivers/virt/Kconfig                    |   2 +
>   drivers/virt/geniezone/Kconfig          |  16 +
>   drivers/virt/geniezone/Makefile         |  10 +
>   drivers/virt/geniezone/gzvm_main.c      | 143 +++++++++
>   drivers/virt/geniezone/gzvm_vm.c        | 400 ++++++++++++++++++++++++
>   include/linux/gzvm_drv.h                |  90 ++++++
>   include/uapi/asm-generic/Kbuild         |   1 +
>   include/uapi/asm-generic/gzvm_arch.h    |  10 +
>   include/uapi/linux/gzvm.h               |  76 +++++
>   15 files changed, 1064 insertions(+)
>   create mode 100644 arch/arm64/geniezone/Makefile
>   create mode 100644 arch/arm64/geniezone/gzvm_arch_common.h
>   create mode 100644 arch/arm64/geniezone/vm.c
>   create mode 100644 arch/arm64/include/uapi/asm/gzvm_arch.h
>   create mode 100644 drivers/virt/geniezone/Kconfig
>   create mode 100644 drivers/virt/geniezone/Makefile
>   create mode 100644 drivers/virt/geniezone/gzvm_main.c
>   create mode 100644 drivers/virt/geniezone/gzvm_vm.c
>   create mode 100644 include/linux/gzvm_drv.h
>   create mode 100644 include/uapi/asm-generic/gzvm_arch.h
>   create mode 100644 include/uapi/linux/gzvm.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index bfbfdb790446..b91d41dd2f2f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8747,6 +8747,12 @@ M:	Ze-Yu Wang <ze-yu.wang@mediatek.com>
>   M:	Yi-De Wu <yi-de.wu@mediatek.com>
>   F:	Documentation/devicetree/bindings/hypervisor/mediatek,geniezone-hyp.yaml
>   F:	Documentation/virt/geniezone/
> +F:	arch/arm64/geniezone/
> +F:	arch/arm64/include/uapi/asm/gzvm_arch.h
> +F:	drivers/virt/geniezone/
> +F:	include/linux/gzvm_drv.h
> +F	include/uapi/asm-generic/gzvm_arch.h
> +F:	include/uapi/linux/gzvm.h
>   
>   GENWQE (IBM Generic Workqueue Card)
>   M:	Frank Haverkamp <haver@linux.ibm.com>
> diff --git a/arch/arm64/Kbuild b/arch/arm64/Kbuild
> index 5bfbf7d79c99..0c3cca572919 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_MTK_GZVM)	+= geniezone/
>   
>   # for cleaning
>   subdir- += boot
> diff --git a/arch/arm64/geniezone/Makefile b/arch/arm64/geniezone/Makefile
> new file mode 100644
> index 000000000000..2957898cdd05
> --- /dev/null
> +++ b/arch/arm64/geniezone/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Main Makefile for gzvm, this one includes drivers/virt/geniezone/Makefile
> +#
> +include $(srctree)/drivers/virt/geniezone/Makefile
> +
> +gzvm-y += vm.o
> +
> +obj-$(CONFIG_MTK_GZVM) += gzvm.o
> diff --git a/arch/arm64/geniezone/gzvm_arch_common.h b/arch/arm64/geniezone/gzvm_arch_common.h
> new file mode 100644
> index 000000000000..fdb95d619102
> --- /dev/null
> +++ b/arch/arm64/geniezone/gzvm_arch_common.h
> @@ -0,0 +1,68 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#ifndef __GZVM_ARCH_COMMON_H__
> +#define __GZVM_ARCH_COMMON_H__
> +
> +#include <linux/arm-smccc.h>
> +
> +enum {
> +	GZVM_FUNC_CREATE_VM = 0,
> +	GZVM_FUNC_DESTROY_VM = 1,
> +	GZVM_FUNC_CREATE_VCPU = 2,
> +	GZVM_FUNC_DESTROY_VCPU = 3,
> +	GZVM_FUNC_SET_MEMREGION = 4,
> +	GZVM_FUNC_RUN = 5,
> +	GZVM_FUNC_GET_ONE_REG = 8,
> +	GZVM_FUNC_SET_ONE_REG = 9,
> +	GZVM_FUNC_IRQ_LINE = 10,
> +	GZVM_FUNC_CREATE_DEVICE = 11,
> +	GZVM_FUNC_PROBE = 12,
> +	GZVM_FUNC_ENABLE_CAP = 13,
> +	NR_GZVM_FUNC,
> +};
> +
> +#define SMC_ENTITY_MTK			59
> +#define GZVM_FUNCID_START		(0x1000)
> +#define GZVM_HCALL_ID(func)						\
> +	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,	\
> +			   SMC_ENTITY_MTK, (GZVM_FUNCID_START + (func)))
> +
> +#define MT_HVC_GZVM_CREATE_VM		GZVM_HCALL_ID(GZVM_FUNC_CREATE_VM)
> +#define MT_HVC_GZVM_DESTROY_VM		GZVM_HCALL_ID(GZVM_FUNC_DESTROY_VM)
> +#define MT_HVC_GZVM_CREATE_VCPU		GZVM_HCALL_ID(GZVM_FUNC_CREATE_VCPU)
> +#define MT_HVC_GZVM_DESTROY_VCPU	GZVM_HCALL_ID(GZVM_FUNC_DESTROY_VCPU)
> +#define MT_HVC_GZVM_SET_MEMREGION	GZVM_HCALL_ID(GZVM_FUNC_SET_MEMREGION)
> +#define MT_HVC_GZVM_RUN			GZVM_HCALL_ID(GZVM_FUNC_RUN)
> +#define MT_HVC_GZVM_GET_ONE_REG		GZVM_HCALL_ID(GZVM_FUNC_GET_ONE_REG)
> +#define MT_HVC_GZVM_SET_ONE_REG		GZVM_HCALL_ID(GZVM_FUNC_SET_ONE_REG)
> +#define MT_HVC_GZVM_IRQ_LINE		GZVM_HCALL_ID(GZVM_FUNC_IRQ_LINE)
> +#define MT_HVC_GZVM_CREATE_DEVICE	GZVM_HCALL_ID(GZVM_FUNC_CREATE_DEVICE)
> +#define MT_HVC_GZVM_PROBE		GZVM_HCALL_ID(GZVM_FUNC_PROBE)
> +#define MT_HVC_GZVM_ENABLE_CAP		GZVM_HCALL_ID(GZVM_FUNC_ENABLE_CAP)
> +
> +/**
> + * gzvm_hypcall_wrapper() - the wrapper for hvc calls
> + * @a0-a7: arguments passed in registers 0 to 7
> + * @res: result values from registers 0 to 3
> + *
> + * Return: The wrapper helps caller to convert geniezone errno to Linux errno.
> + */
> +static inline int gzvm_hypcall_wrapper(unsigned long a0, unsigned long a1,
> +				       unsigned long a2, unsigned long a3,
> +				       unsigned long a4, unsigned long a5,
> +				       unsigned long a6, unsigned long a7,
> +				       struct arm_smccc_res *res)
> +{
> +	arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res);
> +	return gzvm_err_to_errno(res->a0);
> +}
> +
> +static inline u16 get_vmid_from_tuple(unsigned int tuple)
> +{
> +	return (u16)(tuple >> 16);
> +}
> +
> +#endif /* __GZVM_ARCH_COMMON_H__ */
> diff --git a/arch/arm64/geniezone/vm.c b/arch/arm64/geniezone/vm.c
> new file mode 100644
> index 000000000000..e35751b21821
> --- /dev/null
> +++ b/arch/arm64/geniezone/vm.c
> @@ -0,0 +1,212 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#include <asm/sysreg.h>
> +#include <linux/arm-smccc.h>
> +#include <linux/err.h>
> +#include <linux/uaccess.h>
> +
> +#include <linux/gzvm.h>
> +#include <linux/gzvm_drv.h>
> +#include "gzvm_arch_common.h"
> +
> +#define PAR_PA47_MASK ((((1UL << 48) - 1) >> 12) << 12)
> +
> +int gzvm_arch_probe(void)
> +{
> +	struct arm_smccc_res res;
> +
> +	arm_smccc_hvc(MT_HVC_GZVM_PROBE, 0, 0, 0, 0, 0, 0, 0, &res);
> +	if (res.a0 == 0)
> +		return 0;
> +
> +	return -ENXIO;
> +}
> +
> +int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size,
> +			    phys_addr_t region)
> +{
> +	struct arm_smccc_res res;
> +
> +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_SET_MEMREGION, vm_id,
> +				    buf_size, region, 0, 0, 0, 0, &res);
> +}
> +
> +static int gzvm_cap_arm_vm_ipa_size(void __user *argp)
> +{
> +	__u64 value = CONFIG_ARM64_PA_BITS;
> +
> +	if (copy_to_user(argp, &value, sizeof(__u64)))
> +		return -EFAULT;
> +
> +	return 0;
> +}
> +
> +int gzvm_arch_check_extension(struct gzvm *gzvm, __u64 cap, void __user *argp)
> +{
> +	int ret = -EOPNOTSUPP;

int ret;

> +
> +	switch (cap) {
> +	case GZVM_CAP_ARM_PROTECTED_VM: {
> +		__u64 success = 1;
> +
> +		if (copy_to_user(argp, &success, sizeof(__u64)))
> +			return -EFAULT;
> +		ret = 0;

here, instead of ret = 0...

		return 0;

> +		break;
> +	}
> +	case GZVM_CAP_ARM_VM_IPA_SIZE: {
> +		ret = gzvm_cap_arm_vm_ipa_size(argp);

and here
		return ret;

> +		break;
> +	}
> +	default:
> +		ret = -EOPNOTSUPP;

	break;

> +	}
> +

return -EOPNOTSUPP;

> +	return ret;
> +}
> +
> +/**
> + * gzvm_arch_create_vm() - create vm
> + * @vm_type: VM type. Only supports Linux VM now.
> + *
> + * Return:
> + * * positive value	- VM ID
> + * * -ENOMEM		- Memory not enough for storing VM data
> + */
> +int gzvm_arch_create_vm(unsigned long vm_type)
> +{
> +	struct arm_smccc_res res;
> +	int ret;
> +
> +	ret = gzvm_hypcall_wrapper(MT_HVC_GZVM_CREATE_VM, vm_type, 0, 0, 0, 0,
> +				   0, 0, &res);
> +

return ret ? ret : res.a1;

> +	if (ret == 0)
> +		return res.a1;
> +	else
> +		return ret;
> +}
> +
> +int gzvm_arch_destroy_vm(u16 vm_id)
> +{
> +	struct arm_smccc_res res;
> +
> +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_DESTROY_VM, vm_id, 0, 0, 0, 0,
> +				    0, 0, &res);
> +}
> +
> +static int gzvm_vm_arch_enable_cap(struct gzvm *gzvm,
> +				   struct gzvm_enable_cap *cap,
> +				   struct arm_smccc_res *res)
> +{
> +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_ENABLE_CAP, gzvm->vm_id,
> +				    cap->cap, cap->args[0], cap->args[1],
> +				    cap->args[2], cap->args[3], cap->args[4],
> +				    res);
> +}
> +
> +/**
> + * gzvm_vm_ioctl_get_pvmfw_size() - Get pvmfw size from hypervisor, return
> + *				    in x1, and return to userspace in args
> + * @gzvm: Pointer to struct gzvm.
> + * @cap: Pointer to struct gzvm_enable_cap.
> + * @argp: Pointer to struct gzvm_enable_cap in user space.
> + *
> + * Return:
> + * * 0			- Succeed
> + * * -EINVAL		- Hypervisor return invalid results
> + * * -EFAULT		- Fail to copy back to userspace buffer
> + */
> +static int gzvm_vm_ioctl_get_pvmfw_size(struct gzvm *gzvm,
> +					struct gzvm_enable_cap *cap,
> +					void __user *argp)
> +{
> +	struct arm_smccc_res res = {0};
> +
> +	if (gzvm_vm_arch_enable_cap(gzvm, cap, &res) != 0)
> +		return -EINVAL;
> +
> +	cap->args[1] = res.a1;
> +	if (copy_to_user(argp, cap, sizeof(*cap)))
> +		return -EFAULT;
> +
> +	return 0;
> +}
> +
> +/**
> + * gzvm_vm_ioctl_cap_pvm() - Proceed GZVM_CAP_ARM_PROTECTED_VM's subcommands
> + * @gzvm: Pointer to struct gzvm.
> + * @cap: Pointer to struct gzvm_enable_cap.
> + * @argp: Pointer to struct gzvm_enable_cap in user space.
> + *
> + * Return:
> + * * 0			- Succeed
> + * * -EINVAL		- Invalid subcommand or arguments
> + */
> +static int gzvm_vm_ioctl_cap_pvm(struct gzvm *gzvm,
> +				 struct gzvm_enable_cap *cap,
> +				 void __user *argp)
> +{
> +	int ret = -EINVAL;
> +	struct arm_smccc_res res = {0};

Invert for readability; struct arm_smccc_res res first, ret last...
also, you don't need to initialize ret to -EINVAL, because:

> +
> +	switch (cap->args[0]) {
> +	case GZVM_CAP_ARM_PVM_SET_PVMFW_IPA:
> +		fallthrough;
> +	case GZVM_CAP_ARM_PVM_SET_PROTECTED_VM:
> +		ret = gzvm_vm_arch_enable_cap(gzvm, cap, &res);

return ret;

> +		break;
> +	case GZVM_CAP_ARM_PVM_GET_PVMFW_SIZE:
> +		ret = gzvm_vm_ioctl_get_pvmfw_size(gzvm, cap, argp);

return ret;

> +		break;
> +	default:

just break here

> +		ret = -EINVAL;
> +		break;
> +	}
> +

return -EINVAL;

> +	return ret;
> +}
> +
> +int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm,
> +				  struct gzvm_enable_cap *cap,
> +				  void __user *argp)
> +{
> +	int ret = -EINVAL;

same comments here

> +
> +	switch (cap->cap) {
> +	case GZVM_CAP_ARM_PROTECTED_VM:
> +		ret = gzvm_vm_ioctl_cap_pvm(gzvm, cap, argp);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +/**
> + * gzvm_hva_to_pa_arch() - converts hva to pa with arch-specific way
> + * @hva: Host virtual address.
> + *
> + * Return: 0 if translation error
> + */
> +u64 gzvm_hva_to_pa_arch(u64 hva)
> +{
> +	u64 par;
> +	unsigned long flags;

unsigned long flags;
u64 par;

> +
> +	local_irq_save(flags);
> +	asm volatile("at s1e1r, %0" :: "r" (hva));
> +	isb();
> +	par = read_sysreg_par();
> +	local_irq_restore(flags);
> +
> +	if (par & SYS_PAR_EL1_F)
> +		return 0;
> +
> +	return par & PAR_PA47_MASK;
> +}

..snip..

> diff --git a/drivers/virt/geniezone/gzvm_main.c b/drivers/virt/geniezone/gzvm_main.c
> new file mode 100644
> index 000000000000..b629b41a0cd9
> --- /dev/null
> +++ b/drivers/virt/geniezone/gzvm_main.c
> @@ -0,0 +1,143 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#include <linux/anon_inodes.h>
> +#include <linux/device.h>
> +#include <linux/file.h>
> +#include <linux/kdev_t.h>
> +#include <linux/miscdevice.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/gzvm_drv.h>
> +

..snip..

> +
> +static long gzvm_dev_ioctl(struct file *filp, unsigned int cmd,
> +			   unsigned long user_args)
> +{
> +	long ret = -ENOTTY;

long ret;

> +
> +	switch (cmd) {
> +	case GZVM_CREATE_VM:
> +		ret = gzvm_dev_ioctl_create_vm(user_args);

you may even do just

		return gzvm_dev_ioctl_create_vm(user_args);

> +		break;
> +	case GZVM_CHECK_EXTENSION:
> +		if (!user_args)
> +			return -EINVAL;
> +		ret = gzvm_dev_ioctl_check_extension(NULL, user_args);

return ....

> +		break;
> +	default:

break...

> +		ret = -ENOTTY;
> +	}

...return

> +
> +	return ret;
> +}
> +
> +static const struct file_operations gzvm_chardev_ops = {
> +	.unlocked_ioctl = gzvm_dev_ioctl,
> +	.llseek		= noop_llseek,
> +};
> +
> +static struct miscdevice gzvm_dev = {
> +	.minor = MISC_DYNAMIC_MINOR,
> +	.name = KBUILD_MODNAME,
> +	.fops = &gzvm_chardev_ops,
> +};
> +
> +static int gzvm_drv_probe(struct platform_device *pdev)
> +{
> +	int ret;
> +
> +	if (gzvm_arch_probe() != 0) {
> +		dev_err(&pdev->dev, "Not found available conduit\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = misc_register(&gzvm_dev);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int gzvm_drv_remove(struct platform_device *pdev)
> +{
> +	gzvm_destroy_all_vms();
> +	misc_deregister(&gzvm_dev);
> +	return 0;
> +}
> +
> +static const struct of_device_id gzvm_of_match[] = {
> +	{ .compatible = "mediatek,geniezone-hyp", },

Remove the comma after "mediatek,geniezone-hyp" as it's not needed.

> +	{/* sentinel */},
> +};
> +
> +static struct platform_driver gzvm_driver = {
> +	.probe = gzvm_drv_probe,
> +	.remove = gzvm_drv_remove,
> +	.driver = {
> +		.name = KBUILD_MODNAME,
> +		.owner = THIS_MODULE,
> +		.of_match_table = gzvm_of_match,
> +	},
> +};
> +
> +module_platform_driver(gzvm_driver);
> +
> +MODULE_DEVICE_TABLE(of, gzvm_of_match);
> +MODULE_AUTHOR("MediaTek");
> +MODULE_DESCRIPTION("GenieZone interface for VMM");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/virt/geniezone/gzvm_vm.c b/drivers/virt/geniezone/gzvm_vm.c
> new file mode 100644
> index 000000000000..ee751369fd4b
> --- /dev/null
> +++ b/drivers/virt/geniezone/gzvm_vm.c
> @@ -0,0 +1,400 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2023 MediaTek Inc.
> + */
> +
> +#include <linux/anon_inodes.h>
> +#include <linux/file.h>
> +#include <linux/kdev_t.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/gzvm_drv.h>
> +
> +static DEFINE_MUTEX(gzvm_list_lock);
> +static LIST_HEAD(gzvm_list);
> +
> +/**
> + * hva_to_pa_fast() - converts hva to pa in generic fast way
> + * @hva: Host virtual address.
> + *
> + * Return: 0 if translation error
> + */
> +static u64 hva_to_pa_fast(u64 hva)
> +{
> +	struct page *page[1];
> +

Remove extra blank line

> +	u64 pfn;
> +
> +	if (get_user_page_fast_only(hva, 0, page)) {
> +		pfn = page_to_phys(page[0]);
> +		put_page((struct page *)page);
> +		return pfn;

why the else branch? just do...

	if (get_user_page_fast_only(.....)) {
		do_something; return pfn;
	}

	return 0;
}

> +	} else {
> +		return 0;
> +	}
> +}
> +
> +/**
> + * hva_to_pa_slow() - note that this function may sleep

/**
* hva_to_pa_slow() - converts hva to pa in a slow way
* @hva: Host virtual address
*
* This function converts HVA to PA in a slow way (because......)
*
* Context: This function may sleep
* Return: PA or 0 for translation error
*/

> + * @hva: Host virtual address.
> + *
> + * Return: 0 if translation error
> + */
> +static u64 hva_to_pa_slow(u64 hva)
> +{
> +	struct page *page;
> +	int npages;
> +	u64 pfn;
> +
> +	npages = get_user_pages_unlocked(hva, 1, &page, 0);
> +	if (npages != 1)
> +		return 0;
> +
> +	pfn = page_to_phys(page);
> +	put_page(page);
> +
> +	return pfn;
> +}
> +

..snip..

> +
> +/* register_memslot_addr_range() - Register memory region to GZ */

/**
  * register_memslot_addr_range() - Register memory region to GenieZone
  * @gzvm: xxxx
  * @memslot: xxxx
  *
  * Return: something
  */

> +static int
> +register_memslot_addr_range(struct gzvm *gzvm, struct gzvm_memslot *memslot)
> +{
> +	struct gzvm_memory_region_ranges *region;
> +	u32 buf_size;

u32 buf_size = PAGE_SIZE * 2;

> +	int max_nr_consti, remain_pages;
> +	u64 gfn, gfn_end;
> +
> +	buf_size = PAGE_SIZE * 2;
> +	region = alloc_pages_exact(buf_size, GFP_KERNEL);
> +	if (!region)
> +		return -ENOMEM;
> +	max_nr_consti = (buf_size - sizeof(*region)) /
> +			sizeof(struct mem_region_addr_range);
> +
> +	region->slot = memslot->slot_id;
> +	remain_pages = memslot->npages;
> +	gfn = memslot->base_gfn;
> +	gfn_end = gfn + remain_pages;
> +	while (gfn < gfn_end) {
> +		int nr_pages;

int nr_pages = fill_constituents(...)

> +
> +		nr_pages = fill_constituents(region->constituents,
> +					     &region->constituent_cnt,
> +					     max_nr_consti, gfn,
> +					     remain_pages, memslot);
> +		if (nr_pages < 0) {
> +			pr_err("Failed to fill constituents\n");
> +			free_pages_exact(region, buf_size);
> +			return nr_pages;
> +		}
> +		region->gpa = PFN_PHYS(gfn);
> +		region->total_pages = nr_pages;
> +
> +		remain_pages -= nr_pages;
> +		gfn += nr_pages;
> +
> +		if (gzvm_arch_set_memregion(gzvm->vm_id, buf_size,
> +					    virt_to_phys(region))) {
> +			pr_err("Failed to register memregion to hypervisor\n");
> +			free_pages_exact(region, buf_size);
> +			return -EFAULT;
> +		}
> +	}
> +	free_pages_exact(region, buf_size);
> +	return 0;
> +}
> +
> +/**
> + * gzvm_vm_ioctl_set_memory_region() - Set memory region of guest
> + * @gzvm: Pointer to struct gzvm.
> + * @mem: Input memory region from user.
> + *
> + * Return:

* Return: 0 for success, negative number for error
*
* -EXIO     - The memslot is out-of-range
* -EFAULT   - Cannot find corresponding vma
* -EINVAL   - Region size and VMA size mismatch
*/

> + * * -EXIO		- memslot is out-of-range
> + * * -EFAULT		- Cannot find corresponding vma
> + * * -EINVAL		- region size and vma size does not match
> + */
> +static int
> +gzvm_vm_ioctl_set_memory_region(struct gzvm *gzvm,
> +				struct gzvm_userspace_memory_region *mem)
> +{
> +	struct vm_area_struct *vma;
> +	struct gzvm_memslot *memslot;
> +	unsigned long size;
> +	__u32 slot;
> +
> +	slot = mem->slot;
> +	if (slot >= GZVM_MAX_MEM_REGION)
> +		return -ENXIO;
> +	memslot = &gzvm->memslot[slot];
> +
> +	vma = vma_lookup(gzvm->mm, mem->userspace_addr);
> +	if (!vma)
> +		return -EFAULT;
> +
> +	size = vma->vm_end - vma->vm_start;
> +	if (size != mem->memory_size)
> +		return -EINVAL;
> +
> +	memslot->base_gfn = __phys_to_pfn(mem->guest_phys_addr);
> +	memslot->npages = size >> PAGE_SHIFT;
> +	memslot->userspace_addr = mem->userspace_addr;
> +	memslot->vma = vma;
> +	memslot->flags = mem->flags;
> +	memslot->slot_id = mem->slot;
> +	return register_memslot_addr_range(gzvm, memslot);
> +}
> +

There are other instances of the same for all comments in this review, so
fix accordingly everywhere else.

Regards,
Angelo
Yi-De Wu Aug. 1, 2023, 7:03 a.m. UTC | #3
On Thu, 2023-07-27 at 11:51 +0300, Eugen Hristev wrote:
> Hi Yi-De,
> 
> On 7/27/23 10:59, Yi-De Wu wrote:
> > From: "Yingshiuan Pan" <yingshiuan.pan@mediatek.com>
> > 
> > GenieZone is MediaTek hypervisor solution, and it is running in EL2
> > stand alone as a type-I hypervisor. This patch exports a set of
> > ioctl
> > interfaces for userspace VMM (e.g., crosvm) to operate guest VMs
> > lifecycle (creation and destroy) on GenieZone.
> > 
> > Signed-off-by: Yingshiuan Pan <yingshiuan.pan@mediatek.com>
> > Signed-off-by: Jerry Wang <ze-yu.wang@mediatek.com>
> > Signed-off-by: Liju Chen <liju-clr.chen@mediatek.com>
> > Signed-off-by: Yi-De Wu <yi-de.wu@mediatek.com>
> > ---
> >   MAINTAINERS                             |   6 +
> >   arch/arm64/Kbuild                       |   1 +
> >   arch/arm64/geniezone/Makefile           |   9 +
> >   arch/arm64/geniezone/gzvm_arch_common.h |  68 ++++
> >   arch/arm64/geniezone/vm.c               | 212 +++++++++++++
> >   arch/arm64/include/uapi/asm/gzvm_arch.h |  20 ++
> >   drivers/virt/Kconfig                    |   2 +
> >   drivers/virt/geniezone/Kconfig          |  16 +
> >   drivers/virt/geniezone/Makefile         |  10 +
> >   drivers/virt/geniezone/gzvm_main.c      | 143 +++++++++
> >   drivers/virt/geniezone/gzvm_vm.c        | 400
> > ++++++++++++++++++++++++
> >   include/linux/gzvm_drv.h                |  90 ++++++
> >   include/uapi/asm-generic/Kbuild         |   1 +
> >   include/uapi/asm-generic/gzvm_arch.h    |  10 +
> >   include/uapi/linux/gzvm.h               |  76 +++++
> >   15 files changed, 1064 insertions(+)
> >   create mode 100644 arch/arm64/geniezone/Makefile
> >   create mode 100644 arch/arm64/geniezone/gzvm_arch_common.h
> >   create mode 100644 arch/arm64/geniezone/vm.c
> >   create mode 100644 arch/arm64/include/uapi/asm/gzvm_arch.h
> >   create mode 100644 drivers/virt/geniezone/Kconfig
> >   create mode 100644 drivers/virt/geniezone/Makefile
> >   create mode 100644 drivers/virt/geniezone/gzvm_main.c
> >   create mode 100644 drivers/virt/geniezone/gzvm_vm.c
> >   create mode 100644 include/linux/gzvm_drv.h
> >   create mode 100644 include/uapi/asm-generic/gzvm_arch.h
> >   create mode 100644 include/uapi/linux/gzvm.h
> > 
> 
> I have a feeling this patch is a bit big, and could help review if
> it's 
> split into chunks of smaller size.
> 
Sure, we would tear this patch apart into a couple of smaller patches
according to their features.

> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index bfbfdb790446..b91d41dd2f2f 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -8747,6 +8747,12 @@ M:	Ze-Yu Wang <ze-yu.wang@mediatek.com>
> >   M:	Yi-De Wu <yi-de.wu@mediatek.com>
> >   F:	Documentation/devicetree/bindings/hypervisor/mediatek,g
> > eniezone-hyp.yaml
> >   F:	Documentation/virt/geniezone/
> > +F:	arch/arm64/geniezone/
> > +F:	arch/arm64/include/uapi/asm/gzvm_arch.h
> > +F:	drivers/virt/geniezone/
> > +F:	include/linux/gzvm_drv.h
> > +F	include/uapi/asm-generic/gzvm_arch.h
> > +F:	include/uapi/linux/gzvm.h
> >   
> >   GENWQE (IBM Generic Workqueue Card)
> >   M:	Frank Haverkamp <haver@linux.ibm.com>
> > diff --git a/arch/arm64/Kbuild b/arch/arm64/Kbuild
> > index 5bfbf7d79c99..0c3cca572919 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_MTK_GZVM)	+= geniezone/
> >   
> >   # for cleaning
> >   subdir- += boot
> > diff --git a/arch/arm64/geniezone/Makefile
> > b/arch/arm64/geniezone/Makefile
> > new file mode 100644
> > index 000000000000..2957898cdd05
> > --- /dev/null
> > +++ b/arch/arm64/geniezone/Makefile
> > @@ -0,0 +1,9 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +#
> > +# Main Makefile for gzvm, this one includes
> > drivers/virt/geniezone/Makefile
> > +#
> > +include $(srctree)/drivers/virt/geniezone/Makefile
> > +
> > +gzvm-y += vm.o
> > +
> > +obj-$(CONFIG_MTK_GZVM) += gzvm.o
> > diff --git a/arch/arm64/geniezone/gzvm_arch_common.h
> > b/arch/arm64/geniezone/gzvm_arch_common.h
> > new file mode 100644
> > index 000000000000..fdb95d619102
> > --- /dev/null
> > +++ b/arch/arm64/geniezone/gzvm_arch_common.h
> > @@ -0,0 +1,68 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2023 MediaTek Inc.
> > + */
> > +
> > +#ifndef __GZVM_ARCH_COMMON_H__
> > +#define __GZVM_ARCH_COMMON_H__
> > +
> > +#include <linux/arm-smccc.h>
> > +
> > +enum {
> > +	GZVM_FUNC_CREATE_VM = 0,
> > +	GZVM_FUNC_DESTROY_VM = 1,
> > +	GZVM_FUNC_CREATE_VCPU = 2,
> > +	GZVM_FUNC_DESTROY_VCPU = 3,
> > +	GZVM_FUNC_SET_MEMREGION = 4,
> > +	GZVM_FUNC_RUN = 5,
> > +	GZVM_FUNC_GET_ONE_REG = 8,
> > +	GZVM_FUNC_SET_ONE_REG = 9,
> > +	GZVM_FUNC_IRQ_LINE = 10,
> > +	GZVM_FUNC_CREATE_DEVICE = 11,
> > +	GZVM_FUNC_PROBE = 12,
> > +	GZVM_FUNC_ENABLE_CAP = 13,
> > +	NR_GZVM_FUNC,
> > +};
> > +
> > +#define SMC_ENTITY_MTK			59
> > +#define GZVM_FUNCID_START		(0x1000)
> > +#define GZVM_HCALL_ID(func)					
> > 	\
> > +	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,	\
> > +			   SMC_ENTITY_MTK, (GZVM_FUNCID_START +
> > (func)))
> > +
> > +#define MT_HVC_GZVM_CREATE_VM		GZVM_HCALL_ID(GZVM_FUNC
> > _CREATE_VM)
> > +#define MT_HVC_GZVM_DESTROY_VM		GZVM_HCALL_ID(GZVM_FUNC
> > _DESTROY_VM)
> > +#define MT_HVC_GZVM_CREATE_VCPU		GZVM_HCALL_ID(GZVM_FUNC
> > _CREATE_VCPU)
> > +#define MT_HVC_GZVM_DESTROY_VCPU	GZVM_HCALL_ID(GZVM_FUNC_DESTROY
> > _VCPU)
> > +#define MT_HVC_GZVM_SET_MEMREGION	GZVM_HCALL_ID(GZVM_FUNC_SET_MEM
> > REGION)
> > +#define MT_HVC_GZVM_RUN			GZVM_HCALL_ID(GZVM_FUNC
> > _RUN)
> > +#define MT_HVC_GZVM_GET_ONE_REG		GZVM_HCALL_ID(GZVM_FUNC
> > _GET_ONE_REG)
> > +#define MT_HVC_GZVM_SET_ONE_REG		GZVM_HCALL_ID(GZVM_FUNC
> > _SET_ONE_REG)
> > +#define MT_HVC_GZVM_IRQ_LINE		GZVM_HCALL_ID(GZVM_FUNC
> > _IRQ_LINE)
> > +#define MT_HVC_GZVM_CREATE_DEVICE	GZVM_HCALL_ID(GZVM_FUNC_CREATE_
> > DEVICE)
> > +#define MT_HVC_GZVM_PROBE		GZVM_HCALL_ID(GZVM_FUNC_PROBE)
> > +#define MT_HVC_GZVM_ENABLE_CAP		GZVM_HCALL_ID(GZVM_FUNC
> > _ENABLE_CAP)
> > +
> > +/**
> > + * gzvm_hypcall_wrapper() - the wrapper for hvc calls
> > + * @a0-a7: arguments passed in registers 0 to 7
> > + * @res: result values from registers 0 to 3
> > + *
> > + * Return: The wrapper helps caller to convert geniezone errno to
> > Linux errno.
> > + */
> > +static inline int gzvm_hypcall_wrapper(unsigned long a0, unsigned
> > long a1,
> > +				       unsigned long a2, unsigned long
> > a3,
> > +				       unsigned long a4, unsigned long
> > a5,
> > +				       unsigned long a6, unsigned long
> > a7,
> > +				       struct arm_smccc_res *res)
> > +{
> > +	arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res);
> > +	return gzvm_err_to_errno(res->a0);
> > +}
> > +
> > +static inline u16 get_vmid_from_tuple(unsigned int tuple)
> > +{
> > +	return (u16)(tuple >> 16);
> > +}
> > +
> > +#endif /* __GZVM_ARCH_COMMON_H__ */
> > diff --git a/arch/arm64/geniezone/vm.c b/arch/arm64/geniezone/vm.c
> > new file mode 100644
> > index 000000000000..e35751b21821
> > --- /dev/null
> > +++ b/arch/arm64/geniezone/vm.c
> > @@ -0,0 +1,212 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2023 MediaTek Inc.
> > + */
> > +
> > +#include <asm/sysreg.h>
> > +#include <linux/arm-smccc.h>
> > +#include <linux/err.h>
> > +#include <linux/uaccess.h>
> > +
> > +#include <linux/gzvm.h>
> > +#include <linux/gzvm_drv.h>
> > +#include "gzvm_arch_common.h"
> > +
> > +#define PAR_PA47_MASK ((((1UL << 48) - 1) >> 12) << 12)
> > +
> > +int gzvm_arch_probe(void)
> > +{
> > +	struct arm_smccc_res res;
> > +
> > +	arm_smccc_hvc(MT_HVC_GZVM_PROBE, 0, 0, 0, 0, 0, 0, 0, &res);
> > +	if (res.a0 == 0)
> > +		return 0;
> 
> I would see the error path as a particular case here, e.g.
> 
> if (res.a0)
> 	return -ENXIO;
> 
> and on the usual path return success.
> 
> (as you already do below in some functions)...
> 
> > +
> > +	return -ENXIO;
> > +}
> > +
> > +int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size,
> > +			    phys_addr_t region)
> > +{
> > +	struct arm_smccc_res res;
> > +
> > +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_SET_MEMREGION, vm_id,
> > +				    buf_size, region, 0, 0, 0, 0,
> > &res);
> > +}
> > +
> > +static int gzvm_cap_arm_vm_ipa_size(void __user *argp)
> > +{
> > +	__u64 value = CONFIG_ARM64_PA_BITS;
> > +
> > +	if (copy_to_user(argp, &value, sizeof(__u64)))
> > +		return -EFAULT;
> 
> ... e.g. here.
> 
> > +
> > +	return 0;
> > +}
> > +
> > +int gzvm_arch_check_extension(struct gzvm *gzvm, __u64 cap, void
> > __user *argp)
> > +{
> > +	int ret = -EOPNOTSUPP;
> > +
> > +	switch (cap) {
> > +	case GZVM_CAP_ARM_PROTECTED_VM: {
> > +		__u64 success = 1;
> > +
> > +		if (copy_to_user(argp, &success, sizeof(__u64)))
> > +			return -EFAULT;
> > +		ret = 0;
> > +		break;
> > +	}
> > +	case GZVM_CAP_ARM_VM_IPA_SIZE: {
> > +		ret = gzvm_cap_arm_vm_ipa_size(argp);
> > +		break;
> > +	}
> > +	default:
> > +		ret = -EOPNOTSUPP;
> 
> you already initialized ret to -EOPNOTSUPP, why don't you initialize
> it 
> with 0, and just set it as error code here, and avoid setting it to 0
> on 
> the success case above.
> 
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +/**
> > + * gzvm_arch_create_vm() - create vm
> > + * @vm_type: VM type. Only supports Linux VM now.
> > + *
> > + * Return:
> > + * * positive value	- VM ID
> > + * * -ENOMEM		- Memory not enough for storing VM data
> > + */
> > +int gzvm_arch_create_vm(unsigned long vm_type)
> > +{
> > +	struct arm_smccc_res res;
> > +	int ret;
> > +
> > +	ret = gzvm_hypcall_wrapper(MT_HVC_GZVM_CREATE_VM, vm_type, 0,
> > 0, 0, 0,
> > +				   0, 0, &res);
> > +
> > +	if (ret == 0)
> > +		return res.a1;
> > +	else
> > +		return ret;
> > +}
> > +
> > +int gzvm_arch_destroy_vm(u16 vm_id)
> > +{
> > +	struct arm_smccc_res res;
> > +
> > +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_DESTROY_VM, vm_id, 0,
> > 0, 0, 0,
> > +				    0, 0, &res);
> > +}
> > +
> > +static int gzvm_vm_arch_enable_cap(struct gzvm *gzvm,
> > +				   struct gzvm_enable_cap *cap,
> > +				   struct arm_smccc_res *res)
> > +{
> > +	return gzvm_hypcall_wrapper(MT_HVC_GZVM_ENABLE_CAP, gzvm-
> > >vm_id,
> > +				    cap->cap, cap->args[0], cap-
> > >args[1],
> > +				    cap->args[2], cap->args[3], cap-
> > >args[4],
> > +				    res);
> > +}
> > +
> > +/**
> > + * gzvm_vm_ioctl_get_pvmfw_size() - Get pvmfw size from
> > hypervisor, return
> > + *				    in x1, and return to userspace in
> > args
> > + * @gzvm: Pointer to struct gzvm.
> > + * @cap: Pointer to struct gzvm_enable_cap.
> > + * @argp: Pointer to struct gzvm_enable_cap in user space.
> > + *
> > + * Return:
> > + * * 0			- Succeed
> > + * * -EINVAL		- Hypervisor return invalid results
> > + * * -EFAULT		- Fail to copy back to userspace buffer
> > + */
> > +static int gzvm_vm_ioctl_get_pvmfw_size(struct gzvm *gzvm,
> > +					struct gzvm_enable_cap *cap,
> > +					void __user *argp)
> > +{
> > +	struct arm_smccc_res res = {0};
> > +
> > +	if (gzvm_vm_arch_enable_cap(gzvm, cap, &res) != 0)
> > +		return -EINVAL;
> > +
> > +	cap->args[1] = res.a1;
> > +	if (copy_to_user(argp, cap, sizeof(*cap)))
> > +		return -EFAULT;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * gzvm_vm_ioctl_cap_pvm() - Proceed GZVM_CAP_ARM_PROTECTED_VM's
> > subcommands
> > + * @gzvm: Pointer to struct gzvm.
> > + * @cap: Pointer to struct gzvm_enable_cap.
> > + * @argp: Pointer to struct gzvm_enable_cap in user space.
> > + *
> > + * Return:
> > + * * 0			- Succeed
> > + * * -EINVAL		- Invalid subcommand or arguments
> > + */
> > +static int gzvm_vm_ioctl_cap_pvm(struct gzvm *gzvm,
> > +				 struct gzvm_enable_cap *cap,
> > +				 void __user *argp)
> > +{
> > +	int ret = -EINVAL;
> 
> This initialization appears redundant as you always rewrite ret to a
> new 
> value below
> 
> > +	struct arm_smccc_res res = {0};
> > +
> > +	switch (cap->args[0]) {
> > +	case GZVM_CAP_ARM_PVM_SET_PVMFW_IPA:
> > +		fallthrough;
> > +	case GZVM_CAP_ARM_PVM_SET_PROTECTED_VM:
> > +		ret = gzvm_vm_arch_enable_cap(gzvm, cap, &res);
> > +		break;
> > +	case GZVM_CAP_ARM_PVM_GET_PVMFW_SIZE:
> > +		ret = gzvm_vm_ioctl_get_pvmfw_size(gzvm, cap, argp);
> > +		break;
> > +	default:
> > +		ret = -EINVAL;
> > +		break;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm,
> > +				  struct gzvm_enable_cap *cap,
> > +				  void __user *argp)
> > +{
> > +	int ret = -EINVAL;
> 
> same here
> > +
> > +	switch (cap->cap) {
> > +	case GZVM_CAP_ARM_PROTECTED_VM:
> > +		ret = gzvm_vm_ioctl_cap_pvm(gzvm, cap, argp);
> > +		break;
> > +	default:
> > +		ret = -EINVAL;
> > +		break;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +/**
> > + * gzvm_hva_to_pa_arch() - converts hva to pa with arch-specific
> > way
> > + * @hva: Host virtual address.
> > + *
> > + * Return: 0 if translation error
> 
> This is a bit misleading, if you look at the code, you return 0 if
> the 
> bit SYS_PAR_EL1_F is present, but also return 0 if bit PAR_PA47_MASK
> is 
> not present. Are those situations identical ?
> 
> Also, it's a bit strange to return 0 for an error case.
> 
> > + */
> > +u64 gzvm_hva_to_pa_arch(u64 hva)
> > +{
> > +	u64 par;
> > +	unsigned long flags;
> > +
> > +	local_irq_save(flags);
> > +	asm volatile("at s1e1r, %0" :: "r" (hva));
> > +	isb();
> > +	par = read_sysreg_par();
> > +	local_irq_restore(flags);
> > +
> > +	if (par & SYS_PAR_EL1_F)
> > +		return 0;
> > +
> > +	return par & PAR_PA47_MASK;
> > +}
> > diff --git a/arch/arm64/include/uapi/asm/gzvm_arch.h
> > b/arch/arm64/include/uapi/asm/gzvm_arch.h
> > new file mode 100644
> > index 000000000000..847bb627a65d
> > --- /dev/null
> > +++ b/arch/arm64/include/uapi/asm/gzvm_arch.h
> > @@ -0,0 +1,20 @@
> > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> > +/*
> > + * Copyright (c) 2023 MediaTek Inc.
> > + */
> > +
> > +#ifndef __GZVM_ARCH_H__
> > +#define __GZVM_ARCH_H__
> > +
> > +#include <linux/types.h>
> > +
> > +#define GZVM_CAP_ARM_VM_IPA_SIZE	165
> > +#define GZVM_CAP_ARM_PROTECTED_VM	0xffbadab1
> > +
> > +/* sub-commands put in args[0] for GZVM_CAP_ARM_PROTECTED_VM */
> > +#define GZVM_CAP_ARM_PVM_SET_PVMFW_IPA		0
> > +#define GZVM_CAP_ARM_PVM_GET_PVMFW_SIZE		1
> > +/* GZVM_CAP_ARM_PVM_SET_PROTECTED_VM only sets protected but not
> > load pvmfw */
> > +#define GZVM_CAP_ARM_PVM_SET_PROTECTED_VM	2
> > +
> > +#endif /* __GZVM_ARCH_H__ */
> > diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
> > index f79ab13a5c28..9bbf0bdf672c 100644
> > --- a/drivers/virt/Kconfig
> > +++ b/drivers/virt/Kconfig
> > @@ -54,4 +54,6 @@ source "drivers/virt/coco/sev-guest/Kconfig"
> >   
> >   source "drivers/virt/coco/tdx-guest/Kconfig"
> >   
> > +source "drivers/virt/geniezone/Kconfig"
> > +
> >   endif
> > diff --git a/drivers/virt/geniezone/Kconfig
> > b/drivers/virt/geniezone/Kconfig
> > new file mode 100644
> > index 000000000000..2643fb8913cc
> > --- /dev/null
> > +++ b/drivers/virt/geniezone/Kconfig
> > @@ -0,0 +1,16 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +config MTK_GZVM
> > +	tristate "GenieZone Hypervisor driver for guest VM operation"
> > +	depends on ARM64
> 
> if only mediatek SoC is supported, should it depend on it here ?
> 
> > +	help
> > +	  This driver, gzvm, enables to run guest VMs on MTK GenieZone
> > +	  hypervisor. It exports kvm-like interfaces for VMM (e.g.,
> > crosvm) in
> > +	  order to operate guest VMs on GenieZone hypervisor.
> > +
> > +	  GenieZone hypervisor now only supports MediaTek SoC and arm64
> > +	  architecture.
> > +
> > +	  Select M if you want it be built as a module (gzvm.ko).
> > +
> > +	  If unsure, say N.
> > diff --git a/drivers/virt/geniezone/Makefile
> > b/drivers/virt/geniezone/Makefile
> > new file mode 100644
> > index 000000000000..066efddc0b9c
> > --- /dev/null
> > +++ b/drivers/virt/geniezone/Makefile
> > @@ -0,0 +1,10 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +#
> > +# Makefile for GenieZone driver, this file should be include in
> > arch's
> > +# to avoid two ko being generated.
> > +#
> > +
> > +GZVM_DIR ?= ../../../drivers/virt/geniezone
> > +
> > +gzvm-y := $(GZVM_DIR)/gzvm_main.o $(GZVM_DIR)/gzvm_vm.o
> > +
> > diff --git a/drivers/virt/geniezone/gzvm_main.c
> > b/drivers/virt/geniezone/gzvm_main.c
> > new file mode 100644
> > index 000000000000..b629b41a0cd9
> > --- /dev/null
> > +++ b/drivers/virt/geniezone/gzvm_main.c
> > @@ -0,0 +1,143 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2023 MediaTek Inc.
> > + */
> > +
> > +#include <linux/anon_inodes.h>
> > +#include <linux/device.h>
> > +#include <linux/file.h>
> > +#include <linux/kdev_t.h>
> > +#include <linux/miscdevice.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/slab.h>
> > +#include <linux/gzvm_drv.h>
> > +
> > +/**
> > + * gzvm_err_to_errno() - Convert geniezone return value to
> > standard errno
> > + *
> > + * @err: Return value from geniezone function return
> > + *
> > + * Return: Standard errno
> > + */
> > +int gzvm_err_to_errno(unsigned long err)
> > +{
> > +	int gz_err = (int)err;
> > +
> > +	switch (gz_err) {
> > +	case 0:
> > +		return 0;
> > +	case ERR_NO_MEMORY:
> > +		return -ENOMEM;
> > +	case ERR_NOT_SUPPORTED:
> > +		return -EOPNOTSUPP;
> > +	case ERR_NOT_IMPLEMENTED:
> > +		return -EOPNOTSUPP;
> > +	case ERR_FAULT:
> > +		return -EFAULT;
> > +	default:
> > +		break;
> > +	}
> > +
> > +	return -EINVAL;
> > +}
> > +
> > +/**
> > + * gzvm_dev_ioctl_check_extension() - Check if given capability is
> > support
> > + *				      or not
> > + *
> > + * @gzvm: Pointer to struct gzvm
> > + * @args: Pointer in u64 from userspace
> > + *
> > + * Return:
> > + * * 0			- Support, no error
> 
> Supported ?
> 
> > + * * -EOPNOTSUPP	- Not support
> > + * * -EFAULT		- Failed to get data from userspace
> > + */
> > +long gzvm_dev_ioctl_check_extension(struct gzvm *gzvm, unsigned
> > long args)
> > +{
> > +	__u64 cap;
> > +	void __user *argp = (void __user *)args;
> > +
> > +	if (copy_from_user(&cap, argp, sizeof(uint64_t)))
> > +		return -EFAULT;
> > +	return gzvm_arch_check_extension(gzvm, cap, argp);
> > +}
> > +
> > +static long gzvm_dev_ioctl(struct file *filp, unsigned int cmd,
> > +			   unsigned long user_args)
> > +{
> > +	long ret = -ENOTTY;
> 
> again redundant initializations
> > +
> > +	switch (cmd) {
> > +	case GZVM_CREATE_VM:
> > +		ret = gzvm_dev_ioctl_create_vm(user_args);
> > +		break;
> > +	case GZVM_CHECK_EXTENSION:
> > +		if (!user_args)
> > +			return -EINVAL;
> > +		ret = gzvm_dev_ioctl_check_extension(NULL, user_args);
> > +		break;
> > +	default:
> > +		ret = -ENOTTY;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct file_operations gzvm_chardev_ops = {
> > +	.unlocked_ioctl = gzvm_dev_ioctl,
> > +	.llseek		= noop_llseek,
> > +};
> > +
> > +static struct miscdevice gzvm_dev = {
> > +	.minor = MISC_DYNAMIC_MINOR,
> > +	.name = KBUILD_MODNAME,
> > +	.fops = &gzvm_chardev_ops,
> > +};
> > +
> > +static int gzvm_drv_probe(struct platform_device *pdev)
> > +{
> > +	int ret;
> > +
> > +	if (gzvm_arch_probe() != 0) {
> > +		dev_err(&pdev->dev, "Not found available conduit\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	ret = misc_register(&gzvm_dev);
> 
> return misc_register(...) ?
> 
> > +	if (ret)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static int gzvm_drv_remove(struct platform_device *pdev)
> > +{
> > +	gzvm_destroy_all_vms();
> > +	misc_deregister(&gzvm_dev);
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id gzvm_of_match[] = {
> > +	{ .compatible = "mediatek,geniezone-hyp", },
> > +	{/* sentinel */},
> > +};
> > +
> > +static struct platform_driver gzvm_driver = {
> > +	.probe = gzvm_drv_probe,
> > +	.remove = gzvm_drv_remove,
> > +	.driver = {
> > +		.name = KBUILD_MODNAME,
> > +		.owner = THIS_MODULE,
> > +		.of_match_table = gzvm_of_match,
> > +	},
> > +};
> > +
> > +module_platform_driver(gzvm_driver);
> > +
> > +MODULE_DEVICE_TABLE(of, gzvm_of_match);
> > +MODULE_AUTHOR("MediaTek");
> > +MODULE_DESCRIPTION("GenieZone interface for VMM");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/virt/geniezone/gzvm_vm.c
> > b/drivers/virt/geniezone/gzvm_vm.c
> > new file mode 100644
> > index 000000000000..ee751369fd4b
> > --- /dev/null
> > +++ b/drivers/virt/geniezone/gzvm_vm.c
> > @@ -0,0 +1,400 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2023 MediaTek Inc.
> > + */
> > +
> > +#include <linux/anon_inodes.h>
> > +#include <linux/file.h>
> > +#include <linux/kdev_t.h>
> > +#include <linux/mm.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/slab.h>
> > +#include <linux/gzvm_drv.h>
> > +
> > +static DEFINE_MUTEX(gzvm_list_lock);
> > +static LIST_HEAD(gzvm_list);
> > +
> > +/**
> > + * hva_to_pa_fast() - converts hva to pa in generic fast way
> > + * @hva: Host virtual address.
> > + *
> > + * Return: 0 if translation error
> > + */
> > +static u64 hva_to_pa_fast(u64 hva)
> > +{
> > +	struct page *page[1];
> > +
> > +	u64 pfn;
> > +
> > +	if (get_user_page_fast_only(hva, 0, page)) {
> > +		pfn = page_to_phys(page[0]);
> > +		put_page((struct page *)page);
> > +		return pfn;
> > +	} else {
> 
> you can remove the 'else' and just return 0 here as you return pfn
> in 
> the if(true) case.
> 
> > +		return 0;
> > +	}
> > +}
> > +
> > +/**
> > + * hva_to_pa_slow() - note that this function may sleep
> > + * @hva: Host virtual address.
> > + *
> > + * Return: 0 if translation error
> > + */
> > +static u64 hva_to_pa_slow(u64 hva)
> > +{
> > +	struct page *page;
> > +	int npages;
> > +	u64 pfn;
> > +
> > +	npages = get_user_pages_unlocked(hva, 1, &page, 0);
> > +	if (npages != 1)
> > +		return 0;
> > +
> > +	pfn = page_to_phys(page);
> > +	put_page(page);
> > +
> > +	return pfn;
> > +}
> > +
> > +static u64 gzvm_gfn_to_hva_memslot(struct gzvm_memslot *memslot,
> > u64 gfn)
> > +{
> > +	u64 offset = gfn - memslot->base_gfn;
> > +
> > +	return memslot->userspace_addr + offset * PAGE_SIZE;
> > +}
> > +
> > +static u64 __gzvm_gfn_to_pfn_memslot(struct gzvm_memslot *memslot,
> > u64 gfn)
> > +{
> > +	u64 hva, pa;
> > +
> > +	hva = gzvm_gfn_to_hva_memslot(memslot, gfn);
> > +
> > +	pa = gzvm_hva_to_pa_arch(hva);
> > +	if (pa != 0)
> > +		return PHYS_PFN(pa);
> > +
> > +	pa = hva_to_pa_fast(hva);
> > +	if (pa)
> > +		return PHYS_PFN(pa);
> > +
> > +	pa = hva_to_pa_slow(hva);
> > +	if (pa)
> > +		return PHYS_PFN(pa);
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * gzvm_gfn_to_pfn_memslot() - Translate gfn (guest ipa) to pfn
> > (host pa),
> > + *			       result is in @pfn
> > + * @memslot: Pointer to struct gzvm_memslot.
> > + * @gfn: Guest frame number.
> > + * @pfn: Host page frame number.
> > + *
> > + * Return:
> > + * * 0			- Succeed
> > + * * -EFAULT		- Failed to convert
> > + */
> > +static int gzvm_gfn_to_pfn_memslot(struct gzvm_memslot *memslot,
> > u64 gfn,
> > +				   u64 *pfn)
> > +{
> > +	u64 __pfn;
> > +
> > +	if (!memslot)
> > +		return -EFAULT;
> > +
> > +	__pfn = __gzvm_gfn_to_pfn_memslot(memslot, gfn);
> > +	if (__pfn == 0) {
> > +		*pfn = 0;
> > +		return -EFAULT;
> > +	}
> > +
> > +	*pfn = __pfn;
> > +
> > +	return 0;
> > +}
> > +
> > +/**
> > + * fill_constituents() - Populate pa to buffer until full
> > + * @consti: Pointer to struct mem_region_addr_range.
> > + * @consti_cnt: Constituent count.
> > + * @max_nr_consti: Maximum number of constituent count.
> > + * @gfn: Guest frame number.
> > + * @total_pages: Total page numbers.
> > + * @slot: Pointer to struct gzvm_memslot.
> > + *
> > + * Return: how many pages we've fill in, negative if error
> > + */
> > +static int fill_constituents(struct mem_region_addr_range *consti,
> > +			     int *consti_cnt, int max_nr_consti, u64
> > gfn,
> > +			     u32 total_pages, struct gzvm_memslot
> > *slot)
> > +{
> > +	u64 pfn, prev_pfn, gfn_end;
> > +	int nr_pages = 1;
> > +	int i = 0;
> > +
> > +	if (unlikely(total_pages == 0))
> > +		return -EINVAL;
> > +	gfn_end = gfn + total_pages;
> > +
> > +	/* entry 0 */
> > +	if (gzvm_gfn_to_pfn_memslot(slot, gfn, &pfn) != 0)
> > +		return -EFAULT;
> > +	consti[0].address = PFN_PHYS(pfn);
> > +	consti[0].pg_cnt = 1;
> > +	gfn++;
> > +	prev_pfn = pfn;
> > +
> > +	while (i < max_nr_consti && gfn < gfn_end) {
> > +		if (gzvm_gfn_to_pfn_memslot(slot, gfn, &pfn) != 0)
> > +			return -EFAULT;
> > +		if (pfn == (prev_pfn + 1)) {
> > +			consti[i].pg_cnt++;
> > +		} else {
> > +			i++;
> > +			if (i >= max_nr_consti)
> > +				break;
> > +			consti[i].address = PFN_PHYS(pfn);
> > +			consti[i].pg_cnt = 1;
> > +		}
> > +		prev_pfn = pfn;
> > +		gfn++;
> > +		nr_pages++;
> > +	}
> > +	if (i != max_nr_consti)
> > +		i++;
> > +	*consti_cnt = i;
> > +
> > +	return nr_pages;
> > +}
> > +
> > +/* register_memslot_addr_range() - Register memory region to GZ */
> > +static int
> > +register_memslot_addr_range(struct gzvm *gzvm, struct gzvm_memslot
> > *memslot)
> > +{
> > +	struct gzvm_memory_region_ranges *region;
> > +	u32 buf_size;
> > +	int max_nr_consti, remain_pages;
> > +	u64 gfn, gfn_end;
> > +
> > +	buf_size = PAGE_SIZE * 2;
> > +	region = alloc_pages_exact(buf_size, GFP_KERNEL);
> > +	if (!region)
> > +		return -ENOMEM;
> > +	max_nr_consti = (buf_size - sizeof(*region)) /
> > +			sizeof(struct mem_region_addr_range);
> > +
> > +	region->slot = memslot->slot_id;
> > +	remain_pages = memslot->npages;
> > +	gfn = memslot->base_gfn;
> > +	gfn_end = gfn + remain_pages;
> > +	while (gfn < gfn_end) {
> > +		int nr_pages;
> > +
> > +		nr_pages = fill_constituents(region->constituents,
> > +					     &region->constituent_cnt,
> > +					     max_nr_consti, gfn,
> > +					     remain_pages, memslot);
> > +		if (nr_pages < 0) {
> > +			pr_err("Failed to fill constituents\n");
> > +			free_pages_exact(region, buf_size);
> > +			return nr_pages;
> > +		}
> > +		region->gpa = PFN_PHYS(gfn);
> > +		region->total_pages = nr_pages;
> > +
> > +		remain_pages -= nr_pages;
> > +		gfn += nr_pages;
> > +
> > +		if (gzvm_arch_set_memregion(gzvm->vm_id, buf_size,
> > +					    virt_to_phys(region))) {
> > +			pr_err("Failed to register memregion to
> > hypervisor\n");
> > +			free_pages_exact(region, buf_size);
> > +			return -EFAULT;
> > +		}
> > +	}
> > +	free_pages_exact(region, buf_size);
> > +	return 0;
> > +}
> > +
> > +/**
> > + * gzvm_vm_ioctl_set_memory_region() - Set memory region of guest
> > + * @gzvm: Pointer to struct gzvm.
> > + * @mem: Input memory region from user.
> > + *
> > + * Return:
> > + * * -EXIO		- memslot is out-of-range
> > + * * -EFAULT		- Cannot find corresponding vma
> > + * * -EINVAL		- region size and vma size does not
> > match
> 
> I assume 0 for success ?
> 
> > + */
> > +static int
> > +gzvm_vm_ioctl_set_memory_region(struct gzvm *gzvm,
> > +				struct gzvm_userspace_memory_region
> > *mem)
> > +{
> > +	struct vm_area_struct *vma;
> > +	struct gzvm_memslot *memslot;
> > +	unsigned long size;
> > +	__u32 slot;
> > +
> > +	slot = mem->slot;
> > +	if (slot >= GZVM_MAX_MEM_REGION)
> > +		return -ENXIO;
> > +	memslot = &gzvm->memslot[slot];
> > +
> > +	vma = vma_lookup(gzvm->mm, mem->userspace_addr);
> > +	if (!vma)
> > +		return -EFAULT;
> > +
> > +	size = vma->vm_end - vma->vm_start;
> > +	if (size != mem->memory_size)
> > +		return -EINVAL;
> > +
> > +	memslot->base_gfn = __phys_to_pfn(mem->guest_phys_addr);
> > +	memslot->npages = size >> PAGE_SHIFT;
> > +	memslot->userspace_addr = mem->userspace_addr;
> > +	memslot->vma = vma;
> > +	memslot->flags = mem->flags;
> > +	memslot->slot_id = mem->slot;
> > +	return register_memslot_addr_range(gzvm, memslot);
> > +}
> > +
> > +static int gzvm_vm_ioctl_enable_cap(struct gzvm *gzvm,
> > +				    struct gzvm_enable_cap *cap,
> > +				    void __user *argp)
> > +{
> > +	return gzvm_vm_ioctl_arch_enable_cap(gzvm, cap, argp);
> > +}
> > +
> > +/* gzvm_vm_ioctl() - Ioctl handler of VM FD */
> > +static long gzvm_vm_ioctl(struct file *filp, unsigned int ioctl,
> > +			  unsigned long arg)
> > +{
> > +	long ret = -ENOTTY;
> 
> appears to be redundant
> 
> > +	void __user *argp = (void __user *)arg;
> > +	struct gzvm *gzvm = filp->private_data;
> > +
> > +	switch (ioctl) {
> > +	case GZVM_CHECK_EXTENSION: {
> > +		ret = gzvm_dev_ioctl_check_extension(gzvm, arg);
> > +		break;
> > +	}
> > +	case GZVM_SET_USER_MEMORY_REGION: {
> > +		struct gzvm_userspace_memory_region userspace_mem;
> > +
> > +		if (copy_from_user(&userspace_mem, argp,
> > sizeof(userspace_mem))) {
> > +			ret = -EFAULT;
> > +			goto out;
> > +		}
> > +		ret = gzvm_vm_ioctl_set_memory_region(gzvm,
> > &userspace_mem);
> > +		break;
> > +	}
> > +	case GZVM_ENABLE_CAP: {
> > +		struct gzvm_enable_cap cap;
> > +
> > +		if (copy_from_user(&cap, argp, sizeof(cap))) {
> > +			ret = -EFAULT;
> > +			goto out;
> > +		}
> > +		ret = gzvm_vm_ioctl_enable_cap(gzvm, &cap, argp);
> > +		break;
> > +	}
> > +	default:
> > +		ret = -ENOTTY;
> > +	}
> > +out:
> > +	return ret;
> > +}
> > +
> > +static void gzvm_destroy_vm(struct gzvm *gzvm)
> > +{
> > +	pr_debug("VM-%u is going to be destroyed\n", gzvm->vm_id);
> > +
> > +	mutex_lock(&gzvm->lock);
> > +
> > +	gzvm_arch_destroy_vm(gzvm->vm_id);
> > +
> > +	mutex_lock(&gzvm_list_lock);
> > +	list_del(&gzvm->vm_list);
> > +	mutex_unlock(&gzvm_list_lock);
> > +
> > +	mutex_unlock(&gzvm->lock);
> > +
> > +	kfree(gzvm);
> > +}
> > +
> > +static int gzvm_vm_release(struct inode *inode, struct file *filp)
> > +{
> > +	struct gzvm *gzvm = filp->private_data;
> > +
> > +	gzvm_destroy_vm(gzvm);
> > +	return 0;
> > +}
> > +
> > +static const struct file_operations gzvm_vm_fops = {
> > +	.release        = gzvm_vm_release,
> > +	.unlocked_ioctl = gzvm_vm_ioctl,
> > +	.llseek		= noop_llseek,
> > +};
> > +
> > +static struct gzvm *gzvm_create_vm(unsigned long vm_type)
> > +{
> > +	int ret;
> > +	struct gzvm *gzvm;
> > +
> > +	gzvm = kzalloc(sizeof(*gzvm), GFP_KERNEL);
> > +	if (!gzvm)
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	ret = gzvm_arch_create_vm(vm_type);
> > +	if (ret < 0) {
> > +		kfree(gzvm);
> > +		return ERR_PTR(ret);
> > +	}
> > +
> > +	gzvm->vm_id = ret;
> > +	gzvm->mm = current->mm;
> > +	mutex_init(&gzvm->lock);
> > +
> > +	mutex_lock(&gzvm_list_lock);
> > +	list_add(&gzvm->vm_list, &gzvm_list);
> > +	mutex_unlock(&gzvm_list_lock);
> > +
> > +	pr_debug("VM-%u is created\n", gzvm->vm_id);
> > +
> > +	return gzvm;
> > +}
> > +
> > +/**
> > + * gzvm_dev_ioctl_create_vm - Create vm fd
> > + * @vm_type: VM type. Only supports Linux VM now.
> > + *
> > + * Return: fd of vm, negative if error
> > + */
> > +int gzvm_dev_ioctl_create_vm(unsigned long vm_type)
> > +{
> > +	struct gzvm *gzvm;
> > +
> > +	gzvm = gzvm_create_vm(vm_type);
> > +	if (IS_ERR(gzvm))
> > +		return PTR_ERR(gzvm);
> > +
> > +	return anon_inode_getfd("gzvm-vm", &gzvm_vm_fops, gzvm,
> > +			       O_RDWR | O_CLOEXEC);
> > +}
> > +
> > +void gzvm_destroy_all_vms(void)
> > +{
> > +	struct gzvm *gzvm, *tmp;
> > +
> > +	mutex_lock(&gzvm_list_lock);
> > +	if (list_empty(&gzvm_list))
> > +		goto out;
> > +
> > +	list_for_each_entry_safe(gzvm, tmp, &gzvm_list, vm_list)
> > +		gzvm_destroy_vm(gzvm);
> > +
> > +out:
> > +	mutex_unlock(&gzvm_list_lock);
> > +}
> > diff --git a/include/linux/gzvm_drv.h b/include/linux/gzvm_drv.h
> > new file mode 100644
> > index 000000000000..4fd52fcbd5a8
> > --- /dev/null
> > +++ b/include/linux/gzvm_drv.h
> > @@ -0,0 +1,90 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2023 MediaTek Inc.
> > + */
> > +
> > +#ifndef __GZVM_DRV_H__
> > +#define __GZVM_DRV_H__
> > +
> > +#include <linux/list.h>
> > +#include <linux/mutex.h>
> > +#include <linux/gzvm.h>
> > +
> > +#define GZVM_VCPU_MMAP_SIZE  PAGE_SIZE
> > +#define INVALID_VM_ID   0xffff
> > +
> > +/*
> > + * These are the efinitions of APIs between GenieZone hypervisor
> > and driver,
> 
> typo: definitions
> 
> > + * there's no need to be visible to uapi. Furthermore, We need
> > GenieZone
> 
> We doesn't have to be capital
> > + * specific error code in order to map to Linux errno
> > + */
> > +#define NO_ERROR                (0)
> > +#define ERR_NO_MEMORY           (-5)
> > +#define ERR_NOT_SUPPORTED       (-24)
> > +#define ERR_NOT_IMPLEMENTED     (-27)
> > +#define ERR_FAULT               (-40)
> > +
> > +/*
> > + * The following data structures are for data transferring between
> > driver and
> > + * hypervisor, and they're aligned with hypervisor definitions
> > + */
> > +#define GZVM_MAX_VCPUS		 8
> > +#define GZVM_MAX_MEM_REGION	10
> > +
> > +/* struct mem_region_addr_range - Identical to ffa memory
> > constituent */
> > +struct mem_region_addr_range {
> > +	/* the base IPA of the constituent memory region, aligned to 4
> > kiB */
> > +	__u64 address;
> > +	/* the number of 4 kiB pages in the constituent memory region.
> > */
> > +	__u32 pg_cnt;
> > +	__u32 reserved;
> > +};
> > +
> > +struct gzvm_memory_region_ranges {
> > +	__u32 slot;
> > +	__u32 constituent_cnt;
> > +	__u64 total_pages;
> > +	__u64 gpa;
> > +	struct mem_region_addr_range constituents[];
> > +};
> > +
> > +/* struct gzvm_memslot - VM's memory slot descriptor */
> > +struct gzvm_memslot {
> > +	u64 base_gfn;			/* begin of guest page
> > frame */
> > +	unsigned long npages;		/* number of pages this
> > slot covers */
> > +	unsigned long userspace_addr;	/* corresponding userspace
> > va */
> > +	struct vm_area_struct *vma;	/* vma related to this userspace
> > addr */
> > +	u32 flags;
> > +	u32 slot_id;
> > +};
> > +
> > +struct gzvm {
> > +	/* userspace tied to this vm */
> > +	struct mm_struct *mm;
> > +	struct gzvm_memslot memslot[GZVM_MAX_MEM_REGION];
> > +	/* lock for list_add*/
> > +	struct mutex lock;
> > +	struct list_head vm_list;
> > +	u16 vm_id;
> > +};
> > +
> > +long gzvm_dev_ioctl_check_extension(struct gzvm *gzvm, unsigned
> > long args);
> > +int gzvm_dev_ioctl_create_vm(unsigned long vm_type);
> > +
> > +int gzvm_err_to_errno(unsigned long err);
> > +
> > +void gzvm_destroy_all_vms(void);
> > +
> > +/* arch-dependant functions */
> > +int gzvm_arch_probe(void);
> > +int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size,
> > +			    phys_addr_t region);
> > +int gzvm_arch_check_extension(struct gzvm *gzvm, __u64 cap, void
> > __user *argp);
> > +int gzvm_arch_create_vm(unsigned long vm_type);
> > +int gzvm_arch_destroy_vm(u16 vm_id);
> > +int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm,
> > +				  struct gzvm_enable_cap *cap,
> > +				  void __user *argp);
> > +u64 gzvm_hva_to_pa_arch(u64 hva);
> > +
> > +#endif /* __GZVM_DRV_H__ */
> > diff --git a/include/uapi/asm-generic/Kbuild b/include/uapi/asm-
> > generic/Kbuild
> > index ebb180aac74e..5af115a3c1a8 100644
> > --- a/include/uapi/asm-generic/Kbuild
> > +++ b/include/uapi/asm-generic/Kbuild
> > @@ -34,3 +34,4 @@ mandatory-y += termbits.h
> >   mandatory-y += termios.h
> >   mandatory-y += types.h
> >   mandatory-y += unistd.h
> > +mandatory-y += gzvm_arch.h
> > diff --git a/include/uapi/asm-generic/gzvm_arch.h
> > b/include/uapi/asm-generic/gzvm_arch.h
> > new file mode 100644
> > index 000000000000..c4cc12716c91
> > --- /dev/null
> > +++ b/include/uapi/asm-generic/gzvm_arch.h
> > @@ -0,0 +1,10 @@
> > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> > +/*
> > + * Copyright (c) 2023 MediaTek Inc.
> > + */
> > +
> > +#ifndef __ASM_GENERIC_GZVM_ARCH_H
> > +#define __ASM_GENERIC_GZVM_ARCH_H
> > +/* geniezone only supports aarch64 platform for now */
> > +
> > +#endif /* __ASM_GENERIC_GZVM_ARCH_H */
> > diff --git a/include/uapi/linux/gzvm.h b/include/uapi/linux/gzvm.h
> > new file mode 100644
> > index 000000000000..99730c142b0e
> > --- /dev/null
> > +++ b/include/uapi/linux/gzvm.h
> > @@ -0,0 +1,76 @@
> > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> > +/*
> > + * Copyright (c) 2023 MediaTek Inc.
> > + */
> > +
> > +/**
> > + * DOC: UAPI of GenieZone Hypervisor
> > + *
> > + * This file declares common data structure shared among user
> > space,
> > + * kernel space, and GenieZone hypervisor.
> > + */
> > +#ifndef __GZVM_H__
> > +#define __GZVM_H__
> > +
> > +#include <linux/const.h>
> > +#include <linux/types.h>
> > +#include <linux/ioctl.h>
> > +
> > +#include <asm/gzvm_arch.h>
> > +
> > +/* GZVM ioctls */
> > +#define GZVM_IOC_MAGIC			0x92	/* gz */
> > +
> > +/* ioctls for /dev/gzvm fds */
> > +#define GZVM_CREATE_VM             _IO(GZVM_IOC_MAGIC,   0x01) /*
> > Returns a Geniezone VM fd */
> > +
> > +/*
> > + * Check if the given capability is supported or not.
> > + * The argument is capability. Ex. GZVM_CAP_ARM_PROTECTED_VM or
> > GZVM_CAP_ARM_VM_IPA_SIZE
> > + * return is 0 (supported, no error)
> > + * return is -EOPNOTSUPP (unsupported)
> > + * return is -EFAULT (failed to get the argument from userspace)
> > + */
> > +#define GZVM_CHECK_EXTENSION       _IO(GZVM_IOC_MAGIC,   0x03)
> > +
> > +/* ioctls for VM fds */
> > +/* for GZVM_SET_MEMORY_REGION */
> > +struct gzvm_memory_region {
> > +	__u32 slot;
> > +	__u32 flags;
> > +	__u64 guest_phys_addr;
> > +	__u64 memory_size; /* bytes */
> > +};
> > +
> > +#define GZVM_SET_MEMORY_REGION     _IOW(GZVM_IOC_MAGIC,  0x40, \
> > +					struct gzvm_memory_region)
> > +
> > +/* for GZVM_SET_USER_MEMORY_REGION */
> > +struct gzvm_userspace_memory_region {
> > +	__u32 slot;
> > +	__u32 flags;
> > +	__u64 guest_phys_addr;
> > +	/* bytes */
> > +	__u64 memory_size;
> > +	/* start of the userspace allocated memory */
> > +	__u64 userspace_addr;
> > +};
> > +
> > +#define GZVM_SET_USER_MEMORY_REGION _IOW(GZVM_IOC_MAGIC, 0x46, \
> > +					 struct
> > gzvm_userspace_memory_region)
> > +
> > +/* for GZVM_ENABLE_CAP */
> > +struct gzvm_enable_cap {
> > +	/* in */
> > +	__u64 cap;
> > +	/**
> > +	 * we have total 5 (8 - 3) registers can be used for
> 
> which can be used ?
> 
> > +	 * additional args
> > +	 */
> > +	__u64 args[5];
> > +};
> > +
> > +#define GZVM_ENABLE_CAP            _IOW(GZVM_IOC_MAGIC,  0xa3, \
> > +					struct gzvm_enable_cap)
> > +
> > +#endif /* __GZVM_H__ */
> 
> 
> Regards,
> 
> Eugen
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index bfbfdb790446..b91d41dd2f2f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8747,6 +8747,12 @@  M:	Ze-Yu Wang <ze-yu.wang@mediatek.com>
 M:	Yi-De Wu <yi-de.wu@mediatek.com>
 F:	Documentation/devicetree/bindings/hypervisor/mediatek,geniezone-hyp.yaml
 F:	Documentation/virt/geniezone/
+F:	arch/arm64/geniezone/
+F:	arch/arm64/include/uapi/asm/gzvm_arch.h
+F:	drivers/virt/geniezone/
+F:	include/linux/gzvm_drv.h
+F	include/uapi/asm-generic/gzvm_arch.h
+F:	include/uapi/linux/gzvm.h
 
 GENWQE (IBM Generic Workqueue Card)
 M:	Frank Haverkamp <haver@linux.ibm.com>
diff --git a/arch/arm64/Kbuild b/arch/arm64/Kbuild
index 5bfbf7d79c99..0c3cca572919 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_MTK_GZVM)	+= geniezone/
 
 # for cleaning
 subdir- += boot
diff --git a/arch/arm64/geniezone/Makefile b/arch/arm64/geniezone/Makefile
new file mode 100644
index 000000000000..2957898cdd05
--- /dev/null
+++ b/arch/arm64/geniezone/Makefile
@@ -0,0 +1,9 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Main Makefile for gzvm, this one includes drivers/virt/geniezone/Makefile
+#
+include $(srctree)/drivers/virt/geniezone/Makefile
+
+gzvm-y += vm.o
+
+obj-$(CONFIG_MTK_GZVM) += gzvm.o
diff --git a/arch/arm64/geniezone/gzvm_arch_common.h b/arch/arm64/geniezone/gzvm_arch_common.h
new file mode 100644
index 000000000000..fdb95d619102
--- /dev/null
+++ b/arch/arm64/geniezone/gzvm_arch_common.h
@@ -0,0 +1,68 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ */
+
+#ifndef __GZVM_ARCH_COMMON_H__
+#define __GZVM_ARCH_COMMON_H__
+
+#include <linux/arm-smccc.h>
+
+enum {
+	GZVM_FUNC_CREATE_VM = 0,
+	GZVM_FUNC_DESTROY_VM = 1,
+	GZVM_FUNC_CREATE_VCPU = 2,
+	GZVM_FUNC_DESTROY_VCPU = 3,
+	GZVM_FUNC_SET_MEMREGION = 4,
+	GZVM_FUNC_RUN = 5,
+	GZVM_FUNC_GET_ONE_REG = 8,
+	GZVM_FUNC_SET_ONE_REG = 9,
+	GZVM_FUNC_IRQ_LINE = 10,
+	GZVM_FUNC_CREATE_DEVICE = 11,
+	GZVM_FUNC_PROBE = 12,
+	GZVM_FUNC_ENABLE_CAP = 13,
+	NR_GZVM_FUNC,
+};
+
+#define SMC_ENTITY_MTK			59
+#define GZVM_FUNCID_START		(0x1000)
+#define GZVM_HCALL_ID(func)						\
+	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,	\
+			   SMC_ENTITY_MTK, (GZVM_FUNCID_START + (func)))
+
+#define MT_HVC_GZVM_CREATE_VM		GZVM_HCALL_ID(GZVM_FUNC_CREATE_VM)
+#define MT_HVC_GZVM_DESTROY_VM		GZVM_HCALL_ID(GZVM_FUNC_DESTROY_VM)
+#define MT_HVC_GZVM_CREATE_VCPU		GZVM_HCALL_ID(GZVM_FUNC_CREATE_VCPU)
+#define MT_HVC_GZVM_DESTROY_VCPU	GZVM_HCALL_ID(GZVM_FUNC_DESTROY_VCPU)
+#define MT_HVC_GZVM_SET_MEMREGION	GZVM_HCALL_ID(GZVM_FUNC_SET_MEMREGION)
+#define MT_HVC_GZVM_RUN			GZVM_HCALL_ID(GZVM_FUNC_RUN)
+#define MT_HVC_GZVM_GET_ONE_REG		GZVM_HCALL_ID(GZVM_FUNC_GET_ONE_REG)
+#define MT_HVC_GZVM_SET_ONE_REG		GZVM_HCALL_ID(GZVM_FUNC_SET_ONE_REG)
+#define MT_HVC_GZVM_IRQ_LINE		GZVM_HCALL_ID(GZVM_FUNC_IRQ_LINE)
+#define MT_HVC_GZVM_CREATE_DEVICE	GZVM_HCALL_ID(GZVM_FUNC_CREATE_DEVICE)
+#define MT_HVC_GZVM_PROBE		GZVM_HCALL_ID(GZVM_FUNC_PROBE)
+#define MT_HVC_GZVM_ENABLE_CAP		GZVM_HCALL_ID(GZVM_FUNC_ENABLE_CAP)
+
+/**
+ * gzvm_hypcall_wrapper() - the wrapper for hvc calls
+ * @a0-a7: arguments passed in registers 0 to 7
+ * @res: result values from registers 0 to 3
+ *
+ * Return: The wrapper helps caller to convert geniezone errno to Linux errno.
+ */
+static inline int gzvm_hypcall_wrapper(unsigned long a0, unsigned long a1,
+				       unsigned long a2, unsigned long a3,
+				       unsigned long a4, unsigned long a5,
+				       unsigned long a6, unsigned long a7,
+				       struct arm_smccc_res *res)
+{
+	arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res);
+	return gzvm_err_to_errno(res->a0);
+}
+
+static inline u16 get_vmid_from_tuple(unsigned int tuple)
+{
+	return (u16)(tuple >> 16);
+}
+
+#endif /* __GZVM_ARCH_COMMON_H__ */
diff --git a/arch/arm64/geniezone/vm.c b/arch/arm64/geniezone/vm.c
new file mode 100644
index 000000000000..e35751b21821
--- /dev/null
+++ b/arch/arm64/geniezone/vm.c
@@ -0,0 +1,212 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ */
+
+#include <asm/sysreg.h>
+#include <linux/arm-smccc.h>
+#include <linux/err.h>
+#include <linux/uaccess.h>
+
+#include <linux/gzvm.h>
+#include <linux/gzvm_drv.h>
+#include "gzvm_arch_common.h"
+
+#define PAR_PA47_MASK ((((1UL << 48) - 1) >> 12) << 12)
+
+int gzvm_arch_probe(void)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_hvc(MT_HVC_GZVM_PROBE, 0, 0, 0, 0, 0, 0, 0, &res);
+	if (res.a0 == 0)
+		return 0;
+
+	return -ENXIO;
+}
+
+int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size,
+			    phys_addr_t region)
+{
+	struct arm_smccc_res res;
+
+	return gzvm_hypcall_wrapper(MT_HVC_GZVM_SET_MEMREGION, vm_id,
+				    buf_size, region, 0, 0, 0, 0, &res);
+}
+
+static int gzvm_cap_arm_vm_ipa_size(void __user *argp)
+{
+	__u64 value = CONFIG_ARM64_PA_BITS;
+
+	if (copy_to_user(argp, &value, sizeof(__u64)))
+		return -EFAULT;
+
+	return 0;
+}
+
+int gzvm_arch_check_extension(struct gzvm *gzvm, __u64 cap, void __user *argp)
+{
+	int ret = -EOPNOTSUPP;
+
+	switch (cap) {
+	case GZVM_CAP_ARM_PROTECTED_VM: {
+		__u64 success = 1;
+
+		if (copy_to_user(argp, &success, sizeof(__u64)))
+			return -EFAULT;
+		ret = 0;
+		break;
+	}
+	case GZVM_CAP_ARM_VM_IPA_SIZE: {
+		ret = gzvm_cap_arm_vm_ipa_size(argp);
+		break;
+	}
+	default:
+		ret = -EOPNOTSUPP;
+	}
+
+	return ret;
+}
+
+/**
+ * gzvm_arch_create_vm() - create vm
+ * @vm_type: VM type. Only supports Linux VM now.
+ *
+ * Return:
+ * * positive value	- VM ID
+ * * -ENOMEM		- Memory not enough for storing VM data
+ */
+int gzvm_arch_create_vm(unsigned long vm_type)
+{
+	struct arm_smccc_res res;
+	int ret;
+
+	ret = gzvm_hypcall_wrapper(MT_HVC_GZVM_CREATE_VM, vm_type, 0, 0, 0, 0,
+				   0, 0, &res);
+
+	if (ret == 0)
+		return res.a1;
+	else
+		return ret;
+}
+
+int gzvm_arch_destroy_vm(u16 vm_id)
+{
+	struct arm_smccc_res res;
+
+	return gzvm_hypcall_wrapper(MT_HVC_GZVM_DESTROY_VM, vm_id, 0, 0, 0, 0,
+				    0, 0, &res);
+}
+
+static int gzvm_vm_arch_enable_cap(struct gzvm *gzvm,
+				   struct gzvm_enable_cap *cap,
+				   struct arm_smccc_res *res)
+{
+	return gzvm_hypcall_wrapper(MT_HVC_GZVM_ENABLE_CAP, gzvm->vm_id,
+				    cap->cap, cap->args[0], cap->args[1],
+				    cap->args[2], cap->args[3], cap->args[4],
+				    res);
+}
+
+/**
+ * gzvm_vm_ioctl_get_pvmfw_size() - Get pvmfw size from hypervisor, return
+ *				    in x1, and return to userspace in args
+ * @gzvm: Pointer to struct gzvm.
+ * @cap: Pointer to struct gzvm_enable_cap.
+ * @argp: Pointer to struct gzvm_enable_cap in user space.
+ *
+ * Return:
+ * * 0			- Succeed
+ * * -EINVAL		- Hypervisor return invalid results
+ * * -EFAULT		- Fail to copy back to userspace buffer
+ */
+static int gzvm_vm_ioctl_get_pvmfw_size(struct gzvm *gzvm,
+					struct gzvm_enable_cap *cap,
+					void __user *argp)
+{
+	struct arm_smccc_res res = {0};
+
+	if (gzvm_vm_arch_enable_cap(gzvm, cap, &res) != 0)
+		return -EINVAL;
+
+	cap->args[1] = res.a1;
+	if (copy_to_user(argp, cap, sizeof(*cap)))
+		return -EFAULT;
+
+	return 0;
+}
+
+/**
+ * gzvm_vm_ioctl_cap_pvm() - Proceed GZVM_CAP_ARM_PROTECTED_VM's subcommands
+ * @gzvm: Pointer to struct gzvm.
+ * @cap: Pointer to struct gzvm_enable_cap.
+ * @argp: Pointer to struct gzvm_enable_cap in user space.
+ *
+ * Return:
+ * * 0			- Succeed
+ * * -EINVAL		- Invalid subcommand or arguments
+ */
+static int gzvm_vm_ioctl_cap_pvm(struct gzvm *gzvm,
+				 struct gzvm_enable_cap *cap,
+				 void __user *argp)
+{
+	int ret = -EINVAL;
+	struct arm_smccc_res res = {0};
+
+	switch (cap->args[0]) {
+	case GZVM_CAP_ARM_PVM_SET_PVMFW_IPA:
+		fallthrough;
+	case GZVM_CAP_ARM_PVM_SET_PROTECTED_VM:
+		ret = gzvm_vm_arch_enable_cap(gzvm, cap, &res);
+		break;
+	case GZVM_CAP_ARM_PVM_GET_PVMFW_SIZE:
+		ret = gzvm_vm_ioctl_get_pvmfw_size(gzvm, cap, argp);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm,
+				  struct gzvm_enable_cap *cap,
+				  void __user *argp)
+{
+	int ret = -EINVAL;
+
+	switch (cap->cap) {
+	case GZVM_CAP_ARM_PROTECTED_VM:
+		ret = gzvm_vm_ioctl_cap_pvm(gzvm, cap, argp);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+/**
+ * gzvm_hva_to_pa_arch() - converts hva to pa with arch-specific way
+ * @hva: Host virtual address.
+ *
+ * Return: 0 if translation error
+ */
+u64 gzvm_hva_to_pa_arch(u64 hva)
+{
+	u64 par;
+	unsigned long flags;
+
+	local_irq_save(flags);
+	asm volatile("at s1e1r, %0" :: "r" (hva));
+	isb();
+	par = read_sysreg_par();
+	local_irq_restore(flags);
+
+	if (par & SYS_PAR_EL1_F)
+		return 0;
+
+	return par & PAR_PA47_MASK;
+}
diff --git a/arch/arm64/include/uapi/asm/gzvm_arch.h b/arch/arm64/include/uapi/asm/gzvm_arch.h
new file mode 100644
index 000000000000..847bb627a65d
--- /dev/null
+++ b/arch/arm64/include/uapi/asm/gzvm_arch.h
@@ -0,0 +1,20 @@ 
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ */
+
+#ifndef __GZVM_ARCH_H__
+#define __GZVM_ARCH_H__
+
+#include <linux/types.h>
+
+#define GZVM_CAP_ARM_VM_IPA_SIZE	165
+#define GZVM_CAP_ARM_PROTECTED_VM	0xffbadab1
+
+/* sub-commands put in args[0] for GZVM_CAP_ARM_PROTECTED_VM */
+#define GZVM_CAP_ARM_PVM_SET_PVMFW_IPA		0
+#define GZVM_CAP_ARM_PVM_GET_PVMFW_SIZE		1
+/* GZVM_CAP_ARM_PVM_SET_PROTECTED_VM only sets protected but not load pvmfw */
+#define GZVM_CAP_ARM_PVM_SET_PROTECTED_VM	2
+
+#endif /* __GZVM_ARCH_H__ */
diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
index f79ab13a5c28..9bbf0bdf672c 100644
--- a/drivers/virt/Kconfig
+++ b/drivers/virt/Kconfig
@@ -54,4 +54,6 @@  source "drivers/virt/coco/sev-guest/Kconfig"
 
 source "drivers/virt/coco/tdx-guest/Kconfig"
 
+source "drivers/virt/geniezone/Kconfig"
+
 endif
diff --git a/drivers/virt/geniezone/Kconfig b/drivers/virt/geniezone/Kconfig
new file mode 100644
index 000000000000..2643fb8913cc
--- /dev/null
+++ b/drivers/virt/geniezone/Kconfig
@@ -0,0 +1,16 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+
+config MTK_GZVM
+	tristate "GenieZone Hypervisor driver for guest VM operation"
+	depends on ARM64
+	help
+	  This driver, gzvm, enables to run guest VMs on MTK GenieZone
+	  hypervisor. It exports kvm-like interfaces for VMM (e.g., crosvm) in
+	  order to operate guest VMs on GenieZone hypervisor.
+
+	  GenieZone hypervisor now only supports MediaTek SoC and arm64
+	  architecture.
+
+	  Select M if you want it be built as a module (gzvm.ko).
+
+	  If unsure, say N.
diff --git a/drivers/virt/geniezone/Makefile b/drivers/virt/geniezone/Makefile
new file mode 100644
index 000000000000..066efddc0b9c
--- /dev/null
+++ b/drivers/virt/geniezone/Makefile
@@ -0,0 +1,10 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for GenieZone driver, this file should be include in arch's
+# to avoid two ko being generated.
+#
+
+GZVM_DIR ?= ../../../drivers/virt/geniezone
+
+gzvm-y := $(GZVM_DIR)/gzvm_main.o $(GZVM_DIR)/gzvm_vm.o
+
diff --git a/drivers/virt/geniezone/gzvm_main.c b/drivers/virt/geniezone/gzvm_main.c
new file mode 100644
index 000000000000..b629b41a0cd9
--- /dev/null
+++ b/drivers/virt/geniezone/gzvm_main.c
@@ -0,0 +1,143 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ */
+
+#include <linux/anon_inodes.h>
+#include <linux/device.h>
+#include <linux/file.h>
+#include <linux/kdev_t.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gzvm_drv.h>
+
+/**
+ * gzvm_err_to_errno() - Convert geniezone return value to standard errno
+ *
+ * @err: Return value from geniezone function return
+ *
+ * Return: Standard errno
+ */
+int gzvm_err_to_errno(unsigned long err)
+{
+	int gz_err = (int)err;
+
+	switch (gz_err) {
+	case 0:
+		return 0;
+	case ERR_NO_MEMORY:
+		return -ENOMEM;
+	case ERR_NOT_SUPPORTED:
+		return -EOPNOTSUPP;
+	case ERR_NOT_IMPLEMENTED:
+		return -EOPNOTSUPP;
+	case ERR_FAULT:
+		return -EFAULT;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * gzvm_dev_ioctl_check_extension() - Check if given capability is support
+ *				      or not
+ *
+ * @gzvm: Pointer to struct gzvm
+ * @args: Pointer in u64 from userspace
+ *
+ * Return:
+ * * 0			- Support, no error
+ * * -EOPNOTSUPP	- Not support
+ * * -EFAULT		- Failed to get data from userspace
+ */
+long gzvm_dev_ioctl_check_extension(struct gzvm *gzvm, unsigned long args)
+{
+	__u64 cap;
+	void __user *argp = (void __user *)args;
+
+	if (copy_from_user(&cap, argp, sizeof(uint64_t)))
+		return -EFAULT;
+	return gzvm_arch_check_extension(gzvm, cap, argp);
+}
+
+static long gzvm_dev_ioctl(struct file *filp, unsigned int cmd,
+			   unsigned long user_args)
+{
+	long ret = -ENOTTY;
+
+	switch (cmd) {
+	case GZVM_CREATE_VM:
+		ret = gzvm_dev_ioctl_create_vm(user_args);
+		break;
+	case GZVM_CHECK_EXTENSION:
+		if (!user_args)
+			return -EINVAL;
+		ret = gzvm_dev_ioctl_check_extension(NULL, user_args);
+		break;
+	default:
+		ret = -ENOTTY;
+	}
+
+	return ret;
+}
+
+static const struct file_operations gzvm_chardev_ops = {
+	.unlocked_ioctl = gzvm_dev_ioctl,
+	.llseek		= noop_llseek,
+};
+
+static struct miscdevice gzvm_dev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = KBUILD_MODNAME,
+	.fops = &gzvm_chardev_ops,
+};
+
+static int gzvm_drv_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	if (gzvm_arch_probe() != 0) {
+		dev_err(&pdev->dev, "Not found available conduit\n");
+		return -ENODEV;
+	}
+
+	ret = misc_register(&gzvm_dev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int gzvm_drv_remove(struct platform_device *pdev)
+{
+	gzvm_destroy_all_vms();
+	misc_deregister(&gzvm_dev);
+	return 0;
+}
+
+static const struct of_device_id gzvm_of_match[] = {
+	{ .compatible = "mediatek,geniezone-hyp", },
+	{/* sentinel */},
+};
+
+static struct platform_driver gzvm_driver = {
+	.probe = gzvm_drv_probe,
+	.remove = gzvm_drv_remove,
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.owner = THIS_MODULE,
+		.of_match_table = gzvm_of_match,
+	},
+};
+
+module_platform_driver(gzvm_driver);
+
+MODULE_DEVICE_TABLE(of, gzvm_of_match);
+MODULE_AUTHOR("MediaTek");
+MODULE_DESCRIPTION("GenieZone interface for VMM");
+MODULE_LICENSE("GPL");
diff --git a/drivers/virt/geniezone/gzvm_vm.c b/drivers/virt/geniezone/gzvm_vm.c
new file mode 100644
index 000000000000..ee751369fd4b
--- /dev/null
+++ b/drivers/virt/geniezone/gzvm_vm.c
@@ -0,0 +1,400 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ */
+
+#include <linux/anon_inodes.h>
+#include <linux/file.h>
+#include <linux/kdev_t.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gzvm_drv.h>
+
+static DEFINE_MUTEX(gzvm_list_lock);
+static LIST_HEAD(gzvm_list);
+
+/**
+ * hva_to_pa_fast() - converts hva to pa in generic fast way
+ * @hva: Host virtual address.
+ *
+ * Return: 0 if translation error
+ */
+static u64 hva_to_pa_fast(u64 hva)
+{
+	struct page *page[1];
+
+	u64 pfn;
+
+	if (get_user_page_fast_only(hva, 0, page)) {
+		pfn = page_to_phys(page[0]);
+		put_page((struct page *)page);
+		return pfn;
+	} else {
+		return 0;
+	}
+}
+
+/**
+ * hva_to_pa_slow() - note that this function may sleep
+ * @hva: Host virtual address.
+ *
+ * Return: 0 if translation error
+ */
+static u64 hva_to_pa_slow(u64 hva)
+{
+	struct page *page;
+	int npages;
+	u64 pfn;
+
+	npages = get_user_pages_unlocked(hva, 1, &page, 0);
+	if (npages != 1)
+		return 0;
+
+	pfn = page_to_phys(page);
+	put_page(page);
+
+	return pfn;
+}
+
+static u64 gzvm_gfn_to_hva_memslot(struct gzvm_memslot *memslot, u64 gfn)
+{
+	u64 offset = gfn - memslot->base_gfn;
+
+	return memslot->userspace_addr + offset * PAGE_SIZE;
+}
+
+static u64 __gzvm_gfn_to_pfn_memslot(struct gzvm_memslot *memslot, u64 gfn)
+{
+	u64 hva, pa;
+
+	hva = gzvm_gfn_to_hva_memslot(memslot, gfn);
+
+	pa = gzvm_hva_to_pa_arch(hva);
+	if (pa != 0)
+		return PHYS_PFN(pa);
+
+	pa = hva_to_pa_fast(hva);
+	if (pa)
+		return PHYS_PFN(pa);
+
+	pa = hva_to_pa_slow(hva);
+	if (pa)
+		return PHYS_PFN(pa);
+
+	return 0;
+}
+
+/**
+ * gzvm_gfn_to_pfn_memslot() - Translate gfn (guest ipa) to pfn (host pa),
+ *			       result is in @pfn
+ * @memslot: Pointer to struct gzvm_memslot.
+ * @gfn: Guest frame number.
+ * @pfn: Host page frame number.
+ *
+ * Return:
+ * * 0			- Succeed
+ * * -EFAULT		- Failed to convert
+ */
+static int gzvm_gfn_to_pfn_memslot(struct gzvm_memslot *memslot, u64 gfn,
+				   u64 *pfn)
+{
+	u64 __pfn;
+
+	if (!memslot)
+		return -EFAULT;
+
+	__pfn = __gzvm_gfn_to_pfn_memslot(memslot, gfn);
+	if (__pfn == 0) {
+		*pfn = 0;
+		return -EFAULT;
+	}
+
+	*pfn = __pfn;
+
+	return 0;
+}
+
+/**
+ * fill_constituents() - Populate pa to buffer until full
+ * @consti: Pointer to struct mem_region_addr_range.
+ * @consti_cnt: Constituent count.
+ * @max_nr_consti: Maximum number of constituent count.
+ * @gfn: Guest frame number.
+ * @total_pages: Total page numbers.
+ * @slot: Pointer to struct gzvm_memslot.
+ *
+ * Return: how many pages we've fill in, negative if error
+ */
+static int fill_constituents(struct mem_region_addr_range *consti,
+			     int *consti_cnt, int max_nr_consti, u64 gfn,
+			     u32 total_pages, struct gzvm_memslot *slot)
+{
+	u64 pfn, prev_pfn, gfn_end;
+	int nr_pages = 1;
+	int i = 0;
+
+	if (unlikely(total_pages == 0))
+		return -EINVAL;
+	gfn_end = gfn + total_pages;
+
+	/* entry 0 */
+	if (gzvm_gfn_to_pfn_memslot(slot, gfn, &pfn) != 0)
+		return -EFAULT;
+	consti[0].address = PFN_PHYS(pfn);
+	consti[0].pg_cnt = 1;
+	gfn++;
+	prev_pfn = pfn;
+
+	while (i < max_nr_consti && gfn < gfn_end) {
+		if (gzvm_gfn_to_pfn_memslot(slot, gfn, &pfn) != 0)
+			return -EFAULT;
+		if (pfn == (prev_pfn + 1)) {
+			consti[i].pg_cnt++;
+		} else {
+			i++;
+			if (i >= max_nr_consti)
+				break;
+			consti[i].address = PFN_PHYS(pfn);
+			consti[i].pg_cnt = 1;
+		}
+		prev_pfn = pfn;
+		gfn++;
+		nr_pages++;
+	}
+	if (i != max_nr_consti)
+		i++;
+	*consti_cnt = i;
+
+	return nr_pages;
+}
+
+/* register_memslot_addr_range() - Register memory region to GZ */
+static int
+register_memslot_addr_range(struct gzvm *gzvm, struct gzvm_memslot *memslot)
+{
+	struct gzvm_memory_region_ranges *region;
+	u32 buf_size;
+	int max_nr_consti, remain_pages;
+	u64 gfn, gfn_end;
+
+	buf_size = PAGE_SIZE * 2;
+	region = alloc_pages_exact(buf_size, GFP_KERNEL);
+	if (!region)
+		return -ENOMEM;
+	max_nr_consti = (buf_size - sizeof(*region)) /
+			sizeof(struct mem_region_addr_range);
+
+	region->slot = memslot->slot_id;
+	remain_pages = memslot->npages;
+	gfn = memslot->base_gfn;
+	gfn_end = gfn + remain_pages;
+	while (gfn < gfn_end) {
+		int nr_pages;
+
+		nr_pages = fill_constituents(region->constituents,
+					     &region->constituent_cnt,
+					     max_nr_consti, gfn,
+					     remain_pages, memslot);
+		if (nr_pages < 0) {
+			pr_err("Failed to fill constituents\n");
+			free_pages_exact(region, buf_size);
+			return nr_pages;
+		}
+		region->gpa = PFN_PHYS(gfn);
+		region->total_pages = nr_pages;
+
+		remain_pages -= nr_pages;
+		gfn += nr_pages;
+
+		if (gzvm_arch_set_memregion(gzvm->vm_id, buf_size,
+					    virt_to_phys(region))) {
+			pr_err("Failed to register memregion to hypervisor\n");
+			free_pages_exact(region, buf_size);
+			return -EFAULT;
+		}
+	}
+	free_pages_exact(region, buf_size);
+	return 0;
+}
+
+/**
+ * gzvm_vm_ioctl_set_memory_region() - Set memory region of guest
+ * @gzvm: Pointer to struct gzvm.
+ * @mem: Input memory region from user.
+ *
+ * Return:
+ * * -EXIO		- memslot is out-of-range
+ * * -EFAULT		- Cannot find corresponding vma
+ * * -EINVAL		- region size and vma size does not match
+ */
+static int
+gzvm_vm_ioctl_set_memory_region(struct gzvm *gzvm,
+				struct gzvm_userspace_memory_region *mem)
+{
+	struct vm_area_struct *vma;
+	struct gzvm_memslot *memslot;
+	unsigned long size;
+	__u32 slot;
+
+	slot = mem->slot;
+	if (slot >= GZVM_MAX_MEM_REGION)
+		return -ENXIO;
+	memslot = &gzvm->memslot[slot];
+
+	vma = vma_lookup(gzvm->mm, mem->userspace_addr);
+	if (!vma)
+		return -EFAULT;
+
+	size = vma->vm_end - vma->vm_start;
+	if (size != mem->memory_size)
+		return -EINVAL;
+
+	memslot->base_gfn = __phys_to_pfn(mem->guest_phys_addr);
+	memslot->npages = size >> PAGE_SHIFT;
+	memslot->userspace_addr = mem->userspace_addr;
+	memslot->vma = vma;
+	memslot->flags = mem->flags;
+	memslot->slot_id = mem->slot;
+	return register_memslot_addr_range(gzvm, memslot);
+}
+
+static int gzvm_vm_ioctl_enable_cap(struct gzvm *gzvm,
+				    struct gzvm_enable_cap *cap,
+				    void __user *argp)
+{
+	return gzvm_vm_ioctl_arch_enable_cap(gzvm, cap, argp);
+}
+
+/* gzvm_vm_ioctl() - Ioctl handler of VM FD */
+static long gzvm_vm_ioctl(struct file *filp, unsigned int ioctl,
+			  unsigned long arg)
+{
+	long ret = -ENOTTY;
+	void __user *argp = (void __user *)arg;
+	struct gzvm *gzvm = filp->private_data;
+
+	switch (ioctl) {
+	case GZVM_CHECK_EXTENSION: {
+		ret = gzvm_dev_ioctl_check_extension(gzvm, arg);
+		break;
+	}
+	case GZVM_SET_USER_MEMORY_REGION: {
+		struct gzvm_userspace_memory_region userspace_mem;
+
+		if (copy_from_user(&userspace_mem, argp, sizeof(userspace_mem))) {
+			ret = -EFAULT;
+			goto out;
+		}
+		ret = gzvm_vm_ioctl_set_memory_region(gzvm, &userspace_mem);
+		break;
+	}
+	case GZVM_ENABLE_CAP: {
+		struct gzvm_enable_cap cap;
+
+		if (copy_from_user(&cap, argp, sizeof(cap))) {
+			ret = -EFAULT;
+			goto out;
+		}
+		ret = gzvm_vm_ioctl_enable_cap(gzvm, &cap, argp);
+		break;
+	}
+	default:
+		ret = -ENOTTY;
+	}
+out:
+	return ret;
+}
+
+static void gzvm_destroy_vm(struct gzvm *gzvm)
+{
+	pr_debug("VM-%u is going to be destroyed\n", gzvm->vm_id);
+
+	mutex_lock(&gzvm->lock);
+
+	gzvm_arch_destroy_vm(gzvm->vm_id);
+
+	mutex_lock(&gzvm_list_lock);
+	list_del(&gzvm->vm_list);
+	mutex_unlock(&gzvm_list_lock);
+
+	mutex_unlock(&gzvm->lock);
+
+	kfree(gzvm);
+}
+
+static int gzvm_vm_release(struct inode *inode, struct file *filp)
+{
+	struct gzvm *gzvm = filp->private_data;
+
+	gzvm_destroy_vm(gzvm);
+	return 0;
+}
+
+static const struct file_operations gzvm_vm_fops = {
+	.release        = gzvm_vm_release,
+	.unlocked_ioctl = gzvm_vm_ioctl,
+	.llseek		= noop_llseek,
+};
+
+static struct gzvm *gzvm_create_vm(unsigned long vm_type)
+{
+	int ret;
+	struct gzvm *gzvm;
+
+	gzvm = kzalloc(sizeof(*gzvm), GFP_KERNEL);
+	if (!gzvm)
+		return ERR_PTR(-ENOMEM);
+
+	ret = gzvm_arch_create_vm(vm_type);
+	if (ret < 0) {
+		kfree(gzvm);
+		return ERR_PTR(ret);
+	}
+
+	gzvm->vm_id = ret;
+	gzvm->mm = current->mm;
+	mutex_init(&gzvm->lock);
+
+	mutex_lock(&gzvm_list_lock);
+	list_add(&gzvm->vm_list, &gzvm_list);
+	mutex_unlock(&gzvm_list_lock);
+
+	pr_debug("VM-%u is created\n", gzvm->vm_id);
+
+	return gzvm;
+}
+
+/**
+ * gzvm_dev_ioctl_create_vm - Create vm fd
+ * @vm_type: VM type. Only supports Linux VM now.
+ *
+ * Return: fd of vm, negative if error
+ */
+int gzvm_dev_ioctl_create_vm(unsigned long vm_type)
+{
+	struct gzvm *gzvm;
+
+	gzvm = gzvm_create_vm(vm_type);
+	if (IS_ERR(gzvm))
+		return PTR_ERR(gzvm);
+
+	return anon_inode_getfd("gzvm-vm", &gzvm_vm_fops, gzvm,
+			       O_RDWR | O_CLOEXEC);
+}
+
+void gzvm_destroy_all_vms(void)
+{
+	struct gzvm *gzvm, *tmp;
+
+	mutex_lock(&gzvm_list_lock);
+	if (list_empty(&gzvm_list))
+		goto out;
+
+	list_for_each_entry_safe(gzvm, tmp, &gzvm_list, vm_list)
+		gzvm_destroy_vm(gzvm);
+
+out:
+	mutex_unlock(&gzvm_list_lock);
+}
diff --git a/include/linux/gzvm_drv.h b/include/linux/gzvm_drv.h
new file mode 100644
index 000000000000..4fd52fcbd5a8
--- /dev/null
+++ b/include/linux/gzvm_drv.h
@@ -0,0 +1,90 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ */
+
+#ifndef __GZVM_DRV_H__
+#define __GZVM_DRV_H__
+
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/gzvm.h>
+
+#define GZVM_VCPU_MMAP_SIZE  PAGE_SIZE
+#define INVALID_VM_ID   0xffff
+
+/*
+ * These are the efinitions of APIs between GenieZone hypervisor and driver,
+ * there's no need to be visible to uapi. Furthermore, We need GenieZone
+ * specific error code in order to map to Linux errno
+ */
+#define NO_ERROR                (0)
+#define ERR_NO_MEMORY           (-5)
+#define ERR_NOT_SUPPORTED       (-24)
+#define ERR_NOT_IMPLEMENTED     (-27)
+#define ERR_FAULT               (-40)
+
+/*
+ * The following data structures are for data transferring between driver and
+ * hypervisor, and they're aligned with hypervisor definitions
+ */
+#define GZVM_MAX_VCPUS		 8
+#define GZVM_MAX_MEM_REGION	10
+
+/* struct mem_region_addr_range - Identical to ffa memory constituent */
+struct mem_region_addr_range {
+	/* the base IPA of the constituent memory region, aligned to 4 kiB */
+	__u64 address;
+	/* the number of 4 kiB pages in the constituent memory region. */
+	__u32 pg_cnt;
+	__u32 reserved;
+};
+
+struct gzvm_memory_region_ranges {
+	__u32 slot;
+	__u32 constituent_cnt;
+	__u64 total_pages;
+	__u64 gpa;
+	struct mem_region_addr_range constituents[];
+};
+
+/* struct gzvm_memslot - VM's memory slot descriptor */
+struct gzvm_memslot {
+	u64 base_gfn;			/* begin of guest page frame */
+	unsigned long npages;		/* number of pages this slot covers */
+	unsigned long userspace_addr;	/* corresponding userspace va */
+	struct vm_area_struct *vma;	/* vma related to this userspace addr */
+	u32 flags;
+	u32 slot_id;
+};
+
+struct gzvm {
+	/* userspace tied to this vm */
+	struct mm_struct *mm;
+	struct gzvm_memslot memslot[GZVM_MAX_MEM_REGION];
+	/* lock for list_add*/
+	struct mutex lock;
+	struct list_head vm_list;
+	u16 vm_id;
+};
+
+long gzvm_dev_ioctl_check_extension(struct gzvm *gzvm, unsigned long args);
+int gzvm_dev_ioctl_create_vm(unsigned long vm_type);
+
+int gzvm_err_to_errno(unsigned long err);
+
+void gzvm_destroy_all_vms(void);
+
+/* arch-dependant functions */
+int gzvm_arch_probe(void);
+int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size,
+			    phys_addr_t region);
+int gzvm_arch_check_extension(struct gzvm *gzvm, __u64 cap, void __user *argp);
+int gzvm_arch_create_vm(unsigned long vm_type);
+int gzvm_arch_destroy_vm(u16 vm_id);
+int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm,
+				  struct gzvm_enable_cap *cap,
+				  void __user *argp);
+u64 gzvm_hva_to_pa_arch(u64 hva);
+
+#endif /* __GZVM_DRV_H__ */
diff --git a/include/uapi/asm-generic/Kbuild b/include/uapi/asm-generic/Kbuild
index ebb180aac74e..5af115a3c1a8 100644
--- a/include/uapi/asm-generic/Kbuild
+++ b/include/uapi/asm-generic/Kbuild
@@ -34,3 +34,4 @@  mandatory-y += termbits.h
 mandatory-y += termios.h
 mandatory-y += types.h
 mandatory-y += unistd.h
+mandatory-y += gzvm_arch.h
diff --git a/include/uapi/asm-generic/gzvm_arch.h b/include/uapi/asm-generic/gzvm_arch.h
new file mode 100644
index 000000000000..c4cc12716c91
--- /dev/null
+++ b/include/uapi/asm-generic/gzvm_arch.h
@@ -0,0 +1,10 @@ 
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ */
+
+#ifndef __ASM_GENERIC_GZVM_ARCH_H
+#define __ASM_GENERIC_GZVM_ARCH_H
+/* geniezone only supports aarch64 platform for now */
+
+#endif /* __ASM_GENERIC_GZVM_ARCH_H */
diff --git a/include/uapi/linux/gzvm.h b/include/uapi/linux/gzvm.h
new file mode 100644
index 000000000000..99730c142b0e
--- /dev/null
+++ b/include/uapi/linux/gzvm.h
@@ -0,0 +1,76 @@ 
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ */
+
+/**
+ * DOC: UAPI of GenieZone Hypervisor
+ *
+ * This file declares common data structure shared among user space,
+ * kernel space, and GenieZone hypervisor.
+ */
+#ifndef __GZVM_H__
+#define __GZVM_H__
+
+#include <linux/const.h>
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#include <asm/gzvm_arch.h>
+
+/* GZVM ioctls */
+#define GZVM_IOC_MAGIC			0x92	/* gz */
+
+/* ioctls for /dev/gzvm fds */
+#define GZVM_CREATE_VM             _IO(GZVM_IOC_MAGIC,   0x01) /* Returns a Geniezone VM fd */
+
+/*
+ * Check if the given capability is supported or not.
+ * The argument is capability. Ex. GZVM_CAP_ARM_PROTECTED_VM or GZVM_CAP_ARM_VM_IPA_SIZE
+ * return is 0 (supported, no error)
+ * return is -EOPNOTSUPP (unsupported)
+ * return is -EFAULT (failed to get the argument from userspace)
+ */
+#define GZVM_CHECK_EXTENSION       _IO(GZVM_IOC_MAGIC,   0x03)
+
+/* ioctls for VM fds */
+/* for GZVM_SET_MEMORY_REGION */
+struct gzvm_memory_region {
+	__u32 slot;
+	__u32 flags;
+	__u64 guest_phys_addr;
+	__u64 memory_size; /* bytes */
+};
+
+#define GZVM_SET_MEMORY_REGION     _IOW(GZVM_IOC_MAGIC,  0x40, \
+					struct gzvm_memory_region)
+
+/* for GZVM_SET_USER_MEMORY_REGION */
+struct gzvm_userspace_memory_region {
+	__u32 slot;
+	__u32 flags;
+	__u64 guest_phys_addr;
+	/* bytes */
+	__u64 memory_size;
+	/* start of the userspace allocated memory */
+	__u64 userspace_addr;
+};
+
+#define GZVM_SET_USER_MEMORY_REGION _IOW(GZVM_IOC_MAGIC, 0x46, \
+					 struct gzvm_userspace_memory_region)
+
+/* for GZVM_ENABLE_CAP */
+struct gzvm_enable_cap {
+	/* in */
+	__u64 cap;
+	/**
+	 * we have total 5 (8 - 3) registers can be used for
+	 * additional args
+	 */
+	__u64 args[5];
+};
+
+#define GZVM_ENABLE_CAP            _IOW(GZVM_IOC_MAGIC,  0xa3, \
+					struct gzvm_enable_cap)
+
+#endif /* __GZVM_H__ */