zephyrproject-rtos / zephyr

Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
https://docs.zephyrproject.org
Apache License 2.0
10.61k stars 6.5k forks source link

USB DFU implementation does not work with WinUSB because of missing device reset API #49821

Closed jalinei closed 2 years ago

jalinei commented 2 years ago

Describe the bug When uploading a signed image using dfu-util on windows, the transfers fails and output "Lost device after RESET ?".

From what I understood I'm not the only one facing this issue, It has been discussed on Discord both on the general channel but also on the MCUBOOT one.

The SoC I'm using is stm32g474re, but it seems to also affect other vendors. USB DFU works flawlessly on Linux I'm only experiencing the issue on Windows.

What I tried :

To Reproduce

Prj.conf relevant configuration is

# USB
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="MCUBOOT"
CONFIG_USB_COMPOSITE_DEVICE=n
CONFIG_USB_CDC_ACM=n
CONFIG_USB_DEVICE_PID=0x0101
CONFIG_USB_DEVICE_DFU_PID=0x0102

#Upgrade with swapping the memory partition without the need of scratch partition
CONFIG_BOOT_SWAP_USING_MOVE=y

# Enable USB Download Firmware Upgrade (DFU)
CONFIG_BOOT_USB_DFU_GPIO=y

I used Zadig v2.7 to load WinUSB v6.1 driver on MCUBOOT run-time dfu (2fe3:0101).

Expected behavior Dfu-util is supposed to be able to reset the USB from run-time to transfer mode.

Impact The project we are working on aims at having end users able to download binaries to the board using dfu-util. Most users are using Windows and this issue is a showstopper for us.

Logs and console output

When listing the DFU capable USB device, we already see that it fails at enumerating correctly.

C:\Users\jal\Downloads\dfu-util-0.9-win64\dfu-util-0.9-win64>dfu-util -l
dfu-util 0.9

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Cannot open DFU device 04f2:b6c6
Found Runtime: [2fe3:0101] ver=0301, devnum=33, cfg=1, intf=0, path="1-5", alt=0, name="UNKNOWN", serial="5056500D00310056"x

When flashing it gives

C:\Users\jal\Downloads\dfu-util-0.9-win64\dfu-util-0.9-win64>dfu-util -v -a 1 -D firmware.signed.bin 
dfu-util 0.9                                                                                                                                                                                                                                                                                                                                            Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/
Invalid DFU suffix signature 
A valid DFU suffix will be required in a future dfu-util release!!!
Cannot open DFU device 04f2:b6c6
Opening DFU capable USB device...
ID 2fe3:0101
Run-time device DFU version 0110
Claiming USB DFU Runtime Interface... Determining device status: state = appIDLE, status = 0                                                                                                                      Device really in Runtime Mode, send DFU detach request... 
Resetting USB...
Lost device after RESET? 

In triple verbosity I get the following :

Resetting USB...
libusb: debug [libusb_reset_device]
libusb: debug [libusb_release_interface] interface 0
libusb: debug [libusb_close]
libusb: debug [libusb_get_device_list]
libusb: debug [windows_get_device_list] found existing device for session [C1] (1.0)
libusb: debug [windows_get_device_list] found existing device for session [239] (2.0)
libusb: debug [get_api_type] driver(s): USBHUB3
libusb: debug [get_api_type] matched driver name against HUB API
libusb: debug [windows_get_device_list] found existing device for session [1EE] (2.1)
libusb: debug [get_api_type] driver(s): USBHUB3
libusb: debug [get_api_type] matched driver name against HUB API
libusb: debug [windows_get_device_list] found existing device for session [328] (1.1)
libusb: debug [windows_get_device_list] found existing device for session [2FC] (1.3)
libusb: debug [windows_get_device_list] found existing device for session [76] (1.4)
libusb: debug [windows_get_device_list] extra GUID: {0767B285-1E08-4A99-ACF8-7452F512DDFC}
libusb: debug [windows_get_device_list] found existing device for session [22B] (1.33)
libusb: debug [windows_get_device_list] found existing device for session [328] (1.1)
libusb: debug [windows_get_device_list] found existing device for session [1EE] (2.1)
libusb: debug [windows_get_device_list] found existing device for session [225] (1.2)
libusb: debug [get_api_type] driver(s): usbccgp
libusb: debug [get_api_type] matched driver name against Composite API
libusb: debug [windows_get_device_list] found existing device for session [76] (1.4)
libusb: debug [get_api_type] driver(s): WUDFRd
libusb: debug [get_api_type] lower filter driver(s): WinUsb
libusb: debug [get_api_type] matched lower filter driver name against WinUSB
libusb: debug [windows_get_device_list] found existing device for session [225] (1.2)
libusb: debug [get_api_type] driver(s): BTHUSB
libusb: debug [get_api_type] lower filter driver(s): ibtusb
libusb: debug [windows_get_device_list] found existing device for session [2FC] (1.3)
libusb: debug [get_api_type] driver(s): WinUSB
libusb: debug [get_api_type] matched driver name against WinUSB
libusb: debug [windows_get_device_list] found existing device for session [22B] (1.33)
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INT33D2&COL02#6&1061FB50&0&0001' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INT33D2&COL03#6&1061FB50&0&0002' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL03#5&994D12E&0&0002' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#SYNA30B7&COL04#5&B796EAC&0&0003' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#VID_8087&PID_0AC2#6&29A9C256&0&0000' (non USB HID, newly connected, etc.) - ignoring                 
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL09#5&994D12E&0&0008' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#CONVERTEDDEVICE&COL02#5&13060E3C&0&0001' (non USB HID, newly connected, etc.) - ignoring             
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#CONVERTEDDEVICE&COL03#5&13060E3C&0&0002' (non USB HID, newly connected, etc.) - ignoring             
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL02#3&207F9B7F&0&0001' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#VID_8087&PID_0AC2#6&1803663&0&0000' (non USB HID, newly connected, etc.) - ignoring                  
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL04#5&994D12E&0&0003' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL03#3&207F9B7F&0&0002' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL04#3&207F9B7F&0&0003' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL05#3&207F9B7F&0&0004' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#CONVERTEDDEVICE&COL01#5&13060E3C&0&0000' (non USB HID, newly connected, etc.) - ignoring             
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#VID_8087&PID_0AC2#6&13825999&0&0000' (non USB HID, newly connected, etc.) - ignoring                 
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL06#3&207F9B7F&0&0005' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL08#5&994D12E&0&0007' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL01#3&207F9B7F&0&0000' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL0A#3&207F9B7F&0&0009' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL07#3&207F9B7F&0&0006' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL05#5&994D12E&0&0004' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#VID_8087&PID_0AC2#6&38F5BAE3&0&0000' (non USB HID, newly connected, etc.) - ignoring                 
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL08#3&207F9B7F&0&0007' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL09#3&207F9B7F&0&0008' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#HPQ6001#3&9489F59&0&0000' (non USB HID, newly connected, etc.) - ignoring                            
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#SYNA30B7&COL01#5&B796EAC&0&0000' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL06#5&994D12E&0&0005' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL01#5&994D12E&0&0000' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INT33D2&COL01#6&1061FB50&0&0000' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#VID_8087&PID_0AC2#6&263C0A3E&0&0000' (non USB HID, newly connected, etc.) - ignoring                 
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#VID_8087&PID_0AC2#6&C8A8F4&0&0000' (non USB HID, newly connected, etc.) - ignoring                   
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#SYNA30B7&COL02#5&B796EAC&0&0001' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL07#5&994D12E&0&0006' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#INTC816&COL0B#3&207F9B7F&0&000A' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#WACF4233&COL02#5&994D12E&0&0001' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [windows_get_device_list] unlisted ancestor for '\\.\HID#SYNA30B7&COL03#5&B796EAC&0&0002' (non USB HID, newly connected, etc.) - ignoring                     
libusb: debug [get_api_type] driver(s): WinUSB
libusb: debug [get_api_type] matched driver name against WinUSB
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [parse_configuration] skipping descriptor 0xb
libusb: debug [parse_endpoint] skipping descriptor 25
libusb: debug [parse_endpoint] skipping descriptor b
libusb: debug [parse_endpoint] skipping descriptor 25
libusb: debug [parse_endpoint] skipping descriptor b
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_config_descriptor] index 0
Lost device after RESET?   

Environment (please complete the following information):

Additional context Not much more to add AFAIK

nordicjm commented 2 years ago

Issue also observed on nrf52833-based module and stm32f103-based module. Some old discussions on github mentioning this issue from 2+ years ago

jfischer-no commented 2 years ago

Issue also observed on nrf52833-based module and stm32f103-based module.

You can reproduce it on nRF52833DK?

jfischer-no commented 2 years ago

@Light411 Since you opened it here, can you reproduce the issue using Zephyr's sample samples/subsys/usb/dfu?

nordicjm commented 2 years ago

Issue also observed on nrf52833-based module and stm32f103-based module.

You can reproduce it on nRF52833DK?

BL653 DVK which is the same hardware/interface wise as nRF52833dk. After having more of a play with it, it seems to be the winusb driver, I tried installing the libusbK drivers using zadig instead, and after installing the libusbK package with system drivers, suddenly dfu-util can communicate with the device on windows. So it's a far cry faff from the ability to do a mcuboot serial recovery update with just a single executable and no drivers to needing the libusbK driver, libusbK system drivers and an executable for this. Perhaps this should be added to the documentation.

jfischer-no commented 2 years ago

After having more of a play with it, it seems to be the winusb driver, I tried installing the libusbK drivers using zadig instead, and after installing the libusbK package with system drivers, suddenly dfu-util can communicate with the device on windows. So it's a far cry faff from the ability to do a mcuboot serial recovery update with just a single executable and no drivers to needing the libusbK driver, libusbK system drivers and an executable for this. Perhaps this should be added to the documentation.

I agree that "mcuboot serial recovery update" is much easier to use for Windows OS users. I have no measurements regarding throughput but remember that USB DFU was faster. I do not get to test it until week 38.

jalinei commented 2 years ago

I tried using libusbK this afternoon, and I managed to download the image sucessfully on Windows !

IMO the best fix here would be to include the windows descriptor in ucb_dfu.c file so that Windows natively load libusbK driver for the given run-time and dfu mode VID:PID.

I've seen that windows descriptors are already in tree. I tried implementing them to try my luck implementing a WCID feature for my device but I was not able to make it work. More specifically, I was able to populate my descriptors, but I wasn't able to understand how to handle these descriptors to the usb enabling function. Do you think it could work ?

jfischer-no commented 2 years ago

I tried using libusbK this afternoon, and I managed to download the image sucessfully on Windows !

IMO the best fix here would be to include the windows descriptor in ucb_dfu.c file so that Windows natively load libusbK driver for the given run-time and dfu mode VID:PID.

I've seen that windows descriptors are already in tree. I tried implementing them to try my luck implementing a WCID feature for my device but I was not able to make it work. More specifically, I was able to populate my descriptors, but I wasn't able to understand how to handle these descriptors to the usb enabling function. Do you think it could work ?

If it is about Microsoft OS * Descriptors, please take a look at samples/subsys/usb/webusb (for example msos1_compatid_descriptor).

jalinei commented 2 years ago

I found two implementation of MS OS descriptors available in Zephyr CONFIG_USB_DEVICE_OS_DESC which from my understanding is only capable of MS OS descriptor V1 and is focused on WCID feature. CONFIG_USB_DEVICE_BOS which from my understanding is capable of the extended features offered by MS OS descriptor V2 that I indeed found in the very useful webusb sample.

I was thinking that the first one was more suitable for the task as usb_dfu.c only need to provide the descriptors needed for the WCID feature. Also from what I read, WCID has been deployed in 2012 while BOS v2 is more recent and would let older versions of windows aside of the support.

At the moment CONFIG_USB_DEVICE_OS_DESC seems to be used only by subsys/usb/device/class/netusb and by a test tests/subsys/usb/os_desc. I tried to implement it to check the feasibility but I didn't manage, I was not able to pass the descriptor to my usb_enable() function.

My main question here is : Would this fix the problem for windows users ? Second one : Is LibusbK supported by WCID ? Last one : How do vendor bootloaders and dfu compatible boards / device supports Windows in a plug and play fashion ? Using WCID + WinUsb or any other driver ? Or implementing custom drivers and linking them to their VID:PID ?

PS I found this great documentation on WCID: https://github.com/pbatard/libwdi/wiki/WCID-Devices

desowin commented 2 years ago

I tried using libusbK this afternoon, and I managed to download the image sucessfully on Windows !

This is expected due to the actual root cause of the issue. WinUSB does not support host initiated USB bus resets, while libusbK does.

IMO the best fix here would be to include the windows descriptor in ucb_dfu.c file so that Windows natively load libusbK driver for the given run-time and dfu mode VID:PID.

I think adjusting Zephyr for WinUSB limitations is better. USB Device Firmware Upgrade Specification, Revision 1.1 specifies: Bit 3: device will perform a bus detach-attach sequence when it receives a DFU_DETACH request. The host must not issue a USB Reset. (bitWillDetach) 0 = no 1 = yes

Zephyr sets bitWillDetach to 0 and hence DFU does not work with WinUSB, but does with libusbK. To have WinUSB compatible DFU implementation, device have to set bitWillDetach to 1 and perform the bus detach-attach sequence.

Second one : Is LibusbK supported by WCID ?

It can be supported if user (application explicitly run by user) installs the libusbK drivers before the device is plugged in.

Last one : How do vendor bootloaders and dfu compatible boards / device supports Windows in a plug and play fashion ? Using WCID + WinUsb or any other driver ? Or implementing custom drivers and linking them to their VID:PID ?

A lot of different approaches. Some use custom drivers, some use even custom vendor defined protocols. In my opinion using WCID + WinUSB together with USB DFU class is the most cost effective solution (i.e. least amount of effort required both in development and maintenance).

jfischer-no commented 2 years ago

Zephyr sets bitWillDetach to 0 and hence DFU does not work with WinUSB, but does with libusbK.

That does not explain why it does not work. There is comment about it, https://github.com/libusb/libusb/wiki/Windows#Known_Restrictions, outdated explanation can be found in the source code https://github.com/libusb/libusb/blob/master/libusb/os/windows_winusb.c#L3184-L3191, below there is comment about libusbK, but still no background explanation.

desowin commented 2 years ago

Zephyr sets bitWillDetach to 0 and hence DFU does not work with WinUSB, but does with libusbK.

That does not explain why it does not work. There is comment about it, https://github.com/libusb/libusb/wiki/Windows#Known_Restrictions, outdated explanation can be found in the source code https://github.com/libusb/libusb/blob/master/libusb/os/windows_winusb.c#L3184-L3191, below there is comment about libusbK, but still no background explanation.

What do by mean by "background explanation"?

Regarding the WinUSB limitation it is pretty hard to come up with a link to something that does not exist. The winusb.h header documentation lists the API provided by WinUSB. The API does not include host initiated USB reset function. Therefore, it is impossible for any user space application that uses WinUSB driver to issue host initiated USB reset.

Because USB Device Firmware Upgrade Specification, Revision 1.1 requires host initiated resets when bitWillDetach is 0, it is impossible to write host user-space software (dfu-util or equivalent) that would handle the device (with bitWillDetach set to 0) when WinUSB driver is used (the only possibility for DFU with WinUSB driver is when bitWillDetach is set to 1, as host initiates resets are not required). DFU can implemented regardless bitWillDetach state with libusbK driver as libusbK provides necessary API.

jfischer-no commented 2 years ago

Regarding the WinUSB limitation it is pretty hard to come up with a link to something that does not exist. The winusb.h header documentation lists the API provided by WinUSB.

Well it would be easier if they just write that there is no API for it instead of placing dead links in the code. Anyway, limitations of WinUSB do not seem to be a bug in Zephyr to me.

desowin commented 2 years ago

Regarding the WinUSB limitation it is pretty hard to come up with a link to something that does not exist. The winusb.h header documentation lists the API provided by WinUSB.

Well it would be easier if they just write that there is no API for it instead of placing dead links in the code.

Nobody placed dead links in the code. It is just how internet works, the links do become dead over time. So will the links placed in Zephyr code/documentation, especially the ones that point to latest version and not some specific version. This is why besides just a link, there should be enough context for the future reader.

If the comment you are referring to is what you linked earlier then the author did all they could do to provide enough context, that is:

/*
 * from the "How to Use WinUSB to Communicate with a USB Device" Microsoft white paper
 * (http://www.microsoft.com/whdc/connect/usb/winusb_howto.mspx):
 * "WinUSB does not support host-initiated reset port and cycle port operations" and
 * IOCTL_INTERNAL_USB_CYCLE_PORT is only available in kernel mode and the
 * IOCTL_USB_HUB_CYCLE_PORT ioctl was removed from Vista => the best we can do is
 * cycle the pipes (and even then, the control pipe can not be reset using WinUSB)
 */

The "WinUSB does not support host-initiated reset port and cycle port operations" is basically saying there is no API for it. It also provides really good context for people familiar with Windows development that there is basically no hope to implement host initiated reset in user-space.

Anyway, limitations of WinUSB do not seem to be a bug in Zephyr to me.

Then consider WinUSB compatibility as a feature. It is just a taxonomy. The thing is, that the end-user quite often does not make the distinction and instead reports that something does not work and considers this to be a bug. It is easy to notice that something does not work as expected, but it is hard to tell what is the root cause (because otherwise the reporter would just fix the root cause in the first place instead of going through the trouble of reporting).

The average user that buys any peripheral that runs Zephyr and uses USB DFU is likely to use Windows to update the firmware, as Windows currently has about 74% desktop market share. The end user expects the manufacturer to provide working software package that can update the firmware. The lowest maintenance solution is USB DFU with WinUSB WCID descriptors, as it eliminates the driver development, signing and installation problem completely.

jalinei commented 2 years ago

I agree with @desowin. Microsoft has its flaws for sure, but non-expert users will be mostly working on this OS. Figuring out what's causing download issue with dfu-util is out of reach for these people. Dfu-util is quite standard, it should be working on windows as well, no matter if the flaw is in WinUsb.. WCID and this fix will provide the ergonomics expected and needed for most.

jfischer-no commented 2 years ago

WCID and this fix will provide the ergonomics expected and needed for most.

Again, it is not a bug in Zephyr and therefore not a fix and has to wait until release is finished.