Selaa lähdekoodia

Merge branch 'master' of github.com:xmos-jmccarthy/tinyusb

Jeremiah McCarthy 4 vuotta sitten
vanhempi
sitoutus
05892a5a1e

+ 41 - 0
examples/device/dfu/CMakeLists.txt

@@ -0,0 +1,41 @@
+# use directory name for project id
+get_filename_component(PROJECT ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+set(PROJECT ${BOARD}-${PROJECT})
+
+# TOP is absolute path to root directory of TinyUSB git repo
+set(TOP "../../..")
+get_filename_component(TOP "${TOP}" REALPATH)
+
+# Check for -DFAMILY=
+if(FAMILY STREQUAL "rp2040")
+  cmake_minimum_required(VERSION 3.12)
+  set(PICO_SDK_PATH ${TOP}/hw/mcu/raspberrypi/pico-sdk)
+  include(${PICO_SDK_PATH}/pico_sdk_init.cmake)
+  project(${PROJECT})
+  pico_sdk_init()
+  add_executable(${PROJECT})
+
+  include(${TOP}/hw/bsp/${FAMILY}/family.cmake)
+
+  # Example source
+  target_sources(${PROJECT} PUBLIC
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
+  )
+
+  # Example include
+  target_include_directories(${PROJECT} PUBLIC
+    ${CMAKE_CURRENT_SOURCE_DIR}/src
+  )
+
+  # Example defines
+  target_compile_definitions(${PROJECT} PUBLIC
+    CFG_TUSB_OS=OPT_OS_PICO
+  )
+
+  target_link_libraries(${PROJECT} pico_stdlib pico_fix_rp2040_usb_device_enumeration)
+  pico_add_extra_outputs(${PROJECT})
+
+else()
+  message(FATAL_ERROR "Invalid FAMILY specified")
+endif()

+ 12 - 0
examples/device/dfu/Makefile

@@ -0,0 +1,12 @@
+include ../../../tools/top.mk
+include ../../make.mk
+
+INC += \
+	src \
+	$(TOP)/hw \
+
+# Example source
+EXAMPLE_SOURCE += $(wildcard src/*.c)
+SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE))
+
+include ../../rules.mk

+ 188 - 0
examples/device/dfu/src/main.c

@@ -0,0 +1,188 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+ /*
+  * After device is enumerated in dfu mode run the following commands
+  *
+  * To transfer firmware from host to device:
+  *
+  * $ dfu-util -D [filename]
+  *
+  * To transfer firmware from device to host:
+  *
+  * $ dfu-util -U [filename]
+  *
+  */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "bsp/board.h"
+#include "tusb.h"
+
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF PROTYPES
+//--------------------------------------------------------------------+
+#ifndef DFU_VERBOSE
+#define DFU_VERBOSE 0
+#endif
+
+/* Blink pattern
+ * - 1000 ms : device should reboot
+ * - 250 ms  : device not mounted
+ * - 1000 ms : device mounted
+ * - 2500 ms : device is suspended
+ */
+enum  {
+  BLINK_DFU_MODE = 100,
+  BLINK_NOT_MOUNTED = 250,
+  BLINK_MOUNTED = 1000,
+  BLINK_SUSPENDED = 2500,
+};
+
+static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
+
+void led_blinking_task(void);
+
+/*------------- MAIN -------------*/
+int main(void)
+{
+  board_init();
+
+  tusb_init();
+
+  while (1)
+  {
+    tud_task(); // tinyusb device task
+    led_blinking_task();
+  }
+
+  return 0;
+}
+
+//--------------------------------------------------------------------+
+// Device callbacks
+//--------------------------------------------------------------------+
+
+// Invoked when device is mounted
+void tud_mount_cb(void)
+{
+  blink_interval_ms = BLINK_MOUNTED;
+}
+
+// Invoked when device is unmounted
+void tud_umount_cb(void)
+{
+  blink_interval_ms = BLINK_NOT_MOUNTED;
+}
+
+// Invoked when usb bus is suspended
+// remote_wakeup_en : if host allow us  to perform remote wakeup
+// Within 7ms, device must draw an average of current less than 2.5 mA from bus
+void tud_suspend_cb(bool remote_wakeup_en)
+{
+  (void) remote_wakeup_en;
+  blink_interval_ms = BLINK_SUSPENDED;
+}
+
+// Invoked when usb bus is resumed
+void tud_resume_cb(void)
+{
+  blink_interval_ms = BLINK_MOUNTED;
+}
+
+// Invoked on DFU_DETACH request to reboot to the bootloader
+void tud_dfu_runtime_reboot_to_dfu_cb(void)
+{
+  blink_interval_ms = BLINK_DFU_MODE;
+}
+
+//--------------------------------------------------------------------+
+// Class callbacks
+//--------------------------------------------------------------------+
+bool tud_dfu_firmware_valid_check_cb(void)
+{
+  TU_LOG2("    Firmware check\r\n");
+  return true;
+}
+
+void tud_dfu_req_dnload_data_cb(uint16_t wBlockNum, uint8_t* data, uint16_t length)
+{
+  TU_LOG2("    Received BlockNum %u of length %u\r\n", wBlockNum, length);
+
+#if DFU_VERBOSE
+  for(uint16_t i=0; i<length; i++)
+  {
+    TU_LOG2("    [%u][%u]: %x\r\n", wBlockNum, i, (uint8_t)data[i]);
+  }
+#else
+  (void) data;
+#endif
+
+  tud_dfu_dnload_complete();
+}
+
+bool tud_dfu_device_data_done_check_cb(void)
+{
+  TU_LOG2("    Host said no more data... Returning true\r\n");
+  return true;
+}
+
+void tud_dfu_abort_cb(void)
+{
+  TU_LOG2("    Host aborted transfer\r\n");
+}
+
+#define UPLOAD_SIZE (29)
+const uint8_t upload_test[UPLOAD_SIZE] = "Hello world from TinyUSB DFU!"
+
+uint16_t tud_dfu_req_upload_data_cb(uint16_t block_num, uint8_t* data, uint16_t length)
+{
+  (void) block_num;
+  (void) length;
+
+  memcpy(data, upload_test, UPLOAD_SIZE);
+
+  return UPLOAD_SIZE;
+}
+
+//--------------------------------------------------------------------+
+// BLINKING TASK + Indicator pulse
+//--------------------------------------------------------------------+
+
+void led_blinking_task(void)
+{
+  static uint32_t start_ms = 0;
+  static bool led_state = false;
+
+  // Blink every interval ms
+  if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time
+  start_ms += blink_interval_ms;
+
+  board_led_write(led_state);
+  led_state = 1 - led_state; // toggle
+}

+ 90 - 0
examples/device/dfu/src/tusb_config.h

@@ -0,0 +1,90 @@
+/*
+ * tusb_config.h
+ *
+ *  Created on: May 5, 2021
+ *      Author: Jeremiah McCarthy
+ */
+
+#ifndef TUSB_CONFIG_H_
+#define TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//--------------------------------------------------------------------
+// COMMON CONFIGURATION
+//--------------------------------------------------------------------
+
+// defined by board.mk
+#ifndef CFG_TUSB_MCU
+  #error CFG_TUSB_MCU must be defined
+#endif
+
+// RHPort number used for device can be defined by board.mk, default to port 0
+#ifndef BOARD_DEVICE_RHPORT_NUM
+  #define BOARD_DEVICE_RHPORT_NUM     0
+#endif
+
+// RHPort max operational speed can defined by board.mk
+// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed
+#ifndef BOARD_DEVICE_RHPORT_SPEED
+  #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \
+       CFG_TUSB_MCU == OPT_MCU_NUC505  || CFG_TUSB_MCU == OPT_MCU_CXD56)
+    #define BOARD_DEVICE_RHPORT_SPEED   OPT_MODE_HIGH_SPEED
+  #else
+    #define BOARD_DEVICE_RHPORT_SPEED   OPT_MODE_FULL_SPEED
+  #endif
+#endif
+
+// Device mode with rhport and speed defined by board.mk
+#if   BOARD_DEVICE_RHPORT_NUM == 0
+  #define CFG_TUSB_RHPORT0_MODE     (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
+#elif BOARD_DEVICE_RHPORT_NUM == 1
+  #define CFG_TUSB_RHPORT1_MODE     (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
+#else
+  #error "Incorrect RHPort configuration"
+#endif
+
+#ifndef CFG_TUSB_OS
+#define CFG_TUSB_OS               OPT_OS_NONE
+#endif
+
+// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
+// #define CFG_TUSB_DEBUG           0
+
+/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
+ * Tinyusb use follows macros to declare transferring memory so that they can be put
+ * into those specific section.
+ * e.g
+ * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
+ * - CFG_TUSB_MEM_ALIGN   : __attribute__ ((aligned(4)))
+ */
+#ifndef CFG_TUSB_MEM_SECTION
+#define CFG_TUSB_MEM_SECTION
+#endif
+
+#ifndef CFG_TUSB_MEM_ALIGN
+#define CFG_TUSB_MEM_ALIGN          __attribute__ ((aligned(4)))
+#endif
+
+//--------------------------------------------------------------------
+// DEVICE CONFIGURATION
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUD_ENDPOINT0_SIZE
+#define CFG_TUD_ENDPOINT0_SIZE    64
+#endif
+
+#define CFG_TUD_DFU_TRANSFER_BUFFER_SIZE    4096
+
+//------------- CLASS -------------//
+
+#define CFG_TUD_DFU_RUNTIME 1
+#define CFG_TUD_DFU_MODE    1
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* TUSB_CONFIG_H_ */

+ 167 - 0
examples/device/dfu/src/usb_descriptors.c

@@ -0,0 +1,167 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "tusb.h"
+#include "class/dfu/dfu_device.h"
+
+/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
+ * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
+ *
+ * Auto ProductID layout's Bitmap:
+ *   [MSB]         HID | MSC | CDC          [LSB]
+ */
+#define _PID_MAP(itf, n)  ( (CFG_TUD_##itf) << (n) )
+#define USB_PID           (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
+                           _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
+
+//--------------------------------------------------------------------+
+// Device Descriptors
+//--------------------------------------------------------------------+
+tusb_desc_device_t const desc_device =
+{
+    .bLength            = sizeof(tusb_desc_device_t),
+    .bDescriptorType    = TUSB_DESC_DEVICE,
+    .bcdUSB             = 0x0200,
+
+  #if CFG_TUD_CDC
+    // Use Interface Association Descriptor (IAD) for CDC
+    // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
+    .bDeviceClass       = TUSB_CLASS_MISC,
+    .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
+    .bDeviceProtocol    = MISC_PROTOCOL_IAD,
+  #else
+    .bDeviceClass       = 0x00,
+    .bDeviceSubClass    = 0x00,
+    .bDeviceProtocol    = 0x00,
+  #endif
+
+    .bMaxPacketSize0    = CFG_TUD_ENDPOINT0_SIZE,
+
+    .idVendor           = 0xCafe,
+    .idProduct          = USB_PID,
+    .bcdDevice          = 0x0100,
+
+    .iManufacturer      = 0x01,
+    .iProduct           = 0x02,
+    .iSerialNumber      = 0x03,
+
+    .bNumConfigurations = 0x01
+};
+
+// Invoked when received GET DEVICE DESCRIPTOR
+// Application return pointer to descriptor
+uint8_t const * tud_descriptor_device_cb(void)
+{
+  return (uint8_t const *) &desc_device;
+}
+
+//--------------------------------------------------------------------+
+// Configuration Descriptor
+//--------------------------------------------------------------------+
+
+enum
+{
+  ITF1_NUM_DFU_MODE,
+  ITF_NUM_TOTAL
+};
+
+#define CONFIG_TOTAL_LEN    (TUD_CONFIG_DESC_LEN + TUD_DFU_MODE_DESC_LEN)
+
+#define FUNC_ATTRS (DFU_FUNC_ATTR_CAN_UPLOAD_BITMASK | DFU_FUNC_ATTR_CAN_DOWNLOAD_BITMASK)
+
+uint8_t const desc_configuration[] =
+{
+  // Config number, interface count, string index, total length, attribute, power in mA
+  TUD_CONFIG_DESCRIPTOR(1, ITF1_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+  // Interface number, string index, attributes, detach timeout, transfer size */
+  TUD_DFU_MODE_DESCRIPTOR(ITF1_NUM_DFU_MODE, 0, FUNC_ATTRS, 1000, CFG_TUD_DFU_TRANSFER_BUFFER_SIZE),
+};
+
+// Invoked when received GET CONFIGURATION DESCRIPTOR
+// Application return pointer to descriptor
+// Descriptor contents must exist long enough for transfer to complete
+uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
+{
+  (void) index; // for multiple configurations
+  return desc_configuration;
+}
+
+//--------------------------------------------------------------------+
+// String Descriptors
+//--------------------------------------------------------------------+
+
+// array of pointer to string descriptors
+char const* string_desc_arr [] =
+{
+  (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
+  "TinyUSB",                     // 1: Manufacturer
+  "TinyUSB Device",              // 2: Product
+  "123456",                      // 3: Serials, should use chip ID
+  "TinyUSB DFU",                 // 4: DFU
+};
+
+static uint16_t _desc_str[32];
+
+// Invoked when received GET STRING DESCRIPTOR request
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
+uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
+{
+  (void) langid;
+
+  size_t chr_count;
+
+  if ( index == 0)
+  {
+    memcpy(&_desc_str[1], string_desc_arr[0], 2);
+    chr_count = 1;
+  }
+  else
+  {
+    // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
+    // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
+
+    if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
+
+    const char* str = string_desc_arr[index];
+
+    // Cap at max char
+    chr_count = strlen(str);
+    if ( chr_count > 31 ) {
+      chr_count = 31;
+    }
+
+    // Convert ASCII string into UTF-16
+    for(uint8_t i=0; i<chr_count; i++)
+    {
+      _desc_str[1+i] = str[i];
+    }
+  }
+
+  // first byte is length (including header), second byte is string type
+  _desc_str[0] = (uint16_t)((((uint16_t)TUSB_DESC_STRING) << 8 ) | (2u*chr_count + 2u));
+
+  return _desc_str;
+}

+ 118 - 0
src/class/dfu/dfu.h

@@ -0,0 +1,118 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 XMOS LIMITED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef _TUSB_DFU_H_
+#define _TUSB_DFU_H_
+
+#include "common/tusb_common.h"
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Common Definitions
+//--------------------------------------------------------------------+
+// DFU Protocol
+typedef enum
+{
+  DFU_PROTOCOL_RT  = 0x01,
+  DFU_PROTOCOL_DFU = 0x02,
+} dfu_protocol_type_t;
+
+// DFU Descriptor Type
+typedef enum
+{
+  DFU_DESC_FUNCTIONAL = 0x21,
+} dfu_descriptor_type_t;
+
+// DFU Requests
+typedef enum {
+  DFU_REQUEST_DETACH         = 0,
+  DFU_REQUEST_DNLOAD         = 1,
+  DFU_REQUEST_UPLOAD         = 2,
+  DFU_REQUEST_GETSTATUS      = 3,
+  DFU_REQUEST_CLRSTATUS      = 4,
+  DFU_REQUEST_GETSTATE       = 5,
+  DFU_REQUEST_ABORT          = 6,
+} dfu_requests_t;
+
+// DFU States
+typedef enum {
+  APP_IDLE                   = 0,
+  APP_DETACH                 = 1,
+  DFU_IDLE                   = 2,
+  DFU_DNLOAD_SYNC            = 3,
+  DFU_DNBUSY                 = 4,
+  DFU_DNLOAD_IDLE            = 5,
+  DFU_MANIFEST_SYNC          = 6,
+  DFU_MANIFEST               = 7,
+  DFU_MANIFEST_WAIT_RESET    = 8,
+  DFU_UPLOAD_IDLE            = 9,
+  DFU_ERROR                  = 10,
+} dfu_state_t;
+
+// DFU Status
+typedef enum {
+  DFU_STATUS_OK              = 0x00,
+  DFU_STATUS_ERRTARGET       = 0x01,
+  DFU_STATUS_ERRFILE         = 0x02,
+  DFU_STATUS_ERRWRITE        = 0x03,
+  DFU_STATUS_ERRERASE        = 0x04,
+  DFU_STATUS_ERRCHECK_ERASED = 0x05,
+  DFU_STATUS_ERRPROG         = 0x06,
+  DFU_STATUS_ERRVERIFY       = 0x07,
+  DFU_STATUS_ERRADDRESS      = 0x08,
+  DFU_STATUS_ERRNOTDONE      = 0x09,
+  DFU_STATUS_ERRFIRMWARE     = 0x0A,
+  DFU_STATUS_ERRVENDOR       = 0x0B,
+  DFU_STATUS_ERRUSBR         = 0x0C,
+  DFU_STATUS_ERRPOR          = 0x0D,
+  DFU_STATUS_ERRUNKNOWN      = 0x0E,
+  DFU_STATUS_ERRSTALLEDPKT   = 0x0F,
+} dfu_device_status_t;
+
+#define DFU_FUNC_ATTR_CAN_DOWNLOAD_BITMASK              (1 << 0)
+#define DFU_FUNC_ATTR_CAN_UPLOAD_BITMASK                (1 << 1)
+#define DFU_FUNC_ATTR_MANIFESTATION_TOLERANT_BITMASK    (1 << 2)
+#define DFU_FUNC_ATTR_WILL_DETACH_BITMASK               (1 << 3)
+
+// DFU Status Request Payload
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t bStatus;
+  uint8_t bwPollTimeout[3];
+  uint8_t bState;
+  uint8_t iString;
+} dfu_status_req_payload_t;
+
+TU_VERIFY_STATIC( sizeof(dfu_status_req_payload_t) == 6, "size is not correct");
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_DFU_H_ */

+ 616 - 0
src/class/dfu/dfu_device.c

@@ -0,0 +1,616 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 XMOS LIMITED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_DFU_MODE)
+
+#include "dfu_device.h"
+#include "device/usbd_pvt.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
+typedef struct TU_ATTR_PACKED
+{
+    dfu_device_status_t status;
+    dfu_state_t state;
+    uint8_t attrs;
+    bool blk_transfer_in_proc;
+    CFG_TUSB_MEM_ALIGN uint8_t transfer_buf[CFG_TUD_DFU_TRANSFER_BUFFER_SIZE];
+} dfu_state_ctx_t;
+
+// Only a single dfu state is allowed
+CFG_TUSB_MEM_SECTION static dfu_state_ctx_t _dfu_state_ctx;
+
+
+static void dfu_req_dnload_setup(uint8_t rhport, tusb_control_request_t const * request);
+static void dfu_req_getstatus_reply(uint8_t rhport, tusb_control_request_t const * request);
+static uint16_t dfu_req_upload(uint8_t rhport, tusb_control_request_t const * request, uint16_t block_num, uint16_t wLength);
+static void dfu_req_dnload_reply(uint8_t rhport, tusb_control_request_t const * request);
+static bool dfu_state_machine(uint8_t rhport, tusb_control_request_t const * request);
+
+//--------------------------------------------------------------------+
+// Debug
+//--------------------------------------------------------------------+
+#if CFG_TUSB_DEBUG >= 2
+
+static tu_lookup_entry_t const _dfu_request_lookup[] =
+{
+  { .key = DFU_REQUEST_DETACH         , .data = "DETACH" },
+  { .key = DFU_REQUEST_DNLOAD         , .data = "DNLOAD" },
+  { .key = DFU_REQUEST_UPLOAD         , .data = "UPLOAD" },
+  { .key = DFU_REQUEST_GETSTATUS      , .data = "GETSTATUS" },
+  { .key = DFU_REQUEST_CLRSTATUS      , .data = "CLRSTATUS" },
+  { .key = DFU_REQUEST_GETSTATE       , .data = "GETSTATE" },
+  { .key = DFU_REQUEST_ABORT          , .data = "ABORT" },
+};
+
+static tu_lookup_table_t const _dfu_request_table =
+{
+  .count = TU_ARRAY_SIZE(_dfu_request_lookup),
+  .items = _dfu_request_lookup
+};
+
+static tu_lookup_entry_t const _dfu_state_lookup[] =
+{
+  { .key = APP_IDLE                   , .data = "APP_IDLE" },
+  { .key = APP_DETACH                 , .data = "APP_DETACH" },
+  { .key = DFU_IDLE                   , .data = "DFU_IDLE" },
+  { .key = DFU_DNLOAD_SYNC            , .data = "DFU_DNLOAD_SYNC" },
+  { .key = DFU_DNBUSY                 , .data = "DFU_DNBUSY" },
+  { .key = DFU_DNLOAD_IDLE            , .data = "DFU_DNLOAD_IDLE" },
+  { .key = DFU_MANIFEST_SYNC          , .data = "DFU_MANIFEST_SYNC" },
+  { .key = DFU_MANIFEST               , .data = "DFU_MANIFEST" },
+  { .key = DFU_MANIFEST_WAIT_RESET    , .data = "DFU_MANIFEST_WAIT_RESET" },
+  { .key = DFU_UPLOAD_IDLE            , .data = "DFU_UPLOAD_IDLE" },
+  { .key = DFU_ERROR                  , .data = "DFU_ERROR" },
+};
+
+static tu_lookup_table_t const _dfu_state_table =
+{
+  .count = TU_ARRAY_SIZE(_dfu_state_lookup),
+  .items = _dfu_state_lookup
+};
+
+static tu_lookup_entry_t const _dfu_status_lookup[] =
+{
+  { .key = DFU_STATUS_OK              , .data = "OK" },
+  { .key = DFU_STATUS_ERRTARGET       , .data = "errTARGET" },
+  { .key = DFU_STATUS_ERRFILE         , .data = "errFILE" },
+  { .key = DFU_STATUS_ERRWRITE        , .data = "errWRITE" },
+  { .key = DFU_STATUS_ERRERASE        , .data = "errERASE" },
+  { .key = DFU_STATUS_ERRCHECK_ERASED , .data = "errCHECK_ERASED" },
+  { .key = DFU_STATUS_ERRPROG         , .data = "errPROG" },
+  { .key = DFU_STATUS_ERRVERIFY       , .data = "errVERIFY" },
+  { .key = DFU_STATUS_ERRADDRESS      , .data = "errADDRESS" },
+  { .key = DFU_STATUS_ERRNOTDONE      , .data = "errNOTDONE" },
+  { .key = DFU_STATUS_ERRFIRMWARE     , .data = "errFIRMWARE" },
+  { .key = DFU_STATUS_ERRVENDOR       , .data = "errVENDOR" },
+  { .key = DFU_STATUS_ERRUSBR         , .data = "errUSBR" },
+  { .key = DFU_STATUS_ERRPOR          , .data = "errPOR" },
+  { .key = DFU_STATUS_ERRUNKNOWN      , .data = "errUNKNOWN" },
+  { .key = DFU_STATUS_ERRSTALLEDPKT   , .data = "errSTALLEDPKT" },
+};
+
+static tu_lookup_table_t const _dfu_status_table =
+{
+  .count = TU_ARRAY_SIZE(_dfu_status_lookup),
+  .items = _dfu_status_lookup
+};
+
+#endif
+
+#define dfu_debug_print_context()                                              \
+{                                                                              \
+  TU_LOG2("  DFU at State: %s\r\n         Status: %s\r\n",                     \
+          tu_lookup_find(&_dfu_state_table, _dfu_state_ctx.state),        \
+          tu_lookup_find(&_dfu_status_table, _dfu_state_ctx.status) );    \
+}
+
+//--------------------------------------------------------------------+
+// USBD Driver API
+//--------------------------------------------------------------------+
+void dfu_moded_init(void)
+{
+  _dfu_state_ctx.state = DFU_IDLE;
+  _dfu_state_ctx.status = DFU_STATUS_OK;
+  _dfu_state_ctx.attrs = 0;
+  _dfu_state_ctx.blk_transfer_in_proc = false;
+
+  dfu_debug_print_context();
+}
+
+void dfu_moded_reset(uint8_t rhport)
+{
+  _dfu_state_ctx.state = DFU_IDLE;
+  _dfu_state_ctx.status = DFU_STATUS_OK;
+  _dfu_state_ctx.blk_transfer_in_proc = false;
+  dfu_debug_print_context();
+}
+
+uint16_t dfu_moded_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len)
+{
+  (void) rhport;
+  (void) max_len;
+
+  // Ensure this is DFU Mode
+  TU_VERIFY((itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS) &&
+            (itf_desc->bInterfaceProtocol == DFU_PROTOCOL_DFU), 0);
+
+  uint8_t const * p_desc = tu_desc_next( itf_desc );
+  uint16_t drv_len = sizeof(tusb_desc_interface_t);
+
+  if ( TUSB_DESC_FUNCTIONAL == tu_desc_type(p_desc) )
+  {
+    tusb_desc_dfu_functional_t const *dfu_desc = (tusb_desc_dfu_functional_t const *)p_desc;
+    _dfu_state_ctx.attrs = (uint8_t)dfu_desc->bAttributes;
+
+    drv_len += tu_desc_len(p_desc);
+    p_desc   = tu_desc_next(p_desc);
+  }
+
+  return drv_len;
+}
+
+// Invoked when a control transfer occurred on an interface of this class
+// Driver response accordingly to the request and the transfer stage (setup/data/ack)
+// return false to stall control endpoint (e.g unsupported request)
+bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
+{
+  // nothing to do with DATA stage
+  if ( stage == CONTROL_STAGE_DATA ) return true;
+
+  TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE);
+
+  if(stage == CONTROL_STAGE_SETUP)
+  {
+    // dfu-util will try to claim the interface with SET_INTERFACE request before sending DFU request
+    if ( TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type &&
+         TUSB_REQ_SET_INTERFACE == request->bRequest )
+    {
+      tud_control_status(rhport, request);
+      return true;
+    }
+  }
+
+  // Handle class request only from here
+  TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS);
+
+  switch (request->bRequest)
+  {
+    case DFU_REQUEST_DNLOAD:
+    {
+      if ( (stage == CONTROL_STAGE_ACK)
+           && ((_dfu_state_ctx.attrs & DFU_FUNC_ATTR_CAN_DOWNLOAD_BITMASK) != 0)
+           && (_dfu_state_ctx.state == DFU_DNLOAD_SYNC))
+      {
+        dfu_req_dnload_reply(rhport, request);
+        return true;
+      }
+    } // fallthrough
+    case DFU_REQUEST_DETACH:
+    case DFU_REQUEST_UPLOAD:
+    case DFU_REQUEST_GETSTATUS:
+    case DFU_REQUEST_CLRSTATUS:
+    case DFU_REQUEST_GETSTATE:
+    case DFU_REQUEST_ABORT:
+    {
+      if(stage == CONTROL_STAGE_SETUP)
+      {
+        return dfu_state_machine(rhport, request);
+      }
+    }
+    break;
+
+    default:
+    {
+      TU_LOG2("  DFU Nonstandard Request: %u\r\n", request->bRequest);
+      return false; // stall unsupported request
+    }
+    break;
+  }
+
+  return true;
+}
+
+static uint16_t dfu_req_upload(uint8_t rhport, tusb_control_request_t const * request, uint16_t block_num, uint16_t wLength)
+{
+  TU_VERIFY( wLength <= CFG_TUD_DFU_TRANSFER_BUFFER_SIZE);
+  uint16_t retval = tud_dfu_req_upload_data_cb(block_num, (uint8_t *)_dfu_state_ctx.transfer_buf, wLength);
+  tud_control_xfer(rhport, request, _dfu_state_ctx.transfer_buf, retval);
+  return retval;
+}
+
+static void dfu_req_getstatus_reply(uint8_t rhport, tusb_control_request_t const * request)
+{
+  dfu_status_req_payload_t resp;
+
+  resp.bStatus = _dfu_state_ctx.status;
+  memset((uint8_t *)&resp.bwPollTimeout, 0x00, 3);
+  resp.bState = _dfu_state_ctx.state;
+  resp.iString = 0;
+
+  tud_control_xfer(rhport, request, &resp, sizeof(dfu_status_req_payload_t));
+}
+
+static void dfu_req_getstate_reply(uint8_t rhport, tusb_control_request_t const * request)
+{
+  tud_control_xfer(rhport, request, &_dfu_state_ctx.state, 1);
+}
+
+static void dfu_req_dnload_setup(uint8_t rhport, tusb_control_request_t const * request)
+{
+  // TODO: add "zero" copy mode so the buffer we read into can be provided by the user
+  // if they wish, there still will be the internal control buffer copy to this buffer
+  // but this mode would provide zero copy from the class driver to the application
+
+  // setup for data phase
+  tud_control_xfer(rhport, request, _dfu_state_ctx.transfer_buf, request->wLength);
+}
+
+static void dfu_req_dnload_reply(uint8_t rhport, tusb_control_request_t const * request)
+{
+  (void) rhport;
+  tud_dfu_req_dnload_data_cb(request->wValue, (uint8_t *)_dfu_state_ctx.transfer_buf, request->wLength);
+  _dfu_state_ctx.blk_transfer_in_proc = false;
+}
+
+void tud_dfu_dnload_complete(void)
+{
+  if (_dfu_state_ctx.state == DFU_DNBUSY)
+  {
+    _dfu_state_ctx.state = DFU_DNLOAD_SYNC;
+  } else if (_dfu_state_ctx.state == DFU_MANIFEST)
+  {
+    _dfu_state_ctx.state = ((_dfu_state_ctx.attrs & DFU_FUNC_ATTR_MANIFESTATION_TOLERANT_BITMASK) != 0)
+                           ? DFU_MANIFEST_WAIT_RESET : DFU_MANIFEST_SYNC;
+  }
+}
+
+static bool dfu_state_machine(uint8_t rhport, tusb_control_request_t const * request)
+{
+  TU_LOG2("  DFU Request: %s\r\n", tu_lookup_find(&_dfu_request_table, request->bRequest));
+  TU_LOG2("  DFU State Machine: %s\r\n", tu_lookup_find(&_dfu_state_table, _dfu_state_ctx.state));
+
+  switch (_dfu_state_ctx.state)
+  {
+    case DFU_IDLE:
+    {
+      switch (request->bRequest)
+      {
+        case DFU_REQUEST_DNLOAD:
+        {
+          if( ((_dfu_state_ctx.attrs & DFU_FUNC_ATTR_CAN_DOWNLOAD_BITMASK) != 0)
+              && (request->wLength > 0) )
+          {
+            _dfu_state_ctx.state = DFU_DNLOAD_SYNC;
+            _dfu_state_ctx.blk_transfer_in_proc = true;
+            dfu_req_dnload_setup(rhport, request);
+          } else {
+            _dfu_state_ctx.state = DFU_ERROR;
+          }
+        }
+        break;
+
+        case DFU_REQUEST_UPLOAD:
+        {
+          if( ((_dfu_state_ctx.attrs & DFU_FUNC_ATTR_CAN_UPLOAD_BITMASK) != 0) )
+          {
+            _dfu_state_ctx.state = DFU_UPLOAD_IDLE;
+            dfu_req_upload(rhport, request, request->wValue, request->wLength);
+          } else {
+            _dfu_state_ctx.state = DFU_ERROR;
+          }
+        }
+        break;
+
+        case DFU_REQUEST_GETSTATUS:
+        {
+          dfu_req_getstatus_reply(rhport, request);
+        }
+        break;
+
+        case DFU_REQUEST_GETSTATE:
+        {
+          dfu_req_getstate_reply(rhport, request);
+        }
+        break;
+
+        case DFU_REQUEST_ABORT:
+        {
+          ; // do nothing, but don't stall so continue on
+        }
+        break;
+
+        default:
+        {
+          _dfu_state_ctx.state = DFU_ERROR;
+          return false;  // stall on all other requests
+        }
+        break;
+      }
+    }
+    break;
+
+    case DFU_DNLOAD_SYNC:
+    {
+      switch (request->bRequest)
+      {
+        case DFU_REQUEST_GETSTATUS:
+        {
+          if ( _dfu_state_ctx.blk_transfer_in_proc )
+          {
+            _dfu_state_ctx.state = DFU_DNBUSY;
+            dfu_req_getstatus_reply(rhport, request);
+          } else {
+            _dfu_state_ctx.state = DFU_DNLOAD_IDLE;
+            dfu_req_getstatus_reply(rhport, request);
+          }
+        }
+        break;
+
+        case DFU_REQUEST_GETSTATE:
+        {
+          dfu_req_getstate_reply(rhport, request);
+        }
+        break;
+
+        default:
+        {
+          _dfu_state_ctx.state = DFU_ERROR;
+          return false;  // stall on all other requests
+        }
+        break;
+      }
+    }
+    break;
+
+    case DFU_DNBUSY:
+    {
+      switch (request->bRequest)
+      {
+        default:
+        {
+          _dfu_state_ctx.state = DFU_ERROR;
+          return false;  // stall on all other requests
+        }
+        break;
+      }
+    }
+    break;
+
+    case DFU_DNLOAD_IDLE:
+    {
+        switch (request->bRequest)
+        {
+          case DFU_REQUEST_DNLOAD:
+          {
+            if( ((_dfu_state_ctx.attrs & DFU_FUNC_ATTR_CAN_DOWNLOAD_BITMASK) != 0)
+                && (request->wLength > 0) )
+            {
+              _dfu_state_ctx.state = DFU_DNLOAD_SYNC;
+              _dfu_state_ctx.blk_transfer_in_proc = true;
+              dfu_req_dnload_setup(rhport, request);
+            } else {
+              if ( tud_dfu_device_data_done_check_cb() )
+              {
+                _dfu_state_ctx.state = DFU_MANIFEST_SYNC;
+                tud_control_status(rhport, request);
+              } else {
+                _dfu_state_ctx.state = DFU_ERROR;
+                return false;  // stall
+              }
+            }
+          }
+          break;
+
+          case DFU_REQUEST_GETSTATUS:
+          {
+            dfu_req_getstatus_reply(rhport, request);
+          }
+          break;
+
+          case DFU_REQUEST_GETSTATE:
+          {
+            dfu_req_getstate_reply(rhport, request);
+          }
+          break;
+
+          case DFU_REQUEST_ABORT:
+          {
+            if ( tud_dfu_abort_cb )
+            {
+              tud_dfu_abort_cb();
+            }
+            _dfu_state_ctx.state = DFU_IDLE;
+          }
+          break;
+
+          default:
+          {
+            _dfu_state_ctx.state = DFU_ERROR;
+            return false;  // stall on all other requests
+          }
+          break;
+        }
+    }
+    break;
+
+    case DFU_MANIFEST_SYNC:
+    {
+      switch (request->bRequest)
+      {
+        case DFU_REQUEST_GETSTATUS:
+        {
+          if ((_dfu_state_ctx.attrs & DFU_FUNC_ATTR_MANIFESTATION_TOLERANT_BITMASK) != 0)
+          {
+            _dfu_state_ctx.state = DFU_MANIFEST;
+            dfu_req_getstatus_reply(rhport, request);
+          } else {
+            if ( tud_dfu_firmware_valid_check_cb() )
+            {
+              _dfu_state_ctx.state = DFU_IDLE;
+            }
+            dfu_req_getstatus_reply(rhport, request);
+          }
+        }
+        break;
+
+        case DFU_REQUEST_GETSTATE:
+        {
+          dfu_req_getstate_reply(rhport, request);
+        }
+        break;
+
+        default:
+        {
+          _dfu_state_ctx.state = DFU_ERROR;
+          return false;  // stall on all other requests
+        }
+        break;
+      }
+    }
+    break;
+
+    case DFU_MANIFEST:
+    {
+      switch (request->bRequest)
+      {
+        default:
+        {
+          return false;  // stall on all other requests
+        }
+        break;
+      }
+    }
+    break;
+
+    case DFU_MANIFEST_WAIT_RESET:
+    {
+      // technically we should never even get here, but we will handle it just in case
+      TU_LOG2("  DFU was in DFU_MANIFEST_WAIT_RESET and got unexpected request: %u\r\n", request->bRequest);
+      switch (request->bRequest)
+      {
+        default:
+        {
+          return false;  // stall on all other requests
+        }
+        break;
+      }
+    }
+    break;
+
+    case DFU_UPLOAD_IDLE:
+    {
+      switch (request->bRequest)
+      {
+        case DFU_REQUEST_UPLOAD:
+        {
+          if (dfu_req_upload(rhport, request, request->wValue, request->wLength) != request->wLength)
+          {
+            _dfu_state_ctx.state = DFU_IDLE;
+          }
+        }
+        break;
+
+        case DFU_REQUEST_GETSTATUS:
+        {
+          dfu_req_getstatus_reply(rhport, request);
+        }
+        break;
+
+        case DFU_REQUEST_GETSTATE:
+        {
+          dfu_req_getstate_reply(rhport, request);
+        }
+        break;
+
+        case DFU_REQUEST_ABORT:
+        {
+          if (tud_dfu_abort_cb)
+          {
+            tud_dfu_abort_cb();
+          }
+          _dfu_state_ctx.state = DFU_IDLE;
+        }
+        break;
+
+        default:
+        {
+          return false;  // stall on all other requests
+        }
+        break;
+      }
+    }
+    break;
+
+    case DFU_ERROR:
+    {
+      switch (request->bRequest)
+      {
+        case DFU_REQUEST_GETSTATUS:
+        {
+          dfu_req_getstatus_reply(rhport, request);
+        }
+        break;
+
+        case DFU_REQUEST_CLRSTATUS:
+        {
+          _dfu_state_ctx.state = DFU_IDLE;
+        }
+        break;
+
+        case DFU_REQUEST_GETSTATE:
+        {
+          dfu_req_getstate_reply(rhport, request);
+        }
+        break;
+
+        default:
+        {
+          return false;  // stall on all other requests
+        }
+        break;
+      }
+    }
+    break;
+
+    default:
+      _dfu_state_ctx.state = DFU_ERROR;
+      TU_LOG2("  DFU ERROR: Unexpected state\r\nStalling control pipe\r\n");
+      return false;  // Unexpected state, stall and change to error
+  }
+
+  return true;
+}
+
+
+#endif

+ 84 - 0
src/class/dfu/dfu_device.h

@@ -0,0 +1,84 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 XMOS LIMITED
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef _TUSB_DFU_DEVICE_H_
+#define _TUSB_DFU_DEVICE_H_
+
+#include "common/tusb_common.h"
+#include "device/usbd.h"
+#include "dfu.h"
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak is optional)
+//--------------------------------------------------------------------+
+// Invoked during DFU_MANIFEST_SYNC get status request to check if firmware
+// is valid
+bool tud_dfu_firmware_valid_check_cb(void);
+
+// Invoked when a DFU_DNLOAD request is received
+// This callback takes the wBlockNum chunk of length length and provides it
+// to the application at the data pointer.  This data is only valid for this
+// call, so the app must use it not or copy it.
+void tud_dfu_req_dnload_data_cb(uint16_t wBlockNum, uint8_t* data, uint16_t length);
+
+// Must be called when the application is done using the last block of data
+// provided by tud_dfu_req_dnload_data_cb
+void tud_dfu_dnload_complete(void);
+
+// Invoked during the last DFU_DNLOAD request, signifying that the host believes
+// it is done transmitting data.
+// Return true if the application agrees there is no more data
+// Return false if the device disagrees, which will stall the pipe, and the Host
+//              should initiate a recovery procedure
+bool tud_dfu_device_data_done_check_cb(void);
+
+// Invoked when the Host has terminated a download or upload transfer
+TU_ATTR_WEAK void tud_dfu_abort_cb(void);
+
+// Invoked when a DFU_UPLOAD request is received
+// This callback must populate data with up to length bytes
+// Return the number of bytes to write
+uint16_t tud_dfu_req_upload_data_cb(uint16_t block_num, uint8_t* data, uint16_t length);
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+void     dfu_moded_init(void);
+void     dfu_moded_reset(uint8_t rhport);
+uint16_t dfu_moded_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
+bool     dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
+
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_DFU_MODE_DEVICE_H_ */

+ 22 - 35
src/class/dfu/dfu_rt_device.c

@@ -34,23 +34,10 @@
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
 // MACRO CONSTANT TYPEDEF
 // MACRO CONSTANT TYPEDEF
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
-typedef enum {
-  DFU_REQUEST_DETACH      = 0,
-  DFU_REQUEST_DNLOAD      = 1,
-  DFU_REQUEST_UPLOAD      = 2,
-  DFU_REQUEST_GETSTATUS   = 3,
-  DFU_REQUEST_CLRSTATUS   = 4,
-  DFU_REQUEST_GETSTATE    = 5,
-  DFU_REQUEST_ABORT       = 6,
-} dfu_requests_t;
-
-typedef struct TU_ATTR_PACKED
-{
-  uint8_t status;
-  uint8_t poll_timeout[3];
-  uint8_t state;
-  uint8_t istring;
-} dfu_status_t;
+
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
 
 
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
 // USBD Driver API
 // USBD Driver API
@@ -61,7 +48,7 @@ void dfu_rtd_init(void)
 
 
 void dfu_rtd_reset(uint8_t rhport)
 void dfu_rtd_reset(uint8_t rhport)
 {
 {
-  (void) rhport;
+    (void) rhport;
 }
 }
 
 
 uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len)
 uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len)
@@ -70,8 +57,8 @@ uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, ui
   (void) max_len;
   (void) max_len;
 
 
   // Ensure this is DFU Runtime
   // Ensure this is DFU Runtime
-  TU_VERIFY(itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS &&
-            itf_desc->bInterfaceProtocol == DFU_PROTOCOL_RT, 0);
+  TU_VERIFY((itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS) &&
+            (itf_desc->bInterfaceProtocol == DFU_PROTOCOL_RT), 0);
 
 
   uint8_t const * p_desc = tu_desc_next( itf_desc );
   uint8_t const * p_desc = tu_desc_next( itf_desc );
   uint16_t drv_len = sizeof(tusb_desc_interface_t);
   uint16_t drv_len = sizeof(tusb_desc_interface_t);
@@ -90,7 +77,7 @@ uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, ui
 // return false to stall control endpoint (e.g unsupported request)
 // return false to stall control endpoint (e.g unsupported request)
 bool dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
 bool dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
 {
 {
-  // nothing to do with DATA and ACK stage
+  // nothing to do with DATA or ACK stage
   if ( stage != CONTROL_STAGE_SETUP ) return true;
   if ( stage != CONTROL_STAGE_SETUP ) return true;
 
 
   TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE);
   TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE);
@@ -106,34 +93,34 @@ bool dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request
   // Handle class request only from here
   // Handle class request only from here
   TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS);
   TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS);
 
 
-  switch ( request->bRequest )
+  switch (request->bRequest)
   {
   {
     case DFU_REQUEST_DETACH:
     case DFU_REQUEST_DETACH:
+    {
+      TU_LOG2("  DFU RT Request: DETACH\r\n");
       tud_control_status(rhport, request);
       tud_control_status(rhport, request);
       tud_dfu_runtime_reboot_to_dfu_cb();
       tud_dfu_runtime_reboot_to_dfu_cb();
+    }
     break;
     break;
 
 
     case DFU_REQUEST_GETSTATUS:
     case DFU_REQUEST_GETSTATUS:
     {
     {
-      // status = OK, poll timeout = 0, state = app idle, istring = 0
-      uint8_t status_response[6] = { 0, 0, 0, 0, 0, 0 };
-      tud_control_xfer(rhport, request, status_response, sizeof(status_response));
+      TU_LOG2("  DFU RT Request: GETSTATUS\r\n");
+      dfu_status_req_payload_t resp;
+      // Status = OK, Poll timeout is ignored during RT, State = APP_IDLE, IString = 0
+      memset(&resp, 0x00, sizeof(dfu_status_req_payload_t));
+      tud_control_xfer(rhport, request, &resp, sizeof(dfu_status_req_payload_t));
     }
     }
     break;
     break;
 
 
-    default: return false; // stall unsupported request
+    default:
+    {
+      TU_LOG2("  DFU RT Unexpected Request: %d\r\n", request->bRequest);
+      return false; // stall unsupported request
+    }
   }
   }
 
 
   return true;
   return true;
 }
 }
 
 
-bool dfu_rtd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
-{
-  (void) rhport;
-  (void) ep_addr;
-  (void) result;
-  (void) xferred_bytes;
-  return true;
-}
-
 #endif
 #endif

+ 3 - 23
src/class/dfu/dfu_rt_device.h

@@ -29,36 +29,17 @@
 
 
 #include "common/tusb_common.h"
 #include "common/tusb_common.h"
 #include "device/usbd.h"
 #include "device/usbd.h"
+#include "dfu.h"
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
  extern "C" {
  extern "C" {
 #endif
 #endif
 
 
-
-//--------------------------------------------------------------------+
-// Common Definitions
-//--------------------------------------------------------------------+
-
-// DFU Protocol
-typedef enum
-{
-  DFU_PROTOCOL_RT  = 1,
-  DFU_PROTOCOL_DFU = 2,
-} dfu_protocol_type_t;
-
-// DFU Descriptor Type
-typedef enum
-{
-  DFU_DESC_FUNCTIONAL = 0x21,
-} dfu_descriptor_type_t;
-
-
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
 // Application Callback API (weak is optional)
 // Application Callback API (weak is optional)
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
-
-// Invoked when received new data
-TU_ATTR_WEAK void tud_dfu_runtime_reboot_to_dfu_cb(void);
+// Invoked when a DFU_DETACH request is received and bitWillDetach is set
+void tud_dfu_runtime_reboot_to_dfu_cb(void);
 
 
 //--------------------------------------------------------------------+
 //--------------------------------------------------------------------+
 // Internal Class Driver API
 // Internal Class Driver API
@@ -67,7 +48,6 @@ void     dfu_rtd_init(void);
 void     dfu_rtd_reset(uint8_t rhport);
 void     dfu_rtd_reset(uint8_t rhport);
 uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
 uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
 bool     dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
 bool     dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
-bool     dfu_rtd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
  }
  }

+ 1 - 1
src/class/vendor/vendor_device.c

@@ -1,4 +1,4 @@
-/* 
+/*
  * The MIT License (MIT)
  * The MIT License (MIT)
  *
  *
  * Copyright (c) 2019 Ha Thach (tinyusb.org)
  * Copyright (c) 2019 Ha Thach (tinyusb.org)

+ 1 - 1
src/common/tusb_common.h

@@ -1,4 +1,4 @@
-/* 
+/*
  * The MIT License (MIT)
  * The MIT License (MIT)
  *
  *
  * Copyright (c) 2019 Ha Thach (tinyusb.org)
  * Copyright (c) 2019 Ha Thach (tinyusb.org)

+ 23 - 0
src/common/tusb_types.h

@@ -421,6 +421,29 @@ typedef struct TU_ATTR_PACKED
   char    url[];
   char    url[];
 } tusb_desc_webusb_url_t;
 } tusb_desc_webusb_url_t;
 
 
+// DFU Functional Descriptor
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t  bLength;
+  uint8_t  bDescriptorType;
+
+  union {
+    struct TU_ATTR_PACKED {
+      uint8_t bitCanDnload             : 1;
+      uint8_t bitCanUpload             : 1;
+      uint8_t bitManifestationTolerant : 1;
+      uint8_t bitWillDetach            : 1;
+      uint8_t reserved                 : 4;
+    } bmAttributes;
+
+    uint8_t bAttributes;
+  };
+
+  uint16_t wDetachTimeOut;
+  uint16_t wTransferSize;
+  uint16_t bcdDFUVersion;
+} tusb_desc_dfu_functional_t;
+
 /*------------------------------------------------------------------*/
 /*------------------------------------------------------------------*/
 /* Types
 /* Types
  *------------------------------------------------------------------*/
  *------------------------------------------------------------------*/

+ 16 - 4
src/device/usbd.c

@@ -1,4 +1,4 @@
-/* 
+/*
  * The MIT License (MIT)
  * The MIT License (MIT)
  *
  *
  * Copyright (c) 2019 Ha Thach (tinyusb.org)
  * Copyright (c) 2019 Ha Thach (tinyusb.org)
@@ -182,7 +182,19 @@ static usbd_class_driver_t const _usbd_driver[] =
     .reset            = dfu_rtd_reset,
     .reset            = dfu_rtd_reset,
     .open             = dfu_rtd_open,
     .open             = dfu_rtd_open,
     .control_xfer_cb  = dfu_rtd_control_xfer_cb,
     .control_xfer_cb  = dfu_rtd_control_xfer_cb,
-    .xfer_cb          = dfu_rtd_xfer_cb,
+    .xfer_cb          = NULL,
+    .sof              = NULL
+  },
+  #endif
+
+  #if CFG_TUD_DFU_MODE
+  {
+    DRIVER_NAME("DFU-MODE")
+    .init             = dfu_moded_init,
+    .reset            = dfu_moded_reset,
+    .open             = dfu_moded_open,
+    .control_xfer_cb  = dfu_moded_control_xfer_cb,
+    .xfer_cb          = NULL,
     .sof              = NULL
     .sof              = NULL
   },
   },
   #endif
   #endif
@@ -1318,9 +1330,9 @@ bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr)
 
 
 /**
 /**
  * usbd_edpt_close will disable an endpoint.
  * usbd_edpt_close will disable an endpoint.
- * 
+ *
  * In progress transfers on this EP may be delivered after this call.
  * In progress transfers on this EP may be delivered after this call.
- * 
+ *
  */
  */
 void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr)
 void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr)
 {
 {

+ 13 - 2
src/device/usbd.h

@@ -1,4 +1,4 @@
-/* 
+/*
  * The MIT License (MIT)
  * The MIT License (MIT)
  *
  *
  * Copyright (c) 2019 Ha Thach (tinyusb.org)
  * Copyright (c) 2019 Ha Thach (tinyusb.org)
@@ -583,7 +583,7 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb
 
 
 //------------- DFU Runtime -------------//
 //------------- DFU Runtime -------------//
 #define TUD_DFU_APP_CLASS    (TUSB_CLASS_APPLICATION_SPECIFIC)
 #define TUD_DFU_APP_CLASS    (TUSB_CLASS_APPLICATION_SPECIFIC)
-#define TUD_DFU_APP_SUBCLASS 0x01u
+#define TUD_DFU_APP_SUBCLASS (APP_SUBCLASS_DFU_RUNTIME)
 
 
 // Length of template descriptr: 18 bytes
 // Length of template descriptr: 18 bytes
 #define TUD_DFU_RT_DESC_LEN (9 + 9)
 #define TUD_DFU_RT_DESC_LEN (9 + 9)
@@ -596,6 +596,17 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb
   /* Function */ \
   /* Function */ \
   9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101)
   9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101)
 
 
+// Length of template descriptr: 18 bytes
+#define TUD_DFU_MODE_DESC_LEN (9 + 9)
+
+// DFU runtime descriptor
+// Interface number, string index, attributes, detach timeout, transfer size
+#define TUD_DFU_MODE_DESCRIPTOR(_itfnum, _stridx, _attr, _timeout, _xfer_size) \
+  /* Interface */ \
+  9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUD_DFU_APP_CLASS, TUD_DFU_APP_SUBCLASS, DFU_PROTOCOL_DFU, _stridx, \
+  /* Function */ \
+  9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101)
+
 
 
 //------------- CDC-ECM -------------//
 //------------- CDC-ECM -------------//
 
 

+ 1 - 1
src/device/usbd_control.c

@@ -107,7 +107,7 @@ bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, vo
   _ctrl_xfer.buffer        = (uint8_t*) buffer;
   _ctrl_xfer.buffer        = (uint8_t*) buffer;
   _ctrl_xfer.total_xferred = 0U;
   _ctrl_xfer.total_xferred = 0U;
   _ctrl_xfer.data_len      = tu_min16(len, request->wLength);
   _ctrl_xfer.data_len      = tu_min16(len, request->wLength);
-  
+
   if (request->wLength > 0U)
   if (request->wLength > 0U)
   {
   {
     if(_ctrl_xfer.data_len > 0U)
     if(_ctrl_xfer.data_len > 0U)

+ 5 - 1
src/tusb.h

@@ -1,4 +1,4 @@
-/* 
+/*
  * The MIT License (MIT)
  * The MIT License (MIT)
  *
  *
  * Copyright (c) 2019 Ha Thach (tinyusb.org)
  * Copyright (c) 2019 Ha Thach (tinyusb.org)
@@ -96,6 +96,10 @@
     #include "class/dfu/dfu_rt_device.h"
     #include "class/dfu/dfu_rt_device.h"
   #endif
   #endif
 
 
+  #if CFG_TUD_DFU_MODE
+    #include "class/dfu/dfu_device.h"
+  #endif
+
   #if CFG_TUD_NET
   #if CFG_TUD_NET
     #include "class/net/net_device.h"
     #include "class/net/net_device.h"
   #endif
   #endif

+ 9 - 1
src/tusb_option.h

@@ -241,7 +241,15 @@
 #endif
 #endif
 
 
 #ifndef CFG_TUD_DFU_RUNTIME
 #ifndef CFG_TUD_DFU_RUNTIME
-  #define CFG_TUD_DFU_RUNTIME          0
+  #define CFG_TUD_DFU_RUNTIME     0
+#endif
+
+#ifndef CFG_TUD_DFU_MODE
+  #define CFG_TUD_DFU_MODE        0
+#endif
+
+#ifndef CFG_TUD_DFU_TRANSFER_BUFFER_SIZE
+  #define CFG_TUD_DFU_TRANSFER_BUFFER_SIZE  64
 #endif
 #endif
 
 
 #ifndef CFG_TUD_NET
 #ifndef CFG_TUD_NET