1、背景介绍
在使用pcie进行数据传输时,常常需要用到dma,由于dma传输多为异步传输方式,只需要告诉dma起始地址,数据大小,然后启动dma,cpu就可以去做其他事情。不过Dma传输需要有一个前提条件,分配一段连续的物理内存,在linux下,由于存在虚实物理地址转换,用户访问的都是虚地址,分配一段连续的物理内存比较困难。常见的做法是在操作系统启动时预留一段物理内存专门用于dma,缺点是操作系统无法管理这段空间,如果没有dma操作显然空间就浪费了。
2、cma概念
cma是linux中一种动态分配连续物理内存的方式,具体可以参看宋宝华的这篇博文:https://blog.csdn.net/21cnbao/article/details/7309757
这里在zynq中进行了一下cma的实践
3、内核配置
当前使用的内核是xilinx 2017.4版本,linux版本号为4.9。为了使用cma,需要在内核中进行如下配置
上图中设置了cma默认大小为256MB,同时指定了PAGE_SIZE最大值12,即cma区域的页大小为4MB,空间也按照4MB页进行对齐。
4、测试
当linux启动后能在串口中看到如下打印
从物理地址0x3000000开始的256MB为cma空间。测试时可以通过下面代码进行试验,参考:https://lwn.net/Articles/485193/
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
struct cma_allocation {
struct list_head list;
unsigned long size;
dma_addr_t dma;
void *virt;
};
static struct device *cma_dev;
static LIST_HEAD(cma_allocations);
static DEFINE_SPINLOCK(cma_lock);
/*
* any read request will free the 1st allocated coherent memory, eg.
* cat /dev/cma_test
*/
static ssize_t
cma_test_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct cma_allocation *alloc = NULL;
spin_lock(&cma_lock);
if (!list_empty(&cma_allocations)) {
alloc = list_first_entry(&cma_allocations,
struct cma_allocation, list);
list_del(&alloc->list);
}
spin_unlock(&cma_lock);
if (alloc) {
dma_free_coherent(cma_dev, alloc->size, alloc->virt,
alloc->dma);
_dev_info(cma_dev, "free CM at virtual address: 0x%p dma address: 0x%p size:%luKiB\n",
alloc->virt, (void *)alloc->dma, alloc->size / SZ_1K);
kfree(alloc);
}
return 0;
}
/*
* any write request will alloc a new coherent memory, eg.
* echo 1024 > /dev/cma_test
* will request 1024KiB by CMA
*/
static ssize_t
cma_test_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct cma_allocation *alloc;
int ret;
alloc = kmalloc(sizeof *alloc, GFP_KERNEL);
if (!alloc)
return -ENOMEM;
ret = kstrtoul_from_user(buf, count, 0, &alloc->size);
if (ret)
return ret;
if (!alloc->size)
return -EINVAL;
if (alloc->size > (ULONG_MAX << PAGE_SHIFT))
return -EOVERFLOW;
alloc->size *= SZ_1K;
alloc->virt = dma_alloc_coherent(cma_dev, alloc->size,
&alloc->dma, GFP_KERNEL);
if (alloc->virt) {
_dev_info(cma_dev, "allocate CM at virtual address: 0x%p"
"address: 0x%p size:%luKiB\n", alloc->virt,
(void *)alloc->dma, alloc->size / SZ_1K);
spin_lock(&cma_lock);
list_add_tail(&alloc->list, &cma_allocations);
spin_unlock(&cma_lock);
return count;
} else {
dev_err(cma_dev, "no mem in CMA area\n");
kfree(alloc);
return -ENOSPC;
}
}
static const struct file_operations cma_test_fops = {
.owner = THIS_MODULE,
.read = cma_test_read,
.write = cma_test_write,
};
static struct miscdevice cma_test_misc = {
.name = "cma_test",
.fops = &cma_test_fops,
};
static int __init cma_test_init(void)
{
int ret = 0;
ret = misc_register(&cma_test_misc);
if (unlikely(ret)) {
pr_err("failed to register cma test misc device!\n");
return ret;
}
cma_dev = cma_test_misc.this_device;
cma_dev->coherent_dma_mask = ~0;
_dev_info(cma_dev, "registered.\n");
return ret;
}
module_init(cma_test_init);
static void __exit cma_test_exit(void)
{
misc_deregister(&cma_test_misc);
}
module_exit(cma_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Barry Song <Baohua.Song@csr.com>");
MODULE_DESCRIPTION("kernel module to help the test of CMA");
MODULE_ALIAS("CMA test");
这里将测试代码直接编入内核中
操作系统启动后执行echo 51200> /dev/cma_test即分配50MB连续物理内存空间,起始物理地址为0x31000000。