From 91fe754984f583cf84a3f26853ef86a862732cbb Mon Sep 17 00:00:00 2001 From: Sreenad Menon Date: Fri, 26 Apr 2024 02:51:41 -0700 Subject: [PATCH] ANDROID: virt: gunyah: Add pvm firmware boot support Android protected virtual machines use "pvmfw" in their boot flow. Add support to nominate a region of memory for the pvmfw. When nominated, the region is shared to the VM as a memory parcel and is unmarked as a demand-paged region. Bug: 338347082 Change-Id: I57d203761fbe58eb935a158db7f62f762c3c82dd Signed-off-by: Sreenad Menon Signed-off-by: Elliot Berman --- drivers/virt/gunyah/rsc_mgr.h | 3 ++ drivers/virt/gunyah/rsc_mgr_rpc.c | 32 ++++++++++++++++ drivers/virt/gunyah/vm_mgr.c | 64 +++++++++++++++++++++++++++---- drivers/virt/gunyah/vm_mgr.h | 5 +++ include/uapi/linux/gunyah.h | 7 ++++ 5 files changed, 104 insertions(+), 7 deletions(-) diff --git a/drivers/virt/gunyah/rsc_mgr.h b/drivers/virt/gunyah/rsc_mgr.h index 2acaf8dff365..140f10c05efb 100644 --- a/drivers/virt/gunyah/rsc_mgr.h +++ b/drivers/virt/gunyah/rsc_mgr.h @@ -118,6 +118,9 @@ enum gunyah_rm_range_id { GUNYAH_RM_RANGE_ID_FIRMWARE = 1, }; +int gunyah_rm_vm_set_firmware_mem(struct gunyah_rm *rm, u16 vmid, struct gunyah_rm_mem_parcel *parcel, + u64 fw_offset, u64 fw_size); + int gunyah_rm_vm_set_address_layout(struct gunyah_rm *rm, u16 vmid, enum gunyah_rm_range_id range_id, u64 base_address, u64 size); diff --git a/drivers/virt/gunyah/rsc_mgr_rpc.c b/drivers/virt/gunyah/rsc_mgr_rpc.c index eabf8659eb8f..187b8824b45e 100644 --- a/drivers/virt/gunyah/rsc_mgr_rpc.c +++ b/drivers/virt/gunyah/rsc_mgr_rpc.c @@ -25,6 +25,7 @@ #define GUNYAH_RM_RPC_VM_GET_HYP_RESOURCES 0x56000020 #define GUNYAH_RM_RPC_VM_GET_VMID 0x56000024 #define GUNYAH_RM_RPC_VM_SET_BOOT_CONTEXT 0x56000031 +#define GUNYAH_RM_RPC_VM_SET_FIRMWARE_MEM 0x56000032 #define GUNYAH_RM_RPC_VM_SET_DEMAND_PAGING 0x56000033 #define GUNYAH_RM_RPC_VM_SET_ADDRESS_LAYOUT 0x56000034 /* clang-format on */ @@ -132,6 +133,15 @@ struct gunyah_rm_vm_set_address_layout_req { __le64 range_size; } __packed; +/* Call: VM_SET_FIRMWARE_MEM */ +struct gunyah_vm_set_firmware_mem_req { + __le16 vmid; + __le16 reserved; + __le32 mem_handle; + __le64 fw_offset; + __le64 fw_size; +} __packed; + /* * Several RM calls take only a VMID as a parameter and give only standard * response back. Deduplicate boilerplate code by using this common call. @@ -600,3 +610,25 @@ int gunyah_rm_vm_set_address_layout(struct gunyah_rm *rm, u16 vmid, sizeof(req), NULL, NULL); } ALLOW_ERROR_INJECTION(gunyah_rm_vm_set_address_layout, ERRNO); + +/** + * gunyah_rm_vm_set_firmware_mem() - Set the location of firmware for GH_RM_VM_AUTH_QCOM_ANDROID_PVM VMs + * @rm: Handle to a Gunyah resource manager. + * @vmid: VM identifier allocated with gh_rm_alloc_vmid. + * @parcel: Memory parcel where the firmware should be loaded. + * @fw_offset: offset into the memory parcel where the firmware should be loaded. + * @fw_size: Maxmimum size of the fw that can be loaded. + */ +int gunyah_rm_vm_set_firmware_mem(struct gunyah_rm *rm, u16 vmid, struct gunyah_rm_mem_parcel *parcel, + u64 fw_offset, u64 fw_size) +{ + struct gunyah_vm_set_firmware_mem_req req = { + .vmid = cpu_to_le16(vmid), + .mem_handle = cpu_to_le32(parcel->mem_handle), + .fw_offset = cpu_to_le64(fw_offset), + .fw_size = cpu_to_le64(fw_size), + }; + + return gunyah_rm_call(rm, GUNYAH_RM_RPC_VM_SET_FIRMWARE_MEM, &req, sizeof(req), NULL, NULL); +} +ALLOW_ERROR_INJECTION(gunyah_rm_vm_set_firmware_mem, ERRNO); diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c index d39364503ad3..af9c88f798fa 100644 --- a/drivers/virt/gunyah/vm_mgr.c +++ b/drivers/virt/gunyah/vm_mgr.c @@ -615,7 +615,8 @@ int gunyah_gup_setup_demand_paging(struct gunyah_vm *ghvm) down_read(&ghvm->bindings_lock); mt_for_each(&ghvm->bindings, b, index, ULONG_MAX) - if (b->share_type == VM_MEM_LEND) + if (b->share_type == VM_MEM_LEND && + (b->guest_phys_addr != ghvm->fw.config.guest_phys_addr)) count++; if (!count) @@ -629,7 +630,8 @@ int gunyah_gup_setup_demand_paging(struct gunyah_vm *ghvm) index = i = 0; mt_for_each(&ghvm->bindings, b, index, ULONG_MAX) { - if (b->share_type != VM_MEM_LEND) + if (b->share_type != VM_MEM_LEND || + (b->guest_phys_addr == ghvm->fw.config.guest_phys_addr)) continue; entries[i].phys_addr = cpu_to_le64(b->guest_phys_addr); entries[i].size = cpu_to_le64(b->size); @@ -680,6 +682,18 @@ static int gunyah_vm_start(struct gunyah_vm *ghvm) goto err; } + if (ghvm->auth == GUNYAH_RM_VM_AUTH_QCOM_ANDROID_PVM) { + ghvm->fw.parcel_start = ghvm->fw.config.guest_phys_addr >> PAGE_SHIFT; + ghvm->fw.parcel_pages = ghvm->fw.config.size >> PAGE_SHIFT; + ret = gunyah_gup_share_parcel(ghvm, &ghvm->fw.parcel, + &ghvm->fw.parcel_start, + &ghvm->fw.parcel_pages); + if (ret) { + dev_warn(ghvm->parent, + "Failed to allocate parcel for FW: %d\n", ret); + goto err; + } + } ret = gunyah_rm_vm_configure(ghvm->rm, ghvm->vmid, ghvm->auth, ghvm->dtb.parcel.mem_handle, 0, 0, ghvm->dtb.config.guest_phys_addr - @@ -691,6 +705,16 @@ static int gunyah_vm_start(struct gunyah_vm *ghvm) goto err; } + if (ghvm->auth == GUNYAH_RM_VM_AUTH_QCOM_ANDROID_PVM) { + ret = gunyah_rm_vm_set_firmware_mem(ghvm->rm, ghvm->vmid, &ghvm->fw.parcel, + ghvm->fw.config.guest_phys_addr - (ghvm->fw.parcel_start << PAGE_SHIFT), + ghvm->fw.config.size); + if (ret) { + pr_warn("Failed to configure pVM firmware\n"); + goto err; + } + } + ret = gunyah_gup_setup_demand_paging(ghvm); if (ret) { dev_warn(ghvm->parent, @@ -716,11 +740,13 @@ static int gunyah_vm_start(struct gunyah_vm *ghvm) } ghvm->vm_status = GUNYAH_RM_VM_STATUS_READY; - ret = gunyah_vm_fill_boot_context(ghvm); - if (ret) { - dev_warn(ghvm->parent, "Failed to setup boot context: %d\n", - ret); - goto err; + if (ghvm->auth != GUNYAH_RM_VM_AUTH_QCOM_ANDROID_PVM) { + ret = gunyah_vm_fill_boot_context(ghvm); + if (ret) { + dev_warn(ghvm->parent, "Failed to setup boot context: %d\n", + ret); + goto err; + } } ret = gunyah_rm_get_hyp_resources(ghvm->rm, ghvm->vmid, &resources); @@ -820,6 +846,21 @@ static long gunyah_vm_ioctl(struct file *filp, unsigned int cmd, r = 0; break; } + case GH_VM_ANDROID_SET_FW_CONFIG: { + struct gunyah_vm_firmware_config fw_config; + + if (copy_from_user(&fw_config, argp, sizeof(fw_config))) + return -EFAULT; + + if (overflows_type(fw_config.guest_phys_addr + fw_config.size, + u64)) + return -EOVERFLOW; + + ghvm->fw.config = fw_config; + ghvm->auth = GUNYAH_RM_VM_AUTH_QCOM_ANDROID_PVM; + r = 0; + break; + } case GUNYAH_VM_START: { r = gunyah_vm_ensure_started(ghvm); break; @@ -994,6 +1035,15 @@ static void _gunyah_vm_put(struct kref *kref) WARN_ON(!mtree_empty(&ghvm->mm)); mtree_destroy(&ghvm->mm); + if (ghvm->auth == GUNYAH_RM_VM_AUTH_QCOM_ANDROID_PVM) { + ret = gunyah_gup_reclaim_parcel(ghvm, &ghvm->fw.parcel, + ghvm->fw.parcel_start, + ghvm->fw.parcel_pages); + if (ret) + dev_err(ghvm->parent, + "Failed to reclaim firmware parcel: %d\n", ret); + } + if (ghvm->vm_status > GUNYAH_RM_VM_STATUS_NO_STATE) { gunyah_rm_notifier_unregister(ghvm->rm, &ghvm->nb); diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h index 7c4a8ad5494d..42337a4b7730 100644 --- a/drivers/virt/gunyah/vm_mgr.h +++ b/drivers/virt/gunyah/vm_mgr.h @@ -137,6 +137,11 @@ struct gunyah_vm { u64 parcel_start, parcel_pages; struct gunyah_rm_mem_parcel parcel; } dtb; + struct { + struct gunyah_vm_firmware_config config; + u64 parcel_start, parcel_pages; + struct gunyah_rm_mem_parcel parcel; + } fw; struct xarray boot_context; }; diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h index 1e63e6417c3b..93990b20ff34 100644 --- a/include/uapi/linux/gunyah.h +++ b/include/uapi/linux/gunyah.h @@ -380,4 +380,11 @@ struct gunyah_userspace_memory_region { #define GH_VM_ANDROID_LEND_USER_MEM _IOW(GH_ANDROID_IOCTL_TYPE, 0x11, \ struct gunyah_userspace_memory_region) +struct gunyah_vm_firmware_config { + __u64 guest_phys_addr; + __u64 size; +}; + +#define GH_VM_ANDROID_SET_FW_CONFIG _IOW(GH_ANDROID_IOCTL_TYPE, 0x12, \ + struct gunyah_vm_firmware_config) #endif