DMA是计算机系统的一项功能,它允许设备在没有CPU的干预的情况下访问系统主存储器RAM,使CPU完成其他任务。
DMA控制器是负责DMA管理的外设,在现代处理器和微控制器中都能发现它。DMA功能用于执行内存读写和写入操作而不占用CPU周期。当需要传输数据块时,处理器向DMA控制器提供源地址和目标地址以及总字节数。然后,DMA控制器会自动将数据从源传输到目标,而不会占用CPU周期。
1 设置DMA映射
对于任何类型的DMA传输,都需要提供源地址和目标地址,以及需要传输的字数。在外设DMA的情况下,外设的FIFO用作源或目标。
使用外设DMA时,根据传输方向指定源或目标地址,换句话说,DMA传输需要适当的内存映射,这是接下来要讨论的内容。
缓存一致性和DMA
想象一种情况,CPU配备了缓存和外部存储器,设备用DMA可以直接访问它们。当CPU访问内存中X时,当前值将被存储到缓存中,假设采用回写式缓存,对X的后续操作将更新X的缓存副本,而不更新X的外部存储器版本。如果在下一次设备尝试访问X之前,缓存未刷新到内存,则设备将受到旧的X值,同样,如果把新值写入内存时未使缓存的X副本变为无效,那么CPU将操作旧的X值。。
两个独立的设备共享内存就会存在这种问题,缓存一致性确保每个写入操作都是瞬间发生的,共享内存区域的所有设备都会看到完全相同的更改顺序。
DMA映射
所有合适的DMA传输都需要适当的内存映射,DMA映射包括DMA缓冲区和为其生成总线地址。设备实际上使用总线地址。总线地址是dma_addr_t类型的每个实例。
映射分为两种类型:一致DMA映射和流式DMA映射。前者用于多次传输,它会自动解决缓冲一致性问题。流映射有很多限制,他不会自动解决一致性问题,但有一种解决方案,即在每次传输之间调用几个函数。一致DMA映射通常存在于驱动程序的整个生命周期中,而流映射则通常在DMA传输完成时立即取消映射。
处理DMA映射时应该包含的主要头文件如下:
#include <linux/dma-mapping>
一致映射
下面函数设置一致映射:
void *dma_alloc_coherent(struct device *dev,size_t size,dma_addr_t *dma_handle,gfp_t flag);
此函数处理缓冲区的分配和映射,并返回该缓冲区的内核虚拟地址,该缓冲区大小为size字节,可供CPU访问。dev时设备结构。dma_handle指向总线地址,分配给映射的内存物理上一定是连续的,flag决定内存的分配方式,通常是GFP_KERNEL或GFP_ATOMIC。
释放映射可以使用以下函数:
void dma_free_coherent(struct device *dev,size_t size,void *cpu_addr,dma_addr_t dma_handle);
cpu_addr对应dma_alloc_coherent()返回的内核虚拟地址。这个映射是很宝贵的,它可以分配的最小内存值是一个页面,它分配的页面数量只能是2次幂,对于持续存在于设备生命周期内的缓冲区 ,应该使用这种映射。
流式DMA映射
流式映射有更多的限制,由于以下原因而不同于一致映射。
- 映射需要使用已分配的缓冲区
- 映射可以接受几个分散的不连续缓冲区
- 映射的缓冲区属于设备而不属于CPU。CPU在使用缓冲区之前,应该首先解除映射,这是为了缓存
- 对于写入事务,驱动程序应该在映射之前将数据放入缓冲区
- 必须指定数据移动的方向,只能基于该方向使用数据
为什么在取消映射之前不该访问缓冲区?原因很简单:CPU映射是可缓存的。用于流式映射的dma_map_*()系列函数将首先清理与缓冲区相关的缓存(使之无效),在出现相应的dma_unmap_*()之前,CPU不能访问。
实际上流式映射有两种形式:
- 单缓存映射,它只允许单页映射。
- 分散/聚集映射,它允许传递多个缓冲区(缓冲区分散在内存中)
对于这两种中的任何一种映射,都应该用include/linux/dma-direction.h中定义的enum dma_data_direction类型符号来指定方向:
enum dma_data_direction{
DMA_BIDIRECTIONAL = 0,
DMA_TO_DEVICE =1,
DMA_FROM_DEVICE = 2,
DMA_NONE =3,
};
- 单缓存区映射
可以使用下面的方法设置单缓存区
dma_addr_t dma_map_single(struct device *dev,void *ptr,size_t size,enum dma_data_direction direction);
ptr是缓冲区的内核虚拟地址,dma_addr_t是设备返回的总线地址,确保使用真正适合需求的方向。 使用下面的函数释放该映射:
void dma_unmap_single(struct device *dev,dma_addr_t dma_addr,size_t size,enum dma_data_direction direction);
- 分散/聚集映射
分散/聚集映射是一种特殊类型的流式映射,可以在单个槽中传输多个缓冲区区域,而不是单独映射每个缓冲区并逐个传输它们。假设有几个缓冲区物理上是不连续的,所有这些缓存区都需要同时传输到设备或从设备传输。
内核将分散的缓冲区表示为struct scatterlist :
struct scatterlist{
unsigned long page_link;
unsigned int offset;
unsigned int length;
dma_addr_t dma_address;
unsigned int dma_length;
};
为了设置分散列表映射,应该进行如下操作:
- 分配分散的缓冲区
- 创建分散列表数组,并使用sg_set_buf()分配的内存填充它。
- 在该分散列表上调用dma_map_sg()
- 一旦完成DMA,就调用dma_unmap_sg()来取消映射分散列表
u32 *wbuf1,*wbuf2,*wbuf3;
wbuf1 = kzalloc(SDMA_BUF_SIZE,GFP_DMA);
wbuf2 = kzalloc(SDMA_BUF_SIZE,GFP_DMA);
wbuf3 = kzalloc(SDMA_BUF_SIZE/2,GFP_DMA);
struct scatterlist sg[3];
sg_init_table(sg,3);
sg_set_buf(&sg[0],wbuf1,SDMA_BUF_SIZE);
sg_set_buf(&sg[1],wbuf2,SDMA_BUF_SIZE);
sg_set_buf(&sg[2],wbuf3,SDMA_BUF_SIZE/2);
ret = dma_map_sg(NULL,sg,3,DMA_MEM_TO_MEM);
dma_map_sg()和dma_unmap_sg()负责缓存一致性。但是,如果需要使用相同的映射来访问DMA传输之间的数据,则必须以适当的方式在每次传输之间同步缓冲区,如果CPU需要访问缓冲区,可以通过dma_sync_sg_for_cpu()进行同步,如果设备需要,则调用dma_sync_sg_for_device()进行同步。单区域映射的函数是dma_sync_single_for_cpu()和dma_sync_single_for_device()
2 完成的概念
使用完成需要下面的文件:
#include <linux/completion.h>
完成由struct completion表示,可以动态或静态创建。
静态创建生命和初始化如下:
DECLARE_COMPLETION(my_comp);
动态分配如下:
struct completion my_comp;
init_completion(&my_comp);
当驱动程序启动的工作必须等待某件事情完成时,它只需将等待完成事件传递给wait_for_completion()函数,调用wait_for_completion()任务将会被阻塞
void wait_for_completion(struct completion *comp);
代码的其他部分确定该完成事件发生时,它可以用以下方式唤醒正在等待该事件的进程:
void complete(struct completion *comp);
void cimplete_all(struct completion *comp);
complete只会唤醒一个等待进程,而complete_all()会唤醒等待该事件的所有进程。
3 DMA引擎API
DMA引擎是开发DMA控制器驱动程序的通用内核框架。DMA的主要目标是在复制内存时减轻CPU的负担。使用通道将I/O数据传输委托给DMA引擎,DMA引擎通过其驱动程序提供一组可供其他设备(从设备)使用的通道。
必须引用头文件如下:
#include <linux/dmaengine.h>
从设备DMA用法很简单,它包含如下步骤:
- 分配DMA从通道
- 设置从设备和控制器的特定参数
- 获取事务的描述符
- 提交事物
- 发出挂起的请求并等待回调通知
分配DMA从通道
使用dma_request_channel()请求通道:
struct dma_chan * dma_request_channel(const dma_cap_mask_t *mask,dma_filter_fn fn,void *fn_param);
mask是为了指定驱动程序需要执行的传输类型,表示该通道必须满足的功能,是位图掩码
dma_filter_fn定义为:
typedef bool (*dma_filter_fn)(struct dma_chan *chan,void *filter_param);
如果filter_fn参数为NULL,则dma_request_channel将只返回满足功能掩码的第一个通道。否则,当掩码参数不足以指定所需的通道时,可以使用filter_fn作为系统中可用通道的过滤器。
通过此接口分配的通道由调用者独占,直到调用dma_release_channel()为止:
void dma_release_channel(struct dma_chan *chan);
设置从设备和控制器指定参数
此步骤引入了新的数据结构struct dma_slave_config ,它表示DMA从通道的运行配置。这允许为外设指定设置。
int dmaengine_slave_config(struct dma_chan *chan,struct dma_slave_config *config);
struct dma_slave_config {
enum dma_transfer_direction direction;
phys_addr_t src_addr;
phys_addr_t dst_addr;
enum dma_slave_buswidth src_addr_width;
enum dma_slave_buswidth dst_addr_width;
u32 src_maxburst;
u32 dst_maxburst;
u32 src_port_window_size;
u32 dst_port_window_size;
bool device_fc;
unsigned int slave_id;
void *peripheral_config;
size_t peripheral_size;
};
获取事务描述符
在获取DMA通道时,返回值是struct dma_chan 结构的实例,它包含struct dma_device*device 字段,它就是提供该通道的DMA控制器,此控制器内核驱动提供一组函数来准备DMA事务:
struct dma_device {
struct kref ref;
unsigned int chancnt;
unsigned int privatecnt;
struct list_head channels;
struct list_head global_node;
struct dma_filter filter;
dma_cap_mask_t cap_mask;
enum dma_desc_metadata_mode desc_metadata_modes;
unsigned short max_xor;
unsigned short max_pq;
enum dmaengine_alignment copy_align;
enum dmaengine_alignment xor_align;
enum dmaengine_alignment pq_align;
enum dmaengine_alignment fill_align;
#define DMA_HAS_PQ_CONTINUE (1 << 15)
int dev_id;
struct device *dev;
struct module *owner;
struct ida chan_ida;
struct mutex chan_mutex;
u32 src_addr_widths;
u32 dst_addr_widths;
u32 directions;
u32 min_burst;
u32 max_burst;
u32 max_sg_burst;
bool descriptor_reuse;
enum dma_residue_granularity residue_granularity;
int (*device_alloc_chan_resources)(struct dma_chan *chan);
int (*device_router_config)(struct dma_chan *chan);
void (*device_free_chan_resources)(struct dma_chan *chan);
struct dma_async_tx_descriptor *(*device_prep_dma_memcpy)(
struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_xor)(
struct dma_chan *chan, dma_addr_t dst, dma_addr_t *src,
unsigned int src_cnt, size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_xor_val)(
struct dma_chan *chan, dma_addr_t *src, unsigned int src_cnt,
size_t len, enum sum_check_flags *result, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_pq)(
struct dma_chan *chan, dma_addr_t *dst, dma_addr_t *src,
unsigned int src_cnt, const unsigned char *scf,
size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_pq_val)(
struct dma_chan *chan, dma_addr_t *pq, dma_addr_t *src,
unsigned int src_cnt, const unsigned char *scf, size_t len,
enum sum_check_flags *pqres, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_memset)(
struct dma_chan *chan, dma_addr_t dest, int value, size_t len,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_memset_sg)(
struct dma_chan *chan, struct scatterlist *sg,
unsigned int nents, int value, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_interrupt)(
struct dma_chan *chan, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_transfer_direction direction,
unsigned long flags, void *context);
struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_transfer_direction direction,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_interleaved_dma)(
struct dma_chan *chan, struct dma_interleaved_template *xt,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_imm_data)(
struct dma_chan *chan, dma_addr_t dst, u64 data,
unsigned long flags);
void (*device_caps)(struct dma_chan *chan,
struct dma_slave_caps *caps);
int (*device_config)(struct dma_chan *chan,
struct dma_slave_config *config);
int (*device_pause)(struct dma_chan *chan);
int (*device_resume)(struct dma_chan *chan);
int (*device_terminate_all)(struct dma_chan *chan);
void (*device_synchronize)(struct dma_chan *chan);
enum dma_status (*device_tx_status)(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate);
void (*device_issue_pending)(struct dma_chan *chan);
void (*device_release)(struct dma_device *dev);
void (*dbg_summary_show)(struct seq_file *s, struct dma_device *dev);
struct dentry *dbg_dev_root;
};
比如device_prep_dma_memcpy()函数: 准备memcpy操作。其中有些函数都会返回指向struct dma_async_tx_descriptor结构的指针,struct dma_async_tx_descriptor表示我们想操作的事务(执行这些函数)
struct dma_async_tx_descriptor {
dma_cookie_t cookie;
enum dma_ctrl_flags flags;
dma_addr_t phys;
struct dma_chan *chan;
dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
int (*desc_free)(struct dma_async_tx_descriptor *tx);
dma_async_tx_callback callback;
dma_async_tx_callback_result callback_result;
void *callback_param;
struct dmaengine_unmap_data *unmap;
enum dma_desc_metadata_mode desc_metadata_mode;
struct dma_descriptor_metadata_ops *metadata_ops;
#ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH
struct dma_async_tx_descriptor *next;
struct dma_async_tx_descriptor *parent;
spinlock_t lock;
#endif
};
callback域是操作完成后调用的例程,赋值是一个函数名,
typedef void (*dma_async_tx_callback)(void *dma_async_param);
比如执行完device_prep_dma_memcpy()就会调用callback
提交事务
要将事务放入驱动程序的等待列表中,需要调用dmaengine_submit()。一旦准备好描述符并添加回调信息,就应该将其放在DMA引擎驱动程序等待队列中:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);
该函数会返回一个cooke,可以通过其他DMA引擎检查DMA活动的进度。dmaengine_submit() 不会启动DMA操作,它只是将其添加到待处理队列中。
发布待处理DMA请求并等待回调通知
启动事务是DMA传输设置的最后一步,在通道上调用dma_async_issue_pending()来激活通道待处理队列中的事务。如果通道空闲,则队列中的第一个事务将启动,后续事务排队等候。DMA操作完成时,队列中的下一个事务启动,并触发软中断(tasklet)。如果已经设置,则该tasklet负责调用客户端驱动程序的完成回调例程进行通知
void dma_async_issue_pending(struct dma_chan*chan);
4 程序
单缓冲区映射
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/platform_data/dma-imx.h>
#include <linux/dmaengine.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/delay.h>
static int gMajor;
static struct class *dma_tm_class;
u32 *wbuf;
u32 *rbuf;
struct dma_chan *dma_m2m_chan;
struct completion dma_m2m_ok;
#define SDMA_BUF_SIZE 1024
static bool dma_m2m_filter(struct dma_chan *chan, void *param)
{
if (!imx_dma_is_general_purpose(chan))
return false;
chan->private = param;
return true;
}
int sdma_open(struct inode * inode, struct file * filp)
{
dma_cap_mask_t dma_m2m_mask;
struct imx_dma_data m2m_dma_data = {0};
init_completion(&dma_m2m_ok);
dma_cap_zero(dma_m2m_mask);
dma_cap_set(DMA_MEMCPY, dma_m2m_mask);
m2m_dma_data.peripheral_type = IMX_DMATYPE_MEMORY;
m2m_dma_data.priority = DMA_PRIO_HIGH;
dma_m2m_chan = dma_request_channel(dma_m2m_mask, dma_m2m_filter, &m2m_dma_data);
if (!dma_m2m_chan) {
pr_err("Error opening the SDMA memory to memory channel\n");
return -EINVAL;
} else {
pr_info("opened channel %d, req lin %d\n", dma_m2m_chan->chan_id, m2m_dma_data.dma_request);
}
wbuf = kzalloc(SDMA_BUF_SIZE, GFP_DMA);
if(!wbuf) {
pr_err("error wbuf !!!!!!!!!!!\n");
return -1;
}
rbuf = kzalloc(SDMA_BUF_SIZE, GFP_DMA);
if(!rbuf) {
pr_err("error rbuf !!!!!!!!!!!\n");
return -1;
}
return 0;
}
int sdma_release(struct inode * inode, struct file * filp)
{
dma_release_channel(dma_m2m_chan);
dma_m2m_chan = NULL;
kfree(wbuf);
kfree(rbuf);
return 0;
}
ssize_t sdma_read (struct file *filp, char __user * buf, size_t count,
loff_t * offset)
{
int i;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,35))
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
if (*(rbuf+i) != *(wbuf+i)) {
pr_err("Single DMA buffer copy falled!,r=%x,w=%x,%d\n", *(rbuf+i), *(wbuf+i), i);
return 0;
}
}
pr_info("buffer copy passed!\n");
#endif
return 0;
}
static void dma_m2m_callback(void *data)
{
pr_info("in %s\n",__func__);
complete(&dma_m2m_ok);
return ;
}
ssize_t sdma_write(struct file * filp, const char __user * buf, size_t count,
loff_t * offset)
{
u32 *index, i;
struct dma_slave_config dma_m2m_config = {0};
struct dma_async_tx_descriptor *dma_m2m_desc;
dma_addr_t dma_src, dma_dst;
dma_cookie_t cookie;
index = wbuf;
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
*(index + i) = 0x56565656;
}
dma_m2m_config.direction = DMA_MEM_TO_MEM;
dma_m2m_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmaengine_slave_config(dma_m2m_chan, &dma_m2m_config);
dma_src = dma_map_single(NULL, wbuf, SDMA_BUF_SIZE, DMA_TO_DEVICE);
dma_dst = dma_map_single(NULL, rbuf, SDMA_BUF_SIZE, DMA_FROM_DEVICE);
dma_m2m_desc = dma_m2m_chan->device->device_prep_dma_memcpy(dma_m2m_chan, dma_dst, dma_src, SDMA_BUF_SIZE,0);
dma_m2m_desc->callback = dma_m2m_callback;
if (dma_m2m_desc)
pr_info("Got a DMA descriptor\n");
else
pr_err("error in prep_dma_sg\n");
cookie = dmaengine_submit(dma_m2m_desc);
pr_info("Got this cookie: %d\n", cookie);
dma_async_issue_pending(dma_m2m_chan);
pr_info("waiting for DMA transaction...\n");
wait_for_completion(&dma_m2m_ok);
dma_unmap_single(NULL, dma_src, SDMA_BUF_SIZE, DMA_TO_DEVICE);
dma_unmap_single(NULL, dma_dst, SDMA_BUF_SIZE, DMA_FROM_DEVICE);
return count;
}
struct file_operations dma_fops = {
open: sdma_open,
release: sdma_release,
read: sdma_read,
write: sdma_write,
};
int __init sdma_init_module(void)
{
struct device *temp_class;
int error;
error = register_chrdev(0, "sdma_test", &dma_fops);
if (error < 0) {
pr_err("SDMA test driver can't get major number\n");
return error;
}
gMajor = error;
pr_info("SDMA test major number = %d\n",gMajor);
dma_tm_class = class_create(THIS_MODULE, "sdma_test");
if (IS_ERR(dma_tm_class)) {
pr_err(KERN_ERR "Error creating sdma test module class.\n");
unregister_chrdev(gMajor, "sdma_test");
return PTR_ERR(dma_tm_class);
}
temp_class = device_create(dma_tm_class, NULL,
MKDEV(gMajor, 0), NULL, "sdma_test");
if (IS_ERR(temp_class)) {
pr_err(KERN_ERR "Error creating sdma test class device.\n");
class_destroy(dma_tm_class);
unregister_chrdev(gMajor, "sdma_test");
return -1;
}
pr_info("SDMA test Driver Module loaded\n");
return 0;
}
static void sdma_cleanup_module(void)
{
unregister_chrdev(gMajor, "sdma_test");
device_destroy(dma_tm_class, MKDEV(gMajor, 0));
class_destroy(dma_tm_class);
pr_info("SDMA test Driver Module Unloaded\n");
}
module_init(sdma_init_module);
module_exit(sdma_cleanup_module);
MODULE_AUTHOR("Freescale Semiconductor");
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("SDMA test driver");
MODULE_LICENSE("GPL");
- 应用层执行open函数是会分配一个从DMA通道,并且分配两个缓冲区wbuf,rbuf,wbuf是我们要传输的数据,将数据传输到rbuf
- 应用层执行write会依次执行下面的步骤:
- 调用dmaengine_slave_config设置从设备和控制器指定参数
- 获取事务描述符:dma_m2m_desc ,并指定回调函数,dma_m2m_desc 是通过调用通道(dma_m2m_chan)里面的device域中的函数返回的
- 调用dmaengine_submit提交事务
- 调用dma_async_issue_pending函数激活事务
- 调用wait_for_completion来等待事务完成(完成后会自动调用dma_m2m_desc 的回调函数callback ),会执行complete函数,程序会继续执行下去。
分散聚集映射
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/platform_data/dma-imx.h>
#include <asm/mach/dma.h>
#include <linux/dmaengine.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/delay.h>
static int gMajor;
static struct class *dma_tm_class;
u32 *wbuf, *wbuf2, *wbuf3;
u32 *rbuf, *rbuf2, *rbuf3;
struct dma_chan *dma_m2m_chan;
struct completion dma_m2m_ok;
struct scatterlist sg[3], sg2[3];
#define SDMA_BUF_SIZE 1024*16
static bool dma_m2m_filter(struct dma_chan *chan, void *param)
{
if (!imx_dma_is_general_purpose(chan))
return false;
chan->private = param;
return true;
}
int sdma_open(struct inode * inode, struct file * filp)
{
dma_cap_mask_t dma_m2m_mask;
struct imx_dma_data m2m_dma_data = {0};
init_completion(&dma_m2m_ok);
dma_cap_zero(dma_m2m_mask);
dma_cap_set(DMA_MEMCPY, dma_m2m_mask);
m2m_dma_data.peripheral_type = IMX_DMATYPE_MEMORY;
m2m_dma_data.priority = DMA_PRIO_HIGH;
dma_m2m_chan = dma_request_channel(dma_m2m_mask, dma_m2m_filter, &m2m_dma_data);
if (!dma_m2m_chan) {
pr_info("Error opening the SDMA memory to memory channel\n");
return -EINVAL;
} else {
pr_info("opened channel %d, req lin %d\n", dma_m2m_chan->chan_id, m2m_dma_data.dma_request);
}
wbuf = kzalloc(SDMA_BUF_SIZE, GFP_DMA);
if(!wbuf) {
pr_info("error wbuf !!!!!!!!!!!\n");
return -1;
}
wbuf2 = kzalloc(SDMA_BUF_SIZE, GFP_DMA);
if(!wbuf2) {
pr_info("error wbuf2 !!!!!!!!!!!\n");
return -1;
}
wbuf3 = kzalloc(SDMA_BUF_SIZE, GFP_DMA);
if(!wbuf3) {
pr_info("error wbuf3 !!!!!!!!!!!\n");
return -1;
}
rbuf = kzalloc(SDMA_BUF_SIZE, GFP_DMA);
if(!rbuf) {
pr_info("error rbuf !!!!!!!!!!!\n");
return -1;
}
rbuf2 = kzalloc(SDMA_BUF_SIZE, GFP_DMA);
if(!rbuf2) {
pr_info("error rbuf2 !!!!!!!!!!!\n");
return -1;
}
rbuf3 = kzalloc(SDMA_BUF_SIZE, GFP_DMA);
if(!rbuf3) {
pr_info("error rbuf3 !!!!!!!!!!!\n");
return -1;
}
return 0;
}
int sdma_release(struct inode * inode, struct file * filp)
{
dma_release_channel(dma_m2m_chan);
dma_m2m_chan = NULL;
kfree(wbuf);
kfree(wbuf2);
kfree(wbuf3);
kfree(rbuf);
kfree(rbuf2);
kfree(rbuf3);
return 0;
}
ssize_t sdma_read (struct file *filp, char __user * buf, size_t count,
loff_t * offset)
{
int i;
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
if (*(rbuf+i) != *(wbuf+i)) {
pr_info("buffer 1 copy falled!\n");
return 0;
}
}
pr_info("buffer 1 copy passed!\n");
for (i=0; i<SDMA_BUF_SIZE/2/4; i++) {
if (*(rbuf2+i) != *(wbuf2+i)) {
pr_err("buffer 2 copy falled!\n");
return 0;
}
}
pr_info("buffer 2 copy passed!\n");
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
if (*(rbuf3+i) != *(wbuf3+i)) {
pr_info("buffer 3 copy falled!\n");
return 0;
}
}
pr_info("buffer 3 copy passed!\n");
return 0;
}
static void dma_m2m_callback(void *data)
{
pr_info("in %s\n",__func__);
complete(&dma_m2m_ok);
return ;
}
ssize_t sdma_write(struct file * filp, const char __user * buf, size_t count,
loff_t * offset)
{
u32 *index1, *index2, *index3, i, ret;
struct dma_slave_config dma_m2m_config = {0};
struct dma_async_tx_descriptor *dma_m2m_desc;
dma_cookie_t cookie;
struct timeval end_time;
unsigned long end, start;
index1 = wbuf;
index2 = wbuf2;
index3 = wbuf3;
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
*(index1 + i) = 0x12121212;
}
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
*(index2 + i) = 0x34343434;
}
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
*(index3 + i) = 0x56565656;
}
#if 0
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
pr_info("input data_%d : %x\n", i, *(wbuf+i));
}
for (i=0; i<SDMA_BUF_SIZE/2/4; i++) {
pr_info("input data2_%d : %x\n", i, *(wbuf2+i));
}
for (i=0; i<SDMA_BUF_SIZE/4; i++) {
pr_info("input data3_%d : %x\n", i, *(wbuf3+i));
}
#endif
dma_m2m_config.direction = DMA_MEM_TO_MEM;
dma_m2m_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmaengine_slave_config(dma_m2m_chan, &dma_m2m_config);
sg_init_table(sg, 3);
sg_set_buf(&sg[0], wbuf, SDMA_BUF_SIZE);
sg_set_buf(&sg[1], wbuf2, SDMA_BUF_SIZE);
sg_set_buf(&sg[2], wbuf3, SDMA_BUF_SIZE);
ret = dma_map_sg(NULL, sg, 3, dma_m2m_config.direction);
sg_init_table(sg2, 3);
sg_set_buf(&sg2[0], rbuf, SDMA_BUF_SIZE);
sg_set_buf(&sg2[1], rbuf2, SDMA_BUF_SIZE);
sg_set_buf(&sg2[2], rbuf3, SDMA_BUF_SIZE);
ret = dma_map_sg(NULL, sg2, 3, dma_m2m_config.direction);
dma_m2m_desc = dma_m2m_chan->device->device_prep_dma_sg(dma_m2m_chan, sg2, 3, sg, 3, DMA_MEM_TO_MEM);
dma_m2m_desc->callback = dma_m2m_callback;
if (dma_m2m_desc)
pr_info("Got a DMA descriptor\n");
else
pr_info("error in prep_dma_sg\n");
do_gettimeofday(&end_time);
start = end_time.tv_sec*1000000 + end_time.tv_usec;
cookie = dmaengine_submit(dma_m2m_desc);
pr_info("Got this cookie: %d\n", cookie);
dma_async_issue_pending(dma_m2m_chan);
pr_info("waiting for DMA transaction...\n");
wait_for_completion(&dma_m2m_ok);
do_gettimeofday(&end_time);
end = end_time.tv_sec*1000000 + end_time.tv_usec;
pr_info("end - start = %d\n", end - start);
dma_unmap_sg(NULL, sg, 3, dma_m2m_config.direction);
dma_unmap_sg(NULL, sg2, 3, dma_m2m_config.direction);
return count;
}
struct file_operations dma_fops = {
open: sdma_open,
release: sdma_release,
read: sdma_read,
write: sdma_write,
};
int __init sdma_init_module(void)
{
struct device *temp_class;
int error;
error = register_chrdev(0, "sdma_test", &dma_fops);
if (error < 0) {
pr_info("SDMA test driver can't get major number\n");
return error;
}
gMajor = error;
pr_info("SDMA test major number = %d\n",gMajor);
dma_tm_class = class_create(THIS_MODULE, "sdma_test");
if (IS_ERR(dma_tm_class)) {
pr_info(KERN_ERR "Error creating sdma test module class.\n");
unregister_chrdev(gMajor, "sdma_test");
return PTR_ERR(dma_tm_class);
}
temp_class = device_create(dma_tm_class, NULL,
MKDEV(gMajor, 0), NULL, "sdma_test");
if (IS_ERR(temp_class)) {
pr_info(KERN_ERR "Error creating sdma test class device.\n");
class_destroy(dma_tm_class);
unregister_chrdev(gMajor, "sdma_test");
return -1;
}
pr_info("SDMA test Driver Module loaded\n");
return 0;
}
static void sdma_cleanup_module(void)
{
unregister_chrdev(gMajor, "sdma_test");
device_destroy(dma_tm_class, MKDEV(gMajor, 0));
class_destroy(dma_tm_class);
pr_info("SDMA test Driver Module Unloaded\n");
}
module_init(sdma_init_module);
module_exit(sdma_cleanup_module);
MODULE_AUTHOR("Freescale Semiconductor");
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("SDMA test driver");
MODULE_LICENSE("GPL");
|