Created
January 10, 2023 06:10
-
-
Save byteit101/3ccfc07a7fdc355614a38095c70dddd0 to your computer and use it in GitHub Desktop.
TinyUSB C++20 compile-time USB descriptor builder (Pico/RP2040 edition)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "tusb.h" | |
//#include <vector> | |
#include <initializer_list> | |
#include <algorithm> | |
#include <array> | |
//#include <tuple> | |
#include <cstddef> | |
#include <type_traits> | |
namespace gtu { | |
// todo: endpoint in/out may be different types? | |
struct endpoint_addr { | |
bool isIn; | |
uint8_t num; | |
constexpr uint8_t pack(){ // could be a bitfield, probably | |
return num | (isIn? 0x80 : 0x00); | |
} | |
}; | |
template<uint8_t channel> | |
constexpr endpoint_addr ep_in(){ | |
static_assert(channel < 16); | |
return { | |
.isIn = true, | |
.num = channel | |
}; | |
} | |
template<uint8_t channel> | |
constexpr endpoint_addr ep_out(){ | |
static_assert(channel < 16); | |
return { | |
.isIn = false, | |
.num = channel | |
}; | |
} | |
struct interface_ref { | |
uint8_t n; | |
constexpr uint8_t pack(){ | |
return n; | |
} | |
}; | |
// from https://web.archive.org/web/20161215110435/http://b.atch.se/posts/constexpr-counter/ | |
// and https://mc-deltat.github.io/articles/stateful-metaprogramming-cpp20 | |
namespace interface_counter { | |
template<unsigned N> | |
struct reader { | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Wnon-template-friend" | |
friend auto counted_flag(reader<N>); // E1 | |
#pragma GCC diagnostic pop | |
}; | |
template<unsigned N> | |
struct setter { | |
friend auto counted_flag(reader<N>) {} // E2 | |
static constexpr unsigned n = N; | |
}; | |
// E3 | |
template< | |
auto Tag, // E3.1 | |
unsigned NextVal = 0 | |
> | |
[[nodiscard]] | |
consteval interface_ref counter_impl() { | |
constexpr bool counted_past_value = requires(reader<NextVal> r) { | |
counted_flag(r); | |
}; | |
if constexpr (counted_past_value) { | |
return counter_impl<Tag, NextVal + 1>(); // E3.2 | |
} | |
else { | |
// E3.3 | |
setter<NextVal> s; | |
return {.n = (uint8_t)s.n}; | |
} | |
} | |
template< | |
auto Tag = []{}, // E4 | |
auto Val = counter_impl<Tag>() | |
> | |
constexpr auto alloc = Val; | |
// [[nodiscard]] stored_tuple<interface_ref, interface_ref> constexpr alloc2() | |
// { | |
// return store_tuple(alloc<>, alloc<>); | |
// } | |
} | |
// static constexpr auto fsec = interface_counter::alloc2<>; | |
// static constexpr auto fir = interface_counter::alloc<>; | |
// static constexpr auto sec = interface_counter::alloc<>; | |
// static_assert(fir.n == 0, "not 0"); | |
// static_assert(sec.n == 1, "not 0"); | |
constexpr tusb_desc_endpoint_t build_endpoint(tusb_desc_endpoint_t endpoint){ | |
endpoint.bLength = sizeof(tusb_desc_endpoint_t); | |
endpoint.bDescriptorType = TUSB_DESC_ENDPOINT; | |
return endpoint; | |
} | |
constexpr tusb_desc_endpoint_t build_endpoint(endpoint_addr bEndpointAddress, uint8_t bmAttributes, uint16_t wMaxPacketSize , uint8_t bInterval=0){ | |
return build_endpoint({ | |
.bEndpointAddress = bEndpointAddress.pack(), | |
.bmAttributes= bmAttributes, | |
.wMaxPacketSize = wMaxPacketSize, | |
.bInterval= bInterval | |
}); | |
} | |
constexpr tusb_desc_endpoint_t build_endpoint(uint8_t bEndpointAddress, uint8_t bmAttributes, uint16_t wMaxPacketSize , uint8_t bInterval=0){ | |
return build_endpoint({ | |
.bEndpointAddress = bEndpointAddress, | |
.bmAttributes= bmAttributes, | |
.wMaxPacketSize = wMaxPacketSize, | |
.bInterval= bInterval | |
}); | |
} | |
#pragma pack(1) | |
// packed, and well-ordered tuples | |
// std::tuple isn't, so we define our own | |
template <typename... T> | |
struct stored_tuple { }; | |
// sometimes, rest is > 0 when empty, so explicity delete it | |
template<typename T> | |
struct stored_tuple<T> | |
{ | |
T first; | |
constexpr stored_tuple(const T& f) | |
: first(f) {} | |
constexpr stored_tuple(T&& f) | |
: first(std::move(f)) {} | |
// copy and move ctors? | |
// move arg ctors? | |
}; | |
template<typename T, typename U, typename... Rest> | |
struct stored_tuple<T, U, Rest...> | |
{ | |
T first; | |
[[no_unique_address]] stored_tuple<U, Rest...> rest; | |
constexpr stored_tuple(const T& f, const U& u, const Rest&... r) | |
: first(f), rest(u, r...) {} | |
constexpr stored_tuple(T&& f, U&& u, Rest&&... r) | |
: first(std::move(f)), rest(u, r...) {} | |
// copy and move ctors? | |
// move arg ctors? | |
}; | |
static_assert(sizeof(stored_tuple<int>) == sizeof(int)); | |
static_assert(sizeof(stored_tuple<char, int>) == sizeof(char) + sizeof(int)); | |
// forward declaration | |
template<size_t i,typename T> | |
struct sthelper; | |
// mutable version | |
template<size_t i, template <typename...> class stored_tuple, typename... args> | |
constexpr auto& stored_get(stored_tuple<args...> &t) | |
{ | |
return sthelper<i, stored_tuple<args...>>::get(t); | |
} | |
template<typename T,typename... Rest> | |
struct sthelper<0, stored_tuple<T, Rest...>> { | |
constexpr static T& get(stored_tuple<T, Rest...> &data) { | |
return data.first; | |
} | |
constexpr static const T& get(const stored_tuple<T, Rest...> &data) { | |
return data.first; | |
} | |
}; | |
template<size_t i, typename T, typename... Rest> | |
struct sthelper<i, stored_tuple<T, Rest...>> { | |
constexpr static auto& get(stored_tuple<T, Rest...> &data) { | |
return sthelper<i - 1, stored_tuple<Rest...>>::get(data.rest); | |
} | |
constexpr static const auto& get(const stored_tuple<T, Rest...> &data) { | |
return sthelper<i - 1, stored_tuple<Rest...>>::get(data.rest); | |
} | |
}; | |
// const versions | |
// template<size_t i, typename... args> | |
// constexpr const auto& stored_get(const stored_tuple<args...> &t) | |
// { | |
// return sthelper<i, stored_tuple<args...>>::get(t); | |
// } | |
template<size_t i, typename base, typename... args> | |
constexpr const uint8_t* stored_extract(const stored_tuple<args...> &t) | |
{ | |
const base& value = sthelper<i, stored_tuple<args...>>::get(t); | |
return (const uint8_t*)&value; | |
} | |
// helpers | |
template<typename... T> | |
constexpr stored_tuple<std::unwrap_ref_decay_t<T>...> store_tuple(T&&... args) | |
{ | |
return stored_tuple<std::unwrap_ref_decay_t<T>...>(std::forward<T>(args)...); | |
} | |
template<typename T> | |
struct stored_tuple_size; | |
template<typename... T> | |
struct stored_tuple_size<stored_tuple<T...>> : std::integral_constant<std::size_t, sizeof...(T)> { }; | |
template<typename T> | |
inline constexpr std::size_t stored_tuple_size_v = stored_tuple_size<T>::value; | |
namespace audio { | |
/// USB Endpoint Descriptor | |
struct TU_ATTR_PACKED tusb_desc_endpoint_audio_t | |
{ | |
uint8_t bLength ; ///< Size of this descriptor in bytes | |
uint8_t bDescriptorType ; ///< ENDPOINT Descriptor Type | |
uint8_t bEndpointAddress ; | |
struct TU_ATTR_PACKED { | |
uint8_t xfer : 2; | |
uint8_t sync : 2; | |
uint8_t usage : 2; | |
uint8_t : 2; | |
} bmAttributes ; | |
uint16_t wMaxPacketSize; | |
uint8_t bInterval; | |
uint8_t bRefresh; | |
uint8_t bSynchAddress; | |
} ; | |
constexpr tusb_desc_endpoint_audio_t build_endpoint(tusb_desc_endpoint_audio_t endpoint){ | |
endpoint.bLength = sizeof(tusb_desc_endpoint_audio_t); | |
endpoint.bDescriptorType = TUSB_DESC_ENDPOINT; | |
return endpoint; | |
} | |
constexpr tusb_desc_endpoint_audio_t build_endpoint(endpoint_addr bEndpointAddress, uint8_t bmAttributes, uint16_t wMaxPacketSize , uint8_t bInterval=0, uint8_t bRefresh=0, uint8_t bSynchAddress=0){ | |
return build_endpoint({ | |
.bEndpointAddress = bEndpointAddress.pack(), | |
.bmAttributes= bmAttributes, | |
.wMaxPacketSize = wMaxPacketSize, | |
.bInterval= bInterval, | |
.bRefresh=bRefresh, | |
.bSynchAddress = bSynchAddress, | |
}); | |
} | |
constexpr tusb_desc_endpoint_audio_t build_endpoint(uint8_t bEndpointAddress, uint8_t bmAttributes, uint16_t wMaxPacketSize , uint8_t bInterval=0){ | |
return build_endpoint({ | |
.bEndpointAddress = bEndpointAddress, | |
.bmAttributes= bmAttributes, | |
.wMaxPacketSize = wMaxPacketSize, | |
.bInterval= bInterval, | |
}); | |
} | |
} | |
template<typename tuples=stored_tuple<>> | |
// TODO: requires? | |
struct busb_interface_assoc { | |
tusb_desc_interface_assoc_t core; | |
// TODO: fold expressions + https://stackoverflow.com/questions/72297845/how-to-check-if-an-object-is-an-instance-of-template-class-of-multiple-template should allow validating | |
[[no_unique_address]] tuples interfaces; | |
constexpr busb_interface_assoc(tusb_desc_interface_assoc_t interface_assoc, tuples children) | |
: core(interface_assoc), interfaces(children) | |
{ | |
core.bLength = sizeof(interface_assoc); | |
core.bDescriptorType = TUSB_DESC_INTERFACE_ASSOCIATION; | |
// TODO: std::get | |
core.bFirstInterface = stored_get<0>(children).base.bInterfaceNumber; | |
core.bInterfaceCount = numInterfaces(); | |
} | |
constexpr size_t numInterfaces() noexcept { return stored_tuple_size_v<tuples>; } | |
}; | |
template<typename... parts> | |
// TODO: requires? | |
struct busb_interface_tuple { | |
using tuples = stored_tuple<parts...>; | |
tuples interfaces; | |
constexpr busb_interface_tuple(parts... children) | |
: interfaces(store_tuple(children...)) | |
{ | |
} | |
constexpr size_t numInterfaces() noexcept { return sizeof...(parts); } | |
}; | |
template<typename size, typename first, typename... rest> | |
// TODO: requires? | |
struct busb_length_computer { | |
using tuples = stored_tuple<first, rest...>; | |
tuples core; | |
constexpr busb_length_computer(first frst, size first::*setter, rest... children) | |
: core(store_tuple(frst, children...)) | |
{ | |
stored_get<0>(core).*setter = sizeof(tuples); | |
} | |
}; | |
template<typename... config> | |
constexpr stored_tuple<tusb_desc_device_t, tusb_desc_configuration_t, stored_tuple<config...>> build_device(tusb_desc_device_t device, stored_tuple<tusb_desc_configuration_t, config...> configurations){ | |
device.bLength = sizeof(device); | |
device.bDescriptorType = TUSB_DESC_DEVICE; | |
device.bNumConfigurations = 1;// TODO: count types! | |
return store_tuple(device, | |
configurations.first, | |
configurations.rest); | |
} | |
template<typename... config> | |
constexpr stored_tuple<tusb_desc_configuration_t, config...> build_configuration(uint8_t iConfig, uint16_t maxPower, config... elements) | |
{ | |
tusb_desc_configuration_t cfg; | |
cfg.bLength = sizeof(cfg); | |
cfg.bDescriptorType = TUSB_DESC_CONFIGURATION; | |
cfg.bNumInterfaces = (elements.numInterfaces() + ...); | |
cfg.bConfigurationValue = 1;// TODO: expose? | |
cfg.iConfiguration = iConfig; | |
cfg.bmAttributes = TU_BIT(7) | 0;// TODO: expose 0? | |
cfg.bMaxPower = maxPower / 2; | |
return busb_length_computer(cfg, | |
&tusb_desc_configuration_t::wTotalLength, | |
elements...).core; | |
} | |
// All of this nonsense is because sizeof(array<T,0>) > 0 | |
// template<typename T, size_t N> | |
// struct elidable_array { | |
// std::array<T, N> value; | |
// constexpr elidable_array(std::array<T, N> value) : value(value) {} | |
// } | |
template<typename T> | |
struct elidable_array_impl { | |
constexpr elidable_array_impl(std::array<T, 0> value){} | |
constexpr elidable_array_impl(){} | |
}; | |
template<typename T, std::size_t N> | |
using elidable_array = std::conditional_t<N == 0, elidable_array_impl<T>, std::array<T, N>>; | |
template<size_t child_n=0, typename tuples=stored_tuple<>, typename endpoint_t=tusb_desc_endpoint_t> | |
struct busb_interface { | |
// pack away unused fields | |
tusb_desc_interface_t base; | |
[[no_unique_address]] tuples class_specific; | |
[[no_unique_address]] elidable_array<endpoint_t, child_n> children; | |
constexpr busb_interface(interface_ref intf, tusb_desc_interface_t import, tuples class_specific, std::array<endpoint_t, child_n> endpoints) | |
: base(import), class_specific(class_specific), children(endpoints){ | |
base.bLength = sizeof(base); | |
base.bDescriptorType = TUSB_DESC_INTERFACE; | |
base.bInterfaceNumber = intf.pack(); // TODO! | |
base.bNumEndpoints = endpoints.size(); | |
} | |
constexpr busb_interface(interface_ref intf, tusb_desc_interface_t import, std::array<endpoint_t, child_n> endpoints) : busb_interface(intf, import, store_tuple(), endpoints) {} | |
constexpr busb_interface(interface_ref intf, tusb_desc_interface_t import, tuples class_specific) : busb_interface(intf, import, class_specific, std::array<endpoint_t,0>{}) {} | |
constexpr busb_interface(interface_ref intf, tusb_desc_interface_t import) : busb_interface(intf, import, store_tuple(), std::array<endpoint_t,0>{}) {} | |
constexpr size_t numInterfaces() noexcept { return 1; } | |
}; | |
#pragma pack() | |
static_assert(sizeof(busb_interface<>) == sizeof(tusb_desc_interface_t), "packing failure"); | |
#define GTU_TUSB_Tie(name, struct, toptype) | |
#define GTU_TUSB_Tie_CS(name, struct, top, subtype) | |
#define GTU_import(file) | |
//GTU_import("..TODO!/pico-sdk/lib/tinyusb/src/class/cdc/cdc.h") | |
GTU_TUSB_Tie(interface_assoc, tusb_desc_interface_assoc_t, TUSB_DESC_INTERFACE_ASSOCIATION) | |
//\s*(AUDIO_CS_AC_INTERFACE_(\w+))\s+= 0x.., | |
//GTU_TUSB_Tie_CS(\L$1\E, TUSB_DESC_CS_INTERFACE, $1) | |
namespace audio { | |
//GTU_TUSB_Tie_CS(header, audio_desc_cs_ac_interface_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_HEADER) | |
GTU_TUSB_Tie_CS(input_terminal, audio_desc_input_terminal_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL) | |
GTU_TUSB_Tie_CS(output_terminal, audio_desc_output_terminal_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL) | |
GTU_TUSB_Tie_CS(feature_unit, audio_desc_feature_unit_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT) | |
GTU_TUSB_Tie_CS(clock_source, audio_desc_clock_source_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE) | |
GTU_TUSB_Tie_CS(clock_selector, audio_desc_clock_selector_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR) | |
GTU_TUSB_Tie_CS(clock_multiplier, audio_desc_clock_multiplier_t, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER) | |
namespace midi { | |
GTU_TUSB_Tie_CS(header, midi_desc_header_t, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER) | |
GTU_TUSB_Tie_CS(in_jack, midi_desc_in_jack_t, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK) | |
GTU_TUSB_Tie_CS(out_jack, midi_desc_out_jack_t, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK) | |
GTU_TUSB_Tie_CS(element, midi_desc_element_t, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_ELEMENT) | |
constexpr midi_desc_header_t build_header(midi_desc_header_t header) | |
{ | |
header.bLength = sizeof(header); | |
header.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
header.bDescriptorSubType = MIDI_CS_INTERFACE_HEADER; | |
return header; | |
} | |
constexpr midi_desc_header_t build_header(uint16_t bcdMSC, uint16_t wTotalLength) | |
{ | |
return build_header({ | |
.bcdMSC = bcdMSC, | |
.wTotalLength = wTotalLength, | |
}); | |
} | |
template<class... Ts> | |
constexpr busb_length_computer<uint16_t, midi_desc_header_t, Ts...> build_header(uint16_t bcdMSC, Ts... children) | |
{ | |
return gtu::busb_length_computer(build_header({ | |
.bcdMSC = bcdMSC, | |
}), &midi_desc_header_t::wTotalLength, | |
children...); | |
} | |
constexpr midi_desc_in_jack_t build_in_jack(midi_desc_in_jack_t in_jack) | |
{ | |
in_jack.bLength = sizeof(in_jack); | |
in_jack.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
in_jack.bDescriptorSubType = MIDI_CS_INTERFACE_IN_JACK; | |
return in_jack; | |
} | |
constexpr midi_desc_in_jack_t build_in_jack(uint8_t bJackType, uint8_t bJackID, uint8_t iJack) | |
{ | |
return build_in_jack({ | |
.bJackType = bJackType, | |
.bJackID = bJackID, | |
.iJack = iJack, | |
}); | |
} | |
constexpr midi_desc_out_jack_t build_out_jack(midi_desc_out_jack_t out_jack) | |
{ | |
out_jack.bLength = sizeof(out_jack); | |
out_jack.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
out_jack.bDescriptorSubType = MIDI_CS_INTERFACE_OUT_JACK; | |
return out_jack; | |
} | |
constexpr midi_desc_out_jack_t build_out_jack(uint8_t bJackType, uint8_t bJackID, uint8_t bNrInputPins, uint8_t baSourceID, uint8_t baSourcePin, uint8_t iJack) | |
{ | |
return build_out_jack({ | |
.bJackType = bJackType, | |
.bJackID = bJackID, | |
.bNrInputPins = bNrInputPins, | |
.baSourceID = baSourceID, | |
.baSourcePin = baSourcePin, | |
.iJack = iJack, | |
}); | |
} | |
constexpr midi_desc_element_t build_element(midi_desc_element_t element) | |
{ | |
element.bLength = sizeof(element); | |
element.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
element.bDescriptorSubType = MIDI_CS_INTERFACE_ELEMENT; | |
return element; | |
} | |
constexpr midi_desc_element_t build_element(uint8_t bElementID, uint8_t bNrInputPins, uint8_t baSourceID, uint8_t baSourcePin, uint8_t bNrOutputPins, uint8_t bInTerminalLink, uint8_t bOutTerminalLink, uint8_t bElCapsSize, uint16_t bmElementCaps, uint8_t iElement) | |
{ | |
return build_element({ | |
.bElementID = bElementID, | |
.bNrInputPins = bNrInputPins, | |
.baSourceID = baSourceID, | |
.baSourcePin = baSourcePin, | |
.bNrOutputPins = bNrOutputPins, | |
.bInTerminalLink = bInTerminalLink, | |
.bOutTerminalLink = bOutTerminalLink, | |
.bElCapsSize = bElCapsSize, | |
.bmElementCaps = bmElementCaps, | |
.iElement = iElement, | |
}); | |
} | |
/// MIDI Class-Specific MS Bulk Data Endpoint Descriptor (6.2.2) | |
template <size_t N> | |
struct TU_ATTR_PACKED midi_desc_cs_endpoint_t | |
{ | |
uint8_t bLength ; | |
uint8_t bDescriptorType ; | |
uint8_t bDescriptorSubType ; | |
uint8_t bNumEmbMIDIJack; | |
[[no_unique_address]] elidable_array<uint8_t, N> baAssocJackID; | |
}; | |
template <size_t N> | |
constexpr midi_desc_cs_endpoint_t<N> build_midi_endpoint(std::array<uint8_t, N> assocJackIDs) | |
{ | |
midi_desc_cs_endpoint_t<N> header; | |
if constexpr(N != 0) | |
{ | |
header.baAssocJackID = assocJackIDs; | |
} | |
header.bLength = sizeof(header); | |
header.bDescriptorType = TUSB_DESC_CS_ENDPOINT; | |
header.bDescriptorSubType = MIDI_CS_ENDPOINT_GENERAL; | |
header.bNumEmbMIDIJack= static_cast<uint8_t>(N); | |
return header; | |
} | |
} // midi | |
// TODO: this struct has children! | |
/// AUDIO Class-Specific AC Interface Header Descriptor (4.3.2) | |
template <size_t N> | |
struct TU_ATTR_PACKED audio_desc_cs_ac_header_t | |
{ | |
uint8_t bLength ; ///< Size of this descriptor in bytes: 9. | |
uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. | |
uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_HEADER. | |
uint16_t bcdADC ; ///< Audio Device Class Specification Release Number in Binary-Coded Decimal. Value: U16_TO_U8S_LE(0x0100). | |
uint16_t wTotalLength ; ///< Total number of bytes returned for the class-specific AudioControl interface descriptor. Includes the combined length of this descriptor header and all Clock Source, Unit and Terminal descriptors. | |
uint8_t bInCollection; | |
[[no_unique_address]] elidable_array<uint8_t, N> baInterfaceNr; | |
}; | |
template <size_t N> | |
constexpr audio_desc_cs_ac_header_t<N> build_header(uint16_t bcdADC, std::array<uint8_t, N> baInterfaces) | |
{ | |
audio_desc_cs_ac_header_t<N> header; | |
if constexpr(N != 0) | |
{ | |
header.baInterfaceNr = baInterfaces; | |
} | |
header.bcdADC = bcdADC; | |
header.bLength = sizeof(header); | |
header.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
header.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_HEADER; | |
header.bInCollection= static_cast<uint8_t>(N); | |
// TODO: this should inclue children! | |
header.wTotalLength = sizeof(header); | |
return header; | |
} | |
constexpr audio_desc_cs_ac_header_t<1> build_header(uint16_t bcdADC, interface_ref oneInterf) | |
{ | |
return build_header(bcdADC, std::array{oneInterf.pack()}); | |
} | |
constexpr audio_desc_input_terminal_t build_input_terminal(audio_desc_input_terminal_t input_terminal) | |
{ | |
input_terminal.bLength = sizeof(input_terminal); | |
input_terminal.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
input_terminal.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL; | |
return input_terminal; | |
} | |
constexpr audio_desc_input_terminal_t build_input_terminal(uint16_t wTerminalType, uint8_t bAssocTerminal, uint8_t bCSourceID, uint8_t bNrChannels, uint32_t bmChannelConfig, uint16_t bmControls, uint8_t iTerminal) | |
{ | |
return build_input_terminal({ | |
.wTerminalType = wTerminalType, | |
.bAssocTerminal = bAssocTerminal, | |
.bCSourceID = bCSourceID, | |
.bNrChannels = bNrChannels, | |
.bmChannelConfig = bmChannelConfig, | |
.bmControls = bmControls, | |
.iTerminal = iTerminal, | |
}); | |
} | |
constexpr audio_desc_output_terminal_t build_output_terminal(audio_desc_output_terminal_t output_terminal) | |
{ | |
output_terminal.bLength = sizeof(output_terminal); | |
output_terminal.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
output_terminal.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL; | |
return output_terminal; | |
} | |
constexpr audio_desc_output_terminal_t build_output_terminal(uint8_t bTerminalID, uint16_t wTerminalType, uint8_t bAssocTerminal, uint8_t bSourceID, uint8_t bCSourceID, uint16_t bmControls, uint8_t iTerminal) | |
{ | |
return build_output_terminal({ | |
.bTerminalID = bTerminalID, | |
.wTerminalType = wTerminalType, | |
.bAssocTerminal = bAssocTerminal, | |
.bSourceID = bSourceID, | |
.bCSourceID = bCSourceID, | |
.bmControls = bmControls, | |
.iTerminal = iTerminal, | |
}); | |
} | |
constexpr audio_desc_feature_unit_t build_feature_unit(audio_desc_feature_unit_t feature_unit) | |
{ | |
feature_unit.bLength = sizeof(feature_unit); | |
feature_unit.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
feature_unit.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_FEATURE_UNIT; | |
return feature_unit; | |
} | |
constexpr audio_desc_feature_unit_t build_feature_unit(uint8_t bUnitID, uint8_t bSourceID, uint32_t controls, uint8_t iTerminal) | |
{ | |
return build_feature_unit({ | |
.bUnitID = bUnitID, | |
.bSourceID = bSourceID, | |
.controls = controls, | |
.iTerminal = iTerminal, | |
}); | |
} | |
constexpr audio_desc_clock_source_t build_clock_source(audio_desc_clock_source_t clock_source) | |
{ | |
clock_source.bLength = sizeof(clock_source); | |
clock_source.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
clock_source.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE; | |
return clock_source; | |
} | |
constexpr audio_desc_clock_source_t build_clock_source(uint8_t bClockID, uint8_t bmAttributes, uint8_t bmControls, uint8_t bAssocTerminal, uint8_t iClockSource) | |
{ | |
return build_clock_source({ | |
.bClockID = bClockID, | |
.bmAttributes = bmAttributes, | |
.bmControls = bmControls, | |
.bAssocTerminal = bAssocTerminal, | |
.iClockSource = iClockSource, | |
}); | |
} | |
constexpr audio_desc_clock_selector_t build_clock_selector(audio_desc_clock_selector_t clock_selector) | |
{ | |
clock_selector.bLength = sizeof(clock_selector); | |
clock_selector.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
clock_selector.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR; | |
return clock_selector; | |
} | |
constexpr audio_desc_clock_selector_t build_clock_selector(uint8_t bClockID, uint8_t bNrInPins, uint8_t baCSourceID, uint8_t bmControls, uint8_t iClockSource) | |
{ | |
return build_clock_selector({ | |
.bClockID = bClockID, | |
.bNrInPins = bNrInPins, | |
.baCSourceID = baCSourceID, | |
.bmControls = bmControls, | |
.iClockSource = iClockSource, | |
}); | |
} | |
constexpr audio_desc_clock_multiplier_t build_clock_multiplier(audio_desc_clock_multiplier_t clock_multiplier) | |
{ | |
clock_multiplier.bLength = sizeof(clock_multiplier); | |
clock_multiplier.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
clock_multiplier.bDescriptorSubType = AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER; | |
return clock_multiplier; | |
} | |
constexpr audio_desc_clock_multiplier_t build_clock_multiplier(uint8_t bClockID, uint8_t bCSourceID, uint8_t bmControls, uint8_t iClockSource) | |
{ | |
return build_clock_multiplier({ | |
.bClockID = bClockID, | |
.bCSourceID = bCSourceID, | |
.bmControls = bmControls, | |
.iClockSource = iClockSource, | |
}); | |
} | |
} | |
namespace cdc { | |
GTU_TUSB_Tie_CS(header, cdc_desc_func_header_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER) | |
GTU_TUSB_Tie_CS(call_management, cdc_desc_func_call_management_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_CALL_MANAGEMENT) | |
GTU_TUSB_Tie_CS(acm, cdc_desc_func_acm_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT) | |
GTU_TUSB_Tie_CS(direct_line_management, cdc_desc_func_direct_line_management_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT) | |
GTU_TUSB_Tie_CS(telephone_ringer, cdc_desc_func_telephone_ringer_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_TELEPHONE_RINGER) | |
GTU_TUSB_Tie_CS(telephone_call_state_reporting_capabilities, cdc_desc_func_telephone_call_state_reporting_capabilities_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_TELEPHONE_CALL_AND_LINE_STATE_REPORTING_CAPACITY) | |
GTU_TUSB_Tie_CS(_union, cdc_desc_func_union_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION) | |
GTU_TUSB_Tie_CS(country_selection, cdc_desc_func_country_selection_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_COUNTRY_SELECTION) | |
GTU_TUSB_Tie_CS(telephone_operational_modes, cdc_desc_func_telephone_operational_modes_t, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_TELEPHONE_OPERATIONAL_MODES) | |
constexpr cdc_desc_func_header_t build_header(cdc_desc_func_header_t header) | |
{ | |
header.bLength = sizeof(header); | |
header.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
header.bDescriptorSubType = CDC_FUNC_DESC_HEADER; | |
return header; | |
} | |
constexpr cdc_desc_func_header_t build_header(uint16_t bcdCDC) | |
{ | |
return build_header({ | |
.bcdCDC = bcdCDC, | |
}); | |
} | |
constexpr cdc_desc_func_call_management_t build_call_management(cdc_desc_func_call_management_t call_management) | |
{ | |
call_management.bLength = sizeof(call_management); | |
call_management.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
call_management.bDescriptorSubType = CDC_FUNC_DESC_CALL_MANAGEMENT; | |
return call_management; | |
} | |
constexpr cdc_desc_func_call_management_t build_call_management(uint8_t bmCapabilities, uint8_t bDataInterface) | |
{ | |
return build_call_management({ | |
.bmCapabilities = bmCapabilities, | |
.bDataInterface = bDataInterface, | |
}); | |
} | |
constexpr cdc_desc_func_acm_t build_acm(cdc_desc_func_acm_t acm) | |
{ | |
acm.bLength = sizeof(acm); | |
acm.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
acm.bDescriptorSubType = CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT; | |
return acm; | |
} | |
constexpr cdc_desc_func_acm_t build_acm(cdc_acm_capability_t bmCapabilities) | |
{ | |
return build_acm({ | |
.bmCapabilities = bmCapabilities, | |
}); | |
} | |
constexpr cdc_desc_func_direct_line_management_t build_direct_line_management(cdc_desc_func_direct_line_management_t direct_line_management) | |
{ | |
direct_line_management.bLength = sizeof(direct_line_management); | |
direct_line_management.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
direct_line_management.bDescriptorSubType = CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT; | |
return direct_line_management; | |
} | |
constexpr cdc_desc_func_direct_line_management_t build_direct_line_management(uint8_t bmCapabilities) | |
{ | |
return build_direct_line_management({ | |
.bmCapabilities = bmCapabilities, | |
}); | |
} | |
constexpr cdc_desc_func_telephone_ringer_t build_telephone_ringer(cdc_desc_func_telephone_ringer_t telephone_ringer) | |
{ | |
telephone_ringer.bLength = sizeof(telephone_ringer); | |
telephone_ringer.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
telephone_ringer.bDescriptorSubType = CDC_FUNC_DESC_TELEPHONE_RINGER; | |
return telephone_ringer; | |
} | |
constexpr cdc_desc_func_telephone_ringer_t build_telephone_ringer(uint8_t bRingerVolSteps, uint8_t bNumRingerPatterns) | |
{ | |
return build_telephone_ringer({ | |
.bRingerVolSteps = bRingerVolSteps, | |
.bNumRingerPatterns = bNumRingerPatterns, | |
}); | |
} | |
constexpr cdc_desc_func_telephone_call_state_reporting_capabilities_t build_telephone_call_state_reporting_capabilities(cdc_desc_func_telephone_call_state_reporting_capabilities_t telephone_call_state_reporting_capabilities) | |
{ | |
telephone_call_state_reporting_capabilities.bLength = sizeof(telephone_call_state_reporting_capabilities); | |
telephone_call_state_reporting_capabilities.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
telephone_call_state_reporting_capabilities.bDescriptorSubType = CDC_FUNC_DESC_TELEPHONE_CALL_AND_LINE_STATE_REPORTING_CAPACITY; | |
return telephone_call_state_reporting_capabilities; | |
} | |
constexpr cdc_desc_func_telephone_call_state_reporting_capabilities_t build_telephone_call_state_reporting_capabilities(uint32_t bmCapabilities) | |
{ | |
return build_telephone_call_state_reporting_capabilities({ | |
.bmCapabilities = bmCapabilities, | |
}); | |
} | |
constexpr cdc_desc_func_union_t build_union(cdc_desc_func_union_t _union) | |
{ | |
_union.bLength = sizeof(_union); | |
_union.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
_union.bDescriptorSubType = CDC_FUNC_DESC_UNION; | |
return _union; | |
} | |
constexpr cdc_desc_func_union_t build_union(uint8_t bControlInterface, uint8_t bSubordinateInterface) | |
{ | |
return build_union({ | |
.bControlInterface = bControlInterface, | |
.bSubordinateInterface = bSubordinateInterface, | |
}); | |
} | |
constexpr cdc_desc_func_country_selection_t build_country_selection(cdc_desc_func_country_selection_t country_selection) | |
{ | |
country_selection.bLength = sizeof(country_selection); | |
country_selection.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
country_selection.bDescriptorSubType = CDC_FUNC_DESC_COUNTRY_SELECTION; | |
return country_selection; | |
} | |
constexpr cdc_desc_func_country_selection_t build_country_selection(uint8_t iCountryCodeRelDate, uint16_t wCountryCode) | |
{ | |
return build_country_selection({ | |
.iCountryCodeRelDate = iCountryCodeRelDate, | |
.wCountryCode = wCountryCode, | |
}); | |
} | |
constexpr cdc_desc_func_telephone_operational_modes_t build_telephone_operational_modes(cdc_desc_func_telephone_operational_modes_t telephone_operational_modes) | |
{ | |
telephone_operational_modes.bLength = sizeof(telephone_operational_modes); | |
telephone_operational_modes.bDescriptorType = TUSB_DESC_CS_INTERFACE; | |
telephone_operational_modes.bDescriptorSubType = CDC_FUNC_DESC_TELEPHONE_OPERATIONAL_MODES; | |
return telephone_operational_modes; | |
} | |
constexpr cdc_desc_func_telephone_operational_modes_t build_telephone_operational_modes(uint8_t bmCapabilities) | |
{ | |
return build_telephone_operational_modes({ | |
.bmCapabilities = bmCapabilities, | |
}); | |
} | |
} | |
constexpr tusb_desc_interface_assoc_t build_interface_assoc(tusb_desc_interface_assoc_t interface_assoc) | |
{ | |
interface_assoc.bLength = sizeof(interface_assoc); | |
interface_assoc.bDescriptorType = TUSB_DESC_INTERFACE_ASSOCIATION; | |
return interface_assoc; | |
} | |
constexpr tusb_desc_interface_assoc_t build_interface_assoc(uint8_t bInterfaceCount, uint8_t bFunctionClass, uint8_t bFunctionSubClass, uint8_t bFunctionProtocol, uint8_t iFunction) | |
{ | |
return build_interface_assoc({ | |
.bInterfaceCount = bInterfaceCount, | |
.bFunctionClass = bFunctionClass, | |
.bFunctionSubClass = bFunctionSubClass, | |
.bFunctionProtocol = bFunctionProtocol, | |
.iFunction = iFunction, | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
file = File.read(ARGV[0]).gsub(/^\s*#define\s+USB_STRING.*$/,"") | |
known_values = {} | |
# TODO: Doesn't allow parens | |
proc1 = file.split(/(USB_STRING\([^\)]+)\)/).map do |seg| | |
if seg =~ /USB_STRING\((.*)/ | |
if known_values[$1] | |
known_values[$1] | |
else | |
known_values[$1] = known_values.size+1 | |
end.to_s + " /* #{$1} */" | |
else | |
seg | |
end | |
end.join("") | |
lmax = known_values.keys.map(&:size).max | |
proc2 = proc1.sub(/USB_STRING_MAX\(([^;]+)\);/, "(\\1 > #{lmax} ? \\1 : #{lmax}) + 1;"). | |
sub("USB_STRING_TABLE()", "static const char *const usbd_desc_str[] = {\n\tnullptr,\n\t#{known_values.sort_by{|a,b| b}.map{|x,i|"/*[#{i}]=*/#{x}"}.join(",\n\t") }\n};") | |
if ARGV.size == 2 | |
File.write(ARGV[1], proc2) | |
else | |
puts proc2 | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'cast' | |
file = ARGF.read | |
cc = C::Parser.new.parse(File.read('build2/simple.c').gsub(/typedef _([\w\s]+)va_list;/,"").gsub('__asm__ ("" "__xpg_strerror_r")',"").gsub(/__asm[^;]+;/, '').gsub(/\({[^}]+}\);?/,'').tap{|s|File.write("/tmp/mm.c", s)}) | |
files = [] | |
file.scan(/^GTU_import\("(.*)"\)/) do |im_file| | |
files << im_file | |
end | |
structs = cc.entities.select {|n| n.Declaration? and n.type.Struct? or next } | |
outputs = "" | |
file.scan(/^GTU_TUSB_Tie\((\w+),\s*(\w+),\s*(\w+)\)/) do |name, struct, tclass| | |
# [["type", "name"], ...] | |
ipair = structs.filter{|n| n.declarators.any?{|d|d.name == struct} }.map{|n| | |
n.type.members.map{|m| [(m.type.Struct? ? m.type.members.first.type.name : m.type.name), m.declarators.first.name]} | |
}.first | |
p struct | |
p ipair | |
cname = name.sub(/^_/,'') | |
outputs << <<~EOL | |
constexpr #{struct} build_#{cname}(#{struct} #{name}) | |
{ | |
#{name}.bLength = sizeof(#{name}); | |
#{name}.bDescriptorType = #{tclass}; | |
return #{name}; | |
} | |
constexpr #{struct} build_#{cname}(#{ipair[3..-1].map{|x|x.join(" ")}.join(", ")}) | |
{ | |
return build_#{cname}({ | |
#{ipair[3..-1].map {|type, field| | |
" .#{field} = #{field}," | |
}.join("\n")} | |
}); | |
} | |
EOL | |
end | |
file.scan(/^GTU_TUSB_Tie_CS\((\w+),\s*(\w+),\s*(\w+),\s*(\w+)\)/) do |name, struct, tclass, subclass| | |
# [["type", "name"], ...] | |
ipair = structs.filter{|n| n.declarators.any?{|d|d.name == struct} }.map{|n| | |
n.type.members.map{|m| [(m.type.Struct? ? m.type.members.first.type.name : m.type.name), m.declarators.first.name]} | |
}.first | |
p struct | |
p ipair | |
cname = name.sub(/^_/,'') | |
outputs << <<~EOL | |
constexpr #{struct} build_#{cname}(#{struct} #{name}) | |
{ | |
#{name}.bLength = sizeof(#{name}); | |
#{name}.bDescriptorType = #{tclass}; | |
#{name}.bDescriptorSubType = #{subclass}; | |
return #{name}; | |
} | |
constexpr #{struct} build_#{cname}(#{ipair[3..-1].map{|x|x.join(" ")}.join(", ")}) | |
{ | |
return build_#{cname}({ | |
#{ipair[3..-1].map {|type, field| | |
" .#{field} = #{field}," | |
}.join("\n")} | |
}); | |
} | |
EOL | |
end | |
puts outputs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "tusb.h" | |
#include "pico/usb_reset_interface.h" | |
#include "pico/unique_id.h" | |
#include "descriptor_builder.hpp" | |
static constexpr uint16_t USBD_VID = 0x2E8A; // Raspberry Pi | |
static constexpr uint16_t USBD_PID = 0x000a; // Raspberry Pi Pico SDK CDC | |
static constexpr uint8_t USBD_MAX_POWER_MA = 250; //TODO: _mA custom user type? | |
// mutable metaprogramming with C++20 | |
static constexpr auto serial_control = gtu::interface_counter::alloc<>; | |
static constexpr auto serial_data = gtu::interface_counter::alloc<>; | |
static constexpr auto rpi_reset = gtu::interface_counter::alloc<>; | |
static constexpr auto midi_control = gtu::interface_counter::alloc<>; | |
static constexpr auto midi_data = gtu::interface_counter::alloc<>; | |
#define USBD_CDC_CMD_MAX_SIZE (8) | |
#define USBD_CDC_IN_OUT_MAX_SIZE (64) | |
#define MIDI_BUFFERSIZE 64 | |
static constexpr uint8_t cdc_epcmd = 1; | |
static constexpr uint8_t cdc_ep = 2; | |
static constexpr uint8_t midi_ep = 3; | |
/* | |
// Note: descriptors returned from callbacks must exist long enough for transfer to complete | |
*/ | |
// TODO! | |
#define USB_STRING(abc) 0 | |
consteval auto build_midi( | |
gtu::endpoint_addr epout, gtu::endpoint_addr epin, uint16_t epsize, | |
uint8_t string_index_todo, | |
std::array<gtu::interface_ref, 2> interfaces) | |
{ | |
// Audio Control (AC) Interface | |
auto first = gtu::busb_interface(interfaces[0], { | |
.bAlternateSetting = 0, | |
.bInterfaceClass = TUSB_CLASS_AUDIO, | |
.bInterfaceSubClass = AUDIO_SUBCLASS_CONTROL, | |
.bInterfaceProtocol = AUDIO_FUNC_PROTOCOL_CODE_UNDEF, | |
.iInterface = string_index_todo // TODO: string! | |
}, gtu::store_tuple( | |
// AC Header | |
gtu::audio::build_header(0x0100, interfaces[1]) | |
)); | |
uint8_t id_emb_in = 1; | |
uint8_t id_ext_in = 2; | |
uint8_t id_emb_out = 3; | |
uint8_t id_ext_out = 4; | |
// MIDI Streaming (MS) Interface | |
auto second = gtu::busb_interface(interfaces[1], { | |
.bAlternateSetting = 0, | |
.bInterfaceClass = TUSB_CLASS_AUDIO, | |
.bInterfaceSubClass = AUDIO_SUBCLASS_MIDI_STREAMING, | |
.bInterfaceProtocol = AUDIO_FUNC_PROTOCOL_CODE_UNDEF, | |
.iInterface = 0 // TODO: string! | |
}, gtu::store_tuple( | |
// MS Header | |
gtu::audio::midi::build_header(0x0100, | |
// TODO: cables! | |
gtu::audio::midi::build_in_jack(MIDI_JACK_EMBEDDED, id_emb_in /* computed?*/, 0 /*str*/), | |
gtu::audio::midi::build_in_jack(MIDI_JACK_EXTERNAL, id_ext_in /* computed?*/, 0 /*str*/), | |
gtu::audio::midi::build_out_jack(MIDI_JACK_EMBEDDED, id_emb_out, 1, id_ext_in, 1, 0 /*str*/), | |
gtu::audio::midi::build_out_jack(MIDI_JACK_EXTERNAL, id_ext_out, 1, id_emb_in, 1, 0 /*str*/) | |
) | |
),std::array // interface endpoints | |
{ | |
gtu::store_tuple( | |
gtu::audio::build_endpoint(epout, TUSB_XFER_BULK, epsize, 0),// Endpoint Out | |
gtu::audio::midi::build_midi_endpoint(std::array{id_emb_in}) // MS Endpoint (connected to embedded jack) | |
), | |
gtu::store_tuple( | |
gtu::audio::build_endpoint(epin, TUSB_XFER_BULK, epsize, 0),// Endpoint In | |
gtu::audio::midi::build_midi_endpoint(std::array{id_emb_out}) // MS Endpoint (connected to embedded jack) | |
) | |
}); | |
return gtu::busb_interface_tuple(first, second); | |
} | |
consteval auto build_cdc( | |
gtu::endpoint_addr epout, gtu::endpoint_addr epin, uint16_t epsize, | |
gtu::endpoint_addr ep_notif, uint16_t ep_notif_size, | |
std::array<gtu::interface_ref, 2> interfaces) | |
{ | |
// Interface Association | |
return gtu::busb_interface_assoc({ | |
.bFunctionClass = TUSB_CLASS_CDC, | |
.bFunctionSubClass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL, | |
.bFunctionProtocol = CDC_COMM_PROTOCOL_NONE, | |
.iFunction = 0, // TODO: string! | |
}, gtu::store_tuple( | |
// CDC Control Interface | |
gtu::busb_interface(interfaces[0], { | |
.bAlternateSetting = 0, | |
.bInterfaceClass = TUSB_CLASS_CDC, | |
.bInterfaceSubClass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL, | |
.bInterfaceProtocol = CDC_COMM_PROTOCOL_NONE, | |
.iInterface = USB_STRING("Board CDC") // TODO: string! | |
}, | |
// the next things are all ON the interface, ie they have the interface as the parent | |
gtu::store_tuple( | |
gtu::cdc::build_header(0x0120), // CDC Header | |
gtu::cdc::build_call_management(0, interfaces[1].pack()), // CDC Call | |
gtu::cdc::build_acm({ // CDC ACM: support line request | |
.support_line_request = true | |
}), | |
gtu::cdc::build_union({//CDC Union | |
.bControlInterface = interfaces[0].pack(), | |
.bSubordinateInterface = interfaces[1].pack(), | |
}) | |
), | |
std::array // interface endpoints | |
{ | |
// Endpoint Notification | |
gtu::build_endpoint(ep_notif, TUSB_XFER_INTERRUPT, ep_notif_size, 16), | |
} | |
), | |
//CDC Data Interface | |
gtu::busb_interface(interfaces[1], { | |
.bAlternateSetting = 0, | |
.bInterfaceClass = TUSB_CLASS_CDC_DATA, | |
.bInterfaceSubClass = 0, | |
.bInterfaceProtocol = 0, | |
.iInterface = 0, // TODO: string! | |
}, | |
std::array // interface endpoints | |
{ | |
gtu::build_endpoint(epout, TUSB_XFER_BULK, epsize, 0)// Endpoint Out | |
,gtu::build_endpoint(epin, TUSB_XFER_BULK, epsize, 0) //Endpoint In | |
} | |
) | |
)); | |
} | |
consteval auto build_reseter(gtu::interface_ref interface) | |
{ | |
return gtu::busb_interface(interface, { | |
.bAlternateSetting = 0, | |
.bInterfaceClass = TUSB_CLASS_VENDOR_SPECIFIC, | |
.bInterfaceSubClass = RESET_INTERFACE_SUBCLASS, | |
.bInterfaceProtocol = RESET_INTERFACE_PROTOCOL, | |
.iInterface = USB_STRING("Reset") // TODO: string!, | |
}); | |
} | |
struct unique_board_id_hex_str | |
{ | |
constexpr static size_t size = PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1; | |
char c_str[size]; | |
unique_board_id_hex_str() | |
{ | |
pico_get_unique_board_id_string(c_str, sizeof(c_str)); | |
} | |
}; | |
const unique_board_id_hex_str usbd_serial_str{}; | |
#define USB_STRING_TABLE() static const char *const usbd_desc_str[] = {"Template Failure"}; | |
#define USB_STRING_MAX(n) n | |
constexpr uint8_t DESC_STR_MAX = USB_STRING_MAX(unique_board_id_hex_str::size); | |
USB_STRING_TABLE() | |
constexpr auto usbs = gtu::build_device({ | |
.bcdUSB = 0x0200, | |
.bDeviceClass = TUSB_CLASS_MISC, | |
.bDeviceSubClass = MISC_SUBCLASS_COMMON, | |
.bDeviceProtocol = MISC_PROTOCOL_IAD, | |
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, | |
.idVendor = USBD_VID, | |
.idProduct = USBD_PID, | |
.bcdDevice = 0x0100, | |
.iManufacturer = USB_STRING("Raspberry Pi"), | |
.iProduct = USB_STRING("Pico"), | |
.iSerialNumber = USB_STRING(usbd_serial_str.c_str), | |
}, gtu::build_configuration( | |
0, USBD_MAX_POWER_MA, | |
build_cdc(gtu::ep_out<cdc_ep>(), gtu::ep_in<cdc_ep>(), | |
USBD_CDC_IN_OUT_MAX_SIZE, | |
gtu::ep_in<cdc_epcmd>(), USBD_CDC_CMD_MAX_SIZE, | |
std::array{ | |
serial_control, serial_data | |
}), | |
build_reseter(rpi_reset), | |
build_midi(gtu::ep_out<midi_ep>(), gtu::ep_in<midi_ep>(), | |
MIDI_BUFFERSIZE, | |
USB_STRING("Trivial MIDI"), | |
std::array{ | |
midi_control, midi_data | |
}) | |
)); | |
constexpr auto tmpz=build_cdc(gtu::ep_out<0>(), gtu::ep_in<0>(), | |
USBD_CDC_IN_OUT_MAX_SIZE, | |
gtu::ep_in<0>(), USBD_CDC_CMD_MAX_SIZE, | |
std::array{ | |
serial_control, serial_data | |
}); | |
static_assert(sizeof(tmpz) == 66); | |
static_assert(sizeof(tmpz.core) == 8); | |
static_assert(sizeof(tmpz.interfaces) == 58); | |
static_assert(sizeof(tmpz.interfaces.first) == 35); | |
static_assert(sizeof(tmpz.interfaces.rest) == 23); | |
static_assert(usbs.first.bNumConfigurations == 1); | |
// TODO: probably could use a union template | |
const uint8_t *tud_descriptor_device_cb() { | |
// TODO: get only by type? | |
return gtu::stored_extract<0, tusb_desc_device_t>(usbs); | |
} | |
const uint8_t *tud_descriptor_configuration_cb(__unused uint8_t index) { | |
return gtu::stored_extract<1, tusb_desc_configuration_t>(usbs); | |
} | |
const uint16_t *tud_descriptor_string_cb(uint8_t index, __unused uint16_t langid) { | |
static uint16_t desc_str[DESC_STR_MAX]; | |
uint8_t len; | |
if (index == 0) { | |
desc_str[1] = 0x0409; // supported language is English | |
len = 1; | |
} else { | |
if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0])) { | |
return NULL; | |
} | |
const char *str = usbd_desc_str[index]; | |
for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len) { | |
desc_str[1 + len] = str[len]; | |
} | |
} | |
// first byte is length (including header), second byte is string type | |
desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * len + 2)); | |
return desc_str; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment