1. IIC驱动子系统的三层架构设计
Linux的IIC驱动子系统采用了经典的三层架构设计,这种分层模式让硬件操作、核心管理和设备驱动各司其职。我在实际项目中多次使用这种架构,发现它不仅能降低代码耦合度,还能大幅提升驱动程序的移植性。
最底层是IIC控制器驱动层,这层直接和硬件打交道。你可以把它想象成硬件翻译官——它负责初始化IIC控制器硬件,配置时钟和引脚,以及生成精确的IIC时序信号。我记得第一次写控制器驱动时,为了调试起始信号的电平时间,用示波器看了整整一个下午。这层会向系统注册一个i2c_adapter结构体,相当于宣告"这里有一条可用的IIC总线"。
中间是IIC核心层,这是整个子系统的交通枢纽。它不直接操作硬件,而是负责管理所有注册的IIC总线和设备驱动。当系统检测到新的IIC设备时,核心层会拿着设备的"身份证"(比如设备地址和兼容性信息)去询问各个设备驱动:"你们谁认识这个设备?"一旦找到匹配的驱动,就会撮合它们建立连接。核心层还提供了统一的API接口,比如i2c_transfer,让上层驱动不用关心底层硬件的具体实现。
最上层是IIC设备驱动层,这是我们开发者最常打交道的部分。每个设备驱动都是特定设备的专家,知道如何与具体设备通信。比如MPU6050驱动知道如何读取加速度数据,而EEPROM驱动知道如何进行页写入操作。这层通过实现i2c_driver结构体来向核心层声明自己的能力范围。
2. 核心数据结构解析
2.1 i2c_adapter:总线的软件代表
i2c_adapter是控制器驱动层的核心数据结构,它代表了一条物理IIC总线。当我第一次看到这个结构体时,最吸引我的是里面的algo指针,它指向一个包含实际硬件操作函数的结构体。
struct i2c_adapter {
struct module *owner;
const struct i2c_algorithm *algo; /* 算法指针 */
void *algo_data;
int nr; /* 总线编号 */
char name[48]; /* 总线名称 */
struct device dev;
/* ... 其他成员 */
};
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num);
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality)(struct i2c_adapter *adap);
};
master_xfer函数是实现IIC通信的关键,它负责将通用的IIC消息转换成具体的硬件操作。我在一次项目中发现,不同的IIC控制器对这个函数的实现差异很大——有的依赖DMA传输,有的使用轮询方式,但上层驱动完全不用关心这些细节。
2.2 i2c_client:设备的身份证明
i2c_client代表一个具体的IIC从设备,由核心层在检测到设备时自动创建。这个结构体包含了设备的所有关键信息:
struct i2c_client {
unsigned short flags; /* 标志位 */
unsigned short addr; /* 设备地址(低7位) */
char name[I2C_NAME_SIZE]; /* 设备名称 */
struct i2c_adapter *adapter; /* 所属的总线适配器 */
struct device dev; /* 内嵌的设备结构 */
int irq; /* 中断号 */
/* ... 其他成员 */
};
在实际开发中,我经常通过client->adapter获取总线信息,通过client->addr与设备通信。这个结构体就像设备的身份证,确保了驱动能找到正确的通信对象。
2.3 i2c_driver:驱动的能力声明
i2c_driver是设备驱动向核心层注册的结构体,它声明了驱动能管理哪些设备:
struct i2c_driver {
unsigned int class;
int (*pro

&spm=1001.2101.3001.5002&articleId=155901863&d=1&t=3&u=3611a1bd59084aa3a727d33a88e9157e)
342

被折叠的 条评论
为什么被折叠?



