Skip to content

Commit

Permalink
usb: dfu: Support DFU with WinUSB on Windows
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
tmon-nordic authored and fabiobaltieri committed Oct 6, 2022
1 parent fbd88cd commit c27d48c
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 17 deletions.
10 changes: 10 additions & 0 deletions subsys/usb/device/class/dfu/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 46 additions & 17 deletions subsys/usb/device/class/dfu/usb_dfu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
}

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit c27d48c

Please sign in to comment.