首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇

  • 25-03-04 14:01
  • 2866
  • 13153
blog.csdn.net

往期内容

I2C子系统专栏:

  1. I2C(IIC)协议讲解-CSDN博客
  2. SMBus 协议详解-CSDN博客
  3. I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客

总线和设备树专栏:

  1. 专栏跳转
  2. 专栏末篇 – 有专栏文章的阅读顺序

前言

本章主要是分析内核提供的I2C设备驱动程序模板

Linux驱动程序: Linux-4.9.88/drivers/i2c/i2c-dev.c?i2c-dev.c

I2C 资料 (yuque.com)

由于篇幅原因,主要针对字符设备驱动程序框架的讲解和回顾,类比字符设备驱动框架来讲解Linux-4.9.88/drivers/i2c/i2c-dev.c中是如何去注册一个i2c字符设备驱动程序的

关于i2c-dev.c中file_operations结构体的相关性函数实现会另开文章讲解

1. 字符设备驱动程序

img

1.1 怎么编写字符设备驱动程序?

1. 确定主设备号

字符设备需要有主设备号来标识设备类型,次设备号来标识具体的设备。如果设备号由系统动态分配,使用 register_chrdev 时将 major 设置为 0 即可;否则可以手动指定主设备号。

2. 创建 file_operations 结构体

字符设备驱动通过 file_operations 结构体实现对设备的操作,常见的操作包括打开设备、读取设备、写入设备、控制设备等。这些函数的原型是:

static int drv_open(struct inode *inode, struct file *file);
static ssize_t drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
static ssize_t drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
static long drv_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static int drv_release(struct inode *inode, struct file *file);
  • 1
  • 2
  • 3
  • 4
  • 5

然后需要定义一个 file_operations 结构体,并将这些函数指针填入:

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = drv_open,
    .read = drv_read,
    .write = drv_write,
    .unlocked_ioctl = drv_ioctl,
    .release = drv_release,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3. 注册 file_operations 结构体

在驱动的初始化函数中使用 register_chrdev 注册字符设备:

int major = register_chrdev(0, "my_device", &fops);
if (major < 0) {
    printk(KERN_ALERT "Registering char device failed with %d\n", major);
    return major;
}
  • 1
  • 2
  • 3
  • 4
  • 5

这里如果 major 设置为 0,表示系统会自动分配主设备号,返回值即为分配的主设备号。

4. 调用 register_chrdev

register_chrdev 通常在驱动模块的入口函数(即 init 函数)中调用。入口函数用 module_init 宏标记:

static int __init my_init(void)
{
    int result = register_chrdev(0, "my_device", &fops);
    if (result < 0) {
        printk(KERN_ALERT "Registering char device failed\n");
        return result;
    }
    return 0;
}
module_init(my_init);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5. 编写出口函数

驱动模块卸载时需要调用 unregister_chrdev 释放设备号,并清理相关资源。出口函数用 module_exit 宏标记:

static void __exit my_exit(void)
{
    unregister_chrdev(major, "my_device");
}
module_exit(my_exit);
  • 1
  • 2
  • 3
  • 4
  • 5

6. 使用辅助函数自动创建设备节点

在现代 Linux 系统中,使用 class_create 和 device_create 可以帮助自动创建设备节点,避免手动使用 mknod 命令。示例如下:

static struct class *cls;
cls = class_create(THIS_MODULE, "my_class");
device_create(cls, NULL, MKDEV(major, 0), NULL, "my_device");
  • 1
  • 2
  • 3

驱动卸载时要对应清理这些资源:

device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
  • 1
  • 2

以下是一个简单字符设备驱动的完整框架:

#include 
#include 
#include 

#define DEVICE_NAME "my_device"
static int major;
static struct class *cls;

static int drv_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device opened\n");
    return 0;
}

static ssize_t drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    printk(KERN_INFO "Device read\n");
    return 0; // 读取时返回读取的字节数
}

static ssize_t drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk(KERN_INFO "Device write\n");
    return count; // 写入时返回写入的字节数
}

static int drv_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device closed\n");
    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = drv_open,
    .read = drv_read,
    .write = drv_write,
    .release = drv_release,
};

static int __init my_init(void)
{
    major = register_chrdev(0, DEVICE_NAME, &fops);
    if (major < 0) {
        printk(KERN_ALERT "Registering char device failed\n");
        return major;
    }
    cls = class_create(THIS_MODULE, "my_class");
    device_create(cls, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    printk(KERN_INFO "Device registered: %s with major %d\n", DEVICE_NAME, major);
    return 0;
}

static void __exit my_exit(void)
{
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, DEVICE_NAME);
    printk(KERN_INFO "Device unregistered\n");
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("A Simple Character Device Driver");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

这个框架可以作为编写字符设备驱动的基础。在实现过程中,具体的设备操作逻辑可以根据需要填充到 open、read、write 等函数中。

字符设备和之前在 平台总线设备驱动模型 中讲到的平台设备(总线和设备树_憧憬一下的博客-CSDN博客)有什么区别???

1. 字符设备驱动(Character Device Driver)

字符设备驱动是用于处理字符设备(如串口、键盘等)的驱动。它的核心特点是:

  • 按字节处理数据:字符设备一次读取或写入的数据是按字节(或字符)流方式进行的。
  • 用户空间接口:字符设备驱动通过设备文件(通常位于 /dev 目录下,如 /dev/ttyS0)向用户空间暴露接口。用户进程可以通过标准的文件操作(open, read, write, ioctl 等)与设备进行交互。
  • 主设备号和次设备号:字符设备使用主设备号(major number)和次设备号(minor number)来标识设备驱动程序和具体的设备实例。
  • 注册字符设备:通过调用 register_chrdev 或 cdev_add 等函数将字符设备注册到内核中,从而使内核知道如何处理对该设备文件的操作。

2. 平台总线设备驱动(Platform Device Driver)

平台设备驱动(platform device driver)与平台设备(platform device)紧密相关,平台设备是一种不通过 PCI 或其他总线探测的设备,通常是嵌入式系统中的片上外设。其核心特点是:

  • 无总线探测:平台设备是通过设备树(Device Tree)或硬编码的方式直接注册到系统中的,而不是通过动态探测(如 PCI 或 USB 总线)。
  • 匹配机制:平台设备和驱动通过名称(name)进行匹配,即平台设备通过 platform_device_register 注册,驱动通过 platform_driver_register 注册,内核通过名字来将设备与驱动绑定。
  • 设备树支持:平台设备驱动通常依赖设备树来描述硬件,尤其在嵌入式系统中非常常见。

字符设备驱动和平台设备驱动是不同的驱动模型,它们负责不同的层面:

  • 字符设备驱动提供用户空间接口。
  • 平台设备驱动管理底层硬件。

结合使用:当需要让用户空间与平台设备交互时,平台设备驱动可以注册一个字符设备,让用户通过字符设备文件访问底层硬件。

1.2 register_chrdev的内部实现

注册字符设备驱动就需要用到register_chrdev:

//字符设备驱动程序的简单init函数,主要是:
//register_chrdev:1.注册设备号、分配并初始化字符设备结构体,将字符设备加入到系统中,供用户空间访问
//class_create:2.创建设备节点:/dev/name_major-N
static int __init my_init(void)
{
    major = register_chrdev(0, DEVICE_NAME, &fops);
    if (major < 0) {
        printk(KERN_ALERT "Registering char device failed\n");
        return major;
    }
    cls = class_create(THIS_MODULE, "my_class");
    device_create(cls, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    printk(KERN_INFO "Device registered: %s with major %d\n", DEVICE_NAME, major);
    return 0;
}
\Linux-4.9.88\Linux-4.9.88\include\linux\fs.h:
static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}
\Linux-4.9.88\fs\char_dev.c
/*
参数
major: 主设备号,如果为 0 则表示由内核动态分配。
baseminor: 次设备号的基数。
count: 要注册的设备数量,通常为 1。
name: 字符设备的名称,通常用于 /dev 下的设备文件名。
fops: 指向文件操作的函数指针集合,用于定义设备的操作接口(如 read, write, ioctl 等)。
*/
int __register_chrdev(unsigned int major, unsigned int baseminor,
                      unsigned int count, const char *name,
                      const struct file_operations *fops)
{
    struct char_device_struct *cd;   // 用于存储字符设备信息的结构体指针
    struct cdev *cdev;               // 字符设备的核心结构体
    int err = -ENOMEM;               // 初始化错误码为 "内存不足"

    // 分配并注册字符设备号(包括主设备号和次设备号)
    cd = __register_chrdev_region(major, baseminor, count, name);
    // /dev/name_major-cd
    
    // 检查分配结果是否失败,如果失败则返回对应的错误码
    if (IS_ERR(cd))
        return PTR_ERR(cd);

    // 分配字符设备的结构体,返回分配的字符设备指针
    cdev = cdev_alloc();
    
    // 如果字符设备分配失败,跳转到 out2 标签释放资源并返回错误码
    if (!cdev)
        goto out2;

    // 设置字符设备的模块所有者,确保它与文件操作相关联
    cdev->owner = fops->owner;
    
    // 将文件操作结构体(`file_operations`)指针赋值给字符设备
    cdev->ops = fops;
    
    // 为字符设备的 kobject 赋予名称,用于 sysfs 中的表示
    kobject_set_name(&cdev->kobj, "%s", name);

    // 将字符设备加入内核,使用设备号和范围(count 指定的设备数目)
    err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
    
    // 如果字符设备添加失败,跳转到 out 标签释放资源并返回错误码
    if (err)
        goto out;

    // 将字符设备结构体与 `cd` 关联起来,以便后续访问
    cd->cdev = cdev;

    // 如果 `major` 为 0,表示动态分配了主设备号,此时返回分配的主设备号
    // 否则返回 0 表示成功
    return major ? 0 : cd->major;

out:
    // 如果字符设备注册失败,释放 kobject
    kobject_put(&cdev->kobj);

out2:
    // 释放之前分配的设备号资源
    kfree(__unregister_chrdev_region(cd->major, baseminor, count));
    
    // 返回错误码
    return err;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

img

1.3 混淆

1.register_chrdev – 字符设备

用于注册字符设备驱动的函数
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

major:表示要注册的字符设备的主设备号。如果主设备号为 0,则表示让内核自动分配主设备号。如果指定了一个非零的主设备号,则会尝试注册该主设备号,如果已经被占用,则注册失败。

name:是一个字符串,表示要注册的字符设备的名称。这个名称在 /proc/devices 中会出现,用户可以通过这个名称来识别设备。一般约定,这个名称与设备驱动相关的名字一致,方便用户和开发者识别。

fops:是一个指向文件操作结构体的指针,其中定义了设备文件的各种操作函数,如读、写、打开、关闭等。

返回值是分配到的major主设备号
/* 1. create file_operations */
static const struct file_operations hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
    .release    = hello_release,
};

major = register_chrdev(0, "100ask_hello", &hello_drv);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.class_create() – 设备类

这个函数用于创建一个设备类(device class)
struct class *class_create(struct module *owner, const char *name);
	owner:指向拥有此设备类的模块的指针。通常使用 THIS_MODULE 宏作为参数,表示当前模块。
	name:设备类的名称,以字符串形式传入。
    
static struct class *hello_class;
hello_class = class_create(THIS_MODULE, "hello_class");
if (IS_ERR(hello_class)) {
    printk("failed to allocate class\n");
    return PTR_ERR(hello_class);
}

这个函数用于创建一个设备类(device class)。
设备类是一组具有相似功能或属性的设备的集合,用于组织和管理这些设备。
在这里,通过 class_create 创建了一个名为 "hello_class" 的设备类,
并将其与当前模块关联(使用 THIS_MODULE)。如果成功创建了设备类,
则该函数返回一个指向 struct class 结构体的指针,否则返回一个错误指针。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.device_create() – 设备节点

用于在之前创建的设备类中创建一个设备节点,并将其加入到 /dev 文件系统中

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

	class:指向要创建设备节点的设备类的指针。
	parent:指向父设备的指针,通常传入 NULL。
	devt:是用于指定设备号的 dev_t 类型的参数,其中包含主设备号和次设备号。在传入参数时,通常使用 MKDEV(major, minor) 来组合主设备号和次设备号。
	drvdata:与设备相关的私有数据,通常传入 NULL。
	fmt:设备节点的名称格式字符串,用于生成设备节点的名称。
	可变参数:用于填充 fmt 字符串中的格式控制符。
    
//--------------------------------------------------------------------
static int major; //主设备号
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");  /* /dev/hello */
这行代码的作用是在 /dev 文件系统中创建一个名为 /dev/hello 的设备节点,其设备号由 MKDEV(major, 0) 生成,其中 major 是设备的主设备号,0 是设备的次设备号。
设备节点是用户空间程序与设备驱动之间通信的接口,它在文件系统中以文件的形式存在,允许用户通过文件操作来控制设备。
在这里,device_create 函数创建了一个名为 "hello" 的设备节点,并将其加入到 "hello_class" 设备类中。
其设备号由 MKDEV(major, 0) 生成,其中 major 是设备的主设备号,0 是设备的次设备号 
    ---共同组成设备号: major:0,这样可以分辨出该hello设备节点的字符设备是hello,是该字符设备下的第0个设备节点

如果成功创建了设备节点,则该函数返回一个指向 struct device 结构体的指针,否则返回一个错误指针。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

1、2、3函数的关系

  1. 字符设备(Character Device):

    • 字符设备是一种可以按字节流进行读写操作的设备,如串口、硬盘、键盘等。
    • 在 Linux 中,每个字符设备都有一个主设备号和一个或多个次设备号,主设备号用于区分不同类型的设备,次设备号用于区分同一类型设备中的不同实例或逻辑设备。
  2. 设备类(Device Class):

    • 设备类是一种用于组织和管理相似类型设备的概念。
    • 在 Linux 内核中,可以使用 class_create 函数创建一个设备类。设备类通常用于管理一组相关联的字符设备。
  3. 设备节点(Device Node):

    • 设备节点是用户空间访问字符设备的接口,它是在文件系统中表示设备的文件。通过这个文件,用户空间程序可以与设备进行交互。
    • 设备节点通常位于 /dev 目录下,其名称通常与设备类中的设备名称相关联。

这三者之间的关系是:

  • register_chrdev 用于注册一个字符设备驱动,并获取一个主设备号。
  • class_create 用于创建一个设备类,用于组织和管理一组相关的字符设备。
  • device_create 用于在 /dev 文件系统中创建一个设备节点,并将其加入到指定的设备类中。

在典型的使用中,首先使用 register_chrdev 注册字符设备驱动,获取一个主设备号。然后使用 class_create 创建一个设备类,并使用 device_create 在 /dev 文件系统中创建设备节点,并将其加入到设备类中。这样就可以通过设备节点来访问对应的字符设备,完成用户空间与内核空间的通信。

细节

  1. 设备节点是在字符设备之下的

设备节点可以看作是字符设备在用户空间的映射。在 Linux 中,字符设备被抽象为设备节点,用户通过设备节点来访问字符设备。设备节点通常位于
/dev 目录下,其名称通常与设备类中的设备名称相关联。

设备节点与字符设备之间的关系可以理解为:字符设备在内核中表示一个实际的设备,而设备节点则是用户空间访问该设备的接口。设备节点是字符设备在用户空间的映射,它提供了一个标准的文件接口,用户可以通过文件操作来读写设备。

因此,设备节点是字符设备的一种表现形式,是用户空间与字符设备之间的桥梁。

  1. 字符设备有主设备号,设备节点的设备号是由主设备号和次设备号组成的,用于分别设备节点是作用于哪个字符设备

设备节点的设备号通常由主设备号和次设备号组成,其中主设备号用于区分不同类型的设备,而次设备号用于区分同一类型设备中的不同实例或逻辑设备。这样,同一个字符设备类型可以有多个实例,每个实例都有一个唯一的设备号。

设备节点通常用于表示特定的字符设备实例。对于一个字符设备类型,可能会有多个设备节点,每个设备节点对应一个特定的字符设备实例。这些设备节点通过不同的次设备号来区分不同的实例,而主设备号则用于区分不同类型的设备。

例如,假设有一个字符设备类型为串口设备,其主设备号为 3。如果有两个串口设备,分别位于 /dev/ttyS0 和
/dev/ttyS1,那么这两个设备节点的设备号就是由主设备号 3 和不同的次设备号组成的。/dev/ttyS0 对应的设备号可能是 (3,
0),而 /dev/ttyS1 对应的设备号可能是 (3, 1)。这样,用户程序可以通过这两个设备节点分别访问这两个串口设备的不同实例。

  1. 设备类包含多个字符设备,而字符设备则对应着设备节点。
  • 设备类(Device Class):
    • 设备类是用于组织和管理相似类型设备的概念。通过 class_create 函数创建。
    • 一个设备类可以包含多个字符设备。
    • 设备类不直接包含设备节点,而是管理着一组相关联的字符设备。
  • 字符设备(Character Device): (硬件驱动程序)
    • 字符设备是一种可以按字节流进行读写操作的设备。
    • 每个字符设备都有一个主设备号和一个或多个次设备号。
    • 字符设备通常属于某个设备类,即它们被组织在一起,共同受某个设备类管理。
  • 设备节点(Device Node):
    • 设备节点是用户空间访问设备的接口,它是在文件系统中表示设备的文件。通过设备节点,用户空间程序可以与设备进行交互。
    • 设备节点通常位于 /dev 目录下,其名称通常与设备类中的设备名称相关联。

因此,设备类包含多个字符设备,而字符设备则对应着设备节点。设备节点是用户空间访问字符设备的接口,用户通过设备节点来与字符设备进行交互。

2. i2c-dev.c注册过程分析

那么内核提供的i2c-dev.c中注册I2C字符设备驱动,其实和register_chrdev内部实现类似,利用register_chrdev_region注册分配字符设备号(主、次设备号),遍历IIC控制器,调用i2cdev_attach_adapter来构造i2c_dev结构体(就和register_chrdev内部一样去构造cdev,当然i2c_dev结构体内部也有cddev),绑定对应的IIC控制器,同时利用class_create函创建设备类管理多个device_create函数创建的字符设备节点:

static int __init i2c_dev_init(void)
{
    int res;  // 用于存储函数调用的返回结果

    // 打印内核信息,通知已启动 I²C 字符设备驱动
    printk(KERN_INFO "i2c /dev entries driver\n");

    // 注册字符设备区域,指定主设备号和次设备号范围
    res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
    if (res)
        goto out;  // 如果注册失败,跳转到错误处理

    // 创建一个设备类,用于 sysfs 中设备的表示和设备节点的自动创建
    i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
    if (IS_ERR(i2c_dev_class)) {
        res = PTR_ERR(i2c_dev_class);  // 获取错误码
        goto out_unreg_chrdev;  // 跳转到字符设备取消注册
    }
    i2c_dev_class->dev_groups = i2c_groups;  // 设置设备类的属性组

    // 注册 I²C 总线的通知器,用于追踪总线上的设备添加和移除事件
    res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
    if (res)
        goto out_unreg_class;  // 如果通知器注册失败,跳转到类注销

    // 绑定当前系统中已存在的 I²C 适配器
    i2c_for_each_dev(NULL, i2cdev_attach_adapter); //---- (1)进入i2cdev_attach_adapter函数看下

    // 成功返回 0 表示初始化完成
    return 0;

out_unreg_class:
    // 如果类创建失败,销毁之前创建的类
    class_destroy(i2c_dev_class);
out_unreg_chrdev:
    // 如果字符设备区域注册失败,取消设备号注册
    unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
    // 打印错误信息,表明驱动初始化失败
    printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
    return res;  // 返回错误码
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

(1)其中i2cdev_attach_adapter函数:

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
    struct i2c_adapter *adap;
    struct i2c_dev *i2c_dev;
    int res;

    // 检查设备类型,确保它是 I2C 适配器设备
    if (dev->type != &i2c_adapter_type)
        return 0;

    // 将设备对象转换为 I2C 适配器对象
    adap = to_i2c_adapter(dev);

    // 获取一个可用的 i2c_dev 结构,该结构保存 I²C 字符设备的相关信息
    i2c_dev = get_free_i2c_dev(adap);
    if (IS_ERR(i2c_dev))
        return PTR_ERR(i2c_dev);  // 如果出错,返回错误码

    // 初始化字符设备结构体
    cdev_init(&i2c_dev->cdev, &i2cdev_fops);
    i2c_dev->cdev.owner = THIS_MODULE;

    // 添加字符设备到内核中,指定设备号(主设备号为 I2C_MAJOR,次设备号为 adap->nr)
    res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);
    if (res)
        goto error_cdev;  // 如果失败,跳转到错误处理

    // 注册设备,并在 `/dev` 中创建对应的设备节点(如 /dev/i2c-X)
    i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
                                 MKDEV(I2C_MAJOR, adap->nr), NULL,
                                 "i2c-%d", adap->nr);
    if (IS_ERR(i2c_dev->dev)) {
        res = PTR_ERR(i2c_dev->dev);  // 获取错误码
        goto error;  // 如果创建失败,跳转到错误处理
    }

    // 打印调试信息,指明适配器注册成功
    pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
             adap->name, adap->nr);
    return 0;

error:
    // 如果设备创建失败,删除字符设备
    cdev_del(&i2c_dev->cdev);
error_cdev:
    // 释放分配的 i2c_dev 结构体
    put_i2c_dev(i2c_dev);
    return res;  // 返回错误码
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

i2cdev_attach_adapter 函数的作用是为一个 I²C 适配器(adapter)注册字符设备节点,使用户可以通过 /dev/i2c-X 与适配器进行交互。该函数的主要工作是初始化字符设备并将其添加到内核中,然后通过 device_create 函数创建设备节点。

  • 检查设备类型是否为 I²C 适配器;
  • 初始化字符设备;
  • 为适配器创建字符设备节点;
  • 处理错误情况下的资源释放。

img

文章知识点与官方知识档案匹配,可进一步学习相关知识
C技能树首页概览220410 人正在系统学习中
注:本文转载自blog.csdn.net的憧憬一下的文章"https://blog.csdn.net/caiji0169/article/details/142962221"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top