From 54b96109768276fa6dd0bad8e5701ad55643412a Mon Sep 17 00:00:00 2001 From: Samuele Yang Date: Tue, 29 Sep 2026 13:34:08 +1000 Subject: wifi: mm81x: start beaconing on beacon enable not interface add Currently we only initialise and start the beaconing process when the AP interface is first added. If the BSS alone is reconfigured and beaconing is temporarily stopped, the subsequent call to re-enable the beacon via BSS_CHANGED_BEACON_ENABLED is ignored and beaconing will be permanently stopped until the interface is completely re-added. To fix, move the beaconing start/stop to mm81x_mac_ops_bss_info_changed and react accordingly to info->enable_beacon and just keep the per-vif work item init in the add_interface callback. Fixes: b1906cea00b0 ("wifi: mm81x: add mm81x Wi-Fi HaLow driver") Signed-off-by: Samuele Yang Link: https://patch.msgid.link/20260929033408.69266-1-samuele.yang@morsemicro.com Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/mac.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c index 1a7382d27475..13b09d31d80b 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mac.c +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -1355,11 +1355,10 @@ static void mm81x_mac_beacon_work(struct work_struct *work) mm81x_mac_send_buffered_bc(mors, vif); } -static void mm81x_mac_beacon_init(struct mm81x_vif *mors_vif) +static void mm81x_mac_beacon_start(struct mm81x_vif *mors_vif) { struct mm81x *mors = mm81x_vif_to_mors(mors_vif); - INIT_WORK(&mors_vif->u.ap.beacon_work, mm81x_mac_beacon_work); mm81x_mac_beacon_irq_enable(mors_vif, true); atomic_inc(&mors->num_bcn_vifs); } @@ -1582,7 +1581,9 @@ static void mm81x_mac_ops_bss_info_changed(struct ieee80211_hw *hw, mm81x_cmd_config_beacon_timer(mors, mors_vif, info->enable_beacon); - if (!info->enable_beacon) + if (info->enable_beacon) + mm81x_mac_beacon_start(mors_vif); + else mm81x_mac_beacon_finish(mors_vif); } @@ -2260,7 +2261,7 @@ static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw, rcu_assign_pointer(mors->vifs[mors_vif->id], vif); if (vif->type == NL80211_IFTYPE_AP) - mm81x_mac_beacon_init(mors_vif); + INIT_WORK(&mors_vif->u.ap.beacon_work, mm81x_mac_beacon_work); ret = mm81x_cmd_get_capabilities(mors, mors_vif->id, &mors->fw_caps); if (ret) { -- cgit v1.2.3 From 356ed3640068cccb107373b5314edbea215d72c1 Mon Sep 17 00:00:00 2001 From: Yury Norov Date: Fri, 25 Sep 2026 15:59:07 -0400 Subject: wifi: mm81x: switch to use fns() Replace the local nth_bit() helper with fns(), which provides the same zero-based set-bit selection for rate capability masks. Signed-off-by: Yury Norov Link: https://patch.msgid.link/20260925195908.252810-2-ynorov@nvidia.com [remove morsemicro prefix from commit message] Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/mmrc.c | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.c b/drivers/net/wireless/morsemicro/mm81x/mmrc.c index fe7e4f501d6c..4cbc35535e07 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mmrc.c +++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.c @@ -2,6 +2,8 @@ /* * Copyright (c) 2017-2026 Morse Micro */ +#include + #include "mmrc.h" /* @@ -198,22 +200,6 @@ */ static const u32 sym_table[10] = { 24, 36, 48, 72, 96, 144, 192, 216, 256, 288 }; -/* - * Calculate which bit is the nth bit set in an integer based flag. - */ -static u8 nth_bit(u16 in, u16 index) -{ - u32 i; - u8 count = 0; - - for (i = 0; count != index + 1; i++) { - if (((1u << i) & in) != 0) - count++; - } - - return i - 1; -} - /* * Calculate the input bit's index among all the set bits in an integer * based flag. @@ -291,19 +277,19 @@ static struct mmrc_rate get_rate_row(struct mmrc_table *tb, u16 index) u16 mcs_index = index / rows; u16 mcs_modulo = index % rows; - mcs = nth_bit(tb->caps.rates, mcs_index); + mcs = fns(tb->caps.rates, mcs_index); /* Find our spatial stream */ rows = rows / streams; - streams = nth_bit(tb->caps.spatial_streams, mcs_modulo / rows); + streams = fns(tb->caps.spatial_streams, mcs_modulo / rows); /* Find our bandwidth */ ss_index = index % rows; rows = rows / bw; - bw = nth_bit(tb->caps.bandwidth, ss_index / rows); + bw = fns(tb->caps.bandwidth, ss_index / rows); /* Find our guard */ - guard = nth_bit(tb->caps.guard, index % guard); + guard = fns(tb->caps.guard, index % guard); /* Add range checks to keep scan-build happy */ if (bw >= MMRC_BW_MAX) -- cgit v1.2.3 From da28021bc062d9c4dc6e885d6080aafd20359ed5 Mon Sep 17 00:00:00 2001 From: Yury Norov Date: Fri, 25 Sep 2026 15:59:08 -0400 Subject: wifi: mm81x: use hweight16() to calculate bit index Count the set bits through bit_pos with hweight16() and an inclusive mask. Preserve the zero fallback and zero-based index. Signed-off-by: Yury Norov Link: https://patch.msgid.link/20260925195908.252810-3-ynorov@nvidia.com [remove morsemicro prefix from commit message] Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/mmrc.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/mmrc.c b/drivers/net/wireless/morsemicro/mm81x/mmrc.c index 4cbc35535e07..6bedcdbcb61f 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mmrc.c +++ b/drivers/net/wireless/morsemicro/mm81x/mmrc.c @@ -206,13 +206,7 @@ static const u32 sym_table[10] = { 24, 36, 48, 72, 96, 144, 192, 216, 256, 288 } */ static u16 bit_index(u16 in, u32 bit_pos) { - u16 i; - u16 index = 0; - - for (i = 0; i != bit_pos + 1; i++) { - if (((1u << i) & in) != 0) - index++; - } + u16 index = hweight16(in & GENMASK(bit_pos, 0)); if (index == 0) { /* Could not match bit pos to caps */ -- cgit v1.2.3 From ad130fe516914076f3fb11940b1bf9181de04425 Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Fri, 11 Sep 2026 23:15:31 +0100 Subject: wifi: mm81x: validate n_channels in disabled channels response mm81x_mac_check_fw_disabled_chans() iterates over n_channels entries of the firmware response without validating the response length against the array size. A firmware response with n_channels larger than ARRAY_SIZE(mors_s1ghz_channels) reads past the end of the response buffer. To fix, make sure we validate the length prior to use. Assisted-by: LLM Fixes: b1906cea00b0 ("wifi: mm81x: add mm81x Wi-Fi HaLow driver") Signed-off-by: Aamir Ahmed Link: https://patch.msgid.link/AS8P251MB00015A3FFF9F9A9628CB0BB0C8BE2@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM [slightly reword commit message] Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/mac.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c index 13b09d31d80b..383f420b37ec 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mac.c +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -282,6 +282,11 @@ static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw) if (ret) goto out; + if (le32_to_cpu(resp->n_channels) > ARRAY_SIZE(mors_s1ghz_channels)) { + ret = -EINVAL; + goto out; + } + for (i = 0; i < ARRAY_SIZE(mors_s1ghz_channels); i++) { struct ieee80211_channel *ch = &mors_s1ghz_channels[i]; -- cgit v1.2.3 From 54a54c06868d0c2beb9ec3e2ffda0f6cb33d5c8e Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Sun, 20 Sep 2026 00:22:31 +0500 Subject: wifi: mm81x: check bytes_remaining before reading delimiter mm81x_yaps_hw_read_pkts() reads a full 4-byte delimiter and then subtracts sizeof(delim) from bytes_remaining on every loop iteration, guarded only by "bytes_remaining > 0". If 1-3 bytes are left in the window, this reads past the end of the valid data and then subtracts 4 from a value smaller than 4, driving bytes_remaining negative. A negative bytes_remaining then reaches the total_len > bytes_remaining branch, where "bytes_remaining" is passed to memcpy() as the copy length. Implicitly converted to size_t, a negative int becomes a huge value, turning that memcpy() into a massive out-of-bounds copy. Bail out of the split loop once fewer than sizeof(delim) bytes remain, the same way the existing "end of stream" check bails on a zero delimiter. Fixes: b1906cea00b0 ("wifi: mm81x: add mm81x Wi-Fi HaLow driver") Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260919192231.272078-1-meatuni001@gmail.com Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/yaps_hw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c index 3d641d23c35b..e1102b8fb802 100644 --- a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c +++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c @@ -486,6 +486,9 @@ static int mm81x_yaps_hw_read_pkts(struct mm81x_yaps *yaps, int total_len; int pkt_size; + if (bytes_remaining < (int)sizeof(delim)) + break; + delim = le32_to_cpu(*((__le32 *)read_ptr)); read_ptr += sizeof(delim); bytes_remaining -= sizeof(delim); -- cgit v1.2.3 From 48d051024430f77a47a7f6bca240782b6fc274c0 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Wed, 7 Oct 2026 15:59:58 +1100 Subject: wifi: mm81x: provide the station to mac80211 on rx Right now we look up the station on rx to update its average RSSI but then hand the frame to mac80211 without it, leaving mac80211 to look the station up again. Use ieee80211_rx_napi() and rework the code a bit so we pass the station directly to mac80211 to prevent another lookup. Link: https://patch.msgid.link/20261007050000.127009-2-lachlan.hodges@morsemicro.com Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/mac.c | 34 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c index 383f420b37ec..add2b44423c5 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mac.c +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -1932,22 +1932,13 @@ static void mm81x_rx_h_fill_status(struct mm81x *mors, rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; } -static void mm81x_rx_h_update_sta(struct ieee80211_vif *vif, - struct ieee80211_hdr *hdr, +static void mm81x_rx_h_update_sta(struct ieee80211_sta *sta, struct ieee80211_rx_status *rx_status) { - struct ieee80211_sta *sta; - struct mm81x_sta *msta; - u8 *lookup = ieee80211_is_s1g_beacon(hdr->frame_control) ? hdr->addr1 : - hdr->addr2; + struct mm81x_sta *msta = (void *)sta->drv_priv; lockdep_assert_in_rcu_read_lock(); - sta = ieee80211_find_sta(vif, lookup); - if (!sta) - return; - - msta = (void *)sta->drv_priv; if (msta->avg_rssi) { msta->avg_rssi = CALC_AVG_RSSI(msta->avg_rssi, rx_status->signal); @@ -1975,9 +1966,11 @@ void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, struct mm81x_skb_rx_status *hdr_rx_status) { struct ieee80211_vif *vif; + struct ieee80211_sta *sta = NULL; struct ieee80211_hw *hw = mors->hw; struct ieee80211_rx_status rx_status; struct ieee80211_hdr *hdr = (void *)skb->data; + u8 *lookup; memset(&rx_status, 0, sizeof(rx_status)); @@ -1986,19 +1979,24 @@ void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, return; } + lookup = ieee80211_is_s1g_beacon(hdr->frame_control) ? hdr->addr1 : + hdr->addr2; + mm81x_rx_h_fill_status(mors, hdr_rx_status, &rx_status, skb); + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); scoped_guard(rcu) { vif = mm81x_rx_h_skb_get_vif(mors, skb, hdr_rx_status); - if (!vif) - goto rx; + if (vif) { + sta = ieee80211_find_sta(vif, lookup); + if (sta) + mm81x_rx_h_update_sta(sta, &rx_status); + } - mm81x_rx_h_update_sta(vif, hdr, &rx_status); + local_bh_disable(); + ieee80211_rx_napi(hw, sta ? &sta->deflink : NULL, skb, NULL); + local_bh_enable(); } - -rx: - memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); - ieee80211_rx_ni(hw, skb); } static void mm81x_mac_flush_queues(struct mm81x *mors) -- cgit v1.2.3 From 71a6d9ba64ddba00a241006b95d2d8e8dda15fd6 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Wed, 7 Oct 2026 15:59:59 +1100 Subject: wifi: mm81x: add monitor mode support Introduce monitor mode support by extending the command to add a monitor interface to the firmware and inserting the S1G radiotap TLV. Also rework the .configure_filter() callback to better handle frame filtering when we are in monitor mode. This implementation works best as a standalone sniffer currently, as support is yet to be added in mac80211 to handle building the radiotap for tx frames (i.e if you bind a monitor interface to an existing station or AP). Right now the S1G radiotap header is not really complete and lacks the ability to convey the frequency offset and correct NDP reporting. The process to amend the S1G TLV will begin after this initial support is complete. Link: https://patch.msgid.link/20261007050000.127009-3-lachlan.hodges@morsemicro.com Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/command.c | 5 +- drivers/net/wireless/morsemicro/mm81x/core.h | 16 +- drivers/net/wireless/morsemicro/mm81x/mac.c | 208 ++++++++++++++++++++---- 3 files changed, 195 insertions(+), 34 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/command.c b/drivers/net/wireless/morsemicro/mm81x/command.c index afb7ee9bb236..56dd338321f0 100644 --- a/drivers/net/wireless/morsemicro/mm81x/command.c +++ b/drivers/net/wireless/morsemicro/mm81x/command.c @@ -262,6 +262,9 @@ int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr, case NL80211_IFTYPE_AP: req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_AP); break; + case NL80211_IFTYPE_MONITOR: + req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_MON); + break; default: return -EOPNOTSUPP; } @@ -458,7 +461,7 @@ int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors, struct mm81x_vif *mors_vif) { struct host_cmd_req_mcast_filter *req; - struct mcast_filter *filter = mors->mcast_filter; + struct mm81x_mcast_filter *filter = mors->mcast_filter; u16 filter_list_len = sizeof(filter->addr_list[0]) * filter->count; u16 alloc_len = filter_list_len + sizeof(*req); int ret = 0; diff --git a/drivers/net/wireless/morsemicro/mm81x/core.h b/drivers/net/wireless/morsemicro/mm81x/core.h index 75e998e8e324..d46323815304 100644 --- a/drivers/net/wireless/morsemicro/mm81x/core.h +++ b/drivers/net/wireless/morsemicro/mm81x/core.h @@ -225,7 +225,7 @@ struct mm81x_stale_tx_status { struct timer_list timer; }; -struct mcast_filter { +struct mm81x_mcast_filter { u8 count; /* * Integer representation of the last four bytes of a multicast MAC @@ -335,6 +335,18 @@ struct mm81x { struct ieee80211_vif __rcu *vifs[MM81X_MAX_IF]; + /* + * Groups A-MPDU subframes for monitor reporting. The firmware stamps + * every subframe of an A-MPDU with the same receive timestamp, so a + * change in timestamp starts a new reference. + */ + struct { + u64 timestamp; + u32 reference; + } rx_ampdu; + + bool monitor_en; + /* @mm81x_state_flags */ unsigned long state_flags; @@ -377,7 +389,7 @@ struct mm81x { wait_queue_head_t tx_empty_waitq; struct cfg80211_chan_def chandef; - struct mcast_filter *mcast_filter; + struct mm81x_mcast_filter *mcast_filter; atomic_t num_bcn_vifs; unsigned long beacon_irqs_enabled; u8 drv_priv[] __aligned(sizeof(void *)); diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c index add2b44423c5..03c80ab999a1 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mac.c +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include "hif.h" @@ -1400,6 +1401,9 @@ static void mm81x_mac_ops_remove_interface(struct ieee80211_hw *hw, dev_err(mors->dev, "mm81x_cmd_rm_if failed %d", ret); RCU_INIT_POINTER(mors->vifs[mors_vif->id], NULL); + + if (vif->type == NL80211_IFTYPE_MONITOR) + __clear_bit(IEEE80211_HW_RX_INCLUDES_FCS, hw->flags); } static s32 mm81x_mac_get_max_txpower(struct mm81x *mors) @@ -1514,6 +1518,9 @@ static int mm81x_mac_ops_config(struct ieee80211_hw *hw, int radio_idx, return ret; } + if (changed & IEEE80211_CONF_CHANGE_MONITOR) + mors->monitor_en = conf->flags & IEEE80211_CONF_MONITOR; + if ((changed & IEEE80211_CONF_CHANGE_POWER) && !(changed & IEEE80211_CONF_CHANGE_CHANNEL) && !(conf->flags & IEEE80211_CONF_MONITOR)) { @@ -1605,7 +1612,7 @@ static u64 mm81x_mac_ops_prepare_multicast(struct ieee80211_hw *hw, struct netdev_hw_addr_list *mc_list) { struct mm81x *mors = hw->priv; - struct mcast_filter *filter; + struct mm81x_mcast_filter *filter; struct netdev_hw_addr *addr; u16 addr_count = netdev_hw_addr_list_count(mc_list); u16 len = sizeof(*filter) + addr_count * sizeof(filter->addr_list[0]); @@ -1632,17 +1639,38 @@ static u64 mm81x_mac_ops_prepare_multicast(struct ieee80211_hw *hw, return (u64)(unsigned long)filter; } +static void mm81x_mc_iface_iterator(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mm81x_vif *mors_vif = ieee80211_vif_to_mors_vif(vif); + struct mm81x *mors = data; + int ret; + + if (vif->type == NL80211_IFTYPE_MONITOR) + return; + + ret = mm81x_cmd_cfg_multicast_filter(mors, mors_vif); + if (ret) + dev_err(mors->dev, "Multicast filtering failed - rc=%d", ret); +} + +#define MM81X_MONITOR_FILTERS \ + (FIF_ALLMULTI | \ + FIF_BCN_PRBRESP_PROMISC | \ + FIF_CONTROL | \ + FIF_FCSFAIL | \ + FIF_OTHER_BSS | \ + FIF_PSPOLL | \ + FIF_PROBE_REQ | \ + FIF_MCAST_ACTION) + static void mm81x_mac_ops_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, unsigned int *total_flags, u64 multicast) { + struct mm81x_mcast_filter *cmd = (void *)(unsigned long)multicast; struct mm81x *mors = hw->priv; - struct mcast_filter *cmd = (void *)(unsigned long)multicast; - struct mm81x_vif *mors_vif = NULL; - struct ieee80211_vif *vif = NULL; - int vif_id = 0; - int ret = 0; if (!cmd) goto out; @@ -1650,25 +1678,15 @@ static void mm81x_mac_ops_configure_filter(struct ieee80211_hw *hw, kfree(mors->mcast_filter); mors->mcast_filter = cmd; - for (vif_id = 0; vif_id < ARRAY_SIZE(mors->vifs); vif_id++) { - vif = mm81x_rcu_dereference_vif_id(mors, vif_id, false); - if (!vif) - continue; - - mors_vif = ieee80211_vif_to_mors_vif(vif); - - ret = mm81x_cmd_cfg_multicast_filter(mors, mors_vif); - if (!ret) - continue; - - dev_err(mors->dev, "Multicast filtering failed - rc=%d", ret); - mors->mcast_filter = NULL; - kfree(cmd); - break; - } + ieee80211_iterate_active_interfaces(mors->hw, + IEEE80211_IFACE_ITER_NORMAL, + mm81x_mc_iface_iterator, mors); out: - *total_flags &= 0; + if (mors->monitor_en) + *total_flags &= MM81X_MONITOR_FILTERS; + else + *total_flags = 0; } static int mm81x_mac_ops_conf_tx(struct ieee80211_hw *hw, @@ -1906,14 +1924,95 @@ static u8 mm81x_rx_h_rc_bw_to_rx_bw(__le32 ratecode) } } +static u32 mm81x_rx_h_ampdu_reference(struct mm81x *mors, u64 timestamp) +{ + if (timestamp != mors->rx_ampdu.timestamp) { + mors->rx_ampdu.timestamp = timestamp; + mors->rx_ampdu.reference++; + } + + return mors->rx_ampdu.reference; +} + +static enum ieee80211_radiotap_s1g_ppdu_format +mm81x_rx_h_ppdu_format(__le32 ratecode) +{ + switch (mm81x_ratecode_preamble_get(ratecode)) { + case MM81X_RATE_PREAMBLE_S1G_1M: + return IEEE80211_RADIOTAP_S1G_PPDU_FORMAT_1M; + case MM81X_RATE_PREAMBLE_S1G_LONG: + return IEEE80211_RADIOTAP_S1G_PPDU_FORMAT_LONG; + default: + return IEEE80211_RADIOTAP_S1G_PPDU_FORMAT_SHORT; + } +} + +#define S1G_RADIOTAP_TLV_LEN \ + (sizeof(struct ieee80211_radiotap_tlv) + \ + ALIGN(sizeof(struct ieee80211_radiotap_s1g), 4)) + +static void mm81x_rx_h_add_rt_s1g_tlv(struct sk_buff *skb, + struct mm81x_skb_rx_status *hdr_rx_status, + struct ieee80211_rx_status *rx_status) +{ + __le32 ratecode = hdr_rx_status->mm81x_ratecode; + u32 flags = le32_to_cpu(hdr_rx_status->flags); + u8 color = hdr_rx_status->bss_color; + struct ieee80211_radiotap_tlv *tlv; + struct ieee80211_radiotap_s1g s1g; + + if (skb_headroom(skb) < S1G_RADIOTAP_TLV_LEN) + return; + + s1g.known = cpu_to_le16(IEEE80211_RADIOTAP_S1G_KNOWN_PPDU_FORMAT | + IEEE80211_RADIOTAP_S1G_KNOWN_RESPONSE_IND | + IEEE80211_RADIOTAP_S1G_KNOWN_GI | + IEEE80211_RADIOTAP_S1G_KNOWN_BW | + IEEE80211_RADIOTAP_S1G_KNOWN_UPLINK_IND | + IEEE80211_RADIOTAP_S1G_KNOWN_NSS | + IEEE80211_RADIOTAP_S1G_KNOWN_MCS | + IEEE80211_RADIOTAP_S1G_KNOWN_COLOR); + + s1g.data1 = le16_encode_bits(mm81x_rx_h_ppdu_format(ratecode), + IEEE80211_RADIOTAP_S1G_DATA1_PPDU_FORMAT) | + le16_encode_bits(MM81X_RX_STATUS_FLAGS_RI_GET(flags), + IEEE80211_RADIOTAP_S1G_DATA1_RESPONSE_IND) | + le16_encode_bits(mm81x_ratecode_sgi_get(ratecode), + IEEE80211_RADIOTAP_S1G_DATA1_GI) | + le16_encode_bits(mm81x_ratecode_nss_index_get(ratecode), + IEEE80211_RADIOTAP_S1G_DATA1_NSS) | + le16_encode_bits(mm81x_ratecode_bw_index_get(ratecode), + IEEE80211_RADIOTAP_S1G_DATA1_BW) | + le16_encode_bits(mm81x_ratecode_mcs_index_get(ratecode), + IEEE80211_RADIOTAP_S1G_DATA1_MCS); + + s1g.data2 = le16_encode_bits(color, IEEE80211_RADIOTAP_S1G_DATA2_COLOR) | + le16_encode_bits(MM81X_RX_STATUS_FLAGS_UPL_IND_GET(flags), + IEEE80211_RADIOTAP_S1G_DATA2_UPLINK_IND) | + le16_encode_bits((u8)rx_status->signal, + IEEE80211_RADIOTAP_S1G_DATA2_RSSI); + + tlv = skb_push(skb, S1G_RADIOTAP_TLV_LEN); + tlv->type = cpu_to_le16(IEEE80211_RADIOTAP_S1G); + tlv->len = cpu_to_le16(sizeof(s1g)); + + memcpy(tlv->data, &s1g, sizeof(s1g)); + memset(tlv->data + sizeof(s1g), 0, + S1G_RADIOTAP_TLV_LEN - sizeof(*tlv) - sizeof(s1g)); + + skb_set_mac_header(skb, S1G_RADIOTAP_TLV_LEN); + rx_status->flag |= RX_FLAG_RADIOTAP_TLV_AT_END; +} + static void mm81x_rx_h_fill_status(struct mm81x *mors, struct mm81x_skb_rx_status *hdr_rx_status, struct ieee80211_rx_status *rx_status, struct sk_buff *skb) { - u32 flags = le32_to_cpu(hdr_rx_status->flags); + u64 timestamp = le64_to_cpu(hdr_rx_status->rx_timestamp_us); u16 freq_100khz = le16_to_cpu(hdr_rx_status->freq_100khz); __le32 ratecode = hdr_rx_status->mm81x_ratecode; + u32 flags = le32_to_cpu(hdr_rx_status->flags); rx_status->signal = le16_to_cpu(hdr_rx_status->rssi); rx_status->encoding = RX_ENC_S1G; @@ -1921,15 +2020,51 @@ static void mm81x_rx_h_fill_status(struct mm81x *mors, rx_status->freq = KHZ100_TO_MHZ(freq_100khz); rx_status->freq_offset = (freq_100khz % 10) ? 1 : 0; rx_status->nss = NSS_IDX_TO_NSS(mm81x_ratecode_nss_index_get(ratecode)); + rx_status->rate_idx = mm81x_ratecode_mcs_index_get(ratecode); + rx_status->bw = mm81x_rx_h_rc_bw_to_rx_bw(ratecode); + rx_status->mactime = timestamp; + rx_status->flag |= RX_FLAG_MACTIME_IS_RTAP_TS64; if (flags & MM81X_RX_STATUS_FLAGS_DECRYPTED) rx_status->flag |= RX_FLAG_DECRYPTED; - rx_status->rate_idx = mm81x_ratecode_mcs_index_get(ratecode); - rx_status->bw = mm81x_rx_h_rc_bw_to_rx_bw(ratecode); - if (mm81x_ratecode_sgi_get(ratecode)) rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + + if (mors->monitor_en) { + if (flags & MM81X_RX_STATUS_FLAGS_CRC_ERROR) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + + if (flags & MM81X_RX_STATUS_FLAGS_AMPDU) { + rx_status->flag |= RX_FLAG_AMPDU_DETAILS | + RX_FLAG_AMPDU_EOF_BIT_KNOWN; + rx_status->ampdu_reference = + mm81x_rx_h_ampdu_reference(mors, timestamp); + + if (flags & MM81X_RX_STATUS_FLAGS_EOF) + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT; + } + + if (flags & MM81X_RX_STATUS_FLAGS_NDP) { + rx_status->flag |= RX_FLAG_NO_PSDU; + + /* + * S1G PSDUs are not standardised in radiotap yet, so + * for now report as sounding PSDUs so that acks etc. + * can be passed through. + */ + rx_status->zero_length_psdu_type = + IEEE80211_RADIOTAP_ZERO_LEN_PSDU_SOUNDING; + + /* + * Null Data Packets contain no data, therefore no + * mcs encoding. The STF/LTF are usually BPSK, therefore + * the NDP mcs rate can always be considered as 0. + */ + rx_status->rate_idx = 0; + rx_status->nss = 1; + } + } } static void mm81x_rx_h_update_sta(struct ieee80211_sta *sta, @@ -1983,7 +2118,6 @@ void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, hdr->addr2; mm81x_rx_h_fill_status(mors, hdr_rx_status, &rx_status, skb); - memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); scoped_guard(rcu) { vif = mm81x_rx_h_skb_get_vif(mors, skb, hdr_rx_status); @@ -1993,6 +2127,11 @@ void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, mm81x_rx_h_update_sta(sta, &rx_status); } + if (mors->monitor_en) + mm81x_rx_h_add_rt_s1g_tlv(skb, hdr_rx_status, &rx_status); + + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + local_bh_disable(); ieee80211_rx_napi(hw, sta ? &sta->deflink : NULL, skb, NULL); local_bh_enable(); @@ -2263,9 +2402,6 @@ static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw, rcu_assign_pointer(mors->vifs[mors_vif->id], vif); - if (vif->type == NL80211_IFTYPE_AP) - INIT_WORK(&mors_vif->u.ap.beacon_work, mm81x_mac_beacon_work); - ret = mm81x_cmd_get_capabilities(mors, mors_vif->id, &mors->fw_caps); if (ret) { dev_err(mors->dev, @@ -2274,6 +2410,12 @@ static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw, return ret; } + if (vif->type == NL80211_IFTYPE_MONITOR) + ieee80211_hw_set(hw, RX_INCLUDES_FCS); + + if (vif->type == NL80211_IFTYPE_AP) + INIT_WORK(&mors_vif->u.ap.beacon_work, mm81x_mac_beacon_work); + ieee80211_wake_queues(mors->hw); return ret; } @@ -2347,6 +2489,9 @@ static void mm81x_mac_config_hw(struct mm81x *mors) hw->sta_data_size = sizeof(struct mm81x_sta); hw->extra_tx_headroom = sizeof(struct mm81x_skb_hdr) + mm81x_bus_get_alignment(mors); + hw->radiotap_timestamp.units_pos = + IEEE80211_RADIOTAP_TIMESTAMP_UNIT_US | + IEEE80211_RADIOTAP_TIMESTAMP_SPOS_BEGIN_MDPU; mors->wiphy = hw->wiphy; @@ -2361,6 +2506,7 @@ static void mm81x_mac_config_hw(struct mm81x *mors) ieee80211_hw_set(hw, PS_NULLFUNC_STACK); ieee80211_hw_set(hw, SUPPORTS_TX_FRAG); ieee80211_hw_set(hw, SUPPORTS_NDP_BLOCKACK); + ieee80211_hw_set(hw, WANT_MONITOR_VIF); SET_IEEE80211_PERM_ADDR(hw, mors->macaddr); -- cgit v1.2.3 From 4ec869de9c62ff34db1a31fca84c096961175035 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Wed, 7 Oct 2026 16:00:00 +1100 Subject: wifi: mm81x: copy new sband for each module the sband information existed as a single static struct and was directly modified in place by both the stack and the driver. If the regdom was ever changed to 00 where all the S1G channels are NO_IR and a bus module then removed, these flags would remain even after changing the regdom and prevent channels from being used. Instead, make sure we copy the sband each time we init our mac80211 hw such that when a bus module is removed the state is reset and flags don't persist. Link: https://patch.msgid.link/20261007050000.127009-4-lachlan.hodges@morsemicro.com Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/mac.c | 47 +++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c index 03c80ab999a1..6f1feb24fe6a 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mac.c +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -84,7 +84,7 @@ .max_power = 30, \ } -static struct ieee80211_channel mors_s1ghz_channels[] = { +static const struct ieee80211_channel mors_s1ghz_channels[] = { CHANS1G(1, 902, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), CHANS1G(3, 903, 500, 0), CHANS1G(5, 904, 500, 0), @@ -115,11 +115,9 @@ static struct ieee80211_channel mors_s1ghz_channels[] = { /* clang-format on */ -static struct ieee80211_supported_band mors_band_s1ghz = { +static const struct ieee80211_supported_band mors_band_s1ghz = { .band = NL80211_BAND_S1GHZ, .s1g_cap.s1g = true, - .channels = mors_s1ghz_channels, - .n_channels = ARRAY_SIZE(mors_s1ghz_channels), .bitrates = NULL, .n_bitrates = 0, .s1g_cap.cap[4] = 0x80 /* STA type sensor only for AP & STA */ @@ -269,8 +267,10 @@ static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw) u32 i; struct mm81x *mors = hw->priv; struct host_cmd_resp_get_disabled_channels *resp; + struct ieee80211_supported_band *sband = + hw->wiphy->bands[NL80211_BAND_S1GHZ]; u32 resp_len = sizeof(struct host_cmd_disabled_channel_entry) * - ARRAY_SIZE(mors_s1ghz_channels) + + sband->n_channels + sizeof(*resp); resp = kzalloc(resp_len, GFP_KERNEL); @@ -283,13 +283,13 @@ static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw) if (ret) goto out; - if (le32_to_cpu(resp->n_channels) > ARRAY_SIZE(mors_s1ghz_channels)) { + if (le32_to_cpu(resp->n_channels) > sband->n_channels) { ret = -EINVAL; goto out; } - for (i = 0; i < ARRAY_SIZE(mors_s1ghz_channels); i++) { - struct ieee80211_channel *ch = &mors_s1ghz_channels[i]; + for (i = 0; i < sband->n_channels; i++) { + struct ieee80211_channel *ch = &sband->channels[i]; if (ch->flags & IEEE80211_CHAN_DISABLED) continue; @@ -342,7 +342,8 @@ static int mm81x_tx_h_get_max_tx_bw(struct mm81x *mors) static void mm81x_mac_caps_init(struct mm81x *mors) { struct mm81x_fw_caps *fw_caps = &mors->fw_caps; - struct ieee80211_sta_s1g_cap *s1g = &mors_band_s1ghz.s1g_cap; + struct ieee80211_sta_s1g_cap *s1g = + &mors->hw->wiphy->bands[NL80211_BAND_S1GHZ]->s1g_cap; #define __FW_CAP_N(_n, _cap, _bit) \ do { \ @@ -2476,7 +2477,6 @@ static void mm81x_mac_config_hw(struct mm81x *mors) for (i = 0; i < NUM_NL80211_BANDS; i++) hw->wiphy->bands[i] = NULL; - hw->wiphy->bands[NL80211_BAND_S1GHZ] = &mors_band_s1ghz; hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_STATION); hw->wiphy->reg_notifier = mm81x_reg_notifier; @@ -2554,6 +2554,28 @@ static void mm81x_mac_stale_tx_status_timer_init(struct mm81x *mors) timer_setup(&mors->stale_status.timer, mm81x_stale_tx_status_timer, 0); } +static int mm81x_mac_set_sband(struct mm81x *mors) +{ + struct ieee80211_supported_band *sband; + + sband = devm_kmemdup(mors->dev, &mors_band_s1ghz, sizeof(*sband), + GFP_KERNEL); + if (!sband) + return -ENOMEM; + + sband->channels = devm_kmemdup_array(mors->dev, mors_s1ghz_channels, + ARRAY_SIZE(mors_s1ghz_channels), + sizeof(*mors_s1ghz_channels), + GFP_KERNEL); + if (!sband->channels) + return -ENOMEM; + + sband->n_channels = ARRAY_SIZE(mors_s1ghz_channels); + mors->hw->wiphy->bands[NL80211_BAND_S1GHZ] = sband; + + return 0; +} + int mm81x_mac_register(struct mm81x *mors) { int ret; @@ -2568,6 +2590,11 @@ int mm81x_mac_register(struct mm81x *mors) return ret; mm81x_mac_config_hw(mors); + + ret = mm81x_mac_set_sband(mors); + if (ret) + return ret; + mm81x_mac_hw_scan_init(mors); mm81x_mac_stale_tx_status_timer_init(mors); -- cgit v1.2.3 From 7e6689cbbf9e6040e4ce38656a126459aa994f68 Mon Sep 17 00:00:00 2001 From: Felix Hoffmann Date: Wed, 7 Oct 2026 11:30:21 +0200 Subject: wifi: mm81x: validate YAPS receive framing The receive byte count comes from device-controlled status registers, but the packet splitter unconditionally consumes a four-byte delimiter. A count of one to three bytes therefore makes bytes_remaining negative. If the reused receive buffer contains a valid delimiter, the negative value is passed to memcpy() and converted to a near-SIZE_MAX length, corrupting kernel memory. YAPS streams are word aligned, so reject byte counts that cannot contain complete delimiters. Also reject delimiter sizes which do not leave any packet data after removing the reserved metadata page. Assisted-by: LLM Fixes: b1906cea00b0 ("wifi: mm81x: add mm81x Wi-Fi HaLow driver") Signed-off-by: Felix Hoffmann Link: https://patch.msgid.link/20261007093021.17132-1-f3lix.dev@gmx.de [remove Suggested-by and fix Assisted-by] Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/yaps_hw.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c index e1102b8fb802..1d90137470c2 100644 --- a/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c +++ b/drivers/net/wireless/morsemicro/mm81x/yaps_hw.c @@ -401,16 +401,17 @@ exit: static bool mm81x_read_pkts_h_is_valid_delim(u32 delim) { u8 calc_crc = mm81x_yaps_hw_crc(delim); - int pkt_size = YAPS_DELIM_GET_PHANDLE_SIZE(delim); + int pkt_size = YAPS_DELIM_GET_PKT_SIZE(delim); int padding = YAPS_DELIM_GET_PADDING(delim); if (calc_crc != YAPS_DELIM_GET_CRC(delim)) return false; - if (pkt_size == 0) + if (pkt_size <= 0) return false; - if ((pkt_size + padding) > YAPS_MAX_PKT_SIZE_BYTES) + if ((pkt_size + YAPS_RESERVED_PAGE_SIZE + padding) > + YAPS_MAX_PKT_SIZE_BYTES) return false; /* Pkt length + padding should not require more padding */ @@ -455,6 +456,10 @@ static int mm81x_yaps_hw_read_pkts(struct mm81x_yaps *yaps, return 0; if (bytes_remaining < 0) return bytes_remaining; + if (!IS_ALIGNED(bytes_remaining, sizeof(u32))) { + dev_warn(yaps->mors->dev, "yaps truncated delimiter"); + return -EIO; + } if (bytes_remaining > YAPS_HW_WINDOW_SIZE_BYTES) { bytes_remaining = YAPS_HW_WINDOW_SIZE_BYTES; -- cgit v1.2.3