作者:wowo 发布于:2014-4-2 19:28 分类:统一设备模型

http://www.wowotech.net/device_model/device_and_driver.html

前言

device和device driver是Linux驱动开发的基本概念。

Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备,所以kernel就为设备和驱动它的driver定义了两个数据结构,分别是device和device_driver。

因此本文将会围绕这两个数据结构,介绍Linux设备模型的核心逻辑,包括:

  • 设备及设备驱动在kernel中的抽象、使用和维护;
  • 设备及设备驱动的注册、加载、初始化原理;
  • 设备模型在实际驱动开发过程中的使用方法。

注:在介绍device和device_driver的过程中,会遇到很多额外的知识点,如Class、Bus、DMA、电源管理等等,这些知识点都很复杂,任何一个都可以作为一个单独的专题区阐述,因此本文不会深入解析它们,而会在后续的文章中专门描述。

核心数据结构

在阅读Linux内核源代码时,通过核心数据结构,即可理解某个模块60%以上的逻辑,设备模型部分尤为明显。

在include/linux/device.h中,Linux内核定义了设备模型中最重要的两个数据结构,struct device和struct device_driver。

struct device

/* include/linux/device.h, line 660 */
struct device {
struct device *parent; struct device_private *p; struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type; struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/ struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain; #ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif #ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */ struct device_dma_parameters *dma_parms; struct list_head dma_pools; /* dma pools (if dma'ble) */ struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata; struct device_node *of_node; /* associated device tree node */
struct acpi_dev_node acpi_node; /* associated ACPI device node */ dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */ spinlock_t devres_lock;
struct list_head devres_head; struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */ void (*release)(struct device *dev);
struct iommu_group *iommu_group;
};

device结构很复杂(不过linux内核的开发人员素质是很高的,该接口的注释写的非常详细,感兴趣的同学可以参考内核源代码),这里将会选一些对理解设备模型非常关键的字段进行说明。

  • parent,该设备的父设备,一般是该设备所从属的bus、controller等设备。

  • p,一个用于struct device的私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/prent等设备中的链表头等等,具体可查看源代码。

  • kobj,该数据结构对应的struct kobject。

  • init_name,该设备的名称。

注1:在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据“bus name + device ID”的方式创造。

  • type,struct device_type结构是新版本内核新引入的一个结构,它和struct device关系,非常类似stuct kobj_type和struct kobject之间的关系,后续会再详细说明。

  • bus,该device属于哪个总线(后续会详细描述)。

  • driver,该device对应的device driver。

  • platform_data,一个指针,用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存在这里,需要使用的时候,再拿出来,因此设备模型并不关心该指针得实际含义。

  • power、pm_domain,电源管理相关的逻辑,后续会由电源管理专题讲解。

  • pins,"PINCTRL”功能,暂不描述。

  • numa_node,"NUMA”功能,暂不描述。

  • dma_mask~archdata,DMA相关的功能,暂不描述。

  • devt,dev_t是一个32位的整数,它由两个部分(Major和Minor)组成,在需要以设备节点的形式(字符设备和块设备)向用户空间提供接口的设备中,当作设备号使用。在这里,该变量主要用于在sys文件系统中,为每个具有设备号的device,创建/sys/dev/* 下的对应目录,如下:

root@android:/storage/sdcard0 #ls /sys/dev/char/1\:
1/ 1:11/ 1:13/ 1:14/ 1:2/ 1:3/ 1:5/ 1:7/ 1:8/ 1:9/ 1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:1
1/ 1:11/ 1:13/ 1:14/ root@android:/storage/sdcard0 # ls /sys/dev/char/1\:1
/sys/dev/char/1:1
  • class,该设备属于哪个class。

  • groups,该设备的默认attribute集合。将会在设备注册时自动在sysfs中创建对应的文件。

struct device_driver

/* include/linux/device.h, line 213 */
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);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups; const struct dev_pm_ops *pm; struct driver_private *p;
};

device_driver就简单多了(在早期的内核版本中driver的数据结构为"struct driver”,不知道从哪个版本开始,就改成device_driver了):

  • name,该driver的名称。和device结构一样,该名称非常重要,后面会再详细说明。

  • bus,该driver所驱动设备的总线设备。为什么driver需要记录总线设备的指针呢?因为内核要保证在driver运行前,设备所依赖的总线能够正确初始化。

  • owner、mod_name,內核module相关的变量,暂不描述。

  • suppress_bind_attrs,是不在sysfs中启用bind和unbind attribute,如下:

root@android:/storage/sdcard0 # ls /sys/bus/platform/drivers/switch-gpio/
bind uevent unbind

在kernel中,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制。这种机制是在bus.c中完成的,后面会详细解释。

  • probe、remove,这两个接口函数用于实现driver逻辑的开始和结束。Driver是一段软件code,因此会有开始和结束两个代码逻辑,就像PC程序,会有一个main函数,main函数的开始就是开始,return的地方就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备(就是为热拔插起的!)。

  • shutdown、suspend、resume、pm,电源管理相关的内容,会在电源管理专题中详细说明。

  • groups,和struct device结构中的同名变量类似,driver也可以定义一些默认attribute,这样在将driver注册到内核中时,内核设备模型部分的代码(driver/base/driver.c)会自动将这些attribute添加到sysfs中。

  • p,driver core的私有数据指针,其它模块不能访问。

设备模型框架下驱动开发的基本步骤

在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:

步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。

步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。

这两步完成后,内核会在合适的时机(后面会讲),调用struct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。而所有的驱动程序逻辑,都会由这些回调函数实现,此时,驱动开发者眼中便不再有“设备模型”,转而只关心驱动本身的实现。

以上两个步骤的补充说明:

  1. 一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用(也正是因为这个原因,本文并不会过多涉及device、device_driver等模块的实现细节)。

  2. 内核提供很多struct device结构的操作接口(具体可以参考include/linux/device.h和drivers/base/core.c的代码),主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。

  3. device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。

  4. device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。

  5. driver开发者可以在struct device变量中,保存描述设备特征的信息,如寻址空间、依赖的GPIOs等,因为device指针会在执行probe等接口时传入,这时driver就可以根据这些信息,执行相应的逻辑操作了。

设备驱动probe的时机

所谓的"probe”,是指在Linux内核中,如果存在相同名称的device和device_driver(注:还存在其它方式,我们先不关注了),内核就会执行device_driver中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作("remove”是它的反操作,发生在device或者device_driver任何一方从内核注销时,其原理类似,就不再单独说明了)。

设备驱动prove的时机有如下几种(分为自动触发和手动触发):

  • 将struct device类型的变量注册到内核中时自动触发(device_register,device_add,device_create_vargs,device_create)
  • 将struct device_driver类型的变量注册到内核中时自动触发(driver_register)
  • 手动查找同一bus下的所有device_driver,如果有和指定device同名的driver,执行probe操作(device_attach)
  • 手动查找同一bus下的所有device,如果有和指定driver同名的device,执行probe操作(driver_attach)
  • 自行调用driver的probe接口,并在该接口中将该driver绑定到某个device结构中----即设置dev->driver(device_bind_driver)

注2:probe动作实际是由bus模块(会在下一篇文章讲解)实现的,这不难理解:device和device_driver都是挂载在bus这根线上,因此只有bus最清楚应该为哪些device、哪些driver配对。

注3:每个bus都有一个drivers_autoprobe变量,用于控制是否在device或者driver注册时,自动probe。该变量默认为1(即自动probe),bus模块将它开放到sysfs中了,因而可在用户空间修改,进而控制probe行为。

其它杂项

device_attribute和driver_attribute

在"Linux设备模型(4)_sysfs”中,我们有讲到,大多数时候,attribute文件的读写数据流为:vfs---->sysfs---->kobject---->attibute---->kobj_type---->sysfs_ops---->xxx_attribute,其中kobj_type、sysfs_ops和xxx_attribute都是由包含kobject的上层数据结构实现。

Linux内核中关于该内容的例证到处都是,device也不无例外的提供了这种例子,如下:

/* driver/base/core.c, line 118 */
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct device_attribute *dev_attr = to_dev_attr(attr);
struct device *dev = kobj_to_dev(kobj);
ssize_t ret = -EIO; if (dev_attr->show)
ret = dev_attr->show(dev, dev_attr, buf);
if (ret >= (ssize_t)PAGE_SIZE) {
print_symbol("dev_attr_show: %s returned bad count\n",
(unsigned long)dev_attr->show);
}
return ret;
} static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct device_attribute *dev_attr = to_dev_attr(attr);
struct device *dev = kobj_to_dev(kobj);
ssize_t ret = -EIO; if (dev_attr->store)
ret = dev_attr->store(dev, dev_attr, buf, count);
return ret;
} static const struct sysfs_ops dev_sysfs_ops = {
.show = dev_attr_show,
.store = dev_attr_store,
}; /* driver/base/core.c, line 243 */
static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
.namespace = device_namespace,
}; /* include/linux/device.h, line 478 */
/* interface for exporting device attributes */
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);
};

至于driver的attribute,则要简单的多,其数据流为:vfs---->sysfs---->kobject---->attribute---->driver_attribute,如下:

/* include/linux/device.h, line 247 */
/* sysfs interface for exporting driver attributes */ 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(_name, _mode, _show, _store) \
struct driver_attribute driver_attr_##_name = \
__ATTR(_name, _mode, _show, _store)

device_type

device_type是内嵌在struct device结构中的一个数据结构,用于指明设备的类型,并提供一些额外的辅助功能。它的的形式如下:

/* include/linux/device.h, line 467 */
struct device_type {
const char *name;
const struct attribute_group **groups;
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode,
kuid_t *uid, kgid_t *gid);
void (*release)(struct device *dev); const struct dev_pm_ops *pm;
};

device_type的功能包括:

  •   name表示该类型的名称,当该类型的设备添加到内核时,内核会发出"DEVTYPE=‘name’”类型的uevent,告知用户空间某个类型的设备available了
  •   groups,该类型设备的公共attribute集合。设备注册时,会同时注册这些attribute。这就是面向对象中“继承”的概念
  •   uevent,同理,所有相同类型的设备,会有一些共有的uevent需要发送,由该接口实现
  •   devnode,devtmpfs有关的内容,暂不说明
  •   release,如果device结构没有提供release接口,就要查询它所属的type是否提供。用于释放device变量所占的空间

root device

在sysfs中有这样一个目录:/sys/devices,系统中所有的设备,都归集在该目录下。有些设备,是通过device_register注册到Kernel并体现在/sys/devices/xxx/下。但有时候我们仅仅需要在/sys/devices/下注册一个目录,该目录不代表任何的实体设备,这时可以使用下面的接口:

/* include/linux/device.h, line 859 */
/*
* Root device objects for grouping under /sys/devices
*/
extern struct device *__root_device_register(const char *name,
struct module *owner); /*
* This is a macro to avoid include problems with THIS_MODULE,
* just as per what is done for device_schedule_callback() above.
*/
#define root_device_register(name) \
__root_device_register(name, THIS_MODULE) extern void root_device_unregister(struct device *root);

该接口会调用device_register函数,向内核中注册一个设备,但是(你也想到了),没必要注册与之对应的driver(顺便提一下,内核中有很多不需要driver的设备,这是之一)。

Linux设备模型:5、device和device driver的更多相关文章

  1. Linux设备模型(9)_device resource management ---devm申请空间【转】

    转自:http://www.wowotech.net/linux_kenrel/device_resource_management.html . 前言 蜗蜗建议,每一个Linux驱动工程师,都能瞄一 ...

  2. Linux设备模型(总线、设备、驱动程序和类)

    Linux设备驱动程序学习(13) -Linux设备模型(总线.设备.驱动程序和类)[转] 文章的例子和实验使用<LDD3>所配的lddbus模块(稍作修改). 提示:在学习这部分内容是一 ...

  3. Linux设备模型 学习总结

    看LDD3中设备模型一章,觉得思维有些混乱.这里从整体的角度来理理思路.本文从四个方面来总结一些内容: 1.底层数据结构:kobject,kset.2.linux设备模型层次关系:bus_type,d ...

  4. Linux设备模型——设备驱动模型和sysfs文件系统解读

    本文将对Linux系统中的sysfs进行简单的分析,要分析sysfs就必须分析内核的driver-model(驱动模型),两者是紧密联系的.在分析过程中,本文将以platform总线和spi主控制器的 ...

  5. Linux 设备模型浅析之 uevent 篇(2)

    Linux 设备模型浅析之 uevent 篇 本文属本人原创,欢迎转载,转载请注明出处.由于个人的见识和能力有限,不可能面 面俱到,也可能存在谬误,敬请网友指出,本人的邮箱是 yzq.seen@gma ...

  6. linux设备模型:扩展篇

    Linux设备模型组件:总线  一.定义:总线是不同IC器件之间相互通讯的通道;在计算机中,一个总线就是处理器与一个或多个不同外设之间的通讯通道;为了设备模型的目的,所有的设备都通过总线相互连接,甚至 ...

  7. Linux设备模型:基础篇

    linux提供了新的设备模型:总线(bus).设备(device).驱动(driver).其中总线是处理器与设备之间通道,在设备模型中,所有的设备都通过总线相连:设备是对于一个设备的详细信息描述,驱动 ...

  8. Linux设备模型(总结)

    转:http://www.360doc.com/content/11/1219/16/1299815_173418267.shtml 看了一段时间的驱动编程,从LDD3的hello wrod到后来的字 ...

  9. Linux设备模型(热插拔、mdev 与 firmware)【转】

    转自:http://www.cnblogs.com/hnrainll/archive/2011/06/10/2077469.html 转自:http://blog.chinaunix.net/spac ...

  10. linux设备模型与内核中的面向对象思想

    linux内核用C语言实现了C++面向对象的大部分特性:封装,继承,多态.在看内核的过程中,开始追寻其中的设计思想,封装.继承.多态.恰好今天又在看Linux设备模型,找了很多资料.总结如下: 1.l ...

随机推荐

  1. LVS负载均衡(3)-- DR模型搭建实例

    目录 1. LVS DR模型搭建 1.1 DR模型网络规划 1.2 RS设备的VIP冲突解决方式 1.3 DR模型访问流程 1.4 DR模型配置 1.4.1 ROUTER设备配置 1.4.2 后端ng ...

  2. uni-app 安卓和IOS更新方案

    一.安卓平台 整包更新(apk),即常规的整个App安装包重新下载安装. 资源热更新(wgt),即App并重新安装,里面的js等前端代码进行更新. Android App,可以直接下载新的apk,只要 ...

  3. HJ212-2017协议数据采集和接收

    1.客户端TCP和UDP的数据发送工具 工具和软件下载页面如下:http://www.zlmcu.com/document/tcp_debug_tools.html 2.服务端数据接收监控软件 IPA ...

  4. python openstacksdk

    调用方法 参考地址 https://github.com/openstack/openstacksdk 注意事项 1.需要安装openstacksdk.我这里装的好像是1.5版本的.opentask接 ...

  5. vulnhub靶场 --> Red: 1

    靶机下载地址 Red: 1 << 点我 开始打靶 IP发现 nmap扫描网段发现靶机ip:192.168.111.142 端口发现 对靶机进行常规端口扫描 访问网站 到处点击发现存在一个可 ...

  6. centos 7网卡配置文件详解(ifcfg-ens33)

    centos 7网卡配置文件详解(ifcfg-ens33) [root@xuegod63 ~]# cat /etc/sysconfig/network-scripts/ifcfg-ens33 TYPE ...

  7. IceRPC之调度管道->快乐的RPC

    作者引言 很高兴啊,我们来到了IceRPC之调度管道->快乐的RPC, 基础引导,有点小压力,打好基础,才能让自已不在迷茫,快乐的畅游世界. 调度管道 Dispatch pipeline 了解如 ...

  8. Python爬图片

    1 import requests 2 from lxml import etree 3 4 header = { 5 "user-agent": "Mozilla/5. ...

  9. CSS操作——背景属性

    1.background-color(背景颜色) 页面的背景颜色有四种属性值表示,分别是transparent(透明),RGB十进制颜色表示,十六进制颜色表示和颜色单词表示. 属性使用: /* bac ...

  10. 必应每日一图url(可直接使用)

    必应每日一图url 首先放出地址,后面是一堆心路历程(一堆废话),只为链接的可以不用看 https://baotangguo.cn:8081/ 最初 ​ 博客园装饰的时候(虽然是抄的),想着上面背景图 ...