Commit | Line | Data |
---|---|---|
fb791b1c DK |
1 | /* Helpers for managing scan queues |
2 | * | |
3 | * See copyright notice in main.c | |
4 | */ | |
5 | ||
5a0e3ad6 | 6 | #include <linux/gfp.h> |
fb791b1c DK |
7 | #include <linux/kernel.h> |
8 | #include <linux/string.h> | |
c63cdbe8 DK |
9 | #include <linux/ieee80211.h> |
10 | #include <net/cfg80211.h> | |
fb791b1c DK |
11 | |
12 | #include "hermes.h" | |
13 | #include "orinoco.h" | |
c63cdbe8 | 14 | #include "main.h" |
fb791b1c DK |
15 | |
16 | #include "scan.h" | |
17 | ||
c63cdbe8 DK |
18 | #define ZERO_DBM_OFFSET 0x95 |
19 | #define MAX_SIGNAL_LEVEL 0x8A | |
20 | #define MIN_SIGNAL_LEVEL 0x2F | |
fb791b1c | 21 | |
c63cdbe8 DK |
22 | #define SIGNAL_TO_DBM(x) \ |
23 | (clamp_t(s32, (x), MIN_SIGNAL_LEVEL, MAX_SIGNAL_LEVEL) \ | |
24 | - ZERO_DBM_OFFSET) | |
25 | #define SIGNAL_TO_MBM(x) (SIGNAL_TO_DBM(x) * 100) | |
fb791b1c | 26 | |
c63cdbe8 | 27 | static int symbol_build_supp_rates(u8 *buf, const __le16 *rates) |
fb791b1c | 28 | { |
c63cdbe8 DK |
29 | int i; |
30 | u8 rate; | |
31 | ||
32 | buf[0] = WLAN_EID_SUPP_RATES; | |
33 | for (i = 0; i < 5; i++) { | |
34 | rate = le16_to_cpu(rates[i]); | |
35 | /* NULL terminated */ | |
36 | if (rate == 0x0) | |
37 | break; | |
38 | buf[i + 2] = rate; | |
fb791b1c | 39 | } |
c63cdbe8 | 40 | buf[1] = i; |
fb791b1c | 41 | |
c63cdbe8 | 42 | return i + 2; |
fb791b1c DK |
43 | } |
44 | ||
c63cdbe8 | 45 | static int prism_build_supp_rates(u8 *buf, const u8 *rates) |
fb791b1c DK |
46 | { |
47 | int i; | |
48 | ||
c63cdbe8 DK |
49 | buf[0] = WLAN_EID_SUPP_RATES; |
50 | for (i = 0; i < 8; i++) { | |
51 | /* NULL terminated */ | |
52 | if (rates[i] == 0x0) | |
53 | break; | |
54 | buf[i + 2] = rates[i]; | |
55 | } | |
56 | buf[1] = i; | |
57 | ||
58 | /* We might still have another 2 rates, which need to go in | |
59 | * extended supported rates */ | |
60 | if (i == 8 && rates[i] > 0) { | |
61 | buf[10] = WLAN_EID_EXT_SUPP_RATES; | |
62 | for (; i < 10; i++) { | |
63 | /* NULL terminated */ | |
64 | if (rates[i] == 0x0) | |
65 | break; | |
66 | buf[i + 2] = rates[i]; | |
fb791b1c | 67 | } |
c63cdbe8 | 68 | buf[11] = i - 8; |
fb791b1c | 69 | } |
c63cdbe8 DK |
70 | |
71 | return (i < 8) ? i + 2 : i + 4; | |
fb791b1c DK |
72 | } |
73 | ||
c63cdbe8 DK |
74 | static void orinoco_add_hostscan_result(struct orinoco_private *priv, |
75 | const union hermes_scan_info *bss) | |
fb791b1c | 76 | { |
c63cdbe8 DK |
77 | struct wiphy *wiphy = priv_to_wiphy(priv); |
78 | struct ieee80211_channel *channel; | |
9236b2a8 | 79 | struct cfg80211_bss *cbss; |
c63cdbe8 DK |
80 | u8 *ie; |
81 | u8 ie_buf[46]; | |
82 | u64 timestamp; | |
83 | s32 signal; | |
84 | u16 capability; | |
85 | u16 beacon_interval; | |
86 | int ie_len; | |
87 | int freq; | |
88 | int len; | |
89 | ||
90 | len = le16_to_cpu(bss->a.essid_len); | |
91 | ||
92 | /* Reconstruct SSID and bitrate IEs to pass up */ | |
93 | ie_buf[0] = WLAN_EID_SSID; | |
94 | ie_buf[1] = len; | |
95 | memcpy(&ie_buf[2], bss->a.essid, len); | |
96 | ||
97 | ie = ie_buf + len + 2; | |
98 | ie_len = ie_buf[1] + 2; | |
99 | switch (priv->firmware_type) { | |
100 | case FIRMWARE_TYPE_SYMBOL: | |
101 | ie_len += symbol_build_supp_rates(ie, bss->s.rates); | |
fb791b1c | 102 | break; |
fb791b1c | 103 | |
c63cdbe8 DK |
104 | case FIRMWARE_TYPE_INTERSIL: |
105 | ie_len += prism_build_supp_rates(ie, bss->p.rates); | |
106 | break; | |
fb791b1c | 107 | |
c63cdbe8 DK |
108 | case FIRMWARE_TYPE_AGERE: |
109 | default: | |
110 | break; | |
fb791b1c DK |
111 | } |
112 | ||
13c1ac57 | 113 | freq = ieee80211_channel_to_frequency( |
57fbcce3 | 114 | le16_to_cpu(bss->a.channel), NL80211_BAND_2GHZ); |
c63cdbe8 | 115 | channel = ieee80211_get_channel(wiphy, freq); |
46c2cb8c JG |
116 | if (!channel) { |
117 | printk(KERN_DEBUG "Invalid channel designation %04X(%04X)", | |
118 | bss->a.channel, freq); | |
119 | return; /* Then ignore it for now */ | |
120 | } | |
c63cdbe8 DK |
121 | timestamp = 0; |
122 | capability = le16_to_cpu(bss->a.capabilities); | |
123 | beacon_interval = le16_to_cpu(bss->a.beacon_interv); | |
124 | signal = SIGNAL_TO_MBM(le16_to_cpu(bss->a.level)); | |
125 | ||
5bc8c1f2 JB |
126 | cbss = cfg80211_inform_bss(wiphy, channel, CFG80211_BSS_FTYPE_UNKNOWN, |
127 | bss->a.bssid, timestamp, capability, | |
128 | beacon_interval, ie_buf, ie_len, signal, | |
129 | GFP_KERNEL); | |
5b112d3d | 130 | cfg80211_put_bss(wiphy, cbss); |
fb791b1c DK |
131 | } |
132 | ||
c63cdbe8 DK |
133 | void orinoco_add_extscan_result(struct orinoco_private *priv, |
134 | struct agere_ext_scan_info *bss, | |
135 | size_t len) | |
fb791b1c | 136 | { |
c63cdbe8 DK |
137 | struct wiphy *wiphy = priv_to_wiphy(priv); |
138 | struct ieee80211_channel *channel; | |
9236b2a8 | 139 | struct cfg80211_bss *cbss; |
69c264de | 140 | const u8 *ie; |
c63cdbe8 DK |
141 | u64 timestamp; |
142 | s32 signal; | |
143 | u16 capability; | |
144 | u16 beacon_interval; | |
145 | size_t ie_len; | |
146 | int chan, freq; | |
147 | ||
148 | ie_len = len - sizeof(*bss); | |
69c264de | 149 | ie = cfg80211_find_ie(WLAN_EID_DS_PARAMS, bss->data, ie_len); |
c63cdbe8 | 150 | chan = ie ? ie[2] : 0; |
57fbcce3 | 151 | freq = ieee80211_channel_to_frequency(chan, NL80211_BAND_2GHZ); |
c63cdbe8 DK |
152 | channel = ieee80211_get_channel(wiphy, freq); |
153 | ||
154 | timestamp = le64_to_cpu(bss->timestamp); | |
155 | capability = le16_to_cpu(bss->capabilities); | |
156 | beacon_interval = le16_to_cpu(bss->beacon_interval); | |
157 | ie = bss->data; | |
158 | signal = SIGNAL_TO_MBM(bss->level); | |
159 | ||
5bc8c1f2 JB |
160 | cbss = cfg80211_inform_bss(wiphy, channel, CFG80211_BSS_FTYPE_UNKNOWN, |
161 | bss->bssid, timestamp, capability, | |
162 | beacon_interval, ie, ie_len, signal, | |
163 | GFP_KERNEL); | |
5b112d3d | 164 | cfg80211_put_bss(wiphy, cbss); |
c63cdbe8 DK |
165 | } |
166 | ||
167 | void orinoco_add_hostscan_results(struct orinoco_private *priv, | |
168 | unsigned char *buf, | |
169 | size_t len) | |
170 | { | |
171 | int offset; /* In the scan data */ | |
172 | size_t atom_len; | |
173 | bool abort = false; | |
fb791b1c DK |
174 | |
175 | switch (priv->firmware_type) { | |
176 | case FIRMWARE_TYPE_AGERE: | |
177 | atom_len = sizeof(struct agere_scan_apinfo); | |
178 | offset = 0; | |
179 | break; | |
c63cdbe8 | 180 | |
fb791b1c DK |
181 | case FIRMWARE_TYPE_SYMBOL: |
182 | /* Lack of documentation necessitates this hack. | |
183 | * Different firmwares have 68 or 76 byte long atoms. | |
184 | * We try modulo first. If the length divides by both, | |
185 | * we check what would be the channel in the second | |
186 | * frame for a 68-byte atom. 76-byte atoms have 0 there. | |
187 | * Valid channel cannot be 0. */ | |
188 | if (len % 76) | |
189 | atom_len = 68; | |
190 | else if (len % 68) | |
191 | atom_len = 76; | |
192 | else if (len >= 1292 && buf[68] == 0) | |
193 | atom_len = 76; | |
194 | else | |
195 | atom_len = 68; | |
196 | offset = 0; | |
197 | break; | |
c63cdbe8 | 198 | |
fb791b1c DK |
199 | case FIRMWARE_TYPE_INTERSIL: |
200 | offset = 4; | |
201 | if (priv->has_hostscan) { | |
202 | atom_len = le16_to_cpup((__le16 *)buf); | |
203 | /* Sanity check for atom_len */ | |
204 | if (atom_len < sizeof(struct prism2_scan_apinfo)) { | |
205 | printk(KERN_ERR "%s: Invalid atom_len in scan " | |
4244f41a | 206 | "data: %zu\n", priv->ndev->name, |
fb791b1c | 207 | atom_len); |
c63cdbe8 DK |
208 | abort = true; |
209 | goto scan_abort; | |
fb791b1c DK |
210 | } |
211 | } else | |
212 | atom_len = offsetof(struct prism2_scan_apinfo, atim); | |
213 | break; | |
c63cdbe8 | 214 | |
fb791b1c | 215 | default: |
c63cdbe8 DK |
216 | abort = true; |
217 | goto scan_abort; | |
fb791b1c DK |
218 | } |
219 | ||
220 | /* Check that we got an whole number of atoms */ | |
221 | if ((len - offset) % atom_len) { | |
4244f41a DK |
222 | printk(KERN_ERR "%s: Unexpected scan data length %zu, " |
223 | "atom_len %zu, offset %d\n", priv->ndev->name, len, | |
fb791b1c | 224 | atom_len, offset); |
c63cdbe8 DK |
225 | abort = true; |
226 | goto scan_abort; | |
fb791b1c DK |
227 | } |
228 | ||
c63cdbe8 | 229 | /* Process the entries one by one */ |
fb791b1c | 230 | for (; offset + atom_len <= len; offset += atom_len) { |
c63cdbe8 | 231 | union hermes_scan_info *atom; |
fb791b1c | 232 | |
fb791b1c DK |
233 | atom = (union hermes_scan_info *) (buf + offset); |
234 | ||
c63cdbe8 | 235 | orinoco_add_hostscan_result(priv, atom); |
fb791b1c DK |
236 | } |
237 | ||
c63cdbe8 DK |
238 | scan_abort: |
239 | if (priv->scan_request) { | |
240 | cfg80211_scan_done(priv->scan_request, abort); | |
241 | priv->scan_request = NULL; | |
242 | } | |
fb791b1c | 243 | } |
cf63495d DK |
244 | |
245 | void orinoco_scan_done(struct orinoco_private *priv, bool abort) | |
246 | { | |
247 | if (priv->scan_request) { | |
248 | cfg80211_scan_done(priv->scan_request, abort); | |
249 | priv->scan_request = NULL; | |
250 | } | |
251 | } |