USB device-mode support (#613)

This commit is contained in:
Shadowtrance
2026-08-13 05:29:35 +10:00
committed by GitHub
parent 37c507544b
commit cc8be3faef
94 changed files with 3466 additions and 399 deletions
@@ -26,7 +26,8 @@ enum BtHidDeviceMode {
BT_HID_DEVICE_MODE_MOUSE,
/** Keyboard + Consumer + Mouse (report IDs 1, 2, 3). */
BT_HID_DEVICE_MODE_KEYBOARD_MOUSE,
/** Gamepad (report ID 1, 8 bytes: 2-byte buttons + 6-byte axes). */
/** Gamepad (report ID 1, 8 bytes: 5-byte axes X/Y/Rx/Ry/Z, 1-byte hat/dpad, 2-byte
* buttons[10] padded). */
BT_HID_DEVICE_MODE_GAMEPAD,
};
@@ -90,7 +91,7 @@ struct BtHidDeviceApi {
error_t (*send_mouse)(struct Device* device, const uint8_t* report, size_t len);
/**
* Send a gamepad HID report (8 bytes: buttons[2] + axes[6]).
* Send a gamepad HID report (8 bytes: axes[5] + hat[1] + buttons[2]).
* @param[in] device the HID device child device
* @param[in] report pointer to the 8-byte gamepad report
* @param[in] len number of bytes (up to 8)
@@ -43,6 +43,20 @@ struct KeyboardKeyData {
* separately. Drivers whose hardware cannot report Alt leave this false.
*/
bool alt;
/**
* @brief Standard USB HID keyboard usage code for this key (USB HID Usage Tables, page 0x07),
* or 0 if this driver doesn't compute one (most don't - `key` is the only field most consumers
* need). Populated by drivers whose hardware layout maps cleanly onto HID usage codes, so
* consumers that want to mirror physical key presses as real HID reports (e.g. USB HID output)
* don't have to reverse-engineer one out of `key`'s LVGL/ASCII encoding - which is lossy for
* keys with no ASCII/LVGL representation at all, e.g. F1-F12.
*/
uint8_t hid_keycode;
/**
* @brief HID modifier bitmask (report byte 0: bit0=LeftCtrl, bit1=LeftShift, bit2=LeftAlt,
* bit3=LeftGui, bit4-7=Right variants) matching hid_keycode, or 0 if hid_keycode is 0.
*/
uint8_t hid_modifier;
};
/**
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
struct DeviceType;
struct UsbInterfaceContribution;
// ---- USB device-mode CDC-ACM addon ----
/**
* USB CDC-ACM addon API (present a serial console over USB device mode). Unlike MSC/HID/MIDI,
* CDC is not a primary USB device class and does not go through
* usb_device_controller_claim()/release() - it's a devicetree-presence addon the USB device
* controller composites into whichever primary class is active (or stands alone with none
* active), toggled per-board via the usbdevicecdc0 devicetree child's status. This API is
* exposed by that child device and called by the USB device controller itself, not by primary
* class drivers.
*/
struct UsbCdcDeviceApi {
/**
* @param[in] device the CDC child device
* @return true if this board's usbdevicecdc0 child is present and enabled (status != "disabled")
*/
bool (*is_present)(struct Device* device);
/**
* Build this CDC instance's descriptor contribution, requesting interface/endpoint numbers
* from the controller via usb_device_controller_allocate_interfaces(). Called by the USB
* device controller itself during claim(), after the primary class's own contribution has
* already been allocated.
*
* `interface_string_index` is the string-descriptor index CDC's own interface string will
* land at once the controller appends it to the primary's string table (always
* primary_string_descriptor_count, since CDC's string is always appended last) - CDC bakes
* this index into its TUD_CDC_DESCRIPTOR bytes directly since it can't be patched after the
* fact. `out_interface_string` is CDC's own interface string text for the controller to
* append at that same index.
*
* @param[in] device the CDC child device
* @param[in] controller the USB device controller (to call allocate_interfaces() on)
* @param[in] interface_string_index string index this contribution's descriptor bytes must reference
* @param[out] out_contribution the built fragment
* @param[out] out_interface_string CDC's interface string text, to append at interface_string_index
* @retval ERROR_NONE on success
*/
error_t (*build_contribution)(struct Device* device, struct Device* controller,
uint8_t interface_string_index,
struct UsbInterfaceContribution* out_contribution,
const char** out_interface_string);
/**
* Install the CDC ACM interface and the log-mirroring vprintf hook. Called by the USB device
* controller after claim() successfully installs the composite descriptor.
* @param[in] device the CDC child device
* @retval ERROR_NONE on success
*/
error_t (*start_console)(struct Device* device);
/**
* Uninstall the CDC ACM interface and restore the previous vprintf hook. Called by the USB
* device controller before release() uninstalls the TinyUSB driver.
* @param[in] device the CDC child device
* @retval ERROR_NONE on success
*/
error_t (*stop_console)(struct Device* device);
};
extern const struct DeviceType USB_CDC_DEVICE_TYPE;
/**
* Find the first started USB CDC device and take a reference on it.
* @return the device with an outstanding reference, or NULL if none is available - caller must
* call device_put() exactly once when done, same as device_get_first_active_by_type().
*/
struct Device* usb_cdc_device_get(void);
bool usb_cdc_device_is_present(struct Device* device);
error_t usb_cdc_device_build_contribution(struct Device* device, struct Device* controller,
uint8_t interface_string_index,
struct UsbInterfaceContribution* out_contribution,
const char** out_interface_string);
error_t usb_cdc_device_start_console(struct Device* device);
error_t usb_cdc_device_stop_console(struct Device* device);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
struct DeviceType;
// ---- USB device-mode (peripheral) controller ----
/**
* Which USB device-mode class currently owns the single TinyUSB device-mode slot.
* Only one class may be active at a time - see usb_device_controller_claim(). CDC is not part of
* this enum: it's an addon that composites into whichever primary class is active (or stands
* alone with none active) rather than a primary itself - see usb_cdc_device.h.
*/
enum UsbDeviceClass {
USB_DEVICE_CLASS_NONE,
USB_DEVICE_CLASS_MSC,
USB_DEVICE_CLASS_HID_KEYBOARD,
USB_DEVICE_CLASS_MIDI,
};
/**
* A block of raw USB interface-descriptor bytes contributed by one class (MSC/HID/MIDI/CDC) to
* the composite configuration descriptor the controller assembles at claim() time.
*
* Interface and endpoint numbers inside the descriptor bytes must already be renumbered by the
* contributor to the base values the controller handed back via usb_device_controller_allocate_interfaces()
* - the controller does not parse or patch the bytes, it only concatenates them behind the
* config-descriptor header it writes itself.
*
* `hs_descriptor_bytes`/`hs_descriptor_bytes_len` are optional (leave both NULL/0 if this
* contributor's bytes don't differ between full-speed and high-speed, e.g. HID/MIDI/CDC's
* endpoint sizes are already speed-independent for their traffic) - only used when
* TUD_OPT_HIGH_SPEED is set, mirroring the fs_/hs_configuration_descriptor split
* tinyusb_config_t itself has (see MSC, whose bulk endpoint max-packet-size legitimately differs:
* 64 bytes FS vs 512 bytes HS).
*/
struct UsbInterfaceContribution {
const uint8_t* descriptor_bytes; // one or more TUD_*_DESCRIPTOR blocks, back to back (full-speed)
size_t descriptor_bytes_len;
const uint8_t* hs_descriptor_bytes; // optional high-speed variant; NULL to reuse descriptor_bytes
size_t hs_descriptor_bytes_len; // must equal descriptor_bytes_len (same interface/endpoint layout, only sizes differ)
uint8_t interface_count; // interfaces consumed (MSC/HID/CDC=1, MIDI=2)
uint8_t in_endpoint_count; // IN endpoints consumed (excluding EP0)
uint8_t out_endpoint_count; // OUT endpoints consumed
};
/**
* Interface/endpoint numbers assigned to one contributor by the controller before it built its
* descriptor bytes. Endpoint numbers are direction-local (IN and OUT each number from 1), as is
* standard for USB - usb_device_controller_allocate_interfaces() hands out the next free *pair*
* per direction, not a single shared counter.
*/
struct UsbInterfaceAllocation {
uint8_t first_interface_number;
uint8_t first_in_endpoint; // e.g. 0x81, 0x82, ... or 0 if in_endpoint_count was 0
uint8_t first_out_endpoint; // e.g. 0x01, 0x02, ... or 0 if out_endpoint_count was 0
};
/**
* A fully-described primary class descriptor plus device/string descriptor metadata, submitted
* to claim(). CDC (if enabled) is appended by the controller itself - primary contributors never
* see or reference CDC's interface numbers.
*/
struct UsbDeviceClaimConfig {
// Actually a `tusb_desc_device_t*` (TinyUSB's device descriptor struct) - void* here so this
// kernel header doesn't need to depend on TinyUSB's own headers; the controller and every
// contributor already build against TinyUSB directly and cast accordingly. Mutable: the
// controller patches the class triad (bDeviceClass/SubClass/Protocol) in place at claim()
// time depending on whether CDC is composited in.
void* device_descriptor;
const char* const* string_descriptor;
size_t string_descriptor_count;
struct UsbInterfaceContribution primary; // MSC or HID or MIDI's contribution
};
/**
* Shared owner of the TinyUSB device-mode (peripheral) stack. Exactly one primary USB device
* class (mass storage, HID, MIDI) may be installed at a time - callers claim the slot before use
* and release it when done. CDC is a separate, orthogonal addon (see usb_cdc_device.h) that the
* controller composites into whichever primary is active, independent of the claim/release cycle.
* This exists so multiple independent drivers (MSC, HID, MIDI, CDC) can share the single
* underlying `tinyusb_driver_install()` call, composite descriptor assembly, and any
* board-specific PHY routing without needing to know about each other.
*/
struct UsbDeviceControllerApi {
/**
* Starts a new interface/endpoint allocation session, resetting the numbering counters
* allocate_interfaces() hands out. Call once, before building any descriptor bytes, as the
* first step of a claim() attempt (i.e. before the primary class's own allocate_interfaces()
* call) - claim() itself calls this again internally for CDC's allocation, so primary
* contributors only need to call it for their own single call.
*
* There is no explicit abort/cancel: a session abandoned after begin_claim() (e.g. a
* contributor's own allocate_interfaces() call fails and it returns early without calling
* claim()) is simply reset by the next begin_claim() call, which unconditionally reinitializes
* the allocation state regardless of whether the previous session ever finished.
*
* @param[in] device the USB device controller device
* @retval ERROR_RESOURCE_BUSY if a different class already holds the slot
* @retval ERROR_NONE on success
*/
error_t (*begin_claim)(struct Device* device);
/**
* Ask the controller for the next free interface number and endpoint pair, before building
* descriptor bytes. Call once per contributor (primary class, and CDC internally) per
* begin_claim() session, in the order the resulting descriptor should list interfaces:
* primary class first, then CDC (the controller enforces this order internally for CDC;
* primary contributors just call this once for themselves, after begin_claim()).
* @param[in] device the USB device controller device
* @param[in] interface_count number of interfaces this contributor needs
* @param[in] in_endpoint_count number of IN endpoints needed (0 if none)
* @param[in] out_endpoint_count number of OUT endpoints needed (0 if none)
* @param[out] out_allocation the assigned numbers
* @retval ERROR_INVALID_STATE if called without a preceding begin_claim()
* @retval ERROR_NONE on success
*/
error_t (*allocate_interfaces)(struct Device* device, uint8_t interface_count,
uint8_t in_endpoint_count, uint8_t out_endpoint_count,
struct UsbInterfaceAllocation* out_allocation);
/**
* Claim the USB device-mode slot for the given primary class and install the composite
* descriptor (primary + CDC, if the board's usbdevicecdc0 child is enabled). Must be called
* after begin_claim() and the primary's own allocate_interfaces() call, using the same
* device-mode session (no other claim()/begin_claim() calls in between).
* @param[in] device the USB device controller device
* @param[in] usb_class the class to claim the slot for
* @param[in] config the primary class's descriptor contribution and metadata
* @retval ERROR_RESOURCE_BUSY if a different class already holds the slot
* @retval ERROR_NONE on success
*/
error_t (*claim)(struct Device* device, enum UsbDeviceClass usb_class,
const struct UsbDeviceClaimConfig* config);
/**
* Release the USB device-mode slot. Stops the CDC console (if it was composited in),
* disconnects from the host, uninstalls the TinyUSB driver, and restores any board-specific
* PHY routing. Only the current holder may release.
* @param[in] device the USB device controller device
* @param[in] usb_class the class releasing the slot; must match the current holder
* @retval ERROR_INVALID_STATE if usb_class does not hold the slot
* @retval ERROR_NONE on success
*/
error_t (*release)(struct Device* device, enum UsbDeviceClass usb_class);
/**
* @param[in] device the USB device controller device
* @return the class currently holding the slot, or USB_DEVICE_CLASS_NONE if free
*/
enum UsbDeviceClass (*get_active_class)(struct Device* device);
/**
* @param[in] device the USB device controller device
* @return true if a usbdevicecdc0 child device is present and enabled on this board
*/
bool (*is_cdc_enabled)(struct Device* device);
};
extern const struct DeviceType USB_DEVICE_CONTROLLER_TYPE;
/**
* Find the first started USB device controller and take a reference on it.
* @return the device with an outstanding reference, or NULL if none is available - caller must
* call device_put() exactly once when done, same as device_get_first_active_by_type().
*/
struct Device* usb_device_controller_get(void);
error_t usb_device_controller_begin_claim(struct Device* device);
error_t usb_device_controller_allocate_interfaces(struct Device* device, uint8_t interface_count,
uint8_t in_endpoint_count, uint8_t out_endpoint_count,
struct UsbInterfaceAllocation* out_allocation);
error_t usb_device_controller_claim(struct Device* device, enum UsbDeviceClass usb_class,
const struct UsbDeviceClaimConfig* config);
error_t usb_device_controller_release(struct Device* device, enum UsbDeviceClass usb_class);
enum UsbDeviceClass usb_device_controller_get_active_class(struct Device* device);
bool usb_device_controller_is_cdc_enabled(struct Device* device);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
struct DeviceType;
// ---- USB HID device mode ----
/**
* Selects the HID report descriptor installed when this device operates as a USB HID
* peripheral. Mirrors BtHidDeviceMode's shape (bluetooth_hid_device.h) so callers that want to
* support both transports use the same mode taxonomy and send_* call shape.
*/
enum UsbHidDeviceMode {
/** Keyboard (report ID 1, boot-protocol-compatible 8 bytes) + Consumer (report ID 2, 2 bytes). */
USB_HID_DEVICE_MODE_KEYBOARD,
/** Mouse only (report ID 1, 4 bytes). */
USB_HID_DEVICE_MODE_MOUSE,
/** Keyboard + Consumer + Mouse (report IDs 1, 2, 3). */
USB_HID_DEVICE_MODE_KEYBOARD_MOUSE,
/** Gamepad (report ID 1, 8 bytes: 5-byte axes, 1-byte hat/dpad, 2-byte buttons[10] padded). */
USB_HID_DEVICE_MODE_GAMEPAD,
};
/**
* USB HID device profile API (present this device as a USB HID peripheral to a host).
* This API is exposed by a child device of the USB device controller.
*/
struct UsbHidDeviceApi {
/**
* Claim the USB device-mode slot and start advertising as a USB HID device with the given
* mode.
* @param[in] device the HID device child device
* @param[in] mode the HID device mode (keyboard, mouse, keyboard+mouse, gamepad)
* @retval ERROR_RESOURCE_BUSY if another USB device class already holds the slot
* @retval ERROR_NONE on success
*/
error_t (*start)(struct Device* device, enum UsbHidDeviceMode mode);
/**
* Stop presenting as a USB HID device and release the USB device-mode slot.
* @param[in] device the HID device child device
* @return ERROR_NONE on success
*/
error_t (*stop)(struct Device* device);
/**
* Override the USB product name string reported to the host (iProduct descriptor). Only
* takes effect on the next start() - the descriptor is fixed for the lifetime of a claimed
* session, matching bluetooth_set_device_name()'s pattern of being set before starting
* advertising. If never called, each mode falls back to its own default name (e.g.
* "Tactility Keyboard", "Tactility Mouse").
* @param[in] device the HID device child device
* @param[in] name the product name (copied; safe to free/reuse the caller's buffer after
* this call returns)
* @return ERROR_NONE on success
*/
error_t (*set_name)(struct Device* device, const char* name);
/**
* Send a keyboard HID report (report ID 1: modifier, reserved, keycodes[6] - 8 bytes after
* the ID byte). Only valid in USB_HID_DEVICE_MODE_KEYBOARD or _KEYBOARD_MOUSE.
* @param[in] device the HID device child device
* @param[in] report pointer to the 8-byte keyboard report (modifier, reserved, keycode[6])
* @param[in] len number of bytes (up to 8)
* @return ERROR_NONE on success
*/
error_t (*send_keyboard)(struct Device* device, const uint8_t* report, size_t len);
/**
* Send a consumer control HID report (report ID 2: 16-bit usage code, little-endian).
* Only valid in USB_HID_DEVICE_MODE_KEYBOARD or _KEYBOARD_MOUSE.
* @param[in] device the HID device child device
* @param[in] report pointer to the 2-byte consumer report
* @param[in] len number of bytes (up to 2)
* @return ERROR_NONE on success
*/
error_t (*send_consumer)(struct Device* device, const uint8_t* report, size_t len);
/**
* Send a mouse HID report (buttons, X, Y, wheel - 4 bytes). Report ID 1 in
* USB_HID_DEVICE_MODE_MOUSE, report ID 3 in _KEYBOARD_MOUSE.
* @param[in] device the HID device child device
* @param[in] report pointer to the 4-byte mouse report
* @param[in] len number of bytes (up to 4)
* @return ERROR_NONE on success
*/
error_t (*send_mouse)(struct Device* device, const uint8_t* report, size_t len);
/**
* Send a gamepad HID report (report ID 1: axes[5] (X,Y,Rx,Ry,Z), hat/dpad[1] (low nibble),
* buttons[2] (10 buttons + 6 padding bits) - 8 bytes). Only valid in
* USB_HID_DEVICE_MODE_GAMEPAD. See hid_report_map_gamepad in hid_report_descriptors.cpp for
* the full wire layout.
* @param[in] device the HID device child device
* @param[in] report pointer to the 8-byte gamepad report
* @param[in] len number of bytes (up to 8)
* @return ERROR_NONE on success
*/
error_t (*send_gamepad)(struct Device* device, const uint8_t* report, size_t len);
/**
* @param[in] device the HID device child device
* @return true when a USB host has mounted the device and the HID interface is ready
*/
bool (*is_connected)(struct Device* device);
};
extern const struct DeviceType USB_HID_DEVICE_TYPE;
/**
* Find the first started USB HID device child device and take a reference on it.
* @return the device with an outstanding reference, or NULL if none is available - caller must
* call device_put() exactly once when done, same as device_get_first_active_by_type().
*/
struct Device* usb_hid_device_get(void);
error_t usb_hid_device_start(struct Device* device, enum UsbHidDeviceMode mode);
error_t usb_hid_device_stop(struct Device* device);
error_t usb_hid_device_set_name(struct Device* device, const char* name);
error_t usb_hid_device_send_keyboard(struct Device* device, const uint8_t* report, size_t len);
error_t usb_hid_device_send_consumer(struct Device* device, const uint8_t* report, size_t len);
error_t usb_hid_device_send_mouse(struct Device* device, const uint8_t* report, size_t len);
error_t usb_hid_device_send_gamepad(struct Device* device, const uint8_t* report, size_t len);
bool usb_hid_device_is_connected(struct Device* device);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
struct DeviceType;
// ---- USB MIDI device mode ----
/**
* USB MIDI device profile API (present this device as a USB MIDI peripheral to a host).
* This API is exposed by a child device of the USB device controller. Mirrors
* bluetooth_midi.h's shape (no mode enum - unlike HID, MIDI has exactly one device profile) so
* the two transports stay symmetric for callers that want to support both.
*/
struct UsbMidiDeviceApi {
/**
* Claim the USB device-mode slot and start advertising as a USB MIDI device.
* @param[in] device the MIDI device child device
* @retval ERROR_RESOURCE_BUSY if another USB device class already holds the slot
* @retval ERROR_NONE on success
*/
error_t (*start)(struct Device* device);
/**
* Stop presenting as a USB MIDI device and release the USB device-mode slot.
* @param[in] device the MIDI device child device
* @return ERROR_NONE on success
*/
error_t (*stop)(struct Device* device);
/**
* Override the USB product name string reported to the host (iProduct descriptor). Only
* takes effect on the next start() - matches bluetooth_set_device_name()'s pattern of being
* set before starting advertising. If never called, falls back to "Tactility MIDI Device".
* @param[in] device the MIDI device child device
* @param[in] name the product name (copied; safe to free/reuse the caller's buffer after
* this call returns)
* @return ERROR_NONE on success
*/
error_t (*set_name)(struct Device* device, const char* name);
/**
* Send raw MIDI message bytes over the USB MIDI connection.
* @param[in] device the MIDI device child device
* @param[in] msg the raw MIDI bytes
* @param[in] len the number of bytes
* @return ERROR_NONE on success
*/
error_t (*send)(struct Device* device, const uint8_t* msg, size_t len);
/**
* @param[in] device the MIDI device child device
* @return true when a USB host has mounted the device and the MIDI interface is ready
*/
bool (*is_connected)(struct Device* device);
};
extern const struct DeviceType USB_MIDI_DEVICE_TYPE;
/**
* Find the first started USB MIDI device child device and take a reference on it.
* @return the device with an outstanding reference, or NULL if none is available - caller must
* call device_put() exactly once when done, same as device_get_first_active_by_type().
*/
struct Device* usb_midi_device_get(void);
error_t usb_midi_device_start(struct Device* device);
error_t usb_midi_device_stop(struct Device* device);
error_t usb_midi_device_set_name(struct Device* device, const char* name);
error_t usb_midi_device_send(struct Device* device, const uint8_t* msg, size_t len);
bool usb_midi_device_is_connected(struct Device* device);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
struct DeviceType;
// ---- USB mass-storage device mode ----
/** Which backing storage is exposed to the USB host as a mass-storage volume. */
enum UsbMscDeviceSource {
USB_MSC_DEVICE_SOURCE_SDMMC,
USB_MSC_DEVICE_SOURCE_FLASH,
};
/** Fired when the exposed volume's mount state changes (host mounted/unmounted it). */
typedef void (*UsbMscDeviceMountChangedCallback)(bool mounted, void* context);
/**
* USB mass-storage device profile API (present the board's SD card or internal flash as a USB
* mass-storage device to a host).
*/
struct UsbMscDeviceApi {
/**
* Claim the USB device-mode slot and expose the given storage source as a USB mass-storage
* volume.
* @warning the caller must ensure the backing storage is unmounted/quiesced from local use
* before calling this - a source exposed to a USB host while still locally mounted
* risks filesystem corruption from two concurrent writers. Tactility's own flash-MSC
* path (see UsbTusb.cpp / UsbSettingsApp) enforces this via a dedicated reboot-into-
* MSC boot flow rather than unmounting live, so local access never overlaps with USB
* exposure; local availability is only restored by rebooting back to normal OS.
* @param[in] device the MSC device child device
* @param[in] source which backing storage to expose
* @param[in] source_handle the backing storage handle: a `sdmmc_card_t*` when source is
* USB_MSC_DEVICE_SOURCE_SDMMC, or a pointer to a `wl_handle_t` (i.e. `wl_handle_t*`)
* when source is USB_MSC_DEVICE_SOURCE_FLASH - passed by address, not cast through
* `void*` by value, since `wl_handle_t` is a plain integer type where 0 is a valid
* handle and would collide with the nullptr/"no handle" check otherwise. The caller
* resolves this - platform-esp32 has no business knowing which wear-levelling
* partition Tactility mounted as /data.
* @param[in] mount_changed_cb optional callback fired on host mount/unmount, nullable
* @param[in] context passed back to mount_changed_cb, nullable
* @retval ERROR_RESOURCE_BUSY if another USB device class already holds the slot
* @retval ERROR_INVALID_ARGUMENT if source_handle is invalid for the given source
* @retval ERROR_NONE on success
*/
error_t (*start)(struct Device* device, enum UsbMscDeviceSource source, void* source_handle,
UsbMscDeviceMountChangedCallback mount_changed_cb, void* context);
/**
* Stop presenting as a USB mass-storage device and release the USB device-mode slot.
* @param[in] device the MSC device child device
* @return ERROR_NONE on success
*/
error_t (*stop)(struct Device* device);
/**
* @param[in] device the MSC device child device
* @return true when a USB host currently has the volume mounted
*/
bool (*is_connected)(struct Device* device);
};
extern const struct DeviceType USB_MSC_DEVICE_TYPE;
/**
* Find the first started USB MSC device child device and take a reference on it.
* @return the device with an outstanding reference, or NULL if none is available - caller must
* call device_put() exactly once when done, same as device_get_first_active_by_type().
*/
struct Device* usb_msc_device_get(void);
error_t usb_msc_device_start(struct Device* device, enum UsbMscDeviceSource source, void* source_handle,
UsbMscDeviceMountChangedCallback mount_changed_cb, void* context);
error_t usb_msc_device_stop(struct Device* device);
bool usb_msc_device_is_connected(struct Device* device);
#ifdef __cplusplus
}
#endif
+4 -2
View File
@@ -10,10 +10,12 @@ extern "C" {
error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
const auto* driver = device_get_driver(device);
// Default the modifier fields here rather than in each driver: only drivers whose hardware can
// report modifiers set them, and the rest would otherwise leave whatever the caller's stack held.
// Default the modifier/HID fields here rather than in each driver: only drivers whose hardware
// can report them set them, and the rest would otherwise leave whatever the caller's stack held.
data->ctrl = false;
data->alt = false;
data->hid_keycode = 0;
data->hid_modifier = 0;
return KEYBOARD_DRIVER_API(driver)->read_key(device, data);
}
@@ -0,0 +1,38 @@
#include <tactility/drivers/usb_cdc_device.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#define USB_CDC_DEVICE_API(device) ((const struct UsbCdcDeviceApi*)device_get_driver(device)->api)
extern "C" {
const struct DeviceType USB_CDC_DEVICE_TYPE = {
.name = "usb-cdc-device",
};
struct Device* usb_cdc_device_get() {
struct Device* found = nullptr;
device_get_first_active_by_type(&USB_CDC_DEVICE_TYPE, &found);
return found;
}
bool usb_cdc_device_is_present(struct Device* device) {
return USB_CDC_DEVICE_API(device)->is_present(device);
}
error_t usb_cdc_device_build_contribution(struct Device* device, struct Device* controller,
uint8_t interface_string_index,
struct UsbInterfaceContribution* out_contribution,
const char** out_interface_string) {
return USB_CDC_DEVICE_API(device)->build_contribution(device, controller, interface_string_index, out_contribution, out_interface_string);
}
error_t usb_cdc_device_start_console(struct Device* device) {
return USB_CDC_DEVICE_API(device)->start_console(device);
}
error_t usb_cdc_device_stop_console(struct Device* device) {
return USB_CDC_DEVICE_API(device)->stop_console(device);
}
} // extern "C"
@@ -0,0 +1,45 @@
#include <tactility/drivers/usb_device_controller.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#define USB_DEVICE_CONTROLLER_API(device) ((const struct UsbDeviceControllerApi*)device_get_driver(device)->api)
extern "C" {
const struct DeviceType USB_DEVICE_CONTROLLER_TYPE = {
.name = "usb-device-controller",
};
struct Device* usb_device_controller_get() {
struct Device* found = nullptr;
device_get_first_active_by_type(&USB_DEVICE_CONTROLLER_TYPE, &found);
return found;
}
error_t usb_device_controller_begin_claim(struct Device* device) {
return USB_DEVICE_CONTROLLER_API(device)->begin_claim(device);
}
error_t usb_device_controller_allocate_interfaces(struct Device* device, uint8_t interface_count,
uint8_t in_endpoint_count, uint8_t out_endpoint_count,
struct UsbInterfaceAllocation* out_allocation) {
return USB_DEVICE_CONTROLLER_API(device)->allocate_interfaces(device, interface_count, in_endpoint_count, out_endpoint_count, out_allocation);
}
error_t usb_device_controller_claim(struct Device* device, enum UsbDeviceClass usb_class, const struct UsbDeviceClaimConfig* config) {
return USB_DEVICE_CONTROLLER_API(device)->claim(device, usb_class, config);
}
error_t usb_device_controller_release(struct Device* device, enum UsbDeviceClass usb_class) {
return USB_DEVICE_CONTROLLER_API(device)->release(device, usb_class);
}
enum UsbDeviceClass usb_device_controller_get_active_class(struct Device* device) {
return USB_DEVICE_CONTROLLER_API(device)->get_active_class(device);
}
bool usb_device_controller_is_cdc_enabled(struct Device* device) {
return USB_DEVICE_CONTROLLER_API(device)->is_cdc_enabled(device);
}
} // extern "C"
@@ -0,0 +1,51 @@
#include <tactility/drivers/usb_hid_device.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#define USB_HID_DEVICE_API(device) ((const struct UsbHidDeviceApi*)device_get_driver(device)->api)
extern "C" {
const struct DeviceType USB_HID_DEVICE_TYPE = {
.name = "usb-hid-device",
};
struct Device* usb_hid_device_get() {
struct Device* found = nullptr;
device_get_first_active_by_type(&USB_HID_DEVICE_TYPE, &found);
return found;
}
error_t usb_hid_device_start(struct Device* device, enum UsbHidDeviceMode mode) {
return USB_HID_DEVICE_API(device)->start(device, mode);
}
error_t usb_hid_device_stop(struct Device* device) {
return USB_HID_DEVICE_API(device)->stop(device);
}
error_t usb_hid_device_set_name(struct Device* device, const char* name) {
return USB_HID_DEVICE_API(device)->set_name(device, name);
}
error_t usb_hid_device_send_keyboard(struct Device* device, const uint8_t* report, size_t len) {
return USB_HID_DEVICE_API(device)->send_keyboard(device, report, len);
}
error_t usb_hid_device_send_consumer(struct Device* device, const uint8_t* report, size_t len) {
return USB_HID_DEVICE_API(device)->send_consumer(device, report, len);
}
error_t usb_hid_device_send_mouse(struct Device* device, const uint8_t* report, size_t len) {
return USB_HID_DEVICE_API(device)->send_mouse(device, report, len);
}
error_t usb_hid_device_send_gamepad(struct Device* device, const uint8_t* report, size_t len) {
return USB_HID_DEVICE_API(device)->send_gamepad(device, report, len);
}
bool usb_hid_device_is_connected(struct Device* device) {
return USB_HID_DEVICE_API(device)->is_connected(device);
}
} // extern "C"
@@ -0,0 +1,39 @@
#include <tactility/drivers/usb_midi_device.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#define USB_MIDI_DEVICE_API(device) ((const struct UsbMidiDeviceApi*)device_get_driver(device)->api)
extern "C" {
const struct DeviceType USB_MIDI_DEVICE_TYPE = {
.name = "usb-midi-device",
};
struct Device* usb_midi_device_get() {
struct Device* found = nullptr;
device_get_first_active_by_type(&USB_MIDI_DEVICE_TYPE, &found);
return found;
}
error_t usb_midi_device_start(struct Device* device) {
return USB_MIDI_DEVICE_API(device)->start(device);
}
error_t usb_midi_device_stop(struct Device* device) {
return USB_MIDI_DEVICE_API(device)->stop(device);
}
error_t usb_midi_device_set_name(struct Device* device, const char* name) {
return USB_MIDI_DEVICE_API(device)->set_name(device, name);
}
error_t usb_midi_device_send(struct Device* device, const uint8_t* msg, size_t len) {
return USB_MIDI_DEVICE_API(device)->send(device, msg, len);
}
bool usb_midi_device_is_connected(struct Device* device) {
return USB_MIDI_DEVICE_API(device)->is_connected(device);
}
} // extern "C"
@@ -0,0 +1,32 @@
#include <tactility/drivers/usb_msc_device.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#define USB_MSC_DEVICE_API(device) ((const struct UsbMscDeviceApi*)device_get_driver(device)->api)
extern "C" {
const struct DeviceType USB_MSC_DEVICE_TYPE = {
.name = "usb-msc-device",
};
struct Device* usb_msc_device_get() {
struct Device* found = nullptr;
device_get_first_active_by_type(&USB_MSC_DEVICE_TYPE, &found);
return found;
}
error_t usb_msc_device_start(struct Device* device, enum UsbMscDeviceSource source, void* source_handle,
UsbMscDeviceMountChangedCallback mount_changed_cb, void* context) {
return USB_MSC_DEVICE_API(device)->start(device, source, source_handle, mount_changed_cb, context);
}
error_t usb_msc_device_stop(struct Device* device) {
return USB_MSC_DEVICE_API(device)->stop(device);
}
bool usb_msc_device_is_connected(struct Device* device) {
return USB_MSC_DEVICE_API(device)->is_connected(device);
}
} // extern "C"
+38
View File
@@ -33,9 +33,13 @@
#include <tactility/drivers/spi_controller.h>
#include <tactility/drivers/trackball.h>
#include <tactility/drivers/uart_controller.h>
#include <tactility/drivers/usb_device_controller.h>
#include <tactility/drivers/usb_hid_device.h>
#include <tactility/drivers/usb_host_hid.h>
#include <tactility/drivers/usb_host_midi.h>
#include <tactility/drivers/usb_host_msc.h>
#include <tactility/drivers/usb_midi_device.h>
#include <tactility/drivers/usb_msc_device.h>
#include <tactility/drivers/wifi.h>
#include <tactility/error.h>
#include <tactility/filesystem/file_mutex.h>
@@ -422,6 +426,40 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
// drivers/usb_host_msc
DEFINE_MODULE_SYMBOL(usb_msc_eject),
DEFINE_MODULE_SYMBOL(USB_HOST_MSC_TYPE),
// drivers/usb_device_controller
DEFINE_MODULE_SYMBOL(usb_device_controller_get),
DEFINE_MODULE_SYMBOL(usb_device_controller_begin_claim),
DEFINE_MODULE_SYMBOL(usb_device_controller_allocate_interfaces),
DEFINE_MODULE_SYMBOL(usb_device_controller_claim),
DEFINE_MODULE_SYMBOL(usb_device_controller_release),
DEFINE_MODULE_SYMBOL(usb_device_controller_get_active_class),
DEFINE_MODULE_SYMBOL(usb_device_controller_is_cdc_enabled),
DEFINE_MODULE_SYMBOL(USB_DEVICE_CONTROLLER_TYPE),
// drivers/usb_hid_device
DEFINE_MODULE_SYMBOL(usb_hid_device_get),
DEFINE_MODULE_SYMBOL(usb_hid_device_start),
DEFINE_MODULE_SYMBOL(usb_hid_device_stop),
DEFINE_MODULE_SYMBOL(usb_hid_device_set_name),
DEFINE_MODULE_SYMBOL(usb_hid_device_send_keyboard),
DEFINE_MODULE_SYMBOL(usb_hid_device_send_consumer),
DEFINE_MODULE_SYMBOL(usb_hid_device_send_mouse),
DEFINE_MODULE_SYMBOL(usb_hid_device_send_gamepad),
DEFINE_MODULE_SYMBOL(usb_hid_device_is_connected),
DEFINE_MODULE_SYMBOL(USB_HID_DEVICE_TYPE),
// drivers/usb_msc_device
DEFINE_MODULE_SYMBOL(usb_msc_device_get),
DEFINE_MODULE_SYMBOL(usb_msc_device_start),
DEFINE_MODULE_SYMBOL(usb_msc_device_stop),
DEFINE_MODULE_SYMBOL(usb_msc_device_is_connected),
DEFINE_MODULE_SYMBOL(USB_MSC_DEVICE_TYPE),
// drivers/usb_midi_device
DEFINE_MODULE_SYMBOL(usb_midi_device_get),
DEFINE_MODULE_SYMBOL(usb_midi_device_start),
DEFINE_MODULE_SYMBOL(usb_midi_device_stop),
DEFINE_MODULE_SYMBOL(usb_midi_device_set_name),
DEFINE_MODULE_SYMBOL(usb_midi_device_send),
DEFINE_MODULE_SYMBOL(usb_midi_device_is_connected),
DEFINE_MODULE_SYMBOL(USB_MIDI_DEVICE_TYPE),
// concurrent/dispatcher
DEFINE_MODULE_SYMBOL(dispatcher_alloc),
DEFINE_MODULE_SYMBOL(dispatcher_free),