Драйвер устройства - это некий код, который позволяет операционной системе взаимодействовать с устройством. Ядро 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.