Virtual hid device что это
Перейти к содержимому

Virtual hid device что это

Creating a virtual HID device

I’d like to create a virtual HID device (emulate it with a driver).

It must be visible to clients that implement standard HID detection:

  • Call HidD_GetHidGuid() – Get the HID device class GUID
  • Call SetupDiGetClassDevs() – Get a handle to a set of devices which implement the HID interface
  • Call SetupDiEnumDeviceInterfaces() – For each device in the returned set of devices, obtain the interface information for all exposed HID interfaces.
  • Call SetupDiGetDeviceInterfaceDetail() – For each interface obtained in the previous call, get the detailed information block for that interface. This detailed information includes the string that can be passed to CreateFile() to open a handle to the device
  • Call SetupDiDestroyDeviceInfoList() – Free up the device information set that was obtained in the call to SetupDiGetClassDevs().

The device should also support reading, so CreateFile / ReadFile would return data supplied by me from the driver.

I don’t really know where to begin, as I don’t have a lot of exp. in kernel dev. 🙁

Создание виртуального HID-устройства

Я хотел бы создать виртуальное HID-устройство (эмулировать его с помощью драйвера).

Он должен быть виден клиентам, реализующим стандартное обнаружение HID:

  • Вызов HidD_GetHidGuid() — получение GUID класса устройства HID.
  • Вызов SetupDiGetClassDevs() — получение дескриптора набора устройств, которые реализуют интерфейс HID.
  • Вызовите SetupDiEnumDeviceInterfaces() — для каждого устройства в возвращаемом наборе устройств получите информацию об интерфейсе для всех открытых HID-интерфейсов.
  • Вызов SetupDiGetDeviceInterfaceDetail() — для каждого интерфейса, полученного в предыдущем вызове, получить блок подробной информации для этого интерфейса. Эта подробная информация включает в себя строку, которую можно передать в CreateFile(), чтобы открыть дескриптор устройства.
  • Вызов SetupDiDestroyDeviceInfoList() — освобождает набор сведений об устройстве, полученный при вызове SetupDiGetClassDevs().

Устройство также должно поддерживать чтение, чтобы CreateFile/ReadFile возвращали данные, предоставленные мной из драйвера.

Я действительно не знаю, с чего начать, так как у меня мало опыта. в разработчике ядра. 🙁

Работа с устройством USB HID в ОС Windows через Windows API

В прошлой статье я рассказывал как сделать устройство USB HID на микроконтроллере stm32. Но, как вы понимаете, сделать устройство — это только половина дела, вторая половина — как с этим устройством потом работать. Сегодняшняя статья как раз будет посвящена тому, как работать с устройством USB HID из операционной системы Windows с использованием Windows API. Кстати, наиболее полное и толковое описание работы с USB (не только с классом HID) можно найти в книге от Jan Axelson — «USB Complete». Это вообще очень мощная тётка, шикарно пишет (у неё есть книги не только про USB), жаль только нет переводов этих книг с английского на русский (по крайней мере я не нашёл), но и так тоже пойдёт.

В качестве подопытного устройства для тестов я буду использовать как раз USB HID устройство на микроконтроллере stm32 из статьи, ссылка на которую дана выше.

Приведённый в статье пример написан на С++ Builder 10 (из Embarcadero RAD Studio). Для работы с исходниками, на компьютере помимо RAD Studio должен быть установлен Windows DDK (у меня установлен Windows Driver Development Kit Version 7.1.0). Вот ссылка на статью о том, как «подружить» C++ Builder и Windows DDK.

В ОС Windows при подключении устройства в систему, операционка выясняет что это за устройство, к какому оно относится классу и какой для него использовать драйвер. После этого драйвер класса или устройства может зарегистрировать для этого устройства один или несколько классов интерфейса. В дальнейшем подключение выполняется к какому либо из этих интерфейсов. То есть, грубо говоря, у устройства может быть несколько разных интерфейсов, подключаться к устройству можно через любой из них и это будут разные подключения, поэтому вместо термина «подключение к устройству» правильнее, насколько я понимаю, говорить о подключении к тому или иному интерфейсу устройства.

Каждый класс интерфейса имеет свой GUID, — уникальное 128-битное значение, разбитое на 5 групп шестнадцатиричных символов, типа вот такого: <50127DC3-0F36-415E-A6CC-4CB3BE910B65>. Информация обо всех когда-либо подключенных устройствах, использующих тот или иной класс интерфейса хранится в системном реестре. GUID-ы зарегистрированных классов интерфейсов для Windows 7 можно найти в ветке
HKEY_LOCL_MACHINE/Microsoft/Windows NT/CurrentVersion/DeviceDisplayObjects/InterfaceInformation,
а список всех когда либо подключенных устройств, использовавших эти интерфейсы — в ветке
HKEY_LOCL_MACHINE/SYSTEM/CurrentControlSet/Control/DeviceClasses.
Для Windows 10 список предопределённых классов интерфейсов устройств можно найти в официальной документации Microsoft.

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

Таким образом, чтобы найти в системе наше устройства (его интерфейс) и получить заветный путь к нему — нужно пройти небольшой квест:

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

Выполнить этот квест помогут API-функции, описанные в таблице ниже (в среднем столбце указано из какой библиотеки соответствующая функция вызывается):

имя API-функции библиотека предназначение
HidD_GetHidGuid hid.dll получает GUID интерфейса класса HID
SetupDiGetClassDevs setupapi.dll получает список интерфейсов с указанным GUID и набор информации о каждом из них (грубо говоря кусок реестра со списком нужных интерфейсов и информацией о каждом из них)
SetupDiDestroyDeviceInfoList setupapi.dll освобождает ресурсы, используемые функцией SetupDiGetClassDevs
SetupDiEnumDeviceInterfaces setupapi.dll получает из полученного ранее набора информации об интерфейсах информацию об одном конкретном интерфейсе
SetupDiGetDeviceInterfaceDetail setupapi.dll получает подробную информацию о контретном интерфейсе, включая путь для подключения к нему

Теперь несколько слов о том, как опознать в списке «своё» устройство. Самый простой метод идентификации — по VID/PID, альтернативный вариант — используя элемент Usages (возможности). Usages состоит из двух полей, которые вместе описыавают назначение данных в репортах HID-устройства. Первое поле называется Usage, второе — Usage Page (Page ID). Эти поля могут также использоваться для идентификации функционального назначения устройства (мышь, Джостик, геймпад…) На usb.org лежит pdf-документ, в котором описаны стандартные значения Usage и Usage Page. Дополнительно можно проверять задаваемые при конфигурировании строковые дескрипторы Manufacturer и Product, скажем, комбинируя их с Usages.

Кстати, в созданном нами ранее HID-устройстве на stm32 (ссылка на которое приводится в начале статьи) мы сами не создавали дескрипторы репортов и не прописывали Usages, они были прописаны где-то на уровене middleware. Если бы мы хотели сделать это самостоятельно, то нужно было бы создавать HID-устройство на основе класса Custom.

Учтите, что в системе может быть несколько HID-устройств с одинаковыми Usages. Например, подключив к системе две мышки, мы будем видеть на экране движение каждой из них. Это потому что системе пофиг от какой мыши получать информацию, она получает её от всех устройств, у которых в Usages указано что это мышь. С другой стороны ничего не мешает нам также подключить к системе два устройства с одинаковыми VID/PID, так что… В общем мы будем использовать идентификацию устройства по VID/PID.

Кстати, мы в приведённой в качестве примера программе сможем увидеть USB мышь и клавиатуру, но не сможем к ним подключиться, поскольку к этим устройствам операционка подключается в режиме монопольного доступа и откажет в таком доступе функции CreateFile. Но это так, к слову.

После того, как мы определили путь для подключения и подключились к устройству — мы можем специальными функциями запросить описание устройства и его репортов (дескрипторы). API-функции, используемые для запроса разных описаний, перечислены в таблице ниже:

имя API-функции предназначение
HidD_GetPreparsedData возвращает указатель на буфер, содержащий информацию о репортах HID-устройства
HidD_FreePreparsedData освобождает ресурсы, выделенные функцией HidD_GetPreparsedData
HidD_GetCaps получает структуру HID_CAPS, содержащую информацию о возможностях устройства, — поддерживаемых устройством типах репортов и типах информации в них (Usage, UsagePage, InputReportByteLength, OutputReportByteLength, FeatureReportByteLength… Размеры репортов указываются с учётом Report ID, то есть на единицу больше длины пользовательской информации в них). Эту структуру формирует парсер HID, который вообще-то может не суметь итерпретировать все полученные дескрипторы.
HidD_GetPhisicalDescriptor 1 извлекает дескриптор устройства (сырой, без парсинга)
HidD_GetAttributes возвращает указатель на структуру, содержащую VID, PID и Release Number
HidD_GetManufacturerString 1 возвращает строку Manufacturer
HidD_GetProductString 1 возвращает строку Product
HidD_GetSerialNumberString 1 возвращает строку SerialNumber
1 — только для ОС старше Win98

Для обмена данными с устройствами USB HID могут использоваться следующие API-функции:

имя API-функции предназначение
HidD_GetFeature Читает из устройства Feature-репорт, используя управляющую передачу.
HidD_SetFeature Отправляет устройству Feature-репорт, используя управляющую передачу.
HidD_GetInputReport 1 Читает Input-репорт, используя управляющую передачу. Данные читаются в обход буфера, прочитанного драйвером HID
HidD_SetOutputReport 1 Отправляяет устройству Output-репорт, используя управляющую передачу.
ReadFile 1 Читает Input-репорт, используя передачу типа прерывание. Данные на самом деле читаются из буфера, заполняемого драйвером HID, который с заданным периодом опрашивает устройство. Если буфер пуст, функция просто ждёт, пока в него придут данные.
WriteFile 1 Отправляет Output-репорт, используя, если возможно, передачу типа прерывание, в противном случае используется управляющая передача.

Ниже показана структурная схема взаимодействия ПО хоста (в том числе API-функций) и HID-устройства:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *