xref: /openbmc/linux/net/mac80211/mesh_plink.c (revision 3e7759b94a0fcfdd6771caa64a37dda7ce825874)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2c3896d2cSLuis Carlos Cobo /*
3264d9b7dSRui Paulo  * Copyright (c) 2008, 2009 open80211s Ltd.
415ddba5fSAnjaneyulu  * Copyright (C) 2019, 2021-2023 Intel Corporation
5c3896d2cSLuis Carlos Cobo  * Author:     Luis Carlos Cobo <luisca@cozybit.com>
6c3896d2cSLuis Carlos Cobo  */
75a0e3ad6STejun Heo #include <linux/gfp.h>
8902acc78SJohannes Berg #include <linux/kernel.h>
9902acc78SJohannes Berg #include <linux/random.h>
10b2d09103SIngo Molnar #include <linux/rculist.h>
11b2d09103SIngo Molnar 
12c3896d2cSLuis Carlos Cobo #include "ieee80211_i.h"
132c8dccc7SJohannes Berg #include "rate.h"
14c3896d2cSLuis Carlos Cobo #include "mesh.h"
15c3896d2cSLuis Carlos Cobo 
16a69bd8e6SBob Copeland #define PLINK_CNF_AID(mgmt) ((mgmt)->u.action.u.self_prot.variable + 2)
178db09850SThomas Pedersen #define PLINK_GET_LLID(p) (p + 2)
188db09850SThomas Pedersen #define PLINK_GET_PLID(p) (p + 4)
19c3896d2cSLuis Carlos Cobo 
20433f5bc1SJohannes Berg #define mod_plink_timer(s, t) (mod_timer(&s->mesh->plink_timer, \
21cc57ac53SNicholas Mc Guire 				jiffies + msecs_to_jiffies(t)))
22c3896d2cSLuis Carlos Cobo 
23c3896d2cSLuis Carlos Cobo enum plink_event {
24c3896d2cSLuis Carlos Cobo 	PLINK_UNDEFINED,
25c3896d2cSLuis Carlos Cobo 	OPN_ACPT,
26c3896d2cSLuis Carlos Cobo 	OPN_RJCT,
27c3896d2cSLuis Carlos Cobo 	OPN_IGNR,
28c3896d2cSLuis Carlos Cobo 	CNF_ACPT,
29c3896d2cSLuis Carlos Cobo 	CNF_RJCT,
30c3896d2cSLuis Carlos Cobo 	CNF_IGNR,
31c3896d2cSLuis Carlos Cobo 	CLS_ACPT,
32c3896d2cSLuis Carlos Cobo 	CLS_IGNR
33c3896d2cSLuis Carlos Cobo };
34c3896d2cSLuis Carlos Cobo 
3595e48addSThomas Pedersen static const char * const mplstates[] = {
3695e48addSThomas Pedersen 	[NL80211_PLINK_LISTEN] = "LISTEN",
3795e48addSThomas Pedersen 	[NL80211_PLINK_OPN_SNT] = "OPN-SNT",
3895e48addSThomas Pedersen 	[NL80211_PLINK_OPN_RCVD] = "OPN-RCVD",
3995e48addSThomas Pedersen 	[NL80211_PLINK_CNF_RCVD] = "CNF_RCVD",
4095e48addSThomas Pedersen 	[NL80211_PLINK_ESTAB] = "ESTAB",
4195e48addSThomas Pedersen 	[NL80211_PLINK_HOLDING] = "HOLDING",
4295e48addSThomas Pedersen 	[NL80211_PLINK_BLOCKED] = "BLOCKED"
4395e48addSThomas Pedersen };
4495e48addSThomas Pedersen 
4595e48addSThomas Pedersen static const char * const mplevents[] = {
4695e48addSThomas Pedersen 	[PLINK_UNDEFINED] = "NONE",
4795e48addSThomas Pedersen 	[OPN_ACPT] = "OPN_ACPT",
4895e48addSThomas Pedersen 	[OPN_RJCT] = "OPN_RJCT",
4995e48addSThomas Pedersen 	[OPN_IGNR] = "OPN_IGNR",
5095e48addSThomas Pedersen 	[CNF_ACPT] = "CNF_ACPT",
5195e48addSThomas Pedersen 	[CNF_RJCT] = "CNF_RJCT",
5295e48addSThomas Pedersen 	[CNF_IGNR] = "CNF_IGNR",
5395e48addSThomas Pedersen 	[CLS_ACPT] = "CLS_ACPT",
5495e48addSThomas Pedersen 	[CLS_IGNR] = "CLS_IGNR"
5595e48addSThomas Pedersen };
5695e48addSThomas Pedersen 
5736c9bb29SBob Copeland /* We only need a valid sta if user configured a minimum rssi_threshold. */
rssi_threshold_check(struct ieee80211_sub_if_data * sdata,struct sta_info * sta)5836c9bb29SBob Copeland static bool rssi_threshold_check(struct ieee80211_sub_if_data *sdata,
5936c9bb29SBob Copeland 				 struct sta_info *sta)
6036c9bb29SBob Copeland {
6136c9bb29SBob Copeland 	s32 rssi_threshold = sdata->u.mesh.mshcfg.rssi_threshold;
6236c9bb29SBob Copeland 	return rssi_threshold == 0 ||
63e5a9f8d0SJohannes Berg 	       (sta &&
64046d2e7cSSriram R 		(s8)-ewma_signal_read(&sta->deflink.rx_stats_avg.signal) >
65e5a9f8d0SJohannes Berg 		rssi_threshold);
6636c9bb29SBob Copeland }
6736c9bb29SBob Copeland 
68c3896d2cSLuis Carlos Cobo /**
69c3896d2cSLuis Carlos Cobo  * mesh_plink_fsm_restart - restart a mesh peer link finite state machine
70c3896d2cSLuis Carlos Cobo  *
7123c7a29cSRui Paulo  * @sta: mesh peer link to restart
72c3896d2cSLuis Carlos Cobo  *
73433f5bc1SJohannes Berg  * Locking: this function must be called holding sta->mesh->plink_lock
74c3896d2cSLuis Carlos Cobo  */
mesh_plink_fsm_restart(struct sta_info * sta)75c3896d2cSLuis Carlos Cobo static inline void mesh_plink_fsm_restart(struct sta_info *sta)
76c3896d2cSLuis Carlos Cobo {
77433f5bc1SJohannes Berg 	lockdep_assert_held(&sta->mesh->plink_lock);
78433f5bc1SJohannes Berg 	sta->mesh->plink_state = NL80211_PLINK_LISTEN;
79433f5bc1SJohannes Berg 	sta->mesh->llid = sta->mesh->plid = sta->mesh->reason = 0;
80433f5bc1SJohannes Berg 	sta->mesh->plink_retries = 0;
81c3896d2cSLuis Carlos Cobo }
82c3896d2cSLuis Carlos Cobo 
833b144658SThomas Pedersen /*
843b144658SThomas Pedersen  * mesh_set_short_slot_time - enable / disable ERP short slot time.
853b144658SThomas Pedersen  *
863b144658SThomas Pedersen  * The standard indirectly mandates mesh STAs to turn off short slot time by
873b144658SThomas Pedersen  * disallowing advertising this (802.11-2012 8.4.1.4), but that doesn't mean we
883b144658SThomas Pedersen  * can't be sneaky about it. Enable short slot time if all mesh STAs in the
893b144658SThomas Pedersen  * MBSS support ERP rates.
903b144658SThomas Pedersen  *
913b144658SThomas Pedersen  * Returns BSS_CHANGED_ERP_SLOT or 0 for no change.
923b144658SThomas Pedersen  */
mesh_set_short_slot_time(struct ieee80211_sub_if_data * sdata)9315ddba5fSAnjaneyulu static u64 mesh_set_short_slot_time(struct ieee80211_sub_if_data *sdata)
943b144658SThomas Pedersen {
953b144658SThomas Pedersen 	struct ieee80211_local *local = sdata->local;
9621a8e9ddSMohammed Shafi Shajakhan 	struct ieee80211_supported_band *sband;
973b144658SThomas Pedersen 	struct sta_info *sta;
9815ddba5fSAnjaneyulu 	u32 erp_rates = 0;
9915ddba5fSAnjaneyulu 	u64 changed = 0;
1003b144658SThomas Pedersen 	int i;
1013b144658SThomas Pedersen 	bool short_slot = false;
1023b144658SThomas Pedersen 
10321a8e9ddSMohammed Shafi Shajakhan 	sband = ieee80211_get_sband(sdata);
10421a8e9ddSMohammed Shafi Shajakhan 	if (!sband)
10521a8e9ddSMohammed Shafi Shajakhan 		return changed;
10621a8e9ddSMohammed Shafi Shajakhan 
10721a8e9ddSMohammed Shafi Shajakhan 	if (sband->band == NL80211_BAND_5GHZ) {
1083b144658SThomas Pedersen 		/* (IEEE 802.11-2012 19.4.5) */
1093b144658SThomas Pedersen 		short_slot = true;
1103b144658SThomas Pedersen 		goto out;
11121a8e9ddSMohammed Shafi Shajakhan 	} else if (sband->band != NL80211_BAND_2GHZ) {
1123b144658SThomas Pedersen 		goto out;
11321a8e9ddSMohammed Shafi Shajakhan 	}
1143b144658SThomas Pedersen 
1153b144658SThomas Pedersen 	for (i = 0; i < sband->n_bitrates; i++)
1163b144658SThomas Pedersen 		if (sband->bitrates[i].flags & IEEE80211_RATE_ERP_G)
1173b144658SThomas Pedersen 			erp_rates |= BIT(i);
1183b144658SThomas Pedersen 
1193b144658SThomas Pedersen 	if (!erp_rates)
1203b144658SThomas Pedersen 		goto out;
1213b144658SThomas Pedersen 
1223b144658SThomas Pedersen 	rcu_read_lock();
1233b144658SThomas Pedersen 	list_for_each_entry_rcu(sta, &local->sta_list, list) {
1243b144658SThomas Pedersen 		if (sdata != sta->sdata ||
125433f5bc1SJohannes Berg 		    sta->mesh->plink_state != NL80211_PLINK_ESTAB)
1263b144658SThomas Pedersen 			continue;
1273b144658SThomas Pedersen 
1283b144658SThomas Pedersen 		short_slot = false;
129046d2e7cSSriram R 		if (erp_rates & sta->sta.deflink.supp_rates[sband->band])
1303b144658SThomas Pedersen 			short_slot = true;
1313b144658SThomas Pedersen 		 else
1323b144658SThomas Pedersen 			break;
1333b144658SThomas Pedersen 	}
1343b144658SThomas Pedersen 	rcu_read_unlock();
1353b144658SThomas Pedersen 
1363b144658SThomas Pedersen out:
1373b144658SThomas Pedersen 	if (sdata->vif.bss_conf.use_short_slot != short_slot) {
1383b144658SThomas Pedersen 		sdata->vif.bss_conf.use_short_slot = short_slot;
1393b144658SThomas Pedersen 		changed = BSS_CHANGED_ERP_SLOT;
1403b144658SThomas Pedersen 		mpl_dbg(sdata, "mesh_plink %pM: ERP short slot time %d\n",
1413b144658SThomas Pedersen 			sdata->vif.addr, short_slot);
1423b144658SThomas Pedersen 	}
1433b144658SThomas Pedersen 	return changed;
1443b144658SThomas Pedersen }
1453b144658SThomas Pedersen 
1462c53040fSBen Hutchings /**
147cbf9322eSAshok Nagarajan  * mesh_set_ht_prot_mode - set correct HT protection mode
14821439b65SJohannes Berg  * @sdata: the (mesh) interface to handle
14957aac7c5SAshok Nagarajan  *
150cbf9322eSAshok Nagarajan  * Section 9.23.3.5 of IEEE 80211-2012 describes the protection rules for HT
151cbf9322eSAshok Nagarajan  * mesh STA in a MBSS. Three HT protection modes are supported for now, non-HT
152cbf9322eSAshok Nagarajan  * mixed mode, 20MHz-protection and no-protection mode. non-HT mixed mode is
153cbf9322eSAshok Nagarajan  * selected if any non-HT peers are present in our MBSS.  20MHz-protection mode
154cbf9322eSAshok Nagarajan  * is selected if all peers in our 20/40MHz MBSS support HT and at least one
155cbf9322eSAshok Nagarajan  * HT20 peer is present. Otherwise no-protection mode is selected.
15657aac7c5SAshok Nagarajan  */
mesh_set_ht_prot_mode(struct ieee80211_sub_if_data * sdata)15715ddba5fSAnjaneyulu static u64 mesh_set_ht_prot_mode(struct ieee80211_sub_if_data *sdata)
15857aac7c5SAshok Nagarajan {
15957aac7c5SAshok Nagarajan 	struct ieee80211_local *local = sdata->local;
16057aac7c5SAshok Nagarajan 	struct sta_info *sta;
16157aac7c5SAshok Nagarajan 	u16 ht_opmode;
16257aac7c5SAshok Nagarajan 	bool non_ht_sta = false, ht20_sta = false;
16357aac7c5SAshok Nagarajan 
1640418a445SSimon Wunderlich 	switch (sdata->vif.bss_conf.chandef.width) {
1650418a445SSimon Wunderlich 	case NL80211_CHAN_WIDTH_20_NOHT:
1660418a445SSimon Wunderlich 	case NL80211_CHAN_WIDTH_5:
1670418a445SSimon Wunderlich 	case NL80211_CHAN_WIDTH_10:
16857aac7c5SAshok Nagarajan 		return 0;
1690418a445SSimon Wunderlich 	default:
1700418a445SSimon Wunderlich 		break;
1710418a445SSimon Wunderlich 	}
17257aac7c5SAshok Nagarajan 
17357aac7c5SAshok Nagarajan 	rcu_read_lock();
17457aac7c5SAshok Nagarajan 	list_for_each_entry_rcu(sta, &local->sta_list, list) {
175cbf9322eSAshok Nagarajan 		if (sdata != sta->sdata ||
176433f5bc1SJohannes Berg 		    sta->mesh->plink_state != NL80211_PLINK_ESTAB)
177cbf9322eSAshok Nagarajan 			continue;
178cbf9322eSAshok Nagarajan 
179046d2e7cSSriram R 		if (sta->sta.deflink.bandwidth > IEEE80211_STA_RX_BW_20)
18052ac8c48SThomas Pedersen 			continue;
18152ac8c48SThomas Pedersen 
182046d2e7cSSriram R 		if (!sta->sta.deflink.ht_cap.ht_supported) {
18352ac8c48SThomas Pedersen 			mpl_dbg(sdata, "nonHT sta (%pM) is present\n",
18452ac8c48SThomas Pedersen 				       sta->sta.addr);
18557aac7c5SAshok Nagarajan 			non_ht_sta = true;
18657aac7c5SAshok Nagarajan 			break;
18757aac7c5SAshok Nagarajan 		}
18852ac8c48SThomas Pedersen 
18952ac8c48SThomas Pedersen 		mpl_dbg(sdata, "HT20 sta (%pM) is present\n", sta->sta.addr);
19052ac8c48SThomas Pedersen 		ht20_sta = true;
19157aac7c5SAshok Nagarajan 	}
19257aac7c5SAshok Nagarajan 	rcu_read_unlock();
19357aac7c5SAshok Nagarajan 
19457aac7c5SAshok Nagarajan 	if (non_ht_sta)
19557aac7c5SAshok Nagarajan 		ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED;
196466f310dSJohannes Berg 	else if (ht20_sta &&
1974bf88530SJohannes Berg 		 sdata->vif.bss_conf.chandef.width > NL80211_CHAN_WIDTH_20)
19857aac7c5SAshok Nagarajan 		ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_20MHZ;
19957aac7c5SAshok Nagarajan 	else
20057aac7c5SAshok Nagarajan 		ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_NONE;
20157aac7c5SAshok Nagarajan 
20252ac8c48SThomas Pedersen 	if (sdata->vif.bss_conf.ht_operation_mode == ht_opmode)
20352ac8c48SThomas Pedersen 		return 0;
20452ac8c48SThomas Pedersen 
20557aac7c5SAshok Nagarajan 	sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
20670c33eaaSAshok Nagarajan 	sdata->u.mesh.mshcfg.ht_opmode = ht_opmode;
20752ac8c48SThomas Pedersen 	mpl_dbg(sdata, "selected new HT protection mode %d\n", ht_opmode);
20852ac8c48SThomas Pedersen 	return BSS_CHANGED_HT;
20957aac7c5SAshok Nagarajan }
21057aac7c5SAshok Nagarajan 
mesh_plink_frame_tx(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,enum ieee80211_self_protected_actioncode action,u8 * da,u16 llid,u16 plid,u16 reason)211f698d856SJasper Bryant-Greene static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
212a69bd8e6SBob Copeland 			       struct sta_info *sta,
21354ef656bSThomas Pedersen 			       enum ieee80211_self_protected_actioncode action,
2146f101ef0SChun-Yeow Yeoh 			       u8 *da, u16 llid, u16 plid, u16 reason)
215bf7cd94dSJohannes Berg {
216f698d856SJasper Bryant-Greene 	struct ieee80211_local *local = sdata->local;
2173b69a9c5SThomas Pedersen 	struct sk_buff *skb;
218e7570dfbSThomas Pedersen 	struct ieee80211_tx_info *info;
219c3896d2cSLuis Carlos Cobo 	struct ieee80211_mgmt *mgmt;
220c3896d2cSLuis Carlos Cobo 	bool include_plid = false;
2218db09850SThomas Pedersen 	u16 peering_proto = 0;
2223b69a9c5SThomas Pedersen 	u8 *pos, ie_len = 4;
223df1875c4SRyder Lee 	u8 ie_len_he_cap, ie_len_eht_cap;
2244c121fd6SJohannes Berg 	int hdr_len = offsetofend(struct ieee80211_mgmt, u.action.u.self_prot);
225f609a43dSThomas Pedersen 	int err = -ENOMEM;
226c3896d2cSLuis Carlos Cobo 
22760ad72daSSven Eckelmann 	ie_len_he_cap = ieee80211_ie_len_he_cap(sdata,
22860ad72daSSven Eckelmann 						NL80211_IFTYPE_MESH_POINT);
229df1875c4SRyder Lee 	ie_len_eht_cap = ieee80211_ie_len_eht_cap(sdata,
230df1875c4SRyder Lee 						  NL80211_IFTYPE_MESH_POINT);
23165e8b0ccSJavier Cardona 	skb = dev_alloc_skb(local->tx_headroom +
2323b69a9c5SThomas Pedersen 			    hdr_len +
2333b69a9c5SThomas Pedersen 			    2 + /* capability info */
2343b69a9c5SThomas Pedersen 			    2 + /* AID */
2353b69a9c5SThomas Pedersen 			    2 + 8 + /* supported rates */
2363b69a9c5SThomas Pedersen 			    2 + (IEEE80211_MAX_SUPP_RATES - 8) +
2373b69a9c5SThomas Pedersen 			    2 + sdata->u.mesh.mesh_id_len +
2383b69a9c5SThomas Pedersen 			    2 + sizeof(struct ieee80211_meshconf_ie) +
239176f3608SThomas Pedersen 			    2 + sizeof(struct ieee80211_ht_cap) +
240074d46d1SJohannes Berg 			    2 + sizeof(struct ieee80211_ht_operation) +
241c85fb53cSBob Copeland 			    2 + sizeof(struct ieee80211_vht_cap) +
242c85fb53cSBob Copeland 			    2 + sizeof(struct ieee80211_vht_operation) +
24360ad72daSSven Eckelmann 			    ie_len_he_cap +
24460ad72daSSven Eckelmann 			    2 + 1 + sizeof(struct ieee80211_he_operation) +
245d1b7524bSRajkumar Manoharan 				    sizeof(struct ieee80211_he_6ghz_oper) +
24624a2042cSRajkumar Manoharan 			    2 + 1 + sizeof(struct ieee80211_he_6ghz_capa) +
247df1875c4SRyder Lee 			    ie_len_eht_cap +
248df1875c4SRyder Lee 			    2 + 1 + offsetof(struct ieee80211_eht_operation, optional) +
249df1875c4SRyder Lee 				    offsetof(struct ieee80211_eht_operation_info, optional) +
2503b69a9c5SThomas Pedersen 			    2 + 8 + /* peering IE */
2513b69a9c5SThomas Pedersen 			    sdata->u.mesh.ie_len);
252c3896d2cSLuis Carlos Cobo 	if (!skb)
25387d84c45SBob Copeland 		return err;
254e7570dfbSThomas Pedersen 	info = IEEE80211_SKB_CB(skb);
25565e8b0ccSJavier Cardona 	skb_reserve(skb, local->tx_headroom);
256b080db58SJohannes Berg 	mgmt = skb_put_zero(skb, hdr_len);
257e7827a70SHarvey Harrison 	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
258c3896d2cSLuis Carlos Cobo 					  IEEE80211_STYPE_ACTION);
259c3896d2cSLuis Carlos Cobo 	memcpy(mgmt->da, da, ETH_ALEN);
26047846c9bSJohannes Berg 	memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
261915b5c50SJavier Cardona 	memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
2628db09850SThomas Pedersen 	mgmt->u.action.category = WLAN_CATEGORY_SELF_PROTECTED;
2638db09850SThomas Pedersen 	mgmt->u.action.u.self_prot.action_code = action;
264c3896d2cSLuis Carlos Cobo 
2658db09850SThomas Pedersen 	if (action != WLAN_SP_MESH_PEERING_CLOSE) {
26621a8e9ddSMohammed Shafi Shajakhan 		struct ieee80211_supported_band *sband;
26721a8e9ddSMohammed Shafi Shajakhan 		enum nl80211_band band;
26821a8e9ddSMohammed Shafi Shajakhan 
26921a8e9ddSMohammed Shafi Shajakhan 		sband = ieee80211_get_sband(sdata);
27021a8e9ddSMohammed Shafi Shajakhan 		if (!sband) {
27121a8e9ddSMohammed Shafi Shajakhan 			err = -EINVAL;
27221a8e9ddSMohammed Shafi Shajakhan 			goto free;
27321a8e9ddSMohammed Shafi Shajakhan 		}
27421a8e9ddSMohammed Shafi Shajakhan 		band = sband->band;
27555de908aSJohannes Berg 
2768db09850SThomas Pedersen 		/* capability info */
277e45a79daSJohannes Berg 		pos = skb_put_zero(skb, 2);
2788db09850SThomas Pedersen 		if (action == WLAN_SP_MESH_PEERING_CONFIRM) {
2798db09850SThomas Pedersen 			/* AID */
2808db09850SThomas Pedersen 			pos = skb_put(skb, 2);
281a69bd8e6SBob Copeland 			put_unaligned_le16(sta->sta.aid, pos);
282c3896d2cSLuis Carlos Cobo 		}
28355de908aSJohannes Berg 		if (ieee80211_add_srates_ie(sdata, skb, true, band) ||
28455de908aSJohannes Berg 		    ieee80211_add_ext_srates_ie(sdata, skb, true, band) ||
285bf7cd94dSJohannes Berg 		    mesh_add_rsn_ie(sdata, skb) ||
286bf7cd94dSJohannes Berg 		    mesh_add_meshid_ie(sdata, skb) ||
287bf7cd94dSJohannes Berg 		    mesh_add_meshconf_ie(sdata, skb))
288f609a43dSThomas Pedersen 			goto free;
2898db09850SThomas Pedersen 	} else {	/* WLAN_SP_MESH_PEERING_CLOSE */
290e7570dfbSThomas Pedersen 		info->flags |= IEEE80211_TX_CTL_NO_ACK;
291bf7cd94dSJohannes Berg 		if (mesh_add_meshid_ie(sdata, skb))
292f609a43dSThomas Pedersen 			goto free;
293c3896d2cSLuis Carlos Cobo 	}
294c3896d2cSLuis Carlos Cobo 
2958db09850SThomas Pedersen 	/* Add Mesh Peering Management element */
296c3896d2cSLuis Carlos Cobo 	switch (action) {
29754ef656bSThomas Pedersen 	case WLAN_SP_MESH_PEERING_OPEN:
298c3896d2cSLuis Carlos Cobo 		break;
29954ef656bSThomas Pedersen 	case WLAN_SP_MESH_PEERING_CONFIRM:
3008db09850SThomas Pedersen 		ie_len += 2;
301c3896d2cSLuis Carlos Cobo 		include_plid = true;
302c3896d2cSLuis Carlos Cobo 		break;
30354ef656bSThomas Pedersen 	case WLAN_SP_MESH_PEERING_CLOSE:
3048db09850SThomas Pedersen 		if (plid) {
3058db09850SThomas Pedersen 			ie_len += 2;
306c3896d2cSLuis Carlos Cobo 			include_plid = true;
307c3896d2cSLuis Carlos Cobo 		}
3088db09850SThomas Pedersen 		ie_len += 2;	/* reason code */
309c3896d2cSLuis Carlos Cobo 		break;
3108db09850SThomas Pedersen 	default:
311f609a43dSThomas Pedersen 		err = -EINVAL;
312f609a43dSThomas Pedersen 		goto free;
313c3896d2cSLuis Carlos Cobo 	}
314c3896d2cSLuis Carlos Cobo 
3158db09850SThomas Pedersen 	if (WARN_ON(skb_tailroom(skb) < 2 + ie_len))
316f609a43dSThomas Pedersen 		goto free;
3178db09850SThomas Pedersen 
318c3896d2cSLuis Carlos Cobo 	pos = skb_put(skb, 2 + ie_len);
3198db09850SThomas Pedersen 	*pos++ = WLAN_EID_PEER_MGMT;
320c3896d2cSLuis Carlos Cobo 	*pos++ = ie_len;
3218db09850SThomas Pedersen 	memcpy(pos, &peering_proto, 2);
322c3896d2cSLuis Carlos Cobo 	pos += 2;
3236f101ef0SChun-Yeow Yeoh 	put_unaligned_le16(llid, pos);
3248db09850SThomas Pedersen 	pos += 2;
3258db09850SThomas Pedersen 	if (include_plid) {
3266f101ef0SChun-Yeow Yeoh 		put_unaligned_le16(plid, pos);
3278db09850SThomas Pedersen 		pos += 2;
328c3896d2cSLuis Carlos Cobo 	}
32954ef656bSThomas Pedersen 	if (action == WLAN_SP_MESH_PEERING_CLOSE) {
3306f101ef0SChun-Yeow Yeoh 		put_unaligned_le16(reason, pos);
3318db09850SThomas Pedersen 		pos += 2;
332c3896d2cSLuis Carlos Cobo 	}
333176f3608SThomas Pedersen 
334176f3608SThomas Pedersen 	if (action != WLAN_SP_MESH_PEERING_CLOSE) {
335bf7cd94dSJohannes Berg 		if (mesh_add_ht_cap_ie(sdata, skb) ||
336c85fb53cSBob Copeland 		    mesh_add_ht_oper_ie(sdata, skb) ||
337c85fb53cSBob Copeland 		    mesh_add_vht_cap_ie(sdata, skb) ||
33860ad72daSSven Eckelmann 		    mesh_add_vht_oper_ie(sdata, skb) ||
33960ad72daSSven Eckelmann 		    mesh_add_he_cap_ie(sdata, skb, ie_len_he_cap) ||
34024a2042cSRajkumar Manoharan 		    mesh_add_he_oper_ie(sdata, skb) ||
341df1875c4SRyder Lee 		    mesh_add_he_6ghz_cap_ie(sdata, skb) ||
342df1875c4SRyder Lee 		    mesh_add_eht_cap_ie(sdata, skb, ie_len_eht_cap) ||
343df1875c4SRyder Lee 		    mesh_add_eht_oper_ie(sdata, skb))
344f609a43dSThomas Pedersen 			goto free;
345176f3608SThomas Pedersen 	}
346176f3608SThomas Pedersen 
347bf7cd94dSJohannes Berg 	if (mesh_add_vendor_ies(sdata, skb))
348f609a43dSThomas Pedersen 		goto free;
349c3896d2cSLuis Carlos Cobo 
35062ae67beSJohannes Berg 	ieee80211_tx_skb(sdata, skb);
351c3896d2cSLuis Carlos Cobo 	return 0;
352f609a43dSThomas Pedersen free:
353f609a43dSThomas Pedersen 	kfree_skb(skb);
354f609a43dSThomas Pedersen 	return err;
355c3896d2cSLuis Carlos Cobo }
356c3896d2cSLuis Carlos Cobo 
357fa87a656SBob Copeland /**
358fa87a656SBob Copeland  * __mesh_plink_deactivate - deactivate mesh peer link
359fa87a656SBob Copeland  *
360fa87a656SBob Copeland  * @sta: mesh peer link to deactivate
361fa87a656SBob Copeland  *
362e596af82SBob Copeland  * Mesh paths with this peer as next hop should be flushed
363e596af82SBob Copeland  * by the caller outside of plink_lock.
364e596af82SBob Copeland  *
365fa87a656SBob Copeland  * Returns beacon changed flag if the beacon content changed.
366fa87a656SBob Copeland  *
367fa87a656SBob Copeland  * Locking: the caller must hold sta->mesh->plink_lock
368fa87a656SBob Copeland  */
__mesh_plink_deactivate(struct sta_info * sta)36915ddba5fSAnjaneyulu static u64 __mesh_plink_deactivate(struct sta_info *sta)
370fa87a656SBob Copeland {
371fa87a656SBob Copeland 	struct ieee80211_sub_if_data *sdata = sta->sdata;
37215ddba5fSAnjaneyulu 	u64 changed = 0;
373fa87a656SBob Copeland 
374fa87a656SBob Copeland 	lockdep_assert_held(&sta->mesh->plink_lock);
375fa87a656SBob Copeland 
376fa87a656SBob Copeland 	if (sta->mesh->plink_state == NL80211_PLINK_ESTAB)
377fa87a656SBob Copeland 		changed = mesh_plink_dec_estab_count(sdata);
378fa87a656SBob Copeland 	sta->mesh->plink_state = NL80211_PLINK_BLOCKED;
379fa87a656SBob Copeland 
380fa87a656SBob Copeland 	ieee80211_mps_sta_status_update(sta);
381fa87a656SBob Copeland 	changed |= ieee80211_mps_set_sta_local_pm(sta,
382fa87a656SBob Copeland 			NL80211_MESH_POWER_UNKNOWN);
383fa87a656SBob Copeland 
384fa87a656SBob Copeland 	return changed;
385fa87a656SBob Copeland }
386fa87a656SBob Copeland 
387fa87a656SBob Copeland /**
388fa87a656SBob Copeland  * mesh_plink_deactivate - deactivate mesh peer link
389fa87a656SBob Copeland  *
390fa87a656SBob Copeland  * @sta: mesh peer link to deactivate
391fa87a656SBob Copeland  *
392fa87a656SBob Copeland  * All mesh paths with this peer as next hop will be flushed
393fa87a656SBob Copeland  */
mesh_plink_deactivate(struct sta_info * sta)39415ddba5fSAnjaneyulu u64 mesh_plink_deactivate(struct sta_info *sta)
395fa87a656SBob Copeland {
396fa87a656SBob Copeland 	struct ieee80211_sub_if_data *sdata = sta->sdata;
39715ddba5fSAnjaneyulu 	u64 changed;
398fa87a656SBob Copeland 
399fa87a656SBob Copeland 	spin_lock_bh(&sta->mesh->plink_lock);
400fa87a656SBob Copeland 	changed = __mesh_plink_deactivate(sta);
401efc401f4SBob Copeland 
402efc401f4SBob Copeland 	if (!sdata->u.mesh.user_mpm) {
403fa87a656SBob Copeland 		sta->mesh->reason = WLAN_REASON_MESH_PEER_CANCELED;
404a69bd8e6SBob Copeland 		mesh_plink_frame_tx(sdata, sta, WLAN_SP_MESH_PEERING_CLOSE,
405efc401f4SBob Copeland 				    sta->sta.addr, sta->mesh->llid,
406efc401f4SBob Copeland 				    sta->mesh->plid, sta->mesh->reason);
407efc401f4SBob Copeland 	}
408fa87a656SBob Copeland 	spin_unlock_bh(&sta->mesh->plink_lock);
409efc401f4SBob Copeland 	if (!sdata->u.mesh.user_mpm)
410efc401f4SBob Copeland 		del_timer_sync(&sta->mesh->plink_timer);
411e596af82SBob Copeland 	mesh_path_flush_by_nexthop(sta);
412fa87a656SBob Copeland 
413efc401f4SBob Copeland 	/* make sure no readers can access nexthop sta from here on */
414efc401f4SBob Copeland 	synchronize_net();
415efc401f4SBob Copeland 
416fa87a656SBob Copeland 	return changed;
417fa87a656SBob Copeland }
418fa87a656SBob Copeland 
mesh_sta_info_init(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,struct ieee802_11_elems * elems)419296fcba3SThomas Pedersen static void mesh_sta_info_init(struct ieee80211_sub_if_data *sdata,
420296fcba3SThomas Pedersen 			       struct sta_info *sta,
4211d6741d8SBob Copeland 			       struct ieee802_11_elems *elems)
42254ab1ffbSThomas Pedersen {
42354ab1ffbSThomas Pedersen 	struct ieee80211_local *local = sdata->local;
42454ab1ffbSThomas Pedersen 	struct ieee80211_supported_band *sband;
425f68d776aSThomas Pedersen 	u32 rates, basic_rates = 0, changed = 0;
426046d2e7cSSriram R 	enum ieee80211_sta_rx_bandwidth bw = sta->sta.deflink.bandwidth;
42754ab1ffbSThomas Pedersen 
42821a8e9ddSMohammed Shafi Shajakhan 	sband = ieee80211_get_sband(sdata);
42921a8e9ddSMohammed Shafi Shajakhan 	if (!sband)
43021a8e9ddSMohammed Shafi Shajakhan 		return;
43121a8e9ddSMohammed Shafi Shajakhan 
43221a8e9ddSMohammed Shafi Shajakhan 	rates = ieee80211_sta_get_rates(sdata, elems, sband->band,
43321a8e9ddSMohammed Shafi Shajakhan 					&basic_rates);
43454ab1ffbSThomas Pedersen 
435433f5bc1SJohannes Berg 	spin_lock_bh(&sta->mesh->plink_lock);
436046d2e7cSSriram R 	sta->deflink.rx_stats.last_rx = jiffies;
437296fcba3SThomas Pedersen 
438296fcba3SThomas Pedersen 	/* rates and capabilities don't change during peering */
439433f5bc1SJohannes Berg 	if (sta->mesh->plink_state == NL80211_PLINK_ESTAB &&
440433f5bc1SJohannes Berg 	    sta->mesh->processed_beacon)
441296fcba3SThomas Pedersen 		goto out;
442433f5bc1SJohannes Berg 	sta->mesh->processed_beacon = true;
443bae35d92SChun-Yeow Yeoh 
444046d2e7cSSriram R 	if (sta->sta.deflink.supp_rates[sband->band] != rates)
445f68d776aSThomas Pedersen 		changed |= IEEE80211_RC_SUPP_RATES_CHANGED;
446046d2e7cSSriram R 	sta->sta.deflink.supp_rates[sband->band] = rates;
44754ab1ffbSThomas Pedersen 
44852ac8c48SThomas Pedersen 	if (ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
449c71420dbSJohannes Berg 					      elems->ht_cap_elem,
450c71420dbSJohannes Berg 					      &sta->deflink))
451f68d776aSThomas Pedersen 		changed |= IEEE80211_RC_BW_CHANGED;
45252ac8c48SThomas Pedersen 
453c85fb53cSBob Copeland 	ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
454084cf2aeSJohannes Berg 					    elems->vht_cap_elem, NULL,
455c71420dbSJohannes Berg 					    &sta->deflink);
456c85fb53cSBob Copeland 
45760ad72daSSven Eckelmann 	ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband, elems->he_cap,
4581bb9a8a4SJohannes Berg 					  elems->he_cap_len,
4591bb9a8a4SJohannes Berg 					  elems->he_6ghz_capa,
460c71420dbSJohannes Berg 					  &sta->deflink);
46160ad72daSSven Eckelmann 
462df1875c4SRyder Lee 	ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, elems->he_cap,
463df1875c4SRyder Lee 					    elems->he_cap_len,
464df1875c4SRyder Lee 					    elems->eht_cap, elems->eht_cap_len,
465df1875c4SRyder Lee 					    &sta->deflink);
466df1875c4SRyder Lee 
467046d2e7cSSriram R 	if (bw != sta->sta.deflink.bandwidth)
468abcff6efSJanusz.Dziedzic@tieto.com 		changed |= IEEE80211_RC_BW_CHANGED;
469abcff6efSJanusz.Dziedzic@tieto.com 
47052ac8c48SThomas Pedersen 	/* HT peer is operating 20MHz-only */
47152ac8c48SThomas Pedersen 	if (elems->ht_operation &&
47252ac8c48SThomas Pedersen 	    !(elems->ht_operation->ht_param &
47352ac8c48SThomas Pedersen 	      IEEE80211_HT_PARAM_CHAN_WIDTH_ANY)) {
474046d2e7cSSriram R 		if (sta->sta.deflink.bandwidth != IEEE80211_STA_RX_BW_20)
47552ac8c48SThomas Pedersen 			changed |= IEEE80211_RC_BW_CHANGED;
476046d2e7cSSriram R 		sta->sta.deflink.bandwidth = IEEE80211_STA_RX_BW_20;
47757aac7c5SAshok Nagarajan 	}
478c7d25828SThomas Pedersen 
4791d6741d8SBob Copeland 	if (!test_sta_flag(sta, WLAN_STA_RATE_CONTROL))
48054ab1ffbSThomas Pedersen 		rate_control_rate_init(sta);
481f68d776aSThomas Pedersen 	else
482b4f85443SJohannes Berg 		rate_control_rate_update(local, sband, sta, 0, changed);
483296fcba3SThomas Pedersen out:
484433f5bc1SJohannes Berg 	spin_unlock_bh(&sta->mesh->plink_lock);
485296fcba3SThomas Pedersen }
48654ab1ffbSThomas Pedersen 
mesh_allocate_aid(struct ieee80211_sub_if_data * sdata)4870e0060fcSBob Copeland static int mesh_allocate_aid(struct ieee80211_sub_if_data *sdata)
4880e0060fcSBob Copeland {
4890e0060fcSBob Copeland 	struct sta_info *sta;
4900e0060fcSBob Copeland 	unsigned long *aid_map;
4910e0060fcSBob Copeland 	int aid;
4920e0060fcSBob Copeland 
49337babce9SChristophe JAILLET 	aid_map = bitmap_zalloc(IEEE80211_MAX_AID + 1, GFP_KERNEL);
4940e0060fcSBob Copeland 	if (!aid_map)
4950e0060fcSBob Copeland 		return -ENOMEM;
4960e0060fcSBob Copeland 
4970e0060fcSBob Copeland 	/* reserve aid 0 for mcast indication */
4980e0060fcSBob Copeland 	__set_bit(0, aid_map);
4990e0060fcSBob Copeland 
5000e0060fcSBob Copeland 	rcu_read_lock();
5010e0060fcSBob Copeland 	list_for_each_entry_rcu(sta, &sdata->local->sta_list, list)
5020e0060fcSBob Copeland 		__set_bit(sta->sta.aid, aid_map);
5030e0060fcSBob Copeland 	rcu_read_unlock();
5040e0060fcSBob Copeland 
5050e0060fcSBob Copeland 	aid = find_first_zero_bit(aid_map, IEEE80211_MAX_AID + 1);
50637babce9SChristophe JAILLET 	bitmap_free(aid_map);
5070e0060fcSBob Copeland 
5080e0060fcSBob Copeland 	if (aid > IEEE80211_MAX_AID)
5090e0060fcSBob Copeland 		return -ENOBUFS;
5100e0060fcSBob Copeland 
5110e0060fcSBob Copeland 	return aid;
5120e0060fcSBob Copeland }
5130e0060fcSBob Copeland 
514296fcba3SThomas Pedersen static struct sta_info *
__mesh_sta_info_alloc(struct ieee80211_sub_if_data * sdata,u8 * hw_addr)515296fcba3SThomas Pedersen __mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *hw_addr)
516296fcba3SThomas Pedersen {
517296fcba3SThomas Pedersen 	struct sta_info *sta;
5180e0060fcSBob Copeland 	int aid;
519296fcba3SThomas Pedersen 
520296fcba3SThomas Pedersen 	if (sdata->local->num_sta >= MESH_MAX_PLINKS)
521e87278e7SThomas Pedersen 		return NULL;
522e87278e7SThomas Pedersen 
5230e0060fcSBob Copeland 	aid = mesh_allocate_aid(sdata);
5240e0060fcSBob Copeland 	if (aid < 0)
5250e0060fcSBob Copeland 		return NULL;
5260e0060fcSBob Copeland 
527f36fe0a2SJohannes Berg 	sta = sta_info_alloc(sdata, hw_addr, GFP_KERNEL);
528296fcba3SThomas Pedersen 	if (!sta)
529296fcba3SThomas Pedersen 		return NULL;
530296fcba3SThomas Pedersen 
531433f5bc1SJohannes Berg 	sta->mesh->plink_state = NL80211_PLINK_LISTEN;
532a74a8c84SJohannes Berg 	sta->sta.wme = true;
5330e0060fcSBob Copeland 	sta->sta.aid = aid;
534296fcba3SThomas Pedersen 
535296fcba3SThomas Pedersen 	sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
536296fcba3SThomas Pedersen 	sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC);
537296fcba3SThomas Pedersen 	sta_info_pre_move_state(sta, IEEE80211_STA_AUTHORIZED);
538296fcba3SThomas Pedersen 
53954ab1ffbSThomas Pedersen 	return sta;
54054ab1ffbSThomas Pedersen }
54154ab1ffbSThomas Pedersen 
542296fcba3SThomas Pedersen static struct sta_info *
mesh_sta_info_alloc(struct ieee80211_sub_if_data * sdata,u8 * addr,struct ieee802_11_elems * elems,struct ieee80211_rx_status * rx_status)543296fcba3SThomas Pedersen mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *addr,
544ecbc12adSBob Copeland 		    struct ieee802_11_elems *elems,
545ecbc12adSBob Copeland 		    struct ieee80211_rx_status *rx_status)
546296fcba3SThomas Pedersen {
547296fcba3SThomas Pedersen 	struct sta_info *sta = NULL;
548296fcba3SThomas Pedersen 
549a6dad6a2SThomas Pedersen 	/* Userspace handles station allocation */
550a6dad6a2SThomas Pedersen 	if (sdata->u.mesh.user_mpm ||
55111197d00SMasashi Honma 	    sdata->u.mesh.security & IEEE80211_MESH_SEC_AUTHED) {
55211197d00SMasashi Honma 		if (mesh_peer_accepts_plinks(elems) &&
553ecbc12adSBob Copeland 		    mesh_plink_availables(sdata)) {
554ecbc12adSBob Copeland 			int sig = 0;
555ecbc12adSBob Copeland 
556ecbc12adSBob Copeland 			if (ieee80211_hw_check(&sdata->local->hw, SIGNAL_DBM))
557ecbc12adSBob Copeland 				sig = rx_status->signal;
558ecbc12adSBob Copeland 
559296fcba3SThomas Pedersen 			cfg80211_notify_new_peer_candidate(sdata->dev, addr,
560296fcba3SThomas Pedersen 							   elems->ie_start,
561296fcba3SThomas Pedersen 							   elems->total_len,
562ecbc12adSBob Copeland 							   sig, GFP_KERNEL);
563ecbc12adSBob Copeland 		}
56411197d00SMasashi Honma 	} else
565296fcba3SThomas Pedersen 		sta = __mesh_sta_info_alloc(sdata, addr);
566296fcba3SThomas Pedersen 
567296fcba3SThomas Pedersen 	return sta;
568296fcba3SThomas Pedersen }
569296fcba3SThomas Pedersen 
570296fcba3SThomas Pedersen /*
571296fcba3SThomas Pedersen  * mesh_sta_info_get - return mesh sta info entry for @addr.
572296fcba3SThomas Pedersen  *
573296fcba3SThomas Pedersen  * @sdata: local meshif
574296fcba3SThomas Pedersen  * @addr: peer's address
575296fcba3SThomas Pedersen  * @elems: IEs from beacon or mesh peering frame.
576ecbc12adSBob Copeland  * @rx_status: rx status for the frame for signal reporting
577296fcba3SThomas Pedersen  *
578296fcba3SThomas Pedersen  * Return existing or newly allocated sta_info under RCU read lock.
579296fcba3SThomas Pedersen  * (re)initialize with given IEs.
580296fcba3SThomas Pedersen  */
581296fcba3SThomas Pedersen static struct sta_info *
mesh_sta_info_get(struct ieee80211_sub_if_data * sdata,u8 * addr,struct ieee802_11_elems * elems,struct ieee80211_rx_status * rx_status)582296fcba3SThomas Pedersen mesh_sta_info_get(struct ieee80211_sub_if_data *sdata,
583ecbc12adSBob Copeland 		  u8 *addr, struct ieee802_11_elems *elems,
584ecbc12adSBob Copeland 		  struct ieee80211_rx_status *rx_status) __acquires(RCU)
585296fcba3SThomas Pedersen {
586296fcba3SThomas Pedersen 	struct sta_info *sta = NULL;
587296fcba3SThomas Pedersen 
588296fcba3SThomas Pedersen 	rcu_read_lock();
589296fcba3SThomas Pedersen 	sta = sta_info_get(sdata, addr);
590296fcba3SThomas Pedersen 	if (sta) {
5911d6741d8SBob Copeland 		mesh_sta_info_init(sdata, sta, elems);
592296fcba3SThomas Pedersen 	} else {
593296fcba3SThomas Pedersen 		rcu_read_unlock();
594296fcba3SThomas Pedersen 		/* can't run atomic */
595ecbc12adSBob Copeland 		sta = mesh_sta_info_alloc(sdata, addr, elems, rx_status);
596296fcba3SThomas Pedersen 		if (!sta) {
597296fcba3SThomas Pedersen 			rcu_read_lock();
598296fcba3SThomas Pedersen 			return NULL;
599296fcba3SThomas Pedersen 		}
600296fcba3SThomas Pedersen 
6011d6741d8SBob Copeland 		mesh_sta_info_init(sdata, sta, elems);
6023b4797bcSThomas Pedersen 
603296fcba3SThomas Pedersen 		if (sta_info_insert_rcu(sta))
604296fcba3SThomas Pedersen 			return NULL;
605296fcba3SThomas Pedersen 	}
606296fcba3SThomas Pedersen 
607296fcba3SThomas Pedersen 	return sta;
608296fcba3SThomas Pedersen }
609296fcba3SThomas Pedersen 
610296fcba3SThomas Pedersen /*
611296fcba3SThomas Pedersen  * mesh_neighbour_update - update or initialize new mesh neighbor.
612296fcba3SThomas Pedersen  *
613296fcba3SThomas Pedersen  * @sdata: local meshif
614296fcba3SThomas Pedersen  * @addr: peer's address
615296fcba3SThomas Pedersen  * @elems: IEs from beacon or mesh peering frame
616ecbc12adSBob Copeland  * @rx_status: rx status for the frame for signal reporting
617296fcba3SThomas Pedersen  *
618296fcba3SThomas Pedersen  * Initiates peering if appropriate.
619296fcba3SThomas Pedersen  */
mesh_neighbour_update(struct ieee80211_sub_if_data * sdata,u8 * hw_addr,struct ieee802_11_elems * elems,struct ieee80211_rx_status * rx_status)620f743ff49SThomas Pedersen void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
621f743ff49SThomas Pedersen 			   u8 *hw_addr,
622ecbc12adSBob Copeland 			   struct ieee802_11_elems *elems,
623ecbc12adSBob Copeland 			   struct ieee80211_rx_status *rx_status)
624c3896d2cSLuis Carlos Cobo {
625c3896d2cSLuis Carlos Cobo 	struct sta_info *sta;
62615ddba5fSAnjaneyulu 	u64 changed = 0;
627c3896d2cSLuis Carlos Cobo 
628ecbc12adSBob Copeland 	sta = mesh_sta_info_get(sdata, hw_addr, elems, rx_status);
62954ab1ffbSThomas Pedersen 	if (!sta)
63054ab1ffbSThomas Pedersen 		goto out;
63154ab1ffbSThomas Pedersen 
632dbdaee7aSBob Copeland 	sta->mesh->connected_to_gate = elems->mesh_config->meshconf_form &
633dbdaee7aSBob Copeland 		IEEE80211_MESHCONF_FORM_CONNECTED_TO_GATE;
634dbdaee7aSBob Copeland 
6351570ca59SJavier Cardona 	if (mesh_peer_accepts_plinks(elems) &&
636433f5bc1SJohannes Berg 	    sta->mesh->plink_state == NL80211_PLINK_LISTEN &&
637472dbc45SJohannes Berg 	    sdata->u.mesh.accepting_plinks &&
63855335137SAshok Nagarajan 	    sdata->u.mesh.mshcfg.auto_open_plinks &&
63936c9bb29SBob Copeland 	    rssi_threshold_check(sdata, sta))
64039886b61SThomas Pedersen 		changed = mesh_plink_open(sta);
641c3896d2cSLuis Carlos Cobo 
6423f52b7e3SMarco Porsch 	ieee80211_mps_frame_release(sta, elems);
64354ab1ffbSThomas Pedersen out:
644d0709a65SJohannes Berg 	rcu_read_unlock();
6452b5e1967SThomas Pedersen 	ieee80211_mbss_info_change_notify(sdata, changed);
646c3896d2cSLuis Carlos Cobo }
647c3896d2cSLuis Carlos Cobo 
mesh_plink_timer(struct timer_list * t)6484c02d62fSKees Cook void mesh_plink_timer(struct timer_list *t)
649c3896d2cSLuis Carlos Cobo {
6504c02d62fSKees Cook 	struct mesh_sta *mesh = from_timer(mesh, t, plink_timer);
651c3896d2cSLuis Carlos Cobo 	struct sta_info *sta;
6526f101ef0SChun-Yeow Yeoh 	u16 reason = 0;
653c3896d2cSLuis Carlos Cobo 	struct ieee80211_sub_if_data *sdata;
654453e66f2SMarco Porsch 	struct mesh_config *mshcfg;
6554efec451SThomas Pedersen 	enum ieee80211_self_protected_actioncode action = 0;
656c3896d2cSLuis Carlos Cobo 
657d0709a65SJohannes Berg 	/*
658d0709a65SJohannes Berg 	 * This STA is valid because sta_info_destroy() will
659d0709a65SJohannes Berg 	 * del_timer_sync() this timer after having made sure
660d0709a65SJohannes Berg 	 * it cannot be readded (by deleting the plink.)
661d0709a65SJohannes Berg 	 */
6624c02d62fSKees Cook 	sta = mesh->plink_sta;
663c3896d2cSLuis Carlos Cobo 
664690205f1SStanislaw Gruszka 	if (sta->sdata->local->quiescing)
6655bb644a0SJohannes Berg 		return;
6665bb644a0SJohannes Berg 
667433f5bc1SJohannes Berg 	spin_lock_bh(&sta->mesh->plink_lock);
6682b470c39SBob Copeland 
6692b470c39SBob Copeland 	/* If a timer fires just before a state transition on another CPU,
6702b470c39SBob Copeland 	 * we may have already extended the timeout and changed state by the
6712b470c39SBob Copeland 	 * time we've acquired the lock and arrived  here.  In that case,
6722b470c39SBob Copeland 	 * skip this timer and wait for the new one.
6732b470c39SBob Copeland 	 */
674433f5bc1SJohannes Berg 	if (time_before(jiffies, sta->mesh->plink_timer.expires)) {
6752b470c39SBob Copeland 		mpl_dbg(sta->sdata,
6762b470c39SBob Copeland 			"Ignoring timer for %pM in state %s (timer adjusted)",
677433f5bc1SJohannes Berg 			sta->sta.addr, mplstates[sta->mesh->plink_state]);
678433f5bc1SJohannes Berg 		spin_unlock_bh(&sta->mesh->plink_lock);
679c3896d2cSLuis Carlos Cobo 		return;
680c3896d2cSLuis Carlos Cobo 	}
6812b470c39SBob Copeland 
6822b470c39SBob Copeland 	/* del_timer() and handler may race when entering these states */
683433f5bc1SJohannes Berg 	if (sta->mesh->plink_state == NL80211_PLINK_LISTEN ||
684433f5bc1SJohannes Berg 	    sta->mesh->plink_state == NL80211_PLINK_ESTAB) {
6852b470c39SBob Copeland 		mpl_dbg(sta->sdata,
6862b470c39SBob Copeland 			"Ignoring timer for %pM in state %s (timer deleted)",
687433f5bc1SJohannes Berg 			sta->sta.addr, mplstates[sta->mesh->plink_state]);
688433f5bc1SJohannes Berg 		spin_unlock_bh(&sta->mesh->plink_lock);
6892b470c39SBob Copeland 		return;
6902b470c39SBob Copeland 	}
6912b470c39SBob Copeland 
692bdcbd8e0SJohannes Berg 	mpl_dbg(sta->sdata,
6933088f7d2SThomas Pedersen 		"Mesh plink timer for %pM fired on state %s\n",
694433f5bc1SJohannes Berg 		sta->sta.addr, mplstates[sta->mesh->plink_state]);
695d0709a65SJohannes Berg 	sdata = sta->sdata;
696453e66f2SMarco Porsch 	mshcfg = &sdata->u.mesh.mshcfg;
697c3896d2cSLuis Carlos Cobo 
698433f5bc1SJohannes Berg 	switch (sta->mesh->plink_state) {
69957cf8043SJavier Cardona 	case NL80211_PLINK_OPN_RCVD:
70057cf8043SJavier Cardona 	case NL80211_PLINK_OPN_SNT:
701c3896d2cSLuis Carlos Cobo 		/* retry timer */
702433f5bc1SJohannes Berg 		if (sta->mesh->plink_retries < mshcfg->dot11MeshMaxRetries) {
703c3896d2cSLuis Carlos Cobo 			u32 rand;
704bdcbd8e0SJohannes Berg 			mpl_dbg(sta->sdata,
705bdcbd8e0SJohannes Berg 				"Mesh plink for %pM (retry, timeout): %d %d\n",
706433f5bc1SJohannes Berg 				sta->sta.addr, sta->mesh->plink_retries,
707433f5bc1SJohannes Berg 				sta->mesh->plink_timeout);
708c3896d2cSLuis Carlos Cobo 			get_random_bytes(&rand, sizeof(u32));
709433f5bc1SJohannes Berg 			sta->mesh->plink_timeout = sta->mesh->plink_timeout +
710433f5bc1SJohannes Berg 					     rand % sta->mesh->plink_timeout;
711433f5bc1SJohannes Berg 			++sta->mesh->plink_retries;
712433f5bc1SJohannes Berg 			mod_plink_timer(sta, sta->mesh->plink_timeout);
7134efec451SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_OPEN;
714c3896d2cSLuis Carlos Cobo 			break;
715c3896d2cSLuis Carlos Cobo 		}
7166f101ef0SChun-Yeow Yeoh 		reason = WLAN_REASON_MESH_MAX_RETRIES;
717fc0561dcSGustavo A. R. Silva 		fallthrough;
71857cf8043SJavier Cardona 	case NL80211_PLINK_CNF_RCVD:
719c3896d2cSLuis Carlos Cobo 		/* confirm timer */
720c3896d2cSLuis Carlos Cobo 		if (!reason)
7216f101ef0SChun-Yeow Yeoh 			reason = WLAN_REASON_MESH_CONFIRM_TIMEOUT;
722433f5bc1SJohannes Berg 		sta->mesh->plink_state = NL80211_PLINK_HOLDING;
723453e66f2SMarco Porsch 		mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
7244efec451SThomas Pedersen 		action = WLAN_SP_MESH_PEERING_CLOSE;
725c3896d2cSLuis Carlos Cobo 		break;
72657cf8043SJavier Cardona 	case NL80211_PLINK_HOLDING:
727c3896d2cSLuis Carlos Cobo 		/* holding timer */
728433f5bc1SJohannes Berg 		del_timer(&sta->mesh->plink_timer);
729c3896d2cSLuis Carlos Cobo 		mesh_plink_fsm_restart(sta);
730c3896d2cSLuis Carlos Cobo 		break;
731c3896d2cSLuis Carlos Cobo 	default:
732c3896d2cSLuis Carlos Cobo 		break;
733c3896d2cSLuis Carlos Cobo 	}
734433f5bc1SJohannes Berg 	spin_unlock_bh(&sta->mesh->plink_lock);
7354efec451SThomas Pedersen 	if (action)
736a69bd8e6SBob Copeland 		mesh_plink_frame_tx(sdata, sta, action, sta->sta.addr,
737433f5bc1SJohannes Berg 				    sta->mesh->llid, sta->mesh->plid, reason);
738c3896d2cSLuis Carlos Cobo }
739c3896d2cSLuis Carlos Cobo 
mesh_plink_timer_set(struct sta_info * sta,u32 timeout)7400df2f6c1SNicholas Mc Guire static inline void mesh_plink_timer_set(struct sta_info *sta, u32 timeout)
741c3896d2cSLuis Carlos Cobo {
742433f5bc1SJohannes Berg 	sta->mesh->plink_timeout = timeout;
7434c02d62fSKees Cook 	mod_timer(&sta->mesh->plink_timer, jiffies + msecs_to_jiffies(timeout));
744c3896d2cSLuis Carlos Cobo }
745c3896d2cSLuis Carlos Cobo 
llid_in_use(struct ieee80211_sub_if_data * sdata,u16 llid)746204d1304SThomas Pedersen static bool llid_in_use(struct ieee80211_sub_if_data *sdata,
7476f101ef0SChun-Yeow Yeoh 			u16 llid)
748204d1304SThomas Pedersen {
749204d1304SThomas Pedersen 	struct ieee80211_local *local = sdata->local;
750204d1304SThomas Pedersen 	bool in_use = false;
751204d1304SThomas Pedersen 	struct sta_info *sta;
752204d1304SThomas Pedersen 
753204d1304SThomas Pedersen 	rcu_read_lock();
754204d1304SThomas Pedersen 	list_for_each_entry_rcu(sta, &local->sta_list, list) {
755520c75dcSMatthias Schiffer 		if (sdata != sta->sdata)
756520c75dcSMatthias Schiffer 			continue;
757520c75dcSMatthias Schiffer 
758433f5bc1SJohannes Berg 		if (!memcmp(&sta->mesh->llid, &llid, sizeof(llid))) {
759204d1304SThomas Pedersen 			in_use = true;
760204d1304SThomas Pedersen 			break;
761204d1304SThomas Pedersen 		}
762204d1304SThomas Pedersen 	}
763204d1304SThomas Pedersen 	rcu_read_unlock();
764204d1304SThomas Pedersen 
765204d1304SThomas Pedersen 	return in_use;
766204d1304SThomas Pedersen }
767204d1304SThomas Pedersen 
mesh_get_new_llid(struct ieee80211_sub_if_data * sdata)7686f101ef0SChun-Yeow Yeoh static u16 mesh_get_new_llid(struct ieee80211_sub_if_data *sdata)
769204d1304SThomas Pedersen {
770204d1304SThomas Pedersen 	u16 llid;
771204d1304SThomas Pedersen 
772204d1304SThomas Pedersen 	do {
773204d1304SThomas Pedersen 		get_random_bytes(&llid, sizeof(llid));
7746f101ef0SChun-Yeow Yeoh 	} while (llid_in_use(sdata, llid));
775204d1304SThomas Pedersen 
7766f101ef0SChun-Yeow Yeoh 	return llid;
777204d1304SThomas Pedersen }
778204d1304SThomas Pedersen 
mesh_plink_open(struct sta_info * sta)77915ddba5fSAnjaneyulu u64 mesh_plink_open(struct sta_info *sta)
780c3896d2cSLuis Carlos Cobo {
781d0709a65SJohannes Berg 	struct ieee80211_sub_if_data *sdata = sta->sdata;
78215ddba5fSAnjaneyulu 	u64 changed;
783c3896d2cSLuis Carlos Cobo 
784c2c98fdeSJohannes Berg 	if (!test_sta_flag(sta, WLAN_STA_AUTH))
78539886b61SThomas Pedersen 		return 0;
78653e80511SJavier Cardona 
787433f5bc1SJohannes Berg 	spin_lock_bh(&sta->mesh->plink_lock);
788433f5bc1SJohannes Berg 	sta->mesh->llid = mesh_get_new_llid(sdata);
789433f5bc1SJohannes Berg 	if (sta->mesh->plink_state != NL80211_PLINK_LISTEN &&
790433f5bc1SJohannes Berg 	    sta->mesh->plink_state != NL80211_PLINK_BLOCKED) {
791433f5bc1SJohannes Berg 		spin_unlock_bh(&sta->mesh->plink_lock);
79239886b61SThomas Pedersen 		return 0;
793c3896d2cSLuis Carlos Cobo 	}
794433f5bc1SJohannes Berg 	sta->mesh->plink_state = NL80211_PLINK_OPN_SNT;
795453e66f2SMarco Porsch 	mesh_plink_timer_set(sta, sdata->u.mesh.mshcfg.dot11MeshRetryTimeout);
796433f5bc1SJohannes Berg 	spin_unlock_bh(&sta->mesh->plink_lock);
797bdcbd8e0SJohannes Berg 	mpl_dbg(sdata,
798bdcbd8e0SJohannes Berg 		"Mesh plink: starting establishment with %pM\n",
7990c68ae26SJohannes Berg 		sta->sta.addr);
800c3896d2cSLuis Carlos Cobo 
8013f52b7e3SMarco Porsch 	/* set the non-peer mode to active during peering */
80239886b61SThomas Pedersen 	changed = ieee80211_mps_local_status_update(sdata);
8033f52b7e3SMarco Porsch 
804a69bd8e6SBob Copeland 	mesh_plink_frame_tx(sdata, sta, WLAN_SP_MESH_PEERING_OPEN,
805433f5bc1SJohannes Berg 			    sta->sta.addr, sta->mesh->llid, 0, 0);
80639886b61SThomas Pedersen 	return changed;
807c3896d2cSLuis Carlos Cobo }
808c3896d2cSLuis Carlos Cobo 
mesh_plink_block(struct sta_info * sta)80915ddba5fSAnjaneyulu u64 mesh_plink_block(struct sta_info *sta)
810c3896d2cSLuis Carlos Cobo {
81115ddba5fSAnjaneyulu 	u64 changed;
812c9370197SJohn W. Linville 
813433f5bc1SJohannes Berg 	spin_lock_bh(&sta->mesh->plink_lock);
814df323818SMarco Porsch 	changed = __mesh_plink_deactivate(sta);
815433f5bc1SJohannes Berg 	sta->mesh->plink_state = NL80211_PLINK_BLOCKED;
816433f5bc1SJohannes Berg 	spin_unlock_bh(&sta->mesh->plink_lock);
817e596af82SBob Copeland 	mesh_path_flush_by_nexthop(sta);
818c9370197SJohn W. Linville 
81939886b61SThomas Pedersen 	return changed;
820c3896d2cSLuis Carlos Cobo }
821c3896d2cSLuis Carlos Cobo 
mesh_plink_close(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,enum plink_event event)822e76d67f0SBob Copeland static void mesh_plink_close(struct ieee80211_sub_if_data *sdata,
823e76d67f0SBob Copeland 			     struct sta_info *sta,
824e76d67f0SBob Copeland 			     enum plink_event event)
825e76d67f0SBob Copeland {
826e76d67f0SBob Copeland 	struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
8276f101ef0SChun-Yeow Yeoh 	u16 reason = (event == CLS_ACPT) ?
8286f101ef0SChun-Yeow Yeoh 		     WLAN_REASON_MESH_CLOSE : WLAN_REASON_MESH_CONFIG;
829e76d67f0SBob Copeland 
830433f5bc1SJohannes Berg 	sta->mesh->reason = reason;
831433f5bc1SJohannes Berg 	sta->mesh->plink_state = NL80211_PLINK_HOLDING;
832272a9e26SBob Copeland 	mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
833e76d67f0SBob Copeland }
834e76d67f0SBob Copeland 
mesh_plink_establish(struct ieee80211_sub_if_data * sdata,struct sta_info * sta)83515ddba5fSAnjaneyulu static u64 mesh_plink_establish(struct ieee80211_sub_if_data *sdata,
836e76d67f0SBob Copeland 				struct sta_info *sta)
837e76d67f0SBob Copeland {
838e76d67f0SBob Copeland 	struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
83915ddba5fSAnjaneyulu 	u64 changed = 0;
840e76d67f0SBob Copeland 
841433f5bc1SJohannes Berg 	del_timer(&sta->mesh->plink_timer);
842433f5bc1SJohannes Berg 	sta->mesh->plink_state = NL80211_PLINK_ESTAB;
843e76d67f0SBob Copeland 	changed |= mesh_plink_inc_estab_count(sdata);
844e76d67f0SBob Copeland 	changed |= mesh_set_ht_prot_mode(sdata);
845e76d67f0SBob Copeland 	changed |= mesh_set_short_slot_time(sdata);
846e76d67f0SBob Copeland 	mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n", sta->sta.addr);
847e76d67f0SBob Copeland 	ieee80211_mps_sta_status_update(sta);
848e76d67f0SBob Copeland 	changed |= ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode);
849e76d67f0SBob Copeland 	return changed;
850e76d67f0SBob Copeland }
851e76d67f0SBob Copeland 
852c7e67811SThomas Pedersen /**
853c7e67811SThomas Pedersen  * mesh_plink_fsm - step @sta MPM based on @event
854c7e67811SThomas Pedersen  *
855c7e67811SThomas Pedersen  * @sdata: interface
856c7e67811SThomas Pedersen  * @sta: mesh neighbor
857c7e67811SThomas Pedersen  * @event: peering event
858c7e67811SThomas Pedersen  *
859c7e67811SThomas Pedersen  * Return: changed MBSS flags
860c7e67811SThomas Pedersen  */
mesh_plink_fsm(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,enum plink_event event)86115ddba5fSAnjaneyulu static u64 mesh_plink_fsm(struct ieee80211_sub_if_data *sdata,
862c7e67811SThomas Pedersen 			  struct sta_info *sta, enum plink_event event)
863c7e67811SThomas Pedersen {
864c7e67811SThomas Pedersen 	struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
865c7e67811SThomas Pedersen 	enum ieee80211_self_protected_actioncode action = 0;
86615ddba5fSAnjaneyulu 	u64 changed = 0;
867e596af82SBob Copeland 	bool flush = false;
868c7e67811SThomas Pedersen 
869c7e67811SThomas Pedersen 	mpl_dbg(sdata, "peer %pM in state %s got event %s\n", sta->sta.addr,
870433f5bc1SJohannes Berg 		mplstates[sta->mesh->plink_state], mplevents[event]);
871c7e67811SThomas Pedersen 
872433f5bc1SJohannes Berg 	spin_lock_bh(&sta->mesh->plink_lock);
873433f5bc1SJohannes Berg 	switch (sta->mesh->plink_state) {
874c7e67811SThomas Pedersen 	case NL80211_PLINK_LISTEN:
875c7e67811SThomas Pedersen 		switch (event) {
876c7e67811SThomas Pedersen 		case CLS_ACPT:
877c7e67811SThomas Pedersen 			mesh_plink_fsm_restart(sta);
878c7e67811SThomas Pedersen 			break;
879c7e67811SThomas Pedersen 		case OPN_ACPT:
880433f5bc1SJohannes Berg 			sta->mesh->plink_state = NL80211_PLINK_OPN_RCVD;
881433f5bc1SJohannes Berg 			sta->mesh->llid = mesh_get_new_llid(sdata);
882c7e67811SThomas Pedersen 			mesh_plink_timer_set(sta,
883c7e67811SThomas Pedersen 					     mshcfg->dot11MeshRetryTimeout);
884c7e67811SThomas Pedersen 
885c7e67811SThomas Pedersen 			/* set the non-peer mode to active during peering */
886c7e67811SThomas Pedersen 			changed |= ieee80211_mps_local_status_update(sdata);
887c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_OPEN;
888c7e67811SThomas Pedersen 			break;
889c7e67811SThomas Pedersen 		default:
890c7e67811SThomas Pedersen 			break;
891c7e67811SThomas Pedersen 		}
892c7e67811SThomas Pedersen 		break;
893c7e67811SThomas Pedersen 	case NL80211_PLINK_OPN_SNT:
894c7e67811SThomas Pedersen 		switch (event) {
895c7e67811SThomas Pedersen 		case OPN_RJCT:
896c7e67811SThomas Pedersen 		case CNF_RJCT:
897c7e67811SThomas Pedersen 		case CLS_ACPT:
898c7e67811SThomas Pedersen 			mesh_plink_close(sdata, sta, event);
899c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CLOSE;
900c7e67811SThomas Pedersen 			break;
901c7e67811SThomas Pedersen 		case OPN_ACPT:
902c7e67811SThomas Pedersen 			/* retry timer is left untouched */
903433f5bc1SJohannes Berg 			sta->mesh->plink_state = NL80211_PLINK_OPN_RCVD;
904c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CONFIRM;
905c7e67811SThomas Pedersen 			break;
906c7e67811SThomas Pedersen 		case CNF_ACPT:
907433f5bc1SJohannes Berg 			sta->mesh->plink_state = NL80211_PLINK_CNF_RCVD;
9082b470c39SBob Copeland 			mod_plink_timer(sta, mshcfg->dot11MeshConfirmTimeout);
909c7e67811SThomas Pedersen 			break;
910c7e67811SThomas Pedersen 		default:
911c7e67811SThomas Pedersen 			break;
912c7e67811SThomas Pedersen 		}
913c7e67811SThomas Pedersen 		break;
914c7e67811SThomas Pedersen 	case NL80211_PLINK_OPN_RCVD:
915c7e67811SThomas Pedersen 		switch (event) {
916c7e67811SThomas Pedersen 		case OPN_RJCT:
917c7e67811SThomas Pedersen 		case CNF_RJCT:
918c7e67811SThomas Pedersen 		case CLS_ACPT:
919c7e67811SThomas Pedersen 			mesh_plink_close(sdata, sta, event);
920c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CLOSE;
921c7e67811SThomas Pedersen 			break;
922c7e67811SThomas Pedersen 		case OPN_ACPT:
923c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CONFIRM;
924c7e67811SThomas Pedersen 			break;
925c7e67811SThomas Pedersen 		case CNF_ACPT:
926c7e67811SThomas Pedersen 			changed |= mesh_plink_establish(sdata, sta);
927c7e67811SThomas Pedersen 			break;
928c7e67811SThomas Pedersen 		default:
929c7e67811SThomas Pedersen 			break;
930c7e67811SThomas Pedersen 		}
931c7e67811SThomas Pedersen 		break;
932c7e67811SThomas Pedersen 	case NL80211_PLINK_CNF_RCVD:
933c7e67811SThomas Pedersen 		switch (event) {
934c7e67811SThomas Pedersen 		case OPN_RJCT:
935c7e67811SThomas Pedersen 		case CNF_RJCT:
936c7e67811SThomas Pedersen 		case CLS_ACPT:
937c7e67811SThomas Pedersen 			mesh_plink_close(sdata, sta, event);
938c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CLOSE;
939c7e67811SThomas Pedersen 			break;
940c7e67811SThomas Pedersen 		case OPN_ACPT:
941c7e67811SThomas Pedersen 			changed |= mesh_plink_establish(sdata, sta);
942c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CONFIRM;
943c7e67811SThomas Pedersen 			break;
944c7e67811SThomas Pedersen 		default:
945c7e67811SThomas Pedersen 			break;
946c7e67811SThomas Pedersen 		}
947c7e67811SThomas Pedersen 		break;
948c7e67811SThomas Pedersen 	case NL80211_PLINK_ESTAB:
949c7e67811SThomas Pedersen 		switch (event) {
950c7e67811SThomas Pedersen 		case CLS_ACPT:
951c7e67811SThomas Pedersen 			changed |= __mesh_plink_deactivate(sta);
952c7e67811SThomas Pedersen 			changed |= mesh_set_ht_prot_mode(sdata);
953c7e67811SThomas Pedersen 			changed |= mesh_set_short_slot_time(sdata);
954c7e67811SThomas Pedersen 			mesh_plink_close(sdata, sta, event);
955c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CLOSE;
956e596af82SBob Copeland 			flush = true;
957c7e67811SThomas Pedersen 			break;
958c7e67811SThomas Pedersen 		case OPN_ACPT:
959c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CONFIRM;
960c7e67811SThomas Pedersen 			break;
961c7e67811SThomas Pedersen 		default:
962c7e67811SThomas Pedersen 			break;
963c7e67811SThomas Pedersen 		}
964c7e67811SThomas Pedersen 		break;
965c7e67811SThomas Pedersen 	case NL80211_PLINK_HOLDING:
966c7e67811SThomas Pedersen 		switch (event) {
967c7e67811SThomas Pedersen 		case CLS_ACPT:
968433f5bc1SJohannes Berg 			del_timer(&sta->mesh->plink_timer);
969c7e67811SThomas Pedersen 			mesh_plink_fsm_restart(sta);
970c7e67811SThomas Pedersen 			break;
971c7e67811SThomas Pedersen 		case OPN_ACPT:
972c7e67811SThomas Pedersen 		case CNF_ACPT:
973c7e67811SThomas Pedersen 		case OPN_RJCT:
974c7e67811SThomas Pedersen 		case CNF_RJCT:
975c7e67811SThomas Pedersen 			action = WLAN_SP_MESH_PEERING_CLOSE;
976c7e67811SThomas Pedersen 			break;
977c7e67811SThomas Pedersen 		default:
978c7e67811SThomas Pedersen 			break;
979c7e67811SThomas Pedersen 		}
980c7e67811SThomas Pedersen 		break;
981c7e67811SThomas Pedersen 	default:
982c7e67811SThomas Pedersen 		/* should not get here, PLINK_BLOCKED is dealt with at the
983c7e67811SThomas Pedersen 		 * beginning of the function
984c7e67811SThomas Pedersen 		 */
985c7e67811SThomas Pedersen 		break;
986c7e67811SThomas Pedersen 	}
987433f5bc1SJohannes Berg 	spin_unlock_bh(&sta->mesh->plink_lock);
988e596af82SBob Copeland 	if (flush)
989e596af82SBob Copeland 		mesh_path_flush_by_nexthop(sta);
990c7e67811SThomas Pedersen 	if (action) {
991a69bd8e6SBob Copeland 		mesh_plink_frame_tx(sdata, sta, action, sta->sta.addr,
992433f5bc1SJohannes Berg 				    sta->mesh->llid, sta->mesh->plid,
993433f5bc1SJohannes Berg 				    sta->mesh->reason);
994c7e67811SThomas Pedersen 
995c7e67811SThomas Pedersen 		/* also send confirm in open case */
996c7e67811SThomas Pedersen 		if (action == WLAN_SP_MESH_PEERING_OPEN) {
997a69bd8e6SBob Copeland 			mesh_plink_frame_tx(sdata, sta,
998c7e67811SThomas Pedersen 					    WLAN_SP_MESH_PEERING_CONFIRM,
999433f5bc1SJohannes Berg 					    sta->sta.addr, sta->mesh->llid,
1000433f5bc1SJohannes Berg 					    sta->mesh->plid, 0);
1001c7e67811SThomas Pedersen 		}
1002c7e67811SThomas Pedersen 	}
1003c7e67811SThomas Pedersen 
1004c7e67811SThomas Pedersen 	return changed;
1005c7e67811SThomas Pedersen }
1006c7e67811SThomas Pedersen 
1007c99a89edSThomas Pedersen /*
1008c99a89edSThomas Pedersen  * mesh_plink_get_event - get correct MPM event
1009c99a89edSThomas Pedersen  *
1010c99a89edSThomas Pedersen  * @sdata: interface
1011c99a89edSThomas Pedersen  * @sta: peer, leave NULL if processing a frame from a new suitable peer
1012c99a89edSThomas Pedersen  * @elems: peering management IEs
1013c99a89edSThomas Pedersen  * @ftype: frame type
1014c99a89edSThomas Pedersen  * @llid: peer's peer link ID
1015c99a89edSThomas Pedersen  * @plid: peer's local link ID
1016c99a89edSThomas Pedersen  *
1017c99a89edSThomas Pedersen  * Return: new peering event for @sta, but PLINK_UNDEFINED should be treated as
1018c99a89edSThomas Pedersen  * an error.
1019c99a89edSThomas Pedersen  */
1020c99a89edSThomas Pedersen static enum plink_event
mesh_plink_get_event(struct ieee80211_sub_if_data * sdata,struct sta_info * sta,struct ieee802_11_elems * elems,enum ieee80211_self_protected_actioncode ftype,u16 llid,u16 plid)1021c99a89edSThomas Pedersen mesh_plink_get_event(struct ieee80211_sub_if_data *sdata,
1022c99a89edSThomas Pedersen 		     struct sta_info *sta,
1023c99a89edSThomas Pedersen 		     struct ieee802_11_elems *elems,
1024c99a89edSThomas Pedersen 		     enum ieee80211_self_protected_actioncode ftype,
10256f101ef0SChun-Yeow Yeoh 		     u16 llid, u16 plid)
1026c99a89edSThomas Pedersen {
1027c99a89edSThomas Pedersen 	enum plink_event event = PLINK_UNDEFINED;
1028c99a89edSThomas Pedersen 	u8 ie_len = elems->peering_len;
1029c99a89edSThomas Pedersen 	bool matches_local;
1030c99a89edSThomas Pedersen 
1031c99a89edSThomas Pedersen 	matches_local = (ftype == WLAN_SP_MESH_PEERING_CLOSE ||
1032c99a89edSThomas Pedersen 			 mesh_matches_local(sdata, elems));
1033c99a89edSThomas Pedersen 
1034c99a89edSThomas Pedersen 	/* deny open request from non-matching peer */
1035c99a89edSThomas Pedersen 	if (!matches_local && !sta) {
1036c99a89edSThomas Pedersen 		event = OPN_RJCT;
1037c99a89edSThomas Pedersen 		goto out;
1038c99a89edSThomas Pedersen 	}
1039c99a89edSThomas Pedersen 
1040c99a89edSThomas Pedersen 	if (!sta) {
1041c99a89edSThomas Pedersen 		if (ftype != WLAN_SP_MESH_PEERING_OPEN) {
1042c99a89edSThomas Pedersen 			mpl_dbg(sdata, "Mesh plink: cls or cnf from unknown peer\n");
1043c99a89edSThomas Pedersen 			goto out;
1044c99a89edSThomas Pedersen 		}
1045c99a89edSThomas Pedersen 		/* ftype == WLAN_SP_MESH_PEERING_OPEN */
1046c99a89edSThomas Pedersen 		if (!mesh_plink_free_count(sdata)) {
1047c99a89edSThomas Pedersen 			mpl_dbg(sdata, "Mesh plink error: no more free plinks\n");
1048c99a89edSThomas Pedersen 			goto out;
1049c99a89edSThomas Pedersen 		}
1050b8631c00SSunil Shahu 
1051b8631c00SSunil Shahu 		/* new matching peer */
1052b8631c00SSunil Shahu 		event = OPN_ACPT;
1053b8631c00SSunil Shahu 		goto out;
1054c99a89edSThomas Pedersen 	} else {
1055c99a89edSThomas Pedersen 		if (!test_sta_flag(sta, WLAN_STA_AUTH)) {
1056c99a89edSThomas Pedersen 			mpl_dbg(sdata, "Mesh plink: Action frame from non-authed peer\n");
1057c99a89edSThomas Pedersen 			goto out;
1058c99a89edSThomas Pedersen 		}
1059433f5bc1SJohannes Berg 		if (sta->mesh->plink_state == NL80211_PLINK_BLOCKED)
1060c99a89edSThomas Pedersen 			goto out;
1061c99a89edSThomas Pedersen 	}
1062c99a89edSThomas Pedersen 
1063c99a89edSThomas Pedersen 	switch (ftype) {
1064c99a89edSThomas Pedersen 	case WLAN_SP_MESH_PEERING_OPEN:
1065c99a89edSThomas Pedersen 		if (!matches_local)
1066c99a89edSThomas Pedersen 			event = OPN_RJCT;
1067*26479609SJohannes Berg 		else if (!mesh_plink_free_count(sdata) ||
1068433f5bc1SJohannes Berg 			 (sta->mesh->plid && sta->mesh->plid != plid))
1069c99a89edSThomas Pedersen 			event = OPN_IGNR;
1070c99a89edSThomas Pedersen 		else
1071c99a89edSThomas Pedersen 			event = OPN_ACPT;
1072c99a89edSThomas Pedersen 		break;
1073c99a89edSThomas Pedersen 	case WLAN_SP_MESH_PEERING_CONFIRM:
1074c99a89edSThomas Pedersen 		if (!matches_local)
1075c99a89edSThomas Pedersen 			event = CNF_RJCT;
1076*26479609SJohannes Berg 		else if (!mesh_plink_free_count(sdata) ||
1077433f5bc1SJohannes Berg 			 sta->mesh->llid != llid ||
1078433f5bc1SJohannes Berg 			 (sta->mesh->plid && sta->mesh->plid != plid))
1079c99a89edSThomas Pedersen 			event = CNF_IGNR;
1080c99a89edSThomas Pedersen 		else
1081c99a89edSThomas Pedersen 			event = CNF_ACPT;
1082c99a89edSThomas Pedersen 		break;
1083c99a89edSThomas Pedersen 	case WLAN_SP_MESH_PEERING_CLOSE:
1084433f5bc1SJohannes Berg 		if (sta->mesh->plink_state == NL80211_PLINK_ESTAB)
1085c99a89edSThomas Pedersen 			/* Do not check for llid or plid. This does not
1086c99a89edSThomas Pedersen 			 * follow the standard but since multiple plinks
1087c99a89edSThomas Pedersen 			 * per sta are not supported, it is necessary in
1088c99a89edSThomas Pedersen 			 * order to avoid a livelock when MP A sees an
1089c99a89edSThomas Pedersen 			 * establish peer link to MP B but MP B does not
1090c99a89edSThomas Pedersen 			 * see it. This can be caused by a timeout in
1091c99a89edSThomas Pedersen 			 * B's peer link establishment or B beign
1092c99a89edSThomas Pedersen 			 * restarted.
1093c99a89edSThomas Pedersen 			 */
1094c99a89edSThomas Pedersen 			event = CLS_ACPT;
1095433f5bc1SJohannes Berg 		else if (sta->mesh->plid != plid)
1096c99a89edSThomas Pedersen 			event = CLS_IGNR;
1097433f5bc1SJohannes Berg 		else if (ie_len == 8 && sta->mesh->llid != llid)
1098c99a89edSThomas Pedersen 			event = CLS_IGNR;
1099c99a89edSThomas Pedersen 		else
1100c99a89edSThomas Pedersen 			event = CLS_ACPT;
1101c99a89edSThomas Pedersen 		break;
1102c99a89edSThomas Pedersen 	default:
1103c99a89edSThomas Pedersen 		mpl_dbg(sdata, "Mesh plink: unknown frame subtype\n");
1104c99a89edSThomas Pedersen 		break;
1105c99a89edSThomas Pedersen 	}
1106c99a89edSThomas Pedersen 
1107c99a89edSThomas Pedersen out:
1108c99a89edSThomas Pedersen 	return event;
1109c99a89edSThomas Pedersen }
1110c99a89edSThomas Pedersen 
111105a23ae9SThomas Pedersen static void
mesh_process_plink_frame(struct ieee80211_sub_if_data * sdata,struct ieee80211_mgmt * mgmt,struct ieee802_11_elems * elems,struct ieee80211_rx_status * rx_status)111205a23ae9SThomas Pedersen mesh_process_plink_frame(struct ieee80211_sub_if_data *sdata,
111305a23ae9SThomas Pedersen 			 struct ieee80211_mgmt *mgmt,
1114ecbc12adSBob Copeland 			 struct ieee802_11_elems *elems,
1115ecbc12adSBob Copeland 			 struct ieee80211_rx_status *rx_status)
1116c3896d2cSLuis Carlos Cobo {
111705a23ae9SThomas Pedersen 
1118c3896d2cSLuis Carlos Cobo 	struct sta_info *sta;
1119c3896d2cSLuis Carlos Cobo 	enum plink_event event;
112054ef656bSThomas Pedersen 	enum ieee80211_self_protected_actioncode ftype;
112115ddba5fSAnjaneyulu 	u64 changed = 0;
1122c99a89edSThomas Pedersen 	u8 ie_len = elems->peering_len;
11236f101ef0SChun-Yeow Yeoh 	u16 plid, llid = 0;
1124c3896d2cSLuis Carlos Cobo 
112505a23ae9SThomas Pedersen 	if (!elems->peering) {
1126bdcbd8e0SJohannes Berg 		mpl_dbg(sdata,
1127bdcbd8e0SJohannes Berg 			"Mesh plink: missing necessary peer link ie\n");
1128c3896d2cSLuis Carlos Cobo 		return;
1129c3896d2cSLuis Carlos Cobo 	}
1130bf7cd94dSJohannes Berg 
113105a23ae9SThomas Pedersen 	if (elems->rsn_len &&
1132b130e5ceSJavier Cardona 	    sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) {
1133bdcbd8e0SJohannes Berg 		mpl_dbg(sdata,
1134bdcbd8e0SJohannes Berg 			"Mesh plink: can't establish link with secure peer\n");
11355cff5e01SJavier Cardona 		return;
11365cff5e01SJavier Cardona 	}
1137c3896d2cSLuis Carlos Cobo 
11388db09850SThomas Pedersen 	ftype = mgmt->u.action.u.self_prot.action_code;
11398db09850SThomas Pedersen 	if ((ftype == WLAN_SP_MESH_PEERING_OPEN && ie_len != 4) ||
11408db09850SThomas Pedersen 	    (ftype == WLAN_SP_MESH_PEERING_CONFIRM && ie_len != 6) ||
11418db09850SThomas Pedersen 	    (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len != 6
11428db09850SThomas Pedersen 							&& ie_len != 8)) {
1143bdcbd8e0SJohannes Berg 		mpl_dbg(sdata,
1144bdcbd8e0SJohannes Berg 			"Mesh plink: incorrect plink ie length %d %d\n",
11450938393fSRui Paulo 			ftype, ie_len);
1146c3896d2cSLuis Carlos Cobo 		return;
1147c3896d2cSLuis Carlos Cobo 	}
1148c3896d2cSLuis Carlos Cobo 
114954ef656bSThomas Pedersen 	if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
115005a23ae9SThomas Pedersen 	    (!elems->mesh_id || !elems->mesh_config)) {
1151bdcbd8e0SJohannes Berg 		mpl_dbg(sdata, "Mesh plink: missing necessary ie\n");
1152c3896d2cSLuis Carlos Cobo 		return;
1153c3896d2cSLuis Carlos Cobo 	}
1154c3896d2cSLuis Carlos Cobo 	/* Note the lines below are correct, the llid in the frame is the plid
1155c3896d2cSLuis Carlos Cobo 	 * from the point of view of this host.
1156c3896d2cSLuis Carlos Cobo 	 */
1157f8134fedSBob Copeland 	plid = get_unaligned_le16(PLINK_GET_LLID(elems->peering));
115854ef656bSThomas Pedersen 	if (ftype == WLAN_SP_MESH_PEERING_CONFIRM ||
1159f8134fedSBob Copeland 	    (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len == 8))
1160f8134fedSBob Copeland 		llid = get_unaligned_le16(PLINK_GET_PLID(elems->peering));
1161c3896d2cSLuis Carlos Cobo 
1162296fcba3SThomas Pedersen 	/* WARNING: Only for sta pointer, is dropped & re-acquired */
1163d0709a65SJohannes Berg 	rcu_read_lock();
1164d0709a65SJohannes Berg 
1165abe60632SJohannes Berg 	sta = sta_info_get(sdata, mgmt->sa);
116632cb05bfSBob Copeland 
116755335137SAshok Nagarajan 	if (ftype == WLAN_SP_MESH_PEERING_OPEN &&
116836c9bb29SBob Copeland 	    !rssi_threshold_check(sdata, sta)) {
1169bdcbd8e0SJohannes Berg 		mpl_dbg(sdata, "Mesh plink: %pM does not meet rssi threshold\n",
11703d4f9699SAshok Nagarajan 			mgmt->sa);
1171fc10302eSThomas Pedersen 		goto unlock_rcu;
117255335137SAshok Nagarajan 	}
117355335137SAshok Nagarajan 
1174c3896d2cSLuis Carlos Cobo 	/* Now we will figure out the appropriate event... */
1175c99a89edSThomas Pedersen 	event = mesh_plink_get_event(sdata, sta, elems, ftype, llid, plid);
117654ab1ffbSThomas Pedersen 
117754ab1ffbSThomas Pedersen 	if (event == OPN_ACPT) {
1178296fcba3SThomas Pedersen 		rcu_read_unlock();
117954ab1ffbSThomas Pedersen 		/* allocate sta entry if necessary and update info */
1180ecbc12adSBob Copeland 		sta = mesh_sta_info_get(sdata, mgmt->sa, elems, rx_status);
118154ab1ffbSThomas Pedersen 		if (!sta) {
1182bdcbd8e0SJohannes Berg 			mpl_dbg(sdata, "Mesh plink: failed to init peer!\n");
1183fc10302eSThomas Pedersen 			goto unlock_rcu;
118454ab1ffbSThomas Pedersen 		}
1185433f5bc1SJohannes Berg 		sta->mesh->plid = plid;
1186c99a89edSThomas Pedersen 	} else if (!sta && event == OPN_RJCT) {
1187a69bd8e6SBob Copeland 		mesh_plink_frame_tx(sdata, NULL, WLAN_SP_MESH_PEERING_CLOSE,
1188c99a89edSThomas Pedersen 				    mgmt->sa, 0, plid,
11896f101ef0SChun-Yeow Yeoh 				    WLAN_REASON_MESH_CONFIG);
1190c99a89edSThomas Pedersen 		goto unlock_rcu;
1191c99a89edSThomas Pedersen 	} else if (!sta || event == PLINK_UNDEFINED) {
1192c99a89edSThomas Pedersen 		/* something went wrong */
1193c99a89edSThomas Pedersen 		goto unlock_rcu;
1194c3896d2cSLuis Carlos Cobo 	}
1195c3896d2cSLuis Carlos Cobo 
1196a69bd8e6SBob Copeland 	if (event == CNF_ACPT) {
11976c6fa496SBob Copeland 		/* 802.11-2012 13.3.7.2 - update plid on CNF if not set */
11980e0060fcSBob Copeland 		if (!sta->mesh->plid)
1199433f5bc1SJohannes Berg 			sta->mesh->plid = plid;
1200a69bd8e6SBob Copeland 
1201a69bd8e6SBob Copeland 		sta->mesh->aid = get_unaligned_le16(PLINK_CNF_AID(mgmt));
1202a69bd8e6SBob Copeland 	}
12036c6fa496SBob Copeland 
1204c7e67811SThomas Pedersen 	changed |= mesh_plink_fsm(sdata, sta, event);
1205d0709a65SJohannes Berg 
1206fc10302eSThomas Pedersen unlock_rcu:
1207d0709a65SJohannes Berg 	rcu_read_unlock();
120857aac7c5SAshok Nagarajan 
1209ecccd072SThomas Pedersen 	if (changed)
12102b5e1967SThomas Pedersen 		ieee80211_mbss_info_change_notify(sdata, changed);
1211c3896d2cSLuis Carlos Cobo }
121205a23ae9SThomas Pedersen 
mesh_rx_plink_frame(struct ieee80211_sub_if_data * sdata,struct ieee80211_mgmt * mgmt,size_t len,struct ieee80211_rx_status * rx_status)121305a23ae9SThomas Pedersen void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata,
121405a23ae9SThomas Pedersen 			 struct ieee80211_mgmt *mgmt, size_t len,
121505a23ae9SThomas Pedersen 			 struct ieee80211_rx_status *rx_status)
121605a23ae9SThomas Pedersen {
12175d24828dSJohannes Berg 	struct ieee802_11_elems *elems;
121805a23ae9SThomas Pedersen 	size_t baselen;
121905a23ae9SThomas Pedersen 	u8 *baseaddr;
122005a23ae9SThomas Pedersen 
122105a23ae9SThomas Pedersen 	/* need action_code, aux */
122205a23ae9SThomas Pedersen 	if (len < IEEE80211_MIN_ACTION_SIZE + 3)
122305a23ae9SThomas Pedersen 		return;
122405a23ae9SThomas Pedersen 
122505a23ae9SThomas Pedersen 	if (sdata->u.mesh.user_mpm)
122605a23ae9SThomas Pedersen 		/* userspace must register for these */
122705a23ae9SThomas Pedersen 		return;
122805a23ae9SThomas Pedersen 
122905a23ae9SThomas Pedersen 	if (is_multicast_ether_addr(mgmt->da)) {
123005a23ae9SThomas Pedersen 		mpl_dbg(sdata,
123105a23ae9SThomas Pedersen 			"Mesh plink: ignore frame from multicast address\n");
123205a23ae9SThomas Pedersen 		return;
123305a23ae9SThomas Pedersen 	}
123405a23ae9SThomas Pedersen 
123505a23ae9SThomas Pedersen 	baseaddr = mgmt->u.action.u.self_prot.variable;
123605a23ae9SThomas Pedersen 	baselen = (u8 *) mgmt->u.action.u.self_prot.variable - (u8 *) mgmt;
123705a23ae9SThomas Pedersen 	if (mgmt->u.action.u.self_prot.action_code ==
123805a23ae9SThomas Pedersen 						WLAN_SP_MESH_PEERING_CONFIRM) {
123905a23ae9SThomas Pedersen 		baseaddr += 4;
124005a23ae9SThomas Pedersen 		baselen += 4;
1241b3e7de87SBob Copeland 
1242b3e7de87SBob Copeland 		if (baselen > len)
1243b3e7de87SBob Copeland 			return;
124405a23ae9SThomas Pedersen 	}
124538c6aa29SJohannes Berg 	elems = ieee802_11_parse_elems(baseaddr, len - baselen, true, NULL);
12461c8d8012SJohannes Berg 	if (elems) {
12475d24828dSJohannes Berg 		mesh_process_plink_frame(sdata, mgmt, elems, rx_status);
12485d24828dSJohannes Berg 		kfree(elems);
124905a23ae9SThomas Pedersen 	}
12501c8d8012SJohannes Berg }
1251