12.1概述本节课开始,我们一起学习基于ZYNQ的驱动程序开发。首先声明由于本人能力有限作为一名LINUX驱动初学者,教程更多以笔记形势暂时,笔者对于LINUX驱动程序开发的理解,而不是一名资深的LINUX驱动开发者。笔记从自生学习的角度,记录如何学习基于ZYNQ的驱动开发。想必学习过单片机的‘童鞋们’都很怀念这个词:“点灯”。一闪一闪的灯亮瞎你的猫眼…咳咳…言归正传,笔者毫不夸张地说:“点灯这件事看似简单,实则贯穿着无数的知识点。”只是一千个读者有一千个哈姆雷特罢了。好了好了,我们回到今天的内容。 12.2在设备树中添加设备信息本章节先不使用设备树,所以读者可以跳过此步骤。 虽然不使用设备树,但我们也简单介绍一下设备树的作用吧,读者可以先简单理解为:“linux内核通过读取设备树来了解它到底能掌控哪些设备,这些设备有什么样的参数”。 12.3 MISCDEVICE驱动开发概述在linux驱动中,需要提供主设备号和次设备号号,通常使用的主设备号是从 0到 255之间的数,仅仅使用主设备号,还是不叫紧张的,因此需要利用次设备号。linux中,提供了miscdevice这种杂项设备,指定主设备号为10,次设备号可以设置为系统动态分配。在具体分析miscdevice之前,先给出miscdevice的核心设计思想。应用层,打开/dev/xxx 节点,会得到 文件描述符 fd,通过fd,可以执行 read()、wriete()、ioctl()、mmap()等对这个文件的操作。 应用层调用 open()函数,经过系统调用后,会调用驱动程序的 open()函数。在前边分析调用过程时,分析过这个open()的调用过程。 驱动程序中,调用file_opeartions的open()的时候,此时,调用到另外一个 file_operations的open(),此时应用层会得到这个open()时的 文件描述符fd,此时应用层 read()、write()等操作,就调用到了这个file_operations。故在cdev_init时的 fops 充当一个中转的作用,转到具体的转到具体的fops来操作。 12.4 创建驱动开发路径的文件夹首先我们创建一个名为driver_app的文件夹,在文件夹内部创建app_code和drv_code两个文件夹,再把共享文件夹内的本课运行代码和驱动程序相关文件拷过来。 led_dev.c
12.5 驱动函数分析12.5.1驱动的注册驱动编译后执行insmod ./led_dev.ko把驱动注册到内核中,当驱动被注册到内核总的时候系统会对驱动做一些的初始化操作。首先是操作系统对led_dev的驱动程序地址空间的分配,包括定义的结构体,变量,指针,和函数。分配完成后,首先执行static int led_init(void)。 在linux kernel 中,物理地址是不能直接使用的,必须通过转换才可以。转换分为两种, 静态和动态。不过,静态的地址转换,还需要在kernel 初始化的时候作映射。动态映射是使用 ioremap 函数。本节课程中使用 ioremap 函数动态映射。led_base = ioremap(0x41200000, 4);映射了AXI_GPIO的物理地址0x41200000到用户地址空间,地址长度为BYTE。之后misc_register函数注册led_dev的驱动到内核。
12.5.2驱动的打开当应用程序通过调用open("/dev/led_dev", O_RDWR);函数打开设备的时候,此函数被调用。
12.5.3驱动的卸载执行rmmod led_dev的时候卸载驱动
12.5.4通过led_write函数用户程序通过led_write对GPIO进行操作,控制LED.ret = copy_from_user(&val, buf, count);函数实现了把用于空间的程序拷贝到内核空间。iowrite32 可以直接范围我们刚刚ioremap后的地址空间。
12.5.5导出的模块函数Insmod 命令的时候执行module_init()函数,rmmod的时候执行module_exit()函数
12.5.5 GPL v2许可及驱动描述信息其中读者注意最后一条必须这么写,才能符合GPL 的规范,否则驱动可能无法工作。
12.5.6应用程序应用程序中fd = open("/dev/led_dev", O_RDWR); //打开设备。
这个open函数不简单 ,而且如果你不阅读下面的话,你一定理解为我们驱动里面的open函数范围的0.open函数是我们开发中经常会遇到的,这个函数是对文件设备的打开操作,这个函数会返回一个句柄fd,我们通过这个句柄fd对设备文件读写操作。我们在对这个fd作判断的时候,经常会用到:
我们先来看看open函数的原型: int open(constchar*pathname,intflags); int open(constchar*pathname,intflags,mode_tmode); 函数参数: pathname:打开文件的路径名 flags:用来控制打开文件的模式 mode:用来设置创建文件的权限(rwx)。当flags中带有O_CREAT时才有效。 返回值:调用成功时返回一个文件描述符fd,调用失败时返回-1,并修改errno 正确的判断应该是 if(fd < 0),那我们什么时候会fd=0呢,如果fd=0,那么已经正常打开了,但是我们判断了打开错误了。 open函数返回的文件描述符fd一定是未使用的最小的文件描述符,那么如果0没有使用,那么我们open的时候,首先就会获取到fd=0的情况。默认情况下,0,1,2这三个句柄对应的是标准输入,标准输出,标准错误,系统进程默认会打开0,1,2这三个文件描述符,而且指向了键盘和显示器的设备文件。所以通常我们open的返回值是从3开始的。 如果我们在open之前,close其中的任何一个,则open的时候,则会用到close的最小的值: close(0); fd = open(filename,O_RDONLY); printf(“fd = %d\n”, fd); 则可以发现我们就可以open的时候,返回了0的fd. 12.5.7 Makefile
说明: 先说明以下makefile中一些变量意义: (2)-C $(KERN_DIR) 当make的目标为all时,-C $( KERN_DIR)指明跳转到内核源码目录下读取那里的Makefile; (3)M=`pwd`当从内核源码目录返回时,KERNELRELEASE已被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名。 (4)KERN_DIR变量便是当前内核(嵌入式LINUX内核)的源代码目录。 在上面的例子中,我们将该变量设置成了led_dev.o。 关于make modules的更详细的过程可以在内核源码目录下的scripts/Makefile.modpost文件的注释中找到。 如果把led_dev模块移动到内核源代码中。例如放到..kernel/linux/driver/中, KERNELRELEASE就有定义了。 在..kernel/linux/Makefile中有 KERNELRELEASE=$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)$(LOCALVERSION)。 这时候,led_dev模块也不再是单独用make编译,而是在内核中用make modules进行编译,此时驱动模块便和内核编译在一起。 12.7程序测试1>首先我们执行settings64.sh。 2>修改Makefile如下图,主要是修改驱动名和内核文件的路径。 3>执行make编译驱动程序。 4>确认开发板的ip地址和电脑是同一网段,执行ssh连接开发板。 5>在开发板下启用交叉编译工具。 6>拷贝更新后的driver_app文件,使用scp –r命令。 7>进入驱动路径,执行insmod,加载led驱动。 8>成功后驱动已经注册内核,查看这个驱动设备,串口控制台输出也可以看到module init已经完成。 9>进入app_code,使用g++编译run_led.c。 10>执行程序a.out,开发板看到流水灯循环闪烁,串口看到写不同的内容到地址上。 11>使用rmmod卸载驱动,串口打印module exit。 |
XILINX 官网|站点统计|Archiver|手机版|米联客品牌主页|UISRC工程师开源站 ( 苏ICP备19046771号-2 )
GMT+8, 2025-4-2 15:32 , Processed in 0.181806 second(s), 87 queries .