转自:https://www.cnblogs.com/arnoldlu/p/8659981.html

目录:

Linux中断管理

Linux中断管理 (1)Linux中断管理机制

Linux中断管理 (2)软中断和tasklet

Linux中断管理 (3)workqueue工作队列

关键词:GIC、IAR、EOI、SGI/PPI/SPI、中断映射、中断异常向量、中断上下文、内核中断线程、中断注册。

由于篇幅较大,简单梳理一下内容。

本章主要可以分为三大部分:

讲解硬件背景的1. ARM中断控制器

系统初始化的静态过程:GIC初始化和各中断的中断号映射2. 硬件中断号和Linux中断号的映射;每个中断的注册5. 注册中断

一个中断从产生到执行完毕的动态过程:ARM底层通用部分如何处理3. ARM底层中断处理;GIC部分的处理流程以及上层通用处理部分4. 高层中断处理

这里的高层处理,没有包括下半部。下半部在Linux中断管理 (2)软中断和taskletLinux中断管理 (3)workqueue工作队列中进行介绍。

1. ARM中断控制器

1.1 ARM支持中断类型

ARM GIC-v2支持三种类型的中断:

SGI:软件触发中断(Software Generated Interrupt),通常用于多核间通讯,最多支持16个SGI中断,硬件中断号从ID0~ID15。SGI通常在Linux内核中被用作IPI中断(inter-processor interrupts),并会送达到系统指定的CPU上。

PPI:私有外设中断(Private Peripheral Interrupt),是每个CPU私有的中断。最多支持16个PPI中断,硬件中断号从ID16~ID31。PPI通常会送达到指定的CPU上,应用场景有CPU本地时钟。

SPI:公用外设中断(Shared Peripheral Interrupt),最多可以支持988个外设中断,硬件中断号从ID32~ID1019。

1.2 GIC检测中断流程

GIC主要由两部分组成,分别是仲裁单元(Distributor)和CPU接口模块。

GIC仲裁单元为每一个中断维护一个状态机,分别是:inactive、pending、active and pending、active。

下面是来自IHI0048B GIC-V2规格书3.2.4 Interrupt handling state machine截图:

GIC检测中断流程如下:

(1) 当GIC检测到一个中断发生时,会将该中断标记为pending状态(A1)。

(2) 对处于pending状态的中断,仲裁单元回确定目标CPU,将中断请求发送到这个CPU上。

(3) 对于每个CPU,仲裁单元会从众多pending状态的中断中选择一个优先级最高的中断,发送到目标CPU的CPU Interface模块上。

(4) CPU Interface会决定这个中断是否可以发送给CPU。如果该终端优先级满足要求,GIC会发生一个中断信号给该CPU。

(5) 当一个CPU进入中断异常后,会去读取GICC_IAR寄存器来响应该中断(一般是Linux内核的中断处理程序来读寄存器)。寄存器会返回硬件中断号(hardware interrupt ID),对于SGI中断来说是返回源CPU的ID。

当GIC感知到软件读取了该寄存器后,又分为如下情况:

* 如果该中断源是pending状态,那么转改将变成active。(C) 

* 如果该中断又重新产生,那么pending状态变成active and pending。(D)

* 如果该中断是active状态,现在变成active and pending。(A2)

(6) 当处理器完成中断服务,必须发送一个完成信号EOI(End Of Interrupt)给GIC控制器。软件写GICC_EOIR寄存器,状态变成inactive。(E1)

补充:

(7) 对于level triggered类型中断来说,当触发电平消失,状态从active and pending变成active。(B2)

常用路径是A1->D->B2->E1。

1.2.1 GIC中断抢占

GIC中断控制器支持中断优先级抢占,一个高优先级中断可以抢占一个低优先级且处于active状态的中断,即GIC仲裁单元会记录和比较当前优先级最高的pending状态,然后去抢占当前中断,并且发送这个最高优先级的中断请求给CPU,CPU应答了高优先级中断,暂停低优先级中断服务,进而去处理高优先级中断。

GIC会将pending状态优先级最高的中断请求发送给CPU。

1.2.2 Linux对中断抢占处理

从GIC角度看,GIC会发送高优先级中断请求给CPU。

但是目前CPU处于关中断状态,需要等低优先级中断处理完毕,直到发送EOI给GIC。

然后CPU才会响应pending状态中优先级最高的中断进行处理。

所以Linux下:

1. 高优先级中断无法抢占正在执行的低优先级中断。

2.同处于pending状态的中断,优先响应高优先级中断进行处理。

1.3 GIC中断时序

借助GIC-400 Figure B-2 Signaling physical interrupts理解GIC内部工作原理。

M和N都是SPI类型的外设中断,且通过FIQ来处理,高电平触发,N的优先级比M高,他们的目标CPU相同。

(1) T1时刻:GIC的总裁单元检测到中断M的电平变化。

(2) T2时刻:仲裁单元设置中断M的状态为pending。

(3) T17时刻:CPU Interface模块会拉低nFIQCPU[n]信号。在中断M的状态变成pending后,大概需要15个时钟周期后会拉低nFIQCPU[n]信号来向CPU报告中断请求(assertion)。仲裁单元需要这些时间来计算哪个是pending状态下优先级最高的中断。

(4) T42时刻:仲裁单元检测到另外一个优先级更高的中断N。

(5) T43时刻:仲裁单元用中断N替换中断M为当前pending状态下优先级最高的中断,并设置中断N为pending状态。

(6) T58时刻:经过tph个时钟后,CPU Interface拉低你FIOCPU[n]信号来通知CPU。因为此信号在T17时刻已经被拉低,CPU Interface模块会更新GICC_IAR寄存器的Interrupt ID域,该域的值变成中断N的硬件中断号。

(7) T61~T131时刻:Linux对中断N的服务程序--------------------------------------------------------------中断服务程序处理段,从GICC_IAR开始到GICC_EOIR结束。

  T61时刻:CPU(Linux中断服务例程)读取GICC_IAR寄存器,即软件响应了中断N。这时仲裁单元把中断N的状态从pending变成active and pending。读取GICC_IAR

  T64时刻:在中断N被Linux相应3个时钟内,CPU Interface模块完成对nFIQCPU[n]信号的deasserts,即拉高nFIQCPU[n]信号。

  T126时刻:外设也deassert了该中断N。

  T128时刻:仲裁单元移出了中断N的pending状态。

  T131时刻:Linux服务程序把中断N的硬件ID号写入GICC_EOIR寄存器来完成中断N的全部处理过程。写GICC_EOIR

(8) T146时刻:在向GICC_EOIR寄存器写入中断N中断号后的tph个时钟后,仲裁单元会选择下一个最高优先级中断,即中断M,发送中断请求给CPU Interface模块。CPU Interface会拉低nFIQCPU[n]信号来向CPU报告外设M的中断请求。

(9) T211时刻:Linux中断服务程序读取GICC_IAR寄存器来响应中断,仲裁单元设置中断M的状态为active and pending。

(10) T214时刻:在CPU响应中断后的3个时钟内,CPU Interface模块拉高nFIOCPU[n]信号来完成deassert动作。

那么GICC_IAR和GICC_EOIR分别在Linux什么地方触发的呢?

1.4 Cortex A15 A7实例

2. 硬件中断号和Linux中断号的映射

2.1 硬件中断号:一个串口中断实例

2.2 中断控制器初始化

DTS中GIC定义于arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts:

  1. gic: interrupt-controller@2c001000 {
  2. compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic";------------------此设备的标识符是"arm,cortex-a15-gic"
  3. #interrupt-cells = <3>;
  4. #address-cells = <0>;
  5. interrupt-controller;----------------------------------------------------表示此设备是一个中断控制器
  6. reg = <0 0x2c001000 0 0x1000>,
  7. <0 0x2c002000 0 0x1000>,
  8. <0 0x2c004000 0 0x2000>,
  9. <0 0x2c006000 0 0x2000>;
  10. interrupts = <1 9 0xf04>;
  11. };

struct irq_domain用于描述一个中断控制器。

GIC中断控制器在初始化时解析DTS信息中定义了几个GIC控制器,每个GIC控制器注册一个struct irq_domain数据结构。

  1. struct irq_domain {
  2. struct list_head link;-------------------------用于将irq_domain连接到全局链表irq_domain_list中。
  3. const char *name;------------------------------中断控制器名称
  4. const struct irq_domain_ops *ops;--------------irq domain映射操作使用的方法集合
  5. void *host_data;
  6. unsigned int flags;
  7.  
  8. /* Optional data */
  9. struct device_node *of_node;------------------对应中断控制器的device node
  10. struct irq_domain_chip_generic *gc;
  11. #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
  12. struct irq_domain *parent;
  13. #endif
  14.  
  15. /* reverse map data. The linear map gets appended to the irq_domain */
  16. irq_hw_number_t hwirq_max;--------------------该irq domain支持中断数量的最大值。
  17. unsigned int revmap_direct_max_irq;
  18. unsigned int revmap_size;---------------------线性映射的大小
  19. struct radix_tree_root revmap_tree;-----------Radix Tree映射的根节点
  20. unsigned int linear_revmap[];-----------------线性映射用到的lookup table
  21. }

struct irq_domain_ops定义了irq_domain方法集合,xlate从intspec中解析出硬件中断号和中断类型,intspec[0]和intspec[1]决定中断号,intspec[2]决定中断类型。

  1. struct irq_domain_ops {
  2. int (*match)(struct irq_domain *d, struct device_node *node);
  3. int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
  4. void (*unmap)(struct irq_domain *d, unsigned int virq);
  5. int (*xlate)(struct irq_domain *d, struct device_node *node,
  6. const u32 *intspec, unsigned int intsize,
  7. unsigned long *out_hwirq, unsigned int *out_type);
  8.  
  9. #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
  10. /* extended V2 interfaces to support hierarchy irq_domains */
  11. int (*alloc)(struct irq_domain *d, unsigned int virq,
  12. unsigned int nr_irqs, void *arg);
  13. void (*free)(struct irq_domain *d, unsigned int virq,
  14. unsigned int nr_irqs);
  15. void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
  16. void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
  17. #endif
  18. };
  19.  
  20. static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
  21. .xlate =gic_irq_domain_xlate,
  22. .alloc =gic_irq_domain_alloc,
  23. .free =irq_domain_free_irqs_top,
  24. };
  25.  
  26. static int gic_irq_domain_xlate(struct irq_domain *d,
  27. struct device_node *controller,
  28. const u32 *intspec, unsigned int intsize,
  29. unsigned long *out_hwirq, unsigned int *out_type)
  30. {
  31. ...
  32. /* Get the interrupt number and add 16 to skip over SGIs */
  33. *out_hwirq = intspec[1] + 16;--------------------------------------首先+16跳过SGI类型中断
  34.  
  35. /* For SPIs, we need to add 16 more to get the GIC irq ID number */
  36. if (!intspec[0]) {-------------------------------------------------如果是SPI类型中断,还需要+16,跳过PPI类型中断。
  37. ret = gic_routable_irq_domain_ops->xlate(d, controller,
  38. intspec,
  39. intsize,
  40. out_hwirq,
  41. out_type);
  42.  
  43. if (IS_ERR_VALUE(ret))
  44. return ret;
  45. }
  46.  
  47. *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;---------------------中断触发类型,包括四种上升沿、下降沿、高电平、低电平。
  48.  
  49. return ret;
  50. }
  51.  
  52. static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
  53. unsigned int nr_irqs, void *arg)
  54. {
  55. int i, ret;
  56. irq_hw_number_t hwirq;
  57. unsigned int type = IRQ_TYPE_NONE;
  58. struct of_phandle_args *irq_data = arg;
  59.  
  60. ret = gic_irq_domain_xlate(domain, irq_data->np, irq_data->args,
  61. irq_data->args_count, &hwirq, &type);---------------首先根据args翻译出硬件中断号和中断类型。
  62. if (ret)
  63. return ret;
  64.  
  65. for (i = 0; i < nr_irqs; i++)
  66. gic_irq_domain_map(domain, virq + i, hwirq + i);---------------执行软硬件的映射,并且根据中断类型设置struct irq_desc->handle_irq处理函数。
  67.  
  68. return 0;
  69. }
  70.  
  71. void irq_domain_free_irqs_top(struct irq_domain *domain, unsigned int virq,
  72. unsigned int nr_irqs)
  73. {
  74. int i;
  75.  
  76. for (i = 0; i < nr_irqs; i++) {
  77. irq_set_handler_data(virq + i, NULL);
  78. irq_set_handler(virq + i, NULL);
  79. }
  80. irq_domain_free_irqs_common(domain, virq, nr_irqs);
  81. }

针对SPI类型中断,需要进行+16位移。

  1. static int gic_routable_irq_domain_xlate(struct irq_domain *d,
  2. struct device_node *controller,
  3. const u32 *intspec, unsigned int intsize,
  4. unsigned long *out_hwirq,
  5. unsigned int *out_type)
  6. {
  7. *out_hwirq += 16;
  8. return 0;
  9. }

gic_irq_domain_map()入参有struct irq_domain和软硬件中断号,主要分SGI/PPI一组,SPI一组。

主要工作由irq_domain_set_info()处理,irq_domain_set_hwirq_and_chip()通过Linux中断号获取struct irq_data数据结构,设置关联硬件中断号和struct irq_chip gic_chip关联。

__irq_set_handler()设置中断描述符irq_desc->handler_irq回调函数,对SPI类型来说就是handle_fasteoi_irq()。

  1. static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
  2. irq_hw_number_t hw)
  3. {
  4. if (hw < 32) {
  5. irq_set_percpu_devid(irq);-------------------------------PerCPU类型的中断有自己的特殊flag
  6. irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,
  7. handle_percpu_devid_irq, NULL, NULL);
  8. set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
  9. } else {
  10. irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,
  11. handle_fasteoi_irq, NULL, NULL);
  12. set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
  13.  
  14. gic_routable_irq_domain_ops->map(d, irq, hw);
  15. }
  16. return 0;
  17. }
  18.  
  19. void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,
  20. irq_hw_number_t hwirq, struct irq_chip *chip,
  21. void *chip_data, irq_flow_handler_t handler,
  22. void *handler_data, const char *handler_name)
  23. {
  24. irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
  25. __irq_set_handler(virq, handler, 0, handler_name);
  26. irq_set_handler_data(virq, handler_data);
  27. }
  28.  
  29. int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq,
  30. irq_hw_number_t hwirq, struct irq_chip *chip,
  31. void *chip_data)
  32. {
  33. struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);
  34.  
  35. if (!irq_data)
  36. return -ENOENT;
  37.  
  38. irq_data->hwirq = hwirq;
  39. irq_data->chip = chip ? chip : &no_irq_chip;
  40. irq_data->chip_data = chip_data;
  41.  
  42. return 0;
  43. }
  44.  
  45. void
  46. __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
  47. const char *name)
  48. {
  49. unsigned long flags;
  50. struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
  51. ...
  52. desc->handle_irq = handle;--------------------irq_desc->handler_irqname赋值。
  53. desc->name = name;
  54. ...
  55. }

drivers/irqchip/irq-gic.c定义了"arm,cortex-a15-gic"的处理函数gic_of_init,gic_of_init是GIC控制器的初始化函数。

  1. IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
  2.  
  3. static int gic_cnt __initdata;
  4.  
  5. static int __init
  6. gic_of_init(struct device_node *node, struct device_node *parent)
  7. {
  8. ...
  9. gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
  10. if (!gic_cnt)
  11. gic_init_physaddr(node);
  12.  
  13. if (parent) {
  14. irq = irq_of_parse_and_map(node, 0);
  15. gic_cascade_irq(gic_cnt, irq);
  16. }
  17.  
  18. if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
  19. gicv2m_of_init(node, gic_data[gic_cnt].domain);
  20.  
  21. gic_cnt++;
  22. return 0;
  23. }

gic_init_bases的gic_nr是GIC控制器的序号,主要调用irq_domain_add_linear()分配并函数注册一个irq_domain。

  1. void __init gic_init_bases(unsigned int gic_nr, int irq_start,
  2. void __iomem *dist_base, void __iomem *cpu_base,
  3. u32 percpu_offset, struct device_node *node)
  4. {
  5. irq_hw_number_t hwirq_base;
  6. struct gic_chip_data *gic;
  7. int gic_irqs, irq_base, i;
  8. int nr_routable_irqs;
  9.  
  10. BUG_ON(gic_nr >= MAX_GIC_NR);---------------------------gic_nr不超过系统规定的MAX_GIC_NR
  11.  
  12. gic = &gic_data[gic_nr];--------------------------------struct gic_chip_data类型的全局变量gic_data,序号是GIC控制器序号
  13. ...
  14. /*
  15. * Initialize the CPU interface map to all CPUs.
  16. * It will be refined as each CPU probes its ID.
  17. */
  18. for (i = 0; i < NR_GIC_CPU_IF; i++)
  19. gic_cpu_map[i] = 0xff;
  20.  
  21. /*
  22. * Find out how many interrupts are supported.
  23. * The GIC only supports up to 1020 interrupt sources.
  24. */
  25. gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;------------计算GIC控制器最多支持的中断源个数
  26. gic_irqs = (gic_irqs + 1) * 32;
  27. if (gic_irqs > 1020)----------------------------------------------------------------GIC支持的最大中断数据,此处为1020
  28. gic_irqs = 1020;
  29. gic->gic_irqs = gic_irqs;
  30.  
  31. if (node) { /* DT case */
  32. const struct irq_domain_ops *ops = &gic_irq_domain_hierarchy_ops;--------------GICv2struct irq_domain_ops
  33. ...
  34. gic->domain = irq_domain_add_linear(node, gic_irqs, ops, gic);-----------------注册irq_domain,操作函数使用gic_irq_domain_hierarchy_ops
  35. } else { /* Non-DT case */
  36. ...
  37. }
  38.  
  39. if (WARN_ON(!gic->domain))
  40. return;
  41.  
  42. if (gic_nr == 0) {
  43. #ifdef CONFIG_SMP
  44. set_smp_cross_call(gic_raise_softirq);
  45. register_cpu_notifier(&gic_cpu_notifier);
  46. #endifset_handle_irq(gic_handle_irq);-------在irq_handler中调用handle_arch_irq,这里将handle_arch_irq指向gic_handle_irq,实现了平台中断和具体GIC中断的关联。
  47. }
  48.  
  49. gic_chip.flags |= gic_arch_extn.flags;
  50. gic_dist_init(gic);----------------------GIC Distributer部分初始化
  51. gic_cpu_init(gic);-----------------------GIC CPU Interface部分初始化
  52. gic_pm_init(gic);------------------------GIC PM相关初始化
  53. }

irq_domain_add_linear()->__irq_domain_add()分配并初始化struct irq_domain。

  1. struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,
  2. irq_hw_number_t hwirq_max, int direct_max,
  3. const struct irq_domain_ops *ops,
  4. void *host_data)
  5. {
  6. struct irq_domain *domain;
  7.  
  8. domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
  9. GFP_KERNEL, of_node_to_nid(of_node));-------------domain大小为struct irq_domain加上gic_irqsunsigned int
  10. if (WARN_ON(!domain))
  11. return NULL;
  12.  
  13. /* Fill structure */
  14. INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
  15. domain->ops = ops;
  16. domain->host_data = host_data;
  17. domain->of_node = of_node_get(of_node);
  18. domain->hwirq_max = hwirq_max;
  19. domain->revmap_size = size;
  20. domain->revmap_direct_max_irq = direct_max;
  21. irq_domain_check_hierarchy(domain);
  22.  
  23. mutex_lock(&irq_domain_mutex);
  24. list_add(&domain->link, &irq_domain_list);----------------------将创建好的struct irq_domain加入全局链表irq_domain_list
  25. mutex_unlock(&irq_domain_mutex);
  26.  
  27. pr_debug("Added domain %s\n", domain->name);
  28. return domain;
  29. }

2.3 系统初始化之中断号映射

上一小节是中断控制器GIC的初始化,下面看看一个硬件中断是如何映射到Linux空间的中断的。

customize_machine()是arch_initcall阶段调用,很靠前。

customize_machine

->of_platform_populate

->of_platform_bus_create

->of_amba_device_create

->of_amba_device_create

下面结合dtsi文件看看来龙去脉,arch/arm/boot/dts/vexpress-v2m.dtsi。

  1. /dts-v1/;
  2.  
  3. / {
  4. model = "V2P-CA9";
  5. arm,hbi = <0x191>;
  6. arm,vexpress,site = <0xf>;
  7. compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";
  8. interrupt-parent = <&gic>;
  9. #address-cells = <1>;
  10. #size-cells = <1>;
  11. ...
  12. gic: interrupt-controller@1e001000 {
  13. compatible = "arm,cortex-a9-gic";
  14. #interrupt-cells = <3>;
  15. #address-cells = <0>;
  16. interrupt-controller;
  17. reg = <0x1e001000 0x1000>,
  18. <0x1e000100 0x100>;
  19. };
  20. ...
  21. smb {
  22. compatible = "simple-bus";
  23.  
  24. #address-cells = <2>;
  25. #size-cells = <1>;
  26. ranges = <0 0 0x40000000 0x04000000>,
  27. <1 0 0x44000000 0x04000000>,
  28. <2 0 0x48000000 0x04000000>,
  29. <3 0 0x4c000000 0x04000000>,
  30. <7 0 0x10000000 0x00020000>;
  31.  
  32. #interrupt-cells = <1>;
  33. interrupt-map-mask = <0 0 63>;
  34. interrupt-map = <0 0 0 &gic 0 0 4>,
  35. <0 0 1 &gic 0 1 4>,
  36. ...
  37. /include/ "vexpress-v2m.dtsi"
  38. };
  39. }

  40. vexpress-v2m.dtsi文件:
  41. motherboard {
  42. model = "V2M-P1";
  43. arm,hbi = <0x190>;
  44. arm,vexpress,site = <0>;
  45. compatible = "arm,vexpress,v2m-p1", "simple-bus";
  46. #address-cells = <2>; /* SMB chipselect number and offset */
  47. #size-cells = <1>;
  48. #interrupt-cells = <1>;
  49. ranges;
  50. ...
  51. iofpga@7,00000000 {
  52. compatible = "arm,amba-bus", "simple-bus";
  53. #address-cells = <1>;
  54. #size-cells = <1>;
  55. ranges = <0 7 0 0x20000>;
  56. ...
  57. v2m_serial0: uart@09000 {
  58. compatible = "arm,pl011", "arm,primecell";
  59. reg = <0x09000 0x1000>;
  60. interrupts = <5>;
  61. clocks = <&v2m_oscclk2>, <&smbclk>;
  62. clock-names = "uartclk", "apb_pclk";
  63. };
  64. ...
  65. };
  66. }

这里首先从根目录下查找"simple-bus",从上面可以看出指向smb设备。

smb设备包含vexpress-v2m.dtsi文件,然后在of_platform_bus_create()中遍历所有设备。

  1. const struct of_device_id of_default_bus_match_table[] = {
  2. { .compatible = "simple-bus", },
  3. #ifdef CONFIG_ARM_AMBA
  4. { .compatible = "arm,amba-bus", },
  5. #endif /* CONFIG_ARM_AMBA */
  6. {} /* Empty terminated list */
  7. };
  8.  
  9. static int __init customize_machine(void)
  10. {
  11. ...
  12. of_platform_populate(NULL, of_default_bus_match_table,-----------------找到匹配"simple-bus"的设备,这里指向smb
  13. NULL, NULL);
  14. ...
  15. }
  16.  
  17. int of_platform_populate(struct device_node *root,
  18. const struct of_device_id *matches,
  19. const struct of_dev_auxdata *lookup,
  20. struct device *parent)
  21. {
  22. ...
  23. for_each_child_of_node(root, child) {
  24. rc = of_platform_bus_create(child, matches, lookup, parent, true);-----这里的root指向根目录,即"/"
  25. if (rc)
  26. break;
  27. }
  28. ...
  29. }
  30.  
  31. static int of_platform_bus_create(struct device_node *bus,
  32. const struct of_device_id *matches,
  33. const struct of_dev_auxdata *lookup,
  34. struct device *parent, bool strict)
  35. {
  36. const struct of_dev_auxdata *auxdata;
  37. struct device_node *child;
  38. struct platform_device *dev;
  39. const char *bus_id = NULL;
  40. void *platform_data = NULL;
  41. int rc = 0;
  42.  
  43. /* Make sure it has a compatible property */
  44. if (strict && (!of_get_property(bus, "compatible", NULL))) {
  45. pr_debug("%s() - skipping %s, no compatible prop\n",
  46. __func__, bus->full_name);
  47. return 0;
  48. }
  49.  
  50. auxdata = of_dev_lookup(lookup, bus);
  51. if (auxdata) {
  52. bus_id = auxdata->name;
  53. platform_data = auxdata->platform_data;
  54. }
  55.  
  56. if (of_device_is_compatible(bus, "arm,primecell")) {------当遇到匹配"arm,primecell"设备,创建amba设备。在ofpga@7,00000000中创建uart@09000设备。
  57. /*
  58. * Don't return an error here to keep compatibility with older
  59. * device tree files.
  60. */of_amba_device_create(bus, bus_id, platform_data, parent);
  61. return 0;
  62. }
  63.  
  64. dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
  65. if (!dev || !of_match_node(matches, bus))
  66. return 0;
  67.  
  68. for_each_child_of_node(bus, child) {----------------遍历smb下的所有"simple-bus"设备,这里可以嵌套几层。从smb->motherboard->iofpga@7,00000000
  69. pr_debug(" create child: %s\n", child->full_name);
  70. rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
  71. if (rc) {
  72. of_node_put(child);
  73. break;
  74. }
  75. }
  76. of_node_set_flag(bus, OF_POPULATED_BUS);
  77. return rc;
  78. }

of_amba_device_create创建ARM AMBA类型设备,其中中断部分交给irq_of_parse_and_map()处理。

  1. static struct amba_device *of_amba_device_create(struct device_node *node,
  2. const char *bus_id,
  3. void *platform_data,
  4. struct device *parent)
  5. {
  6. ...
  7. /* Decode the IRQs and address ranges */
  8. for (i = 0; i < AMBA_NR_IRQS; i++)
  9. dev->irq[i] =irq_of_parse_and_map(node, i);
  10. ...
  11. }

以uart@09000为例,irq_of_parse_and_map中的of_irq_parse_one()解析设备中的"interrupts"、"regs"等参数,参数放入struct of_phandle_args中,oirq->args[1]中存放中断号5,oirq->np存放struct device_node。

irq_create_of_mapping()建立硬件中断号到Linux中断号的映射。

irq_create_of_mapping主要调用如下,主要工作交给__irq_domain_alloc_irqs()进行处理。

irq_create_of_mapping

->domain->ops->xlate---------------------------------

->irq_find_mapping

->irq_domain_alloc_irqs

->__irq_domain_alloc_irqs

->irq_domain_alloc_descs

->irq_domain_alloc_irq_data

->irq_domain_alloc_irqs_recursive

->gic_irq_domain_alloc

->gic_irq_domain_map-----------------------进行硬件中断号和软件中断号的映射

->gic_irq_domain_set_info----------------设置重要参数到中断描述符中

->irq_domain_insert_irq

  1. unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
  2. {
  3. struct of_phandle_args oirq;
  4.  
  5. if (of_irq_parse_one(dev, index, &oirq))
  6. return 0;
  7.  
  8. return irq_create_of_mapping(&oirq);
  9. }
  10.  
  11. unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
  12. {
  13. struct irq_domain *domain;
  14. irq_hw_number_t hwirq;
  15. unsigned int type = IRQ_TYPE_NONE;
  16. int virq;
  17.  
  18. domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;---找到设备所属的struct irq_domain结构体。
  19. ...
  20. /* If domain has no translation, then we assume interrupt line */
  21. if (domain->ops->xlate == NULL)
  22. hwirq = irq_data->args[0];
  23. else {
  24. if (domain->ops->xlate(domain, irq_data->np, irq_data->args,-------调用gic_irq_domain_xlate()函数进行硬件中断号到Linux中断号的转换。
  25. irq_data->args_count, &hwirq, &type))
  26. return 0;
  27. }
  28.  
  29. if (irq_domain_is_hierarchy(domain)) {-------------------------可以分层挂载
  30. /*
  31. * If we've already configured this interrupt,
  32. * don't do it again, or hell will break loose.
  33. */
  34. virq =irq_find_mapping(domain, hwirq);-------------------从已有的linear_revmap中寻找Linux中断号。
  35. if (virq)
  36. return virq;
  37.  
  38. virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);---------如果没有找到,重新分配中断映射。参数1表示每次只分配一个中断。
  39. if (virq <= 0)
  40. return 0;
  41. } else {
  42. ...
  43. }
  44.  
  45. /* Set type if specified and different than the current one */
  46. if (type != IRQ_TYPE_NONE &&
  47. type != irq_get_trigger_type(virq))
  48. irq_set_irq_type(virq, type);-----------------------------设置中断触发类型
  49. return virq;
  50. }

struct irq_desc定义了中断描述符,irq_desc[]数组定义了NR_IRQS个中断描述符,数组下标表示IRQ中断号,通过IRQ中断号可以找到对应中断描述符。

struct irq_desc内置了struct irq_data结构体,struct irq_data的irq和hwirq分别对应软件中断号和硬件中断号。通过这两个成员,可以将硬件中断号和软件中断号映射起来。

struct irq_chip定义了中断控制器底层操作相关的方法集合。

  1. struct irq_desc {
  2. struct irq_data irq_data;
  3. unsigned int __percpu *kstat_irqs;
  4. irq_flow_handler_t handle_irq;-----------------根据中断号分类,不同类型中断的处理handle0~31对应handle_percpu_devid_irq32~对应handle_fasteoi_irq
  5. #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
  6. irq_preflow_handler_t preflow_handler;
  7. #endif
  8. struct irqaction *action; /* IRQ action list */
  9. unsigned int status_use_accessors;
  10. unsigned int core_internal_state__do_not_mess_with_it;
  11. unsigned int depth; /* nested irq disables */
  12. unsigned int wake_depth; /* nested wake enables */
  13. unsigned int irq_count; /* For detecting broken IRQs */
  14. unsigned long last_unhandled; /* Aging timer for unhandled count */
  15. unsigned int irqs_unhandled;
  16. atomic_t threads_handled;
  17. int threads_handled_last;
  18. raw_spinlock_t lock;
  19. struct cpumask *percpu_enabled;
  20. #ifdef CONFIG_SMP
  21. const struct cpumask *affinity_hint;
  22. struct irq_affinity_notify *affinity_notify;
  23. #ifdef CONFIG_GENERIC_PENDING_IRQ
  24. cpumask_var_t pending_mask;
  25. #endif
  26. #endif
  27. unsigned long threads_oneshot;-------------是一个位图,每个比特位代表正在处理的共享oneshot类型中断的中断线程。
  28. atomic_t threads_active;-------------------表示正在运行的中断线程个数
  29. wait_queue_head_t wait_for_threads;
  30. #ifdef CONFIG_PM_SLEEP
  31. unsigned int nr_actions;
  32. unsigned int no_suspend_depth;
  33. unsigned int cond_suspend_depth;
  34. unsigned int force_resume_depth;
  35. #endif
  36. #ifdef CONFIG_PROC_FS
  37. struct proc_dir_entry *dir;
  38. #endif
  39. int parent_irq;
  40. struct module *owner;
  41. const char *name;
  42. }
  43.  
  44. struct irq_data {
  45. u32 mask;
  46. unsigned int irq;-----------------Linux软件中断号
  47. unsigned long hwirq;--------------硬件中断号
  48. unsigned int node;
  49. unsigned int state_use_accessors;
  50. struct irq_chip *chip;
  51. struct irq_domain *domain;
  52. #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
  53. struct irq_data *parent_data;
  54. #endif
  55. void *handler_data;
  56. void *chip_data;
  57. struct msi_desc *msi_desc;
  58. cpumask_var_t affinity;
  59. }
  60.  
  61. struct irq_chip {
  62. const char *name;
  63. unsigned int (*irq_startup)(struct irq_data *data);-------------初始化中断
  64. void (*irq_shutdown)(struct irq_data *data);----------------结束中断
  65. void (*irq_enable)(struct irq_data *data);------------------使能中断
  66. void (*irq_disable)(struct irq_data *data);-----------------关闭中断
  67.  
  68. void (*irq_ack)(struct irq_data *data);---------------------应答中断
  69. void (*irq_mask)(struct irq_data *data);--------------------屏蔽中断
  70. void (*irq_mask_ack)(struct irq_data *data);----------------应答并屏蔽中断
  71. void (*irq_unmask)(struct irq_data *data);------------------解除中断屏蔽
  72. void (*irq_eoi)(struct irq_data *data);---------------------发送EOI信号,表示硬件中断处理已经完成。
  73.  
  74. int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);--------绑定中断到某个CPU
  75. int (*irq_retrigger)(struct irq_data *data);----------------重新发送中断到CPU
  76. int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);----------------------------设置触发类型
  77. int (*irq_set_wake)(struct irq_data *data, unsigned int on);-----------------------------------使能/关闭中断在电源管理中的唤醒功能。
  78.  
  79. void (*irq_bus_lock)(struct irq_data *data);
  80. void (*irq_bus_sync_unlock)(struct irq_data *data);
  81.  
  82. void (*irq_cpu_online)(struct irq_data *data);
  83. void (*irq_cpu_offline)(struct irq_data *data);
  84.  
  85. void (*irq_suspend)(struct irq_data *data);
  86. void (*irq_resume)(struct irq_data *data);
  87. void (*irq_pm_shutdown)(struct irq_data *data);
  88. ...
  89. unsigned long flags;
  90. }

gic_chip是特定中断控制器的硬件操作函数集,对于GICv2有屏蔽/去屏蔽、EOI、设置中断触发类型、以及设置或者当前芯片状态。

  1. static const struct irq_chip gic_chip = {
  2. .irq_mask = gic_mask_irq,
  3. .irq_unmask = gic_unmask_irq,
  4. .irq_eoi = gic_eoi_irq,
  5. .irq_set_type = gic_set_type,
  6. .irq_get_irqchip_state = gic_irq_get_irqchip_state,
  7. .irq_set_irqchip_state = gic_irq_set_irqchip_state,
  8. .flags = IRQCHIP_SET_TYPE_MASKED |
  9. IRQCHIP_SKIP_SET_WAKE |
  10. IRQCHIP_MASK_ON_SUSPEND,
  11. };
  12.  
  13. static void gic_mask_irq(struct irq_data *d)
  14. {
  15. gic_poke_irq(d, GIC_DIST_ENABLE_CLEAR);
  16. }
  17.  
  18. static void gic_unmask_irq(struct irq_data *d)
  19. {
  20. gic_poke_irq(d, GIC_DIST_ENABLE_SET);
  21. }
  22.  
  23. static void gic_eoi_irq(struct irq_data *d)
  24. {
  25. writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI);
  26. }
  27.  
  28. static int gic_set_type(struct irq_data *d, unsigned int type)
  29. {
  30. void __iomem *base = gic_dist_base(d);
  31. unsigned int gicirq = gic_irq(d);
  32.  
  33. /* Interrupt configuration for SGIs can't be changed */
  34. if (gicirq < 16)
  35. return -EINVAL;
  36.  
  37. /* SPIs have restrictions on the supported types */
  38. if (gicirq >= 32 && type != IRQ_TYPE_LEVEL_HIGH &&
  39. type != IRQ_TYPE_EDGE_RISING)
  40. return -EINVAL;
  41.  
  42. return gic_configure_irq(gicirq, type, base, NULL);
  43. }
  44.  
  45. static int gic_irq_set_irqchip_state(struct irq_data *d,
  46. enum irqchip_irq_state which, bool val)
  47. {
  48. u32 reg;
  49.  
  50. switch (which) {
  51. case IRQCHIP_STATE_PENDING:
  52. reg = val ? GIC_DIST_PENDING_SET : GIC_DIST_PENDING_CLEAR;
  53. break;
  54.  
  55. case IRQCHIP_STATE_ACTIVE:
  56. reg = val ? GIC_DIST_ACTIVE_SET : GIC_DIST_ACTIVE_CLEAR;
  57. break;
  58.  
  59. case IRQCHIP_STATE_MASKED:
  60. reg = val ? GIC_DIST_ENABLE_CLEAR : GIC_DIST_ENABLE_SET;
  61. break;
  62.  
  63. default:
  64. return -EINVAL;
  65. }
  66.  
  67. gic_poke_irq(d, reg);
  68. return 0;
  69. }
  70.  
  71. static int gic_irq_get_irqchip_state(struct irq_data *d,
  72. enum irqchip_irq_state which, bool *val)
  73. {
  74. switch (which) {
  75. case IRQCHIP_STATE_PENDING:
  76. *val = gic_peek_irq(d, GIC_DIST_PENDING_SET);
  77. break;
  78.  
  79. case IRQCHIP_STATE_ACTIVE:
  80. *val = gic_peek_irq(d, GIC_DIST_ACTIVE_SET);
  81. break;
  82.  
  83. case IRQCHIP_STATE_MASKED:
  84. *val = !gic_peek_irq(d, GIC_DIST_ENABLE_SET);
  85. break;
  86.  
  87. default:
  88. return -EINVAL;
  89. }
  90.  
  91. return 0;
  92. }

irq_domain_alloc_irqs()调用__irq_domain_alloc_irqs()进行struct irq_desc、struct irq_data以及中断映射的处理。

这里的参数nr_irqs一般为1,每次只处理一个中断。

irq_domain_alloc_descs()->irq_alloc_descs()->__irq_alloc_descs()进行struct irq_desc的分配,返回的参数是Linux中断号。

  1. int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
  2. unsigned int nr_irqs, int node, void *arg,
  3. bool realloc)
  4. {
  5. ...
  6. if (realloc && irq_base >= 0) {
  7. virq = irq_base;
  8. } else {
  9. virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node);-------从allocated_irqs位图中查找第一个nr_irqs个空闲的比特位,最终调用__irq_alloc_descs
  10. if (virq < 0) {
  11. pr_debug("cannot allocate IRQ(base %d, count %d)\n",
  12. irq_base, nr_irqs);
  13. return virq;
  14. }
  15. }
  16.  
  17. if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {--------------分配struct irq_data数据结构。
  18. pr_debug("cannot allocate memory for IRQ%d\n", virq);
  19. ret = -ENOMEM;
  20. goto out_free_desc;
  21. }
  22.  
  23. mutex_lock(&irq_domain_mutex);
  24. ret =irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg);----调用struct irq_domain中的alloc回调函数进行硬件中断号和软件中断号的映射。
  25. if (ret < 0) {
  26. mutex_unlock(&irq_domain_mutex);
  27. goto out_free_irq_data;
  28. }
  29. for (i = 0; i < nr_irqs; i++)
  30. irq_domain_insert_irq(virq + i);
  31. mutex_unlock(&irq_domain_mutex);
  32.  
  33. return virq;
  34. ...
  35. }
  36.  
  37. int __ref
  38. __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
  39. struct module *owner)
  40. {
  41. ...
  42. mutex_lock(&sparse_irq_lock);
  43.  
  44. start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,
  45. from, cnt, 0);-------------------在allocated_irqs位图中查找第一个连续cnt个为0的比特位区域。
  46. ...
  47. bitmap_set(allocated_irqs, start, cnt);-------------bitmap_set()设置这些比特位,表示这些比特位已经被占用。
  48. mutex_unlock(&sparse_irq_lock);
  49. return alloc_descs(start, cnt, node, owner);--------这里要看是否定义了CONFIG_SPARSE_IRQ,如果定义了需要动态分配一个struct irq_desc数据结构,以Radix Tree方式存储;没有的话则从irq_desc全局变量中加上偏移即可。
  50.  
  51. err:
  52. mutex_unlock(&sparse_irq_lock);
  53. return ret;
  54. }

irq_domain_alloc_irqs_recursive()会根据实际情况决定中断控制器的递归处理,

  1. static int irq_domain_alloc_irqs_recursive(struct irq_domain *domain,
  2. unsigned int irq_base,
  3. unsigned int nr_irqs, void *arg)
  4. {
  5. int ret = 0;
  6. struct irq_domain *parent = domain->parent;
  7. bool recursive = irq_domain_is_auto_recursive(domain);
  8.  
  9. BUG_ON(recursive && !parent);
  10. if (recursive)
  11. ret = irq_domain_alloc_irqs_recursive(parent, irq_base,
  12. nr_irqs, arg);
  13. if (ret >= 0)
  14. ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);
  15. if (ret < 0 && recursive)
  16. irq_domain_free_irqs_recursive(parent, irq_base, nr_irqs);
  17.  
  18. return ret;
  19. }

至此完成了中断DeviceTree的解析,各数据结构的初始化,以及最主要的硬件中断号到Linux中断号的映射。

3. ARM底层中断处理

ARM底层中断处理的范围是从中断异常触发,到irq_handler。

3.1 中断硬件行为

外设有事件需要报告SoC时,通过和SoC链接的中断管脚发送中断信号,可能是边沿触发信号也可能是电平触发信号。

中断控制器会感知中断信号,中断控制器仲裁单元选择优先级最高的中断发送到CPU Interface,CPU Interface决定将中断分发到哪个CPU核心。

GIC控制器和CPU核心之间通过一个nIRQ(IRQ request input line)信号来通知CPU。

CPU核心感知到中断发生之后,硬件会做如下工作:

  • 保存中断发生时CPSR寄存器内容到SPSR_irq寄存器中
  • 修改CPSR寄存器,让CPU进入处理器模式(processor mode)中的IRQ模式,即修改CPSR寄存器中的M域设置为IRQ Mode。
  • 硬件自动关闭中断IRQ或FIQ,即CPSR中的IRQ位或FIQ位置1。------------硬件自动关中断
  • 保存返回地址到LR_irq寄存器中。
  • 硬件自动调转到中断向量表的IRQ向量。-------------------------------------------从此处开始进入软件领域

当从中断返回时需要软件实现如下操作:

  • 从SPSR_irq寄存器中恢复数据到CPSR中。
  • 从LR_irq中恢复内容到PC中,从而返回到中断点的下一个指令处执行。

3.2 中断异常向量

3.2.1 中断异常向量代码段初始化

内核编译时,异常向量表存放在可执行文件的__init段中:arch/arm/kernel/vmlinux.lds.S。

__vectors_start和__vectors_end指向vectors段的开始和结束地址,__stubs_start和__stubs_end存放异常向量stubs代码段。两者都是页面对齐,大小都为一个页面。

  1. __vectors_start = .;
  2. .vectors 0 : AT(__vectors_start) {
  3. *(.vectors)----------------------------------保存.vectors段数据
  4. }
  5. . = __vectors_start + SIZEOF(.vectors);
  6. __vectors_end = .;
  7.  
  8. __stubs_start = .;
  9. .stubs 0x1000 : AT(__stubs_start) {
  10. *(.stubs)------------------------------------存放.stubs段数据
  11. }
  12. . = __stubs_start + SIZEOF(.stubs);
  13. __stubs_end = .;

系统初始化时会把上述两个段复制到高端地址处,即ixffff_0000:start_kernel->setup_arch->paging_init->devicemap_init。

  1. static void __init devicemaps_init(const struct machine_desc *mdesc)
  2. {
  3. struct map_desc map;
  4. unsigned long addr;
  5. void *vectors;
  6.  
  7. /*
  8. * Allocate the vector page early.
  9. */
  10. vectors = early_alloc(PAGE_SIZE * 2);-------------------------------分配两个页面用于映射到high vectors高端地址。
  11.  
  12. early_trap_init(vectors);-------------------------------------------实现异常向量表的复制动作。...
  13. /*
  14. * Create a mapping for the machine vectors at the high-vectors
  15. * location (0xffff0000). If we aren't using high-vectors, also
  16. * create a mapping at the low-vectors virtual address.
  17. */
  18. map.pfn = __phys_to_pfn(virt_to_phys(vectors));---------------------vectors物理页面号
  19. map.virtual = 0xffff0000;-------------------------------------------待映射到的虚拟地址0xffff_0000~0xffff_0fff
  20. map.length = PAGE_SIZE;---------------------------------------------映射区间大小
  21. #ifdef CONFIG_KUSER_HELPERS
  22. map.type = MT_HIGH_VECTORS;-----------------------------------------映射到high vector
  23. #else
  24. map.type = MT_LOW_VECTORS;
  25. #endif
  26. create_mapping(&map);
  27.  
  28. if (!vectors_high()) {
  29. map.virtual = 0;
  30. map.length = PAGE_SIZE * 2;
  31. map.type = MT_LOW_VECTORS;
  32. create_mapping(&map);
  33. }
  34.  
  35. /* Now create a kernel read-only mapping */
  36. map.pfn += 1;
  37. map.virtual = 0xffff0000 + PAGE_SIZE;------------------------------映射到0xffff_1000~0xffff_1ffff
  38. map.length = PAGE_SIZE;
  39. map.type = MT_LOW_VECTORS;
  40. create_mapping(&map);
  41. ...
  42. }

early_trap_init分别将__vectors_start和__stubs_start两个页面复制到分配的两个页面中。

  1. void __init early_trap_init(void *vectors_base)
  2. {
  3. ...
  4. unsigned long vectors = (unsigned long)vectors_base;
  5. extern char __stubs_start[], __stubs_end[];
  6. extern char __vectors_start[], __vectors_end[];
  7. unsigned i;
  8.  
  9. vectors_page = vectors_base;
  10.  
  11. /*
  12. * Poison the vectors page with an undefined instruction. This
  13. * instruction is chosen to be undefined for both ARM and Thumb
  14. * ISAs. The Thumb version is an undefined instruction with a
  15. * branch back to the undefined instruction.
  16. */
  17. for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
  18. ((u32 *)vectors_base)[i] = 0xe7fddef1;---------------------------第一个页面全部填充未定义指令0xe7fddef1
  19.  
  20. /*
  21. * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
  22. * into the vector page, mapped at 0xffff0000, and ensure these
  23. * are visible to the instruction stream.
  24. */
  25. memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
  26. memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
  27. ...
  28. }

3.2.2 中断异常向量

中断发生后,软件跳转到中断向量表开始vector_irq执行,vector_irq在结尾的时候根据中断发生点所在模式,决定跳转到__irq_usr或者__irq_svc。

vector_irq在arch/arm/kernel/entry-armv.S由宏vector_stub定义。

关于correction==4,需要减去4字节才是返回地址?

vector_stub宏参数correction为4,。

正在执行指令A时发生了中断,由于ARM流水线和指令预取等原因,pc指向A+8B处,那么必须等待指令A执行完毕才能处理该中断,这时PC已经更新到A+12B处。

进入中断响应前夕,pc寄存器的内容被装入lr寄存器中,lr=pc-4,即A+8B地址处。

因此返回时要pc=lr-4,才是被中断时要执行的下一条指令。所以lr要回退4B。

  1. .section .vectors, "ax", %progbits
    __vectors_start:
  2. W(b) vector_rst
  3. W(b) vector_und
  4. W(ldr) pc, __vectors_start + 0x1000
  5. W(b) vector_pabt
  6. W(b) vector_dabt
  7. W(b) vector_addrexcptn
  8. W(b) vector_irq---------------------------------------------------------------跳转到vector_irq
  9. W(b) vector_fiq
  10.  
  11. /*
  12. * Interrupt dispatcher
  13. */
  14. vector_stub irq, IRQ_MODE, 4------------------------------------------------vector_stub宏定义了vector_irq
  15.  
  16. .long __irq_usr @ 0 (USR_26 / USR_32)
  17. .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
  18. .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
  19. .long __irq_svc@ 3 (SVC_26 / SVC_32)----------------------------svc模式数值是0b10011,与上0xf后就是3
  20. .long __irq_invalid @ 4
  21. .long __irq_invalid @ 5
  22. .long __irq_invalid @ 6
  23. .long __irq_invalid @ 7
  24. .long __irq_invalid @ 8
  25. .long __irq_invalid @ 9
  26. .long __irq_invalid @ a
  27. .long __irq_invalid @ b
  28. .long __irq_invalid @ c
  29. .long __irq_invalid @ d
  30. .long __irq_invalid @ e
  31. .long __irq_invalid @ f

.macro vector_stub, name, mode, correction=0------------------------------------vector_stub宏定义

  1. .align 5
  1. vector_\name:
  2. .if \correction
  3. sub lr, lr, #\correction-------------------------------------------------------correction==4解释
  4. .endif
  5.  
  6. @
  7. @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
  8. @ (parent CPSR)
  9. @
  10. stmia sp, {r0, lr} @ save r0, lr
  11. mrs lr, spsr
  12. str lr, [sp, #8] @ save spsr
  13.  
  14. @
  15. @ Prepare for SVC32 mode. IRQs remain disabled.
  16. @
  17. mrs r0, cpsr
  18. eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)---------------------------------修改CPSR寄存器的控制域为SVC模式,为了使中断处理在SVC模式下执行。
  19. msr spsr_cxsf, r0
  20.  
  21. @
  22. @ the branch table must immediately follow this code
  23. @
  24. and lr, lr, #0x0f--------------------------------------------------------------低4位反映了进入中断前CPU的运行模式,9为USR,3为SVC模式。
  25. THUMB( adr r0, 1f )
  26. THUMB( ldr lr, [r0, lr, lsl #2] )-------------------------------------------根据中断发生点所在的模式,给lr寄存器赋值,__irq_usr或者__irq_svc标签处。
  27. mov r0, spk
  28. ARM( ldr lr, [pc, lr, lsl #2] )---------------------------------------------得到的lr就是".long __irq_svc"
  29. movs pc, lr @ branch to handler in SVC mode-------------------------把lr的值赋给pc指针,跳转到__irq_usr或者__irq_svc
  30. ENDPROC(vector_\name)

3.3 内核空间中断处理__irq_svc

__irq_svc处理发生在内核空间的中断,主要svc_entry保护中断现场;irq_handler执行中断处理;如果打开抢占功能,检查是否可以抢占;最后svc_exit执行中断退出处理。

  1. __irq_svc:
  2. svc_entry
  3. irq_handler
  4.  
  5. #ifdef CONFIG_PREEMPT-----------------------------------------------------中断处理结束后,发生抢占的地方♥
  6. get_thread_info tsk
  7. ldr r8, [tsk, #TI_PREEMPT] @ get preempt count--------------获取thread_info->preempt_cpunt变量;preempt_count为0,说明可以抢占进程;preempt_count大于0,表示不能抢占。
  8. ldr r0, [tsk, #TI_FLAGS] @ get flags------------------------获取thread_info->flags变量
  9. teq r8, #0 @ if preempt count != 0
  10. movne r0, #0 @ force flags to 0
  11. tst r0, #_TIF_NEED_RESCHED-----------------------------------------判断是否设置了_TIF_NEED_RESCHED标志位
  12. blne svc_preempt
  13. #endifsvc_exitr5, irq = 1 @ return from exception
  14. UNWIND(.fnend )
  15. ENDPROC(__irq_svc)

svc_entry将中断现场保存到内核栈中,主要是struct pt_regs中的寄存器。

  1. .macro svc_entry, stack_hole=0, trace=1
  2. UNWIND(.fnstart )
  3. UNWIND(.save {r0 - pc} )
  4. sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
  5. #ifdef CONFIG_THUMB2_KERNEL
  6. SPFIX( str r0, [sp] ) @ temporarily saved
  7. SPFIX( mov r0, sp )
  8. SPFIX( tst r0, #4 ) @ test original stack alignment
  9. SPFIX( ldr r0, [sp] ) @ restored
  10. #else
  11. SPFIX( tst sp, #4 )
  12. #endif
  13. SPFIX( subeq sp, sp, #4 )
  14. stmia sp, {r1 - r12}
  15.  
  16. ldmia r0, {r3 - r5}
  17. add r7, sp, #S_SP - 4 @ here for interlock avoidance
  18. mov r6, #-1 @ "" "" "" ""
  19. add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4)
  20. SPFIX( addeq r2, r2, #4 )
  21. str r3, [sp, #-4]! @ save the "real" r0 copied
  22. @ from the exception stack
  23.  
  24. mov r3, lr
  25.  
  26. @
  27. @ We are now ready to fill in the remaining blanks on the stack:
  28. @
  29. @ r2 - sp_svc
  30. @ r3 - lr_svc
  31. @ r4 - lr_<exception>, already fixed up for correct return/restart
  32. @ r5 - spsr_<exception>
  33. @ r6 - orig_r0 (see pt_regs definition in ptrace.h)
  34. @
  35. stmia r7, {r2 - r6}
  36.  
  37. .if \trace
  38. #ifdef CONFIG_TRACE_IRQFLAGS
  39. bl trace_hardirqs_off
  40. #endif
  41. .endif
  42. .endm

svc_exit准备返回中断现场,然后通过ldmia指令从栈中恢复15个寄存器,包括pc内容,至此整个中断完成并返回。

  1. .macro svc_exit, rpsr, irq = 0...
  2. msr spsr_cxsf, \rpsr
  3. ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
  4. .endm

irq_handler进入高层中断处理。

4. 高层中断处理

irq_handler汇编宏是ARCH层和高层中断处理分割线,在这里从汇编跳转到C进行GIC相关处理。

前面介绍了一个中断是如何从硬件中断号映射到Linux中断号的,那么当一个中断产生后它从应将到软件识别中断号,再到转换成Linux中断号是什么路径呢?

这里就从irq_handler开始分析流程:

irq_handler()

->handle_arch_irq()->gic_handle_irq()

->handle_domain_irq()->__handle_domain_irq()-------------读取IAR寄存器,响应中断,获取硬件中断号

->irq_find_mapping()------------------------------------------------将硬件中断号转变成Linux中断号

->generic_handle_irq()---------------------------------------------之后的操作都是Linux中断号

->handle_percpu_devid_irq()-----------------------------------SGI/PPI类型中断处理

->handle_fasteoi_irq()--------------------------------------------SPI类型中断处理

->handle_irq_event()->handle_irq_event_percpu()------执行中断处理核心函数

->action->handler-----------------------------------------------执行primary handler。

->__irq_wake_thread()----------------------------------------根据需要唤醒中断内核线程

4.1 irq_handler

irq_handler宏调用handle_arch_irq函数,这个函数set_handle_irq注册,GICv2对应gic_handle_irq。

  1. .macro irq_handler
  2. #ifdef CONFIG_MULTI_IRQ_HANDLER
  3. ldr r1, =handle_arch_irq
  4. mov r0, sp
  5. adr lr, BSYM(9997f)
  6. ldr pc, [r1]
  7. #else
  8. arch_irq_handler_default
  9. #endif
  10. 9997:
  11. .endm

4.2 gic_handle_irq

git_init_bases设置handle_arch_irq为gic_handle_irq。

  1. void __init gic_init_bases(unsigned int gic_nr, int irq_start,
  2. void __iomem *dist_base, void __iomem *cpu_base,
  3. u32 percpu_offset, struct device_node *node)
  4. {
  5. ...
  6. if (gic_nr == 0) {
  7. ...
  8. set_handle_irq(gic_handle_irq);
  9. }
  10. ...
  11. }
  12.  
  13. void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
  14. {
  15. if (handle_arch_irq)
  16. return;
  17.  
  18. handle_arch_irq = handle_irq;
  19. }

gic_handle_irq对将中断分为两组:SGI、PPI/SPI。

SGI类型中断交给handle_IPI()处理;PPI/SPI类型交给handle_domain_irq处理。

  1. static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
  2. {
  3. u32 irqstat, irqnr;
  4. struct gic_chip_data *gic = &gic_data[0];
  5. void __iomem *cpu_base = gic_data_cpu_base(gic);
  6.  
  7. do {
  8. irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);---读取IAR寄存器,表示响应中断。
  9. irqnr = irqstat & GICC_IAR_INT_ID_MASK;-----------------GICC_IAR_INT_ID_MASK0x3ff,即低10位,所以中断最多从0~1023
  10.  
  11. if (likely(irqnr > 15 && irqnr < 1021)) {
  12. handle_domain_irq(gic->domain, irqnr, regs);
  13. continue;
  14. }
  15. if (irqnr < 16) {---------------------------------------SGI类型的中断是CPU核间通信所用,只有定义了CONFIG_SMP才有意义。
  16. writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);----直接写EOI寄存器,表示结束中断。
  17. #ifdef CONFIG_SMP
  18. handle_IPI(irqnr, regs);----------------------------irqnr表示SGI中断类型
  19. #endif
  20. continue;
  21. }
  22. break;
  23. } while (1);
  24. }

handle_domain_irq调用__handle_domain_irq,其中lookup置为true。

irq_enter显式告诉Linux内核现在要进入中断上下文了,在处理完中断后调用irq_exit告诉Linux已经完成中断处理过程。

  1. int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
  2. bool lookup, struct pt_regs *regs)
  3. {
  4. struct pt_regs *old_regs = set_irq_regs(regs);
  5. unsigned int irq = hwirq;
  6. int ret = 0;
  7.  
  8. irq_enter();-----------------------------------------------通过显式增加hardirq域计数,通知Linux进入中断上下文
  9.  
  10. #ifdef CONFIG_IRQ_DOMAIN
  11. if (lookup)
  12. irq =irq_find_mapping(domain, hwirq);-----------------根据硬件中断号找到对应的软件中断号
  13. #endif
  14.  
  15. /*
  16. * Some hardware gives randomly wrong interrupts. Rather
  17. * than crashing, do something sensible.
  18. */
  19. if (unlikely(!irq || irq >= nr_irqs)) {
  20. ack_bad_irq(irq);
  21. ret = -EINVAL;
  22. } else {
  23. generic_handle_irq(irq);--------------------------------开始具体某一个中断的处理,此处irq已经是Linux中断号。
  24. }
  25.  
  26. irq_exit();-------------------------------------------------退出中断上下文
  27. set_irq_regs(old_regs);
  28. return ret;
  29. }

irq_find_mapping在struct irq_domain中根据hwirq找到Linux环境的irq。

  1. unsigned int irq_find_mapping(struct irq_domain *domain,
  2. irq_hw_number_t hwirq)
  3. {
  4. struct irq_data *data;
  5. ...
  6. /* Check if the hwirq is in the linear revmap. */
  7. if (hwirq < domain->revmap_size)
  8. return domain->linear_revmap[hwirq];----------------linear_revmap[]在__irq_domain_alloc_irqs()->irq_domain_insert_irq()时赋值。
  9. ...
  10. }

generic_handle_irq参数是irq号,irq_to_desc()根据irq号找到对应的struct irq_desc。

然后调用irq_desc->handle_irq处理对应的中断。

  1. int generic_handle_irq(unsigned int irq)
  2. {
  3. struct irq_desc *desc = irq_to_desc(irq);
  4.  
  5. if (!desc)
  6. return -EINVAL;
  7. generic_handle_irq_desc(irq, desc);
  8. return 0;
  9. }
  10.  
  11. static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
  12. {
  13. desc->handle_irq(irq, desc);
  14. }

关于desc->handle_irq来历,在每个中断注册的时候,由gic_irq_domain_map根据hwirq号决定。

gic_irq_domain_map的时候根据hw号决定handle,hw硬件中断号小于32指向handle_percpu_devid_irq,其他情况指向handle_fasteoi_irq

  1. void
  2. __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
  3. const char *name)
  4. {
  5. ...
  6. desc->handle_irq = handle;
  7. desc->name = name;
  8. ...
  9. }

handle_percpu_devid_irq处理0~31的SGI/PPI类型中断,首先响应IAR,然后执行handler,最后发送EOI。

  1. void handle_percpu_devid_irq(unsigned int irq, struct irq_desc *desc)
  2. {
  3. struct irq_chip *chip = irq_desc_get_chip(desc);
  4. struct irqaction *action = desc->action;
  5. void *dev_id = raw_cpu_ptr(action->percpu_dev_id);
  6. irqreturn_t res;
  7.  
  8. kstat_incr_irqs_this_cpu(irq, desc);
  9.  
  10. if (chip->irq_ack)
  11. chip->irq_ack(&desc->irq_data);
  12.  
  13. trace_irq_handler_entry(irq, action);
  14. res = action->handler(irq, dev_id);
  15. trace_irq_handler_exit(irq, action, res);
  16.  
  17. if (chip->irq_eoi)
  18. chip->irq_eoi(&desc->irq_data);-------------------调用gic_eoi_irq()函数
  19. }

irq_enter和irq_exit显式地处理hardirq域计数,两者之间的部分属于中断上下文。

  1. /*
  2. * Enter an interrupt context.
  3. */
  4. void irq_enter(void)
  5. {
  6. rcu_irq_enter();
  7. if (is_idle_task(current) && !in_interrupt()) {
  8. /*
  9. * Prevent raise_softirq from needlessly waking up ksoftirqd
  10. * here, as softirq will be serviced on return from interrupt.
  11. */
  12. local_bh_disable();
  13. tick_irq_enter();
  14. _local_bh_enable();
  15. }
  16.  
  17. __irq_enter();---------------------------------------------显式增加hardirq域计数
  18. }
  19.  
  20. #define __irq_enter() \
  21. do { \
  22. account_irq_enter_time(current); \
  23. preempt_count_add(HARDIRQ_OFFSET); \----------------显式增加hardirq域计数
  24. trace_hardirq_enter(); \
  25. } while (0)
  26.  
  27. void irq_exit(void)
  28. {
  29. #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
  30. local_irq_disable();
  31. #else
  32. WARN_ON_ONCE(!irqs_disabled());
  33. #endif
  34.  
  35. account_irq_exit_time(current);
  36. preempt_count_sub(HARDIRQ_OFFSET);---------------------------显式减少hardirq域计数
  37. if (!in_interrupt() && local_softirq_pending())--------------当前不处于中断上下文,且有pendingsoftirq,进行softirq处理。
  38. invoke_softirq();
  39.  
  40. tick_irq_exit();
  41. rcu_irq_exit();
  42. trace_hardirq_exit(); /* must be last! */
  43. }

4.2.1 中断上下文

判断当前进程是处于中断上下文,还是进程上下文依赖于preempt_count,这个变量在struct thread_info中。

preempt_count计数共32bit,从低到高依次是:

  1. #define PREEMPT_BITS 8
  2. #define SOFTIRQ_BITS 8
  3. #define HARDIRQ_BITS 4
  4. #define NMI_BITS 1
  1. #define hardirq_count() (preempt_count() & HARDIRQ_MASK)-----------------硬件中断计数
  2. #define softirq_count() (preempt_count() & SOFTIRQ_MASK)-----------------软中断计数
  3. #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \----包括NMI、硬中断、软中断三者计数
  4. | NMI_MASK))
  5.  
  6. /*
  7. * Are we doing bottom half or hardware interrupt processing?
  8. *
  9. * in_irq() - We're in (hard) IRQ context
  10. * in_softirq() - We have BH disabled, or are processing softirqs
  11. * in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled
  12. * in_serving_softirq() - We're in softirq context
  13. * in_nmi() - We're in NMI context
  14. * in_task() - We're in task context
  15. *
  16. * Note: due to the BH disabled confusion: in_softirq(),in_interrupt() really
  17. * should not be used in new code.
  18. */
  19. #define in_irq() (hardirq_count())----------------------------判断是否正在硬件中断上下文
  20. #define in_softirq() (softirq_count())------------------------判断是否正在处理软中断或者禁止BH
  21. #define in_interrupt() (irq_count())--------------------------判断是否处于NMI、硬中断、软中断三者之一或者兼有上下文
  22. #define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET)---判断是否处于软中断上下文。
  23. #define in_nmi() (preempt_count() & NMI_MASK)-----------------判断是否处于NMI上下文
  24. #define in_task() (!(preempt_count() & \
  25. (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET)))------判断是否处于进程上下文

思考:in_softirq()和in_serving_softirq()区别?in_interrupt()和in_task()中关于SOFTIRQ_MASK和SOFTIRQ_OFFSET区别?

4.3 handle_fasteoi_irq

handle_fsteoi_irq处理SPI类型的中断,将主要工作交给handle_irq_event()。

handle_irq_event_percpu()首先处理action->handler,有需要则唤醒中断内核线程,执行action->thread_fn。

  1. void
  2. handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
  3. {
  4. struct irq_chip *chip = desc->irq_data.chip;
  5.  
  6. raw_spin_lock(&desc->lock);
  7.  
  8. if (!irq_may_run(desc))
  9. goto out;
  10.  
  11. desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
  12. kstat_incr_irqs_this_cpu(irq, desc);
  13.  
  14. /*
  15. * If its disabled or no action available
  16. * then mask it and get out of here:
  17. */
  18. if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {---如果该中断没有指定action描述符或该中断被关闭了IRQD_IRQ_DISABLED,设置该中断状态为IRQS_PENDING,且mask_irq()屏蔽该中断。
  19. desc->istate |= IRQS_PENDING;
  20. mask_irq(desc);
  21. goto out;
  22. }
  23.  
  24. if (desc->istate & IRQS_ONESHOT)----------------------------------------如果中断是IRQS_ONESHOT,不支持中断嵌套,那么应该调用mask_irq()来屏蔽该中断源。
  25. mask_irq(desc);
  26.  
  27. preflow_handler(desc);--------------------------------------------------取决于是否定义了freflow_handler()
  28. handle_irq_event(desc);
  29.  
  30. cond_unmask_eoi_irq(desc, chip);----------------------------------------根据不同条件执行unmask_irq()解除中断屏蔽,或者执行irq_chip->irq_eoi发送EOI信号,通知GIC中断处理完毕。
  31.  
  32. raw_spin_unlock(&desc->lock);
  33. return;
  34. out:
  35. if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
  36. chip->irq_eoi(&desc->irq_data);
  37. raw_spin_unlock(&desc->lock);
  38. }

handle_irq_event调用handle_irq_event_percpu,执行action->handler(),如有需要唤醒内核中断线程执行action->thread_fn。

  1. irqreturn_t handle_irq_event(struct irq_desc *desc)
  2. {
  3. struct irqaction *action = desc->action;
  4. irqreturn_t ret;
  5.  
  6. desc->istate &= ~IRQS_PENDING;--------------------------清除IRQS_PENDING标志位
  7. irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);---------设置IRQD_IRQ_INPROGRESS标志位,表示正在处理硬件中断。
  8. raw_spin_unlock(&desc->lock);
  9.  
  10. ret =handle_irq_event_percpu(desc, action);
  11.  
  12. raw_spin_lock(&desc->lock);
  13. irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);-------清除IRQD_IRQ_INPROGRESS标志位,表示中断处理结束。
  14. return ret;
  15. }
  16.  
  17. irqreturn_t
  18. handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
  19. {
  20. irqreturn_t retval = IRQ_NONE;
  21. unsigned int flags = 0, irq = desc->irq_data.irq;
  22.  
  23. do {----------------------------------------------------遍历中断描述符中的action链表,依次执行每个action元素中的primary handler回调函数action->handler
  24. irqreturn_t res;
  25.  
  26. trace_irq_handler_entry(irq, action);
  27. res = action->handler(irq, action->dev_id);---------执行struct irqactionhandler函数。
  28. trace_irq_handler_exit(irq, action, res);
  29.  
  30. if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
  31. irq, action->handler))
  32. local_irq_disable();---------------------------
  33.  
  34. switch (res) {
  35. case IRQ_WAKE_THREAD:-------------------------------去唤醒内核中断线程
  36. /*
  37. * Catch drivers which return WAKE_THREAD but
  38. * did not set up a thread function
  39. */
  40. if (unlikely(!action->thread_fn)) {
  41. warn_no_thread(irq, action);----------------输出一个打印表示没有中断处理函数
  42. break;
  43. }
  44.  
  45. __irq_wake_thread(desc, action);----------------唤醒此中断对应的内核线程
  46.  
  47. /* Fall through to add to randomness */
  48. case IRQ_HANDLED:-----------------------------------已经处理完毕,可以结束。
  49. flags |= action->flags;
  50. break;
  51.  
  52. default:
  53. break;
  54. }
  55.  
  56. retval |= res;
  57. action = action->next;
  58. } while (action);
  59.  
  60. add_interrupt_randomness(irq, flags);
  61.  
  62. if (!noirqdebug)
  63. note_interrupt(irq, desc, retval);
  64. return retval;
  65. }

4.3.1 唤醒中断内核线程

__irq_wake_thread唤醒对应中断的内核线程。

  1. void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
  2. {
  3. /*
  4. * In case the thread crashed and was killed we just pretend that
  5. * we handled the interrupt. The hardirq handler has disabled the
  6. * device interrupt, so no irq storm is lurking.
  7. */
  8. if (action->thread->flags & PF_EXITING)
  9. return;
  10.  
  11. /*
  12. * Wake up the handler thread for this action. If the
  13. * RUNTHREAD bit is already set, nothing to do.
  14. */
  15. if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))--------------若已经对IRQF_RUNTHREAD置位,表示已经处于唤醒中,该函数直接返回。
  16. return;
  17.  
  18. desc->threads_oneshot |= action->thread_mask;--------------------thread_mask在共享中断中,每一个action有一个比特位来表示。thread_oneshot每个比特位表示正在处理的共享oneshot类型中断的中断线程。
  19.  
  20. atomic_inc(&desc->threads_active);-------------------------------活跃中断线程计数
  21.  
  22. wake_up_process(action->thread);---------------------------------唤醒actionthread内核线程
  23. }

4.3.2 创建内核中断线程

irq_thread在中断注册的时候,如果条件满足同时创建rq/xx-xx内核中断线程,线程优先级是49(99-50),调度策略是SCHED_FIFO。

  1. static int
  2. __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
  3. {
  4. ...
  5. /*
  6. * Create a handler thread when a thread function is supplied
  7. * and the interrupt does not nest into another interrupt
  8. * thread.
  9. */
  10. if (new->thread_fn && !nested) {
  11. struct task_struct *t;
  12. static const struct sched_param param = {
  13. .sched_priority = MAX_USER_RT_PRIO/2,-------------------------------设置irq内核线程的优先级,在/proc/xxx/sched中看到的prioMAX_RT_PRIO-1-sched_priority
  14. };
  15.  
  16. t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
  17. new->name);--------------------------------------------------创建线程名为irq/xxx-xxx的内核线程,线程执行函数是irq_thread
  18. ...
  19. sched_setscheduler_nocheck(t, SCHED_FIFO, &param);----------------------设置进程调度策略为SCHED_FIFO
  20.  
  21. /*
  22. * We keep the reference to the task struct even if
  23. * the thread dies to avoid that the interrupt code
  24. * references an already freed task_struct.
  25. */
  26. get_task_struct(t);
  27. new->thread = t;-------------------------------------------------------将当前线程和irq_action关联起来
  28.  
  29. set_bit(IRQTF_AFFINITY, &new->thread_flags);--------------------------对中断线程设置CPU亲和性
  30. }
  31. ...
  32. }

4.3.3 内核中断线程执行

irq_thread是中断线程的执行函数,在irq_wait_for_interrupt()中等待。

irq_wait_for_interrupt()中判断IRQTF_RUNTHREAD标志位,没有置位则schedule()换出CPU,进行睡眠。

直到__irq_wake_thread()置位了IRQTF_RUNTHREAD,并且wake_up_process()后,irq_wait_for_interrupt()返回0。

  1. static int irq_thread(void *data)
  2. {
  3. struct callback_head on_exit_work;
  4. struct irqaction *action = data;
  5. struct irq_desc *desc = irq_to_desc(action->irq);
  6. irqreturn_t (*handler_fn)(struct irq_desc *desc,
  7. struct irqaction *action);
  8.  
  9. if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
  10. &action->thread_flags))
  11. handler_fn = irq_forced_thread_fn;
  12. else
  13. handler_fn =irq_thread_fn;
  14.  
  15. init_task_work(&on_exit_work, irq_thread_dtor);
  16. task_work_add(current, &on_exit_work, false);
  17.  
  18. irq_thread_check_affinity(desc, action);
  19.  
  20. while (!irq_wait_for_interrupt(action)) {
  21. irqreturn_t action_ret;
  22.  
  23. irq_thread_check_affinity(desc, action);
  24.  
  25. action_ret = handler_fn(desc, action);-----------执行中断内核线程函数
  26. if (action_ret == IRQ_HANDLED)
  27. atomic_inc(&desc->threads_handled);----------增加threads_handled计数
  28.  
  29. wake_threads_waitq(desc);------------------------唤醒wait_for_threads等待队列
  30. }
  31.  
  32. /*
  33. * This is the regular exit path. __free_irq() is stopping the
  34. * thread via kthread_stop() after calling
  35. * synchronize_irq(). So neither IRQTF_RUNTHREAD nor the
  36. * oneshot mask bit can be set. We cannot verify that as we
  37. * cannot touch the oneshot mask at this point anymore as
  38. * __setup_irq() might have given out currents thread_mask
  39. * again.
  40. */
  41. task_work_cancel(current, irq_thread_dtor);
  42. return 0;
  43. }
  44.  
  45. static int irq_wait_for_interrupt(struct irqaction *action)
  46. {
  47. set_current_state(TASK_INTERRUPTIBLE);
  48.  
  49. while (!kthread_should_stop()) {
  50.  
  51. if (test_and_clear_bit(IRQTF_RUNTHREAD,
  52. &action->thread_flags)) {------------判断thread_flags是否设置IRQTF_RUNTHREAD标志位,如果设置则设置当前状态TASK_RUNNING并返回0。此处和__irq_wake_thread中设置IRQTF_RUNTHREAD对应。
  53. __set_current_state(TASK_RUNNING);
  54. return 0;
  55. }
  56. schedule();-----------------------------------------换出CPU,在此等待睡眠
  57. set_current_state(TASK_INTERRUPTIBLE);
  58. }
  59. __set_current_state(TASK_RUNNING);
  60. return -1;
  61. }
  62.  
  63. static irqreturn_t irq_thread_fn(struct irq_desc *desc,
  64. struct irqaction *action)
  65. {
  66. irqreturn_t ret;
  67.  
  68. ret = action->thread_fn(action->irq, action->dev_id);---执行中断内核线程函数,为request_threaded_irq注册中断参数thread_fn
  69. irq_finalize_oneshot(desc, action);---------------------针对oneshot类型中断收尾处理,主要是去屏蔽中断。
  70. return ret;
  71. }

irq_finalize_oneshot()对ontshot类型的中断进行收尾操作。

  1. static void irq_finalize_oneshot(struct irq_desc *desc,
  2. struct irqaction *action)
  3. {
  4. if (!(desc->istate & IRQS_ONESHOT) ||
  5. action->handler == irq_forced_secondary_handler)
  6. return;
  7. again:
  8. chip_bus_lock(desc);
  9. raw_spin_lock_irq(&desc->lock);
  10.  
  11. /*
  12. * Implausible though it may be we need to protect us against
  13. * the following scenario:
  14. *
  15. * The thread is faster done than the hard interrupt handler
  16. * on the other CPU. If we unmask the irq line then the
  17. * interrupt can come in again and masks the line, leaves due
  18. * to IRQS_INPROGRESS and the irq line is masked forever.
  19. *
  20. * This also serializes the state of shared oneshot handlers
  21. * versus "desc->threads_onehsot |= action->thread_mask;" in
  22. * irq_wake_thread(). See the comment there which explains the
  23. * serialization.
  24. */
  25. if (unlikely(irqd_irq_inprogress(&desc->irq_data))) {-----------必须等待硬件中断处理程序清除IRQD_IRQ_INPROGRESS标志位,见handle_irq_event()。因为该标志位表示硬件中断处理程序正在处理硬件中断,直到硬件中断处理完毕才会清除该标志。
  26. raw_spin_unlock_irq(&desc->lock);
  27. chip_bus_sync_unlock(desc);
  28. cpu_relax();
  29. goto again;
  30. }
  31.  
  32. /*
  33. * Now check again, whether the thread should run. Otherwise
  34. * we would clear the threads_oneshot bit of this thread which
  35. * was just set.
  36. */
  37. if (test_bit(IRQTF_RUNTHREAD, &action->thread_flags))
  38. goto out_unlock;
  39.  
  40. desc->threads_oneshot &= ~action->thread_mask;
  41.  
  42. if (!desc->threads_oneshot && !irqd_irq_disabled(&desc->irq_data) &&
  43. irqd_irq_masked(&desc->irq_data))
  44. unmask_threaded_irq(desc);----------------------------------执行EOI或者去中断屏蔽。
  45.  
  46. out_unlock:
  47. raw_spin_unlock_irq(&desc->lock);
  48. chip_bus_sync_unlock(desc);
  49. }

至此一个中断的执行完毕。

4.4 如何保证IRQS_ONESHOT不嵌套?

5. 注册中断

5.1 中断、线程、中断线程化

中断处理程序包括上半部硬件中断处理程序,下半部处理机制,包括软中断、tasklet、workqueue、中断线程化。

当一个外设中断发生后,内核会执行一个函数来响应该中断,这个函数通常被称为中断处理程序或中断服务例程。

上半部硬件中断处理运行在中断上下文中,要求快速完成并且退出中断。

中断线程化是实时Linux项目开发的一个新特性,目的是降低中断处理对系统实时延迟的影响。

在LInux内核里,中断具有最高优先级,只要有中断发生,内核会暂停手头的工作转向中断处理,等到所有挂起等待的中断和软终端处理完毕后才会执行进程调度,因此这个过程会造成实时任务得不到及时处理。

中断上下文总是抢占进程上下文,中断上下文不仅是中断处理程序,还包括softirq、tasklet等,中断上下文成了优化Linux实时性的最大挑战之一。

5.2 中断注册接口

IRQF_*描述的中断标志位用于request_threaded_irq()申请中断时描述该中断的特性。

IRQS_*的中断标志位是位于struct irq_desc数据结构的istate成员,也即core_internal_state__do_not_mess_with_it

IRQD_*是struct irq_data数据结构中的state_use_accessors成员一组中断标志位,通常用于描述底层中断状态。

关于IRQF_ONESHOT特别解释:必须在硬件中断处理结束之后才能重新使能中断;线程化中断处理过程中保持中断线处于关闭状态,直到该中断线上所有thread_fn执行完毕。

  1. #define IRQF_TRIGGER_NONE 0x00000000
  2. #define IRQF_TRIGGER_RISING 0x00000001---------------------------上升沿触发
  3. #define IRQF_TRIGGER_FALLING 0x00000002--------------------------下降沿触发
  4. #define IRQF_TRIGGER_HIGH 0x00000004-----------------------------高电平触发
  5. #define IRQF_TRIGGER_LOW 0x00000008------------------------------地电平触发
  6. #define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
  7. IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)--------四种触发类型
  8. #define IRQF_TRIGGER_PROBE 0x00000010
  9.  
  10. #define IRQF_SHARED 0x00000080-------------------------------多个设备共享一个中断号
  11. #define IRQF_PROBE_SHARED 0x00000100-----------------------------中断处理程序允许sharing mismatch发生
  12. #define __IRQF_TIMER 0x00000200------------------------------标记一个时钟中断
  13. #define IRQF_PERCPU 0x00000400-------------------------------属于某个特定CPU的中断
  14. #define IRQF_NOBALANCING 0x00000800------------------------------禁止在多CPU之间做中断均衡
  15. #define IRQF_IRQPOLL 0x00001000------------------------------中断被用作轮询
  16. #define IRQF_ONESHOT 0x00002000------------------------------一次性触发中断,不允许嵌套。
  17. #define IRQF_NO_SUSPEND 0x00004000---------------------------在系统睡眠过程中不要关闭该中断
  18. #define IRQF_FORCE_RESUME 0x00008000-----------------------------在系统唤醒过程中必须抢孩子打开该中断
  19. #define IRQF_NO_THREAD 0x00010000----------------------------表示该中断不会给线程化
  20. #define IRQF_EARLY_RESUME 0x00020000
  21. #define IRQF_COND_SUSPEND 0x00040000
  22.  
  23. #define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
  24.  
  25. enum {
  26. IRQS_AUTODETECT = 0x00000001,-------------------处于自动侦测状态
  27. IRQS_SPURIOUS_DISABLED = 0x00000002,----------------被视为“伪中断”并被禁用
  28. IRQS_POLL_INPROGRESS = 0x00000008,------------------正处于轮询调用action
  29. IRQS_ONESHOT = 0x00000020,----------------------表示只执行一次,由IRQF_ONESHOT转换而来,在中断线程化执行完成后需要小心对待,见irq_finalize_oneshot()。
  30. IRQS_REPLAY = 0x00000040,-----------------------重新发送一次中断
  31. IRQS_WAITING = 0x00000080,----------------------处于等待状态
  32. IRQS_PENDING = 0x00000200,----------------------该中断被挂起
  33. IRQS_SUSPENDED = 0x00000800,--------------------该中断被暂停
  34. };
  35.  
  36. enum {
  37. IRQD_TRIGGER_MASK = 0xf,-------------------------该中断触发类型
  38. IRQD_SETAFFINITY_PENDING = (1 << 8),
  39. IRQD_NO_BALANCING = (1 << 10),
  40. IRQD_PER_CPU = (1 << 11),
  41. IRQD_AFFINITY_SET = (1 << 12),
  42. IRQD_LEVEL = (1 << 13),
  43. IRQD_WAKEUP_STATE = (1 << 14),
  44. IRQD_MOVE_PCNTXT = (1 << 15),
  45. IRQD_IRQ_DISABLED = (1 << 16),--------------------该中断处于关闭状态
  46. IRQD_IRQ_MASKED = (1 << 17),------------------该中断被屏蔽中
  47. IRQD_IRQ_INPROGRESS = (1 << 18),------------------该中断正在被处理中
  48. IRQD_WAKEUP_ARMED = (1 << 19),
  49. IRQD_FORWARDED_TO_VCPU = (1 << 20),
  50. };

struct irqaction是每个中断的irqaction描述符。

  1. struct irqaction {
  2. irq_handler_t handler;-----------primary handler函数指针
  3. void *dev_id;----------------传递给中断处理程序的参数
  4. void __percpu *percpu_dev_id;
  5. struct irqaction *next;
  6. irq_handler_t thread_fn;---------中断线程处理程序的函数指针
  7. struct task_struct *thread;----------中断线程的task_struct数据结构
  8. unsigned int irq;----------------Linux软件中断号
  9. unsigned int flags;--------------注册中断时用的中断标志位,IRQF_*。
  10. unsigned long thread_flags;------中断线程相关标志位
  11. unsigned long thread_mask;-------在共享中断中,每一个action有一个比特位来表示。
  12. const char *name;----------------中断线程名称
  13. struct proc_dir_entry *dir;
  14. } ____cacheline_internodealigned_in_smp;

request_irq调用request_threaded_irq进行中断注册,只是少了一个thread_fn参数。这也是两则的区别所在,request_irq不能注册线程化中断。

irq:Linux软件中断号,不是硬件中断号。

handler:指primary handler,也即request_irq的中断处理函数handler。

thread_fn:中断线程化的处理函数。

irqflags:中断标志位,见IRQF_*解释。

devname:中断名称。

dev_id:传递给中断处理程序的参数。

handler和thread_fn分别被赋给action->handler和action->thread_fn,组合如下:

  handler thread_fn  
1 先执行handler,然后条件执行thread_fn。
2 × 等同于request_irq()
3 × handler=irq_default_primary_handler
4 × × 返回-EINVAL

很多request_threaded_irq()使用第3种组合,irq_default_primary_handler()返回IRQ_WAKE_THREAD,将工作交给thread_fn进行处理。

第2种组合相当于request_irq()。

第4种组合不被允许,因为中断得不到任何处理。

第1种组合较复杂,在handler根据实际情况返回IRQ_WAKE_THREAD(唤醒内核中断线程)或者IRQ_HANDLED(中断已经处理完毕,不需要唤醒中断内核线程)。

request_threaded_irq()对参数进行检查之后,分配struct irqaction并填充,然后将注册工作交给__setup_irq()。

  1. static inline int __must_check
  2. request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
  3. const char *name, void *dev)
  4. {
  5. returnrequest_threaded_irq(irq, handler, NULL, flags, name, dev);
  6. }
  7.  
  8. int request_threaded_irq(unsigned int irq, irq_handler_t handler,
  9. irq_handler_t thread_fn, unsigned long irqflags,
  10. const char *devname, void *dev_id)
  11. {
  12. ...
  13. if (((irqflags & IRQF_SHARED) && !dev_id) ||-----------------------------共享中断设备必须传递啊dev_id参数来区分是哪个共享外设的中断
  14. (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
  15. ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
  16. return -EINVAL;
  17.  
  18. desc = irq_to_desc(irq);--------------------------------------------------通过Linux中断号找到对应中断描述符struct irq_desc
  19. if (!desc)
  20. return -EINVAL;
  21. ...
  22. if (!handler) {
  23. if (!thread_fn)
  24. return -EINVAL;---------------------------------------------------handlerthread_fn不能同时为NULL
  25. handler = irq_default_primary_handler;--------------------------------没有设置handlerirq_default_primary_handler()默认返回IRQ_WAKE_THREAD
  26. }
  27.  
  28. action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);-------------------分配struct irqaction,并填充相应成员
  29. if (!action)
  30. return -ENOMEM;
  31.  
  32. action->handler = handler;
  33. action->thread_fn = thread_fn;
  34. action->flags = irqflags;
  35. action->name = devname;
  36. action->dev_id = dev_id;
  37.  
  38. chip_bus_lock(desc);-------------------------------------------------------调用desc->irq_data.chip->irq_bus_lock()进行加锁保护
  39. retval =__setup_irq(irq, desc, action);
  40. chip_bus_sync_unlock(desc);
  41.  
  42. if (retval)
  43. kfree(action);
  44. ...
  45. return retval;
  46. }

5.3 __setup_irq

一张图

__setup_irq()首先做参数检查,然后根据需要创建中断内核线程,这期间处理中断嵌套、oneshot、中断共享等问题。

还设置了中断触发类型设置,中断使能等工作。最后根据需要唤醒中断内核线程,并创建此中断相关sysfs节点。

  1. /*
  2. * Internal function to register an irqaction - typically used to
  3. * allocate special interrupts that are part of the architecture.
  4. */
  5. static int
  6. __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
  7. {
  8. struct irqaction *old, **old_ptr;
  9. unsigned long flags, thread_mask = 0;
  10. int ret, nested, shared = 0;
  11. cpumask_var_t mask;
  12.  
  13. if (!desc)
  14. return -EINVAL;
  15.  
  16. if (desc->irq_data.chip == &no_irq_chip)----------------------表示没有正确初始化中断控制器,对于GICv2gic_irq_domain_alloc()中指定chipgic_chip
  17. return -ENOSYS;
  18. if (!try_module_get(desc->owner))
  19. return -ENODEV;
  20.  
  21. /*
  22. * Check whether the interrupt nests into another interrupt
  23. * thread.
  24. */
  25. nested = irq_settings_is_nested_thread(desc);-----------------对于设置了_IRQ_NESTED_THREAD嵌套类型的中断描述符,必须指定thread_fn
  26. if (nested) {
  27. if (!new->thread_fn) {
  28. ret = -EINVAL;
  29. goto out_mput;
  30. }
  31. /*
  32. * Replace the primary handler which was provided from
  33. * the driver for non nested interrupt handling by the
  34. * dummy function which warns when called.
  35. */
  36. new->handler = irq_nested_primary_handler;
  37. } else {
  38. if (irq_settings_can_thread(desc))-----------------------判断该中断是否可以被线程化,如果没有设置_IRQ_NOTHREAD表示可以被强制线程化。
  39. irq_setup_forced_threading(new);
  40. }
  41.  
  42. /*
  43. * Create a handler thread when a thread function is supplied
  44. * and the interrupt does not nest into another interrupt
  45. * thread.
  46. */
  47. if (new->thread_fn && !nested) {-----------------------------对不支持嵌套的线程化中断创建一个内核线程,实时SCHED_FIFO,优先级为50的实时线程。
  48. struct task_struct *t;
  49. static const struct sched_param param = {
  50. .sched_priority = MAX_USER_RT_PRIO/2,
  51. };
  52.  
  53. t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
  54. new->name);-----------------------------------由irq、中断号、中断名组成的中断线程名,处理函数是irq_thread()。
  55. if (IS_ERR(t)) {
  56. ret = PTR_ERR(t);
  57. goto out_mput;
  58. }
  59.  
  60. sched_setscheduler_nocheck(t, SCHED_FIFO, &param);
  61.  
  62. get_task_struct(t);
  63. new->thread = t;
  64.  
  65. set_bit(IRQTF_AFFINITY, &new->thread_flags);
  66. }
  67.  
  68. if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {
  69. ret = -ENOMEM;
  70. goto out_thread;
  71. }
  72.  
  73. /*
  74. * Drivers are often written to work w/o knowledge about the
  75. * underlying irq chip implementation, so a request for a
  76. * threaded irq without a primary hard irq context handler
  77. * requires the ONESHOT flag to be set. Some irq chips like
  78. * MSI based interrupts are per se one shot safe. Check the
  79. * chip flags, so we can avoid the unmask dance at the end of
  80. * the threaded handler for those.
  81. */
  82. if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)----------表示该中断控制器不支持中断嵌套,所以flags去掉IRQF_ONESHOT
  83. new->flags &= ~IRQF_ONESHOT;
  84.  
  85. raw_spin_lock_irqsave(&desc->lock, flags);
  86. old_ptr = &desc->action;
  87. old = *old_ptr;
  88. if (old) {-----------------------------------------------------old指向desc->action指向的链表,old不为空说明已经有中断添加到中断描述符irq_desc中,说明这是一个共享中断。shared=1
  89. ...
  90. /* add new interrupt at end of irq queue */
  91. do {
  92. /*
  93. * Or all existing action->thread_mask bits,
  94. * so we can find the next zero bit for this
  95. * new action.
  96. */
  97. thread_mask |= old->thread_mask;
  98. old_ptr = &old->next;
  99. old = *old_ptr;
  100. } while (old);
  101. shared = 1;
  102. }
  103.  
  104. /*
  105. * Setup the thread mask for this irqaction for ONESHOT. For
  106. * !ONESHOT irqs the thread mask is 0 so we can avoid a
  107. * conditional in irq_wake_thread().
  108. */
  109. if (new->flags & IRQF_ONESHOT) {
  110. /*
  111. * Unlikely to have 32 resp 64 irqs sharing one line,
  112. * but who knows.
  113. */
  114. if (thread_mask == ~0UL) {
  115. ret = -EBUSY;
  116. goto out_mask;
  117. }
  118.  
  119. new->thread_mask = 1 << ffz(thread_mask);
  120.  
  121. } else if (new->handler == irq_default_primary_handler &&---------非IRQF_ONESHOT类型中断,且handler使用默认irq_default_primary_handler(),如果中断触发类型是LEVEL,如果中断出发后不清中断容易引发中断风暴。提醒驱动开发者,没有primary handler且中断控制器不支持硬件oneshot,必须显式指定IRQF_ONESHOT表示位。
  122. !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
  123.  
  124. pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",
  125. irq);
  126. ret = -EINVAL;
  127. goto out_mask;
  128. }
  129.  
  130. if (!shared) {-------------------------------------------------非共享中断情况
  131. ret = irq_request_resources(desc);
  132. if (ret) {
  133. pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
  134. new->name, irq, desc->irq_data.chip->name);
  135. goto out_mask;
  136. }
  137.  
  138. init_waitqueue_head(&desc->wait_for_threads);
  139.  
  140. /* Setup the type (level, edge polarity) if configured: */
  141. if (new->flags & IRQF_TRIGGER_MASK) {
  142. ret = __irq_set_trigger(desc, irq,-------------------调用gic_chip->irq_set_type设置中断触发类型。
  143. new->flags & IRQF_TRIGGER_MASK);
  144.  
  145. if (ret)
  146. goto out_mask;
  147. }
  148.  
  149. desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
  150. IRQS_ONESHOT | IRQS_WAITING);
  151. irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);---------清IRQD_IRQ_INPROGRESS标志位
  152.  
  153. if (new->flags & IRQF_PERCPU) {
  154. irqd_set(&desc->irq_data, IRQD_PER_CPU);
  155. irq_settings_set_per_cpu(desc);
  156. }
  157.  
  158. if (new->flags & IRQF_ONESHOT)
  159. desc->istate |= IRQS_ONESHOT;
  160.  
  161. if (irq_settings_can_autoenable(desc))
  162. irq_startup(desc, true);
  163. else
  164. /* Undo nested disables: */
  165. desc->depth = 1;
  166.  
  167. /* Exclude IRQ from balancing if requested */
  168. if (new->flags & IRQF_NOBALANCING) {
  169. irq_settings_set_no_balancing(desc);
  170. irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
  171. }
  172.  
  173. /* Set default affinity mask once everything is setup */
  174. setup_affinity(irq, desc, mask);
  175.  
  176. } else if (new->flags & IRQF_TRIGGER_MASK) {
  177. ..
  178. }
  179.  
  180. new->irq = irq;
  181. *old_ptr = new;
  182.  
  183. irq_pm_install_action(desc, new);
  184.  
  185. /* Reset broken irq detection when installing new handler */
  186. desc->irq_count = 0;
  187. desc->irqs_unhandled = 0;
  188.  
  189. /*
  190. * Check whether we disabled the irq via the spurious handler
  191. * before. Reenable it and give it another chance.
  192. */
  193. if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
  194. desc->istate &= ~IRQS_SPURIOUS_DISABLED;
  195. __enable_irq(desc, irq);
  196. }
  197.  
  198. raw_spin_unlock_irqrestore(&desc->lock, flags);
  199.  
  200. /*
  201. * Strictly no need to wake it up, but hung_task complains
  202. * when no hard interrupt wakes the thread up.
  203. */
  204. if (new->thread)
  205. wake_up_process(new->thread);------------------------------如果该中断被线程化,那么就唤醒该内核线程。这里每个中断对应一个线程。
  206.  
  207. register_irq_proc(irq, desc);----------------------------------创建/proc/irq/xxx/目录及其节点。
  208. new->dir = NULL;
  209. register_handler_proc(irq, new);-------------------------------以action->name创建目录
  210. free_cpumask_var(mask);
  211.  
  212. return 0;
  213. ...
  214. }

irq_setup_forced_threading()判断是否强制当前中断线程化,然后对thread_flags置位IRQTF_FORCED_THREAD表示此中断被强制线程化。

将原来的primary handler弄到中断线程中去执行,原来的primary handler换成irq_default_primary_handler。

并设置secondary的primary handler指向irq_forced_secondary_handler(),原来的thread_fn移到secondary的中线程中执行。

  1. static int irq_setup_forced_threading(struct irqaction *new)
  2. {
  3. if (!force_irqthreads)---------------------------------------------如果内核启动参数包含threadirqs,则支持强制线程化。或者CONFIG_PREEMPT_RT_BASE实时补丁打开,这里也强制线程化。
  4. return 0;
  5. if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))----和线程化矛盾的标志位。
  6. return 0;
  7.  
  8. new->flags |= IRQF_ONESHOT;----------------------------------------强制线程化的中断都置位IRQF_ONESHOT
  9.  
  10. if (new->handler != irq_default_primary_handler && new->thread_fn) {
  11. /* Allocate the secondary action */
  12. new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
  13. if (!new->secondary)
  14. return -ENOMEM;
  15. new->secondary->handler = irq_forced_secondary_handler;
  16. new->secondary->thread_fn = new->thread_fn;
  17. new->secondary->dev_id = new->dev_id;
  18. new->secondary->irq = new->irq;
  19. new->secondary->name = new->name;
  20. }
  21. /* Deal with the primary handler */
  22. set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
  23. new->thread_fn = new->handler;
  24. new->handler = irq_default_primary_handler;
  25. return 0;
  26. }

setup_irq()、request_threaded_irq()、request_irq()都是对__setup_irq()的包裹。

request_irq()调用request_threaded_irq(),只是少了thread_fn。

request_thraded_irq()和setup_irq()的区别在于,setup_irq()入参是struct irqaction ,而request_threaded_irq()在内部组装struct irqaction。

6. 一个中断的生命

经过上面的分析可以看出一个中断从产生、执行,到最终结束的流程。这里我们用树形代码路径来简要分析一下一个中断的生命周期。

vector_irq()->vector_irq()->__irq_svc()

->svc_entry()--------------------------------------------------------------------------保护中断现场

->irq_handler()->gic_handle_irq()------------------------------------------------具体到GIC中断控制器对应的就是gic_handle_irq(),此处从架构相关进入了GIC相关处理。

->GIC_CPU_INTACK--------------------------------------------------------------读取IAR寄存器,响应中断。

->handle_domain_irq()

->irq_enter()------------------------------------------------------------------------进入硬中断上下文

->generic_handle_irq()

->generic_handle_irq_desc()->handle_fasteoi_irq()--------------------根据中断号分辨不同类型的中断,对应不同处理函数,这里中断号取大于等于32。

->handle_irq_event()->handle_irq_event_percpu()

->action->handler()-----------------------------------------------------------对应到特定中断的处理函数,即上半部

->__irq_wake_thread()-----------------------------------------------------如果中断函数处理返回IRQ_WAKE_THREAD,则唤醒中断线程进行处理,但不是立即执行中断线程。

->irq_exit()---------------------------------------------------------------------------退出硬中断上下文。视情况处理软中断。

->invoke_softirq()-----------------------------------------------------------------处理软中断,超出一定条件任务就会交给软中断线程处理。

->GIC_CPU_EOI--------------------------------------------------------------------写EOI寄存器,表示结束中断。至此GIC才会接收新的硬件中断,此前一直是屏蔽硬件中断的。

->svc_exit-------------------------------------------------------------------------------恢复中断现场

从上面的分析可以看出:

  • 中断上半部的处理是关硬件中断的,这里的关硬件中断是GIC就不接收中断处理。直到写EOI之后,GIC仲裁单元才会重新选择中断进行处理。
  • 软中断运行于软中断上下文中,但是仍然是关硬件中断的,这里需要特别注意,软中断需要快速处理并且不能睡眠。
  • 不是所有软中断都运行于软中断上下文中,部分软中断任务可能会交给ksoftirqd线程处理。
  • 包括IRQ_WAKE_THREAD、ksoftirqd、woker等唤醒线程的情况,都不会在中断上下文中进行处理。中断上下文中所做的处理只是唤醒,执行时机交给系统调度。
  • 如果要提高Linux实时性,有两个要点:一是将上半部线程化;另一个是将软中断都交给ksoftirqd线程处理。
联系方式:arnoldlu@qq.com

Linux中断管理 (1)Linux中断管理机制【转】的更多相关文章

  1. Linux中断管理 (1)Linux中断管理机制

    目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...

  2. Linux内存管理Swap和Buffer Cache机制

    Linux内存管理Swap和Buffer Cache机制 一个完整的Linux系统主要有存储管理,内存管理,文件系统和进程管理等几方面组成,贴出一些以前学习过的一个很好的文章.与大家共享!以下主要说明 ...

  3. 启动期间的内存管理之pagging_init初始化分页机制--Linux内存管理(十四)

    1 今日内容(分页机制初始化) 在初始化内存的结点和内存区域之前, 内核先通过pagging_init初始化了内核的分页机制. 在分页机制完成后, 才会开始初始化系统的内存数据结构(包括内存节点数据和 ...

  4. linux驱动程序之电源管理之linux的电源管理架构(3)

    设备电源管理 Copyright (c) 2010 Rafael J. Wysocki<rjw@sisk.pl>, Novell Inc. Copyright (c) 2010 Alan ...

  5. Linux中断 - IRQ number和中断描述符

    一.前言 本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程.第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述 ...

  6. 解析Linux内核的基本的模块管理与时间管理操作---超时处理【转】

    转自:http://www.jb51.net/article/79960.htm 这篇文章主要介绍了Linux内核的基本的模块管理与时间管理操作,包括模块加载卸载函数的使用和定时器的用法等知识,需要的 ...

  7. linux kernel学习笔记-5内存管理_转

    void * kmalloc(size_t size, gfp_t gfp_mask); kmalloc()第一个参数是要分配的块的大小,第一个参数为分配标志,用于控制kmalloc()的行为. km ...

  8. Linux从头学07:中断那么重要,它的本质到底是什么?

    作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...

  9. Linux内核学习笔记-2.进程管理

    原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...

随机推荐

  1. HTTP与HTTPS初识

    HTTP HTTP是一个属于应用层的协议,特点是简介.快速   HTTP客户端发起请求,创建端口HTTP服务器在端口监听客户端请求HTTP服务器向客户端返回状态和内容 网络请求,页面渲染 1.域名解析 ...

  2. luoguP2163 [SHOI2007]园丁的烦恼

    安利系列博文 https://www.cnblogs.com/tyner/p/11565348.html https://www.cnblogs.com/tyner/p/11605073.html 题 ...

  3. Apache 监听 ipv4

    查看端口信息 ifconfig 发现只有 ipv6 的 80 端口被监听,ipv4 的 80 端口没有被监听 因此通过 ipv4 无法访问 tcp6 0 0 :::80 :::* LISTEN 221 ...

  4. CF-weekly4 F. Kyoya and Colored Balls

    https://codeforces.com/gym/253910/problem/F F. Kyoya and Colored Balls time limit per test 2 seconds ...

  5. 启动tomcat内存溢出

    在运行项目的过程中,启动tomcat内存溢出.查阅了一些解决办法,总结出来留个笔记. 1.使用Myeclipse2014+tomcat 7 ,在MyEclipse中将项目部署到Tomcat下,启动to ...

  6. SQL 错误: ORA-65096: 公用用户名或角色名无效 65096. 00000 - "invalid common user or role name" *Cause: An attempt was made to create a common user or role with a name

    在Oracle SQL Developer中,试图创建RD用户时,出现了如下的错误: 在行: 上开始执行命令时出错 - 错误报告 - SQL 错误: ORA: 公用用户名或角色名无效 . - &quo ...

  7. HTML连载25-通配符选择器&选择器综合练习

    一.通配符选择器 作用:给当前页面上所有的标签设置属性 (2)格式: *{属性:值:} (3)注意点:由于通配符选择器是给界面上所有的标签设置属性,因此在设置之前会遍历所有的标签,如果当前界面上的标签 ...

  8. 题目:利用Calendar类计算自己的出生日期距今天多少天,再将自己的出生日期利用SimpleDateFormat类设定的格式输出显示

    package cn.exercise; import java.util.Calendar; import java.util.Date; import java.text.SimpleDateFo ...

  9. mysql-新增数据表

    新增数据表之前,需确保已经存在数据库,如还没有数据库请先参考上一篇文章新增数据库 1.创建表  create table test( id  int PRIMARY KEY, name  varcha ...

  10. 解决原生javascript 缺少insertAfter的功能,非Jquery方法

    在现有的方法后插入一个新元素,你可能会想:既然有insertBefore方法,是不是也有一个相应的insertAfter()方法.很可惜,DOM没有提供方法.下面编写insertAfter函数,虽然D ...