关于驱动的编译和安装这里就不多讲了,无非就是make 和 insmod 。这里讲一下驱动安装时,控制驱动属性的几个参数:
1.中断模式
static unsigned int interrupt_mode;
module_param(interrupt_mode, uint, 0644);
MODULE_PARM_DESC(interrupt_mode, "0 - Auto , 1 - MSI, 2 - Legacy, 3 - MSI-x");
中断模式分为三种,MSIX是最新的中断模式,老版本的内核可能不支持。就比如说我的内核。如果不指定驱动安装额中断参数,那么就会产生内核安装的错误。所以这里我们选择MSI的中断模式。
2.驱动运行模式
static unsigned int poll_mode;
module_param(poll_mode, uint, 0644);
MODULE_PARM_DESC(poll_mode, "Set 1 for hw polling, default is 0 (interrupts)");
驱动的运行模式分为两种, 中断模式和poll_mode 模式,默认是中断模式,设置为1是硬件polling模式。根据实际应用场景可以选择相应的设置。
3.sgdma传输延时
unsigned int h2c_timeout = 10;
module_param(h2c_timeout, uint, 0644);
MODULE_PARM_DESC(h2c_timeout, "H2C sgdma timeout in seconds, default is 10 sec.");
unsigned int c2h_timeout = 10;
module_param(c2h_timeout, uint, 0644);
MODULE_PARM_DESC(c2h_timeout, "C2H sgdma timeout in seconds, default is 10 sec.");
默认是10s钟, 可以自定义设置。
4.其他参数
static unsigned int enable_st_c2h_credit = 0;
module_param(enable_st_c2h_credit, uint, 0644);
MODULE_PARM_DESC(enable_st_c2h_credit,
"Set 1 to enable ST C2H engine credit feature, default is 0 ( credit control disabled)");
unsigned int desc_blen_max = XDMA_DESC_BLEN_MAX;
module_param(desc_blen_max, uint, 0644);
MODULE_PARM_DESC(desc_blen_max,
"per descriptor max. buffer length, default is (1 << 28) - 1");
这两个参数,没有使用过,基本上也没怎么了解。 大概也就是数据传输的认证和描述符的buf长度吧 。
驱动安装成功后,会生成几个设备文件。对应的应用编程, 就是操作这些设备文件来实现对应的业务逻辑。简单的介绍一下几个设备的功能:
1. xdma0_h2c_0 xdma0_c2h_0
这两个设备就是用来读写的设备文件,h2c 是写设备,c2h是读设备。对应的是FPGA端设置的通道。
2. xdma0_events_x
用于处理中断事件的设备,IP核内设置对应的中断号,对应的中断设备。
3. xdma0_user
xdma_user设备节点用于实现PCIe的地址映射,为上层应用提供编程控制接口、AXI接口等
4. xdma0_control
xdma0_control设备节点主要是为上层提供DMA寄存器接口
比较常用的就这几个设备。
两个线程:一个线程用于处理中断事件,另外一个线程用于处理数据读取。线程之间的同步用信号量。
读取的数据保存在文件。
/*信号量初始化*/
sem_init(&c2h_sem, 0, 0);
/*线程创建*/
pthread_create(&c2h_event_thread, NULL, c2h_event_process, NULL);
pthread_create(&c2h_data_thread, NULL, c2h_data_process, NULL);
void *c2h_event_process(void *param)
{
static int flag = 0;
int fd = open_event("/dev/xdma0_events_0");
printf("c2h event thread running, c2h_event_fd = %d\n", fd);
while(1)
{
int val = read_event(fd);
if(val )
{
sem_post(&c2h_sem);
if (flag < total_frame) {
printf("c2h get %d frame event\n",flag);
flag++;
}else{
pthread_exit(0);
}
}else{
continue;
}
}
}
void *c2h_data_process(void *param)
{
int fd = open("/dev/xdma0_c2h_0",O_RDWR | O_NONBLOCK);
printf("c2h data thread running, c2h_data_fd = %d\n", fd);
/*读取的数据写文件*/
FILE *record_fp = fopen("/mnt/nfs/c2h_record.bin", "wb");
unsigned char *buf = new unsigned char[frame_bytes];
while(1)
{
sem_wait(&c2h_sem);
int read_bytes = read(xdma_c2h_fd, c2h_align_mem, trans_bytes);
memcpy(buf, c2h_align_mem, read_bytes);
recv_frame_cnt++;
fwrite(buf, frame_bytes, 1, record_fp);
printf("Have been read %d frame,with %d irq\n",recv_frame_cnt,irq_num);
if(recv_frame_cnt >= total_frame)
{
clock_gettime(CLOCK_MONOTONIC, &ts_end);
printf("video recv over!\n");
delete buf;
fclose(record_fp);
pthread_exit(0);
}
}
}
读取的数据保存在以4096字节的堆区。函数调用时:
/*4096字节对齐分配内存*/
posix_memalign((void **)&c2h_align_mem,4096, frame_bytes);
设备xdma_usr的使用,之前有说过,xdma_user设备节点用于实现PCIe的地址映射,为上层应用提供编程控制接口、AXI接口等。
那么FPGA端可以设置一些属性放在寄存器中供我们读取。比如我这边就从寄存器a4获取的帧的大小,
mmap 函数是将硬件的地址硬件映射至linux 用户层可访问的虚拟地址空间。
int control_fd = open_control("/dev/xdma0_user");
control_base = mmap_control(control_fd,MAP_SIZE);
/*获取帧大小*/
frame_bytes = read_control(control_base,0x0a4);
printf("frame_bytes:%d\n", frame_bytes);
测试代码很简单,逻辑基本上就是这样。
最近我会去研究XDMA的驱动,看他的实际逻辑时如何的。之后会更新一篇文章,来梳理我之后的学习内容。
相关的的驱动及测试代码,以及FPGA工程,已经上传至网盘,有需要的朋友可以自行下载。
链接:https://pan.baidu.com/s/1wdPqUf8_2K6r8ZbVupizrw
提取码:i2sw