Merge git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[wrt350n-kernel.git] / drivers / net / wireless / libertas / 11d.c
blob5e10ce0d351c9e8467a83a1fb48725dec69f1c9b
1 /**
2 * This file contains functions for 802.11D.
3 */
4 #include <linux/ctype.h>
5 #include <linux/kernel.h>
6 #include <linux/wireless.h>
8 #include "host.h"
9 #include "decl.h"
10 #include "11d.h"
11 #include "dev.h"
12 #include "wext.h"
14 #define TX_PWR_DEFAULT 10
16 static struct region_code_mapping region_code_mapping[] = {
17 {"US ", 0x10}, /* US FCC */
18 {"CA ", 0x10}, /* IC Canada */
19 {"SG ", 0x10}, /* Singapore */
20 {"EU ", 0x30}, /* ETSI */
21 {"AU ", 0x30}, /* Australia */
22 {"KR ", 0x30}, /* Republic Of Korea */
23 {"ES ", 0x31}, /* Spain */
24 {"FR ", 0x32}, /* France */
25 {"JP ", 0x40}, /* Japan */
28 /* Following 2 structure defines the supported channels */
29 static struct chan_freq_power channel_freq_power_UN_BG[] = {
30 {1, 2412, TX_PWR_DEFAULT},
31 {2, 2417, TX_PWR_DEFAULT},
32 {3, 2422, TX_PWR_DEFAULT},
33 {4, 2427, TX_PWR_DEFAULT},
34 {5, 2432, TX_PWR_DEFAULT},
35 {6, 2437, TX_PWR_DEFAULT},
36 {7, 2442, TX_PWR_DEFAULT},
37 {8, 2447, TX_PWR_DEFAULT},
38 {9, 2452, TX_PWR_DEFAULT},
39 {10, 2457, TX_PWR_DEFAULT},
40 {11, 2462, TX_PWR_DEFAULT},
41 {12, 2467, TX_PWR_DEFAULT},
42 {13, 2472, TX_PWR_DEFAULT},
43 {14, 2484, TX_PWR_DEFAULT}
46 static u8 lbs_region_2_code(u8 *region)
48 u8 i;
50 for (i = 0; region[i] && i < COUNTRY_CODE_LEN; i++)
51 region[i] = toupper(region[i]);
53 for (i = 0; i < ARRAY_SIZE(region_code_mapping); i++) {
54 if (!memcmp(region, region_code_mapping[i].region,
55 COUNTRY_CODE_LEN))
56 return (region_code_mapping[i].code);
59 /* default is US */
60 return (region_code_mapping[0].code);
63 static u8 *lbs_code_2_region(u8 code)
65 u8 i;
67 for (i = 0; i < ARRAY_SIZE(region_code_mapping); i++) {
68 if (region_code_mapping[i].code == code)
69 return (region_code_mapping[i].region);
71 /* default is US */
72 return (region_code_mapping[0].region);
75 /**
76 * @brief This function finds the nrchan-th chan after the firstchan
77 * @param band band
78 * @param firstchan first channel number
79 * @param nrchan number of channels
80 * @return the nrchan-th chan number
82 static u8 lbs_get_chan_11d(u8 band, u8 firstchan, u8 nrchan, u8 *chan)
83 /*find the nrchan-th chan after the firstchan*/
85 u8 i;
86 struct chan_freq_power *cfp;
87 u8 cfp_no;
89 cfp = channel_freq_power_UN_BG;
90 cfp_no = ARRAY_SIZE(channel_freq_power_UN_BG);
92 for (i = 0; i < cfp_no; i++) {
93 if ((cfp + i)->channel == firstchan) {
94 lbs_deb_11d("firstchan found\n");
95 break;
99 if (i < cfp_no) {
100 /*if beyond the boundary */
101 if (i + nrchan < cfp_no) {
102 *chan = (cfp + i + nrchan)->channel;
103 return 1;
107 return 0;
111 * @brief This function Checks if chan txpwr is learned from AP/IBSS
112 * @param chan chan number
113 * @param parsed_region_chan pointer to parsed_region_chan_11d
114 * @return TRUE; FALSE
116 static u8 lbs_channel_known_11d(u8 chan,
117 struct parsed_region_chan_11d * parsed_region_chan)
119 struct chan_power_11d *chanpwr = parsed_region_chan->chanpwr;
120 u8 nr_chan = parsed_region_chan->nr_chan;
121 u8 i = 0;
123 lbs_deb_hex(LBS_DEB_11D, "parsed_region_chan", (char *)chanpwr,
124 sizeof(struct chan_power_11d) * nr_chan);
126 for (i = 0; i < nr_chan; i++) {
127 if (chan == chanpwr[i].chan) {
128 lbs_deb_11d("found chan %d\n", chan);
129 return 1;
133 lbs_deb_11d("chan %d not found\n", chan);
134 return 0;
137 u32 lbs_chan_2_freq(u8 chan, u8 band)
139 struct chan_freq_power *cf;
140 u16 i;
141 u32 freq = 0;
143 cf = channel_freq_power_UN_BG;
145 for (i = 0; i < ARRAY_SIZE(channel_freq_power_UN_BG); i++) {
146 if (chan == cf[i].channel)
147 freq = cf[i].freq;
150 return freq;
153 static int generate_domain_info_11d(struct parsed_region_chan_11d
154 *parsed_region_chan,
155 struct lbs_802_11d_domain_reg *domaininfo)
157 u8 nr_subband = 0;
159 u8 nr_chan = parsed_region_chan->nr_chan;
160 u8 nr_parsedchan = 0;
162 u8 firstchan = 0, nextchan = 0, maxpwr = 0;
164 u8 i, flag = 0;
166 memcpy(domaininfo->countrycode, parsed_region_chan->countrycode,
167 COUNTRY_CODE_LEN);
169 lbs_deb_11d("nrchan %d\n", nr_chan);
170 lbs_deb_hex(LBS_DEB_11D, "parsed_region_chan", (char *)parsed_region_chan,
171 sizeof(struct parsed_region_chan_11d));
173 for (i = 0; i < nr_chan; i++) {
174 if (!flag) {
175 flag = 1;
176 nextchan = firstchan =
177 parsed_region_chan->chanpwr[i].chan;
178 maxpwr = parsed_region_chan->chanpwr[i].pwr;
179 nr_parsedchan = 1;
180 continue;
183 if (parsed_region_chan->chanpwr[i].chan == nextchan + 1 &&
184 parsed_region_chan->chanpwr[i].pwr == maxpwr) {
185 nextchan++;
186 nr_parsedchan++;
187 } else {
188 domaininfo->subband[nr_subband].firstchan = firstchan;
189 domaininfo->subband[nr_subband].nrchan =
190 nr_parsedchan;
191 domaininfo->subband[nr_subband].maxtxpwr = maxpwr;
192 nr_subband++;
193 nextchan = firstchan =
194 parsed_region_chan->chanpwr[i].chan;
195 maxpwr = parsed_region_chan->chanpwr[i].pwr;
199 if (flag) {
200 domaininfo->subband[nr_subband].firstchan = firstchan;
201 domaininfo->subband[nr_subband].nrchan = nr_parsedchan;
202 domaininfo->subband[nr_subband].maxtxpwr = maxpwr;
203 nr_subband++;
205 domaininfo->nr_subband = nr_subband;
207 lbs_deb_11d("nr_subband=%x\n", domaininfo->nr_subband);
208 lbs_deb_hex(LBS_DEB_11D, "domaininfo", (char *)domaininfo,
209 COUNTRY_CODE_LEN + 1 +
210 sizeof(struct ieeetypes_subbandset) * nr_subband);
211 return 0;
215 * @brief This function generates parsed_region_chan from Domain Info learned from AP/IBSS
216 * @param region_chan pointer to struct region_channel
217 * @param *parsed_region_chan pointer to parsed_region_chan_11d
218 * @return N/A
220 static void lbs_generate_parsed_region_chan_11d(struct region_channel *region_chan,
221 struct parsed_region_chan_11d *
222 parsed_region_chan)
224 u8 i;
225 struct chan_freq_power *cfp;
227 if (region_chan == NULL) {
228 lbs_deb_11d("region_chan is NULL\n");
229 return;
232 cfp = region_chan->CFP;
233 if (cfp == NULL) {
234 lbs_deb_11d("cfp is NULL \n");
235 return;
238 parsed_region_chan->band = region_chan->band;
239 parsed_region_chan->region = region_chan->region;
240 memcpy(parsed_region_chan->countrycode,
241 lbs_code_2_region(region_chan->region), COUNTRY_CODE_LEN);
243 lbs_deb_11d("region 0x%x, band %d\n", parsed_region_chan->region,
244 parsed_region_chan->band);
246 for (i = 0; i < region_chan->nrcfp; i++, cfp++) {
247 parsed_region_chan->chanpwr[i].chan = cfp->channel;
248 parsed_region_chan->chanpwr[i].pwr = cfp->maxtxpower;
249 lbs_deb_11d("chan %d, pwr %d\n",
250 parsed_region_chan->chanpwr[i].chan,
251 parsed_region_chan->chanpwr[i].pwr);
253 parsed_region_chan->nr_chan = region_chan->nrcfp;
255 lbs_deb_11d("nrchan %d\n", parsed_region_chan->nr_chan);
257 return;
261 * @brief generate parsed_region_chan from Domain Info learned from AP/IBSS
262 * @param region region ID
263 * @param band band
264 * @param chan chan
265 * @return TRUE;FALSE
267 static u8 lbs_region_chan_supported_11d(u8 region, u8 band, u8 chan)
269 struct chan_freq_power *cfp;
270 int cfp_no;
271 u8 idx;
272 int ret = 0;
274 lbs_deb_enter(LBS_DEB_11D);
276 cfp = lbs_get_region_cfp_table(region, band, &cfp_no);
277 if (cfp == NULL)
278 return 0;
280 for (idx = 0; idx < cfp_no; idx++) {
281 if (chan == (cfp + idx)->channel) {
282 /* If Mrvl Chip Supported? */
283 if ((cfp + idx)->unsupported) {
284 ret = 0;
285 } else {
286 ret = 1;
288 goto done;
292 /*chan is not in the region table */
294 done:
295 lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret);
296 return ret;
300 * @brief This function checks if chan txpwr is learned from AP/IBSS
301 * @param chan chan number
302 * @param parsed_region_chan pointer to parsed_region_chan_11d
303 * @return 0
305 static int parse_domain_info_11d(struct ieeetypes_countryinfofullset*
306 countryinfo,
307 u8 band,
308 struct parsed_region_chan_11d *
309 parsed_region_chan)
311 u8 nr_subband, nrchan;
312 u8 lastchan, firstchan;
313 u8 region;
314 u8 curchan = 0;
316 u8 idx = 0; /*chan index in parsed_region_chan */
318 u8 j, i;
320 lbs_deb_enter(LBS_DEB_11D);
322 /*validation Rules:
323 1. valid region Code
324 2. First Chan increment
325 3. channel range no overlap
326 4. channel is valid?
327 5. channel is supported by region?
328 6. Others
331 lbs_deb_hex(LBS_DEB_11D, "countryinfo", (u8 *) countryinfo, 30);
333 if ((*(countryinfo->countrycode)) == 0
334 || (countryinfo->len <= COUNTRY_CODE_LEN)) {
335 /* No region Info or Wrong region info: treat as No 11D info */
336 goto done;
339 /*Step1: check region_code */
340 parsed_region_chan->region = region =
341 lbs_region_2_code(countryinfo->countrycode);
343 lbs_deb_11d("regioncode=%x\n", (u8) parsed_region_chan->region);
344 lbs_deb_hex(LBS_DEB_11D, "countrycode", (char *)countryinfo->countrycode,
345 COUNTRY_CODE_LEN);
347 parsed_region_chan->band = band;
349 memcpy(parsed_region_chan->countrycode, countryinfo->countrycode,
350 COUNTRY_CODE_LEN);
352 nr_subband = (countryinfo->len - COUNTRY_CODE_LEN) /
353 sizeof(struct ieeetypes_subbandset);
355 for (j = 0, lastchan = 0; j < nr_subband; j++) {
357 if (countryinfo->subband[j].firstchan <= lastchan) {
358 /*Step2&3. Check First Chan Num increment and no overlap */
359 lbs_deb_11d("chan %d>%d, overlap\n",
360 countryinfo->subband[j].firstchan, lastchan);
361 continue;
364 firstchan = countryinfo->subband[j].firstchan;
365 nrchan = countryinfo->subband[j].nrchan;
367 for (i = 0; idx < MAX_NO_OF_CHAN && i < nrchan; i++) {
368 /*step4: channel is supported? */
370 if (!lbs_get_chan_11d(band, firstchan, i, &curchan)) {
371 /* Chan is not found in UN table */
372 lbs_deb_11d("chan is not supported: %d \n", i);
373 break;
376 lastchan = curchan;
378 if (lbs_region_chan_supported_11d
379 (region, band, curchan)) {
380 /*step5: Check if curchan is supported by mrvl in region */
381 parsed_region_chan->chanpwr[idx].chan = curchan;
382 parsed_region_chan->chanpwr[idx].pwr =
383 countryinfo->subband[j].maxtxpwr;
384 idx++;
385 } else {
386 /*not supported and ignore the chan */
387 lbs_deb_11d(
388 "i %d, chan %d unsupported in region %x, band %d\n",
389 i, curchan, region, band);
393 /*Step6: Add other checking if any */
397 parsed_region_chan->nr_chan = idx;
399 lbs_deb_11d("nrchan=%x\n", parsed_region_chan->nr_chan);
400 lbs_deb_hex(LBS_DEB_11D, "parsed_region_chan", (u8 *) parsed_region_chan,
401 2 + COUNTRY_CODE_LEN + sizeof(struct parsed_region_chan_11d) * idx);
403 done:
404 lbs_deb_enter(LBS_DEB_11D);
405 return 0;
409 * @brief This function calculates the scan type for channels
410 * @param chan chan number
411 * @param parsed_region_chan pointer to parsed_region_chan_11d
412 * @return PASSIVE if chan is unknown; ACTIVE if chan is known
414 u8 lbs_get_scan_type_11d(u8 chan,
415 struct parsed_region_chan_11d * parsed_region_chan)
417 u8 scan_type = CMD_SCAN_TYPE_PASSIVE;
419 lbs_deb_enter(LBS_DEB_11D);
421 if (lbs_channel_known_11d(chan, parsed_region_chan)) {
422 lbs_deb_11d("found, do active scan\n");
423 scan_type = CMD_SCAN_TYPE_ACTIVE;
424 } else {
425 lbs_deb_11d("not found, do passive scan\n");
428 lbs_deb_leave_args(LBS_DEB_11D, "ret scan_type %d", scan_type);
429 return scan_type;
433 void lbs_init_11d(struct lbs_private *priv)
435 priv->enable11d = 0;
436 memset(&(priv->parsed_region_chan), 0,
437 sizeof(struct parsed_region_chan_11d));
438 return;
442 * @brief This function sets DOMAIN INFO to FW
443 * @param priv pointer to struct lbs_private
444 * @return 0; -1
446 static int set_domain_info_11d(struct lbs_private *priv)
448 int ret;
450 if (!priv->enable11d) {
451 lbs_deb_11d("dnld domain Info with 11d disabled\n");
452 return 0;
455 ret = lbs_prepare_and_send_command(priv, CMD_802_11D_DOMAIN_INFO,
456 CMD_ACT_SET,
457 CMD_OPTION_WAITFORRSP, 0, NULL);
458 if (ret)
459 lbs_deb_11d("fail to dnld domain info\n");
461 return ret;
465 * @brief This function setups scan channels
466 * @param priv pointer to struct lbs_private
467 * @param band band
468 * @return 0
470 int lbs_set_universaltable(struct lbs_private *priv, u8 band)
472 u16 size = sizeof(struct chan_freq_power);
473 u16 i = 0;
475 memset(priv->universal_channel, 0,
476 sizeof(priv->universal_channel));
478 priv->universal_channel[i].nrcfp =
479 sizeof(channel_freq_power_UN_BG) / size;
480 lbs_deb_11d("BG-band nrcfp %d\n",
481 priv->universal_channel[i].nrcfp);
483 priv->universal_channel[i].CFP = channel_freq_power_UN_BG;
484 priv->universal_channel[i].valid = 1;
485 priv->universal_channel[i].region = UNIVERSAL_REGION_CODE;
486 priv->universal_channel[i].band = band;
487 i++;
489 return 0;
493 * @brief This function implements command CMD_802_11D_DOMAIN_INFO
494 * @param priv pointer to struct lbs_private
495 * @param cmd pointer to cmd buffer
496 * @param cmdno cmd ID
497 * @param cmdOption cmd action
498 * @return 0
500 int lbs_cmd_802_11d_domain_info(struct lbs_private *priv,
501 struct cmd_ds_command *cmd, u16 cmdno,
502 u16 cmdoption)
504 struct cmd_ds_802_11d_domain_info *pdomaininfo =
505 &cmd->params.domaininfo;
506 struct mrvlietypes_domainparamset *domain = &pdomaininfo->domain;
507 u8 nr_subband = priv->domainreg.nr_subband;
509 lbs_deb_enter(LBS_DEB_11D);
511 lbs_deb_11d("nr_subband=%x\n", nr_subband);
513 cmd->command = cpu_to_le16(cmdno);
514 pdomaininfo->action = cpu_to_le16(cmdoption);
515 if (cmdoption == CMD_ACT_GET) {
516 cmd->size =
517 cpu_to_le16(sizeof(pdomaininfo->action) + S_DS_GEN);
518 lbs_deb_hex(LBS_DEB_11D, "802_11D_DOMAIN_INFO", (u8 *) cmd,
519 le16_to_cpu(cmd->size));
520 goto done;
523 domain->header.type = cpu_to_le16(TLV_TYPE_DOMAIN);
524 memcpy(domain->countrycode, priv->domainreg.countrycode,
525 sizeof(domain->countrycode));
527 domain->header.len =
528 cpu_to_le16(nr_subband * sizeof(struct ieeetypes_subbandset) +
529 sizeof(domain->countrycode));
531 if (nr_subband) {
532 memcpy(domain->subband, priv->domainreg.subband,
533 nr_subband * sizeof(struct ieeetypes_subbandset));
535 cmd->size = cpu_to_le16(sizeof(pdomaininfo->action) +
536 le16_to_cpu(domain->header.len) +
537 sizeof(struct mrvlietypesheader) +
538 S_DS_GEN);
539 } else {
540 cmd->size =
541 cpu_to_le16(sizeof(pdomaininfo->action) + S_DS_GEN);
544 lbs_deb_hex(LBS_DEB_11D, "802_11D_DOMAIN_INFO", (u8 *) cmd, le16_to_cpu(cmd->size));
546 done:
547 lbs_deb_enter(LBS_DEB_11D);
548 return 0;
552 * @brief This function parses countryinfo from AP and download country info to FW
553 * @param priv pointer to struct lbs_private
554 * @param resp pointer to command response buffer
555 * @return 0; -1
557 int lbs_ret_802_11d_domain_info(struct lbs_private *priv,
558 struct cmd_ds_command *resp)
560 struct cmd_ds_802_11d_domain_info *domaininfo = &resp->params.domaininforesp;
561 struct mrvlietypes_domainparamset *domain = &domaininfo->domain;
562 u16 action = le16_to_cpu(domaininfo->action);
563 s16 ret = 0;
564 u8 nr_subband = 0;
566 lbs_deb_enter(LBS_DEB_11D);
568 lbs_deb_hex(LBS_DEB_11D, "domain info resp", (u8 *) resp,
569 (int)le16_to_cpu(resp->size));
571 nr_subband = (le16_to_cpu(domain->header.len) - COUNTRY_CODE_LEN) /
572 sizeof(struct ieeetypes_subbandset);
574 lbs_deb_11d("domain info resp: nr_subband %d\n", nr_subband);
576 if (nr_subband > MRVDRV_MAX_SUBBAND_802_11D) {
577 lbs_deb_11d("Invalid Numrer of Subband returned!!\n");
578 return -1;
581 switch (action) {
582 case CMD_ACT_SET: /*Proc Set action */
583 break;
585 case CMD_ACT_GET:
586 break;
587 default:
588 lbs_deb_11d("Invalid action:%d\n", domaininfo->action);
589 ret = -1;
590 break;
593 lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret);
594 return ret;
598 * @brief This function parses countryinfo from AP and download country info to FW
599 * @param priv pointer to struct lbs_private
600 * @return 0; -1
602 int lbs_parse_dnld_countryinfo_11d(struct lbs_private *priv,
603 struct bss_descriptor * bss)
605 int ret;
607 lbs_deb_enter(LBS_DEB_11D);
608 if (priv->enable11d) {
609 memset(&priv->parsed_region_chan, 0,
610 sizeof(struct parsed_region_chan_11d));
611 ret = parse_domain_info_11d(&bss->countryinfo, 0,
612 &priv->parsed_region_chan);
614 if (ret == -1) {
615 lbs_deb_11d("error parsing domain_info from AP\n");
616 goto done;
619 memset(&priv->domainreg, 0,
620 sizeof(struct lbs_802_11d_domain_reg));
621 generate_domain_info_11d(&priv->parsed_region_chan,
622 &priv->domainreg);
624 ret = set_domain_info_11d(priv);
626 if (ret) {
627 lbs_deb_11d("error setting domain info\n");
628 goto done;
631 ret = 0;
633 done:
634 lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret);
635 return ret;
639 * @brief This function generates 11D info from user specified regioncode and download to FW
640 * @param priv pointer to struct lbs_private
641 * @return 0; -1
643 int lbs_create_dnld_countryinfo_11d(struct lbs_private *priv)
645 int ret;
646 struct region_channel *region_chan;
647 u8 j;
649 lbs_deb_enter(LBS_DEB_11D);
650 lbs_deb_11d("curbssparams.band %d\n", priv->curbssparams.band);
652 if (priv->enable11d) {
653 /* update parsed_region_chan_11; dnld domaininf to FW */
655 for (j = 0; j < ARRAY_SIZE(priv->region_channel); j++) {
656 region_chan = &priv->region_channel[j];
658 lbs_deb_11d("%d region_chan->band %d\n", j,
659 region_chan->band);
661 if (!region_chan || !region_chan->valid
662 || !region_chan->CFP)
663 continue;
664 if (region_chan->band != priv->curbssparams.band)
665 continue;
666 break;
669 if (j >= ARRAY_SIZE(priv->region_channel)) {
670 lbs_deb_11d("region_chan not found, band %d\n",
671 priv->curbssparams.band);
672 ret = -1;
673 goto done;
676 memset(&priv->parsed_region_chan, 0,
677 sizeof(struct parsed_region_chan_11d));
678 lbs_generate_parsed_region_chan_11d(region_chan,
679 &priv->
680 parsed_region_chan);
682 memset(&priv->domainreg, 0,
683 sizeof(struct lbs_802_11d_domain_reg));
684 generate_domain_info_11d(&priv->parsed_region_chan,
685 &priv->domainreg);
687 ret = set_domain_info_11d(priv);
689 if (ret) {
690 lbs_deb_11d("error setting domain info\n");
691 goto done;
695 ret = 0;
697 done:
698 lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret);
699 return ret;