сложные задачи — простые решения


Start of topic | Skip to actions

Linux Unified Device Model: использование и расширение

В этой статье речь пойдет о том, как использовать и расширять UDM (Unified Device Model).

Напомню, UDM определяет несколько базовых типов данных (SysFsAndKernel26):

  • device
  • device_driver
  • device_attribute
  • bus_type
  • etc...

Как пользоваться этими структурами, если устройство и драйвер имеют много свойств и данных, которые не предусмотрены в структурах device, device_driver? Как расширять существующую модель взаимодействия драйверов и устройств? Какие в ядре существуют готовые решения, упрощающие жизнь программисту?

Встраивание базовых структур, "наследование" в C-стиле

Базовые структуры (см выше) довольно компактны и содержат в себе только самое необходимое для регистрации в связных списках и регистрации в sysfs. А как быть, если то, что мы описываем, требует большего количества данных? И, самое главное, как использовать callback'и, на которых построено взаимодействие устройств и драйверов в UDM, если они на вход принимают только указатели на базовые структуры?

Для этого в UDM предусмотрен механизм "наследования". Этот механизм основан на использовании макроса container_of, определенного в linux/kernel.h:

#define container_of(ptr, type, member)  \
({ \
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type,member) ); \
})

Рассмотрим использование container_of на примере. Для этого определим структуру, встраивающую в себя структуру device.

my_device.h:

#include <linux/device.h>

struct my_device {
  struct device dev;
  struct my_device_data data;
};

#define to_my_device(x) container_of((x), struct my_device, dev)

my_device.c:

#include "my_device.h"

struct my_device my_dev;
struct device* dev = &my_dev.dev;

int test_function( void )
{
   struct my_device* ptr = to_my_device(dev);

   return (ptr == &my_dev) ? 0 : -EFAULT;   
}

Функция test_function вернет 0 (нормальное завершение). Из примера видно, что макрос container_of позволяет, имея указатель на встроенную структуру (в примере struct device), получить указатель на структуру, расширяющую встроенную (в примере struct my_device).

Использование макроса container_of позволяет встраивать базовые структуры UDM в свои структуры, и при этом использовать те же типы для callback'ов, поскольку в callback'и передаются указатели на базовые структуры и ничто не мешает преобразовать их в указатели на производные структуры.

Продолжим пример, чтобы проиллюстрировать использование макроса container_of в callback'ах. Для этого определим переменную типа struct device_driver и callback my_device_probe.

my_device.c:

int my_device_init(struct my_device* my_dev)
{
  // ...
}

int my_device_probe(struct device* dev) 
{
  struct my_device* my_dev = to_my_device(dev);
  
  // Здесь программист должен быть очень внимателен.
  // Является ли структура (*dev) встроенной в my_device?

  return my_device_init( my_dev );
}

struct device_driver my_drv = {
  .name = "my driver",
  .probe = my_device_probe
};

Итак, мы познакомились с принципом наследования в UDM, который позволяет неограниченно расширять существующие типы данных в соответствии с требованиями конкретной задачи.

Посмотрим, как этот принцип используется в ядре linux 2.6.15 на примере расширения platform.

platform: расширение UDM.

UDM - гибкая система взаимодействия устройств и драйверов. Она хорошо подходит для поддержки hotplug-устройств (устройств "горячего" включения). Однако, есть целый класс устройств, которые присутствуют в системе на протяжении всего цикла ее жизни. Этими устройствами являются:

  • Порты ввода/вывода
  • В компактных приборах: LCD дисплей, Touchscreen, Звуковой контроллер
  • etc...

Для поддержки таких устройств в ядре linux разработано расширение для UDM: platform. platform не только определяет новые (производные от базовых) типы данных, но и расширяет возможности функций регистрации этих устройств.

Исходные тексты platform можно посмотреть в drivers/base/ и include/linux/platform_device.h

Вот как определены новые типы данных:

struct platform_device {
        const char      * name;
        u32             id;
        struct device   dev;
        u32             num_resources;
        struct resource * resource;
};

struct platform_driver {
        int (*probe)(struct platform_device *);
        int (*remove)(struct platform_device *);
        void (*shutdown)(struct platform_device *);
        int (*suspend)(struct platform_device *, pm_message_t state);
        int (*resume)(struct platform_device *);
        struct device_driver driver;
};

Видно, что platform_driver просто переопределяет callback'и так, чтобы им передавались указатели не на struct device, а на struct platform_device. А вот platform_device имеет несколько дополнительных членов:

  • name - имя устройства. Имя директории, создаваемой в sysfs при регистрации platform_device, составляется из name и id (в случае если id = -1), в результате получается директория name.id
  • id - идентификатор устройства
  • dev - наследование от struct device
  • resource, num_resource - список ресурсов, занимаемых устройством. Если два устройства пытаются занять один и тот же ресурс, второму будет отказано. Ресурсы описаны в include/linux/ioport.h

Устройство можно определить статически и динамически. В случае со статическим определением, необходимо заполнить поля структуры platform_device и вызвать platform_device_register.

Для динамического определения имеются следующие функции:

extern struct platform_device *platform_device_register_simple(char *, unsigned int, struct resource *, unsigned int);

extern struct platform_device *platform_device_alloc(const char *name, unsigned int id);
extern int platform_device_add_resources(struct platform_device *pdev, struct resource *res, unsigned int num);
extern int platform_device_add_data(struct platform_device *pdev, void *data, size_t size);
extern int platform_device_add(struct platform_device *pdev);
extern void platform_device_put(struct platform_device *pdev);

Рассмотрим подробнее регистрацию устройства:

  • platform_device_register_simple - простая регистрация. Динамически выделяет память, заполняет поля id, name, resource. Основной минус - не позволяет заполнять другие поля, которые необходимо заполнять перед регистрацией устройства (такие как dev.parent)
  • platform_device_alloc - выделяет память под platform_device. Заполняет поля name, id, dev.bus_id, dev.release...
  • platform_device_add_resources - добавляет список ресурсов к устройству, созданному при помощи platform_device_alloc
  • platform_device_add_data - выделяет блок памяти размером size, копирует в этот блок данные из data, указатель на выделенный блок записывает в dev->platform_data.
  • platform_device_add - регистрирует устройство, созданное при помощи platform_device_alloc.

Удаление устройства выполняется при помощи platform_device_put. platform_device_put использует еще один важный принцип модели UDM. Вызов этой функции понижает на 1 счетчик обращений (reference counter) к platform_device. Когда счетчик становится равным нулю, вызывается функция по указателю dev.release, который инициализируется внутри функций platform_device_register_simple & platform_device_alloc. В функции release выполняется дерегистрация устройства и освобождение памяти.

Заключение

UDM представляет собой масштабируемую систему взаимодействия устройств и драйверов. platform является хорошей иллюстрацией решения, основанного на идеологии и архитектуре UDM.

Какие плюсы имеет использование platform_device & platform_driver:

  • дополнительные поля в структуре и алгоритмы их использования: resource и их регистрация, формирование имени из name + id, ...
  • нет необходимости создавать шину (bus_type)
  • удобный механизм динамического выделения/освобождения памяти под platform_device.

Нужно отметить, что в некоторых случаях достаточно использовать поле platform_data в структуре. Например, для какой-то задачи нужно использовать struct device, но для устройства требуется хранить большое количество данных. Можно использовать два метода:

  1. наследование. см. выше
  2. выделить память под структуру данных устройства и указатель на эту структуру сохранить в device->platform_data. В случае использования platform_device этот метод не подходит, так как он задействован системой platform.

Литература, ссылки

  1. http://www.kernel.org
  2. заголовочные файлы ядра linux:
    • include/linux/device.h
    • include/linux/platform_device.h
    • include/linux/ioport.h
  3. исходный код ядра linux:
    • drivers/base/platform.c
    • drivers/base/core.c

Павел Курочкин, НТЦ Метротек

© b4open, 2006