platform/broadcom/saibcm-modules/systems/bde/linux/kernel/linux_dma.c (856 lines of code) (raw):

/* * Copyright 2007-2020 Broadcom Inc. All rights reserved. * * Permission is granted to use, copy, modify and/or distribute this * software under either one of the licenses below. * * License Option 1: GPL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation (the "GPL"). * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 (GPLv2) for more details. * * You should have received a copy of the GNU General Public License * version 2 (GPLv2) along with this source code. * * * License Option 2: Broadcom Open Network Switch APIs (OpenNSA) license * * This software is governed by the Broadcom Open Network Switch APIs license: * https://www.broadcom.com/products/ethernet-connectivity/software/opennsa */ /* * $Id: linux_dma.c,v 1.414 Broadcom SDK $ * $Copyright: (c) 2016 Broadcom Corp. * All Rights Reserved.$ * * Linux Kernel BDE DMA memory allocation * * * DMA memory allocation modes * =========================== * * 1. Using private pool in kernel memory * -------------------------------------- * In this mode the BDE module will try to assemble a physically contiguous * of memory using the kernel page allocator. This memory block is then * administered by the mpool allocation functions. Note that once a system * has been running for a while, the memory fragmentation may prevent the * allocator from assembling a contiguous memory block, however, if the * module is loaded shortly after system startup, it is very unlikely to * fail. * * This allocation method is used by default. * * 2. Using private pool in high memory * ------------------------------------ * In this mode the BDE module will assume that unused physical memory is * present at the high_memory address, i.e. memory not managed by the Linux * memory manager. This memory block is mapped into kernel space and * administered by the mpool allocation functions. High memory must be * reserved using either the mem=xxx kernel parameter (recommended), or by * hardcoding the memory limit in the kernel image. * * The module parameter himem=1 enables this allocation mode. * * 3. Using kernel allocators (kmalloc, __get_free_pages) * ------------------------------------------------------ * In this mode all DMA memory is allocated from the kernel on the fly, i.e. * no private DMA memory pool will be created. If large memory blocks are * only allocated at system startup (or not at all), this allocation method * is the most flexible and memory-efficient, however, it is not recommended * for non-coherent memory platforms due to an overall system performance * degradation arising from the use of cache flush/invalidate instructions. * * The module parameter dmasize=0M enables this allocation mode, however if * DMA memory is requested from a user mode application, a private memory * pool will be created and used irrespectively. */ #include <gmodule.h> #include <linux-bde.h> #include <linux_dma.h> #include <mpool.h> #include <sdk_config.h> #if defined(IPROC_CMICD) && defined(CONFIG_OF) #include <linux/of.h> #endif #ifdef BCM_PLX9656_LOCAL_BUS #include <asm/cacheflush.h> #endif /* allocation types/methods for the DMA memory pool */ #define ALLOC_TYPE_CHUNK 0 /* use small allocations and join them */ #define ALLOC_TYPE_API 1 /* use one allocation */ #define ALLOC_TYPE_HIMEM 2 /* use high memory */ #if _SIMPLE_MEMORY_ALLOCATION_ #include <linux/dma-mapping.h> #if defined(CONFIG_CMA) && defined(CONFIG_CMA_SIZE_MBYTES) #define DMA_MAX_ALLOC_SIZE (CONFIG_CMA_SIZE_MBYTES * 1024 * 1024) #else #define DMA_MAX_ALLOC_SIZE (1 << (MAX_ORDER - 1 + PAGE_SHIFT)) /* Maximum size the kernel can allocate in one allocation */ #endif #endif /* _SIMPLE_MEMORY_ALLOCATION_ */ #if _SIMPLE_MEMORY_ALLOCATION_ == 1 /* Use Linux DMA API to allocate contiguous memory */ #define ALLOC_METHOD_DEFAULT ALLOC_TYPE_API #if defined(__arm__) #define USE_DMA_MMAP_COHERENT #define _PGPROT_NONCACHED(x) x = pgprot_noncached((x)) #elif defined(__aarch64__ ) #define USE_DMA_MMAP_COHERENT #define _PGPROT_NONCACHED(x) x = pgprot_writecombine((x)) #endif #else #define ALLOC_METHOD_DEFAULT ALLOC_TYPE_CHUNK #endif #ifndef _PGPROT_NONCACHED #ifdef REMAP_DMA_NONCACHED #define _PGPROT_NONCACHED(x) x = pgprot_noncached((x)) #else #define _PGPROT_NONCACHED(x) #endif #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)) #include <linux/slab.h> #define virt_to_bus virt_to_phys #define bus_to_virt phys_to_virt #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)) #define VIRT_TO_PAGE(p) virt_to_page((void*)(p)) #else #define VIRT_TO_PAGE(p) virt_to_page((p)) #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) #define BDE_DMA_MAPPING_ERROR(d, p) dma_mapping_error((d),(p)) #else #define BDE_DMA_MAPPING_ERROR(d, p) dma_mapping_error((p)) #endif #ifndef KMALLOC_MAX_SIZE #define KMALLOC_MAX_SIZE (1UL << (MAX_ORDER - 1 + PAGE_SHIFT)) #endif /* Compatibility */ #ifdef LKM_2_4 #define MEM_MAP_RESERVE mem_map_reserve #define MEM_MAP_UNRESERVE mem_map_unreserve #else /* LKM_2_6 */ #define MEM_MAP_RESERVE SetPageReserved #define MEM_MAP_UNRESERVE ClearPageReserved #endif /* LKM_2_x */ #ifndef GFP_DMA32 #define GFP_DMA32 0 #endif /* Flags for memory allocations */ #ifdef SAL_BDE_XLP static int mem_flags = GFP_ATOMIC | GFP_KERNEL | GFP_DMA; #else #if defined(CONFIG_ZONE_DMA32) static int mem_flags = GFP_ATOMIC | GFP_DMA32; #else static int mem_flags = GFP_ATOMIC | GFP_DMA; #endif #endif #ifdef IPROC_CMICD #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0) #ifndef COHERENT_ALLOC_USE_DMA_TO_PHYS #define COHERENT_ALLOC_USE_DMA_TO_PHYS 1 #endif #else #ifndef COHERENT_ALLOC_PHYS_IS_DMA_ADDR #define COHERENT_ALLOC_PHYS_IS_DMA_ADDR 1 #endif #endif #endif /* IPROC_CMICD */ /* * Macros that can be used by the build to control the technique of * setting the physical address for dma_alloc_coherent() */ #ifndef COHERENT_ALLOC_USE_DMA_TO_PHYS #define COHERENT_ALLOC_USE_DMA_TO_PHYS 0 #endif #ifndef COHERENT_ALLOC_PHYS_IS_DMA_ADDR #define COHERENT_ALLOC_PHYS_IS_DMA_ADDR 0 #endif #if COHERENT_ALLOC_USE_DMA_TO_PHYS #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,16,0)) #include <linux/dma-direct.h> #else #include <asm/dma-mapping.h> #endif #endif /* Macro to get the host physical address when using dma_alloc_coherent() */ #if COHERENT_ALLOC_USE_DMA_TO_PHYS #define HOST_PHYS_ADDR(_dev, _dma_a, _kvirt_a) \ ((_dev) ? dma_to_phys((_dev), (_dma_a)) : virt_to_phys(_kvirt_a)) #elif COHERENT_ALLOC_PHYS_IS_DMA_ADDR #define HOST_PHYS_ADDR(_dev, _dma_a, _kvirt_a) (_dma_a) #else #define HOST_PHYS_ADDR(_dev, _dma_a, _kvirt_a) (virt_to_phys(_kvirt_a)) #endif /* Debug output */ static int dma_debug = 0; module_param(dma_debug, int, 0); MODULE_PARM_DESC(dma_debug, "DMA debug output enable (default 0)."); /* DMA memory pool size */ static char *dmasize; LKM_MOD_PARAM(dmasize, "s", charp, 0); MODULE_PARM_DESC(dmasize, "Specify DMA memory size (default 4MB)"); /* Select DMA memory pool allocation method */ static int dmaalloc = ALLOC_METHOD_DEFAULT; LKM_MOD_PARAM(dmaalloc, "i", int, 0); MODULE_PARM_DESC(dmaalloc, "Select DMA memory allocation method"); /* Use high memory for DMA */ static char *himem; LKM_MOD_PARAM(himem, "s", charp, 0); MODULE_PARM_DESC(himem, "Use high memory for DMA (default no)"); /* Physical high memory address to use for DMA */ static char *himemaddr = 0; LKM_MOD_PARAM(himemaddr, "s", charp, 0); MODULE_PARM_DESC(himemaddr, "Physical address to use for high memory DMA"); /* DMA memory allocation */ #define ONE_KB 1024 #define ONE_MB (1024*1024) #define ONE_GB (1024*1024*1024) /* Default DMA memory size */ #ifdef SAL_BDE_DMA_MEM_DEFAULT #define DMA_MEM_DEFAULT (SAL_BDE_DMA_MEM_DEFAULT * ONE_MB) #else #define DMA_MEM_DEFAULT (8 * ONE_MB) #endif #ifdef BDE_EDK_SUPPORT typedef struct { phys_addr_t cpu_pbase; /* CPU physical base address of the DMA pool */ phys_addr_t dma_pbase; /* Bus base address of the DMA pool */ void __iomem *dma_vbase; uint32 size; /* Total size of the pool */ }_edk_dma_pool_t; static _edk_dma_pool_t _edk_dma_pool[LINUX_BDE_MAX_DEVICES]; static int _edk_use_dma_mapping = 0; #endif /* We try to assemble a contiguous segment from chunks of this size */ #define DMA_BLOCK_SIZE (512 * ONE_KB) typedef struct _dma_segment { struct list_head list; unsigned long req_size; /* Requested DMA segment size */ unsigned long blk_size; /* DMA block size */ unsigned long blk_order; /* DMA block size in alternate format */ unsigned long seg_size; /* Current DMA segment size */ unsigned long seg_begin; /* Logical address of segment */ unsigned long seg_end; /* Logical end address of segment */ unsigned long *blk_ptr; /* Array of logical DMA block addresses */ int blk_cnt_max; /* Maximum number of block to allocate */ int blk_cnt; /* Current number of blocks allocated */ } dma_segment_t; static unsigned int _dma_mem_size = DMA_MEM_DEFAULT; static mpool_handle_t _dma_pool = NULL; /* kernel virtual address of the DMA buffer pool */ static void __iomem *_dma_vbase = NULL; /* CPU physical address of the DMA buffer pool, used for mmap */ static phys_addr_t _cpu_pbase = 0; /* * DMA buffer poool PCIe bus address, it is either identical to the CPU * physical address or another address(IOVA) translated by IOMMU. */ static phys_addr_t _dma_pbase = 0; /* states of the DMA pool: */ /* not initialized */ #define DNA_POOL_INVALID 0 /* initialized and not yet mapped */ #define DMA_POOL_INITIALIZED 1 /* initialized and mapped */ #define DMA_POOL_MAPPED 2 /* initialization failed */ #define DMA_POOL_FAILED 3 /* Was the DMA buffer pool allocation attempted? */ static int _dma_pool_alloc_state = DNA_POOL_INVALID; static int _use_himem = 0; static unsigned long _himemaddr = 0; /* None-zero if the DMA buffer pool is not mapped to the bus address by the allocation, and needs to be mapped per device. */ static int _use_dma_mapping = 0; static LIST_HEAD(_dma_seg); extern int nodevices; #if _SIMPLE_MEMORY_ALLOCATION_ static struct device *_dma_alloc_coherent_device = NULL; #endif /* _SIMPLE_MEMORY_ALLOCATION_ */ #define DMA_DEV_INDEX 0 /* Device index to allocate memory pool */ #define DMA_DEV(n) lkbde_get_dma_dev(n) #define BDE_NUM_DEVICES(t) lkbde_get_num_devices(t) /* * Function: _find_largest_segment * * Purpose: * Find largest contiguous segment from a pool of DMA blocks. * Parameters: * dseg - DMA segment descriptor * Returns: * 0 on success, < 0 on error. * Notes: * Assembly stops if a segment of the requested segment size * has been obtained. * * Lower address bits of the DMA blocks are used as follows: * 0: Untagged * 1: Discarded block * 2: Part of largest contiguous segment * 3: Part of current contiguous segment */ static int _find_largest_segment(dma_segment_t *dseg) { int i, j, blks, found; unsigned long b, e, a; blks = dseg->blk_cnt; /* Clear all block tags */ for (i = 0; i < blks; i++) { dseg->blk_ptr[i] &= ~3; } for (i = 0; i < blks && dseg->seg_size < dseg->req_size; i++) { /* First block must be an untagged block */ if ((dseg->blk_ptr[i] & 3) == 0) { /* Initial segment size is the block size */ b = dseg->blk_ptr[i]; e = b + dseg->blk_size; dseg->blk_ptr[i] |= 3; /* Loop looking for adjacent blocks */ do { found = 0; for (j = i + 1; j < blks && (e - b) < dseg->req_size; j++) { a = dseg->blk_ptr[j]; /* Check untagged blocks only */ if ((a & 3) == 0) { if (a == (b - dseg->blk_size)) { /* Found adjacent block below current segment */ dseg->blk_ptr[j] |= 3; b = a; found = 1; } else if (a == e) { /* Found adjacent block above current segment */ dseg->blk_ptr[j] |= 3; e += dseg->blk_size; found = 1; } } } } while (found); if ((e - b) > dseg->seg_size) { /* The current block is largest so far */ dseg->seg_begin = b; dseg->seg_end = e; dseg->seg_size = e - b; /* Re-tag current and previous largest segment */ for (j = 0; j < blks; j++) { if ((dseg->blk_ptr[j] & 3) == 3) { /* Tag current segment as the largest */ dseg->blk_ptr[j] &= ~1; } else if ((dseg->blk_ptr[j] & 3) == 2) { /* Discard previous largest segment */ dseg->blk_ptr[j] ^= 3; } } } else { /* Discard all blocks in current segment */ for (j = 0; j < blks; j++) { if ((dseg->blk_ptr[j] & 3) == 3) { dseg->blk_ptr[j] &= ~2; } } } } } return 0; } /* * Function: _alloc_dma_blocks * * Purpose: * Allocate DMA blocks and add them to the pool. * Parameters: * dseg - DMA segment descriptor * blks - number of DMA blocks to allocate * Returns: * 0 on success, < 0 on error. * Notes: * DMA blocks are allocated using the page allocator. */ static int _alloc_dma_blocks(dma_segment_t *dseg, int blks) { int i, start; unsigned long addr; if (dseg->blk_cnt + blks > dseg->blk_cnt_max) { gprintk("No more DMA blocks\n"); return -1; } start = dseg->blk_cnt; for (i = 0; i < blks; i++) { /* * Note that we cannot use pci_alloc_consistent when we * want to be able to map DMA memory to user space. * * The GFP_DMA flag is omitted as this imposes the ISA * addressing limitations on x86 platforms. As long as * we have less than 1GB of memory, we can do PCI DMA * to all physical RAM locations. */ addr = __get_free_pages(mem_flags, dseg->blk_order); if (addr) { dseg->blk_ptr[start + i] = addr; ++dseg->blk_cnt; } else { gprintk("DMA allocation failed: allocated %d of %d " "requested blocks\n", i, blks); return -1; } } return 0; } /* * Function: _dma_segment_alloc * * Purpose: * Allocate large physically contiguous DMA segment. * Parameters: * size - requested DMA segment size * blk_size - assemble segment from blocks of this size * Returns: * DMA segment descriptor. * Notes: * Since we cannot allocate large blocks of contiguous * memory from the kernel, we simply keep allocating * smaller chunks until we can assemble a contiguous * block of the desired size. * * When system allowed maximum bytes of memory has been allocated * without a successful assembly of a contiguous DMA * segment, the allocation function will return the * largest contiguous segment found so far. It is up * to the calling function to decide whether this * amount is sufficient to proceed. */ static dma_segment_t * _dma_segment_alloc(size_t size, size_t blk_size) { dma_segment_t *dseg; int i, blk_ptr_size; unsigned long page_addr; struct sysinfo si; /* Sanity check */ if (size == 0 || blk_size == 0) { return NULL; } /* Allocate an initialize DMA segment descriptor */ if ((dseg = kmalloc(sizeof(dma_segment_t), GFP_KERNEL)) == NULL) { return NULL; } memset(dseg, 0, sizeof(dma_segment_t)); dseg->req_size = size; dseg->blk_size = PAGE_ALIGN(blk_size); while ((PAGE_SIZE << dseg->blk_order) < dseg->blk_size) { dseg->blk_order++; } si_meminfo(&si); dseg->blk_cnt_max = (si.totalram << PAGE_SHIFT) / dseg->blk_size; blk_ptr_size = dseg->blk_cnt_max * sizeof(unsigned long); if (blk_ptr_size > KMALLOC_MAX_SIZE) { blk_ptr_size = KMALLOC_MAX_SIZE; dseg->blk_cnt_max = KMALLOC_MAX_SIZE / sizeof(unsigned long); } /* Allocate an initialize DMA block pool */ dseg->blk_ptr = KMALLOC(blk_ptr_size, GFP_KERNEL); if (dseg->blk_ptr == NULL) { kfree(dseg); return NULL; } memset(dseg->blk_ptr, 0, blk_ptr_size); /* Allocate minimum number of blocks */ if (_alloc_dma_blocks(dseg, dseg->req_size / dseg->blk_size) != 0) { gprintk("Failed to allocate minimum number of DMA blocks\n"); /* * _alloc_dma_blocks() returns -1 if it fails to allocate the requested * number of blocks, but it may still have allocated something. Fall * through and return dseg filled in with as much memory as we could * allocate. */ } /* Allocate more blocks until we have a complete segment */ do { _find_largest_segment(dseg); if (dseg->seg_size >= dseg->req_size) { break; } } while (_alloc_dma_blocks(dseg, 8) == 0); /* Reserve all pages in the DMA segment and free unused blocks */ for (i = 0; i < dseg->blk_cnt; i++) { if ((dseg->blk_ptr[i] & 3) == 2) { dseg->blk_ptr[i] &= ~3; for (page_addr = dseg->blk_ptr[i]; page_addr < dseg->blk_ptr[i] + dseg->blk_size; page_addr += PAGE_SIZE) { MEM_MAP_RESERVE(VIRT_TO_PAGE(page_addr)); } } else if (dseg->blk_ptr[i]) { dseg->blk_ptr[i] &= ~3; free_pages(dseg->blk_ptr[i], dseg->blk_order); dseg->blk_ptr[i] = 0; } } return dseg; } /* * Function: _dma_segment_free * * Purpose: * Release resources used by DMA segment. * Parameters: * dseg - DMA segment descriptor * Returns: * Nothing. */ static void _dma_segment_free(dma_segment_t *dseg) { int i; unsigned long page_addr; if (dseg->blk_ptr) { for (i = 0; i < dseg->blk_cnt; i++) { if (dseg->blk_ptr[i]) { for (page_addr = dseg->blk_ptr[i]; page_addr < dseg->blk_ptr[i] + dseg->blk_size; page_addr += PAGE_SIZE) { MEM_MAP_UNRESERVE(VIRT_TO_PAGE(page_addr)); } free_pages(dseg->blk_ptr[i], dseg->blk_order); } } kfree(dseg->blk_ptr); kfree(dseg); } } /* * Function: _pgalloc * * Purpose: * Allocate DMA memory using page allocator * Parameters: * size - number of bytes to allocate * Returns: * Pointer to allocated DMA memory or NULL if failure. * Notes: * For any sizes less than DMA_BLOCK_SIZE, we ask the page * allocator for the entire memory block, otherwise we try * to assemble a contiguous segment ourselves. */ static void * _pgalloc(size_t size) { dma_segment_t *dseg; size_t blk_size; blk_size = (size < DMA_BLOCK_SIZE) ? size : DMA_BLOCK_SIZE; if ((dseg = _dma_segment_alloc(size, blk_size)) == NULL) { return NULL; } if (dseg->seg_size < size) { /* If we didn't get the full size then forget it */ gprintk("_pgalloc() failed to get requested size %zu: " "only got %lu contiguous across %d blocks\n", size, dseg->seg_size, dseg->blk_cnt); _dma_segment_free(dseg); return NULL; } list_add(&dseg->list, &_dma_seg); return (void *)dseg->seg_begin; } /* * Function: _pgfree * * Purpose: * Free memory allocated by _pgalloc * Parameters: * ptr - pointer returned by _pgalloc * Returns: * 0 if succesfully freed, otherwise -1. */ static int _pgfree(void *ptr) { struct list_head *pos; list_for_each(pos, &_dma_seg) { dma_segment_t *dseg = list_entry(pos, dma_segment_t, list); if (ptr == (void *)dseg->seg_begin) { list_del(&dseg->list); _dma_segment_free(dseg); return 0; } } return -1; } #ifdef BDE_EDK_SUPPORT /* * Function: _edk_mpool_free * * Purpose: * Free all memory allocated by _adk_mpool_alloc * Parameters: * None * Returns: * Nothing. */ static void _edk_mpool_free(void) { int i, ndevices; ndevices = BDE_NUM_DEVICES(BDE_SWITCH_DEVICES); for (i = 0; i < ndevices && DMA_DEV(i); i ++) { if (_edk_dma_pool[i].dma_vbase) { if (_edk_use_dma_mapping) { dma_unmap_single(DMA_DEV(i), (dma_addr_t)_edk_dma_pool[i].dma_pbase, _edk_dma_pool[i].size, DMA_BIDIRECTIONAL); } _pgfree(_edk_dma_pool[i].dma_vbase); _edk_dma_pool[i].dma_vbase = NULL; } } _edk_use_dma_mapping = 0; } /* * Function: edk_mpool_alloc * * Purpose: * Allocate DMA memory pool for EDK * Parameters: * size - size of DMA memory pool * Returns: * Nothing. */ static void _edk_mpool_alloc(int dev_id, size_t size) { static void __iomem *dma_vbase = NULL; static phys_addr_t cpu_pbase = 0; static phys_addr_t dma_pbase = 0; struct device *dev = DMA_DEV(DMA_DEV_INDEX); unsigned long pbase = 0; dma_vbase = _pgalloc(size); if (!dma_vbase) { gprintk("Failed to allocate memory pool of size 0x%lx for EDK\n", (unsigned long)size); return; } cpu_pbase = virt_to_bus(dma_vbase); /* Use dma_map_single to obtain DMA bus address or IOVA if IOMMU is present. */ if (dev) { pbase = dma_map_single(dev, dma_vbase, size, DMA_BIDIRECTIONAL); if (BDE_DMA_MAPPING_ERROR(dev, pbase)) { gprintk("Failed to map memory at %p for EDK\n", dma_vbase); _pgfree(dma_vbase); dma_vbase = NULL; return; } _edk_use_dma_mapping = 1; } else { pbase = cpu_pbase; } dma_pbase = pbase; #ifdef REMAP_DMA_NONCACHED _dma_vbase = ioremap(dma_pbase, size); #endif _edk_dma_pool[dev_id].cpu_pbase = cpu_pbase; _edk_dma_pool[dev_id].dma_pbase = dma_pbase; _edk_dma_pool[dev_id].dma_vbase = dma_vbase; _edk_dma_pool[dev_id].size = size; } int lkbde_edk_get_dma_info(int dev_id, phys_addr_t* cpu_pbase, phys_addr_t* dma_pbase, ssize_t* size) { if (_edk_dma_pool[dev_id].dma_vbase == NULL) { _edk_mpool_alloc(dev_id, *size * ONE_MB); } if (cpu_pbase) { *cpu_pbase = _edk_dma_pool[dev_id].cpu_pbase; } if (dma_pbase) { *dma_pbase = _edk_dma_pool[dev_id].dma_pbase; } *size = (_edk_dma_pool[dev_id].dma_vbase) ? _edk_dma_pool[dev_id].size : 0; return 0; } /* * The below function validates the memory to the EDK allocated DMA pool, * required to user space via the BDE device file. */ static int _edk_vm_is_valid(struct file *filp, struct vm_area_struct *vma) { unsigned long phys_addr = vma->vm_pgoff << PAGE_SHIFT; unsigned long size = vma->vm_end - vma->vm_start; int i, ndevices; ndevices = BDE_NUM_DEVICES(BDE_SWITCH_DEVICES); for (i = 0; i < ndevices; i++) { if (phys_addr < (unsigned long )_edk_dma_pool[i].cpu_pbase || (phys_addr + size) > ((unsigned long )_edk_dma_pool[i].cpu_pbase + _edk_dma_pool[i].size)) { continue; } return 1; } return 0; } #endif /* * Function: _mpool_free * * Purpose: * Free the DMA buffer pool and stop using it. * Parameters: * None * Returns: * Nothing. */ static void _mpool_free(void) { if (dma_debug >= 1) { gprintk("Freeing DMA buffer pool kernel_virt:0x%lx size:0x%x dmaalloc:%d ndevices=%d\n", (unsigned long)_dma_vbase, _dma_mem_size, dmaalloc, BDE_NUM_DEVICES(BDE_SWITCH_DEVICES)); } /* unmap bus address for all devices */ /* TODO SDK-235729 skip removed devices */ if (_use_dma_mapping) { int i, ndevices; ndevices = BDE_NUM_DEVICES(BDE_SWITCH_DEVICES); for (i = 0; i < ndevices && DMA_DEV(i); i ++) { dma_unmap_single(DMA_DEV(i), (dma_addr_t)_dma_pbase, _dma_mem_size, DMA_BIDIRECTIONAL); } _use_dma_mapping = 0; } #ifndef REMAP_DMA_NONCACHED if (_use_himem) #endif { iounmap(_dma_vbase); } switch (dmaalloc) { #if _SIMPLE_MEMORY_ALLOCATION_ case ALLOC_TYPE_API: if (_dma_vbase) { if (dma_debug >= 1) gprintk("freeing v=0x%lx p=0x%lx size=0x%lx\n", (unsigned long)_dma_vbase,(unsigned long) _dma_pbase, (unsigned long)_dma_mem_size); if (_dma_alloc_coherent_device != NULL) { dma_free_coherent(_dma_alloc_coherent_device, _dma_mem_size, _dma_vbase, _dma_pbase); _dma_alloc_coherent_device = NULL; } } break; #endif /* _SIMPLE_MEMORY_ALLOCATION_ */ case ALLOC_TYPE_CHUNK: { struct list_head *pos, *tmp; list_for_each_safe(pos, tmp, &_dma_seg) { dma_segment_t *dseg = list_entry(pos, dma_segment_t, list); list_del(&dseg->list); _dma_segment_free(dseg); } break; } case ALLOC_TYPE_HIMEM: break; default: gprintk("_mpool_free:DMA memory allocation method dmaalloc=%d is not supported\n", dmaalloc); } _dma_vbase = NULL; _cpu_pbase = 0; _dma_pbase = 0; } /* * Function: _mpool_alloc * * Purpose: * Allocate DMA memory pool * Parameters: * size - size of DMA memory pool * Returns: * Nothing. * Notes: * The DMA buffer pool is allocated if the selected memory allocation * method does not also map it to PCIe bus addresses. * If set up to use high memory, we simply map the memory into * kernel space. * It is assumed there is only one pool. */ static void _mpool_alloc(size_t size) { unsigned long pbase = 0; if (dma_debug >= 1) { gprintk("Allocating DMA memory using method dmaalloc=%d\n", dmaalloc); } #if defined(__arm__) && !defined(CONFIG_HIGHMEM) if (_use_himem) { gprintk("DMA in high memory requires CONFIG_HIGHMEM on ARM CPUs.\n"); return; } #endif if (size == 0) return; if (_use_himem) { /* Use high memory for DMA */ if (_himemaddr) { pbase = _himemaddr; } else { pbase = virt_to_phys(high_memory); } _cpu_pbase = pbase; _dma_vbase = phys_to_virt(pbase); _use_dma_mapping = 1; } else { switch (dmaalloc) { #if _SIMPLE_MEMORY_ALLOCATION_ case ALLOC_TYPE_API: { /* The allocation will be performed together with the mapping to the first device */ if (size > DMA_MAX_ALLOC_SIZE) { gprintk("Will allocate 0x%lx bytes instead of 0x%lx bytes.\n", (unsigned long)DMA_MAX_ALLOC_SIZE, (unsigned long)size); _dma_mem_size = DMA_MAX_ALLOC_SIZE; } if (nodevices == 1) { /* With no devices, allocate immediately mapping to the null device */ _dma_pool_alloc_state = DMA_POOL_INITIALIZED; _dma_per_device_init(0); return; } break; } #endif /* _SIMPLE_MEMORY_ALLOCATION_ */ case ALLOC_TYPE_CHUNK: _dma_vbase = _pgalloc(size); if (!_dma_vbase) { gprintk("Failed to allocate memory pool of size 0x%lx\n", (unsigned long)size); return; } _cpu_pbase = virt_to_phys(_dma_vbase); _use_dma_mapping = 1; break; default: _dma_vbase = NULL; gprintk("DMA memory allocation method dmaalloc=%d is not supported\n", dmaalloc); return; } } _dma_pool_alloc_state = DMA_POOL_INITIALIZED; if (_use_dma_mapping && dma_debug >= 1) { gprintk("DMA buffer pool before mapping kernel_virt:0x%lx physical:0x%lx size:0x%lx dmaalloc:%d\n", (unsigned long)_dma_vbase, (unsigned long)_cpu_pbase, (unsigned long)size, dmaalloc); } } /* * Function: _dma_cleanup * * Purpose: * DMA cleanup function. * Parameters: * None * Returns: * Always 0 */ int _dma_cleanup(void) { #ifdef BDE_EDK_SUPPORT _edk_mpool_free(); #endif if (_dma_vbase) { mpool_destroy(_dma_pool); _mpool_free(); } /* In case support will be added to re-allocate the DMA buffer pool */ _dma_pool_alloc_state = DNA_POOL_INVALID; return 0; } /* * DMA initialization for a device. Called later than _dma_init(). * Perform the IOMMU mapping for the specified device. */ void _dma_per_device_init(int dev_index) { dma_addr_t dma_addr; struct device *dev; int performed_initial_pool_mapping = 0; int dma64_support = 0; if (dma_debug >= 2) { gprintk("_dma_per_device_init dev_index:%d _use_dma_mapping:%d kernel_virt:0x%lx physical:0x%lx size:0x%x dmaalloc:%d\n", dev_index, _use_dma_mapping, (unsigned long)_dma_vbase, (unsigned long)_cpu_pbase, _dma_mem_size, dmaalloc); } #if _SIMPLE_MEMORY_ALLOCATION_ if (nodevices == 1 && _dma_pool_alloc_state == DMA_POOL_INITIALIZED && dmaalloc == ALLOC_TYPE_API) { dev = NULL; /* Map to a null device */ } else #endif /* _SIMPLE_MEMORY_ALLOCATION_ */ if ( dev_index < 0 || (dev = DMA_DEV(dev_index)) == NULL) { return; } #if _SIMPLE_MEMORY_ALLOCATION_ if (_dma_pool_alloc_state == DMA_POOL_INITIALIZED && dmaalloc == ALLOC_TYPE_API) { /* allocate the DMA buffer pool and map it to the device, uses CMA */ _dma_vbase = dma_alloc_coherent(dev, _dma_mem_size, &dma_addr, GFP_KERNEL); if (!_dma_vbase) { _dma_pool_alloc_state = DMA_POOL_FAILED; gprintk("Failed to allocate coherent memory pool of size 0x%x\n", _dma_mem_size); return; } _dma_alloc_coherent_device = dev; performed_initial_pool_mapping = 1; /* Set the host physical address of the DMA buffer pool */ _cpu_pbase = HOST_PHYS_ADDR(dev, dma_addr, _dma_vbase); } else #endif /* _SIMPLE_MEMORY_ALLOCATION_ */ /* IF the DMA buffer pool required mapping to the device, perform it */ if (_use_dma_mapping && _dma_vbase && (_dma_pool_alloc_state == DMA_POOL_INITIALIZED || _dma_pool_alloc_state == DMA_POOL_MAPPED)) { /* Map RAM virtual address space for DMA usage and obtain DMA bus address or IOVA if iommu is present. */ dma_addr = dma_map_single(dev, _dma_vbase, _dma_mem_size, DMA_BIDIRECTIONAL); if (BDE_DMA_MAPPING_ERROR(dev, dma_addr)) { gprintk("Failed to map DMA buffer pool for device %d at kernel_virt:0x%lx\n", dev_index, (unsigned long)_dma_vbase); if (_dma_pool_alloc_state == DMA_POOL_INITIALIZED) { _mpool_free(); _dma_pool_alloc_state = DMA_POOL_FAILED; } return; } if (_dma_pool_alloc_state == DMA_POOL_INITIALIZED) { performed_initial_pool_mapping = 1; } else { if (dma_addr != (dma_addr_t)_dma_pbase) { /* Bus address/IOVA must be identical for all devices. */ gprintk("Device %d has different PCIe bus address:0x%lx (should be 0x%lx)\n", dev_index, (unsigned long)dma_addr, (unsigned long)_dma_pbase); } return; } } /* On the first mapping of the DMA buffer pool, check the mapped address and mark it as mapped */ if (performed_initial_pool_mapping) { #if defined(IPROC_CMICD) && defined(CONFIG_OF) if (of_find_compatible_node(NULL, NULL, "brcm,iproc-cmicx")) { dma64_support = 1; } #endif if (!dma64_support && ((dma_addr + (_dma_mem_size - 1)) >> 16) > DMA_BIT_MASK(16)) { gprintk("DMA memory allocated at dma_bus:0x%lx size 0x%x is beyond the 4GB limit and not supported.\n", (unsigned long)dma_addr, _dma_mem_size); _mpool_free(); _dma_pool_alloc_state = DMA_POOL_FAILED; return; } _dma_pbase = dma_addr; _dma_pool_alloc_state = DMA_POOL_MAPPED; #ifndef REMAP_DMA_NONCACHED if (_use_himem) #endif { if (dma_debug >= 2) { gprintk("remapping DMA buffer pool from physical:0x%lx original kernel_virt:0x%lx\n", (unsigned long)_dma_pbase, (unsigned long)_dma_vbase); } _dma_vbase = ioremap(_dma_pbase, _dma_mem_size); } if (dma_debug >= 1) { gprintk("Mapped DMA buffer pool _use_dma_mapping:%d kernel_virt:0x%lx dma_bus:0x%lx physical:0x%lx size:0x%x dmaalloc:%d, dma64_support:%d\n", _use_dma_mapping, (unsigned long)_dma_vbase, (unsigned long)_dma_pbase, (unsigned long)_cpu_pbase, _dma_mem_size, dmaalloc, dma64_support); } /* DMA buffer pool initialization */ mpool_init(); _dma_pool = mpool_create(_dma_vbase, _dma_mem_size); } return; } /* * If this is the first time the function is called, * allocate the DMA * buffer pool. * Use kernel module arguments for pool size and allocation methods. */ void _dma_init(void) { /* * If DMA buffer pool allocation was already attempted and failed, * do not attempt again. */ if (_dma_pool_alloc_state != DNA_POOL_INVALID || _dma_vbase != NULL) { return; } _dma_pool_alloc_state = DMA_POOL_FAILED; /* dmasize, himem and himemaddr kernel module argument parsing */ if (dmasize) { if ((dmasize[strlen(dmasize)-1] & ~0x20) == 'M') { _dma_mem_size = simple_strtoul(dmasize, NULL, 0); _dma_mem_size *= ONE_MB; } else { gprintk("DMA memory size must be specified as e.g. dmasize=8M\n"); } if (_dma_mem_size & (_dma_mem_size-1)) { gprintk("dmasize must be a power of 2 (1M, 2M, 4M, 8M etc.)\n"); _dma_mem_size = 0; } } if (himem) { if ((himem[0] & ~0x20) == 'Y' || himem[0] == '1') { _use_himem = 1; dmaalloc = ALLOC_TYPE_HIMEM; } else if ((himem[0] & ~0x20) == 'N' || himem[0] == '0') { _use_himem = 0; } } if (himemaddr && strlen(himemaddr) > 0) { char suffix = (himemaddr[strlen(himemaddr)-1] & ~0x20); _himemaddr = simple_strtoul(himemaddr, NULL, 0); if (suffix == 'M') { _himemaddr *= ONE_MB; } else if (suffix == 'G') { _himemaddr *= ONE_GB; } else { gprintk("DMA high memory address must be specified as e.g. himemaddr=8[MG]\n"); } } /* DMA buffer pool allocation and initialization that can be done without mapping to DMA */ if (_dma_mem_size) { /* Allocate the DMA buffer pool */ _mpool_alloc(_dma_mem_size); if (_dma_pool_alloc_state != DMA_POOL_INITIALIZED) { gprintk("no DMA memory available\n"); } } } /* * Some kernels are configured to prevent mapping of kernel RAM memory * into user space via the /dev/mem device. * * The function below provides a backdoor to mapping the DMA pool to * user space via the BDE device file. */ int _dma_mmap(struct file *filp, struct vm_area_struct *vma) { unsigned long phys_addr = vma->vm_pgoff << PAGE_SHIFT; unsigned long size = vma->vm_end - vma->vm_start; if (phys_addr < (unsigned long )_cpu_pbase || (phys_addr + size) > ((unsigned long )_cpu_pbase + _dma_mem_size)) { #ifdef BDE_EDK_SUPPORT if(!_edk_vm_is_valid(filp, vma)) { gprintk("range 0x%lx-0x%lx outside DMA pool\n", phys_addr, phys_addr + size); return -EINVAL; } #else gprintk("range 0x%lx-0x%lx outside DMA pool 0x%lx-0x%lx\n", phys_addr, phys_addr + size, (unsigned long )_cpu_pbase, (unsigned long )_cpu_pbase + _dma_mem_size); return -EINVAL; #endif } #ifdef USE_DMA_MMAP_COHERENT if (dmaalloc == ALLOC_TYPE_API) { vma->vm_pgoff = 0; return dma_mmap_coherent(_dma_alloc_coherent_device, vma, (void *)_dma_vbase, phys_addr, size); } #endif _PGPROT_NONCACHED(vma->vm_page_prot); if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot)) { gprintk("Failed to mmap phys range 0x%lx-0x%lx to 0x%lx-0x%lx\n", phys_addr, phys_addr + size, vma->vm_start,vma->vm_end); return -EAGAIN; } return 0; } /* * Function: _dma_pool_allocated * * Purpose: * Check if DMA pool has been allocated. * Parameters: * None * Returns: * 0 : not allocated * 1 : allocated */ int _dma_pool_allocated(void) { return (_dma_vbase) ? 1 : 0; } sal_paddr_t _l2p(int d, void *vaddr) { if (_dma_mem_size) { /* dma memory is a contiguous block */ if (vaddr) { return _dma_pbase + (PTR_TO_UINTPTR(vaddr) - PTR_TO_UINTPTR(_dma_vbase)); } return 0; } /* TODO will not work with IOMMU */ return ((sal_paddr_t)virt_to_bus(vaddr)); } void * _p2l(int d, sal_paddr_t paddr) { sal_vaddr_t vaddr = (sal_vaddr_t)_dma_vbase; if (_dma_mem_size) { /* DMA memory is a contiguous block */ if (paddr == 0) { return NULL; } return (void *)(vaddr + (sal_vaddr_t)(paddr - _dma_pbase)); } /* TODO will not work with IOMMU */ return bus_to_virt(paddr); } /* * Some of the driver malloc's are too large for * kmalloc(), so 'sal_alloc' and 'sal_free' in the * linux kernel sal cannot be implemented with kmalloc(). * * Instead, they expect someone to provide an allocator * that can handle the gimongous size of some of the * allocations, and we provide it here, by allocating * this memory out of the boot-time dma pool. * * These are the functions in question: */ void* kmalloc_giant(int sz) { return mpool_alloc(_dma_pool, sz); } void kfree_giant(void* ptr) { return mpool_free(_dma_pool, ptr); } uint32_t * _salloc(int d, int size, const char *name) { void *ptr; if (_dma_mem_size) { return mpool_alloc(_dma_pool, size); } if ((ptr = kmalloc(size, mem_flags)) == NULL) { ptr = _pgalloc(size); } return ptr; } void _sfree(int d, void *ptr) { if (_dma_mem_size) { return mpool_free(_dma_pool, ptr); } if (_pgfree(ptr) < 0) { kfree(ptr); } } int _sinval(int d, void *ptr, int length) { #if defined(dma_cache_wback_inv) dma_cache_wback_inv((unsigned long)ptr, length); #else /* FIXME: need proper function to replace dma_cache_sync */ dma_sync_single_for_cpu(NULL, (unsigned long)ptr, length, DMA_BIDIRECTIONAL); #endif return 0; } int _sflush(int d, void *ptr, int length) { #if defined(dma_cache_wback_inv) dma_cache_wback_inv((unsigned long)ptr, length); #else /* FIXME: need proper function to replace dma_cache_sync */ dma_sync_single_for_cpu(NULL, (unsigned long)ptr, length, DMA_BIDIRECTIONAL); #endif return 0; } int lkbde_get_dma_info(phys_addr_t* cpu_pbase, phys_addr_t* dma_pbase, ssize_t* size) { if (_dma_vbase == NULL) { if (_dma_mem_size == 0) { _dma_mem_size = DMA_MEM_DEFAULT; } } *cpu_pbase = _cpu_pbase; *dma_pbase = _dma_pbase; *size = (_dma_vbase) ? _dma_mem_size : 0; return 0; } void _dma_pprint(struct seq_file *m) { pprintf(m, "\tdmasize=%s\n", dmasize); pprintf(m, "\thimem=%s\n", himem); pprintf(m, "\thimemaddr=%s\n", himemaddr); pprintf(m, "DMA Memory (%s): %d bytes, %d used, %d free%s\n", (_use_himem) ? "high" : "kernel", (_dma_vbase) ? _dma_mem_size : 0, (_dma_vbase) ? mpool_usage(_dma_pool) : 0, (_dma_vbase) ? _dma_mem_size - mpool_usage(_dma_pool) : 0, USE_LINUX_BDE_MMAP ? ", local mmap" : ""); } /* * Export functions */ #ifdef BDE_EDK_SUPPORT LKM_EXPORT_SYM(lkbde_edk_get_dma_info); #endif LKM_EXPORT_SYM(kmalloc_giant); LKM_EXPORT_SYM(kfree_giant); LKM_EXPORT_SYM(lkbde_get_dma_info);