Maison  >  Article  >  Tutoriel système  >  Modèle de périphérique Linux (5)_périphérique et pilote de périphérique

Modèle de périphérique Linux (5)_périphérique et pilote de périphérique

WBOY
WBOYavant
2024-02-11 08:21:11597parcourir

1.Introduction

Dans le développement de pilotes Linux, le périphérique et le pilote de périphérique sont des concepts de base. L'idée principale du noyau est de définir deux structures de données, périphérique et périphérique_driver, respectivement pour le périphérique et son pilote. Cet article se concentrera sur ces deux structures de données pour présenter la logique de base du modèle de périphérique Linux, notamment :

.

Linux设备模型(5)_device和device driver

  • Abstraction, utilisation et maintenance des périphériques et pilotes de périphériques dans le noyau
  •  ;
  • Principes d'enregistrement, de chargement et d'initialisation des appareils et des pilotes de périphériques
  •  ;
  • Comment utiliser les modèles de périphériques dans le processus de développement réel des pilotes.

Il est à noter que lors de l'introduction de Device et Device_driver, de nombreux autres points de connaissances peuvent être impliqués, tels que Class, Bus, DMA, gestion de l'alimentation, etc. Ces points de connaissances sont très complexes et chacun d’entre eux peut être expliqué comme un sujet distinct. Par conséquent, cet article ne les analysera pas en profondeur, mais les présentera spécifiquement dans les articles suivants.

2. struct périphérique et struct device_driver

Lors de la lecture du code source du noyau Linux, plus de 60 % de la logique d'un certain module peut être comprise à travers la structure de données de base, en particulier la partie modèle de périphérique.

Dans include/linux/device.h, le noyau Linux définit les deux structures de données les plus importantes dans le modèle de périphérique, struct device et struct device_driver.

  • appareil de construction
 1: /* include/linux/device.h, line 660 */
 2: struct device {
 3:     struct device       *parent;
 4:  
 5:     struct device_private   *p;
 6:  
 7:     struct kobject kobj;
 8:     const char *init_name; /* initial name of the device */
 9:     const struct device_type *type;
 10:  
 11:    struct mutex        mutex; /* mutex to synchronize calls to
 12:                             * its driver.
 13:                             */
 14:  
 15:    struct bus_type *bus; /* type of bus device is on */
 16:    struct device_driver *driver; /* which driver has allocated this
 17:                                 device */
 18:    void *platform_data; /* Platform specific data, device
 19:                         core doesn't touch it */
 20:    struct dev_pm_info  power;
 21:    struct dev_pm_domain    *pm_domain;
 22:  
 23: #ifdef CONFIG_PINCTRL
 24:    struct dev_pin_info *pins;
 25: #endif
 26:  
 27: #ifdef CONFIG_NUMA
 28:    int numa_node; /* NUMA node this device is close to */
 29: #endif
 30:    u64     *dma_mask; /* dma mask (if dma'able device) */
 31:    u64     coherent_dma_mask;/* Like dma_mask, but for
 32:                             alloc_coherent mappings as
 33:                             not all hardware supports
 34:                             64 bit addresses for consistent
 35:                             allocations such descriptors. */
 36:  
 37:    struct device_dma_parameters *dma_parms;
 38:  
 39:    struct list_head    dma_pools; /* dma pools (if dma'ble) */
 40:  
 41:    struct dma_coherent_mem *dma_mem; /* internal for coherent mem
 42:                            override */
 43: #ifdef CONFIG_CMA
 44:    struct cma *cma_area; /* contiguous memory area for dma
 45:                            allocations */
 46: #endif
 47:    /* arch specific additions */
 48:    struct dev_archdata archdata;
 49:  
 50:    struct device_node  *of_node; /* associated device tree node */
 51:    struct acpi_dev_node    acpi_node; /* associated ACPI device node */
 52:  
 53:    dev_t           devt; /* dev_t, creates the sysfs "dev" */
 54:    u32         id; /* device instance */
 55:  
 56:    spinlock_t      devres_lock;
 57:    struct list_head    devres_head;
 58:  
 59:    struct klist_node   knode_class;
 60:    struct class *class;
 61:    const struct attribute_group **groups; /* optional groups */
 62:  
 63:    void (*release)(struct device *dev);
 64:    struct iommu_group  *iommu_group;
 65: };

«

La structure du périphérique est très complexe (mais la qualité des développeurs du noyau Linux est très élevée et les commentaires sur cette interface sont très détaillés. Les étudiants intéressés peuvent se référer au code source du noyau. Nous en sélectionnerons ici quelques-uns qui sont très critiques). comprendre le modèle de l'appareil.

parent, l'appareil parent de l'appareil, généralement le bus, le contrôleur et les autres appareils auxquels l'appareil est subordonné.

p, un pointeur de structure de données privée pour le périphérique struct Ce pointeur enregistrera la liste chaînée des sous-périphériques, l'en-tête de la liste chaînée utilisé pour être ajouté au bus/pilote/prent et à d'autres périphériques, etc. Veuillez consulter le code source pour plus de détails. .

kobj, la struct kobject correspondant à cette structure de données.

init_name, le nom de l'appareil.

​ Note 1 : Dans le modèle de périphérique, le nom est une variable très importante. Tout périphérique enregistré dans le noyau doit avoir un nom légal, qui peut être donné lors de l'initialisation ou par le noyau selon "nom du bus + ID de périphérique".

type, la structure struct device_type est une structure nouvellement introduite dans la nouvelle version du noyau. Sa relation avec struct device est très similaire à la relation entre stuct kobj_type et struct kobject, qui sera expliquée en détail plus tard.

bus, à quel bus appartient l'appareil (sera décrit en détail plus tard).

driver, le pilote de périphérique correspondant au périphérique.

platform_data, un pointeur utilisé pour enregistrer des données spécifiques liées à la plateforme. Le module pilote spécifique peut stocker temporairement certaines données privées ici et les supprimer en cas de besoin, de sorte que le modèle de périphérique ne se soucie pas de la signification réelle du pointeur.

power, pm_domain, la logique liée à la gestion de l'énergie sera expliquée plus tard dans la rubrique sur la gestion de l'énergie.

pins, fonction "PINCTRL", non encore décrite.

numa_node, fonction "NUMA", non encore décrite.

dma_mask~archdata, les fonctions liées au DMA, ne seront pas encore décrites.

devt, dev_t est un entier de 32 bits composé de deux parties (majeur et mineur). Il est utilisé comme numéro de périphérique dans les appareils qui doivent fournir des interfaces à l'espace utilisateur sous la forme de nœuds de périphérique (périphériques de caractères et bloc). appareils). Ici, cette variable est principalement utilisée pour créer un répertoire correspondant sous /sys/dev/* pour chaque périphérique avec un numéro de périphérique dans le système de fichiers sys, comme suit :

1|root@android:/storage/sdcard0 #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/
1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:1
1:1/ 1:11/ 1:13/ 1:14/
1|root@android:/storage/sdcard0 # ls /sys/dev/char/1:1
/sys/dev/char/1:1

classe, à quelle classe appartient l'appareil.

groups, la collection d'attributs par défaut de l'appareil. Le fichier correspondant sera automatiquement créé dans sysfs lors de l'enregistrement de l'appareil.

  • struct device_driver
 1: /* include/linux/device.h, line 213 */
 2: struct device_driver {  
 3:     const char *name;  
 4:     struct bus_type     *bus;
 5:  
 6:     struct module       *owner;
 7:     const char *mod_name; /* used for built-in modules */
 8:  
 9:     bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
 10:  
 11:    const struct of_device_id   *of_match_table;
 12:    const struct acpi_device_id *acpi_match_table;
 13:  
 14:    int (*probe) (struct device *dev);
 15:    int (*remove) (struct device *dev);
 16:    void (*shutdown) (struct device *dev);
 17:    int (*suspend) (struct device *dev, pm_message_t state);
 18:    int (*resume) (struct device *dev);
 19:    const struct attribute_group **groups;
 20:  
 21:    const struct dev_pm_ops *pm;
 22:  
 23:    struct driver_private *p;
 24: };

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代码可以把任何需要的内容放在这里,反正设备模型代码不关心。

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)

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

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

5. 其它杂项

5.1 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也不无例外的提供了这种例子,如下:

 1: /* driver/base/core.c, line 118 */
 2: static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
 3: char *buf) 
 4: {   
 5:     struct device_attribute *dev_attr = to_dev_attr(attr);
 6:     struct device *dev = kobj_to_dev(kobj);
 7:     ssize_t ret = -EIO;
 8: 
 9:     if (dev_attr->show)
 10:        ret = dev_attr->show(dev, dev_attr, buf);
 11:        if (ret >= (ssize_t)PAGE_SIZE) {
 12:            print_symbol("dev_attr_show: %s returned bad count\n",
 13:                        (unsigned long)dev_attr->show);
 14:    }
 15:    return ret;
 16: }
 17:  
 18: static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
 19: const char *buf, size_t count)
 20: {
 21:    struct device_attribute *dev_attr = to_dev_attr(attr);
 22:    struct device *dev = kobj_to_dev(kobj);
 23:    ssize_t ret = -EIO;
 24: 
 25:    if (dev_attr->store)
 26:        ret = dev_attr->store(dev, dev_attr, buf, count);
 27:    return ret;
 28: }
 29:  
 30: static const struct sysfs_ops dev_sysfs_ops = {
 31:    .show   = dev_attr_show,
 32:    .store  = dev_attr_store,
 33: };
 34:  
 35: /* driver/base/core.c, line 243 */
 36: static struct kobj_type device_ktype = {
 37:    .release    = device_release,
 38:    .sysfs_ops  = &dev_sysfs_ops,
 39:    .namespace = device_namespace,
 40: };
 41:  
 42: /* include/linux/device.h, line 478 */
 43: /* interface for exporting device attributes */
 44: struct device_attribute {
 45:    struct attribute    attr;
 46:    ssize_t (*show)(struct device *dev, struct device_attribute *attr,
 47:                    char *buf);
 48:    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
 49:                    const char *buf, size_t count);
 50: };

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

 1: /* include/linux/device.h, line 247 */
 2: /* sysfs interface for exporting driver attributes */
 3:  
 4: struct driver_attribute {
 5:     struct attribute attr;
 6:     ssize_t (*show)(struct device_driver *driver, char *buf);
 7:     ssize_t (*store)(struct device_driver *driver, const char *buf,
 8:                     size_t count);
 9: };
 10:  
 11: #define DRIVER_ATTR(_name, _mode, _show, _store)    \
 12: struct driver_attribute driver_attr_##_name =       \
 13:    __ATTR(_name, _mode, _show, _store)

5.2 device_type

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

 1: /* include/linux/device.h, line 467 */
 2: struct device_type {
 3:     const char *name;
 4:     const struct attribute_group **groups;
 5:     int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
 6:     char *(*devnode)(struct device *dev, umode_t *mode,
 7:                     kuid_t *uid, kgid_t *gid);
 8:     void (*release)(struct device *dev);
 9:  
 10:    const struct dev_pm_ops *pm;
 11: };

device_type的功能包括:

  • name表示该类型的名称,当该类型的设备添加到内核时,内核会发出”DEVTYPE=‘name’”类型的uevent,告知用户空间某个类型的设备available了
  • 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/下注册一个目录,该目录不代表任何的实体设备,这时可以使用下面的接口:

 1: /* include/linux/device.h, line 859 */
 2: /*
 3:  * Root device objects for grouping under /sys/devices
 4:  */
 5: extern struct device *__root_device_register(const char *name,
 6: struct module *owner);
 7:  
 8: /*
 9:  * This is a macro to avoid include problems with THIS_MODULE,
 10:  * just as per what is done for device_schedule_callback() above.
 11:  */
 12: #define root_device_register(name) \
 13: __root_device_register(name, THIS_MODULE)
 14:  
 15: extern void root_device_unregister(struct device *root);

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer