1 实现原理
重点:驱动程序 = 软件架构 + 硬件操作
实现原理看两张图即可
1.1 用户程序访问驱动程序的流程
2.驱动程序的架构
2 代码实现
2.1 搭建框架
led驱动程序: led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
//LED驱动程序的open函数
static int led_open(struct inode *inode, struct file *file)
{
printk("first_drv_open"); //注意不能用printf,而是用内核提供的printk
return 0;
}
// LED驱动程序的write函数
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("first_drv_write");
return 0;
}
// 驱动程序通过下面的文件操作结构体告诉内核对应的操作
// 即用户程序的open、write对应 led_open、lep_write
// 注意:file_operations结构体放在led_open led_write函数下面,不然编译时出现未定义这两个函数
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
// LED驱动程序的入口函数
static int led_init(void)
{
//注册字符设备,即把first_drv_fops插入到内核维护的字符设备数组
//而在这个数组里,主设备号111即为数组索引
register_chrdev(111, "led_drv", &led_fops);
return 0;
}
// LED驱动的卸载函数
void led_exit(void)
{
//在字符设备数组中卸载主设备号为111的first_drv_fops
unregister_chrdev(111, "led_drv");
}
// 让内核维护的一个结构体里的函数指针指向驱动程序的入口函数
module_init(led_init)
// 让内核维护的一个结构体里的函数指针指向驱动程序的卸载函数
module_exit(led_exit)
// 模块注册为GPL,避免内核不能识别LED驱动程序
MODULE_LICENSE("GPL");
Makefile 文件
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led_drv.o
注意:必须先编译linux-2.6.22.6内核,可参考https://blog.youkuaiyun.com/qq_42800075/article/details/105470114
此外,pwd不是单引号,而是 `pwd` ,否则会报错,如下所示
测试函数:led_drv_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char * * argv)
{
int fd, val = 1;
fd = open("/dev/led_drv", O_RDWR);
if(fd < 0)
printf("can't open /dev/led_drv");
write(fd, &val, 4)
return 0;
}
2.2 硬件操作
先看下面对软件框架的编译、测试
3 编译、测试与完善
3.1 编译
当执行make时,运行结果如下
3.2 测试与完善
测试结果
为啥呢?
因为还没创建/dev/led_drv设备节点,看下图
提问1:有没发现,手动指定一个主设备号不仅可能跟现有设备号冲突,且特别麻烦,有没自动指定主设备号?
完善1:看下图
注意,上图两个函数拼写有误,是register_chrdev unregister_chrdev 是dev
而不是 drv
提问2:每次手动新建设备节点,太麻烦了。有没有自动创建设备节点?
完善2:看下图
接下来,让我们填充“硬件操作”,实现三个LED灯的点亮和熄灭控制
硬件操作三步走
- 定义配置寄存器、数据寄存器指针,并进行地址映射,如下图
2.编写驱动程序led_open、led_write
3. 编写main函数,测试
测试图,没问题,on时三个LED都亮了,off时都熄灭
4 总结
驱动程序 = 软件架构 + 硬件操作
软件架构如前面原理讲解的第二张图
硬件操作 = 1.看原理图,确定引脚 + 2.看手册,确定操作流程 + 3 编程
(驱动程序的硬件操作与单片机的硬件操作主要区别:驱动程序操作虚拟地址,需进行地址映射,而单片机直接操作物理地址)