This patch introduces a cma-heap probe function, allowing users to register custom cma heaps in the device tree. A "memory-region" is bound to the cma heap at probe time allowing allocation of DMA buffers from that heap. Use cases: - registration of carved out secure heaps. Some devices are implementing secure memory by reserving a specific memory regions for that purpose. For example, this is the case of platforms making use of early version of ARM TrustZone. - registration of multiple memory regions at different locations for efficiency or HW integration reasons. For example, a peripheral may expect to share data at a specific location in RAM. This information could have been programmed by a FW prior to the kernel boot. * Zeroing of CMA heap allocation: In the case of secure CMA heaps used along with ARM TrustZone, the zeroing of the secure memory could result in a bus fault if performed with `kmap_atomic()` or `page_address()`. To prevent such scenario, the zeroing of the pages is done using a virtual pointer acquired from `vmap()` using: `pgprot_writecombine(PAGE_KERNEL)` as argument. * Idea of improvement: This patch could have an impact on the performance of devices as a result of using `pgprot_writecombine(PAGE_KERNEL)`. It could be prevented by allowing control of this argument via a parameter of some sort. The driver could then use or not `pgprot_writecombine(PAGE_KERNEL)` according to the use case defined by the system integrator. * Note to the reviewers: The patch was used for the development of the protected mode feature in Panthor CSF kernel driver and is not initially thought to land in the Linux kernel. It is mostly relevant if someone wants to reproduce the environment of testing. Please, raise interest if you think the patch has value in the Linux kernel. Signed-off-by: Florent Tomasin <florent.tomasin@xxxxxxx> --- drivers/dma-buf/heaps/cma_heap.c | 120 +++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/drivers/dma-buf/heaps/cma_heap.c b/drivers/dma-buf/heaps/cma_heap.c index 9512d050563a..8f17221311fd 100644 --- a/drivers/dma-buf/heaps/cma_heap.c +++ b/drivers/dma-buf/heaps/cma_heap.c @@ -18,6 +18,9 @@ #include <linux/io.h> #include <linux/mm.h> #include <linux/module.h> +#include <linux/of.h> +#include <linux/of_reserved_mem.h> +#include <linux/platform_device.h> #include <linux/scatterlist.h> #include <linux/slab.h> #include <linux/vmalloc.h> @@ -186,6 +189,7 @@ static int cma_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma) vma->vm_ops = &dma_heap_vm_ops; vma->vm_private_data = buffer; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); return 0; } @@ -194,7 +198,7 @@ static void *cma_heap_do_vmap(struct cma_heap_buffer *buffer) { void *vaddr; - vaddr = vmap(buffer->pages, buffer->pagecount, VM_MAP, PAGE_KERNEL); + vaddr = vmap(buffer->pages, buffer->pagecount, VM_MAP, pgprot_writecombine(PAGE_KERNEL)); if (!vaddr) return ERR_PTR(-ENOMEM); @@ -286,6 +290,7 @@ static struct dma_buf *cma_heap_allocate(struct dma_heap *heap, struct page *cma_pages; struct dma_buf *dmabuf; int ret = -ENOMEM; + void *vaddr; pgoff_t pg; buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); @@ -303,29 +308,6 @@ static struct dma_buf *cma_heap_allocate(struct dma_heap *heap, if (!cma_pages) goto free_buffer; - /* Clear the cma pages */ - if (PageHighMem(cma_pages)) { - unsigned long nr_clear_pages = pagecount; - struct page *page = cma_pages; - - while (nr_clear_pages > 0) { - void *vaddr = kmap_local_page(page); - - memset(vaddr, 0, PAGE_SIZE); - kunmap_local(vaddr); - /* - * Avoid wasting time zeroing memory if the process - * has been killed by SIGKILL. - */ - if (fatal_signal_pending(current)) - goto free_cma; - page++; - nr_clear_pages--; - } - } else { - memset(page_address(cma_pages), 0, size); - } - buffer->pages = kmalloc_array(pagecount, sizeof(*buffer->pages), GFP_KERNEL); if (!buffer->pages) { ret = -ENOMEM; @@ -335,6 +317,14 @@ static struct dma_buf *cma_heap_allocate(struct dma_heap *heap, for (pg = 0; pg < pagecount; pg++) buffer->pages[pg] = &cma_pages[pg]; + /* Clear the cma pages */ + vaddr = vmap(buffer->pages, pagecount, VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + if (!vaddr) + goto free_pages; + + memset(vaddr, 0, size); + vunmap(vaddr); + buffer->cma_pages = cma_pages; buffer->heap = cma_heap; buffer->pagecount = pagecount; @@ -366,17 +356,79 @@ static const struct dma_heap_ops cma_heap_ops = { .allocate = cma_heap_allocate, }; -static int __init __add_cma_heap(struct cma *cma, void *data) +static int cma_heap_probe(struct platform_device *pdev) { + struct dma_heap_export_info *exp_info; + struct cma_heap *cma_heap; + int ret; + + exp_info = devm_kzalloc(&pdev->dev, sizeof(*exp_info), GFP_KERNEL); + if (IS_ERR_OR_NULL(exp_info)) + return -ENOMEM; + + cma_heap = devm_kzalloc(&pdev->dev, sizeof(*cma_heap), GFP_KERNEL); + if (IS_ERR_OR_NULL(cma_heap)) + return -ENOMEM; + + ret = of_reserved_mem_device_init(&pdev->dev); + if (ret) + return ret; + + cma_heap->cma = dev_get_cma_area(&pdev->dev); + if (!cma_heap->cma) { + ret = -EINVAL; + goto error_reserved_mem; + } + + exp_info->name = cma_get_name(cma_heap->cma); + exp_info->ops = &cma_heap_ops; + exp_info->priv = cma_heap; + + cma_heap->heap = dma_heap_add(exp_info); + if (IS_ERR(cma_heap->heap)) { + ret = PTR_ERR(cma_heap->heap); + goto error_reserved_mem; + } + + return 0; + +error_reserved_mem: + of_reserved_mem_device_release(&pdev->dev); + + return ret; +} + +static const struct of_device_id dt_match[] = { + { .compatible = "linux,cma" }, + {} +}; +MODULE_DEVICE_TABLE(of, dt_match); + +static struct platform_driver cma_heap_driver = { + .probe = cma_heap_probe, + .driver = { + .name = "linux,cma", + .of_match_table = dt_match, + }, +}; + +static int __init cma_heap_init(void) +{ + struct cma *cma_area = dev_get_cma_area(NULL); struct cma_heap *cma_heap; struct dma_heap_export_info exp_info; + if (!cma_area) + return -EINVAL; + + /* Add default CMA heap */ cma_heap = kzalloc(sizeof(*cma_heap), GFP_KERNEL); if (!cma_heap) return -ENOMEM; - cma_heap->cma = cma; - exp_info.name = cma_get_name(cma); + cma_heap->cma = cma_area; + + exp_info.name = cma_get_name(cma_area); exp_info.ops = &cma_heap_ops; exp_info.priv = cma_heap; @@ -388,18 +440,8 @@ static int __init __add_cma_heap(struct cma *cma, void *data) return ret; } - return 0; + return platform_driver_register(&cma_heap_driver); } -static int __init add_default_cma_heap(void) -{ - struct cma *default_cma = dev_get_cma_area(NULL); - int ret = 0; - - if (default_cma) - ret = __add_cma_heap(default_cma, NULL); - - return ret; -} -module_init(add_default_cma_heap); +module_init(cma_heap_init); MODULE_DESCRIPTION("DMA-BUF CMA Heap"); -- 2.34.1