Break the device tree in the system

本文章参考学习阅读:
基于设备树的TQ2440的中断(1)
https://www.cnblogs.com/pengdonglin137/p/6847685.html

基于设备树的TQ2440的中断(2)
https://www.cnblogs.com/pengdonglin137/p/6848851.html

基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
http://www.cnblogs.com/pengdonglin137/p/6349209.html

linux kernel的中断子系统之:GIC代码分析
http://www.wowotech.net/irq_subsystem/interrupt_subsystem_architecture.html

本篇文章所用内核源码:
链接:https://pan.baidu.com/s/14WfehP9gF2GLrrCdxWG7yg 
提取码:gymq 
--来自百度网盘超级会员V6的分享

1. The introduction and processing flow of the interrupt concept

1. What is interruption?

  Interrupts are set for the single-chip microcomputer to have real-time processing of external or internal random events. In order to make it easier for everyone to understand the concept of interrupts, let me give you an example:
insert image description here
  As for being able to read this article, everyone should be a veteran of single-chip microcomputers and embedded devices, and you should understand interrupts, just give everyone a brief memory.

(1) The invention of interrupt is used to solve the parallel needs on the macro level. The macro is from the overall point of view, and parallelism means that multiple things have been completed.

(2) Microcosmic parallelism refers to true parallelism, which is accurate to every second or even every moment, and multiple things are carried out at the same time. Parallelism on the macro level is not equal to parallelism on the micro level. Sometimes it is parallel on the macro level and serial on the micro level.

(3) In the example, a person is watching a movie. When the courier arrives, he pauses the movie and runs to receive the courier. After receiving the courier, he continues to come back to watch the movie. This example is a macro parallel and a micro serial. In the example, a person is equivalent to 1 CPU (that is, a single-core CPU) in the SoC. This CPU cannot receive express delivery when watching movies, and cannot watch movies when receiving express delivery (that is, it cannot be truly parallel). A single-core CPU is serial from a microscopic point of view, but because the CPU is fast, it can be parallelized from a macroscopic point of view.

(4) In the above example, most of the time is spent watching movies, and a small amount of time is used to receive express delivery. Then, analogous to the CPU, watching movies should be a routine task of the CPU, while receiving express delivery should be an interrupt routine. That is to say, the CPU has been watching movies all the time. When the courier arrives (an interruption occurs), the courier (similar to the interrupt source) will call someone to receive the courier (the interrupt source will trigger the interrupt and notify the CPU to handle the interrupt). After the person receives the call (the CPU receives the interrupt signal), he will tentatively schedule the movie (the scene where the CPU saves the routine tasks) and run to receive the courier (the CPU executes the interrupt handler ISR to process the interrupt), and after receiving the courier (the ISR is executed) come back and continue Watching a movie (the scene where the CPU resumes the routine task, and continues to execute the routine task)

(5) Why do you need to interrupt? Because a single-core CPU cannot actually be parallelized, but through the interrupt mechanism, false parallelism can be achieved (parallel on the macro level, but actually serial on the micro level).

2. Processing flow

(1)保存现场
(2)处理异常
(3)恢复现场

3. SoC's implementation mechanism for interrupts: exception vector table

(1) The exception vector table is a specific definition of some specific addresses in the CPU . When an interrupt occurs, the interrupt must find a way to notify the CPU to handle the interrupt, how to do it? This depends on the exception vector table .

(2) During the design of the CPU, some specific addresses in the CPU are defined in advance as the entry addresses of specific exceptions (for example, if the address 0x00000000 is defined as the reset exception vector address, the CPU will automatically jump to the address 0x00000000 to execute instructions when a reset exception occurs. .For example, if the exception vector address corresponding to the external interrupt is 0x30000008, after the external interrupt occurs, the CPU will automatically jump to the address 0x30000008 to execute the instruction)

(3) The above is the support for the exception vector table during CPU hardware design, and software support is required. The hardware has already decided which address the CPU automatically jumps to the PC to execute when an exception occurs. What the software needs to do is to fill in the first address of the code that handles the exception into the exception vector address.

(4) The relative position of each vector in the exception vector table is fixed, but their starting addresses are not fixed, and various SoCs can be different, and in complex ARM, users can also set the exception vector table by software base address.

(5) Extended to CPUs of all architectures: CPU implementation interrupts of all architectures (such as 51 single-chip microcomputers, PIC single-chip microcomputers) are realized through the exception vector table , and this mechanism is unchanged; however, the structure and structure of different CPU exception vector tables The location is different.
insert image description here

4. The difference and connection between exception and interruption

(1) For SoC, resets, soft interrupts, interrupts, fast interrupts, instruction fetch exceptions, data exceptions, etc., are collectively called exceptions. So: interrupt is actually a kind of exception.

(2) The definition of exception is an emergency, which interrupts the normal routine business of the CPU, and the CPU has to jump to the exception vector table to execute the exception handling program; interrupt is a kind of exception, and generally refers to the internal Interruption of SoC routine business generated by peripherals, or external interrupts (interrupts transmitted back from GPIO pins of SoC).

  Interrupts can be masked, but exceptions cannot

2. A brief description of the framework and code flow of Linux for interrupt processing

a. Abnormal vector entry: arch\arm\kernel\entry-armv.S

    .section .vectors, "ax", %progbits
.L__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)  pc, .L__vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq
    W(b)    vector_fiq

b. Interrupt vector: vector_irq

/*
 * Interrupt dispatcher
 */
    vector_stub irq, IRQ_MODE, 4   // 相当于 vector_irq: ..., 
                                   // 它会根据SPSR寄存器的值,
                                   // 判断被中断时CPU是处于USR状态还是SVC状态, 
                                   // 然后调用下面的__irq_usr或__irq_svc

    .long   __irq_usr               @  0  (USR_26 / USR_32)
    .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
    .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
    .long   __irq_svc               @  3  (SVC_26 / SVC_32)
    .long   __irq_invalid           @  4
    .long   __irq_invalid           @  5
    .long   __irq_invalid           @  6
    .long   __irq_invalid           @  7
    .long   __irq_invalid           @  8
    .long   __irq_invalid           @  9
    .long   __irq_invalid           @  a
    .long   __irq_invalid           @  b
    .long   __irq_invalid           @  c
    .long   __irq_invalid           @  d
    .long   __irq_invalid           @  e
    .long   __irq_invalid           @  f

c. __irq_usr/__irq_svc

__irq_usr:
	usr_entry
	kuser_cmpxchg_check
	irq_handler
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq
 UNWIND(.fnend		)
ENDPROC(__irq_usr)

	.ltorg

	.align	5
__irq_svc:
	svc_entry
	irq_handler

#ifdef CONFIG_PREEMPT
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	ldr	r0, [tsk, #TI_FLAGS]		@ get flags
	teq	r8, #0				@ if preempt count != 0
	movne	r0, #0				@ force flags to 0
	tst	r0, #_TIF_NEED_RESCHED
	blne	svc_preempt
#endif

	svc_exit r5, irq = 1			@ return from exception
 UNWIND(.fnend		)
ENDPROC(__irq_svc)

	.ltorg

The processing of these two functions is similar:
  save the scene
  and call irq_handler
  to restore the scene

d. irq_handler: will call the C function handle_arch_irq

    .macro  irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
    ldr r1, =handle_arch_irq
    mov r0, sp
    badr    lr, 9997f
    ldr pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm

e. The process of handle_arch_irq

#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;
#endif

(1)读取寄存器获得中断信息: hwirq(hardware irq)

(2)把hwirq(硬件中断号)转换为virq(虚拟中断号),一个硬件中断号可能会对应多个
虚拟中断号,因为存在多个硬件共享一个硬件中断号,但具有不同的虚拟中断号。通过调用
irq_desc[virq].handle_irq实现二者的转换
   
对于S3C2440, s3c24xx_handle_irq 是用于处理中断的C语言入口函数

insert image description here
Interrupt processing flow:

假设中断结构如下:
sub interrupt controller(子中断控制器) ---> interrupt controller(中断控制器) ---> cpu

发生中断时,
cpu跳到"vector_irq", 保存现场, 调用C函数handle_arch_irq

handle_arch_irq:
a. 读 interrupt controller, 得到hwirq
b. 根据hwirq得到virq
c. 调用 irq_desc[virq].handle_irq

如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
a. 取出irq_desc[virq].action 链表 中的每一个handler, 执行它
b. 使用irq_desc[virq].irq_data.chip的函数清中断

如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作:
a. 读 sub interrupt controller, 得到hwirq
b. 根据hwirq得到virq
c. 调用 irq_desc[virq].handle_irq

insert image description here

/**
 * struct irq_desc - interrupt descriptor
 * @irq_common_data:	per irq and chip data passed down to chip functions
 * @kstat_irqs:		irq stats per cpu
 * @handle_irq:		highlevel irq-events handler
 * @preflow_handler:	handler called before the flow handler (currently used by sparc)
 * @action:		the irq action chain
 * @status:		status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:		disable-depth, for nested irq_disable() calls
 * @wake_depth:		enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:		stats field to detect stalled irqs
 * @last_unhandled:	aging timer for unhandled count
 * @irqs_unhandled:	stats field for spurious unhandled interrupts
 * @threads_handled:	stats field for deferred spurious detection of threaded handlers
 * @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
 * @lock:		locking for SMP
 * @affinity_hint:	hint to user space for preferred irq affinity
 * @affinity_notify:	context for notification of affinity changes
 * @pending_mask:	pending rebalanced interrupts
 * @threads_oneshot:	bitfield to handle shared oneshot threads
 * @threads_active:	number of irqaction threads currently running
 * @wait_for_threads:	wait queue for sync_irq to wait for threaded handlers
 * @nr_actions:		number of installed actions on this descriptor
 * @no_suspend_depth:	number of irqactions on a irq descriptor with
 *			IRQF_NO_SUSPEND set
 * @force_resume_depth:	number of irqactions on a irq descriptor with
 *			IRQF_FORCE_RESUME set
 * @rcu:		rcu head for delayed free
 * @kobj:		kobject used to represent this struct in sysfs
 * @request_mutex:	mutex to protect request/free before locking desc->lock
 * @dir:		/proc/irq/ procfs entry
 * @debugfs_file:	dentry for the debugfs file
 * @name:		flow handler name for /proc/interrupts output
 */
struct irq_desc {
    
    
	struct irq_common_data	irq_common_data;
	struct irq_data		irq_data;
	unsigned int __percpu	*kstat_irqs;
	irq_flow_handler_t	handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
	irq_preflow_handler_t	preflow_handler;
#endif
	struct irqaction	*action;	/* IRQ action list */
	unsigned int		status_use_accessors;
	unsigned int		core_internal_state__do_not_mess_with_it;
	unsigned int		depth;		/* nested irq disables */
	unsigned int		wake_depth;	/* nested wake enables */
	unsigned int		irq_count;	/* For detecting broken IRQs */
	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
	unsigned int		irqs_unhandled;
	atomic_t		threads_handled;
	int			threads_handled_last;
	raw_spinlock_t		lock;
	struct cpumask		*percpu_enabled;
	const struct cpumask	*percpu_affinity;
#ifdef CONFIG_SMP
	const struct cpumask	*affinity_hint;
	struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
	cpumask_var_t		pending_mask;
#endif
#endif
	unsigned long		threads_oneshot;
	atomic_t		threads_active;
	wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
	unsigned int		nr_actions;
	unsigned int		no_suspend_depth;
	unsigned int		cond_suspend_depth;
	unsigned int		force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*dir;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
	struct dentry		*debugfs_file;
	const char		*dev_name;
#endif
#ifdef CONFIG_SPARSE_IRQ
	struct rcu_head		rcu;
	struct kobject		kobj;
#endif
	struct mutex		request_mutex;
	int			parent_irq;
	struct module		*owner;
	const char		*name;
} ____cacheline_internodealigned_in_smp;
typedef	void (*irq_flow_handler_t)(struct irq_desc *desc);
/**
 * struct irqaction - per interrupt action descriptor
 * @handler:	interrupt handler function
 * @name:	name of the device
 * @dev_id:	cookie to identify the device
 * @percpu_dev_id:	cookie to identify the device
 * @next:	pointer to the next irqaction for shared interrupts
 * @irq:	interrupt number
 * @flags:	flags (see IRQF_* above)
 * @thread_fn:	interrupt handler function for threaded interrupts
 * @thread:	thread pointer for threaded interrupts
 * @secondary:	pointer to secondary irqaction (force threading)
 * @thread_flags:	flags related to @thread
 * @thread_mask:	bitmask for keeping track of @thread activity
 * @dir:	pointer to the proc/irq/NN/name entry
 */
struct irqaction {
    
    
	irq_handler_t		handler;
	void			*dev_id;
	void __percpu		*percpu_dev_id;
	struct irqaction	*next;
	irq_handler_t		thread_fn;
	struct task_struct	*thread;
	struct irqaction	*secondary;
	unsigned int		irq;
	unsigned int		flags;
	unsigned long		thread_flags;
	unsigned long		thread_mask;
	const char		*name;
	struct proc_dir_entry	*dir;
} ____cacheline_internodealigned_in_smp;

insert image description here

3. The evolution of the interrupt number and irq_domain

1. The previous interrupt handling process

  In the past, the interrupt number (virq) was closely related to the hardware, but the current trend is that the interrupt number has nothing to do with the hardware, it is just a label . In the past, for each hardware interrupt (hwirq), its interrupt number (virq) was predetermined, and these interrupt numbers were generally written in a header file, such as arch\arm\mach-s3c24xx\include\mach\irqs.h
insert image description here

使用时,
a. 执行 request_irq(virq, my_handler) :
   内核根据virq可以知道对应的硬件中断, 然后去设置、使能中断等
b. 发生硬件中断时,
   内核读取硬件信息, 确定hwirq, 反算出virq,
   然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler

  How to calculate virq based on hwirq?
  There are multiple intc (interrupt controllers) on the hardware, for the same hwirq value, it will correspond to different virq, so when talking about hwirq, you should emphasize "which interrupt controller's hwirq", when describing the conversion of hwirq to virq , introduce a concept: irq_domain, domain, in this domain hwirq is converted to a certain virq

/**
 * struct irq_domain - Hardware interrupt number translation object
 * @link: Element in global irq_domain list.
 * @name: Name of interrupt domain
 * @ops: pointer to irq_domain methods
 * @host_data: private data pointer for use by owner.  Not touched by irq_domain
 *             core code.
 * @flags: host per irq_domain flags
 * @mapcount: The number of mapped interrupts
 *
 * Optional elements
 * @fwnode: Pointer to firmware node associated with the irq_domain. Pretty easy
 *          to swap it for the of_node via the irq_domain_get_of_node accessor
 * @gc: Pointer to a list of generic chips. There is a helper function for
 *      setting up one or more generic chips for interrupt controllers
 *      drivers using the generic chip library which uses this pointer.
 * @parent: Pointer to parent irq_domain to support hierarchy irq_domains
 * @debugfs_file: dentry for the domain debugfs file
 *
 * Revmap data, used internally by irq_domain
 * @revmap_direct_max_irq: The largest hwirq that can be set for controllers that
 *                         support direct mapping
 * @revmap_size: Size of the linear map table @linear_revmap[]
 * @revmap_tree: Radix map tree for hwirqs that don't fit in the linear map
 * @linear_revmap: Linear table of hwirq->virq reverse mappings
 */
struct irq_domain {
    
    
	struct list_head link;
	const char *name;
	const struct irq_domain_ops *ops;
	void *host_data;
	unsigned int flags;
	unsigned int mapcount;

	/* Optional data */
	struct fwnode_handle *fwnode;
	enum irq_domain_bus_token bus_token;
	struct irq_domain_chip_generic *gc;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
	struct dentry		*debugfs_file;
#endif

	/* reverse map data. The linear map gets appended to the irq_domain */
	irq_hw_number_t hwirq_max;
	unsigned int revmap_direct_max_irq;
	unsigned int revmap_size;
	struct radix_tree_root revmap_tree;
	struct mutex revmap_tree_mutex;
	unsigned int linear_revmap[];
};

  When there are more and more interrupt controllers and more and more interrupts, the above method (virq and hwirq fixed binding) is flawed:

a. 增加工作量, 你需要给每一个中断确定它的中断号, 写出对应的宏, 可能有成百上千个
b. 你要确保每一个硬件中断对应的中断号互不重复

  Is there any way to improve it?

a. hwirq跟virq之间不再绑定

b. 要使用某个hwirq时, 
   先在irq_desc数组中找到一个空闲项, 它的位置就是virq
   再在irq_desc[virq]中放置处理函数

2. Interrupt handling process after using the device tree

insert image description here

intc: interrupt controller
insert image description here
insert image description here
  In the new interrupt system, how to use interrupts:

a.以前是request_irq发起,
  现在是先在设备树文件中声明想使用哪一个中断(哪一个中断控制器下的哪一个中断)

b. 内核解析设备树时,
   会根据"中断控制器"确定irq_domain,
   根据"哪一个中断"确定hwirq, 
   然后在irq_desc数组中找出一个空闲项, 它的位置就是virq
   并且把virq和hwirq的关系保存在irq_domain中: 
   		irq_domain.linear_revmap[hwirq] = virq;

c. 驱动程序 request_irq(virq, my_handler)

d. 发生硬件中断时,
	内核读取硬件信息, 确定hwirq, 确定 virq =  irq_domain.linear_revmap[hwirq];
然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler

假设要使用子中断控制器(subintc)的n号中断, 它发生时会导致父中断控制器(intc)的m号
中断:
	1. 设备树表明要使用<subintc n>
   		subintc表示要使用<intc m>
	2. 解析设备树时,
  		会为<subintc n>找到空闲项 irq_desc[virq'], sub irq_domain.linear_revmap[n] = virq';
   
   		会为<intc m>   找到空闲项 irq_desc[virq], irq_domain.linear_revmap[m] = virq;
   		并且设置它的handle_irq为某个分析函数demux_func

	3. 驱动程序 request_irq(virq', my_handler)

d. 发生硬件中断时,
	内核读取intc硬件信息, 确定hwirq = m, 确定 virq =  irq_domain.linear_revmap[m];
然后调用 irq_desc[m].handle_irq, 即demux_func

e. demux_func:
	读取sub intc硬件信息, 确定hwirq = n, 确定 virq' =  sub irq_domain.linear_revmap[n];
然后调用 irq_desc[n].handle_irq, 即my_handler

4. Example_Using device tree to describe interrupt experience on S3C2440

a. 某个设备要使用中断, 需要在设备树中描述中断, 如何描述?
   它要用哪一个中断? 这个中断连接到哪一个中断控制器去?: 使用哪一个中断控制器的哪一个中断?
   
   至少有有2个属性:
   interrupts        // 表示要使用哪一个中断, 中断的触发类型等等
   interrupt-parent  // 这个中断要接到哪一个设备去? 即父中断控制器是谁
b. 上述的interrupts属性用多少个u32来表示?
   这应该由它的父中断控制器来描述,
   在父中断控制器中, 至少有2个属性:
   interrupt-controller;   // 表示自己是一个中断控制器
    #interrupt-cells       // 表示自己的子设备里应该有几个U32的数据来描述中断

  Describe the "interrupt hardware information" in the device node of the device tree, indicating the use of "which interrupt in which interrupt controller, and the interrupt trigger mode",

  The device node will be converted to platform_device, and the "interrupt hardware information" will be converted to "interrupt number", which will be stored in the "interrupt resource" of platform_device. The driver will take out the interrupt number from the "interrupt resource" of platform_device, and then request_irq can be made.

实验:
a. jz2440_irq.dts" 放入内核 arch/arm/boot/dts目录,
在内核根目录下执行:make dtbs   // 得到 arch/arm/boot/dts/jz2440_irq.dtb
使用这个jz2440_irq.dtb启动内核;

b. 编译、测试驱动:
b.1 把 buttons_drv 上传到ubuntu
b.2 编译驱动:
export  PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin

cd buttons_drv
make   // 得到 buttons.ko

b.3 编译测试程序:
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/arm/4.3.2/bin

cd buttons_drv

arm-linux-gcc -o buttons_test  buttons_test.c

b.4 测试:
insmod buttons.ko
./buttons_test &
然后按键

button.c file

#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
//#include <asm/arch/regs-gpio.h>
//#include <asm/hardware.h>
#include <linux/device.h>
//#include <mach/gpio.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-samsung.h>
#include <plat/gpio-cfg.h>

#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>

static struct class *sixthdrv_class;
static struct device	*sixthdrv_class_dev;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

static struct timer_list buttons_timer;


static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,sixth_drv_read将它清0 */
static volatile int ev_press = 0;

static struct fasync_struct *button_async;


struct pin_desc{
    
    
	unsigned int pin;
	unsigned int key_val;
	int irq;
};


/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;

struct pin_desc pins_desc[4] = {
    
    
	{
    
    S3C2410_GPF(0), 0x01},
	{
    
    S3C2410_GPF(2), 0x02},
	{
    
    S3C2410_GPG(3), 0x03},
	{
    
    S3C2410_GPG(11), 0x04},
};

static struct pin_desc *irq_pd;

//static atomic_t canopen = ATOMIC_INIT(1);     //定义原子变量并初始化为1

//static DECLARE_MUTEX(button_lock);     //定义互斥锁
struct semaphore button_lock;


/*
  * 确定按键值
  */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    
    
	/* 10ms后启动定时器 */
	irq_pd = (struct pin_desc *)dev_id;
	mod_timer(&buttons_timer, jiffies+HZ/100);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int sixth_drv_open(struct inode *inode, struct file *file)
{
    
    
	int ret;
	
#if 0	
	if (!atomic_dec_and_test(&canopen))
	{
    
    
		atomic_inc(&canopen);
		return -EBUSY;
	}
#endif		

	if (file->f_flags & O_NONBLOCK)
	{
    
    
		if (down_trylock(&button_lock))
			return -EBUSY;
	}
	else
	{
    
    
		/* 获取信号量 */
		down(&button_lock);
	}

	/* 配置GPF0,2为输入引脚 */
	/* 配置GPG3,11为输入引脚 */
	ret = request_irq(pins_desc[0].irq,  buttons_irq, 0, "S2", &pins_desc[0]);
	if (ret) {
    
    
		printk("reqeust_irq %d for EINT0 err : %d!\n", pins_desc[0].irq, ret);
		//return ret;
	}
	
	ret = request_irq(pins_desc[1].irq,  buttons_irq, 0, "S3", &pins_desc[1]);
	if (ret) {
    
    
		printk("reqeust_irq for EINT2 err : %d!\n", ret);
		//return ret;
	}

	ret = request_irq(pins_desc[2].irq, buttons_irq, 0, "S4", &pins_desc[2]);
	if (ret) {
    
    
		printk("reqeust_irq for EINT11 err : %d!\n", ret);
		//return ret;
	}

	ret = request_irq(pins_desc[3].irq, buttons_irq, 0, "S5", &pins_desc[3]);	
	if (ret) {
    
    
		printk("reqeust_irq for EINT19 err : %d!\n", ret);
		//return ret;
	}
	

	return 0;
}

ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    
    
	if (size != 1)
		return -EINVAL;

	if (file->f_flags & O_NONBLOCK)
	{
    
    
		if (!ev_press)
			return -EAGAIN;
	}
	else
	{
    
    
		/* 如果没有按键动作, 休眠 */
		wait_event_interruptible(button_waitq, ev_press);
	}

	/* 如果有按键动作, 返回键值 */
	copy_to_user(buf, &key_val, 1);
	ev_press = 0;
	
	return 1;
}


int sixth_drv_close(struct inode *inode, struct file *file)
{
    
    
	int i;
	int ret;
	
	//atomic_inc(&canopen);
	for (i = 0; i < sizeof(pins_desc)/sizeof(pins_desc[0]); i++) {
    
    
		free_irq(pins_desc[i].irq, &pins_desc[i]);
	}
	up(&button_lock);
	return 0;
}

static unsigned sixth_drv_poll(struct file *file, poll_table *wait)
{
    
    
	unsigned int mask = 0;
	poll_wait(file, &button_waitq, wait); // 不会立即休眠

	if (ev_press)
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

static int sixth_drv_fasync (int fd, struct file *filp, int on)
{
    
    
	printk("driver: sixth_drv_fasync\n");
	return fasync_helper (fd, filp, on, &button_async);
}


static struct file_operations sencod_drv_fops = {
    
    
    .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  sixth_drv_open,     
	.read	 =	sixth_drv_read,	   
	.release =  sixth_drv_close,
	.poll    =  sixth_drv_poll,
	.fasync	 =  sixth_drv_fasync,
};


int major;

static void buttons_timer_function(struct timer_list *t)
{
    
    
	struct pin_desc * pindesc = irq_pd;
	unsigned int pinval;

	if (!pindesc)
		return;
	
	pinval = gpio_get_value(pindesc->pin);

	if (pinval)
	{
    
    
		/* 松开 */
		key_val = 0x80 | pindesc->key_val;
	}
	else
	{
    
    
		/* 按下 */
		key_val = pindesc->key_val;
	}

    ev_press = 1;                  /* 表示中断发生了 */
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
	
	kill_fasync (&button_async, SIGIO, POLL_IN);
}


static int sixth_drv_init(void)
{
    
    
	//init_timer(&buttons_timer);
	//buttons_timer.function = buttons_timer_function;
	//buttons_timer.expires  = 0;
	timer_setup(&buttons_timer, buttons_timer_function, 0);
	add_timer(&buttons_timer); 

	major = register_chrdev(0, "sixth_drv", &sencod_drv_fops);

	sixthdrv_class = class_create(THIS_MODULE, "sixth_drv");

	/* 为了让mdev根据这些信息来创建设备节点 */
	sixthdrv_class_dev = device_create(sixthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */

	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
	gpgdat = gpgcon + 1;

	sema_init(&button_lock, 1);

	return 0;
}

static void sixth_drv_exit(void)
{
    
    
	del_timer(&buttons_timer); 
	unregister_chrdev(major, "sixth_drv");
	device_destroy(sixthdrv_class, MKDEV(major, 0));
	class_destroy(sixthdrv_class);
	iounmap(gpfcon);
	iounmap(gpgcon);
}

static int buttons_probe(struct platform_device *pdev)
{
    
    
	struct device *dev = &pdev->dev;		
	struct device_node *dp_node = dev->of_node;
	struct resource		*res;
	int i;

	for (i = 0; i < sizeof(pins_desc)/sizeof(pins_desc[0]); i++)
	{
    
    
		/* 根据platform_device的资源进行获得中断号,触发类型 */
		res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
		if (res) {
    
    
			pins_desc[i].irq  = res->start;
			printk("get irq %d\n", pins_desc[i].irq);
		}
		else {
    
    
			printk("can not get irq res for eint0\n");
			return -1;
		}

		pins_desc[i].pin = of_get_named_gpio(dp_node, "eint-pins", i);
		printk("pins_desc[%d].pin = %d\n", i, pins_desc[i].pin);
	}

	return sixth_drv_init();
}

static int buttons_remove(struct platform_device *pdev)
{
    
    
	sixth_drv_exit();
	return 0;
}



static const struct of_device_id of_match_buttons[] = {
    
    
	{
    
     .compatible = "jz2440_button", .data = NULL },
	{
    
     /* sentinel */ }
};


struct platform_driver buttons_drv = {
    
    
	.probe		= buttons_probe,
	.remove		= buttons_remove,
	.driver		= {
    
    
		.name	= "mybuttons",
		.of_match_table = of_match_buttons, /* 能支持哪些来自于dts的platform_device */
	}
};


static int buttons_init(void)
{
    
    
	platform_driver_register(&buttons_drv);
	return 0;
}

static void buttons_exit(void)
{
    
    
	platform_driver_unregister(&buttons_drv);
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");

device tree file

/dts-v1/;

/ {
    
    
	compatible = "samsung,s3c2440", "samsung,smdk2440";
	interrupt-parent = <0x1>;
	#address-cells = <0x1>;
	#size-cells = <0x1>;
	model = "JZ2440";

	aliases {
    
    
		pinctrl0 = "/pinctrl@56000000";
		serial0 = "/serial@50000000";
		serial1 = "/serial@50004000";
		serial2 = "/serial@50008000";
		i2c1 = "/i2c-gpio-1";
	};

	interrupt-controller@4a000000 {
    
    
		compatible = "samsung,s3c2410-irq";
		reg = <0x4a000000 0x100>;
		interrupt-controller;
		#interrupt-cells = <0x4>;
		phandle = <0x1>;
	};

	pinctrl@56000000 {
    
    
		reg = <0x56000000 0x1000>;
		compatible = "samsung,s3c2440-pinctrl";

		wakeup-interrupt-controller {
    
    
			compatible = "samsung,s3c2410-wakeup-eint";
			interrupts = <0x0 0x0 0x0 0x3 0x0 0x0 0x1 0x3 0x0 0x0 0x2 0x3 0x0 0x0 0x3 0x3 0x0 0x0 0x4 0x4 0x0 0x0 0x5 0x4>;
		};

		gpa {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
		};

		gpb {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
			phandle = <0xd>;
		};

		gpc {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
		};

		gpd {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
		};

		gpe {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
			phandle = <0x7>;
		};

		gpf {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
			interrupt-controller;
			#interrupt-cells = <0x2>;
			phandle = <0x6>;
		};

		gpg {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
			interrupt-controller;
			#interrupt-cells = <0x2>;
			phandle = <0xe>;
		};

		gph {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
		};

		gpj {
    
    
			gpio-controller;
			#gpio-cells = <0x2>;
		};

		uart0-data {
    
    
			samsung,pins = "gph-0", "gph-1";
			samsung,pin-function = <0x2>;
			phandle = <0x3>;
		};

		i2c0-bus {
    
    
			samsung,pins = "gpe-14", "gpe-15";
			samsung,pin-function = <0x2>;
			phandle = <0x4>;
		};

		nand_pinctrl {
    
    
			samsung,pins = "gpa-17", "gpa-18", "gpa-19", "gpa-20", "gpa-22";
			samsung,pin-function = <0x1>;
			phandle = <0x5>;
		};

		lcd_pinctrl {
    
    
			samsung,pins = "gpc-8", "gpc-9", "gpc-10", "gpc-11", "gpc-12", "gpc-13", "gpc-14", "gpc-15", "gpd-0", "gpd-1", "gpd-2", "gpd-3", "gpd-4", "gpd-5", "gpd-6", "gpd-7", "gpd-8", "gpd-9", "gpd-10", "gpd-11", "gpd-12", "gpd-13", "gpd-14", "gpd-15", "gpc-1", "gpc-2", "gpc-3", "gpc-4";
			samsung,pin-function = <0x2>;
			phandle = <0x8>;
		};

		lcd_backlight {
    
    
			samsung,pins = "gpg-4";
			samsung,pin-function = <0x3>;
			phandle = <0x9>;
		};

		uda1340_codec_pinctrl {
    
    
			samsung,pins = "gpb-4", "gpb-3", "gpb-2";
			samsung,pin-function = <0x1>;
			phandle = <0xc>;
		};

		s3c2440_iis_pinctrl {
    
    
			samsung,pins = "gpe-0", "gpe-1", "gpe-2", "gpe-3", "gpe-4";
			samsung,pin-function = <0x2>;
			phandle = <0xa>;
		};
	};

	timer@51000000 {
    
    
		compatible = "samsung,s3c2410-pwm";
		reg = <0x51000000 0x1000>;
		interrupts = <0x0 0x0 0xa 0x3 0x0 0x0 0xb 0x3 0x0 0x0 0xc 0x3 0x0 0x0 0xd 0x3 0x0 0x0 0xe 0x3>;
		#pwm-cells = <0x4>;
		clock-names = "timers";
		clocks = <0x2 0x19>;
	};

	serial@50000000 {
    
    
		compatible = "samsung,s3c2440-uart";
		reg = <0x50000000 0x4000>;
		interrupts = <0x1 0x1c 0x0 0x4 0x1 0x1c 0x1 0x4>;
		status = "okay";
		clock-names = "uart";
		clocks = <0x2 0x10>;
		pinctrl-names = "default";
		pinctrl-0 = <0x3>;
	};

	serial@50004000 {
    
    
		compatible = "samsung,s3c2410-uart";
		reg = <0x50004000 0x4000>;
		interrupts = <0x1 0x17 0x3 0x4 0x1 0x17 0x4 0x4>;
		status = "disabled";
	};

	serial@50008000 {
    
    
		compatible = "samsung,s3c2410-uart";
		reg = <0x50008000 0x4000>;
		interrupts = <0x1 0xf 0x6 0x4 0x1 0xf 0x7 0x4>;
		status = "disabled";
	};

	watchdog@53000000 {
    
    
		compatible = "samsung,s3c2410-wdt";
		reg = <0x53000000 0x100>;
		interrupts = <0x1 0x9 0x1b 0x3>;
		status = "okay";
		clocks = <0x2 0x6>;
		clock-names = "watchdog";
	};

	rtc@57000000 {
    
    
		compatible = "samsung,s3c2410-rtc";
		reg = <0x57000000 0x100>;
		interrupts = <0x0 0x0 0x1e 0x3 0x0 0x0 0x8 0x3>;
		status = "okay";
		clocks = <0x2 0x1a>;
		clock-names = "rtc";
	};

	i2c@54000000 {
    
    
		compatible = "samsung,s3c2440-i2c";
		reg = <0x54000000 0x100>;
		interrupts = <0x0 0x0 0x1b 0x3>;
		#address-cells = <0x1>;
		#size-cells = <0x0>;
		status = "disabled";
		clocks = <0x2 0x13>;
		clock-names = "i2c";
		pinctrl-names = "default";
		pinctrl-0 = <0x4>;
	};

	cpus {
    
    
		#address-cells = <0x1>;
		#size-cells = <0x0>;

		cpu {
    
    
			compatible = "arm,arm920t";
		};
	};

	xti_clock {
    
    
		compatible = "fixed-clock";
		clock-frequency = <0xb71b00>;
		clock-output-names = "xti";
		#clock-cells = <0x0>;
	};

	clock-controller@4c000000 {
    
    
		compatible = "samsung,s3c2440-clock";
		reg = <0x4c000000 0x20>;
		#clock-cells = <0x1>;
		phandle = <0x2>;
	};

	nand@4e000000 {
    
    
		compatible = "samsung,s3c2440-nand";
		reg = <0x4e000000 0x40>;
		interrupts = <0x0 0x0 0x18 0x3>;
		clocks = <0x2 0x23>;
		clock-names = "nand";
		pinctrl-names = "default";
		pinctrl-0 = <0x5>;
		status = "okay";
		nand,tacls = <0xa>;
		nand,twrph0 = <0x19>;
		nand,twrph1 = <0xa>;
		#address-cells = <0x1>;
		#size-cells = <0x1>;

		partitions {
    
    
			#address-cells = <0x1>;
			#size-cells = <0x1>;
			nr-chips = <0x1>;
			set-name = "jz2440-0";

			partition@0 {
    
    
				label = "bootloader";
				reg = <0x0 0x40000>;
				read-only;
			};

			partition@40000 {
    
    
				label = "device_tree";
				reg = <0x40000 0x20000>;
				read-only;
			};

			partition@60000 {
    
    
				label = "params";
				reg = <0x60000 0x20000>;
				read-only;
			};

			partition@80000 {
    
    
				label = "kernel";
				reg = <0x80000 0x400000>;
				read-only;
			};

			partition@480000 {
    
    
				label = "rootfs";
				reg = <0x480000 0x0>;
			};
		};
	};

	usb_ohci@49000000 {
    
    
		compatible = "samsung,s3c2440-ohci";
		reg = <0x49000000 0x60>;
		interrupts = <0x0 0x0 0x1a 0x3>;
		clocks = <0x2 0x21 0x2 0x7>;
		clock-names = "usb-host", "usb-bus-host";
		status = "okay";
	};

	memory {
    
    
		device_type = "memory";
		reg = <0x30000000 0x4000000>;
	};

	chosen {
    
    
		bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
	};

	srom-cs4@20000000 {
    
    
		compatible = "simple-bus";
		#address-cells = <0x1>;
		#size-cells = <0x1>;
		reg = <0x20000000 0x8000000>;
		ranges;

		ethernet@20000000 {
    
    
			compatible = "davicom,dm9000";
			reg = <0x20000000 0x2 0x20000004 0x2>;
			interrupt-parent = <0x6>;
			interrupts = <0x7 0x1>;
			local-mac-address = [00 00 de ad be ef];
			davicom,no-eeprom;
		};
	};

	i2c-gpio-1 {
    
    
		compatible = "i2c-gpio";
		#address-cells = <0x1>;
		#size-cells = <0x0>;
		gpios = <0x7 0xf 0x0 0x7 0xe 0x0>;
		i2c-gpio,delay-us = <0x5>;
		status = "disabled";

		eeprom@50 {
    
    
			compatible = "24c02";
			reg = <0x50>;
			pagesize = <0x20>;
			status = "okay";
		};
	};

	fb@4d000000 {
    
    
		compatible = "jz2440,lcd";
		reg = <0x4d000000 0x60>;
		interrupts = <0x0 0x0 0x10 0x3>;
		clocks = <0x2 0x20>;
		clock-names = "lcd";
		pinctrl-names = "default";
		pinctrl-0 = <0x8 0x9>;
		status = "okay";
		lcdcon5 = <0xb09>;
		type = <0x60>;
		width = [01 e0];
		height = [01 10];
		pixclock = <0x186a0>;
		xres = [01 e0];
		yres = [01 10];
		bpp = [00 10];
		left_margin = [00 02];
		right_margin = [00 02];
		hsync_len = [00 29];
		upper_margin = [00 02];
		lower_margin = [00 02];
		vsync_len = [00 0a];
	};

	jz2440ts@5800000 {
    
    
		compatible = "jz2440,ts";
		reg = <0x58000000 0x100>;
		reg-names = "adc_ts_physical";
		interrupts = <0x1 0x1f 0x9 0x3 0x1 0x1f 0xa 0x3>;
		interrupt-names = "int_ts", "int_adc_s";
		clocks = <0x2 0x16>;
		clock-names = "adc";
	};

	s3c2410-dma@4B000000 {
    
    
		compatible = "s3c2440-dma";
		reg = <0x4b000000 0x1000>;
		interrupts = <0x0 0x0 0x11 0x3 0x0 0x0 0x12 0x3 0x0 0x0 0x13 0x3 0x0 0x0 0x14 0x3>;
		#dma-cells = <0x1>;
		phandle = <0xb>;
	};

	s3c2440_iis@55000000 {
    
    
		compatible = "s3c24xx-iis";
		reg = <0x55000000 0x100>;
		clocks = <0x2 0x18>;
		clock-names = "iis";
		pinctrl-names = "default";
		pinctrl-0 = <0xa>;
		dmas = <0xb 0x9 0xb 0xa>;
		dma-names = "rx", "tx";
	};

	s3c24xx_uda134x {
    
    
		compatible = "s3c24xx_uda134x";
		clocks = <0x2 0x2 0x2 0x18>;
		clock-names = "mpll", "iis";
	};

	uda134x-codec {
    
    
		compatible = "uda134x-codec";
		pinctrl-names = "default";
		pinctrl-0 = <0xc>;
		uda,clk_gpio = <0xd 0x4 0x1>;
		uda,data_gpio = <0xd 0x3 0x1>;
		uda,mode_gpio = <0xd 0x2 0x1>;
		uda,use_gpios;
		uda,data_hold;
		uda,data_setup;
		uda,clock_high;
		uda,mode_hold;
		uda,mode;
		uda,mode_setup;
		uda,model = <0x2>;
	};

	buttons {
    
    
		compatible = "jz2440_button";
		eint-pins = <0x6 0x0 0x0 0x6 0x2 0x0 0xe 0x3 0x0 0xe 0xb 0x0>;
		interrupts-extended = <0x1 0x0 0x0 0x0 0x3 0x1 0x0 0x0 0x2 0x3 0xe 0x3 0x3 0xe 0xb 0x3>;
	};
};

Button-driven application layer test program:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>


/* sixthdrvtest 
  */
int fd;

void my_signal_fun(int signum)
{
    
    
	unsigned char key_val;
	read(fd, &key_val, 1);
	printf("key_val: 0x%x\n", key_val);
}

int main(int argc, char **argv)
{
    
    
	unsigned char key_val;
	int ret;
	int Oflags;

	//signal(SIGIO, my_signal_fun);
	
	fd = open("/dev/buttons", O_RDWR);
	if (fd < 0)
	{
    
    
		printf("can't open!\n");
		return -1;
	}

	//fcntl(fd, F_SETOWN, getpid());
	
	//Oflags = fcntl(fd, F_GETFL); 
	
	//fcntl(fd, F_SETFL, Oflags | FASYNC);


	while (1)
	{
    
    
		ret = read(fd, &key_val, 1);
		printf("key_val: 0x%x, ret = %d\n", key_val, ret);
		//sleep(5);
	}
	
	return 0;
}

Five, the kernel's processing of device tree interrupt information

  From the perspective of hardware structure, the processing process is divided into two levels: the interrupt controller and the device using the interrupt ;

  From the perspective of software structure, the processing process is divided into two parts: describing information in the device tree, and processing the device tree in the driver ;

(1) 中断控制器
这又分为root irq controller, gpf/gpg irq controller
a. root irq controller
a.1 在设备树中的描述
a.2 在内核中的驱动 

b. 对于S3C2440, 还有: gpf/gpg irq controller
b.1 在设备树中的描述(在pinctrl节点里)
b.2 在内核中的驱动 (在pinctrl驱动中)

(2) 设备的中断
a.1 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式")
a.2 在内核中的驱动 (在platform_driver.probe中获得IRQ资源, 即中断号)

irq_domain是核心:
a. 每一个中断控制器都有一个irq_domain
b. 对设备中断信息的解析, 
b.1 需要调用 irq_domain->ops->xlate (即从设备树中获得hwirq, type)
b.2 获取未使用的virq, 保存: irq_domain->linear_revmap[hwirq] = virq;
b.3 在hwirq和virq之间建立联系:
   要调用 irq_domain->ops->map, 比如根据hwirq的属性设置virq的中断处理函数(是一
个分发函数还是可以直接处理中断)
        irq_desc[virq].handle_irq = 常规函数;
   如果这个hwirq有上一级中断, 假设它的中断号为virq', 还要设置: 
        irq_desc[virq'].handle_irq = 中断分发函数;

(1) How to trigger the interrupt processing?

a. The entry of the initialization interrupt when the kernel starts:

start_kernel // init/main.c
    init_IRQ();
        if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
            irqchip_init();   // 一般使用它
        else
            machine_desc->init_irq();

b. The processing entry of the interrupt controller in the device tree:

irqchip_init // drivers/irqchip/irqchip.c
    of_irq_init(__irqchip_of_table);  // 对设备树文件中每一个中断控制器节点, 调用对应的处理函数
        为每一个符合的"interrupt-controller"节点,
        分配一个of_intc_desc结构体, desc->irq_init_cb = match->data; // = IRQCHIP_DECLARE中传入的函数
        并调用处理函数
        
        (先调用root irq controller对应的函数, 再调用子控制器的函数, 再调用更下一级控制器的函数...)

(2) The driver calling process of the root irq controller:

a. Define a processing function for the root irq controller:

IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);  //drivers/irqchip/irq-s3c24xx.c

其中:
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn) \
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type)           \
    static const struct of_device_id __of_table_##name      \
        __used __section(__##table##_of_table)          \
         = {
      
       .compatible = compat,              \
             .data = (fn == (fn_type)NULL) ? fn : fn  }

展开为:
    static const struct of_device_id __of_table_s3c2410_irq     \
        __used __section("__irqchip_of_table")          \
         = {
    
     .compatible = "samsung,s3c2410-irq",               \
             .data = s3c2410_init_intc_of  }

它定义了一个of_device_id结构体, 段属性为"__irqchip_of_table", 在编译内核时
这些段被放在__irqchip_of_table地址处。即__irqchip_of_table起始地址处,放置
了一个或多个 of_device_id, 它含有compatible成员;

设备树中的设备节点含有compatible属性,如果双方的compatible相同, 并且设备节点
含有"interrupt-controller"属性,则调用of_device_id中的函数来处理该设备节点。

所以: IRQCHIP_DECLARE 是用来声明设备树中的中断控制器的处理函数。

b. The execution process of the root irq controller processing function:

s3c2410_init_intc_of  // drivers/irqchip/irq-s3c24xx.c
    // 初始化中断控制器: intc, subintc
    s3c_init_intc_of(np, interrupt_parent, s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
                
        // 为中断控制器创建irq_domain
        domain = irq_domain_add_linear(np, num_ctrl * 32,
                                 &s3c24xx_irq_ops_of, NULL);

        intc->domain = domain;

        // 设置handle_arch_irq, 即中断处理的C语言总入口函数
        set_handle_irq(s3c24xx_handle_irq);

(3) The driver calling process of the gpf/gpg irq controller in the pinctrl system:

a. The driver of the pinctrl system:

a.1 源代码: drivers/pinctrl/samsung/pinctrl-samsung.c
static struct platform_driver samsung_pinctrl_driver = {
    
    
    .probe      = samsung_pinctrl_probe,
    .driver = {
    
    
        .name   = "samsung-pinctrl",
        .of_match_table = samsung_pinctrl_dt_match, // 含有 { .compatible = "samsung,s3c2440-pinctrl", .data = &s3c2440_of_data },
        .suppress_bind_attrs = true,
        .pm = &samsung_pinctrl_pm_ops,
    },
};

a.2 设备树中:
pinctrl@56000000 {
    
    
    reg = <0x56000000 0x1000>;
    compatible = "samsung,s3c2440-pinctrl";  // 据此找到驱动

a.3 驱动中的操作:
samsung_pinctrl_probe  // drivers/pinctrl/samsung/pinctrl-samsung.c
    最终会调用到 s3c24xx_eint_init // drivers/pinctrl/samsung/pinctrl-s3c24xx.c
    
        // eint0,1,2,3的处理函数在处理root irq controller时已经设置; 
        // 设置eint4_7, eint8_23的处理函数(它们是分发函数)
        for (i = 0; i < NUM_EINT_IRQ; ++i) {
    
    
            unsigned int irq;

            if (handlers[i]) /* add by [email protected], 不再设置eint0,1,2,3的处理函数 */
            {
    
    
                irq = irq_of_parse_and_map(eint_np, i);
                if (!irq) {
    
    
                    dev_err(dev, "failed to get wakeup EINT IRQ %d\n", i);
                    return -ENXIO;
                }

                eint_data->parents[i] = irq;
                irq_set_chained_handler_and_data(irq, handlers[i], eint_data);
            }
        }

        // 为GPF、GPG设置irq_domain
        for (i = 0; i < d->nr_banks; ++i, ++bank) {
    
    
        
            ops = (bank->eint_offset == 0) ? &s3c24xx_gpf_irq_ops
                               : &s3c24xx_gpg_irq_ops;

            bank->irq_domain = irq_domain_add_linear(bank->of_node, bank->nr_pins, ops, ddata);
        }

(4) The driver call process using interrupt:

a. Described in the device node (indicating the use of "which interrupt in which interrupt controller, and interrupt trigger mode")

比如:
    buttons {
    
    
        compatible = "jz2440_button";
        eint-pins  = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>;
        interrupts-extended = <&intc 0 0 0 3>,
                              <&intc 0 0 2 3>,
                              <&gpg 3 3>,
                              <&gpg 11 3>;
    };

b. The device node will be converted to platform_device

   "中断的硬件信息" 会转换为"中断号", 
   保存在platform_device的"中断资源"里

   在之前讲解了设备树中设备节点转换为 platform_device 的过程;
   我们只关心里面对中断信息的处理:

of_device_alloc (drivers/of/platform.c)
    dev = platform_device_alloc("", PLATFORM_DEVID_NONE);  // 分配 platform_device
    
    num_irq = of_irq_count(np);  // 计算中断数
    
    of_irq_to_resource_table(np, res, num_irq) // drivers/of/irq.c, 根据设备节点中的中断信息, 构造中断资源
        of_irq_to_resource
            int irq = of_irq_get(dev, index);  // 获得virq, 中断号
                            rc = of_irq_parse_one(dev, index, &oirq); // drivers/of/irq.c, 解析设备树中的中断信息, 保存在of_phandle_args结构体中
                            
                            domain = irq_find_host(oirq.np);   // 查找irq_domain, 每一个中断控制器都对应一个irq_domain
                            
                            irq_create_of_mapping(&oirq);             // kernel/irq/irqdomain.c, 创建virq和中断信息的映射
                                irq_create_fwspec_mapping(&fwspec);
                                    irq_create_fwspec_mapping(&fwspec);
                                        irq_domain_translate(domain, fwspec, &hwirq, &type) // 调用irq_domain->ops->xlate, 把设备节点里的中断信息解析为hwirq, type
                                        
                                        virq = irq_find_mapping(domain, hwirq); // 看看这个hwirq是否已经映射, 如果virq非0就直接返回
                                        
                                        virq = irq_create_mapping(domain, hwirq); // 否则创建映射
                                                    virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);  // 返回未占用的virq
                                                    
                                                    irq_domain_associate(domain, virq, hwirq) // 调用irq_domain->ops->map(domain, virq, hwirq), 做必要的硬件设置

c. The driver takes out the interrupt number from the "interrupt resource" of platform_device, and then it can request_irq

Note: This article refers to the notes of "Mr. Wei Dongshan's Embedded Course", "Mr. Zhu Youpeng's Embedded Course" notes, "Mr. Guo Tianxiang's 51 Single-chip Microcomputer Book", combined with his own actual development experience and technical articles of others on the Internet, comprehensively organize get. If there is any infringement, please contact to delete! The level is limited, welcome to communicate in the comment area

Guess you like

Origin blog.csdn.net/weixin_45842280/article/details/125074506