dfe7e8b87d
Rename all trailing-underscore private data members to the m_ prefix required by AGENTS.md, across the auth, crypto, gip, mt76, and usb transport classes plus the app session classes. Pure rename; no behavior change. Full build and test suite pass. Co-Authored-By: qwen3.8-27b@q3_k_xl: renamed members to m_ prefix
301 lines
10 KiB
C++
301 lines
10 KiB
C++
#pragma once
|
|
|
|
// ==============================================================================
|
|
// GIP protocol (Game Input Protocol)
|
|
// ==============================================================================
|
|
// Port target: medusalix/xone bus/protocol.c + bus/bus.c.
|
|
//
|
|
// The kernel driver's device model (struct device, sysfs, driver
|
|
// registration) is gone. The adapter owns up to 16 clients; the transport
|
|
// interface abstracts the MT76 data path (Phase 3), and client_listener
|
|
// delivers input/battery/audio events to the HID layer (Phase 4). The
|
|
// adapter is driven single-threaded from the USB read callback.
|
|
// ==============================================================================
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <span>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "auth/auth.hpp"
|
|
#include "common/types.hpp"
|
|
|
|
namespace xone::gip {
|
|
|
|
using u8 = std::uint8_t;
|
|
using u16 = std::uint16_t;
|
|
using u32 = std::uint32_t;
|
|
|
|
inline constexpr u16 k_vid_microsoft = 0x045e;
|
|
inline constexpr int k_audio_interval = 8; // ms between audio packets
|
|
inline constexpr u32 k_pkt_max_length = 58;
|
|
inline constexpr u32 k_chunk_buf_max_length = 0xffff;
|
|
inline constexpr int k_max_clients = 16;
|
|
|
|
enum : u8 {
|
|
GIP_CMD_ACKNOWLEDGE = 0x01,
|
|
GIP_CMD_ANNOUNCE = 0x02,
|
|
GIP_CMD_STATUS = 0x03,
|
|
GIP_CMD_IDENTIFY = 0x04,
|
|
GIP_CMD_POWER = 0x05,
|
|
GIP_CMD_AUTHENTICATE = 0x06,
|
|
GIP_CMD_VIRTUAL_KEY = 0x07,
|
|
GIP_CMD_AUDIO_CONTROL = 0x08,
|
|
GIP_CMD_RUMBLE = 0x09,
|
|
GIP_CMD_LED = 0x0a,
|
|
GIP_CMD_HID_REPORT = 0x0b,
|
|
GIP_CMD_FIRMWARE = 0x0c,
|
|
GIP_CMD_SERIAL_NUMBER = 0x1e,
|
|
GIP_CMD_INPUT = 0x20,
|
|
GIP_CMD_AUDIO_SAMPLES = 0x60,
|
|
};
|
|
|
|
enum : u8 {
|
|
GIP_OPT_ACKNOWLEDGE = 1 << 4,
|
|
GIP_OPT_INTERNAL = 1 << 5,
|
|
GIP_OPT_CHUNK_START = 1 << 6,
|
|
GIP_OPT_CHUNK = 1 << 7,
|
|
};
|
|
|
|
enum : u8 {
|
|
GIP_BATT_TYPE_NONE = 0x00,
|
|
GIP_BATT_TYPE_STANDARD = 0x01,
|
|
GIP_BATT_TYPE_KIT = 0x02,
|
|
};
|
|
|
|
enum : u8 {
|
|
GIP_BATT_LEVEL_LOW = 0x00,
|
|
GIP_BATT_LEVEL_NORMAL = 0x01,
|
|
GIP_BATT_LEVEL_HIGH = 0x02,
|
|
GIP_BATT_LEVEL_FULL = 0x03,
|
|
};
|
|
|
|
enum : u8 {
|
|
GIP_PWR_ON = 0x00,
|
|
GIP_PWR_SLEEP = 0x01,
|
|
GIP_PWR_OFF = 0x04,
|
|
GIP_PWR_RESET = 0x07,
|
|
};
|
|
|
|
enum : u8 {
|
|
GIP_LED_OFF = 0x00,
|
|
GIP_LED_ON = 0x01,
|
|
GIP_LED_BLINK_FAST = 0x02,
|
|
GIP_LED_BLINK_NORMAL = 0x03,
|
|
GIP_LED_BLINK_SLOW = 0x04,
|
|
GIP_LED_FADE_SLOW = 0x08,
|
|
GIP_LED_FADE_FAST = 0x09,
|
|
};
|
|
|
|
enum : u8 {
|
|
GIP_AUD_FORMAT_16KHZ_MONO = 0x05,
|
|
GIP_AUD_FORMAT_24KHZ_MONO = 0x09,
|
|
GIP_AUD_FORMAT_48KHZ_STEREO = 0x10,
|
|
};
|
|
|
|
enum : u8 {
|
|
GIP_AUD_FORMAT_CHAT_24KHZ = 0x04,
|
|
GIP_AUD_FORMAT_CHAT_16KHZ = 0x05,
|
|
};
|
|
|
|
// Decoded wire header (variable length, not packed).
|
|
struct gip_header {
|
|
u8 command = 0;
|
|
u8 options = 0;
|
|
u8 sequence = 0;
|
|
u32 packet_length = 0;
|
|
u32 chunk_offset = 0;
|
|
};
|
|
|
|
struct gip_hardware {
|
|
u16 vendor = 0;
|
|
u16 product = 0;
|
|
u16 version = 0;
|
|
};
|
|
|
|
struct gip_audio_config {
|
|
u8 format = 0;
|
|
int channels = 0;
|
|
int sample_rate = 0;
|
|
int buffer_size = 0;
|
|
int fragment_size = 0;
|
|
int packet_size = 0;
|
|
};
|
|
|
|
// Low-level data path to the MT76 chip (implemented by Phase 3).
|
|
class transport {
|
|
public:
|
|
virtual ~transport() = default;
|
|
|
|
// Send one framed GIP data buffer to the chip.
|
|
virtual auto send_frame(std::span<u8 const> frame) -> int = 0;
|
|
|
|
// Install the AES-CCMP session key for a client (no-op by default).
|
|
virtual auto set_encryption_key(u8 client_id, std::span<u8 const> key) -> int
|
|
{
|
|
(void)client_id;
|
|
(void)key;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
// Events delivered to the HID layer (Phase 4).
|
|
class client_listener {
|
|
public:
|
|
virtual ~client_listener() = default;
|
|
virtual auto on_client_added(class client&) -> void {}
|
|
virtual auto on_client_removed(u8) -> void {}
|
|
virtual auto on_battery(class client&, u8, u8) -> void {}
|
|
virtual auto on_guide_button(class client&, bool) -> void {}
|
|
virtual auto on_input(class client&, std::span<u8 const>) -> void {}
|
|
virtual auto on_hid_report(class client&, std::span<u8 const>) -> void {}
|
|
virtual auto on_audio_ready(class client&) -> void {}
|
|
virtual auto on_audio_volume(class client&, u8, u8) -> void {}
|
|
virtual auto on_audio_samples(class client&, std::span<u8 const>) -> void {}
|
|
};
|
|
|
|
class adapter;
|
|
|
|
// One connected controller. Also implements the auth_sink so the auth
|
|
// handshake can send AUTHENTICATE packets back through the GIP layer.
|
|
class client : public xone::auth::auth_sink {
|
|
public:
|
|
client(adapter& adapter, u8 id);
|
|
|
|
auto id() const -> u8 { return m_id; }
|
|
auto hardware() const -> gip_hardware const& { return m_hardware; }
|
|
auto audio_config_in() const -> gip_audio_config const& { return m_audio_config_in; }
|
|
auto audio_config_out() const -> gip_audio_config const& { return m_audio_config_out; }
|
|
auto classes() const -> std::vector<std::string> const& { return m_classes; }
|
|
|
|
auto has_interface(xone::guid_t const& guid) const -> bool;
|
|
|
|
auto set_power_mode(u8 mode) -> int;
|
|
auto send_rumble(std::span<u8 const> pkt) -> int;
|
|
auto set_led_mode(u8 mode, u8 brightness) -> int;
|
|
auto suggest_audio_format(u8 in, u8 out, bool chat) -> int;
|
|
auto set_audio_volume(u8 in, u8 chat, u8 out) -> int;
|
|
auto send_audio_samples(std::span<u8 const> samples) -> int;
|
|
auto enable_audio() -> int;
|
|
auto init_audio_in() -> int;
|
|
auto init_audio_out() -> int;
|
|
auto disable_audio() -> void;
|
|
|
|
// auth_sink implementation.
|
|
auto send(std::span<u8 const> pkt, bool acknowledge) -> int override;
|
|
auto set_encryption_key(std::span<u8 const> key) -> int override;
|
|
|
|
auto start_auth() -> int { return m_auth.start(); }
|
|
auto process_auth(std::span<u8 const> data) -> int
|
|
{
|
|
return m_auth.process_pkt(data);
|
|
}
|
|
|
|
private:
|
|
friend class adapter;
|
|
|
|
adapter& m_adapter;
|
|
u8 m_id;
|
|
gip_hardware m_hardware{};
|
|
|
|
struct info_element {
|
|
u8 count = 0;
|
|
std::vector<u8> data;
|
|
};
|
|
|
|
std::unique_ptr<info_element> m_client_commands;
|
|
std::unique_ptr<info_element> m_firmware_versions;
|
|
std::unique_ptr<info_element> m_audio_formats;
|
|
std::unique_ptr<info_element> m_capabilities_out;
|
|
std::unique_ptr<info_element> m_capabilities_in;
|
|
std::vector<std::string> m_classes;
|
|
std::unique_ptr<info_element> m_interfaces;
|
|
std::unique_ptr<info_element> m_hid_descriptor;
|
|
|
|
gip_audio_config m_audio_config_in;
|
|
gip_audio_config m_audio_config_out;
|
|
|
|
// Chunk reassembly buffers (large packets are split into chunks).
|
|
struct chunk_buffer {
|
|
gip_header header;
|
|
u32 length = 0;
|
|
std::vector<u8> data;
|
|
};
|
|
std::unique_ptr<chunk_buffer> m_chunk_buf_out;
|
|
std::unique_ptr<chunk_buffer> m_chunk_buf_in;
|
|
|
|
xone::auth::auth m_auth{*this};
|
|
};
|
|
|
|
class adapter {
|
|
public:
|
|
adapter(transport& transport, client_listener& listener,
|
|
int audio_packet_count = 8);
|
|
|
|
// Dispatch one USB data buffer (may contain multiple GIP packets).
|
|
auto process_buffer(std::span<u8 const> data) -> int;
|
|
|
|
auto get_client(u8 id) -> client*;
|
|
auto client_count() const -> int;
|
|
|
|
private:
|
|
friend class client;
|
|
|
|
auto send_pkt(client& c, gip_header& hdr, void const* data) -> int;
|
|
auto send_pkt_simple(gip_header& hdr, void const* data) -> int;
|
|
auto init_chunk_buffer(gip_header const& hdr,
|
|
std::unique_ptr<client::chunk_buffer>& buf) -> int;
|
|
auto send_remaining_chunks(client& c) -> int;
|
|
auto request_identification(client& c) -> int;
|
|
auto acknowledge_pkt(client& c, gip_header const& ack) -> int;
|
|
auto remove_client(client& c) -> void;
|
|
auto free_client_info(client& c) -> void;
|
|
auto add_client(client& c) -> void;
|
|
auto make_audio_config(gip_audio_config& cfg) -> int;
|
|
auto set_audio_format(client& c, u8 in, u8 out) -> int;
|
|
auto set_audio_format_chat(client& c, u8 in_out) -> int;
|
|
|
|
auto dispatch_pkt(client& c, gip_header const& hdr, void const* data, u32 len) -> int;
|
|
auto process_pkt(client& c, gip_header& hdr, void const* data) -> int;
|
|
auto process_pkt_chunked(client& c, gip_header const& hdr, void const* data) -> int;
|
|
|
|
auto handle_pkt_acknowledge(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_announce(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_status(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_identify(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_authenticate(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_virtual_key(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_audio_control(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_hid_report(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_input(client& c, void const* data, u32 len) -> int;
|
|
auto handle_pkt_audio_samples(client& c, void const* data, u32 len) -> int;
|
|
|
|
auto parse_info_element(std::span<u8 const> data, u16 offset, int item_length,
|
|
std::unique_ptr<client::info_element>& out) -> int;
|
|
auto parse_client_commands(client& c, u16 const offsets[8],
|
|
std::span<u8 const> data) -> int;
|
|
auto parse_firmware_versions(client& c, u16 const offsets[8],
|
|
std::span<u8 const> data) -> int;
|
|
auto parse_audio_formats(client& c, u16 const offsets[8],
|
|
std::span<u8 const> data) -> int;
|
|
auto parse_capabilities(client& c, u16 const offsets[8],
|
|
std::span<u8 const> data) -> int;
|
|
auto parse_classes(client& c, std::span<u8 const> data, u16 offset) -> int;
|
|
auto parse_interfaces(client& c, u16 const offsets[8],
|
|
std::span<u8 const> data) -> int;
|
|
auto parse_hid_descriptor(client& c, u16 const offsets[8],
|
|
std::span<u8 const> data) -> int;
|
|
|
|
transport& m_transport;
|
|
client_listener& m_listener;
|
|
int m_audio_packet_count;
|
|
|
|
std::array<std::unique_ptr<client>, k_max_clients> m_clients;
|
|
u8 m_data_sequence = 0;
|
|
u8 m_audio_sequence = 0;
|
|
};
|
|
|
|
} // namespace xone::gip
|