Windows API USB IO (winusb.dll)

Изменить: этот вопрос менялся со временем. Основной вопрос заключался в том, как подключиться к USB-устройству и читать / писать с него в Windows. В конце концов я ответил на вопрос с помощью @benvoigt.

Я написал библиотеку Hid, которая записывает и читает с / с Hid USB-устройств. Это работает хорошо. Однако устройство, к которому я подключаюсь, переключилось с скрытого доступа на обычный USB. Hid-код не работает при подключении к другому типу устройства. Моя цель сейчас - подключиться к интерфейсу USB (в отличие от интерфейса Hid) и читать / писать в / из него.

На всех платформах, имеющих доступ к USB, мы должны запрашивать интерфейсы, существующие для USB-устройства, а затем «запрашивать» интерфейс для чтения и записи. Вот код из образца LibUsbDotNet (LibUsb - это рабочая USB-библиотека C, а LibUsbDotNet ее обертывает) https://github.com/LibUsbDotNet/LibUsbDotNet/blob/master/src/Examples/Read.Write/ReadWrite.cs.

            using (var context = new UsbContext())
            {
                context.SetDebugLevel(LogLevel.Info);

                //Get a list of all connected devices
                var usbDeviceCollection = context.List();

                //Narrow down the device by vendor and pid
                var selectedDevice = usbDeviceCollection.FirstOrDefault(d => d.ProductId == ProductId && d.VendorId == VendorId);

                //Open the device
                selectedDevice.Open();

                //Get the first config number of the interface
                selectedDevice.ClaimInterface(selectedDevice.Configs[0].Interfaces[0].Number);

                //Open up the endpoints
                var writeEndpoint = selectedDevice.OpenEndpointWriter(WriteEndpointID.Ep01);
                var readEnpoint = selectedDevice.OpenEndpointReader(ReadEndpointID.Ep01);

                //Create a buffer with some data in it
                var buffer = new byte[64];
                buffer[0] = 0x3f;
                buffer[1] = 0x23;
                buffer[2] = 0x23;

                //Write three bytes
                writeEndpoint.Write(buffer, 3000, out var bytesWritten);

                var readBuffer = new byte[64];

                //Read some data
                readEnpoint.Read(readBuffer, 3000, out var readBytes);
            }
}

У меня такое ощущение, что LibUsb обеспечивает открытие интерфейсов / конечных точек в C следующим образом (https://github.com/libusb/libusb/blob/c6f3866414e8deeee19e8a9f10f20bde9cb408d3/libusb/os/windows_winus2rel=). Здесь вызывается Initialize: https://github.com/libusb/libusb/blob/c6f3866414e8deeee19e8a9f10f20bde9cb408d3/libusb/os/windows_winusb.c#L2225, где мой код дает сбой.

Небольшой отрывок информации: это определенно устройство WinUSB. Я вижу это здесь:

введите описание изображения здесь

Основываясь на комментариях других людей и образце кода, я вижу, что мне нужно использовать winusb.dll. Я могу вызвать CreateFile, чтобы получить дескриптор устройства. Согласно другому образцу кода, который я видел, следующим шагом будет вызов WinUsb_Initialize. Однако, когда я вызываю это, я получаю код ошибки 8 (ERROR_NOT_ENOUGH_MEMORY). Здесь есть некоторая информация https://docs.microsoft.com/en-us/windows/desktop/api/winusb/nf-winusb-winusb_initialize. Но я не совсем понимаю, что меня просят сделать. Это мой код на данный момент:

    public override async Task InitializeAsync()
    {
        Dispose();

        if (string.IsNullOrEmpty(DeviceId))
        {
            throw new WindowsException($"{nameof(DeviceDefinition)} must be specified before {nameof(InitializeAsync)} can be called.");
        }

        _DeviceHandle = APICalls.CreateFile(DeviceId, (APICalls.GenericWrite | APICalls.GenericRead), APICalls.FileShareRead | APICalls.FileShareWrite, IntPtr.Zero, APICalls.OpenExisting, APICalls.FileAttributeNormal | APICalls.FileFlagOverlapped, IntPtr.Zero);

        var errorCode = Marshal.GetLastWin32Error();

        if (errorCode > 0) throw new Exception($"Write handle no good. Error code: {errorCode}");

        if (_DeviceHandle.IsInvalid) throw new Exception("Device handle no good");

        var isSuccess = WinUsbApiCalls.WinUsb_Initialize(_DeviceHandle, out var interfaceHandle);

        errorCode = Marshal.GetLastWin32Error();

        if (!isSuccess) throw new Exception($"Initialization failed. Error code: {errorCode}");

        IsInitialized = true;

        RaiseConnected();
    }

Вы можете клонировать ветку этого репо здесь: https://github.com/MelbourneDeveloper/Device.Net/tree/WindowsUsbDevice. Просто запустите проект Usb.Net.WindowsSample.

Я тоже пробовал это и получил точно такой же результат:

public override async Task InitializeAsync()
{
    Dispose();

    if (string.IsNullOrEmpty(DeviceId))
    {
        throw new WindowsException($"{nameof(DeviceDefinition)} must be specified before {nameof(InitializeAsync)} can be called.");
    }

    _DeviceHandle = APICalls.CreateFile(DeviceId, (APICalls.GenericWrite | APICalls.GenericRead), APICalls.FileShareRead | APICalls.FileShareWrite, IntPtr.Zero, APICalls.OpenExisting, APICalls.FileAttributeNormal | APICalls.FileFlagOverlapped, IntPtr.Zero);

    var errorCode = Marshal.GetLastWin32Error();

    if (errorCode > 0) throw new Exception($"Write handle no good. Error code: {errorCode}");

    var interfaceHandle = new IntPtr();

    var pDll = NativeMethods.LoadLibrary(@"C:\GitRepos\Device.Net\src\Usb.Net.WindowsSample\bin\Debug\net452\winusb.dll");

    var pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "WinUsb_Initialize");

    var initialize = (WinUsb_Initialize)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(WinUsb_Initialize));

    var isSuccess = initialize(_DeviceHandle, ref interfaceHandle);

    errorCode = Marshal.GetLastWin32Error();

    if (!isSuccess) throw new Exception($"Initialization failed. Error code: {errorCode}");

    IsInitialized = true;

    RaiseConnected();
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool WinUsb_Initialize(SafeFileHandle DeviceHandle, ref IntPtr InterfaceHandle);

Я твердо уверен, что что-то не так с самой реализацией WinUSB устройства. Он работает с другими библиотеками, такими как LibUsb, UWP и Android, но WinUsb, похоже, не любит. Я пробовал эту библиотеку: https://github.com/madwizard-thomas/winusbnet и это также не удается выполнить тот же вызов с тем же кодом ошибки. Строка, на которой происходит сбой и код ошибки 8, находится здесь: https://github.com/madwizard-thomas/winusbnet/blob/8f62d751a99be1e31d34b91115715d60aeff2dfc/WinUSBNet/API/WinUSBDevice.cs=

Что здесь не так с моим кодом? Что мне нужно сделать, чтобы выделить память для вызова WinUsb_Initialize?

Как мне использовать Windows API winusb.dll? Какие вызовы API мне нужно сделать, чтобы заявить и связать с конечной точкой для чтения и записи?

Было бы очень полезно, если бы кто-нибудь мог указать мне на простой пример C или C #, который читает и записывает на USB-устройство и действительно работает.

Вывод WinDBG:

************* Сводка проверки пути ************** Время отклика (мс) Местоположение отложено
srv * Путь поиска символов: srv * Исполняемый путь поиска: ModLoad: 00000236157c0000 00000236157c8000 Usb.Net.WindowsSample.exe ModLoad: 00007ffb62880000 00007ffb62a61000 ntdll.dll ModLoad: 00007ffb60f40000 00007ffb610d0000 C: \ WINDOWS \ System32 \ user32.dll ModLoad: 00007ffb_732_dll WINDOWS \ C: 00007ffb_732_5ed: 00007ffb_732_5ed: 00007ffb_732_5ed : 00007ffb4e1b0000 00007ffb4e214000 C: \ WINDOWS \ SYSTEM32 \ MSCOREE.DLL ModLoad: 00007ffb612a0000 00007ffb612c8000 C: \ WINDOWS \ System32 \ GDI32.dll onecore \ windows \ core \ console \ open \ src \ renderer \ gdi \ invalidate.cpp. (121) exe! 00007FF7169FE2AF: (вызывающий: 00007FF7169FF414) ReturnHr (1) tid (4230) 80070578 Неверный дескриптор окна. ModLoad: 00007ffb60990000 00007ffb60a42000
C: \ WINDOWS \ System32 \ KERNEL32.dll ModLoad: 00007ffb5f000000 00007ffb5f192000 C: \ WINDOWS \ System32 \ gdi32full.dll ModLoad: 00007ffb60d90000 00007ffb60f03000: C: \ WINDOWB60d90000 00007ffb60f03000: C: \ WINDOWS_C: \ F03000: C: \ WINDOWS_3000: \ \ WINDOWS \ System32 \ KERNELBASE.dll ModLoad: 00007ffb60610000 00007ffb606d2000 C: \ WINDOWS \ System32 \ OLEAUT32.dll ModLoad: 00007ffb60f10000 00007ffb60f3d000 C: \ WINDOWS \ System32 \ IMM32.DLL

************* Сводка проверки пути ************** Время отклика (мс) Местоположение отложено
srv * Путь поиска символов: srv * Исполняемый путь поиска: ModLoad: 00007ff7169f0000 00007ff716a8f000 conhost.exe ModLoad: 00007ffb61340000 00007ffb62780000 C: \ WINDOWS \ System32 \ shell32.dll ModLoad: 00007ffb5cd80000 00007ffb5cda9000
C: \ WINDOWS \ system32 \ dwmapi.dll ModLoad 000070000_000_dwmapi.dll: \ WINDOWS \ System32 \ cfgmgr32.dll ModLoad: 00007ffb5f530000 00007ffb5fc3d000
C: \ WINDOWS \ System32 \ windows.storage.dll onecore \ windows \ core \ console \ open \ src \ renderer \ gdi \ invalidate.cpp (121) \ conhost .exe! 00007FF7169FE2AF: (вызывающий: 00007FF7169FF414) ReturnHr (2) tid (4230) 80070578 Неверный дескриптор окна. ModLoad: 00007ffb61140000 00007ffb61191000
C: \ WINDOWS \ System32 \ shlwapi.dll ModLoad: 00007ffb60990000 00007ffb60a42000 C: \ WINDOWS \ System32 \ KERNEL32.DLL ModLoad: 00007ffb5ec30000 00007ffb5ec41000: C: \ WINDOWS_INDOWS_Core_Core_C: \ C: \ WINDOWS \ System32 \ KERNELBASE.dll ModLoad: 00007ffb5ec10000 00007ffb5ec2f000 C: \ WINDOWS \ System32 \ profapi.dll ModLoad: 00007ffb5ebc0000 00007ffb5ec0c000
C: \ WINDOWS \ System32 \ WINDOWS \ WINDOWS \ WINDOWS \ WINDOWS \ WINDOWS \ WINDOWS \ WINDOWS \ WINDOWS \ WINDOWS \ FIND_000 \ ModLoad: 000062880000 00007ffbBlack_DBF_DB_000: 0000_5 .DLL ModLoad: 00007ffb5f490000 00007ffb5f52f000
C: \ WINDOWS \ System32 \ msvcp_win.dll ModLoad: 00007ffb5f1a0000 00007ffb5f29a000 C: \ WINDOWS \ System32 \ ucrtbase.dll ModLoad: 00007ffb606e0000 00007ffbSystem60789000 C: \ WINDOWS606e0000 00007ffbSystem60789000 C: \ WINDOWS C: \ WINDOWS \ WinSxS \ amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.17134.472_none_fb3f9af53068156d \ comctl32.DLL ModLoad: 00007ffb5ca60000 00007ffb5caf8000 System: \ WINDOWC_WINDOWC_WINDOWC_WINDOWC_C: \ WINDOWS_WINDOWC_WINDOWC_WINDOWC_WINDOWC_C: \ WINDOWC_WINDOWC_WINDOWC_WINDOWC_C: \ WINDOWC_WINDOWC_C: \ WINDOWC_WINDOWC_WINDOWC_C: \ WINDOWC_WINDOWC_WINDOWC_System. dll ModLoad: 00007ffb601e0000 00007ffb603 04000 C: \ WINDOWS \ System32 \ RPCRT4.dll ModLoad: 00007ffb60a60000 00007ffb60d82000
C: \ WINDOWS \ System32 \ combase.dll ModLoad: 00007ffb5fc40000 00007ffb5fcba000 C: \ WINDOWS \ System32 \ bcryptPrimitives.dll advapi32.dll ModLoad: 00007ffb610d0000 00007ffb6112b000
C: \ WINDOWS \ System32 \ sechost.dll ModLoad: 00007ffb57b30000 00007ffb57bc6000 C: \ WINDOWS \ System32 \ TextInputFramework.dll (3d80.256c): исключение инструкции прерывания - код 80000003 (первый шанс) ntdll! LdrpDoDebuggerBreak + 0x30: 00007ffb`6294c93c cc
int 3


person Christian Findlay    schedule 09.12.2018    source источник
comment
Как вы гарантируете, что ваш доступ выровнен и использует точные значения, кратные размеру сектора. Почему нет минимального воспроизводимого примера?   -  person David Heffernan    schedule 09.12.2018
comment
Я думаю, что ответ на этот вопрос состоит в том, что я пробовал все размеры секторов (размеры буфера данных), о которых я знаю, и все они дают одинаковый результат. Отчасти проблема заключается в том, что ожидаемый размер устройства составляет 64 байта, но если я укажу 64 байта, я получаю сообщение об ошибке. Мне нужно отправить 64, но укажите 65. Есть какая-то проблема с Windows IO. Чтобы ответить на ваш второй вопрос, если у вас нет аппаратного кошелька Trezor, проверить пример невозможно. Мой вопрос является общим, а не конкретным: есть ли какие-либо советы по поводу различий между разговором с обычным USB-устройством и Hid-устройством?   -  person Christian Findlay    schedule 11.12.2018
comment
Общие вопросы здесь не подходят. Есть ли у кого-нибудь общие советы - не подходящий вопрос. Что касается выравнивания и размера буфера, вы не можете знать, что используется, потому что вы делегировали это потоку, который может выполнять свою собственную буферизацию. Используйте ReadFile напрямую. На C ++ намного проще. По крайней мере, пока занимается отладкой и обучением.   -  person David Heffernan    schedule 11.12.2018
comment
Почему я должен использовать ReadFile напрямую? Я пробовал, и это не сработало, но почему это лучше?   -  person Christian Findlay    schedule 11.12.2018
comment
Потому что тогда вы контролируете выравнивание и размер буфера. Вы также будете получать сообщения об ошибках Win32. Вам было бы намного проще проверить это с помощью кода C ++. Существует так много дополнительных способов, позволяющих что-то пойти не так, используя C #. Есть ли у поставщиков устройств образцы кода?   -  person David Heffernan    schedule 11.12.2018
comment
Когда я попытался вызвать WriteFile, я получил ошибку 1 и 6. Я написал для него код для трех платформ: Android, UWP и Windows. Он работает хорошо, но запись в Hid явно отличается от записи на USB. Мой вопрос: что я должен делать по-другому, разговаривая с обычным USB-устройством, а не с Hid?   -  person Christian Findlay    schedule 11.12.2018
comment
Вот как я контролирую размер буфера: github.com/MelbourneDeveloper/Hid.Net/blob/. Это всегда 64 входа и выхода. Я тщательно это проверил и знаю, что это работает.   -  person Christian Findlay    schedule 11.12.2018
comment
@DavidHeffernan Я убрал этот вопрос. По сути, мне нужно знать, какие вызовы API нужно сделать, чтобы потребовать интерфейс / конечную точку USB для чтения / записи на устройство USB. На C или на C # ответ не имеет значения. Мне просто нужно знать, что такое вызовы API.   -  person Christian Findlay    schedule 26.12.2018
comment
InterfaceHandle должно быть out, а не ref, но это не должно сильно изменить вашу проблему. В остальном декларация выглядит нормально. Я не могу воспроизвести ошибку 8. Когда я пытался открыть устройства на моем ПК, я получаю только ошибку 50 (ERROR_NOT_SUPPORTED). Возможно, проблема в этом конкретном USB-устройстве Trezor.   -  person Simon Mourier    schedule 27.12.2018


Ответы (2)


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

Ограничение передачи записи короткими пакетами

Драйвер стека драйверов USB не накладывает тех же ограничений на размер пакета при записи на устройство, которые он налагает при чтении с устройства. Некоторым клиентским драйверам приходится часто передавать небольшие объемы управляющих данных для управления своими устройствами. В таких случаях нецелесообразно ограничивать передачу данных пакетами одинакового размера. Следовательно, стек драйверов не придает особого значения пакетам, размер которых меньше максимального размера конечной точки во время записи данных. Это позволяет клиентскому драйверу разбивать большую передачу на устройство на несколько URB любого размера, меньшего или равного максимальному.

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

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

Из USB-передачи и размеров пакетов в MSDN


Что до остального ... в зависимости от того, объявляет ли устройство себя составным устройством или нет, загрузчик драйверов Windows выберет один или несколько драйверов для подключения к устройству. Эти драйверы обрабатывают выбор конечной точки на устройстве для связи. Итак, из пользовательского пространства обычно достаточно открыть интерфейс драйвера и начать ввод-вывод. Например, драйвер класса HID знает, как идентифицировать и активировать конечную точку HID.

Поскольку у вас есть вещи, работающие с UWP, вероятно, у вас загружен драйвер WinUSB (поскольку это Шаг №1). Поэтому вы будете использовать WinUSB API, чтобы поговорить с ним.

Вот документация для API C и C ++ для WinUSB. В нем есть примеры настройки конечных точек, и он выглядит немного менее беспорядочным, чем цитируемый вами код libusb (хотя это может иметь отношение также к форматированию и стилю кода).

person Ben Voigt    schedule 26.12.2018
comment
Комментарии не подлежат расширенному обсуждению; этот разговор был перемещен в чат. - person Samuel Liew♦; 28.12.2018

Вот как подключиться и читать / писать на USB-устройство через библиотеку WinUSB

Весь этот код содержится в репозитории Device.Net: https://github.com/MelbourneDeveloper/Device.Net. Здесь есть образец. Он автоматически переключается между Hid и UWP в зависимости от того, какое устройство подключено. https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Usb.Net.WindowsSample/Program.cs

Подключайтесь и получайте информацию

Пишите и читайте

Вызовы API

public static class Kernel32APICalls
{
    //Abridged

    #region Kernel32
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
    #endregion    
}

public static partial class WinUsbApiCalls
{
    public const uint DEVICE_SPEED = 1;
    public const byte USB_ENDPOINT_DIRECTION_MASK = 0X80;
    public const int WritePipeId = 0x80;

    /// <summary>
    /// Not sure where this constant is defined...
    /// </summary>
    public const int DEFAULT_DESCRIPTOR_TYPE = 0x01;

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_ControlTransfer(IntPtr InterfaceHandle, WINUSB_SETUP_PACKET SetupPacket, byte[] Buffer, uint BufferLength, ref uint LengthTransferred, IntPtr Overlapped);

    [DllImport("winusb.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool WinUsb_GetAssociatedInterface(SafeFileHandle InterfaceHandle, byte AssociatedInterfaceIndex, out SafeFileHandle AssociatedInterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, ushort LanguageID, out USB_DEVICE_DESCRIPTOR deviceDesc, uint BufferLength, out uint LengthTransfered);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_Free(SafeFileHandle InterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_Initialize(SafeFileHandle DeviceHandle, out SafeFileHandle InterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryDeviceInformation(IntPtr InterfaceHandle, uint InformationType, ref uint BufferLength, ref byte Buffer);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryInterfaceSettings(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, out USB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryPipe(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, byte PipeIndex, out WINUSB_PIPE_INFORMATION PipeInformation);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_ReadPipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_SetPipePolicy(IntPtr InterfaceHandle, byte PipeID, uint PolicyType, uint ValueLength, ref uint Value);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_WritePipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);
}

Особая благодарность @benvoigt за то, что помог мне решить эту проблему.

person Christian Findlay    schedule 28.12.2018