[X]关闭

CH01 miscdevice AXI GPIO LED驱动程序

文档创建者:uisrc
浏览次数:4982
最后更新:2019-01-09
CH01 miscdevice AXI GPIO LED驱动程序1.1概述
本节课开始,我们一起学习基于ZYNQ的驱动程序开发。首先声明由于本人能力有限作为一名LINUX驱动初学者,教程更多以笔记形势展示笔者对于LINUX驱动程序开发的理解,而不是一名资深的LINUX驱动开发者。笔记从自身学习的角度,记录如何学习基于ZYNQ的驱动开发。
1.2驱动的注册
驱动编译后执行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的驱动到内核。
。//LED设备初始化
static int led_init(void)
{
int ret;
//地址映射:把物理地址转换为虚拟地址
led_base = ioremap(0x41200000, 4);
printk("LED: Access address to device is:0x%x\n", (unsigned int)led_base);
//注册设备到/dev/目录
ret = misc_register(&led_dev);
if(ret)
{
printk("led:[ERROR] Misc device register failed.\n");
return ret;
}
printk("Module init complete!\n");
return 0;
}
1.3驱动的打开
当应用程序通过调用open("/dev/led_dev", O_RDWR);函数打开设备的时候,此函数被调用。
static int led_open(struct inode * inode, struct file * filp)
{
printk("dev is open!");
return 0;
}
1.4驱动的卸载
执行rmmod led_dev的时候卸载驱动
//LED设备退出
static void led_exit(void)
{
printk("Module exit!\n");
iounmap(led_base); //取消物理地址的映射
misc_deregister(&led_dev); //删除/dev/目录下的设备节点
}
1.5通过led_write函数
用户程序通过led_write对GPIO进行操作,控制LED.ret = copy_from_user(&val, buf, count);函数实现了把用于空间的程序拷贝到内核空间。iowrite32 可以直接范围我们刚刚ioremap后的地址空间。
//写设备触发的服务函数 file:设备 buf:数据缓存区 count:数据个数单位字节
static int led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val,ret;
//把buff缓存数据拷贝到val地址空间
ret = copy_from_user(&val, buf, count);
//把val的值写进led_base寄存器
iowrite32(val, led_base);
printk("led : Write 0x%x to 0x%x.\n", val, (unsigned int)led_base);
return 0;
}
1.6 导出的模块函数
Insmod 命令的时候执行module_init()函数,rmmod的时候执行module_exit()函数
module_init(led_init); //模块初始化接口
module_exit(led_exit); //模块推出接口
1.7 GPL v2许可及驱动描述信息
其中读者注意最后一条必须这么写,才能符合GPL 的规范,否则驱动可能无法工作。
MODULE_AUTHOR("123src@tjy");
MODULE_DESCRIPTION("ledDriver");
MODULE_ALIAS("It's only a test");
MODULE_LICENSE("Dual BSD/GPL");
1.8 led_dev.c完整源码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/uaccess.h>
static void __iomem *led_base; //led寄存器基地址
//打开设备触发的服务函数
static int led_open(struct inode * inode, struct file * filp)
{
printk("dev is open!");
return 0;
}
//写设备触发的服务函数 file:设备 buf:数据缓存区 count:数据个数单位字节
static int led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val,ret;
//把buff缓存数据拷贝到val地址空间
ret = copy_from_user(&val, buf, count);
//把val的值写进led_base寄存器
iowrite32(val, led_base);
printk("led : Write 0x%x to 0x%x.\n", val, (unsigned int)led_base);
return 0;
}
//LED函数接口结构体
static const struct file_operations led_fops =
{
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
//LED设备结构体
static struct miscdevice led_dev=
{
.minor = MISC_DYNAMIC_MINOR,
.name = "led_dev", // /dev/目录下的设备节点名
.fops = &led_fops,
};
//LED设备初始化
static int led_init(void)
{
int ret;
//地址映射:把物理地址转换为虚拟地址
led_base = ioremap(0x41200000, 4);
printk("LED: Access address to device is:0x%x\n", (unsigned int)led_base);
//注册设备到/dev/目录
ret = misc_register(&led_dev);
if(ret)
{
printk("led:[ERROR] Misc device register failed.\n");
return ret;
}
printk("Module init complete!\n");
return 0;
}
//LED设备退出
static void led_exit(void)
{
printk("Module exit!\n");
iounmap(led_base); //取消物理地址的映射
misc_deregister(&led_dev); //删除/dev/目录下的设备节点
}
module_init(led_init); //模块初始化接口
module_exit(led_exit); //模块推出接口
//以下代码可解决一些错误信息
MODULE_AUTHOR("123@ ");
MODULE_DESCRIPTION("ledDriver");
MODULE_ALIAS("It's only a test");
MODULE_LICENSE("Dual BSD/GPL");
1.9应用程序
应用程序中fd = open("/dev/led_dev", O_RDWR); //打开设备。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int i;
int fd;
int val=1;
fd = open("/dev/led_dev", O_RDWR); //打开设备
if (fd <= 0) {
                Printf(“open %s error!\n”, filename)
                return;;
        }
//LED流水灯
while(1)
{
for(i=0;i<4;i++)
{
val = (1<<i);
write(fd,&val,4); //把val的值写到fd设备中,大小为4字节
sleep(1);
}
}
return 0;
}

这个open函数不简单 ,而且如果你不阅读下面的话,你一定理解为我们驱动里面的open函数范围的0.
open函数是我们开发中经常会遇到的,这个函数是对文件设备的打开操作,这个函数会返回一个句柄fd,我们通过这个句柄fd对设备文件读写操作。
我们在对这个fd作判断的时候,经常会用到:
        fd = open(filename, O_RDONLY);
        If (fd <= 0) {
                Printf(“open %s error!\n”, filename)
                return;;
        }
我们先来看看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.
1.10 make file
KERN_DIR = /mnt/workspace/linux/linux-xlnx-master
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led_dev.o

说明:
当我们在模块的源代码目录下运行make时,make是怎么执行的呢?
假设模块的源代码目录是/mnt/workspace/osrc/zynq_linux_driver/ch01_axi_led/dev下。
先说明以下makefile中一些变量意义:
(1)KERNELRELEASE在linux内核源代码中的顶层makefile中有定义
(2)shell pwd会取得当前工作路径
(3)shell uname -r会取得当前内核的版本号
(4)KERN_DIR变量便是当前内核(嵌入式LINUX内核)的源代码目录。
make -C $(KERN_DIR) M=`pwd` modules
这就是编译模块了:首先改变目录到-C选项指定的位置(即内核源代码目录),其中保存有内核的顶层makefile;
M=选项让该makefile在构造modules目标之前返回到模块源代码目录;然后,modueles目标指向obj-m变量中设定的模块;
在上面的例子中,我们将该变量设置成了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进行编译,此时驱动模块便和内核编译在一起。
1.11 测试
编译led.app程序,然后把 刚才编译号的驱动,led_dev.ko和a.out程序一并复制到TF卡(已经准备号了硬件工程)。
执行 mount /dev/mmcblk1p1 /mnt (注意,笔者已经调换了EMMC和SD卡的挂载顺序)
cd /tmp
ls
zynq> insmod led_dev.ko
zynq> lsmod
lsmod命令实际上是读取并分析"/proc/modules"文件,我们可以打开一下这个文件查看究竟:
zynq> cat /proc/modules
内核中已加载模块的信息也存在于/sys/module目录下,加载led_dev.ko后,内核中将包含/sys/module/led_dev目录,
有兴趣的可以看一下该目录中包含哪些内容。
可以使用rmmod led_dev命令来卸载当前装入的内核模块
然后执行./a.out 可以看到 流水的正常运行了。


发表评论已发布 2

uisrc

发表于 2018-7-23 16:41:43 | 显示全部楼层

基于最新的ubuntu16虚拟机(VM14)安装VIVADO2017.4进行嵌入式LINUX开发的更新地址。
-------------------------------------------------------------
视频链接教程:
https://pan.baidu.com/s/11HIvHkjSPaEOASq2lcgVdw 密码:goot
-------------------------------------------------------------
CH01为安装的vivado017.4软件的虚拟机,所有板子可以直接下载使用,省去自己安装麻烦链接:https://pan.baidu.com/s/1-jL8W72IPvwmFbVO56PTwA 密码:aqi0
-------------------------------------------------------------
MZ7100链接:
https://pan.baidu.com/s/1GiYCwXr0HVD4bmIB32kE3A 密码:t0xk
MZ7035链接:
https://pan.baidu.com/s/1fk2NO4yWDHloKfG0fuZtkg 密码:bofr
MZ702N链接:
https://pan.baidu.com/s/1Moirq_PJFoUGmaENEstnmw 密码:m8yd
MZ702A链接:
https://pan.baidu.com/s/18bKCoQYTO4UcLzoF8rlEoA 密码:z9cy
MZ702B链接:
https://pan.baidu.com/s/1X4BDGTpqayO4qInHdL5IGQ 密码:g6xj
MIZ701N-7020链接:
https://pan.baidu.com/s/15ZqdHQcDf_sBcwqHYfRJxg 密码:e1w7

越努力越幸运!加油!

ruiyang89

发表于 2019-1-9 09:24:04 | 显示全部楼层

Linux教程还更新吗
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则