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