Linux设备模型剖析系列之三(device和device driver)

优快云链接:

Linux设备模型剖析系列一(基本概念、kobject、kset、kobj_type)

Linux设备模型剖析系列之二(uevent、sysfs)

Linux设备模型剖析系列之三(device和device driver)

Linux设备模型剖析系列之四(BUS)

Linux设备模型剖析系列之五(class)

Linux设备模型系列文章之六(设备资源管理)

Linux设备模型剖析系列文章之七(kobj、kset)

五、device和device driver

1. 前言

  • device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要为指定的设备(device)开发指定的软件(device_driver),所以kernel就为设备和驱动定义了两个数据结构,分别是device和device_driver。下文将会围绕这两个数据结构,介绍Linux设备模型的核心逻辑,包括:
    • 设备及设备驱动在kernel中的抽象、使用和维护;
    • 设备及设备驱动的注册、加载、初始化原理;
    • 设备模型在实际驱动开发过程中的使用方法。

2. struct device和struct device_driver

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

2.1 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) */
    
    /* 
     * Like dma_mask, but for alloc_coherent mappings as  
     * not all hardware supports 64 bit addresses for consistent
     * allocations such descriptors. 
     */
    u64     coherent_dma_mask;
    
    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
    
    struct dev_archdata 		archdata;		/* arch specific additions */
    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结构很复杂,这里将会选一些对理解设备模型非常关键的字段进行说明:

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

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

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

    • init_name:该设备的名称。在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据“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/* 下的对应目录,如下:

      #ls /sys/dev/char/1:                                                                       
      1:1/  1:11/ 1:13/ 1:14/ 1:2/  1:3/  1:5/  1:7/  1:8/  1:9/   
      #ls /sys/dev/char/1:1                                                                      
      1:1/  1:11/ 1:13/ 1:14/  
      # ls /sys/dev/char/1:1   
      /sys/dev/char/1:1 
      
    • class:该设备属于哪个class。

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

2.2 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);
    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,如下:

       # 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两个接口名称的由来:检测到了设备就调用probe接口,、移除了设备(就调用remove)。

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

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

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

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

    • 在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括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就可以根据这些信息,执行相应的逻辑操作了。

4. 设备驱动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)

注意

  • probe动作实际是由bus模块实现的,这不难理解:device和device_driver都是挂载在bus这根线上,因此只有bus最清楚应该为哪两个device和driver配对。
  • 每个bus都有一个drivers_autoprobe变量,用于控制是否在device或者driver注册时,自动probe。该变量默认为1(即自动probe),bus模块将它开放到sysfs中了,因而可在用户空间修改,进而控制probe行为。

5. 其它杂项

5.1 device_attribute和driver_attribute

前面我们有讲到,大多数时候,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)

/* include/linux/sysfs.h, line 41 */
#define __ATTR(_name,_mode,_show,_store) { \
	.attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE },	\
	.show	= _show, \
	.store	= _store,\
}

5.2 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,告知用户空间某个类型的设备可用。
    • groups:该类型设备的公共attribute集合。设备注册时,会同时注册这些attribute。这就是面向对象中“继承”的概念
    • uevent:所有相同类型的设备,会有一些共有的uevent需要发送,由该接口实现。
    • devnode:devtmpfs有关的内容,暂不说明
    • release:如果device结构没有提供release接口,就要查询它所属的type是否提供。用于释放device变量所占的空间。

5.3 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的设备,这是之一)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值