feat: port MT76 radio init and CLI tool

Complete the radio bring-up sequence: init_registers now writes the
upstream register values (PBF out of reset, beacon TX off), crystal
calibration, MAC/BSSID programming, channel evaluation, and beacon
programming through an MCU burst into PBF shared memory. The beacon
txwi is the full 20-byte struct and the RF patch is applied on the
cold firmware path.

Download firmware per product (xone_dongle_02e6.bin / 02fe.bin) and
add the xone_cli debug tool (info, firmware, radio-init/deinit, burst,
reg-read/write, led, recover). Recover uses USBDeviceReEnumerate for a
host-side port reset.

Co-Authored-By: grok4.6: internet search (firmware split, beacon SRAM, upstream issues)
Co-Authored-By: qwen3.8-27b@q2_k_xl: initial radio init and CLI implementation
Co-Authored-By: deepseek/deepseek-v4-pro-0813: final radio init fixes and verification
This commit is contained in:
portersky
2026-08-17 19:29:51 +02:00
parent 4f5f16e43f
commit c9ea1a6919
11 changed files with 1142 additions and 39 deletions
+56 -20
View File
@@ -5,6 +5,7 @@
#include <cerrno>
#include <condition_variable>
#include <cstdint>
#include <cstring>
#include <mutex>
#include <optional>
@@ -15,6 +16,7 @@
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/usb/USB.h>
#include "common/log.hpp"
@@ -96,6 +98,7 @@ struct transport::state {
IOUSBInterfaceInterface190 **iface_ref = nullptr;
};
std::vector<iface_conn> ifaces;
bool re_enumerated = false;
struct pipe_ref {
IOUSBInterfaceInterface190 **iface_ref = nullptr;
@@ -138,37 +141,70 @@ auto transport::probe(frame_callback frames, disconnect_callback disconnected) -
return t;
}
void transport::stop_pump()
{
{
std::lock_guard<std::mutex> lock(state_->lock);
if (state_->stopping)
return;
state_->stopping = true;
}
for (auto &slot : state_->slots)
(*slot.iface_ref)->AbortPipe(slot.iface_ref, slot.pipe_ref);
if (!state_->thread_started)
return;
CFRunLoopRef runloop = nullptr;
{
std::unique_lock<std::mutex> lock(state_->lock);
state_->cv.wait(lock, [this] { return state_->loop_ready && state_->in_flight == 0; });
runloop = state_->runloop;
}
// The reader thread is joined below; no callback can run after this.
CFRunLoopStop(runloop);
state_->thread.join();
}
auto transport::re_enumerate() -> int
{
// Stop the pump first so no completion callback touches the interface
// references while the kernel tears them down.
stop_pump();
auto kr = (*state_->dev_ref)->USBDeviceReEnumerate(state_->dev_ref, kUSBAddExtraResetTimeMask);
if (kr != kIOReturnSuccess) {
xone::log_msg(log_level::error, "usb: re-enumerate failed (%d)", kr);
return -EIO;
}
// The kernel terminated all of our clients; the USB references are dead.
state_->re_enumerated = true;
xone::log_msg(log_level::info, "usb: re-enumerate ok");
return 0;
}
transport::~transport()
{
if (!state_)
return;
// Stop generating completions, then wait for the in-flight ones to drain.
{
std::lock_guard<std::mutex> lock(state_->lock);
state_->stopping = true;
}
for (auto &slot : state_->slots)
(*slot.iface_ref)->AbortPipe(slot.iface_ref, slot.pipe_ref);
if (state_->thread_started) {
CFRunLoopRef runloop = nullptr;
{
std::unique_lock<std::mutex> lock(state_->lock);
state_->cv.wait(lock, [this] { return state_->loop_ready && state_->in_flight == 0; });
runloop = state_->runloop;
}
// The reader thread is joined below; no callback can run after this.
CFRunLoopStop(runloop);
state_->thread.join();
}
stop_pump();
// Release the termination watch and async event sources.
if (state_->termination_iter)
IOObjectRelease(state_->termination_iter);
if (state_->notify_port)
IONotificationPortDestroy(state_->notify_port);
if (state_->re_enumerated) {
// The kernel already tore down the device and interfaces.
if (state_->service)
IOObjectRelease(state_->service);
return;
}
for (auto *source : state_->sources)
CFRelease(source);