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