2023-08-30 17:31:07 +02:00
|
|
|
================================
|
|
|
|
Devres - Managed Device Resource
|
|
|
|
================================
|
|
|
|
|
|
|
|
Tejun Heo <teheo@suse.de>
|
|
|
|
|
|
|
|
First draft 10 January 2007
|
|
|
|
|
|
|
|
.. contents
|
|
|
|
|
|
|
|
1. Intro : Huh? Devres?
|
|
|
|
2. Devres : Devres in a nutshell
|
|
|
|
3. Devres Group : Group devres'es and release them together
|
|
|
|
4. Details : Life time rules, calling context, ...
|
|
|
|
5. Overhead : How much do we have to pay for this?
|
|
|
|
6. List of managed interfaces: Currently implemented managed interfaces
|
|
|
|
|
|
|
|
|
|
|
|
1. Intro
|
|
|
|
--------
|
|
|
|
|
|
|
|
devres came up while trying to convert libata to use iomap. Each
|
|
|
|
iomapped address should be kept and unmapped on driver detach. For
|
|
|
|
example, a plain SFF ATA controller (that is, good old PCI IDE) in
|
|
|
|
native mode makes use of 5 PCI BARs and all of them should be
|
|
|
|
maintained.
|
|
|
|
|
|
|
|
As with many other device drivers, libata low level drivers have
|
|
|
|
sufficient bugs in ->remove and ->probe failure path. Well, yes,
|
|
|
|
that's probably because libata low level driver developers are lazy
|
|
|
|
bunch, but aren't all low level driver developers? After spending a
|
|
|
|
day fiddling with braindamaged hardware with no document or
|
|
|
|
braindamaged document, if it's finally working, well, it's working.
|
|
|
|
|
|
|
|
For one reason or another, low level drivers don't receive as much
|
|
|
|
attention or testing as core code, and bugs on driver detach or
|
|
|
|
initialization failure don't happen often enough to be noticeable.
|
|
|
|
Init failure path is worse because it's much less travelled while
|
|
|
|
needs to handle multiple entry points.
|
|
|
|
|
|
|
|
So, many low level drivers end up leaking resources on driver detach
|
|
|
|
and having half broken failure path implementation in ->probe() which
|
|
|
|
would leak resources or even cause oops when failure occurs. iomap
|
|
|
|
adds more to this mix. So do msi and msix.
|
|
|
|
|
|
|
|
|
|
|
|
2. Devres
|
|
|
|
---------
|
|
|
|
|
|
|
|
devres is basically linked list of arbitrarily sized memory areas
|
|
|
|
associated with a struct device. Each devres entry is associated with
|
|
|
|
a release function. A devres can be released in several ways. No
|
|
|
|
matter what, all devres entries are released on driver detach. On
|
|
|
|
release, the associated release function is invoked and then the
|
|
|
|
devres entry is freed.
|
|
|
|
|
|
|
|
Managed interface is created for resources commonly used by device
|
|
|
|
drivers using devres. For example, coherent DMA memory is acquired
|
|
|
|
using dma_alloc_coherent(). The managed version is called
|
|
|
|
dmam_alloc_coherent(). It is identical to dma_alloc_coherent() except
|
|
|
|
for the DMA memory allocated using it is managed and will be
|
|
|
|
automatically released on driver detach. Implementation looks like
|
|
|
|
the following::
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
If a driver uses dmam_alloc_coherent(), the area is guaranteed to be
|
|
|
|
freed whether initialization fails half-way or the device gets
|
|
|
|
detached. If most resources are acquired using managed interface, a
|
|
|
|
driver can have much simpler init and exit code. Init path basically
|
|
|
|
looks like the following::
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
And exit path::
|
|
|
|
|
|
|
|
my_remove_one()
|
|
|
|
{
|
|
|
|
unregister_from_upper_layer(d);
|
|
|
|
shutdown_my_hardware();
|
|
|
|
}
|
|
|
|
|
|
|
|
As shown above, low level drivers can be simplified a lot by using
|
|
|
|
devres. Complexity is shifted from less maintained low level drivers
|
|
|
|
to better maintained higher layer. Also, as init failure path is
|
|
|
|
shared with exit path, both can get more testing.
|
|
|
|
|
|
|
|
Note though that when converting current calls or assignments to
|
|
|
|
managed devm_* versions it is up to you to check if internal operations
|
|
|
|
like allocating memory, have failed. Managed resources pertains to the
|
|
|
|
freeing of these resources *only* - all other checks needed are still
|
|
|
|
on you. In some cases this may mean introducing checks that were not
|
|
|
|
necessary before moving to the managed devm_* calls.
|
|
|
|
|
|
|
|
|
|
|
|
3. Devres group
|
|
|
|
---------------
|
|
|
|
|
|
|
|
Devres entries can be grouped using devres group. When a group is
|
|
|
|
released, all contained normal devres entries and properly nested
|
|
|
|
groups are released. One usage is to rollback series of acquired
|
|
|
|
resources on failure. For example::
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
As resource acquisition failure usually means probe failure, constructs
|
|
|
|
like above are usually useful in midlayer driver (e.g. libata core
|
|
|
|
layer) where interface function shouldn't have side effect on failure.
|
|
|
|
For LLDs, just returning error code suffices in most cases.
|
|
|
|
|
|
|
|
Each group is identified by `void *id`. It can either be explicitly
|
|
|
|
specified by @id argument to devres_open_group() or automatically
|
|
|
|
created by passing NULL as @id as in the above example. In both
|
|
|
|
cases, devres_open_group() returns the group's id. The returned id
|
|
|
|
can be passed to other devres functions to select the target group.
|
|
|
|
If NULL is given to those functions, the latest open group is
|
|
|
|
selected.
|
|
|
|
|
|
|
|
For example, you can do something like the following::
|
|
|
|
|
|
|
|
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. Details
|
|
|
|
----------
|
|
|
|
|
|
|
|
Lifetime of a devres entry begins on devres allocation and finishes
|
|
|
|
when it is released or destroyed (removed and freed) - no reference
|
|
|
|
counting.
|
|
|
|
|
|
|
|
devres core guarantees atomicity to all basic devres operations and
|
|
|
|
has support for single-instance devres types (atomic
|
|
|
|
lookup-and-add-if-not-found). Other than that, synchronizing
|
|
|
|
concurrent accesses to allocated devres data is caller's
|
|
|
|
responsibility. This is usually non-issue because bus ops and
|
|
|
|
resource allocations already do the job.
|
|
|
|
|
|
|
|
For an example of single-instance devres type, read pcim_iomap_table()
|
|
|
|
in lib/devres.c.
|
|
|
|
|
|
|
|
All devres interface functions can be called without context if the
|
|
|
|
right gfp mask is given.
|
|
|
|
|
|
|
|
|
|
|
|
5. Overhead
|
|
|
|
-----------
|
|
|
|
|
|
|
|
Each devres bookkeeping info is allocated together with requested data
|
|
|
|
area. With debug option turned off, bookkeeping info occupies 16
|
|
|
|
bytes on 32bit machines and 24 bytes on 64bit (three pointers rounded
|
|
|
|
up to ull alignment). If singly linked list is used, it can be
|
|
|
|
reduced to two pointers (8 bytes on 32bit, 16 bytes on 64bit).
|
|
|
|
|
|
|
|
Each devres group occupies 8 pointers. It can be reduced to 6 if
|
|
|
|
singly linked list is used.
|
|
|
|
|
|
|
|
Memory space overhead on ahci controller with two ports is between 300
|
|
|
|
and 400 bytes on 32bit machine after naive conversion (we can
|
|
|
|
certainly invest a bit more effort into libata core layer).
|
|
|
|
|
|
|
|
|
|
|
|
6. List of managed interfaces
|
|
|
|
-----------------------------
|
|
|
|
|
|
|
|
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 region
|
|
|
|
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() : checks resource, requests memory region, ioremaps
|
|
|
|
devm_ioremap_resource_wc()
|
|
|
|
devm_platform_ioremap_resource() : calls devm_ioremap_resource() for platform device
|
|
|
|
devm_platform_ioremap_resource_byname()
|
|
|
|
devm_platform_get_and_ioremap_resource()
|
|
|
|
devm_iounmap()
|
|
|
|
pcim_iomap()
|
|
|
|
pcim_iomap_regions() : do request_region() and iomap() on multiple BARs
|
|
|
|
pcim_iomap_table() : array of mapped addresses indexed by BAR
|
|
|
|
pcim_iounmap()
|
|
|
|
|
|
|
|
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()
|
2023-10-24 12:59:35 +02:00
|
|
|
devm_krealloc_array()
|
2023-08-30 17:31:07 +02:00
|
|
|
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() : managed PCI host bridge allocation
|
|
|
|
devm_pci_remap_cfgspace() : ioremap PCI configuration space
|
|
|
|
devm_pci_remap_cfg_resource() : ioremap PCI configuration space resource
|
|
|
|
pcim_enable_device() : after success, all PCI ops become managed
|
|
|
|
pcim_pin_device() : keep PCI device enabled after release
|
|
|
|
|
|
|
|
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_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_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()
|
|
|
|
|
|
|
|
SLAVE DMA ENGINE
|
|
|
|
devm_acpi_dma_controller_register()
|
|
|
|
devm_acpi_dma_controller_free()
|
|
|
|
|
|
|
|
SPI
|
|
|
|
devm_spi_alloc_master()
|
|
|
|
devm_spi_alloc_slave()
|
|
|
|
devm_spi_register_master()
|
|
|
|
|
|
|
|
WATCHDOG
|
|
|
|
devm_watchdog_register_device()
|