zoukankan      html  css  js  c++  java
  • 5、中断系统中的设备树

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

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

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

    Linux kernel的中断子系统之(一):综述
    Linux kernel的中断子系统之(二):IRQ Domain介绍
    linux kernel的中断子系统之(三):IRQ number和中断描述符
    linux kernel的中断子系统之(四):High level irq event handler 
    Linux kernel中断子系统之(五):驱动申请中断API
    Linux kernel的中断子系统之(六):ARM中断处理过程
    linux kernel的中断子系统之(七):GIC代码分析
    http://www.wowotech.net/irq_subsystem/interrupt_subsystem_architecture.html

    第01节_中断概念的引入与处理流程
    这节视频来自"韦东山第1期裸板视频加强版", 如果已经理解了中断的概念, 请忽略本节

    第02节_Linux对中断处理的框架及代码流程简述

    a. 异常向量入口: archarmkernelentry-armv.S
      .section .vectors, "ax", %progbits
    .L__vectors_start:
      W(b) vector_rst
      W(b) vector_und
      W(ldr) pc, .L__vectors_start + 0x1000
      W(b) vector_pabt
      W(b) vector_dabt
      W(b) vector_addrexcptn
      W(b) vector_irq     #发生中断时调用
      W(b) vector_fiq

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

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

    c. __irq_usr/__irq_svc
      这2个函数的处理过程类似:
      保存现场
      调用 irq_handler
      恢复现场

    d. irq_handler: 将会调用C函数 handle_arch_irq

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

    e. handle_arch_irq的处理过程: 请看视频和图片
      读取寄存器获得中断信息: hwirq
      把hwirq转换为virq
      调用 irq_desc[virq].handle_irq

    对于S3C2440, s3c24xx_handle_irq 是用于处理中断的C语言入口函数 

    中断处理流程:
    假设中断结构如下:
    sub int controller ---> int controller ---> cpu

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

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

    如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
    a. 取出irq_desc[virq].action链表中的每一个handler, 执行它(注意这里是每一个handler,因为可能多个硬件共用一个中断)
    b. 使用irq_desc[virq].irq_data.chip的函数清中断

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


    第03节_中断号的演变与irq_domain

    以前中断号(virq)跟硬件密切相关,
    现在的趋势是中断号跟硬件无关, 仅仅是一个标号而已

     
    以前, 对于每一个硬件中断(hwirq)都预先确定它的中断号(virq),
    这些中断号一般都写在一个头文件里, 比如archarmmach-s3c24xxincludemachirqs.h
    使用时,
    a. 执行 request_irq(virq, my_handler) :
      内核根据virq可以知道对应的硬件中断, 然后去设置、使能中断等
    b. 发生硬件中断时,
      内核读取硬件信息, 确定hwirq, 反算出virq,
      然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler




    怎么根据hwirq计算出virq?
    硬件上有多个intc(中断控制器),
    对于同一个hwirq数值, 会对应不同的virq
    所以在讲hwirq时,应该强调"是哪一个intc的hwirq",
    在描述hwirq转换为virq时, 引入一个概念: irq_domain, 域, 在这个域里hwirq转换为某一个virq

    当中断控制器越来越多、当中断越来越多,上述方法(virq和hwirq固定绑定)有缺陷:
    a. 增加工作量, 你需要给每一个中断确定它的中断号, 写出对应的宏, 可能有成百上千个
    b. 你要确保每一个硬件中断对应的中断号互不重复

    有什么方法改进?  
    a. hwirq跟virq之间不再绑定
    b. 要使用某个hwirq时,
      先在irq_desc数组中找到一个空闲项, 它的位置就是virq
      再在irq_desc[virq]中放置处理函数

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

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

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

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

    假设要使用子中断控制器(subintc)的n号中断, 它发生时会导致父中断控制器(intc)的m号中断:
    a. 设备树表明要使用<subintc n>
      subintc表示要使用<intc m>
    b. 解析设备树时,
      会为<subintc n>找到空闲项 irq_desc[virq'], sub irq_domain.linear_revmap[n] = virq';

      会为<intc m> 找到空闲项 irq_desc[virq], irq_domain.linear_revmap[m] = virq;
      并且设置它的handle_irq为某个分析函数demux_func

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

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

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



    第04节_示例_在S3C2440上使用设备树描述中断体验

    所用文件在: doc_and_sources_for_device_treesource_and_images第5,6课的源码及映像文件(使用了完全版的设备树)内核补丁及设备树

    先解压原始内核(source_and_imageskernel):
      tar xzf linux-4.19-rc3.tar.gz
    打上补丁:
      cd linux-4.19-rc3
      patch -p1 < ../linux-4.19-rc3_device_tree_for_irq_jz2440.patch
    在内核目录下执行:
    export PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-  linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin

    cp config_ok .config
    make uImage // 生成 arch/arm/boot/uImage
    make dtbs // 生成 arch/arm/boot/dts/jz2440_irq.dtb

    老内核:
    / # cat /proc/interrupts
      CPU0
    29: 17593 s3c 13 Edge samsung_time_irq
    42: 0 s3c 26 Edge ohci_hcd:usb1
    43: 0 s3c 27 Edge s3c2440-i2c.0
    74: 86 s3c-level 0 Edge s3c2440-uart
    75: 561 s3c-level 1 Edge s3c2440-uart
    83: 0 s3c-level 9 Edge ts_pen
    84: 0 s3c-level 10 Edge adc
    87: 0 s3c-level 13 Edge s3c2410-wdt

    新内核:
    nfs 30000000 192.168.1.124:/work/nfs_root/uImage;

    nfs 32000000 192.168.1.124:/work/nfs_root/jz2440_irq.dtb; bootm 30000000 - 32000000

    / # cat /proc/interrupts
    CPU0
    8: 0 s3c 8 Edge s3c2410-rtc tick
    13: 936 s3c 13 Edge samsung_time_irq
    30: 0 s3c 30 Edge s3c2410-rtc alarm
    32: 15 s3c-level 32 Level 50000000.serial
    33: 60 s3c-level 33 Level 50000000.serial
    59: 0 s3c-level 59 Edge 53000000.watchdog


    a. 某个设备要使用中断, 需要在设备树中描述中断, 如何?
      它要用哪一个中断? 这个中断连接到哪一个中断控制器去?
      即: 使用哪一个中断控制器的哪一个中断?

      至少有有2个属性:
      interrupts // 表示要使用哪一个中断, 中断的触发类型等等
      interrupt-parent // 这个中断要接到哪一个设备去? 即父中断控制器是谁

    b. 上述的interrupts属性用多少个u32来表示?
      这应该由它的父中断控制器来描述,
      在父中断控制器中, 至少有2个属性:
      interrupt-controller; // 表示自己是一个中断控制器
      #interrupt-cells // 表示自己的子设备里应该有几个U32的数据来描述中断


    第05节_示例_使用设备树描述按键中断

    第2期驱动: 在linux 2.6.22.6上编写
    毕业班视频: 在linux 3.4.2上编写
    设备树视频: 在linux 4.19上编写

    基于这个驱动来修改: "第5课第5节_按键驱动_源码_设备树00th_origin_code", 它来自毕业班视频
    第5课第4节之前的内核, 可以使用 "第5课第5节_按键驱动_源码_设备树01th_buttons_drv"
    第5课第4节之后的内核, 可以使用 "第5课第5节_按键驱动_源码_设备树02th_buttons_drv"

    在设备树的设备节点中描述"中断的硬件信息",
    表明使用了"哪一个中断控制器里的哪一个中断, 及中断触发方式",

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

    驱动程序从platform_device的"中断资源"取出中断号, 就可以request_irq了


    实验:
    a.
    把"002th_buttons_drv/jz2440_irq.dts" 放入内核 arch/arm/boot/dts目录,
    在内核根目录下执行:
    make dtbs // 得到 arch/arm/boot/dts/jz2440_irq.dtb

    使用上节视频的uImage或这个jz2440_irq.dtb启动内核;

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

    cd 002th_buttons_drv
    make // 得到 buttons.ko

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

    cd 002th_buttons_drv

    arm-linux-gcc -o buttons_test buttons_test.c

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


    第06节_内核对设备树中断信息的处理过程


    从硬件结构上看, 处理过程分上下两个层面: 中断控制器, 使用中断的设备
    从软件结构上看, 处理过程分左右两个部分: 在设备树中描述信息, 在驱动中处理设备树
    (1) 中断控制器
    这又分为root irq controller, gpf/gpg irq controller
    a. root irq controller
    a.1 在设备树中的描述
    a.2 在内核中的驱动

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

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


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


    s3c2440设备树中断相关代码调用关系:

    (1) 上述处理过程如何触发?
    a. 内核启动时初始化中断的入口:
    start_kernel // init/main.c
      init_IRQ();
        if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)      如果使能了设备树并且inti_irq为0,或者不存在 
          irqchip_init(); // 一般使用它
        else
          machine_desc->init_irq();

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

        (先调用root irq controller对应的函数, 再调用子控制器的函数, 再调用更下一级控制器的函数...)


    (2) root irq controller的驱动调用过程:

    a. 为root irq controller定义处理函数:
    IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of); //drivers/irqchip/irq-s3c24xx.c

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

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

    它定义了一个of_device_id结构体, 段属性为"__irqchip_of_table", 在编译内核时这些段被放在__irqchip_of_table地址处。
    即__irqchip_of_table起始地址处,
    放置了一个或多个 of_device_id, 它含有compatible成员;
    设备树中的设备节点含有compatible属性,
    如果双方的compatible相同, 并且设备节点含有"interrupt-controller"属性,
    则调用of_device_id中的函数来处理该设备节点。

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


    b. root irq controller处理函数的执行过程:
    s3c2410_init_intc_of // drivers/irqchip/irq-s3c24xx.c
      // 初始化中断控制器: intc, subintc
      s3c_init_intc_of(np, interrupt_parent, s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));

        // 为中断控制器创建irq_domain
        domain = irq_domain_add_linear(np, num_ctrl * 32,
            &s3c24xx_irq_ops_of, NULL);

        intc->domain = domain;

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

    (3) pinctrl系统中gpf/gpg irq controller的驱动调用过程:

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

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

    a.3 驱动中的操作:
    samsung_pinctrl_probe // drivers/pinctrl/samsung/pinctrl-samsung.c
      最终会调用到 s3c24xx_eint_init // drivers/pinctrl/samsung/pinctrl-s3c24xx.c

      // eint0,1,2,3的处理函数在处理root irq controller时已经设置;
      // 设置eint4_7, eint8_23的处理函数(它们是分发函数)
      for (i = 0; i < NUM_EINT_IRQ; ++i) {
        unsigned int irq;

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

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

      // 为GPF、GPG设置irq_domain
      for (i = 0; i < d->nr_banks; ++i, ++bank) {

        ops = (bank->eint_offset == 0) ? &s3c24xx_gpf_irq_ops
            : &s3c24xx_gpg_irq_ops;

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

    (4) 使用中断的驱动调用过程:
    a. 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式")
    比如:
      buttons {
        compatible = "jz2440_button";
        eint-pins = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>;
        interrupts-extended = <&intc 0 0 0 3>,
        <&intc 0 0 2 3>,
        <&gpg 3 3>,
        <&gpg 11 3>;
    };

    b. 设备节点会被转换为 platform_device,
      "中断的硬件信息" 会转换为"中断号",
      保存在platform_device的"中断资源"里

    第3课第05节_device_node转换为platform_device, 讲解了设备树中设备节点转换为   platform_device 的过程;
    我们只关心里面对中断信息的处理:

    of_device_alloc (drivers/of/platform.c)
      dev = platform_device_alloc("", PLATFORM_DEVID_NONE); // 分配 platform_device

      num_irq = of_irq_count(np); // 计算中断数

      of_irq_to_resource_table(np, res, num_irq) // drivers/of/irq.c, 根据设备节点中的中断信息, 构造中断资源
        of_irq_to_resource
          int irq = of_irq_get(dev, index); // 获得virq, 中断号
            rc = of_irq_parse_one(dev, index, &oirq); // drivers/of/irq.c, 解析设备树中的中断信息, 保存在of_phandle_args结构体中

            domain = irq_find_host(oirq.np); // 查找irq_domain, 每一个中断控制器都对应一个irq_domain

            irq_create_of_mapping(&oirq); // kernel/irq/irqdomain.c, 创建virq和中断信息的映射
              irq_create_fwspec_mapping(&fwspec);
                irq_create_fwspec_mapping(&fwspec);
                  irq_domain_translate(domain, fwspec, &hwirq, &type) // 调用irq_domain->ops->xlate, 把设备节点里的中断信息解析为hwirq, type

                  virq = irq_find_mapping(domain, hwirq); // 看看这个hwirq是否已经映射, 如果virq非0就直接返回

                  virq = irq_create_mapping(domain, hwirq); // 否则创建映射
    v                irq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL); // 返回未占用的virq

                    irq_domain_associate(domain, virq, hwirq) // 调用irq_domain->ops->map(domain, virq, hwirq), 做必要的硬件设置


    c. 驱动程序从platform_device的"中断资源"取出中断号, 就可以request_irq了

  • 相关阅读:
    IOS-小项目(饿了么 网络部分 简单实现)
    IOS 网络浅析-(十二 UIWebView简介)
    IOS 网络-深入浅出(一 )
    IOS 杂笔-11(实现在外部无法改变UIView的size)
    IOS 杂笔-12(类别de巧用 有便于Frame的操作)
    IOS 杂笔-13(appearance的巧妙使用)
    IOS 杂笔-14(被人遗忘的owner)
    IOS 杂笔-15(知识小点 readonly)
    IOS 日期的简洁格式展示
    Eclipse代码注释模板修改
  • 原文地址:https://www.cnblogs.com/liusiluandzhangkun/p/11901896.html
Copyright © 2011-2022 走看看