Implement Switch2 descriptors
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
0ec06676c3
commit
3251e49191
2 changed files with 230 additions and 0 deletions
64
switch2_descriptors.cpp
Normal file
64
switch2_descriptors.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include "switch2_descriptors.h"
|
||||
|
||||
// Captured Pro Controller 2 descriptors:
|
||||
// https://github.com/ndeadly/switch2_controller_research/blob/d1c5a7f7ba298f83017fae84952a4e6d2ef8fc92/descriptors.md
|
||||
const uint8_t switch2_device_descriptor[] = {
|
||||
0x12, 0x01, 0x00, 0x02, 0xEF, 0x02, 0x01, 0x40, 0x7E,
|
||||
0x05, 0x69, 0x20, 0x00, 0x02, 0x01, 0x02, 0x03, 0x01,
|
||||
};
|
||||
const size_t switch2_device_descriptor_length = sizeof(switch2_device_descriptor);
|
||||
|
||||
// The capture has five interfaces and a 268-byte configuration. This explicit
|
||||
// experimental subset keeps captured HID/vendor interfaces 0-1 only, changes
|
||||
// wTotalLength to 80 and bNumInterfaces to 2, and zeros uncaptured string
|
||||
// indices iConfiguration and iInterface. Audio interfaces 2-4 are omitted.
|
||||
const uint8_t switch2_configuration_descriptor[] = {
|
||||
0x09, 0x02, 0x50, 0x00, 0x02, 0x01, 0x00, 0xC0, 0xFA,
|
||||
|
||||
0x08, 0x0B, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00,
|
||||
0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00,
|
||||
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x61, 0x00,
|
||||
0x07, 0x05, 0x81, 0x03, 0x40, 0x00, 0x04,
|
||||
0x07, 0x05, 0x01, 0x03, 0x40, 0x00, 0x04,
|
||||
|
||||
0x08, 0x0B, 0x01, 0x01, 0xFF, 0x00, 0x00, 0x00,
|
||||
0x09, 0x04, 0x01, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00,
|
||||
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
|
||||
0x07, 0x05, 0x82, 0x02, 0x40, 0x00, 0x00,
|
||||
};
|
||||
const size_t switch2_configuration_descriptor_length =
|
||||
sizeof(switch2_configuration_descriptor);
|
||||
|
||||
const uint8_t switch2_hid_report_descriptor[] = {
|
||||
0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x05,
|
||||
0x05, 0xFF, 0x09, 0x01, 0x15, 0x00, 0x26, 0xFF, 0x00,
|
||||
0x95, 0x3F, 0x75, 0x08, 0x81, 0x02,
|
||||
|
||||
0x85, 0x09, 0x09, 0x01, 0x95, 0x02, 0x81, 0x02,
|
||||
0x05, 0x09, 0x19, 0x01, 0x29, 0x15, 0x25, 0x01,
|
||||
0x95, 0x15, 0x75, 0x01, 0x81, 0x02,
|
||||
0x95, 0x01, 0x75, 0x03, 0x81, 0x03,
|
||||
0x05, 0x01, 0x09, 0x01, 0xA1, 0x00,
|
||||
0x09, 0x30, 0x09, 0x31, 0x09, 0x33, 0x09, 0x35,
|
||||
0x26, 0xFF, 0x0F, 0x95, 0x04, 0x75, 0x0C, 0x81, 0x02, 0xC0,
|
||||
0x05, 0xFF, 0x09, 0x02, 0x26, 0xFF, 0x00,
|
||||
0x95, 0x34, 0x75, 0x08, 0x81, 0x02,
|
||||
|
||||
0x85, 0x02, 0x09, 0x01, 0x95, 0x3F, 0x91, 0x02, 0xC0,
|
||||
};
|
||||
const size_t switch2_hid_report_descriptor_length =
|
||||
sizeof(switch2_hid_report_descriptor);
|
||||
|
||||
const uint8_t switch2_string_language[] = {0x09, 0x04};
|
||||
const size_t switch2_string_language_length = sizeof(switch2_string_language);
|
||||
const uint8_t switch2_string_manufacturer[] = "Nintendo";
|
||||
const size_t switch2_string_manufacturer_length =
|
||||
sizeof(switch2_string_manufacturer) - 1;
|
||||
const uint8_t switch2_string_product[] = "Switch 2 Pro Controller";
|
||||
const size_t switch2_string_product_length = sizeof(switch2_string_product) - 1;
|
||||
const uint8_t switch2_string_serial[] = "00";
|
||||
const size_t switch2_string_serial_length = sizeof(switch2_string_serial) - 1;
|
||||
|
||||
static_assert(sizeof(switch2_device_descriptor) == 18);
|
||||
static_assert(sizeof(switch2_configuration_descriptor) == 80);
|
||||
static_assert(sizeof(switch2_hid_report_descriptor) == 97);
|
||||
166
tests/firmware/test_switch2_descriptors.cpp
Normal file
166
tests/firmware/test_switch2_descriptors.cpp
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#include "test_support.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "../../switch2_descriptors.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool bytes_equal(
|
||||
const uint8_t* actual,
|
||||
std::size_t actual_length,
|
||||
const uint8_t* expected,
|
||||
std::size_t expected_length) {
|
||||
return actual_length == expected_length &&
|
||||
std::memcmp(actual, expected, expected_length) == 0;
|
||||
}
|
||||
|
||||
bool switch2_descriptor_bytes_match_pinned_capture_subset() {
|
||||
// Given: captured Pro Controller 2 bytes with the documented subset edits.
|
||||
static constexpr uint8_t expected_device[] = {
|
||||
0x12, 0x01, 0x00, 0x02, 0xEF, 0x02, 0x01, 0x40, 0x7E,
|
||||
0x05, 0x69, 0x20, 0x00, 0x02, 0x01, 0x02, 0x03, 0x01,
|
||||
};
|
||||
static constexpr uint8_t expected_configuration[] = {
|
||||
0x09, 0x02, 0x50, 0x00, 0x02, 0x01, 0x00, 0xC0, 0xFA,
|
||||
0x08, 0x0B, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00,
|
||||
0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00,
|
||||
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x61, 0x00,
|
||||
0x07, 0x05, 0x81, 0x03, 0x40, 0x00, 0x04,
|
||||
0x07, 0x05, 0x01, 0x03, 0x40, 0x00, 0x04,
|
||||
0x08, 0x0B, 0x01, 0x01, 0xFF, 0x00, 0x00, 0x00,
|
||||
0x09, 0x04, 0x01, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00,
|
||||
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
|
||||
0x07, 0x05, 0x82, 0x02, 0x40, 0x00, 0x00,
|
||||
};
|
||||
static constexpr uint8_t expected_hid[] = {
|
||||
0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x05, 0x05, 0xFF, 0x09, 0x01, 0x15, 0x00, 0x26, 0xFF,
|
||||
0x00, 0x95, 0x3F, 0x75, 0x08, 0x81, 0x02, 0x85, 0x09, 0x09, 0x01, 0x95, 0x02, 0x81, 0x02, 0x05,
|
||||
0x09, 0x19, 0x01, 0x29, 0x15, 0x25, 0x01, 0x95, 0x15, 0x75, 0x01, 0x81, 0x02, 0x95, 0x01, 0x75,
|
||||
0x03, 0x81, 0x03, 0x05, 0x01, 0x09, 0x01, 0xA1, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x33, 0x09,
|
||||
0x35, 0x26, 0xFF, 0x0F, 0x95, 0x04, 0x75, 0x0C, 0x81, 0x02, 0xC0, 0x05, 0xFF, 0x09, 0x02, 0x26,
|
||||
0xFF, 0x00, 0x95, 0x34, 0x75, 0x08, 0x81, 0x02, 0x85, 0x02, 0x09, 0x01, 0x95, 0x3F, 0x91, 0x02,
|
||||
0xC0,
|
||||
};
|
||||
|
||||
// When/Then: all exported descriptor bytes and lengths match exactly.
|
||||
CHECK(bytes_equal(switch2_device_descriptor, switch2_device_descriptor_length,
|
||||
expected_device, sizeof(expected_device)));
|
||||
CHECK(bytes_equal(switch2_configuration_descriptor, switch2_configuration_descriptor_length,
|
||||
expected_configuration, sizeof(expected_configuration)));
|
||||
CHECK(bytes_equal(switch2_hid_report_descriptor, switch2_hid_report_descriptor_length,
|
||||
expected_hid, sizeof(expected_hid)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool switch2_configuration_iterates_two_interfaces_and_four_endpoints() {
|
||||
// Given: the deliberately reduced configuration descriptor.
|
||||
std::size_t offset = 0;
|
||||
uint8_t interface_count = 0;
|
||||
uint8_t endpoint_count = 0;
|
||||
const uint8_t expected_endpoints[][5] = {
|
||||
{0x81, 0x03, 0x40, 0x00, 0x04},
|
||||
{0x01, 0x03, 0x40, 0x00, 0x04},
|
||||
{0x02, 0x02, 0x40, 0x00, 0x00},
|
||||
{0x82, 0x02, 0x40, 0x00, 0x00},
|
||||
};
|
||||
|
||||
// When: every USB descriptor is iterated by bLength.
|
||||
while (offset < switch2_configuration_descriptor_length) {
|
||||
const uint8_t length = switch2_configuration_descriptor[offset];
|
||||
CHECK(length >= 2);
|
||||
CHECK(offset + length <= switch2_configuration_descriptor_length);
|
||||
const uint8_t type = switch2_configuration_descriptor[offset + 1];
|
||||
if (type == 0x04) {
|
||||
CHECK(length == 9);
|
||||
CHECK(switch2_configuration_descriptor[offset + 5] != 0x01);
|
||||
CHECK(switch2_configuration_descriptor[offset + 8] == 0x00);
|
||||
++interface_count;
|
||||
} else if (type == 0x05) {
|
||||
CHECK(length == 7);
|
||||
CHECK(endpoint_count < 4);
|
||||
CHECK(std::memcmp(&switch2_configuration_descriptor[offset + 2],
|
||||
expected_endpoints[endpoint_count], 5) == 0);
|
||||
++endpoint_count;
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
|
||||
// Then: iteration is exact, contains only HID/vendor, and omits audio.
|
||||
CHECK(offset == switch2_configuration_descriptor_length);
|
||||
CHECK(switch2_configuration_descriptor[6] == 0x00);
|
||||
CHECK(interface_count == 2);
|
||||
CHECK(endpoint_count == 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool switch2_hid_reports_are_exactly_sixty_three_payload_bytes() {
|
||||
// Given: the captured HID report descriptor.
|
||||
uint32_t report_size = 0;
|
||||
uint32_t report_count = 0;
|
||||
uint32_t report_id = 0;
|
||||
uint32_t input_05_bits = 0;
|
||||
uint32_t input_09_bits = 0;
|
||||
uint32_t output_02_bits = 0;
|
||||
|
||||
// When: HID short items are parsed and report fields accumulated.
|
||||
for (std::size_t offset = 0; offset < switch2_hid_report_descriptor_length;) {
|
||||
const uint8_t prefix = switch2_hid_report_descriptor[offset++];
|
||||
CHECK(prefix != 0xFE);
|
||||
const uint8_t size_code = prefix & 0x03;
|
||||
const uint8_t data_size = size_code == 3 ? 4 : size_code;
|
||||
CHECK(offset + data_size <= switch2_hid_report_descriptor_length);
|
||||
uint32_t value = 0;
|
||||
for (uint8_t index = 0; index < data_size; ++index) {
|
||||
value |= static_cast<uint32_t>(switch2_hid_report_descriptor[offset + index]) << (8 * index);
|
||||
}
|
||||
offset += data_size;
|
||||
const uint8_t type = (prefix >> 2) & 0x03;
|
||||
const uint8_t tag = (prefix >> 4) & 0x0F;
|
||||
if (type == 1 && tag == 7) report_size = value;
|
||||
if (type == 1 && tag == 8) report_id = value;
|
||||
if (type == 1 && tag == 9) report_count = value;
|
||||
if (type == 0 && tag == 8 && report_id == 0x05) input_05_bits += report_size * report_count;
|
||||
if (type == 0 && tag == 8 && report_id == 0x09) input_09_bits += report_size * report_count;
|
||||
if (type == 0 && tag == 9 && report_id == 0x02) output_02_bits += report_size * report_count;
|
||||
}
|
||||
|
||||
// Then: each supported transfer is 63 payload bytes plus its report ID.
|
||||
CHECK(input_05_bits == 63 * 8);
|
||||
CHECK(input_09_bits == 63 * 8);
|
||||
CHECK(output_02_bits == 63 * 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool switch2_string_bytes_match_pinned_capture() {
|
||||
// Given/When: the known language and ASCII strings are inspected.
|
||||
static constexpr uint8_t language[] = {0x09, 0x04};
|
||||
static constexpr uint8_t manufacturer[] = "Nintendo";
|
||||
static constexpr uint8_t product[] = "Switch 2 Pro Controller";
|
||||
static constexpr uint8_t serial[] = "00";
|
||||
|
||||
// Then: exported lengths exclude the C terminator and bytes remain exact.
|
||||
CHECK(bytes_equal(switch2_string_language, switch2_string_language_length,
|
||||
language, sizeof(language)));
|
||||
CHECK(bytes_equal(switch2_string_manufacturer, switch2_string_manufacturer_length,
|
||||
manufacturer, sizeof(manufacturer) - 1));
|
||||
CHECK(bytes_equal(switch2_string_product, switch2_string_product_length,
|
||||
product, sizeof(product) - 1));
|
||||
CHECK(bytes_equal(switch2_string_serial, switch2_string_serial_length,
|
||||
serial, sizeof(serial) - 1));
|
||||
CHECK(switch2_string_manufacturer[switch2_string_manufacturer_length] == 0);
|
||||
CHECK(switch2_string_product[switch2_string_product_length] == 0);
|
||||
CHECK(switch2_string_serial[switch2_string_serial_length] == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void run_switch2_descriptor_tests(TestRunner& runner) {
|
||||
runner.run("Switch 2 descriptor exact bytes", switch2_descriptor_bytes_match_pinned_capture_subset);
|
||||
runner.run("Switch 2 configuration iteration", switch2_configuration_iterates_two_interfaces_and_four_endpoints);
|
||||
runner.run("Switch 2 HID report sizes", switch2_hid_reports_are_exactly_sixty_three_payload_bytes);
|
||||
runner.run("Switch 2 string bytes", switch2_string_bytes_match_pinned_capture);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue