Как написать драйвер для usb устройства

Обновлено: 04.07.2024

Описание слайда:

Минск 2012
Министерство образования республики беларусь
Белорусский государственный университет
Факультет прикладной математики и информатики
Кафедра технологии программирования
Толмачев М. А.
Разработка драйвера для контроля подключений USB –устройств

Описание слайда:

Введение
Перемещение информации через границы охраняемого периметра локальной сети компании доставляет, пожалуй, наибольшее количество хлопот службе информационной безопасности.
За последнее время резко увеличилось число всевозможных USB-устройств, которые могут использоваться в качестве накопителей. Поэтому разработка методов контроля подключений USB-утсройств является необходимым условием обеспечения информационной безопасности пользователей.

Описание слайда:

Обзор архитектуры Windows

Описание слайда:

Драйверы ОС Windows
Драйвер устройства – это программа предназначенная для управления каким-то устройством, причем устройство это не обязательно должно быть физическим. Оно может быть логическим или виртуальным.

Описание слайда:

Драйверы пользовательского режима (User-Mode Drivers)
Драйверы виртуальных устройств (Virtual Device Drivers, VDD) - используются для поддержки программ MS-DOS;
Драйверы принтеров (Printer Drivers).

Описание слайда:

Драйверы режима ядра (Kernel-Mode Drivers)
Драйверы файловой системы (File System Drivers) - реализуют ввод-вывод на локальные и сетевые диски;
Унаследованные драйверы (Legacy Drivers) - написаны для предыдущих версий Windows NT;
Драйверы видеоадаптеров (Video Drivers) - реализуют графические операции;
Драйверы потоковых устройств (Streaming Drivers) - реализуют ввод-вывод видео и звука;
WDM-драйверы (Windows Driver Model, WDM) - поддерживают технологию Plag and Play и управление электропитанием

Описание слайда:

Разделение драйверов по способу обработки запросов ввода-вывода
Одноуровневые драйверы
Многоуровневые драйверы

Описание слайда:

Уровни запросов прерывания
Прерывание – неотъемлемая часть любой операционной системы. Прерывание требует обработки, поэтому выполнение текущего кода прекращается и управление передается обработчику прерывания.
Существуют как аппаратные, так и программные прерывания.

Описание слайда:

Общая классификация драйверов WDM
Драйверы шин (Bus Drivers). Управляют логическими или физическими шинами. Отвечают за распознавание устройств, подключение их к управляемой ими шине и оповещение о них диспетчера PnP.
Функциональные драйверы (Function Drivers). Управляют конкретным типом устройств. Экспортируют рабочий интерфейс устройства операционной системе.
Драйверы фильтров (Filter Drivers). Занимая более высокий логический уровень, чем функциональные драйверы, добавляют функциональность или изменяют поведение устройства либо другого драйвера. Этот тип драйверов не обязателен для нормальной работы устройства.

Описание слайда:

Драйверы фильтров
Драйверы фильтров шин (Bus Filter Drivers).
Низкоуровневые драйверы фильтров (Lower-Level Filter Drivers).
Высокоуровневые драйверы фильтров (Upper-Level Filter Drivers).

Описание слайда:

Типы объектов «устройство»
Объект "физическое устройство" (Physical Device Object, PDO) - Создается драйвером шины по заданию диспетчера PnP, когда драйвер шины, перечисляя устройства на своей шине, сообщает о наличии какого-либо устройства. PDO представляет физический интерфейс устройства.
Объект "функциональное устройство" (Functional Device Object, FDO) - Создается функциональным драйвером, который загружается диспетчером PnP для управления обнаруженным устройством. FDO представляет логический интерфейс устройства.
Необязательная группа объектов "устройство-фильтр" (Filter Device Object, FiDO). Одна группа таких объектов размещается между PDO и FDO (эти объекты создаются драйверами фильтров шин), вторая - между первой группой FiDO и FDO (эти объекты создаются низкоуровневыми драйверами фильтров), а третья - над FDO (эти объекты создаются высокоуровневыми драйверами фильтров).

Описание слайда:

Этапы разработки драйвера
Выбор типа драйвера;
Составление спецификации оборудования, достаточной для начала работы над драйвером;
Тестирование установочного файла во всех операционных системах;
Создание панелей управления и прочих вспомогательных программ;
Реализация и тестирование функциональности WMI и журнала событий;

Описание слайда:

Модель взаимодействия
Драйвер
Конфигурационная программа
Пользователь

Описание слайда:

Возможности драйвера
Заблокировать устройство.
Разрешить подключение и дальнейшую работу устройства.

Описание слайда:

Спасибо за внимание

Если Вы считаете, что материал нарушает авторские права либо по каким-то другим причинам должен быть удален с сайта, Вы можете оставить жалобу на материал.

Драйвер-это основа взаимодействия системы с устройством в ОС Windows.Это одновременно удобно и неудобно.
Про удобства я разъяснять не буду - это и так понятно,
а заострюсь я именно на неудобствах драйверов.
В сложившейся ситуации пользователь полностью подчинён воле производителя
- выпусти тот драйвер - хорошо, а не выпустит.
Только продвинутый пользователь, имеющий голову на плечах
(особенно, если он ешё и программер) не станет мириться с таким положением дел
- он просто возьмёт и сам напишет нужный драйвер.
Это нужно и взломщику: драйвер - это удобное окошко в ring0,
которое является раем для хакера. Но хоть написать драйвер и просто,
да не совсем - есть масса подводных камней. Да и документированность данного вопроса на русском языке оставляет желать лучшего.
Этот цикл статей поможет тебе во всём разобраться.
Приступим.

Хочу сразу же сделать несколько предупреждений.
Данная статья всё-таки подразумевает определённый уровень подготовки.
Драйвера-то ведь пишутся на C(++) с большим количеством ассемблерных вставок.
Поэтому хорошее знание обоих языков весьма желательно (если не сказать - обязательно).
Если же ты пока не можешь этим похвастаться,
но желание писать драйвера есть - что ж, так как эта статья вводная, в конце её будет приведён список полезной литературы,
ссылок и т.д. Но помни: учить тебя в этом цикле статей программированию как таковому я тебя не буду.
Может как-нибудь в другой раз. Согласен? Тогда поехали!


Скоро здесь, возможно, будет стоять твоё имя.

Практически в любом деле, как мне кажется, нужно начинать с теории.
Вот и начнём с неё. Для начала уясним себе поточнее основные понятия.
Первое: что есть драйвер? Драйвер - в сущности
кусок кода ОС, отвечающий за взаимодействие с аппаратурой.
Слово "аппаратура" в данном контексте следует понимать в самом широком смысле.
С момента своего появления как такого до сегодняшнего дня драйвер беспрерывно эволюционировал.
Вот, скажем, один из моментов его развития. Как отдельный и довольно независимый модуль драйвер сформировался не сразу.
Да и сейчас этот процесс до конца не завершён:
ты наверняка сталкивался с тем, что во многих
дистрибутивах никсов для установки/перестановки etc драйверов нужно перекомпилировать ядро,
т.е. фактически заново пересобирать систему.
Вот, кстати ещё один близкий моментец: разные принципы работы с драйверами в Windows 9x и NT.
В первом процесс установки/переустановки драйверов проходит практически без проблем,
во втором же случае это тяжёлое и неблагодарное дело,
для "благополучного" завершения которого нередко приходится прибегать к полной переустановке ОС.
А зато в Windows 9x. так,стоп,открывается широкая и волнующая тема,
которая уведёт меня далеко от темы нынешней статьи,
так что вернёмся к нашим баранам. ой,то есть к драйверам.
В порядке общего развития интересно сравнить особенности драйверов в Windows и *nix(xBSD) системах:

1) Способ работы с драйверами как файлами (подробнее см. ниже)
2) Драйвер, как легко заменяемая честь ОС (учитывая уже сказанные выше примечания)
3) Существование режима ядра

Теперь касательно первого пункта. Это значит,
что функции, используемые при взаимодействии с файлами,
как и с драйверами, практически идентичные (имеется в виду лексически):
open, close, read и т.д. И напоследок стоит отметить идентичность механизма
IOCTL (Input/Output Control Code-код управления вводом-выводом)
-запросов.

Драйвера под Windows делятся на два типа:
Legacy (устаревший) и WDM (PnP). Legacy драйверы (иначе называемые "драйверы в стиле
NT") чрезвычайно криво работают (если работают вообще)
под Windows 98, не работают с PnP устройствами, но зато могут пользоваться старыми функциями
HalGetBusData, HalGetInterruptVector etc, но при этом не имеют поддержки в лице шинных драйверов.
Как видишь, весьма средненький драйвер. То ли дело
WDM: главный плюс - поддержка PnP и приличненькая совместимость:
Windows 98, Me, 2000, XP, 2003, Server 2003 и т.д. с вариациями; но он тоже вынужден за это расплачиваться:
например, он не поддерживает некоторые устаревшие функции
(которые всё таки могут быть полезны). В любом случае,
не нужно ничего воспринимать как аксиому, везде бывают свои исключения.
В некоторых случаях лучше написания Legacy драйвера ничего не придумать.

Как ты наверняка знаешь, в Windows есть два мода работы:
User Mode и Kernel Mode - пользовательский режим и режим ядра соответственно.
Первый - непривилегированный, а второй - наоборот.
Вот во втором чаще всего и сидят драйвера (тем
более, что мы в данный момент говорим именно о драйверах режима ядра).
Главные различия между ними: это доступность всяких привилегированных команд процессора.
Программировать (а уж тем более качественно) в Kernel mode посложнее будет,
чем писать прикладные незамысловатые проги.
А драйвера писать без хорошего знания Kernel mode - никак.
Нужно попариться над назначением выполнения разнообразных работ отдельному подходящему уровню IRQL, желательно выучить новое API (так как в Kernel mode API отличается от прикладного).
в общем, предстоит много всяких радостей. Но тем не менее,
это очень интересно, познавательно, и даёт тебе совершенно иной уровень власти над компьютером.

А раз уж я упомянула про IRQL, разьясню и это понятие.
IRQL (Interrupt Request Level - уровень приоритета выполнения) - это приоритеты,
назначаемые специально для кода, работающего в режиме ядра.
Самый низкий уровень выполнения - PASSIVE_LEVEl. Работающий поток может быть прерван потоком только с более высоким
IRQL.

Ну и напоследок разъясним ещё несколько терминов:

1) ISR (Interrupt Service Routine) - процедура обслуживания прерываний.
Эта функция вызывается драйвером в тот момент,
когда обслуживаемая им аппаратура посылает сигнал прерывания.
Делает самые необходимые на первый момент вещи:
регистрирует callback - функцию и т.д.

2) DpcForISR (Deferred Procedure Call for ISR) - процедура отложенного вызова для обслуживания прерываний.
Эту функцию драйвер регистрирует в момент работы ISR для выполнения основной работы.

3) IRP (Input/Output Request Packet) - пакет запроса на ввод - вывод.
Пакет IRP состоит из фиксированной и изменяющейся частей.
Вторая носит название стека IRP или стека ввода - вывода (IO stack).

4) IO stack location - стек ввода - вывода в пакете IRP.

5) Dispatch Routines (Рабочие процедуры) - эти функции регистрируются в самой первой (по вызову) процедуре драйвера.

6) Major IRP Code - старший код IRP пакета.

7) Minor IRP Code - соответственно, младший код IRP пакета.

8) DriverEntry - эта функция драйвера будет вызвана первой при его загрузке.

9) Layering (Многослойность) - данной возможностью обладают только WDM - драйвера.
Она заключается в наличии реализации стекового соединения между драйверами.
Что такое стековое соединение? Для этого необходимо знать про Device
Stack (стек драйверов) - поэтому я обязательно вспомню про всё это чуточку ниже.

10) Device Stack, Driver Stack (стек устройств, стек драйверов) - всего лишь
объемное дерево устройств. Его, кстати, можно рассмотреть во всех подробностях с помощью программы
DeviceTree (из MS DDK), например.

11) Стековое соединение - как и обещала, объясняю. В стеке драйверов самый верхний драйвер - подключившийся позднее.
Он имеет возможность посылать/переадресовывать IRP запросы другим драйверам,
которые находятся ниже его. Воти всё. Правда,просто?

12) AddDevice - функция, которую обязательно должны поддерживать WDM драйверы.
Её название говорит само за себя.

13) Device Object, PDO, FDO (Объект устройства, физический,
функциональный) - при подключении устройства к шине она создаёт PDO.
А уже к PDO будут подключаться FDO объекты WDM драйверов.
Обьект FDO создаётся самим драйвером устройства при помощи функции IOCreateDevice.
Обьект FDO также может иметь свою символическую ссылку, от которой он будет получать запросы от драйвера.
Это что касается WDM драйверов. С драйверами "в стиле NT" ситуация несколько иная.
Если он не обслуживает реальных/PnP устройств,
то PDO не создаётся. Но для связи с внешним миром без FDO не обойтись.
Поэтому он присутствует и тут.

14) Device Extension (Расширение обьекта устройства) - "авторская" структура,
т.е. она полностью определяется разработчиком драйвера.
Правилом хорошего тона считается, например,
размещать в ней глобальные переменные.

15) Monolithic Driver (Монолитный драйвер) - это драйвер,
который самостоятельно обрабатывает все поступающие
IRP пакеты и сам работает с обслуживаемым им устройством
(в стеке драйверов он не состоит). Данный тип драйверов используется только если обслуживается не
PnР устройство или же всего лишь требуется окошко в ring0.

16) DIRQL (уровни аппаратных прерываний) -
прерывания, поступающие от реальных устройств, имеют наивысший приоритет IRQL,
поэтому для них решено было придумать специальное название
(Device IRQL).

17) Mini Driver (Мини - драйвер) - чуть меньше "полного" драйвера.
Обычно реализуется в виде DLL-ки и имеет оболочку в виде "полного" драйвера.

18) Class Driver (Классовый драйвер) - высокоуровневый драйвер,
который предоставляет поддержку класса устройств.

19) РnP Manager (PnP менеджер) - один из главных компонентов операционной системы.
Состоит из двух частей: PnP менеджера пользовательского и "ядерного" режимов.
Первый в основном взаимодействует с пользователем;
когда тому нужно, например, установить новые драйвера и т.д.
А второй управляет работой, загрузкой и т.д. драйверов.

20) Filter Driver (фильтр - драйвер) - драйверы, подключающиеся к основному драйверу либо сверху
(Upper), либо снизу (Lower). Фильтр драйверы (их может быть несколько) выполняют фильтрацию IRP пакетов.
Как правило, для основного драйвера Filter Drivers неощутимы.

21) Filter Device Object - объект устройства, создаваемый фильтр - драйвером.

22) HAL (Hardware Abstraction Layer) - слой аппаратных абстракций.
Данный слой позволяет абстрагироваться компонентам операционной системы от особенностей конкретной платформы.

23) Synchronization Objects (Обьекты синхронизации) - с помощью этих
объектов потоки корректируют и синхронизируют свою работу.

24) Device ID - идентификатор устройства.

25) DMA (Direct Memory Access) - метод обмена данными между устройством и памятью
(оперативной) в котором центральный процессор не принимает участия.

25) Polling - это особый метод программирования, при котором не устройство посылает сигналы прерывания драйверу,
а сам драйвер периодически опрашивает обслуживаемое им устройство.

26) Port Driver (Порт-драйвер) - низкоуровневый драйвер,
принимающий системные запросы. Изолирует классовые драйверы устройств от аппаратной специфики последних.

Ну вот, пожалуй, и хватит терминов. В будущем,
если нужны будут какие-нибудь уточнения по теме,
я обязательно их укажу. А теперь, раз уж эта статья
теоретическая, давай-ка взглянем на архитектуру Windows NT с высоты птичьего полёта.

Краткий экскурс в архитектуру Windows NT

Наш обзор архитектуры Windows NT мы начнём с разговора об уровнях разграничения привилегий. Я уже упоминала об user и kernel mode.
Эти два понятия тесно связаны с так называемыми кольцами (не толкиеновскими ).
Их ( колец) в виде всего четыре: Ring3,2,1 и 0. Ring3 - наименее привилегированное кольцо,
в котором есть множество ограничений по работе с устройствами,
памятью и т.д. Например, в третьем кольце нельзя видеть адресное пространство других приложений без особого на то разрешения. Естественно,
трояну вирусу etc эти разрешения получить будет трудновато, так что хакеру в третьем кольце жизни никакой. В третьем кольце находится user mode. Kernel mode сидит в нулевом кольце - наивысшем уровне привилегий. В этом кольце можно всё:
смотреть адресные пространства чужих приложений без каких - либо ограничений и разрешений, по своему усмотрению поступать с любыми сетевыми пакетами, проходящими через машину, на всю жизнь скрыть какой-нибудь свой процесс или файл и т.д. и т.п. Естественно,
просто так пролезть в нулевое кольцо не получиться:
для этого тоже нужны дополнительные телодвижения. У легального драйвера с этим проблем нет:
ему дадут все необходимые API - шки, доступ ко всем нужным системным таблицам и проч. Хакерской же нечисти опять приходиться туго:
все необходимые привилегии ему приходиться "выбивать"
незаконным путём. Но это уже тема отдельной статьи, и мы к ней как-нибудь ещё вернёмся. А пока продолжим.

У тебя наверняка возник законный вопрос:
а что же сидит в первом и втором кольцах ? В том то всё и дело,
что программисты из Microsoft почему - то обошли эти уровни своим вниманием. Пользовательское ПО сидит в user mode,а всё остальное (ядро,
драйвера. ) - в kernel mode. Почему они так сделали - загадка, но нам это только на руку. А теперь разберёмся с компонентами (или, иначе говоря, слоями ) операционной системы Windows
NT.

Посмотри на схему - по ней многое можно себе уяснить. Разберём её подробнее.
С пользовательским режимом всё понятно. В kernel mode самый низкий уровень аппаратный. Дальше идёт HAL, выше - диспетчер ввода - вывода и драйвера устройств в одной связке, а также ядрышко вместе с исполнительными компонентами. О HAL я уже говорила, поэтому поподробнее поговорим об исполнительных компонентах. Что они дают? Прежде всего они приносят пользу ядру. Как ты уже наверняка уяснил себе по схеме, ядро отделено от исполнительных компонентов. Возникает вопрос:
почему ? Просто на ядре оставили только одну задачу:
просто управление потоками, а все остальные задачи (управление доступом,
памятью для процессов и т.д.) берут на себя исполнительные компоненты (еxecutive). Они реализованы по модульной схеме, но несколько компонентов её (схему) не поддерживают . Такая концепция имеет свои преимущества:
таким образом облегчается расширяемость системы. Перечислю наиболее важные исполнительные компоненты:

1) System Service Interface (Интерфейс системных служб )
2) Configuration Manager (Менеджер конфигурирования)
3) I/O Manager (Диспетчер ввода-вывода,ДВВ)
4) Virtual Memory Manager,VMM (Менеджер виртуальной памяти)
5) Local Procedure Call,LPC (Локальный процедурный вызов )
6) Process Manager (Диспетчер процессов)
7) Object Manager (Менеджер объектов)

Так как эта статья - первая в цикле, обзорная, подробнее на этом пока останавливаться не будем. В процессе практического обучения написанию драйверов, я буду разъяснять все неясные термины и понятия. А пока перейдём к API.

API (Application Programming Interface) - это интерфейс прикладного программирования. Он позволяет обращаться прикладным программам к системным сервисам через их специальные абстракции. API-интерфейсов несколько, таким образом в Windows-системах присутствуют несколько подсистем. Перечислю:

1) Подсистема Win32.
2) Подсистема VDM (Virtual DOS Machine - виртуальная ДОС - машина)
3) Подсистема POSIX (обеспечивает совместимость UNIX - программ)
4) Подсистемиа WOW (Windows on Windows). WOW 16 обеспечивает совместимость 32-х разрядной системы с 16-битными приложениями. В 64-х разрядных системах есть подсистема WOW 32,
которая обеспечивает аналогичную поддержку 32 - битных приложений.
5) Подсистема OS/2. Обеспечивает совместимость с OS/2 приложениями.

Казалось бы, всё вышеперечисленное однозначно говорит в пользу WINDOWS NT систем!
Но не всё так хорошо. Основа WINDOWS NT (имеются ввиду 32-х разрядные версии) - подсистема Win32. Приложения, заточенные под одну подсистему не могут вызывать функции другой. Все остальные (не Win32) подсистемы существуют в винде только в эмуляции и реализуются функции этих подсистем только через соответствующие функции винды. Убогость и ограниченность приложений, разработанных, скажем, для подсистемы POSIX и запущенных под винду - очевидны.
Увы.

Подсистема Win32 отвечает за графический интерфейс пользователя, за обеспечение работоспособности Win32 API и за консольный ввод - вывод. Каждой реализуемой задаче
соответствуют и свои функции: функции, отвечающие за графический фейс,
за консольный ввод - вывод (GDI - функции) и функции управления потоками,
файлами и т.д. Типы драйверов, наличествующие в Windows, я уже упоминала в разделе терминов:
монолитный драйвер, фильтр - драйвер и т.д. А раз так, то пора закругляться. Наш краткий обзор архитектуры Windows NT можно считать завершённым. Этого тебе пока хватит для общего понимания концепций Windows NT, и концепций написания драйверов под эту ось - как следствие.

Инструменты

Описать и/или упомянуть обо всех утилитах, могущих понадобиться при разработке драйверов - немыслимо. Расскажу только об общих направлениях.

Без чего нельзя обойтись ни в коем случае - это Microsoft DDK (Driver Development Kit ). К этому грандиозному пакету прилагается и обширная документация. Её ценность - вопрос спорный. Но в любом случае, хотя бы ознакомиться с первоисточником информации по написанию драйверов для Windows - обязательно. В принципе, можно компилять драйвера и в Visual Studio, но это чревато долгим и нудным копанием в солюшенах и vcproj-ектах, дабы код твоего драйвера нормально откомпилировался. В любом случае, сорцы придётся набивать в визуальной студии, т.к. в DDK не входит
нормальная IDE. Есть пакеты разработки драйверов и от третьих фирм:
WinDriver или NuMega Driver Studio, например. Но у них есть отличия от майкрософтовского базиса функций (порой довольно большие ) и многие другие мелкие неудобства. Так что DDK - лучший вариант. Если же ты хочешь писать драйвера исключительно на ассемблере, тебе подойдёт KmdKit (KernelMode Driver DevelopmentKit) для MASM32. Правда, этот вариант только для Win2k/XP.

Напоследок нельзя не упомянуть такие хорошие проги, как PE
Explorer, PE Browse Professional Explorer, и такие незаменимые, как дизассемблер IDA и лучший отладчик всех времён и народов SoftICE.

Ну вот и подошла к концу первая статья из цикла про написание драйверов под Windows. Теперь ты достаточно "подкован" по
теоретической части, так что в следующей статье мы перейдём к практике. Желаю тебе удачи в этом интереснейшем деле - написании драйверов! Да не облысеют твои пятки!

Какие устройства поддерживает драйвер?

Структура struct usb_device_id содержит список различных типов USB устройств, которые поддерживает этот драйвер. Этот список используется ядром USB, чтобы решить, какой драйвер предоставить устройству, или скриптами горячего подключения, чтобы решить, какой драйвер автоматически загрузить, когда устройство подключается к системе.

Структура struct usb_device_id определена со следующими полями:

Определяет, какие из следующих полей в структуре устройства должны сопоставляться. Это битовое поле определяется разными значениями USB_DEVICE_ID_MATCH_* , указанными в файле include/linux/mod_devicetable.h . Это поле, как правило, никогда не устанавливается напрямую, а инициализируется с помощью макросов типа USB_DEVICE , описываемых ниже.

Идентификатор поставщика USB для устройства. Этот номер присваивается форумом USB для своих членов и не может быть присвоен кем-то еще.

Идентификатор продукта USB для устройства. Все поставщики, которые имеют выданный им идентификатор поставщика, могут управлять своими идентификаторами продукта, как они предпочитают.

Определяют нижнюю и верхнюю границу диапазона назначаемого поставщиком номера версии продукта. Значения bcdDevice_hi является включительным; его значение является значением наибольшего номера устройства. Обе эти величины представлены в двоично-десятичной (BCD) форме. Эти переменные в сочетании с idVendor и idProduct используются для определения данного варианта устройства.

Определяют класс, подкласс и протокол устройства, соответственно. Эти номера присваиваются форумом USB и определены в спецификации USB. Эти значения определяют поведение для всего устройства, в том числе все интерфейсы на этом устройстве.

Подобно зависимым от устройства вышеприведённым величинам, эти определяют класса, подкласс и протокол отдельного интерфейса, соответственно. Эти номера присваиваются форумом USB и определены в спецификации USB.

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

Как и с PCI устройствами, существует ряд макросов, которые используются для инициализации этой структуры:

Создаёт struct usb_device_id , которая может быть использована только для соответствия указанными значениям идентификаторов поставщика и продукта. Она очень часто используется для устройств USB, которым необходим специальный драйвер.

USB_DEVICE_VER(vendor, product, lo, hi)

Создаёт struct usb_device_id , которая может быть использована только для соответствия указанным значениям идентификаторов поставщика и продукта внутри диапазона версий.

USB_DEVICE_INFO(class, subclass, protocol)

Создаёт struct usb_device_id , которая может быть использованы для соответствия определённому классу USB устройств.

USB_INTERFACE_INFO(class, subclass, protocol)

Создаёт struct usb_device_id , которая может быть использована для соответствия определённому классу USB интерфейсов.

Итак, для простого драйвера USB устройства, который управляет только одним USB устройством от одного поставщика, таблица struct usb_device_id будет определяться как:

/* таблица устройств, которые работают с этим драйвером */

static struct usb_device_id skel_table [ ] =

MODULE_DEVICE_TABLE (usb, skel_table);

Как и с драйвером PCI, необходим макрос MODULE_DEVICE_TABLE , чтобы разрешить инструментам пространства пользователя выяснить, какими устройствами может управлять этот драйвер. Но для USB драйверов первым значением в этом макросе должна быть строка usb .

Основной структурой, которую должны создать все USB драйверы, является struct usb_driver . Эта структура должна быть заполнена драйвером USB и состоит из ряда функций обратного вызова и переменных, описывающих USB драйвер для кода USB ядра:

struct module *owner

Указатель на модуль владельца этого драйвера. Ядро USB использует его для правильного подсчёта ссылок на этот драйвер USB, чтобы он не выгружался в несвоевременные моменты. Переменной должен быть присвоен макрос THIS_MODULE .

const char *name

Указатель на имя драйвера. Он должен быть уникальным среди всех USB драйверов в ядре и, как правило, установлен на такое же имя, что и имя модуля драйвера. Оно проявляется в sysfs в /sys/bus/usb/drivers/ , когда драйвер находится в ядре.

const struct usb_device_id *id_table

Указатель на таблицу struct usb_device_id , которая содержит список всех различных видов устройств USB, которые драйвер может распознать. Если эта переменная не установлена, функция обратного вызова probe в драйвере USB никогда не вызывается. Если вы хотите, чтобы ваш драйвер всегда вызывался для каждого USB устройства в системе, создайте запись, которая устанавливает только поле driver_info :

static struct usb_device_id usb_ids[ ] =

int (*probe) (struct usb_interface *intf, const struct usb_device_id *id)

Указатель на зондирующую функцию в USB драйвере. Эта функция (описанная в разделе "probe и disconnect в деталях") вызывается USB ядром, когда оно думает, что оно имеет структуру usb_interface , которую этот драйвер может обработать. Указатель на struct usb_device_id , который использовало USB ядро, чтобы принять это решение также передается в эту функцию. Если USB драйвер признаёт переданную ему структуру usb_interface , он должен правильно проинициализировать устройство и вернуть 0. Если драйвер не хочет признавать устройство или произошла ошибка, он должен вернуть отрицательное значение ошибки.

void (*disconnect) (struct usb_interface *intf)

Указатель на функцию отключения в USB драйвере. Эта функция (описанная в разделе "probe и disconnect в деталях") вызывается USB ядром, когда структура usb_interface была удалена из системы, или когда драйвер выгружается из ядра USB.

Таким образом, чтобы создать значимую структуру struct usb_driver , должны быть проинициализированы только пять полей:

static struct usb_driver skel_driver =

struct usb_driver содержит несколько больше обратных вызовов, которые, как правило, очень часто не используются, и не требуются для правильной работы USB драйвера:

int (*ioctl) (struct usb_interface *intf, unsigned int code, void *buf)

Указатель на функцию ioctl в USB драйвере. Если он присутствует, то вызывается, когда программа пользовательского пространства делает вызов ioctl для записи файловой системы устройств usbfs , связанной с устройством USB, относящемуся к этому USB драйверу. На практике только драйвер USB концентратора использует этот ioctl , так как любому другому USB драйверу нет иной реальной необходимости его использовать.

int (*suspend) (struct usb_interface *intf, u32 state)

Указатель на функцию приостановки в USB драйвере. Она вызывается, когда работа устройства должна быть приостановлена USB ядром.

int (*resume) (struct usb_interface *intf)

Указатель на функцию возобновления в USB драйвере. Она вызывается, когда работа устройства возобновляется USB ядром.

Чтобы зарегистрировать struct usb_driver в USB ядре, выполняется вызов usb_register_driver с указателем на struct usb_driver . Для USB драйвера это традиционно делается в коде инициализации модуле:

static int __init usb_skel_init(void)

/* регистрируем этот драйвер в подсистеме USB */

err("usb_register failed. Error number %d", result);

Когда драйвер USB будет выгружаться, необходимо разрегистрировать struct usb_driver в ядре. Это делается с помощью вызова usb_deregister . Когда происходит этот вызов, любые USB интерфейсы, которые в настоящее время связаны с этим драйвером, отключаются и для них вызывается функция disconnect .

static void __exit usb_skel_exit(void)

/* отменяем регистрацию этого драйвера в подсистеме USB */

probe и disconnect в деталях

В структуре struct usb_driver structure , описанной в предыдущем разделе, драйвер указывает две функции, которые в соответствующее время вызывает ядро USB. Функция probe вызывается, когда установлено устройство, которым, как думает ядро USB, должен управлять этот драйвер; функция probe должна выполнять проверки информации, переданной ей об устройстве, и решать, действительно ли этот драйвер подходит для этого устройства. Функция disconnect вызывается, когда по каким-то причинам драйвер не должен больше управлять устройством и может делать очистку.

Оба функции обратного вызова probe и disconnect вызываются в контексте потока USB узла ядра, так что засыпать в них допускается. Тем не менее, рекомендуется, чтобы большая часть работы выполнялась, когда устройство открыто пользователем, если это возможно, чтобы сократить время зондирования USB к минимуму. Такое требование появляется потому, что USB ядро обрабатывает добавление и удаление устройств USB в одном потоке, так что любой медленный драйвер устройства может привести к замедлению обнаружения USB устройства и это станет заметно для пользователя.

В функции обратного вызова probe , USB драйвер должен проинициализировать любые локальные структуры, которые он может использовать для управления USB устройством. Следует также сохранить в локальные структуры любую необходимую информацию об устройстве, так как это обычно легче сделать в данное время. Например, USB драйверы обычно хотят обнаружить адрес оконечной точки и размеры буферов для данного устройства, так как они необходимы для общения с устройством. Вот пример некоторого кода, который определяет две оконечные точки ВХОДА и ВЫХОДА поточного типа и сохраняет некоторую информацию о них в локальной структуре устройства:


Недавно на eBay мне попалась партия интересных USB-девайсов (Epiphan VGA2USB LR), которые принимают на вход VGA и отдают видео на USB как веб-камера. Меня настолько обрадовала идея, что больше никогда не придётся возиться с VGA-мониторами, и учитывая заявленную поддержку Linux, я рискнул и купил всю партию примерно за 20 фунтов (25 долларов США).

Получив посылку, я подключил устройство, но оно даже не подумало появиться в системе как
К сожалению, поддержка драйверов именно для этих устройств закончилась в Linux 4.9. Таким образом, его не увидит ни одна из моих систем (Debian 10 на Linux 4.19 или последняя версия LTS Ubuntu на Linux 5.0).

Печально. Но здесь не так.

Внутри пакета оказался только предварительно скомпилированный бинарник vga2usb.o . Я начал его изучать, прикидывая сложность реверс-инжиниринга, и нашёл несколько интересных строк:

Чтобы изучить драйвер в его нормальной среде, я поднял виртуальную машину с Debian 9 (последний поддерживаемый релиз) и сделал

Я обнаружил, что при запуске на устройство передаётся большое количество мелких пакетов, прежде чем оно начинает захватывать картинку. Вероятно, оно действительно основано на платформе FPGA без хранилища данных. Каждый раз после подключения драйвер передавал на устройство прошивку в виде

ISL98002CRZ-170 — работает как ADC для сигналов VGA

XC6SLX16 — Xilinx Spartan 6 FPGA

CY7C68013A — USB-контроллер / фронтенд

После распаковки файла AA240.zlib оказалось, что там недостаточно данных для полного битстрима. Поэтому я решил захватить прошивку из пакетов USB.

Считывать USB-пакеты из файлов pcap может и

Оказалось, что из-за небольшой perf для общего представления о трассировке стека драйверов во время работы:


Хотя я мог выловить функции с данными фреймов, но понять кодировку самих данных никак не удавалось.


Чтобы лучше понять, что происходит внутри настоящего драйвера, я даже попробовал инструмент

Хотя Ghidra невероятна (когда я впервые использовал её вместо IDA Pro), но всё ещё недостаточно хороша, чтобы помочь мне понять драйвер. Для реверс-инжиниринга требовался другой путь.

Я решил поднять виртуальную машину Windows 7 и взглянуть на драйвер Windows, вдруг он подбросит идеи. И тогда заметил, что для устройств имеется SDK. Один из инструментов оказался особенно интересным:

Этот инструмент позволяет «выхватывать» единичные фреймы, причём изначально они не сжимаются, чтобы была возможность обработать фреймы позже на более быстрой машине. Это практически идеально, и я реплицировал последовательность пакетов USB, чтобы получить эти несжатые блобы. Количество байтов соответствовало примерно трём (RGB) на пиксель!

Первоначальная обработка этих изображений (просто принимая вывод и записывая его как пиксели RGB) дала нечто отдалённо напоминающее реальную картинку, которое устройство получало через VGA:


После некоторой отладки в hex-редакторе выяснилось, что каждые 1028 байт повторяется какой-то маркер. Немного стыдно, как много времени я потратил на написание фильтра. С другой стороны, в процессе можно было насладиться некоторыми образцами современного искусства.


Затем я понял, что наклон и искажение изображения вызваны пропуском и переносом пикселя на каждой строке (x=799 не равно x=800). И тогда, наконец, у меня получилось почти правильное изображение, если не считать цвета:


Сначала я думал, что проблема с калибровкой из-за выборки данных, когда вход VGA застрял на сплошном цвете. Для исправления я сделал новое тестовое изображение, чтобы выявить такие проблемы. Задним числом понимаю, что надо было использовать что-то вроде

Я загрузил изображение на ноутбук, и тот выдал такую картинку VGA:


Тут мне пришло воспоминание о какой-то давней работе по 3D-рендерингу/шейдеру. Это было очень похоже на цветовую схему YUV .

В итоге я погрузился в чтение литературы по YUV и вспомнил, что во время реверс-инжиниринга официального драйвера ядра, если я ставил точку останова на функции под названием v2ucom_convertI420toBGR24 , то система зависала без возможности возобновления. Так что, может, на входе была кодировка I420 (от -pix_fmt yuv420p ), а выход RGB?

После применения встроенной в Go функции

Мы сделали это! Даже сырой драйвер выдавал 7 кадров в секунду. Честно говоря, мне этого достаточно, так как я использую VGA только в случае аварии как резервный дисплей.

Итак, теперь мы знаем это устройство достаточно хорошо, чтобы объяснить алгоритм его запуска с самого начала:

Предупреждая неизбежные претензии к качеству экспериментального кода, скажу сразу: я им не горжусь. Наверное, он в таком состоянии, какого мне достаточно для приемлемого использования.

Код и готовые сборки для Linux и OSX лежат на GitHub .

Даже если программу никто никогда не запустит, для меня это было чертовски увлекательное путешествие в дебрях протокола USB, отладки ядра, реверс-инжиниринга модуля и формата декодирования видео! Если вам нравятся такие вещи, можете посмотреть другие статьи в блоге .

Читайте также: