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
This commit is contained in:
+1
-1
@@ -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.4 LANGUAGES CXX Swift)
|
project(xone_macos VERSION 0.1.5 LANGUAGES CXX Swift)
|
||||||
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
|||||||
+31
-3
@@ -9,12 +9,40 @@
|
|||||||
// association and WCID/AES-CCMP key setup (encryption runs on-chip).
|
// association and WCID/AES-CCMP key setup (encryption runs on-chip).
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace xone::usb {
|
||||||
|
class transport;
|
||||||
|
}
|
||||||
|
|
||||||
namespace xone::mt76 {
|
namespace xone::mt76 {
|
||||||
|
|
||||||
// Load the firmware image for the given dongle PID into the chip.
|
// Owns the MT76 chip protocol on top of an open USB transport. Port of
|
||||||
// TODO(phase 3): port from xone_mt76_load_firmware().
|
// struct xone_mt76 plus the register/EFUSE layer of transport/mt76.c.
|
||||||
auto load_firmware(std::uint16_t pid, char const *firmware_path) -> bool;
|
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<std::uint8_t, 6>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
usb::transport &transport_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace xone::mt76
|
} // namespace xone::mt76
|
||||||
|
|||||||
@@ -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 <cstdint>
|
||||||
|
|
||||||
|
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
|
||||||
+114
-5
@@ -1,16 +1,125 @@
|
|||||||
// MT76 chip protocol (MediaTek MT76xx radio in the dongle)
|
// 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 "mt76/mt76.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "common/log.hpp"
|
||||||
|
#include "common/types.hpp"
|
||||||
|
#include "mt76/mt76_defs.hpp"
|
||||||
|
#include "usb/usb_transport.hpp"
|
||||||
|
|
||||||
namespace xone::mt76 {
|
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;
|
auto req = usb::vendor_request::multi_read;
|
||||||
(void)firmware_path;
|
if (addr & mt_vend_type_cfg) {
|
||||||
// Not implemented yet: no USB transport in place.
|
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<std::uint16_t>(addr >> 16),
|
||||||
|
static_cast<std::uint16_t>(addr & 0xFFFF),
|
||||||
|
buf, sizeof(buf));
|
||||||
|
if (ret != static_cast<int>(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<std::uint16_t>(addr >> 16),
|
||||||
|
static_cast<std::uint16_t>(addr & 0xFFFF),
|
||||||
|
buf, sizeof(buf));
|
||||||
|
if (ret != static_cast<int>(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;
|
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<std::uint32_t>(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<std::uint16_t>((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<char *>(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<std::uint16_t>((id[1] << 8) | id[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto chip::mac_address() -> std::array<std::uint8_t, 6>
|
||||||
|
{
|
||||||
|
auto address = std::array<std::uint8_t, 6>{};
|
||||||
|
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
|
} // namespace xone::mt76
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ target_compile_features(test_usb PRIVATE cxx_std_23)
|
|||||||
add_test(NAME test_usb COMMAND test_usb)
|
add_test(NAME test_usb COMMAND test_usb)
|
||||||
list(APPEND TEST_TARGETS 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)
|
add_executable(test_version test_version.cpp)
|
||||||
target_include_directories(test_version PRIVATE "${CMAKE_SOURCE_DIR}/include")
|
target_include_directories(test_version PRIVATE "${CMAKE_SOURCE_DIR}/include")
|
||||||
target_link_libraries(test_version PRIVATE xone_api Unity::Unity)
|
target_link_libraries(test_version PRIVATE xone_api Unity::Unity)
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user