Сделаем код чище: Пару слов об управляемых ресурсах в ядре Linux для драйверов устройств

в 12:43, , рубрики: C, linux kernel, linux kernel development, open source, написание драйверов, сделаем код чище, системное программирование, метки: ,

Наблюдая за появляющимися драйверами в ядре Linux, не могу не отметить, что разработчики недостаточно хорошо знают инфраструктуру ядра, точнее внутренний API, значительно упрощающий жизнь при написании драйверов устройств. Сегодня я коснусь темы, посвящённой управляемым ресурсам. В частности поясню каким образом они работают и как упрощают разработку драйверов.

Условно разобью предоставляемый API на:

  • Память, в том числе выделяемая для DMA и ввода-вывода
  • Прерывания
  • Регуляторы напряжения
  • Для драйверов на шинах (PCI, SPI, IIO)
  • GPIO, Pin control
  • Остальное

В каждой из категорий я приведу список доступных функций и в случае необходимости дополнительную информацию, на которую стоит обратить внимание. Но вначале кратко расскажу принцип как устроено управление этими ресурсами.

Управление ресурсами для устройств

В далёком 2007 году Tejun Heo предложил упростить драйвера устройств с помощью управляемых ресурсов устройств, создав так называемое devres API.

Случай в 2007

commit 9ac7849e35f705830f7b016ff272b0ff1f7ff759
Author: Tejun Heo <htejun@gmail.com>

devres: device resource management

Идея заключается в следующем. Каждому устройству соответствует его представление, struct device, в дереве устройств ядра Linux. В структуру было добавлено поле devres_head (на самом деле ещё одно для синхронизации доступа), которое указывает на список управляемых ресурсов, если таковые были использованы на стадии ->probe(). Управляемый ресурс представляет собой выделенную память на куче ассоциированную с некоторой структурой данных, в случае kmalloc(), например, это просто указатель void *.

На этапе ->probe(), как я уже говорил, выполняется пополнение списка управляемых ресурсов, на стадии же ->remove() базовая часть обслуживания драйверов сама позаботится о высвобождении всех занятых ресурсов ровно в порядке обратном их добавлению.

Для исключительных случаев можно вызвать соответствующее высвобождение раньше и явно. Здесь же кроется и проблема, которую мы разберём на примере в следующей части.

Память, в том числе выделяемая для DMA и ввода-вывода

Для выделения памяти можно использовать следующие основные функции:

devm_kasprintf()
devm_kcalloc()
devm_kmalloc()
devm_kmemdup()
devm_kstrdup()
devm_kzalloc()

Предназначение каждой по-моему очевидно, останавливаться не буду на этом. Перейдём к примеру использования.

Допустим, мы выделяем память для нашей структуры и хотим, чтобы она автоматически высвободилась, по окончании работы драйвера.

struct my_cool_device {
  struct device *dev;
  void *memregion;
};

int mydev_probe(...)
{
  struct device *dev = ...;
  struct my_cool_device *cool;

  cool = devm_kzalloc(dev, ...);
  if (!cool)
    return -ENOMEM;

  cool->memregion = devm_kmalloc(dev, ...);
  if (!cool->memregion)
    return -ENOMEM;

  cool->dev = dev;

  dev_info(dev, "Found my cool devicen");
  return 0;
}

void mydev_remove(...)
{
  /* Nothing! */
}

Обратили внимание как красиво у нас выглядит путь выхода при возникновении ошибки в середине функции? И как замечательно выглядит ->remove()!

Теперь примеры как высвободить ресурс, если это по какой-то причине необходимо сделать явно.
Правильно:

void mydev_remove(...)
{
  struct device *dev = ...;
  struct my_cool_device *cool = ...;
  devm_kfree(dev, cool->memregion);
}

Неправильно:

void mydev_remove(...)
{
  struct my_cool_device *cool = ...;
  kfree(cool->memregion);
}

Для выделения памяти, подходящей для DMA используйте

dmam_alloc_coherent()
dmam_alloc_noncoherent()
dmam_pool_create()

Ресурсы ввода-вывода можно выделить используя нижеследующие функции

devm_ioport_map()
devm_ioremap()
devm_ioremap_resource(): проверяет ресурс, запрашивает регион памяти, проецирует его на физические адреса устройства
pcim_iomap()
pcim_iomap_regions(): запрашивает регион и проецирует на него физические адреса указанные в требуемых BAR-ах
pcim_iomap_table(): массив спроецированных адресов, индексируемый по номеру BAR-а

Обратите внимание, что нижняя часть относится к устройствам на шине PCI, но при этом вынесена в категорию ввода-вывода.

Прерывания

devm_request_any_context_irq()
devm_request_irq()
devm_request_threaded_irq()

Регуляторы напряжения

devm_regulator_bulk_get()
devm_regulator_get()
devm_regulator_register()

Для драйверов на шинах (PCI, SPI, IIO)

Шина IIO:

devm_iio_device_alloc()
devm_iio_device_register()
devm_iio_kfifo_allocate()
devm_iio_trigger_alloc()

Шина MDIO:

devm_mdiobus_alloc()
devm_mdiobus_alloc_size()

Шина PCI:

pcim_enable_device(): в случае успешного завершения все операции подразумеваются управляемыми
pcim_pin_device(): оставить устройство разрешённым после высвобождения

Особенно обратите внимание на pcim_enable_device(). Однажды применив её необходимо вычистить высвобождение регионов, даже если их выделение и проецирование осуществляется старым способом. Например, можно посмотреть детали в commit 5618955c4269.

Шина SPI:

devm_spi_register_master()

GPIO, Pin control

GPIO:

devm_gpiod_get()
devm_gpiod_get_index()
devm_gpiod_get_index_optional()
devm_gpiod_get_optional()

Pin control:

devm_pinctrl_get()

Остальное

Я затронул лишь наиболее часто встречаемые и используемые функции-помощники для управления ресурсами. Об остальном можно почитать в соответствующей документации на API ядра: devres.txt.

Автор: andy_shev

Источник

Поделиться новостью

* - обязательные к заполнению поля