Message ID | 20210809175620.720923-8-ltykernel@gmail.com |
---|---|
State | New |
Headers | show |
Series | x86/Hyper-V: Add Hyper-V Isolation VM support | expand |
From: Tianyu Lan <ltykernel@gmail.com> Sent: Monday, August 9, 2021 10:56 AM > > 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_va to store > the remap address and unmap these va when disconnect vmbus. > > Signed-off-by: Tianyu Lan <Tianyu.Lan@microsoft.com> > --- > Change since v1: > * Not remap monitor pages in the non-SNP isolation VM. > --- > drivers/hv/connection.c | 65 +++++++++++++++++++++++++++++++++++++++ > drivers/hv/hyperv_vmbus.h | 1 + > 2 files changed, 66 insertions(+) > > diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c > index 6d315c1465e0..bf0ac3167bd2 100644 > --- a/drivers/hv/connection.c > +++ b/drivers/hv/connection.c > @@ -19,6 +19,7 @@ > #include <linux/vmalloc.h> > #include <linux/hyperv.h> > #include <linux/export.h> > +#include <linux/io.h> > #include <asm/mshyperv.h> > > #include "hyperv_vmbus.h" > @@ -104,6 +105,12 @@ int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version) > > msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages[0]); > msg->monitor_page2 = virt_to_phys(vmbus_connection.monitor_pages[1]); > + > + if (hv_isolation_type_snp()) { > + msg->monitor_page1 += ms_hyperv.shared_gpa_boundary; > + msg->monitor_page2 += ms_hyperv.shared_gpa_boundary; > + } > + > msg->target_vcpu = hv_cpu_number_to_vp_number(VMBUS_CONNECT_CPU); > > /* > @@ -148,6 +155,31 @@ int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version) > return -ECONNREFUSED; > } > > + if (hv_isolation_type_snp()) { > + vmbus_connection.monitor_pages_va[0] > + = vmbus_connection.monitor_pages[0]; > + vmbus_connection.monitor_pages[0] > + = memremap(msg->monitor_page1, HV_HYP_PAGE_SIZE, > + MEMREMAP_WB); > + if (!vmbus_connection.monitor_pages[0]) > + return -ENOMEM; This error case causes vmbus_negotiate_version() to return with vmbus_connection.con_state set to CONNECTED. But the caller never checks the returned error code except for ETIMEDOUT. So the caller will think that vmbus_negotiate_version() succeeded when it didn't. There may be some existing bugs in that error handling code. :-( > + > + vmbus_connection.monitor_pages_va[1] > + = vmbus_connection.monitor_pages[1]; > + vmbus_connection.monitor_pages[1] > + = memremap(msg->monitor_page2, HV_HYP_PAGE_SIZE, > + MEMREMAP_WB); > + if (!vmbus_connection.monitor_pages[1]) { > + memunmap(vmbus_connection.monitor_pages[0]); > + return -ENOMEM; > + } > + > + memset(vmbus_connection.monitor_pages[0], 0x00, > + HV_HYP_PAGE_SIZE); > + memset(vmbus_connection.monitor_pages[1], 0x00, > + HV_HYP_PAGE_SIZE); > + } > + I don't think the memset() calls are needed. The memory was originally allocated with hv_alloc_hyperv_zeroed_page(), so it should already be zeroed. > return ret; > } > > @@ -159,6 +191,7 @@ int vmbus_connect(void) > struct vmbus_channel_msginfo *msginfo = NULL; > int i, ret = 0; > __u32 version; > + u64 pfn[2]; > > /* Initialize the vmbus connection */ > vmbus_connection.conn_state = CONNECTING; > @@ -216,6 +249,16 @@ int vmbus_connect(void) > goto cleanup; > } > > + if (hv_is_isolation_supported()) { > + pfn[0] = virt_to_hvpfn(vmbus_connection.monitor_pages[0]); > + pfn[1] = virt_to_hvpfn(vmbus_connection.monitor_pages[1]); > + if (hv_mark_gpa_visibility(2, pfn, > + VMBUS_PAGE_VISIBLE_READ_WRITE)) { Note that hv_mark_gpa_visibility() will need an appropriate no-op stub so that this architecture independent code will compile for ARM64. > + ret = -EFAULT; > + goto cleanup; > + } > + } > + > msginfo = kzalloc(sizeof(*msginfo) + > sizeof(struct vmbus_channel_initiate_contact), > GFP_KERNEL); > @@ -284,6 +327,8 @@ int vmbus_connect(void) > > void vmbus_disconnect(void) > { > + u64 pfn[2]; > + > /* > * First send the unload request to the host. > */ > @@ -303,6 +348,26 @@ void vmbus_disconnect(void) > vmbus_connection.int_page = NULL; > } > > + if (hv_is_isolation_supported()) { > + if (vmbus_connection.monitor_pages_va[0]) { > + memunmap(vmbus_connection.monitor_pages[0]); > + vmbus_connection.monitor_pages[0] > + = vmbus_connection.monitor_pages_va[0]; > + vmbus_connection.monitor_pages_va[0] = NULL; > + } > + > + if (vmbus_connection.monitor_pages_va[1]) { > + memunmap(vmbus_connection.monitor_pages[1]); > + vmbus_connection.monitor_pages[1] > + = vmbus_connection.monitor_pages_va[1]; > + vmbus_connection.monitor_pages_va[1] = NULL; > + } > + > + pfn[0] = virt_to_hvpfn(vmbus_connection.monitor_pages[0]); > + pfn[1] = virt_to_hvpfn(vmbus_connection.monitor_pages[1]); > + hv_mark_gpa_visibility(2, pfn, VMBUS_PAGE_NOT_VISIBLE); > + } > + The code in this patch feels a bit more complicated than it needs to be. Altogether, there are two different virtual addresses and one physical address for each monitor page. The two virtual addresses are the one obtained from the original memory allocation, and which will be used to free the memory. The second virtual address is the one used to actually access the data, which is the same as the first virtual address for a non-isolated VM. The second VA is the result of memremap() call for an isolated VM. The vmbus_connection data structure should save all three values for each monitor page so they don't need to recomputed or moved around. Then: 1) For isolated and for non-isolated VMs, setup the virtual and physical addresses of the monitor pages in vmbus_connect(), and store them in the vmbus_connection data structure. The physical address should include the shared_gpa_boundary offset in the case of an isolated VM. At this point the two virtual addresses are the same. 2) vmbus_negotiate_version() just grabs the physical address from the vmbus_connection data structure. It doesn't make any changes to the virtual or physical addresses, which keeps it focused just on version negotiation. 3) Once vmbus_negotiate_version() is done, vmbus_connect() can determine the remapped virtual address, and store that. It can also change the visibility of the two pages using the previously stored physical address. 4) vmbus_disconnect() can do the memunmaps() and change the visibility if needed, and then free the memory using the address from the original allocation in Step 1. > hv_free_hyperv_page((unsigned long)vmbus_connection.monitor_pages[0]); > hv_free_hyperv_page((unsigned long)vmbus_connection.monitor_pages[1]); > vmbus_connection.monitor_pages[0] = NULL; > diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h > index 42f3d9d123a1..40bc0eff6665 100644 > --- a/drivers/hv/hyperv_vmbus.h > +++ b/drivers/hv/hyperv_vmbus.h > @@ -240,6 +240,7 @@ struct vmbus_connection { > * is child->parent notification > */ > struct hv_monitor_page *monitor_pages[2]; > + void *monitor_pages_va[2]; > struct list_head chn_msg_list; > spinlock_t channelmsg_lock; > > -- > 2.25.1
diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 6d315c1465e0..bf0ac3167bd2 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -19,6 +19,7 @@ #include <linux/vmalloc.h> #include <linux/hyperv.h> #include <linux/export.h> +#include <linux/io.h> #include <asm/mshyperv.h> #include "hyperv_vmbus.h" @@ -104,6 +105,12 @@ int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version) msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages[0]); msg->monitor_page2 = virt_to_phys(vmbus_connection.monitor_pages[1]); + + if (hv_isolation_type_snp()) { + msg->monitor_page1 += ms_hyperv.shared_gpa_boundary; + msg->monitor_page2 += ms_hyperv.shared_gpa_boundary; + } + msg->target_vcpu = hv_cpu_number_to_vp_number(VMBUS_CONNECT_CPU); /* @@ -148,6 +155,31 @@ int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version) return -ECONNREFUSED; } + if (hv_isolation_type_snp()) { + vmbus_connection.monitor_pages_va[0] + = vmbus_connection.monitor_pages[0]; + vmbus_connection.monitor_pages[0] + = memremap(msg->monitor_page1, HV_HYP_PAGE_SIZE, + MEMREMAP_WB); + if (!vmbus_connection.monitor_pages[0]) + return -ENOMEM; + + vmbus_connection.monitor_pages_va[1] + = vmbus_connection.monitor_pages[1]; + vmbus_connection.monitor_pages[1] + = memremap(msg->monitor_page2, HV_HYP_PAGE_SIZE, + MEMREMAP_WB); + if (!vmbus_connection.monitor_pages[1]) { + memunmap(vmbus_connection.monitor_pages[0]); + return -ENOMEM; + } + + memset(vmbus_connection.monitor_pages[0], 0x00, + HV_HYP_PAGE_SIZE); + memset(vmbus_connection.monitor_pages[1], 0x00, + HV_HYP_PAGE_SIZE); + } + return ret; } @@ -159,6 +191,7 @@ int vmbus_connect(void) struct vmbus_channel_msginfo *msginfo = NULL; int i, ret = 0; __u32 version; + u64 pfn[2]; /* Initialize the vmbus connection */ vmbus_connection.conn_state = CONNECTING; @@ -216,6 +249,16 @@ int vmbus_connect(void) goto cleanup; } + if (hv_is_isolation_supported()) { + pfn[0] = virt_to_hvpfn(vmbus_connection.monitor_pages[0]); + pfn[1] = virt_to_hvpfn(vmbus_connection.monitor_pages[1]); + if (hv_mark_gpa_visibility(2, pfn, + VMBUS_PAGE_VISIBLE_READ_WRITE)) { + ret = -EFAULT; + goto cleanup; + } + } + msginfo = kzalloc(sizeof(*msginfo) + sizeof(struct vmbus_channel_initiate_contact), GFP_KERNEL); @@ -284,6 +327,8 @@ int vmbus_connect(void) void vmbus_disconnect(void) { + u64 pfn[2]; + /* * First send the unload request to the host. */ @@ -303,6 +348,26 @@ void vmbus_disconnect(void) vmbus_connection.int_page = NULL; } + if (hv_is_isolation_supported()) { + if (vmbus_connection.monitor_pages_va[0]) { + memunmap(vmbus_connection.monitor_pages[0]); + vmbus_connection.monitor_pages[0] + = vmbus_connection.monitor_pages_va[0]; + vmbus_connection.monitor_pages_va[0] = NULL; + } + + if (vmbus_connection.monitor_pages_va[1]) { + memunmap(vmbus_connection.monitor_pages[1]); + vmbus_connection.monitor_pages[1] + = vmbus_connection.monitor_pages_va[1]; + vmbus_connection.monitor_pages_va[1] = NULL; + } + + pfn[0] = virt_to_hvpfn(vmbus_connection.monitor_pages[0]); + pfn[1] = virt_to_hvpfn(vmbus_connection.monitor_pages[1]); + hv_mark_gpa_visibility(2, pfn, VMBUS_PAGE_NOT_VISIBLE); + } + hv_free_hyperv_page((unsigned long)vmbus_connection.monitor_pages[0]); hv_free_hyperv_page((unsigned long)vmbus_connection.monitor_pages[1]); vmbus_connection.monitor_pages[0] = NULL; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 42f3d9d123a1..40bc0eff6665 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -240,6 +240,7 @@ struct vmbus_connection { * is child->parent notification */ struct hv_monitor_page *monitor_pages[2]; + void *monitor_pages_va[2]; struct list_head chn_msg_list; spinlock_t channelmsg_lock;