关于DMA的基础知识有空的时候在详细总结一下。
前提:Linux内核中对内存的管理是以页为单位进行的,所以分配内存是以页为单位,起始地址都是页的开始地址。
现在记录关于DMA的一些内存大小,地址问题的解决,下面问题的分析在普通内存分配和映射也是有效的。
上面的图展示的是同一个内存地址空间映射到用户进程空间和内核空间,映射的范围不同。看到系统物理地址上面的地址数据,0x8eb60000是物理地址起始位置,映射到虚拟地址0xb6d75000等位置。
在内核中,内存分配是以页面来作为单位进行的,所以地址的低12位是0,DMA分配的地址起始位置也是在页的起始位置。
DMA地址映射到用户空间虚拟地址,和将普通的物理地址映射到用户空间虚拟地址一样,都是使用mmap函数。
上面图片展示的是映射地址偏移后的关系,而且映射的偏移量必须是页面的倍数。在驱动程序的mmap函数中,物理地址加上偏移的页面数量后,在传入remap_pgn_range(---)函数中的pgn参数中。
linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:(来自https://kenby.iteye.com/blog/1164700)
总结一下就是, 文件大小, mmap的参数 len 都不能决定进程能访问的大小, 而是容纳文件被映射部分的最小页面数决定
使用dma的分配函数例子
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <asm/page.h>
#include <linux/dma-mapping.h>
#define THIS_DMA_SIZE (2*PAGE_SIZE)
dma_addr_t dma_addr=0;
unsigned int addr_virt;
static int __init malloc_and_mmap_init(void){
//获取DMA地址
addr_virt = (unsigned int)dma_alloc_coherent(NULL,THIS_DMA_SIZE,&dma_addr,GFP_KERNEL);
printk(KERN_INFO"addr_virt=%0#X,dma_addr=%0#X",addr_virt,(unsigned int)dma_addr);
return 0;
}
static void __exit malloc_and_mmap_exit(void){
dma_free_coherent(NULL,THIS_DMA_SIZE,(void*)addr_virt,dma_addr);
}
MODULE_LICENSE("GPL");
module_init(malloc_and_mmap_init);
module_exit(malloc_and_mmap_exit);
结果如下:
可以看到dma_addr低12位是0。
帧缓冲设备中的mmap函数分析:
//虚拟地址以页的大小映射到物理内存,物理内存不需要。
//即当5000字节大小的物理内存需要映射,就要虚拟内存空间2个页面对应物理地址
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
__acquires(&info->lock)
__releases(&info->lock)
{
int fbidx = iminor(file->f_path.dentry->d_inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
unsigned long off;
unsigned long start;
u32 len;
//vma->vm_pgoff是以PAGE_SIZE为单位的偏移量
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
//偏移的页数量,如vma->vm_pgoff=1时,表示1<<PAGE_SHIFT==一页的大小
//off是页面地址,低12位=0
off = vma->vm_pgoff << PAGE_SHIFT;
if (!fb)
return -ENODEV;
if (fb->fb_mmap) {
int res;
mutex_lock(&info->lock);
res = fb->fb_mmap(info, vma);
mutex_unlock(&info->lock);
return res;
}
//上面的该部分表示vm_area_struct链表中一个连续虚拟地址区间的页面数量
mutex_lock(&info->lock);
/* frame buffer memory */
start = info->fix.smem_start;//物理地址
//len表示物理地址页面长度,PAGE_MASK=0xfffff000
//PAGE_ALIGN是将地址向下取整的宏
//但是低12位=0,~PAGE_MASK=0x00000fffs
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
//off>=len表示物理地址超过可DMA传输的内存地址
//这种情况就直接映射I/O内存,包括了寄存器的地址
//off表示偏移长度,len表示缓存区长度
if (off >= len) {
/* memory mapped io */
off -= len;
//info->var.accel_flags已经过时,看fb_info的flags
if (info->var.accel_flags) {
mutex_unlock(&info->lock);
return -EINVAL;
}
start = info->fix.mmio_start;//找到作为内存的寄存器地址
//页对齐,向上取整,start & ~PAGE_MASK) + info->fix.mmio_len
//start & ~PAGE_MASK)低十二位取0,表示获得内存所在页地址
//info->fix.mmio_len偏移
//len是页地址偏移个数,低12位=0
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
}
mutex_unlock(&info->lock);
start &= PAGE_MASK;//start页面起始地址
//(vma->vm_end - vma->vm_start + off)是加上偏移地址后的地址长度
//不能超过len。内存映射,映射的虚拟地址的区间要全部或者部分在物理区间内部,不能比物理地址区间多。
if ((vma->vm_end - vma->vm_start + off) > len)
return -EINVAL;
//off的值是在用户程序中调用mmap时传入进来的最后一个参数,表示对于要映射的物理地址需要偏移的大小
off += start;//指定的映射地址
vma->vm_pgoff = off >> PAGE_SHIFT;//页帧号
/* This is an IO map - tell maydump to skip this VMA */
vma->vm_flags |= VM_IO | VM_RESERVED;
fb_pgprotect(file, vma, off);
//映射,off >> PAGE_SHIFT=页帧号
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
return 0;
}