linux内核之中断和异常

一、异常

         中断通常分为同步和异步:

同步中断(异常):当指令执行时由CPU控制单元产生,只有在条指令终止执行后CPU才会发出中断。如:CPU操作错误指令,系统调用等。

异步中断(中断):由其他硬件设备依照CPU时钟信号随机产生。如:网络收发中断等。

         异常是有程序的错误产生,而中断是由间隔定时器和I/O设备产生。

1、类型

         分为三类:故障(Fault)、陷阱(Trap)和终止(Abort),是源自CPU执行指令内部的事件。

陷阱:有意而为之的异常。最常见:操作系统的系统调用。

故障:由错误条件引起的,可能被故障处理例程修复。如果可以修复则继续干活;如果不能修复则会转化终止,进入下一步。如缺页处理。

终止:不可恢复的致命错误造成结果。终止处理程序结束后控制权不返回给引发终止的应用程序而是系统,系统通常做法是终止该应用程序。

2、异常处理

         CPU产生的大部分异常都是有linux解释为出错条件。当一个异常发生时,跳转到相应的异常处理程序,然后向当前进程发送一个SIGFPE信号,这个进程将采取若干必要的步骤来恢复或者中止运行。

         异常处理程序有个标准的结构,由以下三部分组成:

在内核堆栈中保存大多数寄存器的内容

用高级的C函数处理异常

通过ret_from_exception函数从异常处理程序退出。

         在3.10.y中,arm平台arch/arm/kernel/entry-armv.S文件定义了各种异常处理程序入口,用汇编实现。


 

二、中断

         后续讲的中断指异步中断,由外围设备或定时器产生。

1、类型

可屏蔽中断:I/O设备发出的所有中断请求都产生可屏蔽中断,有两种状态:屏蔽和非屏蔽。

不可屏蔽中断:只有几个危机事件(如硬件故障)才引起非屏蔽中断,CPU必须响应处理。

2、描述

         每个能否发出中断请求的硬件设备都有一条与之对应的IRQ line。中断控制器收集各个IRQs的异步事件,用有序、可控的方式通知一个或者多个CPU。CPU跳转到相应中断向量入口执行中断处理程序。其基本框图如下:

         对于arm平台,提供一个通用的中断控制器GIC(Generic Interrupt Controller),它是通过AMBA总线连接到一个或者多个ARMprocessor上。GIC管理以下类型中断:

(1)外设中断(Peripheral interrupt)

         外设的中断可以分成PPI(PrivatePeripheral Interrupt)和SPI(Shared Peripheral Interrupt)。PPI只能分配给一个确定的processor,而SPI可以由Distributor将中断分配给一组Processor中的一个进行处理。

(2)软件出发中断(SGI,Software-generated interrupt)

         软件可以通过写GICD_SGIR寄存器来触发一个中断事件,这样的中断可用于processor之间通信。

(3)虚拟中断(Virtual interrupt)和Maintenance interrupt

GIC最大支持1020个HW interruptID,具体ID分配情况如下(见海思datasheet):

ID0~ID31用于分发到特定的processinterrupt,ID0~ID15用于SGI,ID16~ID31用于PPI。

ID32~ID1019用于SPI。

         在linux2.4之前的内核是不支持抢占特性的,而2.4开始支持抢占式内核,通过CONFIG_PREEMPT选项配置内核支不支持抢占,抢占式内核中断处理基本流程图:


         task1因等待中断的数据进入睡眠状态,调度了task2进入运行(调度发生在内核空间,为了清晰,图中省略);task2运行过程中通过系统调用进入内核运行,然后产生硬件中断,如果没有临界区之类的关闭中断操作会马上响应中断,T0到T2都可以认为中断延迟(硬件产生到真正执行中断程序的时间),T1到T2的时间极短,图中夸大方便看;中断处理程序中唤醒了task1进入running状态(中断里不可调度);待中断处理程序结束,中断返回(两者之间的结束处理时间也极短),因为可抢占,系统进行了任务调度;发现优先级task1>task2,先执行task1,task1执行完毕后在继续之前的task2执行。

3、中断处理程序

         CPU响应一个外部中断时,会执行一个与之对应的特定函数,这个处理函数就称为中断处理程序或中断服务例程。每个中断设备都有一个相应的中断处理程序。

         中断处理程序就是普通的C函数,只是在定义格式上按照规定类型声明,但是只能运行在称之为中断上下文的特殊上下文中。中断上下文和进程没有什么瓜葛,与current宏也是不相干,不同与进程上下文,它不可以阻塞。

(1)注册中断处理程序

         驱动程序可以通过request_irq()函数注册一个中断处理程序,其原型如下:

static inline int __must_check request_irq(unsignedint irq,

irq_handler_t handler,

unsigned long flags,

const char *name,

void *dev)

irq:中断号

handler:中断的实际处理函数

flags:中断处理程序标志

name:与中断相关的设备的ASCII文本表示,显示在/proc/interrupts

dev:注册的设备,如果是共享中断号,则必须传递有效值,否则可以为NULL。

(2)中断处理程序标志

         flags可以为0,也可以是下列一个或多个标志的位掩码:

IRQF_TRIGGER_XXX:描述该interrupt出发类型的flag

IRQF_DISABLED:表示处理中断处理程序期间,禁止所有其他中断。一个废弃的flag,旧内核(2.6.35版本之前)两种handler:slow handler和fast handler,fast handler需要传递该参数关闭CPU中断;新内核不区分,皆为fash handler,都需要关闭CPU中断,需要后续处理的内容推到下半部处理。

IRQF_SHARED:表示多个中断处理程序之间共享一个中断线

IRQF_PERCPU:在SMP的架构下,中断有两种模式:一种是在所有processor之间共享,中断可以分配给任意一个CPU处理;另外一种是特定属于一个CPU,产生中断只能有特定CPU处理。如上面的arm GIC控制器。

IRQF_NOBALANCING:在smp下,上面共享processor的中断会均衡的分配到各个CPU,如果不想均衡就设定该flag。

IRQF_IRQPOLL

IRQF_ONESHOT

IRQF_NO_SUSPEND

IRQF_FORCE_RESUME

IRQF_NO_THREAD

IRQF_EARLY_RESUME

IRQF_TIMER

(3)释放中断处理程序

void free_irq(unsigned int irq, void*dev_id)

(4)共享中断处理程序

         flags有IRQF_SHARED,在注册时必须传递参数dev,而且每个注册程序该dev都是唯一的。因为共享,所以通过该dev来区分该中断调用哪个中断处理程序处理;释放时也是根据dev释放对应的中断处理程序。当内核接收到一个中断后,将依次调用在该irq注册的每一个处理程序。

4、上半部和下半部

         由于中断打断了其他的代码,因此中断处理程序应该迅速执行完毕,避免消耗大量时间,影响系统的实时性。因此,linux一般把中断切换为两个部分:上半部和下半部,上半部(即中断处理程序)只做有严格时限的工作,能够被允许稍后完成的所有工作尽量放在下半部(稍后一定时间内再去处理),细节见下一章。

        

5、/proc/interrupts

         该文件存放的是系统中与中断相关的统计信息:

          CPU0       CPU1       CPU2       CPU3      

 35:    694273          0          0          0       GIC timer4

 36:         0          0     699620          0       GIC timer6

 38:   1285660          0          0          0       GIC uart-pl011

 43:     10509          0          0          0       GIC Hisilicon Dmac

 51:         0          0          0          0       GIC hi_mci

 52:         1          0          0          0       GIC ohci_hcd:usb2

 53:         0          0          0          0       GIC ehci_hcd:usb1

 55:   6918815          0          0          0       GIC stmmaceth

 56:         0          0          0          0       GIC stmmaceth

 57:         0          0          0          0       GIC stmmaceth

 58:         0          0          0          0       GIC stmmaceth

 59:         0          0          0          0       GIC ahci

 60:         0          0          0          0       GIC xhci-hcd:usb3

 78:         0          0          0          0       GIC IVE

 79:         0          0          0          0       GIC AIO Interrupt

 80:         0          0          0          0      GIC  hi_tde_irq

 94:         0          0          0          0       GIC hi_iommu

105:          6          0          0          0       GIC timer

107:          0    688852          0          0       GIC timer5

108:          0          0          0    699547       GIC  timer7

112:          0          0          0          0       GIC VOU Interrupt

IPI0:          0          1          1          1 CPU wakeup interrupts

IPI1:          0          0          0          0 Timer broadcast interrupts

IPI2:  11100056   11902540   10996312  11892147  Rescheduling interrupts

IPI3:          6          6          6          3 Function call interrupts

IPI4:          0          0          0          1 Single function call interrupts

IPI5:         0          0          0          0 CPU stop interrupts

IPI6:          0          0          0          0 CPU backtrace

第一列:中断号

第二列:CPU0~CPU3 接收到中断的计数

第三列:相应的中断名字

6、中断控制接口

         Linux内核提供了一组接口用于操作机器上的中断状态。

(1)禁止和激活中断

local_irq_disable:禁止本地中断

local_irq_enable:激活本地中断

local_irq_save:保存本地中断的当前状态,然后禁止本地中断传递

local_irq_restore:恢复本地中断传递给给定状态

必须在同一个函数内完成

(2)禁止指定中断号

disable_irq:禁止给定中断,确保该函数返回之前在该中断号上没有处理程序在运行

disable_irq_nosync:禁止给定中断

enable_irq:激活给定中断号

(3)中断系统状态

irqs_disabled:如果本地中断被禁止,则返回非0,否则0

in_interrupt:如果在中断上下文中,则返回非0,否则0

in_irq:如果在当前正在执行中断处理程序,则返回非0,否则0


 

三、下半部处理

         下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作,最理想情况下,最好是中断处理程序将所有工作都交给下半部执行。对于上半部和下半部之间划分工作,不存在某种严格的规则,但还是有一些提示可借鉴:

如果一个任务对时间非常敏感,将其放在中断处理程序中执行

如果一个任务和硬件相关,将其放在中断处理程序中执行

如果一个任务要保证不被其他中断打断,将其放在中断处理程序中执行

其他所有任务都考虑防止在下半部执行

和上半部只能通过中断处理程序实现不同,下半部可以通过多种机制实现:软中断softirq、tasklet、工作队列work queue。

1、软中断

(1)描述

软中断使用的比较少,tasklet是下半部更常用的一种形式。但是tasklet也是在软中断基础上实现的,是其中一种的软中断。

软中断本身不能进入休眠。

         软中断是在编译期间静态分配的,不想tasklet可以动态注册或注销。其实现结构体如下:

struct softirq_action

{

   void    (*action)(structsoftirq_action *);

};

static struct softirq_actionsoftirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp一共有NR_SOFTIRQS个软中断,在smp情况下地址按照cacheline对齐,该数据结构多个CPU共享。目前内核总共有如下几类软中断:

enum

{

   HI_SOFTIRQ=0,        //高优先级的tasklet,也就是最先执行

   TIMER_SOFTIRQ,  //基于系统tick的softwaretimer

   NET_TX_SOFTIRQ,  //网卡驱动发送

   NET_RX_SOFTIRQ,  //网卡驱动接收

   BLOCK_SOFTIRQ,   //block设备

   BLOCK_IOPOLL_SOFTIRQ,  //block设备

   TASKLET_SOFTIRQ,  //普通tasklet

   SCHED_SOFTIRQ,   //用于多CPU之间的负载均衡

   HRTIMER_SOFTIRQ, //用于高精度timer

   RCU_SOFTIRQ,    //处理RCU永远用于RCU

   NR_SOFTIRQS

};

         一个软中断不会抢占另外一个软中断,但是中断处理程序可以抢占软中断。但是其他软中断(甚至同一个软中断)可以在其他处理器上同时运行。每个CPU通过下面结构来表示分配给自己的软中断,谁触发谁处理,例如:本次触发CPU0则有CPU0处理,同一硬件中断下次CPU1则CPU1处理。

typedefstruct { 
    unsigned int __softirq_pending; 
#ifdef CONFIG_SMP 
    unsigned int ipi_irqs[NR_IPI]; 
#endif 
} ____cacheline_aligned irq_cpustat_t;

irq_cpustat_tirq_stat[NR_CPUS] ____cacheline_aligned;

__softirq_pending:每1位代表对应上面一个软中断,因此NR_SOFTIRQS最多为32个,每个CPU都有自己的irq_stat记录分配给自己的软中断。

(2)软中断执行

         一个注册的软中断必须被触发(raise_softirq)后才会执行。通常中断处理程序返回时会触发软中断,使其稍后被执行。一般有一下几种情况:

         从硬件中断处理程序处理完退出时,如果在中断上下文irq_exit-> invoke_softirq执行

         从硬件中断处理程序处理完退出时,如果在进程上下文ksoftirqd执行(有几个CPU就有几个这样的线程)

         代码中显示调用执行,如网络子系统。

(3)使用软中断

         软中断保留给系统中对时间要求最严格以及最重要的下半部使用。尽量使用现有的软中断,两个子系统(网络和SCSI)直接使用软中断。此外,内核定时器和tasklet都是建立在软中断上。

         注册软中断处理程序:

voidopen_softirq(int nr, void (*action)(struct softirq_action *))

参数为:软中断的索引号和处理函数(不能睡眠)。

在一个处理程序运行时候,当前处理器的软中断被禁止,但是其他处理器仍然可以处理其他中断。实际上,如果同一个软中断在它被执行的同时再次触发了,那么另外一个处理器可以同时运行其处理程序,这意味着软中断处理函数中的共享数据需要加锁保护。软中断的优势就是其可扩展到不同处理器,加速性能,如果加锁保护影响扩展还不如使用tasklet。因此软中断处理函数最好可重入。

触发软中断:

voidraise_softirq(unsigned int nr);

inline voidraise_softirq_irqoff(unsigned int nr);

前者先禁止本地中断,再触发软中断;后者如果本地中断已经禁止则直接调用,常用于中断处理程序里。

2、tasklet

(1)描述

tasklet是利用软中断实现的一种下半部机制,和软中断本质上相似,也和进程没有任何关系,但是接口更简单,锁保护要求更低。因为同一个tasklet处理程序不能同时在多个处理器上运行,但其他tasklet处理程序可以在其他处理器同时运行,这点和软中断主要区别。

         实质是一种软中断,因此也不能进入睡眠。

         tasklet由两类软中断:HI_SOFTIRQ和TASKLET_SOFTIRQ,在初始化时注册:

open_softirq(TASKLET_SOFTIRQ,tasklet_action);

open_softirq(HI_SOFTIRQ,tasklet_hi_action);

tasklet有tasklet_struct结构体表示:

struct tasklet_struct                                                                                                                            

{                                                                                                                                                

   struct tasklet_struct *next;    //链表中下一个tasklet                                                                                                                 

   unsigned long state;        //tasklet状态                                                                                                                 

   atomic_t count;           //引用计数                                                                                                                   

   void (*func)(unsigned long);  //tasklet处理函数                                                                                                                

   unsigned long data;        //处理函数参数                                                                                                                  

};

state: 只能在0,TASKLET_STATE_SCHED和TASKLET_STATE_RUN之间取值,TASKLET_STATE_SCHED表明已被调度,TASKLET_STATE_RUN表示正在运行确保同一个时刻只能在一个CPU上运行。

count:为0时才能激活运行,非0时表示被禁止

         已经被调度TASKLET_STATE_SCHED的tasklet存放在两个单处理器数据结构:tasklet_vec和tasklet_hi_vec,从下面声明就可以看到每个CPU都有一个这样的定义。该软中断触发时tasklet_action和tasklet_hi_action就会从这个数据结构中获取tasklet进行执行。

static DEFINE_PER_CPU(struct tasklet_head,tasklet_vec);

static DEFINE_PER_CPU(struct tasklet_head,tasklet_hi_vec);

         通过tasklet_schedule和tasklet_hi_schedule来调度一个tasklet。

(2)使用tasklet

A.声明一个tasklet

         DECLARE_TASKLET(name,func, data);

         或者

         tasklet_init(t,tasklet_handler, dev)

B.编写处理程序

         voidtask_handler(unsigned long data)

         不能睡眠

C.调度tasklet

         task_schedule(&tasklet)

         被调度以后,只要有机会就会尽可能早的执行,如果在执行之前又调度了一次,仍然只会执行一次。

3、工作队列

         工作队列与软中断和tasklet有些不同,它是将拖后的任务交给一个内核线程去执行,因此总是会在进程上下文中执行。这样工作队列可以利用进程上下的优势,可以重新调度甚至睡眠。

       工作队列子系统是一个用于创建内核线程的接口,通过它创建的线程负责执行由内核其他部分排到队列里的任务。这些线程称为工作者线程。

(1)数据结构

工作队列线程通过structworkqueue_struct结构表示:

struct workqueue_struct {

structpool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */

structpool_workqueue __rcu *numa_pwq_tbl[]; /* FR: unbound pwqs indexed by node */

}

数据成员struct pool_workqueue __percpu *cpu_pwqs代表每个CPU的工作队列池

struct pool_workqueue {

structwork_struct  unbound_release_work;

structrcu_head     rcu;

}

struct work_struct  unbound_release_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                                                                                                                                           

};

(2)工作队列使用

         使用默认的工作队列:

A.创建工作项

#define DECLARE_WORK(n, f)

or

#define INIT_WORK(_work, _func)

B.工作项处理函数

void work_handler(void *data)

默认情况下,允许响应中断并且不持有任何锁,如果需要函数可以睡眠。使用锁的机制和其他进程上下文中使用锁机制一样。

C.调度执行工作项

schedule_work(&work)

or

schedule_delay_work(&work, delay)延时一定时间后才会执行

cancel_delayed_work(&work)取消延迟

D.刷新操作

flush_scheduled_work()函数会一直等待,直到队列中所有对象都被执行才会返回,该函数会进入是休眠状态。

         创建自己的工作队列:

如果缺省的队列不能满足你的需要,创建一个新的工作队列和与之相应的工作者线程,但是会在每个处理器上都创建一个工作者线程。

struct workqueue_struct*create_workqueu(const char *name)

创建之后可以调用下面列举的函数

int queue_work(struct workqueue_struct *wq,struct work_struct *work)

int queue_delayed_work(structworkqueue_struct *wq, struct work_struct *work, unsigned long delay)

flush_workqueue(struct workqueue_struct*wq)

功能和上面相似。

4、三者选择

         软中断、tasklet和工作队列,前两者相似,工作队列机制不同靠内核线程实现:

下半部

上下文

顺序执行保障

软中断

中断

tasklet

中断

同类型不能同时执行

工作队列

进程

无(同进程一样调度)

 

软中断:

执行序列化保障最少。这要求软中断处理函数必须格外小心的采取一些步骤确保共享数据的安全,两个甚至更多相同类别的软中断有可能在不同处理器上同时执行,因此,最好可重入。它实时性最好。

tasklet:

         两个相同类型的tasklet不能同时执行,实现简单。一种软中断而又不并发运行,因此驱动程序开发者应当尽可能选择tasklet而不是软中断。

工作队列:

         如果需要把下半部推到进程上下文运行即可以睡眠,就只能工作队列。由于是内核线程,涉及进程切换,开销比上面两个大很多。

5、下半部控制

         为了保证共享数据的安全,先得到一个锁,然后再禁止下半部的处理。

函数

描述

local_bh_disable

禁止本地处理器的软中断和tasklet的处理

local_bh_enable

激活本地处理器的软中断和tasklet的处理

需要成对调用,这些函数只影响软中断和tasklet,并不能禁止工作队列的执行。因为工作队列是在进程上下文中运行的,它保护共享数据所做的工作和其他任何进程上下文中做的都差不多。


 

四、中断源码分析

1、中断初始化

(1)中断向量表映射

       在Documentation/arm/memory.txt文档里说明了CPU中断向量映射后地址:00000000~ 00000fff 或ffff0000 ~ fff0fff,具体哪一个有CP15寄存器的V bit决定,如果是1在ffff0000 ~ fff0fff,否则在00000000~ 00000fff。

         在start_kernel-> setup_arch -> paging_init -> devicemaps_init中完成中断向量映射。

static void __init devicemaps_init(structmachine_desc *mdesc)                                                                                   

{                                                                                                                                                

   struct map_desc map;                                                                                                                         

   unsigned long addr;                                                                                                                          

void *vectors;

    //申请向量页                                                                                                                                          

vectors =early_alloc(PAGE_SIZE);

//拷贝向量到申请的向量页                                                                                                                                                                                                                                                  

early_trap_init(vectors);

….

    //完成向量页的地址映射

map.pfn =__phys_to_pfn(virt_to_phys(vectors));

   map.virtual = 0xffff0000;

   map.length = PAGE_SIZE;

   map.type = MT_HIGH_VECTORS;

   create_mapping(&map, false);

         //如果Vbit=0,就映射到low_vector地址00000000 ~ 00000fff,否则就是上面的ffff0000 ~ fff0fff

   if (!vectors_high()) {

       map.virtual = 0;

       map.type = MT_LOW_VECTORS;

       create_mapping(&map, false);

}

}

//将向量表拷贝到申请的向量页

void __init early_trap_init(void*vectors_base)

{  

unsigned longvectors = (unsigned long)vectors_base;

// vectors_start:0xc05c5024, stubs_start:0xc05c4e00,在arch/arm/kernel/entry-armv.S中定义,其中有个偏移计算(未弄明白

   extern char __stubs_start[], __stubs_end[];

   extern char __vectors_start[], __vectors_end[];

   extern char __kuser_helper_start[], __kuser_helper_end[];

   int kuser_sz = __kuser_helper_end - __kuser_helper_start;

   vectors_page = vectors_base;

   /*

    * Copy the vectors, stubs and kuser helpers (in entry-armv.S)

    * into the vector page, mapped at 0xffff0000, and ensure these

    * are visible to the instruction stream.

    */

   memcpy((void *)vectors, __vectors_start, __vectors_end -__vectors_start);

   memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end -__stubs_start);

   memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start,kuser_sz);

   /*

    * Do processor specific fixups for the kuser helpers

    */

   kuser_get_tls_init(vectors);

   /*

     * Copy signal return handlers into the vectorpage, and

    * set sigreturn to be a pointer to these.

    */

   memcpy((void *)(vectors + KERN_SIGRETURN_CODE - CONFIG_VECTORS_BASE),

          sigreturn_codes, sizeof(sigreturn_codes));

   flush_icache_range(vectors, vectors + PAGE_SIZE);

   modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

}

(2)通用中断处理函数初始化

上面完成映射后,产生中断后就会跳转到相应的向量表入口,下面以irq为例。

__stubs_start:

/* 

 *Interrupt dispatcher

 */

   vector_stub irq, IRQ_MODE, 4

   

   .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

红色即为有效入口(为了对齐,其他都是非法入口),分别代表在用户空间和内核空间产生irq中断的入口,这里以__irq_svc为例。

   .align  5

__irq_svc: 

   svc_entry

    irq_handler //中断处理函数

   

#ifdef CONFIG_PREEMPT

   get_thread_info tsk

   ldr r8, [tsk, #TI_PREEMPT]      @get preempt count

   ldr r0, [tsk, #TI_FLAGS]        @get flags

   teq r8, #0              @ ifpreempt 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)

/*

 *Interrupt handling.

 */

   .macro  irq_handler

#ifdef CONFIG_MULTI_IRQ_HANDLER

    ldr r1, =handle_arch_irq//最终的处理函数

   mov r0, sp

   adr lr, BSYM(9997f)

    ldr pc, [r1]

#else

   arch_irq_handler_default

#endif

9997:

.endm

handle_arch_irq初始化过程如下:

start_kernel -> init_IRQ -> machine_desc->init_irq()(hi3536_gic_init_irq)-> gic_init -> gic_init_bases -> set_handle_irq(gic_handle_irq):

void __init set_handle_irq(void(*handle_irq)(struct pt_regs *))

{

   if (handle_arch_irq)

       return;

   

   handle_arch_irq = handle_irq;

}

因此,handle_arch_irq就是上面的gic_handle_irq,最终就是跳转到该函数进行中断处理,具体执行见小节3。

(3)中断域初始化

         handle_arch_irq是任何体系结构产生中断的统一入口地址,中断域根据中断号又进行了中断划分,其初始化如下:

start_kernel -> init_IRQ -> machine_desc->init_irq()(hi3536_gic_init_irq)-> gic_init -> gic_init_bases -> irq_domain_add_legacy:

gic->domain = irq_domain_add_legacy(node,gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops,gic);

const struct irq_domain_ops gic_irq_domain_ops = {

   .map = gic_irq_domain_map,

   .xlate = gic_irq_domain_xlate,

};

static int gic_irq_domain_map(structirq_domain *d, unsigned int irq,

                irq_hw_number_t hw)

{  

   if (hw < 32) {

       irq_set_percpu_devid(irq);

       irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq);

       set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);

    }else {

       irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);

       set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);

    }

   irq_set_chip_data(irq, d->host_data);

   return 0;

}

当中断号<32时使用handle_percpu_devid_irq;>=32时使用handle_fasteoi_irq进行处理,该分类个人认为是根据第二章小节2描述一节的PPI和SPI划分的。

2、中断注册

         以下网卡驱动注册中断为例:

ret = request_irq(SYNOP_GMAC_IRQNUM, stmmac_interrupt, IRQF_SHARED,STMMAC_RESOURCE_NAME,  pdev):

static inline int __must_check

request_irq(unsigned int irq, irq_handler_thandler, unsigned long flags,

       const char *name, void *dev)

{  

   return request_threaded_irq(irq, handler,NULL, flags, name, dev);

}

/*

         申请一个中断line,如果thread_fn不等于NULL,则会创建一个线程。

         申请中断资源,使能中断line和中断处理

*/

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)

{

struct irqaction*action;

   struct irq_desc *desc;

   int retval;

   /*

    * Sanity-check: shared interrupts must pass in a real dev-ID,

    * otherwise we'll have trouble later trying to figure out

    * which interrupt is which (messes up the interrupt freeing

    * logic etc).

    */

   if ((irqflags & IRQF_SHARED) && !dev_id)

       return -EINVAL;

   desc = irq_to_desc(irq); //根据中断号获取相应的中断域

   if (!desc)

       return -EINVAL;

   if (!irq_settings_can_request(desc) ||

       WARN_ON(irq_settings_is_per_cpu_devid(desc)))

       return -EINVAL;

   if (!handler) {

       if (!thread_fn)

           return -EINVAL;

       handler = irq_default_primary_handler;

}

   action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);

   if (!action)

       return -ENOMEM;

         //填充中断处理结构体

   action->handler = handler;

   action->thread_fn = thread_fn;

   action->flags = irqflags;

   action->name = devname;

   action->dev_id = dev_id;

         //将相应中断号的中断处理结构体添加到相应的中断域里

   chip_bus_lock(desc);

   retval = __setup_irq(irq, desc, action);

   chip_bus_sync_unlock(desc);

   if (retval)

       kfree(action);

         returnretval;

}

3、中断执行

         这里主要讲解一下以irq中断产生的正常处理流程。中断产生后,CPU跳转到相应的入口地址:

__stubs_start:

/*

 *Interrupt dispatcher

 */

   vector_stub irq, IRQ_MODE, 4

   .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)

   .align  5

__irq_svc: 

   svc_entry

    irq_handler

   

#ifdef CONFIG_PREEMPT

   get_thread_info tsk

   ldr r8, [tsk, #TI_PREEMPT]      @get preempt count

   ldr r0, [tsk, #TI_FLAGS]        @get flags

   teq r8, #0              @ ifpreempt 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)

   .macro  irq_handler

#ifdef CONFIG_MULTI_IRQ_HANDLER

   ldr r1, =handle_arch_irq见初始化= gic_handle_irq

   mov r0, sp

   adr lr, BSYM(9997f)

   ldr pc, [r1]

#else

   arch_irq_handler_default

#endif

9997:

   .endm

static asmlinkage void__exception_irq_entry gic_handle_irq(struct pt_regs *regs)

{

   u32 irqstat, irqnr;

   struct gic_chip_data *gic = &gic_data[0];

    void__iomem *cpu_base = gic_data_cpu_base(gic);

   do {

       irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);

       irqnr = irqstat & ~0x1c00;

       if (likely(irqnr > 15 && irqnr < 1021)) {

           irqnr = irq_find_mapping(gic->domain, irqnr);//获取中断号

           handle_IRQ(irqnr, regs);

           continue;

       }

       if (irqnr < 16) {

           writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);

       

           /*Call sig proccess func*/

           if ((irqnr == ipc_irq_handle.irq)

                &&(ipc_irq_handle.handle)) {

                ipc_irq_handle.handle(((irqstat>> 10) & 0x7),

                        irqnr, regs);

                continue;

           }

   

#ifdef CONFIG_SMP

            handle_IPI(irqnr, regs);

#endif 

           continue;

       }

       break;

    }while (1);

}

void handle_IRQ(unsigned int irq, structpt_regs *regs)

{  

   struct pt_regs *old_regs = set_irq_regs(regs);

           

   irq_enter();

            

   /* 

    * Some hardware gives randomly wrong interrupts.  Rather

    * than crashing, do something sensible.

    */

   if (unlikely(irq >= nr_irqs)) {

       if (printk_ratelimit())

           printk(KERN_WARNING "Bad IRQ%u\n", irq);

        ack_bad_irq(irq);

    }else {

       generic_handle_irq(irq);

    }

    irq_exit(); //退出中断,如果可能会调用软中断,处理下半部

   set_irq_regs(old_regs);

}

int generic_handle_irq(unsigned int irq)

{  

   struct irq_desc *desc = irq_to_desc(irq);

   

   if (!desc)

       return -EINVAL;

    generic_handle_irq_desc(irq, desc);

   return 0;

}

static inline voidgeneric_handle_irq_desc(unsigned int irq, struct irq_desc *desc)

{  

   desc->handle_irq(irq, desc);

}

这里的handle_irq就是小节1中断域初始化的handle_percpu_devid_irqhandle_fasteoi_irq,这里以handle_fasteoi_irq为例讲解:

void

handle_fasteoi_irq(unsignedint irq, struct irq_desc *desc)

{

    raw_spin_lock(&desc->lock);

    if(unlikely(irqd_irq_inprogress(&desc->irq_data)))

        if (!irq_check_poll(desc))

            goto out;

    desc->istate &= ~(IRQS_REPLAY |IRQS_WAITING);

    kstat_incr_irqs_this_cpu(irq, desc);

    /*

     * If its disabled or no action available

     * then mask it and get out of here:

     */                

    if (unlikely(!desc->action ||irqd_irq_disabled(&desc->irq_data))) {

        desc->istate |= IRQS_PENDING;

        mask_irq(desc);

        goto out;

    }

    if (desc->istate & IRQS_ONESHOT)

        mask_irq(desc);

    preflow_handler(desc);

handle_irq_event(desc);

    if (desc->istate & IRQS_ONESHOT)

        cond_unmask_irq(desc);

out_eoi:

    desc->irq_data.chip->irq_eoi(&desc->irq_data);

out_unlock:

    raw_spin_unlock(&desc->lock);

    return;

out:

    if (!(desc->irq_data.chip->flags& IRQCHIP_EOI_IF_HANDLED))

        goto out_eoi;

    goto out_unlock;

}

irqreturn_t handle_irq_event(structirq_desc *desc)

{      

   struct irqaction *action = desc->action;

   irqreturn_t ret;

   

   desc->istate &= ~IRQS_PENDING;

   irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);

   raw_spin_unlock(&desc->lock);

   ret = handle_irq_event_percpu(desc, action);

       

    raw_spin_lock(&desc->lock);

   irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

   return ret;

}

irqreturn_t

handle_irq_event_percpu(struct irq_desc*desc, struct irqaction *action)

{

   irqreturn_t retval = IRQ_NONE;

   unsigned int flags = 0, irq = desc->irq_data.irq;

   do {

       irqreturn_t res;

       trace_irq_handler_entry(irq, action);

        res =action->handler(irq, action->dev_id); //此处执行的就是驱动中断注册的处理函数。

       trace_irq_handler_exit(irq, action, res);

                   …

       retval |= res;

       action = action->next; //个人认为:当该中断是共享的时候,遍历一遍所有的中断处理函数;如果不是共享的,则next应该为NULL

    }while (action);

   add_interrupt_randomness(irq, flags);

   if (!noirqdebug)

       note_interrupt(irq, desc, retval);

   return retval;

}

通过是中断执行的整个完成代码流程,还有很多细节待进一步了解。

猜你喜欢

转载自blog.csdn.net/heliangbin87/article/details/78546901