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