5d2e74d4127842d67889330af5616efc3beb2449
[openwrt.git] / package / broadcom-wl / src / wlcompat / wlcompat.c
1 /*
2  * wlcompat.c
3  *
4  * Copyright (C) 2005      Mike Baker
5  * Copyright (C) 2005-2007 Felix Fietkau <nbd@openwrt.org>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  *
21  * $Id$
22  */
23
24
25 #include <linux/config.h>
26 #include <linux/module.h>
27 #include <linux/moduleparam.h>
28 #include <linux/init.h>
29 #include <linux/if_arp.h>
30 #include <linux/wireless.h>
31 #include <linux/timer.h>
32 #include <linux/delay.h>
33 #include <linux/random.h>
34 #include <net/iw_handler.h>
35 #include <asm/uaccess.h>
36
37 #include <typedefs.h>
38 #include <bcmutils.h>
39 #include <wlioctl.h>
40
41 char buf[WLC_IOCTL_MAXLEN];
42 static struct net_device *dev;
43 #ifndef DEBUG
44 static int random = 1;
45 #endif
46 #ifndef WL_WEXT
47 static struct iw_statistics wstats;
48 static int last_mode = -1;
49 static int scan_cur = 0;
50
51 /* The frequency of each channel in MHz */
52 const long channel_frequency[] = {
53         2412, 2417, 2422, 2427, 2432, 2437, 2442,
54         2447, 2452, 2457, 2462, 2467, 2472, 2484
55 };
56 #define NUM_CHANNELS ( sizeof(channel_frequency) / sizeof(channel_frequency[0]) )
57 #endif
58
59 #define SCAN_RETRY_MAX  5
60 #define RNG_POLL_FREQ   1
61
62 typedef struct internal_wsec_key {
63         uint8 index;            // 0x00
64         uint8 unknown_1;        // 0x01
65         uint8 type;             // 0x02
66         uint8 unknown_2[7];     // 0x03
67         uint8 len;              // 0x0a
68         uint8 pad[3];
69         char data[32];          // 0x0e
70 } wkey;
71
72
73 #ifdef DEBUG
74 void print_buffer(int len, unsigned char *buf);
75 #endif
76
77 static int wl_ioctl(struct net_device *dev, int cmd, void *buf, int len)
78 {
79         mm_segment_t old_fs = get_fs();
80         struct ifreq ifr;
81         int ret;
82         wl_ioctl_t ioc;
83         ioc.cmd = cmd;
84         ioc.buf = buf;
85         ioc.len = len;
86         strncpy(ifr.ifr_name, dev->name, IFNAMSIZ);
87         ifr.ifr_data = (caddr_t) &ioc;
88         set_fs(KERNEL_DS);
89         ret = dev->do_ioctl(dev,&ifr,SIOCDEVPRIVATE);
90         set_fs (old_fs);
91         return ret;
92 }
93
94 #if !defined(DEBUG) || !defined(WL_WEXT)
95 static int
96 wl_iovar_getbuf(struct net_device *dev, char *iovar, void *param, int paramlen, void *bufptr, int buflen)
97 {
98         int err;
99         uint namelen;
100         uint iolen;
101
102         namelen = strlen(iovar) + 1;     /* length of iovar name plus null */
103         iolen = namelen + paramlen;
104
105         /* check for overflow */
106         if (iolen > buflen)
107                 return (BCME_BUFTOOSHORT);
108
109         memcpy(bufptr, iovar, namelen); /* copy iovar name including null */
110         memcpy((int8*)bufptr + namelen, param, paramlen);
111
112         err = wl_ioctl(dev, WLC_GET_VAR, bufptr, buflen);
113
114         return (err);
115 }
116
117 static int
118 wl_iovar_setbuf(struct net_device *dev, char *iovar, void *param, int paramlen, void *bufptr, int buflen)
119 {
120         uint namelen;
121         uint iolen;
122
123         namelen = strlen(iovar) + 1;     /* length of iovar name plus null */
124         iolen = namelen + paramlen;
125
126         /* check for overflow */
127         if (iolen > buflen)
128                 return (BCME_BUFTOOSHORT);
129
130         memcpy(bufptr, iovar, namelen); /* copy iovar name including null */
131         memcpy((int8*)bufptr + namelen, param, paramlen);
132
133         return wl_ioctl(dev, WLC_SET_VAR, bufptr, iolen);
134 }
135
136 static int
137 wl_iovar_set(struct net_device *dev, char *iovar, void *param, int paramlen)
138 {
139         char smbuf[WLC_IOCTL_SMLEN];
140
141         return wl_iovar_setbuf(dev, iovar, param, paramlen, smbuf, sizeof(smbuf));
142 }
143
144 static int
145 wl_iovar_get(struct net_device *dev, char *iovar, void *bufptr, int buflen)
146 {
147         char smbuf[WLC_IOCTL_SMLEN];
148         int ret;
149
150         /* use the return buffer if it is bigger than what we have on the stack */
151         if (buflen > sizeof(smbuf)) {
152                 ret = wl_iovar_getbuf(dev, iovar, NULL, 0, bufptr, buflen);
153         } else {
154                 ret = wl_iovar_getbuf(dev, iovar, NULL, 0, smbuf, sizeof(smbuf));
155                 if (ret == 0)
156                         memcpy(bufptr, smbuf, buflen);
157         }
158
159         return ret;
160 }
161
162 #ifdef notyet
163 /*
164  * format a bsscfg indexed iovar buffer
165  */
166 static int
167 wl_bssiovar_mkbuf(char *iovar, int bssidx, void *param, int paramlen, void *bufptr, int buflen,
168                   int *plen)
169 {
170         char *prefix = "bsscfg:";
171         int8* p;
172         uint prefixlen;
173         uint namelen;
174         uint iolen;
175
176         prefixlen = strlen(prefix);     /* length of bsscfg prefix */
177         namelen = strlen(iovar) + 1;    /* length of iovar name + null */
178         iolen = prefixlen + namelen + sizeof(int) + paramlen;
179
180         /* check for overflow */
181         if (buflen < 0 || iolen > (uint)buflen) {
182                 *plen = 0;
183                 return BCME_BUFTOOSHORT;
184         }
185
186         p = (int8*)bufptr;
187
188         /* copy prefix, no null */
189         memcpy(p, prefix, prefixlen);
190         p += prefixlen;
191
192         /* copy iovar name including null */
193         memcpy(p, iovar, namelen);
194         p += namelen;
195
196         /* bss config index as first param */
197         memcpy(p, &bssidx, sizeof(int32));
198         p += sizeof(int32);
199
200         /* parameter buffer follows */
201         if (paramlen)
202                 memcpy(p, param, paramlen);
203
204         *plen = iolen;
205         return 0;
206 }
207
208 /*
209  * set named & bss indexed driver variable to buffer value
210  */
211 static int
212 wl_bssiovar_setbuf(struct net_device *dev, char *iovar, int bssidx, void *param, int paramlen, void *bufptr,
213                    int buflen)
214 {
215         int err;
216         int iolen;
217
218         err = wl_bssiovar_mkbuf(iovar, bssidx, param, paramlen, bufptr, buflen, &iolen);
219         if (err)
220                 return err;
221
222         return wl_ioctl(dev, WLC_SET_VAR, bufptr, iolen);
223 }
224
225 /*
226  * get named & bss indexed driver variable buffer value
227  */
228 static int
229 wl_bssiovar_getbuf(struct net_device *dev, char *iovar, int bssidx, void *param, int paramlen, void *bufptr,
230                    int buflen)
231 {
232         int err;
233         int iolen;
234
235         err = wl_bssiovar_mkbuf(iovar, bssidx, param, paramlen, bufptr, buflen, &iolen);
236         if (err)
237                 return err;
238
239         return wl_ioctl(dev, WLC_GET_VAR, bufptr, buflen);
240 }
241
242 /*
243  * set named & bss indexed driver variable to buffer value
244  */
245 static int
246 wl_bssiovar_set(struct net_device *dev, char *iovar, int bssidx, void *param, int paramlen)
247 {
248         char smbuf[WLC_IOCTL_SMLEN];
249
250         return wl_bssiovar_setbuf(dev, iovar, bssidx, param, paramlen, smbuf, sizeof(smbuf));
251 }
252
253 /*
254  * get named & bss indexed driver variable buffer value
255  */
256 static int
257 wl_bssiovar_get(struct net_device *dev, char *iovar, int bssidx, void *outbuf, int len)
258 {
259         char smbuf[WLC_IOCTL_SMLEN];
260         int err;
261
262         /* use the return buffer if it is bigger than what we have on the stack */
263         if (len > (int)sizeof(smbuf)) {
264                 err = wl_bssiovar_getbuf(dev, iovar, bssidx, NULL, 0, outbuf, len);
265         } else {
266                 memset(smbuf, 0, sizeof(smbuf));
267                 err = wl_bssiovar_getbuf(dev, iovar, bssidx, NULL, 0, smbuf, sizeof(smbuf));
268                 if (err == 0)
269                         memcpy(outbuf, smbuf, len);
270         }
271
272         return err;
273 }
274 #endif
275 #endif
276
277 #ifndef WL_WEXT
278
279 int get_primary_key(struct net_device *dev)
280 {
281         int key, val;
282         
283         for (key = val = 0; (key < 4) && (val == 0); key++) {
284                 val = key;
285                 if (wl_ioctl(dev, WLC_GET_KEY_PRIMARY, &val, sizeof(val)) < 0)
286                         return -EINVAL;
287         }
288         return key;
289 }
290
291
292 static int wlcompat_ioctl_getiwrange(struct net_device *dev,
293                                     char *extra)
294 {
295         int i, k;
296         struct iw_range *range;
297
298         range = (struct iw_range *) extra;
299         memset(extra, 0, sizeof(struct iw_range));
300
301         range->we_version_compiled = WIRELESS_EXT;
302         range->we_version_source = WIRELESS_EXT;
303         
304         range->min_nwid = range->max_nwid = 0;
305         
306         range->num_channels = NUM_CHANNELS;
307         k = 0;
308         for (i = 0; i < NUM_CHANNELS; i++) {
309                 range->freq[k].i = i + 1;
310                 range->freq[k].m = channel_frequency[i] * 100000;
311                 range->freq[k].e = 1;
312                 k++;
313                 if (k >= IW_MAX_FREQUENCIES)
314                         break;
315         }
316         range->num_frequency = k;
317         range->sensitivity = 3;
318
319         /* nbd: don't know what this means, but other drivers set it this way */
320         range->pmp_flags = IW_POWER_PERIOD;
321         range->pmt_flags = IW_POWER_TIMEOUT;
322         range->pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT | IW_POWER_UNICAST_R;
323
324         range->min_pmp = 0;
325         range->max_pmp = 65535000;
326         range->min_pmt = 0;
327         range->max_pmt = 65535 * 1000;
328
329         range->max_qual.qual = 0;
330         range->max_qual.level = 0;
331         range->max_qual.noise = 0;
332         
333         range->min_rts = 0;
334         if (wl_iovar_get(dev, "rtsthresh", &range->max_rts, sizeof(int)) < 0)
335                 range->max_rts = 2347;
336
337         range->min_frag = 256;
338         
339         if (wl_iovar_get(dev, "fragthresh", &range->max_frag, sizeof(int)) < 0)
340                 range->max_frag = 2346;
341
342         range->txpower_capa = IW_TXPOW_DBM;
343
344         return 0;
345 }
346
347
348 static int wlcompat_set_scan(struct net_device *dev,
349                          struct iw_request_info *info,
350                          union iwreq_data *wrqu,
351                          char *extra)
352 {
353         int ap = 0;
354         wl_scan_params_t params;
355
356         memset(&params, 0, sizeof(params));
357
358         /* use defaults (same parameters as wl scan) */
359         memset(&params.bssid, 0xff, sizeof(params.bssid));
360         params.bss_type = DOT11_BSSTYPE_ANY;
361         params.scan_type = -1;
362         params.nprobes = -1;
363         params.active_time = -1;
364         params.passive_time = -1;
365         params.home_time = -1;
366         
367         /* can only scan in STA mode */
368         wl_ioctl(dev, WLC_GET_AP, &last_mode, sizeof(last_mode));
369         if (last_mode > 0) {
370                 /* switch to ap mode, scan result query will switch back */
371                 wl_ioctl(dev, WLC_SET_AP, &ap, sizeof(ap));
372
373                 /* wait 250 msec after mode change */
374                 set_current_state(TASK_INTERRUPTIBLE);
375                 schedule_timeout(msecs_to_jiffies(250));
376         }
377
378         scan_cur = SCAN_RETRY_MAX;
379         while (scan_cur-- && (wl_ioctl(dev, WLC_SCAN, &params, 64) < 0)) {
380                 /* sometimes the driver takes a few tries... */
381                 set_current_state(TASK_INTERRUPTIBLE);
382                 schedule_timeout(msecs_to_jiffies(250));
383         }
384
385         if (!scan_cur) 
386                 return -EINVAL;
387         
388         scan_cur = 0;
389
390         /* wait at least 2 seconds for results */
391         set_current_state(TASK_INTERRUPTIBLE);
392         schedule_timeout(msecs_to_jiffies(2000));
393         
394         return 0;
395 }
396
397
398 struct iw_statistics *wlcompat_get_wireless_stats(struct net_device *dev)
399 {
400         struct wl_bss_info *bss_info = (struct wl_bss_info *) buf;
401         get_pktcnt_t pkt;
402         unsigned int rssi, noise, ap;
403         
404         memset(&wstats, 0, sizeof(wstats));
405         memset(&pkt, 0, sizeof(pkt));
406         memset(buf, 0, sizeof(buf));
407         bss_info->version = 0x2000;
408         wl_ioctl(dev, WLC_GET_BSS_INFO, bss_info, WLC_IOCTL_MAXLEN);
409         wl_ioctl(dev, WLC_GET_PKTCNTS, &pkt, sizeof(pkt));
410
411         rssi = 0;
412         if ((wl_ioctl(dev, WLC_GET_AP, &ap, sizeof(ap)) < 0) || ap) {
413                 if (wl_ioctl(dev, WLC_GET_PHY_NOISE, &noise, sizeof(noise)) < 0)
414                         noise = 0;
415         } else {
416                 // somehow the structure doesn't fit here
417                 rssi = buf[82];
418                 noise = buf[84];
419         }
420         rssi = (rssi == 0 ? 1 : rssi);
421         wstats.qual.updated = 0x10;
422         if (rssi <= 1) 
423                 wstats.qual.updated |= 0x20;
424         if (noise <= 1)
425                 wstats.qual.updated |= 0x40;
426
427         if ((wstats.qual.updated & 0x60) == 0x60)
428                 return NULL;
429
430         wstats.qual.level = rssi;
431         wstats.qual.noise = noise;
432         wstats.discard.misc = pkt.rx_bad_pkt;
433         wstats.discard.retries = pkt.tx_bad_pkt;
434
435         return &wstats;
436 }
437
438 static int wlcompat_get_scan(struct net_device *dev,
439                          struct iw_request_info *info,
440                          union iwreq_data *wrqu,
441                          char *extra)
442 {
443         wl_scan_results_t *results = (wl_scan_results_t *) buf;
444         wl_bss_info_t *bss_info;
445         char *info_ptr;
446         char *current_ev = extra;
447         char *current_val;
448         char *end_buf = extra + IW_SCAN_MAX_DATA;
449         struct iw_event iwe;
450         int i, j;
451         int rssi, noise;
452         
453         memset(buf, 0, WLC_IOCTL_MAXLEN);
454         results->buflen = WLC_IOCTL_MAXLEN - sizeof(wl_scan_results_t);
455         
456         if (wl_ioctl(dev, WLC_SCAN_RESULTS, buf, WLC_IOCTL_MAXLEN) < 0)
457                 return -EAGAIN;
458         
459         if ((results->count <= 0) && (scan_cur++ < SCAN_RETRY_MAX))
460                 return -EAGAIN;
461         
462         bss_info = &(results->bss_info[0]);
463         info_ptr = (char *) bss_info;
464         for (i = 0; i < results->count; i++) {
465                 /* send the cell address (must be sent first) */
466                 iwe.cmd = SIOCGIWAP;
467                 iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
468                 memcpy(&iwe.u.ap_addr.sa_data, &bss_info->BSSID, sizeof(bss_info->BSSID));
469                 current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, IW_EV_ADDR_LEN);
470                 
471                 /* send the ESSID */
472                 iwe.cmd = SIOCGIWESSID;
473                 iwe.u.data.length = bss_info->SSID_len;
474                 if (iwe.u.data.length > IW_ESSID_MAX_SIZE)
475                         iwe.u.data.length = IW_ESSID_MAX_SIZE;
476                 iwe.u.data.flags = 1;
477                 current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, bss_info->SSID);
478
479                 /* send mode */
480                 if  (bss_info->capability & (DOT11_CAP_ESS | DOT11_CAP_IBSS)) {
481                         iwe.cmd = SIOCGIWMODE;
482                         if (bss_info->capability & DOT11_CAP_ESS)
483                                 iwe.u.mode = IW_MODE_MASTER;
484                         else if (bss_info->capability & DOT11_CAP_IBSS)
485                                 iwe.u.mode = IW_MODE_ADHOC;
486                         current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, IW_EV_UINT_LEN);
487                 }
488
489                 /* send frequency/channel info */
490                 iwe.cmd = SIOCGIWFREQ;
491                 iwe.u.freq.e = 0;
492                 iwe.u.freq.m = bss_info->chanspec & WL_CHANSPEC_CHAN_MASK;
493                 current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, IW_EV_FREQ_LEN);
494
495                 /* add quality statistics */
496                 iwe.cmd = IWEVQUAL;
497                 iwe.u.qual.qual = 0;
498                 iwe.u.qual.level = bss_info->RSSI;
499                 iwe.u.qual.noise = bss_info->phy_noise;
500                 current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, IW_EV_QUAL_LEN);
501         
502                 /* send encryption capability */
503                 iwe.cmd = SIOCGIWENCODE;
504                 iwe.u.data.pointer = NULL;
505                 iwe.u.data.length = 0;
506                 if (bss_info->capability & DOT11_CAP_PRIVACY)
507                         iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
508                 else
509                         iwe.u.data.flags = IW_ENCODE_DISABLED;
510                 current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, NULL);
511
512                 /* send rate information */
513                 iwe.cmd = SIOCGIWRATE;
514                 current_val = current_ev + IW_EV_LCP_LEN;
515                 iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;
516                 
517                 for(j = 0 ; j < bss_info->rateset.count ; j++) {
518                         iwe.u.bitrate.value = ((bss_info->rateset.rates[j] & 0x7f) * 500000);
519                         current_val = iwe_stream_add_value(current_ev, current_val, end_buf, &iwe, IW_EV_PARAM_LEN);
520                 }
521                 if((current_val - current_ev) > IW_EV_LCP_LEN)
522                         current_ev = current_val;
523
524                 info_ptr += sizeof(wl_bss_info_t);
525                 if (bss_info->ie_length % 4)
526                         info_ptr += bss_info->ie_length + 4 - (bss_info->ie_length % 4);
527                 else
528                         info_ptr += bss_info->ie_length;
529                 bss_info = (wl_bss_info_t *) info_ptr;
530         }
531         
532         wrqu->data.length = (current_ev - extra);
533         wrqu->data.flags = 0;
534
535         if (last_mode > 0)
536                 /* switch back to ap mode */
537                 wl_ioctl(dev, WLC_SET_AP, &last_mode, sizeof(last_mode));
538         
539         return 0;
540 }
541
542 static int wlcompat_ioctl(struct net_device *dev,
543                          struct iw_request_info *info,
544                          union iwreq_data *wrqu,
545                          char *extra)
546 {
547         switch (info->cmd) {
548                 case SIOCGIWNAME:
549                         strcpy(wrqu->name, "IEEE 802.11-DS");
550                         break;
551                 case SIOCGIWFREQ:
552                 {
553                         channel_info_t ci;
554
555                         if (wl_ioctl(dev,WLC_GET_CHANNEL, &ci, sizeof(ci)) < 0)
556                                 return -EINVAL;
557
558                         wrqu->freq.m = ci.target_channel;
559                         wrqu->freq.e = 0;
560                         break;
561                 }
562                 case SIOCSIWFREQ:
563                 {
564                         if (wrqu->freq.m == -1) {
565                                 wrqu->freq.m = 0;
566                                 if (wl_ioctl(dev, WLC_SET_CHANNEL, &wrqu->freq.m, sizeof(int)) < 0)
567                                         return -EINVAL;
568                         } else {
569                                 if (wrqu->freq.e == 1) {
570                                         int channel = 0;
571                                         int f = wrqu->freq.m / 100000;
572                                         while ((channel < NUM_CHANNELS + 1) && (f != channel_frequency[channel]))
573                                                 channel++;
574                                         
575                                         if (channel == NUM_CHANNELS) // channel not found
576                                                 return -EINVAL;
577
578                                         wrqu->freq.e = 0;
579                                         wrqu->freq.m = channel + 1;
580                                 }
581                                 if ((wrqu->freq.e == 0) && (wrqu->freq.m < 1000)) {
582                                         if (wl_ioctl(dev, WLC_SET_CHANNEL, &wrqu->freq.m, sizeof(int)) < 0)
583                                                 return -EINVAL;
584                                 } else {
585                                         return -EINVAL;
586                                 }
587                         }
588                         break;
589                 }
590                 case SIOCSIWAP:
591                 {
592                         int ap = 0;
593                         int infra = 0;
594                         rw_reg_t reg;
595
596                         memset(&reg, 0, sizeof(reg));
597
598                         if (wrqu->ap_addr.sa_family != ARPHRD_ETHER)
599                                 return -EINVAL;
600
601                         if (wl_ioctl(dev, WLC_GET_AP, &ap, sizeof(ap)) < 0)
602                                 return -EINVAL;
603
604                         if (wl_ioctl(dev, WLC_GET_INFRA, &infra, sizeof(infra)) < 0)
605                                 return -EINVAL;
606
607                         if (!infra) 
608                                 wl_ioctl(dev, WLC_SET_BSSID, wrqu->ap_addr.sa_data, 6);
609
610                         if (wl_ioctl(dev, ((ap || !infra) ? WLC_SET_BSSID : WLC_REASSOC), wrqu->ap_addr.sa_data, 6) < 0)
611                                 return -EINVAL;
612
613                         break;
614                 }
615                 case SIOCGIWAP:
616                 {
617                         wrqu->ap_addr.sa_family = ARPHRD_ETHER;
618                         if (wl_ioctl(dev,WLC_GET_BSSID,wrqu->ap_addr.sa_data,6) < 0)
619                                 return -EINVAL;
620                         break;
621                 }
622                 case SIOCGIWESSID:
623                 {
624                         wlc_ssid_t ssid;
625                         
626                         if (wl_ioctl(dev,WLC_GET_SSID, &ssid, sizeof(wlc_ssid_t)) < 0)
627                                 return -EINVAL;
628
629                         wrqu->essid.flags = wrqu->data.flags = 1;
630                         wrqu->essid.length = wrqu->data.length = ssid.SSID_len + 1;
631                         memcpy(extra,ssid.SSID,ssid.SSID_len + 1);
632                         break;
633                 }
634                 case SIOCSIWESSID:
635                 {
636                         wlc_ssid_t ssid;
637                         memset(&ssid, 0, sizeof(ssid));
638                         ssid.SSID_len = strlen(extra);
639                         if (ssid.SSID_len > 32)
640                                 ssid.SSID_len = 32;
641                         memcpy(ssid.SSID, extra, ssid.SSID_len);
642                         if (wl_ioctl(dev, WLC_SET_SSID, &ssid, sizeof(ssid)) < 0)
643                                 return -EINVAL;
644                         break;
645                 }
646                 case SIOCGIWRTS:
647                 {
648                         if (wl_iovar_get(dev, "rtsthresh", &(wrqu->rts.value), sizeof(int)) < 0) 
649                                 return -EINVAL;
650                         break;
651                 }
652                 case SIOCSIWRTS:
653                 {
654                         if (wl_iovar_set(dev, "rtsthresh", &(wrqu->rts.value), sizeof(int)) < 0) 
655                                 return -EINVAL;
656                         break;
657                 }
658                 case SIOCGIWFRAG:
659                 {
660                         if (wl_iovar_get(dev, "fragthresh", &(wrqu->frag.value), sizeof(int)) < 0)
661                                 return -EINVAL;
662                         break;
663                 }
664                 case SIOCSIWFRAG:
665                 {
666                         if (wl_iovar_set(dev, "fragthresh", &(wrqu->frag.value), sizeof(int)) < 0)
667                                 return -EINVAL;
668                         break;
669                 }
670                 case SIOCGIWTXPOW:
671                 {
672                         int radio, override;
673
674                         wl_ioctl(dev, WLC_GET_RADIO, &radio, sizeof(int));
675                         
676                         if (wl_iovar_get(dev, "qtxpower", &(wrqu->txpower.value), sizeof(int)) < 0)
677                                 return -EINVAL;
678                         
679                         override = (wrqu->txpower.value & WL_TXPWR_OVERRIDE) == WL_TXPWR_OVERRIDE;
680                         wrqu->txpower.value &= ~WL_TXPWR_OVERRIDE;
681                         if (!override && (wrqu->txpower.value > 76))
682                                 wrqu->txpower.value = 76;
683                         wrqu->txpower.value /= 4;
684                                 
685                         wrqu->txpower.fixed = 0;
686                         wrqu->txpower.disabled = radio;
687                         wrqu->txpower.flags = IW_TXPOW_DBM;
688                         break;
689                 }
690                 case SIOCSIWTXPOW:
691                 {
692                         /* This is weird: WLC_SET_RADIO with 1 as argument disables the radio */
693                         int radio = wrqu->txpower.disabled;
694
695                         wl_ioctl(dev, WLC_SET_RADIO, &radio, sizeof(int));
696                         
697                         if (!wrqu->txpower.disabled && (wrqu->txpower.value > 0)) {
698                                 int value;
699                                 
700                                 if (wl_iovar_get(dev, "qtxpower", &value, sizeof(int)) < 0)
701                                         return -EINVAL;
702                                 
703                                 value &= WL_TXPWR_OVERRIDE;
704                                 wrqu->txpower.value *= 4;
705                                 wrqu->txpower.value |= value;
706                                 
707                                 if (wrqu->txpower.flags != IW_TXPOW_DBM)
708                                         return -EINVAL;
709                                 
710                                 if (wrqu->txpower.value > 0)
711                                         if (wl_iovar_set(dev, "qtxpower", &(wrqu->txpower.value), sizeof(int)) < 0)
712                                                 return -EINVAL;
713                         }
714                         break;
715                 }
716                 case SIOCSIWENCODE:
717                 {
718                         int val = 0, wep = 1, wrestrict = 1;
719                         int index = (wrqu->data.flags & IW_ENCODE_INDEX) - 1;
720
721                         if (index < 0)
722                                 index = get_primary_key(dev);
723                         
724                         if (wrqu->data.flags & IW_ENCODE_DISABLED) {
725                                 wep = 0;
726                                 if (wl_ioctl(dev, WLC_SET_WSEC, &wep, sizeof(val)) < 0)
727                                         return -EINVAL;
728                                 return 0;
729                         }
730
731                         if (wl_ioctl(dev, WLC_SET_WSEC, &wep, sizeof(val)) < 0)
732                                 return -EINVAL;
733
734                         if (wrqu->data.flags & IW_ENCODE_OPEN)
735                                 wrestrict = 0;
736                         
737                         if (wrqu->data.pointer && (wrqu->data.length > 0) && (wrqu->data.length <= 16)) {
738                                 wl_wsec_key_t key;
739                                 memset(&key, 0, sizeof(key));
740
741                                 key.flags = WL_PRIMARY_KEY;
742                                 key.len = wrqu->data.length;
743                                 key.index = index;
744                                 memcpy(key.data, wrqu->data.pointer, wrqu->data.length);
745
746                                 if (wl_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)) < 0)
747                                         return -EINVAL;
748                         }
749
750                         if (index >= 0)
751                                 wl_ioctl(dev, WLC_SET_KEY_PRIMARY, &index, sizeof(index));
752                         
753                         if (wrestrict >= 0)
754                                 wl_ioctl(dev, WLC_SET_WEP_RESTRICT, &wrestrict, sizeof(wrestrict));
755
756                         break;
757                 }
758                 case SIOCGIWENCODE:
759                 {
760                         int val;
761                         int key = get_primary_key(dev);
762                         int *info_addr; 
763                         wkey *wep_key;
764                         
765                         if (wl_ioctl(dev, WLC_GET_WSEC, &val, sizeof(val)) < 0)
766                                 return -EINVAL;
767                         if (!(val & WEP_ENABLED)) {
768                                 wrqu->data.flags = IW_ENCODE_DISABLED;
769                                 break;
770                         }
771
772                         key = get_primary_key(dev);
773                         wrqu->data.flags = IW_ENCODE_ENABLED;
774
775                         /* the driver apparently doesn't allow us to read the wep key */
776                         wrqu->data.flags |= IW_ENCODE_NOKEY;
777
778                         break;
779                 }
780                 case SIOCGIWRANGE:
781                 {
782                         return wlcompat_ioctl_getiwrange(dev, extra);
783                         break;
784                 }
785                 case SIOCSIWMODE:
786                 {
787                         int ap = -1, infra = -1, passive = 0, wet = 0;
788
789                         wl_ioctl(dev, WLC_GET_WET, &wet, sizeof(wet));
790                         switch (wrqu->mode) {
791                                 case IW_MODE_MONITOR:
792                                         passive = 1;
793                                         break;
794                                 case IW_MODE_ADHOC:
795                                         infra = 0;
796                                         ap = 0;
797                                         break;
798                                 case IW_MODE_MASTER:
799                                         infra = 1;
800                                         ap = 1;
801                                         break;
802                                 case IW_MODE_INFRA:
803                                         infra = 1;
804                                         ap = 0;
805                                         wet = 0;
806                                         break;
807                                 case IW_MODE_REPEAT:
808                                         infra = 1;
809                                         ap = 0;
810                                         wet = 1;
811                                         break;
812                                 default:
813                                         return -EINVAL;
814                         }
815
816                         wl_ioctl(dev, WLC_SET_PASSIVE, &passive, sizeof(passive));
817                         wl_ioctl(dev, WLC_SET_MONITOR, &passive, sizeof(passive));
818                         if ((ap == 0) && (infra == 1))
819                                 wl_ioctl(dev, WLC_SET_WET, &wet, sizeof(wet));
820                         if (ap >= 0)
821                                 wl_ioctl(dev, WLC_SET_AP, &ap, sizeof(ap));
822                         if (infra >= 0)
823                                 wl_ioctl(dev, WLC_SET_INFRA, &infra, sizeof(infra));
824
825                         break;
826                 }
827                 case SIOCGIWMODE:
828                 {
829                         int ap, infra, wet, passive;
830
831                         if (wl_ioctl(dev, WLC_GET_AP, &ap, sizeof(ap)) < 0)
832                                 return -EINVAL;
833                         if (wl_ioctl(dev, WLC_GET_INFRA, &infra, sizeof(infra)) < 0)
834                                 return -EINVAL;
835                         if (wl_ioctl(dev, WLC_GET_PASSIVE, &passive, sizeof(passive)) < 0)
836                                 return -EINVAL;
837
838                         if (passive) {
839                                 wrqu->mode = IW_MODE_MONITOR;
840                         } else if (!infra) {
841                                 wrqu->mode = IW_MODE_ADHOC;
842                         } else {
843                                 if (ap) {
844                                         wrqu->mode = IW_MODE_MASTER;
845                                 } else {
846                                         wrqu->mode = IW_MODE_INFRA;
847                                 }
848                         }
849                         break;
850                 }
851                 default:
852                         return -EINVAL;
853                         break;
854         }
855         
856         return 0;
857 }
858
859 static const iw_handler  wlcompat_handler[] = {
860         NULL,                   /* SIOCSIWCOMMIT */
861         wlcompat_ioctl,         /* SIOCGIWNAME */
862         NULL,                   /* SIOCSIWNWID */
863         NULL,                   /* SIOCGIWNWID */
864         wlcompat_ioctl,         /* SIOCSIWFREQ */
865         wlcompat_ioctl,         /* SIOCGIWFREQ */
866         wlcompat_ioctl,         /* SIOCSIWMODE */
867         wlcompat_ioctl,         /* SIOCGIWMODE */
868         NULL,                   /* SIOCSIWSENS */
869         NULL,                   /* SIOCGIWSENS */
870         NULL,                   /* SIOCSIWRANGE, unused */
871         wlcompat_ioctl,         /* SIOCGIWRANGE */
872         NULL,                   /* SIOCSIWPRIV */
873         NULL,                   /* SIOCGIWPRIV */
874         NULL,                   /* SIOCSIWSTATS */
875         NULL,                   /* SIOCGIWSTATS */
876         iw_handler_set_spy,     /* SIOCSIWSPY */
877         iw_handler_get_spy,     /* SIOCGIWSPY */
878         iw_handler_set_thrspy,  /* SIOCSIWTHRSPY */
879         iw_handler_get_thrspy,  /* SIOCGIWTHRSPY */
880         wlcompat_ioctl,         /* SIOCSIWAP */
881         wlcompat_ioctl,         /* SIOCGIWAP */
882         NULL,                   /* -- hole -- */
883         NULL,                   /* SIOCGIWAPLIST */
884         wlcompat_set_scan,      /* SIOCSIWSCAN */
885         wlcompat_get_scan,      /* SIOCGIWSCAN */
886         wlcompat_ioctl,         /* SIOCSIWESSID */
887         wlcompat_ioctl,         /* SIOCGIWESSID */
888         NULL,                   /* SIOCSIWNICKN */
889         NULL,                   /* SIOCGIWNICKN */
890         NULL,                   /* -- hole -- */
891         NULL,                   /* -- hole -- */
892         NULL,                   /* SIOCSIWRATE */
893         NULL,                   /* SIOCGIWRATE */
894         wlcompat_ioctl,         /* SIOCSIWRTS */
895         wlcompat_ioctl,         /* SIOCGIWRTS */
896         wlcompat_ioctl,         /* SIOCSIWFRAG */
897         wlcompat_ioctl,         /* SIOCGIWFRAG */
898         wlcompat_ioctl,         /* SIOCSIWTXPOW */
899         wlcompat_ioctl,         /* SIOCGIWTXPOW */
900         NULL,                   /* SIOCSIWRETRY */
901         NULL,                   /* SIOCGIWRETRY */
902         wlcompat_ioctl,         /* SIOCSIWENCODE */
903         wlcompat_ioctl,         /* SIOCGIWENCODE */
904 };
905
906
907 static const struct iw_handler_def wlcompat_handler_def =
908 {
909         .standard       = (iw_handler *) wlcompat_handler,
910         .num_standard   = sizeof(wlcompat_handler)/sizeof(iw_handler),
911 };
912
913 #endif
914
915 #ifdef DEBUG
916 void print_buffer(int len, unsigned char *buf) {
917         int x;
918         if (buf != NULL) {
919                 for (x=0;x<len && x<180 ;x++) {
920                         if ((x % 4) == 0)
921                                 printk(" ");
922                         printk("%02X",buf[x]);
923                 }
924         } else {
925                 printk(" NULL");
926         }
927         printk("\n");
928
929 }
930 #endif
931 static int (*old_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
932 static int new_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) {
933         int ret = 0;
934         
935 #ifdef DEBUG
936         printk("dev: %s ioctl: 0x%04x\n",dev->name,cmd);
937         if (cmd==SIOCDEVPRIVATE) {
938                 wl_ioctl_t *ioc = (wl_ioctl_t *)ifr->ifr_data;
939                 unsigned char *buf = ioc->buf;
940                 printk("   cmd: %d buf: 0x%08x len: %d\n",ioc->cmd,&(ioc->buf),ioc->len);
941                 printk("   send: ->");
942                 print_buffer(ioc->len, buf);
943                 ret = old_ioctl(dev,ifr,cmd);
944                 printk("   recv: ->");
945                 print_buffer(ioc->len, buf);
946                 printk("   ret: %d\n", ret);
947         } else
948 #endif
949         {
950                 ret = old_ioctl(dev,ifr,cmd);
951         }
952         return ret;
953 }
954
955 #ifndef DEBUG
956 static struct timer_list rng_timer;
957 static spinlock_t rng_lock = SPIN_LOCK_UNLOCKED;
958
959 static void rng_timer_tick(unsigned long n)
960 {
961         struct net_device *dev = (struct net_device *) n;
962         unsigned long flags;
963         u16 data[4];
964         int i, ret;
965         
966         ret = 0;
967         spin_lock_irqsave(&rng_lock, flags);
968         for (i = 0; i < 3; i++) {
969                 ret |= wl_iovar_get(dev, "rand", &data[i], sizeof(u16));
970         }
971         spin_unlock_irqrestore(&rng_lock, flags);
972
973         if (!ret)
974                 batch_entropy_store(*((u32 *) &data[0]), *((u32 *) &data[2]), (jiffies % 255));
975
976         mod_timer(&rng_timer, jiffies + (HZ/RNG_POLL_FREQ));
977 }
978 #endif
979
980 static int __init wlcompat_init()
981 {
982         int found = 0, i;
983         char devname[4] = "wl0";
984
985         while (!found && (dev = dev_get_by_name(devname))) {
986                 if ((wl_ioctl(dev, WLC_GET_MAGIC, &i, sizeof(i)) == 0) && (i == WLC_IOCTL_MAGIC))
987                         found = 1;
988                 devname[2]++;
989         }
990
991
992         if (!found) {
993                 printk("No Broadcom devices found.\n");
994                 return -ENODEV;
995         }
996
997         old_ioctl = dev->do_ioctl;
998         dev->do_ioctl = new_ioctl;
999 #ifndef WL_WEXT 
1000         dev->wireless_handlers = (struct iw_handler_def *)&wlcompat_handler_def;
1001         dev->get_wireless_stats = wlcompat_get_wireless_stats;
1002 #endif
1003
1004 #ifndef DEBUG
1005         if (random) {
1006                 init_timer(&rng_timer);
1007                 rng_timer.function = rng_timer_tick;
1008                 rng_timer.data = (unsigned long) dev;
1009                 rng_timer_tick((unsigned long) dev);
1010         }
1011 #endif
1012         
1013 #ifdef DEBUG
1014         printk("broadcom driver private data: 0x%08x\n", dev->priv);
1015 #endif
1016         return 0;
1017 }
1018
1019 static void __exit wlcompat_exit()
1020 {
1021 #ifndef DEBUG
1022         if (random)
1023                 del_timer(&rng_timer);
1024 #endif
1025 #ifndef WL_WEXT 
1026         dev->get_wireless_stats = NULL;
1027         dev->wireless_handlers = NULL;
1028 #endif
1029         dev->do_ioctl = old_ioctl;
1030         return;
1031 }
1032
1033 EXPORT_NO_SYMBOLS;
1034 MODULE_AUTHOR("openwrt.org");
1035 MODULE_LICENSE("GPL");
1036
1037 #ifndef DEBUG
1038 module_param(random, int, 0);
1039 #endif
1040 module_init(wlcompat_init);
1041 module_exit(wlcompat_exit);