use embassy_usb_driver::host::{PipeError, UsbHostAllocator, UsbPipe, pipe};
use embassy_usb_driver::{Direction as UsbDirection, EndpointAddress, EndpointInfo, EndpointType};
use crate::descriptor::ConfigurationDescriptor;
use crate::handler::EnumerationInfo;
const GIP_IFACE_CLASS: u8 = 0xFF; const GIP_IFACE_SUBCLASS: u8 = 0x47; const GIP_IFACE_PROTOCOL: u8 = 0xD0; const GIP_DATA_INTERFACE: u8 = 0;
const TRANSFER_TYPE_INTERRUPT: u8 = 0x03;
const GIP_CMD_ACK: u8 = 0x01;
const GIP_CMD_HELLO: u8 = 0x02;
const GIP_CMD_VIRTUAL_KEY: u8 = 0x07;
const GIP_CMD_RUMBLE: u8 = 0x09;
const GIP_CMD_INPUT: u8 = 0x20;
const GIP_FLAG_ACK: u8 = 0x10;
const GIP_FLAG_INTERNAL: u8 = 0x20;
pub const GIP_MAX_PACKET: usize = 64;
const GIP_HEADER_LEN: usize = 4;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct GamepadReport {
pub dpad_up: bool,
pub dpad_down: bool,
pub dpad_left: bool,
pub dpad_right: bool,
pub a: bool,
pub b: bool,
pub x: bool,
pub y: bool,
pub left_bumper: bool,
pub right_bumper: bool,
pub left_stick_press: bool,
pub right_stick_press: bool,
pub menu: bool,
pub view: bool,
pub left_trigger: u16,
pub right_trigger: u16,
pub left_stick_x: i16,
pub left_stick_y: i16,
pub right_stick_x: i16,
pub right_stick_y: i16,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RumbleCommand {
pub left_trigger: u8,
pub right_trigger: u8,
pub strong: u8,
pub weak: u8,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum GipEvent {
Input(GamepadReport),
GuideButton(bool),
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum GipError {
Transfer(PipeError),
NoInterface,
NoPipe,
UnsupportedDevice,
}
impl From<PipeError> for GipError {
fn from(e: PipeError) -> Self {
Self::Transfer(e)
}
}
impl core::fmt::Display for GipError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Transfer(_e) => write!(f, "Transfer error"),
Self::NoInterface => write!(f, "No GIP interface found"),
Self::NoPipe => write!(f, "No free pipe"),
Self::UnsupportedDevice => write!(f, "Unsupported GIP device"),
}
}
}
impl core::error::Error for GipError {}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct GipInterfaceInfo {
pub interface_number: u8,
pub interrupt_in_ep: u8,
pub interrupt_in_mps: u16,
pub interrupt_in_interval: u8,
pub interrupt_out_ep: u8,
pub interrupt_out_mps: u16,
pub interrupt_out_interval: u8,
}
pub fn find_gip(config_desc: &[u8]) -> Option<GipInterfaceInfo> {
let cfg = ConfigurationDescriptor::try_from_slice(config_desc).ok()?;
for iface in cfg.iter_interface() {
if iface.interface_class != GIP_IFACE_CLASS
|| iface.interface_subclass != GIP_IFACE_SUBCLASS
|| iface.interface_protocol != GIP_IFACE_PROTOCOL
{
continue;
}
if iface.interface_number != GIP_DATA_INTERFACE {
continue;
}
let mut in_ep: Option<(u8, u16, u8)> = None;
let mut out_ep: Option<(u8, u16, u8)> = None;
for ep in iface.iter_endpoints() {
if ep.transfer_type() == TRANSFER_TYPE_INTERRUPT {
if ep.is_in() {
in_ep = Some((ep.endpoint_address, ep.max_packet_size, ep.interval));
} else {
out_ep = Some((ep.endpoint_address, ep.max_packet_size, ep.interval));
}
}
}
if let (Some((in_addr, in_mps, in_interval)), Some((out_addr, out_mps, out_interval))) = (in_ep, out_ep) {
return Some(GipInterfaceInfo {
interface_number: iface.interface_number,
interrupt_in_ep: in_addr,
interrupt_in_mps: in_mps,
interrupt_in_interval: in_interval,
interrupt_out_ep: out_addr,
interrupt_out_mps: out_mps,
interrupt_out_interval: out_interval,
});
}
}
None
}
pub fn parse_standard_input(data: &[u8]) -> Option<GamepadReport> {
if data.len() < 18 {
return None;
}
Some(GamepadReport {
menu: data[4] & (1 << 2) != 0,
view: data[4] & (1 << 3) != 0,
a: data[4] & (1 << 4) != 0,
b: data[4] & (1 << 5) != 0,
x: data[4] & (1 << 6) != 0,
y: data[4] & (1 << 7) != 0,
dpad_up: data[5] & (1 << 0) != 0,
dpad_down: data[5] & (1 << 1) != 0,
dpad_left: data[5] & (1 << 2) != 0,
dpad_right: data[5] & (1 << 3) != 0,
left_bumper: data[5] & (1 << 4) != 0,
right_bumper: data[5] & (1 << 5) != 0,
left_stick_press: data[5] & (1 << 6) != 0,
right_stick_press: data[5] & (1 << 7) != 0,
left_trigger: u16::from_le_bytes([data[6], data[7]]),
right_trigger: u16::from_le_bytes([data[8], data[9]]),
left_stick_x: i16::from_le_bytes([data[10], data[11]]),
left_stick_y: i16::from_le_bytes([data[12], data[13]]),
right_stick_x: i16::from_le_bytes([data[14], data[15]]),
right_stick_y: i16::from_le_bytes([data[16], data[17]]),
})
}
pub fn build_standard_rumble(buf: &mut [u8; GIP_MAX_PACKET], seq: u8, cmd: &RumbleCommand) -> usize {
const MOTOR_ALL: u8 = 0x0F;
buf[0] = GIP_CMD_RUMBLE;
buf[1] = 0x00;
buf[2] = seq;
buf[3] = 0x09; buf[4] = 0x00; buf[5] = MOTOR_ALL;
buf[6] = cmd.left_trigger;
buf[7] = cmd.right_trigger;
buf[8] = cmd.strong;
buf[9] = cmd.weak;
buf[10] = 0xFF; buf[11] = 0x00; buf[12] = 0xFF; 13
}
pub trait GipDevice: Sized {
fn try_new(vendor_id: u16, product_id: u16) -> Option<Self>;
fn init_packets(&self) -> &'static [&'static [u8]];
fn parse_input(&self, data: &[u8]) -> Option<GamepadReport> {
parse_standard_input(data)
}
fn parse_guide_button(&self, data: &[u8]) -> Option<bool> {
if data.len() >= 5 {
Some(data[4] & 0x03 != 0)
} else {
None
}
}
fn build_rumble(&self, buf: &mut [u8; GIP_MAX_PACKET], seq: u8, cmd: &RumbleCommand) -> usize {
build_standard_rumble(buf, seq, cmd)
}
}
pub struct XboxOneSGamepad {
extended_init: bool,
}
static POWER_ON: &[u8] = &[0x05, 0x20, 0x00, 0x01, 0x00];
static S_INIT: &[u8] = &[0x05, 0x20, 0x00, 0x0f, 0x06];
static INIT_STANDARD: &[&[u8]] = &[POWER_ON];
static INIT_EXTENDED: &[&[u8]] = &[POWER_ON, S_INIT];
impl GipDevice for XboxOneSGamepad {
fn try_new(vendor_id: u16, product_id: u16) -> Option<Self> {
let extended_init = vendor_id == 0x045e
&& matches!(
product_id,
0x02ea | 0x0b00 | 0x0b12 );
Some(XboxOneSGamepad { extended_init })
}
fn init_packets(&self) -> &'static [&'static [u8]] {
if self.extended_init {
INIT_EXTENDED
} else {
INIT_STANDARD
}
}
}
pub struct GipHost<'d, A: UsbHostAllocator<'d>, DEV: GipDevice> {
in_ch: A::Pipe<pipe::Interrupt, pipe::In>,
out_ch: A::Pipe<pipe::Interrupt, pipe::Out>,
seq: u8,
device: DEV,
_phantom: core::marker::PhantomData<&'d ()>,
}
impl<'d, A: UsbHostAllocator<'d>, DEV: GipDevice> GipHost<'d, A, DEV> {
pub async fn try_register(alloc: &A, config_desc: &[u8], enum_info: &EnumerationInfo) -> Result<Self, GipError> {
let vendor_id = enum_info.device_desc.vendor_id;
let product_id = enum_info.device_desc.product_id;
let device = DEV::try_new(vendor_id, product_id).ok_or(GipError::UnsupportedDevice)?;
let info = find_gip(config_desc).ok_or(GipError::NoInterface)?;
let in_ep_info = EndpointInfo {
addr: EndpointAddress::from_parts((info.interrupt_in_ep & 0x0F) as usize, UsbDirection::In),
ep_type: EndpointType::Interrupt,
max_packet_size: info.interrupt_in_mps,
interval_ms: info.interrupt_in_interval,
};
let out_ep_info = EndpointInfo {
addr: EndpointAddress::from_parts((info.interrupt_out_ep & 0x0F) as usize, UsbDirection::Out),
ep_type: EndpointType::Interrupt,
max_packet_size: info.interrupt_out_mps,
interval_ms: info.interrupt_out_interval,
};
let device_address = enum_info.device_address;
let split = enum_info.split();
let in_ch = alloc
.alloc_pipe::<pipe::Interrupt, pipe::In>(device_address, &in_ep_info, split)
.map_err(|_| GipError::NoPipe)?;
let out_ch = alloc
.alloc_pipe::<pipe::Interrupt, pipe::Out>(device_address, &out_ep_info, split)
.map_err(|_| GipError::NoPipe)?;
let mut host = Self {
in_ch,
out_ch,
seq: 1,
device,
_phantom: core::marker::PhantomData,
};
host.init_device().await?;
Ok(host)
}
async fn send_init_packets(&mut self) -> Result<(), GipError> {
for packet in self.device.init_packets() {
let mut buf = [0u8; GIP_MAX_PACKET];
let len = packet.len().min(GIP_MAX_PACKET);
buf[..len].copy_from_slice(&packet[..len]);
buf[2] = self.next_seq();
self.out_ch.request_out(&buf[..len], true).await?;
}
Ok(())
}
async fn init_device(&mut self) -> Result<(), GipError> {
const MAX_DRAIN: usize = 16;
let mut buf = [0u8; GIP_MAX_PACKET];
for _ in 0..MAX_DRAIN {
let n = self.in_ch.request_in(&mut buf).await?;
if n < GIP_HEADER_LEN {
debug!("GIP init drain: received short packet ({}B), ignoring", n);
continue;
}
let msg_type = buf[0];
let flags = buf[1];
trace!(
"GIP init drain: type={:02x} flags={:02x} seq={:02x} len={:02x} ({}B)",
msg_type, flags, buf[2], buf[3], n
);
if flags & GIP_FLAG_ACK != 0 {
self.send_ack(msg_type, flags, buf[2], buf[3]).await?;
}
match msg_type {
GIP_CMD_HELLO => {
debug!("GIP device hello, sending init sequence");
self.send_init_packets().await?;
}
GIP_CMD_ACK => {}
_ => {
trace!("GIP init handshake complete (saw type 0x{:02x})", msg_type);
return Ok(());
}
}
}
warn!("GIP init drain: hit max iterations without seeing non-hello traffic");
Ok(())
}
pub async fn poll(&mut self) -> Result<GipEvent, GipError> {
let mut buf = [0u8; GIP_MAX_PACKET];
loop {
let n = self.in_ch.request_in(&mut buf).await?;
if n < GIP_HEADER_LEN {
debug!("GIP poll: received short packet ({}B), ignoring", n);
continue;
}
let msg_type = buf[0];
let flags = buf[1];
trace!(
"GIP rx: type={:02x} flags={:02x} seq={:02x} len={:02x} ({}B)",
msg_type, flags, buf[2], buf[3], n
);
if flags & GIP_FLAG_ACK != 0 {
self.send_ack(msg_type, flags, buf[2], buf[3]).await?;
}
match msg_type {
GIP_CMD_INPUT => {
if let Some(report) = self.device.parse_input(&buf[..n]) {
return Ok(GipEvent::Input(report));
}
}
GIP_CMD_VIRTUAL_KEY => {
if let Some(pressed) = self.device.parse_guide_button(&buf[..n]) {
return Ok(GipEvent::GuideButton(pressed));
}
}
_ => {
trace!("GIP message type 0x{:02x} (consumed)", msg_type);
}
}
}
}
pub async fn read_raw(&mut self, buf: &mut [u8]) -> Result<usize, GipError> {
let n = self.in_ch.request_in(buf).await?;
Ok(n)
}
pub async fn set_rumble(&mut self, cmd: &RumbleCommand) -> Result<(), GipError> {
let mut buf = [0u8; GIP_MAX_PACKET];
let seq = self.next_seq();
let len = self.device.build_rumble(&mut buf, seq, cmd);
self.out_ch.request_out(&buf[..len], true).await?;
Ok(())
}
pub async fn stop_rumble(&mut self) -> Result<(), GipError> {
self.set_rumble(&RumbleCommand::default()).await
}
pub async fn write_raw(&mut self, data: &[u8]) -> Result<(), GipError> {
self.out_ch.request_out(data, true).await?;
Ok(())
}
pub fn device(&self) -> &DEV {
&self.device
}
fn next_seq(&mut self) -> u8 {
let s = self.seq;
self.seq = self.seq.wrapping_add(1);
if self.seq == 0 {
self.seq = 1;
}
s
}
async fn send_ack(
&mut self,
orig_type: u8,
orig_flags: u8,
orig_seq: u8,
orig_payload_len: u8,
) -> Result<(), GipError> {
let ack: [u8; 13] = [
GIP_CMD_ACK,
GIP_FLAG_INTERNAL,
orig_seq,
0x09, 0x00,
orig_type,
orig_flags,
orig_payload_len,
0x00,
0x00,
0x00,
0x00,
0x00,
];
self.out_ch.request_out(&ack, true).await?;
Ok(())
}
}