From b4a92803f6c0e09a09c11f18c5b000221225f62d Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sun, 29 Mar 2026 23:57:53 -0700 Subject: [PATCH 1/6] :see_no_evil: Added claude code's .claude folder to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 38500761..f64ff616 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,6 @@ conanbuildinfo.txt # CMake CMakeUserPresets.json + +# Claude Code +.claude/ From 86d4c358cf069ca475a123cae9b607013a334d20 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Fri, 12 Sep 2025 17:02:36 -0700 Subject: [PATCH 2/6] Enumerator works! Refactoring to util now --- demos/CMakeLists.txt | 1 + demos/applications/blinker.cpp | 7 +- demos/applications/descriptors.hpp | 265 +++++++++ demos/applications/enumerator.hpp | 572 +++++++++++++++++++ demos/applications/outputtxt.txt | 0 demos/applications/usb_interface_testing.cpp | 250 ++++++++ demos/applications/utils.hpp | 128 +++++ demos/platforms/stm32f103c8.cpp | 2 +- 8 files changed, 1222 insertions(+), 3 deletions(-) create mode 100644 demos/applications/descriptors.hpp create mode 100644 demos/applications/enumerator.hpp create mode 100644 demos/applications/outputtxt.txt create mode 100644 demos/applications/usb_interface_testing.cpp create mode 100644 demos/applications/utils.hpp diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 734634d5..e7b50a18 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -31,6 +31,7 @@ libhal_build_demos( spi blank watchdog + usb_interface_testing # stream_dac timer usb_cdc_raw diff --git a/demos/applications/blinker.cpp b/demos/applications/blinker.cpp index 24d95d24..5c21a2aa 100644 --- a/demos/applications/blinker.cpp +++ b/demos/applications/blinker.cpp @@ -21,12 +21,15 @@ void application() { auto clock = resources::clock(); auto led = resources::status_led(); + auto console = resources::console(); while (true) { using namespace std::chrono_literals; led->level(false); - hal::delay(*clock, 500ms); + hal::print(*console, "CLAP ON!!!\n"); + hal::delay(*clock, 1000ms); led->level(true); - hal::delay(*clock, 500ms); + hal::print(*console, "CLAP OFF!!!\n"); + hal::delay(*clock, 1000ms); } } diff --git a/demos/applications/descriptors.hpp b/demos/applications/descriptors.hpp new file mode 100644 index 00000000..822755ef --- /dev/null +++ b/demos/applications/descriptors.hpp @@ -0,0 +1,265 @@ +#pragma once + +/* TODO: + Class, subclass, proto validator + Device qualifer descriptor (happens between device and config) + Other speed descriptor (happens with configuration) +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libhal-util/as_bytes.hpp" +#include "libhal/units.hpp" +#include "utils.hpp" +#include +#include + +namespace hal::v5::usb { + +struct device +{ + template + friend class enumerator; + + struct device_arguments + { + u16 p_bcd_usb; + class_code p_device_class; + u8 p_device_subclass; + u8 p_device_protocol; + u16 p_id_vendor; + u16 p_id_product; + u16 p_bcd_device; + std::u16string_view p_manufacturer; + std::u16string_view p_product; + std::u16string_view p_serial_number_str; + }; + + constexpr device(device_arguments&& args) + : manufacturer_str(args.p_manufacturer) + , product_str(args.p_product) + , serial_number_str(args.p_serial_number_str) + { + u8 idx = 0; + auto bcd_usb_bytes = hal::as_bytes(&args.p_bcd_usb, 1); + for (auto& bcd_usb_byte : bcd_usb_bytes) { + m_packed_arr[idx++] = bcd_usb_byte; // 0, 1 + } + + m_packed_arr[idx++] = static_cast(args.p_device_class); // 2 + m_packed_arr[idx++] = args.p_device_subclass; // 3 + m_packed_arr[idx++] = args.p_device_protocol; // 4 + + m_packed_arr[idx++] = 0; // 5 Max Packet length handled by the enumerator + + auto id_vendor_bytes = hal::as_bytes(&args.p_id_vendor, 1); + for (auto& id_vendor_byte : id_vendor_bytes) { + m_packed_arr[idx++] = id_vendor_byte; // 6, 7 + } + + auto id_product_bytes = hal::as_bytes(&args.p_id_product, 1); + for (auto& id_product_byte : id_product_bytes) { + m_packed_arr[idx++] = id_product_byte; // 8, 9 + } + + auto bcd_device_bytes = hal::as_bytes(&args.p_bcd_device, 1); + for (auto& bcd_device_byte : bcd_device_bytes) { + m_packed_arr[idx++] = bcd_device_byte; // 10, 11 + } + + // Evaluated during enumeration + m_packed_arr[idx++] = 0; // 12 string idx of manufacturer + m_packed_arr[idx++] = 0; // 13 string idx of product + m_packed_arr[idx++] = 0; // 14 string idx of serial number + m_packed_arr[idx++] = 0; // 15 Number of possible configurations + }; + + u16& bcd_usb() + { + return *reinterpret_cast(&m_packed_arr[0]); + } + + constexpr u8& device_class() + { + return m_packed_arr[2]; + } + + constexpr u8& device_sub_class() + { + return m_packed_arr[3]; + } + + constexpr u8& device_protocol() + { + return m_packed_arr[4]; + } + + u16& id_vendor() + { + return *reinterpret_cast(&m_packed_arr[6]); + } + + u16& id_product() + { + return *reinterpret_cast(&m_packed_arr[8]); + } + + u16& bcd_device() + { + return *reinterpret_cast(&m_packed_arr[10]); + } + + operator std::span() const + { + return m_packed_arr; + } + + std::u16string_view manufacturer_str; + std::u16string_view product_str; + std::u16string_view serial_number_str; + +private: + constexpr u8& max_packet_size() + { + return m_packed_arr[5]; + } + + constexpr u8& manufacturer_index() + { + return m_packed_arr[12]; + } + constexpr u8& product_index() + { + return m_packed_arr[13]; + } + constexpr u8& serial_number_index() + { + return m_packed_arr[14]; + } + constexpr u8& num_configurations() + { + return m_packed_arr[15]; + } + + std::array m_packed_arr; +}; + +// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors + +template +concept usb_interface_concept = std::derived_from; + +// Calculate: total length, number of interfaces, configuration value +struct configuration +{ + + template + friend class enumerator; + + struct bitmap + { + + constexpr bitmap(u8 p_bitmap) + : m_bitmap(p_bitmap) + { + } + + constexpr bitmap(bool p_self_powered, bool p_remote_wakeup) + { + m_bitmap = (1 << 7) | (p_self_powered << 6) | (p_remote_wakeup << 5); + } + + constexpr u8 to_byte() + { + return m_bitmap; + } + + [[nodiscard]] constexpr bool self_powered() const + { + return static_cast(m_bitmap & 1 << 6); + } + + [[nodiscard]] constexpr bool remote_wakeup() const + { + return static_cast(m_bitmap & 1 << 5); + } + + private: + u8 m_bitmap; + }; + + template + constexpr configuration(std::u16string_view p_name, + bitmap&& p_attributes, + u8&& p_max_power, + std::pmr::polymorphic_allocator<> p_allocator, + strong_ptr... p_interfaces) + : name(p_name) + , interfaces(p_allocator) + { + interfaces.reserve(sizeof...(p_interfaces)); + (interfaces.push_back(p_interfaces), ...); + u8 idx = 0; + + // Anything marked with 0 is to be populated at enumeration time + m_packed_arr[idx++] = 0; // 0 Total Length + m_packed_arr[idx++] = 0; + m_packed_arr[idx++] = interfaces.size(); // 2 number of interfaces + m_packed_arr[idx++] = 0; // 3 Config number + m_packed_arr[idx++] = 0; // 4 Configuration name string index + + m_packed_arr[idx++] = p_attributes.to_byte(); // 5 + m_packed_arr[idx++] = p_max_power; // 6 + } + + operator std::span() const + { + return m_packed_arr; + } + + constexpr bitmap attributes() + { + return { m_packed_arr[5] }; + } + constexpr u8& attributes_byte() + { + return m_packed_arr[5]; + } + + constexpr u8& max_power() + { + return m_packed_arr[6]; + } + + std::u16string_view name; + std::pmr::vector> interfaces; + + // private: + u16& total_length() + { + return *reinterpret_cast(&m_packed_arr[0]); + } + constexpr u8& num_interfaces() + { + return m_packed_arr[2]; + } + constexpr u8& configuration_value() + { + return m_packed_arr[3]; + } + constexpr u8& configuration_index() + { + return m_packed_arr[4]; + } + + std::array m_packed_arr; +}; +} // namespace hal::v5::usb diff --git a/demos/applications/enumerator.hpp b/demos/applications/enumerator.hpp new file mode 100644 index 00000000..2aa0ad7c --- /dev/null +++ b/demos/applications/enumerator.hpp @@ -0,0 +1,572 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "descriptors.hpp" +#include "utils.hpp" + +// TODO: move to util +namespace hal::v5::usb { + +void print_payload(strong_ptr const& p_console, + scatter_span p_payload) +{ + hal::print(*p_console, "Payload: [ "); + for (auto const& s : p_payload) { + for (unsigned char el : s) { + hal::print<10>(*p_console, "0x%X, ", el); + } + } + hal::print(*p_console, "]\n"); +} + +template +constexpr size_t scatter_span_size(scatter_span ss) +{ + size_t res = 0; + for (auto const& s : ss) { + res += s.size(); + } + + return res; +} + +template +constexpr std::pair, sizeof...(Args)>, size_t> +make_sub_scatter_array(size_t p_count, Args&&... p_spans) +{ + std::array, sizeof...(Args)> full_ss{ std::span( + std::forward(p_spans))... }; + + auto res_len = sizeof...(p_spans); + (void)res_len; + size_t total_span_len = scatter_span_size(scatter_span(full_ss)); + std::array, sizeof...(Args)> res; + std::array lens{ std::span(p_spans).size()... }; + + if (total_span_len <= p_count) { + return std::make_pair(full_ss, full_ss.size()); + } + size_t cur_len = 0; + size_t i = 0; + for (; i < lens.size(); i++) { + auto l = lens[i]; + + if (p_count >= (cur_len + l)) { + res[i] = full_ss[i]; + cur_len += l; + continue; + } + + if (cur_len >= p_count) { + return std::make_pair(res, i); + } + + auto delta = p_count - cur_len; + std::span subspan = std::span(full_ss[i]).first(delta); + res[i] = subspan; + break; + } + + return std::make_pair(res, i + 1); +} + +template +constexpr std::pair, sizeof...(Args)>, size_t> +make_sub_scatter_bytes(size_t p_count, Args&&... p_spans) +{ + return make_sub_scatter_array(p_count, + std::forward(p_spans)...); +} + +template +class enumerator +{ + +public: + enumerator(strong_ptr const& p_ctrl_ep, + device const& p_device, + configuration& p_configs, + u16 p_lang_str, + u8&& p_starting_str_idx, + hal::v5::strong_ptr const& p_console, + bool enumerate_immediately = true) + : m_ctrl_ep(p_ctrl_ep) + , m_device(p_device) + , m_configs({ &p_configs }) + , m_lang_str(p_lang_str) + , m_console(p_console) + { + auto s = make_scatter_bytes(p_configs); + print_payload(m_console, s); + // Verify there is space to actually allocate indexes for configuration + // Three string indexes are reserved for the device descriptor, then each + // configuration has a name which reserves a string index strings and + // Index 0 is reserved for the lang string + if (p_starting_str_idx < 1 || p_starting_str_idx > 0xFF - 3 + num_configs) { + safe_throw(hal::argument_out_of_domain(this)); + } + m_starting_str_idx = p_starting_str_idx; + + if (enumerate_immediately) { + enumerate(); + } + } + + void enumerate() + { + // Renumerate, a config will only be set if + if (m_active_conf != nullptr) { + m_active_conf = nullptr; + m_ctrl_ep->connect(false); + } + + auto cur_str_idx = m_starting_str_idx; + byte cur_iface_idx = 0; + // Phase one: Preperation + + // Device + m_device.manufacturer_index() = cur_str_idx++; + m_device.product_index() = cur_str_idx++; + m_device.serial_number_index() = cur_str_idx++; + m_device.num_configurations() = num_configs; + hal::print(*m_console, "Device desc: set up\n"); + + // Configurations + for (size_t i = 0; i < num_configs; i++) { + configuration& config = *m_configs[i]; + config.configuration_index() = cur_str_idx++; + config.configuration_value() = i; + } + hal::print(*m_console, "conf desc: set up\n"); + + for (configuration* config : m_configs) { + auto total_length = static_cast(constants::config_desc_size); + for (auto const& iface : config->interfaces) { + interface::descriptor_count deltas = iface->write_descriptors( + { .interface = cur_iface_idx, .string = cur_str_idx }, + [&total_length](scatter_span p_data) { + total_length += scatter_span_size(p_data); + }); + + cur_iface_idx += deltas.interface; + cur_str_idx += deltas.string; + } + config->total_length() = total_length; + } + hal::print(*m_console, "iface/conf desc: set up\n"); + + // Phase two: Writing + + // TODO: Make async + bool finished_enumeration = false; + bool volatile waiting_for_data = true; + + using on_receive_tag = control_endpoint::on_receive_tag; + hal::print(*m_console, "Begin writing\n"); + + m_ctrl_ep->on_receive( + [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); + m_ctrl_ep->connect(true); + hal::print(*m_console, "Connected\n"); + auto sconf = make_scatter_bytes(*m_configs[0]); + print_payload(m_console, sconf); + std::array raw_req; + do { + // Seriously, make this async + while (waiting_for_data) { + continue; + } + waiting_for_data = true; + + auto scatter_raw_req = make_writable_scatter_bytes(raw_req); + auto num_bytes_read = m_ctrl_ep->read(scatter_raw_req); + + if (num_bytes_read == 0) { + continue; + } + + if (num_bytes_read != constants::size_std_req) { + hal::print<24>(*m_console, "num_bytes_read: %X\n", num_bytes_read); + safe_throw(hal::message_size(num_bytes_read, this)); + } + // hal::print(*m_console, "Raw request: ["); + // for (auto const& el : raw_req) { + // hal::print<8>(*m_console, "0x%X, ", el); + // } + // hal::print(*m_console, "]\n"); + auto req = from_span(raw_req); + hal::print<48>(*m_console, + "\nGot request: [0x%X, 0x%X, 0x%X, 0x%X, 0x%X]\n", + req.request_type, + req.request, + req.value, + req.index, + req.length); + + if (req.get_recipient() != setup_packet::recipient::device) { + safe_throw(hal::not_connected(this)); + } + + // TODO: Handle exception + handle_standard_device_request(req); + // m_ctrl_ep->write({}); // Send ZLP to complete Data Transaction + if (static_cast(req.request) == + standard_request_types::set_configuration) { + finished_enumeration = true; + m_ctrl_ep->on_receive( + [this](on_receive_tag) { m_has_setup_packet = true; }); + } + } while (!finished_enumeration); + } + + [[nodiscard]] configuration& get_active_configuration() + { + if (m_active_conf == nullptr) { + safe_throw(hal::operation_not_permitted(this)); + } + + return *m_active_conf; + } + + void resume_ctrl_transaction() + { + while (!m_has_setup_packet) { + continue; + } + + std::array read_buf; + auto scatter_read_buf = make_writable_scatter_bytes(read_buf); + auto bytes_read = m_ctrl_ep->read(scatter_read_buf); + std::span payload(read_buf.data(), bytes_read); + + setup_packet req = from_span(payload); + if (determine_standard_request(req) == standard_request_types::invalid) { + return; + } + + if (determine_standard_request(req) == + standard_request_types::get_descriptor && + static_cast((req.value & 0xFF << 8) >> 8) == + descriptor_type::string) { + handle_str_descriptors(req.value & 0xFF, req.length > 2); + + } else if (req.get_recipient() == setup_packet::recipient::device) { + handle_standard_device_request(req); + } else { + // Handle iface level requests + auto f = [this](scatter_span resp) { + m_ctrl_ep->write(resp); + }; + bool req_handled = false; + for (auto const& iface : get_active_configuration()) { + req_handled = iface->handle_request( + req.request_type, req.request, req.value, req.index, req.length, f); + if (req_handled) { + break; + } + } + m_ctrl_ep->write( + {}); // A ZLP to terminate Data Transaction just to be safe + + if (!req_handled) { + safe_throw(hal::argument_out_of_domain(this)); + } + } + } + +private: + void handle_standard_device_request(setup_packet& req) + { + + switch (determine_standard_request(req)) { + case standard_request_types::set_address: { + m_ctrl_ep->write({}); + m_ctrl_ep->set_address(req.value); + hal::print<16>(*m_console, "Address set: %x\n", req.value); + break; + } + + case standard_request_types::get_descriptor: { + process_get_descriptor(req); + break; + } + + case standard_request_types::get_configuration: { + if (m_active_conf == nullptr) { + safe_throw(hal::operation_not_permitted(this)); + } + auto scatter_conf = make_scatter_bytes( + std::span(&m_active_conf->configuration_value(), 1)); + m_ctrl_ep->write(scatter_conf); + break; + } + + case standard_request_types::set_configuration: { + m_active_conf = m_configs[req.value]; + break; + } + + case standard_request_types::invalid: + default: + safe_throw(hal::not_connected(this)); + } + } + + void process_get_descriptor(setup_packet& req) + { + hal::byte desc_type = (req.value & 0xFF << 8) >> 8; + [[maybe_unused]] hal::byte desc_idx = req.value & 0xFF; + + switch (static_cast(desc_type)) { + case descriptor_type::device: { + auto header = + std::to_array({ constants::device_desc_size, + static_cast(descriptor_type::device) }); + m_device.max_packet_size() = static_cast(m_ctrl_ep->info().size); + auto scatter_arr_pair = + make_sub_scatter_bytes(req.length, header, m_device); + hal::v5::write_and_flush( + *m_ctrl_ep, + scatter_span(scatter_arr_pair.first) + .first(scatter_arr_pair.second)); + hal::print(*m_console, "Dev desc written\n"); + print_payload(m_console, + scatter_span(scatter_arr_pair.first) + .first(scatter_arr_pair.second)); + break; + } + + case descriptor_type::configuration: { + hal::print<24>(*m_console, "Conf addr: %p\n", m_configs[0]); + configuration& conf = *m_configs[desc_idx]; + auto conf_hdr = + std::to_array({ constants::config_desc_size, + static_cast(descriptor_type::configuration) }); + auto scatter_conf_pair = make_sub_scatter_bytes( + req.length, conf_hdr, static_cast>(conf)); + + m_ctrl_ep->write(scatter_span(scatter_conf_pair.first) + .first(scatter_conf_pair.second)); + hal::print(*m_console, "conf desc header written\n"); + + // Return early if the only thing requested was the config descriptor + print_payload(m_console, + scatter_span(scatter_conf_pair.first) + .first(scatter_conf_pair.second)); + if (req.length <= constants::config_desc_size) { + // print_payload(m_console, + // scatter_span(scatter_conf_pair.first) + // .first(scatter_conf_pair.second)); + m_ctrl_ep->write({}); + return; + } + + u16 total_size = constants::config_desc_size; + for (auto const& iface : conf.interfaces) { + std::ignore = iface->write_descriptors( + { .interface = std::nullopt, .string = std::nullopt }, + [this, &total_size](scatter_span byte_stream) { + hal::v5::write_and_flush(*this->m_ctrl_ep, byte_stream); + total_size += scatter_span_size(byte_stream); + hal::print(*this->m_console, "Iface sent\n"); + print_payload(this->m_console, byte_stream); + }); + } + hal::print(*m_console, "iface descs written\n"); + + // if (total_size != req.length) { + // safe_throw(hal::operation_not_supported( + // this)); // TODO: Make specific exception for this + // } + + break; + } + + case descriptor_type::string: { + if (desc_idx == 0) { + hal::print<32>(*m_console, "Lang str should be: 0x%x\n", m_lang_str); + auto s_hdr = + std::to_array({ static_cast(4), + static_cast(descriptor_type::string) }); + auto lang = setup_packet::to_le_bytes(m_lang_str); + auto scatter_arr_pair = make_scatter_bytes(s_hdr, lang); + // auto p = scatter_span(scatter_arr_pair.first) + // .first(scatter_arr_pair.second); + m_ctrl_ep->write(scatter_arr_pair); + m_ctrl_ep->write({}); + print_payload(m_console, scatter_arr_pair); + hal::print(*m_console, "lang string written\n"); + break; + } + handle_str_descriptors(desc_idx, req.length); // Can throw + break; + } + + // TODO: Interface, endpoint, device_qualifier, interface_power, + // OTHER_SPEED_CONFIGURATION + + default: + safe_throw(hal::operation_not_supported(this)); + } + } + + void handle_str_descriptors(u8 const target_idx, u16 p_len) + { + + u8 cfg_string_end = m_starting_str_idx + 3 + num_configs; + if (target_idx <= cfg_string_end) { + auto r = write_cfg_str_descriptor(target_idx, p_len); + if (!r) { + safe_throw(hal::argument_out_of_domain(this)); + } + m_iface_for_str_desc = std::nullopt; + return; + } + + if (m_iface_for_str_desc.has_value() && + m_iface_for_str_desc->first == target_idx) { + bool success = m_iface_for_str_desc->second->write_string_descriptor( + target_idx, [this](scatter_span desc) { + hal::v5::write_and_flush(*m_ctrl_ep, desc); + }); + if (success) { + return; + hal::print(*m_console, "Wrote cached string\n"); + } + } + + // Iterate through every interface now to find a match + auto f = [this, p_len](scatter_span desc) { + if (p_len > 2) { + hal::v5::write_and_flush(*m_ctrl_ep, desc); + } else { + std::array desc_type{ static_cast( + descriptor_type::string) }; + auto scatter_str_hdr = + make_scatter_bytes(std::span(&desc[0][0], 1), desc_type); + hal::v5::write_and_flush(*m_ctrl_ep, scatter_str_hdr); + } + }; + + if (m_active_conf != nullptr) { + for (auto const& iface : m_active_conf->interfaces) { + auto res = iface->write_string_descriptor(target_idx, f); + if (res) { + return; + } + } + } + + for (configuration* conf : m_configs) { + for (auto const& iface : conf->interfaces) { + auto res = iface->write_string_descriptor(target_idx, f); + if (res) { + break; + } + } + } + hal::print(*m_console, "Wrote iface string\n"); + } + + bool write_cfg_str_descriptor(u8 const target_idx, u16 p_len) + { + constexpr u8 dev_manu_offset = 0; + constexpr u8 dev_prod_offset = 1; + constexpr u8 dev_sn_offset = 2; + std::optional opt_conf_sv; + if (target_idx == (m_starting_str_idx + dev_manu_offset)) { + opt_conf_sv = m_device.manufacturer_str; + + } else if (target_idx == (m_starting_str_idx + dev_prod_offset)) { + opt_conf_sv = m_device.product_str; + + } else if (target_idx == (m_starting_str_idx + dev_sn_offset)) { + opt_conf_sv = m_device.serial_number_str; + + } else { + for (size_t i = 0; i < m_configs.size(); i++) { + configuration* conf = m_configs[i]; + if (target_idx == (m_starting_str_idx + i)) { + opt_conf_sv = conf->name; + } + } + } + + if (opt_conf_sv == std::nullopt) { + return false; + } + + // Acceptable to access without checking because guaranteed to be Some, + // there is no pattern matching in C++ yet so unable to do this cleanly + // (would require a check on every single one) + hal::print<8>(*m_console, "%d: [", target_idx); + auto sv = opt_conf_sv.value(); + std::mbstate_t state{}; + for (wchar_t const wc : sv) { + char tmp[MB_CUR_MAX]; // NOLINT + size_t len = std::wcrtomb(tmp, wc, &state); + if (len == static_cast(-1)) { + hal::print(*m_console, "unable to convert wc\n"); + continue; + } + + for (size_t i = 0; i < len; i++) { + hal::print<8>(*m_console, "%c", tmp[i]); + } + } + hal::print(*m_console, "]\n"); + auto const conf_sv_span = hal::as_bytes(opt_conf_sv.value()); + auto desc_len = static_cast((conf_sv_span.size() + 2)); + hal::print<32>(*m_console, + "%d: (size: 0x%X, psize: 0x%X): [ ", + target_idx, + conf_sv_span.size(), + desc_len); + + for (auto const el : conf_sv_span) { + hal::print<8>(*m_console, "0x%X, ", el); + } + hal::print(*m_console, "]\n"); + hal::print(*m_console, "\n"); + auto hdr_arr = std::to_array( + { desc_len, static_cast(descriptor_type::string) }); + + auto scatter_arr_pair = + make_sub_scatter_bytes(p_len, hdr_arr, conf_sv_span); + + auto p = scatter_span(scatter_arr_pair.first) + .first(scatter_arr_pair.second); + hal::v5::write_and_flush(*m_ctrl_ep, p); + hal::print(*m_console, "Wrote cfg string\n"); + print_payload(m_console, p); + return true; + } + + strong_ptr m_ctrl_ep; + device m_device; + std::array m_configs; + u16 m_lang_str; + u8 m_starting_str_idx; + std::optional>> m_iface_for_str_desc; + configuration* m_active_conf = nullptr; + bool m_has_setup_packet = false; + hal::v5::strong_ptr m_console; +}; +} // namespace hal::v5::usb diff --git a/demos/applications/outputtxt.txt b/demos/applications/outputtxt.txt new file mode 100644 index 00000000..e69de29b diff --git a/demos/applications/usb_interface_testing.cpp b/demos/applications/usb_interface_testing.cpp new file mode 100644 index 00000000..1887c276 --- /dev/null +++ b/demos/applications/usb_interface_testing.cpp @@ -0,0 +1,250 @@ +#include "resource_list.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "enumerator.hpp" + +namespace hal::v5::usb { +class dummy_interface : public interface +{ +public: + constexpr dummy_interface(std::u16string_view p_name) + : m_name(p_name) {}; + +private: + descriptor_count driver_write_descriptors( + descriptor_start p_start, + endpoint_writer const& p_callback) override + { + + if (p_start.interface.has_value()) { + m_packed_array[2] = p_start.interface.value(); + } + + if (p_start.string.has_value()) { + m_packed_array[8] = p_start.string.value(); + } + + auto scatter_arr = + make_scatter_bytes(std::span(m_packed_array)); + + p_callback(scatter_arr); + return { .interface = 1, .string = 1 }; + } + + bool driver_write_string_descriptor( + u8 p_index, + endpoint_writer const& p_callback) override + { + if (p_index != m_packed_array[8]) { + return false; + } + std::array hdr_array( + { static_cast(m_name.length()), + static_cast(descriptor_type::string) }); + auto scatter_payload = make_scatter_bytes( + hdr_array, + std::span(reinterpret_cast(m_name.data()), m_name.length())); + + p_callback(scatter_payload); + return true; + } + + bool driver_handle_request(setup_packet const& p_setup, + endpoint_writer const& p_callback) override + { + std::ignore = p_setup; + std::ignore = p_callback; + return true; + } + +public: + std::array m_packed_array = { + constants::inferface_desc_size, // size + static_cast(descriptor_type::interface), // desc type + 0, // interface_number + 0, // alternate_setting + 0, // num_endpoints + 0, // interface_class + 0, // interface_sub_class + 0, // interface_protocol + 0 // interface name index + }; + + std::u16string_view m_name; +}; +} // namespace hal::v5::usb + +namespace hal5 = hal::v5; + +void application() +{ + namespace usb = hal5::usb; + namespace pmr = std::pmr; + using namespace hal::v5::literals; + using namespace std::chrono_literals; + using namespace std::string_view_literals; + + // static std::array iface_buf; + // pmr::monotonic_buffer_resource pool(iface_buf.data(), iface_buf.size()); + + auto const console = resources::console(); + auto const clk = resources::clock(); + auto const pool = resources::driver_allocator(); + // static std::array buf; + + // pmr::monotonic_buffer_resource pool; + + // while (true) { + // hal::print(*serial, "Testing!\n"); + // hal::delay(*clk, 500ms); + // } + + auto manu_str = u"libhal"sv; + auto prod_str = u"my epic device!!"sv; + auto sn_str = u"2468"sv; + + usb::device dev{ { .p_bcd_usb = 0x2, + .p_device_class = + usb::class_code::use_interface_descriptor, + .p_device_subclass = 0, + .p_device_protocol = 0, + .p_id_vendor = 0x1234, + .p_id_product = 0x5678, + .p_bcd_device = 0x1, + .p_manufacturer = manu_str, + .p_product = prod_str, + .p_serial_number_str = sn_str } }; + + hal::print(*console, "Device made\n"); + auto iface_str = u"dummy"; + auto conf_str = u"test config"; + auto iface_ptr = hal5::make_strong_ptr( + pool, usb::dummy_interface{ iface_str }); + + hal::print(*console, "conf made\n"); + + std::array confs{ usb::configuration{ + conf_str, // yeet + usb::configuration::bitmap(false, false), + 1, + pool, + iface_ptr } }; + hal::print(*console, "conf array made\n"); + + auto ctrl_ep = resources::usb_control_endpoint(); + hal5::u16 const en_lang_str = 0x0409; + + hal::print(*console, "ctrl ep called\n"); + hal::print<48>(*console, "conf pos outside enumerator: %p\n", &confs[0]); + + try { + usb::enumerator<1> en(ctrl_ep, + dev, + confs[0], + en_lang_str, // f + 1, + console, + false); + + en.enumerate(); + } // Catch statements for specific hal exceptions + + catch (hal::no_such_device const& e) { + hal::print(*console, "no_such_device exception has occurred, terminate\n"); + } + + catch (hal::io_error const& e) { + hal::print(*console, "io_error exception has occurred, terminate\n"); + } + + catch (hal::resource_unavailable_try_again const& e) { + hal::print( + *console, + "resource_unavailable_try_again exception has occurred, terminate\n"); + } + + catch (hal::device_or_resource_busy const& e) { + hal::print(*console, + "device_or_resource_busy exception has occurred, terminate\n"); + } + + catch (hal::timed_out const& e) { + hal::print(*console, "timed_out exception has occurred, terminate\n"); + } + + catch (hal::operation_not_supported const& e) { + hal::print(*console, + "operation_not_supported exception has occurred, terminate\n"); + } + + catch (hal::operation_not_permitted const& e) { + hal::print(*console, + "operation_not_permitted exception has occurred, terminate\n"); + } + + catch (hal::argument_out_of_domain const& e) { + hal::print(*console, + "argument_out_of_domain exception has occurred, terminate\n"); + } + + catch (hal::message_size const& e) { + hal::print(*console, "message_size exception has occurred, terminate\n"); + } + + catch (hal::not_connected const& e) { + hal::print(*console, "not_connected exception has occurred, terminate\n"); + } + + catch (hal::unknown const& e) { + hal::print(*console, "unknown exception has occurred, terminate\n"); + } + + catch (hal::bad_weak_ptr const& e) { + hal::print(*console, "bad_weak_ptr exception has occurred, terminate\n"); + } + + catch (hal::out_of_range const& e) { + hal::print(*console, "out_of_range exception has occurred, terminate\n"); + } + + catch (hal::bad_optional_ptr_access const& e) { + hal::print(*console, + "bad_optional_ptr_access exception has occurred, terminate\n"); + } + + // Generic catch for any hal::exception not caught above + catch (hal::exception const& e) { + hal::print(*console, "hal::exception has occurred, terminate\n"); + } + + // Generic catch for any std::exception not caught above + catch (std::exception const& e) { + hal::print(*console, "std::exception has occurred, terminate\n"); + hal::print(*console, e.what()); + hal::print(*console, "\n"); + } + + catch (...) { + hal::print(*console, "what?\n"); + } + + hal::print(*console, "Enumerator made\n"); + + hal::print(*console, "Enumeration complete\n"); + while (true) { + continue; + } + // return; +} diff --git a/demos/applications/utils.hpp b/demos/applications/utils.hpp new file mode 100644 index 00000000..a1e68c75 --- /dev/null +++ b/demos/applications/utils.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include +#include + +namespace hal::v5::usb { + +// TODO: Remove +// u16 from_le_bytes(hal::byte& first, hal::byte& second) +// { +// return static_cast(second) << 8 | first; +// } + +// std::array to_le_bytes(u16 n) +// { +// return { static_cast(n & 0xFF), +// static_cast((n & 0xFF << 8) >> 8) }; +// } + +namespace constants { + +constexpr byte device_desc_size = 18; +constexpr byte config_desc_size = 9; +constexpr byte inferface_desc_size = 9; +constexpr byte endpoint_desc_size = 7; +constexpr byte size_std_req = 8; + +} // namespace constants + +// Maybe move these enum classes into the constants namespace +// Assigned by USB-IF +enum class class_code : hal::byte +{ + use_interface_descriptor = + 0x00, // Use class information in the Interface Descriptors + audio = 0x01, // Audio device class + cdc_control = 0x02, // Communications and CDC Control + hid = 0x03, // Human Interface Device + physical = 0x05, // Physical device class + image = 0x06, // Still Imaging device + printer = 0x07, // Printer device + mass_storage = 0x08, // Mass Storage device + hub = 0x09, // Hub device + cdc_data = 0x0A, // CDC-Data device + smart_card = 0x0B, // Smart Card device + content_security = 0x0D, // Content Security device + video = 0x0E, // Video device + personal_healthcare = 0x0F, // Personal Healthcare device + audio_video = 0x10, // Audio/Video Devices + billboard = 0x11, // Billboard Device Class + usb_c_bridge = 0x12, // USB Type-C Bridge Class + bulk_display = 0x13, // USB Bulk Display Protocol Device Class + mctp = 0x14, // MCTP over USB Protocol Endpoint Device Class + i3c = 0x3C, // I3C Device Class + diagnostic = 0xDC, // Diagnostic Device + wireless_controller = 0xE0, // Wireless Controller + misc = 0xEF, // Miscellaneous + application_specific = 0xFE, // Application Specific + vendor_specific = 0xFF // Vendor Specific +}; + +// Default types +enum class descriptor_type : hal::byte +{ + device = 0x1, + configuration = 0x2, + string = 0x3, + interface = 0x4, + endpoint = 0x5, + device_qualifier = 0x6, + other_speed_configuration = 0x7, + interface_power = 0x8, + otg = 0x9, + debug = 0xA, + interface_association = 0xB, + security = 0xC, + key = 0xD, + encryption_type = 0xE, + bos = 0xF, + device_capability = 0x10, + wireless_endpoint_companion = 0x11, + superspeed_endpoint_companion = 0x30, + superspeed_endpoint_isochronous_companion = 0x31, +}; + +// TODO: Remove +// enum class standard_request_types : hal::byte const +// { +// get_status = 0x00, +// clear_feature = 0x01, +// set_feature = 0x03, +// set_address = 0x05, +// get_descriptor = 0x06, +// set_descriptor = 0x07, +// get_configuration = 0x08, +// set_configuration = 0x09, +// get_interface = 0x0A, +// set_interface = 0x11, +// synch_frame = 0x12, +// invalid +// }; + +// [[nodiscard]] constexpr standard_request_types determine_standard_request( +// setup_packet pkt) +// { +// if (pkt.get_type() != setup_packet::type::standard || pkt.request == 0x04 +// || +// pkt.request > 0x12) { +// return standard_request_types::invalid; +// } + +// return static_cast(pkt.request); +// } + +constexpr setup_packet from_span(std::span raw_req) +{ + setup_packet pkt; + pkt.request_type = raw_req[0]; + pkt.request = raw_req[1]; + pkt.value = setup_packet::from_le_bytes(raw_req[2], raw_req[3]); + pkt.index = setup_packet::from_le_bytes(raw_req[4], raw_req[5]); + pkt.length = setup_packet::from_le_bytes(raw_req[6], raw_req[7]); + return pkt; +} + +} // namespace hal::v5::usb diff --git a/demos/platforms/stm32f103c8.cpp b/demos/platforms/stm32f103c8.cpp index 1bb43fbb..c90de73d 100644 --- a/demos/platforms/stm32f103c8.cpp +++ b/demos/platforms/stm32f103c8.cpp @@ -48,7 +48,7 @@ namespace resources { using namespace hal::literals; using st_peripheral = hal::stm32f1::peripheral; -std::array driver_memory{}; +std::array driver_memory{}; std::pmr::monotonic_buffer_resource resource(driver_memory.data(), driver_memory.size(), std::pmr::null_memory_resource()); From e1058326be877ee4cd57115dc1efea6a84920bf1 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Fri, 12 Sep 2025 18:25:43 -0700 Subject: [PATCH 3/6] Repaired API to what util will be --- demos/applications/enumerator.hpp | 68 +++++++++----------- demos/applications/usb_interface_testing.cpp | 54 +++++++--------- 2 files changed, 54 insertions(+), 68 deletions(-) diff --git a/demos/applications/enumerator.hpp b/demos/applications/enumerator.hpp index 2aa0ad7c..b09da00b 100644 --- a/demos/applications/enumerator.hpp +++ b/demos/applications/enumerator.hpp @@ -54,8 +54,6 @@ make_sub_scatter_array(size_t p_count, Args&&... p_spans) std::array, sizeof...(Args)> full_ss{ std::span( std::forward(p_spans))... }; - auto res_len = sizeof...(p_spans); - (void)res_len; size_t total_span_len = scatter_span_size(scatter_span(full_ss)); std::array, sizeof...(Args)> res; std::array lens{ std::span(p_spans).size()... }; @@ -100,21 +98,20 @@ class enumerator { public: - enumerator(strong_ptr const& p_ctrl_ep, - device const& p_device, - configuration& p_configs, - u16 p_lang_str, - u8&& p_starting_str_idx, - hal::v5::strong_ptr const& p_console, - bool enumerate_immediately = true) + enumerator( + strong_ptr const& p_ctrl_ep, + strong_ptr const& p_device, + strong_ptr> const& p_configs, + u16 p_lang_str, // NOLINT + u8 p_starting_str_idx, + hal::v5::strong_ptr const& p_console, + bool enumerate_immediately = true) : m_ctrl_ep(p_ctrl_ep) , m_device(p_device) - , m_configs({ &p_configs }) + , m_configs(p_configs) , m_lang_str(p_lang_str) , m_console(p_console) { - auto s = make_scatter_bytes(p_configs); - print_payload(m_console, s); // Verify there is space to actually allocate indexes for configuration // Three string indexes are reserved for the device descriptor, then each // configuration has a name which reserves a string index strings and @@ -142,23 +139,23 @@ class enumerator // Phase one: Preperation // Device - m_device.manufacturer_index() = cur_str_idx++; - m_device.product_index() = cur_str_idx++; - m_device.serial_number_index() = cur_str_idx++; - m_device.num_configurations() = num_configs; + m_device->manufacturer_index() = cur_str_idx++; + m_device->product_index() = cur_str_idx++; + m_device->serial_number_index() = cur_str_idx++; + m_device->num_configurations() = num_configs; hal::print(*m_console, "Device desc: set up\n"); // Configurations for (size_t i = 0; i < num_configs; i++) { - configuration& config = *m_configs[i]; + configuration& config = m_configs->at(i); config.configuration_index() = cur_str_idx++; config.configuration_value() = i; } hal::print(*m_console, "conf desc: set up\n"); - for (configuration* config : m_configs) { + for (configuration& config : *m_configs) { auto total_length = static_cast(constants::config_desc_size); - for (auto const& iface : config->interfaces) { + for (auto const& iface : config.interfaces) { interface::descriptor_count deltas = iface->write_descriptors( { .interface = cur_iface_idx, .string = cur_str_idx }, [&total_length](scatter_span p_data) { @@ -168,7 +165,7 @@ class enumerator cur_iface_idx += deltas.interface; cur_str_idx += deltas.string; } - config->total_length() = total_length; + config.total_length() = total_length; } hal::print(*m_console, "iface/conf desc: set up\n"); @@ -185,8 +182,6 @@ class enumerator [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); m_ctrl_ep->connect(true); hal::print(*m_console, "Connected\n"); - auto sconf = make_scatter_bytes(*m_configs[0]); - print_payload(m_console, sconf); std::array raw_req; do { // Seriously, make this async @@ -319,7 +314,7 @@ class enumerator } case standard_request_types::set_configuration: { - m_active_conf = m_configs[req.value]; + m_active_conf = &(m_configs->at(req.value)); break; } @@ -339,9 +334,9 @@ class enumerator auto header = std::to_array({ constants::device_desc_size, static_cast(descriptor_type::device) }); - m_device.max_packet_size() = static_cast(m_ctrl_ep->info().size); + m_device->max_packet_size() = static_cast(m_ctrl_ep->info().size); auto scatter_arr_pair = - make_sub_scatter_bytes(req.length, header, m_device); + make_sub_scatter_bytes(req.length, header, *m_device); hal::v5::write_and_flush( *m_ctrl_ep, scatter_span(scatter_arr_pair.first) @@ -354,8 +349,7 @@ class enumerator } case descriptor_type::configuration: { - hal::print<24>(*m_console, "Conf addr: %p\n", m_configs[0]); - configuration& conf = *m_configs[desc_idx]; + configuration& conf = m_configs->at(desc_idx); auto conf_hdr = std::to_array({ constants::config_desc_size, static_cast(descriptor_type::configuration) }); @@ -474,8 +468,8 @@ class enumerator } } - for (configuration* conf : m_configs) { - for (auto const& iface : conf->interfaces) { + for (configuration const& conf : *m_configs) { + for (auto const& iface : conf.interfaces) { auto res = iface->write_string_descriptor(target_idx, f); if (res) { break; @@ -492,19 +486,19 @@ class enumerator constexpr u8 dev_sn_offset = 2; std::optional opt_conf_sv; if (target_idx == (m_starting_str_idx + dev_manu_offset)) { - opt_conf_sv = m_device.manufacturer_str; + opt_conf_sv = m_device->manufacturer_str; } else if (target_idx == (m_starting_str_idx + dev_prod_offset)) { - opt_conf_sv = m_device.product_str; + opt_conf_sv = m_device->product_str; } else if (target_idx == (m_starting_str_idx + dev_sn_offset)) { - opt_conf_sv = m_device.serial_number_str; + opt_conf_sv = m_device->serial_number_str; } else { - for (size_t i = 0; i < m_configs.size(); i++) { - configuration* conf = m_configs[i]; + for (size_t i = 0; i < m_configs->size(); i++) { + configuration const& conf = m_configs->at(i); if (target_idx == (m_starting_str_idx + i)) { - opt_conf_sv = conf->name; + opt_conf_sv = conf.name; } } } @@ -560,8 +554,8 @@ class enumerator } strong_ptr m_ctrl_ep; - device m_device; - std::array m_configs; + strong_ptr m_device; + strong_ptr> m_configs; u16 m_lang_str; u8 m_starting_str_idx; std::optional>> m_iface_for_str_desc; diff --git a/demos/applications/usb_interface_testing.cpp b/demos/applications/usb_interface_testing.cpp index 1887c276..37d9f6e4 100644 --- a/demos/applications/usb_interface_testing.cpp +++ b/demos/applications/usb_interface_testing.cpp @@ -1,3 +1,4 @@ +#include "applications/descriptors.hpp" #include "resource_list.hpp" #include #include @@ -7,11 +8,9 @@ #include #include #include -#include #include #include #include -#include #include "enumerator.hpp" @@ -96,36 +95,27 @@ void application() using namespace std::chrono_literals; using namespace std::string_view_literals; - // static std::array iface_buf; - // pmr::monotonic_buffer_resource pool(iface_buf.data(), iface_buf.size()); - auto const console = resources::console(); auto const clk = resources::clock(); auto const pool = resources::driver_allocator(); - // static std::array buf; - - // pmr::monotonic_buffer_resource pool; - - // while (true) { - // hal::print(*serial, "Testing!\n"); - // hal::delay(*clk, 500ms); - // } auto manu_str = u"libhal"sv; auto prod_str = u"my epic device!!"sv; auto sn_str = u"2468"sv; - usb::device dev{ { .p_bcd_usb = 0x2, - .p_device_class = - usb::class_code::use_interface_descriptor, - .p_device_subclass = 0, - .p_device_protocol = 0, - .p_id_vendor = 0x1234, - .p_id_product = 0x5678, - .p_bcd_device = 0x1, - .p_manufacturer = manu_str, - .p_product = prod_str, - .p_serial_number_str = sn_str } }; + auto dev = hal::v5::make_strong_ptr( + pool, + usb::device::device_arguments{ .p_bcd_usb = 0x2, + .p_device_class = + usb::class_code::use_interface_descriptor, + .p_device_subclass = 0, + .p_device_protocol = 0, + .p_id_vendor = 0x1234, + .p_id_product = 0x5678, + .p_bcd_device = 0x1, + .p_manufacturer = manu_str, + .p_product = prod_str, + .p_serial_number_str = sn_str }); hal::print(*console, "Device made\n"); auto iface_str = u"dummy"; @@ -135,24 +125,26 @@ void application() hal::print(*console, "conf made\n"); - std::array confs{ usb::configuration{ - conf_str, // yeet - usb::configuration::bitmap(false, false), - 1, + auto confs = hal::v5::make_strong_ptr>( pool, - iface_ptr } }; + std::array{ + usb::configuration{ conf_str, // yeet + usb::configuration::bitmap(false, false), + 1, + pool, + iface_ptr } }); + hal::print(*console, "conf array made\n"); auto ctrl_ep = resources::usb_control_endpoint(); hal5::u16 const en_lang_str = 0x0409; hal::print(*console, "ctrl ep called\n"); - hal::print<48>(*console, "conf pos outside enumerator: %p\n", &confs[0]); try { usb::enumerator<1> en(ctrl_ep, dev, - confs[0], + confs, en_lang_str, // f 1, console, From a772134e131abc85e2608ef458f045db7e4fe704 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sat, 13 Sep 2025 07:16:29 -0700 Subject: [PATCH 4/6] Got cdc working, fixed a few bugs with enumerator --- demos/CMakeLists.txt | 1 + demos/applications/enumerator.hpp | 27 +- demos/applications/usb_cdc.cpp | 641 +++++++++++++++++++ demos/applications/usb_interface_testing.cpp | 2 + demos/applications/utils.hpp | 2 + 5 files changed, 666 insertions(+), 7 deletions(-) create mode 100644 demos/applications/usb_cdc.cpp diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index e7b50a18..20c713bb 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -35,6 +35,7 @@ libhal_build_demos( # stream_dac timer usb_cdc_raw + usb_cdc semihost PACKAGES diff --git a/demos/applications/enumerator.hpp b/demos/applications/enumerator.hpp index b09da00b..a4216c35 100644 --- a/demos/applications/enumerator.hpp +++ b/demos/applications/enumerator.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "descriptors.hpp" @@ -149,7 +150,7 @@ class enumerator for (size_t i = 0; i < num_configs; i++) { configuration& config = m_configs->at(i); config.configuration_index() = cur_str_idx++; - config.configuration_value() = i; + config.configuration_value() = i + 1; } hal::print(*m_console, "conf desc: set up\n"); @@ -165,6 +166,7 @@ class enumerator cur_iface_idx += deltas.interface; cur_str_idx += deltas.string; } + config.num_interfaces() = cur_iface_idx; config.total_length() = total_length; } hal::print(*m_console, "iface/conf desc: set up\n"); @@ -246,6 +248,8 @@ class enumerator continue; } + m_has_setup_packet = false; + std::array read_buf; auto scatter_read_buf = make_writable_scatter_bytes(read_buf); auto bytes_read = m_ctrl_ep->read(scatter_read_buf); @@ -266,9 +270,18 @@ class enumerator handle_standard_device_request(req); } else { // Handle iface level requests - auto f = [this](scatter_span resp) { - m_ctrl_ep->write(resp); - }; + interface::endpoint_writer f; + if (req.is_device_to_host()) { + f = [this](scatter_span resp) { + m_ctrl_ep->write(resp); + }; + } else { + f = [this](scatter_span resp) { + std::ignore = m_ctrl_ep->read( + resp); // Can't use this... TODO: Maybe add a return for callbacks + // for "bytes processed" + }; + } bool req_handled = false; for (auto const& iface : get_active_configuration()) { req_handled = iface->handle_request( @@ -314,7 +327,7 @@ class enumerator } case standard_request_types::set_configuration: { - m_active_conf = &(m_configs->at(req.value)); + m_active_conf = &(m_configs->at(req.value - 1)); break; } @@ -377,14 +390,14 @@ class enumerator std::ignore = iface->write_descriptors( { .interface = std::nullopt, .string = std::nullopt }, [this, &total_size](scatter_span byte_stream) { - hal::v5::write_and_flush(*this->m_ctrl_ep, byte_stream); + m_ctrl_ep->write(byte_stream); total_size += scatter_span_size(byte_stream); hal::print(*this->m_console, "Iface sent\n"); print_payload(this->m_console, byte_stream); }); } hal::print(*m_console, "iface descs written\n"); - + m_ctrl_ep->write({}); // if (total_size != req.length) { // safe_throw(hal::operation_not_supported( // this)); // TODO: Make specific exception for this diff --git a/demos/applications/usb_cdc.cpp b/demos/applications/usb_cdc.cpp new file mode 100644 index 00000000..3cf1d60c --- /dev/null +++ b/demos/applications/usb_cdc.cpp @@ -0,0 +1,641 @@ +#include "applications/descriptors.hpp" +#include "applications/utils.hpp" +#include "resource_list.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "enumerator.hpp" + +namespace hal::v5::usb { +namespace constants { +constexpr hal::u8 cdc_set_line_coding = 0x20; +constexpr hal::u8 cdc_get_line_coding = 0x21; +constexpr hal::u8 cdc_set_control_line_state = 0x22; +constexpr hal::u8 cdc_send_break = 0x23; +} // namespace constants + +class cdc_serial : public interface +{ +public: + ~cdc_serial() override = default; + + // NOLINTBEGIN + cdc_serial(std::u16string_view p_iad_name, + std::u16string_view p_ctrl_name, + std::u16string_view p_data_name, + strong_ptr const& p_ep_data_out, + strong_ptr const& p_ep_data_in, + strong_ptr const& p_ep_status_out, + strong_ptr const& p_ep_status_in, + strong_ptr const& p_console) + : m_iad(0x2, 0x2, 0x1) + , m_ctrl_iface(0x0, 0x2, 0x2, 0x1) + , m_data_iface(0x0, 0xA, 0x0, 0x0) + , m_iad_name(p_iad_name) + , m_ctrl_name(p_ctrl_name) + , m_data_name(p_data_name) + , m_ep_data_out(p_ep_data_out) + , m_ep_data_in(p_ep_data_in) + , m_ep_status_out(p_ep_status_out) + , m_ep_status_in(p_ep_status_in) + , m_console(p_console) {}; + + struct iface_desc + { + // Full Interface Descriptor size: 9 bytes (type + length in header) + std::array(constants::inferface_desc_size)> + data{}; + + // Default values for USB CDC ACM: + // bLength = 9, bDescriptorType = 4 + constexpr iface_desc() + : data{ 0x09, 0x04, 0, 0, 0, 0, 0, 0, 0 } + { + } + + // Customizable constructor (alt setting, class, subclass, protocol) + constexpr iface_desc(byte alt_setting, + byte iface_class, + byte iface_subclass, + byte iface_protocol) + : data{ 0x09, 0x04, 0, alt_setting, 0, iface_class, + iface_subclass, iface_protocol, 0 } + { + } + + // Accessors (now that header is included) + constexpr byte& bLength() + { + return data[0]; + } + constexpr byte& bDescriptorType() + { + return data[1]; + } + constexpr byte& bInterfaceNumber() + { + return data[2]; + } + constexpr byte& bAlternateSetting() + { + return data[3]; + } + constexpr byte& bNumEndpoints() + { + return data[4]; + } + constexpr byte& bInterfaceClass() + { + return data[5]; + } + constexpr byte& bInterfaceSubClass() + { + return data[6]; + } + constexpr byte& bInterfaceProtocol() + { + return data[7]; + } + constexpr byte& iInterface() + { + return data[8]; + } + + // Span conversion operator + constexpr operator std::span() const + { + return std::span(data.data(), data.size()); + } + }; + + struct iad_desc + { + // Full IAD Descriptor size: 8 bytes (type + length in header) + std::array(constants::iad_desc_size)> data{}; + + // Default values for USB CDC ACM: + // bLength = 8, bDescriptorType = 0x0B + constexpr iad_desc() + : data{ 0x08, 0x0B, 0, 0, 0, 0, 0, 0 } + { + } + + // Customizable constructor (class, subclass, protocol) + constexpr iad_desc(byte function_class, + byte function_subclass, + byte function_protocol) + : data{ + 0x08, 0x0B, 0, 0, function_class, function_subclass, function_protocol, + 0 + } + { + } + + // Accessors + constexpr byte& bLength() + { + return data[0]; + } + constexpr byte& bDescriptorType() + { + return data[1]; + } + constexpr byte& bFirstInterface() + { + return data[2]; + } + constexpr byte& bInterfaceCount() + { + return data[3]; + } + constexpr byte& bFunctionClass() + { + return data[4]; + } + constexpr byte& bFunctionSubClass() + { + return data[5]; + } + constexpr byte& bFunctionProtocol() + { + return data[6]; + } + constexpr byte& iFunction() + { + return data[7]; + } + + // Span conversion operator + constexpr operator std::span() const + { + return std::span(data.data(), data.size()); + } + }; + // NOLINTEND + +private: + [[nodiscard]] descriptor_count driver_write_descriptors( + descriptor_start p_start, + endpoint_writer const& p_callback) override + { + + constexpr std::array cdc_hdr_func_desc = { + 0x05, // bLength + 0x24, // bDescriptorType (CS_INTERFACE) + 0x00, // bDescriptorSubtype (Header) + 0x10, + 0x01, // bcdCDC (1.10) + }; + + if (p_start.interface.has_value()) { + byte counter = p_start.interface.value(); + m_ctrl_iface.bInterfaceNumber() = counter; + counter += 1; + m_data_iface.bInterfaceNumber() = counter; + } + + m_ctrl_iface.bNumEndpoints() = 1; + m_data_iface.bNumEndpoints() = 2; + + // Set IAD values + m_iad.bFirstInterface() = m_ctrl_iface.bInterfaceNumber(); + m_iad.bInterfaceCount() = 2; + + auto str_optional = p_start.string; + if (str_optional.has_value()) { + m_iad.iFunction() = str_optional.value(); + str_optional.value() += 1; + } + + // Populate iface string indexes + if (str_optional.has_value()) { + m_ctrl_iface.iInterface() = str_optional.value(); + str_optional.value() += 1; + m_data_iface.iInterface() = str_optional.value(); + } + + // Declare CDC descriptors + constexpr std::array cdc_asm_func_desc = { + 0x04, // bLength + 0x24, // bDescriptorType (CS_INTERFACE) + 0x02, // bDescriptorSubtype (Abstract Control Management) + 0x02, // bmCapabilities + }; + + // Set bSubordinateInterface0 with the Control Interface's index + std::array cdc_union_func_desc{ + 0x05, // bLength + 0x24, // bDescriptorType (CS_INTERFACE) + 0x06, // bDescriptorSubtype (Union) + 0x00, // bControlInterface + m_data_iface.bInterfaceNumber(), // bSubordinateInterface0 + }; + + constexpr std::array cdc_call_mgnt_func_desc{ + // CDC Call Management Functional Descriptor + 0x05, // bLength + 0x24, // bDescriptorType (CS_INTERFACE) + 0x01, // bDescriptorSubtype (Call Management) + 0x00, // bmCapabilities + 0x01, // bDataInterface + }; + + // Declare endpoint descriptor arrays + using endpoint_desc = + std::array(constants::endpoint_desc_size)>; + + // Control Iface Endpoints + endpoint_desc ctrl_in_desc{ static_cast( + constants::endpoint_desc_size), + static_cast(descriptor_type::endpoint), + m_ep_status_in->info().number, + 0x03, + static_cast(m_ep_status_in->info().size), + 0x00, + 0x10 }; + + // Data Iface Endpoints + endpoint_desc data_out_desc{ + static_cast(constants::endpoint_desc_size), + static_cast(descriptor_type::endpoint), + m_ep_data_out->info().number, // bEndpointAddress (OUT + 1) + 0x02, // bmAttributes (Bulk) + static_cast(m_ep_data_out->info().size), + 0x00, // wMaxPacketSize 16 + 0x00, // bInterval (Ignored for Bulk) + }; + + endpoint_desc data_in_desc{ + static_cast(constants::endpoint_desc_size), + static_cast(descriptor_type::endpoint), + m_ep_data_in->info().number, // bEndpointAddress (IN + 1) + 0x02, // bmAttributes (Bulk) + static_cast(m_ep_data_in->info().size), + 0x00, // wMaxPacketSize 16 + 0x00 // bInterval (Ignored for Bulk) + }; + + // Assemble payload + + auto payload = make_scatter_bytes(m_iad, + // Control + m_ctrl_iface, + cdc_hdr_func_desc, + cdc_asm_func_desc, + cdc_union_func_desc, + cdc_call_mgnt_func_desc, + ctrl_in_desc, + // Data + m_data_iface, + data_out_desc, + data_in_desc); + + p_callback(payload); + return { .interface = 2, .string = 3 }; + } + + [[nodiscard]] bool driver_write_string_descriptor( + u8 p_index, + endpoint_writer const& p_callback) override + { + std::array hdr{ 0, static_cast(descriptor_type::string) }; + std::u16string_view sv; + if (p_index == m_iad.iFunction()) { + hdr[0] = p_index; + sv = m_iad_name; + } else if (p_index == m_ctrl_iface.iInterface()) { + hdr[0] = p_index; + sv = m_ctrl_name; + } else if (p_index == m_data_iface.iInterface()) { + hdr[0] = p_index; + sv = m_data_name; + } else { + return false; + } + + auto sv_bytes = hal::as_bytes(sv); + auto payload = make_scatter_bytes(hdr, sv_bytes); + p_callback(payload); + return true; + } + + optional_ptr select_endpoint(byte p_index) + { + if (p_index == m_ep_data_in->info().number) { + return m_ep_data_in; + } else if (p_index == m_ep_data_out->info().number) { + return m_ep_data_out; + } else if (p_index == m_ep_status_in->info().number) { + return m_ep_status_in; + } else if (p_index == m_ep_status_out->info().number) { + return m_ep_status_out; + } else { + return nullptr; + } + } + + bool handle_cdc_requests(setup_packet const& p_setup, + endpoint_writer const& p_callback) + { + // Line Coding Structure (7 bytes) + static auto line_coding = std::to_array({ + 0x00, + 0x96, + 0x00, + 0x00, // Baud rate: 38400 (Little Endian) + 0x00, // Stop bits: 1 + 0x00, // Parity: None + 0x08 // Data bits: 8 + }); + + switch (p_setup.request) { + case constants::cdc_get_line_coding: { // Device to Host + if (!p_setup.is_device_to_host()) { + return false; + } + + auto payload = make_scatter_bytes(line_coding); + p_callback(payload); + return true; + } + + case constants::cdc_set_line_coding: { // Host to Device + if (p_setup.is_device_to_host()) { + return false; + } + + std::array payload; + auto read_buf = make_scatter_bytes(payload); + + // Acquire data, assumed to have read a proper payload, last byte + // are for amt read, it should never be larger than 8 + p_callback(read_buf); + + hal::print(*m_console, "{{"); + for (auto const byte : payload) { + hal::print<8>(*m_console, "0x%" PRIx8 ", ", byte); + } + hal::print(*m_console, "}}\n"); + std::copy_n(payload.begin(), line_coding.size(), line_coding.begin()); + } + + case constants::cdc_set_control_line_state: { // Host to Device + if (p_setup.is_device_to_host()) { + return false; + } + // wValue contains the control signals: + // Bit 0: DTR state + // Bit 1: RTS state + bool const dtr = p_setup.value & 0x01; + bool const rts = p_setup.value & 0x02; + // Handle control line state change + // Most basic implementation just returns success + p_callback({}); + hal::print<32>(*m_console, "DTR = %d, RTS = %d\n", dtr, rts); + return true; + } + + case constants::cdc_send_break: { // Device to Host + if (!p_setup.is_device_to_host()) { // Direction: Host to Device + return false; + } + // wValue contains break duration in milliseconds + // Most basic implementation just returns success + p_callback({}); + hal::print(*m_console, "SEND_BREAK\n"); + return true; + } + + default: + return false; + } + return false; // Fail safe + } + + bool driver_handle_request(setup_packet const& p_setup, + endpoint_writer const& p_callback) override + { + auto pkt_type = determine_standard_request(p_setup); + if (pkt_type == standard_request_types::invalid) { + return handle_request(p_setup, p_callback); + } + + switch (pkt_type) { + case standard_request_types::get_interface: + case standard_request_types::set_interface: + case standard_request_types::set_feature: + return true; + case standard_request_types::clear_feature: { + if (p_setup.get_recipient() == setup_packet::recipient::endpoint) { + auto ep = select_endpoint(p_setup.index & 0xF); + if (!ep) { + return false; + } + ep->stall(false); + } + return true; + } + + case standard_request_types::get_status: { + if (p_setup.get_recipient() == setup_packet::recipient::endpoint) { + auto const ep_idx = p_setup.index & 0xF; + optional_ptr ep = select_endpoint(ep_idx); + if (!ep) { + return false; + } + auto payload = make_scatter_bytes(std::to_array( + { static_cast(ep->info().stalled), 0 })); + p_callback(payload); + return true; + } + + return false; + } + + default: + return false; + } + } + + iad_desc m_iad; + iface_desc m_ctrl_iface; + iface_desc m_data_iface; + + std::u16string_view m_iad_name; + std::u16string_view m_ctrl_name; + std::u16string_view m_data_name; + + strong_ptr m_ep_data_out; + strong_ptr m_ep_data_in; + strong_ptr m_ep_status_out; + strong_ptr m_ep_status_in; + strong_ptr m_console; +}; +} // namespace hal::v5::usb + +namespace hal5 = hal::v5; + +void application() +{ + namespace usb = hal5::usb; + namespace pmr = std::pmr; + using namespace hal::v5::literals; + using namespace std::chrono_literals; + using namespace std::string_view_literals; + + auto const console = resources::console(); + auto const clk = resources::clock(); + auto const pool = resources::driver_allocator(); + + auto manu_str = u"libhal"sv; + auto prod_str = u"my epic cdc device!!"sv; + auto sn_str = u"42069"sv; + + auto dev = hal::v5::make_strong_ptr( + pool, + usb::device::device_arguments{ .p_bcd_usb = 0x2, + .p_device_class = + usb::class_code::use_interface_descriptor, + .p_device_subclass = 0, + .p_device_protocol = 0, + .p_id_vendor = 0x1234, + .p_id_product = 0x5678, + .p_bcd_device = 0x1, + .p_manufacturer = manu_str, + .p_product = prod_str, + .p_serial_number_str = sn_str }); + hal::print(*console, "Device made\n"); + auto conf_str = u"cdc config"sv; + auto control_endpoint = resources::usb_control_endpoint(); + auto serial_data_ep_out = resources::usb_bulk_out_endpoint1(); + auto serial_data_ep_in = resources::usb_bulk_in_endpoint1(); + auto status_ep_out = resources::usb_interrupt_out_endpoint1(); + auto status_ep_in = resources::usb_interrupt_in_endpoint1(); + + hal5::strong_ptr usb_console_iface = + hal5::make_strong_ptr(pool, + u"My Serial Device!"sv, + u"ctrl iface"sv, + u"data iface"sv, + serial_data_ep_out, + serial_data_ep_in, + status_ep_out, + status_ep_in, + console); + + hal5::strong_ptr> confs = + hal::v5::make_strong_ptr>( + pool, + std::array{ + usb::configuration{ conf_str, + usb::configuration::bitmap(false, false), + 0x32, + pool, + usb_console_iface } }); + + hal::print(*console, "conf array made with interfaces"); + try { + usb::enumerator<1> en( + control_endpoint, dev, confs, 0x0409, 1, console, false); + en.enumerate(); + } // Catch statements for specific hal exceptions + + catch (hal::no_such_device const& e) { + hal::print(*console, "no_such_device exception has occurred, terminate\n"); + } + + catch (hal::io_error const& e) { + hal::print(*console, "io_error exception has occurred, terminate\n"); + } + + catch (hal::resource_unavailable_try_again const& e) { + hal::print( + *console, + "resource_unavailable_try_again exception has occurred, terminate\n"); + } + + catch (hal::device_or_resource_busy const& e) { + hal::print(*console, + "device_or_resource_busy exception has occurred, terminate\n"); + } + + catch (hal::timed_out const& e) { + hal::print(*console, "timed_out exception has occurred, terminate\n"); + } + + catch (hal::operation_not_supported const& e) { + hal::print(*console, + "operation_not_supported exception has occurred, terminate\n"); + } + + catch (hal::operation_not_permitted const& e) { + hal::print(*console, + "operation_not_permitted exception has occurred, terminate\n"); + } + + catch (hal::argument_out_of_domain const& e) { + hal::print(*console, + "argument_out_of_domain exception has occurred, terminate\n"); + } + + catch (hal::message_size const& e) { + hal::print(*console, "message_size exception has occurred, terminate\n"); + } + + catch (hal::not_connected const& e) { + hal::print(*console, "not_connected exception has occurred, terminate\n"); + } + + catch (hal::unknown const& e) { + hal::print(*console, "unknown exception has occurred, terminate\n"); + } + + catch (hal::bad_weak_ptr const& e) { + hal::print(*console, "bad_weak_ptr exception has occurred, terminate\n"); + } + + catch (hal::out_of_range const& e) { + hal::print(*console, "out_of_range exception has occurred, terminate\n"); + } + + catch (hal::bad_optional_ptr_access const& e) { + hal::print(*console, + "bad_optional_ptr_access exception has occurred, terminate\n"); + } + + // Generic catch for any hal::exception not caught above + catch (hal::exception const& e) { + hal::print(*console, "hal::exception has occurred, terminate\n"); + } + + // Generic catch for any std::exception not caught above + catch (std::exception const& e) { + hal::print(*console, "std::exception has occurred, terminate\n"); + hal::print(*console, e.what()); + hal::print(*console, "\n"); + } + + catch (...) { + hal::print(*console, "what?\n"); + } + + hal::print(*console, "Enumerator made\n"); + + hal::print(*console, "Enumeration complete\n"); + while (true) { + continue; + } +} diff --git a/demos/applications/usb_interface_testing.cpp b/demos/applications/usb_interface_testing.cpp index 37d9f6e4..3421e096 100644 --- a/demos/applications/usb_interface_testing.cpp +++ b/demos/applications/usb_interface_testing.cpp @@ -1,4 +1,5 @@ #include "applications/descriptors.hpp" +#include "applications/utils.hpp" #include "resource_list.hpp" #include #include @@ -83,6 +84,7 @@ class dummy_interface : public interface std::u16string_view m_name; }; + } // namespace hal::v5::usb namespace hal5 = hal::v5; diff --git a/demos/applications/utils.hpp b/demos/applications/utils.hpp index a1e68c75..c87d1931 100644 --- a/demos/applications/utils.hpp +++ b/demos/applications/utils.hpp @@ -25,6 +25,8 @@ constexpr byte device_desc_size = 18; constexpr byte config_desc_size = 9; constexpr byte inferface_desc_size = 9; constexpr byte endpoint_desc_size = 7; +constexpr byte iad_desc_size = 0x08; + constexpr byte size_std_req = 8; } // namespace constants From 27ae94552d59b9830191599ce63b991273533a04 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Thu, 29 Jan 2026 14:42:14 -0800 Subject: [PATCH 5/6] :recycle: Removed duplicate util files and fixed namespaces --- demos/applications/descriptors.hpp | 265 --------- demos/applications/enumerator.hpp | 579 ------------------- demos/applications/usb_cdc.cpp | 37 +- demos/applications/usb_interface_testing.cpp | 25 +- demos/applications/utils.hpp | 130 ----- 5 files changed, 28 insertions(+), 1008 deletions(-) delete mode 100644 demos/applications/descriptors.hpp delete mode 100644 demos/applications/enumerator.hpp delete mode 100644 demos/applications/utils.hpp diff --git a/demos/applications/descriptors.hpp b/demos/applications/descriptors.hpp deleted file mode 100644 index 822755ef..00000000 --- a/demos/applications/descriptors.hpp +++ /dev/null @@ -1,265 +0,0 @@ -#pragma once - -/* TODO: - Class, subclass, proto validator - Device qualifer descriptor (happens between device and config) - Other speed descriptor (happens with configuration) -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libhal-util/as_bytes.hpp" -#include "libhal/units.hpp" -#include "utils.hpp" -#include -#include - -namespace hal::v5::usb { - -struct device -{ - template - friend class enumerator; - - struct device_arguments - { - u16 p_bcd_usb; - class_code p_device_class; - u8 p_device_subclass; - u8 p_device_protocol; - u16 p_id_vendor; - u16 p_id_product; - u16 p_bcd_device; - std::u16string_view p_manufacturer; - std::u16string_view p_product; - std::u16string_view p_serial_number_str; - }; - - constexpr device(device_arguments&& args) - : manufacturer_str(args.p_manufacturer) - , product_str(args.p_product) - , serial_number_str(args.p_serial_number_str) - { - u8 idx = 0; - auto bcd_usb_bytes = hal::as_bytes(&args.p_bcd_usb, 1); - for (auto& bcd_usb_byte : bcd_usb_bytes) { - m_packed_arr[idx++] = bcd_usb_byte; // 0, 1 - } - - m_packed_arr[idx++] = static_cast(args.p_device_class); // 2 - m_packed_arr[idx++] = args.p_device_subclass; // 3 - m_packed_arr[idx++] = args.p_device_protocol; // 4 - - m_packed_arr[idx++] = 0; // 5 Max Packet length handled by the enumerator - - auto id_vendor_bytes = hal::as_bytes(&args.p_id_vendor, 1); - for (auto& id_vendor_byte : id_vendor_bytes) { - m_packed_arr[idx++] = id_vendor_byte; // 6, 7 - } - - auto id_product_bytes = hal::as_bytes(&args.p_id_product, 1); - for (auto& id_product_byte : id_product_bytes) { - m_packed_arr[idx++] = id_product_byte; // 8, 9 - } - - auto bcd_device_bytes = hal::as_bytes(&args.p_bcd_device, 1); - for (auto& bcd_device_byte : bcd_device_bytes) { - m_packed_arr[idx++] = bcd_device_byte; // 10, 11 - } - - // Evaluated during enumeration - m_packed_arr[idx++] = 0; // 12 string idx of manufacturer - m_packed_arr[idx++] = 0; // 13 string idx of product - m_packed_arr[idx++] = 0; // 14 string idx of serial number - m_packed_arr[idx++] = 0; // 15 Number of possible configurations - }; - - u16& bcd_usb() - { - return *reinterpret_cast(&m_packed_arr[0]); - } - - constexpr u8& device_class() - { - return m_packed_arr[2]; - } - - constexpr u8& device_sub_class() - { - return m_packed_arr[3]; - } - - constexpr u8& device_protocol() - { - return m_packed_arr[4]; - } - - u16& id_vendor() - { - return *reinterpret_cast(&m_packed_arr[6]); - } - - u16& id_product() - { - return *reinterpret_cast(&m_packed_arr[8]); - } - - u16& bcd_device() - { - return *reinterpret_cast(&m_packed_arr[10]); - } - - operator std::span() const - { - return m_packed_arr; - } - - std::u16string_view manufacturer_str; - std::u16string_view product_str; - std::u16string_view serial_number_str; - -private: - constexpr u8& max_packet_size() - { - return m_packed_arr[5]; - } - - constexpr u8& manufacturer_index() - { - return m_packed_arr[12]; - } - constexpr u8& product_index() - { - return m_packed_arr[13]; - } - constexpr u8& serial_number_index() - { - return m_packed_arr[14]; - } - constexpr u8& num_configurations() - { - return m_packed_arr[15]; - } - - std::array m_packed_arr; -}; - -// https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors - -template -concept usb_interface_concept = std::derived_from; - -// Calculate: total length, number of interfaces, configuration value -struct configuration -{ - - template - friend class enumerator; - - struct bitmap - { - - constexpr bitmap(u8 p_bitmap) - : m_bitmap(p_bitmap) - { - } - - constexpr bitmap(bool p_self_powered, bool p_remote_wakeup) - { - m_bitmap = (1 << 7) | (p_self_powered << 6) | (p_remote_wakeup << 5); - } - - constexpr u8 to_byte() - { - return m_bitmap; - } - - [[nodiscard]] constexpr bool self_powered() const - { - return static_cast(m_bitmap & 1 << 6); - } - - [[nodiscard]] constexpr bool remote_wakeup() const - { - return static_cast(m_bitmap & 1 << 5); - } - - private: - u8 m_bitmap; - }; - - template - constexpr configuration(std::u16string_view p_name, - bitmap&& p_attributes, - u8&& p_max_power, - std::pmr::polymorphic_allocator<> p_allocator, - strong_ptr... p_interfaces) - : name(p_name) - , interfaces(p_allocator) - { - interfaces.reserve(sizeof...(p_interfaces)); - (interfaces.push_back(p_interfaces), ...); - u8 idx = 0; - - // Anything marked with 0 is to be populated at enumeration time - m_packed_arr[idx++] = 0; // 0 Total Length - m_packed_arr[idx++] = 0; - m_packed_arr[idx++] = interfaces.size(); // 2 number of interfaces - m_packed_arr[idx++] = 0; // 3 Config number - m_packed_arr[idx++] = 0; // 4 Configuration name string index - - m_packed_arr[idx++] = p_attributes.to_byte(); // 5 - m_packed_arr[idx++] = p_max_power; // 6 - } - - operator std::span() const - { - return m_packed_arr; - } - - constexpr bitmap attributes() - { - return { m_packed_arr[5] }; - } - constexpr u8& attributes_byte() - { - return m_packed_arr[5]; - } - - constexpr u8& max_power() - { - return m_packed_arr[6]; - } - - std::u16string_view name; - std::pmr::vector> interfaces; - - // private: - u16& total_length() - { - return *reinterpret_cast(&m_packed_arr[0]); - } - constexpr u8& num_interfaces() - { - return m_packed_arr[2]; - } - constexpr u8& configuration_value() - { - return m_packed_arr[3]; - } - constexpr u8& configuration_index() - { - return m_packed_arr[4]; - } - - std::array m_packed_arr; -}; -} // namespace hal::v5::usb diff --git a/demos/applications/enumerator.hpp b/demos/applications/enumerator.hpp deleted file mode 100644 index a4216c35..00000000 --- a/demos/applications/enumerator.hpp +++ /dev/null @@ -1,579 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "descriptors.hpp" -#include "utils.hpp" - -// TODO: move to util -namespace hal::v5::usb { - -void print_payload(strong_ptr const& p_console, - scatter_span p_payload) -{ - hal::print(*p_console, "Payload: [ "); - for (auto const& s : p_payload) { - for (unsigned char el : s) { - hal::print<10>(*p_console, "0x%X, ", el); - } - } - hal::print(*p_console, "]\n"); -} - -template -constexpr size_t scatter_span_size(scatter_span ss) -{ - size_t res = 0; - for (auto const& s : ss) { - res += s.size(); - } - - return res; -} - -template -constexpr std::pair, sizeof...(Args)>, size_t> -make_sub_scatter_array(size_t p_count, Args&&... p_spans) -{ - std::array, sizeof...(Args)> full_ss{ std::span( - std::forward(p_spans))... }; - - size_t total_span_len = scatter_span_size(scatter_span(full_ss)); - std::array, sizeof...(Args)> res; - std::array lens{ std::span(p_spans).size()... }; - - if (total_span_len <= p_count) { - return std::make_pair(full_ss, full_ss.size()); - } - size_t cur_len = 0; - size_t i = 0; - for (; i < lens.size(); i++) { - auto l = lens[i]; - - if (p_count >= (cur_len + l)) { - res[i] = full_ss[i]; - cur_len += l; - continue; - } - - if (cur_len >= p_count) { - return std::make_pair(res, i); - } - - auto delta = p_count - cur_len; - std::span subspan = std::span(full_ss[i]).first(delta); - res[i] = subspan; - break; - } - - return std::make_pair(res, i + 1); -} - -template -constexpr std::pair, sizeof...(Args)>, size_t> -make_sub_scatter_bytes(size_t p_count, Args&&... p_spans) -{ - return make_sub_scatter_array(p_count, - std::forward(p_spans)...); -} - -template -class enumerator -{ - -public: - enumerator( - strong_ptr const& p_ctrl_ep, - strong_ptr const& p_device, - strong_ptr> const& p_configs, - u16 p_lang_str, // NOLINT - u8 p_starting_str_idx, - hal::v5::strong_ptr const& p_console, - bool enumerate_immediately = true) - : m_ctrl_ep(p_ctrl_ep) - , m_device(p_device) - , m_configs(p_configs) - , m_lang_str(p_lang_str) - , m_console(p_console) - { - // Verify there is space to actually allocate indexes for configuration - // Three string indexes are reserved for the device descriptor, then each - // configuration has a name which reserves a string index strings and - // Index 0 is reserved for the lang string - if (p_starting_str_idx < 1 || p_starting_str_idx > 0xFF - 3 + num_configs) { - safe_throw(hal::argument_out_of_domain(this)); - } - m_starting_str_idx = p_starting_str_idx; - - if (enumerate_immediately) { - enumerate(); - } - } - - void enumerate() - { - // Renumerate, a config will only be set if - if (m_active_conf != nullptr) { - m_active_conf = nullptr; - m_ctrl_ep->connect(false); - } - - auto cur_str_idx = m_starting_str_idx; - byte cur_iface_idx = 0; - // Phase one: Preperation - - // Device - m_device->manufacturer_index() = cur_str_idx++; - m_device->product_index() = cur_str_idx++; - m_device->serial_number_index() = cur_str_idx++; - m_device->num_configurations() = num_configs; - hal::print(*m_console, "Device desc: set up\n"); - - // Configurations - for (size_t i = 0; i < num_configs; i++) { - configuration& config = m_configs->at(i); - config.configuration_index() = cur_str_idx++; - config.configuration_value() = i + 1; - } - hal::print(*m_console, "conf desc: set up\n"); - - for (configuration& config : *m_configs) { - auto total_length = static_cast(constants::config_desc_size); - for (auto const& iface : config.interfaces) { - interface::descriptor_count deltas = iface->write_descriptors( - { .interface = cur_iface_idx, .string = cur_str_idx }, - [&total_length](scatter_span p_data) { - total_length += scatter_span_size(p_data); - }); - - cur_iface_idx += deltas.interface; - cur_str_idx += deltas.string; - } - config.num_interfaces() = cur_iface_idx; - config.total_length() = total_length; - } - hal::print(*m_console, "iface/conf desc: set up\n"); - - // Phase two: Writing - - // TODO: Make async - bool finished_enumeration = false; - bool volatile waiting_for_data = true; - - using on_receive_tag = control_endpoint::on_receive_tag; - hal::print(*m_console, "Begin writing\n"); - - m_ctrl_ep->on_receive( - [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); - m_ctrl_ep->connect(true); - hal::print(*m_console, "Connected\n"); - std::array raw_req; - do { - // Seriously, make this async - while (waiting_for_data) { - continue; - } - waiting_for_data = true; - - auto scatter_raw_req = make_writable_scatter_bytes(raw_req); - auto num_bytes_read = m_ctrl_ep->read(scatter_raw_req); - - if (num_bytes_read == 0) { - continue; - } - - if (num_bytes_read != constants::size_std_req) { - hal::print<24>(*m_console, "num_bytes_read: %X\n", num_bytes_read); - safe_throw(hal::message_size(num_bytes_read, this)); - } - // hal::print(*m_console, "Raw request: ["); - // for (auto const& el : raw_req) { - // hal::print<8>(*m_console, "0x%X, ", el); - // } - // hal::print(*m_console, "]\n"); - auto req = from_span(raw_req); - hal::print<48>(*m_console, - "\nGot request: [0x%X, 0x%X, 0x%X, 0x%X, 0x%X]\n", - req.request_type, - req.request, - req.value, - req.index, - req.length); - - if (req.get_recipient() != setup_packet::recipient::device) { - safe_throw(hal::not_connected(this)); - } - - // TODO: Handle exception - handle_standard_device_request(req); - // m_ctrl_ep->write({}); // Send ZLP to complete Data Transaction - if (static_cast(req.request) == - standard_request_types::set_configuration) { - finished_enumeration = true; - m_ctrl_ep->on_receive( - [this](on_receive_tag) { m_has_setup_packet = true; }); - } - } while (!finished_enumeration); - } - - [[nodiscard]] configuration& get_active_configuration() - { - if (m_active_conf == nullptr) { - safe_throw(hal::operation_not_permitted(this)); - } - - return *m_active_conf; - } - - void resume_ctrl_transaction() - { - while (!m_has_setup_packet) { - continue; - } - - m_has_setup_packet = false; - - std::array read_buf; - auto scatter_read_buf = make_writable_scatter_bytes(read_buf); - auto bytes_read = m_ctrl_ep->read(scatter_read_buf); - std::span payload(read_buf.data(), bytes_read); - - setup_packet req = from_span(payload); - if (determine_standard_request(req) == standard_request_types::invalid) { - return; - } - - if (determine_standard_request(req) == - standard_request_types::get_descriptor && - static_cast((req.value & 0xFF << 8) >> 8) == - descriptor_type::string) { - handle_str_descriptors(req.value & 0xFF, req.length > 2); - - } else if (req.get_recipient() == setup_packet::recipient::device) { - handle_standard_device_request(req); - } else { - // Handle iface level requests - interface::endpoint_writer f; - if (req.is_device_to_host()) { - f = [this](scatter_span resp) { - m_ctrl_ep->write(resp); - }; - } else { - f = [this](scatter_span resp) { - std::ignore = m_ctrl_ep->read( - resp); // Can't use this... TODO: Maybe add a return for callbacks - // for "bytes processed" - }; - } - bool req_handled = false; - for (auto const& iface : get_active_configuration()) { - req_handled = iface->handle_request( - req.request_type, req.request, req.value, req.index, req.length, f); - if (req_handled) { - break; - } - } - m_ctrl_ep->write( - {}); // A ZLP to terminate Data Transaction just to be safe - - if (!req_handled) { - safe_throw(hal::argument_out_of_domain(this)); - } - } - } - -private: - void handle_standard_device_request(setup_packet& req) - { - - switch (determine_standard_request(req)) { - case standard_request_types::set_address: { - m_ctrl_ep->write({}); - m_ctrl_ep->set_address(req.value); - hal::print<16>(*m_console, "Address set: %x\n", req.value); - break; - } - - case standard_request_types::get_descriptor: { - process_get_descriptor(req); - break; - } - - case standard_request_types::get_configuration: { - if (m_active_conf == nullptr) { - safe_throw(hal::operation_not_permitted(this)); - } - auto scatter_conf = make_scatter_bytes( - std::span(&m_active_conf->configuration_value(), 1)); - m_ctrl_ep->write(scatter_conf); - break; - } - - case standard_request_types::set_configuration: { - m_active_conf = &(m_configs->at(req.value - 1)); - break; - } - - case standard_request_types::invalid: - default: - safe_throw(hal::not_connected(this)); - } - } - - void process_get_descriptor(setup_packet& req) - { - hal::byte desc_type = (req.value & 0xFF << 8) >> 8; - [[maybe_unused]] hal::byte desc_idx = req.value & 0xFF; - - switch (static_cast(desc_type)) { - case descriptor_type::device: { - auto header = - std::to_array({ constants::device_desc_size, - static_cast(descriptor_type::device) }); - m_device->max_packet_size() = static_cast(m_ctrl_ep->info().size); - auto scatter_arr_pair = - make_sub_scatter_bytes(req.length, header, *m_device); - hal::v5::write_and_flush( - *m_ctrl_ep, - scatter_span(scatter_arr_pair.first) - .first(scatter_arr_pair.second)); - hal::print(*m_console, "Dev desc written\n"); - print_payload(m_console, - scatter_span(scatter_arr_pair.first) - .first(scatter_arr_pair.second)); - break; - } - - case descriptor_type::configuration: { - configuration& conf = m_configs->at(desc_idx); - auto conf_hdr = - std::to_array({ constants::config_desc_size, - static_cast(descriptor_type::configuration) }); - auto scatter_conf_pair = make_sub_scatter_bytes( - req.length, conf_hdr, static_cast>(conf)); - - m_ctrl_ep->write(scatter_span(scatter_conf_pair.first) - .first(scatter_conf_pair.second)); - hal::print(*m_console, "conf desc header written\n"); - - // Return early if the only thing requested was the config descriptor - print_payload(m_console, - scatter_span(scatter_conf_pair.first) - .first(scatter_conf_pair.second)); - if (req.length <= constants::config_desc_size) { - // print_payload(m_console, - // scatter_span(scatter_conf_pair.first) - // .first(scatter_conf_pair.second)); - m_ctrl_ep->write({}); - return; - } - - u16 total_size = constants::config_desc_size; - for (auto const& iface : conf.interfaces) { - std::ignore = iface->write_descriptors( - { .interface = std::nullopt, .string = std::nullopt }, - [this, &total_size](scatter_span byte_stream) { - m_ctrl_ep->write(byte_stream); - total_size += scatter_span_size(byte_stream); - hal::print(*this->m_console, "Iface sent\n"); - print_payload(this->m_console, byte_stream); - }); - } - hal::print(*m_console, "iface descs written\n"); - m_ctrl_ep->write({}); - // if (total_size != req.length) { - // safe_throw(hal::operation_not_supported( - // this)); // TODO: Make specific exception for this - // } - - break; - } - - case descriptor_type::string: { - if (desc_idx == 0) { - hal::print<32>(*m_console, "Lang str should be: 0x%x\n", m_lang_str); - auto s_hdr = - std::to_array({ static_cast(4), - static_cast(descriptor_type::string) }); - auto lang = setup_packet::to_le_bytes(m_lang_str); - auto scatter_arr_pair = make_scatter_bytes(s_hdr, lang); - // auto p = scatter_span(scatter_arr_pair.first) - // .first(scatter_arr_pair.second); - m_ctrl_ep->write(scatter_arr_pair); - m_ctrl_ep->write({}); - print_payload(m_console, scatter_arr_pair); - hal::print(*m_console, "lang string written\n"); - break; - } - handle_str_descriptors(desc_idx, req.length); // Can throw - break; - } - - // TODO: Interface, endpoint, device_qualifier, interface_power, - // OTHER_SPEED_CONFIGURATION - - default: - safe_throw(hal::operation_not_supported(this)); - } - } - - void handle_str_descriptors(u8 const target_idx, u16 p_len) - { - - u8 cfg_string_end = m_starting_str_idx + 3 + num_configs; - if (target_idx <= cfg_string_end) { - auto r = write_cfg_str_descriptor(target_idx, p_len); - if (!r) { - safe_throw(hal::argument_out_of_domain(this)); - } - m_iface_for_str_desc = std::nullopt; - return; - } - - if (m_iface_for_str_desc.has_value() && - m_iface_for_str_desc->first == target_idx) { - bool success = m_iface_for_str_desc->second->write_string_descriptor( - target_idx, [this](scatter_span desc) { - hal::v5::write_and_flush(*m_ctrl_ep, desc); - }); - if (success) { - return; - hal::print(*m_console, "Wrote cached string\n"); - } - } - - // Iterate through every interface now to find a match - auto f = [this, p_len](scatter_span desc) { - if (p_len > 2) { - hal::v5::write_and_flush(*m_ctrl_ep, desc); - } else { - std::array desc_type{ static_cast( - descriptor_type::string) }; - auto scatter_str_hdr = - make_scatter_bytes(std::span(&desc[0][0], 1), desc_type); - hal::v5::write_and_flush(*m_ctrl_ep, scatter_str_hdr); - } - }; - - if (m_active_conf != nullptr) { - for (auto const& iface : m_active_conf->interfaces) { - auto res = iface->write_string_descriptor(target_idx, f); - if (res) { - return; - } - } - } - - for (configuration const& conf : *m_configs) { - for (auto const& iface : conf.interfaces) { - auto res = iface->write_string_descriptor(target_idx, f); - if (res) { - break; - } - } - } - hal::print(*m_console, "Wrote iface string\n"); - } - - bool write_cfg_str_descriptor(u8 const target_idx, u16 p_len) - { - constexpr u8 dev_manu_offset = 0; - constexpr u8 dev_prod_offset = 1; - constexpr u8 dev_sn_offset = 2; - std::optional opt_conf_sv; - if (target_idx == (m_starting_str_idx + dev_manu_offset)) { - opt_conf_sv = m_device->manufacturer_str; - - } else if (target_idx == (m_starting_str_idx + dev_prod_offset)) { - opt_conf_sv = m_device->product_str; - - } else if (target_idx == (m_starting_str_idx + dev_sn_offset)) { - opt_conf_sv = m_device->serial_number_str; - - } else { - for (size_t i = 0; i < m_configs->size(); i++) { - configuration const& conf = m_configs->at(i); - if (target_idx == (m_starting_str_idx + i)) { - opt_conf_sv = conf.name; - } - } - } - - if (opt_conf_sv == std::nullopt) { - return false; - } - - // Acceptable to access without checking because guaranteed to be Some, - // there is no pattern matching in C++ yet so unable to do this cleanly - // (would require a check on every single one) - hal::print<8>(*m_console, "%d: [", target_idx); - auto sv = opt_conf_sv.value(); - std::mbstate_t state{}; - for (wchar_t const wc : sv) { - char tmp[MB_CUR_MAX]; // NOLINT - size_t len = std::wcrtomb(tmp, wc, &state); - if (len == static_cast(-1)) { - hal::print(*m_console, "unable to convert wc\n"); - continue; - } - - for (size_t i = 0; i < len; i++) { - hal::print<8>(*m_console, "%c", tmp[i]); - } - } - hal::print(*m_console, "]\n"); - auto const conf_sv_span = hal::as_bytes(opt_conf_sv.value()); - auto desc_len = static_cast((conf_sv_span.size() + 2)); - hal::print<32>(*m_console, - "%d: (size: 0x%X, psize: 0x%X): [ ", - target_idx, - conf_sv_span.size(), - desc_len); - - for (auto const el : conf_sv_span) { - hal::print<8>(*m_console, "0x%X, ", el); - } - hal::print(*m_console, "]\n"); - hal::print(*m_console, "\n"); - auto hdr_arr = std::to_array( - { desc_len, static_cast(descriptor_type::string) }); - - auto scatter_arr_pair = - make_sub_scatter_bytes(p_len, hdr_arr, conf_sv_span); - - auto p = scatter_span(scatter_arr_pair.first) - .first(scatter_arr_pair.second); - hal::v5::write_and_flush(*m_ctrl_ep, p); - hal::print(*m_console, "Wrote cfg string\n"); - print_payload(m_console, p); - return true; - } - - strong_ptr m_ctrl_ep; - strong_ptr m_device; - strong_ptr> m_configs; - u16 m_lang_str; - u8 m_starting_str_idx; - std::optional>> m_iface_for_str_desc; - configuration* m_active_conf = nullptr; - bool m_has_setup_packet = false; - hal::v5::strong_ptr m_console; -}; -} // namespace hal::v5::usb diff --git a/demos/applications/usb_cdc.cpp b/demos/applications/usb_cdc.cpp index 3cf1d60c..810b9988 100644 --- a/demos/applications/usb_cdc.cpp +++ b/demos/applications/usb_cdc.cpp @@ -1,25 +1,21 @@ -#include "applications/descriptors.hpp" -#include "applications/utils.hpp" -#include "resource_list.hpp" #include #include #include + #include #include #include -#include +#include #include #include #include #include #include -#include -#include #include -#include "enumerator.hpp" +#include "resource_list.hpp" -namespace hal::v5::usb { +namespace hal::usb { namespace constants { constexpr hal::u8 cdc_set_line_coding = 0x20; constexpr hal::u8 cdc_get_line_coding = 0x21; @@ -361,7 +357,7 @@ class cdc_serial : public interface 0x08 // Data bits: 8 }); - switch (p_setup.request) { + switch (p_setup.request()) { case constants::cdc_get_line_coding: { // Device to Host if (!p_setup.is_device_to_host()) { return false; @@ -399,8 +395,8 @@ class cdc_serial : public interface // wValue contains the control signals: // Bit 0: DTR state // Bit 1: RTS state - bool const dtr = p_setup.value & 0x01; - bool const rts = p_setup.value & 0x02; + bool const dtr = p_setup.value() & 0x01; + bool const rts = p_setup.value() & 0x02; // Handle control line state change // Most basic implementation just returns success p_callback({}); @@ -439,8 +435,9 @@ class cdc_serial : public interface case standard_request_types::set_feature: return true; case standard_request_types::clear_feature: { - if (p_setup.get_recipient() == setup_packet::recipient::endpoint) { - auto ep = select_endpoint(p_setup.index & 0xF); + if (p_setup.get_recipient() == + setup_packet::request_recipient::endpoint) { + auto ep = select_endpoint(p_setup.index() & 0xF); if (!ep) { return false; } @@ -450,8 +447,9 @@ class cdc_serial : public interface } case standard_request_types::get_status: { - if (p_setup.get_recipient() == setup_packet::recipient::endpoint) { - auto const ep_idx = p_setup.index & 0xF; + if (p_setup.get_recipient() == + setup_packet::request_recipient::endpoint) { + auto const ep_idx = p_setup.index() & 0xF; optional_ptr ep = select_endpoint(ep_idx); if (!ep) { return false; @@ -484,15 +482,15 @@ class cdc_serial : public interface strong_ptr m_ep_status_in; strong_ptr m_console; }; -} // namespace hal::v5::usb +} // namespace hal::usb namespace hal5 = hal::v5; void application() { - namespace usb = hal5::usb; + namespace usb = hal::usb; namespace pmr = std::pmr; - using namespace hal::v5::literals; + using namespace hal::literals; using namespace std::chrono_literals; using namespace std::string_view_literals; @@ -548,8 +546,7 @@ void application() hal::print(*console, "conf array made with interfaces"); try { - usb::enumerator<1> en( - control_endpoint, dev, confs, 0x0409, 1, console, false); + usb::enumerator<1> en(control_endpoint, dev, confs, 0x0409, 1, false); en.enumerate(); } // Catch statements for specific hal exceptions diff --git a/demos/applications/usb_interface_testing.cpp b/demos/applications/usb_interface_testing.cpp index 3421e096..5beb4801 100644 --- a/demos/applications/usb_interface_testing.cpp +++ b/demos/applications/usb_interface_testing.cpp @@ -1,21 +1,19 @@ -#include "applications/descriptors.hpp" -#include "applications/utils.hpp" -#include "resource_list.hpp" #include +#include +#include +#include + #include #include +#include #include #include #include #include -#include -#include -#include -#include -#include "enumerator.hpp" +#include "resource_list.hpp" -namespace hal::v5::usb { +namespace hal::usb { class dummy_interface : public interface { public: @@ -85,15 +83,15 @@ class dummy_interface : public interface std::u16string_view m_name; }; -} // namespace hal::v5::usb +} // namespace hal::usb namespace hal5 = hal::v5; void application() { - namespace usb = hal5::usb; + namespace usb = hal::usb; namespace pmr = std::pmr; - using namespace hal::v5::literals; + using namespace hal::literals; using namespace std::chrono_literals; using namespace std::string_view_literals; @@ -139,7 +137,7 @@ void application() hal::print(*console, "conf array made\n"); auto ctrl_ep = resources::usb_control_endpoint(); - hal5::u16 const en_lang_str = 0x0409; + hal::u16 const en_lang_str = 0x0409; hal::print(*console, "ctrl ep called\n"); @@ -149,7 +147,6 @@ void application() confs, en_lang_str, // f 1, - console, false); en.enumerate(); diff --git a/demos/applications/utils.hpp b/demos/applications/utils.hpp deleted file mode 100644 index c87d1931..00000000 --- a/demos/applications/utils.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace hal::v5::usb { - -// TODO: Remove -// u16 from_le_bytes(hal::byte& first, hal::byte& second) -// { -// return static_cast(second) << 8 | first; -// } - -// std::array to_le_bytes(u16 n) -// { -// return { static_cast(n & 0xFF), -// static_cast((n & 0xFF << 8) >> 8) }; -// } - -namespace constants { - -constexpr byte device_desc_size = 18; -constexpr byte config_desc_size = 9; -constexpr byte inferface_desc_size = 9; -constexpr byte endpoint_desc_size = 7; -constexpr byte iad_desc_size = 0x08; - -constexpr byte size_std_req = 8; - -} // namespace constants - -// Maybe move these enum classes into the constants namespace -// Assigned by USB-IF -enum class class_code : hal::byte -{ - use_interface_descriptor = - 0x00, // Use class information in the Interface Descriptors - audio = 0x01, // Audio device class - cdc_control = 0x02, // Communications and CDC Control - hid = 0x03, // Human Interface Device - physical = 0x05, // Physical device class - image = 0x06, // Still Imaging device - printer = 0x07, // Printer device - mass_storage = 0x08, // Mass Storage device - hub = 0x09, // Hub device - cdc_data = 0x0A, // CDC-Data device - smart_card = 0x0B, // Smart Card device - content_security = 0x0D, // Content Security device - video = 0x0E, // Video device - personal_healthcare = 0x0F, // Personal Healthcare device - audio_video = 0x10, // Audio/Video Devices - billboard = 0x11, // Billboard Device Class - usb_c_bridge = 0x12, // USB Type-C Bridge Class - bulk_display = 0x13, // USB Bulk Display Protocol Device Class - mctp = 0x14, // MCTP over USB Protocol Endpoint Device Class - i3c = 0x3C, // I3C Device Class - diagnostic = 0xDC, // Diagnostic Device - wireless_controller = 0xE0, // Wireless Controller - misc = 0xEF, // Miscellaneous - application_specific = 0xFE, // Application Specific - vendor_specific = 0xFF // Vendor Specific -}; - -// Default types -enum class descriptor_type : hal::byte -{ - device = 0x1, - configuration = 0x2, - string = 0x3, - interface = 0x4, - endpoint = 0x5, - device_qualifier = 0x6, - other_speed_configuration = 0x7, - interface_power = 0x8, - otg = 0x9, - debug = 0xA, - interface_association = 0xB, - security = 0xC, - key = 0xD, - encryption_type = 0xE, - bos = 0xF, - device_capability = 0x10, - wireless_endpoint_companion = 0x11, - superspeed_endpoint_companion = 0x30, - superspeed_endpoint_isochronous_companion = 0x31, -}; - -// TODO: Remove -// enum class standard_request_types : hal::byte const -// { -// get_status = 0x00, -// clear_feature = 0x01, -// set_feature = 0x03, -// set_address = 0x05, -// get_descriptor = 0x06, -// set_descriptor = 0x07, -// get_configuration = 0x08, -// set_configuration = 0x09, -// get_interface = 0x0A, -// set_interface = 0x11, -// synch_frame = 0x12, -// invalid -// }; - -// [[nodiscard]] constexpr standard_request_types determine_standard_request( -// setup_packet pkt) -// { -// if (pkt.get_type() != setup_packet::type::standard || pkt.request == 0x04 -// || -// pkt.request > 0x12) { -// return standard_request_types::invalid; -// } - -// return static_cast(pkt.request); -// } - -constexpr setup_packet from_span(std::span raw_req) -{ - setup_packet pkt; - pkt.request_type = raw_req[0]; - pkt.request = raw_req[1]; - pkt.value = setup_packet::from_le_bytes(raw_req[2], raw_req[3]); - pkt.index = setup_packet::from_le_bytes(raw_req[4], raw_req[5]); - pkt.length = setup_packet::from_le_bytes(raw_req[6], raw_req[7]); - return pkt; -} - -} // namespace hal::v5::usb From a03f56dc2c8f8bd226913a1ec85f6e16095667ae Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 30 Mar 2026 00:19:06 -0700 Subject: [PATCH 6/6] :sparkles: (patch) Finalized usb interface/enumeration testing demo This demo is used to verify that a given usb system is working. It is intended to be used as the most basic possible usb program libhal has and to validate new platforms or USB library features --- demos/CMakeLists.txt | 6 +- demos/applications/outputtxt.txt | 0 demos/applications/usb_interface_testing.cpp | 78 ++++++++++---------- 3 files changed, 44 insertions(+), 40 deletions(-) delete mode 100644 demos/applications/outputtxt.txt diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 20c713bb..a751d529 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -34,9 +34,9 @@ libhal_build_demos( usb_interface_testing # stream_dac timer - usb_cdc_raw - usb_cdc - semihost + # usb_cdc_raw + # usb_cdc + #semihost PACKAGES minimp3 diff --git a/demos/applications/outputtxt.txt b/demos/applications/outputtxt.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/demos/applications/usb_interface_testing.cpp b/demos/applications/usb_interface_testing.cpp index 5beb4801..e944d28d 100644 --- a/demos/applications/usb_interface_testing.cpp +++ b/demos/applications/usb_interface_testing.cpp @@ -21,9 +21,8 @@ class dummy_interface : public interface : m_name(p_name) {}; private: - descriptor_count driver_write_descriptors( - descriptor_start p_start, - endpoint_writer const& p_callback) override + descriptor_count driver_write_descriptors(descriptor_start p_start, + endpoint_io& p_ep_req) override { if (p_start.interface.has_value()) { @@ -37,13 +36,12 @@ class dummy_interface : public interface auto scatter_arr = make_scatter_bytes(std::span(m_packed_array)); - p_callback(scatter_arr); + p_ep_req.write(scatter_arr); return { .interface = 1, .string = 1 }; } - bool driver_write_string_descriptor( - u8 p_index, - endpoint_writer const& p_callback) override + bool driver_write_string_descriptor(u8 p_index, + endpoint_io& p_ep_req) override { if (p_index != m_packed_array[8]) { return false; @@ -55,21 +53,21 @@ class dummy_interface : public interface hdr_array, std::span(reinterpret_cast(m_name.data()), m_name.length())); - p_callback(scatter_payload); + p_ep_req.write(scatter_payload); return true; } bool driver_handle_request(setup_packet const& p_setup, - endpoint_writer const& p_callback) override + endpoint_io& p_ep_req) override { std::ignore = p_setup; - std::ignore = p_callback; + std::ignore = p_ep_req; return true; } public: std::array m_packed_array = { - constants::inferface_desc_size, // size + constants::inferface_descriptor_size, // size static_cast(descriptor_type::interface), // desc type 0, // interface_number 0, // alternate_setting @@ -105,14 +103,14 @@ void application() auto dev = hal::v5::make_strong_ptr( pool, - usb::device::device_arguments{ .p_bcd_usb = 0x2, - .p_device_class = + usb::device::device_arguments{ .bcd_usb = 0x2, + .device_class = usb::class_code::use_interface_descriptor, - .p_device_subclass = 0, - .p_device_protocol = 0, - .p_id_vendor = 0x1234, - .p_id_product = 0x5678, - .p_bcd_device = 0x1, + .device_subclass = 0, + .device_protocol = 0, + .id_vendor = 0x1234, + .id_product = 0x5678, + .bcd_device = 0x1, .p_manufacturer = manu_str, .p_product = prod_str, .p_serial_number_str = sn_str }); @@ -127,12 +125,12 @@ void application() auto confs = hal::v5::make_strong_ptr>( pool, - std::array{ - usb::configuration{ conf_str, // yeet - usb::configuration::bitmap(false, false), - 1, - pool, - iface_ptr } }); + std::array{ usb::configuration{ + { .name = conf_str, + .attributes = usb::configuration::bitmap(false, false), + .max_power = 1, + .allocator = pool }, + iface_ptr } }); hal::print(*console, "conf array made\n"); @@ -140,16 +138,25 @@ void application() hal::u16 const en_lang_str = 0x0409; hal::print(*console, "ctrl ep called\n"); - + usb::enumerator<1> en({ + .ctrl_ep = ctrl_ep, + .device = dev, + .configs = confs, + .lang_str = en_lang_str, + .retry_max = 3, + }); try { - usb::enumerator<1> en(ctrl_ep, - dev, - confs, - en_lang_str, // f - 1, - false); - - en.enumerate(); + + hal::print(*console, "Enumerator made\n"); + + en.start_enumeration(); + hal::print(*console, "start_enumeration() ran\n"); + while (!en.is_enumerated()) { + en.process_ctrl_transfer(); + } + + hal::print(*console, "Enumerator made\n"); + hal::print(*console, "Enumeration complete\n"); } // Catch statements for specific hal exceptions catch (hal::no_such_device const& e) { @@ -231,11 +238,8 @@ void application() hal::print(*console, "what?\n"); } - hal::print(*console, "Enumerator made\n"); - - hal::print(*console, "Enumeration complete\n"); while (true) { - continue; + en.process_ctrl_transfer(); } // return; }