Skip to content
Snippets Groups Projects
Commit 61acee41 authored by q3k's avatar q3k
Browse files

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.
parent 7ea31a6c
No related branches found
No related tags found
No related merge requests found
Pipeline #5730 passed
...@@ -9,6 +9,10 @@ idf_component_register( ...@@ -9,6 +9,10 @@ idf_component_register(
st3m_leds.c st3m_leds.c
st3m_colors.c st3m_colors.c
st3m_io.c st3m_io.c
st3m_usb_cdc.c
st3m_usb_descriptors.c
st3m_usb_msc.c
st3m_usb.c
INCLUDE_DIRS INCLUDE_DIRS
. .
REQUIRES REQUIRES
...@@ -16,4 +20,9 @@ idf_component_register( ...@@ -16,4 +20,9 @@ idf_component_register(
bl00mbox bl00mbox
ctx ctx
fatfs fatfs
tinyusb
usb
) )
idf_component_get_property(tusb_lib tinyusb COMPONENT_LIB)
target_include_directories(${tusb_lib} PRIVATE .)
\ No newline at end of file
#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
#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;
#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
#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
#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
#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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment