четверг, 19 августа 2010 г.

USB драйвер в Linux

Введение

Драйвер устройства - это некий код, который позволяет операционной системе взаимодействовать с устройством. Ядро Linux предоставляет API для работы с физическими интерфейсами (для USB это подсистема ядра USB Core). Этот слой можно назвать транспортным уровнем. Прикладной уровень протоколов (например HID для работы с USB мышью) реализуется драйверами устройств.
В ОС Linux драйвера устройств выполняются в виде модулей ядра. Модулем ядра называют часть кода ядра, который может динамически загружаться и выгружаться во время работы ядра.

Пишем модуль

Примерный код драйвера для usb находится в файле drivers/usb/usb-skeleton.c дерева исходного кода ядра.

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

Для указания точки входа модуля ядра, используется макрос
module_init(my_driver_init);
Прототип функции инициализации модуля -
int __init my_driver_init(void);
В функции выполняется инициализация структур, регистрация устройств и т.д.
Для регистрации драйвера в системе имеется структура usb_driver, в которой указывается имя модуля, параметры работы и указатели на функции обратного вызова драйвера.
Здесь как и во многих подсистемах ядра (например в виртуальной файловой системе), используется объектно ориентированный подход.
Для нашего модуля структура будет выглядеть так:
static struct usb_driver my_driver = {
    .name  = "my_driver",
    .probe  = my_driver_probe,
    .disconnect = my_driver_disconnect,
    .id_table = my_driver_id_table
};

Поле name - имя драйвера. Оно должно быть уникальным и обычно совпадает с именем модуля.
Поле probe - указатель на функцию, которую подсистема USB ядра вызывает при подключении устройства.
Поле disconnect - указатель на функцию, которую подсистема USB ядра вызывает при отключении устройства.
Поле id_table - указатель на структуру usb_device_id (см. ниже).

Функция
my_driver_init()
в нашем случае выглядит вот так:
static int __init my_driver_init(void)
{
    int result;

    printk("My driver init");

    result = usb_register(&my_driver);
    if (result)
        err("My driver register failed with error number %d", result);

    return result;
}

В структуре usb_device_id указываются идентификаторы производителя и устройства, для которых предназначен драйвер. При подключении устройства подсистема USB ядра считывает эти идентификатор с устройства и вызывает соответствующий драйвер.
В нашем случае определение структуры будет следующим:
#define VENDOR_ID  0xfff0
#define PRODUCT_ID  0xfff0

static const struct usb_device_id my_driver_id_table[] = {
    { USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
    { }
};

В реальном драйвере вместо кода 0xfff0 должны стоять идентификаторы реального устройства, которые можно получить у производителя.

Функция probe и disconnect в нашем модуле будут выглядеть следующим образом:
static int my_driver_probe(struct usb_interface* interface, const struct usb_device_id* id)
{
    printk("My moudle probe\n");
}

static void my_driver_disconnect(struct usb_interface* interface)
{
    printk("My driver disconnect\n");
}

Для указания точки выхода используется макрос
module_exit(my_driver_exit);
Прототип функции выхода -
void __exit my_driver_exit(void)
В функции выполняется очистка ресурсов.
В нашем случае она выглядит так:
static void __exit my_driver_exit(void)
{
    printk("My driver exit");
    usb_deregister(&my_driver);
}

Этот минимальный код ничего кроме реакции на подключение устройства пока не делает. В дальнейшем я постараюсь описать процесс обмена данными с устройством.

Литература
1. Р. Лав. Разработка ядра Linux.
2. Linux device drivers 3rd edition.

1 комментарий:

  1. Здравствуйте. Отличный пост! Мне он очень помог. Хотелось бы продолжение об обмене данных с устройством. Или может быть, у вас есть какие-нибудь ссылки по этому поводу?

    ОтветитьУдалить