大道至简【Linux】i2c驱动给其他模块使用
·
完整展示了如何通过 EXPORT_SYMBOL 导出函数给其他模块使用,包含多设备管理、并发控制、错误处理及跨模块调用示例:
一、主I2C驱动模块(导出函数的提供者)
该模块负责初始化I2C设备,封装核心操作函数并导出,同时处理多设备和并发问题。
i2c_core.c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/of.h>
#define MAX_DEVICES 5 // 支持最多5个同类型I2C设备
// 全局结构:管理所有设备的client和互斥锁
static struct {
struct i2c_client *clients[MAX_DEVICES]; // 保存多个i2c_client
int count; // 已注册设备数量
struct mutex lock; // 并发控制锁
} i2c_dev_manager;
// 内部函数:根据设备地址查找对应的i2c_client(辅助其他模块获取client)
static struct i2c_client *find_client_by_addr(u16 addr) {
int i;
struct i2c_client *client = NULL;
mutex_lock(&i2c_dev_manager.lock);
for (i = 0; i < i2c_dev_manager.count; i++) {
if (i2c_dev_manager.clients[i]->addr == addr) {
client = i2c_dev_manager.clients[i];
break;
}
}
mutex_unlock(&i2c_dev_manager.lock);
return client;
}
/**
* 导出函数:读取I2C设备寄存器
* @addr: 设备I2C地址(如0x50)
* @reg: 要读取的寄存器地址
* @val: 输出参数,保存读取到的值
* 返回:0成功,负数失败
*/
int i2c_common_read_reg(u16 addr, u8 reg, u8 *val) {
struct i2c_client *client;
int ret;
struct i2c_msg msg[2] = {
{ .addr = addr, .flags = 0, .len = 1, .buf = ® }, // 写寄存器地址
{ .addr = addr, .flags = I2C_M_RD, .len = 1, .buf = val } // 读数据
};
// 参数检查
if (!val)
return -EINVAL;
// 查找设备对应的client
client = find_client_by_addr(addr);
if (!client)
return -ENODEV;
// 加锁防止并发冲突
mutex_lock(&i2c_dev_manager.lock);
ret = i2c_transfer(client->adapter, msg, 2);
mutex_unlock(&i2c_dev_manager.lock);
return ret == 2 ? 0 : ret; // i2c_transfer返回成功传输的消息数
}
EXPORT_SYMBOL(i2c_common_read_reg); // 导出给其他模块
/**
* 导出函数:写入I2C设备寄存器
* @addr: 设备I2C地址
* @reg: 要写入的寄存器地址
* @val: 要写入的值
* 返回:0成功,负数失败
*/
int i2c_common_write_reg(u16 addr, u8 reg, u8 val) {
struct i2c_client *client;
int ret;
u8 buf[2] = {reg, val}; // 寄存器地址+数据
struct i2c_msg msg = {
.addr = addr, .flags = 0, .len = 2, .buf = buf
};
// 查找设备对应的client
client = find_client_by_addr(addr);
if (!client)
return -ENODEV;
// 加锁防止并发冲突
mutex_lock(&i2c_dev_manager.lock);
ret = i2c_transfer(client->adapter, &msg, 1);
mutex_unlock(&i2c_dev_manager.lock);
return ret == 1 ? 0 : ret; // 成功传输1条消息
}
EXPORT_SYMBOL(i2c_common_write_reg); // 导出给其他模块
// 设备匹配表(对应设备树compatible)
static const struct of_device_id i2c_common_dt_ids[] = {
{ .compatible = "vendor,i2c-common" },
{ }
};
MODULE_DEVICE_TABLE(of, i2c_common_dt_ids);
// probe函数:设备匹配时调用,保存i2c_client
static int i2c_common_probe(struct i2c_client *client, const struct i2c_device_id *id) {
if (!client)
return -ENODEV;
mutex_lock(&i2c_dev_manager.lock);
// 检查是否超过最大设备数
if (i2c_dev_manager.count >= MAX_DEVICES) {
mutex_unlock(&i2c_dev_manager.lock);
dev_err(&client->dev, "Too many devices (max %d)\n", MAX_DEVICES);
return -ENOSPC;
}
// 保存client到管理数组
i2c_dev_manager.clients[i2c_dev_manager.count++] = client;
dev_info(&client->dev, "I2C device registered (addr 0x%x, count %d)\n",
client->addr, i2c_dev_manager.count);
mutex_unlock(&i2c_dev_manager.lock);
return 0;
}
// remove函数:设备卸载时清理
static int i2c_common_remove(struct i2c_client *client) {
int i;
mutex_lock(&i2c_dev_manager.lock);
for (i = 0; i < i2c_dev_manager.count; i++) {
if (i2c_dev_manager.clients[i] == client) {
// 移除设备(将最后一个设备移到当前位置,减少数组操作)
i2c_dev_manager.clients[i] = i2c_dev_manager.clients[--i2c_dev_manager.count];
dev_info(&client->dev, "I2C device unregistered (addr 0x%x)\n", client->addr);
break;
}
}
mutex_unlock(&i2c_dev_manager.lock);
return 0;
}
// I2C驱动结构体
static struct i2c_driver i2c_common_driver = {
.driver = {
.name = "i2c-common",
.of_match_table = i2c_common_dt_ids, // 设备树匹配
},
.probe = i2c_common_probe,
.remove = i2c_common_remove,
};
// 模块初始化与退出
static int __init i2c_common_init(void) {
mutex_init(&i2c_dev_manager.lock); // 初始化互斥锁
return i2c_add_driver(&i2c_common_driver);
}
static void __exit i2c_common_exit(void) {
i2c_del_driver(&i2c_common_driver);
mutex_destroy(&i2c_dev_manager.lock); // 销毁互斥锁
}
module_init(i2c_common_init);
module_exit(i2c_common_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("I2C common driver (export functions to other modules)");
二、设备树节点(配合主驱动)
在I2C控制器节点下添加设备描述,主驱动会通过compatible匹配并初始化设备。
// 假设挂载在I2C1控制器下
&i2c1 {
status = "okay";
clock-frequency = <100000>; // 100KHz
// 第一个I2C设备(地址0x50)
i2c_dev1: i2c-device@50 {
compatible = "vendor,i2c-common"; // 与主驱动的of_match_table匹配
reg = <0x50>; // I2C设备地址
};
// 第二个I2C设备(地址0x51)
i2c_dev2: i2c-device@51 {
compatible = "vendor,i2c-common";
reg = <0x51>;
};
};
三、其他模块(调用导出函数的使用者)
该模块无需编写完整I2C驱动,直接通过extern声明并调用主驱动导出的函数。
i2c_user.c
#include <linux/module.h>
#include <linux/kernel.h>
// 声明主驱动导出的函数(也可通过头文件包含)
extern int i2c_common_read_reg(u16 addr, u8 reg, u8 *val);
extern int i2c_common_write_reg(u16 addr, u8 reg, u8 val);
static int __init i2c_user_init(void) {
int ret;
u8 val;
// 操作地址0x50的设备:写入寄存器0x01值0xAA
ret = i2c_common_write_reg(0x50, 0x01, 0xAA);
if (ret) {
printk(KERN_ERR "Write to 0x50 failed (ret %d)\n", ret);
return ret;
}
// 读取地址0x50的寄存器0x01,验证写入结果
ret = i2c_common_read_reg(0x50, 0x01, &val);
if (ret) {
printk(KERN_ERR "Read from 0x50 failed (ret %d)\n", ret);
return ret;
}
printk(KERN_INFO "Read from 0x50 reg 0x01: 0x%x (expected 0xAA)\n", val);
// 操作地址0x51的设备(示例)
ret = i2c_common_write_reg(0x51, 0x02, 0x55);
if (!ret)
printk(KERN_INFO "Write to 0x51 reg 0x02 success\n");
return 0;
}
static void __exit i2c_user_exit(void) {
printk(KERN_INFO "I2C user module exit\n");
}
module_init(i2c_user_init);
module_exit(i2c_user_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Module using exported I2C functions");
四、关键改进点说明
- 多设备管理:通过数组
clients[]保存多个i2c_client,支持同时管理多个同类型I2C设备(如地址0x50、0x51)。 - 并发控制:使用
mutex互斥锁,确保多个模块同时调用导出函数时不会冲突(如A模块读时B模块不能写)。 - 设备查找机制:提供
find_client_by_addr内部函数,其他模块只需知道设备地址即可操作,无需关心i2c_client的具体来源。 - 错误处理:完善参数检查(如
val为空)、设备不存在判断(返回-ENODEV),返回值更清晰。 - 跨模块调用示例:
i2c_user.c展示了如何通过extern声明直接调用导出函数,无需重复编写I2C读写逻辑。
五、使用流程
- 编译并加载主驱动
i2c_core.ko,内核会匹配设备树中的i2c-device@50和i2c-device@51,保存它们的i2c_client。 - 编译并加载用户模块
i2c_user.ko,模块会调用i2c_common_write_reg和i2c_common_read_reg操作设备,打印结果。 - 通过
dmesg查看输出,验证跨模块调用是否成功。
这个示例真实还原了内核中“核心驱动导出函数,其他模块复用”的场景,避免了重复开发,同时通过锁机制保证了多模块访问的安全性。
更多推荐

所有评论(0)