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