From 1e7a411a55f4cb7d806d82671702673a442b448f Mon Sep 17 00:00:00 2001 From: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> Date: Sun, 1 Nov 2026 10:46:37 -0500 Subject: [PATCH] wifi: apple: centauri: tell the firmware the host is awake before a join Without the host power state the Alpha firmware treats the host as asleep, powers the PHYs down and traps (PHY5G irq_isr.c:105, PHY2G power_manager.c:394, and in a log with a post-join frame also LMAC2G "PMGR - Assert while waiting"). Before the first join of a firmware boot, send command 6 with a zero body (host power state awake), command 43 (packet lifetime) and command 52 (control profile). The host_setup parameter (default on) turns this off. Measured on a Mac mini M6 (J873), WPA2-PSK, 5 GHz: the firmware stayed alive (stage 2) for the whole test, about 17 minutes connected; before, it trapped within 15 s in 14 of 14 variants. A 600 s iperf3 at 20 Mbit/s moved 1.40 GBytes without a stall. On a MacBook Pro M5 Max (J716c) the same firmware family trapped after hours instead of seconds: overnight, the Alpha stage left 2, a country command lost its transport, and Wi-Fi stayed down until reboot. This driver sends none of the three commands either. [Erik: ported from the J813/J873 N1 driver (linux-aurora 1242f12e876b) to the T6050 APIF driver. That driver also has tx_config debugfs files whose applied defaults this skips; this one has none, so each command is sent once per firmware boot and retried on the next join if it fails.] Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> Developed with Claude Code. Signed-off-by: Erik Bourget --- .../wireless/apple/centauri/centauri_wifi.c | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/drivers/net/wireless/apple/centauri/centauri_wifi.c b/drivers/net/wireless/apple/centauri/centauri_wifi.c index 386db2c7f64a6..6d96b6b42c6c1 100644 --- a/drivers/net/wireless/apple/centauri/centauri_wifi.c +++ b/drivers/net/wireless/apple/centauri/centauri_wifi.c @@ -30,6 +30,11 @@ static_assert(DATA_RX_BUFFER + DATA_RX_SLOTS * 4096 <= DATA_TX_BUFFER); static_assert(DATA_TX_BUFFER + DATA_TX_SLOTS * BUFFER_SIZE <= CEN_ALPHA_WINDOW_SIZE); +static bool host_setup = true; +module_param(host_setup, bool, 0644); +MODULE_PARM_DESC(host_setup, + "Before a join, tell the firmware the host is awake (command 6) and apply the reference TX defaults (commands 43 and 52); 1 = on (default)"); + struct centauri_wifi { struct centauri *control; struct cen_alpha_resources a; @@ -69,6 +74,9 @@ struct centauri_wifi { struct net_device_stats station_start; u8 country[3]; bool country_valid; + /* Sent once per firmware boot, before the first join (host_setup). */ + bool host_awake_sent; + u8 tx_defaults_applied; u32 data_rings_open; bool data_failed; struct ieee80211_supported_band bands[2]; @@ -1084,6 +1092,43 @@ static int wifi_connect(struct wiphy *wiphy, struct net_device *netdev, return ret; } +/* + * Without the host power state the firmware treats the host as asleep, powers + * the PHYs down and traps. Called with wifi->lock held. + */ +static void host_setup_commands(struct centauri_wifi *wifi) +{ + static const u8 awake[3] = {}; + static const u8 control_profile[] = { 0x54, 1, 10, 0, 0, 8, 0, 0, + 0xff, 0xff, 0xff, 0xff, 1, 1 }; + struct device *dev = &wifi->control->pdev->dev; + u8 lifetime[20] = {}; + int ret, i; + + if (!host_setup) + return; + if (!wifi->host_awake_sent) { + ret = command(wifi, 6, awake, sizeof(awake)); + dev_info(dev, "host power state awake: rc=%d status=%u\n", ret, + wifi->reply_status); + wifi->host_awake_sent = !ret; + } + if (!(wifi->tx_defaults_applied & BIT(0))) { + for (i = 0; i < 4; i++) + put_unaligned_le32(2000000, lifetime + 4 + 4 * i); + ret = command(wifi, 43, lifetime, sizeof(lifetime)); + if (!ret) + wifi->tx_defaults_applied |= BIT(0); + dev_info(dev, "TX packet lifetime defaults: rc=%d\n", ret); + } + if (!(wifi->tx_defaults_applied & BIT(1))) { + ret = command(wifi, 52, control_profile, sizeof(control_profile)); + if (!ret) + wifi->tx_defaults_applied |= BIT(1); + dev_info(dev, "TX control profile defaults: rc=%d\n", ret); + } +} + static void connect_worker(struct work_struct *work) { struct centauri_wifi *wifi = @@ -1098,6 +1143,7 @@ static void connect_worker(struct work_struct *work) mutex_unlock(&wifi->lock); return; } + host_setup_commands(wifi); slot = wifi->command_head; ret = command(wifi, 131072, wifi->connect_payload, wifi->connect_size); memzero_explicit(wifi->connect_payload, sizeof(wifi->connect_payload));