Как получить дескрипторы USB (приложение UWP)

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

  • Понимание структуры USB-устройства
  • Получение стандартных дескрипторов USB
  • Получение пользовательских дескрипторов

Важные API

Дескрипторы USB

USB-устройство описывает свои возможности в двух основных дескрипторах: дескриптор устройства и дескриптор конфигурации.

USB-устройство должно предоставить дескриптор устройства , содержащий сведения о USB-устройстве в целом. Если устройство не предоставляет дескриптор или предоставляет неправильный дескриптор, Windows не может загрузить драйвер устройства. Наиболее важными сведениями в дескрипторе является идентификатор оборудования устройства для устройства (сочетание полей идентификатора поставщика и идентификатора продукта ). На основе этой информации Windows может подобрать предустановленный драйвер для устройства. Другая информация, которая является ключом, — максимальный размер пакета конечной точки по умолчанию (MaxPacketSize0). Конечная точка по умолчанию — это цель всех запросов управления, которые хост отправляет устройству для его конфигурации.

Длина дескриптора устройства фиксирована.

USB-устройство также должно предоставить полный дескриптор конфигурации. Начальная часть дескриптора имеет фиксированную длину 9 байтов, а остальные — это переменная длина в зависимости от количества интерфейсов и конечных точек, которые поддерживают эти интерфейсы. Часть фиксированной длины содержит сведения о конфигурации USB: количество интерфейсов, поддерживаемых и потребление энергии, когда устройство находится в этой конфигурации. За этими начальными 9 байтами следует переменная часть дескриптора, предоставляющая сведения обо всех USB-интерфейсах. Каждый интерфейс состоит из одного или нескольких параметров интерфейса, и каждый параметр состоит из набора конечных точек. Дескрипторы для интерфейсов, альтернативных параметров и конечных точек включены в часть переменной.

Подробное описание макета устройства см. в разделе "Стандартный usb-дескриптор".

Перед началом работы

  • Необходимо открыть устройство и получить объект UsbDevice. Прочтите о том, как подключиться к USB-устройству(приложение для UWP).
  • Полный код, показанный в этом разделе, можно увидеть в примере CustomUsbDeviceAccess, Scenario5_UsbDescriptors файлах.
  • Получить информацию о компоновке устройства. Usbview.exe (включенное в пакет Windows Software Development Kit (SDK) для Windows 8) — это приложение, которое позволяет просматривать все USB-контроллеры и USB-устройства, подключенные к ним. Для каждого подключенного устройства можно просмотреть дескрипторы устройств, конфигурации, интерфейса и конечных точек, чтобы получить представление о возможностях устройства.

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

Приложение UWP может получить дескриптор устройства из ранее полученного объекта UsbDevice, получив значение свойства UsbDevice.DeviceDescriptor.

В этом примере кода показано, как заполнить строку значениями полей из дескриптора устройства.

String GetDeviceDescriptorAsString (UsbDevice device)
{
    String content = null;

    var deviceDescriptor = device.DeviceDescriptor;

    content = "Device Descriptor\n"
            + "\nUsb Spec Number : 0x" + deviceDescriptor.BcdUsb.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nMax Packet Size (Endpoint 0) : " + deviceDescriptor.MaxPacketSize0.ToString("D", NumberFormatInfo.InvariantInfo)
            + "\nVendor ID : 0x" + deviceDescriptor.IdVendor.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nProduct ID : 0x" + deviceDescriptor.IdProduct.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nDevice Revision : 0x" + deviceDescriptor.BcdDeviceRevision.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nNumber of Configurations : " + deviceDescriptor.NumberOfConfigurations.ToString("D", NumberFormatInfo.InvariantInfo);

    return content;
}

Выходные данные показаны здесь:

дескриптор usb-устройства.

Как получить дескриптор конфигурации

Чтобы получить фиксированную часть дескриптора конфигурации из ранее полученного объекта UsbDevice ,

  1. Получите объект UsbConfiguration из UsbDevice. UsbConfiguration представляет первую USB-конфигурацию, определенную устройством, и по умолчанию выбирается базовым драйвером устройства.
  2. Получите значение свойства UsbConfiguration.ConfigurationDescriptor .

Фиксированная часть дескриптора конфигурации указывает характеристики питания устройства. Например, можно определить, черпает ли устройство питание от шины или внешнего источника (см. UsbConfigurationDescriptor.SelfPowered). Если устройство получает питание от шины, сколько энергии (в миллиампах) потребляется (см. usbConfigurationDescriptor.MaxPowerMilliamps). Кроме того, вы можете определить, может ли устройство пробудить само себя или систему из состояния низкой мощности, получив значение UsbConfigurationDescriptor.RemoteWakeup.

В этом примере кода показано, как получить фиксированную часть дескриптора конфигурации в строке.

String GetConfigurationDescriptorAsString(UsbDevice device)
{
    String content = null;

    var usbConfiguration = device.Configuration;
    var configurationDescriptor = usbConfiguration.ConfigurationDescriptor;

    content = "Configuration Descriptor\n"
            + "\nNumber of Interfaces : " + usbConfiguration.UsbInterfaces.Count.ToString("D", NumberFormatInfo.InvariantInfo)
            + "\nConfiguration Value : 0x" + configurationDescriptor.ConfigurationValue.ToString("X2", NumberFormatInfo.InvariantInfo)
            + "\nSelf Powered : " + configurationDescriptor.SelfPowered.ToString()
            + "\nRemote Wakeup : " + configurationDescriptor.RemoteWakeup.ToString()
            + "\nMax Power (milliAmps) : " + configurationDescriptor.MaxPowerMilliamps.ToString("D", NumberFormatInfo.InvariantInfo);

    return content;
}

Выходные данные показаны здесь:

дескриптор конфигурации usb.

Как получить дескрипторы интерфейса

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

USB-интерфейс — это коллекция параметров интерфейса. Таким образом, дескриптор не описывает весь интерфейс. Дескриптор интерфейса обозначает структуру данных, которая описывает настройку в интерфейсе.

Пространство имен Windows.Devices.Usb предоставляет объекты, которые можно использовать для получения сведений о каждом USB-интерфейсе и всех дескрипторах интерфейсов (для альтернативных параметров), включенных в этот интерфейс. дескрипторы из переменно длинной части дескриптора конфигурации.

Чтобы получить дескрипторы интерфейса из UsbConfiguration,

  1. Получите массив интерфейсов в конфигурации с помощью свойства UsbConfiguration.UsbInterfaces.
  2. Для каждого интерфейса (UsbInterface) получите следующие сведения:
    • Каналы передачи данных для массивного и прерывающего ввода-вывода, которые активны и могут передавать данные.
    • Массив альтернативных параметров в интерфейсе.
    • Массив дескрипторов интерфейса.

Этот пример кода получает все объекты UsbInterface для конфигурации. Из каждого объекта вспомогательный метод получает количество альтернативных настроек и открытые массовые и интерфейсные каналы. Если устройство поддерживает несколько интерфейсов, класс устройств, подкласс и коды протоколов каждого интерфейса могут отличаться. Однако все дескрипторы интерфейса для альтернативных параметров должны указывать одинаковые коды. В этом примере метод получает класс устройства, подкласс и коды протоколов из дескриптора интерфейса первого параметра, чтобы определить код для всего интерфейса.

String GetInterfaceDescriptorsAsString(UsbDevice device)
{
    String content = null;

    var interfaces = device.Configuration.UsbInterfaces;

    content = "Interface Descriptors";

        foreach (UsbInterface usbInterface in interfaces)
        {
            // Class/subclass/protocol values from the first interface setting.

            UsbInterfaceDescriptor usbInterfaceDescriptor = usbInterface.InterfaceSettings[0].InterfaceDescriptor;

            content +="\n\nInterface Number: 0x" +usbInterface.InterfaceNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nClass Code: 0x" +usbInterfaceDescriptor.ClassCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nSubclass Code: 0x" +usbInterfaceDescriptor.SubclassCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nProtocol Code: 0x" +usbInterfaceDescriptor.ProtocolCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of Interface Settings: "+usbInterface.InterfaceSettings.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Bulk In pipes: "+usbInterface.BulkInPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Bulk Out pipes: "+usbInterface.BulkOutPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Interrupt In pipes: "+usbInterface.InterruptInPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Interrupt Out pipes: "+usbInterface.InterruptOutPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo);
       }

    return content;
}

Выходные данные показаны здесь:

дескриптор usb-интерфейса.

Как получить дескрипторы конечных точек

Все конечные точки USB (кроме конечной точки управления по умолчанию) должны иметь дескрипторы конечных точек. Чтобы получить дескрипторы конечных точек для конкретной конечной точки, необходимо знать, какой интерфейс и альтернативный параметр принадлежит конечной точке.

  1. Получите объект UsbInterface , содержащий конечную точку.

  2. Получите массив альтернативных настроек с помощью UsbInterface.InterfaceSettings.

  3. В массиве найдите параметр (UsbInterfaceSetting), использующий конечную точку.

  4. В каждой настройке найдите конечную точку соединения, перечисляя массивы дескрипторов bulk и interrupt.

    Дескрипторы конечных точек представлены этими объектами:

Если устройство имеет только один интерфейс, можно использовать UsbDevice.DefaultInterface для получения интерфейса, как показано в этом примере кода. Здесь вспомогательный метод заполняет строку дескрипторами конечных точек, связанных с каналами настройки активного интерфейса.

private String GetEndpointDescriptorsAsString(UsbDevice device)
{
    String content = null;

    var usbInterface = device.DefaultInterface;
    var bulkInPipes = usbInterface.BulkInPipes;
    var bulkOutPipes = usbInterface.BulkOutPipes;
    var interruptInPipes = usbInterface.InterruptInPipes;
    var interruptOutPipes = usbInterface.InterruptOutPipes;

    content = "Endpoint Descriptors for open pipes";

    // Print Bulk In Endpoint descriptors
    foreach (UsbBulkInPipe bulkInPipe in bulkInPipes)
    {
        var endpointDescriptor = bulkInPipe.EndpointDescriptor;

        content +="\n\nBulk In Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
    }

    // Print Bulk Out Endpoint descriptors
    foreach (UsbBulkOutPipe bulkOutPipe in bulkOutPipes)
    {
        var endpointDescriptor = bulkOutPipe.EndpointDescriptor;

        content +="\n\nBulk Out Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
    }

    // Print Interrupt In Endpoint descriptors
    foreach (UsbInterruptInPipe interruptInPipe in interruptInPipes)
    {
        var endpointDescriptor = interruptInPipe.EndpointDescriptor;

        content +="\n\nInterrupt In Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
                + "\nInterval : " + endpointDescriptor.Interval.Duration.ToString();
    }

    // Print Interrupt Out Endpoint descriptors
    foreach (UsbInterruptOutPipe interruptOutPipe in interruptOutPipes)
    {
        var endpointDescriptor = interruptOutPipe.EndpointDescriptor;

        content +="\n\nInterrupt Out Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
                + "\nInterval : " + endpointDescriptor.Interval.Duration.ToString();
    }

    return content;
}

Выходные данные показаны здесь:

дескрипторы конечной точки usb.

Как получить пользовательские дескрипторы

Обратите внимание, что объекты UsbConfiguration, UsbInterface и UsbInterfaceSetting предоставляют свойство с именем дескрипторов. Это значение свойства извлекает массив дескрипторов, представленных объектами UsbDescriptor . Объект UsbDescriptor позволяет приложению получать дескрипторные данные в буфере. Свойства UsbDescriptor.DescriptorType и UsbDescriptor.Length хранят тип и длину буфера, необходимого для хранения дескриптора.

Заметка Первые два байта всех буферов дескриптора также указывают тип и длину дескриптора.

Например, свойство UsbConfiguration.Descriptors получает массив полного дескриптора конфигурации (части фиксированной и переменной длины). Первый элемент в этом массиве является дескриптором конфигурации фиксированной длины (так же, как и UsbConfigurationDescriptor), вторым элементом является дескриптор интерфейса первого альтернативного параметра и т. д.

Аналогичным образом свойство UsbInterface.Descriptors получает массив всех дескрипторов интерфейса и связанных дескрипторов конечных точек. Свойство UsbInterfaceSetting.Descriptors получает массив всех дескрипторов для этого параметра, например дескрипторов конечных точек.

Этот способ получения дескрипторов полезен, если приложение хочет получить пользовательские дескрипторы или другие дескрипторы, такие как дескрипторы компаньона конечной точки для устройств SuperSpeed.

В этом примере кода показано, как получить данные дескриптора в буфере из дескриптора конфигурации. В примере получается набор дескрипторов конфигурации и анализируется все дескрипторы, содержащиеся в этом наборе. Для каждого дескриптора используется объект DataReader для чтения буфера и отображения длины дескриптора и типа. Вы можете получить пользовательские дескрипторы, как показано в этом примере.

private String GetCustomDescriptorsAsString(UsbDevice device)
{
    String content = null;
    // Descriptor information will be appended to this string and then printed to UI
    content = "Raw Descriptors";

    var configuration = device.Configuration;
    var allRawDescriptors = configuration.Descriptors;

    // Print first 2 bytes of all descriptors within the configuration descriptor
    // because the first 2 bytes are always length and descriptor type
    // the UsbDescriptor's DescriptorType and Length properties, but we will not use these properties
    // in order to demonstrate ReadDescriptorBuffer() and how to parse it.

    foreach (UsbDescriptor descriptor in allRawDescriptors)
    {
        var descriptorBuffer = new Windows.Storage.Streams.Buffer(descriptor.Length);
        descriptor.ReadDescriptorBuffer(descriptorBuffer);

        DataReader reader = DataReader.FromBuffer(descriptorBuffer);

        // USB data is Little Endian according to the USB spec.
        reader.ByteOrder = ByteOrder.LittleEndian;

        // ReadByte has a side effect where it consumes the current byte, so the next ReadByte will read the next character.
        // Putting multiple ReadByte() on the same line (same variable assignment) may cause the bytes to be read out of order.
        var length = reader.ReadByte().ToString("D", NumberFormatInfo.InvariantInfo);
        var type = "0x" + reader.ReadByte().ToString("X2", NumberFormatInfo.InvariantInfo);

        content += "\n\nDescriptor"
                + "\nLength : " + length
                + "\nDescriptorType : " + type;
    }

    return content;
}