一、知识简介(记录学习要点)
二、程序开发流程
1、使用普通中断,大致流程图
设备树文件.dts文件
/* DJW 2022.4.29 /
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = “atkalpha-key”;
pinctrl-names = “default”;
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; / key0 */
status = “okay”;
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
keyirq.c文件
/*
* 根据linux内核的程序查找所使用函数的对应头文件。
*/
#include <linux/module.h> //MODULE_LICENSE,MODULE_AUTHOR
#include <linux/init.h> //module_init,module_exit
#include <linux/kernel.h> //printk
#include <linux/fs.h> //struct file_operations
#include <linux/slab.h> //kmalloc, kfree
#include <linux/uaccess.h> //copy_to_user,copy_from_user
#include <linux/io.h> //ioremap,iounmap
#include <linux/cdev.h> //struct cdev,cdev_init,cdev_add,cdev_del
#include <linux/device.h> //class
#include <linux/of.h> //of_find_node_by_path
#include <linux/of_gpio.h> //of_get_named_gpio
#include <linux/gpio.h> //gpio_request,gpio_direction_output,gpio_set_value
#include <linux/atomic.h> //atomic_t
#include <linux/of_irq.h> //irq_of_parse_and_map
#include <linux/interrupt.h> //request_irq
#include <linux/timer.h> //timer_list
#include <linux/jiffies.h> //jiffies
#include <linux/atomic.h> //atomic_set
#define KEY_NUM 1 //按键个数
#define KEYVALUE 0X01 //按键值
#define INVAKEY 0XFF //无效的按键值
/* 4.1.1 按键key行为结构体 */
struct irq_keydescri{
int gpio; /* io编号 */
int irqnum; /* 中断号 */
unsigned char value; /* 按键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int,void *) /* 中断处理函数 */
};
/* 1.5 keyirq设备结构体 */
struct keyirq_dev {
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
char *name; /* 设备名称 */
int dev_count; /* 设备个数 */
struct cdev cdev; /* 注册设备结构体 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
struct irq_keydescri irqkey[KEY_NUM];/* 按键key行为结构体 */
struct timer_list timer; /* 定时器结构体 */
atomic_t keyvalue; /* 按键值 */
atomic_t release; /* 按键释放标志 */
};
struct keyirq_dev keyirq; /* 定义keyirq设备结构体 */
/*
* inode: 传递给驱动的inode
* filp : 要进行操作的设备文件(文件描述符)
* buf : 数据缓冲区
* cnt : 数据长度
* ppos : 相对于文件首地址的偏移
*/
/* 2.1 打开字符设备文件 */
static int keyirq_open(struct inode *inode, struct file *filp) {
int ret = 0;
/* 设置私有类数据 */
filp->private_data = &keyirq;
return ret;
}
/* 2.2 关闭字符设备文件 */
static int keyirq_release(struct inode *inode, struct file *filp) {
int ret = 0;
return ret;
}
/* 2.3 向字符设备文件读出数据 */
static ssize_t keyirq_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {
int ret = 0;
unsigned char keyvalue;
unsigned char release;
/* 提取私有属性 */
struct keyirq_dev *dev = filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
release = atomic_read(&dev->release);
if(release) {
if(keyvalue & 0X80) {
keyvalue &= ~0X80;
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->release,0); // 按下标志清零
} else {
goto data_error;
}
return ret;
data_error:
return -EINVAL;
}
/* 2.4 向字符设备文件写入数据 */
static ssize_t keyirq_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {
int ret = 0;
return ret;
}
/* 2.5 keyirq设备操作集 */
static const struct file_operations keyirq_fops = {
.owner = THIS_MODULE,
.open = keyirq_open,
.release = keyirq_release,
.read = keyirq_read,
.write = keyirq_write,
};
/* 4.4.1 定时器处理函数,定时器10ms溢出触发定时器中断,进入定时器处理函数 */
static void timer_func(unsigned long arg) {
int value = 0;
struct keyirq_dev *dev = (struct keyirq_dev*)arg;
value = gpio_get_value(dev->irqkey[0].gpio);
if(value == 0) { /* 按下 */
atomic_set(&dev->keyvalue,dev->irqkey[0].value); /* 哪个按键被按下了 */
printk("KEY0 press!\r\n");
} else if(value == 1) { /* 释放 */
atomic_set(&dev->keyvalue,0X80 | dev->irqkey[0].value); /* 按键激活 */
atomic_set(&dev->release,1); /* 完整的按键过程 */
printk("KEY0 release!\r\n");
}
}
/* 4.2.1 按键中断处理函数,param传参是keyirq */
static irqreturn_t key_handler(int irq, void *param)
{
struct keyirq_dev *dev = param;
dev->timer.data = (volatile unsigned long)param; //传递值,会变的
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms定时
return IRQ_HANDLED;
}
/* 4. 按键初始化,这里分别 初始化IO,初始化中断,初始化定时器 */
static int key_init(struct keyirq_dev *dev) {
int ret = 0;
int i;
/* 4.1.2 按键IO初始化 */
dev->nd = of_find_node_by_path("/key"); //获取设备节点
if(dev->nd == NULL) {
ret = -EINVAL;
goto fail_nd;
}
for(i=0; i<KEY_NUM; i++) {
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd,"key-gpio",i); //获取GPIO编号
if(dev->irqkey[i].gpio < 0) {
ret = -EINVAL;
goto fail_gpio;
}
memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name,"KEY%d",i);
ret = gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name); //请求GPIO
if(ret) {
ret = -EBUSY;
printk("IO %d can't request!\r\n",dev->irqkey[i].gpio);
goto fail_request;
}
ret = gpio_direction_input(dev->irqkey[i].gpio); //设置GPIO
if(ret < 0) {
ret = -EINVAL;
goto fail_input;
}
}
/* 4.2 按键中断初始化 */
dev->irqkey[0].handler = key_handler; //初始化中断处理函数
dev->irqkey[0].value = KEYVALUE; //按键值设置0X01
for (i = 0; i < KEY_NUM; i++) {
// dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); //获取中断号
dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i); //通用的获取中断号方法
if (dev->irqkey[i].irqnum < 0) {
printk("%s: Cannot find interrupt.\n", dev->irqkey[i].name);
goto fail_irq;
}
ret = request_irq(dev->irqkey[i].irqnum,dev->irqkey[0].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
dev->irqkey[i].name,&keyirq);
if(ret) {
printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
goto fail_irq;
}
}
/* 4.4 初始化定时器 */
init_timer(&keyirq.timer); //初始化定时器
keyirq.timer.function = timer_func; //添加定时器处理函数
return 0;
fail_irq:
fail_input:
gpio_free(dev->irqkey[i].gpio);
fail_request:
fail_gpio:
fail_nd:
return ret;
}
/* 1.1 驱动模块入口函数 */
static int __init keyirq_init(void) {
int ret = 0;
printk("keyirq_init!\r\n");
/***********************************************************************************************/
/* 3.1 配置keyirq结构体参数 */
keyirq.dev_count = 1; //设置设备号个数
keyirq.name = "keyirq"; //设置设备名字
keyirq.major = 0; //0为系统分配设备号, 1为自定义设备号
if(keyirq.major) {
keyirq.devid = MKDEV(keyirq.major,0); //自定义主设备号和次设备号,并整合为设备号结构体中
ret = register_chrdev_region(keyirq.devid, keyirq.dev_count, keyirq.name); //注册设备号
} else {
alloc_chrdev_region(&keyirq.devid, 0, keyirq.dev_count, keyirq.name); //注册设备号,系统自动分配
keyirq.major = MAJOR(keyirq.devid);
keyirq.minor = MINOR(keyirq.devid);
}
if(ret < 0) {
goto fail_devid; //注册设备号失败
}
printk("keyirq major = %d, minor = %d\r\n",keyirq.major,keyirq.minor); //注册设备号成功了,就打印主次设备号
/* 3.2 注册或者叫添加字符设备 */
cdev_init(&keyirq.cdev, &keyirq_fops); //初始化cdev结构体
ret = cdev_add(&keyirq.cdev, keyirq.devid, keyirq.dev_count); //添加字符设备
if(ret < 0) {
goto fail_cdev; //注册或者叫添加设备失败
}
/* 3.3 自动创建设备节点 */
keyirq.class = class_create(THIS_MODULE, keyirq.name); //创建类
if(IS_ERR(keyirq.class)) {
ret = PTR_ERR(keyirq.class);
goto fail_class;
}
keyirq.device = device_create(keyirq.class, NULL, keyirq.devid, NULL, keyirq.name); //创建设备
if(IS_ERR(keyirq.device)) {
ret = PTR_ERR(keyirq.device);
goto fail_device;
}
/***********************************************************************************************/
/* 5. 初始化原子变量 */
atomic_set(&keyirq.keyvalue,INVAKEY); //设置为无效的按键值
atomic_set(&keyirq.release,0); //0表示按键没有被释放
/* 4.3 调用KEY IO初始化函数初始化IO */
ret = key_init(&keyirq);
if(ret < 0) {
goto fail_keyinit;
}
return ret;
fail_keyinit:
fail_device:
class_destroy(keyirq.class);
fail_class:
cdev_del(&keyirq.cdev);
fail_cdev:
unregister_chrdev_region(keyirq.devid, keyirq.dev_count);
fail_devid:
return ret;
}
/* 1.2 驱动模块出口函数 */
static void __exit keyirq_exit(void) {
int i;
del_timer_sync(&keyirq.timer); //删除定时器
/* 最后一一添加 , 注销字符设备驱动 */
for(i=0;i<KEY_NUM;i++) {
free_irq(keyirq.irqkey[i].irqnum,&keyirq);
gpio_free(keyirq.irqkey[i].gpio);
}
device_destroy(keyirq.class, keyirq.devid); //摧毁设备
class_destroy(keyirq.class); //摧毁类
cdev_del(&keyirq.cdev); //注销字符设备结构体
unregister_chrdev_region(keyirq.devid, keyirq.dev_count); //注销设备号
}
/* 1.3 注册驱动模块 */
module_init(keyirq_init);
module_exit(keyirq_exit);
/* 1.4 驱动许可和个人信息 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");
keyirqAPP.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
/*
* argc:应用程序参数个数
* argv[]:具体打参数内容,字符串形式
* ./keyirqAPP <filename>
* ./keyirqAPP /dev/keyirq
*/
/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned char data;
/* 判断输入的元素个数 */
if(argc != 2) {
printf("ERROR USAGE!\r\n");
return -1;
}
filename = argv[1]; //获取驱动文件的路径
fd = open(filename,O_RDWR); //根据文件路径以读写方式打开文件
if(fd < 0) {
printf("file %s open failed!\r\n",filename);
return -1;
}
/* 循环读取按键值 */
while(1) {
ret = read(fd,&data,sizeof(data));
if (ret < 0)
{
} else {
if(data) {
printf("key value = %#x\r\n",data);
}
}
}
close(fd);
return 0;
}
2、使用任务(tasklet 是利用软中断来实现的另外一种下半部机制)
/* key 任务处理函数 */
❤static void key_tasklet(unsigned long data) {
struct keyirq_dev *dev = (struct keyirq_dev*)data;
dev->timer.data = (volatile unsigned long)data; //传递值,会变的
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms定时
}
/* 4.2.1 按键中断处理函数,param传参是keyirq */
static irqreturn_t key_handler(int irq, void *param)
{
struct keyirq_dev *dev = param;
❤tasklet_schedule(&dev->irqkey[0].tasklet);
return IRQ_HANDLED;
}
/* 4. 按键初始化,这里分别 初始化IO,初始化中断,初始化定时器 */
static int key_init(struct keyirq_dev *dev) {
int ret = 0;
int i;
/* 4.1.2 按键IO初始化 */
dev->nd = of_find_node_by_path("/key"); //获取设备节点
if(dev->nd == NULL) {
ret = -EINVAL;
goto fail_nd;
}
for(i=0; i<KEY_NUM; i++) {
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd,"key-gpio",i); //获取GPIO编号
if(dev->irqkey[i].gpio < 0) {
ret = -EINVAL;
goto fail_gpio;
}
memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name,"KEY%d",i);
ret = gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name); //请求GPIO
if(ret) {
ret = -EBUSY;
printk("IO %d can't request!\r\n",dev->irqkey[i].gpio);
goto fail_request;
}
ret = gpio_direction_input(dev->irqkey[i].gpio); //设置GPIO
if(ret < 0) {
ret = -EINVAL;
goto fail_input;
}
}
/* 4.2 按键中断初始化 */
dev->irqkey[0].handler = key_handler; //初始化中断处理函数
dev->irqkey[0].value = KEYVALUE; //按键值设置0X01
for (i = 0; i < KEY_NUM; i++) {
// dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); //获取中断号
dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i); //通用的获取中断号方法
if (dev->irqkey[i].irqnum < 0) {
printk("%s: Cannot find interrupt.\n", dev->irqkey[i].name);
goto fail_irq;
}
ret = request_irq(dev->irqkey[i].irqnum,dev->irqkey[0].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
dev->irqkey[i].name,&keyirq);
if(ret) {
printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
goto fail_irq;
}
❤tasklet_init(&dev->irqkey[i].tasklet,key_tasklet,(unsigned long)dev);
}
/* 4.4 初始化定时器 */
init_timer(&keyirq.timer); //初始化定时器
keyirq.timer.function = timer_func; //添加定时器处理函数
return 0;
fail_irq:
fail_input:
gpio_free(dev->irqkey[i].gpio);
fail_request:
fail_gpio:
fail_nd:
return ret;
}
3、使用工作队列
作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。
程序与任务tasklet类似。