Linux interrupt programming

Linux interrupt programming

  Interruption: It refers to the occurrence of some abnormal event during the running of the CPU. The CPU needs to suspend the current work first, and then process the newly generated abnormal event. After processing, it returns to the suspended event and continues to execute. For example, we are using a mobile phone for WeChat video chat, and suddenly someone calls. At this time, the mobile phone handles the incoming call by ringing the ringtone to notify the user that the call is coming.
  Interruption is to deal with events that may occur in the future. Interruption events are also called abnormal events. With interrupt processing, CPU processing efficiency can be greatly improved.
  In single-chip microcomputers, we also often use interrupts to handle some emergency events, helping us to quickly respond to some real-time events. Therefore, when we write the interrupt service function, the code should be as concise as possible, and the infinite loop must not be dealt with. If there are many things to be processed, the flag bit should be set in the interrupt, and then the logic code should be put into the main function to implement.
  In the Linux kernel, we generally divide interrupts into a top half and a bottom half. The top half is mainly to process short-time-consuming codes (like setting flags in a microcontroller), and start the bottom half of the codes; the bottom half is mainly to process time-consuming codes and complete event processing after interrupt response.

1. External interrupt under Linux

  To use an external interrupt, you need to complete the configuration of the three elements of the interrupt: interrupt number (irq), interrupt service function, interrupt trigger mode (level trigger, edge trigger).

1.1 Related interface functions

  • Get the interrupt number gpio_to_irq

  A convenient function is provided in the Linux kernel to obtain the pin interrupt number.

int gpio_to_irq(unsigned gpio)
function function: get the interrupt number
return value: successfully return the interrupt number irq corresponding to GPIO

  • Register interrupt request_irq

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
function function: register interrupt
formal parameters: irq --interrupt number, return value of gpio_to_irq function.
    handler -- interrupt service function.
     Service function prototype: typedef irqreturn_t (*irq_handler_t)(int, void *);
    flags -- interrupt triggering method.
     #define IRQF_TRIGGER_RISING 0x00000001 //rising edge
     #define IRQF_TRIGGER_FALLING 0x00000002 //rising edge
     #define IRQF_TRIGGER_HIGH 0x00000004 //high level
    #define IRQF_TRIGGER_LOW 0x00000008 // Low level

    #define IRQF_SHARED 0x00000080 //Shared interrupt
   name -- interrupt registration flag.
   dev -- the parameter passed to the interrupt service function.
Return value: 0 is returned on success, and other values ​​are returned on failure.

  • interrupt service function

typedef irqreturn_t (*irq_handler_t)(int, void *);
Function function: interrupt service function
formal parameters: the first parameter is the interrupt number; the second parameter is the parameter passed in by the registration function dev
return value:
   enum irqreturn {     IRQ_NONE = (0 << 0), //If it is not the interrupt, return this value, only use     IRQ_HANDLED in the shared interrupt = (1 << 0), //Correctly execute the interrupt program to return this value, commonly used     IRQ_WAKE_THREAD = (1 < < 1), // means to wake up the interrupt handler thread   };



  Note: irqreturn_t (*irq_handler_t)(int, void *); functions with sleep cannot appear in the function, such as msleep function; this function must return a value.

  • unregister free_irq

free_irq(unsigned int irq, void *dev_id)
function function: logout interrupt
parameter: irq --interrupt number, return value of gpio_to_irq function.
   dev -- the parameter passed to the interrupt service function. It needs to be the same as when registering

2. Work Queue

  The interrupt processing function is divided into the top half of the interrupt and the bottom half of the interrupt. The implementation of the top half of the code is the interrupt service function, while the bottom half of the code can be completed by the work queue.

2.1 Introduction to Work Queue

  In the operating system, if we need to process a job, we often need to create a task to join the kernel's scheduling queue. A task corresponds to a processing function. If you want to process different transactions, you need to create multiple different tasks. Tasks are the basic unit of CPU scheduling. The larger the number of tasks, the higher the scheduling cost. The workqueue mechanism simplifies the basic task creation and processing mechanism. A workqueue corresponds to an entity task task processing. Multiple work entities can be mounted in the work queue, and each work can correspond to a different work processing function. That is, the user only needs to create one workqueue, and then multiple workqueues with different processing functions can be completed.
   The work queue also has a mechanism for postponing the execution of the work. The work queue can postpone the work and hand it over to a kernel thread for execution. That is to say, the second half can be executed in the process context. The most important thing is that the work queue allows to be rescheduled or even sleep.
  The processing of the workqueue depends on the task task. A workqueue queue will create associated tasks, a workqueue will mount multiple jobs for processing, and each job has a job processing function. When the workqueue is scheduled, that is, its associated task is run, during each task scheduling period, a work will be taken from the work queue in order for processing. When the workqueue module is initialized, a system default work queue will be created, and users can add work to the queue for execution as needed.

2.2 Work related function interface

  • Work structure struct work_struct
#include <linux/workqueue.h>
struct work_struct {
    
    
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;  /*工作处理函数*/
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

  In the work structure, the member we need to care about is the work processing function: work_func_t func , in simple terms, a work will correspond to a processing function. The prototype of the job processing function is as follows:

#include <linux/workqueue.h>
typedef void (*work_func_t)(struct work_struct *work);
  • Initialize work INIT_WORK

#define INIT_WORK(_work, _func)
function function: initialize the work, realize formal
parameters in the form of macro : _work --work structure pointer
   _func --work processing function

  • work schedule schedule_work

int schedule_work(struct work_struct *work)

2.3 Work Queue Usage Steps

  1. Define the work structure struct work_struct, and initialize the work INIT_WORK;
  2. Write work processing function void (*work_func_t)(struct work_struct *work);
  3. Scheduling work in a suitable place (usually in the top half of the interrupt);

2.4 Work Queue Usage Example

  Let’s take the button as an example to realize the button detection in the interrupt mode, process the bottom half of the code through the work queue, and realize the device registration with the miscellaneous device framework.
insert image description here
insert image description here

  • K1 – GPX3_2
  • K2 --GPX3_3
  • K3 --GPX3_4
  • K4 --GPX3_5
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/workqueue.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
struct work_struct key_work;/*工作结构体*/
struct _KEY
{
    
    
	unsigned int gpio;/*按键引脚*/
	char name[20];/*按键名*/
	int irq;/*中断号*/
	int key_num;/*按键编号*/
};
static struct _KEY KEY_GPIO_PIN[]=
{
    
    
	{
    
    EXYNOS4_GPX3(2),"key1",0,1},
	{
    
    EXYNOS4_GPX3(3),"key2",0,2},
	{
    
    EXYNOS4_GPX3(4),"key3",0,3},	
	{
    
    EXYNOS4_GPX3(5),"key4",0,4},	
};
static struct _KEY *key_p;
static unsigned int key_val;
/*工作服务函数*/
void key_work_func(struct work_struct *work)
{
    
    
	msleep(30);/*按键消抖*/
	if(gpio_get_value(key_p->gpio)==0)
	{
    
    
		printk(" key%d 按下\n",key_p->key_num);
	}
	else 
	{
    
    
		printk(" key%d 松开\n",key_p->key_num);
	}
	key_val=key_p->key_num;
}
/*中断服务函数*/
static irqreturn_t key_irq_handler(int irq, void *dev)
{
    
    
	key_p=(struct _KEY *)dev;
	schedule_work(&key_work);/*调度工作*/
	return IRQ_HANDLED;/*中断正常处理*/
}
static int key_open(struct inode *inode, struct file *file)
{
    
    
	printk("设备打开成功\n");
	return 0;
}
static ssize_t key_read(struct file *file, char __user *buf, size_t cnt, loff_t *seek)
{
    
    
	int res=copy_to_user(buf,&key_val, 4);
	key_val=0;
	return 4-res;
}
static int key_release(struct inode *inode, struct file *file)
{
    
    
	printk("设备关闭成功\n");
	return 0;
}
/*文件操作集合*/
static struct file_operations key_fops=
{
    
    
	.owner= THIS_MODULE, /*当前模块文件操作集合所有者*/
	.open=key_open,/*open函数接口*/
	.read=key_read,/*read函数接口*/
	.release=key_release,/*close函数接口*/
};
/*
字符设备注册:主设备+次设备号
主设备  --用来区分类(杂项设备、输入设备)
次设备号  --对应哪个设备
杂项设备的主设备号固定为:10
*/
static struct miscdevice key_miscdev = {
    
    
	.minor	= MISC_DYNAMIC_MINOR,/*次设备号255由内核分配*/
	.name	= "tiny4412_key",/*设备节点名字,会在/dev下生成*/
	.fops	= &key_fops,/**/
};

static int __init tiny4412_key_module_init(void)
{
    
    
	int i=0;
	int res;
    printk("hello,驱动注册成功\n");
	/*初始化工作*/
	INIT_WORK(&key_work,key_work_func);
	/*注册中断*/
	for(i=0;i<sizeof(KEY_GPIO_PIN)/sizeof(KEY_GPIO_PIN[0]);i++)
	{
    
    
		KEY_GPIO_PIN[i].irq=gpio_to_irq(KEY_GPIO_PIN[i].gpio);/*获取中断号*/
		res=request_irq(KEY_GPIO_PIN[i].irq,key_irq_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
												KEY_GPIO_PIN[i].name,&KEY_GPIO_PIN[i]);
		if(res)
		{
    
    
			printk("%d中断注册失败\n",i);
			return -1;
		}
	}
	/*注册杂项设备*/
	misc_register(&key_miscdev);
    return 0;
}

static void __exit tiny4412_key_module_cleanup(void)
{
    
    
	int i=0;
	/*注销中断*/
	for(i=0;i<sizeof(KEY_GPIO_PIN)/sizeof(KEY_GPIO_PIN[0]);i++)
	{
    
    
		free_irq(KEY_GPIO_PIN[i].irq,&KEY_GPIO_PIN[i]);
	}
	/*注销杂项设备*/
	misc_deregister(&key_miscdev);
	printk("Good-bye, 驱动注销成功\n");
}

module_init(tiny4412_key_module_init);/*驱动入口函数:注册驱动时调用*/
module_exit(tiny4412_key_module_cleanup);/*驱动出口函数:注销驱动时调用*/

MODULE_LICENSE("GPL");/*驱动许可证*/

insert image description here

Guess you like

Origin blog.csdn.net/weixin_44453694/article/details/126812705