From 61acee411794a78c760afe07a8ae4ffc9b44fe7b Mon Sep 17 00:00:00 2001
From: Serge Bazanski <q3k@q3k.org>
Date: Tue, 11 Jul 2023 20:56:00 +0200
Subject: [PATCH] st3m: add usb stack (unused)

This implements a st3m-specific USB stack. It's effectively what
espressif provides with their esp_tinyusb component, but allows for
dynamic reconfiguration of the USB device.
---
 components/st3m/CMakeLists.txt         |   9 ++
 components/st3m/st3m_usb.c             | 139 ++++++++++++++++++++
 components/st3m/st3m_usb.h             | 125 ++++++++++++++++++
 components/st3m/st3m_usb_cdc.c         |  76 +++++++++++
 components/st3m/st3m_usb_descriptors.c | 170 +++++++++++++++++++++++++
 components/st3m/st3m_usb_msc.c         | 122 ++++++++++++++++++
 components/st3m/tusb_config.h          |  31 +++++
 7 files changed, 672 insertions(+)
 create mode 100644 components/st3m/st3m_usb.c
 create mode 100644 components/st3m/st3m_usb.h
 create mode 100644 components/st3m/st3m_usb_cdc.c
 create mode 100644 components/st3m/st3m_usb_descriptors.c
 create mode 100644 components/st3m/st3m_usb_msc.c
 create mode 100644 components/st3m/tusb_config.h

diff --git a/components/st3m/CMakeLists.txt b/components/st3m/CMakeLists.txt
index 0dfdc6969d..1b71759191 100644
--- a/components/st3m/CMakeLists.txt
+++ b/components/st3m/CMakeLists.txt
@@ -9,6 +9,10 @@ idf_component_register(
         st3m_leds.c
         st3m_colors.c
         st3m_io.c
+        st3m_usb_cdc.c
+        st3m_usb_descriptors.c
+        st3m_usb_msc.c
+        st3m_usb.c
     INCLUDE_DIRS
         .
     REQUIRES
@@ -16,4 +20,9 @@ idf_component_register(
         bl00mbox
         ctx
         fatfs
+        tinyusb
+        usb
 )
+
+idf_component_get_property(tusb_lib tinyusb COMPONENT_LIB)
+target_include_directories(${tusb_lib} PRIVATE .)
\ No newline at end of file
diff --git a/components/st3m/st3m_usb.c b/components/st3m/st3m_usb.c
new file mode 100644
index 0000000000..04c13e5c7b
--- /dev/null
+++ b/components/st3m/st3m_usb.c
@@ -0,0 +1,139 @@
+#include "st3m_usb.h"
+
+static const char *TAG = "st3m-usb";
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+
+#include "esp_log.h"
+#include "esp_mac.h"
+#include "esp_private/usb_phy.h"
+#include "tusb.h"
+
+static SemaphoreHandle_t _mu = NULL;
+static st3m_usb_mode_kind_t _mode = st3m_usb_mode_kind_disabled;
+static usb_phy_handle_t phy_hdl;
+static bool _connected = false;
+
+static void _usb_task(void *_arg) {
+	(void)_arg;
+    while (true) {
+  		tud_task_ext(10, false);
+
+		xSemaphoreTake(_mu, portMAX_DELAY);
+		st3m_usb_mode_kind_t mode = _mode;
+		xSemaphoreGive(_mu);
+
+		if (mode == st3m_usb_mode_kind_app) {
+			st3m_usb_cdc_txpoll();
+		}
+    }
+}
+
+// Generate USB serial from on-board chip ID / MAC.
+static void _generate_serial(void) {
+	uint8_t mac[6];
+	memset(mac, 0, 6);
+	// Ignore error. Worst case we get zeroes.
+	esp_read_mac(mac, ESP_MAC_WIFI_STA);
+
+	char serial[13];
+	snprintf(serial, 13, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+	st3m_usb_descriptors_set_serial(serial);
+}
+
+void st3m_usb_init(void) {
+	assert(_mu == NULL);
+	_mu = xSemaphoreCreateMutex();
+	assert(_mu != NULL);
+
+	_generate_serial();
+	st3m_usb_cdc_init();
+    usb_phy_config_t phy_conf = {
+        .controller = USB_PHY_CTRL_OTG,
+        .otg_mode = USB_OTG_MODE_DEVICE,
+        .target = USB_PHY_TARGET_INT,
+    };
+	// TODO(q3k): set self-powered based on battery state?
+    ESP_ERROR_CHECK(usb_new_phy(&phy_conf, &phy_hdl));
+
+    if (!tusb_init()) {
+        ESP_LOGE(TAG, "TInyUSB init failed");
+        assert(false);
+    }
+
+   	xTaskCreate(_usb_task, "usb", 4096, NULL, 5, NULL);
+	ESP_LOGI(TAG, "USB stack started");
+}
+
+void st3m_usb_mode_switch(st3m_usb_mode_t *mode) {
+	xSemaphoreTake(_mu, portMAX_DELAY);
+
+	bool running = false;
+	switch (_mode) {
+	case st3m_usb_mode_kind_app:
+	case st3m_usb_mode_kind_disk:
+		running = true;
+	default:
+	}
+
+	bool should_run = false;
+	switch (mode->kind) {
+	case st3m_usb_mode_kind_app:
+	case st3m_usb_mode_kind_disk:
+		should_run = true;
+	default:
+	}
+
+	if (running) {
+		ESP_LOGI(TAG, "stopping and disconnecting");
+        usb_phy_action(phy_hdl, USB_PHY_ACTION_HOST_FORCE_DISCONN);
+		vTaskDelay(1000 / portTICK_PERIOD_MS);
+	}
+
+	st3m_usb_descriptors_switch(mode);
+	if (mode->kind == st3m_usb_mode_kind_disk) {
+		assert(mode->disk != NULL);
+		st3m_usb_msc_set_conf(mode->disk);
+	}
+	if (mode->kind == st3m_usb_mode_kind_app) {
+		assert(mode->app != NULL);
+		st3m_usb_cdc_set_conf(mode->app);
+	}
+
+	if (should_run) {
+		ESP_LOGI(TAG, "reconnecting and starting");
+        usb_phy_action(phy_hdl, USB_PHY_ACTION_HOST_ALLOW_CONN);
+	}
+
+	_mode = mode->kind;
+	_connected = false;
+	xSemaphoreGive(_mu);
+}
+
+bool st3m_usb_connected(void) {
+	xSemaphoreTake(_mu, portMAX_DELAY);
+	bool res = _connected;
+	xSemaphoreGive(_mu);
+	return res;
+}
+
+void tud_mount_cb(void) {
+	ESP_LOGI(TAG, "USB attached");
+	xSemaphoreTake(_mu, portMAX_DELAY);
+	_connected = true;
+	xSemaphoreGive(_mu);
+}
+
+void tud_suspend_cb(bool remote_wakeup_en) {
+	ESP_LOGI(TAG, "USB detached");
+
+	xSemaphoreTake(_mu, portMAX_DELAY);
+	st3m_usb_mode_kind_t mode = _mode;
+	_connected = false;
+	xSemaphoreGive(_mu);
+	if (mode == st3m_usb_mode_kind_app) {
+		st3m_usb_cdc_detached();
+	}
+}
\ No newline at end of file
diff --git a/components/st3m/st3m_usb.h b/components/st3m/st3m_usb.h
new file mode 100644
index 0000000000..a13f7e41e3
--- /dev/null
+++ b/components/st3m/st3m_usb.h
@@ -0,0 +1,125 @@
+#pragma once
+
+// USB support for st3m, via the USB-OTG peripheral (not USB UART/JTAG!).
+//
+// USB is a complex beast. Attempting to make user-friendly wrappers that are
+// also general-purpose is a waste of time.
+//
+// What we do instead is provide a limited feature set for USB. A device is in
+// one of either three modes, and these modes can be switch at runtime:
+//  1. Disabled,
+//  2. Application, or
+//  3. Disk.
+//
+// Disabled mode makes the device appear as if it was just a passive charging
+// device. It does not appear on any host device if plugged in. This is the
+// default mode until the stack fully initializes. Then, the device switches
+// to application mode.
+//
+// Application mode is where the device spends most of its time. If plugged in,
+// it will enumerate as a `flow3r` and expose a CDC-ACM (serial) endpoint which
+// hosts the Micropython console. In the future more USB endpoints will appear
+// here: USB MIDI, for example.
+//
+// Device mode can be requested by software. If the device is plugged into a
+// host, it will enumerate as a USB mass storage device that can be written and
+// read from.
+//
+// Note that this codebase does not implement any of the logic behind any
+// endpoint (like CDC-ACM or MSC). Instead it just provides a callback-base
+// interface that is activated by switching to a mode configuration which
+// collects these callbacks. Higher level layers implement the rest of the
+// stack, like USB console for Micropython or flash/SD card mounting.
+//
+// The current API design assumes there is only one kind of each endpoint in
+// each mode, and thus things are a bit simplistic. This might change in the
+// future.
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+typedef enum {
+	// Device should not enumerate.
+	st3m_usb_mode_kind_disabled = 0,
+	// Device should appear as a 'flow3er' with a CDC-ACM (serial) endpoint.
+	st3m_usb_mode_kind_app = 1,
+	// Device should appear as a 'flower (disk mode)' with a MSC (mass storage)
+	// endpoint.
+	st3m_usb_mode_kind_disk = 2,
+} st3m_usb_mode_kind_t;
+
+// Description of the device in disk mode.
+typedef struct {
+	// Number of blocks.
+	size_t block_size;
+	// Size of each block (usually 512 bytes).
+	size_t block_count;
+	// Product ID, padded with zeroes.
+	uint8_t product_id[16];
+
+	// Optional. Called whenever the host asks if the device is ready. Defaults to always ready.
+	bool (*fn_ready)(uint8_t lun);
+	// Optional. Called whenever the hosts executes a scsi start/stop. Defaults to 'yeah sure' stub.
+	bool (*fn_start_stop)(uint8_t lun, uint8_t power_condition, bool start, bool load_eject);
+	// Required. Called when the host wishes to read from an LBA/offset. Address
+	// = lba*block_size+offset. Must return however many bytes were actually
+	// read.
+	int32_t (*fn_read10)(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
+	// Optional. Called when the host wishes to write to an LBA/offset. Defaults
+	// to ignoring writes.
+	int32_t (*fn_write10)(uint8_t lun, uint32_t lba, uint32_t offset, const void* buffer, uint32_t bufsize);
+} st3m_usb_msc_conf_t;
+
+// Description of the device in application mode.
+typedef struct {
+	// Required. Called whenever the host wrote some bytes. All bytes must be
+	// processed.
+	void (*fn_rx)(uint8_t *buffer, size_t bufsize);
+	// Required. Called whenever the host requests bytes to read. Must return
+	// how many bytes are actually available to transmit to the host.
+	size_t (*fn_txpoll)(uint8_t *buffer, size_t bufsize);
+	// Optional. Called whenever the host has detached from the device.
+	size_t (*fn_detach)(void);
+} st3m_usb_app_conf_t;
+
+// Main configuration structure, passed by pointer to st3m_usb_mode_switch.
+// Describes a requested configuration mode of the USB subsystem.
+typedef struct {
+	st3m_usb_mode_kind_t kind;
+
+	// Only valid if kind == disk.
+	st3m_usb_msc_conf_t *disk;
+	// Only valid if kind == app.
+	st3m_usb_app_conf_t *app;
+} st3m_usb_mode_t;
+
+
+// Immediately switch to a given mode, blocking until that mode is active. A
+// mode being active does not indicate that the device is connected to a host.
+void st3m_usb_mode_switch(st3m_usb_mode_t *target);
+
+// Initialize the subsystem. Must be called, bad things will happen otherwise.
+void st3m_usb_init();
+
+// Return true if the badge is connected to a host.
+bool st3m_usb_connected(void);
+
+// Private.
+void st3m_usb_descriptors_switch(st3m_usb_mode_t *mode);
+void st3m_usb_msc_set_conf(st3m_usb_msc_conf_t *conf);
+void st3m_usb_cdc_set_conf(st3m_usb_app_conf_t *conf);
+void st3m_usb_descriptors_set_serial(const char *serial);
+void st3m_usb_cdc_init(void);
+void st3m_usb_cdc_txpoll(void);
+void st3m_usb_cdc_detached(void);
+
+typedef enum {
+	st3m_usb_interface_disk_msc,
+	st3m_usb_interface_disk_total,
+} st3m_usb_interface_disk_t;
+
+typedef enum {
+	st3m_usb_interface_app_cdc,
+	st3m_usb_interface_app_total,
+} st3m_usb_interface_app_t;
diff --git a/components/st3m/st3m_usb_cdc.c b/components/st3m/st3m_usb_cdc.c
new file mode 100644
index 0000000000..6773d0a67b
--- /dev/null
+++ b/components/st3m/st3m_usb_cdc.c
@@ -0,0 +1,76 @@
+#include "st3m_usb.h"
+
+#include "tusb.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+
+#include "esp_log.h"
+
+static const char *TAG = "st3m-usb-cdc";
+
+static st3m_usb_app_conf_t _conf = { 0 };
+static SemaphoreHandle_t _write_mu;
+
+void st3m_usb_cdc_init(void) {
+    _write_mu = xSemaphoreCreateMutex();
+}
+
+void st3m_usb_cdc_set_conf(st3m_usb_app_conf_t *conf) {
+    assert(conf->fn_rx != NULL);
+    assert(conf->fn_txpoll != NULL);
+    memcpy(&_conf, conf, sizeof(_conf));
+}
+
+static st3m_usb_app_conf_t *_get_conf(void) {
+    assert(_conf.fn_rx != NULL);
+    assert(_conf.fn_txpoll != NULL);
+    return &_conf;
+}
+
+#define CDC_RX_BUFSIZE 64
+// TinyUSB callback: when host sent data over OUT endpoint.
+void tud_cdc_rx_cb(uint8_t itf) {
+    st3m_usb_app_conf_t *conf = _get_conf();
+
+    uint8_t buf[CDC_RX_BUFSIZE];
+    while (tud_cdc_n_available(itf)) {
+        int read_res = tud_cdc_n_read(itf, buf, CDC_RX_BUFSIZE);
+        conf->fn_rx(buf, read_res);
+    }
+}
+
+#define CDC_TX_BUFSIZE 64
+void st3m_usb_cdc_txpoll(void) {
+    st3m_usb_app_conf_t *conf = _get_conf();
+    if (conf->fn_txpoll == NULL) {
+        return;
+    }
+
+	for (;;) {
+        uint32_t space = tud_cdc_n_write_available(st3m_usb_interface_app_cdc);
+        if (space == 0) {
+            return;
+        }
+
+        uint8_t buf[CDC_TX_BUFSIZE];
+        if (space > CDC_TX_BUFSIZE) {
+            space = CDC_TX_BUFSIZE;
+        }
+
+        size_t to_write = conf->fn_txpoll(buf, space);
+        if (to_write == 0) {
+            return;
+        }
+        tud_cdc_n_write(st3m_usb_interface_app_cdc, buf, to_write);
+        tud_cdc_n_write_flush(st3m_usb_interface_app_cdc);
+    }
+}
+
+void st3m_usb_cdc_detached(void) {
+    st3m_usb_app_conf_t *conf = _get_conf();
+    if (conf->fn_detach == NULL) {
+        return;
+    }
+    conf->fn_detach();
+}
\ No newline at end of file
diff --git a/components/st3m/st3m_usb_descriptors.c b/components/st3m/st3m_usb_descriptors.c
new file mode 100644
index 0000000000..983405f071
--- /dev/null
+++ b/components/st3m/st3m_usb_descriptors.c
@@ -0,0 +1,170 @@
+#include "st3m_usb.h"
+
+#include "tusb.h"
+#include "esp_log.h"
+
+static const char *TAG = "st3m-usb-descs";
+
+static const char * const _manufacturer = "flow3r.garden";
+static const char * const _product_app = "flow3r";
+static const char * const _product_disk = "flow3r (disk mode)";
+static const char * const _cdc_repl = "MicroPython REPL";
+static char _serial[32+1] = {0};
+
+typedef struct {
+	const tusb_desc_device_t dev;
+	const uint8_t *cfg;
+	size_t num_str;
+	const char *str[16];
+} st3m_usb_descriptor_set_t;
+
+static const uint32_t st3m_usb_configdesc_len_app = TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN;
+
+#define EPNUM_CDC_0_NOTIF 0x81
+#define EPNUM_CDC_0_OUT   0x02
+#define EPNUM_CDC_0_IN    0x82
+
+static uint8_t _cfg_desc_app[] = {
+   	TUD_CONFIG_DESCRIPTOR(1, st3m_usb_interface_app_total, 0, st3m_usb_configdesc_len_app, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+	TUD_CDC_DESCRIPTOR(st3m_usb_interface_app_cdc, 4, EPNUM_CDC_0_NOTIF, 8, EPNUM_CDC_0_OUT, EPNUM_CDC_0_IN, 64),
+};
+
+static st3m_usb_descriptor_set_t _descset_app = {
+	.dev = {
+    	.bLength = sizeof(tusb_desc_device_t),
+    	.bDescriptorType = TUSB_DESC_DEVICE,
+    	.bcdUSB = 0x0200,
+
+    	.bDeviceClass = 0x00,
+    	.bDeviceSubClass = 0x00,
+    	.bDeviceProtocol = 0x00,
+
+    	.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+    	.idVendor = 0x303a,
+    	.idProduct = 0x4042,
+
+    	.bcdDevice = 0x0100,
+
+    	.iManufacturer = 0x01,
+    	.iProduct = 0x02,
+    	.iSerialNumber = 0x03,
+
+    	.bNumConfigurations = 0x01,
+	},
+	.cfg = _cfg_desc_app,
+	.num_str = 5,
+	.str = {
+    	(char[]){0x09, 0x04}, // EN
+		_manufacturer,
+		_product_app,
+		_serial,
+		_cdc_repl,
+	},
+};
+
+static const uint32_t st3m_usb_configdesc_len_disk = TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN;
+
+ // Use separate endpoint numbers otherwise TinyUSB seems to get very confused...
+ #define EPNUM_1_MSC_OUT 0x03
+ #define EPNUM_1_MSC_IN  0x83
+
+static uint8_t _cfg_desc_disk[] = {
+   	TUD_CONFIG_DESCRIPTOR(1, st3m_usb_interface_disk_total, 0, st3m_usb_configdesc_len_disk, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+	TUD_MSC_DESCRIPTOR(st3m_usb_interface_disk_msc, 0, EPNUM_1_MSC_OUT, EPNUM_1_MSC_IN, 64),
+};
+
+static st3m_usb_descriptor_set_t _descset_disk = {
+	.dev = {
+    	.bLength = sizeof(tusb_desc_device_t),
+    	.bDescriptorType = TUSB_DESC_DEVICE,
+    	.bcdUSB = 0x0200,
+
+    	.bDeviceClass = 0x00,
+    	.bDeviceSubClass = 0x00,
+    	.bDeviceProtocol = 0x00,
+
+    	.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+    	.idVendor = 0x303a,
+    	.idProduct = 0x4023,
+
+    	.bcdDevice = 0x0100,
+
+    	.iManufacturer = 0x01,
+    	.iProduct = 0x02,
+    	.iSerialNumber = 0x03,
+
+    	.bNumConfigurations = 0x01,
+	},
+	.cfg = _cfg_desc_disk,
+	.num_str = 4,
+	.str = {
+    	(char[]){0x09, 0x04}, // EN
+		_manufacturer,
+		_product_disk,
+		_serial,
+	},
+};
+
+static st3m_usb_descriptor_set_t *_descset_current = &_descset_app;
+
+uint8_t const *tud_descriptor_device_cb(void) {
+	return (uint8_t const *)&_descset_current->dev;
+}
+
+uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
+	return _descset_current->cfg;
+}
+
+uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
+{
+    (void) langid; // Unused, this driver supports only one language in string descriptors
+    uint8_t chr_count;
+    static uint16_t _desc_str[64];
+
+	const char **descs = _descset_current->str;
+
+    if (index == 0) {
+        memcpy(&_desc_str[1], descs[0], 2);
+        chr_count = 1;
+    } else {
+        if (index >= _descset_current->num_str) {
+            ESP_LOGW(TAG, "String index (%u) is out of bounds, check your string descriptor", index);
+            return NULL;
+        }
+
+        if (descs[index] == NULL) {
+            ESP_LOGW(TAG, "String index (%u) points to NULL, check your string descriptor", index);
+            return NULL;
+        }
+
+        const char *str = descs[index];
+        chr_count = strnlen(str, 63); // Buffer len - header
+
+        // 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 in bytes (including header), second byte is descriptor type (TUSB_DESC_STRING)
+    _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2 * chr_count + 2);
+
+    return _desc_str;
+}
+
+void st3m_usb_descriptors_switch(st3m_usb_mode_t *mode) {
+	switch (mode->kind) {
+	case st3m_usb_mode_kind_app:
+		_descset_current = &_descset_app;
+		break;
+	case st3m_usb_mode_kind_disk:
+		_descset_current = &_descset_disk;
+		break;
+	default:
+	}
+}
+
+void st3m_usb_descriptors_set_serial(const char *serial) {
+	memset(_serial, 0, 33);
+	strncpy(_serial, serial, 32);
+}
\ No newline at end of file
diff --git a/components/st3m/st3m_usb_msc.c b/components/st3m/st3m_usb_msc.c
new file mode 100644
index 0000000000..3d84e6d73b
--- /dev/null
+++ b/components/st3m/st3m_usb_msc.c
@@ -0,0 +1,122 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "st3m_usb.h"
+
+#include "tusb.h"
+#include "esp_log.h"
+
+static const char *TAG = "st3m-usb-msc";
+
+static st3m_usb_msc_conf_t _conf = { 0 };
+
+void st3m_usb_msc_set_conf(st3m_usb_msc_conf_t *conf) {
+	assert(conf->block_count != 0);
+	assert(conf->block_size != 0);
+	assert(conf->fn_read10 != NULL);
+	memcpy(&_conf, conf, sizeof(_conf));
+}
+
+static st3m_usb_msc_conf_t *_get_conf(void) {
+	assert(_conf.block_count != 0);
+	assert(_conf.block_size != 0);
+	assert(_conf.fn_read10 != NULL);
+	return &_conf;
+}
+
+// TinyUSB callback.
+void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
+{
+    (void) lun;
+
+	st3m_usb_msc_conf_t *conf = _get_conf();
+
+    const char vid[] = "flow3r";
+    const char rev[] = "1.0";
+
+    memcpy(vendor_id  , vid, strlen(vid));
+    memcpy(product_id , conf->product_id, 16);
+    memcpy(product_rev, rev, strlen(rev));
+}
+
+// TinyUSB callback.
+bool tud_msc_test_unit_ready_cb(uint8_t lun)
+{
+    (void) lun;
+	st3m_usb_msc_conf_t *conf = _get_conf();
+	if (conf->fn_ready != NULL) {
+		return conf->fn_ready(lun);
+	}
+    return true;
+}
+
+// TinyUSB callback.
+void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) {
+    (void) lun;
+	st3m_usb_msc_conf_t *conf = _get_conf();
+    *block_count = conf->block_count;
+    *block_size  = conf->block_size;
+}
+
+// TinyUSB callback.
+bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) {
+	st3m_usb_msc_conf_t *conf = _get_conf();
+	if (conf->fn_start_stop != NULL) {
+		return conf->fn_start_stop(lun, power_condition, start, load_eject);
+	}
+	return true;
+}
+
+// TinyUSB callback.
+int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
+	st3m_usb_msc_conf_t *conf = _get_conf();
+	return conf->fn_read10(lun, lba, offset, buffer, bufsize);
+}
+
+// TinyUSB callback.
+int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
+	st3m_usb_msc_conf_t *conf = _get_conf();
+	if (conf->fn_write10 != NULL) {
+		return conf->fn_write10(lun, lba, offset, buffer, bufsize);
+	}
+
+	if (lba >= conf->block_count) {
+		return -1;
+	}
+	return (int32_t) bufsize;
+}
+
+// TinyUSB callback.
+int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) {
+    void const* response = NULL;
+    int32_t resplen = 0;
+
+    // most scsi handled is input
+    bool in_xfer = true;
+
+    switch (scsi_cmd[0]) {
+    default:
+        // Set Sense = Invalid Command Operation
+        tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
+
+        // negative means error -> tinyusb could stall and/or response with failed status
+        resplen = -1;
+        break;
+    }
+
+    // return resplen must not larger than bufsize
+    if ( resplen > bufsize ) {
+		resplen = bufsize;
+	}
+
+    if ( response && (resplen > 0) ) {
+        if(in_xfer) {
+            memcpy(buffer, response, (size_t) resplen);
+        } else {
+            // SCSI output
+        }
+    }
+
+    return resplen;
+}
\ No newline at end of file
diff --git a/components/st3m/tusb_config.h b/components/st3m/tusb_config.h
new file mode 100644
index 0000000000..c4fc055a1a
--- /dev/null
+++ b/components/st3m/tusb_config.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "tusb_option.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CFG_TUSB_RHPORT0_MODE       OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED
+#define CFG_TUSB_OS                 OPT_OS_FREERTOS
+#define CFG_TUSB_MEM_SECTION
+#define CFG_TUSB_MEM_ALIGN       TU_ATTR_ALIGNED(4)
+#define CFG_TUSB_DEBUG 0
+
+int st3m_uart0_debug(const char *fmt, ...);
+#define CFG_TUSB_DEBUG_PRINTF st3m_uart0_debug
+
+#define CFG_TUD_ENDPOINT0_SIZE      64
+
+#define CFG_TUD_CDC 1
+#define CFG_TUD_CDC_RX_BUFSIZE 64
+#define CFG_TUD_CDC_TX_BUFSIZE 64
+
+#define CFG_TUD_MSC 1
+#define CFG_TUD_MSC_EP_BUFSIZE 512
+
+
+#ifdef __cplusplus
+}
+#endif
+
-- 
GitLab