在嵌入式Linux开发中,I2C总线上的传感器驱动加载是个经典问题。最近在调试一块定制板时,遇到了一个典型场景:系统存在多个I2C总线(如i2c-0、i2c-1),但只需要在i2c-1总线上加载特定的传感器驱动。这个需求在多点触控屏、环境传感器阵列等场景都很常见。
传统做法是直接修改驱动代码或设备树,但这会破坏代码的可维护性。经过实践,我总结出一套通过udev规则和驱动参数精准控制设备加载的方法,无需重新编译内核或驱动,仅需用户空间配置即可实现目标。
现代Linux系统的设备加载流程涉及三个关键层:
我们的目标是在不修改前两层的情况下,通过第三层实现精准控制。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 设备树别名 | 静态绑定可靠 | 需重新编译DT | 固定硬件配置 |
| 驱动参数 | 动态配置灵活 | 需驱动支持 | 多总线环境 |
| udev规则 | 用户空间控制 | 依赖设备属性 | 动态热插拔 |
最终选择组合使用驱动参数+udev规则,这是最灵活且非侵入式的方案。
首先通过i2cdetect工具确认设备地址:
bash复制i2cdetect -y 1 # 扫描i2c-1总线
假设输出显示目标传感器位于0x68地址,设备名为bmx160。
修改/etc/modprobe.d/bmx160.conf:
conf复制options bmx160_i2c bus_id=1
这告诉驱动只处理i2c-1总线的设备。需要驱动支持bus_id参数(大多数标准驱动都支持)。
创建/etc/udev/rules.d/99-i2c-sensor.rules:
bash复制SUBSYSTEM=="i2c", ENV{MODALIAS}=="i2c:bmx160", ATTR{name}=="i2c-1", RUN+="/sbin/modprobe bmx160_i2c"
规则解释:
SUBSYSTEM=="i2c":匹配I2C设备ENV{MODALIAS}:匹配设备兼容性标识ATTR{name}:精确匹配总线编号RUN+=:条件满足时加载驱动重新加载udev规则并触发事件:
bash复制udevadm control --reload
udevadm trigger
dmesg | grep bmx160 # 查看驱动加载日志
获取设备的完整属性集:
bash复制udevadm info -a /sys/bus/i2c/devices/i2c-1/1-0068
输出示例:
code复制looking at device '/devices/platform/soc/20804000.i2c/i2c-1/1-0068':
KERNEL=="1-0068"
SUBSYSTEM=="i2c"
DRIVER==""
ATTR{name}=="bmx160"
ATTR{modalias}=="i2c:bmx160"
这些属性都可作为udev规则的匹配条件。
对于需要动态配置的场景,可以在udev规则中设置环境变量:
bash复制ACTION=="add", SUBSYSTEM=="i2c", ENV{MODALIAS}=="i2c:bmx160", \
ENV{BUS_ID}="1", RUN+="/bin/sh -c 'echo bus_id=$env{BUS_ID} > /sys/module/bmx160_i2c/parameters/bus_id'"
检查步骤:
bash复制ls /sys/bus/i2c/devices/ | grep i2c-1
bash复制modprobe bmx160_i2c bus_id=1
bash复制zcat /proc/config.gz | grep CONFIG_I2C
在某些平台上,I2C总线编号可能因枚举顺序变化。解决方案:
bash复制SUBSYSTEM=="i2c", ATTR{phys}=="20804000.i2c", ...
当同一总线有多个同类设备时,需要更精确的匹配:
bash复制SUBSYSTEM=="i2c", ATTR{name}=="i2c-1", ATTR{address}=="68", ...
延迟加载:对于非关键传感器,添加初始化延迟减少系统启动压力:
bash复制RUN+="/bin/sh -c 'echo 2000 > /sys/module/bmx160_i2c/parameters/init_delay'"
电源管理:通过udev规则配置运行时PM:
bash复制ATTR{power/control}="auto"
中断优化:对于高频率传感器,在驱动参数中调整中断模式:
conf复制options bmx160_i2c irq_mode=1 poll_interval=50
这套方案已在多个定制板卡上验证,包括Rockchip、i.MX6和Allwinner平台。关键点在于理解Linux设备模型的分层结构,在合适的层级实施控制。相比直接修改驱动代码,这种方法保持了系统的可维护性和灵活性。