Correct USB power reporting for translated native controllers
This commit is contained in:
parent
dd69f3d487
commit
a407e84a39
7 changed files with 62 additions and 7 deletions
22
README.md
22
README.md
|
|
@ -985,6 +985,28 @@ region matched the pre-update backup. Native R/L descriptors, EP0 identity,
|
|||
initialization and bulk-isolation checks passed while editor traffic was active.
|
||||
No physical Switch L+R button press was claimed by that transport check.
|
||||
|
||||
**USB power-status correction under qualification:** a subsequent Switch trial
|
||||
accepted both halves and delivered roughly 38,000 reports per side before
|
||||
input stopped around 313 seconds. Wii Bluetooth input remained active, and the
|
||||
host cleared endpoint halts. No explicit sleep command or five-minute firmware
|
||||
timer was observed; the cause of the timeout is not yet established.
|
||||
|
||||
That trace exposed a separate concrete metadata error: translated reports had
|
||||
the external-power flag clear. The published
|
||||
[native Power Info format](https://github.com/ndeadly/switch2_controller_research/blob/master/hid_reports.md#input-report-0x07)
|
||||
defines bit 0 as external power. Translated `GAMEPAD` and dedicated `WII` USB
|
||||
outputs now set it while preserving source battery telemetry and leaving the
|
||||
charging bit clear. An unknown battery does not become a fabricated full charge;
|
||||
the old standalone Wii nominal-charge fallback is removed.
|
||||
|
||||
Power-field regressions fail before the correction and pass afterward. Eight
|
||||
focused regressions and both firmware builds pass; a PC capture of real
|
||||
motion-bearing input verified Power Info `0x01` on both halves. The
|
||||
`0.72-native-usb-power` trial is installed with persistent storage verified
|
||||
unchanged. A sustained Switch/idle test is still required before attributing
|
||||
the timeout to this flag or claiming recovery. Additional trace-only logs
|
||||
include raw USB interrupts, frame count and device-watchdog state.
|
||||
|
||||
For sensorless hardware, the checker supports `--input-only`: press real buttons
|
||||
and keep changing controls on both halves during the run. Neutral fallback
|
||||
alone cannot qualify. The result explicitly records that IMU was not required;
|
||||
|
|
|
|||
|
|
@ -917,6 +917,32 @@ void native_hub_task(void) {
|
|||
" lock=%"PRIu32" blocked=%08"PRIx32"/%08"PRIx32" rootnak=%"PRIu32"\n",
|
||||
token_hits[0],token_hits[1],token_hits[2],missed_lock,
|
||||
blocked_buffers,blocked_sie,root_naks);
|
||||
#if SWITCH2_PROBE_TRACE_NATIVE_INPUT
|
||||
probe_router_stats observer;
|
||||
probe_router_snapshot(&observer);
|
||||
uint32_t interrupt_mask = save_and_disable_interrupts();
|
||||
restore_interrupts(interrupt_mask);
|
||||
probe_debug_printf("[HUB_HEALTH] router_ready=%"PRIu32" default=%u irq_mask=%"PRIu32
|
||||
" buf_status=%08"PRIx32" irq_status=%08"PRIx32
|
||||
" hub_ep=%08"PRIx32"/%08"PRIx32" busy=%u\n",
|
||||
observer.ready,default_device,interrupt_mask,
|
||||
usb_hw->buf_status,usb_hw->ints,
|
||||
usb_dpram->ep_ctrl[14].in,buffer_regs()[30],devices[0].ep[2].busy);
|
||||
probe_debug_printf("[HUB_RAW] intr=%08"PRIx32" inte=%08"PRIx32" sof=%"PRIu32
|
||||
" mtime=%08"PRIx32" watchdog=%08"PRIx32" nak_poll=%08"PRIx32"\n",
|
||||
usb_hw->intr,usb_hw->inte,usb_hw->sof_rd,sio_hw->mtime,
|
||||
usb_hw->dev_sm_watchdog,usb_hw->nak_poll);
|
||||
probe_debug_printf("[HUB_PORTS] status=%04x/%04x change=%04x/%04x\n",
|
||||
ports[0].status,ports[1].status,ports[0].change,ports[1].change);
|
||||
for (uint8_t slot = 0; slot < DEVICES; ++slot) {
|
||||
const device_t* d = &devices[slot];
|
||||
probe_debug_printf("[HUB_EP0] slot=%u stage=%u gen=%"PRIu32"/%"PRIu32
|
||||
" reset=%"PRIu32" request=%02x/%02x pos=%u/%u buffers=%08"PRIx32"/%08"PRIx32"\n",
|
||||
slot,(unsigned)d->control.stage,d->control.generation,d->generation,
|
||||
d->reset_generation,d->control.request.bmRequestType,d->control.request.bRequest,
|
||||
d->control.position,d->control.length,d->buffers[0],d->buffers[1]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
watchdog_update();
|
||||
// This qualification firmware must remain recoverable if the hub never
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ void mapped_halves_and_calibration() {
|
|||
source.controller.active = true;
|
||||
source.controller.connection_generation = 7;
|
||||
source.controller.identity = controller_identity_global();
|
||||
source.battery = 255;
|
||||
publish();
|
||||
// Neither an absent source nor an uncalibrated child masquerades as active.
|
||||
assert(!peek(0) && !controls[0].active);
|
||||
|
|
@ -131,6 +132,7 @@ void mapped_halves_and_calibration() {
|
|||
pair();
|
||||
assert(stick_x(0) == 2000 && stick_y(0) == 2100);
|
||||
assert(stick_x(1) == 1800 && stick_y(1) == 1900);
|
||||
assert(reports[0][1] == 0x25 && reports[1][1] == 0x25); // Known full level, USB powered, not charging.
|
||||
// Actual profile transforms can move controls across native children.
|
||||
profile.button_map[static_cast<unsigned>(ControllerProfileLogicalButton::kSouth)] =
|
||||
static_cast<uint8_t>(ControllerProfileLogicalButton::kDpadRight);
|
||||
|
|
@ -362,6 +364,7 @@ void nunchuk_buttons_map_to_native_left_shoulders() {
|
|||
++source.controller.connection_generation;
|
||||
source.controller.state = {};
|
||||
source.accel_valid = source.gyro_valid = false;
|
||||
source.battery = 0; // Unknown source battery must not invent a full charge.
|
||||
profile = controller_profile_default(controller_identity_global(), 0);
|
||||
// The real Wii parser maps Nunchuk C to west and Z to north. These are
|
||||
// ordinary profile inputs, not the unrelated Switch2 extra "C" control.
|
||||
|
|
@ -372,6 +375,7 @@ void nunchuk_buttons_map_to_native_left_shoulders() {
|
|||
source.controller.state.button_west = true; // C -> ZL.
|
||||
publish(false); pair();
|
||||
assert(reports[0][2] == 0 && reports[1][2] == 0x20);
|
||||
assert(reports[0][1] == 0x01 && reports[1][1] == 0x01); // USB power remains real even without battery telemetry.
|
||||
source.controller.state.button_west = false;
|
||||
source.controller.state.button_north = true; // Z -> L.
|
||||
publish(false); pair();
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ int main() {
|
|||
memcpy(source.gyro_q10, bias_q10, sizeof(bias_q10));
|
||||
assert(poll());
|
||||
assert(controls.active && packet[15] == 30); // Wii IMU starts before background bias learning.
|
||||
assert(packet[1] == 0x01); // USB power, no fabricated charge level or charging state.
|
||||
for (unsigned i = 1; i < 450; ++i) assert(poll());
|
||||
assert(controls.active && packet[15] == 30 && packet[19] == 0x0c);
|
||||
assert(signed32(packet+32) == (1 << 28));
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ int g_sensor_status = -1;
|
|||
uint16_t g_stick_center[2]{2048, 2048};
|
||||
uint16_t g_stick_positive[2]{2047, 2047};
|
||||
uint16_t g_stick_negative[2]{2048, 2048};
|
||||
uint8_t g_power_info = 0x24; // Nominal battery until source status; no USB-power flag.
|
||||
uint8_t g_power_info = 0x01; // USB powered; no invented source charge or charging state.
|
||||
uint8_t g_report_counter;
|
||||
uint32_t g_report_serial;
|
||||
uint32_t g_pending_serial;
|
||||
|
|
@ -254,8 +254,8 @@ void poll_wii_source(uint32_t now_ms) {
|
|||
#ifdef SWITCH2_PROBE_TRACE_NATIVE_INPUT
|
||||
g_last_ir_trace_us = now_us;
|
||||
#endif
|
||||
g_power_info = 0x24;
|
||||
probe_debug_printf("[PROBE] Wii source active in slot %u; keep still for native motion calibration\n",
|
||||
g_power_info = 0x01;
|
||||
probe_debug_printf("[PROBE] Wii source active in slot %u; native bias learns in background\n",
|
||||
g_wii.slot);
|
||||
}
|
||||
g_wii_active = true;
|
||||
|
|
@ -273,7 +273,7 @@ void poll_wii_source(uint32_t now_ms) {
|
|||
}
|
||||
if (g_wii.battery != 0) {
|
||||
const unsigned level = (static_cast<unsigned>(g_wii.battery) * 9u + 127u) / 255u;
|
||||
g_power_info = static_cast<uint8_t>(level << 2);
|
||||
g_power_info = static_cast<uint8_t>((level << 2) | 0x01u);
|
||||
}
|
||||
ProbeNativeMotionSample motion{};
|
||||
motion.accel_valid = g_wii.accel_valid;
|
||||
|
|
|
|||
|
|
@ -290,9 +290,11 @@ uint32_t probe_native_gamepad_input_peek_native_report(uint8_t instance, uint32_
|
|||
if (g_report_token == UINT32_MAX) return 0; // Boot-unique, including across children/resets.
|
||||
memset(child.pending_report, 0, sizeof(child.pending_report));
|
||||
child.pending_report[0] = child.counter;
|
||||
// Bluepad32 zero denotes unknown; do not manufacture a full battery.
|
||||
// Battery telemetry belongs to the source; external power belongs to
|
||||
// these USB-powered virtual controllers. Unknown is not a full charge,
|
||||
// and the Pico does not charge the wireless source.
|
||||
const unsigned battery_level = (static_cast<unsigned>(g_source.battery) * 9u + 127u) / 255u;
|
||||
child.pending_report[1] = static_cast<uint8_t>(battery_level << 2);
|
||||
child.pending_report[1] = static_cast<uint8_t>((battery_level << 2) | 0x01u);
|
||||
memcpy(child.pending_report + 2, child.input.buttons, sizeof(child.input.buttons));
|
||||
child.pending_report[4] = 7;
|
||||
memcpy(child.pending_report + 5, child.input.stick, sizeof(child.input.stick));
|
||||
|
|
|
|||
|
|
@ -369,7 +369,7 @@ function(switch2_usb_probe_configure target)
|
|||
endif()
|
||||
if(SWITCH2_PROBE_HUB AND SWITCH2_BRIDGE_FULL_INPUT)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.72-native-gamepad-trace")
|
||||
pico_set_program_version(${target} "0.72-native-usb-power")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.72-native-gamepad")
|
||||
endif()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue