深入linux设备驱动程序内核机制(第五章) 读书笔记

时间:2022-12-23 17:55:15
第5章 中断处理

    外设与处理器交互的手段分为两种:轮询和中断. 对于轮询, 处理器不停的查询外设状态. 而中断, 在外设满
    足处处理器要求时, 主动发送信号给处理器.

本文欢迎转载

出处:http://blog.csdn.net/dyron


5.1 中断的硬件框架

    处理器一般只有两根左右的中断pin, 而外设很多, 为解决这个问题, 设备的中断信号线接在一个中断控制器
    的地方,中断控制器PIC与处理器的中断引脚直接连接. PIC可通过处理器编程配置.

5.2 PIC与软件中断号
    
    在处理器能处理外部的中断前, 需要对PIC进行配置, 配置工作常作为操作系统初始化任务的一部分. 当然中
    断处理框架也需要提供适当的PIC配置接口函数, 因为设备驱动所管理的设备也许并不是一开始就连接到PIC
    某一中断引脚上的.

    对PIC的配置工作包括:
    1. 设备中断触发电信号的类型, 常见的有水平触发, 边沿触发.
    2. 对外设的中断引脚编号映射到处理器可见的软件中断号IRQ.
    3. 屏蔽掉某此外部设备的中断触发.

    为了让处理器可配置自己, PIC需要提供一系列的控制寄存器, 这些寄存器完成上述所有配置工作, 配置粒度
    可以细分到PIC的每一个中断输入脚P.  此处需要明确定义的概念是软件中断号irq. 它是发生中断时处理器从
    PIC中读到的中断号码, 在中断处理框架内, 会用这个IRQ号来标识一个外设的中断并调用相应的中断处理例程.

    中断电信号被处理的流程是,PIC首先接收到该信号, 如果它没有被屏蔽, 那么PIC应该在INT引脚上产生一个中
    断信号并告诉处理器, 处理器接收到该信号后会从PIC得到一个特定的标识号码, 该号码告诉中断处理框架,
    是设备0发生了中断, 于是中断处理框架会调用设备0的中断处理例程, 此处的这个特定的标识设备0的中断号
    就是软件中断号irq.

    中断向量表, 当异常发生时, CPU暂停当前工作, 转而去处理中断/异常, 因而处理器需要知道从哪去获得这些
    中断或异常的处理函数的目标地址, 中断向量表就是来解决这个问题, 其每一项都是一个中断或异常处理函数
    的入口地址. 外设的中断常对应向量表中的某一项, 这个是通过调用的外部中断处理函数的入口,因此在进入
    通用的中断处理函数号, 系统必须要知道正在处理的中断是哪一个设备产生的, 而这正是由前面的软件中断号
    irq决定的.

    中断向量表中的内容由操作系统在初始化阶段来填写, 对于处部中断, 操作系统实现一个通用的外部中断处理
    函数,然后把这个函数的入口地址放到中断向量表中的对应位置.

5.3 通用的中断处理函数

    通常的动作是把当前任务的上下文寄存器保存在一个特定的中断栈中, 关掉处理器响应外部中断的能力等. 这
    些通用的动作结束部分, 硬件逻辑根据中断向量表中的处部中断对应的入口地址, 接着调用操作系统提供的通
    用中断处理函数.

    不同平台的中断处理函数实现也不尽相同, 但开始部分, 都会设法从pic中得到导致本次中断发生的外部设备
    对应的软件中断号, 这部分通常用汇编语言实现. 然后调用处理函数开始调用C函数, ARM平台上是asm_do_IR
    Q. 中断处理的绝大部分流程都浓缩在这个C函数当中, 当这个函数返回时, 通过中断处理函数余下部分将完成
    中断现场的恢复工作.  接着被中断的任务开始继续执行, 仿佛没有发生中断一样.

.........此时, 如果打开可抢占, 如果中断的路径在内核态, 中断返回时会启动调度器以确定是否进行进程切换,
    如果没有开可抢占, 被中断的路径继续执行, 中断的返回不会导致调度器的介入.

    通常, 处理器接收到外部中断信号后, 硬件逻辑会自动屏蔽处理器响应中断的能力, 如果操作系统的中断处理
    框架不主动打开中断的话, 整个中断处理流程是在关中断的情况下进行的, 所以中断处理函数如果执行时间过
    长, 则将导致系统很长时间无法接收中断, 这样可能会使某此外设丢失数据或系统响应时间变长, 为了解决这
    个问题, 内核提代的中断处理机制分成:HARDIRQ/SOFTIRQ, 前者是在关中断的情况下执行的, 所以执行应该尽
    可能短, 后者在开中断情况下进行的, 此时外设仍可以继续中断处理器. 所以比较耗时的工作在这部分执行.
    在do_IRQ函数中, 对irq_enter的调用可以认为是HARDIRQ部分的开始, 而SOFTIRQ则在irq_exit中完成.
???????但这个时间没有考虑到中断嵌套的概念, 前边说的使用专用的栈来保存当前的任务上下文, 是什么特定的
中断栈呢?

5.4 do_IRQ 函数

    void __irq_entry do_IRQ(unsigned int irq, struct pt_regs *regs)    {
struct pt_regs *old_regs = set_irq_regs(regs);
irq_enter();
check_stack_overflow();
generic_handle_irq(irq);
irq_exit();
set_irq_regs(old_regs);
}
    irq 是该函数的调用者, 是通用中断处理函数从PIC中得到的软件中断号, regs是保存下来的被中断任务的执
    行现场, 不同的处理器有不同的执行现场, 也就是有不同的寄存器.

    首先调用set_irq_regs将一个per-cpu型的指针变量__irq_regs保存到old_regs, 然后将__irq_regs赋值为
    regs, 这样在中断处理过程中, 系统中的每一个cpu都可以通过__irq_regs来访问系统保存的中断现场. 函数
    结束时, 调用set_irq_regs(old_regs)来恢复__irq_regs. __irq_regs一般用来调试或者诊断时打印当前栈
    信息, 也可以通过这些保存的中断现场寄存器判断出被中断的进程当时运行在用户态还是内核态.

    接下来irq_enter会更新系统中的统计量, 同时把当前栈中的preempt_count变量加上HARDIRQ_OFFSET来标识一
    个HARDIRQ中断上下文:preempt_count() += HARDIRQ_OFFSET, HARDIRQ是linux下对中断处理上半部分的称谓,
    与之对应的是中断处理的下半部分SOFTIRQ, 此处irq_enter告诉系统现在进入了中断处理的上半部分. 与irq
    _enter配对的是irq_exit, 在中断处理完成退出时调用, 除了更新一些系统统计量和清除中断上下文标识外,
    它还有一个重要的功能是处理软中断, 也就是中断处理的下半部分.

    check_stack_overflow()用来检查当前中断是否会导致栈溢出, 因为每次中断发生时, 系统都会做保护现场动
    作, 从代码层面, 就是将系统寄存器压入中断栈中, 理想情况下, 中断处理结束时将恢复现场, 将之前的栈中
    保存寄存器弹出堆栈, 因此不会发生溢出, 但如果中断处理函数打开了处理器响应外部中断的能力, 有可能在
    当前中断正在被处理,又收到新的中断, 也就是所谓的中断嵌套, 这将导致系统重复地进行中断现场保护动作,
    甚至发生大量的中断嵌套行为,使得栈不断增长, 从而出现溢出,影响系统稳定性.  为此系统使用check_stack
    _overflow来对栈是否溢出进行检查, 如果本次中断可能导致栈的溢出, 通常会打印当前栈的信息, 对于启用
    了watchdog的系统, 也可能会强制系统reset.

    do_IRQ的核心是调用generic_handle_irq, 后者负责对当前发生的中断进行实际的处理:
    static inline void generic_handle_irq(unsigned int irq)    {
struct irq_desc *desc = &irq_desc[irq];
desk->handle_irq(irq, desc);
}
    函数通中断号irq来索引数组irq_desc, 得到struct irq_desc类型的指针变量desc, 调用其成员函数handle
    _irq对当前中断进行实际处理. irq_desc是struct irq_desc类型的数组, 起着沟通从通用中断处理函数到设
    备特定的中断处理例程之间的桥梁作用.
    struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {    [0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = _RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
}
    NR_IRQS是平台相关的常量, 表示当前平台上可以处理的外部中断的数量. 系统初始化期间通过调用early_ir
    q_init来初始化这个数组:
    int __init early_irq_init(void)
    {
    ....
    }

struct irq_desc {
struct irq_data irq_data; //用来保存软件中断号irq和chip相关的数据
struct timer_rand_state *timer_rand_state;
unsigned int __percpu *kstat_irqs; //一个per-cpu型成员, 用于系统中断计数.
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;
raw_spinlock_t lock;
#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_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name; //handle_irq对应的名字, 最终显示在/proc/interrupts文件中.
} ____cacheline_internodealigned_in_smp;

    handle_irq, 用来指定当前设备中断触发电信号类型的相关函数, 如边沿触发, 那么handle_irq指向边沿触发
    类的处理函数, 如水平触发, 则指向水平触发类的处理函数, 通常, 大家都会写一个常规函数来处理两种触发
    . 在这个接口内部, 才会调用设备的中断服务例程. 特定平台的linux在初始化阶段会提供handle_irq的具体
    实现, 这个内核设计者或BSP模块函担的任务.

    struct irqaction *action:
    action是针对某一设备的中断处理的抽象. 驱动通会request_irq来向其中挂载设备特定的中断处理函数, 相
    对于通用的中断处理函数, 这里的action中的handler为设备中断服务例程ISR. 从通用中断处理函数发起的对
    某一中断处理, 实际上被划分成了两个层次, 一层是handle_irq函数, 它与软件中断号irq一一对应, 代表了
    对IRQ line上的处理动作, 而action则代表着与具体设备相关的中断处理, 通过action成员, 可以在IRQ lin
    e上挂多个设备, 形成所谓的中断链.

    unsigned int istate
    实际内核源码中使用的是istate的宏定义core_internal_state__do_not_mess_with_it, 该成员取代了早期版
    本中的status成员, 主要用于当前中断线IRQ line上的状态管理, 由一组状态位掩码构成. IRQS_ONESHOT, I
    RQS_WAITING和IRQS_PENDING等.

    raw_spinlock_t lock
    操作irq_desc数组时用做互斥保护的成员,irq_desc在多处理器间共享, 即便是单处理器, 也有并发操作数组
    的可能.


    struct irq_data {    unsigned int            irq;        //代表中断号
unsigned int node;
unsigned int state_use_accessors;
struct irq_chip *chip;//代表当前中断来自的PIC, linux通过chip结构来屏蔽不同平台上的
//pic差异
void *handler_data;
void *chip_data;
struct msi_desc *msi_desc;
#ifdef CONFIG_SMP
cpumask_var_t affinity;
#endif
};
    linux将中断的处理分成两部分: HARDIRQ/SOFTIRQ, 前者一般在处理器屏蔽外部中断的情况下工作, 后者在工
    作前会启用处理器响应外部中断的能力. 外设的中断到达处理器后, 首先进入通用中断处理函数, 在完成必要
    的工作后,调用do_IRQ来对中断进行衬际的处理, 后者通过引发本次中断的软件中断号来索引irq_desc数组,
    找到对应的处理函数并调用, 而设备驱动等内核模块通过修改irq_desc数组中对应的action成员来达到安装或
    卸载设备中断服务例程ISR的目的.  设备中断处理调用结束后, 中断流程进入SOFTIRQ部分, 这里如果有等待
    处理的softirq需要处理, 则处理之, 否则返回通用中断处理函数.

.....被中断任务--> cpu中断逻辑--> 保存上下文--> do_IRQ--> irq_desc[irq]--> handle_irq--> ISR--> sof
tirq--> 弹出上下文-->cpu中断逻辑-->运行被中断任务/选择调度器

5.5 struct irq_chip

    struct irq_data中的struct irq_chip *chip成员用来表示一个PIC对象, 如果系统中只有一个PIC, 那么irq
    _desc数组的每一项中的chip都应该指向这个PIC对象. 平台的初始化函数负责实现该平台使用的PIC对象并将
    其安装到irq_desc数组中. PIC对象来实现对PIC的配置, 配置工作主要包括设定外部设备的中断触发信号的类
    型,屏幕或者启动某一设备的中断信号, 向发出中断请求的设备发送中断响应信号等.

    struct irq_chip {    const char      *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);

void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);

int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool
force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);

void (*irq_bus_lock)(struct irq_data *data);
void (*irq_bus_sync_unlock)(struct irq_data *data);

void (*irq_cpu_online)(struct irq_data *data);
void (*irq_cpu_offline)(struct irq_data *data);

void (*irq_suspend)(struct irq_data *data);
void (*irq_resume)(struct irq_data *data);
void (*irq_pm_shutdown)(struct irq_data *data);

void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);

unsigned long flags;

/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
};

 5.6 struct irqaction   

    在struct irq_desc结构中, 成员action指向struct irqaction类型的指针, 驱动通过这个结构将中断处理函
    数挂载在action上.
    struct irqaction {    irq_handler_t handler;
unsigned long flags;
void *dev_id;
struct irqaction *next;
int irq;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
    irq_handler_t handler 指向设备特定的中断服务例程函数的指针.
    typedef irqreturn_t (*irq_handler_t)(int, void*);
    设备驱动负责实现该函数, 然后调用request_irq, 把实现的中断服务例程赋值给handler

    void *dev_id 调用handler时传给它的参数, 在共享IRQ的情况下特别重要, 这种链式action中, 驱动通过
    dev_i来标识自己.

    struct irqaction *next 指向下一个action, 共享IRQ的情形下, action通过next构成一个链表.

    struct proc_dir_entry *dir 中断处理函数中用来创建在proc文件系统中的目标项.

    irq_handler_t thread_fn, struct task_struct *thread, unsigned long thread_flags
    当驱动调用 request_threaded_irq来安装中断处理例程时, 用来实现irq_thread机制.

5.7 irq_set_handler

    现在把焦点集中到irq_desc数组中被中断号irq索引的某一项irq_desk[irq], 对于一个特定的irq_desc[irq]
    , 其上的中断处理分两级, 第一级是调用irq_desc[irq].handle_irq, 第二级是设备特定的中断处理例程ISR,
    在handle_irq的内部通过irq_desc[irq].action->handler调用, 第一级函数在平台初始化期间被安装到irq_
    desc数组中. 第二级函数的注册发生在设备驱动调用request_irq安装对应设备的中断处理例程时.  第一级函
    数主要面向PIC的某一中断线IRQ line,第二级函数面向该中断线上连接的具体设备.

    irq_desc[irq].handle_irq会被do_IRQ调用到.
    typedef void(*irq_flow_handler_t)(unsigned int irq, struct irq_desc *desc);

    为了让平台的初始化代码能够通过handle_irq注册第一级中断处理函数, 内核提供了两个接口函数:irq_set_
    handler和irq_set_chained_handler.
    static inline void irq_set_handler(unsigned int irq, irq_flow_handler_t handle)    {
__irq_set_handler(irq, handle,0, NULL);
}

static inline void irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
__irq_set_handler(irq, handle, 1, NULL);
}
    参数hande就要是安装在irq_desc[irq].handle_irq上的第一级处理函数, 最终的安装通过__irq_set_handler
    来完成.
     void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained, const char *name);

__irq_set_handler在对传弟进来的参数进行一些必要的检查后, 将handle安装到irq_desc[irq]上.
irq_desc[irq].handle_irq = handle;
irq_desc[irq].name = name;
    参数is_chained用来表示irq_desc[irq]对应的项是否支持支持中断共享, 如果是刚将irq_desc[irq].status
    _use_accessors做如下设制: desc->static_use_accessors |= _IRQ_NOPROBE|_IRQ_NOREQUEST;

    _IRQ_NOREQUEST意味着对于irq_desc[irq]而言, 无法通过request_irq来安装中断处理例程. __IRQ_NOPROBE
    意味着无法对irq_desc[irq]执行中断号的探测机制, 因此若irq_desc[irq]对应的项支持中断共享, 那么它装
    不支持自动探测中断号, 这是由自动探测机制原理所决定的.

    IRQD_IRQ_DISABLED表示当前的desc指向一个被禁止的中断线IRQ line, IRQD_IRQ_INPROGRESS表示当前的中断
    线正在处理中, 同一中断irq的嵌套或者共享会出现该情况. desc->action为空表示当前中断线上没有安装设
    备的中断ISR.

    如果当前中断线不处于正在被轮询阶段(IRQS_POLL_INPROGRESS), handle_edge_irq需要将desc->istate的IRQ
    S_PENDING位置1, 同时调用mask_ack_irq(desc)利用PIC对象的irq_mask例程将这条中断线在PIC中屏蔽掉, 然
    后将IRQD_IRQ_MASKED位置1. 这样做的原因是, 对一个正在被处理或者被disable或者压根没有安装设备中断
    的处理例程ISR, 对于这样的中断线来说, 这条正在触发中断信号的IRQ line都应该被屏蔽掉, 为了后续的跟
    踪处理, IRQS_PENDING和IRQD_IRQ_MASKED位需要置1.

    kstat_incr_irqs_this_cpu用来更新与中断相关的一些统计量.

    handle_dege_irq首先调用desc->irq_data.chip->irq_ack(&desc->irq_data), 利用PIC对象的irq_ack例程向
    设备发出一个中断响应信号, 从硬件逻辑角度, 这一步通常使得当前发出中断信号的设备中产生一个信号电平
    的转换, 防止设备在它的中断已经在设备驱动程序中处理时依然不停地发出同一中断信号.
???????设备有中断后会拉低电平, 这时候调用irq_ack难倒会又拉高电平???

    do while循环是handle_edge_irq函数的核心, 通过调用handle_irq_event来对本次中断进行实际的处理. 首
    先对irq对应的desc->action指针进行判断, 如果action是空指针, 表时到目前为止还没有设备驱动程序在这
    条线上安装中断处理例程, 对于这种情况的处理是调用mask_irq函数通过PIC对象的irq-mask例程来屏蔽掉当
    前中断线在pic对应的中断位, 同时将desc->irq_data.state_use_accessors的IRQD_IRQ_MASKED位置1. 在没
    有安装中断处理函数的外部中断, 应该屏蔽掉它.

    中断例程在执行过程中又产生了新的中断 , handle_edge_irq做的处理是将desc->istate上的IRQS_PENDING
    位和desc->irq_data.state_use_accessors上的IRQD_IRQ_MASKED位置1, 同时将在PIC中将对应的中断线屏蔽
    掉. 这样当前中断处理例程结束后while循环条件满足, 重新执行do while,在接下来的循环中, 处于IRQS_PE
    NDING状态的中断线在PIC中的屏蔽将被解除, IRQD_IRQ_MASKED位也被清除掉.

5.8 handle_irq_event

    handle_irq_event的工作比较简单, 它为调用设备驱动程序安装的中断例程做最后的准备工作:
    在进入正式的设备中断例程之前, 通过desc->istate &= ~IRQS_PENDING语句清除掉IRQS_PENDING位, 因为紧
    接下来就会调用设备的中断例程, 所以IRQS_PENDING不应该再置1, 同时将当前中断线设置IRQS_IRQ_INPROGR
    ESS, 表明中断线上一个中断正在被处理. 在handle_irq_event_percpu函数中调用设备专用的中断处理函数.

    函数的主体也是do while循环, 用于遍历action可能形成的链表结构, 大部分情况下一个中断线上只安装一个
    设备中断例程, 但是可以看到内核对共享中断是有运动的. 循环一开始就通过action->handler来调用具体设
    备的中断处理例程, 接下来对action->handler调用的返回值进行处理, 驱动中断处理例程绝大多数返回IRQ_
    HANDLED, 返回IRQ_WAKE_THREAD的情形比较少, 如果返回的是IRQ_WAKE_THREAD, 那么函数将调用irq_wake_t
    hread来唤醒action->thread表示的一个内核线程, 在结束一个具体设备的中断处理例程之后, 函数通过acti
    on=action->next来获得action的下一个节点, do while循环条件是action->next 不为空, 这表明正在处理
    一个共享的中断, 对共享中断来说, 对每一个节点调用其上的handler函数.

............这表明, 一条中断线上的一个处理函数处理成功了, 但另外其它的处理函数还是一样会被调用.

5.9 request_irq

    安装中断服务例程是通过request_irq函数完成的.
    第1个参数irq是当前要安装的中断处理例程对应的软中断号; handler是中断处理例程. flags是标志变量,
    可影响内核在安装ISR时的一些行为. name是当前中断ISR的设备名称, 内核会在proc文件系统中生成name的一
    个入口点, dev是传递到中断处理例程的指针, 在中断共享的情况下, free_irq时被用到, 以区分当前free_r
    iq要释放哪一个struct irqaction对象, 因此必须确保dev参数在内核整个中断处理框架中的唯一性, 由于内
    核在用request_irq安装一个中断处理全程时并不对dev的唯一性检查, 所以驱动程序应该努力做到这一些. 通
    常是将设备驱动程序所管理的与设备相关的某一数据结构对象的指针传入做为dev的实参, 因此也可借助它来
    传递数据.

    request_irq函数的核心是通过request_threaded_irq完成中断处理函数的实际安装工作, 可以看到request_
    irq在调用request_threaded_irq函数时传入的第三个参娄是NULL, 这个参数跟内核中一个用于中断处理的线
    程irq_thread有关, 如果驱动通过request_irq来安装一个中断处理例程, 因为对thread_fn传入的参数是NULL
    , 所以不会涉及irq_thread部分, 但驱动也可以直接调用request_threaded_irq来安装, 此时就有机会使用
    irq_thread机制.
    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);
    如果irqflags中的IRQF_SHARED位被置1, 表明正在安装共享中断, 必须要求驱动提供dev_id, 如果dev_id为空
    是非法情况, 因为free_irq中将无法确定制裁哪一个action. 如果desc->status_use_accessors上的IRQ_NORE
    QUEST位被置1, 表明irq_desc数组中的这一项禁止通过request_threaded_irq来安装中断处理函数, 也是非法
    的.

    检查通过后, 函数调用kzalloc分配一块类型为struct irqaction的地址空间action, 然后根据函数传入的参
    数初始化action, 并调用__setup_irq来安装中断处理函数.
    
    static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new);
    __setup_irq的实质工作是将desc中的action成员指针指向要安装的中断处理例程.

    按照request_irq调用时desc->action是否为空分别进行讨论

    . desc->action为空, 意味着当前尚无驱动程序正在使用这条中断线, 所以只需获得批号向desc->action的指
    针old_ptr, struct irqaction **old_ptr=&desc->action, 然后将request_threaded_irq中新分配的action
    指针赋值给old_ptr即可. *old_ptr = new;

    需要注意的是, 在调用request_irq时, 参数flags中设定了IRQF_TRIGGER_MASK标志, 表时驱动程序要利用req
    uest_irq对irq的触发类型进行配置, 因为desk->irq_data中的chip是PIC的抽象, 所以此时只需调用chip中的
    irq_set_type成员就可配置PIC.

    #define IRQF_TRIGGER_RISING 0x0000001    #define IRQF_TRIGGER_FALLING 0x0000002
#define IRQF_TRIGGER_HIGH 0x0000004
#define IRQF_TRIGGER_LOW 0x0000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW| IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING)
    设定中断触发信号类型的函数为__irq_set_trigger, 其主要功能是通过PIC对象的irq_set_type成员函数设定
    当前中断线上有效的中断触发信号类型, 同时将设定的类型高妙到desc->irq_data.state_use_accessors中.

    因为不是共享中断的情形, 所以当前的request_irq调用将独占irq所对应的中断线所有权, 可以根据设备自身
    需要随意设置其中断触发信号的类型, 在共享中断情况下是不可能的.


    . desc->action不为空, 这表明当前中断线已被安装了中断处理函数, 意味着正在安装一个共享该irq的中断
    处理例程. 一个大体的原则是, 新的安装不能破坏之前已有的中断工作模式, 新的irqaction对象的flags成员
    必须与action链上的已有节点的flags成员做比较, 如果不一致, 安装将不会成功, 返回-EBUSY. 被检查的fl
    ags标志有IRQF_SHARED, IRQF_TRIGGER_MASK, IRQF_ONESHOT及IRQF_PERCPU, 这些都是设备驱动程序在调用re
    quest_irq时通过flags传入的.

    在共享中断的情况下, 如果request_riq调用去设定当前的触发信号类型, __setup_irq函数并不会真正调用
    PIC对象的irq_set_type函数, 而只是检查当前要设定的中断触发信号类型是否与这条线上已经设定的类型相
    符, 如果不符合, __setup_irq会给出一个警告, 当然安装可以成功, 但未必能如期的正常工作.

    如果检查都成功通过, request_irq此时要做的是, 将新分配的action加到action链的末尾.

    在__setup_irq函数的结束部分, 如果desc->dir还是空, 那么调用register_irq_proc在/proc/irq目录下创建
    类似/proc/irq/125这样的新目录项. 最后调用register_handler_proc在action->name不为空的情况下, 会为
    此新action在proc文件系统中创建类似/proc/irq/125/action_name这样的目录. 内核通过proc文件, 方便开
    发者在用户空间查看系统中驱动种马主的中断安装情况.

5.10 中断处理的irq_thread机制

    驱动在调用reqeust_threaded_irq来安装一个中断时, 需要在struct irqaction中实现它的thread_fn成员.
    函数内部会生成一个名为irq_thread的独立线程:

    irq_thread线程被创建出来时将以TASK_INTERRUPTIBLE的状态睡眠等待中断的发生, 录中断发生时action->h
    andler只负责唤醒睡眠的irq_thread, 后者将调用action->thread_fn进行实际的中断处理工作. 因为irq_th
    read本质上是系统中的一个独立进程, 所以采用这种机制将使实质的中断处理工作发生在进程空间, 而不是中
    断上下文中.

5.11 free_irq

    extern void free_irq(unsigned int irq, void *dev_id);
    根据第1个参数irq, 在irq_desc数组中查找对应的action, 遍历action所在的链表, 如果有action->dev_id
    == dev_id, 找到后调用kfree释放掉action所占的空间, 如果action是irq_desc[irq]中唯一的action节点,
    那么释放后还需要把desc->irq_data.state_use_accessors的IRQD_DISABLED位置1, 同时调用irq_desc[irq]
    .chip的irq_shutdown或者irq_disable/irq_mask函数在PIC中屏蔽掉irq所对应的处部中断线. request_irq中
    建立的proc节点也将被删除.

5.12 SOFTIRQ
    
    SOFTIRQ的处理是在do_IRQ函数的irq_exit中实现的.
    
    void irq_exit(void)    {
....
sub_preempt_count(IRQ_EXIT_OFFSET)
if(!in_interrupt() && local_softirq_pending())
invoke_softirq();
....
}   
    函数首先把当前栈中的preempt_count变量减去IRQ_EXIT_OFFSET来标识一个HARDIRQ中断上下文的结束: pree
    mpt_count = irq_exit_offset, 这步动作对应do_IRQ中的irq_enter.

??????为什么sub_preempt_count减去一个IRQ_EXIT_OFFSET来标实中断上下文的结束? 仅是一个标记, 对应irq_e
nter?? 难首中断上下文中开辟了这么大一个空间??
??????为什么下边减去一个地址就代表启动了可抢占性, 难倒减丢了就会在preempt_count中置为1了.

    在没有配置内核可抢占的系统中, IRQ_EXIT_OFFSET=HARDIRQ_OFFSET; 如果配置了可抢占, IRQ_EXIT_OFFSET
    =(HARDIRQ_OFFSET-1), 意味着在HARDIRQ部分结束之后, 内核已经启动可抢占性.

    invoke_softirq是真正处理SOFTIRQ部分的函数, 这个函数的调用有个前题, 就是in_interrupt和local_soft
    irq_pending.

    #define in_interrupt() (preempt_count() * (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
    主要用意是根据当前栈中的preempt_count变量, 来判断当前是否在一个中断上下文中执行, 根据宏定义来看,
    linux认为HARDIRQ, SOFTIRQ和NMI都属于interrupt的范畴. 对于HARDIRQ, 前面讨论do_IRQ时可以看到irq_
    enter和irq_exit之前, 内核在preempt_count()上标示了HARDIRQ_OFFSET, 表示这个是HARDIRQ的上下文.

    preempt_count的低8位与PREEMPT相关, 8-15留给SOFTIRQ, 16-26给HARDIRQ, NMI占据1位.

    #define local_softirq_pending() (irq_stat[smp_processor_id()].__softirq_pending)
    irq_stat是个数组, 具体定义取决于__ARCH_IRQ_STAT宏, 大部分体系架构中是个per-CPU变量.

    基本上可以认为这个是一个per-CPU变量, 系统中的每个CPU都拥有各自的副本.
    typedef struct {    unsigned int __softirq_pending;
}__cacheline_aligned irq_cpustat_t;
    内核__softirq_pending表示当前正在等待被处理的softirq, 每一种softirq在__softirq_pending中占据一位
    , 每个cpu都拥有自己的__softirq_pending变量.

    回到irq_exit, 现在知道invoke_softirq被调用的前提是:当前不在interrupt上下文中而且__softirq_pendi
    ng中有等待的softirq. 不在interrupt上下文中保证了如果代码正大SOFTIRQ部分执行时, 如果发生了中断,
    那么中断处理函数结束HARDIRQ部分时, 将不会处理softirq, 而是直接返回, 这样引前中断的SOFTIRQ将继续
    执行.

    #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED    #define invoke_softirq() __do_softirq()
#else
#define invoke_softirq() do_softirq()
#endif
    __ARCH_IRQ_EXIT_IRQS_DISABLED是个体系架构相关的宏, 用来决定在HARDIRQ部分结束时, 有没有关闭处理器
    响应外部中断的能力, 如果宏义了, 意味着在处理SOFTIRQ部分时, 可以保证外部中断已经关闭, 此时可以直
    接调用__do_softirq, 否则调用do_softirq, 后者最终会调用__do_softirq, 之前要做一些中断屏蔽的事件,
    保证__do_softirq开始执行时中断是关闭的.
    enum {    HI_SOFTIRQ=0,    //实现tasklet
TIMER_SOFTIRQ, //用于定时器
NET_TX_SOFTIRQ, //网络发
NET_RX_SOFTIRQ, //网络收
BLOCK_SOFTIRQ, //用于块设备
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, //实现tasklet
SCHED_SOFTIRQ, //用于调度器
HRTIMER_SOFTIRQ, //用于定时器
RCU_SOFTIRQ,
NR_SOFTIRQS
}
    内核基于上面这个enum的基础上定义了一个struct softirq_action类型的数值softirq_vec, 用来存放softi
    rq对就原处理函数:
    struct softirq_action    {
void (*action)(struct softirq_action *);
}

static struct softirq_action softirq-vec[NR_SOFTIRQS]__cacheline_aligned_in_smp;
    __do_softirq的核心思想是:从CPU本地的__softirq_pending的最低位开始, 依次往高扫描, 如果发现某位为
    1, 说明对应位有个等待softirq处理, 那么就调用softirq_vec数组中对应的项的action函数.

    具体的实现上, 有以下几点需要注意:
    1. __local_bh_disable和_lock_bh_enable用来在preempt_count上标示SOFTIRQ的上下文, 考虑到SOFTIRQ执
    行过程可能会被外部中断, 这可以防止SOFTIRQ部分的重入, 因为只有在非interrupt的上下文中才可以进入S
    OFTIRQ.

    2. 在执行softirq的前后调用了local_irq_enable和local_irq_disable, 说明SOFTIRQ部分在执行时处理器可
    以响应并处理外部的中断.

    3. softirq执行的先后顺序由__softirq_pending中的位决定, 低位的softirq要先于高位的softirq执行.

    4. 在do while循环后, 会再次检商量__softirq_pending是否为0, 这主要是因为SOFTIRQ在执行过程中可能被
    外设中断, 其设备驱动程序在实现该中断处理函数时可能使用了一个softirq, 因此在do while之后, 再检测
    有没有softirq.

    5.如果上面第3步执行超过一定的次数, 则需要唤醒ksoftirqd来处理, 如果SOFTIRQ部分耗费太多时间,会导致
    一个中断处理迟迟无法结束, 这意味着被中断的任务无法继续运行, 为了避免这种情况, linux在初始化期间
    生成一个新的进程ksoftirqd, 该进程的主要任务就是调用do_softirq来执行等待中的softirq, 如果没有需要
    处理就睡眠.

    因此, 为了避免一个中断的softirq耗费太多时间, __do_softirq通过wakeup_softirqd唤醒ksoftirqd, 让调
    度器来平衡当前中断在SOFITIRQ部分的工作负荷.

5.13 irq的自动探测

    探测的原理是, 调用probe_irq_on遍历整个irq_desc数组, 对每个action为空的元素且在该项允许自动探测的
    情形下,将其istate上的IRQS_WAITING位置1, 然后让设备产生一次中断, irq_desc数组中与该设备irq关联的
    那一项的第一级中断函数handle_irq会被调用, 后者将会清除IRQS_WAITING位, 然后调用probe_irq_off再遍
    历一遍irq_desc数组, 对于每个action为空的元素, 查看其istate上的IRQS_WAITING位是否被清0, 如果是,
    那么该元素对应的irq就是关系的.

    probe_irq_on函数的主体是三个for_each_irq_desc所引导的循环. 第一个循环从后向前遍历irq_desc数组,
    遍历过程中对于每一个desc, 只要能满足desc->action为空并且desc->status_use_accessors没有设置_IRQ_
    NOPROBE位, 那么就通过PIC中的irq_startup函数把对应的中断启用起来. desc->action为空说明该irq上还
    没有安装中断处理例程, desc->status_use_accessors没有设置_IRQ_NOPROBE位说明该desc允许被探测, 设
    备所关联的irq只可能存在满足这两个条件的desc中.

    第二个for_each_irq_desc循环依旧从后向前遍历irq_desc数组, 对满足desc->action为空并desc->static_
    use_accessors没有设置__IRQ_NOPROBE位的desc, 将其istate重新设置为:
    desc->istate |= IRQS_AUTODETECT | IRQS_WAITING;

    第三个for_each_irq_desc循环从前向后遍历irq_desc数组, 对于满足(desc->istate & IRQS_AUTODETECT)
    !=0的每一个desc, 说明它正是我们在探测的元素, 此时检查desc->istate上的IRQS_WAITING位有没有被置1.
    因为根据探测的流程, 在调用probe_irq_on时, 驱动还滑让设备产生中断, 因此IRQS_WAITING位不可能被清0
    , 如果清0, 说明该desc上的第一级函数被调用了, 这意味着这个irq所对应的中断线上正在产生无意义的触发
    信号, 对此的处理是通过PIC屏蔽该中断, 然后继续查找下一个irq_desc元素.

    这个函数的返回值如同probe_irq_off中的参数一样无实际用途.
    当probe_irq_off被调用时, 驱动已经让设备产生过一次中断, 所以probe_irq_off需要使用for_each_irq_de
    sc循环从前向后遍历irq_desc数组, 试图找到这样一个desc;(desc->istate & IRQS_AUTODETECT)!=0并且des
    c->istate的IRQS_WAITING位被清除, 这正是probe_irq_off的主要流程. 如果找到设备的irq就返回, 否则返
    回0.

5.14 中断处理例程
    stauct irqaction中的handler指针原型    typedef irqreturn_t (irq_handler_t)(int, void*);

enum irqreturn {
IRQ_NONE, //中断例程发现正在处理一个不是自己设备触发的中断, 此时唯一要做的就是返回该值.
IRQ_HANDLED, //中断例程成功处理了自己设备的中断, 返回该值.
IRQ_WAKE_THREAD, //中断例程被用来作唤醒一个等待在它的irq上的一个进程使用, 此时返回该值.
}
    中断例程在中断上下文中执行, 因此它的实现有某些限制, 在中断上下文中, 不能使用current指针, 因为中
    断上下文不属于某个进程, 所以这样做没有意义, 它们游离在linux进程世界的连续, 因此在这种环境下禁止
    任何形式的进程切换.

5.15 中断共享
    
    中断共享就是指多个设备共享一根中断线, 使用同一个irq, 驱动程序在调用request_irq时应该使用IRQF_SH
    ARED标志, 同时提供dev_id, 之所以提供这个参数, 主要是为了在free_irq时能在action中找到它, 因此这
    个dev_id在中断共享的action链中应该具有唯一性.

    中断发生时, 会调用irq上的每一个action中的handler, 即便不是你的设备产生中断, 你也会被调用到, 因此
    共享中断时的ISR需要判断是不是自己设备产生的中断, 这主要靠读自己设备的中断状态寄存器来完成, 如果
    发现你的设备没有产生中断, 那么ISR只需返回一个IRQ_NONE.


本文欢迎转载

出处:http://blog.csdn.net/dyron


5.16 本章小结
    
    中断处理分成了HARDIRQ和SOFTIRQ两部分, HARDIRQ执行时中断是关闭的, 因为这部分代码应该完成中断处理
    中最关键的任务, 执行时间也尽可能短, 如果需要执行时间很长的操作, 可以将其延迟到SOFTIRQ部分执行.
    因为SOFTIRQ部分在执行时处理器的中断是打开的.