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
- 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()- 输入
- 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 内存
- 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()