一、中断处理体系结构的初始化

  Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。

1.先了解中断处理体系结构

  通过irq_desc结构数组就可以了解中断处理体系结构,irq_desc结构的数据类型include/linux/irq.h中定义,如下

  1. struct irq_desc {
  2. unsigned int irq;
  3. struct timer_rand_state *timer_rand_state;
  4. unsigned int *kstat_irqs;
  5. #ifdef CONFIG_INTR_REMAP
  6. struct irq_2_iommu *irq_2_iommu;
  7. #endif
  8. irq_flow_handler_t handle_irq; // 当前中断的处理函数入口
  9.  
  10. struct irq_chip *chip; //低层的硬件访问
  11.  
  12. struct msi_desc *msi_desc;
  13. void *handler_data;
  14. void *chip_data;
  15. struct irqaction *action; // 用户提供的中断处理函数链表
  16.  
  17. unsigned int status; //IRQ状态
  18. ........
  19.  
  20. const char *name; //中断的名称
  21.  
  22. } ____cacheline_internodealigned_in_smp;

  第8行:handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数asm_do_IRQ将根据中断号调用相应irq_desc数组项中handle_irq.  handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函数。

  1. asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
  2. {
  3. struct pt_regs *old_regs = set_irq_regs(regs);
  4. struct irq_desc *desc = irq_desc + irq;
  5.  
  6. /*
  7. * Some hardware gives randomly wrong interrupts. Rather
  8. * than crashing, do something sensible.
  9. */
  10. if (irq >= NR_IRQS)
  11. desc = &bad_irq_desc;
  12.  
  13. irq_enter();
  14.  
  15. desc_handle_irq(irq, desc);
  16.  
  17. /* AT91 specific workaround */
  18. irq_finish(irq);
  19.  
  20. irq_exit();
  21. set_irq_regs(old_regs);
  22. }

  irq_desc是中断函数处理的数组,最终处理在desc_handle_irq(irq, desc); desc是中断全局数组,irq中断号

  1. static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
  2. {
  3. desc->handle_irq(irq, desc);
  4. }

  irq_chip结构类型也是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。

  1. struct irq_chip {
  2.   const char *name;
  3.   unsigned int (*startup)(unsigned int irq);//启动中断,如果不设置,缺省为“enable
  4.   void (*shutdown)(unsigned int irq);/*关闭中断,如果不设置,缺省为"disable"*/
  5.   void (*enable)(unsigned int irq); // 使用中断,如果不设置,缺省为"unmask"
  6.   void (*disable)(unsigned int irq);//禁止中断,如果不设置,缺省为“mask”
  7.   void (*ack)(unsigned int irq);   /*响应中断,通常是清除当前中断使得可以接收下一个中断*/
  8.   void (*mask)(unsigned int irq); //屏蔽中断源
  9.  
  10.   void (*mask_ack)(unsigned int irq);//屏蔽和响应中断
  11.  
  12.   void (*unmask)(unsigned int irq);//开启中断源
  13.  
  14.   void (*eoi)(unsigned int irq);
  15.     ........
  16.   const char *typename;
  17. };

  

  irq_desc结构中的irqaction结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的 irqaction 结构链接成一个链表,以action为表头。irqation结构定义如下

  1. struct irqaction {
  2.   irq_handler_t handler; //用户注册的中断处理函数
  3.   unsigned long flags; //中断标志,比如是否共享中断,电平触发还是边沿触发
  4.   const char *name; //用户注册的中断名字
  5.   void *dev_id;   //用户传给上面的handler的参数,还可以用来区分共享中断
  6.   struct irqaction *next; //指向下一个用户注册函数的指针
  7.   int irq; //中断号
  8.   struct proc_dir_entry *dir; 9 };

 

   irq_desc结构数组、它的成员“struct irq_chip *chip” "struct irqaction *action",这3种数据结构构成了中断处理体系的框架。

  下图中描述了Linxu中断处理体系结构的关系图:

  irq_desc结构数组:每个数组项用来描述一个中断

  中断总入口函数asm_do_IRQ 根据中断号调用里面的handle_irq 成员,handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,

  还要调用用户在action链表中注册的中断处理函数。

中断处理流程如下:

  (1)发生中断时,CPU执行异常向量vector_irq的代码

  (2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ

  (3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。

  (4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等

  (5)handle_irq逐个调用用户在aciton链表中注册的处理函数

  可见, 中断体系结构的初始化就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip等成员;用户注册中断时就是构造action链表;用户卸载中断时就是从action链  表中去除不需要的项。

2. 中断体系结构的初始化

  init_IRQ函数被用来初始化中断处理体系结构,代码在arch/arm/kernel/irq.c中

  1. void __init init_IRQ(void)
  2. {
  3.   int irq;
  4.  
  5.   for (irq = ; irq < NR_IRQS; irq++)
  6.     irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
  7. ......
  8.   init_arch_irq();
  9. }

  第5~6行 初始化irq_desc结构数组中每一项的中断状态

  第8行调用架构相关的中断初始化函数。对于S3C2440开发板,这个函数就是s3c24xx_init_irq,移植machine_desc结构中的init_irq成员就指向这个函数

  s3c24xx_init_irq函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有中断设置了芯片相关的数据结构(irq_desc[irq].chip),设置了处理函数入口(irq_desc[irq].handle_irq)。以外部中断EINT4-EINT23为例,用来设置它们的代码如下:

  1. void __init s3c24xx_init_irq(void)
  2. {
  3. ....
  4.  
  5. case IRQ_UART2:
  6. case IRQ_ADCPARENT:
  7. set_irq_chip(irqno, &s3c_irq_level_chip);
  8. set_irq_handler(irqno, handle_level_irq);
  9. break;
  10.  
  11. case IRQ_RESERVED6:
  12. case IRQ_RESERVED24:
  13. /* no IRQ here */
  14. break;
  15.  
  16. ....
  17. set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
  18.  
  19. ...  /*设置外部中断EINT4-EINT23*/
  20. for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
  21. irqdbf("registering irq %d (extended s3c irq)\n", irqno);
  22. set_irq_chip(irqno, &s3c_irqext_chip);
  23. set_irq_handler(irqno, handle_edge_irq); ..............设置中断handler
  24. set_irq_flags(irqno, IRQF_VALID);
  25. }  

  在22行set_irq_chip函数的作用就是“irq_desc[irno].chip = &s3c_irqext_chip”,以后就可能通过irq_desc[irqno].chip结构中的函数指针设置这些外部中断的触发方式(电平触发,边沿触发),使能中断,禁止中断。

  在23行设置这些中断的处理函数入口为handle_edge_irq,即“irq_desc[irqno].handle_irq =handle_edge_irq”.发生中断时,handle_edge_irq函数会调用用户注册的具体处理函数; 在24行设置中断标志为“IRQF_VALID”,表示可以使用它们。init_IRQ函数执行完后,irq_desc数组项的chip,handl_irq成员就都被设置好了。

二、用户注册中断处理函数的过程 

  用户驱动程序通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的action链表添加一个表项。原先的内核中requset_irq函数在kernel/irq/manage.c中定义 

  1. irq:中断号
    handler:处理函数
    irqflags:上升沿触发,下降沿触发,边沿触发等。指定了快速中断或中断共享等中断处理属性.
    *devname:中断名字。通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟)
    文件上,或内核发生中断错误时使用。
    dev_id 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需要参考的数据地址。也用于卸载action
  1. int request_irq(unsigned int irq, irq_handler_t handler,
  2. unsigned long irqflags, const char *devname, void *dev_id)
  3. {
  4. struct irqaction *action;
  5. int retval;
  6.  
  7. ......
  8. if ((irqflags & IRQF_SHARED) && !dev_id)
  9. return -EINVAL;
  10. ......
  11. if (!handler)
  12. return -EINVAL;
  13.  
  14. action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
  15. if (!action)
  16. return -ENOMEM;
  17.  
  18. action->handler = handler;
  19. action->flags = irqflags;
  20. cpus_clear(action->mask);
  21. action->name = devname;
  22. action->next = NULL;
  23. action->dev_id = dev_id;
  24.  
  25. select_smp_affinity(irq);
  26. ......
  27. retval = setup_irq(irq, action);
  28. ......
  29. }

  requset_threaded_irq函数首先使用这4个参数构造一个irqaction结构,然后调用setup_irq函数将它链入链表中,

  setup_irq函数也是在kernel/irq.manage.c中定义,它完成如下3个主要功能

      (1)将新建的irqaction结构链入irq_desc[irq]结构的action链表中,这有两种可能。

              1.如果action链表为空,则直接链入,

       2.否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致,即是否都声明为"可共享的"(IRQF_SHARED)、是否都使用相同的触发方式,如果一致,则将新建的irqation结构链入

      (2)设置irq_desc[irq]结构中chip成员的还没设置的指针,让它们指向一些默认函数

    chip成员在init_IRQ函数初始化中断体系结构的时候已经设置了,这里只是设置其中还没设置的指针

    这通过irq_chip_set_defaults函数来完成,它在kernel/irq/chip.c中定义

  1. void irq_chip_set_defaults(struct irq_chip *chip)
  2. {
  3.   if (!chip->enable)
  4. 4     chip->enable = default_enable;//调用chip->unmask
  5.   if (!chip->disable)
  6.     chip->disable = default_disable;//此函数为空
  7.   if (!chip->startup)
  8. 8     chip->startup = default_startup;//调用chip->enable
  9.   if (!chip->shutdown)
  10.     chip->shutdown = chip->disable   if (!chip->name)
  11.     chip->name = chip->typename;
  12.   if (!chip->end)
  13. 15     chip->end = dummy_irq_chip.end;
  14. }

  

  (3)启动中断

     如果irq_desc[irq]结构中status成员没有被指明IRQ_NOAUTOEN(表示注册中断时不要使用中断),还要调用chip->startup或chip->enable来启动中断,所谓启动中断通常就是使用中断。一般情况下,只有那些“可以自动使能的”中断对应的irq_desc[irq].status才会被指明为IRQ_NOAUTOEN,所以,无论哪种情况,执行request_irq注册中断之后,这个中断就已经被使能了。

   总结一下request_irq函数注册中断后的“成果”

    (1)irq_des[irq]结构中的action链表中已经链入了用户注册的中断处理函数

    (2)中断的触发方式已经被设好

    (3)中断已经被使能

  1.     总之,执行requset_irq函数之后,中断就可以发生并能被处理了
  2. 三、中断处理过程
      

  (1)中断向量调用总入口函数asm_do_IRQ,传入根据中断号irq

  (2)asm_do_IRQ函数根据中断号irq调用irq_desc[irq].handle_irq,它是这个中断的处理函数入口,对于电平触发的中断,

    这个入口函数通常为handle_level_irq,对于边沿触发的中断,这个入口通常为handle_edge_irq

  (3)入口函数首先清除中断,入口函数是handle_level_irq时还要屏蔽中断

  (4)逐个调用用户在irq_desc[irq].aciton链表中注册的中断处理函数

  (5) 入口函数是handle_level_irq时,还要重新开启中断

  

  卸载中断

  卸载中断处理函数这通过free_irq函数来实现,它与request_irq一样,也是在kernel/irq/mangage.c中定义。

  它需要用到两个参数:irq和dev_id,它们与通过request_irq注册中断函数时使用的参数一样,使用中断号irq定位action链表,

  再使用dev_id在action链表中找到要卸载的表项。同一个中断的不同中断处理函数必须使用不同的dev_id来区分,在注册共享中断时参数dev_id必惟一。

  free_irq函数的处理过程与request_irq函数相反

  (1)根据中断号irq,dev_id从action链表中找到表项,将它移除

  (2)如果它是惟一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN 或IRQ_DESC[IRQ].CHIP->DISABLW来关闭中断。

在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top half),和下半部(bottom half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》.

参考:

http://layty.coding.me/2018/11/22/Linux/

http://www.techbulo.com/1844.html

  1.  

1.字符设备驱动------Linux中断处理体系结构的更多相关文章

  1. 字符设备驱动-----Linux中断处理体系结构

    一.中断处理体系结构的初始化 Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称. ...

  2. 字符设备驱动-------Linux异常处理体系结构

    裸机中断流程 外部触发 CPU 发生中断, 强制的跳到异常向量处 跳转到具体函数 保存被中断处的现场(各种寄存器的值) 执行中断处理函数,处理具体任务 恢复被中断的现场 Linux处理异常流程 异常发 ...

  3. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  4. 嵌入式Linux驱动学习之路(十)字符设备驱动-my_led

    首先贴上代码: 字符设备驱动代码: /** *file name: led.c */#include <linux/sched.h> #include <linux/signal.h ...

  5. Linux应用程序访问字符设备驱动详细过程【转】

    本文转载自:http://blog.csdn.net/coding__madman/article/details/51346532 下面先通过一个编写好的内核驱动模块来体验以下字符设备驱动 可以暂时 ...

  6. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  7. Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...

  8. Smart210学习记录----beep linux字符设备驱动

    今天搞定了beep linux字符设备驱动,心里还是很开心的,哈哈...但在完成的过程中却遇到了一个非常棘手的问题,花费了我大量的时间,,,, 还是把问题描述一下吧,好像这个问题很普遍的,网上许多解决 ...

  9. Linux驱动设计——字符设备驱动(一)

    Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...

随机推荐

  1. hdu 1102 Constructing Roads(kruskal || prim)

    求最小生成树.有一点点的变化,就是有的边已经给出来了.所以,最小生成树里面必须有这些边,kruskal和prim算法都能够,prim更简单一些.有一点须要注意,用克鲁斯卡尔算法的时候须要将已经存在的边 ...

  2. beego简单的验证码实现以及验证

    /** * 程序 */package controllers import (    "github.com/astaxie/beego"    "github.com/ ...

  3. WebSocket 笔记

    WebSocket介绍 WebSocket+Flask开启一个WebSocket服务 群聊小Demo 私聊小Demo WebSocket介绍 - 菜鸟教程详解连接 - 下载:pip install g ...

  4. spring之DelegatingFilterProxy

    DelegatingFilterProxy是一个标准servlet Filter的代理,代理实现了Filter接口的spring管理的Bean.支持一个在web.xml的init-param定义的&q ...

  5. ireport 追加新报表

    ireport  追加新报表 /* To change this template, choose Tools | Templates * and open the template in the e ...

  6. SQL函数_Charindex()

    1 charindex()函数的作用是用于发现目标字符串第一次出现在源字符串中出现的开始位置. 语法如下 : select charinde(目标字符串,列名或字符串[,起始位置]) from 表名

  7. pigofzhou的巧克力棒

    Description 众所周知,pigofzhou有许多妹子.有一天,pigofzhou得到了一根巧克力棒,他想把这根巧克力棒分给他的妹子们.具体地,这根巧克力棒长为 n,他想将这根巧克力棒折成 n ...

  8. JSONArray和JSONObject的简单使用

    一.为什么要使用JSONArray和JSONObject 1.后台 -->前台 能够把java对象和集合转化成json字符串格式,这样在前台的ajax方法中能够直接转化成json对象使用 ,从后 ...

  9. opencms9.0安装

    今天安装opencms 9.0遇到了一些问题,因为是初次安装和使用,导致耽误了非常多时间.所以在此记录一下以备以后借鉴. 首先附上安装步骤链接: http://www.51testing.com/ht ...

  10. 验证list的底层数据结构

    <STL源代码剖析>中,指出SGI STL的list底层数据结构式循环双向链表.而且在链表尾端留一个空白节点.让end指向它.因为是双向的,那么list的迭代器必须是Bidirection ...