前瞻:
1、驱动电灯与裸机点灯区别:
1、Linux系统有内存管理(操作虚拟地址映射物理地址)
2、点灯软件编写必须符合Linux系统架构(字符设备驱动框架)
3、需要将编写的驱动代码加入已经运行的程序
2、Linux系统五大功能:内存管理、任务管理、进程间通信、文件系统管理(一切皆是文件,比如驱动、鼠标/dev/mice、终端/dev/tty、硬盘/dev/sda)、网络管理
3、驱动分类:字符设备、块设备、网络设备
4、字符设备驱动两种加载:静态(代码加入内核编译)、动态(驱动独立编译生成.ko文件,insmod加载到已经运行的linux系统)
5、字符设备驱动框架:
每个字符设备对应一个cdev结构体,结构体里面有:
1、设备号(主设备号(设备类型)+次设备号(同设备类型下的设备编号))关联一个文件:Linux系统中,每一个文件都可以用stat函数找到其设备号
2、file_operation结构体(对设备的系统调用操作):比如led以文件形式存在:/dev/led,可以用文件操作open、read、write、close操作。
6、驱动文件编写
1、写一个简单驱动文件:led_drv.c
//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
/*第二步:写入口、出口内联函数:类型按照附录3写,一般要加上__init,见附录4,作用就是运行完入口代码后就直接删除,节省内存。
int __init led_init(void)
{
pr_info("led driver init success!");
/*在Linux内核打印信息需要用printk(KERN_INFO,""),也就是加上打印级别,见附录5,后来简化为这种。*/
return 0;
}
void __exit led_exit(void)
{
pr_info("led driver exit success!");
return;
}
/*第一步:先写驱动程序入口、出口。不知道函数参数是什么怎么办,因为Linux内核驱动是在内核源码基础上写的,所以直接在内核源码查找见附录1,由于内核源码文件太大,所以需要一个工具ctags,1、ctags -R生成一个ctags目录,用vi -t module_init查找,优先选择include/linux目录下,函数见附录2。可知参数是一个内联函数,可以用ctrl ]查看参数定义,见附录3
*/
module_init(led_init);//入口(动态加载驱动时执行)
module_exit(led_exit);//出口(卸载时执行)
附录1,内核源码文件
附录2,module_init函数
附录3,初始化内联函数
附录4:__init
附录5:printk打印级别
2、编译验证
我们编写的驱动编译时需要调用内核编译make modules,但是我们的文件不在内核文件路径,所以需要-C$(kerdir)指定内核路径,即make -C$(kerdir) modules,会编译内核所以模块,所以依赖文件就是内核所有模块,所以makefile编写时不需要依赖文件,但是这样就不会编译我们的驱动模块,所以:要在原有模块基础上加上我们的驱动模块,就要在内核调用make modules时加上我们的文件:obj-m表示内核编译模块,obj必须小写,加上我们的模块即可:obj-m+=led_drv.o,由于模块不在内核文件夹,所以要加上我们的驱动源码路径,M=$(drvdir)
编译完成会生成.ko模块驱动文件,用insmod即可动态加入运行的linux系统,完整makefile如下:
#模块名
modulename=led_drv
#内核路径
kerdir=/home/linux/imx6ull/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
#当前路径
curpath=$(shell pwd)
#将代码加入模块编译选项中
obj-m+=$(modulename).o
all:
make -C $(kerdir) modules M=$(curpath)
cp $(modulename).ko ~/nfs/rootfs
.PHONY:
clean:
rm $(modulename).ko
3、驱动程序没有错误就会生成.ko驱动文件,然后通过minicom把驱动文件加载到板子:
1、 insmod led_dev.ko 加载成功会打印对应信息,同时还会打印一些其他信息,就是让你遵从GNU组织协定,使用了linux内核源码就需要公开你写的相关代码,要想不显示这些信息就可以在最后加上两行代码,表示你遵从这个协定(GPL协定):
MODULE_LICENSE("GPL");
MODULE_AUTOHR("name");name填你的名字
2、rmmod led_dev.ko 删除模块
7、在上述代码加上驱动框架设备号获取
1、设备号不能随意分配,因为有些设备号已经被分配了。
cat /proc/devices查看设备信息
2、设备号申请与释放
EXPORT_SYMBOL表示在其他文件调用该函数只需要声明一些就可以,不需要包含头文件。
我们一般用alloc_chrdev_region随机获得一个设备号,用unregister_chrdev_region释放设备号。
编译验证:
//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
dev_t dev;定义全局变量作为设备号
//第二步
int __init led_init(void)
{
pr_info("led driver init success!");
int ret = 0;
//第三步
ret = alloc_chrdev_region(&dev,0,"myled");参数一存放设备号,二是设备号从几i开始,三是申请几个设备,四是设备名字,即是哪一类设备。
if(ret){
pr_info("alloc_chrdev_region failed");
return EAGAIN;内核错误一般不返回-1,而是用错误编号见附录1。
}
pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(dev),MINOR(dev));用两个宏获取主次设备号
return 0;
}
void __exit led_exit(void)
{
unregister_chrdev_region(dev,1);释放设备号
pr_info("led driver exit success!");
return;
}
//第一步
module_init(led_init);//入口(动态加载驱动时执行)
module_exit(led_exit);//出口(卸载时执行)
MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");
附录1;错误编号
编译后申请成功就会给出对应主设备号和次设备号。
8、在上述驱动代码加上文件操作,构建cdev结构体
前面已经申请了设备号,接下来我们要在自己的驱动代码加上file_operation,这样用户层调用文件操作函数就会在驱动层调用对应的文件操作。
1、内核里面file_operation是一系列函数指针
2、依照内核文件操作结构体写一个相同类型的结构体,只是成员只需要写上我们用得上的即可。
//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
//自定义函数变量前加static,让其只能在本文件使用。
static dev_t dev;
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
return 0;
}
static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
return 0;
}
static int led_open (struct inode *node, struct file *fp)
{
return 0;
}
static int led_release(struct inode *node, struct file *fp)
{
return 0;
}
//第四步:编写文件操作结构体,类型需要与内核一致,成员是指向自己编写的函数,并且自己编写的函数应该与内核对应的文件操作函数类型一致
static struct file_operations fops = {
.owner = THIS_MODULE, //计数,表示有几个模块调用文件操作
.open = led_opem,
.release = led_release,
.read = led_read,
.write = led_write,
};
//第二步
static int __init led_init(void)
{
pr_info("led driver init success!");
int ret = 0;
//第三步
ret = alloc_chrdev_region(&dev,0,"myled");
if(ret){
pr_info("alloc_chrdev_region failed");
return EAGAIN;
}
pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(dev),MINOR(dev));
return 0;
}
static void __exit led_exit(void)
{
unregister_chrdev_region(dev,1);
pr_info("led driver exit success!");
return;
}
//第一步
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");
3、构建cdev结构体
我们用cdev_alloc申请cdev结构体,并关联设备号和文件操作,这样后面写用户app,当对自己的设备操作时,就会调用自己写的文件操作函数。
//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
//自定义函数变量前加static,让其只能在本文件使用。
static dev_t devno;
static struct cdev *pcdev
//自己定义的文件操作函数,当用户层打开的是我们创建的设备,那么后续文件操作就会调用自己写的read、write,如果不是打开自己创建的设备,就会按照默认驱动文件操作。
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
pr_info("led read success\n");
return 0;
}
static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
pr_info("led write success\n");
return 0;
}
static int led_open (struct inode *node, struct file *fp)
{
pr_info("led open success\n");
return 0;
}
static int led_release(struct inode *node, struct file *fp)
{
pr_info("led release success\n");
return 0;
}
//第四步:
static struct file_operations fops = {
.owner = THIS_MODULE, //计数,表示有几个模块调用文件操作
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
//第二步
static int __init led_init(void)
{
pr_info("led driver init success!");
int ret = 0;
//第三步
ret = alloc_chrdev_region(&devno,0,"myled");
if(ret){
pr_info("alloc_chrdev_region failed");
//return -EFAULT;
goto err_alloc_chrdev_region;
}
pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
//第五步:随机申请1个字符设备(cdev)结构体,跟设备号和file_operations关联,卸载模块时释放
pcdev = cdev_alloc();
if(!pcdev){
pr_info("cdev_alloc failed");
//unregister_chrdev_region(devno,1);//出错执行return语句就会结束,导致设备号没释放,所以先释放设备号再返回值。
//return -EFAULT;
goto err_cdev_alloc;
}
pr_info("cdev_alloc success!");
ret = cdev_add(pcdev,devo,1);//把cdev跟设备号关联
if(ret){
pr_info("cdev_add failed");
//unregister_chrdev_region(devno,1);//可以看到,每次出错都要释放设备号,为了简化,内核一般采用goto语句,我们也用这个,出错就跳转到指定位置
//return -EFAULT;
goto err_cdev_add;
}
pcdev->ops = &fops;//将cdev结构体和文件操作结构体关联
return 0;
}
err_cdev_add:
err_cdev_alloc:
unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
return -1;
static void __exit led_exit(void)
{
cdev_del(pcdev);//释放cdev结构体,后申请的先释放,因为后申请的依赖前面申请的东西。
unregister_chrdev_region(devno,1);
pr_info("led driver exit success!");
return;
}
//第一步
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");
附录1:cdev结构体
这样,一个简单的驱动文件就写好了,后续加入外设相关操作就可以驱动外设了。
由于没有创建设备文件,只有设备号与我们的设备关联,cat /proc/devices可以看的我们的设备号248
我们可以自己在板子linux系统运行后,在用户交互界面创建一个文件与设备号关联即可:
mknod /dev/led c 248 0 创建一个字符设备文件与我们的设备号关联
ls -l /dev/led 就可以看的我们的设备led对应设备号248
9、编写用户程序,调用这个驱动来验证一下。
//用户程序就不需要保护内核头文件了,只需要包含需要的c库头文件和系统调用头文件即可
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
int fd = 0;
int buf[32] = {0};
fd = open("/dev/led", O_RDWR);//打开自己的设备文件,权限为可读写
if (-1 == fd)
{
perror("fail to open");
return -1;
}
write(fd, "hello world", 11);
read(fd, &buf, sizeof(buf));
close(fd);
return 0;
}
写一个makefile方便编译:
#目标文件
OBJ=appname
#依赖文件
OBJS+=main.c
#编译器
CC=arm-linux-gnueabihf-gcc
$(appname):$(OBJS)
$(CC) $^ -o $@
cp $(appname) ~/nfs/rootfs #把可执行程序挂载到共享文件,就可以在板子的用户交互界面运行
.PHONY:
clean:
rm $(appname)
10、写一个makefile调用驱动和用户的makefile,这样就不要make两次了
appdir=led_app
drvdir=led_drv
all:
make -C $(appdir) #表示调用led_app目录下的makefile
make -C $(drvdir)
.PHONY:
clean:
make -C $(appdir) clean #调用led_app目录的make clean
make -C $(drvdir) clean
make后,在板子用户交互界面运行:./appname,如果打印了驱动写的四个打印数据,就表示我们用户层调用了驱动层文件操作函数。
前面创建设备节点都是在板子用户交互界面mknod /dev/led c 248 0后创建出设备,然后用户层就可以访问这个设备节点/dev/led,接下来我们在驱动代码创建设备节点,这样就不需要在用户交互界面输入命令创建了
//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
include<linux/device.h>//第六步:创建节点要加这个头文件
//自定义函数变量前加static,让其只能在本文件使用。
static dev_t devno;
static struct cdev *pcdev;
static struct class* pclass;
static struct device *pdevice;
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
pr_info("led read success\n");
return 0;
}
static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
pr_info("led write success\n");
return 0;
}
static int led_open (struct inode *node, struct file *fp)
{
pr_info("led open success\n");
return 0;
}
static int led_release(struct inode *node, struct file *fp)
{
pr_info("led release success\n");
return 0;
}
//第四步:
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
//第二步
static int __init led_init(void)
{
pr_info("led driver init success!");
int ret = 0;
//第三步
ret = alloc_chrdev_region(&devno,0,"myled");
if(ret){
pr_info("alloc_chrdev_region failed");
//return -EFAULT;
goto err_alloc_chrdev_region;
}
pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
//第五步
pcdev = cdev_alloc();
if(!pcdev){
pr_info("cdev_alloc failed");
goto err_cdev_alloc;
}
pr_info("cdev_alloc success!");
ret = cdev_add(pcdev,devo,1);
if(ret){
pr_info("cdev_add failed");
goto err_cdev_add;
}
pcdev->ops = &fops;
//第六步:创建设备节点。1、先创建设备类。2、创建具体设备
pclass = class_creat(THIS_MODULE,"led_class");
if(!pclass){
pr_info("class creat failed\n");
goto err_class_creat;
}
pr_info("class creat success\n");
pdevice = device_creat(pclass,NULL,devno,NULL,"led%d",0);
if(!pdevice){
pr_info("device creat failed\n");
goto err_device_creat;
}
pr_info("device creat success\n");
return 0;
}
err_device_creat:
class_destroy(pclass);
err_class_creat:
cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
return -1;
static void __exit led_exit(void)
{
device_destroy(pclass,devno);
cdev_del(pcdev);
unregister_chrdev_region(devno,1);
pr_info("led driver exit success!");
return;
}
//第一步
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");
到此,就完成了后两个点,1、符合linux驱动架构,2、可以把驱动加入到运行的系统,还剩下一条,虚拟地址物理地址映射,这就是利用linux操作系统对外设访问的方法了
11、通过linux操作系统虚拟地址到物理地址的映射,来对寄存器进行读写操作,实现led灯亮灭。
1、前面学裸机开发已经知道要想操作imx6ull开发板的led等外设,需要做到以下几点:
1、使能GPIO时钟
2、引脚复用功能设为GPIO
3、配置引脚电器属性
4、配置gpio寄存器,方向寄存器设为输出,对数据寄存器写入高低电平来实现led灯亮灭。
从imx6ull查找相关寄存器地址:
从原理图看出led灯低电平点亮。
Linux驱动不能直接访问物理地址,但可以通过内核提供的映射机制间接访问。
2、利用ioremap函数实现地址映射:
//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include<linux/device.h>
#include<asm/io.h> //第七步:地址映射必备头文件
//自定义函数变量前加static,让其只能在本文件使用。
static dev_t devno;
static struct cdev *pcdev;
static struct class* pclass;
static struct device *pdevice;
static void __iomem *pCCGR1;
static void __iomem *pIOMUX;
static void __iomem *pIOPAD;
static void __iomem *pGPIOGDIR;
static void __iomem *pGPIODR;
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
pr_info("led read success\n");
return 0;
}
static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
pr_info("led write success\n");
return 0;
}
static int led_open (struct inode *node, struct file *fp)
{
pr_info("led open success\n");
return 0;
}
static int led_release(struct inode *node, struct file *fp)
{
pr_info("led release success\n");
return 0;
}
//第四步:
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
//第二步
static int __init led_init(void)
{
pr_info("led driver init success!");
int ret = 0;
//第三步
ret = alloc_chrdev_region(&devno,0,"myled");
if(ret){
pr_info("alloc_chrdev_region failed");
//return -EFAULT;
goto err_alloc_chrdev_region;
}
pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
//第五步
pcdev = cdev_alloc();
if(!pcdev){
pr_info("cdev_alloc failed");
goto err_cdev_alloc;
}
pr_info("cdev_alloc success!");
ret = cdev_add(pcdev,devo,1);
if(ret){
pr_info("cdev_add failed");
goto err_cdev_add;
}
pcdev->ops = &fops;
//第六步
pclass = class_creat(THIS_MODULE,"led_class");
if(!pclass){
pr_info("class creat failed\n");
goto err_class_creat;
}
pr_info("class creat success\n");
pdevice = device_creat(pclass,NULL,devno,NULL,"led%d",0);
if(!pdevice){
pr_info("device creat failed\n");
goto err_device_creat;
}
pr_info("device creat success\n");
//第七步:地址映射:
pCCGR1 = ioremap(0x020c406c,4); //前面是寄存器地址,后面是寄存器字节数,函数返回值就是寄存器虚拟地址,用指定返回值类型指针接住,失败则返回NULL。这个就是CCGR寄存器。
if(!pCCGR1){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pIOMUX = ioremap(0x020e0068,4) ;
if(!pIOMUX){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pIOPAD = ioremap(0x020e02f4,4) ;
if(!pIOPAD){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pGPIOGDIR = ioremap(0x0209c004,4) ;
if(!pGPIOGDIR){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pGPIODR = ioremap(0x0209c000,4) ;
if(!pGPIODR){
pr_info("ioremap failed\n");
goto err_ioremap;
}
return 0;
}
err_ioremap:
device_destroy(pclass,devno);//还需要解除已经成功映射的寄存器,十分麻烦,可以使用devm_ioremap函数进行映射,它会跟设备绑定,这样设备释放后,相关地址映射都会释放,具体传参可以vi -t查看,一般不会出错,就先用原来的。
err_device_creat:
class_destroy(pclass);
err_class_creat:
cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
return -1;
static void __exit led_exit(void)
{
iounremap(pCCGR1);//解除地址映射
iounremap(pIOMUX);
iounremap(pIOPAD);
iounremap(pGPIOGDIR);
iounremap(pGPIODR);
device_destroy(pclass,devno);
cdev_del(pcdev);
unregister_chrdev_region(devno,1);
pr_info("led driver exit success!");
return;
}
//第一步
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");
3、利用readl和writel读写寄存器的值
//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include<linux/device.h>
#include<asm/io.h>
#include<asm-generic/io.h>//第八步要用readl和writel函数需要包含
//自定义函数变量前加static,让其只能在本文件使用。
static dev_t devno;
static struct cdev *pcdev;
static struct class* pclass;
static struct device *pdevice;
static void __iomem *pCCGR1;
static void __iomem *pIOMUX;
static void __iomem *pIOPAD;
static void __iomem *pGPIOGDIR;
static void __iomem *pGPIODR;
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
pr_info("led read success\n");
return 0;
}
static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
unsigned int tmpvalue = 0;
//置0开灯
tmpvalue = readl(pGPIODR);
tmpvalue &= ~(0x01 << 3);
writel(tmpvalue, pGPIODR);
//置1关灯
tmpvalue = readl(pGPIODR);
tmpvalue |= (0x01 << 3);
writel(tmpvalue, pGPIODR);
pr_info("led write success\n");
return 0;
}
static int led_open (struct inode *node, struct file *fp)
{
pr_info("led open success\n");
return 0;
}
static int led_release(struct inode *node, struct file *fp)
{
pr_info("led release success\n");
return 0;
}
//第四步:
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
//第二步
static int __init led_init(void)
{
pr_info("led driver init success!");
unsigned int tmpvalue;//接收寄存器读到的值
int ret = 0;
//第三步
ret = alloc_chrdev_region(&devno,0,"myled");
if(ret){
pr_info("alloc_chrdev_region failed");
//return -EFAULT;
goto err_alloc_chrdev_region;
}
pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
//第五步
pcdev = cdev_alloc();
if(!pcdev){
pr_info("cdev_alloc failed");
goto err_cdev_alloc;
}
pr_info("cdev_alloc success!");
ret = cdev_add(pcdev,devo,1);
if(ret){
pr_info("cdev_add failed");
goto err_cdev_add;
}
pcdev->ops = &fops;
//第六步
pclass = class_creat(THIS_MODULE,"led_class");
if(!pclass){
pr_info("class creat failed\n");
goto err_class_creat;
}
pr_info("class creat success\n");
pdevice = device_creat(pclass,NULL,devno,NULL,"led%d",0);
if(!pdevice){
pr_info("device creat failed\n");
goto err_device_creat;
}
pr_info("device creat success\n");
//第七步
pCCGR1 = ioremap(0x020c406c,4);
if(!pCCGR1){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pIOMUX = ioremap(0x020e0068,4) ;
if(!pIOMUX){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pIOPAD = ioremap(0x020e02f4,4) ;
if(!pIOPAD){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pGPIOGDIR = ioremap(0x0209c004,4) ;
if(!pGPIOGDIR){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pGPIODR = ioremap(0x0209c000,4) ;
if(!pGPIODR){
pr_info("ioremap failed\n");
goto err_ioremap;
}
//第八步:读写寄存器的值,准备工作写在初始化里面,最后改变引脚电平也就是对GPIODR寄存器的读写写出函数操作里面,这样应用层就可以对引脚电平改变了。
//gpio1对应时钟使能
tmpvalue = readl(pCCGR1);这里的l就是long,四字节的意思。
tmpvalue &= ~(0x3 << 26);
tmpvalue |= (0x3 << 26);
writel(tmpvalue,pCCGR1);
//设置为GPIO
writel(0x5, pIOMUX);
//设置电器属性
writel(0x10B0, pIOPAD);
//GPIO方向
tmpvalue = readl(pGPIODIR);
tmpvalue |= 0x1 << 3;
writel(tmpvalue, pGPIODIR);
return 0;
}
err_ioremap:
device_destroy(pclass,devno);
err_device_creat:
class_destroy(pclass);
err_class_creat:
cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
return -1;
static void __exit led_exit(void)
{
iounremap(pCCGR1);
iounremap(pIOMUX);
iounremap(pIOPAD);
iounremap(pGPIOGDIR);
iounremap(pGPIODR);
device_destroy(pclass,devno);
cdev_del(pcdev);
unregister_chrdev_region(devno,1);
pr_info("led driver exit success!");
return;
}
//第一步
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");
这样我们就完成了驱动间接操作寄存器,但是在驱动的write操作函数中,我们写了开灯关灯操作,很明显不完整,我们的加一个选择,让应用层调write时候,可以根据写入的东西来判断什么时候开,什么时候关,static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);从函数原型看到const char __user *puser, size_t n这两个参数似乎就是我们写入的字符串和个数,但是我们没法直接使用这两个参数值,因为这些是系统调用的,这就需要再用两个函数来获得用户写的字符串了:copy_from_user和copy_to_user:
//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include<linux/device.h>
#include<asm/io.h>
#include<asm-generic/io.h>
#include<asm/uaccess.h>第九部:使用copy_from_user和copy_to_user包含
//自定义函数变量前加static,让其只能在本文件使用。
extern void __iomen *devm_ioremap(struct device *dev,resource_size_t offset,resource_size_t size);内核的符号表里面有,声明一下就可以用。
static int ledstat;//定义为全局变量,在read读出状态
static dev_t devno;
static struct cdev *pcdev;
static struct class* pclass;
static struct device *pdevice;
static void __iomem *pCCGR1;
static void __iomem *pIOMUX;
static void __iomem *pIOPAD;
static void __iomem *pGPIOGDIR;
static void __iomem *pGPIODR;
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
unsigned long ret = 0;
ret = copy_to_user(puser,&ledstat,sizeof(ledstat));传递状态信息到用户缓冲区,用户区的fd就是二者的交互接口,返回值是0表示成功,非零表示未拷贝的字节数。
if(!ret){
pr_info("copy_to_user failed");
}
pr_info("led read success\n");
return 0;
}
static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
第九部:利用安全函数拷贝获取const char __user *puser,即用户写的字符串数据
unsigned int tmpvalue = 0;
unsigned int len;
ledstat = 0;
len = copy_from_user(&ledstat,puser,sizeof(ledstat));从puser拷贝到ledstat
if(!len){
pr_info("copy_from_user failed");
}
用户层写1开灯,写0关灯。
if(ledstat == 1){
//置0开灯
tmpvalue = readl(pGPIODR);
tmpvalue &= ~(0x01 << 3);
writel(tmpvalue, pGPIODR);
}
if(ledstat == 0){
//置1关灯
tmpvalue = readl(pGPIODR);
tmpvalue |= (0x01 << 3);
writel(tmpvalue, pGPIODR);
}
pr_info("led write success\n");
return 0;
}
static int led_open (struct inode *node, struct file *fp)
{
pr_info("led open success\n");
return 0;
}
static int led_release(struct inode *node, struct file *fp)
{
pr_info("led release success\n");
return 0;
}
//第四步:
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
//第二步
static int __init led_init(void)
{
pr_info("led driver init success!");
unsigned int tmpvalue;
int ret = 0;
//第三步
ret = alloc_chrdev_region(&devno,0,"myled");
if(ret){
pr_info("alloc_chrdev_region failed");
//return -EFAULT;
goto err_alloc_chrdev_region;
}
pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
//第五步
pcdev = cdev_alloc();
if(!pcdev){
pr_info("cdev_alloc failed");
goto err_cdev_alloc;
}
pr_info("cdev_alloc success!");
ret = cdev_add(pcdev,devno,1);
if(ret){
pr_info("cdev_add failed");
goto err_cdev_add;
}
pcdev->ops = &fops;
//第六步
pclass = class_creat(THIS_MODULE,"led_class");
if(!pclass){
pr_info("class creat failed\n");
goto err_class_creat;
}
pr_info("class creat success\n");
pdevice = device_creat(pclass,NULL,devno,NULL,"led%d",0);
if(!pdevice){
pr_info("device creat failed\n");
goto err_device_creat;
}
pr_info("device creat success\n");
//第七步
pCCGR1 = devm_ioremap(pdevice,0x020c406c,4);由于ioremap进行地址映射没法跟设备绑定,在后面释放设备就不会回收虚拟地址,还需要手动回收,改成devm_ioremap就可以在回收设备时顺便把地址回收,后面错误处理也简单些,所以都用这个。
if(!pCCGR1){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pIOMUX = devm_ioremap(pdevice,0x020e0068,4) ;
if(!pIOMUX){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pIOPAD = devm_ioremap(pdevice,0x020e02f4,4) ;
if(!pIOPAD){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pGPIOGDIR = devm_ioremap(pdevice,0x0209c004,4) ;
if(!pGPIOGDIR){
pr_info("ioremap failed\n");
goto err_ioremap;
}
pGPIODR = devm_ioremap(pdevice,0x0209c000,4) ;
if(!pGPIODR){
pr_info("ioremap failed\n");
goto err_ioremap;
}
//第八步
//gpio1对应时钟使能
tmpvalue = readl(pCCGR1);
tmpvalue &= ~(0x3 << 26);
tmpvalue |= (0x3 << 26);
writel(tmpvalue,pCCGR1);
//设置为GPIO
writel(0x5, pIOMUX);
//设置电器属性
writel(0x10B0, pIOPAD);
//GPIO方向
tmpvalue = readl(pGPIODIR);
tmpvalue |= 0x1 << 3;
writel(tmpvalue, pGPIODIR);
//置1关灯 初始化为关灯状态
tmpvalue = readl(pGPIODR);
tmpvalue |= (0x01 << 3);
writel(tmpvalue, pGPIODR);
return 0;
}
err_ioremap:
device_destroy(pclass,devno);
err_device_creat:
class_destroy(pclass);
err_class_creat:
cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
return -1;
static void __exit led_exit(void)
{
device_destroy(pclass,devno);
cdev_del(pcdev);
unregister_chrdev_region(devno,1);
pr_info("led driver exit success!");
/第一步
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");
用户层:写入整型值
//用户程序就不需要保护内核头文件了,只需要包含需要的c库头文件和系统调用头文件即可
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
int fd = 0;
int ledstat = 0;
fd = open("/dev/led0", O_RDWR);//打开自己的设备文件,权限为可读写
if (-1 == fd)
{
perror("fail to open");
return -1;
}
ledstat = 1;
write(fd, &ledstat, sizeof(ledstat));
read(fd, &ledstat, sizeof(ledstat));
if(ledstat == 1)
{
printf(led on success);
}
close(fd);
return 0;
}
这下,一个简单完整的字符设备驱动程序基本写好了,大致步骤如下:
12、sys文件系统,简单来说就是Linux系统展示内核状态的一个文件系统,如下
比较重要的:
加载完驱动,我们创建的led0就在/sys/class/led_class/led0,
另外,在dev目录下也有:/sys/dev/char/248:0/
可以看到两种路径都进入同一个目录
前瞻结束
一、字符设备驱动框架
1.字符设备流程:
*字符设备驱动加载到Linux内核方式:静态加载和动态加载。
*静态加载是在内核编译阶段,编译到内核中, Linux内核启动后直接拥有了该内核模块。
*动态加载是Linux内核启动完毕后通过insmod加载、 rmmod卸载。
*驱动开发过程更多采用动态加载。
*动态加载需要创建一个cdev结构体,包含:dev_t类型的设备号、操作设备的file_operations结构体。
设备号包含:主设备号表示设备类型,次设备号表示该类型的第几个设备。
file_operations结构体成员是:各种操作设备对应的函数指针。来实现具体的open、 read、 write、 ioctl等一系列对设备的操作。
*用户空间通过open打开对应的设备文件,设备文件通过VFS文件系统找到对应的设备号,通过设备号可以找到字符设备cdev结构体,通过结构体可以找到对设备操作的file_operations结构体,最终通过这个结构体中的函数指针实现对设备具体的操作。
2.file_operation结构体:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//常用
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//常用
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);//常用
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);//常用,相当于close
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,
int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned
long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *,
size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *,
size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};
3.函数接口:
1.cdev操作函数接口:
函数名 | 功能 |
cdev_init | 初始化字符设备结构体 |
cdev_alloc | 申请字符设备结构体 |
cdev_add | 添加字符设备 |
cdev_del | 删除字符设备 |
函数原型
void cdev_init(struct cdev *cdev, const struct file_operations *fops) ;
struct cdev *cdev_alloc(void);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
void cdev_del(struct cdev *p);
2.字符设备操作接口:
函数名 | 功能 |
register_chrdev_region | 静态注册已知设备号范围(需提前确定主设备号) |
alloc_chrdev_region | 动态分配设备号(内核自动分配主设备号) |
unregister_chrdev_region | 释放注册的设备号 |
register_chrdev | 注册设备号并关联file_operations结构体(早期内核使用, 2.6版本以后尽量不使用) |
unregister_chrdev | 注册注册的设备号(早期内核使用, 2.6版本以后尽量不使用) |
函数原型:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const
char *name);
void unregister_chrdev_region(dev_t from, unsigned count);
static inline int register_chrdev(unsigned int major, const char *name, const
struct file_operations *fops);
static inline void unregister_chrdev(unsigned int major, const char *name);
3.设备类创建
函数名 | 功能 |
class_create | 创建设备类目录 |
class_destroy | 销毁设备类 |
函数原型:
#define class_create(owner, name)
void class_destroy(struct class *cls);
4.设备创建
函数名 | 功能 |
device_create | 创建设备 |
device_destroy | 销毁设备 |
函数原型:
struct device *device_create(struct class *class, struct device *parent, dev_t
devt, void *drvdata, const char *fmt, ...);
void device_destroy(struct class *class, dev_t devt);
二、sys文件系统
sysfs是Linux内核中一种特殊虚拟文件系统,用来向用户空间提供内核设备和驱动程序的信息。它提供了一种统一的接口,用于查看和操作设备、驱动程序、文件系统等内核对象。 sysfs存放在内存中,挂载在/sys目录下,文件系统中的文件不对应硬盘上任何文件。
1.sys的目录结构
sys文件系统中重点目录的设计思想如下图所示:
目录名 | 功能 | 目录结构 |
/sys/devices | 表示系统中的物理设备,每个子目录对应一个设备 | 1.系统设备:如CPU、系统内存等2.总线设备:如PCI、 USB、 SCSI等设备3.虚拟设备:如虚拟网络设备 |
/sys/bus | 表示系统总线类型(如PCI、 USB等),每个子目录对应一个总线 | 1.devices:列出所有连接到该总线的设备2.drivers:列出与该总线相关的所有驱动程序3.drivers_autoprobe和drivers_probe:用于自动或手动驱动程序绑定4.uevent:用于触发uevent事件 |
/sys/class | 表示系统中的设备类型(如网络设备、块设备等),子目录按类型分类 | 1.net:表示所有网络接口2.block:表示所有块设备3.tty:表示所有终端设备 |
其余目录还有:
目录名 | 功能 |
/sys/kernel | 表示内核参数和信息,如调度器参数、内核模块等 |
/sys/module | 表示加载的内核模块,每个子目录对应一个模块,包含模块参数和状态信息 |
/sys/block | 表示块设备(如硬盘、 USB存储设备等) |
2.sys文件系统使用
sysfs在驱动中的作用通常包括:
1. 设备信息显示:将内核对象(设备、驱动、总线等)的属性以文件形式展示在/sys目录下,例如设备状态、配置参数、硬件拓扑等。用户可通过cat或者echo命令直接读取/修改这些文件,无需编写专用工具。
2. 动态设备管理:实时反映系统设备变化(如热插拔),目录结构随设备增删动态更新支持设备分类
3. 驱动调试与配置:提供寄存器访问接口,允许直接读取/写入硬件寄存器。
函数名 | 功能 |
kobject_create_and_add | 创建并初 |
sysfs_create_file | 创建一个 |
kobject_get | 减少kobje |
kobject_put | 增加kobject的引用计数 |
sysfs_remove_file | 删除已创建的sysfs属性文件 |
__ATTR |
函数原型:
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
static inline int __must_check sysfs_create_file(struct kobject *kobj, const
struct attribute *attr);
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
static inline void sysfs_remove_file(struct kobject *kobj, const struct attribute
*attr);
#define __ATTR(_name, _mode, _show, _store);
三、LED灯驱动
内存映射接口
函数名 | 功能 |
ioremap | 将内核虚拟地址映射到物理地址 |
iounmap | 解除映射的虚拟地址 |
devm_ioremap | 将物理地址映射到内存虚拟地址空间(驱动卸载时自动解除映射) |
devm_iounmap | 提前解除映射关系 |
devm_ioremap_resouce | 整合资源申请并映射,避免硬件冲突,适用设备树场景 |
函数原型:
void __iomem *ioremap(unsigned long paddr, unsigned long size);
void iounmap(const void __iomem *addr);
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
resource_size_t size);
void devm_iounmap(struct device *dev, void __iomem *addr);
void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res)
//led_drv.c
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm-generic/io.h>
#include <asm/uaccess.h>
#include <linux/of.h>
static ssize_t led_read(struct file *fp, char __user *puser, size_t n, loff_t *off);
static ssize_t led_write(struct file *fp, const char __user *puser, size_t n, loff_t *off);
static int led_open(struct inode *node, struct file *fp);
static int led_release(struct inode *node, struct file *fp);
extern void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, resource_size_t size);
ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static int ledstat;
static struct device *pdevice;
static struct class *pclass;
static struct cdev *pcdev;
static dev_t devno;
static void __iomem *pccgr1;
static void __iomem *imuxrcsw;
static void __iomem *imuxrcpad;
static void __iomem *gpiodir;
static void __iomem *gpiodat;
static struct device_attribute led_attr = __ATTR(ledbright, 0664, led_show, led_store);
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
.open = led_open,
.release = led_release,
};
static ssize_t led_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
{
unsigned long ret = 0;
ret = copy_to_user(puser, &ledstat, sizeof(ledstat));
if (ret)
pr_info("copy_to_user failed\n");
pr_info("led read success!\n");
return 0;
}
static ssize_t led_write(struct file *fp, const char __user *puser, size_t n, loff_t *off)
{
unsigned int tmpvalue = 0;
unsigned long len = 0;
len = copy_from_user(&ledstat, puser, sizeof(ledstat));
if (len) {
pr_info("copy_from_user failed");
}
if (1 == ledstat)
{
//置0开灯
tmpvalue = readl(gpiodat);
tmpvalue &= ~(0x1 << 3);
writel(tmpvalue, gpiodat);
}
else if (0 == ledstat)
{
//置1关灯
tmpvalue = readl(gpiodat);
tmpvalue |= 0x1 << 3;
writel(tmpvalue, gpiodat);
}
pr_info("led write success!\n");
return 0;
}
static int led_open(struct inode *node, struct file *fp)
{
pr_info("led open success!\n");
return 0;
}
static int led_release(struct inode *node, struct file *fp)
{
pr_info("led release success!\n");
return 0;
}
ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t len = 0;
if (1 == ledstat)
{
len = sprintf(buf, "LED_ON");
}
else if (0 == ledstat)
{
len = sprintf(buf, "LED_OFF");
}
return len;
}
ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int tmpvalue = 0;
char tmpbuff[32] = {0};
sscanf(buf, "%s", tmpbuff);
if (!strcmp(tmpbuff, "LED_ON"))
{
//置0开灯
tmpvalue = readl(gpiodat);
tmpvalue &= ~(0x1 << 3);
writel(tmpvalue, gpiodat);
}
else if (!strcmp(tmpbuff, "LED_OFF"))
{
//置1关灯
tmpvalue = readl(gpiodat);
tmpvalue |= 0x1 << 3;
writel(tmpvalue, gpiodat);
}
return count;
}
static int __init led_init(void)
{
int ret = 0;
unsigned int tmpvalue = 0;
struct device_node *pdtsdevice = NULL;
const char *pcompatible = NULL;
unsigned int regaddr[10] = {0};
int i = 0;
//注册设备号
ret = alloc_chrdev_region(&devno, 0, 1, "myled");
if (ret) {
pr_info("alloc_chrdev_region failed\n");
goto err_alloc_chrdev_region;
}
pr_info("major:%d minor:%d\n", MAJOR(devno), MINOR(devno));
//创建cdev并初始化
pcdev = cdev_alloc();
if (!pcdev) {
pr_info("cdev_alloc failed\n");
goto err_cdev_alloc;
}
//将cdev加入字符设备结构中
pcdev->ops = &fops;
ret = cdev_add(pcdev, devno, 1);
if (ret) {
pr_info("cdev_alloc failed\n");
goto err_cdev_add;
}
//创建设备类
pclass = class_create(THIS_MODULE, "led_class");
if (!pclass) {
pr_info("class_create failed\n");
goto err_class_create;
}
//创建具体设备
pdevice = device_create(pclass, NULL, devno, NULL, "led%d", 0);
if (!pclass) {
pr_info("device_create failed\n");
goto err_device_create;
}
//查找设备树节点
pdtsdevice = of_find_node_by_path("/puteleds");
if (!pdtsdevice)
pr_info("of_find_node_by_path");
//读取属性中的compatible字符串
ret = of_property_read_string(pdtsdevice, "compatible", &pcompatible);
if (ret)
pr_info("of_property_read_string failed");
pr_info("compatible = %s\n", pcompatible);
ret = of_property_read_u32_array(pdtsdevice, "reg", regaddr, 10);
if (ret)
pr_info("of_property_read_u32_array");
for (i = 0; i < 10; i+=2)
{
pr_info("addr: %#x size:%d\n", regaddr[i], regaddr[i+1]);
}
//虚拟地址向物理地址映射
pccgr1 = devm_ioremap(pdevice, regaddr[0], regaddr[1]);
if (!pccgr1) {
pr_info("fail to ioremap");
goto err_device_create;
}
imuxrcsw = devm_ioremap(pdevice, regaddr[2], regaddr[3]);
if (!imuxrcsw) {
pr_info("fail to ioremap");
goto err_device_create;
}
imuxrcpad = devm_ioremap(pdevice, regaddr[4], regaddr[5]);
if (!imuxrcpad) {
pr_info("fail to ioremap");
goto err_device_create;
}
gpiodir = devm_ioremap(pdevice, regaddr[6], regaddr[7]);
if (!gpiodir) {
pr_info("fail to ioremap");
goto err_device_create;
}
gpiodat = devm_ioremap(pdevice, regaddr[8], regaddr[9]);
if (!gpiodat) {
pr_info("fail to ioremap");
goto err_device_create;
}
//时钟
tmpvalue = readl(pccgr1);
tmpvalue &= ~(0x3 << 26);
tmpvalue |= (0x3 << 26);
writel(tmpvalue, pccgr1);
//设置为GPIO
writel(0x5, imuxrcsw);
//设置电器属性
writel(0x10B0, imuxrcpad);
//GPIO方向
tmpvalue = readl(gpiodir);
tmpvalue |= 0x1 << 3;
writel(tmpvalue, gpiodir);
//关灯
//置1关灯
tmpvalue = readl(gpiodat);
tmpvalue |= (0x1 << 3);
writel(tmpvalue, gpiodat);
//增加sysfs文件系统中的调试节点
ret = device_create_file(pdevice, &led_attr);
if (ret)
pr_info("device_create_file failed");
pr_info("led init success\n");
return 0;
err_device_create:
class_destroy(pclass);
err_class_create:
cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
unregister_chrdev_region(devno, 1);
err_alloc_chrdev_region:
return -1;
}
static void __exit led_exit(void)
{
device_destroy(pclass, devno);
class_destroy(pclass);
cdev_del(pcdev);
unregister_chrdev_region(devno, 1);
pr_info("led exit success\n");
return;
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("haiersen");
#led_drv的makefile
#模块名
modulename=led_drv
#内核路径
kerdir=/home/linux/imx6ull/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
#当前路径
curpath=$(shell pwd)
#将代码加入模块编译选项中
obj-m+=$(modulename).o
all:
make -C $(kerdir) modules M=$(curpath)
cp $(modulename).ko ~/nfs/rootfs
.PHONY:
clean:
rm $(modulename).ko
//led_app
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(void)
{
int fd = 0;
int ledstat = 0;
fd = open("/dev/led0", O_RDWR);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
ledstat = 1;
write(fd, &ledstat, sizeof(ledstat));
read(fd, &ledstat, sizeof(ledstat));
if (1 == ledstat)
{
printf("open led success\n");
}
sleep(1);
ledstat = 0;
write(fd, &ledstat, sizeof(ledstat));
read(fd, &ledstat, sizeof(ledstat));
if (0 == ledstat)
{
printf("close led success\n");
}
sleep(1);
}
close(fd);
return 0;
}
#led_app的makefile
#模块名称
appname=led_app
#编译器
CC=arm-linux-gnueabihf-gcc
$(appname):main.c
$(CC) main.c -o $@
cp $(appname) ~/nfs/rootfs
.PHONY:
clean:
rm $(appname)
#总makefile
modulename=led
all:
make -C $(modulename)_app
make -C $(modulename)_drv