Devres - 托管设备资源

Tejun Heo <teheo@suse.de>

初稿 2007年1月10日

1. 简介

devres 是在尝试将 libata 转换为使用 iomap 时出现的。每个 iomapped 地址都应该被保留,并在驱动程序分离时取消映射。例如,一个普通 SFF ATA 控制器(即,老式的 PCI IDE)在原生模式下使用 5 个 PCI BAR,所有这些都应该被维护。

与许多其他设备驱动程序一样,libata 底层驱动程序在 ->remove 和 ->probe 失败路径中存在足够的错误。是的,这可能是因为 libata 底层驱动程序开发人员比较懒,但难道不是所有的底层驱动程序开发人员都这样吗?在花了一天时间摆弄没有文档或文档损坏的硬件之后,如果它最终能工作,好吧,它能工作就行了。

由于某种原因,底层驱动程序没有像核心代码那样受到足够的关注或测试,并且驱动程序分离或初始化失败时的错误并不经常发生,因此不太容易被注意到。初始化失败路径更糟,因为它不太常用,同时需要处理多个入口点。

因此,许多底层驱动程序最终会在驱动程序分离时泄漏资源,并且在 ->probe() 中具有半损坏的失败路径实现,这会在发生故障时泄漏资源甚至导致 oops。iomap 为此添加了更多内容。msi 和 msix 也是如此。

2. Devres

devres 基本上是与 struct device 关联的任意大小的内存区域的链表。每个 devres 条目都与一个释放函数相关联。一个 devres 可以通过几种方式释放。无论如何,所有 devres 条目都在驱动程序分离时释放。在释放时,将调用关联的释放函数,然后释放 devres 条目。

为设备驱动程序常用的资源使用 devres 创建托管接口。例如,相干 DMA 内存使用 dma_alloc_coherent() 获取。托管版本称为 dmam_alloc_coherent()。它与 dma_alloc_coherent() 相同,不同之处在于使用它分配的 DMA 内存是托管的,并且将在驱动程序分离时自动释放。实现如下所示:

struct dma_devres {
      size_t          size;
      void            *vaddr;
      dma_addr_t      dma_handle;
};

static void dmam_coherent_release(struct device *dev, void *res)
{
      struct dma_devres *this = res;

      dma_free_coherent(dev, this->size, this->vaddr, this->dma_handle);
}

dmam_alloc_coherent(dev, size, dma_handle, gfp)
{
      struct dma_devres *dr;
      void *vaddr;

      dr = devres_alloc(dmam_coherent_release, sizeof(*dr), gfp);
      ...

      /* alloc DMA memory as usual */
      vaddr = dma_alloc_coherent(...);
      ...

      /* record size, vaddr, dma_handle in dr */
      dr->vaddr = vaddr;
      ...

      devres_add(dev, dr);

      return vaddr;
}

如果驱动程序使用 dmam_alloc_coherent(),则保证该区域在初始化半途失败或设备分离时被释放。如果大多数资源是使用托管接口获取的,则驱动程序可以具有更简单的初始化和退出代码。初始化路径基本上如下所示:

my_init_one()
{
      struct mydev *d;

      d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
      if (!d)
              return -ENOMEM;

      d->ring = dmam_alloc_coherent(...);
      if (!d->ring)
              return -ENOMEM;

      if (check something)
              return -EINVAL;
      ...

      return register_to_upper_layer(d);
}

和退出路径:

my_remove_one()
{
      unregister_from_upper_layer(d);
      shutdown_my_hardware();
}

如上所示,通过使用 devres 可以大大简化底层驱动程序。复杂性从维护较少的底层驱动程序转移到维护较好的高层。此外,由于初始化失败路径与退出路径共享,因此两者都可以获得更多测试。

但请注意,在将当前调用或赋值转换为托管 devm_* 版本时,您需要检查内部操作(如分配内存)是否失败。托管资源仅与释放这些资源有关 - 所有其他需要的检查仍然由您负责。在某些情况下,这可能意味着引入以前在移动到托管 devm_* 调用之前不需要的检查。

3. Devres 组

Devres 条目可以使用 devres 组进行分组。当释放一个组时,所有包含的普通 devres 条目和正确嵌套的组都会被释放。一个用法是在失败时回滚一系列已获取的资源。例如:

 if (!devres_open_group(dev, NULL, GFP_KERNEL))
       return -ENOMEM;

 acquire A;
 if (failed)
       goto err;

 acquire B;
 if (failed)
       goto err;
 ...

 devres_remove_group(dev, NULL);
 return 0;

err:
 devres_release_group(dev, NULL);
 return err_code;

由于资源获取失败通常意味着探测失败,因此像上面这样的结构通常在中间层驱动程序(例如,libata 核心层)中很有用,在失败时接口函数不应产生副作用。对于 LLD,在大多数情况下,只需返回错误代码就足够了。

每个组都由 void *id 标识。它可以由 @id 参数显式指定给 devres_open_group(),也可以在上面的示例中通过传递 NULL 作为 @id 来自动创建。在这两种情况下,devres_open_group() 都会返回组的 id。返回的 id 可以传递给其他 devres 函数以选择目标组。如果将 NULL 传递给这些函数,则选择最新的打开组。

例如,您可以执行以下操作:

int my_midlayer_create_something()
{
      if (!devres_open_group(dev, my_midlayer_create_something, GFP_KERNEL))
              return -ENOMEM;

      ...

      devres_close_group(dev, my_midlayer_create_something);
      return 0;
}

void my_midlayer_destroy_something()
{
      devres_release_group(dev, my_midlayer_create_something);
}

4. 详细信息

devres 条目的生命周期从 devres 分配开始,到它被释放或销毁(删除并释放)时结束 - 没有引用计数。

devres 核心保证所有基本 devres 操作的原子性,并支持单实例 devres 类型(原子查找和添加(如果未找到))。除此之外,同步对已分配 devres 数据的并发访问是调用者的责任。这通常不是问题,因为总线操作和资源分配已经完成了这项工作。

有关单实例 devres 类型的示例,请阅读 lib/devres.c 中的 pcim_iomap_table()

如果给出正确的 gfp 掩码,则可以在没有上下文的情况下调用所有 devres 接口函数。

5. 开销

每个 devres 记账信息都与请求的数据区域一起分配。在关闭调试选项的情况下,记账信息在 32 位机器上占用 16 个字节,在 64 位机器上占用 24 个字节(三个指针向上舍入到 ull 对齐)。如果使用单链表,则可以减少到两个指针(32 位上为 8 个字节,64 位上为 16 个字节)。

每个 devres 组占用 8 个指针。如果使用单链表,则可以减少到 6 个。

在天真的转换后,具有两个端口的 ahci 控制器的内存空间开销在 32 位机器上为 300 到 400 个字节之间(我们当然可以为 libata 核心层投入更多精力)。

6. 托管接口列表

时钟

devm_clk_get() devm_clk_get_optional() devm_clk_put() devm_clk_bulk_get() devm_clk_bulk_get_all() devm_clk_bulk_get_optional() devm_get_clk_from_child() devm_clk_hw_register() devm_of_clk_add_hw_provider() devm_clk_hw_register_clkdev()

DMA

dmaenginem_async_device_register() dmam_alloc_coherent() dmam_alloc_attrs() dmam_free_coherent() dmam_pool_create() dmam_pool_destroy()

DRM

devm_drm_dev_alloc()

GPIO

devm_gpiod_get() devm_gpiod_get_array() devm_gpiod_get_array_optional() devm_gpiod_get_index() devm_gpiod_get_index_optional() devm_gpiod_get_optional() devm_gpiod_put() devm_gpiod_unhinge() devm_gpiochip_add_data() devm_gpio_request() devm_gpio_request_one()

I2C

devm_i2c_add_adapter() devm_i2c_new_dummy_device()

IIO

devm_iio_device_alloc() devm_iio_device_register() devm_iio_dmaengine_buffer_setup() devm_iio_kfifo_buffer_setup() devm_iio_kfifo_buffer_setup_ext() devm_iio_map_array_register() devm_iio_triggered_buffer_setup() devm_iio_triggered_buffer_setup_ext() devm_iio_trigger_alloc() devm_iio_trigger_register() devm_iio_channel_get() devm_iio_channel_get_all() devm_iio_hw_consumer_alloc() devm_fwnode_iio_channel_get_by_name()

输入

devm_input_allocate_device()

IO 区域

devm_release_mem_region() devm_release_region() devm_release_resource() devm_request_mem_region() devm_request_free_mem_region() devm_request_region() devm_request_resource()

IOMAP

devm_ioport_map() devm_ioport_unmap() devm_ioremap() devm_ioremap_uc() devm_ioremap_wc() devm_ioremap_resource() : 检查资源,请求内存区域,ioremap devm_ioremap_resource_wc() devm_platform_ioremap_resource() : 为平台设备调用 devm_ioremap_resource() devm_platform_ioremap_resource_byname() devm_platform_get_and_ioremap_resource() devm_iounmap()

注意:对于 PCI 设备,可以使用特定的 pcim_*() 函数,请参见下文。

IRQ

devm_free_irq() devm_request_any_context_irq() devm_request_irq() devm_request_threaded_irq() devm_irq_alloc_descs() devm_irq_alloc_desc() devm_irq_alloc_desc_at() devm_irq_alloc_desc_from() devm_irq_alloc_descs_from() devm_irq_alloc_generic_chip() devm_irq_setup_generic_chip() devm_irq_domain_create_sim()

LED

devm_led_classdev_register() devm_led_classdev_register_ext() devm_led_classdev_unregister() devm_led_trigger_register() devm_of_led_get()

MDIO

devm_mdiobus_alloc() devm_mdiobus_alloc_size() devm_mdiobus_register() devm_of_mdiobus_register()

MEM

devm_free_pages() devm_get_free_pages() devm_kasprintf() devm_kcalloc() devm_kfree() devm_kmalloc() devm_kmalloc_array() devm_kmemdup() devm_krealloc() devm_krealloc_array() devm_kstrdup() devm_kstrdup_const() devm_kvasprintf() devm_kzalloc()

MFD

devm_mfd_add_devices()

MUX

devm_mux_chip_alloc() devm_mux_chip_register() devm_mux_control_get() devm_mux_state_get()

NET

devm_alloc_etherdev() devm_alloc_etherdev_mqs() devm_register_netdev()

每 CPU 内存

devm_alloc_percpu() devm_free_percpu()

PCI

devm_pci_alloc_host_bridge() : 管理的 PCI 主桥分配 devm_pci_remap_cfgspace() : ioremap PCI 配置空间 devm_pci_remap_cfg_resource() : ioremap PCI 配置空间资源

pcim_enable_device() : 成功后,某些 PCI 操作变为受管理 pcim_iomap() : 在单个 BAR 上执行 iomap() pcim_iomap_regions() : 在多个 BAR 上执行 request_region() 和 iomap() pcim_iomap_table() : 由 BAR 索引的映射地址数组 pcim_iounmap() : 在单个 BAR 上执行 iounmap() pcim_iounmap_regions() : 在多个 BAR 上执行 iounmap() 和 release_region() pcim_pin_device() : 在释放后保持 PCI 设备启用 pcim_set_mwi() : 启用内存写入无效 PCI 事务

PHY

devm_usb_get_phy() devm_usb_get_phy_by_node() devm_usb_get_phy_by_phandle() devm_usb_put_phy()

PINCTRL

devm_pinctrl_get() devm_pinctrl_put() devm_pinctrl_get_select() devm_pinctrl_register() devm_pinctrl_register_and_init() devm_pinctrl_unregister()

POWER

devm_reboot_mode_register() devm_reboot_mode_unregister()

PWM

devm_pwmchip_alloc() devm_pwmchip_add() devm_pwm_get() devm_fwnode_pwm_get()

稳压器

devm_regulator_bulk_register_supply_alias() devm_regulator_bulk_get() devm_regulator_bulk_get_const() devm_regulator_bulk_get_enable() devm_regulator_bulk_put() devm_regulator_get() devm_regulator_get_enable() devm_regulator_get_enable_read_voltage() devm_regulator_get_enable_optional() devm_regulator_get_exclusive() devm_regulator_get_optional() devm_regulator_irq_helper() devm_regulator_put() devm_regulator_register() devm_regulator_register_notifier() devm_regulator_register_supply_alias() devm_regulator_unregister_notifier()

复位

devm_reset_control_get() devm_reset_controller_register()

RTC

devm_rtc_device_register() devm_rtc_allocate_device() devm_rtc_register_device() devm_rtc_nvmem_register()

SERDEV

devm_serdev_device_open()

从 DMA 引擎

devm_acpi_dma_controller_register()

SPI

devm_spi_alloc_host() devm_spi_alloc_target() devm_spi_optimize_message() devm_spi_register_controller() devm_spi_register_host() devm_spi_register_target()

看门狗

devm_watchdog_register_device()