
简介这是一份面向Linux内核驱动开发学习者的Intel ICH系列芯片GPIO驱动源码包聚焦ICH6至ICH10及Series 5/6芯片组的通用输入输出控制。压缩包内仅有1个C语言源文件整体仅4KB短小精悍非常适合研读单个驱动的完整实现。目前已有142人学习过该资源。通过阅读这份源码可以清晰了解GPIO控制器结构、引脚方向与上下拉电阻的初始化配置掌握gpio_get_value/gpio_set_value等操作接口以及中断处理、电源管理等系统级任务的具体写法。同时代码展示了如何遵循Linux内核GPIO子系统标准接口如gpio_request、gpio_direction_input等进行驱动设计并能帮助开发者理解设备树配置与驱动解析的配合方式以及不同ICH版本间的兼容处理思路是入门芯片驱动开发和调试的实用参考。1. 为什么还在折腾gpio-ichIntel ICH 平台 GPIO 的最后一公里在服务器主板上调试 BMC 之外的 GPIO最难受的不是看 datasheet而是明明内核已经识别出 gpiochip写进去的电平就是不动。很多人第一反应是怀疑自己代码写错其实多半是驱动没找对对象。网上流传的 gpio-ich.rar 解压出来其实就是内核里那个老资格的 driver/gpio/gpio-ich.c。它专门伺候 Intel ICH 系列南桥的 GPIO从 ICH6 一路跟到 Atom 时代的某些 SoC解决的是 x86 平台上最基础的一组信号电源使能、复位、LED、开机检测。我下面不打算复述内核文档而是按实际调板子的顺序从寄存器映射讲到用户态控制最后把 GPIO 编号偏移和 ACPI 占用这两个最容易翻车的点一次说清。2. gpio-ich 驱动模型ICH 芯片组 GPIO 寄存器映射与驱动加载逻辑2.1 ICH GPIO 硬件基础GPIOBASE 与 8 组寄存器ICH 系列南桥的 GPIO 控制器不是一个独立 PCI 设备而是挂在 LPC 桥里的功能块。驱动要做的事是先拿到 GPIOBASE。这个基地址由系统固件在 PCI 配置空间的 0x58 偏移处写入芯片内部所有 GPIO 寄存器都在这 64 字节窗口内偏移得到。gpio-ich 驱动在 probe 阶段会调用 pci_read_config_dword 去读这个值读不到就直接返回 -ENODEV。#define GPIOBASE_IO 0x58 #define GPIO_USE_SEL 0x00 #define GP_IO_SEL 0x04 #define GP_LVL 0x0c这段代码里的偏移意思很直白GPIO_USE_SEL 决定哪几个引脚作为 GPIO 使用GP_IO_SEL 决定方向GP_LVL 是数据寄存器。ICH 芯片组的 GPIO 分成 8 组每组理论上 32 个位但实际可用的引脚数量随芯片变化。ICH10 之前很多组只有 8 位或 16 位所以驱动内部维护一张表记录每组有效引脚数和偏移。这也就是为什么你在数据手册上看到的 GPIO33和你在软件里看到的 line 33 往往不是一回事——中间隔了保留位和不可用位。这里要提一句 GPIO 的 8 种工作模式。这个概念来自 STM32 这类 MCUx86 南桥没有那么多花样只有输入、输出、开漏和内部上拉/下拉可选。调 ICH 时如果拿 MCU 思维去套很容易把方向寄存器理解反明明已经设置成输出读回来还是输入状态。偏移寄存器作用0x00GPIO_USE_SEL每个位对应一个引脚1 表示作为 GPIO 使用0x04GP_IO_SEL1 表示输入0 表示输出0x0CGP_LVL输出电平或输入状态2.2 驱动如何确定它管的是哪一代 ICHgpio-ich 本质上是一个 platform driver但绑定方式却依赖 PCI ID。内核里维护了一张 PCI 设备表匹配 ICH6 到 ICH10、以及 Atom 平台常见的 NM10 等 LPC 控制器。以 ICH7 为例它的 LPC 设备 ID 是 0x27bcVENDOR 是 Intel 的 0x8086。static const struct pci_device_id ich_pci_tbl[] { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICH6_GPIO) }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICH7_GPIO) }, { } };这段代码的作用是让驱动在 PCI 探测阶段发现 LPC 桥。找到设备之后驱动读取 0x58 处的 GPIOBASE然后调用 platform_device_register 注册一个 gpiochip。这个注册动作是异步的所以 dmesg 里有时会看到 gpiochip 出现在驱动 probe 日志之后。加载失败时错误信息也有规律报告 failed to get gpio base 说明缓存基址读出来是 0报告 no PCI BDF 说明 PCI 设备表就没匹配上。2.3 与通用 GPIO 框架的接口irq_chip 和 gpio_chipgpio-ich 不只提供输入输出还支持中断。驱动通过 gpiochip_add_data 注册同时设置 irq_chip使得每个 GPIO 都能作为中断源使用。ICH 的内部中断路由比较特殊把 GPI 组的中断合并到 8259 的某个 IRQ 上所以 dmesg 里经常看到类似 gpio_ich: GPIO controller on ICH7: 64 pins 的信息。在实现层面驱动注册了 .request 回调来检查引脚是否已经被 ACPI 占用.get_direction 和 .direction_input/output 负责方向控制。与新的 gpiod API 对接后用户态程序不需要关心寄存器地址内核会通过 line 号到寄存器位的映射表完成转换。提示ICH 的 GPIO 引脚里有很大一部分是复用脚。比如 GPIO33 同时可能是 SMBus 的 ALERT#使用前必须确认它没有被别的子系统占走再往 GP_LVL 里写。2.4 与同代 PCH 上其它 GPIO 驱动的边界很多人会把 gpio-ich 和 gpio-pch、intel-pinctrl 混为一谈。gpio-pch 针对的是日本电产的 Egg Harbor 芯片组和一些老 PCH而 intel-pinctrl 是新一代 Intel SoC 的 GPIO 驱动使用完全不同的一套寄存器。ICH 平台的特点是没有 pin control 子系统GPIO 的方向、复用、上下拉都挤在这 64 字节窗口里因此 gpio-ich 必须自己处理所有细节。判断一块板子该用哪个驱动最直接方式是看 LPC 桥的设备 ID。0x27bc 属于 ICH70x27b8 是 ICH60x3b00 左右是 5 系列 PCH 的 LPC后者往往已经在 intel-pinctrl 的覆盖范围内。调 Intel i210 这类网卡时如果 PHY 的中断接到了某个 GPIO先确认那个 GPIO 属于 LPC 桥还是 PCIe 端口再决定是不是该找 gpio-ich 负责。3. 编译与加载 gpio-ich最小可复现的内核配置与模块参数3.1 确认你的南桥是不是 ICHlspci 与 ACPI 表调板子第一件事不是敲 make menuconfig而是确认南桥型号。ICH 系列广泛存在于 Atom N2xxx、老款凌动以及一批工控主板里。执行lspci -nn | grep -i LPC输出类似00:1f.0 ISA bridge [0601]: Intel Corporation NM10 Family LPC Controller [8086:27bc]。这个 27bc 就是 ICH7 的 LPC 设备 IDgpio-ich 的匹配表里收录了它。如果显示的是 Intel 8 系列 C220 或 9 系列 PCH那已经不是 ICHgpio-ich 不会绑定应该去看 intel-pinctrl 或 gpio-pch。接着看 ACPI 是否已经声明了这个资源grep -i gpio /sys/firmware/acpi/tables/DSDT 2/dev/null多数情况下输出为空说明 GPIO 控制器没有被 ACPI 接管可以放心使用。如果 DSDT 里有 _CRS 方法指定了 GPIO 资源内核的 ACPI GPIO 子系统可能会先注册gpio-ich 再 probe 就会失败。这种情况通常需要靠模块参数把冲突绕开具体见 3.3。3.2 内核配置GPIO_ICH 依赖 GPIO 基础框架内核里 GPIO 驱动编译选项在Device Drivers - GPIO Support下面。需要打开 CONFIG_GPIO_ICH同时确认 CONFIG_GPIOLIB 已经选上。检查当前内核是否包含zcat /proc/config.gz | grep GPIO_ICH输出CONFIG_GPIO_ICHm说明可以按模块加载。没有 config.gz 时看/lib/modules/$(uname -r)/modules.alias里有没有 gpio_ich 条目。下表是我常用的最小配置集配置项推荐值说明CONFIG_GPIOLIByGPIO 基础框架所有 GPIO 驱动都依赖它CONFIG_GPIO_ICHmICH 平台 GPIO 驱动CONFIG_GPIO_SYSFSy老 sysfs 接口调试阶段建议打开CONFIG_GPIODy字符设备接口libgpiod 需要拿到内核源码后编译单个驱动目录再拷贝到目标机速度比整编内核快很多make Mdrivers/gpio/ cp drivers/gpio/gpio-ich.ko /lib/modules/$(uname -r)/kernel/drivers/gpio/ depmod -a modprobe gpio-ichdepmod -a用于刷新模块依赖否则 modprobe 可能找不到新拷贝的 .ko。如果模块提示版本 magic 不匹配说明内核源码版本和目标机内核不是同一套需要重新编译整个内核或调整配置。3.3 模块参数gpio_base、reserve 与 use_acpigpio-ich 的模块参数很少但每个都直接关系到能否在奇怪的主板上跑起来。第一个是 gpio_base手动指定 GPIOBASE 地址跳过 PCI 配置读取。这主要用于 BIOS 没有正确设置 0x58 偏移的场合modprobe gpio-ich gpio_base0x500第二个参数是 reserve。当驱动发现 GPIOBASE 已经由 ACPI 占用时可以用位掩码把特定引脚强制保留。比如要保留 0 到 7 号引脚modprobe gpio-ich gpio_base0x500 reserve0xffreserve 是位掩码置 1 的位会跳过 request 检查。要注意的是这个参数不能把已经被 gpiolib 认领的 line 抢回来它只能让驱动在初始化时忽略这部分引脚的 ACPI 状态。第三个参数 use_acpi 默认值是 1表示优先使用 ACPI 的 _CRS 方法获取资源。如果固件里的 ACPI 表写的基地址是错的反而应该置 0echo options gpio-ich use_acpi0 gpio_base0x500 /etc/modprobe.d/gpio-ich.conf加载后立刻看 dmesg 尾部dmesg | tail -20正常会看到gpio_ich: GPIO controller on ICH7: 64 pins和gpiochip_add: registered GPIOs 0..63 on device gpio_ich。如果只有前者没有后者说明 gpiochip 注册失败多半是 reserve 和 ACPI 冲突。3.4 模块加载失败时按哪三步排查第一步确认设备树是否命中。dmesg | grep gpio_ich没有输出说明驱动的 PCI 表没匹配检查 lspci 看到的设备 ID。第二步检查 gpio_base 是否有效。把gpio_base0x0手动设置去加载如果 dmesg 报 invalid那基本可以排除驱动本身问题回到主板 BIOS 设置。第三步看 ACPI 冲突。gpio_ich: failed to request gpio往往出现在子设备申请特定 line 的时候而不是驱动加载阶段。很多人在这一步会反复尝试不同的 reserve 值其实更好的做法是先查/sys/kernel/debug/gpio看具体哪个 line 被谁占用再决定保留哪几位。盲试位掩码只适合快速验证不适合作为长期配置写入 modprobe.d。4. 通过 gpio-ich 操作 GPIOlibgpiod 与 sysfs 的新旧两套用户态接口4.1 先看 gpiochip 是否注册gpiodetect 与 /sys/class/gpio装好 libgpiod 之后第一步是列出系统中的所有 gpiochipgpiodetect输出里应当包含类似gpiochip0 [gpio_ich] (64 lines)的一行。没有 libgpiod 的老系统里可以看 /sys/class/gpio。gpiochipN 目录下存在 base 和 ngpio 两个文件cat /sys/class/gpio/gpiochip0/base cat /sys/class/gpio/gpiochip0/ngpio这两个值一个代表起始编号一个代表引脚数量。gpio-ich 注册的 base 通常是 0如果系统里还有其它 gpiochipbase 会从上一个 chip 的结束位置继续排。ngpio 根据芯片组不同从 64 到 128 不等。gpiodetect 只给出 chip 总览。要查看每个 line 是否可用、当前默认电平用 gpioinfogpioinfo gpiochip0输出里每行是一个 linename列通常是空的consumer列只有被 request 之后才有内容。调试之前先跑一遍这个命令能直观看到哪些 line 已经被 ACPI 或其它驱动占住。4.2 用 gpioset/gpioget 搭一套最小上电时序有了 chip 之后控制电平就很简单。比如要通过 GPIO24 给外设使能信号先拉低再延时再拉高gpioset gpiochip0 240 sleep 0.1 gpioset gpiochip0 241这里 24 是 chip 内部的 line 号不需要加上 base。gpioset 的-m wait选项可以指定保持时间比如输出 1 后保持 10ms 自动释放gpioset -m wait -t 10ms gpiochip0 241模拟上电时序时把多路 GPIO 按顺序放进去。先使能主电源再释放复位gpioset gpiochip0 121 gpiochip0 130 gpioget gpiochip0 12这样两条命令就完成了一组基本的时序验证。之前调 x86 工控板的上电时序我都是先用这套命令确认每一路电平再去看示波器波形比反复修改内核模块高效得多。读取时如果指定多个引脚输出格式是gpiochip0 121配合-b选项可以去掉 chip 名只显示数值。4.3 从 C 代码调用 libgpiod 做 GPIO 互锁用户态脚本适合调试但真正的逻辑控制建议写在 C 里尤其是电源互锁这种不能出错的场景。libgpiod 提供 gpiod_chip_open_by_name 和 gpiod_chip_get_line 接口。下面是一段最小示例#include gpiod.h #include stdio.h #include unistd.h int main(void) { struct gpiod_chip *chip; struct gpiod_line *line_in, *line_out; int v; chip gpiod_chip_open_by_name(gpiochip0); if (!chip) { perror(open); return 1; } line_in gpiod_chip_get_line(chip, 5); line_out gpiod_chip_get_line(chip, 6); gpiod_line_request_input(line_in, interlock); gpiod_line_request_output(line_out, interlock, 0); while (1) { v gpiod_line_get_value(line_in); gpiod_line_set_value(line_out, v 1 ? 1 : 0); usleep(5000); } gpiod_chip_close(chip); return 0; }这段代码把 line 5 当输入、line 6 当输出输入为高时输出置高5ms 轮询一次。gpiod_line_request_input 的第二个参数是 consumer 名称会显示在 /sys/kernel/debug/gpio 里便于排查占用。编译命令是cc -o interlock interlock.c -lgpiod。注意 ICH 的 GPIO 读周期通常在微秒级轮询间隔不小于 1ms 才比较稳定太快的忙循环反而会把 CPU 占满。5. 三个容易踩的坑gpio-ich 的 GPIO 编号偏移、ACPI 占用与上电时序验证5.1 为什么原理图上的 GPIO 编号和系统里差了一大截ICH 数据手册里写的 GPIO33在 gpiochip 里未必是 33。原因有两层。首先驱动注册的 base 不一定是 0如果系统里已经有一个 gpiochip新的 chip base 会顺延到下一个连续编号可能在 400 甚至 1000。其次ICH 的每组引脚之间存在保留位驱动内部把原始寄存器位映射到连续 line 号时会跳过这些位所以数据手册的编号和系统 line 号完全对不上。查映射最直接的方法是看驱动源码里的ichx_gpio描述表或者通过 gpioinfo 查看每个 line 的 names。日常调试时我会把原理图上用到的 GPIO 号先列出来再用 gpioset 挨个输出高电平拿万用表量引脚确认。这一步能省掉后面大半的排错时间。5.2 被 ACPI 和 PCH 内部逻辑占用的 IOICH 里有一部分 GPIO 在默认状态下直接作为其它功能使用比如 GPIO21 在 ICH7 上连接 LAN 的 PHY 中断GPIO51 可能关联 PCIe 的 wake。ACPI 表如果声明了这些引脚gpio-ich 在 request 时就会返回 busy。遇到这种情况先看 debugfscat /sys/kernel/debug/gpio这里会列出每个 line 的占用者被 ACPI 占用的 line 显示 consumer 为 ACPI。确认引脚没有被固件实际使用后可以通过 3.3 里的 reserve 参数把它从 ACPI 占用列表里摘掉。但要注意有些引脚和 PCH 内部逻辑共享即使驱动能操作外部也读不到预期状态比如和 TCO 定时器复用的引脚。排查时拿万用表量一下引脚电压不响应就换一个引脚。5.3 验证时序的测量点选择别把 3.3V 电平当开漏最后验证时序时逻辑分析仪的测量点要选在负载端而不是直接卡在 CPU 旁边的排阻上。ICH GPIO 输出往往是推挽但很多板子外部又加了一级开漏转换。如果测点放在转换器前面会把内部上拉的下沉脉冲当成平直高电平。另外ICH 的 GPIO 驱动能力很弱手册上典型值只有几毫安。外接 LED 时串一个 1k 电阻别直接灌到 MCU 引脚。用示波器看时序时触发条件最好设成下降沿因为上电时序里最重要的是使能信号撤掉的时刻。验证命令可以用 gpioget 持续采样确认稳态但真正的时序宽度必须靠示波器用户态轮询的抖动太大不能作为验收依据。本文还有配套的精品资源点击获取