Linux驱动开发流程—probe函数

在总线匹配设备和驱动的时候,会调用probe函数然后再调用驱动自己的probe函数,
从而初始化驱动
以i2c为例:
probe函数也是驱动结构体(struct i2c_driver)中很重要的一个函数
而设备本身并不需要有probe函数。实际上,设备的存在是通过硬件描述(例如设备树或ACPI表)或总线扫描来确定的。
当内核检测到一个设备时,它会尝试找到合适的驱动程序,并调用该驱动程序的probe函数来初始化设备。
在device的角度
1.设备注册设备号 device_register(dev)
2.设备初始化 device_initialize()
3.设备添加到内核 device_add(dev)
4.添加到bus上 bus_add_device(dev)
5.等待调用probe bus_probe_device(dev)

在driver的角度
1.注册driver driver_register(drv)
2.绑定到bus上 bus_add_driver(dev)
3.device_attach(dev)
4.bus去找设备,然后driver_attach(drv,dev)
5.匹配到了驱动和设备 driver_match_device(drv,dev)
6.调用probe driver_probe_device(drv,dev)
7.really_probe(drv,dev)

可以看出,在这个过程中设备是被动地在等待,
当内核检测到一个设备时,它会调用驱动程序的probe函数,以便驱动程序可以初始化设备、分配资源、设置寄存器等
probe函数通常有以下结构:
 

static int my_device_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
    
    printk(KERN_INFO "my_device: Probing device\n");
    
    // 调用设备初始化函数
    ret = my_device_init(client);
    if (ret) {
        printk(KERN_ERR "my_device: Failed to initialize device\n");
        return ret;
    }

    printk(KERN_INFO "my_device: Device initialized successfully\n");
    return 0;
}


又发现,probe又是调用的my_device_init()函数来初始化
那么my_device_init()函数是在哪呢
还是举个例子:

static int my_device_init(struct i2c_client *client)
{
    int ret;
    u8 config_data = 0x01;  // 假设0x01表示开启设备功能
    
    // 向设备的寄存器0x00写入配置数据0x01
    ret = i2c_smbus_write_byte_data(client, 0x00, config_data);//重点!这是一个I2C子系统提供的函数
                                                                //用于向设备的特定寄存器写入一个字节的数据。
    if (ret < 0)
        dev_err(&client->dev, "Failed to initialize device, error: %d\n", ret);
        return ret;
    }
    
    printk(KERN_INFO "my_device: Device initialized with config data 0x01\n");
    return 0;
}


在这个函数中,通过向设备的某个寄存器写入配置数据来完成设备的初始化。好的,已经明白了probe函数了,看一下它在具体的一个驱动中怎么使用的:
还是i2c为例:

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/init.h>

// 设备初始化函数
static int my_device_init(struct i2c_client *client)
{
    // 设备的具体初始化代码
    printk(KERN_INFO "my_device: Initializing device\n");
    return 0;
}

// 驱动程序的 probe 函数
static int my_device_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
    
    printk(KERN_INFO "my_device: Probing device\n");
    
    // 调用设备初始化函数
    ret = my_device_init(client);
    if (ret) {
        printk(KERN_ERR "my_device: Failed to initialize device\n");
        return ret;
    }

    printk(KERN_INFO "my_device: Device initialized successfully\n");
    return 0;
}

// 设备ID表
static const struct i2c_device_id my_device_id[] = {
    { "my_device", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, my_device_id);

// I2C驱动结构体
static struct i2c_driver my_device_driver = {
    .driver = {
        .name = "my_device",
    },
    .probe = my_device_probe,
    .id_table = my_device_id,
};

// 模块初始化
static int __init my_device_init_module(void)
{
    return i2c_add_driver(&my_device_driver);
}

// 模块退出
static void __exit my_device_exit_module(void)
{
    i2c_del_driver(&my_device_driver);
}

module_init(my_device_init_module);
module_exit(my_device_exit_module);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My I2C Device Driver");
MODULE_AUTHOR("Author");

/* 上述函数使用 __init 标记可以显著优化内核模块的内存使用,因为它允许内核在初始化完成后释放不再需要的初始化代码和数据。
这不仅可以节省内存,还可以提高系统的整体性能和安全性。
还有其他的标记如_exit标记等:
__initdata: 标记初始化数据。
__exit: 标记模块卸载函数,只有在模块被卸载时才会调用。
__devinit: 标记设备初始化函数,主要用于特定设备的初始化。
__devexit: 标记设备卸载函数,主要用于特定设备的卸载。*/


驱动的角度来看,写好这一部分,已经足够了
关于设备的信息和总线匹配的策略一般是写好了的
设备信息:描述在设备树文件(.dts 文件)、ACPI表或者总线扫描中
重点关注下设备树,设备树的写法如下:
i2c1: i2c@40005400 {
    compatible = "myvendor,mydevice";
    reg = <0x40005400>;
    ...
};
还是很清晰的(设备树的掌握程度要求能够添加和修改一些参数以满足我们的需求)

再看下i2c的设备匹配(策略):

驱动程序通过定义 i2c_device_id 表来匹配设备:

static const struct i2c_device_id my_device_id[] = {
    { "my_device", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, my_device_id);

i2c_device_id 表:定义了设备的名称和相关信息。内核在检测到I2C设备时,会根据设备名称在这个表中查找匹配项。
MODULE_DEVICE_TABLE 宏:将设备ID表导出到用户空间工具(如 modinfo),以便在加载模块时进行匹配。

spi和platform也是类似的


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值