Linux内核的设备资源管理框架详解(二)

水木秋寒Linux驱动 2024-06-30 17:20:21 3835阅读 举报


在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, offsetof(struct devres, data));

    INIT_LIST_HEAD(&dr->node.entry);
    dr->node.release = release;
    return dr;
}

看第一句就可以了,在资源size之前,加一个struct devres的size,就是total分配的空间。

除去struct devres的,就是资源的(由data指针访问)。

之后是初始化struct devres变量的node,可以看到,devres_alloc指定的release方法,便于在适当的时机执行。

devres_add/devres_remove

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);
    // 将资源添加到设备的资源链表头(devres_head)中。
    add_dr(dev, &dr->node);
    spin_unlock_irqrestore(&dev->devres_lock, flags);
}

从资源指针中,取出完整的struct devres指针,调用add_dr接口。

使用add_dr挂入devers链表中

将资源添加到设备的资源链表头(devres_head)中。

add_dr也很简单,把struct devres指针挂到设备的devres_head中即可
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);
}

devres_destroy

/**
 * devres_destroy - Find a device resource and destroy it
 * @dev: Device to find resource from
 * @release: Look for resources associated with this release function
 * @match: Match function (optional)
 * @match_data: Data for the match function
 *
 * Find the latest devres of @dev associated with @release and for
 * which @match returns 1.  If @match is NULL, it's considered to
 * match all.  If found, the resource is removed atomically and freed.
 *
 * Note that the release function for the resource will not be called,
 * only the devres-allocated data will be freed.  The caller becomes
 * responsible for freeing any other data.
 *
 * RETURNS:
 * 0 if devres is found and freed, -ENOENT if not found.
 */
int devres_destroy(struct device *dev, dr_release_t release,
           dr_match_t match, void *match_data)
{
    void *res;

    res = devres_remove(dev, release, match, match_data);
    if (unlikely(!res))
        return -ENOENT;

    devres_free(res);
    return 0;
}
EXPORT_SYMBOL_GPL(devres_destroy);

从设备中找出对应的资源,并摧毁。

以IRQ模块为例看看如何使用资源管理

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

/* 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;

    // 申请设备资源
    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);
    // 如果失败,可以通过devres_free接口释放资源占用的空间
    if (rc) {
        devres_free(dr);
        return rc;
    }
    dr->irq = irq;
    dr->dev_id = dev_id;
    
    
    // 注册所使用的设备资源
    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);

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

irq_devres原型

用于保存和resource有关的信息(对中断来说,就是IRQ num)

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

devm_irq_release

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

static void devm_irq_release(struct device *dev, void *res)
{
    struct irq_devres *this = res;

    free_irq(this->irq, this->dev_id);
}

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

申请设备资源

dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
                      GFP_KERNEL);
    if (!dr)
        return -ENOMEM;

以回调函数、resource的size为参数,调用devres_alloc接口,为resource分配空间。

使用设备资源做自己的事情

    // 使用设备资源做自己的事情
    rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
                              dev_id);
    // 如果失败,可以通过devres_free接口释放资源占用的空间
    if (rc) {
        devres_free(dr);
        return rc;
    }

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

如果失败了,可以通过devres_free接口释放资源占用的空间。

注册所使用的设备资源

注册成功后,以设备指针(dev)和资源指针(dr)为参数,调用devres_add,将资源添加到设备的资源链表头

devres_add(dev, dr);

到这里,设备资源管理框架就可以:用来在不需要使用的时候摧毁资源了。

用完以后摧毁资源

irq系统中,我们会调用devm_free_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);
}

而其中就会调用devres_destroy接口,将devresdevres_head中移除,并释放资源。

向设备模型提供的接口

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

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

devres_release_all

int devres_release_all(struct device *dev)
{
    unsigned long flags;

    /* Looks like an uninitialized device structure */
    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);
}

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

static int release_nodes(struct device *dev, struct list_head *first,
                         struct list_head *end, unsigned long flags)
    __releases(&dev->devres_lock)
{
    LIST_HEAD(todo);
    int cnt;
    struct devres *dr, *tmp;

    // 将设备所有的`devres`从设备的`devres_head`中移除
    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");
        // 调用所有资源的release回调函数(例如上面`devm_irq_release`),
        // 回调函数会回收具体的资源(如`free_irq`)。
        dr->node.release(dev, dr->data);
        // 最后,调用free,释放devres以及资源所占的空间
        kfree(dr);
    }

    return cnt;
}

调用时机

先回忆一下设备模型中probe的流程,devres_release_all接口被调用的时机有两个:

  • really_probe失败
  • 设备与驱动分离时:deriver dettach时(就是driver remove时)

really_probe失败

probe调用过程为(就不详细的贴代码了):__driver_attach/__device_attach-->driver_probe_device—>really_probe

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

static int really_probe(struct device *dev, struct device_driver *drv)
{
    int ret = 0;

    atomic_inc(&probe_count);

    dev->driver = drv;

    /* If using pinctrl, bind pins now before probing */
    ret = pinctrl_bind_pins(dev);
    if (ret)
        goto probe_failed;

    if (driver_sysfs_add(dev)) {
        printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
            __func__, dev_name(dev));
        goto probe_failed;
    }

    if (dev->bus->probe) {
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) {
        ret = drv->probe(dev);
        if (ret)
            goto probe_failed;
    }

    driver_bound(dev);
    ret = 1;
    pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
         drv->bus->name, __func__, dev_name(dev), drv->name);
    goto done;

probe_failed:
    devres_release_all(dev);
    // ...
    return ret;
}

设备与驱动分离时

另外一个时机是在,deriver dettach时(就是driver remove时):driver_detach/bus_remove_device-->__device_release_driver-->devres_release_all

我们看下leds-gpio.c中如何使用的:

int devm_of_led_classdev_register(struct device *parent,
				  struct device_node *np,
				  struct led_classdev *led_cdev)
{
	struct led_classdev **dr;
	int rc;

	dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
	if (!dr)
		return -ENOMEM;

	rc = of_led_classdev_register(parent, np, led_cdev);
	if (rc) {
		devres_free(dr);
		return rc;
	}

	*dr = led_cdev;
	devres_add(parent, dr);

	return 0;
}

这里就是通过devres_alloc分配了一个LED的设备资源空间,然后指向了led_cdev,最后通过devres_add挂到设备资源管理列表中。等到设备detach的时候就会释放掉 led_cdev所占的资源。




标签: #Linux#

版权声明:
作者:水木秋寒
链接:https://www.dianziwang.net/p/187b9742afed8e.html
来源:Linux驱动
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以点击 “举报”


登录 后发表评论
0条评论
还没有人评论过~