feat: expose dongle debug info through the C API
Add an opaque xone_dongle session handle to the C ABI bridge: xone_open() probes, resets, and loads the firmware (path optional), and xone_pid/xone_chip_id/xone_mac_address/xone_firmware_build return debug info for the open session. chip captures the firmware build string from the image header during load. The Swift app opens a session when the dongle appears and shows PID, chip ID, MAC address, and firmware build string in a debug section. Co-Authored-By: qwen3.8-27b@q2_k_xl: added C API session handle and debug UI
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ if(NOT CMAKE_GENERATOR MATCHES "^(Ninja|Xcode)$")
|
||||
endif()
|
||||
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(xone_macos VERSION 0.1.6 LANGUAGES CXX Swift)
|
||||
project(xone_macos VERSION 0.1.7 LANGUAGES CXX Swift)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// ==============================================================================
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -19,6 +20,32 @@ const char *xone_version(void);
|
||||
// TRUE if an Xbox Wireless Dongle is connected, FALSE otherwise.
|
||||
bool xone_dongle_present(void);
|
||||
|
||||
// Opaque handle to an open dongle session (probe + firmware load).
|
||||
// Create with xone_open(); call the getters from the thread that opened
|
||||
// it, and before xone_close(). Only one session may be open at a time.
|
||||
typedef struct xone_dongle xone_dongle;
|
||||
|
||||
// Open a dongle session: probe, USB reset, and (if firmware_path is not
|
||||
// NULL) load the firmware image from that file. Returns NULL if no dongle
|
||||
// is present or opening fails.
|
||||
xone_dongle *xone_open(const char *firmware_path);
|
||||
|
||||
// Close the session and release the dongle.
|
||||
void xone_close(xone_dongle *d);
|
||||
|
||||
// Product ID of the connected dongle (e.g. 0x02E6).
|
||||
uint16_t xone_pid(const xone_dongle *d);
|
||||
|
||||
// Chip ID from EFUSE (e.g. 0x7612 for MT7612). 0 if unknown.
|
||||
uint16_t xone_chip_id(const xone_dongle *d);
|
||||
|
||||
// MAC address from EFUSE as "xx:xx:xx:xx:xx:xx". Valid until xone_close().
|
||||
const char *xone_mac_address(const xone_dongle *d);
|
||||
|
||||
// Build string of the loaded firmware image (e.g. "201703281033____").
|
||||
// Empty string if the firmware was not loaded. Valid until xone_close().
|
||||
const char *xone_firmware_build(const xone_dongle *d);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -45,6 +45,9 @@ public:
|
||||
// Resets the MCU if firmware is already loaded. Returns 0 or -errno.
|
||||
auto load_firmware(char const *path) -> int;
|
||||
|
||||
// Build string of the loaded firmware image (empty until load_firmware).
|
||||
auto firmware_build() const -> char const *;
|
||||
|
||||
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.
|
||||
@@ -59,6 +62,7 @@ private:
|
||||
auto reset_firmware() -> int;
|
||||
|
||||
usb::transport &transport_;
|
||||
char build_time_[17] = {};
|
||||
};
|
||||
|
||||
} // namespace xone::mt76
|
||||
|
||||
@@ -2,12 +2,32 @@
|
||||
|
||||
#include "app/xone_api.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
#include "common/log.hpp"
|
||||
#include "mt76/mt76.hpp"
|
||||
#include "usb/usb_transport.hpp"
|
||||
|
||||
#ifndef XONE_VERSION
|
||||
#define XONE_VERSION "0.1.0"
|
||||
#endif
|
||||
|
||||
// Definition of the opaque struct xone_dongle from app/xone_api.h.
|
||||
struct xone_dongle {
|
||||
std::unique_ptr<xone::usb::transport> transport;
|
||||
std::unique_ptr<xone::mt76::chip> chip;
|
||||
std::uint16_t chip_id = 0;
|
||||
char mac_address[18] = {};
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
// Single dongle session (the app only ever has one).
|
||||
std::unique_ptr<xone_dongle> current_session;
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" const char *xone_version(void)
|
||||
{
|
||||
return XONE_VERSION;
|
||||
@@ -17,3 +37,58 @@ extern "C" bool xone_dongle_present(void)
|
||||
{
|
||||
return xone::usb::dongle_present();
|
||||
}
|
||||
|
||||
extern "C" xone_dongle *xone_open(const char *firmware_path)
|
||||
{
|
||||
if (current_session)
|
||||
return nullptr;
|
||||
|
||||
auto s = std::make_unique<xone_dongle>();
|
||||
s->transport = xone::usb::transport::probe(nullptr, nullptr);
|
||||
if (!s->transport)
|
||||
return nullptr;
|
||||
|
||||
s->chip = std::make_unique<xone::mt76::chip>(*s->transport);
|
||||
|
||||
// EFUSE reads work before and after the firmware load.
|
||||
s->chip_id = s->chip->chip_id();
|
||||
auto mac = s->chip->mac_address();
|
||||
std::snprintf(s->mac_address, sizeof(s->mac_address), "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
|
||||
if (firmware_path) {
|
||||
int ret = s->chip->load_firmware(firmware_path);
|
||||
if (ret != 0)
|
||||
xone::log_msg(xone::log_level::warn, "api: firmware load failed (%d)", ret);
|
||||
}
|
||||
|
||||
current_session = std::move(s);
|
||||
return current_session.get();
|
||||
}
|
||||
|
||||
extern "C" void xone_close(xone_dongle *d)
|
||||
{
|
||||
if (d != current_session.get())
|
||||
return;
|
||||
current_session.reset();
|
||||
}
|
||||
|
||||
extern "C" std::uint16_t xone_pid(const xone_dongle *d)
|
||||
{
|
||||
return d->transport->pid();
|
||||
}
|
||||
|
||||
extern "C" std::uint16_t xone_chip_id(const xone_dongle *d)
|
||||
{
|
||||
return d->chip_id;
|
||||
}
|
||||
|
||||
extern "C" const char *xone_mac_address(const xone_dongle *d)
|
||||
{
|
||||
return d->mac_address;
|
||||
}
|
||||
|
||||
extern "C" const char *xone_firmware_build(const xone_dongle *d)
|
||||
{
|
||||
return d->chip->firmware_build();
|
||||
}
|
||||
|
||||
+39
-1
@@ -26,6 +26,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var donglePresent = false
|
||||
@State private var session: OpaquePointer? = nil
|
||||
|
||||
private let timer = Timer.publish(every: 1.0, on: .main, in: .common)
|
||||
.autoconnect()
|
||||
@@ -42,6 +43,10 @@ struct ContentView: View {
|
||||
|
||||
statusView
|
||||
|
||||
if let session {
|
||||
debugView(session)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
controllersView
|
||||
@@ -62,8 +67,41 @@ struct ContentView: View {
|
||||
.frame(width: 420)
|
||||
.frame(minHeight: 320)
|
||||
.onReceive(timer) { _ in
|
||||
donglePresent = xone_dongle_present()
|
||||
let present = xone_dongle_present()
|
||||
if present && session == nil {
|
||||
session = xone_open("firmware/xow_dongle.bin")
|
||||
} else if !present, let s = session {
|
||||
xone_close(s)
|
||||
session = nil
|
||||
}
|
||||
donglePresent = present
|
||||
}
|
||||
}
|
||||
|
||||
private func debugView(_ session: OpaquePointer) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Debug")
|
||||
.font(.headline)
|
||||
debugRow("PID", String(format: "0x%04X", xone_pid(session)))
|
||||
debugRow("Chip ID", String(format: "0x%04X", xone_chip_id(session)))
|
||||
debugRow("MAC", String(cString: xone_mac_address(session)))
|
||||
debugRow("Firmware", firmwareBuildText(session))
|
||||
}
|
||||
}
|
||||
|
||||
private func debugRow(_ label: String, _ value: String) -> some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Text(value)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
}
|
||||
}
|
||||
|
||||
private func firmwareBuildText(_ session: OpaquePointer) -> String {
|
||||
let build = String(cString: xone_firmware_build(session))
|
||||
return build.isEmpty ? "not loaded" : build
|
||||
}
|
||||
|
||||
private var statusView: some View {
|
||||
|
||||
+7
-3
@@ -212,9 +212,8 @@ auto chip::send_firmware(void const *fw_data, std::size_t fw_size) -> int
|
||||
if (fw_size != sizeof(fw_header) + ilm_len + dlm_len)
|
||||
return -EINVAL;
|
||||
|
||||
char build_time[17] = {};
|
||||
std::memcpy(build_time, header->build_time, sizeof(header->build_time));
|
||||
xone::log_msg(log_level::info, "mt76: firmware build %s", build_time);
|
||||
std::memcpy(build_time_, header->build_time, sizeof(header->build_time));
|
||||
xone::log_msg(log_level::info, "mt76: firmware build %s", build_time_);
|
||||
|
||||
// Configure the DMA, enable FCE and packet DMA.
|
||||
write_register(mt_usb_u3dma_cfg | mt_vend_type_cfg,
|
||||
@@ -249,6 +248,11 @@ auto chip::reset_firmware() -> int
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto chip::firmware_build() const -> char const *
|
||||
{
|
||||
return build_time_;
|
||||
}
|
||||
|
||||
auto chip::load_firmware(char const *path) -> int
|
||||
{
|
||||
// If firmware is already loaded, reset the MCU.
|
||||
|
||||
Reference in New Issue
Block a user