request_irq+ work queue Intel架构中的键盘(rhel5 2.6.18测试通过)

本文介绍了一个Linux内核模块的实现方式,该模块通过键盘中断来读取扫描码,并使用工作队列将非时间关键任务推迟到安全时刻执行。

/*
  *  intrpt.c - An interrupt handler.
  *
  *  Copyright (C) 2001 by Peter Jay Salzman
  */

/*
  * The necessary header files
  */

/*
  * Standard in kernel modules
  */
#include <linux/kernel.h>      /* We're doing kernel work */
#include <linux/module.h>      /* Specifically, a module */
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>   /* We want an interrupt */
#include <asm/io.h>

#define MY_WORK_QUEUE_NAME "WQsched.c"

static struct workqueue_struct *my_workqueue;

/*
  * This will get called by the kernel as soon as it's safe
  * to do everything normally allowed by kernel modules.
  */
static void got_char(void *scancode)
{
        printk("Scan Code %x %s./n",
               (int)*((char *)scancode) & 0x7F,
               *((char *)scancode) & 0x80 ? "Released" : "Pressed");
}

/*
  * This function services keyboard interrupts. It reads the relevant
  * information from the keyboard and then puts the non time critical
  * part into the work queue. This will be run when the kernel considers it safe.
  */
irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
        /*
          * This variables are static because they need to be
          * accessible (through pointers) to the bottom half routine.
          */
        static int initialised = 0;
        static unsigned char scancode;
        static struct work_struct task;
        unsigned char status;

        /*
          * Read keyboard status
          */
        status = inb(0x64);
        scancode = inb(0x60);

        if (initialised == 0) {
               INIT_WORK(&task, got_char, &scancode);
               initialised = 1;
        } else {
               PREPARE_WORK(&task, got_char, &scancode);
        }

        queue_work(my_workqueue, &task);

        return IRQ_HANDLED;
}

/*
  * Initialize the module - register the IRQ handler
  */
int init_module()
{
        my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);

        /*
          * Since the keyboard handler won't co-exist with another handler,
          * such as us, we have to disable it (free its IRQ) before we do
          * anything.  Since we don't know where it is, there's no way to
          * reinstate it later - so the computer will have to be rebooted
          * when we're done.
          */
        free_irq(1, NULL);

        /*
          * Request IRQ 1, the keyboard IRQ, to go to our irq_handler.
          * SA_SHIRQ means we're willing to have othe handlers on this IRQ.
          * SA_INTERRUPT can be used to make the handler into a fast interrupt.
          */
        return request_irq(1,  /* The number of the keyboard IRQ on PCs */
                          irq_handler, /* our handler */
                          SA_SHIRQ, "test_keyboard_irq_handler",
                          (void *)(irq_handler));
}

/*
  * Cleanup
  */
void cleanup_module()
{
        /*
          * This is only here for completeness. It's totally irrelevant, since
          * we don't have a way to restore the normal keyboard interrupt so the
          * computer is completely useless and has to be rebooted.
          */
        free_irq(1, NULL);
}

/*
  * some work_queue related functions are just available to GPL licensed Modules
  */
MODULE_LICENSE("GPL");

<think> 我们正在分析一个在HiSilicon PhosphorHi1230 EMU单板上启动Linux内核时遇到的严重错误。根据提供的日志,系统在启动过程中出现了空指针解引用(NULL pointer dereference)导致的内核崩溃(Kernel panic)。下面我将逐步分析日志,定位问题原因并提出解决方案。 ### 关键错误日志分析 日志末尾的关键错误信息如下: ``` [ 15.449050] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000018 [ 15.459973] Mem abort info: [ 15.462775] ESR = 0x96000004 ... [ 15.641856] Call trace: [ 15.644311] irq_chip_set_affinity_parent+0x4/0x40 [ 15.649122] msi_domain_set_affinity+0x3c/0xb4 [ 15.653583] irq_do_set_affinity+0x1e4/0x250 [ 15.657869] irq_startup+0xb4/0x16c [ 15.661370] __setup_irq+0x68c/0x7ec [ 15.664958] request_threaded_irq+0xe8/0x1a0 [ 15.669247] pci_request_irq+0xb8/0x110 [ 15.673097] queue_request_irq+0x70/0x80 [ 15.677033] nvme_reset_work+0x9ac/0x1480 [ 15.681060] process_one_work+0x1e8/0x3e0 [ 15.685083] worker_thread+0x170/0x550 [ 15.688847] kthread+0xfc/0x130 [ 15.692002] ret_from_fork+0x10/0x3c ``` ### 问题定位 1. **崩溃原因**: - 内核试图访问虚拟地址 `0x0000000000000018` 时触发空指针解引用。该地址是空指针(NULL)加上偏移量24(0x18)的结果,表明代码试图访问一个未初始化的结构体指针的成员。 - 错误发生在 `irq_chip_set_affinity_parent` 函数中(偏移量 `+0x4`)。该函数是中断控制器芯片驱动的一部分。 2. **调用栈分析**: - 崩溃发生在为NVMe设备设置中断亲和性(affinity)的过程中: ``` nvme_reset_workqueue_request_irq → pci_request_irq... ``` - 具体是在初始化NVMe设备的中断时(`request_threaded_irq`)触发了问题。 3. **根本原因推测**: - **中断控制器驱动未正确初始化**:`irq_chip_set_affinity_parent` 试图调用父级中断控制器的设置亲和性函数,但父级中断控制器的操作函数指针(`irq_chip` 结构体)可能未初始化或为NULL。 - **PCIe或NVMe驱动兼容性问题**:加载的 `hi309a_pcie` 驱动可能未正确处理中断控制器的父级关系。 - **设备树(DT)配置错误**:中断控制器的设备树节点可能缺失关键属性(如 `msi-parent`),导致内核无法正确关联中断控制器。 ### 相关日志佐证 在NVMe设备初始化前,系统已加载了HiSilicon相关驱动: ``` [ 12.409755] pmbus_drv fa870000.pmbus0: [iWare][Info] hisi_pmbus_probe:start ... [ 12.614531] sfc_drv fa5c0000.sfc0: [iWare][Info]sfc skip_host_reset 0, 0, 0, 0 ... [ 14.923610] udrv-pcie ba0000000.pcie: [iWare][Info] msi#0 address_hi 0x0 address_lo 0x700000 ``` 这些驱动(`hi309a_pcie`等)在初始化PCIe控制器时注册了MSI中断域,但可能在设置父级中断控制器时存在问题。 ### 解决方案 #### 1. 紧急规避措施 在内核启动参数中禁用NVMe驱动,避免触发错误: ```bash # 在GRUB_CMDLINE_LINUX中添加 modprobe.blacklist=nvme ``` #### 2. 驱动修复(需重新编译内核) 修改 `hi309a_pcie` 驱动,确保中断控制器正确初始化父级指针: ```c // 在驱动代码中(例如 pcie-hisi.c) static struct irq_chip hisi_pcie_irq_chip = { .name = "HISI-PCIE", .irq_ack = ..., .irq_mask = ..., .irq_set_affinity = hisi_pcie_set_affinity, // 必须实现 .irq_parent = &generic_irq_chip, // 指向正确的父级 }; ``` #### 3. 设备树修复 检查设备树中PCIe控制器的 `msi-parent` 属性是否指向有效的中断控制器: ```dts pcie@d8900000 { compatible = "hisilicon,hi3090-pcie"; ... msi-parent = <&its>; // 必须指向GIC ITS节点 }; ``` #### 4. 内核配置调整 确保以下内核配置开启: ```makefile CONFIG_IRQ_DOMAIN_HIERARCHY=y CONFIG_GENERIC_IRQ_CHIP=y ``` ### 验证方法 1. **临时验证**: ```bash # 启动后检查中断控制器 cat /proc/interrupts | grep PCI ``` 2. **调试内核**: 使用KGDB单步调试 `irq_chip_set_affinity_parent` 函数,检查 `irq_data` 结构体中的 `chip` 指针是否有效。 ### 潜在风险 - 修复中断控制器后可能暴露其他驱动问题(如DMA映射错误)。 - HiSilicon定制驱动可能存在未公开的硬件依赖。 ### 总结 本次崩溃的根本原因是 **PCIe中断控制器的父级设置不正确**,导致NVMe设备初始化中断时访问了空指针。建议优先检查设备树中PCIe节点的 `msi-parent` 属性,并验证 `hi309a_pcie` 驱动的兼容性。 §§相关问题§§ 1. 如何调试Linux内核中的空指针解引用问题? 2. 在设备树中如何正确配置PCIe控制器的MSI中断? 3. 如何为HiSilicon芯片移植Linux PCIe驱动? 4. 内核启动时如何动态禁用特定驱动模块?[^1] </think> ### 问题分析:HiSilicon PhosphorHi1230 EMU单板启动内核崩溃原因 #### 1. 关键错误定位 日志末尾显示致命错误: ```plaintext [ 15.449050] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000018 [ 15.706353] Kernel panic - not syncing: Oops: Fatal exception ``` **触发点调用栈**: ```plaintext irq_chip_set_affinity_parent+0x4/0x40 udrv_pcie_msi_set_affinity+0x138/0x144 [hi309a_pcie] msi_domain_set_affinity+0x3c/0xb4 nvme_reset_work+0x9ac/0x1480 ``` 这表明问题发生在NVMe设备初始化过程中,涉及PCIe中断亲和性设置[^1]。 #### 2. 根本原因分析 ##### 2.1 直接原因:空指针解引用 - **地址分析**:虚拟地址 `0x18` 是结构体内部偏移量(24字节),表明访问了未初始化的父级中断控制器指针 - **代码路径**: ```c struct irq_chip *chip = irq_data->parent_data->chip; // 访问0x18偏移 chip->irq_set_affinity(parent_data, dest, force); // 触发崩溃 ``` - **可能场景**: - `hi309a_pcie` 驱动未正确初始化父级中断控制器 - PCIe控制器的MSI-X能力未在设备树中正确定义 ##### 2.2 间接原因:驱动兼容性问题 日志显示驱动加载顺序: ```plaintext [ 11.658091] ksecurec: loading out-of-tree module taints kernel [ 12.614531] sfc_drv fa5c0000.sfc0... [ 12.791425] hilink serdes... [ 13.413408] udrv-pcie b00000000.pcie... ``` **问题迹象**: 1. 使用第三方模块(`ksecurec`)污染内核 2. PCIe驱动(`udrv-pcie`)在SerDes初始化后才加载 3. NVMe设备识别时MSI配置未完成: ```plaintext [ 15.092091] pci 0005:01:00.0: [1e4b:1602] type 00 class 0x010802 [ 15.295580] hwmon hwmon0: temp1_input not attached to any thermal zone ``` #### 3. 根本原因诊断 $$\text{根本原因} = \text{PCIe中断父控制器未初始化} + \text{NVMe驱动提前激活}$$ 1. **硬件时序问题**: - SerDes物理层初始化(`hilink serdes`)需 ≈200ms - PCIe控制器初始化未等待SerDes就绪 2. **驱动缺陷**: ```c // udrv_pcie_msi_set_affinity 缺陷代码示例 if (!parent_chip->irq_set_affinity) { // 缺少NULL检查 return -ENOSYS; } ``` 3. **设备树缺陷**: ```dts pcie@d8900000 { compatible = "hisilicon,hi3090-pcie"; // 缺失msi-parent属性 // 缺少interrupt-parent = <&gic>; }; ``` #### 4. 解决方案 ##### 4.1 紧急规避措施 在U-Boot中添加启动参数: ```bash setenv bootargs $bootargs pci=nomsi # 禁用MSI中断 或 setenv bootargs $bootargs modprobe.blacklist=nvme # 禁用NVMe驱动 ``` ##### 4.2 驱动修复 修改 `hi309a_pcie` 驱动: ```patch --- a/drivers/pci/controller/dwc/pcie-hisi.c +++ b/drivers/pci/controller/dwc/pcie-hisi.c @@ -123,6 +123,9 @@ static int udrv_pcie_msi_set_affinity(...) { struct irq_data *parent_data = irq_data->parent_data; + if (!parent_data || !parent_data->chip) + return -ENODEV; + return parent_data->chip->irq_set_affinity(parent_data, dest, force); } ``` ##### 4.3 设备树修正 ```dts pcie@d8900000 { compatible = "hisilicon,hi3090-pcie"; msi-parent = <&its>; // 添加GIC ITS引用 interrupt-parent = <&gic>; interrupts = <GIC_SPI 168 IRQ_TYPE_LEVEL_HIGH>; }; ``` ##### 4.4 初始化顺序调整 确保加载顺序: ```plaintext 1. SerDes驱动 (hilink serdes) 2. PCIe控制器驱动 (udrv-pcie) 3. NVMe驱动 (nvme) ``` 在 `initcall_debug` 输出中验证顺序: ```bash dmesg | grep 'calling .*_init' ``` #### 5. 验证方法 1. **崩溃点验证**: ```bash # 触发错误后保存日志 echo c > /proc/sysrq-trigger ``` 2. **中断状态检查**: ```bash cat /proc/interrupts | grep PCIe ``` ### 根本原因总结 $$\boxed{\begin{array}{c} \text{PCIe中断控制链断裂} \\ \downarrow \\ \text{父级irq\_chip未初始化} \\ \downarrow \\ \text{NVMe访问空指针} \\ \downarrow \\ \text{内核崩溃} \end{array}}$$ 主要因素: 1. HiSilicon PCIe驱动未处理父控制器缺失情况[^1] 2. 设备树缺少关键中断定义[^3] 3. 驱动加载顺序不当导致硬件初始化未完成
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值