xref: /openbmc/linux/net/mac80211/chan.c (revision 05bcf503)
1 /*
2  * mac80211 - channel management
3  */
4 
5 #include <linux/nl80211.h>
6 #include <net/cfg80211.h>
7 #include "ieee80211_i.h"
8 
9 static enum ieee80211_chan_mode
10 __ieee80211_get_channel_mode(struct ieee80211_local *local,
11 			     struct ieee80211_sub_if_data *ignore)
12 {
13 	struct ieee80211_sub_if_data *sdata;
14 
15 	lockdep_assert_held(&local->iflist_mtx);
16 
17 	list_for_each_entry(sdata, &local->interfaces, list) {
18 		if (sdata == ignore)
19 			continue;
20 
21 		if (!ieee80211_sdata_running(sdata))
22 			continue;
23 
24 		switch (sdata->vif.type) {
25 		case NL80211_IFTYPE_MONITOR:
26 			continue;
27 		case NL80211_IFTYPE_STATION:
28 			if (!sdata->u.mgd.associated)
29 				continue;
30 			break;
31 		case NL80211_IFTYPE_ADHOC:
32 			if (!sdata->u.ibss.ssid_len)
33 				continue;
34 			if (!sdata->u.ibss.fixed_channel)
35 				return CHAN_MODE_HOPPING;
36 			break;
37 		case NL80211_IFTYPE_AP_VLAN:
38 			/* will also have _AP interface */
39 			continue;
40 		case NL80211_IFTYPE_AP:
41 			if (!sdata->u.ap.beacon)
42 				continue;
43 			break;
44 		case NL80211_IFTYPE_MESH_POINT:
45 			if (!sdata->wdev.mesh_id_len)
46 				continue;
47 			break;
48 		default:
49 			break;
50 		}
51 
52 		return CHAN_MODE_FIXED;
53 	}
54 
55 	return CHAN_MODE_UNDEFINED;
56 }
57 
58 enum ieee80211_chan_mode
59 ieee80211_get_channel_mode(struct ieee80211_local *local,
60 			   struct ieee80211_sub_if_data *ignore)
61 {
62 	enum ieee80211_chan_mode mode;
63 
64 	mutex_lock(&local->iflist_mtx);
65 	mode = __ieee80211_get_channel_mode(local, ignore);
66 	mutex_unlock(&local->iflist_mtx);
67 
68 	return mode;
69 }
70 
71 static enum nl80211_channel_type
72 ieee80211_get_superchan(struct ieee80211_local *local,
73 			struct ieee80211_sub_if_data *sdata)
74 {
75 	enum nl80211_channel_type superchan = NL80211_CHAN_NO_HT;
76 	struct ieee80211_sub_if_data *tmp;
77 
78 	mutex_lock(&local->iflist_mtx);
79 	list_for_each_entry(tmp, &local->interfaces, list) {
80 		if (tmp == sdata)
81 			continue;
82 
83 		if (!ieee80211_sdata_running(tmp))
84 			continue;
85 
86 		switch (tmp->vif.bss_conf.channel_type) {
87 		case NL80211_CHAN_NO_HT:
88 		case NL80211_CHAN_HT20:
89 			if (superchan > tmp->vif.bss_conf.channel_type)
90 				break;
91 
92 			superchan = tmp->vif.bss_conf.channel_type;
93 			break;
94 		case NL80211_CHAN_HT40PLUS:
95 			WARN_ON(superchan == NL80211_CHAN_HT40MINUS);
96 			superchan = NL80211_CHAN_HT40PLUS;
97 			break;
98 		case NL80211_CHAN_HT40MINUS:
99 			WARN_ON(superchan == NL80211_CHAN_HT40PLUS);
100 			superchan = NL80211_CHAN_HT40MINUS;
101 			break;
102 		}
103 	}
104 	mutex_unlock(&local->iflist_mtx);
105 
106 	return superchan;
107 }
108 
109 static bool
110 ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1,
111 				       enum nl80211_channel_type chantype2,
112 				       enum nl80211_channel_type *compat)
113 {
114 	/*
115 	 * start out with chantype1 being the result,
116 	 * overwriting later if needed
117 	 */
118 	if (compat)
119 		*compat = chantype1;
120 
121 	switch (chantype1) {
122 	case NL80211_CHAN_NO_HT:
123 		if (compat)
124 			*compat = chantype2;
125 		break;
126 	case NL80211_CHAN_HT20:
127 		/*
128 		 * allow any change that doesn't go to no-HT
129 		 * (if it already is no-HT no change is needed)
130 		 */
131 		if (chantype2 == NL80211_CHAN_NO_HT)
132 			break;
133 		if (compat)
134 			*compat = chantype2;
135 		break;
136 	case NL80211_CHAN_HT40PLUS:
137 	case NL80211_CHAN_HT40MINUS:
138 		/* allow smaller bandwidth and same */
139 		if (chantype2 == NL80211_CHAN_NO_HT)
140 			break;
141 		if (chantype2 == NL80211_CHAN_HT20)
142 			break;
143 		if (chantype2 == chantype1)
144 			break;
145 		return false;
146 	}
147 
148 	return true;
149 }
150 
151 bool ieee80211_set_channel_type(struct ieee80211_local *local,
152 				struct ieee80211_sub_if_data *sdata,
153 				enum nl80211_channel_type chantype)
154 {
155 	enum nl80211_channel_type superchan;
156 	enum nl80211_channel_type compatchan;
157 
158 	superchan = ieee80211_get_superchan(local, sdata);
159 	if (!ieee80211_channel_types_are_compatible(superchan, chantype,
160 						    &compatchan))
161 		return false;
162 
163 	local->_oper_channel_type = compatchan;
164 
165 	if (sdata)
166 		sdata->vif.bss_conf.channel_type = chantype;
167 
168 	return true;
169 
170 }
171