linux 中bus驱动解析

总线(bus)是linux发展过程中抽象出来的一种设备模型,为了统一管理所有的设备,内核中每个设备都会被挂载在总线上,这个bus可以是对应硬件的bus(i2c bus、spi bus)、可以是虚拟bus(platform bus)。

简述bus的工作流程

bus将所有挂在上面的具体设备抽象成两部分,driver和device。

driver实现了同类型设备的驱动程序实现,而device则向系统注册具体的设备需要的资源,每当添加一个新的driver(device)到bus中时,都将调用bus的match函数,试图寻找匹配的device(driver)。

总线大概是这样的:

如果匹配成功,就调用probe函数,在probe函数中实现设备的初始化、各种配置以及生成用户空间的文件接口。

举个例子,针对AT24CXX(一种常用的存储设备)这种同系列产品,他们的操作方式都是非常相似的,不同的无非是容量大小。

那么我们就没有必要为AT24C01、AT24C02去分别写一份驱动程序,而是统一为其写一份兼容所有AT24CXX的驱动程序,然后再传入不同的参数以对应具体的型号。

在linux驱动管理模型中的体现就是:驱动程序对应driver、需要的具体型号的硬件资源对应device,将其挂在bus上。

将driver注册到bus上,当用户需要使用AT24C01时,以AT24C01的参数构建一个对应device,注册到bus中,bus的match函数匹配上之后,调用probe函数,即可完成AT24C01的初始化,完成在用户空间的文件接口注册。

以此类推,添加所有的AT24CXX设备都可以以这样的形式实现,只需要构建一份device,而不用为每个设备重写一份驱动,提高了复用性,节省了内存空间。

linux bus结构体

linux将设备挂在总线上,对应设备的注册和匹配流程由总线进行管理,在linux内核中,每一个bus,都由struct bus_type来描述:

struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
...
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);
void (*shutdown)(struct device *dev);
struct subsys_private *p;
...

};

为了突出重点,省去了一些暂时不需要深入了解的成员,我们来看看其中最主要的几个成员:

name : 该bus的名字,这个名字是这个bus在sysfs文件系统中的体现,对应/sys/bus/$name.

dev_name : 这个dev_name并不对应bus的名称,而是对应bus所包含的struct device的名字,即对应dev_root。

dev_root:bus对应的device结构,每个设备都需要对应一个相应的struct device.

match:bus的device链表和driver链表进行匹配的实际执行回调函数,每当有device或者driver添加到bus中时,调用match函数,为device(driver)寻找匹配的driver(device)。

uevent:bus时间回调函数,当属于这个bus的设备发生添加、删除、修改等行为时,都将出发uvent事件。

probe:当device和driver经由match匹配成功时,将会调用总线的probe函数实现具体driver的初始化。事实上每个driver也会提供相应的probe函数,先调用总线的probe函数,在总线probe函数中调用driver的probe函数。

remove:移除挂载在设备上的driver,bus上的driver部分也会提供remove函数,在执行移除时,先调用driver的remove,然后再调用bus的remove以清除资源。

struct subsys_private *p:见下文

struct subsys_private *p

struct subsys_private *p主要实现了对bus中数据的管理:

struct subsys_private {
struct kset subsys;
struct kset *devices_kset; struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
...
};

其中struct kset subsys、struct kset *devices_kset、struct kset *drivers_kset都是在sysfs文件系统中创建对应的目录。

struct klist klist_devices、struct klist klist_drivers是两个主要的数据部分,klist_devices是存储所有注册到bus的device的链表,而klist_drivers是存储所有注册到bus的driver的链表。

bus的注册

了解了bus的结构,那么,bus是怎么注册的呢?

spi bus的注册过程在KERNEL/drivers/spi/spi.c中:

static int __init spi_init(void)
{
...
status = bus_register(&spi_bus_type);
...
return 0;
} postcore_initcall(spi_init);

i2c bus的注册过程在KERNEL/drivers/i2c/i2c-core-base.c中:

static int __init i2c_init(void)
{
...
bus_register(&i2c_bus_type);
...
}
postcore_initcall(i2c_init);

而platform bus的注册过程在KERNEL/drivers/base/platform.c中:

int __init platform_bus_init(void)
{
...
bus_register(&platform_bus_type);
...
}

i2c和spi为物理总线,这两种总线通过postcore_initcall()将各自的init函数注册到系统中,postcore_initcall的详解可以参考另一篇博客:linux init机制

而platform作为虚拟总线,platform_bus_init被系统初始化时直接调用,调用流程为:

start_kernel
-> rest_init();
-> kernel_thread(kernel_init, NULL, CLONE_FS);
-> kernel_init()
-> kernel_init_freeable();
-> do_basic_setup();
-> driver_init();
->platform_bus_init();

spi_bus_type、i2c_bus_type、platform_bus_type分别为对应的struct bus_type描述结构体。

对应的spi_bus_type、i2c_bus_type、platform_bus_type实现我将分别在spi、i2c、platform具体框架解析中介绍。

bus_register()

可以看到,这三种总线都是由bus_register()接口注册的,那么这个接口到底做了什么呢?

int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct lock_class_key *key = &bus->lock_key; priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM; priv->bus = bus;
bus->p = priv; BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out; priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1; retval = kset_register(&priv->subsys);
if (retval)
goto out; retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail; priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
} priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
} INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL); retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail; retval = bus_add_groups(bus, bus->bus_groups);
if (retval)
goto bus_groups_fail; pr_debug("bus: '%s': registered\n", bus->name);
return 0; }

在上面贴出的代码中,可以看出,bus_register()其实也没做什么特别的事,主要是两个:

  • 在sysfs系统中注册各种用户文件接口,将bus的信息和操作接口导出到用户接口。
  • 初始化device和driver链表。

向总线中添加driver/device

既然bus_register只是初始化了相应的资源,在/sys下导出接口文件,那整个bus是如何工作的呢?

以i2c为例,我们来看看这整个过程:

首先使用i2c_new_device接口来添加一个device:

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
client = kzalloc(sizeof *client, GFP_KERNEL);
client->adapter = adap; client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr; client->irq = info->irq;
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
...
device_register(&client->dev);
...
}

申请一个i2c_client并对其赋值,然后以这个为参数调用device_register(&client->dev),将dangqiandevice添加到bus中。

int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
} int device_add(struct device *dev)
{
...
bus_add_device(dev);
...
}
int bus_add_device(struct device *dev){
...
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
...
bus_probe_device(dev);
...
}

在device_register中,调用了device_add,紧接着调用bus_add_device,根据函数名称可以看出是将这个device添加到对应的bus中。

果然,根据bus_add_device()的源代码,可以看到,将当前device链接到其对应bus的devices链表,然后在下面调用bus_probe_device();这个函数的作用就是轮询对应bus的drivers链接,查看新添加的device是否存在匹配的driver。

对应的,i2c_driver_register()将i2c driver部分添加到bus中,再轮询检查bus的devices链表是否有对应的device能匹配上,有兴趣的可以从i2c_driver_register()开始研究源代码。

device和driver的匹配

上文中提到当bus中有新的device和driver添加时,会调用bus的match函数进行匹配,那么到底是怎么匹配的呢?

简单来说,在静态定义的device中,一般会有.name属性,与driver的.id_table属性相匹配。

device部分还有可能从设备树转换而来,就有设备树中相应的.compatible属性和driver的of_match_table.compatible属性相匹配。

事实上对于匹配这一部分,可以直接参考每个bus的match函数实现。

这一章节只是对linux中的总线做一个概念性的说明,在之后的博客中会详细介绍到相应bus的框架,同时也会详解对应的match()函数实现。

敬请期待!

好了,关于linux的bus讨论就到此为止啦,如果朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言

原创博客,转载请注明出处!

祝各位早日实现项目丛中过,bug不沾身.

linux设备驱动程序--bus的更多相关文章

  1. linux设备驱动程序-i2c(1):i2c总线的添加与实现

    linux设备驱动程序-i2c(1):i2c总线的添加与实现 (基于4.14内核版本) 在上一章节linux设备驱动程序-i2c(0)-i2c设备驱动源码实现中,我们演示了i2c设备驱动程序的源码实现 ...

  2. linux设备驱动程序-设备树(3)-设备树多级子节点的转换

    linux设备驱动程序--设备树多级子节点的转换 在上一章:设备树处理之--device_node转换成platform_device中,有提到在设备树的device_node到platform_de ...

  3. linux设备驱动程序-i2c(2)-adapter和设备树的解析

    linux设备驱动程序-i2c(2)-adapter和设备树的解析 (注: 基于beagle bone green开发板,linux4.14内核版本) 在本系列linux内核i2c框架的前两篇,分别讲 ...

  4. 嵌入式Linux设备驱动程序:在运行时读取驱动程序状态

    嵌入式Linux设备驱动程序:在运行时读取驱动程序状态 Embedded Linux device drivers: Reading driver state at runtime 在运行时了解驱动程 ...

  5. 嵌入式Linux设备驱动程序:用户空间中的设备驱动程序

    嵌入式Linux设备驱动程序:用户空间中的设备驱动程序 Embedded Linux device drivers: Device drivers in user space Interfacing ...

  6. linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)

    原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是 ...

  7. 【转】linux设备驱动程序中的阻塞机制

    原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275272.html 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经 ...

  8. Linux设备驱动程序 第三版 读书笔记(一)

    Linux设备驱动程序 第三版 读书笔记(一) Bob Zhang 2017.08.25 编写基本的Hello World模块 #include <linux/init.h> #inclu ...

  9. Linux设备驱动程序学习之分配内存

    内核为设备驱动提供了一个统一的内存管理接口,所以模块无需涉及分段和分页等问题. 我已经在第一个scull模块中使用了 kmalloc 和 kfree 来分配和释放内存空间. kmalloc 函数内幕 ...

随机推荐

  1. 讲课专用——线段树——BSS

    题目链接:http://codevs.cn/problem/3981/ 题解: 线段树求GSS模板题 一.一段长的区间的 GSS 有三种情况:>1 完全在左子区间>2 完全在右子区间> ...

  2. 余弦相似度-Cosine Similar(转载)

    余弦相似度用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小.相比距离度量,余弦相似度更加注重两个向量在方向上的差异,而非距离或长度上. 与欧几里德距离类似,基于余弦相似度的计算方法也是把用 ...

  3. 小程序报错 “渲染层错误” Expect END descriptor with depth 0 but get another

    项目中有几个页面在控制台出现这个“渲染层错误”,虽然不影响业务操作,怕存在潜在风险,今天抽时间找了下原因,解决这个问题. 控制台报错日志如下: (中国标准时间) 渲染层错误 Error: Expect ...

  4. Hbase操作集锦

    一.Hbase架构与理解 Hbase架构大致如下,图片取自https://www.cnblogs.com/linkworld/p/10963910.html. 二.Hbase shell操作 创建Hb ...

  5. Effective.Java第12-22条

    12.  始终重写toString()方法 如果不重写toString()方法,打印的时候是 类名+@+哈希码的无符号十六进制.我们查看 Object的toString()方法如下: public S ...

  6. Webstorm关闭ESLint警告

    使用Webstorm新创建的VUE项目在npm run dev的时候老是报各种格式问题i:比如多个一个空格?多了一行?分号问题?.....简直是受不鸟... 是因为你使用了eslint,这个是esli ...

  7. Java | Spring Boot Swagger2 集成REST ful API 生成接口文档

      Spring Boot Swagger2 集成REST ful API 生成接口文档 原文 简介 由于Spring Boot 的特性,用来开发 REST ful 变得非常容易,并且结合 Swagg ...

  8. 8. Scala面向对象编程(高级部分)

    8.1 静态属性和静态方法 8.1.1 静态属性-提出问题 有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?请使用面向对象的思想,编写程序解决 8.1.2 基本介绍 -Sca ...

  9. vue mint-ui 框架下拉刷新上拉加载组件的使用

    安装 npm i mint-ui -S 然后在main.js中引入 import MintUI from 'mint-ui' import 'mint-ui/lib/style.css' Vue.us ...

  10. Java 8——Lambda表达式

    本篇文章不介绍Lambda的前世今生,这里只对Lambda表达的应用做详细了解.以及与内部类的差异点. Lambda表达式 Lambda表达式是Java SE 8版本中引入的新的语法糖.将功能看做方法 ...