ANDROID: gunyah: cma_mem: Add gunyah CMA mem driver

This is a CMA allocator for VMs which need their memory to be backed by
contiguous memory. The driver creates /dev/<qtvm_name_cma> to represent
the qtvm's DMA pool. Once opened, VMM can make an IOCTL CREATE_CMA_MEM to
obtain an fd to the file created which represents the VM's memory backed
by the specific DMA pool.

Bug: 399219478
Change-Id: I63f092ed15ef209a8723de8c2b51df19115d1ecb
Signed-off-by: Prakruthi Deepak Heragu <quic_pheragu@quicinc.com>
This commit is contained in:
Prakruthi Deepak Heragu
2025-02-24 11:38:33 -08:00
committed by Todd Kjos
parent f51d178893
commit f9d64cf7d6
5 changed files with 332 additions and 0 deletions
+1
View File
@@ -3,6 +3,7 @@
obj-$(CONFIG_GUNYAH_HYPERCALLS) += gunyah.o
gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o vm_mgr.o vm_mgr_mem.o
gunyah_rsc_mgr-$(CONFIG_DMA_CMA) += vm_mgr_cma_mem.o
obj-$(CONFIG_GUNYAH) += gunyah_rsc_mgr.o gunyah_vcpu.o
obj-$(CONFIG_GUNYAH_PLATFORM_HOOKS) += gunyah_platform_hooks.o
obj-$(CONFIG_GUNYAH_QCOM_PLATFORM) += gunyah_qcom.o
+5
View File
@@ -876,6 +876,10 @@ static int __init gunyah_rm_init(void)
goto deregister_misc;
}
ret = gunyah_cma_mem_init();
if (ret)
pr_err("Failed to register gunyah CMA device\n");
__rm = no_free_ptr(rm);
return 0;
deregister_misc:
@@ -897,6 +901,7 @@ static void __exit gunyah_rm_exit(void)
if (!rm)
return;
gunyah_cma_mem_exit();
auxiliary_device_delete(&rm->adev);
misc_deregister(&rm->miscdev);
free_irq(rm->rx_ghrsc.irq, rm);
+13
View File
@@ -226,4 +226,17 @@ int gunyah_gup_share_parcel(struct gunyah_vm *ghvm,
u64 *gfn, u64 *nr);
int gunyah_gup_demand_page(struct gunyah_vm *ghvm, u64 gpa, bool write);
#ifdef CONFIG_DMA_CMA
int gunyah_cma_mem_init(void);
void gunyah_cma_mem_exit(void);
#else
static inline int gunyah_cma_mem_init(void)
{
return -EOPNOTSUPP;
}
static inline void gunyah_cma_mem_exit(void)
{
}
#endif
#endif
+307
View File
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "gunyah_vm_mgr_cma_mem: " fmt
#include <linux/anon_inodes.h>
#include <linux/cma.h>
#include <linux/file.h>
#include <linux/miscdevice.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include "vm_mgr.h"
struct gunyah_cma {
struct device dev;
struct file *file;
struct page *page;
struct miscdevice miscdev;
struct list_head list;
unsigned long size;
};
struct gunyah_cma_parent {
struct list_head gunyah_cma_children;
};
/*
* gunyah_cma_alloc - Allocate cma region.
* @cma: the gunyah cma memory
* @len: the size of the cma region.
*
* Uses cma_alloc to allocate contiguous memory region of size len.
*
* Return: The 0 on success or an error.
*/
static int gunyah_cma_alloc(struct gunyah_cma *cma, loff_t len)
{
pgoff_t pagecount = len >> PAGE_SHIFT;
unsigned long align = get_order(len);
loff_t max_size;
if (cma->page)
return -EINVAL;
max_size = i_size_read(file_inode(cma->file));
if (len > max_size)
return -EINVAL;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
cma->page = cma_alloc(cma->dev.cma_area, pagecount, align, false);
if (!cma->page)
return -ENOMEM;
return 0;
}
static int gunyah_cma_release(struct inode *inode, struct file *file)
{
struct gunyah_cma *cma = file->private_data;
loff_t max_size = i_size_read(file_inode(cma->file));
unsigned int count = PAGE_ALIGN(max_size) >> PAGE_SHIFT;
if (!cma->page)
return 0;
cma_release(cma->dev.cma_area, cma->page, count);
cma->page = NULL;
return 0;
}
static int gunyah_cma_mmap(struct file *file, struct vm_area_struct *vma)
{
struct gunyah_cma *cma = file->private_data;
unsigned long len = vma->vm_end - vma->vm_start;
int nr_pages = PAGE_ALIGN(len) >> PAGE_SHIFT;
struct page **pages;
int ret, i;
file_accessed(file);
pages = kvmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
if (!pages)
return -ENOMEM;
ret = gunyah_cma_alloc(cma, len);
if (ret < 0) {
kvfree(pages);
return ret;
}
for (i = 0; i < nr_pages; i++)
pages[i] = nth_page(cma->page, i);
ret = vm_map_pages_zero(vma, pages, nr_pages);
if (ret)
pr_err("Mapping memory failed: %d\n", ret);
kvfree(pages);
return ret;
}
static const struct file_operations gunyah_cma_fops = {
.owner = THIS_MODULE,
.llseek = generic_file_llseek,
.mmap = gunyah_cma_mmap,
.open = generic_file_open,
.release = gunyah_cma_release,
};
static long gunyah_cma_create_mem_fd(struct gunyah_cma *cma)
{
unsigned long flags = 0;
struct inode *inode;
struct file *file;
int fd, err;
flags |= O_CLOEXEC;
fd = get_unused_fd_flags(flags);
if (fd < 0)
return fd;
file = anon_inode_create_getfile("[gunyah-cma]", &gunyah_cma_fops,
cma, O_RDWR, NULL);
if (IS_ERR(file)) {
err = PTR_ERR(file);
goto err_put_fd;
}
inode = file->f_inode;
inode->i_mode |= S_IFREG;
/* Platform specific size of CMA per VM */
i_size_write(inode, cma->size);
file->f_flags |= O_LARGEFILE;
file->f_mapping = inode->i_mapping;
cma->file = file;
fd_install(fd, file);
return fd;
err_put_fd:
put_unused_fd(fd);
return err;
}
static long gunyah_cma_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct miscdevice *miscdev = filp->private_data;
struct gunyah_cma *cma = container_of(miscdev, struct gunyah_cma, miscdev);
switch (cmd) {
case GH_ANDROID_CREATE_CMA_MEM_FD: {
return gunyah_cma_create_mem_fd(cma);
}
default:
return -ENOTTY;
}
}
static const struct file_operations gunyah_cma_dev_fops = {
/* clang-format off */
.owner = THIS_MODULE,
.unlocked_ioctl = gunyah_cma_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.llseek = noop_llseek,
/* clang-format on */
};
static void gunyah_cma_device_release(struct device *dev)
{
struct gunyah_cma *cma = container_of(dev, struct gunyah_cma, dev);
kfree(cma);
}
static int gunyah_cma_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
const char **mem_name;
struct gunyah_cma_parent *pcma;
int mem_count, i, ret, err = 0;
struct device_node *mem_node;
struct reserved_mem *rmem;
mem_count = of_property_count_strings(node, "memory-region-names");
if (!mem_count)
return -EINVAL;
mem_name = kmalloc_array(mem_count, sizeof(char *), GFP_KERNEL);
if (!mem_name)
return -ENOMEM;
mem_count = of_property_read_string_array(node, "memory-region-names",
mem_name, mem_count);
if (mem_count < 0) {
err = -EINVAL;
goto err_mem_name;
}
pcma = devm_kzalloc(dev, sizeof(*pcma), GFP_KERNEL);
if (!pcma) {
err = -ENOMEM;
goto err_mem_name;
}
INIT_LIST_HEAD(&pcma->gunyah_cma_children);
for (i = 0; i < mem_count; i++) {
struct gunyah_cma *cma;
rmem = NULL;
cma = kzalloc(sizeof(*cma), GFP_KERNEL);
if (!cma) {
ret = -ENOMEM;
goto err_continue;
}
cma->miscdev.parent = &pdev->dev;
cma->miscdev.name = mem_name[i];
cma->miscdev.minor = MISC_DYNAMIC_MINOR;
cma->miscdev.fops = &gunyah_cma_dev_fops;
ret = misc_register(&cma->miscdev);
if (ret) {
kfree(cma);
goto err_continue;
}
device_initialize(&cma->dev);
cma->dev.parent = &pdev->dev;
cma->dev.release = gunyah_cma_device_release;
cma->dev.init_name = mem_name[i];
ret = of_reserved_mem_device_init_by_name(&cma->dev,
dev->of_node, mem_name[i]);
if (ret)
goto err_device;
mem_node = of_parse_phandle(dev->of_node, "memory-region", i);
if (mem_node)
rmem = of_reserved_mem_lookup(mem_node);
of_node_put(mem_node);
if (!rmem) {
dev_err(dev, "Failed to find reserved memory for %s\n", mem_name[i]);
goto err_device;
}
cma->size = rmem->size;
cma->page = NULL;
list_add(&cma->list, &pcma->gunyah_cma_children);
dev_dbg(dev, "Created a reserved cma pool for %s\n", mem_name[i]);
continue;
err_device:
misc_deregister(&cma->miscdev);
put_device(&cma->dev);
err_continue:
dev_err(dev, "Failed to create reserved cma pool for %s %d\n", mem_name[i], ret);
continue;
}
platform_set_drvdata(pdev, pcma);
err_mem_name:
kfree(mem_name);
return err;
}
static void gunyah_cma_remove(struct platform_device *pdev)
{
struct gunyah_cma_parent *pcma = platform_get_drvdata(pdev);
struct gunyah_cma *cma, *iter;
list_for_each_entry_safe(cma, iter, &pcma->gunyah_cma_children, list) {
misc_deregister(&cma->miscdev);
of_reserved_mem_device_release(&cma->dev);
put_device(&cma->dev);
}
}
static const struct of_device_id gunyah_cma_match_table[] = {
{ .compatible = "gunyah-cma-vm-mem"},
{}
};
static struct platform_driver gunyah_cma_driver = {
.probe = gunyah_cma_probe,
.remove_new = gunyah_cma_remove,
.driver = {
.name = "gunyah_cma_vm_mem_driver",
.of_match_table = gunyah_cma_match_table,
},
};
int gunyah_cma_mem_init(void)
{
return platform_driver_register(&gunyah_cma_driver);
}
void gunyah_cma_mem_exit(void)
{
platform_driver_unregister(&gunyah_cma_driver);
}
+6
View File
@@ -396,4 +396,10 @@ struct gunyah_address_range {
#define GH_VM_RECLAIM_REGION _IOW(GH_ANDROID_IOCTL_TYPE, 0x13, \
struct gunyah_address_range)
/**
* Obtain an fd for the CMA backed file.
*/
#define GH_ANDROID_CREATE_CMA_MEM_FD _IO(GH_ANDROID_IOCTL_TYPE, 0x14)
#endif