1、背景介绍

在使用pcie进行数据传输时,常常需要用到dma,由于dma传输多为异步传输方式,只需要告诉dma起始地址,数据大小,然后启动dma,cpu就可以去做其他事情。不过Dma传输需要有一个前提条件,分配一段连续的物理内存,在linux下,由于存在虚实物理地址转换,用户访问的都是虚地址,分配一段连续的物理内存比较困难。常见的做法是在操作系统启动时预留一段物理内存专门用于dma,缺点是操作系统无法管理这段空间,如果没有dma操作显然空间就浪费了。

2、cma概念

cma是linux中一种动态分配连续物理内存的方式,具体可以参看宋宝华的这篇博文:https://blog.csdn/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/Articles/485193/

#include #include #include #include #include #include #include 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 ");

MODULE_DESCRIPTION("kernel module to help the test of CMA");

MODULE_ALIAS("CMA test");

这里将测试代码直接编入内核中

操作系统启动后执行echo 51200> /dev/cma_test即分配50MB连续物理内存空间,起始物理地址为0x31000000。

更多推荐

zynq跑linux所需内存大小,Zynq-Linux移植学习笔记之33-CMA连续物理内存配置