From: "Kirill A. Korinsky" Subject: sys/iwx: support powersave To: OpenBSD tech Cc: stsp@openbsd.org Date: Sat, 31 Jan 2026 01:42:53 +0100 tech@, here a patch which introduces support of powersave in a way like it implemented in iwn and wpi. I run it for quite some time on: iwx0 at pci0 dev 20 function 3 "Intel Wi-Fi 6 AX201" rev 0x00, msix works, no regression or issue noticed. Ok? Index: sys/dev/pci/if_iwx.c =================================================================== RCS file: /home/cvs/src/sys/dev/pci/if_iwx.c,v diff -u -p -r1.194 if_iwx.c --- sys/dev/pci/if_iwx.c 1 Dec 2025 16:44:13 -0000 1.194 +++ sys/dev/pci/if_iwx.c 31 Jan 2026 00:39:54 -0000 @@ -406,6 +406,7 @@ void iwx_power_build_cmd(struct iwx_soft struct iwx_mac_power_cmd *); int iwx_power_mac_update_mode(struct iwx_softc *, struct iwx_node *); int iwx_power_update_device(struct iwx_softc *); +int iwx_set_pslevel(struct iwx_softc *, int, int, int); int iwx_enable_beacon_filter(struct iwx_softc *, struct iwx_node *); int iwx_disable_beacon_filter(struct iwx_softc *); int iwx_add_sta_cmd(struct iwx_softc *, struct iwx_node *, int); @@ -6645,8 +6646,27 @@ iwx_power_build_cmd(struct iwx_softc *sc keep_alive = roundup(keep_alive, 1000) / 1000; cmd->keep_alive_seconds = htole16(keep_alive); - if (ic->ic_opmode != IEEE80211_M_MONITOR) - cmd->flags = htole16(IWX_POWER_FLAGS_POWER_SAVE_ENA_MSK); + if (ic->ic_opmode != IEEE80211_M_MONITOR && + (ic->ic_flags & IEEE80211_F_PMGTON)) { + const struct iwx_pmgt *pmgt; + int range; + + if (dtim_period <= 2) + range = 0; + else if (dtim_period <= 10) + range = 1; + else + range = 2; + pmgt = &iwx_pmgt[range][3]; + cmd->flags = htole16(IWX_POWER_FLAGS_POWER_SAVE_ENA_MSK | + IWX_POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + cmd->rx_data_timeout = htole32(pmgt->rxtimeout * 1024); + cmd->tx_data_timeout = htole32(pmgt->txtimeout * 1024); + if (pmgt->skip_dtim != 0) { + cmd->flags |= htole16(IWX_POWER_FLAGS_SKIP_OVER_DTIM_MSK); + cmd->skip_dtim_periods = pmgt->skip_dtim + 1; + } + } } int @@ -6676,7 +6696,8 @@ iwx_power_update_device(struct iwx_softc struct iwx_device_power_cmd cmd = { }; struct ieee80211com *ic = &sc->sc_ic; - if (ic->ic_opmode != IEEE80211_M_MONITOR) + if (ic->ic_opmode != IEEE80211_M_MONITOR && + (ic->ic_flags & IEEE80211_F_PMGTON)) cmd.flags = htole16(IWX_DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); return iwx_send_cmd_pdu(sc, @@ -6684,6 +6705,82 @@ iwx_power_update_device(struct iwx_softc } int +iwx_set_pslevel(struct iwx_softc *sc, int dtim, int level, int async) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct iwx_device_power_cmd dcmd = { }; + struct iwx_mac_power_cmd mcmd; + struct iwx_node *in; + struct ieee80211_node *ni; + const struct iwx_pmgt *pmgt; + int range, skip_dtim, cmd_flags, err; + int dtim_period, dtim_msec, keep_alive; + + if (ic->ic_opmode == IEEE80211_M_MONITOR) + return 0; + + if (dtim == 0) { + dtim = 1; + skip_dtim = 0; + } else + skip_dtim = -1; + + if (dtim <= 2) + range = 0; + else if (dtim <= 10) + range = 1; + else + range = 2; + + pmgt = &iwx_pmgt[range][level]; + if (skip_dtim == -1) + skip_dtim = pmgt->skip_dtim; + + if (level != 0) + dcmd.flags = htole16(IWX_DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); + + cmd_flags = async ? IWX_CMD_ASYNC : 0; + err = iwx_send_cmd_pdu(sc, IWX_POWER_TABLE_CMD, cmd_flags, + sizeof(dcmd), &dcmd); + if (err) + return err; + + if ((sc->sc_flags & IWX_FLAG_MAC_ACTIVE) == 0) + return 0; + + in = (void *)ic->ic_bss; + ni = &in->in_ni; + + memset(&mcmd, 0, sizeof(mcmd)); + mcmd.id_and_color = htole32(IWX_FW_CMD_ID_AND_COLOR(in->in_id, + in->in_color)); + dtim_period = ni->ni_dtimperiod ? ni->ni_dtimperiod : 1; + dtim_msec = dtim_period * ni->ni_intval; + keep_alive = MAX(3 * dtim_msec, 1000 * IWX_POWER_KEEP_ALIVE_PERIOD_SEC); + keep_alive = roundup(keep_alive, 1000) / 1000; + mcmd.keep_alive_seconds = htole16(keep_alive); + + if (level != 0) { + mcmd.flags = htole16(IWX_POWER_FLAGS_POWER_SAVE_ENA_MSK | + IWX_POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + mcmd.rx_data_timeout = htole32(pmgt->rxtimeout * 1024); + mcmd.tx_data_timeout = htole32(pmgt->txtimeout * 1024); + if (skip_dtim != 0) { + mcmd.flags |= htole16(IWX_POWER_FLAGS_SKIP_OVER_DTIM_MSK); + mcmd.skip_dtim_periods = skip_dtim + 1; + } + } + + err = iwx_send_cmd_pdu(sc, IWX_MAC_PM_POWER_TABLE, cmd_flags, + sizeof(mcmd), &mcmd); + if (err != 0) + return err; + + return iwx_update_beacon_abort(sc, in, !!(mcmd.flags & + htole16(IWX_POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK))); +} + +int iwx_enable_beacon_filter(struct iwx_softc *sc, struct iwx_node *in) { struct iwx_beacon_filter_cmd cmd = { @@ -8669,11 +8766,13 @@ iwx_run(struct iwx_softc *sc) return err; } - err = iwx_power_update_device(sc); - if (err) { - printf("%s: could not send power command (error %d)\n", - DEVNAME(sc), err); - return err; + if (ic->ic_flags & IEEE80211_F_PMGTON) { + err = iwx_set_pslevel(sc, 0, 3, 1); + if (err) { + printf("%s: could not send power command (error %d)\n", + DEVNAME(sc), err); + return err; + } } #ifdef notyet /* @@ -8691,13 +8790,6 @@ iwx_run(struct iwx_softc *sc) if (ic->ic_opmode == IEEE80211_M_MONITOR) return 0; - err = iwx_power_mac_update_mode(sc, in); - if (err) { - printf("%s: could not update MAC power (error %d)\n", - DEVNAME(sc), err); - return err; - } - /* Start at lowest available bit-rate. Firmware will raise. */ in->in_ni.ni_txrate = 0; in->in_ni.ni_txmcs = 0; @@ -9578,7 +9670,7 @@ iwx_init_hw(struct iwx_softc *sc) goto err; } - err = iwx_power_update_device(sc); + err = iwx_set_pslevel(sc, 0, 0, 0); if (err) { printf("%s: could not send power command (error %d)\n", DEVNAME(sc), err); @@ -9932,6 +10024,7 @@ int iwx_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct iwx_softc *sc = ifp->if_softc; + struct ieee80211com *ic = &sc->sc_ic; int s, err = 0, generation = sc->sc_generation; /* @@ -9964,6 +10057,21 @@ iwx_ioctl(struct ifnet *ifp, u_long cmd, } break; + case SIOCS80211POWER: + err = ieee80211_ioctl(ifp, cmd, data); + if (err != ENETRESET) + break; + if (ic->ic_state == IEEE80211_S_RUN) { + if (ic->ic_flags & IEEE80211_F_PMGTON) + err = iwx_set_pslevel(sc, 0, 3, 0); + else /* back to CAM */ + err = iwx_set_pslevel(sc, 0, 0, 0); + } else { + /* Defer until transition to IEEE80211_S_RUN. */ + err = 0; + } + break; + default: err = ieee80211_ioctl(ifp, cmd, data); } @@ -11848,6 +11956,7 @@ iwx_attach(struct device *parent, struct ic->ic_caps = IEEE80211_C_QOS | IEEE80211_C_TX_AMPDU | /* A-MPDU */ IEEE80211_C_ADDBA_OFFLOAD | /* device sends ADDBA/DELBA frames */ + IEEE80211_C_PMGT | /* power management */ IEEE80211_C_WEP | /* WEP */ IEEE80211_C_RSN | /* WPA/RSN */ IEEE80211_C_SCANALL | /* device scans all channels at once */ Index: sys/dev/pci/if_iwxreg.h =================================================================== RCS file: /home/cvs/src/sys/dev/pci/if_iwxreg.h,v diff -u -p -r1.58 if_iwxreg.h --- sys/dev/pci/if_iwxreg.h 1 Dec 2025 16:44:13 -0000 1.58 +++ sys/dev/pci/if_iwxreg.h 30 Jan 2026 23:52:35 -0000 @@ -5096,6 +5096,42 @@ struct iwx_mac_power_cmd { #define IWX_DEFAULT_PS_TX_DATA_TIMEOUT (100 * 1000) #define IWX_DEFAULT_PS_RX_DATA_TIMEOUT (100 * 1000) +#define IWX_NDTIMRANGES 3 +#define IWX_NPOWERLEVELS 6 +static const struct iwx_pmgt { + uint32_t rxtimeout; + uint32_t txtimeout; + int skip_dtim; +} iwx_pmgt[IWX_NDTIMRANGES][IWX_NPOWERLEVELS] = { + /* DTIM <= 2 */ + { + { 0, 0, 0 }, /* CAM */ + { 200, 500, 0 }, /* PS level 1 */ + { 200, 300, 0 }, /* PS level 2 */ + { 50, 100, 0 }, /* PS level 3 */ + { 50, 25, 1 }, /* PS level 4 */ + { 25, 25, 2 } /* PS level 5 */ + }, + /* 3 <= DTIM <= 10 */ + { + { 0, 0, 0 }, /* CAM */ + { 200, 500, 0 }, /* PS level 1 */ + { 200, 300, 0 }, /* PS level 2 */ + { 50, 100, 0 }, /* PS level 3 */ + { 50, 25, 1 }, /* PS level 4 */ + { 25, 25, 2 } /* PS level 5 */ + }, + /* DTIM >= 11 */ + { + { 0, 0, 0 }, /* CAM */ + { 200, 500, 0 }, /* PS level 1 */ + { 200, 300, 0 }, /* PS level 2 */ + { 50, 100, 0 }, /* PS level 3 */ + { 50, 25, 0 }, /* PS level 4 */ + { 25, 25, 0 } /* PS level 5 */ + } +}; + /* * struct iwx_uapsd_misbehaving_ap_notif - FW sends this notification when * associated AP is identified as improperly implementing uAPSD protocol. -- wbr, Kirill