zoukankan      html  css  js  c++  java
  • DRM(device resource management)介绍

    一、DRM简介

    1. 在DRM出现之前,在probe函数中要顺序申请多种资源(IRQ、Clock、memory、regions、ioremap、dma、等等),只要任意一种资源申请失败,就要回滚释放之前申请的所有资源。于是函数的最后,一定会出现很多的goto标签。最终Linux设备模型借助device resource management(设备资源管理)解决了这个问题。通过”devm_xxx()“函数申请的资源驱动只管申请,不用考虑释放,设备模型帮你释放。

    2. devm_xxx()函数
    它们由各个framework(如clock、regulator、gpio、等等)基于device resource management实现。使用时,直接忽略“devm_”的前缀,后面剩下的部分就是DRM出现之前驱动工程师使用的。虽然通过"devm_xxx()"驱动可以只申请,不释放,设备模型会帮忙释放,但是为了严谨,在driver remove时,也可以主动释。

    3. 一个设备能工作,需要依赖的外部条件,如供电、时钟等等,这些外部条件称作设备资源。可能的资源包括:power、clock、memory(一般使用kzalloc分配)、GPIO、IRQ、DMA、虚拟地址空间(般使用ioremap、request_region等分配)。而在Linux kernel的眼中,“资源”的定义更为广义,比如PWM、RTC、Reset,都可以抽象为供driver使用的资源。下面列举出devm的一些常用函数:

    void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);
    void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res);
    void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, unsigned long size);
    struct clk *devm_clk_get(struct device *dev, const char *id);
    int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
    struct pinctrl * devm_pinctrl_get_select(struct device *dev, const char *name);
    struct pwm_device *devm_pwm_get(struct device *dev,const char *consumer);
    struct regulator *devm_regulator_get(struct device *dev, const char *id);
    int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id);
    struct reset_control *devm_reset_control_get(struct device *dev, const char *id);

    4.device resource management实现位于“drivers/base/devres.c”中,提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在driver detach
    的时候,自动释放。

    5. devm实现框架简图

    二、代码分析

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

    struct device {
        ...
        spinlock_t devres_lock;
        struct list_head devres_head;
        ...
    };

    那资源的数据结构在“drivers/base/devres.c”中定义为struct devres结构,如下。每一份资源都通过一个devres结构表示,通过其node域中的链表节点挂接到struct device结构中的devres_head链表中。

    struct devres {
        struct devres_node node;
        /* -- 3 pointers -- */
        /* guarantee ull alignment */
        unsigned long long data[]; /*data边长数组就表示资源,资源是什么类型就分配多大的空间,然后将其转换成对应的类型*/
    };
    
    struct devres_node {
        struct list_head entry;
        dr_release_t release;
        #ifdef CONFIG_DEBUG_DEVRES
        const char *name;
        size_t size;
        #endif
    };

    设备资源框架是不知道该怎么释放的,因此提供一个回调函数release来让调用者决定。

    注意:devres有关的数据结构,是在devres.c中定义的(不是在.h文件中定义的哦!)。换句话说就是对其它模块透明的。尽量屏蔽细节,多么优雅的设计。

    三、以irq为例的资源申请

    1. irq的devm的申请和释放函数

    /* include/linux/interrupt.h */
    static inline int __must_check devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
        unsigned long irqflags, const char *devname, void *dev_id)
    {
        return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags, devname, dev_id);
    }
    
    /* kernel/irq/devres.c */
    int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn,
        unsigned long irqflags, const char *devname, void *dev_id)
    {
        struct irq_devres *dr;
        int rc;
    
        /*分配data的大小是sizeof(struct irq_devres),对返回的data直接转换成irq_devres结构*/
        dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres), GFP_KERNEL);
        if (!dr)
            return -ENOMEM;
    
        rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname, dev_id);
        if (rc) {
            devres_free(dr);
            return rc;
        }
    
        dr->irq = irq;
        dr->dev_id = dev_id;
        /*再将devres结构放到device结构中的链表上*/
        devres_add(dev, dr);
    
        return 0;
    }
    EXPORT_SYMBOL(devm_request_threaded_irq);
    
    /*可以直接调用这个函数释放,也可以不调用让其自动释放*/
    void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
    {
        struct irq_devres match_data = { irq, dev_id };
    
        WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match, &match_data));
        free_irq(irq, dev_id);
    }
    EXPORT_SYMBOL(devm_free_irq);

    2. 由上可知,irq的资源由irq模块的一个自定义的数据结构struct irq_devres来描述,用于保存和resource有关的信息(对中断来说,就是IRQ num),如下:

    /* Device resource management aware IRQ request/free implementation.*/
    struct irq_devres {
        unsigned int irq;
        void *dev_id;
    };

    irq的释放资源的回调函数,如下:

    static void devm_irq_release(struct device *dev, void *res)
    {
        struct irq_devres *this = res;
        free_irq(this->irq, this->dev_id);
    }

    3. devres_alloc函数以release回调和资源大小为参数来分配内存

    void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
    {
        struct devres *dr;
        dr = alloc_dr(release, size, gfp);
        if (unlikely(!dr))
            return NULL;
        return dr->data;
    }

    调用alloc_dr,分配一个struct devres类型的变量,并返回其中代表资源的data指针,alloc_dr的定义如下:

    static __always_inline struct devres * alloc_dr(dr_release_t release, size_t size, gfp_t gfp)
    {
        size_t tot_size = sizeof(struct devres) + size;
        struct devres *dr;
    
        dr = kmalloc_track_caller(tot_size, gfp);
        if (unlikely(!dr))
            return NULL;
    
        memset(dr, 0, tot_size);
        INIT_LIST_HEAD(&dr->node.entry);
        dr->node.release = release; /*release回调赋值过去*/
        return dr;
    }

    4. 将资源添加到device结构的链表上

    void devres_add(struct device *dev, void *res)
    {
        struct devres *dr = container_of(res, struct devres, data);
        unsigned long flags;
        spin_lock_irqsave(&dev->devres_lock, flags);
        add_dr(dev, &dr->node);
        spin_unlock_irqrestore(&dev->devres_lock, flags);
    }
    
    static void add_dr(struct device *dev, struct devres_node *node)
    {
        devres_log(dev, node, "ADD");
        BUG_ON(!list_empty(&node->entry));
        list_add_tail(&node->entry, &dev->devres_head); /*注意这里是尾插法插入设备的资源链表的*/
    }

    三、资源的自动释放

    1. DRM向设备模型提供的资源释放的接口为devres_release_all()函数,它是重点,用于自动释放资源。根据设备模型中probe的流程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

    2. devres_release_all()的实现如下:

    int devres_release_all(struct device *dev)
    {
        unsigned long flags;
        /* Looks like an uninitialized device structure */
        /* 如果在函数probe()失败时驱动中自己已经释放了,这里就是NULL,就不再次释放了*/
        if (WARN_ON(dev->devres_head.next == NULL))
            return -ENODEV;
        spin_lock_irqsave(&dev->devres_lock, flags);
        return release_nodes(dev, dev->devres_head.next, &dev->devres_head, flags);
    }
    
    sstatic int release_nodes(struct device *dev, struct list_head *first, struct list_head *end, unsigned long flags)
     __releases(&dev->devres_lock) /*此为内核代码静态分析工具Sparse的annotation。Sparse通过gcc的扩展属性__attribute__
     以及自己定义的__context__来对代码进行静态检查。*/
    {
        LIST_HEAD(todo);
        int cnt;
        struct devres *dr, *tmp;
    
        cnt = remove_nodes(dev, first, end, &todo);
    
        spin_unlock_irqrestore(&dev->devres_lock, flags);
    
        /* Release.  Note that both devres and devres_group are
        * handled as devres in the following loop.  This is safe.
        */
        /*最后申请的资源最先释放*/
        list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
            devres_log(dev, &dr->node, "REL");
            dr->node.release(dev, dr->data);
            kfree(dr);
        }
    
        return cnt;
    }

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

    参考:
    Linux设备模型(9)_device resource management: http://www.wowotech.net/device_model/device_resource_management.html
    Linux设备模型(5)_device和device driver: http://www.wowotech.net/linux_kenrel/device_and_driver.html
    内核工具–Sparse 简介:https://www.cnblogs.com/wang_yb/p/3575039.html

  • 相关阅读:
    react-redux简单使用
    jQuery——Js与jQuery的相互转换
    移除HTML5 input在type="number"时的上下小箭头
    echarts 5.0 地图
    Vue echarts 设置初始化默认高亮
    echarts 渐变色
    隐藏滚动条css
    echarts 柱状图--圆角
    echarts 气泡图--option
    Vue 圆柱体组件
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/12548225.html
Copyright © 2011-2022 走看看