From: Tianyu Lan <ltykernel@xxxxxxxxx> Sent: Monday, August 9, 2021 10:56 AM > Subject: [PATCH V3 03/13] x86/HV: Add new hvcall guest address host visibility support Use "x86/hyperv:" tag in the Subject line. > > From: Tianyu Lan <Tianyu.Lan@xxxxxxxxxxxxx> > > Add new hvcall guest address host visibility support to mark > memory visible to host. Call it inside set_memory_decrypted > /encrypted(). Add HYPERVISOR feature check in the > hv_is_isolation_supported() to optimize in non-virtualization > environment. > > Signed-off-by: Tianyu Lan <Tianyu.Lan@xxxxxxxxxxxxx> > --- > Change since v2: > * Rework __set_memory_enc_dec() and call Hyper-V and AMD function > according to platform check. > > Change since v1: > * Use new staic call x86_set_memory_enc to avoid add Hyper-V > specific check in the set_memory code. > --- > arch/x86/hyperv/Makefile | 2 +- > arch/x86/hyperv/hv_init.c | 6 ++ > arch/x86/hyperv/ivm.c | 114 +++++++++++++++++++++++++++++ > arch/x86/include/asm/hyperv-tlfs.h | 20 +++++ > arch/x86/include/asm/mshyperv.h | 4 +- > arch/x86/mm/pat/set_memory.c | 19 +++-- > include/asm-generic/hyperv-tlfs.h | 1 + > include/asm-generic/mshyperv.h | 1 + > 8 files changed, 160 insertions(+), 7 deletions(-) > create mode 100644 arch/x86/hyperv/ivm.c > > diff --git a/arch/x86/hyperv/Makefile b/arch/x86/hyperv/Makefile > index 48e2c51464e8..5d2de10809ae 100644 > --- a/arch/x86/hyperv/Makefile > +++ b/arch/x86/hyperv/Makefile > @@ -1,5 +1,5 @@ > # SPDX-License-Identifier: GPL-2.0-only > -obj-y := hv_init.o mmu.o nested.o irqdomain.o > +obj-y := hv_init.o mmu.o nested.o irqdomain.o ivm.o > obj-$(CONFIG_X86_64) += hv_apic.o hv_proc.o > > ifdef CONFIG_X86_64 > diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c > index 0bb4d9ca7a55..b3683083208a 100644 > --- a/arch/x86/hyperv/hv_init.c > +++ b/arch/x86/hyperv/hv_init.c > @@ -607,6 +607,12 @@ EXPORT_SYMBOL_GPL(hv_get_isolation_type); > > bool hv_is_isolation_supported(void) > { > + if (!cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) > + return 0; > + > + if (!hypervisor_is_type(X86_HYPER_MS_HYPERV)) > + return 0; > + > return hv_get_isolation_type() != HV_ISOLATION_TYPE_NONE; Could all of the tests in this function be run at initialization time, and a single Boolean value pre-computed that this function returns? I don't think any of tests would change during the lifetime of the Linux instance, so running the tests every time is slower than it needs to be. > } > > diff --git a/arch/x86/hyperv/ivm.c b/arch/x86/hyperv/ivm.c > new file mode 100644 > index 000000000000..8c905ffdba7f > --- /dev/null > +++ b/arch/x86/hyperv/ivm.c > @@ -0,0 +1,114 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Hyper-V Isolation VM interface with paravisor and hypervisor > + * > + * Author: > + * Tianyu Lan <Tianyu.Lan@xxxxxxxxxxxxx> > + */ > + > +#include <linux/hyperv.h> > +#include <linux/types.h> > +#include <linux/bitfield.h> > +#include <linux/slab.h> > +#include <asm/io.h> > +#include <asm/mshyperv.h> > + > +/* > + * hv_mark_gpa_visibility - Set pages visible to host via hvcall. > + * > + * In Isolation VM, all guest memory is encripted from host and guest > + * needs to set memory visible to host via hvcall before sharing memory > + * with host. > + */ > +int hv_mark_gpa_visibility(u16 count, const u64 pfn[], > + enum hv_mem_host_visibility visibility) > +{ > + struct hv_gpa_range_for_visibility **input_pcpu, *input; > + u16 pages_processed; > + u64 hv_status; > + unsigned long flags; > + > + /* no-op if partition isolation is not enabled */ > + if (!hv_is_isolation_supported()) > + return 0; > + > + if (count > HV_MAX_MODIFY_GPA_REP_COUNT) { > + pr_err("Hyper-V: GPA count:%d exceeds supported:%lu\n", count, > + HV_MAX_MODIFY_GPA_REP_COUNT); > + return -EINVAL; > + } > + > + local_irq_save(flags); > + input_pcpu = (struct hv_gpa_range_for_visibility **) > + this_cpu_ptr(hyperv_pcpu_input_arg); > + input = *input_pcpu; > + if (unlikely(!input)) { > + local_irq_restore(flags); > + return -EINVAL; > + } > + > + input->partition_id = HV_PARTITION_ID_SELF; > + input->host_visibility = visibility; > + input->reserved0 = 0; > + input->reserved1 = 0; > + memcpy((void *)input->gpa_page_list, pfn, count * sizeof(*pfn)); > + hv_status = hv_do_rep_hypercall( > + HVCALL_MODIFY_SPARSE_GPA_PAGE_HOST_VISIBILITY, count, > + 0, input, &pages_processed); > + local_irq_restore(flags); > + > + if (!(hv_status & HV_HYPERCALL_RESULT_MASK)) > + return 0; pages_processed should also be checked to ensure that it equals count. If not, something has gone wrong in the hypercall. > + > + return hv_status & HV_HYPERCALL_RESULT_MASK; > +} > +EXPORT_SYMBOL(hv_mark_gpa_visibility); > + > +static int __hv_set_mem_host_visibility(void *kbuffer, int pagecount, > + enum hv_mem_host_visibility visibility) > +{ > + u64 *pfn_array; > + int ret = 0; > + int i, pfn; > + > + if (!hv_is_isolation_supported() || !ms_hyperv.ghcb_base) > + return 0; > + > + pfn_array = kzalloc(HV_HYP_PAGE_SIZE, GFP_KERNEL); Does the page need to be zero'ed? All bytes that are used will be explicitly written in the loop below. > + if (!pfn_array) > + return -ENOMEM; > + > + for (i = 0, pfn = 0; i < pagecount; i++) { > + pfn_array[pfn] = virt_to_hvpfn(kbuffer + i * HV_HYP_PAGE_SIZE); > + pfn++; > + > + if (pfn == HV_MAX_MODIFY_GPA_REP_COUNT || i == pagecount - 1) { > + ret |= hv_mark_gpa_visibility(pfn, pfn_array, > + visibility); I don't see why value of "ret" is OR'ed. If the result of hv_mark_gpa_visibility() is ever non-zero, we'll exit immediately. There's no need to accumulate the results of multiple calls to hv_mark_gpa_visibility(). > + pfn = 0; > + > + if (ret) > + goto err_free_pfn_array; > + } > + } > + > + err_free_pfn_array: > + kfree(pfn_array); > + return ret; > +} > + > +/* > + * hv_set_mem_host_visibility - Set specified memory visible to host. > + * > + * In Isolation VM, all guest memory is encrypted from host and guest > + * needs to set memory visible to host via hvcall before sharing memory > + * with host. This function works as wrap of hv_mark_gpa_visibility() > + * with memory base and size. > + */ > +int hv_set_mem_host_visibility(unsigned long addr, int numpages, bool visible) > +{ > + enum hv_mem_host_visibility visibility = visible ? > + VMBUS_PAGE_VISIBLE_READ_WRITE : VMBUS_PAGE_NOT_VISIBLE; > + > + return __hv_set_mem_host_visibility((void *)addr, numpages, visibility); > +} > diff --git a/arch/x86/include/asm/hyperv-tlfs.h b/arch/x86/include/asm/hyperv-tlfs.h > index 2322d6bd5883..1691d2bce0b7 100644 > --- a/arch/x86/include/asm/hyperv-tlfs.h > +++ b/arch/x86/include/asm/hyperv-tlfs.h > @@ -276,6 +276,13 @@ enum hv_isolation_type { > #define HV_X64_MSR_TIME_REF_COUNT HV_REGISTER_TIME_REF_COUNT > #define HV_X64_MSR_REFERENCE_TSC HV_REGISTER_REFERENCE_TSC > > +/* Hyper-V memory host visibility */ > +enum hv_mem_host_visibility { > + VMBUS_PAGE_NOT_VISIBLE = 0, > + VMBUS_PAGE_VISIBLE_READ_ONLY = 1, > + VMBUS_PAGE_VISIBLE_READ_WRITE = 3 > +}; > + > /* > * Declare the MSR used to setup pages used to communicate with the hypervisor. > */ > @@ -587,4 +594,17 @@ enum hv_interrupt_type { > > #include <asm-generic/hyperv-tlfs.h> > > +/* All input parameters should be in single page. */ > +#define HV_MAX_MODIFY_GPA_REP_COUNT \ > + ((PAGE_SIZE / sizeof(u64)) - 2) > + > +/* HvCallModifySparseGpaPageHostVisibility hypercall */ > +struct hv_gpa_range_for_visibility { > + u64 partition_id; > + u32 host_visibility:2; > + u32 reserved0:30; > + u32 reserved1; > + u64 gpa_page_list[HV_MAX_MODIFY_GPA_REP_COUNT]; > +} __packed; > + We should avoid adding definitions *after* the #include of <asm-generic/hyperv-tlfs.h>. That #include should be last. Any reason these can't go earlier? And they really go together with enum hv_mem_host_visibility. Separately, take a look at how the structure hv_memory_hint and HV_MEMORY_HINT_MAX_GPA_PAGE_RANGES is handled. It's a close parallel to what you are doing above, and is a slightly cleaner approach. > #endif > diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h > index 6627cfd2bfba..87a386fa97f7 100644 > --- a/arch/x86/include/asm/mshyperv.h > +++ b/arch/x86/include/asm/mshyperv.h > @@ -190,7 +190,9 @@ struct irq_domain *hv_create_pci_msi_domain(void); > int hv_map_ioapic_interrupt(int ioapic_id, bool level, int vcpu, int vector, > struct hv_interrupt_entry *entry); > int hv_unmap_ioapic_interrupt(int ioapic_id, struct hv_interrupt_entry *entry); > - > +int hv_mark_gpa_visibility(u16 count, const u64 pfn[], > + enum hv_mem_host_visibility visibility); > +int hv_set_mem_host_visibility(unsigned long addr, int numpages, bool visible); > #else /* CONFIG_HYPERV */ > static inline void hyperv_init(void) {} > static inline void hyperv_setup_mmu_ops(void) {} > diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c > index ad8a5c586a35..1e4a0882820a 100644 > --- a/arch/x86/mm/pat/set_memory.c > +++ b/arch/x86/mm/pat/set_memory.c > @@ -29,6 +29,8 @@ > #include <asm/proto.h> > #include <asm/memtype.h> > #include <asm/set_memory.h> > +#include <asm/hyperv-tlfs.h> > +#include <asm/mshyperv.h> > > #include "../mm_internal.h" > > @@ -1980,15 +1982,11 @@ int set_memory_global(unsigned long addr, int numpages) > __pgprot(_PAGE_GLOBAL), 0); > } > > -static int __set_memory_enc_dec(unsigned long addr, int numpages, bool enc) > +static int __set_memory_enc_pgtable(unsigned long addr, int numpages, bool enc) > { > struct cpa_data cpa; > int ret; > > - /* Nothing to do if memory encryption is not active */ > - if (!mem_encrypt_active()) > - return 0; > - > /* Should not be working on unaligned addresses */ > if (WARN_ONCE(addr & ~PAGE_MASK, "misaligned address: %#lx\n", addr)) > addr &= PAGE_MASK; > @@ -2023,6 +2021,17 @@ static int __set_memory_enc_dec(unsigned long addr, int numpages, bool enc) > return ret; > } > > +static int __set_memory_enc_dec(unsigned long addr, int numpages, bool enc) > +{ > + if (hv_is_isolation_supported()) > + return hv_set_mem_host_visibility(addr, numpages, !enc); > + > + if (mem_encrypt_active()) > + return __set_memory_enc_pgtable(addr, numpages, enc); > + > + return 0; > +} > + > int set_memory_encrypted(unsigned long addr, int numpages) > { > return __set_memory_enc_dec(addr, numpages, true); > diff --git a/include/asm-generic/hyperv-tlfs.h b/include/asm-generic/hyperv-tlfs.h > index 56348a541c50..8ed6733d5146 100644 > --- a/include/asm-generic/hyperv-tlfs.h > +++ b/include/asm-generic/hyperv-tlfs.h > @@ -158,6 +158,7 @@ struct ms_hyperv_tsc_page { > #define HVCALL_RETARGET_INTERRUPT 0x007e > #define HVCALL_FLUSH_GUEST_PHYSICAL_ADDRESS_SPACE 0x00af > #define HVCALL_FLUSH_GUEST_PHYSICAL_ADDRESS_LIST 0x00b0 > +#define HVCALL_MODIFY_SPARSE_GPA_PAGE_HOST_VISIBILITY 0x00db > > /* Extended hypercalls */ > #define HV_EXT_CALL_QUERY_CAPABILITIES 0x8001 > diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h > index aa26d24a5ca9..079988ed45b9 100644 > --- a/include/asm-generic/mshyperv.h > +++ b/include/asm-generic/mshyperv.h > @@ -255,6 +255,7 @@ bool hv_query_ext_cap(u64 cap_query); > static inline bool hv_is_hyperv_initialized(void) { return false; } > static inline bool hv_is_hibernation_supported(void) { return false; } > static inline void hyperv_cleanup(void) {} > +static inline hv_is_isolation_supported(void); > #endif /* CONFIG_HYPERV */ > > #endif > -- > 2.25.1