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