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:
@@ -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,10 +67,43 @@ 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 {
|
||||
HStack(spacing: 8) {
|
||||
Circle()
|
||||
|
||||
+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