fix: complete wireless pairing frames

Match the upstream 802.11 management layouts for association responses,
pair responses, and reserved client commands by including the sequence
control and association capability fields. Reuse existing WCIDs for repeated
association requests and end pairing mode after a successful pair response.
Also restore the upstream radio calibration sequence needed for reliable
WLAN associations.

Co-Authored-By: openai/gpt-5.6-luna: fixed pairing frame layouts and lifecycle
This commit is contained in:
portersky
2026-08-29 12:47:14 +02:00
parent baebcb73c2
commit 325923751c
3 changed files with 68 additions and 35 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ if(NOT CMAKE_GENERATOR MATCHES "^(Ninja|Xcode)$")
endif() endif()
cmake_minimum_required(VERSION 3.21) cmake_minimum_required(VERSION 3.21)
project(xone_macos VERSION 0.1.12 LANGUAGES CXX Swift) project(xone_macos VERSION 0.1.13 LANGUAGES CXX Swift)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+23 -6
View File
@@ -203,10 +203,23 @@ auto xone_dongle::set_pairing(bool enable) -> void
auto xone_dongle::handle_association(std::span<std::uint8_t const> addr) -> void auto xone_dongle::handle_association(std::span<std::uint8_t const> addr) -> void
{ {
// Find a free WCID slot (1..16). std::array<std::uint8_t, 6> mac{};
std::memcpy(mac.data(), addr.data(), mac.size());
// Find a free WCID slot (1..16), or reuse the WCID for a retransmitted
// request from a controller already tracked by the session.
std::uint8_t wcid = 0; std::uint8_t wcid = 0;
bool existing = false;
{ {
std::lock_guard<std::mutex> guard(lock); std::lock_guard<std::mutex> guard(lock);
for (auto const& c : controllers) {
if (c.mac == mac) {
wcid = c.wcid;
existing = true;
break;
}
}
if (!existing) {
for (std::uint8_t i = 1; i <= 16; ++i) { for (std::uint8_t i = 1; i <= 16; ++i) {
bool used = false; bool used = false;
for (auto const& c : controllers) for (auto const& c : controllers)
@@ -214,12 +227,10 @@ auto xone_dongle::handle_association(std::span<std::uint8_t const> addr) -> void
if (!used) { wcid = i; break; } if (!used) { wcid = i; break; }
} }
} }
}
if (wcid == 0) if (wcid == 0)
return; // no free slot return; // no free slot
std::array<std::uint8_t, 6> mac{};
std::memcpy(mac.data(), addr.data(), mac.size());
// Program the chip outside the lock (synchronous USB I/O). // Program the chip outside the lock (synchronous USB I/O).
if (chip->associate_client(wcid, mac) != 0) { if (chip->associate_client(wcid, mac) != 0) {
xone::log_msg(xone::log_level::warn, xone::log_msg(xone::log_level::warn,
@@ -227,7 +238,7 @@ auto xone_dongle::handle_association(std::span<std::uint8_t const> addr) -> void
return; return;
} }
{ if (!existing) {
std::lock_guard<std::mutex> guard(lock); std::lock_guard<std::mutex> guard(lock);
controllers.push_back(controller{wcid, mac}); controllers.push_back(controller{wcid, mac});
} }
@@ -274,7 +285,13 @@ auto xone_dongle::handle_client_command(std::span<std::uint8_t const> payload,
case client_cmd::client_pair_req: { case client_cmd::client_pair_req: {
std::array<std::uint8_t, 6> mac{}; std::array<std::uint8_t, 6> mac{};
std::memcpy(mac.data(), addr.data(), mac.size()); std::memcpy(mac.data(), addr.data(), mac.size());
chip->pair_client(mac); if (auto err = chip->pair_client(mac); err != 0) {
xone::log_msg(xone::log_level::warn,
"api: pair response failed (%d)", err);
break;
}
// Upstream ends the 30-second pairing window after PAIR_RESP.
set_pairing(false);
xone::log_msg(xone::log_level::info, xone::log_msg(xone::log_level::info,
"api: controller paired (wcid=%d)", wcid); "api: controller paired (wcid=%d)", wcid);
break; break;
+39 -23
View File
@@ -496,12 +496,25 @@ auto chip::set_idle_time() -> int
auto chip::calibrate_radio() -> int auto chip::calibrate_radio() -> int
{ {
// Enable AGC for all antennas. // Configure automatic gain control (port of upstream AGC setup).
write_register(mt_bbp_agc(0), 0x0000001f); write_register(mt_bbp_agc(8), 0x18365efa);
write_register(mt_bbp_agc(1), 0x0000001f); write_register(mt_bbp_agc(9), 0x18365efa);
write_register(mt_bbp_agc(2), 0x0000001f);
return calibrate(calibration::cal_rc, 0); // Required reset for reliable WLAN associations.
write_register(mt_mac_sys_ctrl, 0);
write_register(mt_rf_bypass_0, 0);
write_register(mt_rf_setting_0, 0);
if (auto err = calibrate(calibration::cal_temp_sensor, 0); err != 0)
return err;
if (auto err = calibrate(calibration::cal_rxdcoc, 1); err != 0)
return err;
if (auto err = calibrate(calibration::cal_rc, 0); err != 0)
return err;
write_register(mt_mac_sys_ctrl,
mt_mac_sys_ctrl_enable_rx | mt_mac_sys_ctrl_enable_tx);
return 0;
} }
auto chip::get_channel_power(channel *chan) -> int auto chip::get_channel_power(channel *chan) -> int
@@ -800,16 +813,18 @@ auto chip::associate_client(std::uint8_t wcid,
{ {
auto address = mac_address(); auto address = mac_address();
// struct ieee80211_mgmt (assoc_resp), 26 bytes: // struct ieee80211_mgmt (assoc_resp), 30 bytes:
// [frame_control][duration][da][sa][bssid][status_code][aid]. // [header][capability_info][status_code][aid].
std::uint8_t mgmt[26] = {}; std::uint8_t mgmt[30] = {};
xone::store_le16(mgmt + 0, ieee80211_ftype_mgmt xone::store_le16(mgmt + 0, ieee80211_ftype_mgmt
| ieee80211_stype_assoc_resp); | ieee80211_stype_assoc_resp);
std::memcpy(mgmt + 4, addr.data(), 6); // da std::memcpy(mgmt + 4, addr.data(), 6); // da
std::memcpy(mgmt + 10, address.data(), 6); // sa std::memcpy(mgmt + 10, address.data(), 6); // sa
std::memcpy(mgmt + 16, address.data(), 6); // bssid std::memcpy(mgmt + 16, address.data(), 6); // bssid
xone::store_le16(mgmt + 22, 0x0110); // status_code (original) // Capability info stays zero, matching the upstream zero-initialized
xone::store_le16(mgmt + 24, 0x0f00); // aid (original) // struct. The original status code and association ID follow it.
xone::store_le16(mgmt + 26, 0x0110); // status_code
xone::store_le16(mgmt + 28, 0x0f00); // aid
// Payload: mgmt frame plus 8 bytes of zero padding. // Payload: mgmt frame plus 8 bytes of zero padding.
std::vector<std::uint8_t> payload(sizeof(mgmt) + 8, 0); std::vector<std::uint8_t> payload(sizeof(mgmt) + 8, 0);
@@ -832,20 +847,20 @@ auto chip::pair_client(std::span<std::uint8_t const> addr) -> int
{ {
auto address = mac_address(); auto address = mac_address();
// struct ieee80211_hdr_3addr (22 bytes) plus the reserved command and a // struct ieee80211_hdr_3addr (24 bytes) plus the reserved command and a
// 9-byte payload: [fc][duration][addr1][addr2][addr3][0x70][PAIR_RESP] // 9-byte payload: [header][0x70][PAIR_RESP][data].
// [data]. std::uint8_t frame[35] = {};
std::uint8_t frame[33] = {};
xone::store_le16(frame + 0, ieee80211_ftype_mgmt xone::store_le16(frame + 0, ieee80211_ftype_mgmt
| ieee80211_stype_wlan_reserved); | ieee80211_stype_wlan_reserved);
std::memcpy(frame + 4, addr.data(), 6); // addr1 std::memcpy(frame + 4, addr.data(), 6); // addr1
std::memcpy(frame + 10, address.data(), 6); // addr2 std::memcpy(frame + 10, address.data(), 6); // addr2
std::memcpy(frame + 16, address.data(), 6); // addr3 std::memcpy(frame + 16, address.data(), 6); // addr3
frame[22] = ieee80211_stype_wlan_reserved; // reserved (0x70) // seq_ctrl stays zero, matching the upstream zero-initialized header.
frame[23] = client_cmd::client_pair_resp; frame[24] = ieee80211_stype_wlan_reserved; // reserved (0x70)
frame[25] = client_cmd::client_pair_resp;
std::uint8_t data[9] = { 0x00, 0x45, 0x55, 0x01, 0x0f, 0x8f, 0xff, 0x87, std::uint8_t data[9] = { 0x00, 0x45, 0x55, 0x01, 0x0f, 0x8f, 0xff, 0x87,
0x1f }; 0x1f };
std::memcpy(frame + 24, data, sizeof(data)); std::memcpy(frame + 26, data, sizeof(data));
return send_wlan({ frame, frame + sizeof(frame) }); return send_wlan({ frame, frame + sizeof(frame) });
} }
@@ -857,9 +872,9 @@ auto chip::send_client_command(std::uint8_t wcid,
{ {
auto address = mac_address(); auto address = mac_address();
// Payload: [info(8)][txwi(20)][hdr(22)][0x70][cmd][data]. // Payload: [info(8)][txwi(20)][hdr(24)][0x70][cmd][data].
auto txwi = build_txwi(wcid - 1, 22 + 2 + data.size()); auto txwi = build_txwi(wcid - 1, 24 + 2 + data.size());
std::vector<std::uint8_t> payload(8 + txwi.size() + 22 + 2 + data.size()); std::vector<std::uint8_t> payload(8 + txwi.size() + 24 + 2 + data.size());
// info: {0, 0, 0, wcid-1, 0, 0, 0, 0}. // info: {0, 0, 0, wcid-1, 0, 0, 0, 0}.
payload[3] = wcid - 1; payload[3] = wcid - 1;
@@ -871,10 +886,11 @@ auto chip::send_client_command(std::uint8_t wcid,
std::memcpy(hdr + 4, addr.data(), 6); // addr1 std::memcpy(hdr + 4, addr.data(), 6); // addr1
std::memcpy(hdr + 10, address.data(), 6); // addr2 std::memcpy(hdr + 10, address.data(), 6); // addr2
std::memcpy(hdr + 16, address.data(), 6); // addr3 std::memcpy(hdr + 16, address.data(), 6); // addr3
hdr[22] = ieee80211_stype_wlan_reserved; // reserved (0x70) // seq_ctrl stays zero, matching the upstream zero-initialized header.
hdr[23] = static_cast<std::uint8_t>(cmd); hdr[24] = ieee80211_stype_wlan_reserved; // reserved (0x70)
hdr[25] = static_cast<std::uint8_t>(cmd);
if (!data.empty()) if (!data.empty())
std::memcpy(hdr + 24, data.data(), data.size()); std::memcpy(hdr + 26, data.data(), data.size());
return send_command(0, payload.data(), payload.size()); return send_command(0, payload.data(), payload.size());
} }