/* RSSI in rx status of the receiver */
        int rx_rssi;
 
+       /* only used when pmsr capability is supplied */
+       struct cfg80211_pmsr_capabilities pmsr_capa;
+
        struct mac80211_hwsim_link_data link_data[IEEE80211_MLD_MAX_NUM_LINKS];
 };
 
 
 /* MAC80211_HWSIM netlink policy */
 
+static const struct nla_policy
+hwsim_ftm_capa_policy[NL80211_PMSR_FTM_CAPA_ATTR_MAX + 1] = {
+       [NL80211_PMSR_FTM_CAPA_ATTR_ASAP] = { .type = NLA_FLAG },
+       [NL80211_PMSR_FTM_CAPA_ATTR_NON_ASAP] = { .type = NLA_FLAG },
+       [NL80211_PMSR_FTM_CAPA_ATTR_REQ_LCI] = { .type = NLA_FLAG },
+       [NL80211_PMSR_FTM_CAPA_ATTR_REQ_CIVICLOC] = { .type = NLA_FLAG },
+       [NL80211_PMSR_FTM_CAPA_ATTR_PREAMBLES] = { .type = NLA_U32 },
+       [NL80211_PMSR_FTM_CAPA_ATTR_BANDWIDTHS] = { .type = NLA_U32 },
+       [NL80211_PMSR_FTM_CAPA_ATTR_MAX_BURSTS_EXPONENT] = NLA_POLICY_MAX(NLA_U8, 15),
+       [NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST] = NLA_POLICY_MAX(NLA_U8, 31),
+       [NL80211_PMSR_FTM_CAPA_ATTR_TRIGGER_BASED] = { .type = NLA_FLAG },
+       [NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED] = { .type = NLA_FLAG },
+};
+
+static const struct nla_policy
+hwsim_pmsr_capa_type_policy[NL80211_PMSR_TYPE_MAX + 1] = {
+       [NL80211_PMSR_TYPE_FTM] = NLA_POLICY_NESTED(hwsim_ftm_capa_policy),
+};
+
+static const struct nla_policy
+hwsim_pmsr_capa_policy[NL80211_PMSR_ATTR_MAX + 1] = {
+       [NL80211_PMSR_ATTR_MAX_PEERS] = { .type = NLA_U32 },
+       [NL80211_PMSR_ATTR_REPORT_AP_TSF] = { .type = NLA_FLAG },
+       [NL80211_PMSR_ATTR_RANDOMIZE_MAC_ADDR] = { .type = NLA_FLAG },
+       [NL80211_PMSR_ATTR_TYPE_CAPA] = NLA_POLICY_NESTED(hwsim_pmsr_capa_type_policy),
+       [NL80211_PMSR_ATTR_PEERS] = { .type = NLA_REJECT }, // only for request.
+};
+
 static const struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = {
        [HWSIM_ATTR_ADDR_RECEIVER] = NLA_POLICY_ETH_ADDR_COMPAT,
        [HWSIM_ATTR_ADDR_TRANSMITTER] = NLA_POLICY_ETH_ADDR_COMPAT,
        [HWSIM_ATTR_IFTYPE_SUPPORT] = { .type = NLA_U32 },
        [HWSIM_ATTR_CIPHER_SUPPORT] = { .type = NLA_BINARY },
        [HWSIM_ATTR_MLO_SUPPORT] = { .type = NLA_FLAG },
+       [HWSIM_ATTR_PMSR_SUPPORT] = NLA_POLICY_NESTED(hwsim_pmsr_capa_policy),
 };
 
 #if IS_REACHABLE(CONFIG_VIRTIO)
        u32 *ciphers;
        u8 n_ciphers;
        bool mlo;
+       const struct cfg80211_pmsr_capabilities *pmsr_capa;
 };
 
 static void hwsim_mcast_config_msg(struct sk_buff *mcast_skb,
                              NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS);
        wiphy_ext_feature_set(hw->wiphy,
                              NL80211_EXT_FEATURE_BEACON_RATE_LEGACY);
+       wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER);
 
        wiphy_ext_feature_set(hw->wiphy,
                              NL80211_EXT_FEATURE_SCAN_MIN_PREQ_CONTENT);
                                    data->debugfs,
                                    data, &hwsim_simulate_radar);
 
+       if (param->pmsr_capa) {
+               data->pmsr_capa = *param->pmsr_capa;
+               hw->wiphy->pmsr_capa = &data->pmsr_capa;
+       }
+
        spin_lock_bh(&hwsim_radio_lock);
        err = rhashtable_insert_fast(&hwsim_radios_rht, &data->rht,
                                     hwsim_rht_params);
        param.regd = data->regd;
        param.channels = data->channels;
        param.hwname = wiphy_name(data->hw->wiphy);
+       param.pmsr_capa = &data->pmsr_capa;
 
        res = append_radio_msg(skb, data->idx, ¶m);
        if (res < 0)
        return true;
 }
 
+static int parse_ftm_capa(const struct nlattr *ftm_capa, struct cfg80211_pmsr_capabilities *out,
+                         struct genl_info *info)
+{
+       struct nlattr *tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX + 1];
+       int ret;
+
+       ret = nla_parse_nested(tb, NL80211_PMSR_FTM_CAPA_ATTR_MAX, ftm_capa, hwsim_ftm_capa_policy,
+                              NULL);
+       if (ret) {
+               NL_SET_ERR_MSG_ATTR(info->extack, ftm_capa, "malformed FTM capability");
+               return -EINVAL;
+       }
+
+       out->ftm.supported = 1;
+       if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PREAMBLES])
+               out->ftm.preambles = nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PREAMBLES]);
+       if (tb[NL80211_PMSR_FTM_CAPA_ATTR_BANDWIDTHS])
+               out->ftm.bandwidths = nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_BANDWIDTHS]);
+       if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_BURSTS_EXPONENT])
+               out->ftm.max_bursts_exponent =
+                       nla_get_u8(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_BURSTS_EXPONENT]);
+       if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST])
+               out->ftm.max_ftms_per_burst =
+                       nla_get_u8(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST]);
+       out->ftm.asap = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_ASAP];
+       out->ftm.non_asap = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_NON_ASAP];
+       out->ftm.request_lci = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_REQ_LCI];
+       out->ftm.request_civicloc = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_REQ_CIVICLOC];
+       out->ftm.trigger_based = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_TRIGGER_BASED];
+       out->ftm.non_trigger_based = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED];
+
+       return 0;
+}
+
+static int parse_pmsr_capa(const struct nlattr *pmsr_capa, struct cfg80211_pmsr_capabilities *out,
+                          struct genl_info *info)
+{
+       struct nlattr *tb[NL80211_PMSR_ATTR_MAX + 1];
+       struct nlattr *nla;
+       int size;
+       int ret;
+
+       ret = nla_parse_nested(tb, NL80211_PMSR_ATTR_MAX, pmsr_capa, hwsim_pmsr_capa_policy, NULL);
+       if (ret) {
+               NL_SET_ERR_MSG_ATTR(info->extack, pmsr_capa, "malformed PMSR capability");
+               return -EINVAL;
+       }
+
+       if (tb[NL80211_PMSR_ATTR_MAX_PEERS])
+               out->max_peers = nla_get_u32(tb[NL80211_PMSR_ATTR_MAX_PEERS]);
+       out->report_ap_tsf = !!tb[NL80211_PMSR_ATTR_REPORT_AP_TSF];
+       out->randomize_mac_addr = !!tb[NL80211_PMSR_ATTR_RANDOMIZE_MAC_ADDR];
+
+       if (!tb[NL80211_PMSR_ATTR_TYPE_CAPA]) {
+               NL_SET_ERR_MSG_ATTR(info->extack, tb[NL80211_PMSR_ATTR_TYPE_CAPA],
+                                   "malformed PMSR type");
+               return -EINVAL;
+       }
+
+       nla_for_each_nested(nla, tb[NL80211_PMSR_ATTR_TYPE_CAPA], size) {
+               switch (nla_type(nla)) {
+               case NL80211_PMSR_TYPE_FTM:
+                       parse_ftm_capa(nla, out, info);
+                       break;
+               default:
+                       NL_SET_ERR_MSG_ATTR(info->extack, nla, "unsupported measurement type");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
 static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
 {
        struct hwsim_new_radio_params param = { 0 };
                param.hwname = hwname;
        }
 
+       if (info->attrs[HWSIM_ATTR_PMSR_SUPPORT]) {
+               struct cfg80211_pmsr_capabilities *pmsr_capa;
+
+               pmsr_capa = kmalloc(sizeof(*pmsr_capa), GFP_KERNEL);
+               if (!pmsr_capa) {
+                       ret = -ENOMEM;
+                       goto out_free;
+               }
+               ret = parse_pmsr_capa(info->attrs[HWSIM_ATTR_PMSR_SUPPORT], pmsr_capa, info);
+               if (ret)
+                       goto out_free;
+               param.pmsr_capa = pmsr_capa;
+       }
+
        ret = mac80211_hwsim_new_radio(info, ¶m);
+
+out_free:
        kfree(hwname);
+       kfree(param.pmsr_capa);
        return ret;
 }