summaryrefslogtreecommitdiffstats
path: root/net/mac80211/chan.c
blob: 0bfc914ddd1504d16a2ebf8ad74655d6deb60e53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
 * mac80211 - channel management
 */

#include <linux/nl80211.h>
#include <net/cfg80211.h>
#include "ieee80211_i.h"

static enum ieee80211_chan_mode
__ieee80211_get_channel_mode(struct ieee80211_local *local,
			     struct ieee80211_sub_if_data *ignore)
{
	struct ieee80211_sub_if_data *sdata;

	lockdep_assert_held(&local->iflist_mtx);

	list_for_each_entry(sdata, &local->interfaces, list) {
		if (sdata == ignore)
			continue;

		if (!ieee80211_sdata_running(sdata))
			continue;

		switch (sdata->vif.type) {
		case NL80211_IFTYPE_MONITOR:
			continue;
		case NL80211_IFTYPE_STATION:
			if (!sdata->u.mgd.associated)
				continue;
			break;
		case NL80211_IFTYPE_ADHOC:
			if (!sdata->u.ibss.ssid_len)
				continue;
			if (!sdata->u.ibss.fixed_channel)
				return CHAN_MODE_HOPPING;
			break;
		case NL80211_IFTYPE_AP_VLAN:
			/* will also have _AP interface */
			continue;
		case NL80211_IFTYPE_AP:
			if (!sdata->u.ap.beacon)
				continue;
			break;
		case NL80211_IFTYPE_MESH_POINT:
			if (!sdata->wdev.mesh_id_len)
				continue;
			break;
		default:
			break;
		}

		return CHAN_MODE_FIXED;
	}

	return CHAN_MODE_UNDEFINED;
}

enum ieee80211_chan_mode
ieee80211_get_channel_mode(struct ieee80211_local *local,
			   struct ieee80211_sub_if_data *ignore)
{
	enum ieee80211_chan_mode mode;

	mutex_lock(&local->iflist_mtx);
	mode = __ieee80211_get_channel_mode(local, ignore);
	mutex_unlock(&local->iflist_mtx);

	return mode;
}

static enum nl80211_channel_type
ieee80211_get_superchan(struct ieee80211_local *local,
			struct ieee80211_sub_if_data *sdata)
{
	enum nl80211_channel_type superchan = NL80211_CHAN_NO_HT;
	struct ieee80211_sub_if_data *tmp;

	mutex_lock(&local->iflist_mtx);
	list_for_each_entry(tmp, &local->interfaces, list) {
		if (tmp == sdata)
			continue;

		if (!ieee80211_sdata_running(tmp))
			continue;

		switch (tmp->vif.bss_conf.channel_type) {
		case NL80211_CHAN_NO_HT:
		case NL80211_CHAN_HT20:
			if (superchan > tmp->vif.bss_conf.channel_type)
				break;

			superchan = tmp->vif.bss_conf.channel_type;
			break;
		case NL80211_CHAN_HT40PLUS:
			WARN_ON(superchan == NL80211_CHAN_HT40MINUS);
			superchan = NL80211_CHAN_HT40PLUS;
			break;
		case NL80211_CHAN_HT40MINUS:
			WARN_ON(superchan == NL80211_CHAN_HT40PLUS);
			superchan = NL80211_CHAN_HT40MINUS;
			break;
		}
	}
	mutex_unlock(&local->iflist_mtx);

	return superchan;
}

static bool
ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1,
				       enum nl80211_channel_type chantype2,
				       enum nl80211_channel_type *compat)
{
	/*
	 * start out with chantype1 being the result,
	 * overwriting later if needed
	 */
	if (compat)
		*compat = chantype1;

	switch (chantype1) {
	case NL80211_CHAN_NO_HT:
		if (compat)
			*compat = chantype2;
		break;
	case NL80211_CHAN_HT20:
		/*
		 * allow any change that doesn't go to no-HT
		 * (if it already is no-HT no change is needed)
		 */
		if (chantype2 == NL80211_CHAN_NO_HT)
			break;
		if (compat)
			*compat = chantype2;
		break;
	case NL80211_CHAN_HT40PLUS:
	case NL80211_CHAN_HT40MINUS:
		/* allow smaller bandwidth and same */
		if (chantype2 == NL80211_CHAN_NO_HT)
			break;
		if (chantype2 == NL80211_CHAN_HT20)
			break;
		if (chantype2 == chantype1)
			break;
		return false;
	}

	return true;
}

bool ieee80211_set_channel_type(struct ieee80211_local *local,
				struct ieee80211_sub_if_data *sdata,
				enum nl80211_channel_type chantype)
{
	enum nl80211_channel_type superchan;
	enum nl80211_channel_type compatchan;

	superchan = ieee80211_get_superchan(local, sdata);
	if (!ieee80211_channel_types_are_compatible(superchan, chantype,
						    &compatchan))
		return false;

	local->_oper_channel_type = compatchan;

	if (sdata)
		sdata->vif.bss_conf.channel_type = chantype;

	return true;

}