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 | ||
c63cdbe8 DK |
113 | freq = ieee80211_dsss_chan_to_freq(le16_to_cpu(bss->a.channel)); |
114 | channel = ieee80211_get_channel(wiphy, freq); | |
46c2cb8c JG |
115 | if (!channel) { |
116 | printk(KERN_DEBUG "Invalid channel designation %04X(%04X)", | |
117 | bss->a.channel, freq); | |
118 | return; /* Then ignore it for now */ | |
119 | } | |
c63cdbe8 DK |
120 | timestamp = 0; |
121 | capability = le16_to_cpu(bss->a.capabilities); | |
122 | beacon_interval = le16_to_cpu(bss->a.beacon_interv); | |
123 | signal = SIGNAL_TO_MBM(le16_to_cpu(bss->a.level)); | |
124 | ||
9236b2a8 DK |
125 | cbss = cfg80211_inform_bss(wiphy, channel, bss->a.bssid, timestamp, |
126 | capability, beacon_interval, ie_buf, ie_len, | |
127 | signal, GFP_KERNEL); | |
5b112d3d | 128 | cfg80211_put_bss(wiphy, cbss); |
fb791b1c DK |
129 | } |
130 | ||
c63cdbe8 DK |
131 | void orinoco_add_extscan_result(struct orinoco_private *priv, |
132 | struct agere_ext_scan_info *bss, | |
133 | size_t len) | |
fb791b1c | 134 | { |
c63cdbe8 DK |
135 | struct wiphy *wiphy = priv_to_wiphy(priv); |
136 | struct ieee80211_channel *channel; | |
9236b2a8 | 137 | struct cfg80211_bss *cbss; |
69c264de | 138 | const u8 *ie; |
c63cdbe8 DK |
139 | u64 timestamp; |
140 | s32 signal; | |
141 | u16 capability; | |
142 | u16 beacon_interval; | |
143 | size_t ie_len; | |
144 | int chan, freq; | |
145 | ||
146 | ie_len = len - sizeof(*bss); | |
69c264de | 147 | ie = cfg80211_find_ie(WLAN_EID_DS_PARAMS, bss->data, ie_len); |
c63cdbe8 DK |
148 | chan = ie ? ie[2] : 0; |
149 | freq = ieee80211_dsss_chan_to_freq(chan); | |
150 | channel = ieee80211_get_channel(wiphy, freq); | |
151 | ||
152 | timestamp = le64_to_cpu(bss->timestamp); | |
153 | capability = le16_to_cpu(bss->capabilities); | |
154 | beacon_interval = le16_to_cpu(bss->beacon_interval); | |
155 | ie = bss->data; | |
156 | signal = SIGNAL_TO_MBM(bss->level); | |
157 | ||
9236b2a8 DK |
158 | cbss = cfg80211_inform_bss(wiphy, channel, bss->bssid, timestamp, |
159 | capability, beacon_interval, ie, ie_len, | |
160 | signal, GFP_KERNEL); | |
5b112d3d | 161 | cfg80211_put_bss(wiphy, cbss); |
c63cdbe8 DK |
162 | } |
163 | ||
164 | void orinoco_add_hostscan_results(struct orinoco_private *priv, | |
165 | unsigned char *buf, | |
166 | size_t len) | |
167 | { | |
168 | int offset; /* In the scan data */ | |
169 | size_t atom_len; | |
170 | bool abort = false; | |
fb791b1c DK |
171 | |
172 | switch (priv->firmware_type) { | |
173 | case FIRMWARE_TYPE_AGERE: | |
174 | atom_len = sizeof(struct agere_scan_apinfo); | |
175 | offset = 0; | |
176 | break; | |
c63cdbe8 | 177 | |
fb791b1c DK |
178 | case FIRMWARE_TYPE_SYMBOL: |
179 | /* Lack of documentation necessitates this hack. | |
180 | * Different firmwares have 68 or 76 byte long atoms. | |
181 | * We try modulo first. If the length divides by both, | |
182 | * we check what would be the channel in the second | |
183 | * frame for a 68-byte atom. 76-byte atoms have 0 there. | |
184 | * Valid channel cannot be 0. */ | |
185 | if (len % 76) | |
186 | atom_len = 68; | |
187 | else if (len % 68) | |
188 | atom_len = 76; | |
189 | else if (len >= 1292 && buf[68] == 0) | |
190 | atom_len = 76; | |
191 | else | |
192 | atom_len = 68; | |
193 | offset = 0; | |
194 | break; | |
c63cdbe8 | 195 | |
fb791b1c DK |
196 | case FIRMWARE_TYPE_INTERSIL: |
197 | offset = 4; | |
198 | if (priv->has_hostscan) { | |
199 | atom_len = le16_to_cpup((__le16 *)buf); | |
200 | /* Sanity check for atom_len */ | |
201 | if (atom_len < sizeof(struct prism2_scan_apinfo)) { | |
202 | printk(KERN_ERR "%s: Invalid atom_len in scan " | |
4244f41a | 203 | "data: %zu\n", priv->ndev->name, |
fb791b1c | 204 | atom_len); |
c63cdbe8 DK |
205 | abort = true; |
206 | goto scan_abort; | |
fb791b1c DK |
207 | } |
208 | } else | |
209 | atom_len = offsetof(struct prism2_scan_apinfo, atim); | |
210 | break; | |
c63cdbe8 | 211 | |
fb791b1c | 212 | default: |
c63cdbe8 DK |
213 | abort = true; |
214 | goto scan_abort; | |
fb791b1c DK |
215 | } |
216 | ||
217 | /* Check that we got an whole number of atoms */ | |
218 | if ((len - offset) % atom_len) { | |
4244f41a DK |
219 | printk(KERN_ERR "%s: Unexpected scan data length %zu, " |
220 | "atom_len %zu, offset %d\n", priv->ndev->name, len, | |
fb791b1c | 221 | atom_len, offset); |
c63cdbe8 DK |
222 | abort = true; |
223 | goto scan_abort; | |
fb791b1c DK |
224 | } |
225 | ||
c63cdbe8 | 226 | /* Process the entries one by one */ |
fb791b1c | 227 | for (; offset + atom_len <= len; offset += atom_len) { |
c63cdbe8 | 228 | union hermes_scan_info *atom; |
fb791b1c | 229 | |
fb791b1c DK |
230 | atom = (union hermes_scan_info *) (buf + offset); |
231 | ||
c63cdbe8 | 232 | orinoco_add_hostscan_result(priv, atom); |
fb791b1c DK |
233 | } |
234 | ||
c63cdbe8 DK |
235 | scan_abort: |
236 | if (priv->scan_request) { | |
237 | cfg80211_scan_done(priv->scan_request, abort); | |
238 | priv->scan_request = NULL; | |
239 | } | |
fb791b1c | 240 | } |
cf63495d DK |
241 | |
242 | void orinoco_scan_done(struct orinoco_private *priv, bool abort) | |
243 | { | |
244 | if (priv->scan_request) { | |
245 | cfg80211_scan_done(priv->scan_request, abort); | |
246 | priv->scan_request = NULL; | |
247 | } | |
248 | } |