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 低级驱动程序开发人员很懒惰,但难道不是所有低级驱动程序开发人员都这样吗?在花费一天时间摆弄没有文档或文档损坏的硬件后,如果它最终可以工作,那么它就可以工作。

由于某种原因,低级驱动程序没有像核心代码那样受到足够的关注或测试,并且驱动程序分离或初始化失败时的错误发生频率不够高,无法引起注意。Init 失败路径更糟糕,因为它在需要处理多个入口点时,遍历的次数要少得多。

因此,许多低级驱动程序最终会在驱动程序分离时泄漏资源,并在 ->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(),则无论初始化中途失败还是设备分离,都保证释放该区域。如果大多数资源都是使用托管接口获取的,则驱动程序可以具有更简单的 init 和 exit 代码。Init 路径基本上如下所示

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);
}

和 exit 路径

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

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

但请注意,当将当前调用或赋值转换为托管的 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;

由于资源获取失败通常意味着 probe 失败,因此上述结构通常在中间层驱动程序(例如,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. 托管接口列表

CLOCK

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()

INPUT

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()

PER-CPU MEM

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_pin_device() : 在释放后保持 PCI 设备启用 pcim_set_mwi() : 启用 Memory-Write-Invalidate PCI 事务

PHY

devm_usb_get_phy() devm_usb_get_phy_by_node() devm_usb_get_phy_by_phandle()

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()

REGULATOR

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()

RESET

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()