一、温故知新
1、字符设备编程框架
什么是字符设备?
在Linux操作系统中文件类型为.c文件,称为字符设备文件
按字节访问 访问的顺序是固定的
1)struct cdev结构
实现一个硬件字符设备的驱动程序时,实际上就是实例化一个struct cdev类型的对象
struct cdev {
const struct file_operations *ops; // 操作函数集合
dev_t dev; // 设备号
}
在实例化时,我们只需关注这两个成员即可
2)dev(设备号)
dev_t dev;
dev_t类型是无符号整型,是32bit的
设备号分为主设备号和次设备号
dev(设备号) = 主设备号(12bit [msb]) + 次设备号(20bit [lsb])
【1】静态注册
静态注册:程序员自己选择一个没有被内核占用且符合规定的设备号去注册
register_chrdev_region()
unregister_chrdev_region()
【2】动态注册
动态注册:内核自动分配一个设备号给我们使用
alloc_chrdev_region()
unregister_chrdev_region()
【3】内核中封装的宏
MKDEV宏:可以把主设备号和次设备号组合到一起
MAJOR宏:从设备号中提取主设备号
MINOR宏:从设备号中提取次设备号
3)ops(操作函数集合)
在struct file_operations结构中,几乎都是函数指针
当我们需要实现一个字符设备驱动的时候,
我们主要的任务就是实现操作函数集合中的函数
4)操作cdev对象的函数
cdev_init(); // 初始化
cdev_add(); // 将cdev对象添加到内核
cdev_del(); // 注销cdev对象
2、GPIO库的使用
寄存器在内核态开发时,无需再关注
因为Linux内核为程序员封装了GPIO相关的库可以使用
GPIO库的函数
gpio_request() // 申请gpio管脚
gpio_direction_input() // 设置gpio为输入模式
gpio_direction_output() // 设置gpio为输出模式
gpio_set_value() // 设置gpio的值
gpio_get_value() // 读取gpio的值
gpio_free() // 注销gpio管脚
3、用户态与内核态的数据交互
用户态与内核态交互的媒介:/dev/myleds(需要手动创建)
mknod /dev/myleds c major minor
major:主设备号
minor:次设备号
需要与cdev注册的设备号相同(不同的话,会报找不到设备文件)
1)数据交互
用户空间不能直接访问内核空间
内核空间不能直接访问用户空间
2)内核提供数据交互的接口
copy_to_user() // 从内核空间到用户空间
copy_from_user() // 从用户空间到内核空间
二、设备文件的自动创建
1、必备条件
1)根文件系统
【1】支持mdev命令
ls -l /sbin/mdev
【2】挂载proc以及sysfs
cat /etc/fstab
proc:是基于内存的文件系统(动态)
可以向用户态导出内核的执行状态
sysfs:是基于内存的文件系统(动态)
描述硬件的驱动模型,可以反映出硬件的层次关系
【3】支持热插拔事件
cat /etc/init.d/rcS
2)驱动程序
产生热插拔事件
注释:热插拔事件
狭义:USB设备的插拔
广义:/sys 目录下的文件变化
我们将设备看成是一棵树
【1】class_create()(创建树枝)
用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。此函数的执行效果就是在目录
/sys/class
下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的。class_create(owner, name)
【2】device_create()(创建果实)
用于动态地创建逻辑设备,并对新的逻辑设备类进行相应的初始化,将其与此函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到Linux内核系统的设备驱动程序模型中。函数能够自动地在
/sys/devices/virtual
目录下创建新的逻辑设备目录,在/dev
目录下创建与逻辑类对应的设备文件。struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
【3】device_destroy()(销毁果实)
用于从Linux内核系统设备驱动程序模型中移除一个设备,并删除
/sys/devices/virtual
目录下对应的设备目录及/dev目录下对应的设备文件。void device_destroy(struct class *class, dev_t devt)
【4】class_destroy()(销毁树枝)
用于删除设备的逻辑类,即从Linux内核系统中删除设备的逻辑类。此函数执行的效果是删除函数__class_create()或宏class_create()在目录
/sys/class
下创建的逻辑类对应的文件夹。void class_destroy(struct class *cls)
3)总结
【1】mdev
在系统启动 \ 热插拔 和动态加载模块时,自动创建设备节点
文件系统中的/dev目录下的设备节点都是由mdev创建的
在加载模块时根据驱动程序,可以在/dev/目录下自动创建设备文件
【2】class(树枝)
内核中定义了一个struct class结构体,class_create()函数可以实例化这个结构体,并将这个类存放在sysfs虚拟系统中。
通过class_create()注册/sys/class/<name>
通过class_destory()注销/sys/class/<name>
【3】device(果实)
在class_create()创建好类之后,再调用device_create()函数
系统会自动在/dev目录下创建相应的设备节点
根目录在加载模块时,用户空间中的mdev(设备管理器)会自动响应device_create()函数
去/sys目录下找对应的类从而创建设备文件
2、实验
【1】进入工程目录
cd /home/zjd/s5p6818/KERNEL/drivers
【2】创建新的工程
mkdir auto_drv
【3】编写程序
vim auto_drv.c
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/gpio.h> #include <mach/platform.h> #include <linux/uaccess.h> #include <linux/device.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Zjd"); #define CHRDEV_MAGOR 200 #define CHRDEV_MINOR 26 #define CHRDEV_NUM 1 #define CHRDEV_NAME "myleds" #define HIGH 1 #define LOW 0 #define LED0 (PAD_GPIO_B + 26) #define LED1 (PAD_GPIO_C + 12) #define LED2 (PAD_GPIO_C + 7) #define LED3 (PAD_GPIO_C + 11) #define TURN_ON LOW #define TURN_OFF HIGH dev_t dev = 0; struct cdev led_cdev; // define the global var of struct class struct class *cls = NULL; typedef struct led_desc{ unsigned int gpio; char *name; }led_desc_t; led_desc_t leds[] = { {LED0, "LED0"}, {LED1, "LED1"}, {LED2, "LED2"}, {LED3, "LED3"} }; long led_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { int k_index = 0; int ret = 0; ret = copy_from_user(&k_index, (const void *)arg, sizeof(int)); if (k_index > 4 || k_index < 1) return -EINVAL; switch (cmd) { case TURN_ON: gpio_set_value(leds[k_index - 1].gpio, LOW); break; case TURN_OFF: gpio_set_value(leds[k_index - 1].gpio, HIGH); break; default: return -EINVAL; } return arg; } struct file_operations led_fops = { .owner = THIS_MODULE, .unlocked_ioctl = led_ioctl }; int __init chrdev_init(void) { int major = CHRDEV_MAGOR; int minor = CHRDEV_MINOR; int i = 0; alloc_chrdev_region(&dev, CHRDEV_MINOR, CHRDEV_NUM, CHRDEV_NAME); major = MAJOR(dev); minor = MINOR(dev); printk(KERN_EMERG "major = %d\nminor = %d\n", major, minor); cdev_init(&led_cdev, &led_fops); cdev_add(&led_cdev, dev, CHRDEV_NUM); // auto mknod the device file // ctreat branch cls = class_create(THIS_MODULE, "easthome_leds"); // create fruit device_create(cls, NULL, dev, NULL, "myleds"); for (i = 0; i < ARRAY_SIZE(leds); i++) { gpio_request(leds[i].gpio, leds[i].name); gpio_direction_output(leds[i].gpio, HIGH); } return 0; } void __exit chrdev_exit(void) { int i = 0; for (i = 0; i < ARRAY_SIZE(leds); i++) { gpio_free(leds[i].gpio); } // auto delete the device file // destory fruit device_destroy(cls, dev); // destory branch class_destroy(cls); cdev_del(&led_cdev); unregister_chrdev_region(dev, CHRDEV_NUM); return ; } module_init(chrdev_init); module_exit(chrdev_exit);
【4】编写Makefile
vim Makefile
obj-m += auto_drv.o KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel ROOTFS_PATH=/nfs_share/_install all: make -C $(KERNEL_PATH) M=$(PWD) modules cp *.ko $(ROOTFS_PATH) clean: make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程
make
【6】编写应用层程序
mkdir test
cd test
vim led_test.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <sys/ioctl.h> #include <stdlib.h> #define ON 0 #define OFF 1 #define CDEV_PATH "/dev/myleds" int main(int argc, char *argv[]) { int fd = 0; int cmd = 0; int index = 0; if (argc < 3) { printf("Usage : %s <on/off> <1/2/3/4>\n", argv[0]); return -1; } if (!strcmp(argv[1], "on")) { cmd = ON; } else if (!strcmp(argv[1], "off")){ cmd = OFF; } else { printf("illegal param\n"); return -2; } index = atoi(argv[2]); if (index < 1 || index > 4) { printf("illegal param\n"); return -2; } if((fd = open(CDEV_PATH, O_RDWR)) < 0) { perror("open()"); return -3; } printf("open success!\n"); ioctl(fd, cmd, &index); printf("closing...\n"); close(fd); return 0; }
vim Makefile
SRC=led_test.c OBJ=led_test ARM_COMPILE=arm-cortex_a9-linux-gnueabi- GCC=gcc ROOTFS_PATH=/nfs_share/_install all: $(ARM_COMPILE)$(GCC) $(SRC) -o $(OBJ) cp $(OBJ) $(ROOTFS_PATH) clean: rm -rf $(OBJ)
【7】编译工程
make
【8】下位机测试
insmod auto.drv.ko
./led_test
三、远程登录开发板
1、准备rootfs
cp /mnt/hgfs/music/easthome_porting/rootfs_1204.tar.gz ./
1)解压缩
tar -xvf rootfs_1204.tar.gz
2)nfs挂载
sudo vim /etc/exports
3)重启服务
sudo /etc/init.d/nfs-kernel-server restart
4)关闭防火墙
sudo /etc/init.d/ufw stop
5)修改下位机环境变量
setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/nfs_share/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 init=linuxrc console=ttySAC0,115200 maxcpus=1 lcd=wy070ml tp=gslx680
saveenv
re
2、telnet
1)下位机开启server
vi etc/init.d/rcS
2)确保存在devpt目录
vi etc/init.d/rcS
注释:
pts是远程虚拟终端
devpts是远程终端的文件设备
通过挂载到/dev/pts可以了解到目前虚拟终端的基本情况
3)下位机的根文件系统有登录检验机制
vi etc/passwd
4)验证
【1】保证下位机可以ping通上位机
ping 192.168.1.8
【2】上位机执行
telent 192.168.1.6
3、ssh
SSH(Secure Shell)是一种网络协议,用于在网络上安全地进行远程登录和执行命令。它通过加密技术保护数据传输的安全性,使得用户可以在不安全的网络中安全地访问远程主机。SSH可以提供对远程主机的终端访问、文件传输和端口转发等功能,被广泛应用于服务器管理、系统维护和远程操作等场景。
1)安装ssh
sudo apt install openssh-client
--------------------------------------
如果需要被操纵,执行以下指令
sudo apt install openssh-server
--------------------------------------
2)验证
ssh
3)开启服务
service sshd start
或:
sudo /etc/init.d/ssh start
4)连接
ssh username@server-ip-address
如:ssh root@192.168.1.6
四、Linux内核中的中断子系统
裸板中的中断处理过程
1、中断的触发
1)中断源级
配置中断的触发方式:上升沿、下降沿、双边沿、高电平、低电平
中断触发(检测到中断信号之后,判断能不能报给CPU CORE)
2)中断控制器级
配置中断的优先级
中断使能
配置以IRQ FIQ的方式报给 CPU CORE
配置报给哪个CPU CORE
3)ARM CORE级
配置寄存器
cpsr.i = 0
中断的使能 I = 0
2、裸板中断处理过程
中断异常发生之后,硬件上自动做4件事儿
1)将cspr备份到spsr
2)修改cpsr的一些位
MODE模式
I 禁止IRQ
F 禁止FIQ
T 切换到ARM工作状态
3)保存返回的地址到 lr 寄存器中
4)跳转到异常向量表中执行
ldr pc, =irq_handler
irq_handler:
现场保护
bl c_irq_handler
恢复现场
c_irq_handler
{
区分哪个硬件出发的irq信号
调用该硬件的中断处理函数
清除pending位
}
3、Linux中断处理过程
Linux的中断处理过程与裸板的中断处理过程是相同的,linux kernel将能够帮程序员写的代码,都写好了,写好的这一部分,就叫做“Linux中断子系统”
注意:
特定硬件的中断的触发方式,需要自行配置(上升沿、下降沿、双边沿、高电平、低电平)
特定硬件的中断处理函数需要自己编写代码来实现
1)中断注册函数
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
注释:
static:声明为静态函数
inline:C++关键字,将函数指定为内联函数
----------------------------------------------
使用inline关键字修饰的函数,编译器在进行编译时,会直接将该函数放在代码段中,省去了函数调用时的开销,典型的空间换时间,比较适用于代码段较小,但调用次数较多且较为耗时的函数。
以下两种情景不适合做内联优化:
【1】代码段很大且不经常使用的函数
【2】递归函数
----------------------------------------------
__must_check:用于表明,如果调用我修饰的函数,则调用者必须对返回值进行处理,否则就会给出警告
irq:中断号
----------------------------------------------
1】将gpio转换为irq
gpio_to_irq(gpio)
2】将irq转换为gpio
irq_to_gpio(irq)
3】查看开发板定义的中断号
vim kernel/arch/arm/mach-s5p6818/include/mach/s5p6818_irq.h
----------------------------------------------
handler:要注册的中断处理函数
----------------------------------------------
返回值为 irqreturn_t 类型
----------------------------------------------
flsgs:自行配置的中断触发方式
----------------------------------------------
vim kernel/include/linux/interrupt.h
----------------------------------------------
name:要注册的中断的名称
dev:要传递给自己注册的中断处理函数的第二个参数
2)中断注销函数
void free_irq(unsigned int irq, void *dev_id)
注释:
irq:中断号
dev_id:要传递给自己注册的中断处理函数的第二个参数
3)实验
【1】进入工程目录
cd /home/zjd/s5p6818/KERNEL/drivers
【2】创建新的工程
mkdir interrupt_btn
【3】编写程序
vim interrupt_btn.c
#include <linux/init.h> #include <linux/module.h> #include <mach/platform.h> #include <linux/interrupt.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Zjd"); #define IRQ_GPIOA28 (IRQ_GPIO_A_START + 28) #define IRQ_GPIOB30 (IRQ_GPIO_B_START + 30) #define IRQ_GPIOB31 (IRQ_GPIO_B_START + 31) #define IRQ_GPIOB9 (IRQ_GPIO_B_START + 9) #define BTN_UP IRQ_GPIOA28 #define BTN_DOWN IRQ_GPIOB30 #define BTN_LEFT IRQ_GPIOB31 #define BTN_RIGHT IRQ_GPIOB9 // define a struct btn_desc to save the info of irq and name typedef struct btn_desc { int irq; char *name; }btn_desc_t; btn_desc_t btns[] = { {BTN_UP , "up"}, {BTN_DOWN , "down"}, {BTN_LEFT , "left"}, {BTN_RIGHT, "right"} }; // register the interrupt handler irqreturn_t btn_irq(int irq, void *data) { btn_desc_t *pdata = (btn_desc_t *)data; printk(KERN_EMERG "%s is pressed!\n", pdata->name); return IRQ_HANDLED; } int __init interrupt_btn_init(void) { int i = 0; int ret = 0; // recive the ret value for (i = 0; i < ARRAY_SIZE(btns); i++) { ret = request_irq(btns[i].irq, btn_irq, IRQF_TRIGGER_FALLING, btns[i].name, &(btns[i])); if (ret) { printk(KERN_EMERG "request_irq is failed!\n"); i --; while (i >= 0) { // if register failed, we should destory the irq free_irq(btns[i].irq, &(btns[i])); i --; } return -EAGAIN; } } return 0; } void __exit interrupt_btn_exit(void) { int i = 0; for (i = 0; i < ARRAY_SIZE(btns); i++) { free_irq(btns[i].irq, &(btns[i])); } return ; } module_init(interrupt_btn_init); module_exit(interrupt_btn_exit);
【4】编写Makefile
vim Makefile
obj-m += interrupt_btn.o KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel ROOTFS_PATH=/nfs_share/rootfs all: make -C $(KERNEL_PATH) M=$(PWD) modules cp *.ko $(ROOTFS_PATH) clean: make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程
make
【6】下位机测试
telnet 192.168.1.6
insmod interrupt_btn.ko
【7】调试
原因:模块冲突,内核中自带了按键驱动程序
解决方案:裁剪内核
1】cd kernel
2】make menuconfig
make menuconfig
Device Drivers --->
Input device support --->
[*] Keyboards --->
< > SLsiAP push Keypad support
3】上位机拷贝内核至下位机
make uImage
cp arch/arm/boot/uImage /tftpboot/
4】下位机更新内核tftp 48000000 uImage
mmc write 48000000 2000 3000
re
5】再次验证
4、总结
中断处理函数存在的疑虑
【1】Linux系统希望中断上下文处理的越快越好(不应该有printk函数,其不具可重入性)
【2】中断处理函数中不应该有返回值
【3】中断处理函数中不应该有额外的参数
五、中断服务程序的特点
1、特点
1)要求执行速度越快越好
2)Linux的中断处理函数中不应该出现引起阻塞或休眠的函数
如:rec() \ sleep()
3)Linux的中断处理过程应该使用独立的栈(内核态的栈)
4)中断服务程序工作于中断上下文,所以不能和用户空间进行数据交互
copy_to_user
copy_from_user
2、中断上下文
中断发生以后,CPU接收到中断信号,硬件会干4件事儿
1)把CPSR备份到SPSR
2)修改CPSR的一些位
MODE模式
I 禁止IRQ
F 禁止FIQ
T 切换到ARM工作状态
3)保存返回地址到LR寄存器
4)跳转到异常向量表中执行
3、如何去做
Linux操作系统中断上下文越快执行完越好,但对于硬件来说,有些硬件执行起来很慢,针对这种情况,Linux内核提出了底半部机制
1)顶半部(上半部)
只做紧急的工作
如:一些寄存器的读写、中断pending的清除操作、登记底半部
2)底半部(下半部)
做不紧急的工作(但必须要做)
如:sleep() \ delay() \ 读写时序的延时
4、登记底半部
1)软中断
如:sei指令(需要修改内核代码,不能以独立的.ko文件存在,实现起来不方便)
2)tasklet
tasklet是基于软中断方式实现的
使用步骤:
【1】定义tasklet变量
struct tasklet_struct btn_tasklet
【2】初始化tasklet变量
static inline void tasklet_init(struct tasklet_struct *tasklet, void (*func)(unsigned long), unsigned long data)
注释:
tasklet:tasklet变量地址
func:底半部函数的地址
data:底半部函数的参数
【3】使用成功初始化的tasklet登记底半部
static inline void tasklet_schedule(struct tasklet_struct *tasklet)
【4】实验
1】进入工程目录
cd /home/zjd/s5p6818/KERNEL/drivers
2】创建新的工程
mkdir tasklet_btn
3】编写程序
vim tasklet_btn.c
#include <linux/init.h> #include <linux/module.h> #include <mach/platform.h> #include <linux/interrupt.h> #include <linux/delay.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Zjd"); #define IRQ_GPIOA28 (IRQ_GPIO_A_START + 28) #define IRQ_GPIOB30 (IRQ_GPIO_B_START + 30) #define IRQ_GPIOB31 (IRQ_GPIO_B_START + 31) #define IRQ_GPIOB9 (IRQ_GPIO_B_START + 9) #define BTN_UP IRQ_GPIOA28 #define BTN_DOWN IRQ_GPIOB30 #define BTN_LEFT IRQ_GPIOB31 #define BTN_RIGHT IRQ_GPIOB9 // define a struct btn_desc to save the info of irq and name typedef struct btn_desc { int irq; char *name; }btn_desc_t; btn_desc_t btns[] = { {BTN_UP , "up"}, {BTN_DOWN , "down"}, {BTN_LEFT , "left"}, {BTN_RIGHT, "right"} }; // step_1 :define the global tasklet_struct btn_tasklet struct tasklet_struct btn_tasklet; // story the counts of press key int count = 0; // register the interrupt handler irqreturn_t btn_irq(int irq, void *data) { btn_desc_t *pdata = (btn_desc_t *)data; printk(KERN_EMERG "%s is pressed!\n", pdata->name); // step_3 : register the partial of buttom tasklet_schedule(&btn_tasklet); return IRQ_HANDLED; } // step_4 : implement the fun of the partial of buttom void btn_tasklet_func(unsigned long data) { int *pdata = (int *)data; printk(KERN_EMERG "do bottom half work : count = %d\n", *pdata); // not delay // udelay(100); // msleep(100); (*pdata)++; return ; } int __init interrupt_btn_init(void) { int i = 0; int ret = 0; // recive the ret value for (i = 0; i < ARRAY_SIZE(btns); i++) { ret = request_irq(btns[i].irq, btn_irq, IRQF_TRIGGER_FALLING, btns[i].name, &(btns[i])); if (ret) { printk(KERN_EMERG "request_irq is failed!\n"); i --; while (i >= 0) { // if register failed, we should destory the irq free_irq(btns[i].irq, &(btns[i])); i --; } return -EAGAIN; } } // step_2 : init the tasklet_struct variable btn_tasklet tasklet_init(&btn_tasklet, btn_tasklet_func, (unsigned long)&count); return 0; } void __exit interrupt_btn_exit(void) { int i = 0; for (i = 0; i < ARRAY_SIZE(btns); i++) { free_irq(btns[i].irq, &(btns[i])); } return ; } module_init(interrupt_btn_init); module_exit(interrupt_btn_exit);
4】编写Makefile
vim Makefile
obj-m += tasklet_btn.o KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel ROOTFS_PATH=/nfs_share/rootfs all: make -C $(KERNEL_PATH) M=$(PWD) modules cp *.ko $(ROOTFS_PATH) clean: make -C $(KERNEL_PATH) M=$(PWD) clean
5】编译工程
make
6】下位机测试
insmod tasklet_btn.ko(不加延时)
insmod tasklet_btn.ko(加延时)
注意:
使用tasklet登记的底半部函数不能调用有阻塞或者休眠的函数,因为tasklet是基于软件中断的方式实现的,其登记的函数工作于中断的上下文,对其进行阻塞或延时对其它需要中断的函数影响很大。
Linux内核的延时会导致内核吐核,从而使得linux内核崩溃,比较严重
(毫秒级的延时会导致内核吐核,微秒级的延时不会导致内核吐核)
3)任务队列
不同于tasklet,我们可以使用工作队列(FIFO),完成我们对底半部使用有阻塞或休眠函数的需求
使用步骤:
【1】定义工作队列对象
struct work_struct btn_work
【2】初始化tasklet变量
INIT_WORK(_work, _func)
注意:
【3】使用成功初始化的work登记底半部
int schedule_work(struct work_struct *work)
【4】对已经登记但还未执行的work进行处理
void flush_scheduled_work(void)
bool cancel_work_sync(struct work_struct *work)
【5】实验
1】进入工程目录
cd /home/zjd/s5p6818/KERNEL/drivers
2】创建新的工程
mkdir work_btn
3】编写程序
vim tasklet_btn.c
#include <linux/init.h> #include <linux/module.h> #include <mach/platform.h> #include <linux/interrupt.h> #include <linux/delay.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Zjd"); #define IRQ_GPIOA28 (IRQ_GPIO_A_START + 28) #define IRQ_GPIOB30 (IRQ_GPIO_B_START + 30) #define IRQ_GPIOB31 (IRQ_GPIO_B_START + 31) #define IRQ_GPIOB9 (IRQ_GPIO_B_START + 9) #define BTN_UP IRQ_GPIOA28 #define BTN_DOWN IRQ_GPIOB30 #define BTN_LEFT IRQ_GPIOB31 #define BTN_RIGHT IRQ_GPIOB9 // define a struct btn_desc to save the info of irq and name typedef struct btn_desc { int irq; char *name; }btn_desc_t; btn_desc_t btns[] = { {BTN_UP , "up"}, {BTN_DOWN , "down"}, {BTN_LEFT , "left"}, {BTN_RIGHT, "right"} }; // step_1 :define the global work_struct btn_work struct work_struct btn_work; // story the counts of press key int count = 0; // register the interrupt handler irqreturn_t btn_irq(int irq, void *data) { btn_desc_t *pdata = (btn_desc_t *)data; printk(KERN_EMERG "%s is pressed!\n", pdata->name); // step_3 : register the partial of buttom schedule_work(&btn_work); return IRQ_HANDLED; } // step_4 : implement the fun of the partial of buttom static void btn_work_func(struct work_struct *work) { printk(KERN_EMERG "do bottom half work !\n"); msleep(10000); printk(KERN_EMERG "time is over!\n"); return ; } int __init interrupt_btn_init(void) { int i = 0; int ret = 0; // recive the ret value for (i = 0; i < ARRAY_SIZE(btns); i++) { ret = request_irq(btns[i].irq, btn_irq, IRQF_TRIGGER_FALLING, btns[i].name, &(btns[i])); if (ret) { printk(KERN_EMERG "request_irq is failed!\n"); i --; while (i >= 0) { // if register failed, we should destory the irq free_irq(btns[i].irq, &(btns[i])); i --; } return -EAGAIN; } } // step_2 : init the work_struct variable btn_work INIT_WORK(&btn_work, btn_work_func); return 0; } void __exit interrupt_btn_exit(void) { int i = 0; // step_5 : deal the without execute func // flush_scheduled_work(&btn_work) cancel_work_sync(&btn_work); for (i = 0; i < ARRAY_SIZE(btns); i++) { free_irq(btns[i].irq, &(btns[i])); } return ; } module_init(interrupt_btn_init); module_exit(interrupt_btn_exit);
4】编写Makefile
vim Makefile
obj-m += work_btn.o KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel ROOTFS_PATH=/nfs_share/rootfs all: make -C $(KERNEL_PATH) M=$(PWD) modules cp *.ko $(ROOTFS_PATH) clean: make -C $(KERNEL_PATH) M=$(PWD) clean
5】编译工程
make
6】下位机测试
insmod work_btn.ko
注意:
使用work登记的底半部函数工作于进程的上下文,所以对底半部的调用没有限制,不会影响其它需要进行中断的函数。
内核中维护了一个工作队列,每调用一次schedule_work(),就会在维护的工作队列中添加一个节点,内核专门维护了一个线程对工作队列中的节点进行扫描,挨个执行。
4)比较
【1】使用tasklet登记的底半部函数不能执行阻塞或睡眠的函数
【2】使用工作队列登记的底半部函数可以调用阻塞或睡眠的函数
5)delayed_work
(在底半部登记完成后,延时一段时间后再执行)
delayed_work的原理和工作队列没什么区别
只不过比工作队列多了一个内核定时器(登记完底半部函数之后会计时)
使用步骤:
【1】定义delayed_work
struct delayed_work btn_dwork
【2】初始化tasklet变量
INIT_DELAYED_WORK(_work, _func)
【3】使用成功初始化的work登记底半部
int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
【4】对已经登记但还未执行的work进行处理
bool flush_delayed_work(struct delayed_work *dwork)
bool cancel_delayed_work_sync(struct delayed_work *dwork)
【5】实验
1】进入工程目录
cd /home/zjd/s5p6818/KERNEL/drivers
2】创建新的工程
mkdir delayed_btn
3】编写程序
vim delayed_btn.c
#include <linux/init.h> #include <linux/module.h> #include <mach/platform.h> #include <linux/interrupt.h> #include <linux/delay.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Zjd"); #define IRQ_GPIOA28 (IRQ_GPIO_A_START + 28) #define IRQ_GPIOB30 (IRQ_GPIO_B_START + 30) #define IRQ_GPIOB31 (IRQ_GPIO_B_START + 31) #define IRQ_GPIOB9 (IRQ_GPIO_B_START + 9) #define BTN_UP IRQ_GPIOA28 #define BTN_DOWN IRQ_GPIOB30 #define BTN_LEFT IRQ_GPIOB31 #define BTN_RIGHT IRQ_GPIOB9 // define a struct btn_desc to save the info of irq and name typedef struct btn_desc { int irq; char *name; }btn_desc_t; btn_desc_t btns[] = { {BTN_UP , "up"}, {BTN_DOWN , "down"}, {BTN_LEFT , "left"}, {BTN_RIGHT, "right"} }; // step_1 :define the global delayed_work btn_dwork struct delayed_work btn_dwork; // story the counts of press key int count = 0; // register the interrupt handler irqreturn_t btn_irq(int irq, void *data) { btn_desc_t *pdata = (btn_desc_t *)data; printk(KERN_EMERG "%s is pressed!\n", pdata->name); // step_3 : register the partial of buttom schedule_delayed_work(&btn_dwork, 10 * HZ); // delay 10s to execute the func return IRQ_HANDLED; } // step_4 : implement the fun of the partial of buttom static void btn_dwork_func(struct work_struct *work) { printk(KERN_EMERG "do bottom half work !\n"); return ; } int __init interrupt_btn_init(void) { int i = 0; int ret = 0; // recive the ret value for (i = 0; i < ARRAY_SIZE(btns); i++) { ret = request_irq(btns[i].irq, btn_irq, IRQF_TRIGGER_FALLING, btns[i].name, &(btns[i])); if (ret) { printk(KERN_EMERG "request_irq is failed!\n"); i --; while (i >= 0) { // if register failed, we should destory the irq free_irq(btns[i].irq, &(btns[i])); i --; } return -EAGAIN; } } // step_2 : init the delayed_work variable btn_dwork INIT_DELAYED_WORK(&btn_dwork, btn_dwork_func); return 0; } void __exit interrupt_btn_exit(void) { int i = 0; // step_5 : deal the without execute func flush_delayed_work(&btn_dwork); //cancel_delayed_work_sync(&btn_dwork); for (i = 0; i < ARRAY_SIZE(btns); i++) { free_irq(btns[i].irq, &(btns[i])); } return ; } module_init(interrupt_btn_init); module_exit(interrupt_btn_exit);
4】编写Makefile
vim Makefile
obj-m += delayed_btn.o KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel ROOTFS_PATH=/nfs_share/rootfs all: make -C $(KERNEL_PATH) M=$(PWD) modules cp *.ko $(ROOTFS_PATH) clean: make -C $(KERNEL_PATH) M=$(PWD) clean
5】编译工程
make
6】下位机测试
insmod delayed_btn.ko
【6】总结
【a】注册多次底半部函数,只有第一次有效
【b】登记底半部函数之后,如果使用的是flush销毁,我们不等函数执行,就将模块卸载,会使得底半部函数提前执行