diff mbox series

[02/15] arm64: kvm: Formalize host-hyp hypcall ABI

Message ID 20200430144831.59194-3-dbrazdil@google.com (mailing list archive)
State New, archived
Headers show
Series Split off nVHE hyp code | expand

Commit Message

David Brazdil April 30, 2020, 2:48 p.m. UTC
From: Quentin Perret <qperret@google.com>

In preparation for removing the hyp code from the TCB, convert the current
EL1 - EL2 KVM ABI to use hypercall numbers in lieu of direct function pointers.
While this in itself does not provide any isolation, it is a first step towards
having a properly defined EL2 ABI.

The implementation is based on a kvm_hcall_table which holds function pointers
to the hyp_text functions corresponding to each hypercall. This is highly
inspired from the arm64 sys_call_table, the main difference being the lack of
hcall wrappers at this stage.

Signed-off-by: Quentin Perret <qperret@google.com>
Signed-off-by: David Brazdil <dbrazdil@google.com>
---
 arch/arm64/include/asm/kvm_host.h            | 20 ++++++-
 arch/arm64/include/asm/kvm_host_hypercalls.h | 62 ++++++++++++++++++++
 arch/arm64/kvm/hyp/Makefile                  |  2 +-
 arch/arm64/kvm/hyp/host_hypercall.c          | 22 +++++++
 arch/arm64/kvm/hyp/hyp-entry.S               | 36 +++++++++++-
 5 files changed, 137 insertions(+), 5 deletions(-)
 create mode 100644 arch/arm64/include/asm/kvm_host_hypercalls.h
 create mode 100644 arch/arm64/kvm/hyp/host_hypercall.c

Comments

Marc Zyngier May 7, 2020, 1:10 p.m. UTC | #1
Hi David, Quentin,

On Thu, 30 Apr 2020 15:48:18 +0100,
David Brazdil <dbrazdil@google.com> wrote:
> 
> From: Quentin Perret <qperret@google.com>
> 
> In preparation for removing the hyp code from the TCB, convert the current

nit: Unless we have a different interpretation of the TCB acronym, I
think you want to remove anything *but* the EL2 code from the TCB.

> EL1 - EL2 KVM ABI to use hypercall numbers in lieu of direct function pointers.
> While this in itself does not provide any isolation, it is a first step towards
> having a properly defined EL2 ABI.
> 
> The implementation is based on a kvm_hcall_table which holds function pointers
> to the hyp_text functions corresponding to each hypercall. This is highly
> inspired from the arm64 sys_call_table, the main difference being the lack of
> hcall wrappers at this stage.
> 
> Signed-off-by: Quentin Perret <qperret@google.com>
> Signed-off-by: David Brazdil <dbrazdil@google.com>
> ---
>  arch/arm64/include/asm/kvm_host.h            | 20 ++++++-
>  arch/arm64/include/asm/kvm_host_hypercalls.h | 62 ++++++++++++++++++++
>  arch/arm64/kvm/hyp/Makefile                  |  2 +-
>  arch/arm64/kvm/hyp/host_hypercall.c          | 22 +++++++
>  arch/arm64/kvm/hyp/hyp-entry.S               | 36 +++++++++++-
>  5 files changed, 137 insertions(+), 5 deletions(-)
>  create mode 100644 arch/arm64/include/asm/kvm_host_hypercalls.h
>  create mode 100644 arch/arm64/kvm/hyp/host_hypercall.c
> 
> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index e61143d6602d..5dec3b06f2b7 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -24,6 +24,7 @@
>  #include <asm/fpsimd.h>
>  #include <asm/kvm.h>
>  #include <asm/kvm_asm.h>
> +#include <asm/kvm_host_hypercalls.h>
>  #include <asm/thread_info.h>
>  #include <asm/virt.h>
>  
> @@ -447,10 +448,25 @@ int kvm_test_age_hva(struct kvm *kvm, unsigned long hva);
>  void kvm_arm_halt_guest(struct kvm *kvm);
>  void kvm_arm_resume_guest(struct kvm *kvm);
>  
> -#define kvm_call_hyp_nvhe(hypfn, ...) \
> -	__kvm_call_hyp((unsigned long)kvm_ksym_ref(hypfn), ##__VA_ARGS__)
> +/*
> + * Call the hypervisor via HVC. The hcall parameter must be the name of
> + * a hypercall as defined in <asm/kvm_host_hypercall.h>.
> + *
> + * Hypercalls take at most 6 parameters.
> + */
> +#define kvm_call_hyp_nvhe(hcall, ...) \
> +	__kvm_call_hyp(KVM_HOST_HCALL_NR(hcall), ##__VA_ARGS__)
>  
>  /*
> + * u64 kvm_call_hyp(hcall, ...);
> + *
> + * Call the hypervisor. The hcall parameter must be the name of a hypercall
> + * defined in <asm/kvm_host_hypercall.h>. In the VHE case, this will be
> + * translated into a direct function call.
> + *
> + * A hcall value of less than 0xfff has a special meaning and is used to
> + * implement hyp stubs in the same way as in arch/arm64/kernel/hyp_stub.S.
> + *
>   * The couple of isb() below are there to guarantee the same behaviour
>   * on VHE as on !VHE, where the eret to EL1 acts as a context
>   * synchronization event.
> diff --git a/arch/arm64/include/asm/kvm_host_hypercalls.h b/arch/arm64/include/asm/kvm_host_hypercalls.h
> new file mode 100644
> index 000000000000..af8ad505d816
> --- /dev/null
> +++ b/arch/arm64/include/asm/kvm_host_hypercalls.h
> @@ -0,0 +1,62 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2020 Google, inc
> + */
> +
> +#ifndef __KVM_HOST_HCALL
> +#define __KVM_HOST_HCALL(x)
> +#endif
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_enable_ssbs		0
> +__KVM_HOST_HCALL(__kvm_enable_ssbs)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_get_mdcr_el2		1
> +__KVM_HOST_HCALL(__kvm_get_mdcr_el2)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_timer_set_cntvoff	2
> +__KVM_HOST_HCALL(__kvm_timer_set_cntvoff)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_local_vmid	3
> +__KVM_HOST_HCALL(__kvm_tlb_flush_local_vmid)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_flush_vm_context	4
> +__KVM_HOST_HCALL(__kvm_flush_vm_context)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_vcpu_run_nvhe		5
> +__KVM_HOST_HCALL(__kvm_vcpu_run_nvhe)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_vmid		6
> +__KVM_HOST_HCALL(__kvm_tlb_flush_vmid)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_vmid_ipa	7
> +__KVM_HOST_HCALL(__kvm_tlb_flush_vmid_ipa)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_init_lrs		8
> +__KVM_HOST_HCALL(__vgic_v3_init_lrs)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_get_ich_vtr_el2	9
> +__KVM_HOST_HCALL(__vgic_v3_get_ich_vtr_el2)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_write_vmcr		10
> +__KVM_HOST_HCALL(__vgic_v3_write_vmcr)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_restore_aprs	11
> +__KVM_HOST_HCALL(__vgic_v3_restore_aprs)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_read_vmcr		12
> +__KVM_HOST_HCALL(__vgic_v3_read_vmcr)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_save_aprs		13
> +__KVM_HOST_HCALL(__vgic_v3_save_aprs)
> +
> +#define __KVM_HOST_HCALL_TABLE_IDX_SIZE				14

This whole thing screams "enum" into my ears. Having to maintain these
as #defines feels like a pain, specially if the numbers are never used
in assembly code. (and for that, we have asm-offset.h).

> +
> +/* XXX - Arbitrary offset for KVM-related hypercalls */
> +#define __KVM_HOST_HCALL_BASE_SHIFT 8
> +#define __KVM_HOST_HCALL_BASE (1ULL << __KVM_HOST_HCALL_BASE_SHIFT)
> +#define __KVM_HOST_HCALL_END (__KVM_HOST_HCALL_BASE + \
> +			      __KVM_HOST_HCALL_TABLE_IDX_SIZE)

I don't really get what is this offset for. It is too small to be used
to skip the stub hypercalls (you'd need to start at 0x1000). Given
that you store the whole thing in an array, you're wasting some
memory. I'm sure you have a good story for it though! ;-)

> +
> +/* Hypercall number = kvm offset + table idx */
> +#define KVM_HOST_HCALL_NR(name) (__KVM_HOST_HCALL_TABLE_IDX_##name + \
> +				 __KVM_HOST_HCALL_BASE)
> diff --git a/arch/arm64/kvm/hyp/Makefile b/arch/arm64/kvm/hyp/Makefile
> index 8c9880783839..29e2b2cd2fbc 100644
> --- a/arch/arm64/kvm/hyp/Makefile
> +++ b/arch/arm64/kvm/hyp/Makefile
> @@ -9,7 +9,7 @@ ccflags-y += -fno-stack-protector -DDISABLE_BRANCH_PROFILING \
>  obj-$(CONFIG_KVM) += hyp.o
>  
>  hyp-y := vgic-v3-sr.o timer-sr.o aarch32.o vgic-v2-cpuif-proxy.o sysreg-sr.o \
> -	 debug-sr.o entry.o switch.o fpsimd.o tlb.o hyp-entry.o
> +	 debug-sr.o entry.o switch.o fpsimd.o tlb.o host_hypercall.o hyp-entry.o
>  
>  # KVM code is run at a different exception code with a different map, so
>  # compiler instrumentation that inserts callbacks or checks into the code may
> diff --git a/arch/arm64/kvm/hyp/host_hypercall.c b/arch/arm64/kvm/hyp/host_hypercall.c
> new file mode 100644
> index 000000000000..6b31310f36a8
> --- /dev/null
> +++ b/arch/arm64/kvm/hyp/host_hypercall.c
> @@ -0,0 +1,22 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2020 Google, inc
> + */
> +
> +#include <asm/kvm_hyp.h>
> +
> +typedef long (*kvm_hcall_fn_t)(void);
> +
> +static long __hyp_text kvm_hc_ni(void)
> +{
> +       return -ENOSYS;
> +}
> +
> +#undef __KVM_HOST_HCALL
> +#define __KVM_HOST_HCALL(name) \
> +	[__KVM_HOST_HCALL_TABLE_IDX_##name] = (long (*)(void))name,
> +
> +const kvm_hcall_fn_t kvm_hcall_table[__KVM_HOST_HCALL_TABLE_IDX_SIZE] = {
> +	[0 ... __KVM_HOST_HCALL_TABLE_IDX_SIZE-1] = kvm_hc_ni,
> +#include <asm/kvm_host_hypercalls.h>
> +};

Cunning. At the same time, if you can do this once, you can do it
twice, and generating the __KVM_HOST_HCALL_TABLE_IDX_* as an enum
should be pretty easy, and could live in its own include file.

> diff --git a/arch/arm64/kvm/hyp/hyp-entry.S b/arch/arm64/kvm/hyp/hyp-entry.S
> index c2a13ab3c471..1a51a0958504 100644
> --- a/arch/arm64/kvm/hyp/hyp-entry.S
> +++ b/arch/arm64/kvm/hyp/hyp-entry.S
> @@ -13,6 +13,7 @@
>  #include <asm/kvm_arm.h>
>  #include <asm/kvm_asm.h>
>  #include <asm/kvm_mmu.h>
> +#include <asm/kvm_host_hypercalls.h>
>  #include <asm/mmu.h>
>  
>  	.text
> @@ -21,17 +22,26 @@
>  .macro do_el2_call
>  	/*
>  	 * Shuffle the parameters before calling the function
> -	 * pointed to in x0. Assumes parameters in x[1,2,3].
> +	 * pointed to in x0. Assumes parameters in x[1,2,3,4,5,6].
>  	 */
>  	str	lr, [sp, #-16]!
>  	mov	lr, x0
>  	mov	x0, x1
>  	mov	x1, x2
>  	mov	x2, x3
> +	mov	x3, x4
> +	mov	x4, x5
> +	mov	x5, x6

Has any function changed prototype as part of this patch that they'd
require this change? It doesn't look like it. I guess this should be
part of another patch.

>  	blr	lr
>  	ldr	lr, [sp], #16
>  .endm
>  
> +/* kern_hyp_va(lm_alias(ksym)) */
> +.macro ksym_hyp_va ksym, lm_offset
> +	sub	\ksym, \ksym, \lm_offset
> +	kern_hyp_va	\ksym
> +.endm
> +
>  el1_sync:				// Guest trapped into EL2
>  
>  	mrs	x0, esr_el2
> @@ -66,10 +76,32 @@ el1_sync:				// Guest trapped into EL2
>  	br	x5
>  
>  1:
> +	/* Check if the hcall number is valid */
> +	cmp	x0, #__KVM_HOST_HCALL_BASE
> +	b.lt	2f
> +	cmp	x0, #__KVM_HOST_HCALL_END
> +	b.lt	3f
> +2:
> +	mov	x0, -ENOSYS
> +	eret
> +
> +3:
> +	/* Compute lm_alias() offset in x9 */
> +	ldr_l	x9, kimage_voffset
> +	ldr_l	x10, physvirt_offset
> +	add	x9, x9, x10
> +
> +	/* Find hcall function pointer in the table */
> +	ldr	x10, =kvm_hcall_table
> +	ksym_hyp_va	x10, x9

Can't kvm_hcall_table be referenced with adr or adr_l? It'd certainly
be nice to avoid the extra load for something that is essentially a
build-time constant.

Another thing that could be improved is to keep the lm_alias offset
somewhere, saving one load per entry. Not a huge deal.

> +	sub	x0, x0, #__KVM_HOST_HCALL_BASE
> +	add	x0, x10, x0, lsl 3

The usual convention for immediate is to prefix them with #.

> +	ldr	x0, [x0]
> +	ksym_hyp_va	x0, x9
> +
>  	/*
>  	 * Perform the EL2 call
>  	 */
> -	kern_hyp_va	x0
>  	do_el2_call
>  
>  	eret
> -- 
> 2.26.1
> 
> 

Thanks,

	M.
Quentin Perret May 7, 2020, 1:33 p.m. UTC | #2
Hey Marc,

On Thursday 07 May 2020 at 14:10:30 (+0100), Marc Zyngier wrote:
> Hi David, Quentin,
> 
> On Thu, 30 Apr 2020 15:48:18 +0100,
> David Brazdil <dbrazdil@google.com> wrote:
> > 
> > From: Quentin Perret <qperret@google.com>
> > 
> > In preparation for removing the hyp code from the TCB, convert the current
> 
> nit: Unless we have a different interpretation of the TCB acronym, I
> think you want to remove anything *but* the EL2 code from the TCB.

Argh! Right... This should have been 'removing the /host/ code' :-)

<snip>
> > diff --git a/arch/arm64/include/asm/kvm_host_hypercalls.h b/arch/arm64/include/asm/kvm_host_hypercalls.h
> > new file mode 100644
> > index 000000000000..af8ad505d816
> > --- /dev/null
> > +++ b/arch/arm64/include/asm/kvm_host_hypercalls.h
> > @@ -0,0 +1,62 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Copyright (C) 2020 Google, inc
> > + */
> > +
> > +#ifndef __KVM_HOST_HCALL
> > +#define __KVM_HOST_HCALL(x)
> > +#endif
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_enable_ssbs		0
> > +__KVM_HOST_HCALL(__kvm_enable_ssbs)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_get_mdcr_el2		1
> > +__KVM_HOST_HCALL(__kvm_get_mdcr_el2)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_timer_set_cntvoff	2
> > +__KVM_HOST_HCALL(__kvm_timer_set_cntvoff)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_local_vmid	3
> > +__KVM_HOST_HCALL(__kvm_tlb_flush_local_vmid)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_flush_vm_context	4
> > +__KVM_HOST_HCALL(__kvm_flush_vm_context)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_vcpu_run_nvhe		5
> > +__KVM_HOST_HCALL(__kvm_vcpu_run_nvhe)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_vmid		6
> > +__KVM_HOST_HCALL(__kvm_tlb_flush_vmid)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_vmid_ipa	7
> > +__KVM_HOST_HCALL(__kvm_tlb_flush_vmid_ipa)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_init_lrs		8
> > +__KVM_HOST_HCALL(__vgic_v3_init_lrs)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_get_ich_vtr_el2	9
> > +__KVM_HOST_HCALL(__vgic_v3_get_ich_vtr_el2)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_write_vmcr		10
> > +__KVM_HOST_HCALL(__vgic_v3_write_vmcr)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_restore_aprs	11
> > +__KVM_HOST_HCALL(__vgic_v3_restore_aprs)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_read_vmcr		12
> > +__KVM_HOST_HCALL(__vgic_v3_read_vmcr)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_save_aprs		13
> > +__KVM_HOST_HCALL(__vgic_v3_save_aprs)
> > +
> > +#define __KVM_HOST_HCALL_TABLE_IDX_SIZE				14
> 
> This whole thing screams "enum" into my ears. Having to maintain these
> as #defines feels like a pain, specially if the numbers are never used
> in assembly code. (and for that, we have asm-offset.h).

This is essentially inspired from the various 'unistd.h' files we have
in the kernel, e.g. include/uapi/asm-generic/unistd.h, which have
exactly this type of construct. So, this was really just for consistency,
but no strong opinion from me.

> 
> > +
> > +/* XXX - Arbitrary offset for KVM-related hypercalls */
> > +#define __KVM_HOST_HCALL_BASE_SHIFT 8
> > +#define __KVM_HOST_HCALL_BASE (1ULL << __KVM_HOST_HCALL_BASE_SHIFT)
> > +#define __KVM_HOST_HCALL_END (__KVM_HOST_HCALL_BASE + \
> > +			      __KVM_HOST_HCALL_TABLE_IDX_SIZE)
> 
> I don't really get what is this offset for. It is too small to be used
> to skip the stub hypercalls (you'd need to start at 0x1000).

Oh, OK. I assumed anything above HVC_STUB_HCALL_NR would be fine. But
yes, this offset is really arbitrary, so if 0x1000 is better then that
totally works. For my education, where is that 0x1000 coming from ?

> Given
> that you store the whole thing in an array, you're wasting some
> memory. I'm sure you have a good story for it though! ;-)

Note that the array's length is __KVM_HOST_HCALL_TABLE_IDX_SIZE, which
doesn't include the offset, so we shouldn't be wasting memory here.

> > +
> > +/* Hypercall number = kvm offset + table idx */
> > +#define KVM_HOST_HCALL_NR(name) (__KVM_HOST_HCALL_TABLE_IDX_##name + \
> > +				 __KVM_HOST_HCALL_BASE)
> > diff --git a/arch/arm64/kvm/hyp/Makefile b/arch/arm64/kvm/hyp/Makefile
> > index 8c9880783839..29e2b2cd2fbc 100644
> > --- a/arch/arm64/kvm/hyp/Makefile
> > +++ b/arch/arm64/kvm/hyp/Makefile
> > @@ -9,7 +9,7 @@ ccflags-y += -fno-stack-protector -DDISABLE_BRANCH_PROFILING \
> >  obj-$(CONFIG_KVM) += hyp.o
> >  
> >  hyp-y := vgic-v3-sr.o timer-sr.o aarch32.o vgic-v2-cpuif-proxy.o sysreg-sr.o \
> > -	 debug-sr.o entry.o switch.o fpsimd.o tlb.o hyp-entry.o
> > +	 debug-sr.o entry.o switch.o fpsimd.o tlb.o host_hypercall.o hyp-entry.o
> >  
> >  # KVM code is run at a different exception code with a different map, so
> >  # compiler instrumentation that inserts callbacks or checks into the code may
> > diff --git a/arch/arm64/kvm/hyp/host_hypercall.c b/arch/arm64/kvm/hyp/host_hypercall.c
> > new file mode 100644
> > index 000000000000..6b31310f36a8
> > --- /dev/null
> > +++ b/arch/arm64/kvm/hyp/host_hypercall.c
> > @@ -0,0 +1,22 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2020 Google, inc
> > + */
> > +
> > +#include <asm/kvm_hyp.h>
> > +
> > +typedef long (*kvm_hcall_fn_t)(void);
> > +
> > +static long __hyp_text kvm_hc_ni(void)
> > +{
> > +       return -ENOSYS;
> > +}
> > +
> > +#undef __KVM_HOST_HCALL
> > +#define __KVM_HOST_HCALL(name) \
> > +	[__KVM_HOST_HCALL_TABLE_IDX_##name] = (long (*)(void))name,
> > +
> > +const kvm_hcall_fn_t kvm_hcall_table[__KVM_HOST_HCALL_TABLE_IDX_SIZE] = {
> > +	[0 ... __KVM_HOST_HCALL_TABLE_IDX_SIZE-1] = kvm_hc_ni,
> > +#include <asm/kvm_host_hypercalls.h>
> > +};
> 
> Cunning. At the same time, if you can do this once, you can do it
> twice, and generating the __KVM_HOST_HCALL_TABLE_IDX_* as an enum
> should be pretty easy, and could live in its own include file.

Right, and that again is inspired from the arm64 syscall table (see
arch/arm64/kernel/sys.c). So the first intention was to keep things
consistent. But again, no strong opinion :)

> > diff --git a/arch/arm64/kvm/hyp/hyp-entry.S b/arch/arm64/kvm/hyp/hyp-entry.S
> > index c2a13ab3c471..1a51a0958504 100644
> > --- a/arch/arm64/kvm/hyp/hyp-entry.S
> > +++ b/arch/arm64/kvm/hyp/hyp-entry.S
> > @@ -13,6 +13,7 @@
> >  #include <asm/kvm_arm.h>
> >  #include <asm/kvm_asm.h>
> >  #include <asm/kvm_mmu.h>
> > +#include <asm/kvm_host_hypercalls.h>
> >  #include <asm/mmu.h>
> >  
> >  	.text
> > @@ -21,17 +22,26 @@
> >  .macro do_el2_call
> >  	/*
> >  	 * Shuffle the parameters before calling the function
> > -	 * pointed to in x0. Assumes parameters in x[1,2,3].
> > +	 * pointed to in x0. Assumes parameters in x[1,2,3,4,5,6].
> >  	 */
> >  	str	lr, [sp, #-16]!
> >  	mov	lr, x0
> >  	mov	x0, x1
> >  	mov	x1, x2
> >  	mov	x2, x3
> > +	mov	x3, x4
> > +	mov	x4, x5
> > +	mov	x5, x6
> 
> Has any function changed prototype as part of this patch that they'd
> require this change? It doesn't look like it. I guess this should be
> part of another patch.

Ack, this isn't needed just yet so we should split that out.

> 
> >  	blr	lr
> >  	ldr	lr, [sp], #16
> >  .endm
> >  
> > +/* kern_hyp_va(lm_alias(ksym)) */
> > +.macro ksym_hyp_va ksym, lm_offset
> > +	sub	\ksym, \ksym, \lm_offset
> > +	kern_hyp_va	\ksym
> > +.endm
> > +
> >  el1_sync:				// Guest trapped into EL2
> >  
> >  	mrs	x0, esr_el2
> > @@ -66,10 +76,32 @@ el1_sync:				// Guest trapped into EL2
> >  	br	x5
> >  
> >  1:
> > +	/* Check if the hcall number is valid */
> > +	cmp	x0, #__KVM_HOST_HCALL_BASE
> > +	b.lt	2f
> > +	cmp	x0, #__KVM_HOST_HCALL_END
> > +	b.lt	3f
> > +2:
> > +	mov	x0, -ENOSYS
> > +	eret
> > +
> > +3:
> > +	/* Compute lm_alias() offset in x9 */
> > +	ldr_l	x9, kimage_voffset
> > +	ldr_l	x10, physvirt_offset
> > +	add	x9, x9, x10
> > +
> > +	/* Find hcall function pointer in the table */
> > +	ldr	x10, =kvm_hcall_table
> > +	ksym_hyp_va	x10, x9
> 
> Can't kvm_hcall_table be referenced with adr or adr_l? It'd certainly
> be nice to avoid the extra load for something that is essentially a
> build-time constant.

In fact David already has a nice patch that transforms the whole thing
in a jump table, which is much nicer. I'll let him share the details :)

> Another thing that could be improved is to keep the lm_alias offset
> somewhere, saving one load per entry. Not a huge deal.

Ack.

> > +	sub	x0, x0, #__KVM_HOST_HCALL_BASE
> > +	add	x0, x10, x0, lsl 3
> 
> The usual convention for immediate is to prefix them with #.

Noted, thanks.

> > +	ldr	x0, [x0]
> > +	ksym_hyp_va	x0, x9
> > +
> >  	/*
> >  	 * Perform the EL2 call
> >  	 */
> > -	kern_hyp_va	x0
> >  	do_el2_call
> >  
> >  	eret
> > -- 
> > 2.26.1
> > 
> > 

Thanks for the review!
Quentin
Marc Zyngier May 10, 2020, 10:16 a.m. UTC | #3
Hi Quentin,

On Thu, 07 May 2020 14:33:20 +0100,
Quentin Perret <qperret@google.com> wrote:
> 
> Hey Marc,
> 
> On Thursday 07 May 2020 at 14:10:30 (+0100), Marc Zyngier wrote:
> > Hi David, Quentin,

[...]

> > > +#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_save_aprs		13
> > > +__KVM_HOST_HCALL(__vgic_v3_save_aprs)
> > > +
> > > +#define __KVM_HOST_HCALL_TABLE_IDX_SIZE				14
> > 
> > This whole thing screams "enum" into my ears. Having to maintain these
> > as #defines feels like a pain, specially if the numbers are never used
> > in assembly code. (and for that, we have asm-offset.h).
> 
> This is essentially inspired from the various 'unistd.h' files we have
> in the kernel, e.g. include/uapi/asm-generic/unistd.h, which have
> exactly this type of construct. So, this was really just for consistency,
> but no strong opinion from me.

I think part of the of the reason for not using an enum in the syscall
code is that part of is is (was?) used from assembly code. In our
case, I'm pretty sure we won't go back to handling calls from asm any
time soon, so a generated enum (and associated function pointer array)
would be better.

> 
> > 
> > > +
> > > +/* XXX - Arbitrary offset for KVM-related hypercalls */
> > > +#define __KVM_HOST_HCALL_BASE_SHIFT 8
> > > +#define __KVM_HOST_HCALL_BASE (1ULL << __KVM_HOST_HCALL_BASE_SHIFT)
> > > +#define __KVM_HOST_HCALL_END (__KVM_HOST_HCALL_BASE + \
> > > +			      __KVM_HOST_HCALL_TABLE_IDX_SIZE)
> > 
> > I don't really get what is this offset for. It is too small to be used
> > to skip the stub hypercalls (you'd need to start at 0x1000).
> 
> Oh, OK. I assumed anything above HVC_STUB_HCALL_NR would be fine. But
> yes, this offset is really arbitrary, so if 0x1000 is better then that
> totally works. For my education, where is that 0x1000 coming from ?

We assumed that we wouldn't get a function pointer in the first 4kB,
and documented as such in hyp.S. I would say that either we keep the
current convention of leaving the first 4k function codes for the
hyp-stubs, or we drop any sort of gap.

Another thing to consider is that there is at least *one* external
hypervisor (Jailhouse) that uses the stubs, so I'd like to make sure
we don't break that (even if we made no promise whatsoever in that
respect).

> 
> > Given
> > that you store the whole thing in an array, you're wasting some
> > memory. I'm sure you have a good story for it though! ;-)
> 
> Note that the array's length is __KVM_HOST_HCALL_TABLE_IDX_SIZE, which
> doesn't include the offset, so we shouldn't be wasting memory here.

Ah, you're right. I got confused between _SIZE and _END.

> 
> > > +
> > > +/* Hypercall number = kvm offset + table idx */
> > > +#define KVM_HOST_HCALL_NR(name) (__KVM_HOST_HCALL_TABLE_IDX_##name + \
> > > +				 __KVM_HOST_HCALL_BASE)
> > > diff --git a/arch/arm64/kvm/hyp/Makefile b/arch/arm64/kvm/hyp/Makefile
> > > index 8c9880783839..29e2b2cd2fbc 100644
> > > --- a/arch/arm64/kvm/hyp/Makefile
> > > +++ b/arch/arm64/kvm/hyp/Makefile
> > > @@ -9,7 +9,7 @@ ccflags-y += -fno-stack-protector -DDISABLE_BRANCH_PROFILING \
> > >  obj-$(CONFIG_KVM) += hyp.o
> > >  
> > >  hyp-y := vgic-v3-sr.o timer-sr.o aarch32.o vgic-v2-cpuif-proxy.o sysreg-sr.o \
> > > -	 debug-sr.o entry.o switch.o fpsimd.o tlb.o hyp-entry.o
> > > +	 debug-sr.o entry.o switch.o fpsimd.o tlb.o host_hypercall.o hyp-entry.o
> > >  
> > >  # KVM code is run at a different exception code with a different map, so
> > >  # compiler instrumentation that inserts callbacks or checks into the code may
> > > diff --git a/arch/arm64/kvm/hyp/host_hypercall.c b/arch/arm64/kvm/hyp/host_hypercall.c
> > > new file mode 100644
> > > index 000000000000..6b31310f36a8
> > > --- /dev/null
> > > +++ b/arch/arm64/kvm/hyp/host_hypercall.c
> > > @@ -0,0 +1,22 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * Copyright (C) 2020 Google, inc
> > > + */
> > > +
> > > +#include <asm/kvm_hyp.h>
> > > +
> > > +typedef long (*kvm_hcall_fn_t)(void);
> > > +
> > > +static long __hyp_text kvm_hc_ni(void)
> > > +{
> > > +       return -ENOSYS;
> > > +}
> > > +
> > > +#undef __KVM_HOST_HCALL
> > > +#define __KVM_HOST_HCALL(name) \
> > > +	[__KVM_HOST_HCALL_TABLE_IDX_##name] = (long (*)(void))name,
> > > +
> > > +const kvm_hcall_fn_t kvm_hcall_table[__KVM_HOST_HCALL_TABLE_IDX_SIZE] = {
> > > +	[0 ... __KVM_HOST_HCALL_TABLE_IDX_SIZE-1] = kvm_hc_ni,
> > > +#include <asm/kvm_host_hypercalls.h>
> > > +};
> > 
> > Cunning. At the same time, if you can do this once, you can do it
> > twice, and generating the __KVM_HOST_HCALL_TABLE_IDX_* as an enum
> > should be pretty easy, and could live in its own include file.
> 
> Right, and that again is inspired from the arm64 syscall table (see
> arch/arm64/kernel/sys.c). So the first intention was to keep things
> consistent. But again, no strong opinion :)

So let's try to build it with an enum instead, and see if it works. If
it doesn't, at least we will know why.

[...]

> > > +	/* Find hcall function pointer in the table */
> > > +	ldr	x10, =kvm_hcall_table
> > > +	ksym_hyp_va	x10, x9
> > 
> > Can't kvm_hcall_table be referenced with adr or adr_l? It'd certainly
> > be nice to avoid the extra load for something that is essentially a
> > build-time constant.
> 
> In fact David already has a nice patch that transforms the whole thing
> in a jump table, which is much nicer. I'll let him share the details
> :)

Ah! Looking forward to reviewing it then!

Thanks,

	M.
David Brazdil May 13, 2020, 10:48 a.m. UTC | #4
> > In fact David already has a nice patch that transforms the whole thing
> > in a jump table, which is much nicer. I'll let him share the details
> > :)
> 
> Ah! Looking forward to reviewing it then!

It's not actually that different. It still has the same header file, just uses
the macros to generate a jump table rather than an array of function pointers.
The main advantage being that we can avoid .hyp.text dependency on
physvirt_offset. Feel free to have a look, branch 'topic/el2-obj-wip' at:
	https://android-kvm.googlesource.com/linux

Perhaps this is not worth the trouble. We do hope to get to a point where the
ABI between .text and .hyp.text is formalized, but in my mind that ABI is
unlikely to be using this same hypcall path.

On the other hand, I've played with preserving the function-pointer interface
in the last couple of days and later in this series we do end up having to
declare all of the hcall entry points (which now have two ELF symbols), so we
end up with a similar table regardless, just with no IDs assigned.

-David
diff mbox series

Patch

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index e61143d6602d..5dec3b06f2b7 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -24,6 +24,7 @@ 
 #include <asm/fpsimd.h>
 #include <asm/kvm.h>
 #include <asm/kvm_asm.h>
+#include <asm/kvm_host_hypercalls.h>
 #include <asm/thread_info.h>
 #include <asm/virt.h>
 
@@ -447,10 +448,25 @@  int kvm_test_age_hva(struct kvm *kvm, unsigned long hva);
 void kvm_arm_halt_guest(struct kvm *kvm);
 void kvm_arm_resume_guest(struct kvm *kvm);
 
-#define kvm_call_hyp_nvhe(hypfn, ...) \
-	__kvm_call_hyp((unsigned long)kvm_ksym_ref(hypfn), ##__VA_ARGS__)
+/*
+ * Call the hypervisor via HVC. The hcall parameter must be the name of
+ * a hypercall as defined in <asm/kvm_host_hypercall.h>.
+ *
+ * Hypercalls take at most 6 parameters.
+ */
+#define kvm_call_hyp_nvhe(hcall, ...) \
+	__kvm_call_hyp(KVM_HOST_HCALL_NR(hcall), ##__VA_ARGS__)
 
 /*
+ * u64 kvm_call_hyp(hcall, ...);
+ *
+ * Call the hypervisor. The hcall parameter must be the name of a hypercall
+ * defined in <asm/kvm_host_hypercall.h>. In the VHE case, this will be
+ * translated into a direct function call.
+ *
+ * A hcall value of less than 0xfff has a special meaning and is used to
+ * implement hyp stubs in the same way as in arch/arm64/kernel/hyp_stub.S.
+ *
  * The couple of isb() below are there to guarantee the same behaviour
  * on VHE as on !VHE, where the eret to EL1 acts as a context
  * synchronization event.
diff --git a/arch/arm64/include/asm/kvm_host_hypercalls.h b/arch/arm64/include/asm/kvm_host_hypercalls.h
new file mode 100644
index 000000000000..af8ad505d816
--- /dev/null
+++ b/arch/arm64/include/asm/kvm_host_hypercalls.h
@@ -0,0 +1,62 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2020 Google, inc
+ */
+
+#ifndef __KVM_HOST_HCALL
+#define __KVM_HOST_HCALL(x)
+#endif
+
+#define __KVM_HOST_HCALL_TABLE_IDX___kvm_enable_ssbs		0
+__KVM_HOST_HCALL(__kvm_enable_ssbs)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___kvm_get_mdcr_el2		1
+__KVM_HOST_HCALL(__kvm_get_mdcr_el2)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___kvm_timer_set_cntvoff	2
+__KVM_HOST_HCALL(__kvm_timer_set_cntvoff)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_local_vmid	3
+__KVM_HOST_HCALL(__kvm_tlb_flush_local_vmid)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___kvm_flush_vm_context	4
+__KVM_HOST_HCALL(__kvm_flush_vm_context)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___kvm_vcpu_run_nvhe		5
+__KVM_HOST_HCALL(__kvm_vcpu_run_nvhe)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_vmid		6
+__KVM_HOST_HCALL(__kvm_tlb_flush_vmid)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___kvm_tlb_flush_vmid_ipa	7
+__KVM_HOST_HCALL(__kvm_tlb_flush_vmid_ipa)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_init_lrs		8
+__KVM_HOST_HCALL(__vgic_v3_init_lrs)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_get_ich_vtr_el2	9
+__KVM_HOST_HCALL(__vgic_v3_get_ich_vtr_el2)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_write_vmcr		10
+__KVM_HOST_HCALL(__vgic_v3_write_vmcr)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_restore_aprs	11
+__KVM_HOST_HCALL(__vgic_v3_restore_aprs)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_read_vmcr		12
+__KVM_HOST_HCALL(__vgic_v3_read_vmcr)
+
+#define __KVM_HOST_HCALL_TABLE_IDX___vgic_v3_save_aprs		13
+__KVM_HOST_HCALL(__vgic_v3_save_aprs)
+
+#define __KVM_HOST_HCALL_TABLE_IDX_SIZE				14
+
+/* XXX - Arbitrary offset for KVM-related hypercalls */
+#define __KVM_HOST_HCALL_BASE_SHIFT 8
+#define __KVM_HOST_HCALL_BASE (1ULL << __KVM_HOST_HCALL_BASE_SHIFT)
+#define __KVM_HOST_HCALL_END (__KVM_HOST_HCALL_BASE + \
+			      __KVM_HOST_HCALL_TABLE_IDX_SIZE)
+
+/* Hypercall number = kvm offset + table idx */
+#define KVM_HOST_HCALL_NR(name) (__KVM_HOST_HCALL_TABLE_IDX_##name + \
+				 __KVM_HOST_HCALL_BASE)
diff --git a/arch/arm64/kvm/hyp/Makefile b/arch/arm64/kvm/hyp/Makefile
index 8c9880783839..29e2b2cd2fbc 100644
--- a/arch/arm64/kvm/hyp/Makefile
+++ b/arch/arm64/kvm/hyp/Makefile
@@ -9,7 +9,7 @@  ccflags-y += -fno-stack-protector -DDISABLE_BRANCH_PROFILING \
 obj-$(CONFIG_KVM) += hyp.o
 
 hyp-y := vgic-v3-sr.o timer-sr.o aarch32.o vgic-v2-cpuif-proxy.o sysreg-sr.o \
-	 debug-sr.o entry.o switch.o fpsimd.o tlb.o hyp-entry.o
+	 debug-sr.o entry.o switch.o fpsimd.o tlb.o host_hypercall.o hyp-entry.o
 
 # KVM code is run at a different exception code with a different map, so
 # compiler instrumentation that inserts callbacks or checks into the code may
diff --git a/arch/arm64/kvm/hyp/host_hypercall.c b/arch/arm64/kvm/hyp/host_hypercall.c
new file mode 100644
index 000000000000..6b31310f36a8
--- /dev/null
+++ b/arch/arm64/kvm/hyp/host_hypercall.c
@@ -0,0 +1,22 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020 Google, inc
+ */
+
+#include <asm/kvm_hyp.h>
+
+typedef long (*kvm_hcall_fn_t)(void);
+
+static long __hyp_text kvm_hc_ni(void)
+{
+       return -ENOSYS;
+}
+
+#undef __KVM_HOST_HCALL
+#define __KVM_HOST_HCALL(name) \
+	[__KVM_HOST_HCALL_TABLE_IDX_##name] = (long (*)(void))name,
+
+const kvm_hcall_fn_t kvm_hcall_table[__KVM_HOST_HCALL_TABLE_IDX_SIZE] = {
+	[0 ... __KVM_HOST_HCALL_TABLE_IDX_SIZE-1] = kvm_hc_ni,
+#include <asm/kvm_host_hypercalls.h>
+};
diff --git a/arch/arm64/kvm/hyp/hyp-entry.S b/arch/arm64/kvm/hyp/hyp-entry.S
index c2a13ab3c471..1a51a0958504 100644
--- a/arch/arm64/kvm/hyp/hyp-entry.S
+++ b/arch/arm64/kvm/hyp/hyp-entry.S
@@ -13,6 +13,7 @@ 
 #include <asm/kvm_arm.h>
 #include <asm/kvm_asm.h>
 #include <asm/kvm_mmu.h>
+#include <asm/kvm_host_hypercalls.h>
 #include <asm/mmu.h>
 
 	.text
@@ -21,17 +22,26 @@ 
 .macro do_el2_call
 	/*
 	 * Shuffle the parameters before calling the function
-	 * pointed to in x0. Assumes parameters in x[1,2,3].
+	 * pointed to in x0. Assumes parameters in x[1,2,3,4,5,6].
 	 */
 	str	lr, [sp, #-16]!
 	mov	lr, x0
 	mov	x0, x1
 	mov	x1, x2
 	mov	x2, x3
+	mov	x3, x4
+	mov	x4, x5
+	mov	x5, x6
 	blr	lr
 	ldr	lr, [sp], #16
 .endm
 
+/* kern_hyp_va(lm_alias(ksym)) */
+.macro ksym_hyp_va ksym, lm_offset
+	sub	\ksym, \ksym, \lm_offset
+	kern_hyp_va	\ksym
+.endm
+
 el1_sync:				// Guest trapped into EL2
 
 	mrs	x0, esr_el2
@@ -66,10 +76,32 @@  el1_sync:				// Guest trapped into EL2
 	br	x5
 
 1:
+	/* Check if the hcall number is valid */
+	cmp	x0, #__KVM_HOST_HCALL_BASE
+	b.lt	2f
+	cmp	x0, #__KVM_HOST_HCALL_END
+	b.lt	3f
+2:
+	mov	x0, -ENOSYS
+	eret
+
+3:
+	/* Compute lm_alias() offset in x9 */
+	ldr_l	x9, kimage_voffset
+	ldr_l	x10, physvirt_offset
+	add	x9, x9, x10
+
+	/* Find hcall function pointer in the table */
+	ldr	x10, =kvm_hcall_table
+	ksym_hyp_va	x10, x9
+	sub	x0, x0, #__KVM_HOST_HCALL_BASE
+	add	x0, x10, x0, lsl 3
+	ldr	x0, [x0]
+	ksym_hyp_va	x0, x9
+
 	/*
 	 * Perform the EL2 call
 	 */
-	kern_hyp_va	x0
 	do_el2_call
 
 	eret