Skip to content

Commit 6d25fb8

Browse files
committed
fix(firmware): match PS3 HID control reports (2026.6.16.0-C8ED)
1 parent 88ca895 commit 6d25fb8

5 files changed

Lines changed: 176 additions & 5 deletions

File tree

pico-bridge/src/dinput.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,11 @@ void dinput_task(void) {
146146
}
147147

148148
static uint16_t copy_report(uint8_t *buffer, uint16_t reqlen, const uint8_t *src, uint16_t len) {
149-
if (reqlen < len)
149+
if (reqlen == 0)
150150
return 0;
151-
memcpy(buffer, src, len);
152-
return len;
151+
uint16_t copy = reqlen < len ? reqlen : len;
152+
memcpy(buffer, src, copy);
153+
return copy;
153154
}
154155

155156
uint16_t dinput_get_report_payload(uint8_t report_id, uint8_t report_type, uint8_t *buffer,

pico-bridge/src/usb_descriptors.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ static const tusb_desc_device_t desc_device_ps3 = {
131131

132132
.iManufacturer = 0x01,
133133
.iProduct = 0x02,
134-
.iSerialNumber = 0x03,
134+
.iSerialNumber = 0x00,
135135
.bNumConfigurations = 0x01,
136136
};
137137

@@ -356,7 +356,7 @@ static const uint8_t desc_hid_report_ps4[] = {
356356
#define HID_GAMEPAD_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + 7)
357357

358358
static const uint8_t desc_configuration_ps3[] = {
359-
TUD_CONFIG_DESCRIPTOR(1, 1, 0, HID_GAMEPAD_CONFIG_TOTAL_LEN, 0x80, 250),
359+
TUD_CONFIG_DESCRIPTOR(1, 1, 0, HID_GAMEPAD_CONFIG_TOTAL_LEN, 0x80, 500),
360360
9,
361361
TUSB_DESC_INTERFACE,
362362
GAMEPAD_HID_ITF_NUM,

pico-bridge/tests/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ target_include_directories(test_dinput_report PRIVATE ${CMAKE_CURRENT_SOURCE_DIR
3535

3636
add_test(NAME dinput_report COMMAND test_dinput_report)
3737

38+
add_executable(test_dinput_features
39+
test_dinput_features.c
40+
${CMAKE_CURRENT_SOURCE_DIR}/../src/dinput.c
41+
${CMAKE_CURRENT_SOURCE_DIR}/../src/dinput_report.c
42+
)
43+
target_include_directories(test_dinput_features PRIVATE
44+
${CMAKE_CURRENT_SOURCE_DIR}
45+
${CMAKE_CURRENT_SOURCE_DIR}/../src
46+
)
47+
48+
add_test(NAME dinput_features COMMAND test_dinput_features)
49+
3850
add_executable(test_maple_proto
3951
test_maple_proto.c
4052
${CMAKE_CURRENT_SOURCE_DIR}/../src/maple_proto.c
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#include <stdbool.h>
2+
#include <stdio.h>
3+
#include <string.h>
4+
5+
#include "pico/stdlib.h"
6+
7+
#include "boot_mode.h"
8+
#include "dinput.h"
9+
#include "gamepad_state.h"
10+
#include "tusb.h"
11+
#include "usb_diag.h"
12+
13+
static int failures = 0;
14+
static run_persona_t current_persona = RUN_PERSONA_PS3;
15+
16+
#define CHECK(cond) \
17+
do { \
18+
if (!(cond)) { \
19+
printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \
20+
failures++; \
21+
} \
22+
} while (0)
23+
24+
volatile gamepad_state_t g_gamepad_state;
25+
volatile uint32_t g_last_packet_ms;
26+
volatile uint8_t g_parsec_connected;
27+
28+
absolute_time_t get_absolute_time(void) {
29+
return 0;
30+
}
31+
32+
uint32_t to_ms_since_boot(absolute_time_t t) {
33+
return t;
34+
}
35+
36+
run_persona_t boot_mode_run_persona(void) {
37+
return current_persona;
38+
}
39+
40+
bool tud_mounted(void) {
41+
return false;
42+
}
43+
44+
bool tud_suspended(void) {
45+
return false;
46+
}
47+
48+
bool tud_hid_ready(void) {
49+
return false;
50+
}
51+
52+
bool tud_hid_report(uint8_t report_id, void const *report, uint8_t len) {
53+
(void)report_id;
54+
(void)report;
55+
(void)len;
56+
return false;
57+
}
58+
59+
void usb_diag_note_xinput_in_queued(uint32_t bytes) {
60+
(void)bytes;
61+
}
62+
63+
void usb_diag_note_xinput_in_sent(uint32_t bytes) {
64+
(void)bytes;
65+
}
66+
67+
void usb_diag_note_xinput_in_blocked(uint8_t reason, uint16_t want, uint16_t got) {
68+
(void)reason;
69+
(void)want;
70+
(void)got;
71+
}
72+
73+
void usb_diag_note_xinput_in_idle_suppressed(void) {}
74+
75+
static uint16_t get_feature_report_wire(uint8_t report_id, uint16_t host_len, uint8_t *wire,
76+
uint16_t wire_len) {
77+
memset(wire, 0xA5, wire_len);
78+
if (host_len == 0 || wire_len < host_len)
79+
return 0;
80+
81+
uint8_t *payload = wire;
82+
uint16_t payload_len = host_len;
83+
uint16_t xfer_len = 0;
84+
85+
if (report_id != HID_REPORT_TYPE_INVALID && payload_len > 1) {
86+
*payload++ = report_id;
87+
payload_len--;
88+
xfer_len++;
89+
}
90+
91+
xfer_len += dinput_get_report_payload(report_id, HID_REPORT_TYPE_FEATURE, payload, payload_len);
92+
return xfer_len;
93+
}
94+
95+
static void test_ps3_operational_report_f2_matches_linux_request_size(void) {
96+
uint8_t wire[32];
97+
current_persona = RUN_PERSONA_PS3;
98+
dinput_init();
99+
100+
uint16_t len = get_feature_report_wire(0xF2, 17, wire, sizeof(wire));
101+
102+
CHECK(len == 17);
103+
CHECK(wire[0] == 0xF2);
104+
CHECK(wire[1] == 0xFF);
105+
CHECK(wire[2] == 0xFF);
106+
CHECK(wire[3] == 0x00);
107+
CHECK(wire[4] == 0x20);
108+
CHECK(wire[5] == 0x40);
109+
CHECK(wire[6] == 0xCE);
110+
CHECK(wire[7] == 0x21);
111+
CHECK(wire[8] == 0x43);
112+
CHECK(wire[9] == 0x65);
113+
}
114+
115+
static void test_ps3_operational_report_f2_accepts_longer_request(void) {
116+
uint8_t wire[32];
117+
current_persona = RUN_PERSONA_PS3;
118+
dinput_init();
119+
120+
uint16_t len = get_feature_report_wire(0xF2, 18, wire, sizeof(wire));
121+
122+
CHECK(len == 18);
123+
CHECK(wire[0] == 0xF2);
124+
CHECK(wire[17] == 0x00);
125+
}
126+
127+
static void test_ps4_feature_reports_still_copy_at_full_size(void) {
128+
uint8_t payload[64];
129+
current_persona = RUN_PERSONA_PS4;
130+
dinput_init();
131+
132+
uint16_t len = dinput_get_report_payload(0xF2, HID_REPORT_TYPE_FEATURE, payload, 15);
133+
134+
CHECK(len == 15);
135+
CHECK(payload[0] == 0x01);
136+
CHECK(payload[1] == 0x10);
137+
CHECK(payload[14] == 0x2A);
138+
}
139+
140+
int main(void) {
141+
test_ps3_operational_report_f2_matches_linux_request_size();
142+
test_ps3_operational_report_f2_accepts_longer_request();
143+
test_ps4_feature_reports_still_copy_at_full_size();
144+
if (failures != 0)
145+
return 1;
146+
puts("dinput_feature tests passed");
147+
return 0;
148+
}

pico-bridge/tests/tusb.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
#pragma once
22

33
#include <stdbool.h>
4+
#include <stdint.h>
5+
6+
typedef enum {
7+
HID_REPORT_TYPE_INVALID = 0,
8+
HID_REPORT_TYPE_INPUT = 1,
9+
HID_REPORT_TYPE_OUTPUT = 2,
10+
HID_REPORT_TYPE_FEATURE = 3,
11+
} hid_report_type_t;
412

513
bool tud_mounted(void);
614
bool tud_suspended(void);
15+
bool tud_hid_ready(void);
16+
bool tud_hid_report(uint8_t report_id, void const *report, uint8_t len);

0 commit comments

Comments
 (0)