В этой статье приведено подробное описание процесса перечисления (опознания, или энумерации) устройства USB стеком USB ядра операционной системы Windows 7 (перевод поста [1], автор Martin Borve, разработчик команды подсистем USB Windows) - с точки зрения стека, когда было детектировано присутствие устройства, и стек указывает менеджеру PnP, что появилось новое устройство.
Прим. переводчика: все непонятные термины, касающиеся USB, ищите в руководстве [3].
Процесс энумерации устройства для порта USB начинается, когда хаб индицирует изменение статуса соединения (connect status) через конечную точку прерывания хаба (hub interrupt endpoint). Если новый статус порта показывает подключенное устройство, USB-драйвер хаба будет использовать следующие шаги по энумерации устройства:
Port Stabilization Debounce - дебоунсинг, процедура борьбы с дребезгом при подключении, стабилизация состояния порта. Драйвер хаба должен отслеживать изменение состояния соединения как минимум 100 мс, пока состояние не перестанет изменяться (USB 2.0 spec, 7.1.7.3, TATTDB). Если порт не стабилизировал свое состояние после 200 мс, драйвер хаба запретит порт и остановит энумерацию. Подсистеме PnP не будет сообщено об устройстве.
First Port Reset - первый сброс порта. Как только дебоунсинг успешно завершился, драйвер хаба выдаст запрос на сброс (reset request) для порта. При нормальном функционировании это приведет к тому, что статус порта перейдет в состояние connected и enabled (соединено и разрешено), и устройство само ответит на адрес USB по умолчанию 0.
Сбросы порта всех устройств USB сериализованы через "enumeration lock" (блокировка энумерации) на базе контроллера хоста, поскольку в любой момент времени может быть разрешено только одно устройство USB с адресом USB по умолчанию 0. Драйвер хаба получит enumeration lock до выдачи первого запроса сброса порта (port reset request), и отменит enumeration lock, когда устройству был присвоен адрес не по умолчанию, или когда энумерация будет отменена. Драйвер хаба использует таймаут 5 секунд для случая, когда port reset request не завершится.
В течение ожидания завершения первого сброса порта драйвер хаба должен быть в состоянии обрабатывать следующие события:
Device Disconnect. Отключение устройства, энумерация отменена. Подсистеме PnP не будет сообщено об устройстве.
Overcurrent Change. Изменение перегрузки по току. В этом случае энумерация нормально завершится, за исключением случаев, когда определено, что превышение тока было. Подсистеме PnP не будет сообщено об устройстве.
Timeout of Port Reset. Истек таймаут сброса порта. В случае таймаута первого запроса сброса драйвер хаба попытается 3 раза повторить энумерацию, чтобы начать заново обрабатывать состояние "First Port Reset". Между каждой попыткой делается задержка 500 мс, чтобы позволить устройству восстановиться. Если вышел таймаут сброса порта после 3-ей попытки, то энумерация будет отменена, и подсистеме PnP будет сообщено об неизвестном устройстве (Unknown Device).
Если port reset request завершится успешно, драйвер хаба перейдет к следующим состояниям, базируясь на состоянии порта:
Device Disconnected. Устройство отключено, энумерация отменена. Подсистеме PnP не будет сообщено об устройстве.
Port Connected and Disabled. Порт в состоянии "соединено", и порт запрещен. Завершение сброса порта было проигнорировано. Будет разрешено продолжение отсчета таймаута сброса, и будут предприняты повторные попытки сброса порта при необходимости.
Port Connected and Suspended. Порт в состоянии "соединено", и порт приостановлен. Энумерация отменена. Подсистеме PnP не будет сообщено об устройстве.
Port Overcurrent. Перегрузка по току (превышение допустимого тока порта). Завершение сброса порта будет проигнорировано. Будет разрешено продолжение отсчета таймаута сброса, и будут предприняты повторные попытки сброса порта при необходимости.
Port Enabled and Connected. Порт разрешен и соединен. Это означает, что сброс порта завершился успешно. Драйвер хаба делает задержку как минимум 10 мс, чтобы позволить устройству восстановиться из сброса (USB 2.0 spec, 7.1.7.3, TRSTRCY). Драйвер переходит в состояние "First Device Descriptor Request".
First Device Descriptor Request - состояние отправки первого запроса дескриптора устройства. Стек драйвера USB выдает запрос на получение дескриптора устройства USB (USB Device Descriptor, запрос GET_DESCRIPTOR для типа дескриптора DEVICE), используя USB-адрес по умолчанию 0, и максимальный размер пакета 8 байт для низко-скоростных устройств (low-speed device) и 64 байта для полноскоростных (full-speed device) и высокоскоростных (high-speed device) устройств.
Прим. переводчика: скорость устройства определяется на основе аппаратного конфигурирования сигналов шины USB с помощью нагрузочных резисторов. Подробнее см. руководство "USB in the NutShell" [3].
Этот запрос дескриптора используется исключительно с целью получить корректный максимальный размер пакета для конечной точки по умолчанию (default control endpoint), размер пакета указан в поле bMaxPacketSize0 дескриптора устройства по смещению 7. Устройства USB должны вернуть в ответ на запрос как минимум первые 8 байт дескриптора устройства (Device Descriptor), когда они сконфигурированы на адрес USB по умолчанию (USB 2.0 spec, 5.5.3).
Когда драйвер хаба запрашивает дескриптор устройства, он указывает размер передачи 64 байт. Так сделано для того, потому что некоторые старые устройства USB буду плохо себя вести, если размер запроса будет меньше. Мы также обнаружили, что некоторые устройства USB будут еще что-то пытаться возвратить после того, как отправят свой дескриптор устройства, однако они все же отправят корректные данные в первых 8 байтах. По этой причине ошибки передачи будут игнорироваться, если устройством будут возвращены как минимум 8 байт.
Если запрос дескриптора устройства терпит неудачу, драйвер хаба попытается 3 раза повторить энумерацию путем 3-кратного возврата в состояние "First Port Reset". Если все 3 попытки энумерации терпят неудачу, то энумерация будет прекращена, и менеджеру PnP будет сообщено о неизвестном устройстве "Unknown Device".
Если запрос дескриптора устройства завершился успешно, драйвер хаба перейдет к шагу "Second Port Reset". Все дальнейшие управляющие передачи (control transfers) для конечной точки по умолчанию (default endpoint) будут использовать максимальный размер пакета, который указан в дескрипторе устройства.
Second Port Reset - второй сброс порта. Он появился, потому что на ранних стадиях развития USB некоторые устройства USB затыкались на втором запросе дескриптора устройства, если они не вернули почему-то полный дескриптор устройства для первого запроса. Чтобы позволить этим устройствам успешно пройти энумерацию, нужен сброс порта между первым и вторым запросами дескриптора устройства.
Драйвер хаба использует 5-секундный таймаут для второго запроса после сброса порта в том случае, если он не никогда не завершится. Во время ожидания завершения второго сброса порта драйвер хаба должен обрабатывать следующие события (он полностью идентичны обработке событий в первом запросе сброса):
Device Disconnect. Отключение устройства, энумерация отменена. Подсистеме PnP не будет сообщено об устройстве.
Overcurrent Change. Перегрузка по току, энумерация отменена. Подсистеме PnP не будет сообщено об устройстве.
Timeout of Port Reset. Истек таймаут сброса порта. В случае таймаута второго запроса сброса драйвер хаба будет пытаться 3 раза повторить энумерацию, путем трехкратного возврата к состоянию "First Port Reset". Если истек таймаут сброса порта после 3-ей попытки, то энумерация будет отменена, и менеджеру PnP будет сообщено про неизвестное устройство (Unknown Device).
Если второй сброс порта завершился успешно, то драйвер хаба перейдет в следующие состояния, основываясь на текущем состоянии порта:
Device Disconnected. Устройство отключено, энумерация прекращена. Подсистеме PnP не будет сообщено об устройстве.
Port Connected and Disabled. Порт подключен и запрещен, завершение сброса порта будет проигнорировано. Будет разрешено продолжение отсчета таймаута сброса, и будут предприняты повторные попытки сброса порта при необходимости.
Port Connected and Suspended. Порт в состоянии "соединено", и порт приостановлен. Энумерация отменена. Подсистеме PnP не будет сообщено об устройстве.
Port Overcurrent. Перегрузка по току (превышение допустимого тока порта). Завершение сброса порта будет проигнорировано. Будет разрешено продолжение отсчета таймаута сброса, и будут предприняты повторные попытки сброса порта при необходимости.
Port Enabled and Connected. Порт разрешен и подключен. Показывает успешное завершение сброса порта. Драйвер хаба делает задержку как минимум 10 мс, чтобы позволить устройству восстановиться из сброса (USB 2.0 spec, 7.1.7.3, TRSTRCY). Драйвер хаба переходит в следующее состояние энумерации "Set USB Address". Драйвер хаба выполнит задержку 100 мс после успешного сброса порта, если энумерация должна быть повторена по крайней мере 1 раз.
Set USB Address. Установка адреса устройства на шине USB. Стек драйвера USB выделяет уникальный (для одного контроллера USB) адрес устройства USB, и выдает запрос устройству SET_ADDRESS. Если запрос SET_ADDRESS потерпел неудачу или вышел таймаут, энумерация будет отменена, и менеджеру PnP будет сообщено о неизвестном устройстве USB (Unknown Device).
Если SET_ADDRESS завершился успешно, до драйвер хаба будет ждать как минимум 10 мс, чтобы позволить устройству стабилизироваться, перед тем как перейти к состоянию "Second Device Descriptor Request".
Драйвер стека USB выдаст устройству второй запрос полного дескриптора устройства USB (USB Device Descriptor, запрос GET_DESCRIPTOR для типа дескриптора DEVICE). Если запрос потерпит неудачу или истек таймаут, порт будет запрещен, и будет попытка повтора энумерации путем возврата в состояние "First Port Reset". Если драйвер хаба уже сделал 3 попытки энумерации, то энумерация будет прекращена и менеджеру PnP будет сообщено о неизвестном устройстве USB (Unknown Device).
После успешного завершения второго запроса дескриптора устройства, драйвер хаба будет проверять дескриптор устройства следующим образом:
• Поле bLength равно или больше, чем размер дескриптора устройства USB (USB Device Descriptor), как это определено в спецификации USB 2.0. • Поле bDescriptorType равно типу дескриптора DEVICE (константа 1).
Если проверка завершилась неудачно, порт будет запрещен и энумерация попытается вернуться в состояние "First Port Reset". Если драйвер хаба уже сделал 3 попытки энумерации, то энумерация будет прекращена и менеджеру PnP будет сообщено о неизвестном устройстве USB (Unknown Device).
После успешной валидации дескриптора устройства драйвер хаба кэширует дескриптор, отменит блокировку энумерации (enumeration lock), и перейдет к состоянию "Configuration Descriptor Request".
Configuration Descriptor Request. Запрос дескриптора конфигурации. Стек драйвера USB выдаст запрос устройству USB на выдачу дескриптора конфигурации (USB Configuration Descriptor, запрос GET_DESCRIPTOR для типа дескриптора CONFIGURATION). По соображениям совместимости запрос дескриптора устройства укажет длину 255 байт.
Если запрос Configuration Descriptor завершится с ошибкой или истек таймаут, то драйвер хаба запретит порт, и попытается повторить энумерацию путем возврата в состояние "First Port Reset". Если драйвер хаба уже сделал 3 попытки энумерации, то энумерация будет прекращена и менеджеру PnP будет сообщено о неизвестном устройстве USB (Unknown Device).
Если запрос Configuration Descriptor завершился успешно, то драйвер хаба будет проверять количество возвращенных байт - равно оно или больше значению поля wTotalLength дескриптора конфигурации. Если оно не больше или равно wTotalLength, то драйвер хаба попытается снова выдать запрос на получение Configuration Descriptor, чтобы удостовериться в том, что устройство не вернуло некорректные данные в дескрипторе.
После успешного завершения запроса Configuration Descriptor стек драйвера USB проверит дескриптор следующим образом:
• Поле bLength равно или больше чем размер USB Configuration Descriptor, как это определено в стандарте USB 2.0. • Поле bDescriptorType равно типу дескриптора CONFIGURATION (константа 2).
Если проверка завершилась неудачно, порт будет запрещен и энумерация попытается вернуться в состояние "First Port Reset". Если драйвер хаба уже сделал 3 попытки энумерации, то энумерация будет прекращена и менеджеру PnP будет сообщено о неизвестном устройстве USB (Unknown Device).
Если проверка завершилась успешно, то Configuration Descriptor кэшируется, и драйвер хаба переходит в состояние "MS OS Descriptor Query".
Microsoft определила набор дескрипторов USB, специфичных для вендора (vendor specific USB descriptors), так называемые Microsoft OS Feature Descriptors, которые запрашиваются во время энумерации устройства.
Если поле bcdUSB дескриптора устройства USB равно 0x0100 или 0x0110, то драйвер хаба пропустит запрос MS OS Descriptor, и перейдет к состоянию "Serial Number String Descriptor Query".
Если драйвер хаба никогда прежде не выполнял энумерацию устройства с тем же самым VID/PID/Revision, как устройство, которое энумерируется сейчас, то он выдаст запрос устройству для получения MS OS String Descriptor (GET_DESCRIPTOR для типа дескриптора STRING), который использует индекс (строки) 0xEE. Это определит идентификатор языка (language ID) 0x00.
Если устройство возвратит MS OS Descriptor, драйвер хаба проверит этот дескриптор следующим образом:
• Поле MicrosoftString должно быть равно "MSFT100".
После успешной проверки поле bVendorCode дескриптора будет сохранено в реестре на базе VID/PID/Revision, в подразделе реестра USBFLAGS, в значении реестра "osvc". Последующие энумерации любого устройства с теми же самыми VID/PID/Revision будут читать bVendorCode из этого значения реестра вместо опроса устройства.
Драйвер хаба перейдет к состоянию "Serial Number String Descriptor Query".
Serial Number String Descriptor Query - запрос строкового дескриптора серийного номера. Если USB Device Descriptor сообщает о ненулевом индексе строки серийного номера, то драйвер хаба выдаст запрос на получение строкового дескриптора серийного номера (GET_DESCRIPTOR для типа дескриптора STRING), используя идентификатор языка American English language ID (0x409) и индекс строки серийного номера.
Драйвер хаба выполняет следующую проверку всех строковых дескрипторов:
• Количество байт, возвращенных для запроса, должно быть больше или равно полю bLength. • Поле bLength должно быть больше 2 байт. • Поле bDescriptorType должно быть равно типу дескриптора STRING (3). • Поле bLength должно быть всегда четным числом, потому что строка в формате Unicode.
Если проверка прошла успешно, драйвер хаба выполняет следующую проверку специально для дескриптора серийного номера:
• Строка должна быть не пустой (non-NULL). • Строка должна быть не длиннее 255 байт. • Возвращенный тип дескриптора, возвращенный дескрипторе, должен быть STRING. • Количество байт в строке должно быть четным числом, потому что строка в формате Unicode. • Строка не должна содержать любых недопустимых символов: - Код символа должен быть больше или равен 0x20. - Код символа должен быть меньше или равен 0x7F. - Код символа не должен быть запятой (','=0x2C).
Если любая из вышеуказанных проверок не прошла, то серийный номер будет отброшен, иначе он будет закэширован.
Независимо от результатов запроса и проверки серийного номера драйвер хаба перейдет к состоянию "MS OS Extended Configuration Descriptor Request", если устройство поддерживает MS OS Descriptor, иначе перейдет к состоянию "Language ID Query".
Если устройство не композитное, то драйвер хаба выдаст запрос MS OS Extended Configuration Descriptor.
Программное обеспечение выдаст начальный запрос для этого дескриптора путем указания размера данных, равного заголовку дескриптора. Это будет использоваться для определения существования дескриптора, и для определения его размера. Если запрос был успешным, драйвер хаба проверит заголовок следующим образом:
• Возвращенное количество байт должно быть равно определенному размеру заголовка. • Версия заголовка, закодированная в формате binary-coded decimal (BCD, двоично-десятичный код), должна быть 1.00. • Поле wIndex должно быть установлено в 4. • Поле bCount не должно быть равно 0. • Поле заголовка dwLength должно быть равно размеру заголовка плюс значение поля bCount в единицах структуры функции дескриптора конфигурации.
Если заголовок дескриптора успешно проверен, драйвер хаба повторно выдает запрос дескриптора, используя размер всего дескриптора, который был возвращено устройством в заголовке дескриптора.
Если устройство успешно вернуло MS OS Extended Configuration Descriptor, драйвер хаба проверит этот дескриптор следующим образом:
• Стандартный дескриптор конфигурации USB будет проверен для функций, описанных дескрипторами привязки интерфейса (Interface Association Descriptors) и функций с одним интерфейсом (single-interface functions), чтобы определить общее количество функций в устройстве. • Следующая проверка будет выполнена на заголовке MS OS Extended Configuration Descriptor: - Значение dwLength заголовка должно быть больше или равно размеру структуры заголовка. - Значение dwLength заголовка должно быть меньше или равно размеру заголовка плюс 256 функций Extended Configuration Descriptor. - Значение dwLength заголовка должно быть меньше или равно количеству байт, возвращенному устройством для дескриптора. - Значение wIndex заголовка должно быть равно 4, индекс MS Extended Configuration Descriptor. - Значение bCount заголовка должно быть меньше или равно количеству функций, которое найдено в USB Configuration Descriptor. - Значение dwLength заголовка должно быть больше или равно размеру структуры заголовка плюс bCount (поле заголовка) величин структуры Extended Configuration Descriptor Function. • Для каждого дескриптора функции будет выполнена следующая проверка: - Поле bFirstInterfaceNumber меньше или равно 256. - Поле bFirstInterfaceNumber соответствует первому интерфейсу функции, описанной Interface Association Descriptor или функции с одним интерфейсом в USB Configuration Descriptor устройства. - Строка CompatibleID содержит символы ASCII только в верхнем регистре ('A'-'Z'), символы чисел ('0'-'9'), и/или символ подчеркивания ('_'). - Строка SubCompatibleID содержит только символы ASCII только в верхнем регистре ('A'-'Z'), символы чисел ('0'-'9'), и/или символ подчеркивания ('_'). - Количество дескрипторов функции должно быть равно значению bCount в заголовке.
Программное обеспечение перейдет к состоянию "MS OS Container ID Descriptor Query".
Windows 7 ввела концепцию Container ID, которая используется для группирования всех функций, которые являются частью физического устройства. Для подробностей, как USB генерирует идентификаторы ID, пожалуйста см. этот отчет.
Если VID/PID/Revision устройства была ранее помечена через реестр как не поддерживающая Container ID Descriptor, драйвер хаба перейдет в состояние "Language ID Query".
Если ни дескриптор хаба, ни пространство имен ACPI не опишут устройство как несъемное (non-removable), и устройство поддерживает MS OS Descriptor, стек USB запросит у устройства MS OS Container ID, иначе драйвер хаба перейдет в состояние "Language ID Query".
Устройство должно показать поддержку container ID путем установки бита 1 поля bFlags в MS OS Descriptor. Если этот бит установлен, то программное обеспечение выдаст запрос для заголовка MS OS Container ID Descriptor. Пакет настройки (setup packet) укажет wIndex 6, и Device в качестве получателя. Если дескриптор успешно возвращен, драйвер хаба его проверит следующим образом:
• Возвращенное количество байт должно быть равно размеру заголовка MS OS Container ID Descriptor. • Поле bcdVersion заголовка MS OS Container ID Descriptor должно быть равно 0x100. • Поле wIndex заголовка MS OS Container ID Descriptor должно быть 6. • Поле dwLength заголовка MS OS Container ID Descriptor должно быть равно размеру MS OS Container ID Descriptor.
Если заголовок успешно проверен, программное обеспечение выдаст запрос на весь MS OS Container ID Descriptor. Пакет настройки (setup packet) снова укажет wIndex 6, и Device как получателя.
Если дескриптор успешно возвращен, драйвер хаба проверит его следующим образом:
• Возвращенное количество байт равно определенному размеру MS OS Container ID Descriptor. • Container ID не должен содержать везде нули.
Если по любой причине запрос Container ID Descriptor потерпел неудачу, будет установлено значение реестра, чтобы показать драйверу хаба, что он должен пропустить этот запрос в будущих энумерациях устройств с теми же VID/PID/Revision. Драйвер хаба запретит порт и попытается повторить энумерацию путем перехода в состояние "First Port Reset". Если драйвер хаба уже сделал 3 попытки энумерации, то энумерация будет прекращена и менеджеру PnP будет сообщено о неизвестном устройстве USB (Unknown Device).
Драйвер хаба USB запросит устройство выдать массив идентификаторов поддерживаемых языков (идентификаторы language ID). Драйвер хаба выдаст запрос для строкового дескриптора по индексу 0, в котором содержится массив 2-байтных кодов LANGID, поддерживаемых устройством, как это определено в секции 9.6.7 спецификации USB 2.0.
Драйвер хаба выполнит стандартную проверку строкового дескриптора для дескриптора Language ID, как это было описано в состоянии "Serial Number String Descriptor Query". Если найденная строка допустимая, драйвер хаба кэширует данные, возвращенные в строке. Затем перейдет к состоянию "Product ID String Query".
Если поле iProduct дескриптора устройства (USB Device Descriptor) не равно 0, то драйвер хаба выдаст запрос строки Product ID, с использованием индекса строки, указанного в поле iProduct с английским идентификатором языка (English language ID 0x409).
Если запрос строкового дескриптора завершился успешно, то то драйвер хаба выполнит проверку Product ID как стандартную проверку для строкового дескриптора. Если проверка была успешна, драйвер хаба кэширует строку. Затем он перейдет к состоянию "Device Qualifier Descriptor Query".
Если устройство было подключено к хабу USB 1.1, работает на скорости Full-Speed, и его поле bcdUSB дескриптора устройства (USB Device Descriptor) больше или равно 0x200, то драйвер хаба выдаст запрос GET_DESCRIPTOR для дескриптора с типом DEVICE_QUALIFIER (6).
Успешное завершение запроса покажет, что устройство может поддерживать работу на скорости USB 2.0 high-speed.
Duplicate Device Detection - детектирование дубликатов устройств. Стек драйвера USB должен разбираться с артефактами разработки EHCI companion controller, где устройство может быстро перемещаться между USB 2.0 EHCI контроллером хоста и companion USB 1.1 controller, когда контроллер USB 2.0 разрешен или запрещен. Это создает сценарий, когда менеджеру PnP может быть сообщено о новом экземпляре устройства USB до того, как предыдущий экземпляр на другом хост-контроллере сообщил о своем удалении менеджеру PnP. Это приведет к ошибочной проверке, если устройство имеет серийный номер, поскольку менеджер PnP увидел бы, что два узла устройства сообщают об одинаковом уникальном идентификаторе экземпляра. Такое поведение может произойти, когда устройство USB перемещается на другой порт, пока система находится в спящем, приостановленном режиме (suspended).
Драйвер хаба USB поддерживает список всех устройств USB, подключенных к системе в настоящий момент, и сообщает об этом менеджеру PnP. Если текущее энумерованное устройство имеет серийный номер, программное обеспечение просмотрит этот список на предмет поиска любого устройства, которое имеет те же vendor ID, product ID, номер ревизии и серийный номер. Если не найдено совпадающего устройства, драйвер хаба перейдет в состояние "Report New Device To PnP Manager" (сообщить о новом устройстве менеджеру PnP).
Если найдено совпадающее устройство, драйвер хаба обработает это по следующей логике:
1. Если совпавшее устройство и энумерованное устройство находится в том же самом порту, то это случай, когда устройство быстро было отключено/заново подключено (возможно случайное изменение состояния соединения). Тогда не будет предпринято никаких действий, так как об удалении предыдущего экземпляра было сообщено одновременно с тем, как был подключен новый экземпляр. 2. Если совпавшее устройство детектируется, как более не присутствующее физически на шине, то программное обеспечение делает задержку 5 секунд, ожидая что о совпавшем устройстве будет сообщено менеджеру PnP как об извлеченном. Если совпавшее устройство не сообщает о себе как об извлеченном по окончании задержки, то порт запрещается и делается попытка повторной энумерации путем перехода к состоянию "First Port Reset". Если программное обеспечение уже сделало 3 попытки энумерации, то порт запрещается и энумерация отменяется. Менеджеру PnP не будет сообщено об устройстве. 3. Если совпавшее устройство все еще физически присутствует на шине, то это случай двух идентичных устройств с одинаковым серийным номером. Тогда серийный номер у только что энумерованного устройства будет отброшен, чтобы предотвратить попадание менеджера PnP в состояние ошибки.
В этой точке устройство USB полностью и успешно прошло энумерацию, и драйвер хаба сообщит менеджеру PnP про новое устройство. Это приведет к вызову IoInvalidateDeviceRelations, и затем сообщению нового устройства при обработке IRP_MN_QUERY_DEVICE_RELATIONS/BusRelations.