diff options
Diffstat (limited to 'package')
38 files changed, 19020 insertions, 0 deletions
diff --git a/package/libertas/Makefile b/package/libertas/Makefile new file mode 100644 index 0000000000..bf8cbbc69e --- /dev/null +++ b/package/libertas/Makefile @@ -0,0 +1,56 @@ +# +# Copyright (C) 2007 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# +# $Id: Makefile 8694 2007-09-08 19:55:42Z nbd $ + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=kmod-libertas +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/libertas + SUBMENU:=Other modules + DEPENDS:=@TARGET_olpc +kmod-ieee80211 + TITLE:=Marvell 88W8015 Wireless Driver + FILES:= \ + $(PKG_BUILD_DIR)/libertas.$(LINUX_KMOD_SUFFIX) \ + $(PKG_BUILD_DIR)/usb8xxx.$(LINUX_KMOD_SUFFIX) + AUTOLOAD:=$(call AutoLoad,20,libertas usb8xxx) +endef + +define Download/firmware + URL:=http://dev.laptop.org/pub/firmware/libertas + FILE:=usb8388-5.220.11.p5.bin + MD5SUM=37cc814d5a475fcf8f8fbe89a9c5d546 +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + SUBDIRS="$(PKG_BUILD_DIR)" \ + CONFIG_LIBERTAS=m \ + CONFIG_LIBERTAS_USB=m \ + EXTRA_CFLAGS="-I$(PKG_BUILD_DIR) -include compat.h -I$(STAGING_DIR)/usr/include/mac80211" \ + modules +endef + +define KernelPackage/libertas/install + $(INSTALL_DIR) $(1)/lib/firmware + $(INSTALL_BIN) $(DL_DIR)/usb8388-5.220.11.p5.bin $(1)/lib/firmware/usb8388.bin + $(INSTALL_DATA) ./files/LICENSE $(1)/lib/firmware/ +endef + +$(eval $(call KernelPackage,libertas)) +$(eval $(call Download,firmware)) diff --git a/package/libertas/files/LICENSE b/package/libertas/files/LICENSE new file mode 100644 index 0000000000..a740952349 --- /dev/null +++ b/package/libertas/files/LICENSE @@ -0,0 +1,33 @@ +Copyright (c) 2006, One Laptop per Child and Marvell Corporation. +All rights reserved. + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +* Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. +* Neither the name of Marvell Corporation nor the names of its suppliers + may be used to endorse or promote products derived from this software + without specific prior written permission. +* No reverse engineering, decompilation, or disassembly of this software + is permitted. +* You may not use or attempt to use this software in conjunction with + any product that is offered by a third party as a replacement, + substitute or alternative to a Marvell Product where a Marvell Product + is defined as a proprietary wireless LAN embedded client solution of + Marvell or a Marvell Affiliate. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/package/libertas/src/11d.c b/package/libertas/src/11d.c new file mode 100644 index 0000000000..5e10ce0d35 --- /dev/null +++ b/package/libertas/src/11d.c @@ -0,0 +1,700 @@ +/** + * This file contains functions for 802.11D. + */ +#include <linux/ctype.h> +#include <linux/kernel.h> +#include <linux/wireless.h> + +#include "host.h" +#include "decl.h" +#include "11d.h" +#include "dev.h" +#include "wext.h" + +#define TX_PWR_DEFAULT 10 + +static struct region_code_mapping region_code_mapping[] = { + {"US ", 0x10}, /* US FCC */ + {"CA ", 0x10}, /* IC Canada */ + {"SG ", 0x10}, /* Singapore */ + {"EU ", 0x30}, /* ETSI */ + {"AU ", 0x30}, /* Australia */ + {"KR ", 0x30}, /* Republic Of Korea */ + {"ES ", 0x31}, /* Spain */ + {"FR ", 0x32}, /* France */ + {"JP ", 0x40}, /* Japan */ +}; + +/* Following 2 structure defines the supported channels */ +static struct chan_freq_power channel_freq_power_UN_BG[] = { + {1, 2412, TX_PWR_DEFAULT}, + {2, 2417, TX_PWR_DEFAULT}, + {3, 2422, TX_PWR_DEFAULT}, + {4, 2427, TX_PWR_DEFAULT}, + {5, 2432, TX_PWR_DEFAULT}, + {6, 2437, TX_PWR_DEFAULT}, + {7, 2442, TX_PWR_DEFAULT}, + {8, 2447, TX_PWR_DEFAULT}, + {9, 2452, TX_PWR_DEFAULT}, + {10, 2457, TX_PWR_DEFAULT}, + {11, 2462, TX_PWR_DEFAULT}, + {12, 2467, TX_PWR_DEFAULT}, + {13, 2472, TX_PWR_DEFAULT}, + {14, 2484, TX_PWR_DEFAULT} +}; + +static u8 lbs_region_2_code(u8 *region) +{ + u8 i; + + for (i = 0; region[i] && i < COUNTRY_CODE_LEN; i++) + region[i] = toupper(region[i]); + + for (i = 0; i < ARRAY_SIZE(region_code_mapping); i++) { + if (!memcmp(region, region_code_mapping[i].region, + COUNTRY_CODE_LEN)) + return (region_code_mapping[i].code); + } + + /* default is US */ + return (region_code_mapping[0].code); +} + +static u8 *lbs_code_2_region(u8 code) +{ + u8 i; + + for (i = 0; i < ARRAY_SIZE(region_code_mapping); i++) { + if (region_code_mapping[i].code == code) + return (region_code_mapping[i].region); + } + /* default is US */ + return (region_code_mapping[0].region); +} + +/** + * @brief This function finds the nrchan-th chan after the firstchan + * @param band band + * @param firstchan first channel number + * @param nrchan number of channels + * @return the nrchan-th chan number +*/ +static u8 lbs_get_chan_11d(u8 band, u8 firstchan, u8 nrchan, u8 *chan) +/*find the nrchan-th chan after the firstchan*/ +{ + u8 i; + struct chan_freq_power *cfp; + u8 cfp_no; + + cfp = channel_freq_power_UN_BG; + cfp_no = ARRAY_SIZE(channel_freq_power_UN_BG); + + for (i = 0; i < cfp_no; i++) { + if ((cfp + i)->channel == firstchan) { + lbs_deb_11d("firstchan found\n"); + break; + } + } + + if (i < cfp_no) { + /*if beyond the boundary */ + if (i + nrchan < cfp_no) { + *chan = (cfp + i + nrchan)->channel; + return 1; + } + } + + return 0; +} + +/** + * @brief This function Checks if chan txpwr is learned from AP/IBSS + * @param chan chan number + * @param parsed_region_chan pointer to parsed_region_chan_11d + * @return TRUE; FALSE +*/ +static u8 lbs_channel_known_11d(u8 chan, + struct parsed_region_chan_11d * parsed_region_chan) +{ + struct chan_power_11d *chanpwr = parsed_region_chan->chanpwr; + u8 nr_chan = parsed_region_chan->nr_chan; + u8 i = 0; + + lbs_deb_hex(LBS_DEB_11D, "parsed_region_chan", (char *)chanpwr, + sizeof(struct chan_power_11d) * nr_chan); + + for (i = 0; i < nr_chan; i++) { + if (chan == chanpwr[i].chan) { + lbs_deb_11d("found chan %d\n", chan); + return 1; + } + } + + lbs_deb_11d("chan %d not found\n", chan); + return 0; +} + +u32 lbs_chan_2_freq(u8 chan, u8 band) +{ + struct chan_freq_power *cf; + u16 i; + u32 freq = 0; + + cf = channel_freq_power_UN_BG; + + for (i = 0; i < ARRAY_SIZE(channel_freq_power_UN_BG); i++) { + if (chan == cf[i].channel) + freq = cf[i].freq; + } + + return freq; +} + +static int generate_domain_info_11d(struct parsed_region_chan_11d + *parsed_region_chan, + struct lbs_802_11d_domain_reg *domaininfo) +{ + u8 nr_subband = 0; + + u8 nr_chan = parsed_region_chan->nr_chan; + u8 nr_parsedchan = 0; + + u8 firstchan = 0, nextchan = 0, maxpwr = 0; + + u8 i, flag = 0; + + memcpy(domaininfo->countrycode, parsed_region_chan->countrycode, + COUNTRY_CODE_LEN); + + lbs_deb_11d("nrchan %d\n", nr_chan); + lbs_deb_hex(LBS_DEB_11D, "parsed_region_chan", (char *)parsed_region_chan, + sizeof(struct parsed_region_chan_11d)); + + for (i = 0; i < nr_chan; i++) { + if (!flag) { + flag = 1; + nextchan = firstchan = + parsed_region_chan->chanpwr[i].chan; + maxpwr = parsed_region_chan->chanpwr[i].pwr; + nr_parsedchan = 1; + continue; + } + + if (parsed_region_chan->chanpwr[i].chan == nextchan + 1 && + parsed_region_chan->chanpwr[i].pwr == maxpwr) { + nextchan++; + nr_parsedchan++; + } else { + domaininfo->subband[nr_subband].firstchan = firstchan; + domaininfo->subband[nr_subband].nrchan = + nr_parsedchan; + domaininfo->subband[nr_subband].maxtxpwr = maxpwr; + nr_subband++; + nextchan = firstchan = + parsed_region_chan->chanpwr[i].chan; + maxpwr = parsed_region_chan->chanpwr[i].pwr; + } + } + + if (flag) { + domaininfo->subband[nr_subband].firstchan = firstchan; + domaininfo->subband[nr_subband].nrchan = nr_parsedchan; + domaininfo->subband[nr_subband].maxtxpwr = maxpwr; + nr_subband++; + } + domaininfo->nr_subband = nr_subband; + + lbs_deb_11d("nr_subband=%x\n", domaininfo->nr_subband); + lbs_deb_hex(LBS_DEB_11D, "domaininfo", (char *)domaininfo, + COUNTRY_CODE_LEN + 1 + + sizeof(struct ieeetypes_subbandset) * nr_subband); + return 0; +} + +/** + * @brief This function generates parsed_region_chan from Domain Info learned from AP/IBSS + * @param region_chan pointer to struct region_channel + * @param *parsed_region_chan pointer to parsed_region_chan_11d + * @return N/A +*/ +static void lbs_generate_parsed_region_chan_11d(struct region_channel *region_chan, + struct parsed_region_chan_11d * + parsed_region_chan) +{ + u8 i; + struct chan_freq_power *cfp; + + if (region_chan == NULL) { + lbs_deb_11d("region_chan is NULL\n"); + return; + } + + cfp = region_chan->CFP; + if (cfp == NULL) { + lbs_deb_11d("cfp is NULL \n"); + return; + } + + parsed_region_chan->band = region_chan->band; + parsed_region_chan->region = region_chan->region; + memcpy(parsed_region_chan->countrycode, + lbs_code_2_region(region_chan->region), COUNTRY_CODE_LEN); + + lbs_deb_11d("region 0x%x, band %d\n", parsed_region_chan->region, + parsed_region_chan->band); + + for (i = 0; i < region_chan->nrcfp; i++, cfp++) { + parsed_region_chan->chanpwr[i].chan = cfp->channel; + parsed_region_chan->chanpwr[i].pwr = cfp->maxtxpower; + lbs_deb_11d("chan %d, pwr %d\n", + parsed_region_chan->chanpwr[i].chan, + parsed_region_chan->chanpwr[i].pwr); + } + parsed_region_chan->nr_chan = region_chan->nrcfp; + + lbs_deb_11d("nrchan %d\n", parsed_region_chan->nr_chan); + + return; +} + +/** + * @brief generate parsed_region_chan from Domain Info learned from AP/IBSS + * @param region region ID + * @param band band + * @param chan chan + * @return TRUE;FALSE +*/ +static u8 lbs_region_chan_supported_11d(u8 region, u8 band, u8 chan) +{ + struct chan_freq_power *cfp; + int cfp_no; + u8 idx; + int ret = 0; + + lbs_deb_enter(LBS_DEB_11D); + + cfp = lbs_get_region_cfp_table(region, band, &cfp_no); + if (cfp == NULL) + return 0; + + for (idx = 0; idx < cfp_no; idx++) { + if (chan == (cfp + idx)->channel) { + /* If Mrvl Chip Supported? */ + if ((cfp + idx)->unsupported) { + ret = 0; + } else { + ret = 1; + } + goto done; + } + } + + /*chan is not in the region table */ + +done: + lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret); + return ret; +} + +/** + * @brief This function checks if chan txpwr is learned from AP/IBSS + * @param chan chan number + * @param parsed_region_chan pointer to parsed_region_chan_11d + * @return 0 +*/ +static int parse_domain_info_11d(struct ieeetypes_countryinfofullset* + countryinfo, + u8 band, + struct parsed_region_chan_11d * + parsed_region_chan) +{ + u8 nr_subband, nrchan; + u8 lastchan, firstchan; + u8 region; + u8 curchan = 0; + + u8 idx = 0; /*chan index in parsed_region_chan */ + + u8 j, i; + + lbs_deb_enter(LBS_DEB_11D); + + /*validation Rules: + 1. valid region Code + 2. First Chan increment + 3. channel range no overlap + 4. channel is valid? + 5. channel is supported by region? + 6. Others + */ + + lbs_deb_hex(LBS_DEB_11D, "countryinfo", (u8 *) countryinfo, 30); + + if ((*(countryinfo->countrycode)) == 0 + || (countryinfo->len <= COUNTRY_CODE_LEN)) { + /* No region Info or Wrong region info: treat as No 11D info */ + goto done; + } + + /*Step1: check region_code */ + parsed_region_chan->region = region = + lbs_region_2_code(countryinfo->countrycode); + + lbs_deb_11d("regioncode=%x\n", (u8) parsed_region_chan->region); + lbs_deb_hex(LBS_DEB_11D, "countrycode", (char *)countryinfo->countrycode, + COUNTRY_CODE_LEN); + + parsed_region_chan->band = band; + + memcpy(parsed_region_chan->countrycode, countryinfo->countrycode, + COUNTRY_CODE_LEN); + + nr_subband = (countryinfo->len - COUNTRY_CODE_LEN) / + sizeof(struct ieeetypes_subbandset); + + for (j = 0, lastchan = 0; j < nr_subband; j++) { + + if (countryinfo->subband[j].firstchan <= lastchan) { + /*Step2&3. Check First Chan Num increment and no overlap */ + lbs_deb_11d("chan %d>%d, overlap\n", + countryinfo->subband[j].firstchan, lastchan); + continue; + } + + firstchan = countryinfo->subband[j].firstchan; + nrchan = countryinfo->subband[j].nrchan; + + for (i = 0; idx < MAX_NO_OF_CHAN && i < nrchan; i++) { + /*step4: channel is supported? */ + + if (!lbs_get_chan_11d(band, firstchan, i, &curchan)) { + /* Chan is not found in UN table */ + lbs_deb_11d("chan is not supported: %d \n", i); + break; + } + + lastchan = curchan; + + if (lbs_region_chan_supported_11d + (region, band, curchan)) { + /*step5: Check if curchan is supported by mrvl in region */ + parsed_region_chan->chanpwr[idx].chan = curchan; + parsed_region_chan->chanpwr[idx].pwr = + countryinfo->subband[j].maxtxpwr; + idx++; + } else { + /*not supported and ignore the chan */ + lbs_deb_11d( + "i %d, chan %d unsupported in region %x, band %d\n", + i, curchan, region, band); + } + } + + /*Step6: Add other checking if any */ + + } + + parsed_region_chan->nr_chan = idx; + + lbs_deb_11d("nrchan=%x\n", parsed_region_chan->nr_chan); + lbs_deb_hex(LBS_DEB_11D, "parsed_region_chan", (u8 *) parsed_region_chan, + 2 + COUNTRY_CODE_LEN + sizeof(struct parsed_region_chan_11d) * idx); + +done: + lbs_deb_enter(LBS_DEB_11D); + return 0; +} + +/** + * @brief This function calculates the scan type for channels + * @param chan chan number + * @param parsed_region_chan pointer to parsed_region_chan_11d + * @return PASSIVE if chan is unknown; ACTIVE if chan is known +*/ +u8 lbs_get_scan_type_11d(u8 chan, + struct parsed_region_chan_11d * parsed_region_chan) +{ + u8 scan_type = CMD_SCAN_TYPE_PASSIVE; + + lbs_deb_enter(LBS_DEB_11D); + + if (lbs_channel_known_11d(chan, parsed_region_chan)) { + lbs_deb_11d("found, do active scan\n"); + scan_type = CMD_SCAN_TYPE_ACTIVE; + } else { + lbs_deb_11d("not found, do passive scan\n"); + } + + lbs_deb_leave_args(LBS_DEB_11D, "ret scan_type %d", scan_type); + return scan_type; + +} + +void lbs_init_11d(struct lbs_private *priv) +{ + priv->enable11d = 0; + memset(&(priv->parsed_region_chan), 0, + sizeof(struct parsed_region_chan_11d)); + return; +} + +/** + * @brief This function sets DOMAIN INFO to FW + * @param priv pointer to struct lbs_private + * @return 0; -1 +*/ +static int set_domain_info_11d(struct lbs_private *priv) +{ + int ret; + + if (!priv->enable11d) { + lbs_deb_11d("dnld domain Info with 11d disabled\n"); + return 0; + } + + ret = lbs_prepare_and_send_command(priv, CMD_802_11D_DOMAIN_INFO, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, 0, NULL); + if (ret) + lbs_deb_11d("fail to dnld domain info\n"); + + return ret; +} + +/** + * @brief This function setups scan channels + * @param priv pointer to struct lbs_private + * @param band band + * @return 0 +*/ +int lbs_set_universaltable(struct lbs_private *priv, u8 band) +{ + u16 size = sizeof(struct chan_freq_power); + u16 i = 0; + + memset(priv->universal_channel, 0, + sizeof(priv->universal_channel)); + + priv->universal_channel[i].nrcfp = + sizeof(channel_freq_power_UN_BG) / size; + lbs_deb_11d("BG-band nrcfp %d\n", + priv->universal_channel[i].nrcfp); + + priv->universal_channel[i].CFP = channel_freq_power_UN_BG; + priv->universal_channel[i].valid = 1; + priv->universal_channel[i].region = UNIVERSAL_REGION_CODE; + priv->universal_channel[i].band = band; + i++; + + return 0; +} + +/** + * @brief This function implements command CMD_802_11D_DOMAIN_INFO + * @param priv pointer to struct lbs_private + * @param cmd pointer to cmd buffer + * @param cmdno cmd ID + * @param cmdOption cmd action + * @return 0 +*/ +int lbs_cmd_802_11d_domain_info(struct lbs_private *priv, + struct cmd_ds_command *cmd, u16 cmdno, + u16 cmdoption) +{ + struct cmd_ds_802_11d_domain_info *pdomaininfo = + &cmd->params.domaininfo; + struct mrvlietypes_domainparamset *domain = &pdomaininfo->domain; + u8 nr_subband = priv->domainreg.nr_subband; + + lbs_deb_enter(LBS_DEB_11D); + + lbs_deb_11d("nr_subband=%x\n", nr_subband); + + cmd->command = cpu_to_le16(cmdno); + pdomaininfo->action = cpu_to_le16(cmdoption); + if (cmdoption == CMD_ACT_GET) { + cmd->size = + cpu_to_le16(sizeof(pdomaininfo->action) + S_DS_GEN); + lbs_deb_hex(LBS_DEB_11D, "802_11D_DOMAIN_INFO", (u8 *) cmd, + le16_to_cpu(cmd->size)); + goto done; + } + + domain->header.type = cpu_to_le16(TLV_TYPE_DOMAIN); + memcpy(domain->countrycode, priv->domainreg.countrycode, + sizeof(domain->countrycode)); + + domain->header.len = + cpu_to_le16(nr_subband * sizeof(struct ieeetypes_subbandset) + + sizeof(domain->countrycode)); + + if (nr_subband) { + memcpy(domain->subband, priv->domainreg.subband, + nr_subband * sizeof(struct ieeetypes_subbandset)); + + cmd->size = cpu_to_le16(sizeof(pdomaininfo->action) + + le16_to_cpu(domain->header.len) + + sizeof(struct mrvlietypesheader) + + S_DS_GEN); + } else { + cmd->size = + cpu_to_le16(sizeof(pdomaininfo->action) + S_DS_GEN); + } + + lbs_deb_hex(LBS_DEB_11D, "802_11D_DOMAIN_INFO", (u8 *) cmd, le16_to_cpu(cmd->size)); + +done: + lbs_deb_enter(LBS_DEB_11D); + return 0; +} + +/** + * @brief This function parses countryinfo from AP and download country info to FW + * @param priv pointer to struct lbs_private + * @param resp pointer to command response buffer + * @return 0; -1 + */ +int lbs_ret_802_11d_domain_info(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11d_domain_info *domaininfo = &resp->params.domaininforesp; + struct mrvlietypes_domainparamset *domain = &domaininfo->domain; + u16 action = le16_to_cpu(domaininfo->action); + s16 ret = 0; + u8 nr_subband = 0; + + lbs_deb_enter(LBS_DEB_11D); + + lbs_deb_hex(LBS_DEB_11D, "domain info resp", (u8 *) resp, + (int)le16_to_cpu(resp->size)); + + nr_subband = (le16_to_cpu(domain->header.len) - COUNTRY_CODE_LEN) / + sizeof(struct ieeetypes_subbandset); + + lbs_deb_11d("domain info resp: nr_subband %d\n", nr_subband); + + if (nr_subband > MRVDRV_MAX_SUBBAND_802_11D) { + lbs_deb_11d("Invalid Numrer of Subband returned!!\n"); + return -1; + } + + switch (action) { + case CMD_ACT_SET: /*Proc Set action */ + break; + + case CMD_ACT_GET: + break; + default: + lbs_deb_11d("Invalid action:%d\n", domaininfo->action); + ret = -1; + break; + } + + lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret); + return ret; +} + +/** + * @brief This function parses countryinfo from AP and download country info to FW + * @param priv pointer to struct lbs_private + * @return 0; -1 + */ +int lbs_parse_dnld_countryinfo_11d(struct lbs_private *priv, + struct bss_descriptor * bss) +{ + int ret; + + lbs_deb_enter(LBS_DEB_11D); + if (priv->enable11d) { + memset(&priv->parsed_region_chan, 0, + sizeof(struct parsed_region_chan_11d)); + ret = parse_domain_info_11d(&bss->countryinfo, 0, + &priv->parsed_region_chan); + + if (ret == -1) { + lbs_deb_11d("error parsing domain_info from AP\n"); + goto done; + } + + memset(&priv->domainreg, 0, + sizeof(struct lbs_802_11d_domain_reg)); + generate_domain_info_11d(&priv->parsed_region_chan, + &priv->domainreg); + + ret = set_domain_info_11d(priv); + + if (ret) { + lbs_deb_11d("error setting domain info\n"); + goto done; + } + } + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret); + return ret; +} + +/** + * @brief This function generates 11D info from user specified regioncode and download to FW + * @param priv pointer to struct lbs_private + * @return 0; -1 + */ +int lbs_create_dnld_countryinfo_11d(struct lbs_private *priv) +{ + int ret; + struct region_channel *region_chan; + u8 j; + + lbs_deb_enter(LBS_DEB_11D); + lbs_deb_11d("curbssparams.band %d\n", priv->curbssparams.band); + + if (priv->enable11d) { + /* update parsed_region_chan_11; dnld domaininf to FW */ + + for (j = 0; j < ARRAY_SIZE(priv->region_channel); j++) { + region_chan = &priv->region_channel[j]; + + lbs_deb_11d("%d region_chan->band %d\n", j, + region_chan->band); + + if (!region_chan || !region_chan->valid + || !region_chan->CFP) + continue; + if (region_chan->band != priv->curbssparams.band) + continue; + break; + } + + if (j >= ARRAY_SIZE(priv->region_channel)) { + lbs_deb_11d("region_chan not found, band %d\n", + priv->curbssparams.band); + ret = -1; + goto done; + } + + memset(&priv->parsed_region_chan, 0, + sizeof(struct parsed_region_chan_11d)); + lbs_generate_parsed_region_chan_11d(region_chan, + &priv-> + parsed_region_chan); + + memset(&priv->domainreg, 0, + sizeof(struct lbs_802_11d_domain_reg)); + generate_domain_info_11d(&priv->parsed_region_chan, + &priv->domainreg); + + ret = set_domain_info_11d(priv); + + if (ret) { + lbs_deb_11d("error setting domain info\n"); + goto done; + } + + } + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret); + return ret; +} diff --git a/package/libertas/src/11d.h b/package/libertas/src/11d.h new file mode 100644 index 0000000000..811eea2cfb --- /dev/null +++ b/package/libertas/src/11d.h @@ -0,0 +1,105 @@ +/** + * This header file contains data structures and + * function declarations of 802.11d + */ +#ifndef _LBS_11D_ +#define _LBS_11D_ + +#include "types.h" +#include "defs.h" + +#define UNIVERSAL_REGION_CODE 0xff + +/** (Beaconsize(256)-5(IEId,len,contrystr(3))/3(FirstChan,NoOfChan,MaxPwr) + */ +#define MRVDRV_MAX_SUBBAND_802_11D 83 + +#define COUNTRY_CODE_LEN 3 +#define MAX_NO_OF_CHAN 40 + +struct cmd_ds_command; + +/** Data structure for Country IE*/ +struct ieeetypes_subbandset { + u8 firstchan; + u8 nrchan; + u8 maxtxpwr; +} __attribute__ ((packed)); + +struct ieeetypes_countryinfoset { + u8 element_id; + u8 len; + u8 countrycode[COUNTRY_CODE_LEN]; + struct ieeetypes_subbandset subband[1]; +}; + +struct ieeetypes_countryinfofullset { + u8 element_id; + u8 len; + u8 countrycode[COUNTRY_CODE_LEN]; + struct ieeetypes_subbandset subband[MRVDRV_MAX_SUBBAND_802_11D]; +} __attribute__ ((packed)); + +struct mrvlietypes_domainparamset { + struct mrvlietypesheader header; + u8 countrycode[COUNTRY_CODE_LEN]; + struct ieeetypes_subbandset subband[1]; +} __attribute__ ((packed)); + +struct cmd_ds_802_11d_domain_info { + __le16 action; + struct mrvlietypes_domainparamset domain; +} __attribute__ ((packed)); + +/** domain regulatory information */ +struct lbs_802_11d_domain_reg { + /** country Code*/ + u8 countrycode[COUNTRY_CODE_LEN]; + /** No. of subband*/ + u8 nr_subband; + struct ieeetypes_subbandset subband[MRVDRV_MAX_SUBBAND_802_11D]; +}; + +struct chan_power_11d { + u8 chan; + u8 pwr; +} __attribute__ ((packed)); + +struct parsed_region_chan_11d { + u8 band; + u8 region; + s8 countrycode[COUNTRY_CODE_LEN]; + struct chan_power_11d chanpwr[MAX_NO_OF_CHAN]; + u8 nr_chan; +} __attribute__ ((packed)); + +struct region_code_mapping { + u8 region[COUNTRY_CODE_LEN]; + u8 code; +}; + +struct lbs_private; + +u8 lbs_get_scan_type_11d(u8 chan, + struct parsed_region_chan_11d *parsed_region_chan); + +u32 lbs_chan_2_freq(u8 chan, u8 band); + +void lbs_init_11d(struct lbs_private *priv); + +int lbs_set_universaltable(struct lbs_private *priv, u8 band); + +int lbs_cmd_802_11d_domain_info(struct lbs_private *priv, + struct cmd_ds_command *cmd, u16 cmdno, + u16 cmdOption); + +int lbs_ret_802_11d_domain_info(struct lbs_private *priv, + struct cmd_ds_command *resp); + +struct bss_descriptor; +int lbs_parse_dnld_countryinfo_11d(struct lbs_private *priv, + struct bss_descriptor * bss); + +int lbs_create_dnld_countryinfo_11d(struct lbs_private *priv); + +#endif diff --git a/package/libertas/src/:0 b/package/libertas/src/:0 new file mode 100644 index 0000000000..ff68859c72 --- /dev/null +++ b/package/libertas/src/:0 @@ -0,0 +1,62 @@ +# +# Copyright (C) 2007 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# +# $Id: Makefile 8694 2007-09-08 19:55:42Z nbd $ + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=kmod-libertas +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/libertas + SUBMENU:=Other modules + DEPENDS:=@TARGET_olpc +kmod-mac80211 + TITLE:=Marvell 88W8015 Wireless Driver + FILES:= \ + $(PKG_BUILD_DIR)/libertas.$(LINUX_KMOD_SUFFIX) \ + $(PKG_BUILD_DIR)/usb8xxx.$(LINUX_KMOD_SUFFIX) +# AUTOLOAD:=$(call AutoLoad,20,switch-core switch-robo switch-adm) +endef + +define Download/firmware + URL:=http://dev.laptop.org/pub/firmware/libertas + FILE:=usb8388-5.220.11.p5.bin + MD5SUM=123 +endef + +define Download/firmware_license + URL:=http://dev.laptop.org/pub/firmware/libertas + FILE:=LICENSE + MD5SUM=123 +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + SUBDIRS="$(PKG_BUILD_DIR)" \ + CONFIG_LIBERTAS=m \ + CONFIG_LIBERTAS_USB=m \ + EXTRA_CFLAGS="-I$(PKG_BUILD_DIR) -include compat.h -I$(STAGING_DIR)/usr/include/mac80211" \ + modules +endef + +define KernelPackage/libertas/install + $(INSTALL_DIR) $(1)/lib/firmware + $(INSTALL_BIN) $(DL_DIR)/usb8388-5.220.11.p5.bin $(1)/lib/firmware/usb8388.bin + $(INSTALL_BIN) $(DL_DIR)/LICENSE $(1)/lib/firmware/ +endef + +$(eval $(call KernelPackage,libertas)) +$(eval $(call Download,firmware)) diff --git a/package/libertas/src/LICENSE b/package/libertas/src/LICENSE new file mode 100644 index 0000000000..8862742213 --- /dev/null +++ b/package/libertas/src/LICENSE @@ -0,0 +1,16 @@ + Copyright (c) 2003-2006, Marvell International Ltd. + All Rights Reserved + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + diff --git a/package/libertas/src/Makefile b/package/libertas/src/Makefile new file mode 100644 index 0000000000..0e2787691f --- /dev/null +++ b/package/libertas/src/Makefile @@ -0,0 +1,15 @@ +libertas-objs := main.o wext.o \ + rx.o tx.o cmd.o \ + cmdresp.o scan.o \ + join.o 11d.o \ + debugfs.o \ + ethtool.o assoc.o + +usb8xxx-objs += if_usb.o +libertas_cs-objs += if_cs.o +libertas_sdio-objs += if_sdio.o + +obj-$(CONFIG_LIBERTAS) += libertas.o +obj-$(CONFIG_LIBERTAS_USB) += usb8xxx.o +obj-$(CONFIG_LIBERTAS_CS) += libertas_cs.o +obj-$(CONFIG_LIBERTAS_SDIO) += libertas_sdio.o diff --git a/package/libertas/src/README b/package/libertas/src/README new file mode 100644 index 0000000000..d860fc3757 --- /dev/null +++ b/package/libertas/src/README @@ -0,0 +1,229 @@ +================================================================================ + README for USB8388 + + (c) Copyright © 2003-2006, Marvell International Ltd. + All Rights Reserved + + This software file (the "File") is distributed by Marvell International + Ltd. under the terms of the GNU General Public License Version 2, June 1991 + (the "License"). You may use, redistribute and/or modify this File in + accordance with the terms and conditions of the License, a copy of which + is available along with the File in the license.txt file or by writing to + the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 or on the worldwide web at http://www.gnu.org/licenses/gpl.txt. + + THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + ARE EXPRESSLY DISCLAIMED. The License provides additional details about + this warranty disclaimer. +================================================================================ + +===================== +DRIVER LOADING +===================== + + o. Copy the firmware image (e.g. usb8388.bin) to /lib/firmware/ + + o. Load driver by using the following command: + + insmod usb8388.ko [fw_name=usb8388.bin] + +========================= +ETHTOOL +========================= + + +Use the -i option to retrieve version information from the driver. + +# ethtool -i eth0 +driver: libertas +version: COMM-USB8388-318.p4 +firmware-version: 5.110.7 +bus-info: + +Use the -e option to read the EEPROM contents of the card. + + Usage: + ethtool -e ethX [raw on|off] [offset N] [length N] + + -e retrieves and prints an EEPROM dump for the specified ethernet + device. When raw is enabled, then it dumps the raw EEPROM data + to stdout. The length and offset parameters allow dumping cer- + tain portions of the EEPROM. Default is to dump the entire EEP- + ROM. + +# ethtool -e eth0 offset 0 length 16 +Offset Values +------ ------ +0x0000 38 33 30 58 00 00 34 f4 00 00 10 00 00 c4 17 00 + +======================== +DEBUGFS COMMANDS +======================== + +those commands are used via debugfs interface + +=========== +rdmac +rdbbp +rdrf + These commands are used to read the MAC, BBP and RF registers from the + card. These commands take one parameter that specifies the offset + location that is to be read. This parameter must be specified in + hexadecimal (its possible to preceed preceding the number with a "0x"). + + Path: /debugfs/libertas_wireless/ethX/registers/ + + Usage: + echo "0xa123" > rdmac ; cat rdmac + echo "0xa123" > rdbbp ; cat rdbbp + echo "0xa123" > rdrf ; cat rdrf +wrmac +wrbbp +wrrf + These commands are used to write the MAC, BBP and RF registers in the + card. These commands take two parameters that specify the offset + location and the value that is to be written. This parameters must + be specified in hexadecimal (its possible to preceed the number + with a "0x"). + + Usage: + echo "0xa123 0xaa" > wrmac + echo "0xa123 0xaa" > wrbbp + echo "0xa123 0xaa" > wrrf + +sleepparams + This command is used to set the sleepclock configurations + + Path: /debugfs/libertas_wireless/ethX/ + + Usage: + cat sleepparams: reads the current sleepclock configuration + + echo "p1 p2 p3 p4 p5 p6" > sleepparams: writes the sleepclock configuration. + + where: + p1 is Sleep clock error in ppm (0-65535) + p2 is Wakeup offset in usec (0-65535) + p3 is Clock stabilization time in usec (0-65535) + p4 is Control periodic calibration (0-2) + p5 is Control the use of external sleep clock (0-2) + p6 is reserved for debug (0-65535) + +subscribed_events + + The subscribed_events directory contains the interface for the + subscribed events API. + + Path: /debugfs/libertas_wireless/ethX/subscribed_events/ + + Each event is represented by a filename. Each filename consists of the + following three fields: + Value Frequency Subscribed + + To read the current values for a given event, do: + cat event + To set the current values, do: + echo "60 2 1" > event + + Frequency field specifies the reporting frequency for this event. + If it is set to 0, then the event is reported only once, and then + automatically unsubscribed. If it is set to 1, then the event is + reported every time it occurs. If it is set to N, then the event is + reported every Nth time it occurs. + + beacon_missed + Value field specifies the number of consecutive missing beacons which + triggers the LINK_LOSS event. This event is generated only once after + which the firmware resets its state. At initialization, the LINK_LOSS + event is subscribed by default. The default value of MissedBeacons is + 60. + + failure_count + Value field specifies the consecutive failure count threshold which + triggers the generation of the MAX_FAIL event. Once this event is + generated, the consecutive failure count is reset to 0. + At initialization, the MAX_FAIL event is NOT subscribed by + default. + + high_rssi + This event is generated when the average received RSSI in beacons goes + above a threshold, specified by Value. + + low_rssi + This event is generated when the average received RSSI in beacons goes + below a threshold, specified by Value. + + high_snr + This event is generated when the average received SNR in beacons goes + above a threshold, specified by Value. + + low_snr + This event is generated when the average received SNR in beacons goes + below a threshold, specified by Value. + +extscan + This command is used to do a specific scan. + + Path: /debugfs/libertas_wireless/ethX/ + + Usage: echo "SSID" > extscan + + Example: + echo "LINKSYS-AP" > extscan + + To see the results of use getscantable command. + +getscantable + + Display the current contents of the driver scan table (ie. get the + scan results). + + Path: /debugfs/libertas_wireless/ethX/ + + Usage: + cat getscantable + +setuserscan + Initiate a customized scan and retrieve the results + + + Path: /debugfs/libertas_wireless/ethX/ + + Usage: + echo "[ARGS]" > setuserscan + + where [ARGS]: + + bssid=xx:xx:xx:xx:xx:xx specify a BSSID filter for the scan + ssid="[SSID]" specify a SSID filter for the scan + keep=[0 or 1] keep the previous scan results (1), discard (0) + dur=[scan time] time to scan for each channel in milliseconds + type=[1,2,3] BSS type: 1 (Infra), 2(Adhoc), 3(Any) + + Any combination of the above arguments can be supplied on the command + line. If dur tokens are absent, the driver default setting will be used. + The bssid and ssid fields, if blank, will produce an unfiltered scan. + The type field will default to 3 (Any) and the keep field will default + to 0 (Discard). + + Examples: + 1) Perform a passive scan on all channels for 20 ms per channel: + echo "dur=20" > setuserscan + + 2) Perform an active scan for a specific SSID: + echo "ssid="TestAP"" > setuserscan + + 3) Scan all available channels (B/G, A bands) for a specific BSSID, keep + the current scan table intact, update existing or append new scan data: + echo "bssid=00:50:43:20:12:82 keep=1" > setuserscan + + 4) Scan for all infrastructure networks. + Keep the previous scan table intact. Update any duplicate BSSID/SSID + matches with the new scan data: + echo "type=1 keep=1" > setuserscan + + All entries in the scan table (not just the new scan data when keep=1) + will be displayed upon completion by use of the getscantable ioctl. + +============================================================================== diff --git a/package/libertas/src/assoc.c b/package/libertas/src/assoc.c new file mode 100644 index 0000000000..7b672fe62c --- /dev/null +++ b/package/libertas/src/assoc.c @@ -0,0 +1,766 @@ +/* Copyright (C) 2006, Red Hat, Inc. */ + +#include <linux/bitops.h> +#include <net/ieee80211.h> +#include <linux/etherdevice.h> + +#include "assoc.h" +#include "join.h" +#include "decl.h" +#include "hostcmd.h" +#include "host.h" +#include "cmd.h" + + +static const u8 bssid_any[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +static const u8 bssid_off[ETH_ALEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + +static int assoc_helper_essid(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0; + struct bss_descriptor * bss; + int channel = -1; + + lbs_deb_enter(LBS_DEB_ASSOC); + + /* FIXME: take channel into account when picking SSIDs if a channel + * is set. + */ + + if (test_bit(ASSOC_FLAG_CHANNEL, &assoc_req->flags)) + channel = assoc_req->channel; + + lbs_deb_assoc("SSID '%s' requested\n", + escape_essid(assoc_req->ssid, assoc_req->ssid_len)); + if (assoc_req->mode == IW_MODE_INFRA) { + lbs_send_specific_ssid_scan(priv, assoc_req->ssid, + assoc_req->ssid_len, 0); + + bss = lbs_find_ssid_in_list(priv, assoc_req->ssid, + assoc_req->ssid_len, NULL, IW_MODE_INFRA, channel); + if (bss != NULL) { + memcpy(&assoc_req->bss, bss, sizeof(struct bss_descriptor)); + ret = lbs_associate(priv, assoc_req); + } else { + lbs_deb_assoc("SSID not found; cannot associate\n"); + } + } else if (assoc_req->mode == IW_MODE_ADHOC) { + /* Scan for the network, do not save previous results. Stale + * scan data will cause us to join a non-existant adhoc network + */ + lbs_send_specific_ssid_scan(priv, assoc_req->ssid, + assoc_req->ssid_len, 1); + + /* Search for the requested SSID in the scan table */ + bss = lbs_find_ssid_in_list(priv, assoc_req->ssid, + assoc_req->ssid_len, NULL, IW_MODE_ADHOC, channel); + if (bss != NULL) { + lbs_deb_assoc("SSID found, will join\n"); + memcpy(&assoc_req->bss, bss, sizeof(struct bss_descriptor)); + lbs_join_adhoc_network(priv, assoc_req); + } else { + /* else send START command */ + lbs_deb_assoc("SSID not found, creating adhoc network\n"); + memcpy(&assoc_req->bss.ssid, &assoc_req->ssid, + IW_ESSID_MAX_SIZE); + assoc_req->bss.ssid_len = assoc_req->ssid_len; + lbs_start_adhoc_network(priv, assoc_req); + } + } + + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + + +static int assoc_helper_bssid(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0; + struct bss_descriptor * bss; + DECLARE_MAC_BUF(mac); + + lbs_deb_enter_args(LBS_DEB_ASSOC, "BSSID %s", + print_mac(mac, assoc_req->bssid)); + + /* Search for index position in list for requested MAC */ + bss = lbs_find_bssid_in_list(priv, assoc_req->bssid, + assoc_req->mode); + if (bss == NULL) { + lbs_deb_assoc("ASSOC: WAP: BSSID %s not found, " + "cannot associate.\n", print_mac(mac, assoc_req->bssid)); + goto out; + } + + memcpy(&assoc_req->bss, bss, sizeof(struct bss_descriptor)); + if (assoc_req->mode == IW_MODE_INFRA) { + ret = lbs_associate(priv, assoc_req); + lbs_deb_assoc("ASSOC: lbs_associate(bssid) returned %d\n", ret); + } else if (assoc_req->mode == IW_MODE_ADHOC) { + lbs_join_adhoc_network(priv, assoc_req); + } + +out: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + + +static int assoc_helper_associate(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0, done = 0; + + lbs_deb_enter(LBS_DEB_ASSOC); + + /* If we're given and 'any' BSSID, try associating based on SSID */ + + if (test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)) { + if (compare_ether_addr(bssid_any, assoc_req->bssid) + && compare_ether_addr(bssid_off, assoc_req->bssid)) { + ret = assoc_helper_bssid(priv, assoc_req); + done = 1; + } + } + + if (!done && test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)) { + ret = assoc_helper_essid(priv, assoc_req); + } + + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + + +static int assoc_helper_mode(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_ASSOC); + + if (assoc_req->mode == priv->mode) + goto done; + + if (assoc_req->mode == IW_MODE_INFRA) { + if (priv->psstate != PS_STATE_FULL_POWER) + lbs_ps_wakeup(priv, CMD_OPTION_WAITFORRSP); + priv->psmode = LBS802_11POWERMODECAM; + } + + priv->mode = assoc_req->mode; + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_SNMP_MIB, + 0, CMD_OPTION_WAITFORRSP, + OID_802_11_INFRASTRUCTURE_MODE, + /* Shoot me now */ (void *) (size_t) assoc_req->mode); + +done: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + + +static int update_channel(struct lbs_private *priv) +{ + int ret; + + /* the channel in f/w could be out of sync, get the current channel */ + lbs_deb_enter(LBS_DEB_ASSOC); + + ret = lbs_get_channel(priv); + if (ret > 0) + priv->curbssparams.channel = (u8) ret; + + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + +void lbs_sync_channel(struct work_struct *work) +{ + struct lbs_private *priv = container_of(work, struct lbs_private, + sync_channel); + + lbs_deb_enter(LBS_DEB_ASSOC); + if (update_channel(priv) != 0) + lbs_pr_info("Channel synchronization failed."); + lbs_deb_leave(LBS_DEB_ASSOC); +} + +static int assoc_helper_channel(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_ASSOC); + + ret = update_channel(priv); + if (ret < 0) { + lbs_deb_assoc("ASSOC: channel: error getting channel."); + } + + if (assoc_req->channel == priv->curbssparams.channel) + goto done; + + if (priv->mesh_dev) { + /* Disconnect mesh while associating -- otherwise it + won't let us change channels */ + lbs_mesh_config(priv, 0); + } + + lbs_deb_assoc("ASSOC: channel: %d -> %d\n", + priv->curbssparams.channel, assoc_req->channel); + + ret = lbs_set_channel(priv, assoc_req->channel); + if (ret < 0) + lbs_deb_assoc("ASSOC: channel: error setting channel."); + + /* FIXME: shouldn't need to grab the channel _again_ after setting + * it since the firmware is supposed to return the new channel, but + * whatever... */ + ret = update_channel(priv); + if (ret < 0) + lbs_deb_assoc("ASSOC: channel: error getting channel."); + + if (assoc_req->channel != priv->curbssparams.channel) { + lbs_deb_assoc("ASSOC: channel: failed to update channel to %d\n", + assoc_req->channel); + goto restore_mesh; + } + + if ( assoc_req->secinfo.wep_enabled + && (assoc_req->wep_keys[0].len + || assoc_req->wep_keys[1].len + || assoc_req->wep_keys[2].len + || assoc_req->wep_keys[3].len)) { + /* Make sure WEP keys are re-sent to firmware */ + set_bit(ASSOC_FLAG_WEP_KEYS, &assoc_req->flags); + } + + /* Must restart/rejoin adhoc networks after channel change */ + set_bit(ASSOC_FLAG_SSID, &assoc_req->flags); + + restore_mesh: + if (priv->mesh_dev) + lbs_mesh_config(priv, 1); + + done: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + + +static int assoc_helper_wep_keys(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int i; + int ret = 0; + + lbs_deb_enter(LBS_DEB_ASSOC); + + /* Set or remove WEP keys */ + if ( assoc_req->wep_keys[0].len + || assoc_req->wep_keys[1].len + || assoc_req->wep_keys[2].len + || assoc_req->wep_keys[3].len) { + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_SET_WEP, + CMD_ACT_ADD, + CMD_OPTION_WAITFORRSP, + 0, assoc_req); + } else { + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_SET_WEP, + CMD_ACT_REMOVE, + CMD_OPTION_WAITFORRSP, + 0, NULL); + } + + if (ret) + goto out; + + /* enable/disable the MAC's WEP packet filter */ + if (assoc_req->secinfo.wep_enabled) + priv->currentpacketfilter |= CMD_ACT_MAC_WEP_ENABLE; + else + priv->currentpacketfilter &= ~CMD_ACT_MAC_WEP_ENABLE; + ret = lbs_set_mac_packet_filter(priv); + if (ret) + goto out; + + mutex_lock(&priv->lock); + + /* Copy WEP keys into priv wep key fields */ + for (i = 0; i < 4; i++) { + memcpy(&priv->wep_keys[i], &assoc_req->wep_keys[i], + sizeof(struct enc_key)); + } + priv->wep_tx_keyidx = assoc_req->wep_tx_keyidx; + + mutex_unlock(&priv->lock); + +out: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + +static int assoc_helper_secinfo(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0; + u32 do_wpa; + u32 rsn = 0; + + lbs_deb_enter(LBS_DEB_ASSOC); + + memcpy(&priv->secinfo, &assoc_req->secinfo, + sizeof(struct lbs_802_11_security)); + + ret = lbs_set_mac_packet_filter(priv); + if (ret) + goto out; + + /* If RSN is already enabled, don't try to enable it again, since + * ENABLE_RSN resets internal state machines and will clobber the + * 4-way WPA handshake. + */ + + /* Get RSN enabled/disabled */ + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_ENABLE_RSN, + CMD_ACT_GET, + CMD_OPTION_WAITFORRSP, + 0, &rsn); + if (ret) { + lbs_deb_assoc("Failed to get RSN status: %d", ret); + goto out; + } + + /* Don't re-enable RSN if it's already enabled */ + do_wpa = (assoc_req->secinfo.WPAenabled || assoc_req->secinfo.WPA2enabled); + if (do_wpa == rsn) + goto out; + + /* Set RSN enabled/disabled */ + rsn = do_wpa; + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_ENABLE_RSN, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, + 0, &rsn); + +out: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + + +static int assoc_helper_wpa_keys(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0; + unsigned int flags = assoc_req->flags; + + lbs_deb_enter(LBS_DEB_ASSOC); + + /* Work around older firmware bug where WPA unicast and multicast + * keys must be set independently. Seen in SDIO parts with firmware + * version 5.0.11p0. + */ + + if (test_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags)) { + clear_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags); + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_KEY_MATERIAL, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, + 0, assoc_req); + assoc_req->flags = flags; + } + + if (ret) + goto out; + + if (test_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags)) { + clear_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags); + + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_KEY_MATERIAL, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, + 0, assoc_req); + assoc_req->flags = flags; + } + +out: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + + +static int assoc_helper_wpa_ie(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_ASSOC); + + if (assoc_req->secinfo.WPAenabled || assoc_req->secinfo.WPA2enabled) { + memcpy(&priv->wpa_ie, &assoc_req->wpa_ie, assoc_req->wpa_ie_len); + priv->wpa_ie_len = assoc_req->wpa_ie_len; + } else { + memset(&priv->wpa_ie, 0, MAX_WPA_IE_LEN); + priv->wpa_ie_len = 0; + } + + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + + +static int should_deauth_infrastructure(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_ASSOC); + + if (priv->connect_status != LBS_CONNECTED) + return 0; + + if (test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)) { + lbs_deb_assoc("Deauthenticating due to new SSID\n"); + ret = 1; + goto out; + } + + if (test_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags)) { + if (priv->secinfo.auth_mode != assoc_req->secinfo.auth_mode) { + lbs_deb_assoc("Deauthenticating due to new security\n"); + ret = 1; + goto out; + } + } + + if (test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)) { + lbs_deb_assoc("Deauthenticating due to new BSSID\n"); + ret = 1; + goto out; + } + + if (test_bit(ASSOC_FLAG_CHANNEL, &assoc_req->flags)) { + lbs_deb_assoc("Deauthenticating due to channel switch\n"); + ret = 1; + goto out; + } + + /* FIXME: deal with 'auto' mode somehow */ + if (test_bit(ASSOC_FLAG_MODE, &assoc_req->flags)) { + if (assoc_req->mode != IW_MODE_INFRA) { + lbs_deb_assoc("Deauthenticating due to leaving " + "infra mode\n"); + ret = 1; + goto out; + } + } + +out: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return 0; +} + + +static int should_stop_adhoc(struct lbs_private *priv, + struct assoc_request * assoc_req) +{ + lbs_deb_enter(LBS_DEB_ASSOC); + + if (priv->connect_status != LBS_CONNECTED) + return 0; + + if (lbs_ssid_cmp(priv->curbssparams.ssid, + priv->curbssparams.ssid_len, + assoc_req->ssid, assoc_req->ssid_len) != 0) + return 1; + + /* FIXME: deal with 'auto' mode somehow */ + if (test_bit(ASSOC_FLAG_MODE, &assoc_req->flags)) { + if (assoc_req->mode != IW_MODE_ADHOC) + return 1; + } + + if (test_bit(ASSOC_FLAG_CHANNEL, &assoc_req->flags)) { + if (assoc_req->channel != priv->curbssparams.channel) + return 1; + } + + lbs_deb_leave(LBS_DEB_ASSOC); + return 0; +} + + +void lbs_association_worker(struct work_struct *work) +{ + struct lbs_private *priv = container_of(work, struct lbs_private, + assoc_work.work); + struct assoc_request * assoc_req = NULL; + int ret = 0; + int find_any_ssid = 0; + DECLARE_MAC_BUF(mac); + + lbs_deb_enter(LBS_DEB_ASSOC); + + mutex_lock(&priv->lock); + assoc_req = priv->pending_assoc_req; + priv->pending_assoc_req = NULL; + priv->in_progress_assoc_req = assoc_req; + mutex_unlock(&priv->lock); + + if (!assoc_req) + goto done; + + lbs_deb_assoc( + "Association Request:\n" + " flags: 0x%08lx\n" + " SSID: '%s'\n" + " chann: %d\n" + " band: %d\n" + " mode: %d\n" + " BSSID: %s\n" + " secinfo: %s%s%s\n" + " auth_mode: %d\n", + assoc_req->flags, + escape_essid(assoc_req->ssid, assoc_req->ssid_len), + assoc_req->channel, assoc_req->band, assoc_req->mode, + print_mac(mac, assoc_req->bssid), + assoc_req->secinfo.WPAenabled ? " WPA" : "", + assoc_req->secinfo.WPA2enabled ? " WPA2" : "", + assoc_req->secinfo.wep_enabled ? " WEP" : "", + assoc_req->secinfo.auth_mode); + + /* If 'any' SSID was specified, find an SSID to associate with */ + if (test_bit(ASSOC_FLAG_SSID, &assoc_req->flags) + && !assoc_req->ssid_len) + find_any_ssid = 1; + + /* But don't use 'any' SSID if there's a valid locked BSSID to use */ + if (test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)) { + if (compare_ether_addr(assoc_req->bssid, bssid_any) + && compare_ether_addr(assoc_req->bssid, bssid_off)) + find_any_ssid = 0; + } + + if (find_any_ssid) { + u8 new_mode; + + ret = lbs_find_best_network_ssid(priv, assoc_req->ssid, + &assoc_req->ssid_len, assoc_req->mode, &new_mode); + if (ret) { + lbs_deb_assoc("Could not find best network\n"); + ret = -ENETUNREACH; + goto out; + } + + /* Ensure we switch to the mode of the AP */ + if (assoc_req->mode == IW_MODE_AUTO) { + set_bit(ASSOC_FLAG_MODE, &assoc_req->flags); + assoc_req->mode = new_mode; + } + } + + /* + * Check if the attributes being changing require deauthentication + * from the currently associated infrastructure access point. + */ + if (priv->mode == IW_MODE_INFRA) { + if (should_deauth_infrastructure(priv, assoc_req)) { + ret = lbs_send_deauthentication(priv); + if (ret) { + lbs_deb_assoc("Deauthentication due to new " + "configuration request failed: %d\n", + ret); + } + } + } else if (priv->mode == IW_MODE_ADHOC) { + if (should_stop_adhoc(priv, assoc_req)) { + ret = lbs_stop_adhoc_network(priv); + if (ret) { + lbs_deb_assoc("Teardown of AdHoc network due to " + "new configuration request failed: %d\n", + ret); + } + + } + } + + /* Send the various configuration bits to the firmware */ + if (test_bit(ASSOC_FLAG_MODE, &assoc_req->flags)) { + ret = assoc_helper_mode(priv, assoc_req); + if (ret) + goto out; + } + + if (test_bit(ASSOC_FLAG_CHANNEL, &assoc_req->flags)) { + ret = assoc_helper_channel(priv, assoc_req); + if (ret) + goto out; + } + + if ( test_bit(ASSOC_FLAG_WEP_KEYS, &assoc_req->flags) + || test_bit(ASSOC_FLAG_WEP_TX_KEYIDX, &assoc_req->flags)) { + ret = assoc_helper_wep_keys(priv, assoc_req); + if (ret) + goto out; + } + + if (test_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags)) { + ret = assoc_helper_secinfo(priv, assoc_req); + if (ret) + goto out; + } + + if (test_bit(ASSOC_FLAG_WPA_IE, &assoc_req->flags)) { + ret = assoc_helper_wpa_ie(priv, assoc_req); + if (ret) + goto out; + } + + if (test_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags) + || test_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags)) { + ret = assoc_helper_wpa_keys(priv, assoc_req); + if (ret) + goto out; + } + + /* SSID/BSSID should be the _last_ config option set, because they + * trigger the association attempt. + */ + if (test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags) + || test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)) { + int success = 1; + + ret = assoc_helper_associate(priv, assoc_req); + if (ret) { + lbs_deb_assoc("ASSOC: association unsuccessful: %d\n", + ret); + success = 0; + } + + if (priv->connect_status != LBS_CONNECTED) { + lbs_deb_assoc("ASSOC: association unsuccessful, " + "not connected\n"); + success = 0; + } + + if (success) { + lbs_deb_assoc("ASSOC: associated to '%s', %s\n", + escape_essid(priv->curbssparams.ssid, + priv->curbssparams.ssid_len), + print_mac(mac, priv->curbssparams.bssid)); + lbs_prepare_and_send_command(priv, + CMD_802_11_RSSI, + 0, CMD_OPTION_WAITFORRSP, 0, NULL); + + lbs_prepare_and_send_command(priv, + CMD_802_11_GET_LOG, + 0, CMD_OPTION_WAITFORRSP, 0, NULL); + } else { + ret = -1; + } + } + +out: + if (ret) { + lbs_deb_assoc("ASSOC: reconfiguration attempt unsuccessful: %d\n", + ret); + } + + mutex_lock(&priv->lock); + priv->in_progress_assoc_req = NULL; + mutex_unlock(&priv->lock); + kfree(assoc_req); + +done: + lbs_deb_leave(LBS_DEB_ASSOC); +} + + +/* + * Caller MUST hold any necessary locks + */ +struct assoc_request *lbs_get_association_request(struct lbs_private *priv) +{ + struct assoc_request * assoc_req; + + lbs_deb_enter(LBS_DEB_ASSOC); + if (!priv->pending_assoc_req) { + priv->pending_assoc_req = kzalloc(sizeof(struct assoc_request), + GFP_KERNEL); + if (!priv->pending_assoc_req) { + lbs_pr_info("Not enough memory to allocate association" + " request!\n"); + return NULL; + } + } + + /* Copy current configuration attributes to the association request, + * but don't overwrite any that are already set. + */ + assoc_req = priv->pending_assoc_req; + if (!test_bit(ASSOC_FLAG_SSID, &assoc_req->flags)) { + memcpy(&assoc_req->ssid, &priv->curbssparams.ssid, + IW_ESSID_MAX_SIZE); + assoc_req->ssid_len = priv->curbssparams.ssid_len; + } + + if (!test_bit(ASSOC_FLAG_CHANNEL, &assoc_req->flags)) + assoc_req->channel = priv->curbssparams.channel; + + if (!test_bit(ASSOC_FLAG_BAND, &assoc_req->flags)) + assoc_req->band = priv->curbssparams.band; + + if (!test_bit(ASSOC_FLAG_MODE, &assoc_req->flags)) + assoc_req->mode = priv->mode; + + if (!test_bit(ASSOC_FLAG_BSSID, &assoc_req->flags)) { + memcpy(&assoc_req->bssid, priv->curbssparams.bssid, + ETH_ALEN); + } + + if (!test_bit(ASSOC_FLAG_WEP_KEYS, &assoc_req->flags)) { + int i; + for (i = 0; i < 4; i++) { + memcpy(&assoc_req->wep_keys[i], &priv->wep_keys[i], + sizeof(struct enc_key)); + } + } + + if (!test_bit(ASSOC_FLAG_WEP_TX_KEYIDX, &assoc_req->flags)) + assoc_req->wep_tx_keyidx = priv->wep_tx_keyidx; + + if (!test_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags)) { + memcpy(&assoc_req->wpa_mcast_key, &priv->wpa_mcast_key, + sizeof(struct enc_key)); + } + + if (!test_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags)) { + memcpy(&assoc_req->wpa_unicast_key, &priv->wpa_unicast_key, + sizeof(struct enc_key)); + } + + if (!test_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags)) { + memcpy(&assoc_req->secinfo, &priv->secinfo, + sizeof(struct lbs_802_11_security)); + } + + if (!test_bit(ASSOC_FLAG_WPA_IE, &assoc_req->flags)) { + memcpy(&assoc_req->wpa_ie, &priv->wpa_ie, + MAX_WPA_IE_LEN); + assoc_req->wpa_ie_len = priv->wpa_ie_len; + } + + lbs_deb_leave(LBS_DEB_ASSOC); + return assoc_req; +} diff --git a/package/libertas/src/assoc.h b/package/libertas/src/assoc.h new file mode 100644 index 0000000000..08372bbf37 --- /dev/null +++ b/package/libertas/src/assoc.h @@ -0,0 +1,12 @@ +/* Copyright (C) 2006, Red Hat, Inc. */ + +#ifndef _LBS_ASSOC_H_ +#define _LBS_ASSOC_H_ + +#include "dev.h" + +void lbs_association_worker(struct work_struct *work); +struct assoc_request *lbs_get_association_request(struct lbs_private *priv); +void lbs_sync_channel(struct work_struct *work); + +#endif /* _LBS_ASSOC_H */ diff --git a/package/libertas/src/cmd.c b/package/libertas/src/cmd.c new file mode 100644 index 0000000000..01d23493b4 --- /dev/null +++ b/package/libertas/src/cmd.c @@ -0,0 +1,2233 @@ +/** + * This file contains the handling of command. + * It prepares command and sends it to firmware when it is ready. + */ + +#include <net/iw_handler.h> +#include "host.h" +#include "hostcmd.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "join.h" +#include "wext.h" +#include "cmd.h" + +static void cleanup_cmdnode(struct cmd_ctrl_node *ptempnode); +static struct cmd_ctrl_node *lbs_get_cmd_ctrl_node(struct lbs_private *priv); +static void lbs_set_cmd_ctrl_node(struct lbs_private *priv, + struct cmd_ctrl_node *ptempnode, + u16 wait_option, void *pdata_buf); + + +/** + * @brief Checks whether a command is allowed in Power Save mode + * + * @param command the command ID + * @return 1 if allowed, 0 if not allowed + */ +static u8 is_command_allowed_in_ps(u16 cmd) +{ + switch (cmd) { + case CMD_802_11_RSSI: + return 1; + default: + break; + } + return 0; +} + +/** + * @brief Updates the hardware details like MAC address and regulatory region + * + * @param priv A pointer to struct lbs_private structure + * + * @return 0 on success, error on failure + */ +int lbs_update_hw_spec(struct lbs_private *priv) +{ + struct cmd_ds_get_hw_spec cmd; + int ret = -1; + u32 i; + DECLARE_MAC_BUF(mac); + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + memcpy(cmd.permanentaddr, priv->current_addr, ETH_ALEN); + ret = lbs_cmd_with_response(priv, CMD_GET_HW_SPEC, cmd); + if (ret) + goto out; + + priv->fwcapinfo = le32_to_cpu(cmd.fwcapinfo); + memcpy(priv->fwreleasenumber, cmd.fwreleasenumber, 4); + + lbs_deb_cmd("GET_HW_SPEC: firmware release %u.%u.%up%u\n", + priv->fwreleasenumber[2], priv->fwreleasenumber[1], + priv->fwreleasenumber[0], priv->fwreleasenumber[3]); + lbs_deb_cmd("GET_HW_SPEC: MAC addr %s\n", + print_mac(mac, cmd.permanentaddr)); + lbs_deb_cmd("GET_HW_SPEC: hardware interface 0x%x, hardware spec 0x%04x\n", + cmd.hwifversion, cmd.version); + + /* Clamp region code to 8-bit since FW spec indicates that it should + * only ever be 8-bit, even though the field size is 16-bit. Some firmware + * returns non-zero high 8 bits here. + */ + priv->regioncode = le16_to_cpu(cmd.regioncode) & 0xFF; + + for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) { + /* use the region code to search for the index */ + if (priv->regioncode == lbs_region_code_to_index[i]) + break; + } + + /* if it's unidentified region code, use the default (USA) */ + if (i >= MRVDRV_MAX_REGION_CODE) { + priv->regioncode = 0x10; + lbs_pr_info("unidentified region code; using the default (USA)\n"); + } + + if (priv->current_addr[0] == 0xff) + memmove(priv->current_addr, cmd.permanentaddr, ETH_ALEN); + + memcpy(priv->dev->dev_addr, priv->current_addr, ETH_ALEN); + if (priv->mesh_dev) + memcpy(priv->mesh_dev->dev_addr, priv->current_addr, ETH_ALEN); + + if (lbs_set_regiontable(priv, priv->regioncode, 0)) { + ret = -1; + goto out; + } + + if (lbs_set_universaltable(priv, 0)) { + ret = -1; + goto out; + } + +out: + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} + +static int lbs_cmd_802_11_ps_mode(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action) +{ + struct cmd_ds_802_11_ps_mode *psm = &cmd->params.psmode; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->command = cpu_to_le16(CMD_802_11_PS_MODE); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_ps_mode) + + S_DS_GEN); + psm->action = cpu_to_le16(cmd_action); + psm->multipledtim = 0; + switch (cmd_action) { + case CMD_SUBCMD_ENTER_PS: + lbs_deb_cmd("PS command:" "SubCode- Enter PS\n"); + + psm->locallisteninterval = 0; + psm->nullpktinterval = 0; + psm->multipledtim = + cpu_to_le16(MRVDRV_DEFAULT_MULTIPLE_DTIM); + break; + + case CMD_SUBCMD_EXIT_PS: + lbs_deb_cmd("PS command:" "SubCode- Exit PS\n"); + break; + + case CMD_SUBCMD_SLEEP_CONFIRMED: + lbs_deb_cmd("PS command: SubCode- sleep confirm\n"); + break; + + default: + break; + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_inactivity_timeout(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action, void *pdata_buf) +{ + u16 *timeout = pdata_buf; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->command = cpu_to_le16(CMD_802_11_INACTIVITY_TIMEOUT); + cmd->size = + cpu_to_le16(sizeof(struct cmd_ds_802_11_inactivity_timeout) + + S_DS_GEN); + + cmd->params.inactivity_timeout.action = cpu_to_le16(cmd_action); + + if (cmd_action) + cmd->params.inactivity_timeout.timeout = cpu_to_le16(*timeout); + else + cmd->params.inactivity_timeout.timeout = 0; + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_sleep_params(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action) +{ + struct cmd_ds_802_11_sleep_params *sp = &cmd->params.sleep_params; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->size = cpu_to_le16((sizeof(struct cmd_ds_802_11_sleep_params)) + + S_DS_GEN); + cmd->command = cpu_to_le16(CMD_802_11_SLEEP_PARAMS); + + if (cmd_action == CMD_ACT_GET) { + memset(&priv->sp, 0, sizeof(struct sleep_params)); + memset(sp, 0, sizeof(struct cmd_ds_802_11_sleep_params)); + sp->action = cpu_to_le16(cmd_action); + } else if (cmd_action == CMD_ACT_SET) { + sp->action = cpu_to_le16(cmd_action); + sp->error = cpu_to_le16(priv->sp.sp_error); + sp->offset = cpu_to_le16(priv->sp.sp_offset); + sp->stabletime = cpu_to_le16(priv->sp.sp_stabletime); + sp->calcontrol = (u8) priv->sp.sp_calcontrol; + sp->externalsleepclk = (u8) priv->sp.sp_extsleepclk; + sp->reserved = cpu_to_le16(priv->sp.sp_reserved); + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_set_wep(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u32 cmd_act, + void * pdata_buf) +{ + struct cmd_ds_802_11_set_wep *wep = &cmd->params.wep; + int ret = 0; + struct assoc_request * assoc_req = pdata_buf; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->command = cpu_to_le16(CMD_802_11_SET_WEP); + cmd->size = cpu_to_le16(sizeof(*wep) + S_DS_GEN); + + if (cmd_act == CMD_ACT_ADD) { + int i; + + if (!assoc_req) { + lbs_deb_cmd("Invalid association request!"); + ret = -1; + goto done; + } + + wep->action = cpu_to_le16(CMD_ACT_ADD); + + /* default tx key index */ + wep->keyindex = cpu_to_le16((u16)(assoc_req->wep_tx_keyidx & + (u32)CMD_WEP_KEY_INDEX_MASK)); + + /* Copy key types and material to host command structure */ + for (i = 0; i < 4; i++) { + struct enc_key * pkey = &assoc_req->wep_keys[i]; + + switch (pkey->len) { + case KEY_LEN_WEP_40: + wep->keytype[i] = CMD_TYPE_WEP_40_BIT; + memmove(&wep->keymaterial[i], pkey->key, + pkey->len); + lbs_deb_cmd("SET_WEP: add key %d (40 bit)\n", i); + break; + case KEY_LEN_WEP_104: + wep->keytype[i] = CMD_TYPE_WEP_104_BIT; + memmove(&wep->keymaterial[i], pkey->key, + pkey->len); + lbs_deb_cmd("SET_WEP: add key %d (104 bit)\n", i); + break; + case 0: + break; + default: + lbs_deb_cmd("SET_WEP: invalid key %d, length %d\n", + i, pkey->len); + ret = -1; + goto done; + break; + } + } + } else if (cmd_act == CMD_ACT_REMOVE) { + /* ACT_REMOVE clears _all_ WEP keys */ + wep->action = cpu_to_le16(CMD_ACT_REMOVE); + + /* default tx key index */ + wep->keyindex = cpu_to_le16((u16)(priv->wep_tx_keyidx & + (u32)CMD_WEP_KEY_INDEX_MASK)); + lbs_deb_cmd("SET_WEP: remove key %d\n", priv->wep_tx_keyidx); + } + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +static int lbs_cmd_802_11_enable_rsn(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action, + void * pdata_buf) +{ + struct cmd_ds_802_11_enable_rsn *penableRSN = &cmd->params.enbrsn; + u32 * enable = pdata_buf; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->command = cpu_to_le16(CMD_802_11_ENABLE_RSN); + cmd->size = cpu_to_le16(sizeof(*penableRSN) + S_DS_GEN); + penableRSN->action = cpu_to_le16(cmd_action); + + if (cmd_action == CMD_ACT_SET) { + if (*enable) + penableRSN->enable = cpu_to_le16(CMD_ENABLE_RSN); + else + penableRSN->enable = cpu_to_le16(CMD_DISABLE_RSN); + lbs_deb_cmd("ENABLE_RSN: %d\n", *enable); + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + + +static ssize_t lbs_tlv_size(const u8 *tlv, u16 size) +{ + ssize_t pos = 0; + struct mrvlietypesheader *tlv_h; + while (pos < size) { + u16 length; + tlv_h = (struct mrvlietypesheader *) tlv; + if (tlv_h->len == 0) + return pos; + length = le16_to_cpu(tlv_h->len) + + sizeof(struct mrvlietypesheader); + pos += length; + tlv += length; + } + return pos; +} + + +static void lbs_cmd_802_11_subscribe_event(struct lbs_private *priv, + struct cmd_ds_command *cmd, u16 cmd_action, + void *pdata_buf) +{ + struct cmd_ds_802_11_subscribe_event *events = + (struct cmd_ds_802_11_subscribe_event *) pdata_buf; + + /* pdata_buf points to a struct cmd_ds_802_11_subscribe_event and room + * for various Marvell TLVs */ + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->size = cpu_to_le16(sizeof(*events) + - sizeof(events->tlv) + + S_DS_GEN); + cmd->params.subscribe_event.action = cpu_to_le16(cmd_action); + if (cmd_action == CMD_ACT_GET) { + cmd->params.subscribe_event.events = 0; + } else { + ssize_t sz = lbs_tlv_size(events->tlv, sizeof(events->tlv)); + cmd->size = cpu_to_le16(le16_to_cpu(cmd->size) + sz); + cmd->params.subscribe_event.events = events->events; + memcpy(cmd->params.subscribe_event.tlv, events->tlv, sz); + } + + lbs_deb_leave(LBS_DEB_CMD); +} + +static void set_one_wpa_key(struct MrvlIEtype_keyParamSet * pkeyparamset, + struct enc_key * pkey) +{ + lbs_deb_enter(LBS_DEB_CMD); + + if (pkey->flags & KEY_INFO_WPA_ENABLED) { + pkeyparamset->keyinfo |= cpu_to_le16(KEY_INFO_WPA_ENABLED); + } + if (pkey->flags & KEY_INFO_WPA_UNICAST) { + pkeyparamset->keyinfo |= cpu_to_le16(KEY_INFO_WPA_UNICAST); + } + if (pkey->flags & KEY_INFO_WPA_MCAST) { + pkeyparamset->keyinfo |= cpu_to_le16(KEY_INFO_WPA_MCAST); + } + + pkeyparamset->type = cpu_to_le16(TLV_TYPE_KEY_MATERIAL); + pkeyparamset->keytypeid = cpu_to_le16(pkey->type); + pkeyparamset->keylen = cpu_to_le16(pkey->len); + memcpy(pkeyparamset->key, pkey->key, pkey->len); + pkeyparamset->length = cpu_to_le16( sizeof(pkeyparamset->keytypeid) + + sizeof(pkeyparamset->keyinfo) + + sizeof(pkeyparamset->keylen) + + sizeof(pkeyparamset->key)); + lbs_deb_leave(LBS_DEB_CMD); +} + +static int lbs_cmd_802_11_key_material(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action, + u32 cmd_oid, void *pdata_buf) +{ + struct cmd_ds_802_11_key_material *pkeymaterial = + &cmd->params.keymaterial; + struct assoc_request * assoc_req = pdata_buf; + int ret = 0; + int index = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->command = cpu_to_le16(CMD_802_11_KEY_MATERIAL); + pkeymaterial->action = cpu_to_le16(cmd_action); + + if (cmd_action == CMD_ACT_GET) { + cmd->size = cpu_to_le16(S_DS_GEN + sizeof (pkeymaterial->action)); + ret = 0; + goto done; + } + + memset(&pkeymaterial->keyParamSet, 0, sizeof(pkeymaterial->keyParamSet)); + + if (test_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags)) { + set_one_wpa_key(&pkeymaterial->keyParamSet[index], + &assoc_req->wpa_unicast_key); + index++; + } + + if (test_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags)) { + set_one_wpa_key(&pkeymaterial->keyParamSet[index], + &assoc_req->wpa_mcast_key); + index++; + } + + cmd->size = cpu_to_le16( S_DS_GEN + + sizeof (pkeymaterial->action) + + (index * sizeof(struct MrvlIEtype_keyParamSet))); + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +static int lbs_cmd_802_11_reset(struct lbs_private *priv, + struct cmd_ds_command *cmd, int cmd_action) +{ + struct cmd_ds_802_11_reset *reset = &cmd->params.reset; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->command = cpu_to_le16(CMD_802_11_RESET); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_reset) + S_DS_GEN); + reset->action = cpu_to_le16(cmd_action); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_get_log(struct lbs_private *priv, + struct cmd_ds_command *cmd) +{ + lbs_deb_enter(LBS_DEB_CMD); + cmd->command = cpu_to_le16(CMD_802_11_GET_LOG); + cmd->size = + cpu_to_le16(sizeof(struct cmd_ds_802_11_get_log) + S_DS_GEN); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_get_stat(struct lbs_private *priv, + struct cmd_ds_command *cmd) +{ + lbs_deb_enter(LBS_DEB_CMD); + cmd->command = cpu_to_le16(CMD_802_11_GET_STAT); + cmd->size = + cpu_to_le16(sizeof(struct cmd_ds_802_11_get_stat) + S_DS_GEN); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_snmp_mib(struct lbs_private *priv, + struct cmd_ds_command *cmd, + int cmd_action, + int cmd_oid, void *pdata_buf) +{ + struct cmd_ds_802_11_snmp_mib *pSNMPMIB = &cmd->params.smib; + u8 ucTemp; + + lbs_deb_enter(LBS_DEB_CMD); + + lbs_deb_cmd("SNMP_CMD: cmd_oid = 0x%x\n", cmd_oid); + + cmd->command = cpu_to_le16(CMD_802_11_SNMP_MIB); + cmd->size = cpu_to_le16(sizeof(*pSNMPMIB) + S_DS_GEN); + + switch (cmd_oid) { + case OID_802_11_INFRASTRUCTURE_MODE: + { + u8 mode = (u8) (size_t) pdata_buf; + pSNMPMIB->querytype = cpu_to_le16(CMD_ACT_SET); + pSNMPMIB->oid = cpu_to_le16((u16) DESIRED_BSSTYPE_I); + pSNMPMIB->bufsize = cpu_to_le16(sizeof(u8)); + if (mode == IW_MODE_ADHOC) { + ucTemp = SNMP_MIB_VALUE_ADHOC; + } else { + /* Infra and Auto modes */ + ucTemp = SNMP_MIB_VALUE_INFRA; + } + + memmove(pSNMPMIB->value, &ucTemp, sizeof(u8)); + + break; + } + + case OID_802_11D_ENABLE: + { + u32 ulTemp; + + pSNMPMIB->oid = cpu_to_le16((u16) DOT11D_I); + + if (cmd_action == CMD_ACT_SET) { + pSNMPMIB->querytype = cpu_to_le16(CMD_ACT_SET); + pSNMPMIB->bufsize = cpu_to_le16(sizeof(u16)); + ulTemp = *(u32 *)pdata_buf; + *((__le16 *)(pSNMPMIB->value)) = + cpu_to_le16((u16) ulTemp); + } + break; + } + + case OID_802_11_FRAGMENTATION_THRESHOLD: + { + u32 ulTemp; + + pSNMPMIB->oid = cpu_to_le16((u16) FRAGTHRESH_I); + + if (cmd_action == CMD_ACT_GET) { + pSNMPMIB->querytype = cpu_to_le16(CMD_ACT_GET); + } else if (cmd_action == CMD_ACT_SET) { + pSNMPMIB->querytype = cpu_to_le16(CMD_ACT_SET); + pSNMPMIB->bufsize = cpu_to_le16(sizeof(u16)); + ulTemp = *((u32 *) pdata_buf); + *((__le16 *)(pSNMPMIB->value)) = + cpu_to_le16((u16) ulTemp); + + } + + break; + } + + case OID_802_11_RTS_THRESHOLD: + { + + u32 ulTemp; + pSNMPMIB->oid = cpu_to_le16(RTSTHRESH_I); + + if (cmd_action == CMD_ACT_GET) { + pSNMPMIB->querytype = cpu_to_le16(CMD_ACT_GET); + } else if (cmd_action == CMD_ACT_SET) { + pSNMPMIB->querytype = cpu_to_le16(CMD_ACT_SET); + pSNMPMIB->bufsize = cpu_to_le16(sizeof(u16)); + ulTemp = *((u32 *)pdata_buf); + *(__le16 *)(pSNMPMIB->value) = + cpu_to_le16((u16) ulTemp); + + } + break; + } + case OID_802_11_TX_RETRYCOUNT: + pSNMPMIB->oid = cpu_to_le16((u16) SHORT_RETRYLIM_I); + + if (cmd_action == CMD_ACT_GET) { + pSNMPMIB->querytype = cpu_to_le16(CMD_ACT_GET); + } else if (cmd_action == CMD_ACT_SET) { + pSNMPMIB->querytype = cpu_to_le16(CMD_ACT_SET); + pSNMPMIB->bufsize = cpu_to_le16(sizeof(u16)); + *((__le16 *)(pSNMPMIB->value)) = + cpu_to_le16((u16) priv->txretrycount); + } + + break; + default: + break; + } + + lbs_deb_cmd( + "SNMP_CMD: command=0x%x, size=0x%x, seqnum=0x%x, result=0x%x\n", + le16_to_cpu(cmd->command), le16_to_cpu(cmd->size), + le16_to_cpu(cmd->seqnum), le16_to_cpu(cmd->result)); + + lbs_deb_cmd( + "SNMP_CMD: action 0x%x, oid 0x%x, oidsize 0x%x, value 0x%x\n", + le16_to_cpu(pSNMPMIB->querytype), le16_to_cpu(pSNMPMIB->oid), + le16_to_cpu(pSNMPMIB->bufsize), + le16_to_cpu(*(__le16 *) pSNMPMIB->value)); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_radio_control(struct lbs_private *priv, + struct cmd_ds_command *cmd, + int cmd_action) +{ + struct cmd_ds_802_11_radio_control *pradiocontrol = &cmd->params.radio; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->size = + cpu_to_le16((sizeof(struct cmd_ds_802_11_radio_control)) + + S_DS_GEN); + cmd->command = cpu_to_le16(CMD_802_11_RADIO_CONTROL); + + pradiocontrol->action = cpu_to_le16(cmd_action); + + switch (priv->preamble) { + case CMD_TYPE_SHORT_PREAMBLE: + pradiocontrol->control = cpu_to_le16(SET_SHORT_PREAMBLE); + break; + + case CMD_TYPE_LONG_PREAMBLE: + pradiocontrol->control = cpu_to_le16(SET_LONG_PREAMBLE); + break; + + case CMD_TYPE_AUTO_PREAMBLE: + default: + pradiocontrol->control = cpu_to_le16(SET_AUTO_PREAMBLE); + break; + } + + if (priv->radioon) + pradiocontrol->control |= cpu_to_le16(TURN_ON_RF); + else + pradiocontrol->control &= cpu_to_le16(~TURN_ON_RF); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_rf_tx_power(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action, void *pdata_buf) +{ + + struct cmd_ds_802_11_rf_tx_power *prtp = &cmd->params.txp; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->size = + cpu_to_le16((sizeof(struct cmd_ds_802_11_rf_tx_power)) + S_DS_GEN); + cmd->command = cpu_to_le16(CMD_802_11_RF_TX_POWER); + prtp->action = cpu_to_le16(cmd_action); + + lbs_deb_cmd("RF_TX_POWER_CMD: size:%d cmd:0x%x Act:%d\n", + le16_to_cpu(cmd->size), le16_to_cpu(cmd->command), + le16_to_cpu(prtp->action)); + + switch (cmd_action) { + case CMD_ACT_TX_POWER_OPT_GET: + prtp->action = cpu_to_le16(CMD_ACT_GET); + prtp->currentlevel = 0; + break; + + case CMD_ACT_TX_POWER_OPT_SET_HIGH: + prtp->action = cpu_to_le16(CMD_ACT_SET); + prtp->currentlevel = cpu_to_le16(CMD_ACT_TX_POWER_INDEX_HIGH); + break; + + case CMD_ACT_TX_POWER_OPT_SET_MID: + prtp->action = cpu_to_le16(CMD_ACT_SET); + prtp->currentlevel = cpu_to_le16(CMD_ACT_TX_POWER_INDEX_MID); + break; + + case CMD_ACT_TX_POWER_OPT_SET_LOW: + prtp->action = cpu_to_le16(CMD_ACT_SET); + prtp->currentlevel = cpu_to_le16(*((u16 *) pdata_buf)); + break; + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_monitor_mode(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action, void *pdata_buf) +{ + struct cmd_ds_802_11_monitor_mode *monitor = &cmd->params.monitor; + + cmd->command = cpu_to_le16(CMD_802_11_MONITOR_MODE); + cmd->size = + cpu_to_le16(sizeof(struct cmd_ds_802_11_monitor_mode) + + S_DS_GEN); + + monitor->action = cpu_to_le16(cmd_action); + if (cmd_action == CMD_ACT_SET) { + monitor->mode = + cpu_to_le16((u16) (*(u32 *) pdata_buf)); + } + + return 0; +} + +static int lbs_cmd_802_11_rate_adapt_rateset(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action) +{ + struct cmd_ds_802_11_rate_adapt_rateset + *rateadapt = &cmd->params.rateset; + + lbs_deb_enter(LBS_DEB_CMD); + cmd->size = + cpu_to_le16(sizeof(struct cmd_ds_802_11_rate_adapt_rateset) + + S_DS_GEN); + cmd->command = cpu_to_le16(CMD_802_11_RATE_ADAPT_RATESET); + + rateadapt->action = cpu_to_le16(cmd_action); + rateadapt->enablehwauto = cpu_to_le16(priv->enablehwauto); + rateadapt->bitmap = cpu_to_le16(priv->ratebitmap); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +/** + * @brief Get the current data rate + * + * @param priv A pointer to struct lbs_private structure + * + * @return The data rate on success, error on failure + */ +int lbs_get_data_rate(struct lbs_private *priv) +{ + struct cmd_ds_802_11_data_rate cmd; + int ret = -1; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_GET_TX_RATE); + + ret = lbs_cmd_with_response(priv, CMD_802_11_DATA_RATE, cmd); + if (ret) + goto out; + + lbs_deb_hex(LBS_DEB_CMD, "DATA_RATE_RESP", (u8 *) &cmd, sizeof (cmd)); + + ret = (int) lbs_fw_index_to_data_rate(cmd.rates[0]); + lbs_deb_cmd("DATA_RATE: current rate 0x%02x\n", ret); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +/** + * @brief Set the data rate + * + * @param priv A pointer to struct lbs_private structure + * @param rate The desired data rate, or 0 to clear a locked rate + * + * @return 0 on success, error on failure + */ +int lbs_set_data_rate(struct lbs_private *priv, u8 rate) +{ + struct cmd_ds_802_11_data_rate cmd; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + + if (rate > 0) { + cmd.action = cpu_to_le16(CMD_ACT_SET_TX_FIX_RATE); + cmd.rates[0] = lbs_data_rate_to_fw_index(rate); + if (cmd.rates[0] == 0) { + lbs_deb_cmd("DATA_RATE: invalid requested rate of" + " 0x%02X\n", rate); + ret = 0; + goto out; + } + lbs_deb_cmd("DATA_RATE: set fixed 0x%02X\n", cmd.rates[0]); + } else { + cmd.action = cpu_to_le16(CMD_ACT_SET_TX_AUTO); + lbs_deb_cmd("DATA_RATE: setting auto\n"); + } + + ret = lbs_cmd_with_response(priv, CMD_802_11_DATA_RATE, cmd); + if (ret) + goto out; + + lbs_deb_hex(LBS_DEB_CMD, "DATA_RATE_RESP", (u8 *) &cmd, sizeof (cmd)); + + /* FIXME: get actual rates FW can do if this command actually returns + * all data rates supported. + */ + priv->cur_rate = lbs_fw_index_to_data_rate(cmd.rates[0]); + lbs_deb_cmd("DATA_RATE: current rate is 0x%02x\n", priv->cur_rate); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +static int lbs_cmd_mac_multicast_adr(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action) +{ + struct cmd_ds_mac_multicast_adr *pMCastAdr = &cmd->params.madr; + + lbs_deb_enter(LBS_DEB_CMD); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_mac_multicast_adr) + + S_DS_GEN); + cmd->command = cpu_to_le16(CMD_MAC_MULTICAST_ADR); + + lbs_deb_cmd("MULTICAST_ADR: setting %d addresses\n", pMCastAdr->nr_of_adrs); + pMCastAdr->action = cpu_to_le16(cmd_action); + pMCastAdr->nr_of_adrs = + cpu_to_le16((u16) priv->nr_of_multicastmacaddr); + memcpy(pMCastAdr->maclist, priv->multicastlist, + priv->nr_of_multicastmacaddr * ETH_ALEN); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +/** + * @brief Get the radio channel + * + * @param priv A pointer to struct lbs_private structure + * + * @return The channel on success, error on failure + */ +int lbs_get_channel(struct lbs_private *priv) +{ + struct cmd_ds_802_11_rf_channel cmd; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_GET); + + ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, cmd); + if (ret) + goto out; + + ret = le16_to_cpu(cmd.channel); + lbs_deb_cmd("current radio channel is %d\n", ret); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +/** + * @brief Set the radio channel + * + * @param priv A pointer to struct lbs_private structure + * @param channel The desired channel, or 0 to clear a locked channel + * + * @return 0 on success, error on failure + */ +int lbs_set_channel(struct lbs_private *priv, u8 channel) +{ + struct cmd_ds_802_11_rf_channel cmd; + u8 old_channel = priv->curbssparams.channel; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_SET); + cmd.channel = cpu_to_le16(channel); + + ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, cmd); + if (ret) + goto out; + + priv->curbssparams.channel = (uint8_t) le16_to_cpu(cmd.channel); + lbs_deb_cmd("channel switch from %d to %d\n", old_channel, + priv->curbssparams.channel); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +static int lbs_cmd_802_11_rssi(struct lbs_private *priv, + struct cmd_ds_command *cmd) +{ + + lbs_deb_enter(LBS_DEB_CMD); + cmd->command = cpu_to_le16(CMD_802_11_RSSI); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_rssi) + S_DS_GEN); + cmd->params.rssi.N = cpu_to_le16(DEFAULT_BCN_AVG_FACTOR); + + /* reset Beacon SNR/NF/RSSI values */ + priv->SNR[TYPE_BEACON][TYPE_NOAVG] = 0; + priv->SNR[TYPE_BEACON][TYPE_AVG] = 0; + priv->NF[TYPE_BEACON][TYPE_NOAVG] = 0; + priv->NF[TYPE_BEACON][TYPE_AVG] = 0; + priv->RSSI[TYPE_BEACON][TYPE_NOAVG] = 0; + priv->RSSI[TYPE_BEACON][TYPE_AVG] = 0; + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_reg_access(struct lbs_private *priv, + struct cmd_ds_command *cmdptr, + u8 cmd_action, void *pdata_buf) +{ + struct lbs_offset_value *offval; + + lbs_deb_enter(LBS_DEB_CMD); + + offval = (struct lbs_offset_value *)pdata_buf; + + switch (le16_to_cpu(cmdptr->command)) { + case CMD_MAC_REG_ACCESS: + { + struct cmd_ds_mac_reg_access *macreg; + + cmdptr->size = + cpu_to_le16(sizeof (struct cmd_ds_mac_reg_access) + + S_DS_GEN); + macreg = + (struct cmd_ds_mac_reg_access *)&cmdptr->params. + macreg; + + macreg->action = cpu_to_le16(cmd_action); + macreg->offset = cpu_to_le16((u16) offval->offset); + macreg->value = cpu_to_le32(offval->value); + + break; + } + + case CMD_BBP_REG_ACCESS: + { + struct cmd_ds_bbp_reg_access *bbpreg; + + cmdptr->size = + cpu_to_le16(sizeof + (struct cmd_ds_bbp_reg_access) + + S_DS_GEN); + bbpreg = + (struct cmd_ds_bbp_reg_access *)&cmdptr->params. + bbpreg; + + bbpreg->action = cpu_to_le16(cmd_action); + bbpreg->offset = cpu_to_le16((u16) offval->offset); + bbpreg->value = (u8) offval->value; + + break; + } + + case CMD_RF_REG_ACCESS: + { + struct cmd_ds_rf_reg_access *rfreg; + + cmdptr->size = + cpu_to_le16(sizeof + (struct cmd_ds_rf_reg_access) + + S_DS_GEN); + rfreg = + (struct cmd_ds_rf_reg_access *)&cmdptr->params. + rfreg; + + rfreg->action = cpu_to_le16(cmd_action); + rfreg->offset = cpu_to_le16((u16) offval->offset); + rfreg->value = (u8) offval->value; + + break; + } + + default: + break; + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_mac_address(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action) +{ + + lbs_deb_enter(LBS_DEB_CMD); + cmd->command = cpu_to_le16(CMD_802_11_MAC_ADDRESS); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_mac_address) + + S_DS_GEN); + cmd->result = 0; + + cmd->params.macadd.action = cpu_to_le16(cmd_action); + + if (cmd_action == CMD_ACT_SET) { + memcpy(cmd->params.macadd.macadd, + priv->current_addr, ETH_ALEN); + lbs_deb_hex(LBS_DEB_CMD, "SET_CMD: MAC addr", priv->current_addr, 6); + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_802_11_eeprom_access(struct lbs_private *priv, + struct cmd_ds_command *cmd, + int cmd_action, void *pdata_buf) +{ + struct lbs_ioctl_regrdwr *ea = pdata_buf; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->command = cpu_to_le16(CMD_802_11_EEPROM_ACCESS); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_eeprom_access) + + S_DS_GEN); + cmd->result = 0; + + cmd->params.rdeeprom.action = cpu_to_le16(ea->action); + cmd->params.rdeeprom.offset = cpu_to_le16(ea->offset); + cmd->params.rdeeprom.bytecount = cpu_to_le16(ea->NOB); + cmd->params.rdeeprom.value = 0; + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_bt_access(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action, void *pdata_buf) +{ + struct cmd_ds_bt_access *bt_access = &cmd->params.bt; + lbs_deb_enter_args(LBS_DEB_CMD, "action %d", cmd_action); + + cmd->command = cpu_to_le16(CMD_BT_ACCESS); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_bt_access) + S_DS_GEN); + cmd->result = 0; + bt_access->action = cpu_to_le16(cmd_action); + + switch (cmd_action) { + case CMD_ACT_BT_ACCESS_ADD: + memcpy(bt_access->addr1, pdata_buf, 2 * ETH_ALEN); + lbs_deb_hex(LBS_DEB_MESH, "BT_ADD: blinded MAC addr", bt_access->addr1, 6); + break; + case CMD_ACT_BT_ACCESS_DEL: + memcpy(bt_access->addr1, pdata_buf, 1 * ETH_ALEN); + lbs_deb_hex(LBS_DEB_MESH, "BT_DEL: blinded MAC addr", bt_access->addr1, 6); + break; + case CMD_ACT_BT_ACCESS_LIST: + bt_access->id = cpu_to_le32(*(u32 *) pdata_buf); + break; + case CMD_ACT_BT_ACCESS_RESET: + break; + case CMD_ACT_BT_ACCESS_SET_INVERT: + bt_access->id = cpu_to_le32(*(u32 *) pdata_buf); + break; + case CMD_ACT_BT_ACCESS_GET_INVERT: + break; + default: + break; + } + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_cmd_fwt_access(struct lbs_private *priv, + struct cmd_ds_command *cmd, + u16 cmd_action, void *pdata_buf) +{ + struct cmd_ds_fwt_access *fwt_access = &cmd->params.fwt; + lbs_deb_enter_args(LBS_DEB_CMD, "action %d", cmd_action); + + cmd->command = cpu_to_le16(CMD_FWT_ACCESS); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_fwt_access) + S_DS_GEN); + cmd->result = 0; + + if (pdata_buf) + memcpy(fwt_access, pdata_buf, sizeof(*fwt_access)); + else + memset(fwt_access, 0, sizeof(*fwt_access)); + + fwt_access->action = cpu_to_le16(cmd_action); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +int lbs_mesh_access(struct lbs_private *priv, uint16_t cmd_action, + struct cmd_ds_mesh_access *cmd) +{ + int ret; + + lbs_deb_enter_args(LBS_DEB_CMD, "action %d", cmd_action); + + cmd->hdr.command = cpu_to_le16(CMD_MESH_ACCESS); + cmd->hdr.size = cpu_to_le16(sizeof(struct cmd_ds_mesh_access) + S_DS_GEN); + cmd->hdr.result = 0; + + cmd->action = cpu_to_le16(cmd_action); + + ret = lbs_cmd_with_response(priv, CMD_MESH_ACCESS, (*cmd)); + + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_mesh_access); + +int lbs_mesh_config(struct lbs_private *priv, int enable) +{ + struct cmd_ds_mesh_config cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.action = cpu_to_le16(enable); + cmd.channel = cpu_to_le16(priv->curbssparams.channel); + cmd.type = cpu_to_le16(0x100 + 37); + + if (enable) { + cmd.length = cpu_to_le16(priv->mesh_ssid_len); + memcpy(cmd.data, priv->mesh_ssid, priv->mesh_ssid_len); + } + + return lbs_cmd_with_response(priv, CMD_MESH_CONFIG, cmd); +} + +static int lbs_cmd_bcn_ctrl(struct lbs_private * priv, + struct cmd_ds_command *cmd, + u16 cmd_action) +{ + struct cmd_ds_802_11_beacon_control + *bcn_ctrl = &cmd->params.bcn_ctrl; + + lbs_deb_enter(LBS_DEB_CMD); + cmd->size = + cpu_to_le16(sizeof(struct cmd_ds_802_11_beacon_control) + + S_DS_GEN); + cmd->command = cpu_to_le16(CMD_802_11_BEACON_CTRL); + + bcn_ctrl->action = cpu_to_le16(cmd_action); + bcn_ctrl->beacon_enable = cpu_to_le16(priv->beacon_enable); + bcn_ctrl->beacon_period = cpu_to_le16(priv->beacon_period); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +/* + * Note: NEVER use lbs_queue_cmd() with addtail==0 other than for + * the command timer, because it does not account for queued commands. + */ +void lbs_queue_cmd(struct lbs_private *priv, + struct cmd_ctrl_node *cmdnode, + u8 addtail) +{ + unsigned long flags; + + lbs_deb_enter(LBS_DEB_HOST); + + if (!cmdnode || !cmdnode->cmdbuf) { + lbs_deb_host("QUEUE_CMD: cmdnode or cmdbuf is NULL\n"); + goto done; + } + + /* Exit_PS command needs to be queued in the header always. */ + if (le16_to_cpu(cmdnode->cmdbuf->command) == CMD_802_11_PS_MODE) { + struct cmd_ds_802_11_ps_mode *psm = (void *) cmdnode->cmdbuf; + + if (psm->action == cpu_to_le16(CMD_SUBCMD_EXIT_PS)) { + if (priv->psstate != PS_STATE_FULL_POWER) + addtail = 0; + } + } + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (addtail) + list_add_tail(&cmdnode->list, &priv->cmdpendingq); + else + list_add(&cmdnode->list, &priv->cmdpendingq); + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbs_deb_host("QUEUE_CMD: inserted command 0x%04x into cmdpendingq\n", + le16_to_cpu(cmdnode->cmdbuf->command)); + +done: + lbs_deb_leave(LBS_DEB_HOST); +} + +/* + * TODO: Fix the issue when DownloadcommandToStation is being called the + * second time when the command times out. All the cmdptr->xxx are in little + * endian and therefore all the comparissions will fail. + * For now - we are not performing the endian conversion the second time - but + * for PS and DEEP_SLEEP we need to worry + */ +static int DownloadcommandToStation(struct lbs_private *priv, + struct cmd_ctrl_node *cmdnode) +{ + unsigned long flags; + struct cmd_header *cmd; + int ret = -1; + u16 cmdsize; + u16 command; + + lbs_deb_enter(LBS_DEB_HOST); + + if (!priv || !cmdnode) { + lbs_deb_host("DNLD_CMD: priv or cmdmode is NULL\n"); + goto done; + } + + cmd = cmdnode->cmdbuf; + + spin_lock_irqsave(&priv->driver_lock, flags); + if (!cmd || !cmd->size) { + lbs_deb_host("DNLD_CMD: cmdptr is NULL or zero\n"); + __lbs_cleanup_and_insert_cmd(priv, cmdnode); + spin_unlock_irqrestore(&priv->driver_lock, flags); + goto done; + } + + priv->cur_cmd = cmdnode; + priv->cur_cmd_retcode = 0; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + cmdsize = le16_to_cpu(cmd->size); + command = le16_to_cpu(cmd->command); + + lbs_deb_host("DNLD_CMD: command 0x%04x, size %d, jiffies %lu\n", + command, cmdsize, jiffies); + lbs_deb_hex(LBS_DEB_HOST, "DNLD_CMD", (void *) cmdnode->cmdbuf, cmdsize); + + cmdnode->cmdwaitqwoken = 0; + + ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) cmd, cmdsize); + + if (ret != 0) { + lbs_deb_host("DNLD_CMD: hw_host_to_card failed\n"); + spin_lock_irqsave(&priv->driver_lock, flags); + priv->cur_cmd_retcode = ret; + __lbs_cleanup_and_insert_cmd(priv, priv->cur_cmd); + priv->cur_cmd = NULL; + spin_unlock_irqrestore(&priv->driver_lock, flags); + goto done; + } + + lbs_deb_cmd("DNLD_CMD: sent command 0x%04x, jiffies %lu\n", command, jiffies); + + /* Setup the timer after transmit command */ + if (command == CMD_802_11_SCAN || command == CMD_802_11_AUTHENTICATE + || command == CMD_802_11_ASSOCIATE) + mod_timer(&priv->command_timer, jiffies + (10*HZ)); + else + mod_timer(&priv->command_timer, jiffies + (5*HZ)); + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} + +static int lbs_cmd_mac_control(struct lbs_private *priv, + struct cmd_ds_command *cmd) +{ + struct cmd_ds_mac_control *mac = &cmd->params.macctrl; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd->command = cpu_to_le16(CMD_MAC_CONTROL); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_mac_control) + S_DS_GEN); + mac->action = cpu_to_le16(priv->currentpacketfilter); + + lbs_deb_cmd("MAC_CONTROL: action 0x%x, size %d\n", + le16_to_cpu(mac->action), le16_to_cpu(cmd->size)); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +/** + * This function inserts command node to cmdfreeq + * after cleans it. Requires priv->driver_lock held. + */ +void __lbs_cleanup_and_insert_cmd(struct lbs_private *priv, + struct cmd_ctrl_node *ptempcmd) +{ + + if (!ptempcmd) + return; + + cleanup_cmdnode(ptempcmd); + list_add_tail(&ptempcmd->list, &priv->cmdfreeq); +} + +static void lbs_cleanup_and_insert_cmd(struct lbs_private *priv, + struct cmd_ctrl_node *ptempcmd) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->driver_lock, flags); + __lbs_cleanup_and_insert_cmd(priv, ptempcmd); + spin_unlock_irqrestore(&priv->driver_lock, flags); +} + +int lbs_set_radio_control(struct lbs_private *priv) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_RADIO_CONTROL, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, 0, NULL); + + lbs_deb_cmd("RADIO_SET: radio %d, preamble %d\n", + priv->radioon, priv->preamble); + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +int lbs_set_mac_packet_filter(struct lbs_private *priv) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + /* Send MAC control command to station */ + ret = lbs_prepare_and_send_command(priv, + CMD_MAC_CONTROL, 0, 0, 0, NULL); + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +/** + * @brief This function prepare the command before send to firmware. + * + * @param priv A pointer to struct lbs_private structure + * @param cmd_no command number + * @param cmd_action command action: GET or SET + * @param wait_option wait option: wait response or not + * @param cmd_oid cmd oid: treated as sub command + * @param pdata_buf A pointer to informaion buffer + * @return 0 or -1 + */ +int lbs_prepare_and_send_command(struct lbs_private *priv, + u16 cmd_no, + u16 cmd_action, + u16 wait_option, u32 cmd_oid, void *pdata_buf) +{ + int ret = 0; + struct cmd_ctrl_node *cmdnode; + struct cmd_ds_command *cmdptr; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_HOST); + + if (!priv) { + lbs_deb_host("PREP_CMD: priv is NULL\n"); + ret = -1; + goto done; + } + + if (priv->surpriseremoved) { + lbs_deb_host("PREP_CMD: card removed\n"); + ret = -1; + goto done; + } + + cmdnode = lbs_get_cmd_ctrl_node(priv); + + if (cmdnode == NULL) { + lbs_deb_host("PREP_CMD: cmdnode is NULL\n"); + + /* Wake up main thread to execute next command */ + wake_up_interruptible(&priv->waitq); + ret = -1; + goto done; + } + + lbs_set_cmd_ctrl_node(priv, cmdnode, wait_option, pdata_buf); + + cmdptr = (struct cmd_ds_command *)cmdnode->cmdbuf; + + lbs_deb_host("PREP_CMD: command 0x%04x\n", cmd_no); + + if (!cmdptr) { + lbs_deb_host("PREP_CMD: cmdptr is NULL\n"); + lbs_cleanup_and_insert_cmd(priv, cmdnode); + ret = -1; + goto done; + } + + /* Set sequence number, command and INT option */ + priv->seqnum++; + cmdptr->seqnum = cpu_to_le16(priv->seqnum); + + cmdptr->command = cpu_to_le16(cmd_no); + cmdptr->result = 0; + + switch (cmd_no) { + case CMD_802_11_PS_MODE: + ret = lbs_cmd_802_11_ps_mode(priv, cmdptr, cmd_action); + break; + + case CMD_802_11_SCAN: + ret = lbs_cmd_80211_scan(priv, cmdptr, pdata_buf); + break; + + case CMD_MAC_CONTROL: + ret = lbs_cmd_mac_control(priv, cmdptr); + break; + + case CMD_802_11_ASSOCIATE: + case CMD_802_11_REASSOCIATE: + ret = lbs_cmd_80211_associate(priv, cmdptr, pdata_buf); + break; + + case CMD_802_11_DEAUTHENTICATE: + ret = lbs_cmd_80211_deauthenticate(priv, cmdptr); + break; + + case CMD_802_11_SET_WEP: + ret = lbs_cmd_802_11_set_wep(priv, cmdptr, cmd_action, pdata_buf); + break; + + case CMD_802_11_AD_HOC_START: + ret = lbs_cmd_80211_ad_hoc_start(priv, cmdptr, pdata_buf); + break; + case CMD_CODE_DNLD: + break; + + case CMD_802_11_RESET: + ret = lbs_cmd_802_11_reset(priv, cmdptr, cmd_action); + break; + + case CMD_802_11_GET_LOG: + ret = lbs_cmd_802_11_get_log(priv, cmdptr); + break; + + case CMD_802_11_AUTHENTICATE: + ret = lbs_cmd_80211_authenticate(priv, cmdptr, pdata_buf); + break; + + case CMD_802_11_GET_STAT: + ret = lbs_cmd_802_11_get_stat(priv, cmdptr); + break; + + case CMD_802_11_SNMP_MIB: + ret = lbs_cmd_802_11_snmp_mib(priv, cmdptr, + cmd_action, cmd_oid, pdata_buf); + break; + + case CMD_MAC_REG_ACCESS: + case CMD_BBP_REG_ACCESS: + case CMD_RF_REG_ACCESS: + ret = lbs_cmd_reg_access(priv, cmdptr, cmd_action, pdata_buf); + break; + + case CMD_802_11_RF_TX_POWER: + ret = lbs_cmd_802_11_rf_tx_power(priv, cmdptr, + cmd_action, pdata_buf); + break; + + case CMD_802_11_RADIO_CONTROL: + ret = lbs_cmd_802_11_radio_control(priv, cmdptr, cmd_action); + break; + + case CMD_802_11_RATE_ADAPT_RATESET: + ret = lbs_cmd_802_11_rate_adapt_rateset(priv, + cmdptr, cmd_action); + break; + + case CMD_MAC_MULTICAST_ADR: + ret = lbs_cmd_mac_multicast_adr(priv, cmdptr, cmd_action); + break; + + case CMD_802_11_MONITOR_MODE: + ret = lbs_cmd_802_11_monitor_mode(priv, cmdptr, + cmd_action, pdata_buf); + break; + + case CMD_802_11_AD_HOC_JOIN: + ret = lbs_cmd_80211_ad_hoc_join(priv, cmdptr, pdata_buf); + break; + + case CMD_802_11_RSSI: + ret = lbs_cmd_802_11_rssi(priv, cmdptr); + break; + + case CMD_802_11_AD_HOC_STOP: + ret = lbs_cmd_80211_ad_hoc_stop(priv, cmdptr); + break; + + case CMD_802_11_ENABLE_RSN: + ret = lbs_cmd_802_11_enable_rsn(priv, cmdptr, cmd_action, + pdata_buf); + break; + + case CMD_802_11_KEY_MATERIAL: + ret = lbs_cmd_802_11_key_material(priv, cmdptr, cmd_action, + cmd_oid, pdata_buf); + break; + + case CMD_802_11_PAIRWISE_TSC: + break; + case CMD_802_11_GROUP_TSC: + break; + + case CMD_802_11_MAC_ADDRESS: + ret = lbs_cmd_802_11_mac_address(priv, cmdptr, cmd_action); + break; + + case CMD_802_11_EEPROM_ACCESS: + ret = lbs_cmd_802_11_eeprom_access(priv, cmdptr, + cmd_action, pdata_buf); + break; + + case CMD_802_11_SET_AFC: + case CMD_802_11_GET_AFC: + + cmdptr->command = cpu_to_le16(cmd_no); + cmdptr->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_afc) + + S_DS_GEN); + + memmove(&cmdptr->params.afc, + pdata_buf, sizeof(struct cmd_ds_802_11_afc)); + + ret = 0; + goto done; + + case CMD_802_11D_DOMAIN_INFO: + ret = lbs_cmd_802_11d_domain_info(priv, cmdptr, + cmd_no, cmd_action); + break; + + case CMD_802_11_SLEEP_PARAMS: + ret = lbs_cmd_802_11_sleep_params(priv, cmdptr, cmd_action); + break; + case CMD_802_11_INACTIVITY_TIMEOUT: + ret = lbs_cmd_802_11_inactivity_timeout(priv, cmdptr, + cmd_action, pdata_buf); + lbs_set_cmd_ctrl_node(priv, cmdnode, 0, pdata_buf); + break; + + case CMD_802_11_TPC_CFG: + cmdptr->command = cpu_to_le16(CMD_802_11_TPC_CFG); + cmdptr->size = + cpu_to_le16(sizeof(struct cmd_ds_802_11_tpc_cfg) + + S_DS_GEN); + + memmove(&cmdptr->params.tpccfg, + pdata_buf, sizeof(struct cmd_ds_802_11_tpc_cfg)); + + ret = 0; + break; + case CMD_802_11_LED_GPIO_CTRL: + { + struct mrvlietypes_ledgpio *gpio = + (struct mrvlietypes_ledgpio*) + cmdptr->params.ledgpio.data; + + memmove(&cmdptr->params.ledgpio, + pdata_buf, + sizeof(struct cmd_ds_802_11_led_ctrl)); + + cmdptr->command = + cpu_to_le16(CMD_802_11_LED_GPIO_CTRL); + +#define ACTION_NUMLED_TLVTYPE_LEN_FIELDS_LEN 8 + cmdptr->size = + cpu_to_le16(le16_to_cpu(gpio->header.len) + + S_DS_GEN + + ACTION_NUMLED_TLVTYPE_LEN_FIELDS_LEN); + gpio->header.len = gpio->header.len; + + ret = 0; + break; + } + case CMD_802_11_SUBSCRIBE_EVENT: + lbs_cmd_802_11_subscribe_event(priv, cmdptr, + cmd_action, pdata_buf); + break; + case CMD_802_11_PWR_CFG: + cmdptr->command = cpu_to_le16(CMD_802_11_PWR_CFG); + cmdptr->size = + cpu_to_le16(sizeof(struct cmd_ds_802_11_pwr_cfg) + + S_DS_GEN); + memmove(&cmdptr->params.pwrcfg, pdata_buf, + sizeof(struct cmd_ds_802_11_pwr_cfg)); + + ret = 0; + break; + case CMD_BT_ACCESS: + ret = lbs_cmd_bt_access(priv, cmdptr, cmd_action, pdata_buf); + break; + + case CMD_FWT_ACCESS: + ret = lbs_cmd_fwt_access(priv, cmdptr, cmd_action, pdata_buf); + break; + + case CMD_GET_TSF: + cmdptr->command = cpu_to_le16(CMD_GET_TSF); + cmdptr->size = cpu_to_le16(sizeof(struct cmd_ds_get_tsf) + + S_DS_GEN); + ret = 0; + break; + case CMD_802_11_BEACON_CTRL: + ret = lbs_cmd_bcn_ctrl(priv, cmdptr, cmd_action); + break; + default: + lbs_deb_host("PREP_CMD: unknown command 0x%04x\n", cmd_no); + ret = -1; + break; + } + + /* return error, since the command preparation failed */ + if (ret != 0) { + lbs_deb_host("PREP_CMD: command preparation failed\n"); + lbs_cleanup_and_insert_cmd(priv, cmdnode); + ret = -1; + goto done; + } + + cmdnode->cmdwaitqwoken = 0; + + lbs_queue_cmd(priv, cmdnode, 1); + wake_up_interruptible(&priv->waitq); + + if (wait_option & CMD_OPTION_WAITFORRSP) { + lbs_deb_host("PREP_CMD: wait for response\n"); + might_sleep(); + wait_event_interruptible(cmdnode->cmdwait_q, + cmdnode->cmdwaitqwoken); + } + + spin_lock_irqsave(&priv->driver_lock, flags); + if (priv->cur_cmd_retcode) { + lbs_deb_host("PREP_CMD: command failed with return code %d\n", + priv->cur_cmd_retcode); + priv->cur_cmd_retcode = 0; + ret = -1; + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + +done: + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_prepare_and_send_command); + +/** + * @brief This function allocates the command buffer and link + * it to command free queue. + * + * @param priv A pointer to struct lbs_private structure + * @return 0 or -1 + */ +int lbs_allocate_cmd_buffer(struct lbs_private *priv) +{ + int ret = 0; + u32 bufsize; + u32 i; + struct cmd_ctrl_node *cmdarray; + + lbs_deb_enter(LBS_DEB_HOST); + + /* Allocate and initialize the command array */ + bufsize = sizeof(struct cmd_ctrl_node) * LBS_NUM_CMD_BUFFERS; + if (!(cmdarray = kzalloc(bufsize, GFP_KERNEL))) { + lbs_deb_host("ALLOC_CMD_BUF: tempcmd_array is NULL\n"); + ret = -1; + goto done; + } + priv->cmd_array = cmdarray; + + /* Allocate and initialize each command buffer in the command array */ + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + cmdarray[i].cmdbuf = kzalloc(LBS_CMD_BUFFER_SIZE, GFP_KERNEL); + if (!cmdarray[i].cmdbuf) { + lbs_deb_host("ALLOC_CMD_BUF: ptempvirtualaddr is NULL\n"); + ret = -1; + goto done; + } + } + + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + init_waitqueue_head(&cmdarray[i].cmdwait_q); + lbs_cleanup_and_insert_cmd(priv, &cmdarray[i]); + } + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} + +/** + * @brief This function frees the command buffer. + * + * @param priv A pointer to struct lbs_private structure + * @return 0 or -1 + */ +int lbs_free_cmd_buffer(struct lbs_private *priv) +{ + struct cmd_ctrl_node *cmdarray; + unsigned int i; + + lbs_deb_enter(LBS_DEB_HOST); + + /* need to check if cmd array is allocated or not */ + if (priv->cmd_array == NULL) { + lbs_deb_host("FREE_CMD_BUF: cmd_array is NULL\n"); + goto done; + } + + cmdarray = priv->cmd_array; + + /* Release shared memory buffers */ + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + if (cmdarray[i].cmdbuf) { + kfree(cmdarray[i].cmdbuf); + cmdarray[i].cmdbuf = NULL; + } + } + + /* Release cmd_ctrl_node */ + if (priv->cmd_array) { + kfree(priv->cmd_array); + priv->cmd_array = NULL; + } + +done: + lbs_deb_leave(LBS_DEB_HOST); + return 0; +} + +/** + * @brief This function gets a free command node if available in + * command free queue. + * + * @param priv A pointer to struct lbs_private structure + * @return cmd_ctrl_node A pointer to cmd_ctrl_node structure or NULL + */ +static struct cmd_ctrl_node *lbs_get_cmd_ctrl_node(struct lbs_private *priv) +{ + struct cmd_ctrl_node *tempnode; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_HOST); + + if (!priv) + return NULL; + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (!list_empty(&priv->cmdfreeq)) { + tempnode = list_first_entry(&priv->cmdfreeq, + struct cmd_ctrl_node, list); + list_del(&tempnode->list); + } else { + lbs_deb_host("GET_CMD_NODE: cmd_ctrl_node is not available\n"); + tempnode = NULL; + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (tempnode) + cleanup_cmdnode(tempnode); + + lbs_deb_leave(LBS_DEB_HOST); + return tempnode; +} + +/** + * @brief This function cleans command node. + * + * @param ptempnode A pointer to cmdCtrlNode structure + * @return n/a + */ +static void cleanup_cmdnode(struct cmd_ctrl_node *cmdnode) +{ + lbs_deb_enter(LBS_DEB_HOST); + + if (!cmdnode) + return; + cmdnode->cmdwaitqwoken = 1; + wake_up_interruptible(&cmdnode->cmdwait_q); + cmdnode->wait_option = 0; + cmdnode->pdata_buf = NULL; + cmdnode->callback = NULL; + cmdnode->callback_arg = 0; + + if (cmdnode->cmdbuf != NULL) + memset(cmdnode->cmdbuf, 0, LBS_CMD_BUFFER_SIZE); + + lbs_deb_leave(LBS_DEB_HOST); +} + +/** + * @brief This function initializes the command node. + * + * @param priv A pointer to struct lbs_private structure + * @param ptempnode A pointer to cmd_ctrl_node structure + * @param wait_option wait option: wait response or not + * @param pdata_buf A pointer to informaion buffer + * @return 0 or -1 + */ +static void lbs_set_cmd_ctrl_node(struct lbs_private *priv, + struct cmd_ctrl_node *ptempnode, + u16 wait_option, void *pdata_buf) +{ + lbs_deb_enter(LBS_DEB_HOST); + + if (!ptempnode) + return; + + ptempnode->wait_option = wait_option; + ptempnode->pdata_buf = pdata_buf; + ptempnode->callback = NULL; + ptempnode->callback_arg = 0; + + lbs_deb_leave(LBS_DEB_HOST); +} + +/** + * @brief This function executes next command in command + * pending queue. It will put fimware back to PS mode + * if applicable. + * + * @param priv A pointer to struct lbs_private structure + * @return 0 or -1 + */ +int lbs_execute_next_command(struct lbs_private *priv) +{ + struct cmd_ctrl_node *cmdnode = NULL; + struct cmd_header *cmd; + unsigned long flags; + int ret = 0; + + // Debug group is LBS_DEB_THREAD and not LBS_DEB_HOST, because the + // only caller to us is lbs_thread() and we get even when a + // data packet is received + lbs_deb_enter(LBS_DEB_THREAD); + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->cur_cmd) { + lbs_pr_alert( "EXEC_NEXT_CMD: already processing command!\n"); + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + + if (!list_empty(&priv->cmdpendingq)) { + cmdnode = list_first_entry(&priv->cmdpendingq, + struct cmd_ctrl_node, list); + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (cmdnode) { + cmd = cmdnode->cmdbuf; + + if (is_command_allowed_in_ps(le16_to_cpu(cmd->command))) { + if ((priv->psstate == PS_STATE_SLEEP) || + (priv->psstate == PS_STATE_PRE_SLEEP)) { + lbs_deb_host( + "EXEC_NEXT_CMD: cannot send cmd 0x%04x in psstate %d\n", + le16_to_cpu(cmd->command), + priv->psstate); + ret = -1; + goto done; + } + lbs_deb_host("EXEC_NEXT_CMD: OK to send command " + "0x%04x in psstate %d\n", + le16_to_cpu(cmd->command), priv->psstate); + } else if (priv->psstate != PS_STATE_FULL_POWER) { + /* + * 1. Non-PS command: + * Queue it. set needtowakeup to TRUE if current state + * is SLEEP, otherwise call lbs_ps_wakeup to send Exit_PS. + * 2. PS command but not Exit_PS: + * Ignore it. + * 3. PS command Exit_PS: + * Set needtowakeup to TRUE if current state is SLEEP, + * otherwise send this command down to firmware + * immediately. + */ + if (cmd->command != cpu_to_le16(CMD_802_11_PS_MODE)) { + /* Prepare to send Exit PS, + * this non PS command will be sent later */ + if ((priv->psstate == PS_STATE_SLEEP) + || (priv->psstate == PS_STATE_PRE_SLEEP) + ) { + /* w/ new scheme, it will not reach here. + since it is blocked in main_thread. */ + priv->needtowakeup = 1; + } else + lbs_ps_wakeup(priv, 0); + + ret = 0; + goto done; + } else { + /* + * PS command. Ignore it if it is not Exit_PS. + * otherwise send it down immediately. + */ + struct cmd_ds_802_11_ps_mode *psm = (void *)cmd; + + lbs_deb_host( + "EXEC_NEXT_CMD: PS cmd, action 0x%02x\n", + psm->action); + if (psm->action != + cpu_to_le16(CMD_SUBCMD_EXIT_PS)) { + lbs_deb_host( + "EXEC_NEXT_CMD: ignore ENTER_PS cmd\n"); + list_del(&cmdnode->list); + lbs_cleanup_and_insert_cmd(priv, cmdnode); + + ret = 0; + goto done; + } + + if ((priv->psstate == PS_STATE_SLEEP) || + (priv->psstate == PS_STATE_PRE_SLEEP)) { + lbs_deb_host( + "EXEC_NEXT_CMD: ignore EXIT_PS cmd in sleep\n"); + list_del(&cmdnode->list); + lbs_cleanup_and_insert_cmd(priv, cmdnode); + priv->needtowakeup = 1; + + ret = 0; + goto done; + } + + lbs_deb_host( + "EXEC_NEXT_CMD: sending EXIT_PS\n"); + } + } + list_del(&cmdnode->list); + lbs_deb_host("EXEC_NEXT_CMD: sending command 0x%04x\n", + le16_to_cpu(cmd->command)); + DownloadcommandToStation(priv, cmdnode); + } else { + /* + * check if in power save mode, if yes, put the device back + * to PS mode + */ + if ((priv->psmode != LBS802_11POWERMODECAM) && + (priv->psstate == PS_STATE_FULL_POWER) && + ((priv->connect_status == LBS_CONNECTED) || + (priv->mesh_connect_status == LBS_CONNECTED))) { + if (priv->secinfo.WPAenabled || + priv->secinfo.WPA2enabled) { + /* check for valid WPA group keys */ + if (priv->wpa_mcast_key.len || + priv->wpa_unicast_key.len) { + lbs_deb_host( + "EXEC_NEXT_CMD: WPA enabled and GTK_SET" + " go back to PS_SLEEP"); + lbs_ps_sleep(priv, 0); + } + } else { + lbs_deb_host( + "EXEC_NEXT_CMD: cmdpendingq empty, " + "go back to PS_SLEEP"); + lbs_ps_sleep(priv, 0); + } + } + } + + ret = 0; +done: + lbs_deb_leave(LBS_DEB_THREAD); + return ret; +} + +void lbs_send_iwevcustom_event(struct lbs_private *priv, s8 *str) +{ + union iwreq_data iwrq; + u8 buf[50]; + + lbs_deb_enter(LBS_DEB_WEXT); + + memset(&iwrq, 0, sizeof(union iwreq_data)); + memset(buf, 0, sizeof(buf)); + + snprintf(buf, sizeof(buf) - 1, "%s", str); + + iwrq.data.length = strlen(buf) + 1 + IW_EV_LCP_LEN; + + /* Send Event to upper layer */ + lbs_deb_wext("event indication string %s\n", (char *)buf); + lbs_deb_wext("event indication length %d\n", iwrq.data.length); + lbs_deb_wext("sending wireless event IWEVCUSTOM for %s\n", str); + + wireless_send_event(priv->dev, IWEVCUSTOM, &iwrq, buf); + + lbs_deb_leave(LBS_DEB_WEXT); +} + +static int sendconfirmsleep(struct lbs_private *priv, u8 *cmdptr, u16 size) +{ + unsigned long flags; + int ret = 0; + + lbs_deb_enter(LBS_DEB_HOST); + + lbs_deb_host("SEND_SLEEPC_CMD: before download, cmd size %d\n", + size); + + lbs_deb_hex(LBS_DEB_HOST, "sleep confirm command", cmdptr, size); + + ret = priv->hw_host_to_card(priv, MVMS_CMD, cmdptr, size); + priv->dnld_sent = DNLD_RES_RECEIVED; + + spin_lock_irqsave(&priv->driver_lock, flags); + if (priv->intcounter || priv->currenttxskb) + lbs_deb_host("SEND_SLEEPC_CMD: intcounter %d, currenttxskb %p\n", + priv->intcounter, priv->currenttxskb); + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (ret) { + lbs_pr_alert( + "SEND_SLEEPC_CMD: Host to Card failed for Confirm Sleep\n"); + } else { + spin_lock_irqsave(&priv->driver_lock, flags); + if (!priv->intcounter) { + priv->psstate = PS_STATE_SLEEP; + } else { + lbs_deb_host("SEND_SLEEPC_CMD: after sent, intcounter %d\n", + priv->intcounter); + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbs_deb_host("SEND_SLEEPC_CMD: sent confirm sleep\n"); + } + + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} + +void lbs_ps_sleep(struct lbs_private *priv, int wait_option) +{ + lbs_deb_enter(LBS_DEB_HOST); + + /* + * PS is currently supported only in Infrastructure mode + * Remove this check if it is to be supported in IBSS mode also + */ + + lbs_prepare_and_send_command(priv, CMD_802_11_PS_MODE, + CMD_SUBCMD_ENTER_PS, wait_option, 0, NULL); + + lbs_deb_leave(LBS_DEB_HOST); +} + +/** + * @brief This function sends Exit_PS command to firmware. + * + * @param priv A pointer to struct lbs_private structure + * @param wait_option wait response or not + * @return n/a + */ +void lbs_ps_wakeup(struct lbs_private *priv, int wait_option) +{ + __le32 Localpsmode; + + lbs_deb_enter(LBS_DEB_HOST); + + Localpsmode = cpu_to_le32(LBS802_11POWERMODECAM); + + lbs_prepare_and_send_command(priv, CMD_802_11_PS_MODE, + CMD_SUBCMD_EXIT_PS, + wait_option, 0, &Localpsmode); + + lbs_deb_leave(LBS_DEB_HOST); +} + +/** + * @brief This function checks condition and prepares to + * send sleep confirm command to firmware if ok. + * + * @param priv A pointer to struct lbs_private structure + * @param psmode Power Saving mode + * @return n/a + */ +void lbs_ps_confirm_sleep(struct lbs_private *priv, u16 psmode) +{ + unsigned long flags =0; + u8 allowed = 1; + + lbs_deb_enter(LBS_DEB_HOST); + + if (priv->dnld_sent) { + allowed = 0; + lbs_deb_host("dnld_sent was set"); + } + + spin_lock_irqsave(&priv->driver_lock, flags); + if (priv->cur_cmd) { + allowed = 0; + lbs_deb_host("cur_cmd was set"); + } + if (priv->intcounter > 0) { + allowed = 0; + lbs_deb_host("intcounter %d", priv->intcounter); + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (allowed) { + lbs_deb_host("sending lbs_ps_confirm_sleep\n"); + sendconfirmsleep(priv, (u8 *) & priv->lbs_ps_confirm_sleep, + sizeof(struct PS_CMD_ConfirmSleep)); + } else { + lbs_deb_host("sleep confirm has been delayed\n"); + } + + lbs_deb_leave(LBS_DEB_HOST); +} + + +/** + * @brief Simple callback that copies response back into command + * + * @param priv A pointer to struct lbs_private structure + * @param extra A pointer to the original command structure for which + * 'resp' is a response + * @param resp A pointer to the command response + * + * @return 0 on success, error on failure + */ +int lbs_cmd_copyback(struct lbs_private *priv, unsigned long extra, + struct cmd_header *resp) +{ + struct cmd_header *buf = (void *)extra; + uint16_t copy_len; + + lbs_deb_enter(LBS_DEB_CMD); + + copy_len = min(le16_to_cpu(buf->size), le16_to_cpu(resp->size)); + lbs_deb_cmd("Copying back %u bytes; command response was %u bytes, " + "copy back buffer was %u bytes\n", copy_len, + le16_to_cpu(resp->size), le16_to_cpu(buf->size)); + memcpy(buf, resp, copy_len); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +/** + * @brief Simple way to call firmware functions + * + * @param priv A pointer to struct lbs_private structure + * @param psmode one of the many CMD_802_11_xxxx + * @param cmd pointer to the parameters structure for above command + * (this should not include the command, size, sequence + * and result fields from struct cmd_ds_gen) + * @param cmd_size size structure pointed to by cmd + * @param rsp pointer to an area where the result should be placed + * @param rsp_size pointer to the size of the rsp area. If the firmware + * returns fewer bytes, then this *rsp_size will be + * changed to the actual size. + * @return -1 in case of a higher level error, otherwise + * the result code from the firmware + */ +int __lbs_cmd(struct lbs_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *), + unsigned long callback_arg) +{ + struct cmd_ctrl_node *cmdnode; + unsigned long flags; + int ret = 0; + + lbs_deb_enter(LBS_DEB_HOST); + + if (!priv) { + lbs_deb_host("PREP_CMD: priv is NULL\n"); + ret = -1; + goto done; + } + + if (priv->surpriseremoved) { + lbs_deb_host("PREP_CMD: card removed\n"); + ret = -1; + goto done; + } + + cmdnode = lbs_get_cmd_ctrl_node(priv); + if (cmdnode == NULL) { + lbs_deb_host("PREP_CMD: cmdnode is NULL\n"); + + /* Wake up main thread to execute next command */ + wake_up_interruptible(&priv->waitq); + ret = -1; + goto done; + } + + cmdnode->wait_option = CMD_OPTION_WAITFORRSP; + cmdnode->callback = callback; + cmdnode->callback_arg = callback_arg; + + /* Copy the incoming command to the buffer */ + memcpy(cmdnode->cmdbuf, in_cmd, in_cmd_size); + + /* Set sequence number, clean result, move to buffer */ + priv->seqnum++; + cmdnode->cmdbuf->command = cpu_to_le16(command); + cmdnode->cmdbuf->size = cpu_to_le16(in_cmd_size); + cmdnode->cmdbuf->seqnum = cpu_to_le16(priv->seqnum); + cmdnode->cmdbuf->result = 0; + + lbs_deb_host("PREP_CMD: command 0x%04x\n", command); + + /* here was the big old switch() statement, which is now obsolete, + * because the caller of lbs_cmd() sets up all of *cmd for us. */ + + cmdnode->cmdwaitqwoken = 0; + lbs_queue_cmd(priv, cmdnode, 1); + wake_up_interruptible(&priv->waitq); + + might_sleep(); + wait_event_interruptible(cmdnode->cmdwait_q, cmdnode->cmdwaitqwoken); + + spin_lock_irqsave(&priv->driver_lock, flags); + if (priv->cur_cmd_retcode) { + lbs_deb_host("PREP_CMD: command failed with return code %d\n", + priv->cur_cmd_retcode); + priv->cur_cmd_retcode = 0; + ret = -1; + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + +done: + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(__lbs_cmd); + + diff --git a/package/libertas/src/cmd.h b/package/libertas/src/cmd.h new file mode 100644 index 0000000000..80714b5128 --- /dev/null +++ b/package/libertas/src/cmd.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2007, Red Hat, Inc. */ + +#ifndef _LBS_CMD_H_ +#define _LBS_CMD_H_ + +#include "hostcmd.h" +#include "dev.h" + +#define lbs_cmd(priv, cmdnr, cmd, callback, callback_arg) \ + __lbs_cmd(priv, cmdnr, &(cmd).hdr, sizeof(cmd), \ + callback, callback_arg) + +#define lbs_cmd_with_response(priv, cmdnr, cmd) \ + __lbs_cmd(priv, cmdnr, &(cmd).hdr, sizeof(cmd), \ + lbs_cmd_copyback, (unsigned long) &cmd) + +int __lbs_cmd(struct lbs_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *), + unsigned long callback_arg); + +int lbs_cmd_copyback(struct lbs_private *priv, unsigned long extra, + struct cmd_header *resp); + +int lbs_update_hw_spec(struct lbs_private *priv); + +int lbs_mesh_access(struct lbs_private *priv, uint16_t cmd_action, + struct cmd_ds_mesh_access *cmd); + +int lbs_get_data_rate(struct lbs_private *priv); +int lbs_set_data_rate(struct lbs_private *priv, u8 rate); + +int lbs_get_channel(struct lbs_private *priv); +int lbs_set_channel(struct lbs_private *priv, u8 channel); + +int lbs_mesh_config(struct lbs_private *priv, int enable); + +#endif /* _LBS_CMD_H */ diff --git a/package/libertas/src/cmdresp.c b/package/libertas/src/cmdresp.c new file mode 100644 index 0000000000..bf9941ecc2 --- /dev/null +++ b/package/libertas/src/cmdresp.c @@ -0,0 +1,897 @@ +/** + * This file contains the handling of command + * responses as well as events generated by firmware. + */ +#include <linux/delay.h> +#include <linux/if_arp.h> +#include <linux/netdevice.h> + +#include <net/iw_handler.h> + +#include "host.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "join.h" +#include "wext.h" + +/** + * @brief This function handles disconnect event. it + * reports disconnect to upper layer, clean tx/rx packets, + * reset link state etc. + * + * @param priv A pointer to struct lbs_private structure + * @return n/a + */ +void lbs_mac_event_disconnected(struct lbs_private *priv) +{ + union iwreq_data wrqu; + + if (priv->connect_status != LBS_CONNECTED) + return; + + lbs_deb_enter(LBS_DEB_ASSOC); + + memset(wrqu.ap_addr.sa_data, 0x00, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + + /* + * Cisco AP sends EAP failure and de-auth in less than 0.5 ms. + * It causes problem in the Supplicant + */ + + msleep_interruptible(1000); + wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL); + + /* Free Tx and Rx packets */ + kfree_skb(priv->currenttxskb); + priv->currenttxskb = NULL; + + /* report disconnect to upper layer */ + netif_stop_queue(priv->dev); + netif_carrier_off(priv->dev); + + /* reset SNR/NF/RSSI values */ + memset(priv->SNR, 0x00, sizeof(priv->SNR)); + memset(priv->NF, 0x00, sizeof(priv->NF)); + memset(priv->RSSI, 0x00, sizeof(priv->RSSI)); + memset(priv->rawSNR, 0x00, sizeof(priv->rawSNR)); + memset(priv->rawNF, 0x00, sizeof(priv->rawNF)); + priv->nextSNRNF = 0; + priv->numSNRNF = 0; + priv->connect_status = LBS_DISCONNECTED; + + /* Clear out associated SSID and BSSID since connection is + * no longer valid. + */ + memset(&priv->curbssparams.bssid, 0, ETH_ALEN); + memset(&priv->curbssparams.ssid, 0, IW_ESSID_MAX_SIZE); + priv->curbssparams.ssid_len = 0; + + if (priv->psstate != PS_STATE_FULL_POWER) { + /* make firmware to exit PS mode */ + lbs_deb_cmd("disconnected, so exit PS mode\n"); + lbs_ps_wakeup(priv, 0); + } + lbs_deb_leave(LBS_DEB_CMD); +} + +/** + * @brief This function handles MIC failure event. + * + * @param priv A pointer to struct lbs_private structure + * @para event the event id + * @return n/a + */ +static void handle_mic_failureevent(struct lbs_private *priv, u32 event) +{ + char buf[50]; + + lbs_deb_enter(LBS_DEB_CMD); + memset(buf, 0, sizeof(buf)); + + sprintf(buf, "%s", "MLME-MICHAELMICFAILURE.indication "); + + if (event == MACREG_INT_CODE_MIC_ERR_UNICAST) { + strcat(buf, "unicast "); + } else { + strcat(buf, "multicast "); + } + + lbs_send_iwevcustom_event(priv, buf); + lbs_deb_leave(LBS_DEB_CMD); +} + +static int lbs_ret_reg_access(struct lbs_private *priv, + u16 type, struct cmd_ds_command *resp) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + switch (type) { + case CMD_RET(CMD_MAC_REG_ACCESS): + { + struct cmd_ds_mac_reg_access *reg = &resp->params.macreg; + + priv->offsetvalue.offset = (u32)le16_to_cpu(reg->offset); + priv->offsetvalue.value = le32_to_cpu(reg->value); + break; + } + + case CMD_RET(CMD_BBP_REG_ACCESS): + { + struct cmd_ds_bbp_reg_access *reg = &resp->params.bbpreg; + + priv->offsetvalue.offset = (u32)le16_to_cpu(reg->offset); + priv->offsetvalue.value = reg->value; + break; + } + + case CMD_RET(CMD_RF_REG_ACCESS): + { + struct cmd_ds_rf_reg_access *reg = &resp->params.rfreg; + + priv->offsetvalue.offset = (u32)le16_to_cpu(reg->offset); + priv->offsetvalue.value = reg->value; + break; + } + + default: + ret = -1; + } + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +static int lbs_ret_802_11_sleep_params(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_sleep_params *sp = &resp->params.sleep_params; + + lbs_deb_enter(LBS_DEB_CMD); + + lbs_deb_cmd("error 0x%x, offset 0x%x, stabletime 0x%x, calcontrol 0x%x " + "extsleepclk 0x%x\n", le16_to_cpu(sp->error), + le16_to_cpu(sp->offset), le16_to_cpu(sp->stabletime), + sp->calcontrol, sp->externalsleepclk); + + priv->sp.sp_error = le16_to_cpu(sp->error); + priv->sp.sp_offset = le16_to_cpu(sp->offset); + priv->sp.sp_stabletime = le16_to_cpu(sp->stabletime); + priv->sp.sp_calcontrol = sp->calcontrol; + priv->sp.sp_extsleepclk = sp->externalsleepclk; + priv->sp.sp_reserved = le16_to_cpu(sp->reserved); + + lbs_deb_enter(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_stat(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + lbs_deb_enter(LBS_DEB_CMD); +/* currently priv->wlan802_11Stat is unused + + struct cmd_ds_802_11_get_stat *p11Stat = &resp->params.gstat; + + // TODO Convert it to Big endian befor copy + memcpy(&priv->wlan802_11Stat, + p11Stat, sizeof(struct cmd_ds_802_11_get_stat)); +*/ + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_snmp_mib(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_snmp_mib *smib = &resp->params.smib; + u16 oid = le16_to_cpu(smib->oid); + u16 querytype = le16_to_cpu(smib->querytype); + + lbs_deb_enter(LBS_DEB_CMD); + + lbs_deb_cmd("SNMP_RESP: oid 0x%x, querytype 0x%x\n", oid, + querytype); + lbs_deb_cmd("SNMP_RESP: Buf size %d\n", le16_to_cpu(smib->bufsize)); + + if (querytype == CMD_ACT_GET) { + switch (oid) { + case FRAGTHRESH_I: + priv->fragthsd = + le16_to_cpu(*((__le16 *)(smib->value))); + lbs_deb_cmd("SNMP_RESP: frag threshold %u\n", + priv->fragthsd); + break; + case RTSTHRESH_I: + priv->rtsthsd = + le16_to_cpu(*((__le16 *)(smib->value))); + lbs_deb_cmd("SNMP_RESP: rts threshold %u\n", + priv->rtsthsd); + break; + case SHORT_RETRYLIM_I: + priv->txretrycount = + le16_to_cpu(*((__le16 *)(smib->value))); + lbs_deb_cmd("SNMP_RESP: tx retry count %u\n", + priv->rtsthsd); + break; + default: + break; + } + } + + lbs_deb_enter(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_key_material(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_key_material *pkeymaterial = + &resp->params.keymaterial; + u16 action = le16_to_cpu(pkeymaterial->action); + + lbs_deb_enter(LBS_DEB_CMD); + + /* Copy the returned key to driver private data */ + if (action == CMD_ACT_GET) { + u8 * buf_ptr = (u8 *) &pkeymaterial->keyParamSet; + u8 * resp_end = (u8 *) (resp + le16_to_cpu(resp->size)); + + while (buf_ptr < resp_end) { + struct MrvlIEtype_keyParamSet * pkeyparamset = + (struct MrvlIEtype_keyParamSet *) buf_ptr; + struct enc_key * pkey; + u16 param_set_len = le16_to_cpu(pkeyparamset->length); + u16 key_len = le16_to_cpu(pkeyparamset->keylen); + u16 key_flags = le16_to_cpu(pkeyparamset->keyinfo); + u16 key_type = le16_to_cpu(pkeyparamset->keytypeid); + u8 * end; + + end = (u8 *) pkeyparamset + sizeof (pkeyparamset->type) + + sizeof (pkeyparamset->length) + + param_set_len; + /* Make sure we don't access past the end of the IEs */ + if (end > resp_end) + break; + + if (key_flags & KEY_INFO_WPA_UNICAST) + pkey = &priv->wpa_unicast_key; + else if (key_flags & KEY_INFO_WPA_MCAST) + pkey = &priv->wpa_mcast_key; + else + break; + + /* Copy returned key into driver */ + memset(pkey, 0, sizeof(struct enc_key)); + if (key_len > sizeof(pkey->key)) + break; + pkey->type = key_type; + pkey->flags = key_flags; + pkey->len = key_len; + memcpy(pkey->key, pkeyparamset->key, pkey->len); + + buf_ptr = end + 1; + } + } + + lbs_deb_enter(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_mac_address(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_mac_address *macadd = &resp->params.macadd; + + lbs_deb_enter(LBS_DEB_CMD); + + memcpy(priv->current_addr, macadd->macadd, ETH_ALEN); + + lbs_deb_enter(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_rf_tx_power(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_rf_tx_power *rtp = &resp->params.txp; + + lbs_deb_enter(LBS_DEB_CMD); + + priv->txpowerlevel = le16_to_cpu(rtp->currentlevel); + + lbs_deb_cmd("TX power currently %d\n", priv->txpowerlevel); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_rate_adapt_rateset(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_rate_adapt_rateset *rates = &resp->params.rateset; + + lbs_deb_enter(LBS_DEB_CMD); + + if (rates->action == CMD_ACT_GET) { + priv->enablehwauto = le16_to_cpu(rates->enablehwauto); + priv->ratebitmap = le16_to_cpu(rates->bitmap); + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_rssi(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_rssi_rsp *rssirsp = &resp->params.rssirsp; + + lbs_deb_enter(LBS_DEB_CMD); + + /* store the non average value */ + priv->SNR[TYPE_BEACON][TYPE_NOAVG] = le16_to_cpu(rssirsp->SNR); + priv->NF[TYPE_BEACON][TYPE_NOAVG] = le16_to_cpu(rssirsp->noisefloor); + + priv->SNR[TYPE_BEACON][TYPE_AVG] = le16_to_cpu(rssirsp->avgSNR); + priv->NF[TYPE_BEACON][TYPE_AVG] = le16_to_cpu(rssirsp->avgnoisefloor); + + priv->RSSI[TYPE_BEACON][TYPE_NOAVG] = + CAL_RSSI(priv->SNR[TYPE_BEACON][TYPE_NOAVG], + priv->NF[TYPE_BEACON][TYPE_NOAVG]); + + priv->RSSI[TYPE_BEACON][TYPE_AVG] = + CAL_RSSI(priv->SNR[TYPE_BEACON][TYPE_AVG] / AVG_SCALE, + priv->NF[TYPE_BEACON][TYPE_AVG] / AVG_SCALE); + + lbs_deb_cmd("RSSI: beacon %d, avg %d\n", + priv->RSSI[TYPE_BEACON][TYPE_NOAVG], + priv->RSSI[TYPE_BEACON][TYPE_AVG]); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_eeprom_access(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct lbs_ioctl_regrdwr *pbuf; + pbuf = (struct lbs_ioctl_regrdwr *) priv->prdeeprom; + + lbs_deb_enter_args(LBS_DEB_CMD, "len %d", + le16_to_cpu(resp->params.rdeeprom.bytecount)); + if (pbuf->NOB < le16_to_cpu(resp->params.rdeeprom.bytecount)) { + pbuf->NOB = 0; + lbs_deb_cmd("EEPROM read length too big\n"); + return -1; + } + pbuf->NOB = le16_to_cpu(resp->params.rdeeprom.bytecount); + if (pbuf->NOB > 0) { + + memcpy(&pbuf->value, (u8 *) & resp->params.rdeeprom.value, + le16_to_cpu(resp->params.rdeeprom.bytecount)); + lbs_deb_hex(LBS_DEB_CMD, "EEPROM", (char *)&pbuf->value, + le16_to_cpu(resp->params.rdeeprom.bytecount)); + } + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_get_log(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_get_log *logmessage = &resp->params.glog; + + lbs_deb_enter(LBS_DEB_CMD); + + /* Stored little-endian */ + memcpy(&priv->logmsg, logmessage, sizeof(struct cmd_ds_802_11_get_log)); + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_enable_rsn(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_enable_rsn *enable_rsn = &resp->params.enbrsn; + u32 * pdata_buf = priv->cur_cmd->pdata_buf; + + lbs_deb_enter(LBS_DEB_CMD); + + if (enable_rsn->action == cpu_to_le16(CMD_ACT_GET)) { + if (pdata_buf) + *pdata_buf = (u32) le16_to_cpu(enable_rsn->enable); + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_bcn_ctrl(struct lbs_private * priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_beacon_control *bcn_ctrl = + &resp->params.bcn_ctrl; + + lbs_deb_enter(LBS_DEB_CMD); + + if (bcn_ctrl->action == CMD_ACT_GET) { + priv->beacon_enable = (u8) le16_to_cpu(bcn_ctrl->beacon_enable); + priv->beacon_period = le16_to_cpu(bcn_ctrl->beacon_period); + } + + lbs_deb_enter(LBS_DEB_CMD); + return 0; +} + +static int lbs_ret_802_11_subscribe_event(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_subscribe_event *cmd_event = + &resp->params.subscribe_event; + struct cmd_ds_802_11_subscribe_event *dst_event = + priv->cur_cmd->pdata_buf; + + lbs_deb_enter(LBS_DEB_CMD); + + if (dst_event->action == cpu_to_le16(CMD_ACT_GET)) { + dst_event->events = cmd_event->events; + memcpy(dst_event->tlv, cmd_event->tlv, sizeof(dst_event->tlv)); + } + + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +static inline int handle_cmd_response(struct lbs_private *priv, + unsigned long dummy, + struct cmd_header *cmd_response) +{ + struct cmd_ds_command *resp = (struct cmd_ds_command *) cmd_response; + int ret = 0; + unsigned long flags; + uint16_t respcmd = le16_to_cpu(resp->command); + + lbs_deb_enter(LBS_DEB_HOST); + + switch (respcmd) { + case CMD_RET(CMD_MAC_REG_ACCESS): + case CMD_RET(CMD_BBP_REG_ACCESS): + case CMD_RET(CMD_RF_REG_ACCESS): + ret = lbs_ret_reg_access(priv, respcmd, resp); + break; + + case CMD_RET(CMD_802_11_SCAN): + ret = lbs_ret_80211_scan(priv, resp); + break; + + case CMD_RET(CMD_802_11_GET_LOG): + ret = lbs_ret_get_log(priv, resp); + break; + + case CMD_RET_802_11_ASSOCIATE: + case CMD_RET(CMD_802_11_ASSOCIATE): + case CMD_RET(CMD_802_11_REASSOCIATE): + ret = lbs_ret_80211_associate(priv, resp); + break; + + case CMD_RET(CMD_802_11_DISASSOCIATE): + case CMD_RET(CMD_802_11_DEAUTHENTICATE): + ret = lbs_ret_80211_disassociate(priv, resp); + break; + + case CMD_RET(CMD_802_11_AD_HOC_START): + case CMD_RET(CMD_802_11_AD_HOC_JOIN): + ret = lbs_ret_80211_ad_hoc_start(priv, resp); + break; + + case CMD_RET(CMD_802_11_GET_STAT): + ret = lbs_ret_802_11_stat(priv, resp); + break; + + case CMD_RET(CMD_802_11_SNMP_MIB): + ret = lbs_ret_802_11_snmp_mib(priv, resp); + break; + + case CMD_RET(CMD_802_11_RF_TX_POWER): + ret = lbs_ret_802_11_rf_tx_power(priv, resp); + break; + + case CMD_RET(CMD_802_11_SET_AFC): + case CMD_RET(CMD_802_11_GET_AFC): + spin_lock_irqsave(&priv->driver_lock, flags); + memmove(priv->cur_cmd->pdata_buf, &resp->params.afc, + sizeof(struct cmd_ds_802_11_afc)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + + break; + + case CMD_RET(CMD_MAC_MULTICAST_ADR): + case CMD_RET(CMD_MAC_CONTROL): + case CMD_RET(CMD_802_11_SET_WEP): + case CMD_RET(CMD_802_11_RESET): + case CMD_RET(CMD_802_11_AUTHENTICATE): + case CMD_RET(CMD_802_11_RADIO_CONTROL): + case CMD_RET(CMD_802_11_BEACON_STOP): + break; + + case CMD_RET(CMD_802_11_ENABLE_RSN): + ret = lbs_ret_802_11_enable_rsn(priv, resp); + break; + + case CMD_RET(CMD_802_11_RATE_ADAPT_RATESET): + ret = lbs_ret_802_11_rate_adapt_rateset(priv, resp); + break; + + case CMD_RET(CMD_802_11_RSSI): + ret = lbs_ret_802_11_rssi(priv, resp); + break; + + case CMD_RET(CMD_802_11_MAC_ADDRESS): + ret = lbs_ret_802_11_mac_address(priv, resp); + break; + + case CMD_RET(CMD_802_11_AD_HOC_STOP): + ret = lbs_ret_80211_ad_hoc_stop(priv, resp); + break; + + case CMD_RET(CMD_802_11_KEY_MATERIAL): + ret = lbs_ret_802_11_key_material(priv, resp); + break; + + case CMD_RET(CMD_802_11_EEPROM_ACCESS): + ret = lbs_ret_802_11_eeprom_access(priv, resp); + break; + + case CMD_RET(CMD_802_11D_DOMAIN_INFO): + ret = lbs_ret_802_11d_domain_info(priv, resp); + break; + + case CMD_RET(CMD_802_11_SLEEP_PARAMS): + ret = lbs_ret_802_11_sleep_params(priv, resp); + break; + case CMD_RET(CMD_802_11_INACTIVITY_TIMEOUT): + spin_lock_irqsave(&priv->driver_lock, flags); + *((u16 *) priv->cur_cmd->pdata_buf) = + le16_to_cpu(resp->params.inactivity_timeout.timeout); + spin_unlock_irqrestore(&priv->driver_lock, flags); + break; + + case CMD_RET(CMD_802_11_TPC_CFG): + spin_lock_irqsave(&priv->driver_lock, flags); + memmove(priv->cur_cmd->pdata_buf, &resp->params.tpccfg, + sizeof(struct cmd_ds_802_11_tpc_cfg)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + break; + case CMD_RET(CMD_802_11_LED_GPIO_CTRL): + spin_lock_irqsave(&priv->driver_lock, flags); + memmove(priv->cur_cmd->pdata_buf, &resp->params.ledgpio, + sizeof(struct cmd_ds_802_11_led_ctrl)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + break; + case CMD_RET(CMD_802_11_SUBSCRIBE_EVENT): + ret = lbs_ret_802_11_subscribe_event(priv, resp); + break; + + case CMD_RET(CMD_802_11_PWR_CFG): + spin_lock_irqsave(&priv->driver_lock, flags); + memmove(priv->cur_cmd->pdata_buf, &resp->params.pwrcfg, + sizeof(struct cmd_ds_802_11_pwr_cfg)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + + break; + + case CMD_RET(CMD_GET_TSF): + spin_lock_irqsave(&priv->driver_lock, flags); + memcpy(priv->cur_cmd->pdata_buf, + &resp->params.gettsf.tsfvalue, sizeof(u64)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + break; + case CMD_RET(CMD_BT_ACCESS): + spin_lock_irqsave(&priv->driver_lock, flags); + if (priv->cur_cmd->pdata_buf) + memcpy(priv->cur_cmd->pdata_buf, + &resp->params.bt.addr1, 2 * ETH_ALEN); + spin_unlock_irqrestore(&priv->driver_lock, flags); + break; + case CMD_RET(CMD_FWT_ACCESS): + spin_lock_irqsave(&priv->driver_lock, flags); + if (priv->cur_cmd->pdata_buf) + memcpy(priv->cur_cmd->pdata_buf, &resp->params.fwt, + sizeof(resp->params.fwt)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + break; + case CMD_RET(CMD_802_11_BEACON_CTRL): + ret = lbs_ret_802_11_bcn_ctrl(priv, resp); + break; + + default: + lbs_deb_host("CMD_RESP: unknown cmd response 0x%04x\n", + resp->command); + break; + } + lbs_deb_leave(LBS_DEB_HOST); + return ret; +} + +int lbs_process_rx_command(struct lbs_private *priv) +{ + u16 respcmd; + struct cmd_header *resp; + int ret = 0; + ulong flags; + u16 result; + + lbs_deb_enter(LBS_DEB_HOST); + + /* Now we got response from FW, cancel the command timer */ + del_timer(&priv->command_timer); + + mutex_lock(&priv->lock); + spin_lock_irqsave(&priv->driver_lock, flags); + + if (!priv->cur_cmd) { + lbs_deb_host("CMD_RESP: cur_cmd is NULL\n"); + ret = -1; + spin_unlock_irqrestore(&priv->driver_lock, flags); + goto done; + } + resp = priv->cur_cmd->cmdbuf; + + respcmd = le16_to_cpu(resp->command); + result = le16_to_cpu(resp->result); + + lbs_deb_host("CMD_RESP: response 0x%04x, size %d, jiffies %lu\n", + respcmd, priv->upld_len, jiffies); + lbs_deb_hex(LBS_DEB_HOST, "CMD_RESP", (void *) resp, priv->upld_len); + + if (!(respcmd & 0x8000)) { + lbs_deb_host("invalid response!\n"); + priv->cur_cmd_retcode = -1; + __lbs_cleanup_and_insert_cmd(priv, priv->cur_cmd); + priv->cur_cmd = NULL; + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + + /* Store the response code to cur_cmd_retcode. */ + priv->cur_cmd_retcode = result; + + if (respcmd == CMD_RET(CMD_802_11_PS_MODE)) { + struct cmd_ds_802_11_ps_mode *psmode = (void *) resp; + u16 action = le16_to_cpu(psmode->action); + + lbs_deb_host( + "CMD_RESP: PS_MODE cmd reply result 0x%x, action 0x%x\n", + result, action); + + if (result) { + lbs_deb_host("CMD_RESP: PS command failed with 0x%x\n", + result); + /* + * We should not re-try enter-ps command in + * ad-hoc mode. It takes place in + * lbs_execute_next_command(). + */ + if (priv->mode == IW_MODE_ADHOC && + action == CMD_SUBCMD_ENTER_PS) + priv->psmode = LBS802_11POWERMODECAM; + } else if (action == CMD_SUBCMD_ENTER_PS) { + priv->needtowakeup = 0; + priv->psstate = PS_STATE_AWAKE; + + lbs_deb_host("CMD_RESP: ENTER_PS command response\n"); + if (priv->connect_status != LBS_CONNECTED) { + /* + * When Deauth Event received before Enter_PS command + * response, We need to wake up the firmware. + */ + lbs_deb_host( + "disconnected, invoking lbs_ps_wakeup\n"); + + spin_unlock_irqrestore(&priv->driver_lock, flags); + mutex_unlock(&priv->lock); + lbs_ps_wakeup(priv, 0); + mutex_lock(&priv->lock); + spin_lock_irqsave(&priv->driver_lock, flags); + } + } else if (action == CMD_SUBCMD_EXIT_PS) { + priv->needtowakeup = 0; + priv->psstate = PS_STATE_FULL_POWER; + lbs_deb_host("CMD_RESP: EXIT_PS command response\n"); + } else { + lbs_deb_host("CMD_RESP: PS action 0x%X\n", action); + } + + __lbs_cleanup_and_insert_cmd(priv, priv->cur_cmd); + priv->cur_cmd = NULL; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + ret = 0; + goto done; + } + + /* If the command is not successful, cleanup and return failure */ + if ((result != 0 || !(respcmd & 0x8000))) { + lbs_deb_host("CMD_RESP: error 0x%04x in command reply 0x%04x\n", + result, respcmd); + /* + * Handling errors here + */ + switch (respcmd) { + case CMD_RET(CMD_GET_HW_SPEC): + case CMD_RET(CMD_802_11_RESET): + lbs_deb_host("CMD_RESP: reset failed\n"); + break; + + } + + __lbs_cleanup_and_insert_cmd(priv, priv->cur_cmd); + priv->cur_cmd = NULL; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + ret = -1; + goto done; + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (priv->cur_cmd && priv->cur_cmd->callback) { + ret = priv->cur_cmd->callback(priv, priv->cur_cmd->callback_arg, + resp); + } else + ret = handle_cmd_response(priv, 0, resp); + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->cur_cmd) { + /* Clean up and Put current command back to cmdfreeq */ + __lbs_cleanup_and_insert_cmd(priv, priv->cur_cmd); + priv->cur_cmd = NULL; + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + +done: + mutex_unlock(&priv->lock); + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} + +int lbs_process_event(struct lbs_private *priv) +{ + int ret = 0; + u32 eventcause; + + lbs_deb_enter(LBS_DEB_CMD); + + spin_lock_irq(&priv->driver_lock); + eventcause = priv->eventcause >> SBI_EVENT_CAUSE_SHIFT; + spin_unlock_irq(&priv->driver_lock); + + lbs_deb_cmd("event cause %d\n", eventcause); + + switch (eventcause) { + case MACREG_INT_CODE_LINK_SENSED: + lbs_deb_cmd("EVENT: MACREG_INT_CODE_LINK_SENSED\n"); + break; + + case MACREG_INT_CODE_DEAUTHENTICATED: + lbs_deb_cmd("EVENT: deauthenticated\n"); + lbs_mac_event_disconnected(priv); + break; + + case MACREG_INT_CODE_DISASSOCIATED: + lbs_deb_cmd("EVENT: disassociated\n"); + lbs_mac_event_disconnected(priv); + break; + + case MACREG_INT_CODE_LINK_LOST_NO_SCAN: + lbs_deb_cmd("EVENT: link lost\n"); + lbs_mac_event_disconnected(priv); + break; + + case MACREG_INT_CODE_PS_SLEEP: + lbs_deb_cmd("EVENT: sleep\n"); + + /* handle unexpected PS SLEEP event */ + if (priv->psstate == PS_STATE_FULL_POWER) { + lbs_deb_cmd( + "EVENT: in FULL POWER mode, ignoreing PS_SLEEP\n"); + break; + } + priv->psstate = PS_STATE_PRE_SLEEP; + + lbs_ps_confirm_sleep(priv, (u16) priv->psmode); + + break; + + case MACREG_INT_CODE_PS_AWAKE: + lbs_deb_cmd("EVENT: awake\n"); + + /* handle unexpected PS AWAKE event */ + if (priv->psstate == PS_STATE_FULL_POWER) { + lbs_deb_cmd( + "EVENT: In FULL POWER mode - ignore PS AWAKE\n"); + break; + } + + priv->psstate = PS_STATE_AWAKE; + + if (priv->needtowakeup) { + /* + * wait for the command processing to finish + * before resuming sending + * priv->needtowakeup will be set to FALSE + * in lbs_ps_wakeup() + */ + lbs_deb_cmd("waking up ...\n"); + lbs_ps_wakeup(priv, 0); + } + break; + + case MACREG_INT_CODE_MIC_ERR_UNICAST: + lbs_deb_cmd("EVENT: UNICAST MIC ERROR\n"); + handle_mic_failureevent(priv, MACREG_INT_CODE_MIC_ERR_UNICAST); + break; + + case MACREG_INT_CODE_MIC_ERR_MULTICAST: + lbs_deb_cmd("EVENT: MULTICAST MIC ERROR\n"); + handle_mic_failureevent(priv, MACREG_INT_CODE_MIC_ERR_MULTICAST); + break; + case MACREG_INT_CODE_MIB_CHANGED: + case MACREG_INT_CODE_INIT_DONE: + break; + + case MACREG_INT_CODE_ADHOC_BCN_LOST: + lbs_deb_cmd("EVENT: ADHOC beacon lost\n"); + break; + + case MACREG_INT_CODE_RSSI_LOW: + lbs_pr_alert("EVENT: rssi low\n"); + break; + case MACREG_INT_CODE_SNR_LOW: + lbs_pr_alert("EVENT: snr low\n"); + break; + case MACREG_INT_CODE_MAX_FAIL: + lbs_pr_alert("EVENT: max fail\n"); + break; + case MACREG_INT_CODE_RSSI_HIGH: + lbs_pr_alert("EVENT: rssi high\n"); + break; + case MACREG_INT_CODE_SNR_HIGH: + lbs_pr_alert("EVENT: snr high\n"); + break; + + case MACREG_INT_CODE_MESH_AUTO_STARTED: + /* Ignore spurious autostart events if autostart is disabled */ + if (!priv->mesh_autostart_enabled) { + lbs_pr_info("EVENT: MESH_AUTO_STARTED (ignoring)\n"); + break; + } + lbs_pr_info("EVENT: MESH_AUTO_STARTED\n"); + priv->mesh_connect_status = LBS_CONNECTED; + if (priv->mesh_open == 1) { + netif_wake_queue(priv->mesh_dev); + netif_carrier_on(priv->mesh_dev); + } + priv->mode = IW_MODE_ADHOC; + schedule_work(&priv->sync_channel); + break; + + default: + lbs_pr_alert("EVENT: unknown event id %d\n", eventcause); + break; + } + + spin_lock_irq(&priv->driver_lock); + priv->eventcause = 0; + spin_unlock_irq(&priv->driver_lock); + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} diff --git a/package/libertas/src/compat.h b/package/libertas/src/compat.h new file mode 100644 index 0000000000..26c636343e --- /dev/null +++ b/package/libertas/src/compat.h @@ -0,0 +1,17 @@ +#ifndef __COMPAT_H +#define __COMPAT_H + +#include <linux/kernel.h> +#include <linux/types.h> + +#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" +#define DECLARE_MAC_BUF(var) char var[18] __maybe_unused + +static inline char *print_mac(char *buf, const u8 *addr) +{ + sprintf(buf, MAC_FMT, + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + return buf; +} + +#endif diff --git a/package/libertas/src/debugfs.c b/package/libertas/src/debugfs.c new file mode 100644 index 0000000000..f4858bd7d5 --- /dev/null +++ b/package/libertas/src/debugfs.c @@ -0,0 +1,1163 @@ +#include <linux/module.h> +#include <linux/dcache.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <net/iw_handler.h> + +#include "dev.h" +#include "decl.h" +#include "host.h" +#include "debugfs.h" + +static struct dentry *lbs_dir; +static char *szStates[] = { + "Connected", + "Disconnected" +}; + +#ifdef PROC_DEBUG +static void lbs_debug_init(struct lbs_private *priv, struct net_device *dev); +#endif + +static int open_file_generic(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t write_file_dummy(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static const size_t len = PAGE_SIZE; + +static ssize_t lbs_dev_info(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + size_t pos = 0; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + ssize_t res; + + pos += snprintf(buf+pos, len-pos, "state = %s\n", + szStates[priv->connect_status]); + pos += snprintf(buf+pos, len-pos, "region_code = %02x\n", + (u32) priv->regioncode); + + res = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + + free_page(addr); + return res; +} + + +static ssize_t lbs_getscantable(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + size_t pos = 0; + int numscansdone = 0, res; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + DECLARE_MAC_BUF(mac); + struct bss_descriptor * iter_bss; + + pos += snprintf(buf+pos, len-pos, + "# | ch | rssi | bssid | cap | Qual | SSID \n"); + + mutex_lock(&priv->lock); + list_for_each_entry (iter_bss, &priv->network_list, list) { + u16 ibss = (iter_bss->capability & WLAN_CAPABILITY_IBSS); + u16 privacy = (iter_bss->capability & WLAN_CAPABILITY_PRIVACY); + u16 spectrum_mgmt = (iter_bss->capability & WLAN_CAPABILITY_SPECTRUM_MGMT); + + pos += snprintf(buf+pos, len-pos, + "%02u| %03d | %04ld | %s |", + numscansdone, iter_bss->channel, iter_bss->rssi, + print_mac(mac, iter_bss->bssid)); + pos += snprintf(buf+pos, len-pos, " %04x-", iter_bss->capability); + pos += snprintf(buf+pos, len-pos, "%c%c%c |", + ibss ? 'A' : 'I', privacy ? 'P' : ' ', + spectrum_mgmt ? 'S' : ' '); + pos += snprintf(buf+pos, len-pos, " %04d |", SCAN_RSSI(iter_bss->rssi)); + pos += snprintf(buf+pos, len-pos, " %s\n", + escape_essid(iter_bss->ssid, iter_bss->ssid_len)); + + numscansdone++; + } + mutex_unlock(&priv->lock); + + res = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + + free_page(addr); + return res; +} + +static ssize_t lbs_sleepparams_write(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t buf_size, res; + int p1, p2, p3, p4, p5, p6; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, user_buf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + res = sscanf(buf, "%d %d %d %d %d %d", &p1, &p2, &p3, &p4, &p5, &p6); + if (res != 6) { + res = -EFAULT; + goto out_unlock; + } + priv->sp.sp_error = p1; + priv->sp.sp_offset = p2; + priv->sp.sp_stabletime = p3; + priv->sp.sp_calcontrol = p4; + priv->sp.sp_extsleepclk = p5; + priv->sp.sp_reserved = p6; + + res = lbs_prepare_and_send_command(priv, + CMD_802_11_SLEEP_PARAMS, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, 0, NULL); + + if (!res) + res = count; + else + res = -EINVAL; + +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res; + size_t pos = 0; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + res = lbs_prepare_and_send_command(priv, + CMD_802_11_SLEEP_PARAMS, + CMD_ACT_GET, + CMD_OPTION_WAITFORRSP, 0, NULL); + if (res) { + res = -EFAULT; + goto out_unlock; + } + + pos += snprintf(buf, len, "%d %d %d %d %d %d\n", priv->sp.sp_error, + priv->sp.sp_offset, priv->sp.sp_stabletime, + priv->sp.sp_calcontrol, priv->sp.sp_extsleepclk, + priv->sp.sp_reserved); + + res = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_extscan(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + union iwreq_data wrqu; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + + lbs_send_specific_ssid_scan(priv, buf, strlen(buf)-1, 0); + + memset(&wrqu, 0, sizeof(union iwreq_data)); + wireless_send_event(priv->dev, SIOCGIWSCAN, &wrqu, NULL); + +out_unlock: + free_page(addr); + return count; +} + +static void lbs_parse_bssid(char *buf, size_t count, + struct lbs_ioctl_user_scan_cfg *scan_cfg) +{ + char *hold; + unsigned int mac[ETH_ALEN]; + + hold = strstr(buf, "bssid="); + if (!hold) + return; + hold += 6; + sscanf(hold, MAC_FMT, mac, mac+1, mac+2, mac+3, mac+4, mac+5); + memcpy(scan_cfg->bssid, mac, ETH_ALEN); +} + +static void lbs_parse_ssid(char *buf, size_t count, + struct lbs_ioctl_user_scan_cfg *scan_cfg) +{ + char *hold, *end; + ssize_t size; + + hold = strstr(buf, "ssid="); + if (!hold) + return; + hold += 5; + end = strchr(hold, ' '); + if (!end) + end = buf + count - 1; + + size = min((size_t)IW_ESSID_MAX_SIZE, (size_t) (end - hold)); + strncpy(scan_cfg->ssid, hold, size); + + return; +} + +static int lbs_parse_clear(char *buf, size_t count, const char *tag) +{ + char *hold; + int val; + + hold = strstr(buf, tag); + if (!hold) + return 0; + hold += strlen(tag); + sscanf(hold, "%d", &val); + + if (val != 0) + val = 1; + + return val; +} + +static int lbs_parse_dur(char *buf, size_t count, + struct lbs_ioctl_user_scan_cfg *scan_cfg) +{ + char *hold; + int val; + + hold = strstr(buf, "dur="); + if (!hold) + return 0; + hold += 4; + sscanf(hold, "%d", &val); + + return val; +} + +static void lbs_parse_type(char *buf, size_t count, + struct lbs_ioctl_user_scan_cfg *scan_cfg) +{ + char *hold; + int val; + + hold = strstr(buf, "type="); + if (!hold) + return; + hold += 5; + sscanf(hold, "%d", &val); + + /* type=1,2 or 3 */ + if (val < 1 || val > 3) + return; + + scan_cfg->bsstype = val; + + return; +} + +static ssize_t lbs_setuserscan(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + struct lbs_ioctl_user_scan_cfg *scan_cfg; + union iwreq_data wrqu; + int dur; + char *buf = (char *)get_zeroed_page(GFP_KERNEL); + + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_buf; + } + + scan_cfg = kzalloc(sizeof(struct lbs_ioctl_user_scan_cfg), GFP_KERNEL); + if (!scan_cfg) { + res = -ENOMEM; + goto out_buf; + } + res = count; + + scan_cfg->bsstype = LBS_SCAN_BSS_TYPE_ANY; + + dur = lbs_parse_dur(buf, count, scan_cfg); + lbs_parse_bssid(buf, count, scan_cfg); + scan_cfg->clear_bssid = lbs_parse_clear(buf, count, "clear_bssid="); + lbs_parse_ssid(buf, count, scan_cfg); + scan_cfg->clear_ssid = lbs_parse_clear(buf, count, "clear_ssid="); + lbs_parse_type(buf, count, scan_cfg); + + lbs_scan_networks(priv, scan_cfg, 1); + wait_event_interruptible(priv->cmd_pending, + priv->surpriseremoved || !priv->last_scanned_channel); + + if (priv->surpriseremoved) + goto out_scan_cfg; + + memset(&wrqu, 0x00, sizeof(union iwreq_data)); + wireless_send_event(priv->dev, SIOCGIWSCAN, &wrqu, NULL); + + out_scan_cfg: + kfree(scan_cfg); + out_buf: + free_page((unsigned long)buf); + return res; +} + + +/* + * When calling CMD_802_11_SUBSCRIBE_EVENT with CMD_ACT_GET, me might + * get a bunch of vendor-specific TLVs (a.k.a. IEs) back from the + * firmware. Here's an example: + * 04 01 02 00 00 00 05 01 02 00 00 00 06 01 02 00 + * 00 00 07 01 02 00 3c 00 00 00 00 00 00 00 03 03 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * + * The 04 01 is the TLV type (here TLV_TYPE_RSSI_LOW), 02 00 is the length, + * 00 00 are the data bytes of this TLV. For this TLV, their meaning is + * defined in mrvlietypes_thresholds + * + * This function searches in this TLV data chunk for a given TLV type + * and returns a pointer to the first data byte of the TLV, or to NULL + * if the TLV hasn't been found. + */ +static void *lbs_tlv_find(u16 tlv_type, const u8 *tlv, u16 size) +{ + __le16 le_type = cpu_to_le16(tlv_type); + ssize_t pos = 0; + struct mrvlietypesheader *tlv_h; + while (pos < size) { + u16 length; + tlv_h = (struct mrvlietypesheader *) tlv; + if (tlv_h->type == le_type) + return tlv_h; + if (tlv_h->len == 0) + return NULL; + length = le16_to_cpu(tlv_h->len) + + sizeof(struct mrvlietypesheader); + pos += length; + tlv += length; + } + return NULL; +} + + +/* + * This just gets the bitmap of currently subscribed events. Used when + * adding an additonal event subscription. + */ +static u16 lbs_get_events_bitmap(struct lbs_private *priv) +{ + ssize_t res; + + struct cmd_ds_802_11_subscribe_event *events = kzalloc( + sizeof(struct cmd_ds_802_11_subscribe_event), + GFP_KERNEL); + + res = lbs_prepare_and_send_command(priv, + CMD_802_11_SUBSCRIBE_EVENT, CMD_ACT_GET, + CMD_OPTION_WAITFORRSP, 0, events); + + if (res) { + kfree(events); + return 0; + } + return le16_to_cpu(events->events); +} + + +static ssize_t lbs_threshold_read( + u16 tlv_type, u16 event_mask, + struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res = 0; + size_t pos = 0; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + u8 value; + u8 freq; + int events = 0; + + struct cmd_ds_802_11_subscribe_event *subscribed = kzalloc( + sizeof(struct cmd_ds_802_11_subscribe_event), + GFP_KERNEL); + struct mrvlietypes_thresholds *got; + + res = lbs_prepare_and_send_command(priv, + CMD_802_11_SUBSCRIBE_EVENT, CMD_ACT_GET, + CMD_OPTION_WAITFORRSP, 0, subscribed); + if (res) { + kfree(subscribed); + return res; + } + + got = lbs_tlv_find(tlv_type, subscribed->tlv, sizeof(subscribed->tlv)); + if (got) { + value = got->value; + freq = got->freq; + events = le16_to_cpu(subscribed->events); + } + kfree(subscribed); + + if (got) + pos += snprintf(buf, len, "%d %d %d\n", value, freq, + !!(events & event_mask)); + + res = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + + free_page(addr); + return res; +} + + +static ssize_t lbs_threshold_write( + u16 tlv_type, u16 event_mask, + struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + int value, freq, curr_mask, new_mask; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + struct cmd_ds_802_11_subscribe_event *events; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + res = sscanf(buf, "%d %d %d", &value, &freq, &new_mask); + if (res != 3) { + res = -EFAULT; + goto out_unlock; + } + curr_mask = lbs_get_events_bitmap(priv); + + if (new_mask) + new_mask = curr_mask | event_mask; + else + new_mask = curr_mask & ~event_mask; + + /* Now everything is set and we can send stuff down to the firmware */ + events = kzalloc( + sizeof(struct cmd_ds_802_11_subscribe_event), + GFP_KERNEL); + if (events) { + struct mrvlietypes_thresholds *tlv = + (struct mrvlietypes_thresholds *) events->tlv; + events->action = cpu_to_le16(CMD_ACT_SET); + events->events = cpu_to_le16(new_mask); + tlv->header.type = cpu_to_le16(tlv_type); + tlv->header.len = cpu_to_le16( + sizeof(struct mrvlietypes_thresholds) - + sizeof(struct mrvlietypesheader)); + tlv->value = value; + if (tlv_type != TLV_TYPE_BCNMISS) + tlv->freq = freq; + lbs_prepare_and_send_command(priv, + CMD_802_11_SUBSCRIBE_EVENT, CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, 0, events); + kfree(events); + } + + res = count; +out_unlock: + free_page(addr); + return res; +} + + +static ssize_t lbs_lowrssi_read( + struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_RSSI_LOW, CMD_SUBSCRIBE_RSSI_LOW, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_lowrssi_write( + struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_RSSI_LOW, CMD_SUBSCRIBE_RSSI_LOW, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_lowsnr_read( + struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_SNR_LOW, CMD_SUBSCRIBE_SNR_LOW, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_lowsnr_write( + struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_SNR_LOW, CMD_SUBSCRIBE_SNR_LOW, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_failcount_read( + struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_FAILCOUNT, CMD_SUBSCRIBE_FAILCOUNT, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_failcount_write( + struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_FAILCOUNT, CMD_SUBSCRIBE_FAILCOUNT, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_highrssi_read( + struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_RSSI_HIGH, CMD_SUBSCRIBE_RSSI_HIGH, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_highrssi_write( + struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_RSSI_HIGH, CMD_SUBSCRIBE_RSSI_HIGH, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_highsnr_read( + struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_SNR_HIGH, CMD_SUBSCRIBE_SNR_HIGH, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_highsnr_write( + struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_SNR_HIGH, CMD_SUBSCRIBE_SNR_HIGH, + file, userbuf, count, ppos); +} + +static ssize_t lbs_bcnmiss_read( + struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_BCNMISS, CMD_SUBSCRIBE_BCNMISS, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_bcnmiss_write( + struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_BCNMISS, CMD_SUBSCRIBE_BCNMISS, + file, userbuf, count, ppos); +} + + + + + + + + +static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + struct lbs_offset_value offval; + ssize_t pos = 0; + int ret; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + offval.offset = priv->mac_offset; + offval.value = 0; + + ret = lbs_prepare_and_send_command(priv, + CMD_MAC_REG_ACCESS, 0, + CMD_OPTION_WAITFORRSP, 0, &offval); + mdelay(10); + pos += snprintf(buf+pos, len-pos, "MAC[0x%x] = 0x%08x\n", + priv->mac_offset, priv->offsetvalue.value); + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + free_page(addr); + return ret; +} + +static ssize_t lbs_rdmac_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + priv->mac_offset = simple_strtoul((char *)buf, NULL, 16); + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_wrmac_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + u32 offset, value; + struct lbs_offset_value offval; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + res = sscanf(buf, "%x %x", &offset, &value); + if (res != 2) { + res = -EFAULT; + goto out_unlock; + } + + offval.offset = offset; + offval.value = value; + res = lbs_prepare_and_send_command(priv, + CMD_MAC_REG_ACCESS, 1, + CMD_OPTION_WAITFORRSP, 0, &offval); + mdelay(10); + + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + struct lbs_offset_value offval; + ssize_t pos = 0; + int ret; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + offval.offset = priv->bbp_offset; + offval.value = 0; + + ret = lbs_prepare_and_send_command(priv, + CMD_BBP_REG_ACCESS, 0, + CMD_OPTION_WAITFORRSP, 0, &offval); + mdelay(10); + pos += snprintf(buf+pos, len-pos, "BBP[0x%x] = 0x%08x\n", + priv->bbp_offset, priv->offsetvalue.value); + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + free_page(addr); + + return ret; +} + +static ssize_t lbs_rdbbp_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + priv->bbp_offset = simple_strtoul((char *)buf, NULL, 16); + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_wrbbp_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + u32 offset, value; + struct lbs_offset_value offval; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + res = sscanf(buf, "%x %x", &offset, &value); + if (res != 2) { + res = -EFAULT; + goto out_unlock; + } + + offval.offset = offset; + offval.value = value; + res = lbs_prepare_and_send_command(priv, + CMD_BBP_REG_ACCESS, 1, + CMD_OPTION_WAITFORRSP, 0, &offval); + mdelay(10); + + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + struct lbs_offset_value offval; + ssize_t pos = 0; + int ret; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + offval.offset = priv->rf_offset; + offval.value = 0; + + ret = lbs_prepare_and_send_command(priv, + CMD_RF_REG_ACCESS, 0, + CMD_OPTION_WAITFORRSP, 0, &offval); + mdelay(10); + pos += snprintf(buf+pos, len-pos, "RF[0x%x] = 0x%08x\n", + priv->rf_offset, priv->offsetvalue.value); + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + free_page(addr); + + return ret; +} + +static ssize_t lbs_rdrf_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + priv->rf_offset = simple_strtoul((char *)buf, NULL, 16); + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_wrrf_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + u32 offset, value; + struct lbs_offset_value offval; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + res = sscanf(buf, "%x %x", &offset, &value); + if (res != 2) { + res = -EFAULT; + goto out_unlock; + } + + offval.offset = offset; + offval.value = value; + res = lbs_prepare_and_send_command(priv, + CMD_RF_REG_ACCESS, 1, + CMD_OPTION_WAITFORRSP, 0, &offval); + mdelay(10); + + res = count; +out_unlock: + free_page(addr); + return res; +} + +#define FOPS(fread, fwrite) { \ + .owner = THIS_MODULE, \ + .open = open_file_generic, \ + .read = (fread), \ + .write = (fwrite), \ +} + +struct lbs_debugfs_files { + char *name; + int perm; + struct file_operations fops; +}; + +static struct lbs_debugfs_files debugfs_files[] = { + { "info", 0444, FOPS(lbs_dev_info, write_file_dummy), }, + { "getscantable", 0444, FOPS(lbs_getscantable, + write_file_dummy), }, + { "sleepparams", 0644, FOPS(lbs_sleepparams_read, + lbs_sleepparams_write), }, + { "extscan", 0600, FOPS(NULL, lbs_extscan), }, + { "setuserscan", 0600, FOPS(NULL, lbs_setuserscan), }, +}; + +static struct lbs_debugfs_files debugfs_events_files[] = { + {"low_rssi", 0644, FOPS(lbs_lowrssi_read, + lbs_lowrssi_write), }, + {"low_snr", 0644, FOPS(lbs_lowsnr_read, + lbs_lowsnr_write), }, + {"failure_count", 0644, FOPS(lbs_failcount_read, + lbs_failcount_write), }, + {"beacon_missed", 0644, FOPS(lbs_bcnmiss_read, + lbs_bcnmiss_write), }, + {"high_rssi", 0644, FOPS(lbs_highrssi_read, + lbs_highrssi_write), }, + {"high_snr", 0644, FOPS(lbs_highsnr_read, + lbs_highsnr_write), }, +}; + +static struct lbs_debugfs_files debugfs_regs_files[] = { + {"rdmac", 0644, FOPS(lbs_rdmac_read, lbs_rdmac_write), }, + {"wrmac", 0600, FOPS(NULL, lbs_wrmac_write), }, + {"rdbbp", 0644, FOPS(lbs_rdbbp_read, lbs_rdbbp_write), }, + {"wrbbp", 0600, FOPS(NULL, lbs_wrbbp_write), }, + {"rdrf", 0644, FOPS(lbs_rdrf_read, lbs_rdrf_write), }, + {"wrrf", 0600, FOPS(NULL, lbs_wrrf_write), }, +}; + +void lbs_debugfs_init(void) +{ + if (!lbs_dir) + lbs_dir = debugfs_create_dir("lbs_wireless", NULL); + + return; +} + +void lbs_debugfs_remove(void) +{ + if (lbs_dir) + debugfs_remove(lbs_dir); + return; +} + +void lbs_debugfs_init_one(struct lbs_private *priv, struct net_device *dev) +{ + int i; + struct lbs_debugfs_files *files; + if (!lbs_dir) + goto exit; + + priv->debugfs_dir = debugfs_create_dir(dev->name, lbs_dir); + if (!priv->debugfs_dir) + goto exit; + + for (i=0; i<ARRAY_SIZE(debugfs_files); i++) { + files = &debugfs_files[i]; + priv->debugfs_files[i] = debugfs_create_file(files->name, + files->perm, + priv->debugfs_dir, + priv, + &files->fops); + } + + priv->events_dir = debugfs_create_dir("subscribed_events", priv->debugfs_dir); + if (!priv->events_dir) + goto exit; + + for (i=0; i<ARRAY_SIZE(debugfs_events_files); i++) { + files = &debugfs_events_files[i]; + priv->debugfs_events_files[i] = debugfs_create_file(files->name, + files->perm, + priv->events_dir, + priv, + &files->fops); + } + + priv->regs_dir = debugfs_create_dir("registers", priv->debugfs_dir); + if (!priv->regs_dir) + goto exit; + + for (i=0; i<ARRAY_SIZE(debugfs_regs_files); i++) { + files = &debugfs_regs_files[i]; + priv->debugfs_regs_files[i] = debugfs_create_file(files->name, + files->perm, + priv->regs_dir, + priv, + &files->fops); + } + +#ifdef PROC_DEBUG + lbs_debug_init(priv, dev); +#endif +exit: + return; +} + +void lbs_debugfs_remove_one(struct lbs_private *priv) +{ + int i; + + for(i=0; i<ARRAY_SIZE(debugfs_regs_files); i++) + debugfs_remove(priv->debugfs_regs_files[i]); + + debugfs_remove(priv->regs_dir); + + for(i=0; i<ARRAY_SIZE(debugfs_events_files); i++) + debugfs_remove(priv->debugfs_events_files[i]); + + debugfs_remove(priv->events_dir); +#ifdef PROC_DEBUG + debugfs_remove(priv->debugfs_debug); +#endif + for(i=0; i<ARRAY_SIZE(debugfs_files); i++) + debugfs_remove(priv->debugfs_files[i]); + debugfs_remove(priv->debugfs_dir); +} + + + +/* debug entry */ + +#ifdef PROC_DEBUG + +#define item_size(n) (FIELD_SIZEOF(struct lbs_private, n)) +#define item_addr(n) (offsetof(struct lbs_private, n)) + + +struct debug_data { + char name[32]; + u32 size; + size_t addr; +}; + +/* To debug any member of struct lbs_private, simply add one line here. + */ +static struct debug_data items[] = { + {"intcounter", item_size(intcounter), item_addr(intcounter)}, + {"psmode", item_size(psmode), item_addr(psmode)}, + {"psstate", item_size(psstate), item_addr(psstate)}, +}; + +static int num_of_items = ARRAY_SIZE(items); + +/** + * @brief proc read function + * + * @param page pointer to buffer + * @param s read data starting position + * @param off offset + * @param cnt counter + * @param eof end of file flag + * @param data data to output + * @return number of output data + */ +static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + int val = 0; + size_t pos = 0; + ssize_t res; + char *p; + int i; + struct debug_data *d; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + + p = buf; + + d = (struct debug_data *)file->private_data; + + for (i = 0; i < num_of_items; i++) { + if (d[i].size == 1) + val = *((u8 *) d[i].addr); + else if (d[i].size == 2) + val = *((u16 *) d[i].addr); + else if (d[i].size == 4) + val = *((u32 *) d[i].addr); + else if (d[i].size == 8) + val = *((u64 *) d[i].addr); + + pos += sprintf(p + pos, "%s=%d\n", d[i].name, val); + } + + res = simple_read_from_buffer(userbuf, count, ppos, p, pos); + + free_page(addr); + return res; +} + +/** + * @brief proc write function + * + * @param f file pointer + * @param buf pointer to data buffer + * @param cnt data number to write + * @param data data to write + * @return number of data + */ +static ssize_t lbs_debugfs_write(struct file *f, const char __user *buf, + size_t cnt, loff_t *ppos) +{ + int r, i; + char *pdata; + char *p; + char *p0; + char *p1; + char *p2; + struct debug_data *d = (struct debug_data *)f->private_data; + + pdata = kmalloc(cnt, GFP_KERNEL); + if (pdata == NULL) + return 0; + + if (copy_from_user(pdata, buf, cnt)) { + lbs_deb_debugfs("Copy from user failed\n"); + kfree(pdata); + return 0; + } + + p0 = pdata; + for (i = 0; i < num_of_items; i++) { + do { + p = strstr(p0, d[i].name); + if (p == NULL) + break; + p1 = strchr(p, '\n'); + if (p1 == NULL) + break; + p0 = p1++; + p2 = strchr(p, '='); + if (!p2) + break; + p2++; + r = simple_strtoul(p2, NULL, 0); + if (d[i].size == 1) + *((u8 *) d[i].addr) = (u8) r; + else if (d[i].size == 2) + *((u16 *) d[i].addr) = (u16) r; + else if (d[i].size == 4) + *((u32 *) d[i].addr) = (u32) r; + else if (d[i].size == 8) + *((u64 *) d[i].addr) = (u64) r; + break; + } while (1); + } + kfree(pdata); + + return (ssize_t)cnt; +} + +static struct file_operations lbs_debug_fops = { + .owner = THIS_MODULE, + .open = open_file_generic, + .write = lbs_debugfs_write, + .read = lbs_debugfs_read, +}; + +/** + * @brief create debug proc file + * + * @param priv pointer struct lbs_private + * @param dev pointer net_device + * @return N/A + */ +static void lbs_debug_init(struct lbs_private *priv, struct net_device *dev) +{ + int i; + + if (!priv->debugfs_dir) + return; + + for (i = 0; i < num_of_items; i++) + items[i].addr += (size_t) priv; + + priv->debugfs_debug = debugfs_create_file("debug", 0644, + priv->debugfs_dir, &items[0], + &lbs_debug_fops); +} +#endif diff --git a/package/libertas/src/debugfs.h b/package/libertas/src/debugfs.h new file mode 100644 index 0000000000..f2b9c7ffe0 --- /dev/null +++ b/package/libertas/src/debugfs.h @@ -0,0 +1,10 @@ +#ifndef _LBS_DEBUGFS_H_ +#define _LBS_DEBUGFS_H_ + +void lbs_debugfs_init(void); +void lbs_debugfs_remove(void); + +void lbs_debugfs_init_one(struct lbs_private *priv, struct net_device *dev); +void lbs_debugfs_remove_one(struct lbs_private *priv); + +#endif diff --git a/package/libertas/src/decl.h b/package/libertas/src/decl.h new file mode 100644 index 0000000000..9b0ef16618 --- /dev/null +++ b/package/libertas/src/decl.h @@ -0,0 +1,80 @@ +/** + * This file contains declaration referring to + * functions defined in other source files + */ + +#ifndef _LBS_DECL_H_ +#define _LBS_DECL_H_ + +#include <linux/device.h> + +#include "defs.h" + +/** Function Prototype Declaration */ +struct lbs_private; +struct sk_buff; +struct net_device; +struct cmd_ctrl_node; +struct cmd_ds_command; + +int lbs_set_mac_packet_filter(struct lbs_private *priv); + +void lbs_send_tx_feedback(struct lbs_private *priv); + +int lbs_free_cmd_buffer(struct lbs_private *priv); + +int lbs_prepare_and_send_command(struct lbs_private *priv, + u16 cmd_no, + u16 cmd_action, + u16 wait_option, u32 cmd_oid, void *pdata_buf); + +void lbs_queue_cmd(struct lbs_private *priv, + struct cmd_ctrl_node *cmdnode, + u8 addtail); + +int lbs_allocate_cmd_buffer(struct lbs_private *priv); +int lbs_execute_next_command(struct lbs_private *priv); +int lbs_process_event(struct lbs_private *priv); +void lbs_interrupt(struct lbs_private *priv); +int lbs_set_radio_control(struct lbs_private *priv); +u32 lbs_fw_index_to_data_rate(u8 index); +u8 lbs_data_rate_to_fw_index(u32 rate); +void lbs_get_fwversion(struct lbs_private *priv, + char *fwversion, + int maxlen); + +/** The proc fs interface */ +int lbs_process_rx_command(struct lbs_private *priv); +void __lbs_cleanup_and_insert_cmd(struct lbs_private *priv, + struct cmd_ctrl_node *ptempcmd); + +int lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev); +int lbs_set_regiontable(struct lbs_private *priv, u8 region, u8 band); + +int lbs_process_rxed_packet(struct lbs_private *priv, struct sk_buff *); + +void lbs_ps_sleep(struct lbs_private *priv, int wait_option); +void lbs_ps_confirm_sleep(struct lbs_private *priv, u16 psmode); +void lbs_ps_wakeup(struct lbs_private *priv, int wait_option); + +struct chan_freq_power *lbs_find_cfp_by_band_and_channel( + struct lbs_private *priv, + u8 band, + u16 channel); + +void lbs_mac_event_disconnected(struct lbs_private *priv); + +void lbs_send_iwevcustom_event(struct lbs_private *priv, s8 *str); + +/* main.c */ +struct chan_freq_power *lbs_get_region_cfp_table(u8 region, + u8 band, + int *cfp_no); +struct lbs_private *lbs_add_card(void *card, struct device *dmdev); +int lbs_remove_card(struct lbs_private *priv); +int lbs_start_card(struct lbs_private *priv); +int lbs_stop_card(struct lbs_private *priv); +int lbs_reset_device(struct lbs_private *priv); +void lbs_host_to_card_done(struct lbs_private *priv); + +#endif diff --git a/package/libertas/src/defs.h b/package/libertas/src/defs.h new file mode 100644 index 0000000000..9b98ae720b --- /dev/null +++ b/package/libertas/src/defs.h @@ -0,0 +1,379 @@ +/** + * This header file contains global constant/enum definitions, + * global variable declaration. + */ +#ifndef _LBS_DEFS_H_ +#define _LBS_DEFS_H_ + +#include <linux/spinlock.h> + +#ifdef CONFIG_LIBERTAS_DEBUG +#define DEBUG +#define PROC_DEBUG +#endif + +#ifndef DRV_NAME +#define DRV_NAME "libertas" +#endif + + +#define LBS_DEB_ENTER 0x00000001 +#define LBS_DEB_LEAVE 0x00000002 +#define LBS_DEB_MAIN 0x00000004 +#define LBS_DEB_NET 0x00000008 +#define LBS_DEB_MESH 0x00000010 +#define LBS_DEB_WEXT 0x00000020 +#define LBS_DEB_IOCTL 0x00000040 +#define LBS_DEB_SCAN 0x00000080 +#define LBS_DEB_ASSOC 0x00000100 +#define LBS_DEB_JOIN 0x00000200 +#define LBS_DEB_11D 0x00000400 +#define LBS_DEB_DEBUGFS 0x00000800 +#define LBS_DEB_ETHTOOL 0x00001000 +#define LBS_DEB_HOST 0x00002000 +#define LBS_DEB_CMD 0x00004000 +#define LBS_DEB_RX 0x00008000 +#define LBS_DEB_TX 0x00010000 +#define LBS_DEB_USB 0x00020000 +#define LBS_DEB_CS 0x00040000 +#define LBS_DEB_FW 0x00080000 +#define LBS_DEB_THREAD 0x00100000 +#define LBS_DEB_HEX 0x00200000 +#define LBS_DEB_SDIO 0x00400000 + +extern unsigned int lbs_debug; + +#ifdef DEBUG +#define LBS_DEB_LL(grp, grpnam, fmt, args...) \ +do { if ((lbs_debug & (grp)) == (grp)) \ + printk(KERN_DEBUG DRV_NAME grpnam "%s: " fmt, \ + in_interrupt() ? " (INT)" : "", ## args); } while (0) +#else +#define LBS_DEB_LL(grp, grpnam, fmt, args...) do {} while (0) +#endif + +#define lbs_deb_enter(grp) \ + LBS_DEB_LL(grp | LBS_DEB_ENTER, " enter", "%s():%d\n", __FUNCTION__, __LINE__); +#define lbs_deb_enter_args(grp, fmt, args...) \ + LBS_DEB_LL(grp | LBS_DEB_ENTER, " enter", "%s(" fmt "):%d\n", __FUNCTION__, ## args, __LINE__); +#define lbs_deb_leave(grp) \ + LBS_DEB_LL(grp | LBS_DEB_LEAVE, " leave", "%s():%d\n", __FUNCTION__, __LINE__); +#define lbs_deb_leave_args(grp, fmt, args...) \ + LBS_DEB_LL(grp | LBS_DEB_LEAVE, " leave", "%s():%d, " fmt "\n", \ + __FUNCTION__, __LINE__, ##args); +#define lbs_deb_main(fmt, args...) LBS_DEB_LL(LBS_DEB_MAIN, " main", fmt, ##args) +#define lbs_deb_net(fmt, args...) LBS_DEB_LL(LBS_DEB_NET, " net", fmt, ##args) +#define lbs_deb_mesh(fmt, args...) LBS_DEB_LL(LBS_DEB_MESH, " mesh", fmt, ##args) +#define lbs_deb_wext(fmt, args...) LBS_DEB_LL(LBS_DEB_WEXT, " wext", fmt, ##args) +#define lbs_deb_ioctl(fmt, args...) LBS_DEB_LL(LBS_DEB_IOCTL, " ioctl", fmt, ##args) +#define lbs_deb_scan(fmt, args...) LBS_DEB_LL(LBS_DEB_SCAN, " scan", fmt, ##args) +#define lbs_deb_assoc(fmt, args...) LBS_DEB_LL(LBS_DEB_ASSOC, " assoc", fmt, ##args) +#define lbs_deb_join(fmt, args...) LBS_DEB_LL(LBS_DEB_JOIN, " join", fmt, ##args) +#define lbs_deb_11d(fmt, args...) LBS_DEB_LL(LBS_DEB_11D, " 11d", fmt, ##args) +#define lbs_deb_debugfs(fmt, args...) LBS_DEB_LL(LBS_DEB_DEBUGFS, " debugfs", fmt, ##args) +#define lbs_deb_ethtool(fmt, args...) LBS_DEB_LL(LBS_DEB_ETHTOOL, " ethtool", fmt, ##args) +#define lbs_deb_host(fmt, args...) LBS_DEB_LL(LBS_DEB_HOST, " host", fmt, ##args) +#define lbs_deb_cmd(fmt, args...) LBS_DEB_LL(LBS_DEB_CMD, " cmd", fmt, ##args) +#define lbs_deb_rx(fmt, args...) LBS_DEB_LL(LBS_DEB_RX, " rx", fmt, ##args) +#define lbs_deb_tx(fmt, args...) LBS_DEB_LL(LBS_DEB_TX, " tx", fmt, ##args) +#define lbs_deb_fw(fmt, args...) LBS_DEB_LL(LBS_DEB_FW, " fw", fmt, ##args) +#define lbs_deb_usb(fmt, args...) LBS_DEB_LL(LBS_DEB_USB, " usb", fmt, ##args) +#define lbs_deb_usbd(dev, fmt, args...) LBS_DEB_LL(LBS_DEB_USB, " usbd", "%s:" fmt, (dev)->bus_id, ##args) +#define lbs_deb_cs(fmt, args...) LBS_DEB_LL(LBS_DEB_CS, " cs", fmt, ##args) +#define lbs_deb_thread(fmt, args...) LBS_DEB_LL(LBS_DEB_THREAD, " thread", fmt, ##args) +#define lbs_deb_sdio(fmt, args...) LBS_DEB_LL(LBS_DEB_SDIO, " thread", fmt, ##args) + +#define lbs_pr_info(format, args...) \ + printk(KERN_INFO DRV_NAME": " format, ## args) +#define lbs_pr_err(format, args...) \ + printk(KERN_ERR DRV_NAME": " format, ## args) +#define lbs_pr_alert(format, args...) \ + printk(KERN_ALERT DRV_NAME": " format, ## args) + +#ifdef DEBUG +static inline void lbs_deb_hex(unsigned int grp, const char *prompt, u8 *buf, int len) +{ + int i = 0; + + if (len && + (lbs_debug & LBS_DEB_HEX) && + (lbs_debug & grp)) + { + for (i = 1; i <= len; i++) { + if ((i & 0xf) == 1) { + if (i != 1) + printk("\n"); + printk(DRV_NAME " %s: ", prompt); + } + printk("%02x ", (u8) * buf); + buf++; + } + printk("\n"); + } +} +#else +#define lbs_deb_hex(grp,prompt,buf,len) do {} while (0) +#endif + + + +/** Buffer Constants */ + +/* The size of SQ memory PPA, DPA are 8 DWORDs, that keep the physical +* addresses of TxPD buffers. Station has only 8 TxPD available, Whereas +* driver has more local TxPDs. Each TxPD on the host memory is associated +* with a Tx control node. The driver maintains 8 RxPD descriptors for +* station firmware to store Rx packet information. +* +* Current version of MAC has a 32x6 multicast address buffer. +* +* 802.11b can have up to 14 channels, the driver keeps the +* BSSID(MAC address) of each APs or Ad hoc stations it has sensed. +*/ + +#define MRVDRV_MAX_MULTICAST_LIST_SIZE 32 +#define LBS_NUM_CMD_BUFFERS 10 +#define LBS_CMD_BUFFER_SIZE (2 * 1024) +#define MRVDRV_MAX_CHANNEL_SIZE 14 +#define MRVDRV_ASSOCIATION_TIME_OUT 255 +#define MRVDRV_SNAP_HEADER_LEN 8 + +#define LBS_UPLD_SIZE 2312 +#define DEV_NAME_LEN 32 + +/** Misc constants */ +/* This section defines 802.11 specific contants */ + +#define MRVDRV_MAX_BSS_DESCRIPTS 16 +#define MRVDRV_MAX_REGION_CODE 6 + +#define MRVDRV_IGNORE_MULTIPLE_DTIM 0xfffe +#define MRVDRV_MIN_MULTIPLE_DTIM 1 +#define MRVDRV_MAX_MULTIPLE_DTIM 5 +#define MRVDRV_DEFAULT_MULTIPLE_DTIM 1 + +#define MRVDRV_DEFAULT_LISTEN_INTERVAL 10 + +#define MRVDRV_CHANNELS_PER_SCAN 4 +#define MRVDRV_MAX_CHANNELS_PER_SCAN 14 + +#define MRVDRV_MIN_BEACON_INTERVAL 20 +#define MRVDRV_MAX_BEACON_INTERVAL 1000 +#define MRVDRV_BEACON_INTERVAL 100 + +#define MARVELL_MESH_IE_LENGTH 9 + +/** INT status Bit Definition*/ +#define MRVDRV_TX_DNLD_RDY 0x0001 +#define MRVDRV_RX_UPLD_RDY 0x0002 +#define MRVDRV_CMD_DNLD_RDY 0x0004 +#define MRVDRV_CMD_UPLD_RDY 0x0008 +#define MRVDRV_CARDEVENT 0x0010 + +#define SBI_EVENT_CAUSE_SHIFT 3 + +/** TxPD status */ + +/* Station firmware use TxPD status field to report final Tx transmit +* result, Bit masks are used to present combined situations. +*/ + +#define MRVDRV_TxPD_POWER_MGMT_NULL_PACKET 0x01 +#define MRVDRV_TxPD_POWER_MGMT_LAST_PACKET 0x08 + +/** Tx mesh flag */ +/* Currently we are using normal WDS flag as mesh flag. + * TODO: change to proper mesh flag when MAC understands it. + */ +#define TxPD_CONTROL_WDS_FRAME (1<<17) +#define TxPD_MESH_FRAME TxPD_CONTROL_WDS_FRAME + +/** RxPD status */ + +#define MRVDRV_RXPD_STATUS_OK 0x0001 + +/** RxPD status - Received packet types */ +/** Rx mesh flag */ +/* Currently we are using normal WDS flag as mesh flag. + * TODO: change to proper mesh flag when MAC understands it. + */ +#define RxPD_CONTROL_WDS_FRAME (0x40) +#define RxPD_MESH_FRAME RxPD_CONTROL_WDS_FRAME + +/** RSSI-related defines */ +/* RSSI constants are used to implement 802.11 RSSI threshold +* indication. if the Rx packet signal got too weak for 5 consecutive +* times, miniport driver (driver) will report this event to wrapper +*/ + +#define MRVDRV_NF_DEFAULT_SCAN_VALUE (-96) + +/** RTS/FRAG related defines */ +#define MRVDRV_RTS_MIN_VALUE 0 +#define MRVDRV_RTS_MAX_VALUE 2347 +#define MRVDRV_FRAG_MIN_VALUE 256 +#define MRVDRV_FRAG_MAX_VALUE 2346 + +/* This is for firmware specific length */ +#define EXTRA_LEN 36 + +#define MRVDRV_ETH_TX_PACKET_BUFFER_SIZE \ + (ETH_FRAME_LEN + sizeof(struct txpd) + EXTRA_LEN) + +#define MRVDRV_ETH_RX_PACKET_BUFFER_SIZE \ + (ETH_FRAME_LEN + sizeof(struct rxpd) \ + + MRVDRV_SNAP_HEADER_LEN + EXTRA_LEN) + +#define CMD_F_HOSTCMD (1 << 0) +#define FW_CAPINFO_WPA (1 << 0) + +#define KEY_LEN_WPA_AES 16 +#define KEY_LEN_WPA_TKIP 32 +#define KEY_LEN_WEP_104 13 +#define KEY_LEN_WEP_40 5 + +#define RF_ANTENNA_1 0x1 +#define RF_ANTENNA_2 0x2 +#define RF_ANTENNA_AUTO 0xFFFF + +#define BAND_B (0x01) +#define BAND_G (0x02) +#define ALL_802_11_BANDS (BAND_B | BAND_G) + +/** MACRO DEFINITIONS */ +#define CAL_NF(NF) ((s32)(-(s32)(NF))) +#define CAL_RSSI(SNR, NF) ((s32)((s32)(SNR) + CAL_NF(NF))) +#define SCAN_RSSI(RSSI) (0x100 - ((u8)(RSSI))) + +#define DEFAULT_BCN_AVG_FACTOR 8 +#define DEFAULT_DATA_AVG_FACTOR 8 +#define AVG_SCALE 100 +#define CAL_AVG_SNR_NF(AVG, SNRNF, N) \ + (((AVG) == 0) ? ((u16)(SNRNF) * AVG_SCALE) : \ + ((((int)(AVG) * (N -1)) + ((u16)(SNRNF) * \ + AVG_SCALE)) / N)) + +#define MAX_RATES 14 + +#define MAX_LEDS 8 + +/** Global Variable Declaration */ +extern const char lbs_driver_version[]; +extern u16 lbs_region_code_to_index[MRVDRV_MAX_REGION_CODE]; + +extern u8 lbs_bg_rates[MAX_RATES]; + +/** ENUM definition*/ +/** SNRNF_TYPE */ +enum SNRNF_TYPE { + TYPE_BEACON = 0, + TYPE_RXPD, + MAX_TYPE_B +}; + +/** SNRNF_DATA*/ +enum SNRNF_DATA { + TYPE_NOAVG = 0, + TYPE_AVG, + MAX_TYPE_AVG +}; + +/** LBS_802_11_POWER_MODE */ +enum LBS_802_11_POWER_MODE { + LBS802_11POWERMODECAM, + LBS802_11POWERMODEMAX_PSP, + LBS802_11POWERMODEFAST_PSP, + /*not a real mode, defined as an upper bound */ + LBS802_11POWEMODEMAX +}; + +/** PS_STATE */ +enum PS_STATE { + PS_STATE_FULL_POWER, + PS_STATE_AWAKE, + PS_STATE_PRE_SLEEP, + PS_STATE_SLEEP +}; + +/** DNLD_STATE */ +enum DNLD_STATE { + DNLD_RES_RECEIVED, + DNLD_DATA_SENT, + DNLD_CMD_SENT +}; + +/** LBS_MEDIA_STATE */ +enum LBS_MEDIA_STATE { + LBS_CONNECTED, + LBS_DISCONNECTED +}; + +/** LBS_802_11_PRIVACY_FILTER */ +enum LBS_802_11_PRIVACY_FILTER { + LBS802_11PRIVFILTERACCEPTALL, + LBS802_11PRIVFILTER8021XWEP +}; + +/** mv_ms_type */ +enum mv_ms_type { + MVMS_DAT = 0, + MVMS_CMD = 1, + MVMS_TXDONE = 2, + MVMS_EVENT +}; + +/** SNMP_MIB_INDEX_e */ +enum SNMP_MIB_INDEX_e { + DESIRED_BSSTYPE_I = 0, + OP_RATESET_I, + BCNPERIOD_I, + DTIMPERIOD_I, + ASSOCRSP_TIMEOUT_I, + RTSTHRESH_I, + SHORT_RETRYLIM_I, + LONG_RETRYLIM_I, + FRAGTHRESH_I, + DOT11D_I, + DOT11H_I, + MANUFID_I, + PRODID_I, + MANUF_OUI_I, + MANUF_NAME_I, + MANUF_PRODNAME_I, + MANUF_PRODVER_I, +}; + +/** KEY_TYPE_ID */ +enum KEY_TYPE_ID { + KEY_TYPE_ID_WEP = 0, + KEY_TYPE_ID_TKIP, + KEY_TYPE_ID_AES +}; + +/** KEY_INFO_WPA (applies to both TKIP and AES/CCMP) */ +enum KEY_INFO_WPA { + KEY_INFO_WPA_MCAST = 0x01, + KEY_INFO_WPA_UNICAST = 0x02, + KEY_INFO_WPA_ENABLED = 0x04 +}; + +/** SNMP_MIB_VALUE_e */ +enum SNMP_MIB_VALUE_e { + SNMP_MIB_VALUE_INFRA = 1, + SNMP_MIB_VALUE_ADHOC +}; + +/* Default values for fwt commands. */ +#define FWT_DEFAULT_METRIC 0 +#define FWT_DEFAULT_DIR 1 +/* Default Rate, 11Mbps */ +#define FWT_DEFAULT_RATE 3 +#define FWT_DEFAULT_SSN 0xffffffff +#define FWT_DEFAULT_DSN 0 +#define FWT_DEFAULT_HOPCOUNT 0 +#define FWT_DEFAULT_TTL 0 +#define FWT_DEFAULT_EXPIRATION 0 +#define FWT_DEFAULT_SLEEPMODE 0 +#define FWT_DEFAULT_SNR 0 + +#endif diff --git a/package/libertas/src/dev.h b/package/libertas/src/dev.h new file mode 100644 index 0000000000..86b45a471f --- /dev/null +++ b/package/libertas/src/dev.h @@ -0,0 +1,365 @@ +/** + * This file contains definitions and data structures specific + * to Marvell 802.11 NIC. It contains the Device Information + * structure struct lbs_private.. + */ +#ifndef _LBS_DEV_H_ +#define _LBS_DEV_H_ + +#include <linux/netdevice.h> +#include <linux/wireless.h> +#include <linux/ethtool.h> +#include <linux/debugfs.h> + +#include "defs.h" +#include "scan.h" + +extern struct ethtool_ops lbs_ethtool_ops; + +#define MAX_BSSID_PER_CHANNEL 16 + +#define NR_TX_QUEUE 3 + +/* For the extended Scan */ +#define MAX_EXTENDED_SCAN_BSSID_LIST MAX_BSSID_PER_CHANNEL * \ + MRVDRV_MAX_CHANNEL_SIZE + 1 + +#define MAX_REGION_CHANNEL_NUM 2 + +/** Chan-freq-TxPower mapping table*/ +struct chan_freq_power { + /** channel Number */ + u16 channel; + /** frequency of this channel */ + u32 freq; + /** Max allowed Tx power level */ + u16 maxtxpower; + /** TRUE:channel unsupported; FLASE:supported*/ + u8 unsupported; +}; + +/** region-band mapping table*/ +struct region_channel { + /** TRUE if this entry is valid */ + u8 valid; + /** region code for US, Japan ... */ + u8 region; + /** band B/G/A, used for BAND_CONFIG cmd */ + u8 band; + /** Actual No. of elements in the array below */ + u8 nrcfp; + /** chan-freq-txpower mapping table*/ + struct chan_freq_power *CFP; +}; + +struct lbs_802_11_security { + u8 WPAenabled; + u8 WPA2enabled; + u8 wep_enabled; + u8 auth_mode; +}; + +/** Current Basic Service Set State Structure */ +struct current_bss_params { + /** bssid */ + u8 bssid[ETH_ALEN]; + /** ssid */ + u8 ssid[IW_ESSID_MAX_SIZE + 1]; + u8 ssid_len; + + /** band */ + u8 band; + /** channel */ + u8 channel; + /** zero-terminated array of supported data rates */ + u8 rates[MAX_RATES + 1]; +}; + +/** sleep_params */ +struct sleep_params { + u16 sp_error; + u16 sp_offset; + u16 sp_stabletime; + u8 sp_calcontrol; + u8 sp_extsleepclk; + u16 sp_reserved; +}; + +/* Mesh statistics */ +struct lbs_mesh_stats { + u32 fwd_bcast_cnt; /* Fwd: Broadcast counter */ + u32 fwd_unicast_cnt; /* Fwd: Unicast counter */ + u32 fwd_drop_ttl; /* Fwd: TTL zero */ + u32 fwd_drop_rbt; /* Fwd: Recently Broadcasted */ + u32 fwd_drop_noroute; /* Fwd: No route to Destination */ + u32 fwd_drop_nobuf; /* Fwd: Run out of internal buffers */ + u32 drop_blind; /* Rx: Dropped by blinding table */ + u32 tx_failed_cnt; /* Tx: Failed transmissions */ +}; + +/** Private structure for the MV device */ +struct lbs_private { + int mesh_open; + int infra_open; + int mesh_autostart_enabled; + __le16 boot2_version; + + char name[DEV_NAME_LEN]; + + void *card; + struct net_device *dev; + + struct net_device_stats stats; + struct net_device *mesh_dev; /* Virtual device */ + struct net_device *rtap_net_dev; + + struct iw_statistics wstats; + struct lbs_mesh_stats mstats; + struct dentry *debugfs_dir; + struct dentry *debugfs_debug; + struct dentry *debugfs_files[6]; + + struct dentry *events_dir; + struct dentry *debugfs_events_files[6]; + + struct dentry *regs_dir; + struct dentry *debugfs_regs_files[6]; + + u32 mac_offset; + u32 bbp_offset; + u32 rf_offset; + + /** Upload length */ + u32 upld_len; + /* Upload buffer */ + u8 upld_buf[LBS_UPLD_SIZE]; + /* Download sent: + bit0 1/0=data_sent/data_tx_done, + bit1 1/0=cmd_sent/cmd_tx_done, + all other bits reserved 0 */ + u8 dnld_sent; + + /** thread to service interrupts */ + struct task_struct *main_thread; + wait_queue_head_t waitq; + struct workqueue_struct *work_thread; + + struct delayed_work scan_work; + struct delayed_work assoc_work; + struct work_struct sync_channel; + + /** Hardware access */ + int (*hw_host_to_card) (struct lbs_private *priv, u8 type, u8 *payload, u16 nb); + int (*hw_get_int_status) (struct lbs_private *priv, u8 *); + int (*hw_read_event_cause) (struct lbs_private *); + + /* was struct lbs_adapter from here... */ + + /** Wlan adapter data structure*/ + /** STATUS variables */ + u8 fwreleasenumber[4]; + u32 fwcapinfo; + /* protected with big lock */ + + struct mutex lock; + + /* TX packet ready to be sent... */ + int tx_pending_len; /* -1 while building packet */ + + u8 tx_pending_buf[LBS_UPLD_SIZE]; + /* protected by hard_start_xmit serialization */ + + /** command-related variables */ + u16 seqnum; + /* protected by big lock */ + + struct cmd_ctrl_node *cmd_array; + /** Current command */ + struct cmd_ctrl_node *cur_cmd; + int cur_cmd_retcode; + /** command Queues */ + /** Free command buffers */ + struct list_head cmdfreeq; + /** Pending command buffers */ + struct list_head cmdpendingq; + + wait_queue_head_t cmd_pending; + /* command related variables protected by priv->driver_lock */ + + /** Async and Sync Event variables */ + u32 intcounter; + u32 eventcause; + u8 nodename[16]; /* nickname */ + + /** spin locks */ + spinlock_t driver_lock; + + /** Timers */ + struct timer_list command_timer; + + u8 hisregcpy; + + /** current ssid/bssid related parameters*/ + struct current_bss_params curbssparams; + u8 mesh_ssid[IW_ESSID_MAX_SIZE + 1]; + u8 mesh_ssid_len; + + /* IW_MODE_* */ + u8 mode; + + /* Scan results list */ + struct list_head network_list; + struct list_head network_free_list; + struct bss_descriptor *networks; + + u16 beacon_period; + u8 beacon_enable; + u8 adhoccreate; + + /** capability Info used in Association, start, join */ + u16 capability; + + /** MAC address information */ + u8 current_addr[ETH_ALEN]; + u8 multicastlist[MRVDRV_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; + u32 nr_of_multicastmacaddr; + + /** 802.11 statistics */ +// struct cmd_DS_802_11_GET_STAT wlan802_11Stat; + + u16 enablehwauto; + u16 ratebitmap; + + u32 fragthsd; + u32 rtsthsd; + + u8 txretrycount; + + /** Tx-related variables (for single packet tx) */ + struct sk_buff *currenttxskb; + + /** NIC Operation characteristics */ + u16 currentpacketfilter; + u32 connect_status; + u32 mesh_connect_status; + u16 regioncode; + u16 txpowerlevel; + + /** POWER MANAGEMENT AND PnP SUPPORT */ + u8 surpriseremoved; + + u16 psmode; /* Wlan802_11PowermodeCAM=disable + Wlan802_11PowermodeMAX_PSP=enable */ + u32 psstate; + u8 needtowakeup; + + struct PS_CMD_ConfirmSleep lbs_ps_confirm_sleep; + + struct assoc_request * pending_assoc_req; + struct assoc_request * in_progress_assoc_req; + + /** Encryption parameter */ + struct lbs_802_11_security secinfo; + + /** WEP keys */ + struct enc_key wep_keys[4]; + u16 wep_tx_keyidx; + + /** WPA keys */ + struct enc_key wpa_mcast_key; + struct enc_key wpa_unicast_key; + + /** WPA Information Elements*/ + u8 wpa_ie[MAX_WPA_IE_LEN]; + u8 wpa_ie_len; + + /** Requested Signal Strength*/ + u16 SNR[MAX_TYPE_B][MAX_TYPE_AVG]; + u16 NF[MAX_TYPE_B][MAX_TYPE_AVG]; + u8 RSSI[MAX_TYPE_B][MAX_TYPE_AVG]; + u8 rawSNR[DEFAULT_DATA_AVG_FACTOR]; + u8 rawNF[DEFAULT_DATA_AVG_FACTOR]; + u16 nextSNRNF; + u16 numSNRNF; + + u8 radioon; + u32 preamble; + + /** data rate stuff */ + u8 cur_rate; + u8 auto_rate; + + /** sleep_params */ + struct sleep_params sp; + + /** RF calibration data */ + +#define MAX_REGION_CHANNEL_NUM 2 + /** region channel data */ + struct region_channel region_channel[MAX_REGION_CHANNEL_NUM]; + + struct region_channel universal_channel[MAX_REGION_CHANNEL_NUM]; + + /** 11D and Domain Regulatory Data */ + struct lbs_802_11d_domain_reg domainreg; + struct parsed_region_chan_11d parsed_region_chan; + + /** FSM variable for 11d support */ + u32 enable11d; + + /** MISCELLANEOUS */ + u8 *prdeeprom; + struct lbs_offset_value offsetvalue; + + struct cmd_ds_802_11_get_log logmsg; + + u32 monitormode; + int last_scanned_channel; + u8 fw_ready; +}; + +/** Association request + * + * Encapsulates all the options that describe a specific assocation request + * or configuration of the wireless card's radio, mode, and security settings. + */ +struct assoc_request { +#define ASSOC_FLAG_SSID 1 +#define ASSOC_FLAG_CHANNEL 2 +#define ASSOC_FLAG_BAND 3 +#define ASSOC_FLAG_MODE 4 +#define ASSOC_FLAG_BSSID 5 +#define ASSOC_FLAG_WEP_KEYS 6 +#define ASSOC_FLAG_WEP_TX_KEYIDX 7 +#define ASSOC_FLAG_WPA_MCAST_KEY 8 +#define ASSOC_FLAG_WPA_UCAST_KEY 9 +#define ASSOC_FLAG_SECINFO 10 +#define ASSOC_FLAG_WPA_IE 11 + unsigned long flags; + + u8 ssid[IW_ESSID_MAX_SIZE + 1]; + u8 ssid_len; + u8 channel; + u8 band; + u8 mode; + u8 bssid[ETH_ALEN]; + + /** WEP keys */ + struct enc_key wep_keys[4]; + u16 wep_tx_keyidx; + + /** WPA keys */ + struct enc_key wpa_mcast_key; + struct enc_key wpa_unicast_key; + + struct lbs_802_11_security secinfo; + + /** WPA Information Elements*/ + u8 wpa_ie[MAX_WPA_IE_LEN]; + u8 wpa_ie_len; + + /* BSS to associate with for infrastructure of Ad-Hoc join */ + struct bss_descriptor bss; +}; + +#endif diff --git a/package/libertas/src/ethtool.c b/package/libertas/src/ethtool.c new file mode 100644 index 0000000000..98757f1e81 --- /dev/null +++ b/package/libertas/src/ethtool.c @@ -0,0 +1,172 @@ +#include <linux/netdevice.h> +#include <linux/ethtool.h> +#include <linux/delay.h> + +#include "host.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "join.h" +#include "wext.h" +static const char * mesh_stat_strings[]= { + "drop_duplicate_bcast", + "drop_ttl_zero", + "drop_no_fwd_route", + "drop_no_buffers", + "fwded_unicast_cnt", + "fwded_bcast_cnt", + "drop_blind_table", + "tx_failed_cnt" +}; + +static void lbs_ethtool_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + struct lbs_private *priv = (struct lbs_private *) dev->priv; + char fwver[32]; + + lbs_get_fwversion(priv, fwver, sizeof(fwver) - 1); + + strcpy(info->driver, "libertas"); + strcpy(info->version, lbs_driver_version); + strcpy(info->fw_version, fwver); +} + +/* All 8388 parts have 16KiB EEPROM size at the time of writing. + * In case that changes this needs fixing. + */ +#define LBS_EEPROM_LEN 16384 + +static int lbs_ethtool_get_eeprom_len(struct net_device *dev) +{ + return LBS_EEPROM_LEN; +} + +static int lbs_ethtool_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 * bytes) +{ + struct lbs_private *priv = (struct lbs_private *) dev->priv; + struct lbs_ioctl_regrdwr regctrl; + char *ptr; + int ret; + + regctrl.action = 0; + regctrl.offset = eeprom->offset; + regctrl.NOB = eeprom->len; + + if (eeprom->offset + eeprom->len > LBS_EEPROM_LEN) + return -EINVAL; + +// mutex_lock(&priv->mutex); + + priv->prdeeprom = kmalloc(eeprom->len+sizeof(regctrl), GFP_KERNEL); + if (!priv->prdeeprom) + return -ENOMEM; + memcpy(priv->prdeeprom, ®ctrl, sizeof(regctrl)); + + /* +14 is for action, offset, and NOB in + * response */ + lbs_deb_ethtool("action:%d offset: %x NOB: %02x\n", + regctrl.action, regctrl.offset, regctrl.NOB); + + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_EEPROM_ACCESS, + regctrl.action, + CMD_OPTION_WAITFORRSP, 0, + ®ctrl); + + if (ret) { + if (priv->prdeeprom) + kfree(priv->prdeeprom); + goto done; + } + + mdelay(10); + + ptr = (char *)priv->prdeeprom; + + /* skip the command header, but include the "value" u32 variable */ + ptr = ptr + sizeof(struct lbs_ioctl_regrdwr) - 4; + + /* + * Return the result back to the user + */ + memcpy(bytes, ptr, eeprom->len); + + if (priv->prdeeprom) + kfree(priv->prdeeprom); +// mutex_unlock(&priv->mutex); + + ret = 0; + +done: + lbs_deb_enter_args(LBS_DEB_ETHTOOL, "ret %d", ret); + return ret; +} + +static void lbs_ethtool_get_stats(struct net_device * dev, + struct ethtool_stats * stats, u64 * data) +{ + struct lbs_private *priv = dev->priv; + struct cmd_ds_mesh_access mesh_access; + int ret; + + lbs_deb_enter(LBS_DEB_ETHTOOL); + + /* Get Mesh Statistics */ + ret = lbs_prepare_and_send_command(priv, + CMD_MESH_ACCESS, CMD_ACT_MESH_GET_STATS, + CMD_OPTION_WAITFORRSP, 0, &mesh_access); + + if (ret) + return; + + priv->mstats.fwd_drop_rbt = le32_to_cpu(mesh_access.data[0]); + priv->mstats.fwd_drop_ttl = le32_to_cpu(mesh_access.data[1]); + priv->mstats.fwd_drop_noroute = le32_to_cpu(mesh_access.data[2]); + priv->mstats.fwd_drop_nobuf = le32_to_cpu(mesh_access.data[3]); + priv->mstats.fwd_unicast_cnt = le32_to_cpu(mesh_access.data[4]); + priv->mstats.fwd_bcast_cnt = le32_to_cpu(mesh_access.data[5]); + priv->mstats.drop_blind = le32_to_cpu(mesh_access.data[6]); + priv->mstats.tx_failed_cnt = le32_to_cpu(mesh_access.data[7]); + + data[0] = priv->mstats.fwd_drop_rbt; + data[1] = priv->mstats.fwd_drop_ttl; + data[2] = priv->mstats.fwd_drop_noroute; + data[3] = priv->mstats.fwd_drop_nobuf; + data[4] = priv->mstats.fwd_unicast_cnt; + data[5] = priv->mstats.fwd_bcast_cnt; + data[6] = priv->mstats.drop_blind; + data[7] = priv->mstats.tx_failed_cnt; + + lbs_deb_enter(LBS_DEB_ETHTOOL); +} + +static void lbs_ethtool_get_strings(struct net_device *dev, + u32 stringset, + u8 * s) +{ + int i; + + lbs_deb_enter(LBS_DEB_ETHTOOL); + + switch (stringset) { + case ETH_SS_STATS: + for (i=0; i < MESH_STATS_NUM; i++) { + memcpy(s + i * ETH_GSTRING_LEN, + mesh_stat_strings[i], + ETH_GSTRING_LEN); + } + break; + } + lbs_deb_enter(LBS_DEB_ETHTOOL); +} + +struct ethtool_ops lbs_ethtool_ops = { + .get_drvinfo = lbs_ethtool_get_drvinfo, + .get_eeprom = lbs_ethtool_get_eeprom, + .get_eeprom_len = lbs_ethtool_get_eeprom_len, + .get_ethtool_stats = lbs_ethtool_get_stats, + .get_strings = lbs_ethtool_get_strings, +}; + diff --git a/package/libertas/src/host.h b/package/libertas/src/host.h new file mode 100644 index 0000000000..64178cff2f --- /dev/null +++ b/package/libertas/src/host.h @@ -0,0 +1,286 @@ +/** + * This file contains definitions of WLAN commands. + */ + +#ifndef _LBS_HOST_H_ +#define _LBS_HOST_H_ + +/** PUBLIC DEFINITIONS */ +#define DEFAULT_AD_HOC_CHANNEL 6 +#define DEFAULT_AD_HOC_CHANNEL_A 36 + +/** IEEE 802.11 oids */ +#define OID_802_11_SSID 0x00008002 +#define OID_802_11_INFRASTRUCTURE_MODE 0x00008008 +#define OID_802_11_FRAGMENTATION_THRESHOLD 0x00008009 +#define OID_802_11_RTS_THRESHOLD 0x0000800A +#define OID_802_11_TX_ANTENNA_SELECTED 0x0000800D +#define OID_802_11_SUPPORTED_RATES 0x0000800E +#define OID_802_11_STATISTICS 0x00008012 +#define OID_802_11_TX_RETRYCOUNT 0x0000801D +#define OID_802_11D_ENABLE 0x00008020 + +#define CMD_OPTION_WAITFORRSP 0x0002 + +/** Host command IDs */ + +/* Return command are almost always the same as the host command, but with + * bit 15 set high. There are a few exceptions, though... + */ +#define CMD_RET(cmd) (0x8000 | cmd) + +/* Return command convention exceptions: */ +#define CMD_RET_802_11_ASSOCIATE 0x8012 + +/* Command codes */ +#define CMD_CODE_DNLD 0x0002 +#define CMD_GET_HW_SPEC 0x0003 +#define CMD_EEPROM_UPDATE 0x0004 +#define CMD_802_11_RESET 0x0005 +#define CMD_802_11_SCAN 0x0006 +#define CMD_802_11_GET_LOG 0x000b +#define CMD_MAC_MULTICAST_ADR 0x0010 +#define CMD_802_11_AUTHENTICATE 0x0011 +#define CMD_802_11_EEPROM_ACCESS 0x0059 +#define CMD_802_11_ASSOCIATE 0x0050 +#define CMD_802_11_SET_WEP 0x0013 +#define CMD_802_11_GET_STAT 0x0014 +#define CMD_802_3_GET_STAT 0x0015 +#define CMD_802_11_SNMP_MIB 0x0016 +#define CMD_MAC_REG_MAP 0x0017 +#define CMD_BBP_REG_MAP 0x0018 +#define CMD_MAC_REG_ACCESS 0x0019 +#define CMD_BBP_REG_ACCESS 0x001a +#define CMD_RF_REG_ACCESS 0x001b +#define CMD_802_11_RADIO_CONTROL 0x001c +#define CMD_802_11_RF_CHANNEL 0x001d +#define CMD_802_11_RF_TX_POWER 0x001e +#define CMD_802_11_RSSI 0x001f +#define CMD_802_11_RF_ANTENNA 0x0020 +#define CMD_802_11_PS_MODE 0x0021 +#define CMD_802_11_DATA_RATE 0x0022 +#define CMD_RF_REG_MAP 0x0023 +#define CMD_802_11_DEAUTHENTICATE 0x0024 +#define CMD_802_11_REASSOCIATE 0x0025 +#define CMD_802_11_DISASSOCIATE 0x0026 +#define CMD_MAC_CONTROL 0x0028 +#define CMD_802_11_AD_HOC_START 0x002b +#define CMD_802_11_AD_HOC_JOIN 0x002c +#define CMD_802_11_QUERY_TKIP_REPLY_CNTRS 0x002e +#define CMD_802_11_ENABLE_RSN 0x002f +#define CMD_802_11_PAIRWISE_TSC 0x0036 +#define CMD_802_11_GROUP_TSC 0x0037 +#define CMD_802_11_SET_AFC 0x003c +#define CMD_802_11_GET_AFC 0x003d +#define CMD_802_11_AD_HOC_STOP 0x0040 +#define CMD_802_11_BEACON_STOP 0x0049 +#define CMD_802_11_MAC_ADDRESS 0x004d +#define CMD_802_11_LED_GPIO_CTRL 0x004e +#define CMD_802_11_EEPROM_ACCESS 0x0059 +#define CMD_802_11_BAND_CONFIG 0x0058 +#define CMD_802_11D_DOMAIN_INFO 0x005b +#define CMD_802_11_KEY_MATERIAL 0x005e +#define CMD_802_11_SLEEP_PARAMS 0x0066 +#define CMD_802_11_INACTIVITY_TIMEOUT 0x0067 +#define CMD_802_11_TPC_CFG 0x0072 +#define CMD_802_11_PWR_CFG 0x0073 +#define CMD_802_11_SUBSCRIBE_EVENT 0x0075 +#define CMD_802_11_RATE_ADAPT_RATESET 0x0076 +#define CMD_802_11_TX_RATE_QUERY 0x007f +#define CMD_GET_TSF 0x0080 +#define CMD_BT_ACCESS 0x0087 +#define CMD_FWT_ACCESS 0x0095 +#define CMD_802_11_MONITOR_MODE 0x0098 +#define CMD_MESH_ACCESS 0x009b +#define CMD_MESH_CONFIG 0x00a3 +#define CMD_SET_BOOT2_VER 0x00a5 +#define CMD_802_11_BEACON_CTRL 0x00b0 + +/* For the IEEE Power Save */ +#define CMD_SUBCMD_ENTER_PS 0x0030 +#define CMD_SUBCMD_EXIT_PS 0x0031 +#define CMD_SUBCMD_SLEEP_CONFIRMED 0x0034 +#define CMD_SUBCMD_FULL_POWERDOWN 0x0035 +#define CMD_SUBCMD_FULL_POWERUP 0x0036 + +#define CMD_ENABLE_RSN 0x0001 +#define CMD_DISABLE_RSN 0x0000 + +#define CMD_ACT_GET 0x0000 +#define CMD_ACT_SET 0x0001 +#define CMD_ACT_GET_AES 0x0002 +#define CMD_ACT_SET_AES 0x0003 +#define CMD_ACT_REMOVE_AES 0x0004 + +/* Define action or option for CMD_802_11_SET_WEP */ +#define CMD_ACT_ADD 0x0002 +#define CMD_ACT_REMOVE 0x0004 +#define CMD_ACT_USE_DEFAULT 0x0008 + +#define CMD_TYPE_WEP_40_BIT 0x01 +#define CMD_TYPE_WEP_104_BIT 0x02 + +#define CMD_NUM_OF_WEP_KEYS 4 + +#define CMD_WEP_KEY_INDEX_MASK 0x3fff + +/* Define action or option for CMD_802_11_RESET */ +#define CMD_ACT_HALT 0x0003 + +/* Define action or option for CMD_802_11_SCAN */ +#define CMD_BSS_TYPE_BSS 0x0001 +#define CMD_BSS_TYPE_IBSS 0x0002 +#define CMD_BSS_TYPE_ANY 0x0003 + +/* Define action or option for CMD_802_11_SCAN */ +#define CMD_SCAN_TYPE_ACTIVE 0x0000 +#define CMD_SCAN_TYPE_PASSIVE 0x0001 + +#define CMD_SCAN_RADIO_TYPE_BG 0 + +#define CMD_SCAN_PROBE_DELAY_TIME 0 + +/* Define action or option for CMD_MAC_CONTROL */ +#define CMD_ACT_MAC_RX_ON 0x0001 +#define CMD_ACT_MAC_TX_ON 0x0002 +#define CMD_ACT_MAC_LOOPBACK_ON 0x0004 +#define CMD_ACT_MAC_WEP_ENABLE 0x0008 +#define CMD_ACT_MAC_INT_ENABLE 0x0010 +#define CMD_ACT_MAC_MULTICAST_ENABLE 0x0020 +#define CMD_ACT_MAC_BROADCAST_ENABLE 0x0040 +#define CMD_ACT_MAC_PROMISCUOUS_ENABLE 0x0080 +#define CMD_ACT_MAC_ALL_MULTICAST_ENABLE 0x0100 +#define CMD_ACT_MAC_STRICT_PROTECTION_ENABLE 0x0400 + +/* Define action or option for CMD_802_11_RADIO_CONTROL */ +#define CMD_TYPE_AUTO_PREAMBLE 0x0001 +#define CMD_TYPE_SHORT_PREAMBLE 0x0002 +#define CMD_TYPE_LONG_PREAMBLE 0x0003 + +/* Event flags for CMD_802_11_SUBSCRIBE_EVENT */ +#define CMD_SUBSCRIBE_RSSI_LOW 0x0001 +#define CMD_SUBSCRIBE_SNR_LOW 0x0002 +#define CMD_SUBSCRIBE_FAILCOUNT 0x0004 +#define CMD_SUBSCRIBE_BCNMISS 0x0008 +#define CMD_SUBSCRIBE_RSSI_HIGH 0x0010 +#define CMD_SUBSCRIBE_SNR_HIGH 0x0020 + +#define TURN_ON_RF 0x01 +#define RADIO_ON 0x01 +#define RADIO_OFF 0x00 + +#define SET_AUTO_PREAMBLE 0x05 +#define SET_SHORT_PREAMBLE 0x03 +#define SET_LONG_PREAMBLE 0x01 + +/* Define action or option for CMD_802_11_RF_CHANNEL */ +#define CMD_OPT_802_11_RF_CHANNEL_GET 0x00 +#define CMD_OPT_802_11_RF_CHANNEL_SET 0x01 + +/* Define action or option for CMD_802_11_RF_TX_POWER */ +#define CMD_ACT_TX_POWER_OPT_GET 0x0000 +#define CMD_ACT_TX_POWER_OPT_SET_HIGH 0x8007 +#define CMD_ACT_TX_POWER_OPT_SET_MID 0x8004 +#define CMD_ACT_TX_POWER_OPT_SET_LOW 0x8000 + +#define CMD_ACT_TX_POWER_INDEX_HIGH 0x0007 +#define CMD_ACT_TX_POWER_INDEX_MID 0x0004 +#define CMD_ACT_TX_POWER_INDEX_LOW 0x0000 + +/* Define action or option for CMD_802_11_DATA_RATE */ +#define CMD_ACT_SET_TX_AUTO 0x0000 +#define CMD_ACT_SET_TX_FIX_RATE 0x0001 +#define CMD_ACT_GET_TX_RATE 0x0002 + +#define CMD_ACT_SET_RX 0x0001 +#define CMD_ACT_SET_TX 0x0002 +#define CMD_ACT_SET_BOTH 0x0003 +#define CMD_ACT_GET_RX 0x0004 +#define CMD_ACT_GET_TX 0x0008 +#define CMD_ACT_GET_BOTH 0x000c + +/* Define action or option for CMD_802_11_PS_MODE */ +#define CMD_TYPE_CAM 0x0000 +#define CMD_TYPE_MAX_PSP 0x0001 +#define CMD_TYPE_FAST_PSP 0x0002 + +/* Define action or option for CMD_BT_ACCESS */ +enum cmd_bt_access_opts { + /* The bt commands start at 5 instead of 1 because the old dft commands + * are mapped to 1-4. These old commands are no longer maintained and + * should not be called. + */ + CMD_ACT_BT_ACCESS_ADD = 5, + CMD_ACT_BT_ACCESS_DEL, + CMD_ACT_BT_ACCESS_LIST, + CMD_ACT_BT_ACCESS_RESET, + CMD_ACT_BT_ACCESS_SET_INVERT, + CMD_ACT_BT_ACCESS_GET_INVERT +}; + +/* Define action or option for CMD_FWT_ACCESS */ +enum cmd_fwt_access_opts { + CMD_ACT_FWT_ACCESS_ADD = 1, + CMD_ACT_FWT_ACCESS_DEL, + CMD_ACT_FWT_ACCESS_LOOKUP, + CMD_ACT_FWT_ACCESS_LIST, + CMD_ACT_FWT_ACCESS_LIST_ROUTE, + CMD_ACT_FWT_ACCESS_LIST_NEIGHBOR, + CMD_ACT_FWT_ACCESS_RESET, + CMD_ACT_FWT_ACCESS_CLEANUP, + CMD_ACT_FWT_ACCESS_TIME, +}; + +/* Define action or option for CMD_MESH_ACCESS */ +enum cmd_mesh_access_opts { + CMD_ACT_MESH_GET_TTL = 1, + CMD_ACT_MESH_SET_TTL, + CMD_ACT_MESH_GET_STATS, + CMD_ACT_MESH_GET_ANYCAST, + CMD_ACT_MESH_SET_ANYCAST, + CMD_ACT_MESH_SET_LINK_COSTS, + CMD_ACT_MESH_GET_LINK_COSTS, + CMD_ACT_MESH_SET_BCAST_RATE, + CMD_ACT_MESH_GET_BCAST_RATE, + CMD_ACT_MESH_SET_RREQ_DELAY, + CMD_ACT_MESH_GET_RREQ_DELAY, + CMD_ACT_MESH_SET_ROUTE_EXP, + CMD_ACT_MESH_GET_ROUTE_EXP, + CMD_ACT_MESH_SET_AUTOSTART_ENABLED, + CMD_ACT_MESH_GET_AUTOSTART_ENABLED, +}; + +/** Card Event definition */ +#define MACREG_INT_CODE_TX_PPA_FREE 0 +#define MACREG_INT_CODE_TX_DMA_DONE 1 +#define MACREG_INT_CODE_LINK_LOST_W_SCAN 2 +#define MACREG_INT_CODE_LINK_LOST_NO_SCAN 3 +#define MACREG_INT_CODE_LINK_SENSED 4 +#define MACREG_INT_CODE_CMD_FINISHED 5 +#define MACREG_INT_CODE_MIB_CHANGED 6 +#define MACREG_INT_CODE_INIT_DONE 7 +#define MACREG_INT_CODE_DEAUTHENTICATED 8 +#define MACREG_INT_CODE_DISASSOCIATED 9 +#define MACREG_INT_CODE_PS_AWAKE 10 +#define MACREG_INT_CODE_PS_SLEEP 11 +#define MACREG_INT_CODE_MIC_ERR_MULTICAST 13 +#define MACREG_INT_CODE_MIC_ERR_UNICAST 14 +#define MACREG_INT_CODE_WM_AWAKE 15 +#define MACREG_INT_CODE_DEEP_SLEEP_AWAKE 16 +#define MACREG_INT_CODE_ADHOC_BCN_LOST 17 +#define MACREG_INT_CODE_HOST_AWAKE 18 +#define MACREG_INT_CODE_STOP_TX 19 +#define MACREG_INT_CODE_START_TX 20 +#define MACREG_INT_CODE_CHANNEL_SWITCH 21 +#define MACREG_INT_CODE_MEASUREMENT_RDY 22 +#define MACREG_INT_CODE_WMM_CHANGE 23 +#define MACREG_INT_CODE_BG_SCAN_REPORT 24 +#define MACREG_INT_CODE_RSSI_LOW 25 +#define MACREG_INT_CODE_SNR_LOW 26 +#define MACREG_INT_CODE_MAX_FAIL 27 +#define MACREG_INT_CODE_RSSI_HIGH 28 +#define MACREG_INT_CODE_SNR_HIGH 29 +#define MACREG_INT_CODE_MESH_AUTO_STARTED 35 +#define MACREG_INT_CODE_FIRMWARE_READY 48 + +#endif diff --git a/package/libertas/src/hostcmd.h b/package/libertas/src/hostcmd.h new file mode 100644 index 0000000000..aab5d64f32 --- /dev/null +++ b/package/libertas/src/hostcmd.h @@ -0,0 +1,713 @@ +/* + * This file contains the function prototypes, data structure + * and defines for all the host/station commands + */ +#ifndef _LBS_HOSTCMD_H +#define _LBS_HOSTCMD_H + +#include <linux/wireless.h> +#include "11d.h" +#include "types.h" + +/* 802.11-related definitions */ + +/* TxPD descriptor */ +struct txpd { + /* Current Tx packet status */ + __le32 tx_status; + /* Tx control */ + __le32 tx_control; + __le32 tx_packet_location; + /* Tx packet length */ + __le16 tx_packet_length; + /* First 2 byte of destination MAC address */ + u8 tx_dest_addr_high[2]; + /* Last 4 byte of destination MAC address */ + u8 tx_dest_addr_low[4]; + /* Pkt Priority */ + u8 priority; + /* Pkt Trasnit Power control */ + u8 powermgmt; + /* Amount of time the packet has been queued in the driver (units = 2ms) */ + u8 pktdelay_2ms; + /* reserved */ + u8 reserved1; +}; + +/* RxPD Descriptor */ +struct rxpd { + /* Current Rx packet status */ + __le16 status; + + /* SNR */ + u8 snr; + + /* Tx control */ + u8 rx_control; + + /* Pkt length */ + __le16 pkt_len; + + /* Noise Floor */ + u8 nf; + + /* Rx Packet Rate */ + u8 rx_rate; + + /* Pkt addr */ + __le32 pkt_ptr; + + /* Next Rx RxPD addr */ + __le32 next_rxpd_ptr; + + /* Pkt Priority */ + u8 priority; + u8 reserved[3]; +}; + +struct cmd_header { + __le16 command; + __le16 size; + __le16 seqnum; + __le16 result; +} __attribute__ ((packed)); + +struct cmd_ctrl_node { + struct list_head list; + /* wait for finish or not */ + u16 wait_option; + /* command response */ + void *pdata_buf; + int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *); + unsigned long callback_arg; + /* command data */ + struct cmd_header *cmdbuf; + /* wait queue */ + u16 cmdwaitqwoken; + wait_queue_head_t cmdwait_q; +}; + +/* Generic structure to hold all key types. */ +struct enc_key { + u16 len; + u16 flags; /* KEY_INFO_* from defs.h */ + u16 type; /* KEY_TYPE_* from defs.h */ + u8 key[32]; +}; + +/* lbs_offset_value */ +struct lbs_offset_value { + u32 offset; + u32 value; +}; + +/* Define general data structure */ +/* cmd_DS_GEN */ +struct cmd_ds_gen { + __le16 command; + __le16 size; + __le16 seqnum; + __le16 result; + void *cmdresp[0]; +}; + +#define S_DS_GEN sizeof(struct cmd_ds_gen) + + +/* + * Define data structure for CMD_GET_HW_SPEC + * This structure defines the response for the GET_HW_SPEC command + */ +struct cmd_ds_get_hw_spec { + struct cmd_header hdr; + + /* HW Interface version number */ + __le16 hwifversion; + /* HW version number */ + __le16 version; + /* Max number of TxPD FW can handle */ + __le16 nr_txpd; + /* Max no of Multicast address */ + __le16 nr_mcast_adr; + /* MAC address */ + u8 permanentaddr[6]; + + /* region Code */ + __le16 regioncode; + + /* Number of antenna used */ + __le16 nr_antenna; + + /* FW release number, example 1,2,3,4 = 3.2.1p4 */ + u8 fwreleasenumber[4]; + + /* Base Address of TxPD queue */ + __le32 wcb_base; + /* Read Pointer of RxPd queue */ + __le32 rxpd_rdptr; + + /* Write Pointer of RxPd queue */ + __le32 rxpd_wrptr; + + /*FW/HW capability */ + __le32 fwcapinfo; +} __attribute__ ((packed)); + +struct cmd_ds_802_11_reset { + __le16 action; +}; + +struct cmd_ds_802_11_subscribe_event { + __le16 action; + __le16 events; + + /* A TLV to the CMD_802_11_SUBSCRIBE_EVENT command can contain a + * number of TLVs. From the v5.1 manual, those TLVs would add up to + * 40 bytes. However, future firmware might add additional TLVs, so I + * bump this up a bit. + */ + u8 tlv[128]; +}; + +/* + * This scan handle Country Information IE(802.11d compliant) + * Define data structure for CMD_802_11_SCAN + */ +struct cmd_ds_802_11_scan { + u8 bsstype; + u8 bssid[ETH_ALEN]; + u8 tlvbuffer[1]; +#if 0 + mrvlietypes_ssidparamset_t ssidParamSet; + mrvlietypes_chanlistparamset_t ChanListParamSet; + mrvlietypes_ratesparamset_t OpRateSet; +#endif +}; + +struct cmd_ds_802_11_scan_rsp { + __le16 bssdescriptsize; + u8 nr_sets; + u8 bssdesc_and_tlvbuffer[1]; +}; + +struct cmd_ds_802_11_get_log { + __le32 mcasttxframe; + __le32 failed; + __le32 retry; + __le32 multiretry; + __le32 framedup; + __le32 rtssuccess; + __le32 rtsfailure; + __le32 ackfailure; + __le32 rxfrag; + __le32 mcastrxframe; + __le32 fcserror; + __le32 txframe; + __le32 wepundecryptable; +}; + +struct cmd_ds_mac_control { + __le16 action; + __le16 reserved; +}; + +struct cmd_ds_mac_multicast_adr { + __le16 action; + __le16 nr_of_adrs; + u8 maclist[ETH_ALEN * MRVDRV_MAX_MULTICAST_LIST_SIZE]; +}; + +struct cmd_ds_802_11_authenticate { + u8 macaddr[ETH_ALEN]; + u8 authtype; + u8 reserved[10]; +}; + +struct cmd_ds_802_11_deauthenticate { + u8 macaddr[6]; + __le16 reasoncode; +}; + +struct cmd_ds_802_11_associate { + u8 peerstaaddr[6]; + __le16 capability; + __le16 listeninterval; + __le16 bcnperiod; + u8 dtimperiod; + +#if 0 + mrvlietypes_ssidparamset_t ssidParamSet; + mrvlietypes_phyparamset_t phyparamset; + mrvlietypes_ssparamset_t ssparamset; + mrvlietypes_ratesparamset_t ratesParamSet; +#endif +} __attribute__ ((packed)); + +struct cmd_ds_802_11_disassociate { + u8 destmacaddr[6]; + __le16 reasoncode; +}; + +struct cmd_ds_802_11_associate_rsp { + struct ieeetypes_assocrsp assocRsp; +}; + +struct cmd_ds_802_11_ad_hoc_result { + u8 pad[3]; + u8 bssid[ETH_ALEN]; +}; + +struct cmd_ds_802_11_set_wep { + /* ACT_ADD, ACT_REMOVE or ACT_ENABLE */ + __le16 action; + + /* key Index selected for Tx */ + __le16 keyindex; + + /* 40, 128bit or TXWEP */ + u8 keytype[4]; + u8 keymaterial[4][16]; +}; + +struct cmd_ds_802_3_get_stat { + __le32 xmitok; + __le32 rcvok; + __le32 xmiterror; + __le32 rcverror; + __le32 rcvnobuffer; + __le32 rcvcrcerror; +}; + +struct cmd_ds_802_11_get_stat { + __le32 txfragmentcnt; + __le32 mcasttxframecnt; + __le32 failedcnt; + __le32 retrycnt; + __le32 Multipleretrycnt; + __le32 rtssuccesscnt; + __le32 rtsfailurecnt; + __le32 ackfailurecnt; + __le32 frameduplicatecnt; + __le32 rxfragmentcnt; + __le32 mcastrxframecnt; + __le32 fcserrorcnt; + __le32 bcasttxframecnt; + __le32 bcastrxframecnt; + __le32 txbeacon; + __le32 rxbeacon; + __le32 wepundecryptable; +}; + +struct cmd_ds_802_11_snmp_mib { + __le16 querytype; + __le16 oid; + __le16 bufsize; + u8 value[128]; +}; + +struct cmd_ds_mac_reg_map { + __le16 buffersize; + u8 regmap[128]; + __le16 reserved; +}; + +struct cmd_ds_bbp_reg_map { + __le16 buffersize; + u8 regmap[128]; + __le16 reserved; +}; + +struct cmd_ds_rf_reg_map { + __le16 buffersize; + u8 regmap[64]; + __le16 reserved; +}; + +struct cmd_ds_mac_reg_access { + __le16 action; + __le16 offset; + __le32 value; +}; + +struct cmd_ds_bbp_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +}; + +struct cmd_ds_rf_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +}; + +struct cmd_ds_802_11_radio_control { + __le16 action; + __le16 control; +}; + +struct cmd_ds_802_11_beacon_control { + __le16 action; + __le16 beacon_enable; + __le16 beacon_period; +}; + +struct cmd_ds_802_11_sleep_params { + /* ACT_GET/ACT_SET */ + __le16 action; + + /* Sleep clock error in ppm */ + __le16 error; + + /* Wakeup offset in usec */ + __le16 offset; + + /* Clock stabilization time in usec */ + __le16 stabletime; + + /* control periodic calibration */ + u8 calcontrol; + + /* control the use of external sleep clock */ + u8 externalsleepclk; + + /* reserved field, should be set to zero */ + __le16 reserved; +}; + +struct cmd_ds_802_11_inactivity_timeout { + /* ACT_GET/ACT_SET */ + __le16 action; + + /* Inactivity timeout in msec */ + __le16 timeout; +}; + +struct cmd_ds_802_11_rf_channel { + struct cmd_header hdr; + + __le16 action; + __le16 channel; + __le16 rftype; /* unused */ + __le16 reserved; /* unused */ + u8 channellist[32]; /* unused */ +}; + +struct cmd_ds_802_11_rssi { + /* weighting factor */ + __le16 N; + + __le16 reserved_0; + __le16 reserved_1; + __le16 reserved_2; +}; + +struct cmd_ds_802_11_rssi_rsp { + __le16 SNR; + __le16 noisefloor; + __le16 avgSNR; + __le16 avgnoisefloor; +}; + +struct cmd_ds_802_11_mac_address { + __le16 action; + u8 macadd[ETH_ALEN]; +}; + +struct cmd_ds_802_11_rf_tx_power { + __le16 action; + __le16 currentlevel; +}; + +struct cmd_ds_802_11_rf_antenna { + __le16 action; + + /* Number of antennas or 0xffff(diversity) */ + __le16 antennamode; + +}; + +struct cmd_ds_802_11_monitor_mode { + __le16 action; + __le16 mode; +}; + +struct cmd_ds_set_boot2_ver { + struct cmd_header hdr; + + __le16 action; + __le16 version; +}; + +struct cmd_ds_802_11_ps_mode { + __le16 action; + __le16 nullpktinterval; + __le16 multipledtim; + __le16 reserved; + __le16 locallisteninterval; +}; + +struct PS_CMD_ConfirmSleep { + __le16 command; + __le16 size; + __le16 seqnum; + __le16 result; + + __le16 action; + __le16 reserved1; + __le16 multipledtim; + __le16 reserved; + __le16 locallisteninterval; +}; + +struct cmd_ds_802_11_data_rate { + struct cmd_header hdr; + + __le16 action; + __le16 reserved; + u8 rates[MAX_RATES]; +}; + +struct cmd_ds_802_11_rate_adapt_rateset { + __le16 action; + __le16 enablehwauto; + __le16 bitmap; +}; + +struct cmd_ds_802_11_ad_hoc_start { + u8 ssid[IW_ESSID_MAX_SIZE]; + u8 bsstype; + __le16 beaconperiod; + u8 dtimperiod; + union IEEEtypes_ssparamset ssparamset; + union ieeetypes_phyparamset phyparamset; + __le16 probedelay; + __le16 capability; + u8 rates[MAX_RATES]; + u8 tlv_memory_size_pad[100]; +} __attribute__ ((packed)); + +struct adhoc_bssdesc { + u8 bssid[6]; + u8 ssid[32]; + u8 type; + __le16 beaconperiod; + u8 dtimperiod; + __le64 timestamp; + __le64 localtime; + union ieeetypes_phyparamset phyparamset; + union IEEEtypes_ssparamset ssparamset; + __le16 capability; + u8 rates[MAX_RATES]; + + /* DO NOT ADD ANY FIELDS TO THIS STRUCTURE. It is used below in the + * Adhoc join command and will cause a binary layout mismatch with + * the firmware + */ +} __attribute__ ((packed)); + +struct cmd_ds_802_11_ad_hoc_join { + struct adhoc_bssdesc bss; + __le16 failtimeout; + __le16 probedelay; + +} __attribute__ ((packed)); + +struct cmd_ds_802_11_enable_rsn { + __le16 action; + __le16 enable; +} __attribute__ ((packed)); + +struct MrvlIEtype_keyParamSet { + /* type ID */ + __le16 type; + + /* length of Payload */ + __le16 length; + + /* type of key: WEP=0, TKIP=1, AES=2 */ + __le16 keytypeid; + + /* key control Info specific to a keytypeid */ + __le16 keyinfo; + + /* length of key */ + __le16 keylen; + + /* key material of size keylen */ + u8 key[32]; +}; + +struct cmd_ds_802_11_key_material { + __le16 action; + struct MrvlIEtype_keyParamSet keyParamSet[2]; +} __attribute__ ((packed)); + +struct cmd_ds_802_11_eeprom_access { + __le16 action; + + /* multiple 4 */ + __le16 offset; + __le16 bytecount; + u8 value; +} __attribute__ ((packed)); + +struct cmd_ds_802_11_tpc_cfg { + __le16 action; + u8 enable; + s8 P0; + s8 P1; + s8 P2; + u8 usesnr; +} __attribute__ ((packed)); + +struct cmd_ds_802_11_led_ctrl { + __le16 action; + __le16 numled; + u8 data[256]; +} __attribute__ ((packed)); + +struct cmd_ds_802_11_pwr_cfg { + __le16 action; + u8 enable; + s8 PA_P0; + s8 PA_P1; + s8 PA_P2; +} __attribute__ ((packed)); + +struct cmd_ds_802_11_afc { + __le16 afc_auto; + union { + struct { + __le16 threshold; + __le16 period; + }; + struct { + __le16 timing_offset; /* signed */ + __le16 carrier_offset; /* signed */ + }; + }; +} __attribute__ ((packed)); + +struct cmd_tx_rate_query { + __le16 txrate; +} __attribute__ ((packed)); + +struct cmd_ds_get_tsf { + __le64 tsfvalue; +} __attribute__ ((packed)); + +struct cmd_ds_bt_access { + __le16 action; + __le32 id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; +} __attribute__ ((packed)); + +struct cmd_ds_fwt_access { + __le16 action; + __le32 id; + u8 valid; + u8 da[ETH_ALEN]; + u8 dir; + u8 ra[ETH_ALEN]; + __le32 ssn; + __le32 dsn; + __le32 metric; + u8 rate; + u8 hopcount; + u8 ttl; + __le32 expiration; + u8 sleepmode; + __le32 snr; + __le32 references; + u8 prec[ETH_ALEN]; +} __attribute__ ((packed)); + + +struct cmd_ds_mesh_config { + struct cmd_header hdr; + + __le16 action; + __le16 channel; + __le16 type; + __le16 length; + u8 data[128]; /* last position reserved */ +} __attribute__ ((packed)); + + +struct cmd_ds_mesh_access { + struct cmd_header hdr; + + __le16 action; + __le32 data[32]; /* last position reserved */ +} __attribute__ ((packed)); + +/* Number of stats counters returned by the firmware */ +#define MESH_STATS_NUM 8 + +struct cmd_ds_command { + /* command header */ + __le16 command; + __le16 size; + __le16 seqnum; + __le16 result; + + /* command Body */ + union { + struct cmd_ds_802_11_ps_mode psmode; + struct cmd_ds_802_11_scan scan; + struct cmd_ds_802_11_scan_rsp scanresp; + struct cmd_ds_mac_control macctrl; + struct cmd_ds_802_11_associate associate; + struct cmd_ds_802_11_deauthenticate deauth; + struct cmd_ds_802_11_set_wep wep; + struct cmd_ds_802_11_ad_hoc_start ads; + struct cmd_ds_802_11_reset reset; + struct cmd_ds_802_11_ad_hoc_result result; + struct cmd_ds_802_11_get_log glog; + struct cmd_ds_802_11_authenticate auth; + struct cmd_ds_802_11_get_stat gstat; + struct cmd_ds_802_3_get_stat gstat_8023; + struct cmd_ds_802_11_snmp_mib smib; + struct cmd_ds_802_11_rf_tx_power txp; + struct cmd_ds_802_11_rf_antenna rant; + struct cmd_ds_802_11_monitor_mode monitor; + struct cmd_ds_802_11_rate_adapt_rateset rateset; + struct cmd_ds_mac_multicast_adr madr; + struct cmd_ds_802_11_ad_hoc_join adj; + struct cmd_ds_802_11_radio_control radio; + struct cmd_ds_802_11_rf_channel rfchannel; + struct cmd_ds_802_11_rssi rssi; + struct cmd_ds_802_11_rssi_rsp rssirsp; + struct cmd_ds_802_11_disassociate dassociate; + struct cmd_ds_802_11_mac_address macadd; + struct cmd_ds_802_11_enable_rsn enbrsn; + struct cmd_ds_802_11_key_material keymaterial; + struct cmd_ds_mac_reg_access macreg; + struct cmd_ds_bbp_reg_access bbpreg; + struct cmd_ds_rf_reg_access rfreg; + struct cmd_ds_802_11_eeprom_access rdeeprom; + + struct cmd_ds_802_11d_domain_info domaininfo; + struct cmd_ds_802_11d_domain_info domaininforesp; + + struct cmd_ds_802_11_sleep_params sleep_params; + struct cmd_ds_802_11_inactivity_timeout inactivity_timeout; + struct cmd_ds_802_11_tpc_cfg tpccfg; + struct cmd_ds_802_11_pwr_cfg pwrcfg; + struct cmd_ds_802_11_afc afc; + struct cmd_ds_802_11_led_ctrl ledgpio; + + struct cmd_tx_rate_query txrate; + struct cmd_ds_bt_access bt; + struct cmd_ds_fwt_access fwt; + struct cmd_ds_get_tsf gettsf; + struct cmd_ds_802_11_subscribe_event subscribe_event; + struct cmd_ds_802_11_beacon_control bcn_ctrl; + } params; +} __attribute__ ((packed)); + +#endif diff --git a/package/libertas/src/if_cs.c b/package/libertas/src/if_cs.c new file mode 100644 index 0000000000..58143637c7 --- /dev/null +++ b/package/libertas/src/if_cs.c @@ -0,0 +1,973 @@ +/* + + Driver for the Marvell 8385 based compact flash WLAN cards. + + (C) 2007 by Holger Schurig <hs4233@mail.mn-solutions.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/moduleparam.h> +#include <linux/firmware.h> +#include <linux/netdevice.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#include <linux/io.h> + +#define DRV_NAME "libertas_cs" + +#include "decl.h" +#include "defs.h" +#include "dev.h" + + +/********************************************************************/ +/* Module stuff */ +/********************************************************************/ + +MODULE_AUTHOR("Holger Schurig <hs4233@mail.mn-solutions.de>"); +MODULE_DESCRIPTION("Driver for Marvell 83xx compact flash WLAN cards"); +MODULE_LICENSE("GPL"); + + + +/********************************************************************/ +/* Data structures */ +/********************************************************************/ + +struct if_cs_card { + struct pcmcia_device *p_dev; + struct lbs_private *priv; + void __iomem *iobase; +}; + + + +/********************************************************************/ +/* Hardware access */ +/********************************************************************/ + +/* This define enables wrapper functions which allow you + to dump all register accesses. You normally won't this, + except for development */ +/* #define DEBUG_IO */ + +#ifdef DEBUG_IO +static int debug_output = 0; +#else +/* This way the compiler optimizes the printk's away */ +#define debug_output 0 +#endif + +static inline unsigned int if_cs_read8(struct if_cs_card *card, uint reg) +{ + unsigned int val = ioread8(card->iobase + reg); + if (debug_output) + printk(KERN_INFO "##inb %08x<%02x\n", reg, val); + return val; +} +static inline unsigned int if_cs_read16(struct if_cs_card *card, uint reg) +{ + unsigned int val = ioread16(card->iobase + reg); + if (debug_output) + printk(KERN_INFO "##inw %08x<%04x\n", reg, val); + return val; +} +static inline void if_cs_read16_rep( + struct if_cs_card *card, + uint reg, + void *buf, + unsigned long count) +{ + if (debug_output) + printk(KERN_INFO "##insw %08x<(0x%lx words)\n", + reg, count); + ioread16_rep(card->iobase + reg, buf, count); +} + +static inline void if_cs_write8(struct if_cs_card *card, uint reg, u8 val) +{ + if (debug_output) + printk(KERN_INFO "##outb %08x>%02x\n", reg, val); + iowrite8(val, card->iobase + reg); +} + +static inline void if_cs_write16(struct if_cs_card *card, uint reg, u16 val) +{ + if (debug_output) + printk(KERN_INFO "##outw %08x>%04x\n", reg, val); + iowrite16(val, card->iobase + reg); +} + +static inline void if_cs_write16_rep( + struct if_cs_card *card, + uint reg, + void *buf, + unsigned long count) +{ + if (debug_output) + printk(KERN_INFO "##outsw %08x>(0x%lx words)\n", + reg, count); + iowrite16_rep(card->iobase + reg, buf, count); +} + + +/* + * I know that polling/delaying is frowned upon. However, this procedure + * with polling is needed while downloading the firmware. At this stage, + * the hardware does unfortunately not create any interrupts. + * + * Fortunately, this function is never used once the firmware is in + * the card. :-) + * + * As a reference, see the "Firmware Specification v5.1", page 18 + * and 19. I did not follow their suggested timing to the word, + * but this works nice & fast anyway. + */ +static int if_cs_poll_while_fw_download(struct if_cs_card *card, uint addr, u8 reg) +{ + int i; + + for (i = 0; i < 1000; i++) { + u8 val = if_cs_read8(card, addr); + if (val == reg) + return i; + udelay(500); + } + return -ETIME; +} + + + +/* Host control registers and their bit definitions */ + +#define IF_CS_H_STATUS 0x00000000 +#define IF_CS_H_STATUS_TX_OVER 0x0001 +#define IF_CS_H_STATUS_RX_OVER 0x0002 +#define IF_CS_H_STATUS_DNLD_OVER 0x0004 + +#define IF_CS_H_INT_CAUSE 0x00000002 +#define IF_CS_H_IC_TX_OVER 0x0001 +#define IF_CS_H_IC_RX_OVER 0x0002 +#define IF_CS_H_IC_DNLD_OVER 0x0004 +#define IF_CS_H_IC_POWER_DOWN 0x0008 +#define IF_CS_H_IC_HOST_EVENT 0x0010 +#define IF_CS_H_IC_MASK 0x001f + +#define IF_CS_H_INT_MASK 0x00000004 +#define IF_CS_H_IM_MASK 0x001f + +#define IF_CS_H_WRITE_LEN 0x00000014 + +#define IF_CS_H_WRITE 0x00000016 + +#define IF_CS_H_CMD_LEN 0x00000018 + +#define IF_CS_H_CMD 0x0000001A + +#define IF_CS_C_READ_LEN 0x00000024 + +#define IF_CS_H_READ 0x00000010 + +/* Card control registers and their bit definitions */ + +#define IF_CS_C_STATUS 0x00000020 +#define IF_CS_C_S_TX_DNLD_RDY 0x0001 +#define IF_CS_C_S_RX_UPLD_RDY 0x0002 +#define IF_CS_C_S_CMD_DNLD_RDY 0x0004 +#define IF_CS_C_S_CMD_UPLD_RDY 0x0008 +#define IF_CS_C_S_CARDEVENT 0x0010 +#define IF_CS_C_S_MASK 0x001f +#define IF_CS_C_S_STATUS_MASK 0x7f00 +/* The following definitions should be the same as the MRVDRV_ ones */ + +#if MRVDRV_CMD_DNLD_RDY != IF_CS_C_S_CMD_DNLD_RDY +#error MRVDRV_CMD_DNLD_RDY and IF_CS_C_S_CMD_DNLD_RDY not in sync +#endif +#if MRVDRV_CMD_UPLD_RDY != IF_CS_C_S_CMD_UPLD_RDY +#error MRVDRV_CMD_UPLD_RDY and IF_CS_C_S_CMD_UPLD_RDY not in sync +#endif +#if MRVDRV_CARDEVENT != IF_CS_C_S_CARDEVENT +#error MRVDRV_CARDEVENT and IF_CS_C_S_CARDEVENT not in sync +#endif + +#define IF_CS_C_INT_CAUSE 0x00000022 +#define IF_CS_C_IC_MASK 0x001f + +#define IF_CS_C_SQ_READ_LOW 0x00000028 +#define IF_CS_C_SQ_HELPER_OK 0x10 + +#define IF_CS_C_CMD_LEN 0x00000030 + +#define IF_CS_C_CMD 0x00000012 + +#define IF_CS_SCRATCH 0x0000003F + + + +/********************************************************************/ +/* Interrupts */ +/********************************************************************/ + +static inline void if_cs_enable_ints(struct if_cs_card *card) +{ + lbs_deb_enter(LBS_DEB_CS); + if_cs_write16(card, IF_CS_H_INT_MASK, 0); +} + +static inline void if_cs_disable_ints(struct if_cs_card *card) +{ + lbs_deb_enter(LBS_DEB_CS); + if_cs_write16(card, IF_CS_H_INT_MASK, IF_CS_H_IM_MASK); +} + +static irqreturn_t if_cs_interrupt(int irq, void *data) +{ + struct if_cs_card *card = data; + u16 int_cause; + + lbs_deb_enter(LBS_DEB_CS); + + int_cause = if_cs_read16(card, IF_CS_C_INT_CAUSE); + if(int_cause == 0x0) { + /* Not for us */ + return IRQ_NONE; + + } else if (int_cause == 0xffff) { + /* Read in junk, the card has probably been removed */ + card->priv->surpriseremoved = 1; + + } else { + if (int_cause & IF_CS_H_IC_TX_OVER) + lbs_host_to_card_done(card->priv); + + /* clear interrupt */ + if_cs_write16(card, IF_CS_C_INT_CAUSE, int_cause & IF_CS_C_IC_MASK); + } + spin_lock(&card->priv->driver_lock); + lbs_interrupt(card->priv); + spin_unlock(&card->priv->driver_lock); + + return IRQ_HANDLED; +} + + + + +/********************************************************************/ +/* I/O */ +/********************************************************************/ + +/* + * Called from if_cs_host_to_card to send a command to the hardware + */ +static int if_cs_send_cmd(struct lbs_private *priv, u8 *buf, u16 nb) +{ + struct if_cs_card *card = (struct if_cs_card *)priv->card; + int ret = -1; + int loops = 0; + + lbs_deb_enter(LBS_DEB_CS); + + /* Is hardware ready? */ + while (1) { + u16 val = if_cs_read16(card, IF_CS_C_STATUS); + if (val & IF_CS_C_S_CMD_DNLD_RDY) + break; + if (++loops > 100) { + lbs_pr_err("card not ready for commands\n"); + goto done; + } + mdelay(1); + } + + if_cs_write16(card, IF_CS_H_CMD_LEN, nb); + + if_cs_write16_rep(card, IF_CS_H_CMD, buf, nb / 2); + /* Are we supposed to transfer an odd amount of bytes? */ + if (nb & 1) + if_cs_write8(card, IF_CS_H_CMD, buf[nb-1]); + + /* "Assert the download over interrupt command in the Host + * status register" */ + if_cs_write16(card, IF_CS_H_STATUS, IF_CS_H_STATUS_DNLD_OVER); + + /* "Assert the download over interrupt command in the Card + * interrupt case register" */ + if_cs_write16(card, IF_CS_H_INT_CAUSE, IF_CS_H_IC_DNLD_OVER); + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + + +/* + * Called from if_cs_host_to_card to send a data to the hardware + */ +static void if_cs_send_data(struct lbs_private *priv, u8 *buf, u16 nb) +{ + struct if_cs_card *card = (struct if_cs_card *)priv->card; + + lbs_deb_enter(LBS_DEB_CS); + + if_cs_write16(card, IF_CS_H_WRITE_LEN, nb); + + /* write even number of bytes, then odd byte if necessary */ + if_cs_write16_rep(card, IF_CS_H_WRITE, buf, nb / 2); + if (nb & 1) + if_cs_write8(card, IF_CS_H_WRITE, buf[nb-1]); + + if_cs_write16(card, IF_CS_H_STATUS, IF_CS_H_STATUS_TX_OVER); + if_cs_write16(card, IF_CS_H_INT_CAUSE, IF_CS_H_STATUS_TX_OVER); + + lbs_deb_leave(LBS_DEB_CS); +} + + +/* + * Get the command result out of the card. + */ +static int if_cs_receive_cmdres(struct lbs_private *priv, u8 *data, u32 *len) +{ + int ret = -1; + u16 val; + + lbs_deb_enter(LBS_DEB_CS); + + /* is hardware ready? */ + val = if_cs_read16(priv->card, IF_CS_C_STATUS); + if ((val & IF_CS_C_S_CMD_UPLD_RDY) == 0) { + lbs_pr_err("card not ready for CMD\n"); + goto out; + } + + *len = if_cs_read16(priv->card, IF_CS_C_CMD_LEN); + if ((*len == 0) || (*len > LBS_CMD_BUFFER_SIZE)) { + lbs_pr_err("card cmd buffer has invalid # of bytes (%d)\n", *len); + goto out; + } + + /* read even number of bytes, then odd byte if necessary */ + if_cs_read16_rep(priv->card, IF_CS_C_CMD, data, *len/sizeof(u16)); + if (*len & 1) + data[*len-1] = if_cs_read8(priv->card, IF_CS_C_CMD); + + /* This is a workaround for a firmware that reports too much + * bytes */ + *len -= 8; + ret = 0; +out: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d, len %d", ret, *len); + return ret; +} + + +static struct sk_buff *if_cs_receive_data(struct lbs_private *priv) +{ + struct sk_buff *skb = NULL; + u16 len; + u8 *data; + + lbs_deb_enter(LBS_DEB_CS); + + len = if_cs_read16(priv->card, IF_CS_C_READ_LEN); + if (len == 0 || len > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) { + lbs_pr_err("card data buffer has invalid # of bytes (%d)\n", len); + priv->stats.rx_dropped++; + printk(KERN_INFO "##HS %s:%d TODO\n", __FUNCTION__, __LINE__); + goto dat_err; + } + + //TODO: skb = dev_alloc_skb(len+ETH_FRAME_LEN+MRVDRV_SNAP_HEADER_LEN+EXTRA_LEN); + skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + 2); + if (!skb) + goto out; + skb_put(skb, len); + skb_reserve(skb, 2);/* 16 byte align */ + data = skb->data; + + /* read even number of bytes, then odd byte if necessary */ + if_cs_read16_rep(priv->card, IF_CS_H_READ, data, len/sizeof(u16)); + if (len & 1) + data[len-1] = if_cs_read8(priv->card, IF_CS_H_READ); + +dat_err: + if_cs_write16(priv->card, IF_CS_H_STATUS, IF_CS_H_STATUS_RX_OVER); + if_cs_write16(priv->card, IF_CS_H_INT_CAUSE, IF_CS_H_IC_RX_OVER); + +out: + lbs_deb_leave_args(LBS_DEB_CS, "ret %p", skb); + return skb; +} + + + +/********************************************************************/ +/* Firmware */ +/********************************************************************/ + +/* + * Tries to program the helper firmware. + * + * Return 0 on success + */ +static int if_cs_prog_helper(struct if_cs_card *card) +{ + int ret = 0; + int sent = 0; + u8 scratch; + const struct firmware *fw; + + lbs_deb_enter(LBS_DEB_CS); + + scratch = if_cs_read8(card, IF_CS_SCRATCH); + + /* "If the value is 0x5a, the firmware is already + * downloaded successfully" + */ + if (scratch == 0x5a) + goto done; + + /* "If the value is != 00, it is invalid value of register */ + if (scratch != 0x00) { + ret = -ENODEV; + goto done; + } + + /* TODO: make firmware file configurable */ + ret = request_firmware(&fw, "libertas_cs_helper.fw", + &handle_to_dev(card->p_dev)); + if (ret) { + lbs_pr_err("can't load helper firmware\n"); + ret = -ENODEV; + goto done; + } + lbs_deb_cs("helper size %td\n", fw->size); + + /* "Set the 5 bytes of the helper image to 0" */ + /* Not needed, this contains an ARM branch instruction */ + + for (;;) { + /* "the number of bytes to send is 256" */ + int count = 256; + int remain = fw->size - sent; + + if (remain < count) + count = remain; + /* printk(KERN_INFO "//HS %d loading %d of %d bytes\n", + __LINE__, sent, fw->size); */ + + /* "write the number of bytes to be sent to the I/O Command + * write length register" */ + if_cs_write16(card, IF_CS_H_CMD_LEN, count); + + /* "write this to I/O Command port register as 16 bit writes */ + if (count) + if_cs_write16_rep(card, IF_CS_H_CMD, + &fw->data[sent], + count >> 1); + + /* "Assert the download over interrupt command in the Host + * status register" */ + if_cs_write8(card, IF_CS_H_STATUS, IF_CS_H_STATUS_DNLD_OVER); + + /* "Assert the download over interrupt command in the Card + * interrupt case register" */ + if_cs_write16(card, IF_CS_H_INT_CAUSE, IF_CS_H_IC_DNLD_OVER); + + /* "The host polls the Card Status register ... for 50 ms before + declaring a failure */ + ret = if_cs_poll_while_fw_download(card, IF_CS_C_STATUS, + IF_CS_C_S_CMD_DNLD_RDY); + if (ret < 0) { + lbs_pr_err("can't download helper at 0x%x, ret %d\n", + sent, ret); + goto done; + } + + if (count == 0) + break; + + sent += count; + } + + release_firmware(fw); + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + + +static int if_cs_prog_real(struct if_cs_card *card) +{ + const struct firmware *fw; + int ret = 0; + int retry = 0; + int len = 0; + int sent; + + lbs_deb_enter(LBS_DEB_CS); + + /* TODO: make firmware file configurable */ + ret = request_firmware(&fw, "libertas_cs.fw", + &handle_to_dev(card->p_dev)); + if (ret) { + lbs_pr_err("can't load firmware\n"); + ret = -ENODEV; + goto done; + } + lbs_deb_cs("fw size %td\n", fw->size); + + ret = if_cs_poll_while_fw_download(card, IF_CS_C_SQ_READ_LOW, IF_CS_C_SQ_HELPER_OK); + if (ret < 0) { + int i; + lbs_pr_err("helper firmware doesn't answer\n"); + for (i = 0; i < 0x50; i += 2) + printk(KERN_INFO "## HS %02x: %04x\n", + i, if_cs_read16(card, i)); + goto err_release; + } + + for (sent = 0; sent < fw->size; sent += len) { + len = if_cs_read16(card, IF_CS_C_SQ_READ_LOW); + /* printk(KERN_INFO "//HS %d loading %d of %d bytes\n", + __LINE__, sent, fw->size); */ + if (len & 1) { + retry++; + lbs_pr_info("odd, need to retry this firmware block\n"); + } else { + retry = 0; + } + + if (retry > 20) { + lbs_pr_err("could not download firmware\n"); + ret = -ENODEV; + goto err_release; + } + if (retry) { + sent -= len; + } + + + if_cs_write16(card, IF_CS_H_CMD_LEN, len); + + if_cs_write16_rep(card, IF_CS_H_CMD, + &fw->data[sent], + (len+1) >> 1); + if_cs_write8(card, IF_CS_H_STATUS, IF_CS_H_STATUS_DNLD_OVER); + if_cs_write16(card, IF_CS_H_INT_CAUSE, IF_CS_H_IC_DNLD_OVER); + + ret = if_cs_poll_while_fw_download(card, IF_CS_C_STATUS, + IF_CS_C_S_CMD_DNLD_RDY); + if (ret < 0) { + lbs_pr_err("can't download firmware at 0x%x\n", sent); + goto err_release; + } + } + + ret = if_cs_poll_while_fw_download(card, IF_CS_SCRATCH, 0x5a); + if (ret < 0) { + lbs_pr_err("firmware download failed\n"); + goto err_release; + } + + ret = 0; + goto done; + + +err_release: + release_firmware(fw); + +done: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + + + +/********************************************************************/ +/* Callback functions for libertas.ko */ +/********************************************************************/ + +/* Send commands or data packets to the card */ +static int if_cs_host_to_card(struct lbs_private *priv, + u8 type, + u8 *buf, + u16 nb) +{ + int ret = -1; + + lbs_deb_enter_args(LBS_DEB_CS, "type %d, bytes %d", type, nb); + + switch (type) { + case MVMS_DAT: + priv->dnld_sent = DNLD_DATA_SENT; + if_cs_send_data(priv, buf, nb); + ret = 0; + break; + case MVMS_CMD: + priv->dnld_sent = DNLD_CMD_SENT; + ret = if_cs_send_cmd(priv, buf, nb); + break; + default: + lbs_pr_err("%s: unsupported type %d\n", __FUNCTION__, type); + } + + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + + +static int if_cs_get_int_status(struct lbs_private *priv, u8 *ireg) +{ + struct if_cs_card *card = (struct if_cs_card *)priv->card; + int ret = 0; + u16 int_cause; + u8 *cmdbuf; + *ireg = 0; + + lbs_deb_enter(LBS_DEB_CS); + + if (priv->surpriseremoved) + goto out; + + int_cause = if_cs_read16(card, IF_CS_C_INT_CAUSE) & IF_CS_C_IC_MASK; + if_cs_write16(card, IF_CS_C_INT_CAUSE, int_cause); + + *ireg = if_cs_read16(card, IF_CS_C_STATUS) & IF_CS_C_S_MASK; + + if (!*ireg) + goto sbi_get_int_status_exit; + +sbi_get_int_status_exit: + + /* is there a data packet for us? */ + if (*ireg & IF_CS_C_S_RX_UPLD_RDY) { + struct sk_buff *skb = if_cs_receive_data(priv); + lbs_process_rxed_packet(priv, skb); + *ireg &= ~IF_CS_C_S_RX_UPLD_RDY; + } + + if (*ireg & IF_CS_C_S_TX_DNLD_RDY) { + priv->dnld_sent = DNLD_RES_RECEIVED; + } + + /* Card has a command result for us */ + if (*ireg & IF_CS_C_S_CMD_UPLD_RDY) { + spin_lock(&priv->driver_lock); + if (!priv->cur_cmd) { + cmdbuf = priv->upld_buf; + priv->hisregcpy &= ~IF_CS_C_S_RX_UPLD_RDY; + } else { + cmdbuf = (u8 *) priv->cur_cmd->cmdbuf; + } + + ret = if_cs_receive_cmdres(priv, cmdbuf, &priv->upld_len); + spin_unlock(&priv->driver_lock); + if (ret < 0) + lbs_pr_err("could not receive cmd from card\n"); + } + +out: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d, ireg 0x%x, hisregcpy 0x%x", ret, *ireg, priv->hisregcpy); + return ret; +} + + +static int if_cs_read_event_cause(struct lbs_private *priv) +{ + lbs_deb_enter(LBS_DEB_CS); + + priv->eventcause = (if_cs_read16(priv->card, IF_CS_C_STATUS) & IF_CS_C_S_STATUS_MASK) >> 5; + if_cs_write16(priv->card, IF_CS_H_INT_CAUSE, IF_CS_H_IC_HOST_EVENT); + + return 0; +} + + + +/********************************************************************/ +/* Card Services */ +/********************************************************************/ + +/* + * After a card is removed, if_cs_release() will unregister the + * device, and release the PCMCIA configuration. If the device is + * still open, this will be postponed until it is closed. + */ +static void if_cs_release(struct pcmcia_device *p_dev) +{ + struct if_cs_card *card = p_dev->priv; + + lbs_deb_enter(LBS_DEB_CS); + + pcmcia_disable_device(p_dev); + free_irq(p_dev->irq.AssignedIRQ, card); + if (card->iobase) + ioport_unmap(card->iobase); + + lbs_deb_leave(LBS_DEB_CS); +} + + +/* + * This creates an "instance" of the driver, allocating local data + * structures for one device. The device is registered with Card + * Services. + * + * The dev_link structure is initialized, but we don't actually + * configure the card at this point -- we wait until we receive a card + * insertion event. + */ +static int if_cs_probe(struct pcmcia_device *p_dev) +{ + int ret = -ENOMEM; + struct lbs_private *priv; + struct if_cs_card *card; + /* CIS parsing */ + tuple_t tuple; + cisparse_t parse; + cistpl_cftable_entry_t *cfg = &parse.cftable_entry; + cistpl_io_t *io = &cfg->io; + u_char buf[64]; + + lbs_deb_enter(LBS_DEB_CS); + + card = kzalloc(sizeof(struct if_cs_card), GFP_KERNEL); + if (!card) { + lbs_pr_err("error in kzalloc\n"); + goto out; + } + card->p_dev = p_dev; + p_dev->priv = card; + + p_dev->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING; + p_dev->irq.Handler = NULL; + p_dev->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID; + + p_dev->conf.Attributes = 0; + p_dev->conf.IntType = INT_MEMORY_AND_IO; + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + if ((ret = pcmcia_get_first_tuple(p_dev, &tuple)) != 0 || + (ret = pcmcia_get_tuple_data(p_dev, &tuple)) != 0 || + (ret = pcmcia_parse_tuple(p_dev, &tuple, &parse)) != 0) + { + lbs_pr_err("error in pcmcia_get_first_tuple etc\n"); + goto out1; + } + + p_dev->conf.ConfigIndex = cfg->index; + + /* Do we need to allocate an interrupt? */ + if (cfg->irq.IRQInfo1) { + p_dev->conf.Attributes |= CONF_ENABLE_IRQ; + } + + /* IO window settings */ + if (cfg->io.nwin != 1) { + lbs_pr_err("wrong CIS (check number of IO windows)\n"); + ret = -ENODEV; + goto out1; + } + p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + p_dev->io.BasePort1 = io->win[0].base; + p_dev->io.NumPorts1 = io->win[0].len; + + /* This reserves IO space but doesn't actually enable it */ + ret = pcmcia_request_io(p_dev, &p_dev->io); + if (ret) { + lbs_pr_err("error in pcmcia_request_io\n"); + goto out1; + } + + /* + * Allocate an interrupt line. Note that this does not assign + * a handler to the interrupt, unless the 'Handler' member of + * the irq structure is initialized. + */ + if (p_dev->conf.Attributes & CONF_ENABLE_IRQ) { + ret = pcmcia_request_irq(p_dev, &p_dev->irq); + if (ret) { + lbs_pr_err("error in pcmcia_request_irq\n"); + goto out1; + } + } + + /* Initialize io access */ + card->iobase = ioport_map(p_dev->io.BasePort1, p_dev->io.NumPorts1); + if (!card->iobase) { + lbs_pr_err("error in ioport_map\n"); + ret = -EIO; + goto out1; + } + + /* + * This actually configures the PCMCIA socket -- setting up + * the I/O windows and the interrupt mapping, and putting the + * card and host interface into "Memory and IO" mode. + */ + ret = pcmcia_request_configuration(p_dev, &p_dev->conf); + if (ret) { + lbs_pr_err("error in pcmcia_request_configuration\n"); + goto out2; + } + + /* Finally, report what we've done */ + lbs_deb_cs("irq %d, io 0x%04x-0x%04x\n", + p_dev->irq.AssignedIRQ, p_dev->io.BasePort1, + p_dev->io.BasePort1 + p_dev->io.NumPorts1 - 1); + + + /* Load the firmware early, before calling into libertas.ko */ + ret = if_cs_prog_helper(card); + if (ret == 0) + ret = if_cs_prog_real(card); + if (ret) + goto out2; + + /* Make this card known to the libertas driver */ + priv = lbs_add_card(card, &p_dev->dev); + if (!priv) { + ret = -ENOMEM; + goto out2; + } + + /* Store pointers to our call-back functions */ + card->priv = priv; + priv->card = card; + priv->hw_host_to_card = if_cs_host_to_card; + priv->hw_get_int_status = if_cs_get_int_status; + priv->hw_read_event_cause = if_cs_read_event_cause; + + priv->fw_ready = 1; + + /* Now actually get the IRQ */ + ret = request_irq(p_dev->irq.AssignedIRQ, if_cs_interrupt, + IRQF_SHARED, DRV_NAME, card); + if (ret) { + lbs_pr_err("error in request_irq\n"); + goto out3; + } + + /* Clear any interrupt cause that happend while sending + * firmware/initializing card */ + if_cs_write16(card, IF_CS_C_INT_CAUSE, IF_CS_C_IC_MASK); + if_cs_enable_ints(card); + + /* And finally bring the card up */ + if (lbs_start_card(priv) != 0) { + lbs_pr_err("could not activate card\n"); + goto out3; + } + + ret = 0; + goto out; + +out3: + lbs_remove_card(priv); +out2: + ioport_unmap(card->iobase); +out1: + pcmcia_disable_device(p_dev); +out: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + + +/* + * This deletes a driver "instance". The device is de-registered with + * Card Services. If it has been released, all local data structures + * are freed. Otherwise, the structures will be freed when the device + * is released. + */ +static void if_cs_detach(struct pcmcia_device *p_dev) +{ + struct if_cs_card *card = p_dev->priv; + + lbs_deb_enter(LBS_DEB_CS); + + lbs_stop_card(card->priv); + lbs_remove_card(card->priv); + if_cs_disable_ints(card); + if_cs_release(p_dev); + kfree(card); + + lbs_deb_leave(LBS_DEB_CS); +} + + + +/********************************************************************/ +/* Module initialization */ +/********************************************************************/ + +static struct pcmcia_device_id if_cs_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x02df, 0x8103), + PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, if_cs_ids); + + +static struct pcmcia_driver lbs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = DRV_NAME, + }, + .probe = if_cs_probe, + .remove = if_cs_detach, + .id_table = if_cs_ids, +}; + + +static int __init if_cs_init(void) +{ + int ret; + + lbs_deb_enter(LBS_DEB_CS); + ret = pcmcia_register_driver(&lbs_driver); + lbs_deb_leave(LBS_DEB_CS); + return ret; +} + + +static void __exit if_cs_exit(void) +{ + lbs_deb_enter(LBS_DEB_CS); + pcmcia_unregister_driver(&lbs_driver); + lbs_deb_leave(LBS_DEB_CS); +} + + +module_init(if_cs_init); +module_exit(if_cs_exit); diff --git a/package/libertas/src/if_sdio.c b/package/libertas/src/if_sdio.c new file mode 100644 index 0000000000..85675c5fa4 --- /dev/null +++ b/package/libertas/src/if_sdio.c @@ -0,0 +1,1079 @@ +/* + * linux/drivers/net/wireless/libertas/if_sdio.c + * + * Copyright 2007 Pierre Ossman + * + * Inspired by if_cs.c, Copyright 2007 Holger Schurig + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This hardware has more or less no CMD53 support, so all registers + * must be accessed using sdio_readb()/sdio_writeb(). + * + * Transfers must be in one transaction or the firmware goes bonkers. + * This means that the transfer must either be small enough to do a + * byte based transfer or it must be padded to a multiple of the + * current block size. + * + * As SDIO is still new to the kernel, it is unfortunately common with + * bugs in the host controllers related to that. One such bug is that + * controllers cannot do transfers that aren't a multiple of 4 bytes. + * If you don't have time to fix the host controller driver, you can + * work around the problem by modifying if_sdio_host_to_card() and + * if_sdio_card_to_host() to pad the data. + */ + +#include <linux/moduleparam.h> +#include <linux/firmware.h> +#include <linux/netdevice.h> +#include <linux/delay.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> + +#include "host.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "if_sdio.h" + +static char *lbs_helper_name = NULL; +module_param_named(helper_name, lbs_helper_name, charp, 0644); + +static char *lbs_fw_name = NULL; +module_param_named(fw_name, lbs_fw_name, charp, 0644); + +static const struct sdio_device_id if_sdio_ids[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_LIBERTAS) }, + { /* end: all zeroes */ }, +}; + +MODULE_DEVICE_TABLE(sdio, if_sdio_ids); + +struct if_sdio_model { + int model; + const char *helper; + const char *firmware; +}; + +static struct if_sdio_model if_sdio_models[] = { + { + /* 8385 */ + .model = 0x04, + .helper = "sd8385_helper.bin", + .firmware = "sd8385.bin", + }, + { + /* 8686 */ + .model = 0x0B, + .helper = "sd8686_helper.bin", + .firmware = "sd8686.bin", + }, +}; + +struct if_sdio_packet { + struct if_sdio_packet *next; + u16 nb; + u8 buffer[0] __attribute__((aligned(4))); +}; + +struct if_sdio_card { + struct sdio_func *func; + struct lbs_private *priv; + + int model; + unsigned long ioport; + + const char *helper; + const char *firmware; + + u8 buffer[65536]; + u8 int_cause; + u32 event; + + spinlock_t lock; + struct if_sdio_packet *packets; + struct work_struct packet_worker; +}; + +/********************************************************************/ +/* I/O */ +/********************************************************************/ + +static u16 if_sdio_read_scratch(struct if_sdio_card *card, int *err) +{ + int ret, reg; + u16 scratch; + + if (card->model == 0x04) + reg = IF_SDIO_SCRATCH_OLD; + else + reg = IF_SDIO_SCRATCH; + + scratch = sdio_readb(card->func, reg, &ret); + if (!ret) + scratch |= sdio_readb(card->func, reg + 1, &ret) << 8; + + if (err) + *err = ret; + + if (ret) + return 0xffff; + + return scratch; +} + +static int if_sdio_handle_cmd(struct if_sdio_card *card, + u8 *buffer, unsigned size) +{ + int ret; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_SDIO); + + spin_lock_irqsave(&card->priv->driver_lock, flags); + + if (!card->priv->cur_cmd) { + lbs_deb_sdio("discarding spurious response\n"); + ret = 0; + goto out; + } + + if (size > LBS_CMD_BUFFER_SIZE) { + lbs_deb_sdio("response packet too large (%d bytes)\n", + (int)size); + ret = -E2BIG; + goto out; + } + + memcpy(card->priv->cur_cmd->cmdbuf, buffer, size); + card->priv->upld_len = size; + + card->int_cause |= MRVDRV_CMD_UPLD_RDY; + + lbs_interrupt(card->priv); + + ret = 0; + +out: + spin_unlock_irqrestore(&card->priv->driver_lock, flags); + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_handle_data(struct if_sdio_card *card, + u8 *buffer, unsigned size) +{ + int ret; + struct sk_buff *skb; + char *data; + + lbs_deb_enter(LBS_DEB_SDIO); + + if (size > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) { + lbs_deb_sdio("response packet too large (%d bytes)\n", + (int)size); + ret = -E2BIG; + goto out; + } + + skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + NET_IP_ALIGN); + if (!skb) { + ret = -ENOMEM; + goto out; + } + + skb_reserve(skb, NET_IP_ALIGN); + + data = skb_put(skb, size); + + memcpy(data, buffer, size); + + lbs_process_rxed_packet(card->priv, skb); + + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_handle_event(struct if_sdio_card *card, + u8 *buffer, unsigned size) +{ + int ret; + unsigned long flags; + u32 event; + + lbs_deb_enter(LBS_DEB_SDIO); + + if (card->model == 0x04) { + event = sdio_readb(card->func, IF_SDIO_EVENT, &ret); + if (ret) + goto out; + } else { + if (size < 4) { + lbs_deb_sdio("event packet too small (%d bytes)\n", + (int)size); + ret = -EINVAL; + goto out; + } + event = buffer[3] << 24; + event |= buffer[2] << 16; + event |= buffer[1] << 8; + event |= buffer[0] << 0; + event <<= SBI_EVENT_CAUSE_SHIFT; + } + + spin_lock_irqsave(&card->priv->driver_lock, flags); + + card->event = event; + card->int_cause |= MRVDRV_CARDEVENT; + + lbs_interrupt(card->priv); + + spin_unlock_irqrestore(&card->priv->driver_lock, flags); + + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_card_to_host(struct if_sdio_card *card) +{ + int ret; + u8 status; + u16 size, type, chunk; + unsigned long timeout; + + lbs_deb_enter(LBS_DEB_SDIO); + + size = if_sdio_read_scratch(card, &ret); + if (ret) + goto out; + + if (size < 4) { + lbs_deb_sdio("invalid packet size (%d bytes) from firmware\n", + (int)size); + ret = -EINVAL; + goto out; + } + + timeout = jiffies + HZ; + while (1) { + status = sdio_readb(card->func, IF_SDIO_STATUS, &ret); + if (ret) + goto out; + if (status & IF_SDIO_IO_RDY) + break; + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto out; + } + mdelay(1); + } + + /* + * The transfer must be in one transaction or the firmware + * goes suicidal. + */ + chunk = size; + if ((chunk > card->func->cur_blksize) || (chunk > 512)) { + chunk = (chunk + card->func->cur_blksize - 1) / + card->func->cur_blksize * card->func->cur_blksize; + } + + ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk); + if (ret) + goto out; + + chunk = card->buffer[0] | (card->buffer[1] << 8); + type = card->buffer[2] | (card->buffer[3] << 8); + + lbs_deb_sdio("packet of type %d and size %d bytes\n", + (int)type, (int)chunk); + + if (chunk > size) { + lbs_deb_sdio("packet fragment (%d > %d)\n", + (int)chunk, (int)size); + ret = -EINVAL; + goto out; + } + + if (chunk < size) { + lbs_deb_sdio("packet fragment (%d < %d)\n", + (int)chunk, (int)size); + } + + switch (type) { + case MVMS_CMD: + ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4); + if (ret) + goto out; + break; + case MVMS_DAT: + ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4); + if (ret) + goto out; + break; + case MVMS_EVENT: + ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4); + if (ret) + goto out; + break; + default: + lbs_deb_sdio("invalid type (%d) from firmware\n", + (int)type); + ret = -EINVAL; + goto out; + } + +out: + if (ret) + lbs_pr_err("problem fetching packet from firmware\n"); + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static void if_sdio_host_to_card_worker(struct work_struct *work) +{ + struct if_sdio_card *card; + struct if_sdio_packet *packet; + unsigned long timeout; + u8 status; + int ret; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_SDIO); + + card = container_of(work, struct if_sdio_card, packet_worker); + + while (1) { + spin_lock_irqsave(&card->lock, flags); + packet = card->packets; + if (packet) + card->packets = packet->next; + spin_unlock_irqrestore(&card->lock, flags); + + if (!packet) + break; + + sdio_claim_host(card->func); + + timeout = jiffies + HZ; + while (1) { + status = sdio_readb(card->func, IF_SDIO_STATUS, &ret); + if (ret) + goto release; + if (status & IF_SDIO_IO_RDY) + break; + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto release; + } + mdelay(1); + } + + ret = sdio_writesb(card->func, card->ioport, + packet->buffer, packet->nb); + if (ret) + goto release; +release: + sdio_release_host(card->func); + + kfree(packet); + } + + lbs_deb_leave(LBS_DEB_SDIO); +} + +/********************************************************************/ +/* Firmware */ +/********************************************************************/ + +static int if_sdio_prog_helper(struct if_sdio_card *card) +{ + int ret; + u8 status; + const struct firmware *fw; + unsigned long timeout; + u8 *chunk_buffer; + u32 chunk_size; + u8 *firmware; + size_t size; + + lbs_deb_enter(LBS_DEB_SDIO); + + ret = request_firmware(&fw, card->helper, &card->func->dev); + if (ret) { + lbs_pr_err("can't load helper firmware\n"); + goto out; + } + + chunk_buffer = kzalloc(64, GFP_KERNEL); + if (!chunk_buffer) { + ret = -ENOMEM; + goto release_fw; + } + + sdio_claim_host(card->func); + + ret = sdio_set_block_size(card->func, 32); + if (ret) + goto release; + + firmware = fw->data; + size = fw->size; + + while (size) { + timeout = jiffies + HZ; + while (1) { + status = sdio_readb(card->func, IF_SDIO_STATUS, &ret); + if (ret) + goto release; + if ((status & IF_SDIO_IO_RDY) && + (status & IF_SDIO_DL_RDY)) + break; + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto release; + } + mdelay(1); + } + + chunk_size = min(size, (size_t)60); + + *((__le32*)chunk_buffer) = cpu_to_le32(chunk_size); + memcpy(chunk_buffer + 4, firmware, chunk_size); +/* + lbs_deb_sdio("sending %d bytes chunk\n", chunk_size); +*/ + ret = sdio_writesb(card->func, card->ioport, + chunk_buffer, 64); + if (ret) + goto release; + + firmware += chunk_size; + size -= chunk_size; + } + + /* an empty block marks the end of the transfer */ + memset(chunk_buffer, 0, 4); + ret = sdio_writesb(card->func, card->ioport, chunk_buffer, 64); + if (ret) + goto release; + + lbs_deb_sdio("waiting for helper to boot...\n"); + + /* wait for the helper to boot by looking at the size register */ + timeout = jiffies + HZ; + while (1) { + u16 req_size; + + req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, &ret); + if (ret) + goto release; + + req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, &ret) << 8; + if (ret) + goto release; + + if (req_size != 0) + break; + + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto release; + } + + msleep(10); + } + + ret = 0; + +release: + sdio_set_block_size(card->func, 0); + sdio_release_host(card->func); + kfree(chunk_buffer); +release_fw: + release_firmware(fw); + +out: + if (ret) + lbs_pr_err("failed to load helper firmware\n"); + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_prog_real(struct if_sdio_card *card) +{ + int ret; + u8 status; + const struct firmware *fw; + unsigned long timeout; + u8 *chunk_buffer; + u32 chunk_size; + u8 *firmware; + size_t size, req_size; + + lbs_deb_enter(LBS_DEB_SDIO); + + ret = request_firmware(&fw, card->firmware, &card->func->dev); + if (ret) { + lbs_pr_err("can't load firmware\n"); + goto out; + } + + chunk_buffer = kzalloc(512, GFP_KERNEL); + if (!chunk_buffer) { + ret = -ENOMEM; + goto release_fw; + } + + sdio_claim_host(card->func); + + ret = sdio_set_block_size(card->func, 32); + if (ret) + goto release; + + firmware = fw->data; + size = fw->size; + + while (size) { + timeout = jiffies + HZ; + while (1) { + status = sdio_readb(card->func, IF_SDIO_STATUS, &ret); + if (ret) + goto release; + if ((status & IF_SDIO_IO_RDY) && + (status & IF_SDIO_DL_RDY)) + break; + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto release; + } + mdelay(1); + } + + req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, &ret); + if (ret) + goto release; + + req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, &ret) << 8; + if (ret) + goto release; +/* + lbs_deb_sdio("firmware wants %d bytes\n", (int)req_size); +*/ + if (req_size == 0) { + lbs_deb_sdio("firmware helper gave up early\n"); + ret = -EIO; + goto release; + } + + if (req_size & 0x01) { + lbs_deb_sdio("firmware helper signalled error\n"); + ret = -EIO; + goto release; + } + + if (req_size > size) + req_size = size; + + while (req_size) { + chunk_size = min(req_size, (size_t)512); + + memcpy(chunk_buffer, firmware, chunk_size); +/* + lbs_deb_sdio("sending %d bytes (%d bytes) chunk\n", + chunk_size, (chunk_size + 31) / 32 * 32); +*/ + ret = sdio_writesb(card->func, card->ioport, + chunk_buffer, (chunk_size + 31) / 32 * 32); + if (ret) + goto release; + + firmware += chunk_size; + size -= chunk_size; + req_size -= chunk_size; + } + } + + ret = 0; + + lbs_deb_sdio("waiting for firmware to boot...\n"); + + /* wait for the firmware to boot */ + timeout = jiffies + HZ; + while (1) { + u16 scratch; + + scratch = if_sdio_read_scratch(card, &ret); + if (ret) + goto release; + + if (scratch == IF_SDIO_FIRMWARE_OK) + break; + + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto release; + } + + msleep(10); + } + + ret = 0; + +release: + sdio_set_block_size(card->func, 0); + sdio_release_host(card->func); + kfree(chunk_buffer); +release_fw: + release_firmware(fw); + +out: + if (ret) + lbs_pr_err("failed to load firmware\n"); + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_prog_firmware(struct if_sdio_card *card) +{ + int ret; + u16 scratch; + + lbs_deb_enter(LBS_DEB_SDIO); + + sdio_claim_host(card->func); + scratch = if_sdio_read_scratch(card, &ret); + sdio_release_host(card->func); + + if (ret) + goto out; + + if (scratch == IF_SDIO_FIRMWARE_OK) { + lbs_deb_sdio("firmware already loaded\n"); + goto success; + } + + ret = if_sdio_prog_helper(card); + if (ret) + goto out; + + ret = if_sdio_prog_real(card); + if (ret) + goto out; + +success: + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +/*******************************************************************/ +/* Libertas callbacks */ +/*******************************************************************/ + +static int if_sdio_host_to_card(struct lbs_private *priv, + u8 type, u8 *buf, u16 nb) +{ + int ret; + struct if_sdio_card *card; + struct if_sdio_packet *packet, *cur; + u16 size; + unsigned long flags; + + lbs_deb_enter_args(LBS_DEB_SDIO, "type %d, bytes %d", type, nb); + + card = priv->card; + + if (nb > (65536 - sizeof(struct if_sdio_packet) - 4)) { + ret = -EINVAL; + goto out; + } + + /* + * The transfer must be in one transaction or the firmware + * goes suicidal. + */ + size = nb + 4; + if ((size > card->func->cur_blksize) || (size > 512)) { + size = (size + card->func->cur_blksize - 1) / + card->func->cur_blksize * card->func->cur_blksize; + } + + packet = kzalloc(sizeof(struct if_sdio_packet) + size, + GFP_ATOMIC); + if (!packet) { + ret = -ENOMEM; + goto out; + } + + packet->next = NULL; + packet->nb = size; + + /* + * SDIO specific header. + */ + packet->buffer[0] = (nb + 4) & 0xff; + packet->buffer[1] = ((nb + 4) >> 8) & 0xff; + packet->buffer[2] = type; + packet->buffer[3] = 0; + + memcpy(packet->buffer + 4, buf, nb); + + spin_lock_irqsave(&card->lock, flags); + + if (!card->packets) + card->packets = packet; + else { + cur = card->packets; + while (cur->next) + cur = cur->next; + cur->next = packet; + } + + switch (type) { + case MVMS_CMD: + priv->dnld_sent = DNLD_CMD_SENT; + break; + case MVMS_DAT: + priv->dnld_sent = DNLD_DATA_SENT; + break; + default: + lbs_deb_sdio("unknown packet type %d\n", (int)type); + } + + spin_unlock_irqrestore(&card->lock, flags); + + schedule_work(&card->packet_worker); + + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_get_int_status(struct lbs_private *priv, u8 *ireg) +{ + struct if_sdio_card *card; + + lbs_deb_enter(LBS_DEB_SDIO); + + card = priv->card; + + *ireg = card->int_cause; + card->int_cause = 0; + + lbs_deb_leave(LBS_DEB_SDIO); + + return 0; +} + +static int if_sdio_read_event_cause(struct lbs_private *priv) +{ + struct if_sdio_card *card; + + lbs_deb_enter(LBS_DEB_SDIO); + + card = priv->card; + + priv->eventcause = card->event; + + lbs_deb_leave(LBS_DEB_SDIO); + + return 0; +} + +/*******************************************************************/ +/* SDIO callbacks */ +/*******************************************************************/ + +static void if_sdio_interrupt(struct sdio_func *func) +{ + int ret; + struct if_sdio_card *card; + u8 cause; + + lbs_deb_enter(LBS_DEB_SDIO); + + card = sdio_get_drvdata(func); + + cause = sdio_readb(card->func, IF_SDIO_H_INT_STATUS, &ret); + if (ret) + goto out; + + lbs_deb_sdio("interrupt: 0x%X\n", (unsigned)cause); + + sdio_writeb(card->func, ~cause, IF_SDIO_H_INT_STATUS, &ret); + if (ret) + goto out; + + /* + * Ignore the define name, this really means the card has + * successfully received the command. + */ + if (cause & IF_SDIO_H_INT_DNLD) + lbs_host_to_card_done(card->priv); + + + if (cause & IF_SDIO_H_INT_UPLD) { + ret = if_sdio_card_to_host(card); + if (ret) + goto out; + } + + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); +} + +static int if_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct if_sdio_card *card; + struct lbs_private *priv; + int ret, i; + unsigned int model; + struct if_sdio_packet *packet; + + lbs_deb_enter(LBS_DEB_SDIO); + + for (i = 0;i < func->card->num_info;i++) { + if (sscanf(func->card->info[i], + "802.11 SDIO ID: %x", &model) == 1) + break; + if (sscanf(func->card->info[i], + "ID: %x", &model) == 1) + break; + } + + if (i == func->card->num_info) { + lbs_pr_err("unable to identify card model\n"); + return -ENODEV; + } + + card = kzalloc(sizeof(struct if_sdio_card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->func = func; + card->model = model; + spin_lock_init(&card->lock); + INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker); + + for (i = 0;i < ARRAY_SIZE(if_sdio_models);i++) { + if (card->model == if_sdio_models[i].model) + break; + } + + if (i == ARRAY_SIZE(if_sdio_models)) { + lbs_pr_err("unkown card model 0x%x\n", card->model); + ret = -ENODEV; + goto free; + } + + card->helper = if_sdio_models[i].helper; + card->firmware = if_sdio_models[i].firmware; + + if (lbs_helper_name) { + lbs_deb_sdio("overriding helper firmware: %s\n", + lbs_helper_name); + card->helper = lbs_helper_name; + } + + if (lbs_fw_name) { + lbs_deb_sdio("overriding firmware: %s\n", lbs_fw_name); + card->firmware = lbs_fw_name; + } + + sdio_claim_host(func); + + ret = sdio_enable_func(func); + if (ret) + goto release; + + ret = sdio_claim_irq(func, if_sdio_interrupt); + if (ret) + goto disable; + + card->ioport = sdio_readb(func, IF_SDIO_IOPORT, &ret); + if (ret) + goto release_int; + + card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 1, &ret) << 8; + if (ret) + goto release_int; + + card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 2, &ret) << 16; + if (ret) + goto release_int; + + sdio_release_host(func); + + sdio_set_drvdata(func, card); + + lbs_deb_sdio("class = 0x%X, vendor = 0x%X, " + "device = 0x%X, model = 0x%X, ioport = 0x%X\n", + func->class, func->vendor, func->device, + model, (unsigned)card->ioport); + + ret = if_sdio_prog_firmware(card); + if (ret) + goto reclaim; + + priv = lbs_add_card(card, &func->dev); + if (!priv) { + ret = -ENOMEM; + goto reclaim; + } + + card->priv = priv; + + priv->card = card; + priv->hw_host_to_card = if_sdio_host_to_card; + priv->hw_get_int_status = if_sdio_get_int_status; + priv->hw_read_event_cause = if_sdio_read_event_cause; + + priv->fw_ready = 1; + + /* + * Enable interrupts now that everything is set up + */ + sdio_claim_host(func); + sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret); + sdio_release_host(func); + if (ret) + goto reclaim; + + ret = lbs_start_card(priv); + if (ret) + goto err_activate_card; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; + +err_activate_card: + flush_scheduled_work(); + free_netdev(priv->dev); + kfree(priv); +reclaim: + sdio_claim_host(func); +release_int: + sdio_release_irq(func); +disable: + sdio_disable_func(func); +release: + sdio_release_host(func); +free: + while (card->packets) { + packet = card->packets; + card->packets = card->packets->next; + kfree(packet); + } + + kfree(card); + + goto out; +} + +static void if_sdio_remove(struct sdio_func *func) +{ + struct if_sdio_card *card; + struct if_sdio_packet *packet; + + lbs_deb_enter(LBS_DEB_SDIO); + + card = sdio_get_drvdata(func); + + card->priv->surpriseremoved = 1; + + lbs_deb_sdio("call remove card\n"); + lbs_stop_card(card->priv); + lbs_remove_card(card->priv); + + flush_scheduled_work(); + + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + + while (card->packets) { + packet = card->packets; + card->packets = card->packets->next; + kfree(packet); + } + + kfree(card); + + lbs_deb_leave(LBS_DEB_SDIO); +} + +static struct sdio_driver if_sdio_driver = { + .name = "libertas_sdio", + .id_table = if_sdio_ids, + .probe = if_sdio_probe, + .remove = if_sdio_remove, +}; + +/*******************************************************************/ +/* Module functions */ +/*******************************************************************/ + +static int __init if_sdio_init_module(void) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_SDIO); + + printk(KERN_INFO "libertas_sdio: Libertas SDIO driver\n"); + printk(KERN_INFO "libertas_sdio: Copyright Pierre Ossman\n"); + + ret = sdio_register_driver(&if_sdio_driver); + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static void __exit if_sdio_exit_module(void) +{ + lbs_deb_enter(LBS_DEB_SDIO); + + sdio_unregister_driver(&if_sdio_driver); + + lbs_deb_leave(LBS_DEB_SDIO); +} + +module_init(if_sdio_init_module); +module_exit(if_sdio_exit_module); + +MODULE_DESCRIPTION("Libertas SDIO WLAN Driver"); +MODULE_AUTHOR("Pierre Ossman"); +MODULE_LICENSE("GPL"); diff --git a/package/libertas/src/if_sdio.h b/package/libertas/src/if_sdio.h new file mode 100644 index 0000000000..533bdfbf5d --- /dev/null +++ b/package/libertas/src/if_sdio.h @@ -0,0 +1,45 @@ +/* + * linux/drivers/net/wireless/libertas/if_sdio.h + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _LBS_IF_SDIO_H +#define _LBS_IF_SDIO_H + +#define IF_SDIO_IOPORT 0x00 + +#define IF_SDIO_H_INT_MASK 0x04 +#define IF_SDIO_H_INT_OFLOW 0x08 +#define IF_SDIO_H_INT_UFLOW 0x04 +#define IF_SDIO_H_INT_DNLD 0x02 +#define IF_SDIO_H_INT_UPLD 0x01 + +#define IF_SDIO_H_INT_STATUS 0x05 +#define IF_SDIO_H_INT_RSR 0x06 +#define IF_SDIO_H_INT_STATUS2 0x07 + +#define IF_SDIO_RD_BASE 0x10 + +#define IF_SDIO_STATUS 0x20 +#define IF_SDIO_IO_RDY 0x08 +#define IF_SDIO_CIS_RDY 0x04 +#define IF_SDIO_UL_RDY 0x02 +#define IF_SDIO_DL_RDY 0x01 + +#define IF_SDIO_C_INT_MASK 0x24 +#define IF_SDIO_C_INT_STATUS 0x28 +#define IF_SDIO_C_INT_RSR 0x2C + +#define IF_SDIO_SCRATCH 0x34 +#define IF_SDIO_SCRATCH_OLD 0x80fe +#define IF_SDIO_FIRMWARE_OK 0xfedc + +#define IF_SDIO_EVENT 0x80fc + +#endif diff --git a/package/libertas/src/if_usb.c b/package/libertas/src/if_usb.c new file mode 100644 index 0000000000..02192e8a15 --- /dev/null +++ b/package/libertas/src/if_usb.c @@ -0,0 +1,1043 @@ +/** + * This file contains functions used in USB interface module. + */ +#include <linux/delay.h> +#include <linux/moduleparam.h> +#include <linux/firmware.h> +#include <linux/netdevice.h> +#include <linux/usb.h> + +#define DRV_NAME "usb8xxx" + +#include "host.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "cmd.h" +#include "if_usb.h" + +#define MESSAGE_HEADER_LEN 4 + +static char *lbs_fw_name = "usb8388.bin"; +module_param_named(fw_name, lbs_fw_name, charp, 0644); + +static struct usb_device_id if_usb_table[] = { + /* Enter the device signature inside */ + { USB_DEVICE(0x1286, 0x2001) }, + { USB_DEVICE(0x05a3, 0x8388) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, if_usb_table); + +static void if_usb_receive(struct urb *urb); +static void if_usb_receive_fwload(struct urb *urb); +static int if_usb_prog_firmware(struct usb_card_rec *cardp); +static int if_usb_host_to_card(struct lbs_private *priv, + u8 type, + u8 *payload, + u16 nb); +static int if_usb_get_int_status(struct lbs_private *priv, u8 *); +static int if_usb_read_event_cause(struct lbs_private *); +static int usb_tx_block(struct usb_card_rec *cardp, u8 *payload, u16 nb); +static void if_usb_free(struct usb_card_rec *cardp); +static int if_usb_submit_rx_urb(struct usb_card_rec *cardp); +static int if_usb_reset_device(struct usb_card_rec *cardp); + +/** + * @brief call back function to handle the status of the URB + * @param urb pointer to urb structure + * @return N/A + */ +static void if_usb_write_bulk_callback(struct urb *urb) +{ + struct usb_card_rec *cardp = (struct usb_card_rec *) urb->context; + + /* handle the transmission complete validations */ + + if (urb->status == 0) { + struct lbs_private *priv = cardp->priv; + + /* + lbs_deb_usbd(&urb->dev->dev, "URB status is successfull\n"); + lbs_deb_usbd(&urb->dev->dev, "Actual length transmitted %d\n", + urb->actual_length); + */ + + /* Used for both firmware TX and regular TX. priv isn't + * valid at firmware load time. + */ + if (priv) + lbs_host_to_card_done(priv); + } else { + /* print the failure status number for debug */ + lbs_pr_info("URB in failure status: %d\n", urb->status); + } + + return; +} + +/** + * @brief free tx/rx urb, skb and rx buffer + * @param cardp pointer usb_card_rec + * @return N/A + */ +static void if_usb_free(struct usb_card_rec *cardp) +{ + lbs_deb_enter(LBS_DEB_USB); + + /* Unlink tx & rx urb */ + usb_kill_urb(cardp->tx_urb); + usb_kill_urb(cardp->rx_urb); + + usb_free_urb(cardp->tx_urb); + cardp->tx_urb = NULL; + + usb_free_urb(cardp->rx_urb); + cardp->rx_urb = NULL; + + kfree(cardp->bulk_out_buffer); + cardp->bulk_out_buffer = NULL; + + lbs_deb_leave(LBS_DEB_USB); +} + +static void if_usb_set_boot2_ver(struct lbs_private *priv) +{ + struct cmd_ds_set_boot2_ver b2_cmd; + + b2_cmd.action = 0; + b2_cmd.version = priv->boot2_version; + + if (lbs_cmd(priv, CMD_SET_BOOT2_VER, b2_cmd, NULL, 0)) + lbs_deb_usb("Setting boot2 version failed\n"); +} + +static void if_usb_fw_timeo(unsigned long priv) +{ + struct usb_card_rec *cardp = (void *)priv; + + if (cardp->fwdnldover) { + lbs_deb_usb("Download complete, no event. Assuming success\n"); + } else { + lbs_pr_err("Download timed out\n"); + cardp->surprise_removed = 1; + } + wake_up(&cardp->fw_wq); +} +/** + * @brief sets the configuration values + * @param ifnum interface number + * @param id pointer to usb_device_id + * @return 0 on success, error code on failure + */ +static int if_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct lbs_private *priv; + struct usb_card_rec *cardp; + int i; + + udev = interface_to_usbdev(intf); + + cardp = kzalloc(sizeof(struct usb_card_rec), GFP_KERNEL); + if (!cardp) { + lbs_pr_err("Out of memory allocating private data.\n"); + goto error; + } + + setup_timer(&cardp->fw_timeout, if_usb_fw_timeo, (unsigned long)cardp); + init_waitqueue_head(&cardp->fw_wq); + + cardp->udev = udev; + iface_desc = intf->cur_altsetting; + + lbs_deb_usbd(&udev->dev, "bcdUSB = 0x%X bDeviceClass = 0x%X" + " bDeviceSubClass = 0x%X, bDeviceProtocol = 0x%X\n", + le16_to_cpu(udev->descriptor.bcdUSB), + udev->descriptor.bDeviceClass, + udev->descriptor.bDeviceSubClass, + udev->descriptor.bDeviceProtocol); + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_BULK)) { + /* we found a bulk in endpoint */ + lbs_deb_usbd(&udev->dev, "Bulk in size is %d\n", + le16_to_cpu(endpoint->wMaxPacketSize)); + if (!(cardp->rx_urb = usb_alloc_urb(0, GFP_KERNEL))) { + lbs_deb_usbd(&udev->dev, + "Rx URB allocation failed\n"); + goto dealloc; + } + cardp->bulk_in_size = + le16_to_cpu(endpoint->wMaxPacketSize); + cardp->bulk_in_endpointAddr = + (endpoint-> + bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + lbs_deb_usbd(&udev->dev, "in_endpoint = %d\n", + endpoint->bEndpointAddress); + } + + if (((endpoint-> + bEndpointAddress & USB_ENDPOINT_DIR_MASK) == + USB_DIR_OUT) + && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_BULK)) { + /* We found bulk out endpoint */ + if (!(cardp->tx_urb = usb_alloc_urb(0, GFP_KERNEL))) { + lbs_deb_usbd(&udev->dev, + "Tx URB allocation failed\n"); + goto dealloc; + } + + cardp->bulk_out_size = + le16_to_cpu(endpoint->wMaxPacketSize); + lbs_deb_usbd(&udev->dev, + "Bulk out size is %d\n", + le16_to_cpu(endpoint->wMaxPacketSize)); + cardp->bulk_out_endpointAddr = + endpoint->bEndpointAddress; + lbs_deb_usbd(&udev->dev, "out_endpoint = %d\n", + endpoint->bEndpointAddress); + cardp->bulk_out_buffer = + kmalloc(MRVDRV_ETH_TX_PACKET_BUFFER_SIZE, + GFP_KERNEL); + + if (!cardp->bulk_out_buffer) { + lbs_deb_usbd(&udev->dev, + "Could not allocate buffer\n"); + goto dealloc; + } + } + } + + /* Upload firmware */ + cardp->rinfo.cardp = cardp; + if (if_usb_prog_firmware(cardp)) { + lbs_deb_usbd(&udev->dev, "FW upload failed"); + goto err_prog_firmware; + } + + if (!(priv = lbs_add_card(cardp, &udev->dev))) + goto err_prog_firmware; + + cardp->priv = priv; + cardp->priv->fw_ready = 1; + + priv->hw_host_to_card = if_usb_host_to_card; + priv->hw_get_int_status = if_usb_get_int_status; + priv->hw_read_event_cause = if_usb_read_event_cause; + priv->boot2_version = udev->descriptor.bcdDevice; + + if_usb_submit_rx_urb(cardp); + + if (lbs_start_card(priv)) + goto err_start_card; + + if_usb_set_boot2_ver(priv); + + usb_get_dev(udev); + usb_set_intfdata(intf, cardp); + + return 0; + +err_start_card: + lbs_remove_card(priv); +err_prog_firmware: + if_usb_reset_device(cardp); +dealloc: + if_usb_free(cardp); + +error: + return -ENOMEM; +} + +/** + * @brief free resource and cleanup + * @param intf USB interface structure + * @return N/A + */ +static void if_usb_disconnect(struct usb_interface *intf) +{ + struct usb_card_rec *cardp = usb_get_intfdata(intf); + struct lbs_private *priv = (struct lbs_private *) cardp->priv; + + lbs_deb_enter(LBS_DEB_MAIN); + + /* Update Surprise removed to TRUE */ + cardp->surprise_removed = 1; + + if (priv) { + + priv->surpriseremoved = 1; + lbs_stop_card(priv); + lbs_remove_card(priv); + } + + /* this is (apparently?) necessary for future usage of the device */ + lbs_prepare_and_send_command(priv, CMD_802_11_RESET, CMD_ACT_HALT, + 0, 0, NULL); + + /* Unlink and free urb */ + if_usb_free(cardp); + + usb_set_intfdata(intf, NULL); + usb_put_dev(interface_to_usbdev(intf)); + + lbs_deb_leave(LBS_DEB_MAIN); +} + +/** + * @brief This function download FW + * @param priv pointer to struct lbs_private + * @return 0 + */ +static int if_usb_send_fw_pkt(struct usb_card_rec *cardp) +{ + struct FWData *fwdata; + struct fwheader *fwheader; + u8 *firmware = cardp->fw->data; + + fwdata = kmalloc(sizeof(struct FWData), GFP_ATOMIC); + + if (!fwdata) + return -1; + + fwheader = &fwdata->fwheader; + + if (!cardp->CRC_OK) { + cardp->totalbytes = cardp->fwlastblksent; + cardp->fwseqnum = cardp->lastseqnum - 1; + } + + /* + lbs_deb_usbd(&cardp->udev->dev, "totalbytes = %d\n", + cardp->totalbytes); + */ + + memcpy(fwheader, &firmware[cardp->totalbytes], + sizeof(struct fwheader)); + + cardp->fwlastblksent = cardp->totalbytes; + cardp->totalbytes += sizeof(struct fwheader); + + /* lbs_deb_usbd(&cardp->udev->dev,"Copy Data\n"); */ + memcpy(fwdata->data, &firmware[cardp->totalbytes], + le32_to_cpu(fwdata->fwheader.datalength)); + + /* + lbs_deb_usbd(&cardp->udev->dev, + "Data length = %d\n", le32_to_cpu(fwdata->fwheader.datalength)); + */ + + cardp->fwseqnum = cardp->fwseqnum + 1; + + fwdata->seqnum = cpu_to_le32(cardp->fwseqnum); + cardp->lastseqnum = cardp->fwseqnum; + cardp->totalbytes += le32_to_cpu(fwdata->fwheader.datalength); + + if (fwheader->dnldcmd == cpu_to_le32(FW_HAS_DATA_TO_RECV)) { + /* + lbs_deb_usbd(&cardp->udev->dev, "There are data to follow\n"); + lbs_deb_usbd(&cardp->udev->dev, + "seqnum = %d totalbytes = %d\n", cardp->fwseqnum, + cardp->totalbytes); + */ + memcpy(cardp->bulk_out_buffer, fwheader, FW_DATA_XMIT_SIZE); + usb_tx_block(cardp, cardp->bulk_out_buffer, FW_DATA_XMIT_SIZE); + + } else if (fwdata->fwheader.dnldcmd == cpu_to_le32(FW_HAS_LAST_BLOCK)) { + /* + lbs_deb_usbd(&cardp->udev->dev, + "Host has finished FW downloading\n"); + lbs_deb_usbd(&cardp->udev->dev, + "Donwloading FW JUMP BLOCK\n"); + */ + memcpy(cardp->bulk_out_buffer, fwheader, FW_DATA_XMIT_SIZE); + usb_tx_block(cardp, cardp->bulk_out_buffer, FW_DATA_XMIT_SIZE); + cardp->fwfinalblk = 1; + } + + /* + lbs_deb_usbd(&cardp->udev->dev, + "The firmware download is done size is %d\n", + cardp->totalbytes); + */ + + kfree(fwdata); + + return 0; +} + +static int if_usb_reset_device(struct usb_card_rec *cardp) +{ + struct cmd_ds_command *cmd = (void *)&cardp->bulk_out_buffer[4]; + int ret; + + lbs_deb_enter(LBS_DEB_USB); + + *(__le32 *)cardp->bulk_out_buffer = cpu_to_le32(CMD_TYPE_REQUEST); + + cmd->command = cpu_to_le16(CMD_802_11_RESET); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_reset) + S_DS_GEN); + cmd->result = cpu_to_le16(0); + cmd->seqnum = cpu_to_le16(0x5a5a); + cmd->params.reset.action = cpu_to_le16(CMD_ACT_HALT); + usb_tx_block(cardp, cardp->bulk_out_buffer, 4 + S_DS_GEN + sizeof(struct cmd_ds_802_11_reset)); + + msleep(100); + ret = usb_reset_device(cardp->udev); + msleep(100); + + lbs_deb_leave_args(LBS_DEB_USB, "ret %d", ret); + + return ret; +} + +/** + * @brief This function transfer the data to the device. + * @param priv pointer to struct lbs_private + * @param payload pointer to payload data + * @param nb data length + * @return 0 or -1 + */ +static int usb_tx_block(struct usb_card_rec *cardp, u8 * payload, u16 nb) +{ + int ret = -1; + + /* check if device is removed */ + if (cardp->surprise_removed) { + lbs_deb_usbd(&cardp->udev->dev, "Device removed\n"); + goto tx_ret; + } + + usb_fill_bulk_urb(cardp->tx_urb, cardp->udev, + usb_sndbulkpipe(cardp->udev, + cardp->bulk_out_endpointAddr), + payload, nb, if_usb_write_bulk_callback, cardp); + + cardp->tx_urb->transfer_flags |= URB_ZERO_PACKET; + + if ((ret = usb_submit_urb(cardp->tx_urb, GFP_ATOMIC))) { + /* transfer failed */ + lbs_deb_usbd(&cardp->udev->dev, "usb_submit_urb failed: %d\n", ret); + ret = -1; + } else { + /* lbs_deb_usbd(&cardp->udev->dev, "usb_submit_urb success\n"); */ + ret = 0; + } + +tx_ret: + return ret; +} + +static int __if_usb_submit_rx_urb(struct usb_card_rec *cardp, + void (*callbackfn)(struct urb *urb)) +{ + struct sk_buff *skb; + struct read_cb_info *rinfo = &cardp->rinfo; + int ret = -1; + + if (!(skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE))) { + lbs_pr_err("No free skb\n"); + goto rx_ret; + } + + rinfo->skb = skb; + + /* Fill the receive configuration URB and initialise the Rx call back */ + usb_fill_bulk_urb(cardp->rx_urb, cardp->udev, + usb_rcvbulkpipe(cardp->udev, + cardp->bulk_in_endpointAddr), + (void *) (skb->tail + (size_t) IPFIELD_ALIGN_OFFSET), + MRVDRV_ETH_RX_PACKET_BUFFER_SIZE, callbackfn, + rinfo); + + cardp->rx_urb->transfer_flags |= URB_ZERO_PACKET; + + /* lbs_deb_usbd(&cardp->udev->dev, "Pointer for rx_urb %p\n", cardp->rx_urb); */ + if ((ret = usb_submit_urb(cardp->rx_urb, GFP_ATOMIC))) { + /* handle failure conditions */ + lbs_deb_usbd(&cardp->udev->dev, "Submit Rx URB failed: %d\n", ret); + kfree_skb(skb); + rinfo->skb = NULL; + ret = -1; + } else { + /* lbs_deb_usbd(&cardp->udev->dev, "Submit Rx URB success\n"); */ + ret = 0; + } + +rx_ret: + return ret; +} + +static int if_usb_submit_rx_urb_fwload(struct usb_card_rec *cardp) +{ + return __if_usb_submit_rx_urb(cardp, &if_usb_receive_fwload); +} + +static int if_usb_submit_rx_urb(struct usb_card_rec *cardp) +{ + return __if_usb_submit_rx_urb(cardp, &if_usb_receive); +} + +static void if_usb_receive_fwload(struct urb *urb) +{ + struct read_cb_info *rinfo = (struct read_cb_info *)urb->context; + struct sk_buff *skb = rinfo->skb; + struct usb_card_rec *cardp = (struct usb_card_rec *)rinfo->cardp; + struct fwsyncheader *syncfwheader; + struct bootcmdrespStr bootcmdresp; + + if (urb->status) { + lbs_deb_usbd(&cardp->udev->dev, + "URB status is failed during fw load\n"); + kfree_skb(skb); + return; + } + + if (cardp->fwdnldover) { + __le32 *tmp = (__le32 *)(skb->data + IPFIELD_ALIGN_OFFSET); + + if (tmp[0] == cpu_to_le32(CMD_TYPE_INDICATION) && + tmp[1] == cpu_to_le32(MACREG_INT_CODE_FIRMWARE_READY)) { + lbs_pr_info("Firmware ready event received\n"); + wake_up(&cardp->fw_wq); + } else { + lbs_deb_usb("Waiting for confirmation; got %x %x\n", le32_to_cpu(tmp[0]), + le32_to_cpu(tmp[1])); + if_usb_submit_rx_urb_fwload(cardp); + } + kfree_skb(skb); + return; + } + if (cardp->bootcmdresp <= 0) { + memcpy (&bootcmdresp, skb->data + IPFIELD_ALIGN_OFFSET, + sizeof(bootcmdresp)); + if (le16_to_cpu(cardp->udev->descriptor.bcdDevice) < 0x3106) { + kfree_skb(skb); + if_usb_submit_rx_urb_fwload(cardp); + cardp->bootcmdresp = 1; + lbs_deb_usbd(&cardp->udev->dev, + "Received valid boot command response\n"); + return; + } + if (bootcmdresp.u32magicnumber != cpu_to_le32(BOOT_CMD_MAGIC_NUMBER)) { + if (bootcmdresp.u32magicnumber == cpu_to_le32(CMD_TYPE_REQUEST) || + bootcmdresp.u32magicnumber == cpu_to_le32(CMD_TYPE_DATA) || + bootcmdresp.u32magicnumber == cpu_to_le32(CMD_TYPE_INDICATION)) { + if (!cardp->bootcmdresp) + lbs_pr_info("Firmware already seems alive; resetting\n"); + cardp->bootcmdresp = -1; + } else { + lbs_pr_info("boot cmd response wrong magic number (0x%x)\n", + le32_to_cpu(bootcmdresp.u32magicnumber)); + } + } else if (bootcmdresp.u8cmd_tag != BOOT_CMD_FW_BY_USB) { + lbs_pr_info( + "boot cmd response cmd_tag error (%d)\n", + bootcmdresp.u8cmd_tag); + } else if (bootcmdresp.u8result != BOOT_CMD_RESP_OK) { + lbs_pr_info( + "boot cmd response result error (%d)\n", + bootcmdresp.u8result); + } else { + cardp->bootcmdresp = 1; + lbs_deb_usbd(&cardp->udev->dev, + "Received valid boot command response\n"); + } + kfree_skb(skb); + if_usb_submit_rx_urb_fwload(cardp); + return; + } + + syncfwheader = kmalloc(sizeof(struct fwsyncheader), GFP_ATOMIC); + if (!syncfwheader) { + lbs_deb_usbd(&cardp->udev->dev, "Failure to allocate syncfwheader\n"); + kfree_skb(skb); + return; + } + + memcpy(syncfwheader, skb->data + IPFIELD_ALIGN_OFFSET, + sizeof(struct fwsyncheader)); + + if (!syncfwheader->cmd) { + /* + lbs_deb_usbd(&cardp->udev->dev, + "FW received Blk with correct CRC\n"); + lbs_deb_usbd(&cardp->udev->dev, + "FW received Blk seqnum = %d\n", + syncfwheader->seqnum); + */ + cardp->CRC_OK = 1; + } else { + lbs_deb_usbd(&cardp->udev->dev, + "FW received Blk with CRC error\n"); + cardp->CRC_OK = 0; + } + + kfree_skb(skb); + + /* reschedule timer for 200ms hence */ + mod_timer(&cardp->fw_timeout, jiffies + (HZ/5)); + + if (cardp->fwfinalblk) { + cardp->fwdnldover = 1; + goto exit; + } + + if_usb_send_fw_pkt(cardp); + + exit: + if_usb_submit_rx_urb_fwload(cardp); + + kfree(syncfwheader); + + return; +} + +#define MRVDRV_MIN_PKT_LEN 30 + +static inline void process_cmdtypedata(int recvlength, struct sk_buff *skb, + struct usb_card_rec *cardp, + struct lbs_private *priv) +{ + if (recvlength > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + + MESSAGE_HEADER_LEN || recvlength < MRVDRV_MIN_PKT_LEN) { + lbs_deb_usbd(&cardp->udev->dev, + "Packet length is Invalid\n"); + kfree_skb(skb); + return; + } + + skb_reserve(skb, IPFIELD_ALIGN_OFFSET); + skb_put(skb, recvlength); + skb_pull(skb, MESSAGE_HEADER_LEN); + lbs_process_rxed_packet(priv, skb); + priv->upld_len = (recvlength - MESSAGE_HEADER_LEN); +} + +static inline void process_cmdrequest(int recvlength, u8 *recvbuff, + struct sk_buff *skb, + struct usb_card_rec *cardp, + struct lbs_private *priv) +{ + u8 *cmdbuf; + if (recvlength > LBS_CMD_BUFFER_SIZE) { + lbs_deb_usbd(&cardp->udev->dev, + "The receive buffer is too large\n"); + kfree_skb(skb); + return; + } + + if (!in_interrupt()) + BUG(); + + spin_lock(&priv->driver_lock); + /* take care of cur_cmd = NULL case by reading the + * data to clear the interrupt */ + if (!priv->cur_cmd) { + cmdbuf = priv->upld_buf; + priv->hisregcpy &= ~MRVDRV_CMD_UPLD_RDY; + } else + cmdbuf = (u8 *) priv->cur_cmd->cmdbuf; + + cardp->usb_int_cause |= MRVDRV_CMD_UPLD_RDY; + priv->upld_len = (recvlength - MESSAGE_HEADER_LEN); + memcpy(cmdbuf, recvbuff + MESSAGE_HEADER_LEN, + priv->upld_len); + + kfree_skb(skb); + lbs_interrupt(priv); + spin_unlock(&priv->driver_lock); + + lbs_deb_usbd(&cardp->udev->dev, + "Wake up main thread to handle cmd response\n"); + + return; +} + +/** + * @brief This function reads of the packet into the upload buff, + * wake up the main thread and initialise the Rx callack. + * + * @param urb pointer to struct urb + * @return N/A + */ +static void if_usb_receive(struct urb *urb) +{ + struct read_cb_info *rinfo = (struct read_cb_info *)urb->context; + struct sk_buff *skb = rinfo->skb; + struct usb_card_rec *cardp = (struct usb_card_rec *) rinfo->cardp; + struct lbs_private *priv = cardp->priv; + + int recvlength = urb->actual_length; + u8 *recvbuff = NULL; + u32 recvtype = 0; + + lbs_deb_enter(LBS_DEB_USB); + + if (recvlength) { + __le32 tmp; + + if (urb->status) { + lbs_deb_usbd(&cardp->udev->dev, + "URB status is failed\n"); + kfree_skb(skb); + goto setup_for_next; + } + + recvbuff = skb->data + IPFIELD_ALIGN_OFFSET; + memcpy(&tmp, recvbuff, sizeof(u32)); + recvtype = le32_to_cpu(tmp); + lbs_deb_usbd(&cardp->udev->dev, + "Recv length = 0x%x, Recv type = 0x%X\n", + recvlength, recvtype); + } else if (urb->status) { + kfree_skb(skb); + goto rx_exit; + } + + switch (recvtype) { + case CMD_TYPE_DATA: + process_cmdtypedata(recvlength, skb, cardp, priv); + break; + + case CMD_TYPE_REQUEST: + process_cmdrequest(recvlength, recvbuff, skb, cardp, priv); + break; + + case CMD_TYPE_INDICATION: + /* Event cause handling */ + spin_lock(&priv->driver_lock); + cardp->usb_event_cause = le32_to_cpu(*(__le32 *) (recvbuff + MESSAGE_HEADER_LEN)); + lbs_deb_usbd(&cardp->udev->dev,"**EVENT** 0x%X\n", + cardp->usb_event_cause); + if (cardp->usb_event_cause & 0xffff0000) { + lbs_send_tx_feedback(priv); + spin_unlock(&priv->driver_lock); + break; + } + cardp->usb_event_cause <<= 3; + cardp->usb_int_cause |= MRVDRV_CARDEVENT; + kfree_skb(skb); + lbs_interrupt(priv); + spin_unlock(&priv->driver_lock); + goto rx_exit; + default: + lbs_deb_usbd(&cardp->udev->dev, "Unknown command type 0x%X\n", + recvtype); + kfree_skb(skb); + break; + } + +setup_for_next: + if_usb_submit_rx_urb(cardp); +rx_exit: + lbs_deb_leave(LBS_DEB_USB); +} + +/** + * @brief This function downloads data to FW + * @param priv pointer to struct lbs_private structure + * @param type type of data + * @param buf pointer to data buffer + * @param len number of bytes + * @return 0 or -1 + */ +static int if_usb_host_to_card(struct lbs_private *priv, + u8 type, + u8 *payload, + u16 nb) +{ + struct usb_card_rec *cardp = (struct usb_card_rec *)priv->card; + + lbs_deb_usbd(&cardp->udev->dev,"*** type = %u\n", type); + lbs_deb_usbd(&cardp->udev->dev,"size after = %d\n", nb); + + if (type == MVMS_CMD) { + __le32 tmp = cpu_to_le32(CMD_TYPE_REQUEST); + priv->dnld_sent = DNLD_CMD_SENT; + memcpy(cardp->bulk_out_buffer, (u8 *) & tmp, + MESSAGE_HEADER_LEN); + + } else { + __le32 tmp = cpu_to_le32(CMD_TYPE_DATA); + priv->dnld_sent = DNLD_DATA_SENT; + memcpy(cardp->bulk_out_buffer, (u8 *) & tmp, + MESSAGE_HEADER_LEN); + } + + memcpy((cardp->bulk_out_buffer + MESSAGE_HEADER_LEN), payload, nb); + + return usb_tx_block(cardp, cardp->bulk_out_buffer, + nb + MESSAGE_HEADER_LEN); +} + +/* called with priv->driver_lock held */ +static int if_usb_get_int_status(struct lbs_private *priv, u8 *ireg) +{ + struct usb_card_rec *cardp = priv->card; + + *ireg = cardp->usb_int_cause; + cardp->usb_int_cause = 0; + + lbs_deb_usbd(&cardp->udev->dev,"Int cause is 0x%X\n", *ireg); + + return 0; +} + +static int if_usb_read_event_cause(struct lbs_private *priv) +{ + struct usb_card_rec *cardp = priv->card; + + priv->eventcause = cardp->usb_event_cause; + /* Re-submit rx urb here to avoid event lost issue */ + if_usb_submit_rx_urb(cardp); + return 0; +} + +/** + * @brief This function issues Boot command to the Boot2 code + * @param ivalue 1:Boot from FW by USB-Download + * 2:Boot from FW in EEPROM + * @return 0 + */ +static int if_usb_issue_boot_command(struct usb_card_rec *cardp, int ivalue) +{ + struct bootcmdstr sbootcmd; + int i; + + /* Prepare command */ + sbootcmd.u32magicnumber = cpu_to_le32(BOOT_CMD_MAGIC_NUMBER); + sbootcmd.u8cmd_tag = ivalue; + for (i=0; i<11; i++) + sbootcmd.au8dumy[i]=0x00; + memcpy(cardp->bulk_out_buffer, &sbootcmd, sizeof(struct bootcmdstr)); + + /* Issue command */ + usb_tx_block(cardp, cardp->bulk_out_buffer, sizeof(struct bootcmdstr)); + + return 0; +} + + +/** + * @brief This function checks the validity of Boot2/FW image. + * + * @param data pointer to image + * len image length + * @return 0 or -1 + */ +static int check_fwfile_format(u8 *data, u32 totlen) +{ + u32 bincmd, exit; + u32 blksize, offset, len; + int ret; + + ret = 1; + exit = len = 0; + + do { + struct fwheader *fwh = (void *)data; + + bincmd = le32_to_cpu(fwh->dnldcmd); + blksize = le32_to_cpu(fwh->datalength); + switch (bincmd) { + case FW_HAS_DATA_TO_RECV: + offset = sizeof(struct fwheader) + blksize; + data += offset; + len += offset; + if (len >= totlen) + exit = 1; + break; + case FW_HAS_LAST_BLOCK: + exit = 1; + ret = 0; + break; + default: + exit = 1; + break; + } + } while (!exit); + + if (ret) + lbs_pr_err("firmware file format check FAIL\n"); + else + lbs_deb_fw("firmware file format check PASS\n"); + + return ret; +} + + +static int if_usb_prog_firmware(struct usb_card_rec *cardp) +{ + int i = 0; + static int reset_count = 10; + int ret = 0; + + lbs_deb_enter(LBS_DEB_USB); + + if ((ret = request_firmware(&cardp->fw, lbs_fw_name, + &cardp->udev->dev)) < 0) { + lbs_pr_err("request_firmware() failed with %#x\n", ret); + lbs_pr_err("firmware %s not found\n", lbs_fw_name); + goto done; + } + + if (check_fwfile_format(cardp->fw->data, cardp->fw->size)) + goto release_fw; + +restart: + if (if_usb_submit_rx_urb_fwload(cardp) < 0) { + lbs_deb_usbd(&cardp->udev->dev, "URB submission is failed\n"); + ret = -1; + goto release_fw; + } + + cardp->bootcmdresp = 0; + do { + int j = 0; + i++; + /* Issue Boot command = 1, Boot from Download-FW */ + if_usb_issue_boot_command(cardp, BOOT_CMD_FW_BY_USB); + /* wait for command response */ + do { + j++; + msleep_interruptible(100); + } while (cardp->bootcmdresp == 0 && j < 10); + } while (cardp->bootcmdresp == 0 && i < 5); + + if (cardp->bootcmdresp <= 0) { + if (--reset_count >= 0) { + if_usb_reset_device(cardp); + goto restart; + } + return -1; + } + + i = 0; + + cardp->totalbytes = 0; + cardp->fwlastblksent = 0; + cardp->CRC_OK = 1; + cardp->fwdnldover = 0; + cardp->fwseqnum = -1; + cardp->totalbytes = 0; + cardp->fwfinalblk = 0; + + /* Send the first firmware packet... */ + if_usb_send_fw_pkt(cardp); + + /* ... and wait for the process to complete */ + wait_event_interruptible(cardp->fw_wq, cardp->surprise_removed || cardp->fwdnldover); + + del_timer_sync(&cardp->fw_timeout); + usb_kill_urb(cardp->rx_urb); + + if (!cardp->fwdnldover) { + lbs_pr_info("failed to load fw, resetting device!\n"); + if (--reset_count >= 0) { + if_usb_reset_device(cardp); + goto restart; + } + + lbs_pr_info("FW download failure, time = %d ms\n", i * 100); + ret = -1; + goto release_fw; + } + +release_fw: + release_firmware(cardp->fw); + cardp->fw = NULL; + +done: + lbs_deb_leave_args(LBS_DEB_USB, "ret %d", ret); + return ret; +} + + +#ifdef CONFIG_PM +static int if_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_card_rec *cardp = usb_get_intfdata(intf); + struct lbs_private *priv = cardp->priv; + + lbs_deb_enter(LBS_DEB_USB); + + if (priv->psstate != PS_STATE_FULL_POWER) + return -1; + + netif_device_detach(priv->dev); + netif_device_detach(priv->mesh_dev); + + /* Unlink tx & rx urb */ + usb_kill_urb(cardp->tx_urb); + usb_kill_urb(cardp->rx_urb); + + lbs_deb_leave(LBS_DEB_USB); + return 0; +} + +static int if_usb_resume(struct usb_interface *intf) +{ + struct usb_card_rec *cardp = usb_get_intfdata(intf); + struct lbs_private *priv = cardp->priv; + + lbs_deb_enter(LBS_DEB_USB); + + if_usb_submit_rx_urb(cardp); + + netif_device_attach(priv->dev); + netif_device_attach(priv->mesh_dev); + + lbs_deb_leave(LBS_DEB_USB); + return 0; +} +#else +#define if_usb_suspend NULL +#define if_usb_resume NULL +#endif + +static struct usb_driver if_usb_driver = { + .name = DRV_NAME, + .probe = if_usb_probe, + .disconnect = if_usb_disconnect, + .id_table = if_usb_table, + .suspend = if_usb_suspend, + .resume = if_usb_resume, +}; + +static int __init if_usb_init_module(void) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_MAIN); + + ret = usb_register(&if_usb_driver); + + lbs_deb_leave_args(LBS_DEB_MAIN, "ret %d", ret); + return ret; +} + +static void __exit if_usb_exit_module(void) +{ + lbs_deb_enter(LBS_DEB_MAIN); + + usb_deregister(&if_usb_driver); + + lbs_deb_leave(LBS_DEB_MAIN); +} + +module_init(if_usb_init_module); +module_exit(if_usb_exit_module); + +MODULE_DESCRIPTION("8388 USB WLAN Driver"); +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_LICENSE("GPL"); diff --git a/package/libertas/src/if_usb.h b/package/libertas/src/if_usb.h new file mode 100644 index 0000000000..668410f820 --- /dev/null +++ b/package/libertas/src/if_usb.h @@ -0,0 +1,108 @@ +#ifndef _LBS_IF_USB_H +#define _LBS_IF_USB_H + +#include <linux/wait.h> +#include <linux/timer.h> + +struct lbs_private; + +/** + * This file contains definition for USB interface. + */ +#define CMD_TYPE_REQUEST 0xF00DFACE +#define CMD_TYPE_DATA 0xBEADC0DE +#define CMD_TYPE_INDICATION 0xBEEFFACE + +#define IPFIELD_ALIGN_OFFSET 2 + +#define BOOT_CMD_FW_BY_USB 0x01 +#define BOOT_CMD_FW_IN_EEPROM 0x02 +#define BOOT_CMD_UPDATE_BOOT2 0x03 +#define BOOT_CMD_UPDATE_FW 0x04 +#define BOOT_CMD_MAGIC_NUMBER 0x4C56524D /* M=>0x4D,R=>0x52,V=>0x56,L=>0x4C */ + +struct bootcmdstr +{ + __le32 u32magicnumber; + u8 u8cmd_tag; + u8 au8dumy[11]; +}; + +#define BOOT_CMD_RESP_OK 0x0001 +#define BOOT_CMD_RESP_FAIL 0x0000 + +struct bootcmdrespStr +{ + __le32 u32magicnumber; + u8 u8cmd_tag; + u8 u8result; + u8 au8dumy[2]; +}; + +/* read callback private data */ +struct read_cb_info { + struct usb_card_rec *cardp; + struct sk_buff *skb; +}; + +/** USB card description structure*/ +struct usb_card_rec { + struct usb_device *udev; + struct urb *rx_urb, *tx_urb; + struct lbs_private *priv; + struct read_cb_info rinfo; + + int bulk_in_size; + u8 bulk_in_endpointAddr; + + u8 *bulk_out_buffer; + int bulk_out_size; + u8 bulk_out_endpointAddr; + + const struct firmware *fw; + struct timer_list fw_timeout; + wait_queue_head_t fw_wq; + u8 CRC_OK; + u32 fwseqnum; + u32 lastseqnum; + u32 totalbytes; + u32 fwlastblksent; + u8 fwdnldover; + u8 fwfinalblk; + u8 surprise_removed; + + u32 usb_event_cause; + u8 usb_int_cause; + + s8 bootcmdresp; +}; + +/** fwheader */ +struct fwheader { + __le32 dnldcmd; + __le32 baseaddr; + __le32 datalength; + __le32 CRC; +}; + +#define FW_MAX_DATA_BLK_SIZE 600 +/** FWData */ +struct FWData { + struct fwheader fwheader; + __le32 seqnum; + u8 data[FW_MAX_DATA_BLK_SIZE]; +}; + +/** fwsyncheader */ +struct fwsyncheader { + __le32 cmd; + __le32 seqnum; +}; + +#define FW_HAS_DATA_TO_RECV 0x00000001 +#define FW_HAS_LAST_BLOCK 0x00000004 + +#define FW_DATA_XMIT_SIZE \ + sizeof(struct fwheader) + le32_to_cpu(fwdata->fwheader.datalength) + sizeof(u32) + +#endif diff --git a/package/libertas/src/join.c b/package/libertas/src/join.c new file mode 100644 index 0000000000..14425d9a19 --- /dev/null +++ b/package/libertas/src/join.c @@ -0,0 +1,894 @@ +/** + * Functions implementing wlan infrastructure and adhoc join routines, + * IOCTL handlers as well as command preperation and response routines + * for sending adhoc start, adhoc join, and association commands + * to the firmware. + */ +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/wireless.h> +#include <linux/etherdevice.h> + +#include <net/iw_handler.h> + +#include "host.h" +#include "decl.h" +#include "join.h" +#include "dev.h" +#include "assoc.h" + +/* The firmware needs certain bits masked out of the beacon-derviced capability + * field when associating/joining to BSSs. + */ +#define CAPINFO_MASK (~(0xda00)) + +/** + * @brief This function finds common rates between rate1 and card rates. + * + * It will fill common rates in rate1 as output if found. + * + * NOTE: Setting the MSB of the basic rates need to be taken + * care, either before or after calling this function + * + * @param priv A pointer to struct lbs_private structure + * @param rate1 the buffer which keeps input and output + * @param rate1_size the size of rate1 buffer; new size of buffer on return + * + * @return 0 or -1 + */ +static int get_common_rates(struct lbs_private *priv, + u8 *rates, + u16 *rates_size) +{ + u8 *card_rates = lbs_bg_rates; + size_t num_card_rates = sizeof(lbs_bg_rates); + int ret = 0, i, j; + u8 tmp[30]; + size_t tmp_size = 0; + + /* For each rate in card_rates that exists in rate1, copy to tmp */ + for (i = 0; card_rates[i] && (i < num_card_rates); i++) { + for (j = 0; rates[j] && (j < *rates_size); j++) { + if (rates[j] == card_rates[i]) + tmp[tmp_size++] = card_rates[i]; + } + } + + lbs_deb_hex(LBS_DEB_JOIN, "AP rates ", rates, *rates_size); + lbs_deb_hex(LBS_DEB_JOIN, "card rates ", card_rates, num_card_rates); + lbs_deb_hex(LBS_DEB_JOIN, "common rates", tmp, tmp_size); + lbs_deb_join("TX data rate 0x%02x\n", priv->cur_rate); + + if (!priv->auto_rate) { + for (i = 0; i < tmp_size; i++) { + if (tmp[i] == priv->cur_rate) + goto done; + } + lbs_pr_alert("Previously set fixed data rate %#x isn't " + "compatible with the network.\n", priv->cur_rate); + ret = -1; + goto done; + } + ret = 0; + +done: + memset(rates, 0, *rates_size); + *rates_size = min_t(int, tmp_size, *rates_size); + memcpy(rates, tmp, *rates_size); + return ret; +} + + +/** + * @brief Sets the MSB on basic rates as the firmware requires + * + * Scan through an array and set the MSB for basic data rates. + * + * @param rates buffer of data rates + * @param len size of buffer + */ +static void lbs_set_basic_rate_flags(u8 *rates, size_t len) +{ + int i; + + for (i = 0; i < len; i++) { + if (rates[i] == 0x02 || rates[i] == 0x04 || + rates[i] == 0x0b || rates[i] == 0x16) + rates[i] |= 0x80; + } +} + +/** + * @brief Unsets the MSB on basic rates + * + * Scan through an array and unset the MSB for basic data rates. + * + * @param rates buffer of data rates + * @param len size of buffer + */ +void lbs_unset_basic_rate_flags(u8 *rates, size_t len) +{ + int i; + + for (i = 0; i < len; i++) + rates[i] &= 0x7f; +} + + +/** + * @brief Associate to a specific BSS discovered in a scan + * + * @param priv A pointer to struct lbs_private structure + * @param pbssdesc Pointer to the BSS descriptor to associate with. + * + * @return 0-success, otherwise fail + */ +int lbs_associate(struct lbs_private *priv, struct assoc_request *assoc_req) +{ + int ret; + + lbs_deb_enter(LBS_DEB_ASSOC); + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_AUTHENTICATE, + 0, CMD_OPTION_WAITFORRSP, + 0, assoc_req->bss.bssid); + + if (ret) + goto done; + + /* set preamble to firmware */ + if ( (priv->capability & WLAN_CAPABILITY_SHORT_PREAMBLE) + && (assoc_req->bss.capability & WLAN_CAPABILITY_SHORT_PREAMBLE)) + priv->preamble = CMD_TYPE_SHORT_PREAMBLE; + else + priv->preamble = CMD_TYPE_LONG_PREAMBLE; + + lbs_set_radio_control(priv); + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_ASSOCIATE, + 0, CMD_OPTION_WAITFORRSP, 0, assoc_req); + +done: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + +/** + * @brief Start an Adhoc Network + * + * @param priv A pointer to struct lbs_private structure + * @param adhocssid The ssid of the Adhoc Network + * @return 0--success, -1--fail + */ +int lbs_start_adhoc_network(struct lbs_private *priv, + struct assoc_request *assoc_req) +{ + int ret = 0; + + priv->adhoccreate = 1; + + if (priv->capability & WLAN_CAPABILITY_SHORT_PREAMBLE) { + lbs_deb_join("AdhocStart: Short preamble\n"); + priv->preamble = CMD_TYPE_SHORT_PREAMBLE; + } else { + lbs_deb_join("AdhocStart: Long preamble\n"); + priv->preamble = CMD_TYPE_LONG_PREAMBLE; + } + + lbs_set_radio_control(priv); + + lbs_deb_join("AdhocStart: channel = %d\n", assoc_req->channel); + lbs_deb_join("AdhocStart: band = %d\n", assoc_req->band); + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_AD_HOC_START, + 0, CMD_OPTION_WAITFORRSP, 0, assoc_req); + + return ret; +} + +/** + * @brief Join an adhoc network found in a previous scan + * + * @param priv A pointer to struct lbs_private structure + * @param pbssdesc Pointer to a BSS descriptor found in a previous scan + * to attempt to join + * + * @return 0--success, -1--fail + */ +int lbs_join_adhoc_network(struct lbs_private *priv, + struct assoc_request *assoc_req) +{ + struct bss_descriptor * bss = &assoc_req->bss; + int ret = 0; + + lbs_deb_join("%s: Current SSID '%s', ssid length %u\n", + __func__, + escape_essid(priv->curbssparams.ssid, + priv->curbssparams.ssid_len), + priv->curbssparams.ssid_len); + lbs_deb_join("%s: requested ssid '%s', ssid length %u\n", + __func__, escape_essid(bss->ssid, bss->ssid_len), + bss->ssid_len); + + /* check if the requested SSID is already joined */ + if ( priv->curbssparams.ssid_len + && !lbs_ssid_cmp(priv->curbssparams.ssid, + priv->curbssparams.ssid_len, + bss->ssid, bss->ssid_len) + && (priv->mode == IW_MODE_ADHOC) + && (priv->connect_status == LBS_CONNECTED)) { + union iwreq_data wrqu; + + lbs_deb_join("ADHOC_J_CMD: New ad-hoc SSID is the same as " + "current, not attempting to re-join"); + + /* Send the re-association event though, because the association + * request really was successful, even if just a null-op. + */ + memset(&wrqu, 0, sizeof(wrqu)); + memcpy(wrqu.ap_addr.sa_data, priv->curbssparams.bssid, + ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL); + goto out; + } + + /* Use shortpreamble only when both creator and card supports + short preamble */ + if ( !(bss->capability & WLAN_CAPABILITY_SHORT_PREAMBLE) + || !(priv->capability & WLAN_CAPABILITY_SHORT_PREAMBLE)) { + lbs_deb_join("AdhocJoin: Long preamble\n"); + priv->preamble = CMD_TYPE_LONG_PREAMBLE; + } else { + lbs_deb_join("AdhocJoin: Short preamble\n"); + priv->preamble = CMD_TYPE_SHORT_PREAMBLE; + } + + lbs_set_radio_control(priv); + + lbs_deb_join("AdhocJoin: channel = %d\n", assoc_req->channel); + lbs_deb_join("AdhocJoin: band = %c\n", assoc_req->band); + + priv->adhoccreate = 0; + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_AD_HOC_JOIN, + 0, CMD_OPTION_WAITFORRSP, + OID_802_11_SSID, assoc_req); + +out: + return ret; +} + +int lbs_stop_adhoc_network(struct lbs_private *priv) +{ + return lbs_prepare_and_send_command(priv, CMD_802_11_AD_HOC_STOP, + 0, CMD_OPTION_WAITFORRSP, 0, NULL); +} + +/** + * @brief Send Deauthentication Request + * + * @param priv A pointer to struct lbs_private structure + * @return 0--success, -1--fail + */ +int lbs_send_deauthentication(struct lbs_private *priv) +{ + return lbs_prepare_and_send_command(priv, CMD_802_11_DEAUTHENTICATE, + 0, CMD_OPTION_WAITFORRSP, 0, NULL); +} + +/** + * @brief This function prepares command of authenticate. + * + * @param priv A pointer to struct lbs_private structure + * @param cmd A pointer to cmd_ds_command structure + * @param pdata_buf Void cast of pointer to a BSSID to authenticate with + * + * @return 0 or -1 + */ +int lbs_cmd_80211_authenticate(struct lbs_private *priv, + struct cmd_ds_command *cmd, + void *pdata_buf) +{ + struct cmd_ds_802_11_authenticate *pauthenticate = &cmd->params.auth; + int ret = -1; + u8 *bssid = pdata_buf; + DECLARE_MAC_BUF(mac); + + lbs_deb_enter(LBS_DEB_JOIN); + + cmd->command = cpu_to_le16(CMD_802_11_AUTHENTICATE); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_authenticate) + + S_DS_GEN); + + /* translate auth mode to 802.11 defined wire value */ + switch (priv->secinfo.auth_mode) { + case IW_AUTH_ALG_OPEN_SYSTEM: + pauthenticate->authtype = 0x00; + break; + case IW_AUTH_ALG_SHARED_KEY: + pauthenticate->authtype = 0x01; + break; + case IW_AUTH_ALG_LEAP: + pauthenticate->authtype = 0x80; + break; + default: + lbs_deb_join("AUTH_CMD: invalid auth alg 0x%X\n", + priv->secinfo.auth_mode); + goto out; + } + + memcpy(pauthenticate->macaddr, bssid, ETH_ALEN); + + lbs_deb_join("AUTH_CMD: BSSID %s, auth 0x%x\n", + print_mac(mac, bssid), pauthenticate->authtype); + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_JOIN, "ret %d", ret); + return ret; +} + +int lbs_cmd_80211_deauthenticate(struct lbs_private *priv, + struct cmd_ds_command *cmd) +{ + struct cmd_ds_802_11_deauthenticate *dauth = &cmd->params.deauth; + + lbs_deb_enter(LBS_DEB_JOIN); + + cmd->command = cpu_to_le16(CMD_802_11_DEAUTHENTICATE); + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_deauthenticate) + + S_DS_GEN); + + /* set AP MAC address */ + memmove(dauth->macaddr, priv->curbssparams.bssid, ETH_ALEN); + + /* Reason code 3 = Station is leaving */ +#define REASON_CODE_STA_LEAVING 3 + dauth->reasoncode = cpu_to_le16(REASON_CODE_STA_LEAVING); + + lbs_deb_leave(LBS_DEB_JOIN); + return 0; +} + +int lbs_cmd_80211_associate(struct lbs_private *priv, + struct cmd_ds_command *cmd, void *pdata_buf) +{ + struct cmd_ds_802_11_associate *passo = &cmd->params.associate; + int ret = 0; + struct assoc_request * assoc_req = pdata_buf; + struct bss_descriptor * bss = &assoc_req->bss; + u8 *pos; + u16 tmpcap, tmplen; + struct mrvlietypes_ssidparamset *ssid; + struct mrvlietypes_phyparamset *phy; + struct mrvlietypes_ssparamset *ss; + struct mrvlietypes_ratesparamset *rates; + struct mrvlietypes_rsnparamset *rsn; + + lbs_deb_enter(LBS_DEB_ASSOC); + + pos = (u8 *) passo; + + if (!priv) { + ret = -1; + goto done; + } + + cmd->command = cpu_to_le16(CMD_802_11_ASSOCIATE); + + memcpy(passo->peerstaaddr, bss->bssid, sizeof(passo->peerstaaddr)); + pos += sizeof(passo->peerstaaddr); + + /* set the listen interval */ + passo->listeninterval = cpu_to_le16(MRVDRV_DEFAULT_LISTEN_INTERVAL); + + pos += sizeof(passo->capability); + pos += sizeof(passo->listeninterval); + pos += sizeof(passo->bcnperiod); + pos += sizeof(passo->dtimperiod); + + ssid = (struct mrvlietypes_ssidparamset *) pos; + ssid->header.type = cpu_to_le16(TLV_TYPE_SSID); + tmplen = bss->ssid_len; + ssid->header.len = cpu_to_le16(tmplen); + memcpy(ssid->ssid, bss->ssid, tmplen); + pos += sizeof(ssid->header) + tmplen; + + phy = (struct mrvlietypes_phyparamset *) pos; + phy->header.type = cpu_to_le16(TLV_TYPE_PHY_DS); + tmplen = sizeof(phy->fh_ds.dsparamset); + phy->header.len = cpu_to_le16(tmplen); + memcpy(&phy->fh_ds.dsparamset, + &bss->phyparamset.dsparamset.currentchan, + tmplen); + pos += sizeof(phy->header) + tmplen; + + ss = (struct mrvlietypes_ssparamset *) pos; + ss->header.type = cpu_to_le16(TLV_TYPE_CF); + tmplen = sizeof(ss->cf_ibss.cfparamset); + ss->header.len = cpu_to_le16(tmplen); + pos += sizeof(ss->header) + tmplen; + + rates = (struct mrvlietypes_ratesparamset *) pos; + rates->header.type = cpu_to_le16(TLV_TYPE_RATES); + memcpy(&rates->rates, &bss->rates, MAX_RATES); + tmplen = MAX_RATES; + if (get_common_rates(priv, rates->rates, &tmplen)) { + ret = -1; + goto done; + } + pos += sizeof(rates->header) + tmplen; + rates->header.len = cpu_to_le16(tmplen); + lbs_deb_assoc("ASSOC_CMD: num rates %u\n", tmplen); + + /* Copy the infra. association rates into Current BSS state structure */ + memset(&priv->curbssparams.rates, 0, sizeof(priv->curbssparams.rates)); + memcpy(&priv->curbssparams.rates, &rates->rates, tmplen); + + /* Set MSB on basic rates as the firmware requires, but _after_ + * copying to current bss rates. + */ + lbs_set_basic_rate_flags(rates->rates, tmplen); + + if (assoc_req->secinfo.WPAenabled || assoc_req->secinfo.WPA2enabled) { + rsn = (struct mrvlietypes_rsnparamset *) pos; + /* WPA_IE or WPA2_IE */ + rsn->header.type = cpu_to_le16((u16) assoc_req->wpa_ie[0]); + tmplen = (u16) assoc_req->wpa_ie[1]; + rsn->header.len = cpu_to_le16(tmplen); + memcpy(rsn->rsnie, &assoc_req->wpa_ie[2], tmplen); + lbs_deb_hex(LBS_DEB_JOIN, "ASSOC_CMD: RSN IE", (u8 *) rsn, + sizeof(rsn->header) + tmplen); + pos += sizeof(rsn->header) + tmplen; + } + + /* update curbssparams */ + priv->curbssparams.channel = bss->phyparamset.dsparamset.currentchan; + + if (lbs_parse_dnld_countryinfo_11d(priv, bss)) { + ret = -1; + goto done; + } + + cmd->size = cpu_to_le16((u16) (pos - (u8 *) passo) + S_DS_GEN); + + /* set the capability info */ + tmpcap = (bss->capability & CAPINFO_MASK); + if (bss->mode == IW_MODE_INFRA) + tmpcap |= WLAN_CAPABILITY_ESS; + passo->capability = cpu_to_le16(tmpcap); + lbs_deb_assoc("ASSOC_CMD: capability 0x%04x\n", tmpcap); + +done: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + +int lbs_cmd_80211_ad_hoc_start(struct lbs_private *priv, + struct cmd_ds_command *cmd, void *pdata_buf) +{ + struct cmd_ds_802_11_ad_hoc_start *adhs = &cmd->params.ads; + int ret = 0; + int cmdappendsize = 0; + struct assoc_request * assoc_req = pdata_buf; + u16 tmpcap = 0; + size_t ratesize = 0; + + lbs_deb_enter(LBS_DEB_JOIN); + + if (!priv) { + ret = -1; + goto done; + } + + cmd->command = cpu_to_le16(CMD_802_11_AD_HOC_START); + + /* + * Fill in the parameters for 2 data structures: + * 1. cmd_ds_802_11_ad_hoc_start command + * 2. priv->scantable[i] + * + * Driver will fill up SSID, bsstype,IBSS param, Physical Param, + * probe delay, and cap info. + * + * Firmware will fill up beacon period, DTIM, Basic rates + * and operational rates. + */ + + memset(adhs->ssid, 0, IW_ESSID_MAX_SIZE); + memcpy(adhs->ssid, assoc_req->ssid, assoc_req->ssid_len); + + lbs_deb_join("ADHOC_S_CMD: SSID '%s', ssid length %u\n", + escape_essid(assoc_req->ssid, assoc_req->ssid_len), + assoc_req->ssid_len); + + /* set the BSS type */ + adhs->bsstype = CMD_BSS_TYPE_IBSS; + priv->mode = IW_MODE_ADHOC; + if (priv->beacon_period == 0) + priv->beacon_period = MRVDRV_BEACON_INTERVAL; + adhs->beaconperiod = cpu_to_le16(priv->beacon_period); + + /* set Physical param set */ +#define DS_PARA_IE_ID 3 +#define DS_PARA_IE_LEN 1 + + adhs->phyparamset.dsparamset.elementid = DS_PARA_IE_ID; + adhs->phyparamset.dsparamset.len = DS_PARA_IE_LEN; + + WARN_ON(!assoc_req->channel); + + lbs_deb_join("ADHOC_S_CMD: Creating ADHOC on channel %d\n", + assoc_req->channel); + + adhs->phyparamset.dsparamset.currentchan = assoc_req->channel; + + /* set IBSS param set */ +#define IBSS_PARA_IE_ID 6 +#define IBSS_PARA_IE_LEN 2 + + adhs->ssparamset.ibssparamset.elementid = IBSS_PARA_IE_ID; + adhs->ssparamset.ibssparamset.len = IBSS_PARA_IE_LEN; + adhs->ssparamset.ibssparamset.atimwindow = 0; + + /* set capability info */ + tmpcap = WLAN_CAPABILITY_IBSS; + if (assoc_req->secinfo.wep_enabled) { + lbs_deb_join("ADHOC_S_CMD: WEP enabled, setting privacy on\n"); + tmpcap |= WLAN_CAPABILITY_PRIVACY; + } else { + lbs_deb_join("ADHOC_S_CMD: WEP disabled, setting privacy off\n"); + } + adhs->capability = cpu_to_le16(tmpcap); + + /* probedelay */ + adhs->probedelay = cpu_to_le16(CMD_SCAN_PROBE_DELAY_TIME); + + memset(adhs->rates, 0, sizeof(adhs->rates)); + ratesize = min(sizeof(adhs->rates), sizeof(lbs_bg_rates)); + memcpy(adhs->rates, lbs_bg_rates, ratesize); + + /* Copy the ad-hoc creating rates into Current BSS state structure */ + memset(&priv->curbssparams.rates, 0, sizeof(priv->curbssparams.rates)); + memcpy(&priv->curbssparams.rates, &adhs->rates, ratesize); + + /* Set MSB on basic rates as the firmware requires, but _after_ + * copying to current bss rates. + */ + lbs_set_basic_rate_flags(adhs->rates, ratesize); + + lbs_deb_join("ADHOC_S_CMD: rates=%02x %02x %02x %02x \n", + adhs->rates[0], adhs->rates[1], adhs->rates[2], adhs->rates[3]); + + lbs_deb_join("ADHOC_S_CMD: AD HOC Start command is ready\n"); + + if (lbs_create_dnld_countryinfo_11d(priv)) { + lbs_deb_join("ADHOC_S_CMD: dnld_countryinfo_11d failed\n"); + ret = -1; + goto done; + } + + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_ad_hoc_start) + + S_DS_GEN + cmdappendsize); + + ret = 0; +done: + lbs_deb_leave_args(LBS_DEB_JOIN, "ret %d", ret); + return ret; +} + +int lbs_cmd_80211_ad_hoc_stop(struct lbs_private *priv, + struct cmd_ds_command *cmd) +{ + cmd->command = cpu_to_le16(CMD_802_11_AD_HOC_STOP); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +int lbs_cmd_80211_ad_hoc_join(struct lbs_private *priv, + struct cmd_ds_command *cmd, void *pdata_buf) +{ + struct cmd_ds_802_11_ad_hoc_join *join_cmd = &cmd->params.adj; + struct assoc_request * assoc_req = pdata_buf; + struct bss_descriptor *bss = &assoc_req->bss; + int cmdappendsize = 0; + int ret = 0; + u16 ratesize = 0; + DECLARE_MAC_BUF(mac); + + lbs_deb_enter(LBS_DEB_JOIN); + + cmd->command = cpu_to_le16(CMD_802_11_AD_HOC_JOIN); + + join_cmd->bss.type = CMD_BSS_TYPE_IBSS; + join_cmd->bss.beaconperiod = cpu_to_le16(bss->beaconperiod); + + memcpy(&join_cmd->bss.bssid, &bss->bssid, ETH_ALEN); + memcpy(&join_cmd->bss.ssid, &bss->ssid, bss->ssid_len); + + memcpy(&join_cmd->bss.phyparamset, &bss->phyparamset, + sizeof(union ieeetypes_phyparamset)); + + memcpy(&join_cmd->bss.ssparamset, &bss->ssparamset, + sizeof(union IEEEtypes_ssparamset)); + + join_cmd->bss.capability = cpu_to_le16(bss->capability & CAPINFO_MASK); + lbs_deb_join("ADHOC_J_CMD: tmpcap=%4X CAPINFO_MASK=%4X\n", + bss->capability, CAPINFO_MASK); + + /* information on BSSID descriptor passed to FW */ + lbs_deb_join( + "ADHOC_J_CMD: BSSID = %s, SSID = '%s'\n", + print_mac(mac, join_cmd->bss.bssid), + join_cmd->bss.ssid); + + /* failtimeout */ + join_cmd->failtimeout = cpu_to_le16(MRVDRV_ASSOCIATION_TIME_OUT); + + /* probedelay */ + join_cmd->probedelay = cpu_to_le16(CMD_SCAN_PROBE_DELAY_TIME); + + priv->curbssparams.channel = bss->channel; + + /* Copy Data rates from the rates recorded in scan response */ + memset(join_cmd->bss.rates, 0, sizeof(join_cmd->bss.rates)); + ratesize = min_t(u16, sizeof(join_cmd->bss.rates), MAX_RATES); + memcpy(join_cmd->bss.rates, bss->rates, ratesize); + if (get_common_rates(priv, join_cmd->bss.rates, &ratesize)) { + lbs_deb_join("ADHOC_J_CMD: get_common_rates returns error.\n"); + ret = -1; + goto done; + } + + /* Copy the ad-hoc creating rates into Current BSS state structure */ + memset(&priv->curbssparams.rates, 0, sizeof(priv->curbssparams.rates)); + memcpy(&priv->curbssparams.rates, join_cmd->bss.rates, ratesize); + + /* Set MSB on basic rates as the firmware requires, but _after_ + * copying to current bss rates. + */ + lbs_set_basic_rate_flags(join_cmd->bss.rates, ratesize); + + join_cmd->bss.ssparamset.ibssparamset.atimwindow = + cpu_to_le16(bss->atimwindow); + + if (assoc_req->secinfo.wep_enabled) { + u16 tmp = le16_to_cpu(join_cmd->bss.capability); + tmp |= WLAN_CAPABILITY_PRIVACY; + join_cmd->bss.capability = cpu_to_le16(tmp); + } + + if (priv->psmode == LBS802_11POWERMODEMAX_PSP) { + /* wake up first */ + __le32 Localpsmode; + + Localpsmode = cpu_to_le32(LBS802_11POWERMODECAM); + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_PS_MODE, + CMD_ACT_SET, + 0, 0, &Localpsmode); + + if (ret) { + ret = -1; + goto done; + } + } + + if (lbs_parse_dnld_countryinfo_11d(priv, bss)) { + ret = -1; + goto done; + } + + cmd->size = cpu_to_le16(sizeof(struct cmd_ds_802_11_ad_hoc_join) + + S_DS_GEN + cmdappendsize); + +done: + lbs_deb_leave_args(LBS_DEB_JOIN, "ret %d", ret); + return ret; +} + +int lbs_ret_80211_associate(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + int ret = 0; + union iwreq_data wrqu; + struct ieeetypes_assocrsp *passocrsp; + struct bss_descriptor * bss; + u16 status_code; + + lbs_deb_enter(LBS_DEB_ASSOC); + + if (!priv->in_progress_assoc_req) { + lbs_deb_assoc("ASSOC_RESP: no in-progress assoc request\n"); + ret = -1; + goto done; + } + bss = &priv->in_progress_assoc_req->bss; + + passocrsp = (struct ieeetypes_assocrsp *) & resp->params; + + /* + * Older FW versions map the IEEE 802.11 Status Code in the association + * response to the following values returned in passocrsp->statuscode: + * + * IEEE Status Code Marvell Status Code + * 0 -> 0x0000 ASSOC_RESULT_SUCCESS + * 13 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED + * 14 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED + * 15 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED + * 16 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED + * others -> 0x0003 ASSOC_RESULT_REFUSED + * + * Other response codes: + * 0x0001 -> ASSOC_RESULT_INVALID_PARAMETERS (unused) + * 0x0002 -> ASSOC_RESULT_TIMEOUT (internal timer expired waiting for + * association response from the AP) + */ + + status_code = le16_to_cpu(passocrsp->statuscode); + switch (status_code) { + case 0x00: + break; + case 0x01: + lbs_deb_assoc("ASSOC_RESP: invalid parameters\n"); + break; + case 0x02: + lbs_deb_assoc("ASSOC_RESP: internal timer " + "expired while waiting for the AP\n"); + break; + case 0x03: + lbs_deb_assoc("ASSOC_RESP: association " + "refused by AP\n"); + break; + case 0x04: + lbs_deb_assoc("ASSOC_RESP: authentication " + "refused by AP\n"); + break; + default: + lbs_deb_assoc("ASSOC_RESP: failure reason 0x%02x " + " unknown\n", status_code); + break; + } + + if (status_code) { + lbs_mac_event_disconnected(priv); + ret = -1; + goto done; + } + + lbs_deb_hex(LBS_DEB_ASSOC, "ASSOC_RESP", (void *)&resp->params, + le16_to_cpu(resp->size) - S_DS_GEN); + + /* Send a Media Connected event, according to the Spec */ + priv->connect_status = LBS_CONNECTED; + + /* Update current SSID and BSSID */ + memcpy(&priv->curbssparams.ssid, &bss->ssid, IW_ESSID_MAX_SIZE); + priv->curbssparams.ssid_len = bss->ssid_len; + memcpy(priv->curbssparams.bssid, bss->bssid, ETH_ALEN); + + lbs_deb_assoc("ASSOC_RESP: currentpacketfilter is 0x%x\n", + priv->currentpacketfilter); + + priv->SNR[TYPE_RXPD][TYPE_AVG] = 0; + priv->NF[TYPE_RXPD][TYPE_AVG] = 0; + + memset(priv->rawSNR, 0x00, sizeof(priv->rawSNR)); + memset(priv->rawNF, 0x00, sizeof(priv->rawNF)); + priv->nextSNRNF = 0; + priv->numSNRNF = 0; + + netif_carrier_on(priv->dev); + netif_wake_queue(priv->dev); + + + memcpy(wrqu.ap_addr.sa_data, priv->curbssparams.bssid, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL); + +done: + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + +int lbs_ret_80211_disassociate(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + lbs_deb_enter(LBS_DEB_JOIN); + + lbs_mac_event_disconnected(priv); + + lbs_deb_leave(LBS_DEB_JOIN); + return 0; +} + +int lbs_ret_80211_ad_hoc_start(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + int ret = 0; + u16 command = le16_to_cpu(resp->command); + u16 result = le16_to_cpu(resp->result); + struct cmd_ds_802_11_ad_hoc_result *padhocresult; + union iwreq_data wrqu; + struct bss_descriptor *bss; + DECLARE_MAC_BUF(mac); + + lbs_deb_enter(LBS_DEB_JOIN); + + padhocresult = &resp->params.result; + + lbs_deb_join("ADHOC_RESP: size = %d\n", le16_to_cpu(resp->size)); + lbs_deb_join("ADHOC_RESP: command = %x\n", command); + lbs_deb_join("ADHOC_RESP: result = %x\n", result); + + if (!priv->in_progress_assoc_req) { + lbs_deb_join("ADHOC_RESP: no in-progress association request\n"); + ret = -1; + goto done; + } + bss = &priv->in_progress_assoc_req->bss; + + /* + * Join result code 0 --> SUCCESS + */ + if (result) { + lbs_deb_join("ADHOC_RESP: failed\n"); + if (priv->connect_status == LBS_CONNECTED) { + lbs_mac_event_disconnected(priv); + } + ret = -1; + goto done; + } + + /* + * Now the join cmd should be successful + * If BSSID has changed use SSID to compare instead of BSSID + */ + lbs_deb_join("ADHOC_RESP: associated to '%s'\n", + escape_essid(bss->ssid, bss->ssid_len)); + + /* Send a Media Connected event, according to the Spec */ + priv->connect_status = LBS_CONNECTED; + + if (command == CMD_RET(CMD_802_11_AD_HOC_START)) { + /* Update the created network descriptor with the new BSSID */ + memcpy(bss->bssid, padhocresult->bssid, ETH_ALEN); + } + + /* Set the BSSID from the joined/started descriptor */ + memcpy(&priv->curbssparams.bssid, bss->bssid, ETH_ALEN); + + /* Set the new SSID to current SSID */ + memcpy(&priv->curbssparams.ssid, &bss->ssid, IW_ESSID_MAX_SIZE); + priv->curbssparams.ssid_len = bss->ssid_len; + + netif_carrier_on(priv->dev); + netif_wake_queue(priv->dev); + + memset(&wrqu, 0, sizeof(wrqu)); + memcpy(wrqu.ap_addr.sa_data, priv->curbssparams.bssid, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL); + + lbs_deb_join("ADHOC_RESP: - Joined/Started Ad Hoc\n"); + lbs_deb_join("ADHOC_RESP: channel = %d\n", priv->curbssparams.channel); + lbs_deb_join("ADHOC_RESP: BSSID = %s\n", + print_mac(mac, padhocresult->bssid)); + +done: + lbs_deb_leave_args(LBS_DEB_JOIN, "ret %d", ret); + return ret; +} + +int lbs_ret_80211_ad_hoc_stop(struct lbs_private *priv, + struct cmd_ds_command *resp) +{ + lbs_deb_enter(LBS_DEB_JOIN); + + lbs_mac_event_disconnected(priv); + + lbs_deb_leave(LBS_DEB_JOIN); + return 0; +} diff --git a/package/libertas/src/join.h b/package/libertas/src/join.h new file mode 100644 index 0000000000..c617d071f7 --- /dev/null +++ b/package/libertas/src/join.h @@ -0,0 +1,53 @@ +/** + * Interface for the wlan infrastructure and adhoc join routines + * + * Driver interface functions and type declarations for the join module + * implemented in join.c. Process all start/join requests for + * both adhoc and infrastructure networks + */ +#ifndef _LBS_JOIN_H +#define _LBS_JOIN_H + +#include "defs.h" +#include "dev.h" + +struct cmd_ds_command; +int lbs_cmd_80211_authenticate(struct lbs_private *priv, + struct cmd_ds_command *cmd, + void *pdata_buf); +int lbs_cmd_80211_ad_hoc_join(struct lbs_private *priv, + struct cmd_ds_command *cmd, + void *pdata_buf); +int lbs_cmd_80211_ad_hoc_stop(struct lbs_private *priv, + struct cmd_ds_command *cmd); +int lbs_cmd_80211_ad_hoc_start(struct lbs_private *priv, + struct cmd_ds_command *cmd, + void *pdata_buf); +int lbs_cmd_80211_deauthenticate(struct lbs_private *priv, + struct cmd_ds_command *cmd); +int lbs_cmd_80211_associate(struct lbs_private *priv, + struct cmd_ds_command *cmd, + void *pdata_buf); + +int lbs_ret_80211_ad_hoc_start(struct lbs_private *priv, + struct cmd_ds_command *resp); +int lbs_ret_80211_ad_hoc_stop(struct lbs_private *priv, + struct cmd_ds_command *resp); +int lbs_ret_80211_disassociate(struct lbs_private *priv, + struct cmd_ds_command *resp); +int lbs_ret_80211_associate(struct lbs_private *priv, + struct cmd_ds_command *resp); + +int lbs_start_adhoc_network(struct lbs_private *priv, + struct assoc_request * assoc_req); +int lbs_join_adhoc_network(struct lbs_private *priv, + struct assoc_request * assoc_req); +int lbs_stop_adhoc_network(struct lbs_private *priv); + +int lbs_send_deauthentication(struct lbs_private *priv); + +int lbs_associate(struct lbs_private *priv, struct assoc_request *assoc_req); + +void lbs_unset_basic_rate_flags(u8 *rates, size_t len); + +#endif diff --git a/package/libertas/src/main.c b/package/libertas/src/main.c new file mode 100644 index 0000000000..406d874075 --- /dev/null +++ b/package/libertas/src/main.c @@ -0,0 +1,1474 @@ +/** + * This file contains the major functions in WLAN + * driver. It includes init, exit, open, close and main + * thread etc.. + */ + +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/freezer.h> +#include <linux/etherdevice.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/kthread.h> + +#include <net/iw_handler.h> +#include <net/ieee80211.h> + +#include "host.h" +#include "decl.h" +#include "dev.h" +#include "wext.h" +#include "debugfs.h" +#include "assoc.h" +#include "join.h" +#include "cmd.h" + +#define DRIVER_RELEASE_VERSION "323.p0" +const char lbs_driver_version[] = "COMM-USB8388-" DRIVER_RELEASE_VERSION +#ifdef DEBUG + "-dbg" +#endif + ""; + + +/* Module parameters */ +unsigned int lbs_debug; +EXPORT_SYMBOL_GPL(lbs_debug); +module_param_named(libertas_debug, lbs_debug, int, 0644); + + +#define LBS_TX_PWR_DEFAULT 20 /*100mW */ +#define LBS_TX_PWR_US_DEFAULT 20 /*100mW */ +#define LBS_TX_PWR_JP_DEFAULT 16 /*50mW */ +#define LBS_TX_PWR_FR_DEFAULT 20 /*100mW */ +#define LBS_TX_PWR_EMEA_DEFAULT 20 /*100mW */ + +/* Format { channel, frequency (MHz), maxtxpower } */ +/* band: 'B/G', region: USA FCC/Canada IC */ +static struct chan_freq_power channel_freq_power_US_BG[] = { + {1, 2412, LBS_TX_PWR_US_DEFAULT}, + {2, 2417, LBS_TX_PWR_US_DEFAULT}, + {3, 2422, LBS_TX_PWR_US_DEFAULT}, + {4, 2427, LBS_TX_PWR_US_DEFAULT}, + {5, 2432, LBS_TX_PWR_US_DEFAULT}, + {6, 2437, LBS_TX_PWR_US_DEFAULT}, + {7, 2442, LBS_TX_PWR_US_DEFAULT}, + {8, 2447, LBS_TX_PWR_US_DEFAULT}, + {9, 2452, LBS_TX_PWR_US_DEFAULT}, + {10, 2457, LBS_TX_PWR_US_DEFAULT}, + {11, 2462, LBS_TX_PWR_US_DEFAULT} +}; + +/* band: 'B/G', region: Europe ETSI */ +static struct chan_freq_power channel_freq_power_EU_BG[] = { + {1, 2412, LBS_TX_PWR_EMEA_DEFAULT}, + {2, 2417, LBS_TX_PWR_EMEA_DEFAULT}, + {3, 2422, LBS_TX_PWR_EMEA_DEFAULT}, + {4, 2427, LBS_TX_PWR_EMEA_DEFAULT}, + {5, 2432, LBS_TX_PWR_EMEA_DEFAULT}, + {6, 2437, LBS_TX_PWR_EMEA_DEFAULT}, + {7, 2442, LBS_TX_PWR_EMEA_DEFAULT}, + {8, 2447, LBS_TX_PWR_EMEA_DEFAULT}, + {9, 2452, LBS_TX_PWR_EMEA_DEFAULT}, + {10, 2457, LBS_TX_PWR_EMEA_DEFAULT}, + {11, 2462, LBS_TX_PWR_EMEA_DEFAULT}, + {12, 2467, LBS_TX_PWR_EMEA_DEFAULT}, + {13, 2472, LBS_TX_PWR_EMEA_DEFAULT} +}; + +/* band: 'B/G', region: Spain */ +static struct chan_freq_power channel_freq_power_SPN_BG[] = { + {10, 2457, LBS_TX_PWR_DEFAULT}, + {11, 2462, LBS_TX_PWR_DEFAULT} +}; + +/* band: 'B/G', region: France */ +static struct chan_freq_power channel_freq_power_FR_BG[] = { + {10, 2457, LBS_TX_PWR_FR_DEFAULT}, + {11, 2462, LBS_TX_PWR_FR_DEFAULT}, + {12, 2467, LBS_TX_PWR_FR_DEFAULT}, + {13, 2472, LBS_TX_PWR_FR_DEFAULT} +}; + +/* band: 'B/G', region: Japan */ +static struct chan_freq_power channel_freq_power_JPN_BG[] = { + {1, 2412, LBS_TX_PWR_JP_DEFAULT}, + {2, 2417, LBS_TX_PWR_JP_DEFAULT}, + {3, 2422, LBS_TX_PWR_JP_DEFAULT}, + {4, 2427, LBS_TX_PWR_JP_DEFAULT}, + {5, 2432, LBS_TX_PWR_JP_DEFAULT}, + {6, 2437, LBS_TX_PWR_JP_DEFAULT}, + {7, 2442, LBS_TX_PWR_JP_DEFAULT}, + {8, 2447, LBS_TX_PWR_JP_DEFAULT}, + {9, 2452, LBS_TX_PWR_JP_DEFAULT}, + {10, 2457, LBS_TX_PWR_JP_DEFAULT}, + {11, 2462, LBS_TX_PWR_JP_DEFAULT}, + {12, 2467, LBS_TX_PWR_JP_DEFAULT}, + {13, 2472, LBS_TX_PWR_JP_DEFAULT}, + {14, 2484, LBS_TX_PWR_JP_DEFAULT} +}; + +/** + * the structure for channel, frequency and power + */ +struct region_cfp_table { + u8 region; + struct chan_freq_power *cfp_BG; + int cfp_no_BG; +}; + +/** + * the structure for the mapping between region and CFP + */ +static struct region_cfp_table region_cfp_table[] = { + {0x10, /*US FCC */ + channel_freq_power_US_BG, + ARRAY_SIZE(channel_freq_power_US_BG), + } + , + {0x20, /*CANADA IC */ + channel_freq_power_US_BG, + ARRAY_SIZE(channel_freq_power_US_BG), + } + , + {0x30, /*EU*/ channel_freq_power_EU_BG, + ARRAY_SIZE(channel_freq_power_EU_BG), + } + , + {0x31, /*SPAIN*/ channel_freq_power_SPN_BG, + ARRAY_SIZE(channel_freq_power_SPN_BG), + } + , + {0x32, /*FRANCE*/ channel_freq_power_FR_BG, + ARRAY_SIZE(channel_freq_power_FR_BG), + } + , + {0x40, /*JAPAN*/ channel_freq_power_JPN_BG, + ARRAY_SIZE(channel_freq_power_JPN_BG), + } + , +/*Add new region here */ +}; + +/** + * the table to keep region code + */ +u16 lbs_region_code_to_index[MRVDRV_MAX_REGION_CODE] = + { 0x10, 0x20, 0x30, 0x31, 0x32, 0x40 }; + +/** + * 802.11b/g supported bitrates (in 500Kb/s units) + */ +u8 lbs_bg_rates[MAX_RATES] = + { 0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c, +0x00, 0x00 }; + +/** + * FW rate table. FW refers to rates by their index in this table, not by the + * rate value itself. Values of 0x00 are + * reserved positions. + */ +static u8 fw_data_rates[MAX_RATES] = + { 0x02, 0x04, 0x0B, 0x16, 0x00, 0x0C, 0x12, + 0x18, 0x24, 0x30, 0x48, 0x60, 0x6C, 0x00 +}; + +/** + * @brief use index to get the data rate + * + * @param idx The index of data rate + * @return data rate or 0 + */ +u32 lbs_fw_index_to_data_rate(u8 idx) +{ + if (idx >= sizeof(fw_data_rates)) + idx = 0; + return fw_data_rates[idx]; +} + +/** + * @brief use rate to get the index + * + * @param rate data rate + * @return index or 0 + */ +u8 lbs_data_rate_to_fw_index(u32 rate) +{ + u8 i; + + if (!rate) + return 0; + + for (i = 0; i < sizeof(fw_data_rates); i++) { + if (rate == fw_data_rates[i]) + return i; + } + return 0; +} + +/** + * Attributes exported through sysfs + */ + +/** + * @brief Get function for sysfs attribute anycast_mask + */ +static ssize_t lbs_anycast_get(struct device *dev, + struct device_attribute *attr, char * buf) +{ + struct lbs_private *priv = to_net_dev(dev)->priv; + struct cmd_ds_mesh_access mesh_access; + int ret; + + memset(&mesh_access, 0, sizeof(mesh_access)); + + ret = lbs_mesh_access(priv, CMD_ACT_MESH_GET_ANYCAST, &mesh_access); + if (ret) + return ret; + + return snprintf(buf, 12, "0x%X\n", le32_to_cpu(mesh_access.data[0])); +} + +/** + * @brief Set function for sysfs attribute anycast_mask + */ +static ssize_t lbs_anycast_set(struct device *dev, + struct device_attribute *attr, const char * buf, size_t count) +{ + struct lbs_private *priv = to_net_dev(dev)->priv; + struct cmd_ds_mesh_access mesh_access; + uint32_t datum; + int ret; + + memset(&mesh_access, 0, sizeof(mesh_access)); + sscanf(buf, "%x", &datum); + mesh_access.data[0] = cpu_to_le32(datum); + + ret = lbs_mesh_access(priv, CMD_ACT_MESH_SET_ANYCAST, &mesh_access); + if (ret) + return ret; + + return strlen(buf); +} + +static int lbs_add_rtap(struct lbs_private *priv); +static void lbs_remove_rtap(struct lbs_private *priv); +static int lbs_add_mesh(struct lbs_private *priv); +static void lbs_remove_mesh(struct lbs_private *priv); + + +/** + * Get function for sysfs attribute rtap + */ +static ssize_t lbs_rtap_get(struct device *dev, + struct device_attribute *attr, char * buf) +{ + struct lbs_private *priv = to_net_dev(dev)->priv; + return snprintf(buf, 5, "0x%X\n", priv->monitormode); +} + +/** + * Set function for sysfs attribute rtap + */ +static ssize_t lbs_rtap_set(struct device *dev, + struct device_attribute *attr, const char * buf, size_t count) +{ + int monitor_mode; + struct lbs_private *priv = to_net_dev(dev)->priv; + + sscanf(buf, "%x", &monitor_mode); + if (monitor_mode != LBS_MONITOR_OFF) { + if(priv->monitormode == monitor_mode) + return strlen(buf); + if (priv->monitormode == LBS_MONITOR_OFF) { + if (priv->infra_open || priv->mesh_open) + return -EBUSY; + if (priv->mode == IW_MODE_INFRA) + lbs_send_deauthentication(priv); + else if (priv->mode == IW_MODE_ADHOC) + lbs_stop_adhoc_network(priv); + lbs_add_rtap(priv); + } + priv->monitormode = monitor_mode; + } + + else { + if (priv->monitormode == LBS_MONITOR_OFF) + return strlen(buf); + priv->monitormode = LBS_MONITOR_OFF; + lbs_remove_rtap(priv); + + if (priv->currenttxskb) { + dev_kfree_skb_any(priv->currenttxskb); + priv->currenttxskb = NULL; + } + + /* Wake queues, command thread, etc. */ + lbs_host_to_card_done(priv); + } + + lbs_prepare_and_send_command(priv, + CMD_802_11_MONITOR_MODE, CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, 0, &priv->monitormode); + return strlen(buf); +} + +/** + * lbs_rtap attribute to be exported per ethX interface + * through sysfs (/sys/class/net/ethX/lbs_rtap) + */ +static DEVICE_ATTR(lbs_rtap, 0644, lbs_rtap_get, lbs_rtap_set ); + +/** + * Get function for sysfs attribute mesh + */ +static ssize_t lbs_mesh_get(struct device *dev, + struct device_attribute *attr, char * buf) +{ + struct lbs_private *priv = to_net_dev(dev)->priv; + return snprintf(buf, 5, "0x%X\n", !!priv->mesh_dev); +} + +/** + * Set function for sysfs attribute mesh + */ +static ssize_t lbs_mesh_set(struct device *dev, + struct device_attribute *attr, const char * buf, size_t count) +{ + struct lbs_private *priv = to_net_dev(dev)->priv; + int enable; + int ret; + + sscanf(buf, "%x", &enable); + enable = !!enable; + if (enable == !!priv->mesh_dev) + return count; + + ret = lbs_mesh_config(priv, enable); + if (ret) + return ret; + + if (enable) + lbs_add_mesh(priv); + else + lbs_remove_mesh(priv); + + return count; +} + +/** + * lbs_mesh attribute to be exported per ethX interface + * through sysfs (/sys/class/net/ethX/lbs_mesh) + */ +static DEVICE_ATTR(lbs_mesh, 0644, lbs_mesh_get, lbs_mesh_set); + +/** + * anycast_mask attribute to be exported per mshX interface + * through sysfs (/sys/class/net/mshX/anycast_mask) + */ +static DEVICE_ATTR(anycast_mask, 0644, lbs_anycast_get, lbs_anycast_set); + +static struct attribute *lbs_mesh_sysfs_entries[] = { + &dev_attr_anycast_mask.attr, + NULL, +}; + +static struct attribute_group lbs_mesh_attr_group = { + .attrs = lbs_mesh_sysfs_entries, +}; + +/** + * @brief This function opens the ethX or mshX interface + * + * @param dev A pointer to net_device structure + * @return 0 or -EBUSY if monitor mode active + */ +static int lbs_dev_open(struct net_device *dev) +{ + struct lbs_private *priv = (struct lbs_private *) dev->priv ; + int ret = 0; + + spin_lock_irq(&priv->driver_lock); + + if (priv->monitormode != LBS_MONITOR_OFF) { + ret = -EBUSY; + goto out; + } + + if (dev == priv->mesh_dev) { + priv->mesh_open = 1; + priv->mesh_connect_status = LBS_CONNECTED; + netif_carrier_on(dev); + } else { + priv->infra_open = 1; + + if (priv->connect_status == LBS_CONNECTED) + netif_carrier_on(dev); + else + netif_carrier_off(dev); + } + + if (!priv->tx_pending_len) + netif_wake_queue(dev); + out: + + spin_unlock_irq(&priv->driver_lock); + return ret; +} + +/** + * @brief This function closes the mshX interface + * + * @param dev A pointer to net_device structure + * @return 0 + */ +static int lbs_mesh_stop(struct net_device *dev) +{ + struct lbs_private *priv = (struct lbs_private *) (dev->priv); + + spin_lock_irq(&priv->driver_lock); + + priv->mesh_open = 0; + priv->mesh_connect_status = LBS_DISCONNECTED; + + netif_stop_queue(dev); + netif_carrier_off(dev); + + spin_unlock_irq(&priv->driver_lock); + return 0; +} + +/** + * @brief This function closes the ethX interface + * + * @param dev A pointer to net_device structure + * @return 0 + */ +static int lbs_eth_stop(struct net_device *dev) +{ + struct lbs_private *priv = (struct lbs_private *) dev->priv; + + spin_lock_irq(&priv->driver_lock); + + priv->infra_open = 0; + + netif_stop_queue(dev); + + spin_unlock_irq(&priv->driver_lock); + return 0; +} + +static void lbs_tx_timeout(struct net_device *dev) +{ + struct lbs_private *priv = (struct lbs_private *) dev->priv; + + lbs_deb_enter(LBS_DEB_TX); + + lbs_pr_err("tx watch dog timeout\n"); + + dev->trans_start = jiffies; + + if (priv->currenttxskb) { + priv->eventcause = 0x01000000; + lbs_send_tx_feedback(priv); + } + /* XX: Shouldn't we also call into the hw-specific driver + to kick it somehow? */ + lbs_host_to_card_done(priv); + + lbs_deb_leave(LBS_DEB_TX); +} + +void lbs_host_to_card_done(struct lbs_private *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->driver_lock, flags); + + priv->dnld_sent = DNLD_RES_RECEIVED; + + /* Wake main thread if commands are pending */ + if (!priv->cur_cmd) + wake_up_interruptible(&priv->waitq); + + /* Don't wake netif queues if we're in monitor mode and + a TX packet is already pending, or if there are commands + queued to be sent. */ + if (!priv->currenttxskb && list_empty(&priv->cmdpendingq)) { + if (priv->dev && priv->connect_status == LBS_CONNECTED) + netif_wake_queue(priv->dev); + + if (priv->mesh_dev && priv->mesh_connect_status == LBS_CONNECTED) + netif_wake_queue(priv->mesh_dev); + } + spin_unlock_irqrestore(&priv->driver_lock, flags); +} +EXPORT_SYMBOL_GPL(lbs_host_to_card_done); + +/** + * @brief This function returns the network statistics + * + * @param dev A pointer to struct lbs_private structure + * @return A pointer to net_device_stats structure + */ +static struct net_device_stats *lbs_get_stats(struct net_device *dev) +{ + struct lbs_private *priv = (struct lbs_private *) dev->priv; + + return &priv->stats; +} + +static int lbs_set_mac_address(struct net_device *dev, void *addr) +{ + int ret = 0; + struct lbs_private *priv = (struct lbs_private *) dev->priv; + struct sockaddr *phwaddr = addr; + + lbs_deb_enter(LBS_DEB_NET); + + /* In case it was called from the mesh device */ + dev = priv->dev ; + + memset(priv->current_addr, 0, ETH_ALEN); + + /* dev->dev_addr is 8 bytes */ + lbs_deb_hex(LBS_DEB_NET, "dev->dev_addr", dev->dev_addr, ETH_ALEN); + + lbs_deb_hex(LBS_DEB_NET, "addr", phwaddr->sa_data, ETH_ALEN); + memcpy(priv->current_addr, phwaddr->sa_data, ETH_ALEN); + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_MAC_ADDRESS, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, 0, NULL); + + if (ret) { + lbs_deb_net("set MAC address failed\n"); + ret = -1; + goto done; + } + + lbs_deb_hex(LBS_DEB_NET, "priv->macaddr", priv->current_addr, ETH_ALEN); + memcpy(dev->dev_addr, priv->current_addr, ETH_ALEN); + if (priv->mesh_dev) + memcpy(priv->mesh_dev->dev_addr, priv->current_addr, ETH_ALEN); + +done: + lbs_deb_leave_args(LBS_DEB_NET, "ret %d", ret); + return ret; +} + +static int lbs_copy_multicast_address(struct lbs_private *priv, + struct net_device *dev) +{ + int i = 0; + struct dev_mc_list *mcptr = dev->mc_list; + + for (i = 0; i < dev->mc_count; i++) { + memcpy(&priv->multicastlist[i], mcptr->dmi_addr, ETH_ALEN); + mcptr = mcptr->next; + } + + return i; + +} + +static void lbs_set_multicast_list(struct net_device *dev) +{ + struct lbs_private *priv = dev->priv; + int oldpacketfilter; + DECLARE_MAC_BUF(mac); + + lbs_deb_enter(LBS_DEB_NET); + + oldpacketfilter = priv->currentpacketfilter; + + if (dev->flags & IFF_PROMISC) { + lbs_deb_net("enable promiscuous mode\n"); + priv->currentpacketfilter |= + CMD_ACT_MAC_PROMISCUOUS_ENABLE; + priv->currentpacketfilter &= + ~(CMD_ACT_MAC_ALL_MULTICAST_ENABLE | + CMD_ACT_MAC_MULTICAST_ENABLE); + } else { + /* Multicast */ + priv->currentpacketfilter &= + ~CMD_ACT_MAC_PROMISCUOUS_ENABLE; + + if (dev->flags & IFF_ALLMULTI || dev->mc_count > + MRVDRV_MAX_MULTICAST_LIST_SIZE) { + lbs_deb_net( "enabling all multicast\n"); + priv->currentpacketfilter |= + CMD_ACT_MAC_ALL_MULTICAST_ENABLE; + priv->currentpacketfilter &= + ~CMD_ACT_MAC_MULTICAST_ENABLE; + } else { + priv->currentpacketfilter &= + ~CMD_ACT_MAC_ALL_MULTICAST_ENABLE; + + if (!dev->mc_count) { + lbs_deb_net("no multicast addresses, " + "disabling multicast\n"); + priv->currentpacketfilter &= + ~CMD_ACT_MAC_MULTICAST_ENABLE; + } else { + int i; + + priv->currentpacketfilter |= + CMD_ACT_MAC_MULTICAST_ENABLE; + + priv->nr_of_multicastmacaddr = + lbs_copy_multicast_address(priv, dev); + + lbs_deb_net("multicast addresses: %d\n", + dev->mc_count); + + for (i = 0; i < dev->mc_count; i++) { + lbs_deb_net("Multicast address %d:%s\n", + i, print_mac(mac, + priv->multicastlist[i])); + } + /* send multicast addresses to firmware */ + lbs_prepare_and_send_command(priv, + CMD_MAC_MULTICAST_ADR, + CMD_ACT_SET, 0, 0, + NULL); + } + } + } + + if (priv->currentpacketfilter != oldpacketfilter) { + lbs_set_mac_packet_filter(priv); + } + + lbs_deb_leave(LBS_DEB_NET); +} + +/** + * @brief This function handles the major jobs in the LBS driver. + * It handles all events generated by firmware, RX data received + * from firmware and TX data sent from kernel. + * + * @param data A pointer to lbs_thread structure + * @return 0 + */ +static int lbs_thread(void *data) +{ + struct net_device *dev = data; + struct lbs_private *priv = dev->priv; + wait_queue_t wait; + u8 ireg = 0; + + lbs_deb_enter(LBS_DEB_THREAD); + + init_waitqueue_entry(&wait, current); + + set_freezable(); + + for (;;) { + int shouldsleep; + + lbs_deb_thread( "main-thread 111: intcounter=%d currenttxskb=%p dnld_sent=%d\n", + priv->intcounter, priv->currenttxskb, priv->dnld_sent); + + add_wait_queue(&priv->waitq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irq(&priv->driver_lock); + + if (priv->surpriseremoved) + shouldsleep = 0; /* Bye */ + else if (priv->psstate == PS_STATE_SLEEP) + shouldsleep = 1; /* Sleep mode. Nothing we can do till it wakes */ + else if (priv->intcounter) + shouldsleep = 0; /* Interrupt pending. Deal with it now */ + else if (!priv->fw_ready) + shouldsleep = 1; /* Firmware not ready. We're waiting for it */ + else if (priv->dnld_sent) + shouldsleep = 1; /* Something is en route to the device already */ + else if (priv->tx_pending_len > 0) + shouldsleep = 0; /* We've a packet to send */ + else if (priv->cur_cmd) + shouldsleep = 1; /* Can't send a command; one already running */ + else if (!list_empty(&priv->cmdpendingq)) + shouldsleep = 0; /* We have a command to send */ + else + shouldsleep = 1; /* No command */ + + if (shouldsleep) { + lbs_deb_thread("main-thread sleeping... Conn=%d IntC=%d PS_mode=%d PS_State=%d\n", + priv->connect_status, priv->intcounter, + priv->psmode, priv->psstate); + spin_unlock_irq(&priv->driver_lock); + schedule(); + } else + spin_unlock_irq(&priv->driver_lock); + + lbs_deb_thread("main-thread 222 (waking up): intcounter=%d currenttxskb=%p dnld_sent=%d\n", + priv->intcounter, priv->currenttxskb, priv->dnld_sent); + + set_current_state(TASK_RUNNING); + remove_wait_queue(&priv->waitq, &wait); + try_to_freeze(); + + lbs_deb_thread("main-thread 333: intcounter=%d currenttxskb=%p dnld_sent=%d\n", + priv->intcounter, priv->currenttxskb, priv->dnld_sent); + + if (kthread_should_stop() || priv->surpriseremoved) { + lbs_deb_thread("main-thread: break from main thread: surpriseremoved=0x%x\n", + priv->surpriseremoved); + break; + } + + + spin_lock_irq(&priv->driver_lock); + + if (priv->intcounter) { + u8 int_status; + + priv->intcounter = 0; + int_status = priv->hw_get_int_status(priv, &ireg); + + if (int_status) { + lbs_deb_thread("main-thread: reading HOST_INT_STATUS_REG failed\n"); + spin_unlock_irq(&priv->driver_lock); + continue; + } + priv->hisregcpy |= ireg; + } + + lbs_deb_thread("main-thread 444: intcounter=%d currenttxskb=%p dnld_sent=%d\n", + priv->intcounter, priv->currenttxskb, priv->dnld_sent); + + /* command response? */ + if (priv->hisregcpy & MRVDRV_CMD_UPLD_RDY) { + lbs_deb_thread("main-thread: cmd response ready\n"); + + priv->hisregcpy &= ~MRVDRV_CMD_UPLD_RDY; + spin_unlock_irq(&priv->driver_lock); + lbs_process_rx_command(priv); + spin_lock_irq(&priv->driver_lock); + } + + /* Any Card Event */ + if (priv->hisregcpy & MRVDRV_CARDEVENT) { + lbs_deb_thread("main-thread: Card Event Activity\n"); + + priv->hisregcpy &= ~MRVDRV_CARDEVENT; + + if (priv->hw_read_event_cause(priv)) { + lbs_pr_alert("main-thread: hw_read_event_cause failed\n"); + spin_unlock_irq(&priv->driver_lock); + continue; + } + spin_unlock_irq(&priv->driver_lock); + lbs_process_event(priv); + } else + spin_unlock_irq(&priv->driver_lock); + + if (!priv->fw_ready) + continue; + + /* Check if we need to confirm Sleep Request received previously */ + if (priv->psstate == PS_STATE_PRE_SLEEP && + !priv->dnld_sent && !priv->cur_cmd) { + if (priv->connect_status == LBS_CONNECTED) { + lbs_deb_thread("main_thread: PRE_SLEEP--intcounter=%d currenttxskb=%p dnld_sent=%d cur_cmd=%p, confirm now\n", + priv->intcounter, priv->currenttxskb, priv->dnld_sent, priv->cur_cmd); + + lbs_ps_confirm_sleep(priv, (u16) priv->psmode); + } else { + /* workaround for firmware sending + * deauth/linkloss event immediately + * after sleep request; remove this + * after firmware fixes it + */ + priv->psstate = PS_STATE_AWAKE; + lbs_pr_alert("main-thread: ignore PS_SleepConfirm in non-connected state\n"); + } + } + + /* The PS state is changed during processing of Sleep Request + * event above + */ + if ((priv->psstate == PS_STATE_SLEEP) || + (priv->psstate == PS_STATE_PRE_SLEEP)) + continue; + + /* Execute the next command */ + if (!priv->dnld_sent && !priv->cur_cmd) + lbs_execute_next_command(priv); + + /* Wake-up command waiters which can't sleep in + * lbs_prepare_and_send_command + */ + if (!list_empty(&priv->cmdpendingq)) + wake_up_all(&priv->cmd_pending); + + spin_lock_irq(&priv->driver_lock); + if (!priv->dnld_sent && priv->tx_pending_len > 0) { + int ret = priv->hw_host_to_card(priv, MVMS_DAT, + priv->tx_pending_buf, + priv->tx_pending_len); + if (ret) { + lbs_deb_tx("host_to_card failed %d\n", ret); + priv->dnld_sent = DNLD_RES_RECEIVED; + } + priv->tx_pending_len = 0; + if (!priv->currenttxskb) { + /* We can wake the queues immediately if we aren't + waiting for TX feedback */ + if (priv->connect_status == LBS_CONNECTED) + netif_wake_queue(priv->dev); + if (priv->mesh_dev && + priv->mesh_connect_status == LBS_CONNECTED) + netif_wake_queue(priv->mesh_dev); + } + } + spin_unlock_irq(&priv->driver_lock); + } + + del_timer(&priv->command_timer); + wake_up_all(&priv->cmd_pending); + + lbs_deb_leave(LBS_DEB_THREAD); + return 0; +} + +/** + * @brief This function downloads firmware image, gets + * HW spec from firmware and set basic parameters to + * firmware. + * + * @param priv A pointer to struct lbs_private structure + * @return 0 or -1 + */ +static int lbs_setup_firmware(struct lbs_private *priv) +{ + int ret = -1; + + lbs_deb_enter(LBS_DEB_FW); + + /* + * Read MAC address from HW + */ + memset(priv->current_addr, 0xff, ETH_ALEN); + ret = lbs_update_hw_spec(priv); + if (ret) { + ret = -1; + goto done; + } + + lbs_set_mac_packet_filter(priv); + + ret = lbs_get_data_rate(priv); + if (ret < 0) { + ret = -1; + goto done; + } + + ret = 0; +done: + lbs_deb_leave_args(LBS_DEB_FW, "ret %d", ret); + return ret; +} + +/** + * This function handles the timeout of command sending. + * It will re-send the same command again. + */ +static void command_timer_fn(unsigned long data) +{ + struct lbs_private *priv = (struct lbs_private *)data; + struct cmd_ctrl_node *node; + unsigned long flags; + + node = priv->cur_cmd; + if (node == NULL) { + lbs_deb_fw("ptempnode empty\n"); + return; + } + + if (!node->cmdbuf) { + lbs_deb_fw("cmd is NULL\n"); + return; + } + + lbs_deb_fw("command_timer_fn fired, cmd %x\n", node->cmdbuf->command); + + if (!priv->fw_ready) + return; + + spin_lock_irqsave(&priv->driver_lock, flags); + priv->cur_cmd = NULL; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbs_deb_fw("re-sending same command because of timeout\n"); + lbs_queue_cmd(priv, node, 0); + + wake_up_interruptible(&priv->waitq); + + return; +} + +static int lbs_init_adapter(struct lbs_private *priv) +{ + size_t bufsize; + int i, ret = 0; + + /* Allocate buffer to store the BSSID list */ + bufsize = MAX_NETWORK_COUNT * sizeof(struct bss_descriptor); + priv->networks = kzalloc(bufsize, GFP_KERNEL); + if (!priv->networks) { + lbs_pr_err("Out of memory allocating beacons\n"); + ret = -1; + goto out; + } + + /* Initialize scan result lists */ + INIT_LIST_HEAD(&priv->network_free_list); + INIT_LIST_HEAD(&priv->network_list); + for (i = 0; i < MAX_NETWORK_COUNT; i++) { + list_add_tail(&priv->networks[i].list, + &priv->network_free_list); + } + + priv->lbs_ps_confirm_sleep.seqnum = cpu_to_le16(++priv->seqnum); + priv->lbs_ps_confirm_sleep.command = + cpu_to_le16(CMD_802_11_PS_MODE); + priv->lbs_ps_confirm_sleep.size = + cpu_to_le16(sizeof(struct PS_CMD_ConfirmSleep)); + priv->lbs_ps_confirm_sleep.action = + cpu_to_le16(CMD_SUBCMD_SLEEP_CONFIRMED); + + memset(priv->current_addr, 0xff, ETH_ALEN); + + priv->connect_status = LBS_DISCONNECTED; + priv->mesh_connect_status = LBS_DISCONNECTED; + priv->secinfo.auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + priv->mode = IW_MODE_INFRA; + priv->curbssparams.channel = DEFAULT_AD_HOC_CHANNEL; + priv->currentpacketfilter = CMD_ACT_MAC_RX_ON | CMD_ACT_MAC_TX_ON; + priv->radioon = RADIO_ON; + priv->auto_rate = 1; + priv->capability = WLAN_CAPABILITY_SHORT_PREAMBLE; + priv->psmode = LBS802_11POWERMODECAM; + priv->psstate = PS_STATE_FULL_POWER; + + mutex_init(&priv->lock); + + setup_timer(&priv->command_timer, command_timer_fn, + (unsigned long)priv); + + INIT_LIST_HEAD(&priv->cmdfreeq); + INIT_LIST_HEAD(&priv->cmdpendingq); + + spin_lock_init(&priv->driver_lock); + init_waitqueue_head(&priv->cmd_pending); + + /* Allocate the command buffers */ + if (lbs_allocate_cmd_buffer(priv)) { + lbs_pr_err("Out of memory allocating command buffers\n"); + ret = -1; + } + +out: + return ret; +} + +static void lbs_free_adapter(struct lbs_private *priv) +{ + lbs_deb_fw("free command buffer\n"); + lbs_free_cmd_buffer(priv); + + lbs_deb_fw("free command_timer\n"); + del_timer(&priv->command_timer); + + lbs_deb_fw("free scan results table\n"); + kfree(priv->networks); + priv->networks = NULL; +} + +/** + * @brief This function adds the card. it will probe the + * card, allocate the lbs_priv and initialize the device. + * + * @param card A pointer to card + * @return A pointer to struct lbs_private structure + */ +struct lbs_private *lbs_add_card(void *card, struct device *dmdev) +{ + struct net_device *dev = NULL; + struct lbs_private *priv = NULL; + + lbs_deb_enter(LBS_DEB_NET); + + /* Allocate an Ethernet device and register it */ + dev = alloc_etherdev(sizeof(struct lbs_private)); + if (!dev) { + lbs_pr_err("init ethX device failed\n"); + goto done; + } + priv = dev->priv; + + if (lbs_init_adapter(priv)) { + lbs_pr_err("failed to initialize adapter structure.\n"); + goto err_init_adapter; + } + + priv->dev = dev; + priv->card = card; + priv->mesh_open = 0; + priv->infra_open = 0; + + /* Setup the OS Interface to our functions */ + dev->open = lbs_dev_open; + dev->hard_start_xmit = lbs_hard_start_xmit; + dev->stop = lbs_eth_stop; + dev->set_mac_address = lbs_set_mac_address; + dev->tx_timeout = lbs_tx_timeout; + dev->get_stats = lbs_get_stats; + dev->watchdog_timeo = 5 * HZ; + dev->ethtool_ops = &lbs_ethtool_ops; +#ifdef WIRELESS_EXT + dev->wireless_handlers = (struct iw_handler_def *)&lbs_handler_def; +#endif + dev->flags |= IFF_BROADCAST | IFF_MULTICAST; + dev->set_multicast_list = lbs_set_multicast_list; + + SET_NETDEV_DEV(dev, dmdev); + + priv->rtap_net_dev = NULL; + + lbs_deb_thread("Starting main thread...\n"); + init_waitqueue_head(&priv->waitq); + priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main"); + if (IS_ERR(priv->main_thread)) { + lbs_deb_thread("Error creating main thread.\n"); + goto err_init_adapter; + } + + priv->work_thread = create_singlethread_workqueue("lbs_worker"); + INIT_DELAYED_WORK(&priv->assoc_work, lbs_association_worker); + INIT_DELAYED_WORK(&priv->scan_work, lbs_scan_worker); + INIT_WORK(&priv->sync_channel, lbs_sync_channel); + + sprintf(priv->mesh_ssid, "mesh"); + priv->mesh_ssid_len = 4; + + goto done; + +err_init_adapter: + lbs_free_adapter(priv); + free_netdev(dev); + priv = NULL; + +done: + lbs_deb_leave_args(LBS_DEB_NET, "priv %p", priv); + return priv; +} +EXPORT_SYMBOL_GPL(lbs_add_card); + + +int lbs_remove_card(struct lbs_private *priv) +{ + struct net_device *dev = priv->dev; + union iwreq_data wrqu; + + lbs_deb_enter(LBS_DEB_MAIN); + + lbs_remove_mesh(priv); + lbs_remove_rtap(priv); + + dev = priv->dev; + + cancel_delayed_work(&priv->scan_work); + cancel_delayed_work(&priv->assoc_work); + destroy_workqueue(priv->work_thread); + + if (priv->psmode == LBS802_11POWERMODEMAX_PSP) { + priv->psmode = LBS802_11POWERMODECAM; + lbs_ps_wakeup(priv, CMD_OPTION_WAITFORRSP); + } + + memset(wrqu.ap_addr.sa_data, 0xaa, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(priv->dev, SIOCGIWAP, &wrqu, NULL); + + /* Stop the thread servicing the interrupts */ + priv->surpriseremoved = 1; + kthread_stop(priv->main_thread); + + lbs_free_adapter(priv); + + priv->dev = NULL; + free_netdev(dev); + + lbs_deb_leave(LBS_DEB_MAIN); + return 0; +} +EXPORT_SYMBOL_GPL(lbs_remove_card); + + +int lbs_start_card(struct lbs_private *priv) +{ + struct net_device *dev = priv->dev; + int ret = -1; + + lbs_deb_enter(LBS_DEB_MAIN); + + /* poke the firmware */ + ret = lbs_setup_firmware(priv); + if (ret) + goto done; + + /* init 802.11d */ + lbs_init_11d(priv); + + if (register_netdev(dev)) { + lbs_pr_err("cannot register ethX device\n"); + goto done; + } + if (device_create_file(&dev->dev, &dev_attr_lbs_rtap)) + lbs_pr_err("cannot register lbs_rtap attribute\n"); + if (device_create_file(&dev->dev, &dev_attr_lbs_mesh)) + lbs_pr_err("cannot register lbs_mesh attribute\n"); + + lbs_debugfs_init_one(priv, dev); + + lbs_pr_info("%s: Marvell WLAN 802.11 adapter\n", dev->name); + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_MAIN, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_start_card); + + +int lbs_stop_card(struct lbs_private *priv) +{ + struct net_device *dev = priv->dev; + int ret = -1; + struct cmd_ctrl_node *cmdnode; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_MAIN); + + netif_stop_queue(priv->dev); + netif_carrier_off(priv->dev); + + lbs_debugfs_remove_one(priv); + device_remove_file(&dev->dev, &dev_attr_lbs_rtap); + device_remove_file(&dev->dev, &dev_attr_lbs_mesh); + + /* Flush pending command nodes */ + spin_lock_irqsave(&priv->driver_lock, flags); + list_for_each_entry(cmdnode, &priv->cmdpendingq, list) { + cmdnode->cmdwaitqwoken = 1; + wake_up_interruptible(&cmdnode->cmdwait_q); + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + + unregister_netdev(dev); + + lbs_deb_leave_args(LBS_DEB_MAIN, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_stop_card); + + +/** + * @brief This function adds mshX interface + * + * @param priv A pointer to the struct lbs_private structure + * @return 0 if successful, -X otherwise + */ +static int lbs_add_mesh(struct lbs_private *priv) +{ + struct net_device *mesh_dev = NULL; + int ret = 0; + + lbs_deb_enter(LBS_DEB_MESH); + + /* Allocate a virtual mesh device */ + if (!(mesh_dev = alloc_netdev(0, "msh%d", ether_setup))) { + lbs_deb_mesh("init mshX device failed\n"); + ret = -ENOMEM; + goto done; + } + mesh_dev->priv = priv; + priv->mesh_dev = mesh_dev; + + mesh_dev->open = lbs_dev_open; + mesh_dev->hard_start_xmit = lbs_hard_start_xmit; + mesh_dev->stop = lbs_mesh_stop; + mesh_dev->get_stats = lbs_get_stats; + mesh_dev->set_mac_address = lbs_set_mac_address; + mesh_dev->ethtool_ops = &lbs_ethtool_ops; + memcpy(mesh_dev->dev_addr, priv->dev->dev_addr, + sizeof(priv->dev->dev_addr)); + + SET_NETDEV_DEV(priv->mesh_dev, priv->dev->dev.parent); + +#ifdef WIRELESS_EXT + mesh_dev->wireless_handlers = (struct iw_handler_def *)&mesh_handler_def; +#endif + /* Register virtual mesh interface */ + ret = register_netdev(mesh_dev); + if (ret) { + lbs_pr_err("cannot register mshX virtual interface\n"); + goto err_free; + } + + ret = sysfs_create_group(&(mesh_dev->dev.kobj), &lbs_mesh_attr_group); + if (ret) + goto err_unregister; + + /* Everything successful */ + ret = 0; + goto done; + +err_unregister: + unregister_netdev(mesh_dev); + +err_free: + free_netdev(mesh_dev); + +done: + lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_add_mesh); + + +static void lbs_remove_mesh(struct lbs_private *priv) +{ + struct net_device *mesh_dev; + + lbs_deb_enter(LBS_DEB_MAIN); + + if (!priv) + goto out; + + mesh_dev = priv->mesh_dev; + if (!mesh_dev) + goto out; + + netif_stop_queue(mesh_dev); + netif_carrier_off(priv->mesh_dev); + + sysfs_remove_group(&(mesh_dev->dev.kobj), &lbs_mesh_attr_group); + unregister_netdev(mesh_dev); + + priv->mesh_dev = NULL; + free_netdev(mesh_dev); + +out: + lbs_deb_leave(LBS_DEB_MAIN); +} +EXPORT_SYMBOL_GPL(lbs_remove_mesh); + +/** + * @brief This function finds the CFP in + * region_cfp_table based on region and band parameter. + * + * @param region The region code + * @param band The band + * @param cfp_no A pointer to CFP number + * @return A pointer to CFP + */ +struct chan_freq_power *lbs_get_region_cfp_table(u8 region, u8 band, int *cfp_no) +{ + int i, end; + + lbs_deb_enter(LBS_DEB_MAIN); + + end = ARRAY_SIZE(region_cfp_table); + + for (i = 0; i < end ; i++) { + lbs_deb_main("region_cfp_table[i].region=%d\n", + region_cfp_table[i].region); + if (region_cfp_table[i].region == region) { + *cfp_no = region_cfp_table[i].cfp_no_BG; + lbs_deb_leave(LBS_DEB_MAIN); + return region_cfp_table[i].cfp_BG; + } + } + + lbs_deb_leave_args(LBS_DEB_MAIN, "ret NULL"); + return NULL; +} + +int lbs_set_regiontable(struct lbs_private *priv, u8 region, u8 band) +{ + int ret = 0; + int i = 0; + + struct chan_freq_power *cfp; + int cfp_no; + + lbs_deb_enter(LBS_DEB_MAIN); + + memset(priv->region_channel, 0, sizeof(priv->region_channel)); + + { + cfp = lbs_get_region_cfp_table(region, band, &cfp_no); + if (cfp != NULL) { + priv->region_channel[i].nrcfp = cfp_no; + priv->region_channel[i].CFP = cfp; + } else { + lbs_deb_main("wrong region code %#x in band B/G\n", + region); + ret = -1; + goto out; + } + priv->region_channel[i].valid = 1; + priv->region_channel[i].region = region; + priv->region_channel[i].band = band; + i++; + } +out: + lbs_deb_leave_args(LBS_DEB_MAIN, "ret %d", ret); + return ret; +} + +/** + * @brief This function handles the interrupt. it will change PS + * state if applicable. it will wake up main_thread to handle + * the interrupt event as well. + * + * @param dev A pointer to net_device structure + * @return n/a + */ +void lbs_interrupt(struct lbs_private *priv) +{ + lbs_deb_enter(LBS_DEB_THREAD); + + lbs_deb_thread("lbs_interrupt: intcounter=%d\n", priv->intcounter); + + if (!spin_trylock(&priv->driver_lock)) { + spin_unlock(&priv->driver_lock); + printk(KERN_CRIT "%s called without driver_lock held\n", __func__); + WARN_ON(1); + } + + priv->intcounter++; + + if (priv->psstate == PS_STATE_SLEEP) + priv->psstate = PS_STATE_AWAKE; + + wake_up_interruptible(&priv->waitq); + + lbs_deb_leave(LBS_DEB_THREAD); +} +EXPORT_SYMBOL_GPL(lbs_interrupt); + +int lbs_reset_device(struct lbs_private *priv) +{ + int ret; + + lbs_deb_enter(LBS_DEB_MAIN); + ret = lbs_prepare_and_send_command(priv, CMD_802_11_RESET, + CMD_ACT_HALT, 0, 0, NULL); + msleep_interruptible(10); + + lbs_deb_leave_args(LBS_DEB_MAIN, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_reset_device); + +static int __init lbs_init_module(void) +{ + lbs_deb_enter(LBS_DEB_MAIN); + lbs_debugfs_init(); + lbs_deb_leave(LBS_DEB_MAIN); + return 0; +} + +static void __exit lbs_exit_module(void) +{ + lbs_deb_enter(LBS_DEB_MAIN); + + lbs_debugfs_remove(); + + lbs_deb_leave(LBS_DEB_MAIN); +} + +/* + * rtap interface support fuctions + */ + +static int lbs_rtap_open(struct net_device *dev) +{ + /* Yes, _stop_ the queue. Because we don't support injection */ + netif_carrier_off(dev); + netif_stop_queue(dev); + return 0; +} + +static int lbs_rtap_stop(struct net_device *dev) +{ + return 0; +} + +static int lbs_rtap_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + netif_stop_queue(dev); + return NETDEV_TX_BUSY; +} + +static struct net_device_stats *lbs_rtap_get_stats(struct net_device *dev) +{ + struct lbs_private *priv = dev->priv; + return &priv->stats; +} + + +static void lbs_remove_rtap(struct lbs_private *priv) +{ + if (priv->rtap_net_dev == NULL) + return; + unregister_netdev(priv->rtap_net_dev); + free_netdev(priv->rtap_net_dev); + priv->rtap_net_dev = NULL; +} + +static int lbs_add_rtap(struct lbs_private *priv) +{ + int rc = 0; + struct net_device *rtap_dev; + + if (priv->rtap_net_dev) + return -EPERM; + + rtap_dev = alloc_netdev(0, "rtap%d", ether_setup); + if (rtap_dev == NULL) + return -ENOMEM; + + memcpy(rtap_dev->dev_addr, priv->current_addr, ETH_ALEN); + rtap_dev->type = ARPHRD_IEEE80211_RADIOTAP; + rtap_dev->open = lbs_rtap_open; + rtap_dev->stop = lbs_rtap_stop; + rtap_dev->get_stats = lbs_rtap_get_stats; + rtap_dev->hard_start_xmit = lbs_rtap_hard_start_xmit; + rtap_dev->set_multicast_list = lbs_set_multicast_list; + rtap_dev->priv = priv; + + rc = register_netdev(rtap_dev); + if (rc) { + free_netdev(rtap_dev); + return rc; + } + priv->rtap_net_dev = rtap_dev; + + return 0; +} + + +module_init(lbs_init_module); +module_exit(lbs_exit_module); + +MODULE_DESCRIPTION("Libertas WLAN Driver Library"); +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_LICENSE("GPL"); diff --git a/package/libertas/src/radiotap.h b/package/libertas/src/radiotap.h new file mode 100644 index 0000000000..5d118f40cf --- /dev/null +++ b/package/libertas/src/radiotap.h @@ -0,0 +1,57 @@ +#include <net/ieee80211_radiotap.h> + +struct tx_radiotap_hdr { + struct ieee80211_radiotap_header hdr; + u8 rate; + u8 txpower; + u8 rts_retries; + u8 data_retries; +#if 0 + u8 pad[IEEE80211_RADIOTAP_HDRLEN - 12]; +#endif +} __attribute__ ((packed)); + +#define TX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_DBM_TX_POWER) | \ + (1 << IEEE80211_RADIOTAP_RTS_RETRIES) | \ + (1 << IEEE80211_RADIOTAP_DATA_RETRIES) | \ + 0) + +#define IEEE80211_FC_VERSION_MASK 0x0003 +#define IEEE80211_FC_TYPE_MASK 0x000c +#define IEEE80211_FC_TYPE_MGT 0x0000 +#define IEEE80211_FC_TYPE_CTL 0x0004 +#define IEEE80211_FC_TYPE_DATA 0x0008 +#define IEEE80211_FC_SUBTYPE_MASK 0x00f0 +#define IEEE80211_FC_TOFROMDS_MASK 0x0300 +#define IEEE80211_FC_TODS_MASK 0x0100 +#define IEEE80211_FC_FROMDS_MASK 0x0200 +#define IEEE80211_FC_NODS 0x0000 +#define IEEE80211_FC_TODS 0x0100 +#define IEEE80211_FC_FROMDS 0x0200 +#define IEEE80211_FC_DSTODS 0x0300 + +struct rx_radiotap_hdr { + struct ieee80211_radiotap_header hdr; + u8 flags; + u8 rate; + u16 chan_freq; + u16 chan_flags; + u8 antenna; + u8 antsignal; + u16 rx_flags; +#if 0 + u8 pad[IEEE80211_RADIOTAP_HDRLEN - 18]; +#endif +} __attribute__ ((packed)); + +#define RX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL) |\ + (1 << IEEE80211_RADIOTAP_RX_FLAGS) | \ + 0) + diff --git a/package/libertas/src/rx.c b/package/libertas/src/rx.c new file mode 100644 index 0000000000..6332fd451a --- /dev/null +++ b/package/libertas/src/rx.c @@ -0,0 +1,400 @@ +/** + * This file contains the handling of RX in wlan driver. + */ +#include <linux/etherdevice.h> +#include <linux/types.h> + +#include "hostcmd.h" +#include "radiotap.h" +#include "decl.h" +#include "dev.h" +#include "wext.h" + +struct eth803hdr { + u8 dest_addr[6]; + u8 src_addr[6]; + u16 h803_len; +} __attribute__ ((packed)); + +struct rfc1042hdr { + u8 llc_dsap; + u8 llc_ssap; + u8 llc_ctrl; + u8 snap_oui[3]; + u16 snap_type; +} __attribute__ ((packed)); + +struct rxpackethdr { + struct rxpd rx_pd; + struct eth803hdr eth803_hdr; + struct rfc1042hdr rfc1042_hdr; +} __attribute__ ((packed)); + +struct rx80211packethdr { + struct rxpd rx_pd; + void *eth80211_hdr; +} __attribute__ ((packed)); + +static int process_rxed_802_11_packet(struct lbs_private *priv, + struct sk_buff *skb); + +/** + * @brief This function computes the avgSNR . + * + * @param priv A pointer to struct lbs_private structure + * @return avgSNR + */ +static u8 lbs_getavgsnr(struct lbs_private *priv) +{ + u8 i; + u16 temp = 0; + if (priv->numSNRNF == 0) + return 0; + for (i = 0; i < priv->numSNRNF; i++) + temp += priv->rawSNR[i]; + return (u8) (temp / priv->numSNRNF); + +} + +/** + * @brief This function computes the AvgNF + * + * @param priv A pointer to struct lbs_private structure + * @return AvgNF + */ +static u8 lbs_getavgnf(struct lbs_private *priv) +{ + u8 i; + u16 temp = 0; + if (priv->numSNRNF == 0) + return 0; + for (i = 0; i < priv->numSNRNF; i++) + temp += priv->rawNF[i]; + return (u8) (temp / priv->numSNRNF); + +} + +/** + * @brief This function save the raw SNR/NF to our internel buffer + * + * @param priv A pointer to struct lbs_private structure + * @param prxpd A pointer to rxpd structure of received packet + * @return n/a + */ +static void lbs_save_rawSNRNF(struct lbs_private *priv, struct rxpd *p_rx_pd) +{ + if (priv->numSNRNF < DEFAULT_DATA_AVG_FACTOR) + priv->numSNRNF++; + priv->rawSNR[priv->nextSNRNF] = p_rx_pd->snr; + priv->rawNF[priv->nextSNRNF] = p_rx_pd->nf; + priv->nextSNRNF++; + if (priv->nextSNRNF >= DEFAULT_DATA_AVG_FACTOR) + priv->nextSNRNF = 0; + return; +} + +/** + * @brief This function computes the RSSI in received packet. + * + * @param priv A pointer to struct lbs_private structure + * @param prxpd A pointer to rxpd structure of received packet + * @return n/a + */ +static void lbs_compute_rssi(struct lbs_private *priv, struct rxpd *p_rx_pd) +{ + + lbs_deb_enter(LBS_DEB_RX); + + lbs_deb_rx("rxpd: SNR %d, NF %d\n", p_rx_pd->snr, p_rx_pd->nf); + lbs_deb_rx("before computing SNR: SNR-avg = %d, NF-avg = %d\n", + priv->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE, + priv->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE); + + priv->SNR[TYPE_RXPD][TYPE_NOAVG] = p_rx_pd->snr; + priv->NF[TYPE_RXPD][TYPE_NOAVG] = p_rx_pd->nf; + lbs_save_rawSNRNF(priv, p_rx_pd); + + priv->SNR[TYPE_RXPD][TYPE_AVG] = lbs_getavgsnr(priv) * AVG_SCALE; + priv->NF[TYPE_RXPD][TYPE_AVG] = lbs_getavgnf(priv) * AVG_SCALE; + lbs_deb_rx("after computing SNR: SNR-avg = %d, NF-avg = %d\n", + priv->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE, + priv->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE); + + priv->RSSI[TYPE_RXPD][TYPE_NOAVG] = + CAL_RSSI(priv->SNR[TYPE_RXPD][TYPE_NOAVG], + priv->NF[TYPE_RXPD][TYPE_NOAVG]); + + priv->RSSI[TYPE_RXPD][TYPE_AVG] = + CAL_RSSI(priv->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE, + priv->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE); + + lbs_deb_leave(LBS_DEB_RX); +} + +/** + * @brief This function processes received packet and forwards it + * to kernel/upper layer + * + * @param priv A pointer to struct lbs_private + * @param skb A pointer to skb which includes the received packet + * @return 0 or -1 + */ +int lbs_process_rxed_packet(struct lbs_private *priv, struct sk_buff *skb) +{ + int ret = 0; + struct net_device *dev = priv->dev; + struct rxpackethdr *p_rx_pkt; + struct rxpd *p_rx_pd; + + int hdrchop; + struct ethhdr *p_ethhdr; + + const u8 rfc1042_eth_hdr[] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 }; + + lbs_deb_enter(LBS_DEB_RX); + + skb->ip_summed = CHECKSUM_NONE; + + if (priv->monitormode != LBS_MONITOR_OFF) + return process_rxed_802_11_packet(priv, skb); + + p_rx_pkt = (struct rxpackethdr *) skb->data; + p_rx_pd = &p_rx_pkt->rx_pd; + if (priv->mesh_dev && (p_rx_pd->rx_control & RxPD_MESH_FRAME)) + dev = priv->mesh_dev; + + lbs_deb_hex(LBS_DEB_RX, "RX Data: Before chop rxpd", skb->data, + min_t(unsigned int, skb->len, 100)); + + if (skb->len < (ETH_HLEN + 8 + sizeof(struct rxpd))) { + lbs_deb_rx("rx err: frame received with bad length\n"); + priv->stats.rx_length_errors++; + ret = 0; + goto done; + } + + /* + * Check rxpd status and update 802.3 stat, + */ + if (!(p_rx_pd->status & cpu_to_le16(MRVDRV_RXPD_STATUS_OK))) { + lbs_deb_rx("rx err: frame received with bad status\n"); + lbs_pr_alert("rxpd not ok\n"); + priv->stats.rx_errors++; + ret = 0; + goto done; + } + + lbs_deb_rx("rx data: skb->len-sizeof(RxPd) = %d-%zd = %zd\n", + skb->len, sizeof(struct rxpd), skb->len - sizeof(struct rxpd)); + + lbs_deb_hex(LBS_DEB_RX, "RX Data: Dest", p_rx_pkt->eth803_hdr.dest_addr, + sizeof(p_rx_pkt->eth803_hdr.dest_addr)); + lbs_deb_hex(LBS_DEB_RX, "RX Data: Src", p_rx_pkt->eth803_hdr.src_addr, + sizeof(p_rx_pkt->eth803_hdr.src_addr)); + + if (memcmp(&p_rx_pkt->rfc1042_hdr, + rfc1042_eth_hdr, sizeof(rfc1042_eth_hdr)) == 0) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with an + * EthernetII header, keep the src/dst and snap_type (ethertype) + * + * The firmware only passes up SNAP frames converting + * all RX Data from 802.11 to 802.2/LLC/SNAP frames. + * + * To create the Ethernet II, just move the src, dst address right + * before the snap_type. + */ + p_ethhdr = (struct ethhdr *) + ((u8 *) & p_rx_pkt->eth803_hdr + + sizeof(p_rx_pkt->eth803_hdr) + sizeof(p_rx_pkt->rfc1042_hdr) + - sizeof(p_rx_pkt->eth803_hdr.dest_addr) + - sizeof(p_rx_pkt->eth803_hdr.src_addr) + - sizeof(p_rx_pkt->rfc1042_hdr.snap_type)); + + memcpy(p_ethhdr->h_source, p_rx_pkt->eth803_hdr.src_addr, + sizeof(p_ethhdr->h_source)); + memcpy(p_ethhdr->h_dest, p_rx_pkt->eth803_hdr.dest_addr, + sizeof(p_ethhdr->h_dest)); + + /* Chop off the rxpd + the excess memory from the 802.2/llc/snap header + * that was removed + */ + hdrchop = (u8 *) p_ethhdr - (u8 *) p_rx_pkt; + } else { + lbs_deb_hex(LBS_DEB_RX, "RX Data: LLC/SNAP", + (u8 *) & p_rx_pkt->rfc1042_hdr, + sizeof(p_rx_pkt->rfc1042_hdr)); + + /* Chop off the rxpd */ + hdrchop = (u8 *) & p_rx_pkt->eth803_hdr - (u8 *) p_rx_pkt; + } + + /* Chop off the leading header bytes so the skb points to the start of + * either the reconstructed EthII frame or the 802.2/llc/snap frame + */ + skb_pull(skb, hdrchop); + + /* Take the data rate from the rxpd structure + * only if the rate is auto + */ + if (priv->auto_rate) + priv->cur_rate = lbs_fw_index_to_data_rate(p_rx_pd->rx_rate); + + lbs_compute_rssi(priv, p_rx_pd); + + lbs_deb_rx("rx data: size of actual packet %d\n", skb->len); + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + + ret = 0; +done: + lbs_deb_leave_args(LBS_DEB_RX, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_process_rxed_packet); + +/** + * @brief This function converts Tx/Rx rates from the Marvell WLAN format + * (see Table 2 in Section 3.1) to IEEE80211_RADIOTAP_RATE units (500 Kb/s) + * + * @param rate Input rate + * @return Output Rate (0 if invalid) + */ +static u8 convert_mv_rate_to_radiotap(u8 rate) +{ + switch (rate) { + case 0: /* 1 Mbps */ + return 2; + case 1: /* 2 Mbps */ + return 4; + case 2: /* 5.5 Mbps */ + return 11; + case 3: /* 11 Mbps */ + return 22; + /* case 4: reserved */ + case 5: /* 6 Mbps */ + return 12; + case 6: /* 9 Mbps */ + return 18; + case 7: /* 12 Mbps */ + return 24; + case 8: /* 18 Mbps */ + return 36; + case 9: /* 24 Mbps */ + return 48; + case 10: /* 36 Mbps */ + return 72; + case 11: /* 48 Mbps */ + return 96; + case 12: /* 54 Mbps */ + return 108; + } + lbs_pr_alert("Invalid Marvell WLAN rate %i\n", rate); + return 0; +} + +/** + * @brief This function processes a received 802.11 packet and forwards it + * to kernel/upper layer + * + * @param priv A pointer to struct lbs_private + * @param skb A pointer to skb which includes the received packet + * @return 0 or -1 + */ +static int process_rxed_802_11_packet(struct lbs_private *priv, + struct sk_buff *skb) +{ + int ret = 0; + + struct rx80211packethdr *p_rx_pkt; + struct rxpd *prxpd; + struct rx_radiotap_hdr radiotap_hdr; + struct rx_radiotap_hdr *pradiotap_hdr; + + lbs_deb_enter(LBS_DEB_RX); + + p_rx_pkt = (struct rx80211packethdr *) skb->data; + prxpd = &p_rx_pkt->rx_pd; + + // lbs_deb_hex(LBS_DEB_RX, "RX Data: Before chop rxpd", skb->data, min(skb->len, 100)); + + if (skb->len < (ETH_HLEN + 8 + sizeof(struct rxpd))) { + lbs_deb_rx("rx err: frame received with bad length\n"); + priv->stats.rx_length_errors++; + ret = -EINVAL; + kfree(skb); + goto done; + } + + /* + * Check rxpd status and update 802.3 stat, + */ + if (!(prxpd->status & cpu_to_le16(MRVDRV_RXPD_STATUS_OK))) { + //lbs_deb_rx("rx err: frame received with bad status\n"); + priv->stats.rx_errors++; + } + + lbs_deb_rx("rx data: skb->len-sizeof(RxPd) = %d-%zd = %zd\n", + skb->len, sizeof(struct rxpd), skb->len - sizeof(struct rxpd)); + + /* create the exported radio header */ + + /* radiotap header */ + radiotap_hdr.hdr.it_version = 0; + /* XXX must check this value for pad */ + radiotap_hdr.hdr.it_pad = 0; + radiotap_hdr.hdr.it_len = cpu_to_le16 (sizeof(struct rx_radiotap_hdr)); + radiotap_hdr.hdr.it_present = cpu_to_le32 (RX_RADIOTAP_PRESENT); + /* unknown values */ + radiotap_hdr.flags = 0; + radiotap_hdr.chan_freq = 0; + radiotap_hdr.chan_flags = 0; + radiotap_hdr.antenna = 0; + /* known values */ + radiotap_hdr.rate = convert_mv_rate_to_radiotap(prxpd->rx_rate); + /* XXX must check no carryout */ + radiotap_hdr.antsignal = prxpd->snr + prxpd->nf; + radiotap_hdr.rx_flags = 0; + if (!(prxpd->status & cpu_to_le16(MRVDRV_RXPD_STATUS_OK))) + radiotap_hdr.rx_flags |= IEEE80211_RADIOTAP_F_RX_BADFCS; + //memset(radiotap_hdr.pad, 0x11, IEEE80211_RADIOTAP_HDRLEN - 18); + + /* chop the rxpd */ + skb_pull(skb, sizeof(struct rxpd)); + + /* add space for the new radio header */ + if ((skb_headroom(skb) < sizeof(struct rx_radiotap_hdr)) && + pskb_expand_head(skb, sizeof(struct rx_radiotap_hdr), 0, GFP_ATOMIC)) { + lbs_pr_alert("%s: couldn't pskb_expand_head\n", __func__); + ret = -ENOMEM; + kfree_skb(skb); + goto done; + } + + pradiotap_hdr = (void *)skb_push(skb, sizeof(struct rx_radiotap_hdr)); + memcpy(pradiotap_hdr, &radiotap_hdr, sizeof(struct rx_radiotap_hdr)); + + /* Take the data rate from the rxpd structure + * only if the rate is auto + */ + if (priv->auto_rate) + priv->cur_rate = lbs_fw_index_to_data_rate(prxpd->rx_rate); + + lbs_compute_rssi(priv, prxpd); + + lbs_deb_rx("rx data: size of actual packet %d\n", skb->len); + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + + skb->protocol = eth_type_trans(skb, priv->rtap_net_dev); + netif_rx(skb); + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_RX, "ret %d", ret); + return ret; +} diff --git a/package/libertas/src/scan.c b/package/libertas/src/scan.c new file mode 100644 index 0000000000..92d84c72e2 --- /dev/null +++ b/package/libertas/src/scan.c @@ -0,0 +1,1643 @@ +/** + * Functions implementing wlan scan IOCTL and firmware command APIs + * + * IOCTL handlers as well as command preperation and response routines + * for sending scan commands to the firmware. + */ +#include <linux/ctype.h> +#include <linux/if.h> +#include <linux/netdevice.h> +#include <linux/wireless.h> +#include <linux/etherdevice.h> + +#include <net/ieee80211.h> +#include <net/iw_handler.h> + +#include <asm/unaligned.h> + +#include "host.h" +#include "decl.h" +#include "dev.h" +#include "scan.h" +#include "join.h" + +//! Approximate amount of data needed to pass a scan result back to iwlist +#define MAX_SCAN_CELL_SIZE (IW_EV_ADDR_LEN \ + + IW_ESSID_MAX_SIZE \ + + IW_EV_UINT_LEN \ + + IW_EV_FREQ_LEN \ + + IW_EV_QUAL_LEN \ + + IW_ESSID_MAX_SIZE \ + + IW_EV_PARAM_LEN \ + + 40) /* 40 for WPAIE */ + +//! Memory needed to store a max sized channel List TLV for a firmware scan +#define CHAN_TLV_MAX_SIZE (sizeof(struct mrvlietypesheader) \ + + (MRVDRV_MAX_CHANNELS_PER_SCAN \ + * sizeof(struct chanscanparamset))) + +//! Memory needed to store a max number/size SSID TLV for a firmware scan +#define SSID_TLV_MAX_SIZE (1 * sizeof(struct mrvlietypes_ssidparamset)) + +//! Maximum memory needed for a lbs_scan_cmd_config with all TLVs at max +#define MAX_SCAN_CFG_ALLOC (sizeof(struct lbs_scan_cmd_config) \ + + CHAN_TLV_MAX_SIZE \ + + SSID_TLV_MAX_SIZE) + +//! The maximum number of channels the firmware can scan per command +#define MRVDRV_MAX_CHANNELS_PER_SCAN 14 + +/** + * @brief Number of channels to scan per firmware scan command issuance. + * + * Number restricted to prevent hitting the limit on the amount of scan data + * returned in a single firmware scan command. + */ +#define MRVDRV_CHANNELS_PER_SCAN_CMD 4 + +//! Scan time specified in the channel TLV for each channel for passive scans +#define MRVDRV_PASSIVE_SCAN_CHAN_TIME 100 + +//! Scan time specified in the channel TLV for each channel for active scans +#define MRVDRV_ACTIVE_SCAN_CHAN_TIME 100 + +static const u8 zeromac[ETH_ALEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const u8 bcastmac[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + + + +/*********************************************************************/ +/* */ +/* Misc helper functions */ +/* */ +/*********************************************************************/ + +static inline void clear_bss_descriptor (struct bss_descriptor * bss) +{ + /* Don't blow away ->list, just BSS data */ + memset(bss, 0, offsetof(struct bss_descriptor, list)); +} + +/** + * @brief Compare two SSIDs + * + * @param ssid1 A pointer to ssid to compare + * @param ssid2 A pointer to ssid to compare + * + * @return 0: ssid is same, otherwise is different + */ +int lbs_ssid_cmp(u8 *ssid1, u8 ssid1_len, u8 *ssid2, u8 ssid2_len) +{ + if (ssid1_len != ssid2_len) + return -1; + + return memcmp(ssid1, ssid2, ssid1_len); +} + +static inline int match_bss_no_security(struct lbs_802_11_security *secinfo, + struct bss_descriptor * match_bss) +{ + if ( !secinfo->wep_enabled + && !secinfo->WPAenabled + && !secinfo->WPA2enabled + && match_bss->wpa_ie[0] != MFIE_TYPE_GENERIC + && match_bss->rsn_ie[0] != MFIE_TYPE_RSN + && !(match_bss->capability & WLAN_CAPABILITY_PRIVACY)) { + return 1; + } + return 0; +} + +static inline int match_bss_static_wep(struct lbs_802_11_security *secinfo, + struct bss_descriptor * match_bss) +{ + if ( secinfo->wep_enabled + && !secinfo->WPAenabled + && !secinfo->WPA2enabled + && (match_bss->capability & WLAN_CAPABILITY_PRIVACY)) { + return 1; + } + return 0; +} + +static inline int match_bss_wpa(struct lbs_802_11_security *secinfo, + struct bss_descriptor * match_bss) +{ + if ( !secinfo->wep_enabled + && secinfo->WPAenabled + && (match_bss->wpa_ie[0] == MFIE_TYPE_GENERIC) + /* privacy bit may NOT be set in some APs like LinkSys WRT54G + && (match_bss->capability & WLAN_CAPABILITY_PRIVACY)) { + */ + ) { + return 1; + } + return 0; +} + +static inline int match_bss_wpa2(struct lbs_802_11_security *secinfo, + struct bss_descriptor * match_bss) +{ + if ( !secinfo->wep_enabled + && secinfo->WPA2enabled + && (match_bss->rsn_ie[0] == MFIE_TYPE_RSN) + /* privacy bit may NOT be set in some APs like LinkSys WRT54G + && (match_bss->capability & WLAN_CAPABILITY_PRIVACY)) { + */ + ) { + return 1; + } + return 0; +} + +static inline int match_bss_dynamic_wep(struct lbs_802_11_security *secinfo, + struct bss_descriptor * match_bss) +{ + if ( !secinfo->wep_enabled + && !secinfo->WPAenabled + && !secinfo->WPA2enabled + && (match_bss->wpa_ie[0] != MFIE_TYPE_GENERIC) + && (match_bss->rsn_ie[0] != MFIE_TYPE_RSN) + && (match_bss->capability & WLAN_CAPABILITY_PRIVACY)) { + return 1; + } + return 0; +} + +static inline int is_same_network(struct bss_descriptor *src, + struct bss_descriptor *dst) +{ + /* A network is only a duplicate if the channel, BSSID, and ESSID + * all match. We treat all <hidden> with the same BSSID and channel + * as one network */ + return ((src->ssid_len == dst->ssid_len) && + (src->channel == dst->channel) && + !compare_ether_addr(src->bssid, dst->bssid) && + !memcmp(src->ssid, dst->ssid, src->ssid_len)); +} + +/** + * @brief Check if a scanned network compatible with the driver settings + * + * WEP WPA WPA2 ad-hoc encrypt Network + * enabled enabled enabled AES mode privacy WPA WPA2 Compatible + * 0 0 0 0 NONE 0 0 0 yes No security + * 1 0 0 0 NONE 1 0 0 yes Static WEP + * 0 1 0 0 x 1x 1 x yes WPA + * 0 0 1 0 x 1x x 1 yes WPA2 + * 0 0 0 1 NONE 1 0 0 yes Ad-hoc AES + * 0 0 0 0 !=NONE 1 0 0 yes Dynamic WEP + * + * + * @param priv A pointer to struct lbs_private + * @param index Index in scantable to check against current driver settings + * @param mode Network mode: Infrastructure or IBSS + * + * @return Index in scantable, or error code if negative + */ +static int is_network_compatible(struct lbs_private *priv, + struct bss_descriptor * bss, u8 mode) +{ + int matched = 0; + + lbs_deb_enter(LBS_DEB_SCAN); + + if (bss->mode != mode) + goto done; + + if ((matched = match_bss_no_security(&priv->secinfo, bss))) { + goto done; + } else if ((matched = match_bss_static_wep(&priv->secinfo, bss))) { + goto done; + } else if ((matched = match_bss_wpa(&priv->secinfo, bss))) { + lbs_deb_scan( + "is_network_compatible() WPA: wpa_ie 0x%x " + "wpa2_ie 0x%x WEP %s WPA %s WPA2 %s " + "privacy 0x%x\n", bss->wpa_ie[0], bss->rsn_ie[0], + priv->secinfo.wep_enabled ? "e" : "d", + priv->secinfo.WPAenabled ? "e" : "d", + priv->secinfo.WPA2enabled ? "e" : "d", + (bss->capability & WLAN_CAPABILITY_PRIVACY)); + goto done; + } else if ((matched = match_bss_wpa2(&priv->secinfo, bss))) { + lbs_deb_scan( + "is_network_compatible() WPA2: wpa_ie 0x%x " + "wpa2_ie 0x%x WEP %s WPA %s WPA2 %s " + "privacy 0x%x\n", bss->wpa_ie[0], bss->rsn_ie[0], + priv->secinfo.wep_enabled ? "e" : "d", + priv->secinfo.WPAenabled ? "e" : "d", + priv->secinfo.WPA2enabled ? "e" : "d", + (bss->capability & WLAN_CAPABILITY_PRIVACY)); + goto done; + } else if ((matched = match_bss_dynamic_wep(&priv->secinfo, bss))) { + lbs_deb_scan( + "is_network_compatible() dynamic WEP: " + "wpa_ie 0x%x wpa2_ie 0x%x privacy 0x%x\n", + bss->wpa_ie[0], bss->rsn_ie[0], + (bss->capability & WLAN_CAPABILITY_PRIVACY)); + goto done; + } + + /* bss security settings don't match those configured on card */ + lbs_deb_scan( + "is_network_compatible() FAILED: wpa_ie 0x%x " + "wpa2_ie 0x%x WEP %s WPA %s WPA2 %s privacy 0x%x\n", + bss->wpa_ie[0], bss->rsn_ie[0], + priv->secinfo.wep_enabled ? "e" : "d", + priv->secinfo.WPAenabled ? "e" : "d", + priv->secinfo.WPA2enabled ? "e" : "d", + (bss->capability & WLAN_CAPABILITY_PRIVACY)); + +done: + lbs_deb_leave_args(LBS_DEB_SCAN, "matched: %d", matched); + return matched; +} + + + + +/*********************************************************************/ +/* */ +/* Main scanning support */ +/* */ +/*********************************************************************/ + +void lbs_scan_worker(struct work_struct *work) +{ + struct lbs_private *priv = + container_of(work, struct lbs_private, scan_work.work); + + lbs_deb_enter(LBS_DEB_SCAN); + lbs_scan_networks(priv, NULL, 0); + lbs_deb_leave(LBS_DEB_SCAN); +} + + +/** + * @brief Create a channel list for the driver to scan based on region info + * + * Only used from lbs_scan_setup_scan_config() + * + * Use the driver region/band information to construct a comprehensive list + * of channels to scan. This routine is used for any scan that is not + * provided a specific channel list to scan. + * + * @param priv A pointer to struct lbs_private structure + * @param scanchanlist Output parameter: resulting channel list to scan + * @param filteredscan Flag indicating whether or not a BSSID or SSID filter + * is being sent in the command to firmware. Used to + * increase the number of channels sent in a scan + * command and to disable the firmware channel scan + * filter. + * + * @return void + */ +static int lbs_scan_create_channel_list(struct lbs_private *priv, + struct chanscanparamset * scanchanlist, + u8 filteredscan) +{ + + struct region_channel *scanregion; + struct chan_freq_power *cfp; + int rgnidx; + int chanidx; + int nextchan; + u8 scantype; + + chanidx = 0; + + /* Set the default scan type to the user specified type, will later + * be changed to passive on a per channel basis if restricted by + * regulatory requirements (11d or 11h) + */ + scantype = CMD_SCAN_TYPE_ACTIVE; + + for (rgnidx = 0; rgnidx < ARRAY_SIZE(priv->region_channel); rgnidx++) { + if (priv->enable11d && + (priv->connect_status != LBS_CONNECTED) && + (priv->mesh_connect_status != LBS_CONNECTED)) { + /* Scan all the supported chan for the first scan */ + if (!priv->universal_channel[rgnidx].valid) + continue; + scanregion = &priv->universal_channel[rgnidx]; + + /* clear the parsed_region_chan for the first scan */ + memset(&priv->parsed_region_chan, 0x00, + sizeof(priv->parsed_region_chan)); + } else { + if (!priv->region_channel[rgnidx].valid) + continue; + scanregion = &priv->region_channel[rgnidx]; + } + + for (nextchan = 0; + nextchan < scanregion->nrcfp; nextchan++, chanidx++) { + + cfp = scanregion->CFP + nextchan; + + if (priv->enable11d) { + scantype = + lbs_get_scan_type_11d(cfp->channel, + &priv-> + parsed_region_chan); + } + + switch (scanregion->band) { + case BAND_B: + case BAND_G: + default: + scanchanlist[chanidx].radiotype = + CMD_SCAN_RADIO_TYPE_BG; + break; + } + + if (scantype == CMD_SCAN_TYPE_PASSIVE) { + scanchanlist[chanidx].maxscantime = + cpu_to_le16(MRVDRV_PASSIVE_SCAN_CHAN_TIME); + scanchanlist[chanidx].chanscanmode.passivescan = + 1; + } else { + scanchanlist[chanidx].maxscantime = + cpu_to_le16(MRVDRV_ACTIVE_SCAN_CHAN_TIME); + scanchanlist[chanidx].chanscanmode.passivescan = + 0; + } + + scanchanlist[chanidx].channumber = cfp->channel; + + if (filteredscan) { + scanchanlist[chanidx].chanscanmode. + disablechanfilt = 1; + } + } + } + return chanidx; +} + + +/* + * Add SSID TLV of the form: + * + * TLV-ID SSID 00 00 + * length 06 00 + * ssid 4d 4e 54 45 53 54 + */ +static int lbs_scan_add_ssid_tlv(u8 *tlv, + const struct lbs_ioctl_user_scan_cfg *user_cfg) +{ + struct mrvlietypes_ssidparamset *ssid_tlv = + (struct mrvlietypes_ssidparamset *)tlv; + ssid_tlv->header.type = cpu_to_le16(TLV_TYPE_SSID); + ssid_tlv->header.len = cpu_to_le16(user_cfg->ssid_len); + memcpy(ssid_tlv->ssid, user_cfg->ssid, user_cfg->ssid_len); + return sizeof(ssid_tlv->header) + user_cfg->ssid_len; +} + + +/* + * Add CHANLIST TLV of the form + * + * TLV-ID CHANLIST 01 01 + * length 5b 00 + * channel 1 00 01 00 00 00 64 00 + * radio type 00 + * channel 01 + * scan type 00 + * min scan time 00 00 + * max scan time 64 00 + * channel 2 00 02 00 00 00 64 00 + * channel 3 00 03 00 00 00 64 00 + * channel 4 00 04 00 00 00 64 00 + * channel 5 00 05 00 00 00 64 00 + * channel 6 00 06 00 00 00 64 00 + * channel 7 00 07 00 00 00 64 00 + * channel 8 00 08 00 00 00 64 00 + * channel 9 00 09 00 00 00 64 00 + * channel 10 00 0a 00 00 00 64 00 + * channel 11 00 0b 00 00 00 64 00 + * channel 12 00 0c 00 00 00 64 00 + * channel 13 00 0d 00 00 00 64 00 + * + */ +static int lbs_scan_add_chanlist_tlv(u8 *tlv, + struct chanscanparamset *chan_list, + int chan_count) +{ + size_t size = sizeof(struct chanscanparamset) * chan_count; + struct mrvlietypes_chanlistparamset *chan_tlv = + (struct mrvlietypes_chanlistparamset *) tlv; + + chan_tlv->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + memcpy(chan_tlv->chanscanparam, chan_list, size); + chan_tlv->header.len = cpu_to_le16(size); + return sizeof(chan_tlv->header) + size; +} + + +/* + * Add RATES TLV of the form + * + * TLV-ID RATES 01 00 + * length 0e 00 + * rates 82 84 8b 96 0c 12 18 24 30 48 60 6c + * + * The rates are in lbs_bg_rates[], but for the 802.11b + * rates the high bit isn't set. + */ +static int lbs_scan_add_rates_tlv(u8 *tlv) +{ + int i; + struct mrvlietypes_ratesparamset *rate_tlv = + (struct mrvlietypes_ratesparamset *) tlv; + + rate_tlv->header.type = cpu_to_le16(TLV_TYPE_RATES); + tlv += sizeof(rate_tlv->header); + for (i = 0; i < MAX_RATES; i++) { + *tlv = lbs_bg_rates[i]; + if (*tlv == 0) + break; + /* This code makes sure that the 802.11b rates (1 MBit/s, 2 + MBit/s, 5.5 MBit/s and 11 MBit/s get's the high bit set. + Note that the values are MBit/s * 2, to mark them as + basic rates so that the firmware likes it better */ + if (*tlv == 0x02 || *tlv == 0x04 || + *tlv == 0x0b || *tlv == 0x16) + *tlv |= 0x80; + tlv++; + } + rate_tlv->header.len = cpu_to_le16(i); + return sizeof(rate_tlv->header) + i; +} + + +/* + * Generate the CMD_802_11_SCAN command with the proper tlv + * for a bunch of channels. + */ +static int lbs_do_scan(struct lbs_private *priv, + u8 bsstype, + struct chanscanparamset *chan_list, + int chan_count, + const struct lbs_ioctl_user_scan_cfg *user_cfg) +{ + int ret = -ENOMEM; + struct lbs_scan_cmd_config *scan_cmd; + u8 *tlv; /* pointer into our current, growing TLV storage area */ + + lbs_deb_enter_args(LBS_DEB_SCAN, "bsstype %d, chanlist[].chan %d, " + "chan_count %d", + bsstype, chan_list[0].channumber, chan_count); + + /* create the fixed part for scan command */ + scan_cmd = kzalloc(MAX_SCAN_CFG_ALLOC, GFP_KERNEL); + if (scan_cmd == NULL) + goto out; + tlv = scan_cmd->tlvbuffer; + if (user_cfg) + memcpy(scan_cmd->bssid, user_cfg->bssid, ETH_ALEN); + scan_cmd->bsstype = bsstype; + + /* add TLVs */ + if (user_cfg && user_cfg->ssid_len) + tlv += lbs_scan_add_ssid_tlv(tlv, user_cfg); + if (chan_list && chan_count) + tlv += lbs_scan_add_chanlist_tlv(tlv, chan_list, chan_count); + tlv += lbs_scan_add_rates_tlv(tlv); + + /* This is the final data we are about to send */ + scan_cmd->tlvbufferlen = tlv - scan_cmd->tlvbuffer; + lbs_deb_hex(LBS_DEB_SCAN, "SCAN_CMD", (void *)scan_cmd, 1+6); + lbs_deb_hex(LBS_DEB_SCAN, "SCAN_TLV", scan_cmd->tlvbuffer, + scan_cmd->tlvbufferlen); + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_SCAN, 0, + CMD_OPTION_WAITFORRSP, 0, scan_cmd); +out: + kfree(scan_cmd); + lbs_deb_leave_args(LBS_DEB_SCAN, "ret %d", ret); + return ret; +} + + +/** + * @brief Internal function used to start a scan based on an input config + * + * Also used from debugfs + * + * Use the input user scan configuration information when provided in + * order to send the appropriate scan commands to firmware to populate or + * update the internal driver scan table + * + * @param priv A pointer to struct lbs_private structure + * @param puserscanin Pointer to the input configuration for the requested + * scan. + * + * @return 0 or < 0 if error + */ +int lbs_scan_networks(struct lbs_private *priv, + const struct lbs_ioctl_user_scan_cfg *user_cfg, + int full_scan) +{ + int ret = -ENOMEM; + struct chanscanparamset *chan_list; + struct chanscanparamset *curr_chans; + int chan_count; + u8 bsstype = CMD_BSS_TYPE_ANY; + int numchannels = MRVDRV_CHANNELS_PER_SCAN_CMD; + int filteredscan = 0; + union iwreq_data wrqu; +#ifdef CONFIG_LIBERTAS_DEBUG + struct bss_descriptor *iter; + int i = 0; + DECLARE_MAC_BUF(mac); +#endif + + lbs_deb_enter_args(LBS_DEB_SCAN, "full_scan %d", + full_scan); + + /* Cancel any partial outstanding partial scans if this scan + * is a full scan. + */ + if (full_scan && delayed_work_pending(&priv->scan_work)) + cancel_delayed_work(&priv->scan_work); + + /* Determine same scan parameters */ + if (user_cfg) { + if (user_cfg->bsstype) + bsstype = user_cfg->bsstype; + if (compare_ether_addr(user_cfg->bssid, &zeromac[0]) != 0) { + numchannels = MRVDRV_MAX_CHANNELS_PER_SCAN; + filteredscan = 1; + } + } + lbs_deb_scan("numchannels %d, bsstype %d, " + "filteredscan %d\n", + numchannels, bsstype, filteredscan); + + /* Create list of channels to scan */ + chan_list = kzalloc(sizeof(struct chanscanparamset) * + LBS_IOCTL_USER_SCAN_CHAN_MAX, GFP_KERNEL); + if (!chan_list) { + lbs_pr_alert("SCAN: chan_list empty\n"); + goto out; + } + + /* We want to scan all channels */ + chan_count = lbs_scan_create_channel_list(priv, chan_list, + filteredscan); + + netif_stop_queue(priv->dev); + netif_carrier_off(priv->dev); + if (priv->mesh_dev) { + netif_stop_queue(priv->mesh_dev); + netif_carrier_off(priv->mesh_dev); + } + + /* Prepare to continue an interrupted scan */ + lbs_deb_scan("chan_count %d, last_scanned_channel %d\n", + chan_count, priv->last_scanned_channel); + curr_chans = chan_list; + /* advance channel list by already-scanned-channels */ + if (priv->last_scanned_channel > 0) { + curr_chans += priv->last_scanned_channel; + chan_count -= priv->last_scanned_channel; + } + + /* Send scan command(s) + * numchannels contains the number of channels we should maximally scan + * chan_count is the total number of channels to scan + */ + + while (chan_count) { + int to_scan = min(numchannels, chan_count); + lbs_deb_scan("scanning %d of %d channels\n", + to_scan, chan_count); + ret = lbs_do_scan(priv, bsstype, curr_chans, + to_scan, user_cfg); + if (ret) { + lbs_pr_err("SCAN_CMD failed\n"); + goto out2; + } + curr_chans += to_scan; + chan_count -= to_scan; + + /* somehow schedule the next part of the scan */ + if (chan_count && + !full_scan && + !priv->surpriseremoved) { + /* -1 marks just that we're currently scanning */ + if (priv->last_scanned_channel < 0) + priv->last_scanned_channel = to_scan; + else + priv->last_scanned_channel += to_scan; + cancel_delayed_work(&priv->scan_work); + queue_delayed_work(priv->work_thread, &priv->scan_work, + msecs_to_jiffies(300)); + /* skip over GIWSCAN event */ + goto out; + } + + } + memset(&wrqu, 0, sizeof(union iwreq_data)); + wireless_send_event(priv->dev, SIOCGIWSCAN, &wrqu, NULL); + +#ifdef CONFIG_LIBERTAS_DEBUG + /* Dump the scan table */ + mutex_lock(&priv->lock); + lbs_deb_scan("scan table:\n"); + list_for_each_entry(iter, &priv->network_list, list) + lbs_deb_scan("%02d: BSSID %s, RSSI %d, SSID '%s'\n", + i++, print_mac(mac, iter->bssid), (s32) iter->rssi, + escape_essid(iter->ssid, iter->ssid_len)); + mutex_unlock(&priv->lock); +#endif + +out2: + priv->last_scanned_channel = 0; + +out: + if (priv->connect_status == LBS_CONNECTED) { + netif_carrier_on(priv->dev); + netif_wake_queue(priv->dev); + } + if (priv->mesh_dev && (priv->mesh_connect_status == LBS_CONNECTED)) { + netif_carrier_on(priv->mesh_dev); + netif_wake_queue(priv->mesh_dev); + } + kfree(chan_list); + + lbs_deb_leave_args(LBS_DEB_SCAN, "ret %d", ret); + return ret; +} + + + + +/*********************************************************************/ +/* */ +/* Result interpretation */ +/* */ +/*********************************************************************/ + +/** + * @brief Interpret a BSS scan response returned from the firmware + * + * Parse the various fixed fields and IEs passed back for a a BSS probe + * response or beacon from the scan command. Record information as needed + * in the scan table struct bss_descriptor for that entry. + * + * @param bss Output parameter: Pointer to the BSS Entry + * + * @return 0 or -1 + */ +static int lbs_process_bss(struct bss_descriptor *bss, + u8 ** pbeaconinfo, int *bytesleft) +{ + struct ieeetypes_fhparamset *pFH; + struct ieeetypes_dsparamset *pDS; + struct ieeetypes_cfparamset *pCF; + struct ieeetypes_ibssparamset *pibss; + DECLARE_MAC_BUF(mac); + struct ieeetypes_countryinfoset *pcountryinfo; + u8 *pos, *end, *p; + u8 n_ex_rates = 0, got_basic_rates = 0, n_basic_rates = 0; + u16 beaconsize = 0; + int ret; + + lbs_deb_enter(LBS_DEB_SCAN); + + if (*bytesleft >= sizeof(beaconsize)) { + /* Extract & convert beacon size from the command buffer */ + beaconsize = le16_to_cpu(get_unaligned((__le16 *)*pbeaconinfo)); + *bytesleft -= sizeof(beaconsize); + *pbeaconinfo += sizeof(beaconsize); + } + + if (beaconsize == 0 || beaconsize > *bytesleft) { + *pbeaconinfo += *bytesleft; + *bytesleft = 0; + ret = -1; + goto done; + } + + /* Initialize the current working beacon pointer for this BSS iteration */ + pos = *pbeaconinfo; + end = pos + beaconsize; + + /* Advance the return beacon pointer past the current beacon */ + *pbeaconinfo += beaconsize; + *bytesleft -= beaconsize; + + memcpy(bss->bssid, pos, ETH_ALEN); + lbs_deb_scan("process_bss: BSSID %s\n", print_mac(mac, bss->bssid)); + pos += ETH_ALEN; + + if ((end - pos) < 12) { + lbs_deb_scan("process_bss: Not enough bytes left\n"); + ret = -1; + goto done; + } + + /* + * next 4 fields are RSSI, time stamp, beacon interval, + * and capability information + */ + + /* RSSI is 1 byte long */ + bss->rssi = *pos; + lbs_deb_scan("process_bss: RSSI %d\n", *pos); + pos++; + + /* time stamp is 8 bytes long */ + pos += 8; + + /* beacon interval is 2 bytes long */ + bss->beaconperiod = le16_to_cpup((void *) pos); + pos += 2; + + /* capability information is 2 bytes long */ + bss->capability = le16_to_cpup((void *) pos); + lbs_deb_scan("process_bss: capabilities 0x%04x\n", bss->capability); + pos += 2; + + if (bss->capability & WLAN_CAPABILITY_PRIVACY) + lbs_deb_scan("process_bss: WEP enabled\n"); + if (bss->capability & WLAN_CAPABILITY_IBSS) + bss->mode = IW_MODE_ADHOC; + else + bss->mode = IW_MODE_INFRA; + + /* rest of the current buffer are IE's */ + lbs_deb_scan("process_bss: IE len %zd\n", end - pos); + lbs_deb_hex(LBS_DEB_SCAN, "process_bss: IE info", pos, end - pos); + + /* process variable IE */ + while (pos <= end - 2) { + struct ieee80211_info_element * elem = + (struct ieee80211_info_element *) pos; + + if (pos + elem->len > end) { + lbs_deb_scan("process_bss: error in processing IE, " + "bytes left < IE length\n"); + break; + } + + switch (elem->id) { + case MFIE_TYPE_SSID: + bss->ssid_len = elem->len; + memcpy(bss->ssid, elem->data, elem->len); + lbs_deb_scan("got SSID IE: '%s', len %u\n", + escape_essid(bss->ssid, bss->ssid_len), + bss->ssid_len); + break; + + case MFIE_TYPE_RATES: + n_basic_rates = min_t(u8, MAX_RATES, elem->len); + memcpy(bss->rates, elem->data, n_basic_rates); + got_basic_rates = 1; + lbs_deb_scan("got RATES IE\n"); + break; + + case MFIE_TYPE_FH_SET: + pFH = (struct ieeetypes_fhparamset *) pos; + memmove(&bss->phyparamset.fhparamset, pFH, + sizeof(struct ieeetypes_fhparamset)); + lbs_deb_scan("got FH IE\n"); + break; + + case MFIE_TYPE_DS_SET: + pDS = (struct ieeetypes_dsparamset *) pos; + bss->channel = pDS->currentchan; + memcpy(&bss->phyparamset.dsparamset, pDS, + sizeof(struct ieeetypes_dsparamset)); + lbs_deb_scan("got DS IE, channel %d\n", bss->channel); + break; + + case MFIE_TYPE_CF_SET: + pCF = (struct ieeetypes_cfparamset *) pos; + memcpy(&bss->ssparamset.cfparamset, pCF, + sizeof(struct ieeetypes_cfparamset)); + lbs_deb_scan("got CF IE\n"); + break; + + case MFIE_TYPE_IBSS_SET: + pibss = (struct ieeetypes_ibssparamset *) pos; + bss->atimwindow = le16_to_cpu(pibss->atimwindow); + memmove(&bss->ssparamset.ibssparamset, pibss, + sizeof(struct ieeetypes_ibssparamset)); + lbs_deb_scan("got IBSS IE\n"); + break; + + case MFIE_TYPE_COUNTRY: + pcountryinfo = (struct ieeetypes_countryinfoset *) pos; + lbs_deb_scan("got COUNTRY IE\n"); + if (pcountryinfo->len < sizeof(pcountryinfo->countrycode) + || pcountryinfo->len > 254) { + lbs_deb_scan("process_bss: 11D- Err " + "CountryInfo len %d, min %zd, max 254\n", + pcountryinfo->len, + sizeof(pcountryinfo->countrycode)); + ret = -1; + goto done; + } + + memcpy(&bss->countryinfo, + pcountryinfo, pcountryinfo->len + 2); + lbs_deb_hex(LBS_DEB_SCAN, "process_bss: 11d countryinfo", + (u8 *) pcountryinfo, + (u32) (pcountryinfo->len + 2)); + break; + + case MFIE_TYPE_RATES_EX: + /* only process extended supported rate if data rate is + * already found. Data rate IE should come before + * extended supported rate IE + */ + lbs_deb_scan("got RATESEX IE\n"); + if (!got_basic_rates) { + lbs_deb_scan("... but ignoring it\n"); + break; + } + + n_ex_rates = elem->len; + if (n_basic_rates + n_ex_rates > MAX_RATES) + n_ex_rates = MAX_RATES - n_basic_rates; + + p = bss->rates + n_basic_rates; + memcpy(p, elem->data, n_ex_rates); + break; + + case MFIE_TYPE_GENERIC: + if (elem->len >= 4 && + elem->data[0] == 0x00 && + elem->data[1] == 0x50 && + elem->data[2] == 0xf2 && + elem->data[3] == 0x01) { + bss->wpa_ie_len = min(elem->len + 2, + MAX_WPA_IE_LEN); + memcpy(bss->wpa_ie, elem, bss->wpa_ie_len); + lbs_deb_scan("got WPA IE\n"); + lbs_deb_hex(LBS_DEB_SCAN, "WPA IE", bss->wpa_ie, + elem->len); + } else if (elem->len >= MARVELL_MESH_IE_LENGTH && + elem->data[0] == 0x00 && + elem->data[1] == 0x50 && + elem->data[2] == 0x43 && + elem->data[3] == 0x04) { + lbs_deb_scan("got mesh IE\n"); + bss->mesh = 1; + } else { + lbs_deb_scan("got generiec IE: " + "%02x:%02x:%02x:%02x, len %d\n", + elem->data[0], elem->data[1], + elem->data[2], elem->data[3], + elem->len); + } + break; + + case MFIE_TYPE_RSN: + lbs_deb_scan("got RSN IE\n"); + bss->rsn_ie_len = min(elem->len + 2, MAX_WPA_IE_LEN); + memcpy(bss->rsn_ie, elem, bss->rsn_ie_len); + lbs_deb_hex(LBS_DEB_SCAN, "process_bss: RSN_IE", + bss->rsn_ie, elem->len); + break; + + default: + lbs_deb_scan("got IE 0x%04x, len %d\n", + elem->id, elem->len); + break; + } + + pos += elem->len + 2; + } + + /* Timestamp */ + bss->last_scanned = jiffies; + lbs_unset_basic_rate_flags(bss->rates, sizeof(bss->rates)); + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_SCAN, "ret %d", ret); + return ret; +} + +/** + * @brief This function finds a specific compatible BSSID in the scan list + * + * Used in association code + * + * @param priv A pointer to struct lbs_private + * @param bssid BSSID to find in the scan list + * @param mode Network mode: Infrastructure or IBSS + * + * @return index in BSSID list, or error return code (< 0) + */ +struct bss_descriptor *lbs_find_bssid_in_list(struct lbs_private *priv, + u8 * bssid, u8 mode) +{ + struct bss_descriptor * iter_bss; + struct bss_descriptor * found_bss = NULL; + + lbs_deb_enter(LBS_DEB_SCAN); + + if (!bssid) + goto out; + + lbs_deb_hex(LBS_DEB_SCAN, "looking for", + bssid, ETH_ALEN); + + /* Look through the scan table for a compatible match. The loop will + * continue past a matched bssid that is not compatible in case there + * is an AP with multiple SSIDs assigned to the same BSSID + */ + mutex_lock(&priv->lock); + list_for_each_entry (iter_bss, &priv->network_list, list) { + if (compare_ether_addr(iter_bss->bssid, bssid)) + continue; /* bssid doesn't match */ + switch (mode) { + case IW_MODE_INFRA: + case IW_MODE_ADHOC: + if (!is_network_compatible(priv, iter_bss, mode)) + break; + found_bss = iter_bss; + break; + default: + found_bss = iter_bss; + break; + } + } + mutex_unlock(&priv->lock); + +out: + lbs_deb_leave_args(LBS_DEB_SCAN, "found_bss %p", found_bss); + return found_bss; +} + +/** + * @brief This function finds ssid in ssid list. + * + * Used in association code + * + * @param priv A pointer to struct lbs_private + * @param ssid SSID to find in the list + * @param bssid BSSID to qualify the SSID selection (if provided) + * @param mode Network mode: Infrastructure or IBSS + * + * @return index in BSSID list + */ +struct bss_descriptor *lbs_find_ssid_in_list(struct lbs_private *priv, + u8 *ssid, u8 ssid_len, u8 * bssid, u8 mode, + int channel) +{ + u8 bestrssi = 0; + struct bss_descriptor * iter_bss = NULL; + struct bss_descriptor * found_bss = NULL; + struct bss_descriptor * tmp_oldest = NULL; + + lbs_deb_enter(LBS_DEB_SCAN); + + mutex_lock(&priv->lock); + + list_for_each_entry (iter_bss, &priv->network_list, list) { + if ( !tmp_oldest + || (iter_bss->last_scanned < tmp_oldest->last_scanned)) + tmp_oldest = iter_bss; + + if (lbs_ssid_cmp(iter_bss->ssid, iter_bss->ssid_len, + ssid, ssid_len) != 0) + continue; /* ssid doesn't match */ + if (bssid && compare_ether_addr(iter_bss->bssid, bssid) != 0) + continue; /* bssid doesn't match */ + if ((channel > 0) && (iter_bss->channel != channel)) + continue; /* channel doesn't match */ + + switch (mode) { + case IW_MODE_INFRA: + case IW_MODE_ADHOC: + if (!is_network_compatible(priv, iter_bss, mode)) + break; + + if (bssid) { + /* Found requested BSSID */ + found_bss = iter_bss; + goto out; + } + + if (SCAN_RSSI(iter_bss->rssi) > bestrssi) { + bestrssi = SCAN_RSSI(iter_bss->rssi); + found_bss = iter_bss; + } + break; + case IW_MODE_AUTO: + default: + if (SCAN_RSSI(iter_bss->rssi) > bestrssi) { + bestrssi = SCAN_RSSI(iter_bss->rssi); + found_bss = iter_bss; + } + break; + } + } + +out: + mutex_unlock(&priv->lock); + lbs_deb_leave_args(LBS_DEB_SCAN, "found_bss %p", found_bss); + return found_bss; +} + +/** + * @brief This function finds the best SSID in the Scan List + * + * Search the scan table for the best SSID that also matches the current + * adapter network preference (infrastructure or adhoc) + * + * @param priv A pointer to struct lbs_private + * + * @return index in BSSID list + */ +static struct bss_descriptor *lbs_find_best_ssid_in_list( + struct lbs_private *priv, + u8 mode) +{ + u8 bestrssi = 0; + struct bss_descriptor * iter_bss; + struct bss_descriptor * best_bss = NULL; + + lbs_deb_enter(LBS_DEB_SCAN); + + mutex_lock(&priv->lock); + + list_for_each_entry (iter_bss, &priv->network_list, list) { + switch (mode) { + case IW_MODE_INFRA: + case IW_MODE_ADHOC: + if (!is_network_compatible(priv, iter_bss, mode)) + break; + if (SCAN_RSSI(iter_bss->rssi) <= bestrssi) + break; + bestrssi = SCAN_RSSI(iter_bss->rssi); + best_bss = iter_bss; + break; + case IW_MODE_AUTO: + default: + if (SCAN_RSSI(iter_bss->rssi) <= bestrssi) + break; + bestrssi = SCAN_RSSI(iter_bss->rssi); + best_bss = iter_bss; + break; + } + } + + mutex_unlock(&priv->lock); + lbs_deb_leave_args(LBS_DEB_SCAN, "best_bss %p", best_bss); + return best_bss; +} + +/** + * @brief Find the AP with specific ssid in the scan list + * + * Used from association worker. + * + * @param priv A pointer to struct lbs_private structure + * @param pSSID A pointer to AP's ssid + * + * @return 0--success, otherwise--fail + */ +int lbs_find_best_network_ssid(struct lbs_private *priv, + u8 *out_ssid, u8 *out_ssid_len, u8 preferred_mode, u8 *out_mode) +{ + int ret = -1; + struct bss_descriptor * found; + + lbs_deb_enter(LBS_DEB_SCAN); + + lbs_scan_networks(priv, NULL, 1); + if (priv->surpriseremoved) + goto out; + + found = lbs_find_best_ssid_in_list(priv, preferred_mode); + if (found && (found->ssid_len > 0)) { + memcpy(out_ssid, &found->ssid, IW_ESSID_MAX_SIZE); + *out_ssid_len = found->ssid_len; + *out_mode = found->mode; + ret = 0; + } + +out: + lbs_deb_leave_args(LBS_DEB_SCAN, "ret %d", ret); + return ret; +} + + +/** + * @brief Send a scan command for all available channels filtered on a spec + * + * Used in association code and from debugfs + * + * @param priv A pointer to struct lbs_private structure + * @param ssid A pointer to the SSID to scan for + * @param ssid_len Length of the SSID + * @param clear_ssid Should existing scan results with this SSID + * be cleared? + * + * @return 0-success, otherwise fail + */ +int lbs_send_specific_ssid_scan(struct lbs_private *priv, + u8 *ssid, u8 ssid_len, u8 clear_ssid) +{ + struct lbs_ioctl_user_scan_cfg scancfg; + int ret = 0; + + lbs_deb_enter_args(LBS_DEB_SCAN, "SSID '%s', clear %d", + escape_essid(ssid, ssid_len), clear_ssid); + + if (!ssid_len) + goto out; + + memset(&scancfg, 0x00, sizeof(scancfg)); + memcpy(scancfg.ssid, ssid, ssid_len); + scancfg.ssid_len = ssid_len; + scancfg.clear_ssid = clear_ssid; + + lbs_scan_networks(priv, &scancfg, 1); + if (priv->surpriseremoved) { + ret = -1; + goto out; + } + +out: + lbs_deb_leave_args(LBS_DEB_SCAN, "ret %d", ret); + return ret; +} + + + + +/*********************************************************************/ +/* */ +/* Support for Wireless Extensions */ +/* */ +/*********************************************************************/ + + +#define MAX_CUSTOM_LEN 64 + +static inline char *lbs_translate_scan(struct lbs_private *priv, + char *start, char *stop, + struct bss_descriptor *bss) +{ + struct chan_freq_power *cfp; + char *current_val; /* For rates */ + struct iw_event iwe; /* Temporary buffer */ + int j; +#define PERFECT_RSSI ((u8)50) +#define WORST_RSSI ((u8)0) +#define RSSI_DIFF ((u8)(PERFECT_RSSI - WORST_RSSI)) + u8 rssi; + + lbs_deb_enter(LBS_DEB_SCAN); + + cfp = lbs_find_cfp_by_band_and_channel(priv, 0, bss->channel); + if (!cfp) { + lbs_deb_scan("Invalid channel number %d\n", bss->channel); + start = NULL; + goto out; + } + + /* First entry *MUST* be the BSSID */ + iwe.cmd = SIOCGIWAP; + iwe.u.ap_addr.sa_family = ARPHRD_ETHER; + memcpy(iwe.u.ap_addr.sa_data, &bss->bssid, ETH_ALEN); + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_ADDR_LEN); + + /* SSID */ + iwe.cmd = SIOCGIWESSID; + iwe.u.data.flags = 1; + iwe.u.data.length = min((u32) bss->ssid_len, (u32) IW_ESSID_MAX_SIZE); + start = iwe_stream_add_point(start, stop, &iwe, bss->ssid); + + /* Mode */ + iwe.cmd = SIOCGIWMODE; + iwe.u.mode = bss->mode; + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_UINT_LEN); + + /* Frequency */ + iwe.cmd = SIOCGIWFREQ; + iwe.u.freq.m = (long)cfp->freq * 100000; + iwe.u.freq.e = 1; + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_FREQ_LEN); + + /* Add quality statistics */ + iwe.cmd = IWEVQUAL; + iwe.u.qual.updated = IW_QUAL_ALL_UPDATED; + iwe.u.qual.level = SCAN_RSSI(bss->rssi); + + rssi = iwe.u.qual.level - MRVDRV_NF_DEFAULT_SCAN_VALUE; + iwe.u.qual.qual = + (100 * RSSI_DIFF * RSSI_DIFF - (PERFECT_RSSI - rssi) * + (15 * (RSSI_DIFF) + 62 * (PERFECT_RSSI - rssi))) / + (RSSI_DIFF * RSSI_DIFF); + if (iwe.u.qual.qual > 100) + iwe.u.qual.qual = 100; + + if (priv->NF[TYPE_BEACON][TYPE_NOAVG] == 0) { + iwe.u.qual.noise = MRVDRV_NF_DEFAULT_SCAN_VALUE; + } else { + iwe.u.qual.noise = + CAL_NF(priv->NF[TYPE_BEACON][TYPE_NOAVG]); + } + + /* Locally created ad-hoc BSSs won't have beacons if this is the + * only station in the adhoc network; so get signal strength + * from receive statistics. + */ + if ((priv->mode == IW_MODE_ADHOC) + && priv->adhoccreate + && !lbs_ssid_cmp(priv->curbssparams.ssid, + priv->curbssparams.ssid_len, + bss->ssid, bss->ssid_len)) { + int snr, nf; + snr = priv->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE; + nf = priv->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE; + iwe.u.qual.level = CAL_RSSI(snr, nf); + } + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_QUAL_LEN); + + /* Add encryption capability */ + iwe.cmd = SIOCGIWENCODE; + if (bss->capability & WLAN_CAPABILITY_PRIVACY) { + iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; + } else { + iwe.u.data.flags = IW_ENCODE_DISABLED; + } + iwe.u.data.length = 0; + start = iwe_stream_add_point(start, stop, &iwe, bss->ssid); + + current_val = start + IW_EV_LCP_LEN; + + iwe.cmd = SIOCGIWRATE; + iwe.u.bitrate.fixed = 0; + iwe.u.bitrate.disabled = 0; + iwe.u.bitrate.value = 0; + + for (j = 0; bss->rates[j] && (j < sizeof(bss->rates)); j++) { + /* Bit rate given in 500 kb/s units */ + iwe.u.bitrate.value = bss->rates[j] * 500000; + current_val = iwe_stream_add_value(start, current_val, + stop, &iwe, IW_EV_PARAM_LEN); + } + if ((bss->mode == IW_MODE_ADHOC) + && !lbs_ssid_cmp(priv->curbssparams.ssid, + priv->curbssparams.ssid_len, + bss->ssid, bss->ssid_len) + && priv->adhoccreate) { + iwe.u.bitrate.value = 22 * 500000; + current_val = iwe_stream_add_value(start, current_val, + stop, &iwe, IW_EV_PARAM_LEN); + } + /* Check if we added any event */ + if((current_val - start) > IW_EV_LCP_LEN) + start = current_val; + + memset(&iwe, 0, sizeof(iwe)); + if (bss->wpa_ie_len) { + char buf[MAX_WPA_IE_LEN]; + memcpy(buf, bss->wpa_ie, bss->wpa_ie_len); + iwe.cmd = IWEVGENIE; + iwe.u.data.length = bss->wpa_ie_len; + start = iwe_stream_add_point(start, stop, &iwe, buf); + } + + memset(&iwe, 0, sizeof(iwe)); + if (bss->rsn_ie_len) { + char buf[MAX_WPA_IE_LEN]; + memcpy(buf, bss->rsn_ie, bss->rsn_ie_len); + iwe.cmd = IWEVGENIE; + iwe.u.data.length = bss->rsn_ie_len; + start = iwe_stream_add_point(start, stop, &iwe, buf); + } + + if (bss->mesh) { + char custom[MAX_CUSTOM_LEN]; + char *p = custom; + + iwe.cmd = IWEVCUSTOM; + p += snprintf(p, MAX_CUSTOM_LEN - (p - custom), + "mesh-type: olpc"); + iwe.u.data.length = p - custom; + if (iwe.u.data.length) + start = iwe_stream_add_point(start, stop, &iwe, custom); + } + +out: + lbs_deb_leave_args(LBS_DEB_SCAN, "start %p", start); + return start; +} + + +/** + * @brief Handle Scan Network ioctl + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * + * @return 0 --success, otherwise fail + */ +int lbs_set_scan(struct net_device *dev, struct iw_request_info *info, + struct iw_param *wrqu, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_SCAN); + + if (!netif_running(dev)) + return -ENETDOWN; + + /* mac80211 does this: + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + if (sdata->type != IEEE80211_IF_TYPE_xxx) + return -EOPNOTSUPP; + + if (wrqu->data.length == sizeof(struct iw_scan_req) && + wrqu->data.flags & IW_SCAN_THIS_ESSID) { + req = (struct iw_scan_req *)extra; + ssid = req->essid; + ssid_len = req->essid_len; + } + */ + + if (!delayed_work_pending(&priv->scan_work)) + queue_delayed_work(priv->work_thread, &priv->scan_work, + msecs_to_jiffies(50)); + /* set marker that currently a scan is taking place */ + priv->last_scanned_channel = -1; + + if (priv->surpriseremoved) + return -EIO; + + lbs_deb_leave(LBS_DEB_SCAN); + return 0; +} + + +/** + * @brief Handle Retrieve scan table ioctl + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param dwrq A pointer to iw_point structure + * @param extra A pointer to extra data buf + * + * @return 0 --success, otherwise fail + */ +int lbs_get_scan(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ +#define SCAN_ITEM_SIZE 128 + struct lbs_private *priv = dev->priv; + int err = 0; + char *ev = extra; + char *stop = ev + dwrq->length; + struct bss_descriptor * iter_bss; + struct bss_descriptor * safe; + + lbs_deb_enter(LBS_DEB_SCAN); + + /* iwlist should wait until the current scan is finished */ + if (priv->last_scanned_channel) + return -EAGAIN; + + /* Update RSSI if current BSS is a locally created ad-hoc BSS */ + if ((priv->mode == IW_MODE_ADHOC) && priv->adhoccreate) { + lbs_prepare_and_send_command(priv, CMD_802_11_RSSI, 0, + CMD_OPTION_WAITFORRSP, 0, NULL); + } + + mutex_lock(&priv->lock); + list_for_each_entry_safe (iter_bss, safe, &priv->network_list, list) { + char * next_ev; + unsigned long stale_time; + + if (stop - ev < SCAN_ITEM_SIZE) { + err = -E2BIG; + break; + } + + /* For mesh device, list only mesh networks */ + if (dev == priv->mesh_dev && !iter_bss->mesh) + continue; + + /* Prune old an old scan result */ + stale_time = iter_bss->last_scanned + DEFAULT_MAX_SCAN_AGE; + if (time_after(jiffies, stale_time)) { + list_move_tail (&iter_bss->list, + &priv->network_free_list); + clear_bss_descriptor(iter_bss); + continue; + } + + /* Translate to WE format this entry */ + next_ev = lbs_translate_scan(priv, ev, stop, iter_bss); + if (next_ev == NULL) + continue; + ev = next_ev; + } + mutex_unlock(&priv->lock); + + dwrq->length = (ev - extra); + dwrq->flags = 0; + + lbs_deb_leave_args(LBS_DEB_SCAN, "ret %d", err); + return err; +} + + + + +/*********************************************************************/ +/* */ +/* Command execution */ +/* */ +/*********************************************************************/ + + +/** + * @brief Prepare a scan command to be sent to the firmware + * + * Called via lbs_prepare_and_send_command(priv, CMD_802_11_SCAN, ...) + * from cmd.c + * + * Sends a fixed lenght data part (specifying the BSS type and BSSID filters) + * as well as a variable number/length of TLVs to the firmware. + * + * @param priv A pointer to struct lbs_private structure + * @param cmd A pointer to cmd_ds_command structure to be sent to + * firmware with the cmd_DS_801_11_SCAN structure + * @param pdata_buf Void pointer cast of a lbs_scan_cmd_config struct used + * to set the fields/TLVs for the command sent to firmware + * + * @return 0 or -1 + */ +int lbs_cmd_80211_scan(struct lbs_private *priv, + struct cmd_ds_command *cmd, void *pdata_buf) +{ + struct cmd_ds_802_11_scan *pscan = &cmd->params.scan; + struct lbs_scan_cmd_config *pscancfg = pdata_buf; + + lbs_deb_enter(LBS_DEB_SCAN); + + /* Set fixed field variables in scan command */ + pscan->bsstype = pscancfg->bsstype; + memcpy(pscan->bssid, pscancfg->bssid, ETH_ALEN); + memcpy(pscan->tlvbuffer, pscancfg->tlvbuffer, pscancfg->tlvbufferlen); + + /* size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->size = cpu_to_le16(sizeof(pscan->bsstype) + ETH_ALEN + + pscancfg->tlvbufferlen + S_DS_GEN); + + lbs_deb_leave(LBS_DEB_SCAN); + return 0; +} + +/** + * @brief This function handles the command response of scan + * + * Called from handle_cmd_response() in cmdrespc. + * + * The response buffer for the scan command has the following + * memory layout: + * + * .-----------------------------------------------------------. + * | header (4 * sizeof(u16)): Standard command response hdr | + * .-----------------------------------------------------------. + * | bufsize (u16) : sizeof the BSS Description data | + * .-----------------------------------------------------------. + * | NumOfSet (u8) : Number of BSS Descs returned | + * .-----------------------------------------------------------. + * | BSSDescription data (variable, size given in bufsize) | + * .-----------------------------------------------------------. + * | TLV data (variable, size calculated using header->size, | + * | bufsize and sizeof the fixed fields above) | + * .-----------------------------------------------------------. + * + * @param priv A pointer to struct lbs_private structure + * @param resp A pointer to cmd_ds_command + * + * @return 0 or -1 + */ +int lbs_ret_80211_scan(struct lbs_private *priv, struct cmd_ds_command *resp) +{ + struct cmd_ds_802_11_scan_rsp *pscan; + struct bss_descriptor * iter_bss; + struct bss_descriptor * safe; + u8 *pbssinfo; + u16 scanrespsize; + int bytesleft; + int idx; + int tlvbufsize; + int ret; + + lbs_deb_enter(LBS_DEB_SCAN); + + /* Prune old entries from scan table */ + list_for_each_entry_safe (iter_bss, safe, &priv->network_list, list) { + unsigned long stale_time = iter_bss->last_scanned + DEFAULT_MAX_SCAN_AGE; + if (time_before(jiffies, stale_time)) + continue; + list_move_tail (&iter_bss->list, &priv->network_free_list); + clear_bss_descriptor(iter_bss); + } + + pscan = &resp->params.scanresp; + + if (pscan->nr_sets > MAX_NETWORK_COUNT) { + lbs_deb_scan( + "SCAN_RESP: too many scan results (%d, max %d)!!\n", + pscan->nr_sets, MAX_NETWORK_COUNT); + ret = -1; + goto done; + } + + bytesleft = le16_to_cpu(pscan->bssdescriptsize); + lbs_deb_scan("SCAN_RESP: bssdescriptsize %d\n", bytesleft); + + scanrespsize = le16_to_cpu(resp->size); + lbs_deb_scan("SCAN_RESP: scan results %d\n", pscan->nr_sets); + + pbssinfo = pscan->bssdesc_and_tlvbuffer; + + /* The size of the TLV buffer is equal to the entire command response + * size (scanrespsize) minus the fixed fields (sizeof()'s), the + * BSS Descriptions (bssdescriptsize as bytesLef) and the command + * response header (S_DS_GEN) + */ + tlvbufsize = scanrespsize - (bytesleft + sizeof(pscan->bssdescriptsize) + + sizeof(pscan->nr_sets) + + S_DS_GEN); + + /* + * Process each scan response returned (pscan->nr_sets). Save + * the information in the newbssentry and then insert into the + * driver scan table either as an update to an existing entry + * or as an addition at the end of the table + */ + for (idx = 0; idx < pscan->nr_sets && bytesleft; idx++) { + struct bss_descriptor new; + struct bss_descriptor * found = NULL; + struct bss_descriptor * oldest = NULL; + DECLARE_MAC_BUF(mac); + + /* Process the data fields and IEs returned for this BSS */ + memset(&new, 0, sizeof (struct bss_descriptor)); + if (lbs_process_bss(&new, &pbssinfo, &bytesleft) != 0) { + /* error parsing the scan response, skipped */ + lbs_deb_scan("SCAN_RESP: process_bss returned ERROR\n"); + continue; + } + + /* Try to find this bss in the scan table */ + list_for_each_entry (iter_bss, &priv->network_list, list) { + if (is_same_network(iter_bss, &new)) { + found = iter_bss; + break; + } + + if ((oldest == NULL) || + (iter_bss->last_scanned < oldest->last_scanned)) + oldest = iter_bss; + } + + if (found) { + /* found, clear it */ + clear_bss_descriptor(found); + } else if (!list_empty(&priv->network_free_list)) { + /* Pull one from the free list */ + found = list_entry(priv->network_free_list.next, + struct bss_descriptor, list); + list_move_tail(&found->list, &priv->network_list); + } else if (oldest) { + /* If there are no more slots, expire the oldest */ + found = oldest; + clear_bss_descriptor(found); + list_move_tail(&found->list, &priv->network_list); + } else { + continue; + } + + lbs_deb_scan("SCAN_RESP: BSSID %s\n", + print_mac(mac, new.bssid)); + + /* Copy the locally created newbssentry to the scan table */ + memcpy(found, &new, offsetof(struct bss_descriptor, list)); + } + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_SCAN, "ret %d", ret); + return ret; +} diff --git a/package/libertas/src/scan.h b/package/libertas/src/scan.h new file mode 100644 index 0000000000..319f70dde3 --- /dev/null +++ b/package/libertas/src/scan.h @@ -0,0 +1,205 @@ +/** + * Interface for the wlan network scan routines + * + * Driver interface functions and type declarations for the scan module + * implemented in scan.c. + */ +#ifndef _LBS_SCAN_H +#define _LBS_SCAN_H + +#include <net/ieee80211.h> +#include "hostcmd.h" + +/** + * @brief Maximum number of channels that can be sent in a setuserscan ioctl + * + * @sa lbs_ioctl_user_scan_cfg + */ +#define LBS_IOCTL_USER_SCAN_CHAN_MAX 50 + +//! Infrastructure BSS scan type in lbs_scan_cmd_config +#define LBS_SCAN_BSS_TYPE_BSS 1 + +//! Adhoc BSS scan type in lbs_scan_cmd_config +#define LBS_SCAN_BSS_TYPE_IBSS 2 + +//! Adhoc or Infrastructure BSS scan type in lbs_scan_cmd_config, no filter +#define LBS_SCAN_BSS_TYPE_ANY 3 + +/** + * @brief Structure used internally in the wlan driver to configure a scan. + * + * Sent to the command processing module to configure the firmware + * scan command prepared by lbs_cmd_80211_scan. + * + * @sa lbs_scan_networks + * + */ +struct lbs_scan_cmd_config { + /** + * @brief BSS type to be sent in the firmware command + * + * Field can be used to restrict the types of networks returned in the + * scan. valid settings are: + * + * - LBS_SCAN_BSS_TYPE_BSS (infrastructure) + * - LBS_SCAN_BSS_TYPE_IBSS (adhoc) + * - LBS_SCAN_BSS_TYPE_ANY (unrestricted, adhoc and infrastructure) + */ + u8 bsstype; + + /** + * @brief Specific BSSID used to filter scan results in the firmware + */ + u8 bssid[ETH_ALEN]; + + /** + * @brief length of TLVs sent in command starting at tlvBuffer + */ + int tlvbufferlen; + + /** + * @brief SSID TLV(s) and ChanList TLVs to be sent in the firmware command + * + * @sa TLV_TYPE_CHANLIST, mrvlietypes_chanlistparamset_t + * @sa TLV_TYPE_SSID, mrvlietypes_ssidparamset_t + */ + u8 tlvbuffer[1]; //!< SSID TLV(s) and ChanList TLVs are stored here +}; + +/** + * @brief IOCTL channel sub-structure sent in lbs_ioctl_user_scan_cfg + * + * Multiple instances of this structure are included in the IOCTL command + * to configure a instance of a scan on the specific channel. + */ +struct lbs_ioctl_user_scan_chan { + u8 channumber; //!< channel Number to scan + u8 radiotype; //!< Radio type: 'B/G' band = 0, 'A' band = 1 + u8 scantype; //!< Scan type: Active = 0, Passive = 1 + u16 scantime; //!< Scan duration in milliseconds; if 0 default used +}; + +/** + * @brief IOCTL input structure to configure an immediate scan cmd to firmware + * + * Used in the setuserscan (LBS_SET_USER_SCAN) private ioctl. Specifies + * a number of parameters to be used in general for the scan as well + * as a channel list (lbs_ioctl_user_scan_chan) for each scan period + * desired. + * + * @sa lbs_set_user_scan_ioctl + */ +struct lbs_ioctl_user_scan_cfg { + /** + * @brief BSS type to be sent in the firmware command + * + * Field can be used to restrict the types of networks returned in the + * scan. valid settings are: + * + * - LBS_SCAN_BSS_TYPE_BSS (infrastructure) + * - LBS_SCAN_BSS_TYPE_IBSS (adhoc) + * - LBS_SCAN_BSS_TYPE_ANY (unrestricted, adhoc and infrastructure) + */ + u8 bsstype; + + /** + * @brief BSSID filter sent in the firmware command to limit the results + */ + u8 bssid[ETH_ALEN]; + + /* Clear existing scan results matching this BSSID */ + u8 clear_bssid; + + /** + * @brief SSID filter sent in the firmware command to limit the results + */ + char ssid[IW_ESSID_MAX_SIZE]; + u8 ssid_len; + + /* Clear existing scan results matching this SSID */ + u8 clear_ssid; +}; + +/** + * @brief Structure used to store information for each beacon/probe response + */ +struct bss_descriptor { + u8 bssid[ETH_ALEN]; + + u8 ssid[IW_ESSID_MAX_SIZE + 1]; + u8 ssid_len; + + u16 capability; + + /* receive signal strength in dBm */ + long rssi; + + u32 channel; + + u16 beaconperiod; + + u32 atimwindow; + + /* IW_MODE_AUTO, IW_MODE_ADHOC, IW_MODE_INFRA */ + u8 mode; + + /* zero-terminated array of supported data rates */ + u8 rates[MAX_RATES + 1]; + + unsigned long last_scanned; + + union ieeetypes_phyparamset phyparamset; + union IEEEtypes_ssparamset ssparamset; + + struct ieeetypes_countryinfofullset countryinfo; + + u8 wpa_ie[MAX_WPA_IE_LEN]; + size_t wpa_ie_len; + u8 rsn_ie[MAX_WPA_IE_LEN]; + size_t rsn_ie_len; + + u8 mesh; + + struct list_head list; +}; + +int lbs_ssid_cmp(u8 *ssid1, u8 ssid1_len, u8 *ssid2, u8 ssid2_len); + +struct bss_descriptor *lbs_find_ssid_in_list(struct lbs_private *priv, + u8 *ssid, u8 ssid_len, u8 *bssid, u8 mode, + int channel); + +struct bss_descriptor *lbs_find_bssid_in_list(struct lbs_private *priv, + u8 *bssid, u8 mode); + +int lbs_find_best_network_ssid(struct lbs_private *priv, u8 *out_ssid, + u8 *out_ssid_len, u8 preferred_mode, u8 *out_mode); + +int lbs_send_specific_ssid_scan(struct lbs_private *priv, u8 *ssid, + u8 ssid_len, u8 clear_ssid); + +int lbs_cmd_80211_scan(struct lbs_private *priv, + struct cmd_ds_command *cmd, + void *pdata_buf); + +int lbs_ret_80211_scan(struct lbs_private *priv, + struct cmd_ds_command *resp); + +int lbs_scan_networks(struct lbs_private *priv, + const struct lbs_ioctl_user_scan_cfg *puserscanin, + int full_scan); + +struct ifreq; + +struct iw_point; +struct iw_param; +struct iw_request_info; +int lbs_get_scan(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra); +int lbs_set_scan(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra); + +void lbs_scan_worker(struct work_struct *work); + +#endif diff --git a/package/libertas/src/tx.c b/package/libertas/src/tx.c new file mode 100644 index 0000000000..8a1a3965f1 --- /dev/null +++ b/package/libertas/src/tx.c @@ -0,0 +1,221 @@ +/** + * This file contains the handling of TX in wlan driver. + */ +#include <linux/netdevice.h> +#include <linux/etherdevice.h> + +#include "hostcmd.h" +#include "radiotap.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "wext.h" + +/** + * @brief This function converts Tx/Rx rates from IEEE80211_RADIOTAP_RATE + * units (500 Kb/s) into Marvell WLAN format (see Table 8 in Section 3.2.1) + * + * @param rate Input rate + * @return Output Rate (0 if invalid) + */ +static u32 convert_radiotap_rate_to_mv(u8 rate) +{ + switch (rate) { + case 2: /* 1 Mbps */ + return 0 | (1 << 4); + case 4: /* 2 Mbps */ + return 1 | (1 << 4); + case 11: /* 5.5 Mbps */ + return 2 | (1 << 4); + case 22: /* 11 Mbps */ + return 3 | (1 << 4); + case 12: /* 6 Mbps */ + return 4 | (1 << 4); + case 18: /* 9 Mbps */ + return 5 | (1 << 4); + case 24: /* 12 Mbps */ + return 6 | (1 << 4); + case 36: /* 18 Mbps */ + return 7 | (1 << 4); + case 48: /* 24 Mbps */ + return 8 | (1 << 4); + case 72: /* 36 Mbps */ + return 9 | (1 << 4); + case 96: /* 48 Mbps */ + return 10 | (1 << 4); + case 108: /* 54 Mbps */ + return 11 | (1 << 4); + } + return 0; +} + +/** + * @brief This function checks the conditions and sends packet to IF + * layer if everything is ok. + * + * @param priv A pointer to struct lbs_private structure + * @param skb A pointer to skb which includes TX packet + * @return 0 or -1 + */ +int lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + unsigned long flags; + struct lbs_private *priv = dev->priv; + struct txpd *txpd; + char *p802x_hdr; + uint16_t pkt_len; + int ret; + + lbs_deb_enter(LBS_DEB_TX); + + ret = NETDEV_TX_OK; + + /* We need to protect against the queues being restarted before + we get round to stopping them */ + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->surpriseremoved) + goto free; + + if (!skb->len || (skb->len > MRVDRV_ETH_TX_PACKET_BUFFER_SIZE)) { + lbs_deb_tx("tx err: skb length %d 0 or > %zd\n", + skb->len, MRVDRV_ETH_TX_PACKET_BUFFER_SIZE); + /* We'll never manage to send this one; drop it and return 'OK' */ + + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + goto free; + } + + + netif_stop_queue(priv->dev); + if (priv->mesh_dev) + netif_stop_queue(priv->mesh_dev); + + if (priv->tx_pending_len) { + /* This can happen if packets come in on the mesh and eth + device simultaneously -- there's no mutual exclusion on + hard_start_xmit() calls between devices. */ + lbs_deb_tx("Packet on %s while busy\n", dev->name); + ret = NETDEV_TX_BUSY; + goto unlock; + } + + priv->tx_pending_len = -1; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbs_deb_hex(LBS_DEB_TX, "TX Data", skb->data, min_t(unsigned int, skb->len, 100)); + + txpd = (void *)priv->tx_pending_buf; + memset(txpd, 0, sizeof(struct txpd)); + + p802x_hdr = skb->data; + pkt_len = skb->len; + + if (dev == priv->rtap_net_dev) { + struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; + + /* set txpd fields from the radiotap header */ + txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate)); + + /* skip the radiotap header */ + p802x_hdr += sizeof(*rtap_hdr); + pkt_len -= sizeof(*rtap_hdr); + + /* copy destination address from 802.11 header */ + memcpy(txpd->tx_dest_addr_high, p802x_hdr + 4, ETH_ALEN); + } else { + /* copy destination address from 802.3 header */ + memcpy(txpd->tx_dest_addr_high, p802x_hdr, ETH_ALEN); + } + + txpd->tx_packet_length = cpu_to_le16(pkt_len); + txpd->tx_packet_location = cpu_to_le32(sizeof(struct txpd)); + + if (dev == priv->mesh_dev) + txpd->tx_control |= cpu_to_le32(TxPD_MESH_FRAME); + + lbs_deb_hex(LBS_DEB_TX, "txpd", (u8 *) &txpd, sizeof(struct txpd)); + + lbs_deb_hex(LBS_DEB_TX, "Tx Data", (u8 *) p802x_hdr, le16_to_cpu(txpd->tx_packet_length)); + + memcpy(&txpd[1], p802x_hdr, le16_to_cpu(txpd->tx_packet_length)); + + spin_lock_irqsave(&priv->driver_lock, flags); + priv->tx_pending_len = pkt_len + sizeof(struct txpd); + + lbs_deb_tx("%s lined up packet\n", __func__); + + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + + dev->trans_start = jiffies; + + if (priv->monitormode != LBS_MONITOR_OFF) { + /* Keep the skb to echo it back once Tx feedback is + received from FW */ + skb_orphan(skb); + + /* Keep the skb around for when we get feedback */ + priv->currenttxskb = skb; + } else { + free: + dev_kfree_skb_any(skb); + } + unlock: + spin_unlock_irqrestore(&priv->driver_lock, flags); + wake_up(&priv->waitq); + + lbs_deb_leave_args(LBS_DEB_TX, "ret %d", ret); + return ret; +} + +/** + * @brief This function sends to the host the last transmitted packet, + * filling the radiotap headers with transmission information. + * + * @param priv A pointer to struct lbs_private structure + * @param status A 32 bit value containing transmission status. + * + * @returns void + */ +void lbs_send_tx_feedback(struct lbs_private *priv) +{ + struct tx_radiotap_hdr *radiotap_hdr; + u32 status = priv->eventcause; + int txfail; + int try_count; + + if (priv->monitormode == LBS_MONITOR_OFF || + priv->currenttxskb == NULL) + return; + + radiotap_hdr = (struct tx_radiotap_hdr *)priv->currenttxskb->data; + + txfail = (status >> 24); + +#if 0 + /* The version of roofnet that we've tested does not use this yet + * But it may be used in the future. + */ + if (txfail) + radiotap_hdr->flags &= IEEE80211_RADIOTAP_F_TX_FAIL; +#endif + try_count = (status >> 16) & 0xff; + radiotap_hdr->data_retries = (try_count) ? + (1 + priv->txretrycount - try_count) : 0; + + + priv->currenttxskb->protocol = eth_type_trans(priv->currenttxskb, + priv->rtap_net_dev); + netif_rx(priv->currenttxskb); + + priv->currenttxskb = NULL; + + if (priv->connect_status == LBS_CONNECTED) + netif_wake_queue(priv->dev); + + if (priv->mesh_dev && (priv->mesh_connect_status == LBS_CONNECTED)) + netif_wake_queue(priv->mesh_dev); +} +EXPORT_SYMBOL_GPL(lbs_send_tx_feedback); diff --git a/package/libertas/src/types.h b/package/libertas/src/types.h new file mode 100644 index 0000000000..f0d57958b3 --- /dev/null +++ b/package/libertas/src/types.h @@ -0,0 +1,242 @@ +/** + * This header file contains definition for global types + */ +#ifndef _LBS_TYPES_H_ +#define _LBS_TYPES_H_ + +#include <linux/if_ether.h> +#include <asm/byteorder.h> + +struct ieeetypes_cfparamset { + u8 elementid; + u8 len; + u8 cfpcnt; + u8 cfpperiod; + __le16 cfpmaxduration; + __le16 cfpdurationremaining; +} __attribute__ ((packed)); + + +struct ieeetypes_ibssparamset { + u8 elementid; + u8 len; + __le16 atimwindow; +} __attribute__ ((packed)); + +union IEEEtypes_ssparamset { + struct ieeetypes_cfparamset cfparamset; + struct ieeetypes_ibssparamset ibssparamset; +} __attribute__ ((packed)); + +struct ieeetypes_fhparamset { + u8 elementid; + u8 len; + __le16 dwelltime; + u8 hopset; + u8 hoppattern; + u8 hopindex; +} __attribute__ ((packed)); + +struct ieeetypes_dsparamset { + u8 elementid; + u8 len; + u8 currentchan; +} __attribute__ ((packed)); + +union ieeetypes_phyparamset { + struct ieeetypes_fhparamset fhparamset; + struct ieeetypes_dsparamset dsparamset; +} __attribute__ ((packed)); + +struct ieeetypes_assocrsp { + __le16 capability; + __le16 statuscode; + __le16 aid; + u8 iebuffer[1]; +} __attribute__ ((packed)); + +/** TLV type ID definition */ +#define PROPRIETARY_TLV_BASE_ID 0x0100 + +/* Terminating TLV type */ +#define MRVL_TERMINATE_TLV_ID 0xffff + +#define TLV_TYPE_SSID 0x0000 +#define TLV_TYPE_RATES 0x0001 +#define TLV_TYPE_PHY_FH 0x0002 +#define TLV_TYPE_PHY_DS 0x0003 +#define TLV_TYPE_CF 0x0004 +#define TLV_TYPE_IBSS 0x0006 + +#define TLV_TYPE_DOMAIN 0x0007 + +#define TLV_TYPE_POWER_CAPABILITY 0x0021 + +#define TLV_TYPE_KEY_MATERIAL (PROPRIETARY_TLV_BASE_ID + 0) +#define TLV_TYPE_CHANLIST (PROPRIETARY_TLV_BASE_ID + 1) +#define TLV_TYPE_NUMPROBES (PROPRIETARY_TLV_BASE_ID + 2) +#define TLV_TYPE_RSSI_LOW (PROPRIETARY_TLV_BASE_ID + 4) +#define TLV_TYPE_SNR_LOW (PROPRIETARY_TLV_BASE_ID + 5) +#define TLV_TYPE_FAILCOUNT (PROPRIETARY_TLV_BASE_ID + 6) +#define TLV_TYPE_BCNMISS (PROPRIETARY_TLV_BASE_ID + 7) +#define TLV_TYPE_LED_GPIO (PROPRIETARY_TLV_BASE_ID + 8) +#define TLV_TYPE_LEDBEHAVIOR (PROPRIETARY_TLV_BASE_ID + 9) +#define TLV_TYPE_PASSTHROUGH (PROPRIETARY_TLV_BASE_ID + 10) +#define TLV_TYPE_REASSOCAP (PROPRIETARY_TLV_BASE_ID + 11) +#define TLV_TYPE_POWER_TBL_2_4GHZ (PROPRIETARY_TLV_BASE_ID + 12) +#define TLV_TYPE_POWER_TBL_5GHZ (PROPRIETARY_TLV_BASE_ID + 13) +#define TLV_TYPE_BCASTPROBE (PROPRIETARY_TLV_BASE_ID + 14) +#define TLV_TYPE_NUMSSID_PROBE (PROPRIETARY_TLV_BASE_ID + 15) +#define TLV_TYPE_WMMQSTATUS (PROPRIETARY_TLV_BASE_ID + 16) +#define TLV_TYPE_CRYPTO_DATA (PROPRIETARY_TLV_BASE_ID + 17) +#define TLV_TYPE_WILDCARDSSID (PROPRIETARY_TLV_BASE_ID + 18) +#define TLV_TYPE_TSFTIMESTAMP (PROPRIETARY_TLV_BASE_ID + 19) +#define TLV_TYPE_RSSI_HIGH (PROPRIETARY_TLV_BASE_ID + 22) +#define TLV_TYPE_SNR_HIGH (PROPRIETARY_TLV_BASE_ID + 23) + +/** TLV related data structures*/ +struct mrvlietypesheader { + __le16 type; + __le16 len; +} __attribute__ ((packed)); + +struct mrvlietypes_data { + struct mrvlietypesheader header; + u8 Data[1]; +} __attribute__ ((packed)); + +struct mrvlietypes_ratesparamset { + struct mrvlietypesheader header; + u8 rates[1]; +} __attribute__ ((packed)); + +struct mrvlietypes_ssidparamset { + struct mrvlietypesheader header; + u8 ssid[1]; +} __attribute__ ((packed)); + +struct mrvlietypes_wildcardssidparamset { + struct mrvlietypesheader header; + u8 MaxSsidlength; + u8 ssid[1]; +} __attribute__ ((packed)); + +struct chanscanmode { +#ifdef __BIG_ENDIAN_BITFIELD + u8 reserved_2_7:6; + u8 disablechanfilt:1; + u8 passivescan:1; +#else + u8 passivescan:1; + u8 disablechanfilt:1; + u8 reserved_2_7:6; +#endif +} __attribute__ ((packed)); + +struct chanscanparamset { + u8 radiotype; + u8 channumber; + struct chanscanmode chanscanmode; + __le16 minscantime; + __le16 maxscantime; +} __attribute__ ((packed)); + +struct mrvlietypes_chanlistparamset { + struct mrvlietypesheader header; + struct chanscanparamset chanscanparam[1]; +} __attribute__ ((packed)); + +struct cfparamset { + u8 cfpcnt; + u8 cfpperiod; + __le16 cfpmaxduration; + __le16 cfpdurationremaining; +} __attribute__ ((packed)); + +struct ibssparamset { + __le16 atimwindow; +} __attribute__ ((packed)); + +struct mrvlietypes_ssparamset { + struct mrvlietypesheader header; + union { + struct cfparamset cfparamset[1]; + struct ibssparamset ibssparamset[1]; + } cf_ibss; +} __attribute__ ((packed)); + +struct fhparamset { + __le16 dwelltime; + u8 hopset; + u8 hoppattern; + u8 hopindex; +} __attribute__ ((packed)); + +struct dsparamset { + u8 currentchan; +} __attribute__ ((packed)); + +struct mrvlietypes_phyparamset { + struct mrvlietypesheader header; + union { + struct fhparamset fhparamset[1]; + struct dsparamset dsparamset[1]; + } fh_ds; +} __attribute__ ((packed)); + +struct mrvlietypes_rsnparamset { + struct mrvlietypesheader header; + u8 rsnie[1]; +} __attribute__ ((packed)); + +struct mrvlietypes_tsftimestamp { + struct mrvlietypesheader header; + __le64 tsftable[1]; +} __attribute__ ((packed)); + +/** Local Power capability */ +struct mrvlietypes_powercapability { + struct mrvlietypesheader header; + s8 minpower; + s8 maxpower; +} __attribute__ ((packed)); + +/* used in CMD_802_11_SUBSCRIBE_EVENT for SNR, RSSI and Failure */ +struct mrvlietypes_thresholds { + struct mrvlietypesheader header; + u8 value; + u8 freq; +} __attribute__ ((packed)); + +struct mrvlietypes_beaconsmissed { + struct mrvlietypesheader header; + u8 beaconmissed; + u8 reserved; +} __attribute__ ((packed)); + +struct mrvlietypes_numprobes { + struct mrvlietypesheader header; + __le16 numprobes; +} __attribute__ ((packed)); + +struct mrvlietypes_bcastprobe { + struct mrvlietypesheader header; + __le16 bcastprobe; +} __attribute__ ((packed)); + +struct mrvlietypes_numssidprobe { + struct mrvlietypesheader header; + __le16 numssidprobe; +} __attribute__ ((packed)); + +struct led_pin { + u8 led; + u8 pin; +} __attribute__ ((packed)); + +struct mrvlietypes_ledgpio { + struct mrvlietypesheader header; + struct led_pin ledpin[1]; +} __attribute__ ((packed)); + +#endif diff --git a/package/libertas/src/wext.c b/package/libertas/src/wext.c new file mode 100644 index 0000000000..262d4cc580 --- /dev/null +++ b/package/libertas/src/wext.c @@ -0,0 +1,2213 @@ +/** + * This file contains ioctl functions + */ +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/if.h> +#include <linux/if_arp.h> +#include <linux/wireless.h> +#include <linux/bitops.h> + +#include <net/ieee80211.h> +#include <net/iw_handler.h> + +#include "host.h" +#include "radiotap.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "join.h" +#include "wext.h" +#include "assoc.h" +#include "cmd.h" + + +static inline void lbs_postpone_association_work(struct lbs_private *priv) +{ + if (priv->surpriseremoved) + return; + cancel_delayed_work(&priv->assoc_work); + queue_delayed_work(priv->work_thread, &priv->assoc_work, HZ / 2); +} + +static inline void lbs_cancel_association_work(struct lbs_private *priv) +{ + cancel_delayed_work(&priv->assoc_work); + kfree(priv->pending_assoc_req); + priv->pending_assoc_req = NULL; +} + + +/** + * @brief Find the channel frequency power info with specific channel + * + * @param priv A pointer to struct lbs_private structure + * @param band it can be BAND_A, BAND_G or BAND_B + * @param channel the channel for looking + * @return A pointer to struct chan_freq_power structure or NULL if not find. + */ +struct chan_freq_power *lbs_find_cfp_by_band_and_channel( + struct lbs_private *priv, + u8 band, + u16 channel) +{ + struct chan_freq_power *cfp = NULL; + struct region_channel *rc; + int i, j; + + for (j = 0; !cfp && (j < ARRAY_SIZE(priv->region_channel)); j++) { + rc = &priv->region_channel[j]; + + if (priv->enable11d) + rc = &priv->universal_channel[j]; + if (!rc->valid || !rc->CFP) + continue; + if (rc->band != band) + continue; + for (i = 0; i < rc->nrcfp; i++) { + if (rc->CFP[i].channel == channel) { + cfp = &rc->CFP[i]; + break; + } + } + } + + if (!cfp && channel) + lbs_deb_wext("lbs_find_cfp_by_band_and_channel: can't find " + "cfp by band %d / channel %d\n", band, channel); + + return cfp; +} + +/** + * @brief Find the channel frequency power info with specific frequency + * + * @param priv A pointer to struct lbs_private structure + * @param band it can be BAND_A, BAND_G or BAND_B + * @param freq the frequency for looking + * @return A pointer to struct chan_freq_power structure or NULL if not find. + */ +static struct chan_freq_power *find_cfp_by_band_and_freq( + struct lbs_private *priv, + u8 band, + u32 freq) +{ + struct chan_freq_power *cfp = NULL; + struct region_channel *rc; + int i, j; + + for (j = 0; !cfp && (j < ARRAY_SIZE(priv->region_channel)); j++) { + rc = &priv->region_channel[j]; + + if (priv->enable11d) + rc = &priv->universal_channel[j]; + if (!rc->valid || !rc->CFP) + continue; + if (rc->band != band) + continue; + for (i = 0; i < rc->nrcfp; i++) { + if (rc->CFP[i].freq == freq) { + cfp = &rc->CFP[i]; + break; + } + } + } + + if (!cfp && freq) + lbs_deb_wext("find_cfp_by_band_and_freql: can't find cfp by " + "band %d / freq %d\n", band, freq); + + return cfp; +} + + +/** + * @brief Set Radio On/OFF + * + * @param priv A pointer to struct lbs_private structure + * @option Radio Option + * @return 0 --success, otherwise fail + */ +static int lbs_radio_ioctl(struct lbs_private *priv, u8 option) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_WEXT); + + if (priv->radioon != option) { + lbs_deb_wext("switching radio %s\n", option ? "on" : "off"); + priv->radioon = option; + + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_RADIO_CONTROL, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, 0, NULL); + } + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +/** + * @brief Copy active data rates based on adapter mode and status + * + * @param priv A pointer to struct lbs_private structure + * @param rate The buf to return the active rates + */ +static void copy_active_data_rates(struct lbs_private *priv, u8 *rates) +{ + lbs_deb_enter(LBS_DEB_WEXT); + + if ((priv->connect_status != LBS_CONNECTED) && + (priv->mesh_connect_status != LBS_CONNECTED)) + memcpy(rates, lbs_bg_rates, MAX_RATES); + else + memcpy(rates, priv->curbssparams.rates, MAX_RATES); + + lbs_deb_leave(LBS_DEB_WEXT); +} + +static int lbs_get_name(struct net_device *dev, struct iw_request_info *info, + char *cwrq, char *extra) +{ + + lbs_deb_enter(LBS_DEB_WEXT); + + /* We could add support for 802.11n here as needed. Jean II */ + snprintf(cwrq, IFNAMSIZ, "IEEE 802.11b/g"); + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_get_freq(struct net_device *dev, struct iw_request_info *info, + struct iw_freq *fwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + struct chan_freq_power *cfp; + + lbs_deb_enter(LBS_DEB_WEXT); + + cfp = lbs_find_cfp_by_band_and_channel(priv, 0, + priv->curbssparams.channel); + + if (!cfp) { + if (priv->curbssparams.channel) + lbs_deb_wext("invalid channel %d\n", + priv->curbssparams.channel); + return -EINVAL; + } + + fwrq->m = (long)cfp->freq * 100000; + fwrq->e = 1; + + lbs_deb_wext("freq %u\n", fwrq->m); + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_get_wap(struct net_device *dev, struct iw_request_info *info, + struct sockaddr *awrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + if (priv->connect_status == LBS_CONNECTED) { + memcpy(awrq->sa_data, priv->curbssparams.bssid, ETH_ALEN); + } else { + memset(awrq->sa_data, 0, ETH_ALEN); + } + awrq->sa_family = ARPHRD_ETHER; + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_set_nick(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* + * Check the size of the string + */ + + if (dwrq->length > 16) { + return -E2BIG; + } + + mutex_lock(&priv->lock); + memset(priv->nodename, 0, sizeof(priv->nodename)); + memcpy(priv->nodename, extra, dwrq->length); + mutex_unlock(&priv->lock); + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_get_nick(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + dwrq->length = strlen(priv->nodename); + memcpy(extra, priv->nodename, dwrq->length); + extra[dwrq->length] = '\0'; + + dwrq->flags = 1; /* active */ + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int mesh_get_nick(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* Use nickname to indicate that mesh is on */ + + if (priv->mesh_connect_status == LBS_CONNECTED) { + strncpy(extra, "Mesh", 12); + extra[12] = '\0'; + dwrq->length = strlen(extra); + } + + else { + extra[0] = '\0'; + dwrq->length = 0; + } + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_set_rts(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + u32 rthr = vwrq->value; + + lbs_deb_enter(LBS_DEB_WEXT); + + if (vwrq->disabled) { + priv->rtsthsd = rthr = MRVDRV_RTS_MAX_VALUE; + } else { + if (rthr < MRVDRV_RTS_MIN_VALUE || rthr > MRVDRV_RTS_MAX_VALUE) + return -EINVAL; + priv->rtsthsd = rthr; + } + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_SNMP_MIB, + CMD_ACT_SET, CMD_OPTION_WAITFORRSP, + OID_802_11_RTS_THRESHOLD, &rthr); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_get_rts(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + priv->rtsthsd = 0; + ret = lbs_prepare_and_send_command(priv, CMD_802_11_SNMP_MIB, + CMD_ACT_GET, CMD_OPTION_WAITFORRSP, + OID_802_11_RTS_THRESHOLD, NULL); + if (ret) + goto out; + + vwrq->value = priv->rtsthsd; + vwrq->disabled = ((vwrq->value < MRVDRV_RTS_MIN_VALUE) + || (vwrq->value > MRVDRV_RTS_MAX_VALUE)); + vwrq->fixed = 1; + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_set_frag(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = 0; + u32 fthr = vwrq->value; + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + if (vwrq->disabled) { + priv->fragthsd = fthr = MRVDRV_FRAG_MAX_VALUE; + } else { + if (fthr < MRVDRV_FRAG_MIN_VALUE + || fthr > MRVDRV_FRAG_MAX_VALUE) + return -EINVAL; + priv->fragthsd = fthr; + } + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_SNMP_MIB, + CMD_ACT_SET, CMD_OPTION_WAITFORRSP, + OID_802_11_FRAGMENTATION_THRESHOLD, &fthr); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_get_frag(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + priv->fragthsd = 0; + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_SNMP_MIB, + CMD_ACT_GET, CMD_OPTION_WAITFORRSP, + OID_802_11_FRAGMENTATION_THRESHOLD, NULL); + if (ret) + goto out; + + vwrq->value = priv->fragthsd; + vwrq->disabled = ((vwrq->value < MRVDRV_FRAG_MIN_VALUE) + || (vwrq->value > MRVDRV_FRAG_MAX_VALUE)); + vwrq->fixed = 1; + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_get_mode(struct net_device *dev, + struct iw_request_info *info, u32 * uwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + *uwrq = priv->mode; + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int mesh_wlan_get_mode(struct net_device *dev, + struct iw_request_info *info, u32 * uwrq, + char *extra) +{ + lbs_deb_enter(LBS_DEB_WEXT); + + *uwrq = IW_MODE_REPEAT ; + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_get_txpow(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_RF_TX_POWER, + CMD_ACT_TX_POWER_OPT_GET, + CMD_OPTION_WAITFORRSP, 0, NULL); + + if (ret) + goto out; + + lbs_deb_wext("tx power level %d dbm\n", priv->txpowerlevel); + vwrq->value = priv->txpowerlevel; + vwrq->fixed = 1; + if (priv->radioon) { + vwrq->disabled = 0; + vwrq->flags = IW_TXPOW_DBM; + } else { + vwrq->disabled = 1; + } + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_set_retry(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + if (vwrq->flags == IW_RETRY_LIMIT) { + /* The MAC has a 4-bit Total_Tx_Count register + Total_Tx_Count = 1 + Tx_Retry_Count */ +#define TX_RETRY_MIN 0 +#define TX_RETRY_MAX 14 + if (vwrq->value < TX_RETRY_MIN || vwrq->value > TX_RETRY_MAX) + return -EINVAL; + + /* Adding 1 to convert retry count to try count */ + priv->txretrycount = vwrq->value + 1; + + ret = lbs_prepare_and_send_command(priv, CMD_802_11_SNMP_MIB, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, + OID_802_11_TX_RETRYCOUNT, NULL); + + if (ret) + goto out; + } else { + return -EOPNOTSUPP; + } + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_get_retry(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + int ret = 0; + + lbs_deb_enter(LBS_DEB_WEXT); + + priv->txretrycount = 0; + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_SNMP_MIB, + CMD_ACT_GET, CMD_OPTION_WAITFORRSP, + OID_802_11_TX_RETRYCOUNT, NULL); + if (ret) + goto out; + + vwrq->disabled = 0; + if (!vwrq->flags) { + vwrq->flags = IW_RETRY_LIMIT; + /* Subtract 1 to convert try count to retry count */ + vwrq->value = priv->txretrycount - 1; + } + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static inline void sort_channels(struct iw_freq *freq, int num) +{ + int i, j; + struct iw_freq temp; + + for (i = 0; i < num; i++) + for (j = i + 1; j < num; j++) + if (freq[i].i > freq[j].i) { + temp.i = freq[i].i; + temp.m = freq[i].m; + + freq[i].i = freq[j].i; + freq[i].m = freq[j].m; + + freq[j].i = temp.i; + freq[j].m = temp.m; + } +} + +/* data rate listing + MULTI_BANDS: + abg a b b/g + Infra G(12) A(8) B(4) G(12) + Adhoc A+B(12) A(8) B(4) B(4) + + non-MULTI_BANDS: + b b/g + Infra B(4) G(12) + Adhoc B(4) B(4) + */ +/** + * @brief Get Range Info + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return 0 --success, otherwise fail + */ +static int lbs_get_range(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + int i, j; + struct lbs_private *priv = dev->priv; + struct iw_range *range = (struct iw_range *)extra; + struct chan_freq_power *cfp; + u8 rates[MAX_RATES + 1]; + + u8 flag = 0; + + lbs_deb_enter(LBS_DEB_WEXT); + + dwrq->length = sizeof(struct iw_range); + memset(range, 0, sizeof(struct iw_range)); + + range->min_nwid = 0; + range->max_nwid = 0; + + memset(rates, 0, sizeof(rates)); + copy_active_data_rates(priv, rates); + range->num_bitrates = strnlen(rates, IW_MAX_BITRATES); + for (i = 0; i < range->num_bitrates; i++) + range->bitrate[i] = rates[i] * 500000; + range->num_bitrates = i; + lbs_deb_wext("IW_MAX_BITRATES %d, num_bitrates %d\n", IW_MAX_BITRATES, + range->num_bitrates); + + range->num_frequency = 0; + if (priv->enable11d && + (priv->connect_status == LBS_CONNECTED || + priv->mesh_connect_status == LBS_CONNECTED)) { + u8 chan_no; + u8 band; + + struct parsed_region_chan_11d *parsed_region_chan = + &priv->parsed_region_chan; + + if (parsed_region_chan == NULL) { + lbs_deb_wext("11d: parsed_region_chan is NULL\n"); + goto out; + } + band = parsed_region_chan->band; + lbs_deb_wext("band %d, nr_char %d\n", band, + parsed_region_chan->nr_chan); + + for (i = 0; (range->num_frequency < IW_MAX_FREQUENCIES) + && (i < parsed_region_chan->nr_chan); i++) { + chan_no = parsed_region_chan->chanpwr[i].chan; + lbs_deb_wext("chan_no %d\n", chan_no); + range->freq[range->num_frequency].i = (long)chan_no; + range->freq[range->num_frequency].m = + (long)lbs_chan_2_freq(chan_no, band) * 100000; + range->freq[range->num_frequency].e = 1; + range->num_frequency++; + } + flag = 1; + } + if (!flag) { + for (j = 0; (range->num_frequency < IW_MAX_FREQUENCIES) + && (j < ARRAY_SIZE(priv->region_channel)); j++) { + cfp = priv->region_channel[j].CFP; + for (i = 0; (range->num_frequency < IW_MAX_FREQUENCIES) + && priv->region_channel[j].valid + && cfp + && (i < priv->region_channel[j].nrcfp); i++) { + range->freq[range->num_frequency].i = + (long)cfp->channel; + range->freq[range->num_frequency].m = + (long)cfp->freq * 100000; + range->freq[range->num_frequency].e = 1; + cfp++; + range->num_frequency++; + } + } + } + + lbs_deb_wext("IW_MAX_FREQUENCIES %d, num_frequency %d\n", + IW_MAX_FREQUENCIES, range->num_frequency); + + range->num_channels = range->num_frequency; + + sort_channels(&range->freq[0], range->num_frequency); + + /* + * Set an indication of the max TCP throughput in bit/s that we can + * expect using this interface + */ + if (i > 2) + range->throughput = 5000 * 1000; + else + range->throughput = 1500 * 1000; + + range->min_rts = MRVDRV_RTS_MIN_VALUE; + range->max_rts = MRVDRV_RTS_MAX_VALUE; + range->min_frag = MRVDRV_FRAG_MIN_VALUE; + range->max_frag = MRVDRV_FRAG_MAX_VALUE; + + range->encoding_size[0] = 5; + range->encoding_size[1] = 13; + range->num_encoding_sizes = 2; + range->max_encoding_tokens = 4; + + range->min_pmp = 1000000; + range->max_pmp = 120000000; + range->min_pmt = 1000; + range->max_pmt = 1000000; + range->pmp_flags = IW_POWER_PERIOD; + range->pmt_flags = IW_POWER_TIMEOUT; + range->pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT | IW_POWER_ALL_R; + + /* + * Minimum version we recommend + */ + range->we_version_source = 15; + + /* + * Version we are compiled with + */ + range->we_version_compiled = WIRELESS_EXT; + + range->retry_capa = IW_RETRY_LIMIT; + range->retry_flags = IW_RETRY_LIMIT | IW_RETRY_MAX; + + range->min_retry = TX_RETRY_MIN; + range->max_retry = TX_RETRY_MAX; + + /* + * Set the qual, level and noise range values + */ + range->max_qual.qual = 100; + range->max_qual.level = 0; + range->max_qual.noise = 0; + range->max_qual.updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; + + range->avg_qual.qual = 70; + /* TODO: Find real 'good' to 'bad' threshold value for RSSI */ + range->avg_qual.level = 0; + range->avg_qual.noise = 0; + range->avg_qual.updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; + + range->sensitivity = 0; + + /* + * Setup the supported power level ranges + */ + memset(range->txpower, 0, sizeof(range->txpower)); + range->txpower[0] = 5; + range->txpower[1] = 7; + range->txpower[2] = 9; + range->txpower[3] = 11; + range->txpower[4] = 13; + range->txpower[5] = 15; + range->txpower[6] = 17; + range->txpower[7] = 19; + + range->num_txpower = 8; + range->txpower_capa = IW_TXPOW_DBM; + range->txpower_capa |= IW_TXPOW_RANGE; + + range->event_capa[0] = (IW_EVENT_CAPA_K_0 | + IW_EVENT_CAPA_MASK(SIOCGIWAP) | + IW_EVENT_CAPA_MASK(SIOCGIWSCAN)); + range->event_capa[1] = IW_EVENT_CAPA_K_1; + + if (priv->fwcapinfo & FW_CAPINFO_WPA) { + range->enc_capa = IW_ENC_CAPA_WPA + | IW_ENC_CAPA_WPA2 + | IW_ENC_CAPA_CIPHER_TKIP + | IW_ENC_CAPA_CIPHER_CCMP; + } + +out: + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_set_power(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* PS is currently supported only in Infrastructure mode + * Remove this check if it is to be supported in IBSS mode also + */ + + if (vwrq->disabled) { + priv->psmode = LBS802_11POWERMODECAM; + if (priv->psstate != PS_STATE_FULL_POWER) { + lbs_ps_wakeup(priv, CMD_OPTION_WAITFORRSP); + } + + return 0; + } + + if ((vwrq->flags & IW_POWER_TYPE) == IW_POWER_TIMEOUT) { + lbs_deb_wext( + "setting power timeout is not supported\n"); + return -EINVAL; + } else if ((vwrq->flags & IW_POWER_TYPE) == IW_POWER_PERIOD) { + lbs_deb_wext("setting power period not supported\n"); + return -EINVAL; + } + + if (priv->psmode != LBS802_11POWERMODECAM) { + return 0; + } + + priv->psmode = LBS802_11POWERMODEMAX_PSP; + + if (priv->connect_status == LBS_CONNECTED) { + lbs_ps_sleep(priv, CMD_OPTION_WAITFORRSP); + } + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_get_power(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + int mode; + + lbs_deb_enter(LBS_DEB_WEXT); + + mode = priv->psmode; + + if ((vwrq->disabled = (mode == LBS802_11POWERMODECAM)) + || priv->connect_status == LBS_DISCONNECTED) + { + goto out; + } + + vwrq->value = 0; + +out: + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static struct iw_statistics *lbs_get_wireless_stats(struct net_device *dev) +{ + enum { + POOR = 30, + FAIR = 60, + GOOD = 80, + VERY_GOOD = 90, + EXCELLENT = 95, + PERFECT = 100 + }; + struct lbs_private *priv = dev->priv; + u32 rssi_qual; + u32 tx_qual; + u32 quality = 0; + int stats_valid = 0; + u8 rssi; + u32 tx_retries; + + lbs_deb_enter(LBS_DEB_WEXT); + + priv->wstats.status = priv->mode; + + /* If we're not associated, all quality values are meaningless */ + if ((priv->connect_status != LBS_CONNECTED) && + (priv->mesh_connect_status != LBS_CONNECTED)) + goto out; + + /* Quality by RSSI */ + priv->wstats.qual.level = + CAL_RSSI(priv->SNR[TYPE_BEACON][TYPE_NOAVG], + priv->NF[TYPE_BEACON][TYPE_NOAVG]); + + if (priv->NF[TYPE_BEACON][TYPE_NOAVG] == 0) { + priv->wstats.qual.noise = MRVDRV_NF_DEFAULT_SCAN_VALUE; + } else { + priv->wstats.qual.noise = + CAL_NF(priv->NF[TYPE_BEACON][TYPE_NOAVG]); + } + + lbs_deb_wext("signal level %#x\n", priv->wstats.qual.level); + lbs_deb_wext("noise %#x\n", priv->wstats.qual.noise); + + rssi = priv->wstats.qual.level - priv->wstats.qual.noise; + if (rssi < 15) + rssi_qual = rssi * POOR / 10; + else if (rssi < 20) + rssi_qual = (rssi - 15) * (FAIR - POOR) / 5 + POOR; + else if (rssi < 30) + rssi_qual = (rssi - 20) * (GOOD - FAIR) / 5 + FAIR; + else if (rssi < 40) + rssi_qual = (rssi - 30) * (VERY_GOOD - GOOD) / + 10 + GOOD; + else + rssi_qual = (rssi - 40) * (PERFECT - VERY_GOOD) / + 10 + VERY_GOOD; + quality = rssi_qual; + + /* Quality by TX errors */ + priv->wstats.discard.retries = priv->stats.tx_errors; + + tx_retries = le32_to_cpu(priv->logmsg.retry); + + if (tx_retries > 75) + tx_qual = (90 - tx_retries) * POOR / 15; + else if (tx_retries > 70) + tx_qual = (75 - tx_retries) * (FAIR - POOR) / 5 + POOR; + else if (tx_retries > 65) + tx_qual = (70 - tx_retries) * (GOOD - FAIR) / 5 + FAIR; + else if (tx_retries > 50) + tx_qual = (65 - tx_retries) * (VERY_GOOD - GOOD) / + 15 + GOOD; + else + tx_qual = (50 - tx_retries) * + (PERFECT - VERY_GOOD) / 50 + VERY_GOOD; + quality = min(quality, tx_qual); + + priv->wstats.discard.code = le32_to_cpu(priv->logmsg.wepundecryptable); + priv->wstats.discard.fragment = le32_to_cpu(priv->logmsg.rxfrag); + priv->wstats.discard.retries = tx_retries; + priv->wstats.discard.misc = le32_to_cpu(priv->logmsg.ackfailure); + + /* Calculate quality */ + priv->wstats.qual.qual = min_t(u8, quality, 100); + priv->wstats.qual.updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; + stats_valid = 1; + + /* update stats asynchronously for future calls */ + lbs_prepare_and_send_command(priv, CMD_802_11_RSSI, 0, + 0, 0, NULL); + lbs_prepare_and_send_command(priv, CMD_802_11_GET_LOG, 0, + 0, 0, NULL); +out: + if (!stats_valid) { + priv->wstats.miss.beacon = 0; + priv->wstats.discard.retries = 0; + priv->wstats.qual.qual = 0; + priv->wstats.qual.level = 0; + priv->wstats.qual.noise = 0; + priv->wstats.qual.updated = IW_QUAL_ALL_UPDATED; + priv->wstats.qual.updated |= IW_QUAL_NOISE_INVALID | + IW_QUAL_QUAL_INVALID | IW_QUAL_LEVEL_INVALID; + } + + lbs_deb_leave(LBS_DEB_WEXT); + return &priv->wstats; + + +} + +static int lbs_set_freq(struct net_device *dev, struct iw_request_info *info, + struct iw_freq *fwrq, char *extra) +{ + int ret = -EINVAL; + struct lbs_private *priv = dev->priv; + struct chan_freq_power *cfp; + struct assoc_request * assoc_req; + + lbs_deb_enter(LBS_DEB_WEXT); + + mutex_lock(&priv->lock); + assoc_req = lbs_get_association_request(priv); + if (!assoc_req) { + ret = -ENOMEM; + goto out; + } + + /* If setting by frequency, convert to a channel */ + if (fwrq->e == 1) { + long f = fwrq->m / 100000; + + cfp = find_cfp_by_band_and_freq(priv, 0, f); + if (!cfp) { + lbs_deb_wext("invalid freq %ld\n", f); + goto out; + } + + fwrq->e = 0; + fwrq->m = (int) cfp->channel; + } + + /* Setting by channel number */ + if (fwrq->m > 1000 || fwrq->e > 0) { + goto out; + } + + cfp = lbs_find_cfp_by_band_and_channel(priv, 0, fwrq->m); + if (!cfp) { + goto out; + } + + assoc_req->channel = fwrq->m; + ret = 0; + +out: + if (ret == 0) { + set_bit(ASSOC_FLAG_CHANNEL, &assoc_req->flags); + lbs_postpone_association_work(priv); + } else { + lbs_cancel_association_work(priv); + } + mutex_unlock(&priv->lock); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_mesh_set_freq(struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *fwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + struct chan_freq_power *cfp; + int ret = -EINVAL; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* If setting by frequency, convert to a channel */ + if (fwrq->e == 1) { + long f = fwrq->m / 100000; + + cfp = find_cfp_by_band_and_freq(priv, 0, f); + if (!cfp) { + lbs_deb_wext("invalid freq %ld\n", f); + goto out; + } + + fwrq->e = 0; + fwrq->m = (int) cfp->channel; + } + + /* Setting by channel number */ + if (fwrq->m > 1000 || fwrq->e > 0) { + goto out; + } + + cfp = lbs_find_cfp_by_band_and_channel(priv, 0, fwrq->m); + if (!cfp) { + goto out; + } + + if (fwrq->m != priv->curbssparams.channel) { + lbs_deb_wext("mesh channel change forces eth disconnect\n"); + if (priv->mode == IW_MODE_INFRA) + lbs_send_deauthentication(priv); + else if (priv->mode == IW_MODE_ADHOC) + lbs_stop_adhoc_network(priv); + } + priv->curbssparams.channel = fwrq->m; + lbs_mesh_config(priv, 0); + lbs_mesh_config(priv, 1); + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_set_rate(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + u8 new_rate = 0; + int ret = -EINVAL; + u8 rates[MAX_RATES + 1]; + + lbs_deb_enter(LBS_DEB_WEXT); + lbs_deb_wext("vwrq->value %d\n", vwrq->value); + + /* Auto rate? */ + if (vwrq->value == -1) { + priv->auto_rate = 1; + priv->cur_rate = 0; + } else { + if (vwrq->value % 100000) + goto out; + + memset(rates, 0, sizeof(rates)); + copy_active_data_rates(priv, rates); + new_rate = vwrq->value / 500000; + if (!memchr(rates, new_rate, sizeof(rates))) { + lbs_pr_alert("fixed data rate 0x%X out of range\n", + new_rate); + goto out; + } + + priv->cur_rate = new_rate; + priv->auto_rate = 0; + } + + ret = lbs_set_data_rate(priv, new_rate); + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_get_rate(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + if (priv->connect_status == LBS_CONNECTED) { + vwrq->value = priv->cur_rate * 500000; + + if (priv->auto_rate) + vwrq->fixed = 0; + else + vwrq->fixed = 1; + + } else { + vwrq->fixed = 0; + vwrq->value = 0; + } + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_set_mode(struct net_device *dev, + struct iw_request_info *info, u32 * uwrq, char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + struct assoc_request * assoc_req; + + lbs_deb_enter(LBS_DEB_WEXT); + + if ( (*uwrq != IW_MODE_ADHOC) + && (*uwrq != IW_MODE_INFRA) + && (*uwrq != IW_MODE_AUTO)) { + lbs_deb_wext("Invalid mode: 0x%x\n", *uwrq); + ret = -EINVAL; + goto out; + } + + mutex_lock(&priv->lock); + assoc_req = lbs_get_association_request(priv); + if (!assoc_req) { + ret = -ENOMEM; + lbs_cancel_association_work(priv); + } else { + assoc_req->mode = *uwrq; + set_bit(ASSOC_FLAG_MODE, &assoc_req->flags); + lbs_postpone_association_work(priv); + lbs_deb_wext("Switching to mode: 0x%x\n", *uwrq); + } + mutex_unlock(&priv->lock); + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + + +/** + * @brief Get Encryption key + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return 0 --success, otherwise fail + */ +static int lbs_get_encode(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, u8 * extra) +{ + struct lbs_private *priv = dev->priv; + int index = (dwrq->flags & IW_ENCODE_INDEX) - 1; + + lbs_deb_enter(LBS_DEB_WEXT); + + lbs_deb_wext("flags 0x%x, index %d, length %d, wep_tx_keyidx %d\n", + dwrq->flags, index, dwrq->length, priv->wep_tx_keyidx); + + dwrq->flags = 0; + + /* Authentication method */ + switch (priv->secinfo.auth_mode) { + case IW_AUTH_ALG_OPEN_SYSTEM: + dwrq->flags = IW_ENCODE_OPEN; + break; + + case IW_AUTH_ALG_SHARED_KEY: + case IW_AUTH_ALG_LEAP: + dwrq->flags = IW_ENCODE_RESTRICTED; + break; + default: + dwrq->flags = IW_ENCODE_DISABLED | IW_ENCODE_OPEN; + break; + } + + memset(extra, 0, 16); + + mutex_lock(&priv->lock); + + /* Default to returning current transmit key */ + if (index < 0) + index = priv->wep_tx_keyidx; + + if ((priv->wep_keys[index].len) && priv->secinfo.wep_enabled) { + memcpy(extra, priv->wep_keys[index].key, + priv->wep_keys[index].len); + dwrq->length = priv->wep_keys[index].len; + + dwrq->flags |= (index + 1); + /* Return WEP enabled */ + dwrq->flags &= ~IW_ENCODE_DISABLED; + } else if ((priv->secinfo.WPAenabled) + || (priv->secinfo.WPA2enabled)) { + /* return WPA enabled */ + dwrq->flags &= ~IW_ENCODE_DISABLED; + dwrq->flags |= IW_ENCODE_NOKEY; + } else { + dwrq->flags |= IW_ENCODE_DISABLED; + } + + mutex_unlock(&priv->lock); + + lbs_deb_wext("key: %02x:%02x:%02x:%02x:%02x:%02x, keylen %d\n", + extra[0], extra[1], extra[2], + extra[3], extra[4], extra[5], dwrq->length); + + lbs_deb_wext("return flags 0x%x\n", dwrq->flags); + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +/** + * @brief Set Encryption key (internal) + * + * @param priv A pointer to private card structure + * @param key_material A pointer to key material + * @param key_length length of key material + * @param index key index to set + * @param set_tx_key Force set TX key (1 = yes, 0 = no) + * @return 0 --success, otherwise fail + */ +static int lbs_set_wep_key(struct assoc_request *assoc_req, + const char *key_material, + u16 key_length, + u16 index, + int set_tx_key) +{ + int ret = 0; + struct enc_key *pkey; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* Paranoid validation of key index */ + if (index > 3) { + ret = -EINVAL; + goto out; + } + + /* validate max key length */ + if (key_length > KEY_LEN_WEP_104) { + ret = -EINVAL; + goto out; + } + + pkey = &assoc_req->wep_keys[index]; + + if (key_length > 0) { + memset(pkey, 0, sizeof(struct enc_key)); + pkey->type = KEY_TYPE_ID_WEP; + + /* Standardize the key length */ + pkey->len = (key_length > KEY_LEN_WEP_40) ? + KEY_LEN_WEP_104 : KEY_LEN_WEP_40; + memcpy(pkey->key, key_material, key_length); + } + + if (set_tx_key) { + /* Ensure the chosen key is valid */ + if (!pkey->len) { + lbs_deb_wext("key not set, so cannot enable it\n"); + ret = -EINVAL; + goto out; + } + assoc_req->wep_tx_keyidx = index; + } + + assoc_req->secinfo.wep_enabled = 1; + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int validate_key_index(u16 def_index, u16 raw_index, + u16 *out_index, u16 *is_default) +{ + if (!out_index || !is_default) + return -EINVAL; + + /* Verify index if present, otherwise use default TX key index */ + if (raw_index > 0) { + if (raw_index > 4) + return -EINVAL; + *out_index = raw_index - 1; + } else { + *out_index = def_index; + *is_default = 1; + } + return 0; +} + +static void disable_wep(struct assoc_request *assoc_req) +{ + int i; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* Set Open System auth mode */ + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + + /* Clear WEP keys and mark WEP as disabled */ + assoc_req->secinfo.wep_enabled = 0; + for (i = 0; i < 4; i++) + assoc_req->wep_keys[i].len = 0; + + set_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags); + set_bit(ASSOC_FLAG_WEP_KEYS, &assoc_req->flags); + + lbs_deb_leave(LBS_DEB_WEXT); +} + +static void disable_wpa(struct assoc_request *assoc_req) +{ + lbs_deb_enter(LBS_DEB_WEXT); + + memset(&assoc_req->wpa_mcast_key, 0, sizeof (struct enc_key)); + assoc_req->wpa_mcast_key.flags = KEY_INFO_WPA_MCAST; + set_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags); + + memset(&assoc_req->wpa_unicast_key, 0, sizeof (struct enc_key)); + assoc_req->wpa_unicast_key.flags = KEY_INFO_WPA_UNICAST; + set_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags); + + assoc_req->secinfo.WPAenabled = 0; + assoc_req->secinfo.WPA2enabled = 0; + set_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags); + + lbs_deb_leave(LBS_DEB_WEXT); +} + +/** + * @brief Set Encryption key + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return 0 --success, otherwise fail + */ +static int lbs_set_encode(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + struct assoc_request * assoc_req; + u16 is_default = 0, index = 0, set_tx_key = 0; + + lbs_deb_enter(LBS_DEB_WEXT); + + mutex_lock(&priv->lock); + assoc_req = lbs_get_association_request(priv); + if (!assoc_req) { + ret = -ENOMEM; + goto out; + } + + if (dwrq->flags & IW_ENCODE_DISABLED) { + disable_wep (assoc_req); + disable_wpa (assoc_req); + goto out; + } + + ret = validate_key_index(assoc_req->wep_tx_keyidx, + (dwrq->flags & IW_ENCODE_INDEX), + &index, &is_default); + if (ret) { + ret = -EINVAL; + goto out; + } + + /* If WEP isn't enabled, or if there is no key data but a valid + * index, set the TX key. + */ + if (!assoc_req->secinfo.wep_enabled || (dwrq->length == 0 && !is_default)) + set_tx_key = 1; + + ret = lbs_set_wep_key(assoc_req, extra, dwrq->length, index, set_tx_key); + if (ret) + goto out; + + if (dwrq->length) + set_bit(ASSOC_FLAG_WEP_KEYS, &assoc_req->flags); + if (set_tx_key) + set_bit(ASSOC_FLAG_WEP_TX_KEYIDX, &assoc_req->flags); + + if (dwrq->flags & IW_ENCODE_RESTRICTED) { + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_SHARED_KEY; + } else if (dwrq->flags & IW_ENCODE_OPEN) { + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + } + +out: + if (ret == 0) { + set_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags); + lbs_postpone_association_work(priv); + } else { + lbs_cancel_association_work(priv); + } + mutex_unlock(&priv->lock); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +/** + * @brief Get Extended Encryption key (WPA/802.1x and WEP) + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return 0 on success, otherwise failure + */ +static int lbs_get_encodeext(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra) +{ + int ret = -EINVAL; + struct lbs_private *priv = dev->priv; + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + int index, max_key_len; + + lbs_deb_enter(LBS_DEB_WEXT); + + max_key_len = dwrq->length - sizeof(*ext); + if (max_key_len < 0) + goto out; + + index = dwrq->flags & IW_ENCODE_INDEX; + if (index) { + if (index < 1 || index > 4) + goto out; + index--; + } else { + index = priv->wep_tx_keyidx; + } + + if (!(ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) && + ext->alg != IW_ENCODE_ALG_WEP) { + if (index != 0 || priv->mode != IW_MODE_INFRA) + goto out; + } + + dwrq->flags = index + 1; + memset(ext, 0, sizeof(*ext)); + + if ( !priv->secinfo.wep_enabled + && !priv->secinfo.WPAenabled + && !priv->secinfo.WPA2enabled) { + ext->alg = IW_ENCODE_ALG_NONE; + ext->key_len = 0; + dwrq->flags |= IW_ENCODE_DISABLED; + } else { + u8 *key = NULL; + + if ( priv->secinfo.wep_enabled + && !priv->secinfo.WPAenabled + && !priv->secinfo.WPA2enabled) { + /* WEP */ + ext->alg = IW_ENCODE_ALG_WEP; + ext->key_len = priv->wep_keys[index].len; + key = &priv->wep_keys[index].key[0]; + } else if ( !priv->secinfo.wep_enabled + && (priv->secinfo.WPAenabled || + priv->secinfo.WPA2enabled)) { + /* WPA */ + struct enc_key * pkey = NULL; + + if ( priv->wpa_mcast_key.len + && (priv->wpa_mcast_key.flags & KEY_INFO_WPA_ENABLED)) + pkey = &priv->wpa_mcast_key; + else if ( priv->wpa_unicast_key.len + && (priv->wpa_unicast_key.flags & KEY_INFO_WPA_ENABLED)) + pkey = &priv->wpa_unicast_key; + + if (pkey) { + if (pkey->type == KEY_TYPE_ID_AES) { + ext->alg = IW_ENCODE_ALG_CCMP; + } else { + ext->alg = IW_ENCODE_ALG_TKIP; + } + ext->key_len = pkey->len; + key = &pkey->key[0]; + } else { + ext->alg = IW_ENCODE_ALG_TKIP; + ext->key_len = 0; + } + } else { + goto out; + } + + if (ext->key_len > max_key_len) { + ret = -E2BIG; + goto out; + } + + if (ext->key_len) + memcpy(ext->key, key, ext->key_len); + else + dwrq->flags |= IW_ENCODE_NOKEY; + dwrq->flags |= IW_ENCODE_ENABLED; + } + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +/** + * @brief Set Encryption key Extended (WPA/802.1x and WEP) + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return 0 --success, otherwise fail + */ +static int lbs_set_encodeext(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + int alg = ext->alg; + struct assoc_request * assoc_req; + + lbs_deb_enter(LBS_DEB_WEXT); + + mutex_lock(&priv->lock); + assoc_req = lbs_get_association_request(priv); + if (!assoc_req) { + ret = -ENOMEM; + goto out; + } + + if ((alg == IW_ENCODE_ALG_NONE) || (dwrq->flags & IW_ENCODE_DISABLED)) { + disable_wep (assoc_req); + disable_wpa (assoc_req); + } else if (alg == IW_ENCODE_ALG_WEP) { + u16 is_default = 0, index, set_tx_key = 0; + + ret = validate_key_index(assoc_req->wep_tx_keyidx, + (dwrq->flags & IW_ENCODE_INDEX), + &index, &is_default); + if (ret) + goto out; + + /* If WEP isn't enabled, or if there is no key data but a valid + * index, or if the set-TX-key flag was passed, set the TX key. + */ + if ( !assoc_req->secinfo.wep_enabled + || (dwrq->length == 0 && !is_default) + || (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY)) + set_tx_key = 1; + + /* Copy key to driver */ + ret = lbs_set_wep_key(assoc_req, ext->key, ext->key_len, index, + set_tx_key); + if (ret) + goto out; + + if (dwrq->flags & IW_ENCODE_RESTRICTED) { + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_SHARED_KEY; + } else if (dwrq->flags & IW_ENCODE_OPEN) { + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + } + + /* Mark the various WEP bits as modified */ + set_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags); + if (dwrq->length) + set_bit(ASSOC_FLAG_WEP_KEYS, &assoc_req->flags); + if (set_tx_key) + set_bit(ASSOC_FLAG_WEP_TX_KEYIDX, &assoc_req->flags); + } else if ((alg == IW_ENCODE_ALG_TKIP) || (alg == IW_ENCODE_ALG_CCMP)) { + struct enc_key * pkey; + + /* validate key length */ + if (((alg == IW_ENCODE_ALG_TKIP) + && (ext->key_len != KEY_LEN_WPA_TKIP)) + || ((alg == IW_ENCODE_ALG_CCMP) + && (ext->key_len != KEY_LEN_WPA_AES))) { + lbs_deb_wext("invalid size %d for key of alg " + "type %d\n", + ext->key_len, + alg); + ret = -EINVAL; + goto out; + } + + if (ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) { + pkey = &assoc_req->wpa_mcast_key; + set_bit(ASSOC_FLAG_WPA_MCAST_KEY, &assoc_req->flags); + } else { + pkey = &assoc_req->wpa_unicast_key; + set_bit(ASSOC_FLAG_WPA_UCAST_KEY, &assoc_req->flags); + } + + memset(pkey, 0, sizeof (struct enc_key)); + memcpy(pkey->key, ext->key, ext->key_len); + pkey->len = ext->key_len; + if (pkey->len) + pkey->flags |= KEY_INFO_WPA_ENABLED; + + /* Do this after zeroing key structure */ + if (ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) { + pkey->flags |= KEY_INFO_WPA_MCAST; + } else { + pkey->flags |= KEY_INFO_WPA_UNICAST; + } + + if (alg == IW_ENCODE_ALG_TKIP) { + pkey->type = KEY_TYPE_ID_TKIP; + } else if (alg == IW_ENCODE_ALG_CCMP) { + pkey->type = KEY_TYPE_ID_AES; + } + + /* If WPA isn't enabled yet, do that now */ + if ( assoc_req->secinfo.WPAenabled == 0 + && assoc_req->secinfo.WPA2enabled == 0) { + assoc_req->secinfo.WPAenabled = 1; + assoc_req->secinfo.WPA2enabled = 1; + set_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags); + } + + disable_wep (assoc_req); + } + +out: + if (ret == 0) { + lbs_postpone_association_work(priv); + } else { + lbs_cancel_association_work(priv); + } + mutex_unlock(&priv->lock); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + + +static int lbs_set_genie(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra) +{ + struct lbs_private *priv = dev->priv; + int ret = 0; + struct assoc_request * assoc_req; + + lbs_deb_enter(LBS_DEB_WEXT); + + mutex_lock(&priv->lock); + assoc_req = lbs_get_association_request(priv); + if (!assoc_req) { + ret = -ENOMEM; + goto out; + } + + if (dwrq->length > MAX_WPA_IE_LEN || + (dwrq->length && extra == NULL)) { + ret = -EINVAL; + goto out; + } + + if (dwrq->length) { + memcpy(&assoc_req->wpa_ie[0], extra, dwrq->length); + assoc_req->wpa_ie_len = dwrq->length; + } else { + memset(&assoc_req->wpa_ie[0], 0, sizeof(priv->wpa_ie)); + assoc_req->wpa_ie_len = 0; + } + +out: + if (ret == 0) { + set_bit(ASSOC_FLAG_WPA_IE, &assoc_req->flags); + lbs_postpone_association_work(priv); + } else { + lbs_cancel_association_work(priv); + } + mutex_unlock(&priv->lock); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_get_genie(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, + char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + if (priv->wpa_ie_len == 0) { + dwrq->length = 0; + goto out; + } + + if (dwrq->length < priv->wpa_ie_len) { + ret = -E2BIG; + goto out; + } + + dwrq->length = priv->wpa_ie_len; + memcpy(extra, &priv->wpa_ie[0], priv->wpa_ie_len); + +out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + + +static int lbs_set_auth(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *dwrq, + char *extra) +{ + struct lbs_private *priv = dev->priv; + struct assoc_request * assoc_req; + int ret = 0; + int updated = 0; + + lbs_deb_enter(LBS_DEB_WEXT); + + mutex_lock(&priv->lock); + assoc_req = lbs_get_association_request(priv); + if (!assoc_req) { + ret = -ENOMEM; + goto out; + } + + switch (dwrq->flags & IW_AUTH_INDEX) { + case IW_AUTH_TKIP_COUNTERMEASURES: + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + case IW_AUTH_KEY_MGMT: + case IW_AUTH_DROP_UNENCRYPTED: + /* + * libertas does not use these parameters + */ + break; + + case IW_AUTH_WPA_VERSION: + if (dwrq->value & IW_AUTH_WPA_VERSION_DISABLED) { + assoc_req->secinfo.WPAenabled = 0; + assoc_req->secinfo.WPA2enabled = 0; + disable_wpa (assoc_req); + } + if (dwrq->value & IW_AUTH_WPA_VERSION_WPA) { + assoc_req->secinfo.WPAenabled = 1; + assoc_req->secinfo.wep_enabled = 0; + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + } + if (dwrq->value & IW_AUTH_WPA_VERSION_WPA2) { + assoc_req->secinfo.WPA2enabled = 1; + assoc_req->secinfo.wep_enabled = 0; + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + } + updated = 1; + break; + + case IW_AUTH_80211_AUTH_ALG: + if (dwrq->value & IW_AUTH_ALG_SHARED_KEY) { + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_SHARED_KEY; + } else if (dwrq->value & IW_AUTH_ALG_OPEN_SYSTEM) { + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + } else if (dwrq->value & IW_AUTH_ALG_LEAP) { + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_LEAP; + } else { + ret = -EINVAL; + } + updated = 1; + break; + + case IW_AUTH_WPA_ENABLED: + if (dwrq->value) { + if (!assoc_req->secinfo.WPAenabled && + !assoc_req->secinfo.WPA2enabled) { + assoc_req->secinfo.WPAenabled = 1; + assoc_req->secinfo.WPA2enabled = 1; + assoc_req->secinfo.wep_enabled = 0; + assoc_req->secinfo.auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + } + } else { + assoc_req->secinfo.WPAenabled = 0; + assoc_req->secinfo.WPA2enabled = 0; + disable_wpa (assoc_req); + } + updated = 1; + break; + + default: + ret = -EOPNOTSUPP; + break; + } + +out: + if (ret == 0) { + if (updated) + set_bit(ASSOC_FLAG_SECINFO, &assoc_req->flags); + lbs_postpone_association_work(priv); + } else if (ret != -EOPNOTSUPP) { + lbs_cancel_association_work(priv); + } + mutex_unlock(&priv->lock); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_get_auth(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *dwrq, + char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + switch (dwrq->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + dwrq->value = 0; + if (priv->secinfo.WPAenabled) + dwrq->value |= IW_AUTH_WPA_VERSION_WPA; + if (priv->secinfo.WPA2enabled) + dwrq->value |= IW_AUTH_WPA_VERSION_WPA2; + if (!dwrq->value) + dwrq->value |= IW_AUTH_WPA_VERSION_DISABLED; + break; + + case IW_AUTH_80211_AUTH_ALG: + dwrq->value = priv->secinfo.auth_mode; + break; + + case IW_AUTH_WPA_ENABLED: + if (priv->secinfo.WPAenabled && priv->secinfo.WPA2enabled) + dwrq->value = 1; + break; + + default: + ret = -EOPNOTSUPP; + } + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + + +static int lbs_set_txpow(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = 0; + struct lbs_private *priv = dev->priv; + + u16 dbm; + + lbs_deb_enter(LBS_DEB_WEXT); + + if (vwrq->disabled) { + lbs_radio_ioctl(priv, RADIO_OFF); + return 0; + } + + priv->preamble = CMD_TYPE_AUTO_PREAMBLE; + + lbs_radio_ioctl(priv, RADIO_ON); + + /* Userspace check in iwrange if it should use dBm or mW, + * therefore this should never happen... Jean II */ + if ((vwrq->flags & IW_TXPOW_TYPE) == IW_TXPOW_MWATT) { + return -EOPNOTSUPP; + } else + dbm = (u16) vwrq->value; + + /* auto tx power control */ + + if (vwrq->fixed == 0) + dbm = 0xffff; + + lbs_deb_wext("txpower set %d dbm\n", dbm); + + ret = lbs_prepare_and_send_command(priv, + CMD_802_11_RF_TX_POWER, + CMD_ACT_TX_POWER_OPT_SET_LOW, + CMD_OPTION_WAITFORRSP, 0, (void *)&dbm); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_get_essid(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* + * Note : if dwrq->flags != 0, we should get the relevant SSID from + * the SSID list... + */ + + /* + * Get the current SSID + */ + if (priv->connect_status == LBS_CONNECTED) { + memcpy(extra, priv->curbssparams.ssid, + priv->curbssparams.ssid_len); + extra[priv->curbssparams.ssid_len] = '\0'; + } else { + memset(extra, 0, 32); + extra[priv->curbssparams.ssid_len] = '\0'; + } + /* + * If none, we may want to get the one that was set + */ + + dwrq->length = priv->curbssparams.ssid_len; + + dwrq->flags = 1; /* active */ + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_set_essid(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + int ret = 0; + u8 ssid[IW_ESSID_MAX_SIZE]; + u8 ssid_len = 0; + struct assoc_request * assoc_req; + int in_ssid_len = dwrq->length; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* Check the size of the string */ + if (in_ssid_len > IW_ESSID_MAX_SIZE) { + ret = -E2BIG; + goto out; + } + + memset(&ssid, 0, sizeof(ssid)); + + if (!dwrq->flags || !in_ssid_len) { + /* "any" SSID requested; leave SSID blank */ + } else { + /* Specific SSID requested */ + memcpy(&ssid, extra, in_ssid_len); + ssid_len = in_ssid_len; + } + + if (!ssid_len) { + lbs_deb_wext("requested any SSID\n"); + } else { + lbs_deb_wext("requested SSID '%s'\n", + escape_essid(ssid, ssid_len)); + } + +out: + mutex_lock(&priv->lock); + if (ret == 0) { + /* Get or create the current association request */ + assoc_req = lbs_get_association_request(priv); + if (!assoc_req) { + ret = -ENOMEM; + } else { + /* Copy the SSID to the association request */ + memcpy(&assoc_req->ssid, &ssid, IW_ESSID_MAX_SIZE); + assoc_req->ssid_len = ssid_len; + set_bit(ASSOC_FLAG_SSID, &assoc_req->flags); + lbs_postpone_association_work(priv); + } + } + + /* Cancel the association request if there was an error */ + if (ret != 0) { + lbs_cancel_association_work(priv); + } + + mutex_unlock(&priv->lock); + + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +static int lbs_mesh_get_essid(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + + lbs_deb_enter(LBS_DEB_WEXT); + + memcpy(extra, priv->mesh_ssid, priv->mesh_ssid_len); + + dwrq->length = priv->mesh_ssid_len; + + dwrq->flags = 1; /* active */ + + lbs_deb_leave(LBS_DEB_WEXT); + return 0; +} + +static int lbs_mesh_set_essid(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + int ret = 0; + + lbs_deb_enter(LBS_DEB_WEXT); + + /* Check the size of the string */ + if (dwrq->length > IW_ESSID_MAX_SIZE) { + ret = -E2BIG; + goto out; + } + + if (!dwrq->flags || !dwrq->length) { + ret = -EINVAL; + goto out; + } else { + /* Specific SSID requested */ + memcpy(priv->mesh_ssid, extra, dwrq->length); + priv->mesh_ssid_len = dwrq->length; + } + + lbs_mesh_config(priv, 1); + out: + lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); + return ret; +} + +/** + * @brief Connect to the AP or Ad-hoc Network with specific bssid + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param awrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return 0 --success, otherwise fail + */ +static int lbs_set_wap(struct net_device *dev, struct iw_request_info *info, + struct sockaddr *awrq, char *extra) +{ + struct lbs_private *priv = dev->priv; + struct assoc_request * assoc_req; + int ret = 0; + DECLARE_MAC_BUF(mac); + + lbs_deb_enter(LBS_DEB_WEXT); + + if (awrq->sa_family != ARPHRD_ETHER) + return -EINVAL; + + lbs_deb_wext("ASSOC: WAP: sa_data %s\n", print_mac(mac, awrq->sa_data)); + + mutex_lock(&priv->lock); + + /* Get or create the current association request */ + assoc_req = lbs_get_association_request(priv); + if (!assoc_req) { + lbs_cancel_association_work(priv); + ret = -ENOMEM; + } else { + /* Copy the BSSID to the association request */ + memcpy(&assoc_req->bssid, awrq->sa_data, ETH_ALEN); + set_bit(ASSOC_FLAG_BSSID, &assoc_req->flags); + lbs_postpone_association_work(priv); + } + + mutex_unlock(&priv->lock); + + return ret; +} + +void lbs_get_fwversion(struct lbs_private *priv, char *fwversion, int maxlen) +{ + char fwver[32]; + + mutex_lock(&priv->lock); + + if (priv->fwreleasenumber[3] == 0) + sprintf(fwver, "%u.%u.%u", + priv->fwreleasenumber[2], + priv->fwreleasenumber[1], + priv->fwreleasenumber[0]); + else + sprintf(fwver, "%u.%u.%u.p%u", + priv->fwreleasenumber[2], + priv->fwreleasenumber[1], + priv->fwreleasenumber[0], + priv->fwreleasenumber[3]); + + mutex_unlock(&priv->lock); + snprintf(fwversion, maxlen, fwver); +} + + +/* + * iwconfig settable callbacks + */ +static const iw_handler lbs_handler[] = { + (iw_handler) NULL, /* SIOCSIWCOMMIT */ + (iw_handler) lbs_get_name, /* SIOCGIWNAME */ + (iw_handler) NULL, /* SIOCSIWNWID */ + (iw_handler) NULL, /* SIOCGIWNWID */ + (iw_handler) lbs_set_freq, /* SIOCSIWFREQ */ + (iw_handler) lbs_get_freq, /* SIOCGIWFREQ */ + (iw_handler) lbs_set_mode, /* SIOCSIWMODE */ + (iw_handler) lbs_get_mode, /* SIOCGIWMODE */ + (iw_handler) NULL, /* SIOCSIWSENS */ + (iw_handler) NULL, /* SIOCGIWSENS */ + (iw_handler) NULL, /* SIOCSIWRANGE */ + (iw_handler) lbs_get_range, /* SIOCGIWRANGE */ + (iw_handler) NULL, /* SIOCSIWPRIV */ + (iw_handler) NULL, /* SIOCGIWPRIV */ + (iw_handler) NULL, /* SIOCSIWSTATS */ + (iw_handler) NULL, /* SIOCGIWSTATS */ + iw_handler_set_spy, /* SIOCSIWSPY */ + iw_handler_get_spy, /* SIOCGIWSPY */ + iw_handler_set_thrspy, /* SIOCSIWTHRSPY */ + iw_handler_get_thrspy, /* SIOCGIWTHRSPY */ + (iw_handler) lbs_set_wap, /* SIOCSIWAP */ + (iw_handler) lbs_get_wap, /* SIOCGIWAP */ + (iw_handler) NULL, /* SIOCSIWMLME */ + (iw_handler) NULL, /* SIOCGIWAPLIST - deprecated */ + (iw_handler) lbs_set_scan, /* SIOCSIWSCAN */ + (iw_handler) lbs_get_scan, /* SIOCGIWSCAN */ + (iw_handler) lbs_set_essid, /* SIOCSIWESSID */ + (iw_handler) lbs_get_essid, /* SIOCGIWESSID */ + (iw_handler) lbs_set_nick, /* SIOCSIWNICKN */ + (iw_handler) lbs_get_nick, /* SIOCGIWNICKN */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) lbs_set_rate, /* SIOCSIWRATE */ + (iw_handler) lbs_get_rate, /* SIOCGIWRATE */ + (iw_handler) lbs_set_rts, /* SIOCSIWRTS */ + (iw_handler) lbs_get_rts, /* SIOCGIWRTS */ + (iw_handler) lbs_set_frag, /* SIOCSIWFRAG */ + (iw_handler) lbs_get_frag, /* SIOCGIWFRAG */ + (iw_handler) lbs_set_txpow, /* SIOCSIWTXPOW */ + (iw_handler) lbs_get_txpow, /* SIOCGIWTXPOW */ + (iw_handler) lbs_set_retry, /* SIOCSIWRETRY */ + (iw_handler) lbs_get_retry, /* SIOCGIWRETRY */ + (iw_handler) lbs_set_encode, /* SIOCSIWENCODE */ + (iw_handler) lbs_get_encode, /* SIOCGIWENCODE */ + (iw_handler) lbs_set_power, /* SIOCSIWPOWER */ + (iw_handler) lbs_get_power, /* SIOCGIWPOWER */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) lbs_set_genie, /* SIOCSIWGENIE */ + (iw_handler) lbs_get_genie, /* SIOCGIWGENIE */ + (iw_handler) lbs_set_auth, /* SIOCSIWAUTH */ + (iw_handler) lbs_get_auth, /* SIOCGIWAUTH */ + (iw_handler) lbs_set_encodeext,/* SIOCSIWENCODEEXT */ + (iw_handler) lbs_get_encodeext,/* SIOCGIWENCODEEXT */ + (iw_handler) NULL, /* SIOCSIWPMKSA */ +}; + +static const iw_handler mesh_wlan_handler[] = { + (iw_handler) NULL, /* SIOCSIWCOMMIT */ + (iw_handler) lbs_get_name, /* SIOCGIWNAME */ + (iw_handler) NULL, /* SIOCSIWNWID */ + (iw_handler) NULL, /* SIOCGIWNWID */ + (iw_handler) lbs_mesh_set_freq, /* SIOCSIWFREQ */ + (iw_handler) lbs_get_freq, /* SIOCGIWFREQ */ + (iw_handler) NULL, /* SIOCSIWMODE */ + (iw_handler) mesh_wlan_get_mode, /* SIOCGIWMODE */ + (iw_handler) NULL, /* SIOCSIWSENS */ + (iw_handler) NULL, /* SIOCGIWSENS */ + (iw_handler) NULL, /* SIOCSIWRANGE */ + (iw_handler) lbs_get_range, /* SIOCGIWRANGE */ + (iw_handler) NULL, /* SIOCSIWPRIV */ + (iw_handler) NULL, /* SIOCGIWPRIV */ + (iw_handler) NULL, /* SIOCSIWSTATS */ + (iw_handler) NULL, /* SIOCGIWSTATS */ + iw_handler_set_spy, /* SIOCSIWSPY */ + iw_handler_get_spy, /* SIOCGIWSPY */ + iw_handler_set_thrspy, /* SIOCSIWTHRSPY */ + iw_handler_get_thrspy, /* SIOCGIWTHRSPY */ + (iw_handler) NULL, /* SIOCSIWAP */ + (iw_handler) NULL, /* SIOCGIWAP */ + (iw_handler) NULL, /* SIOCSIWMLME */ + (iw_handler) NULL, /* SIOCGIWAPLIST - deprecated */ + (iw_handler) lbs_set_scan, /* SIOCSIWSCAN */ + (iw_handler) lbs_get_scan, /* SIOCGIWSCAN */ + (iw_handler) lbs_mesh_set_essid,/* SIOCSIWESSID */ + (iw_handler) lbs_mesh_get_essid,/* SIOCGIWESSID */ + (iw_handler) NULL, /* SIOCSIWNICKN */ + (iw_handler) mesh_get_nick, /* SIOCGIWNICKN */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) lbs_set_rate, /* SIOCSIWRATE */ + (iw_handler) lbs_get_rate, /* SIOCGIWRATE */ + (iw_handler) lbs_set_rts, /* SIOCSIWRTS */ + (iw_handler) lbs_get_rts, /* SIOCGIWRTS */ + (iw_handler) lbs_set_frag, /* SIOCSIWFRAG */ + (iw_handler) lbs_get_frag, /* SIOCGIWFRAG */ + (iw_handler) lbs_set_txpow, /* SIOCSIWTXPOW */ + (iw_handler) lbs_get_txpow, /* SIOCGIWTXPOW */ + (iw_handler) lbs_set_retry, /* SIOCSIWRETRY */ + (iw_handler) lbs_get_retry, /* SIOCGIWRETRY */ + (iw_handler) lbs_set_encode, /* SIOCSIWENCODE */ + (iw_handler) lbs_get_encode, /* SIOCGIWENCODE */ + (iw_handler) lbs_set_power, /* SIOCSIWPOWER */ + (iw_handler) lbs_get_power, /* SIOCGIWPOWER */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) lbs_set_genie, /* SIOCSIWGENIE */ + (iw_handler) lbs_get_genie, /* SIOCGIWGENIE */ + (iw_handler) lbs_set_auth, /* SIOCSIWAUTH */ + (iw_handler) lbs_get_auth, /* SIOCGIWAUTH */ + (iw_handler) lbs_set_encodeext,/* SIOCSIWENCODEEXT */ + (iw_handler) lbs_get_encodeext,/* SIOCGIWENCODEEXT */ + (iw_handler) NULL, /* SIOCSIWPMKSA */ +}; +struct iw_handler_def lbs_handler_def = { + .num_standard = ARRAY_SIZE(lbs_handler), + .standard = (iw_handler *) lbs_handler, + .get_wireless_stats = lbs_get_wireless_stats, +}; + +struct iw_handler_def mesh_handler_def = { + .num_standard = ARRAY_SIZE(mesh_wlan_handler), + .standard = (iw_handler *) mesh_wlan_handler, + .get_wireless_stats = lbs_get_wireless_stats, +}; diff --git a/package/libertas/src/wext.h b/package/libertas/src/wext.h new file mode 100644 index 0000000000..a563d9a231 --- /dev/null +++ b/package/libertas/src/wext.h @@ -0,0 +1,23 @@ +/** + * This file contains definition for IOCTL call. + */ +#ifndef _LBS_WEXT_H_ +#define _LBS_WEXT_H_ + +/** lbs_ioctl_regrdwr */ +struct lbs_ioctl_regrdwr { + /** Which register to access */ + u16 whichreg; + /** Read or Write */ + u16 action; + u32 offset; + u16 NOB; + u32 value; +}; + +#define LBS_MONITOR_OFF 0 + +extern struct iw_handler_def lbs_handler_def; +extern struct iw_handler_def mesh_handler_def; + +#endif |