Разработка HID-драйвера

В статье приведён обзор общего подхода к разработке HID-драйверов


Note: В публичном репозитории можно изучить реализацию опубликованных примеров реальных драйверов.

Список подразделов:

Создание модуля драйвера
Инициализация модуля драйвера: io_hid_dll_entry_t :: init()
Денициализация модуля драйвера: io_hid_dll_entry_t :: shutdown()
Создание интерфейса драйвера
io_hid_registrant_funcs_t :: client_attach()
io_hid_registrant_funcs_t :: client_detach()
io_hid_registrant_funcs_t :: rbuffer_alloc()
io_hid_registrant_funcs_t :: rbuffer_free()
io_hid_registrant_funcs_t :: report_read()
io_hid_registrant_funcs_t :: report_write()
io_hid_registrant_funcs_t :: get_idle()
io_hid_registrant_funcs_t :: set_idle()
io_hid_registrant_funcs_t :: get_protocol()
io_hid_registrant_funcs_t :: set_protocol()
io_hid_registrant_funcs_t :: string()
io_hid_registrant_funcs_t :: indexed_string()
io_hid_registrant_funcs_t :: reset()
io_hid_registrant_funcs_t :: reserved()

Создание модуля драйвера

При разработке драйвера в первую очередь следует создать модуль драйвера. Он описывается структурой io_hid_dll_entry_t.

Пример:

io_hid_dll_entry_t io_hid_dll_entry = {
"devh-sample",
2,
sample_init,
sample_stop
};

Инициализация модуля драйвера: io_hid_dll_entry_t :: init()

Вызывается однократно при запуске драйвера. io-hid отправляет запрос INIT с аргументами: дескриптор модуля драйвера (ioh_dll), дескриптор структуры диспетчера (dpp), дескриптор структуры вызовов (ioh) и переданные аргументы (options). Следует инициализировать глобальные указатели для первых трех аргументов, т.к. их использование пригодится в дальнешей реализации драйвера. Так же необходимо сделать:

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

Полное описание прототипа функции и кодов возврата описано на странице io_hid_dll_entry_t :: init().


Note: Дескриптор интерфейса драйвера см. в Создание интерфейса драйвера.

Пример:

static unsigned char smaple_rdesc[] = {
0x05, 0x01,
0x09, 0x01,
0xa1, 0x01,
0x09, 0x01,
/* ... */
0xc0,
0xc0
};
typedef struct _sample_ctrl {
/* Другие поля */
/* ... */
void *dll_hdl;
io_hid_self_t *ioh;
} sample_ctrl_t;
typedef struct _sample {
/* Другие поля дескриптора отчета */
/* ... */
int hid_hdl;
} sample_t;
sample_ctrl_t SAMPLECtrl;
int sample_get_report_descriptor (sample_t* sample)
{
/* Структуры и переменные, которые необходимы для регистрации устройства в io-hid */
hidd_device_ident_t device_ident;
io_hid_registrant_t dev;
int sample_hdl;
int result = EOK;
/* Получение информации для заполения hidd_device_ident_t */
/* ... */
device_ident.vendor_id = /* ... */ ;
device_ident.product_id = /* ... */ ;
device_ident.version = /* ... */ ;
dev.flags = 0;
/* Инициализация информации об устройстве */
dev.device_ident = &device_ident;
/* Инициализация строкового дескриптора */
dev.desc = sample_rdesc;
dev.dlen = sizeof (sample_rdesc);
/* Инициализация дескриптора отчета */
dev.user_hdl = sample;
/* Инициализация дескриптора интерфейсa драйвера */
dev.funcs = &sample_funcs;
/* Регистрация дескриптора модуля драйвера в io-hid */
if ( (result = (*SAMPLECtrl.ioh->reg) (SAMPLECtrl.dll_hdl, &dev, &sample_hdl)) == EOK )
sample->hid_hdl = sample_hdl;
return (result);
}
char *sample_opts[] = {
/* Поддерживаемые опции */,
NULL
};
int sample_start_driver (char *options)
{
int status = EOK;
while (options && *options != '\0') {
switch (getsubopt (&options, sample_opts, &value)) {
/* Обработка опций */
/* ... */
}
}
return (status);
}
void *sample_event_handler_polling (sample_t* sample)
{
/* Обработка прерываний / приема данных */
/* ... */
if ( (*SAMPLECtrl.ioh->get_buffer)(sample->hid_hdl, &sample->rbuffer) != EOK ) {}
/* Отправление отчета с обработанными данными */
(*SAMPLECtrl.ioh->send_report) (sample->hid_hdl, (_uint8 *) sample->rbuffer, (_uint32) packet_len);
}
int sample_init (void *dll_hdl, dispatch_t *dpp, io_hid_self_t *ioh, char *options)
{
/* Инициализация дескрипторов */
SAMPLECtrl.dll_hdl = dll_hdl;
SAMPLECtrl.ioh = ioh;
sample_t sample;
if ( sample_start_driver (options) != EOK ) {
fprintf (stderr, "error starting SAMPLE HID class driver\n");
return (EXIT_FAILURE);
}
sample_get_report_descriptor (&sample_t);
/* Инициализация потока обработки прерываний / приема данных */
if ( pthread_create( NULL, NULL, (void *)sample_event_handler_polling, &sample ) != EOK ) {
fprintf (stderr, "error starting SAMPLE HID class driver\n");
return (EXIT_FAILURE);
}
return (EOK);
}

Денициализация модуля драйвера: io_hid_dll_entry_t :: shutdown()

Вызывается при завершении работы драйвера. Следует осовободить выделенные в io_hid_dll_entry_t :: init() ресурсы. Принимает в качестве аргумента дескриптор модуля драйвера (dll_hdl).

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

Полное описание прототипа функции и кодов возврата описано на странице io_hid_dll_entry_t :: shutdown().

Пример:

int sample_stop (void *dll_hdl)
{
/* Освобождение ресурсов */
/* ... */
return (EOK);
}

Создание интерфейса драйвера

Дескриптор интерфейса драйвера представляет собой набор функций-обработчиков, которые могут быть выполнены на устройстве по обращению клиента. Фактическая реализация этих функций будет зависеть от контекста и конкретных требований драйвера или HID-библиотеки, с которыми вы работаете.

Полное описание прототипа интерфейса описано на странице io_hid_registrant_funcs_t.

Пример:

#include <sys/io-hid.h>
io_hid_registrant_funcs_t sample_funcs = {
_IO_HID_REG_NFUNCS,
sample_client_attach,
sample_client_detach,
sample_rbuffer_alloc,
sample_rbuffer_free,
sample_report_read,
sample_report_write,
sample_get_idle,
sample_set_idle,
sample_get_protocol,
sample_set_protocol,
sample_get_string,
sample_get_indexed_string,
sample_reset,
};

io_hid_registrant_funcs_t :: client_attach()

Вызывается однократно при загрузке клиента драйвера. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl) и дескриптор отчета (user). В данной функции следует описать проверку успешной регистрации модуля драйвера вызовом get_buffer в io_hid_self_t и при необходимости выполнить действия для проверки активности устройства.

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


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: client_attach().

Пример:

int sample_check_device (void)
{
/* ... */
}
int sample_client_attach (int reg_hdl, void *user)
{
sample_t *sample = user;
int status;
/* ... */
/* Проверка, что инициализация дескриптора модуля драйвера была успешно завершена */
if ( (*SAMPLECtrl.ioh->get_buffer) (sample->hid_hdl, &sample->rbuffer) != EOK ) {}
/* ... */
/* Девайс специфичные дополнения для проверки, что устройство подключено */
if ( sample_check_device() != EOK ) {
return (EXIT_FAILURE);
}
return (EOK);
}

io_hid_registrant_funcs_t :: client_detach()

Вызывается при завершении работы клиента драйвера. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl) и дескриптор отчета (user). Следует освободить выделенные ресурсы.

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


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: client_detach().

Пример:

int sample_client_detach (int reg_hdl, void *user)
{
sample_t *sample = user;
/* Освобождение ресурсов */
/* ... */
return (EOK);
}

io_hid_registrant_funcs_t :: rbuffer_alloc()

Выделяет память под буфер отчета указанного размера и возвращает указатель на него. Принимает в качестве аргументов: размер (size) и указатель буфера отчета (bptr).

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное выделение памяти, а код ошибки указывает на возможные проблемы при выполнении операции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: rbuffer_alloc().

Пример:

int sample_rbuffer_alloc (_uint16 size, void **bptr)
{
if ( (*bptr = malloc( size )) == NULL )
return (ENOMEM);
memset ( *bptr, 0xff, size );
return (EOK);
}

io_hid_registrant_funcs_t :: rbuffer_free()

Освобождает выделенную пямять под буфер отчета. Принимает в качестве аргумента указатель на буфера отчета (bptr).

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное освобождение памяти, а код ошибки указывает на возможные проблемы при выполнении операции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: rbuffer_free().

Пример:

int sample_rbuffer_free (void *bptr)
{
free (bptr);
return (EOK);
}

io_hid_registrant_funcs_t :: report_read()

Позволяет получить клиенту отчет с устройства. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl), дескриптор отчета (user), идентификатор отчета (rid), тип отчета (rtype), указатель на дескриптор отчета (rdata) и размер отчета (rlen). Функция предоставляет интерфейс для чтения отчетов с устройства, поддерживающего HID протокол. Она принимает идентификатор зарегистрированный дескриптор модуля драйвера, дескриптор отчета, идентификатор и тип отчета, а также буфер и его размер для хранения прочитанных данных.

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное чтение, а код ошибки указывает на возможные проблемы при выполнении операции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: report_read().

Пример:

int sample_report_read (int reg_hdl, void *user, _uint8 rid, _uint16 rtype, void *rdata, _uint16 rlen)
{
/* Получение отчета в соответствии с идентификатором и типом */
/* ... */
return (EOK);
}

io_hid_registrant_funcs_t :: report_write()

Позволяет отправить клиенту отчет на устройство, устанавливая состояние элементов управления вводом, выводом или функциями. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl), дескриптор отчета (user), идентификатор отчета (rid), тип отчета (rtype), указатель на дескриптор отчета (rdata) и размер отчета (rlen). Необходимо реализовать обработку отчетов.

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешную отправку отчета, а код ошибки указывает на возможные проблемы при выполнении операции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

ы

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: report_write().

Пример:

int sample_report_write (int reg_hdl, void *user, _uint8 rid, _uint16 rtype, void *rdata, _uint16 rlen)
{
sample_t *sample = user;
sample_data_packet_t *data = (sample_data_packet_t *) rdata;
switch (data->cmd) {
/* Обработка полученного отчета */
/* ... */
default:
fprintf (stderr, "Received an unknown command from somewhere\n");
break;
}
return (EOK);
}

io_hid_registrant_funcs_t :: get_idle()

Считывает текущую частоту простоя для конкретного входного отчета. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl), дескриптор отчета (user), идентификатор отчета (rid) и частоту опроса (idle_rate). Необходимо присвоить соответствующую частоту простоя входному аргументу idle_rate для rid отчета. Установить @ idle_rate равной 0, если не используется PROTOCOL_BOOT.

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное чтение, а код ошибки указывает на возможные проблемы при выполнении тела функции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: get_idle().

Пример:

int sample_get_idle (int reg_hdl, void *user, _uint8 rid, _uint16 *idle_rate)
{
/* Обработка идетификатора отчета и установка соответствующей частоты опроса */
/* ... */
return (EOK);
}

io_hid_registrant_funcs_t :: set_idle()

Задает частоту опроса отчета с идетификатором rid. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl), дескриптор отчета (user), идентификатор отчета (rid) и частоту опроса (idle_rate). Используется для ограничения передачи отчетов клиенту.

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное выполнение, а код ошибки указывает на возможные проблемы при выполнении тела функции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: set_idle().

Пример:

int sample_set_idle (int reg_hdl, void *user, _uint8 rid, _uint16 idle_rate)
{
/* Обработка идетификатора отчета и получение соответствующей частоты опроса */
/* ... */
return (EOK);
}

io_hid_registrant_funcs_t :: get_protocol()

Возвращает поддерживаемый драйвером протокол. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl), дескриптор отчета (user) и идентификатор протокола (protocol).

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное выполнение, а код ошибки указывает на возможные проблемы при выполнении тела функции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: get_protocol().

Пример:

int sample_get_protocol (int reg_hdl, void *user, _uint8 *protocol)
{
/* Присвоить идентификатор протокола */
protocol = SAMPLECtrl.protocol;
return (EOK);
}

io_hid_registrant_funcs_t :: set_protocol()

Устанавливает протокол используемый клиентом. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl), дескриптор отчета (user) и идентификатор протокола (protocol).

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное выполнение, а код ошибки указывает на возможные проблемы при выполнении тела функции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: set_protocol().

Пример:

int sample_set_protocol (int reg_hdl, void *user, _uint8 protocol)
{
/* Присвоить полученный идентификатор протокола */
SAMPLECtrl.protocol = protocol;
return (EOK);
}

io_hid_registrant_funcs_t :: string()

Получает строковый дескриптор от устройства. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl), дескриптор отчета (user), тип строкового дескриптора (request) и указатель на строковый дескрпитор (string). Входной аргумент request используется для запроса конкретного строкового дескриптора, а в string сохраняется результат: строковый дескриптор устройства.

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное выполнение, а код ошибки указывает на возможные проблемы при выполнении тела функции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: string().

Пример:

int sample_get_string (int reg_hdl, void *user, _uint16 request, void **string)
{
sample_t *sample = user;
sampled_device_descriptor_t *device_desc;
/* Получение строкового дескриптора от устройства */
/* ... */
/* Обработка какой строковый дескриптор надо вернуть */
switch (request) {
case 1:
*string = /* ... */ ;
break;
/* ... */
default:
return (ENOENT);
}
return (*string ? EOK : ENOENT);
}

io_hid_registrant_funcs_t :: indexed_string()

Устанавливает строковый дескриптор устройства. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl), дескриптор отчета (user), индекс строкового дескриптора (index) и указатель на строковый дескрпитор (string). Входной аргумент index указывает, какой строковый дескриптор требуется установить в string.

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное выполнение, а код ошибки указывает на возможные проблемы при выполнении тела функции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: indexed_string().

Пример:

int sample_get_indexed_string (int reg_hdl, void *user, _uint16 index, void **string)
{
sample_t *sample = user;
/* Получение строкового дескриптора устройства */
*string = sampled_string (sample->device, index, 0);
return (*string ? EOK : ENOENT);
}

io_hid_registrant_funcs_t :: reset()

Используется для сброса состояния модуля или устройства. Принимает в качестве аргументов: зарегистрированный дескриптор модуля драйвера (reg_hdl) и дескриптор отчета (user). Драйвер может вызывать этот callback при любых неисправностях.

Результат вызова функции отображается возвращаемым значением, где EOK указывает на успешное выполнение, а код ошибки указывает на возможные проблемы при выполнении тела функции.


Note: Можно ограничиться возвратом кода EOK, не выполняя полную реализацию функции обработчика.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: reset().

Пример:

int sample_device_reset (sample_t *sample)
{
int status = EOK;
/* Выполнить сброс устройства и состояния драйвера */
/* ... */
return (status);
}
int sample_reset (int reg_hdl, void *user)
{
sample_t *sample = user;
return (sample_device_reset (sample));
}

io_hid_registrant_funcs_t :: reserved()

Реализация необязательна.

Полное описание прототипа функции и кодов возврата описано на странице io_hid_registrant_funcs_t :: reserved().




Предыдущий раздел: Библиотека разработки HID-драйверов