From 8e3a62bf009a820f4fccf7186bae6ee546f54dfd Mon Sep 17 00:00:00 2001 From: Prakruthi Deepak Heragu Date: Mon, 24 Feb 2025 11:49:10 -0800 Subject: [PATCH] ANDROID: gunyah: Add CMA bindings support Allow VMMs to add a binding backed by CMA. Extend the generic Gunyah mem APIs to handle bindings backed by CMA. Bug: 399219478 Change-Id: I1f71641420c8c30f4dfad021d92f70ef6ffdfb29 Signed-off-by: Prakruthi Deepak Heragu --- drivers/virt/gunyah/vm_mgr.c | 13 +++ drivers/virt/gunyah/vm_mgr.h | 38 +++++++- drivers/virt/gunyah/vm_mgr_cma_mem.c | 138 +++++++++++++++++++++++++++ drivers/virt/gunyah/vm_mgr_mem.c | 30 ++++-- include/uapi/linux/gunyah.h | 18 ++++ 5 files changed, 229 insertions(+), 8 deletions(-) diff --git a/drivers/virt/gunyah/vm_mgr.c b/drivers/virt/gunyah/vm_mgr.c index 7c8bfda49f02..d0c21028f1da 100644 --- a/drivers/virt/gunyah/vm_mgr.c +++ b/drivers/virt/gunyah/vm_mgr.c @@ -898,6 +898,19 @@ static long gunyah_vm_ioctl(struct file *filp, unsigned int cmd, gunyah_gpa_to_gfn(range.size) - 1); break; } + case GH_VM_ANDROID_MAP_CMA_MEM: { + struct gunyah_map_cma_mem_args cma_mem; + + /* only allow owner task to add memory */ + if (ghvm->mm_s != current->mm) + return -EPERM; + + if (copy_from_user(&cma_mem, argp, sizeof(cma_mem))) + return -EFAULT; + + r = gunyah_vm_binding_cma_alloc(ghvm, &cma_mem); + break; + } case GUNYAH_VM_SET_BOOT_CONTEXT: { struct gunyah_vm_boot_context boot_ctx; diff --git a/drivers/virt/gunyah/vm_mgr.h b/drivers/virt/gunyah/vm_mgr.h index c580c5bd313e..71ecb2308062 100644 --- a/drivers/virt/gunyah/vm_mgr.h +++ b/drivers/virt/gunyah/vm_mgr.h @@ -27,8 +27,15 @@ struct gunyah_vm_parcel { }; struct gunyah_vm_binding { - enum { VM_MEM_USER } mem_type; - u64 userspace_addr; + enum { VM_MEM_USER, VM_MEM_CMA } mem_type; + union { + u64 userspace_addr; + struct { + struct file *file; + u32 fd; + u64 offset; + } cma; + }; struct gunyah_vm_parcel *vm_parcel; enum { VM_MEM_SHARE, VM_MEM_LEND } share_type; u64 guest_phys_addr; @@ -233,6 +240,15 @@ int gunyah_setup_demand_paging(struct gunyah_vm *ghvm, u64 start_gfn, #ifdef CONFIG_DMA_CMA int gunyah_cma_mem_init(void); void gunyah_cma_mem_exit(void); +int gunyah_vm_binding_cma_alloc(struct gunyah_vm *ghvm, + struct gunyah_map_cma_mem_args *cma_map); +int gunyah_cma_share_parcel(struct gunyah_vm *ghvm, + struct gunyah_vm_parcel *parcel, + struct gunyah_vm_binding *b, + u64 *gfn, u64 *nr); +int gunyah_cma_reclaim_parcel(struct gunyah_vm *ghvm, + struct gunyah_vm_parcel *parcel, + struct gunyah_vm_binding *b); #else static inline int gunyah_cma_mem_init(void) { @@ -241,6 +257,24 @@ static inline int gunyah_cma_mem_init(void) static inline void gunyah_cma_mem_exit(void) { } +static inline int gunyah_vm_binding_cma_alloc(struct gunyah_vm *ghvm, + struct gunyah_map_cma_mem_args *cma_map) +{ + return -ENOSYS; +} +static inline int gunyah_cma_share_parcel(struct gunyah_vm *ghvm, + struct gunyah_vm_parcel *parcel, + struct gunyah_vm_binding *b, + u64 *gfn, u64 *nr) +{ + return -EINVAL; +} +static inline int gunyah_cma_reclaim_parcel(struct gunyah_vm *ghvm, + struct gunyah_vm_parcel *parcel, + struct gunyah_vm_binding *b) +{ + return -EINVAL; +} #endif #endif diff --git a/drivers/virt/gunyah/vm_mgr_cma_mem.c b/drivers/virt/gunyah/vm_mgr_cma_mem.c index 5631ad33f38b..07ffe49c6287 100644 --- a/drivers/virt/gunyah/vm_mgr_cma_mem.c +++ b/drivers/virt/gunyah/vm_mgr_cma_mem.c @@ -112,6 +112,144 @@ static const struct file_operations gunyah_cma_fops = { .release = gunyah_cma_release, }; +int gunyah_cma_reclaim_parcel(struct gunyah_vm *ghvm, struct gunyah_vm_parcel *vm_parcel, + struct gunyah_vm_binding *b) +{ + struct gunyah_rm_mem_parcel *parcel = &vm_parcel->parcel; + int ret; + + if (parcel->mem_handle == GUNYAH_MEM_HANDLE_INVAL) + return 0; + + ret = gunyah_rm_mem_reclaim(ghvm->rm, parcel); + if (ret) { + dev_err(ghvm->parent, "Failed to reclaim parcel: %d\n", + ret); + /* We can't reclaim the pages -- hold onto the pages + * forever because we don't know what state the memory + * is in + */ + return ret; + } + parcel->mem_handle = GUNYAH_MEM_HANDLE_INVAL; + kfree(parcel->mem_entries); + kfree(parcel->acl_entries); + vm_parcel->start = 0; + vm_parcel->pages = 0; + b->vm_parcel = NULL; + fput(b->cma.file); + return ret; +} + +int gunyah_cma_share_parcel(struct gunyah_vm *ghvm, struct gunyah_vm_parcel *vm_parcel, + struct gunyah_vm_binding *b, u64 *gfn, u64 *nr) +{ + struct gunyah_rm_mem_parcel *parcel = &vm_parcel->parcel; + unsigned long offset; + struct gunyah_cma *cma; + struct file *file; + int ret; + + if ((*nr << PAGE_SHIFT) > b->size) + return -EINVAL; + + file = fget(b->cma.fd); + if (!file) + return -EINVAL; + + if (file->f_op != &gunyah_cma_fops) { + fput(file); + return -EINVAL; + } + + cma = file->private_data; + b->cma.file = file; + + parcel->n_mem_entries = 1; + parcel->mem_entries = kcalloc(parcel->n_mem_entries, sizeof(parcel->mem_entries[0]), + GFP_KERNEL_ACCOUNT); + if (!parcel->mem_entries) { + fput(file); + return -ENOMEM; + } + + offset = gunyah_gfn_to_gpa(*gfn) - b->guest_phys_addr; + parcel->mem_entries[0].size = cpu_to_le64(*nr << PAGE_SHIFT); + parcel->mem_entries[0].phys_addr = + cpu_to_le64(page_to_phys(cma->page + b->cma.offset + offset)); + + ret = gunyah_rm_mem_share(ghvm->rm, parcel); + if (ret) + goto free_mem_entries; + + vm_parcel->start = *gfn; + vm_parcel->pages = *nr; + b->vm_parcel = vm_parcel; + return ret; + +free_mem_entries: + kfree(parcel->mem_entries); + parcel->mem_entries = NULL; + parcel->n_mem_entries = 0; + fput(file); + return ret; +} + +int gunyah_vm_binding_cma_alloc(struct gunyah_vm *ghvm, + struct gunyah_map_cma_mem_args *cma_map) +{ + struct gunyah_vm_binding *binding; + struct file *file; + loff_t max_size; + int ret = 0; + + if (!cma_map->size || !PAGE_ALIGNED(cma_map->size) || + !PAGE_ALIGNED(cma_map->guest_addr)) + return -EINVAL; + + if (overflows_type(cma_map->guest_addr + cma_map->size, u64)) + return -EOVERFLOW; + + file = fget(cma_map->guest_mem_fd); + max_size = i_size_read(file_inode(file)); + if (cma_map->offset + cma_map->size > max_size) { + fput(file); + return -EOVERFLOW; + } + fput(file); + + binding = kzalloc(sizeof(*binding), GFP_KERNEL_ACCOUNT); + if (!binding) + return -ENOMEM; + + binding->mem_type = VM_MEM_CMA; + binding->cma.fd = cma_map->guest_mem_fd; + binding->cma.offset = cma_map->offset; + binding->guest_phys_addr = cma_map->guest_addr; + binding->label = cma_map->label; + binding->size = cma_map->size; + binding->flags = cma_map->flags; + binding->vm_parcel = NULL; + + if (binding->flags & GUNYAH_MEM_FORCE_LEND) + binding->share_type = VM_MEM_LEND; + else + binding->share_type = VM_MEM_SHARE; + + down_write(&ghvm->bindings_lock); + ret = mtree_insert_range(&ghvm->bindings, + gunyah_gpa_to_gfn(binding->guest_phys_addr), + gunyah_gpa_to_gfn(binding->guest_phys_addr + cma_map->size - 1), + binding, GFP_KERNEL); + + if (ret != 0) + kfree(binding); + + up_write(&ghvm->bindings_lock); + + return ret; +} + static long gunyah_cma_create_mem_fd(struct gunyah_cma *cma) { unsigned long flags = 0; diff --git a/drivers/virt/gunyah/vm_mgr_mem.c b/drivers/virt/gunyah/vm_mgr_mem.c index 729c4cebff4d..5cf28ecd8ff1 100644 --- a/drivers/virt/gunyah/vm_mgr_mem.c +++ b/drivers/virt/gunyah/vm_mgr_mem.c @@ -496,7 +496,13 @@ int gunyah_demand_page(struct gunyah_vm *ghvm, u64 gpa, bool write) ret = -ENOENT; goto unlock; } - ret = gunyah_gup_demand_page(ghvm, b, gpa, write); + + if (b->mem_type == VM_MEM_CMA) { + dev_warn(ghvm->parent, "Demand paging of CMA mem not supported\n"); + ret = -EOPNOTSUPP; + } else { + ret = gunyah_gup_demand_page(ghvm, b, gpa, write); + } unlock: up_read(&ghvm->bindings_lock); @@ -651,10 +657,18 @@ int gunyah_share_parcel(struct gunyah_vm *ghvm, struct gunyah_vm_parcel *vm_parc parcel->acl_entries[1].perms = GUNYAH_RM_ACL_R | GUNYAH_RM_ACL_W | GUNYAH_RM_ACL_X; } - ret = gunyah_gup_share_parcel(ghvm, vm_parcel, b, gfn, nr); - if (ret) { - dev_warn(ghvm->parent, "Failed to share GUP memory: %d\n", ret); - goto free_acl; + if (b->mem_type == VM_MEM_CMA) { + ret = gunyah_cma_share_parcel(ghvm, vm_parcel, b, gfn, nr); + if (ret) { + dev_warn(ghvm->parent, "Failed to share CMA memory: %d\n", ret); + goto free_acl; + } + } else { + ret = gunyah_gup_share_parcel(ghvm, vm_parcel, b, gfn, nr); + if (ret) { + dev_warn(ghvm->parent, "Failed to share GUP memory: %d\n", ret); + goto free_acl; + } } goto unlock; @@ -742,7 +756,11 @@ static int gunyah_reclaim_parcel(struct gunyah_vm *ghvm, ret = -ENOENT; goto unlock; } - ret = gunyah_gup_reclaim_parcel(ghvm, vm_parcel, b); + + if (b->mem_type == VM_MEM_CMA) + ret = gunyah_cma_reclaim_parcel(ghvm, vm_parcel, b); + else + ret = gunyah_gup_reclaim_parcel(ghvm, vm_parcel, b); unlock: up_write(&ghvm->bindings_lock); diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h index 1dc8a1babfc5..a41bedef6fe2 100644 --- a/include/uapi/linux/gunyah.h +++ b/include/uapi/linux/gunyah.h @@ -402,4 +402,22 @@ struct gunyah_address_range { */ #define GH_ANDROID_CREATE_CMA_MEM_FD _IO(GH_ANDROID_IOCTL_TYPE, 0x14) +/** + * struct gunyah_map_cma_mem_args - Description to provide CMA based guest memory into a VM + * @guest_addr: Location in guest address space to place the memory + * @flags: See &enum gunyah_map_flags. + * @guest_mem_fd: File descriptor created by GH_ANDROID_CREATE_CMA_MEM_FD + * @offset: Offset into the guest memory file + * @size: Size of the region to be mapped. + */ +struct gunyah_map_cma_mem_args { + __u32 label; + __u64 guest_addr; + __u32 flags; + __u32 guest_mem_fd; + __u64 offset; + __u64 size; +}; + +#define GH_VM_ANDROID_MAP_CMA_MEM _IOW(GH_ANDROID_IOCTL_TYPE, 0x15, struct gunyah_map_cma_mem_args) #endif