Skip to content

Conversation

@PhazonicRidley
Copy link

@PhazonicRidley PhazonicRidley commented Jan 30, 2026

Implemented a USB enumerator that can be used in various projects. This is meant as a default and reference implementation of an enumerator using the libhal USB system.

@PhazonicRidley PhazonicRidley changed the title ✨ Usb enumeration ✨ (minor) Usb enumeration Jan 30, 2026
@PhazonicRidley PhazonicRidley marked this pull request as draft February 1, 2026 05:19
@PhazonicRidley PhazonicRidley force-pushed the usb-enumeration branch 2 times, most recently from 780ef28 to f41be54 Compare February 5, 2026 20:57
@kammce kammce self-requested a review February 7, 2026 22:59
Copy link
Member

@kammce kammce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase your project and fix your merge conflicts. Also, we moved the libhal-util code into v5 in preperation for v6 which will use libhal-v5. So we can remove the include files within the include directory and only keep the ones within v5/*

@PhazonicRidley PhazonicRidley force-pushed the usb-enumeration branch 3 times, most recently from 6b474f8 to c9c62f1 Compare February 9, 2026 19:10
@PhazonicRidley PhazonicRidley marked this pull request as ready for review February 10, 2026 00:26
PhazonicRidley and others added 17 commits February 9, 2026 16:28
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.
Simulated host by sending enumerator requests and having it respond.
Correct payloads were verified
Changed usb.hpp to be a shorthand for including all usb utils
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.
Simulated host by sending enumerator requests and having it respond.
Correct payloads were verified
Changed usb.hpp to be a shorthand for including all usb utils
Copy link
Member

@kammce kammce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed everything. I'm posting what I have now because I'm not sure if github will lose my info.

}

std::u16string_view name;
std::pmr::vector<strong_ptr<interface>> interfaces;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using hal::allocated_buffer<strong_ptr<interface>> instead of vector. We don't need to resize the array after construction, and thus all of its code needed to handle pushing and resizing can be eliminated. This way we pay for only what we needed which is a pointer, a length, and the allocator that created.

#include <libhal/allocated_buffer.hpp>

It was meant for just buffers but its usable for any type. (maybe I should make an alias for it thats dynamic_array).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized a concern I have with std::pmr::vector. And apologies for bringing this up on late notice, but I think we should design with the intention that our allocator is monotonic and thus doesn't free memory. Meaning, if we make a copy of this configuration and destroy the old one, we don't get that memory back. That makes copying this type problematic. I don't have an answer. We may want to talk over voice chat to discuss this further.

}
}

void enumerate()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is purely a suggestion but I think we might benefit from this rename:

Suggested change
void enumerate()
void enumerate_sync_prototype0()

Why? We can provide this temporary API that we plan to deprecate in the future. This way we can work with the experimental stuff while preventing the API from being broken. This will allow us to publish the code and leave space for a better implementation in the future. Because making this async may require a breaking API change. Or a new API called enumerate2().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about enumerate_sync()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because I don't think this API is going to stick around. I don't know if enumeration_sync() is usable without an RTOS. I'm hoping we don't need an RTOS for our APIs.

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
Copy link
Member

@kammce kammce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a partial review of the changes you've made. Things are looking really good. I plan to dig into the details of the enumerator next. Thank you for your effort on this!

#pragma once

#include <array>
#include <libhal/allocated_buffer.hpp>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this with the other libhal includes. Keep it away from the C++ headers.

}

std::u16string_view name;
std::pmr::vector<strong_ptr<interface>> interfaces;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized a concern I have with std::pmr::vector. And apologies for bringing this up on late notice, but I think we should design with the intention that our allocator is monotonic and thus doesn't free memory. Meaning, if we make a copy of this configuration and destroy the old one, we don't get that memory back. That makes copying this type problematic. I don't have an answer. We may want to talk over voice chat to discuss this further.

if (determine_standard_request(req) ==
standard_request_types::get_descriptor &&
static_cast<descriptor_type>((req.value() & 0xFF << 8) >> 8) ==
static_cast<descriptor_type>(req.value() >> 8 & 0xFF) ==
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: I see this value() >> 8 & 0xFF a few times in the code? Maybe this warrants a free function or class method for getting this specific value from the request?

@@ -74,11 +74,17 @@ boost::ut::suite<"enumeration_test"> enumeration_test = [] {
make_strong_ptr<std::array<configuration, 1>>(&pool, conf_arr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned that this results in a copy. It should be a move. I'm wondering if we should make configuration a move-only object.

Copy link
Member

@kammce kammce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright, this is the review of the enumerator it self. There is a lot of extra code hanging around that needs some cleanup. And I think we should think very critically about errors and how we expect developers to handle them. No error should come without a means to handle it. And I think in most cases, the exceptions can be eliminated entirely.

Comment on lines +255 to +259
//
// for (auto const& el : raw_req) {
//
// }
//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//
// for (auto const& el : raw_req) {
//
// }
//


// TODO: Handle exception
handle_standard_device_request(req);
// m_ctrl_ep->write({}); // Send ZLP to complete Data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// m_ctrl_ep->write({}); // Send ZLP to complete Data

Was that still needed?

} while (!finished_enumeration);
}

[[nodiscard]] configuration& get_active_configuration()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to be public? And if so, what is the expected use case for devs?

Comment on lines +289 to +291
while (!m_has_setup_packet) {
continue;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not just return from this point? Why must we halt until a setup package comes in?

standard_request_types::get_descriptor &&
static_cast<descriptor_type>(req.value() >> 8 & 0xFF) ==
descriptor_type::string) {
handle_str_descriptors(req.value() & 0xFF, req.length() > 2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API accepts a length as the second parameter, but you've only passed in a bool which is true/false based on the request length. Is that what you wanted?

Comment on lines +441 to +445
auto s_hdr =
std::to_array({ static_cast<byte>(4),
static_cast<byte>(descriptor_type::string) });
auto lang = setup_packet::to_le_u16(m_lang_str);
auto scatter_arr_pair = make_scatter_bytes(s_hdr, lang);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be its own descriptor to manage all of this?

}
}

void handle_str_descriptors(u8 const target_idx, u16 p_len)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void handle_str_descriptors(u8 const target_idx, u16 p_len)
void handle_str_descriptors(u8 target_idx, u16 p_len)

No need for const here.

Comment on lines +557 to +568
std::mbstate_t state{};
for (wchar_t const wc : sv) {
std::array<char, 1024> tmp;
size_t len = std::wcrtomb(tmp.data(), wc, &state);
if (len == static_cast<size_t>(-1)) {

continue;
}

for (size_t i = 0; i < len; i++) {
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intention of this code?

Comment on lines +570 to +580
auto const conf_sv_span = hal::as_bytes(opt_conf_sv.value());
auto desc_len = static_cast<hal::byte>((conf_sv_span.size() + 2));

auto hdr_arr = std::to_array(
{ desc_len, static_cast<hal::byte>(descriptor_type::string) });

auto scatter_arr_pair =
make_sub_scatter_bytes(p_len, hdr_arr, conf_sv_span);

auto p = scatter_span<byte const>(scatter_arr_pair.spans)
.first(scatter_arr_pair.count);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like a string_descriptor like thing would be useful. One that provides sub scatter spans based on length.

Comment on lines +337 to +339
if (!req_handled) {
safe_throw(hal::argument_out_of_domain(this));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should the caller of this function do when they get this exception? This is more of an issue regarding the host. And if so, is reporting this to the caller important or should the enumerator handle it itself. Like using STALL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants