在代码中获得中断的方法
对于platform_device
一个节点能被转换为platform_device,如果它的设备树里指定了中断属性,那么可以从platform_device中获得“中断资源”,函数如下,可以使用下列函数获得IORESOURCE_IRQ资源,即中断号

/**
 * platform_get_resource - get a resource for a device
 * @dev: platform device
 * @type: resource type   // 取哪类资源?IORESOURCE_MEM、IORESOURCE_REG
*                      // IORESOURCE_IRQ等
 * @num: resource index  // 这类资源中的哪一个?
 */
struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num);

对于I2C设备、SPI设备
对于I2C设备节点,I2C总线驱动在处理设备树里的I2C子节点时,也会处理其中的中断信息。一个I2C设备会被转换为一个i2c_client结构体,中断号会保存在i2c_client的irq成员里
对于SPI设备节点,SPI总线驱动在处理设备树里的SPI子节点时,也会处理其中的中断信息。一个SPI设备会被转换为一个spi_device结构体,中断号会保存在spi_device的irq成员里

调用of_irq_get获得中断号
如果你的设备节点既不能转换为platform_device,它也不是I2C设备,不是SPI设备,那么在驱动程序中可以自行调用of_irq_get函数去解析设备树,得到中断号
对于GPIO作为中断
可以使用int gpio_to_irq(unsigned int gpio)
或gpiod_to_irq获得中断号。
通用的获得中断号, 适用任意节点
编写驱动的时候需要用到中断号,我们用到中断号,中断信息已经写到了设备树里面,因
此可以通过 irq_of_parse_and_map函数从 interupts属性中提取到对应的设备号,函数原型如下:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
dev:设备节点。
index:索引号,interrupts属性可能包含多条中断信息,通过 index指定要获取的信息。

注册中断服务函数
使用函数request_irq

/*
@Param irq 中断号
@Param handler 中断服务函数 typedef irqreturn_t (*irq_handler_t)(int, void *);
@Param flags 中断触发条件
@Param name 随便写
@Param dev 传入中断服务函数的参数
*/
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev);

关于中断号如何获取 , 可以从设备树中读取

中断下半部的调度方法 linux/interrupt.h

内核对中断下半部有 1,软中断 2,工作队列 3,线程化中断等方式.

在中断服务函数中调度中断下半部
内核将每个下半部的任务抽象成tasklet结构体, 调度的过程就是将tasklet结构体放到执行队列中

  • tasklet 软中断方式, 不能执行太久, 在中断上下文中执行, 无法休眠
    构造一个tasklet结构体 使用 tasklet_init函数初始化 使用tasklet_kill卸载 函数原型
struct tasklet_struct tasklet;//定义

/*
@param data 传给函数func的参数
*/
void tasklet_init(struct tasklet_struct *t,
			 void (*func)(unsigned long), unsigned long data);
void tasklet_kill(struct tasklet_struct *t); //卸载tasklet

在中断函数中调度tasklet 使用函数tasklet_schedule
函数原型:

void tasklet_schedule(struct tasklet_struct *t);
  • 工作队列work 使用内核线程来处理中断下半部 linux/workqueue.h
    这个线程作为一个死循环存在
while(1){
	1,判断work队列中是否有work?
	2,无work, 则sleep
	3,work加入队列, 将线程唤醒, 取出work
	4,执行work->fun
}

使用一个线程依次执行work, 导致某个work如果耗时很长/休眠, 会影响其他的work效率, 应酌情自行创建新的工作线程

相关使用流程
work结构体:

struct work_struct{
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};
typedef void (*work_func_t)(struct work_struct *work);1个宏是用来定义一个work_struct结构体,要指定它的函数。
#define DECLARE_WORK(n, f)						\
	struct work_struct n = __WORK_INITIALIZER(n, f)2个宏用来定义一个delayed_work结构体,也要指定它的函数。
所以“delayed”,意思就是说要让它运行时,可以指定:某段时间之后你再执行。
#define DECLARE_DELAYED_WORK(n, f)					\
	struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
如果要在代码中初始化work_struct结构体,可以使用下面的宏:
#define INIT_WORK(_work, _func)	

把work放入系统工作队列 并唤醒内核线程 使用schedule_work
函数原型

static inline bool schedule_work(struct work_struct *work)
{
	return queue_work(system_wq, work);
}

其他函数:

create_workqueue	
在Linux系统中已经有了现成的system_wq等工作队列,
你当然也可以自己调用create_workqueue创建工作队列,
对于SMP系统,这个工作队列会有多个内核线程与它对应,
创建工作队列时,内核会帮这个工作队列创建多个内核线程

#define create_workqueue(name)						\
	alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))


create_singlethread_workqueue	
如果想只有一个内核线程与工作队列对应,
可以用本函数创建工作队列,
创建工作队列时,内核会帮这个工作队列创建一个内核线程
#define create_singlethread_workqueue(name)				\
	alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)

销毁工作队列
void destroy_workqueue(struct workqueue_struct *wq);	

调度执行一个具体的work,执行的work将会被挂入Linux系统提供的工作队列
bool schedule_work(struct work_struct *work);

延迟一定时间去执行一个具体的任务,
功能与schedule_work类似,多了一个延迟时间
bool schedule_delayed_work(struct delayed_work *dwork,
					 unsigned long delay);

跟schedule_work类似,schedule_work是在系统默认的工作队列上执行一个work,
queue_work需要自己指定工作队列
bool queue_work_on(int cpu, struct workqueue_struct *wq,
			struct work_struct *work);

跟schedule_delayed_work类似,
schedule_delayed_work是在系统默认的工作队列上执行一个work,
queue_delayed_work需要自己指定工作队列
bool queue_delayed_work(struct workqueue_struct *wq,
				      struct delayed_work *dwork,
				      unsigned long delay);
				      
等待一个work执行完毕,
如果这个work已经被放入队列,那么本函数等它执行完毕,并且返回true;
如果这个work已经执行完华才调用本函数,那么直接返回false
bool flush_work(struct work_struct *work);

如果这个delayed_work已经被放入队列,那么本函数等它执行完毕,并且返回true;
如果这个delayed_work已经执行完华才调用本函数,那么直接返回false
等待一个delayed_work执行完毕,
bool flush_delayed_work(struct delayed_work *dwork);

  • 中断线程化处理 直接使用线程来处理中断
    在注册中断函数时直接使用request_threaded_irq 注册成一个线程化的中断
    函数原型:
/*
@Param irq 中断号
@Param handler 中断上半部函数(可以为空) 为空的话自动唤醒thread_fn
@Param thread_fn 线程化的函数 当handler的返回值为IRQ_HANDLED表示中断处理完毕, 如果返回 IRQ_WAKE_THREAD,则唤醒线程
@Param name 随便写
@Param dev_id 传入handler thread_fn的参数
*/
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
			 irq_handler_t thread_fn, unsigned long irqflags,
			 const char *devname, void *dev_id)

修改设备树
在根节点下添加一个按键的输入节点作为中断源

key{
	compatible = "jzy,gpio_key";//指定驱动
	pinctrl-nams = "default";//指定pinctrl
	pinctrl-0 = <&pinctrl_key>; //需要根据引脚添加 参考pinctrl章节
	key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH
					&gpio1 14 GPIO_ACTIVE_LOW>;//添加gpioctrl
	status = "okay";
	interrupt-parent = <&gpio1>;//指定中断控制器
	interrupts = <18 IRQ_TYPE_DEGE_BOTH>, 
					<14 IRQ_TYPE_DEGE_BOTH>;//指定中断资源 中断号  触发类型
};
**编写驱动**

```c
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>



struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod; //gpio描述符
	int flag;  //保存有效状态      	 高电平有效/低电平有效
	int irq;				 //中断号
	struct tasklet_struct tasklet;
	struct work_struct work;
} ;

static struct gpio_key *gpio_key_array;//动态申请按键数组

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	val = gpiod_get_value(gpio_key->gpiod);
	
	tasklet_schedule(&gpio_key->tasklet);
	schedule_work(&gpio_key->work);
	
	return IRQ_HANDLED;
}


static void key_tasklet_func(unsigned long data)
{
	/* data ==> gpio */
	struct gpio_key *gpio_key = data;
	int val;
	int key;

	val = gpiod_get_value(gpio_key->gpiod);


	printk("key_tasklet_func key %d %d\n", gpio_key->gpio, val);
}

static void key_work_func(struct work_struct *work)
{
//这个宏可以通过成员变量推算出结构体首地址
	struct gpio_key *gpio_key = container_of(work, struct gpio_key, work);
	int val;

	val = gpiod_get_value(gpio_key->gpiod);

	printk("key_work_func: the process is %s pid %d\n",current->comm, current->pid);	
	printk("key_work_func key %d %d\n", gpio_key->gpio, val);
}

static irqreturn_t gpio_key_thread_func(int irq, void *data)
{
	struct gpio_key *gpio_key = data;
	int val;

	val = gpiod_get_value(gpio_key->gpiod);

	printk("gpio_key_thread_func: the process is %s pid %d\n",current->comm, current->pid);	
	printk("gpio_key_thread_func key %d %d\n", gpio_key->gpio, val);
	
	return IRQ_HANDLED;
}



/* 1. 从platform_device获得GPIO
 * 2. gpio=>irq
 * 3. request_irq
 */
static int gpio_key_probe(struct platform_device *pdev)
{
	int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;
	enum of_gpio_flags flag;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	count = of_gpio_count(node); //从设备树的gpio属性获取个数
	if (!count)
	{
		printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
		return -1;
	}

	gpio_key_array = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL); //动态申请空间
	for (i = 0; i < count; i++)
	{
		gpio_key_array[i].gpio = of_get_gpio_flags(node, i, &flag); //从节点获取gpio 以及flag
		if (gpio_key_array[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_key_array[i].gpiod = gpio_to_desc(gpio_key_array[i].gpio);
		gpio_key_array[i].flag = flag & OF_GPIO_ACTIVE_LOW; 		  //是否为有效值, 低电平为有效值
		gpio_key_array[i].irq  = gpio_to_irq(gpio_key_array[i].gpio); //获取中断号


		//初始化tasklet
		tasklet_init(&gpio_key_array[i].tasklet, key_tasklet_func, &gpio_key_array[i]);

		//初始化work
		INIT_WORK(&gpio_key_array[i].work, key_work_func);
	}

	for (i = 0; i < count; i++)
	{
	//注册中断函数
//		err = request_irq(gpio_key_array[i].irq, gpio_key_isr, 
//						IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 
//						"gpio_key", &gpio_key_array[i]);
	//注册线程化中断
		
		err = request_threaded_irq(gpio_keys_100ask[i].irq, gpio_key_isr, gpio_key_thread_func, 
									IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", 
									&gpio_keys_100ask[i]);
	}
        
    return 0;
    
}

static int gpio_key_remove(struct platform_device *pdev)
{
	//int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;

	count = of_gpio_count(node);
	for (i = 0; i < count; i++)
	{
		free_irq(gpio_key_array[i].irq, &gpio_key_array[i]);
	}
	kfree(gpio_key_array);
    return 0;
}


static const struct of_device_id match_keys[] = {
    { .compatible = "zjy,gpio_key" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "gpio_key",
        .of_match_table = match_keys,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&gpio_keys_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit gpio_key_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");







Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐