Zynq-Linux移植学习笔记之33-CMA连续物理内存配置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jj12345jj198999/article/details/83245682

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 <[email protected]>");
MODULE_DESCRIPTION("kernel module to help the test of CMA");
MODULE_ALIAS("CMA test");

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

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

猜你喜欢

转载自blog.csdn.net/jj12345jj198999/article/details/83245682