Linux系统应用调驱动过程

在Linux系统中,应用程序打开一个驱动节点需要经过一系列的调用过程,涉及到设备文件的打开、设备驱动的注册、文件操作函数的调用等。下面是整个调用过程的一般步骤:

  1. 设备文件的打开:
    应用程序使用系统调用函数(如open())来打开设备文件,以获取设备文件描述符。设备文件通常位于/dev目录下,具体路径根据驱动程序的实现而定。

  2. 文件操作函数的调用:
    打开设备文件后,应用程序可以使用系统调用函数(如read()、write()、ioctl()等)来进行读写操作或控制设备。这些系统调用函数将调用内核提供的文件操作函数。

  3. 设备驱动的注册:
    当设备文件首次打开时,内核会在设备驱动模块中查找与该设备文件对应的设备驱动程序。设备驱动程序需要经过probing阶段,并通过注册函数(如register_chrdev())将自己注册到内核。

  4. 设备的打开:
    当设备驱动程序被成功注册后,内核将调用设备驱动程序中的open()函数来处理设备文件的打开请求。在open()函数中,设备驱动程序可以进行相关设备的初始化操作,并返回设备的私有数据结构指针,供后续的读写操作使用。

  5. 文件操作函数的注册:
    在设备驱动程序中,需要实现一系列的文件操作函数(如read()、write()、ioctl()等),以提供应用程序对设备的读写控制功能。这些函数通常会在设备的打开过程中被注册到内核的file_operations结构体中。

  6. 应用程序的读写操作:
    应用程序使用系统调用函数(如read()、write()、ioctl()等)进行读写操作时,将会调用内核中对应的文件操作函数。这些函数由设备驱动程序实现,用于处理读写请求,对设备进行读写操作。

  7. 设备的关闭:
    当应用程序关闭设备文件时,内核将调用设备驱动程序中的close()函数来处理设备的关闭操作。在close()函数中,设备驱动程序可以进行相关资源的释放和清理工作。

总的来说,应用程序打开一个驱动节点的过程包括设备文件的打开、设备驱动的注册、文件操作函数的调用等步骤。其中,设备驱动程序起到了桥梁的作用,将应用程序的请求传递给对应的设备操作函数来完成实际的设备操作。

下面是调用open(I2C_BUS, O_RDWR)函数时的一个简化的过程追踪示例:

  1. 应用程序调用open函数,传入I2C总线设备文件路径 I2C_BUS 和读写模式标志 O_RDWR
  2. 应用程序进入内核空间,内核开始处理该系统调用。
  3. 内核根据设备文件路径 I2C_BUS 找到对应的文件对象,并验证权限。
  4. 内核返回一个可用的文件描述符来表示该文件打开实例。文件描述符是一个对该文件的引用。
  5. 内核为该文件描述符设置状态标志位,包括读写模式、文件访问权限等。
  6. 内核分配一个新的file结构体,用于表示该文件对象,并关联到文件描述符上。
  7. 内核调用I2C驱动程序的open方法,传递相关参数,如文件对象、读写模式等。
  8. I2C驱动程序的open方法执行相应的操作,比如打开设备、初始化硬件、分配资源等。
  9. 在I2C驱动程序完成初始化操作后,open方法返回给内核。
  10. 内核再将文件描述符返回给应用程序。
  11. 应用程序继续执行后续的操作,如读写文件等。

在这个过程中,关键步骤包括打开文件、创建文件描述符、设置文件状态标志位、调用驱动程序的open方法等。这些步骤在内核空间中完成,并为应用程序提供了一个用于后续操作的文件描述符。驱动程序的open方法可以执行与设备初始化和资源分配相关的操作,以确保设备在后续的读写操作中正常工作。

请注意,这只是一个简化的示例,实际的过程可能涉及更多的细节和操作。此外,具体的实现可能因操作系统和设备驱动的差异而有所不同。

当涉及到Linux内核中的I2C驱动时,以下是每个组件的详细介绍:

  1. I2C核心层(I2C Core):

    • 设备的自动探测:I2C核心层负责自动探测总线上连接的设备。它会扫描总线并检测设备的存在。
    • 设备地址管理:在I2C总线上,每个设备都有一个唯一的地址。I2C核心层管理设备地址,使得应用程序能够与特定设备进行通信。
    • 时序控制:I2C核心层确保正确的时序和时钟生成,以满足I2C通信的要求。
    • 访问接口函数:I2C核心层提供了一组API函数,使设备驱动程序能够与I2C总线进行交互,包括读取和写入数据。
  2. I2C适配器驱动(I2C Adapter Driver):

    • 硬件配置:I2C适配器驱动程序负责与硬件平台相关的操作,包括I2C控制器的配置和初始化。
    • 时钟频率设置:适配器驱动程序设置I2C总线的时钟频率,以控制数据传输速度。
    • 中断处理:适配器驱动程序处理I2C控制器相关的中断,以便更好地管理和响应总线事件。
  3. I2C设备驱动(I2C Device Driver):

    • 寄存器配置:设备驱动程序负责对设备进行初始化和配置,包括设置设备寄存器的值和模式。
    • 传输协议操作:设备驱动程序使用I2C核心层提供的接口函数执行传输协议操作,如发送读写命令、读取数据等。
    • 数据解析:设备驱动程序负责解析从设备读取的原始数据,并将其转换为应用程序可以理解的格式。
  4. I2C设备模型(I2C Device Model):

    • 设备管理和表示:I2C设备模型提供了一种层次结构的组织方式,用于表示和管理连接在I2C总线上的设备。它将设备抽象为一个结构体,包括设备的地址、供应商ID、设备ID等信息。
    • 驱动绑定和解绑:设备模型负责将设备与相应的设备驱动程序进行绑定,以确保正确的设备驱动程序用于特定的设备。
  5. I2C设备板级文件(I2C Board File):

    • 硬件配置描述:板级文件包含特定硬件平台的配置信息,如引脚连接、时序参数、设备地址等。它描述了I2C适配器的硬件连接和初始化配置。
    • 与设备驱动匹配:设备模型会根据板级文件中的配置信息与适当的设备驱动程序进行匹配,并将驱动绑定到设备上。

这些组件共同协作,实现了在Linux内核中对I2C总线和设备的管理、控制和访问。I2C核心层处理总线和设备的基本功能,适配器驱动负责底层硬件操作,设备驱动程序管理特定设备的初始化和通信,设备模型提供设备的抽象表示和管理,而板级文件描述硬件平台的配置信息。

当应用程序调用 read 和 write 系统调用时,内核会自动调用对应的设备驱动程序的 read 和 write 函数。下面是一个简单的示例,展示了如何在设备驱动程序中实现 read 和 write 函数来进行读写操作:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>

#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_readmy_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 函数在以下情况下会被调用:

  1. 驱动程序注册时,系统检测到与该驱动程序匹配的设备存在时。
  2. 设备被插入系统时(热插拔设备)。

需要注意的是,probe 函数和其他驱动程序函数(如 readwrite 等)是在内核中被自动调用的,而不是由用户空间的应用程序直接调用。它们通过设备和驱动程序之间的注册关系和设备文件操作接口进行间接调用。

是的,I2C 驱动程序中通常没有 open 函数。这是因为 I2C 总线是一种多主机总线,设备之间的通信是通过发送和接收消息来完成的,而不是通过打开和关闭设备文件。

在 Linux 内核中,设备文件操作接口(file operations interface)是用于处理字符设备或块设备的操作的,而 I2C 设备并不常被视为字符设备或块设备。因此,I2C 驱动程序通常不需要实现 openrelease 等设备文件操作函数。

相反,I2C 设备的通信通常是由应用程序直接调用 readwrite 系统调用,通过打开 I2C 总线的设备文件来进行操作。这些系统调用将由内核调用驱动程序的 readwrite 函数来实际处理数据传输。在 I2C 驱动程序中,你需要实现 readwrite 函数,以处理来自应用程序的读写请求。

因此,在 I2C 驱动程序中,你不需要关注 open 函数。你只需要关注实现 proberemovereadwrite 函数来处理设备的初始化、注销和数据传输。根据你的应用需求,你可能还需要实现其他的驱动程序函数,如 ioctl 等。

需要注意的是,以上内容适用于大多数情况,但在某些特定情况下,可能会有一些定制的 I2C 驱动程序需要实现 open 函数。这取决于具体的设备和应用场景。但一般情况下,I2C 驱动程序中并不包含 open 函数。

如果你需要在 I2C 驱动程序中实现 ioctl 函数以支持特定的控制操作,你可以添加相应的代码。下面是一个简化的示例,展示了如何在 I2C 驱动程序中实现 ioctl 函数:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>

#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_CMD1MY_IOCTL_CMD2 是所定义的两个示例 ioctl 命令。你需要根据具体的应用需求定义相应的命令和参数,并在函数中实现对应的逻辑。

在注册文件操作接口时,将 my_i2c_ioctl 函数指定为 unlocked_ioctl 字段,以便内核在收到 ioctl 系统调用时调用该函数。

需要注意的是,在用户空间应用程序中,你需要使用 ioctl 系统调用并传递相应的命令和参数来触发对应的 ioctl 操作。

要确定 I2C-0 对应的具体驱动设备,你可以通过查看设备树(DTS/DTB)文件或内核日志来找到相应的信息。

  1. 设备树:在设备树中,每个 I2C 控制器和其对应的设备节点都会有一个唯一的标识符。你可以查找设备树中与 I2C 控制器0 (i2c-0) 相关联的节点,以确定其对应的设备。

在设备树中,通常会有一个 i2c@<address> 的节点,其中 <address> 是 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>;
  };
};
  1. 内核日志:你可以通过查看内核启动过程中的日志来获取有关 I2C-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 控制器对应的具体驱动设备。重要的信息包括设备名称、设备标识符和驱动程序名称。根据这些信息,你可以进一步了解驱动设备的特性和功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值