JJJ-2 init_IRQ

本文详细解析了Linux内核中针对IMX6UL处理器的初始化流程,包括中断初始化、设备树的使用、中断控制器的设置以及重置控制器的初始化。其中,`init_IRQ`函数调用了不同的初始化子函数,如`imx6ul_init_irq`,并涉及到设备节点的匹配和中断控制器的配置。`of_irq_init`函数遍历设备树,初始化中断控制器。同时,文章还介绍了`imx_src_init`函数对源复位控制器的初始化过程。
void __init init_IRQ(void)
{
    int ret;

    if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
        irqchip_init();
    else // init_irq成员定义为imx6ul_init_irq,会走这个分支
        machine_desc->init_irq(); 

    if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
        (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
        if (!outer_cache.write_sec)
            outer_cache.write_sec = machine_desc->l2c_write_sec;
        ret = l2x0_of_init(machine_desc->l2c_aux_val,
                   machine_desc->l2c_aux_mask);
        if (ret)
            pr_err("L2C: failed to init: %d\n", ret);
    }
}

看imx6ul_init_irq

static void __init imx6ul_init_irq(void)
{
    imx_gpc_check_dt();
    imx_init_revision_from_anatop();
    imx_src_init();
    irqchip_init();
}

看imx_gpc_check_dt

void __init imx_gpc_check_dt(void)
{
    struct device_node *np;
	// 应该是定义在 imx6ul.dtsi
    np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc");
    if (WARN_ON(!np))
        return;

    if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) {
        pr_warn("Outdated DT detected, suspend/resume will NOT work\n");

        /* map GPC, so that at least CPUidle and WARs keep working */
        gpc_base = of_iomap(np, 0);
    }
}

设备节点如下:
gpc: gpc@020dc000 {
                compatible = "fsl,imx6ul-gpc", "fsl,imx6q-gpc";
                reg = <0x020dc000 0x4000>;
                interrupt-controller;
                #interrupt-cells = <3>;
                interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
                interrupt-parent = <&intc>;
                fsl,mf-mix-wakeup-irq = <0x7c00000 0x7d00 0x0 0x1400640>;
            };

看 imx_init_revision_from_anatop

void __init imx_init_revision_from_anatop(void)
{
    struct device_node *np;
    void __iomem *anatop_base;
    unsigned int revision;
    u32 digprog;
    u16 offset = ANADIG_DIGPROG;

    np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop");
    anatop_base = of_iomap(np, 0);
    WARN_ON(!anatop_base);
    if (of_device_is_compatible(np, "fsl,imx6sl-anatop")) // 不会走这个分支
        offset = ANADIG_DIGPROG_IMX6SL;
    if (of_device_is_compatible(np, "fsl,imx7d-anatop")) // 不会走这个分支
        offset = ANADIG_DIGPROG_IMX7D;
    根据offset偏移然后去读寄存器的值
    digprog = readl_relaxed(anatop_base + offset);
    iounmap(anatop_base);

    switch (digprog & 0xff) {
    case 0:
        if (digprog >> 8 & 0x01)
            revision = IMX_CHIP_REVISION_2_0;
        else
            revision = IMX_CHIP_REVISION_1_0;
        break;
    case 1:
        revision = IMX_CHIP_REVISION_1_1;
        break;
    case 2:
		revision = IMX_CHIP_REVISION_1_2;
        break;
    case 3:
        revision = IMX_CHIP_REVISION_1_3;
        break;
    case 4:
        revision = IMX_CHIP_REVISION_1_4;
        break;
    case 5:
        /*
         * i.MX6DQ TO1.5 is defined as Rev 1.3 in Data Sheet, marked
         * as 'D' in Part Number last character.
         */
        revision = IMX_CHIP_REVISION_1_5;
        break;
    default:
        /*
         * Fail back to return raw register value instead of 0xff.
         * It will be easy know version information in SOC if it
         * can't recongized by known version. And some chip like
         * i.MX7D soc digprog value match linux version format,
         * needn't map again and direct use register value.
         */
        revision = digprog & 0xff;
    }

    mxc_set_cpu_type(digprog >> 16 & 0xff);
    imx_set_soc_revision(revision);
}

看 mxc_set_cpu_type

// 判断 cpu类型,赋值给 soc_id 字串
void mxc_set_cpu_type(unsigned int type)
{
    __mxc_cpu_type = type;
}

看 mxc_set_arch_type

// 判断arch,似乎没怎么用到
void mxc_set_arch_type(unsigned int type)
{
    __mxc_arch_type = type;
}

看 imx_src_init

void __init imx_src_init(void)
{
    struct device_node *np;
    u32 val;

    np = of_find_compatible_node(NULL, NULL, "fsl,imx51-src");
    if (!np)
        return;
    src_base = of_iomap(np, 0);
    WARN_ON(!src_base);

	//这个if没跑到
    if (cpu_is_imx7d()) {
        val = readl_relaxed(src_base + SRC_M4RCR);
        if (((val & BIT(3)) == BIT(3)) && !(val & BIT(0)))
            m4_is_enabled = true;
        else
            m4_is_enabled = false;
        return;
    }
	// 详见下
    imx_reset_controller.of_node = np;
    if (IS_ENABLED(CONFIG_RESET_CONTROLLER))
        reset_controller_register(&imx_reset_controller);

    /*
     * force warm reset sources to generate cold reset
     * for a more reliable restart
     */
    spin_lock(&src_lock);
    val = readl_relaxed(src_base + SRC_SCR);

    /* bit 4 is m4c_non_sclr_rst on i.MX6SX */
    if (cpu_is_imx6sx() && ((val &
		(1 << BP_SRC_SCR_SW_OPEN_VG_RST)) == 0))
        m4_is_enabled = true;
    else
        m4_is_enabled = false;

    val &= ~(1 << BP_SRC_SCR_WARM_RESET_ENABLE);
    writel_relaxed(val, src_base + SRC_SCR);
    spin_unlock(&src_lock);
}

linux reset framework介绍

compatible 为 “fsl,imx51-src” 对应的节点为:

imx6ul.dtsi
            src: src@020d8000 {
                compatible = "fsl,imx6ul-src", "fsl,imx51-src";
                reg = <0x020d8000 0x4000>;
                interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>,
                         <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
                #reset-cells = <1>;
            };

看 irqchip_init

void __init irqchip_init(void)
{
    of_irq_init(__irqchip_of_table);
	// 空函数
    acpi_irq_init();
}

/**
 * of_irq_init - Scan and init matching interrupt controllers in DT
 * @matches: 0 terminated array of nodes to match and init function to call
 *
 * This function scans the device tree for matching interrupt controller nodes,
 * and calls their initialization functions in order with parents first.
 */
void __init of_irq_init(const struct of_device_id *matches)
{
    struct device_node *np, *parent = NULL;
    struct intc_desc *desc, *temp_desc;
    struct list_head intc_desc_list, intc_parent_list;

    INIT_LIST_HEAD(&intc_desc_list);
    INIT_LIST_HEAD(&intc_parent_list);

	// 遍历所有的node,寻找定义了interrupt-controller属性的node,如果定义了interrupt-controller属性则说明该node就是一个中断控制器。详细分析见下
    for_each_matching_node(np, matches) {
        if (!of_find_property(np, "interrupt-controller", NULL) ||
                !of_device_is_available(np))
            continue;
        /*
         * Here, we allocate and populate an intc_desc with the node
         * pointer, interrupt-parent device_node etc.
         */
        desc = kzalloc(sizeof(*desc), GFP_KERNEL);
        if (WARN_ON(!desc))
            goto err;

		// 此处会有两个node符合条件,分别为interrupt-controller和gpc
        desc->dev = np;
        desc->interrupt_parent = of_irq_find_parent(np);
        if (desc->interrupt_parent == np)
			desc->interrupt_parent = NULL;
        list_add_tail(&desc->list, &intc_desc_list);
    }

    /*
     * The root irq controller is the one without an interrupt-parent.
     * That one goes first, followed by the controllers that reference it,
     * followed by the ones that reference the 2nd level controllers, etc.
     */
    while (!list_empty(&intc_desc_list)) {
        /*
         * Process all controllers with the current 'parent'.
         * First pass will be looking for NULL as the parent.
         * The assumption is that NULL parent means a root controller.
         */
        list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
            const struct of_device_id *match;
            int ret;
            // typedef int (*of_irq_init_cb_t)(struct device_node *, struct device_node *);
            of_irq_init_cb_t irq_init_cb;

            if (desc->interrupt_parent != parent)
                continue;

            list_del(&desc->list);
            // 传入一个node设备树节点,让它与给定的一组的matches匹配,返回其中最合适的matches
            match = of_match_node(matches, desc->dev);
            if (WARN(!match->data,
                "of_irq_init: no init function for %s\n",
                match->compatible)) {
				kfree(desc);
                continue;
            }

            pr_debug("of_irq_init: init %s @ %p, parent %p\n",
                 match->compatible,
                 desc->dev, desc->interrupt_parent);
            //jl_test 能走到这儿的也只有两个节点,分别为interrupt-controller和gpc
            pr_err("irq_test:%s:%d:node_name=%s:compatible=%s\n", __func__, __LINE__, desc->dev->name, match->compatible);
            irq_init_cb = (of_irq_init_cb_t)match->data;
            // gic_of_init 和 imx_gpc_init 接下来要重点分析这两个函数了,见3
            ret = irq_init_cb(desc->dev, desc->interrupt_parent);
            if (ret) {
                kfree(desc);
                continue;
            }

            /*
             * This one is now set up; add it to the parent list so
             * its children can get processed in a subsequent pass.
             */
            list_add_tail(&desc->list, &intc_parent_list);
        }

        /* Get the next pending parent that might have children */
        desc = list_first_entry_or_null(&intc_parent_list,
                        typeof(*desc), list);
        if (!desc) {
            pr_err("of_irq_init: children remain, but no parents\n");
            break;
        }
        list_del(&desc->list);
        parent = desc->dev;
 		kfree(desc);
    }

    list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
        list_del(&desc->list);
        kfree(desc);
    }
err:
    list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
        list_del(&desc->list);
        kfree(desc);
    }
}
                                                          

关于reset framework 详见这篇

关于for_each_matching_node,详见这篇

这段代码是触摸屏驱动中用于 **触控上报频率(report rate)监控与统计** 的函数 `tp_rate_calc`,它通过时间戳计算中断触发频率、处理耗时,并可用于性能分析或健康状态上报。 --- ### ✅ 函数原型说明 ```c static void tp_rate_calc(struct touchpanel_data *ts, tp_rate tp_rate_type) ``` | 参数 | 说明 | |------|------| | `ts` | 指向触摸面板私有数据结构,保存时间、计数器等信息 | | `tp_rate_type` | 枚举类型,表示当前操作的阶段:开始、清零、计算等 | 该函数根据传入的 `tp_rate_type` 执行不同的逻辑分支,主要用于: - 记录中断时间 - 统计单位时间内中断次数(即触控采样率) - 计算中断处理延迟 - 上报异常(如采样率过低) --- ## 🔍 分段详解 我们逐部分解析其功能和实现原理。 --- ### 1. `TP_RATE_START`: 记录当前时间 ```c case TP_RATE_START: ts->curr_time = ktime_to_ms(ktime_get()); break; ``` - 使用 `ktime_get()` 获取当前高精度时间(`ktime_t` 类型),转换为毫秒。 - 存储到 `ts->curr_time`,作为后续计算的时间基准。 > 📌 这个通常用在中断到来时标记“本次 IRQ 开始处理”的时间点。 --- ### 2. `TP_RATE_CLEAR`: 清空中断计数 ```c case TP_RATE_CLEAR: ts->irq_num = 0; break; ``` - 将中断累计数量 `ts->irq_num` 归零。 - 用于重置统计周期,比如进入新场景前清除旧数据。 > ⚠️ 常见于调试模式下手动重启统计,避免历史数据干扰。 --- ### 3. `TP_RATE_CALC`: 核心计算逻辑(重点!) 这是最复杂也是最重要的分支,负责: - 计算两次中断之间的时间间隔(IRQ interval) - 计算中断处理耗时(IRQ handle time) - 输出日志打印当前采样率 - 判断是否低于设定阈值并上报健康事件 #### (1) 初始化 `irq_interval` ```c if (ts->irq_interval == 0) { ts->irq_interval = ts->curr_time; } ``` - 如果 `irq_interval` 是首次使用(为 0),则将其设为当前时间。 - 目的是设置第一次的参考时间点,防止除以零错误。 #### (2) 计算中断间隔(delta T) ```c ts->irq_interval = ts->curr_time - ts->irq_interval; ``` - 当前时刻减去上一次记录的时间 → 得到两个中断之间的**时间差(ms)** - 注意:这里复用了 `ts->irq_interval` 变量存储差值,稍后会被更新回时间戳 #### (3) 计算中断处理耗时 ```c ts->irq_handle_time = ktime_to_ms(ktime_get()) - ts->curr_time; ``` - 当前时间减去“中断开始处理时间”(`curr_time`) - 表示从进入中断到执行到这里所花费的时间 → 即 **中断处理延迟** > 💡 越小越好。若过大说明系统负载高或调度延迟严重。 #### (4) 日志输出(按条件打印) ```c if(ts->irq_num > 0 && ts->monitor_data.in_game_mode && ts->irq_num % 10 == 0) { TP_INFO(... "rate = %llu, ts->irq_handle_time =%llu, curr_time = %llu\n", 1000/ts->irq_interval, ts->irq_handle_time, ts->curr_time); } else { if (ts->irq_num > 0 && ts->irq_num % 100 == 0) { TP_INFO(...); } } ``` ##### 解释: - `ts->irq_num`: 已处理的中断次数 - 在游戏模式下更频繁地打印(每 10 次中断一次) - 非游戏模式下较稀疏打印(每 100 次一次) ##### 关键指标: - `rate = 1000 / irq_interval` → 单位:Hz(每秒多少次中断) - 如 `irq_interval = 5ms` → `rate = 200Hz` - 表示当前触控芯片上报数据的频率 > 🎮 游戏手机常支持 240Hz/480Hz 高刷触控,此值可验证是否达标 #### (5) 健康监测:判断采样率是否过低 ```c if (ts->irq_num > 0 && 1000/ts->irq_interval < ts->monitor_data.RATE_MIN && ts->health_monitor_support) { tp_healthinfo_report(&ts->monitor_data, HEALTH_BELOW_RATE, (void *)&ts->irq_num); } ``` - 如果当前计算出的 `rate < RATE_MIN`(例如期望最低 180Hz,实际只有 120Hz) - 且启用了健康监控功能 → 触发异常上报 > 🛑 这意味着: > - 触控 IC 工作异常 > - I2C 通信延迟 > - CPU 被占用导致无法及时读取数据 > > 上层可通过 `tp_healthinfo_report` 收集此类问题用于售后分析或 OTA 优化 #### (6) 更新状态变量 ```c ts->irq_interval = ts->curr_time; // 把 interval 更新为当前时间戳,供下次计算 delta ts->irq_num++; // 中断计数 +1 ``` - 完成本轮计算后,将 `irq_interval` 设置为当前时间,用于下一轮间隔计算 - `irq_num` 自增,用于控制日志频率和判断有效性 --- ## 🧩 总体流程图解 ```text [TP_RATE_START] ——> 记录 curr_time ↓ [TP_RATE_CALC 第一次] ——> 设置 irq_interval = curr_time(初始化) ↓ [TP_RATE_CALC 第二次] ——> irq_interval = curr_time - 上次值 → 得到 Δt → rate = 1000 / Δt → handle_time = now - curr_time → 打印日志 / 上报健康事件 → irq_interval = curr_time(更新时间戳) → irq_num++ ``` --- ## 📊 实际应用场景举例 | 场景 | 行为 | |------|------| | **日常使用** | 每 100 次中断打印一次 rate,观察稳定性 | | **游戏模式** | 每 10 次打印一次,实时监控高刷表现 | | **产线测试** | 检查 rate 是否稳定达到标称值(如 240Hz) | | **售后分析** | 若频繁触发 `HEALTH_BELOW_RATE`,提示可能存在硬件故障或固件 bug | --- ## ❗注意事项 & 潜在问题 1. **整数除法精度丢失** ```c 1000 / ts->irq_interval ``` - 若 `irq_interval = 7ms` → `1000/7 ≈ 142.85` → 截断为 `142Hz` - 建议改为浮点或放大倍数提升精度(但在内核中慎用 float) 2. **并发安全问题** - 若多线程/中断上下文同时调用此函数,需加锁保护 `ts->xxx` 成员 - 一般在单一线程(如 workqueue)中调用可避免 3. **变量命名歧义** - `ts->irq_interval` 实际上先存时间戳,再被赋值为时间差,容易误解 - 更清晰做法:使用两个变量 `last_time`, `interval` 4. **溢出风险** - 长时间运行可能导致 `ktime_to_ms()` 回绕(但约 50 天才溢出一次,影响较小) --- ## ✅ 改进建议(可选优化) ```c // 更清晰的写法建议: static void tp_rate_calc_optimized(struct touchpanel_data *ts, tp_rate type) { s64 now = ktime_to_ms(ktime_get()); switch (type) { case TP_RATE_START: ts->last_irq_time = now; break; case TP_RATE_CLEAR: ts->irq_num = 0; break; case TP_RATE_CALC: if (ts->irq_num == 0) { ts->last_irq_time = now; ts->irq_num++; return; } s64 delta = now - ts->last_irq_time; u32 rate = delta ? (1000 + delta/2) / delta : 0; // 四舍五入 s64 handle_time = now - ts->curr_time; if ((ts->monitor_data.in_game_mode && ts->irq_num % 10 == 0) || (!ts->monitor_data.in_game_mode && ts->irq_num % 100 == 0)) { TP_INFO(ts->tp_index, "rate=%u, handle_time=%lldms, time=%lld\n", rate, handle_time, now); } if (rate < ts->monitor_data.RATE_MIN && ts->health_monitor_support) { tp_healthinfo_report(&ts->monitor_data, HEALTH_BELOW_RATE, &ts->irq_num); } ts->last_irq_time = now; ts->irq_num++; break; } } ``` --- ##
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值