本文以JFMQL100的Linux系统的AXI接口的平台驱动为例,介绍嵌入式Linux的平台驱动编写、测试软件编写以及验证方式。本文的方法适用于任意嵌入式芯片Linux的物理地址映射的平台(platform)驱动的编写、测试与应用。
本文中AXI的开始地址为0x80000000,长度为0x2000(8KB)的地址空间进行地址映射。Vivado中查看AXI的地址范围如下所示:
本文PL-PS的中断(IRQ_F2P[0]),提醒PS端进行AXI数据读取。该中断在进口ZYNQ7的中断号为61,在JFMQL100TAI的中断号为57。在Linux系统设备树配置上均需要中断号减32,即ZYNQ7的中断号配置为29,JFMQL100TAI的中断号配置为25。
1 设备树修改
在设备树的根节点添加自定义的axi驱动节点,如下所示:
2 驱动源码
1.新建axi驱动目录,如下图所示:
2.在axi驱动目录下,新建axi驱动源文件custom_axi_driver.c,驱动源文件如下所示:
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/version.h>
#include <linux/io.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/of_address.h>
#include <linux/of_dma.h>
#include <linux/of_platform.h>
#include <linux/of_irq.h>
#define DEVICE_NAME "custom_axi_driver"
#define AXI_BASE_ADDR_LEN 0x4000
#define AXI_BASE_ADDR 0x80000000
enum IOCTL_CMD
{
CMD_GET_FPGA_VERSION = 10, //获得FPGA版本号 10
CMD_GET_FPGA_TEMP , //获得FPGA温度 11
CMD_READ_REG, //读axi寄存器,单个寄存器操作 12
CMD_WRITE_REG //写axi寄存器,单个寄存器操作 13
};
void __iomem *axi_base_addr; //ioremap映射的虚拟地址
/*定义一个平台*/
typedef struct axi_struct
{
int major;
struct class *axi_class; //
struct device *axi_class_dev; //设备
struct cdev axi_cdev; //cdev 结构
struct file_operations *drv_fops; //操作函数接口
char axi_name[20]; //设备名称
struct platform_device *pdev; //平台设备
struct device_node *nd; //设备节点
}struct_axi_driver,*ptstruct_axi_driver;
static dev_t dev_id; //设备编号
static int irq; //中断号
static struct fasync_struct *irq_async;
//设备打开
static int custom_axi_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO"%s axi_open!!\n",__FUNCTION__);
return 0;
}
//设备关闭
static int custom_axi_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO"%s axi_release!!\n",__FUNCTION__);
return 0;
}
//设备写
static ssize_t custom_axi_write(struct file *flip,const char __user *buf,size_t cnt,loff_t *offt)
{
unsigned int start_addr = 0;
unsigned int len = 0;
if(copy_from_user(&start_addr, buf, sizeof(unsigned int)))
{
printk(KERN_ERR"%s copy_from_user is error!!\n",__FUNCTION__);
return -1;
}
if(copy_from_user(&len, buf + sizeof(unsigned int), sizeof(unsigned int)))
{
printk(KERN_ERR"%s copy_from_user is error!!\n",__FUNCTION__);
return -1;
}
if(len <= AXI_BASE_ADDR_LEN)
{
if(copy_from_user((unsigned int *)(axi_base_addr + start_addr), buf + sizeof(unsigned int)+ sizeof(unsigned int), len))
{
printk("%s copy_from_user is error!!\n",__FUNCTION__);
return -1;
}
}
else
{
printk("%s write num beyond AXI_ADDRESS_LEN!!\n",__FUNCTION__);
return -1;
}
return len;
}
//设备读
static ssize_t custom_axi_read(struct file *flip, char __user *buf, size_t cnt, loff_t *offt)
{
unsigned int start_addr = 0;
unsigned int len = 0;
if(copy_from_user(&start_addr, buf, sizeof(unsigned int)))
{
printk(KERN_ERR"%s copy_from_user start_addr is error!!\n",__FUNCTION__);
return -1;
}
if(copy_from_user(&len, buf + sizeof(unsigned int), sizeof(unsigned int)))
{
printk(KERN_ERR"%s copy_from_user len is error!!\n",__FUNCTION__);
return -1;
}
if(len <= AXI_BASE_ADDR_LEN)
{
if(copy_to_user(buf + sizeof(unsigned int) + sizeof(unsigned int), (unsigned int *)(axi_base_addr + start_addr), len) != 0)
{
printk(KERN_ERR"copy to user error!!\n");
return -1;
}
}
else
{
printk("%s read num beyond AXI_ADDRESS_LEN!!\n",__FUNCTION__);
return -1;
}
return len;
}
//设备IO Control
static long custom_axi_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
unsigned int reg_value = 0;
unsigned int reg_addr = 0;
switch(cmd)
{
case CMD_GET_FPGA_VERSION://读FPGA版本号
reg_value=__raw_readl(axi_base_addr);
if(copy_to_user(argp,(unsigned int *)®_value ,sizeof(unsigned int)))
{
printk("%s copy_to_user is error!!\n",__FUNCTION__);
return -1;
}
break;
case CMD_GET_FPGA_TEMP://读FPGA温度
reg_value=__raw_readl((axi_base_addr + sizeof(unsigned int)));
if(copy_to_user(argp,(unsigned int *)®_value ,sizeof(unsigned int)))
{
printk("%s copy_to_user is error!!\n",__FUNCTION__);
return -1;
}
break;
case CMD_READ_REG://读axi寄存器
if(copy_from_user((unsigned int *)®_addr,argp,sizeof(unsigned int)))
{
printk("%s copy_from_user is error!!\n",__FUNCTION__);
return -1;
}
reg_value=__raw_readl((axi_base_addr + reg_addr));
if(copy_to_user(argp + sizeof(unsigned int),(unsigned int *)®_value ,sizeof(unsigned int)))
{
printk("%s copy_to_user is error!!\n",__FUNCTION__);
return -1;
}
break;
case CMD_WRITE_REG://写axi寄存器
if(copy_from_user((unsigned int *)®_addr,argp,sizeof(unsigned int)))
{
printk("%s copy_from_user is error!!\n",__FUNCTION__);
return -1;
}
if(copy_from_user((unsigned int *)®_value,argp+sizeof(unsigned int),sizeof(unsigned int)))
{
printk("%s copy_from_user is error!!\n",__FUNCTION__);
return -1;
}
__raw_writel(reg_value,(axi_base_addr + reg_addr));
break;
default:
break;
}
return sizeof(unsigned int);//返回操作字节数
}
//中断
static int irq_drv_fasync (int fd, struct file *filp, int on)
{
return fasync_helper (fd, filp, on, &irq_async);
}
//中断触发函数
static irqreturn_t irq_interrupt(int irq, void *dev_id)
{
kill_fasync (&irq_async, SIGIO, POLL_IN);
return 0;
}
struct file_operations axi_fops = {
.owner = THIS_MODULE,
.open = custom_axi_open,
.release = custom_axi_release,
.read = custom_axi_read,
.write = custom_axi_write,
.fasync = irq_drv_fasync,
.unlocked_ioctl = custom_axi_ioctl
};
static int custom_axi_probe(struct platform_device *pdev)
{
int ret;
ptstruct_axi_driver pt_axidriver = NULL;
printk("axi probe start!\n");
/***********************************************************************
* register character device
***********************************************************************/
printk("axi-reg-driver: register device...\n");
pt_axidriver = (ptstruct_axi_driver)kmalloc(sizeof(struct_axi_driver),GFP_KERNEL);
if (pt_axidriver == NULL)
{
printk(KERN_ERR"%s kmalloc is error!\n",__FUNCTION__);
return -1;
}
axi_base_addr = ioremap(AXI_BASE_ADDR, AXI_BASE_ADDR_LEN);//映射本地地址
if (!axi_base_addr)
{
printk(KERN_ERR"%s ioremap AXI_base is error!!\n",__FUNCTION__);
goto error5;
}
pt_axidriver->pdev = pdev;
platform_set_drvdata(pdev, pt_axidriver);
pt_axidriver->drv_fops = &axi_fops;
strcpy(pt_axidriver->axi_name,DEVICE_NAME);
/*从系统获取主设备编号*/
ret = alloc_chrdev_region(&dev_id, 0, 5, pt_axidriver->axi_name);
if (ret < 0)
{
printk(KERN_ERR"cannot alloc_chrdev_region!\n");
goto error4;
}
pt_axidriver->major = MAJOR(dev_id);
printk("MAJOR dev is successed!\n");
cdev_init(&pt_axidriver->axi_cdev , &axi_fops); //初始化axi结构,fops
printk("axi_cdev init!\n");
if(cdev_add(&pt_axidriver->axi_cdev , dev_id, 5) != 0)
{
printk(KERN_ERR"add axi error\n");
goto error3;
}
printk("axi_cdev add ok!\n");
pt_axidriver->axi_class = class_create(THIS_MODULE, pt_axidriver->axi_name);
if(pt_axidriver->axi_class==NULL)
{
printk(KERN_ERR"creat axi class error\n");
goto error2;
}
printk("axi_cdev class init!\n");
pt_axidriver->axi_class_dev = device_create(pt_axidriver->axi_class, NULL, MKDEV(pt_axidriver->major, 0), NULL, pt_axidriver->axi_name);
if(pt_axidriver->axi_class_dev == NULL)
{
printk(KERN_ERR"device_create is error!\n");
goto error1;
}
printk("axi_cdev class dev init!\n");
irq = platform_get_irq(pdev,0);
if (irq <= 0)
{
goto error0;
}
printk("system irq = %d\n", irq);
//申请上升沿中断触发
ret = request_irq(irq,
irq_interrupt,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
pt_axidriver->axi_name, NULL);
if (ret)
{
printk(KERN_ERR"irq_probe irq error!\n");
goto error0;
}
return 0;
free_irq(irq, NULL);
error0:
device_destroy(pt_axidriver->axi_class,MKDEV(pt_axidriver->major, 0));
error1:
class_destroy(pt_axidriver->axi_class);
error2:
cdev_del(&pt_axidriver->axi_cdev); //移除字符设备
error3:
unregister_chrdev_region(dev_id, 5);
error4:
iounmap(axi_base_addr);//释放地址映射
error5:
kfree(pt_axidriver);
return -1;
}
static int custom_axi_remove(struct platform_device *pdev)
{
ptstruct_axi_driver pt_axidriver = (ptstruct_axi_driver)platform_get_drvdata(pdev);
free_irq(irq, NULL);//释放中断
device_destroy(pt_axidriver->axi_class,MKDEV(pt_axidriver->major, 0));
class_destroy(pt_axidriver->axi_class);
cdev_del(&pt_axidriver->axi_cdev); //移除字符设备
unregister_chrdev_region(dev_id, 5); //释放设备
iounmap(axi_base_addr); //释放axi虚拟地址映射
kfree(pt_axidriver); //释放驱动
return 0;
}
/*定义,初始化平台驱动*/
static const struct of_device_id custom_axi_of_match[] = {
{.compatible = "lsl,custom_axi_driver"},
{},
};
MODULE_DEVICE_TABLE(of, custom_axi_of_match);
static struct platform_driver custom_axi_platform_driver = {
.driver = {
.name = "custom_axi_driver",
.of_match_table = custom_axi_of_match,
},
.probe = custom_axi_probe,
.remove = custom_axi_remove,
};
module_platform_driver(custom_axi_platform_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("lsl custom_axi_driver platform driver");
MODULE_AUTHOR("LSL");
3.在axi驱动目录下,新建Makefile文件,Makefile文件中指定内核源码路径。如下图所示:
4.在axi驱动目录下,新建编译脚本build.sh,编译脚本内容如下所示:
5.运行编译脚本,对驱动进行动态编译,生成驱动的ko文件custom_axi_driver.ko,编译结果如下所示:
3 驱动测试程序
1.新建驱动测试程序目录app_driver_test,如下图所示:
2.在驱动测试目录下,新建驱动测试软件app_driver_test.c,驱动测试源文件如下图所示:
3.在驱动测试目录下,新建驱动测试软件app_driver_test.c,驱动测试源文件如下图所示:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <linux/ioctl.h>
#include "app_driver_test.h"
int pl_axi_fd;
static int irq_count = 0;
int axi_writeReg(unsigned int reg,unsigned int value, unsigned int axi_offset_addr)
{
Axi_CtrlRwStruct Axi_CtrlRwStructure;
int ret;
Axi_CtrlRwStructure.reg_addr=REG_ADDR(reg)+axi_offset_addr;
Axi_CtrlRwStructure.reg_value=value;
ret = ioctl(pl_axi_fd,CMD_WRITE_REG,&Axi_CtrlRwStructure);
if(ret<0)
{
printf("axi_writeReg addr %xis error!\n",Axi_CtrlRwStructure.reg_addr);
return -1;
}
return ret;
}
int axi_readReg(unsigned int reg,unsigned int *value,unsigned int axi_offset_addr)
{
int ret=0;
Axi_CtrlRwStruct Axi_CtrlRwStructure;
Axi_CtrlRwStructure.reg_addr=REG_ADDR(reg)+axi_offset_addr;
ret = ioctl(pl_axi_fd,CMD_READ_REG,&Axi_CtrlRwStructure);
if(ret<0)
{
printf("axi_readReg addr %xis error!\n",Axi_CtrlRwStructure.reg_addr);
return -1;
}
*value=Axi_CtrlRwStructure.reg_value;
return ret;
}
int axi_GetFpga_Version(unsigned int *version)
{
unsigned int buf;
int ret;
ret = ioctl(pl_axi_fd,CMD_GET_FPGA_VERSION,&buf);
if(ret<0)
{
printf("axi_GetFpga_Version is error!\n");
return -1;
}
*version=buf;
return ret;
}
int axi_GetFpga_Temp(unsigned int *temp)
{
unsigned int buf;
int ret;
ret = ioctl(pl_axi_fd,CMD_GET_FPGA_TEMP,&buf);
if(ret<0)
{
printf("axi_GetFpga_Temp is error!\n");
return -1;
}
*temp=buf;
return ret;
}
int axi_writeData(unsigned int start_addr, unsigned int len, unsigned char *value)
{
int ret;
Axi_DataRwStruct Axi_DataRwStructure;
if(len>MAX_DATA_BUFF_SIZE)
{
printf("write the data len beyond Max buffer!\n");
return -1;
}
Axi_DataRwStructure.start_addr=(start_addr/4)*4;//开始地址4字节对齐
Axi_DataRwStructure.len=(len/4)*4;//长度4字节对齐
memcpy(Axi_DataRwStructure.buff,value,Axi_DataRwStructure.len);
ret=write(pl_axi_fd, &Axi_DataRwStructure, Axi_DataRwStructure.len+2*sizeof(unsigned int));
if(ret<0)
{
printf("axi_writeData is error!\n");
return -1;
}
return ret;
}
int axi_readData(unsigned int start_addr, unsigned int len, unsigned char *value)
{
int ret;
Axi_DataRwStruct Axi_DataRwStructure;
if(len>MAX_DATA_BUFF_SIZE)
{
printf("read the data len beyond Max buffer!\n");
return -1;
}
Axi_DataRwStructure.start_addr=(start_addr/4)*4;//开始地址4字节对齐
Axi_DataRwStructure.len=(len/4)*4;//长度4字节对齐
ret=read(pl_axi_fd, &Axi_DataRwStructure, Axi_DataRwStructure.len+2*sizeof(unsigned int));
if(ret<0)
{
printf("axi_readData is error!\n");
return -1;
}
memcpy(value,Axi_DataRwStructure.buff,ret);
return ret;
}
void pl_axi_device_init(void)
{
int Oflags;
pl_axi_fd = open(PL_AXI_DEV, O_RDWR);
if (pl_axi_fd < 0)
{
printf("can't open PL_AXI_DEV!\n");
}
fcntl(pl_axi_fd, F_SETOWN, getpid());
Oflags = fcntl(pl_axi_fd, F_GETFL);
fcntl(pl_axi_fd, F_SETFL, Oflags | FASYNC);
}
void sigio_irq_service(int signum)
{
irq_count+=1;
printf("recv the irq_count=%d\n",irq_count);//打印irq中断计数
}
void pl_sigio_irq_init(void)
{
signal(SIGIO, sigio_irq_service);//注册irq服务函数
}
//main入口函数
int main(int argc, char **argv)
{
unsigned int ver,temp;//定义版本号和温度的变量
unsigned int reg_in=0x5aa51234;
unsigned int reg_out;
unsigned char data_in[12]={1,2,3,4,5,6,7,8,9,10,11,12};
unsigned char data_out[12];
unsigned int i,flag;
pl_sigio_irq_init(); //中断使能
pl_axi_device_init();//pl_axi设备打开
axi_GetFpga_Version(&ver);
printf("the fpga version is=0x%x\n",ver);//打印版本号
axi_GetFpga_Temp(&temp);
printf("the fpga temp is=0x%x\n",temp);//打印温度
while(1)
{
axi_writeReg(8,reg_in, 0);//向8号寄存器写入reg_in的值
axi_readReg(8,®_out,0);
if(reg_in==reg_out)//判断读写寄存器函数是否正确
{
printf("write read reg is right!\n");
}
else
{
printf("write read reg is error!\n");
}
axi_writeData(8,sizeof(data_in),data_in);//向8号寄存器写入连续的数据
axi_readData(8,sizeof(data_out),data_out);
for(i=0;i<sizeof(data_out);i++)
{
if(data_in[i]!=data_out[i])//判断读写寄存器函数是否正确
{
flag=1;
printf("write read data is error!\n");
}
}
if(flag==0)
printf("write read data is right!\n");
}
return 0;
}
4.在axi驱动目录下,新建app_driver_test.h的头文件。头文件内容如下图所示:
5.在axi驱动测试程序目录下,新建Makefile文件,Makefile指定交叉编译器环境变量。如下图所示:
6.在axi驱动目录下,新建编译脚本build.sh,编译脚本内容如下所示:
7.运行编译脚本,对驱动测试程序进行编译,生成驱动测试程序的可执行文件app_driver_test,编译结果如下所示:
4 驱动与测试软件的验证
1.拷贝驱动ko文件和驱动测试程序可执行文件到嵌入式系统的工作目录下。
tftp -g -r custom_axi_driver.ko 192.168.0.11
tftp -g -r app_driver_test 192.168.0.11
chmod +x app_driver_test
2.驱动加载
insmod custom_axi_driver.ko
3.运行嵌入式程序的可执行文件。
./app_driver_test