From: Tianyu Lan <hidden> Date: 2021-08-27 17:21:28
From: Tianyu Lan <redacted>
Hyper-V provides two kinds of Isolation VMs. VBS(Virtualization-based
security) and AMD SEV-SNP unenlightened Isolation VMs. This patchset
is to add support for these Isolation VM support in Linux.
The memory of these vms are encrypted and host can't access guest
memory directly. Hyper-V provides new host visibility hvcall and
the guest needs to call new hvcall to mark memory visible to host
before sharing memory with host. For security, all network/storage
stack memory should not be shared with host and so there is bounce
buffer requests.
Vmbus channel ring buffer already plays bounce buffer role because
all data from/to host needs to copy from/to between the ring buffer
and IO stack memory. So mark vmbus channel ring buffer visible.
There are two exceptions - packets sent by vmbus_sendpacket_
pagebuffer() and vmbus_sendpacket_mpb_desc(). These packets
contains IO stack memory address and host will access these memory.
So add allocation bounce buffer support in vmbus for these packets.
For SNP isolation VM, guest needs to access the shared memory via
extra address space which is specified by Hyper-V CPUID HYPERV_CPUID_
ISOLATION_CONFIG. The access physical address of the shared memory
should be bounce buffer memory GPA plus with shared_gpa_boundary
reported by CPUID.
This patchset is based on the Hyper-V next branch.
Change since V3:
- Initalize GHCB page in the cpu init callbac.
- Change vmbus_teardown_gpadl() parameter in order to
mask the memory back to non-visible to host.
- Merge hv_ringbuffer_post_init() into hv_ringbuffer_init().
- Keep Hyper-V bounce buffer size as same as AMD SEV VM
- Use dma_map_sg() instead of dm_map_page() in the storvsc driver.
Change since V2:
- Drop x86_set_memory_enc static call and use platform check
in the __set_memory_enc_dec() to run platform callback of
set memory encrypted or decrypted.
Change since V1:
- Introduce x86_set_memory_enc static call and so platforms can
override __set_memory_enc_dec() with their implementation
- Introduce sev_es_ghcb_hv_call_simple() and share code
between SEV and Hyper-V code.
- Not remap monitor pages in the non-SNP isolation VM
- Make swiotlb_init_io_tlb_mem() return error code and return
error when dma_map_decrypted() fails.
Change since RFC V4:
- Introduce dma map decrypted function to remap bounce buffer
and provide dma map decrypted ops for platform to hook callback.
- Split swiotlb and dma map decrypted change into two patches
- Replace vstart with vaddr in swiotlb changes.
Change since RFC v3:
- Add interface set_memory_decrypted_map() to decrypt memory and
map bounce buffer in extra address space
- Remove swiotlb remap function and store the remap address
returned by set_memory_decrypted_map() in swiotlb mem data structure.
- Introduce hv_set_mem_enc() to make code more readable in the __set_memory_enc_dec().
Change since RFC v2:
- Remove not UIO driver in Isolation VM patch
- Use vmap_pfn() to replace ioremap_page_range function in
order to avoid exposing symbol ioremap_page_range() and
ioremap_page_range()
- Call hv set mem host visibility hvcall in set_memory_encrypted/decrypted()
- Enable swiotlb force mode instead of adding Hyper-V dma map/unmap hook
- Fix code style
Tianyu Lan (13):
x86/hyperv: Initialize GHCB page in Isolation VM
x86/hyperv: Initialize shared memory boundary in the Isolation VM.
x86/hyperv: Add new hvcall guest address host visibility support
hyperv: Mark vmbus ring buffer visible to host in Isolation VM
hyperv: Add Write/Read MSR registers via ghcb page
hyperv: Add ghcb hvcall support for SNP VM
hyperv/Vmbus: Add SNP support for VMbus channel initiate message
hyperv/vmbus: Initialize VMbus ring buffer for Isolation VM
DMA: Add dma_map_decrypted/dma_unmap_encrypted() function
x86/Swiotlb: Add Swiotlb bounce buffer remap function for HV IVM
hyperv/IOMMU: Enable swiotlb bounce buffer for Isolation VM
hv_netvsc: Add Isolation VM support for netvsc driver
hv_storvsc: Add Isolation VM support for storvsc driver
arch/arm64/include/asm/mshyperv.h | 23 ++
arch/x86/hyperv/Makefile | 2 +-
arch/x86/hyperv/hv_init.c | 78 +++++--
arch/x86/hyperv/ivm.c | 325 +++++++++++++++++++++++++++++
arch/x86/include/asm/hyperv-tlfs.h | 17 ++
arch/x86/include/asm/mshyperv.h | 88 +++++++-
arch/x86/include/asm/sev.h | 3 +
arch/x86/kernel/cpu/mshyperv.c | 5 +
arch/x86/kernel/sev-shared.c | 63 +++---
arch/x86/mm/mem_encrypt.c | 3 +-
arch/x86/mm/pat/set_memory.c | 19 +-
arch/x86/xen/pci-swiotlb-xen.c | 3 +-
drivers/hv/Kconfig | 1 +
drivers/hv/channel.c | 55 +++--
drivers/hv/connection.c | 81 ++++++-
drivers/hv/hv.c | 120 +++++++----
drivers/hv/hv_common.c | 12 ++
drivers/hv/hyperv_vmbus.h | 1 +
drivers/hv/ring_buffer.c | 56 +++--
drivers/hv/vmbus_drv.c | 4 +
drivers/iommu/hyperv-iommu.c | 61 ++++++
drivers/net/hyperv/hyperv_net.h | 6 +
drivers/net/hyperv/netvsc.c | 151 +++++++++++++-
drivers/net/hyperv/rndis_filter.c | 2 +
drivers/scsi/storvsc_drv.c | 41 ++--
drivers/uio/uio_hv_generic.c | 14 +-
include/asm-generic/hyperv-tlfs.h | 1 +
include/asm-generic/mshyperv.h | 19 +-
include/linux/dma-map-ops.h | 9 +
include/linux/hyperv.h | 15 +-
include/linux/swiotlb.h | 4 +
kernel/dma/mapping.c | 22 ++
kernel/dma/swiotlb.c | 32 ++-
33 files changed, 1166 insertions(+), 170 deletions(-)
create mode 100644 arch/x86/hyperv/ivm.c
--
2.25.1
From: Tianyu Lan <hidden> Date: 2021-08-27 17:21:31
From: Tianyu Lan <redacted>
Hyperv exposes GHCB page via SEV ES GHCB MSR for SNP guest
to communicate with hypervisor. Map GHCB page for all
cpus to read/write MSR register and submit hvcall request
via ghcb page.
Signed-off-by: Tianyu Lan <redacted>
---
Chagne since v3:
* Rename ghcb_base to hv_ghcb_pg and move it out of
struct ms_hyperv_info.
* Allocate hv_ghcb_pg before cpuhp_setup_state() and leverage
hv_cpu_init() to initialize ghcb page.
---
arch/x86/hyperv/hv_init.c | 68 +++++++++++++++++++++++++++++----
arch/x86/include/asm/mshyperv.h | 4 ++
arch/x86/kernel/cpu/mshyperv.c | 3 ++
include/asm-generic/mshyperv.h | 1 +
4 files changed, 69 insertions(+), 7 deletions(-)
@@ -36,12 +37,42 @@ EXPORT_SYMBOL_GPL(hv_current_partition_id);void*hv_hypercall_pg;EXPORT_SYMBOL_GPL(hv_hypercall_pg);+void__percpu**hv_ghcb_pg;+/* Storage to save the hypercall page temporarily for hibernation */staticvoid*hv_hypercall_pg_saved;structhv_vp_assist_page**hv_vp_assist_page;EXPORT_SYMBOL_GPL(hv_vp_assist_page);+staticinthyperv_init_ghcb(void)+{+u64ghcb_gpa;+void*ghcb_va;+void**ghcb_base;++if(!hv_isolation_type_snp())+return0;++if(!hv_ghcb_pg)+return-EINVAL;++/*+*GHCBpageisallocatedbyparavisor.Theaddress+*returnedbyMSR_AMD64_SEV_ES_GHCBisaboveshared+*ghcbboundaryandmapithere.+*/+rdmsrl(MSR_AMD64_SEV_ES_GHCB,ghcb_gpa);+ghcb_va=memremap(ghcb_gpa,HV_HYP_PAGE_SIZE,MEMREMAP_WB);+if(!ghcb_va)+return-ENOMEM;++ghcb_base=(void**)this_cpu_ptr(hv_ghcb_pg);+*ghcb_base=ghcb_va;++return0;+}+staticinthv_cpu_init(unsignedintcpu){unionhv_vp_assist_msr_contentsmsr={0};
@@ -85,7 +116,7 @@ static int hv_cpu_init(unsigned int cpu)}}-return0;+returnhyperv_init_ghcb();}staticvoid(*hv_reenlightenment_cb)(void);
@@ -177,6 +208,14 @@ static int hv_cpu_die(unsigned int cpu){structhv_reenlightenment_controlre_ctrl;unsignedintnew_cpu;+void**ghcb_va;++if(hv_ghcb_pg){+ghcb_va=(void**)this_cpu_ptr(hv_ghcb_pg);+if(*ghcb_va)+memunmap(*ghcb_va);+*ghcb_va=NULL;+}hv_common_cpu_die(cpu);
@@ -316,6 +316,9 @@ static void __init ms_hyperv_init_platform(void)pr_info("Hyper-V: Isolation Config: Group A 0x%x, Group B 0x%x\n",ms_hyperv.isolation_config_a,ms_hyperv.isolation_config_b);++if(hv_get_isolation_type()==HV_ISOLATION_TYPE_SNP)+static_branch_enable(&isolation_type_snp);}if(hv_max_functions_eax>=HYPERV_CPUID_NESTED_FEATURES){
From: Tianyu Lan <hidden> Date: 2021-08-27 17:21:42
From: Tianyu Lan <redacted>
Hyper-V exposes shared memory boundary via cpuid
HYPERV_CPUID_ISOLATION_CONFIG and store it in the
shared_gpa_boundary of ms_hyperv struct. This prepares
to share memory with host for SNP guest.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* user BIT_ULL to get shared_gpa_boundary
* Rename field Reserved* to reserved
---
arch/x86/kernel/cpu/mshyperv.c | 2 ++
include/asm-generic/mshyperv.h | 12 +++++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
@@ -313,6 +313,8 @@ static void __init ms_hyperv_init_platform(void)if(ms_hyperv.priv_high&HV_ISOLATION){ms_hyperv.isolation_config_a=cpuid_eax(HYPERV_CPUID_ISOLATION_CONFIG);ms_hyperv.isolation_config_b=cpuid_ebx(HYPERV_CPUID_ISOLATION_CONFIG);+ms_hyperv.shared_gpa_boundary=+BIT_ULL(ms_hyperv.shared_gpa_boundary_bits);pr_info("Hyper-V: Isolation Config: Group A 0x%x, Group B 0x%x\n",ms_hyperv.isolation_config_a,ms_hyperv.isolation_config_b);
From: Tianyu Lan <hidden> Date: 2021-08-27 17:21:44
From: Tianyu Lan <redacted>
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.
Acked-by: Dave Hansen <redacted>
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Fix error code handle in the __hv_set_mem_host_visibility().
* Move HvCallModifySparseGpaPageHostVisibility near to enum
hv_mem_host_visibility.
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 | 113 +++++++++++++++++++++++++++++
arch/x86/include/asm/hyperv-tlfs.h | 17 +++++
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, 156 insertions(+), 7 deletions(-)
create mode 100644 arch/x86/hyperv/ivm.c
@@ -1980,15 +1982,11 @@ int set_memory_global(unsigned long addr, int numpages)__pgprot(_PAGE_GLOBAL),0);}-staticint__set_memory_enc_dec(unsignedlongaddr,intnumpages,boolenc)+staticint__set_memory_enc_pgtable(unsignedlongaddr,intnumpages,boolenc){structcpa_datacpa;intret;-/* Nothing to do if memory encryption is not active */-if(!mem_encrypt_active())-return0;-/* 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)returnret;}+staticint__set_memory_enc_dec(unsignedlongaddr,intnumpages,boolenc)+{+if(hv_is_isolation_supported())+returnhv_set_mem_host_visibility(addr,numpages,!enc);++if(mem_encrypt_active())+return__set_memory_enc_pgtable(addr,numpages,enc);++return0;+}+intset_memory_encrypted(unsignedlongaddr,intnumpages){return__set_memory_enc_dec(addr,numpages,true);
From: Tianyu Lan <hidden> Date: 2021-08-27 17:21:49
From: Tianyu Lan <redacted>
The monitor pages in the CHANNELMSG_INITIATE_CONTACT msg are shared
with host in Isolation VM and so it's necessary to use hvcall to set
them visible to host. In Isolation VM with AMD SEV SNP, the access
address should be in the extra space which is above shared gpa
boundary. So remap these pages into the extra address(pa +
shared_gpa_boundary).
Introduce monitor_pages_original[] in the struct vmbus_connection
to store monitor page virtual address returned by hv_alloc_hyperv_
zeroed_page() and free monitor page via monitor_pages_original in
the vmbus_disconnect(). The monitor_pages[] is to used to access
monitor page and it is initialized to be equal with monitor_pages_
original. The monitor_pages[] will be overridden in the isolation VM
with va of extra address.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Rename monitor_pages_va with monitor_pages_original
* free monitor page via monitor_pages_original and
monitor_pages is used to access monitor page.
Change since v1:
* Not remap monitor pages in the non-SNP isolation VM.
---
drivers/hv/connection.c | 75 ++++++++++++++++++++++++++++++++++++---
drivers/hv/hyperv_vmbus.h | 1 +
2 files changed, 72 insertions(+), 4 deletions(-)
From: Tianyu Lan <hidden> Date: 2021-08-27 17:21:51
From: Tianyu Lan <redacted>
VMbus ring buffer are shared with host and it's need to
be accessed via extra address space of Isolation VM with
AMD SNP support. This patch is to map the ring buffer
address in extra address space via vmap_pfn(). Hyperv set
memory host visibility hvcall smears data in the ring buffer
and so reset the ring buffer memory to zero after mapping.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Remove hv_ringbuffer_post_init(), merge map
operation for Isolation VM into hv_ringbuffer_init()
* Call hv_ringbuffer_init() after __vmbus_establish_gpadl().
---
drivers/hv/Kconfig | 1 +
drivers/hv/channel.c | 19 +++++++-------
drivers/hv/ring_buffer.c | 56 ++++++++++++++++++++++++++++++----------
3 files changed, 54 insertions(+), 22 deletions(-)
@@ -679,15 +679,6 @@ static int __vmbus_open(struct vmbus_channel *newchannel,if(!newchannel->max_pkt_size)newchannel->max_pkt_size=VMBUS_DEFAULT_MAX_PKT_SIZE;-err=hv_ringbuffer_init(&newchannel->outbound,page,send_pages,0);-if(err)-gotoerror_clean_ring;--err=hv_ringbuffer_init(&newchannel->inbound,&page[send_pages],-recv_pages,newchannel->max_pkt_size);-if(err)-gotoerror_clean_ring;-/* Establish the gpadl for the ring buffer */newchannel->ringbuffer_gpadlhandle=0;
@@ -699,6 +690,16 @@ static int __vmbus_open(struct vmbus_channel *newchannel,if(err)gotoerror_clean_ring;+err=hv_ringbuffer_init(&newchannel->outbound,+page,send_pages,0);+if(err)+gotoerror_free_gpadl;++err=hv_ringbuffer_init(&newchannel->inbound,&page[send_pages],+recv_pages,newchannel->max_pkt_size);+if(err)+gotoerror_free_gpadl;+/* Create and init the channel open message */open_info=kzalloc(sizeof(*open_info)+sizeof(structvmbus_channel_open_channel),
@@ -192,23 +196,49 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,*Firstpageholdsstructhv_ring_buffer,dowraparoundmappingfor*therest.*/-pages_wraparound=kcalloc(page_cnt*2-1,sizeof(structpage*),-GFP_KERNEL);-if(!pages_wraparound)-return-ENOMEM;+if(hv_isolation_type_snp()){+pfn=page_to_pfn(pages)++HVPFN_DOWN(ms_hyperv.shared_gpa_boundary);-pages_wraparound[0]=pages;-for(i=0;i<2*(page_cnt-1);i++)-pages_wraparound[i+1]=&pages[i%(page_cnt-1)+1];+pfns_wraparound=kcalloc(page_cnt*2-1,+sizeof(unsignedlong),GFP_KERNEL);+if(!pfns_wraparound)+return-ENOMEM;-ring_info->ring_buffer=(structhv_ring_buffer*)-vmap(pages_wraparound,page_cnt*2-1,VM_MAP,PAGE_KERNEL);+pfns_wraparound[0]=pfn;+for(i=0;i<2*(page_cnt-1);i++)+pfns_wraparound[i+1]=pfn+i%(page_cnt-1)+1;-kfree(pages_wraparound);+ring_info->ring_buffer=(structhv_ring_buffer*)+vmap_pfn(pfns_wraparound,page_cnt*2-1,+PAGE_KERNEL);+kfree(pfns_wraparound);+if(!ring_info->ring_buffer)+return-ENOMEM;++/* Zero ring buffer after setting memory host visibility. */+memset(ring_info->ring_buffer,0x00,+HV_HYP_PAGE_SIZE*page_cnt);+}else{+pages_wraparound=kcalloc(page_cnt*2-1,+sizeof(structpage*),+GFP_KERNEL);++pages_wraparound[0]=pages;+for(i=0;i<2*(page_cnt-1);i++)+pages_wraparound[i+1]=+&pages[i%(page_cnt-1)+1];++ring_info->ring_buffer=(structhv_ring_buffer*)+vmap(pages_wraparound,page_cnt*2-1,VM_MAP,+PAGE_KERNEL);++kfree(pages_wraparound);+if(!ring_info->ring_buffer)+return-ENOMEM;+}-if(!ring_info->ring_buffer)-return-ENOMEM;ring_info->ring_buffer->read_index=ring_info->ring_buffer->write_index=0;
@@ -98,7 +98,13 @@ int hv_post_message(union hv_connection_id connection_id,aligned_msg->payload_size=payload_size;memcpy((void*)aligned_msg->payload,payload,payload_size);-status=hv_do_hypercall(HVCALL_POST_MESSAGE,aligned_msg,NULL);+if(hv_isolation_type_snp())+status=hv_ghcb_hypercall(HVCALL_POST_MESSAGE,+(void*)aligned_msg,NULL,+sizeof(structhv_input_post_message));+else+status=hv_do_hypercall(HVCALL_POST_MESSAGE,+aligned_msg,NULL);/* Preemption must remain disabled until after the hypercall*sosomeotherthreadcan'tgetscheduledontothiscpuand
From: Tianyu Lan <hidden> Date: 2021-08-27 17:22:00
From: Tianyu Lan <redacted>
Hyperv provides GHCB protocol to write Synthetic Interrupt
Controller MSR registers in Isolation VM with AMD SEV SNP
and these registers are emulated by hypervisor directly.
Hyperv requires to write SINTx MSR registers twice. First
writes MSR via GHCB page to communicate with hypervisor
and then writes wrmsr instruction to talk with paravisor
which runs in VMPL0. Guest OS ID MSR also needs to be set
via GHCB page.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v1:
* Introduce sev_es_ghcb_hv_call_simple() and share code
between SEV and Hyper-V code.
Change since v3:
* Pass old_msg_type to hv_signal_eom() as parameter.
* Use HV_REGISTER_* marcro instead of HV_X64_MSR_*
* Add hv_isolation_type_snp() weak function.
* Add maros to set syinc register in ARM code.
---
arch/arm64/include/asm/mshyperv.h | 23 ++++++
arch/x86/hyperv/hv_init.c | 36 ++--------
arch/x86/hyperv/ivm.c | 112 ++++++++++++++++++++++++++++++
arch/x86/include/asm/mshyperv.h | 80 ++++++++++++++++++++-
arch/x86/include/asm/sev.h | 3 +
arch/x86/kernel/sev-shared.c | 63 ++++++++++-------
drivers/hv/hv.c | 112 ++++++++++++++++++++----------
drivers/hv/hv_common.c | 6 ++
include/asm-generic/mshyperv.h | 4 +-
9 files changed, 345 insertions(+), 94 deletions(-)
@@ -37,7 +37,7 @@ EXPORT_SYMBOL_GPL(hv_current_partition_id);void*hv_hypercall_pg;EXPORT_SYMBOL_GPL(hv_hypercall_pg);-void__percpu**hv_ghcb_pg;+unionhv_ghcb__percpu**hv_ghcb_pg;/* Storage to save the hypercall page temporarily for hibernation */staticvoid*hv_hypercall_pg_saved;
@@ -424,6 +424,9 @@ void __init hyperv_init(void)guest_id=generate_guest_id(0,LINUX_VERSION_CODE,0);wrmsrl(HV_X64_MSR_GUEST_OS_ID,guest_id);+/* Hyper-V requires to write guest os id via ghcb in SNP IVM. */+hv_ghcb_msr_write(HV_X64_MSR_GUEST_OS_ID,guest_id);+hv_hypercall_pg=__vmalloc_node_range(PAGE_SIZE,1,VMALLOC_START,VMALLOC_END,GFP_KERNEL,PAGE_KERNEL_ROX,VM_FLUSH_RESET_PERMS,NUMA_NO_NODE,
@@ -6,13 +6,125 @@*TianyuLan<Tianyu.Lan@microsoft.com>*/+#include<linux/types.h>+#include<linux/bitfield.h>#include<linux/hyperv.h>#include<linux/types.h>#include<linux/bitfield.h>#include<linux/slab.h>+#include<asm/svm.h>+#include<asm/sev.h>#include<asm/io.h>#include<asm/mshyperv.h>+unionhv_ghcb{+structghcbghcb;+}__packed__aligned(HV_HYP_PAGE_SIZE);++voidhv_ghcb_msr_write(u64msr,u64value)+{+unionhv_ghcb*hv_ghcb;+void**ghcb_base;+unsignedlongflags;++if(!hv_ghcb_pg)+return;++WARN_ON(in_nmi());++local_irq_save(flags);+ghcb_base=(void**)this_cpu_ptr(hv_ghcb_pg);+hv_ghcb=(unionhv_ghcb*)*ghcb_base;+if(!hv_ghcb){+local_irq_restore(flags);+return;+}++ghcb_set_rcx(&hv_ghcb->ghcb,msr);+ghcb_set_rax(&hv_ghcb->ghcb,lower_32_bits(value));+ghcb_set_rdx(&hv_ghcb->ghcb,upper_32_bits(value));++if(sev_es_ghcb_hv_call_simple(&hv_ghcb->ghcb,SVM_EXIT_MSR,1,0))+pr_warn("Fail to write msr via ghcb %llx.\n",msr);++local_irq_restore(flags);+}++voidhv_ghcb_msr_read(u64msr,u64*value)+{+unionhv_ghcb*hv_ghcb;+void**ghcb_base;+unsignedlongflags;++/* Check size of union hv_ghcb here. */+BUILD_BUG_ON(sizeof(unionhv_ghcb)!=HV_HYP_PAGE_SIZE);++if(!hv_ghcb_pg)+return;++WARN_ON(in_nmi());++local_irq_save(flags);+ghcb_base=(void**)this_cpu_ptr(hv_ghcb_pg);+hv_ghcb=(unionhv_ghcb*)*ghcb_base;+if(!hv_ghcb){+local_irq_restore(flags);+return;+}++ghcb_set_rcx(&hv_ghcb->ghcb,msr);+if(sev_es_ghcb_hv_call_simple(&hv_ghcb->ghcb,SVM_EXIT_MSR,0,0))+pr_warn("Fail to read msr via ghcb %llx.\n",msr);+else+*value=(u64)lower_32_bits(hv_ghcb->ghcb.save.rax)+|((u64)lower_32_bits(hv_ghcb->ghcb.save.rdx)<<32);+local_irq_restore(flags);+}++voidhv_sint_rdmsrl_ghcb(u64msr,u64*value)+{+hv_ghcb_msr_read(msr,value);+}+EXPORT_SYMBOL_GPL(hv_sint_rdmsrl_ghcb);++voidhv_sint_wrmsrl_ghcb(u64msr,u64value)+{+hv_ghcb_msr_write(msr,value);++/* Write proxy bit vua wrmsrl instruction. */+if(msr>=HV_X64_MSR_SINT0&&msr<=HV_X64_MSR_SINT15)+wrmsrl(msr,value|1<<20);+}+EXPORT_SYMBOL_GPL(hv_sint_wrmsrl_ghcb);++enumhv_isolation_typehv_get_isolation_type(void)+{+if(!(ms_hyperv.priv_high&HV_ISOLATION))+returnHV_ISOLATION_TYPE_NONE;+returnFIELD_GET(HV_ISOLATION_TYPE,ms_hyperv.isolation_config_b);+}+EXPORT_SYMBOL_GPL(hv_get_isolation_type);++/*+*hv_is_isolation_supported-ChecksystemrunsintheHyper-V+*isolationVM.+*/+boolhv_is_isolation_supported(void)+{+returnhv_get_isolation_type()!=HV_ISOLATION_TYPE_NONE;+}++DEFINE_STATIC_KEY_FALSE(isolation_type_snp);++/*+*hv_isolation_type_snp-ChecksystemrunsintheAMDSEV-SNPbased+*isolationVM.+*/+boolhv_isolation_type_snp(void)+{+returnstatic_branch_unlikely(&isolation_type_snp);+}+/**hv_mark_gpa_visibility-Setpagesvisibletohostviahvcall.*
@@ -109,29 +108,45 @@ static enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb,ghcb_set_sw_exit_info_1(ghcb,exit_info_1);ghcb_set_sw_exit_info_2(ghcb,exit_info_2);-sev_es_wr_ghcb_msr(__pa(ghcb));VMGEXIT();-if((ghcb->save.sw_exit_info_1&0xffffffff)==1){-u64info=ghcb->save.sw_exit_info_2;-unsignedlongv;--info=ghcb->save.sw_exit_info_2;-v=info&SVM_EVTINJ_VEC_MASK;--/* Check if exception information from hypervisor is sane. */-if((info&SVM_EVTINJ_VALID)&&-((v==X86_TRAP_GP)||(v==X86_TRAP_UD))&&-((info&SVM_EVTINJ_TYPE_MASK)==SVM_EVTINJ_TYPE_EXEPT)){-ctxt->fi.vector=v;-if(info&SVM_EVTINJ_VALID_ERR)-ctxt->fi.error_code=info>>32;-ret=ES_EXCEPTION;-}else{-ret=ES_VMM_ERROR;-}-}else{+if((ghcb->save.sw_exit_info_1&0xffffffff)==1)+ret=ES_VMM_ERROR;+elseret=ES_OK;++returnret;+}++staticenumes_resultsev_es_ghcb_hv_call(structghcb*ghcb,+structes_em_ctxt*ctxt,+u64exit_code,u64exit_info_1,+u64exit_info_2)+{+unsignedlongv;+enumes_resultret;+u64info;++sev_es_wr_ghcb_msr(__pa(ghcb));++ret=sev_es_ghcb_hv_call_simple(ghcb,exit_code,exit_info_1,+exit_info_2);+if(ret==ES_OK)+returnret;++info=ghcb->save.sw_exit_info_2;+v=info&SVM_EVTINJ_VEC_MASK;++/* Check if exception information from hypervisor is sane. */+if((info&SVM_EVTINJ_VALID)&&+((v==X86_TRAP_GP)||(v==X86_TRAP_UD))&&+((info&SVM_EVTINJ_TYPE_MASK)==SVM_EVTINJ_TYPE_EXEPT)){+ctxt->fi.vector=v;+if(info&SVM_EVTINJ_VALID_ERR)+ctxt->fi.error_code=info>>32;+ret=ES_EXCEPTION;+}else{+ret=ES_VMM_ERROR;}returnret;
@@ -136,17 +137,24 @@ int hv_synic_alloc(void)tasklet_init(&hv_cpu->msg_dpc,vmbus_on_msg_dpc,(unsignedlong)hv_cpu);-hv_cpu->synic_message_page=-(void*)get_zeroed_page(GFP_ATOMIC);-if(hv_cpu->synic_message_page==NULL){-pr_err("Unable to allocate SYNIC message page\n");-gotoerr;-}+/*+*Synicmessageandeventpagesareallocatedbyparavisor.+*Skipthesepagesallocationhere.+*/+if(!hv_isolation_type_snp()){+hv_cpu->synic_message_page=+(void*)get_zeroed_page(GFP_ATOMIC);+if(hv_cpu->synic_message_page==NULL){+pr_err("Unable to allocate SYNIC message page\n");+gotoerr;+}-hv_cpu->synic_event_page=(void*)get_zeroed_page(GFP_ATOMIC);-if(hv_cpu->synic_event_page==NULL){-pr_err("Unable to allocate SYNIC event page\n");-gotoerr;+hv_cpu->synic_event_page=+(void*)get_zeroed_page(GFP_ATOMIC);+if(hv_cpu->synic_event_page==NULL){+pr_err("Unable to allocate SYNIC event page\n");+gotoerr;+}}hv_cpu->post_msg_page=(void*)get_zeroed_page(GFP_ATOMIC);
@@ -199,26 +207,43 @@ void hv_synic_enable_regs(unsigned int cpu)unionhv_synic_scontrolsctrl;/* Setup the Synic's message page */-simp.as_uint64=hv_get_register(HV_REGISTER_SIMP);+hv_get_simp(simp.as_uint64);simp.simp_enabled=1;-simp.base_simp_gpa=virt_to_phys(hv_cpu->synic_message_page)->>HV_HYP_PAGE_SHIFT;-hv_set_register(HV_REGISTER_SIMP,simp.as_uint64);+if(hv_isolation_type_snp()){+hv_cpu->synic_message_page+=memremap(simp.base_simp_gpa<<HV_HYP_PAGE_SHIFT,+HV_HYP_PAGE_SIZE,MEMREMAP_WB);+if(!hv_cpu->synic_message_page)+pr_err("Fail to map syinc message page.\n");+}else{+simp.base_simp_gpa=virt_to_phys(hv_cpu->synic_message_page)+>>HV_HYP_PAGE_SHIFT;+}++hv_set_simp(simp.as_uint64);/* Setup the Synic's event page */-siefp.as_uint64=hv_get_register(HV_REGISTER_SIEFP);+hv_get_siefp(siefp.as_uint64);siefp.siefp_enabled=1;-siefp.base_siefp_gpa=virt_to_phys(hv_cpu->synic_event_page)->>HV_HYP_PAGE_SHIFT;-hv_set_register(HV_REGISTER_SIEFP,siefp.as_uint64);+if(hv_isolation_type_snp()){+hv_cpu->synic_event_page=+memremap(siefp.base_siefp_gpa<<HV_HYP_PAGE_SHIFT,+HV_HYP_PAGE_SIZE,MEMREMAP_WB);++if(!hv_cpu->synic_event_page)+pr_err("Fail to map syinc event page.\n");+}else{+siefp.base_siefp_gpa=virt_to_phys(hv_cpu->synic_event_page)+>>HV_HYP_PAGE_SHIFT;+}+hv_set_siefp(siefp.as_uint64);/* Setup the shared SINT. */if(vmbus_irq!=-1)enable_percpu_irq(vmbus_irq,0);-shared_sint.as_uint64=hv_get_register(HV_REGISTER_SINT0+-VMBUS_MESSAGE_SINT);+hv_get_synint_state(VMBUS_MESSAGE_SINT,shared_sint.as_uint64);shared_sint.vector=vmbus_interrupt;shared_sint.masked=false;
@@ -233,14 +258,12 @@ void hv_synic_enable_regs(unsigned int cpu)#elseshared_sint.auto_eoi=0;#endif-hv_set_register(HV_REGISTER_SINT0+VMBUS_MESSAGE_SINT,-shared_sint.as_uint64);+hv_set_synint_state(VMBUS_MESSAGE_SINT,shared_sint.as_uint64);/* Enable the global synic bit */-sctrl.as_uint64=hv_get_register(HV_REGISTER_SCONTROL);+hv_get_synic_state(sctrl.as_uint64);sctrl.enable=1;--hv_set_register(HV_REGISTER_SCONTROL,sctrl.as_uint64);+hv_set_synic_state(sctrl.as_uint64);}inthv_synic_init(unsignedintcpu)
@@ -257,37 +280,50 @@ int hv_synic_init(unsigned int cpu)*/voidhv_synic_disable_regs(unsignedintcpu){+structhv_per_cpu_context*hv_cpu+=per_cpu_ptr(hv_context.cpu_context,cpu);unionhv_synic_sintshared_sint;unionhv_synic_simpsimp;unionhv_synic_siefpsiefp;unionhv_synic_scontrolsctrl;-shared_sint.as_uint64=hv_get_register(HV_REGISTER_SINT0+-VMBUS_MESSAGE_SINT);-+hv_get_synint_state(VMBUS_MESSAGE_SINT,shared_sint.as_uint64);shared_sint.masked=1;+hv_set_synint_state(VMBUS_MESSAGE_SINT,shared_sint.as_uint64);+/* Need to correctly cleanup in the case of SMP!!! *//* Disable the interrupt */-hv_set_register(HV_REGISTER_SINT0+VMBUS_MESSAGE_SINT,-shared_sint.as_uint64);+hv_get_simp(simp.as_uint64);-simp.as_uint64=hv_get_register(HV_REGISTER_SIMP);+/*+*InIsolationVM,simandsiefpagesareallocatedby+*paravisor.Thesepagesalsowillbeusedbykdump+*kernel.Sojustresetenablebithereandkeeppage+*addresses.+*/simp.simp_enabled=0;-simp.base_simp_gpa=0;+if(hv_isolation_type_snp())+memunmap(hv_cpu->synic_message_page);+else+simp.base_simp_gpa=0;-hv_set_register(HV_REGISTER_SIMP,simp.as_uint64);+hv_set_simp(simp.as_uint64);-siefp.as_uint64=hv_get_register(HV_REGISTER_SIEFP);+hv_get_siefp(siefp.as_uint64);siefp.siefp_enabled=0;-siefp.base_siefp_gpa=0;-hv_set_register(HV_REGISTER_SIEFP,siefp.as_uint64);+if(hv_isolation_type_snp())+memunmap(hv_cpu->synic_event_page);+else+siefp.base_siefp_gpa=0;++hv_set_siefp(siefp.as_uint64);/* Disable the global synic bit */-sctrl.as_uint64=hv_get_register(HV_REGISTER_SCONTROL);+hv_get_synic_state(sctrl.as_uint64);sctrl.enable=0;-hv_set_register(HV_REGISTER_SCONTROL,sctrl.as_uint64);+hv_set_synic_state(sctrl.as_uint64);if(vmbus_irq!=-1)disable_percpu_irq(vmbus_irq);
@@ -474,6 +475,13 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,if(ret)returnret;+ret=set_memory_decrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+if(ret){+pr_warn("Failed to set host visibility for new GPADL %d.\n",ret);+returnret;+}+init_completion(&msginfo->waitevent);msginfo->waiting_channel=channel;
@@ -549,6 +557,11 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,}kfree(msginfo);++if(ret)+set_memory_encrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+returnret;}
@@ -639,6 +652,7 @@ static int __vmbus_open(struct vmbus_channel *newchannel,structvmbus_channel_open_channel*open_msg;structvmbus_channel_msginfo*open_info=NULL;structpage*page=newchannel->ringbuffer_page;+structvmbus_gpadlgpadl;u32send_pages,recv_pages;unsignedlongflags;interr;
@@ -759,7 +773,10 @@ static int __vmbus_open(struct vmbus_channel *newchannel,error_free_info:kfree(open_info);error_free_gpadl:-vmbus_teardown_gpadl(newchannel,newchannel->ringbuffer_gpadlhandle);+gpadl.gpadl_handle=newchannel->ringbuffer_gpadlhandle;+gpadl.buffer=page_address(newchannel->ringbuffer_page);+gpadl.size=(send_pages+recv_pages)<<PAGE_SHIFT;+vmbus_teardown_gpadl(newchannel,&gpadl);newchannel->ringbuffer_gpadlhandle=0;error_clean_ring:hv_ringbuffer_cleanup(&newchannel->outbound);
@@ -825,7 +842,7 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)msg->header.msgtype=CHANNELMSG_GPADL_TEARDOWN;msg->child_relid=channel->offermsg.child_relid;-msg->gpadl=gpadl_handle;+msg->gpadl=gpadl->gpadl_handle;spin_lock_irqsave(&vmbus_connection.channelmsg_lock,flags);list_add_tail(&info->msglistentry,
@@ -859,6 +876,12 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,flags);kfree(info);++ret=set_memory_encrypted((unsignedlong)gpadl->buffer,+HVPFN_UP(gpadl->size));+if(ret)+pr_warn("Fail to set mem host visibility in GPADL teardown %d.\n",ret);+returnret;}EXPORT_SYMBOL_GPL(vmbus_teardown_gpadl);
@@ -934,8 +958,10 @@ static int vmbus_close_internal(struct vmbus_channel *channel)/* Tear down the gpadl for the channel's ring buffer */elseif(channel->ringbuffer_gpadlhandle){-ret=vmbus_teardown_gpadl(channel,-channel->ringbuffer_gpadlhandle);+gpadl.gpadl_handle=channel->ringbuffer_gpadlhandle;+gpadl.buffer=page_address(channel->ringbuffer_page);+gpadl.size=channel->ringbuffer_pagecount;+ret=vmbus_teardown_gpadl(channel,&gpadl);if(ret){pr_err("Close failed: teardown gpadl return %d\n",ret);/*
@@ -276,11 +276,14 @@ static void netvsc_teardown_recv_gpadl(struct hv_device *device,structnetvsc_device*net_device,structnet_device*ndev){+structvmbus_gpadlgpadl;intret;if(net_device->recv_buf_gpadl_handle){-ret=vmbus_teardown_gpadl(device->channel,-net_device->recv_buf_gpadl_handle);+gpadl.gpadl_handle=net_device->recv_buf_gpadl_handle;+gpadl.buffer=net_device->recv_buf;+gpadl.size=net_device->recv_buf_size;+ret=vmbus_teardown_gpadl(device->channel,&gpadl);/* If we failed here, we might as well return and have a leak*ratherthancontinueandabugchk
@@ -298,11 +301,15 @@ static void netvsc_teardown_send_gpadl(struct hv_device *device,structnetvsc_device*net_device,structnet_device*ndev){+structvmbus_gpadlgpadl;intret;if(net_device->send_buf_gpadl_handle){-ret=vmbus_teardown_gpadl(device->channel,-net_device->send_buf_gpadl_handle);+gpadl.gpadl_handle=net_device->send_buf_gpadl_handle;+gpadl.buffer=net_device->send_buf;+gpadl.size=net_device->send_buf_size;++ret=vmbus_teardown_gpadl(device->channel,&gpadl);/* If we failed here, we might as well return and have a leak*ratherthancontinueandabugchk
@@ -463,6 +470,7 @@ static int netvsc_init_buf(struct hv_device *device,ret=-ENOMEM;gotocleanup;}+net_device->send_buf_size=buf_size;/* Establish the gpadl handle for this buffer on this*channel.Note:Thiscallusesthevmbusconnectionrather
From: Tianyu Lan <hidden> Date: 2021-08-27 17:22:12
From: Tianyu Lan <redacted>
In Hyper-V Isolation VM with AMD SEV, swiotlb boucne buffer
needs to be mapped into address space above vTOM and so
introduce dma_map_decrypted/dma_unmap_encrypted() to map/unmap
bounce buffer memory. The platform can populate man/unmap callback
in the dma memory decrypted ops. The swiotlb bounce buffer
PA will be returned to driver and used for DMA address. The new
mapped virtual address is just to acess bounce buffer in the
swiotlb code. PAs passed to DMA API still have backing struct page.
Signed-off-by: Tianyu Lan <redacted>
---
include/linux/dma-map-ops.h | 9 +++++++++
kernel/dma/mapping.c | 22 ++++++++++++++++++++++
2 files changed, 31 insertions(+)
From: Tianyu Lan <hidden> Date: 2021-08-27 17:22:18
From: Tianyu Lan <redacted>
In Isolation VM with AMD SEV, bounce buffer needs to be accessed via
extra address space which is above shared_gpa_boundary
(E.G 39 bit address line) reported by Hyper-V CPUID ISOLATION_CONFIG.
The access physical address will be original physical address +
shared_gpa_boundary. The shared_gpa_boundary in the AMD SEV SNP
spec is called virtual top of memory(vTOM). Memory addresses below
vTOM are automatically treated as private while memory above
vTOM is treated as shared.
Use dma_map_decrypted() in the swiotlb code, store remap address returned
and use the remap address to copy data from/to swiotlb bounce buffer.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v1:
* Make swiotlb_init_io_tlb_mem() return error code and return
error when dma_map_decrypted() fails.
---
include/linux/swiotlb.h | 4 ++++
kernel/dma/swiotlb.c | 32 ++++++++++++++++++++++++--------
2 files changed, 28 insertions(+), 8 deletions(-)
@@ -216,7 +223,11 @@ int __init swiotlb_init_with_tbl(char *tlb, unsigned long nslabs, int verbose)panic("%s: Failed to allocate %zu bytes align=0x%lx\n",__func__,alloc_size,PAGE_SIZE);-swiotlb_init_io_tlb_mem(mem,__pa(tlb),nslabs,false);+ret=swiotlb_init_io_tlb_mem(mem,__pa(tlb),nslabs,false);+if(ret){+memblock_free(__pa(mem),alloc_size);+returnret;+}io_tlb_default_mem=mem;if(verbose)
From: Tianyu Lan <hidden> Date: 2021-08-27 17:22:23
From: Tianyu Lan <redacted>
hyperv Isolation VM requires bounce buffer support to copy
data from/to encrypted memory and so enable swiotlb force
mode to use swiotlb bounce buffer for DMA transaction.
In Isolation VM with AMD SEV, the bounce buffer needs to be
accessed via extra address space which is above shared_gpa_boundary
(E.G 39 bit address line) reported by Hyper-V CPUID ISOLATION_CONFIG.
The access physical address will be original physical address +
shared_gpa_boundary. The shared_gpa_boundary in the AMD SEV SNP
spec is called virtual top of memory(vTOM). Memory addresses below
vTOM are automatically treated as private while memory above
vTOM is treated as shared.
Swiotlb bounce buffer code calls dma_map_decrypted()
to mark bounce buffer visible to host and map it in extra
address space. Populate dma memory decrypted ops with hv
map/unmap function.
Hyper-V initalizes swiotlb bounce buffer and default swiotlb
needs to be disabled. pci_swiotlb_detect_override() and
pci_swiotlb_detect_4gb() enable the default one. To override
the setting, hyperv_swiotlb_detect() needs to run before
these detect functions which depends on the pci_xen_swiotlb_
init(). Make pci_xen_swiotlb_init() depends on the hyperv_swiotlb
_detect() to keep the order.
The map function vmap_pfn() can't work in the early place
hyperv_iommu_swiotlb_init() and so initialize swiotlb bounce
buffer in the hyperv_iommu_swiotlb_later_init().
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Get hyperv bounce bufffer size via default swiotlb
bounce buffer size function and keep default size as
same as the one in the AMD SEV VM.
---
arch/x86/hyperv/ivm.c | 28 +++++++++++++++
arch/x86/include/asm/mshyperv.h | 2 ++
arch/x86/mm/mem_encrypt.c | 3 +-
arch/x86/xen/pci-swiotlb-xen.c | 3 +-
drivers/hv/vmbus_drv.c | 3 ++
drivers/iommu/hyperv-iommu.c | 61 +++++++++++++++++++++++++++++++++
include/linux/hyperv.h | 1 +
7 files changed, 99 insertions(+), 2 deletions(-)
@@ -294,3 +294,31 @@ int hv_set_mem_host_visibility(unsigned long addr, int numpages, bool visible)return__hv_set_mem_host_visibility((void*)addr,numpages,visibility);}++/*+*hv_map_memory-mapmemorytoextraspaceintheAMDSEV-SNPIsolationVM.+*/+void*hv_map_memory(void*addr,unsignedlongsize)+{+unsignedlong*pfns=kcalloc(size/HV_HYP_PAGE_SIZE,+sizeof(unsignedlong),GFP_KERNEL);+void*vaddr;+inti;++if(!pfns)+returnNULL;++for(i=0;i<size/PAGE_SIZE;i++)+pfns[i]=virt_to_hvpfn(addr+i*PAGE_SIZE)++(ms_hyperv.shared_gpa_boundary>>PAGE_SHIFT);++vaddr=vmap_pfn(pfns,size/PAGE_SIZE,PAGE_KERNEL_IO);+kfree(pfns);++returnvaddr;+}++voidhv_unmap_memory(void*addr)+{+vunmap(addr);+}
From: Tianyu Lan <hidden> Date: 2021-08-27 17:22:27
From: Tianyu Lan <redacted>
In Isolation VM, all shared memory with host needs to mark visible
to host via hvcall. vmbus_establish_gpadl() has already done it for
netvsc rx/tx ring buffer. The page buffer used by vmbus_sendpacket_
pagebuffer() stills need to be handled. Use DMA API to map/umap
these memory during sending/receiving packet and Hyper-V swiotlb
bounce buffer dma adress will be returned. The swiotlb bounce buffer
has been masked to be visible to host during boot up.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Add comment to explain why not to use dma_map_sg()
* Fix some error handle.
---
arch/x86/hyperv/ivm.c | 1 +
drivers/net/hyperv/hyperv_net.h | 5 ++
drivers/net/hyperv/netvsc.c | 135 +++++++++++++++++++++++++++++-
drivers/net/hyperv/rndis_filter.c | 2 +
include/linux/hyperv.h | 5 ++
5 files changed, 145 insertions(+), 3 deletions(-)
@@ -1074,6 +1075,7 @@ struct netvsc_device {/* Receive buffer allocated by us but manages by NetVSP */void*recv_buf;+void*recv_original_buf;u32recv_buf_size;/* allocated bytes */u32recv_buf_gpadl_handle;u32recv_section_cnt;
@@ -1082,6 +1084,7 @@ struct netvsc_device {/* Send buffer allocated by us */void*send_buf;+void*send_original_buf;u32send_buf_size;u32send_buf_gpadl_handle;u32send_section_cnt;
@@ -347,6 +360,7 @@ static int netvsc_init_buf(struct hv_device *device,unsignedintbuf_size;size_tmap_words;inti,ret=0;+void*vaddr;/* Get receive buffer area. */buf_size=device_info->recv_sections*device_info->recv_section_size;
@@ -382,6 +396,17 @@ static int netvsc_init_buf(struct hv_device *device,gotocleanup;}+if(hv_isolation_type_snp()){+vaddr=hv_map_memory(net_device->recv_buf,buf_size);+if(!vaddr){+ret=-ENOMEM;+gotocleanup;+}++net_device->recv_original_buf=net_device->recv_buf;+net_device->recv_buf=vaddr;+}+/* Notify the NetVsp of the gpadl handle */init_packet=&net_device->channel_init_pkt;memset(init_packet,0,sizeof(structnvsp_message));
@@ -485,6 +510,17 @@ static int netvsc_init_buf(struct hv_device *device,gotocleanup;}+if(hv_isolation_type_snp()){+vaddr=hv_map_memory(net_device->send_buf,buf_size);+if(!vaddr){+ret=-ENOMEM;+gotocleanup;+}++net_device->send_original_buf=net_device->send_buf;+net_device->send_buf=vaddr;+}+/* Notify the NetVsp of the gpadl handle */init_packet=&net_device->channel_init_pkt;memset(init_packet,0,sizeof(structnvsp_message));
@@ -775,7 +811,7 @@ static void netvsc_send_tx_complete(struct net_device *ndev,/* Notify the layer above us */if(likely(skb)){-conststructhv_netvsc_packet*packet+structhv_netvsc_packet*packet=(structhv_netvsc_packet*)skb->cb;u32send_index=packet->send_buf_index;structnetvsc_stats*tx_stats;
From: Tianyu Lan <hidden> Date: 2021-08-27 17:22:34
From: Tianyu Lan <redacted>
In Isolation VM, all shared memory with host needs to mark visible
to host via hvcall. vmbus_establish_gpadl() has already done it for
storvsc rx/tx ring buffer. The page buffer used by vmbus_sendpacket_
mpb_desc() still needs to be handled. Use DMA API(dma_map_sg) to map
these memory during sending/receiving packet and return swiotlb bounce
buffer dma address. In Isolation VM, swiotlb bounce buffer is marked
to be visible to host and the swiotlb force mode is enabled.
Set device's dma min align mask to HV_HYP_PAGE_SIZE - 1 in order to
keep the original data offset in the bounce buffer.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Rplace dma_map_page with dma_map_sg()
* Use for_each_sg() to populate payload->range.pfn_array.
* Remove storvsc_dma_map macro
---
drivers/hv/vmbus_drv.c | 1 +
drivers/scsi/storvsc_drv.c | 41 +++++++++++++++-----------------------
include/linux/hyperv.h | 1 +
3 files changed, 18 insertions(+), 25 deletions(-)
@@ -1274,6 +1274,7 @@ struct hv_device {structvmbus_channel*channel;structkset*channels_kset;+structdevice_dma_parametersdma_parms;/* place holder to keep track of the dir for hv device in debugfs */structdentry*debug_dir;
@@ -474,6 +475,13 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,if(ret)returnret;+ret=set_memory_decrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+if(ret){+pr_warn("Failed to set host visibility for new GPADL %d.\n",ret);
dev_warn()? You have access to a struct device, why not use it?
same for all other instances here.
thanks,
greg k-h
On Fri, Aug 27, 2021 at 01:21:03PM -0400, Tianyu Lan wrote:
quoted hunk
From: Tianyu Lan <redacted>
Hyperv provides GHCB protocol to write Synthetic Interrupt
Controller MSR registers in Isolation VM with AMD SEV SNP
and these registers are emulated by hypervisor directly.
Hyperv requires to write SINTx MSR registers twice. First
writes MSR via GHCB page to communicate with hypervisor
and then writes wrmsr instruction to talk with paravisor
which runs in VMPL0. Guest OS ID MSR also needs to be set
via GHCB page.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v1:
* Introduce sev_es_ghcb_hv_call_simple() and share code
between SEV and Hyper-V code.
Change since v3:
* Pass old_msg_type to hv_signal_eom() as parameter.
* Use HV_REGISTER_* marcro instead of HV_X64_MSR_*
* Add hv_isolation_type_snp() weak function.
* Add maros to set syinc register in ARM code.
---
arch/arm64/include/asm/mshyperv.h | 23 ++++++
arch/x86/hyperv/hv_init.c | 36 ++--------
arch/x86/hyperv/ivm.c | 112 ++++++++++++++++++++++++++++++
arch/x86/include/asm/mshyperv.h | 80 ++++++++++++++++++++-
arch/x86/include/asm/sev.h | 3 +
arch/x86/kernel/sev-shared.c | 63 ++++++++++-------
drivers/hv/hv.c | 112 ++++++++++++++++++++----------
drivers/hv/hv_common.c | 6 ++
include/asm-generic/mshyperv.h | 4 +-
9 files changed, 345 insertions(+), 94 deletions(-)
@@ -474,6 +475,13 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,if(ret)returnret;+ret=set_memory_decrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+if(ret){+pr_warn("Failed to set host visibility for new GPADL %d.\n",ret);
dev_warn()? You have access to a struct device, why not use it?
same for all other instances here.
Yes, dav_warn() is better. Will update in the next version. Thanks.
From: Tianyu Lan <hidden> Date: 2021-08-27 17:47:04
On 8/28/2021 1:41 AM, Greg KH wrote:
On Fri, Aug 27, 2021 at 01:21:03PM -0400, Tianyu Lan wrote:
quoted
From: Tianyu Lan <redacted>
Hyperv provides GHCB protocol to write Synthetic Interrupt
Controller MSR registers in Isolation VM with AMD SEV SNP
and these registers are emulated by hypervisor directly.
Hyperv requires to write SINTx MSR registers twice. First
writes MSR via GHCB page to communicate with hypervisor
and then writes wrmsr instruction to talk with paravisor
which runs in VMPL0. Guest OS ID MSR also needs to be set
via GHCB page.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v1:
* Introduce sev_es_ghcb_hv_call_simple() and share code
between SEV and Hyper-V code.
Change since v3:
* Pass old_msg_type to hv_signal_eom() as parameter.
* Use HV_REGISTER_* marcro instead of HV_X64_MSR_*
* Add hv_isolation_type_snp() weak function.
* Add maros to set syinc register in ARM code.
---
arch/arm64/include/asm/mshyperv.h | 23 ++++++
arch/x86/hyperv/hv_init.c | 36 ++--------
arch/x86/hyperv/ivm.c | 112 ++++++++++++++++++++++++++++++
arch/x86/include/asm/mshyperv.h | 80 ++++++++++++++++++++-
arch/x86/include/asm/sev.h | 3 +
arch/x86/kernel/sev-shared.c | 63 ++++++++++-------
drivers/hv/hv.c | 112 ++++++++++++++++++++----------
drivers/hv/hv_common.c | 6 ++
include/asm-generic/mshyperv.h | 4 +-
9 files changed, 345 insertions(+), 94 deletions(-)
From: Christoph Hellwig <hch@lst.de> Date: 2021-08-30 12:01:36
Sorry for the delayed answer, but I look at the vmap_pfn usage in the
previous version and tried to come up with a better version. This
mostly untested branch:
http://git.infradead.org/users/hch/misc.git/shortlog/refs/heads/hyperv-vmap
get us there for swiotlb and the channel infrastructure I've started
looking at the network driver and didn't get anywhere due to other work.
As far as I can tell the network driver does gigantic multi-megabyte
vmalloc allocation for the send and receive buffers, which are then
passed to the hardware, but always copied to/from when interacting
with the networking stack. Did I see that right? Are these big
buffers actually required unlike the normal buffer management schemes
in other Linux network drivers?
If so I suspect the best way to allocate them is by not using vmalloc
but just discontiguous pages, and then use kmap_local_pfn where the
PFN includes the share_gpa offset when actually copying from/to the
skbs.
No problem. Thank you very much for your suggestion patches and they are
very helpful.
get us there for swiotlb and the channel infrastructure I've started
looking at the network driver and didn't get anywhere due to other work.
As far as I can tell the network driver does gigantic multi-megabyte
vmalloc allocation for the send and receive buffers, which are then
passed to the hardware, but always copied to/from when interacting
with the networking stack. Did I see that right? Are these big
buffers actually required unlike the normal buffer management schemes
in other Linux network drivers?
For send packet, netvsc tries batching packet in send buffer if
possible. It passes the original skb pages directly to
hypervisor when send buffer is not enough or packet length is larger
than section size. These packets are sent via
vmbus_sendpacket_pagebuffer() finally. Please see netvsc_send() for
detail. The following code is to check whether the packet could be
copied into send buffer. If not, the packet will be sent with original
skb pages.
1239 /* batch packets in send buffer if possible */
1240 msdp = &nvchan->msd;
1241 if (msdp->pkt)
1242 msd_len = msdp->pkt->total_data_buflen;
1243
1244 try_batch = msd_len > 0 && msdp->count < net_device->max_pkt;
1245 if (try_batch && msd_len + pktlen + net_device->pkt_align <
1246 net_device->send_section_size) {
1247 section_index = msdp->pkt->send_buf_index;
1248
1249 } else if (try_batch && msd_len + packet->rmsg_size <
1250 net_device->send_section_size) {
1251 section_index = msdp->pkt->send_buf_index;
1252 packet->cp_partial = true;
1253
1254 } else if (pktlen + net_device->pkt_align <
1255 net_device->send_section_size) {
1256 section_index =
netvsc_get_next_send_section(net_device);
1257 if (unlikely(section_index == NETVSC_INVALID_INDEX)) {
1258 ++ndev_ctx->eth_stats.tx_send_full;
1259 } else {
1260 move_pkt_msd(&msd_send, &msd_skb, msdp);
1261 msd_len = 0;
1262 }
1263 }
1264
For receive packet, the data is always copied from recv buffer.
If so I suspect the best way to allocate them is by not using vmalloc
but just discontiguous pages, and then use kmap_local_pfn where the
PFN includes the share_gpa offset when actually copying from/to the
skbs.
When netvsc needs to copy packet data to send buffer, it needs to
caculate position with section_index and send_section_size.
Please seee netvsc_copy_to_send_buf() detail. So the contiguous virtual
address of send buffer is necessary to copy data and batch packets.
From: Michael Kelley <hidden> Date: 2021-08-31 17:16:40
From: Christoph Hellwig <hch@lst.de> Sent: Monday, August 30, 2021 5:01 AM
Sorry for the delayed answer, but I look at the vmap_pfn usage in the
previous version and tried to come up with a better version. This
mostly untested branch:
http://git.infradead.org/users/hch/misc.git/shortlog/refs/heads/hyperv-vmap
get us there for swiotlb and the channel infrastructure I've started
looking at the network driver and didn't get anywhere due to other work.
As far as I can tell the network driver does gigantic multi-megabyte
vmalloc allocation for the send and receive buffers, which are then
passed to the hardware, but always copied to/from when interacting
with the networking stack. Did I see that right? Are these big
buffers actually required unlike the normal buffer management schemes
in other Linux network drivers?
If so I suspect the best way to allocate them is by not using vmalloc
but just discontiguous pages, and then use kmap_local_pfn where the
PFN includes the share_gpa offset when actually copying from/to the
skbs.
As a quick overview, I think there are four places where the
shared_gpa_boundary must be applied to adjust the guest physical
address that is used. Each requires mapping a corresponding
virtual address range. Here are the four places:
1) The so-called "monitor pages" that are a core communication
mechanism between the guest and Hyper-V. These are two single
pages, and the mapping is handled by calling memremap() for
each of the two pages. See Patch 7 of Tianyu's series.
2) The VMbus channel ring buffers. You have proposed using
your new vmap_phys_range() helper, but I don't think that works
here. More details below.
3) The network driver send and receive buffers. vmap_phys_range()
should work here.
4) The swiotlb memory used for bounce buffers. vmap_phys_range()
should work here as well.
Case #2 above does unusual mapping. The ring buffer consists of a ring
buffer header page, followed by one or more pages that are the actual
ring buffer. The pages making up the actual ring buffer are mapped
twice in succession. For example, if the ring buffer has 4 pages
(one header page and three ring buffer pages), the contiguous
virtual mapping must cover these seven pages: 0, 1, 2, 3, 1, 2, 3.
The duplicate contiguous mapping allows the code that is reading
or writing the actual ring buffer to not be concerned about wrap-around
because writing off the end of the ring buffer is automatically
wrapped-around by the mapping. The amount of data read or
written in one batch never exceeds the size of the ring buffer, and
after a batch is read or written, the read or write indices are adjusted
to put them back into the range of the first mapping of the actual
ring buffer pages. So there's method to the madness, and the
technique works pretty well. But this kind of mapping is not
amenable to using vmap_phys_range().
Michael
From: Michael Kelley <hidden> Date: 2021-09-02 00:15:10
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
quoted hunk
Hyperv exposes GHCB page via SEV ES GHCB MSR for SNP guest
to communicate with hypervisor. Map GHCB page for all
cpus to read/write MSR register and submit hvcall request
via ghcb page.
Signed-off-by: Tianyu Lan <redacted>
---
Chagne since v3:
* Rename ghcb_base to hv_ghcb_pg and move it out of
struct ms_hyperv_info.
* Allocate hv_ghcb_pg before cpuhp_setup_state() and leverage
hv_cpu_init() to initialize ghcb page.
---
arch/x86/hyperv/hv_init.c | 68 +++++++++++++++++++++++++++++----
arch/x86/include/asm/mshyperv.h | 4 ++
arch/x86/kernel/cpu/mshyperv.c | 3 ++
include/asm-generic/mshyperv.h | 1 +
4 files changed, 69 insertions(+), 7 deletions(-)
@@ -36,12 +37,42 @@ EXPORT_SYMBOL_GPL(hv_current_partition_id);void*hv_hypercall_pg;EXPORT_SYMBOL_GPL(hv_hypercall_pg);+void__percpu**hv_ghcb_pg;+/* Storage to save the hypercall page temporarily for hibernation */staticvoid*hv_hypercall_pg_saved;structhv_vp_assist_page**hv_vp_assist_page;EXPORT_SYMBOL_GPL(hv_vp_assist_page);+staticinthyperv_init_ghcb(void)+{+u64ghcb_gpa;+void*ghcb_va;+void**ghcb_base;++if(!hv_isolation_type_snp())+return0;++if(!hv_ghcb_pg)+return-EINVAL;++/*+*GHCBpageisallocatedbyparavisor.Theaddress+*returnedbyMSR_AMD64_SEV_ES_GHCBisaboveshared+*ghcbboundaryandmapithere.
I'm not sure what the "shared ghcb boundary" is. Did you
mean "shared_gpa_boundary"?
@@ -316,6 +316,9 @@ static void __init ms_hyperv_init_platform(void)pr_info("Hyper-V: Isolation Config: Group A 0x%x, Group B 0x%x\n",ms_hyperv.isolation_config_a,ms_hyperv.isolation_config_b);++if(hv_get_isolation_type()==HV_ISOLATION_TYPE_SNP)+static_branch_enable(&isolation_type_snp);}if(hv_max_functions_eax>=HYPERV_CPUID_NESTED_FEATURES){
From: Michael Kelley <hidden> Date: 2021-09-02 00:15:35
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
quoted hunk
Hyper-V exposes shared memory boundary via cpuid
HYPERV_CPUID_ISOLATION_CONFIG and store it in the
shared_gpa_boundary of ms_hyperv struct. This prepares
to share memory with host for SNP guest.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* user BIT_ULL to get shared_gpa_boundary
* Rename field Reserved* to reserved
---
arch/x86/kernel/cpu/mshyperv.c | 2 ++
include/asm-generic/mshyperv.h | 12 +++++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
@@ -313,6 +313,8 @@ static void __init ms_hyperv_init_platform(void)if(ms_hyperv.priv_high&HV_ISOLATION){ms_hyperv.isolation_config_a=cpuid_eax(HYPERV_CPUID_ISOLATION_CONFIG);ms_hyperv.isolation_config_b=cpuid_ebx(HYPERV_CPUID_ISOLATION_CONFIG);+ms_hyperv.shared_gpa_boundary=+BIT_ULL(ms_hyperv.shared_gpa_boundary_bits);pr_info("Hyper-V: Isolation Config: Group A 0x%x, Group B 0x%x\n",ms_hyperv.isolation_config_a,ms_hyperv.isolation_config_b);
I'm still curious about the "11" and "12" in the reserved
field names. Why not just "reserved1" and "reserved2"?
Having the "11" and "12" isn't wrong, but it makes one
wonder why since it's not usual. :-)
From: Michael Kelley <hidden> Date: 2021-09-02 00:16:42
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
quoted hunk
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.
Acked-by: Dave Hansen <redacted>
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Fix error code handle in the __hv_set_mem_host_visibility().
* Move HvCallModifySparseGpaPageHostVisibility near to enum
hv_mem_host_visibility.
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 | 113 +++++++++++++++++++++++++++++
arch/x86/include/asm/hyperv-tlfs.h | 17 +++++
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, 156 insertions(+), 7 deletions(-)
create mode 100644 arch/x86/hyperv/ivm.c
In later comments on Patch 7 of this series, I have suggested that
code in that patch should not call hv_mark_gpa_visibility() directly,
but instead should call set_memory_encrypted() and
set_memory_decrypted(). I'm thinking that those functions should
be the standard way to change the visibility of pages in the Isolated
VM case. Then hv_mark_gpa_visibility() could be static and it would
not need a stub version for ARM64. It would only be called by
__hv_set_mem_host_visibility below, and in turn by
set_memory_encrypted()/decrypted().
+
+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() || !hv_hypercall_pg)
+ return 0;
+
+ pfn_array = kmalloc(HV_HYP_PAGE_SIZE, GFP_KERNEL);
+ 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);
+ if (ret)
+ goto err_free_pfn_array;
+ pfn = 0;
+ }
+ }
+
+ 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);
+}
Is there a need for this wrapper function? Couldn't the handling of the host
visibility enum be folded into __hv_set_mem_host_visibility() and the initial
double underscore dropped? Maybe I missed it, but I don't see that
__hv_set_mem_host_visibility() is called anyplace else. Just trying to avoid
complexity if it isn't really needed.
@@ -1980,15 +1982,11 @@ int set_memory_global(unsigned long addr, int numpages)__pgprot(_PAGE_GLOBAL),0);}-staticint__set_memory_enc_dec(unsignedlongaddr,intnumpages,boolenc)+staticint__set_memory_enc_pgtable(unsignedlongaddr,intnumpages,boolenc){structcpa_datacpa;intret;-/* Nothing to do if memory encryption is not active */-if(!mem_encrypt_active())-return0;-/* 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)returnret;}+staticint__set_memory_enc_dec(unsignedlongaddr,intnumpages,boolenc)+{+if(hv_is_isolation_supported())+returnhv_set_mem_host_visibility(addr,numpages,!enc);++if(mem_encrypt_active())+return__set_memory_enc_pgtable(addr,numpages,enc);++return0;+}+intset_memory_encrypted(unsignedlongaddr,intnumpages){return__set_memory_enc_dec(addr,numpages,true);
@@ -474,6 +475,13 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,if(ret)returnret;+ret=set_memory_decrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+if(ret){+pr_warn("Failed to set host visibility for new GPADL %d.\n",ret);+returnret;+}+init_completion(&msginfo->waitevent);msginfo->waiting_channel=channel;
@@ -549,6 +557,11 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,}kfree(msginfo);++if(ret)+set_memory_encrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+returnret;}
@@ -639,6 +652,7 @@ static int __vmbus_open(struct vmbus_channel *newchannel,structvmbus_channel_open_channel*open_msg;structvmbus_channel_msginfo*open_info=NULL;structpage*page=newchannel->ringbuffer_page;+structvmbus_gpadlgpadl;u32send_pages,recv_pages;unsignedlongflags;interr;
@@ -759,7 +773,10 @@ static int __vmbus_open(struct vmbus_channel *newchannel,error_free_info:kfree(open_info);error_free_gpadl:-vmbus_teardown_gpadl(newchannel,newchannel->ringbuffer_gpadlhandle);+gpadl.gpadl_handle=newchannel->ringbuffer_gpadlhandle;+gpadl.buffer=page_address(newchannel->ringbuffer_page);+gpadl.size=(send_pages+recv_pages)<<PAGE_SHIFT;+vmbus_teardown_gpadl(newchannel,&gpadl);newchannel->ringbuffer_gpadlhandle=0;error_clean_ring:hv_ringbuffer_cleanup(&newchannel->outbound);
@@ -825,7 +842,7 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)msg->header.msgtype=CHANNELMSG_GPADL_TEARDOWN;msg->child_relid=channel->offermsg.child_relid;-msg->gpadl=gpadl_handle;+msg->gpadl=gpadl->gpadl_handle;spin_lock_irqsave(&vmbus_connection.channelmsg_lock,flags);list_add_tail(&info->msglistentry,
@@ -859,6 +876,12 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,flags);kfree(info);++ret=set_memory_encrypted((unsignedlong)gpadl->buffer,+HVPFN_UP(gpadl->size));+if(ret)+pr_warn("Fail to set mem host visibility in GPADL teardown %d.\n",ret);+returnret;}EXPORT_SYMBOL_GPL(vmbus_teardown_gpadl);
@@ -934,8 +958,10 @@ static int vmbus_close_internal(struct vmbus_channel *channel)/* Tear down the gpadl for the channel's ring buffer */elseif(channel->ringbuffer_gpadlhandle){-ret=vmbus_teardown_gpadl(channel,-channel->ringbuffer_gpadlhandle);+gpadl.gpadl_handle=channel->ringbuffer_gpadlhandle;+gpadl.buffer=page_address(channel->ringbuffer_page);+gpadl.size=channel->ringbuffer_pagecount;+ret=vmbus_teardown_gpadl(channel,&gpadl);if(ret){pr_err("Close failed: teardown gpadl return %d\n",ret);/*
@@ -276,11 +276,14 @@ static void netvsc_teardown_recv_gpadl(struct hv_device *device,structnetvsc_device*net_device,structnet_device*ndev){+structvmbus_gpadlgpadl;intret;if(net_device->recv_buf_gpadl_handle){-ret=vmbus_teardown_gpadl(device->channel,-net_device->recv_buf_gpadl_handle);+gpadl.gpadl_handle=net_device->recv_buf_gpadl_handle;+gpadl.buffer=net_device->recv_buf;+gpadl.size=net_device->recv_buf_size;+ret=vmbus_teardown_gpadl(device->channel,&gpadl);/* If we failed here, we might as well return and have a leak*ratherthancontinueandabugchk
@@ -298,11 +301,15 @@ static void netvsc_teardown_send_gpadl(struct hv_device *device,structnetvsc_device*net_device,structnet_device*ndev){+structvmbus_gpadlgpadl;intret;if(net_device->send_buf_gpadl_handle){-ret=vmbus_teardown_gpadl(device->channel,-net_device->send_buf_gpadl_handle);+gpadl.gpadl_handle=net_device->send_buf_gpadl_handle;+gpadl.buffer=net_device->send_buf;+gpadl.size=net_device->send_buf_size;++ret=vmbus_teardown_gpadl(device->channel,&gpadl);/* If we failed here, we might as well return and have a leak*ratherthancontinueandabugchk
@@ -463,6 +470,7 @@ static int netvsc_init_buf(struct hv_device *device,ret=-ENOMEM;gotocleanup;}+net_device->send_buf_size=buf_size;/* Establish the gpadl handle for this buffer on this*channel.Note:Thiscallusesthevmbusconnectionrather
@@ -1195,7 +1201,7 @@ extern int vmbus_establish_gpadl(struct vmbus_channel *channel,u32*gpadl_handle);externintvmbus_teardown_gpadl(structvmbus_channel*channel,-u32gpadl_handle);+structvmbus_gpadl*gpadl);voidvmbus_reset_channel_cb(structvmbus_channel*channel);--
2.25.1
This isn't quite what I had in mind in my comments on v3 of this
patch series. My idea is to store the full struct vmbus_gpadl
data structure in places where previously just the
u32 gpadl_handle was stored. Then pass around a pointer to the
struct vmbus_gpadl where previously just the gpadl_handle (or a
pointer to it) was passed. This lets __vmbus_establish_gpadl()
fill in the actual handle value as well the other info (buffer pointer
and size) that vmbus_teardown_gpadl() needs. Callers of the
gpadl functions don't need to worry about saving or finding
the right info. Most of the changes are just tweaking the references
to what is now a struct instead of a u32.
Here's a diff of what I had in mind. My version also has
vmbus_teardown_gpadl() set the handle field to zero, rather than
each caller having to do it. The code compiles, but I
have not done a runtime test. This diff is a net +21 lines of code,
whereas your v3 and v4 patches were both +51 lines of code.
@@ -474,6 +475,13 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,if(ret)returnret;+ret=set_memory_decrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+if(ret){+pr_warn("Failed to set host visibility for new GPADL %d.\n",ret);+returnret;+}+init_completion(&msginfo->waitevent);msginfo->waiting_channel=channel;
@@ -537,7 +545,9 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,}/* At this point, we received the gpadl created msg */-*gpadl_handle=gpadlmsg->gpadl;+gpadl_handle->handle=gpadlmsg->gpadl;+gpadl_handle->buffer=kbuffer;+gpadl_handle->size=size;cleanup:spin_lock_irqsave(&vmbus_connection.channelmsg_lock,flags);
@@ -549,6 +559,11 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,}kfree(msginfo);++if(ret)+set_memory_encrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+returnret;}
@@ -561,7 +576,7 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,*@gpadl_handle:somefunkything*/intvmbus_establish_gpadl(structvmbus_channel*channel,void*kbuffer,-u32size,u32*gpadl_handle)+u32size,structvmbus_gpadl*gpadl_handle){return__vmbus_establish_gpadl(channel,HV_GPADL_BUFFER,kbuffer,size,0U,gpadl_handle);
@@ -675,7 +690,7 @@ static int __vmbus_open(struct vmbus_channel *newchannel,gotoerror_clean_ring;/* Establish the gpadl for the ring buffer */-newchannel->ringbuffer_gpadlhandle=0;+newchannel->ringbuffer_gpadlhandle.handle=0;err=__vmbus_establish_gpadl(newchannel,HV_GPADL_RING,page_address(newchannel->ringbuffer_page),
@@ -701,7 +716,7 @@ static int __vmbus_open(struct vmbus_channel *newchannel,open_msg->header.msgtype=CHANNELMSG_OPENCHANNEL;open_msg->openid=newchannel->offermsg.child_relid;open_msg->child_relid=newchannel->offermsg.child_relid;-open_msg->ringbuffer_gpadlhandle=newchannel->ringbuffer_gpadlhandle;+open_msg->ringbuffer_gpadlhandle=newchannel->ringbuffer_gpadlhandle.handle;/**Theunitof->downstream_ringbuffer_pageoffsetisHV_HYP_PAGEand*theunitof->ringbuffer_send_offset(i.e.send_pages)isPAGE,so
@@ -759,8 +774,7 @@ static int __vmbus_open(struct vmbus_channel *newchannel,error_free_info:kfree(open_info);error_free_gpadl:-vmbus_teardown_gpadl(newchannel,newchannel->ringbuffer_gpadlhandle);-newchannel->ringbuffer_gpadlhandle=0;+vmbus_teardown_gpadl(newchannel,&newchannel->ringbuffer_gpadlhandle);error_clean_ring:hv_ringbuffer_cleanup(&newchannel->outbound);hv_ringbuffer_cleanup(&newchannel->inbound);
@@ -806,7 +820,7 @@ int vmbus_open(struct vmbus_channel *newchannel,/**vmbus_teardown_gpadl-TeardownthespecifiedGPADLhandle*/-intvmbus_teardown_gpadl(structvmbus_channel*channel,u32gpadl_handle)+intvmbus_teardown_gpadl(structvmbus_channel*channel,structvmbus_gpadl*gpadl){structvmbus_channel_gpadl_teardown*msg;structvmbus_channel_msginfo*info;
@@ -825,7 +839,7 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)msg->header.msgtype=CHANNELMSG_GPADL_TEARDOWN;msg->child_relid=channel->offermsg.child_relid;-msg->gpadl=gpadl_handle;+msg->gpadl=gpadl->handle;spin_lock_irqsave(&vmbus_connection.channelmsg_lock,flags);list_add_tail(&info->msglistentry,
@@ -844,6 +858,7 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)gotopost_msg_err;wait_for_completion(&info->waitevent);+gpadl->handle=0;post_msg_err:/*
@@ -859,6 +874,12 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,flags);kfree(info);++ret=set_memory_encrypted((unsignedlong)gpadl->buffer,+HVPFN_UP(gpadl->size));+if(ret)+pr_warn("Fail to set mem host visibility in GPADL teardown %d.\n",ret);+returnret;}EXPORT_SYMBOL_GPL(vmbus_teardown_gpadl);
@@ -933,9 +954,9 @@ static int vmbus_close_internal(struct vmbus_channel *channel)}/* Tear down the gpadl for the channel's ring buffer */-elseif(channel->ringbuffer_gpadlhandle){+elseif(channel->ringbuffer_gpadlhandle.handle){ret=vmbus_teardown_gpadl(channel,-channel->ringbuffer_gpadlhandle);+&channel->ringbuffer_gpadlhandle);if(ret){pr_err("Close failed: teardown gpadl return %d\n",ret);/*
@@ -943,8 +964,6 @@ static int vmbus_close_internal(struct vmbus_channel *channel)*itisperhapsbettertoleakmemory.*/}--channel->ringbuffer_gpadlhandle=0;}if(!ret)
@@ -1075,14 +1075,14 @@ struct netvsc_device {/* Receive buffer allocated by us but manages by NetVSP */void*recv_buf;u32recv_buf_size;/* allocated bytes */-u32recv_buf_gpadl_handle;+structvmbus_gpadlrecv_buf_gpadl_handle;u32recv_section_cnt;u32recv_section_size;u32recv_completion_cnt;/* Send buffer allocated by us */void*send_buf;-u32send_buf_gpadl_handle;+structvmbus_gpadlsend_buf_gpadl_handle;u32send_section_cnt;u32send_section_size;unsignedlong*send_section_map;
@@ -278,9 +278,9 @@ static void netvsc_teardown_recv_gpadl(struct hv_device *device,{intret;-if(net_device->recv_buf_gpadl_handle){+if(net_device->recv_buf_gpadl_handle.handle){ret=vmbus_teardown_gpadl(device->channel,-net_device->recv_buf_gpadl_handle);+&net_device->recv_buf_gpadl_handle);/* If we failed here, we might as well return and have a leak*ratherthancontinueandabugchk
@@ -300,9 +299,9 @@ static void netvsc_teardown_send_gpadl(struct hv_device *device,{intret;-if(net_device->send_buf_gpadl_handle){+if(net_device->send_buf_gpadl_handle.handle){ret=vmbus_teardown_gpadl(device->channel,-net_device->send_buf_gpadl_handle);+&net_device->send_buf_gpadl_handle);/* If we failed here, we might as well return and have a leak*ratherthancontinueandabugchk
The hypercall.hypercalloutput.callstatus value must be saved
in a local variable *before* the call to local_irq_restore(). Then
the local variable is the return value. Once local_irq_restore()
is called, the GHCB page could get reused.
@@ -98,7 +98,13 @@ int hv_post_message(union hv_connection_id connection_id,aligned_msg->payload_size=payload_size;memcpy((void*)aligned_msg->payload,payload,payload_size);-status=hv_do_hypercall(HVCALL_POST_MESSAGE,aligned_msg,NULL);+if(hv_isolation_type_snp())+status=hv_ghcb_hypercall(HVCALL_POST_MESSAGE,+(void*)aligned_msg,NULL,+sizeof(structhv_input_post_message));
As above, use "sizeof(*aligned_msg)".
quoted hunk
+ else
+ status = hv_do_hypercall(HVCALL_POST_MESSAGE,
+ aligned_msg, NULL);
/* Preemption must remain disabled until after the hypercall
* so some other thread can't get scheduled onto this cpu and
From: Michael Kelley <hidden> Date: 2021-09-02 00:21:33
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
Subject line tag should be "Drivers: hv: vmbus:"
quoted hunk
The monitor pages in the CHANNELMSG_INITIATE_CONTACT msg are shared
with host in Isolation VM and so it's necessary to use hvcall to set
them visible to host. In Isolation VM with AMD SEV SNP, the access
address should be in the extra space which is above shared gpa
boundary. So remap these pages into the extra address(pa +
shared_gpa_boundary).
Introduce monitor_pages_original[] in the struct vmbus_connection
to store monitor page virtual address returned by hv_alloc_hyperv_
zeroed_page() and free monitor page via monitor_pages_original in
the vmbus_disconnect(). The monitor_pages[] is to used to access
monitor page and it is initialized to be equal with monitor_pages_
original. The monitor_pages[] will be overridden in the isolation VM
with va of extra address.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Rename monitor_pages_va with monitor_pages_original
* free monitor page via monitor_pages_original and
monitor_pages is used to access monitor page.
Change since v1:
* Not remap monitor pages in the non-SNP isolation VM.
---
drivers/hv/connection.c | 75 ++++++++++++++++++++++++++++++++++++---
drivers/hv/hyperv_vmbus.h | 1 +
2 files changed, 72 insertions(+), 4 deletions(-)
@@ -104,6 +105,12 @@ int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)msg->monitor_page1=virt_to_phys(vmbus_connection.monitor_pages[0]);msg->monitor_page2=virt_to_phys(vmbus_connection.monitor_pages[1]);++if(hv_isolation_type_snp()){+msg->monitor_page1+=ms_hyperv.shared_gpa_boundary;+msg->monitor_page2+=ms_hyperv.shared_gpa_boundary;+}+msg->target_vcpu=hv_cpu_number_to_vp_number(VMBUS_CONNECT_CPU);/*
@@ -148,6 +155,35 @@ int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)return-ECONNREFUSED;}++if(hv_is_isolation_supported()){+if(hv_isolation_type_snp()){+vmbus_connection.monitor_pages[0]+=memremap(msg->monitor_page1,HV_HYP_PAGE_SIZE,+MEMREMAP_WB);+if(!vmbus_connection.monitor_pages[0])+return-ENOMEM;++vmbus_connection.monitor_pages[1]+=memremap(msg->monitor_page2,HV_HYP_PAGE_SIZE,+MEMREMAP_WB);+if(!vmbus_connection.monitor_pages[1]){+memunmap(vmbus_connection.monitor_pages[0]);+return-ENOMEM;+}+}++/*+*Setmemoryhostvisibilityhvcallsmearsmemory+*andsozeromonitorpageshere.+*/+memset(vmbus_connection.monitor_pages[0],0x00,+HV_HYP_PAGE_SIZE);+memset(vmbus_connection.monitor_pages[1],0x00,+HV_HYP_PAGE_SIZE);++}
I still find it somewhat confusing to have the handling of the
shared_gpa_boundary and memory mapping in the function for
negotiating the VMbus version. I think the code works as written,
but it would seem cleaner and easier to understand to precompute
the physical addresses and do all the mapping and memory zero'ing
in a single place in vmbus_connect(). Then the negotiate version
function can focus on doing only the version negotiation.
quoted hunk
+
return ret;
}
@@ -159,6 +195,7 @@ int vmbus_connect(void) struct vmbus_channel_msginfo *msginfo = NULL; int i, ret = 0; __u32 version;+ u64 pfn[2]; /* Initialize the vmbus connection */ vmbus_connection.conn_state = CONNECTING;
@@ -216,6 +253,21 @@ int vmbus_connect(void) goto cleanup; }+ vmbus_connection.monitor_pages_original[0]+ = vmbus_connection.monitor_pages[0];+ vmbus_connection.monitor_pages_original[1]+ = vmbus_connection.monitor_pages[1];++ if (hv_is_isolation_supported()) {+ pfn[0] = virt_to_hvpfn(vmbus_connection.monitor_pages[0]);+ pfn[1] = virt_to_hvpfn(vmbus_connection.monitor_pages[1]);+ if (hv_mark_gpa_visibility(2, pfn,+ VMBUS_PAGE_VISIBLE_READ_WRITE)) {
In Patch 4 of this series, host visibility for the specified buffer is done
by calling set_memory_decrypted()/set_memory_encrypted(). Could
the same be done here? The code would be more consistent overall
with better encapsulation. hv_mark_gpa_visibility() would not need to
be exported or need an ARM64 stub.
set_memory_decrypted()/encrypted() seem to be the primary functions
that should be used for this purpose, and they have already have the
appropriate stubs for architectures that don't support memory encryption.
From: Michael Kelley <hidden> Date: 2021-09-02 00:23:16
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
Subject tag should be "Drivers: hv: vmbus: "
quoted hunk
VMbus ring buffer are shared with host and it's need to
be accessed via extra address space of Isolation VM with
AMD SNP support. This patch is to map the ring buffer
address in extra address space via vmap_pfn(). Hyperv set
memory host visibility hvcall smears data in the ring buffer
and so reset the ring buffer memory to zero after mapping.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Remove hv_ringbuffer_post_init(), merge map
operation for Isolation VM into hv_ringbuffer_init()
* Call hv_ringbuffer_init() after __vmbus_establish_gpadl().
---
drivers/hv/Kconfig | 1 +
drivers/hv/channel.c | 19 +++++++-------
drivers/hv/ring_buffer.c | 56 ++++++++++++++++++++++++++++++----------
3 files changed, 54 insertions(+), 22 deletions(-)
@@ -679,15 +679,6 @@ static int __vmbus_open(struct vmbus_channel *newchannel,if(!newchannel->max_pkt_size)newchannel->max_pkt_size=VMBUS_DEFAULT_MAX_PKT_SIZE;-err=hv_ringbuffer_init(&newchannel->outbound,page,send_pages,0);-if(err)-gotoerror_clean_ring;--err=hv_ringbuffer_init(&newchannel->inbound,&page[send_pages],-recv_pages,newchannel->max_pkt_size);-if(err)-gotoerror_clean_ring;-/* Establish the gpadl for the ring buffer */newchannel->ringbuffer_gpadlhandle=0;
@@ -699,6 +690,16 @@ static int __vmbus_open(struct vmbus_channel *newchannel,if(err)gotoerror_clean_ring;+err=hv_ringbuffer_init(&newchannel->outbound,+page,send_pages,0);+if(err)+gotoerror_free_gpadl;++err=hv_ringbuffer_init(&newchannel->inbound,&page[send_pages],+recv_pages,newchannel->max_pkt_size);+if(err)+gotoerror_free_gpadl;+/* Create and init the channel open message */open_info=kzalloc(sizeof(*open_info)+sizeof(structvmbus_channel_open_channel),
With this patch, the code is a big "if" statement with two halves -- one
when SNP isolation is in effect, and the other when not. The SNP isolation
case does the work using PFNs with the shared_gpa_boundary added,
while the other case does the same work but using struct page. Perhaps
I'm missing something, but can both halves be combined and always
do the work using PFNs? The only difference is whether to add the
shared_gpa_boundary, and whether to zero the memory when done.
So get the starting PFN, then have an "if" statement for whether to
add the shared_gpa_boundary. Then everything else is the same.
At the end, use an "if" statement to decide whether to zero the
memory. It would really be better to have the logic in this algorithm
coded only once.
From: Michael Kelley <hidden> Date: 2021-09-02 01:27:35
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
quoted hunk
hyperv Isolation VM requires bounce buffer support to copy
data from/to encrypted memory and so enable swiotlb force
mode to use swiotlb bounce buffer for DMA transaction.
In Isolation VM with AMD SEV, the bounce buffer needs to be
accessed via extra address space which is above shared_gpa_boundary
(E.G 39 bit address line) reported by Hyper-V CPUID ISOLATION_CONFIG.
The access physical address will be original physical address +
shared_gpa_boundary. The shared_gpa_boundary in the AMD SEV SNP
spec is called virtual top of memory(vTOM). Memory addresses below
vTOM are automatically treated as private while memory above
vTOM is treated as shared.
Swiotlb bounce buffer code calls dma_map_decrypted()
to mark bounce buffer visible to host and map it in extra
address space. Populate dma memory decrypted ops with hv
map/unmap function.
Hyper-V initalizes swiotlb bounce buffer and default swiotlb
needs to be disabled. pci_swiotlb_detect_override() and
pci_swiotlb_detect_4gb() enable the default one. To override
the setting, hyperv_swiotlb_detect() needs to run before
these detect functions which depends on the pci_xen_swiotlb_
init(). Make pci_xen_swiotlb_init() depends on the hyperv_swiotlb
_detect() to keep the order.
The map function vmap_pfn() can't work in the early place
hyperv_iommu_swiotlb_init() and so initialize swiotlb bounce
buffer in the hyperv_iommu_swiotlb_later_init().
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Get hyperv bounce bufffer size via default swiotlb
bounce buffer size function and keep default size as
same as the one in the AMD SEV VM.
---
arch/x86/hyperv/ivm.c | 28 +++++++++++++++
arch/x86/include/asm/mshyperv.h | 2 ++
arch/x86/mm/mem_encrypt.c | 3 +-
arch/x86/xen/pci-swiotlb-xen.c | 3 +-
drivers/hv/vmbus_drv.c | 3 ++
drivers/iommu/hyperv-iommu.c | 61 +++++++++++++++++++++++++++++++++
include/linux/hyperv.h | 1 +
7 files changed, 99 insertions(+), 2 deletions(-)
@@ -294,3 +294,31 @@ int hv_set_mem_host_visibility(unsigned long addr, int numpages, bool visible)return__hv_set_mem_host_visibility((void*)addr,numpages,visibility);}++/*+*hv_map_memory-mapmemorytoextraspaceintheAMDSEV-SNPIsolationVM.+*/+void*hv_map_memory(void*addr,unsignedlongsize)+{+unsignedlong*pfns=kcalloc(size/HV_HYP_PAGE_SIZE,+sizeof(unsignedlong),GFP_KERNEL);
Should be PAGE_SIZE, not HV_HYP_PAGE_SIZE, since this code
only manipulates guest page tables. There's no communication with
Hyper-V that requires HV_HYP_PAGE_SIZE.
+ void *vaddr;
+ int i;
+
+ if (!pfns)
+ return NULL;
+
+ for (i = 0; i < size / PAGE_SIZE; i++)
+ pfns[i] = virt_to_hvpfn(addr + i * PAGE_SIZE) +
Use virt_to_pfn(), not virt_to_hvpfn(), for the same reason.
From: Michael Kelley <hidden> Date: 2021-09-02 02:08:47
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
Per previous comment, the Subject line tag should be "scsi: storvsc: "
quoted hunk
In Isolation VM, all shared memory with host needs to mark visible
to host via hvcall. vmbus_establish_gpadl() has already done it for
storvsc rx/tx ring buffer. The page buffer used by vmbus_sendpacket_
mpb_desc() still needs to be handled. Use DMA API(dma_map_sg) to map
these memory during sending/receiving packet and return swiotlb bounce
buffer dma address. In Isolation VM, swiotlb bounce buffer is marked
to be visible to host and the swiotlb force mode is enabled.
Set device's dma min align mask to HV_HYP_PAGE_SIZE - 1 in order to
keep the original data offset in the bounce buffer.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Rplace dma_map_page with dma_map_sg()
* Use for_each_sg() to populate payload->range.pfn_array.
* Remove storvsc_dma_map macro
---
drivers/hv/vmbus_drv.c | 1 +
drivers/scsi/storvsc_drv.c | 41 +++++++++++++++-----------------------
include/linux/hyperv.h | 1 +
3 files changed, 18 insertions(+), 25 deletions(-)
@@ -1807,10 +1811,11 @@ static int storvsc_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scmnd) payload_sz = sizeof(cmd_request->mpb); if (sg_count) {- unsigned int hvpgoff, hvpfns_to_add; unsigned long offset_in_hvpg = offset_in_hvpage(sgl->offset); unsigned int hvpg_count = HVPFN_UP(offset_in_hvpg + length);- u64 hvpfn;+ struct scatterlist *sg;+ unsigned long hvpfn, hvpfns_to_add;+ int j, i = 0; if (hvpg_count > MAX_PAGE_BUFFER_COUNT) {
@@ -1824,31 +1829,16 @@ static int storvsc_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scmnd) payload->range.len = length; payload->range.offset = offset_in_hvpg;+ if (dma_map_sg(&dev->device, sgl, sg_count,+ scmnd->sc_data_direction) == 0)+ return SCSI_MLQUEUE_DEVICE_BUSY;- for (i = 0; sgl != NULL; sgl = sg_next(sgl)) {- /*- * Init values for the current sgl entry. hvpgoff- * and hvpfns_to_add are in units of Hyper-V size- * pages. Handling the PAGE_SIZE != HV_HYP_PAGE_SIZE- * case also handles values of sgl->offset that are- * larger than PAGE_SIZE. Such offsets are handled- * even on other than the first sgl entry, provided- * they are a multiple of PAGE_SIZE.- */
Any reason not to keep this comment? It's still correct and
mentions important cases that must be handled.
There's a subtle issue here in that the number of entries in the
mapped sgl might not be the same as the number of entries prior
to the mapping. A change in the count probably never happens for
the direct DMA mapping being done here, but let's code to be
correct in the general case. Either need to refetch the value of
sg_count, or arrange to use something like for_each_sgtable_dma_sg().
+ hvpfns_to_add = HVPFN_UP(sg_dma_len(sg));
This simplification in calculating hvpnfs_to_add is not correct. Consider
the case of one sgl entry specifying a buffer of 3 Kbytes that starts at a
2K offset in the first page and runs over into the second page. This case
can happen when the physical memory for the two pages is contiguous
due to random happenstance, due to huge pages, or due to being on an
architecture like ARM64 where the guest page size may be larger than
the Hyper-V page size.
In this case, we need two Hyper-V PFNs because the buffer crosses a
Hyper-V page boundary. But the above will calculate only one PFN.
The original algorithm handles this case correctly.
+ hvpfn = HVPFN_DOWN(sg_dma_address(sg));
- /*
- * Fill the next portion of the PFN array with
- * sequential Hyper-V PFNs for the continguous physical
- * memory described by the sgl entry. The end of the
- * last sgl should be reached at the same time that
- * the PFN array is filled.
- */
Any reason not to keep this comment? It's still correct.
@@ -1274,6 +1274,7 @@ struct hv_device {structvmbus_channel*channel;structkset*channels_kset;+structdevice_dma_parametersdma_parms;/* place holder to keep track of the dir for hv device in debugfs */structdentry*debug_dir;--
From: Michael Kelley <hidden> Date: 2021-09-02 02:34:33
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
quoted hunk
In Isolation VM, all shared memory with host needs to mark visible
to host via hvcall. vmbus_establish_gpadl() has already done it for
netvsc rx/tx ring buffer. The page buffer used by vmbus_sendpacket_
pagebuffer() stills need to be handled. Use DMA API to map/umap
these memory during sending/receiving packet and Hyper-V swiotlb
bounce buffer dma adress will be returned. The swiotlb bounce buffer
has been masked to be visible to host during boot up.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* Add comment to explain why not to use dma_map_sg()
* Fix some error handle.
---
arch/x86/hyperv/ivm.c | 1 +
drivers/net/hyperv/hyperv_net.h | 5 ++
drivers/net/hyperv/netvsc.c | 135 +++++++++++++++++++++++++++++-
drivers/net/hyperv/rndis_filter.c | 2 +
include/linux/hyperv.h | 5 ++
5 files changed, 145 insertions(+), 3 deletions(-)
@@ -1074,6 +1075,7 @@ struct netvsc_device {/* Receive buffer allocated by us but manages by NetVSP */void*recv_buf;+void*recv_original_buf;u32recv_buf_size;/* allocated bytes */u32recv_buf_gpadl_handle;u32recv_section_cnt;
@@ -1082,6 +1084,7 @@ struct netvsc_device {/* Send buffer allocated by us */void*send_buf;+void*send_original_buf;u32send_buf_size;u32send_buf_gpadl_handle;u32send_section_cnt;
In patch 11, you have added a hv_unmap_memory()
function as the inverse of hv_map_memory(). Since this
buffer was mapped with hv_map_memory() and you have
added that function, the cleanup should use
hv_unmap_memory() rather than calling vunmap() directly.
+ vfree(nvdev->send_original_buf);
+ } else {
+ vfree(nvdev->send_buf);
+ }
+
kfree(nvdev->send_section_map);
for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
@@ -347,6 +360,7 @@ static int netvsc_init_buf(struct hv_device *device, unsigned int buf_size; size_t map_words; int i, ret = 0;+ void *vaddr; /* Get receive buffer area. */ buf_size = device_info->recv_sections * device_info->recv_section_size;
@@ -382,6 +396,17 @@ static int netvsc_init_buf(struct hv_device *device, goto cleanup; }+ if (hv_isolation_type_snp()) {+ vaddr = hv_map_memory(net_device->recv_buf, buf_size);
Since the netvsc driver is architecture neutral, this code also needs
to compile for ARM64. A stub will be needed for hv_map_memory()
on the ARM64 side. Same for hv_unmap_memory() as suggested
above. Or better, move hv_map_memory() and hv_unmap_memory()
to an architecture neutral module such as hv_common.c.
Or if Christop's approach of creating the vmap_phys_addr() helper
comes to fruition, that's an even better approach since it will already
handle multiple architectures.
quoted hunk
+ if (!vaddr) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ net_device->recv_original_buf = net_device->recv_buf;
+ net_device->recv_buf = vaddr;
+ }
+
/* Notify the NetVsp of the gpadl handle */
init_packet = &net_device->channel_init_pkt;
memset(init_packet, 0, sizeof(struct nvsp_message));
@@ -485,6 +510,17 @@ static int netvsc_init_buf(struct hv_device *device, goto cleanup; }+ if (hv_isolation_type_snp()) {+ vaddr = hv_map_memory(net_device->send_buf, buf_size);+ if (!vaddr) {+ ret = -ENOMEM;+ goto cleanup;+ }++ net_device->send_original_buf = net_device->send_buf;+ net_device->send_buf = vaddr;+ }+ /* Notify the NetVsp of the gpadl handle */ init_packet = &net_device->channel_init_pkt; memset(init_packet, 0, sizeof(struct nvsp_message));
@@ -955,6 +992,87 @@ static void netvsc_copy_to_send_buf(struct netvsc_device *net_device, memset(dest, 0, padding); }+void netvsc_dma_unmap(struct hv_device *hv_dev,+ struct hv_netvsc_packet *packet)+{+ u32 page_count = packet->cp_partial ?+ packet->page_buf_cnt - packet->rmsg_pgcnt :+ packet->page_buf_cnt;+ int i;++ if (!hv_is_isolation_supported())+ return;++ if (!packet->dma_range)+ return;++ for (i = 0; i < page_count; i++)+ dma_unmap_single(&hv_dev->device, packet->dma_range[i].dma,+ packet->dma_range[i].mapping_size,+ DMA_TO_DEVICE);++ kfree(packet->dma_range);+}++/* netvsc_dma_map - Map swiotlb bounce buffer with data page of+ * packet sent by vmbus_sendpacket_pagebuffer() in the Isolation+ * VM.+ *+ * In isolation VM, netvsc send buffer has been marked visible to+ * host and so the data copied to send buffer doesn't need to use+ * bounce buffer. The data pages handled by vmbus_sendpacket_pagebuffer()+ * may not be copied to send buffer and so these pages need to be+ * mapped with swiotlb bounce buffer. netvsc_dma_map() is to do+ * that. The pfns in the struct hv_page_buffer need to be converted+ * to bounce buffer's pfn. The loop here is necessary becuase the
s/becuase/because/
+ * entries in the page buffer array are not necessarily full
+ * pages of data. Each entry in the array has a separate offset and
+ * len that may be non-zero, even for entries in the middle of the
+ * array. And the entries are not physically contiguous. So each
+ * entry must be individually mapped rather than as a contiguous unit.
+ * So not use dma_map_sg() here.
+ */
+int netvsc_dma_map(struct hv_device *hv_dev,
+ struct hv_netvsc_packet *packet,
+ struct hv_page_buffer *pb)
+{
+ u32 page_count = packet->cp_partial ?
+ packet->page_buf_cnt - packet->rmsg_pgcnt :
+ packet->page_buf_cnt;
+ dma_addr_t dma;
+ int i;
+
+ if (!hv_is_isolation_supported())
+ return 0;
+
+ packet->dma_range = kcalloc(page_count,
+ sizeof(*packet->dma_range),
+ GFP_KERNEL);
+ if (!packet->dma_range)
+ return -ENOMEM;
+
+ for (i = 0; i < page_count; i++) {
+ char *src = phys_to_virt((pb[i].pfn << HV_HYP_PAGE_SHIFT)
+ + pb[i].offset);
+ u32 len = pb[i].len;
+
+ dma = dma_map_single(&hv_dev->device, src, len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(&hv_dev->device, dma)) {
+ kfree(packet->dma_range);
+ return -ENOMEM;
+ }
+
+ packet->dma_range[i].dma = dma;
+ packet->dma_range[i].mapping_size = len;
+ pb[i].pfn = dma >> HV_HYP_PAGE_SHIFT;
+ pb[i].offset = offset_in_hvpage(dma);
+ pb[i].len = len;
+ }
Just to confirm, this driver does *not* set the DMA min_align_mask
like storvsc does. So after the call to dma_map_single(), the offset
in the page could be different. That's why you are updating
the pb[i].offset value. Alternatively, you could set the DMA
min_align_mask, which would ensure the offset is unchanged.
I'm OK with either approach, though perhaps a comment is
warranted to explain, as this is a subtle issue.
From: Michael Kelley <hidden> Date: 2021-09-02 03:32:20
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
quoted hunk
Hyperv provides GHCB protocol to write Synthetic Interrupt
Controller MSR registers in Isolation VM with AMD SEV SNP
and these registers are emulated by hypervisor directly.
Hyperv requires to write SINTx MSR registers twice. First
writes MSR via GHCB page to communicate with hypervisor
and then writes wrmsr instruction to talk with paravisor
which runs in VMPL0. Guest OS ID MSR also needs to be set
via GHCB page.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v1:
* Introduce sev_es_ghcb_hv_call_simple() and share code
between SEV and Hyper-V code.
Change since v3:
* Pass old_msg_type to hv_signal_eom() as parameter.
* Use HV_REGISTER_* marcro instead of HV_X64_MSR_*
* Add hv_isolation_type_snp() weak function.
* Add maros to set syinc register in ARM code.
---
arch/arm64/include/asm/mshyperv.h | 23 ++++++
arch/x86/hyperv/hv_init.c | 36 ++--------
arch/x86/hyperv/ivm.c | 112 ++++++++++++++++++++++++++++++
arch/x86/include/asm/mshyperv.h | 80 ++++++++++++++++++++-
arch/x86/include/asm/sev.h | 3 +
arch/x86/kernel/sev-shared.c | 63 ++++++++++-------
drivers/hv/hv.c | 112 ++++++++++++++++++++----------
drivers/hv/hv_common.c | 6 ++
include/asm-generic/mshyperv.h | 4 +-
9 files changed, 345 insertions(+), 94 deletions(-)
@@ -37,7 +37,7 @@ EXPORT_SYMBOL_GPL(hv_current_partition_id);void*hv_hypercall_pg;EXPORT_SYMBOL_GPL(hv_hypercall_pg);-void__percpu**hv_ghcb_pg;+unionhv_ghcb__percpu**hv_ghcb_pg;/* Storage to save the hypercall page temporarily for hibernation */staticvoid*hv_hypercall_pg_saved;
@@ -424,6 +424,9 @@ void __init hyperv_init(void)guest_id=generate_guest_id(0,LINUX_VERSION_CODE,0);wrmsrl(HV_X64_MSR_GUEST_OS_ID,guest_id);+/* Hyper-V requires to write guest os id via ghcb in SNP IVM. */+hv_ghcb_msr_write(HV_X64_MSR_GUEST_OS_ID,guest_id);+hv_hypercall_pg=__vmalloc_node_range(PAGE_SIZE,1,VMALLOC_START,VMALLOC_END,GFP_KERNEL,PAGE_KERNEL_ROX,VM_FLUSH_RESET_PERMS,NUMA_NO_NODE,
@@ -6,13 +6,125 @@*TianyuLan<Tianyu.Lan@microsoft.com>*/+#include<linux/types.h>+#include<linux/bitfield.h>#include<linux/hyperv.h>#include<linux/types.h>#include<linux/bitfield.h>#include<linux/slab.h>+#include<asm/svm.h>+#include<asm/sev.h>#include<asm/io.h>#include<asm/mshyperv.h>+unionhv_ghcb{+structghcbghcb;+}__packed__aligned(HV_HYP_PAGE_SIZE);++voidhv_ghcb_msr_write(u64msr,u64value)+{+unionhv_ghcb*hv_ghcb;+void**ghcb_base;+unsignedlongflags;++if(!hv_ghcb_pg)+return;++WARN_ON(in_nmi());++local_irq_save(flags);+ghcb_base=(void**)this_cpu_ptr(hv_ghcb_pg);+hv_ghcb=(unionhv_ghcb*)*ghcb_base;+if(!hv_ghcb){+local_irq_restore(flags);+return;+}++ghcb_set_rcx(&hv_ghcb->ghcb,msr);+ghcb_set_rax(&hv_ghcb->ghcb,lower_32_bits(value));+ghcb_set_rdx(&hv_ghcb->ghcb,upper_32_bits(value));++if(sev_es_ghcb_hv_call_simple(&hv_ghcb->ghcb,SVM_EXIT_MSR,1,0))+pr_warn("Fail to write msr via ghcb %llx.\n",msr);++local_irq_restore(flags);+}++voidhv_ghcb_msr_read(u64msr,u64*value)+{+unionhv_ghcb*hv_ghcb;+void**ghcb_base;+unsignedlongflags;++/* Check size of union hv_ghcb here. */+BUILD_BUG_ON(sizeof(unionhv_ghcb)!=HV_HYP_PAGE_SIZE);++if(!hv_ghcb_pg)+return;++WARN_ON(in_nmi());++local_irq_save(flags);+ghcb_base=(void**)this_cpu_ptr(hv_ghcb_pg);+hv_ghcb=(unionhv_ghcb*)*ghcb_base;+if(!hv_ghcb){+local_irq_restore(flags);+return;+}++ghcb_set_rcx(&hv_ghcb->ghcb,msr);+if(sev_es_ghcb_hv_call_simple(&hv_ghcb->ghcb,SVM_EXIT_MSR,0,0))+pr_warn("Fail to read msr via ghcb %llx.\n",msr);+else+*value=(u64)lower_32_bits(hv_ghcb->ghcb.save.rax)+|((u64)lower_32_bits(hv_ghcb->ghcb.save.rdx)<<32);+local_irq_restore(flags);+}++voidhv_sint_rdmsrl_ghcb(u64msr,u64*value)+{+hv_ghcb_msr_read(msr,value);+}+EXPORT_SYMBOL_GPL(hv_sint_rdmsrl_ghcb);++voidhv_sint_wrmsrl_ghcb(u64msr,u64value)+{+hv_ghcb_msr_write(msr,value);++/* Write proxy bit vua wrmsrl instruction. */
s/vua/via/
quoted hunk
+ if (msr >= HV_X64_MSR_SINT0 && msr <= HV_X64_MSR_SINT15)
+ wrmsrl(msr, value | 1 << 20);
+}
+EXPORT_SYMBOL_GPL(hv_sint_wrmsrl_ghcb);
+
+enum hv_isolation_type hv_get_isolation_type(void)
+{
+ if (!(ms_hyperv.priv_high & HV_ISOLATION))
+ return HV_ISOLATION_TYPE_NONE;
+ return FIELD_GET(HV_ISOLATION_TYPE, ms_hyperv.isolation_config_b);
+}
+EXPORT_SYMBOL_GPL(hv_get_isolation_type);
+
+/*
+ * hv_is_isolation_supported - Check system runs in the Hyper-V
+ * isolation VM.
+ */
+bool hv_is_isolation_supported(void)
+{
+ return hv_get_isolation_type() != HV_ISOLATION_TYPE_NONE;
+}
+
+DEFINE_STATIC_KEY_FALSE(isolation_type_snp);
+
+/*
+ * hv_isolation_type_snp - Check system runs in the AMD SEV-SNP based
+ * isolation VM.
+ */
+bool hv_isolation_type_snp(void)
+{
+ return static_branch_unlikely(&isolation_type_snp);
+}
+
/*
* hv_mark_gpa_visibility - Set pages visible to host via hvcall.
*
I'm not seeing the value in the multiple layers of #define to get and set the
various syinc registers. My thought was a completely different approach, which is
to simply implement the hv_get_register() and hv_set_register() functions with
a little bit more logic. Here's my proposal. This code is not even compile tested,
but you get the idea:
static bool hv_is_synic_reg(unsigned int reg)
{
if ((reg >= HV_REGISTER_SCONTROL) &&
(reg <= HV_REGISTER_SINT15))
return true;
return false;
}
u64 hv_get_register(unsigned int reg)
{
u64 value;
if (hv_is_synic_reg(reg) && hv_isolation_type_snp())
hv_ghcb_msr_read(reg, &value);
else
rdmsrl(reg, value);
return value;
}
void hv_set_register(unsigned int reg, u64 value)
{
if (hv_is_synic_reg(reg) && hv_isolation_type_snp()) {
hv_ghcb_msr_write(reg, value);
/* Write proxy bit via wrmsl instruction */
if (reg >= HV_REGISTER_SINT0 &&
reg <= HV_REGISTER_SINT15)
wrmsrl(reg, value | 1 << 20);
} else {
wrmsrl(reg, value);
}
}
If the above code is implemented in one of the modules under arch/x86
to replace the existing implementations in arch/x86/include/asm/mshyper.h,
then it will only be built for x86/x64, and the existing code will just work
for ARM64. Architecture neutral code in hv_synic_enable_regs() and
hv_synic_disable_regs() will still need check for hv_isolation_type_snp()
and take some special actions, but the calls to hv_get_register() and
hv_set_register() can remain unchanged.
This approach seems a lot simpler to me, but maybe I'm missing
something that your current patch is doing.
Your code does have a special case for HV_REGISTER_EOM. Is
there a reason it needs to do an additional check of the old_msg_type?
I'm just not understanding why an SNP isolated VM requires
special treatment of this register.
A key point: Getting/setting any of the synthetic MSRs requires
a trap to the hypervisor. So they already not super-fast. The
only code path in Linux that is performance sensitive is setting
HV_REGISTER_STIMER0_COUNT in hv_ce_set_next_event().
That's not a synic register, so the only additional burden with the
above implementation is checking the MSR value to see if it is
in the synic range. The cost of that check is reasonable for
something that has to trap to the hypervisor anyway.
@@ -109,29 +108,45 @@ static enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb,ghcb_set_sw_exit_info_1(ghcb,exit_info_1);ghcb_set_sw_exit_info_2(ghcb,exit_info_2);-sev_es_wr_ghcb_msr(__pa(ghcb));VMGEXIT();-if((ghcb->save.sw_exit_info_1&0xffffffff)==1){-u64info=ghcb->save.sw_exit_info_2;-unsignedlongv;--info=ghcb->save.sw_exit_info_2;-v=info&SVM_EVTINJ_VEC_MASK;--/* Check if exception information from hypervisor is sane. */-if((info&SVM_EVTINJ_VALID)&&-((v==X86_TRAP_GP)||(v==X86_TRAP_UD))&&-((info&SVM_EVTINJ_TYPE_MASK)==SVM_EVTINJ_TYPE_EXEPT)){-ctxt->fi.vector=v;-if(info&SVM_EVTINJ_VALID_ERR)-ctxt->fi.error_code=info>>32;-ret=ES_EXCEPTION;-}else{-ret=ES_VMM_ERROR;-}-}else{+if((ghcb->save.sw_exit_info_1&0xffffffff)==1)+ret=ES_VMM_ERROR;+elseret=ES_OK;++returnret;+}++staticenumes_resultsev_es_ghcb_hv_call(structghcb*ghcb,+structes_em_ctxt*ctxt,+u64exit_code,u64exit_info_1,+u64exit_info_2)+{+unsignedlongv;+enumes_resultret;+u64info;++sev_es_wr_ghcb_msr(__pa(ghcb));++ret=sev_es_ghcb_hv_call_simple(ghcb,exit_code,exit_info_1,+exit_info_2);+if(ret==ES_OK)+returnret;++info=ghcb->save.sw_exit_info_2;+v=info&SVM_EVTINJ_VEC_MASK;++/* Check if exception information from hypervisor is sane. */+if((info&SVM_EVTINJ_VALID)&&+((v==X86_TRAP_GP)||(v==X86_TRAP_UD))&&+((info&SVM_EVTINJ_TYPE_MASK)==SVM_EVTINJ_TYPE_EXEPT)){+ctxt->fi.vector=v;+if(info&SVM_EVTINJ_VALID_ERR)+ctxt->fi.error_code=info>>32;+ret=ES_EXCEPTION;+}else{+ret=ES_VMM_ERROR;}returnret;
@@ -136,17 +137,24 @@ int hv_synic_alloc(void)tasklet_init(&hv_cpu->msg_dpc,vmbus_on_msg_dpc,(unsignedlong)hv_cpu);-hv_cpu->synic_message_page=-(void*)get_zeroed_page(GFP_ATOMIC);-if(hv_cpu->synic_message_page==NULL){-pr_err("Unable to allocate SYNIC message page\n");-gotoerr;-}+/*+*Synicmessageandeventpagesareallocatedbyparavisor.+*Skipthesepagesallocationhere.+*/+if(!hv_isolation_type_snp()){+hv_cpu->synic_message_page=+(void*)get_zeroed_page(GFP_ATOMIC);+if(hv_cpu->synic_message_page==NULL){+pr_err("Unable to allocate SYNIC message page\n");+gotoerr;+}-hv_cpu->synic_event_page=(void*)get_zeroed_page(GFP_ATOMIC);-if(hv_cpu->synic_event_page==NULL){-pr_err("Unable to allocate SYNIC event page\n");-gotoerr;+hv_cpu->synic_event_page=+(void*)get_zeroed_page(GFP_ATOMIC);+if(hv_cpu->synic_event_page==NULL){+pr_err("Unable to allocate SYNIC event page\n");+gotoerr;+}}hv_cpu->post_msg_page=(void*)get_zeroed_page(GFP_ATOMIC);
@@ -199,26 +207,43 @@ void hv_synic_enable_regs(unsigned int cpu)unionhv_synic_scontrolsctrl;/* Setup the Synic's message page */-simp.as_uint64=hv_get_register(HV_REGISTER_SIMP);+hv_get_simp(simp.as_uint64);simp.simp_enabled=1;-simp.base_simp_gpa=virt_to_phys(hv_cpu->synic_message_page)->>HV_HYP_PAGE_SHIFT;-hv_set_register(HV_REGISTER_SIMP,simp.as_uint64);+if(hv_isolation_type_snp()){+hv_cpu->synic_message_page+=memremap(simp.base_simp_gpa<<HV_HYP_PAGE_SHIFT,+HV_HYP_PAGE_SIZE,MEMREMAP_WB);+if(!hv_cpu->synic_message_page)+pr_err("Fail to map syinc message page.\n");+}else{+simp.base_simp_gpa=virt_to_phys(hv_cpu->synic_message_page)+>>HV_HYP_PAGE_SHIFT;+}++hv_set_simp(simp.as_uint64);/* Setup the Synic's event page */-siefp.as_uint64=hv_get_register(HV_REGISTER_SIEFP);+hv_get_siefp(siefp.as_uint64);siefp.siefp_enabled=1;-siefp.base_siefp_gpa=virt_to_phys(hv_cpu->synic_event_page)->>HV_HYP_PAGE_SHIFT;-hv_set_register(HV_REGISTER_SIEFP,siefp.as_uint64);+if(hv_isolation_type_snp()){+hv_cpu->synic_event_page=+memremap(siefp.base_siefp_gpa<<HV_HYP_PAGE_SHIFT,+HV_HYP_PAGE_SIZE,MEMREMAP_WB);++if(!hv_cpu->synic_event_page)+pr_err("Fail to map syinc event page.\n");+}else{+siefp.base_siefp_gpa=virt_to_phys(hv_cpu->synic_event_page)+>>HV_HYP_PAGE_SHIFT;+}+hv_set_siefp(siefp.as_uint64);/* Setup the shared SINT. */if(vmbus_irq!=-1)enable_percpu_irq(vmbus_irq,0);-shared_sint.as_uint64=hv_get_register(HV_REGISTER_SINT0+-VMBUS_MESSAGE_SINT);+hv_get_synint_state(VMBUS_MESSAGE_SINT,shared_sint.as_uint64);shared_sint.vector=vmbus_interrupt;shared_sint.masked=false;
@@ -233,14 +258,12 @@ void hv_synic_enable_regs(unsigned int cpu)#elseshared_sint.auto_eoi=0;#endif-hv_set_register(HV_REGISTER_SINT0+VMBUS_MESSAGE_SINT,-shared_sint.as_uint64);+hv_set_synint_state(VMBUS_MESSAGE_SINT,shared_sint.as_uint64);/* Enable the global synic bit */-sctrl.as_uint64=hv_get_register(HV_REGISTER_SCONTROL);+hv_get_synic_state(sctrl.as_uint64);sctrl.enable=1;--hv_set_register(HV_REGISTER_SCONTROL,sctrl.as_uint64);+hv_set_synic_state(sctrl.as_uint64);}inthv_synic_init(unsignedintcpu)
@@ -257,37 +280,50 @@ int hv_synic_init(unsigned int cpu)*/voidhv_synic_disable_regs(unsignedintcpu){+structhv_per_cpu_context*hv_cpu+=per_cpu_ptr(hv_context.cpu_context,cpu);unionhv_synic_sintshared_sint;unionhv_synic_simpsimp;unionhv_synic_siefpsiefp;unionhv_synic_scontrolsctrl;-shared_sint.as_uint64=hv_get_register(HV_REGISTER_SINT0+-VMBUS_MESSAGE_SINT);-+hv_get_synint_state(VMBUS_MESSAGE_SINT,shared_sint.as_uint64);shared_sint.masked=1;+hv_set_synint_state(VMBUS_MESSAGE_SINT,shared_sint.as_uint64);+/* Need to correctly cleanup in the case of SMP!!! *//* Disable the interrupt */-hv_set_register(HV_REGISTER_SINT0+VMBUS_MESSAGE_SINT,-shared_sint.as_uint64);+hv_get_simp(simp.as_uint64);-simp.as_uint64=hv_get_register(HV_REGISTER_SIMP);+/*+*InIsolationVM,simandsiefpagesareallocatedby+*paravisor.Thesepagesalsowillbeusedbykdump+*kernel.Sojustresetenablebithereandkeeppage+*addresses.+*/simp.simp_enabled=0;-simp.base_simp_gpa=0;+if(hv_isolation_type_snp())+memunmap(hv_cpu->synic_message_page);+else+simp.base_simp_gpa=0;-hv_set_register(HV_REGISTER_SIMP,simp.as_uint64);+hv_set_simp(simp.as_uint64);-siefp.as_uint64=hv_get_register(HV_REGISTER_SIEFP);+hv_get_siefp(siefp.as_uint64);siefp.siefp_enabled=0;-siefp.base_siefp_gpa=0;-hv_set_register(HV_REGISTER_SIEFP,siefp.as_uint64);+if(hv_isolation_type_snp())+memunmap(hv_cpu->synic_event_page);+else+siefp.base_siefp_gpa=0;++hv_set_siefp(siefp.as_uint64);/* Disable the global synic bit */-sctrl.as_uint64=hv_get_register(HV_REGISTER_SCONTROL);+hv_get_synic_state(sctrl.as_uint64);sctrl.enable=0;-hv_set_register(HV_REGISTER_SCONTROL,sctrl.as_uint64);+hv_set_synic_state(sctrl.as_uint64);if(vmbus_irq!=-1)disable_percpu_irq(vmbus_irq);
Just to confirm, this driver does *not* set the DMA min_align_mask
like storvsc does. So after the call to dma_map_single(), the offset
in the page could be different. That's why you are updating
the pb[i].offset value. Alternatively, you could set the DMA
min_align_mask, which would ensure the offset is unchanged.
I'm OK with either approach, though perhaps a comment is
warranted to explain, as this is a subtle issue.
On second thought, I don't think either approach is OK. The default
alignment in the swiotlb is 2K, and if the length of the data in the
buffer was 3K, the data could cross a page boundary in the bounce
buffer when it originally did not. This would break the above code
which can only deal with one page at a time. So I think the netvsc
driver also must set the DMA min_align_mask to 4K, which will
preserve the offset.
Michael
From: Tianyu Lan <hidden> Date: 2021-09-02 06:35:32
Hi Michael:
Thanks for your review.
On 9/2/2021 8:15 AM, Michael Kelley wrote:
From: Tianyu Lan <redacted> Sent: Friday, August 27, 2021 10:21 AM
quoted
Hyper-V exposes shared memory boundary via cpuid
HYPERV_CPUID_ISOLATION_CONFIG and store it in the
shared_gpa_boundary of ms_hyperv struct. This prepares
to share memory with host for SNP guest.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v3:
* user BIT_ULL to get shared_gpa_boundary
* Rename field Reserved* to reserved
---
arch/x86/kernel/cpu/mshyperv.c | 2 ++
include/asm-generic/mshyperv.h | 12 +++++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
@@ -313,6 +313,8 @@ static void __init ms_hyperv_init_platform(void)if(ms_hyperv.priv_high&HV_ISOLATION){ms_hyperv.isolation_config_a=cpuid_eax(HYPERV_CPUID_ISOLATION_CONFIG);ms_hyperv.isolation_config_b=cpuid_ebx(HYPERV_CPUID_ISOLATION_CONFIG);+ms_hyperv.shared_gpa_boundary=+BIT_ULL(ms_hyperv.shared_gpa_boundary_bits);pr_info("Hyper-V: Isolation Config: Group A 0x%x, Group B 0x%x\n",ms_hyperv.isolation_config_a,ms_hyperv.isolation_config_b);
I'm still curious about the "11" and "12" in the reserved
field names. Why not just "reserved1" and "reserved2"?
Having the "11" and "12" isn't wrong, but it makes one
wonder why since it's not usual. :-)
From: Christoph Hellwig <hch@lst.de> Date: 2021-09-02 07:51:42
On Tue, Aug 31, 2021 at 11:20:06PM +0800, Tianyu Lan wrote:
quoted
If so I suspect the best way to allocate them is by not using vmalloc
but just discontiguous pages, and then use kmap_local_pfn where the
PFN includes the share_gpa offset when actually copying from/to the
skbs.
When netvsc needs to copy packet data to send buffer, it needs to caculate
position with section_index and send_section_size.
Please seee netvsc_copy_to_send_buf() detail. So the contiguous virtual
address of send buffer is necessary to copy data and batch packets.
Actually that makes the kmap approach much easier. The phys_to_virt
can just be replaced with a kmap_local_pfn and the unmap needs to
be added. I've been mostly focussing on the receive path, which
would need a similar treatment.
From: Christoph Hellwig <hch@lst.de> Date: 2021-09-02 07:59:53
On Tue, Aug 31, 2021 at 05:16:19PM +0000, Michael Kelley wrote:
As a quick overview, I think there are four places where the
shared_gpa_boundary must be applied to adjust the guest physical
address that is used. Each requires mapping a corresponding
virtual address range. Here are the four places:
1) The so-called "monitor pages" that are a core communication
mechanism between the guest and Hyper-V. These are two single
pages, and the mapping is handled by calling memremap() for
each of the two pages. See Patch 7 of Tianyu's series.
Ah, interesting.
3) The network driver send and receive buffers. vmap_phys_range()
should work here.
Actually it won't. The problem with these buffers is that they are
physically non-contiguous allocations. We really have two sensible
options:
1) use vmap_pfn as in the current series. But in that case I think
we should get rid of the other mapping created by vmalloc. I
though a bit about finding a way to apply the offset in vmalloc
itself, but I think it would be too invasive to the normal fast
path. So the other sub-option would be to allocate the pages
manually (maybe even using high order allocations to reduce TLB
pressure) and then remap them
2) do away with the contiguous kernel mapping entirely. This means
the simple memcpy calls become loops over kmap_local_pfn. As
I just found out for the send side that would be pretty easy,
but the receive side would be more work. We'd also need to check
the performance implications.
4) The swiotlb memory used for bounce buffers. vmap_phys_range()
should work here as well.
Or memremap if it works for 1.
Case #2 above does unusual mapping. The ring buffer consists of a ring
buffer header page, followed by one or more pages that are the actual
ring buffer. The pages making up the actual ring buffer are mapped
twice in succession. For example, if the ring buffer has 4 pages
(one header page and three ring buffer pages), the contiguous
virtual mapping must cover these seven pages: 0, 1, 2, 3, 1, 2, 3.
The duplicate contiguous mapping allows the code that is reading
or writing the actual ring buffer to not be concerned about wrap-around
because writing off the end of the ring buffer is automatically
wrapped-around by the mapping. The amount of data read or
written in one batch never exceeds the size of the ring buffer, and
after a batch is read or written, the read or write indices are adjusted
to put them back into the range of the first mapping of the actual
ring buffer pages. So there's method to the madness, and the
technique works pretty well. But this kind of mapping is not
amenable to using vmap_phys_range().
Hmm. Can you point me to where this is mapped? Especially for the
classic non-isolated case where no vmap/vmalloc mapping is involved
at all?
From: Tianyu Lan <hidden> Date: 2021-09-02 11:21:38
On 9/2/2021 3:59 PM, Christoph Hellwig wrote:
On Tue, Aug 31, 2021 at 05:16:19PM +0000, Michael Kelley wrote:
quoted
As a quick overview, I think there are four places where the
shared_gpa_boundary must be applied to adjust the guest physical
address that is used. Each requires mapping a corresponding
virtual address range. Here are the four places:
1) The so-called "monitor pages" that are a core communication
mechanism between the guest and Hyper-V. These are two single
pages, and the mapping is handled by calling memremap() for
each of the two pages. See Patch 7 of Tianyu's series.
Ah, interesting.
quoted
3) The network driver send and receive buffers. vmap_phys_range()
should work here.
Actually it won't. The problem with these buffers is that they are
physically non-contiguous allocations. We really have two sensible
options:
1) use vmap_pfn as in the current series. But in that case I think
we should get rid of the other mapping created by vmalloc. I
though a bit about finding a way to apply the offset in vmalloc
itself, but I think it would be too invasive to the normal fast
path. So the other sub-option would be to allocate the pages
manually (maybe even using high order allocations to reduce TLB
pressure) and then remap them
Agree. In such case, the map for memory below shared_gpa_boundary is not
necessary. allocate_pages() is limited by MAX_ORDER and needs to be
called repeatedly to get enough memory.
2) do away with the contiguous kernel mapping entirely. This means
the simple memcpy calls become loops over kmap_local_pfn. As
I just found out for the send side that would be pretty easy,
but the receive side would be more work. We'd also need to check
the performance implications.
kmap_local_pfn() requires pfn with backing struct page and this doesn't
work pfn above shared_gpa_boundary.
quoted
4) The swiotlb memory used for bounce buffers. vmap_phys_range()
should work here as well.
Or memremap if it works for 1.
Now use vmap_pfn() and the hv map function is reused in the netvsc driver.
quoted
Case #2 above does unusual mapping. The ring buffer consists of a ring
buffer header page, followed by one or more pages that are the actual
ring buffer. The pages making up the actual ring buffer are mapped
twice in succession. For example, if the ring buffer has 4 pages
(one header page and three ring buffer pages), the contiguous
virtual mapping must cover these seven pages: 0, 1, 2, 3, 1, 2, 3.
The duplicate contiguous mapping allows the code that is reading
or writing the actual ring buffer to not be concerned about wrap-around
because writing off the end of the ring buffer is automatically
wrapped-around by the mapping. The amount of data read or
written in one batch never exceeds the size of the ring buffer, and
after a batch is read or written, the read or write indices are adjusted
to put them back into the range of the first mapping of the actual
ring buffer pages. So there's method to the madness, and the
technique works pretty well. But this kind of mapping is not
amenable to using vmap_phys_range().
Hmm. Can you point me to where this is mapped? Especially for the
classic non-isolated case where no vmap/vmalloc mapping is involved
at all?
This is done via vmap() in the hv_ringbuffer_init()
182/* Initialize the ring buffer. */
183int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
184 struct page *pages, u32 page_cnt, u32
max_pkt_size)
185{
186 int i;
187 struct page **pages_wraparound;
188
189 BUILD_BUG_ON((sizeof(struct hv_ring_buffer) != PAGE_SIZE));
190
191 /*
192 * First page holds struct hv_ring_buffer, do wraparound
mapping for
193 * the rest.
194 */
195 pages_wraparound = kcalloc(page_cnt * 2 - 1, sizeof(struct
page *),
196 GFP_KERNEL);
197 if (!pages_wraparound)
198 return -ENOMEM;
199
/* prepare to wrap page array */
200 pages_wraparound[0] = pages;
201 for (i = 0; i < 2 * (page_cnt - 1); i++)
202 pages_wraparound[i + 1] = &pages[i % (page_cnt - 1) + 1];
203
/* map */
204 ring_info->ring_buffer = (struct hv_ring_buffer *)
205 vmap(pages_wraparound, page_cnt * 2 - 1, VM_MAP,
PAGE_KERNEL);
206
207 kfree(pages_wraparound);
208
209
210 if (!ring_info->ring_buffer)
211 return -ENOMEM;
212
213 ring_info->ring_buffer->read_index =
214 ring_info->ring_buffer->write_index = 0;
With this patch, the code is a big "if" statement with two halves -- one
when SNP isolation is in effect, and the other when not. The SNP isolation
case does the work using PFNs with the shared_gpa_boundary added,
while the other case does the same work but using struct page. Perhaps
I'm missing something, but can both halves be combined and always
do the work using PFNs? The only difference is whether to add the
shared_gpa_boundary, and whether to zero the memory when done.
So get the starting PFN, then have an "if" statement for whether to
add the shared_gpa_boundary. Then everything else is the same.
At the end, use an "if" statement to decide whether to zero the
memory. It would really be better to have the logic in this algorithm
coded only once.
Hi Michael:
I have tried this before. But vmap_pfn() only works for those pfns out
of normal memory. Please see vmap_pfn_apply() for detail and
return error when the PFN is valid.
From: Michael Kelley <hidden> Date: 2021-09-02 15:57:43
From: Christoph Hellwig <hch@lst.de> Sent: Thursday, September 2, 2021 1:00 AM
On Tue, Aug 31, 2021 at 05:16:19PM +0000, Michael Kelley wrote:
quoted
As a quick overview, I think there are four places where the
shared_gpa_boundary must be applied to adjust the guest physical
address that is used. Each requires mapping a corresponding
virtual address range. Here are the four places:
1) The so-called "monitor pages" that are a core communication
mechanism between the guest and Hyper-V. These are two single
pages, and the mapping is handled by calling memremap() for
each of the two pages. See Patch 7 of Tianyu's series.
Ah, interesting.
quoted
3) The network driver send and receive buffers. vmap_phys_range()
should work here.
Actually it won't. The problem with these buffers is that they are
physically non-contiguous allocations.
Indeed you are right. These buffers are allocated with vzalloc().
We really have two sensible options:
1) use vmap_pfn as in the current series. But in that case I think
we should get rid of the other mapping created by vmalloc. I
though a bit about finding a way to apply the offset in vmalloc
itself, but I think it would be too invasive to the normal fast
path. So the other sub-option would be to allocate the pages
manually (maybe even using high order allocations to reduce TLB
pressure) and then remap them
What's the benefit of getting rid of the other mapping created by
vmalloc if it isn't referenced? Just page table space? The default sizes
are a 16 Meg receive buffer and a 1 Meg send buffer for each VMbus
channel used by netvsc, and usually the max number of channels
is 8. So there's 128 Meg of virtual space to be saved on the receive
buffers, which could be worth it.
Allocating the pages manually is also an option, but we have to
be careful about high order allocations. While typically these buffers
are allocated during system boot, these synthetic NICs can be hot
added and removed while the VM is running. The channel count
can also be changed while the VM is running. So multiple 16 Meg
receive buffer allocations may need to be done after the system has
been running a long time.
2) do away with the contiguous kernel mapping entirely. This means
the simple memcpy calls become loops over kmap_local_pfn. As
I just found out for the send side that would be pretty easy,
but the receive side would be more work. We'd also need to check
the performance implications.
Doing away with the contiguous kernel mapping entirely seems like
it would result in fairly messy code to access the buffer. What's the
benefit of doing away with the mapping? I'm not an expert on the
netvsc driver, but decoding the incoming packets is already fraught
with complexities because of the nature of the protocol with Hyper-V.
The contiguous kernel mapping at least keeps the basics sane.
quoted
4) The swiotlb memory used for bounce buffers. vmap_phys_range()
should work here as well.
Or memremap if it works for 1.
quoted
Case #2 above does unusual mapping. The ring buffer consists of a ring
buffer header page, followed by one or more pages that are the actual
ring buffer. The pages making up the actual ring buffer are mapped
twice in succession. For example, if the ring buffer has 4 pages
(one header page and three ring buffer pages), the contiguous
virtual mapping must cover these seven pages: 0, 1, 2, 3, 1, 2, 3.
The duplicate contiguous mapping allows the code that is reading
or writing the actual ring buffer to not be concerned about wrap-around
because writing off the end of the ring buffer is automatically
wrapped-around by the mapping. The amount of data read or
written in one batch never exceeds the size of the ring buffer, and
after a batch is read or written, the read or write indices are adjusted
to put them back into the range of the first mapping of the actual
ring buffer pages. So there's method to the madness, and the
technique works pretty well. But this kind of mapping is not
amenable to using vmap_phys_range().
Hmm. Can you point me to where this is mapped? Especially for the
classic non-isolated case where no vmap/vmalloc mapping is involved
at all?
The existing code is in hv_ringbuffer_init() in drivers/hv/ring_buffer.c.
The code hasn't changed in a while, so any recent upstream code tree
is valid to look at. The memory pages are typically allocated
in vmbus_alloc_ring() in drivers/hv/channel.c.
Michael
With this patch, the code is a big "if" statement with two halves -- one
when SNP isolation is in effect, and the other when not. The SNP isolation
case does the work using PFNs with the shared_gpa_boundary added,
while the other case does the same work but using struct page. Perhaps
I'm missing something, but can both halves be combined and always
do the work using PFNs? The only difference is whether to add the
shared_gpa_boundary, and whether to zero the memory when done.
So get the starting PFN, then have an "if" statement for whether to
add the shared_gpa_boundary. Then everything else is the same.
At the end, use an "if" statement to decide whether to zero the
memory. It would really be better to have the logic in this algorithm
coded only once.
Hi Michael:
I have tried this before. But vmap_pfn() only works for those pfns out
of normal memory. Please see vmap_pfn_apply() for detail and
return error when the PFN is valid.
Indeed. This ties into the discussion with Christoph about coming up
with generalized helper functions to assist in handling the
shared_gpa_boundary. Having a single implementation here in
hv_ringbuffer_init() would be a good goal as well.
Michael
From: Tianyu Lan <hidden> Date: 2021-09-14 14:47:18
Hi Michael and Christoph:
I just sent out V5 patchset. I use alloc_pages() to allocate rx/tx
ring buffer in Isolation VM and use vmap() to map rx/tx buffer first
because the vmbus_establish_gpadl() still needs to va of low end memory
to initialize gpadl buffer. After calling vmbus_establish_gpadl(), the
va returned by vmap will be unmapped to release virtual address space
which will not be used in the following code and then map these pages in
the extra address space above shared_gpa_boundary via vmap_pfn(). Please
have a look.
https://lkml.org/lkml/2021/9/14/672
Thanks.
On 9/2/2021 11:57 PM, Michael Kelley wrote:
From: Christoph Hellwig <hch@lst.de> Sent: Thursday, September 2, 2021 1:00 AM
quoted
On Tue, Aug 31, 2021 at 05:16:19PM +0000, Michael Kelley wrote:
quoted
As a quick overview, I think there are four places where the
shared_gpa_boundary must be applied to adjust the guest physical
address that is used. Each requires mapping a corresponding
virtual address range. Here are the four places:
1) The so-called "monitor pages" that are a core communication
mechanism between the guest and Hyper-V. These are two single
pages, and the mapping is handled by calling memremap() for
each of the two pages. See Patch 7 of Tianyu's series.
Ah, interesting.
quoted
3) The network driver send and receive buffers. vmap_phys_range()
should work here.
Actually it won't. The problem with these buffers is that they are
physically non-contiguous allocations.
Indeed you are right. These buffers are allocated with vzalloc().
quoted
We really have two sensible options:
1) use vmap_pfn as in the current series. But in that case I think
we should get rid of the other mapping created by vmalloc. I
though a bit about finding a way to apply the offset in vmalloc
itself, but I think it would be too invasive to the normal fast
path. So the other sub-option would be to allocate the pages
manually (maybe even using high order allocations to reduce TLB
pressure) and then remap them
What's the benefit of getting rid of the other mapping created by
vmalloc if it isn't referenced? Just page table space? The default sizes
are a 16 Meg receive buffer and a 1 Meg send buffer for each VMbus
channel used by netvsc, and usually the max number of channels
is 8. So there's 128 Meg of virtual space to be saved on the receive
buffers, which could be worth it.
Allocating the pages manually is also an option, but we have to
be careful about high order allocations. While typically these buffers
are allocated during system boot, these synthetic NICs can be hot
added and removed while the VM is running. The channel count
can also be changed while the VM is running. So multiple 16 Meg
receive buffer allocations may need to be done after the system has
been running a long time.
quoted
2) do away with the contiguous kernel mapping entirely. This means
the simple memcpy calls become loops over kmap_local_pfn. As
I just found out for the send side that would be pretty easy,
but the receive side would be more work. We'd also need to check
the performance implications.
Doing away with the contiguous kernel mapping entirely seems like
it would result in fairly messy code to access the buffer. What's the
benefit of doing away with the mapping? I'm not an expert on the
netvsc driver, but decoding the incoming packets is already fraught
with complexities because of the nature of the protocol with Hyper-V.
The contiguous kernel mapping at least keeps the basics sane.
quoted
quoted
4) The swiotlb memory used for bounce buffers. vmap_phys_range()
should work here as well.
Or memremap if it works for 1.
quoted
Case #2 above does unusual mapping. The ring buffer consists of a ring
buffer header page, followed by one or more pages that are the actual
ring buffer. The pages making up the actual ring buffer are mapped
twice in succession. For example, if the ring buffer has 4 pages
(one header page and three ring buffer pages), the contiguous
virtual mapping must cover these seven pages: 0, 1, 2, 3, 1, 2, 3.
The duplicate contiguous mapping allows the code that is reading
or writing the actual ring buffer to not be concerned about wrap-around
because writing off the end of the ring buffer is automatically
wrapped-around by the mapping. The amount of data read or
written in one batch never exceeds the size of the ring buffer, and
after a batch is read or written, the read or write indices are adjusted
to put them back into the range of the first mapping of the actual
ring buffer pages. So there's method to the madness, and the
technique works pretty well. But this kind of mapping is not
amenable to using vmap_phys_range().
Hmm. Can you point me to where this is mapped? Especially for the
classic non-isolated case where no vmap/vmalloc mapping is involved
at all?
The existing code is in hv_ringbuffer_init() in drivers/hv/ring_buffer.c.
The code hasn't changed in a while, so any recent upstream code tree
is valid to look at. The memory pages are typically allocated
in vmbus_alloc_ring() in drivers/hv/channel.c.
Michael