在内核中有时需要申请一段大内存,方法之一是可以采取如下方法:
示例: 如何将1M的物理内存作为私人使用(假设物理内存大小为256M):
1. 在内核启动时,通过mem=255M参数,让内核只能使用255M的空间。
2. 然后通过如下调用来使用这个1M的私人空间:
dmabuf= ioremap (0xFF00000 /* 255M */, 0x100000 /* 1M */);
不过,这种方法不宜使用在开启了高内存的系统上。
DMA
默认情况下,Linux内核都假定设备都能在32位地址上进行DMA操作,如果不是这样,那么需要通过如下调用来告知内核:
int dma_set_mask(struct device *dev, u64mask);
下面是一个只支持24位地址DMA操作的示例:
if (dma_set_mask (dev, 0xffffff)) card->use_dma = 1; else { card->use_dma = 0; /* We'll have to live without DMA */ printk (KERN_WARN, "mydev: DMA not supported\n"); }
当然,如果设备本身支持32位DMA操作,则没有必要调用dma_set_mask。
DMA映射(大块数据分配)
建立DMA映射包含两个步骤:
1.分配一个DMA缓冲空间。
2. 为该缓冲空间生成一个设备可访问的地址。
DMA映射中需要处理cache一致性的问题。
表示总线地址的数据类型:dma_addr_t。
根据DMA缓冲区存在时间的长短,有两种类型的DMA映射:
1. 一致性DMA映射(Coherent DMA mappings)
这种映射在驱动的生命同期中一直存在。一致缓冲区必须同时对CPU和外设可用,所以一致映射必须存在于cache-cohrent内存中。这种映射使用和建立的代价比较高。
2. 流式DMA映射(Streaming DMA mappings)
流式映射是一种短期的映射,它支持一个或多个DMA操作。根据体系结构的要求,可能会涉及到创建bounce buffer, IOMMU寄存器编程,冲刷处理Caches等。当然,这种方式下,对缓冲区的访问要受制于一些苛刻的规则,特别是对缓冲区的所有权,当映射建立后,缓冲区的所有权属于设备,处理器不能访问或修改它。一般应尽可能地使用这种DMA映射方式,理由如下:1. 一致性DMA映射会独占映射中使用到的寄存器,导致其他模块无法访问。2. 流式DMA映射有更多的优化方式。
通过如下函数可以建立一致映射:
/** * dma_alloc_coherent - allocate consistent memory for DMA * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices * @size: required memory size * @handle: bus-specific DMA address * * Allocate some uncached, unbuffered memory for a device for * performing DMA. This function allocates pages, and will * return the CPU-viewed address, and sets @handle to be the * device-viewed address. */ void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);
上述函数分配的BUFFER大小至少是一个页的大小, 属于大内存分配的情形。
通过如下函数可以将DMA Buffer映射到请求的VMA中:
/** * dma_mmap_coherent - map a coherent DMA allocation into user space * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices * @vma: vm_area_struct describing requested user mapping * @cpu_addr: kernel CPU-view address returned from dma_alloc_coherent * @handle: device-view address returned from dma_alloc_coherent * @size: size of memory originally requested in dma_alloc_coherent * * Map a coherent DMA buffer previously allocated by dma_alloc_coherent * into user space. The coherent DMA buffer must not be freed by the * driver until the user space mapping has been released. */ int dma_mmap_coherent(struct device *dev, struct vm_area_struct *vma, void *cpu_addr, dma_addr_t handle, size_t size);
DMA池
DMA池是针对小的,一致性DMA映射的分配机制。相关函数:
struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation); void dma_pool_destroy(struct dma_pool *pool); void *dma_pool_alloc(struct dma_pool *pool, int mem_flags, dma_addr_t *handle); void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr);
对于流式DMA映射,接口要复杂些。建立映射时,需要指定数据移动的方向,根据不同的目的,有如下一些选项:
DMA_TO_DEVICE |
data is being sent to the device (in response, perhaps, to a write system call), DMA_TO_DEVICE should be used; |
DMA_FROM_DEVICE |
data going to the CPU, is marked with DMA_FROM_DEVICE. |
DMA_BIDIRECTIONAL |
If data can move in either direction, use DMA_BIDIRECTIONAL |
DMA_NONE |
only for debug purpose, should never use |
传输单个缓冲区:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction); void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction);
一旦缓冲区被映射后,它只属于设备,驱动不能访问。如果一定要在映射期间访问缓冲区的内容,必须调用如下相关的接口:
void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction); … void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
单页流式映射
有时,要映射包含struct page指针的缓冲区,可使用如下接口:
dma_addr_t dma_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size, enum dma_data_direction direction); void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size, enum dma_data_direction direction);
代码示例:
static u32 _kernel_page_allocate(void) { struct page *new_page; u32 linux_phys_addr; new_page = alloc_page(GFP_HIGHUSER | __GFP_ZERO | __GFP_REPEAT | __GFP_NOWARN | __GFP_COLD); if ( NULL == new_page ) { return 0; } /* Ensure page is flushed from CPU caches. */ linux_phys_addr = dma_map_page(NULL, new_page, 0, PAGE_SIZE, DMA_BIDIRECTIONAL); return linux_phys_addr; } static void _kernel_page_release(u32 physical_address) { struct page *unmap_page; #if 1 dma_unmap_page(NULL, physical_address, PAGE_SIZE, DMA_BIDIRECTIONAL); #endif unmap_page = pfn_to_page( physical_address >> PAGE_SHIFT ); MALI_DEBUG_ASSERT_POINTER( unmap_page ); __free_page( unmap_page ); }
scatter/gather I/O
它是一种特殊的流式DMA映射,将多个BUFFER,通过一个DMA操作,从或往设备传输数据。