forked from Minki/linux
2cf2aa6a69
Commit5cf4537975
("dma-mapping: introduce a dma_common_find_pages helper") changed invalid input check in dma_common_free_remap() from: if (!area || !area->flags != VM_DMA_COHERENT) to if (!area || !area->flags != VM_DMA_COHERENT || !area->pages) which seem to produce false positives for memory obtained via dma_common_contiguous_remap() This triggers the following warning message when doing "reboot" on ZII VF610 Dev Board Rev B: WARNING: CPU: 0 PID: 1 at kernel/dma/remap.c:112 dma_common_free_remap+0x88/0x8c trying to free invalid coherent area: 9ef82980 Modules linked in: CPU: 0 PID: 1 Comm: systemd-shutdow Not tainted 5.3.0-rc6-next-20190820 #119 Hardware name: Freescale Vybrid VF5xx/VF6xx (Device Tree) Backtrace: [<8010d1ec>] (dump_backtrace) from [<8010d588>] (show_stack+0x20/0x24) r7:8015ed78 r6:00000009 r5:00000000 r4:9f4d9b14 [<8010d568>] (show_stack) from [<8077e3f0>] (dump_stack+0x24/0x28) [<8077e3cc>] (dump_stack) from [<801197a0>] (__warn.part.3+0xcc/0xe4) [<801196d4>] (__warn.part.3) from [<80119830>] (warn_slowpath_fmt+0x78/0x94) r6:00000070 r5:808e540c r4:81c03048 [<801197bc>] (warn_slowpath_fmt) from [<8015ed78>] (dma_common_free_remap+0x88/0x8c) r3:9ef82980 r2:808e53e0 r7:00001000 r6:a0b1e000 r5:a0b1e000 r4:00001000 [<8015ecf0>] (dma_common_free_remap) from [<8010fa9c>] (remap_allocator_free+0x60/0x68) r5:81c03048 r4:9f4d9b78 [<8010fa3c>] (remap_allocator_free) from [<801100d0>] (__arm_dma_free.constprop.3+0xf8/0x148) r5:81c03048 r4:9ef82900 [<8010ffd8>] (__arm_dma_free.constprop.3) from [<80110144>] (arm_dma_free+0x24/0x2c) r5:9f563410 r4:80110120 [<80110120>] (arm_dma_free) from [<8015d80c>] (dma_free_attrs+0xa0/0xdc) [<8015d76c>] (dma_free_attrs) from [<8020f3e4>] (dma_pool_destroy+0xc0/0x154) r8:9efa8860 r7:808f02f0 r6:808f02d0 r5:9ef82880 r4:9ef82780 [<8020f324>] (dma_pool_destroy) from [<805525d0>] (ehci_mem_cleanup+0x6c/0x150) r7:9f563410 r6:9efa8810 r5:00000000 r4:9efd0148 [<80552564>] (ehci_mem_cleanup) from [<80558e0c>] (ehci_stop+0xac/0xc0) r5:9efd0148 r4:9efd0000 [<80558d60>] (ehci_stop) from [<8053c4bc>] (usb_remove_hcd+0xf4/0x1b0) r7:9f563410 r6:9efd0074 r5:81c03048 r4:9efd0000 [<8053c3c8>] (usb_remove_hcd) from [<8056361c>] (host_stop+0x48/0xb8) r7:9f563410 r6:9efd0000 r5:9f5f4040 r4:9f5f5040 [<805635d4>] (host_stop) from [<80563d0c>] (ci_hdrc_host_destroy+0x34/0x38) r7:9f563410 r6:9f5f5040 r5:9efa8800 r4:9f5f4040 [<80563cd8>] (ci_hdrc_host_destroy) from [<8055ef18>] (ci_hdrc_remove+0x50/0x10c) [<8055eec8>] (ci_hdrc_remove) from [<804a2ed8>] (platform_drv_remove+0x34/0x4c) r7:9f563410 r6:81c4f99c r5:9efa8810 r4:9efa8810 [<804a2ea4>] (platform_drv_remove) from [<804a18a8>] (device_release_driver_internal+0xec/0x19c) r5:00000000 r4:9efa8810 [<804a17bc>] (device_release_driver_internal) from [<804a1978>] (device_release_driver+0x20/0x24) r7:9f563410 r6:81c41ed0 r5:9efa8810 r4:9f4a1dac [<804a1958>] (device_release_driver) from [<804a01b8>] (bus_remove_device+0xdc/0x108) [<804a00dc>] (bus_remove_device) from [<8049c204>] (device_del+0x150/0x36c) r7:9f563410 r6:81c03048 r5:9efa8854 r4:9efa8810 [<8049c0b4>] (device_del) from [<804a3368>] (platform_device_del.part.2+0x20/0x84) r10:9f563414 r9:809177e0 r8:81cb07dc r7:81c78320 r6:9f563454 r5:9efa8800 r4:9efa8800 [<804a3348>] (platform_device_del.part.2) from [<804a3420>] (platform_device_unregister+0x28/0x34) r5:9f563400 r4:9efa8800 [<804a33f8>] (platform_device_unregister) from [<8055dce0>] (ci_hdrc_remove_device+0x1c/0x30) r5:9f563400 r4:00000001 [<8055dcc4>] (ci_hdrc_remove_device) from [<805652ac>] (ci_hdrc_imx_remove+0x38/0x118) r7:81c78320 r6:9f563454 r5:9f563410 r4:9f541010 [<8056538c>] (ci_hdrc_imx_shutdown) from [<804a2970>] (platform_drv_shutdown+0x2c/0x30) [<804a2944>] (platform_drv_shutdown) from [<8049e4fc>] (device_shutdown+0x158/0x1f0) [<8049e3a4>] (device_shutdown) from [<8013ac80>] (kernel_restart_prepare+0x44/0x48) r10:00000058 r9:9f4d8000 r8:fee1dead r7:379ce700 r6:81c0b280 r5:81c03048 r4:00000000 [<8013ac3c>] (kernel_restart_prepare) from [<8013ad14>] (kernel_restart+0x1c/0x60) [<8013acf8>] (kernel_restart) from [<8013af84>] (__do_sys_reboot+0xe0/0x1d8) r5:81c03048 r4:00000000 [<8013aea4>] (__do_sys_reboot) from [<8013b0ec>] (sys_reboot+0x18/0x1c) r8:80101204 r7:00000058 r6:00000000 r5:00000000 r4:00000000 [<8013b0d4>] (sys_reboot) from [<80101000>] (ret_fast_syscall+0x0/0x54) Exception stack(0x9f4d9fa8 to 0x9f4d9ff0) 9fa0: 00000000 00000000 fee1dead 28121969 01234567 379ce700 9fc0: 00000000 00000000 00000000 00000058 00000000 00000000 00000000 00016d04 9fe0: 00028e0c 7ec87c64 000135ec 76c1f410 Restore original invalid input check in dma_common_free_remap() to avoid this problem. Fixes:5cf4537975
("dma-mapping: introduce a dma_common_find_pages helper") Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com> [hch: just revert the offending hunk instead of creating a new helper] Signed-off-by: Christoph Hellwig <hch@lst.de>
269 lines
6.2 KiB
C
269 lines
6.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
* Copyright (c) 2014 The Linux Foundation
|
|
*/
|
|
#include <linux/dma-direct.h>
|
|
#include <linux/dma-noncoherent.h>
|
|
#include <linux/dma-contiguous.h>
|
|
#include <linux/init.h>
|
|
#include <linux/genalloc.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
struct page **dma_common_find_pages(void *cpu_addr)
|
|
{
|
|
struct vm_struct *area = find_vm_area(cpu_addr);
|
|
|
|
if (!area || area->flags != VM_DMA_COHERENT)
|
|
return NULL;
|
|
return area->pages;
|
|
}
|
|
|
|
static struct vm_struct *__dma_common_pages_remap(struct page **pages,
|
|
size_t size, pgprot_t prot, const void *caller)
|
|
{
|
|
struct vm_struct *area;
|
|
|
|
area = get_vm_area_caller(size, VM_DMA_COHERENT, caller);
|
|
if (!area)
|
|
return NULL;
|
|
|
|
if (map_vm_area(area, prot, pages)) {
|
|
vunmap(area->addr);
|
|
return NULL;
|
|
}
|
|
|
|
return area;
|
|
}
|
|
|
|
/*
|
|
* Remaps an array of PAGE_SIZE pages into another vm_area.
|
|
* Cannot be used in non-sleeping contexts
|
|
*/
|
|
void *dma_common_pages_remap(struct page **pages, size_t size,
|
|
pgprot_t prot, const void *caller)
|
|
{
|
|
struct vm_struct *area;
|
|
|
|
area = __dma_common_pages_remap(pages, size, prot, caller);
|
|
if (!area)
|
|
return NULL;
|
|
|
|
area->pages = pages;
|
|
|
|
return area->addr;
|
|
}
|
|
|
|
/*
|
|
* Remaps an allocated contiguous region into another vm_area.
|
|
* Cannot be used in non-sleeping contexts
|
|
*/
|
|
void *dma_common_contiguous_remap(struct page *page, size_t size,
|
|
pgprot_t prot, const void *caller)
|
|
{
|
|
int i;
|
|
struct page **pages;
|
|
struct vm_struct *area;
|
|
|
|
pages = kmalloc(sizeof(struct page *) << get_order(size), GFP_KERNEL);
|
|
if (!pages)
|
|
return NULL;
|
|
|
|
for (i = 0; i < (size >> PAGE_SHIFT); i++)
|
|
pages[i] = nth_page(page, i);
|
|
|
|
area = __dma_common_pages_remap(pages, size, prot, caller);
|
|
|
|
kfree(pages);
|
|
|
|
if (!area)
|
|
return NULL;
|
|
return area->addr;
|
|
}
|
|
|
|
/*
|
|
* Unmaps a range previously mapped by dma_common_*_remap
|
|
*/
|
|
void dma_common_free_remap(void *cpu_addr, size_t size)
|
|
{
|
|
struct vm_struct *area = find_vm_area(cpu_addr);
|
|
|
|
if (!area || area->flags != VM_DMA_COHERENT) {
|
|
WARN(1, "trying to free invalid coherent area: %p\n", cpu_addr);
|
|
return;
|
|
}
|
|
|
|
unmap_kernel_range((unsigned long)cpu_addr, PAGE_ALIGN(size));
|
|
vunmap(cpu_addr);
|
|
}
|
|
|
|
#ifdef CONFIG_DMA_DIRECT_REMAP
|
|
static struct gen_pool *atomic_pool __ro_after_init;
|
|
|
|
#define DEFAULT_DMA_COHERENT_POOL_SIZE SZ_256K
|
|
static size_t atomic_pool_size __initdata = DEFAULT_DMA_COHERENT_POOL_SIZE;
|
|
|
|
static int __init early_coherent_pool(char *p)
|
|
{
|
|
atomic_pool_size = memparse(p, &p);
|
|
return 0;
|
|
}
|
|
early_param("coherent_pool", early_coherent_pool);
|
|
|
|
static gfp_t dma_atomic_pool_gfp(void)
|
|
{
|
|
if (IS_ENABLED(CONFIG_ZONE_DMA))
|
|
return GFP_DMA;
|
|
if (IS_ENABLED(CONFIG_ZONE_DMA32))
|
|
return GFP_DMA32;
|
|
return GFP_KERNEL;
|
|
}
|
|
|
|
static int __init dma_atomic_pool_init(void)
|
|
{
|
|
unsigned int pool_size_order = get_order(atomic_pool_size);
|
|
unsigned long nr_pages = atomic_pool_size >> PAGE_SHIFT;
|
|
struct page *page;
|
|
void *addr;
|
|
int ret;
|
|
|
|
if (dev_get_cma_area(NULL))
|
|
page = dma_alloc_from_contiguous(NULL, nr_pages,
|
|
pool_size_order, false);
|
|
else
|
|
page = alloc_pages(dma_atomic_pool_gfp(), pool_size_order);
|
|
if (!page)
|
|
goto out;
|
|
|
|
arch_dma_prep_coherent(page, atomic_pool_size);
|
|
|
|
atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
|
|
if (!atomic_pool)
|
|
goto free_page;
|
|
|
|
addr = dma_common_contiguous_remap(page, atomic_pool_size,
|
|
pgprot_dmacoherent(PAGE_KERNEL),
|
|
__builtin_return_address(0));
|
|
if (!addr)
|
|
goto destroy_genpool;
|
|
|
|
ret = gen_pool_add_virt(atomic_pool, (unsigned long)addr,
|
|
page_to_phys(page), atomic_pool_size, -1);
|
|
if (ret)
|
|
goto remove_mapping;
|
|
gen_pool_set_algo(atomic_pool, gen_pool_first_fit_order_align, NULL);
|
|
|
|
pr_info("DMA: preallocated %zu KiB pool for atomic allocations\n",
|
|
atomic_pool_size / 1024);
|
|
return 0;
|
|
|
|
remove_mapping:
|
|
dma_common_free_remap(addr, atomic_pool_size);
|
|
destroy_genpool:
|
|
gen_pool_destroy(atomic_pool);
|
|
atomic_pool = NULL;
|
|
free_page:
|
|
if (!dma_release_from_contiguous(NULL, page, nr_pages))
|
|
__free_pages(page, pool_size_order);
|
|
out:
|
|
pr_err("DMA: failed to allocate %zu KiB pool for atomic coherent allocation\n",
|
|
atomic_pool_size / 1024);
|
|
return -ENOMEM;
|
|
}
|
|
postcore_initcall(dma_atomic_pool_init);
|
|
|
|
bool dma_in_atomic_pool(void *start, size_t size)
|
|
{
|
|
if (unlikely(!atomic_pool))
|
|
return false;
|
|
|
|
return addr_in_gen_pool(atomic_pool, (unsigned long)start, size);
|
|
}
|
|
|
|
void *dma_alloc_from_pool(size_t size, struct page **ret_page, gfp_t flags)
|
|
{
|
|
unsigned long val;
|
|
void *ptr = NULL;
|
|
|
|
if (!atomic_pool) {
|
|
WARN(1, "coherent pool not initialised!\n");
|
|
return NULL;
|
|
}
|
|
|
|
val = gen_pool_alloc(atomic_pool, size);
|
|
if (val) {
|
|
phys_addr_t phys = gen_pool_virt_to_phys(atomic_pool, val);
|
|
|
|
*ret_page = pfn_to_page(__phys_to_pfn(phys));
|
|
ptr = (void *)val;
|
|
memset(ptr, 0, size);
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
bool dma_free_from_pool(void *start, size_t size)
|
|
{
|
|
if (!dma_in_atomic_pool(start, size))
|
|
return false;
|
|
gen_pool_free(atomic_pool, (unsigned long)start, size);
|
|
return true;
|
|
}
|
|
|
|
void *arch_dma_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle,
|
|
gfp_t flags, unsigned long attrs)
|
|
{
|
|
struct page *page = NULL;
|
|
void *ret;
|
|
|
|
size = PAGE_ALIGN(size);
|
|
|
|
if (!gfpflags_allow_blocking(flags)) {
|
|
ret = dma_alloc_from_pool(size, &page, flags);
|
|
if (!ret)
|
|
return NULL;
|
|
goto done;
|
|
}
|
|
|
|
page = __dma_direct_alloc_pages(dev, size, dma_handle, flags, attrs);
|
|
if (!page)
|
|
return NULL;
|
|
|
|
/* remove any dirty cache lines on the kernel alias */
|
|
arch_dma_prep_coherent(page, size);
|
|
|
|
/* create a coherent mapping */
|
|
ret = dma_common_contiguous_remap(page, size,
|
|
dma_pgprot(dev, PAGE_KERNEL, attrs),
|
|
__builtin_return_address(0));
|
|
if (!ret) {
|
|
__dma_direct_free_pages(dev, size, page);
|
|
return ret;
|
|
}
|
|
|
|
memset(ret, 0, size);
|
|
done:
|
|
*dma_handle = phys_to_dma(dev, page_to_phys(page));
|
|
return ret;
|
|
}
|
|
|
|
void arch_dma_free(struct device *dev, size_t size, void *vaddr,
|
|
dma_addr_t dma_handle, unsigned long attrs)
|
|
{
|
|
if (!dma_free_from_pool(vaddr, PAGE_ALIGN(size))) {
|
|
phys_addr_t phys = dma_to_phys(dev, dma_handle);
|
|
struct page *page = pfn_to_page(__phys_to_pfn(phys));
|
|
|
|
vunmap(vaddr);
|
|
__dma_direct_free_pages(dev, size, page);
|
|
}
|
|
}
|
|
|
|
long arch_dma_coherent_to_pfn(struct device *dev, void *cpu_addr,
|
|
dma_addr_t dma_addr)
|
|
{
|
|
return __phys_to_pfn(dma_to_phys(dev, dma_addr));
|
|
}
|
|
#endif /* CONFIG_DMA_DIRECT_REMAP */
|