Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4dd07d2b authored by Sergey Matyukevich's avatar Sergey Matyukevich Committed by Kalle Valo
Browse files

qtnfmac: updates for regulatory support



On startup driver obtains regulatory rules from firmware and
enables them during wiphy registration. Later on regulatory
domain change can be requested by host. In this case firmware
is notified about the upcoming changes. If the change is valid,
then firmware updates hardware channel configuration and host
driver receives updated channel info for each band.

Signed-off-by: default avatarIgor Mitsyanko <igor.mitsyanko.os@quantenna.com>
Signed-off-by: default avatarSergey Matyukevich <sergey.matyukevich.os@quantenna.com>
Signed-off-by: default avatarAvinash Patil <avinashp@quantenna.com>
Signed-off-by: default avatarKalle Valo <kvalo@codeaurora.org>
parent 1d9b168d
Loading
Loading
Loading
Loading
+26 −55
Original line number Diff line number Diff line
@@ -700,66 +700,43 @@ static struct cfg80211_ops qtn_cfg80211_ops = {
	.disconnect		= qtnf_disconnect
};

static void qtnf_cfg80211_reg_notifier(struct wiphy *wiphy,
static void qtnf_cfg80211_reg_notifier(struct wiphy *wiphy_in,
				       struct regulatory_request *req)
{
	struct qtnf_wmac *mac = wiphy_priv(wiphy);
	struct qtnf_bus *bus;
	struct qtnf_vif *vif;
	struct qtnf_wmac *chan_mac;
	int i;
	struct qtnf_wmac *mac = wiphy_priv(wiphy_in);
	struct qtnf_bus *bus = mac->bus;
	struct wiphy *wiphy;
	unsigned int mac_idx;
	enum nl80211_band band;

	bus = mac->bus;
	int ret;

	pr_debug("MAC%u: initiator=%d alpha=%c%c\n", mac->macid, req->initiator,
		 req->alpha2[0], req->alpha2[1]);

	vif = qtnf_mac_get_base_vif(mac);
	if (!vif) {
		pr_err("MAC%u: primary VIF is not configured\n", mac->macid);
		return;
	}

	/* ignore non-ISO3166 country codes */
	for (i = 0; i < sizeof(req->alpha2); i++) {
		if (req->alpha2[i] < 'A' || req->alpha2[i] > 'Z') {
			pr_err("MAC%u: not an ISO3166 code\n", mac->macid);
			return;
		}
	}
	if (!strncasecmp(req->alpha2, bus->hw_info.alpha2_code,
			 sizeof(req->alpha2))) {
		pr_warn("MAC%u: unchanged country code\n", mac->macid);
		return;
	}

	if (qtnf_cmd_send_regulatory_config(mac, req->alpha2)) {
		pr_err("MAC%u: failed to configure regulatory\n", mac->macid);
	ret = qtnf_cmd_reg_notify(bus, req);
	if (ret) {
		if (ret != -EOPNOTSUPP && ret != -EALREADY)
			pr_err("failed to update reg domain to %c%c\n",
			       req->alpha2[0], req->alpha2[1]);
		return;
	}

	for (i = 0; i < bus->hw_info.num_mac; i++) {
		chan_mac = bus->mac[i];

		if (!chan_mac)
	for (mac_idx = 0; mac_idx < QTNF_MAX_MAC; ++mac_idx) {
		if (!(bus->hw_info.mac_bitmap & (1 << mac_idx)))
			continue;

		if (!(bus->hw_info.mac_bitmap & BIT(i)))
			continue;
		mac = bus->mac[mac_idx];
		wiphy = priv_to_wiphy(mac);

		for (band = 0; band < NUM_NL80211_BANDS; ++band) {
			if (!wiphy->bands[band])
				continue;

			if (qtnf_cmd_get_mac_chan_info(chan_mac,
						       wiphy->bands[band])) {
				pr_err("MAC%u: can't get channel info\n",
				       chan_mac->macid);
				qtnf_core_detach(bus);

				return;
			}
			ret = qtnf_cmd_get_mac_chan_info(mac,
							 wiphy->bands[band]);
			if (ret)
				pr_err("failed to get chan info for mac %u band %u\n",
				       mac_idx, band);
		}
	}
}
@@ -844,10 +821,8 @@ int qtnf_wiphy_register(struct qtnf_hw_info *hw_info, struct qtnf_wmac *mac)
	}

	iface_comb = kzalloc(sizeof(*iface_comb), GFP_KERNEL);
	if (!iface_comb) {
		ret = -ENOMEM;
		goto out;
	}
	if (!iface_comb)
		return -ENOMEM;

	ret = qtnf_wiphy_setup_if_comb(wiphy, iface_comb, &mac->macinfo);
	if (ret)
@@ -889,21 +864,17 @@ int qtnf_wiphy_register(struct qtnf_hw_info *hw_info, struct qtnf_wmac *mac)
	ether_addr_copy(wiphy->perm_addr, mac->macaddr);

	if (hw_info->hw_capab & QLINK_HW_SUPPORTS_REG_UPDATE) {
		pr_debug("device supports REG_UPDATE\n");
		wiphy->regulatory_flags |= REGULATORY_STRICT_REG |
			REGULATORY_CUSTOM_REG;
		wiphy->reg_notifier = qtnf_cfg80211_reg_notifier;
		pr_debug("hint regulatory about EP region: %c%c\n",
			 hw_info->alpha2_code[0],
			 hw_info->alpha2_code[1]);
		regulatory_hint(wiphy, hw_info->alpha2_code);
		wiphy_apply_custom_regulatory(wiphy, hw_info->rd);
	} else {
		pr_debug("device doesn't support REG_UPDATE\n");
		wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED;
	}

	ret = wiphy_register(wiphy);

out:
	if (ret < 0) {
	if (ret) {
		kfree(iface_comb);
		return ret;
	}
+237 −41
Original line number Diff line number Diff line
@@ -181,38 +181,6 @@ int qtnf_cmd_send_start_ap(struct qtnf_vif *vif)
	return ret;
}

int qtnf_cmd_send_regulatory_config(struct qtnf_wmac *mac, const char *alpha2)
{
	struct sk_buff *cmd_skb;
	u16 res_code = QLINK_CMD_RESULT_OK;
	int ret;

	cmd_skb = qtnf_cmd_alloc_new_cmdskb(mac->macid, QLINK_VIFID_RSVD,
					    QLINK_CMD_REG_REGION,
					    sizeof(struct qlink_cmd));
	if (unlikely(!cmd_skb))
		return -ENOMEM;

	qtnf_cmd_skb_put_tlv_arr(cmd_skb, WLAN_EID_COUNTRY, alpha2,
				 QTNF_MAX_ALPHA_LEN);

	ret = qtnf_cmd_send(mac->bus, cmd_skb, &res_code);

	if (unlikely(ret))
		goto out;

	if (unlikely(res_code != QLINK_CMD_RESULT_OK)) {
		pr_err("MAC%u: CMD failed: %u\n", mac->macid, res_code);
		ret = -EFAULT;
		goto out;
	}

	memcpy(mac->bus->hw_info.alpha2_code, alpha2,
	       sizeof(mac->bus->hw_info.alpha2_code));
out:
	return ret;
}

int qtnf_cmd_send_config_ap(struct qtnf_vif *vif)
{
	struct sk_buff *cmd_skb;
@@ -848,25 +816,168 @@ int qtnf_cmd_send_del_intf(struct qtnf_vif *vif)
	return ret;
}

static u32 qtnf_cmd_resp_reg_rule_flags_parse(u32 qflags)
{
	u32 flags = 0;

	if (qflags & QLINK_RRF_NO_OFDM)
		flags |= NL80211_RRF_NO_OFDM;

	if (qflags & QLINK_RRF_NO_CCK)
		flags |= NL80211_RRF_NO_CCK;

	if (qflags & QLINK_RRF_NO_INDOOR)
		flags |= NL80211_RRF_NO_INDOOR;

	if (qflags & QLINK_RRF_NO_OUTDOOR)
		flags |= NL80211_RRF_NO_OUTDOOR;

	if (qflags & QLINK_RRF_DFS)
		flags |= NL80211_RRF_DFS;

	if (qflags & QLINK_RRF_PTP_ONLY)
		flags |= NL80211_RRF_PTP_ONLY;

	if (qflags & QLINK_RRF_PTMP_ONLY)
		flags |= NL80211_RRF_PTMP_ONLY;

	if (qflags & QLINK_RRF_NO_IR)
		flags |= NL80211_RRF_NO_IR;

	if (qflags & QLINK_RRF_AUTO_BW)
		flags |= NL80211_RRF_AUTO_BW;

	if (qflags & QLINK_RRF_IR_CONCURRENT)
		flags |= NL80211_RRF_IR_CONCURRENT;

	if (qflags & QLINK_RRF_NO_HT40MINUS)
		flags |= NL80211_RRF_NO_HT40MINUS;

	if (qflags & QLINK_RRF_NO_HT40PLUS)
		flags |= NL80211_RRF_NO_HT40PLUS;

	if (qflags & QLINK_RRF_NO_80MHZ)
		flags |= NL80211_RRF_NO_80MHZ;

	if (qflags & QLINK_RRF_NO_160MHZ)
		flags |= NL80211_RRF_NO_160MHZ;

	return flags;
}

static int
qtnf_cmd_resp_proc_hw_info(struct qtnf_bus *bus,
			   const struct qlink_resp_get_hw_info *resp)
			   const struct qlink_resp_get_hw_info *resp,
			   size_t info_len)
{
	struct qtnf_hw_info *hwinfo = &bus->hw_info;
	const struct qlink_tlv_hdr *tlv;
	const struct qlink_tlv_reg_rule *tlv_rule;
	struct ieee80211_reg_rule *rule;
	u16 tlv_type;
	u16 tlv_value_len;
	unsigned int rule_idx = 0;

	if (WARN_ON(resp->n_reg_rules > NL80211_MAX_SUPP_REG_RULES))
		return -E2BIG;

	hwinfo->rd = kzalloc(sizeof(*hwinfo->rd)
			     + sizeof(struct ieee80211_reg_rule)
			     * resp->n_reg_rules, GFP_KERNEL);

	if (!hwinfo->rd)
		return -ENOMEM;

	hwinfo->num_mac = resp->num_mac;
	hwinfo->mac_bitmap = resp->mac_bitmap;
	hwinfo->fw_ver = le32_to_cpu(resp->fw_ver);
	hwinfo->ql_proto_ver = le16_to_cpu(resp->ql_proto_ver);
	memcpy(hwinfo->alpha2_code, resp->alpha2_code,
	       sizeof(hwinfo->alpha2_code));
	hwinfo->total_tx_chain = resp->total_tx_chain;
	hwinfo->total_rx_chain = resp->total_rx_chain;
	hwinfo->hw_capab = le32_to_cpu(resp->hw_capab);
	hwinfo->rd->n_reg_rules = resp->n_reg_rules;
	hwinfo->rd->alpha2[0] = resp->alpha2[0];
	hwinfo->rd->alpha2[1] = resp->alpha2[1];

	switch (resp->dfs_region) {
	case QLINK_DFS_FCC:
		hwinfo->rd->dfs_region = NL80211_DFS_FCC;
		break;
	case QLINK_DFS_ETSI:
		hwinfo->rd->dfs_region = NL80211_DFS_ETSI;
		break;
	case QLINK_DFS_JP:
		hwinfo->rd->dfs_region = NL80211_DFS_JP;
		break;
	case QLINK_DFS_UNSET:
	default:
		hwinfo->rd->dfs_region = NL80211_DFS_UNSET;
		break;
	}

	tlv = (const struct qlink_tlv_hdr *)resp->info;

	while (info_len >= sizeof(*tlv)) {
		tlv_type = le16_to_cpu(tlv->type);
		tlv_value_len = le16_to_cpu(tlv->len);

		if (tlv_value_len + sizeof(*tlv) > info_len) {
			pr_warn("malformed TLV 0x%.2X; LEN: %u\n",
				tlv_type, tlv_value_len);
			return -EINVAL;
		}

		switch (tlv_type) {
		case QTN_TLV_ID_REG_RULE:
			if (rule_idx >= resp->n_reg_rules) {
				pr_warn("unexpected number of rules: %u\n",
					resp->n_reg_rules);
				return -EINVAL;
			}

			if (tlv_value_len != sizeof(*tlv_rule) - sizeof(*tlv)) {
				pr_warn("malformed TLV 0x%.2X; LEN: %u\n",
					tlv_type, tlv_value_len);
				return -EINVAL;
			}

			tlv_rule = (const struct qlink_tlv_reg_rule *)tlv;
			rule = &hwinfo->rd->reg_rules[rule_idx++];

			rule->freq_range.start_freq_khz =
				le32_to_cpu(tlv_rule->start_freq_khz);
			rule->freq_range.end_freq_khz =
				le32_to_cpu(tlv_rule->end_freq_khz);
			rule->freq_range.max_bandwidth_khz =
				le32_to_cpu(tlv_rule->max_bandwidth_khz);
			rule->power_rule.max_antenna_gain =
				le32_to_cpu(tlv_rule->max_antenna_gain);
			rule->power_rule.max_eirp =
				le32_to_cpu(tlv_rule->max_eirp);
			rule->dfs_cac_ms =
				le32_to_cpu(tlv_rule->dfs_cac_ms);
			rule->flags = qtnf_cmd_resp_reg_rule_flags_parse(
					le32_to_cpu(tlv_rule->flags));
			break;
		default:
			break;
		}

		info_len -= tlv_value_len + sizeof(*tlv);
		tlv = (struct qlink_tlv_hdr *)(tlv->val + tlv_value_len);
	}

	if (rule_idx != resp->n_reg_rules) {
		pr_warn("unexpected number of rules: expected %u got %u\n",
			resp->n_reg_rules, rule_idx);
		kfree(hwinfo->rd);
		hwinfo->rd = NULL;
		return -EINVAL;
	}

	pr_info("fw_version=%d, MACs map %#x, alpha2=\"%c%c\", chains Tx=%u Rx=%u\n",
		hwinfo->fw_ver, hwinfo->mac_bitmap,
		hwinfo->alpha2_code[0], hwinfo->alpha2_code[1],
		hwinfo->rd->alpha2[0], hwinfo->rd->alpha2[1],
		hwinfo->total_tx_chain, hwinfo->total_rx_chain);

	return 0;
@@ -1013,14 +1124,24 @@ qtnf_cmd_resp_fill_channels_info(struct ieee80211_supported_band *band,
	unsigned int chidx = 0;
	u32 qflags;

	if (band->channels) {
		if (band->n_channels == resp->num_chans) {
			memset(band->channels, 0,
			       sizeof(*band->channels) * band->n_channels);
		} else {
			kfree(band->channels);
			band->n_channels = 0;
			band->channels = NULL;
		}
	}

	band->n_channels = resp->num_chans;
	if (band->n_channels == 0)
		return 0;

	band->channels = kcalloc(band->n_channels, sizeof(*chan), GFP_KERNEL);
	if (!band->channels)
		band->channels = kcalloc(band->n_channels, sizeof(*chan),
					 GFP_KERNEL);
	if (!band->channels) {
		band->n_channels = 0;
		return -ENOMEM;
@@ -1256,6 +1377,7 @@ int qtnf_cmd_get_hw_info(struct qtnf_bus *bus)
	const struct qlink_resp_get_hw_info *resp;
	u16 res_code = QLINK_CMD_RESULT_OK;
	int ret = 0;
	size_t info_len;

	cmd_skb = qtnf_cmd_alloc_new_cmdskb(QLINK_MACID_RSVD, QLINK_VIFID_RSVD,
					    QLINK_CMD_GET_HW_INFO,
@@ -1266,7 +1388,7 @@ int qtnf_cmd_get_hw_info(struct qtnf_bus *bus)
	qtnf_bus_lock(bus);

	ret = qtnf_cmd_send_with_reply(bus, cmd_skb, &resp_skb, &res_code,
				       sizeof(*resp), NULL);
				       sizeof(*resp), &info_len);

	if (unlikely(ret))
		goto out;
@@ -1278,7 +1400,7 @@ int qtnf_cmd_get_hw_info(struct qtnf_bus *bus)
	}

	resp = (const struct qlink_resp_get_hw_info *)resp_skb->data;
	ret = qtnf_cmd_resp_proc_hw_info(bus, resp);
	ret = qtnf_cmd_resp_proc_hw_info(bus, resp, info_len);

out:
	qtnf_bus_unlock(bus);
@@ -1976,3 +2098,77 @@ int qtnf_cmd_send_updown_intf(struct qtnf_vif *vif, bool up)
	qtnf_bus_unlock(vif->mac->bus);
	return ret;
}

int qtnf_cmd_reg_notify(struct qtnf_bus *bus, struct regulatory_request *req)
{
	struct sk_buff *cmd_skb;
	int ret;
	u16 res_code;
	struct qlink_cmd_reg_notify *cmd;

	cmd_skb = qtnf_cmd_alloc_new_cmdskb(QLINK_MACID_RSVD, QLINK_VIFID_RSVD,
					    QLINK_CMD_REG_NOTIFY,
					    sizeof(*cmd));
	if (!cmd_skb)
		return -ENOMEM;

	cmd = (struct qlink_cmd_reg_notify *)cmd_skb->data;
	cmd->alpha2[0] = req->alpha2[0];
	cmd->alpha2[1] = req->alpha2[1];

	switch (req->initiator) {
	case NL80211_REGDOM_SET_BY_CORE:
		cmd->initiator = QLINK_REGDOM_SET_BY_CORE;
		break;
	case NL80211_REGDOM_SET_BY_USER:
		cmd->initiator = QLINK_REGDOM_SET_BY_USER;
		break;
	case NL80211_REGDOM_SET_BY_DRIVER:
		cmd->initiator = QLINK_REGDOM_SET_BY_DRIVER;
		break;
	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
		cmd->initiator = QLINK_REGDOM_SET_BY_COUNTRY_IE;
		break;
	}

	switch (req->user_reg_hint_type) {
	case NL80211_USER_REG_HINT_USER:
		cmd->user_reg_hint_type = QLINK_USER_REG_HINT_USER;
		break;
	case NL80211_USER_REG_HINT_CELL_BASE:
		cmd->user_reg_hint_type = QLINK_USER_REG_HINT_CELL_BASE;
		break;
	case NL80211_USER_REG_HINT_INDOOR:
		cmd->user_reg_hint_type = QLINK_USER_REG_HINT_INDOOR;
		break;
	}

	qtnf_bus_lock(bus);

	ret = qtnf_cmd_send(bus, cmd_skb, &res_code);
	if (ret)
		goto out;

	switch (res_code) {
	case QLINK_CMD_RESULT_ENOTSUPP:
		pr_warn("reg update not supported\n");
		ret = -EOPNOTSUPP;
		break;
	case QLINK_CMD_RESULT_EALREADY:
		pr_info("regulatory domain is already set to %c%c",
			req->alpha2[0], req->alpha2[1]);
		ret = -EALREADY;
		break;
	case QLINK_CMD_RESULT_OK:
		ret = 0;
		break;
	default:
		ret = -EFAULT;
		break;
	}

out:
	qtnf_bus_unlock(bus);

	return ret;
}
+1 −0
Original line number Diff line number Diff line
@@ -70,5 +70,6 @@ int qtnf_cmd_send_disconnect(struct qtnf_vif *vif,
			     u16 reason_code);
int qtnf_cmd_send_updown_intf(struct qtnf_vif *vif,
			      bool up);
int qtnf_cmd_reg_notify(struct qtnf_bus *bus, struct regulatory_request *req);

#endif /* QLINK_COMMANDS_H_ */
+3 −0
Original line number Diff line number Diff line
@@ -549,6 +549,9 @@ void qtnf_core_detach(struct qtnf_bus *bus)
		destroy_workqueue(bus->workqueue);
	}

	kfree(bus->hw_info.rd);
	bus->hw_info.rd = NULL;

	qtnf_trans_free(bus);
}
EXPORT_SYMBOL_GPL(qtnf_core_detach);
+3 −4
Original line number Diff line number Diff line
@@ -42,7 +42,6 @@

#define QTNF_MAX_SSID_LIST_LENGTH	2
#define QTNF_MAX_VSIE_LEN		255
#define QTNF_MAX_ALPHA_LEN		2
#define QTNF_MAX_INTF			8
#define QTNF_MAX_EVENT_QUEUE_LEN	255
#define QTNF_DEFAULT_BG_SCAN_PERIOD	300
@@ -136,14 +135,14 @@ struct qtnf_wmac {
};

struct qtnf_hw_info {
	u16 ql_proto_ver;
	u8 num_mac;
	u8 mac_bitmap;
	u8 alpha2_code[QTNF_MAX_ALPHA_LEN];
	u32 fw_ver;
	u16 ql_proto_ver;
	u32 hw_capab;
	struct ieee80211_regdomain *rd;
	u8 total_tx_chain;
	u8 total_rx_chain;
	u32 hw_capab;
};

struct qtnf_vif *qtnf_mac_get_free_vif(struct qtnf_wmac *mac);
Loading