DeviceDriver (6): Interrupt

One: Introduction to Linux interrupts

      The Linux kernel provides a complete interrupt framework. There is no need to configure registers, enable IRQ, etc., just apply for interrupts, and then register the interrupt processing function. Each interrupt has an interrupt number, and different interrupts can be distinguished by the interrupt number. Use an int variable to represent the interrupt number in the Linux kernel.

Two: interrupt API

1. Request interrupt request_irq

int request_irq(unsigned int irq,             //中断号
               irq_handler_t handler,         //中断处理函数
               unsigned long flags,           //中断标志
               const char *name,              //中断名字
               void *dev)                     //一般设置为设备结构体,传递给中断处理函数irq_handler_t的第二个参数

Interrupt flag:

2. Release the interrupt free_irq

void free_irq(unsigned int irq, void *dev_id)

3. Interrupt handling function

irqreturn_t (*irq_handler_t)(int, void *);

4. Interrupt enable and disable

(1) The disable_irq function does not return until the currently executing interrupt processing function is executed. Therefore, the user needs to ensure that no new interrupts will be generated, and that all interrupt processing functions that have already started execution have all exited. The disable_irq_nosync function returns immediately after the call, without waiting for the completion of the current interrupt handler.

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
void disable_irq_nosync(unsigned int irq)

(2) Global interrupt enable and disable

local_irq_enable();
local_irq_disable();

local_irq_save(flags)
local_irq_restore(flags)

5. Get the interrupt number

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

This function can extract the corresponding device number from the "interupts" attribute of the device tree file.

dev: device node

index: Index number, which specifies the information to be obtained.

6. Get the interrupt number corresponding to the GPIO

int gpio_to_irq(unsigned int gpio)

gpio: the gpio number to be obtained.

The return value is the interrupt number corresponding to gpio.

Three: Interrupt the upper and lower half

1. The interrupt handling process is divided into two parts: the upper half and the lower half

Upper part: interrupt processing function, the processing process is fast, and the processing that does not take a long time can be completed in the upper part.

Lower part: If the interrupt handling process is time-consuming, then extract these time-consuming codes and give them to the lower part to execute, so that the interrupt handling function will be fast in and out.

2. Interrupt design ideas:

(1) If the content to be processed does not want to be interrupted by other interrupts, it can be placed in the upper half.

(2) If the task to be processed is time-sensitive, you can put it in the upper half.

(3) If the task to be processed is related to hardware, it can be placed in the upper half.

(4) Other tasks are given priority to the lower half.

3. The lower half of the mechanism

(1) Soft interrupt

The Linux kernel uses the structure softirq_action to represent soft interrupts:

struct softirq_action
{
	void	(*action)(struct softirq_action *);
};

10 soft interrupts are defined in the Linux kernel kernel/softirq.c file:

struct softirq_action softirq_vec[NR_SOFTIRQS]

enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	BLOCK_IOPOLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};

To use soft interrupt, you must first register the corresponding interrupt processing function, because all CPUs can access the soft interrupt service function. Although the interrupt trigger mechanism and control mechanism of different CPUs are different, the access is all the action defined in softirq_vec function.

Soft interrupt registration function:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

After the soft interrupt is registered, it needs to be triggered:

void raise_softirq(unsigned int nr)

The soft interrupt must be registered statically at compile time. The Linux kernel uses the softirq_init function to initialize the soft interrupt:

void __init softirq_init(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;
	}

	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

(2)tasklet

Taskled is another lower half mechanism realized by soft interrupt.

Tasklet structure definition:

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

Initialize tasklet_init:

static inline void tasklet_init(struct tasklet_struct *tasklet,
                                void (*func)(unsigned long),
                                unsigned long data)

Macro definition: name means tastlet, func means processing function, data means func function parameter

DECLARE_TASKLET(name, func, data)

In the upper part, the tasklet_schedule function is called in the interrupt handler function to make the tasklet run at the appropriate time:

void tasklet_schedule(struct tasklet_struct *t)

Example:

/* 定义 taselet */
struct tasklet_struct testtasklet;

/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
    /* tasklet 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
    ......
    /* 调度 tasklet */
    tasklet_schedule(&testtasklet);
    ......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
    ......
    /* 初始化 tasklet */
    tasklet_init(&testtasklet, testtasklet_func, data);
    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    ......
}

(3) Work queue

       The work queue is another way of execution in the lower half. The work queue is executed in the context of the process. The work queue transfers the postponement work to a kernel thread for execution. Because the work queue works in the context of the process, the work queue allows sleep or rescheduling. . Therefore, if the postponement work can sleep, you can choose the work queue, otherwise you can only choose soft interrupt or tasklet.

In LInux, work_struct is used to represent a work:

struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func; /* 工作队列处理函数 */
};

The work queue is represented by the workqueue_struct structure:

struct workqueue_struct {
    struct list_head pwqs;
    struct list_head list;
    struct mutex mutex;
    int work_color;
    int flush_color;
    atomic_t nr_pwqs_to_flush;
    struct wq_flusher *first_flusher;
    struct list_head flusher_queue;
    struct list_head flusher_overflow;
    struct list_head maydays;
    struct worker *rescuer;
    int nr_drainers;
    int saved_max_active;
    struct workqueue_attrs *unbound_attrs;
    struct pool_workqueue *dfl_pwq;
    char name[WQ_NAME_LEN];
    struct rcu_head rcu;
    unsigned int flags ____cacheline_aligned;
    struct pool_workqueue __percpu *cpu_pwqs;
    struct pool_workqueue __rcu *numa_pwq_tbl[];
};

The LInux kernel uses worker threads to process various jobs in the work queue, and the Linux kernel uses the worker structure to represent the worker threads:

struct worker {
	/* on idle list while idle, on busy hash table while busy */
	union {
		struct list_head	entry;	/* L: while idle */
		struct hlist_node	hentry;	/* L: while busy */
	};

	struct work_struct	*current_work;	/* L: work being processed */
	work_func_t		current_func;	/* L: current_work's fn */
	struct pool_workqueue	*current_pwq; /* L: current_work's pwq */
	bool			desc_valid;	/* ->desc is valid */
	struct list_head	scheduled;	/* L: scheduled works */

	/* 64 bytes boundary on 64bit, 32 on 32bit */

	struct task_struct	*task;		/* I: worker task */
	struct worker_pool	*pool;		/* I: the associated pool */
						/* L: for rescuers */
	struct list_head	node;		/* A: anchored at pool->workers */
						/* A: runs through worker->node */

	unsigned long		last_active;	/* L: last active timestamp */
	unsigned int		flags;		/* X: flags */
	int			id;		/* I: worker id */

	/*
	 * Opaque string set with work_set_desc().  Printed out with task
	 * dump for debugging - WARN, BUG, panic or sysrq.
	 */
	char			desc[WORKER_DESC_LEN];

	/* used only by rescuers to point to the target workqueue */
	struct workqueue_struct	*rescue_wq;	/* I: the workqueue to rescue */
};

In actual development, you only need to define work_struct, and then use the INIT_WORK macro to initialize: work represents the initialization work, func represents the processing function corresponding to the work

#define INIT_WORK(_work, _func)

You can also use the DECLARE_WORK macro to complete the creation and initialization of the work at one time: n means work_struct, f means processing function

#define DECLARE_WORK(n, f)

Like tasklet, work needs to be scheduled to run. The work scheduling function is schedule_work:

bool schedule_work(struct work_struct *work)

Example:

/* 定义工作(work) */
struct work_struct testwork;

/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
    /* work 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
    ......
    /* 调度 work */
    schedule_work(&testwork);
    ......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
    ......
    /* 初始化 work */
    INIT_WORK(&testwork, testwork_func_t);
    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    ......
}

Four: Device tree file interrupt configuration

       If the Linux kernel uses the device tree, then it needs to set the interrupt information in the device tree file. The Linux kernel configures the interrupt by reading the interrupt attribute information in the device tree file. The interrupt control node of imx6ull is intc, and the relevant driver files can be found in the kernel according to the compatible attribute.

intc: interrupt-controller@00a01000 {
	compatible = "arm,cortex-a7-gic";
	#interrupt-cells = <3>;
	interrupt-controller;
	reg = <0x00a01000 0x1000>,
	      <0x00a02000 0x100>;
};

1. The attribute "#interrupt-cells" indicates the cell size of the device under the interrupt controller. For the GIC of the ARM processor, there are a total of 3 cells:

(1) Interrupt type, 0 means SPI interrupt, 1 means PPI interrupt.

(2) Interrupt number. For SPI interrupt, the range of interrupt number is 0~987, for PPI interrupt, the range of interrupt number is 0~15

(3) Flag, bit[3:0] represents the interrupt trigger type, 1 represents the rising edge trigger, 2 represents the falling edge trigger, 4 represents the high level trigger, and 8 represents the low level trigger. bit[15:8] is the CPU mask of the PPI interrupt.

2. The attribute "interrupt-controller" node is empty, indicating that the current node is an interrupt controller.

Example: gpio1 as an interrupt controller

gpio1: gpio@0209c000 {
	compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	reg = <0x0209c000 0x4000>;
	interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
	gpio-controller;
	#gpio-cells = <2>;
	interrupt-controller;
	#interrupt-cells = <2>;
};

Specific device: GPIO1_IO18

key {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "my-key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
	interrupt-parent = <&gpio1>;
	interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
	status = "okay";
};

3. The attribute "interrupts" specifies the interrupt number and trigger method. The specific information quantity is specified by "#interrupt-cells". When it is 2, for the example "interrupts = <18 IRQ_TYPE_EDGE_BOTH": 0 means GPIO5_IO00, IRQ_TYPE_EDGE_BOTH means edge trigger.

4. The attribute "interrupt-parent" specifies the parent interrupt, which is the interrupt controller.

 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/103761144