feat: split up usb transfer logic in preparation for controller mode

This commit is contained in:
Naxdy 2024-10-12 13:46:03 +02:00
parent 4e1af50ebd
commit 753cb59fe6
Signed by: Naxdy
GPG key ID: CC15075846BCE91B
2 changed files with 256 additions and 174 deletions

View file

@ -16,7 +16,10 @@ use packed_struct::{
};
use crate::{
gcc_hid::{Buttons1, Buttons2, MUTEX_INPUT_CONSISTENCY_MODE, SIGNAL_CHANGE_RUMBLE_STRENGTH},
gcc_hid::{
GcButtons1, GcButtons2, MUTEX_CONTROLLER_MODE, MUTEX_INPUT_CONSISTENCY_MODE,
SIGNAL_CHANGE_RUMBLE_STRENGTH,
},
helpers::{PackedFloat, ToPackedFloatArray, ToRegularArray, XyValuePair},
input::{
read_ext_adc, Stick, StickAxis, FLOAT_ORIGIN, SPI_ACS_SHARED, SPI_CCS_SHARED, SPI_SHARED,
@ -500,7 +503,7 @@ enum NotchAdjustmentType {
/// This needs to be incremented for ANY change to ControllerConfig
/// else we risk loading uninitialized memory.
pub const CONTROLLER_CONFIG_REVISION: u8 = 1;
pub const CONTROLLER_CONFIG_REVISION: u8 = 2;
#[derive(Debug, Clone, Format, PackedStruct)]
#[packed_struct(endian = "msb")]
@ -563,6 +566,14 @@ pub enum InputConsistencyMode {
PC = 3,
}
#[derive(Debug, Clone, Copy, Format, PrimitiveEnum_u8, PartialEq, Eq)]
pub enum ControllerMode {
/// Advertise itself as a GCC adapter with 1 controller (itself) connected.
GcAdapter = 0,
/// Pretend to be a Nintendo Switch Pro Controller connected via USB.
Procon = 1,
}
#[derive(Debug, Clone, Format, PackedStruct)]
#[packed_struct(endian = "msb")]
pub struct ControllerConfig {
@ -580,6 +591,8 @@ pub struct ControllerConfig {
pub astick_config: StickConfig,
#[packed_field(size_bytes = "328")]
pub cstick_config: StickConfig,
#[packed_field(size_bits = "8", ty = "enum")]
pub controller_mode: ControllerMode,
}
impl Default for ControllerConfig {
@ -590,6 +603,7 @@ impl Default for ControllerConfig {
astick_config: StickConfig::default(),
rumble_strength: 9,
cstick_config: StickConfig::default(),
controller_mode: ControllerMode::GcAdapter,
}
}
}
@ -1197,12 +1211,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1225,12 +1239,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1254,12 +1268,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1313,12 +1327,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1399,12 +1413,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1485,12 +1499,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1560,12 +1574,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1616,12 +1630,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1664,13 +1678,13 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
button_z: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1700,12 +1714,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1734,12 +1748,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1760,12 +1774,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1786,12 +1800,12 @@ async fn configuration_main_loop<
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_r: true,
button_l: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,
@ -1840,6 +1854,11 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
*m_input_consistency = Some(current_config.input_consistency_mode);
}
{
let mut m_is_procon = MUTEX_CONTROLLER_MODE.lock().await;
*m_is_procon = Some(current_config.controller_mode);
}
SIGNAL_CHANGE_RUMBLE_STRENGTH.signal(current_config.rumble_strength);
SIGNAL_CONFIG_CHANGE.signal(current_config.clone());
@ -1857,12 +1876,12 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
report: GcReport {
trigger_r: 255,
trigger_l: 255,
buttons_2: Buttons2 {
buttons_2: GcButtons2 {
button_l: true,
button_r: true,
..Default::default()
},
buttons_1: Buttons1 {
buttons_1: GcButtons1 {
button_x: true,
button_y: true,
button_a: true,

View file

@ -2,28 +2,32 @@
* Communication with the console / PC over USB HID.
* Includes the HID report descriptor, and the GcReport struct.
*/
use core::default::Default;
use core::{default::Default, future::Future};
use defmt::{debug, info, trace, warn, Format};
use embassy_futures::join::join;
use embassy_rp::{
peripherals::{PIN_25, PIN_29, PWM_CH4, PWM_CH6, USB},
pwm::Pwm,
usb::Driver,
usb::Driver as EmbassyDriver,
};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
use embassy_time::{Duration, Instant, Timer};
use embassy_usb::{
class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
class::hid::{HidReader, HidReaderWriter, HidWriter, ReportId, RequestHandler, State},
control::OutResponse,
driver::Driver,
msos::{self, windows_version},
Builder, Handler,
Builder, Handler, UsbDevice,
};
use libm::powf;
use packed_struct::{derive::PackedStruct, PackedStruct};
use crate::{config::InputConsistencyMode, input::CHANNEL_GCC_STATE};
use crate::{
config::{ControllerMode, InputConsistencyMode},
input::CHANNEL_GCC_STATE,
};
static SIGNAL_RUMBLE: Signal<CriticalSectionRawMutex, bool> = Signal::new();
@ -37,6 +41,10 @@ pub static MUTEX_INPUT_CONSISTENCY_MODE: Mutex<
Option<InputConsistencyMode>,
> = Mutex::new(None);
/// Only dispatched ONCE after powerup, to determine how to advertise itself via USB.
pub static MUTEX_CONTROLLER_MODE: Mutex<CriticalSectionRawMutex, Option<ControllerMode>> =
Mutex::new(None);
/// Vendor-defined property data
const DEVICE_INTERFACE_GUID: &str = "{ecceff35-146c-4ff3-acd9-8f992d09acdd}";
@ -97,9 +105,19 @@ pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
0xC0, // End Collection
];
trait HidReportable<const LEN: usize> {
fn get_hid_report(&self) -> [u8; LEN];
}
struct UsbConfig {
vid: u16,
pid: u16,
report_descriptor: &'static [u8],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
pub struct Buttons1 {
pub struct GcButtons1 {
#[packed_field(bits = "0")]
pub button_a: bool,
#[packed_field(bits = "1")]
@ -120,7 +138,7 @@ pub struct Buttons1 {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
pub struct Buttons2 {
pub struct GcButtons2 {
#[packed_field(bits = "0")]
pub button_start: bool,
#[packed_field(bits = "1")]
@ -137,9 +155,9 @@ pub struct Buttons2 {
#[packed_struct(bit_numbering = "msb0", size_bytes = "8")]
pub struct GcReport {
#[packed_field(bits = "0..=7")]
pub buttons_1: Buttons1,
pub buttons_1: GcButtons1,
#[packed_field(bits = "8..=15")]
pub buttons_2: Buttons2,
pub buttons_2: GcButtons2,
#[packed_field(bits = "16..=23")]
pub stick_x: u8,
#[packed_field(bits = "24..=31")]
@ -157,8 +175,8 @@ pub struct GcReport {
impl Default for GcReport {
fn default() -> Self {
Self {
buttons_1: Buttons1::default(),
buttons_2: Buttons2::default(),
buttons_1: GcButtons1::default(),
buttons_2: GcButtons2::default(),
stick_x: 127,
stick_y: 127,
cstick_x: 127,
@ -169,15 +187,29 @@ impl Default for GcReport {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C, align(8))]
pub struct RawConsoleReport {
pub packet: [u8; 64],
}
impl HidReportable<37> for GcReport {
fn get_hid_report(&self) -> [u8; 37] {
static mut GC_FIRST: bool = false;
impl Default for RawConsoleReport {
fn default() -> Self {
Self { packet: [0u8; 64] }
let mut buffer = [0u8; 37];
buffer[0] = 0x21;
buffer[1] |= 0x14;
let data = self.pack().expect("Failed to pack GC input data");
if unsafe { !GC_FIRST } {
buffer[1] |= 0x04;
buffer[10] |= 0x04;
buffer[19] |= 0x04;
buffer[28] |= 0x04;
unsafe { GC_FIRST = true };
} else {
// controller in "port 1"
buffer[2..=9].copy_from_slice(&data[0..=7]);
}
buffer
}
}
@ -204,30 +236,6 @@ impl RequestHandler for GccRequestHandler {
}
}
fn get_gcinput_hid_report(input_state: &GcReport) -> [u8; 37] {
static mut GC_FIRST: bool = false;
let mut buffer = [0u8; 37];
buffer[0] = 0x21;
buffer[1] |= 0x14;
let data = input_state.pack().expect("Failed to pack GC input data");
if unsafe { !GC_FIRST } {
buffer[1] |= 0x04;
buffer[10] |= 0x04;
buffer[19] |= 0x04;
buffer[28] |= 0x04;
unsafe { GC_FIRST = true };
} else {
// controller in "port 1"
buffer[2..=9].copy_from_slice(&data[0..=7]);
}
buffer
}
struct MyDeviceHandler {
configured: bool,
}
@ -270,8 +278,154 @@ impl Handler for MyDeviceHandler {
}
}
fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>(
input_consistency_mode: InputConsistencyMode,
report_descriptor: &'d [u8],
request_handler: &'d GccRequestHandler,
mut builder: Builder<'d, D>,
state: &'d mut State<'d>,
) -> (UsbDevice<'d, D>, HidReader<'d, D, R>, HidWriter<'d, D, W>) {
let hid_config = embassy_usb::class::hid::Config {
report_descriptor,
request_handler: Some(request_handler),
poll_ms: match input_consistency_mode {
InputConsistencyMode::Original => 8,
InputConsistencyMode::ConsistencyHack
| InputConsistencyMode::SuperHack
| InputConsistencyMode::PC => 1,
},
max_packet_size_in: W as u16,
max_packet_size_out: R as u16,
};
let hid: HidReaderWriter<'d, D, R, W> =
HidReaderWriter::<'_, D, R, W>::new(&mut builder, state, hid_config);
let usb = builder.build();
let (reader, writer) = hid.split();
(usb, reader, writer)
}
fn mk_usb_transfer_futures<'d, D, F, H, const R: usize, const W: usize>(
input_consistency_mode: InputConsistencyMode,
usb_config: UsbConfig,
request_handler: &'d GccRequestHandler,
builder: Builder<'d, D>,
state: &'d mut State<'d>,
preprocess_report: F,
) -> (
impl Future<Output = ()> + 'd,
impl Future<Output = ()> + 'd,
impl Future<Output = ()> + 'd,
)
where
D: Driver<'d> + 'd,
F: Fn(GcReport) -> H + 'd,
H: HidReportable<W>,
{
let (mut usb, mut reader, mut writer) = mk_hid_reader_writer::<_, R, W>(
input_consistency_mode,
usb_config.report_descriptor,
request_handler,
builder,
state,
);
let usb_fut = async move {
loop {
usb.run_until_suspend().await;
debug!("Suspended");
usb.wait_resume().await;
debug!("RESUMED!");
}
};
let in_fut = async move {
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
let mut last_report_time = Instant::now();
let mut rate_limit_end_time = Instant::now();
loop {
// This is what we like to call a "hack".
// It forces reports to be sent at least every 8.33ms instead of every 8ms.
// 8.33ms is a multiple of the game's frame interval (16.66ms), so if we
// send a report every 8.33ms, it should (in theory) ensure (close to)
// 100% input accuracy.
//
// From the console's perspective, we are basically a laggy adapter, taking
// a minimum of 333 extra us to send a report every time it's polled, but it
// works to our advantage.
match input_consistency_mode {
InputConsistencyMode::SuperHack | InputConsistencyMode::ConsistencyHack => {
// "Ticker at home", so we can use this for both consistency and SuperHack mode
Timer::at(rate_limit_end_time).await;
}
InputConsistencyMode::Original | InputConsistencyMode::PC => {}
}
writer.ready().await;
let state = preprocess_report(gcc_subscriber.next_message_pure().await);
let report = state.get_hid_report();
trace!("Writing report: {:08b}", report);
match writer.write(&report).await {
Ok(()) => {
let currtime = Instant::now();
let polltime = currtime.duration_since(last_report_time);
let micros = polltime.as_micros();
debug!("Report written in {}us", micros);
if input_consistency_mode != InputConsistencyMode::Original
&& input_consistency_mode != InputConsistencyMode::PC
{
while rate_limit_end_time < currtime {
rate_limit_end_time += Duration::from_micros(8333);
}
}
last_report_time = currtime;
}
Err(e) => warn!("Failed to send report: {:?}", e),
}
}
};
let out_fut = async move {
loop {
trace!("Readery loop");
let mut buf = [0u8; R];
match reader.read(&mut buf).await {
Ok(_e) => {
debug!("READ SOMETHIN: {:08b}", buf);
SIGNAL_RUMBLE.signal((buf[1] & 0x01) != 0);
}
Err(e) => {
warn!("Failed to read: {:?}", e);
}
}
}
};
let usb_fut_wrapped = async {
usb_fut.await;
debug!("USB FUT DED");
};
(usb_fut_wrapped, in_fut, out_fut)
}
#[embassy_executor::task]
pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>) {
pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'static, USB>) {
let config = UsbConfig {
vid: 0x057e,
pid: 0x0337,
report_descriptor: GCC_REPORT_DESCRIPTOR,
};
let input_consistency_mode = {
while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
Timer::after(Duration::from_millis(100)).await;
@ -300,7 +454,7 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
info!("Detected flash with unique serial number {}", serial);
trace!("Start of config");
let mut usb_config = embassy_usb::Config::new(0x057e, 0x0337);
let mut usb_config = embassy_usb::Config::new(config.vid, config.pid);
usb_config.manufacturer = Some("Naxdy");
usb_config.product = Some(match input_consistency_mode {
InputConsistencyMode::Original => "NaxGCC (OG Mode)",
@ -349,105 +503,14 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
builder.handler(&mut device_handler);
let hid_config = embassy_usb::class::hid::Config {
report_descriptor: GCC_REPORT_DESCRIPTOR,
request_handler: Some(&request_handler),
poll_ms: match input_consistency_mode {
InputConsistencyMode::Original => 8,
InputConsistencyMode::ConsistencyHack
| InputConsistencyMode::SuperHack
| InputConsistencyMode::PC => 1,
},
max_packet_size_in: 37,
max_packet_size_out: 5,
};
let hid = HidReaderWriter::<_, 5, 37>::new(&mut builder, &mut state, hid_config);
let mut usb = builder.build();
let usb_fut = async {
loop {
usb.run_until_suspend().await;
debug!("Suspended");
usb.wait_resume().await;
debug!("RESUMED!");
}
};
let (mut reader, mut writer) = hid.split();
let in_fut = async {
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
let mut last_report_time = Instant::now();
let mut rate_limit_end_time = Instant::now();
loop {
// This is what we like to call a "hack".
// It forces reports to be sent at least every 8.33ms instead of every 8ms.
// 8.33ms is a multiple of the game's frame interval (16.66ms), so if we
// send a report every 8.33ms, it should (in theory) ensure (close to)
// 100% input accuracy.
//
// From the console's perspective, we are basically a laggy adapter, taking
// a minimum of 333 extra us to send a report every time it's polled, but it
// works to our advantage.
match input_consistency_mode {
InputConsistencyMode::SuperHack | InputConsistencyMode::ConsistencyHack => {
// "Ticker at home", so we can use this for both consistency and SuperHack mode
Timer::at(rate_limit_end_time).await;
}
InputConsistencyMode::Original | InputConsistencyMode::PC => {}
}
match writer
.write(&{
let state = gcc_subscriber.next_message_pure().await;
let report = get_gcinput_hid_report(&state);
trace!("Report Written: {:08b}", report);
report
})
.await
{
Ok(()) => {
let currtime = Instant::now();
let polltime = currtime.duration_since(last_report_time);
let micros = polltime.as_micros();
debug!("Report written in {}us", micros);
if input_consistency_mode != InputConsistencyMode::Original
&& input_consistency_mode != InputConsistencyMode::PC
{
while rate_limit_end_time < currtime {
rate_limit_end_time += Duration::from_micros(8333);
}
}
last_report_time = currtime;
}
Err(e) => warn!("Failed to send report: {:?}", e),
}
}
};
let out_fut = async {
loop {
trace!("Readery loop");
let mut buf = [0u8; 5];
match reader.read(&mut buf).await {
Ok(_e) => {
debug!("READ SOMETHIN: {:08b}", buf);
SIGNAL_RUMBLE.signal((buf[1] & 0x01) != 0);
}
Err(e) => {
warn!("Failed to read: {:?}", e);
}
}
}
};
let usb_fut_wrapped = async {
usb_fut.await;
debug!("USB FUT DED");
};
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 5, 37>(
input_consistency_mode,
config,
&request_handler,
builder,
&mut state,
|state| state,
);
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
}