From: Tianyu Lan <hidden> Date: 2021-09-14 13:39: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 5.15-rc1.
Change since v4:
- Hide hv_mark_gpa_visibility() and set memory visibility via
set_memory_encrypted/decrypted()
- Change gpadl handle in netvsc and uio driver from u32 to
struct vmbus_gpadl.
- Change vmbus_establish_gpadl()'s gpadl_handle parameter
to vmbus_gpadl data structure.
- Remove hv_get_simp(), hv_get_siefp() hv_get_synint_*()
helper function. Move the logic into hv_get/set_register().
- Use scsi_dma_map/unmap() instead of dma_map/unmap_sg() in storvsc driver.
- Allocate rx/tx ring buffer via alloc_pages() in Isolation VM
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 (12):
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
Drivers: hv: vmbus: Mark vmbus ring buffer visible to host in
Isolation VM
x86/hyperv: Add Write/Read MSR registers via ghcb page
x86/hyperv: Add ghcb hvcall support for SNP VM
Drivers: hv: vmbus: Add SNP support for VMbus channel initiate
message
Drivers: hv : vmbus: Initialize VMbus ring buffer for Isolation VM
x86/Swiotlb: Add Swiotlb bounce buffer remap function for HV IVM
hyperv/IOMMU: Enable swiotlb bounce buffer for Isolation VM
scsi: storvsc: Add Isolation VM support for storvsc driver
net: netvsc: Add Isolation VM support for netvsc driver
arch/x86/hyperv/Makefile | 2 +-
arch/x86/hyperv/hv_init.c | 78 ++++++--
arch/x86/hyperv/ivm.c | 282 ++++++++++++++++++++++++++
arch/x86/include/asm/hyperv-tlfs.h | 17 ++
arch/x86/include/asm/mshyperv.h | 62 ++++--
arch/x86/include/asm/sev.h | 6 +
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 | 73 ++++---
drivers/hv/connection.c | 96 ++++++++-
drivers/hv/hv.c | 85 ++++++--
drivers/hv/hv_common.c | 12 ++
drivers/hv/hyperv_vmbus.h | 2 +
drivers/hv/ring_buffer.c | 55 ++++--
drivers/hv/vmbus_drv.c | 4 +
drivers/iommu/hyperv-iommu.c | 60 ++++++
drivers/net/hyperv/hyperv_net.h | 12 +-
drivers/net/hyperv/netvsc.c | 304 +++++++++++++++++++++++++++--
drivers/net/hyperv/netvsc_drv.c | 1 +
drivers/net/hyperv/rndis_filter.c | 2 +
drivers/scsi/storvsc_drv.c | 24 ++-
drivers/uio/uio_hv_generic.c | 20 +-
include/asm-generic/hyperv-tlfs.h | 1 +
include/asm-generic/mshyperv.h | 17 +-
include/linux/hyperv.h | 19 +-
include/linux/swiotlb.h | 6 +
kernel/dma/swiotlb.c | 41 +++-
31 files changed, 1204 insertions(+), 171 deletions(-)
create mode 100644 arch/x86/hyperv/ivm.c
--
2.25.1
From: Tianyu Lan <hidden> Date: 2021-09-14 13:39:34
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>
---
Change since v4:
* Fix typo comment
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+*memoryboundaryandmapithere.+*/+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-09-14 13:39:39
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 v4:
* Rename reserve field.
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-09-14 13:39:48
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 v4:
* Fix typo in the comment
* Make hv_mark_gpa_visibility() to be a static function
* Merge __hv_set_mem_host_visibility() and hv_set_mem_host_visibility()
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 | 105 +++++++++++++++++++++++++++++
arch/x86/include/asm/hyperv-tlfs.h | 17 +++++
arch/x86/include/asm/mshyperv.h | 2 +-
arch/x86/mm/pat/set_memory.c | 19 ++++--
include/asm-generic/hyperv-tlfs.h | 1 +
include/asm-generic/mshyperv.h | 1 +
8 files changed, 146 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);
@@ -474,6 +475,15 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,if(ret)returnret;+ret=set_memory_decrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+if(ret){+dev_warn(&channel->device_obj->device,+"Failed to set host visibility for new GPADL %d.\n",+ret);+returnret;+}+init_completion(&msginfo->waitevent);msginfo->waiting_channel=channel;
@@ -537,7 +547,10 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,}/* At this point, we received the gpadl created msg */-*gpadl_handle=gpadlmsg->gpadl;+gpadl->gpadl_handle=gpadlmsg->gpadl;+gpadl->buffer=kbuffer;+gpadl->size=size;+cleanup:spin_lock_irqsave(&vmbus_connection.channelmsg_lock,flags);
@@ -549,6 +562,11 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,}kfree(msginfo);++if(ret)+set_memory_encrypted((unsignedlong)kbuffer,+HVPFN_UP(size));+returnret;}
@@ -561,10 +579,10 @@ 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){return__vmbus_establish_gpadl(channel,HV_GPADL_BUFFER,kbuffer,size,-0U,gpadl_handle);+0U,gpadl);}EXPORT_SYMBOL_GPL(vmbus_establish_gpadl);
@@ -639,6 +657,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;
@@ -675,7 +694,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.gpadl_handle=0;err=__vmbus_establish_gpadl(newchannel,HV_GPADL_RING,page_address(newchannel->ringbuffer_page),
@@ -701,7 +720,8 @@ 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.gpadl_handle;/**Theunitof->downstream_ringbuffer_pageoffsetisHV_HYP_PAGEand*theunitof->ringbuffer_send_offset(i.e.send_pages)isPAGE,so
@@ -759,8 +779,8 @@ 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);+newchannel->ringbuffer_gpadlhandle.gpadl_handle=0;error_clean_ring:hv_ringbuffer_cleanup(&newchannel->outbound);hv_ringbuffer_cleanup(&newchannel->inbound);
@@ -825,7 +845,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 +879,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 +960,8 @@ 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);+elseif(channel->ringbuffer_gpadlhandle.gpadl_handle){+ret=vmbus_teardown_gpadl(channel,&channel->ringbuffer_gpadlhandle);if(ret){pr_err("Close failed: teardown gpadl return %d\n",ret);/*
@@ -944,7 +970,7 @@ static int vmbus_close_internal(struct vmbus_channel *channel)*/}-channel->ringbuffer_gpadlhandle=0;+channel->ringbuffer_gpadlhandle.gpadl_handle=0;}if(!ret)
@@ -1075,14 +1075,15 @@ 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;+u32send_buf_size;+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.gpadl_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 +300,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.gpadl_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
@@ -380,7 +380,7 @@ static int netvsc_init_buf(struct hv_device *device,memset(init_packet,0,sizeof(structnvsp_message));init_packet->hdr.msg_type=NVSP_MSG1_TYPE_SEND_RECV_BUF;init_packet->msg.v1_msg.send_recv_buf.-gpadl_handle=net_device->recv_buf_gpadl_handle;+gpadl_handle=net_device->recv_buf_gpadl_handle.gpadl_handle;init_packet->msg.v1_msg.send_recv_buf.id=NETVSC_RECEIVE_BUFFER_ID;
@@ -463,6 +463,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
@@ -482,7 +483,7 @@ static int netvsc_init_buf(struct hv_device *device,memset(init_packet,0,sizeof(structnvsp_message));init_packet->hdr.msg_type=NVSP_MSG1_TYPE_SEND_SEND_BUF;init_packet->msg.v1_msg.send_send_buf.gpadl_handle=-net_device->send_buf_gpadl_handle;+net_device->send_buf_gpadl_handle.gpadl_handle;init_packet->msg.v1_msg.send_send_buf.id=NETVSC_SEND_BUFFER_ID;trace_nvsp_send(ndev,init_packet);
@@ -303,7 +303,7 @@ hv_uio_probe(struct hv_device *dev,/* put Global Physical Address Label in name */snprintf(pdata->recv_name,sizeof(pdata->recv_name),-"recv:%u",pdata->recv_gpadl);+"recv:%u",pdata->recv_gpadl.gpadl_handle);pdata->info.mem[RECV_BUF_MAP].name=pdata->recv_name;pdata->info.mem[RECV_BUF_MAP].addr=(uintptr_t)pdata->recv_buf;
@@ -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(*aligned_msg));+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-09-14 13:40: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 v4:
* Remove hv_get_simp(), hv_get_siefp() hv_get_synint_*()
helper function. Move the logic into hv_get/set_register().
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.
Change since v1:
* Introduce sev_es_ghcb_hv_call_simple() and share code
between SEV and Hyper-V code.
Fix for hyperv: Add Write/Read MSR registers via ghcb page
---
arch/x86/hyperv/hv_init.c | 36 +++--------
arch/x86/hyperv/ivm.c | 103 ++++++++++++++++++++++++++++++++
arch/x86/include/asm/mshyperv.h | 56 ++++++++++++-----
arch/x86/include/asm/sev.h | 6 ++
arch/x86/kernel/sev-shared.c | 63 +++++++++++--------
drivers/hv/hv.c | 77 +++++++++++++++++++-----
drivers/hv/hv_common.c | 6 ++
include/asm-generic/mshyperv.h | 2 +
8 files changed, 266 insertions(+), 83 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,12 +6,115 @@*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>+#include<asm/hypervisor.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);+}++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)+{+if(!cpu_feature_enabled(X86_FEATURE_HYPERVISOR))+returnfalse;++if(!hypervisor_is_type(X86_HYPER_MS_HYPERV))+returnfalse;++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);
@@ -201,16 +209,35 @@ void hv_synic_enable_regs(unsigned int cpu)/* Setup the Synic's message page */simp.as_uint64=hv_get_register(HV_REGISTER_SIMP);simp.simp_enabled=1;-simp.base_simp_gpa=virt_to_phys(hv_cpu->synic_message_page)->>HV_HYP_PAGE_SHIFT;++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_register(HV_REGISTER_SIMP,simp.as_uint64);/* Setup the Synic's event page */siefp.as_uint64=hv_get_register(HV_REGISTER_SIEFP);siefp.siefp_enabled=1;-siefp.base_siefp_gpa=virt_to_phys(hv_cpu->synic_event_page)->>HV_HYP_PAGE_SHIFT;++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_register(HV_REGISTER_SIEFP,siefp.as_uint64);
@@ -257,30 +284,48 @@ 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);shared_sint.masked=1;++/* 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);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);siefp.as_uint64=hv_get_register(HV_REGISTER_SIEFP);siefp.siefp_enabled=0;-siefp.base_siefp_gpa=0;++if(hv_isolation_type_snp())+memunmap(hv_cpu->synic_event_page);+else+siefp.base_siefp_gpa=0;hv_set_register(HV_REGISTER_SIEFP,siefp.as_uint64);
From: Tianyu Lan <hidden> Date: 2021-09-14 13:40:06
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. Introduce monitor_pages_pa[] to store
monitor pages' physical address and use it to populate pa in the
initiate msg.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Introduce monitor_pages_pa[] to store monitor pages' physical
address and use it to populate pa in the initiate msg.
* Move code of mapping moniter pages in extra address into
vmbus_connect().
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 | 90 ++++++++++++++++++++++++++++++++++++---
drivers/hv/hyperv_vmbus.h | 2 +
2 files changed, 86 insertions(+), 6 deletions(-)
From: Tianyu Lan <hidden> Date: 2021-09-14 13:40:28
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 v4:
* Use PFN_DOWN instead of HVPFN_DOWN in the hv_ringbuffer_init()
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 | 55 ++++++++++++++++++++++++++++++----------
3 files changed, 53 insertions(+), 22 deletions(-)
@@ -684,15 +684,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.gpadl_handle=0;
@@ -704,6 +695,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,48 @@ 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)++PFN_DOWN(ms_hyperv.shared_gpa_boundary);++pfns_wraparound=kcalloc(page_cnt*2-1,+sizeof(unsignedlong),GFP_KERNEL);+if(!pfns_wraparound)+return-ENOMEM;++pfns_wraparound[0]=pfn;+for(i=0;i<2*(page_cnt-1);i++)+pfns_wraparound[i+1]=pfn+i%(page_cnt-1)+1;-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_pfn(pfns_wraparound,page_cnt*2-1,+PAGE_KERNEL);+kfree(pfns_wraparound);-ring_info->ring_buffer=(structhv_ring_buffer*)-vmap(pages_wraparound,page_cnt*2-1,VM_MAP,PAGE_KERNEL);+if(!ring_info->ring_buffer)+return-ENOMEM;++/* Zero ring buffer after setting memory host visibility. */+memset(ring_info->ring_buffer,0x00,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];-kfree(pages_wraparound);+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;
From: Tianyu Lan <hidden> Date: 2021-09-14 13:40:35
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.
Expose swiotlb_unencrypted_base for platforms to set unencrypted
memory base offset and call memremap() to map bounce buffer in the
swiotlb code, store map address and use the address to copy data
from/to swiotlb bounce buffer.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Expose swiotlb_unencrypted_base to set unencrypted memory
offset.
* Use memremap() to map bounce buffer if swiotlb_unencrypted_
base is set.
Change since v1:
* Make swiotlb_init_io_tlb_mem() return error code and return
error when dma_map_decrypted() fails.
---
include/linux/swiotlb.h | 6 ++++++
kernel/dma/swiotlb.c | 41 +++++++++++++++++++++++++++++++++++------
2 files changed, 41 insertions(+), 6 deletions(-)
@@ -196,13 +199,34 @@ static void swiotlb_init_io_tlb_mem(struct io_tlb_mem *mem, phys_addr_t start,mem->slots[i].orig_addr=INVALID_PHYS_ADDR;mem->slots[i].alloc_size=0;}++if(set_memory_decrypted((unsignedlong)vaddr,bytes>>PAGE_SHIFT))+return-EFAULT;++/*+*Mapmemoryintheunencryptedphysicaladdressspacewhenrequested+*(e.g.forHyper-VAMDSEV-SNPIsolationVMs).+*/+if(swiotlb_unencrypted_base){+phys_addr_tpaddr=__pa(vaddr)+swiotlb_unencrypted_base;++vaddr=memremap(paddr,bytes,MEMREMAP_WB);+if(!vaddr){+pr_err("Failed to map the unencrypted memory.\n");+return-ENOMEM;+}+}+memset(vaddr,0,bytes);+mem->vaddr=vaddr;+return0;}int__initswiotlb_init_with_tbl(char*tlb,unsignedlongnslabs,intverbose){structio_tlb_mem*mem=&io_tlb_default_mem;size_talloc_size;+intret;if(swiotlb_force==SWIOTLB_NO_FORCE)return0;
@@ -217,7 +241,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;+}if(verbose)swiotlb_print_info();
From: Tianyu Lan <hidden> Date: 2021-09-14 13:40:42
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.
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.
Swiotlb bounce buffer code calls set_memory_decrypted()
to mark bounce buffer visible to host and map it in extra
address space via memremap. Populate the shared_gpa_boundary
(vTOM) via swiotlb_unencrypted_base variable.
The map function memremap() 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 v4:
* Use swiotlb_unencrypted_base variable to pass shared_gpa_
boundary and map bounce buffer inside swiotlb code.
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/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 | 60 +++++++++++++++++++++++++++++++++
include/linux/hyperv.h | 1 +
6 files changed, 70 insertions(+), 2 deletions(-)
From: Tianyu Lan <hidden> Date: 2021-09-14 13:40:53
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(scsi_dma_map/unmap)
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 v4:
* use scsi_dma_map/unmap() instead of dma_map/unmap_sg()
* Add deleted comments back.
* Fix error calculation of hvpnfs_to_add
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 | 24 +++++++++++++++---------
include/linux/hyperv.h | 1 +
3 files changed, 17 insertions(+), 9 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;
From: Tianyu Lan <hidden> Date: 2021-09-14 13:41:33
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 address will be returned. The swiotlb bounce buffer
has been masked to be visible to host during boot up.
Allocate rx/tx ring buffer via alloc_pages() in Isolation VM and map
these pages via vmap(). After calling vmbus_establish_gpadl() which
marks these pages visible to host, unmap these pages to release the
virtual address mapped with physical address below shared_gpa_boundary
and map them in the extra address space via vmap_pfn().
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Allocate rx/tx ring buffer via alloc_pages() in Isolation VM
* Map pages after calling vmbus_establish_gpadl().
* set dma_set_min_align_mask for netvsc driver.
Change since v3:
* Add comment to explain why not to use dma_map_sg()
* Fix some error handle.
---
drivers/net/hyperv/hyperv_net.h | 7 +
drivers/net/hyperv/netvsc.c | 287 +++++++++++++++++++++++++++++-
drivers/net/hyperv/netvsc_drv.c | 1 +
drivers/net/hyperv/rndis_filter.c | 2 +
include/linux/hyperv.h | 5 +
5 files changed, 296 insertions(+), 6 deletions(-)
@@ -1074,6 +1075,8 @@ struct netvsc_device {/* Receive buffer allocated by us but manages by NetVSP */void*recv_buf;+structpage**recv_pages;+u32recv_page_count;u32recv_buf_size;/* allocated bytes */structvmbus_gpadlrecv_buf_gpadl_handle;u32recv_section_cnt;
@@ -1082,6 +1085,8 @@ struct netvsc_device {/* Send buffer allocated by us */void*send_buf;+structpage**send_pages;+u32send_page_count;u32send_buf_size;structvmbus_gpadlsend_buf_gpadl_handle;u32send_section_cnt;
@@ -330,6 +353,108 @@ int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx)returnnvchan->mrc.slots?0:-ENOMEM;}+void*netvsc_alloc_pages(structpage***pages_array,unsignedint*array_len,+unsignedlongsize)+{+structpage*page,**pages,**vmap_pages;+unsignedlongpg_count=size>>PAGE_SHIFT;+intalloc_unit=MAX_ORDER_NR_PAGES;+inti,j,vmap_page_index=0;+void*vaddr;++if(pg_count<alloc_unit)+alloc_unit=1;++/* vmap() accepts page array with PAGE_SIZE as unit while try to+*allocatehighorderpageshereinordertosavepagearrayspace.+*vmap_pages[]isusedasinputparameterofvmap().pages[]isto+*storeallocatedpagesandmapthemlater.+*/+vmap_pages=kmalloc_array(pg_count,sizeof(*vmap_pages),GFP_KERNEL);+if(!vmap_pages)+returnNULL;++retry:+*array_len=pg_count/alloc_unit;+pages=kmalloc_array(*array_len,sizeof(*pages),GFP_KERNEL);+if(!pages)+gotocleanup;++for(i=0;i<*array_len;i++){+page=alloc_pages(GFP_KERNEL|__GFP_ZERO,+get_order(alloc_unit<<PAGE_SHIFT));+if(!page){+/* Try allocating small pages if high order pages are not available. */+if(alloc_unit==1){+gotocleanup;+}else{+memset(vmap_pages,0,+sizeof(*vmap_pages)*vmap_page_index);+vmap_page_index=0;++for(j=0;j<i;j++)+__free_pages(pages[j],alloc_unit);++kfree(pages);+alloc_unit=1;+gotoretry;+}+}++pages[i]=page;+for(j=0;j<alloc_unit;j++)+vmap_pages[vmap_page_index++]=page++;+}++vaddr=vmap(vmap_pages,vmap_page_index,VM_MAP,PAGE_KERNEL);+kfree(vmap_pages);++*pages_array=pages;+returnvaddr;++cleanup:+for(j=0;j<i;j++)+__free_pages(pages[i],alloc_unit);++kfree(pages);+kfree(vmap_pages);+returnNULL;+}++staticvoid*netvsc_map_pages(structpage**pages,intcount,intalloc_unit)+{+intpg_count=count*alloc_unit;+structpage*page;+unsignedlong*pfns;+intpfn_index=0;+void*vaddr;+inti,j;++if(!pages)+returnNULL;++pfns=kcalloc(pg_count,sizeof(*pfns),GFP_KERNEL);+if(!pfns)+returnNULL;++for(i=0;i<count;i++){+page=pages[i];+if(!page){+pr_warn("page is not available %d.\n",i);+returnNULL;+}++for(j=0;j<alloc_unit;j++){+pfns[pfn_index++]=page_to_pfn(page++)++(ms_hyperv.shared_gpa_boundary>>PAGE_SHIFT);+}+}++vaddr=vmap_pfn(pfns,pg_count,PAGE_KERNEL_IO);+kfree(pfns);+returnvaddr;+}+staticintnetvsc_init_buf(structhv_device*device,structnetvsc_device*net_device,conststructnetvsc_device_info*device_info)
@@ -337,7 +462,7 @@ static int netvsc_init_buf(struct hv_device *device,structnvsp_1_message_send_receive_buffer_complete*resp;structnet_device*ndev=hv_get_drvdata(device);structnvsp_message*init_packet;-unsignedintbuf_size;+unsignedintbuf_size,alloc_unit;size_tmap_words;inti,ret=0;
@@ -350,7 +475,14 @@ static int netvsc_init_buf(struct hv_device *device,buf_size=min_t(unsignedint,buf_size,NETVSC_RECEIVE_BUFFER_SIZE_LEGACY);-net_device->recv_buf=vzalloc(buf_size);+if(hv_isolation_type_snp())+net_device->recv_buf=+netvsc_alloc_pages(&net_device->recv_pages,+&net_device->recv_page_count,+buf_size);+else+net_device->recv_buf=vzalloc(buf_size);+if(!net_device->recv_buf){netdev_err(ndev,"unable to allocate receive buffer of size %u\n",
@@ -375,6 +507,27 @@ static int netvsc_init_buf(struct hv_device *device,gotocleanup;}+if(hv_isolation_type_snp()){+alloc_unit=(buf_size/net_device->recv_page_count)+>>PAGE_SHIFT;++/* Unmap previous virtual address and map pages in the extra+*addressspace(abovesharedgpaboundary)inIsolationVM.+*/+vunmap(net_device->recv_buf);+net_device->recv_buf=+netvsc_map_pages(net_device->recv_pages,+net_device->recv_page_count,+alloc_unit);+if(!net_device->recv_buf){+netdev_err(ndev,+"unable to allocate receive buffer of size %u\n",+buf_size);+ret=-ENOMEM;+gotocleanup;+}+}+/* Notify the NetVsp of the gpadl handle */init_packet=&net_device->channel_init_pkt;memset(init_packet,0,sizeof(structnvsp_message));
@@ -456,13 +609,21 @@ static int netvsc_init_buf(struct hv_device *device,buf_size=device_info->send_sections*device_info->send_section_size;buf_size=round_up(buf_size,PAGE_SIZE);-net_device->send_buf=vzalloc(buf_size);+if(hv_isolation_type_snp())+net_device->send_buf=+netvsc_alloc_pages(&net_device->send_pages,+&net_device->send_page_count,+buf_size);+else+net_device->send_buf=vzalloc(buf_size);+if(!net_device->send_buf){netdev_err(ndev,"unable to allocate send buffer of size %u\n",buf_size);ret=-ENOMEM;gotocleanup;}+net_device->send_buf_size=buf_size;/* Establish the gpadl handle for this buffer on this
@@ -478,6 +639,27 @@ static int netvsc_init_buf(struct hv_device *device,gotocleanup;}+if(hv_isolation_type_snp()){+alloc_unit=(buf_size/net_device->send_page_count)+>>PAGE_SHIFT;++/* Unmap previous virtual address and map pages in the extra+*addressspace(abovesharedgpaboundary)inIsolationVM.+*/+vunmap(net_device->send_buf);+net_device->send_buf=+netvsc_map_pages(net_device->send_pages,+net_device->send_page_count,+alloc_unit);+if(!net_device->send_buf){+netdev_err(ndev,+"unable to allocate receive buffer of size %u\n",+buf_size);+ret=-ENOMEM;+gotocleanup;+}+}+/* Notify the NetVsp of the gpadl handle */init_packet=&net_device->channel_init_pkt;memset(init_packet,0,sizeof(structnvsp_message));
@@ -768,7 +950,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;
-----Original Message-----
From: Tianyu Lan <redacted>
Sent: Tuesday, September 14, 2021 9:39 AM
To: KY Srinivasan <kys@microsoft.com>; Haiyang Zhang
[off-list ref]; Stephen Hemminger [off-list ref];
wei.liu@kernel.org; Dexuan Cui [off-list ref]; tglx@linutronix.de;
mingo@redhat.com; bp@alien8.de; x86@kernel.org; hpa@zytor.com;
dave.hansen@linux.intel.com; luto@kernel.org; peterz@infradead.org;
konrad.wilk@oracle.com; boris.ostrovsky@oracle.com; jgross@suse.com;
sstabellini@kernel.org; joro@8bytes.org; will@kernel.org;
davem@davemloft.net; kuba@kernel.org; jejb@linux.ibm.com;
martin.petersen@oracle.com; gregkh@linuxfoundation.org; arnd@arndb.de;
hch@lst.de; m.szyprowski@samsung.com; robin.murphy@arm.com;
brijesh.singh@amd.com; Tianyu Lan [off-list ref];
thomas.lendacky@amd.com; pgonda@google.com; akpm@linux-foundation.org;
kirill.shutemov@linux.intel.com; rppt@kernel.org; sfr@canb.auug.org.au;
aneesh.kumar@linux.ibm.com; saravanand@fb.com;
krish.sadhukhan@oracle.com; xen-devel@lists.xenproject.org;
tj@kernel.org; rientjes@google.com; Michael Kelley
[off-list ref]
Cc: iommu@lists.linux-foundation.org; linux-arch@vger.kernel.org; linux-
hyperv@vger.kernel.org; linux-kernel@vger.kernel.org; linux-
scsi@vger.kernel.org; netdev@vger.kernel.org; vkuznets
[off-list ref]; parri.andrea@gmail.com; dave.hansen@intel.com
Subject: [PATCH V5 12/12] net: netvsc: Add Isolation VM support for
netvsc driver
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 address will be returned. The swiotlb bounce buffer
has been masked to be visible to host during boot up.
Allocate rx/tx ring buffer via alloc_pages() in Isolation VM and map
these pages via vmap(). After calling vmbus_establish_gpadl() which
marks these pages visible to host, unmap these pages to release the
virtual address mapped with physical address below shared_gpa_boundary
and map them in the extra address space via vmap_pfn().
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Allocate rx/tx ring buffer via alloc_pages() in Isolation VM
* Map pages after calling vmbus_establish_gpadl().
* set dma_set_min_align_mask for netvsc driver.
Change since v3:
* Add comment to explain why not to use dma_map_sg()
* Fix some error handle.
---
@@ -474,6 +475,15 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,if(ret)returnret;+ret=set_memory_decrypted((unsignedlong)kbuffer,+HVPFN_UP(size));
This should be PFN_UP, not HVPFN_UP. The numpages parameter to
set_memory_decrypted() is in guest size pages, not Hyper-V size pages.
quoted hunk
+ if (ret) {
+ dev_warn(&channel->device_obj->device,
+ "Failed to set host visibility for new GPADL %d.\n",
+ ret);
+ return ret;
+ }
+
init_completion(&msginfo->waitevent);
msginfo->waiting_channel = channel;
@@ -537,7 +547,10 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel, } /* At this point, we received the gpadl created msg */- *gpadl_handle = gpadlmsg->gpadl;+ gpadl->gpadl_handle = gpadlmsg->gpadl;+ gpadl->buffer = kbuffer;+ gpadl->size = size;+ cleanup: spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
@@ -549,6 +562,11 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel, } kfree(msginfo);++ if (ret)+ set_memory_encrypted((unsigned long)kbuffer,+ HVPFN_UP(size));
Should be PFN_UP as noted on the previous call to set_memory_decrypted().
I think this local variable was needed in a previous version of the patch, but
is now unused and should be deleted.
quoted hunk
u32 send_pages, recv_pages;
unsigned long flags;
int err;
@@ -675,7 +694,7 @@ static int __vmbus_open(struct vmbus_channel *newchannel, goto error_clean_ring; /* Establish the gpadl for the ring buffer */- newchannel->ringbuffer_gpadlhandle = 0;+ newchannel->ringbuffer_gpadlhandle.gpadl_handle = 0; err = __vmbus_establish_gpadl(newchannel, HV_GPADL_RING, page_address(newchannel->ringbuffer_page),
@@ -701,7 +720,8 @@ 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.gpadl_handle; /* * The unit of ->downstream_ringbuffer_pageoffset is HV_HYP_PAGE and * the unit of ->ringbuffer_send_offset (i.e. send_pages) is PAGE, so
My previous comments had suggested letting vmbus_teardown_gpadl() set the
gpadl_handle to zero, avoiding the need for all the callers to set it to zero.
Did that not work for some reason? Just curious ....
I think this local variable was needed in a previous version of the patch, but
is now unused and should be deleted.
quoted hunk
int ret;
vmbus_reset_channel_cb(channel);
@@ -933,9 +960,8 @@ static int vmbus_close_internal(struct vmbus_channel *channel) } /* Tear down the gpadl for the channel's ring buffer */- else if (channel->ringbuffer_gpadlhandle) {- ret = vmbus_teardown_gpadl(channel,- channel->ringbuffer_gpadlhandle);+ else if (channel->ringbuffer_gpadlhandle.gpadl_handle) {+ ret = vmbus_teardown_gpadl(channel, &channel->ringbuffer_gpadlhandle); if (ret) { pr_err("Close failed: teardown gpadl return %d\n", ret); /*
@@ -944,7 +970,7 @@ static int vmbus_close_internal(struct vmbus_channel *channel) */ }- channel->ringbuffer_gpadlhandle = 0;+ channel->ringbuffer_gpadlhandle.gpadl_handle = 0; } if (!ret)
@@ -1075,14 +1075,15 @@ 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;+u32send_buf_size;+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.gpadl_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 +300,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.gpadl_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
@@ -380,7 +380,7 @@ static int netvsc_init_buf(struct hv_device *device,memset(init_packet,0,sizeof(structnvsp_message));init_packet->hdr.msg_type=NVSP_MSG1_TYPE_SEND_RECV_BUF;init_packet->msg.v1_msg.send_recv_buf.-gpadl_handle=net_device->recv_buf_gpadl_handle;+gpadl_handle=net_device->recv_buf_gpadl_handle.gpadl_handle;init_packet->msg.v1_msg.send_recv_buf.id=NETVSC_RECEIVE_BUFFER_ID;
@@ -463,6 +463,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
@@ -482,7 +483,7 @@ static int netvsc_init_buf(struct hv_device *device,memset(init_packet,0,sizeof(structnvsp_message));init_packet->hdr.msg_type=NVSP_MSG1_TYPE_SEND_SEND_BUF;init_packet->msg.v1_msg.send_send_buf.gpadl_handle=-net_device->send_buf_gpadl_handle;+net_device->send_buf_gpadl_handle.gpadl_handle;init_packet->msg.v1_msg.send_send_buf.id=NETVSC_SEND_BUFFER_ID;trace_nvsp_send(ndev,init_packet);
@@ -303,7 +303,7 @@ hv_uio_probe(struct hv_device *dev,/* put Global Physical Address Label in name */snprintf(pdata->recv_name,sizeof(pdata->recv_name),-"recv:%u",pdata->recv_gpadl);+"recv:%u",pdata->recv_gpadl.gpadl_handle);pdata->info.mem[RECV_BUF_MAP].name=pdata->recv_name;pdata->info.mem[RECV_BUF_MAP].addr=(uintptr_t)pdata->recv_buf;
From: Michael Kelley <hidden> Date: 2021-09-15 15:41:29
From: Tianyu Lan <redacted> Sent: Tuesday, September 14, 2021 6:39 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 v4:
* Remove hv_get_simp(), hv_get_siefp() hv_get_synint_*()
helper function. Move the logic into hv_get/set_register().
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.
Change since v1:
* Introduce sev_es_ghcb_hv_call_simple() and share code
between SEV and Hyper-V code.
Fix for hyperv: Add Write/Read MSR registers via ghcb page
---
arch/x86/hyperv/hv_init.c | 36 +++--------
arch/x86/hyperv/ivm.c | 103 ++++++++++++++++++++++++++++++++
arch/x86/include/asm/mshyperv.h | 56 ++++++++++++-----
arch/x86/include/asm/sev.h | 6 ++
arch/x86/kernel/sev-shared.c | 63 +++++++++++--------
drivers/hv/hv.c | 77 +++++++++++++++++++-----
drivers/hv/hv_common.c | 6 ++
include/asm-generic/mshyperv.h | 2 +
8 files changed, 266 insertions(+), 83 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,12 +6,115 @@*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>+#include<asm/hypervisor.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);+}++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)+{+if(!cpu_feature_enabled(X86_FEATURE_HYPERVISOR))+returnfalse;++if(!hypervisor_is_type(X86_HYPER_MS_HYPERV))+returnfalse;++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.
This all looks OK to me, except that it would really be nice if the
#include of asm-generic/mshyperv.h stays last in the file. I think the
problem is needing a declaration for hv_isolation_type_snp(), right?
And it is added into asm-generic/mshyperv.h at the very end of this
patch.
The alternative would be to put hv_get_register() and
hv_set_register() in a .c file rather than as static inline. They get
called in quite a few places, and arguably are now fairly large for
being static inline, in my judgment. But I guess I'm OK either way.
In previous versions, the EOM register was being handled
differently (GHCB vs. MSR write) for timer messages vs. other messages.
That distinction is no longer being made. Did you learn something new
from the Hyper-V team about this? Just want to make sure nothing
was inadvertently dropped.
@@ -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);
@@ -201,16 +209,35 @@ void hv_synic_enable_regs(unsigned int cpu)/* Setup the Synic's message page */simp.as_uint64=hv_get_register(HV_REGISTER_SIMP);simp.simp_enabled=1;-simp.base_simp_gpa=virt_to_phys(hv_cpu->synic_message_page)->>HV_HYP_PAGE_SHIFT;++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_register(HV_REGISTER_SIMP,simp.as_uint64);/* Setup the Synic's event page */siefp.as_uint64=hv_get_register(HV_REGISTER_SIEFP);siefp.siefp_enabled=1;-siefp.base_siefp_gpa=virt_to_phys(hv_cpu->synic_event_page)->>HV_HYP_PAGE_SHIFT;++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_register(HV_REGISTER_SIEFP,siefp.as_uint64);
@@ -257,30 +284,48 @@ 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;+
/* 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);
simp.as_uint64 = hv_get_register(HV_REGISTER_SIMP);
+ /*
+ * In Isolation VM, sim and sief pages are allocated by
+ * paravisor. These pages also will be used by kdump
+ * kernel. So just reset enable bit here and keep page
+ * 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);
siefp.as_uint64 = hv_get_register(HV_REGISTER_SIEFP);
siefp.siefp_enabled = 0;
- siefp.base_siefp_gpa = 0;
+
+ if (hv_isolation_type_snp())
+ memunmap(hv_cpu->synic_event_page);
+ else
+ siefp.base_siefp_gpa = 0;
hv_set_register(HV_REGISTER_SIEFP, siefp.as_uint64);
This #include should not be done. The architecture specific version
of mshyperv.h #includes the asm-generic version, not the other
way around.
In any case, I'm not seeing that this #include is needed.
From: Michael Kelley <hidden> Date: 2021-09-15 15:42:08
From: Tianyu Lan <redacted> Sent: Tuesday, September 14, 2021 6:39 AM
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. Introduce monitor_pages_pa[] to store
monitor pages' physical address and use it to populate pa in the
initiate msg.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Introduce monitor_pages_pa[] to store monitor pages' physical
address and use it to populate pa in the initiate msg.
* Move code of mapping moniter pages in extra address into
vmbus_connect().
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 | 90 ++++++++++++++++++++++++++++++++++++---
drivers/hv/hyperv_vmbus.h | 2 +
2 files changed, 86 insertions(+), 6 deletions(-)
The type of this field really should be phys_addr_t. In addition to
just making semantic sense, then it will match the return type from
virt_to_phys() and the input arg to memremap() since resource_size_t
is typedef'ed as phys_addr_t.
From: Michael Kelley <hidden> Date: 2021-09-15 15:42:46
From: Tianyu Lan <redacted> Sent: Tuesday, September 14, 2021 6:39 AM
quoted hunk
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.
Expose swiotlb_unencrypted_base for platforms to set unencrypted
memory base offset and call memremap() to map bounce buffer in the
swiotlb code, store map address and use the address to copy data
from/to swiotlb bounce buffer.
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Expose swiotlb_unencrypted_base to set unencrypted memory
offset.
* Use memremap() to map bounce buffer if swiotlb_unencrypted_
base is set.
Change since v1:
* Make swiotlb_init_io_tlb_mem() return error code and return
error when dma_map_decrypted() fails.
---
include/linux/swiotlb.h | 6 ++++++
kernel/dma/swiotlb.c | 41 +++++++++++++++++++++++++++++++++++------
2 files changed, 41 insertions(+), 6 deletions(-)
From: Michael Kelley <hidden> Date: 2021-09-15 15:43:20
From: Tianyu Lan <redacted> Sent: Tuesday, September 14, 2021 6:39 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.
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.
Swiotlb bounce buffer code calls set_memory_decrypted()
to mark bounce buffer visible to host and map it in extra
address space via memremap. Populate the shared_gpa_boundary
(vTOM) via swiotlb_unencrypted_base variable.
The map function memremap() 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 v4:
* Use swiotlb_unencrypted_base variable to pass shared_gpa_
boundary and map bounce buffer inside swiotlb code.
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/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 | 60 +++++++++++++++++++++++++++++++++
include/linux/hyperv.h | 1 +
6 files changed, 70 insertions(+), 2 deletions(-)
@@ -182,6 +182,8 @@ int hv_map_ioapic_interrupt(int ioapic_id, bool level, int vcpu, int vector,structhv_interrupt_entry*entry);inthv_unmap_ioapic_interrupt(intioapic_id,structhv_interrupt_entry*entry);inthv_set_mem_host_visibility(unsignedlongaddr,intnumpages,boolvisible);+void*hv_map_memory(void*addr,unsignedlongsize);+voidhv_unmap_memory(void*addr);
From: Michael Kelley <hidden> Date: 2021-09-15 15:43:32
From: Tianyu Lan <redacted> Sent: Tuesday, September 14, 2021 6:39 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
storvsc rx/tx ring buffer. The page buffer used by vmbus_sendpacket_
mpb_desc() still needs to be handled. Use DMA API(scsi_dma_map/unmap)
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 v4:
* use scsi_dma_map/unmap() instead of dma_map/unmap_sg()
* Add deleted comments back.
* Fix error calculation of hvpnfs_to_add
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 | 24 +++++++++++++++---------
include/linux/hyperv.h | 1 +
3 files changed, 17 insertions(+), 9 deletions(-)
Nit: The above comment is now out-of-date because hvpgoff has
been removed.
quoted hunk
@@ -1845,10 +1851,9 @@ static int storvsc_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scmnd) * even on other than the first sgl entry, provided * they are a multiple of PAGE_SIZE. */- hvpgoff = HVPFN_DOWN(sgl->offset);- hvpfn = page_to_hvpfn(sg_page(sgl)) + hvpgoff;- hvpfns_to_add = HVPFN_UP(sgl->offset + sgl->length) -- hvpgoff;+ hvpfn = HVPFN_DOWN(sg_dma_address(sg));+ hvpfns_to_add = HVPFN_UP(sg_dma_address(sg) ++ sg_dma_len(sg)) - hvpfn;
Good. This looks correct now.
quoted hunk
/*
* Fill the next portion of the PFN array with
@@ -1858,7 +1863,7 @@ static int storvsc_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scmnd) * the PFN array is filled. */ while (hvpfns_to_add--)- payload->range.pfn_array[i++] = hvpfn++;+ payload->range.pfn_array[i++] = hvpfn++; } }
@@ -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-15 16:22:21
From: Tianyu Lan <redacted> Sent: Tuesday, September 14, 2021 6:39 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 address will be returned. The swiotlb bounce buffer
has been masked to be visible to host during boot up.
Allocate rx/tx ring buffer via alloc_pages() in Isolation VM and map
these pages via vmap(). After calling vmbus_establish_gpadl() which
marks these pages visible to host, unmap these pages to release the
virtual address mapped with physical address below shared_gpa_boundary
and map them in the extra address space via vmap_pfn().
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Allocate rx/tx ring buffer via alloc_pages() in Isolation VM
* Map pages after calling vmbus_establish_gpadl().
* set dma_set_min_align_mask for netvsc driver.
Change since v3:
* Add comment to explain why not to use dma_map_sg()
* Fix some error handle.
---
drivers/net/hyperv/hyperv_net.h | 7 +
drivers/net/hyperv/netvsc.c | 287 +++++++++++++++++++++++++++++-
drivers/net/hyperv/netvsc_drv.c | 1 +
drivers/net/hyperv/rndis_filter.c | 2 +
include/linux/hyperv.h | 5 +
5 files changed, 296 insertions(+), 6 deletions(-)
@@ -1074,6 +1075,8 @@ struct netvsc_device {/* Receive buffer allocated by us but manages by NetVSP */void*recv_buf;+structpage**recv_pages;+u32recv_page_count;u32recv_buf_size;/* allocated bytes */structvmbus_gpadlrecv_buf_gpadl_handle;u32recv_section_cnt;
@@ -1082,6 +1085,8 @@ struct netvsc_device {/* Send buffer allocated by us */void*send_buf;+structpage**send_pages;+u32send_page_count;u32send_buf_size;structvmbus_gpadlsend_buf_gpadl_handle;u32send_section_cnt;
@@ -330,6 +353,108 @@ int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx)returnnvchan->mrc.slots?0:-ENOMEM;}+void*netvsc_alloc_pages(structpage***pages_array,unsignedint*array_len,+unsignedlongsize)+{+structpage*page,**pages,**vmap_pages;+unsignedlongpg_count=size>>PAGE_SHIFT;+intalloc_unit=MAX_ORDER_NR_PAGES;+inti,j,vmap_page_index=0;+void*vaddr;++if(pg_count<alloc_unit)+alloc_unit=1;++/* vmap() accepts page array with PAGE_SIZE as unit while try to+*allocatehighorderpageshereinordertosavepagearrayspace.+*vmap_pages[]isusedasinputparameterofvmap().pages[]isto+*storeallocatedpagesandmapthemlater.+*/+vmap_pages=kmalloc_array(pg_count,sizeof(*vmap_pages),GFP_KERNEL);+if(!vmap_pages)+returnNULL;++retry:+*array_len=pg_count/alloc_unit;+pages=kmalloc_array(*array_len,sizeof(*pages),GFP_KERNEL);+if(!pages)+gotocleanup;++for(i=0;i<*array_len;i++){+page=alloc_pages(GFP_KERNEL|__GFP_ZERO,+get_order(alloc_unit<<PAGE_SHIFT));+if(!page){+/* Try allocating small pages if high order pages are not available. */+if(alloc_unit==1){+gotocleanup;+}else{
The "else" clause isn't really needed because of the goto cleanup above. Then
the indentation of the code below could be reduced by one level.
This is the case where a large enough contiguous physical memory chunk could
not be found. But rather than dropping all the way down to single pages,
would it make sense to try something smaller, but not 1? For example,
cut the alloc_unit in half and try again. But I'm not sure of all the implications.
I think you are proposing this approach to allocating memory for the send
and receive buffers so that you can avoid having two virtual mappings for
the memory, per comments from Christop Hellwig. But overall, the approach
seems a bit complex and I wonder if it is worth it. If allocating large contiguous
chunks of physical memory is successful, then there is some memory savings
in that the data structures needed to keep track of the physical pages is
smaller than the equivalent page tables might be. But if you have to revert
to allocating individual pages, then the memory savings is reduced.
Ultimately, the list of actual PFNs has to be kept somewhere. Another approach
would be to do the reverse of what hv_map_memory() from the v4 patch
series does. I.e., you could do virt_to_phys() on each virtual address that
maps above VTOM, and subtract out the shared_gpa_boundary to get the
list of actual PFNs that need to be freed. This way you don't have two copies
of the list of PFNs -- one with and one without the shared_gpa_boundary added.
But it comes at the cost of additional code so that may not be a great idea.
I think what you have here works, and I don't have a clearly better solution
at the moment except perhaps to revert to the v4 solution and just have two
virtual mappings. I'll keep thinking about it. Maybe Christop has other
thoughts.
I wonder if it is necessary to have two different code paths here. The
allocating and freeing of the send and receive buffers is not perf
sensitive, and it seems like netvsc_alloc_pages() could be used
regardless of whether SNP Isolation is in effect. To my thinking,
one code path is better than two code paths unless there's a
compelling reason to have two.
quoted hunk
if (!net_device->recv_buf) {
netdev_err(ndev,
"unable to allocate receive buffer of size %u\n",
@@ -375,6 +507,27 @@ static int netvsc_init_buf(struct hv_device *device, goto cleanup; }+ if (hv_isolation_type_snp()) {+ alloc_unit = (buf_size / net_device->recv_page_count)+ >> PAGE_SHIFT;++ /* Unmap previous virtual address and map pages in the extra+ * address space(above shared gpa boundary) in Isolation VM.+ */+ vunmap(net_device->recv_buf);+ net_device->recv_buf =+ netvsc_map_pages(net_device->recv_pages,+ net_device->recv_page_count,+ alloc_unit);+ if (!net_device->recv_buf) {+ netdev_err(ndev,+ "unable to allocate receive buffer of size %u\n",+ buf_size);+ ret = -ENOMEM;+ goto cleanup;+ }+ }+ /* Notify the NetVsp of the gpadl handle */ init_packet = &net_device->channel_init_pkt; memset(init_packet, 0, sizeof(struct nvsp_message));
@@ -456,13 +609,21 @@ static int netvsc_init_buf(struct hv_device *device, buf_size = device_info->send_sections * device_info->send_section_size; buf_size = round_up(buf_size, PAGE_SIZE);- net_device->send_buf = vzalloc(buf_size);+ if (hv_isolation_type_snp())+ net_device->send_buf =+ netvsc_alloc_pages(&net_device->send_pages,+ &net_device->send_page_count,+ buf_size);+ else+ net_device->send_buf = vzalloc(buf_size);+ if (!net_device->send_buf) { netdev_err(ndev, "unable to allocate send buffer of size %u\n", buf_size); ret = -ENOMEM; goto cleanup; }+ net_device->send_buf_size = buf_size; /* Establish the gpadl handle for this buffer on this
@@ -478,6 +639,27 @@ static int netvsc_init_buf(struct hv_device *device, goto cleanup; }+ if (hv_isolation_type_snp()) {+ alloc_unit = (buf_size / net_device->send_page_count)+ >> PAGE_SHIFT;++ /* Unmap previous virtual address and map pages in the extra+ * address space(above shared gpa boundary) in Isolation VM.+ */+ vunmap(net_device->send_buf);+ net_device->send_buf =+ netvsc_map_pages(net_device->send_pages,+ net_device->send_page_count,+ alloc_unit);+ if (!net_device->send_buf) {+ netdev_err(ndev,+ "unable to allocate receive buffer of size %u\n",+ buf_size);+ ret = -ENOMEM;+ goto cleanup;+ }+ }+ /* Notify the NetVsp of the gpadl handle */ init_packet = &net_device->channel_init_pkt; memset(init_packet, 0, sizeof(struct nvsp_message));
@@ -948,6 +1131,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 because the+ * 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.+ */+static 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);
With the DMA min align mask now being set, the offset within
the Hyper-V page won't be changed by dma_map_single(). So I
think the above statement can be removed.
+ pb[i].len = len;
A few lines above, the value of "len" is set from pb[i].len. Neither
"len" nor "i" is changed in the loop, so this statement can also be
removed.
-----Original Message-----
From: Michael Kelley <redacted>
Sent: Wednesday, September 15, 2021 12:22 PM
To: Tianyu Lan <redacted>; KY Srinivasan <kys@microsoft.com>;
This is the case where a large enough contiguous physical memory chunk
could not be found. But rather than dropping all the way down to single
pages, would it make sense to try something smaller, but not 1? For
example, cut the alloc_unit in half and try again. But I'm not sure of
all the implications.
I had the same question. But probably gradually decrementing uses too much
time?
I think you are proposing this approach to allocating memory for the
send and receive buffers so that you can avoid having two virtual
mappings for the memory, per comments from Christop Hellwig. But
overall, the approach seems a bit complex and I wonder if it is worth it.
If allocating large contiguous chunks of physical memory is successful,
then there is some memory savings in that the data structures needed to
keep track of the physical pages is smaller than the equivalent page
tables might be. But if you have to revert to allocating individual
pages, then the memory savings is reduced.
Ultimately, the list of actual PFNs has to be kept somewhere. Another
approach would be to do the reverse of what hv_map_memory() from the v4
patch series does. I.e., you could do virt_to_phys() on each virtual
address that maps above VTOM, and subtract out the shared_gpa_boundary
to get the
list of actual PFNs that need to be freed. This way you don't have two
copies
of the list of PFNs -- one with and one without the shared_gpa_boundary
added.
But it comes at the cost of additional code so that may not be a great
idea.
I think what you have here works, and I don't have a clearly better
solution at the moment except perhaps to revert to the v4 solution and
just have two virtual mappings. I'll keep thinking about it. Maybe
Christop has other thoughts.
I wonder if it is necessary to have two different code paths here. The
allocating and freeing of the send and receive buffers is not perf
sensitive, and it seems like netvsc_alloc_pages() could be used
regardless of whether SNP Isolation is in effect. To my thinking, one
code path is better than two code paths unless there's a compelling
reason to have two.
I still prefer keeping the simple vzalloc for the non isolated VMs, because
simple code path usually means more robust.
I don't know how much time difference between the two, but in some cases
we really care about boot time?
Also in the multi vPort case for MANA, we potentially support hundreds of
vPorts, and there will be the same number of synthetic NICs associated with
them. So even small time difference in the initialization time may add up.
Thanks,
- Haiyang
The type of this field really should be phys_addr_t. In addition to
just making semantic sense, then it will match the return type from
virt_to_phys() and the input arg to memremap() since resource_size_t
is typedef'ed as phys_addr_t.
This is the case where a large enough contiguous physical memory chunk
could not be found. But rather than dropping all the way down to single
pages, would it make sense to try something smaller, but not 1? For
example, cut the alloc_unit in half and try again. But I'm not sure of
all the implications.
I had the same question. But probably gradually decrementing uses too much
time?
This version is to propose the solution. We may optimize this to try
smaller size until to single page if this is right direction.
From: Tianyu Lan <hidden> Date: 2021-09-16 14:44:14
On 9/16/2021 12:21 AM, Michael Kelley wrote:
I think you are proposing this approach to allocating memory for the send
and receive buffers so that you can avoid having two virtual mappings for
the memory, per comments from Christop Hellwig. But overall, the approach
seems a bit complex and I wonder if it is worth it. If allocating large contiguous
chunks of physical memory is successful, then there is some memory savings
in that the data structures needed to keep track of the physical pages is
smaller than the equivalent page tables might be. But if you have to revert
to allocating individual pages, then the memory savings is reduced.
Yes, this version follows idea from Christop in the previous
discussion.(https://lkml.org/lkml/2021/9/2/112)
This patch shows the implementation and check whether this is a right
direction.
Ultimately, the list of actual PFNs has to be kept somewhere. Another approach
would be to do the reverse of what hv_map_memory() from the v4 patch
series does. I.e., you could do virt_to_phys() on each virtual address that
maps above VTOM, and subtract out the shared_gpa_boundary to get the
list of actual PFNs that need to be freed.
virt_to_phys() doesn't work for virtual address returned by
vmap/vmap_pfn() (just like it doesn't work for va returned by
vmalloc()). The pfn above vTom doesn't have struct page backing and
vmap_pfn() populates the pfn directly in the pte.(Please see the
vmap_pfn_apply()). So it's not easy to convert the va to pa.
This way you don't have two copies
of the list of PFNs -- one with and one without the shared_gpa_boundary added.
But it comes at the cost of additional code so that may not be a great idea.
I think what you have here works, and I don't have a clearly better solution
at the moment except perhaps to revert to the v4 solution and just have two
virtual mappings. I'll keep thinking about it. Maybe Christop has other
thoughts.
From: Tianyu Lan <hidden> Date: 2021-09-22 10:34:52
Hi Christoph:
This patch follows your purposal in the previous discussion.
Could you have a look?
"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(https://lkml.org/lkml/2021/9/2/112)
Otherwise, I merge your previous change for swiotlb into patch 9
“x86/Swiotlb: Add Swiotlb bounce buffer remap function for HV IVM”
You previous change
link.(http://git.infradead.org/users/hch/misc.git/commit/8248f295928aded3364a1e54a4e0022e93d3610c)
Please have a look.
Thanks.
On 9/16/2021 12:21 AM, Michael Kelley wrote:
From: Tianyu Lan <redacted> Sent: Tuesday, September 14, 2021 6:39 AM
quoted
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 address will be returned. The swiotlb bounce buffer
has been masked to be visible to host during boot up.
Allocate rx/tx ring buffer via alloc_pages() in Isolation VM and map
these pages via vmap(). After calling vmbus_establish_gpadl() which
marks these pages visible to host, unmap these pages to release the
virtual address mapped with physical address below shared_gpa_boundary
and map them in the extra address space via vmap_pfn().
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Allocate rx/tx ring buffer via alloc_pages() in Isolation VM
* Map pages after calling vmbus_establish_gpadl().
* set dma_set_min_align_mask for netvsc driver.
Change since v3:
* Add comment to explain why not to use dma_map_sg()
* Fix some error handle.
---
drivers/net/hyperv/hyperv_net.h | 7 +
drivers/net/hyperv/netvsc.c | 287 +++++++++++++++++++++++++++++-
drivers/net/hyperv/netvsc_drv.c | 1 +
drivers/net/hyperv/rndis_filter.c | 2 +
include/linux/hyperv.h | 5 +
5 files changed, 296 insertions(+), 6 deletions(-)
@@ -1074,6 +1075,8 @@ struct netvsc_device {/* Receive buffer allocated by us but manages by NetVSP */void*recv_buf;+structpage**recv_pages;+u32recv_page_count;u32recv_buf_size;/* allocated bytes */structvmbus_gpadlrecv_buf_gpadl_handle;u32recv_section_cnt;
@@ -1082,6 +1085,8 @@ struct netvsc_device {/* Send buffer allocated by us */void*send_buf;+structpage**send_pages;+u32send_page_count;u32send_buf_size;structvmbus_gpadlsend_buf_gpadl_handle;u32send_section_cnt;
@@ -330,6 +353,108 @@ int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx)returnnvchan->mrc.slots?0:-ENOMEM;}+void*netvsc_alloc_pages(structpage***pages_array,unsignedint*array_len,+unsignedlongsize)+{+structpage*page,**pages,**vmap_pages;+unsignedlongpg_count=size>>PAGE_SHIFT;+intalloc_unit=MAX_ORDER_NR_PAGES;+inti,j,vmap_page_index=0;+void*vaddr;++if(pg_count<alloc_unit)+alloc_unit=1;++/* vmap() accepts page array with PAGE_SIZE as unit while try to+*allocatehighorderpageshereinordertosavepagearrayspace.+*vmap_pages[]isusedasinputparameterofvmap().pages[]isto+*storeallocatedpagesandmapthemlater.+*/+vmap_pages=kmalloc_array(pg_count,sizeof(*vmap_pages),GFP_KERNEL);+if(!vmap_pages)+returnNULL;++retry:+*array_len=pg_count/alloc_unit;+pages=kmalloc_array(*array_len,sizeof(*pages),GFP_KERNEL);+if(!pages)+gotocleanup;++for(i=0;i<*array_len;i++){+page=alloc_pages(GFP_KERNEL|__GFP_ZERO,+get_order(alloc_unit<<PAGE_SHIFT));+if(!page){+/* Try allocating small pages if high order pages are not available. */+if(alloc_unit==1){+gotocleanup;+}else{
The "else" clause isn't really needed because of the goto cleanup above. Then
the indentation of the code below could be reduced by one level.
This is the case where a large enough contiguous physical memory chunk could
not be found. But rather than dropping all the way down to single pages,
would it make sense to try something smaller, but not 1? For example,
cut the alloc_unit in half and try again. But I'm not sure of all the implications.
I think you are proposing this approach to allocating memory for the send
and receive buffers so that you can avoid having two virtual mappings for
the memory, per comments from Christop Hellwig. But overall, the approach
seems a bit complex and I wonder if it is worth it. If allocating large contiguous
chunks of physical memory is successful, then there is some memory savings
in that the data structures needed to keep track of the physical pages is
smaller than the equivalent page tables might be. But if you have to revert
to allocating individual pages, then the memory savings is reduced.
Ultimately, the list of actual PFNs has to be kept somewhere. Another approach
would be to do the reverse of what hv_map_memory() from the v4 patch
series does. I.e., you could do virt_to_phys() on each virtual address that
maps above VTOM, and subtract out the shared_gpa_boundary to get the
list of actual PFNs that need to be freed. This way you don't have two copies
of the list of PFNs -- one with and one without the shared_gpa_boundary added.
But it comes at the cost of additional code so that may not be a great idea.
I think what you have here works, and I don't have a clearly better solution
at the moment except perhaps to revert to the v4 solution and just have two
virtual mappings. I'll keep thinking about it. Maybe Christop has other
thoughts.
I wonder if it is necessary to have two different code paths here. The
allocating and freeing of the send and receive buffers is not perf
sensitive, and it seems like netvsc_alloc_pages() could be used
regardless of whether SNP Isolation is in effect. To my thinking,
one code path is better than two code paths unless there's a
compelling reason to have two.
quoted
if (!net_device->recv_buf) {
netdev_err(ndev,
"unable to allocate receive buffer of size %u\n",
@@ -375,6 +507,27 @@ static int netvsc_init_buf(struct hv_device *device, goto cleanup; }+ if (hv_isolation_type_snp()) {+ alloc_unit = (buf_size / net_device->recv_page_count)+ >> PAGE_SHIFT;++ /* Unmap previous virtual address and map pages in the extra+ * address space(above shared gpa boundary) in Isolation VM.+ */+ vunmap(net_device->recv_buf);+ net_device->recv_buf =+ netvsc_map_pages(net_device->recv_pages,+ net_device->recv_page_count,+ alloc_unit);+ if (!net_device->recv_buf) {+ netdev_err(ndev,+ "unable to allocate receive buffer of size %u\n",+ buf_size);+ ret = -ENOMEM;+ goto cleanup;+ }+ }+ /* Notify the NetVsp of the gpadl handle */ init_packet = &net_device->channel_init_pkt; memset(init_packet, 0, sizeof(struct nvsp_message));
@@ -456,13 +609,21 @@ static int netvsc_init_buf(struct hv_device *device, buf_size = device_info->send_sections * device_info->send_section_size; buf_size = round_up(buf_size, PAGE_SIZE);- net_device->send_buf = vzalloc(buf_size);+ if (hv_isolation_type_snp())+ net_device->send_buf =+ netvsc_alloc_pages(&net_device->send_pages,+ &net_device->send_page_count,+ buf_size);+ else+ net_device->send_buf = vzalloc(buf_size);+ if (!net_device->send_buf) { netdev_err(ndev, "unable to allocate send buffer of size %u\n", buf_size); ret = -ENOMEM; goto cleanup; }+ net_device->send_buf_size = buf_size; /* Establish the gpadl handle for this buffer on this
@@ -478,6 +639,27 @@ static int netvsc_init_buf(struct hv_device *device, goto cleanup; }+ if (hv_isolation_type_snp()) {+ alloc_unit = (buf_size / net_device->send_page_count)+ >> PAGE_SHIFT;++ /* Unmap previous virtual address and map pages in the extra+ * address space(above shared gpa boundary) in Isolation VM.+ */+ vunmap(net_device->send_buf);+ net_device->send_buf =+ netvsc_map_pages(net_device->send_pages,+ net_device->send_page_count,+ alloc_unit);+ if (!net_device->send_buf) {+ netdev_err(ndev,+ "unable to allocate receive buffer of size %u\n",+ buf_size);+ ret = -ENOMEM;+ goto cleanup;+ }+ }+ /* Notify the NetVsp of the gpadl handle */ init_packet = &net_device->channel_init_pkt; memset(init_packet, 0, sizeof(struct nvsp_message));
@@ -948,6 +1131,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 because the+ * 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.+ */+static 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);
With the DMA min align mask now being set, the offset within
the Hyper-V page won't be changed by dma_map_single(). So I
think the above statement can be removed.
quoted
+ pb[i].len = len;
A few lines above, the value of "len" is set from pb[i].len. Neither
"len" nor "i" is changed in the loop, so this statement can also be
removed.
From: Tianyu Lan <hidden> Date: 2021-09-27 14:27:04
Hi Christoph:
Gentile ping. The swiotlb and shared memory mapping changes in this
patchset needs your reivew. Could you have a look?
Thanks.
On 9/22/2021 6:34 PM, Tianyu Lan wrote:
Hi Christoph:
This patch follows your purposal in the previous discussion.
Could you have a look?
"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(https://lkml.org/lkml/2021/9/2/112)
Otherwise, I merge your previous change for swiotlb into patch 9
“x86/Swiotlb: Add Swiotlb bounce buffer remap function for HV IVM”
You previous change
link.(http://git.infradead.org/users/hch/misc.git/commit/8248f295928aded3364a1e54a4e0022e93d3610c)
Please have a look.
Thanks.
On 9/16/2021 12:21 AM, Michael Kelley wrote:
quoted
From: Tianyu Lan <redacted> Sent: Tuesday, September 14,
2021 6:39 AM
quoted
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 address will be returned. The swiotlb bounce buffer
has been masked to be visible to host during boot up.
Allocate rx/tx ring buffer via alloc_pages() in Isolation VM and map
these pages via vmap(). After calling vmbus_establish_gpadl() which
marks these pages visible to host, unmap these pages to release the
virtual address mapped with physical address below shared_gpa_boundary
and map them in the extra address space via vmap_pfn().
Signed-off-by: Tianyu Lan <redacted>
---
Change since v4:
* Allocate rx/tx ring buffer via alloc_pages() in Isolation VM
* Map pages after calling vmbus_establish_gpadl().
* set dma_set_min_align_mask for netvsc driver.
Change since v3:
* Add comment to explain why not to use dma_map_sg()
* Fix some error handle.
---
drivers/net/hyperv/hyperv_net.h | 7 +
drivers/net/hyperv/netvsc.c | 287 +++++++++++++++++++++++++++++-
drivers/net/hyperv/netvsc_drv.c | 1 +
drivers/net/hyperv/rndis_filter.c | 2 +
include/linux/hyperv.h | 5 +
5 files changed, 296 insertions(+), 6 deletions(-)
diff --git a/drivers/net/hyperv/hyperv_net.h
b/drivers/net/hyperv/hyperv_net.h
index 315278a7cf88..87e8c74398a5 100644
*head)
{
struct netvsc_device *nvdev
= container_of(head, struct netvsc_device, rcu);
+ unsigned int alloc_unit;
int i;
kfree(nvdev->extension);
- vfree(nvdev->recv_buf);
- vfree(nvdev->send_buf);
+
+ if (nvdev->recv_pages) {
+ alloc_unit = (nvdev->recv_buf_size /
+ nvdev->recv_page_count) >> PAGE_SHIFT;
+
+ vunmap(nvdev->recv_buf);
+ for (i = 0; i < nvdev->recv_page_count; i++)
+ __free_pages(nvdev->recv_pages[i], alloc_unit);
+ } else {
+ vfree(nvdev->recv_buf);
+ }
+
+ if (nvdev->send_pages) {
+ alloc_unit = (nvdev->send_buf_size /
+ nvdev->send_page_count) >> PAGE_SHIFT;
+
+ vunmap(nvdev->send_buf);
+ for (i = 0; i < nvdev->send_page_count; i++)
+ __free_pages(nvdev->send_pages[i], alloc_unit);
+ } else {
+ vfree(nvdev->send_buf);
+ }
+
kfree(nvdev->send_section_map);
for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
@@ -330,6 +353,108 @@ int netvsc_alloc_recv_comp_ring(struct
netvsc_device *net_device, u32 q_idx)
return nvchan->mrc.slots ? 0 : -ENOMEM;
}
+void *netvsc_alloc_pages(struct page ***pages_array, unsigned int
*array_len,
+ unsigned long size)
+{
+ struct page *page, **pages, **vmap_pages;
+ unsigned long pg_count = size >> PAGE_SHIFT;
+ int alloc_unit = MAX_ORDER_NR_PAGES;
+ int i, j, vmap_page_index = 0;
+ void *vaddr;
+
+ if (pg_count < alloc_unit)
+ alloc_unit = 1;
+
+ /* vmap() accepts page array with PAGE_SIZE as unit while try to
+ * allocate high order pages here in order to save page array
space.
+ * vmap_pages[] is used as input parameter of vmap(). pages[] is to
+ * store allocated pages and map them later.
+ */
+ vmap_pages = kmalloc_array(pg_count, sizeof(*vmap_pages),
GFP_KERNEL);
+ if (!vmap_pages)
+ return NULL;
+
+retry:
+ *array_len = pg_count / alloc_unit;
+ pages = kmalloc_array(*array_len, sizeof(*pages), GFP_KERNEL);
+ if (!pages)
+ goto cleanup;
+
+ for (i = 0; i < *array_len; i++) {
+ page = alloc_pages(GFP_KERNEL | __GFP_ZERO,
+ get_order(alloc_unit << PAGE_SHIFT));
+ if (!page) {
+ /* Try allocating small pages if high order pages are
not available. */
+ if (alloc_unit == 1) {
+ goto cleanup;
+ } else {
The "else" clause isn't really needed because of the goto cleanup
above. Then
the indentation of the code below could be reduced by one level.
This is the case where a large enough contiguous physical memory chunk
could
not be found. But rather than dropping all the way down to single pages,
would it make sense to try something smaller, but not 1? For example,
cut the alloc_unit in half and try again. But I'm not sure of all the
implications.
I think you are proposing this approach to allocating memory for the send
and receive buffers so that you can avoid having two virtual mappings for
the memory, per comments from Christop Hellwig. But overall, the
approach
seems a bit complex and I wonder if it is worth it. If allocating
large contiguous
chunks of physical memory is successful, then there is some memory
savings
in that the data structures needed to keep track of the physical pages is
smaller than the equivalent page tables might be. But if you have to
revert
to allocating individual pages, then the memory savings is reduced.
Ultimately, the list of actual PFNs has to be kept somewhere. Another
approach
would be to do the reverse of what hv_map_memory() from the v4 patch
series does. I.e., you could do virt_to_phys() on each virtual
address that
maps above VTOM, and subtract out the shared_gpa_boundary to get the
list of actual PFNs that need to be freed. This way you don't have
two copies
of the list of PFNs -- one with and one without the
shared_gpa_boundary added.
But it comes at the cost of additional code so that may not be a great
idea.
I think what you have here works, and I don't have a clearly better
solution
at the moment except perhaps to revert to the v4 solution and just
have two
virtual mappings. I'll keep thinking about it. Maybe Christop has other
thoughts.
I wonder if it is necessary to have two different code paths here. The
allocating and freeing of the send and receive buffers is not perf
sensitive, and it seems like netvsc_alloc_pages() could be used
regardless of whether SNP Isolation is in effect. To my thinking,
one code path is better than two code paths unless there's a
compelling reason to have two.
quoted
if (!net_device->recv_buf) {
netdev_err(ndev,
"unable to allocate receive buffer of size %u\n",
@@ -375,6 +507,27 @@ static int netvsc_init_buf(struct hv_device
*device,
goto cleanup;
}
+ if (hv_isolation_type_snp()) {
+ alloc_unit = (buf_size / net_device->recv_page_count)
+ >> PAGE_SHIFT;
+
+ /* Unmap previous virtual address and map pages in the extra
+ * address space(above shared gpa boundary) in Isolation VM.
+ */
+ vunmap(net_device->recv_buf);
+ net_device->recv_buf =
+ netvsc_map_pages(net_device->recv_pages,
+ net_device->recv_page_count,
+ alloc_unit);
+ if (!net_device->recv_buf) {
+ netdev_err(ndev,
+ "unable to allocate receive buffer of size %u\n",
+ buf_size);
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ }
+
/* Notify the NetVsp of the gpadl handle */
init_packet = &net_device->channel_init_pkt;
memset(init_packet, 0, sizeof(struct nvsp_message));
@@ -456,13 +609,21 @@ static int netvsc_init_buf(struct hv_device
*device,
buf_size = device_info->send_sections *
device_info->send_section_size;
buf_size = round_up(buf_size, PAGE_SIZE);
- net_device->send_buf = vzalloc(buf_size);
+ if (hv_isolation_type_snp())
+ net_device->send_buf =
+ netvsc_alloc_pages(&net_device->send_pages,
+ &net_device->send_page_count,
+ buf_size);
+ else
+ net_device->send_buf = vzalloc(buf_size);
+
if (!net_device->send_buf) {
netdev_err(ndev, "unable to allocate send buffer of size
%u\n",
buf_size);
ret = -ENOMEM;
goto cleanup;
}
+
net_device->send_buf_size = buf_size;
/* Establish the gpadl handle for this buffer on this
@@ -478,6 +639,27 @@ static int netvsc_init_buf(struct hv_device
*device,
goto cleanup;
}
+ if (hv_isolation_type_snp()) {
+ alloc_unit = (buf_size / net_device->send_page_count)
+ >> PAGE_SHIFT;
+
+ /* Unmap previous virtual address and map pages in the extra
+ * address space(above shared gpa boundary) in Isolation VM.
+ */
+ vunmap(net_device->send_buf);
+ net_device->send_buf =
+ netvsc_map_pages(net_device->send_pages,
+ net_device->send_page_count,
+ alloc_unit);
+ if (!net_device->send_buf) {
+ netdev_err(ndev,
+ "unable to allocate receive buffer of size %u\n",
+ buf_size);
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ }
+
/* Notify the NetVsp of the gpadl handle */
init_packet = &net_device->channel_init_pkt;
memset(init_packet, 0, sizeof(struct nvsp_message));
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 because the
+ * 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.
+ */
+static 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);
With the DMA min align mask now being set, the offset within
the Hyper-V page won't be changed by dma_map_single(). So I
think the above statement can be removed.
quoted
+ pb[i].len = len;
A few lines above, the value of "len" is set from pb[i].len. Neither
"len" nor "i" is changed in the loop, so this statement can also be
removed.
From: Christoph Hellwig <hch@lst.de> Date: 2021-09-28 05:39:26
On Mon, Sep 27, 2021 at 10:26:43PM +0800, Tianyu Lan wrote:
Hi Christoph:
Gentile ping. The swiotlb and shared memory mapping changes in this
patchset needs your reivew. Could you have a look?
I'm a little too busy for a review of such a huge patchset right now.
That being said here are my comments from a very quick review:
- the bare memremap usage in swiotlb looks strange and I'd
definitively expect a well documented wrapper.
- given that we can now hand out swiotlb memory for coherent mappings
we need to carefully audit what happens when this memremaped
memory gets mmaped or used through dma_get_sgtable
- the netscv changes I'm not happy with at all. A large part of it
is that the driver already has a bad structure, but this series
is making it significantly worse. We'll need to find a way
to use the proper dma mapping abstractions here. One option
if you want to stick to the double vmapped buffer would be something
like using dma_alloc_noncontigous plus a variant of
dma_vmap_noncontiguous that takes the shared_gpa_boundary into
account.
- given that we can now hand out swiotlb memory for coherent mappings
we need to carefully audit what happens when this memremaped
memory gets mmaped or used through dma_get_sgtable
OK. I check that.
- the netscv changes I'm not happy with at all. A large part of it
is that the driver already has a bad structure, but this series
is making it significantly worse. We'll need to find a way
to use the proper dma mapping abstractions here. One option
if you want to stick to the double vmapped buffer would be something
like using dma_alloc_noncontigous plus a variant of
dma_vmap_noncontiguous that takes the shared_gpa_boundary into
account.
From: Christoph Hellwig <hch@lst.de> Date: 2021-09-30 05:48:31
On Tue, Sep 28, 2021 at 05:23:31PM +0800, Tianyu Lan wrote:
quoted
- the bare memremap usage in swiotlb looks strange and I'd
definitively expect a well documented wrapper.
OK. Should the wrapper in the DMA code? How about dma_map_decrypted()
introduced in the V4?
A mentioned then the name is a pretty bad choice as it touches the dma_map*
namespace that it is not related to. I suspect just a little helper
in the swiotlb code that explains how it is used might be enogh for now.