如果你翻过Linux内核里各种外设驱动的代码,一定会注意到一个有趣的现象:
有的驱动
一、从一个困惑说起
都是I2C设备,凭什么写法不一样?多套一层regmap到底图什么?
这篇文章就带你搞清楚regmap子系统的来龙去脉。我们会深入到源码层面,讲清楚它的架构设计、读写流程、缓存机制,并用安卓手机上真实的驱动案例来解释:什么时候该用regmap,什么时候不该用。
二、regmap是什么?——内核里的"万能遥控器"
regmap(Register Map)是Linux 3.1内核引入的一个寄存器映射抽象层,源代码位于 drivers/base/regmap/。它的设计哲学可以用一句话概括:
驱动只关心寄存器的地址和值,不关心寄存器是挂在I2C上、SPI上还是MMIO上。
打个比方,regmap就像一个万能遥控器——不管你家电视是索尼的还是三星的(I2C还是SPI),遥控器(regmap)都提供同一套按键(regmap_read/write)。它帮你屏蔽了底层通信协议的差异。
那它的内部结构是怎样的?如图1所示,regmap分为四个层次:
最上层是驱动直接调用的API:regmap_read()、regmap_write()、regmap_update_bits()。驱动工程师写代码时只需要和这一层打交道。
第二层是regmap核心(regmap Core),这是整个子系统的大脑。它负责锁管理(保证多线程安全)、寄存器格式化(把地址和值按位宽打包)、缓存管理(regcache)和debugfs调试追踪。
第三层是总线适配层。regmap为每种总线都写了一个适配器:regmap-i2c.c 对接I2C、regmap-spi.c 对接SPI、regmap-mmio.c 对接内存映射IO……目前已经支持10种总线(I2C/SPI/MMIO/AC97/Slimbus/SoundWire/SPMI/W1/SCCB/I3C)。
最底层是硬件总线控制器,比如SoC内部的I2C Adapter、SPI Master等。
三、regmap读操作:从API到硬件的完整旅程
驱动调用 regmap_read(map, reg, &val) 这一行代码背后,内核到底做了多少事?我们来看看完整的调用链。

▲ 图2:regmap_read() 完整调用链 — 从API入口到硬件I2C传输,再到缓存回填
图2展示了一条regmap读操作的完整路径。最关键的环节是第②步:查缓存。如果寄存器已经在缓存中、且不是volatile寄存器,regmap会直接返回缓存值,完全跳过硬件访问。这就是regmap带来的最大性能收益——对于I2C这种慢速总线(100kHz下传输2字节约需200μs),缓存命中可以把延迟降到纳秒级。
但要小心:如果某个寄存器的值会被硬件自动改变(比如中断状态寄存器、ADC数据寄存器),你却忘了在 volatile_reg 回调中标记它,那regmap就会静默返回过期缓存数据——这是最难排查的bug之一。
⚠ 避坑指南:建议默认把所有寄存器都标记为volatile,然后只把明确安全可缓存的寄存器加入白名单。宁可少缓存,不要错缓存。
四、regmap写操作:一次写入的三条路径
写操作和读操作最大的不同在于顺序:regmap是先写缓存,再写硬件。这样即便硬件写入失败,缓存中已经有了最新值,后续读操作可以从缓存获取。但这也引入了一个状态标志:cache_dirty——当缓存比硬件更新时,这个标志被置位。芯片从休眠唤醒后,需要调用 regcache_sync() 将脏缓存刷回硬件。
特别值得关注的是右侧的 regmap_update_bits()。在底层驱动中,最常见的操作不是全寄存器写入,而是只修改寄存器的某几个bit——比如只改第3位的使能开关,不能影响其他位。传统做法是:
/* 传统方式:三步操作,不是原子的!*/
这三个步骤不是原子的——如果第①步和第③步之间被中断打断,另一个线程修改了同一个寄存器,就会产生竞态条件。而regmap通过内置的锁机制,保证了 regmap_update_bits() 的整个读-改-写过程是原子的。
五、regcache缓存机制:快慢总线的加速器
缓存是regmap最核心的能力之一。它解决了什么问题?I2C/SPI这类慢速总线,每次寄存器访问需要200μs以上,而CPU一个时钟周期才几纳秒——这差了5个数量级。如果能用内存缓存来减少总线事务,性能提升是巨大的。
▲ 图4:regcache三种缓存模式对比 + 四种标志位控制逻辑
regmap提供了三种缓存策略:
- FLAT(扁平数组):最简单,把所有寄存器值放在一个连续数组里,O(1)查找。适合寄存器地址连续且数量少的情况。
- RBTREE(红黑树):用红黑树管理稀疏的寄存器表,O(log n)查找。适合寄存器多、地址不连续的情况。大多数音频Codec都用这个。
- COMPRESSED(LZO压缩):在FLAT基础上做了LZO压缩,省内存。适合内存受限的嵌入式场景。
最容易被忽视的是 volatile_reg 这个回调。它告诉regmap"这个寄存器的值会自己变,别缓存"。常见的volatile寄存器包括:中断状态寄存器(读后自动清零)、ADC数据寄存器(硬件持续更新)、FIFO寄存器(读取消耗数据)、实时状态寄存器(温度、电压等)。