Heim  >  Artikel  >  System-Tutorial  >  Linux-Gerätemodell(9)_Geräteressourcenverwaltung

Linux-Gerätemodell(9)_Geräteressourcenverwaltung

PHPz
PHPznach vorne
2024-02-10 21:15:411188Durchsuche

1. Einführung

Es wird jedem Linux-Treiberentwickler empfohlen, sich die Zeit zu nehmen, diesen Artikel zu lesen.

Die in diesem Artikel vorgestellten Themen sind sehr praktisch, können einige Verwirrungen lösen und unseren Code auch einfach und prägnant machen. Schauen wir uns zunächst ein Beispiel an:

   1: /* drivers/media/platform/soc_camera/mx1_camera.c, line 695 */
   2: static int __init mx1_camera_probe(struct platform_device *pdev)
   3: {
   4:     ...
   5:  
   6:     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
   7:     irq = platform_get_irq(pdev, 0);
   8:     if (!res || (int)irq exit;
  11:     }
  12:  
  13:     clk = clk_get(&pdev->dev, "csi_clk");
  14:     if (IS_ERR(clk)) {
  15:         err = PTR_ERR(clk);
  16:         goto exit;
  17:     }
  18:  
  19:     pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
  20:     if (!pcdev) {
  21:         dev_err(&pdev->dev, "Could not allocate pcdev\n");
  22:         err = -ENOMEM;
  23:         goto exit_put_clk;
  24:     }
  25:  
  26:     ...
  27:  
  28:     /*
  29:      * Request the regions.
  30:      */
  31:     if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) {
  32:         err = -EBUSY;
  33:         goto exit_kfree;
  34:     }
  35:  
  36:     base = ioremap(res->start, resource_size(res));
  37:     if (!base) {
  38:         err = -ENOMEM;
  39:         goto exit_release;
  40:     }
  41:     ...
  42:  
  43:     /* request dma */
  44:     pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
  45:     if (pcdev->dma_chan dev, "Can't request DMA for MX1 CSI\n");
  47:         err = -EBUSY;
  48:         goto exit_iounmap;
  49:     }
  50:     ...
  51:  
  52:     /* request irq */
  53:     err = claim_fiq(&fh);
  54:     if (err) {
  55:         dev_err(&pdev->dev, "Camera interrupt register failed\n");
  56:         goto exit_free_dma;
  57:     }
  58:  
  59:     ...
  60:     err = soc_camera_host_register(&pcdev->soc_host);
  61:     if (err)
  62:         goto exit_free_irq;
  63:  
  64:     dev_info(&pdev->dev, "MX1 Camera driver loaded\n");
  65:  
  66:     return 0;
  67:  
  68: exit_free_irq:
  69:     disable_fiq(irq);
  70:     mxc_set_irq_fiq(irq, 0);
  71:     release_fiq(&fh);
  72: exit_free_dma:
  73:     imx_dma_free(pcdev->dma_chan);
  74: exit_iounmap:
  75:     iounmap(base);
  76: exit_release:
  77:     release_mem_region(res->start, resource_size(res));
  78: exit_kfree:
  79:     kfree(pcdev);
  80: exit_put_clk:
  81:     clk_put(clk);
  82: exit:
  83:     return err;
  84: }

Ich glaube, dass jeder Ingenieur, der einen Linux-Treiber geschrieben hat, bei der Sondenfunktion auf ähnliche Probleme gestoßen ist: Mehrere Ressourcen (IRQ, Takt, Speicher, Bereich, Ioremap, DMA usw.) müssen in einer bestimmten Reihenfolge beantragt werden Einer davon: Wenn die Ressourcenanwendung fehlschlägt, müssen Sie ein Rollback durchführen und alle zuvor angewendeten Ressourcen freigeben. Daher befinden sich am Ende der Funktion normalerweise viele Goto-Beschriftungen (z. B. exit_free_irq, exit_free_dma usw.), sodass Sie bei einem Fehler bei der Ressourcenanwendung vorsichtig zur richtigen Beschriftung springen können, um die angewendeten Ressourcen freizugeben.

Ein solcher Code ist jedoch voll von großen Abschnitten wiederholten Codes und enthält viel „if (condition) { err = xxx; goto xxx; }“-Logik, was nicht nur Zeitverschwendung und fehleranfällig ist, sondern auch unansehnlich. Wenn wir jedoch verwirrt sind, gibt es Raum für Verbesserungen. Letztendlich löst das Linux-Gerätemodell dieses Problem für uns mithilfe der Geräteressourcenverwaltung: Der Treiber muss nur Ressourcen beantragen und muss sich nicht darum kümmern, wie die Ressourcen freigegeben werden. Das Gerätemodell übernimmt dies für uns. Letztendlich können wir den Treibercode vereinfachen durch:

   1: static int __init mx1_camera_probe(struct platform_device *pdev)
   2: {
   3:     ...
   4:  
   5:     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
   6:     irq = platform_get_irq(pdev, 0);
   7:     if (!res || (int)irq return -ENODEV;
   9:     }
  10:  
  11:     clk = devm_clk_get(&pdev->dev, "csi_clk");
  12:     if (IS_ERR(clk)) {
  13:         return PTR_ERR(clk);
  14:     }
  15:  
  16:     pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL);
  17:     if (!pcdev) {
  18:         dev_err(&pdev->dev, "Could not allocate pcdev\n");
  19:         return -ENOMEM;
  20:     }
  21:  
  22:     ...
  23:  
  24:     /*
  25:      * Request the regions.
  26:      */
  27:     if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), DRIVER_NAME)) {
  28:         return -EBUSY;
  29:     }
  30:  
  31:     base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
  32:     if (!base) {
  33:         return -ENOMEM;
  34:     }
  35:     ...
  36:  
  37:     /* request dma */
  38:     pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
  39:     if (pcdev->dma_chan dev, "Can't request DMA for MX1 CSI\n");
  41:         return -EBUSY;
  42:     }
  43:     ...
  44:  
  45:     /* request irq */
  46:     err = claim_fiq(&fh);
  47:     if (err) {
  48:         dev_err(&pdev->dev, "Camera interrupt register failed\n");
  49:         return err;
  50:     }
  51:  
  52:     ...
  53:     err = soc_camera_host_register(&pcdev->soc_host);
  54:     if (err)
  55:         return err;
  56:  
  57:     dev_info(&pdev->dev, "MX1 Camera driver loaded\n");
  58:  
  59:     return 0;
  60: }

Wie geht das? Achten Sie auf die Schnittstelle, die oben mit „devm_“ beginnt. Die Antwort finden Sie dort. Verwenden Sie nicht diese herkömmlichen Ressourcenanwendungsschnittstellen, sondern stattdessen die devm_xxx-Schnittstelle. Um die Kompatibilität aufrechtzuerhalten, bleiben die Parameter dieser neuen Schnittstellen dieselben wie die der alten Schnittstellen, mit der Ausnahme, dass „devm_“ vor dem Namen hinzugefügt wird und ein zusätzlicher Strukturgerätezeiger hinzugefügt wird.

2.devm_xxx

Im Folgenden sind einige häufig verwendete Ressourcenanwendungsschnittstellen aufgeführt, die von verschiedenen Frameworks (z. B. Uhr, Regler, GPIO usw.) basierend auf der Geräteressourcenverwaltung implementiert werden. Wenn Sie es verwenden, ignorieren Sie einfach das Präfix „devm_“, und der Rest des Teils wird den Treiberingenieuren bekannt sein. Denken Sie daran: Der Treiber kann nur angewendet, aber nicht freigegeben werden. Das Gerätemodell hilft bei der Freigabe. Wenn Sie jedoch streng sein möchten, können Sie es beim Entfernen des Treibers aktiv freigeben (es gibt auch entsprechende Schnittstellen, die hier nicht aufgeführt sind).

   1: extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);
   2:  
   3: void __iomem *devm_ioremap_resource(struct device *dev, 
   4:   struct resource *res);
   5: void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
   6:   unsigned long size);
   7:  
   8: struct clk *devm_clk_get(struct device *dev, const char *id);
   9:  
  10: int devm_gpio_request(struct device *dev, unsigned gpio,
  11:   const char *label);
  12:  
  13: static inline struct pinctrl * devm_pinctrl_get_select(
  14:   struct device *dev, const char *name)
  15:  
  16: static inline struct pwm_device *devm_pwm_get(struct device *dev,
  17:   const char *consumer);
  18:  
  19: struct regulator *devm_regulator_get(struct device *dev, const char *id);
  20:  
  21: static inline int devm_request_irq(struct device *dev, unsigned int irq, 
  22:   irq_handler_t handler, unsigned long irqflags, 
  23:   const char *devname, void *dev_id);
  24:  
  25: struct reset_control *devm_reset_control_get(struct device *dev, 
  26:   const char *id);

3. Was sind „Geräteressourcen“

Damit ein Gerät funktioniert, hängt es von vielen äußeren Bedingungen ab, wie z. B. der Stromversorgung, der Uhr usw. Diese äußeren Bedingungen werden als Geräteressourcen bezeichnet. Zu den möglichen Ressourcen für die Architektur moderner Computer gehören:

a) Strom, Stromversorgung.
b) Uhr, Uhr.
c) Speicher. Der Speicher wird im Allgemeinen mit kzalloc im Kernel zugewiesen.
d) GPIO, der Benutzer und die CPU tauschen einfache Steuerungs-, Status- und andere Informationen aus.
e) IRQ, löst Interrupt aus.
f) DMA, Datenübertragung ohne CPU-Beteiligung.
g) Der virtuelle Adressraum wird im Allgemeinen mithilfe von ioremap, request_region usw. zugewiesen.
h) Warte

In den Augen des Linux-Kernels ist die Definition von „Ressourcen“ umfassender, wie etwa PWM, RTC und Reset, die alle in Ressourcen zur Verwendung durch den Treiber abstrahiert werden können.

Im früheren Kernel war das System nicht besonders komplex und jedes Framework hatte noch nicht Gestalt angenommen, sodass die meisten Ressourcen vom Treiber selbst verwaltet wurden. Da jedoch die Komplexität des Systems zunimmt, werden immer mehr Ressourcen zwischen den Treibern geteilt und die Notwendigkeit einer Energieverwaltung wird immer dringlicher. Der Kernel übernimmt also die Verwaltungsrechte für jede Ressource. Basierend auf dem „Device Resource Management“-Framework verwaltet jedes Framework diese einheitlich, einschließlich Zuweisung und Recycling.

4. Software-Framework für die Geräteressourcenverwaltung

Linux设备模型(9)_device resource managementDie Geräteressourcenverwaltung befindet sich in „drivers/base/devres.c“. Die Implementierung ist sehr einfach. Warum? Da es viele Arten von Ressourcen und verschiedene Ausdrucksformen gibt und Devres sie nicht alle kennen kann, ist eine spezifische Zuordnung und Wiederverwertung nicht möglich. Was Devres also tun kann (und seine einzige Funktion hat), ist:

提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在driver detach的时候,自动释放。

而更为具体的事情,如怎么抽象某一种设备,则由上层的framework负责。这些framework包括:regulator framework(管理power资源),clock framework(管理clock资源),interrupt framework(管理中断资源)、gpio framework(管理gpio资源),pwm framework(管理PWM),等等。

其它的driver,位于这些framework之上,使用它们提供的机制和接口,开发起来就非常方便了。

5. 代码分析

5.1 数据结构

先从struct device开始吧!该结构中有一个名称为“devres_head”的链表头,用于保存该设备申请的所有资源,如下:

   1: struct device {
   2:         ...
   3:         spinlock_t              devres_lock;
   4:         struct list_head        devres_head;
   5:         ...
   6: }
   7:  

那资源的数据结构呢?在“drivers/base/devres.c”中,名称为struct devres,如下:

   1: struct devres {
   2:         struct devres_node              node;
   3:         /* -- 3 pointers */
   4:         unsigned long long              data[]; /* guarantee ull alignment */
   5: };

咋一看非常简单,一个struct devres_node的变量node,一个零长度数组data,但其中有无穷奥妙,让我们继续分析。

node用于将devres组织起来,方便插入到device结构的devres_head链表中,因此一定也有一个list_head(见下面的entry)。另外,资源的存在形式到底是什么,device resource management并不知情,因此需要上层模块提供一个release的回调函数,用于release资源,如下:

   1: struct devres_node {
   2:         struct list_head                entry;
   3:         dr_release_t                    release;
   4: #ifdef CONFIG_DEBUG_DEVRES
   5:         const char                      *name;
   6:         size_t                          size;
   7: #endif
   8: };

抛开用于debug的变量不说,也很简单,一个entry list_head,一个release回调函数。看不出怎么抽象资源啊!别急,奥妙都在data这个零长度数组上面呢。

注1:不知道您是否注意到,devres有关的数据结构,是在devres.c中定义的(是C文件哦!)。换句话说,是对其它模块透明的。这真是优雅的设计(尽量屏蔽细节)!

5.2 一个无关话题:零长度数组

零长度数组的英文原名为Arrays of Length Zero,是GNU C的规范,主要用途是用来作为结构体的最后一个成员,然后用它来访问此结构体对象之后的一段内存(通常是动态分配的内存)。什么意思呢?

以struct devres为例,node变量的长度为3个指针的长度,而struct devres的长度也是3个指针的长度。而data只是一个标记,当有人分配了大于3个指针长度的空间并把它转换为struct devres类型的变量后,我们就可以通过data来访问多出来的memory。也就是说,有了零长度数组data,struct devres结构的长度可以不定,完全依赖于你分配的空间的大小。有什么用呢?

以本文的应用场景为例,多出来的、可通过data访问的空间,正是具体的device resource所占的空间。资源的类型不同,占用的空间的多少也不同,但devres模块的主要功能又是释放资源所占的资源。这是就是零长度数组的功能之一,因为整个memory空间是连续的,因此可以通过释devres指针,释放所有的空间,包括data所指的那片不定长度的、具体资源所用的空间。

零长度数组(data[0]),在不同的C版本中,有不同的实现方案,包括1长度数组(data[1])和不定长度数组(data[],本文所描述就是这一种),具体可参考GCC的规范:

https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

5.3 向上层framework提供的接口:devres_alloc/devres_free、devres_add/devres_remove

先看一个使用device resource management的例子(IRQ模块):

   1: /* include/linux/interrupt.h */
   2: static inline int __must_check
   3: devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
   4:                  unsigned long irqflags, const char *devname, void *dev_id)
   5: {
   6:         return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
   7:                                          devname, dev_id);
   8: }
   9:  
  10:  
  11: /* kernel/irq/devres.c */
  12: int devm_request_threaded_irq(struct device *dev, unsigned int irq,
  13:                               irq_handler_t handler, irq_handler_t thread_fn,
  14:                               unsigned long irqflags, const char *devname,
  15:                               void *dev_id)
  16: {
  17:         struct irq_devres *dr;
  18:         int rc;
  19:  
  20:         dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
  21:                           GFP_KERNEL);
  22:         if (!dr)
  23:                 return -ENOMEM;
  24:  
  25:         rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
  26:                                   dev_id);
  27:         if (rc) {
  28:                 devres_free(dr);
  29:                 return rc;
  30:         }
  31:  
  32:         dr->irq = irq;
  33:         dr->dev_id = dev_id;
  34:         devres_add(dev, dr);
  35:  
  36:         return 0;
  37: }
  38: EXPORT_SYMBOL(devm_request_threaded_irq);
  39:  
  40: void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
  41: {
  42:         struct irq_devres match_data = { irq, dev_id };
  43:  
  44:         WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match,
  45:                                &match_data));
  46:         free_irq(irq, dev_id);
  47: }
  48: EXPORT_SYMBOL(devm_free_irq);

前面我们提过,上层的IRQ framework,会提供两个和request_irq/free_irq基本兼容的接口,这两个接口的实现非常简单,就是在原有的实现之上,封装一层devres的操作,如要包括:

1)一个自定义的数据结构(struct irq_devres),用于保存和resource有关的信息(对中断来说,就是IRQ num),如下:

   1: /*
   2:  * Device resource management aware IRQ request/free implementation.
   3:  */
   4: struct irq_devres {
   5:         unsigned int irq;
   6:         void *dev_id;
   7: };

2)一个用于release resource的回调函数(这里的release,和memory无关,例如free IRQ),如下:

   1: static void devm_irq_release(struct device *dev, void *res)
   2: {
   3:         struct irq_devres *this = res;
   4:  
   5:         free_irq(this->irq, this->dev_id);
   6: }

因为回调函数是由devres模块调用的,由它的参数可知,struct irq_devres变量就是实际的“资源”,但对devres而言,它并不知道该资源的实际形态,因而是void类型指针。也只有这样,devres模块才可以统一的处理所有类型的资源。

3)以回调函数、resource的size为参数,调用devres_alloc接口,为resource分配空间。该接口的定义如下:

   1: void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
   2: {
   3:         struct devres *dr;
   4:  
   5:         dr = alloc_dr(release, size, gfp);
   6:         if (unlikely(!dr))
   7:                 return NULL;
   8:         return dr->data;
   9: }

调用alloc_dr,分配一个struct devres类型的变量,并返回其中的data指针(5.2小节讲过了,data变量实际上是资源的代表)。alloc_dr的定义如下:

   1: static __always_inline struct devres * alloc_dr(dr_release_t release,
   2:                                                 size_t size, gfp_t gfp)
   3: {
   4:         size_t tot_size = sizeof(struct devres) + size;
   5:         struct devres *dr;
   6:  
   7:         dr = kmalloc_track_caller(tot_size, gfp);
   8:         if (unlikely(!dr))
   9:                 return NULL;
  10:  
  11:         memset(dr, 0, tot_size);
  12:         INIT_LIST_HEAD(&dr->node.entry);
  13:         dr->node.release = release;
  14:         return dr;
  15: }

看第一句就可以了,在资源size之前,加一个struct devres的size,就是total分配的空间。除去struct devres的,就是资源的(由data指针访问)。之后是初始化struct devres变量的node。

4)调用原来的中断注册接口(这里是request_threaded_irq),注册中断。该步骤和device resource management无关。

5)注册成功后,以设备指针(dev)和资源指针(dr)为参数,调用devres_add,将资源添加到设备的资源链表头(devres_head)中,该接口定义如下:

   1: void devres_add(struct device *dev, void *res)
   2: {
   3:         struct devres *dr = container_of(res, struct devres, data);
   4:         unsigned long flags;
   5:  
   6:         spin_lock_irqsave(&dev->devres_lock, flags);
   7:         add_dr(dev, &dr->node);
   8:         spin_unlock_irqrestore(&dev->devres_lock, flags);
   9: }

从资源指针中,取出完整的struct devres指针,调用add_dr接口。add_dr也很简单,把struct devres指针挂到设备的devres_head中即可:

   1: static void add_dr(struct device *dev, struct devres_node *node)
   2: {
   3:         devres_log(dev, node, "ADD");
   4:         BUG_ON(!list_empty(&node->entry));
   5:         list_add_tail(&node->entry, &dev->devres_head);
   6: }

6)如果失败,可以通过devres_free接口释放资源占用的空间,devm_free_irq接口中,会调用devres_destroy接口,将devres从devres_head中移除,并释放资源。这里就不详细描述了。

5.4 向设备模型提供的接口:devres_release_all

这里是重点,用于自动释放资源。

先回忆一下设备模型中probe的流程(可参考“Linux设备模型(5)_device和device driver”),devres_release_all接口被调用的时机有两个:

1)probe失败时,调用过程为(就不详细的贴代码了):

__driver_attach/__device_attach–>driver_probe_device—>really_probe,really_probe调用driver或者bus的probe接口,如果失败(返回值非零,可参考本文开头的例子),则会调用devres_release_all。

2)deriver dettach时(就是driver remove时)

driver_detach/bus_remove_device–>__device_release_driver–>devres_release_all

devres_release_all的实现如下:

   1: int devres_release_all(struct device *dev)
   2: {
   3:         unsigned long flags;
   4:  
   5:         /* Looks like an uninitialized device structure */
   6:         if (WARN_ON(dev->devres_head.next == NULL))
   7:                 return -ENODEV;
   8:         spin_lock_irqsave(&dev->devres_lock, flags);
   9:         return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
  10:                              flags);
  11: }

以设备指针为参数,直接调用release_nodes:

   1: static int release_nodes(struct device *dev, struct list_head *first,
   2:                          struct list_head *end, unsigned long flags)
   3:         __releases(&dev->devres_lock)
   4: {
   5:         LIST_HEAD(todo);
   6:         int cnt;
   7:         struct devres *dr, *tmp;
   8:  
   9:         cnt = remove_nodes(dev, first, end, &todo);
  10:  
  11:         spin_unlock_irqrestore(&dev->devres_lock, flags);
  12:  
  13:         /* Release.  Note that both devres and devres_group are
  14:          * handled as devres in the following loop.  This is safe.
  15:          */
  16:         list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
  17:                 devres_log(dev, &dr->node, "REL");
  18:                 dr->node.release(dev, dr->data);
  19:                 kfree(dr);
  20:         }
  21:  
  22:         return cnt;
  23: }

release_nodes会先调用remove_nodes,将设备所有的struct devres指针从设备的devres_head中移除。然后,调用所有资源的release回调函数(如5.3小节描述的devm_irq_release),回调函数会回收具体的资源(如free_irq)。最后,调用free,释放devres以及资源所占的空间。

Das obige ist der detaillierte Inhalt vonLinux-Gerätemodell(9)_Geräteressourcenverwaltung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:lxlinux.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen