ddgo-pnp-controller/src/controller/emulated.rs
2025-05-11 18:00:54 +02:00

357 lines
11 KiB
Rust

use std::fs;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use std::process::Command;
use std::thread;
use std::thread::sleep;
use std::time::Duration;
use crate::controller::physical::ControllerState;
mod dgoc44u;
mod slph00051;
mod sotp031201_p4b2b7;
mod sotp031201_p4b7;
mod sotp031201_p5b5;
mod sotp031201_p5b7;
mod tc5175290;
mod tcpp20003;
mod tcpp20009;
mod tcpp20011;
mod zkns001;
mod generic;
const FFS_MOUNT: &str = "/tmp/ffs";
const ENDPOINT0: &str = "/tmp/ffs/ep0";
const ENDPOINT1: &str = "/tmp/ffs/ep1";
const ANDROID_GADGET: &str = "/sys/class/android_usb/android0";
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum ControllerModel {
DGOC44U,
SLPH00051,
SOTP031201P4B7,
SOTP031201P4B2B7,
SOTP031201P5B5,
SOTP031201P5B7,
TC5175290,
TCPP20003,
TCPP20009,
TCPP20011,
ZKNS001,
GENERIC,
}
pub struct DeviceDescriptor {
b_device_class: u8,
b_device_sub_class: u8,
id_vendor: u16,
id_product: u16,
bcd_device: u16,
i_manufacturer: &'static str,
i_product: &'static str,
i_serial_number: &'static str,
}
pub fn set_model(state: &ControllerState) -> Option<ControllerModel> {
let model;
let model_name;
let descriptors: (&DeviceDescriptor, &[u8], &[u8]);
if state.button_right {
model_name = "DGOC44-U";
model = ControllerModel::DGOC44U;
descriptors = (
&dgoc44u::DEVICE_DESCRIPTOR,
&dgoc44u::DESCRIPTORS,
&dgoc44u::STRINGS,
);
} else if state.button_up {
model_name = "ZKNS-001";
model = ControllerModel::ZKNS001;
descriptors = (
&zkns001::DEVICE_DESCRIPTOR,
&zkns001::DESCRIPTORS,
&zkns001::STRINGS,
);
} else if state.button_down && state.power == 0 {
model_name = "SLPH-00051";
model = ControllerModel::SLPH00051;
descriptors = (
&slph00051::DEVICE_DESCRIPTOR,
&slph00051::DESCRIPTORS,
&slph00051::STRINGS,
);
} else if state.button_down && state.power == 1 {
model_name = "TCPP-20004";
model = ControllerModel::TCPP20003;
descriptors = (
&tcpp20003::DEVICE_DESCRIPTOR,
&tcpp20003::DESCRIPTORS,
&tcpp20003::STRINGS,
);
} else if state.button_down && state.power == 2 {
model_name = "TC-5175290";
model = ControllerModel::TC5175290;
descriptors = (
&tc5175290::DEVICE_DESCRIPTOR,
&tc5175290::DESCRIPTORS,
&tc5175290::STRINGS,
);
} else if state.button_d {
model_name = "TCPP-20009";
model = ControllerModel::TCPP20009;
descriptors = (
&tcpp20009::DEVICE_DESCRIPTOR,
&tcpp20009::DESCRIPTORS,
&tcpp20009::STRINGS,
);
} else if state.button_a {
model_name = "Generic Train Controller";
model = ControllerModel::GENERIC;
descriptors = (
&generic::DEVICE_DESCRIPTOR,
&generic::DESCRIPTORS,
&generic::STRINGS,
);
} else if state.button_b {
model_name = "TCPP-20011";
model = ControllerModel::TCPP20011;
descriptors = (
&tcpp20011::DEVICE_DESCRIPTOR,
&tcpp20011::DESCRIPTORS,
&tcpp20011::STRINGS,
);
} else if state.button_c && state.power == 0 {
model_name = "SOTP-031201 (P4/B7 mode)";
model = ControllerModel::SOTP031201P4B7;
descriptors = (
&sotp031201_p4b7::DEVICE_DESCRIPTOR,
&sotp031201_p4b7::DESCRIPTORS,
&sotp031201_p4b7::STRINGS,
);
} else if state.button_c && state.power == 1 {
model_name = "SOTP-031201 (P4/B2-B7 mode)";
model = ControllerModel::SOTP031201P4B2B7;
descriptors = (
&sotp031201_p4b2b7::DEVICE_DESCRIPTOR,
&sotp031201_p4b2b7::DESCRIPTORS,
&sotp031201_p4b2b7::STRINGS,
);
} else if state.button_c && state.power == 2 {
model_name = "SOTP-031201 (P5/B5 mode)";
model = ControllerModel::SOTP031201P5B5;
descriptors = (
&sotp031201_p5b5::DEVICE_DESCRIPTOR,
&sotp031201_p5b5::DESCRIPTORS,
&sotp031201_p5b5::STRINGS,
);
} else if state.button_c && state.power == 3 {
model_name = "SOTP-031201 (P5/B7 mode)";
model = ControllerModel::SOTP031201P5B7;
descriptors = (
&sotp031201_p5b7::DEVICE_DESCRIPTOR,
&sotp031201_p5b7::DESCRIPTORS,
&sotp031201_p5b7::STRINGS,
);
} else {
println!("ddgo-pnp-controller: No controller selected, starting RNDIS gadget.");
Command::new("rndis-gadget.sh").output().ok();
return None;
}
println!("ddgo-pnp-controller: Selected controller {}.", model_name);
init_gadget(&model, descriptors);
return Some(model);
}
pub fn set_state(state: &mut ControllerState, model: &ControllerModel) {
match model {
ControllerModel::DGOC44U => {
dgoc44u::update_gadget(state);
}
ControllerModel::TC5175290 => {
tc5175290::update_gadget(state);
}
ControllerModel::TCPP20003 => {
tcpp20003::update_gadget(state);
}
ControllerModel::TCPP20009 => {
tcpp20009::update_gadget(state);
}
ControllerModel::TCPP20011 => {
tcpp20011::update_gadget(state);
}
ControllerModel::SOTP031201P4B7 => {
sotp031201_p4b7::update_gadget(state);
}
ControllerModel::SOTP031201P4B2B7 => {
sotp031201_p4b2b7::update_gadget(state);
}
ControllerModel::SOTP031201P5B5 => {
sotp031201_p5b5::update_gadget(state);
}
ControllerModel::SOTP031201P5B7 => {
sotp031201_p5b7::update_gadget(state);
}
ControllerModel::ZKNS001 => {
zkns001::update_gadget(state);
}
ControllerModel::SLPH00051 => {
slph00051::update_gadget(state);
}
ControllerModel::GENERIC => {
generic::update_gadget(state);
}
}
}
pub fn handle_ctrl_transfer(model: ControllerModel, data: &[u8]) {
println!("ddgo-pnp-controller: CTRL REQ: {:?}", data);
if data[1] == 6 && data[3] == 34 {
// Get HID report descriptor
let report: Option<&[u8]>;
match model {
ControllerModel::DGOC44U => {
report = Some(&dgoc44u::HID_REPORT_DESCRIPTOR);
}
ControllerModel::ZKNS001 => {
report = Some(&zkns001::HID_REPORT_DESCRIPTOR);
}
ControllerModel::SLPH00051 => {
report = Some(&slph00051::HID_REPORT_DESCRIPTOR);
}
ControllerModel::TC5175290 => {
report = Some(&tc5175290::HID_REPORT_DESCRIPTOR);
}
ControllerModel::TCPP20003 => {
report = Some(&tcpp20003::HID_REPORT_DESCRIPTOR);
}
ControllerModel::GENERIC => {
report = Some(&generic::HID_REPORT_DESCRIPTOR);
}
_ => {
report = None;
}
}
match report {
Some(rep) => {
if let Ok(mut file) = File::create(ENDPOINT0) {
file.write(rep).ok();
}
}
None => (),
}
} else {
match model {
ControllerModel::SLPH00051 => {
slph00051::handle_ctrl_transfer(data);
}
ControllerModel::TC5175290 => {
tc5175290::handle_ctrl_transfer(data);
}
ControllerModel::TCPP20003 => {
tcpp20003::handle_ctrl_transfer(data);
}
ControllerModel::GENERIC => {
generic::handle_ctrl_transfer(data);
}
_ => (),
}
}
}
fn init_gadget(
model: &ControllerModel,
(device, descriptors, strings): (&DeviceDescriptor, &[u8], &[u8]),
) {
// Init g_ffs kernel module
Command::new("modprobe")
.arg("g_ffs")
.arg(String::from("bDeviceClass=") + &device.b_device_class.to_string())
.arg(String::from("bDeviceSubClass=") + &device.b_device_sub_class.to_string())
.arg(String::from("idVendor=") + &device.id_vendor.to_string())
.arg(String::from("idProduct=") + &device.id_product.to_string())
.arg(String::from("bcdDevice=") + &device.bcd_device.to_string())
.arg(String::from("iManufacturer=") + device.i_manufacturer)
.arg(String::from("iProduct=") + device.i_product)
.arg(String::from("iSerialNumber=") + device.i_serial_number)
.output()
.ok();
Command::new("mkdir").args(["-p", &FFS_MOUNT]).output().ok();
Command::new("mount")
.args(["-t", "functionfs", "ffs", &FFS_MOUNT])
.output()
.ok();
let controller_model = model.clone();
thread::spawn(move || {
if let Ok(mut ep0) = File::open(&ENDPOINT0) {
let mut buffer = [0; 12];
loop {
if let Ok(_result) = ep0.read(&mut buffer) {
if buffer[8] == 0x4 {
// Control transfer received
handle_ctrl_transfer(controller_model, &buffer[0..8]);
}
}
// Wait between cycles
sleep(Duration::from_millis(10));
}
}
});
if let Ok(mut ep0) = File::create(&ENDPOINT0) {
ep0.write_all(descriptors).ok();
println!("ddgo-pnp-controller: Descriptors written to EP0");
ep0.write_all(strings).ok();
println!("ddgo-pnp-controller: Strings written to EP0");
}
// Init Android Gadget for old 3.4 kernel
let gadget = Path::new(&ANDROID_GADGET);
if gadget.is_dir() {
fs::write(
gadget.join(Path::new("bDeviceClass")),
&device.b_device_class.to_string(),
)
.ok();
fs::write(
gadget.join(Path::new("bDeviceSubClass")),
&device.b_device_sub_class.to_string(),
)
.ok();
fs::write(
gadget.join(Path::new("idVendor")),
format!("{:x}", &device.id_vendor),
)
.ok();
fs::write(
gadget.join(Path::new("idProduct")),
format!("{:x}", &device.id_product),
)
.ok();
fs::write(
gadget.join(Path::new("bcdDevice")),
format!("{:x}", &device.bcd_device),
)
.ok();
fs::write(
gadget.join(Path::new("iManufacturer")),
&device.i_manufacturer.to_string(),
)
.ok();
fs::write(
gadget.join(Path::new("iProduct")),
&device.i_product.to_string(),
)
.ok();
fs::write(
gadget.join(Path::new("iSerial")),
&device.i_serial_number.to_string(),
)
.ok();
fs::write(gadget.join(Path::new("functions")), "ffs").ok();
fs::write(gadget.join(Path::new("f_ffs/aliases")), "ffs").ok();
fs::write(gadget.join(Path::new("enable")), "1").ok();
}
}