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