完整展示了如何通过 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 = &reg },         // 写寄存器地址
        { .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");

四、关键改进点说明

  1. 多设备管理:通过数组clients[]保存多个i2c_client,支持同时管理多个同类型I2C设备(如地址0x50、0x51)。
  2. 并发控制:使用mutex互斥锁,确保多个模块同时调用导出函数时不会冲突(如A模块读时B模块不能写)。
  3. 设备查找机制:提供find_client_by_addr内部函数,其他模块只需知道设备地址即可操作,无需关心i2c_client的具体来源。
  4. 错误处理:完善参数检查(如val为空)、设备不存在判断(返回-ENODEV),返回值更清晰。
  5. 跨模块调用示例i2c_user.c展示了如何通过extern声明直接调用导出函数,无需重复编写I2C读写逻辑。

五、使用流程

  1. 编译并加载主驱动i2c_core.ko,内核会匹配设备树中的i2c-device@50i2c-device@51,保存它们的i2c_client
  2. 编译并加载用户模块i2c_user.ko,模块会调用i2c_common_write_regi2c_common_read_reg操作设备,打印结果。
  3. 通过dmesg查看输出,验证跨模块调用是否成功。

这个示例真实还原了内核中“核心驱动导出函数,其他模块复用”的场景,避免了重复开发,同时通过锁机制保证了多模块访问的安全性。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐