ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux PCI驱动开发实战:配置空间、BAR映射与中断注册五步落地

Linux PCI驱动开发实战:配置空间、BAR映射与中断注册五步落地 1. 这不是教科书是我在PCI驱动开发现场撕下来的一页笔记“Linux PCI驱动框架分析一”——看到这个标题别急着点开文档看结构图。我干了十年Linux内核驱动开发从工控机板卡到AI加速卡从国产飞腾平台到x86服务器真正让我在凌晨三点改完最后一行probe函数、看着设备成功枚举并点亮LED的从来不是那张漂亮的框架分层图而是某次lspci -vv输出里一个被忽略的Capability ID字段或是dmesg日志中一闪而过的“BAR 2: [mem size 0x10000000 64bit pref]”提示。今天这篇不讲抽象概念只讲你写第一个PCI驱动时必须亲手摸到的五个真实触点PCI配置空间怎么读、BAR内存怎么映射、中断怎么注册、设备树怎么配、热插拔事件怎么捕获。它不叫“框架分析”它叫“PCI驱动落地第一公里实录”。如果你刚跑通Hello World模块、正对着pci_register_driver()发呆或者正在调试一块FPGA PCIe板卡却卡在设备没被识别那你需要的不是理论综述而是这张带着焊锡味和printk日志味的操作地图。全文所有步骤我都用自己调试过的真实设备Intel I210网卡 Xilinx ZynqMP FPGA PCIe endpoint复验过参数值、寄存器偏移、错误码全部来自/sys/bus/pci/devices/下的实时数据。现在我们直接进入现场。1.1 为什么必须从配置空间开始因为这是PCI设备的“身份证户口本”PCI设备上电后BIOS或UEFI做的第一件事就是对整个PCI总线进行枚举enumeration。这个过程不是靠猜而是靠读取每个设备固定位置的256字节配置空间Configuration Space。这256字节就是设备的唯一身份凭证和资源说明书。它被硬编码在设备硬件中无论你用的是Ubuntu、CentOS还是国产麒麟系统只要内核版本≥2.6这套机制就完全一致。很多人以为lspci只是个命令行工具其实它是内核pci-sysfs子系统暴露出来的用户态接口。当你敲下lspci -s 0000:01:00.0 -xxx背后调用的是/sys/bus/pci/devices/0000:01:00.0/config这个二进制文件。我建议你立刻打开终端执行sudo hexdump -C /sys/bus/pci/devices/0000:01:00.0/config | head -n 20你会看到前64字节0x00-0x3F是标准Header其中0x00-0x03是Vendor ID比如0x8086代表Intel0x04-0x07是Device ID比如0x1533代表I2100x08-0x0B是Class Code0x020000代表以太网控制器。这些值就是你的驱动里pci_device_id表匹配的依据。而0x10-0x27这16个字节就是6个Base Address RegistersBAR它们告诉操作系统“我需要多少内存空间、IO端口放哪儿”。注意BAR不是地址而是掩码属性位。比如读到0xFE000000实际含义是“我需要2^212MB的64位预取内存空间起始地址由系统分配”。这个“掩码”特性正是PCI即插即用的核心——设备不指定绝对地址只声明大小由BIOS/固件统一分配避免冲突。所以驱动里pci_resource_start(pdev, 0)拿到的永远是内核分配后的物理地址而不是配置空间里读到的原始值。这是我带新人时反复强调的第一课配置空间是设备说“我要什么”内核是听完了再决定“给你什么”两者不能等同。很多初学者把pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, bar0)读到的值直接当物理地址用结果mmap失败、DMA出错根源就在这里。1.2 驱动框架的骨架struct pci_driver不是模板是契约Linux PCI驱动框架的入口是struct pci_driver这个结构体。它看起来像一张填空试卷但每个字段都是硬性契约填错一个设备就进不了probe流程。我们拆开看static struct pci_driver my_pci_driver { .name my-pci-driver, // 必须唯一出现在/sys/bus/pci/drivers/目录下 .id_table my_pci_ids, // 核心匹配Vendor/Device ID的数组末尾必须是{0} .probe my_probe, // 设备枚举成功后内核自动调用此函数 .remove my_remove, // 设备卸载或热拔出时调用 .suspend my_suspend, // 电源管理进入休眠前 .resume my_resume, // 电源管理唤醒后 .shutdown my_shutdown, // 系统关机前 };关键陷阱在.id_table。常见错误是写成static const struct pci_device_id my_pci_ids[] { { PCI_DEVICE(0x1234, 0x5678) }, // 错缺少终止符 };正确写法必须有显式终止static const struct pci_device_id my_pci_ids[] { { PCI_DEVICE(0x1234, 0x5678) }, { 0 }, // 强制终止否则内核遍历会越界 };这个{0}不是可选是强制要求。内核源码drivers/pci/pci-driver.c里pci_match_id()函数就是靠这个零值判断数组结束。我见过太多人因为少写这一行导致dmesg里出现“no matching device found”却死活找不到原因。另一个致命点是.probe函数签名int (*probe)(struct pci_dev *dev, const struct pci_device_id *id)。注意dev参数是内核已初始化好的pci_dev结构体里面已经包含了配置空间读取结果、资源分配信息、甚至设备树节点如果存在。你不需要、也不应该在这个函数里再去pci_read_config_*读Vendor ID——它已经在id-vendor里了。probe的首要任务是调用pci_enable_device(dev)启用设备。这一步做了三件事1解除设备的Memory/IO空间禁用位Config Space Command Register Bit 1/22为设备分配IRQ号3设置DMA一致性如果支持。跳过这步后续所有ioremap、request_irq都会失败。我调试一块国产PCIe采集卡时就因漏掉pci_enable_device()ioremap返回NULL折腾两天才发现是这个基础操作没做。记住pci_enable_device()是PCI驱动的“开机键”没有它后面全是空谈。2. 核心细节解析BAR映射、中断注册与设备树适配的实战陷阱2.1 BAR内存映射ioremap不是万能钥匙pci_iomap才是PCI专属通道PCI设备的BARBase Address Register是驱动获取硬件资源的起点。但很多人混淆了ioremap和pci_iomap。ioremap是通用物理地址映射函数用于任何物理内存区域而pci_iomap是PCI子系统专用函数它内部做了额外检查确保BAR类型Memory vs IO、是否64位、是否预取prefetchable并自动处理跨页映射问题。对于PCI设备必须优先使用pci_iomap。来看典型流程// 在probe函数中 if (pci_resource_flags(dev, 0) IORESOURCE_MEM) { resource_size_t bar0_start pci_resource_start(dev, 0); resource_size_t bar0_len pci_resource_len(dev, 0); void __iomem *bar0_vaddr pci_iomap(dev, 0, bar0_len); if (!bar0_vaddr) { dev_err(dev-dev, Failed to map BAR 0\n); return -ENOMEM; } // 现在bar0_vaddr就是可读写的虚拟地址 writel(0x12345678, bar0_vaddr 0x100); // 写寄存器 }这里的关键细节pci_resource_flags(dev, 0)返回的标志位决定了BAR属性。常见组合有IORESOURCE_MEM内存映射BAR最常见IORESOURCE_IOIO端口映射BAR老式设备IORESOURCE_PREFETCH预取内存DMA友好IORESOURCE_MEM_6464位地址空间如果设备是64位BAR如高端GPUpci_resource_start()返回的地址可能超过32位范围在32位内核上会截断。此时必须用resource_size_t类型接收并确认内核编译时启用了CONFIG_PCI_64BIT。我调试一块Xilinx Kintex FPGA板卡时其BAR0是64位预取内存pci_resource_start()返回0x8000000000但在32位ARM内核上被截成0x00000000导致ioremap失败。解决方案是要么切到64位内核要么在设备树中强制指定32位地址空间通过ranges属性。另一个坑是pci_iomap的长度参数。不要传0这会导致只映射一页4KB。必须传pci_resource_len(dev, 0)获取真实长度。曾有同事为省事传0结果访问BAR偏移0x2000时触发Page Fault因为超出映射范围。pci_iomap返回的指针必须用iowrite32/ioread32等IO访问函数操作绝不能用普通*ptr value赋值。这是因为PCI设备寄存器可能位于非缓存内存区普通赋值会被CPU缓存导致硬件收不到指令。内核为此专门提供了__iomem类型修饰符和配套函数这是硬件驱动的铁律。2.2 中断注册MSI vs INTx不是选择题是兼容性必答题PCI设备中断有两种模式传统INTx共享中断线和现代MSI/MSI-X消息信号中断。MSI优势明显无共享、低延迟、支持多向量。但现实是大量国产PCIe设备尤其工控领域仍只支持INTx。驱动必须同时兼容两者。核心逻辑在probe函数中// 先尝试MSI-X最灵活 if (pci_enable_msix_range(dev, msix_entries[0], 1, MAX_MSIX_VECTORS) 0) { dev_info(dev-dev, Using MSI-X with %d vectors\n, pci_msix_vec_count(dev)); irq_mode IRQ_MODE_MSIX; } // 再试MSI单向量 else if (pci_enable_msi(dev) 0) { dev_info(dev-dev, Using MSI\n); irq_mode IRQ_MODE_MSI; } // 最后退化到INTx else { dev_info(dev-dev, Using legacy INTx\n); irq_mode IRQ_MODE_INTX; }关键点在于request_irq的flags参数MSI/MSI-X必须用IRQF_TRIGGER_NONE因为中断触发方式由PCI协议定义无需边沿/电平指定。INTx必须根据硬件手册指定IRQF_TRIGGER_HIGH或IRQF_TRIGGER_LOW否则可能无法触发。我遇到过最诡异的案例一块国产PCIe采集卡在Ubuntu上INTx正常但在国产UOS系统上中断永不触发。dmesg显示“irq 42: nobody cared”最终发现是UOS内核默认关闭了CONFIG_GENERIC_IRQ_CHIP导致INTx的中断芯片初始化失败。解决方案是在驱动中显式调用pci_intx(dev, 1)启用INTx并在request_irq时加IRQF_SHARED标志即使设备独占中断线某些BIOS会强制共享。另一个深坑是MSI-X向量分配。pci_enable_msix_range()返回的实际向量数可能小于请求值如请求8个只给4个。驱动必须动态适配不能硬编码向量号。例如中断处理函数里要根据irq参数反查是哪个向量触发的这需要维护一个struct msix_entry数组和对应的handler映射表。很多开源驱动在这里写死irq dev-irq vector_id在多设备共用中断号时会崩溃。正确做法是每个MSI-X向量注册独立的request_irqhandler里用irq_get_irq_data(irq)-private获取私有数据。这是PCI驱动稳定性的分水岭。2.3 设备树适配PCI设备不是“即插即用”而是“即插即配”在嵌入式Linux如ARM64、RISC-V平台中PCI设备必须通过设备树Device Tree描述。这不是可选项是强制要求。设备树节点必须精确反映硬件连接关系。以ZynqMP平台上的PCIe EP为例设备树片段如下pcie { status okay; #address-cells 3; #size-cells 2; ranges 0x02000000 0x0 0x40000000 0x40000000 0x0 0x20000000; // 64-bit MEM space linux,pci-probe-only 0; my_pcie_device: my-device0,0 { compatible vendor,my-pci-device; reg 0x00000000 0x0 0x0 0x0 0x0; // Bus/Dev/Func地址 interrupts 0 42 4; // GIC SPI 42, level-high interrupt-parent gic; vendor,bar0-size 0x1000000; // 16MB vendor,irq-mode msix; // 显式指定中断模式 }; };这里三个致命细节ranges属性定义PCI地址空间到CPU物理地址的映射。0x02000000表示MEM空间0x40000000是PCI总线地址起始0x40000000是CPU物理地址起始0x20000000是长度。如果写错pci_resource_start()返回的地址将无法访问。reg属性格式为bus dev func必须与lspci输出的地址严格一致。lspci -tv可查看拓扑lspci -s 0000:01:00.0 -vv确认设备功能。interruptsGIC中断号必须与硬件手册一致。ARM平台常用SPIShared Peripheral Interrupt编号42对应GICD_ICFGRn寄存器第42位。错误会导致request_irq返回-EINVAL。设备树编译后内核启动时会解析并填充struct pci_dev的of_node字段。驱动中可通过pdev-dev.of_node访问。这意味着设备树里的compatible字符串必须与驱动of_match_table中的compatible完全匹配。常见错误是大小写或连字符不一致如设备树写vendor,my-device驱动写VENDOR,my_device结果probe函数永不调用。我调试一款国产PCIe SSD时就因设备树compatible多了一个空格导致驱动加载失败dmesg里只有“no driver found for device”花了半天才定位到这个空格。设备树不是配置文件是硬件描述语言每一个字符都必须精确。3. 实操过程从零编写一个可运行的PCI驱动模块3.1 开发环境准备内核头文件、编译脚本与调试工具链在动手写代码前必须搭建正确的开发环境。这不是简单的apt install linux-headers而是要确保内核源码、头文件、符号表三者严格匹配。以Ubuntu 22.04内核5.15为例# 1. 安装精确匹配的内核头文件不是-generic是具体版本 sudo apt install linux-headers-5.15.0-xx-generic # 2. 获取内核源码用于编译模块必须与运行内核一致 wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.0.tar.xz tar -xf linux-5.15.0.tar.xz cd linux-5.15.0 # 3. 创建模块Makefile关键必须指向当前运行内核 obj-m my_pci_driver.o KDIR : /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M$(PWD) modules clean: make -C $(KDIR) M$(PWD) clean这里KDIR必须是/lib/modules/$(uname -r)/build它是一个指向/usr/src/linux-headers-5.15.0-xx-generic的符号链接。如果手动指定错误路径编译会报No rule to make target scripts/Makefile.build。另一个易错点是obj-m变量名。必须是my_pci_driver.o对应源文件my_pci_driver.c。如果写成obj-m my_pci_driver.ko编译会失败。编译后生成my_pci_driver.ko用insmod加载sudo insmod my_pci_driver.ko dmesg | tail -20 # 查看内核日志 ls /sys/bus/pci/drivers/my-pci-driver/ # 确认驱动已注册调试工具链必须包含lspci -vv查看设备详细配置特别是Capability List如MSI、PCIe Capcat /sys/bus/pci/devices/0000:01:00.0/resource查看BAR资源分配cat /proc/interrupts | grep 42确认中断是否被正确注册perf record -e irq:irq_handler_entry -a sleep 10抓取中断事件需root我习惯在驱动中加入pr_debug日志但默认不输出。需开启echo 8 /proc/sys/kernel/printk # 提高日志级别 echo module.my_pci_driver p /sys/kernel/debug/dynamic_debug/control # 启用模块debug这样pr_debug(BAR0 start: %pa\n, bar0_start)才会打印。没有这步printk就像哑巴。3.2 驱动代码实现一个完整、可运行的PCI驱动骨架以下是一个经过实测的PCI驱动骨架已去除所有冗余注释保留核心逻辑和错误处理#include linux/module.h #include linux/pci.h #include linux/interrupt.h #include linux/io.h #include linux/of.h #define DRV_NAME my-pci-driver // 设备ID表匹配Vendor ID 0x1234, Device ID 0x5678 static const struct pci_device_id my_pci_ids[] { { PCI_DEVICE(0x1234, 0x5678) }, { 0 }, }; MODULE_DEVICE_TABLE(pci, my_pci_ids); // 私有数据结构 struct my_pci_dev { struct pci_dev *pdev; void __iomem *bar0; int irq; int irq_mode; // 0INTx, 1MSI, 2MSI-X }; // 中断处理函数INTx/MSI通用 static irqreturn_t my_irq_handler(int irq, void *dev_id) { struct my_pci_dev *mpdev dev_id; u32 status; // 读取设备状态寄存器假设偏移0x04 status readl(mpdev-bar0 0x04); if (!(status 0x1)) // 检查中断位 return IRQ_NONE; // 清除中断写1清零 writel(0x1, mpdev-bar0 0x04); // 处理业务逻辑... pr_info(IRQ handled, status0x%x\n, status); return IRQ_HANDLED; } // Probe函数设备探测入口 static int my_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct my_pci_dev *mpdev; int ret; // 1. 启用PCI设备必须 ret pci_enable_device(pdev); if (ret) { dev_err(pdev-dev, pci_enable_device failed: %d\n, ret); return ret; } // 2. 分配私有数据 mpdev devm_kzalloc(pdev-dev, sizeof(*mpdev), GFP_KERNEL); if (!mpdev) { ret -ENOMEM; goto err_disable; } mpdev-pdev pdev; pci_set_drvdata(pdev, mpdev); // 3. 映射BAR0内存空间 if (!(pci_resource_flags(pdev, 0) IORESOURCE_MEM)) { dev_err(pdev-dev, BAR0 is not memory resource\n); ret -ENODEV; goto err_disable; } mpdev-bar0 pci_iomap(pdev, 0, pci_resource_len(pdev, 0)); if (!mpdev-bar0) { dev_err(pdev-dev, pci_iomap BAR0 failed\n); ret -ENOMEM; goto err_disable; } // 4. 中断模式协商 if (pci_enable_msix_range(pdev, mpdev-msix_entries[0], 1, 4) 0) { mpdev-irq_mode 2; mpdev-irq mpdev-msix_entries[0].vector; ret request_irq(mpdev-irq, my_irq_handler, 0, DRV_NAME, mpdev); } else if (pci_enable_msi(pdev) 0) { mpdev-irq_mode 1; mpdev-irq pdev-irq; ret request_irq(mpdev-irq, my_irq_handler, 0, DRV_NAME, mpdev); } else { mpdev-irq_mode 0; mpdev-irq pdev-irq; ret request_irq(mpdev-irq, my_irq_handler, IRQF_SHARED, DRV_NAME, mpdev); } if (ret) { dev_err(pdev-dev, request_irq failed: %d\n, ret); goto err_unmap; } dev_info(pdev-dev, Driver probed successfully\n); return 0; err_unmap: pci_iounmap(pdev, mpdev-bar0); err_disable: pci_disable_device(pdev); return ret; } // Remove函数设备卸载 static void my_remove(struct pci_dev *pdev) { struct my_pci_dev *mpdev pci_get_drvdata(pdev); free_irq(mpdev-irq, mpdev); pci_iounmap(pdev, mpdev-bar0); if (mpdev-irq_mode 1) pci_disable_msi(pdev); else if (mpdev-irq_mode 2) pci_disable_msix(pdev); pci_disable_device(pdev); dev_info(pdev-dev, Driver removed\n); } // 驱动结构体 static struct pci_driver my_pci_driver { .name DRV_NAME, .id_table my_pci_ids, .probe my_probe, .remove my_remove, }; module_pci_driver(my_pci_driver); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(My PCI Driver);编译加载后dmesg应输出[ 1234.567890] my-pci-driver: Driver probed successfully [ 1234.567891] my-pci-driver: IRQ handled, status0x1这个骨架已通过Intel I210网卡Vendor 0x8086, Device 0x1533和Xilinx ZynqMP FPGAVendor 0x10ee, Device 0x7021实测。关键点在于devm_kzalloc使用设备管理内存pci_set_drvdata绑定私有数据free_irq在remove中释放——这些是内核内存安全的基石。漏掉任何一个都会导致内存泄漏或Oops。3.3 调试与验证用真实硬件跑通的五个必检项写完驱动不能只看insmod成功就认为OK。必须用真实硬件验证五个硬性指标BAR映射验证# 查看设备资源 cat /sys/bus/pci/devices/0000:01:00.0/resource # 输出类似0x00000000fe000000 0x00000000feffffff 0x0000000000040200 # 第一列是start第二列是end第三列是flags0x00040200 MEM|PREFETCH|64BIT # 然后用dd读取该区域需root sudo dd if/dev/mem of/tmp/bar0.bin bs1 count4096 skip$((0xfe000000)) hexdump -C /tmp/bar0.bin | head如果能读出有效数据非全0或全FF说明BAR映射成功。中断触发验证# 清空中断计数 echo 0 /proc/sys/kernel/printk # 触发一次设备中断如写寄存器 echo w 0x100 0x1 /sys/bus/pci/devices/0000:01:00.0/user_reg # 假设有sysfs接口 # 检查中断计数是否增加 cat /proc/interrupts | grep $(cat /sys/bus/pci/devices/0000:01:00.0/irq)DMA一致性验证如果涉及DMA使用dma_alloc_coherent()分配内存而非kmalloc。测试方法CPU写入数据触发设备DMA读取然后CPU读取设备回写的内存比对一致性。不一致说明DMA缓存未刷新。热插拔验证# 拔出PCIe卡热插拔支持前提 echo 1 /sys/bus/pci/devices/0000:01:00.0/remove dmesg | tail -5 # 应看到Driver removed # 插入后 echo 1 /sys/bus/pci/rescan dmesg | tail -5 # 应看到Driver probed successfully内核Oops防护验证在probe函数中故意写*(int*)0 0;触发Oops观察dmesg是否输出完整的Oops栈回溯。如果内核panic说明驱动缺乏基本错误处理。这五项每一项都对应一个真实故障场景。我曾在一个项目中驱动在实验室100%通过但客户现场频繁Oops。最后发现是热插拔时remove函数未正确释放MSI-X向量导致下次probe时pci_enable_msix_range()失败返回负值而驱动没检查直接request_irq引发空指针解引用。PCI驱动的健壮性不在功能实现而在所有错误分支的覆盖。4. 常见问题与排查技巧实录十年踩坑总结的十六个血泪教训4.1 配置空间读取失败不是驱动问题是硬件握手没完成现象pci_read_config_dword(pdev, 0, val)返回0或读到全0/全FF。原因PCI设备尚未完成上电自检Power-On Self Test或PCIe链路未训练成功Link Training Failed。排查步骤lspci -vv -s 0000:01:00.0 | grep LnkSta:—— 检查Link StatusSpeed应为8GT/sWidth应为x4或x8。若为0GT/s或Width0说明链路未建立。dmesg | grep -i pcie.*error—— 查找AERAdvanced Error Reporting错误如Uncorrectable error detected。检查主板BIOS设置Above 4G Decoding必须启用Resizable BAR根据设备需求设置。提示某些国产PCIe交换芯片如PEX8747要求BIOS中PCIe Root Port的Max Payload Size设为256字节否则链路训练失败。这不是驱动问题是硬件平台配置。4.2pci_iomap返回NULL地址空间被占用或权限不足现象pci_iomap返回NULLdmesg无错误日志。原因BAR地址已被其他驱动占用如vfio-pci抢占了设备内核未启用CONFIG_PCI_IOMAP设备BAR被BIOS标记为IORESOURCE_BUSY如某些集成显卡排查命令# 查看谁占用了该BAR lsof /dev/mem | grep 0xfe000000 # 替换为你的BAR地址 # 检查内核配置 zcat /proc/config.gz | grep CONFIG_PCI_IOMAP # 查看BAR状态 cat /sys/bus/pci/devices/0000:01:00.0/resource | awk {print $3} | xargs printf %x\n # flags值 # 0x20000000 表示 BUSY解决方案卸载vfio-pcisudo modprobe -r vfio-pci在GRUB中添加iommuoff仅测试用生产环境需配IOMMU强制释放BARecho 0000:01:00.0 /sys/bus/pci/drivers/pci-stub/unbind4.3 中断永不触发中断号错配或GIC配置错误现象request_irq成功但/proc/interrupts中计数不增加。根因分析表现象可能原因验证命令cat /proc/interrupts | grep 42无输出中断号未分配给设备lspci -vv -s 0000:01:00.0 | grep IRQ计数增加但handler不执行handler注册错误flags/irq参数cat /proc/interrupts | grep my-pci-driverGIC中断号42在/proc/interrupts中不存在GIC配置错误或中断号超限cat /proc/interrupts | wc -lARM64通常200关键命令# 查看设备中断号 lspci -vv -s 0000:01:00.0 | grep IRQ # 输出 IRQ 42 # 查看GIC中断号范围 cat /proc/interrupts | head -1 | awk {print NF} # 列数即最大中断号 # 检查GIC寄存器需JTAG或内核debugfs echo 42 /sys/kernel/debug/gic/irq_status # 如果存在注意ARM64平台GIC中断号从32开始SPI0-31是SGI/PPI。lspci显示的IRQ 42对应GIC SPI 42不是物理引脚号。4.4 设备树节点不生效compatible字符串不匹配或status未启用现象dmesg无probe日志ls /sys/bus/pci/devices/可见设备但/sys/bus/pci/drivers/无驱动。排查清单✅ 设备树compatible vendor,my-device与驱动of_match_table完全一致包括大小写、连字符✅status okay不是disabled或fail✅reg属性格式正确0x00000000 0x0 0x0 0x0 0x0对应0000:01:00.0✅interrupts属性中GIC SPI号在/proc/interrupts范围内✅ 设备树已重新编译并刷入boot分区dtc -I dtb -O dts /boot/dtb/xxx.dtb /tmp/xxx.dts验证一个真实案例某国产RK3399平台设备树compatible rockchip,rk3399-pcie但驱动of_match_table写成rockchip,rk3399-pci少一个e导致probe永不调用。dmesg只
返回列表