From ccc5bc819d60add94521f3b9e54babd8bcd5ac89 Mon Sep 17 00:00:00 2001 From: portersky Date: Mon, 17 Aug 2026 16:15:56 +0200 Subject: [PATCH] feat: port MT76 register and EFUSE access Add the mt76::chip class on top of the USB transport: 32-bit register read/write via vendor requests, register polling, and EFUSE reads with the kick/control sequence from transport/mt76.c. Expose chip_id() and mac_address() (with the 62:45:bd fallback). Verified on hardware: chip ID 0x7612 (MT7612) and MAC address read from EFUSE. Co-Authored-By: qwen3.8-27b@q2_k_xl: ported register and EFUSE layer --- CMakeLists.txt | 2 +- include/mt76/mt76.hpp | 34 ++++++++++- include/mt76/mt76_defs.hpp | 57 ++++++++++++++++++ src/mt76/mt76.cpp | 119 +++++++++++++++++++++++++++++++++++-- tests/CMakeLists.txt | 7 +++ tests/test_mt76.cpp | 41 +++++++++++++ 6 files changed, 251 insertions(+), 9 deletions(-) create mode 100644 include/mt76/mt76_defs.hpp create mode 100644 tests/test_mt76.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8afdf32..2a2b621 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT CMAKE_GENERATOR MATCHES "^(Ninja|Xcode)$") endif() cmake_minimum_required(VERSION 3.21) -project(xone_macos VERSION 0.1.4 LANGUAGES CXX Swift) +project(xone_macos VERSION 0.1.5 LANGUAGES CXX Swift) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/include/mt76/mt76.hpp b/include/mt76/mt76.hpp index 4fc3be7..74c003b 100644 --- a/include/mt76/mt76.hpp +++ b/include/mt76/mt76.hpp @@ -9,12 +9,40 @@ // association and WCID/AES-CCMP key setup (encryption runs on-chip). // ============================================================================== +#include +#include #include +namespace xone::usb { +class transport; +} + namespace xone::mt76 { -// Load the firmware image for the given dongle PID into the chip. -// TODO(phase 3): port from xone_mt76_load_firmware(). -auto load_firmware(std::uint16_t pid, char const *firmware_path) -> bool; +// Owns the MT76 chip protocol on top of an open USB transport. Port of +// struct xone_mt76 plus the register/EFUSE layer of transport/mt76.c. +class chip { +public: + explicit chip(usb::transport &transport); + + // 32-bit register read/write (addr may include mt_vend_type_cfg). + auto read_register(std::uint32_t addr) -> std::uint32_t; + auto write_register(std::uint32_t addr, std::uint32_t val) -> void; + + // Poll until (read_register(offset) & mask) == val. False on timeout. + auto poll(std::uint32_t offset, std::uint32_t mask, std::uint32_t val) -> bool; + + // Read EFUSE bytes at the 16-bit block address. Returns 0 or -errno. + auto read_efuse(std::uint16_t addr, void *data, std::size_t len) -> int; + + // Chip ID from EFUSE (e.g. MT7612). 0 on failure. + auto chip_id() -> std::uint16_t; + + // MAC address from EFUSE with the 62:45:bd fallback applied. + auto mac_address() -> std::array; + +private: + usb::transport &transport_; +}; } // namespace xone::mt76 diff --git a/include/mt76/mt76_defs.hpp b/include/mt76/mt76_defs.hpp new file mode 100644 index 0000000..2302fd5 --- /dev/null +++ b/include/mt76/mt76_defs.hpp @@ -0,0 +1,57 @@ +#pragma once + +// ============================================================================== +// MT76 chip register definitions (port of transport/mt76_defs.h) +// ============================================================================== +// Only the registers needed so far are ported; the rest land with the radio +// init and firmware load increments. +// ============================================================================== + +#include + +namespace xone::mt76 { + +// Bitfield helpers (port of the kernel BIT/GENMASK/FIELD_PREP macros). +inline constexpr auto bit(std::uint32_t n) -> std::uint32_t +{ + return 1u << n; +} + +inline constexpr auto genmask(std::uint32_t hi, std::uint32_t lo) -> std::uint32_t +{ + return (~0u << lo) & (~0u >> (31 - hi)); +} + +inline constexpr auto field_prep(std::uint32_t mask, std::uint32_t val) -> std::uint32_t +{ + return (val << __builtin_ctz(mask)) & mask; +} + +// Register address flag: use the config-space vendor request codes. +constexpr std::uint32_t mt_vend_type_cfg = bit(30); + +// EFUSE control register. +constexpr std::uint32_t mt_efuse_ctrl = 0x0024; +constexpr std::uint32_t mt_efuse_ctrl_aout = genmask(5, 0); +constexpr std::uint32_t mt_efuse_ctrl_mode = genmask(7, 6); +constexpr std::uint32_t mt_efuse_ctrl_ldo_off_time = genmask(13, 8); +constexpr std::uint32_t mt_efuse_ctrl_ldo_on_time = genmask(15, 14); +constexpr std::uint32_t mt_efuse_ctrl_ain = genmask(25, 16); +constexpr std::uint32_t mt_efuse_ctrl_kick = bit(30); +constexpr std::uint32_t mt_efuse_ctrl_sel = bit(31); + +// EFUSE data registers (one per 32-bit word of the 16-byte block). +constexpr std::uint32_t mt_efuse_data_base = 0x0028; + +// EFUSE block addresses. +constexpr std::uint16_t mt_ee_chip_id = 0x0000; +constexpr std::uint16_t mt_ee_version = 0x0002; +constexpr std::uint16_t mt_ee_mac_addr = 0x0004; + +// EFUSE read modes (MT_EFUSE_CTRL_MODE field). +enum efuse_mode : std::uint32_t { + efuse_read = 0, + efuse_physical_read, +}; + +} // namespace xone::mt76 diff --git a/src/mt76/mt76.cpp b/src/mt76/mt76.cpp index aee1e8f..e9b057d 100644 --- a/src/mt76/mt76.cpp +++ b/src/mt76/mt76.cpp @@ -1,16 +1,125 @@ // MT76 chip protocol (MediaTek MT76xx radio in the dongle) -// TODO(phase 3): port from medusalix/xone transport/mt76.c. +// Port of medusalix/xone transport/mt76.c (register + EFUSE layer). #include "mt76/mt76.hpp" +#include +#include +#include + +#include + +#include "common/log.hpp" +#include "common/types.hpp" +#include "mt76/mt76_defs.hpp" +#include "usb/usb_transport.hpp" + namespace xone::mt76 { -auto load_firmware(std::uint16_t pid, char const *firmware_path) -> bool +// Poll retry count (port of XONE_MT_POLL_RETRIES). +constexpr int poll_retries = 50; + +chip::chip(usb::transport &transport) : transport_(transport) {} + +auto chip::read_register(std::uint32_t addr) -> std::uint32_t { - (void)pid; - (void)firmware_path; - // Not implemented yet: no USB transport in place. + auto req = usb::vendor_request::multi_read; + if (addr & mt_vend_type_cfg) { + req = usb::vendor_request::read_cfg; + addr &= ~mt_vend_type_cfg; + } + + std::uint8_t buf[4] = {}; + int ret = transport_.send_vendor_request(req, true, + static_cast(addr >> 16), + static_cast(addr & 0xFFFF), + buf, sizeof(buf)); + if (ret != static_cast(sizeof(buf))) { + xone::log_msg(log_level::error, "mt76: register read 0x%04x failed (%d)", addr, ret); + return 0; + } + + return xone::load_le32(buf); +} + +auto chip::write_register(std::uint32_t addr, std::uint32_t val) -> void +{ + auto req = usb::vendor_request::multi_write; + if (addr & mt_vend_type_cfg) { + req = usb::vendor_request::write_cfg; + addr &= ~mt_vend_type_cfg; + } + + std::uint8_t buf[4]; + xone::store_le32(buf, val); + int ret = transport_.send_vendor_request(req, false, + static_cast(addr >> 16), + static_cast(addr & 0xFFFF), + buf, sizeof(buf)); + if (ret != static_cast(sizeof(buf))) + xone::log_msg(log_level::error, "mt76: register write 0x%04x failed (%d)", addr, ret); +} + +auto chip::poll(std::uint32_t offset, std::uint32_t mask, std::uint32_t val) -> bool +{ + for (int i = 0; i < poll_retries; i++) { + auto reg = read_register(offset); + if ((reg & mask) == val) + return true; + usleep(10000); // upstream: usleep_range(10000, 20000) + } + return false; } +auto chip::read_efuse(std::uint16_t addr, void *data, std::size_t len) -> int +{ + auto ctrl = read_register(mt_efuse_ctrl); + ctrl &= ~(mt_efuse_ctrl_ain | mt_efuse_ctrl_mode); + ctrl |= mt_efuse_ctrl_kick; + ctrl |= field_prep(mt_efuse_ctrl_ain, static_cast(addr) & ~0x0Fu); + // efuse_read (mode 0) leaves the MODE bits cleared. + write_register(mt_efuse_ctrl, ctrl); + + if (!poll(mt_efuse_ctrl, mt_efuse_ctrl_kick, 0)) + return -ETIMEDOUT; + + for (std::size_t i = 0; i < len; i += sizeof(std::uint32_t)) { + // Block data offset (multiple of 32 bits) + auto offset = static_cast((addr & genmask(3, 2)) + i); + std::uint8_t buf[4]; + xone::store_le32(buf, read_register(mt_efuse_data_base + offset)); + std::memcpy(static_cast(data) + i, buf, + std::min(len - i, sizeof(std::uint32_t))); + } + + return 0; +} + +auto chip::chip_id() -> std::uint16_t +{ + std::uint8_t id[4] = {}; + if (read_efuse(mt_ee_chip_id, id, sizeof(id)) != 0) + return 0; + + return static_cast((id[1] << 8) | id[2]); +} + +auto chip::mac_address() -> std::array +{ + auto address = std::array{}; + if (read_efuse(mt_ee_mac_addr, address.data(), address.size()) != 0) + return address; + + // Some addresses start with 6c:5d:3a. + // Clients only connect to 62:45:bd:xx:xx:xx. + if (address[0] != 0x62) { + address[0] = 0x62; + address[1] = 0x45; + address[2] = 0xbd; + } + + return address; +} + } // namespace xone::mt76 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dd27bba..cd6dbcf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,13 @@ target_compile_features(test_usb PRIVATE cxx_std_23) add_test(NAME test_usb COMMAND test_usb) list(APPEND TEST_TARGETS test_usb) +add_executable(test_mt76 test_mt76.cpp) +target_include_directories(test_mt76 PRIVATE "${CMAKE_SOURCE_DIR}/include") +target_link_libraries(test_mt76 PRIVATE xone_mt76 Unity::Unity) +target_compile_features(test_mt76 PRIVATE cxx_std_23) +add_test(NAME test_mt76 COMMAND test_mt76) +list(APPEND TEST_TARGETS test_mt76) + add_executable(test_version test_version.cpp) target_include_directories(test_version PRIVATE "${CMAKE_SOURCE_DIR}/include") target_link_libraries(test_version PRIVATE xone_api Unity::Unity) diff --git a/tests/test_mt76.cpp b/tests/test_mt76.cpp new file mode 100644 index 0000000..0efbc3e --- /dev/null +++ b/tests/test_mt76.cpp @@ -0,0 +1,41 @@ +#include "unity.h" + +#include "mt76/mt76_defs.hpp" + +void setUp() {} +void tearDown() {} + +// Pin the bitfield helpers to kernel BIT/GENMASK/FIELD_PREP semantics. +void test_bitfield_helpers(void) +{ + TEST_ASSERT_EQUAL_UINT32(xone::mt76::bit(0), 0x1u); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::bit(30), 0x40000000u); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::genmask(5, 0), 0x3Fu); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::genmask(7, 6), 0xC0u); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::genmask(25, 16), 0x03FF0000u); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::genmask(31, 0), 0xFFFFFFFFu); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::field_prep(xone::mt76::genmask(7, 6), 1), 0x40u); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::field_prep(xone::mt76::genmask(25, 16), 0x01), + 0x00010000u); +} + +// Pin the EFUSE register layout to transport/mt76_defs.h values. +void test_efuse_registers(void) +{ + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_efuse_ctrl, 0x0024); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_efuse_ctrl_kick, 0x40000000u); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_efuse_ctrl_ain, 0x03FF0000u); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_efuse_data_base, 0x0028); + TEST_ASSERT_EQUAL_UINT16(xone::mt76::mt_ee_chip_id, 0x0000); + TEST_ASSERT_EQUAL_UINT16(xone::mt76::mt_ee_version, 0x0002); + TEST_ASSERT_EQUAL_UINT16(xone::mt76::mt_ee_mac_addr, 0x0004); + TEST_ASSERT_EQUAL_UINT32(xone::mt76::mt_vend_type_cfg, 0x40000000u); +} + +int main(void) +{ + UNITY_BEGIN(); + RUN_TEST(test_bitfield_helpers); + RUN_TEST(test_efuse_registers); + return UNITY_END(); +}