From ce7b663d19fa6c8b7c9c46a6679782b1671e34f6 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sun, 31 Aug 2025 02:02:44 -0700 Subject: [PATCH 01/18] :sparkles: feat(usb_enumeration): Made an enumerator For devices that use the libhal usb interface high level class. Handles configuration at the device and usb configuration level Handles multiple configurations and multiple interfaces. --- include/libhal-util/usb/descriptors.hpp | 262 +++++++++++++++ include/libhal-util/usb/endpoints.hpp | 123 +++++++ include/libhal-util/usb/enumerator.hpp | 406 ++++++++++++++++++++++++ include/libhal-util/usb/utils.hpp | 148 +++++++++ v5/include/libhal-util/usb.hpp | 14 - 5 files changed, 939 insertions(+), 14 deletions(-) create mode 100644 include/libhal-util/usb/descriptors.hpp create mode 100644 include/libhal-util/usb/endpoints.hpp create mode 100644 include/libhal-util/usb/enumerator.hpp create mode 100644 include/libhal-util/usb/utils.hpp diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp new file mode 100644 index 0000000..4c23079 --- /dev/null +++ b/include/libhal-util/usb/descriptors.hpp @@ -0,0 +1,262 @@ +#pragma once + +// TODO: Move to util +/* TODO: + Class, subclass, proto validator + Device qualifer descriptor + Other speed descriptor + Interface Association Descriptor + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../as_bytes.hpp" +#include "libhal-util/as_bytes.hpp" +#include "libhal/units.hpp" +#include "utils.hpp" +#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; // NOLINT + u8 p_device_protocol; + u16 p_id_vendor; // NOLINT + u16 p_id_product; + u16 p_bcd_device; + std::string_view p_manufacturer; // NOLINT + std::string_view p_product; + std::string_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, 2); + for (auto& bcd_usb_byte : bcd_usb_bytes) { + m_packed_arr[idx++] = bcd_usb_byte; + } + m_packed_arr[idx++] = args.p_bcd_usb; + m_packed_arr[idx++] = static_cast(args.p_device_class); + m_packed_arr[idx++] = args.p_device_subclass; + m_packed_arr[idx++] = args.p_device_protocol; + + m_packed_arr[idx++] = 0; // Max Packet length handled by the enumerator + auto id_vendor_bytes = hal::as_bytes(&args.p_id_vendor, 2); + for (auto& id_vendor_byte : id_vendor_bytes) { + m_packed_arr[idx++] = id_vendor_byte; + } + + auto id_product_bytes = hal::as_bytes(&args.p_id_product, 2); + for (auto& id_product_byte : id_product_bytes) { + m_packed_arr[idx++] = id_product_byte; + } + + auto bcd_device_bytes = hal::as_bytes(&args.p_bcd_device, 2); + for (auto& bcd_device_byte : bcd_device_bytes) { + m_packed_arr[idx++] = bcd_device_byte; + } + + // Evaluated during enumeration + m_packed_arr[idx++] = 0; // string idx of manufacturer + m_packed_arr[idx++] = 0; // string idx of product + m_packed_arr[idx++] = 0; // string idx of serial number + m_packed_arr[idx++] = 0; // Number of possible configurations + }; + + constexpr u16& bcd_usb() + { + return *reinterpret_cast(&m_packed_arr[0]); + } + + constexpr u8& device_class() + { + return m_packed_arr[1]; + } + + constexpr u8& device_sub_class() + { + return m_packed_arr[2]; + } + + constexpr u8& device_protocol() + { + return m_packed_arr[3]; + } + + constexpr u16& id_vendor() + { + return *reinterpret_cast(&m_packed_arr[5]); + } + + constexpr u16& id_product() + { + return *reinterpret_cast(&m_packed_arr[7]); + } + + constexpr u16& bcd_device() + { + return *reinterpret_cast(&m_packed_arr[9]); + } + + operator std::span() const + { + return m_packed_arr; + } + + std::string_view manufacturer_str; + std::string_view product_str; + std::string_view serial_number_str; + +private: + constexpr u8& max_packet_size() + { + return m_packed_arr[4]; + } + + 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::string_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; // Total Length + m_packed_arr[idx++] = interfaces.size(); // number of interfaces + m_packed_arr[idx++] = 0; // Config number + m_packed_arr[idx++] = 0; // Configuration name string index + + m_packed_arr[idx++] = p_attributes.to_byte(); + m_packed_arr[idx++] = p_max_power; + } + + 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::string_view name; + std::pmr::vector> interfaces; + +private: + constexpr 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/include/libhal-util/usb/endpoints.hpp b/include/libhal-util/usb/endpoints.hpp new file mode 100644 index 0000000..d65a49f --- /dev/null +++ b/include/libhal-util/usb/endpoints.hpp @@ -0,0 +1,123 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +namespace hal::v5 { +// TODO(#79): Add doxygen docs to USB APIs +inline void write(usb_control_endpoint& p_endpoint, + scatter_span p_data_out) +{ + p_endpoint.write(p_data_out); +} + +inline void write_and_flush(usb_control_endpoint& p_endpoint, + scatter_span p_data_out) +{ + p_endpoint.write(p_data_out); + p_endpoint.write({}); +} + +inline void write(usb_control_endpoint& p_endpoint, + std::span p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out)); +} + +inline void write_and_flush(usb_control_endpoint& p_endpoint, + std::span p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out)); + p_endpoint.write({}); +} + +inline void write(usb_in_endpoint& p_endpoint, + scatter_span p_data_out) +{ + p_endpoint.write(p_data_out); +} + +inline void write_and_flush(usb_in_endpoint& p_endpoint, + scatter_span p_data_out) +{ + p_endpoint.write(p_data_out); + p_endpoint.write({}); +} + +inline void write(usb_in_endpoint& p_endpoint, + std::span p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out)); +} + +inline void write_and_flush(usb_in_endpoint& p_endpoint, + std::span p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out)); + p_endpoint.write({}); +} + +inline void write(usb_in_endpoint& p_endpoint, + spanable_bytes auto... p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out...)); +} + +inline void write_and_flush(usb_in_endpoint& p_endpoint, + spanable_bytes auto... p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out...)); + p_endpoint.write({}); +} + +inline auto read(usb_out_endpoint& p_endpoint, + scatter_span p_data_out) +{ + return p_endpoint.read(p_data_out); +} + +inline auto read(usb_out_endpoint& p_endpoint, std::span p_data_out) +{ + return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); +} + +inline auto read(usb_out_endpoint& p_endpoint, + spanable_writable_bytes auto... p_data_out) +{ + return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); +} + +inline auto read(usb_control_endpoint& p_endpoint, + scatter_span p_data_out) +{ + return p_endpoint.read(p_data_out); +} + +inline auto read(usb_control_endpoint& p_endpoint, + std::span p_data_out) +{ + return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); +} + +inline auto read(usb_control_endpoint& p_endpoint, + spanable_writable_bytes auto... p_data_out) +{ + return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); +} +} // namespace hal::v5 diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp new file mode 100644 index 0000000..72d1d37 --- /dev/null +++ b/include/libhal-util/usb/enumerator.hpp @@ -0,0 +1,406 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "descriptors.hpp" +#include "utils.hpp" + +// TODO: move to util +namespace hal::v5::usb { + +template +class enumerator +{ + +public: + enumerator(strong_ptr const& p_ctrl_ep, + device&& p_device, + std::array&& p_configs, + std::string_view p_lang_str, + u8&& p_starting_str_idx) + : m_ctrl_ep(p_ctrl_ep) + , m_device(p_device) + , m_configs(p_configs) + , m_lang_str(p_lang_str) + { + // 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; + 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.id_product() = cur_str_idx++; + m_device.serial_number_index() = cur_str_idx++; + m_device.num_configurations() = num_configs; + + // Configurations + for (auto i = 0; i < num_configs; i++) { + configuration& config = m_configs[i]; + config.configuration_index() = cur_str_idx++; + config.configuration_value() = i; + } + + for (configuration& config : m_configs) { + auto total_length = static_cast(constants::config_desc_size); + for (auto const& iface : config.interfaces) { + auto deltas = iface->write_descriptors( + [&total_length](scatter_span p_data) { + total_length += p_data.size(); + }, + cur_iface_idx, + cur_str_idx); + + cur_iface_idx += deltas.iface_idxes; + cur_str_idx += deltas.str_idxes; + } + config.total_length() = total_length; + } + + // Phase two: Writing + + // TODO: Make async + bool finished_enumeration = false; + bool waiting_for_data = true; + + using on_receive_tag = usb_control_endpoint::on_receive_tag; + using standard_request_types = setup_request::standard_request_types; + m_ctrl_ep->on_receive( + [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); + m_ctrl_ep->connect(true); + 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 != constants::size_std_req) { + safe_throw(hal::message_size(this)); + } + setup_request req(raw_req); + + if (req.request_type.get_recipient() != + setup_request::bitmap::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::get_descriptor && + (static_cast((req.value & 0xFF << 8) >> 8) == + descriptor_type::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() + { + using std_req_type = setup_request::standard_request_types; + using req_bitmap = setup_request::bitmap; + + 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_request req(payload); + if (req.get_standard_request() == std_req_type::invalid) { + return; + } + + if (req.get_standard_request() == std_req_type::get_descriptor && + static_cast(req.value & 0xFF << 8) == + descriptor_type::string) { + handle_str_descriptors(req.value & 0xFF, req.length > 2); + + } else if (req.request_type.get_recipient() == + req_bitmap::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_request& req) + { + using standard_request_types = setup_request::standard_request_types; + + switch (req.get_standard_request()) { + case standard_request_types::set_address: { + m_ctrl_ep->set_address(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_request& req) + { + hal::byte desc_type = req.value & 0xFF << 8; + [[maybe_unused]] hal::byte desc_idx = req.value & 0xFF; + + switch (static_cast(desc_type)) { + case descriptor_type::device: { + auto scatter_arr = make_scatter_bytes( + std::to_array({ constants::device_desc_size, + static_cast(descriptor_type::device) }), + m_device); + m_ctrl_ep->write(static_cast>(scatter_arr)); + break; + } + + case descriptor_type::configuration: { + configuration& conf = m_configs[desc_idx]; + if (req.length <= 2) { // requesting total length + auto scatter_tot_len = + make_scatter_bytes(to_le_bytes(conf.total_length())); + m_ctrl_ep->write(scatter_tot_len); + break; + } + + // if its >2 then assumed to be requesting desc type + u16 total_size = constants::config_desc_size; + scatter_span scatter_conf_hdr = make_scatter_bytes( + { constants::config_desc_size, descriptor_type::configuration }, + m_configs[desc_idx]); + + m_ctrl_ep->write(scatter_conf_hdr); + + for (size_t i = 0; i < conf.interfaces.size(); i++) { + auto iface = conf.interfaces[i]; + iface->write_descriptors( + [this, &total_size, i](scatter_span byte_stream) { + m_ctrl_ep->write(byte_stream); + total_size += byte_stream.size(); + }, + i); + } + + if (total_size != req.length) { + safe_throw( + hal::exception(this)); // TODO: Make specific exception for this + } + + break; + } + + case descriptor_type::string: { + if (desc_idx == 0) { + auto scatter_arr = make_scatter_bytes( + std::to_array({ static_cast(m_lang_str.length()), + static_cast(descriptor_type::string) }), + std::span(m_lang_str)); + m_ctrl_ep->write(scatter_arr); + break; + } + handle_str_descriptors(desc_idx, req.length > 2); // 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, bool const write_full_desc) + { + + 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, write_full_desc); + 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 && write_full_desc) { + m_iface_for_str_desc->second->write_descriptors( + [this](scatter_span desc) { m_ctrl_ep->write(desc); }, + target_idx); + return; + } + + // Iterate through every interface now to find a match + auto f = [this, write_full_desc](scatter_span desc) { + if (write_full_desc) { + m_ctrl_ep->write(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); + m_ctrl_ep->write(scatter_str_hdr); + } + }; + + if (m_active_conf != nullptr) { + for (auto const& iface : m_active_conf->interfaces) { + auto res = iface->write_string_descriptor(f, target_idx); + if (res) { + return; + } + } + } + + for (configuration& conf : m_configs) { + for (auto const& iface : conf.interfaces) { + auto res = iface->write_string_descriptor(f, target_idx); + if (res) { + break; + } + } + } + } + + bool write_cfg_str_descriptor(u8 const target_idx, bool const write_full_desc) + { + constexpr u8 dev_manu_offset = 0; + constexpr u8 dev_prod_offset = 1; + constexpr u8 dev_sn_offset = 2; + std::optional conf_sv; + if (target_idx == (m_starting_str_idx + dev_manu_offset)) { + conf_sv = m_device.manufacturer_str; + + } else if (target_idx == (m_starting_str_idx + dev_prod_offset)) { + conf_sv = m_device.product_str; + + } else if (target_idx == (m_starting_str_idx + dev_sn_offset)) { + 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)) { + conf_sv = conf.name; + } + } + } + + if (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) + auto scatter_arr_str_hdr = make_scatter_bytes( + std::to_array({ static_cast(conf_sv->length()), + static_cast(descriptor_type::string) })); + m_ctrl_ep->write(scatter_arr_str_hdr); + + if (write_full_desc) { + // Hack to force scatter_span to accept string view + auto scatter_arr = make_scatter_bytes(std::span( + reinterpret_cast(conf_sv->data()), conf_sv->size())); + m_ctrl_ep->write(scatter_arr); + } + + return true; + } + + strong_ptr m_ctrl_ep; + device m_device; + std::array m_configs; + std::string_view 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; +}; +} // namespace hal::v5::usb diff --git a/include/libhal-util/usb/utils.hpp b/include/libhal-util/usb/utils.hpp new file mode 100644 index 0000000..0eeb8a3 --- /dev/null +++ b/include/libhal-util/usb/utils.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include +#include +#include +#include +#include + +// TODO: Move to util +namespace hal::v5::usb { + +// TODO: Make generic +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, +}; + +struct setup_request +{ + using bitmap = v5::usb_interface::req_bitmap; + + 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 + }; + + constexpr setup_request(bitmap p_request_type, + hal::byte p_request, // NOLINT + u16 p_value, + u16 p_index, + u16 p_length) + : request_type(p_request_type) + , request(p_request) + , value(p_value) + , index(p_index) + , length(p_length) {}; + + constexpr setup_request(std::span raw_req) + : request_type(raw_req[0]) + , request(raw_req[1]) + , value(from_le_bytes(raw_req[2], raw_req[3])) + , index(from_le_bytes(raw_req[4], raw_req[5])) + , length(from_le_bytes(raw_req[6], raw_req[7])) + + {}; + + // Probably better semantics for this function, this disjoints + [[nodiscard]] constexpr standard_request_types get_standard_request() const + { + if (request_type.get_type() != bitmap::type::standard || request == 0x04 || + request > 0x12) { + return standard_request_types::invalid; + } + + return static_cast(request); + } + + bitmap const request_type; + hal::byte const request; + u16 const value; + u16 const index; + u16 const length; +}; + +} // namespace hal::v5::usb diff --git a/v5/include/libhal-util/usb.hpp b/v5/include/libhal-util/usb.hpp index 0bfa259..fed9f7c 100644 --- a/v5/include/libhal-util/usb.hpp +++ b/v5/include/libhal-util/usb.hpp @@ -1,17 +1,3 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #pragma once #include From e655173da6d4693b6ce3956247a4c5d07ee338a5 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 1 Sep 2025 16:57:19 -0700 Subject: [PATCH 02/18] :label: Updated usb utils to use libhal 4.18.0+ APIs --- include/libhal-util/usb/descriptors.hpp | 58 ++++++------ include/libhal-util/usb/endpoints.hpp | 39 ++++---- include/libhal-util/usb/enumerator.hpp | 97 ++++++++++---------- include/libhal-util/usb/utils.hpp | 116 ++++++++++-------------- 4 files changed, 145 insertions(+), 165 deletions(-) diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp index 4c23079..bc4eb12 100644 --- a/include/libhal-util/usb/descriptors.hpp +++ b/include/libhal-util/usb/descriptors.hpp @@ -1,12 +1,10 @@ #pragma once -// TODO: Move to util /* TODO: Class, subclass, proto validator - Device qualifer descriptor - Other speed descriptor - Interface Association Descriptor - */ + Device qualifer descriptor (happens between device and config) + Other speed descriptor (happens with configuration) +*/ #include #include @@ -33,12 +31,12 @@ struct device { u16 p_bcd_usb; class_code p_device_class; - u8 p_device_subclass; // NOLINT + u8 p_device_subclass; u8 p_device_protocol; - u16 p_id_vendor; // NOLINT + u16 p_id_vendor; u16 p_id_product; u16 p_bcd_device; - std::string_view p_manufacturer; // NOLINT + std::string_view p_manufacturer; std::string_view p_product; std::string_view p_serial_number_str; }; @@ -49,36 +47,37 @@ struct device , serial_number_str(args.p_serial_number_str) { u8 idx = 0; - auto bcd_usb_bytes = hal::as_bytes(&args.p_bcd_usb, 2); + 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; + m_packed_arr[idx++] = bcd_usb_byte; // 0, 1 } - m_packed_arr[idx++] = args.p_bcd_usb; - m_packed_arr[idx++] = static_cast(args.p_device_class); - m_packed_arr[idx++] = args.p_device_subclass; - m_packed_arr[idx++] = args.p_device_protocol; - m_packed_arr[idx++] = 0; // Max Packet length handled by the enumerator - auto id_vendor_bytes = hal::as_bytes(&args.p_id_vendor, 2); + 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; + m_packed_arr[idx++] = id_vendor_byte; // 6, 7 } - auto id_product_bytes = hal::as_bytes(&args.p_id_product, 2); + 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; + m_packed_arr[idx++] = id_product_byte; // 8, 9 } - auto bcd_device_bytes = hal::as_bytes(&args.p_bcd_device, 2); + 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; + m_packed_arr[idx++] = bcd_device_byte; // 10, 11 } // Evaluated during enumeration - m_packed_arr[idx++] = 0; // string idx of manufacturer - m_packed_arr[idx++] = 0; // string idx of product - m_packed_arr[idx++] = 0; // string idx of serial number - m_packed_arr[idx++] = 0; // Number of possible configurations + 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 }; constexpr u16& bcd_usb() @@ -116,7 +115,7 @@ struct device return *reinterpret_cast(&m_packed_arr[9]); } - operator std::span() const + operator std::span() const { return m_packed_arr; } @@ -154,7 +153,7 @@ struct device // https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors template -concept usb_interface_concept = std::derived_from; +concept usb_interface_concept = std::derived_from; // Calculate: total length, number of interfaces, configuration value struct configuration @@ -209,7 +208,8 @@ struct configuration u8 idx = 0; // Anything marked with 0 is to be populated at enumeration time - m_packed_arr[idx++] = 0; // Total Length + m_packed_arr[idx] = 0; // Total Length + idx += 2; m_packed_arr[idx++] = interfaces.size(); // number of interfaces m_packed_arr[idx++] = 0; // Config number m_packed_arr[idx++] = 0; // Configuration name string index @@ -237,7 +237,7 @@ struct configuration } std::string_view name; - std::pmr::vector> interfaces; + std::pmr::vector> interfaces; private: constexpr u16& total_length() diff --git a/include/libhal-util/usb/endpoints.hpp b/include/libhal-util/usb/endpoints.hpp index d65a49f..77972f3 100644 --- a/include/libhal-util/usb/endpoints.hpp +++ b/include/libhal-util/usb/endpoints.hpp @@ -19,105 +19,102 @@ #include #include -namespace hal::v5 { +namespace hal::v5::usb { // TODO(#79): Add doxygen docs to USB APIs -inline void write(usb_control_endpoint& p_endpoint, +inline void write(control_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); } -inline void write_and_flush(usb_control_endpoint& p_endpoint, +inline void write_and_flush(control_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } -inline void write(usb_control_endpoint& p_endpoint, +inline void write(control_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } -inline void write_and_flush(usb_control_endpoint& p_endpoint, +inline void write_and_flush(control_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } -inline void write(usb_in_endpoint& p_endpoint, +inline void write(in_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); } -inline void write_and_flush(usb_in_endpoint& p_endpoint, +inline void write_and_flush(in_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } -inline void write(usb_in_endpoint& p_endpoint, +inline void write(in_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } -inline void write_and_flush(usb_in_endpoint& p_endpoint, +inline void write_and_flush(in_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } -inline void write(usb_in_endpoint& p_endpoint, - spanable_bytes auto... p_data_out) +inline void write(in_endpoint& p_endpoint, spanable_bytes auto... p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out...)); } -inline void write_and_flush(usb_in_endpoint& p_endpoint, +inline void write_and_flush(in_endpoint& p_endpoint, spanable_bytes auto... p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out...)); p_endpoint.write({}); } -inline auto read(usb_out_endpoint& p_endpoint, - scatter_span p_data_out) +inline auto read(out_endpoint& p_endpoint, scatter_span p_data_out) { return p_endpoint.read(p_data_out); } -inline auto read(usb_out_endpoint& p_endpoint, std::span p_data_out) +inline auto read(out_endpoint& p_endpoint, std::span p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); } -inline auto read(usb_out_endpoint& p_endpoint, +inline auto read(out_endpoint& p_endpoint, spanable_writable_bytes auto... p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); } -inline auto read(usb_control_endpoint& p_endpoint, +inline auto read(control_endpoint& p_endpoint, scatter_span p_data_out) { return p_endpoint.read(p_data_out); } -inline auto read(usb_control_endpoint& p_endpoint, - std::span p_data_out) +inline auto read(control_endpoint& p_endpoint, std::span p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); } -inline auto read(usb_control_endpoint& p_endpoint, +inline auto read(control_endpoint& p_endpoint, spanable_writable_bytes auto... p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); } -} // namespace hal::v5 +} // namespace hal::v5::usb diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index 72d1d37..25329ca 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -21,11 +21,12 @@ class enumerator { public: - enumerator(strong_ptr const& p_ctrl_ep, + enumerator(strong_ptr const& p_ctrl_ep, device&& p_device, std::array&& p_configs, std::string_view p_lang_str, - u8&& p_starting_str_idx) + u8&& p_starting_str_idx, + bool enumerate_immediately = true) : m_ctrl_ep(p_ctrl_ep) , m_device(p_device) , m_configs(p_configs) @@ -39,7 +40,10 @@ class enumerator safe_throw(hal::argument_out_of_domain(this)); } m_starting_str_idx = p_starting_str_idx; - enumerate(); + + if (enumerate_immediately) { + enumerate(); + } } void enumerate() @@ -61,7 +65,7 @@ class enumerator m_device.num_configurations() = num_configs; // Configurations - for (auto i = 0; i < num_configs; i++) { + for (size_t i = 0; i < num_configs; i++) { configuration& config = m_configs[i]; config.configuration_index() = cur_str_idx++; config.configuration_value() = i; @@ -70,15 +74,14 @@ class enumerator for (configuration& config : m_configs) { auto total_length = static_cast(constants::config_desc_size); for (auto const& iface : config.interfaces) { - auto deltas = iface->write_descriptors( + interface::descriptor_count deltas = iface->write_descriptors( + { .interface = cur_iface_idx, .string = cur_str_idx }, [&total_length](scatter_span p_data) { total_length += p_data.size(); - }, - cur_iface_idx, - cur_str_idx); + }); - cur_iface_idx += deltas.iface_idxes; - cur_str_idx += deltas.str_idxes; + cur_iface_idx += deltas.interface; + cur_str_idx += deltas.string; } config.total_length() = total_length; } @@ -89,8 +92,7 @@ class enumerator bool finished_enumeration = false; bool waiting_for_data = true; - using on_receive_tag = usb_control_endpoint::on_receive_tag; - using standard_request_types = setup_request::standard_request_types; + using on_receive_tag = control_endpoint::on_receive_tag; m_ctrl_ep->on_receive( [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); m_ctrl_ep->connect(true); @@ -106,12 +108,11 @@ class enumerator auto num_bytes_read = m_ctrl_ep->read(scatter_raw_req); if (num_bytes_read != constants::size_std_req) { - safe_throw(hal::message_size(this)); + safe_throw(hal::message_size(num_bytes_read, this)); } - setup_request req(raw_req); + auto req = from_span(raw_req); - if (req.request_type.get_recipient() != - setup_request::bitmap::recipient::device) { + if (req.get_recipient() != setup_packet::recipient::device) { safe_throw(hal::not_connected(this)); } @@ -140,9 +141,6 @@ class enumerator void resume_ctrl_transaction() { - using std_req_type = setup_request::standard_request_types; - using req_bitmap = setup_request::bitmap; - while (!m_has_setup_packet) { continue; } @@ -152,18 +150,18 @@ class enumerator auto bytes_read = m_ctrl_ep->read(scatter_read_buf); std::span payload(read_buf.data(), bytes_read); - setup_request req(payload); - if (req.get_standard_request() == std_req_type::invalid) { + setup_packet req = from_span(payload); + if (determine_standard_request(req) == standard_request_types::invalid) { return; } - if (req.get_standard_request() == std_req_type::get_descriptor && + if (determine_standard_request(req) == + standard_request_types::get_descriptor && static_cast(req.value & 0xFF << 8) == descriptor_type::string) { handle_str_descriptors(req.value & 0xFF, req.length > 2); - } else if (req.request_type.get_recipient() == - req_bitmap::recipient::device) { + } else if (req.get_recipient() == setup_packet::recipient::device) { handle_standard_device_request(req); } else { // Handle iface level requests @@ -188,11 +186,10 @@ class enumerator } private: - void handle_standard_device_request(setup_request& req) + void handle_standard_device_request(setup_packet& req) { - using standard_request_types = setup_request::standard_request_types; - switch (req.get_standard_request()) { + switch (determine_standard_request(req)) { case standard_request_types::set_address: { m_ctrl_ep->set_address(req.value); break; @@ -224,7 +221,7 @@ class enumerator } } - void process_get_descriptor(setup_request& req) + void process_get_descriptor(setup_packet& req) { hal::byte desc_type = req.value & 0xFF << 8; [[maybe_unused]] hal::byte desc_idx = req.value & 0xFF; @@ -243,32 +240,34 @@ class enumerator configuration& conf = m_configs[desc_idx]; if (req.length <= 2) { // requesting total length auto scatter_tot_len = - make_scatter_bytes(to_le_bytes(conf.total_length())); + make_scatter_bytes(setup_packet::to_le_bytes(conf.total_length())); m_ctrl_ep->write(scatter_tot_len); break; } // if its >2 then assumed to be requesting desc type u16 total_size = constants::config_desc_size; - scatter_span scatter_conf_hdr = make_scatter_bytes( - { constants::config_desc_size, descriptor_type::configuration }, + auto scatter_conf_hdr = make_scatter_bytes( + std::to_array({ constants::config_desc_size, + static_cast(descriptor_type::configuration) }), m_configs[desc_idx]); - m_ctrl_ep->write(scatter_conf_hdr); + m_ctrl_ep->write(scatter_span(scatter_conf_hdr)); for (size_t i = 0; i < conf.interfaces.size(); i++) { auto iface = conf.interfaces[i]; - iface->write_descriptors( - [this, &total_size, i](scatter_span byte_stream) { + byte i_byte = static_cast(i); + std::ignore = iface->write_descriptors( + { .interface = i_byte, .string = i_byte }, + [this, &total_size](scatter_span byte_stream) { m_ctrl_ep->write(byte_stream); total_size += byte_stream.size(); - }, - i); + }); } if (total_size != req.length) { - safe_throw( - hal::exception(this)); // TODO: Make specific exception for this + safe_throw(hal::operation_not_supported( + this)); // TODO: Make specific exception for this } break; @@ -279,7 +278,9 @@ class enumerator auto scatter_arr = make_scatter_bytes( std::to_array({ static_cast(m_lang_str.length()), static_cast(descriptor_type::string) }), - std::span(m_lang_str)); + std::span( + reinterpret_cast(m_lang_str.data()), + m_lang_str.size())); m_ctrl_ep->write(scatter_arr); break; } @@ -310,10 +311,12 @@ class enumerator if (m_iface_for_str_desc.has_value() && m_iface_for_str_desc->first == target_idx && write_full_desc) { - m_iface_for_str_desc->second->write_descriptors( - [this](scatter_span desc) { m_ctrl_ep->write(desc); }, - target_idx); - return; + bool success = m_iface_for_str_desc->second->write_string_descriptor( + target_idx, + [this](scatter_span desc) { m_ctrl_ep->write(desc); }); + if (success) { + return; + } } // Iterate through every interface now to find a match @@ -331,7 +334,7 @@ class enumerator if (m_active_conf != nullptr) { for (auto const& iface : m_active_conf->interfaces) { - auto res = iface->write_string_descriptor(f, target_idx); + auto res = iface->write_string_descriptor(target_idx, f); if (res) { return; } @@ -340,7 +343,7 @@ class enumerator for (configuration& conf : m_configs) { for (auto const& iface : conf.interfaces) { - auto res = iface->write_string_descriptor(f, target_idx); + auto res = iface->write_string_descriptor(target_idx, f); if (res) { break; } @@ -394,12 +397,12 @@ class enumerator return true; } - strong_ptr m_ctrl_ep; + strong_ptr m_ctrl_ep; device m_device; std::array m_configs; std::string_view m_lang_str; u8 m_starting_str_idx; - std::optional>> m_iface_for_str_desc; + std::optional>> m_iface_for_str_desc; configuration* m_active_conf = nullptr; bool m_has_setup_packet = false; }; diff --git a/include/libhal-util/usb/utils.hpp b/include/libhal-util/usb/utils.hpp index 0eeb8a3..a1e68c7 100644 --- a/include/libhal-util/usb/utils.hpp +++ b/include/libhal-util/usb/utils.hpp @@ -1,25 +1,23 @@ #pragma once -#include #include #include #include #include -// TODO: Move to util namespace hal::v5::usb { -// TODO: Make generic -u16 from_le_bytes(hal::byte& first, hal::byte& second) -{ - return static_cast(second) << 8 | first; -} +// 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) }; -} +// std::array to_le_bytes(u16 n) +// { +// return { static_cast(n & 0xFF), +// static_cast((n & 0xFF << 8) >> 8) }; +// } namespace constants { @@ -87,62 +85,44 @@ enum class descriptor_type : hal::byte superspeed_endpoint_isochronous_companion = 0x31, }; -struct setup_request +// 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) { - using bitmap = v5::usb_interface::req_bitmap; - - 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 - }; - - constexpr setup_request(bitmap p_request_type, - hal::byte p_request, // NOLINT - u16 p_value, - u16 p_index, - u16 p_length) - : request_type(p_request_type) - , request(p_request) - , value(p_value) - , index(p_index) - , length(p_length) {}; - - constexpr setup_request(std::span raw_req) - : request_type(raw_req[0]) - , request(raw_req[1]) - , value(from_le_bytes(raw_req[2], raw_req[3])) - , index(from_le_bytes(raw_req[4], raw_req[5])) - , length(from_le_bytes(raw_req[6], raw_req[7])) - - {}; - - // Probably better semantics for this function, this disjoints - [[nodiscard]] constexpr standard_request_types get_standard_request() const - { - if (request_type.get_type() != bitmap::type::standard || request == 0x04 || - request > 0x12) { - return standard_request_types::invalid; - } - - return static_cast(request); - } - - bitmap const request_type; - hal::byte const request; - u16 const value; - u16 const index; - u16 const length; -}; + 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 4cf652b851dc0e5fd1a6175c8316f903cc8e2e22 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sat, 6 Sep 2025 22:03:46 -0700 Subject: [PATCH 03/18] :white_check_mark: Tested enumerator Simulated host by sending enumerator requests and having it respond. Correct payloads were verified --- include/libhal-util/usb/descriptors.hpp | 35 +- include/libhal-util/usb/enumerator.hpp | 61 +- v5/tests/usb.test.cpp | 757 +++++++++++++++++++++++- 3 files changed, 810 insertions(+), 43 deletions(-) diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp index bc4eb12..23c89c8 100644 --- a/include/libhal-util/usb/descriptors.hpp +++ b/include/libhal-util/usb/descriptors.hpp @@ -87,32 +87,32 @@ struct device constexpr u8& device_class() { - return m_packed_arr[1]; + return m_packed_arr[2]; } constexpr u8& device_sub_class() { - return m_packed_arr[2]; + return m_packed_arr[3]; } constexpr u8& device_protocol() { - return m_packed_arr[3]; + return m_packed_arr[4]; } constexpr u16& id_vendor() { - return *reinterpret_cast(&m_packed_arr[5]); + return *reinterpret_cast(&m_packed_arr[6]); } constexpr u16& id_product() { - return *reinterpret_cast(&m_packed_arr[7]); + return *reinterpret_cast(&m_packed_arr[8]); } constexpr u16& bcd_device() { - return *reinterpret_cast(&m_packed_arr[9]); + return *reinterpret_cast(&m_packed_arr[10]); } operator std::span() const @@ -127,7 +127,7 @@ struct device private: constexpr u8& max_packet_size() { - return m_packed_arr[4]; + return m_packed_arr[5]; } constexpr u8& manufacturer_index() @@ -172,7 +172,7 @@ struct configuration constexpr bitmap(bool p_self_powered, bool p_remote_wakeup) { - m_bitmap = (1 << 7) & (p_self_powered << 6) & (p_remote_wakeup << 5); + m_bitmap = (1 << 7) | (p_self_powered << 6) | (p_remote_wakeup << 5); } constexpr u8 to_byte() @@ -208,14 +208,14 @@ struct configuration u8 idx = 0; // Anything marked with 0 is to be populated at enumeration time - m_packed_arr[idx] = 0; // Total Length - idx += 2; - m_packed_arr[idx++] = interfaces.size(); // number of interfaces - m_packed_arr[idx++] = 0; // Config number - m_packed_arr[idx++] = 0; // Configuration name string index - - m_packed_arr[idx++] = p_attributes.to_byte(); - m_packed_arr[idx++] = p_max_power; + 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 @@ -231,6 +231,7 @@ struct configuration { return m_packed_arr[5]; } + constexpr u8& max_power() { return m_packed_arr[6]; @@ -239,7 +240,7 @@ struct configuration std::string_view name; std::pmr::vector> interfaces; -private: + // private: constexpr u16& total_length() { return *reinterpret_cast(&m_packed_arr[0]); diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index 25329ca..39bf3af 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -16,6 +16,17 @@ // TODO: move to util namespace hal::v5::usb { +template +size_t scatter_span_size(scatter_span ss) +{ + size_t res = 0; + for (auto const& s : ss) { + res += s.size(); + } + + return res; +} + template class enumerator { @@ -60,7 +71,7 @@ class enumerator // Device m_device.manufacturer_index() = cur_str_idx++; - m_device.id_product() = cur_str_idx++; + m_device.product_index() = cur_str_idx++; m_device.serial_number_index() = cur_str_idx++; m_device.num_configurations() = num_configs; @@ -77,7 +88,7 @@ class enumerator interface::descriptor_count deltas = iface->write_descriptors( { .interface = cur_iface_idx, .string = cur_str_idx }, [&total_length](scatter_span p_data) { - total_length += p_data.size(); + total_length += scatter_span_size(p_data); }); cur_iface_idx += deltas.interface; @@ -120,9 +131,7 @@ class enumerator handle_standard_device_request(req); m_ctrl_ep->write({}); // Send ZLP to complete Data Transaction if (static_cast(req.request) == - standard_request_types::get_descriptor && - (static_cast((req.value & 0xFF << 8) >> 8) == - descriptor_type::configuration)) { + standard_request_types::set_configuration) { finished_enumeration = true; m_ctrl_ep->on_receive( [this](on_receive_tag) { m_has_setup_packet = true; }); @@ -157,7 +166,7 @@ class enumerator if (determine_standard_request(req) == standard_request_types::get_descriptor && - static_cast(req.value & 0xFF << 8) == + static_cast((req.value & 0xFF << 8) >> 8) == descriptor_type::string) { handle_str_descriptors(req.value & 0xFF, req.length > 2); @@ -223,15 +232,17 @@ class enumerator void process_get_descriptor(setup_packet& req) { - hal::byte desc_type = req.value & 0xFF << 8; + 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 scatter_arr = make_scatter_bytes( + auto header = std::to_array({ constants::device_desc_size, - static_cast(descriptor_type::device) }), - m_device); + static_cast(descriptor_type::device) }); + m_device.max_packet_size() = + static_cast(m_ctrl_ep->info().size); + auto scatter_arr = make_scatter_bytes(header, m_device); m_ctrl_ep->write(static_cast>(scatter_arr)); break; } @@ -239,29 +250,28 @@ class enumerator case descriptor_type::configuration: { configuration& conf = m_configs[desc_idx]; if (req.length <= 2) { // requesting total length - auto scatter_tot_len = - make_scatter_bytes(setup_packet::to_le_bytes(conf.total_length())); + auto tl = setup_packet::to_le_bytes(conf.total_length()); + auto scatter_tot_len = make_scatter_bytes(tl); m_ctrl_ep->write(scatter_tot_len); break; } // if its >2 then assumed to be requesting desc type u16 total_size = constants::config_desc_size; - auto scatter_conf_hdr = make_scatter_bytes( + auto conf_hdr = std::to_array({ constants::config_desc_size, - static_cast(descriptor_type::configuration) }), - m_configs[desc_idx]); + static_cast(descriptor_type::configuration) }); + auto scatter_conf_hdr = + make_scatter_bytes(conf_hdr, m_configs[desc_idx]); m_ctrl_ep->write(scatter_span(scatter_conf_hdr)); - for (size_t i = 0; i < conf.interfaces.size(); i++) { - auto iface = conf.interfaces[i]; - byte i_byte = static_cast(i); + for (auto const& iface : conf.interfaces) { std::ignore = iface->write_descriptors( - { .interface = i_byte, .string = i_byte }, + { .interface = std::nullopt, .string = std::nullopt }, [this, &total_size](scatter_span byte_stream) { m_ctrl_ep->write(byte_stream); - total_size += byte_stream.size(); + total_size += scatter_span_size(byte_stream); }); } @@ -275,9 +285,11 @@ class enumerator case descriptor_type::string: { if (desc_idx == 0) { - auto scatter_arr = make_scatter_bytes( + auto s_hdr = std::to_array({ static_cast(m_lang_str.length()), - static_cast(descriptor_type::string) }), + static_cast(descriptor_type::string) }); + auto scatter_arr = make_scatter_bytes( + s_hdr, std::span( reinterpret_cast(m_lang_str.data()), m_lang_str.size())); @@ -382,9 +394,10 @@ class enumerator // 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) - auto scatter_arr_str_hdr = make_scatter_bytes( + auto hdr_arr = std::to_array({ static_cast(conf_sv->length()), - static_cast(descriptor_type::string) })); + static_cast(descriptor_type::string) }); + auto scatter_arr_str_hdr = make_scatter_bytes(hdr_arr); m_ctrl_ep->write(scatter_arr_str_hdr); if (write_full_desc) { diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index 5206c46..69fa5d1 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -12,12 +12,765 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "libhal-util/usb/descriptors.hpp" +#include "libhal-util/usb/utils.hpp" +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace hal { +namespace hal::v5::usb { +namespace { + +// constexpr setup_packet set_addr_master{ false, +// setup_packet::type::standard, +// setup_packet::recipient::device, +// static_cast( +// standard_request_types::set_address), +// 0x30, +// 0, // a +// 0 }; + +constexpr u8 iface_desc_length = 9; +constexpr u8 iface_desc_type = 0x4; +constexpr u8 str_desc_type = 0x3; +constexpr u8 iad_length = 0x08; +constexpr u8 iad_type = 0x0B; + +template +size_t scatter_span_size(scatter_span ss) +{ + size_t res = 0; + for (auto const& s : ss) { + res += s.size(); + } + + return res; +} + +template +bool span_eq(std::span const lhs, std::span const rhs) +{ + if (lhs.size() != rhs.size()) { + return false; + } + + for (size_t i = 0; i < lhs.size(); i++) { + if (lhs[i] != rhs[i]) { + return false; + } + } + return true; +} + +template +bool span_ne(std::span const lhs, std::span const rhs) +{ + return !(lhs == rhs); +} + +constexpr std::vector pkt_to_scatter(setup_packet const& req) +{ + std::vector vec; + vec.push_back(req.request_type); + vec.push_back(req.request); + vec.append_range(setup_packet::to_le_bytes(req.value)); + vec.append_range(setup_packet::to_le_bytes(req.index)); + vec.append_range(setup_packet::to_le_bytes(req.length)); + + return vec; +} + +class mock_usb_endpoint : public usb::endpoint +{ +public: + usb::endpoint_info m_info{}; + bool m_stall_called{ false }; + bool m_should_stall{ false }; + bool m_reset_called{ false }; + +protected: + [[nodiscard]] usb::endpoint_info driver_info() const override + { + return m_info; + } + + void driver_stall(bool p_should_stall) override + { + m_stall_called = true; + m_should_stall = p_should_stall; + } + + void driver_reset() override + { + m_reset_called = true; + } +}; + +class mock_usb_control_endpoint : public control_endpoint +{ +public: + mock_usb_endpoint m_endpoint; + bool m_is_connected{ false }; + u8 m_address{ 0 }; + std::vector m_out_buf; + std::array m_req_buf; + usize m_read_result{ 0 }; + callback m_receive_callback{}; + + void write_request(scatter_span p_data) + { + size_t total_len = 0; + for (std::span const& s : p_data) { + for (byte const el : s) { + m_req_buf[total_len] = el; + total_len++; + } + } + } + + void simulate_interrupt() + { + m_receive_callback(on_receive_tag{}); + } + +private: + [[nodiscard]] endpoint_info driver_info() const override + { + return m_endpoint.info(); + } + + void driver_stall(bool p_should_stall) override + { + m_endpoint.stall(p_should_stall); + } + + void driver_connect(bool p_should_connect) override + { + m_is_connected = p_should_connect; + } + + void driver_set_address(u8 p_address) override + { + m_address = p_address; + } + + void driver_write(scatter_span p_data) override + { + // Ignore ZLPs + if (p_data.empty() || + (scatter_span_size(p_data) == 1 && p_data[0][0] == 0)) { + return; + } + for (std::span const& s : p_data) { + for (byte const el : s) { + m_out_buf.push_back(el); + } + } + } + + usize driver_read(scatter_span p_buffer) override + { + size_t src_idx = 0; + for (auto s : p_buffer) { + for (unsigned char& el : s) { + el = m_req_buf[src_idx]; + src_idx++; + } + } + + return src_idx; + } + + void driver_on_receive( + callback const& p_callback) override + { + m_receive_callback = p_callback; + } + + void driver_reset() override + { + m_endpoint.reset(); + } +}; + +struct mock : public interface +{ + + constexpr mock(std::string_view p_name) + : name(p_name) + { + } + + [[nodiscard]] descriptor_count driver_write_descriptors( + descriptor_start p_start, + endpoint_writer const& p_callback) override + { + byte res_iface_idx = 0; + if (p_start.interface.has_value()) { + interface_number() = p_start.interface.value(); + res_iface_idx = 1; + } + + byte res_str_idx = 0; + if (p_start.string.has_value()) { + interface_name_string_idx() = p_start.string.value(); + res_str_idx = 1; + } + + p_callback(make_scatter_bytes(m_packed_array)); + + return { .interface = res_iface_idx, .string = res_str_idx }; + } + + [[nodiscard]] bool driver_write_string_descriptor( + u8 p_index, + endpoint_writer const& p_callback) override + { + if (p_index != interface_name_string_idx()) { + return false; + } + std::array str_hdr( + { static_cast(descriptor_type::string), + static_cast(name.length()) }); + + auto scatter_arr = make_scatter_bytes( + str_hdr, + std::span(reinterpret_cast(name.data()), name.length())); + + p_callback(scatter_span(scatter_arr)); + + 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; + } + + constexpr byte& interface_number() + { + return m_packed_array[2]; + } + + constexpr byte& alt_settings() + { + return m_packed_array[3]; + } + + constexpr byte& interface_name_string_idx() + { + return m_packed_array[8]; + } + + std::array m_packed_array = { + iface_desc_length, + iface_desc_type, + 0, // interface_number + 0, // alternate_setting + 1, // num_endpoints + 2, // interface_class + 3, // interface_sub_class + 4, // interface_protocol + 0 // interface name index + }; + std::string_view name; +}; + +class iad_mock : public interface +{ +public: + ~iad_mock() override = default; + + iad_mock(std::string_view p_iface_name_one, // NOLINT + std::string_view p_iface_name_two) + : m_name_one(p_iface_name_one) + , m_name_two(p_iface_name_two) {}; + + struct mock_iface_descriptor + { + byte num; + byte alt_settings; + byte str_idx; + bool feature; + }; + +private: + [[nodiscard]] descriptor_count driver_write_descriptors( + descriptor_start p_start, + endpoint_writer const& p_callback) override + { + if (!p_start.interface.has_value() || !p_start.string.has_value()) { + throw hal::argument_out_of_domain(this); + } + + auto iface_idx = p_start.interface.value(); + auto str_idx = p_start.string.value(); + std::array iad_buf{ + iad_length, iad_type, + 0, // first interface + 2, // iface count + 0, // class + 0, // subclass + 0, // proto + str_idx++ // string idx + }; + + std::array iface_header = { iface_desc_length, + iface_desc_type }; + std::array static_desc_vars = { + 0, // altsettings + 1, // num endpoints + 0, // class + 0, // subclass + 0, // protocol + }; + + m_iface_one = { .num = iface_idx++, + .alt_settings = 0, + .str_idx = str_idx++, + .feature = false }; + m_iface_two = { .num = iface_idx++, + .alt_settings = 0, + .str_idx = str_idx++, + .feature = false }; + + std::array iface_one_arr({ m_iface_one.num }); + std::array iface_one_str_arr({ m_iface_one.str_idx }); + + std::array iface_two_arr({ m_iface_two.num }); + std::array iface_two_str_arr({ m_iface_two.str_idx }); + + auto span_arr = make_scatter_bytes(iad_buf, + + // iface one + iface_header, + std::span(iface_one_arr), + static_desc_vars, + std::span(iface_one_str_arr), + + // iface two + iface_header, + std::span(iface_two_arr), + static_desc_vars, + std::span(iface_two_str_arr)); + + p_callback(span_arr); + m_wrote_descriptors = true; + return { .interface = 2, .string = 3 }; + } + + [[nodiscard]] bool driver_write_string_descriptor( + u8 p_index, + endpoint_writer const& p_callback) override + { + if (!m_wrote_descriptors) { + safe_throw(hal::operation_not_permitted(this)); + } + std::array header{ 0, str_desc_type }; + if (p_index == m_iface_one.str_idx) { + header[0] = m_name_one.length() + 2; + auto arr = make_scatter_bytes( + header, + std::span(reinterpret_cast(m_name_one.data()), + m_name_one.length())); + p_callback(arr); + return true; + } else if (p_index == m_iface_two.str_idx) { + header[0] = m_iface_two.str_idx + 2; + auto arr = make_scatter_bytes( + header, + std::span(reinterpret_cast(m_name_two.data()), + m_name_two.length())); + + p_callback(arr); + return true; + } + + return false; + } + + u16 driver_get_status(setup_packet p_pkt) + { + if (p_pkt.get_recipient() != setup_packet::recipient::interface) { + // std::println("Unsupported recipient"); + safe_throw(hal::operation_not_supported(this)); + } + + auto iface_idx = p_pkt.index & 0xFF; + + if (iface_idx == m_iface_one.num) { + return m_iface_one.num; + } else if (iface_idx == m_iface_two.num) { + return m_iface_two.num; + } + + safe_throw(hal::operation_not_supported(this)); + } + + void manage_features(setup_packet p_pkt, bool p_set, u16 p_selector) + { + std::ignore = p_selector; + if (p_pkt.get_recipient() != setup_packet::recipient::interface) { + // std::println("Unsupported recipient"); + safe_throw(hal::operation_not_supported(this)); + } + + auto iface_idx = p_pkt.index & 0xFF; + + if (iface_idx == m_iface_one.num) { + m_iface_one.feature = p_set; + } else if (iface_idx == m_iface_two.num) { + m_iface_two.feature = p_set; + } else { + // std::println("Invalid interface index"); + safe_throw(hal::operation_not_supported(this)); + } + } + + u8 driver_get_interface(setup_packet p_pkt) + { + auto iface_idx = p_pkt.index & 0xFF; + + if (iface_idx == m_iface_one.num) { + return m_iface_one.alt_settings; + } else if (iface_idx == m_iface_two.num) { + return m_iface_two.alt_settings; + } else { + // std::println("Invalid interface index"); + safe_throw(hal::operation_not_supported(this)); + } + } + + void driver_set_interface(setup_packet p_pkt) + { + auto iface_idx = p_pkt.index & 0xFF; + auto alt_setting = p_pkt.value; + if (iface_idx == m_iface_one.num) { + m_iface_one.alt_settings = alt_setting; + } else if (iface_idx == m_iface_two.num) { + m_iface_two.alt_settings = alt_setting; + } else { + // std::println("Invalid interface index"); + safe_throw(hal::operation_not_supported(this)); + } + } + + bool driver_handle_request(setup_packet const& p_setup, + endpoint_writer const& p_callback) override + { + std::ignore = p_setup; + std::ignore = p_callback; + return false; + } + +public: + mock_iface_descriptor m_iface_one; + std::string_view m_name_one; + mock_iface_descriptor m_iface_two; + std::string_view m_name_two; + bool m_wrote_descriptors = false; +}; + +void simulate_sending_payload( + strong_ptr const& ctrl_ptr, + setup_packet& req) +{ + auto vec = pkt_to_scatter(req); + auto scatter_arr = make_scatter_bytes(vec); + scatter_span ss(scatter_arr); + + ctrl_ptr->write_request(ss); +} + +// u8 calculate_conf_desc_recursive(std::span p_conf_arr) +// { +// u8 total_len = 0; +// for (configuration& conf : p_conf_arr) { +// total_len += 9; +// for (auto& iface : conf.interfaces) { +// auto real_iface = dynamic_cast(&(*iface)); +// std::ignore = iface->write_descriptors( +// { .interface = 0, .string = real_iface->interface_name_string_idx() +// }, +// [&total_len](scatter_span p_data) { +// total_len += scatter_span_size(p_data); +// }); +// } +// } + +// return total_len; +// } + +// std::vector generate_conf_descriptors(std::span +// p_conf_arr) +// { +// std::vector vec; +// for (configuration const& conf : p_conf_arr) { +// vec.append_range(std::span(conf)); +// for (auto const& iface : conf.interfaces) { +// std::ignore = +// iface->write_descriptors({ .interface = 0, .string = 0 }, +// [&vec](scatter_span p_dat) { +// for (auto const& s : p_dat) { +// for (auto const& el : s) { +// vec.push_back(el); +// } +// } +// }); +// } +// } + +// return vec; +// } + +} // namespace boost::ut::suite<"usb_test"> usb_test = [] { // TODO(#78): Add usb utility tests }; -} // namespace hal + +boost::ut::suite<"enumeration_test"> enumeration_test = [] { + using namespace boost::ut; + using namespace hal::literals; + namespace pmr = std::pmr; + static std::array iface_buf; + + iface_buf.fill(0); + device dev({ .p_bcd_usb = 0x0002, + .p_device_class = class_code::application_specific, + .p_device_subclass = 0, + .p_device_protocol = 0, + .p_id_vendor = 0xffff, + .p_id_product = 0x0000, + .p_bcd_device = 0x0001, + .p_manufacturer = "libhal", + .p_product = "Unit Test", + .p_serial_number_str = "123456789" }); + pmr::monotonic_buffer_resource pool(iface_buf.data(), std::size(iface_buf)); + std::array conf_arr{ configuration{ + "Test Config", + configuration::bitmap(true, false), + 1, + &pool, + make_strong_ptr(&pool, mock("Mock Iface")) } }; + + mock_usb_control_endpoint ctrl_ep; + ctrl_ep.m_endpoint.m_info = { .size = 8, .number = 0, .stalled = false }; + auto ctrl_ptr = make_strong_ptr(&pool, ctrl_ep); + enumerator<1> en{ + ctrl_ptr, device(dev), std::array(conf_arr), "LANG", 1, + false // NOLINT + }; + + "basic usb enumeration test"_test = [&en, &ctrl_ptr] { + // Start enumeration process and verify connection + auto f = [&en]() { en.enumerate(); }; + constexpr byte delay_time_ms = 1000; + auto& ctrl_buf = ctrl_ptr->m_out_buf; + std::thread ejh(f); + std::this_thread::sleep_for(std::chrono::milliseconds( + delay_time_ms)); // Should be enough time to connect + expect(that % true == ctrl_ptr->m_is_connected); + ctrl_buf.clear(); + + u16 expected_addr = 0x30; + setup_packet set_addr{ false, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast( + standard_request_types::set_address), + 0x30, + 0, // a + 0 }; + + simulate_sending_payload(ctrl_ptr, set_addr); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + expect(that % expected_addr == ctrl_ptr->m_address); + ctrl_buf.clear(); + + // Get device descriptor + u16 desc_t_idx = static_cast(descriptor_type::device) << 8; + setup_packet get_desc( + true, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast(standard_request_types::get_descriptor), + desc_t_idx, + 0, + 18); + simulate_sending_payload(ctrl_ptr, get_desc); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + std::span dev_actual(ctrl_buf.data(), 18); + std::array dev_expected{ + 0x12, // length + static_cast(descriptor_type::device), // type + 0x02, // usb bcd + 0x00, + static_cast(class_code::application_specific), + 0, // subclass + 0, // protocol + 8, // max packet size + 0xff, // vendor id + 0xff, + 0, // product id + 0, + 0x1, // bcd device + 0x0, + 1, // manufactures index + 2, // product index + 3, // product index + 1 // num configuration + }; + + expect(that % (span_eq(std::span(dev_expected), dev_actual))); + ctrl_buf.clear(); + + // Get a string descriptor header from device + // where 1 is the manufacture string index + constexpr u16 str_desc_t_idx = + static_cast(descriptor_type::string) << 8 | 1; + setup_packet str_hdr_req( + true, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast(standard_request_types::get_descriptor), + str_desc_t_idx, + 0, + 2); + + simulate_sending_payload(ctrl_ptr, str_hdr_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + std::array expected_str_hdr{ + static_cast(6), // string is "libhal" + static_cast(descriptor_type::string) + }; + std::span actual_dev_str_hdr(ctrl_buf.data(), 2); + expect(that % (span_eq(std::span(expected_str_hdr), + actual_dev_str_hdr))); + ctrl_buf.clear(); + + // Get a string descriptor from device + setup_packet str_req(str_hdr_req); + str_req.length = expected_str_hdr[0]; + simulate_sending_payload(ctrl_ptr, str_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + std::string_view expected_manu_str = "libhal"; + std::array manu_str_hdr( + { static_cast(expected_manu_str.length()), + static_cast(descriptor_type::string) }); + auto expected_manu_str_scatter = + make_scatter_bytes(manu_str_hdr, + std::span(reinterpret_cast( + expected_manu_str.data()), + expected_manu_str.length())); + auto actual_manu_str_scatter = make_scatter_bytes(ctrl_buf); + expect(that % (scatter_span(expected_manu_str_scatter) == + scatter_span(actual_manu_str_scatter))); + ctrl_buf.clear(); + + // Get Configuration length + setup_packet conf_hdr_req( + true, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast(standard_request_types::get_descriptor), + static_cast(descriptor_type::configuration) << 8, + 0, + 2); + + // Expected Config + interface descriptor + std::array expected_conf_iface_desc{ + // config descriptor + 0x9, // len + static_cast(descriptor_type::configuration), // type + 0x12, // TODO: total length + 0x0, // tl + 0x1, // number of interfaces + 0x0, // config value + 0x4, // name string index + 0xc0, // bmattributes (selfpowered = true) + 0x1, // max power + + // Interface descriptor + iface_desc_length, + iface_desc_type, + 0x0, // iface number + 0x0, // alt settings + 0x1, // number of endpoints + 0x2, // class + 0x3, // subclass + 0x4, // protocol + 0x5 // iface name string index + }; + + simulate_sending_payload(ctrl_ptr, conf_hdr_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + + auto expected_tl_hh = expected_conf_iface_desc[2]; + auto expected_tl_ll = expected_conf_iface_desc[3]; + auto expected_total_len = + setup_packet::from_le_bytes(expected_tl_hh, expected_tl_ll); + + auto actual_tl_hh = ctrl_buf[0]; + auto actual_tl_ll = ctrl_buf[1]; + auto actual_total_len = + setup_packet::from_le_bytes(actual_tl_hh, actual_tl_ll); + expect(that % expected_total_len == actual_total_len); + ctrl_buf.clear(); + + // Get Configuration Descriptor + interface descriptor + endpoint descriptor + setup_packet conf_req(conf_hdr_req); + conf_req.length = expected_total_len; + simulate_sending_payload(ctrl_ptr, conf_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + expect(that % (span_eq(std::span(expected_conf_iface_desc), + std::span(ctrl_buf)))); + + // Set configuration + setup_packet set_conf_req( + false, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast(standard_request_types::set_configuration), + 0, + 0, + 0); + + simulate_sending_payload(ctrl_ptr, set_conf_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + + ejh.join(); + // Verify active config + expect(that % + (span_eq(std::span(expected_conf_iface_desc.data() + 2, 7), + std::span(en.get_active_configuration())))); + }; +}; + +} // namespace hal::v5::usb From 767b30525458df12f7a647abfcf71945606101ff Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 8 Sep 2025 01:59:08 -0700 Subject: [PATCH 04/18] :recycle: Moved endpoint utils to their own file Changed usb.hpp to be a shorthand for including all usb utils --- include/libhal-util/usb/endpoints.hpp | 39 ++++----- v5/include/libhal-util/usb.hpp | 111 +------------------------- 2 files changed, 25 insertions(+), 125 deletions(-) diff --git a/include/libhal-util/usb/endpoints.hpp b/include/libhal-util/usb/endpoints.hpp index 77972f3..0bfa259 100644 --- a/include/libhal-util/usb/endpoints.hpp +++ b/include/libhal-util/usb/endpoints.hpp @@ -19,102 +19,105 @@ #include #include -namespace hal::v5::usb { +namespace hal::v5 { // TODO(#79): Add doxygen docs to USB APIs -inline void write(control_endpoint& p_endpoint, +inline void write(usb::control_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); } -inline void write_and_flush(control_endpoint& p_endpoint, +inline void write_and_flush(usb::control_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } -inline void write(control_endpoint& p_endpoint, +inline void write(usb::control_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } -inline void write_and_flush(control_endpoint& p_endpoint, +inline void write_and_flush(usb::control_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } -inline void write(in_endpoint& p_endpoint, +inline void write(usb::in_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); } -inline void write_and_flush(in_endpoint& p_endpoint, +inline void write_and_flush(usb::in_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } -inline void write(in_endpoint& p_endpoint, +inline void write(usb::in_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } -inline void write_and_flush(in_endpoint& p_endpoint, +inline void write_and_flush(usb::in_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } -inline void write(in_endpoint& p_endpoint, spanable_bytes auto... p_data_out) +inline void write(usb::in_endpoint& p_endpoint, + spanable_bytes auto... p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out...)); } -inline void write_and_flush(in_endpoint& p_endpoint, +inline void write_and_flush(usb::in_endpoint& p_endpoint, spanable_bytes auto... p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out...)); p_endpoint.write({}); } -inline auto read(out_endpoint& p_endpoint, scatter_span p_data_out) +inline auto read(usb::out_endpoint& p_endpoint, + scatter_span p_data_out) { return p_endpoint.read(p_data_out); } -inline auto read(out_endpoint& p_endpoint, std::span p_data_out) +inline auto read(usb::out_endpoint& p_endpoint, std::span p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); } -inline auto read(out_endpoint& p_endpoint, +inline auto read(usb::out_endpoint& p_endpoint, spanable_writable_bytes auto... p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); } -inline auto read(control_endpoint& p_endpoint, +inline auto read(usb::control_endpoint& p_endpoint, scatter_span p_data_out) { return p_endpoint.read(p_data_out); } -inline auto read(control_endpoint& p_endpoint, std::span p_data_out) +inline auto read(usb::control_endpoint& p_endpoint, + std::span p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); } -inline auto read(control_endpoint& p_endpoint, +inline auto read(usb::control_endpoint& p_endpoint, spanable_writable_bytes auto... p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); } -} // namespace hal::v5::usb +} // namespace hal::v5 diff --git a/v5/include/libhal-util/usb.hpp b/v5/include/libhal-util/usb.hpp index fed9f7c..0764c04 100644 --- a/v5/include/libhal-util/usb.hpp +++ b/v5/include/libhal-util/usb.hpp @@ -1,109 +1,6 @@ #pragma once -#include -#include -#include -#include - -namespace hal::v5 { -// TODO(#79): Add doxygen docs to USB APIs -inline void write(usb::control_endpoint& p_endpoint, - scatter_span p_data_out) -{ - p_endpoint.write(p_data_out); -} - -inline void write_and_flush(usb::control_endpoint& p_endpoint, - scatter_span p_data_out) -{ - p_endpoint.write(p_data_out); - p_endpoint.write({}); -} - -inline void write(usb::control_endpoint& p_endpoint, - std::span p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out)); -} - -inline void write_and_flush(usb::control_endpoint& p_endpoint, - std::span p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out)); - p_endpoint.write({}); -} - -inline void write(usb::in_endpoint& p_endpoint, - scatter_span p_data_out) -{ - p_endpoint.write(p_data_out); -} - -inline void write_and_flush(usb::in_endpoint& p_endpoint, - scatter_span p_data_out) -{ - p_endpoint.write(p_data_out); - p_endpoint.write({}); -} - -inline void write(usb::in_endpoint& p_endpoint, - std::span p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out)); -} - -inline void write_and_flush(usb::in_endpoint& p_endpoint, - std::span p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out)); - p_endpoint.write({}); -} - -inline void write(usb::in_endpoint& p_endpoint, - spanable_bytes auto... p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out...)); -} - -inline void write_and_flush(usb::in_endpoint& p_endpoint, - spanable_bytes auto... p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out...)); - p_endpoint.write({}); -} - -inline auto read(usb::out_endpoint& p_endpoint, - scatter_span p_data_out) -{ - return p_endpoint.read(p_data_out); -} - -inline auto read(usb::out_endpoint& p_endpoint, std::span p_data_out) -{ - return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); -} - -inline auto read(usb::out_endpoint& p_endpoint, - spanable_writable_bytes auto... p_data_out) -{ - return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); -} - -inline auto read(usb::control_endpoint& p_endpoint, - scatter_span p_data_out) -{ - return p_endpoint.read(p_data_out); -} - -inline auto read(usb::control_endpoint& p_endpoint, - std::span p_data_out) -{ - return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); -} - -inline auto read(usb::control_endpoint& p_endpoint, - spanable_writable_bytes auto... p_data_out) -{ - return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); -} -} // namespace hal::v5 +#include "usb/descriptors.hpp" +#include "usb/endpoints.hpp" +#include "usb/enumerator.hpp" +#include "usb/utils.hpp" From 67a1e4c063d20a7c38c94a8aa930805546293afe Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 8 Sep 2025 02:20:17 -0700 Subject: [PATCH 05/18] :bug: Enumerator wait loop got optmized out, volatile prevents this --- include/libhal-util/usb/enumerator.hpp | 2 +- v5/tests/usb.test.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index 39bf3af..5873979 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -101,7 +101,7 @@ class enumerator // TODO: Make async bool finished_enumeration = false; - bool waiting_for_data = true; + bool volatile waiting_for_data = true; using on_receive_tag = control_endpoint::on_receive_tag; m_ctrl_ep->on_receive( diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index 69fa5d1..b511b91 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include From 033e5d78484c41414b4e52ccec3e53d3267f96f7 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 29 Sep 2025 00:46:27 -0700 Subject: [PATCH 06/18] :bug: Fixed logic to have enumeration work on real hardware --- include/libhal-util/usb/descriptors.hpp | 63 +++-- include/libhal-util/usb/enumerator.hpp | 293 +++++++++++++++++------- include/libhal-util/usb/utils.hpp | 31 +-- v5/tests/usb.test.cpp | 96 +++++--- 4 files changed, 306 insertions(+), 177 deletions(-) diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp index 23c89c8..750c875 100644 --- a/include/libhal-util/usb/descriptors.hpp +++ b/include/libhal-util/usb/descriptors.hpp @@ -1,24 +1,39 @@ -#pragma once +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -/* TODO: - Class, subclass, proto validator - Device qualifer descriptor (happens between device and config) - Other speed descriptor (happens with configuration) -*/ +#pragma once #include -#include -#include -#include #include #include #include +#include + +#include +#include +#include +#include -#include "../as_bytes.hpp" #include "libhal-util/as_bytes.hpp" #include "libhal/units.hpp" #include "utils.hpp" -#include + +/* TODO (PhazonicRidley): + Class, subclass, proto validator + Device qualifer descriptor (happens between device and config) + Other speed descriptor (happens with configuration) +*/ namespace hal::v5::usb { @@ -36,9 +51,9 @@ struct device u16 p_id_vendor; u16 p_id_product; u16 p_bcd_device; - std::string_view p_manufacturer; - std::string_view p_product; - std::string_view p_serial_number_str; + std::u16string_view p_manufacturer; + std::u16string_view p_product; + std::u16string_view p_serial_number_str; }; constexpr device(device_arguments&& args) @@ -80,7 +95,7 @@ struct device m_packed_arr[idx++] = 0; // 15 Number of possible configurations }; - constexpr u16& bcd_usb() + u16& bcd_usb() { return *reinterpret_cast(&m_packed_arr[0]); } @@ -100,17 +115,17 @@ struct device return m_packed_arr[4]; } - constexpr u16& id_vendor() + u16& id_vendor() { return *reinterpret_cast(&m_packed_arr[6]); } - constexpr u16& id_product() + u16& id_product() { return *reinterpret_cast(&m_packed_arr[8]); } - constexpr u16& bcd_device() + u16& bcd_device() { return *reinterpret_cast(&m_packed_arr[10]); } @@ -120,9 +135,9 @@ struct device return m_packed_arr; } - std::string_view manufacturer_str; - std::string_view product_str; - std::string_view serial_number_str; + std::u16string_view manufacturer_str; + std::u16string_view product_str; + std::u16string_view serial_number_str; private: constexpr u8& max_packet_size() @@ -195,7 +210,7 @@ struct configuration }; template - constexpr configuration(std::string_view p_name, + constexpr configuration(std::u16string_view p_name, bitmap&& p_attributes, u8&& p_max_power, std::pmr::polymorphic_allocator<> p_allocator, @@ -237,11 +252,11 @@ struct configuration return m_packed_arr[6]; } - std::string_view name; + std::u16string_view name; std::pmr::vector> interfaces; // private: - constexpr u16& total_length() + u16& total_length() { return *reinterpret_cast(&m_packed_arr[0]); } diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index 5873979..bf7dcf8 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -1,23 +1,48 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #pragma once -#include #include +#include + +#include +#include + +#include +#include +#include +#include +#include + #include #include #include +#include #include #include -#include -#include #include "descriptors.hpp" +#include "libhal-util/as_bytes.hpp" +#include "libhal-util/usb/endpoints.hpp" #include "utils.hpp" // TODO: move to util namespace hal::v5::usb { template -size_t scatter_span_size(scatter_span ss) +constexpr size_t scatter_span_size(scatter_span ss) { size_t res = 0; for (auto const& s : ss) { @@ -26,18 +51,64 @@ size_t scatter_span_size(scatter_span ss) 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, - device&& p_device, - std::array&& p_configs, - std::string_view p_lang_str, - u8&& p_starting_str_idx, - 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, + bool enumerate_immediately = true) : m_ctrl_ep(p_ctrl_ep) , m_device(p_device) , m_configs(p_configs) @@ -70,19 +141,19 @@ 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; // 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; + config.configuration_value() = i + 1; } - 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) { interface::descriptor_count deltas = iface->write_descriptors( @@ -94,6 +165,7 @@ class enumerator cur_iface_idx += deltas.interface; cur_str_idx += deltas.string; } + config.num_interfaces() = cur_iface_idx; config.total_length() = total_length; } @@ -104,9 +176,11 @@ class enumerator bool volatile waiting_for_data = true; using on_receive_tag = control_endpoint::on_receive_tag; + m_ctrl_ep->on_receive( [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); m_ctrl_ep->connect(true); + std::array raw_req; do { // Seriously, make this async @@ -118,9 +192,19 @@ class enumerator 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) { + safe_throw(hal::message_size(num_bytes_read, this)); } + // + // for (auto const& el : raw_req) { + // + // } + // auto req = from_span(raw_req); if (req.get_recipient() != setup_packet::recipient::device) { @@ -129,7 +213,8 @@ class enumerator // TODO: Handle exception handle_standard_device_request(req); - m_ctrl_ep->write({}); // Send ZLP to complete Data Transaction + // m_ctrl_ep->write({}); // Send ZLP to complete Data + // Transaction if (static_cast(req.request) == standard_request_types::set_configuration) { finished_enumeration = true; @@ -154,6 +239,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); @@ -174,9 +261,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( @@ -200,7 +296,9 @@ class enumerator switch (determine_standard_request(req)) { case standard_request_types::set_address: { + m_ctrl_ep->write({}); m_ctrl_ep->set_address(req.value); + break; } @@ -220,7 +318,7 @@ class enumerator } case standard_request_types::set_configuration: { - m_active_conf = &m_configs[req.value]; + m_active_conf = &(m_configs->at(req.value - 1)); break; } @@ -239,33 +337,36 @@ class enumerator 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 = make_scatter_bytes(header, m_device); - m_ctrl_ep->write(static_cast>(scatter_arr)); + 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)); + break; } case descriptor_type::configuration: { - configuration& conf = m_configs[desc_idx]; - if (req.length <= 2) { // requesting total length - auto tl = setup_packet::to_le_bytes(conf.total_length()); - auto scatter_tot_len = make_scatter_bytes(tl); - m_ctrl_ep->write(scatter_tot_len); - break; - } - - // if its >2 then assumed to be requesting desc type - u16 total_size = constants::config_desc_size; + 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_hdr = - make_scatter_bytes(conf_hdr, m_configs[desc_idx]); + 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)); - m_ctrl_ep->write(scatter_span(scatter_conf_hdr)); + // Return early if the only thing requested was the config descriptor + if (req.length <= constants::config_desc_size) { + 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 }, @@ -275,28 +376,30 @@ class enumerator }); } - if (total_size != req.length) { - safe_throw(hal::operation_not_supported( - this)); // TODO: Make specific exception for this - } + 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) { + auto s_hdr = - std::to_array({ static_cast(m_lang_str.length()), - static_cast(descriptor_type::string) }); - auto scatter_arr = make_scatter_bytes( - s_hdr, - std::span( - reinterpret_cast(m_lang_str.data()), - m_lang_str.size())); - m_ctrl_ep->write(scatter_arr); + 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({}); break; } - handle_str_descriptors(desc_idx, req.length > 2); // Can throw + handle_str_descriptors(desc_idx, req.length); // Can throw break; } @@ -308,12 +411,12 @@ class enumerator } } - void handle_str_descriptors(u8 const target_idx, bool const write_full_desc) + 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, write_full_desc); + auto r = write_cfg_str_descriptor(target_idx, p_len); if (!r) { safe_throw(hal::argument_out_of_domain(this)); } @@ -322,25 +425,26 @@ class enumerator } if (m_iface_for_str_desc.has_value() && - m_iface_for_str_desc->first == target_idx && write_full_desc) { + 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) { m_ctrl_ep->write(desc); }); + target_idx, [this](scatter_span desc) { + hal::v5::write_and_flush(*m_ctrl_ep, desc); + }); if (success) { return; } } // Iterate through every interface now to find a match - auto f = [this, write_full_desc](scatter_span desc) { - if (write_full_desc) { - m_ctrl_ep->write(desc); + 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( + std::array desc_type{ static_cast( descriptor_type::string) }; auto scatter_str_hdr = make_scatter_bytes(std::span(&desc[0][0], 1), desc_type); - m_ctrl_ep->write(scatter_str_hdr); + hal::v5::write_and_flush(*m_ctrl_ep, scatter_str_hdr); } }; @@ -353,7 +457,7 @@ class enumerator } } - for (configuration& conf : m_configs) { + for (configuration const& conf : *m_configs) { for (auto const& iface : conf.interfaces) { auto res = iface->write_string_descriptor(target_idx, f); if (res) { @@ -363,57 +467,72 @@ class enumerator } } - bool write_cfg_str_descriptor(u8 const target_idx, bool const write_full_desc) + 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 conf_sv; + std::optional opt_conf_sv; if (target_idx == (m_starting_str_idx + dev_manu_offset)) { - conf_sv = m_device.manufacturer_str; + opt_conf_sv = m_device->manufacturer_str; } else if (target_idx == (m_starting_str_idx + dev_prod_offset)) { - conf_sv = m_device.product_str; + opt_conf_sv = m_device->product_str; } else if (target_idx == (m_starting_str_idx + dev_sn_offset)) { - 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)) { - conf_sv = conf.name; + opt_conf_sv = conf.name; } } } - if (conf_sv == std::nullopt) { + 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) - auto hdr_arr = - std::to_array({ static_cast(conf_sv->length()), - static_cast(descriptor_type::string) }); - auto scatter_arr_str_hdr = make_scatter_bytes(hdr_arr); - m_ctrl_ep->write(scatter_arr_str_hdr); - - if (write_full_desc) { - // Hack to force scatter_span to accept string view - auto scatter_arr = make_scatter_bytes(std::span( - reinterpret_cast(conf_sv->data()), conf_sv->size())); - m_ctrl_ep->write(scatter_arr); + + auto sv = opt_conf_sv.value(); + std::mbstate_t state{}; + for (wchar_t const wc : sv) { + std::array tmp; + size_t len = std::wcrtomb(tmp.data(), wc, &state); + if (len == static_cast(-1)) { + + continue; + } + + for (size_t i = 0; i < len; i++) { + } } + auto const conf_sv_span = hal::as_bytes(opt_conf_sv.value()); + auto desc_len = static_cast((conf_sv_span.size() + 2)); + + 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); + return true; } strong_ptr m_ctrl_ep; - device m_device; - std::array m_configs; - std::string_view m_lang_str; + 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; diff --git a/include/libhal-util/usb/utils.hpp b/include/libhal-util/usb/utils.hpp index a1e68c7..204de2b 100644 --- a/include/libhal-util/usb/utils.hpp +++ b/include/libhal-util/usb/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 @@ -85,35 +87,6 @@ enum class descriptor_type : hal::byte 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; diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index b511b91..8790f28 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -212,7 +213,7 @@ class mock_usb_control_endpoint : public control_endpoint struct mock : public interface { - constexpr mock(std::string_view p_name) + constexpr mock(std::u16string_view p_name) : name(p_name) { } @@ -292,7 +293,7 @@ struct mock : public interface 4, // interface_protocol 0 // interface name index }; - std::string_view name; + std::u16string_view name; }; class iad_mock : public interface @@ -300,8 +301,8 @@ class iad_mock : public interface public: ~iad_mock() override = default; - iad_mock(std::string_view p_iface_name_one, // NOLINT - std::string_view p_iface_name_two) + iad_mock(std::u16string_view p_iface_name_one, // NOLINT + std::u16string_view p_iface_name_two) : m_name_one(p_iface_name_one) , m_name_two(p_iface_name_two) {}; @@ -411,7 +412,6 @@ class iad_mock : public interface u16 driver_get_status(setup_packet p_pkt) { if (p_pkt.get_recipient() != setup_packet::recipient::interface) { - // std::println("Unsupported recipient"); safe_throw(hal::operation_not_supported(this)); } @@ -430,7 +430,6 @@ class iad_mock : public interface { std::ignore = p_selector; if (p_pkt.get_recipient() != setup_packet::recipient::interface) { - // std::println("Unsupported recipient"); safe_throw(hal::operation_not_supported(this)); } @@ -441,7 +440,6 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { m_iface_two.feature = p_set; } else { - // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -455,7 +453,6 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { return m_iface_two.alt_settings; } else { - // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -469,7 +466,6 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { m_iface_two.alt_settings = alt_setting; } else { - // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -484,9 +480,9 @@ class iad_mock : public interface public: mock_iface_descriptor m_iface_one; - std::string_view m_name_one; + std::u16string_view m_name_one; mock_iface_descriptor m_iface_two; - std::string_view m_name_two; + std::u16string_view m_name_two; bool m_wrote_descriptors = false; }; @@ -542,6 +538,31 @@ void simulate_sending_payload( // return vec; // } +class mock_serial : public hal::serial +{ + void driver_configure(settings const& p_settings) override + { + std::ignore = p_settings; + } + + write_t driver_write(std::span p_data) override + { + std::string_view sv(reinterpret_cast(p_data.data()), + p_data.size()); + return { .data = p_data }; + } + + read_t driver_read(std::span p_data) override + { + std::ignore = p_data; + return { .data = {}, .available = 0, .capacity = 0 }; + } + + void driver_flush() override + { + } +}; + } // namespace boost::ut::suite<"usb_test"> usb_test = [] { // TODO(#78): Add usb utility tests @@ -561,24 +582,26 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { .p_id_vendor = 0xffff, .p_id_product = 0x0000, .p_bcd_device = 0x0001, - .p_manufacturer = "libhal", - .p_product = "Unit Test", - .p_serial_number_str = "123456789" }); + .p_manufacturer = u"libhal", + .p_product = u"Unit Test", + .p_serial_number_str = u"123456789" }); + pmr::monotonic_buffer_resource pool(iface_buf.data(), std::size(iface_buf)); std::array conf_arr{ configuration{ - "Test Config", + u"Test Config", configuration::bitmap(true, false), 1, &pool, - make_strong_ptr(&pool, mock("Mock Iface")) } }; + make_strong_ptr(&pool, mock(u"Mock Iface")) } }; mock_usb_control_endpoint ctrl_ep; ctrl_ep.m_endpoint.m_info = { .size = 8, .number = 0, .stalled = false }; auto ctrl_ptr = make_strong_ptr(&pool, ctrl_ep); - enumerator<1> en{ - ctrl_ptr, device(dev), std::array(conf_arr), "LANG", 1, - false // NOLINT - }; + auto const conf_arr_ptr = + make_strong_ptr>(&pool, conf_arr); + auto dev_ptr = make_strong_ptr(&pool, dev); + auto console_ptr = make_strong_ptr(&pool, mock_serial()); + enumerator<1> en{ ctrl_ptr, dev_ptr, conf_arr_ptr, 0x0409, 1, false }; "basic usb enumeration test"_test = [&en, &ctrl_ptr] { // Start enumeration process and verify connection @@ -661,30 +684,28 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { simulate_sending_payload(ctrl_ptr, str_hdr_req); ctrl_ptr->simulate_interrupt(); std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - std::array expected_str_hdr{ - static_cast(6), // string is "libhal" + std::array expected_manu_str_hdr{ + static_cast(14), // string is "libhal" static_cast(descriptor_type::string) }; std::span actual_dev_str_hdr(ctrl_buf.data(), 2); - expect(that % (span_eq(std::span(expected_str_hdr), + expect(that % (span_eq(std::span(expected_manu_str_hdr), actual_dev_str_hdr))); ctrl_buf.clear(); // Get a string descriptor from device setup_packet str_req(str_hdr_req); - str_req.length = expected_str_hdr[0]; + str_req.length = expected_manu_str_hdr[0]; simulate_sending_payload(ctrl_ptr, str_req); ctrl_ptr->simulate_interrupt(); std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - std::string_view expected_manu_str = "libhal"; - std::array manu_str_hdr( - { static_cast(expected_manu_str.length()), - static_cast(descriptor_type::string) }); + std::u16string_view expected_manu_str = u"libhal"; + auto expected_manu_str_scatter = - make_scatter_bytes(manu_str_hdr, + make_scatter_bytes(expected_manu_str_hdr, std::span(reinterpret_cast( expected_manu_str.data()), - expected_manu_str.length())); + expected_manu_str.length() * 2)); auto actual_manu_str_scatter = make_scatter_bytes(ctrl_buf); expect(that % (scatter_span(expected_manu_str_scatter) == scatter_span(actual_manu_str_scatter))); @@ -698,17 +719,17 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { static_cast(standard_request_types::get_descriptor), static_cast(descriptor_type::configuration) << 8, 0, - 2); + 9); // Expected Config + interface descriptor std::array expected_conf_iface_desc{ // config descriptor 0x9, // len static_cast(descriptor_type::configuration), // type - 0x12, // TODO: total length - 0x0, // tl + 0x12, // HH: total length + 0x0, // LL: tl 0x1, // number of interfaces - 0x0, // config value + 0x1, // config value 0x4, // name string index 0xc0, // bmattributes (selfpowered = true) 0x1, // max power @@ -725,6 +746,7 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { 0x5 // iface name string index }; + // Get Configuration descriptor header simulate_sending_payload(ctrl_ptr, conf_hdr_req); ctrl_ptr->simulate_interrupt(); std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); @@ -734,8 +756,8 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { auto expected_total_len = setup_packet::from_le_bytes(expected_tl_hh, expected_tl_ll); - auto actual_tl_hh = ctrl_buf[0]; - auto actual_tl_ll = ctrl_buf[1]; + auto actual_tl_hh = ctrl_buf[2]; + auto actual_tl_ll = ctrl_buf[3]; auto actual_total_len = setup_packet::from_le_bytes(actual_tl_hh, actual_tl_ll); expect(that % expected_total_len == actual_total_len); @@ -756,7 +778,7 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { setup_packet::type::standard, setup_packet::recipient::device, static_cast(standard_request_types::set_configuration), - 0, + 1, 0, 0); From 1a0339015e6cfc6a2156ef7ba570e166da9335ce Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 29 Sep 2025 01:17:57 -0700 Subject: [PATCH 07/18] :memo: Document descriptors and enumerator class --- include/libhal-util/usb/enumerator.hpp | 72 ++++++++++++++++-------- include/libhal-util/usb/utils.hpp | 78 ++++++++++---------------- 2 files changed, 79 insertions(+), 71 deletions(-) diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index bf7dcf8..0b3b265 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -38,9 +38,16 @@ #include "libhal-util/usb/endpoints.hpp" #include "utils.hpp" -// TODO: move to util namespace hal::v5::usb { +/** + TODO: (PhazonicRidley) Make class method for scatterspan class + @brief Get the size of a scatter span, meaning how many elements is this + scatterspan viewing. + + @param ss The scatter span to get the size of + @return The number of elements ss is viewing + */ template constexpr size_t scatter_span_size(scatter_span ss) { @@ -51,6 +58,28 @@ constexpr size_t scatter_span_size(scatter_span ss) return res; } + +/** + TODO: (PhazonicRidley) Make class method for scatterspan class + @brief Creates a scatter_span with p_spans and calculates how many internal + spans are used for a given sub scatter_span starting at index zero and going + to the p_count element. + + ```C++ + auto pair = make_scatter_span(n, ...); + auto my_scatter_span = scatter_span(pair.first).first(pair.second) + ``` + + @tparam T The type all collections are to have of. + @tparam Args... A packed set of collections of type T + + @param p_count How many elements (from the start) to take into the subspan + @param p_spans The spans the scatter span is to view into + + @return A pair where the first element contains an array of spans used within + the subscatter span, and the second element is the number of spans within + the sub scatterspan +*/ template constexpr std::pair, sizeof...(Args)>, size_t> make_sub_scatter_array(size_t p_count, Args&&... p_spans) @@ -89,6 +118,19 @@ make_sub_scatter_array(size_t p_count, Args&&... p_spans) return std::make_pair(res, i + 1); } +/** + TODO: (PhazonicRidley) Make class method for scatterspan class + @brief Create a sub scatter span array from spans of bytes + + @tparam Args... A packed set of collections of bytes + + @param p_count How many elements (from the start) to take into the subspan + @param p_spans The spans the scatter span is to view into + + @return A pair where the first element contains an array of spans used within + the subscatter span, and the second element is the number of bytes within + the sub scatterspan +*/ template constexpr std::pair, sizeof...(Args)>, size_t> make_sub_scatter_bytes(size_t p_count, Args&&... p_spans) @@ -183,7 +225,7 @@ class enumerator std::array raw_req; do { - // Seriously, make this async + // TODO: Seriously, make this async while (waiting_for_data) { continue; } @@ -200,21 +242,15 @@ class enumerator safe_throw(hal::message_size(num_bytes_read, this)); } - // - // for (auto const& el : raw_req) { - // - // } - // - auto req = from_span(raw_req); + + auto req = setup_packet(raw_req); if (req.get_recipient() != setup_packet::recipient::device) { safe_throw(hal::not_connected(this)); } - // TODO: Handle exception + // ZLP is handled at write site 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; @@ -246,7 +282,7 @@ class enumerator auto bytes_read = m_ctrl_ep->read(scatter_read_buf); std::span payload(read_buf.data(), bytes_read); - setup_packet req = from_span(payload); + setup_packet req(payload); if (determine_standard_request(req) == standard_request_types::invalid) { return; } @@ -268,9 +304,7 @@ class enumerator }; } 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" + std::ignore = m_ctrl_ep->read(resp); }; } bool req_handled = false; @@ -377,11 +411,6 @@ class enumerator } m_ctrl_ep->write({}); - // if (total_size != req.length) { - // safe_throw(hal::operation_not_supported( - // this)); // TODO: Make specific exception for this - // } - break; } @@ -403,9 +432,6 @@ class enumerator break; } - // TODO: Interface, endpoint, device_qualifier, interface_power, - // OTHER_SPEED_CONFIGURATION - default: safe_throw(hal::operation_not_supported(this)); } diff --git a/include/libhal-util/usb/utils.hpp b/include/libhal-util/usb/utils.hpp index 204de2b..a5bf1aa 100644 --- a/include/libhal-util/usb/utils.hpp +++ b/include/libhal-util/usb/utils.hpp @@ -3,24 +3,10 @@ #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; @@ -28,11 +14,12 @@ 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 +/** + * @brief USB Class Code indicating the type of device or interface. Assigned by + * USB-IF. + */ enum class class_code : hal::byte { use_interface_descriptor = @@ -63,39 +50,34 @@ enum class class_code : hal::byte vendor_specific = 0xFF // Vendor Specific }; -// Default types +/** + * @brief USB Descriptor Type values as defined by the USB specification. + * These values are used in the bDescriptorType field of USB descriptors + * to identify the type and structure of the descriptor data. + */ 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, + device = 0x1, // Device descriptor + configuration = 0x2, // Configuration descriptor + string = 0x3, // String descriptor + interface = 0x4, // Interface descriptor + endpoint = 0x5, // Endpoint descriptor + device_qualifier = 0x6, // Device qualifier descriptor + other_speed_configuration = 0x7, // Other speed configuration descriptor + interface_power = 0x8, // Interface power descriptor + otg = 0x9, // OTG descriptor + debug = 0xA, // Debug descriptor + interface_association = 0xB, // Interface Association descriptor + security = 0xC, // Security descriptor + key = 0xD, // Key descriptor + encryption_type = 0xE, // Encryption type descriptor + bos = 0xF, // Binary Object Store (BOS) descriptor + device_capability = 0x10, // Device capability descriptor + wireless_endpoint_companion = 0x11, // Wireless endpoint companion descriptor + superspeed_endpoint_companion = + 0x30, // SuperSpeed endpoint companion descriptor + superspeed_endpoint_isochronous_companion = + 0x31, // SuperSpeed isochronous endpoint companion descriptor }; -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 2bef819f2bf8d82b948359bb066cac4ab79f2d61 Mon Sep 17 00:00:00 2001 From: Khalil Estell Date: Mon, 27 Oct 2025 09:36:00 -0700 Subject: [PATCH 08/18] :recycle: Move source code to v5/ directory (#82) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58fec50..7e891f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: secrets: inherit deployment_check_v5: - uses: libhal/ci/.github/workflows/package_and_upload_all.yml@5.x.y + uses: libhal/ci/.github/workflows/deploy_all.yml@5.x.y with: dir: v5 secrets: inherit From a89d57aa2f8b7ea494da295568bf95bd6081f447 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sun, 31 Aug 2025 02:02:44 -0700 Subject: [PATCH 09/18] :sparkles: feat(usb_enumeration): Made an enumerator For devices that use the libhal usb interface high level class. Handles configuration at the device and usb configuration level Handles multiple configurations and multiple interfaces. --- include/libhal-util/usb/descriptors.hpp | 144 ++++---- include/libhal-util/usb/endpoints.hpp | 32 +- include/libhal-util/usb/enumerator.hpp | 427 ++++++++---------------- include/libhal-util/usb/utils.hpp | 129 +++++-- 4 files changed, 310 insertions(+), 422 deletions(-) diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp index 750c875..4c23079 100644 --- a/include/libhal-util/usb/descriptors.hpp +++ b/include/libhal-util/usb/descriptors.hpp @@ -1,39 +1,26 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #pragma once -#include -#include -#include -#include -#include +// TODO: Move to util +/* TODO: + Class, subclass, proto validator + Device qualifer descriptor + Other speed descriptor + Interface Association Descriptor + */ +#include #include #include -#include #include +#include +#include +#include +#include "../as_bytes.hpp" #include "libhal-util/as_bytes.hpp" #include "libhal/units.hpp" #include "utils.hpp" - -/* TODO (PhazonicRidley): - Class, subclass, proto validator - Device qualifer descriptor (happens between device and config) - Other speed descriptor (happens with configuration) -*/ +#include namespace hal::v5::usb { @@ -46,14 +33,14 @@ struct device { u16 p_bcd_usb; class_code p_device_class; - u8 p_device_subclass; + u8 p_device_subclass; // NOLINT u8 p_device_protocol; - u16 p_id_vendor; + u16 p_id_vendor; // NOLINT 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; + std::string_view p_manufacturer; // NOLINT + std::string_view p_product; + std::string_view p_serial_number_str; }; constexpr device(device_arguments&& args) @@ -62,87 +49,86 @@ struct device , serial_number_str(args.p_serial_number_str) { u8 idx = 0; - auto bcd_usb_bytes = hal::as_bytes(&args.p_bcd_usb, 1); + auto bcd_usb_bytes = hal::as_bytes(&args.p_bcd_usb, 2); for (auto& bcd_usb_byte : bcd_usb_bytes) { - m_packed_arr[idx++] = bcd_usb_byte; // 0, 1 + m_packed_arr[idx++] = bcd_usb_byte; } + m_packed_arr[idx++] = args.p_bcd_usb; + m_packed_arr[idx++] = static_cast(args.p_device_class); + m_packed_arr[idx++] = args.p_device_subclass; + m_packed_arr[idx++] = args.p_device_protocol; - 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); + m_packed_arr[idx++] = 0; // Max Packet length handled by the enumerator + auto id_vendor_bytes = hal::as_bytes(&args.p_id_vendor, 2); for (auto& id_vendor_byte : id_vendor_bytes) { - m_packed_arr[idx++] = id_vendor_byte; // 6, 7 + m_packed_arr[idx++] = id_vendor_byte; } - auto id_product_bytes = hal::as_bytes(&args.p_id_product, 1); + auto id_product_bytes = hal::as_bytes(&args.p_id_product, 2); for (auto& id_product_byte : id_product_bytes) { - m_packed_arr[idx++] = id_product_byte; // 8, 9 + m_packed_arr[idx++] = id_product_byte; } - auto bcd_device_bytes = hal::as_bytes(&args.p_bcd_device, 1); + auto bcd_device_bytes = hal::as_bytes(&args.p_bcd_device, 2); for (auto& bcd_device_byte : bcd_device_bytes) { - m_packed_arr[idx++] = bcd_device_byte; // 10, 11 + m_packed_arr[idx++] = bcd_device_byte; } // 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 + m_packed_arr[idx++] = 0; // string idx of manufacturer + m_packed_arr[idx++] = 0; // string idx of product + m_packed_arr[idx++] = 0; // string idx of serial number + m_packed_arr[idx++] = 0; // Number of possible configurations }; - u16& bcd_usb() + constexpr u16& bcd_usb() { return *reinterpret_cast(&m_packed_arr[0]); } constexpr u8& device_class() { - return m_packed_arr[2]; + return m_packed_arr[1]; } constexpr u8& device_sub_class() { - return m_packed_arr[3]; + return m_packed_arr[2]; } constexpr u8& device_protocol() { - return m_packed_arr[4]; + return m_packed_arr[3]; } - u16& id_vendor() + constexpr u16& id_vendor() { - return *reinterpret_cast(&m_packed_arr[6]); + return *reinterpret_cast(&m_packed_arr[5]); } - u16& id_product() + constexpr u16& id_product() { - return *reinterpret_cast(&m_packed_arr[8]); + return *reinterpret_cast(&m_packed_arr[7]); } - u16& bcd_device() + constexpr u16& bcd_device() { - return *reinterpret_cast(&m_packed_arr[10]); + return *reinterpret_cast(&m_packed_arr[9]); } - operator std::span() const + operator std::span() const { return m_packed_arr; } - std::u16string_view manufacturer_str; - std::u16string_view product_str; - std::u16string_view serial_number_str; + std::string_view manufacturer_str; + std::string_view product_str; + std::string_view serial_number_str; private: constexpr u8& max_packet_size() { - return m_packed_arr[5]; + return m_packed_arr[4]; } constexpr u8& manufacturer_index() @@ -168,7 +154,7 @@ struct device // https://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors template -concept usb_interface_concept = std::derived_from; +concept usb_interface_concept = std::derived_from; // Calculate: total length, number of interfaces, configuration value struct configuration @@ -187,7 +173,7 @@ struct configuration constexpr bitmap(bool p_self_powered, bool p_remote_wakeup) { - m_bitmap = (1 << 7) | (p_self_powered << 6) | (p_remote_wakeup << 5); + m_bitmap = (1 << 7) & (p_self_powered << 6) & (p_remote_wakeup << 5); } constexpr u8 to_byte() @@ -210,7 +196,7 @@ struct configuration }; template - constexpr configuration(std::u16string_view p_name, + constexpr configuration(std::string_view p_name, bitmap&& p_attributes, u8&& p_max_power, std::pmr::polymorphic_allocator<> p_allocator, @@ -223,14 +209,13 @@ struct configuration 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 + m_packed_arr[idx++] = 0; // Total Length + m_packed_arr[idx++] = interfaces.size(); // number of interfaces + m_packed_arr[idx++] = 0; // Config number + m_packed_arr[idx++] = 0; // Configuration name string index + + m_packed_arr[idx++] = p_attributes.to_byte(); + m_packed_arr[idx++] = p_max_power; } operator std::span() const @@ -246,17 +231,16 @@ struct configuration { return m_packed_arr[5]; } - constexpr u8& max_power() { return m_packed_arr[6]; } - std::u16string_view name; - std::pmr::vector> interfaces; + std::string_view name; + std::pmr::vector> interfaces; - // private: - u16& total_length() +private: + constexpr u16& total_length() { return *reinterpret_cast(&m_packed_arr[0]); } diff --git a/include/libhal-util/usb/endpoints.hpp b/include/libhal-util/usb/endpoints.hpp index 0bfa259..d65a49f 100644 --- a/include/libhal-util/usb/endpoints.hpp +++ b/include/libhal-util/usb/endpoints.hpp @@ -21,101 +21,101 @@ namespace hal::v5 { // TODO(#79): Add doxygen docs to USB APIs -inline void write(usb::control_endpoint& p_endpoint, +inline void write(usb_control_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); } -inline void write_and_flush(usb::control_endpoint& p_endpoint, +inline void write_and_flush(usb_control_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } -inline void write(usb::control_endpoint& p_endpoint, +inline void write(usb_control_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } -inline void write_and_flush(usb::control_endpoint& p_endpoint, +inline void write_and_flush(usb_control_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } -inline void write(usb::in_endpoint& p_endpoint, +inline void write(usb_in_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); } -inline void write_and_flush(usb::in_endpoint& p_endpoint, +inline void write_and_flush(usb_in_endpoint& p_endpoint, scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } -inline void write(usb::in_endpoint& p_endpoint, +inline void write(usb_in_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } -inline void write_and_flush(usb::in_endpoint& p_endpoint, +inline void write_and_flush(usb_in_endpoint& p_endpoint, std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } -inline void write(usb::in_endpoint& p_endpoint, +inline void write(usb_in_endpoint& p_endpoint, spanable_bytes auto... p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out...)); } -inline void write_and_flush(usb::in_endpoint& p_endpoint, +inline void write_and_flush(usb_in_endpoint& p_endpoint, spanable_bytes auto... p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out...)); p_endpoint.write({}); } -inline auto read(usb::out_endpoint& p_endpoint, +inline auto read(usb_out_endpoint& p_endpoint, scatter_span p_data_out) { return p_endpoint.read(p_data_out); } -inline auto read(usb::out_endpoint& p_endpoint, std::span p_data_out) +inline auto read(usb_out_endpoint& p_endpoint, std::span p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); } -inline auto read(usb::out_endpoint& p_endpoint, +inline auto read(usb_out_endpoint& p_endpoint, spanable_writable_bytes auto... p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); } -inline auto read(usb::control_endpoint& p_endpoint, +inline auto read(usb_control_endpoint& p_endpoint, scatter_span p_data_out) { return p_endpoint.read(p_data_out); } -inline auto read(usb::control_endpoint& p_endpoint, +inline auto read(usb_control_endpoint& p_endpoint, std::span p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); } -inline auto read(usb::control_endpoint& p_endpoint, +inline auto read(usb_control_endpoint& p_endpoint, spanable_writable_bytes auto... p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index 0b3b265..72d1d37 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -1,156 +1,31 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #pragma once -#include -#include - #include -#include - -#include -#include -#include -#include -#include - +#include #include #include #include -#include #include #include +#include +#include #include "descriptors.hpp" -#include "libhal-util/as_bytes.hpp" -#include "libhal-util/usb/endpoints.hpp" #include "utils.hpp" +// TODO: move to util namespace hal::v5::usb { -/** - TODO: (PhazonicRidley) Make class method for scatterspan class - @brief Get the size of a scatter span, meaning how many elements is this - scatterspan viewing. - - @param ss The scatter span to get the size of - @return The number of elements ss is viewing - */ -template -constexpr size_t scatter_span_size(scatter_span ss) -{ - size_t res = 0; - for (auto const& s : ss) { - res += s.size(); - } - - return res; -} - -/** - TODO: (PhazonicRidley) Make class method for scatterspan class - @brief Creates a scatter_span with p_spans and calculates how many internal - spans are used for a given sub scatter_span starting at index zero and going - to the p_count element. - - ```C++ - auto pair = make_scatter_span(n, ...); - auto my_scatter_span = scatter_span(pair.first).first(pair.second) - ``` - - @tparam T The type all collections are to have of. - @tparam Args... A packed set of collections of type T - - @param p_count How many elements (from the start) to take into the subspan - @param p_spans The spans the scatter span is to view into - - @return A pair where the first element contains an array of spans used within - the subscatter span, and the second element is the number of spans within - the sub scatterspan -*/ -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); -} - -/** - TODO: (PhazonicRidley) Make class method for scatterspan class - @brief Create a sub scatter span array from spans of bytes - - @tparam Args... A packed set of collections of bytes - - @param p_count How many elements (from the start) to take into the subspan - @param p_spans The spans the scatter span is to view into - - @return A pair where the first element contains an array of spans used within - the subscatter span, and the second element is the number of bytes within - the sub scatterspan -*/ -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, - bool enumerate_immediately = true) + enumerator(strong_ptr const& p_ctrl_ep, + device&& p_device, + std::array&& p_configs, + std::string_view p_lang_str, + u8&& p_starting_str_idx) : m_ctrl_ep(p_ctrl_ep) , m_device(p_device) , m_configs(p_configs) @@ -164,10 +39,7 @@ class enumerator safe_throw(hal::argument_out_of_domain(this)); } m_starting_str_idx = p_starting_str_idx; - - if (enumerate_immediately) { - enumerate(); - } + enumerate(); } void enumerate() @@ -183,31 +55,31 @@ 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.id_product() = cur_str_idx++; + m_device.serial_number_index() = cur_str_idx++; + m_device.num_configurations() = num_configs; // Configurations - for (size_t i = 0; i < num_configs; i++) { - configuration& config = m_configs->at(i); + for (auto i = 0; i < num_configs; i++) { + configuration& config = m_configs[i]; config.configuration_index() = cur_str_idx++; - config.configuration_value() = i + 1; + config.configuration_value() = i; } - 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) { - interface::descriptor_count deltas = iface->write_descriptors( - { .interface = cur_iface_idx, .string = cur_str_idx }, + auto deltas = iface->write_descriptors( [&total_length](scatter_span p_data) { - total_length += scatter_span_size(p_data); - }); + total_length += p_data.size(); + }, + cur_iface_idx, + cur_str_idx); - cur_iface_idx += deltas.interface; - cur_str_idx += deltas.string; + cur_iface_idx += deltas.iface_idxes; + cur_str_idx += deltas.str_idxes; } - config.num_interfaces() = cur_iface_idx; config.total_length() = total_length; } @@ -215,17 +87,16 @@ class enumerator // TODO: Make async bool finished_enumeration = false; - bool volatile waiting_for_data = true; - - using on_receive_tag = control_endpoint::on_receive_tag; + bool waiting_for_data = true; + using on_receive_tag = usb_control_endpoint::on_receive_tag; + using standard_request_types = setup_request::standard_request_types; m_ctrl_ep->on_receive( [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); m_ctrl_ep->connect(true); - std::array raw_req; do { - // TODO: Seriously, make this async + // Seriously, make this async while (waiting_for_data) { continue; } @@ -234,25 +105,23 @@ class enumerator 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) { - - safe_throw(hal::message_size(num_bytes_read, this)); + safe_throw(hal::message_size(this)); } + setup_request req(raw_req); - auto req = setup_packet(raw_req); - - if (req.get_recipient() != setup_packet::recipient::device) { + if (req.request_type.get_recipient() != + setup_request::bitmap::recipient::device) { safe_throw(hal::not_connected(this)); } - // ZLP is handled at write site + // 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) { + standard_request_types::get_descriptor && + (static_cast((req.value & 0xFF << 8) >> 8) == + descriptor_type::configuration)) { finished_enumeration = true; m_ctrl_ep->on_receive( [this](on_receive_tag) { m_has_setup_packet = true; }); @@ -271,42 +140,36 @@ class enumerator void resume_ctrl_transaction() { + using std_req_type = setup_request::standard_request_types; + using req_bitmap = setup_request::bitmap; + 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(payload); - if (determine_standard_request(req) == standard_request_types::invalid) { + setup_request req(payload); + if (req.get_standard_request() == std_req_type::invalid) { return; } - if (determine_standard_request(req) == - standard_request_types::get_descriptor && - static_cast((req.value & 0xFF << 8) >> 8) == + if (req.get_standard_request() == std_req_type::get_descriptor && + static_cast(req.value & 0xFF << 8) == descriptor_type::string) { handle_str_descriptors(req.value & 0xFF, req.length > 2); - } else if (req.get_recipient() == setup_packet::recipient::device) { + } else if (req.request_type.get_recipient() == + req_bitmap::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); - }; - } + 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( @@ -325,14 +188,13 @@ class enumerator } private: - void handle_standard_device_request(setup_packet& req) + void handle_standard_device_request(setup_request& req) { + using standard_request_types = setup_request::standard_request_types; - switch (determine_standard_request(req)) { + switch (req.get_standard_request()) { case standard_request_types::set_address: { - m_ctrl_ep->write({}); m_ctrl_ep->set_address(req.value); - break; } @@ -352,7 +214,7 @@ class enumerator } case standard_request_types::set_configuration: { - m_active_conf = &(m_configs->at(req.value - 1)); + m_active_conf = &m_configs[req.value]; break; } @@ -362,87 +224,83 @@ class enumerator } } - void process_get_descriptor(setup_packet& req) + void process_get_descriptor(setup_request& req) { - hal::byte desc_type = (req.value & 0xFF << 8) >> 8; + hal::byte desc_type = req.value & 0xFF << 8; [[maybe_unused]] hal::byte desc_idx = req.value & 0xFF; switch (static_cast(desc_type)) { case descriptor_type::device: { - auto header = + auto scatter_arr = make_scatter_bytes( 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)); - + static_cast(descriptor_type::device) }), + m_device); + m_ctrl_ep->write(static_cast>(scatter_arr)); 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)); - - // Return early if the only thing requested was the config descriptor - if (req.length <= constants::config_desc_size) { - m_ctrl_ep->write({}); - return; + configuration& conf = m_configs[desc_idx]; + if (req.length <= 2) { // requesting total length + auto scatter_tot_len = + make_scatter_bytes(to_le_bytes(conf.total_length())); + m_ctrl_ep->write(scatter_tot_len); + break; } + // if its >2 then assumed to be requesting desc type 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) { + scatter_span scatter_conf_hdr = make_scatter_bytes( + { constants::config_desc_size, descriptor_type::configuration }, + m_configs[desc_idx]); + + m_ctrl_ep->write(scatter_conf_hdr); + + for (size_t i = 0; i < conf.interfaces.size(); i++) { + auto iface = conf.interfaces[i]; + iface->write_descriptors( + [this, &total_size, i](scatter_span byte_stream) { m_ctrl_ep->write(byte_stream); - total_size += scatter_span_size(byte_stream); - }); + total_size += byte_stream.size(); + }, + i); + } + + if (total_size != req.length) { + safe_throw( + hal::exception(this)); // TODO: Make specific exception for this } - m_ctrl_ep->write({}); break; } case descriptor_type::string: { if (desc_idx == 0) { - - 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({}); + auto scatter_arr = make_scatter_bytes( + std::to_array({ static_cast(m_lang_str.length()), + static_cast(descriptor_type::string) }), + std::span(m_lang_str)); + m_ctrl_ep->write(scatter_arr); break; } - handle_str_descriptors(desc_idx, req.length); // Can throw + handle_str_descriptors(desc_idx, req.length > 2); // 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) + void handle_str_descriptors(u8 const target_idx, bool const write_full_desc) { 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); + auto r = write_cfg_str_descriptor(target_idx, write_full_desc); if (!r) { safe_throw(hal::argument_out_of_domain(this)); } @@ -451,41 +309,38 @@ class enumerator } 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; - } + m_iface_for_str_desc->first == target_idx && write_full_desc) { + m_iface_for_str_desc->second->write_descriptors( + [this](scatter_span desc) { m_ctrl_ep->write(desc); }, + target_idx); + return; } // 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); + auto f = [this, write_full_desc](scatter_span desc) { + if (write_full_desc) { + m_ctrl_ep->write(desc); } else { - std::array desc_type{ static_cast( + 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); + m_ctrl_ep->write(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); + auto res = iface->write_string_descriptor(f, target_idx); if (res) { return; } } } - for (configuration const& conf : *m_configs) { + for (configuration& conf : m_configs) { for (auto const& iface : conf.interfaces) { - auto res = iface->write_string_descriptor(target_idx, f); + auto res = iface->write_string_descriptor(f, target_idx); if (res) { break; } @@ -493,74 +348,58 @@ class enumerator } } - bool write_cfg_str_descriptor(u8 const target_idx, u16 p_len) + bool write_cfg_str_descriptor(u8 const target_idx, bool const write_full_desc) { constexpr u8 dev_manu_offset = 0; constexpr u8 dev_prod_offset = 1; constexpr u8 dev_sn_offset = 2; - std::optional opt_conf_sv; + std::optional conf_sv; if (target_idx == (m_starting_str_idx + dev_manu_offset)) { - opt_conf_sv = m_device->manufacturer_str; + conf_sv = m_device.manufacturer_str; } else if (target_idx == (m_starting_str_idx + dev_prod_offset)) { - opt_conf_sv = m_device->product_str; + 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; + 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); + 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; + conf_sv = conf.name; } } } - if (opt_conf_sv == std::nullopt) { + if (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) - - auto sv = opt_conf_sv.value(); - std::mbstate_t state{}; - for (wchar_t const wc : sv) { - std::array tmp; - size_t len = std::wcrtomb(tmp.data(), wc, &state); - if (len == static_cast(-1)) { - - continue; - } - - for (size_t i = 0; i < len; i++) { - } + auto scatter_arr_str_hdr = make_scatter_bytes( + std::to_array({ static_cast(conf_sv->length()), + static_cast(descriptor_type::string) })); + m_ctrl_ep->write(scatter_arr_str_hdr); + + if (write_full_desc) { + // Hack to force scatter_span to accept string view + auto scatter_arr = make_scatter_bytes(std::span( + reinterpret_cast(conf_sv->data()), conf_sv->size())); + m_ctrl_ep->write(scatter_arr); } - auto const conf_sv_span = hal::as_bytes(opt_conf_sv.value()); - auto desc_len = static_cast((conf_sv_span.size() + 2)); - - 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); - return true; } - strong_ptr m_ctrl_ep; - strong_ptr m_device; - strong_ptr> m_configs; - u16 m_lang_str; + strong_ptr m_ctrl_ep; + device m_device; + std::array m_configs; + std::string_view m_lang_str; u8 m_starting_str_idx; - std::optional>> m_iface_for_str_desc; + std::optional>> m_iface_for_str_desc; configuration* m_active_conf = nullptr; bool m_has_setup_packet = false; }; diff --git a/include/libhal-util/usb/utils.hpp b/include/libhal-util/usb/utils.hpp index a5bf1aa..0eeb8a3 100644 --- a/include/libhal-util/usb/utils.hpp +++ b/include/libhal-util/usb/utils.hpp @@ -1,25 +1,38 @@ #pragma once +#include #include #include #include +#include +// TODO: Move to util namespace hal::v5::usb { +// TODO: Make generic +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 -/** - * @brief USB Class Code indicating the type of device or interface. Assigned by - * USB-IF. - */ +// Maybe move these enum classes into the constants namespace +// Assigned by USB-IF enum class class_code : hal::byte { use_interface_descriptor = @@ -50,34 +63,86 @@ enum class class_code : hal::byte vendor_specific = 0xFF // Vendor Specific }; -/** - * @brief USB Descriptor Type values as defined by the USB specification. - * These values are used in the bDescriptorType field of USB descriptors - * to identify the type and structure of the descriptor data. - */ +// Default types enum class descriptor_type : hal::byte { - device = 0x1, // Device descriptor - configuration = 0x2, // Configuration descriptor - string = 0x3, // String descriptor - interface = 0x4, // Interface descriptor - endpoint = 0x5, // Endpoint descriptor - device_qualifier = 0x6, // Device qualifier descriptor - other_speed_configuration = 0x7, // Other speed configuration descriptor - interface_power = 0x8, // Interface power descriptor - otg = 0x9, // OTG descriptor - debug = 0xA, // Debug descriptor - interface_association = 0xB, // Interface Association descriptor - security = 0xC, // Security descriptor - key = 0xD, // Key descriptor - encryption_type = 0xE, // Encryption type descriptor - bos = 0xF, // Binary Object Store (BOS) descriptor - device_capability = 0x10, // Device capability descriptor - wireless_endpoint_companion = 0x11, // Wireless endpoint companion descriptor - superspeed_endpoint_companion = - 0x30, // SuperSpeed endpoint companion descriptor - superspeed_endpoint_isochronous_companion = - 0x31, // SuperSpeed isochronous endpoint companion descriptor + 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, +}; + +struct setup_request +{ + using bitmap = v5::usb_interface::req_bitmap; + + 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 + }; + + constexpr setup_request(bitmap p_request_type, + hal::byte p_request, // NOLINT + u16 p_value, + u16 p_index, + u16 p_length) + : request_type(p_request_type) + , request(p_request) + , value(p_value) + , index(p_index) + , length(p_length) {}; + + constexpr setup_request(std::span raw_req) + : request_type(raw_req[0]) + , request(raw_req[1]) + , value(from_le_bytes(raw_req[2], raw_req[3])) + , index(from_le_bytes(raw_req[4], raw_req[5])) + , length(from_le_bytes(raw_req[6], raw_req[7])) + + {}; + + // Probably better semantics for this function, this disjoints + [[nodiscard]] constexpr standard_request_types get_standard_request() const + { + if (request_type.get_type() != bitmap::type::standard || request == 0x04 || + request > 0x12) { + return standard_request_types::invalid; + } + + return static_cast(request); + } + + bitmap const request_type; + hal::byte const request; + u16 const value; + u16 const index; + u16 const length; }; } // namespace hal::v5::usb From b1c0e04c4b9a7691180a29730c6e1ec923451208 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sat, 6 Sep 2025 22:03:46 -0700 Subject: [PATCH 10/18] :white_check_mark: Tested enumerator Simulated host by sending enumerator requests and having it respond. Correct payloads were verified --- include/libhal-util/usb/descriptors.hpp | 34 +-- include/libhal-util/usb/enumerator.hpp | 78 ++++--- v5/tests/usb.test.cpp | 271 ++---------------------- 3 files changed, 77 insertions(+), 306 deletions(-) diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp index 4c23079..90540a0 100644 --- a/include/libhal-util/usb/descriptors.hpp +++ b/include/libhal-util/usb/descriptors.hpp @@ -88,32 +88,32 @@ struct device constexpr u8& device_class() { - return m_packed_arr[1]; + return m_packed_arr[2]; } constexpr u8& device_sub_class() { - return m_packed_arr[2]; + return m_packed_arr[3]; } constexpr u8& device_protocol() { - return m_packed_arr[3]; + return m_packed_arr[4]; } constexpr u16& id_vendor() { - return *reinterpret_cast(&m_packed_arr[5]); + return *reinterpret_cast(&m_packed_arr[6]); } constexpr u16& id_product() { - return *reinterpret_cast(&m_packed_arr[7]); + return *reinterpret_cast(&m_packed_arr[8]); } constexpr u16& bcd_device() { - return *reinterpret_cast(&m_packed_arr[9]); + return *reinterpret_cast(&m_packed_arr[10]); } operator std::span() const @@ -128,7 +128,7 @@ struct device private: constexpr u8& max_packet_size() { - return m_packed_arr[4]; + return m_packed_arr[5]; } constexpr u8& manufacturer_index() @@ -173,7 +173,7 @@ struct configuration constexpr bitmap(bool p_self_powered, bool p_remote_wakeup) { - m_bitmap = (1 << 7) & (p_self_powered << 6) & (p_remote_wakeup << 5); + m_bitmap = (1 << 7) | (p_self_powered << 6) | (p_remote_wakeup << 5); } constexpr u8 to_byte() @@ -209,13 +209,14 @@ struct configuration u8 idx = 0; // Anything marked with 0 is to be populated at enumeration time - m_packed_arr[idx++] = 0; // Total Length - m_packed_arr[idx++] = interfaces.size(); // number of interfaces - m_packed_arr[idx++] = 0; // Config number - m_packed_arr[idx++] = 0; // Configuration name string index - - m_packed_arr[idx++] = p_attributes.to_byte(); - m_packed_arr[idx++] = p_max_power; + 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 @@ -231,6 +232,7 @@ struct configuration { return m_packed_arr[5]; } + constexpr u8& max_power() { return m_packed_arr[6]; @@ -239,7 +241,7 @@ struct configuration std::string_view name; std::pmr::vector> interfaces; -private: + // private: constexpr u16& total_length() { return *reinterpret_cast(&m_packed_arr[0]); diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index 72d1d37..ac64e2c 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -16,6 +16,17 @@ // TODO: move to util namespace hal::v5::usb { +template +size_t scatter_span_size(scatter_span ss) +{ + size_t res = 0; + for (auto const& s : ss) { + res += s.size(); + } + + return res; +} + template class enumerator { @@ -56,7 +67,7 @@ class enumerator // Device m_device.manufacturer_index() = cur_str_idx++; - m_device.id_product() = cur_str_idx++; + m_device.product_index() = cur_str_idx++; m_device.serial_number_index() = cur_str_idx++; m_device.num_configurations() = num_configs; @@ -72,10 +83,8 @@ class enumerator for (auto const& iface : config.interfaces) { auto deltas = iface->write_descriptors( [&total_length](scatter_span p_data) { - total_length += p_data.size(); - }, - cur_iface_idx, - cur_str_idx); + total_length += scatter_span_size(p_data); + }); cur_iface_idx += deltas.iface_idxes; cur_str_idx += deltas.str_idxes; @@ -119,9 +128,7 @@ class enumerator handle_standard_device_request(req); m_ctrl_ep->write({}); // Send ZLP to complete Data Transaction if (static_cast(req.request) == - standard_request_types::get_descriptor && - (static_cast((req.value & 0xFF << 8) >> 8) == - descriptor_type::configuration)) { + standard_request_types::set_configuration) { finished_enumeration = true; m_ctrl_ep->on_receive( [this](on_receive_tag) { m_has_setup_packet = true; }); @@ -157,8 +164,9 @@ class enumerator return; } - if (req.get_standard_request() == std_req_type::get_descriptor && - static_cast(req.value & 0xFF << 8) == + 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); @@ -226,15 +234,17 @@ class enumerator void process_get_descriptor(setup_request& req) { - hal::byte desc_type = req.value & 0xFF << 8; + 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 scatter_arr = make_scatter_bytes( + auto header = std::to_array({ constants::device_desc_size, - static_cast(descriptor_type::device) }), - m_device); + static_cast(descriptor_type::device) }); + m_device.max_packet_size() = + static_cast(m_ctrl_ep->info().size); + auto scatter_arr = make_scatter_bytes(header, m_device); m_ctrl_ep->write(static_cast>(scatter_arr)); break; } @@ -242,28 +252,29 @@ class enumerator case descriptor_type::configuration: { configuration& conf = m_configs[desc_idx]; if (req.length <= 2) { // requesting total length - auto scatter_tot_len = - make_scatter_bytes(to_le_bytes(conf.total_length())); + auto tl = setup_packet::to_le_bytes(conf.total_length()); + auto scatter_tot_len = make_scatter_bytes(tl); m_ctrl_ep->write(scatter_tot_len); break; } // if its >2 then assumed to be requesting desc type u16 total_size = constants::config_desc_size; - scatter_span scatter_conf_hdr = make_scatter_bytes( - { constants::config_desc_size, descriptor_type::configuration }, - m_configs[desc_idx]); + auto conf_hdr = + std::to_array({ constants::config_desc_size, + static_cast(descriptor_type::configuration) }); + auto scatter_conf_hdr = + make_scatter_bytes(conf_hdr, m_configs[desc_idx]); m_ctrl_ep->write(scatter_conf_hdr); - for (size_t i = 0; i < conf.interfaces.size(); i++) { - auto iface = conf.interfaces[i]; - iface->write_descriptors( - [this, &total_size, i](scatter_span byte_stream) { + 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 += byte_stream.size(); - }, - i); + total_size += scatter_span_size(byte_stream); + }); } if (total_size != req.length) { @@ -276,10 +287,14 @@ class enumerator case descriptor_type::string: { if (desc_idx == 0) { - auto scatter_arr = make_scatter_bytes( + auto s_hdr = std::to_array({ static_cast(m_lang_str.length()), - static_cast(descriptor_type::string) }), - std::span(m_lang_str)); + static_cast(descriptor_type::string) }); + auto scatter_arr = make_scatter_bytes( + s_hdr, + std::span( + reinterpret_cast(m_lang_str.data()), + m_lang_str.size())); m_ctrl_ep->write(scatter_arr); break; } @@ -379,9 +394,10 @@ class enumerator // 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) - auto scatter_arr_str_hdr = make_scatter_bytes( + auto hdr_arr = std::to_array({ static_cast(conf_sv->length()), - static_cast(descriptor_type::string) })); + static_cast(descriptor_type::string) }); + auto scatter_arr_str_hdr = make_scatter_bytes(hdr_arr); m_ctrl_ep->write(scatter_arr_str_hdr); if (write_full_desc) { diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index 8790f28..81d7386 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -24,10 +24,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -213,7 +213,7 @@ class mock_usb_control_endpoint : public control_endpoint struct mock : public interface { - constexpr mock(std::u16string_view p_name) + constexpr mock(std::string_view p_name) : name(p_name) { } @@ -293,7 +293,7 @@ struct mock : public interface 4, // interface_protocol 0 // interface name index }; - std::u16string_view name; + std::string_view name; }; class iad_mock : public interface @@ -301,8 +301,8 @@ class iad_mock : public interface public: ~iad_mock() override = default; - iad_mock(std::u16string_view p_iface_name_one, // NOLINT - std::u16string_view p_iface_name_two) + iad_mock(std::string_view p_iface_name_one, // NOLINT + std::string_view p_iface_name_two) : m_name_one(p_iface_name_one) , m_name_two(p_iface_name_two) {}; @@ -412,6 +412,7 @@ class iad_mock : public interface u16 driver_get_status(setup_packet p_pkt) { if (p_pkt.get_recipient() != setup_packet::recipient::interface) { + // std::println("Unsupported recipient"); safe_throw(hal::operation_not_supported(this)); } @@ -430,6 +431,7 @@ class iad_mock : public interface { std::ignore = p_selector; if (p_pkt.get_recipient() != setup_packet::recipient::interface) { + // std::println("Unsupported recipient"); safe_throw(hal::operation_not_supported(this)); } @@ -440,6 +442,7 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { m_iface_two.feature = p_set; } else { + // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -453,6 +456,7 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { return m_iface_two.alt_settings; } else { + // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -466,6 +470,7 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { m_iface_two.alt_settings = alt_setting; } else { + // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -480,9 +485,9 @@ class iad_mock : public interface public: mock_iface_descriptor m_iface_one; - std::u16string_view m_name_one; + std::string_view m_name_one; mock_iface_descriptor m_iface_two; - std::u16string_view m_name_two; + std::string_view m_name_two; bool m_wrote_descriptors = false; }; @@ -538,260 +543,8 @@ void simulate_sending_payload( // return vec; // } -class mock_serial : public hal::serial -{ - void driver_configure(settings const& p_settings) override - { - std::ignore = p_settings; - } - - write_t driver_write(std::span p_data) override - { - std::string_view sv(reinterpret_cast(p_data.data()), - p_data.size()); - return { .data = p_data }; - } - - read_t driver_read(std::span p_data) override - { - std::ignore = p_data; - return { .data = {}, .available = 0, .capacity = 0 }; - } - - void driver_flush() override - { - } -}; - } // namespace boost::ut::suite<"usb_test"> usb_test = [] { // TODO(#78): Add usb utility tests }; - -boost::ut::suite<"enumeration_test"> enumeration_test = [] { - using namespace boost::ut; - using namespace hal::literals; - namespace pmr = std::pmr; - static std::array iface_buf; - - iface_buf.fill(0); - device dev({ .p_bcd_usb = 0x0002, - .p_device_class = class_code::application_specific, - .p_device_subclass = 0, - .p_device_protocol = 0, - .p_id_vendor = 0xffff, - .p_id_product = 0x0000, - .p_bcd_device = 0x0001, - .p_manufacturer = u"libhal", - .p_product = u"Unit Test", - .p_serial_number_str = u"123456789" }); - - pmr::monotonic_buffer_resource pool(iface_buf.data(), std::size(iface_buf)); - std::array conf_arr{ configuration{ - u"Test Config", - configuration::bitmap(true, false), - 1, - &pool, - make_strong_ptr(&pool, mock(u"Mock Iface")) } }; - - mock_usb_control_endpoint ctrl_ep; - ctrl_ep.m_endpoint.m_info = { .size = 8, .number = 0, .stalled = false }; - auto ctrl_ptr = make_strong_ptr(&pool, ctrl_ep); - auto const conf_arr_ptr = - make_strong_ptr>(&pool, conf_arr); - auto dev_ptr = make_strong_ptr(&pool, dev); - auto console_ptr = make_strong_ptr(&pool, mock_serial()); - enumerator<1> en{ ctrl_ptr, dev_ptr, conf_arr_ptr, 0x0409, 1, false }; - - "basic usb enumeration test"_test = [&en, &ctrl_ptr] { - // Start enumeration process and verify connection - auto f = [&en]() { en.enumerate(); }; - constexpr byte delay_time_ms = 1000; - auto& ctrl_buf = ctrl_ptr->m_out_buf; - std::thread ejh(f); - std::this_thread::sleep_for(std::chrono::milliseconds( - delay_time_ms)); // Should be enough time to connect - expect(that % true == ctrl_ptr->m_is_connected); - ctrl_buf.clear(); - - u16 expected_addr = 0x30; - setup_packet set_addr{ false, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast( - standard_request_types::set_address), - 0x30, - 0, // a - 0 }; - - simulate_sending_payload(ctrl_ptr, set_addr); - ctrl_ptr->simulate_interrupt(); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - expect(that % expected_addr == ctrl_ptr->m_address); - ctrl_buf.clear(); - - // Get device descriptor - u16 desc_t_idx = static_cast(descriptor_type::device) << 8; - setup_packet get_desc( - true, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast(standard_request_types::get_descriptor), - desc_t_idx, - 0, - 18); - simulate_sending_payload(ctrl_ptr, get_desc); - ctrl_ptr->simulate_interrupt(); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - std::span dev_actual(ctrl_buf.data(), 18); - std::array dev_expected{ - 0x12, // length - static_cast(descriptor_type::device), // type - 0x02, // usb bcd - 0x00, - static_cast(class_code::application_specific), - 0, // subclass - 0, // protocol - 8, // max packet size - 0xff, // vendor id - 0xff, - 0, // product id - 0, - 0x1, // bcd device - 0x0, - 1, // manufactures index - 2, // product index - 3, // product index - 1 // num configuration - }; - - expect(that % (span_eq(std::span(dev_expected), dev_actual))); - ctrl_buf.clear(); - - // Get a string descriptor header from device - // where 1 is the manufacture string index - constexpr u16 str_desc_t_idx = - static_cast(descriptor_type::string) << 8 | 1; - setup_packet str_hdr_req( - true, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast(standard_request_types::get_descriptor), - str_desc_t_idx, - 0, - 2); - - simulate_sending_payload(ctrl_ptr, str_hdr_req); - ctrl_ptr->simulate_interrupt(); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - std::array expected_manu_str_hdr{ - static_cast(14), // string is "libhal" - static_cast(descriptor_type::string) - }; - std::span actual_dev_str_hdr(ctrl_buf.data(), 2); - expect(that % (span_eq(std::span(expected_manu_str_hdr), - actual_dev_str_hdr))); - ctrl_buf.clear(); - - // Get a string descriptor from device - setup_packet str_req(str_hdr_req); - str_req.length = expected_manu_str_hdr[0]; - simulate_sending_payload(ctrl_ptr, str_req); - ctrl_ptr->simulate_interrupt(); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - std::u16string_view expected_manu_str = u"libhal"; - - auto expected_manu_str_scatter = - make_scatter_bytes(expected_manu_str_hdr, - std::span(reinterpret_cast( - expected_manu_str.data()), - expected_manu_str.length() * 2)); - auto actual_manu_str_scatter = make_scatter_bytes(ctrl_buf); - expect(that % (scatter_span(expected_manu_str_scatter) == - scatter_span(actual_manu_str_scatter))); - ctrl_buf.clear(); - - // Get Configuration length - setup_packet conf_hdr_req( - true, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast(standard_request_types::get_descriptor), - static_cast(descriptor_type::configuration) << 8, - 0, - 9); - - // Expected Config + interface descriptor - std::array expected_conf_iface_desc{ - // config descriptor - 0x9, // len - static_cast(descriptor_type::configuration), // type - 0x12, // HH: total length - 0x0, // LL: tl - 0x1, // number of interfaces - 0x1, // config value - 0x4, // name string index - 0xc0, // bmattributes (selfpowered = true) - 0x1, // max power - - // Interface descriptor - iface_desc_length, - iface_desc_type, - 0x0, // iface number - 0x0, // alt settings - 0x1, // number of endpoints - 0x2, // class - 0x3, // subclass - 0x4, // protocol - 0x5 // iface name string index - }; - - // Get Configuration descriptor header - simulate_sending_payload(ctrl_ptr, conf_hdr_req); - ctrl_ptr->simulate_interrupt(); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - - auto expected_tl_hh = expected_conf_iface_desc[2]; - auto expected_tl_ll = expected_conf_iface_desc[3]; - auto expected_total_len = - setup_packet::from_le_bytes(expected_tl_hh, expected_tl_ll); - - auto actual_tl_hh = ctrl_buf[2]; - auto actual_tl_ll = ctrl_buf[3]; - auto actual_total_len = - setup_packet::from_le_bytes(actual_tl_hh, actual_tl_ll); - expect(that % expected_total_len == actual_total_len); - ctrl_buf.clear(); - - // Get Configuration Descriptor + interface descriptor + endpoint descriptor - setup_packet conf_req(conf_hdr_req); - conf_req.length = expected_total_len; - simulate_sending_payload(ctrl_ptr, conf_req); - ctrl_ptr->simulate_interrupt(); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - expect(that % (span_eq(std::span(expected_conf_iface_desc), - std::span(ctrl_buf)))); - - // Set configuration - setup_packet set_conf_req( - false, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast(standard_request_types::set_configuration), - 1, - 0, - 0); - - simulate_sending_payload(ctrl_ptr, set_conf_req); - ctrl_ptr->simulate_interrupt(); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - - ejh.join(); - // Verify active config - expect(that % - (span_eq(std::span(expected_conf_iface_desc.data() + 2, 7), - std::span(en.get_active_configuration())))); - }; -}; - } // namespace hal::v5::usb From 778857c880af1a9ec9c93427a48b26f006822d9a Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 8 Sep 2025 01:59:08 -0700 Subject: [PATCH 11/18] :recycle: Moved endpoint utils to their own file Changed usb.hpp to be a shorthand for including all usb utils --- include/libhal-util/usb/endpoints.hpp | 48 ++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/include/libhal-util/usb/endpoints.hpp b/include/libhal-util/usb/endpoints.hpp index d65a49f..f53cb37 100644 --- a/include/libhal-util/usb/endpoints.hpp +++ b/include/libhal-util/usb/endpoints.hpp @@ -21,101 +21,133 @@ namespace hal::v5 { // TODO(#79): Add doxygen docs to USB APIs +<<<<<<< HEAD inline void write(usb_control_endpoint& p_endpoint, +======= +inline void write(usb::control_endpoint& p_endpoint, +>>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) scatter_span p_data_out) { p_endpoint.write(p_data_out); } +<<<<<<< HEAD inline void write_and_flush(usb_control_endpoint& p_endpoint, +======= +inline void write_and_flush(usb::control_endpoint& p_endpoint, +>>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } +<<<<<<< HEAD inline void write(usb_control_endpoint& p_endpoint, +======= +inline void write(usb::control_endpoint& p_endpoint, +>>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } +<<<<<<< HEAD inline void write_and_flush(usb_control_endpoint& p_endpoint, +======= +inline void write_and_flush(usb::control_endpoint& p_endpoint, +>>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } +<<<<<<< HEAD inline void write(usb_in_endpoint& p_endpoint, +======= +inline void write(usb::in_endpoint& p_endpoint, +>>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) scatter_span p_data_out) { p_endpoint.write(p_data_out); } +<<<<<<< HEAD inline void write_and_flush(usb_in_endpoint& p_endpoint, +======= +inline void write_and_flush(usb::in_endpoint& p_endpoint, +>>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } +<<<<<<< HEAD inline void write(usb_in_endpoint& p_endpoint, +======= +inline void write(usb::in_endpoint& p_endpoint, +>>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } +<<<<<<< HEAD inline void write_and_flush(usb_in_endpoint& p_endpoint, +======= +inline void write_and_flush(usb::in_endpoint& p_endpoint, +>>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } -inline void write(usb_in_endpoint& p_endpoint, +inline void write(usb::in_endpoint& p_endpoint, spanable_bytes auto... p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out...)); } -inline void write_and_flush(usb_in_endpoint& p_endpoint, +inline void write_and_flush(usb::in_endpoint& p_endpoint, spanable_bytes auto... p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out...)); p_endpoint.write({}); } -inline auto read(usb_out_endpoint& p_endpoint, +inline auto read(usb::out_endpoint& p_endpoint, scatter_span p_data_out) { return p_endpoint.read(p_data_out); } -inline auto read(usb_out_endpoint& p_endpoint, std::span p_data_out) +inline auto read(usb::out_endpoint& p_endpoint, std::span p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); } -inline auto read(usb_out_endpoint& p_endpoint, +inline auto read(usb::out_endpoint& p_endpoint, spanable_writable_bytes auto... p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); } -inline auto read(usb_control_endpoint& p_endpoint, +inline auto read(usb::control_endpoint& p_endpoint, scatter_span p_data_out) { return p_endpoint.read(p_data_out); } -inline auto read(usb_control_endpoint& p_endpoint, +inline auto read(usb::control_endpoint& p_endpoint, std::span p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); } -inline auto read(usb_control_endpoint& p_endpoint, +inline auto read(usb::control_endpoint& p_endpoint, spanable_writable_bytes auto... p_data_out) { return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); From 6677c554b019ec9f613a58f3a70f7e780752a3dd Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 8 Sep 2025 02:20:17 -0700 Subject: [PATCH 12/18] :bug: Enumerator wait loop got optmized out, volatile prevents this --- include/libhal-util/usb/enumerator.hpp | 2 +- v5/tests/usb.test.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index ac64e2c..62de9ea 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -96,7 +96,7 @@ class enumerator // TODO: Make async bool finished_enumeration = false; - bool waiting_for_data = true; + bool volatile waiting_for_data = true; using on_receive_tag = usb_control_endpoint::on_receive_tag; using standard_request_types = setup_request::standard_request_types; diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index 81d7386..f03b933 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include From c04d212a2f1d64fb68a448b68d253613ef8e1b79 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Mon, 29 Sep 2025 00:46:27 -0700 Subject: [PATCH 13/18] :bug: Fixed logic to have enumeration work on real hardware --- include/libhal-util/usb/descriptors.hpp | 67 ++--- include/libhal-util/usb/enumerator.hpp | 309 +++++++++++++++++------- include/libhal-util/usb/utils.hpp | 67 +---- v5/tests/usb.test.cpp | 270 ++++++++++++++++++++- 4 files changed, 525 insertions(+), 188 deletions(-) diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp index 90540a0..f163e54 100644 --- a/include/libhal-util/usb/descriptors.hpp +++ b/include/libhal-util/usb/descriptors.hpp @@ -1,26 +1,39 @@ -#pragma once +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -// TODO: Move to util -/* TODO: - Class, subclass, proto validator - Device qualifer descriptor - Other speed descriptor - Interface Association Descriptor - */ +#pragma once #include -#include -#include -#include #include #include #include +#include + +#include +#include +#include +#include -#include "../as_bytes.hpp" #include "libhal-util/as_bytes.hpp" #include "libhal/units.hpp" #include "utils.hpp" -#include + +/* TODO (PhazonicRidley): + Class, subclass, proto validator + Device qualifer descriptor (happens between device and config) + Other speed descriptor (happens with configuration) +*/ namespace hal::v5::usb { @@ -38,9 +51,9 @@ struct device u16 p_id_vendor; // NOLINT u16 p_id_product; u16 p_bcd_device; - std::string_view p_manufacturer; // NOLINT - std::string_view p_product; - std::string_view p_serial_number_str; + std::u16string_view p_manufacturer; + std::u16string_view p_product; + std::u16string_view p_serial_number_str; }; constexpr device(device_arguments&& args) @@ -81,7 +94,7 @@ struct device m_packed_arr[idx++] = 0; // Number of possible configurations }; - constexpr u16& bcd_usb() + u16& bcd_usb() { return *reinterpret_cast(&m_packed_arr[0]); } @@ -101,17 +114,17 @@ struct device return m_packed_arr[4]; } - constexpr u16& id_vendor() + u16& id_vendor() { return *reinterpret_cast(&m_packed_arr[6]); } - constexpr u16& id_product() + u16& id_product() { return *reinterpret_cast(&m_packed_arr[8]); } - constexpr u16& bcd_device() + u16& bcd_device() { return *reinterpret_cast(&m_packed_arr[10]); } @@ -121,9 +134,9 @@ struct device return m_packed_arr; } - std::string_view manufacturer_str; - std::string_view product_str; - std::string_view serial_number_str; + std::u16string_view manufacturer_str; + std::u16string_view product_str; + std::u16string_view serial_number_str; private: constexpr u8& max_packet_size() @@ -196,7 +209,7 @@ struct configuration }; template - constexpr configuration(std::string_view p_name, + constexpr configuration(std::u16string_view p_name, bitmap&& p_attributes, u8&& p_max_power, std::pmr::polymorphic_allocator<> p_allocator, @@ -238,11 +251,11 @@ struct configuration return m_packed_arr[6]; } - std::string_view name; - std::pmr::vector> interfaces; + std::u16string_view name; + std::pmr::vector> interfaces; // private: - constexpr u16& total_length() + u16& total_length() { return *reinterpret_cast(&m_packed_arr[0]); } diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index 62de9ea..ed20dc2 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -1,23 +1,48 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #pragma once -#include #include +#include + +#include +#include + +#include +#include +#include +#include +#include + #include #include #include +#include #include #include -#include -#include #include "descriptors.hpp" +#include "libhal-util/as_bytes.hpp" +#include "libhal-util/usb/endpoints.hpp" #include "utils.hpp" // TODO: move to util namespace hal::v5::usb { template -size_t scatter_span_size(scatter_span ss) +constexpr size_t scatter_span_size(scatter_span ss) { size_t res = 0; for (auto const& s : ss) { @@ -26,17 +51,64 @@ size_t scatter_span_size(scatter_span ss) 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, - device&& p_device, - std::array&& p_configs, - std::string_view p_lang_str, - u8&& p_starting_str_idx) + 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, + bool enumerate_immediately = true) : m_ctrl_ep(p_ctrl_ep) , m_device(p_device) , m_configs(p_configs) @@ -66,19 +138,19 @@ 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; // Configurations - for (auto i = 0; i < num_configs; i++) { - configuration& config = m_configs[i]; + 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; } - 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) { auto deltas = iface->write_descriptors( @@ -89,6 +161,7 @@ class enumerator cur_iface_idx += deltas.iface_idxes; cur_str_idx += deltas.str_idxes; } + config.num_interfaces() = cur_iface_idx; config.total_length() = total_length; } @@ -98,11 +171,12 @@ class enumerator bool finished_enumeration = false; bool volatile waiting_for_data = true; - using on_receive_tag = usb_control_endpoint::on_receive_tag; - using standard_request_types = setup_request::standard_request_types; + using on_receive_tag = control_endpoint::on_receive_tag; + m_ctrl_ep->on_receive( [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); m_ctrl_ep->connect(true); + std::array raw_req; do { // Seriously, make this async @@ -114,10 +188,20 @@ class enumerator 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) { - safe_throw(hal::message_size(this)); + + safe_throw(hal::message_size(num_bytes_read, this)); } - setup_request req(raw_req); + // + // for (auto const& el : raw_req) { + // + // } + // + auto req = from_span(raw_req); if (req.request_type.get_recipient() != setup_request::bitmap::recipient::device) { @@ -126,7 +210,8 @@ class enumerator // TODO: Handle exception handle_standard_device_request(req); - m_ctrl_ep->write({}); // Send ZLP to complete Data Transaction + // m_ctrl_ep->write({}); // Send ZLP to complete Data + // Transaction if (static_cast(req.request) == standard_request_types::set_configuration) { finished_enumeration = true; @@ -154,6 +239,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); @@ -175,9 +262,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( @@ -202,7 +298,9 @@ class enumerator switch (req.get_standard_request()) { case standard_request_types::set_address: { + m_ctrl_ep->write({}); m_ctrl_ep->set_address(req.value); + break; } @@ -222,7 +320,7 @@ class enumerator } case standard_request_types::set_configuration: { - m_active_conf = &m_configs[req.value]; + m_active_conf = &(m_configs->at(req.value - 1)); break; } @@ -241,33 +339,36 @@ class enumerator 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 = make_scatter_bytes(header, m_device); - m_ctrl_ep->write(static_cast>(scatter_arr)); + 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)); + break; } case descriptor_type::configuration: { - configuration& conf = m_configs[desc_idx]; - if (req.length <= 2) { // requesting total length - auto tl = setup_packet::to_le_bytes(conf.total_length()); - auto scatter_tot_len = make_scatter_bytes(tl); - m_ctrl_ep->write(scatter_tot_len); - break; - } - - // if its >2 then assumed to be requesting desc type - u16 total_size = constants::config_desc_size; + 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_hdr = - make_scatter_bytes(conf_hdr, m_configs[desc_idx]); + auto scatter_conf_pair = make_sub_scatter_bytes( + req.length, conf_hdr, static_cast>(conf)); - m_ctrl_ep->write(scatter_conf_hdr); + m_ctrl_ep->write(scatter_span(scatter_conf_pair.first) + .first(scatter_conf_pair.second)); + // Return early if the only thing requested was the config descriptor + if (req.length <= constants::config_desc_size) { + 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 }, @@ -277,28 +378,30 @@ class enumerator }); } - if (total_size != req.length) { - safe_throw( - hal::exception(this)); // TODO: Make specific exception for this - } + 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) { + auto s_hdr = - std::to_array({ static_cast(m_lang_str.length()), - static_cast(descriptor_type::string) }); - auto scatter_arr = make_scatter_bytes( - s_hdr, - std::span( - reinterpret_cast(m_lang_str.data()), - m_lang_str.size())); - m_ctrl_ep->write(scatter_arr); + 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({}); break; } - handle_str_descriptors(desc_idx, req.length > 2); // Can throw + handle_str_descriptors(desc_idx, req.length); // Can throw break; } @@ -310,12 +413,12 @@ class enumerator } } - void handle_str_descriptors(u8 const target_idx, bool const write_full_desc) + 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, write_full_desc); + auto r = write_cfg_str_descriptor(target_idx, p_len); if (!r) { safe_throw(hal::argument_out_of_domain(this)); } @@ -324,23 +427,26 @@ class enumerator } if (m_iface_for_str_desc.has_value() && - m_iface_for_str_desc->first == target_idx && write_full_desc) { - m_iface_for_str_desc->second->write_descriptors( - [this](scatter_span desc) { m_ctrl_ep->write(desc); }, - target_idx); - return; + 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; + } } // Iterate through every interface now to find a match - auto f = [this, write_full_desc](scatter_span desc) { - if (write_full_desc) { - m_ctrl_ep->write(desc); + 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( + std::array desc_type{ static_cast( descriptor_type::string) }; auto scatter_str_hdr = make_scatter_bytes(std::span(&desc[0][0], 1), desc_type); - m_ctrl_ep->write(scatter_str_hdr); + hal::v5::write_and_flush(*m_ctrl_ep, scatter_str_hdr); } }; @@ -353,7 +459,7 @@ class enumerator } } - for (configuration& conf : m_configs) { + for (configuration const& conf : *m_configs) { for (auto const& iface : conf.interfaces) { auto res = iface->write_string_descriptor(f, target_idx); if (res) { @@ -363,57 +469,72 @@ class enumerator } } - bool write_cfg_str_descriptor(u8 const target_idx, bool const write_full_desc) + 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 conf_sv; + std::optional opt_conf_sv; if (target_idx == (m_starting_str_idx + dev_manu_offset)) { - conf_sv = m_device.manufacturer_str; + opt_conf_sv = m_device->manufacturer_str; } else if (target_idx == (m_starting_str_idx + dev_prod_offset)) { - conf_sv = m_device.product_str; + opt_conf_sv = m_device->product_str; } else if (target_idx == (m_starting_str_idx + dev_sn_offset)) { - 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)) { - conf_sv = conf.name; + opt_conf_sv = conf.name; } } } - if (conf_sv == std::nullopt) { + 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) - auto hdr_arr = - std::to_array({ static_cast(conf_sv->length()), - static_cast(descriptor_type::string) }); - auto scatter_arr_str_hdr = make_scatter_bytes(hdr_arr); - m_ctrl_ep->write(scatter_arr_str_hdr); - - if (write_full_desc) { - // Hack to force scatter_span to accept string view - auto scatter_arr = make_scatter_bytes(std::span( - reinterpret_cast(conf_sv->data()), conf_sv->size())); - m_ctrl_ep->write(scatter_arr); + + auto sv = opt_conf_sv.value(); + std::mbstate_t state{}; + for (wchar_t const wc : sv) { + std::array tmp; + size_t len = std::wcrtomb(tmp.data(), wc, &state); + if (len == static_cast(-1)) { + + continue; + } + + for (size_t i = 0; i < len; i++) { + } } + auto const conf_sv_span = hal::as_bytes(opt_conf_sv.value()); + auto desc_len = static_cast((conf_sv_span.size() + 2)); + + 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); + return true; } - strong_ptr m_ctrl_ep; - device m_device; - std::array m_configs; - std::string_view m_lang_str; + 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; diff --git a/include/libhal-util/usb/utils.hpp b/include/libhal-util/usb/utils.hpp index 0eeb8a3..5f2aacf 100644 --- a/include/libhal-util/usb/utils.hpp +++ b/include/libhal-util/usb/utils.hpp @@ -27,6 +27,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 @@ -87,62 +89,15 @@ enum class descriptor_type : hal::byte superspeed_endpoint_isochronous_companion = 0x31, }; -struct setup_request +constexpr setup_packet from_span(std::span raw_req) { - using bitmap = v5::usb_interface::req_bitmap; - - 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 - }; - - constexpr setup_request(bitmap p_request_type, - hal::byte p_request, // NOLINT - u16 p_value, - u16 p_index, - u16 p_length) - : request_type(p_request_type) - , request(p_request) - , value(p_value) - , index(p_index) - , length(p_length) {}; - - constexpr setup_request(std::span raw_req) - : request_type(raw_req[0]) - , request(raw_req[1]) - , value(from_le_bytes(raw_req[2], raw_req[3])) - , index(from_le_bytes(raw_req[4], raw_req[5])) - , length(from_le_bytes(raw_req[6], raw_req[7])) - - {}; - - // Probably better semantics for this function, this disjoints - [[nodiscard]] constexpr standard_request_types get_standard_request() const - { - if (request_type.get_type() != bitmap::type::standard || request == 0x04 || - request > 0x12) { - return standard_request_types::invalid; - } - - return static_cast(request); - } - - bitmap const request_type; - hal::byte const request; - u16 const value; - u16 const index; - u16 const length; -}; + 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/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index f03b933..8790f28 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -212,7 +213,7 @@ class mock_usb_control_endpoint : public control_endpoint struct mock : public interface { - constexpr mock(std::string_view p_name) + constexpr mock(std::u16string_view p_name) : name(p_name) { } @@ -292,7 +293,7 @@ struct mock : public interface 4, // interface_protocol 0 // interface name index }; - std::string_view name; + std::u16string_view name; }; class iad_mock : public interface @@ -300,8 +301,8 @@ class iad_mock : public interface public: ~iad_mock() override = default; - iad_mock(std::string_view p_iface_name_one, // NOLINT - std::string_view p_iface_name_two) + iad_mock(std::u16string_view p_iface_name_one, // NOLINT + std::u16string_view p_iface_name_two) : m_name_one(p_iface_name_one) , m_name_two(p_iface_name_two) {}; @@ -411,7 +412,6 @@ class iad_mock : public interface u16 driver_get_status(setup_packet p_pkt) { if (p_pkt.get_recipient() != setup_packet::recipient::interface) { - // std::println("Unsupported recipient"); safe_throw(hal::operation_not_supported(this)); } @@ -430,7 +430,6 @@ class iad_mock : public interface { std::ignore = p_selector; if (p_pkt.get_recipient() != setup_packet::recipient::interface) { - // std::println("Unsupported recipient"); safe_throw(hal::operation_not_supported(this)); } @@ -441,7 +440,6 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { m_iface_two.feature = p_set; } else { - // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -455,7 +453,6 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { return m_iface_two.alt_settings; } else { - // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -469,7 +466,6 @@ class iad_mock : public interface } else if (iface_idx == m_iface_two.num) { m_iface_two.alt_settings = alt_setting; } else { - // std::println("Invalid interface index"); safe_throw(hal::operation_not_supported(this)); } } @@ -484,9 +480,9 @@ class iad_mock : public interface public: mock_iface_descriptor m_iface_one; - std::string_view m_name_one; + std::u16string_view m_name_one; mock_iface_descriptor m_iface_two; - std::string_view m_name_two; + std::u16string_view m_name_two; bool m_wrote_descriptors = false; }; @@ -542,8 +538,260 @@ void simulate_sending_payload( // return vec; // } +class mock_serial : public hal::serial +{ + void driver_configure(settings const& p_settings) override + { + std::ignore = p_settings; + } + + write_t driver_write(std::span p_data) override + { + std::string_view sv(reinterpret_cast(p_data.data()), + p_data.size()); + return { .data = p_data }; + } + + read_t driver_read(std::span p_data) override + { + std::ignore = p_data; + return { .data = {}, .available = 0, .capacity = 0 }; + } + + void driver_flush() override + { + } +}; + } // namespace boost::ut::suite<"usb_test"> usb_test = [] { // TODO(#78): Add usb utility tests }; + +boost::ut::suite<"enumeration_test"> enumeration_test = [] { + using namespace boost::ut; + using namespace hal::literals; + namespace pmr = std::pmr; + static std::array iface_buf; + + iface_buf.fill(0); + device dev({ .p_bcd_usb = 0x0002, + .p_device_class = class_code::application_specific, + .p_device_subclass = 0, + .p_device_protocol = 0, + .p_id_vendor = 0xffff, + .p_id_product = 0x0000, + .p_bcd_device = 0x0001, + .p_manufacturer = u"libhal", + .p_product = u"Unit Test", + .p_serial_number_str = u"123456789" }); + + pmr::monotonic_buffer_resource pool(iface_buf.data(), std::size(iface_buf)); + std::array conf_arr{ configuration{ + u"Test Config", + configuration::bitmap(true, false), + 1, + &pool, + make_strong_ptr(&pool, mock(u"Mock Iface")) } }; + + mock_usb_control_endpoint ctrl_ep; + ctrl_ep.m_endpoint.m_info = { .size = 8, .number = 0, .stalled = false }; + auto ctrl_ptr = make_strong_ptr(&pool, ctrl_ep); + auto const conf_arr_ptr = + make_strong_ptr>(&pool, conf_arr); + auto dev_ptr = make_strong_ptr(&pool, dev); + auto console_ptr = make_strong_ptr(&pool, mock_serial()); + enumerator<1> en{ ctrl_ptr, dev_ptr, conf_arr_ptr, 0x0409, 1, false }; + + "basic usb enumeration test"_test = [&en, &ctrl_ptr] { + // Start enumeration process and verify connection + auto f = [&en]() { en.enumerate(); }; + constexpr byte delay_time_ms = 1000; + auto& ctrl_buf = ctrl_ptr->m_out_buf; + std::thread ejh(f); + std::this_thread::sleep_for(std::chrono::milliseconds( + delay_time_ms)); // Should be enough time to connect + expect(that % true == ctrl_ptr->m_is_connected); + ctrl_buf.clear(); + + u16 expected_addr = 0x30; + setup_packet set_addr{ false, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast( + standard_request_types::set_address), + 0x30, + 0, // a + 0 }; + + simulate_sending_payload(ctrl_ptr, set_addr); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + expect(that % expected_addr == ctrl_ptr->m_address); + ctrl_buf.clear(); + + // Get device descriptor + u16 desc_t_idx = static_cast(descriptor_type::device) << 8; + setup_packet get_desc( + true, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast(standard_request_types::get_descriptor), + desc_t_idx, + 0, + 18); + simulate_sending_payload(ctrl_ptr, get_desc); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + std::span dev_actual(ctrl_buf.data(), 18); + std::array dev_expected{ + 0x12, // length + static_cast(descriptor_type::device), // type + 0x02, // usb bcd + 0x00, + static_cast(class_code::application_specific), + 0, // subclass + 0, // protocol + 8, // max packet size + 0xff, // vendor id + 0xff, + 0, // product id + 0, + 0x1, // bcd device + 0x0, + 1, // manufactures index + 2, // product index + 3, // product index + 1 // num configuration + }; + + expect(that % (span_eq(std::span(dev_expected), dev_actual))); + ctrl_buf.clear(); + + // Get a string descriptor header from device + // where 1 is the manufacture string index + constexpr u16 str_desc_t_idx = + static_cast(descriptor_type::string) << 8 | 1; + setup_packet str_hdr_req( + true, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast(standard_request_types::get_descriptor), + str_desc_t_idx, + 0, + 2); + + simulate_sending_payload(ctrl_ptr, str_hdr_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + std::array expected_manu_str_hdr{ + static_cast(14), // string is "libhal" + static_cast(descriptor_type::string) + }; + std::span actual_dev_str_hdr(ctrl_buf.data(), 2); + expect(that % (span_eq(std::span(expected_manu_str_hdr), + actual_dev_str_hdr))); + ctrl_buf.clear(); + + // Get a string descriptor from device + setup_packet str_req(str_hdr_req); + str_req.length = expected_manu_str_hdr[0]; + simulate_sending_payload(ctrl_ptr, str_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + std::u16string_view expected_manu_str = u"libhal"; + + auto expected_manu_str_scatter = + make_scatter_bytes(expected_manu_str_hdr, + std::span(reinterpret_cast( + expected_manu_str.data()), + expected_manu_str.length() * 2)); + auto actual_manu_str_scatter = make_scatter_bytes(ctrl_buf); + expect(that % (scatter_span(expected_manu_str_scatter) == + scatter_span(actual_manu_str_scatter))); + ctrl_buf.clear(); + + // Get Configuration length + setup_packet conf_hdr_req( + true, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast(standard_request_types::get_descriptor), + static_cast(descriptor_type::configuration) << 8, + 0, + 9); + + // Expected Config + interface descriptor + std::array expected_conf_iface_desc{ + // config descriptor + 0x9, // len + static_cast(descriptor_type::configuration), // type + 0x12, // HH: total length + 0x0, // LL: tl + 0x1, // number of interfaces + 0x1, // config value + 0x4, // name string index + 0xc0, // bmattributes (selfpowered = true) + 0x1, // max power + + // Interface descriptor + iface_desc_length, + iface_desc_type, + 0x0, // iface number + 0x0, // alt settings + 0x1, // number of endpoints + 0x2, // class + 0x3, // subclass + 0x4, // protocol + 0x5 // iface name string index + }; + + // Get Configuration descriptor header + simulate_sending_payload(ctrl_ptr, conf_hdr_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + + auto expected_tl_hh = expected_conf_iface_desc[2]; + auto expected_tl_ll = expected_conf_iface_desc[3]; + auto expected_total_len = + setup_packet::from_le_bytes(expected_tl_hh, expected_tl_ll); + + auto actual_tl_hh = ctrl_buf[2]; + auto actual_tl_ll = ctrl_buf[3]; + auto actual_total_len = + setup_packet::from_le_bytes(actual_tl_hh, actual_tl_ll); + expect(that % expected_total_len == actual_total_len); + ctrl_buf.clear(); + + // Get Configuration Descriptor + interface descriptor + endpoint descriptor + setup_packet conf_req(conf_hdr_req); + conf_req.length = expected_total_len; + simulate_sending_payload(ctrl_ptr, conf_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + expect(that % (span_eq(std::span(expected_conf_iface_desc), + std::span(ctrl_buf)))); + + // Set configuration + setup_packet set_conf_req( + false, + setup_packet::type::standard, + setup_packet::recipient::device, + static_cast(standard_request_types::set_configuration), + 1, + 0, + 0); + + simulate_sending_payload(ctrl_ptr, set_conf_req); + ctrl_ptr->simulate_interrupt(); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); + + ejh.join(); + // Verify active config + expect(that % + (span_eq(std::span(expected_conf_iface_desc.data() + 2, 7), + std::span(en.get_active_configuration())))); + }; +}; + } // namespace hal::v5::usb From fd3074d18b9355fedfa51735c5876c62e1e104cd Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Wed, 28 Jan 2026 19:02:09 -0800 Subject: [PATCH 14/18] :recycle: Refactored USB utils into their own folder and namespace --- v5/include/libhal-util/usb/descriptors.hpp | 278 ++++++++++ v5/include/libhal-util/usb/endpoints.hpp | 123 +++++ v5/include/libhal-util/usb/enumerator.hpp | 567 +++++++++++++++++++++ v5/include/libhal-util/usb/utils.hpp | 83 +++ v5/tests/usb.test.cpp | 4 +- 5 files changed, 1053 insertions(+), 2 deletions(-) create mode 100644 v5/include/libhal-util/usb/descriptors.hpp create mode 100644 v5/include/libhal-util/usb/endpoints.hpp create mode 100644 v5/include/libhal-util/usb/enumerator.hpp create mode 100644 v5/include/libhal-util/usb/utils.hpp diff --git a/v5/include/libhal-util/usb/descriptors.hpp b/v5/include/libhal-util/usb/descriptors.hpp new file mode 100644 index 0000000..75f3476 --- /dev/null +++ b/v5/include/libhal-util/usb/descriptors.hpp @@ -0,0 +1,278 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "libhal-util/as_bytes.hpp" +#include "utils.hpp" + +/* TODO (PhazonicRidley): + Class, subclass, proto validator + Device qualifer descriptor (happens between device and config) + Other speed descriptor (happens with configuration) +*/ + +namespace hal::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::usb diff --git a/v5/include/libhal-util/usb/endpoints.hpp b/v5/include/libhal-util/usb/endpoints.hpp new file mode 100644 index 0000000..0bfa259 --- /dev/null +++ b/v5/include/libhal-util/usb/endpoints.hpp @@ -0,0 +1,123 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +namespace hal::v5 { +// TODO(#79): Add doxygen docs to USB APIs +inline void write(usb::control_endpoint& p_endpoint, + scatter_span p_data_out) +{ + p_endpoint.write(p_data_out); +} + +inline void write_and_flush(usb::control_endpoint& p_endpoint, + scatter_span p_data_out) +{ + p_endpoint.write(p_data_out); + p_endpoint.write({}); +} + +inline void write(usb::control_endpoint& p_endpoint, + std::span p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out)); +} + +inline void write_and_flush(usb::control_endpoint& p_endpoint, + std::span p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out)); + p_endpoint.write({}); +} + +inline void write(usb::in_endpoint& p_endpoint, + scatter_span p_data_out) +{ + p_endpoint.write(p_data_out); +} + +inline void write_and_flush(usb::in_endpoint& p_endpoint, + scatter_span p_data_out) +{ + p_endpoint.write(p_data_out); + p_endpoint.write({}); +} + +inline void write(usb::in_endpoint& p_endpoint, + std::span p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out)); +} + +inline void write_and_flush(usb::in_endpoint& p_endpoint, + std::span p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out)); + p_endpoint.write({}); +} + +inline void write(usb::in_endpoint& p_endpoint, + spanable_bytes auto... p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out...)); +} + +inline void write_and_flush(usb::in_endpoint& p_endpoint, + spanable_bytes auto... p_data_out) +{ + p_endpoint.write(make_scatter_bytes(p_data_out...)); + p_endpoint.write({}); +} + +inline auto read(usb::out_endpoint& p_endpoint, + scatter_span p_data_out) +{ + return p_endpoint.read(p_data_out); +} + +inline auto read(usb::out_endpoint& p_endpoint, std::span p_data_out) +{ + return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); +} + +inline auto read(usb::out_endpoint& p_endpoint, + spanable_writable_bytes auto... p_data_out) +{ + return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); +} + +inline auto read(usb::control_endpoint& p_endpoint, + scatter_span p_data_out) +{ + return p_endpoint.read(p_data_out); +} + +inline auto read(usb::control_endpoint& p_endpoint, + std::span p_data_out) +{ + return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); +} + +inline auto read(usb::control_endpoint& p_endpoint, + spanable_writable_bytes auto... p_data_out) +{ + return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); +} +} // namespace hal::v5 diff --git a/v5/include/libhal-util/usb/enumerator.hpp b/v5/include/libhal-util/usb/enumerator.hpp new file mode 100644 index 0000000..7a8a7ee --- /dev/null +++ b/v5/include/libhal-util/usb/enumerator.hpp @@ -0,0 +1,567 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "descriptors.hpp" +#include "libhal-util/as_bytes.hpp" +#include "libhal-util/usb/endpoints.hpp" +#include "utils.hpp" + +namespace hal::usb { + +/** + TODO: (PhazonicRidley) Make class method for scatterspan class + @brief Get the size of a scatter span, meaning how many elements is this + scatterspan viewing. + + @param ss The scatter span to get the size of + @return The number of elements ss is viewing + */ +template +constexpr size_t scatter_span_size(hal::v5::scatter_span ss) +{ + size_t res = 0; + for (auto const& s : ss) { + res += s.size(); + } + + return res; +} + +/** + TODO: (PhazonicRidley) Make class method for scatterspan class + @brief Creates a scatter_span with p_spans and calculates how many internal + spans are used for a given sub scatter_span starting at index zero and going + to the p_count element. + + ```C++ + auto pair = make_scatter_span(n, ...); + auto my_scatter_span = scatter_span(pair.first).first(pair.second) + ``` + + @tparam T The type all collections are to have of. + @tparam Args... A packed set of collections of type T + + @param p_count How many elements (from the start) to take into the subspan + @param p_spans The spans the scatter span is to view into + + @return A pair where the first element contains an array of spans used within + the subscatter span, and the second element is the number of spans within + the sub scatterspan +*/ +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); +} + +/** + TODO: (PhazonicRidley) Make class method for scatterspan class + @brief Create a sub scatter span array from spans of bytes + + @tparam Args... A packed set of collections of bytes + + @param p_count How many elements (from the start) to take into the subspan + @param p_spans The spans the scatter span is to view into + + @return A pair where the first element contains an array of spans used within + the subscatter span, and the second element is the number of bytes within + the sub scatterspan +*/ +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, + bool enumerate_immediately = true) + : m_ctrl_ep(p_ctrl_ep) + , m_device(p_device) + , m_configs(p_configs) + , m_lang_str(p_lang_str) + { + // 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; + + // 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; + } + + 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; + } + + // 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; + + m_ctrl_ep->on_receive( + [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); + m_ctrl_ep->connect(true); + + std::array raw_req; + do { + // TODO: 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) { + + safe_throw(hal::message_size(num_bytes_read, this)); + } + + auto req = setup_packet(raw_req); + + if (req.get_recipient() != setup_packet::recipient::device) { + safe_throw(hal::not_connected(this)); + } + + // ZLP is handled at write site + handle_standard_device_request(req); + 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(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); + }; + } + 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); + + 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)); + + 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)); + + // Return early if the only thing requested was the config descriptor + if (req.length <= constants::config_desc_size) { + 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); + }); + } + + m_ctrl_ep->write({}); + break; + } + + case descriptor_type::string: { + if (desc_idx == 0) { + + 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({}); + break; + } + handle_str_descriptors(desc_idx, req.length); // Can throw + break; + } + + 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; + } + } + + // 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; + } + } + } + } + + 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) + + auto sv = opt_conf_sv.value(); + std::mbstate_t state{}; + for (wchar_t const wc : sv) { + std::array tmp; + size_t len = std::wcrtomb(tmp.data(), wc, &state); + if (len == static_cast(-1)) { + + continue; + } + + for (size_t i = 0; i < len; i++) { + } + } + + auto const conf_sv_span = hal::as_bytes(opt_conf_sv.value()); + auto desc_len = static_cast((conf_sv_span.size() + 2)); + + 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); + + 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; +}; +} // namespace hal::usb diff --git a/v5/include/libhal-util/usb/utils.hpp b/v5/include/libhal-util/usb/utils.hpp new file mode 100644 index 0000000..15e2082 --- /dev/null +++ b/v5/include/libhal-util/usb/utils.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include + +namespace hal::usb { + +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 + +/** + * @brief USB Class Code indicating the type of device or interface. 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 +}; + +/** + * @brief USB Descriptor Type values as defined by the USB specification. + * These values are used in the bDescriptorType field of USB descriptors + * to identify the type and structure of the descriptor data. + */ +enum class descriptor_type : hal::byte +{ + device = 0x1, // Device descriptor + configuration = 0x2, // Configuration descriptor + string = 0x3, // String descriptor + interface = 0x4, // Interface descriptor + endpoint = 0x5, // Endpoint descriptor + device_qualifier = 0x6, // Device qualifier descriptor + other_speed_configuration = 0x7, // Other speed configuration descriptor + interface_power = 0x8, // Interface power descriptor + otg = 0x9, // OTG descriptor + debug = 0xA, // Debug descriptor + interface_association = 0xB, // Interface Association descriptor + security = 0xC, // Security descriptor + key = 0xD, // Key descriptor + encryption_type = 0xE, // Encryption type descriptor + bos = 0xF, // Binary Object Store (BOS) descriptor + device_capability = 0x10, // Device capability descriptor + wireless_endpoint_companion = 0x11, // Wireless endpoint companion descriptor + superspeed_endpoint_companion = + 0x30, // SuperSpeed endpoint companion descriptor + superspeed_endpoint_isochronous_companion = + 0x31, // SuperSpeed isochronous endpoint companion descriptor +}; + +} // namespace hal::usb diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index 8790f28..2954e89 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -35,7 +35,7 @@ #include #include -namespace hal::v5::usb { +namespace hal::usb { namespace { // constexpr setup_packet set_addr_master{ false, @@ -794,4 +794,4 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { }; }; -} // namespace hal::v5::usb +} // namespace hal::usb From 70b04050c73e22beec014bbe5b941b27fe8f0fae Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sat, 31 Jan 2026 21:17:41 -0800 Subject: [PATCH 15/18] :technologist: Updated to support new API for setup_packet in libhal --- include/libhal-util/usb/enumerator.hpp | 2 +- v5/include/libhal-util/usb/enumerator.hpp | 38 ++++---- v5/tests/usb.test.cpp | 111 ++++++++++------------ 3 files changed, 74 insertions(+), 77 deletions(-) diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp index ed20dc2..bfe9c0d 100644 --- a/include/libhal-util/usb/enumerator.hpp +++ b/include/libhal-util/usb/enumerator.hpp @@ -393,7 +393,7 @@ class enumerator 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 lang = setup_packet::to_le_u16(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); diff --git a/v5/include/libhal-util/usb/enumerator.hpp b/v5/include/libhal-util/usb/enumerator.hpp index 7a8a7ee..c7bbcf3 100644 --- a/v5/include/libhal-util/usb/enumerator.hpp +++ b/v5/include/libhal-util/usb/enumerator.hpp @@ -245,13 +245,13 @@ class enumerator auto req = setup_packet(raw_req); - if (req.get_recipient() != setup_packet::recipient::device) { + if (req.get_recipient() != setup_packet::request_recipient::device) { safe_throw(hal::not_connected(this)); } // ZLP is handled at write site handle_standard_device_request(req); - if (static_cast(req.request) == + if (static_cast(req.request()) == standard_request_types::set_configuration) { finished_enumeration = true; m_ctrl_ep->on_receive( @@ -282,18 +282,18 @@ class enumerator auto bytes_read = m_ctrl_ep->read(scatter_read_buf); std::span payload(read_buf.data(), bytes_read); - setup_packet req(payload); + setup_packet req(read_buf); 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) == + static_cast((req.value() & 0xFF << 8) >> 8) == descriptor_type::string) { - handle_str_descriptors(req.value & 0xFF, req.length > 2); + handle_str_descriptors(req.value() & 0xFF, req.length() > 2); - } else if (req.get_recipient() == setup_packet::recipient::device) { + } else if (req.get_recipient() == setup_packet::request_recipient::device) { handle_standard_device_request(req); } else { // Handle iface level requests @@ -309,8 +309,12 @@ class enumerator } 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); + req_handled = iface->handle_request(req.request_type(), + req.request(), + req.value(), + req.index(), + req.length(), + f); if (req_handled) { break; } @@ -331,7 +335,7 @@ class enumerator switch (determine_standard_request(req)) { case standard_request_types::set_address: { m_ctrl_ep->write({}); - m_ctrl_ep->set_address(req.value); + m_ctrl_ep->set_address(req.value()); break; } @@ -352,7 +356,7 @@ class enumerator } case standard_request_types::set_configuration: { - m_active_conf = &(m_configs->at(req.value - 1)); + m_active_conf = &(m_configs->at(req.value() - 1)); break; } @@ -364,8 +368,8 @@ class enumerator 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; + 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: { @@ -374,7 +378,7 @@ class enumerator 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); + make_sub_scatter_bytes(req.length(), header, *m_device); hal::v5::write_and_flush( *m_ctrl_ep, scatter_span(scatter_arr_pair.first) @@ -389,13 +393,13 @@ class enumerator 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)); + req.length(), conf_hdr, static_cast>(conf)); m_ctrl_ep->write(scatter_span(scatter_conf_pair.first) .first(scatter_conf_pair.second)); // Return early if the only thing requested was the config descriptor - if (req.length <= constants::config_desc_size) { + if (req.length() <= constants::config_desc_size) { m_ctrl_ep->write({}); return; } @@ -420,7 +424,7 @@ class enumerator 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 lang = setup_packet::to_le_u16(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); @@ -428,7 +432,7 @@ class enumerator m_ctrl_ep->write({}); break; } - handle_str_descriptors(desc_idx, req.length); // Can throw + handle_str_descriptors(desc_idx, req.length()); // Can throw break; } diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index 2954e89..13d9676 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -38,15 +38,6 @@ namespace hal::usb { namespace { -// constexpr setup_packet set_addr_master{ false, -// setup_packet::type::standard, -// setup_packet::recipient::device, -// static_cast( -// standard_request_types::set_address), -// 0x30, -// 0, // a -// 0 }; - constexpr u8 iface_desc_length = 9; constexpr u8 iface_desc_type = 0x4; constexpr u8 str_desc_type = 0x3; @@ -88,11 +79,11 @@ bool span_ne(std::span const lhs, std::span const rhs) constexpr std::vector pkt_to_scatter(setup_packet const& req) { std::vector vec; - vec.push_back(req.request_type); - vec.push_back(req.request); - vec.append_range(setup_packet::to_le_bytes(req.value)); - vec.append_range(setup_packet::to_le_bytes(req.index)); - vec.append_range(setup_packet::to_le_bytes(req.length)); + vec.push_back(req.request_type()); + vec.push_back(req.request()); + vec.append_range(setup_packet::to_le_u16(req.value())); + vec.append_range(setup_packet::to_le_u16(req.index())); + vec.append_range(setup_packet::to_le_u16(req.length())); return vec; } @@ -411,11 +402,11 @@ class iad_mock : public interface u16 driver_get_status(setup_packet p_pkt) { - if (p_pkt.get_recipient() != setup_packet::recipient::interface) { + if (p_pkt.get_recipient() != setup_packet::request_recipient::interface) { safe_throw(hal::operation_not_supported(this)); } - auto iface_idx = p_pkt.index & 0xFF; + auto iface_idx = p_pkt.index() & 0xFF; if (iface_idx == m_iface_one.num) { return m_iface_one.num; @@ -429,11 +420,11 @@ class iad_mock : public interface void manage_features(setup_packet p_pkt, bool p_set, u16 p_selector) { std::ignore = p_selector; - if (p_pkt.get_recipient() != setup_packet::recipient::interface) { + if (p_pkt.get_recipient() != setup_packet::request_recipient::interface) { safe_throw(hal::operation_not_supported(this)); } - auto iface_idx = p_pkt.index & 0xFF; + auto iface_idx = p_pkt.index() & 0xFF; if (iface_idx == m_iface_one.num) { m_iface_one.feature = p_set; @@ -446,7 +437,7 @@ class iad_mock : public interface u8 driver_get_interface(setup_packet p_pkt) { - auto iface_idx = p_pkt.index & 0xFF; + auto iface_idx = p_pkt.index() & 0xFF; if (iface_idx == m_iface_one.num) { return m_iface_one.alt_settings; @@ -459,8 +450,8 @@ class iad_mock : public interface void driver_set_interface(setup_packet p_pkt) { - auto iface_idx = p_pkt.index & 0xFF; - auto alt_setting = p_pkt.value; + auto iface_idx = p_pkt.index() & 0xFF; + auto alt_setting = p_pkt.value(); if (iface_idx == m_iface_one.num) { m_iface_one.alt_settings = alt_setting; } else if (iface_idx == m_iface_two.num) { @@ -615,14 +606,15 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { ctrl_buf.clear(); u16 expected_addr = 0x30; - setup_packet set_addr{ false, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast( - standard_request_types::set_address), - 0x30, - 0, // a - 0 }; + setup_packet set_addr{ + { .device_to_host = false, + .type = setup_packet::request_type::standard, + .recipient = setup_packet::request_recipient::device, + .request = static_cast(standard_request_types::set_address), + .value = 0x30, + .index = 0, // a + .length = 0 } + }; simulate_sending_payload(ctrl_ptr, set_addr); ctrl_ptr->simulate_interrupt(); @@ -633,13 +625,13 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { // Get device descriptor u16 desc_t_idx = static_cast(descriptor_type::device) << 8; setup_packet get_desc( - true, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast(standard_request_types::get_descriptor), - desc_t_idx, - 0, - 18); + { .device_to_host = true, + .type = setup_packet::request_type::standard, + .recipient = setup_packet::request_recipient::device, + .request = static_cast(standard_request_types::get_descriptor), + .value = desc_t_idx, + .index = 0, + .length = 18 }); simulate_sending_payload(ctrl_ptr, get_desc); ctrl_ptr->simulate_interrupt(); std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); @@ -673,13 +665,13 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { constexpr u16 str_desc_t_idx = static_cast(descriptor_type::string) << 8 | 1; setup_packet str_hdr_req( - true, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast(standard_request_types::get_descriptor), - str_desc_t_idx, - 0, - 2); + { .device_to_host = true, + .type = setup_packet::request_type::standard, + .recipient = setup_packet::request_recipient::device, + .request = static_cast(standard_request_types::get_descriptor), + .value = str_desc_t_idx, + .index = 0, + .length = 2 }); simulate_sending_payload(ctrl_ptr, str_hdr_req); ctrl_ptr->simulate_interrupt(); @@ -695,7 +687,7 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { // Get a string descriptor from device setup_packet str_req(str_hdr_req); - str_req.length = expected_manu_str_hdr[0]; + str_req.length(expected_manu_str_hdr[0]); simulate_sending_payload(ctrl_ptr, str_req); ctrl_ptr->simulate_interrupt(); std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); @@ -713,13 +705,13 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { // Get Configuration length setup_packet conf_hdr_req( - true, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast(standard_request_types::get_descriptor), - static_cast(descriptor_type::configuration) << 8, - 0, - 9); + { .device_to_host = true, + .type = setup_packet::request_type::standard, + .recipient = setup_packet::request_recipient::device, + .request = static_cast(standard_request_types::get_descriptor), + .value = static_cast(descriptor_type::configuration) << 8, + .index = 0, + .length = 9 }); // Expected Config + interface descriptor std::array expected_conf_iface_desc{ @@ -765,7 +757,7 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { // Get Configuration Descriptor + interface descriptor + endpoint descriptor setup_packet conf_req(conf_hdr_req); - conf_req.length = expected_total_len; + conf_req.length(expected_total_len); simulate_sending_payload(ctrl_ptr, conf_req); ctrl_ptr->simulate_interrupt(); std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); @@ -774,13 +766,14 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { // Set configuration setup_packet set_conf_req( - false, - setup_packet::type::standard, - setup_packet::recipient::device, - static_cast(standard_request_types::set_configuration), - 1, - 0, - 0); + { .device_to_host = false, + .type = setup_packet::request_type::standard, + .recipient = setup_packet::request_recipient::device, + .request = + static_cast(standard_request_types::set_configuration), + .value = 1, + .index = 0, + .length = 0 }); simulate_sending_payload(ctrl_ptr, set_conf_req); ctrl_ptr->simulate_interrupt(); From 50ebee42b652ebef8e9cd6f7590f086ecd1b547b Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sat, 31 Jan 2026 21:17:41 -0800 Subject: [PATCH 16/18] :technologist: Updated to support new API for setup_packet in libhal --- .github/workflows/ci.yml | 2 +- include/libhal-util/usb/descriptors.hpp | 2 +- include/libhal-util/usb/endpoints.hpp | 32 ------------------------- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e891f4..58fec50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: secrets: inherit deployment_check_v5: - uses: libhal/ci/.github/workflows/deploy_all.yml@5.x.y + uses: libhal/ci/.github/workflows/package_and_upload_all.yml@5.x.y with: dir: v5 secrets: inherit diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp index f163e54..1feae74 100644 --- a/include/libhal-util/usb/descriptors.hpp +++ b/include/libhal-util/usb/descriptors.hpp @@ -23,10 +23,10 @@ #include #include #include +#include #include #include "libhal-util/as_bytes.hpp" -#include "libhal/units.hpp" #include "utils.hpp" /* TODO (PhazonicRidley): diff --git a/include/libhal-util/usb/endpoints.hpp b/include/libhal-util/usb/endpoints.hpp index f53cb37..0bfa259 100644 --- a/include/libhal-util/usb/endpoints.hpp +++ b/include/libhal-util/usb/endpoints.hpp @@ -21,84 +21,52 @@ namespace hal::v5 { // TODO(#79): Add doxygen docs to USB APIs -<<<<<<< HEAD -inline void write(usb_control_endpoint& p_endpoint, -======= inline void write(usb::control_endpoint& p_endpoint, ->>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) scatter_span p_data_out) { p_endpoint.write(p_data_out); } -<<<<<<< HEAD -inline void write_and_flush(usb_control_endpoint& p_endpoint, -======= inline void write_and_flush(usb::control_endpoint& p_endpoint, ->>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } -<<<<<<< HEAD -inline void write(usb_control_endpoint& p_endpoint, -======= inline void write(usb::control_endpoint& p_endpoint, ->>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } -<<<<<<< HEAD -inline void write_and_flush(usb_control_endpoint& p_endpoint, -======= inline void write_and_flush(usb::control_endpoint& p_endpoint, ->>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); p_endpoint.write({}); } -<<<<<<< HEAD -inline void write(usb_in_endpoint& p_endpoint, -======= inline void write(usb::in_endpoint& p_endpoint, ->>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) scatter_span p_data_out) { p_endpoint.write(p_data_out); } -<<<<<<< HEAD -inline void write_and_flush(usb_in_endpoint& p_endpoint, -======= inline void write_and_flush(usb::in_endpoint& p_endpoint, ->>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) scatter_span p_data_out) { p_endpoint.write(p_data_out); p_endpoint.write({}); } -<<<<<<< HEAD -inline void write(usb_in_endpoint& p_endpoint, -======= inline void write(usb::in_endpoint& p_endpoint, ->>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); } -<<<<<<< HEAD -inline void write_and_flush(usb_in_endpoint& p_endpoint, -======= inline void write_and_flush(usb::in_endpoint& p_endpoint, ->>>>>>> 4f3234c (:recycle: Moved endpoint utils to their own file) std::span p_data_out) { p_endpoint.write(make_scatter_bytes(p_data_out)); From ff91bbf3ac533dfe2d1fec7022c222a5451704e0 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Sun, 8 Feb 2026 21:15:43 -0800 Subject: [PATCH 17/18] :truck: Restructured files and APIs to align with latest libhal --- include/libhal-util/usb/descriptors.hpp | 277 ----------- include/libhal-util/usb/endpoints.hpp | 123 ----- include/libhal-util/usb/enumerator.hpp | 543 --------------------- include/libhal-util/usb/utils.hpp | 103 ---- v5/include/libhal-util/mock/usb.hpp | 526 ++++++++++++++++++++ v5/include/libhal-util/usb/descriptors.hpp | 40 +- v5/include/libhal-util/usb/enumerator.hpp | 103 ++-- v5/include/libhal-util/usb/utils.hpp | 79 +-- v5/tests/usb.test.cpp | 542 +------------------- 9 files changed, 644 insertions(+), 1692 deletions(-) delete mode 100644 include/libhal-util/usb/descriptors.hpp delete mode 100644 include/libhal-util/usb/endpoints.hpp delete mode 100644 include/libhal-util/usb/enumerator.hpp delete mode 100644 include/libhal-util/usb/utils.hpp create mode 100644 v5/include/libhal-util/mock/usb.hpp diff --git a/include/libhal-util/usb/descriptors.hpp b/include/libhal-util/usb/descriptors.hpp deleted file mode 100644 index 1feae74..0000000 --- a/include/libhal-util/usb/descriptors.hpp +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "libhal-util/as_bytes.hpp" -#include "utils.hpp" - -/* TODO (PhazonicRidley): - Class, subclass, proto validator - Device qualifer descriptor (happens between device and config) - Other speed descriptor (happens with configuration) -*/ - -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; // NOLINT - u8 p_device_protocol; - u16 p_id_vendor; // NOLINT - 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, 2); - for (auto& bcd_usb_byte : bcd_usb_bytes) { - m_packed_arr[idx++] = bcd_usb_byte; - } - m_packed_arr[idx++] = args.p_bcd_usb; - m_packed_arr[idx++] = static_cast(args.p_device_class); - m_packed_arr[idx++] = args.p_device_subclass; - m_packed_arr[idx++] = args.p_device_protocol; - - m_packed_arr[idx++] = 0; // Max Packet length handled by the enumerator - auto id_vendor_bytes = hal::as_bytes(&args.p_id_vendor, 2); - for (auto& id_vendor_byte : id_vendor_bytes) { - m_packed_arr[idx++] = id_vendor_byte; - } - - auto id_product_bytes = hal::as_bytes(&args.p_id_product, 2); - for (auto& id_product_byte : id_product_bytes) { - m_packed_arr[idx++] = id_product_byte; - } - - auto bcd_device_bytes = hal::as_bytes(&args.p_bcd_device, 2); - for (auto& bcd_device_byte : bcd_device_bytes) { - m_packed_arr[idx++] = bcd_device_byte; - } - - // Evaluated during enumeration - m_packed_arr[idx++] = 0; // string idx of manufacturer - m_packed_arr[idx++] = 0; // string idx of product - m_packed_arr[idx++] = 0; // string idx of serial number - m_packed_arr[idx++] = 0; // 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/include/libhal-util/usb/endpoints.hpp b/include/libhal-util/usb/endpoints.hpp deleted file mode 100644 index 0bfa259..0000000 --- a/include/libhal-util/usb/endpoints.hpp +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include -#include - -namespace hal::v5 { -// TODO(#79): Add doxygen docs to USB APIs -inline void write(usb::control_endpoint& p_endpoint, - scatter_span p_data_out) -{ - p_endpoint.write(p_data_out); -} - -inline void write_and_flush(usb::control_endpoint& p_endpoint, - scatter_span p_data_out) -{ - p_endpoint.write(p_data_out); - p_endpoint.write({}); -} - -inline void write(usb::control_endpoint& p_endpoint, - std::span p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out)); -} - -inline void write_and_flush(usb::control_endpoint& p_endpoint, - std::span p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out)); - p_endpoint.write({}); -} - -inline void write(usb::in_endpoint& p_endpoint, - scatter_span p_data_out) -{ - p_endpoint.write(p_data_out); -} - -inline void write_and_flush(usb::in_endpoint& p_endpoint, - scatter_span p_data_out) -{ - p_endpoint.write(p_data_out); - p_endpoint.write({}); -} - -inline void write(usb::in_endpoint& p_endpoint, - std::span p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out)); -} - -inline void write_and_flush(usb::in_endpoint& p_endpoint, - std::span p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out)); - p_endpoint.write({}); -} - -inline void write(usb::in_endpoint& p_endpoint, - spanable_bytes auto... p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out...)); -} - -inline void write_and_flush(usb::in_endpoint& p_endpoint, - spanable_bytes auto... p_data_out) -{ - p_endpoint.write(make_scatter_bytes(p_data_out...)); - p_endpoint.write({}); -} - -inline auto read(usb::out_endpoint& p_endpoint, - scatter_span p_data_out) -{ - return p_endpoint.read(p_data_out); -} - -inline auto read(usb::out_endpoint& p_endpoint, std::span p_data_out) -{ - return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); -} - -inline auto read(usb::out_endpoint& p_endpoint, - spanable_writable_bytes auto... p_data_out) -{ - return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); -} - -inline auto read(usb::control_endpoint& p_endpoint, - scatter_span p_data_out) -{ - return p_endpoint.read(p_data_out); -} - -inline auto read(usb::control_endpoint& p_endpoint, - std::span p_data_out) -{ - return p_endpoint.read(make_writable_scatter_bytes(p_data_out)); -} - -inline auto read(usb::control_endpoint& p_endpoint, - spanable_writable_bytes auto... p_data_out) -{ - return p_endpoint.read(make_writable_scatter_bytes(p_data_out...)); -} -} // namespace hal::v5 diff --git a/include/libhal-util/usb/enumerator.hpp b/include/libhal-util/usb/enumerator.hpp deleted file mode 100644 index bfe9c0d..0000000 --- a/include/libhal-util/usb/enumerator.hpp +++ /dev/null @@ -1,543 +0,0 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "descriptors.hpp" -#include "libhal-util/as_bytes.hpp" -#include "libhal-util/usb/endpoints.hpp" -#include "utils.hpp" - -// TODO: move to util -namespace hal::v5::usb { - -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, - bool enumerate_immediately = true) - : m_ctrl_ep(p_ctrl_ep) - , m_device(p_device) - , m_configs(p_configs) - , m_lang_str(p_lang_str) - { - // 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; - 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; - - // 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; - } - - for (configuration& config : *m_configs) { - auto total_length = static_cast(constants::config_desc_size); - for (auto const& iface : config.interfaces) { - auto deltas = iface->write_descriptors( - [&total_length](scatter_span p_data) { - total_length += scatter_span_size(p_data); - }); - - cur_iface_idx += deltas.iface_idxes; - cur_str_idx += deltas.str_idxes; - } - config.num_interfaces() = cur_iface_idx; - config.total_length() = total_length; - } - - // 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; - - m_ctrl_ep->on_receive( - [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); - m_ctrl_ep->connect(true); - - 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) { - - safe_throw(hal::message_size(num_bytes_read, this)); - } - // - // for (auto const& el : raw_req) { - // - // } - // - auto req = from_span(raw_req); - - if (req.request_type.get_recipient() != - setup_request::bitmap::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() - { - using std_req_type = setup_request::standard_request_types; - using req_bitmap = setup_request::bitmap; - - 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_request req(payload); - if (req.get_standard_request() == std_req_type::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.request_type.get_recipient() == - req_bitmap::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_request& req) - { - using standard_request_types = setup_request::standard_request_types; - - switch (req.get_standard_request()) { - case standard_request_types::set_address: { - m_ctrl_ep->write({}); - m_ctrl_ep->set_address(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_request& 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)); - - 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)); - - // Return early if the only thing requested was the config descriptor - if (req.length <= constants::config_desc_size) { - 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); - }); - } - - 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) { - - auto s_hdr = - std::to_array({ static_cast(4), - static_cast(descriptor_type::string) }); - auto lang = setup_packet::to_le_u16(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({}); - 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; - } - } - - // 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(f, target_idx); - if (res) { - return; - } - } - } - - for (configuration const& conf : *m_configs) { - for (auto const& iface : conf.interfaces) { - auto res = iface->write_string_descriptor(f, target_idx); - if (res) { - break; - } - } - } - } - - 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) - - auto sv = opt_conf_sv.value(); - std::mbstate_t state{}; - for (wchar_t const wc : sv) { - std::array tmp; - size_t len = std::wcrtomb(tmp.data(), wc, &state); - if (len == static_cast(-1)) { - - continue; - } - - for (size_t i = 0; i < len; i++) { - } - } - - auto const conf_sv_span = hal::as_bytes(opt_conf_sv.value()); - auto desc_len = static_cast((conf_sv_span.size() + 2)); - - 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); - - 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; -}; -} // namespace hal::v5::usb diff --git a/include/libhal-util/usb/utils.hpp b/include/libhal-util/usb/utils.hpp deleted file mode 100644 index 5f2aacf..0000000 --- a/include/libhal-util/usb/utils.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -// TODO: Move to util -namespace hal::v5::usb { - -// TODO: Make generic -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, -}; - -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/v5/include/libhal-util/mock/usb.hpp b/v5/include/libhal-util/mock/usb.hpp new file mode 100644 index 0000000..ba73477 --- /dev/null +++ b/v5/include/libhal-util/mock/usb.hpp @@ -0,0 +1,526 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libhal-util/usb/utils.hpp" + +namespace hal::v5::usb { +constexpr u8 iface_desc_length = 9; +constexpr u8 iface_desc_type = 0x4; +constexpr u8 str_desc_type = 0x3; +constexpr u8 iad_length = 0x08; +constexpr u8 iad_type = 0x0B; + +template +bool span_eq(std::span const lhs, std::span const rhs) +{ + if (lhs.size() != rhs.size()) { + return false; + } + + for (size_t i = 0; i < lhs.size(); i++) { + if (lhs[i] != rhs[i]) { + return false; + } + } + return true; +} + +template +bool span_ne(std::span const lhs, std::span const rhs) +{ + return !(lhs == rhs); +} + +constexpr std::vector pkt_to_scatter(setup_packet const& req) +{ + std::vector vec; + vec.push_back(req.bm_request_type()); + vec.push_back(req.request()); + vec.append_range(setup_packet::to_le_u16(req.value())); + vec.append_range(setup_packet::to_le_u16(req.index())); + vec.append_range(setup_packet::to_le_u16(req.length())); + + return vec; +} + +class mock_usb_endpoint : public usb::endpoint +{ +public: + usb::endpoint_info m_info{}; + bool m_stall_called{ false }; + bool m_should_stall{ false }; + bool m_reset_called{ false }; + +protected: + [[nodiscard]] usb::endpoint_info driver_info() const override + { + return m_info; + } + + void driver_stall(bool p_should_stall) override + { + m_stall_called = true; + m_should_stall = p_should_stall; + } + + void driver_reset() override + { + m_reset_called = true; + } +}; + +class mock_usb_control_endpoint : public control_endpoint +{ +public: + mock_usb_endpoint m_endpoint; + bool m_is_connected{ false }; + u8 m_address{ 0 }; + std::vector m_out_buf; + std::array m_req_buf; + usize m_read_result{ 0 }; + callback m_receive_callback{}; + + void write_request(scatter_span p_data) + { + size_t total_len = 0; + for (std::span const& s : p_data) { + for (byte const el : s) { + m_req_buf[total_len] = el; + total_len++; + } + } + } + + void simulate_interrupt() + { + m_receive_callback(on_receive_tag{}); + } + +private: + [[nodiscard]] endpoint_info driver_info() const override + { + return m_endpoint.info(); + } + + void driver_stall(bool p_should_stall) override + { + m_endpoint.stall(p_should_stall); + } + + void driver_connect(bool p_should_connect) override + { + m_is_connected = p_should_connect; + } + + void driver_set_address(u8 p_address) override + { + m_address = p_address; + } + + void driver_write(scatter_span p_data) override + { + // Ignore ZLPs + if (p_data.empty() || + (scatter_span_size(p_data) == 1 && p_data[0][0] == 0)) { + return; + } + for (std::span const& s : p_data) { + for (byte const el : s) { + m_out_buf.push_back(el); + } + } + } + + usize driver_read(scatter_span p_buffer) override + { + size_t src_idx = 0; + for (auto s : p_buffer) { + for (unsigned char& el : s) { + el = m_req_buf[src_idx]; + src_idx++; + } + } + + return src_idx; + } + + void driver_on_receive( + callback const& p_callback) override + { + m_receive_callback = p_callback; + } + + void driver_reset() override + { + m_endpoint.reset(); + } +}; + +struct mock : public interface +{ + + constexpr mock(std::u16string_view p_name) + : name(p_name) + { + } + + [[nodiscard]] descriptor_count driver_write_descriptors( + descriptor_start p_start, + endpoint_writer const& p_callback) override + { + byte res_iface_idx = 0; + if (p_start.interface.has_value()) { + interface_number() = p_start.interface.value(); + res_iface_idx = 1; + } + + byte res_str_idx = 0; + if (p_start.string.has_value()) { + interface_name_string_idx() = p_start.string.value(); + res_str_idx = 1; + } + + p_callback(make_scatter_bytes(m_packed_array)); + + return { .interface = res_iface_idx, .string = res_str_idx }; + } + + [[nodiscard]] bool driver_write_string_descriptor( + u8 p_index, + endpoint_writer const& p_callback) override + { + if (p_index != interface_name_string_idx()) { + return false; + } + std::array str_hdr( + { static_cast(descriptor_type::string), + static_cast(name.length()) }); + + auto scatter_arr = make_scatter_bytes( + str_hdr, + std::span(reinterpret_cast(name.data()), name.length())); + + p_callback(scatter_span(scatter_arr)); + + 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; + } + + constexpr byte& interface_number() + { + return m_packed_array[2]; + } + + constexpr byte& alt_settings() + { + return m_packed_array[3]; + } + + constexpr byte& interface_name_string_idx() + { + return m_packed_array[8]; + } + + std::array m_packed_array = { + iface_desc_length, + iface_desc_type, + 0, // interface_number + 0, // alternate_setting + 1, // num_endpoints + 2, // interface_class + 3, // interface_sub_class + 4, // interface_protocol + 0 // interface name index + }; + std::u16string_view name; +}; + +class iad_mock : public interface +{ +public: + ~iad_mock() override = default; + + iad_mock(std::u16string_view p_iface_name_one, // NOLINT + std::u16string_view p_iface_name_two) + : m_name_one(p_iface_name_one) + , m_name_two(p_iface_name_two) {}; + + struct mock_iface_descriptor + { + byte num; + byte alt_settings; + byte str_idx; + bool feature; + }; + +private: + [[nodiscard]] descriptor_count driver_write_descriptors( + descriptor_start p_start, + endpoint_writer const& p_callback) override + { + if (!p_start.interface.has_value() || !p_start.string.has_value()) { + throw hal::argument_out_of_domain(this); + } + + auto iface_idx = p_start.interface.value(); + auto str_idx = p_start.string.value(); + std::array iad_buf{ + iad_length, iad_type, + 0, // first interface + 2, // iface count + 0, // class + 0, // subclass + 0, // proto + str_idx++ // string idx + }; + + std::array iface_header = { iface_desc_length, + iface_desc_type }; + std::array static_desc_vars = { + 0, // altsettings + 1, // num endpoints + 0, // class + 0, // subclass + 0, // protocol + }; + + m_iface_one = { .num = iface_idx++, + .alt_settings = 0, + .str_idx = str_idx++, + .feature = false }; + m_iface_two = { .num = iface_idx++, + .alt_settings = 0, + .str_idx = str_idx++, + .feature = false }; + + std::array iface_one_arr({ m_iface_one.num }); + std::array iface_one_str_arr({ m_iface_one.str_idx }); + + std::array iface_two_arr({ m_iface_two.num }); + std::array iface_two_str_arr({ m_iface_two.str_idx }); + + auto span_arr = make_scatter_bytes(iad_buf, + + // iface one + iface_header, + std::span(iface_one_arr), + static_desc_vars, + std::span(iface_one_str_arr), + + // iface two + iface_header, + std::span(iface_two_arr), + static_desc_vars, + std::span(iface_two_str_arr)); + + p_callback(span_arr); + m_wrote_descriptors = true; + return { .interface = 2, .string = 3 }; + } + + [[nodiscard]] bool driver_write_string_descriptor( + u8 p_index, + endpoint_writer const& p_callback) override + { + if (!m_wrote_descriptors) { + safe_throw(hal::operation_not_permitted(this)); + } + std::array header{ 0, str_desc_type }; + if (p_index == m_iface_one.str_idx) { + header[0] = m_name_one.length() + 2; + auto arr = make_scatter_bytes( + header, + std::span(reinterpret_cast(m_name_one.data()), + m_name_one.length())); + p_callback(arr); + return true; + } else if (p_index == m_iface_two.str_idx) { + header[0] = m_iface_two.str_idx + 2; + auto arr = make_scatter_bytes( + header, + std::span(reinterpret_cast(m_name_two.data()), + m_name_two.length())); + + p_callback(arr); + return true; + } + + return false; + } + + u16 driver_get_status(setup_packet p_pkt) + { + if (p_pkt.get_recipient() != setup_packet::request_recipient::interface) { + safe_throw(hal::operation_not_supported(this)); + } + + auto iface_idx = p_pkt.index() & 0xFF; + + if (iface_idx == m_iface_one.num) { + return m_iface_one.num; + } else if (iface_idx == m_iface_two.num) { + return m_iface_two.num; + } + + safe_throw(hal::operation_not_supported(this)); + } + + void manage_features(setup_packet p_pkt, bool p_set, u16 p_selector) + { + std::ignore = p_selector; + if (p_pkt.get_recipient() != setup_packet::request_recipient::interface) { + safe_throw(hal::operation_not_supported(this)); + } + + auto iface_idx = p_pkt.index() & 0xFF; + + if (iface_idx == m_iface_one.num) { + m_iface_one.feature = p_set; + } else if (iface_idx == m_iface_two.num) { + m_iface_two.feature = p_set; + } else { + safe_throw(hal::operation_not_supported(this)); + } + } + + u8 driver_get_interface(setup_packet p_pkt) + { + auto iface_idx = p_pkt.index() & 0xFF; + + if (iface_idx == m_iface_one.num) { + return m_iface_one.alt_settings; + } else if (iface_idx == m_iface_two.num) { + return m_iface_two.alt_settings; + } else { + safe_throw(hal::operation_not_supported(this)); + } + } + + void driver_set_interface(setup_packet p_pkt) + { + auto iface_idx = p_pkt.index() & 0xFF; + auto alt_setting = p_pkt.value(); + if (iface_idx == m_iface_one.num) { + m_iface_one.alt_settings = alt_setting; + } else if (iface_idx == m_iface_two.num) { + m_iface_two.alt_settings = alt_setting; + } else { + safe_throw(hal::operation_not_supported(this)); + } + } + + bool driver_handle_request(setup_packet const& p_setup, + endpoint_writer const& p_callback) override + { + std::ignore = p_setup; + std::ignore = p_callback; + return false; + } + +public: + mock_iface_descriptor m_iface_one; + std::u16string_view m_name_one; + mock_iface_descriptor m_iface_two; + std::u16string_view m_name_two; + bool m_wrote_descriptors = false; +}; + +void simulate_sending_payload( + strong_ptr const& ctrl_ptr, + setup_packet& req) +{ + auto vec = pkt_to_scatter(req); + auto scatter_arr = make_scatter_bytes(vec); + scatter_span ss(scatter_arr); + + ctrl_ptr->write_request(ss); +} + +// u8 calculate_conf_desc_recursive(std::span p_conf_arr) +// { +// u8 total_len = 0; +// for (configuration& conf : p_conf_arr) { +// total_len += 9; +// for (auto& iface : conf.interfaces) { +// auto real_iface = dynamic_cast(&(*iface)); +// std::ignore = iface->write_descriptors( +// { .interface = 0, .string = real_iface->interface_name_string_idx() +// }, +// [&total_len](scatter_span p_data) { +// total_len += scatter_span_size(p_data); +// }); +// } +// } + +// return total_len; +// } + +// std::vector generate_conf_descriptors(std::span +// p_conf_arr) +// { +// std::vector vec; +// for (configuration const& conf : p_conf_arr) { +// vec.append_range(std::span(conf)); +// for (auto const& iface : conf.interfaces) { +// std::ignore = +// iface->write_descriptors({ .interface = 0, .string = 0 }, +// [&vec](scatter_span p_dat) { +// for (auto const& s : p_dat) { +// for (auto const& el : s) { +// vec.push_back(el); +// } +// } +// }); +// } +// } + +// return vec; +// } + +class mock_serial : public hal::serial +{ + void driver_configure(settings const& p_settings) override + { + std::ignore = p_settings; + } + + write_t driver_write(std::span p_data) override + { + std::string_view sv(reinterpret_cast(p_data.data()), + p_data.size()); + return { .data = p_data }; + } + + read_t driver_read(std::span p_data) override + { + std::ignore = p_data; + return { .data = {}, .available = 0, .capacity = 0 }; + } + + void driver_flush() override + { + } +}; + +} // namespace hal::v5::usb diff --git a/v5/include/libhal-util/usb/descriptors.hpp b/v5/include/libhal-util/usb/descriptors.hpp index 75f3476..dad93cc 100644 --- a/v5/include/libhal-util/usb/descriptors.hpp +++ b/v5/include/libhal-util/usb/descriptors.hpp @@ -35,7 +35,7 @@ Other speed descriptor (happens with configuration) */ -namespace hal::usb { +namespace hal::v5::usb { struct device { @@ -46,9 +46,9 @@ struct device { u16 p_bcd_usb; class_code p_device_class; - u8 p_device_subclass; + u8 p_device_subclass; // NOLINT u8 p_device_protocol; - u16 p_id_vendor; + u16 p_id_vendor; // NOLINT u16 p_id_product; u16 p_bcd_device; std::u16string_view p_manufacturer; @@ -64,35 +64,33 @@ struct device 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++] = bcd_usb_byte; } + m_packed_arr[idx++] = static_cast(args.p_device_class); + m_packed_arr[idx++] = args.p_device_subclass; + m_packed_arr[idx++] = args.p_device_protocol; - 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 - + m_packed_arr[idx++] = 0; // 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 + m_packed_arr[idx++] = id_vendor_byte; } 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 + m_packed_arr[idx++] = id_product_byte; } 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 + m_packed_arr[idx++] = bcd_device_byte; } // 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 + m_packed_arr[idx++] = 0; // string idx of manufacturer + m_packed_arr[idx++] = 0; // string idx of product + m_packed_arr[idx++] = 0; // string idx of serial number + m_packed_arr[idx++] = 0; // Number of possible configurations }; u16& bcd_usb() @@ -130,7 +128,7 @@ struct device return *reinterpret_cast(&m_packed_arr[10]); } - operator std::span() const + operator std::span() const { return m_packed_arr; } @@ -275,4 +273,10 @@ struct configuration std::array m_packed_arr; }; +} // namespace hal::v5::usb + +namespace hal::usb { +using v5::usb::configuration; +using v5::usb::device; +using v5::usb::usb_interface_concept; } // namespace hal::usb diff --git a/v5/include/libhal-util/usb/enumerator.hpp b/v5/include/libhal-util/usb/enumerator.hpp index c7bbcf3..e323e2f 100644 --- a/v5/include/libhal-util/usb/enumerator.hpp +++ b/v5/include/libhal-util/usb/enumerator.hpp @@ -16,10 +16,9 @@ #include #include - -#include #include +#include #include #include #include @@ -38,18 +37,11 @@ #include "libhal-util/usb/endpoints.hpp" #include "utils.hpp" -namespace hal::usb { - -/** - TODO: (PhazonicRidley) Make class method for scatterspan class - @brief Get the size of a scatter span, meaning how many elements is this - scatterspan viewing. +// TODO: move to util +namespace hal::v5::usb { - @param ss The scatter span to get the size of - @return The number of elements ss is viewing - */ template -constexpr size_t scatter_span_size(hal::v5::scatter_span ss) +constexpr size_t scatter_span_size(scatter_span ss) { size_t res = 0; for (auto const& s : ss) { @@ -58,28 +50,6 @@ constexpr size_t scatter_span_size(hal::v5::scatter_span ss) return res; } - -/** - TODO: (PhazonicRidley) Make class method for scatterspan class - @brief Creates a scatter_span with p_spans and calculates how many internal - spans are used for a given sub scatter_span starting at index zero and going - to the p_count element. - - ```C++ - auto pair = make_scatter_span(n, ...); - auto my_scatter_span = scatter_span(pair.first).first(pair.second) - ``` - - @tparam T The type all collections are to have of. - @tparam Args... A packed set of collections of type T - - @param p_count How many elements (from the start) to take into the subspan - @param p_spans The spans the scatter span is to view into - - @return A pair where the first element contains an array of spans used within - the subscatter span, and the second element is the number of spans within - the sub scatterspan -*/ template constexpr std::pair, sizeof...(Args)>, size_t> make_sub_scatter_array(size_t p_count, Args&&... p_spans) @@ -118,19 +88,6 @@ make_sub_scatter_array(size_t p_count, Args&&... p_spans) return std::make_pair(res, i + 1); } -/** - TODO: (PhazonicRidley) Make class method for scatterspan class - @brief Create a sub scatter span array from spans of bytes - - @tparam Args... A packed set of collections of bytes - - @param p_count How many elements (from the start) to take into the subspan - @param p_spans The spans the scatter span is to view into - - @return A pair where the first element contains an array of spans used within - the subscatter span, and the second element is the number of bytes within - the sub scatterspan -*/ template constexpr std::pair, sizeof...(Args)>, size_t> make_sub_scatter_bytes(size_t p_count, Args&&... p_spans) @@ -198,7 +155,7 @@ class enumerator 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( + auto 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); @@ -223,15 +180,16 @@ class enumerator [&waiting_for_data](on_receive_tag) { waiting_for_data = false; }); m_ctrl_ep->connect(true); - std::array raw_req; + // std::array raw_req; + setup_packet req; do { - // TODO: Seriously, make this async + // Seriously, make this async while (waiting_for_data) { continue; } waiting_for_data = true; - auto scatter_raw_req = make_writable_scatter_bytes(raw_req); + auto scatter_raw_req = make_writable_scatter_bytes(req.raw_request_bytes); auto num_bytes_read = m_ctrl_ep->read(scatter_raw_req); if (num_bytes_read == 0) { @@ -242,15 +200,20 @@ class enumerator safe_throw(hal::message_size(num_bytes_read, this)); } - - auto req = setup_packet(raw_req); + // + // for (auto const& el : raw_req) { + // + // } + // if (req.get_recipient() != setup_packet::request_recipient::device) { safe_throw(hal::not_connected(this)); } - // ZLP is handled at write site + // 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; @@ -277,13 +240,13 @@ class enumerator m_has_setup_packet = false; - std::array read_buf; + setup_packet req; + auto& read_buf = req.raw_request_bytes; 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(read_buf); - if (determine_standard_request(req) == standard_request_types::invalid) { + if (req.get_type() == setup_packet::request_type::invalid) { return; } @@ -304,17 +267,14 @@ class enumerator }; } else { f = [this](scatter_span resp) { - std::ignore = m_ctrl_ep->read(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); + req_handled = iface->handle_request(req, f); if (req_handled) { break; } @@ -331,7 +291,6 @@ class enumerator private: void handle_standard_device_request(setup_packet& req) { - switch (determine_standard_request(req)) { case standard_request_types::set_address: { m_ctrl_ep->write({}); @@ -415,6 +374,11 @@ class enumerator } m_ctrl_ep->write({}); + // if (total_size != req.length()) { + // safe_throw(hal::operation_not_supported( + // this)); // TODO: Make specific exception for this + // } + break; } @@ -436,6 +400,9 @@ class enumerator break; } + // TODO: Interface, endpoint, device_qualifier, interface_power, + // OTHER_SPEED_CONFIGURATION + default: safe_throw(hal::operation_not_supported(this)); } @@ -568,4 +535,8 @@ class enumerator configuration* m_active_conf = nullptr; bool m_has_setup_packet = false; }; -} // namespace hal::usb +} // namespace hal::v5::usb + +namespace hal::usb { +using v5::usb::enumerator; +} diff --git a/v5/include/libhal-util/usb/utils.hpp b/v5/include/libhal-util/usb/utils.hpp index 15e2082..77488a4 100644 --- a/v5/include/libhal-util/usb/utils.hpp +++ b/v5/include/libhal-util/usb/utils.hpp @@ -1,12 +1,27 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #pragma once -#include +#include #include #include -namespace hal::usb { +namespace hal::v5::usb { namespace constants { + constexpr byte device_desc_size = 18; constexpr byte config_desc_size = 9; constexpr byte inferface_desc_size = 9; @@ -14,12 +29,11 @@ constexpr byte endpoint_desc_size = 7; constexpr byte iad_desc_size = 0x08; constexpr byte size_std_req = 8; + } // namespace constants -/** - * @brief USB Class Code indicating the type of device or interface. Assigned by - * USB-IF. - */ +// Maybe move these enum classes into the constants namespace +// Assigned by USB-IF enum class class_code : hal::byte { use_interface_descriptor = @@ -50,34 +64,35 @@ enum class class_code : hal::byte vendor_specific = 0xFF // Vendor Specific }; -/** - * @brief USB Descriptor Type values as defined by the USB specification. - * These values are used in the bDescriptorType field of USB descriptors - * to identify the type and structure of the descriptor data. - */ +// Default types enum class descriptor_type : hal::byte { - device = 0x1, // Device descriptor - configuration = 0x2, // Configuration descriptor - string = 0x3, // String descriptor - interface = 0x4, // Interface descriptor - endpoint = 0x5, // Endpoint descriptor - device_qualifier = 0x6, // Device qualifier descriptor - other_speed_configuration = 0x7, // Other speed configuration descriptor - interface_power = 0x8, // Interface power descriptor - otg = 0x9, // OTG descriptor - debug = 0xA, // Debug descriptor - interface_association = 0xB, // Interface Association descriptor - security = 0xC, // Security descriptor - key = 0xD, // Key descriptor - encryption_type = 0xE, // Encryption type descriptor - bos = 0xF, // Binary Object Store (BOS) descriptor - device_capability = 0x10, // Device capability descriptor - wireless_endpoint_companion = 0x11, // Wireless endpoint companion descriptor - superspeed_endpoint_companion = - 0x30, // SuperSpeed endpoint companion descriptor - superspeed_endpoint_isochronous_companion = - 0x31, // SuperSpeed isochronous endpoint companion descriptor + 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, }; +} // namespace hal::v5::usb + +namespace hal::usb { +namespace constants = v5::usb::constants; +using v5::usb::class_code; +using v5::usb::descriptor_type; + } // namespace hal::usb diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index 13d9676..728891a 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -12,549 +12,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "libhal-util/usb/descriptors.hpp" -#include "libhal-util/usb/utils.hpp" #include #include -#include #include -#include +#include +#include +#include +#include +#include -#include +#include #include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include - -namespace hal::usb { -namespace { - -constexpr u8 iface_desc_length = 9; -constexpr u8 iface_desc_type = 0x4; -constexpr u8 str_desc_type = 0x3; -constexpr u8 iad_length = 0x08; -constexpr u8 iad_type = 0x0B; - -template -size_t scatter_span_size(scatter_span ss) -{ - size_t res = 0; - for (auto const& s : ss) { - res += s.size(); - } - - return res; -} - -template -bool span_eq(std::span const lhs, std::span const rhs) -{ - if (lhs.size() != rhs.size()) { - return false; - } - - for (size_t i = 0; i < lhs.size(); i++) { - if (lhs[i] != rhs[i]) { - return false; - } - } - return true; -} - -template -bool span_ne(std::span const lhs, std::span const rhs) -{ - return !(lhs == rhs); -} - -constexpr std::vector pkt_to_scatter(setup_packet const& req) -{ - std::vector vec; - vec.push_back(req.request_type()); - vec.push_back(req.request()); - vec.append_range(setup_packet::to_le_u16(req.value())); - vec.append_range(setup_packet::to_le_u16(req.index())); - vec.append_range(setup_packet::to_le_u16(req.length())); - - return vec; -} - -class mock_usb_endpoint : public usb::endpoint -{ -public: - usb::endpoint_info m_info{}; - bool m_stall_called{ false }; - bool m_should_stall{ false }; - bool m_reset_called{ false }; - -protected: - [[nodiscard]] usb::endpoint_info driver_info() const override - { - return m_info; - } - - void driver_stall(bool p_should_stall) override - { - m_stall_called = true; - m_should_stall = p_should_stall; - } - - void driver_reset() override - { - m_reset_called = true; - } -}; - -class mock_usb_control_endpoint : public control_endpoint -{ -public: - mock_usb_endpoint m_endpoint; - bool m_is_connected{ false }; - u8 m_address{ 0 }; - std::vector m_out_buf; - std::array m_req_buf; - usize m_read_result{ 0 }; - callback m_receive_callback{}; - - void write_request(scatter_span p_data) - { - size_t total_len = 0; - for (std::span const& s : p_data) { - for (byte const el : s) { - m_req_buf[total_len] = el; - total_len++; - } - } - } - - void simulate_interrupt() - { - m_receive_callback(on_receive_tag{}); - } - -private: - [[nodiscard]] endpoint_info driver_info() const override - { - return m_endpoint.info(); - } - - void driver_stall(bool p_should_stall) override - { - m_endpoint.stall(p_should_stall); - } - - void driver_connect(bool p_should_connect) override - { - m_is_connected = p_should_connect; - } - - void driver_set_address(u8 p_address) override - { - m_address = p_address; - } - - void driver_write(scatter_span p_data) override - { - // Ignore ZLPs - if (p_data.empty() || - (scatter_span_size(p_data) == 1 && p_data[0][0] == 0)) { - return; - } - for (std::span const& s : p_data) { - for (byte const el : s) { - m_out_buf.push_back(el); - } - } - } - - usize driver_read(scatter_span p_buffer) override - { - size_t src_idx = 0; - for (auto s : p_buffer) { - for (unsigned char& el : s) { - el = m_req_buf[src_idx]; - src_idx++; - } - } - - return src_idx; - } - - void driver_on_receive( - callback const& p_callback) override - { - m_receive_callback = p_callback; - } - - void driver_reset() override - { - m_endpoint.reset(); - } -}; - -struct mock : public interface -{ - - constexpr mock(std::u16string_view p_name) - : name(p_name) - { - } - - [[nodiscard]] descriptor_count driver_write_descriptors( - descriptor_start p_start, - endpoint_writer const& p_callback) override - { - byte res_iface_idx = 0; - if (p_start.interface.has_value()) { - interface_number() = p_start.interface.value(); - res_iface_idx = 1; - } - - byte res_str_idx = 0; - if (p_start.string.has_value()) { - interface_name_string_idx() = p_start.string.value(); - res_str_idx = 1; - } - - p_callback(make_scatter_bytes(m_packed_array)); - - return { .interface = res_iface_idx, .string = res_str_idx }; - } - - [[nodiscard]] bool driver_write_string_descriptor( - u8 p_index, - endpoint_writer const& p_callback) override - { - if (p_index != interface_name_string_idx()) { - return false; - } - std::array str_hdr( - { static_cast(descriptor_type::string), - static_cast(name.length()) }); - - auto scatter_arr = make_scatter_bytes( - str_hdr, - std::span(reinterpret_cast(name.data()), name.length())); - - p_callback(scatter_span(scatter_arr)); - 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; - } - - constexpr byte& interface_number() - { - return m_packed_array[2]; - } - - constexpr byte& alt_settings() - { - return m_packed_array[3]; - } - - constexpr byte& interface_name_string_idx() - { - return m_packed_array[8]; - } - - std::array m_packed_array = { - iface_desc_length, - iface_desc_type, - 0, // interface_number - 0, // alternate_setting - 1, // num_endpoints - 2, // interface_class - 3, // interface_sub_class - 4, // interface_protocol - 0 // interface name index - }; - std::u16string_view name; -}; - -class iad_mock : public interface -{ -public: - ~iad_mock() override = default; - - iad_mock(std::u16string_view p_iface_name_one, // NOLINT - std::u16string_view p_iface_name_two) - : m_name_one(p_iface_name_one) - , m_name_two(p_iface_name_two) {}; - - struct mock_iface_descriptor - { - byte num; - byte alt_settings; - byte str_idx; - bool feature; - }; - -private: - [[nodiscard]] descriptor_count driver_write_descriptors( - descriptor_start p_start, - endpoint_writer const& p_callback) override - { - if (!p_start.interface.has_value() || !p_start.string.has_value()) { - throw hal::argument_out_of_domain(this); - } - - auto iface_idx = p_start.interface.value(); - auto str_idx = p_start.string.value(); - std::array iad_buf{ - iad_length, iad_type, - 0, // first interface - 2, // iface count - 0, // class - 0, // subclass - 0, // proto - str_idx++ // string idx - }; - - std::array iface_header = { iface_desc_length, - iface_desc_type }; - std::array static_desc_vars = { - 0, // altsettings - 1, // num endpoints - 0, // class - 0, // subclass - 0, // protocol - }; - - m_iface_one = { .num = iface_idx++, - .alt_settings = 0, - .str_idx = str_idx++, - .feature = false }; - m_iface_two = { .num = iface_idx++, - .alt_settings = 0, - .str_idx = str_idx++, - .feature = false }; - - std::array iface_one_arr({ m_iface_one.num }); - std::array iface_one_str_arr({ m_iface_one.str_idx }); - - std::array iface_two_arr({ m_iface_two.num }); - std::array iface_two_str_arr({ m_iface_two.str_idx }); - - auto span_arr = make_scatter_bytes(iad_buf, - - // iface one - iface_header, - std::span(iface_one_arr), - static_desc_vars, - std::span(iface_one_str_arr), - - // iface two - iface_header, - std::span(iface_two_arr), - static_desc_vars, - std::span(iface_two_str_arr)); - - p_callback(span_arr); - m_wrote_descriptors = true; - return { .interface = 2, .string = 3 }; - } - - [[nodiscard]] bool driver_write_string_descriptor( - u8 p_index, - endpoint_writer const& p_callback) override - { - if (!m_wrote_descriptors) { - safe_throw(hal::operation_not_permitted(this)); - } - std::array header{ 0, str_desc_type }; - if (p_index == m_iface_one.str_idx) { - header[0] = m_name_one.length() + 2; - auto arr = make_scatter_bytes( - header, - std::span(reinterpret_cast(m_name_one.data()), - m_name_one.length())); - p_callback(arr); - return true; - } else if (p_index == m_iface_two.str_idx) { - header[0] = m_iface_two.str_idx + 2; - auto arr = make_scatter_bytes( - header, - std::span(reinterpret_cast(m_name_two.data()), - m_name_two.length())); - - p_callback(arr); - return true; - } - - return false; - } - - u16 driver_get_status(setup_packet p_pkt) - { - if (p_pkt.get_recipient() != setup_packet::request_recipient::interface) { - safe_throw(hal::operation_not_supported(this)); - } - - auto iface_idx = p_pkt.index() & 0xFF; - - if (iface_idx == m_iface_one.num) { - return m_iface_one.num; - } else if (iface_idx == m_iface_two.num) { - return m_iface_two.num; - } - - safe_throw(hal::operation_not_supported(this)); - } - - void manage_features(setup_packet p_pkt, bool p_set, u16 p_selector) - { - std::ignore = p_selector; - if (p_pkt.get_recipient() != setup_packet::request_recipient::interface) { - safe_throw(hal::operation_not_supported(this)); - } - - auto iface_idx = p_pkt.index() & 0xFF; - - if (iface_idx == m_iface_one.num) { - m_iface_one.feature = p_set; - } else if (iface_idx == m_iface_two.num) { - m_iface_two.feature = p_set; - } else { - safe_throw(hal::operation_not_supported(this)); - } - } - - u8 driver_get_interface(setup_packet p_pkt) - { - auto iface_idx = p_pkt.index() & 0xFF; - - if (iface_idx == m_iface_one.num) { - return m_iface_one.alt_settings; - } else if (iface_idx == m_iface_two.num) { - return m_iface_two.alt_settings; - } else { - safe_throw(hal::operation_not_supported(this)); - } - } - - void driver_set_interface(setup_packet p_pkt) - { - auto iface_idx = p_pkt.index() & 0xFF; - auto alt_setting = p_pkt.value(); - if (iface_idx == m_iface_one.num) { - m_iface_one.alt_settings = alt_setting; - } else if (iface_idx == m_iface_two.num) { - m_iface_two.alt_settings = alt_setting; - } else { - safe_throw(hal::operation_not_supported(this)); - } - } - - bool driver_handle_request(setup_packet const& p_setup, - endpoint_writer const& p_callback) override - { - std::ignore = p_setup; - std::ignore = p_callback; - return false; - } - -public: - mock_iface_descriptor m_iface_one; - std::u16string_view m_name_one; - mock_iface_descriptor m_iface_two; - std::u16string_view m_name_two; - bool m_wrote_descriptors = false; -}; - -void simulate_sending_payload( - strong_ptr const& ctrl_ptr, - setup_packet& req) -{ - auto vec = pkt_to_scatter(req); - auto scatter_arr = make_scatter_bytes(vec); - scatter_span ss(scatter_arr); - - ctrl_ptr->write_request(ss); -} - -// u8 calculate_conf_desc_recursive(std::span p_conf_arr) -// { -// u8 total_len = 0; -// for (configuration& conf : p_conf_arr) { -// total_len += 9; -// for (auto& iface : conf.interfaces) { -// auto real_iface = dynamic_cast(&(*iface)); -// std::ignore = iface->write_descriptors( -// { .interface = 0, .string = real_iface->interface_name_string_idx() -// }, -// [&total_len](scatter_span p_data) { -// total_len += scatter_span_size(p_data); -// }); -// } -// } - -// return total_len; -// } - -// std::vector generate_conf_descriptors(std::span -// p_conf_arr) -// { -// std::vector vec; -// for (configuration const& conf : p_conf_arr) { -// vec.append_range(std::span(conf)); -// for (auto const& iface : conf.interfaces) { -// std::ignore = -// iface->write_descriptors({ .interface = 0, .string = 0 }, -// [&vec](scatter_span p_dat) { -// for (auto const& s : p_dat) { -// for (auto const& el : s) { -// vec.push_back(el); -// } -// } -// }); -// } -// } - -// return vec; -// } - -class mock_serial : public hal::serial -{ - void driver_configure(settings const& p_settings) override - { - std::ignore = p_settings; - } - - write_t driver_write(std::span p_data) override - { - std::string_view sv(reinterpret_cast(p_data.data()), - p_data.size()); - return { .data = p_data }; - } +#include "libhal-util/mock/usb.hpp" +#include "libhal-util/usb/descriptors.hpp" +#include "libhal-util/usb/utils.hpp" - read_t driver_read(std::span p_data) override - { - std::ignore = p_data; - return { .data = {}, .available = 0, .capacity = 0 }; - } +#include - void driver_flush() override - { - } -}; +namespace hal::v5::usb { -} // namespace boost::ut::suite<"usb_test"> usb_test = [] { // TODO(#78): Add usb utility tests }; @@ -787,4 +269,4 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { }; }; -} // namespace hal::usb +} // namespace hal::v5::usb From 89d140ec455cc77fe5fa5e7b114fa4c99cb47c10 Mon Sep 17 00:00:00 2001 From: PhazonicRidley Date: Tue, 10 Feb 2026 21:16:19 -0800 Subject: [PATCH 18/18] Addressed concerns in PR#92 Added required copyright notices Changed over from span_eq to ranges::equal and removed function Renamed truncated constant names to their full names Changed mock interface's packed array to use a predefined constant instead of a magic number Changed headers in the same library to be relative paths Factored out ctor arguments for device, config, and enumeration Enforced endianess with device descriptor Changed ref getters to return by value and add setters where needed Documented make_sub_scatter_array and changed return type to a struct for clarity Hardcoded string indexes for device descriptors, removed the ablity to set a start for string descriptors --- v5/include/libhal-util/mock/usb.hpp | 62 +++---- v5/include/libhal-util/usb.hpp | 14 ++ v5/include/libhal-util/usb/descriptors.hpp | 151 ++++++++++------ v5/include/libhal-util/usb/enumerator.hpp | 199 +++++++++++++-------- v5/include/libhal-util/usb/utils.hpp | 12 +- v5/tests/usb.test.cpp | 62 ++++--- 6 files changed, 307 insertions(+), 193 deletions(-) diff --git a/v5/include/libhal-util/mock/usb.hpp b/v5/include/libhal-util/mock/usb.hpp index ba73477..db6ded0 100644 --- a/v5/include/libhal-util/mock/usb.hpp +++ b/v5/include/libhal-util/mock/usb.hpp @@ -1,3 +1,17 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #pragma once #include @@ -15,35 +29,14 @@ #include #include -#include "libhal-util/usb/utils.hpp" +#include "../usb/utils.hpp" namespace hal::v5::usb { -constexpr u8 iface_desc_length = 9; -constexpr u8 iface_desc_type = 0x4; -constexpr u8 str_desc_type = 0x3; -constexpr u8 iad_length = 0x08; -constexpr u8 iad_type = 0x0B; - -template -bool span_eq(std::span const lhs, std::span const rhs) -{ - if (lhs.size() != rhs.size()) { - return false; - } - - for (size_t i = 0; i < lhs.size(); i++) { - if (lhs[i] != rhs[i]) { - return false; - } - } - return true; -} - -template -bool span_ne(std::span const lhs, std::span const rhs) -{ - return !(lhs == rhs); -} +constexpr u8 interface_description_length = 9; +constexpr u8 interface_description_type = 0x4; +constexpr u8 string_description_type = 0x3; +constexpr u8 interface_association_descriptor_length = 0x08; +constexpr u8 interface_association_descriptor_type = 0x0B; constexpr std::vector pkt_to_scatter(setup_packet const& req) { @@ -242,9 +235,9 @@ struct mock : public interface return m_packed_array[8]; } - std::array m_packed_array = { - iface_desc_length, - iface_desc_type, + std::array m_packed_array = { + sizeof(m_packed_array), + interface_description_type, 0, // interface_number 0, // alternate_setting 1, // num_endpoints @@ -286,7 +279,8 @@ class iad_mock : public interface auto iface_idx = p_start.interface.value(); auto str_idx = p_start.string.value(); std::array iad_buf{ - iad_length, iad_type, + interface_association_descriptor_length, + interface_association_descriptor_type, 0, // first interface 2, // iface count 0, // class @@ -295,8 +289,8 @@ class iad_mock : public interface str_idx++ // string idx }; - std::array iface_header = { iface_desc_length, - iface_desc_type }; + std::array iface_header = { interface_description_length, + interface_description_type }; std::array static_desc_vars = { 0, // altsettings 1, // num endpoints @@ -346,7 +340,7 @@ class iad_mock : public interface if (!m_wrote_descriptors) { safe_throw(hal::operation_not_permitted(this)); } - std::array header{ 0, str_desc_type }; + std::array header{ 0, string_description_type }; if (p_index == m_iface_one.str_idx) { header[0] = m_name_one.length() + 2; auto arr = make_scatter_bytes( diff --git a/v5/include/libhal-util/usb.hpp b/v5/include/libhal-util/usb.hpp index 0764c04..abfbfb3 100644 --- a/v5/include/libhal-util/usb.hpp +++ b/v5/include/libhal-util/usb.hpp @@ -1,3 +1,17 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #pragma once #include "usb/descriptors.hpp" diff --git a/v5/include/libhal-util/usb/descriptors.hpp b/v5/include/libhal-util/usb/descriptors.hpp index dad93cc..73d3b9c 100644 --- a/v5/include/libhal-util/usb/descriptors.hpp +++ b/v5/include/libhal-util/usb/descriptors.hpp @@ -15,6 +15,7 @@ #pragma once #include +#include #include #include #include @@ -26,7 +27,6 @@ #include #include -#include "libhal-util/as_bytes.hpp" #include "utils.hpp" /* TODO (PhazonicRidley): @@ -44,88 +44,91 @@ struct device struct device_arguments { - u16 p_bcd_usb; - class_code p_device_class; - u8 p_device_subclass; // NOLINT - u8 p_device_protocol; - u16 p_id_vendor; // NOLINT - u16 p_id_product; - u16 p_bcd_device; + u16 bcd_usb; + class_code device_class; + u8 device_subclass; + u8 device_protocol; + u16 id_vendor; + u16 id_product; + u16 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) + constexpr device(device_arguments&& p_args) + : manufacturer_str(p_args.p_manufacturer) + , product_str(p_args.p_product) + , serial_number_str(p_args.p_serial_number_str) { u8 idx = 0; - auto bcd_usb_bytes = hal::as_bytes(&args.p_bcd_usb, 1); + auto bcd_usb_bytes = setup_packet::to_le_u16(p_args.bcd_usb); for (auto& bcd_usb_byte : bcd_usb_bytes) { m_packed_arr[idx++] = bcd_usb_byte; } - m_packed_arr[idx++] = static_cast(args.p_device_class); - m_packed_arr[idx++] = args.p_device_subclass; - m_packed_arr[idx++] = args.p_device_protocol; + m_packed_arr[idx++] = static_cast(p_args.device_class); + m_packed_arr[idx++] = p_args.device_subclass; + m_packed_arr[idx++] = p_args.device_protocol; m_packed_arr[idx++] = 0; // Max Packet length handled by the enumerator - auto id_vendor_bytes = hal::as_bytes(&args.p_id_vendor, 1); + auto id_vendor_bytes = setup_packet::to_le_u16(p_args.id_vendor); for (auto& id_vendor_byte : id_vendor_bytes) { m_packed_arr[idx++] = id_vendor_byte; } - auto id_product_bytes = hal::as_bytes(&args.p_id_product, 1); + auto id_product_bytes = setup_packet::to_le_u16(p_args.id_product); for (auto& id_product_byte : id_product_bytes) { m_packed_arr[idx++] = id_product_byte; } - auto bcd_device_bytes = hal::as_bytes(&args.p_bcd_device, 1); + auto bcd_device_bytes = setup_packet::to_le_u16(p_args.bcd_device); for (auto& bcd_device_byte : bcd_device_bytes) { m_packed_arr[idx++] = bcd_device_byte; } + // Default string indexes, assuming enumeration will use 4 onward for string + // indexes are these are required (can be modified by enumerator if desired) + m_packed_arr[idx++] = 1; // string idx of manufacturer + m_packed_arr[idx++] = 2; // string idx of product + m_packed_arr[idx++] = 3; // string idx of serial number + // Evaluated during enumeration - m_packed_arr[idx++] = 0; // string idx of manufacturer - m_packed_arr[idx++] = 0; // string idx of product - m_packed_arr[idx++] = 0; // string idx of serial number m_packed_arr[idx++] = 0; // Number of possible configurations }; - u16& bcd_usb() + [[nodiscard]] constexpr u16 bcd_usb() const { - return *reinterpret_cast(&m_packed_arr[0]); + return setup_packet::from_le_bytes(m_packed_arr[0], m_packed_arr[1]); } - constexpr u8& device_class() + [[nodiscard]] constexpr u8 device_class() const { return m_packed_arr[2]; } - constexpr u8& device_sub_class() + [[nodiscard]] constexpr u8 device_sub_class() const { return m_packed_arr[3]; } - constexpr u8& device_protocol() + [[nodiscard]] constexpr u8 device_protocol() const { return m_packed_arr[4]; } - u16& id_vendor() + [[nodiscard]] constexpr u16 id_vendor() const { - return *reinterpret_cast(&m_packed_arr[6]); + return setup_packet::from_le_bytes(m_packed_arr[6], m_packed_arr[7]); } - u16& id_product() + [[nodiscard]] constexpr u16 id_product() const { - return *reinterpret_cast(&m_packed_arr[8]); + return setup_packet::from_le_bytes(m_packed_arr[8], m_packed_arr[9]); } - u16& bcd_device() + [[nodiscard]] constexpr u16 bcd_device() const { - return *reinterpret_cast(&m_packed_arr[10]); + return setup_packet::from_le_bytes(m_packed_arr[10], m_packed_arr[11]); } operator std::span() const @@ -138,27 +141,38 @@ struct device std::u16string_view serial_number_str; private: - constexpr u8& max_packet_size() + [[nodiscard]] constexpr u8 max_packet_size() const { return m_packed_arr[5]; } + constexpr void set_max_packet_size(u8 p_size) + { + m_packed_arr[5] = p_size; + } - constexpr u8& manufacturer_index() + [[nodiscard]] constexpr u8 manufacturer_index() const { return m_packed_arr[12]; } - constexpr u8& product_index() + + [[nodiscard]] constexpr u8 product_index() const { return m_packed_arr[13]; } - constexpr u8& serial_number_index() + + [[nodiscard]] constexpr u8 serial_number_index() const { return m_packed_arr[14]; } - constexpr u8& num_configurations() + + [[nodiscard]] constexpr u8 num_configurations() const { return m_packed_arr[15]; } + constexpr void set_num_configurations(u8 p_count) + { + m_packed_arr[15] = p_count; + } std::array m_packed_arr; }; @@ -207,14 +221,22 @@ struct configuration u8 m_bitmap; }; + /** Configuration descriptor info. + * @note The name string_view must outlive the configuration object. + */ + struct configuration_info + { + std::u16string_view name; + bitmap attributes; + u8 max_power; + std::pmr::polymorphic_allocator<> allocator; + }; + template - constexpr configuration(std::u16string_view p_name, - bitmap&& p_attributes, - u8&& p_max_power, - std::pmr::polymorphic_allocator<> p_allocator, + constexpr configuration(configuration_info p_info, strong_ptr... p_interfaces) - : name(p_name) - , interfaces(p_allocator) + : name(p_info.name) + , interfaces(p_info.allocator) { interfaces.reserve(sizeof...(p_interfaces)); (interfaces.push_back(p_interfaces), ...); @@ -227,8 +249,8 @@ struct configuration 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 + m_packed_arr[idx++] = p_info.attributes.to_byte(); // 5 + m_packed_arr[idx++] = p_info.max_power; // 6 } operator std::span() const @@ -240,12 +262,12 @@ struct configuration { return { m_packed_arr[5] }; } - constexpr u8& attributes_byte() + [[nodiscard]] constexpr u8 attributes_byte() const { return m_packed_arr[5]; } - constexpr u8& max_power() + [[nodiscard]] constexpr u8 max_power() const { return m_packed_arr[6]; } @@ -253,23 +275,44 @@ struct configuration std::u16string_view name; std::pmr::vector> interfaces; - // private: - u16& total_length() +private: + [[nodiscard]] constexpr u16 total_length() const { - return *reinterpret_cast(&m_packed_arr[0]); + return setup_packet::from_le_bytes(m_packed_arr[0], m_packed_arr[1]); } - constexpr u8& num_interfaces() + + constexpr void set_total_length(u16 p_length) + { + auto bytes = setup_packet::to_le_u16(p_length); + m_packed_arr[0] = bytes[0]; + m_packed_arr[1] = bytes[1]; + } + [[nodiscard]] constexpr u8 num_interfaces() const { return m_packed_arr[2]; } - constexpr u8& configuration_value() + constexpr void set_num_interfaces(u8 p_count) + { + m_packed_arr[2] = p_count; + } + + [[nodiscard]] constexpr u8 configuration_value() const { return m_packed_arr[3]; } - constexpr u8& configuration_index() + constexpr void set_configuration_value(u8 p_value) + { + m_packed_arr[3] = p_value; + } + + [[nodiscard]] constexpr u8 configuration_index() const { return m_packed_arr[4]; } + constexpr void set_configuration_index(u8 p_index) + { + m_packed_arr[4] = p_index; + } std::array m_packed_arr; }; diff --git a/v5/include/libhal-util/usb/enumerator.hpp b/v5/include/libhal-util/usb/enumerator.hpp index e323e2f..4077ee9 100644 --- a/v5/include/libhal-util/usb/enumerator.hpp +++ b/v5/include/libhal-util/usb/enumerator.hpp @@ -37,7 +37,6 @@ #include "libhal-util/usb/endpoints.hpp" #include "utils.hpp" -// TODO: move to util namespace hal::v5::usb { template @@ -50,9 +49,68 @@ constexpr size_t scatter_span_size(scatter_span ss) return res; } + +/** + * @brief Result of make_sub_scatter_array containing the scatter span array + * and the number of valid spans within it. + */ +template +struct sub_scatter_result +{ + // Array of spans composing the sub scatter span + std::array, N> spans; + // Number of valid spans in the array (may be less than N if truncated) + size_t count; +}; + +// TODO: Create proper scatter span data structure and remove this +/** + * @brief Create a sub scatter span from span fragments. Meaning only create a + * composite scatter span of a desired length instead of being composed of every + * span. + * + * eg: + * first_span = {1, 2, 3} + * second_span = {4, 5, 6, 7} + * new_sub_span = make_scatter_span_array(5, first_span, second_span) => {1, 2, + * 3, 4, 5} + * + * Unfortunately, it is not as clean as the above psuedo code. In reality + * this function returns the required spans of a given sub scatter span and the + * number of required spans. As of this time starting location is always the + * start of the first span given + * + * Usage: + * @code{.cpp} + * std::array first = {1, 2, 3}; + * std::array second = {4, 5, 6, 7}; + * std::array third = {8, 9}; + * + * // Request only 5 bytes from 9 total + * auto result = make_sub_scatter_bytes(5, first, second, third); + * + * // result.spans -> Spans in sub scatter span + * // result.count -> Number of scatter spans needed to account for desired + * elements + * + * // Use count to limit the scatter_span to valid spans only + * auto ss = scatter_span(result.spans).first(result.count); + * @endcode + * + * @tparam T - The type each span contains + * @tparam Args... - The spans to compose the scatter span + * + * @param p_count - Number of elements (of type T) desired for scatter span + * @param p_spans - The spans that will be used to compose the new sub scatter + * span. + * + * @return A sub_scatter_result containing the spans used within the + * scatter_span and the number of spans in the sub scatter span. + */ template -constexpr std::pair, sizeof...(Args)>, size_t> -make_sub_scatter_array(size_t p_count, Args&&... p_spans) +constexpr sub_scatter_result make_sub_scatter_array( + size_t p_count, + Args&&... p_spans) { std::array, sizeof...(Args)> full_ss{ std::span( std::forward(p_spans))... }; @@ -62,21 +120,21 @@ make_sub_scatter_array(size_t p_count, Args&&... p_spans) std::array lens{ std::span(p_spans).size()... }; if (total_span_len <= p_count) { - return std::make_pair(full_ss, full_ss.size()); + return { .spans = full_ss, .count = full_ss.size() }; } size_t cur_len = 0; size_t i = 0; for (; i < lens.size(); i++) { - auto l = lens[i]; + auto ith_span_length = lens[i]; - if (p_count >= (cur_len + l)) { + if (p_count >= (cur_len + ith_span_length)) { res[i] = full_ss[i]; - cur_len += l; + cur_len += ith_span_length; continue; } if (cur_len >= p_count) { - return std::make_pair(res, i); + return { .spans = res, .count = i }; } auto delta = p_count - cur_len; @@ -85,11 +143,15 @@ make_sub_scatter_array(size_t p_count, Args&&... p_spans) break; } - return std::make_pair(res, i + 1); + return { .spans = res, .count = i + 1 }; } +/** + * @brief Convenience wrapper for make_sub_scatter_array with byte const type. + * @see make_sub_scatter_array + */ template -constexpr std::pair, sizeof...(Args)>, size_t> +constexpr sub_scatter_result make_sub_scatter_bytes(size_t p_count, Args&&... p_spans) { return make_sub_scatter_array(p_count, @@ -101,30 +163,20 @@ 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, - bool enumerate_immediately = true) - : m_ctrl_ep(p_ctrl_ep) - , m_device(p_device) - , m_configs(p_configs) - , m_lang_str(p_lang_str) + struct args + { + strong_ptr ctrl_ep; + strong_ptr device; + strong_ptr> configs; + u16 lang_str; + }; + + enumerator(args p_args) + : m_ctrl_ep(p_args.ctrl_ep) + , m_device(p_args.device) + , m_configs(p_args.configs) + , m_lang_str(p_args.lang_str) { - // 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() @@ -135,25 +187,25 @@ class enumerator m_ctrl_ep->connect(false); } - auto cur_str_idx = m_starting_str_idx; + // String indexes 1-3 are reserved for device descriptor strings + // (manufacturer, product, serial number). Configuration strings start at 4. + u8 cur_str_idx = 4; 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; + m_device->set_num_configurations(num_configs); // 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; + config.set_configuration_index(cur_str_idx++); + config.set_configuration_value(i + 1); } for (configuration& config : *m_configs) { - auto total_length = static_cast(constants::config_desc_size); + auto total_length = + static_cast(constants::configuration_descriptor_size); for (auto const& iface : config.interfaces) { auto deltas = iface->write_descriptors( { .interface = cur_iface_idx, .string = cur_str_idx }, @@ -164,8 +216,8 @@ class enumerator cur_iface_idx += deltas.interface; cur_str_idx += deltas.string; } - config.num_interfaces() = cur_iface_idx; - config.total_length() = total_length; + config.set_num_interfaces(cur_iface_idx); + config.set_total_length(total_length); } // Phase two: Writing @@ -196,7 +248,7 @@ class enumerator continue; } - if (num_bytes_read != constants::size_std_req) { + if (num_bytes_read != constants::standard_request_size) { safe_throw(hal::message_size(num_bytes_read, this)); } @@ -252,7 +304,7 @@ class enumerator if (determine_standard_request(req) == standard_request_types::get_descriptor && - static_cast((req.value() & 0xFF << 8) >> 8) == + static_cast(req.value() >> 8 & 0xFF) == descriptor_type::string) { handle_str_descriptors(req.value() & 0xFF, req.length() > 2); @@ -308,8 +360,8 @@ class enumerator 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)); + auto conf_value = m_active_conf->configuration_value(); + auto scatter_conf = make_scatter_bytes(std::span(&conf_value, 1)); m_ctrl_ep->write(scatter_conf); break; } @@ -327,21 +379,22 @@ class enumerator void process_get_descriptor(setup_packet& req) { - hal::byte desc_type = (req.value() & 0xFF << 8) >> 8; + hal::byte desc_type = req.value() >> 8 & 0xFF; [[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, + std::to_array({ constants::device_descriptor_size, static_cast(descriptor_type::device) }); - m_device->max_packet_size() = static_cast(m_ctrl_ep->info().size); + m_device->set_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)); + scatter_span(scatter_arr_pair.spans) + .first(scatter_arr_pair.count)); break; } @@ -349,21 +402,21 @@ class enumerator case descriptor_type::configuration: { configuration& conf = m_configs->at(desc_idx); auto conf_hdr = - std::to_array({ constants::config_desc_size, + std::to_array({ constants::configuration_descriptor_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)); + m_ctrl_ep->write(scatter_span(scatter_conf_pair.spans) + .first(scatter_conf_pair.count)); // Return early if the only thing requested was the config descriptor - if (req.length() <= constants::config_desc_size) { + if (req.length() <= constants::configuration_descriptor_size) { m_ctrl_ep->write({}); return; } - u16 total_size = constants::config_desc_size; + u16 total_size = constants::configuration_descriptor_size; for (auto const& iface : conf.interfaces) { std::ignore = iface->write_descriptors( { .interface = std::nullopt, .string = std::nullopt }, @@ -390,8 +443,8 @@ class enumerator static_cast(descriptor_type::string) }); auto lang = setup_packet::to_le_u16(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); + // auto p = scatter_span(scatter_arr_pair.spans) + // .first(scatter_arr_pair.count); m_ctrl_ep->write(scatter_arr_pair); m_ctrl_ep->write({}); break; @@ -410,8 +463,9 @@ class enumerator void handle_str_descriptors(u8 const target_idx, u16 p_len) { - - u8 cfg_string_end = m_starting_str_idx + 3 + num_configs; + // Device strings at indexes 1-3, configuration strings start at 4 + constexpr u8 device_str_start = 1; + u8 cfg_string_end = device_str_start + 3 + num_configs; if (target_idx <= cfg_string_end) { auto r = write_cfg_str_descriptor(target_idx, p_len); if (!r) { @@ -466,23 +520,26 @@ class enumerator 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; + // Device strings are at fixed indexes: 1=manufacturer, 2=product, 3=serial + constexpr u8 manufacturer_idx = 1; + constexpr u8 product_idx = 2; + constexpr u8 serial_number_idx = 3; + constexpr u8 config_start_idx = 4; + std::optional opt_conf_sv; - if (target_idx == (m_starting_str_idx + dev_manu_offset)) { + if (target_idx == manufacturer_idx) { opt_conf_sv = m_device->manufacturer_str; - } else if (target_idx == (m_starting_str_idx + dev_prod_offset)) { + } else if (target_idx == product_idx) { opt_conf_sv = m_device->product_str; - } else if (target_idx == (m_starting_str_idx + dev_sn_offset)) { + } else if (target_idx == serial_number_idx) { 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)) { + if (target_idx == (config_start_idx + i)) { opt_conf_sv = conf.name; } } @@ -519,8 +576,8 @@ class enumerator 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); + auto p = scatter_span(scatter_arr_pair.spans) + .first(scatter_arr_pair.count); hal::v5::write_and_flush(*m_ctrl_ep, p); return true; @@ -530,7 +587,7 @@ class enumerator 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; diff --git a/v5/include/libhal-util/usb/utils.hpp b/v5/include/libhal-util/usb/utils.hpp index 77488a4..74a739c 100644 --- a/v5/include/libhal-util/usb/utils.hpp +++ b/v5/include/libhal-util/usb/utils.hpp @@ -22,13 +22,13 @@ namespace hal::v5::usb { 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 device_descriptor_size = 18; +constexpr byte configuration_descriptor_size = 9; +constexpr byte inferface_descriptor_size = 9; +constexpr byte endpoint_descriptor_size = 7; +constexpr byte interface_association_descriptor_size = 0x08; -constexpr byte size_std_req = 8; +constexpr byte standard_request_size = 8; } // namespace constants diff --git a/v5/tests/usb.test.cpp b/v5/tests/usb.test.cpp index 728891a..55e3987 100644 --- a/v5/tests/usb.test.cpp +++ b/v5/tests/usb.test.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -21,7 +22,10 @@ #include #include +#include #include +#include +#include #include #include #include @@ -29,10 +33,6 @@ #include #include -#include "libhal-util/mock/usb.hpp" -#include "libhal-util/usb/descriptors.hpp" -#include "libhal-util/usb/utils.hpp" - #include namespace hal::v5::usb { @@ -48,23 +48,23 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { static std::array iface_buf; iface_buf.fill(0); - device dev({ .p_bcd_usb = 0x0002, - .p_device_class = class_code::application_specific, - .p_device_subclass = 0, - .p_device_protocol = 0, - .p_id_vendor = 0xffff, - .p_id_product = 0x0000, - .p_bcd_device = 0x0001, + device dev({ .bcd_usb = 0x0002, + .device_class = class_code::application_specific, + .device_subclass = 0, + .device_protocol = 0, + .id_vendor = 0xffff, + .id_product = 0x0000, + .bcd_device = 0x0001, .p_manufacturer = u"libhal", .p_product = u"Unit Test", .p_serial_number_str = u"123456789" }); pmr::monotonic_buffer_resource pool(iface_buf.data(), std::size(iface_buf)); std::array conf_arr{ configuration{ - u"Test Config", - configuration::bitmap(true, false), - 1, - &pool, + { .name = u"Test Config", + .attributes = configuration::bitmap(true, false), + .max_power = 1, + .allocator = &pool }, make_strong_ptr(&pool, mock(u"Mock Iface")) } }; mock_usb_control_endpoint ctrl_ep; @@ -74,11 +74,17 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { make_strong_ptr>(&pool, conf_arr); auto dev_ptr = make_strong_ptr(&pool, dev); auto console_ptr = make_strong_ptr(&pool, mock_serial()); - enumerator<1> en{ ctrl_ptr, dev_ptr, conf_arr_ptr, 0x0409, 1, false }; - "basic usb enumeration test"_test = [&en, &ctrl_ptr] { + "basic usb enumeration test"_test = [&ctrl_ptr, &dev_ptr, &conf_arr_ptr] { // Start enumeration process and verify connection - auto f = [&en]() { en.enumerate(); }; + std::optional> en; + auto f = [&en, &ctrl_ptr, &dev_ptr, &conf_arr_ptr]() { + en.emplace(enumerator<1>::args{ .ctrl_ep = ctrl_ptr, + .device = dev_ptr, + .configs = conf_arr_ptr, + .lang_str = 0x0409 }); + en->enumerate(); + }; constexpr byte delay_time_ms = 1000; auto& ctrl_buf = ctrl_ptr->m_out_buf; std::thread ejh(f); @@ -139,7 +145,7 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { 1 // num configuration }; - expect(that % (span_eq(std::span(dev_expected), dev_actual))); + expect(std::ranges::equal(std::span(dev_expected), dev_actual)); ctrl_buf.clear(); // Get a string descriptor header from device @@ -163,8 +169,8 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { static_cast(descriptor_type::string) }; std::span actual_dev_str_hdr(ctrl_buf.data(), 2); - expect(that % (span_eq(std::span(expected_manu_str_hdr), - actual_dev_str_hdr))); + expect(std::ranges::equal(std::span(expected_manu_str_hdr), + actual_dev_str_hdr)); ctrl_buf.clear(); // Get a string descriptor from device @@ -209,8 +215,8 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { 0x1, // max power // Interface descriptor - iface_desc_length, - iface_desc_type, + interface_description_length, + interface_description_type, 0x0, // iface number 0x0, // alt settings 0x1, // number of endpoints @@ -243,8 +249,8 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { simulate_sending_payload(ctrl_ptr, conf_req); ctrl_ptr->simulate_interrupt(); std::this_thread::sleep_for(std::chrono::milliseconds(delay_time_ms)); - expect(that % (span_eq(std::span(expected_conf_iface_desc), - std::span(ctrl_buf)))); + expect(std::ranges::equal(std::span(expected_conf_iface_desc), + std::span(ctrl_buf))); // Set configuration setup_packet set_conf_req( @@ -263,9 +269,9 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] { ejh.join(); // Verify active config - expect(that % - (span_eq(std::span(expected_conf_iface_desc.data() + 2, 7), - std::span(en.get_active_configuration())))); + expect(std::ranges::equal( + std::span(expected_conf_iface_desc.data() + 2, 7), + std::span(en->get_active_configuration()))); }; };