在Linux系统中,应用程序打开一个驱动节点需要经过一系列的调用过程,涉及到设备文件的打开、设备驱动的注册、文件操作函数的调用等。下面是整个调用过程的一般步骤:
设备文件的打开:
应用程序使用系统调用函数(如open())来打开设备文件,以获取设备文件描述符。设备文件通常位于/dev目录下,具体路径根据驱动程序的实现而定。
文件操作函数的调用:
打开设备文件后,应用程序可以使用系统调用函数(如read()、write()、ioctl()等)来进行读写操作或控制设备。这些系统调用函数将调用内核提供的文件操作函数。
设备驱动的注册:
当设备文件首次打开时,内核会在设备驱动模块中查找与该设备文件对应的设备驱动程序。设备驱动程序需要经过probing阶段,并通过注册函数(如register_chrdev())将自己注册到内核。
设备的打开:
当设备驱动程序被成功注册后,内核将调用设备驱动程序中的open()函数来处理设备文件的打开请求。在open()函数中,设备驱动程序可以进行相关设备的初始化操作,并返回设备的私有数据结构指针,供后续的读写操作使用。
文件操作函数的注册:
在设备驱动程序中,需要实现一系列的文件操作函数(如read()、write()、ioctl()等),以提供应用程序对设备的读写控制功能。这些函数通常会在设备的打开过程中被注册到内核的file_operations结构体中。
应用程序的读写操作:
应用程序使用系统调用函数(如read()、write()、ioctl()等)进行读写操作时,将会调用内核中对应的文件操作函数。这些函数由设备驱动程序实现,用于处理读写请求,对设备进行读写操作。
设备的关闭:
当应用程序关闭设备文件时,内核将调用设备驱动程序中的close()函数来处理设备的关闭操作。在close()函数中,设备驱动程序可以进行相关资源的释放和清理工作。
总的来说,应用程序打开一个驱动节点的过程包括设备文件的打开、设备驱动的注册、文件操作函数的调用等步骤。其中,设备驱动程序起到了桥梁的作用,将应用程序的请求传递给对应的设备操作函数来完成实际的设备操作。
下面是调用open(I2C_BUS, O_RDWR)
函数时的一个简化的过程追踪示例:
open
函数,传入I2C总线设备文件路径 I2C_BUS
和读写模式标志 O_RDWR
。I2C_BUS
找到对应的文件对象,并验证权限。file
结构体,用于表示该文件对象,并关联到文件描述符上。open
方法,传递相关参数,如文件对象、读写模式等。open
方法执行相应的操作,比如打开设备、初始化硬件、分配资源等。open
方法返回给内核。在这个过程中,关键步骤包括打开文件、创建文件描述符、设置文件状态标志位、调用驱动程序的open
方法等。这些步骤在内核空间中完成,并为应用程序提供了一个用于后续操作的文件描述符。驱动程序的open
方法可以执行与设备初始化和资源分配相关的操作,以确保设备在后续的读写操作中正常工作。
请注意,这只是一个简化的示例,实际的过程可能涉及更多的细节和操作。此外,具体的实现可能因操作系统和设备驱动的差异而有所不同。
当涉及到Linux内核中的I2C驱动时,以下是每个组件的详细介绍:
I2C核心层(I2C Core):
I2C适配器驱动(I2C Adapter Driver):
I2C设备驱动(I2C Device Driver):
I2C设备模型(I2C Device Model):
I2C设备板级文件(I2C Board File):
这些组件共同协作,实现了在Linux内核中对I2C总线和设备的管理、控制和访问。I2C核心层处理总线和设备的基本功能,适配器驱动负责底层硬件操作,设备驱动程序管理特定设备的初始化和通信,设备模型提供设备的抽象表示和管理,而板级文件描述硬件平台的配置信息。
当应用程序调用 read 和 write 系统调用时,内核会自动调用对应的设备驱动程序的 read 和 write 函数。下面是一个简单的示例,展示了如何在设备驱动程序中实现 read 和 write 函数来进行读写操作:
#include
#include
#include
#include
#define DEVICE_NAME "my_i2c_device"
static struct i2c_client *my_i2c_client;
static ssize_t my_i2c_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
u8 data[32];
int ret;
// 在这里使用 my_i2c_client 进行 I2C 读取操作,并将结果保存到 data 数组中
// 这里只是一个示例,你需要根据实际的设备和协议进行具体的读取操作
ret = i2c_master_recv(my_i2c_client, data, count);
if (ret < 0) {
printk(KERN_ERR "I2C read error: %d\n", ret);
return ret;
}
// 将读取的数据从内核空间复制到用户空间
if (copy_to_user(buf, data, count))
return -EFAULT;
return count;
}
static ssize_t my_i2c_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
u8 data[32];
int ret;
// 将数据从用户空间复制到内核空间
if (copy_from_user(data, buf, count))
return -EFAULT;
// 在这里使用 my_i2c_client 进行 I2C 写入操作,写入 data 数组中的数据
// 这里只是一个示例,你需要根据实际的设备和协议进行具体的写入操作
ret = i2c_master_send(my_i2c_client, data, count);
if (ret < 0) {
printk(KERN_ERR "I2C write error: %d\n", ret);
return ret;
}
return count;
}
static struct file_operations my_i2c_fops = {
.owner = THIS_MODULE,
.read = my_i2c_read,
.write = my_i2c_write,
};
static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// 设置驱动程序的文件操作接口
cdev_init(&my_i2c_cdev, &my_i2c_fops);
my_i2c_cdev.owner = THIS_MODULE;
// 在这里完成其他的初始化操作,如设备寄存器的配置等
// 注册设备
int ret = alloc_chrdev_region(&my_i2c_devno, 0, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "chardev region allocation failed\n");
return ret;
}
ret = cdev_add(&my_i2c_cdev, my_i2c_devno, 1);
if (ret < 0) {
printk(KERN_ERR "chardev addition failed\n");
unregister_chrdev_region(my_i2c_devno, 1);
return ret;
}
// 保存 I2C 设备的句柄,以供 read 和 write 函数使用
my_i2c_client = client;
return 0; // 返回 0 表示设备识别成功
}
static int my_i2c_remove(struct i2c_client *client)
{
// 在这里完成设备的清理操作
// 删除设备
cdev_del(&my_i2c_cdev);
unregister_chrdev_region(my_i2c_devno, 1);
return 0;
}
static struct i2c_device_id my_i2c_id[] = {
{ "my_i2c_device", 0 }, // 设备识别 ID
{ }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id);
static struct of_device_id my_i2c_of_match[] = {
{ .compatible = "my_i2c_device" }, // 设备的设备树匹配信息
{ }
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "my_i2c_device_driver",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(my_i2c_of_match),
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
.id_table = my_i2c_id,
};
static int __init my_i2c_driver_init(void)
{
return i2c_add_driver(&my_i2c_driver);
}
module_init(my_i2c_driver_init);
static void __exit my_i2c_driver_exit(void)
{
i2c_del_driver(&my_i2c_driver);
}
module_exit(my_i2c_driver_exit);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My I2C Device Driver");
MODULE_LICENSE("GPL");
在这个示例中,我们注册了一个字符设备,并将 read 和 write 函数设置为设备驱动程序的操作函数。在 read 函数中,我们使用 i2c_master_recv
函数从设备中读取数据,并将数据从内核空间复制到用户空间。在 write 函数中,我们使用 i2c_master_send
函数将数据从用户空间复制到内核空间,并发送给设备进行写入。
请注意,此示例中的 my_i2c_read
和 my_i2c_write
函数是以字符设备驱动的形式示例,仅适用于以文件操作接口(file operation interface)进行读写的场景。如果你的应用程序与设备的交互方式不是通过文件操作接口,你可能需要根据实际情况进行适当的修改。
此示例只是一个基本框架,实际的设备驱动程序需要根据具体的设备和应用需求进行定制开发。
在 Linux 内核中,probe
函数是由设备驱动程序在与设备匹配成功后调用的。当系统检测到与驱动程序匹配的设备时,会调用该驱动程序的 probe
函数进行设备初始化和注册。
probe
函数在设备驱动程序注册时通过 struct i2c_driver
结构体中的 probe
字段指定。当内核发现一个与该驱动程序匹配的设备时,在 i2c_add_driver
或其他类似的注册函数执行时,会遍历驱动程序列表,查找合适的驱动程序并调用其 probe
函数。
在 probe
函数中,你可以执行与设备初始化相关的操作,例如配置设备寄存器、申请设备资源、创建设备节点等。此外,你还可以完成一些与设备相关的初始化操作,如分配内存、初始化变量、注册中断处理程序等。
需要注意的是,probe
函数在驱动程序注册时只会被调用一次。当设备被插入系统或系统启动时,如果与该驱动程序匹配的设备存在,则会调用 probe
函数。如果设备是热插拔设备(hot-pluggable device),那么在设备插入系统时也会调用 probe
函数。
总结起来,probe
函数在以下情况下会被调用:
需要注意的是,probe
函数和其他驱动程序函数(如 read
、write
等)是在内核中被自动调用的,而不是由用户空间的应用程序直接调用。它们通过设备和驱动程序之间的注册关系和设备文件操作接口进行间接调用。
是的,I2C 驱动程序中通常没有 open
函数。这是因为 I2C 总线是一种多主机总线,设备之间的通信是通过发送和接收消息来完成的,而不是通过打开和关闭设备文件。
在 Linux 内核中,设备文件操作接口(file operations interface)是用于处理字符设备或块设备的操作的,而 I2C 设备并不常被视为字符设备或块设备。因此,I2C 驱动程序通常不需要实现 open
、release
等设备文件操作函数。
相反,I2C 设备的通信通常是由应用程序直接调用 read
和 write
系统调用,通过打开 I2C 总线的设备文件来进行操作。这些系统调用将由内核调用驱动程序的 read
和 write
函数来实际处理数据传输。在 I2C 驱动程序中,你需要实现 read
和 write
函数,以处理来自应用程序的读写请求。
因此,在 I2C 驱动程序中,你不需要关注 open
函数。你只需要关注实现 probe
、remove
、read
和 write
函数来处理设备的初始化、注销和数据传输。根据你的应用需求,你可能还需要实现其他的驱动程序函数,如 ioctl
等。
需要注意的是,以上内容适用于大多数情况,但在某些特定情况下,可能会有一些定制的 I2C 驱动程序需要实现 open
函数。这取决于具体的设备和应用场景。但一般情况下,I2C 驱动程序中并不包含 open
函数。
如果你需要在 I2C 驱动程序中实现 ioctl 函数以支持特定的控制操作,你可以添加相应的代码。下面是一个简化的示例,展示了如何在 I2C 驱动程序中实现 ioctl 函数:
#include
#include
#include
#include
#define DEVICE_NAME "my_i2c_device"
static struct i2c_client *my_i2c_client;
static long my_i2c_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
// 解析命令和参数,根据实际需求进行相关的操作
switch (cmd) {
case MY_IOCTL_CMD1:
// 执行命令1的处理逻辑
break;
case MY_IOCTL_CMD2:
// 执行命令2的处理逻辑
break;
default:
return -EINVAL; // 无效的命令
}
return 0;
}
static ssize_t my_i2c_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
// 实现读取逻辑,与之前提到的示例类似
}
static ssize_t my_i2c_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
// 实现写入逻辑,与之前提到的示例类似
}
static struct file_operations my_i2c_fops = {
.owner = THIS_MODULE,
.read = my_i2c_read,
.write = my_i2c_write,
.unlocked_ioctl = my_i2c_ioctl, // 添加 ioctl 函数的处理
};
static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// 同之前的示例
// 保存 I2C 设备的句柄,以供 read、write 和 ioctl 函数使用
my_i2c_client = client;
return 0; // 返回 0 表示设备识别成功
}
// 同之前的示例
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "my_i2c_device_driver",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(my_i2c_of_match),
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
.id_table = my_i2c_id,
};
// 同之前的示例
在上面的示例中,我们定义了 my_i2c_ioctl
函数来处理 ioctl 命令。根据需要,你可以在函数内根据传递的命令和参数执行相应的操作逻辑。在 my_i2c_ioctl
函数中,你可以利用 I2C 设备句柄 my_i2c_client
进行相应的控制操作。
请注意,示例中的 MY_IOCTL_CMD1
和 MY_IOCTL_CMD2
是所定义的两个示例 ioctl 命令。你需要根据具体的应用需求定义相应的命令和参数,并在函数中实现对应的逻辑。
在注册文件操作接口时,将 my_i2c_ioctl
函数指定为 unlocked_ioctl
字段,以便内核在收到 ioctl 系统调用时调用该函数。
需要注意的是,在用户空间应用程序中,你需要使用 ioctl
系统调用并传递相应的命令和参数来触发对应的 ioctl 操作。
要确定 I2C-0 对应的具体驱动设备,你可以通过查看设备树(DTS/DTB)文件或内核日志来找到相应的信息。
i2c-0
) 相关联的节点,以确定其对应的设备。在设备树中,通常会有一个 i2c@
的节点,其中 是 I2C 控制器的物理地址或其他唯一标识符。你可以查找这个节点,并进一步查看其子节点,找到与
i2c-0
对应的设备节点,该节点将包含设备的名称和其他属性。
示例设备树片段:
i2c@0 {
compatible = "my_i2c_controller";
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
my_device@1 {
compatible = "my_device";
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
};
};
dmesg
或查看 /var/log/messages
或 /var/log/kern.log
文件,搜索与 I2C 控制器0 (i2c-0
) 相关的日志信息。查找与 i2c-0
相关的日志行,通常会包含有关驱动设备的信息,如设备名称、设备标识符、驱动程序名称等。
示例内核日志行:
i2c i2c-0: my_device: Registered device my_device at address 0x01
通过查看设备树或内核日志,你应该能够确定 I2C-0 控制器对应的具体驱动设备。重要的信息包括设备名称、设备标识符和驱动程序名称。根据这些信息,你可以进一步了解驱动设备的特性和功能。