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