usb: dfu: Support DFU with WinUSB on Windows

DFU on Windows with WinUSB driver failed with "Lost device after RESET?"
error because WinUSB does not support host initiated resets. USB Device
Firmware Upgrade Specification Revision 1.1 attribute bitWillDetach can
be used to overcome WinUSB reset limitation. When bitWillDetach is set,
it is the device responsibility to detach and reattach itself to the bus
after receiving DFU_DETACH request.

Add and enable by default USB_DFU_WILL_DETACH configuration option,
because it is the only way to support WinUSB driver. WinUSB driver is
preferable because it can be automatically installed on Windows 8 and
later if USB device implements WCID descriptors.

Fixes: #49821

Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
This commit is contained in:
Tomasz Moń 2022-09-15 12:01:06 +02:00 committed by Fabio Baltieri
parent fbd88cd5bb
commit c27d48c89a
2 changed files with 56 additions and 17 deletions

View File

@ -18,8 +18,18 @@ config USB_DEVICE_DFU_PID
help
USB device product ID in DFU mode. MUST be configured by vendor.
config USB_DFU_WILL_DETACH
bool "Generate detach-attach sequence on DFU detach"
default y
help
Enabling this option makes the device responsible for detaching
itself from the bus after the DFU_DETACH request. Select this
for compatibility with host drivers that cannot issue USB reset.
DFU fails on Windows with WinUSB driver if this is not enabled.
config USB_DFU_DETACH_TIMEOUT
int
default 100 if USB_DFU_WILL_DETACH
default 1000
config USB_DFU_DEFAULT_POLLTIMEOUT

View File

@ -79,9 +79,16 @@ LOG_MODULE_REGISTER(usb_dfu);
#define DFU_DESC_ATTRIBUTES_CAN_UPLOAD 0
#endif
#if IS_ENABLED(CONFIG_USB_DFU_WILL_DETACH)
#define DFU_DESC_ATTRIBUTES_WILL_DETACH DFU_ATTR_WILL_DETACH
#else
#define DFU_DESC_ATTRIBUTES_WILL_DETACH 0
#endif
#define DFU_DESC_ATTRIBUTES (DFU_ATTR_CAN_DNLOAD | \
DFU_DESC_ATTRIBUTES_CAN_UPLOAD |\
DFU_DESC_ATTRIBUTES_MANIF_TOL)
DFU_DESC_ATTRIBUTES_MANIF_TOL |\
DFU_DESC_ATTRIBUTES_WILL_DETACH)
static struct k_poll_event dfu_event;
static struct k_poll_signal dfu_signal;
@ -400,10 +407,31 @@ static void dfu_flash_write(uint8_t *data, size_t len)
LOG_DBG("bytes written 0x%x", flash_img_bytes_written(&dfu_data.ctx));
}
static void dfu_enter_idle(void)
{
dfu_data.state = dfuIDLE;
/* Set the DFU mode descriptors to be used after reset */
dfu_config.usb_device_description = (uint8_t *) &dfu_mode_desc;
if (usb_set_config(dfu_config.usb_device_description)) {
LOG_ERR("usb_set_config failed during DFU idle entry");
}
}
static void dfu_timer_expired(struct k_timer *timer)
{
if (dfu_data.state == appDETACH) {
dfu_data.state = appIDLE;
if (IS_ENABLED(CONFIG_USB_DFU_WILL_DETACH)) {
if (usb_dc_detach()) {
LOG_ERR("usb_dc_detach failed");
}
dfu_enter_idle();
if (usb_dc_attach()) {
LOG_ERR("usb_dc_attach failed");
}
} else {
dfu_data.state = appIDLE;
}
}
}
@ -655,8 +683,17 @@ static int dfu_class_handle_to_device(struct usb_setup_packet *setup,
/* Move to appDETACH state */
dfu_data.state = appDETACH;
/* Begin detach timeout timer */
timeout = MIN(setup->wValue, CONFIG_USB_DFU_DETACH_TIMEOUT);
if (IS_ENABLED(CONFIG_USB_DFU_WILL_DETACH)) {
/* Note: Detach should happen once the status stage
* finishes but the USB device stack does not expose
* such callback. Wait fixed time (ignore wValue) to
* let device finish control transfer status stage.
*/
timeout = CONFIG_USB_DFU_DETACH_TIMEOUT;
} else {
/* Begin detach timeout timer */
timeout = MIN(setup->wValue, CONFIG_USB_DFU_DETACH_TIMEOUT);
}
k_timer_start(&dfu_timer, K_MSEC(timeout), K_FOREVER);
break;
default:
@ -709,19 +746,11 @@ static void dfu_status_cb(struct usb_cfg_data *cfg,
break;
case USB_DC_RESET:
LOG_DBG("USB device reset detected, state %d", dfu_data.state);
/* Stop the appDETACH timeout timer */
k_timer_stop(&dfu_timer);
if (dfu_data.state == appDETACH) {
dfu_data.state = dfuIDLE;
/* Set the DFU mode descriptors to be used after
* reset
*/
dfu_config.usb_device_description =
(uint8_t *) &dfu_mode_desc;
if (usb_set_config(dfu_config.usb_device_description)) {
LOG_ERR("usb_set_config failed during USB "
"device reset");
if (!IS_ENABLED(CONFIG_USB_DFU_WILL_DETACH)) {
/* Stop the appDETACH timeout timer */
k_timer_stop(&dfu_timer);
if (dfu_data.state == appDETACH) {
dfu_enter_idle();
}
}
break;