Skip to content
Snippets Groups Projects
  • q3k's avatar
    61acee41
    st3m: add usb stack (unused) · 61acee41
    q3k authored
    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.
    61acee41
    History
    st3m: add usb stack (unused)
    q3k authored
    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.
st3m_usb.h 4.77 KiB
#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;