2026-08-12 21:29:35 +02:00

193 lines
9.7 KiB
C

// 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