feat: pair controllers and show them in the app

Port the MT76 client functions (send_wlan, associate_client,
pair_client, set_client_key, remove_client) and wire the RX dispatch
into the session: EP IN frames are parsed, ASSOC_REQ associates a
controller (WCID plus chip programming), PAIR_REQ replies with
PAIR_RESP, and DISASSOC or client-lost removes it. The C API exposes
connected controllers and the app lists them by MAC. Bumps
xone_cli/xone_app to C++23: they were falling back to the default
standard once mt76.hpp started using std::span. Adds a unit test pinning
the WCID regions, the 32-byte rxwi, and the new frame control values.

Co-Authored-By: qwen3.8-27b@q2_k_xl: client functions, RX dispatch, GUI list
This commit is contained in:
portersky
2026-08-17 20:40:47 +02:00
parent b3e747b900
commit dae51f085b
9 changed files with 682 additions and 7 deletions
+7
View File
@@ -65,6 +65,13 @@ const char *xone_mac_address(const xone_dongle *d);
// Empty string if the firmware was not loaded. Valid until xone_close().
const char *xone_firmware_build(const xone_dongle *d);
// Number of connected controllers.
int xone_controller_count(xone_dongle const *d);
// Copy the controller at index into buf as "xx:xx:xx:xx:xx:xx". Returns 0 on
// success, -1 if index is out of range.
int xone_controller_mac(xone_dongle const *d, int index, char *buf, int len);
#ifdef __cplusplus
}
#endif
+33 -1
View File
@@ -12,6 +12,7 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
#include "mt76/mt76_defs.hpp"
@@ -55,7 +56,7 @@ public:
auto select_function(std::uint32_t func, std::uint32_t val) -> int;
auto set_power_mode(power_mode mode) -> int;
auto load_cr(cr_mode mode) -> int;
auto write_burst(std::uint32_t idx, void *data, std::size_t len) -> int;
auto write_burst(std::uint32_t idx, void const *data, std::size_t len) -> int;
auto send_ms_command(ms_command cmd, void *data, std::size_t len) -> int;
auto calibrate(calibration calib, std::uint32_t val) -> int;
@@ -69,6 +70,37 @@ public:
// Enter or leave pairing mode (port of xone_mt76_set_pairing).
auto set_pairing(bool enable) -> int;
// Controller association and data path (ports of the xone_mt76_* client
// functions). `addr` is a 6-byte controller MAC.
//
// send_wlan prepends a TXWI and frames the payload as an MCU message,
// then bulk-writes it to EP OUT.
auto send_wlan(std::span<std::uint8_t const> frame) -> int;
// Associate a controller: program its WCID MAC, ADD_CLIENT ms_command,
// and transmit an ASSOC_RESP mgmt frame.
auto associate_client(std::uint8_t wcid, std::span<std::uint8_t const> addr)
-> int;
// Reply to a pairing request with a PAIR_RESP mgmt frame.
auto pair_client(std::span<std::uint8_t const> addr) -> int;
// Send a reserved (0x70) management command to a client, waiting for an
// acknowledgment (port of xone_mt76_send_client_command).
auto send_client_command(std::uint8_t wcid,
std::span<std::uint8_t const> addr,
client_cmd cmd,
std::span<std::uint8_t const> data) -> int;
// Install the per-client AES-CCMP key (16 bytes) in the chip WCID
// registers (port of xone_mt76_set_client_key).
auto set_client_key(std::uint8_t wcid, std::span<std::uint8_t const> key)
-> int;
// Remove a client: REMOVE_CLIENT ms_command and zero its WCID regions
// (port of xone_mt76_remove_client).
auto remove_client(std::uint8_t wcid) -> int;
private:
// Send an MCU command (port of xone_mt76_send_command). `cmd` is the
// MT_MCU_MSG_CMD_TYPE field. Returns bytes written, or a negative errno.
+105
View File
@@ -30,6 +30,13 @@ inline constexpr auto field_prep(std::uint32_t mask, std::uint32_t val) -> std::
return (val << __builtin_ctz(mask)) & mask;
}
// Extract a bitfield (port of the kernel FIELD_GET macro).
inline constexpr auto field_get(std::uint32_t mask, std::uint32_t val)
-> std::uint32_t
{
return (val & mask) >> __builtin_ctz(mask);
}
// Register address flag: use the config-space vendor request codes.
constexpr std::uint32_t mt_vend_type_cfg = bit(30);
@@ -382,4 +389,102 @@ constexpr std::uint8_t mt_txwi_ack_ctl_nseq = bit(1);
// 802.11 frame control for a beacon (MGMT type, BEACON subtype).
constexpr std::uint16_t ieee80211_fc_beacon = 0x0080;
// WCID regions (port of MT_WCID_*). n is the zero-based WCID index.
constexpr std::uint32_t mt_wcid_addr_base = 0x1800;
inline constexpr auto mt_wcid_addr(std::size_t n) -> std::uint32_t
{
return mt_wcid_addr_base + (n << 3);
}
constexpr std::uint32_t mt_wcid_key_base = 0x8000;
inline constexpr auto mt_wcid_key(std::size_t n) -> std::uint32_t
{
return mt_wcid_key_base + (n << 5);
}
constexpr std::uint32_t mt_wcid_iv_base = 0xa000;
inline constexpr auto mt_wcid_iv(std::size_t n) -> std::uint32_t
{
return mt_wcid_iv_base + (n << 3);
}
constexpr std::uint32_t mt_wcid_attr_base = 0xa800;
inline constexpr auto mt_wcid_attr(std::size_t n) -> std::uint32_t
{
return mt_wcid_attr_base + (n << 2);
}
constexpr std::uint32_t mt_wcid_attr_pairwise = bit(0);
constexpr std::uint32_t mt_wcid_attr_pkey_mode = genmask(3, 1);
constexpr std::size_t xone_mt_wcid_key_len = 16;
// TX descriptor info fields (port of MT_TXD_INFO_*), used by send_wlan.
constexpr std::uint32_t mt_txd_info_80211 = bit(19);
constexpr std::uint32_t mt_txd_info_wiv = bit(24);
constexpr std::uint32_t mt_txd_info_qsel = genmask(26, 25);
constexpr std::uint32_t mt_txd_info_dport = genmask(29, 27);
// RX FCE info fields (port of MT_RX_FCE_INFO_*), used by the RX path.
constexpr std::uint32_t mt_rx_fce_info_len = genmask(13, 0);
constexpr std::uint32_t mt_rx_fce_info_evt_type = genmask(23, 20);
// Receive descriptor (port of struct mt76_rxwi). Little-endian wire order,
// 32 bytes total. Only rxinfo and ctl are consumed by the RX path.
struct mt76_rxwi {
std::uint32_t rxinfo; // MT_RXINFO_L2PAD etc.
std::uint32_t ctl; // wcid (bits 7:0), mpdu_len (bits 29:16)
std::uint16_t tid_sn;
std::uint16_t rate;
std::uint8_t rssi[4];
std::uint32_t bbp_rxinfo[4];
};
constexpr std::uint32_t mt_rxinfo_l2pad = bit(14);
constexpr std::uint32_t mt_rxwi_ctl_wcid = genmask(7, 0);
constexpr std::uint32_t mt_rxwi_ctl_mpdu_len = genmask(29, 16);
// CPU RX event types (port of XONE_MT_EVT_*).
enum cpu_evt : std::uint32_t {
evt_button = 0x04,
evt_packet_rx = 0x0c,
evt_client_lost = 0x0e,
};
// Client command codes (port of XONE_MT_CLIENT_*).
enum client_cmd : std::uint32_t {
client_pair_req = 0x01,
client_pair_resp = 0x02,
client_enable_encryption = 0x10,
};
// 802.11 frame control: type (bits 3:2) and subtype (bits 7:4).
// FCTL_FTYPE/FCTL_STYPE are the masks used to match a frame's type/subtype.
constexpr std::uint16_t ieee80211_fctl_ftype = 0x0c;
constexpr std::uint16_t ieee80211_fctl_stype = 0xf0;
constexpr std::uint16_t ieee80211_ftype_mgmt = 0x00;
constexpr std::uint16_t ieee80211_ftype_data = 0x08;
constexpr std::uint16_t ieee80211_stype_assoc_req = 0x00;
constexpr std::uint16_t ieee80211_stype_assoc_resp = 0x10;
constexpr std::uint16_t ieee80211_stype_disassoc = 0xa0;
constexpr std::uint16_t ieee80211_stype_qos_data = 0x70;
// MGMT subtype for reserved (pairing) frames.
constexpr std::uint16_t ieee80211_stype_wlan_reserved = 0x70;
// TX descriptor fields (port of MT_TXWI_*).
constexpr std::uint32_t mt_txwi_flags_mpdu_density = genmask(7, 5);
constexpr std::uint8_t mt_txwi_ack_ctl_req = bit(0);
constexpr std::uint8_t ieee80211_ht_mpdu_density_4 = 4;
// TX queue selection (port of enum mt76_qsel).
enum qsel : std::uint32_t {
qsel_mgmt = 0,
qsel_hcca = 1,
qsel_edca = 2,
qsel_edca_2 = 3,
};
// Cipher types (port of enum mt76_cipher_type).
enum cipher_type : std::uint32_t {
cipher_none = 0,
cipher_wep40 = 1,
cipher_wep104 = 2,
cipher_tkip = 3,
cipher_aes_ccmp = 4,
};
} // namespace xone::mt76