前言

6. 总线-设备-驱动

总线-设备-驱动 又称为 设备驱动模型

6.1 概念

总线(bus):负责管理挂载对应总线的设备以及驱动;

设备(device):挂载在某个总线的物理设备;

驱动(driver):与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;

类(class):对于具有相同功能的设备,归结到一种类别,进行分类管理;

6.2 工作原理

以下只说 总线-设备-驱动 模式下的操作

总线

  • 总线管理着两个链表:设备链表驱动链表
  • 当我们向内核注册一个驱动时,便插入到总线的驱动链表
  • 当我们向内核注册一个设备时,便插入到总线的设备链表
  • 在插入的同时,总线会执行一个 bus_type 结构体中的 match 方法对新插入的 设备/驱动 进行匹配。(例如以名字的方式匹配。方式有很多总,下面再详细分析。
  • 匹配成功后,会调用 驱动 device_driver 结构体中的 probe 方法。(通常在 probe 中获取设备资源。具体有开发人员决定。
  • 在移除设备或驱动时,会调用 device_driver 结构体中的 remove 方法。

6.3 总线

6.3.1 总线介绍

总线

  • 总线是连接处理器和设备之间的桥梁
  • 代表着同类设备需要共同遵循的工作时序。

总线驱动

  • 负责实现总线行为,管理两个链表。

总线结构体

struct bus_type {
const char *name;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups; int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev); int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev); const struct dev_pm_ops *pm; struct subsys_private *p;
};
  • name:指定总线的名称,当新注册一种总线类型时,会在 /sys/bus 目录创建一个新的目录,目录名就是该参数的值;
  • bus_groups、dev_groups、drv_groups:分别表示 总线、设备、驱动的属性。
    • 通常会在对应的 /sys 目录下在以文件的形式存在,对于驱动而言,在目录 /sys/bus//driver/ 存放了驱动的默认属性;设备则在目录 /sys/bus//devices/ 中。这些文件一般是可读写的,用户可以通过读写操作来获取和设置这些 attribute 的值。
  • match:当向总线注册一个新的设备或者是新的驱动时,会调用该回调函数。该设备主要负责匹配工作。
  • uevent:总线上的设备发生添加、移除或者其它动作时,就会调用该函数,来通知驱动做出相应的对策。
  • probe:当总线将设备以及驱动相匹配之后,执行该回调函数,最终会调用驱动提供的probe 函数。
  • remove:当设备从总线移除时,调用该回调函数。
  • suspend、resume:电源管理的相关函数,当总线进入睡眠模式时,会调用suspend回调函数;而resume回调函数则是在唤醒总线的状态下执行。
  • pm:电源管理的结构体,存放了一系列跟总线电源管理有关的函数,与 device_driver 结构体中的 pm_ops 有关。
  • p:该结构体用于存放特定的私有数据,其成员 klist_devicesklist_drivers 记录了挂载在该总线的设备和驱动。

6.3.2 注册总线

在实际的驱动开发中,Linux 已经为我们编写好了大部分的总线驱动。

但是内核也提供了注册总线的 API。

bus_register()

  • bus_register() 函数用于注册总线。
  • 内核源码路径:内核源码/drivers/base/bus.c
  • 函数原型:int bus_register(struct bus_type *bus);
    • bus:bus_type 类型的结构体指针。
    • 返回值:
      • 成功:0;
      • 失败:负数。

bus_unregister()

  • bus_unregister() 用于注销总线。
  • 内核源码路径:内核源码/drivers/base/bus.c
  • 函数原型:int bus_unregister(struct bus_type *bus);
    • bus:bus_type 类型的结构体指针。
    • 返回值:无。

当我们成功注册总线时,会在 /sys/bus/ 目录下创建一个新目录,目录名为我们新注册的总线名。

6.4 设备

6.4.1 设备介绍

/sys/devices 目录记录了系统中所有的设备。

/sys 下的所有设备文件和 /sys/dev 下的所有设备节点都是链接文件,实际上都指向了对应的设备文件。

device 结构体:

struct device
{
const char *init_name;
struct device *parent;
struct bus_type *bus;
struct device_driver *driver;
void *platform_data;
void *driver_data;
struct device_node *of_node;
dev_t devt;
struct class *class;
void (*release)(struct device *dev);
const struct attribute_group **groups; /* optional groups */
struct device_private *p;
};
  • 内核源码路径:内核源码/include/linux/device.h
  • init_name:指定该设备的名称,总线匹配时,一般会根据比较名字来进行配对。
  • parent:表示该设备的父对象,旧版本的设备之间没有任何联系,引入 Linux 设备驱动模块后,设备之间呈现树状结构,便于管理各种设备。
  • bus:归属与哪个总线。当我们注册设备时,内核便会将该设备注册到对应的总线。(相爱
  • of_node:存放设备树中匹配的设备节点。当内核使能设备树,总线负责将驱动的 of_match_table 以及设备树的 compatible 属性进行比较之后,将匹配的节点保存到该变量。
  • platform_data:特定设备的私有数据,通常定义在板级文件中。
  • driver_data:驱动层可以通过 dev_set/get_drvdata 函数来获取该成员变量。
  • class:指向该设备对应类。
  • dev:设备号。dev_t 类型。
  • release:回调函数。当设备被注销时,该函数被调用。
  • group:指向 struct attribute_group 类型指针指定该设备属性。

6.4.2 设备注册、注销

在前面的字符设备驱动编写中,我们使用到了 device_create() 函数和 device_destroy() 函数来创建和删除设备。

现在介绍向总线注册和注销设备。

向总线注册设备

  • 函数原型:int device_register(struct device *dev);

    • 内核源码路径:内核源码/driver/base/core.c
    • devstruct device 结构体类型指针。
    • 返回:
      • 成功:0;
      • 失败:负数。

向总线注销设备

  • 函数原型:int device_unregister(struct device *dev);

    • 内核源码路径:内核源码/driver/base/core.c
    • devstruct device 结构体类型指针。
    • 返回:无。

6.5 驱动

6.5.1 驱动介绍

driver 结构体:


struct device_driver
{
const char *name;
struct bus_type *bus; struct module *owner;
const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table; int (*probe) (struct device *dev);
int (*remove) (struct device *dev); const struct attribute_group **groups;
struct driver_private *p;
};
  • 内核源码路径:内核源码/include/linux/device.h
  • name:指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较。
  • bus:归属与哪个总线。内核需要保证在驱动执行之前,对应的总线能够正常工作。
  • suppress_bind_attrs:布尔量,用于指定是否通过 sysfs 导出 bindunbind文件,bindunbind 文件是驱动用于绑定/解绑关联的设备。
  • owner:表示该驱动的拥有者,一般设置为 THIS_MODULE
  • of_match_table:指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的 compatible 属性进行比较。
  • remove:当设备从操作系统中拔出或者是系统重启时,会调用该回调函数。
  • probe:当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以main函数开始执行的,但是在内核的驱动代码,都是从 probe 函数开始的。
  • group:指向 struct attribute_group 类型的指针,指定该驱动的属性。

6.4.2 驱动注册、注销

向总线注册驱动

  • 函数原型:int driver_register(struct device_driver *drv);

    • 内核源码路径:内核源码/include/linux/device.h
    • drvstruct device_driver 结构体类型指针。
    • 返回:
      • 成功:0;
      • 失败:负数。

向总线注销驱动

  • 函数原型:int driver_unregister(struct device_driver *drv);

    • 内核源码路径:内核源码/include/linux/device.h
    • drvstruct device_driver 结构体类型指针。
    • 返回:无。

6.5 便解图文

数据结构该系统

注册流程图

  • 系统启动之后会调用buses_init函数创建/sys/bus文件目录,这部分系统在开机时已经帮我们准备好了, 接下去就是通过总线注册函数bus_register进行总线注册,注册完总线后在总线的目录下生成devices文件夹和drivers文件夹, 最后分别通过device_register以及driver_register函数注册相对应的设备和驱动。

6.6 attribute属性文件

6.6.1 attribute 介绍

attribute 结构体:

struct attribute {
const char *name;
umode_t mode;
};
  • 内核源码路径:内核源码/include/linux/sysfs.h
  • name:指定文件的文件名。
  • mode:指定文件的权限。

bus_type、device、device_driver 结构体中都包含了一种数据类型 struct attribute_group,它是多个 attribute 文件的集合, 利用它进行初始化,可以避免一个个注册 attribute

struct attribute_group 结构体:

struct attribute_group
{
const char *name;
umode_t (*is_visible)(struct kobject *, struct attribute *, int);
struct attribute **attrs;
struct bin_attribute **bin_attrs;
};
  • 内核源码路径:内核源码/include/linux/sysfs.h

6.6.2 设备属性文件

Linux 提供注册和注销设备属性文件的 API。我们可以通过这些 API 直接在用户层进行查询和修改。

struct device_attribute 结构体:

struct device_attribute
{
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
}; #define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
extern int device_create_file(struct device *device, const struct device_attribute *entry);
extern void device_remove_file(struct device *dev, const struct device_attribute *attr);
  • 内核源码路径:内核源码/include/linux/device.h
  • DEVICE_ATTR 宏:用于定义一个device_attribute类型的变量。
    • 参数 _name,_mode,_show,_store,分别代表了文件名, 文件权限,show 回调函数,store 回调函数。
    • show 回调函数以及 store 回调函数分别对应着用户层的 catecho 命令,当我们使用 cat 命令,来获取 /sys 目录下某个文件时,最终会执行 show 回调函数;使用 echo 命令,则会执行 store 回调函数。 参数 _mode 的值,可以使用S_IRUSR、S_IWUSR、S_IXUSR 等宏定义,更多选项可以查看读写文件章节关于文件权限的内容。
  • device_create_file:该函数用于创建文件。
    • device:表示设备。也是归属总线下的 device 目录下的目录名称。
    • entry:自定义的 device_attribute 类型变量。
  • device_remove_file:该函数用于删除文件。当注销驱动时,对应目录以及文件都被移除。
    • device:表示设备。也是归属总线下的 device 目录下的目录名称。
    • entry:自定义的 device_attribute 类型变量。

6.6.3 驱动属性文件

Linux 提供注册和注销驱动属性文件的 API。我们可以通过这些 API 直接在用户层进行查询和修改。

struct driver_attribute 结构体:

struct driver_attribute
{
struct attribute attr;
ssize_t (*show)(struct device_driver *driver, char *buf);
ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count);}; #define DRIVER_ATTR_RW(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_WO(_name) extern int __must_check driver_create_file(struct device_driver *driver, const struct driver_attribute *attr);
extern void driver_remove_file(struct device_driver *driver, const struct driver_attribute *attr);
  • 内核源码路径:内核源码/include/linux/device.h
  • DRIVER_ATTR_RW、DRIVER_ATTR_RO 以及 DRIVER_ATTR_WO 宏:用于定义一个driver_attribute类型的变量。
    • 带有 driver_attr_ 的前缀,区别在于文件权限不同;
    • RW 后缀表示文件可读写;
    • RO 后缀表示文件仅可读;
    • WO 后缀表示文件仅可写。
    • DRIVER_ATTR 类型的宏定义没有参数来设置 showstore 回调函数,在写驱动代码时,只需要提供 xxx_store 以及 xxx_show 这两个函数, 并确保两个函数的 xxxDRIVER_ATTR 类型的宏定义中名字是一致的即可。
  • driver_create_filedriver_remove_file 函数用于创建和移除文件,使用driver_create_file 函数, 会在 /sys/bus//drivers// 目录下创建文件。

6.6.4 总线属性文件

struct bus_attribute 结构体:

struct bus_attribute
{
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
}; #define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store) extern int __must_check bus_create_file(struct bus_type *, struct bus_attribute *);
extern void bus_remove_file(struct bus_type *, struct bus_attribute *);
  • 内核源码路径:内核源码/include/linux/device.h
  • BUS_ATTR 宏定义用于定义一个 bus_attribute 变量;
  • 使用 bus_create_file 函数,会在 /sys/bus/ 下创建对应的文件。
  • bus_remove_file 则用于移除该文件。

6.7 匹配规则 **

下章笔记就是记录平台设备。本次匹配规则就参考 平台设备驱动 源码。

6.7.1 最先比较

最先比较 platform_device.driver_overrideplatform_driver.driver.name

可以设置 platform_devicedriver_override,强制选择某个 platform_driver

6.7.2 其次比较

其次比较 platform_device.nameplatform_driver.id_table[i].name

platform_driver.id_tableplatform_device_id 指针,表示该 drv 支持若干个 device,它里面列出了各个 device{.name, .driver_data},其中的 name 表示该 drv 支持的设备的名字,driver_data是些提供给该 device 的私有数据。

6.7.3 最后比较

最后比较 platform_device.nameplatform_driver.driver.name

由于 platform_driver.id_table 可能为空,所以,接下来就可以使用 platform_driver.driver.name 来匹配。

6.7.4 函数调用关系

platform_device_register
platform_device_add
device_add
bus_add_device // 放入链表
bus_probe_device // probe 枚举设备,即找到匹配的(dev, drv)
device_initial_probe
__device_attach
bus_for_each_drv(...,__device_attach_driver,...)
__device_attach_driver
driver_match_device(drv, dev) // 是否匹配
driver_probe_device // 调用 drv 的 probe platform_driver_register
__platform_driver_register
driver_register
bus_add_driver // 放入链表
driver_attach(drv)
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
__driver_attach
driver_match_device(drv, dev) // 是否匹配
driver_probe_device // 调用 drv 的 probe

6.8 实现设备模型的简要步骤 *

总线、设备、驱动都基于驱动模型上实现,方便插入。

模块三步骤:

  1. 入口函数;
  2. 出口函数;
  3. 协议。

6.8.1 总线

  1. 实现总线结构体内容;
  2. 填充总线结构体;
  3. 实现属性文件内容,包括设置属性文件结构体;
  4. 注册总线;(模块入口函数
  5. 创建属性文件;
  6. 移除属性文件;(模块出口函数
  7. 注销总线。

6.8.2 设备

  1. 声明外部总线变量;
  2. 实现设备结构体内容;
  3. 填充设备结构体;
  4. 实现属性文件内容,包括设置属性文件结构体;
  5. 注册设备;(模块入口函数
  6. 创建属性文件;
  7. 移除属性文件;(模块出口函数
  8. 注销设备。

6.8.3 驱动

  1. 声明外部总线变量;
  2. 实现驱动结构体内容;
  3. 填充驱动结构体;
  4. 实现属性文件内容,包括设置属性文件结构体;
  5. 注册驱动;(模块入口函数
  6. 创建属性文件;
  7. 移除属性文件;(模块出口函数
  8. 注销驱动。

参考

【linux】驱动-6-总线-设备-驱动的更多相关文章

  1. Linux学习 : 总线-设备-驱动模型

    platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver.Linux 2.6的设备驱动模型中,把I2C.RTC.LCD等都归纳为pl ...

  2. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  3. 芯灵思SinlinxA33开发板 Linux平台总线设备驱动

    1.什么是platform(平台)总线? 相对于USB.PCI.I2C.SPI等物理总线来说,platform总线是一种虚拟.抽象出来的总线,实际中并不存在这样的总线. 那为什么需要platform总 ...

  4. Linux的总线设备驱动模型

    裸机编写驱动比较自由,按照手册实现其功能即可,每个人写出来都有很大不同: 而Linux中还需要按照Linux的驱动模型来编写,也就是需要按照"模板"来写,写出来的驱动就比较统一. ...

  5. usb驱动开发4之总线设备驱动模型

    在上文说usb_init函数,却给我们留下了很多岔路口.这次就来好好聊聊关于总线设备驱动模型.这节只讲理论,不讲其中的函数方法,关于函数方法使用参考其他资料. 总线.设备.驱动对应内核结构体分别为bu ...

  6. i2c总线驱动,总线设备(适配器),从设备,从设备驱动的注册以及匹配

    常用链接 我的随笔 我的评论 我的参与 最新评论 我的标签 随笔分类 ARM裸机(13) C(8) C++(8) GNU-ARM汇编 Linux驱动(24) Linux应用编程(5) Makefile ...

  7. 《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

    USB 的全称是 Universal Serial Bus,顾名思义:通用串行总线. 提到总线,联想一下,在你心目中总线总是用来干嘛的?还记得 I2C 总线? I2C 总线上挂有二条信号线,一条是 S ...

  8. Linux 设备驱动开发 —— platform设备驱动应用实例解析

    前面我们已经学习了platform设备的理论知识Linux 设备驱动开发 —— platform 设备驱动 ,下面将通过一个实例来深入我们的学习. 一.platform 驱动的工作过程 platfor ...

  9. Linux与Windows的设备驱动模型对比

    Linux与Windows的设备驱动模型对比 名词缩写: API 应用程序接口(Application Program Interface ) ABI 应用系统二进制接口(Application Bi ...

随机推荐

  1. Creative Commons : CC (知识共享署名 授权许可)

    1 https://creativecommons.org/      Keep the internet creative, free and open. Creative Commons help ...

  2. React Security Best Practices All In One

    React Security Best Practices All In One Default XSS Protection with Data Binding Dangerous URLs Ren ...

  3. Object 循环引用 All In One

    Object 循环引用 All In One circular reference bug var a = {}; a.a = a; refs deep copy bug https://segmen ...

  4. js type automatic conversion

    js type automatic conversion String & Number `255` < 16; false `15` < 16; true `25` < 1 ...

  5. css & box-shadow & outline

    css & box-shadow & outline CSS3 box-shadow : 4 sides symmetry https://learning.xgqfrms.xyz/C ...

  6. CNN专访灵石CTO:Baccarat流动性挖矿能否持续?

    近日,CNN记者Robert独家专访Baccarat的项目团队CTO STEPHEN LITAN,跟他特别聊了聊DeFi的近况. 以下是专访全文: Robert:推出Baccarat的契机是什么? S ...

  7. 「NGK每日快讯」12.3日NGK公链第30期官方快讯!

  8. OAuth:每次授权暗中保护你的那个“MAN”

    摘要:OAuth是一种授权协议,允许用户在不将账号口令泄露给第三方应用的前提下,使第三方应用可以获得用户在某个web服务上存放资源的访问权限. 背景 在传统模式下,用户的客户端在访问某个web服务提供 ...

  9. 对Innodb中MVCC的理解

    一.什么是MVCC MVCC (Multiversion Concurrency Control) 中文全程叫多版本并发控制,是现代数据库(如MySql)引擎实现中常用的处理读写冲突的手段,目的在于提 ...

  10. redis源码之dict

    大家都知道redis默认是16个db,但是这些db底层的设计结构是什么样的呢? 我们来简单的看一下源码,重要的字段都有所注释 typedef struct redisDb { dict *dict; ...