mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-06 21:14:18 +00:00

iwlmld was planned to be used for HR/GF, which has versions 5/6, but it was decided at the end to use iwlmvm for HR/GF, so iwlmld only needs to support version 8. Remove versions 5 and 6 support. Reviewed-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com> Link: https://patch.msgid.link/20250711183056.9c64bfbb16cb.I109bee4d4bf455cbffbb8d2340023338bcab886d@changeid
286 lines
7.0 KiB
C
286 lines
7.0 KiB
C
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
|
/*
|
|
* Copyright (C) 2024-2025 Intel Corporation
|
|
*/
|
|
|
|
#include <net/cfg80211.h>
|
|
#include <net/mac80211.h>
|
|
|
|
#include <fw/dbg.h>
|
|
#include <iwl-nvm-parse.h>
|
|
|
|
#include "mld.h"
|
|
#include "hcmd.h"
|
|
#include "mcc.h"
|
|
|
|
/* It is the caller's responsibility to free the pointer returned here */
|
|
static struct iwl_mcc_update_resp_v8 *
|
|
iwl_mld_copy_mcc_resp(const struct iwl_rx_packet *pkt)
|
|
{
|
|
const struct iwl_mcc_update_resp_v8 *mcc_resp_v8 = (const void *)pkt->data;
|
|
int n_channels = __le32_to_cpu(mcc_resp_v8->n_channels);
|
|
struct iwl_mcc_update_resp_v8 *resp_cp;
|
|
int notif_len = struct_size(resp_cp, channels, n_channels);
|
|
|
|
if (iwl_rx_packet_payload_len(pkt) != notif_len)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
resp_cp = kmemdup(mcc_resp_v8, notif_len, GFP_KERNEL);
|
|
if (!resp_cp)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
return resp_cp;
|
|
}
|
|
|
|
/* It is the caller's responsibility to free the pointer returned here */
|
|
static struct iwl_mcc_update_resp_v8 *
|
|
iwl_mld_update_mcc(struct iwl_mld *mld, const char *alpha2,
|
|
enum iwl_mcc_source src_id)
|
|
{
|
|
struct iwl_mcc_update_cmd mcc_update_cmd = {
|
|
.mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]),
|
|
.source_id = (u8)src_id,
|
|
};
|
|
struct iwl_mcc_update_resp_v8 *resp_cp;
|
|
struct iwl_rx_packet *pkt;
|
|
struct iwl_host_cmd cmd = {
|
|
.id = MCC_UPDATE_CMD,
|
|
.flags = CMD_WANT_SKB,
|
|
.data = { &mcc_update_cmd },
|
|
.len[0] = sizeof(mcc_update_cmd),
|
|
};
|
|
int ret;
|
|
u16 mcc;
|
|
|
|
IWL_DEBUG_LAR(mld, "send MCC update to FW with '%c%c' src = %d\n",
|
|
alpha2[0], alpha2[1], src_id);
|
|
|
|
ret = iwl_mld_send_cmd(mld, &cmd);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
pkt = cmd.resp_pkt;
|
|
|
|
resp_cp = iwl_mld_copy_mcc_resp(pkt);
|
|
if (IS_ERR(resp_cp))
|
|
goto exit;
|
|
|
|
mcc = le16_to_cpu(resp_cp->mcc);
|
|
|
|
IWL_FW_CHECK(mld, !mcc, "mcc can't be 0: %d\n", mcc);
|
|
|
|
IWL_DEBUG_LAR(mld,
|
|
"MCC response status: 0x%x. new MCC: 0x%x ('%c%c')\n",
|
|
le32_to_cpu(resp_cp->status), mcc, mcc >> 8, mcc & 0xff);
|
|
|
|
exit:
|
|
iwl_free_resp(&cmd);
|
|
return resp_cp;
|
|
}
|
|
|
|
/* It is the caller's responsibility to free the pointer returned here */
|
|
struct ieee80211_regdomain *
|
|
iwl_mld_get_regdomain(struct iwl_mld *mld,
|
|
const char *alpha2,
|
|
enum iwl_mcc_source src_id,
|
|
bool *changed)
|
|
{
|
|
struct ieee80211_regdomain *regd = NULL;
|
|
struct iwl_mcc_update_resp_v8 *resp;
|
|
u8 resp_ver = iwl_fw_lookup_notif_ver(mld->fw, IWL_ALWAYS_LONG_GROUP,
|
|
MCC_UPDATE_CMD, 0);
|
|
|
|
IWL_DEBUG_LAR(mld, "Getting regdomain data for %s from FW\n", alpha2);
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
resp = iwl_mld_update_mcc(mld, alpha2, src_id);
|
|
if (IS_ERR(resp)) {
|
|
IWL_DEBUG_LAR(mld, "Could not get update from FW %ld\n",
|
|
PTR_ERR(resp));
|
|
resp = NULL;
|
|
goto out;
|
|
}
|
|
|
|
if (changed) {
|
|
u32 status = le32_to_cpu(resp->status);
|
|
|
|
*changed = (status == MCC_RESP_NEW_CHAN_PROFILE ||
|
|
status == MCC_RESP_ILLEGAL);
|
|
}
|
|
IWL_DEBUG_LAR(mld, "MCC update response version: %d\n", resp_ver);
|
|
|
|
regd = iwl_parse_nvm_mcc_info(mld->trans,
|
|
__le32_to_cpu(resp->n_channels),
|
|
resp->channels,
|
|
__le16_to_cpu(resp->mcc),
|
|
__le16_to_cpu(resp->geo_info),
|
|
le32_to_cpu(resp->cap), resp_ver);
|
|
|
|
if (IS_ERR(regd)) {
|
|
IWL_DEBUG_LAR(mld, "Could not get parse update from FW %ld\n",
|
|
PTR_ERR(regd));
|
|
goto out;
|
|
}
|
|
|
|
IWL_DEBUG_LAR(mld, "setting alpha2 from FW to %s (0x%x, 0x%x) src=%d\n",
|
|
regd->alpha2, regd->alpha2[0],
|
|
regd->alpha2[1], resp->source_id);
|
|
|
|
mld->mcc_src = resp->source_id;
|
|
|
|
/* FM is the earliest supported and later always do puncturing */
|
|
if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) {
|
|
if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing,
|
|
le16_to_cpu(resp->mcc)))
|
|
ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING);
|
|
else
|
|
__clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING,
|
|
mld->hw->flags);
|
|
}
|
|
|
|
out:
|
|
kfree(resp);
|
|
return regd;
|
|
}
|
|
|
|
/* It is the caller's responsibility to free the pointer returned here */
|
|
static struct ieee80211_regdomain *
|
|
iwl_mld_get_current_regdomain(struct iwl_mld *mld,
|
|
bool *changed)
|
|
{
|
|
return iwl_mld_get_regdomain(mld, "ZZ",
|
|
MCC_SOURCE_GET_CURRENT, changed);
|
|
}
|
|
|
|
void iwl_mld_update_changed_regdomain(struct iwl_mld *mld)
|
|
{
|
|
struct ieee80211_regdomain *regd;
|
|
bool changed;
|
|
|
|
regd = iwl_mld_get_current_regdomain(mld, &changed);
|
|
|
|
if (IS_ERR_OR_NULL(regd))
|
|
return;
|
|
|
|
if (changed)
|
|
regulatory_set_wiphy_regd(mld->wiphy, regd);
|
|
kfree(regd);
|
|
}
|
|
|
|
static int iwl_mld_apply_last_mcc(struct iwl_mld *mld,
|
|
const char *alpha2)
|
|
{
|
|
struct ieee80211_regdomain *regd;
|
|
u32 used_src;
|
|
bool changed;
|
|
int ret;
|
|
|
|
/* save the last source in case we overwrite it below */
|
|
used_src = mld->mcc_src;
|
|
|
|
/* Notify the firmware we support wifi location updates */
|
|
regd = iwl_mld_get_current_regdomain(mld, NULL);
|
|
if (!IS_ERR_OR_NULL(regd))
|
|
kfree(regd);
|
|
|
|
/* Now set our last stored MCC and source */
|
|
regd = iwl_mld_get_regdomain(mld, alpha2, used_src,
|
|
&changed);
|
|
if (IS_ERR_OR_NULL(regd))
|
|
return -EIO;
|
|
|
|
/* update cfg80211 if the regdomain was changed */
|
|
if (changed)
|
|
ret = regulatory_set_wiphy_regd_sync(mld->wiphy, regd);
|
|
else
|
|
ret = 0;
|
|
|
|
kfree(regd);
|
|
return ret;
|
|
}
|
|
|
|
int iwl_mld_init_mcc(struct iwl_mld *mld)
|
|
{
|
|
const struct ieee80211_regdomain *r;
|
|
struct ieee80211_regdomain *regd;
|
|
char mcc[3];
|
|
int retval;
|
|
|
|
/* try to replay the last set MCC to FW */
|
|
r = wiphy_dereference(mld->wiphy, mld->wiphy->regd);
|
|
|
|
if (r)
|
|
return iwl_mld_apply_last_mcc(mld, r->alpha2);
|
|
|
|
regd = iwl_mld_get_current_regdomain(mld, NULL);
|
|
if (IS_ERR_OR_NULL(regd))
|
|
return -EIO;
|
|
|
|
if (!iwl_bios_get_mcc(&mld->fwrt, mcc)) {
|
|
kfree(regd);
|
|
regd = iwl_mld_get_regdomain(mld, mcc, MCC_SOURCE_BIOS, NULL);
|
|
if (IS_ERR_OR_NULL(regd))
|
|
return -EIO;
|
|
}
|
|
|
|
retval = regulatory_set_wiphy_regd_sync(mld->wiphy, regd);
|
|
|
|
kfree(regd);
|
|
return retval;
|
|
}
|
|
|
|
static void iwl_mld_find_assoc_vif_iterator(void *data, u8 *mac,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
bool *assoc = data;
|
|
|
|
if (vif->type == NL80211_IFTYPE_STATION &&
|
|
vif->cfg.assoc)
|
|
*assoc = true;
|
|
}
|
|
|
|
static bool iwl_mld_is_a_vif_assoc(struct iwl_mld *mld)
|
|
{
|
|
bool assoc = false;
|
|
|
|
ieee80211_iterate_active_interfaces_atomic(mld->hw,
|
|
IEEE80211_IFACE_ITER_NORMAL,
|
|
iwl_mld_find_assoc_vif_iterator,
|
|
&assoc);
|
|
return assoc;
|
|
}
|
|
|
|
void iwl_mld_handle_update_mcc(struct iwl_mld *mld, struct iwl_rx_packet *pkt)
|
|
{
|
|
struct iwl_mcc_chub_notif *notif = (void *)pkt->data;
|
|
enum iwl_mcc_source src;
|
|
char mcc[3];
|
|
struct ieee80211_regdomain *regd;
|
|
bool changed;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
if (iwl_mld_is_a_vif_assoc(mld) &&
|
|
notif->source_id == MCC_SOURCE_WIFI) {
|
|
IWL_DEBUG_LAR(mld, "Ignore mcc update while associated\n");
|
|
return;
|
|
}
|
|
|
|
mcc[0] = le16_to_cpu(notif->mcc) >> 8;
|
|
mcc[1] = le16_to_cpu(notif->mcc) & 0xff;
|
|
mcc[2] = '\0';
|
|
src = notif->source_id;
|
|
|
|
IWL_DEBUG_LAR(mld,
|
|
"RX: received chub update mcc cmd (mcc '%s' src %d)\n",
|
|
mcc, src);
|
|
regd = iwl_mld_get_regdomain(mld, mcc, src, &changed);
|
|
if (IS_ERR_OR_NULL(regd))
|
|
return;
|
|
|
|
if (changed)
|
|
regulatory_set_wiphy_regd(mld->hw->wiphy, regd);
|
|
kfree(regd);
|
|
}
|