feat: intel wi-fi (AX211) driver with network scanning

This commit is contained in:
2026-08-06 08:36:10 +02:00
parent 75d92d560c
commit a01e63c717
21 changed files with 5649 additions and 4 deletions
+46
View File
@@ -219,6 +219,12 @@ namespace montauk::abi {
static constexpr uint64_t SYS_DISPLAYSETMODE = 156;
static constexpr uint64_t SYS_DISPLAYBRIGHTNESS = 157;
// Wi-Fi adapter control
static constexpr uint64_t SYS_WIFI_SCAN = 158; // (WifiNetwork*, maxCount, timeoutMs) -> count
static constexpr uint64_t SYS_WIFI_INFO = 159; // (WifiInfo*) -> 0, -1 if absent
static constexpr uint64_t SYS_WIFI_CONNECT = 160; // (ssid, password) -> 0, <0 on error
static constexpr uint64_t SYS_WIFI_DISCONNECT = 161; // () -> 0
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
@@ -564,6 +570,46 @@ namespace montauk::abi {
uint32_t _pad2;
};
// Wi-Fi security suites reported in WifiNetwork.security.
static constexpr uint8_t WIFI_SEC_OPEN = 0;
static constexpr uint8_t WIFI_SEC_WEP = 1;
static constexpr uint8_t WIFI_SEC_WPA = 2;
static constexpr uint8_t WIFI_SEC_WPA2 = 3;
static constexpr uint8_t WIFI_SEC_WPA3 = 4;
// Adapter states reported in WifiInfo.state.
static constexpr uint8_t WIFI_STATE_ABSENT = 0; // no device
static constexpr uint8_t WIFI_STATE_DETECTED = 1; // waiting for firmware load
static constexpr uint8_t WIFI_STATE_BOOTING = 2;
static constexpr uint8_t WIFI_STATE_RUNNING = 3;
static constexpr uint8_t WIFI_STATE_ERROR = 4;
static constexpr uint8_t WIFI_STATE_RFKILL = 5; // radio disabled in hardware
// One scanned network (returned by SYS_WIFI_SCAN).
struct WifiNetwork {
char ssid[36]; // NUL-terminated; empty for hidden networks
uint8_t bssid[6];
uint8_t channel;
int8_t rssi; // dBm
uint8_t band; // 0 = 2.4 GHz, 1 = 5 GHz
uint8_t security; // WIFI_SEC_*
uint16_t beaconInterval; // TU
};
// Adapter status (returned by SYS_WIFI_INFO).
struct WifiInfo {
uint8_t mac[6];
uint8_t present; // 1 if a supported device was found
uint8_t state; // WIFI_STATE_*
uint8_t scanning;
uint8_t bands; // bit0 = 2.4 GHz, bit1 = 5 GHz
uint16_t channels; // usable channels after regulatory filtering
char fwVersion[32];
uint64_t rxPackets;
uint32_t fwErrors;
uint32_t connState; // 0 idle, >0 connection setup in progress
};
struct ThermalInfo {
char name[32]; // short zone name (e.g. "THRM", "TZ00")
int32_t temperature; // tenths of degrees Celsius, or -1 if unavailable
+4
View File
@@ -181,6 +181,10 @@ extern "C" {
#define MTK_SYS_DISPLAYMODES 155
#define MTK_SYS_DISPLAYSETMODE 156
#define MTK_SYS_DISPLAYBRIGHTNESS 157
#define MTK_SYS_WIFI_SCAN 158
#define MTK_SYS_WIFI_INFO 159
#define MTK_SYS_WIFI_CONNECT 160
#define MTK_SYS_WIFI_DISCONNECT 161
/* @SYSCALLS-END */
#define MTK_SOCK_TCP 1
+21
View File
@@ -596,6 +596,27 @@ namespace montauk {
return (int)syscall1(montauk::abi::SYS_BTINFO, (uint64_t)buf);
}
// Wi-Fi. scan() runs a full channel sweep and blocks until it finishes or
// timeoutMs elapses, then fills buf with the networks seen; it returns the
// number of entries written, or -1 when no adapter is ready.
inline int wifi_scan(montauk::abi::WifiNetwork* buf, int maxCount,
uint32_t timeoutMs) {
return (int)syscall3(montauk::abi::SYS_WIFI_SCAN, (uint64_t)buf,
(uint64_t)maxCount, (uint64_t)timeoutMs);
}
inline int wifi_info(montauk::abi::WifiInfo* out) {
return (int)syscall1(montauk::abi::SYS_WIFI_INFO, (uint64_t)out);
}
// Association is groundwork only: open networks bring the firmware
// contexts up, encrypted ones are rejected with -2.
inline int wifi_connect(const char* ssid, const char* password) {
return (int)syscall2(montauk::abi::SYS_WIFI_CONNECT, (uint64_t)ssid,
(uint64_t)password);
}
inline int wifi_disconnect() {
return (int)syscall0(montauk::abi::SYS_WIFI_DISCONNECT);
}
// Software-defined radio (Rx). Receivers are identified by index [0, count);
// open() returns a handle used by the rest of the calls. Samples are read
// as interleaved 8-bit unsigned I/Q (CU8) from the device's ring buffer.
+74
View File
@@ -0,0 +1,74 @@
.TH WIFI 1
.SH NAME
wifi - scan for wireless networks and inspect the Wi-Fi adapter
.SH SYNOPSIS
wifi [scan [seconds]]
wifi info
wifi debug
wifi connect <ssid> [passphrase]
wifi disconnect
.SH DESCRIPTION
Drives the Intel Wi-Fi adapter. With no arguments, runs a five
second scan and lists the networks found, strongest signal first.
The driver supports Intel AX210-family devices whose RF type is
GF (marketed as AX211), which is what the bundled firmware image
covers. Other Intel wireless parts are left unclaimed.
.SH COMMANDS
scan [seconds]
Sweep every regulatory-permitted channel and report the
networks seen. The duration is clamped to 1-20 seconds and
defaults to 5.
info
Adapter status: MAC address, firmware version, supported
bands, usable channel count and whether a scan is running.
debug
Everything info prints, plus RX/error counters, a scan with
per-BSS detail (BSSID, channel, RSSI, security) and band and
security tallies. Use this when a scan finds nothing.
connect <ssid> [passphrase]
Brings the firmware contexts (PHY, MAC, binding, station) up
for a network from the last scan. The 802.11 authentication
and association exchange is not implemented, so this does not
produce a usable link; encrypted networks are refused outright.
disconnect
Tears those contexts back down.
.SH OUTPUT
SSID SIGNAL RSSI CH BAND SECURITY
-------------------------------------------------------------------
homenet [####] -42 6 2.4GHz WPA2
homenet-5g [###.] -61 44 5GHz WPA3
guest [##..] -74 11 2.4GHz open
.SH FIRMWARE
The adapter needs firmware from the ramdisk:
0:/os/firmware/intel/iwlwifi-so-a0-gf-a0-<api>.ucode
0:/os/firmware/intel/iwlwifi-so-a0-gf-a0.pnvm
Loading happens shortly after boot, off the boot-critical path,
so "wifi info" may briefly report "detected (firmware not loaded
yet)". The PNVM file carries regulatory data; without it the
firmware falls back to conservative built-in limits.
.SH DIAGNOSTICS
no supported Wi-Fi adapter found
No matching device, or its RF type is not GF.
radio disabled (RF-kill)
The hardware kill switch is off. No scanning is possible.
error
Firmware load or configuration failed; klog(1) has the
detail under the WiFi and WiFi-FW tags.
.SH SEE ALSO
ifconfig(1), klog(1), dhcp(1), syscalls(2)
+387
View File
@@ -0,0 +1,387 @@
/*
* main.cpp
* wifi - Wi-Fi scanning and adapter diagnostics.
*
* Usage:
* wifi scan and list nearby networks
* wifi scan [seconds] scan for a specific duration (default 5, max 20)
* wifi info adapter, firmware and radio status
* wifi debug info plus raw counters, repeated scan detail
* wifi connect <ssid> bring up the firmware contexts for an open network
* wifi disconnect tear those contexts back down
*
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/syscall.h>
#include <montauk/string.h>
using namespace montauk;
// ---------------------------------------------------------------------------
// Formatting helpers (freestanding; no libc printf here)
// ---------------------------------------------------------------------------
static void put_u64(uint64_t n) {
char buf[24];
int i = 0;
if (n == 0) { putchar('0'); return; }
while (n) { buf[i++] = (char)('0' + (n % 10)); n /= 10; }
while (i) putchar(buf[--i]);
}
static void put_i64(int64_t n) {
if (n < 0) { putchar('-'); n = -n; }
put_u64((uint64_t)n);
}
// Right-align an unsigned value in a field of `width` characters.
static void put_u64_pad(uint64_t n, int width) {
int digits = 1;
for (uint64_t t = n; t >= 10; t /= 10) digits++;
for (int i = digits; i < width; i++) putchar(' ');
put_u64(n);
}
static void put_hex2(uint8_t v) {
const char* hex = "0123456789abcdef";
putchar(hex[(v >> 4) & 0xF]);
putchar(hex[v & 0xF]);
}
static void put_mac(const uint8_t* m) {
for (int i = 0; i < 6; i++) {
if (i) putchar(':');
put_hex2(m[i]);
}
}
static void put_padded_str(const char* s, int width) {
int n = 0;
while (s[n] && n < width) { putchar(s[n]); n++; }
for (; n < width; n++) putchar(' ');
}
static const char* security_name(uint8_t sec) {
switch (sec) {
case abi::WIFI_SEC_OPEN: return "open";
case abi::WIFI_SEC_WEP: return "WEP";
case abi::WIFI_SEC_WPA: return "WPA";
case abi::WIFI_SEC_WPA2: return "WPA2";
case abi::WIFI_SEC_WPA3: return "WPA3";
default: return "?";
}
}
static const char* state_name(uint8_t state) {
switch (state) {
case abi::WIFI_STATE_ABSENT: return "no adapter";
case abi::WIFI_STATE_DETECTED: return "detected (firmware not loaded yet)";
case abi::WIFI_STATE_BOOTING: return "loading firmware";
case abi::WIFI_STATE_RUNNING: return "ready";
case abi::WIFI_STATE_ERROR: return "error";
case abi::WIFI_STATE_RFKILL: return "radio disabled (RF-kill)";
default: return "unknown";
}
}
// A rough signal-quality bar from the RSSI: -50 dBm and up is excellent,
// -90 dBm and below is unusable.
static void put_signal_bar(int8_t rssi) {
int bars = 0;
if (rssi >= -55) bars = 4;
else if (rssi >= -67) bars = 3;
else if (rssi >= -78) bars = 2;
else if (rssi >= -88) bars = 1;
putchar('[');
for (int i = 0; i < 4; i++) putchar(i < bars ? '#' : '.');
putchar(']');
}
// ---------------------------------------------------------------------------
// Argument helpers
// ---------------------------------------------------------------------------
static uint64_t parse_u64(const char* s) {
uint64_t v = 0;
while (*s >= '0' && *s <= '9') { v = v * 10 + (uint64_t)(*s - '0'); s++; }
return v;
}
static bool next_token(const char** rest, char* tok, int cap) {
const char* s = skip_spaces(*rest);
if (*s == '\0') { *rest = s; return false; }
int i = 0;
while (*s && *s != ' ' && i < cap - 1) tok[i++] = *s++;
tok[i] = '\0';
*rest = s;
return true;
}
// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------
static abi::WifiNetwork g_nets[64];
static bool require_adapter(abi::WifiInfo& info) {
if (wifi_info(&info) != 0 || !info.present) {
print("wifi: no supported Wi-Fi adapter found.\n");
print(" Only Intel AX210-family devices with an RF type of GF "
"(AX211) are supported.\n");
return false;
}
if (info.state != abi::WIFI_STATE_RUNNING) {
print("wifi: adapter is not ready ("); print(state_name(info.state));
print(").\n");
if (info.state == abi::WIFI_STATE_DETECTED)
print(" Firmware loads shortly after boot; try again in a moment.\n");
else if (info.state == abi::WIFI_STATE_ERROR)
print(" Check the kernel log (klog) for firmware errors.\n");
return false;
}
return true;
}
static void print_info(const abi::WifiInfo& info, bool verbose) {
print("Wi-Fi adapter\n");
print(" status : "); print(state_name(info.state)); print("\n");
print(" MAC : "); put_mac(info.mac); print("\n");
print(" firmware : ");
print(info.fwVersion[0] ? info.fwVersion : "(not loaded)");
print("\n");
print(" bands : ");
if (info.bands & 1) print("2.4GHz ");
if (info.bands & 2) print("5GHz");
if (!info.bands) print("(unknown)");
print("\n");
print(" channels : "); put_u64(info.channels);
print(" usable after regulatory filtering\n");
print(" scanning : "); print(info.scanning ? "yes" : "no"); print("\n");
if (verbose) {
print(" rx packets : "); put_u64(info.rxPackets); print("\n");
print(" fw errors : "); put_u64(info.fwErrors); print("\n");
print(" conn state : "); put_u64(info.connState);
print(" (0 = idle)\n");
}
}
static int cmd_scan(uint32_t seconds, bool verbose) {
abi::WifiInfo info;
if (!require_adapter(info)) return 1;
uint32_t timeoutMs = seconds * 1000;
if (timeoutMs < 1000) timeoutMs = 5000;
if (timeoutMs > 20000) timeoutMs = 20000;
print("Scanning for up to ");
put_u64(timeoutMs / 1000);
print(" seconds...\n");
int n = wifi_scan(g_nets, 64, timeoutMs);
if (n < 0) {
print("wifi: scan failed.\n");
return 1;
}
if (n == 0) {
print("No networks found.\n");
return 0;
}
// Strongest signal first (small n, so a simple selection sort is fine).
for (int i = 0; i < n - 1; i++) {
int best = i;
for (int j = i + 1; j < n; j++)
if (g_nets[j].rssi > g_nets[best].rssi) best = j;
if (best != i) {
abi::WifiNetwork tmp = g_nets[i];
g_nets[i] = g_nets[best];
g_nets[best] = tmp;
}
}
print("\n");
print("SSID SIGNAL RSSI CH BAND SECURITY\n");
print("-------------------------------------------------------------------\n");
for (int i = 0; i < n; i++) {
const abi::WifiNetwork& w = g_nets[i];
put_padded_str(w.ssid[0] ? w.ssid : "(hidden)", 32);
putchar(' ');
put_signal_bar(w.rssi);
print(" ");
put_i64(w.rssi);
print(w.rssi > -100 ? " " : " ");
put_u64_pad(w.channel, 3);
print(" ");
print(w.band ? "5GHz " : "2.4GHz");
print(" ");
print(security_name(w.security));
print("\n");
if (verbose) {
print(" bssid "); put_mac(w.bssid);
print(" beacon interval "); put_u64(w.beaconInterval);
print(" TU\n");
}
}
print("\n");
put_u64((uint64_t)n);
print(n == 1 ? " network found.\n" : " networks found.\n");
return 0;
}
static int cmd_info(bool verbose) {
abi::WifiInfo info;
if (wifi_info(&info) != 0 && !info.present) {
print("wifi: no supported Wi-Fi adapter found.\n");
return 1;
}
print_info(info, verbose);
return 0;
}
static int cmd_debug() {
abi::WifiInfo info;
if (wifi_info(&info) != 0 && !info.present) {
print("wifi: no supported Wi-Fi adapter found.\n");
return 1;
}
print("=== Wi-Fi diagnostics ===\n\n");
print_info(info, true);
if (info.state != abi::WIFI_STATE_RUNNING) {
print("\nAdapter is not ready; skipping the scan test.\n");
print("Kernel-side detail is in the klog (look for the WiFi/WiFi-FW tags).\n");
return 0;
}
print("\nRunning a 5 second scan and reporting per-BSS detail...\n");
int n = wifi_scan(g_nets, 64, 5000);
if (n < 0) {
print("scan failed\n");
return 1;
}
int open = 0, wpa2 = 0, wpa3 = 0, other = 0, band24 = 0, band5 = 0;
for (int i = 0; i < n; i++) {
switch (g_nets[i].security) {
case abi::WIFI_SEC_OPEN: open++; break;
case abi::WIFI_SEC_WPA2: wpa2++; break;
case abi::WIFI_SEC_WPA3: wpa3++; break;
default: other++; break;
}
if (g_nets[i].band) band5++; else band24++;
}
print("\nBSS count : "); put_u64((uint64_t)n); print("\n");
print(" 2.4 GHz : "); put_u64((uint64_t)band24); print("\n");
print(" 5 GHz : "); put_u64((uint64_t)band5); print("\n");
print(" open : "); put_u64((uint64_t)open); print("\n");
print(" WPA2 : "); put_u64((uint64_t)wpa2); print("\n");
print(" WPA3 : "); put_u64((uint64_t)wpa3); print("\n");
print(" other : "); put_u64((uint64_t)other); print("\n");
if (n > 0) {
print("\nPer-BSS detail:\n");
for (int i = 0; i < n; i++) {
const abi::WifiNetwork& w = g_nets[i];
print(" "); put_mac(w.bssid);
print(" ch "); put_u64_pad(w.channel, 3);
print(" "); put_i64(w.rssi); print(" dBm ");
print(security_name(w.security));
print(" ");
print(w.ssid[0] ? w.ssid : "(hidden)");
print("\n");
}
}
// Re-read the counters so the caller can see whether frames actually moved.
abi::WifiInfo after;
if (wifi_info(&after) == 0) {
print("\nRX packets: "); put_u64(info.rxPackets);
print(" -> "); put_u64(after.rxPackets);
print(" firmware errors: "); put_u64(after.fwErrors);
print("\n");
}
return 0;
}
static int cmd_connect(const char* ssid, const char* password) {
abi::WifiInfo info;
if (!require_adapter(info)) return 1;
print("Connecting to \""); print(ssid); print("\"...\n");
int rc = wifi_connect(ssid, password);
if (rc == 0) {
print("Firmware contexts are up.\n");
print("NOTE: the 802.11 authentication exchange is not implemented yet,\n");
print(" so this does not establish a usable link.\n");
return 0;
}
if (rc == -2) {
print("wifi: \""); print(ssid); print("\" is encrypted (WPA2/WPA3).\n");
print(" Only open networks can be joined so far - the key exchange\n");
print(" is not implemented yet. Scanning is unaffected.\n");
return 1;
}
print("wifi: \""); print(ssid); print("\" was not found in the last scan.\n");
print(" Run \"wifi scan\" first, and check the SSID spelling.\n");
return 1;
}
static void usage() {
print("usage: wifi [command]\n\n");
print(" scan [seconds] scan and list nearby networks (default 5)\n");
print(" info adapter, firmware and radio status\n");
print(" debug diagnostics plus per-BSS scan detail\n");
print(" connect <ssid> bring up firmware contexts (open networks)\n");
print(" disconnect tear those contexts down\n\n");
print("With no command, runs a 5 second scan.\n");
}
extern "C" void _start() {
char args[192];
int alen = getargs(args, sizeof(args));
const char* rest = (alen > 0) ? args : "";
char cmd[32];
if (!next_token(&rest, cmd, sizeof(cmd))) {
exit(cmd_scan(5, false));
}
if (streq(cmd, "scan")) {
char tok[16];
uint32_t secs = 5;
if (next_token(&rest, tok, sizeof(tok))) secs = (uint32_t)parse_u64(tok);
exit(cmd_scan(secs, false));
} else if (streq(cmd, "info")) {
exit(cmd_info(false));
} else if (streq(cmd, "debug")) {
exit(cmd_debug());
} else if (streq(cmd, "connect")) {
char ssid[40], pass[80];
if (!next_token(&rest, ssid, sizeof(ssid))) {
print("wifi: connect needs an SSID\n");
exit(1);
}
pass[0] = '\0';
next_token(&rest, pass, sizeof(pass));
exit(cmd_connect(ssid, pass));
} else if (streq(cmd, "disconnect")) {
wifi_disconnect();
print("Disconnected.\n");
exit(0);
} else if (streq(cmd, "help") || streq(cmd, "-h") || streq(cmd, "--help")) {
usage();
exit(0);
}
print("wifi: unknown command \""); print(cmd); print("\"\n\n");
usage();
exit(1);
}