杂项设备驱动和字符设备驱动代码上的区别在于,杂项设备驱动无需复杂的注册设备了,省了一大段代码,看起来更好理解。
第一步
首先还是要找到相应的寄存器和GPIO的引脚,我们还是选择GPIO4,具体过程请看上篇 树莓派驱动开发(三)字符设备驱动之点亮LED 这里不再赘述
第二步
我将驱动代码命名为led.c,以下为头文件以及我们要操作的寄存器地址
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/io.h>
#define GPIO4_DR 0xfe200000
#define GPIO4_H 0xfe20001c
#define GPIO4_L 0xfe200028
unsigned int *vir_gpio4_dr=NULL;
unsigned int *vir_gpio4_h=NULL;
unsigned int *vir_gpio4_l=NULL;
接着是我们的初始化函数和退出函数,利用API函数注册了杂项设备以及地址映射
//文件操作集
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
//miscdevice 结构体
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc",
.fops = &misc_fops,
};
static int misc_init(void)
{
int ret;
ret = misc_register(&misc_dev); //注册杂项设备
if (ret < 0)
{
printk("misc registe is error \n");
}
printk("misc registe is succeed \n");
vir_gpio4_dr=ioremap(GPIO4_DR,4);
if(vir_gpio4_dr==NULL )
{
printk("gpio4dr ioremap error\n");
return EBUSY;
}
vir_gpio4_h=ioremap(GPIO4_H,4);
if( vir_gpio4_h==NULL)
{
printk("gpio4h ioremap error\n");
return EBUSY;
}
vir_gpio4_l=ioremap(GPIO4_L,4);
if(vir_gpio4_l==NULL)
{
printk("gpio4l ioremap error\n");
return EBUSY;
}
printk("gpio ioremap success\n");
return 0;
}
static void misc_exit(void)
{
misc_deregister(&misc_dev); //卸载杂项设备
printk(" misc gooodbye! \n");
iounmap(vir_gpio4_dr);
iounmap(vir_gpio4_h);
iounmap(vir_gpio4_l);
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
最后是杂项设备的操作函数,读和写都是利用api函数进行内核空间和用户空间的数据交换
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[] = "hehe";
if (copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0)
{
printk("copy_to_user error\n ");
return -1;
}
printk("misc_read\n ");
return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = {0};
if (copy_from_user(kbuf, ubuf, size) != 0)
{
printk("copy_from_user error\n ");
return -1;
}
printk("kbuf is %s\n ", kbuf);
*vir_gpio4_dr |=(1<<(3*4));
if(kbuf[0]==1)
{
*vir_gpio4_h |=(1<<4);
}
else if(kbuf[0]==0)
{
*vir_gpio4_l |=(1<<4);
}
return 0;
}
int misc_release(struct inode *inode, struct file *file)
{
printk("hello misc_relaease bye bye \n ");
return 0;
}
int misc_open(struct inode *inode, struct file *file)
{
printk("hello misc_open\n ");
return 0;
}
全部代码
led.c
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/io.h>
#define GPIO4_DR 0xfe200000
#define GPIO4_H 0xfe20001c
#define GPIO4_L 0xfe200028
unsigned int *vir_gpio4_dr=NULL;
unsigned int *vir_gpio4_h=NULL;
unsigned int *vir_gpio4_l=NULL;
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[] = "hehe";
if (copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0)
{
printk("copy_to_user error\n ");
return -1;
}
printk("misc_read\n ");
return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = {0};
if (copy_from_user(kbuf, ubuf, size) != 0)
{
printk("copy_from_user error\n ");
return -1;
}
printk("kbuf is %s\n ", kbuf);
*vir_gpio4_dr |=(1<<(3*4));
if(kbuf[0]==1)
{
*vir_gpio4_h |=(1<<4);
}
else if(kbuf[0]==0)
{
*vir_gpio4_l |=(1<<4);
}
return 0;
}
int misc_release(struct inode *inode, struct file *file)
{
printk("hello misc_relaease bye bye \n ");
return 0;
}
int misc_open(struct inode *inode, struct file *file)
{
printk("hello misc_open\n ");
return 0;
}
//文件操作集
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
//miscdevice 结构体
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc",
.fops = &misc_fops,
};
static int misc_init(void)
{
int ret;
ret = misc_register(&misc_dev); //注册杂项设备
if (ret < 0)
{
printk("misc registe is error \n");
}
printk("misc registe is succeed \n");
vir_gpio4_dr=ioremap(GPIO4_DR,4);
if(vir_gpio4_dr==NULL )
{
printk("gpio4dr ioremap error\n");
return EBUSY;
}
vir_gpio4_h=ioremap(GPIO4_H,4);
if( vir_gpio4_h==NULL)
{
printk("gpio4h ioremap error\n");
return EBUSY;
}
vir_gpio4_l=ioremap(GPIO4_L,4);
if(vir_gpio4_l==NULL)
{
printk("gpio4l ioremap error\n");
return EBUSY;
}
printk("gpio ioremap success\n");
return 0;
}
static void misc_exit(void)
{
misc_deregister(&misc_dev); //卸载杂项设备
printk(" misc gooodbye! \n");
iounmap(vir_gpio4_dr);
iounmap(vir_gpio4_h);
iounmap(vir_gpio4_l);
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
还要写个应用层的测试文件来控制LED的亮灭
app.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int fd;
char buf[64] = "0";
fd = open("/dev/hello_misc",O_RDWR);//打开设备节点
if(fd < 0)
{
perror("open error \n");
return fd;
}
buf[0]=atoi(argv[1]);
//read(fd,buf,sizeof(buf));
write(fd,buf,sizeof(buf)); //向内核层写数据
//printf("buf is %s\n",buf);
close(fd);
return 0;
}
Makefile每次只需要改变要编译的文件名就行
Makefile
# 判断是否在内核构建系统内。如果没有定义 KERNELRELEASE,则表示这是从命令行调用。
ifneq ($(KERNELRELEASE),)
# 如果不是内核构建系统,则定义需要编译的模块对象文件。
# obj-m 是内核模块的编译变量,+= 表示添加模块文件(.o)
obj-m += led.o
else
# 定义内核头文件的位置,使用当前正在运行的内核版本。
KDIR := /home/interest/linux/lib/modules/$(shell uname -r)/build
# 定义当前的工作目录。
PWD := $(shell pwd)
# 默认目标。如果调用了 make 而没有指定目标,会执行这个部分。
# -C $(KDIR) 表示切换到内核源码目录进行编译
# M=$(PWD) 表示在当前模块的目录下执行内核模块编译
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
# 清理目标:用于清理编译产生的中间文件。
clean:
rm -f *.mod.c *.order *.ko *.o *.mod *.symvers
endif
测试
将led一遍接gpio4的引脚,一遍接GND
Make //编译驱动代码
gcc app.c -o app //编译测试代码
sudo insmod led.ko //加载驱动模块
sudo ./app 1 //led亮
sudo ./app 0 //led灭
sudo rmmod led //卸载模块
完成!!