PRIMAP(WMI_HOST_HW_MODE_MAX),
};
+/*
+ * Interference bitmap transform maps used by
+ * ath12k_wmi_transform_interference_bitmap().
+ *
+ * Firmware reports bitmap bits in a primary-based order where:
+ * - bit 0 is always the primary 20 MHz segment,
+ * - bit 1 is the adjacent 20 MHz in the same 40 MHz block,
+ * - bit 2 is the lower 20 MHz segment of the adjacent 40 MHz segment
+ * - bit 3 is the higher 20 MHz segment of the adjacent 40 MHz segment
+ * - remaining bits continue outward in 80/160/320 MHz groups.
+ *
+ * cfg80211 userspace notification expects absolute frequency order where:
+ * - bit 0 is the lowest-frequency 20 MHz segment in the current chandef,
+ * - bit N increases monotonically toward higher frequency.
+ *
+ * For each bandwidth-specific map:
+ * - row index = primary 20 MHz index in absolute (low->high) order,
+ * - column index = source bit position from firmware bitmap,
+ * - value = destination bit position in absolute order bitmap.
+ *
+ * Example for 80 MHz: if primary index is 2 (third 20 MHz chunk from low
+ * frequency), row intf_map_80[2] = { 2, 3, 0, 1 } means firmware bits {0,1,2,3}
+ * are remapped to destination bits {2,3,0,1} before notifying cfg80211.
+ */
+
+static const int intf_map_80[4][4] = {
+ { 0, 1, 2, 3 },
+ { 1, 0, 2, 3 },
+ { 2, 3, 0, 1 },
+ { 3, 2, 0, 1 }
+};
+
+static const int intf_map_160[8][8] = {
+ { 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 1, 0, 2, 3, 4, 5, 6, 7 },
+ { 2, 3, 0, 1, 4, 5, 6, 7 },
+ { 3, 2, 0, 1, 4, 5, 6, 7 },
+ { 4, 5, 6, 7, 0, 1, 2, 3 },
+ { 5, 4, 6, 7, 0, 1, 2, 3 },
+ { 6, 7, 4, 5, 0, 1, 2, 3 },
+ { 7, 6, 4, 5, 0, 1, 2, 3 }
+};
+
+static const int intf_map_320[16][16] = {
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 2, 3, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 3, 2, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 4, 5, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 5, 4, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 6, 7, 4, 5, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 7, 6, 4, 5, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 9, 8, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 10, 11, 8, 9, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 11, 10, 8, 9, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 12, 13, 14, 15, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 13, 12, 14, 15, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 14, 15, 12, 13, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 15, 14, 12, 13, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 }
+};
+
static int
ath12k_wmi_tlv_iter(struct ath12k_base *ab, const void *ptr, size_t len,
int (*iter)(struct ath12k_base *ab, u16 tag, u16 len,
ev->ctl_failsafe_status);
}
+static int
+ath12k_wmi_incumbent_signal_interference_subtlv_parser(struct ath12k_base *ab,
+ u16 tag, u16 len,
+ const void *ptr,
+ void *data)
+{
+ const struct ath12k_wmi_incumbent_signal_interference_params *info;
+ struct ath12k_wmi_incumbent_signal_interference_arg *arg = data;
+
+ switch (tag) {
+ case WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE:
+ if (len < sizeof(*info)) {
+ ath12k_warn(ab,
+ "DCS incumbent signal interference subtlv 0x%x invalid len %u\n",
+ tag, len);
+ return -EINVAL;
+ }
+
+ info = ptr;
+
+ arg->chan_width = le32_to_cpu(info->chan_width);
+ arg->chan_freq = le32_to_cpu(info->chan_freq);
+ arg->center_freq0 = le32_to_cpu(info->center_freq0);
+ arg->center_freq1 = le32_to_cpu(info->center_freq1);
+ arg->chan_bw_interference_bitmap =
+ le32_to_cpu(info->chan_bw_interference_bitmap);
+
+ ath12k_dbg(ab, ATH12K_DBG_WMI,
+ "incumbent signal interference chan width %u freq %u center_freq0 %u center_freq1 %u bitmap 0x%x\n",
+ arg->chan_width, arg->chan_freq,
+ arg->center_freq0, arg->center_freq1,
+ arg->chan_bw_interference_bitmap);
+ break;
+ default:
+ ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS interference in subtlvs\n",
+ tag);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ath12k_wmi_dcs_interference_event_parser(struct ath12k_base *ab,
+ u16 tag, u16 len,
+ const void *ptr, void *data)
+{
+ int ret = 0;
+
+ switch (tag) {
+ case WMI_TAG_DCS_INTERFERENCE_EVENT:
+ /* Fixed param should already be processed */
+ break;
+ case WMI_TAG_ARRAY_STRUCT:
+ ret = ath12k_wmi_tlv_iter(ab, ptr, len,
+ ath12k_wmi_incumbent_signal_interference_subtlv_parser,
+ data);
+ break;
+ default:
+ ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS interference event\n",
+ tag);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static bool
+ath12k_wmi_validate_interference_info(struct ath12k *ar,
+ struct ath12k_wmi_incumbent_signal_interference_arg *info)
+{
+ switch (info->chan_width) {
+ case WMI_CHAN_WIDTH_20:
+ if (info->chan_bw_interference_bitmap > ATH12K_WMI_DCS_SEG_PRI20) {
+ ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+ "DCS interference event received with wrong chan width bmap 0x%x for 20 MHz",
+ info->chan_bw_interference_bitmap);
+ return false;
+ }
+ break;
+ case WMI_CHAN_WIDTH_40:
+ if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
+ ATH12K_WMI_DCS_SEG_SEC20)) {
+ ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+ "DCS interference event received with wrong chan width bmap 0x%x for 40 MHz",
+ info->chan_bw_interference_bitmap);
+ return false;
+ }
+ break;
+ case WMI_CHAN_WIDTH_80:
+ if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
+ ATH12K_WMI_DCS_SEG_SEC20 |
+ ATH12K_WMI_DCS_SEG_SEC40)) {
+ ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+ "DCS interference event received with wrong chan width bmap 0x%x for 80 MHz",
+ info->chan_bw_interference_bitmap);
+ return false;
+ }
+ break;
+ case WMI_CHAN_WIDTH_160:
+ if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
+ ATH12K_WMI_DCS_SEG_SEC20 |
+ ATH12K_WMI_DCS_SEG_SEC40 |
+ ATH12K_WMI_DCS_SEG_SEC80)) {
+ ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+ "DCS interference event received with wrong chan width bmap 0x%x for 160 MHz",
+ info->chan_bw_interference_bitmap);
+ return false;
+ }
+ break;
+ case WMI_CHAN_WIDTH_320:
+ if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
+ ATH12K_WMI_DCS_SEG_SEC20 |
+ ATH12K_WMI_DCS_SEG_SEC40 |
+ ATH12K_WMI_DCS_SEG_SEC80 |
+ ATH12K_WMI_DCS_SEG_SEC160)) {
+ ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+ "DCS interference event received with wrong chan width bmap 0x%x for 320 MHz",
+ info->chan_bw_interference_bitmap);
+ return false;
+ }
+ break;
+ default:
+ ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+ "DCS interference event received with unknown channel width %u",
+ info->chan_width);
+ return false;
+ }
+ return true;
+}
+
+static u32
+ath12k_wmi_transform_interference_bitmap(int input_bitmap,
+ struct cfg80211_chan_def *chandef)
+{
+ u16 output_bits[ATH12K_MAX_20MHZ_SEGMENTS] = {};
+ u16 input_bits[ATH12K_MAX_20MHZ_SEGMENTS] = {};
+ u32 start_freq, segment_freq;
+ int primary_index = -1;
+ u32 output_bitmap = 0;
+ u16 num_sub_chans;
+ int bandwidth;
+
+ bandwidth = nl80211_chan_width_to_mhz(chandef->width);
+ if (bandwidth < 0)
+ return 0;
+
+ /*
+ * Firmware reports bit 0 as primary 20 MHz irrespective of absolute
+ * frequency position. Convert to standardized lowest-to-highest 20 MHz
+ * ordering expected by cfg80211/mac80211 userspace consumers.
+ */
+ num_sub_chans = bandwidth / 20;
+ start_freq = (chandef->center_freq1 - bandwidth / 2) + 10;
+
+ for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; i++) {
+ segment_freq = start_freq + (i * 20);
+ if (segment_freq == chandef->chan->center_freq) {
+ primary_index = i;
+ break;
+ }
+ }
+ if (primary_index == -1)
+ return 0;
+
+ for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; ++i)
+ input_bits[i] = BIT(i) & input_bitmap;
+
+ for (int i = 0; i < num_sub_chans; ++i) {
+ int src = i, dst = i;
+
+ switch (bandwidth) {
+ case 40:
+ if (primary_index == 1)
+ dst = 1 - i;
+ break;
+ case 80:
+ dst = intf_map_80[primary_index][i];
+ break;
+ case 160:
+ dst = intf_map_160[primary_index][i];
+ break;
+ case 320:
+ dst = intf_map_320[primary_index][i];
+ break;
+ }
+ output_bits[dst] = input_bits[src];
+ }
+
+ for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; ++i)
+ output_bitmap |= output_bits[i] ? BIT(i) : 0;
+
+ return output_bitmap;
+}
+
+static void
+ath12k_wmi_process_incumbent_signal_interference_evt(struct ath12k_base *ab,
+ struct sk_buff *skb,
+ const struct ath12k_wmi_intf_arg *intf_arg)
+{
+ struct ath12k_wmi_incumbent_signal_interference_arg info = {};
+ struct ath12k_incumbent_signal_interference *incumbent;
+ struct ath12k_mac_get_any_chanctx_conf_arg arg;
+ u32 transformed_intf_bitmap;
+ struct ieee80211_hw *hw;
+ struct ath12k *ar;
+ int ret;
+
+ guard(rcu)();
+
+ ar = ath12k_mac_get_ar_by_pdev_id(ab, intf_arg->pdev_id);
+ if (!ar) {
+ ath12k_warn(ab, "incumbent signal interference detected on invalid pdev %d\n",
+ intf_arg->pdev_id);
+ return;
+ }
+ if (!ar->supports_6ghz) {
+ ath12k_warn(ab, "pdev does not support 6 GHz, dropping DCS interference event\n");
+ return;
+ }
+
+ incumbent = &ar->incumbent_signal_interference;
+ spin_lock_bh(&ar->data_lock);
+ if (incumbent->handling_in_progress) {
+ spin_unlock_bh(&ar->data_lock);
+ ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+ "incumbent signal interference handling ongoing, dropping DCS interference event");
+ return;
+ }
+ spin_unlock_bh(&ar->data_lock);
+
+ ret = ath12k_wmi_tlv_iter(ab, skb->data, skb->len,
+ ath12k_wmi_dcs_interference_event_parser,
+ &info);
+ if (ret) {
+ ath12k_warn(ab,
+ "failed to parse incumbent signal interference TLV. Error %d\n",
+ ret);
+ return;
+ }
+
+ if (!ath12k_wmi_validate_interference_info(ar, &info)) {
+ ath12k_warn(ab, "invalid DCS incumbent signal interference TLV - Skipping event");
+ return;
+ }
+
+ arg.ar = ar;
+ arg.chanctx_conf = NULL;
+ hw = ath12k_ar_to_hw(ar);
+ ieee80211_iter_chan_contexts_atomic(hw,
+ ath12k_mac_get_any_chanctx_conf_iter,
+ &arg);
+ if (!arg.chanctx_conf) {
+ ath12k_warn(ab, "failed to find valid chanctx_conf in incumbent signal intf detected event\n");
+ return;
+ }
+
+ if (info.chan_freq != arg.chanctx_conf->def.chan->center_freq) {
+ ath12k_dbg(ab, ATH12K_DBG_WMI,
+ "dcs interference event received with wrong channel %d (ctx freq %d)",
+ info.chan_freq, arg.chanctx_conf->def.chan->center_freq);
+ return;
+ }
+
+ spin_lock_bh(&ar->data_lock);
+ incumbent->center_freq = arg.chanctx_conf->def.chan->center_freq;
+ incumbent->width = arg.chanctx_conf->def.width;
+ incumbent->chan_bw_interference_bitmap = info.chan_bw_interference_bitmap;
+ incumbent->handling_in_progress = true;
+ spin_unlock_bh(&ar->data_lock);
+ transformed_intf_bitmap =
+ ath12k_wmi_transform_interference_bitmap(info.chan_bw_interference_bitmap,
+ &arg.chanctx_conf->def);
+ ath12k_dbg(ab, ATH12K_DBG_WMI,
+ "incumbent signal interference bitmap 0x%x (transformed 0x%x)\n",
+ info.chan_bw_interference_bitmap, transformed_intf_bitmap);
+ cfg80211_incumbent_signal_notify(hw->wiphy,
+ &arg.chanctx_conf->def,
+ transformed_intf_bitmap,
+ GFP_ATOMIC);
+}
+
+static void
+ath12k_wmi_dcs_interference_event(struct ath12k_base *ab,
+ struct sk_buff *skb)
+{
+ const struct ath12k_wmi_dcs_interference_ev_fixed_params *dcs_intf_ev;
+ struct ath12k_wmi_intf_arg dcs_intf_arg;
+ const struct wmi_tlv *tlv;
+ u16 tlv_tag;
+ u8 *ptr;
+
+ if (skb->len < (sizeof(*dcs_intf_ev) + TLV_HDR_SIZE)) {
+ ath12k_warn(ab, "DCS interference event is of incorrect length\n");
+ return;
+ }
+
+ ptr = skb->data;
+ tlv = (struct wmi_tlv *)ptr;
+ tlv_tag = le32_get_bits(tlv->header, WMI_TLV_TAG);
+ ptr += sizeof(*tlv);
+
+ if (tlv_tag != WMI_TAG_DCS_INTERFERENCE_EVENT) {
+ ath12k_warn(ab, "DCS interference event received with wrong tag\n");
+ return;
+ }
+
+ dcs_intf_ev = (struct ath12k_wmi_dcs_interference_ev_fixed_params *)ptr;
+
+ dcs_intf_arg.interference_type =
+ le32_to_cpu(dcs_intf_ev->interference_type);
+ dcs_intf_arg.pdev_id = le32_to_cpu(dcs_intf_ev->pdev_id);
+
+ if (dcs_intf_arg.interference_type ==
+ ATH12K_WMI_DCS_INCUMBENT_SIGNAL_INTERFERENCE) {
+ ath12k_dbg(ab, ATH12K_DBG_WMI,
+ "incumbent signal interference (Type %u) detected on pdev %u.",
+ dcs_intf_arg.interference_type,
+ dcs_intf_arg.pdev_id);
+ ath12k_wmi_process_incumbent_signal_interference_evt(ab, skb,
+ &dcs_intf_arg);
+ }
+}
+
static void
ath12k_wmi_process_csa_switch_count_event(struct ath12k_base *ab,
const struct ath12k_wmi_pdev_csa_event *ev,
case WMI_OBSS_COLOR_COLLISION_DETECTION_EVENTID:
ath12k_wmi_obss_color_collision_event(ab, skb);
break;
+ case WMI_DCS_INTERFERENCE_EVENTID:
+ ath12k_wmi_dcs_interference_event(ab, skb);
+ break;
/* add Unsupported events (rare) here */
case WMI_TBTTOFFSET_EXT_UPDATE_EVENTID:
case WMI_PEER_OPER_MODE_CHANGE_EVENTID: